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