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