* sock_getln (): CtdlSockGetLine() gives us lines without \r\n, no need to look for...
[citadel.git] / citadel / modules / smtp / serv_smtp.c
1 /*
2  * This module is an SMTP and ESMTP implementation for the Citadel system.
3  * It is compliant with all of the following:
4  *
5  * RFC  821 - Simple Mail Transfer Protocol
6  * RFC  876 - Survey of SMTP Implementations
7  * RFC 1047 - Duplicate messages and SMTP
8  * RFC 1652 - 8 bit MIME
9  * RFC 1869 - Extended Simple Mail Transfer Protocol
10  * RFC 1870 - SMTP Service Extension for Message Size Declaration
11  * RFC 2033 - Local Mail Transfer Protocol
12  * RFC 2197 - SMTP Service Extension for Command Pipelining
13  * RFC 2476 - Message Submission
14  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15  * RFC 2554 - SMTP Service Extension for Authentication
16  * RFC 2821 - Simple Mail Transfer Protocol
17  * RFC 2822 - Internet Message Format
18  * RFC 2920 - SMTP Service Extension for Command Pipelining
19  *  
20  * The VRFY and EXPN commands have been removed from this implementation
21  * because nobody uses these commands anymore, except for spammers.
22  *
23  * Copyright (c) 1998-2009 by the citadel.org team
24  *
25  *  This program is free software; you can redistribute it and/or modify
26  *  it under the terms of the GNU General Public License as published by
27  *  the Free Software Foundation; either version 3 of the License, or
28  *  (at your option) any later version.
29  *
30  *  This program is distributed in the hope that it will be useful,
31  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
32  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  *  GNU General Public License for more details.
34  *
35  *  You should have received a copy of the GNU General Public License
36  *  along with this program; if not, write to the Free Software
37  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
38  */
39
40 #include "sysdep.h"
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <fcntl.h>
45 #include <signal.h>
46 #include <pwd.h>
47 #include <errno.h>
48 #include <sys/types.h>
49 #include <syslog.h>
50
51 #if TIME_WITH_SYS_TIME
52 # include <sys/time.h>
53 # include <time.h>
54 #else
55 # if HAVE_SYS_TIME_H
56 #  include <sys/time.h>
57 # else
58 #  include <time.h>
59 # endif
60 #endif
61
62 #include <sys/wait.h>
63 #include <ctype.h>
64 #include <string.h>
65 #include <limits.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
70 #include "citadel.h"
71 #include "server.h"
72 #include "citserver.h"
73 #include "support.h"
74 #include "config.h"
75 #include "control.h"
76 #include "user_ops.h"
77 #include "database.h"
78 #include "msgbase.h"
79 #include "internet_addressing.h"
80 #include "genstamp.h"
81 #include "domain.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
85
86
87
88 #ifndef HAVE_SNPRINTF
89 #include "snprintf.h"
90 #endif
91
92
93 #include "ctdl_module.h"
94
95
96
97 typedef struct _citsmtp {               /* Information about the current session */
98         int command_state;
99         char helo_node[SIZ];
100         char from[SIZ];
101         char recipients[SIZ];
102         int number_of_recipients;
103         int delivery_mode;
104         int message_originated_locally;
105         int is_lmtp;
106         int is_unfiltered;
107         int is_msa;
108 }citsmtp;
109
110 enum {                          /* Command states for login authentication */
111         smtp_command,
112         smtp_user,
113         smtp_password,
114         smtp_plain
115 };
116
117 #define SMTP            ((citsmtp *)CC->session_specific_data)
118
119
120 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
121
122
123 /*****************************************************************************/
124 /*                      SMTP SERVER (INBOUND) STUFF                          */
125 /*****************************************************************************/
126
127
128 /*
129  * Here's where our SMTP session begins its happy day.
130  */
131 void smtp_greeting(int is_msa)
132 {
133         citsmtp *sSMTP;
134         char message_to_spammer[1024];
135
136         strcpy(CC->cs_clientname, "SMTP session");
137         CC->internal_pgm = 1;
138         CC->cs_flags |= CS_STEALTH;
139         CC->session_specific_data = malloc(sizeof(citsmtp));
140         memset(SMTP, 0, sizeof(citsmtp));
141         sSMTP = SMTP;
142         sSMTP->is_msa = is_msa;
143
144         /* If this config option is set, reject connections from problem
145          * addresses immediately instead of after they execute a RCPT
146          */
147         if ( (config.c_rbl_at_greeting) && (sSMTP->is_msa == 0) ) {
148                 if (rbl_check(message_to_spammer)) {
149                         if (CtdlThreadCheckStop())
150                                 cprintf("421 %s\r\n", message_to_spammer);
151                         else
152                                 cprintf("550 %s\r\n", message_to_spammer);
153                         CC->kill_me = 1;
154                         /* no need to free_recipients(valid), it's not allocated yet */
155                         return;
156                 }
157         }
158
159         /* Otherwise we're either clean or we check later. */
160
161         if (CC->nologin==1) {
162                 cprintf("500 Too many users are already online (maximum is %d)\r\n",
163                         config.c_maxsessions
164                 );
165                 CC->kill_me = 1;
166                 /* no need to free_recipients(valid), it's not allocated yet */
167                 return;
168         }
169
170         /* Note: the FQDN *must* appear as the first thing after the 220 code.
171          * Some clients (including citmail.c) depend on it being there.
172          */
173         cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn);
174 }
175
176
177 /*
178  * SMTPS is just like SMTP, except it goes crypto right away.
179  */
180 void smtps_greeting(void) {
181         CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
182 #ifdef HAVE_OPENSSL
183         if (!CC->redirect_ssl) CC->kill_me = 1;         /* kill session if no crypto */
184 #endif
185         smtp_greeting(0);
186 }
187
188
189 /*
190  * SMTP MSA port requires authentication.
191  */
192 void smtp_msa_greeting(void) {
193         smtp_greeting(1);
194 }
195
196
197 /*
198  * LMTP is like SMTP but with some extra bonus footage added.
199  */
200 void lmtp_greeting(void) {
201         citsmtp *sSMTP;
202
203         smtp_greeting(0);
204         sSMTP = SMTP;
205         SMTP->is_lmtp = 1;
206 }
207
208
209 /* 
210  * Generic SMTP MTA greeting
211  */
212 void smtp_mta_greeting(void) {
213         smtp_greeting(0);
214 }
215
216
217 /*
218  * We also have an unfiltered LMTP socket that bypasses spam filters.
219  */
220 void lmtp_unfiltered_greeting(void) {
221         citsmtp *sSMTP;
222
223         smtp_greeting(0);
224         sSMTP = SMTP;
225         sSMTP->is_lmtp = 1;
226         sSMTP->is_unfiltered = 1;
227 }
228
229
230 /*
231  * Login greeting common to all auth methods
232  */
233 void smtp_auth_greeting(void) {
234                 cprintf("235 Hello, %s\r\n", CC->user.fullname);
235                 CtdlLogPrintf(CTDL_NOTICE, "SMTP authenticated %s\n", CC->user.fullname);
236                 CC->internal_pgm = 0;
237                 CC->cs_flags &= ~CS_STEALTH;
238 }
239
240
241 /*
242  * Implement HELO and EHLO commands.
243  *
244  * which_command:  0=HELO, 1=EHLO, 2=LHLO
245  */
246 void smtp_hello(char *argbuf, int which_command) {
247         citsmtp *sSMTP = SMTP;
248
249         safestrncpy(sSMTP->helo_node, argbuf, sizeof sSMTP->helo_node);
250
251         if ( (which_command != 2) && (sSMTP->is_lmtp) ) {
252                 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
253                 return;
254         }
255
256         if ( (which_command == 2) && (sSMTP->is_lmtp == 0) ) {
257                 cprintf("500 LHLO is only allowed when running LMTP\r\n");
258                 return;
259         }
260
261         if (which_command == 0) {
262                 cprintf("250 Hello %s (%s [%s])\r\n",
263                         sSMTP->helo_node,
264                         CC->cs_host,
265                         CC->cs_addr
266                 );
267         }
268         else {
269                 if (which_command == 1) {
270                         cprintf("250-Hello %s (%s [%s])\r\n",
271                                 sSMTP->helo_node,
272                                 CC->cs_host,
273                                 CC->cs_addr
274                         );
275                 }
276                 else {
277                         cprintf("250-Greetings and joyous salutations.\r\n");
278                 }
279                 cprintf("250-HELP\r\n");
280                 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
281
282 #ifdef HAVE_OPENSSL
283                 /*
284                  * Offer TLS, but only if TLS is not already active.
285                  * Furthermore, only offer TLS when running on
286                  * the SMTP-MSA port, not on the SMTP-MTA port, due to
287                  * questionable reliability of TLS in certain sending MTA's.
288                  */
289                 if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) {
290                         cprintf("250-STARTTLS\r\n");
291                 }
292 #endif  /* HAVE_OPENSSL */
293
294                 cprintf("250-AUTH LOGIN PLAIN\r\n"
295                         "250-AUTH=LOGIN PLAIN\r\n"
296                         "250 8BITMIME\r\n"
297                 );
298         }
299 }
300
301
302
303 /*
304  * Implement HELP command.
305  */
306 void smtp_help(void) {
307         cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
308 }
309
310
311 /*
312  *
313  */
314 void smtp_get_user(char *argbuf) {
315         char buf[SIZ];
316         char username[SIZ];
317         citsmtp *sSMTP = SMTP;
318
319         CtdlDecodeBase64(username, argbuf, SIZ);
320         /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", username); */
321         if (CtdlLoginExistingUser(NULL, username) == login_ok) {
322                 CtdlEncodeBase64(buf, "Password:", 9, 0);
323                 cprintf("334 %s\r\n", buf);
324                 sSMTP->command_state = smtp_password;
325         }
326         else {
327                 cprintf("500 No such user.\r\n");
328                 sSMTP->command_state = smtp_command;
329         }
330 }
331
332
333 /*
334  *
335  */
336 void smtp_get_pass(char *argbuf) {
337         char password[SIZ];
338         long len;
339
340         memset(password, 0, sizeof(password));  
341         len = CtdlDecodeBase64(password, argbuf, SIZ);
342         /* CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", password); */
343         if (CtdlTryPassword(password, len) == pass_ok) {
344                 smtp_auth_greeting();
345         }
346         else {
347                 cprintf("535 Authentication failed.\r\n");
348         }
349         SMTP->command_state = smtp_command;
350 }
351
352
353 /*
354  * Back end for PLAIN auth method (either inline or multistate)
355  */
356 void smtp_try_plain(char *encoded_authstring) {
357         char decoded_authstring[1024];
358         char ident[256];
359         char user[256];
360         char pass[256];
361         int result;
362         long len;
363
364         CtdlDecodeBase64(decoded_authstring, encoded_authstring, strlen(encoded_authstring) );
365         safestrncpy(ident, decoded_authstring, sizeof ident);
366         safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
367         len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
368         if (len == -1)
369                 len = sizeof(pass) - 1;
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, len) == 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|SUPPRESS_ENV_TO) );
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 = cmemreadline(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);
1083                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
1084                 if (sock >= 0) 
1085                 {
1086                         CtdlLogPrintf(CTDL_DEBUG, "SMTP client: connected!\n");
1087                                 int fdflags; 
1088                                 fdflags = fcntl(sock, F_GETFL);
1089                                 if (fdflags < 0)
1090                                         CtdlLogPrintf(CTDL_DEBUG,
1091                                                       "unable to get SMTP-Client socket flags! %s \n",
1092                                                       strerror(errno));
1093                                 fdflags = fdflags | O_NONBLOCK;
1094                                 if (fcntl(sock, F_SETFL, fdflags) < 0)
1095                                         CtdlLogPrintf(CTDL_DEBUG,
1096                                                       "unable to set SMTP-Client socket nonblocking flags! %s \n",
1097                                                       strerror(errno));
1098                 }
1099                 if (sock < 0) {
1100                         if (errno > 0) {
1101                                 snprintf(dsn, SIZ, "%s", strerror(errno));
1102                         }
1103                         else {
1104                                 snprintf(dsn, SIZ, "Unable to connect to %s : %s\n", mx_host, mx_port);
1105                         }
1106                 }
1107         }
1108
1109         if (sock < 0) {
1110                 *status = 4;    /* dsn is already filled in */
1111                 return;
1112         }
1113
1114         CCC->sReadBuf = NewStrBuf();
1115         CCC->sMigrateBuf = NewStrBuf();
1116         CCC->sPos = NULL;
1117
1118         /* Process the SMTP greeting from the server */
1119         if (ml_sock_gets(&sock, buf) < 0) {
1120                 *status = 4;
1121                 strcpy(dsn, "Connection broken during SMTP conversation");
1122                 goto bail;
1123         }
1124         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1125         if (buf[0] != '2') {
1126                 if (buf[0] == '4') {
1127                         *status = 4;
1128                         safestrncpy(dsn, &buf[4], 1023);
1129                         goto bail;
1130                 }
1131                 else {
1132                         *status = 5;
1133                         safestrncpy(dsn, &buf[4], 1023);
1134                         goto bail;
1135                 }
1136         }
1137
1138         /* At this point we know we are talking to a real SMTP server */
1139
1140         /* Do a EHLO command.  If it fails, try the HELO command. */
1141         snprintf(buf, sizeof buf, "EHLO %s\r\n", config.c_fqdn);
1142         CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1143         sock_write(&sock, buf, strlen(buf));
1144         if (ml_sock_gets(&sock, buf) < 0) {
1145                 *status = 4;
1146                 strcpy(dsn, "Connection broken during SMTP HELO");
1147                 goto bail;
1148         }
1149         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1150         if (buf[0] != '2') {
1151                 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1152                 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1153                 sock_write(&sock, buf, strlen(buf));
1154                 if (ml_sock_gets(&sock, buf) < 0) {
1155                         *status = 4;
1156                         strcpy(dsn, "Connection broken during SMTP HELO");
1157                         goto bail;
1158                 }
1159         }
1160         if (buf[0] != '2') {
1161                 if (buf[0] == '4') {
1162                         *status = 4;
1163                         safestrncpy(dsn, &buf[4], 1023);
1164                         goto bail;
1165                 }
1166                 else {
1167                         *status = 5;
1168                         safestrncpy(dsn, &buf[4], 1023);
1169                         goto bail;
1170                 }
1171         }
1172
1173         /* Do an AUTH command if necessary */
1174         if (!IsEmptyStr(mx_user)) {
1175                 char encoded[1024];
1176                 sprintf(buf, "%s%c%s%c%s", mx_user, '\0', mx_user, '\0', mx_pass);
1177                 CtdlEncodeBase64(encoded, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 2, 0);
1178                 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", encoded);
1179                 CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1180                 sock_write(&sock, buf, strlen(buf));
1181                 if (ml_sock_gets(&sock, buf) < 0) {
1182                         *status = 4;
1183                         strcpy(dsn, "Connection broken during SMTP AUTH");
1184                         goto bail;
1185                 }
1186                 CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1187                 if (buf[0] != '2') {
1188                         if (buf[0] == '4') {
1189                                 *status = 4;
1190                                 safestrncpy(dsn, &buf[4], 1023);
1191                                 goto bail;
1192                         }
1193                         else {
1194                                 *status = 5;
1195                                 safestrncpy(dsn, &buf[4], 1023);
1196                                 goto bail;
1197                         }
1198                 }
1199         }
1200
1201         /* previous command succeeded, now try the MAIL FROM: command */
1202         snprintf(buf, sizeof buf, "MAIL FROM:<%s>\r\n", envelope_from);
1203         CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1204         sock_write(&sock, buf, strlen(buf));
1205         if (ml_sock_gets(&sock, buf) < 0) {
1206                 *status = 4;
1207                 strcpy(dsn, "Connection broken during SMTP MAIL");
1208                 goto bail;
1209         }
1210         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1211         if (buf[0] != '2') {
1212                 if (buf[0] == '4') {
1213                         *status = 4;
1214                         safestrncpy(dsn, &buf[4], 1023);
1215                         goto bail;
1216                 }
1217                 else {
1218                         *status = 5;
1219                         safestrncpy(dsn, &buf[4], 1023);
1220                         goto bail;
1221                 }
1222         }
1223
1224         /* MAIL succeeded, now try the RCPT To: command */
1225         snprintf(buf, sizeof buf, "RCPT TO:<%s@%s>\r\n", user, node);
1226         CtdlLogPrintf(CTDL_DEBUG, ">%s", buf);
1227         sock_write(&sock, buf, strlen(buf));
1228         if (ml_sock_gets(&sock, buf) < 0) {
1229                 *status = 4;
1230                 strcpy(dsn, "Connection broken during SMTP RCPT");
1231                 goto bail;
1232         }
1233         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1234         if (buf[0] != '2') {
1235                 if (buf[0] == '4') {
1236                         *status = 4;
1237                         safestrncpy(dsn, &buf[4], 1023);
1238                         goto bail;
1239                 }
1240                 else {
1241                         *status = 5;
1242                         safestrncpy(dsn, &buf[4], 1023);
1243                         goto bail;
1244                 }
1245         }
1246
1247         /* RCPT succeeded, now try the DATA command */
1248         CtdlLogPrintf(CTDL_DEBUG, ">DATA\n");
1249         sock_write(&sock, "DATA\r\n", 6);
1250         if (ml_sock_gets(&sock, buf) < 0) {
1251                 *status = 4;
1252                 strcpy(dsn, "Connection broken during SMTP DATA");
1253                 goto bail;
1254         }
1255         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1256         if (buf[0] != '3') {
1257                 if (buf[0] == '4') {
1258                         *status = 3;
1259                         safestrncpy(dsn, &buf[4], 1023);
1260                         goto bail;
1261                 }
1262                 else {
1263                         *status = 5;
1264                         safestrncpy(dsn, &buf[4], 1023);
1265                         goto bail;
1266                 }
1267         }
1268
1269         /* If we reach this point, the server is expecting data.*/
1270         sock_write(&sock, msgtext, msg_size);
1271         if (msgtext[msg_size-1] != 10) {
1272                 CtdlLogPrintf(CTDL_WARNING, "Possible problem: message did not "
1273                         "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1274                                 buf[msg_size-1]);
1275                 sock_write(&sock, "\r\n", 2);
1276         }
1277
1278         sock_write(&sock, ".\r\n", 3);
1279         if (ml_sock_gets(&sock, buf) < 0) {
1280                 *status = 4;
1281                 strcpy(dsn, "Connection broken during SMTP message transmit");
1282                 goto bail;
1283         }
1284         CtdlLogPrintf(CTDL_DEBUG, "%s\n", buf);
1285         if (buf[0] != '2') {
1286                 if (buf[0] == '4') {
1287                         *status = 4;
1288                         safestrncpy(dsn, &buf[4], 1023);
1289                         goto bail;
1290                 }
1291                 else {
1292                         *status = 5;
1293                         safestrncpy(dsn, &buf[4], 1023);
1294                         goto bail;
1295                 }
1296         }
1297
1298         /* We did it! */
1299         safestrncpy(dsn, &buf[4], 1023);
1300         *status = 2;
1301
1302         CtdlLogPrintf(CTDL_DEBUG, ">QUIT\n");
1303         sock_write(&sock, "QUIT\r\n", 6);
1304         ml_sock_gets(&sock, buf);
1305         CtdlLogPrintf(CTDL_DEBUG, "<%s\n", buf);
1306         CtdlLogPrintf(CTDL_INFO, "SMTP client: delivery to <%s> @ <%s> (%s) succeeded\n",
1307                 user, node, name);
1308
1309 bail:   free(msgtext);
1310         FreeStrBuf(&CCC->sReadBuf);
1311         FreeStrBuf(&CCC->sMigrateBuf);
1312         if (sock != -1)
1313                 sock_close(sock);
1314
1315         /* Write something to the syslog (which may or may not be where the
1316          * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1317          */
1318         if (enable_syslog) {
1319                 syslog((LOG_MAIL | LOG_INFO),
1320                         "%ld: to=<%s>, relay=%s, stat=%s",
1321                         msgnum,
1322                         addr,
1323                         mx_host,
1324                         dsn
1325                 );
1326         }
1327
1328         return;
1329 }
1330
1331
1332
1333 /*
1334  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1335  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1336  * a "bounce" message (delivery status notification).
1337  */
1338 void smtp_do_bounce(char *instr) {
1339         int i;
1340         int lines;
1341         int status;
1342         char buf[1024];
1343         char key[1024];
1344         char addr[1024];
1345         char dsn[1024];
1346         char bounceto[1024];
1347         StrBuf *boundary;
1348         int num_bounces = 0;
1349         int bounce_this = 0;
1350         long bounce_msgid = (-1);
1351         time_t submitted = 0L;
1352         struct CtdlMessage *bmsg = NULL;
1353         int give_up = 0;
1354         struct recptypes *valid;
1355         int successful_bounce = 0;
1356         static int seq = 0;
1357         StrBuf *BounceMB;
1358         long omsgid = (-1);
1359
1360         CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1361         strcpy(bounceto, "");
1362         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
1363         StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
1364         lines = num_tokens(instr, '\n');
1365
1366         /* See if it's time to give up on delivery of this message */
1367         for (i=0; i<lines; ++i) {
1368                 extract_token(buf, instr, i, '\n', sizeof buf);
1369                 extract_token(key, buf, 0, '|', sizeof key);
1370                 extract_token(addr, buf, 1, '|', sizeof addr);
1371                 if (!strcasecmp(key, "submitted")) {
1372                         submitted = atol(addr);
1373                 }
1374         }
1375
1376         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1377                 give_up = 1;
1378         }
1379
1380         /* Start building our bounce message */
1381
1382         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1383         if (bmsg == NULL) return;
1384         memset(bmsg, 0, sizeof(struct CtdlMessage));
1385         BounceMB = NewStrBufPlain(NULL, 1024);
1386
1387         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1388         bmsg->cm_anon_type = MES_NORMAL;
1389         bmsg->cm_format_type = FMT_RFC822;
1390         bmsg->cm_fields['A'] = strdup("Citadel");
1391         bmsg->cm_fields['O'] = strdup(MAILROOM);
1392         bmsg->cm_fields['N'] = strdup(config.c_nodename);
1393         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1394         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
1395         StrBufAppendBuf(BounceMB, boundary, 0);
1396         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
1397         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
1398         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
1399         StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
1400         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1401         StrBufAppendBuf(BounceMB, boundary, 0);
1402         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1403         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
1404
1405         if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
1406 "A message you sent could not be delivered to some or all of its recipients\n"
1407 "due to prolonged unavailability of its destination(s).\n"
1408 "Giving up on the following addresses:\n\n"
1409                                                   ), 0);
1410
1411         else StrBufAppendBufPlain(BounceMB, HKEY(
1412 "A message you sent could not be delivered to some or all of its recipients.\n"
1413 "The following addresses were undeliverable:\n\n"
1414                                           ), 0);
1415
1416         /*
1417          * Now go through the instructions checking for stuff.
1418          */
1419         for (i=0; i<lines; ++i) {
1420                 long addrlen;
1421                 long dsnlen;
1422                 extract_token(buf, instr, i, '\n', sizeof buf);
1423                 extract_token(key, buf, 0, '|', sizeof key);
1424                 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
1425                 status = extract_int(buf, 2);
1426                 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
1427                 bounce_this = 0;
1428
1429                 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1430                         key, addr, status, dsn);
1431
1432                 if (!strcasecmp(key, "bounceto")) {
1433                         strcpy(bounceto, addr);
1434                 }
1435
1436                 if (!strcasecmp(key, "msgid")) {
1437                         omsgid = atol(addr);
1438                 }
1439
1440                 if (!strcasecmp(key, "remote")) {
1441                         if (status == 5) bounce_this = 1;
1442                         if (give_up) bounce_this = 1;
1443                 }
1444
1445                 if (bounce_this) {
1446                         ++num_bounces;
1447
1448                         StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
1449                         StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
1450                         StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
1451                         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1452
1453                         remove_token(instr, i, '\n');
1454                         --i;
1455                         --lines;
1456                 }
1457         }
1458
1459         /* Attach the original message */
1460         if (omsgid >= 0) {
1461                 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1462                 StrBufAppendBuf(BounceMB, boundary, 0);
1463                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1464                 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
1465                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
1466                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
1467                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
1468         
1469                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
1470                 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
1471                 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
1472                 FreeStrBuf(&CC->redirect_buffer);
1473         }
1474
1475         /* Close the multipart MIME scope */
1476         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
1477         StrBufAppendBuf(BounceMB, boundary, 0);
1478         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
1479         bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
1480         /* Deliver the bounce if there's anything worth mentioning */
1481         CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1482         if (num_bounces > 0) {
1483
1484                 /* First try the user who sent the message */
1485                 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1486                 if (IsEmptyStr(bounceto)) {
1487                         CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
1488                         bounce_msgid = (-1L);
1489                 }
1490
1491                 /* Can we deliver the bounce to the original sender? */
1492                 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
1493                 if (valid != NULL) {
1494                         if (valid->num_error == 0) {
1495                                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
1496                                 successful_bounce = 1;
1497                         }
1498                 }
1499
1500                 /* If not, post it in the Aide> room */
1501                 if (successful_bounce == 0) {
1502                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
1503                 }
1504
1505                 /* Free up the memory we used */
1506                 if (valid != NULL) {
1507                         free_recipients(valid);
1508                 }
1509         }
1510         FreeStrBuf(&boundary);
1511         CtdlFreeMessage(bmsg);
1512         CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
1513 }
1514
1515
1516 /*
1517  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1518  * set of delivery instructions for completed deliveries and remove them.
1519  *
1520  * It returns the number of incomplete deliveries remaining.
1521  */
1522 int smtp_purge_completed_deliveries(char *instr) {
1523         int i;
1524         int lines;
1525         int status;
1526         char buf[1024];
1527         char key[1024];
1528         char addr[1024];
1529         char dsn[1024];
1530         int completed;
1531         int incomplete = 0;
1532
1533         lines = num_tokens(instr, '\n');
1534         for (i=0; i<lines; ++i) {
1535                 extract_token(buf, instr, i, '\n', sizeof buf);
1536                 extract_token(key, buf, 0, '|', sizeof key);
1537                 extract_token(addr, buf, 1, '|', sizeof addr);
1538                 status = extract_int(buf, 2);
1539                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1540
1541                 completed = 0;
1542
1543                 if (!strcasecmp(key, "remote")) {
1544                         if (status == 2) completed = 1;
1545                         else ++incomplete;
1546                 }
1547
1548                 if (completed) {
1549                         remove_token(instr, i, '\n');
1550                         --i;
1551                         --lines;
1552                 }
1553         }
1554
1555         return(incomplete);
1556 }
1557
1558
1559 /*
1560  * smtp_do_procmsg()
1561  *
1562  * Called by smtp_do_queue() to handle an individual message.
1563  */
1564 void smtp_do_procmsg(long msgnum, void *userdata) {
1565         struct CtdlMessage *msg = NULL;
1566         char *instr = NULL;
1567         char *results = NULL;
1568         int i;
1569         int lines;
1570         int status;
1571         char buf[1024];
1572         char key[1024];
1573         char addr[1024];
1574         char dsn[1024];
1575         char envelope_from[1024];
1576         long text_msgid = (-1);
1577         int incomplete_deliveries_remaining;
1578         time_t attempted = 0L;
1579         time_t last_attempted = 0L;
1580         time_t retry = SMTP_RETRY_INTERVAL;
1581
1582         CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
1583         strcpy(envelope_from, "");
1584
1585         msg = CtdlFetchMessage(msgnum, 1);
1586         if (msg == NULL) {
1587                 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
1588                 return;
1589         }
1590
1591         instr = strdup(msg->cm_fields['M']);
1592         CtdlFreeMessage(msg);
1593
1594         /* Strip out the headers amd any other non-instruction line */
1595         lines = num_tokens(instr, '\n');
1596         for (i=0; i<lines; ++i) {
1597                 extract_token(buf, instr, i, '\n', sizeof buf);
1598                 if (num_tokens(buf, '|') < 2) {
1599                         remove_token(instr, i, '\n');
1600                         --lines;
1601                         --i;
1602                 }
1603         }
1604
1605         /* Learn the message ID and find out about recent delivery attempts */
1606         lines = num_tokens(instr, '\n');
1607         for (i=0; i<lines; ++i) {
1608                 extract_token(buf, instr, i, '\n', sizeof buf);
1609                 extract_token(key, buf, 0, '|', sizeof key);
1610                 if (!strcasecmp(key, "msgid")) {
1611                         text_msgid = extract_long(buf, 1);
1612                 }
1613                 if (!strcasecmp(key, "envelope_from")) {
1614                         extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
1615                 }
1616                 if (!strcasecmp(key, "retry")) {
1617                         /* double the retry interval after each attempt */
1618                         retry = extract_long(buf, 1) * 2L;
1619                         if (retry > SMTP_RETRY_MAX) {
1620                                 retry = SMTP_RETRY_MAX;
1621                         }
1622                         remove_token(instr, i, '\n');
1623                 }
1624                 if (!strcasecmp(key, "attempted")) {
1625                         attempted = extract_long(buf, 1);
1626                         if (attempted > last_attempted)
1627                                 last_attempted = attempted;
1628                 }
1629         }
1630
1631         /*
1632          * Postpone delivery if we've already tried recently.
1633          */
1634         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1635                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
1636                 free(instr);
1637                 return;
1638         }
1639
1640
1641         /*
1642          * Bail out if there's no actual message associated with this
1643          */
1644         if (text_msgid < 0L) {
1645                 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
1646                 free(instr);
1647                 return;
1648         }
1649
1650         /* Plow through the instructions looking for 'remote' directives and
1651          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1652          * were experienced and it's time to try again)
1653          */
1654         lines = num_tokens(instr, '\n');
1655         for (i=0; i<lines; ++i) {
1656                 extract_token(buf, instr, i, '\n', sizeof buf);
1657                 extract_token(key, buf, 0, '|', sizeof key);
1658                 extract_token(addr, buf, 1, '|', sizeof addr);
1659                 status = extract_int(buf, 2);
1660                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1661                 if ( (!strcasecmp(key, "remote"))
1662                    && ((status==0)||(status==3)||(status==4)) ) {
1663
1664                         /* Remove this "remote" instruction from the set,
1665                          * but replace the set's final newline if
1666                          * remove_token() stripped it.  It has to be there.
1667                          */
1668                         remove_token(instr, i, '\n');
1669                         if (instr[strlen(instr)-1] != '\n') {
1670                                 strcat(instr, "\n");
1671                         }
1672
1673                         --i;
1674                         --lines;
1675                         CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1676                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1677                         if (status != 2) {
1678                                 if (results == NULL) {
1679                                         results = malloc(1024);
1680                                         memset(results, 0, 1024);
1681                                 }
1682                                 else {
1683                                         results = realloc(results, strlen(results) + 1024);
1684                                 }
1685                                 snprintf(&results[strlen(results)], 1024,
1686                                         "%s|%s|%d|%s\n",
1687                                         key, addr, status, dsn);
1688                         }
1689                 }
1690         }
1691
1692         if (results != NULL) {
1693                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1694                 strcat(instr, results);
1695                 free(results);
1696         }
1697
1698
1699         /* Generate 'bounce' messages */
1700         smtp_do_bounce(instr);
1701
1702         /* Go through the delivery list, deleting completed deliveries */
1703         incomplete_deliveries_remaining = 
1704                 smtp_purge_completed_deliveries(instr);
1705
1706
1707         /*
1708          * No delivery instructions remain, so delete both the instructions
1709          * message and the message message.
1710          */
1711         if (incomplete_deliveries_remaining <= 0) {
1712                 long delmsgs[2];
1713                 delmsgs[0] = msgnum;
1714                 delmsgs[1] = text_msgid;
1715                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1716         }
1717
1718         /*
1719          * Uncompleted delivery instructions remain, so delete the old
1720          * instructions and replace with the updated ones.
1721          */
1722         if (incomplete_deliveries_remaining > 0) {
1723                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1724                 msg = malloc(sizeof(struct CtdlMessage));
1725                 memset(msg, 0, sizeof(struct CtdlMessage));
1726                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1727                 msg->cm_anon_type = MES_NORMAL;
1728                 msg->cm_format_type = FMT_RFC822;
1729                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1730                 snprintf(msg->cm_fields['M'],
1731                         strlen(instr)+SIZ,
1732                         "Content-type: %s\n\n%s\n"
1733                         "attempted|%ld\n"
1734                         "retry|%ld\n",
1735                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1736                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1737                 CtdlFreeMessage(msg);
1738         }
1739
1740         free(instr);
1741 }
1742
1743
1744
1745
1746 /*
1747  * smtp_queue_thread()
1748  * 
1749  * Run through the queue sending out messages.
1750  */
1751 void *smtp_queue_thread(void *arg) {
1752         int num_processed = 0;
1753         struct CitContext smtp_queue_CC;
1754
1755         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1756         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1757         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1758
1759         while (!CtdlThreadCheckStop()) {
1760                 
1761                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1762
1763                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1764                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1765                 }
1766                 else {
1767                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1768                 }
1769                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1770                 CtdlThreadSleep(60);
1771         }
1772
1773         CtdlClearSystemContext();
1774         return(NULL);
1775 }
1776
1777
1778
1779 /*****************************************************************************/
1780 /*                          SMTP UTILITY COMMANDS                            */
1781 /*****************************************************************************/
1782
1783 void cmd_smtp(char *argbuf) {
1784         char cmd[64];
1785         char node[256];
1786         char buf[1024];
1787         int i;
1788         int num_mxhosts;
1789
1790         if (CtdlAccessCheck(ac_aide)) return;
1791
1792         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1793
1794         if (!strcasecmp(cmd, "mx")) {
1795                 extract_token(node, argbuf, 1, '|', sizeof node);
1796                 num_mxhosts = getmx(buf, node);
1797                 cprintf("%d %d MX hosts listed for %s\n",
1798                         LISTING_FOLLOWS, num_mxhosts, node);
1799                 for (i=0; i<num_mxhosts; ++i) {
1800                         extract_token(node, buf, i, '|', sizeof node);
1801                         cprintf("%s\n", node);
1802                 }
1803                 cprintf("000\n");
1804                 return;
1805         }
1806
1807         else if (!strcasecmp(cmd, "runqueue")) {
1808                 run_queue_now = 1;
1809                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1810                 return;
1811         }
1812
1813         else {
1814                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1815         }
1816
1817 }
1818
1819
1820 /*
1821  * Initialize the SMTP outbound queue
1822  */
1823 void smtp_init_spoolout(void) {
1824         struct ctdlroom qrbuf;
1825
1826         /*
1827          * Create the room.  This will silently fail if the room already
1828          * exists, and that's perfectly ok, because we want it to exist.
1829          */
1830         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1831
1832         /*
1833          * Make sure it's set to be a "system room" so it doesn't show up
1834          * in the <K>nown rooms list for Aides.
1835          */
1836         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1837                 qrbuf.QRflags2 |= QR2_SYSTEM;
1838                 CtdlPutRoomLock(&qrbuf);
1839         }
1840 }
1841
1842
1843
1844
1845 /*****************************************************************************/
1846 /*                      MODULE INITIALIZATION STUFF                          */
1847 /*****************************************************************************/
1848 /*
1849  * This cleanup function blows away the temporary memory used by
1850  * the SMTP server.
1851  */
1852 void smtp_cleanup_function(void) {
1853
1854         /* Don't do this stuff if this is not an SMTP session! */
1855         if (CC->h_command_function != smtp_command_loop) return;
1856
1857         CtdlLogPrintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1858         free(SMTP);
1859 }
1860
1861
1862
1863 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
1864 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
1865 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
1866 const char *CitadelServiceSMTP_LMTP="LMTP";
1867 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
1868
1869 CTDL_MODULE_INIT(smtp)
1870 {
1871         if (!threading)
1872         {
1873                 CtdlRegisterServiceHook(config.c_smtp_port,     /* SMTP MTA */
1874                                         NULL,
1875                                         smtp_mta_greeting,
1876                                         smtp_command_loop,
1877                                         NULL, 
1878                                         CitadelServiceSMTP_MTA);
1879
1880 #ifdef HAVE_OPENSSL
1881                 CtdlRegisterServiceHook(config.c_smtps_port,
1882                                         NULL,
1883                                         smtps_greeting,
1884                                         smtp_command_loop,
1885                                         NULL,
1886                                         CitadelServiceSMTPS_MTA);
1887 #endif
1888
1889                 CtdlRegisterServiceHook(config.c_msa_port,      /* SMTP MSA */
1890                                         NULL,
1891                                         smtp_msa_greeting,
1892                                         smtp_command_loop,
1893                                         NULL,
1894                                         CitadelServiceSMTP_MSA);
1895
1896                 CtdlRegisterServiceHook(0,                      /* local LMTP */
1897                                         file_lmtp_socket,
1898                                         lmtp_greeting,
1899                                         smtp_command_loop,
1900                                         NULL,
1901                                         CitadelServiceSMTP_LMTP);
1902
1903                 CtdlRegisterServiceHook(0,                      /* local LMTP */
1904                                         file_lmtp_unfiltered_socket,
1905                                         lmtp_unfiltered_greeting,
1906                                         smtp_command_loop,
1907                                         NULL,
1908                                         CitadelServiceSMTP_LMTP_UNF);
1909
1910                 smtp_init_spoolout();
1911                 CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1912                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1913                 CtdlThreadCreate("SMTP Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1914         }
1915         
1916         /* return our Subversion id for the Log */
1917         return "smtp";
1918 }