]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* Minor fix to ESMTP greeting (missing '-' screwed up pipelining)
[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 usersupp 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 long SYM_SMTP;
92 long SYM_SMTP_RECPS;
93 long SYM_SMTP_ROOMS;
94
95 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
96
97
98
99 /*****************************************************************************/
100 /*                      SMTP SERVER (INBOUND) STUFF                          */
101 /*****************************************************************************/
102
103
104
105
106 /*
107  * Here's where our SMTP session begins its happy day.
108  */
109 void smtp_greeting(void) {
110
111         strcpy(CC->cs_clientname, "SMTP session");
112         CC->internal_pgm = 1;
113         CC->cs_flags |= CS_STEALTH;
114         CtdlAllocUserData(SYM_SMTP, sizeof(struct citsmtp));
115         CtdlAllocUserData(SYM_SMTP_RECPS, SIZ);
116         CtdlAllocUserData(SYM_SMTP_ROOMS, SIZ);
117         snprintf(SMTP_RECPS, SIZ, "%s", "");
118         snprintf(SMTP_ROOMS, SIZ, "%s", "");
119
120         cprintf("220 %s ESMTP Citadel/UX server ready.\r\n", config.c_fqdn);
121 }
122
123
124 /*
125  * Implement HELO and EHLO commands.
126  */
127 void smtp_hello(char *argbuf, int is_esmtp) {
128
129         safestrncpy(SMTP->helo_node, argbuf, sizeof SMTP->helo_node);
130
131         if (!is_esmtp) {
132                 cprintf("250 Greetings and joyous salutations.\r\n");
133         }
134         else {
135                 cprintf("250-Greetings and joyous salutations.\r\n");
136                 cprintf("250-HELP\r\n");
137                 cprintf("250-SIZE %ld\r\n", config.c_maxmsglen);
138                 cprintf("250-PIPELINING\r\n");
139                 cprintf("250-AUTH=LOGIN\r\n");
140                 cprintf("250 ENHANCEDSTATUSCODES\r\n");
141         }
142 }
143
144
145 /*
146  * Implement HELP command.
147  */
148 void smtp_help(void) {
149         cprintf("214-Commands accepted:\r\n");
150         cprintf("214-    DATA\r\n");
151         cprintf("214-    EHLO\r\n");
152         cprintf("214-    EXPN\r\n");
153         cprintf("214-    HELO\r\n");
154         cprintf("214-    HELP\r\n");
155         cprintf("214-    MAIL\r\n");
156         cprintf("214-    NOOP\r\n");
157         cprintf("214-    QUIT\r\n");
158         cprintf("214-    RCPT\r\n");
159         cprintf("214-    RSET\r\n");
160         cprintf("214-    VRFY\r\n");
161         cprintf("214     \r\n");
162 }
163
164
165 /*
166  *
167  */
168 void smtp_get_user(char *argbuf) {
169         char buf[SIZ];
170         char username[SIZ];
171
172         CtdlDecodeBase64(username, argbuf, SIZ);
173         lprintf(9, "Trying <%s>\n", username);
174         if (CtdlLoginExistingUser(username) == login_ok) {
175                 CtdlEncodeBase64(buf, "Password:", 9);
176                 cprintf("334 %s\r\n", buf);
177                 SMTP->command_state = smtp_password;
178         }
179         else {
180                 cprintf("500 5.7.0 No such user.\r\n");
181                 SMTP->command_state = smtp_command;
182         }
183 }
184
185
186 /*
187  *
188  */
189 void smtp_get_pass(char *argbuf) {
190         char password[SIZ];
191
192         CtdlDecodeBase64(password, argbuf, SIZ);
193         lprintf(9, "Trying <%s>\n", password);
194         if (CtdlTryPassword(password) == pass_ok) {
195                 cprintf("235 2.0.0 Hello, %s\r\n", CC->usersupp.fullname);
196                 lprintf(9, "SMTP authenticated login successful\n");
197                 CC->internal_pgm = 0;
198                 CC->cs_flags &= ~CS_STEALTH;
199         }
200         else {
201                 cprintf("500 5.7.0 Authentication failed.\r\n");
202         }
203         SMTP->command_state = smtp_command;
204 }
205
206
207 /*
208  *
209  */
210 void smtp_auth(char *argbuf) {
211         char buf[SIZ];
212
213         if (strncasecmp(argbuf, "login", 5) ) {
214                 cprintf("550 5.7.4 We only support LOGIN authentication.\r\n");
215                 return;
216         }
217
218         if (strlen(argbuf) >= 7) {
219                 smtp_get_user(&argbuf[6]);
220         }
221
222         else {
223                 CtdlEncodeBase64(buf, "Username:", 9);
224                 cprintf("334 %s\r\n", buf);
225                 SMTP->command_state = smtp_user;
226         }
227 }
228
229
230 /*
231  * Back end for smtp_vrfy() command
232  */
233 void smtp_vrfy_backend(struct usersupp *us, void *data) {
234
235         if (!fuzzy_match(us, SMTP->vrfy_match)) {
236                 ++SMTP->vrfy_count;
237                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
238         }
239 }
240
241
242 /* 
243  * Implements the VRFY (verify user name) command.
244  * Performs fuzzy match on full user names.
245  */
246 void smtp_vrfy(char *argbuf) {
247         SMTP->vrfy_count = 0;
248         strcpy(SMTP->vrfy_match, argbuf);
249         ForEachUser(smtp_vrfy_backend, NULL);
250
251         if (SMTP->vrfy_count < 1) {
252                 cprintf("550 5.1.1 String does not match anything.\r\n");
253         }
254         else if (SMTP->vrfy_count == 1) {
255                 cprintf("250 %s <cit%ld@%s>\r\n",
256                         SMTP->vrfy_buffer.fullname,
257                         SMTP->vrfy_buffer.usernum,
258                         config.c_fqdn);
259         }
260         else if (SMTP->vrfy_count > 1) {
261                 cprintf("553 5.1.4 Request ambiguous: %d users matched.\r\n",
262                         SMTP->vrfy_count);
263         }
264
265 }
266
267
268
269 /*
270  * Back end for smtp_expn() command
271  */
272 void smtp_expn_backend(struct usersupp *us, void *data) {
273
274         if (!fuzzy_match(us, SMTP->vrfy_match)) {
275
276                 if (SMTP->vrfy_count >= 1) {
277                         cprintf("250-%s <cit%ld@%s>\r\n",
278                                 SMTP->vrfy_buffer.fullname,
279                                 SMTP->vrfy_buffer.usernum,
280                                 config.c_fqdn);
281                 }
282
283                 ++SMTP->vrfy_count;
284                 memcpy(&SMTP->vrfy_buffer, us, sizeof(struct usersupp));
285         }
286 }
287
288
289 /* 
290  * Implements the EXPN (expand user name) command.
291  * Performs fuzzy match on full user names.
292  */
293 void smtp_expn(char *argbuf) {
294         SMTP->vrfy_count = 0;
295         strcpy(SMTP->vrfy_match, argbuf);
296         ForEachUser(smtp_expn_backend, NULL);
297
298         if (SMTP->vrfy_count < 1) {
299                 cprintf("550 5.1.1 String does not match anything.\r\n");
300         }
301         else if (SMTP->vrfy_count >= 1) {
302                 cprintf("250 %s <cit%ld@%s>\r\n",
303                         SMTP->vrfy_buffer.fullname,
304                         SMTP->vrfy_buffer.usernum,
305                         config.c_fqdn);
306         }
307 }
308
309
310 /*
311  * Implements the RSET (reset state) command.
312  * Currently this just zeroes out the state buffer.  If pointers to data
313  * allocated with mallok() are ever placed in the state buffer, we have to
314  * be sure to phree() them first!
315  */
316 void smtp_rset(void) {
317         memset(SMTP, 0, sizeof(struct citsmtp));
318
319         /*
320          * It is somewhat ambiguous whether we want to log out when a RSET
321          * command is issued.  Here's the code to do it.  It is commented out
322          * because some clients (such as Pine) issue RSET commands before
323          * each message, but still expect to be logged in.
324          *
325          * if (CC->logged_in) {
326          *      logout(CC);
327          * }
328          */
329
330         cprintf("250 2.0.0 Zap!\r\n");
331 }
332
333 /*
334  * Clear out the portions of the state buffer that need to be cleared out
335  * after the DATA command finishes.
336  */
337 void smtp_data_clear(void) {
338         strcpy(SMTP->from, "");
339         strcpy(SMTP->recipients, "");
340         SMTP->number_of_recipients = 0;
341         SMTP->delivery_mode = 0;
342         SMTP->message_originated_locally = 0;
343 }
344
345
346
347 /*
348  * Implements the "MAIL From:" command
349  */
350 void smtp_mail(char *argbuf) {
351         char user[SIZ];
352         char node[SIZ];
353         char name[SIZ];
354
355         if (strlen(SMTP->from) != 0) {
356                 cprintf("503 5.1.0 Only one sender permitted\r\n");
357                 return;
358         }
359
360         if (strncasecmp(argbuf, "From:", 5)) {
361                 cprintf("501 5.1.7 Syntax error\r\n");
362                 return;
363         }
364
365         strcpy(SMTP->from, &argbuf[5]);
366         striplt(SMTP->from);
367         stripallbut(SMTP->from, '<', '>');
368
369         if (strlen(SMTP->from) == 0) {
370                 cprintf("501 5.1.7 Empty sender name is not permitted\r\n");
371                 return;
372         }
373
374         /* If this SMTP connection is from a logged-in user, force the 'from'
375          * to be the user's Internet e-mail address as Citadel knows it.
376          */
377         if (CC->logged_in) {
378                 strcpy(SMTP->from, CC->cs_inet_email);
379                 cprintf("250 2.1.0 Sender ok <%s>\r\n", SMTP->from);
380                 SMTP->message_originated_locally = 1;
381                 return;
382         }
383
384         /* Otherwise, make sure outsiders aren't trying to forge mail from
385          * this system.
386          */
387         else {
388                 process_rfc822_addr(SMTP->from, user, node, name);
389                 if (CtdlHostAlias(node) != hostalias_nomatch) {
390                         cprintf("550 5.1.8 "
391                                 "You must log in to send mail from %s\r\n",
392                                 node);
393                         strcpy(SMTP->from, "");
394                         return;
395                 }
396         }
397
398         cprintf("250 2.0.0 Sender ok\r\n");
399 }
400
401
402
403 /*
404  * Implements the "RCPT To:" command
405  */
406 void smtp_rcpt(char *argbuf) {
407         char recp[SIZ];
408         char message_to_spammer[SIZ];
409         struct recptypes *valid = NULL;
410
411         if (strlen(SMTP->from) == 0) {
412                 cprintf("503 5.5.1 Need MAIL before RCPT\r\n");
413                 return;
414         }
415
416         if (strncasecmp(argbuf, "To:", 3)) {
417                 cprintf("501 5.1.7 Syntax error\r\n");
418                 return;
419         }
420
421         strcpy(recp, &argbuf[3]);
422         striplt(recp);
423         stripallbut(recp, '<', '>');
424
425         if ( (strlen(recp) + strlen(SMTP->recipients) + 1 ) >= SIZ) {
426                 cprintf("452 4.5.3 Too many recipients\r\n");
427                 return;
428         }
429
430         /* RBL check */
431         if ( (!CC->logged_in) && (!CC->is_local_socket) ) {
432                 if (rbl_check(message_to_spammer)) {
433                         cprintf("550 %s\r\n", message_to_spammer);
434                         /* no need to phree(valid), it's not allocated yet */
435                         return;
436                 }
437         }
438
439         valid = validate_recipients(recp);
440         if (valid->num_error > 0) {
441                 cprintf("599 5.1.1 Error: %s\r\n", valid->errormsg);
442                 phree(valid);
443                 return;
444         }
445
446         if (valid->num_internet > 0) {
447                 if (SMTP->message_originated_locally == 0) {
448                         cprintf("551 5.7.1 Relaying denied <%s>\r\n", recp);
449                         phree(valid);
450                         return;
451                 }
452         }
453
454         cprintf("250 2.1.5 RCPT ok <%s>\r\n", recp);
455         if (strlen(SMTP->recipients) > 0) {
456                 strcat(SMTP->recipients, ",");
457         }
458         strcat(SMTP->recipients, recp);
459         SMTP->number_of_recipients += 1;
460 }
461
462
463
464
465 /*
466  * Implements the DATA command
467  */
468 void smtp_data(void) {
469         char *body;
470         struct CtdlMessage *msg;
471         long msgnum;
472         char nowstamp[SIZ];
473         struct recptypes *valid;
474         int scan_errors;
475
476         if (strlen(SMTP->from) == 0) {
477                 cprintf("503 5.5.1 Need MAIL command first.\r\n");
478                 return;
479         }
480
481         if (SMTP->number_of_recipients < 1) {
482                 cprintf("503 5.5.1 Need RCPT command first.\r\n");
483                 return;
484         }
485
486         cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
487         
488         datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
489         body = mallok(4096);
490
491         /* FIXME
492            it should be Received: from %s (real.name.dom [w.x.y.z])
493          */
494         if (body != NULL) snprintf(body, 4096,
495                 "Received: from %s (%s)\n"
496                 "       by %s; %s\n",
497                         SMTP->helo_node,
498                         CC->cs_host,
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->usersupp.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         for (mx=0; mx<num_mxhosts; ++mx) {
762                 extract(buf, mxhosts, mx);
763                 lprintf(9, "Trying <%s>\n", buf);
764                 sock = sock_connect(buf, "25", "tcp");
765                 snprintf(dsn, SIZ, "Could not connect: %s", strerror(errno));
766                 if (sock >= 0) lprintf(9, "Connected!\n");
767                 if (sock < 0) snprintf(dsn, SIZ, "%s", strerror(errno));
768         }
769
770         if (sock < 0) {
771                 *status = 4;    /* dsn is already filled in */
772                 return;
773         }
774
775         /* Process the SMTP greeting from the server */
776         if (ml_sock_gets(sock, buf) < 0) {
777                 *status = 4;
778                 strcpy(dsn, "Connection broken during SMTP conversation");
779                 goto bail;
780         }
781         lprintf(9, "<%s\n", buf);
782         if (buf[0] != '2') {
783                 if (buf[0] == '4') {
784                         *status = 4;
785                         safestrncpy(dsn, &buf[4], 1023);
786                         goto bail;
787                 }
788                 else {
789                         *status = 5;
790                         safestrncpy(dsn, &buf[4], 1023);
791                         goto bail;
792                 }
793         }
794
795         /* At this point we know we are talking to a real SMTP server */
796
797         /* Do a HELO command */
798         snprintf(buf, sizeof buf, "HELO %s\r\n", config.c_fqdn);
799         lprintf(9, ">%s", buf);
800         sock_write(sock, buf, strlen(buf));
801         if (ml_sock_gets(sock, buf) < 0) {
802                 *status = 4;
803                 strcpy(dsn, "Connection broken during SMTP HELO");
804                 goto bail;
805         }
806         lprintf(9, "<%s\n", buf);
807         if (buf[0] != '2') {
808                 if (buf[0] == '4') {
809                         *status = 4;
810                         safestrncpy(dsn, &buf[4], 1023);
811                         goto bail;
812                 }
813                 else {
814                         *status = 5;
815                         safestrncpy(dsn, &buf[4], 1023);
816                         goto bail;
817                 }
818         }
819
820
821         /* HELO succeeded, now try the MAIL From: command */
822         snprintf(buf, sizeof buf, "MAIL From: <%s>\r\n", mailfrom);
823         lprintf(9, ">%s", buf);
824         sock_write(sock, buf, strlen(buf));
825         if (ml_sock_gets(sock, buf) < 0) {
826                 *status = 4;
827                 strcpy(dsn, "Connection broken during SMTP MAIL");
828                 goto bail;
829         }
830         lprintf(9, "<%s\n", buf);
831         if (buf[0] != '2') {
832                 if (buf[0] == '4') {
833                         *status = 4;
834                         safestrncpy(dsn, &buf[4], 1023);
835                         goto bail;
836                 }
837                 else {
838                         *status = 5;
839                         safestrncpy(dsn, &buf[4], 1023);
840                         goto bail;
841                 }
842         }
843
844
845         /* MAIL succeeded, now try the RCPT To: command */
846         snprintf(buf, sizeof buf, "RCPT To: <%s>\r\n", addr);
847         lprintf(9, ">%s", buf);
848         sock_write(sock, buf, strlen(buf));
849         if (ml_sock_gets(sock, buf) < 0) {
850                 *status = 4;
851                 strcpy(dsn, "Connection broken during SMTP RCPT");
852                 goto bail;
853         }
854         lprintf(9, "<%s\n", buf);
855         if (buf[0] != '2') {
856                 if (buf[0] == '4') {
857                         *status = 4;
858                         safestrncpy(dsn, &buf[4], 1023);
859                         goto bail;
860                 }
861                 else {
862                         *status = 5;
863                         safestrncpy(dsn, &buf[4], 1023);
864                         goto bail;
865                 }
866         }
867
868
869         /* RCPT succeeded, now try the DATA command */
870         lprintf(9, ">DATA\n");
871         sock_write(sock, "DATA\r\n", 6);
872         if (ml_sock_gets(sock, buf) < 0) {
873                 *status = 4;
874                 strcpy(dsn, "Connection broken during SMTP DATA");
875                 goto bail;
876         }
877         lprintf(9, "<%s\n", buf);
878         if (buf[0] != '3') {
879                 if (buf[0] == '4') {
880                         *status = 3;
881                         safestrncpy(dsn, &buf[4], 1023);
882                         goto bail;
883                 }
884                 else {
885                         *status = 5;
886                         safestrncpy(dsn, &buf[4], 1023);
887                         goto bail;
888                 }
889         }
890
891         /* If we reach this point, the server is expecting data */
892         rewind(msg_fp);
893         while (msg_size > 0) {
894                 blocksize = sizeof(buf);
895                 if (blocksize > msg_size) blocksize = msg_size;
896                 fread(buf, blocksize, 1, msg_fp);
897                 sock_write(sock, buf, blocksize);
898                 msg_size -= blocksize;
899         }
900         if (buf[blocksize-1] != 10) {
901                 lprintf(5, "Possible problem: message did not correctly "
902                         "terminate. (expecting 0x10, got 0x%02x)\n",
903                                 buf[blocksize-1]);
904         }
905
906         sock_write(sock, ".\r\n", 3);
907         if (ml_sock_gets(sock, buf) < 0) {
908                 *status = 4;
909                 strcpy(dsn, "Connection broken during SMTP message transmit");
910                 goto bail;
911         }
912         lprintf(9, "%s\n", buf);
913         if (buf[0] != '2') {
914                 if (buf[0] == '4') {
915                         *status = 4;
916                         safestrncpy(dsn, &buf[4], 1023);
917                         goto bail;
918                 }
919                 else {
920                         *status = 5;
921                         safestrncpy(dsn, &buf[4], 1023);
922                         goto bail;
923                 }
924         }
925
926         /* We did it! */
927         safestrncpy(dsn, &buf[4], 1023);
928         *status = 2;
929
930         lprintf(9, ">QUIT\n");
931         sock_write(sock, "QUIT\r\n", 6);
932         ml_sock_gets(sock, buf);
933         lprintf(9, "<%s\n", buf);
934
935 bail:   if (msg_fp != NULL) fclose(msg_fp);
936         sock_close(sock);
937         return;
938 }
939
940
941
942 /*
943  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
944  * instructions for "5" codes (permanent fatal errors) and produce/deliver
945  * a "bounce" message (delivery status notification).
946  */
947 void smtp_do_bounce(char *instr) {
948         int i;
949         int lines;
950         int status;
951         char buf[1024];
952         char key[1024];
953         char addr[1024];
954         char dsn[1024];
955         char bounceto[1024];
956         int num_bounces = 0;
957         int bounce_this = 0;
958         long bounce_msgid = (-1);
959         time_t submitted = 0L;
960         struct CtdlMessage *bmsg = NULL;
961         int give_up = 0;
962         struct recptypes *valid;
963         int successful_bounce = 0;
964
965         lprintf(9, "smtp_do_bounce() called\n");
966         strcpy(bounceto, "");
967
968         lines = num_tokens(instr, '\n');
969
970
971         /* See if it's time to give up on delivery of this message */
972         for (i=0; i<lines; ++i) {
973                 extract_token(buf, instr, i, '\n');
974                 extract(key, buf, 0);
975                 extract(addr, buf, 1);
976                 if (!strcasecmp(key, "submitted")) {
977                         submitted = atol(addr);
978                 }
979         }
980
981         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
982                 give_up = 1;
983         }
984
985
986
987         bmsg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
988         if (bmsg == NULL) return;
989         memset(bmsg, 0, sizeof(struct CtdlMessage));
990
991         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
992         bmsg->cm_anon_type = MES_NORMAL;
993         bmsg->cm_format_type = 1;
994         bmsg->cm_fields['A'] = strdoop("Citadel");
995         bmsg->cm_fields['O'] = strdoop(MAILROOM);
996         bmsg->cm_fields['N'] = strdoop(config.c_nodename);
997
998         if (give_up) bmsg->cm_fields['M'] = strdoop(
999 "A message you sent could not be delivered to some or all of its recipients\n"
1000 "due to prolonged unavailability of its destination(s).\n"
1001 "Giving up on the following addresses:\n\n"
1002 );
1003
1004         else bmsg->cm_fields['M'] = strdoop(
1005 "A message you sent could not be delivered to some or all of its recipients.\n"
1006 "The following addresses were undeliverable:\n\n"
1007 );
1008
1009         /*
1010          * Now go through the instructions checking for stuff.
1011          */
1012         for (i=0; i<lines; ++i) {
1013                 extract_token(buf, instr, i, '\n');
1014                 extract(key, buf, 0);
1015                 extract(addr, buf, 1);
1016                 status = extract_int(buf, 2);
1017                 extract(dsn, buf, 3);
1018                 bounce_this = 0;
1019
1020                 lprintf(9, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
1021                         key, addr, status, dsn);
1022
1023                 if (!strcasecmp(key, "bounceto")) {
1024                         strcpy(bounceto, addr);
1025                 }
1026
1027                 if (
1028                    (!strcasecmp(key, "local"))
1029                    || (!strcasecmp(key, "remote"))
1030                    || (!strcasecmp(key, "ignet"))
1031                    || (!strcasecmp(key, "room"))
1032                 ) {
1033                         if (status == 5) bounce_this = 1;
1034                         if (give_up) bounce_this = 1;
1035                 }
1036
1037                 if (bounce_this) {
1038                         ++num_bounces;
1039
1040                         if (bmsg->cm_fields['M'] == NULL) {
1041                                 lprintf(2, "ERROR ... M field is null "
1042                                         "(%s:%d)\n", __FILE__, __LINE__);
1043                         }
1044
1045                         bmsg->cm_fields['M'] = reallok(bmsg->cm_fields['M'],
1046                                 strlen(bmsg->cm_fields['M']) + 1024 );
1047                         strcat(bmsg->cm_fields['M'], addr);
1048                         strcat(bmsg->cm_fields['M'], ": ");
1049                         strcat(bmsg->cm_fields['M'], dsn);
1050                         strcat(bmsg->cm_fields['M'], "\n");
1051
1052                         remove_token(instr, i, '\n');
1053                         --i;
1054                         --lines;
1055                 }
1056         }
1057
1058         /* Deliver the bounce if there's anything worth mentioning */
1059         lprintf(9, "num_bounces = %d\n", num_bounces);
1060         if (num_bounces > 0) {
1061
1062                 /* First try the user who sent the message */
1063                 lprintf(9, "bounce to user? <%s>\n", bounceto);
1064                 if (strlen(bounceto) == 0) {
1065                         lprintf(7, "No bounce address specified\n");
1066                         bounce_msgid = (-1L);
1067                 }
1068
1069                 /* Can we deliver the bounce to the original sender? */
1070                 valid = validate_recipients(bounceto);
1071                 if (valid != NULL) {
1072                         if (valid->num_error == 0) {
1073                                 CtdlSubmitMsg(bmsg, valid, "");
1074                                 successful_bounce = 1;
1075                         }
1076                 }
1077
1078                 /* If not, post it in the Aide> room */
1079                 if (successful_bounce == 0) {
1080                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom);
1081                 }
1082
1083                 /* Free up the memory we used */
1084                 if (valid != NULL) {
1085                         phree(valid);
1086                 }
1087         }
1088
1089         CtdlFreeMessage(bmsg);
1090         lprintf(9, "Done processing bounces\n");
1091 }
1092
1093
1094 /*
1095  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
1096  * set of delivery instructions for completed deliveries and remove them.
1097  *
1098  * It returns the number of incomplete deliveries remaining.
1099  */
1100 int smtp_purge_completed_deliveries(char *instr) {
1101         int i;
1102         int lines;
1103         int status;
1104         char buf[1024];
1105         char key[1024];
1106         char addr[1024];
1107         char dsn[1024];
1108         int completed;
1109         int incomplete = 0;
1110
1111         lines = num_tokens(instr, '\n');
1112         for (i=0; i<lines; ++i) {
1113                 extract_token(buf, instr, i, '\n');
1114                 extract(key, buf, 0);
1115                 extract(addr, buf, 1);
1116                 status = extract_int(buf, 2);
1117                 extract(dsn, buf, 3);
1118
1119                 completed = 0;
1120
1121                 if (
1122                    (!strcasecmp(key, "local"))
1123                    || (!strcasecmp(key, "remote"))
1124                    || (!strcasecmp(key, "ignet"))
1125                    || (!strcasecmp(key, "room"))
1126                 ) {
1127                         if (status == 2) completed = 1;
1128                         else ++incomplete;
1129                 }
1130
1131                 if (completed) {
1132                         remove_token(instr, i, '\n');
1133                         --i;
1134                         --lines;
1135                 }
1136         }
1137
1138         return(incomplete);
1139 }
1140
1141
1142 /*
1143  * smtp_do_procmsg()
1144  *
1145  * Called by smtp_do_queue() to handle an individual message.
1146  */
1147 void smtp_do_procmsg(long msgnum, void *userdata) {
1148         struct CtdlMessage *msg;
1149         char *instr = NULL;
1150         char *results = NULL;
1151         int i;
1152         int lines;
1153         int status;
1154         char buf[1024];
1155         char key[1024];
1156         char addr[1024];
1157         char dsn[1024];
1158         long text_msgid = (-1);
1159         int incomplete_deliveries_remaining;
1160         time_t attempted = 0L;
1161         time_t last_attempted = 0L;
1162         time_t retry = SMTP_RETRY_INTERVAL;
1163
1164         lprintf(9, "smtp_do_procmsg(%ld)\n", msgnum);
1165
1166         msg = CtdlFetchMessage(msgnum);
1167         if (msg == NULL) {
1168                 lprintf(3, "SMTP: tried %ld but no such message!\n", msgnum);
1169                 return;
1170         }
1171
1172         instr = strdoop(msg->cm_fields['M']);
1173         CtdlFreeMessage(msg);
1174
1175         /* Strip out the headers amd any other non-instruction line */
1176         lines = num_tokens(instr, '\n');
1177         for (i=0; i<lines; ++i) {
1178                 extract_token(buf, instr, i, '\n');
1179                 if (num_tokens(buf, '|') < 2) {
1180                         remove_token(instr, i, '\n');
1181                         --lines;
1182                         --i;
1183                 }
1184         }
1185
1186         /* Learn the message ID and find out about recent delivery attempts */
1187         lines = num_tokens(instr, '\n');
1188         for (i=0; i<lines; ++i) {
1189                 extract_token(buf, instr, i, '\n');
1190                 extract(key, buf, 0);
1191                 if (!strcasecmp(key, "msgid")) {
1192                         text_msgid = extract_long(buf, 1);
1193                 }
1194                 if (!strcasecmp(key, "retry")) {
1195                         /* double the retry interval after each attempt */
1196                         retry = extract_long(buf, 1) * 2L;
1197                         if (retry > SMTP_RETRY_MAX) {
1198                                 retry = SMTP_RETRY_MAX;
1199                         }
1200                         remove_token(instr, i, '\n');
1201                 }
1202                 if (!strcasecmp(key, "attempted")) {
1203                         attempted = extract_long(buf, 1);
1204                         if (attempted > last_attempted)
1205                                 last_attempted = attempted;
1206                 }
1207         }
1208
1209         /*
1210          * Postpone delivery if we've already tried recently.
1211          */
1212         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
1213                 lprintf(7, "Retry time not yet reached.\n");
1214                 phree(instr);
1215                 return;
1216         }
1217
1218
1219         /*
1220          * Bail out if there's no actual message associated with this
1221          */
1222         if (text_msgid < 0L) {
1223                 lprintf(3, "SMTP: no 'msgid' directive found!\n");
1224                 phree(instr);
1225                 return;
1226         }
1227
1228         /* Plow through the instructions looking for 'remote' directives and
1229          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
1230          * were experienced and it's time to try again)
1231          */
1232         lines = num_tokens(instr, '\n');
1233         for (i=0; i<lines; ++i) {
1234                 extract_token(buf, instr, i, '\n');
1235                 extract(key, buf, 0);
1236                 extract(addr, buf, 1);
1237                 status = extract_int(buf, 2);
1238                 extract(dsn, buf, 3);
1239                 if ( (!strcasecmp(key, "remote"))
1240                    && ((status==0)||(status==3)||(status==4)) ) {
1241
1242                         /* Remove this "remote" instruction from the set,
1243                          * but replace the set's final newline if
1244                          * remove_token() stripped it.  It has to be there.
1245                          */
1246                         remove_token(instr, i, '\n');
1247                         if (instr[strlen(instr)-1] != '\n') {
1248                                 strcat(instr, "\n");
1249                         }
1250
1251                         --i;
1252                         --lines;
1253                         lprintf(9, "SMTP: Trying <%s>\n", addr);
1254                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid);
1255                         if (status != 2) {
1256                                 if (results == NULL) {
1257                                         results = mallok(1024);
1258                                         memset(results, 0, 1024);
1259                                 }
1260                                 else {
1261                                         results = reallok(results,
1262                                                 strlen(results) + 1024);
1263                                 }
1264                                 snprintf(&results[strlen(results)], 1024,
1265                                         "%s|%s|%d|%s\n",
1266                                         key, addr, status, dsn);
1267                         }
1268                 }
1269         }
1270
1271         if (results != NULL) {
1272                 instr = reallok(instr, strlen(instr) + strlen(results) + 2);
1273                 strcat(instr, results);
1274                 phree(results);
1275         }
1276
1277
1278         /* Generate 'bounce' messages */
1279         smtp_do_bounce(instr);
1280
1281         /* Go through the delivery list, deleting completed deliveries */
1282         incomplete_deliveries_remaining = 
1283                 smtp_purge_completed_deliveries(instr);
1284
1285
1286         /*
1287          * No delivery instructions remain, so delete both the instructions
1288          * message and the message message.
1289          */
1290         if (incomplete_deliveries_remaining <= 0) {
1291                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1292                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, text_msgid, "");
1293         }
1294
1295
1296         /*
1297          * Uncompleted delivery instructions remain, so delete the old
1298          * instructions and replace with the updated ones.
1299          */
1300         if (incomplete_deliveries_remaining > 0) {
1301                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, msgnum, "");
1302                 msg = mallok(sizeof(struct CtdlMessage));
1303                 memset(msg, 0, sizeof(struct CtdlMessage));
1304                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1305                 msg->cm_anon_type = MES_NORMAL;
1306                 msg->cm_format_type = FMT_RFC822;
1307                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1308                 snprintf(msg->cm_fields['M'],
1309                         strlen(instr)+SIZ,
1310                         "Content-type: %s\n\n%s\n"
1311                         "attempted|%ld\n"
1312                         "retry|%ld\n",
1313                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1314                 phree(instr);
1315                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM);
1316                 CtdlFreeMessage(msg);
1317         }
1318
1319 }
1320
1321
1322
1323 /*
1324  * smtp_do_queue()
1325  * 
1326  * Run through the queue sending out messages.
1327  */
1328 void smtp_do_queue(void) {
1329         static int doing_queue = 0;
1330
1331         /*
1332          * This is a simple concurrency check to make sure only one queue run
1333          * is done at a time.  We could do this with a mutex, but since we
1334          * don't really require extremely fine granularity here, we'll do it
1335          * with a static variable instead.
1336          */
1337         if (doing_queue) return;
1338         doing_queue = 1;
1339
1340         /* 
1341          * Go ahead and run the queue
1342          */
1343         lprintf(7, "SMTP: processing outbound queue\n");
1344
1345         if (getroom(&CC->quickroom, SMTP_SPOOLOUT_ROOM) != 0) {
1346                 lprintf(3, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1347                 return;
1348         }
1349         CtdlForEachMessage(MSGS_ALL, 0L,
1350                 SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1351
1352         lprintf(7, "SMTP: queue run completed\n");
1353         run_queue_now = 0;
1354         doing_queue = 0;
1355 }
1356
1357
1358
1359 /*****************************************************************************/
1360 /*                          SMTP UTILITY COMMANDS                            */
1361 /*****************************************************************************/
1362
1363 void cmd_smtp(char *argbuf) {
1364         char cmd[SIZ];
1365         char node[SIZ];
1366         char buf[SIZ];
1367         int i;
1368         int num_mxhosts;
1369
1370         if (CtdlAccessCheck(ac_aide)) return;
1371
1372         extract(cmd, argbuf, 0);
1373
1374         if (!strcasecmp(cmd, "mx")) {
1375                 extract(node, argbuf, 1);
1376                 num_mxhosts = getmx(buf, node);
1377                 cprintf("%d %d MX hosts listed for %s\n",
1378                         LISTING_FOLLOWS, num_mxhosts, node);
1379                 for (i=0; i<num_mxhosts; ++i) {
1380                         extract(node, buf, i);
1381                         cprintf("%s\n", node);
1382                 }
1383                 cprintf("000\n");
1384                 return;
1385         }
1386
1387         else if (!strcasecmp(cmd, "runqueue")) {
1388                 run_queue_now = 1;
1389                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1390                 return;
1391         }
1392
1393         else {
1394                 cprintf("%d Invalid command.\n", ERROR+ILLEGAL_VALUE);
1395         }
1396
1397 }
1398
1399
1400 /*
1401  * Initialize the SMTP outbound queue
1402  */
1403 void smtp_init_spoolout(void) {
1404         struct quickroom qrbuf;
1405
1406         /*
1407          * Create the room.  This will silently fail if the room already
1408          * exists, and that's perfectly ok, because we want it to exist.
1409          */
1410         create_room(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0);
1411
1412         /*
1413          * Make sure it's set to be a "system room" so it doesn't show up
1414          * in the <K>nown rooms list for Aides.
1415          */
1416         if (lgetroom(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1417                 qrbuf.QRflags2 |= QR2_SYSTEM;
1418                 lputroom(&qrbuf);
1419         }
1420 }
1421
1422
1423
1424
1425 /*****************************************************************************/
1426 /*                      MODULE INITIALIZATION STUFF                          */
1427 /*****************************************************************************/
1428
1429
1430 char *serv_smtp_init(void)
1431 {
1432         SYM_SMTP = CtdlGetDynamicSymbol();
1433
1434         CtdlRegisterServiceHook(config.c_smtp_port,     /* On the net... */
1435                                 NULL,
1436                                 smtp_greeting,
1437                                 smtp_command_loop);
1438
1439         CtdlRegisterServiceHook(0,                      /* ...and locally */
1440                                 "smtp.socket",
1441                                 smtp_greeting,
1442                                 smtp_command_loop);
1443
1444         smtp_init_spoolout();
1445         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1446         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1447         return "$Id$";
1448 }