* Changed '3' delivery code to '4' to make it more like SMTP
[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 <string.h>
15 #include <limits.h>
16 #include "citadel.h"
17 #include "server.h"
18 #include <time.h>
19 #include "sysdep_decls.h"
20 #include "citserver.h"
21 #include "support.h"
22 #include "config.h"
23 #include "dynloader.h"
24 #include "room_ops.h"
25 #include "user_ops.h"
26 #include "policy.h"
27 #include "database.h"
28 #include "msgbase.h"
29 #include "tools.h"
30 #include "internet_addressing.h"
31 #include "genstamp.h"
32 #include "domain.h"
33 #include "clientsocket.h"
34
35
36 struct citsmtp {                /* Information about the current session */
37         int command_state;
38         struct usersupp vrfy_buffer;
39         int vrfy_count;
40         char vrfy_match[256];
41         char from[256];
42         int number_of_recipients;
43         int delivery_mode;
44 };
45
46 enum {                          /* Command states for login authentication */
47         smtp_command,
48         smtp_user,
49         smtp_password
50 };
51
52 enum {                          /* Delivery modes */
53         smtp_deliver_local,
54         smtp_deliver_remote
55 };
56
57 #define SMTP            ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
58 #define SMTP_RECP       ((char *)CtdlGetUserData(SYM_SMTP_RECP))
59
60 long SYM_SMTP;
61 long SYM_SMTP_RECP;
62
63
64
65 /*****************************************************************************/
66 /*                      SMTP SERVER (INBOUND) STUFF                          */
67 /*****************************************************************************/
68
69
70 /*
71  * Here's where our SMTP session begins its happy day.
72  */
73 void smtp_greeting(void) {
74
75         strcpy(CC->cs_clientname, "SMTP session");
76         CC->internal_pgm = 1;
77         CC->cs_flags |= CS_STEALTH;
78         CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
79         CtdlAllocUserData(SYM_SMTP_RECP, 256);
80         sprintf(SMTP_RECP, "%s", "");
81
82         cprintf("220 Welcome to the Citadel/UX ESMTP server at %s\r\n",
83                 config.c_fqdn);
84 }
85
86
87 /*
88  * Implement HELO and EHLO commands.
89  */
90 void smtp_hello(char *argbuf, int is_esmtp) {
91
92         if (!is_esmtp) {
93                 cprintf("250 Greetings and joyous salutations.\r\n");
94         }
95         else {
96                 cprintf("250-Greetings and joyous salutations.\r\n");
97                 cprintf("250-HELP\r\n");
98                 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
99                 cprintf("250 AUTH=LOGIN\r\n");
100         }
101 }
102
103
104 /*
105  * Implement HELP command.
106  */
107 void smtp_help(void) {
108         cprintf("214-Here's the frequency, Kenneth:\r\n");
109         cprintf("214-    DATA\r\n");
110         cprintf("214-    EHLO\r\n");
111         cprintf("214-    EXPN\r\n");
112         cprintf("214-    HELO\r\n");
113         cprintf("214-    HELP\r\n");
114         cprintf("214-    MAIL\r\n");
115         cprintf("214-    NOOP\r\n");
116         cprintf("214-    QUIT\r\n");
117         cprintf("214-    RCPT\r\n");
118         cprintf("214-    RSET\r\n");
119         cprintf("214-    VRFY\r\n");
120         cprintf("214 I could tell you more, but then I'd have to kill you.\r\n");
121 }
122
123
124 /*
125  *
126  */
127 void smtp_get_user(char *argbuf) {
128         char buf[256];
129         char username[256];
130
131         decode_base64(username, argbuf);
132         lprintf(9, "Trying <%s>\n", username);
133         if (CtdlLoginExistingUser(username) == login_ok) {
134                 encode_base64(buf, "Password:");
135                 cprintf("334 %s\r\n", buf);
136                 SMTP->command_state = smtp_password;
137         }
138         else {
139                 cprintf("500 No such user.\r\n");
140                 SMTP->command_state = smtp_command;
141         }
142 }
143
144
145 /*
146  *
147  */
148 void smtp_get_pass(char *argbuf) {
149         char password[256];
150
151         decode_base64(password, argbuf);
152         lprintf(9, "Trying <%s>\n", password);
153         if (CtdlTryPassword(password) == pass_ok) {
154                 cprintf("235 Authentication successful.\r\n");
155                 lprintf(9, "SMTP authenticated login successful\n");
156                 CC->internal_pgm = 0;
157                 CC->cs_flags &= ~CS_STEALTH;
158         }
159         else {
160                 cprintf("500 Authentication failed.\r\n");
161         }
162         SMTP->command_state = smtp_command;
163 }
164
165
166 /*
167  *
168  */
169 void smtp_auth(char *argbuf) {
170         char buf[256];
171
172         if (strncasecmp(argbuf, "login", 5) ) {
173                 cprintf("550 We only support LOGIN authentication.\r\n");
174                 return;
175         }
176
177         if (strlen(argbuf) >= 7) {
178                 smtp_get_user(&argbuf[6]);
179         }
180
181         else {
182                 encode_base64(buf, "Username:");
183                 cprintf("334 %s\r\n", buf);
184                 SMTP->command_state = smtp_user;
185         }
186 }
187
188
189 /*
190  * Back end for smtp_vrfy() command
191  */
192 void smtp_vrfy_backend(struct usersupp *us, void *data) {
193
194         if (!fuzzy_match(us, SMTP->vrfy_match)) {
195                 ++SMTP->vrfy_count;
196                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
197         }
198 }
199
200
201 /* 
202  * Implements the VRFY (verify user name) command.
203  * Performs fuzzy match on full user names.
204  */
205 void smtp_vrfy(char *argbuf) {
206         SMTP->vrfy_count = 0;
207         strcpy(SMTP->vrfy_match, argbuf);
208         ForEachUser(smtp_vrfy_backend, NULL);
209
210         if (SMTP->vrfy_count < 1) {
211                 cprintf("550 String does not match anything.\r\n");
212         }
213         else if (SMTP->vrfy_count == 1) {
214                 cprintf("250 %s <cit%ld@%s>\r\n",
215                         SMTP->vrfy_buffer.fullname,
216                         SMTP->vrfy_buffer.usernum,
217                         config.c_fqdn);
218         }
219         else if (SMTP->vrfy_count > 1) {
220                 cprintf("553 Request ambiguous: %d users matched.\r\n",
221                         SMTP->vrfy_count);
222         }
223
224 }
225
226
227
228 /*
229  * Back end for smtp_expn() command
230  */
231 void smtp_expn_backend(struct usersupp *us, void *data) {
232
233         if (!fuzzy_match(us, SMTP->vrfy_match)) {
234
235                 if (SMTP->vrfy_count >= 1) {
236                         cprintf("250-%s <cit%ld@%s>\r\n",
237                                 SMTP->vrfy_buffer.fullname,
238                                 SMTP->vrfy_buffer.usernum,
239                                 config.c_fqdn);
240                 }
241
242                 ++SMTP->vrfy_count;
243                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
244         }
245 }
246
247
248 /* 
249  * Implements the EXPN (expand user name) command.
250  * Performs fuzzy match on full user names.
251  */
252 void smtp_expn(char *argbuf) {
253         SMTP->vrfy_count = 0;
254         strcpy(SMTP->vrfy_match, argbuf);
255         ForEachUser(smtp_expn_backend, NULL);
256
257         if (SMTP->vrfy_count < 1) {
258                 cprintf("550 String does not match anything.\r\n");
259         }
260         else if (SMTP->vrfy_count >= 1) {
261                 cprintf("250 %s <cit%ld@%s>\r\n",
262                         SMTP->vrfy_buffer.fullname,
263                         SMTP->vrfy_buffer.usernum,
264                         config.c_fqdn);
265         }
266 }
267
268
269 /*
270  * Implements the RSET (reset state) command.
271  * Currently this just zeroes out the state buffer.  If pointers to data
272  * allocated with mallok() are ever placed in the state buffer, we have to
273  * be sure to phree() them first!
274  */
275 void smtp_rset(void) {
276         memset(SMTP, 0, sizeof(struct citsmtp));
277         if (CC->logged_in) logout(CC);
278         cprintf("250 Zap!\r\n");
279 }
280
281
282
283 /*
284  * Implements the "MAIL From:" command
285  */
286 void smtp_mail(char *argbuf) {
287         char user[256];
288         char node[256];
289         int cvt;
290
291         if (strlen(SMTP->from) != 0) {
292                 cprintf("503 Only one sender permitted\r\n");
293                 return;
294         }
295
296         if (strncasecmp(argbuf, "From:", 5)) {
297                 cprintf("501 Syntax error\r\n");
298                 return;
299         }
300
301         strcpy(SMTP->from, &argbuf[5]);
302         striplt(SMTP->from);
303
304         if (strlen(SMTP->from) == 0) {
305                 cprintf("501 Empty sender name is not permitted\r\n");
306                 return;
307         }
308
309
310         /* If this SMTP connection is from a logged-in user, make sure that
311          * the user only sends email from his/her own address.
312          */
313         if (CC->logged_in) {
314                 cvt = convert_internet_address(user, node, SMTP->from);
315                 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
316                 if ( (cvt != 0) || (strcasecmp(user, CC->usersupp.fullname))) {
317                         cprintf("550 <%s> is not your address.\r\n", SMTP->from);
318                         strcpy(SMTP->from, "");
319                         return;
320                 }
321         }
322
323         /* Otherwise, make sure outsiders aren't trying to forge mail from
324          * this system.
325          */
326         else {
327                 cvt = convert_internet_address(user, node, SMTP->from);
328                 lprintf(9, "cvt=%d, citaddr=<%s@%s>\n", cvt, user, node);
329                 if (!strcasecmp(node, config.c_nodename)) { /* FIX use fcn */
330                         cprintf("550 You must log in to send mail from %s\r\n",
331                                 config.c_fqdn);
332                         strcpy(SMTP->from, "");
333                         return;
334                 }
335         }
336
337         cprintf("250 Sender ok.  Groovy.\r\n");
338 }
339
340
341
342 /*
343  * Implements the "RCPT To:" command
344  */
345 void smtp_rcpt(char *argbuf) {
346         int cvt;
347         char user[256];
348         char node[256];
349         char recp[256];
350         int is_spam = 0;        /* FIX implement anti-spamming */
351
352         if (strlen(SMTP->from) == 0) {
353                 cprintf("503 MAIL first, then RCPT.  Duh.\r\n");
354                 return;
355         }
356
357         if (strncasecmp(argbuf, "To:", 3)) {
358                 cprintf("501 Syntax error\r\n");
359                 return;
360         }
361
362         strcpy(recp, &argbuf[3]);
363         striplt(recp);
364         alias(recp);
365
366         cvt = convert_internet_address(user, node, recp);
367         sprintf(recp, "%s@%s", user, node);
368
369
370         switch(cvt) {
371                 case rfc822_address_locally_validated:
372                         cprintf("250 %s is a valid recipient.\r\n", user);
373                         ++SMTP->number_of_recipients;
374                         CtdlReallocUserData(SYM_SMTP_RECP,
375                                 strlen(SMTP_RECP) + 1024 );
376                         strcat(SMTP_RECP, "local|");
377                         strcat(SMTP_RECP, user);
378                         strcat(SMTP_RECP, "|0\n");
379                         return;
380
381                 case rfc822_room_delivery:
382                         cprintf("250 Delivering to room '%s'\r\n", user);
383                         ++SMTP->number_of_recipients;
384                         CtdlReallocUserData(SYM_SMTP_RECP,
385                                 strlen(SMTP_RECP) + 1024 );
386                         strcat(SMTP_RECP, "room|");
387                         strcat(SMTP_RECP, user);
388                         strcat(SMTP_RECP, "|0|\n");
389                         return;
390
391                 case rfc822_no_such_user:
392                         cprintf("550 %s: no such user\r\n", recp);
393                         return;
394
395                 case rfc822_address_invalid:
396                         if (is_spam) {
397                                 cprintf("551 Away with thee, spammer!\r\n");
398                         }
399                         else {
400                                 cprintf("250 Remote recipient %s ok\r\n", recp);
401                                 ++SMTP->number_of_recipients;
402                                 CtdlReallocUserData(SYM_SMTP_RECP,
403                                         strlen(SMTP_RECP) + 1024 );
404                                 strcat(SMTP_RECP, "remote|");
405                                 strcat(SMTP_RECP, recp);
406                                 strcat(SMTP_RECP, "|0|\n");
407                                 return;
408                         }
409                         return;
410         }
411
412         cprintf("599 Unknown error\r\n");
413 }
414
415
416
417
418
419 /*
420  * Back end for smtp_data()  ... this does the actual delivery of the message
421  * Returns 0 on success, nonzero on failure
422  */
423 int smtp_message_delivery(struct CtdlMessage *msg) {
424         char user[1024];
425         char node[1024];
426         char name[1024];
427         char buf[1024];
428         char dtype[1024];
429         char room[1024];
430         int successful_saves = 0;       /* number of successful local saves */
431         int failed_saves = 0;           /* number of failed deliveries */
432         int remote_spools = 0;          /* number of copies to send out */
433         long msgid = (-1L);
434         int i;
435         struct usersupp userbuf;
436         char *instr;                    /* Remote delivery instructions */
437         struct CtdlMessage *imsg;
438
439         lprintf(9, "smtp_message_delivery() called\n");
440
441         /* Fill in 'from' fields with envelope information if missing */
442         process_rfc822_addr(SMTP->from, user, node, name);
443         if (msg->cm_fields['A']==NULL) msg->cm_fields['A'] = strdoop(user);
444         if (msg->cm_fields['N']==NULL) msg->cm_fields['N'] = strdoop(node);
445         if (msg->cm_fields['H']==NULL) msg->cm_fields['H'] = strdoop(name);
446
447         /* Save the message in the queue */
448         msgid = CtdlSaveMsg(msg,
449                 "",
450                 SMTP_SPOOLOUT_ROOM,
451                 MES_LOCAL,
452                 1);
453         ++successful_saves;
454
455         instr = mallok(1024);
456         sprintf(instr, "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n",
457                 SPOOLMIME, msgid, time(NULL) );
458
459         for (i=0; i<SMTP->number_of_recipients; ++i) {
460                 extract_token(buf, SMTP_RECP, i, '\n');
461                 extract(dtype, buf, 0);
462
463                 /* Stuff local mailboxes */
464                 if (!strcasecmp(dtype, "local")) {
465                         extract(user, buf, 1);
466                         if (getuser(&userbuf, user) == 0) {
467                                 MailboxName(room, &userbuf, MAILROOM);
468                                 CtdlSaveMsgPointerInRoom(room, msgid, 0);
469                                 ++successful_saves;
470                         }
471                         else {
472                                 ++failed_saves;
473                         }
474                 }
475
476                 /* Delivery to local non-mailbox rooms */
477                 if (!strcasecmp(dtype, "room")) {
478                         extract(room, buf, 1);
479                         CtdlSaveMsgPointerInRoom(room, msgid, 0);
480                         ++successful_saves;
481                 }
482
483                 /* Remote delivery */
484                 if (!strcasecmp(dtype, "remote")) {
485                         extract(user, buf, 1);
486                         instr = reallok(instr, strlen(instr) + 1024);
487                         sprintf(&instr[strlen(instr)],
488                                 "remote|%s|0\n",
489                                 user);
490                         ++remote_spools;
491                 }
492
493         }
494
495         /* If there are remote spools to be done, save the instructions */
496         if (remote_spools > 0) {
497                 imsg = mallok(sizeof(struct CtdlMessage));
498                 memset(imsg, 0, sizeof(struct CtdlMessage));
499                 imsg->cm_magic = CTDLMESSAGE_MAGIC;
500                 imsg->cm_anon_type = MES_NORMAL;
501                 imsg->cm_format_type = FMT_RFC822;
502                 imsg->cm_fields['M'] = instr;
503                 CtdlSaveMsg(imsg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
504                 CtdlFreeMessage(imsg);
505         }
506
507         /* If there are no remote spools, delete the message */ 
508         else {
509                 phree(instr);   /* only needed here, because CtdlSaveMsg()
510                                  * would free this buffer otherwise */
511                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgid, NULL); 
512         }
513
514         return(failed_saves);
515 }
516
517
518
519 /*
520  * Implements the DATA command
521  */
522 void smtp_data(void) {
523         char *body;
524         struct CtdlMessage *msg;
525         int retval;
526         char nowstamp[256];
527
528         if (strlen(SMTP->from) == 0) {
529                 cprintf("503 Need MAIL command first.\r\n");
530                 return;
531         }
532
533         if (SMTP->number_of_recipients < 1) {
534                 cprintf("503 Need RCPT command first.\r\n");
535                 return;
536         }
537
538         cprintf("354 Transmit message now; terminate with '.' by itself\r\n");
539         
540         generate_rfc822_datestamp(nowstamp, time(NULL));
541         body = mallok(4096);
542         if (body != NULL) sprintf(body,
543                 "Received: from %s\n"
544                 "       by %s;\n"
545                 "       %s\n",
546                         "FIX.FIX.com",
547                         config.c_fqdn,
548                         nowstamp);
549         
550         body = CtdlReadMessageBody(".", config.c_maxmsglen, body);
551         if (body == NULL) {
552                 cprintf("550 Unable to save message text: internal error.\r\n");
553                 return;
554         }
555
556         lprintf(9, "Converting message...\n");
557         msg = convert_internet_message(body);
558
559         /* If the user is locally authenticated, FORCE the From: header to
560          * show up as the real sender
561          */
562         if (CC->logged_in) {
563                 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
564                 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
565                 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
566                 msg->cm_fields['A'] = strdoop(CC->usersupp.fullname);
567                 msg->cm_fields['N'] = strdoop(config.c_nodename);
568                 msg->cm_fields['H'] = strdoop(config.c_humannode);
569         }
570
571         retval = smtp_message_delivery(msg);
572         CtdlFreeMessage(msg);
573
574         if (!retval) {
575                 cprintf("250 Message accepted for delivery.\r\n");
576         }
577         else {
578                 cprintf("550 Internal delivery errors: %d\r\n", retval);
579         }
580 }
581
582
583
584
585 /* 
586  * Main command loop for SMTP sessions.
587  */
588 void smtp_command_loop(void) {
589         char cmdbuf[256];
590
591         time(&CC->lastcmd);
592         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
593         if (client_gets(cmdbuf) < 1) {
594                 lprintf(3, "SMTP socket is broken.  Ending session.\n");
595                 CC->kill_me = 1;
596                 return;
597         }
598         lprintf(5, "citserver[%3d]: %s\n", CC->cs_pid, cmdbuf);
599         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
600
601         if (SMTP->command_state == smtp_user) {
602                 smtp_get_user(cmdbuf);
603         }
604
605         else if (SMTP->command_state == smtp_password) {
606                 smtp_get_pass(cmdbuf);
607         }
608
609         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
610                 smtp_auth(&cmdbuf[5]);
611         }
612
613         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
614                 smtp_data();
615         }
616
617         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
618                 smtp_hello(&cmdbuf[5], 1);
619         }
620
621         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
622                 smtp_expn(&cmdbuf[5]);
623         }
624
625         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
626                 smtp_hello(&cmdbuf[5], 0);
627         }
628
629         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
630                 smtp_help();
631         }
632
633         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
634                 smtp_mail(&cmdbuf[5]);
635         }
636
637         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
638                 cprintf("250 This command successfully did nothing.\r\n");
639         }
640
641         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
642                 cprintf("221 Goodbye...\r\n");
643                 CC->kill_me = 1;
644                 return;
645                 }
646
647         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
648                 smtp_rcpt(&cmdbuf[5]);
649         }
650
651         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
652                 smtp_rset();
653         }
654
655         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
656                 smtp_vrfy(&cmdbuf[5]);
657         }
658
659         else {
660                 cprintf("502 I'm sorry Dave, I'm afraid I can't do that.\r\n");
661         }
662
663 }
664
665
666
667
668 /*****************************************************************************/
669 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
670 /*****************************************************************************/
671
672
673
674 /*
675  * smtp_try()
676  *
677  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
678  *
679  */
680 void smtp_try(char *key, char *addr, int *status, char *dsn, long msgnum)
681 {
682         char buf[256];
683         int sock = (-1);
684         char mxhosts[1024];
685         int num_mxhosts;
686         int mx;
687         char user[256], node[256], name[256];
688
689         /* Parse out the host portion of the recipient address */
690         process_rfc822_addr(addr, user, node, name);
691         lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
692                 user, node, name);
693
694         num_mxhosts = getmx(mxhosts, node);
695         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
696         if (num_mxhosts < 1) {
697                 *status = 5;
698                 sprintf(dsn, "No MX hosts found for <%s>", node);
699                 return;
700         }
701
702         for (mx=0; mx<num_mxhosts; ++mx) {
703                 extract(buf, mxhosts, mx);
704                 lprintf(9, "Trying <%s>\n", buf);
705                 sock = sock_connect(buf, "25", "tcp");
706                 sprintf(dsn, "Could not connect: %s", strerror(errno));
707                 if (sock >= 0) lprintf(9, "Connected!\n");
708                 if (sock < 0) sprintf(dsn, "%s", strerror(errno));
709                 if (sock >= 0) break;
710         }
711
712         if (sock < 0) {
713                 *status = 4;    /* dsn is already filled in */
714                 return;
715         }
716
717         /* Process the SMTP greeting from the server */
718         if (sock_gets(sock, buf) < 0) {
719                 *status = 4;
720                 strcpy(dsn, "Connection broken during SMTP conversation");
721                 sock_close(sock);
722                 return;
723         }
724         lprintf(9, "%s\n", buf);
725         if (buf[0] != '2') {
726                 if (buf[0] == '4') {
727                         *status = 4;
728                         strcpy(dsn, &buf[4]);
729                         sock_close(sock);
730                         return;
731                 }
732                 else {
733                         *status = 5;
734                         strcpy(dsn, &buf[4]);
735                         sock_close(sock);
736                         return;
737                 }
738         }
739
740         /* At this point we know we are talking to a real SMTP server */
741
742         /* Do a HELO command */
743         sprintf(buf, "HELO %s", config.c_fqdn);
744         sock_puts(sock, buf);
745         if (sock_gets(sock, buf) < 0) {
746                 *status = 4;
747                 strcpy(dsn, "Connection broken during SMTP conversation");
748                 sock_close(sock);
749                 return;
750         }
751         lprintf(9, "%s\n", buf);
752         if (buf[0] != '2') {
753                 if (buf[0] == '4') {
754                         *status = 4;
755                         strcpy(dsn, &buf[4]);
756                         sock_close(sock);
757                         return;
758                 }
759                 else {
760                         *status = 5;
761                         strcpy(dsn, &buf[4]);
762                         sock_close(sock);
763                         return;
764                 }
765         }
766
767
768         /* HELO succeeded, now try the MAIL From: command */
769         sprintf(buf, "MAIL From: FIX@uncnsrd.mt-kisco.ny.us"); /* FIX */
770         sock_puts(sock, buf);
771         if (sock_gets(sock, buf) < 0) {
772                 *status = 4;
773                 strcpy(dsn, "Connection broken during SMTP conversation");
774                 sock_close(sock);
775                 return;
776         }
777         lprintf(9, "%s\n", buf);
778         if (buf[0] != '2') {
779                 if (buf[0] == '4') {
780                         *status = 4;
781                         strcpy(dsn, &buf[4]);
782                         sock_close(sock);
783                         return;
784                 }
785                 else {
786                         *status = 5;
787                         strcpy(dsn, &buf[4]);
788                         sock_close(sock);
789                         return;
790                 }
791         }
792
793
794         /* MAIL succeeded, now try the RCPT To: command */
795         sprintf(buf, "RCPT To: %s", addr);
796         sock_puts(sock, buf);
797         if (sock_gets(sock, buf) < 0) {
798                 *status = 4;
799                 strcpy(dsn, "Connection broken during SMTP conversation");
800                 sock_close(sock);
801                 return;
802         }
803         lprintf(9, "%s\n", buf);
804         if (buf[0] != '2') {
805                 if (buf[0] == '4') {
806                         *status = 4;
807                         strcpy(dsn, &buf[4]);
808                         sock_close(sock);
809                         return;
810                 }
811                 else {
812                         *status = 5;
813                         strcpy(dsn, &buf[4]);
814                         sock_close(sock);
815                         return;
816                 }
817         }
818
819
820         /* RCPT succeeded, now try the DATA command */
821         sock_puts(sock, "DATA");
822         if (sock_gets(sock, buf) < 0) {
823                 *status = 4;
824                 strcpy(dsn, "Connection broken during SMTP conversation");
825                 sock_close(sock);
826                 return;
827         }
828         lprintf(9, "%s\n", buf);
829         if (buf[0] != '3') {
830                 if (buf[0] == '4') {
831                         *status = 3;
832                         strcpy(dsn, &buf[4]);
833                         sock_close(sock);
834                         return;
835                 }
836                 else {
837                         *status = 5;
838                         strcpy(dsn, &buf[4]);
839                         sock_close(sock);
840                         return;
841                 }
842         }
843
844         /* If we reach this point, the server is expecting data */
845         CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, NULL, sock, 1);
846         sock_puts(sock, ".");
847         if (sock_gets(sock, buf) < 0) {
848                 *status = 4;
849                 strcpy(dsn, "Connection broken during SMTP conversation");
850                 sock_close(sock);
851                 return;
852         }
853         lprintf(9, "%s\n", buf);
854         if (buf[0] != '2') {
855                 if (buf[0] == '4') {
856                         *status = 4;
857                         strcpy(dsn, &buf[4]);
858                         sock_close(sock);
859                         return;
860                 }
861                 else {
862                         *status = 5;
863                         strcpy(dsn, &buf[4]);
864                         sock_close(sock);
865                         return;
866                 }
867         }
868
869         /* We did it! */
870         strcpy(dsn, &buf[4]);
871         *status = 2;
872
873         sock_puts(sock, "QUIT");
874         sock_gets(sock, buf);
875         lprintf(9, "%s\n", buf);
876         sock_close(sock);
877 }
878
879
880
881
882 /*
883  * smtp_do_procmsg()
884  *
885  * Called by smtp_do_queue() to handle an individual message.
886  */
887 void smtp_do_procmsg(long msgnum) {
888         struct CtdlMessage *msg;
889         char *instr = NULL;
890         char *results = NULL;
891         int i;
892         int lines;
893         int status;
894         char buf[1024];
895         char key[1024];
896         char addr[1024];
897         char dsn[1024];
898         long text_msgid = (-1);
899
900         msg = CtdlFetchMessage(msgnum);
901         if (msg == NULL) {
902                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
903                 return;
904         }
905
906         instr = strdoop(msg->cm_fields['M']);
907         CtdlFreeMessage(msg);
908
909         /* Strip out the headers amd any other non-instruction line */
910         lines = num_tokens(instr, '\n');
911         for (i=0; i<lines; ++i) {
912                 extract_token(buf, instr, i, '\n');
913                 if (num_tokens(buf, '|') < 2) {
914                         lprintf(9, "removing <%s>\n", buf);
915                         remove_token(instr, i, '\n');
916                         --lines;
917                         --i;
918                 }
919         }
920
921         /* Learn the message ID */
922         lines = num_tokens(instr, '\n');
923         for (i=0; i<lines; ++i) {
924                 extract_token(buf, instr, i, '\n');
925                 extract(key, buf, 0);
926                 if (!strcasecmp(key, "msgid")) {
927                         text_msgid = extract_long(buf, 1);
928                 }
929         }
930
931         if (text_msgid < 0L) {
932                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
933                 phree(instr);
934                 return;
935         }
936
937         /* Plow through the instructions looking for 'remote' directives and
938          * a status of 0 (no delivery yet attempted) or 3 (transient errors
939          * were experienced and it's time to try again)
940          */
941         lines = num_tokens(instr, '\n');
942         for (i=0; i<lines; ++i) {
943                 extract_token(buf, instr, i, '\n');
944                 extract(key, buf, 0);
945                 extract(addr, buf, 1);
946                 status = extract_int(buf, 2);
947                 extract(dsn, buf, 3);
948                 if ( (!strcasecmp(key, "remote"))
949                    && ((status==0)||(status==3)) ) {
950                         remove_token(instr, i, '\n');
951                         --i;
952                         --lines;
953                         lprintf(9, "SMTP: Trying <%s>\n", addr);
954                         smtp_try(key, addr, &status, dsn, text_msgid);
955                         if (status != 2) {
956                                 if (results == NULL) {
957                                         results = mallok(1024);
958                                         memset(results, 0, 1024);
959                                 }
960                                 else {
961                                         results = reallok(results,
962                                                 strlen(results) + 1024);
963                                 }
964                                 sprintf(&results[strlen(results)],
965                                         "%s|%s|%d|%s\n",
966                                         key, addr, status, dsn);
967                         }
968                 }
969         }
970
971         if (results != NULL) {
972                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
973                 strcat(instr, results);
974                 phree(results);
975         }
976
977         /* Delete the instructions and replace with the updated ones */
978         CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
979         msg = mallok(sizeof(struct CtdlMessage));
980         memset(msg, 0, sizeof(struct CtdlMessage));
981         msg->cm_magic = CTDLMESSAGE_MAGIC;
982         msg->cm_anon_type = MES_NORMAL;
983         msg->cm_format_type = FMT_RFC822;
984         msg->cm_fields['M'] = malloc(strlen(instr)+256);
985         sprintf(msg->cm_fields['M'],
986                 "Content-type: %s\n\n%s\n", SPOOLMIME, instr);
987         phree(instr);
988         CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
989         CtdlFreeMessage(msg);
990 }
991
992
993
994 /*
995  * smtp_do_queue()
996  * 
997  * Run through the queue sending out messages.
998  */
999 void smtp_do_queue(void) {
1000         lprintf(5, "SMTP: processing outbound queue\n");
1001
1002         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1003                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1004                 return;
1005         }
1006         CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
1007
1008         lprintf(5, "SMTP: queue run completed\n");
1009 }
1010
1011
1012 /**** FIX  temporary hack to run the queue *****/
1013 void cmd_qqqq(char *argbuf) {
1014         smtp_do_queue();
1015         cprintf("%d ok\n", OK);
1016 }
1017
1018
1019
1020 /*****************************************************************************/
1021 /*                      MODULE INITIALIZATION STUFF                          */
1022 /*****************************************************************************/
1023
1024
1025 char *Dynamic_Module_Init(void)
1026 {
1027         SYM_SMTP = CtdlGetDynamicSymbol();
1028         SYM_SMTP_RECP = CtdlGetDynamicSymbol();
1029         CtdlRegisterServiceHook(SMTP_PORT,
1030                                 smtp_greeting,
1031                                 smtp_command_loop);
1032
1033         /****  FIX ... temporary hack to run the queue ******/
1034         CtdlRegisterProtoHook(cmd_qqqq, "QQQQ", "run the queue");  
1035
1036         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
1037         return "$Id$";
1038 }
1039