]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* Realized that memreadline() does NOT return a NULL pointer when it hits
[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         char *msgtext;
907         char *ptr;
908         size_t msg_size;
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 */
918         CC->redirect_buffer = malloc(SIZ);
919         CC->redirect_len = 0;
920         CC->redirect_alloc = SIZ;
921         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
922         msgtext = CC->redirect_buffer;
923         msg_size = CC->redirect_len;
924         CC->redirect_buffer = NULL;
925         CC->redirect_len = 0;
926         CC->redirect_alloc = 0;
927
928         /* Extract something to send later in the 'MAIL From:' command */
929         strcpy(mailfrom, "");
930         scan_done = 0;
931         ptr = msgtext;
932         do {
933                 if (ptr = memreadline(ptr, buf, sizeof buf), *ptr == 0) {
934                         scan_done = 1;
935                 }
936                 if (!strncasecmp(buf, "From:", 5)) {
937                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
938                         striplt(mailfrom);
939                         for (i=0; i<strlen(mailfrom); ++i) {
940                                 if (!isprint(mailfrom[i])) {
941                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
942                                         i=0;
943                                 }
944                         }
945
946                         /* Strip out parenthesized names */
947                         lp = (-1);
948                         rp = (-1);
949                         for (i=0; i<strlen(mailfrom); ++i) {
950                                 if (mailfrom[i] == '(') lp = i;
951                                 if (mailfrom[i] == ')') rp = i;
952                         }
953                         if ((lp>0)&&(rp>lp)) {
954                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
955                         }
956
957                         /* Prefer brokketized names */
958                         lp = (-1);
959                         rp = (-1);
960                         for (i=0; i<strlen(mailfrom); ++i) {
961                                 if (mailfrom[i] == '<') lp = i;
962                                 if (mailfrom[i] == '>') rp = i;
963                         }
964                         if ( (lp>=0) && (rp>lp) ) {
965                                 mailfrom[rp] = 0;
966                                 strcpy(mailfrom, &mailfrom[lp]);
967                         }
968
969                         scan_done = 1;
970                 }
971         } while (scan_done == 0);
972         if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
973
974         /* Figure out what mail exchanger host we have to connect to */
975         num_mxhosts = getmx(mxhosts, node);
976         lprintf(CTDL_DEBUG, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
977         if (num_mxhosts < 1) {
978                 *status = 5;
979                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
980                 return;
981         }
982
983         sock = (-1);
984         for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
985                 extract(buf, mxhosts, mx);
986                 lprintf(CTDL_DEBUG, "Trying <%s>\n", buf);
987                 sock = sock_connect(buf, "25", "tcp");
988                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
989                 if (sock >= 0) lprintf(CTDL_DEBUG, "Connected!\n");
990                 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
991         }
992
993         if (sock < 0) {
994                 *status = 4;    /* dsn is already filled in */
995                 return;
996         }
997
998         /* Process the SMTP greeting from the server */
999         if (ml_sock_gets(sock, buf) < 0) {
1000                 *status = 4;
1001                 strcpy(dsn, "Connection broken during SMTP conversation");
1002                 goto bail;
1003         }
1004         lprintf(CTDL_DEBUG, "<%s\n", buf);
1005         if (buf[0] != '2') {
1006                 if (buf[0] == '4') {
1007                         *status = 4;
1008                         safestrncpy(dsn, &buf[4], 1023);
1009                         goto bail;
1010                 }
1011                 else {
1012                         *status = 5;
1013                         safestrncpy(dsn, &buf[4], 1023);
1014                         goto bail;
1015                 }
1016         }
1017
1018         /* At this point we know we are talking to a real SMTP server */
1019
1020         /* Do a HELO command */
1021         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1022         lprintf(CTDL_DEBUG, ">%s", buf);
1023         sock_write(sock, buf, strlen(buf));
1024         if (ml_sock_gets(sock, buf) < 0) {
1025                 *status = 4;
1026                 strcpy(dsn, "Connection broken during SMTP HELO");
1027                 goto bail;
1028         }
1029         lprintf(CTDL_DEBUG, "<%s\n", buf);
1030         if (buf[0] != '2') {
1031                 if (buf[0] == '4') {
1032                         *status = 4;
1033                         safestrncpy(dsn, &buf[4], 1023);
1034                         goto bail;
1035                 }
1036                 else {
1037                         *status = 5;
1038                         safestrncpy(dsn, &buf[4], 1023);
1039                         goto bail;
1040                 }
1041         }
1042
1043         /* HELO succeeded, now try the MAIL From: command */
1044         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1045         lprintf(CTDL_DEBUG, ">%s", buf);
1046         sock_write(sock, buf, strlen(buf));
1047         if (ml_sock_gets(sock, buf) < 0) {
1048                 *status = 4;
1049                 strcpy(dsn, "Connection broken during SMTP MAIL");
1050                 goto bail;
1051         }
1052         lprintf(CTDL_DEBUG, "<%s\n", buf);
1053         if (buf[0] != '2') {
1054                 if (buf[0] == '4') {
1055                         *status = 4;
1056                         safestrncpy(dsn, &buf[4], 1023);
1057                         goto bail;
1058                 }
1059                 else {
1060                         *status = 5;
1061                         safestrncpy(dsn, &buf[4], 1023);
1062                         goto bail;
1063                 }
1064         }
1065
1066         /* MAIL succeeded, now try the RCPT To: command */
1067         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
1068         lprintf(CTDL_DEBUG, ">%s", buf);
1069         sock_write(sock, buf, strlen(buf));
1070         if (ml_sock_gets(sock, buf) < 0) {
1071                 *status = 4;
1072                 strcpy(dsn, "Connection broken during SMTP RCPT");
1073                 goto bail;
1074         }
1075         lprintf(CTDL_DEBUG, "<%s\n", buf);
1076         if (buf[0] != '2') {
1077                 if (buf[0] == '4') {
1078                         *status = 4;
1079                         safestrncpy(dsn, &buf[4], 1023);
1080                         goto bail;
1081                 }
1082                 else {
1083                         *status = 5;
1084                         safestrncpy(dsn, &buf[4], 1023);
1085                         goto bail;
1086                 }
1087         }
1088
1089         /* RCPT succeeded, now try the DATA command */
1090         lprintf(CTDL_DEBUG, ">DATA\n");
1091         sock_write(sock, "DATA\r\n", 6);
1092         if (ml_sock_gets(sock, buf) < 0) {
1093                 *status = 4;
1094                 strcpy(dsn, "Connection broken during SMTP DATA");
1095                 goto bail;
1096         }
1097         lprintf(CTDL_DEBUG, "<%s\n", buf);
1098         if (buf[0] != '3') {
1099                 if (buf[0] == '4') {
1100                         *status = 3;
1101                         safestrncpy(dsn, &buf[4], 1023);
1102                         goto bail;
1103                 }
1104                 else {
1105                         *status = 5;
1106                         safestrncpy(dsn, &buf[4], 1023);
1107                         goto bail;
1108                 }
1109         }
1110
1111         /* If we reach this point, the server is expecting data */
1112         sock_write(sock, msgtext, msg_size);
1113         if (msgtext[msg_size-1] != 10) {
1114                 lprintf(CTDL_WARNING, "Possible problem: message did not "
1115                         "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1116                                 buf[msg_size-1]);
1117         }
1118
1119         sock_write(sock, ".\r\n", 3);
1120         if (ml_sock_gets(sock, buf) < 0) {
1121                 *status = 4;
1122                 strcpy(dsn, "Connection broken during SMTP message transmit");
1123                 goto bail;
1124         }
1125         lprintf(CTDL_DEBUG, "%s\n", buf);
1126         if (buf[0] != '2') {
1127                 if (buf[0] == '4') {
1128                         *status = 4;
1129                         safestrncpy(dsn, &buf[4], 1023);
1130                         goto bail;
1131                 }
1132                 else {
1133                         *status = 5;
1134                         safestrncpy(dsn, &buf[4], 1023);
1135                         goto bail;
1136                 }
1137         }
1138
1139         /* We did it! */
1140         safestrncpy(dsn, &buf[4], 1023);
1141         *status = 2;
1142
1143         lprintf(CTDL_DEBUG, ">QUIT\n");
1144         sock_write(sock, "QUIT\r\n", 6);
1145         ml_sock_gets(sock, buf);
1146         lprintf(CTDL_DEBUG, "<%s\n", buf);
1147         lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1148                 user, node, name);
1149
1150 bail:   free(msgtext);
1151         sock_close(sock);
1152         return;
1153 }
1154
1155
1156
1157 /*
1158  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1159  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1160  * a "bounce" message (delivery status notification).
1161  */
1162 void smtp_do_bounce(char *instr) {
1163         int i;
1164         int lines;
1165         int status;
1166         char buf[1024];
1167         char key[1024];
1168         char addr[1024];
1169         char dsn[1024];
1170         char bounceto[1024];
1171         int num_bounces = 0;
1172         int bounce_this = 0;
1173         long bounce_msgid = (-1);
1174         time_t submitted = 0L;
1175         struct CtdlMessage *bmsg = NULL;
1176         int give_up = 0;
1177         struct recptypes *valid;
1178         int successful_bounce = 0;
1179
1180         lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1181         strcpy(bounceto, "");
1182
1183         lines = num_tokens(instr, '\n');
1184
1185
1186         /* See if it's time to give up on delivery of this message */
1187         for (i=0; i<lines; ++i) {
1188                 extract_token(buf, instr, i, '\n');
1189                 extract(key, buf, 0);
1190                 extract(addr, buf, 1);
1191                 if (!strcasecmp(key, "submitted")) {
1192                         submitted = atol(addr);
1193                 }
1194         }
1195
1196         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1197                 give_up = 1;
1198         }
1199
1200
1201
1202         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1203         if (bmsg == NULL) return;
1204         memset(bmsg, 0, sizeof(struct CtdlMessage));
1205
1206         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1207         bmsg->cm_anon_type = MES_NORMAL;
1208         bmsg->cm_format_type = 1;
1209         bmsg->cm_fields['A'] = strdup("Citadel");
1210         bmsg->cm_fields['O'] = strdup(MAILROOM);
1211         bmsg->cm_fields['N'] = strdup(config.c_nodename);
1212
1213         if (give_up) bmsg->cm_fields['M'] = strdup(
1214 "A message you sent could not be delivered to some or all of its recipients\n"
1215 "due to prolonged unavailability of its destination(s).\n"
1216 "Giving up on the following addresses:\n\n"
1217 );
1218
1219         else bmsg->cm_fields['M'] = strdup(
1220 "A message you sent could not be delivered to some or all of its recipients.\n"
1221 "The following addresses were undeliverable:\n\n"
1222 );
1223
1224         /*
1225          * Now go through the instructions checking for stuff.
1226          */
1227         for (i=0; i<lines; ++i) {
1228                 extract_token(buf, instr, i, '\n');
1229                 extract(key, buf, 0);
1230                 extract(addr, buf, 1);
1231                 status = extract_int(buf, 2);
1232                 extract(dsn, buf, 3);
1233                 bounce_this = 0;
1234
1235                 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1236                         key, addr, status, dsn);
1237
1238                 if (!strcasecmp(key, "bounceto")) {
1239                         strcpy(bounceto, addr);
1240                 }
1241
1242                 if (
1243                    (!strcasecmp(key, "local"))
1244                    || (!strcasecmp(key, "remote"))
1245                    || (!strcasecmp(key, "ignet"))
1246                    || (!strcasecmp(key, "room"))
1247                 ) {
1248                         if (status == 5) bounce_this = 1;
1249                         if (give_up) bounce_this = 1;
1250                 }
1251
1252                 if (bounce_this) {
1253                         ++num_bounces;
1254
1255                         if (bmsg->cm_fields['M'] == NULL) {
1256                                 lprintf(CTDL_ERR, "ERROR ... M field is null "
1257                                         "(%s:%d)\n", __FILE__, __LINE__);
1258                         }
1259
1260                         bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1261                                 strlen(bmsg->cm_fields['M']) + 1024 );
1262                         strcat(bmsg->cm_fields['M'], addr);
1263                         strcat(bmsg->cm_fields['M'], ": ");
1264                         strcat(bmsg->cm_fields['M'], dsn);
1265                         strcat(bmsg->cm_fields['M'], "\n");
1266
1267                         remove_token(instr, i, '\n');
1268                         --i;
1269                         --lines;
1270                 }
1271         }
1272
1273         /* Deliver the bounce if there's anything worth mentioning */
1274         lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1275         if (num_bounces > 0) {
1276
1277                 /* First try the user who sent the message */
1278                 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1279                 if (strlen(bounceto) == 0) {
1280                         lprintf(CTDL_ERR, "No bounce address specified\n");
1281                         bounce_msgid = (-1L);
1282                 }
1283
1284                 /* Can we deliver the bounce to the original sender? */
1285                 valid = validate_recipients(bounceto);
1286                 if (valid != NULL) {
1287                         if (valid->num_error == 0) {
1288                                 CtdlSubmitMsg(bmsg, valid, "");
1289                                 successful_bounce = 1;
1290                         }
1291                 }
1292
1293                 /* If not, post it in the Aide> room */
1294                 if (successful_bounce == 0) {
1295                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1296                 }
1297
1298                 /* Free up the memory we used */
1299                 if (valid != NULL) {
1300                         free(valid);
1301                 }
1302         }
1303
1304         CtdlFreeMessage(bmsg);
1305         lprintf(CTDL_DEBUG, "Done processing bounces\n");
1306 }
1307
1308
1309 /*
1310  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1311  * set of delivery instructions for completed deliveries and remove them.
1312  *
1313  * It returns the number of incomplete deliveries remaining.
1314  */
1315 int smtp_purge_completed_deliveries(char *instr) {
1316         int i;
1317         int lines;
1318         int status;
1319         char buf[1024];
1320         char key[1024];
1321         char addr[1024];
1322         char dsn[1024];
1323         int completed;
1324         int incomplete = 0;
1325
1326         lines = num_tokens(instr, '\n');
1327         for (i=0; i<lines; ++i) {
1328                 extract_token(buf, instr, i, '\n');
1329                 extract(key, buf, 0);
1330                 extract(addr, buf, 1);
1331                 status = extract_int(buf, 2);
1332                 extract(dsn, buf, 3);
1333
1334                 completed = 0;
1335
1336                 if (
1337                    (!strcasecmp(key, "local"))
1338                    || (!strcasecmp(key, "remote"))
1339                    || (!strcasecmp(key, "ignet"))
1340                    || (!strcasecmp(key, "room"))
1341                 ) {
1342                         if (status == 2) completed = 1;
1343                         else ++incomplete;
1344                 }
1345
1346                 if (completed) {
1347                         remove_token(instr, i, '\n');
1348                         --i;
1349                         --lines;
1350                 }
1351         }
1352
1353         return(incomplete);
1354 }
1355
1356
1357 /*
1358  * smtp_do_procmsg()
1359  *
1360  * Called by smtp_do_queue() to handle an individual message.
1361  */
1362 void smtp_do_procmsg(long msgnum, void *userdata) {
1363         struct CtdlMessage *msg;
1364         char *instr = NULL;
1365         char *results = NULL;
1366         int i;
1367         int lines;
1368         int status;
1369         char buf[1024];
1370         char key[1024];
1371         char addr[1024];
1372         char dsn[1024];
1373         long text_msgid = (-1);
1374         int incomplete_deliveries_remaining;
1375         time_t attempted = 0L;
1376         time_t last_attempted = 0L;
1377         time_t retry = SMTP_RETRY_INTERVAL;
1378
1379         lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1380
1381         msg = CtdlFetchMessage(msgnum, 1);
1382         if (msg == NULL) {
1383                 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1384                 return;
1385         }
1386
1387         instr = strdup(msg->cm_fields['M']);
1388         CtdlFreeMessage(msg);
1389
1390         /* Strip out the headers amd any other non-instruction line */
1391         lines = num_tokens(instr, '\n');
1392         for (i=0; i<lines; ++i) {
1393                 extract_token(buf, instr, i, '\n');
1394                 if (num_tokens(buf, '|') < 2) {
1395                         remove_token(instr, i, '\n');
1396                         --lines;
1397                         --i;
1398                 }
1399         }
1400
1401         /* Learn the message ID and find out about recent delivery attempts */
1402         lines = num_tokens(instr, '\n');
1403         for (i=0; i<lines; ++i) {
1404                 extract_token(buf, instr, i, '\n');
1405                 extract(key, buf, 0);
1406                 if (!strcasecmp(key, "msgid")) {
1407                         text_msgid = extract_long(buf, 1);
1408                 }
1409                 if (!strcasecmp(key, "retry")) {
1410                         /* double the retry interval after each attempt */
1411                         retry = extract_long(buf, 1) * 2L;
1412                         if (retry > SMTP_RETRY_MAX) {
1413                                 retry = SMTP_RETRY_MAX;
1414                         }
1415                         remove_token(instr, i, '\n');
1416                 }
1417                 if (!strcasecmp(key, "attempted")) {
1418                         attempted = extract_long(buf, 1);
1419                         if (attempted > last_attempted)
1420                                 last_attempted = attempted;
1421                 }
1422         }
1423
1424         /*
1425          * Postpone delivery if we've already tried recently.
1426          */
1427         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1428                 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1429                 free(instr);
1430                 return;
1431         }
1432
1433
1434         /*
1435          * Bail out if there's no actual message associated with this
1436          */
1437         if (text_msgid < 0L) {
1438                 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1439                 free(instr);
1440                 return;
1441         }
1442
1443         /* Plow through the instructions looking for 'remote' directives and
1444          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1445          * were experienced and it's time to try again)
1446          */
1447         lines = num_tokens(instr, '\n');
1448         for (i=0; i<lines; ++i) {
1449                 extract_token(buf, instr, i, '\n');
1450                 extract(key, buf, 0);
1451                 extract(addr, buf, 1);
1452                 status = extract_int(buf, 2);
1453                 extract(dsn, buf, 3);
1454                 if ( (!strcasecmp(key, "remote"))
1455                    && ((status==0)||(status==3)||(status==4)) ) {
1456
1457                         /* Remove this "remote" instruction from the set,
1458                          * but replace the set's final newline if
1459                          * remove_token() stripped it.  It has to be there.
1460                          */
1461                         remove_token(instr, i, '\n');
1462                         if (instr[strlen(instr)-1] != '\n') {
1463                                 strcat(instr, "\n");
1464                         }
1465
1466                         --i;
1467                         --lines;
1468                         lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1469                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1470                         if (status != 2) {
1471                                 if (results == NULL) {
1472                                         results = malloc(1024);
1473                                         memset(results, 0, 1024);
1474                                 }
1475                                 else {
1476                                         results = realloc(results,
1477                                                 strlen(results) + 1024);
1478                                 }
1479                                 snprintf(&results[strlen(results)], 1024,
1480                                         "%s|%s|%d|%s\n",
1481                                         key, addr, status, dsn);
1482                         }
1483                 }
1484         }
1485
1486         if (results != NULL) {
1487                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1488                 strcat(instr, results);
1489                 free(results);
1490         }
1491
1492
1493         /* Generate 'bounce' messages */
1494         smtp_do_bounce(instr);
1495
1496         /* Go through the delivery list, deleting completed deliveries */
1497         incomplete_deliveries_remaining = 
1498                 smtp_purge_completed_deliveries(instr);
1499
1500
1501         /*
1502          * No delivery instructions remain, so delete both the instructions
1503          * message and the message message.
1504          */
1505         if (incomplete_deliveries_remaining <= 0) {
1506                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1507                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1508         }
1509
1510
1511         /*
1512          * Uncompleted delivery instructions remain, so delete the old
1513          * instructions and replace with the updated ones.
1514          */
1515         if (incomplete_deliveries_remaining > 0) {
1516                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1517                 msg = malloc(sizeof(struct CtdlMessage));
1518                 memset(msg, 0, sizeof(struct CtdlMessage));
1519                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1520                 msg->cm_anon_type = MES_NORMAL;
1521                 msg->cm_format_type = FMT_RFC822;
1522                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1523                 snprintf(msg->cm_fields['M'],
1524                         strlen(instr)+SIZ,
1525                         "Content-type: %s\n\n%s\n"
1526                         "attempted|%ld\n"
1527                         "retry|%ld\n",
1528                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1529                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1530                 CtdlFreeMessage(msg);
1531         }
1532
1533         free(instr);
1534 }
1535
1536
1537
1538 /*
1539  * smtp_do_queue()
1540  * 
1541  * Run through the queue sending out messages.
1542  */
1543 void smtp_do_queue(void) {
1544         static int doing_queue = 0;
1545
1546         /*
1547          * This is a simple concurrency check to make sure only one queue run
1548          * is done at a time.  We could do this with a mutex, but since we
1549          * don't really require extremely fine granularity here, we'll do it
1550          * with a static variable instead.
1551          */
1552         if (doing_queue) return;
1553         doing_queue = 1;
1554
1555         /* 
1556          * Go ahead and run the queue
1557          */
1558         lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1559
1560         if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1561                 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1562                 return;
1563         }
1564         CtdlForEachMessage(MSGS_ALL, 0L,
1565                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1566
1567         lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1568         run_queue_now = 0;
1569         doing_queue = 0;
1570 }
1571
1572
1573
1574 /*****************************************************************************/
1575 /*                          SMTP UTILITY COMMANDS                            */
1576 /*****************************************************************************/
1577
1578 void cmd_smtp(char *argbuf) {
1579         char cmd[SIZ];
1580         char node[SIZ];
1581         char buf[SIZ];
1582         int i;
1583         int num_mxhosts;
1584
1585         if (CtdlAccessCheck(ac_aide)) return;
1586
1587         extract(cmd, argbuf, 0);
1588
1589         if (!strcasecmp(cmd, "mx")) {
1590                 extract(node, argbuf, 1);
1591                 num_mxhosts = getmx(buf, node);
1592                 cprintf("%d %d MX hosts listed for %s\n",
1593                         LISTING_FOLLOWS, num_mxhosts, node);
1594                 for (i=0; i<num_mxhosts; ++i) {
1595                         extract(node, buf, i);
1596                         cprintf("%s\n", node);
1597                 }
1598                 cprintf("000\n");
1599                 return;
1600         }
1601
1602         else if (!strcasecmp(cmd, "runqueue")) {
1603                 run_queue_now = 1;
1604                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1605                 return;
1606         }
1607
1608         else {
1609                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1610         }
1611
1612 }
1613
1614
1615 /*
1616  * Initialize the SMTP outbound queue
1617  */
1618 void smtp_init_spoolout(void) {
1619         struct ctdlroom qrbuf;
1620
1621         /*
1622          * Create the room.  This will silently fail if the room already
1623          * exists, and that's perfectly ok, because we want it to exist.
1624          */
1625         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1626
1627         /*
1628          * Make sure it's set to be a "system room" so it doesn't show up
1629          * in the <K>nown rooms list for Aides.
1630          */
1631         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1632                 qrbuf.QRflags2 |= QR2_SYSTEM;
1633                 lputroom(&qrbuf);
1634         }
1635 }
1636
1637
1638
1639
1640 /*****************************************************************************/
1641 /*                      MODULE INITIALIZATION STUFF                          */
1642 /*****************************************************************************/
1643
1644
1645 char *serv_smtp_init(void)
1646 {
1647         CtdlRegisterServiceHook(config.c_smtp_port,     /* SMTP MTA */
1648                                 NULL,
1649                                 smtp_greeting,
1650                                 smtp_command_loop,
1651                                 NULL);
1652
1653 #ifdef HAVE_OPENSSL
1654         CtdlRegisterServiceHook(config.c_smtps_port,
1655                                 NULL,
1656                                 smtps_greeting,
1657                                 smtp_command_loop,
1658                                 NULL);
1659 #endif
1660
1661         CtdlRegisterServiceHook(config.c_msa_port,      /* SMTP MSA */
1662                                 NULL,
1663                                 smtp_msa_greeting,
1664                                 smtp_command_loop,
1665                                 NULL);
1666
1667         CtdlRegisterServiceHook(0,                      /* local LMTP */
1668                                 "lmtp.socket",
1669                                 lmtp_greeting,
1670                                 smtp_command_loop,
1671                                 NULL);
1672
1673         smtp_init_spoolout();
1674         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1675         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1676         return "$Id$";
1677 }