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