]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
serv_smtp.c: when sending out mail, try EHLO first, then HELO
[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 EHLO command.  If it fails, try the HELO command. */
1084         snprintf(buf, sizeof buf, "EHLO %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                 snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
1095                 lprintf(CTDL_DEBUG, ">%s", buf);
1096                 sock_write(sock, buf, strlen(buf));
1097                 if (ml_sock_gets(sock, buf) < 0) {
1098                         *status = 4;
1099                         strcpy(dsn, "Connection broken during SMTP HELO");
1100                         goto bail;
1101                 }
1102         }
1103         if (buf[0] != '2') {
1104                 if (buf[0] == '4') {
1105                         *status = 4;
1106                         safestrncpy(dsn, &buf[4], 1023);
1107                         goto bail;
1108                 }
1109                 else {
1110                         *status = 5;
1111                         safestrncpy(dsn, &buf[4], 1023);
1112                         goto bail;
1113                 }
1114         }
1115
1116         /* Do an AUTH command if necessary */
1117         if (strlen(mx_user) > 0) {
1118                 sprintf(buf, "%s%c%s%c%s%c", mx_user, 0, mx_user, 0, mx_pass, 0);
1119                 CtdlEncodeBase64(mailfrom, buf, strlen(mx_user) + strlen(mx_user) + strlen(mx_pass) + 3);
1120                 snprintf(buf, sizeof buf, "AUTH PLAIN %s\r\n", mailfrom);
1121                 lprintf(CTDL_DEBUG, ">%s", buf);
1122                 sock_write(sock, buf, strlen(buf));
1123                 if (ml_sock_gets(sock, buf) < 0) {
1124                         *status = 4;
1125                         strcpy(dsn, "Connection broken during SMTP AUTH");
1126                         goto bail;
1127                 }
1128                 lprintf(CTDL_DEBUG, "<%s\n", buf);
1129                 if (buf[0] != '2') {
1130                         if (buf[0] == '4') {
1131                                 *status = 4;
1132                                 safestrncpy(dsn, &buf[4], 1023);
1133                                 goto bail;
1134                         }
1135                         else {
1136                                 *status = 5;
1137                                 safestrncpy(dsn, &buf[4], 1023);
1138                                 goto bail;
1139                         }
1140                 }
1141         }
1142
1143         /* previous command succeeded, now try the MAIL From: command */
1144         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
1145         lprintf(CTDL_DEBUG, ">%s", buf);
1146         sock_write(sock, buf, strlen(buf));
1147         if (ml_sock_gets(sock, buf) < 0) {
1148                 *status = 4;
1149                 strcpy(dsn, "Connection broken during SMTP MAIL");
1150                 goto bail;
1151         }
1152         lprintf(CTDL_DEBUG, "<%s\n", buf);
1153         if (buf[0] != '2') {
1154                 if (buf[0] == '4') {
1155                         *status = 4;
1156                         safestrncpy(dsn, &buf[4], 1023);
1157                         goto bail;
1158                 }
1159                 else {
1160                         *status = 5;
1161                         safestrncpy(dsn, &buf[4], 1023);
1162                         goto bail;
1163                 }
1164         }
1165
1166         /* MAIL succeeded, now try the RCPT To: command */
1167         snprintf(buf, sizeof buf, "RCPT To: <%s@%s>\r\n", user, node);
1168         lprintf(CTDL_DEBUG, ">%s", buf);
1169         sock_write(sock, buf, strlen(buf));
1170         if (ml_sock_gets(sock, buf) < 0) {
1171                 *status = 4;
1172                 strcpy(dsn, "Connection broken during SMTP RCPT");
1173                 goto bail;
1174         }
1175         lprintf(CTDL_DEBUG, "<%s\n", buf);
1176         if (buf[0] != '2') {
1177                 if (buf[0] == '4') {
1178                         *status = 4;
1179                         safestrncpy(dsn, &buf[4], 1023);
1180                         goto bail;
1181                 }
1182                 else {
1183                         *status = 5;
1184                         safestrncpy(dsn, &buf[4], 1023);
1185                         goto bail;
1186                 }
1187         }
1188
1189         /* RCPT succeeded, now try the DATA command */
1190         lprintf(CTDL_DEBUG, ">DATA\n");
1191         sock_write(sock, "DATA\r\n", 6);
1192         if (ml_sock_gets(sock, buf) < 0) {
1193                 *status = 4;
1194                 strcpy(dsn, "Connection broken during SMTP DATA");
1195                 goto bail;
1196         }
1197         lprintf(CTDL_DEBUG, "<%s\n", buf);
1198         if (buf[0] != '3') {
1199                 if (buf[0] == '4') {
1200                         *status = 3;
1201                         safestrncpy(dsn, &buf[4], 1023);
1202                         goto bail;
1203                 }
1204                 else {
1205                         *status = 5;
1206                         safestrncpy(dsn, &buf[4], 1023);
1207                         goto bail;
1208                 }
1209         }
1210
1211         /* If we reach this point, the server is expecting data */
1212         sock_write(sock, msgtext, msg_size);
1213         if (msgtext[msg_size-1] != 10) {
1214                 lprintf(CTDL_WARNING, "Possible problem: message did not "
1215                         "correctly terminate. (expecting 0x10, got 0x%02x)\n",
1216                                 buf[msg_size-1]);
1217         }
1218
1219         sock_write(sock, ".\r\n", 3);
1220         if (ml_sock_gets(sock, buf) < 0) {
1221                 *status = 4;
1222                 strcpy(dsn, "Connection broken during SMTP message transmit");
1223                 goto bail;
1224         }
1225         lprintf(CTDL_DEBUG, "%s\n", buf);
1226         if (buf[0] != '2') {
1227                 if (buf[0] == '4') {
1228                         *status = 4;
1229                         safestrncpy(dsn, &buf[4], 1023);
1230                         goto bail;
1231                 }
1232                 else {
1233                         *status = 5;
1234                         safestrncpy(dsn, &buf[4], 1023);
1235                         goto bail;
1236                 }
1237         }
1238
1239         /* We did it! */
1240         safestrncpy(dsn, &buf[4], 1023);
1241         *status = 2;
1242
1243         lprintf(CTDL_DEBUG, ">QUIT\n");
1244         sock_write(sock, "QUIT\r\n", 6);
1245         ml_sock_gets(sock, buf);
1246         lprintf(CTDL_DEBUG, "<%s\n", buf);
1247         lprintf(CTDL_INFO, "SMTP delivery to <%s> @ <%s> (%s) succeeded\n",
1248                 user, node, name);
1249
1250 bail:   free(msgtext);
1251         sock_close(sock);
1252
1253         /* Write something to the syslog (which may or may not be where the
1254          * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
1255          */
1256         if (enable_syslog) {
1257                 syslog((LOG_MAIL | LOG_INFO),
1258                         "%ld: to=<%s>, relay=%s, stat=%s",
1259                         msgnum,
1260                         addr,
1261                         mx_host,
1262                         dsn
1263                 );
1264         }
1265
1266         return;
1267 }
1268
1269
1270
1271 /*
1272  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
1273  * instructions for "5" codes (permanent fatal errors) and produce/deliver
1274  * a "bounce" message (delivery status notification).
1275  */
1276 void smtp_do_bounce(char *instr) {
1277         int i;
1278         int lines;
1279         int status;
1280         char buf[1024];
1281         char key[1024];
1282         char addr[1024];
1283         char dsn[1024];
1284         char bounceto[1024];
1285         int num_bounces = 0;
1286         int bounce_this = 0;
1287         long bounce_msgid = (-1);
1288         time_t submitted = 0L;
1289         struct CtdlMessage *bmsg = NULL;
1290         int give_up = 0;
1291         struct recptypes *valid;
1292         int successful_bounce = 0;
1293
1294         lprintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
1295         strcpy(bounceto, "");
1296
1297         lines = num_tokens(instr, '\n');
1298
1299
1300         /* See if it's time to give up on delivery of this message */
1301         for (i=0; i<lines; ++i) {
1302                 extract_token(buf, instr, i, '\n', sizeof buf);
1303                 extract_token(key, buf, 0, '|', sizeof key);
1304                 extract_token(addr, buf, 1, '|', sizeof addr);
1305                 if (!strcasecmp(key, "submitted")) {
1306                         submitted = atol(addr);
1307                 }
1308         }
1309
1310         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1311                 give_up = 1;
1312         }
1313
1314
1315
1316         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1317         if (bmsg == NULL) return;
1318         memset(bmsg, 0, sizeof(struct CtdlMessage));
1319
1320         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1321         bmsg->cm_anon_type = MES_NORMAL;
1322         bmsg->cm_format_type = 1;
1323         bmsg->cm_fields['A'] = strdup("Citadel");
1324         bmsg->cm_fields['O'] = strdup(MAILROOM);
1325         bmsg->cm_fields['N'] = strdup(config.c_nodename);
1326         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
1327
1328         if (give_up) bmsg->cm_fields['M'] = strdup(
1329 "A message you sent could not be delivered to some or all of its recipients\n"
1330 "due to prolonged unavailability of its destination(s).\n"
1331 "Giving up on the following addresses:\n\n"
1332 );
1333
1334         else bmsg->cm_fields['M'] = strdup(
1335 "A message you sent could not be delivered to some or all of its recipients.\n"
1336 "The following addresses were undeliverable:\n\n"
1337 );
1338
1339         /*
1340          * Now go through the instructions checking for stuff.
1341          */
1342         for (i=0; i<lines; ++i) {
1343                 extract_token(buf, instr, i, '\n', sizeof buf);
1344                 extract_token(key, buf, 0, '|', sizeof key);
1345                 extract_token(addr, buf, 1, '|', sizeof addr);
1346                 status = extract_int(buf, 2);
1347                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1348                 bounce_this = 0;
1349
1350                 lprintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1351                         key, addr, status, dsn);
1352
1353                 if (!strcasecmp(key, "bounceto")) {
1354                         strcpy(bounceto, addr);
1355                 }
1356
1357                 if (
1358                    (!strcasecmp(key, "local"))
1359                    || (!strcasecmp(key, "remote"))
1360                    || (!strcasecmp(key, "ignet"))
1361                    || (!strcasecmp(key, "room"))
1362                 ) {
1363                         if (status == 5) bounce_this = 1;
1364                         if (give_up) bounce_this = 1;
1365                 }
1366
1367                 if (bounce_this) {
1368                         ++num_bounces;
1369
1370                         if (bmsg->cm_fields['M'] == NULL) {
1371                                 lprintf(CTDL_ERR, "ERROR ... M field is null "
1372                                         "(%s:%d)\n", __FILE__, __LINE__);
1373                         }
1374
1375                         bmsg->cm_fields['M'] = realloc(bmsg->cm_fields['M'],
1376                                 strlen(bmsg->cm_fields['M']) + 1024 );
1377                         strcat(bmsg->cm_fields['M'], addr);
1378                         strcat(bmsg->cm_fields['M'], ": ");
1379                         strcat(bmsg->cm_fields['M'], dsn);
1380                         strcat(bmsg->cm_fields['M'], "\n");
1381
1382                         remove_token(instr, i, '\n');
1383                         --i;
1384                         --lines;
1385                 }
1386         }
1387
1388         /* Deliver the bounce if there's anything worth mentioning */
1389         lprintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
1390         if (num_bounces > 0) {
1391
1392                 /* First try the user who sent the message */
1393                 lprintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
1394                 if (strlen(bounceto) == 0) {
1395                         lprintf(CTDL_ERR, "No bounce address specified\n");
1396                         bounce_msgid = (-1L);
1397                 }
1398
1399                 /* Can we deliver the bounce to the original sender? */
1400                 valid = validate_recipients(bounceto);
1401                 if (valid != NULL) {
1402                         if (valid->num_error == 0) {
1403                                 CtdlSubmitMsg(bmsg, valid, "");
1404                                 successful_bounce = 1;
1405                         }
1406                 }
1407
1408                 /* If not, post it in the Aide> room */
1409                 if (successful_bounce == 0) {
1410                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1411                 }
1412
1413                 /* Free up the memory we used */
1414                 if (valid != NULL) {
1415                         free(valid);
1416                 }
1417         }
1418
1419         CtdlFreeMessage(bmsg);
1420         lprintf(CTDL_DEBUG, "Done processing bounces\n");
1421 }
1422
1423
1424 /*
1425  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1426  * set of delivery instructions for completed deliveries and remove them.
1427  *
1428  * It returns the number of incomplete deliveries remaining.
1429  */
1430 int smtp_purge_completed_deliveries(char *instr) {
1431         int i;
1432         int lines;
1433         int status;
1434         char buf[1024];
1435         char key[1024];
1436         char addr[1024];
1437         char dsn[1024];
1438         int completed;
1439         int incomplete = 0;
1440
1441         lines = num_tokens(instr, '\n');
1442         for (i=0; i<lines; ++i) {
1443                 extract_token(buf, instr, i, '\n', sizeof buf);
1444                 extract_token(key, buf, 0, '|', sizeof key);
1445                 extract_token(addr, buf, 1, '|', sizeof addr);
1446                 status = extract_int(buf, 2);
1447                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1448
1449                 completed = 0;
1450
1451                 if (
1452                    (!strcasecmp(key, "local"))
1453                    || (!strcasecmp(key, "remote"))
1454                    || (!strcasecmp(key, "ignet"))
1455                    || (!strcasecmp(key, "room"))
1456                 ) {
1457                         if (status == 2) completed = 1;
1458                         else ++incomplete;
1459                 }
1460
1461                 if (completed) {
1462                         remove_token(instr, i, '\n');
1463                         --i;
1464                         --lines;
1465                 }
1466         }
1467
1468         return(incomplete);
1469 }
1470
1471
1472 /*
1473  * smtp_do_procmsg()
1474  *
1475  * Called by smtp_do_queue() to handle an individual message.
1476  */
1477 void smtp_do_procmsg(long msgnum, void *userdata) {
1478         struct CtdlMessage *msg;
1479         char *instr = NULL;
1480         char *results = NULL;
1481         int i;
1482         int lines;
1483         int status;
1484         char buf[1024];
1485         char key[1024];
1486         char addr[1024];
1487         char dsn[1024];
1488         long text_msgid = (-1);
1489         int incomplete_deliveries_remaining;
1490         time_t attempted = 0L;
1491         time_t last_attempted = 0L;
1492         time_t retry = SMTP_RETRY_INTERVAL;
1493
1494         lprintf(CTDL_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum);
1495
1496         msg = CtdlFetchMessage(msgnum, 1);
1497         if (msg == NULL) {
1498                 lprintf(CTDL_ERR, "SMTP: tried %ld but no such message!\n", msgnum);
1499                 return;
1500         }
1501
1502         instr = strdup(msg->cm_fields['M']);
1503         CtdlFreeMessage(msg);
1504
1505         /* Strip out the headers amd any other non-instruction line */
1506         lines = num_tokens(instr, '\n');
1507         for (i=0; i<lines; ++i) {
1508                 extract_token(buf, instr, i, '\n', sizeof buf);
1509                 if (num_tokens(buf, '|') < 2) {
1510                         remove_token(instr, i, '\n');
1511                         --lines;
1512                         --i;
1513                 }
1514         }
1515
1516         /* Learn the message ID and find out about recent delivery attempts */
1517         lines = num_tokens(instr, '\n');
1518         for (i=0; i<lines; ++i) {
1519                 extract_token(buf, instr, i, '\n', sizeof buf);
1520                 extract_token(key, buf, 0, '|', sizeof key);
1521                 if (!strcasecmp(key, "msgid")) {
1522                         text_msgid = extract_long(buf, 1);
1523                 }
1524                 if (!strcasecmp(key, "retry")) {
1525                         /* double the retry interval after each attempt */
1526                         retry = extract_long(buf, 1) * 2L;
1527                         if (retry > SMTP_RETRY_MAX) {
1528                                 retry = SMTP_RETRY_MAX;
1529                         }
1530                         remove_token(instr, i, '\n');
1531                 }
1532                 if (!strcasecmp(key, "attempted")) {
1533                         attempted = extract_long(buf, 1);
1534                         if (attempted > last_attempted)
1535                                 last_attempted = attempted;
1536                 }
1537         }
1538
1539         /*
1540          * Postpone delivery if we've already tried recently.
1541          */
1542         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1543                 lprintf(CTDL_DEBUG, "Retry time not yet reached.\n");
1544                 free(instr);
1545                 return;
1546         }
1547
1548
1549         /*
1550          * Bail out if there's no actual message associated with this
1551          */
1552         if (text_msgid < 0L) {
1553                 lprintf(CTDL_ERR, "SMTP: no 'msgid' directive found!\n");
1554                 free(instr);
1555                 return;
1556         }
1557
1558         /* Plow through the instructions looking for 'remote' directives and
1559          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1560          * were experienced and it's time to try again)
1561          */
1562         lines = num_tokens(instr, '\n');
1563         for (i=0; i<lines; ++i) {
1564                 extract_token(buf, instr, i, '\n', sizeof buf);
1565                 extract_token(key, buf, 0, '|', sizeof key);
1566                 extract_token(addr, buf, 1, '|', sizeof addr);
1567                 status = extract_int(buf, 2);
1568                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1569                 if ( (!strcasecmp(key, "remote"))
1570                    && ((status==0)||(status==3)||(status==4)) ) {
1571
1572                         /* Remove this "remote" instruction from the set,
1573                          * but replace the set's final newline if
1574                          * remove_token() stripped it.  It has to be there.
1575                          */
1576                         remove_token(instr, i, '\n');
1577                         if (instr[strlen(instr)-1] != '\n') {
1578                                 strcat(instr, "\n");
1579                         }
1580
1581                         --i;
1582                         --lines;
1583                         lprintf(CTDL_DEBUG, "SMTP: Trying <%s>\n", addr);
1584                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1585                         if (status != 2) {
1586                                 if (results == NULL) {
1587                                         results = malloc(1024);
1588                                         memset(results, 0, 1024);
1589                                 }
1590                                 else {
1591                                         results = realloc(results,
1592                                                 strlen(results) + 1024);
1593                                 }
1594                                 snprintf(&results[strlen(results)], 1024,
1595                                         "%s|%s|%d|%s\n",
1596                                         key, addr, status, dsn);
1597                         }
1598                 }
1599         }
1600
1601         if (results != NULL) {
1602                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1603                 strcat(instr, results);
1604                 free(results);
1605         }
1606
1607
1608         /* Generate 'bounce' messages */
1609         smtp_do_bounce(instr);
1610
1611         /* Go through the delivery list, deleting completed deliveries */
1612         incomplete_deliveries_remaining = 
1613                 smtp_purge_completed_deliveries(instr);
1614
1615
1616         /*
1617          * No delivery instructions remain, so delete both the instructions
1618          * message and the message message.
1619          */
1620         if (incomplete_deliveries_remaining <= 0) {
1621                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1622                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "", 0);
1623         }
1624
1625
1626         /*
1627          * Uncompleted delivery instructions remain, so delete the old
1628          * instructions and replace with the updated ones.
1629          */
1630         if (incomplete_deliveries_remaining > 0) {
1631                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "", 0);
1632                 msg = malloc(sizeof(struct CtdlMessage));
1633                 memset(msg, 0, sizeof(struct CtdlMessage));
1634                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1635                 msg->cm_anon_type = MES_NORMAL;
1636                 msg->cm_format_type = FMT_RFC822;
1637                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1638                 snprintf(msg->cm_fields['M'],
1639                         strlen(instr)+SIZ,
1640                         "Content-type: %s\n\n%s\n"
1641                         "attempted|%ld\n"
1642                         "retry|%ld\n",
1643                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1644                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1645                 CtdlFreeMessage(msg);
1646         }
1647
1648         free(instr);
1649 }
1650
1651
1652
1653 /*
1654  * smtp_do_queue()
1655  * 
1656  * Run through the queue sending out messages.
1657  */
1658 void smtp_do_queue(void) {
1659         static int doing_queue = 0;
1660
1661         /*
1662          * This is a simple concurrency check to make sure only one queue run
1663          * is done at a time.  We could do this with a mutex, but since we
1664          * don't really require extremely fine granularity here, we'll do it
1665          * with a static variable instead.
1666          */
1667         if (doing_queue) return;
1668         doing_queue = 1;
1669
1670         /* 
1671          * Go ahead and run the queue
1672          */
1673         lprintf(CTDL_INFO, "SMTP: processing outbound queue\n");
1674
1675         if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1676                 lprintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1677                 return;
1678         }
1679         CtdlForEachMessage(MSGS_ALL, 0L,
1680                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1681
1682         lprintf(CTDL_INFO, "SMTP: queue run completed\n");
1683         run_queue_now = 0;
1684         doing_queue = 0;
1685 }
1686
1687
1688
1689 /*****************************************************************************/
1690 /*                          SMTP UTILITY COMMANDS                            */
1691 /*****************************************************************************/
1692
1693 void cmd_smtp(char *argbuf) {
1694         char cmd[64];
1695         char node[256];
1696         char buf[1024];
1697         int i;
1698         int num_mxhosts;
1699
1700         if (CtdlAccessCheck(ac_aide)) return;
1701
1702         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1703
1704         if (!strcasecmp(cmd, "mx")) {
1705                 extract_token(node, argbuf, 1, '|', sizeof node);
1706                 num_mxhosts = getmx(buf, node);
1707                 cprintf("%d %d MX hosts listed for %s\n",
1708                         LISTING_FOLLOWS, num_mxhosts, node);
1709                 for (i=0; i<num_mxhosts; ++i) {
1710                         extract_token(node, buf, i, '|', sizeof node);
1711                         cprintf("%s\n", node);
1712                 }
1713                 cprintf("000\n");
1714                 return;
1715         }
1716
1717         else if (!strcasecmp(cmd, "runqueue")) {
1718                 run_queue_now = 1;
1719                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1720                 return;
1721         }
1722
1723         else {
1724                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1725         }
1726
1727 }
1728
1729
1730 /*
1731  * Initialize the SMTP outbound queue
1732  */
1733 void smtp_init_spoolout(void) {
1734         struct ctdlroom qrbuf;
1735
1736         /*
1737          * Create the room.  This will silently fail if the room already
1738          * exists, and that's perfectly ok, because we want it to exist.
1739          */
1740         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1741
1742         /*
1743          * Make sure it's set to be a "system room" so it doesn't show up
1744          * in the <K>nown rooms list for Aides.
1745          */
1746         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1747                 qrbuf.QRflags2 |= QR2_SYSTEM;
1748                 lputroom(&qrbuf);
1749         }
1750 }
1751
1752
1753
1754
1755 /*****************************************************************************/
1756 /*                      MODULE INITIALIZATION STUFF                          */
1757 /*****************************************************************************/
1758 /*
1759  * This cleanup function blows away the temporary memory used by
1760  * the SMTP server.
1761  */
1762 void smtp_cleanup_function(void) {
1763
1764         /* Don't do this stuff if this is not an SMTP session! */
1765         if (CC->h_command_function != smtp_command_loop) return;
1766
1767         lprintf(CTDL_DEBUG, "Performing SMTP cleanup hook\n");
1768         free(SMTP);
1769         free(SMTP_ROOMS);
1770         free(SMTP_RECPS);
1771 }
1772
1773
1774
1775
1776
1777 char *serv_smtp_init(void)
1778 {
1779
1780         CtdlRegisterServiceHook(config.c_smtp_port,     /* SMTP MTA */
1781                                 NULL,
1782                                 smtp_greeting,
1783                                 smtp_command_loop,
1784                                 NULL);
1785
1786 #ifdef HAVE_OPENSSL
1787         CtdlRegisterServiceHook(config.c_smtps_port,
1788                                 NULL,
1789                                 smtps_greeting,
1790                                 smtp_command_loop,
1791                                 NULL);
1792 #endif
1793
1794         CtdlRegisterServiceHook(config.c_msa_port,      /* SMTP MSA */
1795                                 NULL,
1796                                 smtp_msa_greeting,
1797                                 smtp_command_loop,
1798                                 NULL);
1799
1800         CtdlRegisterServiceHook(0,                      /* local LMTP */
1801                                                         file_lmtp_socket,
1802                                                         lmtp_greeting,
1803                                                         smtp_command_loop,
1804                                                         NULL);
1805
1806         CtdlRegisterServiceHook(0,                      /* local LMTP */
1807                                                         file_lmtp_unfiltered_socket,
1808                                                         lmtp_unfiltered_greeting,
1809                                                         smtp_command_loop,
1810                                                         NULL);
1811
1812         smtp_init_spoolout();
1813         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1814         CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP);
1815         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1816         return "$Id$";
1817 }