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