]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* bounce messages need subjects
[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         if (haschar(SMTP->from, '<') > 0) {
523                 stripallbut(SMTP->from, '<', '>');
524         }
525
526         /* We used to reject empty sender names, until it was brought to our
527          * attention that RFC1123 5.2.9 requires that this be allowed.  So now
528          * we allow it, but replace the empty string with a fake
529          * address so we don't have to contend with the empty string causing
530          * other code to fail when it's expecting something there.
531          */
532         if (strlen(SMTP->from) == 0) {
533                 strcpy(SMTP->from, "someone@somewhere.org");
534         }
535
536         /* If this SMTP connection is from a logged-in user, force the 'from'
537          * to be the user's Internet e-mail address as Citadel knows it.
538          */
539         if (CC->logged_in) {
540                 safestrncpy(SMTP->from, CC->cs_inet_email, sizeof SMTP->from);
541                 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
542                 SMTP->message_originated_locally = 1;
543                 return;
544         }
545
546         else if (SMTP->is_lmtp) {
547                 /* Bypass forgery checking for LMTP */
548         }
549
550         /* Otherwise, make sure outsiders aren't trying to forge mail from
551          * this system.
552          */
553         else {
554                 process_rfc822_addr(SMTP->from, user, node, name);
555                 if (CtdlHostAlias(node) != hostalias_nomatch) {
556                         cprintf("550 5.1.8 "
557                                 "You must log in to send mail from %s\r\n",
558                                 node);
559                         strcpy(SMTP->from, "");
560                         return;
561                 }
562         }
563
564         cprintf("250 2.0.0 Sender ok\r\n");
565 }
566
567
568
569 /*
570  * Implements the "RCPT To:" command
571  */
572 void smtp_rcpt(char *argbuf) {
573         char recp[SIZ];
574         char message_to_spammer[SIZ];
575         struct recptypes *valid = NULL;
576
577         if (strlen(SMTP->from) == 0) {
578                 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
579                 return;
580         }
581
582         if (strncasecmp(argbuf, "To:", 3)) {
583                 cprintf("501 5.1.7 Syntax error\r\n");
584                 return;
585         }
586
587         if ( (SMTP->is_msa) && (!CC->logged_in) ) {
588                 cprintf("550 5.1.8 "
589                         "You must log in to send mail on this port.\r\n");
590                 strcpy(SMTP->from, "");
591                 return;
592         }
593
594         strcpy(recp, &argbuf[3]);
595         striplt(recp);
596         stripallbut(recp, '<', '>');
597
598         if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
599                 cprintf("452 4.5.3 Too many recipients\r\n");
600                 return;
601         }
602
603         /* RBL check */
604         if ( (!CC->logged_in)
605            && (!SMTP->is_lmtp) ) {
606                 if (rbl_check(message_to_spammer)) {
607                         cprintf("550 %s\r\n", message_to_spammer);
608                         /* no need to free(valid), it's not allocated yet */
609                         return;
610                 }
611         }
612
613         valid = validate_recipients(recp);
614         if (valid->num_error > 0) {
615                 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
616                 free(valid);
617                 return;
618         }
619
620         if (valid->num_internet > 0) {
621                 if (CC->logged_in) {
622                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
623                                 cprintf("551 5.7.1 <%s> - you do not have permission to send Internet mail\r\n", recp);
624                                 free(valid);
625                                 return;
626                         }
627                 }
628         }
629
630         if (valid->num_internet > 0) {
631                 if ( (SMTP->message_originated_locally == 0)
632                    && (SMTP->is_lmtp == 0) ) {
633                         cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
634                         free(valid);
635                         return;
636                 }
637         }
638
639         cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
640         if (strlen(SMTP->recipients) > 0) {
641                 strcat(SMTP->recipients, ",");
642         }
643         strcat(SMTP->recipients, recp);
644         SMTP->number_of_recipients += 1;
645 }
646
647
648
649
650 /*
651  * Implements the DATA command
652  */
653 void smtp_data(void) {
654         char *body;
655         struct CtdlMessage *msg;
656         long msgnum;
657         char nowstamp[SIZ];
658         struct recptypes *valid;
659         int scan_errors;
660         int i;
661         char result[SIZ];
662
663         if (strlen(SMTP->from) == 0) {
664                 cprintf("503 5.5.1 Need MAIL command first.\r\n");
665                 return;
666         }
667
668         if (SMTP->number_of_recipients < 1) {
669                 cprintf("503 5.5.1 Need RCPT command first.\r\n");
670                 return;
671         }
672
673         cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
674         
675         datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
676         body = malloc(4096);
677
678         if (body != NULL) snprintf(body, 4096,
679                 "Received: from %s (%s [%s])\n"
680                 "       by %s; %s\n",
681                         SMTP->helo_node,
682                         CC->cs_host,
683                         CC->cs_addr,
684                         config.c_fqdn,
685                         nowstamp);
686         
687         body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
688         if (body == NULL) {
689                 cprintf("550 5.6.5 "
690                         "Unable to save message: internal error.\r\n");
691                 return;
692         }
693
694         lprintf(CTDL_DEBUG, "Converting message...\n");
695         msg = convert_internet_message(body);
696
697         /* If the user is locally authenticated, FORCE the From: header to
698          * show up as the real sender.  Yes, this violates the RFC standard,
699          * but IT MAKES SENSE.  If you prefer strict RFC adherence over
700          * common sense, you can disable this in the configuration.
701          *
702          * We also set the "message room name" ('O' field) to MAILROOM
703          * (which is Mail> on most systems) to prevent it from getting set
704          * to something ugly like "0000058008.Sent Items>" when the message
705          * is read with a Citadel client.
706          */
707         if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
708                 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
709                 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
710                 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
711                 if (msg->cm_fields['F'] != NULL) free(msg->cm_fields['F']);
712                 if (msg->cm_fields['O'] != NULL) free(msg->cm_fields['O']);
713                 msg->cm_fields['A'] = strdup(CC->user.fullname);
714                 msg->cm_fields['N'] = strdup(config.c_nodename);
715                 msg->cm_fields['H'] = strdup(config.c_humannode);
716                 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
717                 msg->cm_fields['O'] = strdup(MAILROOM);
718         }
719
720         /* Submit the message into the Citadel system. */
721         valid = validate_recipients(SMTP->recipients);
722
723         /* If there are modules that want to scan this message before final
724          * submission (such as virus checkers or spam filters), call them now
725          * and give them an opportunity to reject the message.
726          */
727         scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
728
729         if (scan_errors > 0) {  /* We don't want this message! */
730
731                 if (msg->cm_fields['0'] == NULL) {
732                         msg->cm_fields['0'] = strdup(
733                                 "5.7.1 Message rejected by filter");
734                 }
735
736                 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
737         }
738         
739         else {                  /* Ok, we'll accept this message. */
740                 msgnum = CtdlSubmitMsg(msg, valid, "");
741                 if (msgnum > 0L) {
742                         sprintf(result, "250 2.0.0 Message accepted.\r\n");
743                 }
744                 else {
745                         sprintf(result, "550 5.5.0 Internal delivery error\r\n");
746                 }
747         }
748
749         /* For SMTP and ESTMP, just print the result message.  For LMTP, we
750          * have to print one result message for each recipient.  Since there
751          * is nothing in Citadel which would cause different recipients to
752          * have different results, we can get away with just spitting out the
753          * same message once for each recipient.
754          */
755         if (SMTP->is_lmtp) {
756                 for (i=0; i<SMTP->number_of_recipients; ++i) {
757                         cprintf("%s", result);
758                 }
759         }
760         else {
761                 cprintf("%s", result);
762         }
763
764         CtdlFreeMessage(msg);
765         free(valid);
766         smtp_data_clear();      /* clear out the buffers now */
767 }
768
769
770 /*
771  * implements the STARTTLS command (Citadel API version)
772  */
773 #ifdef HAVE_OPENSSL
774 void smtp_starttls(void)
775 {
776         char ok_response[SIZ];
777         char nosup_response[SIZ];
778         char error_response[SIZ];
779
780         sprintf(ok_response,
781                 "200 2.0.0 Begin TLS negotiation now\r\n");
782         sprintf(nosup_response,
783                 "554 5.7.3 TLS not supported here\r\n");
784         sprintf(error_response,
785                 "554 5.7.3 Internal error\r\n");
786         CtdlStartTLS(ok_response, nosup_response, error_response);
787         smtp_rset(0);
788 }
789 #endif
790
791
792
793 /* 
794  * Main command loop for SMTP sessions.
795  */
796 void smtp_command_loop(void) {
797         char cmdbuf[SIZ];
798
799         time(&CC->lastcmd);
800         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
801         if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
802                 lprintf(CTDL_CRIT, "SMTP socket is broken.  Ending session.\n");
803                 CC->kill_me = 1;
804                 return;
805         }
806         lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
807         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
808
809         if (SMTP->command_state == smtp_user) {
810                 smtp_get_user(cmdbuf);
811         }
812
813         else if (SMTP->command_state == smtp_password) {
814                 smtp_get_pass(cmdbuf);
815         }
816
817         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
818                 smtp_auth(&cmdbuf[5]);
819         }
820
821         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
822                 smtp_data();
823         }
824
825         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
826                 smtp_expn(&cmdbuf[5]);
827         }
828
829         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
830                 smtp_hello(&cmdbuf[5], 0);
831         }
832
833         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
834                 smtp_hello(&cmdbuf[5], 1);
835         }
836
837         else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
838                 smtp_hello(&cmdbuf[5], 2);
839         }
840
841         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
842                 smtp_help();
843         }
844
845         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
846                 smtp_mail(&cmdbuf[5]);
847         }
848
849         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
850                 cprintf("250 NOOP\r\n");
851         }
852
853         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
854                 cprintf("221 Goodbye...\r\n");
855                 CC->kill_me = 1;
856                 return;
857         }
858
859         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
860                 smtp_rcpt(&cmdbuf[5]);
861         }
862
863         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
864                 smtp_rset(1);
865         }
866 #ifdef HAVE_OPENSSL
867         else if (!strcasecmp(cmdbuf, "STARTTLS")) {
868                 smtp_starttls();
869         }
870 #endif
871         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
872                 smtp_vrfy(&cmdbuf[5]);
873         }
874
875         else {
876                 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
877         }
878
879
880 }
881
882
883
884
885 /*****************************************************************************/
886 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
887 /*****************************************************************************/
888
889
890
891 /*
892  * smtp_try()
893  *
894  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
895  *
896  */
897 void smtp_try(const char *key, const char *addr, int *status,
898               char *dsn, size_t n, long msgnum)
899 {
900         int sock = (-1);
901         char mxhosts[1024];
902         int num_mxhosts;
903         int mx;
904         int i;
905         char user[SIZ], node[SIZ], name[SIZ];
906         char buf[1024];
907         char mailfrom[1024];
908         int lp, rp;
909         char *msgtext;
910         char *ptr;
911         size_t msg_size;
912         int scan_done;
913
914         /* Parse out the host portion of the recipient address */
915         process_rfc822_addr(addr, user, node, name);
916
917         lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
918                 user, node, name);
919
920         /* Load the message out of the database */
921         CC->redirect_buffer = malloc(SIZ);
922         CC->redirect_len = 0;
923         CC->redirect_alloc = SIZ;
924         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
925         msgtext = CC->redirect_buffer;
926         msg_size = CC->redirect_len;
927         CC->redirect_buffer = NULL;
928         CC->redirect_len = 0;
929         CC->redirect_alloc = 0;
930
931         /* Extract something to send later in the 'MAIL From:' command */
932         strcpy(mailfrom, "");
933         scan_done = 0;
934         ptr = msgtext;
935         do {
936                 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
937                         scan_done = 1;
938                 }
939                 if (!strncasecmp(buf, "From:", 5)) {
940                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
941                         striplt(mailfrom);
942                         for (i=0; i<strlen(mailfrom); ++i) {
943                                 if (!isprint(mailfrom[i])) {
944                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
945                                         i=0;
946                                 }
947                         }
948
949                         /* Strip out parenthesized names */
950                         lp = (-1);
951                         rp = (-1);
952                         for (i=0; i<strlen(mailfrom); ++i) {
953                                 if (mailfrom[i] == '(') lp = i;
954                                 if (mailfrom[i] == ')') rp = i;
955                         }
956                         if ((lp>0)&&(rp>lp)) {
957                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
958                         }
959
960                         /* Prefer brokketized names */
961                         lp = (-1);
962                         rp = (-1);
963                         for (i=0; i<strlen(mailfrom); ++i) {
964                                 if (mailfrom[i] == '<') lp = i;
965                                 if (mailfrom[i] == '>') rp = i;
966                         }
967                         if ( (lp>=0) && (rp>lp) ) {
968                                 mailfrom[rp] = 0;
969                                 strcpy(mailfrom, &mailfrom[lp]);
970                         }
971
972                         scan_done = 1;
973                 }
974         } while (scan_done == 0);
975         if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
976         stripallbut(mailfrom, '<', '>');
977
978         /* Figure out what mail exchanger host we have to connect to */
979         num_mxhosts = getmx(mxhosts, node);
980         lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
981         if (num_mxhosts < 1) {
982                 *status = 5;
983                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
984                 return;
985         }
986
987         sock = (-1);
988         for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
989                 extract_token(buf, mxhosts, mx, '|', sizeof buf);
990                 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
991                 sock = sock_connect(buf, "25", "tcp");
992                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
993                 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
994                 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
995         }
996
997         if (sock < 0) {
998                 *status = 4;    /* dsn is already filled in */
999                 return;
1000         }
1001
1002         /* Process the SMTP greeting from the server */
1003         if (ml_sock_gets(sock, buf) < 0) {
1004                 *status = 4;
1005                 strcpy(dsn, "Connection broken during SMTP conversation");
1006                 goto bail;
1007         }
1008         lprintf(CTDL_DEBUG, "<%s\n", buf);
1009         if (buf[0] != '2') {
1010                 if (buf[0] == '4') {
1011                         *status = 4;
1012                         safestrncpy(dsn, &buf[4], 1023);
1013                         goto bail;
1014                 }
1015                 else {
1016                         *status = 5;
1017                         safestrncpy(dsn, &buf[4], 1023);
1018                         goto bail;
1019                 }
1020         }
1021
1022         /* At this point we know we are talking to a real SMTP server */
1023
1024         /* Do a HELO command */
1025         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1026         lprintf(CTDL_DEBUG, ">%s", buf);
1027         sock_write(sock, buf, strlen(buf));
1028         if (ml_sock_gets(sock, buf) < 0) {
1029                 *status = 4;
1030                 strcpy(dsn, "Connection broken during SMTP HELO");
1031                 goto bail;
1032         }
1033         lprintf(CTDL_DEBUG, "<%s\n", buf);
1034         if (buf[0] != '2') {
1035                 if (buf[0] == '4') {
1036                         *status = 4;
1037                         safestrncpy(dsn, &buf[4], 1023);
1038                         goto bail;
1039                 }
1040                 else {
1041                         *status = 5;
1042                         safestrncpy(dsn, &buf[4], 1023);
1043                         goto bail;
1044                 }
1045         }
1046
1047         /* HELO succeeded, now try the MAIL From: command */
1048         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1049         lprintf(CTDL_DEBUG, ">%s", buf);
1050         sock_write(sock, buf, strlen(buf));
1051         if (ml_sock_gets(sock, buf) < 0) {
1052                 *status = 4;
1053                 strcpy(dsn, "Connection broken during SMTP MAIL");
1054                 goto bail;
1055         }
1056         lprintf(CTDL_DEBUG, "<%s\n", buf);
1057         if (buf[0] != '2') {
1058                 if (buf[0] == '4') {
1059                         *status = 4;
1060                         safestrncpy(dsn, &buf[4], 1023);
1061                         goto bail;
1062                 }
1063                 else {
1064                         *status = 5;
1065                         safestrncpy(dsn, &buf[4], 1023);
1066                         goto bail;
1067                 }
1068         }
1069
1070         /* MAIL succeeded, now try the RCPT To: command */
1071         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1072         lprintf(CTDL_DEBUG, ">%s", buf);
1073         sock_write(sock, buf, strlen(buf));
1074         if (ml_sock_gets(sock, buf) < 0) {
1075                 *status = 4;
1076                 strcpy(dsn, "Connection broken during SMTP RCPT");
1077                 goto bail;
1078         }
1079         lprintf(CTDL_DEBUG, "<%s\n", buf);
1080         if (buf[0] != '2') {
1081                 if (buf[0] == '4') {
1082                         *status = 4;
1083                         safestrncpy(dsn, &buf[4], 1023);
1084                         goto bail;
1085                 }
1086                 else {
1087                         *status = 5;
1088                         safestrncpy(dsn, &buf[4], 1023);
1089                         goto bail;
1090                 }
1091         }
1092
1093         /* RCPT succeeded, now try the DATA command */
1094         lprintf(CTDL_DEBUG, ">DATA\n");
1095         sock_write(sock, "DATA\r\n", 6);
1096         if (ml_sock_gets(sock, buf) < 0) {
1097                 *status = 4;
1098                 strcpy(dsn, "Connection broken during SMTP DATA");
1099                 goto bail;
1100         }
1101         lprintf(CTDL_DEBUG, "<%s\n", buf);
1102         if (buf[0] != '3') {
1103                 if (buf[0] == '4') {
1104                         *status = 3;
1105                         safestrncpy(dsn, &buf[4], 1023);
1106                         goto bail;
1107                 }
1108                 else {
1109                         *status = 5;
1110                         safestrncpy(dsn, &buf[4], 1023);
1111                         goto bail;
1112                 }
1113         }
1114
1115         /* If we reach this point, the server is expecting data */
1116         sock_write(sock, msgtext, msg_size);
1117         if (msgtext[msg_size-1] != 10) {
1118                 lprintf(CTDL_WARNING, "Possible problem: message did not "
1119                         "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1120                                 buf[msg_size-1]);
1121         }
1122
1123         sock_write(sock, ".\r\n", 3);
1124         if (ml_sock_gets(sock, buf) < 0) {
1125                 *status = 4;
1126                 strcpy(dsn, "Connection broken during SMTP message transmit");
1127                 goto bail;
1128         }
1129         lprintf(CTDL_DEBUG, "%s\n", buf);
1130         if (buf[0] != '2') {
1131                 if (buf[0] == '4') {
1132                         *status = 4;
1133                         safestrncpy(dsn, &buf[4], 1023);
1134                         goto bail;
1135                 }
1136                 else {
1137                         *status = 5;
1138                         safestrncpy(dsn, &buf[4], 1023);
1139                         goto bail;
1140                 }
1141         }
1142
1143         /* We did it! */
1144         safestrncpy(dsn, &buf[4], 1023);
1145         *status = 2;
1146
1147         lprintf(CTDL_DEBUG, ">QUIT\n");
1148         sock_write(sock, "QUIT\r\n", 6);
1149         ml_sock_gets(sock, buf);
1150         lprintf(CTDL_DEBUG, "<%s\n", buf);
1151         lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1152                 user, node, name);
1153
1154 bail:   free(msgtext);
1155         sock_close(sock);
1156         return;
1157 }
1158
1159
1160
1161 /*
1162  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1163  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1164  * a "bounce" message (delivery status notification).
1165  */
1166 void smtp_do_bounce(char *instr) {
1167         int i;
1168         int lines;
1169         int status;
1170         char buf[1024];
1171         char key[1024];
1172         char addr[1024];
1173         char dsn[1024];
1174         char bounceto[1024];
1175         int num_bounces = 0;
1176         int bounce_this = 0;
1177         long bounce_msgid = (-1);
1178         time_t submitted = 0L;
1179         struct CtdlMessage *bmsg = NULL;
1180         int give_up = 0;
1181         struct recptypes *valid;
1182         int successful_bounce = 0;
1183
1184         lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1185         strcpy(bounceto, "");
1186
1187         lines = num_tokens(instr, '\n');
1188
1189
1190         /* See if it's time to give up on delivery of this message */
1191         for (i=0; i<lines; ++i) {
1192                 extract_token(buf, instr, i, '\n', sizeof buf);
1193                 extract_token(key, buf, 0, '|', sizeof key);
1194                 extract_token(addr, buf, 1, '|', sizeof addr);
1195                 if (!strcasecmp(key, "submitted")) {
1196                         submitted = atol(addr);
1197                 }
1198         }
1199
1200         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1201                 give_up = 1;
1202         }
1203
1204
1205
1206         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1207         if (bmsg == NULL) return;
1208         memset(bmsg, 0, sizeof(struct CtdlMessage));
1209
1210         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1211         bmsg->cm_anon_type = MES_NORMAL;
1212         bmsg->cm_format_type = 1;
1213         bmsg->cm_fields['A'] = strdup("Citadel");
1214         bmsg->cm_fields['O'] = strdup(MAILROOM);
1215         bmsg->cm_fields['N'] = strdup(config.c_nodename);
1216         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1217
1218         if (give_up) bmsg->cm_fields['M'] = strdup(
1219 "A message you sent could not be delivered to some or all of its recipients\n"
1220 "due to prolonged unavailability of its destination(s).\n"
1221 "Giving up on the following addresses:\n\n"
1222 );
1223
1224         else bmsg->cm_fields['M'] = strdup(
1225 "A message you sent could not be delivered to some or all of its recipients.\n"
1226 "The following addresses were undeliverable:\n\n"
1227 );
1228
1229         /*
1230          * Now go through the instructions checking for stuff.
1231          */
1232         for (i=0; i<lines; ++i) {
1233                 extract_token(buf, instr, i, '\n', sizeof buf);
1234                 extract_token(key, buf, 0, '|', sizeof key);
1235                 extract_token(addr, buf, 1, '|', sizeof addr);
1236                 status = extract_int(buf, 2);
1237                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1238                 bounce_this = 0;
1239
1240                 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1241                         key, addr, status, dsn);
1242
1243                 if (!strcasecmp(key, "bounceto")) {
1244                         strcpy(bounceto, addr);
1245                 }
1246
1247                 if (
1248                    (!strcasecmp(key, "local"))
1249                    || (!strcasecmp(key, "remote"))
1250                    || (!strcasecmp(key, "ignet"))
1251                    || (!strcasecmp(key, "room"))
1252                 ) {
1253                         if (status == 5) bounce_this = 1;
1254                         if (give_up) bounce_this = 1;
1255                 }
1256
1257                 if (bounce_this) {
1258                         ++num_bounces;
1259
1260                         if (bmsg->cm_fields['M'] == NULL) {
1261                                 lprintf(CTDL_ERR, "ERROR ... M field is null "
1262                                         "(%s:%d)\n", __FILE__, __LINE__);
1263                         }
1264
1265                         bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1266                                 strlen(bmsg->cm_fields['M']) + 1024 );
1267                         strcat(bmsg->cm_fields['M'], addr);
1268                         strcat(bmsg->cm_fields['M'], ": ");
1269                         strcat(bmsg->cm_fields['M'], dsn);
1270                         strcat(bmsg->cm_fields['M'], "\n");
1271
1272                         remove_token(instr, i, '\n');
1273                         --i;
1274                         --lines;
1275                 }
1276         }
1277
1278         /* Deliver the bounce if there's anything worth mentioning */
1279         lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1280         if (num_bounces > 0) {
1281
1282                 /* First try the user who sent the message */
1283                 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1284                 if (strlen(bounceto) == 0) {
1285                         lprintf(CTDL_ERR, "No bounce address specified\n");
1286                         bounce_msgid = (-1L);
1287                 }
1288
1289                 /* Can we deliver the bounce to the original sender? */
1290                 valid = validate_recipients(bounceto);
1291                 if (valid != NULL) {
1292                         if (valid->num_error == 0) {
1293                                 CtdlSubmitMsg(bmsg, valid, "");
1294                                 successful_bounce = 1;
1295                         }
1296                 }
1297
1298                 /* If not, post it in the Aide> room */
1299                 if (successful_bounce == 0) {
1300                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1301                 }
1302
1303                 /* Free up the memory we used */
1304                 if (valid != NULL) {
1305                         free(valid);
1306                 }
1307         }
1308
1309         CtdlFreeMessage(bmsg);
1310         lprintf(CTDL_DEBUG, "Done processing bounces\n");
1311 }
1312
1313
1314 /*
1315  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1316  * set of delivery instructions for completed deliveries and remove them.
1317  *
1318  * It returns the number of incomplete deliveries remaining.
1319  */
1320 int smtp_purge_completed_deliveries(char *instr) {
1321         int i;
1322         int lines;
1323         int status;
1324         char buf[1024];
1325         char key[1024];
1326         char addr[1024];
1327         char dsn[1024];
1328         int completed;
1329         int incomplete = 0;
1330
1331         lines = num_tokens(instr, '\n');
1332         for (i=0; i<lines; ++i) {
1333                 extract_token(buf, instr, i, '\n', sizeof buf);
1334                 extract_token(key, buf, 0, '|', sizeof key);
1335                 extract_token(addr, buf, 1, '|', sizeof addr);
1336                 status = extract_int(buf, 2);
1337                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1338
1339                 completed = 0;
1340
1341                 if (
1342                    (!strcasecmp(key, "local"))
1343                    || (!strcasecmp(key, "remote"))
1344                    || (!strcasecmp(key, "ignet"))
1345                    || (!strcasecmp(key, "room"))
1346                 ) {
1347                         if (status == 2) completed = 1;
1348                         else ++incomplete;
1349                 }
1350
1351                 if (completed) {
1352                         remove_token(instr, i, '\n');
1353                         --i;
1354                         --lines;
1355                 }
1356         }
1357
1358         return(incomplete);
1359 }
1360
1361
1362 /*
1363  * smtp_do_procmsg()
1364  *
1365  * Called by smtp_do_queue() to handle an individual message.
1366  */
1367 void smtp_do_procmsg(long msgnum, void *userdata) {
1368         struct CtdlMessage *msg;
1369         char *instr = NULL;
1370         char *results = NULL;
1371         int i;
1372         int lines;
1373         int status;
1374         char buf[1024];
1375         char key[1024];
1376         char addr[1024];
1377         char dsn[1024];
1378         long text_msgid = (-1);
1379         int incomplete_deliveries_remaining;
1380         time_t attempted = 0L;
1381         time_t last_attempted = 0L;
1382         time_t retry = SMTP_RETRY_INTERVAL;
1383
1384         lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1385
1386         msg = CtdlFetchMessage(msgnum, 1);
1387         if (msg == NULL) {
1388                 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1389                 return;
1390         }
1391
1392         instr = strdup(msg->cm_fields['M']);
1393         CtdlFreeMessage(msg);
1394
1395         /* Strip out the headers amd any other non-instruction line */
1396         lines = num_tokens(instr, '\n');
1397         for (i=0; i<lines; ++i) {
1398                 extract_token(buf, instr, i, '\n', sizeof buf);
1399                 if (num_tokens(buf, '|') < 2) {
1400                         remove_token(instr, i, '\n');
1401                         --lines;
1402                         --i;
1403                 }
1404         }
1405
1406         /* Learn the message ID and find out about recent delivery attempts */
1407         lines = num_tokens(instr, '\n');
1408         for (i=0; i<lines; ++i) {
1409                 extract_token(buf, instr, i, '\n', sizeof buf);
1410                 extract_token(key, buf, 0, '|', sizeof key);
1411                 if (!strcasecmp(key, "msgid")) {
1412                         text_msgid = extract_long(buf, 1);
1413                 }
1414                 if (!strcasecmp(key, "retry")) {
1415                         /* double the retry interval after each attempt */
1416                         retry = extract_long(buf, 1) * 2L;
1417                         if (retry > SMTP_RETRY_MAX) {
1418                                 retry = SMTP_RETRY_MAX;
1419                         }
1420                         remove_token(instr, i, '\n');
1421                 }
1422                 if (!strcasecmp(key, "attempted")) {
1423                         attempted = extract_long(buf, 1);
1424                         if (attempted > last_attempted)
1425                                 last_attempted = attempted;
1426                 }
1427         }
1428
1429         /*
1430          * Postpone delivery if we've already tried recently.
1431          */
1432         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1433                 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1434                 free(instr);
1435                 return;
1436         }
1437
1438
1439         /*
1440          * Bail out if there's no actual message associated with this
1441          */
1442         if (text_msgid < 0L) {
1443                 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1444                 free(instr);
1445                 return;
1446         }
1447
1448         /* Plow through the instructions looking for 'remote' directives and
1449          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1450          * were experienced and it's time to try again)
1451          */
1452         lines = num_tokens(instr, '\n');
1453         for (i=0; i<lines; ++i) {
1454                 extract_token(buf, instr, i, '\n', sizeof buf);
1455                 extract_token(key, buf, 0, '|', sizeof key);
1456                 extract_token(addr, buf, 1, '|', sizeof addr);
1457                 status = extract_int(buf, 2);
1458                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1459                 if ( (!strcasecmp(key, "remote"))
1460                    && ((status==0)||(status==3)||(status==4)) ) {
1461
1462                         /* Remove this "remote" instruction from the set,
1463                          * but replace the set's final newline if
1464                          * remove_token() stripped it.  It has to be there.
1465                          */
1466                         remove_token(instr, i, '\n');
1467                         if (instr[strlen(instr)-1] != '\n') {
1468                                 strcat(instr, "\n");
1469                         }
1470
1471                         --i;
1472                         --lines;
1473                         lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1474                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1475                         if (status != 2) {
1476                                 if (results == NULL) {
1477                                         results = malloc(1024);
1478                                         memset(results, 0, 1024);
1479                                 }
1480                                 else {
1481                                         results = realloc(results,
1482                                                 strlen(results) + 1024);
1483                                 }
1484                                 snprintf(&results[strlen(results)], 1024,
1485                                         "%s|%s|%d|%s\n",
1486                                         key, addr, status, dsn);
1487                         }
1488                 }
1489         }
1490
1491         if (results != NULL) {
1492                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1493                 strcat(instr, results);
1494                 free(results);
1495         }
1496
1497
1498         /* Generate 'bounce' messages */
1499         smtp_do_bounce(instr);
1500
1501         /* Go through the delivery list, deleting completed deliveries */
1502         incomplete_deliveries_remaining = 
1503                 smtp_purge_completed_deliveries(instr);
1504
1505
1506         /*
1507          * No delivery instructions remain, so delete both the instructions
1508          * message and the message message.
1509          */
1510         if (incomplete_deliveries_remaining <= 0) {
1511                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1512                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1513         }
1514
1515
1516         /*
1517          * Uncompleted delivery instructions remain, so delete the old
1518          * instructions and replace with the updated ones.
1519          */
1520         if (incomplete_deliveries_remaining > 0) {
1521                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1522                 msg = malloc(sizeof(struct CtdlMessage));
1523                 memset(msg, 0, sizeof(struct CtdlMessage));
1524                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1525                 msg->cm_anon_type = MES_NORMAL;
1526                 msg->cm_format_type = FMT_RFC822;
1527                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1528                 snprintf(msg->cm_fields['M'],
1529                         strlen(instr)+SIZ,
1530                         "Content-type: %s\n\n%s\n"
1531                         "attempted|%ld\n"
1532                         "retry|%ld\n",
1533                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1534                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1535                 CtdlFreeMessage(msg);
1536         }
1537
1538         free(instr);
1539 }
1540
1541
1542
1543 /*
1544  * smtp_do_queue()
1545  * 
1546  * Run through the queue sending out messages.
1547  */
1548 void smtp_do_queue(void) {
1549         static int doing_queue = 0;
1550
1551         /*
1552          * This is a simple concurrency check to make sure only one queue run
1553          * is done at a time.  We could do this with a mutex, but since we
1554          * don't really require extremely fine granularity here, we'll do it
1555          * with a static variable instead.
1556          */
1557         if (doing_queue) return;
1558         doing_queue = 1;
1559
1560         /* 
1561          * Go ahead and run the queue
1562          */
1563         lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1564
1565         if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1566                 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1567                 return;
1568         }
1569         CtdlForEachMessage(MSGS_ALL, 0L,
1570                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1571
1572         lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1573         run_queue_now = 0;
1574         doing_queue = 0;
1575 }
1576
1577
1578
1579 /*****************************************************************************/
1580 /*                          SMTP UTILITY COMMANDS                            */
1581 /*****************************************************************************/
1582
1583 void cmd_smtp(char *argbuf) {
1584         char cmd[64];
1585         char node[256];
1586         char buf[1024];
1587         int i;
1588         int num_mxhosts;
1589
1590         if (CtdlAccessCheck(ac_aide)) return;
1591
1592         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1593
1594         if (!strcasecmp(cmd, "mx")) {
1595                 extract_token(node, argbuf, 1, '|', sizeof node);
1596                 num_mxhosts = getmx(buf, node);
1597                 cprintf("%d %d MX hosts listed for %s\n",
1598                         LISTING_FOLLOWS, num_mxhosts, node);
1599                 for (i=0; i<num_mxhosts; ++i) {
1600                         extract_token(node, buf, i, '|', sizeof node);
1601                         cprintf("%s\n", node);
1602                 }
1603                 cprintf("000\n");
1604                 return;
1605         }
1606
1607         else if (!strcasecmp(cmd, "runqueue")) {
1608                 run_queue_now = 1;
1609                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1610                 return;
1611         }
1612
1613         else {
1614                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1615         }
1616
1617 }
1618
1619
1620 /*
1621  * Initialize the SMTP outbound queue
1622  */
1623 void smtp_init_spoolout(void) {
1624         struct ctdlroom qrbuf;
1625
1626         /*
1627          * Create the room.  This will silently fail if the room already
1628          * exists, and that's perfectly ok, because we want it to exist.
1629          */
1630         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1631
1632         /*
1633          * Make sure it's set to be a "system room" so it doesn't show up
1634          * in the <K>nown rooms list for Aides.
1635          */
1636         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1637                 qrbuf.QRflags2 |= QR2_SYSTEM;
1638                 lputroom(&qrbuf);
1639         }
1640 }
1641
1642
1643
1644
1645 /*****************************************************************************/
1646 /*                      MODULE INITIALIZATION STUFF                          */
1647 /*****************************************************************************/
1648 /*
1649  * This cleanup function blows away the temporary memory used by
1650  * the SMTP server.
1651  */
1652 void smtp_cleanup_function(void) {
1653
1654         /* Don't do this stuff if this is not an SMTP session! */
1655         if (CC->h_command_function != smtp_command_loop) return;
1656
1657         lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1658         free(SMTP);
1659         free(SMTP_ROOMS);
1660         free(SMTP_RECPS);
1661 }
1662
1663
1664
1665
1666
1667 char *serv_smtp_init(void)
1668 {
1669         CtdlRegisterServiceHook(config.c_smtp_port,     /* SMTP MTA */
1670                                 NULL,
1671                                 smtp_greeting,
1672                                 smtp_command_loop,
1673                                 NULL);
1674
1675 #ifdef HAVE_OPENSSL
1676         CtdlRegisterServiceHook(config.c_smtps_port,
1677                                 NULL,
1678                                 smtps_greeting,
1679                                 smtp_command_loop,
1680                                 NULL);
1681 #endif
1682
1683         CtdlRegisterServiceHook(config.c_msa_port,      /* SMTP MSA */
1684                                 NULL,
1685                                 smtp_msa_greeting,
1686                                 smtp_command_loop,
1687                                 NULL);
1688
1689         CtdlRegisterServiceHook(0,                      /* local LMTP */
1690                                 "lmtp.socket",
1691                                 lmtp_greeting,
1692                                 smtp_command_loop,
1693                                 NULL);
1694
1695         smtp_init_spoolout();
1696         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1697         CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1698         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1699         return "$Id$";
1700 }