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