4b4e44c08ab958bdb32478fa6e1629dbaaf3cd8b
[citadel.git] / citadel / serv_extensions.c
1 /*
2  * Citadel Dynamic Loading Module
3  * Written by Brian Costello <btx@calyx.net>
4  *
5  * Copyright (c) 1987-2011 by the citadel.org team
6  *
7  * This program is open source software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License, version 3.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  */
15
16 #include <stdio.h>
17 #include <libcitadel.h>
18
19 #include "sysdep_decls.h"
20 #include "modules/crypto/serv_crypto.h" /* Needed until a universal crypto startup hook is implimented for CtdlStartTLS */
21
22 #include "serv_extensions.h"
23 #include "ctdl_module.h"
24
25
26 int DebugModules = 0;
27  
28 /*
29  * Structure defentitions for hook tables
30  */
31
32 HashList *LogDebugEntryTable = NULL;
33
34 typedef struct LogFunctionHook LogFunctionHook;
35 struct LogFunctionHook {
36         LogFunctionHook *next;
37         int loglevel;
38         void (*h_function_pointer) (char *);
39 };
40
41 LogFunctionHook *LogHookTable = NULL;
42
43 typedef struct FixedOutputHook FixedOutputHook;
44 struct FixedOutputHook {
45         FixedOutputHook *next;
46         char content_type[64];
47         void (*h_function_pointer) (char *, int);
48 };
49 FixedOutputHook *FixedOutputTable = NULL;
50
51
52
53 /*
54  * TDAPVetoHookFunctionHook extensions are used for any type of hook for which
55  * may prevent the autopurger to run for this specific data class.
56  * the function should at least LOG_INFO that it does so.
57  */
58 typedef struct TDAPVetoHookFunctionHook TDAPVetoHookFunctionHook;
59 struct TDAPVetoHookFunctionHook {
60         TDAPVetoHookFunctionHook *next;
61         int Priority;
62         int (*h_function_pointer) (StrBuf *);
63         int eventtype;
64 };
65 TDAPVetoHookFunctionHook *TDAPVetoHookTable = NULL;
66
67
68
69 /*
70  * SessionFunctionHook extensions are used for any type of hook for which
71  * the context in which it's being called (which is determined by the event
72  * type) will make it obvious for the hook function to know where to look for
73  * pertinent data.
74  */
75 typedef struct SessionFunctionHook SessionFunctionHook;
76 struct SessionFunctionHook {
77         SessionFunctionHook *next;
78         int Priority;
79         void (*h_function_pointer) (void);
80         int eventtype;
81 };
82 SessionFunctionHook *SessionHookTable = NULL;
83
84 /*
85  * UserFunctionHook extensions are used for any type of hook which implements
86  * an operation on a user or username (potentially) other than the one
87  * operating the current session.
88  */
89 typedef struct UserFunctionHook UserFunctionHook;
90 struct UserFunctionHook {
91         UserFunctionHook *next;
92         void (*h_function_pointer) (struct ctdluser *usbuf);
93         int eventtype;
94 };
95 UserFunctionHook *UserHookTable = NULL;
96
97 /*
98  * MessageFunctionHook extensions are used for hooks which implement handlers
99  * for various types of message operations (save, read, etc.)
100  */
101 typedef struct MessageFunctionHook MessageFunctionHook;
102 struct MessageFunctionHook {
103         MessageFunctionHook *next;
104         int (*h_function_pointer) (struct CtdlMessage *msg, recptypes *recps);
105         int eventtype;
106 };
107 MessageFunctionHook *MessageHookTable = NULL;
108
109
110 /*
111  * NetprocFunctionHook extensions are used for hooks which implement handlers
112  * for incoming network messages.
113  */
114 typedef struct NetprocFunctionHook NetprocFunctionHook;
115 struct NetprocFunctionHook {
116         NetprocFunctionHook *next;
117         int (*h_function_pointer) (struct CtdlMessage *msg, char *target_room);
118 };
119 NetprocFunctionHook *NetprocHookTable = NULL;
120
121
122 /*
123  * DeleteFunctionHook extensions are used for hooks which get called when a
124  * message is about to be deleted.
125  */
126 typedef struct DeleteFunctionHook DeleteFunctionHook;
127 struct DeleteFunctionHook {
128         DeleteFunctionHook *next;
129         void (*h_function_pointer) (char *target_room, long msgnum);
130 };
131 DeleteFunctionHook *DeleteHookTable = NULL;
132
133
134 /*
135  * ExpressMessageFunctionHook extensions are used for hooks which implement
136  * the sending of an instant message through various channels.  Any function
137  * registered should return the number of recipients to whom the message was
138  * successfully transmitted.
139  */
140 typedef struct XmsgFunctionHook XmsgFunctionHook;
141 struct XmsgFunctionHook {
142         XmsgFunctionHook *next;
143         int (*h_function_pointer) (char *, char *, char *, char *);
144         int order;
145 };
146 XmsgFunctionHook *XmsgHookTable = NULL;
147
148
149
150
151 /*
152  * RoomFunctionHook extensions are used for hooks which impliment room
153  * processing functions when new messages are added EG. SIEVE.
154  */
155 typedef struct RoomFunctionHook RoomFunctionHook;
156 struct RoomFunctionHook {
157         RoomFunctionHook *next;
158         int (*fcn_ptr) (struct ctdlroom *);
159 };
160 RoomFunctionHook *RoomHookTable = NULL;
161
162
163
164 typedef struct SearchFunctionHook SearchFunctionHook;
165 struct SearchFunctionHook {
166         SearchFunctionHook *next;
167         void (*fcn_ptr) (int *, long **, const char *);
168         char *name;
169 };
170 SearchFunctionHook *SearchFunctionHookTable = NULL;
171
172 CleanupFunctionHook *CleanupHookTable = NULL;
173 CleanupFunctionHook *EVCleanupHookTable = NULL;
174
175 ServiceFunctionHook *ServiceHookTable = NULL;
176
177 typedef struct ProtoFunctionHook ProtoFunctionHook;
178 struct ProtoFunctionHook {
179         void (*handler) (char *cmdbuf);
180         const char *cmd;
181         const char *desc;
182 };
183
184 HashList *ProtoHookList = NULL;
185
186
187 #define ERR_PORT (1 << 1)
188
189
190 static StrBuf *portlist = NULL;
191
192 static StrBuf *errormessages = NULL;
193
194
195 long   DetailErrorFlags;
196 ConstStr Empty = {HKEY("")};
197 char *ErrSubject = "Startup Problems";
198 ConstStr ErrGeneral[] = {
199         {HKEY("Citadel had trouble on starting up. ")},
200         {HKEY(" This means, citadel won't be the service provider for a specific service you configured it to.\n\n"
201               "If you don't want citadel to provide these services, turn them off in WebCit via: ")},
202         {HKEY("To make both ways actualy take place restart the citserver with \"sendcommand down\"\n\n"
203               "The errors returned by the system were:\n")},
204         {HKEY("You can recheck the above if you follow this faq item:\n"
205               "http://www.citadel.org/doku.php?id=faq:mastering_your_os:net#netstat")}
206 };
207
208 ConstStr ErrPortShort = { HKEY("We couldn't bind all ports you configured to be provided by citadel server.\n")};
209 ConstStr ErrPortWhere = { HKEY("\"Admin->System Preferences->Network\".\n\nThe failed ports and sockets are: ")};
210 ConstStr ErrPortHint  = { HKEY("If you want citadel to provide you with that functionality, "
211                                "check the output of \"netstat -lnp\" on linux Servers or \"netstat -na\" on *BSD"
212                                " and stop the program that binds these ports.\n You should eventually remove "
213                                " their initscripts in /etc/init.d so that you won't get this trouble once more.\n"
214                                " After that goto \"Administration -> Shutdown Citadel\" to make Citadel restart & retry to bind this port.\n")};
215
216
217 void LogPrintMessages(long err)
218 {
219         StrBuf *Message;
220         StrBuf *List, *DetailList;
221         ConstStr *Short, *Where, *Hint; 
222
223         
224         Message = NewStrBufPlain(NULL, 
225                                  StrLength(portlist) + StrLength(errormessages));
226         
227         DetailErrorFlags = DetailErrorFlags & ~err;
228
229         switch (err)
230         {
231         case ERR_PORT:
232                 Short = &ErrPortShort;
233                 Where = &ErrPortWhere;
234                 Hint  = &ErrPortHint;
235                 List  = portlist;
236                 DetailList = errormessages;
237                 break;
238         default:
239                 Short = &Empty;
240                 Where = &Empty;
241                 Hint  = &Empty;
242                 List  = NULL;
243                 DetailList = NULL;
244         }
245
246         StrBufAppendBufPlain(Message, CKEY(ErrGeneral[0]), 0);
247         StrBufAppendBufPlain(Message, CKEY(*Short), 0); 
248         StrBufAppendBufPlain(Message, CKEY(ErrGeneral[1]), 0);
249         StrBufAppendBufPlain(Message, CKEY(*Where), 0);
250         StrBufAppendBuf(Message, List, 0);
251         StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
252         StrBufAppendBufPlain(Message, CKEY(*Hint), 0);
253         StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
254         StrBufAppendBufPlain(Message, CKEY(ErrGeneral[2]), 0);
255         StrBufAppendBuf(Message, DetailList, 0);
256         StrBufAppendBufPlain(Message, HKEY("\n\n"), 0);
257         StrBufAppendBufPlain(Message, CKEY(ErrGeneral[3]), 0);
258
259         MOD_syslog(LOG_EMERG, "%s", ChrPtr(Message));
260         MOD_syslog(LOG_EMERG, "%s", ErrSubject);
261         quickie_message("Citadel", NULL, NULL, AIDEROOM, ChrPtr(Message), FMT_FIXED, ErrSubject);
262
263         FreeStrBuf(&Message);
264         FreeStrBuf(&List);
265         FreeStrBuf(&DetailList);
266 }
267
268
269 void AddPortError(char *Port, char *ErrorMessage)
270 {
271         long len;
272
273         DetailErrorFlags |= ERR_PORT;
274
275         len = StrLength(errormessages);
276         if (len > 0) StrBufAppendBufPlain(errormessages, HKEY("; "), 0);
277         else errormessages = NewStrBuf();
278         StrBufAppendBufPlain(errormessages, ErrorMessage, -1, 0);
279
280
281         len = StrLength(portlist);
282         if (len > 0) StrBufAppendBufPlain(portlist, HKEY(";"), 0);
283         else portlist = NewStrBuf();
284         StrBufAppendBufPlain(portlist, Port, -1, 0);
285 }
286
287
288 int DLoader_Exec_Cmd(char *cmdbuf)
289 {
290         void *vP;
291         ProtoFunctionHook *p;
292
293         if (GetHash(ProtoHookList, cmdbuf, 4, &vP) && (vP != NULL)) {
294                 p = (ProtoFunctionHook*) vP;
295                 p->handler(&cmdbuf[5]);
296                 return 1;
297         }
298         return 0;
299 }
300
301 long FourHash(const char *key, long length) 
302 {
303         int i;
304         int ret = 0;
305         const unsigned char *ptr = (const unsigned char*)key;
306
307         for (i = 0; i < 4; i++, ptr ++) 
308                 ret = (ret << 8) | 
309                         ( ((*ptr >= 'a') &&
310                            (*ptr <= 'z'))? 
311                           *ptr - 'a' + 'A': 
312                           *ptr);
313
314         return ret;
315 }
316
317 void CtdlRegisterDebugFlagHook(const char *Name, long Len, CtdlDbgFunction F, const int *LogP)
318 {
319         LogDebugEntry *E;
320         if (LogDebugEntryTable == NULL)
321                 LogDebugEntryTable = NewHash(1, NULL);
322         E = (LogDebugEntry*) malloc(sizeof(LogDebugEntry));
323         E->F = F;
324         E->Name = Name;
325         E->Len = Len;
326         E->LogP = LogP;
327         Put(LogDebugEntryTable, Name, Len, E, NULL);
328         
329 }
330 void CtdlSetDebugLogFacilities(const char **Str, long n)
331 {
332         StrBuf *Token = NULL;
333         StrBuf *Buf = NULL;
334         const char *ch;
335         int i;
336         int DoAll = 0;
337         void *vptr;
338
339         for (i=0; i < n; i++){
340                 if ((Str[i] != NULL) && !IsEmptyStr(Str[i])) {
341                         if (strcmp(Str[i], "all") == 0) {
342                                 DoAll = 1;
343                                 continue;
344                         }
345                         Buf = NewStrBufPlain(Str[i], -1);
346                         ch = NULL;
347                         if (Token == NULL)
348                                 Token = NewStrBufPlain(NULL, StrLength(Buf));
349                         while ((ch != StrBufNOTNULL) &&
350                                StrBufExtract_NextToken(Token, Buf, &ch, ',')) {
351                                 if (GetHash(LogDebugEntryTable, SKEY(Token), &vptr) && 
352                                     (vptr != NULL))
353                                 {
354                                         LogDebugEntry *E = (LogDebugEntry*)vptr;
355                                         E->F(1);
356                                 }
357                         }
358                 }
359                 FreeStrBuf(&Buf);
360         }
361         FreeStrBuf(&Token);
362         if (DoAll) {
363                 long HKLen;
364                 const char *ch;
365                 HashPos *Pos;
366
367                 Pos = GetNewHashPos(LogDebugEntryTable, 0);
368                 while (GetNextHashPos(LogDebugEntryTable, Pos, &HKLen, &ch, &vptr)) {
369                         LogDebugEntry *E = (LogDebugEntry*)vptr;
370                         E->F(1);
371                 }
372
373                 DeleteHashPos(&Pos);
374         }
375 }
376 void CtdlDestroyDebugTable(void)
377 {
378
379         DeleteHash(&LogDebugEntryTable);
380 }
381
382 void CtdlRegisterProtoHook(void (*handler) (char *), char *cmd, char *desc)
383 {
384         ProtoFunctionHook *p;
385
386         if (ProtoHookList == NULL)
387                 ProtoHookList = NewHash (1, FourHash);
388
389
390         p = (ProtoFunctionHook *)
391                 malloc(sizeof(ProtoFunctionHook));
392
393         if (p == NULL) {
394                 fprintf(stderr, "can't malloc new ProtoFunctionHook\n");
395                 exit(EXIT_FAILURE);
396         }
397         p->handler = handler;
398         p->cmd = cmd;
399         p->desc = desc;
400
401         Put(ProtoHookList, cmd, 4, p, NULL);
402         MOD_syslog(LOG_DEBUG, "Registered server command %s (%s)\n", cmd, desc);
403 }
404
405 void CtdlDestroyProtoHooks(void)
406 {
407
408         DeleteHash(&ProtoHookList);
409 }
410
411
412 void CtdlRegisterCleanupHook(void (*fcn_ptr) (void))
413 {
414
415         CleanupFunctionHook *newfcn;
416
417         newfcn = (CleanupFunctionHook *)
418             malloc(sizeof(CleanupFunctionHook));
419         newfcn->next = CleanupHookTable;
420         newfcn->h_function_pointer = fcn_ptr;
421         CleanupHookTable = newfcn;
422
423         MODM_syslog(LOG_DEBUG, "Registered a new cleanup function\n");
424 }
425
426
427 void CtdlUnregisterCleanupHook(void (*fcn_ptr) (void))
428 {
429         CleanupFunctionHook *cur, *p, *last;
430         last = NULL;
431         cur = CleanupHookTable;
432         while (cur != NULL)
433         {
434                 if (fcn_ptr == cur->h_function_pointer)
435                 {
436                         MODM_syslog(LOG_DEBUG, "Unregistered cleanup function\n");
437                         p = cur->next;
438
439                         free(cur);
440                         cur = NULL;
441
442                         if (last != NULL)
443                                 last->next = p;
444                         else 
445                                 CleanupHookTable = p;
446                         cur = p;
447                 }
448                 else {
449                         last = cur;
450                         cur = cur->next;
451                 }
452         }
453 }
454
455
456 void CtdlDestroyCleanupHooks(void)
457 {
458         CleanupFunctionHook *cur, *p;
459
460         cur = CleanupHookTable;
461         while (cur != NULL)
462         {
463                 MODM_syslog(LOG_DEBUG, "Destroyed cleanup function\n");
464                 p = cur->next;
465                 free(cur);
466                 cur = p;
467         }
468         CleanupHookTable = NULL;
469 }
470
471 void CtdlRegisterEVCleanupHook(void (*fcn_ptr) (void))
472 {
473
474         CleanupFunctionHook *newfcn;
475
476         newfcn = (CleanupFunctionHook *)
477             malloc(sizeof(CleanupFunctionHook));
478         newfcn->next = EVCleanupHookTable;
479         newfcn->h_function_pointer = fcn_ptr;
480         EVCleanupHookTable = newfcn;
481
482         MODM_syslog(LOG_DEBUG, "Registered a new cleanup function\n");
483 }
484
485
486 void CtdlUnregisterEVCleanupHook(void (*fcn_ptr) (void))
487 {
488         CleanupFunctionHook *cur, *p, *last;
489         last = NULL;
490         cur = EVCleanupHookTable;
491         while (cur != NULL)
492         {
493                 if (fcn_ptr == cur->h_function_pointer)
494                 {
495                         MODM_syslog(LOG_DEBUG, "Unregistered cleanup function\n");
496                         p = cur->next;
497
498                         free(cur);
499                         cur = NULL;
500
501                         if (last != NULL)
502                                 last->next = p;
503                         else 
504                                 EVCleanupHookTable = p;
505                         cur = p;
506                 }
507                 else {
508                         last = cur;
509                         cur = cur->next;
510                 }
511         }
512 }
513
514
515 void CtdlDestroyEVCleanupHooks(void)
516 {
517         CleanupFunctionHook *cur, *p;
518
519         cur = EVCleanupHookTable;
520         while (cur != NULL)
521         {
522                 MODM_syslog(LOG_DEBUG, "Destroyed cleanup function\n");
523                 p = cur->next;
524                 cur->h_function_pointer();
525                 free(cur);
526                 cur = p;
527         }
528         EVCleanupHookTable = NULL;
529 }
530
531 void CtdlRegisterTDAPVetoHook(int (*fcn_ptr) (StrBuf*), int EventType, int Priority)
532 {
533         TDAPVetoHookFunctionHook *newfcn;
534
535         newfcn = (TDAPVetoHookFunctionHook *)
536             malloc(sizeof(TDAPVetoHookFunctionHook));
537         newfcn->Priority = Priority;
538         newfcn->h_function_pointer = fcn_ptr;
539         newfcn->eventtype = EventType;
540
541         TDAPVetoHookFunctionHook **pfcn;
542         pfcn = &TDAPVetoHookTable;
543         while ((*pfcn != NULL) && 
544                ((*pfcn)->Priority < newfcn->Priority) &&
545                ((*pfcn)->next != NULL))
546                 pfcn = &(*pfcn)->next;
547                 
548         newfcn->next = *pfcn;
549         *pfcn = newfcn;
550         
551         MOD_syslog(LOG_DEBUG, "Registered a new TDAP Veto function (type %d Priority %d)\n",
552                    EventType, Priority);
553 }
554
555
556 void CtdlUnregisterTDAPVetoHook(int (*fcn_ptr) (StrBuf*), int EventType)
557 {
558         TDAPVetoHookFunctionHook *cur, *p, *last;
559         last = NULL;
560         cur = TDAPVetoHookTable;
561         while  (cur != NULL) {
562                 if ((fcn_ptr == cur->h_function_pointer) &&
563                     (EventType == cur->eventtype))
564                 {
565                         MOD_syslog(LOG_DEBUG, "Unregistered TDAP Veto function (type %d)\n",
566                                    EventType);
567                         p = cur->next;
568
569                         free(cur);
570                         cur = NULL;
571
572                         if (last != NULL)
573                                 last->next = p;
574                         else 
575                                 TDAPVetoHookTable = p;
576                         cur = p;
577                 }
578                 else {
579                         last = cur;
580                         cur = cur->next;
581                 }
582         }
583 }
584
585 void CtdlDestroyTDAPVetoHooks(void)
586 {
587         TDAPVetoHookFunctionHook *cur, *p;
588
589         cur = TDAPVetoHookTable;
590         while (cur != NULL)
591         {
592                 MODM_syslog(LOG_DEBUG, "Destroyed TDAP Veto function\n");
593                 p = cur->next;
594                 free(cur);
595                 cur = p;
596         }
597         TDAPVetoHookTable = NULL;
598 }
599
600
601 void CtdlRegisterSessionHook(void (*fcn_ptr) (void), int EventType, int Priority)
602 {
603         SessionFunctionHook *newfcn;
604
605         newfcn = (SessionFunctionHook *)
606             malloc(sizeof(SessionFunctionHook));
607         newfcn->Priority = Priority;
608         newfcn->h_function_pointer = fcn_ptr;
609         newfcn->eventtype = EventType;
610
611         SessionFunctionHook **pfcn;
612         pfcn = &SessionHookTable;
613         while ((*pfcn != NULL) && 
614                ((*pfcn)->Priority < newfcn->Priority) &&
615                ((*pfcn)->next != NULL))
616                 pfcn = &(*pfcn)->next;
617                 
618         newfcn->next = *pfcn;
619         *pfcn = newfcn;
620         
621         MOD_syslog(LOG_DEBUG, "Registered a new session function (type %d Priority %d)\n",
622                    EventType, Priority);
623 }
624
625
626 void CtdlUnregisterSessionHook(void (*fcn_ptr) (void), int EventType)
627 {
628         SessionFunctionHook *cur, *p, *last;
629         last = NULL;
630         cur = SessionHookTable;
631         while  (cur != NULL) {
632                 if ((fcn_ptr == cur->h_function_pointer) &&
633                     (EventType == cur->eventtype))
634                 {
635                         MOD_syslog(LOG_DEBUG, "Unregistered session function (type %d)\n",
636                                    EventType);
637                         p = cur->next;
638
639                         free(cur);
640                         cur = NULL;
641
642                         if (last != NULL)
643                                 last->next = p;
644                         else 
645                                 SessionHookTable = p;
646                         cur = p;
647                 }
648                 else {
649                         last = cur;
650                         cur = cur->next;
651                 }
652         }
653 }
654
655 void CtdlDestroySessionHooks(void)
656 {
657         SessionFunctionHook *cur, *p;
658
659         cur = SessionHookTable;
660         while (cur != NULL)
661         {
662                 MODM_syslog(LOG_DEBUG, "Destroyed session function\n");
663                 p = cur->next;
664                 free(cur);
665                 cur = p;
666         }
667         SessionHookTable = NULL;
668 }
669
670
671 void CtdlRegisterUserHook(void (*fcn_ptr) (ctdluser *), int EventType)
672 {
673
674         UserFunctionHook *newfcn;
675
676         newfcn = (UserFunctionHook *)
677             malloc(sizeof(UserFunctionHook));
678         newfcn->next = UserHookTable;
679         newfcn->h_function_pointer = fcn_ptr;
680         newfcn->eventtype = EventType;
681         UserHookTable = newfcn;
682
683         MOD_syslog(LOG_DEBUG, "Registered a new user function (type %d)\n",
684                    EventType);
685 }
686
687
688 void CtdlUnregisterUserHook(void (*fcn_ptr) (struct ctdluser *), int EventType)
689 {
690         UserFunctionHook *cur, *p, *last;
691         last = NULL;
692         cur = UserHookTable;
693         while (cur != NULL) {
694                 if ((fcn_ptr == cur->h_function_pointer) &&
695                     (EventType == cur->eventtype))
696                 {
697                         MOD_syslog(LOG_DEBUG, "Unregistered user function (type %d)\n",
698                                    EventType);
699                         p = cur->next;
700
701                         free(cur);
702                         cur = NULL;
703
704                         if (last != NULL)
705                                 last->next = p;
706                         else 
707                                 UserHookTable = p;
708                         cur = p;
709                 }
710                 else {
711                         last = cur;
712                         cur = cur->next;
713                 }
714         }
715 }
716
717 void CtdlDestroyUserHooks(void)
718 {
719         UserFunctionHook *cur, *p;
720
721         cur = UserHookTable;
722         while (cur != NULL)
723         {
724                 MODM_syslog(LOG_DEBUG, "Destroyed user function \n");
725                 p = cur->next;
726                 free(cur);
727                 cur = p;
728         }
729         UserHookTable = NULL;
730 }
731
732
733 void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, recptypes *),
734                                 int EventType)
735 {
736
737         MessageFunctionHook *newfcn;
738
739         newfcn = (MessageFunctionHook *)
740             malloc(sizeof(MessageFunctionHook));
741         newfcn->next = MessageHookTable;
742         newfcn->h_function_pointer = handler;
743         newfcn->eventtype = EventType;
744         MessageHookTable = newfcn;
745
746         MOD_syslog(LOG_DEBUG, "Registered a new message function (type %d)\n",
747                    EventType);
748 }
749
750
751 void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, recptypes *),
752                 int EventType)
753 {
754         MessageFunctionHook *cur, *p, *last;
755         last = NULL;
756         cur = MessageHookTable;
757         while (cur != NULL) {
758                 if ((handler == cur->h_function_pointer) &&
759                     (EventType == cur->eventtype))
760                 {
761                         MOD_syslog(LOG_DEBUG, "Unregistered message function (type %d)\n",
762                                    EventType);
763                         p = cur->next;
764                         free(cur);
765                         cur = NULL;
766
767                         if (last != NULL)
768                                 last->next = p;
769                         else 
770                                 MessageHookTable = p;
771                         cur = p;
772                 }
773                 else {
774                         last = cur;
775                         cur = cur->next;
776                 }
777         }
778 }
779
780 void CtdlDestroyMessageHook(void)
781 {
782         MessageFunctionHook *cur, *p;
783
784         cur = MessageHookTable; 
785         while (cur != NULL)
786         {
787                 MOD_syslog(LOG_DEBUG, "Destroyed message function (type %d)\n", cur->eventtype);
788                 p = cur->next;
789                 free(cur);
790                 cur = p;
791         }
792         MessageHookTable = NULL;
793 }
794
795
796 void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
797 {
798         RoomFunctionHook *newfcn;
799
800         newfcn = (RoomFunctionHook *)
801             malloc(sizeof(RoomFunctionHook));
802         newfcn->next = RoomHookTable;
803         newfcn->fcn_ptr = fcn_ptr;
804         RoomHookTable = newfcn;
805
806         MODM_syslog(LOG_DEBUG, "Registered a new room function\n");
807 }
808
809
810 void CtdlUnregisterRoomHook(int (*fcn_ptr)(struct ctdlroom *))
811 {
812         RoomFunctionHook *cur, *p, *last;
813         last = NULL;
814         cur = RoomHookTable;
815         while (cur != NULL)
816         {
817                 if (fcn_ptr == cur->fcn_ptr) {
818                         MODM_syslog(LOG_DEBUG, "Unregistered room function\n");
819                         p = cur->next;
820
821                         free(cur);
822                         cur = NULL;
823
824                         if (last != NULL)
825                                 last->next = p;
826                         else 
827                                 RoomHookTable = p;
828                         cur = p;
829                 }
830                 else {
831                         last = cur;
832                         cur = cur->next;
833                 }
834         }
835 }
836
837
838 void CtdlDestroyRoomHooks(void)
839 {
840         RoomFunctionHook *cur, *p;
841
842         cur = RoomHookTable;
843         while (cur != NULL)
844         {
845                 MODM_syslog(LOG_DEBUG, "Destroyed room function\n");
846                 p = cur->next;
847                 free(cur);
848                 cur = p;
849         }
850         RoomHookTable = NULL;
851 }
852
853 void CtdlRegisterNetprocHook(int (*handler)(struct CtdlMessage *, char *) )
854 {
855         NetprocFunctionHook *newfcn;
856
857         newfcn = (NetprocFunctionHook *)
858             malloc(sizeof(NetprocFunctionHook));
859         newfcn->next = NetprocHookTable;
860         newfcn->h_function_pointer = handler;
861         NetprocHookTable = newfcn;
862
863         MODM_syslog(LOG_DEBUG, "Registered a new netproc function\n");
864 }
865
866
867 void CtdlUnregisterNetprocHook(int (*handler)(struct CtdlMessage *, char *) )
868 {
869         NetprocFunctionHook *cur, *p, *last;
870
871         cur = NetprocHookTable;
872         last = NULL;
873
874         while (cur != NULL) {
875                 if (handler == cur->h_function_pointer)
876                 {
877                         MODM_syslog(LOG_DEBUG, "Unregistered netproc function\n");
878                         p = cur->next;
879                         free(cur);
880                         if (last != NULL) {
881                                 last->next = p;
882                         }
883                         else {
884                                 NetprocHookTable = p;
885                         }
886                         cur = p;
887                 }
888                 else {
889                         last = cur;
890                         cur = cur->next;
891                 }
892         }
893 }
894
895 void CtdlDestroyNetprocHooks(void)
896 {
897         NetprocFunctionHook *cur, *p;
898
899         cur = NetprocHookTable;
900         while (cur != NULL)
901         {
902                 MODM_syslog(LOG_DEBUG, "Destroyed netproc function\n");
903                 p = cur->next;
904                 free(cur);
905                 cur = p;
906         }
907         NetprocHookTable = NULL;
908 }
909
910
911 void CtdlRegisterDeleteHook(void (*handler)(char *, long) )
912 {
913         DeleteFunctionHook *newfcn;
914
915         newfcn = (DeleteFunctionHook *)
916             malloc(sizeof(DeleteFunctionHook));
917         newfcn->next = DeleteHookTable;
918         newfcn->h_function_pointer = handler;
919         DeleteHookTable = newfcn;
920
921         MODM_syslog(LOG_DEBUG, "Registered a new delete function\n");
922 }
923
924
925 void CtdlUnregisterDeleteHook(void (*handler)(char *, long) )
926 {
927         DeleteFunctionHook *cur, *p, *last;
928
929         last = NULL;
930         cur = DeleteHookTable;
931         while (cur != NULL) {
932                 if (handler == cur->h_function_pointer )
933                 {
934                         MODM_syslog(LOG_DEBUG, "Unregistered delete function\n");
935                         p = cur->next;
936                         free(cur);
937
938                         if (last != NULL)
939                                 last->next = p;
940                         else
941                                 DeleteHookTable = p;
942
943                         cur = p;
944                 }
945                 else {
946                         last = cur;
947                         cur = cur->next;
948                 }
949         }
950 }
951 void CtdlDestroyDeleteHooks(void)
952 {
953         DeleteFunctionHook *cur, *p;
954
955         cur = DeleteHookTable;
956         while (cur != NULL)
957         {
958                 MODM_syslog(LOG_DEBUG, "Destroyed delete function\n");
959                 p = cur->next;
960                 free(cur);
961                 cur = p;                
962         }
963         DeleteHookTable = NULL;
964 }
965
966
967
968
969 void CtdlRegisterFixedOutputHook(char *content_type, void (*handler)(char *, int) )
970 {
971         FixedOutputHook *newfcn;
972
973         newfcn = (FixedOutputHook *)
974             malloc(sizeof(FixedOutputHook));
975         newfcn->next = FixedOutputTable;
976         newfcn->h_function_pointer = handler;
977         safestrncpy(newfcn->content_type, content_type, sizeof newfcn->content_type);
978         FixedOutputTable = newfcn;
979
980         MOD_syslog(LOG_DEBUG, "Registered a new fixed output function for %s\n", newfcn->content_type);
981 }
982
983
984 void CtdlUnregisterFixedOutputHook(char *content_type)
985 {
986         FixedOutputHook *cur, *p, *last;
987
988         last = NULL;
989         cur = FixedOutputTable;
990         while (cur != NULL) {
991                 /* This will also remove duplicates if any */
992                 if (!strcasecmp(content_type, cur->content_type)) {
993                         MOD_syslog(LOG_DEBUG,
994                                    "Unregistered fixed output function for %s\n",
995                                    content_type);
996
997                         p = cur->next;
998                         free(cur);
999
1000                         if (last != NULL)
1001                                 last->next = p;
1002                         else
1003                                 FixedOutputTable = p;
1004                         
1005                         cur = p;
1006                 }
1007                 else
1008                 {
1009                         last = cur;
1010                         cur = cur->next;
1011                 }
1012         }
1013 }
1014
1015 void CtdlDestroyFixedOutputHooks(void)
1016 {
1017         FixedOutputHook *cur, *p;
1018
1019         cur = FixedOutputTable; 
1020         while (cur != NULL)
1021         {
1022                 MOD_syslog(LOG_DEBUG, "Destroyed fixed output function for %s\n", cur->content_type);
1023                 p = cur->next;
1024                 free(cur);
1025                 cur = p;
1026                 
1027         }
1028         FixedOutputTable = NULL;
1029 }
1030
1031 /* returns nonzero if we found a hook and used it */
1032 int PerformFixedOutputHooks(char *content_type, char *content, int content_length)
1033 {
1034         FixedOutputHook *fcn;
1035
1036         for (fcn = FixedOutputTable; fcn != NULL; fcn = fcn->next) {
1037                 if (!strcasecmp(content_type, fcn->content_type)) {
1038                         (*fcn->h_function_pointer) (content, content_length);
1039                         return(1);
1040                 }
1041         }
1042         return(0);
1043 }
1044
1045
1046
1047
1048
1049 void CtdlRegisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
1050 {
1051
1052         XmsgFunctionHook *newfcn;
1053
1054         newfcn = (XmsgFunctionHook *) malloc(sizeof(XmsgFunctionHook));
1055         newfcn->next = XmsgHookTable;
1056         newfcn->order = order;
1057         newfcn->h_function_pointer = fcn_ptr;
1058         XmsgHookTable = newfcn;
1059         MOD_syslog(LOG_DEBUG, "Registered a new x-msg function (priority %d)\n", order);
1060 }
1061
1062
1063 void CtdlUnregisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order)
1064 {
1065         XmsgFunctionHook *cur, *p, *last;
1066
1067         last = NULL;
1068         cur = XmsgHookTable;
1069         while (cur != NULL) {
1070                 /* This will also remove duplicates if any */
1071                 if (fcn_ptr == cur->h_function_pointer &&
1072                     order == cur->order) {
1073                         MOD_syslog(LOG_DEBUG, "Unregistered x-msg function "
1074                                    "(priority %d)\n", order);
1075                         p = cur->next;
1076                         free(cur);
1077
1078                         if (last != NULL)
1079                                 last->next = p;
1080                         else
1081                                 XmsgHookTable = p;
1082                         
1083                         cur = p;
1084                 }
1085                 else {
1086                         last = cur;
1087                         cur = cur->next;
1088                 }
1089         }
1090 }
1091
1092 void CtdlDestroyXmsgHooks(void)
1093 {
1094         XmsgFunctionHook *cur, *p;
1095
1096         cur = XmsgHookTable;
1097         while (cur != NULL)
1098         {
1099                 MOD_syslog(LOG_DEBUG, "Destroyed x-msg function "
1100                         "(priority %d)\n", cur->order);
1101                 p = cur->next;
1102                         
1103                 free(cur);
1104                 cur = p;
1105         }
1106         XmsgHookTable = NULL;
1107 }
1108
1109
1110 void CtdlRegisterServiceHook(int tcp_port,
1111                              char *sockpath,
1112                              void (*h_greeting_function) (void),
1113                              void (*h_command_function) (void),
1114                              void (*h_async_function) (void),
1115                              const char *ServiceName)
1116 {
1117         ServiceFunctionHook *newfcn;
1118         char *message;
1119         char error[SIZ];
1120
1121         strcpy(error, "");
1122         newfcn = (ServiceFunctionHook *) malloc(sizeof(ServiceFunctionHook));
1123         message = (char*) malloc (SIZ + SIZ);
1124         
1125         newfcn->next = ServiceHookTable;
1126         newfcn->tcp_port = tcp_port;
1127         newfcn->sockpath = sockpath;
1128         newfcn->h_greeting_function = h_greeting_function;
1129         newfcn->h_command_function = h_command_function;
1130         newfcn->h_async_function = h_async_function;
1131         newfcn->ServiceName = ServiceName;
1132
1133         if (sockpath != NULL) {
1134                 newfcn->msock = ctdl_uds_server(sockpath, config.c_maxsessions, error);
1135                 snprintf(message, SIZ, "Unix domain socket '%s': ", sockpath);
1136         }
1137         else if (tcp_port <= 0) {       /* port -1 to disable */
1138                 MOD_syslog(LOG_INFO, "Service %s has been manually disabled, skipping\n", ServiceName);
1139                 free (message);
1140                 free(newfcn);
1141                 return;
1142         }
1143         else {
1144                 newfcn->msock = ctdl_tcp_server(config.c_ip_addr,
1145                                               tcp_port,
1146                                               config.c_maxsessions, 
1147                                               error);
1148                 snprintf(message, SIZ, "TCP port %s:%d: (%s) ", 
1149                          config.c_ip_addr, tcp_port, ServiceName);
1150         }
1151
1152         if (newfcn->msock > 0) {
1153                 ServiceHookTable = newfcn;
1154                 strcat(message, "registered.");
1155                 MOD_syslog(LOG_INFO, "%s\n", message);
1156         }
1157         else {
1158                 AddPortError(message, error);
1159                 strcat(message, "FAILED.");
1160                 MOD_syslog(LOG_CRIT, "%s\n", message);
1161                 free(newfcn);
1162         }
1163         free(message);
1164 }
1165
1166
1167 void CtdlUnregisterServiceHook(int tcp_port, char *sockpath,
1168                         void (*h_greeting_function) (void),
1169                         void (*h_command_function) (void),
1170                         void (*h_async_function) (void)
1171                         )
1172 {
1173         ServiceFunctionHook *cur, *p, *last;
1174
1175         last = NULL;
1176         cur = ServiceHookTable;
1177         while (cur != NULL) {
1178                 /* This will also remove duplicates if any */
1179                 if (h_greeting_function == cur->h_greeting_function &&
1180                     h_command_function == cur->h_command_function &&
1181                     h_async_function == cur->h_async_function &&
1182                     tcp_port == cur->tcp_port && 
1183                     !(sockpath && cur->sockpath && strcmp(sockpath, cur->sockpath)) )
1184                 {
1185                         if (cur->msock > 0)
1186                                 close(cur->msock);
1187                         if (sockpath) {
1188                                 MOD_syslog(LOG_INFO, "Closed UNIX domain socket %s\n",
1189                                            sockpath);
1190                                 unlink(sockpath);
1191                         } else if (tcp_port) {
1192                                 MOD_syslog(LOG_INFO, "Closed TCP port %d\n", tcp_port);
1193                         } else {
1194                                 MOD_syslog(LOG_INFO, "Unregistered service \"%s\"\n", cur->ServiceName);
1195                         }
1196                         p = cur->next;
1197                         free(cur);
1198                         if (last != NULL)
1199                                 last->next = p;
1200                         else
1201                                 ServiceHookTable = p;
1202                         cur = p;
1203                 }
1204                 else {
1205                         last = cur;
1206                         cur = cur->next;
1207                 }
1208         }
1209 }
1210
1211
1212 void CtdlShutdownServiceHooks(void)
1213 {
1214         /* sort of a duplicate of close_masters() but called earlier */
1215         ServiceFunctionHook *cur;
1216
1217         cur = ServiceHookTable;
1218         while (cur != NULL) 
1219         {
1220                 if (cur->msock != -1)
1221                 {
1222                         close(cur->msock);
1223                         cur->msock = -1;
1224                         if (cur->sockpath != NULL){
1225                                 MOD_syslog(LOG_INFO, "[%s] Closed UNIX domain socket %s\n",
1226                                            cur->ServiceName,
1227                                            cur->sockpath);
1228                                 unlink(cur->sockpath);
1229                         } else {
1230                                 MOD_syslog(LOG_INFO, "[%s] closing service\n", 
1231                                            cur->ServiceName);
1232                         }
1233                 }
1234                 cur = cur->next;
1235         }
1236 }
1237
1238 void CtdlDestroyServiceHook(void)
1239 {
1240         const char *Text;
1241         ServiceFunctionHook *cur, *p;
1242
1243         cur = ServiceHookTable;
1244         while (cur != NULL)
1245         {
1246                 if (cur->msock != -1)
1247                 {
1248                         close(cur->msock);
1249                         Text = "Closed";
1250                 }
1251                 else
1252                 {
1253                         Text = " Not closing again";
1254                 }
1255
1256                 if (cur->sockpath) {
1257                         MOD_syslog(LOG_INFO, "%s UNIX domain socket %s\n",
1258                                    Text,
1259                                    cur->sockpath);
1260                         unlink(cur->sockpath);
1261                 } else if (cur->tcp_port) {
1262                         MOD_syslog(LOG_INFO, "%s TCP port %d\n", Text, cur->tcp_port);
1263                 } else {
1264                         MOD_syslog(LOG_INFO, "Destroyed service \"%s\"\n", cur->ServiceName);
1265                 }
1266                 p = cur->next;
1267                 free(cur);
1268                 cur = p;
1269         }
1270         ServiceHookTable = NULL;
1271 }
1272
1273 void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
1274 {
1275         SearchFunctionHook *newfcn;
1276
1277         if (!name || !fcn_ptr) {
1278                 return;
1279         }
1280         
1281         newfcn = (SearchFunctionHook *)
1282             malloc(sizeof(SearchFunctionHook));
1283         newfcn->next = SearchFunctionHookTable;
1284         newfcn->name = name;
1285         newfcn->fcn_ptr = fcn_ptr;
1286         SearchFunctionHookTable = newfcn;
1287
1288         MOD_syslog(LOG_DEBUG, "Registered a new search function (%s)\n", name);
1289 }
1290
1291 void CtdlUnregisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name)
1292 {
1293         SearchFunctionHook *cur, *p, *last;
1294         
1295         last = NULL;
1296         cur = SearchFunctionHookTable;
1297         while (cur != NULL) {
1298                 if (fcn_ptr &&
1299                     (cur->fcn_ptr == fcn_ptr) &&
1300                     name && !strcmp(name, cur->name))
1301                 {
1302                         MOD_syslog(LOG_DEBUG, "Unregistered search function(%s)\n", name);
1303                         p = cur->next;
1304                         free (cur);
1305                         if (last != NULL)
1306                                 last->next = p;
1307                         else
1308                                 SearchFunctionHookTable = p;
1309                         cur = p;
1310                 }
1311                 else {
1312                         last = cur;
1313                         cur = cur->next;
1314                 }
1315         }
1316 }
1317
1318 void CtdlDestroySearchHooks(void)
1319 {
1320         SearchFunctionHook *cur, *p;
1321
1322         cur = SearchFunctionHookTable;
1323         SearchFunctionHookTable = NULL;
1324         while (cur != NULL) {
1325                 p = cur->next;
1326                 free(cur);
1327                 cur = p;
1328         }
1329 }
1330
1331 void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name)
1332 {
1333         SearchFunctionHook *fcn = NULL;
1334
1335         for (fcn = SearchFunctionHookTable; fcn != NULL; fcn = fcn->next) {
1336                 if (!func_name || !strcmp(func_name, fcn->name)) {
1337                         (*fcn->fcn_ptr) (num_msgs, search_msgs, search_string);
1338                         return;
1339                 }
1340         }
1341         *num_msgs = 0;
1342 }
1343
1344 int CheckTDAPVeto (int DBType, StrBuf *ErrMsg)
1345 {
1346         int Result = 0;
1347         TDAPVetoHookFunctionHook *fcn = NULL;
1348
1349         for (fcn = TDAPVetoHookTable; (fcn != NULL) && (Result == 0); fcn = fcn->next) {
1350                 if (fcn->eventtype == DBType) {
1351                         Result = (*fcn->h_function_pointer) (ErrMsg);
1352                 }
1353         }
1354         return Result;
1355 }
1356
1357 void PerformSessionHooks(int EventType)
1358 {
1359         SessionFunctionHook *fcn = NULL;
1360
1361         for (fcn = SessionHookTable; fcn != NULL; fcn = fcn->next) {
1362                 if (fcn->eventtype == EventType) {
1363                         if (EventType == EVT_TIMER) {
1364                                 pthread_setspecific(MyConKey, NULL);    /* for every hook */
1365                         }
1366                         (*fcn->h_function_pointer) ();
1367                 }
1368         }
1369 }
1370
1371 void PerformUserHooks(ctdluser *usbuf, int EventType)
1372 {
1373         UserFunctionHook *fcn = NULL;
1374
1375         for (fcn = UserHookTable; fcn != NULL; fcn = fcn->next) {
1376                 if (fcn->eventtype == EventType) {
1377                         (*fcn->h_function_pointer) (usbuf);
1378                 }
1379         }
1380 }
1381
1382 int PerformMessageHooks(struct CtdlMessage *msg, recptypes *recps, int EventType)
1383 {
1384         MessageFunctionHook *fcn = NULL;
1385         int total_retval = 0;
1386
1387         /* Other code may elect to protect this message from server-side
1388          * handlers; if this is the case, don't do anything.
1389         MOD_syslog(LOG_DEBUG, "** Event type is %d, flags are %d\n", EventType, msg->cm_flags);
1390          */
1391         if (msg->cm_flags & CM_SKIP_HOOKS) {
1392                 MODM_syslog(LOG_DEBUG, "Skipping hooks\n");
1393                 return(0);
1394         }
1395
1396         /* Otherwise, run all the hooks appropriate to this event type.
1397          */
1398         for (fcn = MessageHookTable; fcn != NULL; fcn = fcn->next) {
1399                 if (fcn->eventtype == EventType) {
1400                         total_retval = total_retval + (*fcn->h_function_pointer) (msg, recps);
1401                 }
1402         }
1403
1404         /* Return the sum of the return codes from the hook functions.  If
1405          * this is an EVT_BEFORESAVE event, a nonzero return code will cause
1406          * the save operation to abort.
1407          */
1408         return total_retval;
1409 }
1410
1411
1412 int PerformRoomHooks(struct ctdlroom *target_room)
1413 {
1414         RoomFunctionHook *fcn;
1415         int total_retval = 0;
1416
1417         MOD_syslog(LOG_DEBUG, "Performing room hooks for <%s>\n", target_room->QRname);
1418
1419         for (fcn = RoomHookTable; fcn != NULL; fcn = fcn->next) {
1420                 total_retval = total_retval + (*fcn->fcn_ptr) (target_room);
1421         }
1422
1423         /* Return the sum of the return codes from the hook functions.
1424          */
1425         return total_retval;
1426 }
1427
1428
1429 int PerformNetprocHooks(struct CtdlMessage *msg, char *target_room)
1430 {
1431         NetprocFunctionHook *fcn;
1432         int total_retval = 0;
1433
1434         for (fcn = NetprocHookTable; fcn != NULL; fcn = fcn->next) {
1435                 total_retval = total_retval +
1436                         (*fcn->h_function_pointer) (msg, target_room);
1437         }
1438
1439         /* Return the sum of the return codes from the hook functions.
1440          * A nonzero return code will cause the message to *not* be imported.
1441          */
1442         return total_retval;
1443 }
1444
1445
1446 void PerformDeleteHooks(char *room, long msgnum)
1447 {
1448         DeleteFunctionHook *fcn;
1449
1450         for (fcn = DeleteHookTable; fcn != NULL; fcn = fcn->next) {
1451                 (*fcn->h_function_pointer) (room, msgnum);
1452         }
1453 }
1454
1455
1456
1457
1458
1459 int PerformXmsgHooks(char *sender, char *sender_email, char *recp, char *msg)
1460 {
1461         XmsgFunctionHook *fcn;
1462         int total_sent = 0;
1463         int p;
1464
1465         for (p=0; p<MAX_XMSG_PRI; ++p) {
1466                 for (fcn = XmsgHookTable; fcn != NULL; fcn = fcn->next) {
1467                         if (fcn->order == p) {
1468                                 total_sent +=
1469                                         (*fcn->h_function_pointer)
1470                                                 (sender, sender_email, recp, msg);
1471                         }
1472                 }
1473                 /* Break out of the loop if a higher-priority function
1474                  * successfully delivered the message.  This prevents duplicate
1475                  * deliveries to local users simultaneously signed onto
1476                  * remote services.
1477                  */
1478                 if (total_sent) break;
1479         }
1480         return total_sent;
1481 }
1482
1483
1484 /*
1485  * Dirty hack until we impliment a hook mechanism for this
1486  */
1487 void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response)
1488 {
1489 #ifdef HAVE_OPENSSL
1490         CtdlStartTLS (ok_response, nosup_response, error_response);
1491 #endif
1492 }
1493
1494 void DebugModulesEnable(const int n)
1495 {
1496         DebugModules = n;
1497 }
1498 CTDL_MODULE_INIT(modules)
1499 {
1500         if (!threading) {
1501                 CtdlRegisterDebugFlagHook(HKEY("modules"), DebugModulesEnable, &DebugModules);
1502         }
1503         return "modules";
1504 }