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