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