Libevent integration
[citadel.git] / citadel / modules / smtp / serv_smtpeventclient.c
1 /*
2  * This module is an SMTP and ESMTP implementation for the Citadel system.
3  * It is compliant with all of the following:
4  *
5  * RFC  821 - Simple Mail Transfer Protocol
6  * RFC  876 - Survey of SMTP Implementations
7  * RFC 1047 - Duplicate messages and SMTP
8  * RFC 1652 - 8 bit MIME
9  * RFC 1869 - Extended Simple Mail Transfer Protocol
10  * RFC 1870 - SMTP Service Extension for Message Size Declaration
11  * RFC 2033 - Local Mail Transfer Protocol
12  * RFC 2197 - SMTP Service Extension for Command Pipelining
13  * RFC 2476 - Message Submission
14  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
15  * RFC 2554 - SMTP Service Extension for Authentication
16  * RFC 2821 - Simple Mail Transfer Protocol
17  * RFC 2822 - Internet Message Format
18  * RFC 2920 - SMTP Service Extension for Command Pipelining
19  *  
20  * The VRFY and EXPN commands have been removed from this implementation
21  * because nobody uses these commands anymore, except for spammers.
22  *
23  * Copyright (c) 1998-2009 by the citadel.org team
24  *
25  *  This program is free software; you can redistribute it and/or modify
26  *  it under the terms of the GNU General Public License as published by
27  *  the Free Software Foundation; either version 3 of the License, or
28  *  (at your option) any later version.
29  *
30  *  This program is distributed in the hope that it will be useful,
31  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
32  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33  *  GNU General Public License for more details.
34  *
35  *  You should have received a copy of the GNU General Public License
36  *  along with this program; if not, write to the Free Software
37  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
38  */
39
40 #include "sysdep.h"
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <termios.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <pwd.h>
48 #include <errno.h>
49 #include <sys/types.h>
50 #include <syslog.h>
51
52 #if TIME_WITH_SYS_TIME
53 # include <sys/time.h>
54 # include <time.h>
55 #else
56 # if HAVE_SYS_TIME_H
57 #  include <sys/time.h>
58 # else
59 #  include <time.h>
60 # endif
61 #endif
62 #include <sys/wait.h>
63 #include <ctype.h>
64 #include <string.h>
65 #include <limits.h>
66 #include <sys/socket.h>
67 #include <netinet/in.h>
68 #include <arpa/inet.h>
69 #include <libcitadel.h>
70 #include "citadel.h"
71 #include "server.h"
72 #include "citserver.h"
73 #include "support.h"
74 #include "config.h"
75 #include "control.h"
76 #include "user_ops.h"
77 #include "database.h"
78 #include "msgbase.h"
79 #include "internet_addressing.h"
80 #include "genstamp.h"
81 #include "domain.h"
82 #include "clientsocket.h"
83 #include "locate_host.h"
84 #include "citadel_dirs.h"
85
86 #include "ctdl_module.h"
87
88 #include "smtp_util.h"
89 #include "event_client.h"
90
91 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
92
93 int run_queue_now = 0;  /* Set to 1 to ignore SMTP send retry times */
94 int MsgCount = 0;
95 /*****************************************************************************/
96 /*               SMTP CLIENT (OUTBOUND PROCESSING) STUFF                     */
97 /*****************************************************************************/
98
99
100 typedef enum _eSMTP_C_States {
101         eConnect, 
102         eEHLO,
103         eHELO,
104         eSMTPAuth,
105         eFROM,
106         eRCPT,
107         eDATA,
108         eDATABody,
109         eDATATerminateBody,
110         eQUIT,
111         eMaxSMTPC
112 } eSMTP_C_States;
113
114 const long SMTP_C_ReadTimeouts[eMaxSMTPC] = {
115         90, /* Greeting... */
116         30, /* EHLO */
117         30, /* HELO */
118         30, /* Auth */
119         30, /* From */
120         30, /* RCPT */
121         30, /* DATA */
122         90, /* DATABody */
123         900, /* end of body... */
124         30  /* QUIT */
125 };
126 /*
127 const long SMTP_C_SendTimeouts[eMaxSMTPC] = {
128
129 }; */
130 const char *ReadErrors[eMaxSMTPC] = {
131         "Connection broken during SMTP conversation",
132         "Connection broken during SMTP EHLO",
133         "Connection broken during SMTP HELO",
134         "Connection broken during SMTP AUTH",
135         "Connection broken during SMTP MAIL FROM",
136         "Connection broken during SMTP RCPT",
137         "Connection broken during SMTP DATA",
138         "Connection broken during SMTP message transmit",
139         ""/* quit reply, don't care. */
140 };
141
142
143 typedef struct _stmp_out_msg {
144         long n;
145         AsyncIO IO;
146
147         eSMTP_C_States State;
148
149         int SMTPstatus;
150
151         int i_mx;
152         int n_mx;
153         int num_mxhosts;
154         char mx_user[1024];
155         char mx_pass[1024];
156         char mx_host[1024];
157         char mx_port[1024];
158         char mxhosts[SIZ];
159
160         StrBuf *msgtext;
161         char *envelope_from;
162         char user[1024];
163         char node[1024];
164         char name[1024];
165         char addr[SIZ];
166         char dsn[1024];
167         char envelope_from_buf[1024];
168         char mailfrom[1024];
169 } SmtpOutMsg;
170
171 eNextState SMTP_C_DispatchReadDone(void *Data);
172 eNextState SMTP_C_DispatchWriteDone(void *Data);
173
174
175 typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg);
176 typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg);
177
178
179 eReadState SMTP_C_ReadServerStatus(AsyncIO *IO)
180 {
181         eReadState Finished = eBufferNotEmpty; 
182
183         while (Finished == eBufferNotEmpty) {
184                 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
185                 
186                 switch (Finished) {
187                 case eMustReadMore: /// read new from socket... 
188                         return Finished;
189                         break;
190                 case eBufferNotEmpty: /* shouldn't happen... */
191                 case eReadSuccess: /// done for now...
192                         if (StrLength(IO->IOBuf) < 4)
193                                 continue;
194                         if (ChrPtr(IO->IOBuf)[3] == '-')
195                                 Finished = eBufferNotEmpty;
196                         else 
197                                 return Finished;
198                         break;
199                 case eReadFail: /// WHUT?
200                         ///todo: shut down! 
201                         break;
202                 }
203         }
204         return Finished;
205 }
206
207
208
209
210 /**
211  * this one has to have the context for loading the message via the redirect buffer...
212  */
213 SmtpOutMsg *smtp_load_msg(long msgnum, const char *addr, char *envelope_from)
214 {
215         CitContext *CCC=CC;
216         SmtpOutMsg *SendMsg;
217         
218         SendMsg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg));
219
220         memset(SendMsg, 0, sizeof(SmtpOutMsg));
221         SendMsg->IO.sock = (-1);
222         
223         SendMsg->n = MsgCount++;
224         /* Load the message out of the database */
225         CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
226         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO) );
227         SendMsg->msgtext = CCC->redirect_buffer;
228         CCC->redirect_buffer = NULL;
229         if ((StrLength(SendMsg->msgtext) > 0) && 
230             ChrPtr(SendMsg->msgtext)[StrLength(SendMsg->msgtext) - 1] != '\n') {
231                 CtdlLogPrintf(CTDL_WARNING, 
232                               "SMTP client[%ld]: Possible problem: message did not "
233                               "correctly terminate. (expecting 0x10, got 0x%02x)\n",
234                               SendMsg->n,
235                               ChrPtr(SendMsg->msgtext)[StrLength(SendMsg->msgtext) - 1] );
236                 StrBufAppendBufPlain(SendMsg->msgtext, HKEY("\r\n"), 0);
237         }
238
239         safestrncpy(SendMsg->addr, addr, SIZ);
240         safestrncpy(SendMsg->envelope_from_buf, envelope_from, 1024);
241
242         return SendMsg;
243 }
244
245
246 int smtp_resolve_recipients(SmtpOutMsg *SendMsg)
247 {
248         const char *ptr;
249         char buf[1024];
250         int scan_done;
251         int lp, rp;
252         int i;
253
254         /* Parse out the host portion of the recipient address */
255         process_rfc822_addr(SendMsg->addr, SendMsg->user, SendMsg->node, SendMsg->name);
256
257         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Attempting delivery to <%s> @ <%s> (%s)\n",
258                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
259         /* If no envelope_from is supplied, extract one from the message */
260         if ( (SendMsg->envelope_from == NULL) || 
261              (IsEmptyStr(SendMsg->envelope_from)) ) {
262                 SendMsg->mailfrom[0] = '\0';
263                 scan_done = 0;
264                 ptr = ChrPtr(SendMsg->msgtext);
265                 do {
266                         if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) {
267                                 scan_done = 1;
268                         }
269                         if (!strncasecmp(buf, "From:", 5)) {
270                                 safestrncpy(SendMsg->mailfrom, &buf[5], sizeof SendMsg->mailfrom);
271                                 striplt(SendMsg->mailfrom);
272                                 for (i=0; SendMsg->mailfrom[i]; ++i) {
273                                         if (!isprint(SendMsg->mailfrom[i])) {
274                                                 strcpy(&SendMsg->mailfrom[i], &SendMsg->mailfrom[i+1]);
275                                                 i=0;
276                                         }
277                                 }
278         
279                                 /* Strip out parenthesized names */
280                                 lp = (-1);
281                                 rp = (-1);
282                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
283                                         if (SendMsg->mailfrom[i] == '(') lp = i;
284                                         if (SendMsg->mailfrom[i] == ')') rp = i;
285                                 }
286                                 if ((lp>0)&&(rp>lp)) {
287                                         strcpy(&SendMsg->mailfrom[lp-1], &SendMsg->mailfrom[rp+1]);
288                                 }
289         
290                                 /* Prefer brokketized names */
291                                 lp = (-1);
292                                 rp = (-1);
293                                 for (i=0; !IsEmptyStr(SendMsg->mailfrom + i); ++i) {
294                                         if (SendMsg->mailfrom[i] == '<') lp = i;
295                                         if (SendMsg->mailfrom[i] == '>') rp = i;
296                                 }
297                                 if ( (lp>=0) && (rp>lp) ) {
298                                         SendMsg->mailfrom[rp] = 0;
299                                         memmove(SendMsg->mailfrom, 
300                                                 &SendMsg->mailfrom[lp + 1], 
301                                                 rp - lp);
302                                 }
303         
304                                 scan_done = 1;
305                         }
306                 } while (scan_done == 0);
307                 if (IsEmptyStr(SendMsg->mailfrom)) strcpy(SendMsg->mailfrom, "someone@somewhere.org");
308                 stripallbut(SendMsg->mailfrom, '<', '>');
309                 SendMsg->envelope_from = SendMsg->mailfrom;
310         }
311
312         return 0;
313 }
314
315 void resolve_mx_hosts(SmtpOutMsg *SendMsg)
316 {
317         /// well this is blocking and sux, but libevent jsut supports async dns since v2
318         /* Figure out what mail exchanger host we have to connect to */
319         SendMsg->num_mxhosts = getmx(SendMsg->mxhosts, SendMsg->node);
320         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: Number of MX hosts for <%s> is %d [%s]\n", 
321                       SendMsg->n, SendMsg->node, SendMsg->num_mxhosts, SendMsg->mxhosts);
322         if (SendMsg->num_mxhosts < 1) {
323                 SendMsg->SMTPstatus = 5;
324                 snprintf(SendMsg->dsn, SIZ, "No MX hosts found for <%s>", SendMsg->node);
325                 return; ///////TODO: abort!
326         }
327
328 }
329 /* TODO: abort... */
330 #define SMTP_ERROR(WHICH_ERR, ERRSTR) {SendMsg->SMTPstatus = WHICH_ERR; memcpy(SendMsg->dsn, HKEY(ERRSTR) + 1); return eAbort; }
331 #define SMTP_VERROR(WHICH_ERR) { SendMsg->SMTPstatus = WHICH_ERR; safestrncpy(SendMsg->dsn, &ChrPtr(SendMsg->IO.IOBuf)[4], sizeof(SendMsg->dsn)); return eAbort; }
332 #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(SendMsg->IO.IOBuf)[0] == WHICH_STATE)
333
334 #define SMTP_DBG_SEND() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: > %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
335 #define SMTP_DBG_READ() CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: < %s\n", SendMsg->n, ChrPtr(SendMsg->IO.IOBuf))
336
337 void connect_one_smtpsrv(SmtpOutMsg *SendMsg)
338 {
339         char *endpart;
340         char buf[SIZ];
341
342         extract_token(buf, SendMsg->mxhosts, SendMsg->n_mx, '|', sizeof(buf));
343         strcpy(SendMsg->mx_user, "");
344         strcpy(SendMsg->mx_pass, "");
345         if (num_tokens(buf, '@') > 1) {
346                 strcpy (SendMsg->mx_user, buf);
347                 endpart = strrchr(SendMsg->mx_user, '@');
348                 *endpart = '\0';
349                 strcpy (SendMsg->mx_host, endpart + 1);
350                 endpart = strrchr(SendMsg->mx_user, ':');
351                 if (endpart != NULL) {
352                         strcpy(SendMsg->mx_pass, endpart+1);
353                         *endpart = '\0';
354                 }
355         }
356         else
357                 strcpy (SendMsg->mx_host, buf);
358         endpart = strrchr(SendMsg->mx_host, ':');
359         if (endpart != 0){
360                 *endpart = '\0';
361                 strcpy(SendMsg->mx_port, endpart + 1);
362         }               
363         else {
364                 strcpy(SendMsg->mx_port, "25");
365         }
366         CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connecting to %s : %s ...\n", 
367                       SendMsg->n, SendMsg->mx_host, SendMsg->mx_port);
368
369 }
370
371
372 int connect_one_smtpsrv_xamine_result(void *Ctx)
373 {
374         SmtpOutMsg *SendMsg = Ctx;
375         SendMsg->IO.SendBuf.fd = 
376         SendMsg->IO.RecvBuf.fd = 
377         SendMsg->IO.sock = sock_connect(SendMsg->mx_host, SendMsg->mx_port);
378
379         snprintf(SendMsg->dsn, SIZ, "Could not connect: %s", strerror(errno));
380         if (SendMsg->IO.sock >= 0) 
381         {
382                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client[%ld]: connected!\n", SendMsg->n);
383                 int fdflags; 
384                 fdflags = fcntl(SendMsg->IO.sock, F_GETFL);
385                 if (fdflags < 0)
386                         CtdlLogPrintf(CTDL_DEBUG,
387                                       "SMTP client[%ld]: unable to get socket flags! %s \n",
388                                       SendMsg->n, strerror(errno));
389                 fdflags = fdflags | O_NONBLOCK;
390                 if (fcntl(SendMsg->IO.sock, F_SETFL, fdflags) < 0)
391                         CtdlLogPrintf(CTDL_DEBUG,
392                                       "SMTP client[%ld]: unable to set socket nonblocking flags! %s \n",
393                                       SendMsg->n, strerror(errno));
394         }
395         if (SendMsg->IO.sock < 0) {
396                 if (errno > 0) {
397                         snprintf(SendMsg->dsn, SIZ, "%s", strerror(errno));
398                 }
399                 else {
400                         snprintf(SendMsg->dsn, SIZ, "Unable to connect to %s : %s\n", 
401                                  SendMsg->mx_host, SendMsg->mx_port);
402                 }
403         }
404         /// hier: naechsten mx ausprobieren.
405         if (SendMsg->IO.sock < 0) {
406                 SendMsg->SMTPstatus = 4;        /* dsn is already filled in */
407                 //// hier: abbrechen & bounce.
408                 return -1;
409         }
410         SendMsg->IO.SendBuf.Buf = NewStrBuf();
411         SendMsg->IO.RecvBuf.Buf = NewStrBuf();
412         SendMsg->IO.IOBuf = NewStrBuf();
413         InitEventIO(&SendMsg->IO, SendMsg, 
414                     SMTP_C_DispatchReadDone, 
415                     SMTP_C_DispatchWriteDone, 
416                     SMTP_C_ReadServerStatus,
417                     1);
418         return 0;
419 }
420
421 eNextState SMTPC_read_greeting(SmtpOutMsg *SendMsg)
422 {
423         /* Process the SMTP greeting from the server */
424         SMTP_DBG_READ();
425
426         if (!SMTP_IS_STATE('2')) {
427                 if (SMTP_IS_STATE('4')) 
428                         SMTP_VERROR(4)
429                 else 
430                         SMTP_VERROR(5)
431         }
432         return eSendReply;
433 }
434
435 eNextState SMTPC_send_EHLO(SmtpOutMsg *SendMsg)
436 {
437         /* At this point we know we are talking to a real SMTP server */
438
439         /* Do a EHLO command.  If it fails, try the HELO command. */
440         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
441                      "EHLO %s\r\n", config.c_fqdn);
442
443         SMTP_DBG_SEND();
444         return eReadMessage;
445 }
446
447 eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *SendMsg)
448 {
449         SMTP_DBG_READ();
450
451         if (SMTP_IS_STATE('2')) {
452                 SendMsg->State ++;
453                 if (IsEmptyStr(SendMsg->mx_user))
454                         SendMsg->State ++; /* Skip auth... */
455         }
456         /* else we fall back to 'helo' */
457         return eSendReply;
458 }
459
460 eNextState STMPC_send_HELO(SmtpOutMsg *SendMsg)
461 {
462         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
463                      "HELO %s\r\n", config.c_fqdn);
464
465         SMTP_DBG_SEND();
466         return eReadMessage;
467 }
468
469 eNextState SMTPC_read_HELO_reply(SmtpOutMsg *SendMsg)
470 {
471         SMTP_DBG_READ();
472
473         if (!SMTP_IS_STATE('2')) {
474                 if (SMTP_IS_STATE('4'))
475                         SMTP_VERROR(4)
476                 else 
477                         SMTP_VERROR(5)
478         }
479         if (!IsEmptyStr(SendMsg->mx_user))
480                 SendMsg->State ++; /* Skip auth... */
481         return eSendReply;
482 }
483
484 eNextState SMTPC_send_auth(SmtpOutMsg *SendMsg)
485 {
486         char buf[SIZ];
487         char encoded[1024];
488
489         /* Do an AUTH command if necessary */
490         sprintf(buf, "%s%c%s%c%s", 
491                 SendMsg->mx_user, '\0', 
492                 SendMsg->mx_user, '\0', 
493                 SendMsg->mx_pass);
494         CtdlEncodeBase64(encoded, buf, 
495                          strlen(SendMsg->mx_user) + 
496                          strlen(SendMsg->mx_user) + 
497                          strlen(SendMsg->mx_pass) + 2, 0);
498         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
499                      "AUTH PLAIN %s\r\n", encoded);
500         
501         SMTP_DBG_SEND();
502         return eReadMessage;
503 }
504
505 eNextState SMTPC_read_auth_reply(SmtpOutMsg *SendMsg)
506 {
507         /* Do an AUTH command if necessary */
508         
509         SMTP_DBG_READ();
510         
511         if (!SMTP_IS_STATE('2')) {
512                 if (SMTP_IS_STATE('4'))
513                         SMTP_VERROR(4)
514                 else 
515                         SMTP_VERROR(5)
516         }
517         return eSendReply;
518 }
519
520 eNextState SMTPC_send_FROM(SmtpOutMsg *SendMsg)
521 {
522         /* previous command succeeded, now try the MAIL FROM: command */
523         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
524                      "MAIL FROM:<%s>\r\n", 
525                      SendMsg->envelope_from);
526
527         SMTP_DBG_SEND();
528         return eReadMessage;
529 }
530
531 eNextState SMTPC_read_FROM_reply(SmtpOutMsg *SendMsg)
532 {
533         SMTP_DBG_READ();
534
535         if (!SMTP_IS_STATE('2')) {
536                 if (SMTP_IS_STATE('4'))
537                         SMTP_VERROR(4)
538                 else 
539                         SMTP_VERROR(5)
540         }
541         return eSendReply;
542 }
543
544
545 eNextState SMTPC_send_RCPT(SmtpOutMsg *SendMsg)
546 {
547         /* MAIL succeeded, now try the RCPT To: command */
548         StrBufPrintf(SendMsg->IO.SendBuf.Buf,
549                      "RCPT TO:<%s@%s>\r\n", 
550                      SendMsg->user, 
551                      SendMsg->node);
552
553         SMTP_DBG_SEND();
554         return eReadMessage;
555 }
556
557 eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *SendMsg)
558 {
559         SMTP_DBG_READ();
560
561         if (!SMTP_IS_STATE('2')) {
562                 if (SMTP_IS_STATE('4')) 
563                         SMTP_VERROR(4)
564                 else 
565                         SMTP_VERROR(5)
566         }
567         return eSendReply;
568 }
569
570 eNextState SMTPC_send_DATAcmd(SmtpOutMsg *SendMsg)
571 {
572         /* RCPT succeeded, now try the DATA command */
573         StrBufPlain(SendMsg->IO.SendBuf.Buf,
574                     HKEY("DATA\r\n"));
575
576         SMTP_DBG_SEND();
577         return eReadMessage;
578 }
579
580 eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *SendMsg)
581 {
582         SMTP_DBG_READ();
583
584         if (!SMTP_IS_STATE('3')) {
585                 if (SMTP_IS_STATE('4')) 
586                         SMTP_VERROR(3)
587                 else 
588                         SMTP_VERROR(5)
589         }
590         return eSendReply;
591 }
592
593 eNextState SMTPC_send_data_body(SmtpOutMsg *SendMsg)
594 {
595         StrBuf *Buf;
596         /* If we reach this point, the server is expecting data.*/
597
598         Buf = SendMsg->IO.SendBuf.Buf;
599         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
600         SendMsg->msgtext = Buf;
601         //// TODO timeout like that: (SendMsg->msg_size / 128) + 50);
602         SendMsg->State ++;
603
604         return eSendMore;
605 }
606
607 eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *SendMsg)
608 {
609         StrBuf *Buf;
610
611         Buf = SendMsg->IO.SendBuf.Buf;
612         SendMsg->IO.SendBuf.Buf = SendMsg->msgtext;
613         SendMsg->msgtext = Buf;
614
615         StrBufPlain(SendMsg->IO.SendBuf.Buf,
616                     HKEY(".\r\n"));
617
618         return eReadMessage;
619
620 }
621
622 eNextState SMTPC_read_data_body_reply(SmtpOutMsg *SendMsg)
623 {
624         SMTP_DBG_READ();
625
626         if (!SMTP_IS_STATE('2')) {
627                 if (SMTP_IS_STATE('4'))
628                         SMTP_VERROR(4)
629                 else 
630                         SMTP_VERROR(5)
631         }
632
633         /* We did it! */
634         safestrncpy(SendMsg->dsn, &ChrPtr(SendMsg->IO.RecvBuf.Buf)[4], 1023);
635         SendMsg->SMTPstatus = 2;
636         return eSendReply;
637 }
638
639 eNextState SMTPC_send_QUIT(SmtpOutMsg *SendMsg)
640 {
641         StrBufPlain(SendMsg->IO.SendBuf.Buf,
642                     HKEY("QUIT\r\n"));
643
644         SMTP_DBG_SEND();
645         return eReadMessage;
646 }
647
648 eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *SendMsg)
649 {
650         SMTP_DBG_READ();
651
652         CtdlLogPrintf(CTDL_INFO, "SMTP client[%ld]: delivery to <%s> @ <%s> (%s) succeeded\n",
653                       SendMsg->n, SendMsg->user, SendMsg->node, SendMsg->name);
654         return eSendReply;
655 }
656
657 eNextState SMTPC_read_dummy(SmtpOutMsg *SendMsg)
658 {
659         return eSendReply;
660 }
661
662 eNextState SMTPC_send_dummy(SmtpOutMsg *SendMsg)
663 {
664         return eReadMessage;
665 }
666
667 /*
668  * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery
669  * instructions for "5" codes (permanent fatal errors) and produce/deliver
670  * a "bounce" message (delivery status notification).
671  */
672 void smtp_do_bounce(char *instr) {
673         int i;
674         int lines;
675         int status;
676         char buf[1024];
677         char key[1024];
678         char addr[1024];
679         char dsn[1024];
680         char bounceto[1024];
681         StrBuf *boundary;
682         int num_bounces = 0;
683         int bounce_this = 0;
684         long bounce_msgid = (-1);
685         time_t submitted = 0L;
686         struct CtdlMessage *bmsg = NULL;
687         int give_up = 0;
688         struct recptypes *valid;
689         int successful_bounce = 0;
690         static int seq = 0;
691         StrBuf *BounceMB;
692         long omsgid = (-1);
693
694         CtdlLogPrintf(CTDL_DEBUG, "smtp_do_bounce() called\n");
695         strcpy(bounceto, "");
696         boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_"));
697         StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq);
698         lines = num_tokens(instr, '\n');
699
700         /* See if it's time to give up on delivery of this message */
701         for (i=0; i<lines; ++i) {
702                 extract_token(buf, instr, i, '\n', sizeof buf);
703                 extract_token(key, buf, 0, '|', sizeof key);
704                 extract_token(addr, buf, 1, '|', sizeof addr);
705                 if (!strcasecmp(key, "submitted")) {
706                         submitted = atol(addr);
707                 }
708         }
709
710         if ( (time(NULL) - submitted) > SMTP_GIVE_UP ) {
711                 give_up = 1;
712         }
713
714         /* Start building our bounce message */
715
716         bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
717         if (bmsg == NULL) return;
718         memset(bmsg, 0, sizeof(struct CtdlMessage));
719         BounceMB = NewStrBufPlain(NULL, 1024);
720
721         bmsg->cm_magic = CTDLMESSAGE_MAGIC;
722         bmsg->cm_anon_type = MES_NORMAL;
723         bmsg->cm_format_type = FMT_RFC822;
724         bmsg->cm_fields['A'] = strdup("Citadel");
725         bmsg->cm_fields['O'] = strdup(MAILROOM);
726         bmsg->cm_fields['N'] = strdup(config.c_nodename);
727         bmsg->cm_fields['U'] = strdup("Delivery Status Notification (Failure)");
728         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0);
729         StrBufAppendBuf(BounceMB, boundary, 0);
730         StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0);
731         StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0);
732         StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0);
733         StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0);
734         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
735         StrBufAppendBuf(BounceMB, boundary, 0);
736         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
737         StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0);
738
739         if (give_up) StrBufAppendBufPlain(BounceMB, HKEY(
740 "A message you sent could not be delivered to some or all of its recipients\n"
741 "due to prolonged unavailability of its destination(s).\n"
742 "Giving up on the following addresses:\n\n"
743                                                   ), 0);
744
745         else StrBufAppendBufPlain(BounceMB, HKEY(
746 "A message you sent could not be delivered to some or all of its recipients.\n"
747 "The following addresses were undeliverable:\n\n"
748                                           ), 0);
749
750         /*
751          * Now go through the instructions checking for stuff.
752          */
753         for (i=0; i<lines; ++i) {
754                 long addrlen;
755                 long dsnlen;
756                 extract_token(buf, instr, i, '\n', sizeof buf);
757                 extract_token(key, buf, 0, '|', sizeof key);
758                 addrlen = extract_token(addr, buf, 1, '|', sizeof addr);
759                 status = extract_int(buf, 2);
760                 dsnlen = extract_token(dsn, buf, 3, '|', sizeof dsn);
761                 bounce_this = 0;
762
763                 CtdlLogPrintf(CTDL_DEBUG, "key=<%s> addr=<%s> status=%d dsn=<%s>\n",
764                         key, addr, status, dsn);
765
766                 if (!strcasecmp(key, "bounceto")) {
767                         strcpy(bounceto, addr);
768                 }
769
770                 if (!strcasecmp(key, "msgid")) {
771                         omsgid = atol(addr);
772                 }
773
774                 if (!strcasecmp(key, "remote")) {
775                         if (status == 5) bounce_this = 1;
776                         if (give_up) bounce_this = 1;
777                 }
778
779                 if (bounce_this) {
780                         ++num_bounces;
781
782                         StrBufAppendBufPlain(BounceMB, addr, addrlen, 0);
783                         StrBufAppendBufPlain(BounceMB, HKEY(": "), 0);
784                         StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0);
785                         StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
786
787                         remove_token(instr, i, '\n');
788                         --i;
789                         --lines;
790                 }
791         }
792
793         /* Attach the original message */
794         if (omsgid >= 0) {
795                 StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
796                 StrBufAppendBuf(BounceMB, boundary, 0);
797                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
798                 StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0);
799                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0);
800                 StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0);
801                 StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0);
802         
803                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
804                 CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0);
805                 StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0);
806                 FreeStrBuf(&CC->redirect_buffer);
807         }
808
809         /* Close the multipart MIME scope */
810         StrBufAppendBufPlain(BounceMB, HKEY("--"), 0);
811         StrBufAppendBuf(BounceMB, boundary, 0);
812         StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0);
813         if (bmsg->cm_fields['A'] != NULL)
814                 free(bmsg->cm_fields['A']);
815         bmsg->cm_fields['A'] = SmashStrBuf(&BounceMB);
816         /* Deliver the bounce if there's anything worth mentioning */
817         CtdlLogPrintf(CTDL_DEBUG, "num_bounces = %d\n", num_bounces);
818         if (num_bounces > 0) {
819
820                 /* First try the user who sent the message */
821                 CtdlLogPrintf(CTDL_DEBUG, "bounce to user? <%s>\n", bounceto);
822                 if (IsEmptyStr(bounceto)) {
823                         CtdlLogPrintf(CTDL_ERR, "No bounce address specified\n");
824                         bounce_msgid = (-1L);
825                 }
826
827                 /* Can we deliver the bounce to the original sender? */
828                 valid = validate_recipients(bounceto, smtp_get_Recipients (), 0);
829                 if (valid != NULL) {
830                         if (valid->num_error == 0) {
831                                 CtdlSubmitMsg(bmsg, valid, "", QP_EADDR);
832                                 successful_bounce = 1;
833                         }
834                 }
835
836                 /* If not, post it in the Aide> room */
837                 if (successful_bounce == 0) {
838                         CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR);
839                 }
840
841                 /* Free up the memory we used */
842                 if (valid != NULL) {
843                         free_recipients(valid);
844                 }
845         }
846         FreeStrBuf(&boundary);
847         CtdlFreeMessage(bmsg);
848         CtdlLogPrintf(CTDL_DEBUG, "Done processing bounces\n");
849 }
850
851
852 /*
853  * smtp_purge_completed_deliveries() is caled by smtp_do_procmsg() to scan a
854  * set of delivery instructions for completed deliveries and remove them.
855  *
856  * It returns the number of incomplete deliveries remaining.
857  */
858 int smtp_purge_completed_deliveries(char *instr) {
859         int i;
860         int lines;
861         int status;
862         char buf[1024];
863         char key[1024];
864         char addr[1024];
865         char dsn[1024];
866         int completed;
867         int incomplete = 0;
868
869         lines = num_tokens(instr, '\n');
870         for (i=0; i<lines; ++i) {
871                 extract_token(buf, instr, i, '\n', sizeof buf);
872                 extract_token(key, buf, 0, '|', sizeof key);
873                 extract_token(addr, buf, 1, '|', sizeof addr);
874                 status = extract_int(buf, 2);
875                 extract_token(dsn, buf, 3, '|', sizeof dsn);
876
877                 completed = 0;
878
879                 if (!strcasecmp(key, "remote")) {
880                         if (status == 2) completed = 1;
881                         else ++incomplete;
882                 }
883
884                 if (completed) {
885                         remove_token(instr, i, '\n');
886                         --i;
887                         --lines;
888                 }
889         }
890
891         return(incomplete);
892 }
893
894 void smtp_try(const char *key, const char *addr, int *status,
895               char *dsn, size_t n, long msgnum, char *envelope_from)
896 {
897         SmtpOutMsg * SmtpC = smtp_load_msg(msgnum, addr, envelope_from);
898         smtp_resolve_recipients(SmtpC);
899         resolve_mx_hosts(SmtpC);
900         connect_one_smtpsrv(SmtpC);
901         QueueEventContext(SmtpC, connect_one_smtpsrv_xamine_result);
902 }
903
904 /*
905  * smtp_do_procmsg()
906  *
907  * Called by smtp_do_queue() to handle an individual message.
908  */
909 void smtp_do_procmsg(long msgnum, void *userdata) {
910         struct CtdlMessage *msg = NULL;
911         char *instr = NULL;
912         char *results = NULL;
913         int i;
914         int lines;
915         int status;
916         char buf[1024];
917         char key[1024];
918         char addr[1024];
919         char dsn[1024];
920         char envelope_from[1024];
921         long text_msgid = (-1);
922         int incomplete_deliveries_remaining;
923         time_t attempted = 0L;
924         time_t last_attempted = 0L;
925         time_t retry = SMTP_RETRY_INTERVAL;
926
927         CtdlLogPrintf(CTDL_DEBUG, "SMTP client: smtp_do_procmsg(%ld)\n", msgnum);
928         strcpy(envelope_from, "");
929
930         msg = CtdlFetchMessage(msgnum, 1);
931         if (msg == NULL) {
932                 CtdlLogPrintf(CTDL_ERR, "SMTP client: tried %ld but no such message!\n", msgnum);
933                 return;
934         }
935
936         instr = strdup(msg->cm_fields['M']);
937         CtdlFreeMessage(msg);
938
939         /* Strip out the headers amd any other non-instruction line */
940         lines = num_tokens(instr, '\n');
941         for (i=0; i<lines; ++i) {
942                 extract_token(buf, instr, i, '\n', sizeof buf);
943                 if (num_tokens(buf, '|') < 2) {
944                         remove_token(instr, i, '\n');
945                         --lines;
946                         --i;
947                 }
948         }
949
950         /* Learn the message ID and find out about recent delivery attempts */
951         lines = num_tokens(instr, '\n');
952         for (i=0; i<lines; ++i) {
953                 extract_token(buf, instr, i, '\n', sizeof buf);
954                 extract_token(key, buf, 0, '|', sizeof key);
955                 if (!strcasecmp(key, "msgid")) {
956                         text_msgid = extract_long(buf, 1);
957                 }
958                 if (!strcasecmp(key, "envelope_from")) {
959                         extract_token(envelope_from, buf, 1, '|', sizeof envelope_from);
960                 }
961                 if (!strcasecmp(key, "retry")) {
962                         /* double the retry interval after each attempt */
963                         retry = extract_long(buf, 1) * 2L;
964                         if (retry > SMTP_RETRY_MAX) {
965                                 retry = SMTP_RETRY_MAX;
966                         }
967                         remove_token(instr, i, '\n');
968                 }
969                 if (!strcasecmp(key, "attempted")) {
970                         attempted = extract_long(buf, 1);
971                         if (attempted > last_attempted)
972                                 last_attempted = attempted;
973                 }
974         }
975
976         /*
977          * Postpone delivery if we've already tried recently.
978          * /
979         if (((time(NULL) - last_attempted) < retry) && (run_queue_now == 0)) {
980                 CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Retry time not yet reached.\n");
981                 free(instr);
982                 return;
983         }
984 TMP TODO        */
985
986         /*
987          * Bail out if there's no actual message associated with this
988          */
989         if (text_msgid < 0L) {
990                 CtdlLogPrintf(CTDL_ERR, "SMTP client: no 'msgid' directive found!\n");
991                 free(instr);
992                 return;
993         }
994
995         /* Plow through the instructions looking for 'remote' directives and
996          * a status of 0 (no delivery yet attempted) or 3/4 (transient errors
997          * were experienced and it's time to try again)
998          */
999         lines = num_tokens(instr, '\n');
1000         for (i=0; i<lines; ++i) {
1001                 extract_token(buf, instr, i, '\n', sizeof buf);
1002                 extract_token(key, buf, 0, '|', sizeof key);
1003                 extract_token(addr, buf, 1, '|', sizeof addr);
1004                 status = extract_int(buf, 2);
1005                 extract_token(dsn, buf, 3, '|', sizeof dsn);
1006                 if ( (!strcasecmp(key, "remote"))
1007                    && ((status==0)||(status==3)||(status==4)) ) {
1008
1009                         /* Remove this "remote" instruction from the set,
1010                          * but replace the set's final newline if
1011                          * remove_token() stripped it.  It has to be there.
1012                          */
1013                         remove_token(instr, i, '\n');
1014                         if (instr[strlen(instr)-1] != '\n') {
1015                                 strcat(instr, "\n");
1016                         }
1017
1018                         --i;
1019                         --lines;
1020                         CtdlLogPrintf(CTDL_DEBUG, "SMTP client: Trying <%s>\n", addr);
1021                         smtp_try(key, addr, &status, dsn, sizeof dsn, text_msgid, envelope_from);
1022                         if (status != 2) {
1023                                 if (results == NULL) {
1024                                         results = malloc(1024);
1025                                         memset(results, 0, 1024);
1026                                 }
1027                                 else {
1028                                         results = realloc(results, strlen(results) + 1024);
1029                                 }
1030                                 snprintf(&results[strlen(results)], 1024,
1031                                         "%s|%s|%d|%s\n",
1032                                         key, addr, status, dsn);
1033                         }
1034                 }
1035         }
1036
1037         if (results != NULL) {
1038                 instr = realloc(instr, strlen(instr) + strlen(results) + 2);
1039                 strcat(instr, results);
1040                 free(results);
1041         }
1042
1043
1044         /* Generate 'bounce' messages */
1045         smtp_do_bounce(instr);
1046
1047         /* Go through the delivery list, deleting completed deliveries */
1048         incomplete_deliveries_remaining = 
1049                 smtp_purge_completed_deliveries(instr);
1050
1051
1052         /*
1053          * No delivery instructions remain, so delete both the instructions
1054          * message and the message message.
1055          */
1056         if (incomplete_deliveries_remaining <= 0) {
1057                 long delmsgs[2];
1058                 delmsgs[0] = msgnum;
1059                 delmsgs[1] = text_msgid;
1060                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, delmsgs, 2, "");
1061         }
1062
1063         /*
1064          * Uncompleted delivery instructions remain, so delete the old
1065          * instructions and replace with the updated ones.
1066          */
1067         if (incomplete_deliveries_remaining > 0) {
1068                 CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &msgnum, 1, "");
1069                 msg = malloc(sizeof(struct CtdlMessage));
1070                 memset(msg, 0, sizeof(struct CtdlMessage));
1071                 msg->cm_magic = CTDLMESSAGE_MAGIC;
1072                 msg->cm_anon_type = MES_NORMAL;
1073                 msg->cm_format_type = FMT_RFC822;
1074                 msg->cm_fields['M'] = malloc(strlen(instr)+SIZ);
1075                 snprintf(msg->cm_fields['M'],
1076                         strlen(instr)+SIZ,
1077                         "Content-type: %s\n\n%s\n"
1078                         "attempted|%ld\n"
1079                         "retry|%ld\n",
1080                         SPOOLMIME, instr, (long)time(NULL), (long)retry );
1081                 CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
1082                 CtdlFreeMessage(msg);
1083         }
1084
1085         free(instr);
1086 }
1087
1088
1089 /*****************************************************************************/
1090 /*                          SMTP UTILITY COMMANDS                            */
1091 /*****************************************************************************/
1092
1093 void cmd_smtp(char *argbuf) {
1094         char cmd[64];
1095         char node[256];
1096         char buf[1024];
1097         int i;
1098         int num_mxhosts;
1099
1100         if (CtdlAccessCheck(ac_aide)) return;
1101
1102         extract_token(cmd, argbuf, 0, '|', sizeof cmd);
1103
1104         if (!strcasecmp(cmd, "mx")) {
1105                 extract_token(node, argbuf, 1, '|', sizeof node);
1106                 num_mxhosts = getmx(buf, node);
1107                 cprintf("%d %d MX hosts listed for %s\n",
1108                         LISTING_FOLLOWS, num_mxhosts, node);
1109                 for (i=0; i<num_mxhosts; ++i) {
1110                         extract_token(node, buf, i, '|', sizeof node);
1111                         cprintf("%s\n", node);
1112                 }
1113                 cprintf("000\n");
1114                 return;
1115         }
1116
1117         else if (!strcasecmp(cmd, "runqueue")) {
1118                 run_queue_now = 1;
1119                 cprintf("%d All outbound SMTP will be retried now.\n", CIT_OK);
1120                 return;
1121         }
1122
1123         else {
1124                 cprintf("%d Invalid command.\n", ERROR + ILLEGAL_VALUE);
1125         }
1126
1127 }
1128
1129
1130 /*
1131  * smtp_queue_thread()
1132  * 
1133  * Run through the queue sending out messages.
1134  */
1135 void *smtp_queue_thread(void *arg) {
1136         int num_processed = 0;
1137         struct CitContext smtp_queue_CC;
1138
1139         CtdlFillSystemContext(&smtp_queue_CC, "SMTP Send");
1140         citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
1141         CtdlLogPrintf(CTDL_DEBUG, "smtp_queue_thread() initializing\n");
1142
1143         while (!CtdlThreadCheckStop()) {
1144                 
1145                 CtdlLogPrintf(CTDL_INFO, "SMTP client: processing outbound queue\n");
1146
1147                 if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) {
1148                         CtdlLogPrintf(CTDL_ERR, "Cannot find room <%s>\n", SMTP_SPOOLOUT_ROOM);
1149                 }
1150                 else {
1151                         num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, NULL);
1152                 }
1153                 CtdlLogPrintf(CTDL_INFO, "SMTP client: queue run completed; %d messages processed\n", num_processed);
1154                 CtdlThreadSleep(60);
1155         }
1156
1157         CtdlClearSystemContext();
1158         return(NULL);
1159 }
1160
1161
1162 /*
1163  * Initialize the SMTP outbound queue
1164  */
1165 void smtp_init_spoolout(void) {
1166         struct ctdlroom qrbuf;
1167
1168         /*
1169          * Create the room.  This will silently fail if the room already
1170          * exists, and that's perfectly ok, because we want it to exist.
1171          */
1172         CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_MAILBOX);
1173
1174         /*
1175          * Make sure it's set to be a "system room" so it doesn't show up
1176          * in the <K>nown rooms list for Aides.
1177          */
1178         if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) {
1179                 qrbuf.QRflags2 |= QR2_SYSTEM;
1180                 CtdlPutRoomLock(&qrbuf);
1181         }
1182 }
1183
1184
1185 SMTPReadHandler ReadHandlers[eMaxSMTPC] = {
1186         SMTPC_read_greeting,
1187         SMTPC_read_EHLO_reply,
1188         SMTPC_read_HELO_reply,
1189         SMTPC_read_auth_reply,
1190         SMTPC_read_FROM_reply,
1191         SMTPC_read_RCPT_reply,
1192         SMTPC_read_DATAcmd_reply,
1193         SMTPC_read_dummy,
1194         SMTPC_read_data_body_reply,
1195         SMTPC_read_QUIT_reply
1196 };
1197
1198 SMTPSendHandler SendHandlers[eMaxSMTPC] = {
1199         SMTPC_send_dummy, /* we don't send a greeting, the server does... */
1200         SMTPC_send_EHLO,
1201         STMPC_send_HELO,
1202         SMTPC_send_auth,
1203         SMTPC_send_FROM,
1204         SMTPC_send_RCPT,
1205         SMTPC_send_DATAcmd,
1206         SMTPC_send_data_body,
1207         SMTPC_send_terminate_data_body,
1208         SMTPC_send_QUIT
1209 };
1210
1211 eNextState SMTP_C_DispatchReadDone(void *Data)
1212 {
1213         SmtpOutMsg *pMsg = Data;
1214         eNextState rc = ReadHandlers[pMsg->State](pMsg);
1215         pMsg->State++;
1216         return rc;
1217 }
1218
1219 eNextState SMTP_C_DispatchWriteDone(void *Data)
1220 {
1221         SmtpOutMsg *pMsg = Data;
1222         return SendHandlers[pMsg->State](pMsg);
1223         
1224 }
1225
1226
1227 #endif
1228 CTDL_MODULE_INIT(smtp_eventclient)
1229 {
1230 #ifdef EXPERIMENTAL_SMTP_EVENT_CLIENT
1231         if (!threading)
1232         {
1233                 smtp_init_spoolout();
1234                 CtdlThreadCreate("SMTPEvent Send", CTDLTHREAD_BIGSTACK, smtp_queue_thread, NULL);
1235
1236                 CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands");
1237         }
1238 #endif
1239         
1240         /* return our Subversion id for the Log */
1241         return "smtpeventclient";
1242 }