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