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