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