]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* Finished the updates to serv_smtp.c, although I think there may be a
[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
727         /* Figure out what mail exchanger host we have to connect to */
728         num_mxhosts = getmx(mxhosts, node);
729         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
730         if (num_mxhosts < 1) {
731                 *status = 5;
732                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
733                 return;
734         }
735
736         for (mx=0; mx<num_mxhosts; ++mx) {
737                 extract(buf, mxhosts, mx);
738                 lprintf(9, "Trying <%s>\n", buf);
739                 sock = sock_connect(buf, "25", "tcp");
740                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
741                 if (sock >= 0) lprintf(9, "Connected!\n");
742                 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
743                 if (sock >= 0) break;
744         }
745
746         if (sock < 0) {
747                 *status = 4;    /* dsn is already filled in */
748                 return;
749         }
750
751         /* Process the SMTP greeting from the server */
752         if (ml_sock_gets(sock, buf) < 0) {
753                 *status = 4;
754                 strcpy(dsn, "Connection broken during SMTP conversation");
755                 goto bail;
756         }
757         lprintf(9, "<%s\n", buf);
758         if (buf[0] != '2') {
759                 if (buf[0] == '4') {
760                         *status = 4;
761                         safestrncpy(dsn, &buf[4], 1023);
762                         goto bail;
763                 }
764                 else {
765                         *status = 5;
766                         safestrncpy(dsn, &buf[4], 1023);
767                         goto bail;
768                 }
769         }
770
771         /* At this point we know we are talking to a real SMTP server */
772
773         /* Do a HELO command */
774         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
775         lprintf(9, ">%s", buf);
776         sock_write(sock, buf, strlen(buf));
777         if (ml_sock_gets(sock, buf) < 0) {
778                 *status = 4;
779                 strcpy(dsn, "Connection broken during SMTP HELO");
780                 goto bail;
781         }
782         lprintf(9, "<%s\n", buf);
783         if (buf[0] != '2') {
784                 if (buf[0] == '4') {
785                         *status = 4;
786                         safestrncpy(dsn, &buf[4], 1023);
787                         goto bail;
788                 }
789                 else {
790                         *status = 5;
791                         safestrncpy(dsn, &buf[4], 1023);
792                         goto bail;
793                 }
794         }
795
796
797         /* HELO succeeded, now try the MAIL From: command */
798         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
799         lprintf(9, ">%s", buf);
800         sock_write(sock, buf, strlen(buf));
801         if (ml_sock_gets(sock, buf) < 0) {
802                 *status = 4;
803                 strcpy(dsn, "Connection broken during SMTP MAIL");
804                 goto bail;
805         }
806         lprintf(9, "<%s\n", buf);
807         if (buf[0] != '2') {
808                 if (buf[0] == '4') {
809                         *status = 4;
810                         safestrncpy(dsn, &buf[4], 1023);
811                         goto bail;
812                 }
813                 else {
814                         *status = 5;
815                         safestrncpy(dsn, &buf[4], 1023);
816                         goto bail;
817                 }
818         }
819
820
821         /* MAIL succeeded, now try the RCPT To: command */
822         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
823         lprintf(9, ">%s", buf);
824         sock_write(sock, buf, strlen(buf));
825         if (ml_sock_gets(sock, buf) < 0) {
826                 *status = 4;
827                 strcpy(dsn, "Connection broken during SMTP RCPT");
828                 goto bail;
829         }
830         lprintf(9, "<%s\n", buf);
831         if (buf[0] != '2') {
832                 if (buf[0] == '4') {
833                         *status = 4;
834                         safestrncpy(dsn, &buf[4], 1023);
835                         goto bail;
836                 }
837                 else {
838                         *status = 5;
839                         safestrncpy(dsn, &buf[4], 1023);
840                         goto bail;
841                 }
842         }
843
844
845         /* RCPT succeeded, now try the DATA command */
846         lprintf(9, ">DATA\n");
847         sock_write(sock, "DATA\r\n", 6);
848         if (ml_sock_gets(sock, buf) < 0) {
849                 *status = 4;
850                 strcpy(dsn, "Connection broken during SMTP DATA");
851                 goto bail;
852         }
853         lprintf(9, "<%s\n", buf);
854         if (buf[0] != '3') {
855                 if (buf[0] == '4') {
856                         *status = 3;
857                         safestrncpy(dsn, &buf[4], 1023);
858                         goto bail;
859                 }
860                 else {
861                         *status = 5;
862                         safestrncpy(dsn, &buf[4], 1023);
863                         goto bail;
864                 }
865         }
866
867         /* If we reach this point, the server is expecting data */
868         rewind(msg_fp);
869         while (msg_size > 0) {
870                 blocksize = sizeof(buf);
871                 if (blocksize > msg_size) blocksize = msg_size;
872                 fread(buf, blocksize, 1, msg_fp);
873                 sock_write(sock, buf, blocksize);
874                 msg_size -= blocksize;
875         }
876         if (buf[blocksize-1] != 10) {
877                 lprintf(5, "Possible problem: message did not correctly "
878                         "terminate. (expecting 0x10, got 0x%02x)\n",
879                                 buf[blocksize-1]);
880         }
881
882         sock_write(sock, ".\r\n", 3);
883         if (ml_sock_gets(sock, buf) < 0) {
884                 *status = 4;
885                 strcpy(dsn, "Connection broken during SMTP message transmit");
886                 goto bail;
887         }
888         lprintf(9, "%s\n", buf);
889         if (buf[0] != '2') {
890                 if (buf[0] == '4') {
891                         *status = 4;
892                         safestrncpy(dsn, &buf[4], 1023);
893                         goto bail;
894                 }
895                 else {
896                         *status = 5;
897                         safestrncpy(dsn, &buf[4], 1023);
898                         goto bail;
899                 }
900         }
901
902         /* We did it! */
903         safestrncpy(dsn, &buf[4], 1023);
904         *status = 2;
905
906         lprintf(9, ">QUIT\n");
907         sock_write(sock, "QUIT\r\n", 6);
908         ml_sock_gets(sock, buf);
909         lprintf(9, "<%s\n", buf);
910
911 bail:   if (msg_fp != NULL) fclose(msg_fp);
912         sock_close(sock);
913         return;
914 }
915
916
917
918 /*
919  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
920  * instructions for "5" codes (permanent fatal errors) and produce/deliver
921  * a "bounce" message (delivery status notification).
922  */
923 void smtp_do_bounce(char *instr) {
924         int i;
925         int lines;
926         int status;
927         char buf[1024];
928         char key[1024];
929         char addr[1024];
930         char dsn[1024];
931         char bounceto[1024];
932         int num_bounces = 0;
933         int bounce_this = 0;
934         long bounce_msgid = (-1);
935         time_t submitted = 0L;
936         struct CtdlMessage *bmsg = NULL;
937         int give_up = 0;
938         struct recptypes *valid;
939         int successful_bounce = 0;
940
941         lprintf(9, "smtp_do_bounce() called\n");
942         strcpy(bounceto, "");
943
944         lines = num_tokens(instr, '\n');
945
946
947         /* See if it's time to give up on delivery of this message */
948         for (i=0; i<lines; ++i) {
949                 extract_token(buf, instr, i, '\n');
950                 extract(key, buf, 0);
951                 extract(addr, buf, 1);
952                 if (!strcasecmp(key, "submitted")) {
953                         submitted = atol(addr);
954                 }
955         }
956
957         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
958                 give_up = 1;
959         }
960
961
962
963         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
964         if (bmsg == NULL) return;
965         memset(bmsg, 0, sizeof(struct CtdlMessage));
966
967         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
968         bmsg->cm_anon_type = MES_NORMAL;
969         bmsg->cm_format_type = 1;
970         bmsg->cm_fields['A'] = strdoop("Citadel");
971         bmsg->cm_fields['O'] = strdoop(MAILROOM);
972         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
973
974         if (give_up) bmsg->cm_fields['M'] = strdoop(
975 "A message you sent could not be delivered to some or all of its recipients\n"
976 "due to prolonged unavailability of its destination(s).\n"
977 "Giving up on the following addresses:\n\n"
978 );
979
980         else bmsg->cm_fields['M'] = strdoop(
981 "A message you sent could not be delivered to some or all of its recipients.\n"
982 "The following addresses were undeliverable:\n\n"
983 );
984
985         /*
986          * Now go through the instructions checking for stuff.
987          */
988
989         for (i=0; i<lines; ++i) {
990                 extract_token(buf, instr, i, '\n');
991                 extract(key, buf, 0);
992                 extract(addr, buf, 1);
993                 status = extract_int(buf, 2);
994                 extract(dsn, buf, 3);
995                 bounce_this = 0;
996
997                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
998                         key, addr, status, dsn);
999
1000                 if (!strcasecmp(key, "bounceto")) {
1001                         strcpy(bounceto, addr);
1002                 }
1003
1004                 if (
1005                    (!strcasecmp(key, "local"))
1006                    || (!strcasecmp(key, "remote"))
1007                    || (!strcasecmp(key, "ignet"))
1008                    || (!strcasecmp(key, "room"))
1009                 ) {
1010                         if (status == 5) bounce_this = 1;
1011                         if (give_up) bounce_this = 1;
1012                 }
1013
1014                 if (bounce_this) {
1015                         ++num_bounces;
1016
1017                         if (bmsg->cm_fields['M'] == NULL) {
1018                                 lprintf(2, "ERROR ... M field is null "
1019                                         "(%s:%d)\n", __FILE__, __LINE__);
1020                         }
1021
1022                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1023                                 strlen(bmsg->cm_fields['M']) + 1024 );
1024                         strcat(bmsg->cm_fields['M'], addr);
1025                         strcat(bmsg->cm_fields['M'], ": ");
1026                         strcat(bmsg->cm_fields['M'], dsn);
1027                         strcat(bmsg->cm_fields['M'], "\n");
1028
1029                         remove_token(instr, i, '\n');
1030                         --i;
1031                         --lines;
1032                 }
1033         }
1034
1035         /* Deliver the bounce if there's anything worth mentioning */
1036         lprintf(9, "num_bounces = %d\n", num_bounces);
1037         if (num_bounces > 0) {
1038
1039                 /* First try the user who sent the message */
1040                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1041                 TRACE;
1042                 if (strlen(bounceto) == 0) {
1043                         lprintf(7, "No bounce address specified\n");
1044                         bounce_msgid = (-1L);
1045                 }
1046
1047                 /* Can we deliver the bounce to the original sender? */
1048                 valid = validate_recipients(bounceto);
1049                 if (valid != NULL) {
1050                         if (valid->num_error == 0) {
1051                                 CtdlSubmitMsg(bmsg, valid, "");
1052                                 successful_bounce = 1;
1053                         }
1054                 }
1055
1056                 /* If not, post it in the Aide> room */
1057                 if (successful_bounce == 0) {
1058                         CtdlSubmitMsg(bmsg, NULL, AIDEROOM);
1059                 }
1060         }
1061
1062         CtdlFreeMessage(bmsg);
1063         lprintf(9, "Done processing bounces\n");
1064 }
1065
1066
1067 /*
1068  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1069  * set of delivery instructions for completed deliveries and remove them.
1070  *
1071  * It returns the number of incomplete deliveries remaining.
1072  */
1073 int smtp_purge_completed_deliveries(char *instr) {
1074         int i;
1075         int lines;
1076         int status;
1077         char buf[1024];
1078         char key[1024];
1079         char addr[1024];
1080         char dsn[1024];
1081         int completed;
1082         int incomplete = 0;
1083
1084         lines = num_tokens(instr, '\n');
1085         for (i=0; i<lines; ++i) {
1086                 extract_token(buf, instr, i, '\n');
1087                 extract(key, buf, 0);
1088                 extract(addr, buf, 1);
1089                 status = extract_int(buf, 2);
1090                 extract(dsn, buf, 3);
1091
1092                 completed = 0;
1093
1094                 if (
1095                    (!strcasecmp(key, "local"))
1096                    || (!strcasecmp(key, "remote"))
1097                    || (!strcasecmp(key, "ignet"))
1098                    || (!strcasecmp(key, "room"))
1099                 ) {
1100                         if (status == 2) completed = 1;
1101                         else ++incomplete;
1102                 }
1103
1104                 if (completed) {
1105                         remove_token(instr, i, '\n');
1106                         --i;
1107                         --lines;
1108                 }
1109         }
1110
1111         return(incomplete);
1112 }
1113
1114
1115 /*
1116  * smtp_do_procmsg()
1117  *
1118  * Called by smtp_do_queue() to handle an individual message.
1119  */
1120 void smtp_do_procmsg(long msgnum, void *userdata) {
1121         struct CtdlMessage *msg;
1122         char *instr = NULL;
1123         char *results = NULL;
1124         int i;
1125         int lines;
1126         int status;
1127         char buf[1024];
1128         char key[1024];
1129         char addr[1024];
1130         char dsn[1024];
1131         long text_msgid = (-1);
1132         int incomplete_deliveries_remaining;
1133         time_t attempted = 0L;
1134         time_t last_attempted = 0L;
1135         time_t retry = SMTP_RETRY_INTERVAL;
1136
1137         lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1138
1139         msg = CtdlFetchMessage(msgnum);
1140         if (msg == NULL) {
1141                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1142                 return;
1143         }
1144
1145         instr = strdoop(msg->cm_fields['M']);
1146         CtdlFreeMessage(msg);
1147
1148         /* Strip out the headers amd any other non-instruction line */
1149         lines = num_tokens(instr, '\n');
1150         for (i=0; i<lines; ++i) {
1151                 extract_token(buf, instr, i, '\n');
1152                 if (num_tokens(buf, '|') < 2) {
1153                         remove_token(instr, i, '\n');
1154                         --lines;
1155                         --i;
1156                 }
1157         }
1158
1159         /* Learn the message ID and find out about recent delivery attempts */
1160         lines = num_tokens(instr, '\n');
1161         for (i=0; i<lines; ++i) {
1162                 extract_token(buf, instr, i, '\n');
1163                 extract(key, buf, 0);
1164                 if (!strcasecmp(key, "msgid")) {
1165                         text_msgid = extract_long(buf, 1);
1166                 }
1167                 if (!strcasecmp(key, "retry")) {
1168                         /* double the retry interval after each attempt */
1169                         retry = extract_long(buf, 1) * 2L;
1170                         if (retry > SMTP_RETRY_MAX) {
1171                                 retry = SMTP_RETRY_MAX;
1172                         }
1173                         remove_token(instr, i, '\n');
1174                 }
1175                 if (!strcasecmp(key, "attempted")) {
1176                         attempted = extract_long(buf, 1);
1177                         if (attempted > last_attempted)
1178                                 last_attempted = attempted;
1179                 }
1180         }
1181
1182         /*
1183          * Postpone delivery if we've already tried recently.
1184          */
1185         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1186                 lprintf(7, "Retry time not yet reached.\n");
1187                 phree(instr);
1188                 return;
1189         }
1190
1191
1192         /*
1193          * Bail out if there's no actual message associated with this
1194          */
1195         if (text_msgid < 0L) {
1196                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1197                 phree(instr);
1198                 return;
1199         }
1200
1201         /* Plow through the instructions looking for 'remote' directives and
1202          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1203          * were experienced and it's time to try again)
1204          */
1205         lines = num_tokens(instr, '\n');
1206         for (i=0; i<lines; ++i) {
1207                 extract_token(buf, instr, i, '\n');
1208                 extract(key, buf, 0);
1209                 extract(addr, buf, 1);
1210                 status = extract_int(buf, 2);
1211                 extract(dsn, buf, 3);
1212                 if ( (!strcasecmp(key, "remote"))
1213                    && ((status==0)||(status==3)||(status==4)) ) {
1214                         remove_token(instr, i, '\n');
1215                         --i;
1216                         --lines;
1217                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1218                         smtp_try(key, addr, &status, dsn, text_msgid);
1219                         if (status != 2) {
1220                                 if (results == NULL) {
1221                                         results = mallok(1024);
1222                                         memset(results, 0, 1024);
1223                                 }
1224                                 else {
1225                                         results = reallok(results,
1226                                                 strlen(results) + 1024);
1227                                 }
1228                                 sprintf(&results[strlen(results)],
1229                                         "%s|%s|%d|%s\n",
1230                                         key, addr, status, dsn);
1231                         }
1232                 }
1233         }
1234
1235         if (results != NULL) {
1236                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1237                 strcat(instr, results);
1238                 phree(results);
1239         }
1240
1241
1242         /* Generate 'bounce' messages */
1243         smtp_do_bounce(instr);
1244
1245         /* Go through the delivery list, deleting completed deliveries */
1246         incomplete_deliveries_remaining = 
1247                 smtp_purge_completed_deliveries(instr);
1248
1249
1250         /*
1251          * No delivery instructions remain, so delete both the instructions
1252          * message and the message message.
1253          */
1254         if (incomplete_deliveries_remaining <= 0) {
1255                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1256                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1257         }
1258
1259
1260         /*
1261          * Uncompleted delivery instructions remain, so delete the old
1262          * instructions and replace with the updated ones.
1263          */
1264         if (incomplete_deliveries_remaining > 0) {
1265                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1266                 msg = mallok(sizeof(struct CtdlMessage));
1267                 memset(msg, 0, sizeof(struct CtdlMessage));
1268                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1269                 msg->cm_anon_type = MES_NORMAL;
1270                 msg->cm_format_type = FMT_RFC822;
1271                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1272                 snprintf(msg->cm_fields['M'],
1273                         strlen(instr)+SIZ,
1274                         "Content-type: %s\n\n%s\n"
1275                         "attempted|%ld\n"
1276                         "retry|%ld\n",
1277                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1278                 phree(instr);
1279                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1280                 CtdlFreeMessage(msg);
1281         }
1282
1283 }
1284
1285
1286
1287 /*
1288  * smtp_do_queue()
1289  * 
1290  * Run through the queue sending out messages.
1291  */
1292 void smtp_do_queue(void) {
1293         static int doing_queue = 0;
1294
1295         /*
1296          * This is a simple concurrency check to make sure only one queue run
1297          * is done at a time.  We could do this with a mutex, but since we
1298          * don't really require extremely fine granularity here, we'll do it
1299          * with a static variable instead.
1300          */
1301         if (doing_queue) return;
1302         doing_queue = 1;
1303
1304         /* 
1305          * Go ahead and run the queue
1306          */
1307         lprintf(7, "SMTP: processing outbound queue\n");
1308
1309         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1310                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1311                 return;
1312         }
1313         CtdlForEachMessage(MSGS_ALL, 0L, (-127),
1314                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1315
1316         lprintf(7, "SMTP: queue run completed\n");
1317         run_queue_now = 0;
1318         doing_queue = 0;
1319 }
1320
1321
1322
1323 /*****************************************************************************/
1324 /*                          SMTP UTILITY COMMANDS                            */
1325 /*****************************************************************************/
1326
1327 void cmd_smtp(char *argbuf) {
1328         char cmd[SIZ];
1329         char node[SIZ];
1330         char buf[SIZ];
1331         int i;
1332         int num_mxhosts;
1333
1334         if (CtdlAccessCheck(ac_aide)) return;
1335
1336         extract(cmd, argbuf, 0);
1337
1338         if (!strcasecmp(cmd, "mx")) {
1339                 extract(node, argbuf, 1);
1340                 num_mxhosts = getmx(buf, node);
1341                 cprintf("%d %d MX hosts listed for %s\n",
1342                         LISTING_FOLLOWS, num_mxhosts, node);
1343                 for (i=0; i<num_mxhosts; ++i) {
1344                         extract(node, buf, i);
1345                         cprintf("%s\n", node);
1346                 }
1347                 cprintf("000\n");
1348                 return;
1349         }
1350
1351         else if (!strcasecmp(cmd, "runqueue")) {
1352                 run_queue_now = 1;
1353                 cprintf("%d All outbound SMTP will be retried now.\n", OK);
1354                 return;
1355         }
1356
1357         else {
1358                 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1359         }
1360
1361 }
1362
1363
1364
1365
1366 /*****************************************************************************/
1367 /*                      MODULE INITIALIZATION STUFF                          */
1368 /*****************************************************************************/
1369
1370
1371 char *Dynamic_Module_Init(void)
1372 {
1373         SYM_SMTP = CtdlGetDynamicSymbol();
1374
1375         CtdlRegisterServiceHook(config.c_smtp_port,     /* On the net... */
1376                                 NULL,
1377                                 smtp_greeting,
1378                                 smtp_command_loop);
1379
1380         CtdlRegisterServiceHook(0,                      /* ...and locally */
1381                                 "smtp.socket",
1382                                 smtp_greeting,
1383                                 smtp_command_loop);
1384
1385         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1);
1386         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1387         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1388         return "$Id$";
1389 }