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