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