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