* More message submission queue stuff
[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 Authentication successful.\r\n");
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
405         if (strlen(SMTP->from) == 0) {
406                 cprintf("503 Need MAIL before RCPT\r\n");
407                 return;
408         }
409
410         if (strncasecmp(argbuf, "To:", 3)) {
411                 cprintf("501 Syntax error\r\n");
412                 return;
413         }
414
415         strcpy(recp, &argbuf[3]);
416         striplt(recp);
417         alias(recp);
418
419         cvt = convert_internet_address(user, node, recp);
420         snprintf(recp, sizeof recp, "%s@%s", user, node);
421         lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
422
423         /* FIXME possible buffer overflow type of issues here. */
424         switch(cvt) {
425                 case rfc822_address_locally_validated:
426                         cprintf("250 %s is a valid local user.\r\n", user);
427                         if (SMTP->valid.num_local > 0) {
428                                 strcat(SMTP->valid.recp_local, "|");
429                         }
430                         strcat(SMTP->valid.recp_local, user);
431                         SMTP->valid.num_local += 1;
432                         SMTP->number_of_recipients += 1;
433                         return;
434
435                 case rfc822_room_delivery:
436                         cprintf("250 Delivering to room '%s'\r\n", user);
437                         cprintf("250 %s is a valid recipient.\r\n", user);
438                         if (SMTP->valid.num_room > 0) {
439                                 strcat(SMTP->valid.recp_room, "|");
440                         }
441                         strcat(SMTP->valid.recp_room, user);
442                         SMTP->valid.num_room += 1;
443                         SMTP->number_of_recipients += 1;
444                         return;
445
446                 case rfc822_address_on_citadel_network:
447                         cprintf("250 '%s' is a valid network user.\r\n", user);
448                         if (SMTP->valid.num_ignet > 0) {
449                                 strcat(SMTP->valid.recp_ignet, "|");
450                         }
451                         strcat(SMTP->valid.recp_ignet, user);
452                         SMTP->valid.num_ignet += 1;
453                         SMTP->number_of_recipients += 1;
454                         return;
455
456                 case rfc822_no_such_user:
457                         cprintf("550 %s: no such user\r\n", recp);
458                         return;
459
460                 case rfc822_address_nonlocal:
461                         if (SMTP->message_originated_locally == 0) {
462                                 cprintf("551 Relaying denied.\r\n");
463                         }
464                         else {
465                                 cprintf("250 Remote recipient %s ok\r\n", recp);
466                                 SMTP->number_of_recipients += 1;
467                                 return;
468                         }
469                         return;
470         }
471
472         cprintf("599 Unknown error\r\n");
473 }
474
475
476
477
478 /*
479  * Implements the DATA command
480  */
481 void smtp_data(void) {
482         char *body;
483         struct CtdlMessage *msg;
484         long msgnum;
485         char nowstamp[SIZ];
486
487         if (strlen(SMTP->from) == 0) {
488                 cprintf("503 Need MAIL command first.\r\n");
489                 return;
490         }
491
492         if (SMTP->number_of_recipients < 1) {
493                 cprintf("503 Need RCPT command first.\r\n");
494                 return;
495         }
496
497         cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
498         
499         datestring(nowstamp, time(NULL), DATESTRING_RFC822);
500         body = mallok(4096);
501
502         if (body != NULL) snprintf(body, 4096,
503                 "Received: from %s\n"
504                 "       by %s;\n"
505                 "       %s\n",
506                         SMTP->helo_node,
507                         config.c_fqdn,
508                         nowstamp);
509         
510         body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
511         if (body == NULL) {
512                 cprintf("550 Unable to save message: internal error.\r\n");
513                 return;
514         }
515
516         lprintf(9, "Converting message...\n");
517         msg = convert_internet_message(body);
518
519         /* If the user is locally authenticated, FORCE the From: header to
520          * show up as the real sender.  Yes, this violates the RFC standard,
521          * but IT MAKES SENSE.  Comment it out if you don't like this behavior.
522          */
523         if (CC->logged_in) {
524                 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
525                 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
526                 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
527                 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
528                 msg->cm_fields['N'] = strdoop(config.c_nodename);
529                 msg->cm_fields['H'] = strdoop(config.c_humannode);
530         }
531
532         /* Submit the message into the Citadel system. */
533         msgnum = CtdlSubmitMsg(msg, &SMTP->valid, "");
534         CtdlFreeMessage(msg);
535
536         if (msgnum > 0L) {
537                 cprintf("250 Message accepted.\r\n");
538         }
539         else {
540                 cprintf("550 Internal delivery error\r\n");
541         }
542
543         smtp_data_clear();      /* clear out the buffers now */
544 }
545
546
547
548
549 /* 
550  * Main command loop for SMTP sessions.
551  */
552 void smtp_command_loop(void) {
553         char cmdbuf[SIZ];
554
555         time(&CC->lastcmd);
556         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
557         if (client_gets(cmdbuf) < 1) {
558                 lprintf(3, "SMTP socket is broken.  Ending session.\n");
559                 CC->kill_me = 1;
560                 return;
561         }
562         lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
563         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
564
565         if (SMTP->command_state == smtp_user) {
566                 smtp_get_user(cmdbuf);
567         }
568
569         else if (SMTP->command_state == smtp_password) {
570                 smtp_get_pass(cmdbuf);
571         }
572
573         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
574                 smtp_auth(&cmdbuf[5]);
575         }
576
577         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
578                 smtp_data();
579         }
580
581         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
582                 smtp_hello(&cmdbuf[5], 1);
583         }
584
585         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
586                 smtp_expn(&cmdbuf[5]);
587         }
588
589         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
590                 smtp_hello(&cmdbuf[5], 0);
591         }
592
593         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
594                 smtp_help();
595         }
596
597         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
598                 smtp_mail(&cmdbuf[5]);
599         }
600
601         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
602                 cprintf("250 NOOP\r\n");
603         }
604
605         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
606                 cprintf("221 Goodbye...\r\n");
607                 CC->kill_me = 1;
608                 return;
609         }
610
611         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
612                 smtp_rcpt(&cmdbuf[5]);
613         }
614
615         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
616                 smtp_rset();
617         }
618
619         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
620                 smtp_vrfy(&cmdbuf[5]);
621         }
622
623         else {
624                 cprintf("502 I'm afraid I can't do that.\r\n");
625         }
626
627 }
628
629
630
631
632 /*****************************************************************************/
633 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
634 /*****************************************************************************/
635
636
637
638 /*
639  * smtp_try()
640  *
641  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
642  *
643  */
644 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
645 {
646         int sock = (-1);
647         char mxhosts[1024];
648         int num_mxhosts;
649         int mx;
650         int i;
651         char user[SIZ], node[SIZ], name[SIZ];
652         char buf[1024];
653         char mailfrom[1024];
654         int lp, rp;
655         FILE *msg_fp = NULL;
656         size_t msg_size;
657         size_t blocksize = 0;
658         int scan_done;
659
660         /* Parse out the host portion of the recipient address */
661         process_rfc822_addr(addr, user, node, name);
662
663         lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
664                 user, node, name);
665
666         /* Load the message out of the database into a temp file */
667         msg_fp = tmpfile();
668         if (msg_fp == NULL) {
669                 *status = 4;
670                 sprintf(dsn, "Error creating temporary file");
671                 return;
672         }
673         else {
674                 CtdlRedirectOutput(msg_fp, -1);
675                 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
676                 CtdlRedirectOutput(NULL, -1);
677                 fseek(msg_fp, 0L, SEEK_END);
678                 msg_size = ftell(msg_fp);
679         }
680
681
682         /* Extract something to send later in the 'MAIL From:' command */
683         strcpy(mailfrom, "");
684         rewind(msg_fp);
685         scan_done = 0;
686         do {
687                 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
688                 if (!strncasecmp(buf, "From:", 5)) {
689                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
690                         striplt(mailfrom);
691                         for (i=0; i<strlen(mailfrom); ++i) {
692                                 if (!isprint(mailfrom[i])) {
693                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
694                                         i=0;
695                                 }
696                         }
697
698                         /* Strip out parenthesized names */
699                         lp = (-1);
700                         rp = (-1);
701                         for (i=0; i<strlen(mailfrom); ++i) {
702                                 if (mailfrom[i] == '(') lp = i;
703                                 if (mailfrom[i] == ')') rp = i;
704                         }
705                         if ((lp>0)&&(rp>lp)) {
706                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
707                         }
708
709                         /* Prefer brokketized names */
710                         lp = (-1);
711                         rp = (-1);
712                         for (i=0; i<strlen(mailfrom); ++i) {
713                                 if (mailfrom[i] == '<') lp = i;
714                                 if (mailfrom[i] == '>') rp = i;
715                         }
716                         if ( (lp>=0) && (rp>lp) ) {
717                                 mailfrom[rp] = 0;
718                                 strcpy(mailfrom, &mailfrom[lp]);
719                         }
720
721                         scan_done = 1;
722                 }
723         } while (scan_done == 0);
724         if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
725
726         /* Figure out what mail exchanger host we have to connect to */
727         num_mxhosts = getmx(mxhosts, node);
728         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
729         if (num_mxhosts < 1) {
730                 *status = 5;
731                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
732                 return;
733         }
734
735         for (mx=0; mx<num_mxhosts; ++mx) {
736                 extract(buf, mxhosts, mx);
737                 lprintf(9, "Trying <%s>\n", buf);
738                 sock = sock_connect(buf, "25", "tcp");
739                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
740                 if (sock >= 0) lprintf(9, "Connected!\n");
741                 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
742                 if (sock >= 0) break;
743         }
744
745         if (sock < 0) {
746                 *status = 4;    /* dsn is already filled in */
747                 return;
748         }
749
750         /* Process the SMTP greeting from the server */
751         if (ml_sock_gets(sock, buf) < 0) {
752                 *status = 4;
753                 strcpy(dsn, "Connection broken during SMTP conversation");
754                 goto bail;
755         }
756         lprintf(9, "<%s\n", buf);
757         if (buf[0] != '2') {
758                 if (buf[0] == '4') {
759                         *status = 4;
760                         safestrncpy(dsn, &buf[4], 1023);
761                         goto bail;
762                 }
763                 else {
764                         *status = 5;
765                         safestrncpy(dsn, &buf[4], 1023);
766                         goto bail;
767                 }
768         }
769
770         /* At this point we know we are talking to a real SMTP server */
771
772         /* Do a HELO command */
773         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
774         lprintf(9, ">%s", buf);
775         sock_write(sock, buf, strlen(buf));
776         if (ml_sock_gets(sock, buf) < 0) {
777                 *status = 4;
778                 strcpy(dsn, "Connection broken during SMTP HELO");
779                 goto bail;
780         }
781         lprintf(9, "<%s\n", buf);
782         if (buf[0] != '2') {
783                 if (buf[0] == '4') {
784                         *status = 4;
785                         safestrncpy(dsn, &buf[4], 1023);
786                         goto bail;
787                 }
788                 else {
789                         *status = 5;
790                         safestrncpy(dsn, &buf[4], 1023);
791                         goto bail;
792                 }
793         }
794
795
796         /* HELO succeeded, now try the MAIL From: command */
797         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
798         lprintf(9, ">%s", buf);
799         sock_write(sock, buf, strlen(buf));
800         if (ml_sock_gets(sock, buf) < 0) {
801                 *status = 4;
802                 strcpy(dsn, "Connection broken during SMTP MAIL");
803                 goto bail;
804         }
805         lprintf(9, "<%s\n", buf);
806         if (buf[0] != '2') {
807                 if (buf[0] == '4') {
808                         *status = 4;
809                         safestrncpy(dsn, &buf[4], 1023);
810                         goto bail;
811                 }
812                 else {
813                         *status = 5;
814                         safestrncpy(dsn, &buf[4], 1023);
815                         goto bail;
816                 }
817         }
818
819
820         /* MAIL succeeded, now try the RCPT To: command */
821         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
822         lprintf(9, ">%s", buf);
823         sock_write(sock, buf, strlen(buf));
824         if (ml_sock_gets(sock, buf) < 0) {
825                 *status = 4;
826                 strcpy(dsn, "Connection broken during SMTP RCPT");
827                 goto bail;
828         }
829         lprintf(9, "<%s\n", buf);
830         if (buf[0] != '2') {
831                 if (buf[0] == '4') {
832                         *status = 4;
833                         safestrncpy(dsn, &buf[4], 1023);
834                         goto bail;
835                 }
836                 else {
837                         *status = 5;
838                         safestrncpy(dsn, &buf[4], 1023);
839                         goto bail;
840                 }
841         }
842
843
844         /* RCPT succeeded, now try the DATA command */
845         lprintf(9, ">DATA\n");
846         sock_write(sock, "DATA\r\n", 6);
847         if (ml_sock_gets(sock, buf) < 0) {
848                 *status = 4;
849                 strcpy(dsn, "Connection broken during SMTP DATA");
850                 goto bail;
851         }
852         lprintf(9, "<%s\n", buf);
853         if (buf[0] != '3') {
854                 if (buf[0] == '4') {
855                         *status = 3;
856                         safestrncpy(dsn, &buf[4], 1023);
857                         goto bail;
858                 }
859                 else {
860                         *status = 5;
861                         safestrncpy(dsn, &buf[4], 1023);
862                         goto bail;
863                 }
864         }
865
866         /* If we reach this point, the server is expecting data */
867         rewind(msg_fp);
868         while (msg_size > 0) {
869                 blocksize = sizeof(buf);
870                 if (blocksize > msg_size) blocksize = msg_size;
871                 fread(buf, blocksize, 1, msg_fp);
872                 sock_write(sock, buf, blocksize);
873                 msg_size -= blocksize;
874         }
875         if (buf[blocksize-1] != 10) {
876                 lprintf(5, "Possible problem: message did not correctly "
877                         "terminate. (expecting 0x10, got 0x%02x)\n",
878                                 buf[blocksize-1]);
879         }
880
881         sock_write(sock, ".\r\n", 3);
882         if (ml_sock_gets(sock, buf) < 0) {
883                 *status = 4;
884                 strcpy(dsn, "Connection broken during SMTP message transmit");
885                 goto bail;
886         }
887         lprintf(9, "%s\n", buf);
888         if (buf[0] != '2') {
889                 if (buf[0] == '4') {
890                         *status = 4;
891                         safestrncpy(dsn, &buf[4], 1023);
892                         goto bail;
893                 }
894                 else {
895                         *status = 5;
896                         safestrncpy(dsn, &buf[4], 1023);
897                         goto bail;
898                 }
899         }
900
901         /* We did it! */
902         safestrncpy(dsn, &buf[4], 1023);
903         *status = 2;
904
905         lprintf(9, ">QUIT\n");
906         sock_write(sock, "QUIT\r\n", 6);
907         ml_sock_gets(sock, buf);
908         lprintf(9, "<%s\n", buf);
909
910 bail:   if (msg_fp != NULL) fclose(msg_fp);
911         sock_close(sock);
912         return;
913 }
914
915
916
917 /*
918  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
919  * instructions for "5" codes (permanent fatal errors) and produce/deliver
920  * a "bounce" message (delivery status notification).
921  */
922 void smtp_do_bounce(char *instr) {
923         int i;
924         int lines;
925         int status;
926         char buf[1024];
927         char key[1024];
928         char addr[1024];
929         char dsn[1024];
930         char bounceto[1024];
931         int num_bounces = 0;
932         int bounce_this = 0;
933         long bounce_msgid = (-1);
934         time_t submitted = 0L;
935         struct CtdlMessage *bmsg = NULL;
936         int give_up = 0;
937         struct recptypes *valid;
938         int successful_bounce = 0;
939
940         lprintf(9, "smtp_do_bounce() called\n");
941         strcpy(bounceto, "");
942
943         lines = num_tokens(instr, '\n');
944
945
946         /* See if it's time to give up on delivery of this message */
947         for (i=0; i<lines; ++i) {
948                 extract_token(buf, instr, i, '\n');
949                 extract(key, buf, 0);
950                 extract(addr, buf, 1);
951                 if (!strcasecmp(key, "submitted")) {
952                         submitted = atol(addr);
953                 }
954         }
955
956         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
957                 give_up = 1;
958         }
959
960
961
962         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
963         if (bmsg == NULL) return;
964         memset(bmsg, 0, sizeof(struct CtdlMessage));
965
966         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
967         bmsg->cm_anon_type = MES_NORMAL;
968         bmsg->cm_format_type = 1;
969         bmsg->cm_fields['A'] = strdoop("Citadel");
970         bmsg->cm_fields['O'] = strdoop(MAILROOM);
971         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
972
973         if (give_up) bmsg->cm_fields['M'] = strdoop(
974 "A message you sent could not be delivered to some or all of its recipients\n"
975 "due to prolonged unavailability of its destination(s).\n"
976 "Giving up on the following addresses:\n\n"
977 );
978
979         else bmsg->cm_fields['M'] = strdoop(
980 "A message you sent could not be delivered to some or all of its recipients.\n"
981 "The following addresses were undeliverable:\n\n"
982 );
983
984         /*
985          * Now go through the instructions checking for stuff.
986          */
987
988         for (i=0; i<lines; ++i) {
989                 extract_token(buf, instr, i, '\n');
990                 extract(key, buf, 0);
991                 extract(addr, buf, 1);
992                 status = extract_int(buf, 2);
993                 extract(dsn, buf, 3);
994                 bounce_this = 0;
995
996                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
997                         key, addr, status, dsn);
998
999                 if (!strcasecmp(key, "bounceto")) {
1000                         strcpy(bounceto, addr);
1001                 }
1002
1003                 if (
1004                    (!strcasecmp(key, "local"))
1005                    || (!strcasecmp(key, "remote"))
1006                    || (!strcasecmp(key, "ignet"))
1007                    || (!strcasecmp(key, "room"))
1008                 ) {
1009                         if (status == 5) bounce_this = 1;
1010                         if (give_up) bounce_this = 1;
1011                 }
1012
1013                 if (bounce_this) {
1014                         ++num_bounces;
1015
1016                         if (bmsg->cm_fields['M'] == NULL) {
1017                                 lprintf(2, "ERROR ... M field is null "
1018                                         "(%s:%d)\n", __FILE__, __LINE__);
1019                         }
1020
1021                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1022                                 strlen(bmsg->cm_fields['M']) + 1024 );
1023                         strcat(bmsg->cm_fields['M'], addr);
1024                         strcat(bmsg->cm_fields['M'], ": ");
1025                         strcat(bmsg->cm_fields['M'], dsn);
1026                         strcat(bmsg->cm_fields['M'], "\n");
1027
1028                         remove_token(instr, i, '\n');
1029                         --i;
1030                         --lines;
1031                 }
1032         }
1033
1034         /* Deliver the bounce if there's anything worth mentioning */
1035         lprintf(9, "num_bounces = %d\n", num_bounces);
1036         if (num_bounces > 0) {
1037
1038                 /* First try the user who sent the message */
1039                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1040                 TRACE;
1041                 if (strlen(bounceto) == 0) {
1042                         lprintf(7, "No bounce address specified\n");
1043                         bounce_msgid = (-1L);
1044                 }
1045
1046                 /* Can we deliver the bounce to the original sender? */
1047                 valid = validate_recipients(bounceto);
1048                 if (valid != NULL) {
1049                         if (valid->num_error == 0) {
1050                                 CtdlSubmitMsg(bmsg, valid, "");
1051                                 successful_bounce = 1;
1052                         }
1053                 }
1054
1055                 /* If not, post it in the Aide> room */
1056                 if (successful_bounce == 0) {
1057                         CtdlSubmitMsg(bmsg, NULL, AIDEROOM);
1058                 }
1059
1060                 /* Free up the memory we used */
1061                 if (valid != NULL) {
1062                         phree(valid);
1063                 }
1064         }
1065
1066         CtdlFreeMessage(bmsg);
1067         lprintf(9, "Done processing bounces\n");
1068 }
1069
1070
1071 /*
1072  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1073  * set of delivery instructions for completed deliveries and remove them.
1074  *
1075  * It returns the number of incomplete deliveries remaining.
1076  */
1077 int smtp_purge_completed_deliveries(char *instr) {
1078         int i;
1079         int lines;
1080         int status;
1081         char buf[1024];
1082         char key[1024];
1083         char addr[1024];
1084         char dsn[1024];
1085         int completed;
1086         int incomplete = 0;
1087
1088         lines = num_tokens(instr, '\n');
1089         for (i=0; i<lines; ++i) {
1090                 extract_token(buf, instr, i, '\n');
1091                 extract(key, buf, 0);
1092                 extract(addr, buf, 1);
1093                 status = extract_int(buf, 2);
1094                 extract(dsn, buf, 3);
1095
1096                 completed = 0;
1097
1098                 if (
1099                    (!strcasecmp(key, "local"))
1100                    || (!strcasecmp(key, "remote"))
1101                    || (!strcasecmp(key, "ignet"))
1102                    || (!strcasecmp(key, "room"))
1103                 ) {
1104                         if (status == 2) completed = 1;
1105                         else ++incomplete;
1106                 }
1107
1108                 if (completed) {
1109                         remove_token(instr, i, '\n');
1110                         --i;
1111                         --lines;
1112                 }
1113         }
1114
1115         return(incomplete);
1116 }
1117
1118
1119 /*
1120  * smtp_do_procmsg()
1121  *
1122  * Called by smtp_do_queue() to handle an individual message.
1123  */
1124 void smtp_do_procmsg(long msgnum, void *userdata) {
1125         struct CtdlMessage *msg;
1126         char *instr = NULL;
1127         char *results = NULL;
1128         int i;
1129         int lines;
1130         int status;
1131         char buf[1024];
1132         char key[1024];
1133         char addr[1024];
1134         char dsn[1024];
1135         long text_msgid = (-1);
1136         int incomplete_deliveries_remaining;
1137         time_t attempted = 0L;
1138         time_t last_attempted = 0L;
1139         time_t retry = SMTP_RETRY_INTERVAL;
1140
1141         lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1142
1143         msg = CtdlFetchMessage(msgnum);
1144         if (msg == NULL) {
1145                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1146                 return;
1147         }
1148
1149         instr = strdoop(msg->cm_fields['M']);
1150         CtdlFreeMessage(msg);
1151
1152         /* Strip out the headers amd any other non-instruction line */
1153         lines = num_tokens(instr, '\n');
1154         for (i=0; i<lines; ++i) {
1155                 extract_token(buf, instr, i, '\n');
1156                 if (num_tokens(buf, '|') < 2) {
1157                         remove_token(instr, i, '\n');
1158                         --lines;
1159                         --i;
1160                 }
1161         }
1162
1163         /* Learn the message ID and find out about recent delivery attempts */
1164         lines = num_tokens(instr, '\n');
1165         for (i=0; i<lines; ++i) {
1166                 extract_token(buf, instr, i, '\n');
1167                 extract(key, buf, 0);
1168                 if (!strcasecmp(key, "msgid")) {
1169                         text_msgid = extract_long(buf, 1);
1170                 }
1171                 if (!strcasecmp(key, "retry")) {
1172                         /* double the retry interval after each attempt */
1173                         retry = extract_long(buf, 1) * 2L;
1174                         if (retry > SMTP_RETRY_MAX) {
1175                                 retry = SMTP_RETRY_MAX;
1176                         }
1177                         remove_token(instr, i, '\n');
1178                 }
1179                 if (!strcasecmp(key, "attempted")) {
1180                         attempted = extract_long(buf, 1);
1181                         if (attempted > last_attempted)
1182                                 last_attempted = attempted;
1183                 }
1184         }
1185
1186         /*
1187          * Postpone delivery if we've already tried recently.
1188          */
1189         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1190                 lprintf(7, "Retry time not yet reached.\n");
1191                 phree(instr);
1192                 return;
1193         }
1194
1195
1196         /*
1197          * Bail out if there's no actual message associated with this
1198          */
1199         if (text_msgid < 0L) {
1200                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1201                 phree(instr);
1202                 return;
1203         }
1204
1205         /* Plow through the instructions looking for 'remote' directives and
1206          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1207          * were experienced and it's time to try again)
1208          */
1209         lines = num_tokens(instr, '\n');
1210         for (i=0; i<lines; ++i) {
1211                 extract_token(buf, instr, i, '\n');
1212                 extract(key, buf, 0);
1213                 extract(addr, buf, 1);
1214                 status = extract_int(buf, 2);
1215                 extract(dsn, buf, 3);
1216                 if ( (!strcasecmp(key, "remote"))
1217                    && ((status==0)||(status==3)||(status==4)) ) {
1218                         remove_token(instr, i, '\n');
1219                         --i;
1220                         --lines;
1221                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1222                         smtp_try(key, addr, &status, dsn, text_msgid);
1223                         if (status != 2) {
1224                                 if (results == NULL) {
1225                                         results = mallok(1024);
1226                                         memset(results, 0, 1024);
1227                                 }
1228                                 else {
1229                                         results = reallok(results,
1230                                                 strlen(results) + 1024);
1231                                 }
1232                                 sprintf(&results[strlen(results)],
1233                                         "%s|%s|%d|%s\n",
1234                                         key, addr, status, dsn);
1235                         }
1236                 }
1237         }
1238
1239         if (results != NULL) {
1240                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1241                 strcat(instr, results);
1242                 phree(results);
1243         }
1244
1245
1246         /* Generate 'bounce' messages */
1247         smtp_do_bounce(instr);
1248
1249         /* Go through the delivery list, deleting completed deliveries */
1250         incomplete_deliveries_remaining = 
1251                 smtp_purge_completed_deliveries(instr);
1252
1253
1254         /*
1255          * No delivery instructions remain, so delete both the instructions
1256          * message and the message message.
1257          */
1258         if (incomplete_deliveries_remaining <= 0) {
1259                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1260                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1261         }
1262
1263
1264         /*
1265          * Uncompleted delivery instructions remain, so delete the old
1266          * instructions and replace with the updated ones.
1267          */
1268         if (incomplete_deliveries_remaining > 0) {
1269                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1270                 msg = mallok(sizeof(struct CtdlMessage));
1271                 memset(msg, 0, sizeof(struct CtdlMessage));
1272                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1273                 msg->cm_anon_type = MES_NORMAL;
1274                 msg->cm_format_type = FMT_RFC822;
1275                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1276                 snprintf(msg->cm_fields['M'],
1277                         strlen(instr)+SIZ,
1278                         "Content-type: %s\n\n%s\n"
1279                         "attempted|%ld\n"
1280                         "retry|%ld\n",
1281                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1282                 phree(instr);
1283                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1284                 CtdlFreeMessage(msg);
1285         }
1286
1287 }
1288
1289
1290
1291 /*
1292  * smtp_do_queue()
1293  * 
1294  * Run through the queue sending out messages.
1295  */
1296 void smtp_do_queue(void) {
1297         static int doing_queue = 0;
1298
1299         /*
1300          * This is a simple concurrency check to make sure only one queue run
1301          * is done at a time.  We could do this with a mutex, but since we
1302          * don't really require extremely fine granularity here, we'll do it
1303          * with a static variable instead.
1304          */
1305         if (doing_queue) return;
1306         doing_queue = 1;
1307
1308         /* 
1309          * Go ahead and run the queue
1310          */
1311         lprintf(7, "SMTP: processing outbound queue\n");
1312
1313         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1314                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1315                 return;
1316         }
1317         CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1318                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1319
1320         lprintf(7, "SMTP: queue run completed\n");
1321         run_queue_now = 0;
1322         doing_queue = 0;
1323 }
1324
1325
1326
1327 /*****************************************************************************/
1328 /*                          SMTP UTILITY COMMANDS                            */
1329 /*****************************************************************************/
1330
1331 void cmd_smtp(char *argbuf) {
1332         char cmd[SIZ];
1333         char node[SIZ];
1334         char buf[SIZ];
1335         int i;
1336         int num_mxhosts;
1337
1338         if (CtdlAccessCheck(ac_aide)) return;
1339
1340         extract(cmd, argbuf, 0);
1341
1342         if (!strcasecmp(cmd, "mx")) {
1343                 extract(node, argbuf, 1);
1344                 num_mxhosts = getmx(buf, node);
1345                 cprintf("%d %d MX hosts listed for %s\n",
1346                         LISTING_FOLLOWS, num_mxhosts, node);
1347                 for (i=0; i<num_mxhosts; ++i) {
1348                         extract(node, buf, i);
1349                         cprintf("%s\n", node);
1350                 }
1351                 cprintf("000\n");
1352                 return;
1353         }
1354
1355         else if (!strcasecmp(cmd, "runqueue")) {
1356                 run_queue_now = 1;
1357                 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1358                 return;
1359         }
1360
1361         else {
1362                 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1363         }
1364
1365 }
1366
1367
1368
1369
1370 /*****************************************************************************/
1371 /*                      MODULE INITIALIZATION STUFF                          */
1372 /*****************************************************************************/
1373
1374
1375 char *Dynamic_Module_Init(void)
1376 {
1377         SYM_SMTP = CtdlGetDynamicSymbol();
1378
1379         CtdlRegisterServiceHook(config.c_smtp_port,     /* On the net... */
1380                                 NULL,
1381                                 smtp_greeting,
1382                                 smtp_command_loop);
1383
1384         CtdlRegisterServiceHook(0,                      /* ...and locally */
1385                                 "smtp.socket",
1386                                 smtp_greeting,
1387                                 smtp_command_loop);
1388
1389         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1390         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1391         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1392         return "$Id$";
1393 }