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