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