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