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