]> code.citadel.org Git - citadel.git/blob - citadel/serv_smtp.c
* Fixed a subtle but ugly bug in the SMTP sender that was causing it to
[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 <%s> - relaying denied\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         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->quickroom, 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 quickroom 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         SYM_SMTP = CtdlGetDynamicSymbol();
1434
1435         CtdlRegisterServiceHook(config.c_smtp_port,     /* On the net... */
1436                                 NULL,
1437                                 smtp_greeting,
1438                                 smtp_command_loop);
1439
1440         CtdlRegisterServiceHook(0,                      /* ...and locally */
1441                                 "smtp.socket",
1442                                 smtp_greeting,
1443                                 smtp_command_loop);
1444
1445         smtp_init_spoolout();
1446         CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER);
1447         CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1448         return "$Id$";
1449 }