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