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