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