]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* Added some more debugging to serv_smtp.c to try to figure out the
[citadel.git] / citadel / serv_smtp.c
1 /*
2  * $Id$
3  *
4  * This module is an SMTP and ESMTP implementation for the Citadel system.
5  * It is compliant with all of the following:
6  *
7  * RFC  821 - Simple Mail Transfer Protocol
8  * RFC  876 - Survey of SMTP Implementations
9  * RFC 1047 - Duplicate messages and SMTP
10  * RFC 1854 - command pipelining
11  * RFC 1869 - Extended Simple Mail Transfer Protocol
12  * RFC 1870 - SMTP Service Extension for Message Size Declaration
13  * RFC 1893 - Enhanced Mail System Status Codes
14  * RFC 2033 - Local Mail Transfer Protocol
15  * RFC 2034 - SMTP Service Extension for Returning Enhanced Error Codes
16  * RFC 2197 - SMTP Service Extension for Command Pipelining
17  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
18  * RFC 2554 - SMTP Service Extension for Authentication
19  * RFC 2821 - Simple Mail Transfer Protocol
20  * RFC 2822 - Internet Message Format
21  * RFC 2920 - SMTP Service Extension for Command Pipelining
22  *
23  */
24
25 #include "sysdep.h"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #include <pwd.h>
32 #include <errno.h>
33 #include <sys/types.h>
34
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
37 # include <time.h>
38 #else
39 # if HAVE_SYS_TIME_H
40 #  include <sys/time.h>
41 # else
42 #  include <time.h>
43 # endif
44 #endif
45
46 #include <sys/wait.h>
47 #include <ctype.h>
48 #include <string.h>
49 #include <limits.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
53 #include "citadel.h"
54 #include "server.h"
55 #include "sysdep_decls.h"
56 #include "citserver.h"
57 #include "support.h"
58 #include "config.h"
59 #include "control.h"
60 #include "serv_extensions.h"
61 #include "room_ops.h"
62 #include "user_ops.h"
63 #include "policy.h"
64 #include "database.h"
65 #include "msgbase.h"
66 #include "tools.h"
67 #include "internet_addressing.h"
68 #include "genstamp.h"
69 #include "domain.h"
70 #include "clientsocket.h"
71 #include "locate_host.h"
72
73 #ifdef HAVE_OPENSSL
74 #include "serv_crypto.h"
75 #endif
76
77
78
79 #ifndef HAVE_SNPRINTF
80 #include "snprintf.h"
81 #endif
82
83 struct citsmtp {                /* Information about the current session */
84         int command_state;
85         char helo_node[SIZ];
86         struct ctdluser vrfy_buffer;
87         int vrfy_count;
88         char vrfy_match[SIZ];
89         char from[SIZ];
90         char recipients[SIZ];
91         int number_of_recipients;
92         int delivery_mode;
93         int message_originated_locally;
94         int is_lmtp;
95 };
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                 cprintf("250-PIPELINING\r\n");
201                 cprintf("250-AUTH LOGIN PLAIN\r\n");
202                 cprintf("250-AUTH=LOGIN PLAIN\r\n");
203 #ifdef HAVE_OPENSSL
204                 cprintf("250-STARTTLS\r\n");
205 #endif
206                 cprintf("250 ENHANCEDSTATUSCODES\r\n");
207         }
208 }
209
210
211
212 /*
213  * Implement HELP command.
214  */
215 void smtp_help(void) {
216         cprintf("214-Commands accepted:\r\n");
217         cprintf("214-    DATA\r\n");
218         cprintf("214-    EHLO\r\n");
219         cprintf("214-    EXPN\r\n");
220         cprintf("214-    HELO\r\n");
221         cprintf("214-    HELP\r\n");
222         cprintf("214-    MAIL\r\n");
223         cprintf("214-    NOOP\r\n");
224         cprintf("214-    QUIT\r\n");
225         cprintf("214-    RCPT\r\n");
226         cprintf("214-    RSET\r\n");
227         cprintf("214-    VRFY\r\n");
228         cprintf("214     \r\n");
229 }
230
231
232 /*
233  *
234  */
235 void smtp_get_user(char *argbuf) {
236         char buf[SIZ];
237         char username[SIZ];
238
239         CtdlDecodeBase64(username, argbuf, SIZ);
240         lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
241         if (CtdlLoginExistingUser(username) == login_ok) {
242                 CtdlEncodeBase64(buf, "Password:", 9);
243                 cprintf("334 %s\r\n", buf);
244                 SMTP->command_state = smtp_password;
245         }
246         else {
247                 cprintf("500 5.7.0 No such user.\r\n");
248                 SMTP->command_state = smtp_command;
249         }
250 }
251
252
253 /*
254  *
255  */
256 void smtp_get_pass(char *argbuf) {
257         char password[SIZ];
258
259         CtdlDecodeBase64(password, argbuf, SIZ);
260         lprintf(CTDL_DEBUG, "Trying <%s>\n", password);
261         if (CtdlTryPassword(password) == pass_ok) {
262                 smtp_auth_greeting();
263         }
264         else {
265                 cprintf("535 5.7.0 Authentication failed.\r\n");
266         }
267         SMTP->command_state = smtp_command;
268 }
269
270
271 /*
272  *
273  */
274 void smtp_auth(char *argbuf) {
275         char buf[SIZ];
276         char method[SIZ];
277         char encoded_authstring[SIZ];
278         char decoded_authstring[SIZ];
279         char ident[SIZ];
280         char user[SIZ];
281         char pass[SIZ];
282
283         if (CC->logged_in) {
284                 cprintf("504 5.7.4 Already logged in.\r\n");
285                 return;
286         }
287
288         extract_token(method, argbuf, 0, ' ');
289
290         if (!strncasecmp(method, "login", 5) ) {
291                 if (strlen(argbuf) >= 7) {
292                         smtp_get_user(&argbuf[6]);
293                 }
294                 else {
295                         CtdlEncodeBase64(buf, "Username:", 9);
296                         cprintf("334 %s\r\n", buf);
297                         SMTP->command_state = smtp_user;
298                 }
299                 return;
300         }
301
302         if (!strncasecmp(method, "plain", 5) ) {
303                 extract_token(encoded_authstring, argbuf, 1, ' ');
304                 CtdlDecodeBase64(decoded_authstring,
305                                 encoded_authstring,
306                                 strlen(encoded_authstring) );
307                 strcpy(ident, decoded_authstring);
308                 strcpy(user, &decoded_authstring[strlen(ident) + 1] );
309                 strcpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2] );
310
311                 if (CtdlLoginExistingUser(user) == login_ok) {
312                         if (CtdlTryPassword(pass) == pass_ok) {
313                                 smtp_auth_greeting();
314                                 return;
315                         }
316                 }
317                 cprintf("504 5.7.4 Authentication failed.\r\n");
318         }
319
320         if (strncasecmp(method, "login", 5) ) {
321                 cprintf("504 5.7.4 Unknown authentication method.\r\n");
322                 return;
323         }
324
325 }
326
327
328 /*
329  * Back end for smtp_vrfy() command
330  */
331 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
332
333         if (!fuzzy_match(us, SMTP->vrfy_match)) {
334                 ++SMTP->vrfy_count;
335                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
336         }
337 }
338
339
340 /* 
341  * Implements the VRFY (verify user name) command.
342  * Performs fuzzy match on full user names.
343  */
344 void smtp_vrfy(char *argbuf) {
345         SMTP->vrfy_count = 0;
346         strcpy(SMTP->vrfy_match, argbuf);
347         ForEachUser(smtp_vrfy_backend, NULL);
348
349         if (SMTP->vrfy_count < 1) {
350                 cprintf("550 5.1.1 String does not match anything.\r\n");
351         }
352         else if (SMTP->vrfy_count == 1) {
353                 cprintf("250 %s <cit%ld@%s>\r\n",
354                         SMTP->vrfy_buffer.fullname,
355                         SMTP->vrfy_buffer.usernum,
356                         config.c_fqdn);
357         }
358         else if (SMTP->vrfy_count > 1) {
359                 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
360                         SMTP->vrfy_count);
361         }
362
363 }
364
365
366
367 /*
368  * Back end for smtp_expn() command
369  */
370 void smtp_expn_backend(struct ctdluser *us, void *data) {
371
372         if (!fuzzy_match(us, SMTP->vrfy_match)) {
373
374                 if (SMTP->vrfy_count >= 1) {
375                         cprintf("250-%s <cit%ld@%s>\r\n",
376                                 SMTP->vrfy_buffer.fullname,
377                                 SMTP->vrfy_buffer.usernum,
378                                 config.c_fqdn);
379                 }
380
381                 ++SMTP->vrfy_count;
382                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
383         }
384 }
385
386
387 /* 
388  * Implements the EXPN (expand user name) command.
389  * Performs fuzzy match on full user names.
390  */
391 void smtp_expn(char *argbuf) {
392         SMTP->vrfy_count = 0;
393         strcpy(SMTP->vrfy_match, argbuf);
394         ForEachUser(smtp_expn_backend, NULL);
395
396         if (SMTP->vrfy_count < 1) {
397                 cprintf("550 5.1.1 String does not match anything.\r\n");
398         }
399         else if (SMTP->vrfy_count >= 1) {
400                 cprintf("250 %s <cit%ld@%s>\r\n",
401                         SMTP->vrfy_buffer.fullname,
402                         SMTP->vrfy_buffer.usernum,
403                         config.c_fqdn);
404         }
405 }
406
407
408 /*
409  * Implements the RSET (reset state) command.
410  * Currently this just zeroes out the state buffer.  If pointers to data
411  * allocated with mallok() are ever placed in the state buffer, we have to
412  * be sure to phree() them first!
413  *
414  * Set do_response to nonzero to output the SMTP RSET response code.
415  */
416 void smtp_rset(int do_response) {
417         int is_lmtp;
418
419         /*
420          * Our entire SMTP state is discarded when a RSET command is issued,
421          * but we need to preserve this one little piece of information, so
422          * we save it for later.
423          */
424         is_lmtp = SMTP->is_lmtp;
425
426         memset(SMTP, 0, sizeof(struct citsmtp));
427
428         /*
429          * It is somewhat ambiguous whether we want to log out when a RSET
430          * command is issued.  Here's the code to do it.  It is commented out
431          * because some clients (such as Pine) issue RSET commands before
432          * each message, but still expect to be logged in.
433          *
434          * if (CC->logged_in) {
435          *      logout(CC);
436          * }
437          */
438
439         /*
440          * Reinstate this little piece of information we saved (see above).
441          */
442         SMTP->is_lmtp = is_lmtp;
443
444         if (do_response) {
445                 cprintf("250 2.0.0 Zap!\r\n");
446         }
447 }
448
449 /*
450  * Clear out the portions of the state buffer that need to be cleared out
451  * after the DATA command finishes.
452  */
453 void smtp_data_clear(void) {
454         strcpy(SMTP->from, "");
455         strcpy(SMTP->recipients, "");
456         SMTP->number_of_recipients = 0;
457         SMTP->delivery_mode = 0;
458         SMTP->message_originated_locally = 0;
459 }
460
461
462
463 /*
464  * Implements the "MAIL From:" command
465  */
466 void smtp_mail(char *argbuf) {
467         char user[SIZ];
468         char node[SIZ];
469         char name[SIZ];
470
471         if (strlen(SMTP->from) != 0) {
472                 cprintf("503 5.1.0 Only one sender permitted\r\n");
473                 return;
474         }
475
476         if (strncasecmp(argbuf, "From:", 5)) {
477                 cprintf("501 5.1.7 Syntax error\r\n");
478                 return;
479         }
480
481         strcpy(SMTP->from, &argbuf[5]);
482         striplt(SMTP->from);
483         stripallbut(SMTP->from, '<', '>');
484
485         /* We used to reject empty sender names, until it was brought to our
486          * attention that RFC1123 5.2.9 requires that this be allowed.  So now
487          * we allow it, but replace the empty string with a fake
488          * address so we don't have to contend with the empty string causing
489          * other code to fail when it's expecting something there.
490          */
491         if (strlen(SMTP->from) == 0) {
492                 strcpy(SMTP->from, "someone@somewhere.org");
493         }
494
495         /* If this SMTP connection is from a logged-in user, force the 'from'
496          * to be the user's Internet e-mail address as Citadel knows it.
497          */
498         if (CC->logged_in) {
499                 strcpy(SMTP->from, CC->cs_inet_email);
500                 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
501                 SMTP->message_originated_locally = 1;
502                 return;
503         }
504
505         else if (SMTP->is_lmtp) {
506                 /* Bypass forgery checking for LMTP */
507         }
508
509         /* Otherwise, make sure outsiders aren't trying to forge mail from
510          * this system.
511          */
512         else {
513                 process_rfc822_addr(SMTP->from, user, node, name);
514                 if (CtdlHostAlias(node) != hostalias_nomatch) {
515                         cprintf("550 5.1.8 "
516                                 "You must log in to send mail from %s\r\n",
517                                 node);
518                         strcpy(SMTP->from, "");
519                         return;
520                 }
521         }
522
523         cprintf("250 2.0.0 Sender ok\r\n");
524 }
525
526
527
528 /*
529  * Implements the "RCPT To:" command
530  */
531 void smtp_rcpt(char *argbuf) {
532         char recp[SIZ];
533         char message_to_spammer[SIZ];
534         struct recptypes *valid = NULL;
535
536         if (strlen(SMTP->from) == 0) {
537                 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
538                 return;
539         }
540
541         if (strncasecmp(argbuf, "To:", 3)) {
542                 cprintf("501 5.1.7 Syntax error\r\n");
543                 return;
544         }
545
546         strcpy(recp, &argbuf[3]);
547         striplt(recp);
548         stripallbut(recp, '<', '>');
549
550         if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
551                 cprintf("452 4.5.3 Too many recipients\r\n");
552                 return;
553         }
554
555         /* RBL check */
556         if ( (!CC->logged_in)
557            && (!SMTP->is_lmtp) ) {
558                 if (rbl_check(message_to_spammer)) {
559                         cprintf("550 %s\r\n", message_to_spammer);
560                         /* no need to phree(valid), it's not allocated yet */
561                         return;
562                 }
563         }
564
565         valid = validate_recipients(recp);
566         if (valid->num_error > 0) {
567                 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
568                 phree(valid);
569                 return;
570         }
571
572         if (valid->num_internet > 0) {
573                 if ( (SMTP->message_originated_locally == 0)
574                    && (SMTP->is_lmtp == 0) ) {
575                         cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
576                         phree(valid);
577                         return;
578                 }
579         }
580
581         cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
582         if (strlen(SMTP->recipients) > 0) {
583                 strcat(SMTP->recipients, ",");
584         }
585         strcat(SMTP->recipients, recp);
586         SMTP->number_of_recipients += 1;
587 }
588
589
590
591
592 /*
593  * Implements the DATA command
594  */
595 void smtp_data(void) {
596         char *body;
597         struct CtdlMessage *msg;
598         long msgnum;
599         char nowstamp[SIZ];
600         struct recptypes *valid;
601         int scan_errors;
602         int i;
603         char result[SIZ];
604
605         if (strlen(SMTP->from) == 0) {
606                 cprintf("503 5.5.1 Need MAIL command first.\r\n");
607                 return;
608         }
609
610         if (SMTP->number_of_recipients < 1) {
611                 cprintf("503 5.5.1 Need RCPT command first.\r\n");
612                 return;
613         }
614
615         cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
616         
617         datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
618         body = mallok(4096);
619
620         if (body != NULL) snprintf(body, 4096,
621                 "Received: from %s (%s [%s])\n"
622                 "       by %s; %s\n",
623                         SMTP->helo_node,
624                         CC->cs_host,
625                         CC->cs_addr,
626                         config.c_fqdn,
627                         nowstamp);
628         
629         body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
630         if (body == NULL) {
631                 cprintf("550 5.6.5 "
632                         "Unable to save message: internal error.\r\n");
633                 return;
634         }
635
636         lprintf(CTDL_DEBUG, "Converting message...\n");
637         msg = convert_internet_message(body);
638
639         /* If the user is locally authenticated, FORCE the From: header to
640          * show up as the real sender.  Yes, this violates the RFC standard,
641          * but IT MAKES SENSE.  If you prefer strict RFC adherence over
642          * common sense, you can disable this in the configuration.
643          *
644          * We also set the "message room name" ('O' field) to MAILROOM
645          * (which is Mail> on most systems) to prevent it from getting set
646          * to something ugly like "0000058008.Sent Items>" when the message
647          * is read with a Citadel client.
648          */
649         if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
650                 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
651                 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
652                 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
653                 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
654                 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
655                 msg->cm_fields['A'] = strdoop(CC->user.fullname);
656                 msg->cm_fields['N'] = strdoop(config.c_nodename);
657                 msg->cm_fields['H'] = strdoop(config.c_humannode);
658                 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
659                 msg->cm_fields['O'] = strdoop(MAILROOM);
660         }
661
662         /* Submit the message into the Citadel system. */
663         valid = validate_recipients(SMTP->recipients);
664
665         /* If there are modules that want to scan this message before final
666          * submission (such as virus checkers or spam filters), call them now
667          * and give them an opportunity to reject the message.
668          */
669         scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
670
671         if (scan_errors > 0) {  /* We don't want this message! */
672
673                 if (msg->cm_fields['0'] == NULL) {
674                         msg->cm_fields['0'] = strdoop(
675                                 "5.7.1 Message rejected by filter");
676                 }
677
678                 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
679         }
680         
681         else {                  /* Ok, we'll accept this message. */
682                 msgnum = CtdlSubmitMsg(msg, valid, "");
683                 if (msgnum > 0L) {
684                         sprintf(result, "250 2.0.0 Message accepted.\r\n");
685                 }
686                 else {
687                         sprintf(result, "550 5.5.0 Internal delivery error\r\n");
688                 }
689         }
690
691         /* For SMTP and ESTMP, just print the result message.  For LMTP, we
692          * have to print one result message for each recipient.  Since there
693          * is nothing in Citadel which would cause different recipients to
694          * have different results, we can get away with just spitting out the
695          * same message once for each recipient.
696          */
697         if (SMTP->is_lmtp) {
698                 for (i=0; i<SMTP->number_of_recipients; ++i) {
699                         cprintf("%s", result);
700                 }
701         }
702         else {
703                 cprintf("%s", result);
704         }
705
706         CtdlFreeMessage(msg);
707         phree(valid);
708         smtp_data_clear();      /* clear out the buffers now */
709 }
710
711
712 /*
713  * implements the STARTTLS command (Citadel API version)
714  */
715 #ifdef HAVE_OPENSSL
716 void smtp_starttls(void)
717 {
718         char ok_response[SIZ];
719         char nosup_response[SIZ];
720         char error_response[SIZ];
721
722         sprintf(ok_response,
723                 "200 2.0.0 Begin TLS negotiation now\r\n");
724         sprintf(nosup_response,
725                 "554 5.7.3 TLS not supported here\r\n");
726         sprintf(error_response,
727                 "554 5.7.3 Internal error\r\n");
728         CtdlStartTLS(ok_response, nosup_response, error_response);
729         smtp_rset(0);
730 }
731 #endif
732
733
734
735 /* 
736  * Main command loop for SMTP sessions.
737  */
738 void smtp_command_loop(void) {
739         char cmdbuf[SIZ];
740
741         lprintf(CTDL_DEBUG, "Start of SMTP command loop\n");
742         time(&CC->lastcmd);
743         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
744         lprintf(CTDL_DEBUG, "Awaiting SMTP command...\n");
745         if (client_gets(cmdbuf) < 1) {
746                 lprintf(CTDL_CRIT, "SMTP socket is broken.  Ending session.\n");
747                 CC->kill_me = 1;
748                 return;
749         }
750         lprintf(CTDL_INFO, "SMTP: %s\n", cmdbuf);
751         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
752
753         if (SMTP->command_state == smtp_user) {
754                 smtp_get_user(cmdbuf);
755         }
756
757         else if (SMTP->command_state == smtp_password) {
758                 smtp_get_pass(cmdbuf);
759         }
760
761         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
762                 smtp_auth(&cmdbuf[5]);
763         }
764
765         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
766                 smtp_data();
767         }
768
769         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
770                 smtp_expn(&cmdbuf[5]);
771         }
772
773         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
774                 smtp_hello(&cmdbuf[5], 0);
775         }
776
777         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
778                 smtp_hello(&cmdbuf[5], 1);
779         }
780
781         else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
782                 smtp_hello(&cmdbuf[5], 2);
783         }
784
785         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
786                 smtp_help();
787         }
788
789         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
790                 lprintf(CTDL_DEBUG, "Performing MAIL command\n");
791                 smtp_mail(&cmdbuf[5]);
792                 lprintf(CTDL_DEBUG, "Finished MAIL command\n");
793         }
794
795         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
796                 cprintf("250 NOOP\r\n");
797         }
798
799         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
800                 cprintf("221 Goodbye...\r\n");
801                 CC->kill_me = 1;
802                 return;
803         }
804
805         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
806                 smtp_rcpt(&cmdbuf[5]);
807         }
808
809         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
810                 smtp_rset(1);
811         }
812 #ifdef HAVE_OPENSSL
813         else if (!strcasecmp(cmdbuf, "STARTTLS")) {
814                 smtp_starttls();
815         }
816 #endif
817         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
818                 smtp_vrfy(&cmdbuf[5]);
819         }
820
821         else {
822                 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
823         }
824
825         lprintf(CTDL_DEBUG, "End of SMTP command loop\n");
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 }