cd858ba10fee0b289f037b8d45e6b9d408635da2
[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@example.com");
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("550 %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("Message rejected by filter");
737                 }
738
739                 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
740         }
741         
742         else {                  /* Ok, we'll accept this message. */
743                 msgnum = CtdlSubmitMsg(msg, valid, "");
744                 if (msgnum > 0L) {
745                         sprintf(result, "250 Message accepted.\r\n");
746                 }
747                 else {
748                         sprintf(result, "550 Internal delivery error\r\n");
749                 }
750         }
751
752         /* For SMTP and ESTMP, just print the result message.  For LMTP, we
753          * have to print one result message for each recipient.  Since there
754          * is nothing in Citadel which would cause different recipients to
755          * have different results, we can get away with just spitting out the
756          * same message once for each recipient.
757          */
758         if (SMTP->is_lmtp) {
759                 for (i=0; i<SMTP->number_of_recipients; ++i) {
760                         cprintf("%s", result);
761                 }
762         }
763         else {
764                 cprintf("%s", result);
765         }
766
767         /* Write something to the syslog (which may or may not be where the
768          * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
769          */
770         if (enable_syslog) {
771                 syslog((LOG_MAIL | LOG_INFO),
772                         "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
773                         msgnum,
774                         SMTP->from,
775                         SMTP->number_of_recipients,
776                         CC->cs_host,
777                         CC->cs_addr,
778                         result
779                 );
780         }
781
782         /* Clean up */
783         CtdlFreeMessage(msg);
784         free_recipients(valid);
785         smtp_data_clear();      /* clear out the buffers now */
786 }
787
788
789 /*
790  * implements the STARTTLS command (Citadel API version)
791  */
792 void smtp_starttls(void)
793 {
794         char ok_response[SIZ];
795         char nosup_response[SIZ];
796         char error_response[SIZ];
797
798         sprintf(ok_response,
799                 "220 Begin TLS negotiation now\r\n");
800         sprintf(nosup_response,
801                 "554 TLS not supported here\r\n");
802         sprintf(error_response,
803                 "554 Internal error\r\n");
804         CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
805         smtp_rset(0);
806 }
807
808
809
810 /* 
811  * Main command loop for SMTP sessions.
812  */
813 void smtp_command_loop(void) {
814         char cmdbuf[SIZ];
815
816         time(&CC->lastcmd);
817         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
818         if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
819                 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
820                 CC->kill_me = 1;
821                 return;
822         }
823         lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
824         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
825
826         if (SMTP->command_state == smtp_user) {
827                 smtp_get_user(cmdbuf);
828         }
829
830         else if (SMTP->command_state == smtp_password) {
831                 smtp_get_pass(cmdbuf);
832         }
833
834         else if (SMTP->command_state == smtp_plain) {
835                 smtp_try_plain(cmdbuf);
836         }
837
838         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
839                 smtp_auth(&cmdbuf[5]);
840         }
841
842         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
843                 smtp_data();
844         }
845
846         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
847                 smtp_hello(&cmdbuf[5], 0);
848         }
849
850         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
851                 smtp_hello(&cmdbuf[5], 1);
852         }
853
854         else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
855                 smtp_hello(&cmdbuf[5], 2);
856         }
857
858         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
859                 smtp_help();
860         }
861
862         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
863                 smtp_mail(&cmdbuf[5]);
864         }
865
866         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
867                 cprintf("250 NOOP\r\n");
868         }
869
870         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
871                 cprintf("221 Goodbye...\r\n");
872                 CC->kill_me = 1;
873                 return;
874         }
875
876         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
877                 smtp_rcpt(&cmdbuf[5]);
878         }
879
880         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
881                 smtp_rset(1);
882         }
883 #ifdef HAVE_OPENSSL
884         else if (!strcasecmp(cmdbuf, "STARTTLS")) {
885                 smtp_starttls();
886         }
887 #endif
888         else {
889                 cprintf("502 I'm afraid I can't do that.\r\n");
890         }
891
892
893 }
894
895
896
897
898 /*****************************************************************************/
899 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
900 /*****************************************************************************/
901
902
903
904 /*
905  * smtp_try()
906  *
907  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
908  *
909  */
910 void smtp_try(const char *key, const char *addr, int *status,
911               char *dsn, size_t n, long msgnum)
912 {
913         int sock = (-1);
914         char mxhosts[1024];
915         int num_mxhosts;
916         int mx;
917         int i;
918         char user[1024], node[1024], name[1024];
919         char buf[1024];
920         char mailfrom[1024];
921         char mx_user[256];
922         char mx_pass[256];
923         char mx_host[256];
924         char mx_port[256];
925         int lp, rp;
926         char *msgtext;
927         char *ptr;
928         size_t msg_size;
929         int scan_done;
930
931         /* Parse out the host portion of the recipient address */
932         process_rfc822_addr(addr, user, node, name);
933
934         lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
935                 user, node, name);
936
937         /* Load the message out of the database */
938         CC->redirect_buffer = malloc(SIZ);
939         CC->redirect_len = 0;
940         CC->redirect_alloc = SIZ;
941         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
942         msgtext = CC->redirect_buffer;
943         msg_size = CC->redirect_len;
944         CC->redirect_buffer = NULL;
945         CC->redirect_len = 0;
946         CC->redirect_alloc = 0;
947
948         /* Extract something to send later in the 'MAIL From:' command */
949         strcpy(mailfrom, "");
950         scan_done = 0;
951         ptr = msgtext;
952         do {
953                 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
954                         scan_done = 1;
955                 }
956                 if (!strncasecmp(buf, "From:", 5)) {
957                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
958                         striplt(mailfrom);
959                         for (i=0; mailfrom[i]; ++i) {
960                                 if (!isprint(mailfrom[i])) {
961                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
962                                         i=0;
963                                 }
964                         }
965
966                         /* Strip out parenthesized names */
967                         lp = (-1);
968                         rp = (-1);
969                         for (i=0; mailfrom[i]; ++i) {
970                                 if (mailfrom[i] == '(') lp = i;
971                                 if (mailfrom[i] == ')') rp = i;
972                         }
973                         if ((lp>0)&&(rp>lp)) {
974                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
975                         }
976
977                         /* Prefer brokketized names */
978                         lp = (-1);
979                         rp = (-1);
980                         for (i=0; mailfrom[i]; ++i) {
981                                 if (mailfrom[i] == '<') lp = i;
982                                 if (mailfrom[i] == '>') rp = i;
983                         }
984                         if ( (lp>=0) && (rp>lp) ) {
985                                 mailfrom[rp] = 0;
986                                 strcpy(mailfrom, &mailfrom[lp]);
987                         }
988
989                         scan_done = 1;
990                 }
991         } while (scan_done == 0);
992         if (IsEmptyStr(mailfrom)) strcpy(mailfrom, "someone@somewhere.org");
993         stripallbut(mailfrom, '<', '>');
994
995         /* Figure out what mail exchanger host we have to connect to */
996         num_mxhosts = getmx(mxhosts, node);
997         lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
998         if (num_mxhosts < 1) {
999                 *status = 5;
1000                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
1001                 return;
1002         }
1003
1004         sock = (-1);
1005         for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
1006                 char *endpart;
1007                 extract_token(buf, mxhosts, mx, '|', sizeof buf);
1008                 strcpy(mx_user, "");
1009                 strcpy(mx_pass, "");
1010                 if (num_tokens(buf, '@') > 1) {
1011                         strcpy (mx_user, buf);
1012                         endpart = strrchr(mx_user, '@');
1013                         *endpart = '\0';
1014                         strcpy (mx_host, endpart + 1);
1015                         endpart = strrchr(mx_user, ':');
1016                         if (endpart != NULL) {
1017                                 strcpy(mx_pass, endpart+1);
1018                                 *endpart = '\0';
1019                         }
1020                 }
1021                 else
1022                         strcpy (mx_host, buf);
1023                 endpart = strrchr(mx_host, ':');
1024                 if (endpart != 0){
1025                         *endpart = '\0';
1026                         strcpy(mx_port, endpart + 1);
1027                 }               
1028                 else {
1029                         strcpy(mx_port, "25");
1030                 }
1031                 lprintf(CTDL_DEBUG, "Trying %s : %s ...\n", mx_host, mx_port);
1032                 sock = sock_connect(mx_host, mx_port, "tcp");
1033                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1034                 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
1035                 if (sock < 0) {
1036                         if (errno > 0) {
1037                                 snprintf(dsn, SIZ, "%s", strerror(errno));
1038                         }
1039                         else {
1040                                 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1041                         }
1042                 }
1043         }
1044
1045         if (sock < 0) {
1046                 *status = 4;    /* dsn is already filled in */
1047                 return;
1048         }
1049
1050         /* Process the SMTP greeting from the server */
1051         if (ml_sock_gets(sock, buf) < 0) {
1052                 *status = 4;
1053                 strcpy(dsn, "Connection broken during SMTP conversation");
1054                 goto bail;
1055         }
1056         lprintf(CTDL_DEBUG, "<%s\n", buf);
1057         if (buf[0] != '2') {
1058                 if (buf[0] == '4') {
1059                         *status = 4;
1060                         safestrncpy(dsn, &buf[4], 1023);
1061                         goto bail;
1062                 }
1063                 else {
1064                         *status = 5;
1065                         safestrncpy(dsn, &buf[4], 1023);
1066                         goto bail;
1067                 }
1068         }
1069
1070         /* At this point we know we are talking to a real SMTP server */
1071
1072         /* Do a EHLO command.  If it fails, try the HELO command. */
1073         snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1074         lprintf(CTDL_DEBUG, ">%s", buf);
1075         sock_write(sock, buf, strlen(buf));
1076         if (ml_sock_gets(sock, buf) < 0) {
1077                 *status = 4;
1078                 strcpy(dsn, "Connection broken during SMTP HELO");
1079                 goto bail;
1080         }
1081         lprintf(CTDL_DEBUG, "<%s\n", buf);
1082         if (buf[0] != '2') {
1083                 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1084                 lprintf(CTDL_DEBUG, ">%s", buf);
1085                 sock_write(sock, buf, strlen(buf));
1086                 if (ml_sock_gets(sock, buf) < 0) {
1087                         *status = 4;
1088                         strcpy(dsn, "Connection broken during SMTP HELO");
1089                         goto bail;
1090                 }
1091         }
1092         if (buf[0] != '2') {
1093                 if (buf[0] == '4') {
1094                         *status = 4;
1095                         safestrncpy(dsn, &buf[4], 1023);
1096                         goto bail;
1097                 }
1098                 else {
1099                         *status = 5;
1100                         safestrncpy(dsn, &buf[4], 1023);
1101                         goto bail;
1102                 }
1103         }
1104
1105         /* Do an AUTH command if necessary */
1106         if (!IsEmptyStr(mx_user)) {
1107                 char encoded[1024];
1108                 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1109                 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1110                 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1111                 lprintf(CTDL_DEBUG, ">%s", buf);
1112                 sock_write(sock, buf, strlen(buf));
1113                 if (ml_sock_gets(sock, buf) < 0) {
1114                         *status = 4;
1115                         strcpy(dsn, "Connection broken during SMTP AUTH");
1116                         goto bail;
1117                 }
1118                 lprintf(CTDL_DEBUG, "<%s\n", buf);
1119                 if (buf[0] != '2') {
1120                         if (buf[0] == '4') {
1121                                 *status = 4;
1122                                 safestrncpy(dsn, &buf[4], 1023);
1123                                 goto bail;
1124                         }
1125                         else {
1126                                 *status = 5;
1127                                 safestrncpy(dsn, &buf[4], 1023);
1128                                 goto bail;
1129                         }
1130                 }
1131         }
1132
1133         /* previous command succeeded, now try the MAIL From: command */
1134         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1135         lprintf(CTDL_DEBUG, ">%s", buf);
1136         sock_write(sock, buf, strlen(buf));
1137         if (ml_sock_gets(sock, buf) < 0) {
1138                 *status = 4;
1139                 strcpy(dsn, "Connection broken during SMTP MAIL");
1140                 goto bail;
1141         }
1142         lprintf(CTDL_DEBUG, "<%s\n", buf);
1143         if (buf[0] != '2') {
1144                 if (buf[0] == '4') {
1145                         *status = 4;
1146                         safestrncpy(dsn, &buf[4], 1023);
1147                         goto bail;
1148                 }
1149                 else {
1150                         *status = 5;
1151                         safestrncpy(dsn, &buf[4], 1023);
1152                         goto bail;
1153                 }
1154         }
1155
1156         /* MAIL succeeded, now try the RCPT To: command */
1157         snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1158         lprintf(CTDL_DEBUG, ">%s", buf);
1159         sock_write(sock, buf, strlen(buf));
1160         if (ml_sock_gets(sock, buf) < 0) {
1161                 *status = 4;
1162                 strcpy(dsn, "Connection broken during SMTP RCPT");
1163                 goto bail;
1164         }
1165         lprintf(CTDL_DEBUG, "<%s\n", buf);
1166         if (buf[0] != '2') {
1167                 if (buf[0] == '4') {
1168                         *status = 4;
1169                         safestrncpy(dsn, &buf[4], 1023);
1170                         goto bail;
1171                 }
1172                 else {
1173                         *status = 5;
1174                         safestrncpy(dsn, &buf[4], 1023);
1175                         goto bail;
1176                 }
1177         }
1178
1179         /* RCPT succeeded, now try the DATA command */
1180         lprintf(CTDL_DEBUG, ">DATA\n");
1181         sock_write(sock, "DATA\r\n", 6);
1182         if (ml_sock_gets(sock, buf) < 0) {
1183                 *status = 4;
1184                 strcpy(dsn, "Connection broken during SMTP DATA");
1185                 goto bail;
1186         }
1187         lprintf(CTDL_DEBUG, "<%s\n", buf);
1188         if (buf[0] != '3') {
1189                 if (buf[0] == '4') {
1190                         *status = 3;
1191                         safestrncpy(dsn, &buf[4], 1023);
1192                         goto bail;
1193                 }
1194                 else {
1195                         *status = 5;
1196                         safestrncpy(dsn, &buf[4], 1023);
1197                         goto bail;
1198                 }
1199         }
1200
1201         /* If we reach this point, the server is expecting data */
1202         sock_write(sock, msgtext, msg_size);
1203         if (msgtext[msg_size-1] != 10) {
1204                 lprintf(CTDL_WARNING, "Possible problem: message did not "
1205                         "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1206                                 buf[msg_size-1]);
1207         }
1208
1209         sock_write(sock, ".\r\n", 3);
1210         if (ml_sock_gets(sock, buf) < 0) {
1211                 *status = 4;
1212                 strcpy(dsn, "Connection broken during SMTP message transmit");
1213                 goto bail;
1214         }
1215         lprintf(CTDL_DEBUG, "%s\n", buf);
1216         if (buf[0] != '2') {
1217                 if (buf[0] == '4') {
1218                         *status = 4;
1219                         safestrncpy(dsn, &buf[4], 1023);
1220                         goto bail;
1221                 }
1222                 else {
1223                         *status = 5;
1224                         safestrncpy(dsn, &buf[4], 1023);
1225                         goto bail;
1226                 }
1227         }
1228
1229         /* We did it! */
1230         safestrncpy(dsn, &buf[4], 1023);
1231         *status = 2;
1232
1233         lprintf(CTDL_DEBUG, ">QUIT\n");
1234         sock_write(sock, "QUIT\r\n", 6);
1235         ml_sock_gets(sock, buf);
1236         lprintf(CTDL_DEBUG, "<%s\n", buf);
1237         lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1238                 user, node, name);
1239
1240 bail:   free(msgtext);
1241         sock_close(sock);
1242
1243         /* Write something to the syslog (which may or may not be where the
1244          * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1245          */
1246         if (enable_syslog) {
1247                 syslog((LOG_MAIL | LOG_INFO),
1248                         "%ld: to=<%s>, relay=%s, stat=%s",
1249                         msgnum,
1250                         addr,
1251                         mx_host,
1252                         dsn
1253                 );
1254         }
1255
1256         return;
1257 }
1258
1259
1260
1261 /*
1262  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1263  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1264  * a "bounce" message (delivery status notification).
1265  */
1266 void smtp_do_bounce(char *instr) {
1267         int i;
1268         int lines;
1269         int status;
1270         char buf[1024];
1271         char key[1024];
1272         char addr[1024];
1273         char dsn[1024];
1274         char bounceto[1024];
1275         char boundary[64];
1276         int num_bounces = 0;
1277         int bounce_this = 0;
1278         long bounce_msgid = (-1);
1279         time_t submitted = 0L;
1280         struct CtdlMessage *bmsg = NULL;
1281         int give_up = 0;
1282         struct recptypes *valid;
1283         int successful_bounce = 0;
1284         static int seq = 0;
1285         char *omsgtext;
1286         size_t omsgsize;
1287         long omsgid = (-1);
1288
1289         lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1290         strcpy(bounceto, "");
1291         sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1292         lines = num_tokens(instr, '\n');
1293
1294         /* See if it's time to give up on delivery of this message */
1295         for (i=0; i<lines; ++i) {
1296                 extract_token(buf, instr, i, '\n', sizeof buf);
1297                 extract_token(key, buf, 0, '|', sizeof key);
1298                 extract_token(addr, buf, 1, '|', sizeof addr);
1299                 if (!strcasecmp(key, "submitted")) {
1300                         submitted = atol(addr);
1301                 }
1302         }
1303
1304         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1305                 give_up = 1;
1306         }
1307
1308         /* Start building our bounce message */
1309
1310         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1311         if (bmsg == NULL) return;
1312         memset(bmsg, 0, sizeof(struct CtdlMessage));
1313
1314         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1315         bmsg->cm_anon_type = MES_NORMAL;
1316         bmsg->cm_format_type = FMT_RFC822;
1317         bmsg->cm_fields['A'] = strdup("Citadel");
1318         bmsg->cm_fields['O'] = strdup(MAILROOM);
1319         bmsg->cm_fields['N'] = strdup(config.c_nodename);
1320         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1321         bmsg->cm_fields['M'] = malloc(1024);
1322
1323         strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1324         strcat(bmsg->cm_fields['M'], boundary);
1325         strcat(bmsg->cm_fields['M'], "\"\r\n");
1326         strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1327         strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1328         strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1329         strcat(bmsg->cm_fields['M'], "--");
1330         strcat(bmsg->cm_fields['M'], boundary);
1331         strcat(bmsg->cm_fields['M'], "\r\n");
1332         strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1333
1334         if (give_up) strcat(bmsg->cm_fields['M'],
1335 "A message you sent could not be delivered to some or all of its recipients\n"
1336 "due to prolonged unavailability of its destination(s).\n"
1337 "Giving up on the following addresses:\n\n"
1338 );
1339
1340         else strcat(bmsg->cm_fields['M'],
1341 "A message you sent could not be delivered to some or all of its recipients.\n"
1342 "The following addresses were undeliverable:\n\n"
1343 );
1344
1345         /*
1346          * Now go through the instructions checking for stuff.
1347          */
1348         for (i=0; i<lines; ++i) {
1349                 extract_token(buf, instr, i, '\n', sizeof buf);
1350                 extract_token(key, buf, 0, '|', sizeof key);
1351                 extract_token(addr, buf, 1, '|', sizeof addr);
1352                 status = extract_int(buf, 2);
1353                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1354                 bounce_this = 0;
1355
1356                 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1357                         key, addr, status, dsn);
1358
1359                 if (!strcasecmp(key, "bounceto")) {
1360                         strcpy(bounceto, addr);
1361                 }
1362
1363                 if (!strcasecmp(key, "msgid")) {
1364                         omsgid = atol(addr);
1365                 }
1366
1367                 if (!strcasecmp(key, "remote")) {
1368                         if (status == 5) bounce_this = 1;
1369                         if (give_up) bounce_this = 1;
1370                 }
1371
1372                 if (bounce_this) {
1373                         ++num_bounces;
1374
1375                         if (bmsg->cm_fields['M'] == NULL) {
1376                                 lprintf(CTDL_ERR, "ERROR ... M field is null "
1377                                         "(%s:%d)\n", __FILE__, __LINE__);
1378                         }
1379
1380                         bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1381                                 strlen(bmsg->cm_fields['M']) + 1024 );
1382                         strcat(bmsg->cm_fields['M'], addr);
1383                         strcat(bmsg->cm_fields['M'], ": ");
1384                         strcat(bmsg->cm_fields['M'], dsn);
1385                         strcat(bmsg->cm_fields['M'], "\r\n");
1386
1387                         remove_token(instr, i, '\n');
1388                         --i;
1389                         --lines;
1390                 }
1391         }
1392
1393         /* Attach the original message */
1394         if (omsgid >= 0) {
1395                 strcat(bmsg->cm_fields['M'], "--");
1396                 strcat(bmsg->cm_fields['M'], boundary);
1397                 strcat(bmsg->cm_fields['M'], "\r\n");
1398                 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1399                 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1400                 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1401                 strcat(bmsg->cm_fields['M'], "\r\n");
1402         
1403                 CC->redirect_buffer = malloc(SIZ);
1404                 CC->redirect_len = 0;
1405                 CC->redirect_alloc = SIZ;
1406                 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1407                 omsgtext = CC->redirect_buffer;
1408                 omsgsize = CC->redirect_len;
1409                 CC->redirect_buffer = NULL;
1410                 CC->redirect_len = 0;
1411                 CC->redirect_alloc = 0;
1412                 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1413                                 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1414                 strcat(bmsg->cm_fields['M'], omsgtext);
1415                 free(omsgtext);
1416         }
1417
1418         /* Close the multipart MIME scope */
1419         strcat(bmsg->cm_fields['M'], "--");
1420         strcat(bmsg->cm_fields['M'], boundary);
1421         strcat(bmsg->cm_fields['M'], "--\r\n");
1422
1423         /* Deliver the bounce if there's anything worth mentioning */
1424         lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1425         if (num_bounces > 0) {
1426
1427                 /* First try the user who sent the message */
1428                 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1429                 if (IsEmptyStr(bounceto)) {
1430                         lprintf(CTDL_ERR, "No bounce address specified\n");
1431                         bounce_msgid = (-1L);
1432                 }
1433
1434                 /* Can we deliver the bounce to the original sender? */
1435                 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1436                 if (valid != NULL) {
1437                         if (valid->num_error == 0) {
1438                                 CtdlSubmitMsg(bmsg, valid, "");
1439                                 successful_bounce = 1;
1440                         }
1441                 }
1442
1443                 /* If not, post it in the Aide> room */
1444                 if (successful_bounce == 0) {
1445                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1446                 }
1447
1448                 /* Free up the memory we used */
1449                 if (valid != NULL) {
1450                         free_recipients(valid);
1451                 }
1452         }
1453
1454         CtdlFreeMessage(bmsg);
1455         lprintf(CTDL_DEBUG, "Done processing bounces\n");
1456 }
1457
1458
1459 /*
1460  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1461  * set of delivery instructions for completed deliveries and remove them.
1462  *
1463  * It returns the number of incomplete deliveries remaining.
1464  */
1465 int smtp_purge_completed_deliveries(char *instr) {
1466         int i;
1467         int lines;
1468         int status;
1469         char buf[1024];
1470         char key[1024];
1471         char addr[1024];
1472         char dsn[1024];
1473         int completed;
1474         int incomplete = 0;
1475
1476         lines = num_tokens(instr, '\n');
1477         for (i=0; i<lines; ++i) {
1478                 extract_token(buf, instr, i, '\n', sizeof buf);
1479                 extract_token(key, buf, 0, '|', sizeof key);
1480                 extract_token(addr, buf, 1, '|', sizeof addr);
1481                 status = extract_int(buf, 2);
1482                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1483
1484                 completed = 0;
1485
1486                 if (!strcasecmp(key, "remote")) {
1487                         if (status == 2) completed = 1;
1488                         else ++incomplete;
1489                 }
1490
1491                 if (completed) {
1492                         remove_token(instr, i, '\n');
1493                         --i;
1494                         --lines;
1495                 }
1496         }
1497
1498         return(incomplete);
1499 }
1500
1501
1502 /*
1503  * smtp_do_procmsg()
1504  *
1505  * Called by smtp_do_queue() to handle an individual message.
1506  */
1507 void smtp_do_procmsg(long msgnum, void *userdata) {
1508         struct CtdlMessage *msg = NULL;
1509         char *instr = NULL;
1510         char *results = NULL;
1511         int i;
1512         int lines;
1513         int status;
1514         char buf[1024];
1515         char key[1024];
1516         char addr[1024];
1517         char dsn[1024];
1518         long text_msgid = (-1);
1519         int incomplete_deliveries_remaining;
1520         time_t attempted = 0L;
1521         time_t last_attempted = 0L;
1522         time_t retry = SMTP_RETRY_INTERVAL;
1523
1524         lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1525
1526         msg = CtdlFetchMessage(msgnum, 1);
1527         if (msg == NULL) {
1528                 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1529                 return;
1530         }
1531
1532         instr = strdup(msg->cm_fields['M']);
1533         CtdlFreeMessage(msg);
1534
1535         /* Strip out the headers amd any other non-instruction line */
1536         lines = num_tokens(instr, '\n');
1537         for (i=0; i<lines; ++i) {
1538                 extract_token(buf, instr, i, '\n', sizeof buf);
1539                 if (num_tokens(buf, '|') < 2) {
1540                         remove_token(instr, i, '\n');
1541                         --lines;
1542                         --i;
1543                 }
1544         }
1545
1546         /* Learn the message ID and find out about recent delivery attempts */
1547         lines = num_tokens(instr, '\n');
1548         for (i=0; i<lines; ++i) {
1549                 extract_token(buf, instr, i, '\n', sizeof buf);
1550                 extract_token(key, buf, 0, '|', sizeof key);
1551                 if (!strcasecmp(key, "msgid")) {
1552                         text_msgid = extract_long(buf, 1);
1553                 }
1554                 if (!strcasecmp(key, "retry")) {
1555                         /* double the retry interval after each attempt */
1556                         retry = extract_long(buf, 1) * 2L;
1557                         if (retry > SMTP_RETRY_MAX) {
1558                                 retry = SMTP_RETRY_MAX;
1559                         }
1560                         remove_token(instr, i, '\n');
1561                 }
1562                 if (!strcasecmp(key, "attempted")) {
1563                         attempted = extract_long(buf, 1);
1564                         if (attempted > last_attempted)
1565                                 last_attempted = attempted;
1566                 }
1567         }
1568
1569         /*
1570          * Postpone delivery if we've already tried recently.
1571          */
1572         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1573                 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1574                 free(instr);
1575                 return;
1576         }
1577
1578
1579         /*
1580          * Bail out if there's no actual message associated with this
1581          */
1582         if (text_msgid < 0L) {
1583                 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1584                 free(instr);
1585                 return;
1586         }
1587
1588         /* Plow through the instructions looking for 'remote' directives and
1589          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1590          * were experienced and it's time to try again)
1591          */
1592         lines = num_tokens(instr, '\n');
1593         for (i=0; i<lines; ++i) {
1594                 extract_token(buf, instr, i, '\n', sizeof buf);
1595                 extract_token(key, buf, 0, '|', sizeof key);
1596                 extract_token(addr, buf, 1, '|', sizeof addr);
1597                 status = extract_int(buf, 2);
1598                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1599                 if ( (!strcasecmp(key, "remote"))
1600                    && ((status==0)||(status==3)||(status==4)) ) {
1601
1602                         /* Remove this "remote" instruction from the set,
1603                          * but replace the set's final newline if
1604                          * remove_token() stripped it.  It has to be there.
1605                          */
1606                         remove_token(instr, i, '\n');
1607                         if (instr[strlen(instr)-1] != '\n') {
1608                                 strcat(instr, "\n");
1609                         }
1610
1611                         --i;
1612                         --lines;
1613                         lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1614                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1615                         if (status != 2) {
1616                                 if (results == NULL) {
1617                                         results = malloc(1024);
1618                                         memset(results, 0, 1024);
1619                                 }
1620                                 else {
1621                                         results = realloc(results,
1622                                                 strlen(results) + 1024);
1623                                 }
1624                                 snprintf(&results[strlen(results)], 1024,
1625                                         "%s|%s|%d|%s\n",
1626                                         key, addr, status, dsn);
1627                         }
1628                 }
1629         }
1630
1631         if (results != NULL) {
1632                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1633                 strcat(instr, results);
1634                 free(results);
1635         }
1636
1637
1638         /* Generate 'bounce' messages */
1639         smtp_do_bounce(instr);
1640
1641         /* Go through the delivery list, deleting completed deliveries */
1642         incomplete_deliveries_remaining = 
1643                 smtp_purge_completed_deliveries(instr);
1644
1645
1646         /*
1647          * No delivery instructions remain, so delete both the instructions
1648          * message and the message message.
1649          */
1650         if (incomplete_deliveries_remaining <= 0) {
1651                 long delmsgs[2];
1652                 delmsgs[0] = msgnum;
1653                 delmsgs[1] = text_msgid;
1654                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1655         }
1656
1657         /*
1658          * Uncompleted delivery instructions remain, so delete the old
1659          * instructions and replace with the updated ones.
1660          */
1661         if (incomplete_deliveries_remaining > 0) {
1662                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1663                 msg = malloc(sizeof(struct CtdlMessage));
1664                 memset(msg, 0, sizeof(struct CtdlMessage));
1665                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1666                 msg->cm_anon_type = MES_NORMAL;
1667                 msg->cm_format_type = FMT_RFC822;
1668                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1669                 snprintf(msg->cm_fields['M'],
1670                         strlen(instr)+SIZ,
1671                         "Content-type: %s\n\n%s\n"
1672                         "attempted|%ld\n"
1673                         "retry|%ld\n",
1674                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1675                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1676                 CtdlFreeMessage(msg);
1677         }
1678
1679         free(instr);
1680 }
1681
1682
1683
1684 /*
1685  * smtp_do_queue()
1686  * 
1687  * Run through the queue sending out messages.
1688  */
1689 void smtp_do_queue(void) {
1690         static int doing_queue = 0;
1691
1692         /*
1693          * This is a simple concurrency check to make sure only one queue run
1694          * is done at a time.  We could do this with a mutex, but since we
1695          * don't really require extremely fine granularity here, we'll do it
1696          * with a static variable instead.
1697          */
1698         if (doing_queue) return;
1699         doing_queue = 1;
1700
1701         /* 
1702          * Go ahead and run the queue
1703          */
1704         lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1705
1706         if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1707                 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1708                 return;
1709         }
1710         CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1711                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1712
1713         lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1714         run_queue_now = 0;
1715         doing_queue = 0;
1716 }
1717
1718
1719
1720 /*****************************************************************************/
1721 /*                          SMTP UTILITY COMMANDS                            */
1722 /*****************************************************************************/
1723
1724 void cmd_smtp(char *argbuf) {
1725         char cmd[64];
1726         char node[256];
1727         char buf[1024];
1728         int i;
1729         int num_mxhosts;
1730
1731         if (CtdlAccessCheck(ac_aide)) return;
1732
1733         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1734
1735         if (!strcasecmp(cmd, "mx")) {
1736                 extract_token(node, argbuf, 1, '|', sizeof node);
1737                 num_mxhosts = getmx(buf, node);
1738                 cprintf("%d %d MX hosts listed for %s\n",
1739                         LISTING_FOLLOWS, num_mxhosts, node);
1740                 for (i=0; i<num_mxhosts; ++i) {
1741                         extract_token(node, buf, i, '|', sizeof node);
1742                         cprintf("%s\n", node);
1743                 }
1744                 cprintf("000\n");
1745                 return;
1746         }
1747
1748         else if (!strcasecmp(cmd, "runqueue")) {
1749                 run_queue_now = 1;
1750                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1751                 return;
1752         }
1753
1754         else {
1755                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1756         }
1757
1758 }
1759
1760
1761 /*
1762  * Initialize the SMTP outbound queue
1763  */
1764 void smtp_init_spoolout(void) {
1765         struct ctdlroom qrbuf;
1766
1767         /*
1768          * Create the room.  This will silently fail if the room already
1769          * exists, and that's perfectly ok, because we want it to exist.
1770          */
1771         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1772
1773         /*
1774          * Make sure it's set to be a "system room" so it doesn't show up
1775          * in the <K>nown rooms list for Aides.
1776          */
1777         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1778                 qrbuf.QRflags2 |= QR2_SYSTEM;
1779                 lputroom(&qrbuf);
1780         }
1781 }
1782
1783
1784
1785
1786 /*****************************************************************************/
1787 /*                      MODULE INITIALIZATION STUFF                          */
1788 /*****************************************************************************/
1789 /*
1790  * This cleanup function blows away the temporary memory used by
1791  * the SMTP server.
1792  */
1793 void smtp_cleanup_function(void) {
1794
1795         /* Don't do this stuff if this is not an SMTP session! */
1796         if (CC->h_command_function != smtp_command_loop) return;
1797
1798         lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1799         free(SMTP);
1800 }
1801
1802
1803
1804 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1805 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1806 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1807 const char *CitadelServiceSMTP_LMTP="LMTP";
1808 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1809
1810 CTDL_MODULE_INIT(smtp)
1811 {
1812         if (!threading)
1813         {
1814                 CtdlRegisterServiceHook(config.c_smtp_port,     /* SMTP MTA */
1815                                         NULL,
1816                                         smtp_mta_greeting,
1817                                         smtp_command_loop,
1818                                         NULL, 
1819                                         CitadelServiceSMTP_MTA);
1820
1821 #ifdef HAVE_OPENSSL
1822                 CtdlRegisterServiceHook(config.c_smtps_port,
1823                                         NULL,
1824                                         smtps_greeting,
1825                                         smtp_command_loop,
1826                                         NULL,
1827                                         CitadelServiceSMTPS_MTA);
1828 #endif
1829
1830                 CtdlRegisterServiceHook(config.c_msa_port,      /* SMTP MSA */
1831                                         NULL,
1832                                         smtp_msa_greeting,
1833                                         smtp_command_loop,
1834                                         NULL,
1835                                         CitadelServiceSMTP_MSA);
1836
1837                 CtdlRegisterServiceHook(0,                      /* local LMTP */
1838                                         file_lmtp_socket,
1839                                         lmtp_greeting,
1840                                         smtp_command_loop,
1841                                         NULL,
1842                                         CitadelServiceSMTP_LMTP);
1843
1844                 CtdlRegisterServiceHook(0,                      /* local LMTP */
1845                                         file_lmtp_unfiltered_socket,
1846                                         lmtp_unfiltered_greeting,
1847                                         smtp_command_loop,
1848                                         NULL,
1849                                         CitadelServiceSMTP_LMTP_UNF);
1850
1851                 smtp_init_spoolout();
1852                 CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1853                 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1854                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1855         }
1856         
1857         /* return our Subversion id for the Log */
1858         return "$Id$";
1859 }