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