fb01d6329c5994ed929bf0125de068091ef14f6c
[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) {
681         char buf[256];
682         int sock = (-1);
683         char mxhosts[1024];
684         int num_mxhosts;
685         int mx;
686         char user[256], node[256], name[256];
687
688         /* Parse out the host portion of the recipient address */
689         process_rfc822_addr(addr, user, node, name);
690         lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
691                 user, node, name);
692
693         num_mxhosts = getmx(mxhosts, node);
694         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
695         if (num_mxhosts < 1) {
696                 *status = 5;
697                 sprintf(dsn, "No MX hosts found for <%s>", node);
698                 return;
699         }
700
701         for (mx=0; mx<num_mxhosts; ++mx) {
702                 extract(buf, mxhosts, mx);
703                 lprintf(9, "Trying <%s>\n", buf);
704                 sock = sock_connect(buf, "25", "tcp");
705                 sprintf(dsn, "Could not connect: %s", strerror(errno));
706                 if (sock >= 0) lprintf(9, "Connected!\n");
707                 if (sock < 0) sprintf(dsn, "%s", strerror(errno));
708                 if (sock >= 0) break;
709         }
710
711         if (sock < 0) {
712                 *status = 3;    /* dsn is already filled in */
713                 return;
714         }
715
716         /* Process the SMTP greeting from the server */
717         if (sock_gets(sock, buf) < 0) {
718                 *status = 3;
719                 strcpy(dsn, "Connection broken during SMTP conversation");
720                 sock_close(sock);
721                 return;
722         }
723         lprintf(9, "%s\n", buf);
724         if (buf[0] != '2') {
725                 if (buf[0] == '3') {
726                         *status = 3;
727                         strcpy(dsn, &buf[4]);
728                         sock_close(sock);
729                         return;
730                 }
731                 else {
732                         *status = 5;
733                         strcpy(dsn, &buf[4]);
734                         sock_close(sock);
735                         return;
736                 }
737         }
738
739         /* At this point we know we are talking to a real SMTP server */
740
741         /* Do a HELO command */
742         sprintf(buf, "HELO %s", config.c_fqdn);
743         sock_puts(sock, buf);
744         if (sock_gets(sock, buf) < 0) {
745                 *status = 3;
746                 strcpy(dsn, "Connection broken during SMTP conversation");
747                 sock_close(sock);
748                 return;
749         }
750         lprintf(9, "%s\n", buf);
751         if (buf[0] != '2') {
752                 if (buf[0] == '3') {
753                         *status = 3;
754                         strcpy(dsn, &buf[4]);
755                         sock_close(sock);
756                         return;
757                 }
758                 else {
759                         *status = 5;
760                         strcpy(dsn, &buf[4]);
761                         sock_close(sock);
762                         return;
763                 }
764         }
765
766         sock_puts(sock, "QUIT");
767         sock_gets(sock, buf);
768         lprintf(9, "%s\n", buf);
769         sock_close(sock);
770
771         /* temporary hook to make things not go kerblooie */
772         *status = 3;
773         strcpy(dsn, "smtp_try() is not finished yet");
774         /*                                                */
775 }
776
777
778
779
780 /*
781  * smtp_do_procmsg()
782  *
783  * Called by smtp_do_queue() to handle an individual message.
784  */
785 void smtp_do_procmsg(long msgnum) {
786         struct CtdlMessage *msg;
787         char *instr = NULL;
788         char *results = NULL;
789         int i;
790         int lines;
791         int status;
792         char buf[1024];
793         char key[1024];
794         char addr[1024];
795         char dsn[1024];
796         long text_msgid = (-1);
797
798         msg = CtdlFetchMessage(msgnum);
799         if (msg == NULL) {
800                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
801                 return;
802         }
803
804         instr = strdoop(msg->cm_fields['M']);
805         CtdlFreeMessage(msg);
806
807         /* Strip out the headers amd any other non-instruction line */
808         lines = num_tokens(instr, '\n');
809         for (i=0; i<lines; ++i) {
810                 extract_token(buf, instr, i, '\n');
811                 if (num_tokens(buf, '|') < 2) {
812                         lprintf(9, "removing <%s>\n", buf);
813                         remove_token(instr, i, '\n');
814                         --lines;
815                         --i;
816                 }
817         }
818
819         /* Learn the message ID */
820         lines = num_tokens(instr, '\n');
821         for (i=0; i<lines; ++i) {
822                 extract_token(buf, instr, i, '\n');
823                 extract(key, buf, 0);
824                 if (!strcasecmp(key, "msgid")) {
825                         text_msgid = extract_long(buf, 1);
826                 }
827         }
828
829         if (text_msgid < 0L) {
830                 lprintf(3, "SMTP: no 'msgid' directive found!\n", msgnum);
831                 phree(instr);
832                 return;
833         }
834
835         /* Plow through the instructions looking for 'remote' directives and
836          * a status of 0 (no delivery yet attempted) or 3 (transient errors
837          * were experienced and it's time to try again)
838          */
839         lines = num_tokens(instr, '\n');
840         for (i=0; i<lines; ++i) {
841                 extract_token(buf, instr, i, '\n');
842                 extract(key, buf, 0);
843                 extract(addr, buf, 1);
844                 status = extract_int(buf, 2);
845                 extract(dsn, buf, 3);
846                 if ( (!strcasecmp(key, "remote"))
847                    && ((status==0)||(status==3)) ) {
848                         remove_token(instr, i, '\n');
849                         --i;
850                         --lines;
851                         lprintf(9, "SMTP: Trying <%s>\n", addr);
852                         smtp_try(key, addr, &status, dsn);
853                         if (status != 2) {
854                                 if (results == NULL) {
855                                         results = mallok(1024);
856                                         memset(results, 0, 1024);
857                                 }
858                                 else {
859                                         results = reallok(results,
860                                                 strlen(results) + 1024);
861                                 }
862                                 sprintf(&results[strlen(results)],
863                                         "%s|%s|%d|%s\n",
864                                         key, addr, status, dsn);
865                         }
866                 }
867         }
868
869         if (results != NULL) {
870                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
871                 strcat(instr, results);
872                 phree(results);
873         }
874
875         /* Delete the instructions and replace with the updated ones */
876         CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, NULL);    
877         msg = mallok(sizeof(struct CtdlMessage));
878         memset(msg, 0, sizeof(struct CtdlMessage));
879         msg->cm_magic = CTDLMESSAGE_MAGIC;
880         msg->cm_anon_type = MES_NORMAL;
881         msg->cm_format_type = FMT_RFC822;
882         msg->cm_fields['M'] = malloc(strlen(instr)+256);
883         sprintf(msg->cm_fields['M'],
884                 "Content-type: %s\n\n%s\n", SPOOLMIME, instr);
885         phree(instr);
886         CtdlSaveMsg(msg, "", SMTP_SPOOLOUT_ROOM, MES_LOCAL, 1);
887         CtdlFreeMessage(msg);
888 }
889
890
891
892 /*
893  * smtp_do_queue()
894  * 
895  * Run through the queue sending out messages.
896  */
897 void smtp_do_queue(void) {
898         lprintf(5, "SMTP: processing outbound queue\n");
899
900         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
901                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
902                 return;
903         }
904         CtdlForEachMessage(MSGS_ALL, 0L, SPOOLMIME, NULL, smtp_do_procmsg);
905
906         lprintf(5, "SMTP: queue run completed\n");
907 }
908
909
910 /**** FIX  temporary hack to run the queue *****/
911 void cmd_qqqq(char *argbuf) {
912         smtp_do_queue();
913         cprintf("%d ok\n", OK);
914 }
915
916
917
918 /*****************************************************************************/
919 /*                      MODULE INITIALIZATION STUFF                          */
920 /*****************************************************************************/
921
922
923 char *Dynamic_Module_Init(void)
924 {
925         SYM_SMTP = CtdlGetDynamicSymbol();
926         SYM_SMTP_RECP = CtdlGetDynamicSymbol();
927         CtdlRegisterServiceHook(SMTP_PORT,
928                                 smtp_greeting,
929                                 smtp_command_loop);
930
931         /****  FIX ... temporary hack to run the queue ******/
932         CtdlRegisterProtoHook(cmd_qqqq, "QQQQ", "run the queue");  
933
934         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0);
935         return "$Id$";
936 }
937