]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* Suppress domain forgery check when using LMTP
[citadel.git] / citadel / serv_smtp.c
1 /*
2  * $Id$
3  *
4  * This module implements the following protocols for the Citadel system:
5  *
6  * RFC 821 (Simple Mail Transfer Protocol)
7  * RFC 1854 (command pipelining)
8  * RFC 1869 (Extended Simple Mail Transfer Protocol)
9  * RFC 2033 (Local Mail Transfer Protocol)
10  * RFC 2034 (enhanced status codes)
11  *
12  */
13
14 #include "sysdep.h"
15 #include <stdlib.h>
16 #include <unistd.h>
17 #include <stdio.h>
18 #include <fcntl.h>
19 #include <signal.h>
20 #include <pwd.h>
21 #include <errno.h>
22 #include <sys/types.h>
23
24 #if TIME_WITH_SYS_TIME
25 # include <sys/time.h>
26 # include <time.h>
27 #else
28 # if HAVE_SYS_TIME_H
29 #  include <sys/time.h>
30 # else
31 #  include <time.h>
32 # endif
33 #endif
34
35 #include <sys/wait.h>
36 #include <ctype.h>
37 #include <string.h>
38 #include <limits.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 #include "citadel.h"
43 #include "server.h"
44 #include "sysdep_decls.h"
45 #include "citserver.h"
46 #include "support.h"
47 #include "config.h"
48 #include "control.h"
49 #include "serv_extensions.h"
50 #include "room_ops.h"
51 #include "user_ops.h"
52 #include "policy.h"
53 #include "database.h"
54 #include "msgbase.h"
55 #include "tools.h"
56 #include "internet_addressing.h"
57 #include "genstamp.h"
58 #include "domain.h"
59 #include "clientsocket.h"
60 #include "locate_host.h"
61
62
63 #ifndef HAVE_SNPRINTF
64 #include "snprintf.h"
65 #endif
66
67 struct citsmtp {                /* Information about the current session */
68         int command_state;
69         char helo_node[SIZ];
70         struct ctdluser vrfy_buffer;
71         int vrfy_count;
72         char vrfy_match[SIZ];
73         char from[SIZ];
74         char recipients[SIZ];
75         int number_of_recipients;
76         int delivery_mode;
77         int message_originated_locally;
78         int is_lmtp;
79 };
80
81 enum {                          /* Command states for login authentication */
82         smtp_command,
83         smtp_user,
84         smtp_password
85 };
86
87 enum {                          /* Delivery modes */
88         smtp_deliver_local,
89         smtp_deliver_remote
90 };
91
92 #define SMTP            ((struct citsmtp *)CtdlGetUserData(SYM_SMTP))
93 #define SMTP_RECPS      ((char *)CtdlGetUserData(SYM_SMTP_RECPS))
94 #define SMTP_ROOMS      ((char *)CtdlGetUserData(SYM_SMTP_ROOMS))
95
96
97 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
98
99
100
101 /*****************************************************************************/
102 /*                      SMTP SERVER (INBOUND) STUFF                          */
103 /*****************************************************************************/
104
105
106
107
108 /*
109  * Here's where our SMTP session begins its happy day.
110  */
111 void smtp_greeting(void) {
112
113         strcpy(CC->cs_clientname, "SMTP session");
114         CC->internal_pgm = 1;
115         CC->cs_flags |= CS_STEALTH;
116         CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
117         CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
118         CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
119         snprintf(SMTP_RECPS, SIZ, "%s", "");
120         snprintf(SMTP_ROOMS, SIZ, "%s", "");
121
122         cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
123 }
124
125 /*
126  * LMTP is like SMTP but with some extra bonus footage added.
127  */
128 void lmtp_greeting(void) {
129         smtp_greeting();
130         SMTP->is_lmtp = 1;
131 }
132
133
134 /*
135  * Implement HELO and EHLO commands.
136  *
137  * which_command:  0=HELO, 1=EHLO, 2=LHLO
138  */
139 void smtp_hello(char *argbuf, int which_command) {
140
141         safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
142
143         if ( (which_command != 2) && (SMTP->is_lmtp) ) {
144                 cprintf("500 Only LHLO is allowed when running LMTP\r\n");
145                 return;
146         }
147
148         if ( (which_command == 2) && (SMTP->is_lmtp == 0) ) {
149                 cprintf("500 LHLO is only allowed when running LMTP\r\n");
150                 return;
151         }
152
153         if (which_command == 0) {
154                 cprintf("250 Greetings and joyous salutations.\r\n");
155         }
156         else {
157                 cprintf("250-Greetings and joyous salutations.\r\n");
158                 cprintf("250-HELP\r\n");
159                 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
160                 cprintf("250-PIPELINING\r\n");
161                 cprintf("250-AUTH=LOGIN\r\n");
162                 cprintf("250 ENHANCEDSTATUSCODES\r\n");
163         }
164 }
165
166
167 /*
168  * Implement HELP command.
169  */
170 void smtp_help(void) {
171         cprintf("214-Commands accepted:\r\n");
172         cprintf("214-    DATA\r\n");
173         cprintf("214-    EHLO\r\n");
174         cprintf("214-    EXPN\r\n");
175         cprintf("214-    HELO\r\n");
176         cprintf("214-    HELP\r\n");
177         cprintf("214-    MAIL\r\n");
178         cprintf("214-    NOOP\r\n");
179         cprintf("214-    QUIT\r\n");
180         cprintf("214-    RCPT\r\n");
181         cprintf("214-    RSET\r\n");
182         cprintf("214-    VRFY\r\n");
183         cprintf("214     \r\n");
184 }
185
186
187 /*
188  *
189  */
190 void smtp_get_user(char *argbuf) {
191         char buf[SIZ];
192         char username[SIZ];
193
194         CtdlDecodeBase64(username, argbuf, SIZ);
195         lprintf(9, "Trying <%s>\n", username);
196         if (CtdlLoginExistingUser(username) == login_ok) {
197                 CtdlEncodeBase64(buf, "Password:", 9);
198                 cprintf("334 %s\r\n", buf);
199                 SMTP->command_state = smtp_password;
200         }
201         else {
202                 cprintf("500 5.7.0 No such user.\r\n");
203                 SMTP->command_state = smtp_command;
204         }
205 }
206
207
208 /*
209  *
210  */
211 void smtp_get_pass(char *argbuf) {
212         char password[SIZ];
213
214         CtdlDecodeBase64(password, argbuf, SIZ);
215         lprintf(9, "Trying <%s>\n", password);
216         if (CtdlTryPassword(password) == pass_ok) {
217                 cprintf("235 2.0.0 Hello, %s\r\n", CC->user.fullname);
218                 lprintf(9, "SMTP authenticated login successful\n");
219                 CC->internal_pgm = 0;
220                 CC->cs_flags &= ~CS_STEALTH;
221         }
222         else {
223                 cprintf("500 5.7.0 Authentication failed.\r\n");
224         }
225         SMTP->command_state = smtp_command;
226 }
227
228
229 /*
230  *
231  */
232 void smtp_auth(char *argbuf) {
233         char buf[SIZ];
234
235         if (strncasecmp(argbuf, "login", 5) ) {
236                 cprintf("550 5.7.4 We only support LOGIN authentication.\r\n");
237                 return;
238         }
239
240         if (strlen(argbuf) >= 7) {
241                 smtp_get_user(&argbuf[6]);
242         }
243
244         else {
245                 CtdlEncodeBase64(buf, "Username:", 9);
246                 cprintf("334 %s\r\n", buf);
247                 SMTP->command_state = smtp_user;
248         }
249 }
250
251
252 /*
253  * Back end for smtp_vrfy() command
254  */
255 void smtp_vrfy_backend(struct ctdluser *us, void *data) {
256
257         if (!fuzzy_match(us, SMTP->vrfy_match)) {
258                 ++SMTP->vrfy_count;
259                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
260         }
261 }
262
263
264 /* 
265  * Implements the VRFY (verify user name) command.
266  * Performs fuzzy match on full user names.
267  */
268 void smtp_vrfy(char *argbuf) {
269         SMTP->vrfy_count = 0;
270         strcpy(SMTP->vrfy_match, argbuf);
271         ForEachUser(smtp_vrfy_backend, NULL);
272
273         if (SMTP->vrfy_count < 1) {
274                 cprintf("550 5.1.1 String does not match anything.\r\n");
275         }
276         else if (SMTP->vrfy_count == 1) {
277                 cprintf("250 %s <cit%ld@%s>\r\n",
278                         SMTP->vrfy_buffer.fullname,
279                         SMTP->vrfy_buffer.usernum,
280                         config.c_fqdn);
281         }
282         else if (SMTP->vrfy_count > 1) {
283                 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
284                         SMTP->vrfy_count);
285         }
286
287 }
288
289
290
291 /*
292  * Back end for smtp_expn() command
293  */
294 void smtp_expn_backend(struct ctdluser *us, void *data) {
295
296         if (!fuzzy_match(us, SMTP->vrfy_match)) {
297
298                 if (SMTP->vrfy_count >= 1) {
299                         cprintf("250-%s <cit%ld@%s>\r\n",
300                                 SMTP->vrfy_buffer.fullname,
301                                 SMTP->vrfy_buffer.usernum,
302                                 config.c_fqdn);
303                 }
304
305                 ++SMTP->vrfy_count;
306                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct ctdluser));
307         }
308 }
309
310
311 /* 
312  * Implements the EXPN (expand user name) command.
313  * Performs fuzzy match on full user names.
314  */
315 void smtp_expn(char *argbuf) {
316         SMTP->vrfy_count = 0;
317         strcpy(SMTP->vrfy_match, argbuf);
318         ForEachUser(smtp_expn_backend, NULL);
319
320         if (SMTP->vrfy_count < 1) {
321                 cprintf("550 5.1.1 String does not match anything.\r\n");
322         }
323         else if (SMTP->vrfy_count >= 1) {
324                 cprintf("250 %s <cit%ld@%s>\r\n",
325                         SMTP->vrfy_buffer.fullname,
326                         SMTP->vrfy_buffer.usernum,
327                         config.c_fqdn);
328         }
329 }
330
331
332 /*
333  * Implements the RSET (reset state) command.
334  * Currently this just zeroes out the state buffer.  If pointers to data
335  * allocated with mallok() are ever placed in the state buffer, we have to
336  * be sure to phree() them first!
337  */
338 void smtp_rset(void) {
339         memset(SMTP, 0, sizeof(struct citsmtp));
340
341         /*
342          * It is somewhat ambiguous whether we want to log out when a RSET
343          * command is issued.  Here's the code to do it.  It is commented out
344          * because some clients (such as Pine) issue RSET commands before
345          * each message, but still expect to be logged in.
346          *
347          * if (CC->logged_in) {
348          *      logout(CC);
349          * }
350          */
351
352         cprintf("250 2.0.0 Zap!\r\n");
353 }
354
355 /*
356  * Clear out the portions of the state buffer that need to be cleared out
357  * after the DATA command finishes.
358  */
359 void smtp_data_clear(void) {
360         strcpy(SMTP->from, "");
361         strcpy(SMTP->recipients, "");
362         SMTP->number_of_recipients = 0;
363         SMTP->delivery_mode = 0;
364         SMTP->message_originated_locally = 0;
365 }
366
367
368
369 /*
370  * Implements the "MAIL From:" command
371  */
372 void smtp_mail(char *argbuf) {
373         char user[SIZ];
374         char node[SIZ];
375         char name[SIZ];
376
377         if (strlen(SMTP->from) != 0) {
378                 cprintf("503 5.1.0 Only one sender permitted\r\n");
379                 return;
380         }
381
382         if (strncasecmp(argbuf, "From:", 5)) {
383                 cprintf("501 5.1.7 Syntax error\r\n");
384                 return;
385         }
386
387         strcpy(SMTP->from, &argbuf[5]);
388         striplt(SMTP->from);
389         stripallbut(SMTP->from, '<', '>');
390
391         /* We used to reject empty sender names, until it was brought to our
392          * attention that RFC1123 5.2.9 requires that this be allowed.  So now
393          * we allow it, but replace the empty string with a fake
394          * address so we don't have to contend with the empty string causing
395          * other code to fail when it's expecting something there.
396          */
397         if (strlen(SMTP->from) == 0) {
398                 strcpy(SMTP->from, "someone@somewhere.org");
399         }
400
401         /* If this SMTP connection is from a logged-in user, force the 'from'
402          * to be the user's Internet e-mail address as Citadel knows it.
403          */
404         if (CC->logged_in) {
405                 strcpy(SMTP->from, CC->cs_inet_email);
406                 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
407                 SMTP->message_originated_locally = 1;
408                 return;
409         }
410
411         else if (SMTP->is_lmtp) {
412                 /* Bypass forgery checking for LMTP */
413         }
414
415         /* Otherwise, make sure outsiders aren't trying to forge mail from
416          * this system.
417          */
418         else {
419                 process_rfc822_addr(SMTP->from, user, node, name);
420                 if (CtdlHostAlias(node) != hostalias_nomatch) {
421                         cprintf("550 5.1.8 "
422                                 "You must log in to send mail from %s\r\n",
423                                 node);
424                         strcpy(SMTP->from, "");
425                         return;
426                 }
427         }
428
429         cprintf("250 2.0.0 Sender ok\r\n");
430 }
431
432
433
434 /*
435  * Implements the "RCPT To:" command
436  */
437 void smtp_rcpt(char *argbuf) {
438         char recp[SIZ];
439         char message_to_spammer[SIZ];
440         struct recptypes *valid = NULL;
441
442         if (strlen(SMTP->from) == 0) {
443                 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
444                 return;
445         }
446
447         if (strncasecmp(argbuf, "To:", 3)) {
448                 cprintf("501 5.1.7 Syntax error\r\n");
449                 return;
450         }
451
452         strcpy(recp, &argbuf[3]);
453         striplt(recp);
454         stripallbut(recp, '<', '>');
455
456         if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
457                 cprintf("452 4.5.3 Too many recipients\r\n");
458                 return;
459         }
460
461         /* RBL check */
462         if ( (!CC->logged_in)
463            && (!SMTP->is_lmtp) ) {
464                 if (rbl_check(message_to_spammer)) {
465                         cprintf("550 %s\r\n", message_to_spammer);
466                         /* no need to phree(valid), it's not allocated yet */
467                         return;
468                 }
469         }
470
471         valid = validate_recipients(recp);
472         if (valid->num_error > 0) {
473                 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
474                 phree(valid);
475                 return;
476         }
477
478         if (valid->num_internet > 0) {
479                 if ( (SMTP->message_originated_locally == 0)
480                    && (SMTP->is_lmtp == 0) ) {
481                         cprintf("551 5.7.1 <%s> - relaying denied\r\n", recp);
482                         phree(valid);
483                         return;
484                 }
485         }
486
487         cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
488         if (strlen(SMTP->recipients) > 0) {
489                 strcat(SMTP->recipients, ",");
490         }
491         strcat(SMTP->recipients, recp);
492         SMTP->number_of_recipients += 1;
493 }
494
495
496
497
498 /*
499  * Implements the DATA command
500  */
501 void smtp_data(void) {
502         char *body;
503         struct CtdlMessage *msg;
504         long msgnum;
505         char nowstamp[SIZ];
506         struct recptypes *valid;
507         int scan_errors;
508         int i;
509         char result[SIZ];
510
511         if (strlen(SMTP->from) == 0) {
512                 cprintf("503 5.5.1 Need MAIL command first.\r\n");
513                 return;
514         }
515
516         if (SMTP->number_of_recipients < 1) {
517                 cprintf("503 5.5.1 Need RCPT command first.\r\n");
518                 return;
519         }
520
521         cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
522         
523         datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
524         body = mallok(4096);
525
526         if (body != NULL) snprintf(body, 4096,
527                 "Received: from %s (%s [%s])\n"
528                 "       by %s; %s\n",
529                         SMTP->helo_node,
530                         CC->cs_host,
531                         CC->cs_addr,
532                         config.c_fqdn,
533                         nowstamp);
534         
535         body = CtdlReadMessageBody(".", config.c_maxmsglen, body, 1);
536         if (body == NULL) {
537                 cprintf("550 5.6.5 "
538                         "Unable to save message: internal error.\r\n");
539                 return;
540         }
541
542         lprintf(9, "Converting message...\n");
543         msg = convert_internet_message(body);
544
545         /* If the user is locally authenticated, FORCE the From: header to
546          * show up as the real sender.  Yes, this violates the RFC standard,
547          * but IT MAKES SENSE.  If you prefer strict RFC adherence over
548          * common sense, you can disable this in the configuration.
549          *
550          * We also set the "message room name" ('O' field) to MAILROOM
551          * (which is Mail> on most systems) to prevent it from getting set
552          * to something ugly like "0000058008.Sent Items>" when the message
553          * is read with a Citadel client.
554          */
555         if ( (CC->logged_in) && (config.c_rfc822_strict_from == 0) ) {
556                 if (msg->cm_fields['A'] != NULL) phree(msg->cm_fields['A']);
557                 if (msg->cm_fields['N'] != NULL) phree(msg->cm_fields['N']);
558                 if (msg->cm_fields['H'] != NULL) phree(msg->cm_fields['H']);
559                 if (msg->cm_fields['F'] != NULL) phree(msg->cm_fields['F']);
560                 if (msg->cm_fields['O'] != NULL) phree(msg->cm_fields['O']);
561                 msg->cm_fields['A'] = strdoop(CC->user.fullname);
562                 msg->cm_fields['N'] = strdoop(config.c_nodename);
563                 msg->cm_fields['H'] = strdoop(config.c_humannode);
564                 msg->cm_fields['F'] = strdoop(CC->cs_inet_email);
565                 msg->cm_fields['O'] = strdoop(MAILROOM);
566         }
567
568         /* Submit the message into the Citadel system. */
569         valid = validate_recipients(SMTP->recipients);
570
571         /* If there are modules that want to scan this message before final
572          * submission (such as virus checkers or spam filters), call them now
573          * and give them an opportunity to reject the message.
574          */
575         scan_errors = PerformMessageHooks(msg, EVT_SMTPSCAN);
576
577         if (scan_errors > 0) {  /* We don't want this message! */
578
579                 if (msg->cm_fields['0'] == NULL) {
580                         msg->cm_fields['0'] = strdoop(
581                                 "5.7.1 Message rejected by filter");
582                 }
583
584                 sprintf(result, "550 %s\r\n", msg->cm_fields['0']);
585         }
586         
587         else {                  /* Ok, we'll accept this message. */
588                 msgnum = CtdlSubmitMsg(msg, valid, "");
589                 if (msgnum > 0L) {
590                         sprintf(result, "250 2.0.0 Message accepted.\r\n");
591                 }
592                 else {
593                         sprintf(result, "550 5.5.0 Internal delivery error\r\n");
594                 }
595         }
596
597         /* For SMTP and ESTMP, just print the result message.  For LMTP, we
598          * have to print one result message for each recipient.  Since there
599          * is nothing in Citadel which would cause different recipients to
600          * have different results, we can get away with just spitting out the
601          * same message once for each recipient.
602          */
603         if (SMTP->is_lmtp) {
604                 for (i=0; i<SMTP->number_of_recipients; ++i) {
605                         cprintf("%s", result);
606                 }
607         }
608         else {
609                 cprintf("%s", result);
610         }
611
612         CtdlFreeMessage(msg);
613         phree(valid);
614         smtp_data_clear();      /* clear out the buffers now */
615 }
616
617
618
619
620 /* 
621  * Main command loop for SMTP sessions.
622  */
623 void smtp_command_loop(void) {
624         char cmdbuf[SIZ];
625
626         time(&CC->lastcmd);
627         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
628         if (client_gets(cmdbuf) < 1) {
629                 lprintf(3, "SMTP socket is broken.  Ending session.\n");
630                 CC->kill_me = 1;
631                 return;
632         }
633         lprintf(5, "SMTP: %s\n", cmdbuf);
634         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
635
636         lprintf(9, "CC->logged_in = %d\n", CC->logged_in);
637
638         if (SMTP->command_state == smtp_user) {
639                 smtp_get_user(cmdbuf);
640         }
641
642         else if (SMTP->command_state == smtp_password) {
643                 smtp_get_pass(cmdbuf);
644         }
645
646         else if (!strncasecmp(cmdbuf, "AUTH", 4)) {
647                 smtp_auth(&cmdbuf[5]);
648         }
649
650         else if (!strncasecmp(cmdbuf, "DATA", 4)) {
651                 smtp_data();
652         }
653
654         else if (!strncasecmp(cmdbuf, "EXPN", 4)) {
655                 smtp_expn(&cmdbuf[5]);
656         }
657
658         else if (!strncasecmp(cmdbuf, "HELO", 4)) {
659                 smtp_hello(&cmdbuf[5], 0);
660         }
661
662         else if (!strncasecmp(cmdbuf, "EHLO", 4)) {
663                 smtp_hello(&cmdbuf[5], 1);
664         }
665
666         else if (!strncasecmp(cmdbuf, "LHLO", 4)) {
667                 smtp_hello(&cmdbuf[5], 2);
668         }
669
670         else if (!strncasecmp(cmdbuf, "HELP", 4)) {
671                 smtp_help();
672         }
673
674         else if (!strncasecmp(cmdbuf, "MAIL", 4)) {
675                 smtp_mail(&cmdbuf[5]);
676         }
677
678         else if (!strncasecmp(cmdbuf, "NOOP", 4)) {
679                 cprintf("250 NOOP\r\n");
680         }
681
682         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
683                 cprintf("221 Goodbye...\r\n");
684                 CC->kill_me = 1;
685                 return;
686         }
687
688         else if (!strncasecmp(cmdbuf, "RCPT", 4)) {
689                 smtp_rcpt(&cmdbuf[5]);
690         }
691
692         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
693                 smtp_rset();
694         }
695
696         else if (!strncasecmp(cmdbuf, "VRFY", 4)) {
697                 smtp_vrfy(&cmdbuf[5]);
698         }
699
700         else {
701                 cprintf("502 5.0.0 I'm afraid I can't do that.\r\n");
702         }
703
704 }
705
706
707
708
709 /*****************************************************************************/
710 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
711 /*****************************************************************************/
712
713
714
715 /*
716  * smtp_try()
717  *
718  * Called by smtp_do_procmsg() to attempt delivery to one SMTP host
719  *
720  */
721 void smtp_try(const char *key, const char *addr, int *status,
722               char *dsn, size_t n, long msgnum)
723 {
724         int sock = (-1);
725         char mxhosts[1024];
726         int num_mxhosts;
727         int mx;
728         int i;
729         char user[SIZ], node[SIZ], name[SIZ];
730         char buf[1024];
731         char mailfrom[1024];
732         int lp, rp;
733         FILE *msg_fp = NULL;
734         size_t msg_size;
735         size_t blocksize = 0;
736         int scan_done;
737
738         /* Parse out the host portion of the recipient address */
739         process_rfc822_addr(addr, user, node, name);
740
741         lprintf(9, "Attempting SMTP delivery to <%s> @ <%s> (%s)\n",
742                 user, node, name);
743
744         /* Load the message out of the database into a temp file */
745         msg_fp = tmpfile();
746         if (msg_fp == NULL) {
747                 *status = 4;
748                 snprintf(dsn, n, "Error creating temporary file");
749                 return;
750         }
751         else {
752                 CtdlRedirectOutput(msg_fp, -1);
753                 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
754                 CtdlRedirectOutput(NULL, -1);
755                 fseek(msg_fp, 0L, SEEK_END);
756                 msg_size = ftell(msg_fp);
757         }
758
759
760         /* Extract something to send later in the 'MAIL From:' command */
761         strcpy(mailfrom, "");
762         rewind(msg_fp);
763         scan_done = 0;
764         do {
765                 if (fgets(buf, sizeof buf, msg_fp)==NULL) scan_done = 1;
766                 if (!strncasecmp(buf, "From:", 5)) {
767                         safestrncpy(mailfrom, &buf[5], sizeof mailfrom);
768                         striplt(mailfrom);
769                         for (i=0; i<strlen(mailfrom); ++i) {
770                                 if (!isprint(mailfrom[i])) {
771                                         strcpy(&mailfrom[i], &mailfrom[i+1]);
772                                         i=0;
773                                 }
774                         }
775
776                         /* Strip out parenthesized names */
777                         lp = (-1);
778                         rp = (-1);
779                         for (i=0; i<strlen(mailfrom); ++i) {
780                                 if (mailfrom[i] == '(') lp = i;
781                                 if (mailfrom[i] == ')') rp = i;
782                         }
783                         if ((lp>0)&&(rp>lp)) {
784                                 strcpy(&mailfrom[lp-1], &mailfrom[rp+1]);
785                         }
786
787                         /* Prefer brokketized names */
788                         lp = (-1);
789                         rp = (-1);
790                         for (i=0; i<strlen(mailfrom); ++i) {
791                                 if (mailfrom[i] == '<') lp = i;
792                                 if (mailfrom[i] == '>') rp = i;
793                         }
794                         if ( (lp>=0) && (rp>lp) ) {
795                                 mailfrom[rp] = 0;
796                                 strcpy(mailfrom, &mailfrom[lp]);
797                         }
798
799                         scan_done = 1;
800                 }
801         } while (scan_done == 0);
802         if (strlen(mailfrom)==0) strcpy(mailfrom, "someone@somewhere.org");
803
804         /* Figure out what mail exchanger host we have to connect to */
805         num_mxhosts = getmx(mxhosts, node);
806         lprintf(9, "Number of MX hosts for <%s> is %d\n", node, num_mxhosts);
807         if (num_mxhosts < 1) {
808                 *status = 5;
809                 snprintf(dsn, SIZ, "No MX hosts found for <%s>", node);
810                 return;
811         }
812
813         sock = (-1);
814         for (mx=0; (mx<num_mxhosts && sock < 0); ++mx) {
815                 extract(buf, mxhosts, mx);
816                 lprintf(9, "Trying <%s>\n", buf);
817                 sock = sock_connect(buf, "25", "tcp");
818                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
819                 if (sock >= 0) lprintf(9, "Connected!\n");
820                 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
821         }
822
823         if (sock < 0) {
824                 *status = 4;    /* dsn is already filled in */
825                 return;
826         }
827
828         /* Process the SMTP greeting from the server */
829         if (ml_sock_gets(sock, buf) < 0) {
830                 *status = 4;
831                 strcpy(dsn, "Connection broken during SMTP conversation");
832                 goto bail;
833         }
834         lprintf(9, "<%s\n", buf);
835         if (buf[0] != '2') {
836                 if (buf[0] == '4') {
837                         *status = 4;
838                         safestrncpy(dsn, &buf[4], 1023);
839                         goto bail;
840                 }
841                 else {
842                         *status = 5;
843                         safestrncpy(dsn, &buf[4], 1023);
844                         goto bail;
845                 }
846         }
847
848         /* At this point we know we are talking to a real SMTP server */
849
850         /* Do a HELO command */
851         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
852         lprintf(9, ">%s", buf);
853         sock_write(sock, buf, strlen(buf));
854         if (ml_sock_gets(sock, buf) < 0) {
855                 *status = 4;
856                 strcpy(dsn, "Connection broken during SMTP HELO");
857                 goto bail;
858         }
859         lprintf(9, "<%s\n", buf);
860         if (buf[0] != '2') {
861                 if (buf[0] == '4') {
862                         *status = 4;
863                         safestrncpy(dsn, &buf[4], 1023);
864                         goto bail;
865                 }
866                 else {
867                         *status = 5;
868                         safestrncpy(dsn, &buf[4], 1023);
869                         goto bail;
870                 }
871         }
872
873
874         /* HELO succeeded, now try the MAIL From: command */
875         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
876         lprintf(9, ">%s", buf);
877         sock_write(sock, buf, strlen(buf));
878         if (ml_sock_gets(sock, buf) < 0) {
879                 *status = 4;
880                 strcpy(dsn, "Connection broken during SMTP MAIL");
881                 goto bail;
882         }
883         lprintf(9, "<%s\n", buf);
884         if (buf[0] != '2') {
885                 if (buf[0] == '4') {
886                         *status = 4;
887                         safestrncpy(dsn, &buf[4], 1023);
888                         goto bail;
889                 }
890                 else {
891                         *status = 5;
892                         safestrncpy(dsn, &buf[4], 1023);
893                         goto bail;
894                 }
895         }
896
897
898         /* MAIL succeeded, now try the RCPT To: command */
899         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
900         lprintf(9, ">%s", buf);
901         sock_write(sock, buf, strlen(buf));
902         if (ml_sock_gets(sock, buf) < 0) {
903                 *status = 4;
904                 strcpy(dsn, "Connection broken during SMTP RCPT");
905                 goto bail;
906         }
907         lprintf(9, "<%s\n", buf);
908         if (buf[0] != '2') {
909                 if (buf[0] == '4') {
910                         *status = 4;
911                         safestrncpy(dsn, &buf[4], 1023);
912                         goto bail;
913                 }
914                 else {
915                         *status = 5;
916                         safestrncpy(dsn, &buf[4], 1023);
917                         goto bail;
918                 }
919         }
920
921
922         /* RCPT succeeded, now try the DATA command */
923         lprintf(9, ">DATA\n");
924         sock_write(sock, "DATA\r\n", 6);
925         if (ml_sock_gets(sock, buf) < 0) {
926                 *status = 4;
927                 strcpy(dsn, "Connection broken during SMTP DATA");
928                 goto bail;
929         }
930         lprintf(9, "<%s\n", buf);
931         if (buf[0] != '3') {
932                 if (buf[0] == '4') {
933                         *status = 3;
934                         safestrncpy(dsn, &buf[4], 1023);
935                         goto bail;
936                 }
937                 else {
938                         *status = 5;
939                         safestrncpy(dsn, &buf[4], 1023);
940                         goto bail;
941                 }
942         }
943
944         /* If we reach this point, the server is expecting data */
945         rewind(msg_fp);
946         while (msg_size > 0) {
947                 blocksize = sizeof(buf);
948                 if (blocksize > msg_size) blocksize = msg_size;
949                 fread(buf, blocksize, 1, msg_fp);
950                 sock_write(sock, buf, blocksize);
951                 msg_size -= blocksize;
952         }
953         if (buf[blocksize-1] != 10) {
954                 lprintf(5, "Possible problem: message did not correctly "
955                         "terminate. (expecting 0x10, got 0x%02x)\n",
956                                 buf[blocksize-1]);
957         }
958
959         sock_write(sock, ".\r\n", 3);
960         if (ml_sock_gets(sock, buf) < 0) {
961                 *status = 4;
962                 strcpy(dsn, "Connection broken during SMTP message transmit");
963                 goto bail;
964         }
965         lprintf(9, "%s\n", buf);
966         if (buf[0] != '2') {
967                 if (buf[0] == '4') {
968                         *status = 4;
969                         safestrncpy(dsn, &buf[4], 1023);
970                         goto bail;
971                 }
972                 else {
973                         *status = 5;
974                         safestrncpy(dsn, &buf[4], 1023);
975                         goto bail;
976                 }
977         }
978
979         /* We did it! */
980         safestrncpy(dsn, &buf[4], 1023);
981         *status = 2;
982
983         lprintf(9, ">QUIT\n");
984         sock_write(sock, "QUIT\r\n", 6);
985         ml_sock_gets(sock, buf);
986         lprintf(9, "<%s\n", buf);
987
988 bail:   if (msg_fp != NULL) fclose(msg_fp);
989         sock_close(sock);
990         return;
991 }
992
993
994
995 /*
996  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
997  * instructions for "5" codes (permanent fatal errors) and produce/deliver
998  * a "bounce" message (delivery status notification).
999  */
1000 void smtp_do_bounce(char *instr) {
1001         int i;
1002         int lines;
1003         int status;
1004         char buf[1024];
1005         char key[1024];
1006         char addr[1024];
1007         char dsn[1024];
1008         char bounceto[1024];
1009         int num_bounces = 0;
1010         int bounce_this = 0;
1011         long bounce_msgid = (-1);
1012         time_t submitted = 0L;
1013         struct CtdlMessage *bmsg = NULL;
1014         int give_up = 0;
1015         struct recptypes *valid;
1016         int successful_bounce = 0;
1017
1018         lprintf(9, "smtp_do_bounce() called\n");
1019         strcpy(bounceto, "");
1020
1021         lines = num_tokens(instr, '\n');
1022
1023
1024         /* See if it's time to give up on delivery of this message */
1025         for (i=0; i<lines; ++i) {
1026                 extract_token(buf, instr, i, '\n');
1027                 extract(key, buf, 0);
1028                 extract(addr, buf, 1);
1029                 if (!strcasecmp(key, "submitted")) {
1030                         submitted = atol(addr);
1031                 }
1032         }
1033
1034         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
1035                 give_up = 1;
1036         }
1037
1038
1039
1040         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
1041         if (bmsg == NULL) return;
1042         memset(bmsg, 0, sizeof(struct CtdlMessage));
1043
1044         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
1045         bmsg->cm_anon_type = MES_NORMAL;
1046         bmsg->cm_format_type = 1;
1047         bmsg->cm_fields['A'] = strdoop("Citadel");
1048         bmsg->cm_fields['O'] = strdoop(MAILROOM);
1049         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
1050
1051         if (give_up) bmsg->cm_fields['M'] = strdoop(
1052 "A message you sent could not be delivered to some or all of its recipients\n"
1053 "due to prolonged unavailability of its destination(s).\n"
1054 "Giving up on the following addresses:\n\n"
1055 );
1056
1057         else bmsg->cm_fields['M'] = strdoop(
1058 "A message you sent could not be delivered to some or all of its recipients.\n"
1059 "The following addresses were undeliverable:\n\n"
1060 );
1061
1062         /*
1063          * Now go through the instructions checking for stuff.
1064          */
1065         for (i=0; i<lines; ++i) {
1066                 extract_token(buf, instr, i, '\n');
1067                 extract(key, buf, 0);
1068                 extract(addr, buf, 1);
1069                 status = extract_int(buf, 2);
1070                 extract(dsn, buf, 3);
1071                 bounce_this = 0;
1072
1073                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1074                         key, addr, status, dsn);
1075
1076                 if (!strcasecmp(key, "bounceto")) {
1077                         strcpy(bounceto, addr);
1078                 }
1079
1080                 if (
1081                    (!strcasecmp(key, "local"))
1082                    || (!strcasecmp(key, "remote"))
1083                    || (!strcasecmp(key, "ignet"))
1084                    || (!strcasecmp(key, "room"))
1085                 ) {
1086                         if (status == 5) bounce_this = 1;
1087                         if (give_up) bounce_this = 1;
1088                 }
1089
1090                 if (bounce_this) {
1091                         ++num_bounces;
1092
1093                         if (bmsg->cm_fields['M'] == NULL) {
1094                                 lprintf(2, "ERROR ... M field is null "
1095                                         "(%s:%d)\n", __FILE__, __LINE__);
1096                         }
1097
1098                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1099                                 strlen(bmsg->cm_fields['M']) + 1024 );
1100                         strcat(bmsg->cm_fields['M'], addr);
1101                         strcat(bmsg->cm_fields['M'], ": ");
1102                         strcat(bmsg->cm_fields['M'], dsn);
1103                         strcat(bmsg->cm_fields['M'], "\n");
1104
1105                         remove_token(instr, i, '\n');
1106                         --i;
1107                         --lines;
1108                 }
1109         }
1110
1111         /* Deliver the bounce if there's anything worth mentioning */
1112         lprintf(9, "num_bounces = %d\n", num_bounces);
1113         if (num_bounces > 0) {
1114
1115                 /* First try the user who sent the message */
1116                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1117                 if (strlen(bounceto) == 0) {
1118                         lprintf(7, "No bounce address specified\n");
1119                         bounce_msgid = (-1L);
1120                 }
1121
1122                 /* Can we deliver the bounce to the original sender? */
1123                 valid = validate_recipients(bounceto);
1124                 if (valid != NULL) {
1125                         if (valid->num_error == 0) {
1126                                 CtdlSubmitMsg(bmsg, valid, "");
1127                                 successful_bounce = 1;
1128                         }
1129                 }
1130
1131                 /* If not, post it in the Aide> room */
1132                 if (successful_bounce == 0) {
1133                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1134                 }
1135
1136                 /* Free up the memory we used */
1137                 if (valid != NULL) {
1138                         phree(valid);
1139                 }
1140         }
1141
1142         CtdlFreeMessage(bmsg);
1143         lprintf(9, "Done processing bounces\n");
1144 }
1145
1146
1147 /*
1148  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1149  * set of delivery instructions for completed deliveries and remove them.
1150  *
1151  * It returns the number of incomplete deliveries remaining.
1152  */
1153 int smtp_purge_completed_deliveries(char *instr) {
1154         int i;
1155         int lines;
1156         int status;
1157         char buf[1024];
1158         char key[1024];
1159         char addr[1024];
1160         char dsn[1024];
1161         int completed;
1162         int incomplete = 0;
1163
1164         lines = num_tokens(instr, '\n');
1165         for (i=0; i<lines; ++i) {
1166                 extract_token(buf, instr, i, '\n');
1167                 extract(key, buf, 0);
1168                 extract(addr, buf, 1);
1169                 status = extract_int(buf, 2);
1170                 extract(dsn, buf, 3);
1171
1172                 completed = 0;
1173
1174                 if (
1175                    (!strcasecmp(key, "local"))
1176                    || (!strcasecmp(key, "remote"))
1177                    || (!strcasecmp(key, "ignet"))
1178                    || (!strcasecmp(key, "room"))
1179                 ) {
1180                         if (status == 2) completed = 1;
1181                         else ++incomplete;
1182                 }
1183
1184                 if (completed) {
1185                         remove_token(instr, i, '\n');
1186                         --i;
1187                         --lines;
1188                 }
1189         }
1190
1191         return(incomplete);
1192 }
1193
1194
1195 /*
1196  * smtp_do_procmsg()
1197  *
1198  * Called by smtp_do_queue() to handle an individual message.
1199  */
1200 void smtp_do_procmsg(long msgnum, void *userdata) {
1201         struct CtdlMessage *msg;
1202         char *instr = NULL;
1203         char *results = NULL;
1204         int i;
1205         int lines;
1206         int status;
1207         char buf[1024];
1208         char key[1024];
1209         char addr[1024];
1210         char dsn[1024];
1211         long text_msgid = (-1);
1212         int incomplete_deliveries_remaining;
1213         time_t attempted = 0L;
1214         time_t last_attempted = 0L;
1215         time_t retry = SMTP_RETRY_INTERVAL;
1216
1217         lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1218
1219         msg = CtdlFetchMessage(msgnum);
1220         if (msg == NULL) {
1221                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1222                 return;
1223         }
1224
1225         instr = strdoop(msg->cm_fields['M']);
1226         CtdlFreeMessage(msg);
1227
1228         /* Strip out the headers amd any other non-instruction line */
1229         lines = num_tokens(instr, '\n');
1230         for (i=0; i<lines; ++i) {
1231                 extract_token(buf, instr, i, '\n');
1232                 if (num_tokens(buf, '|') < 2) {
1233                         remove_token(instr, i, '\n');
1234                         --lines;
1235                         --i;
1236                 }
1237         }
1238
1239         /* Learn the message ID and find out about recent delivery attempts */
1240         lines = num_tokens(instr, '\n');
1241         for (i=0; i<lines; ++i) {
1242                 extract_token(buf, instr, i, '\n');
1243                 extract(key, buf, 0);
1244                 if (!strcasecmp(key, "msgid")) {
1245                         text_msgid = extract_long(buf, 1);
1246                 }
1247                 if (!strcasecmp(key, "retry")) {
1248                         /* double the retry interval after each attempt */
1249                         retry = extract_long(buf, 1) * 2L;
1250                         if (retry > SMTP_RETRY_MAX) {
1251                                 retry = SMTP_RETRY_MAX;
1252                         }
1253                         remove_token(instr, i, '\n');
1254                 }
1255                 if (!strcasecmp(key, "attempted")) {
1256                         attempted = extract_long(buf, 1);
1257                         if (attempted > last_attempted)
1258                                 last_attempted = attempted;
1259                 }
1260         }
1261
1262         /*
1263          * Postpone delivery if we've already tried recently.
1264          */
1265         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1266                 lprintf(7, "Retry time not yet reached.\n");
1267                 phree(instr);
1268                 return;
1269         }
1270
1271
1272         /*
1273          * Bail out if there's no actual message associated with this
1274          */
1275         if (text_msgid < 0L) {
1276                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1277                 phree(instr);
1278                 return;
1279         }
1280
1281         /* Plow through the instructions looking for 'remote' directives and
1282          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1283          * were experienced and it's time to try again)
1284          */
1285         lines = num_tokens(instr, '\n');
1286         for (i=0; i<lines; ++i) {
1287                 extract_token(buf, instr, i, '\n');
1288                 extract(key, buf, 0);
1289                 extract(addr, buf, 1);
1290                 status = extract_int(buf, 2);
1291                 extract(dsn, buf, 3);
1292                 if ( (!strcasecmp(key, "remote"))
1293                    && ((status==0)||(status==3)||(status==4)) ) {
1294
1295                         /* Remove this "remote" instruction from the set,
1296                          * but replace the set's final newline if
1297                          * remove_token() stripped it.  It has to be there.
1298                          */
1299                         remove_token(instr, i, '\n');
1300                         if (instr[strlen(instr)-1] != '\n') {
1301                                 strcat(instr, "\n");
1302                         }
1303
1304                         --i;
1305                         --lines;
1306                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1307                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1308                         if (status != 2) {
1309                                 if (results == NULL) {
1310                                         results = mallok(1024);
1311                                         memset(results, 0, 1024);
1312                                 }
1313                                 else {
1314                                         results = reallok(results,
1315                                                 strlen(results) + 1024);
1316                                 }
1317                                 snprintf(&results[strlen(results)], 1024,
1318                                         "%s|%s|%d|%s\n",
1319                                         key, addr, status, dsn);
1320                         }
1321                 }
1322         }
1323
1324         if (results != NULL) {
1325                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1326                 strcat(instr, results);
1327                 phree(results);
1328         }
1329
1330
1331         /* Generate 'bounce' messages */
1332         smtp_do_bounce(instr);
1333
1334         /* Go through the delivery list, deleting completed deliveries */
1335         incomplete_deliveries_remaining = 
1336                 smtp_purge_completed_deliveries(instr);
1337
1338
1339         /*
1340          * No delivery instructions remain, so delete both the instructions
1341          * message and the message message.
1342          */
1343         if (incomplete_deliveries_remaining <= 0) {
1344                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1345                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1346         }
1347
1348
1349         /*
1350          * Uncompleted delivery instructions remain, so delete the old
1351          * instructions and replace with the updated ones.
1352          */
1353         if (incomplete_deliveries_remaining > 0) {
1354                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1355                 msg = mallok(sizeof(struct CtdlMessage));
1356                 memset(msg, 0, sizeof(struct CtdlMessage));
1357                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1358                 msg->cm_anon_type = MES_NORMAL;
1359                 msg->cm_format_type = FMT_RFC822;
1360                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1361                 snprintf(msg->cm_fields['M'],
1362                         strlen(instr)+SIZ,
1363                         "Content-type: %s\n\n%s\n"
1364                         "attempted|%ld\n"
1365                         "retry|%ld\n",
1366                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1367                 phree(instr);
1368                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1369                 CtdlFreeMessage(msg);
1370         }
1371
1372 }
1373
1374
1375
1376 /*
1377  * smtp_do_queue()
1378  * 
1379  * Run through the queue sending out messages.
1380  */
1381 void smtp_do_queue(void) {
1382         static int doing_queue = 0;
1383
1384         /*
1385          * This is a simple concurrency check to make sure only one queue run
1386          * is done at a time.  We could do this with a mutex, but since we
1387          * don't really require extremely fine granularity here, we'll do it
1388          * with a static variable instead.
1389          */
1390         if (doing_queue) return;
1391         doing_queue = 1;
1392
1393         /* 
1394          * Go ahead and run the queue
1395          */
1396         lprintf(7, "SMTP: processing outbound queue\n");
1397
1398         if (getroom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1399                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1400                 return;
1401         }
1402         CtdlForEachMessage(MSGS_ALL, 0L,
1403                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1404
1405         lprintf(7, "SMTP: queue run completed\n");
1406         run_queue_now = 0;
1407         doing_queue = 0;
1408 }
1409
1410
1411
1412 /*****************************************************************************/
1413 /*                          SMTP UTILITY COMMANDS                            */
1414 /*****************************************************************************/
1415
1416 void cmd_smtp(char *argbuf) {
1417         char cmd[SIZ];
1418         char node[SIZ];
1419         char buf[SIZ];
1420         int i;
1421         int num_mxhosts;
1422
1423         if (CtdlAccessCheck(ac_aide)) return;
1424
1425         extract(cmd, argbuf, 0);
1426
1427         if (!strcasecmp(cmd, "mx")) {
1428                 extract(node, argbuf, 1);
1429                 num_mxhosts = getmx(buf, node);
1430                 cprintf("%d %d MX hosts listed for %s\n",
1431                         LISTING_FOLLOWS, num_mxhosts, node);
1432                 for (i=0; i<num_mxhosts; ++i) {
1433                         extract(node, buf, i);
1434                         cprintf("%s\n", node);
1435                 }
1436                 cprintf("000\n");
1437                 return;
1438         }
1439
1440         else if (!strcasecmp(cmd, "runqueue")) {
1441                 run_queue_now = 1;
1442                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1443                 return;
1444         }
1445
1446         else {
1447                 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1448         }
1449
1450 }
1451
1452
1453 /*
1454  * Initialize the SMTP outbound queue
1455  */
1456 void smtp_init_spoolout(void) {
1457         struct ctdlroom qrbuf;
1458
1459         /*
1460          * Create the room.  This will silently fail if the room already
1461          * exists, and that's perfectly ok, because we want it to exist.
1462          */
1463         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1464
1465         /*
1466          * Make sure it's set to be a "system room" so it doesn't show up
1467          * in the <K>nown rooms list for Aides.
1468          */
1469         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1470                 qrbuf.QRflags2 |= QR2_SYSTEM;
1471                 lputroom(&qrbuf);
1472         }
1473 }
1474
1475
1476
1477
1478 /*****************************************************************************/
1479 /*                      MODULE INITIALIZATION STUFF                          */
1480 /*****************************************************************************/
1481
1482
1483 char *serv_smtp_init(void)
1484 {
1485         CtdlRegisterServiceHook(config.c_smtp_port,     /* On the net... */
1486                                 NULL,
1487                                 smtp_greeting,
1488                                 smtp_command_loop);
1489
1490         CtdlRegisterServiceHook(0,                      /* ...and locally */
1491                                 "lmtp.socket",
1492                                 lmtp_greeting,
1493                                 smtp_command_loop);
1494
1495         smtp_init_spoolout();
1496         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1497         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1498         return "$Id$";
1499 }