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