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