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