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