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