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