spf dynamic module
[exim.git] / src / src / drtables.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10
11 #include "exim.h"
12
13 #include <string.h>
14
15 /* This module contains tables that define the lookup methods and drivers
16 that are actually included in the binary. Its contents are controlled by
17 various macros in config.h that ultimately come from Local/Makefile. They are
18 all described in src/EDITME. */
19
20
21 //lookup_info **lookup_list;
22 tree_node * lookups_tree = NULL;
23 unsigned lookup_list_count = 0;
24
25 /* Lists of information about which drivers are included in the exim binary. */
26
27 auth_info * auths_available= NULL;
28 router_info * routers_available = NULL;
29 transport_info * transports_available = NULL;
30
31
32
33 #ifndef MACRO_PREDEF
34
35 gstring *
36 auth_show_supported(gstring * g)
37 {
38 uschar * b = US""               /* static-build authenticatornames */
39 #if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5!=2
40   " cram_md5"
41 #endif
42 #if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL!=2
43   " cyrus_sasl"
44 #endif
45 #if defined(AUTH_DOVECOT) && AUTH_DOVECOT!=2
46   " dovecot"
47 #endif
48 #if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL!=2
49   " external"
50 #endif
51 #if defined(AUTH_GSASL) && AUTH_GSASL!=2
52   " gsasl"
53 #endif
54 #if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI!=2
55   " heimdal_gssapi"
56 #endif
57 #if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT!=2
58   " plaintext"
59 #endif
60 #if defined(AUTH_SPA) && AUTH_SPA!=2
61   " spa"
62 #endif
63 #if defined(AUTH_TLS) && AUTH_TLS!=2
64   " tls"
65 #endif
66   ;
67
68 uschar * d = US""               /* dynamic-module authenticator names */
69 #if defined(AUTH_CRAM_MD5) && AUTH_CRAM_MD5==2
70   " cram_md5"
71 #endif
72 #if defined(AUTH_CYRUS_SASL) && AUTH_CYRUS_SASL==2
73   " cyrus_sasl"
74 #endif
75 #if defined(AUTH_DOVECOT) && AUTH_DOVECOT==2
76   " dovecot"
77 #endif
78 #if defined(AUTH_EXTERNAL) && AUTH_EXTERNAL==2
79   " external"
80 #endif
81 #if defined(AUTH_GSASL) && AUTH_GSASL==2
82   " gsasl"
83 #endif
84 #if defined(AUTH_HEIMDAL_GSSAPI) && AUTH_HEIMDAL_GSSAPI==2
85   " heimdal_gssapi"
86 #endif
87 #if defined(AUTH_PLAINTEXT) && AUTH_PLAINTEXT==2
88   " plaintext"
89 #endif
90 #if defined(AUTH_SPA) && AUTH_SPA==2
91   " spa"
92 #endif
93 #if defined(AUTH_TLS) && AUTH_TLS==2
94   " tls"
95 #endif
96   ;
97
98 if (*b) g = string_fmt_append(g, "Authenticators (built-in):%s\n", b);
99 if (*d) g = string_fmt_append(g, "Authenticators (dynamic): %s\n", d);
100 return g;
101 }
102
103 gstring *
104 route_show_supported(gstring * g)
105 {
106 uschar * b = US""               /* static-build router names */
107 #if defined(ROUTER_ACCEPT) && ROUTER_ACCEPT!=2
108   " accept"
109 #endif
110 #if defined(ROUTER_DNSLOOKUP) && ROUTER_DNSLOOKUP!=2
111   " dnslookup"
112 #endif
113 # if defined(ROUTER_IPLITERAL) && ROUTER_IPLITERAL!=2
114   " ipliteral"
115 #endif
116 #if defined(ROUTER_IPLOOKUP) && ROUTER_IPLOOKUP!=2
117   " iplookup"
118 #endif
119 #if defined(ROUTER_MANUALROUTE) && ROUTER_MANUALROUTE!=2
120   " manualroute"
121 #endif
122 #if defined(ROUTER_REDIRECT) && ROUTER_REDIRECT!=2
123   " redirect"
124 #endif
125 #if defined(ROUTER_QUERYPROGRAM) && ROUTER_QUERYPROGRAM!=2
126   " queryprogram"
127 #endif
128   ;
129
130 uschar * d = US""               /* dynamic-module router names */
131 #if defined(ROUTER_ACCEPT) && ROUTER_ACCEPT==2
132   " accept"
133 #endif
134 #if defined(ROUTER_DNSLOOKUP) && ROUTER_DNSLOOKUP==2
135   " dnslookup"
136 #endif
137 # if defined(ROUTER_IPLITERAL) && ROUTER_IPLITERAL==2
138   " ipliteral"
139 #endif
140 #if defined(ROUTER_IPLOOKUP) && ROUTER_IPLOOKUP==2
141   " iplookup"
142 #endif
143 #if defined(ROUTER_MANUALROUTE) && ROUTER_MANUALROUTE==2
144   " manualroute"
145 #endif
146 #if defined(ROUTER_REDIRECT) && ROUTER_REDIRECT==2
147   " redirect"
148 #endif
149 #if defined(ROUTER_QUERYPROGRAM) && ROUTER_QUERYPROGRAM==2
150   " queryprogram"
151 #endif
152   ;
153
154 if (*b) g = string_fmt_append(g, "Routers (built-in):%s\n", b);
155 if (*d) g = string_fmt_append(g, "Routers (dynamic): %s\n", d);
156 return g;
157 }
158
159 gstring *
160 transport_show_supported(gstring * g)
161 {
162 uschar * b = US""               /* static-build transportnames */
163 #if defined(TRANSPORT_APPENDFILE) && TRANSPORT_APPENDFILE!=2
164   " appendfile"
165 # ifdef SUPPORT_MAILDIR
166     "/maildir"
167 # endif
168 # ifdef SUPPORT_MAILSTORE
169     "/mailstore"
170 # endif
171 # ifdef SUPPORT_MBX
172     "/mbx"
173 # endif
174 #endif
175 #if defined(TRANSPORT_AUTOREPLY) && TRANSPORT_AUTOREPLY!=2
176   " autoreply"
177 #endif
178 #if defined(TRANSPORT_LMTP) && TRANSPORT_LMTP!=2
179   " lmtp"
180 #endif
181 #if defined(TRANSPORT_PIPE) && TRANSPORT_PIPE!=2
182   " pipe"
183 #endif
184 #if defined(EXPERIMENTAL_QUEUEFILE) && EXPERIMENTAL_QUEUEFILE!=2
185   " queuefile"
186 #endif
187 #if defined(TRANSPORT_SMTP) && TRANSPORT_SMTP!=2
188   " smtp"
189 #endif
190   ;
191
192 uschar * d = US""               /* dynamic-module transportnames */
193 #if defined(TRANSPORT_APPENDFILE) && TRANSPORT_APPENDFILE==2
194   " appendfile"
195 # ifdef SUPPORT_MAILDIR
196     "/maildir"
197 # endif
198 # ifdef SUPPORT_MAILSTORE
199     "/mailstore"
200 # endif
201 # ifdef SUPPORT_MBX
202     "/mbx"
203 # endif
204 #endif
205 #if defined(TRANSPORT_AUTOREPLY) && TRANSPORT_AUTOREPLY==2
206   " autoreply"
207 #endif
208 #if defined(TRANSPORT_LMTP) && TRANSPORT_LMTP==2
209   " lmtp"
210 #endif
211 #if defined(TRANSPORT_PIPE) && TRANSPORT_PIPE==2
212   " pipe"
213 #endif
214 #if defined(EXPERIMENTAL_QUEUEFILE) && EXPERIMENTAL_QUEUEFILE==2
215   " queuefile"
216 #endif
217 #if defined(TRANSPORT_SMTP) && TRANSPORT_SMTP==2
218   " smtp"
219 #endif
220   ;
221
222 if (*b) g = string_fmt_append(g, "Transports (built-in):%s\n", b);
223 if (*d) g = string_fmt_append(g, "Transports (dynamic): %s\n", d);
224 return g;
225 }
226
227
228
229 static void
230 add_lookup_to_tree(lookup_info * li)
231 {
232 tree_node * new = store_get_perm(sizeof(tree_node) + Ustrlen(li->name),
233                                                         GET_UNTAINTED);
234 new->data.ptr = (void *)li;
235 Ustrcpy(new->name, li->name);
236 if (tree_insertnode(&lookups_tree, new))
237   li->acq_num = lookup_list_count++;
238 else
239   log_write(0, LOG_MAIN|LOG_PANIC, "Duplicate lookup name '%s'", li->name);
240 }
241
242
243 /* Add all the lookup types provided by the module */
244 static void
245 addlookupmodule(const struct lookup_module_info * lmi)
246 {
247 for (int j = 0; j < lmi->lookupcount; j++)
248   add_lookup_to_tree(lmi->lookups[j]);
249 }
250
251
252
253 /* Hunt for the lookup with the given acquisition number */
254
255 static unsigned hunt_acq;
256
257 static void
258 acq_cb(uschar * name, uschar * ptr, void * ctx)
259 {
260 lookup_info * li = (lookup_info *)ptr;
261 if (li->acq_num == hunt_acq) *(lookup_info **)ctx = li;
262 }
263
264 const lookup_info *
265 lookup_with_acq_num(unsigned k)
266 {
267 const lookup_info * li = NULL;
268 hunt_acq = k;
269 tree_walk(lookups_tree, acq_cb, &li);
270 return li;
271 }
272
273
274
275 /* These need to be at file level for old versions of gcc (2.95.2 reported),
276 which give parse errors on an extern in function scope.  Each entry needs
277 to also be invoked in init_lookup_list() below  */
278
279 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
280 extern lookup_module_info cdb_lookup_module_info;
281 #endif
282 #if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
283 extern lookup_module_info dbmdb_lookup_module_info;
284 #endif
285 #if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
286 extern lookup_module_info dnsdb_lookup_module_info;
287 #endif
288 #if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
289 extern lookup_module_info dsearch_lookup_module_info;
290 #endif
291 #if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
292 extern lookup_module_info ibase_lookup_module_info;
293 #endif
294 #if defined(LOOKUP_JSON) && LOOKUP_JSON!=2
295 extern lookup_module_info json_lookup_module_info;
296 #endif
297 #if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
298 extern lookup_module_info ldap_lookup_module_info;
299 #endif
300 #if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
301 extern lookup_module_info lsearch_lookup_module_info;
302 #endif
303 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
304 extern lookup_module_info mysql_lookup_module_info;
305 #endif
306 #if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
307 extern lookup_module_info nis_lookup_module_info;
308 #endif
309 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
310 extern lookup_module_info nisplus_lookup_module_info;
311 #endif
312 #if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
313 extern lookup_module_info oracle_lookup_module_info;
314 #endif
315 #if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
316 extern lookup_module_info passwd_lookup_module_info;
317 #endif
318 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
319 extern lookup_module_info pgsql_lookup_module_info;
320 #endif
321 #if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
322 extern lookup_module_info redis_lookup_module_info;
323 #endif
324 #if defined(LOOKUP_LMDB) && LOOKUP_LMDB!=2
325 extern lookup_module_info lmdb_lookup_module_info;
326 #endif
327 #if defined(SUPPORT_SPF)
328 extern lookup_module_info spf_lookup_module_info;       /* see below */
329 #endif
330 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
331 extern lookup_module_info sqlite_lookup_module_info;
332 #endif
333 #if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
334 extern lookup_module_info testdb_lookup_module_info;
335 #endif
336 #if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
337 extern lookup_module_info whoson_lookup_module_info;
338 #endif
339
340 extern lookup_module_info readsock_lookup_module_info;
341
342
343 #ifdef LOOKUP_MODULE_DIR
344 static void *
345 mod_open(const uschar * name, const uschar * class, uschar ** errstr)
346 {
347 const uschar * path = string_sprintf(
348   LOOKUP_MODULE_DIR "/%s_%s." DYNLIB_FN_EXT, name, class);
349 void * dl;
350 if (!(dl = dlopen(CS path, RTLD_NOW)))
351   {
352   if (errstr)
353     *errstr = string_sprintf("Error loading %s: %s", name, dlerror());
354   else
355     (void) dlerror();           /* clear out error state */
356   return NULL;
357   }
358
359 /* FreeBSD nsdispatch() can trigger dlerror() errors about
360 _nss_cache_cycle_prevention_function; we need to clear the dlerror()
361 state before calling dlsym(), so that any error afterwards only comes
362 from dlsym().  */
363
364 (void) dlerror();
365 return dl;
366 }
367
368
369 /* Try to load a lookup module with the given name.
370
371 Arguments:
372     name                name of the lookup
373     errstr              if not NULL, place "open fail" error message here
374
375 Return: boolean success
376 */
377
378 static BOOL
379 lookup_mod_load(const uschar * name, uschar ** errstr)
380 {
381 void * dl;
382 struct lookup_module_info * info;
383 const char * errormsg;
384
385 if (!(dl = mod_open(name, US"lookup", errstr)))
386   return FALSE;
387
388 info = (struct lookup_module_info *) dlsym(dl, "_lookup_module_info");
389 if ((errormsg = dlerror()))
390   {
391   fprintf(stderr, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
392   log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be a lookup module (%s)", name, errormsg);
393   dlclose(dl);
394   return FALSE;
395   }
396 if (info->magic != LOOKUP_MODULE_INFO_MAGIC)
397   {
398   fprintf(stderr, "Lookup module %s is not compatible with this version of Exim\n", name);
399   log_write(0, LOG_MAIN|LOG_PANIC, "Lookup module %s is not compatible with this version of Exim", name);
400   dlclose(dl);
401   return FALSE;
402   }
403
404 addlookupmodule(info);
405 DEBUG(D_lookup) debug_printf_indent("Loaded \"%s\" (%d lookup type%s)\n",
406                                     name, info->lookupcount,
407                                     info->lookupcount > 1 ? "s" : "");
408 return TRUE;
409 }
410
411
412 /* Try to load a lookup module, assuming the module name is the same
413 as the lookup type name.  This will only work for single-method modules.
414 Other have to be always-load (see the RE in init_lookup_list() below).
415 */
416
417 BOOL
418 lookup_one_mod_load(const uschar * name, uschar ** errstr)
419 {
420 if (!lookup_mod_load(name, errstr)) return FALSE;
421 /*XXX notify daemon? */
422 return TRUE;
423 }
424
425 #endif  /*LOOKUP_MODULE_DIR*/
426
427
428
429 misc_module_info * misc_module_list = NULL;
430
431 static void
432 misc_mod_add(misc_module_info * mi)
433 {
434 if (mi->init) mi->init(mi);
435 DEBUG(D_lookup) if (mi->lib_vers_report)
436   debug_printf_indent("%Y\n", mi->lib_vers_report(NULL));
437
438 mi->next = misc_module_list;
439 misc_module_list = mi;
440 }
441
442
443 #ifdef LOOKUP_MODULE_DIR
444
445 /* Load a "misc" module, and add to list */
446
447 static misc_module_info *
448 misc_mod_load(const uschar * name, uschar ** errstr)
449 {
450 void * dl;
451 struct misc_module_info * mi;
452 const char * errormsg;
453
454 DEBUG(D_any) debug_printf_indent("loading module '%s'\n", name);
455 if (!(dl = mod_open(name, US"miscmod", errstr)))
456   return NULL;
457
458 mi = (struct misc_module_info *) dlsym(dl,
459                                     CS string_sprintf("%s_module_info", name));
460 if ((errormsg = dlerror()))
461   {
462   fprintf(stderr, "%s does not appear to be an spf module (%s)\n", name, errormsg);
463   log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be an spf module (%s)", name, errormsg);
464   dlclose(dl);
465   return NULL;
466   }
467 if (mi->dyn_magic != MISC_MODULE_MAGIC)
468   {
469   fprintf(stderr, "Module %s is not compatible with this version of Exim\n", name);
470   log_write(0, LOG_MAIN|LOG_PANIC, "Module %s is not compatible with this version of Exim", name);
471   dlclose(dl);
472   return FALSE;
473   }
474
475 DEBUG(D_lookup) debug_printf_indent("Loaded \"%s\"\n", name);
476 misc_mod_add(mi);
477 return mi;
478 }
479
480 #endif  /*LOOKUP_MODULE_DIR*/
481
482
483 /* Find a "misc" module by name, if loaded.
484 For now use a linear search down a linked list.  If the number of
485 modules gets large, we might consider a tree.
486 */
487
488 misc_module_info *
489 misc_mod_findonly(const uschar * name)
490 {
491 for (misc_module_info * mi = misc_module_list; mi; mi = mi->next)
492   if (Ustrcmp(name, mi->name) == 0)
493     return mi;
494 }
495
496 /* Find a "misc" module, possibly already loaded, by name. */
497
498 misc_module_info *
499 misc_mod_find(const uschar * name, uschar ** errstr)
500 {
501 misc_module_info * mi;
502 if ((mi = misc_mod_findonly(name))) return mi;
503 #ifdef LOOKUP_MODULE_DIR
504 return misc_mod_load(name, errstr);
505 #else
506 return NULL;
507 #endif  /*LOOKUP_MODULE_DIR*/
508 }
509
510
511
512
513
514 void
515 init_lookup_list(void)
516 {
517 #ifdef LOOKUP_MODULE_DIR
518 DIR * dd;
519 int countmodules = 0;
520 #endif
521 static BOOL lookup_list_init_done = FALSE;
522
523 if (lookup_list_init_done)
524   return;
525 lookup_list_init_done = TRUE;
526
527 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
528 addlookupmodule(&cdb_lookup_module_info);
529 #endif
530
531 #if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
532 addlookupmodule(&dbmdb_lookup_module_info);
533 #endif
534
535 #if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
536 addlookupmodule(&dnsdb_lookup_module_info);
537 #endif
538
539 #if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
540 addlookupmodule(&dsearch_lookup_module_info);
541 #endif
542
543 #if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
544 addlookupmodule(&ibase_lookup_module_info);
545 #endif
546
547 #if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
548 addlookupmodule(&ldap_lookup_module_info);
549 #endif
550
551 #if defined(LOOKUP_JSON) && LOOKUP_JSON!=2
552 addlookupmodule(&json_lookup_module_info);
553 #endif
554
555 #if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
556 addlookupmodule(&lsearch_lookup_module_info);
557 #endif
558
559 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
560 addlookupmodule(&mysql_lookup_module_info);
561 #endif
562
563 #if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
564 addlookupmodule(&nis_lookup_module_info);
565 #endif
566
567 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
568 addlookupmodule(&nisplus_lookup_module_info);
569 #endif
570
571 #if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
572 addlookupmodule(&oracle_lookup_module_info);
573 #endif
574
575 #if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
576 addlookupmodule(&passwd_lookup_module_info);
577 #endif
578
579 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
580 addlookupmodule(&pgsql_lookup_module_info);
581 #endif
582
583 #if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
584 addlookupmodule(&redis_lookup_module_info);
585 #endif
586
587 #if defined(LOOKUP_LMDB) && LOOKUP_LMDB!=2
588 addlookupmodule(&lmdb_lookup_module_info);
589 #endif
590
591 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
592 addlookupmodule(&sqlite_lookup_module_info);
593 #endif
594
595 #if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
596 addlookupmodule(&testdb_lookup_module_info);
597 #endif
598
599 #if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
600 addlookupmodule(&whoson_lookup_module_info);
601 #endif
602
603 /* This is provided by the spf "misc" module, and the lookup aspect is always
604 linked statically whether or not the "misc" module (and hence libspf2) is
605 dynamic-load. */
606
607 #if defined(SUPPORT_SPF)
608 addlookupmodule(&spf_lookup_module_info);
609 #endif
610
611 /* This is a custom expansion, and not available as either
612 a list-syntax lookup or a lookup expansion. However, it is
613 implemented by a lookup module. */
614
615 addlookupmodule(&readsock_lookup_module_info);
616
617 DEBUG(D_lookup) debug_printf("Total %d built-in lookups\n", lookup_list_count);
618
619
620 #ifdef LOOKUP_MODULE_DIR
621 if (!(dd = exim_opendir(CUS LOOKUP_MODULE_DIR)))
622   {
623   DEBUG(D_lookup) debug_printf("Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
624   log_write(0, LOG_MAIN|LOG_PANIC,
625           "Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
626   }
627 else
628   {
629   /* Look specifically for modules we know offer several lookup types and
630   load them now, since we cannot load-on-first-use. */
631
632   struct dirent * ent;
633   const pcre2_code * regex_islookupmod = regex_must_compile(
634     US"(lsearch|ldap|nis)_lookup\\." DYNLIB_FN_EXT "$", MCS_NOFLAGS, TRUE);
635
636   DEBUG(D_lookup) debug_printf("Loading lookup modules from %s\n", LOOKUP_MODULE_DIR);
637   while ((ent = readdir(dd)))
638     {
639     char * name = ent->d_name;
640     int len = (int)strlen(name);
641     if (regex_match_and_setup(regex_islookupmod, US name, 0, 0))
642       {
643       uschar * errstr;
644       if (lookup_mod_load(expand_nstring[1], &errstr))
645         countmodules++;
646       else
647         {
648         fprintf(stderr, "%s\n", errstr);
649         log_write(0, LOG_MAIN|LOG_PANIC, "%s", errstr);
650         }
651       }
652     }
653   closedir(dd);
654   }
655
656 DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules);
657 #endif
658 }
659
660
661 #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
662 extern misc_module_info spf_module_info;
663 #endif
664
665 void
666 init_misc_mod_list(void)
667 {
668 static BOOL onetime = FALSE;
669 if (onetime) return;
670 #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
671 misc_mod_add(&spf_module_info);
672 #endif
673 onetime = TRUE;
674 }
675
676
677 #endif  /*!MACRO_PREDEF*/
678 /* End of drtables.c */