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