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