61a5e5ef3476d3423462e9e58813a4be23b9130c
[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                 SPOOLMIME, msgid, time(NULL) );
464
465         for (i=0; i<SMTP->number_of_recipients; ++i) {
466                 extract_token(buf, SMTP_RECP, i, '\n');
467                 extract(dtype, buf, 0);
468
469                 /* Stuff local mailboxes */
470                 if (!strcasecmp(dtype, "local")) {
471                         extract(user, buf, 1);
472                         if (getuser(&userbuf, user) == 0) {
473                                 MailboxName(room, &userbuf, MAILROOM);
474                                 CtdlSaveMsgPointerInRoom(room, msgid, 0);
475                                 ++successful_saves;
476                         }
477                         else {
478                                 ++failed_saves;
479                         }
480                 }
481
482                 /* Delivery to local non-mailbox rooms */
483                 if (!strcasecmp(dtype, "room")) {
484                         extract(room, buf, 1);
485                         CtdlSaveMsgPointerInRoom(room, msgid, 0);
486                         ++successful_saves;
487                 }
488
489                 /* Remote delivery */
490                 if (!strcasecmp(dtype, "remote")) {
491                         extract(user, buf, 1);
492                         instr = reallok(instr, strlen(instr) + 1024);
493                         sprintf(&instr[strlen(instr)],
494                                 "remote|%s|0\n",
495                                 user);
496                         ++remote_spools;
497                 }
498
499         }
500
501         /* If there are remote spools to be done, save the instructions */
502         if (remote_spools > 0) {
503                 imsg = mallok(sizeof(struct CtdlMessage));
504                 memset(imsg, 0, sizeof(struct CtdlMessage));
505                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
506                 imsg->cm_anon_type = MES_NORMAL;
507                 imsg->cm_format_type = FMT_RFC822;
508                 imsg->cm_fields['M'] = instr;
509                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
510                 CtdlFreeMessage(imsg);
511         }
512
513         /* If there are no remote spools, delete the message */ 
514         else {
515                 phree(instr);   /* only needed here, because CtdlSaveMsg()
516                                  * would free this buffer otherwise */
517                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, NULL); 
518         }
519
520         return(failed_saves);
521 }
522
523
524
525 /*
526  * Implements the DATA command
527  */
528 void smtp_data(void) {
529         char *body;
530         struct CtdlMessage *msg;
531         int retval;
532         char nowstamp[256];
533
534         if (strlen(SMTP->from) == 0) {
535                 cprintf("503 Need MAIL command first.\r\n");
536                 return;
537         }
538
539         if (SMTP->number_of_recipients < 1) {
540                 cprintf("503 Need RCPT command first.\r\n");
541                 return;
542         }
543
544         cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
545         
546         generate_rfc822_datestamp(nowstamp, time(NULL));
547         body = mallok(4096);
548
549         if (body != NULL) sprintf(body,
550                 "Received: from %s\n"
551                 "       by %s;\n"
552                 "       %s\n",
553                         SMTP->helo_node,
554                         config.c_fqdn,
555                         nowstamp);
556         
557         body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
558         if (body == NULL) {
559                 cprintf("550 Unable to save message text: internal error.\r\n");
560                 return;
561         }
562
563         lprintf(9, "Converting message...\n");
564         msg = convert_internet_message(body);
565
566         /* If the user is locally authenticated, FORCE the From: header to
567          * show up as the real sender
568          */
569         if (CC->logged_in) {
570                 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
571                 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
572                 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
573                 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
574                 msg->cm_fields['N'] = strdoop(config.c_nodename);
575                 msg->cm_fields['H'] = strdoop(config.c_humannode);
576         }
577
578         retval = smtp_message_delivery(msg);
579         CtdlFreeMessage(msg);
580
581         if (!retval) {
582                 cprintf("250 Message accepted for delivery.\r\n");
583         }
584         else {
585                 cprintf("550 Internal delivery errors: %d\r\n", retval);
586         }
587 }
588
589
590
591
592 /* 
593  * Main command loop for SMTP sessions.
594  */
595 void smtp_command_loop(void) {
596         char cmdbuf[256];
597
598         time(&CC->lastcmd);
599         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
600         if (client_gets(cmdbuf) < 1) {
601                 lprintf(3, "SMTP socket is broken.  Ending session.\n");
602                 CC->kill_me = 1;
603                 return;
604         }
605         lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
606         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
607
608         if (SMTP->command_state == smtp_user) {
609                 smtp_get_user(cmdbuf);
610         }
611
612         else if (SMTP->command_state == smtp_password) {
613                 smtp_get_pass(cmdbuf);
614         }
615
616         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
617                 smtp_auth(&cmdbuf[5]);
618         }
619
620         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
621                 smtp_data();
622         }
623
624         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
625                 smtp_hello(&cmdbuf[5], 1);
626         }
627
628         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
629                 smtp_expn(&cmdbuf[5]);
630         }
631
632         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
633                 smtp_hello(&cmdbuf[5], 0);
634         }
635
636         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
637                 smtp_help();
638         }
639
640         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
641                 smtp_mail(&cmdbuf[5]);
642         }
643
644         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
645                 cprintf("250 This command successfully did nothing.\r\n");
646         }
647
648         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
649                 cprintf("221 Goodbye...\r\n");
650                 CC->kill_me = 1;
651                 return;
652                 }
653
654         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
655                 smtp_rcpt(&cmdbuf[5]);
656         }
657
658         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
659                 smtp_rset();
660         }
661
662         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
663                 smtp_vrfy(&cmdbuf[5]);
664         }
665
666         else {
667                 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
668         }
669
670 }
671
672
673
674
675 /*****************************************************************************/
676 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
677 /*****************************************************************************/
678
679
680
681 /*
682  * smtp_try()
683  *
684  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
685  *
686  */
687 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
688 {
689         int sock = (-1);
690         char mxhosts[1024];
691         int num_mxhosts;
692         int mx;
693         int i;
694         char user[256], node[256], name[256];
695         char buf[1024];
696         char mailfrom[1024];
697         int lp, rp;
698         FILE *msg_fp = NULL;
699         size_t msg_size;
700         size_t blocksize = 0;
701         int scan_done;
702
703         /* Parse out the host portion of the recipient address */
704         process_rfc822_addr(addr, user, node, name);
705         lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
706                 user, node, name);
707
708         /* Load the message out of the database into a temp file */
709         msg_fp = tmpfile();
710         if (msg_fp == NULL) {
711                 *status = 4;
712                 sprintf(dsn, "Error creating temporary file");
713                 return;
714         }
715         else {
716                 CtdlRedirectOutput(msg_fp, -1);
717                 CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
718                 CtdlRedirectOutput(NULL, -1);
719                 fseek(msg_fp, 0L, SEEK_END);
720                 msg_size = ftell(msg_fp);
721         }
722
723
724         /* Extract something to send later in the 'MAIL From:' command */
725         strcpy(mailfrom, "");
726         rewind(msg_fp);
727         scan_done = 0;
728         do {
729                 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
730                 if (!strncasecmp(buf, "From:", 5)) {
731                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
732                         striplt(mailfrom);
733                         for (i=0; i<strlen(mailfrom); ++i) {
734                                 if (!isprint(mailfrom[i])) {
735                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
736                                         i=0;
737                                 }
738                         }
739
740                         /* Strip out parenthesized names */
741                         lp = (-1);
742                         rp = (-1);
743                         for (i=0; i<strlen(mailfrom); ++i) {
744                                 if (mailfrom[i] == '(') lp = i;
745                                 if (mailfrom[i] == ')') rp = i;
746                         }
747                         if ((lp>0)&&(rp>lp)) {
748                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
749                         }
750
751                         /* Prefer brokketized names */
752                         lp = (-1);
753                         rp = (-1);
754                         for (i=0; i<strlen(mailfrom); ++i) {
755                                 if (mailfrom[i] == '<') lp = i;
756                                 if (mailfrom[i] == '>') rp = i;
757                         }
758                         if ((lp>=0)&&(rp>lp)) {
759                                 mailfrom[rp] = 0;
760                                 strcpy(mailfrom, &mailfrom[lp]);
761                         }
762
763                         scan_done = 1;
764                 }
765         } while (scan_done == 0);
766         if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
767
768
769         /* Figure out what mail exchanger host we have to connect to */
770         num_mxhosts = getmx(mxhosts, node);
771         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
772         if (num_mxhosts < 1) {
773                 *status = 5;
774                 sprintf(dsn, "No MX hosts found for <%s>", node);
775                 return;
776         }
777
778         for (mx=0; mx<num_mxhosts; ++mx) {
779                 extract(buf, mxhosts, mx);
780                 lprintf(9, "Trying <%s>\n", buf);
781                 sock = sock_connect(buf, "25", "tcp");
782                 sprintf(dsn, "Could not connect: %s", strerror(errno));
783                 if (sock >= 0) lprintf(9, "Connected!\n");
784                 if (sock < 0) sprintf(dsn, "%s", strerror(errno));
785                 if (sock >= 0) break;
786         }
787
788         if (sock < 0) {
789                 *status = 4;    /* dsn is already filled in */
790                 return;
791         }
792
793         /* Process the SMTP greeting from the server */
794         if (sock_gets(sock, buf) < 0) {
795                 *status = 4;
796                 strcpy(dsn, "Connection broken during SMTP conversation");
797                 goto bail;
798         }
799         lprintf(9, "<%s\n", buf);
800         if (buf[0] != '2') {
801                 if (buf[0] == '4') {
802                         *status = 4;
803                         strcpy(dsn, &buf[4]);
804                         goto bail;
805                 }
806                 else {
807                         *status = 5;
808                         strcpy(dsn, &buf[4]);
809                         goto bail;
810                 }
811         }
812
813         /* At this point we know we are talking to a real SMTP server */
814
815         /* Do a HELO command */
816         sprintf(buf, "HELO %s", config.c_fqdn);
817         lprintf(9, ">%s\n", buf);
818         sock_puts(sock, buf);
819         if (sock_gets(sock, buf) < 0) {
820                 *status = 4;
821                 strcpy(dsn, "Connection broken during SMTP conversation");
822                 goto bail;
823         }
824         lprintf(9, "<%s\n", buf);
825         if (buf[0] != '2') {
826                 if (buf[0] == '4') {
827                         *status = 4;
828                         strcpy(dsn, &buf[4]);
829                         goto bail;
830                 }
831                 else {
832                         *status = 5;
833                         strcpy(dsn, &buf[4]);
834                         goto bail;
835                 }
836         }
837
838
839         /* HELO succeeded, now try the MAIL From: command */
840         sprintf(buf, "MAIL From: %s", mailfrom);
841         lprintf(9, ">%s\n", buf);
842         sock_puts(sock, buf);
843         if (sock_gets(sock, buf) < 0) {
844                 *status = 4;
845                 strcpy(dsn, "Connection broken during SMTP conversation");
846                 goto bail;
847         }
848         lprintf(9, "<%s\n", buf);
849         if (buf[0] != '2') {
850                 if (buf[0] == '4') {
851                         *status = 4;
852                         strcpy(dsn, &buf[4]);
853                         goto bail;
854                 }
855                 else {
856                         *status = 5;
857                         strcpy(dsn, &buf[4]);
858                         goto bail;
859                 }
860         }
861
862
863         /* MAIL succeeded, now try the RCPT To: command */
864         sprintf(buf, "RCPT To: %s", addr);
865         lprintf(9, ">%s\n", buf);
866         sock_puts(sock, buf);
867         if (sock_gets(sock, buf) < 0) {
868                 *status = 4;
869                 strcpy(dsn, "Connection broken during SMTP conversation");
870                 goto bail;
871         }
872         lprintf(9, "<%s\n", buf);
873         if (buf[0] != '2') {
874                 if (buf[0] == '4') {
875                         *status = 4;
876                         strcpy(dsn, &buf[4]);
877                         goto bail;
878                 }
879                 else {
880                         *status = 5;
881                         strcpy(dsn, &buf[4]);
882                         goto bail;
883                 }
884         }
885
886
887         /* RCPT succeeded, now try the DATA command */
888         lprintf(9, ">DATA\n");
889         sock_puts(sock, "DATA");
890         if (sock_gets(sock, buf) < 0) {
891                 *status = 4;
892                 strcpy(dsn, "Connection broken during SMTP conversation");
893                 goto bail;
894         }
895         lprintf(9, "<%s\n", buf);
896         if (buf[0] != '3') {
897                 if (buf[0] == '4') {
898                         *status = 3;
899                         strcpy(dsn, &buf[4]);
900                         goto bail;
901                 }
902                 else {
903                         *status = 5;
904                         strcpy(dsn, &buf[4]);
905                         goto bail;
906                 }
907         }
908
909         /* If we reach this point, the server is expecting data */
910         rewind(msg_fp);
911         while (msg_size > 0) {
912                 blocksize = sizeof(buf);
913                 if (blocksize > msg_size) blocksize = msg_size;
914                 fread(buf, blocksize, 1, msg_fp);
915                 sock_write(sock, buf, blocksize);
916                 msg_size -= blocksize;
917         }
918         if (buf[blocksize-1] != 10) {
919                 lprintf(5, "Possible problem: message did not correctly "
920                         "terminate. (expecting 0x10, got 0x%02x)\n",
921                                 buf[blocksize-1]);
922         }
923
924         sock_write(sock, ".\r\n", 3);
925         if (sock_gets(sock, buf) < 0) {
926                 *status = 4;
927                 strcpy(dsn, "Connection broken during SMTP conversation");
928                 goto bail;
929         }
930         lprintf(9, "%s\n", buf);
931         if (buf[0] != '2') {
932                 if (buf[0] == '4') {
933                         *status = 4;
934                         strcpy(dsn, &buf[4]);
935                         goto bail;
936                 }
937                 else {
938                         *status = 5;
939                         strcpy(dsn, &buf[4]);
940                         goto bail;
941                 }
942         }
943
944         /* We did it! */
945         strcpy(dsn, &buf[4]);
946         *status = 2;
947
948         lprintf(9, ">QUIT\n");
949         sock_puts(sock, "QUIT");
950         sock_gets(sock, buf);
951         lprintf(9, "<%s\n", buf);
952
953 bail:   if (msg_fp != NULL) fclose(msg_fp);
954         sock_close(sock);
955         return;
956 }
957
958
959
960 /*
961  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to remove
962  * all of the completed deliveries from a set of delivery instructions.
963  *
964  * It returns the number of incomplete deliveries remaining.
965  */
966 int smtp_purge_completed_deliveries(char *instr) {
967         int i;
968         int lines;
969         int status;
970         char buf[1024];
971         char key[1024];
972         char addr[1024];
973         char dsn[1024];
974         int completed;
975         int incomplete = 0;
976
977         lines = num_tokens(instr, '\n');
978         for (i=0; i<lines; ++i) {
979                 extract_token(buf, instr, i, '\n');
980                 extract(key, buf, 0);
981                 extract(addr, buf, 1);
982                 status = extract_int(buf, 2);
983                 extract(dsn, buf, 3);
984
985                 completed = 0;
986
987                 if (
988                    (!strcasecmp(key, "local"))
989                    || (!strcasecmp(key, "remote"))
990                    || (!strcasecmp(key, "ignet"))
991                    || (!strcasecmp(key, "room"))
992                 ) {
993                         if (status == 2) completed = 1;
994                         else ++incomplete;
995                 }
996
997                 if (completed) {
998                         remove_token(instr, i, '\n');
999                         --i;
1000                         --lines;
1001                 }
1002         }
1003
1004         return(incomplete);
1005 }
1006
1007
1008 /*
1009  * smtp_do_procmsg()
1010  *
1011  * Called by smtp_do_queue() to handle an individual message.
1012  */
1013 void smtp_do_procmsg(long msgnum) {
1014         struct CtdlMessage *msg;
1015         char *instr = NULL;
1016         char *results = NULL;
1017         int i;
1018         int lines;
1019         int status;
1020         char buf[1024];
1021         char key[1024];
1022         char addr[1024];
1023         char dsn[1024];
1024         long text_msgid = (-1);
1025         int incomplete_deliveries_remaining;
1026
1027         msg = CtdlFetchMessage(msgnum);
1028         if (msg == NULL) {
1029                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1030                 return;
1031         }
1032
1033         instr = strdoop(msg->cm_fields['M']);
1034         CtdlFreeMessage(msg);
1035
1036         /* Strip out the headers amd any other non-instruction line */
1037         lines = num_tokens(instr, '\n');
1038         for (i=0; i<lines; ++i) {
1039                 extract_token(buf, instr, i, '\n');
1040                 if (num_tokens(buf, '|') < 2) {
1041                         lprintf(9, "removing <%s>\n", buf);
1042                         remove_token(instr, i, '\n');
1043                         --lines;
1044                         --i;
1045                 }
1046         }
1047
1048         /* Learn the message ID */
1049         lines = num_tokens(instr, '\n');
1050         for (i=0; i<lines; ++i) {
1051                 extract_token(buf, instr, i, '\n');
1052                 extract(key, buf, 0);
1053                 if (!strcasecmp(key, "msgid")) {
1054                         text_msgid = extract_long(buf, 1);
1055                 }
1056         }
1057
1058         if (text_msgid < 0L) {
1059                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1060                 phree(instr);
1061                 return;
1062         }
1063
1064         /* Plow through the instructions looking for 'remote' directives and
1065          * a status of 0 (no delivery yet attempted) or 3 (transient errors
1066          * were experienced and it's time to try again)
1067          */
1068         lines = num_tokens(instr, '\n');
1069         for (i=0; i<lines; ++i) {
1070                 extract_token(buf, instr, i, '\n');
1071                 extract(key, buf, 0);
1072                 extract(addr, buf, 1);
1073                 status = extract_int(buf, 2);
1074                 extract(dsn, buf, 3);
1075                 if ( (!strcasecmp(key, "remote"))
1076                    && ((status==0)||(status==3)) ) {
1077                         remove_token(instr, i, '\n');
1078                         --i;
1079                         --lines;
1080                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1081                         smtp_try(key, addr, &status, dsn, text_msgid);
1082                         if (status != 2) {
1083                                 if (results == NULL) {
1084                                         results = mallok(1024);
1085                                         memset(results, 0, 1024);
1086                                 }
1087                                 else {
1088                                         results = reallok(results,
1089                                                 strlen(results) + 1024);
1090                                 }
1091                                 sprintf(&results[strlen(results)],
1092                                         "%s|%s|%d|%s\n",
1093                                         key, addr, status, dsn);
1094                         }
1095                 }
1096         }
1097
1098         if (results != NULL) {
1099                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1100                 strcat(instr, results);
1101                 phree(results);
1102         }
1103
1104
1105
1106         /*
1107          * Go through the delivery list, deleting completed deliveries
1108          */
1109         incomplete_deliveries_remaining = 
1110                 smtp_purge_completed_deliveries(instr);
1111
1112
1113         /*
1114          * No delivery instructions remain, so delete both the instructions
1115          * message and the message message.
1116          */
1117         if (incomplete_deliveries_remaining <= 0)  {
1118                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
1119                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, NULL);    
1120         }
1121
1122
1123         /*
1124          * Uncompleted delivery instructions remain, so delete the old
1125          * instructions and replace with the updated ones.
1126          */
1127         if (incomplete_deliveries_remaining > 0) {
1128                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
1129                 msg = mallok(sizeof(struct CtdlMessage));
1130                 memset(msg, 0, sizeof(struct CtdlMessage));
1131                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1132                 msg->cm_anon_type = MES_NORMAL;
1133                 msg->cm_format_type = FMT_RFC822;
1134                 msg->cm_fields['M'] = malloc(strlen(instr)+256);
1135                 sprintf(msg->cm_fields['M'],
1136                         "Content-type: %s\n\n%s\n", SPOOLMIME, instr);
1137                 phree(instr);
1138                 CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
1139                 CtdlFreeMessage(msg);
1140         }
1141
1142 }
1143
1144
1145
1146 /*
1147  * smtp_do_queue()
1148  * 
1149  * Run through the queue sending out messages.
1150  */
1151 void smtp_do_queue(void) {
1152         lprintf(5, "SMTP: processing outbound queue\n");
1153
1154         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1155                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1156                 return;
1157         }
1158         CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
1159
1160         lprintf(5, "SMTP: queue run completed\n");
1161 }
1162
1163
1164
1165 /*****************************************************************************/
1166 /*                      MODULE INITIALIZATION STUFF                          */
1167 /*****************************************************************************/
1168
1169
1170 char *Dynamic_Module_Init(void)
1171 {
1172         SYM_SMTP = CtdlGetDynamicSymbol();
1173         SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1174         CtdlRegisterServiceHook(SMTP_PORT,
1175                                 smtp_greeting,
1176                                 smtp_command_loop);
1177         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1178         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1179         return "$Id$";
1180 }
1181