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