Logging enhancement & SMTP client fixes
[citadel.git] / citadel / modules / eventclient / serv_eventclient.c
1 /*
2  * Copyright (c) 1998-2009 by the citadel.org team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 #include "sysdep.h"
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <termios.h>
24 #include <fcntl.h>
25 #include <signal.h>
26 #include <pwd.h>
27 #include <errno.h>
28 #include <sys/types.h>
29 #include <syslog.h>
30
31 #if TIME_WITH_SYS_TIME
32 # include <sys/time.h>
33 # include <time.h>
34 #else
35 # if HAVE_SYS_TIME_H
36 #  include <sys/time.h>
37 # else
38 #  include <time.h>
39 # endif
40 #endif
41 #include <sys/wait.h>
42 #include <ctype.h>
43 #include <string.h>
44 #include <limits.h>
45 #include <sys/socket.h>
46 #include <netinet/in.h>
47 #include <arpa/inet.h>
48 #include <libcitadel.h>
49 #include <curl/curl.h>
50 #include <curl/multi.h>
51 #include "citadel.h"
52 #include "server.h"
53 #include "citserver.h"
54 #include "support.h"
55
56 #include "ctdl_module.h"
57
58 #include "event_client.h"
59 #include "serv_curl.h"
60
61 ev_loop *event_base;
62
63 long EvIDSource = 0;
64 /*****************************************************************************
65  *                   libevent / curl integration                             *
66  *****************************************************************************/
67 #define MOPT(s, v)                                                      \
68         do {                                                            \
69                 sta = curl_multi_setopt(mhnd, (CURLMOPT_##s), (v));     \
70                 if (sta) {                                              \
71                         syslog(LOG_ERR, "EVCURL: error setting option " #s " on curl multi handle: %s\n", curl_easy_strerror(sta)); \
72                         exit (1);                                       \
73                 }                                                       \
74         } while (0)
75
76 typedef struct _evcurl_global_data {
77         int magic;
78         CURLM *mhnd;
79         ev_timer timeev;
80         int nrun;
81 } evcurl_global_data;
82
83 ev_async WakeupCurl;
84 evcurl_global_data global;
85
86 static void
87 gotstatus(int nnrun) 
88 {
89         CURLM *mhnd;
90         CURLMsg *msg;
91         int nmsg;
92
93         global.nrun = nnrun;
94         mhnd = global.mhnd;
95
96         syslog(LOG_DEBUG, "CURLEV: gotstatus(): about to call curl_multi_info_read\n");
97         while ((msg = curl_multi_info_read(mhnd, &nmsg))) {
98                 syslog(LOG_ERR, "EVCURL: got curl multi_info message msg=%d\n", msg->msg);
99                 if (CURLMSG_DONE == msg->msg) {
100                         CURL *chnd;
101                         char *chandle;
102                         CURLcode sta;
103                         CURLMcode msta;
104                         AsyncIO  *IO;
105
106                         chandle = NULL;;
107                         chnd = msg->easy_handle;
108                         sta = curl_easy_getinfo(chnd, CURLINFO_PRIVATE, &chandle);
109                         syslog(LOG_ERR, "EVCURL: request complete\n");
110                         if (sta)
111                                 syslog(LOG_ERR, "EVCURL: error asking curl for private cookie of curl handle: %s\n", curl_easy_strerror(sta));
112                         IO = (AsyncIO *)chandle;
113                         
114                         ev_io_stop(event_base, &IO->recv_event);
115                         ev_io_stop(event_base, &IO->send_event);
116
117                         sta = msg->data.result;
118                         if (sta) {
119                                 EV_syslog(LOG_ERR, "EVCURL: error description: %s\n", IO->HttpReq.errdesc);
120                                 EV_syslog(LOG_ERR, "EVCURL: error performing request: %s\n", curl_easy_strerror(sta));
121                         }
122                         sta = curl_easy_getinfo(chnd, CURLINFO_RESPONSE_CODE, &IO->HttpReq.httpcode);
123                         if (sta)
124                                 EV_syslog(LOG_ERR, "EVCURL: error asking curl for response code from request: %s\n", curl_easy_strerror(sta));
125                         EV_syslog(LOG_ERR, "EVCURL: http response code was %ld\n", (long)IO->HttpReq.httpcode);
126
127
128                         curl_slist_free_all(IO->HttpReq.headers);
129                         msta = curl_multi_remove_handle(mhnd, chnd);
130                         if (msta)
131                                 EV_syslog(LOG_ERR, "EVCURL: warning problem detaching completed handle from curl multi: %s\n", curl_multi_strerror(msta));
132
133                         curl_easy_cleanup(IO->HttpReq.chnd);
134                         IO->HttpReq.chnd = NULL;
135
136                         IO->HttpReq.attached = 0;
137                         IO->SendDone(IO);
138
139                         FreeStrBuf(&IO->HttpReq.ReplyData);
140                         FreeURL(&IO->ConnectMe);
141                         RemoveContext(IO->CitContext);
142                         IO->Terminate(IO);
143                 }
144         }
145 }
146
147 static void
148 stepmulti(void *data, curl_socket_t fd, int which)
149 {
150         int running_handles = 0;
151         CURLMcode msta;
152         
153         msta = curl_multi_socket_action(global.mhnd, fd, which, &running_handles);
154         syslog(LOG_DEBUG, "EVCURL: stepmulti(): calling gotstatus()\n");
155         if (msta)
156                 syslog(LOG_ERR, "EVCURL: error in curl processing events on multi handle, fd %d: %s\n", (int)fd, curl_multi_strerror(msta));
157         if (global.nrun != running_handles)
158                 gotstatus(running_handles);
159 }
160
161 static void
162 gottime(struct ev_loop *loop, ev_timer *timeev, int events) {
163         syslog(LOG_DEBUG, "EVCURL: waking up curl for timeout\n");
164         stepmulti(NULL, CURL_SOCKET_TIMEOUT, 0);
165 }
166
167 static void
168 got_in(struct ev_loop *loop, ev_io *ioev, int events) {
169         syslog(LOG_DEBUG, "EVCURL: waking up curl for io on fd %d\n", (int)ioev->fd);
170         stepmulti(ioev->data, ioev->fd, CURL_CSELECT_IN);
171 }
172
173 static void
174 got_out(struct ev_loop *loop, ev_io *ioev, int events) {
175         syslog(LOG_DEBUG, "EVCURL: waking up curl for io on fd %d\n", (int)ioev->fd);
176         stepmulti(ioev->data, ioev->fd, CURL_CSELECT_OUT);
177 }
178
179 static size_t
180 gotdata(void *data, size_t size, size_t nmemb, void *cglobal) {
181         AsyncIO *IO = (AsyncIO*) cglobal;
182
183         if (IO->HttpReq.ReplyData == NULL)
184         {
185             IO->HttpReq.ReplyData = NewStrBufPlain(NULL, SIZ);
186         }
187         return CurlFillStrBuf_callback(data, size, nmemb, IO->HttpReq.ReplyData);
188 }
189
190 static int
191 gotwatchtime(CURLM *multi, long tblock_ms, void *cglobal) {
192         syslog(LOG_DEBUG, "EVCURL: gotwatchtime called %ld ms\n", tblock_ms);
193         evcurl_global_data *global = cglobal;
194         ev_timer_stop(EV_DEFAULT, &global->timeev);
195         if (tblock_ms < 0 || 14000 < tblock_ms)
196                 tblock_ms = 14000;
197         ev_timer_set(&global->timeev, 0.5e-3 + 1.0e-3 * tblock_ms, 14.0);
198         ev_timer_start(EV_DEFAULT_UC, &global->timeev);
199         curl_multi_perform(global, CURL_POLL_NONE);
200         return 0;
201 }
202
203 static int
204 gotwatchsock(CURL *easy, curl_socket_t fd, int action, void *cglobal, void *vIO) {
205         evcurl_global_data *global = cglobal;
206         CURLM *mhnd = global->mhnd;
207         char *f;
208         AsyncIO *IO = (AsyncIO*) vIO;
209         CURLcode sta;
210
211         EV_syslog(LOG_DEBUG, "EVCURL: gotwatchsock called fd=%d action=%d\n", (int)fd, action);
212         if (IO == NULL) {
213                 sta = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &f);
214                 if (sta) {
215                         EV_syslog(LOG_ERR, "EVCURL: error asking curl for private cookie of curl handle: %s\n", curl_easy_strerror(sta));
216                         return -1;
217                 }
218                 IO = (AsyncIO *) f;
219                 if (IO->SendBuf.fd != 0)
220                 {
221                         ev_io_stop(event_base, &IO->recv_event);
222                         ev_io_stop(event_base, &IO->send_event);
223                 }
224                 IO->SendBuf.fd = fd;
225                 ev_io_init(&IO->recv_event, &got_in, fd, EV_READ);
226                 ev_io_init(&IO->send_event, &got_out, fd, EV_WRITE);
227                 curl_multi_assign(mhnd, fd, IO);
228
229         }
230
231         switch (action)
232         {
233         case CURL_POLL_NONE:
234                 EVM_syslog(LOG_ERR,"EVCURL: called first time to register this sockwatcker\n");
235                 break;
236         case CURL_POLL_REMOVE:
237                 EVM_syslog(LOG_ERR,"EVCURL: called last time to unregister this sockwatcher\n");
238                 ev_io_stop(event_base, &IO->recv_event);
239                 ev_io_stop(event_base, &IO->send_event);
240                 break;
241         case CURL_POLL_IN:
242                 ev_io_stop(event_base, &IO->recv_event);
243                 ev_io_start(event_base, &IO->send_event);
244                 break;
245         case CURL_POLL_OUT:
246                 ev_io_stop(event_base, &IO->send_event);
247                 ev_io_start(event_base, &IO->recv_event);
248                 break;
249         case CURL_POLL_INOUT:
250                 ev_io_start(event_base, &IO->send_event);
251                 ev_io_start(event_base, &IO->recv_event);
252                 break;
253         }
254         return 0;
255 }
256
257 void curl_init_connectionpool(void) 
258 {
259         CURLM *mhnd ;
260
261         ev_timer_init(&global.timeev, &gottime, 14.0, 14.0);
262         global.timeev.data = (void *)&global;
263         global.nrun = -1;
264         CURLcode sta = curl_global_init(CURL_GLOBAL_ALL);
265
266         if (sta) 
267         {
268                 syslog(LOG_ERR,"EVCURL: error initializing curl library: %s\n", curl_easy_strerror(sta));
269                 exit(1);
270         }
271         mhnd = global.mhnd = curl_multi_init();
272         if (!mhnd)
273         {
274                 syslog(LOG_ERR,"EVCURL: error initializing curl multi handle\n");
275                 exit(3);
276         }
277
278         MOPT(SOCKETFUNCTION, &gotwatchsock);
279         MOPT(SOCKETDATA, (void *)&global);
280         MOPT(TIMERFUNCTION, &gotwatchtime);
281         MOPT(TIMERDATA, (void *)&global);
282
283         return;
284 }
285
286
287
288
289 int evcurl_init(AsyncIO *IO, 
290                 void *CustomData, 
291                 const char* Desc,
292                 IO_CallBack CallBack, 
293                 IO_CallBack Terminate)
294 {
295         CURLcode sta;
296         CURL *chnd;
297
298         EVM_syslog(LOG_DEBUG, "EVCURL: evcurl_init called ms\n");
299         IO->HttpReq.attached = 0;
300         IO->SendDone = CallBack;
301         IO->Terminate = Terminate;
302         chnd = IO->HttpReq.chnd = curl_easy_init();
303         if (!chnd)
304         {
305                 EVM_syslog(LOG_ERR, "EVCURL: error initializing curl handle\n");
306                 return 1;
307         }
308
309         strcpy(IO->HttpReq.errdesc, Desc);
310
311         OPT(VERBOSE, (long)1);
312                 /* unset in production */
313         OPT(NOPROGRESS, (long)1); 
314         OPT(NOSIGNAL, (long)1);
315         OPT(FAILONERROR, (long)1);
316         OPT(ENCODING, "");
317         OPT(FOLLOWLOCATION, (long)1);
318         OPT(MAXREDIRS, (long)7);
319         OPT(USERAGENT, CITADEL);
320
321         OPT(TIMEOUT, (long)1800);
322         OPT(LOW_SPEED_LIMIT, (long)64);
323         OPT(LOW_SPEED_TIME, (long)600);
324         OPT(CONNECTTIMEOUT, (long)600); 
325         OPT(PRIVATE, (void *)IO);
326
327         OPT(FORBID_REUSE, 1);
328         OPT(WRITEFUNCTION, &gotdata); 
329         OPT(WRITEDATA, (void *)IO);
330         OPT(ERRORBUFFER, IO->HttpReq.errdesc);
331
332         if (
333                 (!IsEmptyStr(config.c_ip_addr))
334                 && (strcmp(config.c_ip_addr, "*"))
335                 && (strcmp(config.c_ip_addr, "::"))
336                 && (strcmp(config.c_ip_addr, "0.0.0.0"))
337         ) {
338                 OPT(INTERFACE, config.c_ip_addr);
339         }
340                 /* point to a structure that points back to the perl structure and stuff */
341         EV_syslog(LOG_DEBUG, "EVCURL: Loading URL: %s\n", IO->ConnectMe->PlainUrl);
342         OPT(URL, IO->ConnectMe->PlainUrl);
343         if (StrLength(IO->ConnectMe->CurlCreds))
344         {
345                 OPT(HTTPAUTH, (long)CURLAUTH_BASIC);
346                 OPT(USERPWD, ChrPtr(IO->ConnectMe->CurlCreds));
347         }
348 #ifdef CURLOPT_HTTP_CONTENT_DECODING
349         OPT(HTTP_CONTENT_DECODING, 1);
350         OPT(ENCODING, "");
351 #endif
352         if (StrLength(IO->HttpReq.PostData) > 0)
353         { 
354                 OPT(POSTFIELDS, ChrPtr(IO->HttpReq.PostData));
355                 OPT(POSTFIELDSIZE, StrLength(IO->HttpReq.PostData));
356
357         }
358         else if ((IO->HttpReq.PlainPostDataLen != 0) && (IO->HttpReq.PlainPostData != NULL))
359         {
360                 OPT(POSTFIELDS, IO->HttpReq.PlainPostData);
361                 OPT(POSTFIELDSIZE, IO->HttpReq.PlainPostDataLen);
362         }
363
364         IO->HttpReq.headers = curl_slist_append(IO->HttpReq.headers, "Connection: close");
365         OPT(HTTPHEADER, IO->HttpReq.headers);
366
367         return 1;
368 }
369
370 eNextState
371 evcurl_handle_start(AsyncIO *IO) 
372 {
373         CURLMcode msta;
374         IO->NextState = eConnect;
375         EVM_syslog(LOG_DEBUG, "EVCURL: attaching to curl multi handle\n");
376         msta = curl_multi_add_handle(global.mhnd, IO->HttpReq.chnd);
377         if (msta)
378                 EV_syslog(LOG_ERR, "EVCURL: error attaching to curl multi handle: %s\n", curl_multi_strerror(msta));
379         IO->HttpReq.attached = 1;
380         ev_async_send (event_base, &WakeupCurl);
381         return eReadMessage;
382 }
383
384 static void WakeupCurlCallback(EV_P_ ev_async *w, int revents)
385 {
386         syslog(LOG_DEBUG, "EVCURL: waking up curl multi handle\n");
387
388         curl_multi_perform(&global, CURL_POLL_NONE);
389 }
390
391 static void evcurl_shutdown (void)
392 {
393         curl_multi_cleanup(global.mhnd);
394 }
395 /*****************************************************************************
396  *                       libevent integration                                *
397  *****************************************************************************/
398 /*
399  * client event queue plus its methods.
400  * this currently is the main loop (which may change in some future?)
401  */
402 int evbase_count = 0;
403 int event_add_pipe[2] = {-1, -1};
404 pthread_mutex_t EventQueueMutex; /* locks the access to the following vars: */
405 HashList *QueueEvents = NULL;
406 HashList *InboundEventQueue = NULL;
407 HashList *InboundEventQueues[2] = { NULL, NULL };
408
409 ev_async AddJob;   
410 ev_async ExitEventLoop;
411
412 static void QueueEventAddCallback(EV_P_ ev_async *w, int revents)
413 {
414         HashList *q;
415         void *v;
416         HashPos  *It;
417         long len;
418         const char *Key;
419
420         /* get the control command... */
421         pthread_mutex_lock(&EventQueueMutex);
422
423         if (InboundEventQueues[0] == InboundEventQueue) {
424                 InboundEventQueue = InboundEventQueues[1];
425                 q = InboundEventQueues[0];
426         }
427         else {
428                 InboundEventQueue = InboundEventQueues[0];
429                 q = InboundEventQueues[1];
430         }
431         pthread_mutex_unlock(&EventQueueMutex);
432
433         It = GetNewHashPos(q, 0);
434         while (GetNextHashPos(q, It, &len, &Key, &v))
435         {
436                 IOAddHandler *h = v;
437                 if (h->IO->ID == 0)
438                         h->IO->ID = EvIDSource++;
439                 h->EvAttch(h->IO);
440         }
441         DeleteHashPos(&It);
442         DeleteHashContent(&q);
443         syslog(LOG_DEBUG, "EVENT Q Read done.\n");
444 }
445
446
447 static void EventExitCallback(EV_P_ ev_async *w, int revents)
448 {
449         ev_break(event_base, EVBREAK_ALL);
450
451         syslog(LOG_DEBUG, "EVENT Q exiting.\n");
452 }
453
454
455
456 void InitEventQueue(void)
457 {
458         struct rlimit LimitSet;
459
460         pthread_mutex_init(&EventQueueMutex, NULL);
461
462         if (pipe(event_add_pipe) != 0) {
463                 syslog(LOG_EMERG, "Unable to create pipe for libev queueing: %s\n", strerror(errno));
464                 abort();
465         }
466         LimitSet.rlim_cur = 1;
467         LimitSet.rlim_max = 1;
468         setrlimit(event_add_pipe[1], &LimitSet);
469
470         QueueEvents = NewHash(1, Flathash);
471         InboundEventQueues[0] = NewHash(1, Flathash);
472         InboundEventQueues[1] = NewHash(1, Flathash);
473         InboundEventQueue = InboundEventQueues[0];
474 }
475 /*
476  * this thread operates the select() etc. via libev.
477  * 
478  * 
479  */
480 void *client_event_thread(void *arg) 
481 {
482         struct CitContext libev_client_CC;
483
484         CtdlFillSystemContext(&libev_client_CC, "LibEv Thread");
485 //      citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
486         syslog(LOG_DEBUG, "client_ev_thread() initializing\n");
487
488         event_base = ev_default_loop (EVFLAG_AUTO);
489
490         ev_async_init(&AddJob, QueueEventAddCallback);
491         ev_async_start(event_base, &AddJob);
492         ev_async_init(&ExitEventLoop, EventExitCallback);
493         ev_async_start(event_base, &ExitEventLoop);
494         ev_async_init(&WakeupCurl, WakeupCurlCallback);
495         ev_async_start(event_base, &WakeupCurl);
496
497         curl_init_connectionpool();
498
499         ev_run (event_base, 0);
500
501
502 ///what todo here?      CtdlClearSystemContext();
503         ev_loop_destroy (EV_DEFAULT_UC);
504         
505         DeleteHash(&QueueEvents);
506         InboundEventQueue = NULL;
507         DeleteHash(&InboundEventQueues[0]);
508         DeleteHash(&InboundEventQueues[1]);
509 /*      citthread_mutex_destroy(&EventQueueMutex); TODO */
510         evcurl_shutdown();
511
512         return(NULL);
513 }
514 /*------------------------------------------------------------------------------*/
515 /*
516  * DB-Queue; does async bdb operations.
517  * has its own set of handlers.
518  */
519 ev_loop *event_db;
520 int evdb_count = 0;
521 int evdb_add_pipe[2] = {-1, -1};
522 pthread_mutex_t DBEventQueueMutex; /* locks the access to the following vars: */
523 HashList *DBQueueEvents = NULL;
524 HashList *DBInboundEventQueue = NULL;
525 HashList *DBInboundEventQueues[2] = { NULL, NULL };
526
527 ev_async DBAddJob;   
528 ev_async DBExitEventLoop;
529
530 extern void ShutDownDBCLient(AsyncIO *IO);
531
532 static void DBQueueEventAddCallback(EV_P_ ev_async *w, int revents)
533 {
534         HashList *q;
535         void *v;
536         HashPos  *It;
537         long len;
538         const char *Key;
539
540         /* get the control command... */
541         pthread_mutex_lock(&DBEventQueueMutex);
542
543         if (DBInboundEventQueues[0] == DBInboundEventQueue) {
544                 DBInboundEventQueue = DBInboundEventQueues[1];
545                 q = DBInboundEventQueues[0];
546         }
547         else {
548                 DBInboundEventQueue = DBInboundEventQueues[0];
549                 q = DBInboundEventQueues[1];
550         }
551         pthread_mutex_unlock(&DBEventQueueMutex);
552
553         It = GetNewHashPos(q, 0);
554         while (GetNextHashPos(q, It, &len, &Key, &v))
555         {
556                 IOAddHandler *h = v;
557                 eNextState rc;
558                 if (h->IO->ID == 0)
559                         h->IO->ID = EvIDSource++;
560                 rc = h->EvAttch(h->IO);
561                 switch (rc)
562                 {
563                 case eAbort:
564                     ShutDownDBCLient(h->IO);
565                 default:
566                     break;
567                 }
568         }
569         DeleteHashPos(&It);
570         DeleteHashContent(&q);
571         syslog(LOG_DEBUG, "DBEVENT Q Read done.\n");
572 }
573
574
575 static void DBEventExitCallback(EV_P_ ev_async *w, int revents)
576 {
577         syslog(LOG_DEBUG, "EVENT Q exiting.\n");
578         ev_break(event_db, EVBREAK_ALL);
579 }
580
581
582
583 void DBInitEventQueue(void)
584 {
585         struct rlimit LimitSet;
586
587         pthread_mutex_init(&DBEventQueueMutex, NULL);
588
589         if (pipe(evdb_add_pipe) != 0) {
590                 syslog(LOG_EMERG, "Unable to create pipe for libev queueing: %s\n", strerror(errno));
591                 abort();
592         }
593         LimitSet.rlim_cur = 1;
594         LimitSet.rlim_max = 1;
595         setrlimit(evdb_add_pipe[1], &LimitSet);
596
597         DBQueueEvents = NewHash(1, Flathash);
598         DBInboundEventQueues[0] = NewHash(1, Flathash);
599         DBInboundEventQueues[1] = NewHash(1, Flathash);
600         DBInboundEventQueue = DBInboundEventQueues[0];
601 }
602
603 /*
604  * this thread operates writing to the message database via libev.
605  * 
606  * 
607  */
608 void *db_event_thread(void *arg) 
609 {
610         struct CitContext libev_msg_CC;
611
612         CtdlFillSystemContext(&libev_msg_CC, "LibEv DB IO Thread");
613 //      citthread_setspecific(MyConKey, (void *)&smtp_queue_CC);
614         syslog(LOG_DEBUG, "client_msgev_thread() initializing\n");
615
616         event_db = ev_loop_new (EVFLAG_AUTO);
617
618         ev_async_init(&DBAddJob, DBQueueEventAddCallback);
619         ev_async_start(event_db, &DBAddJob);
620         ev_async_init(&DBExitEventLoop, DBEventExitCallback);
621         ev_async_start(event_db, &DBExitEventLoop);
622
623         ev_run (event_db, 0);
624
625
626 //// what to do here?   CtdlClearSystemContext();
627         ev_loop_destroy (event_db);
628
629         DeleteHash(&DBQueueEvents);
630         DBInboundEventQueue = NULL;
631         DeleteHash(&DBInboundEventQueues[0]);
632         DeleteHash(&DBInboundEventQueues[1]);
633 /*      citthread_mutex_destroy(&DBEventQueueMutex); TODO */
634
635         return(NULL);
636 }
637
638 CTDL_MODULE_INIT(event_client)
639 {
640         if (!threading)
641         {
642                 InitEventQueue();
643                 DBInitEventQueue();
644                 CtdlThreadCreate(/*"Client event", */ client_event_thread);
645                 CtdlThreadCreate(/*"DB event", */db_event_thread);
646 /// todo register shutdown callback.
647         }
648         return "event";
649 }