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