* Coded up the "bounce" functions. Still a coupla bugs.
[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;        /* FIX 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         struct CtdlMessage *bmsg = NULL;
980
981         lprintf(9, "smtp_do_bounce() called\n");
982         strcpy(bounceto, "");
983
984         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
985         if (bmsg == NULL) return;
986         memset(bmsg, 0, sizeof(struct CtdlMessage));
987
988         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
989         bmsg->cm_anon_type = MES_NORMAL;
990         bmsg->cm_format_type = 1;
991         bmsg->cm_fields['A'] = strdoop("Citadel");
992         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
993         bmsg->cm_fields['M'] = strdoop(
994                 "BOUNCE!  BOUNCE!!  BOUNCE!!!\n\n"
995                 "FIX ... this message should be made to look nice and stuff.\n"
996                 "In the meantime, you should be aware that the following\n"
997                 "recipient addresses had permanent fatal errors:\n\n");
998
999         lines = num_tokens(instr, '\n');
1000         for (i=0; i<lines; ++i) {
1001                 extract_token(buf, instr, i, '\n');
1002                 extract(key, buf, 0);
1003                 extract(addr, buf, 1);
1004                 status = extract_int(buf, 2);
1005                 extract(dsn, buf, 3);
1006                 bounce_this = 0;
1007
1008                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1009                         key, addr, status, dsn);
1010
1011                 if (!strcasecmp(key, "bounceto")) {
1012                         strcpy(bounceto, addr);
1013                 }
1014
1015                 if (
1016                    (!strcasecmp(key, "local"))
1017                    || (!strcasecmp(key, "remote"))
1018                    || (!strcasecmp(key, "ignet"))
1019                    || (!strcasecmp(key, "room"))
1020                 ) {
1021                         if (status == 5) bounce_this = 1;
1022                 }
1023
1024                 if (bounce_this) {
1025                         ++num_bounces;
1026
1027                         /*  FIX put this back in!
1028                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1029                                 strlen(bmsg->cm_fields['M'] + 1024) );
1030                         strcat(bmsg->cm_fields['M'], addr);
1031                         strcat(bmsg->cm_fields['M'], ": ");
1032                         strcat(bmsg->cm_fields['M'], dsn);
1033                         strcat(bmsg->cm_fields['M'], "\n");
1034                         */
1035
1036                         remove_token(instr, i, '\n');
1037                         --i;
1038                         --lines;
1039                 }
1040         }
1041
1042         /* Deliver the bounce if there's anything worth mentioning */
1043         lprintf(9, "num_bounces = %d\n", num_bounces);
1044         if (num_bounces > 0) {
1045
1046                 /* First try the user who sent the message   FIX
1047                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1048                 if (strlen(bounceto) == 0) bounce_msgid = (-1L);
1049                 else bounce_msgid = CtdlSaveMsg(bmsg,
1050                         bounceto,
1051                         "", MES_LOCAL, 1);
1052                 */
1053
1054                 /* Otherwise, go to the Aide> room */
1055                 lprintf(9, "bounce to room?\n");
1056                 if (bounce_msgid < 0L) bounce_msgid = CtdlSaveMsg(bmsg,
1057                         "", AIDEROOM,
1058                         MES_LOCAL, 1);
1059         }
1060
1061         CtdlFreeMessage(bmsg);
1062         lprintf(9, "Done processing bounces\n");
1063 }
1064
1065
1066 /*
1067  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1068  * set of delivery instructions for completed deliveries and remove them.
1069  *
1070  * It returns the number of incomplete deliveries remaining.
1071  */
1072 int smtp_purge_completed_deliveries(char *instr) {
1073         int i;
1074         int lines;
1075         int status;
1076         char buf[1024];
1077         char key[1024];
1078         char addr[1024];
1079         char dsn[1024];
1080         int completed;
1081         int incomplete = 0;
1082
1083         lines = num_tokens(instr, '\n');
1084         for (i=0; i<lines; ++i) {
1085                 extract_token(buf, instr, i, '\n');
1086                 extract(key, buf, 0);
1087                 extract(addr, buf, 1);
1088                 status = extract_int(buf, 2);
1089                 extract(dsn, buf, 3);
1090
1091                 completed = 0;
1092
1093                 if (
1094                    (!strcasecmp(key, "local"))
1095                    || (!strcasecmp(key, "remote"))
1096                    || (!strcasecmp(key, "ignet"))
1097                    || (!strcasecmp(key, "room"))
1098                 ) {
1099                         if (status == 2) completed = 1;
1100                         else ++incomplete;
1101                 }
1102
1103                 if (completed) {
1104                         remove_token(instr, i, '\n');
1105                         --i;
1106                         --lines;
1107                 }
1108         }
1109
1110         return(incomplete);
1111 }
1112
1113
1114 /*
1115  * smtp_do_procmsg()
1116  *
1117  * Called by smtp_do_queue() to handle an individual message.
1118  */
1119 void smtp_do_procmsg(long msgnum) {
1120         struct CtdlMessage *msg;
1121         char *instr = NULL;
1122         char *results = NULL;
1123         int i;
1124         int lines;
1125         int status;
1126         char buf[1024];
1127         char key[1024];
1128         char addr[1024];
1129         char dsn[1024];
1130         long text_msgid = (-1);
1131         int incomplete_deliveries_remaining;
1132
1133         msg = CtdlFetchMessage(msgnum);
1134         if (msg == NULL) {
1135                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1136                 return;
1137         }
1138
1139         instr = strdoop(msg->cm_fields['M']);
1140         CtdlFreeMessage(msg);
1141
1142         /* Strip out the headers amd any other non-instruction line */
1143         lines = num_tokens(instr, '\n');
1144         for (i=0; i<lines; ++i) {
1145                 extract_token(buf, instr, i, '\n');
1146                 if (num_tokens(buf, '|') < 2) {
1147                         lprintf(9, "removing <%s>\n", buf);
1148                         remove_token(instr, i, '\n');
1149                         --lines;
1150                         --i;
1151                 }
1152         }
1153
1154         /* Learn the message ID */
1155         lines = num_tokens(instr, '\n');
1156         for (i=0; i<lines; ++i) {
1157                 extract_token(buf, instr, i, '\n');
1158                 extract(key, buf, 0);
1159                 if (!strcasecmp(key, "msgid")) {
1160                         text_msgid = extract_long(buf, 1);
1161                 }
1162         }
1163
1164         if (text_msgid < 0L) {
1165                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1166                 phree(instr);
1167                 return;
1168         }
1169
1170         /* Plow through the instructions looking for 'remote' directives and
1171          * a status of 0 (no delivery yet attempted) or 3 (transient errors
1172          * were experienced and it's time to try again)
1173          */
1174         lines = num_tokens(instr, '\n');
1175         for (i=0; i<lines; ++i) {
1176                 extract_token(buf, instr, i, '\n');
1177                 extract(key, buf, 0);
1178                 extract(addr, buf, 1);
1179                 status = extract_int(buf, 2);
1180                 extract(dsn, buf, 3);
1181                 if ( (!strcasecmp(key, "remote"))
1182                    && ((status==0)||(status==3)) ) {
1183                         remove_token(instr, i, '\n');
1184                         --i;
1185                         --lines;
1186                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1187                         smtp_try(key, addr, &status, dsn, text_msgid);
1188                         if (status != 2) {
1189                                 if (results == NULL) {
1190                                         results = mallok(1024);
1191                                         memset(results, 0, 1024);
1192                                 }
1193                                 else {
1194                                         results = reallok(results,
1195                                                 strlen(results) + 1024);
1196                                 }
1197                                 sprintf(&results[strlen(results)],
1198                                         "%s|%s|%d|%s\n",
1199                                         key, addr, status, dsn);
1200                         }
1201                 }
1202         }
1203
1204         if (results != NULL) {
1205                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1206                 strcat(instr, results);
1207                 phree(results);
1208         }
1209
1210
1211         /* Generate 'bounce' messages */
1212         smtp_do_bounce(instr);
1213
1214         /* Go through the delivery list, deleting completed deliveries */
1215         incomplete_deliveries_remaining = 
1216                 smtp_purge_completed_deliveries(instr);
1217
1218
1219         /*
1220          * No delivery instructions remain, so delete both the instructions
1221          * message and the message message.
1222          */
1223         if (incomplete_deliveries_remaining <= 0)  {
1224                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
1225                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, NULL);    
1226         }
1227
1228
1229         /*
1230          * Uncompleted delivery instructions remain, so delete the old
1231          * instructions and replace with the updated ones.
1232          */
1233         if (incomplete_deliveries_remaining > 0) {
1234                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
1235                 msg = mallok(sizeof(struct CtdlMessage));
1236                 memset(msg, 0, sizeof(struct CtdlMessage));
1237                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1238                 msg->cm_anon_type = MES_NORMAL;
1239                 msg->cm_format_type = FMT_RFC822;
1240                 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1241                 sprintf(msg->cm_fields['M'],
1242                         "Content-type: %s\n\n%s\nattempted|%ld\n",
1243                         SPOOLMIME, instr, time(NULL) );
1244                 phree(instr);
1245                 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1246                 CtdlFreeMessage(msg);
1247         }
1248
1249 }
1250
1251
1252
1253 /*
1254  * smtp_do_queue()
1255  * 
1256  * Run through the queue sending out messages.
1257  */
1258 void smtp_do_queue(void) {
1259         lprintf(5, "SMTP: processing outbound queue\n");
1260
1261         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1262                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1263                 return;
1264         }
1265         CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
1266
1267         lprintf(5, "SMTP: queue run completed\n");
1268 }
1269
1270
1271
1272 /*****************************************************************************/
1273 /*                      MODULE INITIALIZATION STUFF                          */
1274 /*****************************************************************************/
1275
1276
1277 char *Dynamic_Module_Init(void)
1278 {
1279         SYM_SMTP = CtdlGetDynamicSymbol();
1280         SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1281         CtdlRegisterServiceHook(SMTP_PORT,
1282                                 smtp_greeting,
1283                                 smtp_command_loop);
1284         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1285         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1286         return "$Id$";
1287 }
1288