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