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