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