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