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