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