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