Eliminated unnecessary complexity by removing support
[citadel.git] / citadel / modules / smtp / serv_smtp.c
1 /*
2  * $Id$
3  *
4  * This module is an SMTP and ESMTP implementation for the Citadel system.
5  * It is compliant with all of the following:
6  *
7  * RFC  821 - Simple Mail Transfer Protocol
8  * RFC  876 - Survey of SMTP Implementations
9  * RFC 1047 - Duplicate messages and SMTP
10  * RFC 1652 - 8 bit MIME
11  * RFC 1869 - Extended Simple Mail Transfer Protocol
12  * RFC 1870 - SMTP Service Extension for Message Size Declaration
13  * RFC 2033 - Local Mail Transfer Protocol
14  * RFC 2197 - SMTP Service Extension for Command Pipelining
15  * RFC 2476 - Message Submission
16  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
17  * RFC 2554 - SMTP Service Extension for Authentication
18  * RFC 2821 - Simple Mail Transfer Protocol
19  * RFC 2822 - Internet Message Format
20  * RFC 2920 - SMTP Service Extension for Command Pipelining
21  *  
22  * The VRFY and EXPN commands have been removed from this implementation
23  * because nobody uses these commands anymore, except for spammers.
24  *
25  */
26
27 #include "sysdep.h"
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <stdio.h>
31 #include <fcntl.h>
32 #include <signal.h>
33 #include <pwd.h>
34 #include <errno.h>
35 #include <sys/types.h>
36 #include <syslog.h>
37
38 #if TIME_WITH_SYS_TIME
39 # include <sys/time.h>
40 # include <time.h>
41 #else
42 # if HAVE_SYS_TIME_H
43 #  include <sys/time.h>
44 # else
45 #  include <time.h>
46 # endif
47 #endif
48
49 #include <sys/wait.h>
50 #include <ctype.h>
51 #include <string.h>
52 #include <limits.h>
53 #include <sys/socket.h>
54 #include <netinet/in.h>
55 #include <arpa/inet.h>
56 #include <libcitadel.h>
57 #include "citadel.h"
58 #include "server.h"
59 #include "citserver.h"
60 #include "support.h"
61 #include "config.h"
62 #include "control.h"
63 #include "room_ops.h"
64 #include "user_ops.h"
65 #include "policy.h"
66 #include "database.h"
67 #include "msgbase.h"
68 #include "internet_addressing.h"
69 #include "genstamp.h"
70 #include "domain.h"
71 #include "clientsocket.h"
72 #include "locate_host.h"
73 #include "citadel_dirs.h"
74
75
76
77 #ifndef HAVE_SNPRINTF
78 #include "snprintf.h"
79 #endif
80
81
82 #include "ctdl_module.h"
83
84
85
86 struct citsmtp {                /* Information about the current session */
87         int command_state;
88         char helo_node[SIZ];
89         char from[SIZ];
90         char recipients[SIZ];
91         int number_of_recipients;
92         int delivery_mode;
93         int message_originated_locally;
94         int is_lmtp;
95         int is_unfiltered;
96         int is_msa;
97 };
98
99 enum {                          /* Command states for login authentication */
100         smtp_command,
101         smtp_user,
102         smtp_password,
103         smtp_plain
104 };
105
106 #define SMTP            ((struct citsmtp *)CC->session_specific_data)
107
108
109 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
110
111
112
113 /*****************************************************************************/
114 /*                      SMTP SERVER (INBOUND) STUFF                          */
115 /*****************************************************************************/
116
117
118 /*
119  * Here's where our SMTP session begins its happy day.
120  */
121 void smtp_greeting(int is_msa)
122 {
123         char message_to_spammer[1024];
124
125         strcpy(CC->cs_clientname, "SMTP session");
126         CC->internal_pgm = 1;
127         CC->cs_flags |= CS_STEALTH;
128         CC->session_specific_data = malloc(sizeof(struct citsmtp));
129         memset(SMTP, 0, sizeof(struct citsmtp));
130         SMTP->is_msa = is_msa;
131
132         /* If this config option is set, reject connections from problem
133          * addresses immediately instead of after they execute a RCPT
134          */
135         if ( (config.c_rbl_at_greeting) && (SMTP->is_msa == 0) ) {
136                 if (rbl_check(message_to_spammer)) {
137                         cprintf("550 %s\r\n", message_to_spammer);
138                         CC->kill_me = 1;
139                         /* no need to free_recipients(valid), it's not allocated yet */
140                         return;
141                 }
142         }
143
144         /* Otherwise we're either clean or we check later. */
145
146         if (CC->nologin==1) {
147                 cprintf("500 Too many users are already online (maximum is %d)\r\n",
148                         config.c_maxsessions
149                 );
150                 CC->kill_me = 1;
151                 /* no need to free_recipients(valid), it's not allocated yet */
152                 return;
153         }
154
155         /* Note: the FQDN *must* appear as the first thing after the 220 code.
156          * Some clients (including citmail.c) depend on it being there.
157          */
158         cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
159 }
160
161
162 /*
163  * SMTPS is just like SMTP, except it goes crypto right away.
164  */
165 void smtps_greeting(void) {
166         CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
167 #ifdef HAVE_OPENSSL
168         if (!CC->redirect_ssl) CC->kill_me = 1;         /* kill session if no crypto */
169 #endif
170         smtp_greeting(0);
171 }
172
173
174 /*
175  * SMTP MSA port requires authentication.
176  */
177 void smtp_msa_greeting(void) {
178         smtp_greeting(1);
179 }
180
181
182 /*
183  * LMTP is like SMTP but with some extra bonus footage added.
184  */
185 void lmtp_greeting(void) {
186         smtp_greeting(0);
187         SMTP->is_lmtp = 1;
188 }
189
190
191 /* 
192  * Generic SMTP MTA greeting
193  */
194 void smtp_mta_greeting(void) {
195         smtp_greeting(0);
196 }
197
198
199 /*
200  * We also have an unfiltered LMTP socket that bypasses spam filters.
201  */
202 void lmtp_unfiltered_greeting(void) {
203         smtp_greeting(0);
204         SMTP->is_lmtp = 1;
205         SMTP->is_unfiltered = 1;
206 }
207
208
209 /*
210  * Login greeting common to all auth methods
211  */
212 void smtp_auth_greeting(void) {
213                 cprintf("235 Hello, %s\r\n", CC->user.fullname);
214                 lprintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
215                 CC->internal_pgm = 0;
216                 CC->cs_flags &= ~CS_STEALTH;
217 }
218
219
220 /*
221  * Implement HELO and EHLO commands.
222  *
223  * which_command:  0=HELO, 1=EHLO, 2=LHLO
224  */
225 void smtp_hello(char *argbuf, int which_command) {
226
227         safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
228
229         if ( (which_command != 2) && (SMTP->is_lmtp) ) {
230                 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
231                 return;
232         }
233
234         if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
235                 cprintf("500 LHLO is only allowed when running LMTP\r\n");
236                 return;
237         }
238
239         if (which_command == 0) {
240                 cprintf("250 Hello %s (%s [%s])\r\n",
241                         SMTP->helo_node,
242                         CC->cs_host,
243                         CC->cs_addr
244                 );
245         }
246         else {
247                 if (which_command == 1) {
248                         cprintf("250-Hello %s (%s [%s])\r\n",
249                                 SMTP->helo_node,
250                                 CC->cs_host,
251                                 CC->cs_addr
252                         );
253                 }
254                 else {
255                         cprintf("250-Greetings and joyous salutations.\r\n");
256                 }
257                 cprintf("250-HELP\r\n");
258                 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
259
260 #ifdef HAVE_OPENSSL
261                 /*
262                  * Offer TLS, but only if TLS is not already active.
263                  * Furthermore, only offer TLS when running on
264                  * the SMTP-MSA port, not on the SMTP-MTA port, due to
265                  * questionable reliability of TLS in certain sending MTA's.
266                  */
267                 if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
268                         cprintf("250-STARTTLS\r\n");
269                 }
270 #endif  /* HAVE_OPENSSL */
271
272                 cprintf("250-AUTH LOGIN PLAIN\r\n"
273                         "250-AUTH=LOGIN PLAIN\r\n"
274                         "250 8BITMIME\r\n"
275                 );
276         }
277 }
278
279
280
281 /*
282  * Implement HELP command.
283  */
284 void smtp_help(void) {
285         cprintf("214-Commands accepted:\r\n");
286         cprintf("214-    DATA\r\n");
287         cprintf("214-    EHLO\r\n");
288         cprintf("214-    HELO\r\n");
289         cprintf("214-    HELP\r\n");
290         cprintf("214-    MAIL\r\n");
291         cprintf("214-    NOOP\r\n");
292         cprintf("214-    QUIT\r\n");
293         cprintf("214-    RCPT\r\n");
294         cprintf("214-    RSET\r\n");
295         cprintf("214     \r\n");
296 }
297
298
299 /*
300  *
301  */
302 void smtp_get_user(char *argbuf) {
303         char buf[SIZ];
304         char username[SIZ];
305
306         CtdlDecodeBase64(username, argbuf, SIZ);
307         /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
308         if (CtdlLoginExistingUser(NULL, username) == login_ok) {
309                 CtdlEncodeBase64(buf, "Password:", 9, 0);
310                 cprintf("334 %s\r\n", buf);
311                 SMTP->command_state = smtp_password;
312         }
313         else {
314                 cprintf("500 No such user.\r\n");
315                 SMTP->command_state = smtp_command;
316         }
317 }
318
319
320 /*
321  *
322  */
323 void smtp_get_pass(char *argbuf) {
324         char password[SIZ];
325
326         CtdlDecodeBase64(password, argbuf, SIZ);
327         /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
328         if (CtdlTryPassword(password) == pass_ok) {
329                 smtp_auth_greeting();
330         }
331         else {
332                 cprintf("535 Authentication failed.\r\n");
333         }
334         SMTP->command_state = smtp_command;
335 }
336
337
338 /*
339  * Back end for PLAIN auth method (either inline or multistate)
340  */
341 void smtp_try_plain(char *encoded_authstring) {
342         char decoded_authstring[1024];
343         char ident[256];
344         char user[256];
345         char pass[256];
346         int result;
347
348         CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
349         safestrncpy(ident, decoded_authstring, sizeof ident);
350         safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
351         safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
352
353         SMTP->command_state = smtp_command;
354
355         if (!IsEmptyStr(ident)) {
356                 result = CtdlLoginExistingUser(user, ident);
357         }
358         else {
359                 result = CtdlLoginExistingUser(NULL, user);
360         }
361
362         if (result == login_ok) {
363                 if (CtdlTryPassword(pass) == pass_ok) {
364                         smtp_auth_greeting();
365                         return;
366                 }
367         }
368         cprintf("504 Authentication failed.\r\n");
369 }
370
371
372 /*
373  * Attempt to perform authenticated SMTP
374  */
375 void smtp_auth(char *argbuf) {
376         char username_prompt[64];
377         char method[64];
378         char encoded_authstring[1024];
379
380         if (CC->logged_in) {
381                 cprintf("504 Already logged in.\r\n");
382                 return;
383         }
384
385         extract_token(method, argbuf, 0, ' ', sizeof method);
386
387         if (!strncasecmp(method, "login", 5) ) {
388                 if (strlen(argbuf) >= 7) {
389                         smtp_get_user(&argbuf[6]);
390                 }
391                 else {
392                         CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
393                         cprintf("334 %s\r\n", username_prompt);
394                         SMTP->command_state = smtp_user;
395                 }
396                 return;
397         }
398
399         if (!strncasecmp(method, "plain", 5) ) {
400                 if (num_tokens(argbuf, ' ') < 2) {
401                         cprintf("334 \r\n");
402                         SMTP->command_state = smtp_plain;
403                         return;
404                 }
405
406                 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
407
408                 smtp_try_plain(encoded_authstring);
409                 return;
410         }
411
412         if (strncasecmp(method, "login", 5) ) {
413                 cprintf("504 Unknown authentication method.\r\n");
414                 return;
415         }
416
417 }
418
419
420 /*
421  * Implements the RSET (reset state) command.
422  * Currently this just zeroes out the state buffer.  If pointers to data
423  * allocated with malloc() are ever placed in the state buffer, we have to
424  * be sure to free() them first!
425  *
426  * Set do_response to nonzero to output the SMTP RSET response code.
427  */
428 void smtp_rset(int do_response) {
429         int is_lmtp;
430         int is_unfiltered;
431
432         /*
433          * Our entire SMTP state is discarded when a RSET command is issued,
434          * but we need to preserve this one little piece of information, so
435          * we save it for later.
436          */
437         is_lmtp = SMTP->is_lmtp;
438         is_unfiltered = SMTP->is_unfiltered;
439
440         memset(SMTP, 0, sizeof(struct citsmtp));
441
442         /*
443          * It is somewhat ambiguous whether we want to log out when a RSET
444          * command is issued.  Here's the code to do it.  It is commented out
445          * because some clients (such as Pine) issue RSET commands before
446          * each message, but still expect to be logged in.
447          *
448          * if (CC->logged_in) {
449          *      logout(CC);
450          * }
451          */
452
453         /*
454          * Reinstate this little piece of information we saved (see above).
455          */
456         SMTP->is_lmtp = is_lmtp;
457         SMTP->is_unfiltered = is_unfiltered;
458
459         if (do_response) {
460                 cprintf("250 Zap!\r\n");
461         }
462 }
463
464 /*
465  * Clear out the portions of the state buffer that need to be cleared out
466  * after the DATA command finishes.
467  */
468 void smtp_data_clear(void) {
469         strcpy(SMTP->from, "");
470         strcpy(SMTP->recipients, "");
471         SMTP->number_of_recipients = 0;
472         SMTP->delivery_mode = 0;
473         SMTP->message_originated_locally = 0;
474 }
475
476 const char *smtp_get_Recipients(void)
477 {
478         if (SMTP == NULL)
479                 return NULL;
480         else return SMTP->from;
481
482 }
483
484 /*
485  * Implements the "MAIL From:" command
486  */
487 void smtp_mail(char *argbuf) {
488         char user[SIZ];
489         char node[SIZ];
490         char name[SIZ];
491
492         if (!IsEmptyStr(SMTP->from)) {
493                 cprintf("503 Only one sender permitted\r\n");
494                 return;
495         }
496
497         if (strncasecmp(argbuf, "From:", 5)) {
498                 cprintf("501 Syntax error\r\n");
499                 return;
500         }
501
502         strcpy(SMTP->from, &argbuf[5]);
503         striplt(SMTP->from);
504         if (haschar(SMTP->from, '<') > 0) {
505                 stripallbut(SMTP->from, '<', '>');
506         }
507
508         /* We used to reject empty sender names, until it was brought to our
509          * attention that RFC1123 5.2.9 requires that this be allowed.  So now
510          * we allow it, but replace the empty string with a fake
511          * address so we don't have to contend with the empty string causing
512          * other code to fail when it's expecting something there.
513          */
514         if (IsEmptyStr(SMTP->from)) {
515                 strcpy(SMTP->from, "someone@somewhere.org");
516         }
517
518         /* If this SMTP connection is from a logged-in user, force the 'from'
519          * to be the user's Internet e-mail address as Citadel knows it.
520          */
521         if (CC->logged_in) {
522                 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
523                 cprintf("250 Sender ok <%s>\r\n", SMTP->from);
524                 SMTP->message_originated_locally = 1;
525                 return;
526         }
527
528         else if (SMTP->is_lmtp) {
529                 /* Bypass forgery checking for LMTP */
530         }
531
532         /* Otherwise, make sure outsiders aren't trying to forge mail from
533          * this system (unless, of course, c_allow_spoofing is enabled)
534          */
535         else if (config.c_allow_spoofing == 0) {
536                 process_rfc822_addr(SMTP->from, user, node, name);
537                 if (CtdlHostAlias(node) != hostalias_nomatch) {
538                         cprintf("550 You must log in to send mail from %s\r\n", node);
539                         strcpy(SMTP->from, "");
540                         return;
541                 }
542         }
543
544         cprintf("250 Sender ok\r\n");
545 }
546
547
548
549 /*
550  * Implements the "RCPT To:" command
551  */
552 void smtp_rcpt(char *argbuf) {
553         char recp[1024];
554         char message_to_spammer[SIZ];
555         struct recptypes *valid = NULL;
556
557         if (IsEmptyStr(SMTP->from)) {
558                 cprintf("503 Need MAIL before RCPT\r\n");
559                 return;
560         }
561
562         if (strncasecmp(argbuf, "To:", 3)) {
563                 cprintf("501 Syntax error\r\n");
564                 return;
565         }
566
567         if ( (SMTP->is_msa) && (!CC->logged_in) ) {
568                 cprintf("550 You must log in to send mail on this port.\r\n");
569                 strcpy(SMTP->from, "");
570                 return;
571         }
572
573         safestrncpy(recp, &argbuf[3], sizeof recp);
574         striplt(recp);
575         stripallbut(recp, '<', '>');
576
577         if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
578                 cprintf("452 Too many recipients\r\n");
579                 return;
580         }
581
582         /* RBL check */
583         if ( (!CC->logged_in)   /* Don't RBL authenticated users */
584            && (!SMTP->is_lmtp) ) {      /* Don't RBL LMTP clients */
585                 if (config.c_rbl_at_greeting == 0) {    /* Don't RBL again if we already did it */
586                         if (rbl_check(message_to_spammer)) {
587                                 cprintf("550 %s\r\n", message_to_spammer);
588                                 /* no need to free_recipients(valid), it's not allocated yet */
589                                 return;
590                         }
591                 }
592         }
593
594         valid = validate_recipients(recp, 
595                                     smtp_get_Recipients (),
596                                     (CC->logged_in)? POST_LOGGED_IN:POST_EXTERNAL);
597         if (valid->num_error != 0) {
598                 cprintf("599 Error: %s\r\n", valid->errormsg);
599                 free_recipients(valid);
600                 return;
601         }
602
603         if (valid->num_internet > 0) {
604                 if (CC->logged_in) {
605                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
606                                 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", recp);
607                                 free_recipients(valid);
608                                 return;
609                         }
610                 }
611         }
612
613         if (valid->num_internet > 0) {
614                 if ( (SMTP->message_originated_locally == 0)
615                    && (SMTP->is_lmtp == 0) ) {
616                         cprintf("551 <%s> - relaying denied\r\n", recp);
617                         free_recipients(valid);
618                         return;
619                 }
620         }
621
622         cprintf("250 RCPT ok <%s>\r\n", recp);
623         if (!IsEmptyStr(SMTP->recipients)) {
624                 strcat(SMTP->recipients, ",");
625         }
626         strcat(SMTP->recipients, recp);
627         SMTP->number_of_recipients += 1;
628         if (valid != NULL)  {
629                 free_recipients(valid);
630         }
631 }
632
633
634
635
636 /*
637  * Implements the DATA command
638  */
639 void smtp_data(void) {
640         char *body;
641         struct CtdlMessage *msg = NULL;
642         long msgnum = (-1L);
643         char nowstamp[SIZ];
644         struct recptypes *valid;
645         int scan_errors;
646         int i;
647         char result[SIZ];
648
649         if (IsEmptyStr(SMTP->from)) {
650                 cprintf("503 Need MAIL command first.\r\n");
651                 return;
652         }
653
654         if (SMTP->number_of_recipients < 1) {
655                 cprintf("503 Need RCPT command first.\r\n");
656                 return;
657         }
658
659         cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
660         
661         datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
662         body = malloc(4096);
663
664         if (body != NULL) snprintf(body, 4096,
665                 "Received: from %s (%s [%s])\n"
666                 "       by %s; %s\n",
667                         SMTP->helo_node,
668                         CC->cs_host,
669                         CC->cs_addr,
670                         config.c_fqdn,
671                         nowstamp);
672         
673         body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1, 0);
674         if (body == NULL) {
675                 cprintf("550 Unable to save message: internal error.\r\n");
676                 return;
677         }
678
679         lprintf(CTDL_DEBUG, "Converting message...\n");
680         msg = convert_internet_message(body);
681
682         /* If the user is locally authenticated, FORCE the From: header to
683          * show up as the real sender.  Yes, this violates the RFC standard,
684          * but IT MAKES SENSE.  If you prefer strict RFC adherence over
685          * common sense, you can disable this in the configuration.
686          *
687          * We also set the "message room name" ('O' field) to MAILROOM
688          * (which is Mail> on most systems) to prevent it from getting set
689          * to something ugly like "0000058008.Sent Items>" when the message
690          * is read with a Citadel client.
691          */
692         if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
693                 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
694                 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
695                 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
696                 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
697                 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
698                 msg->cm_fields['A'] = strdup(CC->user.fullname);
699                 msg->cm_fields['N'] = strdup(config.c_nodename);
700                 msg->cm_fields['H'] = strdup(config.c_humannode);
701                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
702                 msg->cm_fields['O'] = strdup(MAILROOM);
703         }
704
705         /* Set the "envelope from" address */
706         if (msg->cm_fields['P'] != NULL) {
707                 free(msg->cm_fields['P']);
708         }
709         msg->cm_fields['P'] = strdup(SMTP->from);
710
711         /* Set the "envelope to" address */
712         if (msg->cm_fields['V'] != NULL) {
713                 free(msg->cm_fields['V']);
714         }
715         msg->cm_fields['V'] = strdup(SMTP->recipients);
716
717         /* Submit the message into the Citadel system. */
718         valid = validate_recipients(SMTP->recipients, 
719                                     smtp_get_Recipients (),
720                                     (CC->logged_in)? POST_LOGGED_IN:POST_EXTERNAL);
721
722         /* If there are modules that want to scan this message before final
723          * submission (such as virus checkers or spam filters), call them now
724          * and give them an opportunity to reject the message.
725          */
726         if (SMTP->is_unfiltered) {
727                 scan_errors = 0;
728         }
729         else {
730                 scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
731         }
732
733         if (scan_errors > 0) {  /* We don't want this message! */
734
735                 if (msg->cm_fields['0'] == NULL) {
736                         msg->cm_fields['0'] = strdup(
737                                 "5.7.1 Message rejected by filter");
738                 }
739
740                 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
741         }
742         
743         else {                  /* Ok, we'll accept this message. */
744                 msgnum = CtdlSubmitMsg(msg, valid, "");
745                 if (msgnum > 0L) {
746                         sprintf(result, "250 2.0.0 Message accepted.\r\n");
747                 }
748                 else {
749                         sprintf(result, "550 5.5.0 Internal delivery error\r\n");
750                 }
751         }
752
753         /* For SMTP and ESTMP, just print the result message.  For LMTP, we
754          * have to print one result message for each recipient.  Since there
755          * is nothing in Citadel which would cause different recipients to
756          * have different results, we can get away with just spitting out the
757          * same message once for each recipient.
758          */
759         if (SMTP->is_lmtp) {
760                 for (i=0; i<SMTP->number_of_recipients; ++i) {
761                         cprintf("%s", result);
762                 }
763         }
764         else {
765                 cprintf("%s", result);
766         }
767
768         /* Write something to the syslog (which may or may not be where the
769          * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
770          */
771         if (enable_syslog) {
772                 syslog((LOG_MAIL | LOG_INFO),
773                         "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
774                         msgnum,
775                         SMTP->from,
776                         SMTP->number_of_recipients,
777                         CC->cs_host,
778                         CC->cs_addr,
779                         result
780                 );
781         }
782
783         /* Clean up */
784         CtdlFreeMessage(msg);
785         free_recipients(valid);
786         smtp_data_clear();      /* clear out the buffers now */
787 }
788
789
790 /*
791  * implements the STARTTLS command (Citadel API version)
792  */
793 void smtp_starttls(void)
794 {
795         char ok_response[SIZ];
796         char nosup_response[SIZ];
797         char error_response[SIZ];
798
799         sprintf(ok_response,
800                 "220 2.0.0 Begin TLS negotiation now\r\n");
801         sprintf(nosup_response,
802                 "554 5.7.3 TLS not supported here\r\n");
803         sprintf(error_response,
804                 "554 5.7.3 Internal error\r\n");
805         CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
806         smtp_rset(0);
807 }
808
809
810
811 /* 
812  * Main command loop for SMTP sessions.
813  */
814 void smtp_command_loop(void) {
815         char cmdbuf[SIZ];
816
817         time(&CC->lastcmd);
818         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
819         if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
820                 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
821                 CC->kill_me = 1;
822                 return;
823         }
824         lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
825         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
826
827         if (SMTP->command_state == smtp_user) {
828                 smtp_get_user(cmdbuf);
829         }
830
831         else if (SMTP->command_state == smtp_password) {
832                 smtp_get_pass(cmdbuf);
833         }
834
835         else if (SMTP->command_state == smtp_plain) {
836                 smtp_try_plain(cmdbuf);
837         }
838
839         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
840                 smtp_auth(&cmdbuf[5]);
841         }
842
843         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
844                 smtp_data();
845         }
846
847         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
848                 smtp_hello(&cmdbuf[5], 0);
849         }
850
851         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
852                 smtp_hello(&cmdbuf[5], 1);
853         }
854
855         else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
856                 smtp_hello(&cmdbuf[5], 2);
857         }
858
859         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
860                 smtp_help();
861         }
862
863         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
864                 smtp_mail(&cmdbuf[5]);
865         }
866
867         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
868                 cprintf("250 NOOP\r\n");
869         }
870
871         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
872                 cprintf("221 Goodbye...\r\n");
873                 CC->kill_me = 1;
874                 return;
875         }
876
877         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
878                 smtp_rcpt(&cmdbuf[5]);
879         }
880
881         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
882                 smtp_rset(1);
883         }
884 #ifdef HAVE_OPENSSL
885         else if (!strcasecmp(cmdbuf, "STARTTLS")) {
886                 smtp_starttls();
887         }
888 #endif
889         else {
890                 cprintf("502 I'm afraid I can't do that.\r\n");
891         }
892
893
894 }
895
896
897
898
899 /*****************************************************************************/
900 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
901 /*****************************************************************************/
902
903
904
905 /*
906  * smtp_try()
907  *
908  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
909  *
910  */
911 void smtp_try(const char *key, const char *addr, int *status,
912               char *dsn, size_t n, long msgnum)
913 {
914         int sock = (-1);
915         char mxhosts[1024];
916         int num_mxhosts;
917         int mx;
918         int i;
919         char user[1024], node[1024], name[1024];
920         char buf[1024];
921         char mailfrom[1024];
922         char mx_user[256];
923         char mx_pass[256];
924         char mx_host[256];
925         char mx_port[256];
926         int lp, rp;
927         char *msgtext;
928         char *ptr;
929         size_t msg_size;
930         int scan_done;
931
932         /* Parse out the host portion of the recipient address */
933         process_rfc822_addr(addr, user, node, name);
934
935         lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
936                 user, node, name);
937
938         /* Load the message out of the database */
939         CC->redirect_buffer = malloc(SIZ);
940         CC->redirect_len = 0;
941         CC->redirect_alloc = SIZ;
942         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
943         msgtext = CC->redirect_buffer;
944         msg_size = CC->redirect_len;
945         CC->redirect_buffer = NULL;
946         CC->redirect_len = 0;
947         CC->redirect_alloc = 0;
948
949         /* Extract something to send later in the 'MAIL From:' command */
950         strcpy(mailfrom, "");
951         scan_done = 0;
952         ptr = msgtext;
953         do {
954                 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
955                         scan_done = 1;
956                 }
957                 if (!strncasecmp(buf, "From:", 5)) {
958                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
959                         striplt(mailfrom);
960                         for (i=0; mailfrom[i]; ++i) {
961                                 if (!isprint(mailfrom[i])) {
962                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
963                                         i=0;
964                                 }
965                         }
966
967                         /* Strip out parenthesized names */
968                         lp = (-1);
969                         rp = (-1);
970                         for (i=0; mailfrom[i]; ++i) {
971                                 if (mailfrom[i] == '(') lp = i;
972                                 if (mailfrom[i] == ')') rp = i;
973                         }
974                         if ((lp>0)&&(rp>lp)) {
975                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
976                         }
977
978                         /* Prefer brokketized names */
979                         lp = (-1);
980                         rp = (-1);
981                         for (i=0; mailfrom[i]; ++i) {
982                                 if (mailfrom[i] == '<') lp = i;
983                                 if (mailfrom[i] == '>') rp = i;
984                         }
985                         if ( (lp>=0) && (rp>lp) ) {
986                                 mailfrom[rp] = 0;
987                                 strcpy(mailfrom, &mailfrom[lp]);
988                         }
989
990                         scan_done = 1;
991                 }
992         } while (scan_done == 0);
993         if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
994         stripallbut(mailfrom, '<', '>');
995
996         /* Figure out what mail exchanger host we have to connect to */
997         num_mxhosts = getmx(mxhosts, node);
998         lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
999         if (num_mxhosts < 1) {
1000                 *status = 5;
1001                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1002                 return;
1003         }
1004
1005         sock = (-1);
1006         for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1007                 char *endpart;
1008                 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1009                 strcpy(mx_user, "");
1010                 strcpy(mx_pass, "");
1011                 if (num_tokens(buf, '@') > 1) {
1012                         strcpy (mx_user, buf);
1013                         endpart = strrchr(mx_user, '@');
1014                         *endpart = '\0';
1015                         strcpy (mx_host, endpart + 1);
1016                         endpart = strrchr(mx_user, ':');
1017                         if (endpart != NULL) {
1018                                 strcpy(mx_pass, endpart+1);
1019                                 *endpart = '\0';
1020                         }
1021                 }
1022                 else
1023                         strcpy (mx_host, buf);
1024                 endpart = strrchr(mx_host, ':');
1025                 if (endpart != 0){
1026                         *endpart = '\0';
1027                         strcpy(mx_port, endpart + 1);
1028                 }               
1029                 else {
1030                         strcpy(mx_port, "25");
1031                 }
1032                 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1033                 sock = sock_connect(mx_host, mx_port, "tcp");
1034                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1035                 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1036                 if (sock < 0) {
1037                         if (errno > 0) {
1038                                 snprintf(dsn, SIZ, "%s", strerror(errno));
1039                         }
1040                         else {
1041                                 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1042                         }
1043                 }
1044         }
1045
1046         if (sock < 0) {
1047                 *status = 4;    /* dsn is already filled in */
1048                 return;
1049         }
1050
1051         /* Process the SMTP greeting from the server */
1052         if (ml_sock_gets(sock, buf) < 0) {
1053                 *status = 4;
1054                 strcpy(dsn, "Connection broken during SMTP conversation");
1055                 goto bail;
1056         }
1057         lprintf(CTDL_DEBUG, "<%s\n", buf);
1058         if (buf[0] != '2') {
1059                 if (buf[0] == '4') {
1060                         *status = 4;
1061                         safestrncpy(dsn, &buf[4], 1023);
1062                         goto bail;
1063                 }
1064                 else {
1065                         *status = 5;
1066                         safestrncpy(dsn, &buf[4], 1023);
1067                         goto bail;
1068                 }
1069         }
1070
1071         /* At this point we know we are talking to a real SMTP server */
1072
1073         /* Do a EHLO command.  If it fails, try the HELO command. */
1074         snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1075         lprintf(CTDL_DEBUG, ">%s", buf);
1076         sock_write(sock, buf, strlen(buf));
1077         if (ml_sock_gets(sock, buf) < 0) {
1078                 *status = 4;
1079                 strcpy(dsn, "Connection broken during SMTP HELO");
1080                 goto bail;
1081         }
1082         lprintf(CTDL_DEBUG, "<%s\n", buf);
1083         if (buf[0] != '2') {
1084                 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1085                 lprintf(CTDL_DEBUG, ">%s", buf);
1086                 sock_write(sock, buf, strlen(buf));
1087                 if (ml_sock_gets(sock, buf) < 0) {
1088                         *status = 4;
1089                         strcpy(dsn, "Connection broken during SMTP HELO");
1090                         goto bail;
1091                 }
1092         }
1093         if (buf[0] != '2') {
1094                 if (buf[0] == '4') {
1095                         *status = 4;
1096                         safestrncpy(dsn, &buf[4], 1023);
1097                         goto bail;
1098                 }
1099                 else {
1100                         *status = 5;
1101                         safestrncpy(dsn, &buf[4], 1023);
1102                         goto bail;
1103                 }
1104         }
1105
1106         /* Do an AUTH command if necessary */
1107         if (!IsEmptyStr(mx_user)) {
1108                 char encoded[1024];
1109                 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1110                 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1111                 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1112                 lprintf(CTDL_DEBUG, ">%s", buf);
1113                 sock_write(sock, buf, strlen(buf));
1114                 if (ml_sock_gets(sock, buf) < 0) {
1115                         *status = 4;
1116                         strcpy(dsn, "Connection broken during SMTP AUTH");
1117                         goto bail;
1118                 }
1119                 lprintf(CTDL_DEBUG, "<%s\n", buf);
1120                 if (buf[0] != '2') {
1121                         if (buf[0] == '4') {
1122                                 *status = 4;
1123                                 safestrncpy(dsn, &buf[4], 1023);
1124                                 goto bail;
1125                         }
1126                         else {
1127                                 *status = 5;
1128                                 safestrncpy(dsn, &buf[4], 1023);
1129                                 goto bail;
1130                         }
1131                 }
1132         }
1133
1134         /* previous command succeeded, now try the MAIL From: command */
1135         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1136         lprintf(CTDL_DEBUG, ">%s", buf);
1137         sock_write(sock, buf, strlen(buf));
1138         if (ml_sock_gets(sock, buf) < 0) {
1139                 *status = 4;
1140                 strcpy(dsn, "Connection broken during SMTP MAIL");
1141                 goto bail;
1142         }
1143         lprintf(CTDL_DEBUG, "<%s\n", buf);
1144         if (buf[0] != '2') {
1145                 if (buf[0] == '4') {
1146                         *status = 4;
1147                         safestrncpy(dsn, &buf[4], 1023);
1148                         goto bail;
1149                 }
1150                 else {
1151                         *status = 5;
1152                         safestrncpy(dsn, &buf[4], 1023);
1153                         goto bail;
1154                 }
1155         }
1156
1157         /* MAIL succeeded, now try the RCPT To: command */
1158         snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1159         lprintf(CTDL_DEBUG, ">%s", buf);
1160         sock_write(sock, buf, strlen(buf));
1161         if (ml_sock_gets(sock, buf) < 0) {
1162                 *status = 4;
1163                 strcpy(dsn, "Connection broken during SMTP RCPT");
1164                 goto bail;
1165         }
1166         lprintf(CTDL_DEBUG, "<%s\n", buf);
1167         if (buf[0] != '2') {
1168                 if (buf[0] == '4') {
1169                         *status = 4;
1170                         safestrncpy(dsn, &buf[4], 1023);
1171                         goto bail;
1172                 }
1173                 else {
1174                         *status = 5;
1175                         safestrncpy(dsn, &buf[4], 1023);
1176                         goto bail;
1177                 }
1178         }
1179
1180         /* RCPT succeeded, now try the DATA command */
1181         lprintf(CTDL_DEBUG, ">DATA\n");
1182         sock_write(sock, "DATA\r\n", 6);
1183         if (ml_sock_gets(sock, buf) < 0) {
1184                 *status = 4;
1185                 strcpy(dsn, "Connection broken during SMTP DATA");
1186                 goto bail;
1187         }
1188         lprintf(CTDL_DEBUG, "<%s\n", buf);
1189         if (buf[0] != '3') {
1190                 if (buf[0] == '4') {
1191                         *status = 3;
1192                         safestrncpy(dsn, &buf[4], 1023);
1193                         goto bail;
1194                 }
1195                 else {
1196                         *status = 5;
1197                         safestrncpy(dsn, &buf[4], 1023);
1198                         goto bail;
1199                 }
1200         }
1201
1202         /* If we reach this point, the server is expecting data */
1203         sock_write(sock, msgtext, msg_size);
1204         if (msgtext[msg_size-1] != 10) {
1205                 lprintf(CTDL_WARNING, "Possible problem: message did not "
1206                         "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1207                                 buf[msg_size-1]);
1208         }
1209
1210         sock_write(sock, ".\r\n", 3);
1211         if (ml_sock_gets(sock, buf) < 0) {
1212                 *status = 4;
1213                 strcpy(dsn, "Connection broken during SMTP message transmit");
1214                 goto bail;
1215         }
1216         lprintf(CTDL_DEBUG, "%s\n", buf);
1217         if (buf[0] != '2') {
1218                 if (buf[0] == '4') {
1219                         *status = 4;
1220                         safestrncpy(dsn, &buf[4], 1023);
1221                         goto bail;
1222                 }
1223                 else {
1224                         *status = 5;
1225                         safestrncpy(dsn, &buf[4], 1023);
1226                         goto bail;
1227                 }
1228         }
1229
1230         /* We did it! */
1231         safestrncpy(dsn, &buf[4], 1023);
1232         *status = 2;
1233
1234         lprintf(CTDL_DEBUG, ">QUIT\n");
1235         sock_write(sock, "QUIT\r\n", 6);
1236         ml_sock_gets(sock, buf);
1237         lprintf(CTDL_DEBUG, "<%s\n", buf);
1238         lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1239                 user, node, name);
1240
1241 bail:   free(msgtext);
1242         sock_close(sock);
1243
1244         /* Write something to the syslog (which may or may not be where the
1245          * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1246          */
1247         if (enable_syslog) {
1248                 syslog((LOG_MAIL | LOG_INFO),
1249                         "%ld: to=<%s>, relay=%s, stat=%s",
1250                         msgnum,
1251                         addr,
1252                         mx_host,
1253                         dsn
1254                 );
1255         }
1256
1257         return;
1258 }
1259
1260
1261
1262 /*
1263  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1264  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1265  * a "bounce" message (delivery status notification).
1266  */
1267 void smtp_do_bounce(char *instr) {
1268         int i;
1269         int lines;
1270         int status;
1271         char buf[1024];
1272         char key[1024];
1273         char addr[1024];
1274         char dsn[1024];
1275         char bounceto[1024];
1276         char boundary[64];
1277         int num_bounces = 0;
1278         int bounce_this = 0;
1279         long bounce_msgid = (-1);
1280         time_t submitted = 0L;
1281         struct CtdlMessage *bmsg = NULL;
1282         int give_up = 0;
1283         struct recptypes *valid;
1284         int successful_bounce = 0;
1285         static int seq = 0;
1286         char *omsgtext;
1287         size_t omsgsize;
1288         long omsgid = (-1);
1289
1290         lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1291         strcpy(bounceto, "");
1292         sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1293         lines = num_tokens(instr, '\n');
1294
1295         /* See if it's time to give up on delivery of this message */
1296         for (i=0; i<lines; ++i) {
1297                 extract_token(buf, instr, i, '\n', sizeof buf);
1298                 extract_token(key, buf, 0, '|', sizeof key);
1299                 extract_token(addr, buf, 1, '|', sizeof addr);
1300                 if (!strcasecmp(key, "submitted")) {
1301                         submitted = atol(addr);
1302                 }
1303         }
1304
1305         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1306                 give_up = 1;
1307         }
1308
1309         /* Start building our bounce message */
1310
1311         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1312         if (bmsg == NULL) return;
1313         memset(bmsg, 0, sizeof(struct CtdlMessage));
1314
1315         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1316         bmsg->cm_anon_type = MES_NORMAL;
1317         bmsg->cm_format_type = FMT_RFC822;
1318         bmsg->cm_fields['A'] = strdup("Citadel");
1319         bmsg->cm_fields['O'] = strdup(MAILROOM);
1320         bmsg->cm_fields['N'] = strdup(config.c_nodename);
1321         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1322         bmsg->cm_fields['M'] = malloc(1024);
1323
1324         strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1325         strcat(bmsg->cm_fields['M'], boundary);
1326         strcat(bmsg->cm_fields['M'], "\"\r\n");
1327         strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1328         strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1329         strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1330         strcat(bmsg->cm_fields['M'], "--");
1331         strcat(bmsg->cm_fields['M'], boundary);
1332         strcat(bmsg->cm_fields['M'], "\r\n");
1333         strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1334
1335         if (give_up) strcat(bmsg->cm_fields['M'],
1336 "A message you sent could not be delivered to some or all of its recipients\n"
1337 "due to prolonged unavailability of its destination(s).\n"
1338 "Giving up on the following addresses:\n\n"
1339 );
1340
1341         else strcat(bmsg->cm_fields['M'],
1342 "A message you sent could not be delivered to some or all of its recipients.\n"
1343 "The following addresses were undeliverable:\n\n"
1344 );
1345
1346         /*
1347          * Now go through the instructions checking for stuff.
1348          */
1349         for (i=0; i<lines; ++i) {
1350                 extract_token(buf, instr, i, '\n', sizeof buf);
1351                 extract_token(key, buf, 0, '|', sizeof key);
1352                 extract_token(addr, buf, 1, '|', sizeof addr);
1353                 status = extract_int(buf, 2);
1354                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1355                 bounce_this = 0;
1356
1357                 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1358                         key, addr, status, dsn);
1359
1360                 if (!strcasecmp(key, "bounceto")) {
1361                         strcpy(bounceto, addr);
1362                 }
1363
1364                 if (!strcasecmp(key, "msgid")) {
1365                         omsgid = atol(addr);
1366                 }
1367
1368                 if (!strcasecmp(key, "remote")) {
1369                         if (status == 5) bounce_this = 1;
1370                         if (give_up) bounce_this = 1;
1371                 }
1372
1373                 if (bounce_this) {
1374                         ++num_bounces;
1375
1376                         if (bmsg->cm_fields['M'] == NULL) {
1377                                 lprintf(CTDL_ERR, "ERROR ... M field is null "
1378                                         "(%s:%d)\n", __FILE__, __LINE__);
1379                         }
1380
1381                         bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1382                                 strlen(bmsg->cm_fields['M']) + 1024 );
1383                         strcat(bmsg->cm_fields['M'], addr);
1384                         strcat(bmsg->cm_fields['M'], ": ");
1385                         strcat(bmsg->cm_fields['M'], dsn);
1386                         strcat(bmsg->cm_fields['M'], "\r\n");
1387
1388                         remove_token(instr, i, '\n');
1389                         --i;
1390                         --lines;
1391                 }
1392         }
1393
1394         /* Attach the original message */
1395         if (omsgid >= 0) {
1396                 strcat(bmsg->cm_fields['M'], "--");
1397                 strcat(bmsg->cm_fields['M'], boundary);
1398                 strcat(bmsg->cm_fields['M'], "\r\n");
1399                 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1400                 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1401                 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1402                 strcat(bmsg->cm_fields['M'], "\r\n");
1403         
1404                 CC->redirect_buffer = malloc(SIZ);
1405                 CC->redirect_len = 0;
1406                 CC->redirect_alloc = SIZ;
1407                 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1408                 omsgtext = CC->redirect_buffer;
1409                 omsgsize = CC->redirect_len;
1410                 CC->redirect_buffer = NULL;
1411                 CC->redirect_len = 0;
1412                 CC->redirect_alloc = 0;
1413                 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1414                                 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1415                 strcat(bmsg->cm_fields['M'], omsgtext);
1416                 free(omsgtext);
1417         }
1418
1419         /* Close the multipart MIME scope */
1420         strcat(bmsg->cm_fields['M'], "--");
1421         strcat(bmsg->cm_fields['M'], boundary);
1422         strcat(bmsg->cm_fields['M'], "--\r\n");
1423
1424         /* Deliver the bounce if there's anything worth mentioning */
1425         lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1426         if (num_bounces > 0) {
1427
1428                 /* First try the user who sent the message */
1429                 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1430                 if (IsEmptyStr(bounceto)) {
1431                         lprintf(CTDL_ERR, "No bounce address specified\n");
1432                         bounce_msgid = (-1L);
1433                 }
1434
1435                 /* Can we deliver the bounce to the original sender? */
1436                 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1437                 if (valid != NULL) {
1438                         if (valid->num_error == 0) {
1439                                 CtdlSubmitMsg(bmsg, valid, "");
1440                                 successful_bounce = 1;
1441                         }
1442                 }
1443
1444                 /* If not, post it in the Aide> room */
1445                 if (successful_bounce == 0) {
1446                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1447                 }
1448
1449                 /* Free up the memory we used */
1450                 if (valid != NULL) {
1451                         free_recipients(valid);
1452                 }
1453         }
1454
1455         CtdlFreeMessage(bmsg);
1456         lprintf(CTDL_DEBUG, "Done processing bounces\n");
1457 }
1458
1459
1460 /*
1461  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1462  * set of delivery instructions for completed deliveries and remove them.
1463  *
1464  * It returns the number of incomplete deliveries remaining.
1465  */
1466 int smtp_purge_completed_deliveries(char *instr) {
1467         int i;
1468         int lines;
1469         int status;
1470         char buf[1024];
1471         char key[1024];
1472         char addr[1024];
1473         char dsn[1024];
1474         int completed;
1475         int incomplete = 0;
1476
1477         lines = num_tokens(instr, '\n');
1478         for (i=0; i<lines; ++i) {
1479                 extract_token(buf, instr, i, '\n', sizeof buf);
1480                 extract_token(key, buf, 0, '|', sizeof key);
1481                 extract_token(addr, buf, 1, '|', sizeof addr);
1482                 status = extract_int(buf, 2);
1483                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1484
1485                 completed = 0;
1486
1487                 if (!strcasecmp(key, "remote")) {
1488                         if (status == 2) completed = 1;
1489                         else ++incomplete;
1490                 }
1491
1492                 if (completed) {
1493                         remove_token(instr, i, '\n');
1494                         --i;
1495                         --lines;
1496                 }
1497         }
1498
1499         return(incomplete);
1500 }
1501
1502
1503 /*
1504  * smtp_do_procmsg()
1505  *
1506  * Called by smtp_do_queue() to handle an individual message.
1507  */
1508 void smtp_do_procmsg(long msgnum, void *userdata) {
1509         struct CtdlMessage *msg = NULL;
1510         char *instr = NULL;
1511         char *results = NULL;
1512         int i;
1513         int lines;
1514         int status;
1515         char buf[1024];
1516         char key[1024];
1517         char addr[1024];
1518         char dsn[1024];
1519         long text_msgid = (-1);
1520         int incomplete_deliveries_remaining;
1521         time_t attempted = 0L;
1522         time_t last_attempted = 0L;
1523         time_t retry = SMTP_RETRY_INTERVAL;
1524
1525         lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1526
1527         msg = CtdlFetchMessage(msgnum, 1);
1528         if (msg == NULL) {
1529                 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1530                 return;
1531         }
1532
1533         instr = strdup(msg->cm_fields['M']);
1534         CtdlFreeMessage(msg);
1535
1536         /* Strip out the headers amd any other non-instruction line */
1537         lines = num_tokens(instr, '\n');
1538         for (i=0; i<lines; ++i) {
1539                 extract_token(buf, instr, i, '\n', sizeof buf);
1540                 if (num_tokens(buf, '|') < 2) {
1541                         remove_token(instr, i, '\n');
1542                         --lines;
1543                         --i;
1544                 }
1545         }
1546
1547         /* Learn the message ID and find out about recent delivery attempts */
1548         lines = num_tokens(instr, '\n');
1549         for (i=0; i<lines; ++i) {
1550                 extract_token(buf, instr, i, '\n', sizeof buf);
1551                 extract_token(key, buf, 0, '|', sizeof key);
1552                 if (!strcasecmp(key, "msgid")) {
1553                         text_msgid = extract_long(buf, 1);
1554                 }
1555                 if (!strcasecmp(key, "retry")) {
1556                         /* double the retry interval after each attempt */
1557                         retry = extract_long(buf, 1) * 2L;
1558                         if (retry > SMTP_RETRY_MAX) {
1559                                 retry = SMTP_RETRY_MAX;
1560                         }
1561                         remove_token(instr, i, '\n');
1562                 }
1563                 if (!strcasecmp(key, "attempted")) {
1564                         attempted = extract_long(buf, 1);
1565                         if (attempted > last_attempted)
1566                                 last_attempted = attempted;
1567                 }
1568         }
1569
1570         /*
1571          * Postpone delivery if we've already tried recently.
1572          */
1573         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1574                 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1575                 free(instr);
1576                 return;
1577         }
1578
1579
1580         /*
1581          * Bail out if there's no actual message associated with this
1582          */
1583         if (text_msgid < 0L) {
1584                 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1585                 free(instr);
1586                 return;
1587         }
1588
1589         /* Plow through the instructions looking for 'remote' directives and
1590          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1591          * were experienced and it's time to try again)
1592          */
1593         lines = num_tokens(instr, '\n');
1594         for (i=0; i<lines; ++i) {
1595                 extract_token(buf, instr, i, '\n', sizeof buf);
1596                 extract_token(key, buf, 0, '|', sizeof key);
1597                 extract_token(addr, buf, 1, '|', sizeof addr);
1598                 status = extract_int(buf, 2);
1599                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1600                 if ( (!strcasecmp(key, "remote"))
1601                    && ((status==0)||(status==3)||(status==4)) ) {
1602
1603                         /* Remove this "remote" instruction from the set,
1604                          * but replace the set's final newline if
1605                          * remove_token() stripped it.  It has to be there.
1606                          */
1607                         remove_token(instr, i, '\n');
1608                         if (instr[strlen(instr)-1] != '\n') {
1609                                 strcat(instr, "\n");
1610                         }
1611
1612                         --i;
1613                         --lines;
1614                         lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1615                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1616                         if (status != 2) {
1617                                 if (results == NULL) {
1618                                         results = malloc(1024);
1619                                         memset(results, 0, 1024);
1620                                 }
1621                                 else {
1622                                         results = realloc(results,
1623                                                 strlen(results) + 1024);
1624                                 }
1625                                 snprintf(&results[strlen(results)], 1024,
1626                                         "%s|%s|%d|%s\n",
1627                                         key, addr, status, dsn);
1628                         }
1629                 }
1630         }
1631
1632         if (results != NULL) {
1633                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1634                 strcat(instr, results);
1635                 free(results);
1636         }
1637
1638
1639         /* Generate 'bounce' messages */
1640         smtp_do_bounce(instr);
1641
1642         /* Go through the delivery list, deleting completed deliveries */
1643         incomplete_deliveries_remaining = 
1644                 smtp_purge_completed_deliveries(instr);
1645
1646
1647         /*
1648          * No delivery instructions remain, so delete both the instructions
1649          * message and the message message.
1650          */
1651         if (incomplete_deliveries_remaining <= 0) {
1652                 long delmsgs[2];
1653                 delmsgs[0] = msgnum;
1654                 delmsgs[1] = text_msgid;
1655                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1656         }
1657
1658         /*
1659          * Uncompleted delivery instructions remain, so delete the old
1660          * instructions and replace with the updated ones.
1661          */
1662         if (incomplete_deliveries_remaining > 0) {
1663                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1664                 msg = malloc(sizeof(struct CtdlMessage));
1665                 memset(msg, 0, sizeof(struct CtdlMessage));
1666                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1667                 msg->cm_anon_type = MES_NORMAL;
1668                 msg->cm_format_type = FMT_RFC822;
1669                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1670                 snprintf(msg->cm_fields['M'],
1671                         strlen(instr)+SIZ,
1672                         "Content-type: %s\n\n%s\n"
1673                         "attempted|%ld\n"
1674                         "retry|%ld\n",
1675                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1676                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1677                 CtdlFreeMessage(msg);
1678         }
1679
1680         free(instr);
1681 }
1682
1683
1684
1685 /*
1686  * smtp_do_queue()
1687  * 
1688  * Run through the queue sending out messages.
1689  */
1690 void smtp_do_queue(void) {
1691         static int doing_queue = 0;
1692
1693         /*
1694          * This is a simple concurrency check to make sure only one queue run
1695          * is done at a time.  We could do this with a mutex, but since we
1696          * don't really require extremely fine granularity here, we'll do it
1697          * with a static variable instead.
1698          */
1699         if (doing_queue) return;
1700         doing_queue = 1;
1701
1702         /* 
1703          * Go ahead and run the queue
1704          */
1705         lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1706
1707         if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1708                 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1709                 return;
1710         }
1711         CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1712                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1713
1714         lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1715         run_queue_now = 0;
1716         doing_queue = 0;
1717 }
1718
1719
1720
1721 /*****************************************************************************/
1722 /*                          SMTP UTILITY COMMANDS                            */
1723 /*****************************************************************************/
1724
1725 void cmd_smtp(char *argbuf) {
1726         char cmd[64];
1727         char node[256];
1728         char buf[1024];
1729         int i;
1730         int num_mxhosts;
1731
1732         if (CtdlAccessCheck(ac_aide)) return;
1733
1734         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1735
1736         if (!strcasecmp(cmd, "mx")) {
1737                 extract_token(node, argbuf, 1, '|', sizeof node);
1738                 num_mxhosts = getmx(buf, node);
1739                 cprintf("%d %d MX hosts listed for %s\n",
1740                         LISTING_FOLLOWS, num_mxhosts, node);
1741                 for (i=0; i<num_mxhosts; ++i) {
1742                         extract_token(node, buf, i, '|', sizeof node);
1743                         cprintf("%s\n", node);
1744                 }
1745                 cprintf("000\n");
1746                 return;
1747         }
1748
1749         else if (!strcasecmp(cmd, "runqueue")) {
1750                 run_queue_now = 1;
1751                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1752                 return;
1753         }
1754
1755         else {
1756                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1757         }
1758
1759 }
1760
1761
1762 /*
1763  * Initialize the SMTP outbound queue
1764  */
1765 void smtp_init_spoolout(void) {
1766         struct ctdlroom qrbuf;
1767
1768         /*
1769          * Create the room.  This will silently fail if the room already
1770          * exists, and that's perfectly ok, because we want it to exist.
1771          */
1772         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1773
1774         /*
1775          * Make sure it's set to be a "system room" so it doesn't show up
1776          * in the <K>nown rooms list for Aides.
1777          */
1778         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1779                 qrbuf.QRflags2 |= QR2_SYSTEM;
1780                 lputroom(&qrbuf);
1781         }
1782 }
1783
1784
1785
1786
1787 /*****************************************************************************/
1788 /*                      MODULE INITIALIZATION STUFF                          */
1789 /*****************************************************************************/
1790 /*
1791  * This cleanup function blows away the temporary memory used by
1792  * the SMTP server.
1793  */
1794 void smtp_cleanup_function(void) {
1795
1796         /* Don't do this stuff if this is not an SMTP session! */
1797         if (CC->h_command_function != smtp_command_loop) return;
1798
1799         lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1800         free(SMTP);
1801 }
1802
1803
1804
1805 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1806 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1807 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1808 const char *CitadelServiceSMTP_LMTP="LMTP";
1809 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1810
1811 CTDL_MODULE_INIT(smtp)
1812 {
1813         if (!threading)
1814         {
1815                 CtdlRegisterServiceHook(config.c_smtp_port,     /* SMTP MTA */
1816                                         NULL,
1817                                         smtp_mta_greeting,
1818                                         smtp_command_loop,
1819                                         NULL, 
1820                                         CitadelServiceSMTP_MTA);
1821
1822 #ifdef HAVE_OPENSSL
1823                 CtdlRegisterServiceHook(config.c_smtps_port,
1824                                         NULL,
1825                                         smtps_greeting,
1826                                         smtp_command_loop,
1827                                         NULL,
1828                                         CitadelServiceSMTPS_MTA);
1829 #endif
1830
1831                 CtdlRegisterServiceHook(config.c_msa_port,      /* SMTP MSA */
1832                                         NULL,
1833                                         smtp_msa_greeting,
1834                                         smtp_command_loop,
1835                                         NULL,
1836                                         CitadelServiceSMTP_MSA);
1837
1838                 CtdlRegisterServiceHook(0,                      /* local LMTP */
1839                                         file_lmtp_socket,
1840                                         lmtp_greeting,
1841                                         smtp_command_loop,
1842                                         NULL,
1843                                         CitadelServiceSMTP_LMTP);
1844
1845                 CtdlRegisterServiceHook(0,                      /* local LMTP */
1846                                         file_lmtp_unfiltered_socket,
1847                                         lmtp_unfiltered_greeting,
1848                                         smtp_command_loop,
1849                                         NULL,
1850                                         CitadelServiceSMTP_LMTP_UNF);
1851
1852                 smtp_init_spoolout();
1853                 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1854                 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1855                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1856         }
1857         
1858         /* return our Subversion id for the Log */
1859         return "$Id$";
1860 }