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