]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
Original message is now attached to smtp bounces.
[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         char *omsgtext;
1374         size_t omsgsize;
1375         long omsgid = (-1);
1376
1377         lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1378         strcpy(bounceto, "");
1379         sprintf(boundary, "=_Citadel_Multipart_%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1380         lines = num_tokens(instr, '\n');
1381
1382         /* See if it's time to give up on delivery of this message */
1383         for (i=0; i<lines; ++i) {
1384                 extract_token(buf, instr, i, '\n', sizeof buf);
1385                 extract_token(key, buf, 0, '|', sizeof key);
1386                 extract_token(addr, buf, 1, '|', sizeof addr);
1387                 if (!strcasecmp(key, "submitted")) {
1388                         submitted = atol(addr);
1389                 }
1390         }
1391
1392         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1393                 give_up = 1;
1394         }
1395
1396         /* Start building our bounce message */
1397
1398         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1399         if (bmsg == NULL) return;
1400         memset(bmsg, 0, sizeof(struct CtdlMessage));
1401
1402         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1403         bmsg->cm_anon_type = MES_NORMAL;
1404         bmsg->cm_format_type = FMT_RFC822;
1405         bmsg->cm_fields['A'] = strdup("Citadel");
1406         bmsg->cm_fields['O'] = strdup(MAILROOM);
1407         bmsg->cm_fields['N'] = strdup(config.c_nodename);
1408         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1409         bmsg->cm_fields['M'] = malloc(1024);
1410
1411         strcpy(bmsg->cm_fields['M'], "Content-type: multipart/mixed; boundary=\"");
1412         strcat(bmsg->cm_fields['M'], boundary);
1413         strcat(bmsg->cm_fields['M'], "\"\r\n");
1414         strcat(bmsg->cm_fields['M'], "MIME-Version: 1.0\r\n");
1415         strcat(bmsg->cm_fields['M'], "X-Mailer: " CITADEL "\r\n");
1416         strcat(bmsg->cm_fields['M'], "\r\nThis is a multipart message in MIME format.\r\n\r\n");
1417         strcat(bmsg->cm_fields['M'], "--");
1418         strcat(bmsg->cm_fields['M'], boundary);
1419         strcat(bmsg->cm_fields['M'], "\r\n");
1420         strcat(bmsg->cm_fields['M'], "Content-type: text/plain\r\n\r\n");
1421
1422         if (give_up) strcat(bmsg->cm_fields['M'],
1423 "A message you sent could not be delivered to some or all of its recipients\n"
1424 "due to prolonged unavailability of its destination(s).\n"
1425 "Giving up on the following addresses:\n\n"
1426 );
1427
1428         else strcat(bmsg->cm_fields['M'],
1429 "A message you sent could not be delivered to some or all of its recipients.\n"
1430 "The following addresses were undeliverable:\n\n"
1431 );
1432
1433         /*
1434          * Now go through the instructions checking for stuff.
1435          */
1436         for (i=0; i<lines; ++i) {
1437                 extract_token(buf, instr, i, '\n', sizeof buf);
1438                 extract_token(key, buf, 0, '|', sizeof key);
1439                 extract_token(addr, buf, 1, '|', sizeof addr);
1440                 status = extract_int(buf, 2);
1441                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1442                 bounce_this = 0;
1443
1444                 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1445                         key, addr, status, dsn);
1446
1447                 if (!strcasecmp(key, "bounceto")) {
1448                         strcpy(bounceto, addr);
1449                 }
1450
1451                 if (!strcasecmp(key, "msgid")) {
1452                         omsgid = atol(addr);
1453                 }
1454
1455                 if (
1456                    (!strcasecmp(key, "local"))
1457                    || (!strcasecmp(key, "remote"))
1458                    || (!strcasecmp(key, "ignet"))
1459                    || (!strcasecmp(key, "room"))
1460                 ) {
1461                         if (status == 5) bounce_this = 1;
1462                         if (give_up) bounce_this = 1;
1463                 }
1464
1465                 if (bounce_this) {
1466                         ++num_bounces;
1467
1468                         if (bmsg->cm_fields['M'] == NULL) {
1469                                 lprintf(CTDL_ERR, "ERROR ... M field is null "
1470                                         "(%s:%d)\n", __FILE__, __LINE__);
1471                         }
1472
1473                         bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1474                                 strlen(bmsg->cm_fields['M']) + 1024 );
1475                         strcat(bmsg->cm_fields['M'], addr);
1476                         strcat(bmsg->cm_fields['M'], ": ");
1477                         strcat(bmsg->cm_fields['M'], dsn);
1478                         strcat(bmsg->cm_fields['M'], "\n");
1479
1480                         remove_token(instr, i, '\n');
1481                         --i;
1482                         --lines;
1483                 }
1484         }
1485
1486         /* Attach the original message */
1487         if (omsgid >= 0) {
1488                 strcat(bmsg->cm_fields['M'], "--");
1489                 strcat(bmsg->cm_fields['M'], boundary);
1490                 strcat(bmsg->cm_fields['M'], "\r\n");
1491                 strcat(bmsg->cm_fields['M'], "Content-type: message/rfc822\r\n");
1492                 strcat(bmsg->cm_fields['M'], "Content-Transfer-Encoding: 7bit\r\n");
1493                 strcat(bmsg->cm_fields['M'], "Content-Disposition: inline\r\n");
1494                 strcat(bmsg->cm_fields['M'], "\r\n");
1495         
1496                 CC->redirect_buffer = malloc(SIZ);
1497                 CC->redirect_len = 0;
1498                 CC->redirect_alloc = SIZ;
1499                 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
1500                 omsgtext = CC->redirect_buffer;
1501                 omsgsize = CC->redirect_len;
1502                 CC->redirect_buffer = NULL;
1503                 CC->redirect_len = 0;
1504                 CC->redirect_alloc = 0;
1505                 bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1506                                 (strlen(bmsg->cm_fields['M']) + omsgsize + 1024) );
1507                 strcat(bmsg->cm_fields['M'], omsgtext);
1508                 free(omsgtext);
1509         }
1510
1511         /* Close the multipart MIME scope */
1512         strcat(bmsg->cm_fields['M'], "--");
1513         strcat(bmsg->cm_fields['M'], boundary);
1514         strcat(bmsg->cm_fields['M'], "--\r\n");
1515
1516         /* Deliver the bounce if there's anything worth mentioning */
1517         lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1518         if (num_bounces > 0) {
1519
1520                 /* First try the user who sent the message */
1521                 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1522                 if (strlen(bounceto) == 0) {
1523                         lprintf(CTDL_ERR, "No bounce address specified\n");
1524                         bounce_msgid = (-1L);
1525                 }
1526
1527                 /* Can we deliver the bounce to the original sender? */
1528                 valid = validate_recipients(bounceto);
1529                 if (valid != NULL) {
1530                         if (valid->num_error == 0) {
1531                                 CtdlSubmitMsg(bmsg, valid, "");
1532                                 successful_bounce = 1;
1533                         }
1534                 }
1535
1536                 /* If not, post it in the Aide> room */
1537                 if (successful_bounce == 0) {
1538                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1539                 }
1540
1541                 /* Free up the memory we used */
1542                 if (valid != NULL) {
1543                         free(valid);
1544                 }
1545         }
1546
1547         CtdlFreeMessage(bmsg);
1548         lprintf(CTDL_DEBUG, "Done processing bounces\n");
1549 }
1550
1551
1552 /*
1553  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1554  * set of delivery instructions for completed deliveries and remove them.
1555  *
1556  * It returns the number of incomplete deliveries remaining.
1557  */
1558 int smtp_purge_completed_deliveries(char *instr) {
1559         int i;
1560         int lines;
1561         int status;
1562         char buf[1024];
1563         char key[1024];
1564         char addr[1024];
1565         char dsn[1024];
1566         int completed;
1567         int incomplete = 0;
1568
1569         lines = num_tokens(instr, '\n');
1570         for (i=0; i<lines; ++i) {
1571                 extract_token(buf, instr, i, '\n', sizeof buf);
1572                 extract_token(key, buf, 0, '|', sizeof key);
1573                 extract_token(addr, buf, 1, '|', sizeof addr);
1574                 status = extract_int(buf, 2);
1575                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1576
1577                 completed = 0;
1578
1579                 if (
1580                    (!strcasecmp(key, "local"))
1581                    || (!strcasecmp(key, "remote"))
1582                    || (!strcasecmp(key, "ignet"))
1583                    || (!strcasecmp(key, "room"))
1584                 ) {
1585                         if (status == 2) completed = 1;
1586                         else ++incomplete;
1587                 }
1588
1589                 if (completed) {
1590                         remove_token(instr, i, '\n');
1591                         --i;
1592                         --lines;
1593                 }
1594         }
1595
1596         return(incomplete);
1597 }
1598
1599
1600 /*
1601  * smtp_do_procmsg()
1602  *
1603  * Called by smtp_do_queue() to handle an individual message.
1604  */
1605 void smtp_do_procmsg(long msgnum, void *userdata) {
1606         struct CtdlMessage *msg = NULL;
1607         char *instr = NULL;
1608         char *results = NULL;
1609         int i;
1610         int lines;
1611         int status;
1612         char buf[1024];
1613         char key[1024];
1614         char addr[1024];
1615         char dsn[1024];
1616         long text_msgid = (-1);
1617         int incomplete_deliveries_remaining;
1618         time_t attempted = 0L;
1619         time_t last_attempted = 0L;
1620         time_t retry = SMTP_RETRY_INTERVAL;
1621
1622         lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1623
1624         msg = CtdlFetchMessage(msgnum, 1);
1625         if (msg == NULL) {
1626                 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1627                 return;
1628         }
1629
1630         instr = strdup(msg->cm_fields['M']);
1631         CtdlFreeMessage(msg);
1632
1633         /* Strip out the headers amd any other non-instruction line */
1634         lines = num_tokens(instr, '\n');
1635         for (i=0; i<lines; ++i) {
1636                 extract_token(buf, instr, i, '\n', sizeof buf);
1637                 if (num_tokens(buf, '|') < 2) {
1638                         remove_token(instr, i, '\n');
1639                         --lines;
1640                         --i;
1641                 }
1642         }
1643
1644         /* Learn the message ID and find out about recent delivery attempts */
1645         lines = num_tokens(instr, '\n');
1646         for (i=0; i<lines; ++i) {
1647                 extract_token(buf, instr, i, '\n', sizeof buf);
1648                 extract_token(key, buf, 0, '|', sizeof key);
1649                 if (!strcasecmp(key, "msgid")) {
1650                         text_msgid = extract_long(buf, 1);
1651                 }
1652                 if (!strcasecmp(key, "retry")) {
1653                         /* double the retry interval after each attempt */
1654                         retry = extract_long(buf, 1) * 2L;
1655                         if (retry > SMTP_RETRY_MAX) {
1656                                 retry = SMTP_RETRY_MAX;
1657                         }
1658                         remove_token(instr, i, '\n');
1659                 }
1660                 if (!strcasecmp(key, "attempted")) {
1661                         attempted = extract_long(buf, 1);
1662                         if (attempted > last_attempted)
1663                                 last_attempted = attempted;
1664                 }
1665         }
1666
1667         /*
1668          * Postpone delivery if we've already tried recently.
1669          */
1670         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1671                 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1672                 free(instr);
1673                 return;
1674         }
1675
1676
1677         /*
1678          * Bail out if there's no actual message associated with this
1679          */
1680         if (text_msgid < 0L) {
1681                 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1682                 free(instr);
1683                 return;
1684         }
1685
1686         /* Plow through the instructions looking for 'remote' directives and
1687          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1688          * were experienced and it's time to try again)
1689          */
1690         lines = num_tokens(instr, '\n');
1691         for (i=0; i<lines; ++i) {
1692                 extract_token(buf, instr, i, '\n', sizeof buf);
1693                 extract_token(key, buf, 0, '|', sizeof key);
1694                 extract_token(addr, buf, 1, '|', sizeof addr);
1695                 status = extract_int(buf, 2);
1696                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1697                 if ( (!strcasecmp(key, "remote"))
1698                    && ((status==0)||(status==3)||(status==4)) ) {
1699
1700                         /* Remove this "remote" instruction from the set,
1701                          * but replace the set's final newline if
1702                          * remove_token() stripped it.  It has to be there.
1703                          */
1704                         remove_token(instr, i, '\n');
1705                         if (instr[strlen(instr)-1] != '\n') {
1706                                 strcat(instr, "\n");
1707                         }
1708
1709                         --i;
1710                         --lines;
1711                         lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1712                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1713                         if (status != 2) {
1714                                 if (results == NULL) {
1715                                         results = malloc(1024);
1716                                         memset(results, 0, 1024);
1717                                 }
1718                                 else {
1719                                         results = realloc(results,
1720                                                 strlen(results) + 1024);
1721                                 }
1722                                 snprintf(&results[strlen(results)], 1024,
1723                                         "%s|%s|%d|%s\n",
1724                                         key, addr, status, dsn);
1725                         }
1726                 }
1727         }
1728
1729         if (results != NULL) {
1730                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1731                 strcat(instr, results);
1732                 free(results);
1733         }
1734
1735
1736         /* Generate 'bounce' messages */
1737         smtp_do_bounce(instr);
1738
1739         /* Go through the delivery list, deleting completed deliveries */
1740         incomplete_deliveries_remaining = 
1741                 smtp_purge_completed_deliveries(instr);
1742
1743
1744         /*
1745          * No delivery instructions remain, so delete both the instructions
1746          * message and the message message.
1747          */
1748         if (incomplete_deliveries_remaining <= 0) {
1749                 long delmsgs[2];
1750                 delmsgs[0] = msgnum;
1751                 delmsgs[1] = text_msgid;
1752                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1753         }
1754
1755         /*
1756          * Uncompleted delivery instructions remain, so delete the old
1757          * instructions and replace with the updated ones.
1758          */
1759         if (incomplete_deliveries_remaining > 0) {
1760                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1761                 msg = malloc(sizeof(struct CtdlMessage));
1762                 memset(msg, 0, sizeof(struct CtdlMessage));
1763                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1764                 msg->cm_anon_type = MES_NORMAL;
1765                 msg->cm_format_type = FMT_RFC822;
1766                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1767                 snprintf(msg->cm_fields['M'],
1768                         strlen(instr)+SIZ,
1769                         "Content-type: %s\n\n%s\n"
1770                         "attempted|%ld\n"
1771                         "retry|%ld\n",
1772                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1773                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1774                 CtdlFreeMessage(msg);
1775         }
1776
1777         free(instr);
1778 }
1779
1780
1781
1782 /*
1783  * smtp_do_queue()
1784  * 
1785  * Run through the queue sending out messages.
1786  */
1787 void smtp_do_queue(void) {
1788         static int doing_queue = 0;
1789
1790         /*
1791          * This is a simple concurrency check to make sure only one queue run
1792          * is done at a time.  We could do this with a mutex, but since we
1793          * don't really require extremely fine granularity here, we'll do it
1794          * with a static variable instead.
1795          */
1796         if (doing_queue) return;
1797         doing_queue = 1;
1798
1799         /* 
1800          * Go ahead and run the queue
1801          */
1802         lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1803
1804         if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1805                 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1806                 return;
1807         }
1808         CtdlForEachMessage(MSGS_ALL, 0L, NULL,
1809                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1810
1811         lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1812         run_queue_now = 0;
1813         doing_queue = 0;
1814 }
1815
1816
1817
1818 /*****************************************************************************/
1819 /*                          SMTP UTILITY COMMANDS                            */
1820 /*****************************************************************************/
1821
1822 void cmd_smtp(char *argbuf) {
1823         char cmd[64];
1824         char node[256];
1825         char buf[1024];
1826         int i;
1827         int num_mxhosts;
1828
1829         if (CtdlAccessCheck(ac_aide)) return;
1830
1831         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1832
1833         if (!strcasecmp(cmd, "mx")) {
1834                 extract_token(node, argbuf, 1, '|', sizeof node);
1835                 num_mxhosts = getmx(buf, node);
1836                 cprintf("%d %d MX hosts listed for %s\n",
1837                         LISTING_FOLLOWS, num_mxhosts, node);
1838                 for (i=0; i<num_mxhosts; ++i) {
1839                         extract_token(node, buf, i, '|', sizeof node);
1840                         cprintf("%s\n", node);
1841                 }
1842                 cprintf("000\n");
1843                 return;
1844         }
1845
1846         else if (!strcasecmp(cmd, "runqueue")) {
1847                 run_queue_now = 1;
1848                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1849                 return;
1850         }
1851
1852         else {
1853                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1854         }
1855
1856 }
1857
1858
1859 /*
1860  * Initialize the SMTP outbound queue
1861  */
1862 void smtp_init_spoolout(void) {
1863         struct ctdlroom qrbuf;
1864
1865         /*
1866          * Create the room.  This will silently fail if the room already
1867          * exists, and that's perfectly ok, because we want it to exist.
1868          */
1869         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1870
1871         /*
1872          * Make sure it's set to be a "system room" so it doesn't show up
1873          * in the <K>nown rooms list for Aides.
1874          */
1875         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1876                 qrbuf.QRflags2 |= QR2_SYSTEM;
1877                 lputroom(&qrbuf);
1878         }
1879 }
1880
1881
1882
1883
1884 /*****************************************************************************/
1885 /*                      MODULE INITIALIZATION STUFF                          */
1886 /*****************************************************************************/
1887 /*
1888  * This cleanup function blows away the temporary memory used by
1889  * the SMTP server.
1890  */
1891 void smtp_cleanup_function(void) {
1892
1893         /* Don't do this stuff if this is not an SMTP session! */
1894         if (CC->h_command_function != smtp_command_loop) return;
1895
1896         lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1897         free(SMTP);
1898         free(SMTP_ROOMS);
1899         free(SMTP_RECPS);
1900 }
1901
1902
1903
1904
1905
1906 char *serv_smtp_init(void)
1907 {
1908
1909         CtdlRegisterServiceHook(config.c_smtp_port,     /* SMTP MTA */
1910                                 NULL,
1911                                 smtp_greeting,
1912                                 smtp_command_loop,
1913                                 NULL);
1914
1915 #ifdef HAVE_OPENSSL
1916         CtdlRegisterServiceHook(config.c_smtps_port,
1917                                 NULL,
1918                                 smtps_greeting,
1919                                 smtp_command_loop,
1920                                 NULL);
1921 #endif
1922
1923         CtdlRegisterServiceHook(config.c_msa_port,      /* SMTP MSA */
1924                                 NULL,
1925                                 smtp_msa_greeting,
1926                                 smtp_command_loop,
1927                                 NULL);
1928
1929         CtdlRegisterServiceHook(0,                      /* local LMTP */
1930                                                         file_lmtp_socket,
1931                                                         lmtp_greeting,
1932                                                         smtp_command_loop,
1933                                                         NULL);
1934
1935         CtdlRegisterServiceHook(0,                      /* local LMTP */
1936                                                         file_lmtp_unfiltered_socket,
1937                                                         lmtp_unfiltered_greeting,
1938                                                         smtp_command_loop,
1939                                                         NULL);
1940
1941         smtp_init_spoolout();
1942         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1943         CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1944         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1945         return "$Id$";
1946 }