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