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