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