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