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