]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* removed some debug messages
[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         time(&CC->lastcmd);
747         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
748         if (client_gets(cmdbuf) < 1) {
749                 lprintf(CTDL_CRIT, "SMTP socket is broken.  Ending session.\n");
750                 CC->kill_me = 1;
751                 return;
752         }
753         lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
754         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
755
756         if (SMTP->command_state == smtp_user) {
757                 smtp_get_user(cmdbuf);
758         }
759
760         else if (SMTP->command_state == smtp_password) {
761                 smtp_get_pass(cmdbuf);
762         }
763
764         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
765                 smtp_auth(&cmdbuf[5]);
766         }
767
768         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
769                 smtp_data();
770         }
771
772         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
773                 smtp_expn(&cmdbuf[5]);
774         }
775
776         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
777                 smtp_hello(&cmdbuf[5], 0);
778         }
779
780         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
781                 smtp_hello(&cmdbuf[5], 1);
782         }
783
784         else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
785                 smtp_hello(&cmdbuf[5], 2);
786         }
787
788         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
789                 smtp_help();
790         }
791
792         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
793                 smtp_mail(&cmdbuf[5]);
794         }
795
796         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
797                 cprintf("250 NOOP\r\n");
798         }
799
800         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
801                 cprintf("221 Goodbye...\r\n");
802                 CC->kill_me = 1;
803                 return;
804         }
805
806         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
807                 smtp_rcpt(&cmdbuf[5]);
808         }
809
810         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
811                 smtp_rset(1);
812         }
813 #ifdef HAVE_OPENSSL
814         else if (!strcasecmp(cmdbuf, "STARTTLS")) {
815                 smtp_starttls();
816         }
817 #endif
818         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
819                 smtp_vrfy(&cmdbuf[5]);
820         }
821
822         else {
823                 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
824         }
825
826
827 }
828
829
830
831
832 /*****************************************************************************/
833 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
834 /*****************************************************************************/
835
836
837
838 /*
839  * smtp_try()
840  *
841  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
842  *
843  */
844 void smtp_try(const char *key, const char *addr, int *status,
845               char *dsn, size_t n, long msgnum)
846 {
847         int sock = (-1);
848         char mxhosts[1024];
849         int num_mxhosts;
850         int mx;
851         int i;
852         char user[SIZ], node[SIZ], name[SIZ];
853         char buf[1024];
854         char mailfrom[1024];
855         int lp, rp;
856         FILE *msg_fp = NULL;
857         size_t msg_size;
858         size_t blocksize = 0;
859         int scan_done;
860
861         /* Parse out the host portion of the recipient address */
862         process_rfc822_addr(addr, user, node, name);
863
864         lprintf(CTDL_DEBUG, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
865                 user, node, name);
866
867         /* Load the message out of the database into a temp file */
868         msg_fp = tmpfile();
869         if (msg_fp == NULL) {
870                 *status = 4;
871                 snprintf(dsn, n, "Error creating temporary file");
872                 return;
873         }
874         else {
875                 CtdlRedirectOutput(msg_fp, -1);
876                 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
877                 CtdlRedirectOutput(NULL, -1);
878                 fseek(msg_fp, 0L, SEEK_END);
879                 msg_size = ftell(msg_fp);
880         }
881
882
883         /* Extract something to send later in the 'MAIL From:' command */
884         strcpy(mailfrom, "");
885         rewind(msg_fp);
886         scan_done = 0;
887         do {
888                 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
889                 if (!strncasecmp(buf, "From:", 5)) {
890                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
891                         striplt(mailfrom);
892                         for (i=0; i<strlen(mailfrom); ++i) {
893                                 if (!isprint(mailfrom[i])) {
894                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
895                                         i=0;
896                                 }
897                         }
898
899                         /* Strip out parenthesized names */
900                         lp = (-1);
901                         rp = (-1);
902                         for (i=0; i<strlen(mailfrom); ++i) {
903                                 if (mailfrom[i] == '(') lp = i;
904                                 if (mailfrom[i] == ')') rp = i;
905                         }
906                         if ((lp>0)&&(rp>lp)) {
907                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
908                         }
909
910                         /* Prefer brokketized names */
911                         lp = (-1);
912                         rp = (-1);
913                         for (i=0; i<strlen(mailfrom); ++i) {
914                                 if (mailfrom[i] == '<') lp = i;
915                                 if (mailfrom[i] == '>') rp = i;
916                         }
917                         if ( (lp>=0) && (rp>lp) ) {
918                                 mailfrom[rp] = 0;
919                                 strcpy(mailfrom, &mailfrom[lp]);
920                         }
921
922                         scan_done = 1;
923                 }
924         } while (scan_done == 0);
925         if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
926
927         /* Figure out what mail exchanger host we have to connect to */
928         num_mxhosts = getmx(mxhosts, node);
929         lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
930         if (num_mxhosts < 1) {
931                 *status = 5;
932                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
933                 return;
934         }
935
936         sock = (-1);
937         for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
938                 extract(buf, mxhosts, mx);
939                 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
940                 sock = sock_connect(buf, "25", "tcp");
941                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
942                 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
943                 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
944         }
945
946         if (sock < 0) {
947                 *status = 4;    /* dsn is already filled in */
948                 return;
949         }
950
951         /* Process the SMTP greeting from the server */
952         if (ml_sock_gets(sock, buf) < 0) {
953                 *status = 4;
954                 strcpy(dsn, "Connection broken during SMTP conversation");
955                 goto bail;
956         }
957         lprintf(CTDL_DEBUG, "<%s\n", buf);
958         if (buf[0] != '2') {
959                 if (buf[0] == '4') {
960                         *status = 4;
961                         safestrncpy(dsn, &buf[4], 1023);
962                         goto bail;
963                 }
964                 else {
965                         *status = 5;
966                         safestrncpy(dsn, &buf[4], 1023);
967                         goto bail;
968                 }
969         }
970
971         /* At this point we know we are talking to a real SMTP server */
972
973         /* Do a HELO command */
974         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
975         lprintf(CTDL_DEBUG, ">%s", buf);
976         sock_write(sock, buf, strlen(buf));
977         if (ml_sock_gets(sock, buf) < 0) {
978                 *status = 4;
979                 strcpy(dsn, "Connection broken during SMTP HELO");
980                 goto bail;
981         }
982         lprintf(CTDL_DEBUG, "<%s\n", buf);
983         if (buf[0] != '2') {
984                 if (buf[0] == '4') {
985                         *status = 4;
986                         safestrncpy(dsn, &buf[4], 1023);
987                         goto bail;
988                 }
989                 else {
990                         *status = 5;
991                         safestrncpy(dsn, &buf[4], 1023);
992                         goto bail;
993                 }
994         }
995
996
997         /* HELO succeeded, now try the MAIL From: command */
998         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
999         lprintf(CTDL_DEBUG, ">%s", buf);
1000         sock_write(sock, buf, strlen(buf));
1001         if (ml_sock_gets(sock, buf) < 0) {
1002                 *status = 4;
1003                 strcpy(dsn, "Connection broken during SMTP MAIL");
1004                 goto bail;
1005         }
1006         lprintf(CTDL_DEBUG, "<%s\n", buf);
1007         if (buf[0] != '2') {
1008                 if (buf[0] == '4') {
1009                         *status = 4;
1010                         safestrncpy(dsn, &buf[4], 1023);
1011                         goto bail;
1012                 }
1013                 else {
1014                         *status = 5;
1015                         safestrncpy(dsn, &buf[4], 1023);
1016                         goto bail;
1017                 }
1018         }
1019
1020
1021         /* MAIL succeeded, now try the RCPT To: command */
1022         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1023         lprintf(CTDL_DEBUG, ">%s", buf);
1024         sock_write(sock, buf, strlen(buf));
1025         if (ml_sock_gets(sock, buf) < 0) {
1026                 *status = 4;
1027                 strcpy(dsn, "Connection broken during SMTP RCPT");
1028                 goto bail;
1029         }
1030         lprintf(CTDL_DEBUG, "<%s\n", buf);
1031         if (buf[0] != '2') {
1032                 if (buf[0] == '4') {
1033                         *status = 4;
1034                         safestrncpy(dsn, &buf[4], 1023);
1035                         goto bail;
1036                 }
1037                 else {
1038                         *status = 5;
1039                         safestrncpy(dsn, &buf[4], 1023);
1040                         goto bail;
1041                 }
1042         }
1043
1044
1045         /* RCPT succeeded, now try the DATA command */
1046         lprintf(CTDL_DEBUG, ">DATA\n");
1047         sock_write(sock, "DATA\r\n", 6);
1048         if (ml_sock_gets(sock, buf) < 0) {
1049                 *status = 4;
1050                 strcpy(dsn, "Connection broken during SMTP DATA");
1051                 goto bail;
1052         }
1053         lprintf(CTDL_DEBUG, "<%s\n", buf);
1054         if (buf[0] != '3') {
1055                 if (buf[0] == '4') {
1056                         *status = 3;
1057                         safestrncpy(dsn, &buf[4], 1023);
1058                         goto bail;
1059                 }
1060                 else {
1061                         *status = 5;
1062                         safestrncpy(dsn, &buf[4], 1023);
1063                         goto bail;
1064                 }
1065         }
1066
1067         /* If we reach this point, the server is expecting data */
1068         rewind(msg_fp);
1069         while (msg_size > 0) {
1070                 blocksize = sizeof(buf);
1071                 if (blocksize > msg_size) blocksize = msg_size;
1072                 fread(buf, blocksize, 1, msg_fp);
1073                 sock_write(sock, buf, blocksize);
1074                 msg_size -= blocksize;
1075         }
1076         if (buf[blocksize-1] != 10) {
1077                 lprintf(CTDL_WARNING, "Possible problem: message did not "
1078                         "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1079                                 buf[blocksize-1]);
1080         }
1081
1082         sock_write(sock, ".\r\n", 3);
1083         if (ml_sock_gets(sock, buf) < 0) {
1084                 *status = 4;
1085                 strcpy(dsn, "Connection broken during SMTP message transmit");
1086                 goto bail;
1087         }
1088         lprintf(CTDL_DEBUG, "%s\n", buf);
1089         if (buf[0] != '2') {
1090                 if (buf[0] == '4') {
1091                         *status = 4;
1092                         safestrncpy(dsn, &buf[4], 1023);
1093                         goto bail;
1094                 }
1095                 else {
1096                         *status = 5;
1097                         safestrncpy(dsn, &buf[4], 1023);
1098                         goto bail;
1099                 }
1100         }
1101
1102         /* We did it! */
1103         safestrncpy(dsn, &buf[4], 1023);
1104         *status = 2;
1105
1106         lprintf(CTDL_DEBUG, ">QUIT\n");
1107         sock_write(sock, "QUIT\r\n", 6);
1108         ml_sock_gets(sock, buf);
1109         lprintf(CTDL_DEBUG, "<%s\n", buf);
1110         lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1111                 user, node, name);
1112
1113 bail:   if (msg_fp != NULL) fclose(msg_fp);
1114         sock_close(sock);
1115         return;
1116 }
1117
1118
1119
1120 /*
1121  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1122  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1123  * a "bounce" message (delivery status notification).
1124  */
1125 void smtp_do_bounce(char *instr) {
1126         int i;
1127         int lines;
1128         int status;
1129         char buf[1024];
1130         char key[1024];
1131         char addr[1024];
1132         char dsn[1024];
1133         char bounceto[1024];
1134         int num_bounces = 0;
1135         int bounce_this = 0;
1136         long bounce_msgid = (-1);
1137         time_t submitted = 0L;
1138         struct CtdlMessage *bmsg = NULL;
1139         int give_up = 0;
1140         struct recptypes *valid;
1141         int successful_bounce = 0;
1142
1143         lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1144         strcpy(bounceto, "");
1145
1146         lines = num_tokens(instr, '\n');
1147
1148
1149         /* See if it's time to give up on delivery of this message */
1150         for (i=0; i<lines; ++i) {
1151                 extract_token(buf, instr, i, '\n');
1152                 extract(key, buf, 0);
1153                 extract(addr, buf, 1);
1154                 if (!strcasecmp(key, "submitted")) {
1155                         submitted = atol(addr);
1156                 }
1157         }
1158
1159         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1160                 give_up = 1;
1161         }
1162
1163
1164
1165         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1166         if (bmsg == NULL) return;
1167         memset(bmsg, 0, sizeof(struct CtdlMessage));
1168
1169         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1170         bmsg->cm_anon_type = MES_NORMAL;
1171         bmsg->cm_format_type = 1;
1172         bmsg->cm_fields['A'] = strdoop("Citadel");
1173         bmsg->cm_fields['O'] = strdoop(MAILROOM);
1174         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1175
1176         if (give_up) bmsg->cm_fields['M'] = strdoop(
1177 "A message you sent could not be delivered to some or all of its recipients\n"
1178 "due to prolonged unavailability of its destination(s).\n"
1179 "Giving up on the following addresses:\n\n"
1180 );
1181
1182         else bmsg->cm_fields['M'] = strdoop(
1183 "A message you sent could not be delivered to some or all of its recipients.\n"
1184 "The following addresses were undeliverable:\n\n"
1185 );
1186
1187         /*
1188          * Now go through the instructions checking for stuff.
1189          */
1190         for (i=0; i<lines; ++i) {
1191                 extract_token(buf, instr, i, '\n');
1192                 extract(key, buf, 0);
1193                 extract(addr, buf, 1);
1194                 status = extract_int(buf, 2);
1195                 extract(dsn, buf, 3);
1196                 bounce_this = 0;
1197
1198                 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1199                         key, addr, status, dsn);
1200
1201                 if (!strcasecmp(key, "bounceto")) {
1202                         strcpy(bounceto, addr);
1203                 }
1204
1205                 if (
1206                    (!strcasecmp(key, "local"))
1207                    || (!strcasecmp(key, "remote"))
1208                    || (!strcasecmp(key, "ignet"))
1209                    || (!strcasecmp(key, "room"))
1210                 ) {
1211                         if (status == 5) bounce_this = 1;
1212                         if (give_up) bounce_this = 1;
1213                 }
1214
1215                 if (bounce_this) {
1216                         ++num_bounces;
1217
1218                         if (bmsg->cm_fields['M'] == NULL) {
1219                                 lprintf(CTDL_ERR, "ERROR ... M field is null "
1220                                         "(%s:%d)\n", __FILE__, __LINE__);
1221                         }
1222
1223                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1224                                 strlen(bmsg->cm_fields['M']) + 1024 );
1225                         strcat(bmsg->cm_fields['M'], addr);
1226                         strcat(bmsg->cm_fields['M'], ": ");
1227                         strcat(bmsg->cm_fields['M'], dsn);
1228                         strcat(bmsg->cm_fields['M'], "\n");
1229
1230                         remove_token(instr, i, '\n');
1231                         --i;
1232                         --lines;
1233                 }
1234         }
1235
1236         /* Deliver the bounce if there's anything worth mentioning */
1237         lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1238         if (num_bounces > 0) {
1239
1240                 /* First try the user who sent the message */
1241                 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1242                 if (strlen(bounceto) == 0) {
1243                         lprintf(CTDL_ERR, "No bounce address specified\n");
1244                         bounce_msgid = (-1L);
1245                 }
1246
1247                 /* Can we deliver the bounce to the original sender? */
1248                 valid = validate_recipients(bounceto);
1249                 if (valid != NULL) {
1250                         if (valid->num_error == 0) {
1251                                 CtdlSubmitMsg(bmsg, valid, "");
1252                                 successful_bounce = 1;
1253                         }
1254                 }
1255
1256                 /* If not, post it in the Aide> room */
1257                 if (successful_bounce == 0) {
1258                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1259                 }
1260
1261                 /* Free up the memory we used */
1262                 if (valid != NULL) {
1263                         phree(valid);
1264                 }
1265         }
1266
1267         CtdlFreeMessage(bmsg);
1268         lprintf(CTDL_DEBUG, "Done processing bounces\n");
1269 }
1270
1271
1272 /*
1273  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1274  * set of delivery instructions for completed deliveries and remove them.
1275  *
1276  * It returns the number of incomplete deliveries remaining.
1277  */
1278 int smtp_purge_completed_deliveries(char *instr) {
1279         int i;
1280         int lines;
1281         int status;
1282         char buf[1024];
1283         char key[1024];
1284         char addr[1024];
1285         char dsn[1024];
1286         int completed;
1287         int incomplete = 0;
1288
1289         lines = num_tokens(instr, '\n');
1290         for (i=0; i<lines; ++i) {
1291                 extract_token(buf, instr, i, '\n');
1292                 extract(key, buf, 0);
1293                 extract(addr, buf, 1);
1294                 status = extract_int(buf, 2);
1295                 extract(dsn, buf, 3);
1296
1297                 completed = 0;
1298
1299                 if (
1300                    (!strcasecmp(key, "local"))
1301                    || (!strcasecmp(key, "remote"))
1302                    || (!strcasecmp(key, "ignet"))
1303                    || (!strcasecmp(key, "room"))
1304                 ) {
1305                         if (status == 2) completed = 1;
1306                         else ++incomplete;
1307                 }
1308
1309                 if (completed) {
1310                         remove_token(instr, i, '\n');
1311                         --i;
1312                         --lines;
1313                 }
1314         }
1315
1316         return(incomplete);
1317 }
1318
1319
1320 /*
1321  * smtp_do_procmsg()
1322  *
1323  * Called by smtp_do_queue() to handle an individual message.
1324  */
1325 void smtp_do_procmsg(long msgnum, void *userdata) {
1326         struct CtdlMessage *msg;
1327         char *instr = NULL;
1328         char *results = NULL;
1329         int i;
1330         int lines;
1331         int status;
1332         char buf[1024];
1333         char key[1024];
1334         char addr[1024];
1335         char dsn[1024];
1336         long text_msgid = (-1);
1337         int incomplete_deliveries_remaining;
1338         time_t attempted = 0L;
1339         time_t last_attempted = 0L;
1340         time_t retry = SMTP_RETRY_INTERVAL;
1341
1342         lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1343
1344         msg = CtdlFetchMessage(msgnum);
1345         if (msg == NULL) {
1346                 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1347                 return;
1348         }
1349
1350         instr = strdoop(msg->cm_fields['M']);
1351         CtdlFreeMessage(msg);
1352
1353         /* Strip out the headers amd any other non-instruction line */
1354         lines = num_tokens(instr, '\n');
1355         for (i=0; i<lines; ++i) {
1356                 extract_token(buf, instr, i, '\n');
1357                 if (num_tokens(buf, '|') < 2) {
1358                         remove_token(instr, i, '\n');
1359                         --lines;
1360                         --i;
1361                 }
1362         }
1363
1364         /* Learn the message ID and find out about recent delivery attempts */
1365         lines = num_tokens(instr, '\n');
1366         for (i=0; i<lines; ++i) {
1367                 extract_token(buf, instr, i, '\n');
1368                 extract(key, buf, 0);
1369                 if (!strcasecmp(key, "msgid")) {
1370                         text_msgid = extract_long(buf, 1);
1371                 }
1372                 if (!strcasecmp(key, "retry")) {
1373                         /* double the retry interval after each attempt */
1374                         retry = extract_long(buf, 1) * 2L;
1375                         if (retry > SMTP_RETRY_MAX) {
1376                                 retry = SMTP_RETRY_MAX;
1377                         }
1378                         remove_token(instr, i, '\n');
1379                 }
1380                 if (!strcasecmp(key, "attempted")) {
1381                         attempted = extract_long(buf, 1);
1382                         if (attempted > last_attempted)
1383                                 last_attempted = attempted;
1384                 }
1385         }
1386
1387         /*
1388          * Postpone delivery if we've already tried recently.
1389          */
1390         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1391                 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1392                 phree(instr);
1393                 return;
1394         }
1395
1396
1397         /*
1398          * Bail out if there's no actual message associated with this
1399          */
1400         if (text_msgid < 0L) {
1401                 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1402                 phree(instr);
1403                 return;
1404         }
1405
1406         /* Plow through the instructions looking for 'remote' directives and
1407          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1408          * were experienced and it's time to try again)
1409          */
1410         lines = num_tokens(instr, '\n');
1411         for (i=0; i<lines; ++i) {
1412                 extract_token(buf, instr, i, '\n');
1413                 extract(key, buf, 0);
1414                 extract(addr, buf, 1);
1415                 status = extract_int(buf, 2);
1416                 extract(dsn, buf, 3);
1417                 if ( (!strcasecmp(key, "remote"))
1418                    && ((status==0)||(status==3)||(status==4)) ) {
1419
1420                         /* Remove this "remote" instruction from the set,
1421                          * but replace the set's final newline if
1422                          * remove_token() stripped it.  It has to be there.
1423                          */
1424                         remove_token(instr, i, '\n');
1425                         if (instr[strlen(instr)-1] != '\n') {
1426                                 strcat(instr, "\n");
1427                         }
1428
1429                         --i;
1430                         --lines;
1431                         lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1432                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1433                         if (status != 2) {
1434                                 if (results == NULL) {
1435                                         results = mallok(1024);
1436                                         memset(results, 0, 1024);
1437                                 }
1438                                 else {
1439                                         results = reallok(results,
1440                                                 strlen(results) + 1024);
1441                                 }
1442                                 snprintf(&results[strlen(results)], 1024,
1443                                         "%s|%s|%d|%s\n",
1444                                         key, addr, status, dsn);
1445                         }
1446                 }
1447         }
1448
1449         if (results != NULL) {
1450                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1451                 strcat(instr, results);
1452                 phree(results);
1453         }
1454
1455
1456         /* Generate 'bounce' messages */
1457         smtp_do_bounce(instr);
1458
1459         /* Go through the delivery list, deleting completed deliveries */
1460         incomplete_deliveries_remaining = 
1461                 smtp_purge_completed_deliveries(instr);
1462
1463
1464         /*
1465          * No delivery instructions remain, so delete both the instructions
1466          * message and the message message.
1467          */
1468         if (incomplete_deliveries_remaining <= 0) {
1469                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1470                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1471         }
1472
1473
1474         /*
1475          * Uncompleted delivery instructions remain, so delete the old
1476          * instructions and replace with the updated ones.
1477          */
1478         if (incomplete_deliveries_remaining > 0) {
1479                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1480                 msg = mallok(sizeof(struct CtdlMessage));
1481                 memset(msg, 0, sizeof(struct CtdlMessage));
1482                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1483                 msg->cm_anon_type = MES_NORMAL;
1484                 msg->cm_format_type = FMT_RFC822;
1485                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1486                 snprintf(msg->cm_fields['M'],
1487                         strlen(instr)+SIZ,
1488                         "Content-type: %s\n\n%s\n"
1489                         "attempted|%ld\n"
1490                         "retry|%ld\n",
1491                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1492                 phree(instr);
1493                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1494                 CtdlFreeMessage(msg);
1495         }
1496
1497 }
1498
1499
1500
1501 /*
1502  * smtp_do_queue()
1503  * 
1504  * Run through the queue sending out messages.
1505  */
1506 void smtp_do_queue(void) {
1507         static int doing_queue = 0;
1508
1509         /*
1510          * This is a simple concurrency check to make sure only one queue run
1511          * is done at a time.  We could do this with a mutex, but since we
1512          * don't really require extremely fine granularity here, we'll do it
1513          * with a static variable instead.
1514          */
1515         if (doing_queue) return;
1516         doing_queue = 1;
1517
1518         /* 
1519          * Go ahead and run the queue
1520          */
1521         lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1522
1523         if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1524                 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1525                 return;
1526         }
1527         CtdlForEachMessage(MSGS_ALL, 0L,
1528                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1529
1530         lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1531         run_queue_now = 0;
1532         doing_queue = 0;
1533 }
1534
1535
1536
1537 /*****************************************************************************/
1538 /*                          SMTP UTILITY COMMANDS                            */
1539 /*****************************************************************************/
1540
1541 void cmd_smtp(char *argbuf) {
1542         char cmd[SIZ];
1543         char node[SIZ];
1544         char buf[SIZ];
1545         int i;
1546         int num_mxhosts;
1547
1548         if (CtdlAccessCheck(ac_aide)) return;
1549
1550         extract(cmd, argbuf, 0);
1551
1552         if (!strcasecmp(cmd, "mx")) {
1553                 extract(node, argbuf, 1);
1554                 num_mxhosts = getmx(buf, node);
1555                 cprintf("%d %d MX hosts listed for %s\n",
1556                         LISTING_FOLLOWS, num_mxhosts, node);
1557                 for (i=0; i<num_mxhosts; ++i) {
1558                         extract(node, buf, i);
1559                         cprintf("%s\n", node);
1560                 }
1561                 cprintf("000\n");
1562                 return;
1563         }
1564
1565         else if (!strcasecmp(cmd, "runqueue")) {
1566                 run_queue_now = 1;
1567                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1568                 return;
1569         }
1570
1571         else {
1572                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1573         }
1574
1575 }
1576
1577
1578 /*
1579  * Initialize the SMTP outbound queue
1580  */
1581 void smtp_init_spoolout(void) {
1582         struct ctdlroom qrbuf;
1583
1584         /*
1585          * Create the room.  This will silently fail if the room already
1586          * exists, and that's perfectly ok, because we want it to exist.
1587          */
1588         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1589
1590         /*
1591          * Make sure it's set to be a "system room" so it doesn't show up
1592          * in the <K>nown rooms list for Aides.
1593          */
1594         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1595                 qrbuf.QRflags2 |= QR2_SYSTEM;
1596                 lputroom(&qrbuf);
1597         }
1598 }
1599
1600
1601
1602
1603 /*****************************************************************************/
1604 /*                      MODULE INITIALIZATION STUFF                          */
1605 /*****************************************************************************/
1606
1607
1608 char *serv_smtp_init(void)
1609 {
1610         CtdlRegisterServiceHook(config.c_smtp_port,     /* On the net... */
1611                                 NULL,
1612                                 smtp_greeting,
1613                                 smtp_command_loop);
1614
1615         CtdlRegisterServiceHook(0,                      /* ...and locally */
1616                                 "lmtp.socket",
1617                                 lmtp_greeting,
1618                                 smtp_command_loop);
1619
1620         smtp_init_spoolout();
1621         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1622         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1623         return "$Id$";
1624 }