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