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