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