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