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