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