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