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