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