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