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