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