* Implemented holdoff time (15 minutes) for SMTP send retry.
[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 "dynloader.h"
25 #include "room_ops.h"
26 #include "user_ops.h"
27 #include "policy.h"
28 #include "database.h"
29 #include "msgbase.h"
30 #include "tools.h"
31 #include "internet_addressing.h"
32 #include "genstamp.h"
33 #include "domain.h"
34 #include "clientsocket.h"
35
36
37 struct citsmtp {                /* Information about the current session */
38         int command_state;
39         char helo_node[256];
40         struct usersupp vrfy_buffer;
41         int vrfy_count;
42         char vrfy_match[256];
43         char from[256];
44         int number_of_recipients;
45         int delivery_mode;
46 };
47
48 enum {                          /* Command states for login authentication */
49         smtp_command,
50         smtp_user,
51         smtp_password
52 };
53
54 enum {                          /* Delivery modes */
55         smtp_deliver_local,
56         smtp_deliver_remote
57 };
58
59 #define SMTP            ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
60 #define SMTP_RECP       ((char *)CtdlGetUserData(SYM_SMTP_RECP))
61
62 long SYM_SMTP;
63 long SYM_SMTP_RECP;
64
65
66
67 /*****************************************************************************/
68 /*                      SMTP SERVER (INBOUND) STUFF                          */
69 /*****************************************************************************/
70
71
72
73
74 /*
75  * Here's where our SMTP session begins its happy day.
76  */
77 void smtp_greeting(void) {
78
79         strcpy(CC->cs_clientname, "SMTP session");
80         CC->internal_pgm = 1;
81         CC->cs_flags |= CS_STEALTH;
82         CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
83         CtdlAllocUserData(SYM_SMTP_RECP, 256);
84         sprintf(SMTP_RECP, "%s", "");
85
86         cprintf("220 Welcome to the Citadel/UX ESMTP server at %s\r\n",
87                 config.c_fqdn);
88 }
89
90
91 /*
92  * Implement HELO and EHLO commands.
93  */
94 void smtp_hello(char *argbuf, int is_esmtp) {
95
96         safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
97
98         if (!is_esmtp) {
99                 cprintf("250 Greetings and joyous salutations.\r\n");
100         }
101         else {
102                 cprintf("250-Greetings and joyous salutations.\r\n");
103                 cprintf("250-HELP\r\n");
104                 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
105                 cprintf("250 AUTH=LOGIN\r\n");
106         }
107 }
108
109
110 /*
111  * Implement HELP command.
112  */
113 void smtp_help(void) {
114         cprintf("214-Here's the frequency, Kenneth:\r\n");
115         cprintf("214-    DATA\r\n");
116         cprintf("214-    EHLO\r\n");
117         cprintf("214-    EXPN\r\n");
118         cprintf("214-    HELO\r\n");
119         cprintf("214-    HELP\r\n");
120         cprintf("214-    MAIL\r\n");
121         cprintf("214-    NOOP\r\n");
122         cprintf("214-    QUIT\r\n");
123         cprintf("214-    RCPT\r\n");
124         cprintf("214-    RSET\r\n");
125         cprintf("214-    VRFY\r\n");
126         cprintf("214 I could tell you more, but then I'd have to kill you.\r\n");
127 }
128
129
130 /*
131  *
132  */
133 void smtp_get_user(char *argbuf) {
134         char buf[256];
135         char username[256];
136
137         decode_base64(username, argbuf);
138         lprintf(9, "Trying <%s>\n", username);
139         if (CtdlLoginExistingUser(username) == login_ok) {
140                 encode_base64(buf, "Password:");
141                 cprintf("334 %s\r\n", buf);
142                 SMTP->command_state = smtp_password;
143         }
144         else {
145                 cprintf("500 No such user.\r\n");
146                 SMTP->command_state = smtp_command;
147         }
148 }
149
150
151 /*
152  *
153  */
154 void smtp_get_pass(char *argbuf) {
155         char password[256];
156
157         decode_base64(password, argbuf);
158         lprintf(9, "Trying <%s>\n", password);
159         if (CtdlTryPassword(password) == pass_ok) {
160                 cprintf("235 Authentication successful.\r\n");
161                 lprintf(9, "SMTP authenticated login successful\n");
162                 CC->internal_pgm = 0;
163                 CC->cs_flags &= ~CS_STEALTH;
164         }
165         else {
166                 cprintf("500 Authentication failed.\r\n");
167         }
168         SMTP->command_state = smtp_command;
169 }
170
171
172 /*
173  *
174  */
175 void smtp_auth(char *argbuf) {
176         char buf[256];
177
178         if (strncasecmp(argbuf, "login", 5) ) {
179                 cprintf("550 We only support LOGIN authentication.\r\n");
180                 return;
181         }
182
183         if (strlen(argbuf) >= 7) {
184                 smtp_get_user(&argbuf[6]);
185         }
186
187         else {
188                 encode_base64(buf, "Username:");
189                 cprintf("334 %s\r\n", buf);
190                 SMTP->command_state = smtp_user;
191         }
192 }
193
194
195 /*
196  * Back end for smtp_vrfy() command
197  */
198 void smtp_vrfy_backend(struct usersupp *us, void *data) {
199
200         if (!fuzzy_match(us, SMTP->vrfy_match)) {
201                 ++SMTP->vrfy_count;
202                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
203         }
204 }
205
206
207 /* 
208  * Implements the VRFY (verify user name) command.
209  * Performs fuzzy match on full user names.
210  */
211 void smtp_vrfy(char *argbuf) {
212         SMTP->vrfy_count = 0;
213         strcpy(SMTP->vrfy_match, argbuf);
214         ForEachUser(smtp_vrfy_backend, NULL);
215
216         if (SMTP->vrfy_count < 1) {
217                 cprintf("550 String does not match anything.\r\n");
218         }
219         else if (SMTP->vrfy_count == 1) {
220                 cprintf("250 %s <cit%ld@%s>\r\n",
221                         SMTP->vrfy_buffer.fullname,
222                         SMTP->vrfy_buffer.usernum,
223                         config.c_fqdn);
224         }
225         else if (SMTP->vrfy_count > 1) {
226                 cprintf("553 Request ambiguous: %d users matched.\r\n",
227                         SMTP->vrfy_count);
228         }
229
230 }
231
232
233
234 /*
235  * Back end for smtp_expn() command
236  */
237 void smtp_expn_backend(struct usersupp *us, void *data) {
238
239         if (!fuzzy_match(us, SMTP->vrfy_match)) {
240
241                 if (SMTP->vrfy_count >= 1) {
242                         cprintf("250-%s <cit%ld@%s>\r\n",
243                                 SMTP->vrfy_buffer.fullname,
244                                 SMTP->vrfy_buffer.usernum,
245                                 config.c_fqdn);
246                 }
247
248                 ++SMTP->vrfy_count;
249                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
250         }
251 }
252
253
254 /* 
255  * Implements the EXPN (expand user name) command.
256  * Performs fuzzy match on full user names.
257  */
258 void smtp_expn(char *argbuf) {
259         SMTP->vrfy_count = 0;
260         strcpy(SMTP->vrfy_match, argbuf);
261         ForEachUser(smtp_expn_backend, NULL);
262
263         if (SMTP->vrfy_count < 1) {
264                 cprintf("550 String does not match anything.\r\n");
265         }
266         else if (SMTP->vrfy_count >= 1) {
267                 cprintf("250 %s <cit%ld@%s>\r\n",
268                         SMTP->vrfy_buffer.fullname,
269                         SMTP->vrfy_buffer.usernum,
270                         config.c_fqdn);
271         }
272 }
273
274
275 /*
276  * Implements the RSET (reset state) command.
277  * Currently this just zeroes out the state buffer.  If pointers to data
278  * allocated with mallok() are ever placed in the state buffer, we have to
279  * be sure to phree() them first!
280  */
281 void smtp_rset(void) {
282         memset(SMTP, 0, sizeof(struct citsmtp));
283         if (CC->logged_in) logout(CC);
284         cprintf("250 Zap!\r\n");
285 }
286
287
288
289 /*
290  * Implements the "MAIL From:" command
291  */
292 void smtp_mail(char *argbuf) {
293         char user[256];
294         char node[256];
295         int cvt;
296
297         if (strlen(SMTP->from) != 0) {
298                 cprintf("503 Only one sender permitted\r\n");
299                 return;
300         }
301
302         if (strncasecmp(argbuf, "From:", 5)) {
303                 cprintf("501 Syntax error\r\n");
304                 return;
305         }
306
307         strcpy(SMTP->from, &argbuf[5]);
308         striplt(SMTP->from);
309
310         if (strlen(SMTP->from) == 0) {
311                 cprintf("501 Empty sender name is not permitted\r\n");
312                 return;
313         }
314
315
316         /* If this SMTP connection is from a logged-in user, make sure that
317          * the user only sends email from his/her own address.
318          */
319         if (CC->logged_in) {
320                 cvt = convert_internet_address(user, node, SMTP->from);
321                 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
322                 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
323                         cprintf("550 <%s> is not your address.\r\n", SMTP->from);
324                         strcpy(SMTP->from, "");
325                         return;
326                 }
327         }
328
329         /* Otherwise, make sure outsiders aren't trying to forge mail from
330          * this system.
331          */
332         else {
333                 cvt = convert_internet_address(user, node, SMTP->from);
334                 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
335                 if (CtdlHostAlias(node) == hostalias_localhost) {
336                         cprintf("550 You must log in to send mail from %s\r\n",
337                                 node);
338                         strcpy(SMTP->from, "");
339                         return;
340                 }
341         }
342
343         cprintf("250 Sender ok.  Groovy.\r\n");
344 }
345
346
347
348 /*
349  * Implements the "RCPT To:" command
350  */
351 void smtp_rcpt(char *argbuf) {
352         int cvt;
353         char user[256];
354         char node[256];
355         char recp[256];
356         int is_spam = 0;        /* FIXME implement anti-spamming */
357
358         if (strlen(SMTP->from) == 0) {
359                 cprintf("503 MAIL first, then RCPT.  Duh.\r\n");
360                 return;
361         }
362
363         if (strncasecmp(argbuf, "To:", 3)) {
364                 cprintf("501 Syntax error\r\n");
365                 return;
366         }
367
368         strcpy(recp, &argbuf[3]);
369         striplt(recp);
370         alias(recp);
371
372         cvt = convert_internet_address(user, node, recp);
373         sprintf(recp, "%s@%s", user, node);
374
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
983         lprintf(9, "smtp_do_bounce() called\n");
984         strcpy(bounceto, "");
985
986         lines = num_tokens(instr, '\n');
987
988
989         /* See if it's time to give up on delivery of this message */
990         for (i=0; i<lines; ++i) {
991                 extract_token(buf, instr, i, '\n');
992                 extract(key, buf, 0);
993                 extract(addr, buf, 1);
994                 if (!strcasecmp(key, "submitted")) {
995                         submitted = atol(addr);
996                 }
997         }
998
999         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1000                 give_up = 1;
1001         }
1002
1003
1004
1005         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1006         if (bmsg == NULL) return;
1007         memset(bmsg, 0, sizeof(struct CtdlMessage));
1008
1009         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1010         bmsg->cm_anon_type = MES_NORMAL;
1011         bmsg->cm_format_type = 1;
1012         bmsg->cm_fields['A'] = strdoop("Citadel");
1013         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1014
1015         if (give_up) bmsg->cm_fields['M'] = strdoop(
1016 "BOUNCE!  BOUNCE!!  BOUNCE!!!\n\n"
1017 "FIXME ... this message should be made to look nice and stuff.\n"
1018 "In the meantime, you should be aware that we're giving up on the\n"
1019 "following deliveries because their mail servers are fux0red and\n"
1020 "would not accept the message for a very, very long time:\n\n"
1021 );
1022
1023         else bmsg->cm_fields['M'] = strdoop(
1024 "BOUNCE!  BOUNCE!!  BOUNCE!!!\n\n"
1025 "FIXME ... this message should be made to look nice and stuff.\n"
1026 "In the meantime, you should be aware that the following\n"
1027 "recipient addresses had permanent fatal errors:\n\n"
1028 );
1029
1030
1031
1032         /*
1033          * Now go through the instructions checking for stuff.
1034          */
1035
1036         for (i=0; i<lines; ++i) {
1037                 extract_token(buf, instr, i, '\n');
1038                 extract(key, buf, 0);
1039                 extract(addr, buf, 1);
1040                 status = extract_int(buf, 2);
1041                 extract(dsn, buf, 3);
1042                 bounce_this = 0;
1043
1044                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1045                         key, addr, status, dsn);
1046
1047                 if (!strcasecmp(key, "bounceto")) {
1048                         strcpy(bounceto, addr);
1049                 }
1050
1051                 if (
1052                    (!strcasecmp(key, "local"))
1053                    || (!strcasecmp(key, "remote"))
1054                    || (!strcasecmp(key, "ignet"))
1055                    || (!strcasecmp(key, "room"))
1056                 ) {
1057                         if (status == 5) bounce_this = 1;
1058                         if (give_up) bounce_this = 1;
1059                 }
1060
1061                 if (bounce_this) {
1062                         ++num_bounces;
1063
1064                         if (bmsg->cm_fields['M'] == NULL) {
1065                                 lprintf(2, "ERROR ... M field is null "
1066                                         "(%s:%d)\n", __FILE__, __LINE__);
1067                         }
1068
1069                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1070                                 strlen(bmsg->cm_fields['M']) + 1024 );
1071                         strcat(bmsg->cm_fields['M'], addr);
1072                         strcat(bmsg->cm_fields['M'], ": ");
1073                         strcat(bmsg->cm_fields['M'], dsn);
1074                         strcat(bmsg->cm_fields['M'], "\n");
1075
1076                         remove_token(instr, i, '\n');
1077                         --i;
1078                         --lines;
1079                 }
1080         }
1081
1082         /* Deliver the bounce if there's anything worth mentioning */
1083         lprintf(9, "num_bounces = %d\n", num_bounces);
1084         if (num_bounces > 0) {
1085
1086                 /* First try the user who sent the message */
1087                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1088                 if (strlen(bounceto) == 0) bounce_msgid = (-1L);
1089                 else bounce_msgid = CtdlSaveMsg(bmsg,
1090                         bounceto,
1091                         "", MES_LOCAL, 1);
1092
1093                 /* Otherwise, go to the Aide> room */
1094                 lprintf(9, "bounce to room?\n");
1095                 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1096                         "", AIDEROOM,
1097                         MES_LOCAL, 1);
1098         }
1099
1100         CtdlFreeMessage(bmsg);
1101         lprintf(9, "Done processing bounces\n");
1102 }
1103
1104
1105 /*
1106  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1107  * set of delivery instructions for completed deliveries and remove them.
1108  *
1109  * It returns the number of incomplete deliveries remaining.
1110  */
1111 int smtp_purge_completed_deliveries(char *instr) {
1112         int i;
1113         int lines;
1114         int status;
1115         char buf[1024];
1116         char key[1024];
1117         char addr[1024];
1118         char dsn[1024];
1119         int completed;
1120         int incomplete = 0;
1121
1122         lines = num_tokens(instr, '\n');
1123         for (i=0; i<lines; ++i) {
1124                 extract_token(buf, instr, i, '\n');
1125                 extract(key, buf, 0);
1126                 extract(addr, buf, 1);
1127                 status = extract_int(buf, 2);
1128                 extract(dsn, buf, 3);
1129
1130                 completed = 0;
1131
1132                 if (
1133                    (!strcasecmp(key, "local"))
1134                    || (!strcasecmp(key, "remote"))
1135                    || (!strcasecmp(key, "ignet"))
1136                    || (!strcasecmp(key, "room"))
1137                 ) {
1138                         if (status == 2) completed = 1;
1139                         else ++incomplete;
1140                 }
1141
1142                 if (completed) {
1143                         remove_token(instr, i, '\n');
1144                         --i;
1145                         --lines;
1146                 }
1147         }
1148
1149         return(incomplete);
1150 }
1151
1152
1153 /*
1154  * smtp_do_procmsg()
1155  *
1156  * Called by smtp_do_queue() to handle an individual message.
1157  */
1158 void smtp_do_procmsg(long msgnum) {
1159         struct CtdlMessage *msg;
1160         char *instr = NULL;
1161         char *results = NULL;
1162         int i;
1163         int lines;
1164         int status;
1165         char buf[1024];
1166         char key[1024];
1167         char addr[1024];
1168         char dsn[1024];
1169         long text_msgid = (-1);
1170         int incomplete_deliveries_remaining;
1171         time_t attempted = 0L;
1172         time_t last_attempted = 0L;
1173
1174         msg = CtdlFetchMessage(msgnum);
1175         if (msg == NULL) {
1176                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1177                 return;
1178         }
1179
1180         instr = strdoop(msg->cm_fields['M']);
1181         CtdlFreeMessage(msg);
1182
1183         /* Strip out the headers amd any other non-instruction line */
1184         lines = num_tokens(instr, '\n');
1185         for (i=0; i<lines; ++i) {
1186                 extract_token(buf, instr, i, '\n');
1187                 if (num_tokens(buf, '|') < 2) {
1188                         lprintf(9, "removing <%s>\n", buf);
1189                         remove_token(instr, i, '\n');
1190                         --lines;
1191                         --i;
1192                 }
1193         }
1194
1195         /* Learn the message ID and find out about recent delivery attempts */
1196         lines = num_tokens(instr, '\n');
1197         for (i=0; i<lines; ++i) {
1198                 extract_token(buf, instr, i, '\n');
1199                 extract(key, buf, 0);
1200                 if (!strcasecmp(key, "msgid")) {
1201                         text_msgid = extract_long(buf, 1);
1202                 }
1203                 if (!strcasecmp(key, "attempted")) {
1204                         attempted = extract_long(buf, 1);
1205                         if (attempted > last_attempted)
1206                                 last_attempted = attempted;
1207                 }
1208         }
1209
1210
1211         /*
1212          * Postpone delivery if we've already tried recently.
1213          */
1214         if ( (time(NULL) - last_attempted) < SMTP_RETRY_INTERVAL) {
1215                 lprintf(7, "Retry time not yet reached.\n");
1216                 phree(instr);
1217                 return;
1218         }
1219
1220
1221         /*
1222          * Bail out if there's no actual message associated with this
1223          */
1224         if (text_msgid < 0L) {
1225                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1226                 phree(instr);
1227                 return;
1228         }
1229
1230         /* Plow through the instructions looking for 'remote' directives and
1231          * a status of 0 (no delivery yet attempted) or 3 (transient errors
1232          * were experienced and it's time to try again)
1233          */
1234         lines = num_tokens(instr, '\n');
1235         for (i=0; i<lines; ++i) {
1236                 extract_token(buf, instr, i, '\n');
1237                 extract(key, buf, 0);
1238                 extract(addr, buf, 1);
1239                 status = extract_int(buf, 2);
1240                 extract(dsn, buf, 3);
1241                 if ( (!strcasecmp(key, "remote"))
1242                    && ((status==0)||(status==3)) ) {
1243                         remove_token(instr, i, '\n');
1244                         --i;
1245                         --lines;
1246                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1247                         smtp_try(key, addr, &status, dsn, text_msgid);
1248                         if (status != 2) {
1249                                 if (results == NULL) {
1250                                         results = mallok(1024);
1251                                         memset(results, 0, 1024);
1252                                 }
1253                                 else {
1254                                         results = reallok(results,
1255                                                 strlen(results) + 1024);
1256                                 }
1257                                 sprintf(&results[strlen(results)],
1258                                         "%s|%s|%d|%s\n",
1259                                         key, addr, status, dsn);
1260                         }
1261                 }
1262         }
1263
1264         if (results != NULL) {
1265                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1266                 strcat(instr, results);
1267                 phree(results);
1268         }
1269
1270
1271         /* Generate 'bounce' messages */
1272         smtp_do_bounce(instr);
1273
1274         /* Go through the delivery list, deleting completed deliveries */
1275         incomplete_deliveries_remaining = 
1276                 smtp_purge_completed_deliveries(instr);
1277
1278
1279         /*
1280          * No delivery instructions remain, so delete both the instructions
1281          * message and the message message.
1282          */
1283         if (incomplete_deliveries_remaining <= 0)  {
1284                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
1285                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, NULL);    
1286         }
1287
1288
1289         /*
1290          * Uncompleted delivery instructions remain, so delete the old
1291          * instructions and replace with the updated ones.
1292          */
1293         if (incomplete_deliveries_remaining > 0) {
1294                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
1295                 msg = mallok(sizeof(struct CtdlMessage));
1296                 memset(msg, 0, sizeof(struct CtdlMessage));
1297                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1298                 msg->cm_anon_type = MES_NORMAL;
1299                 msg->cm_format_type = FMT_RFC822;
1300                 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1301                 sprintf(msg->cm_fields['M'],
1302                         "Content-type: %s\n\n%s\nattempted|%ld\n",
1303                         SPOOLMIME, instr, time(NULL) );
1304                 phree(instr);
1305                 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1306                 CtdlFreeMessage(msg);
1307         }
1308
1309 }
1310
1311
1312
1313 /*
1314  * smtp_do_queue()
1315  * 
1316  * Run through the queue sending out messages.
1317  */
1318 void smtp_do_queue(void) {
1319         lprintf(5, "SMTP: processing outbound queue\n");
1320
1321         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1322                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1323                 return;
1324         }
1325         CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
1326
1327         lprintf(5, "SMTP: queue run completed\n");
1328 }
1329
1330
1331
1332 /*****************************************************************************/
1333 /*                      MODULE INITIALIZATION STUFF                          */
1334 /*****************************************************************************/
1335
1336
1337 char *Dynamic_Module_Init(void)
1338 {
1339         SYM_SMTP = CtdlGetDynamicSymbol();
1340         SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1341         CtdlRegisterServiceHook(SMTP_PORT,
1342                                 smtp_greeting,
1343                                 smtp_command_loop);
1344         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1345         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1346         return "$Id$";
1347 }