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