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