1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2023 */
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 */
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. */
21 lookup_info **lookup_list;
22 int lookup_list_count = 0;
24 /* Table of information about all possible authentication mechanisms. All
25 entries are always present if any mechanism is declared, but the functions are
26 set to NULL for those that are not compiled into the binary. */
29 #include "auths/cram_md5.h"
32 #ifdef AUTH_CYRUS_SASL
33 #include "auths/cyrus_sasl.h"
37 #include "auths/dovecot.h"
41 #include "auths/external.h"
45 #include "auths/gsasl_exim.h"
48 #ifdef AUTH_HEIMDAL_GSSAPI
49 #include "auths/heimdal_gssapi.h"
53 #include "auths/plaintext.h"
57 #include "auths/spa.h"
61 #include "auths/tls.h"
64 auth_info auths_available[] = {
66 /* Checking by an expansion condition on plain text */
70 .driver_name = US"cram_md5", /* lookup name */
71 .options = auth_cram_md5_options,
72 .options_count = &auth_cram_md5_options_count,
73 .options_block = &auth_cram_md5_option_defaults,
74 .options_len = sizeof(auth_cram_md5_options_block),
75 .init = auth_cram_md5_init,
76 .servercode = auth_cram_md5_server,
77 .clientcode = auth_cram_md5_client,
78 .version_report = NULL,
79 .macros_create = NULL,
83 #ifdef AUTH_CYRUS_SASL
85 .driver_name = US"cyrus_sasl",
86 .options = auth_cyrus_sasl_options,
87 .options_count = &auth_cyrus_sasl_options_count,
88 .options_block = &auth_cyrus_sasl_option_defaults,
89 .options_len = sizeof(auth_cyrus_sasl_options_block),
90 .init = auth_cyrus_sasl_init,
91 .servercode = auth_cyrus_sasl_server,
93 .version_report = auth_cyrus_sasl_version_report,
94 .macros_create = NULL,
100 .driver_name = US"dovecot",
101 .options = auth_dovecot_options,
102 .options_count = &auth_dovecot_options_count,
103 .options_block = &auth_dovecot_option_defaults,
104 .options_len = sizeof(auth_dovecot_options_block),
105 .init = auth_dovecot_init,
106 .servercode = auth_dovecot_server,
108 .version_report = NULL,
109 .macros_create = NULL,
115 .driver_name = US"external",
116 .options = auth_external_options,
117 .options_count = &auth_external_options_count,
118 .options_block = &auth_external_option_defaults,
119 .options_len = sizeof(auth_external_options_block),
120 .init = auth_external_init,
121 .servercode = auth_external_server,
122 .clientcode = auth_external_client,
123 .version_report = NULL,
124 .macros_create = NULL,
130 .driver_name = US"gsasl",
131 .options = auth_gsasl_options,
132 .options_count = &auth_gsasl_options_count,
133 .options_block = &auth_gsasl_option_defaults,
134 .options_len = sizeof(auth_gsasl_options_block),
135 .init = auth_gsasl_init,
136 .servercode = auth_gsasl_server,
137 .clientcode = auth_gsasl_client,
138 .version_report = auth_gsasl_version_report,
139 .macros_create = auth_gsasl_macros,
143 #ifdef AUTH_HEIMDAL_GSSAPI
145 .driver_name = US"heimdal_gssapi",
146 .options = auth_heimdal_gssapi_options,
147 .options_count = &auth_heimdal_gssapi_options_count,
148 .options_block = &auth_heimdal_gssapi_option_defaults,
149 .options_len = sizeof(auth_heimdal_gssapi_options_block),
150 .init = auth_heimdal_gssapi_init,
151 .servercode = auth_heimdal_gssapi_server,
153 .version_report = auth_heimdal_gssapi_version_report,
154 .macros_create = NULL,
158 #ifdef AUTH_PLAINTEXT
160 .driver_name = US"plaintext",
161 .options = auth_plaintext_options,
162 .options_count = &auth_plaintext_options_count,
163 .options_block = &auth_plaintext_option_defaults,
164 .options_len = sizeof(auth_plaintext_options_block),
165 .init = auth_plaintext_init,
166 .servercode = auth_plaintext_server,
167 .clientcode = auth_plaintext_client,
168 .version_report = NULL,
169 .macros_create = NULL,
175 .driver_name = US"spa",
176 .options = auth_spa_options,
177 .options_count = &auth_spa_options_count,
178 .options_block = &auth_spa_option_defaults,
179 .options_len = sizeof(auth_spa_options_block),
180 .init = auth_spa_init,
181 .servercode = auth_spa_server,
182 .clientcode = auth_spa_client,
183 .version_report = NULL,
184 .macros_create = NULL,
190 .driver_name = US"tls",
191 .options = auth_tls_options,
192 .options_count = &auth_tls_options_count,
193 .options_block = &auth_tls_option_defaults,
194 .options_len = sizeof(auth_tls_options_block),
195 .init = auth_tls_init,
196 .servercode = auth_tls_server,
198 .version_report = NULL,
199 .macros_create = NULL,
203 { .driver_name = US"" } /* end marker */
207 /* Tables of information about which routers and transports are included in the
210 /* Pull in the necessary header files */
212 #include "routers/rf_functions.h"
215 #include "routers/accept.h"
218 #ifdef ROUTER_DNSLOOKUP
219 #include "routers/dnslookup.h"
222 #ifdef ROUTER_MANUALROUTE
223 #include "routers/manualroute.h"
226 #ifdef ROUTER_IPLITERAL
227 #include "routers/ipliteral.h"
230 #ifdef ROUTER_IPLOOKUP
231 #include "routers/iplookup.h"
234 #ifdef ROUTER_QUERYPROGRAM
235 #include "routers/queryprogram.h"
238 #ifdef ROUTER_REDIRECT
239 #include "routers/redirect.h"
242 #ifdef TRANSPORT_APPENDFILE
243 #include "transports/appendfile.h"
246 #ifdef TRANSPORT_AUTOREPLY
247 #include "transports/autoreply.h"
250 #ifdef TRANSPORT_LMTP
251 #include "transports/lmtp.h"
254 #ifdef TRANSPORT_PIPE
255 #include "transports/pipe.h"
258 #ifdef EXPERIMENTAL_QUEUEFILE
259 #include "transports/queuefile.h"
262 #ifdef TRANSPORT_SMTP
263 #include "transports/smtp.h"
267 /* Now set up the structures, terminated by an entry with a null name. */
269 router_info routers_available[] = {
272 .driver_name = US"accept",
273 .options = accept_router_options,
274 .options_count = &accept_router_options_count,
275 .options_block = &accept_router_option_defaults,
276 .options_len = sizeof(accept_router_options_block),
277 .init = accept_router_init,
278 .code = accept_router_entry,
279 .tidyup = NULL, /* no tidyup entry */
280 .ri_flags = ri_yestransport
283 #ifdef ROUTER_DNSLOOKUP
285 .driver_name = US"dnslookup",
286 .options = dnslookup_router_options,
287 .options_count = &dnslookup_router_options_count,
288 .options_block = &dnslookup_router_option_defaults,
289 .options_len = sizeof(dnslookup_router_options_block),
290 .init = dnslookup_router_init,
291 .code = dnslookup_router_entry,
292 .tidyup = NULL, /* no tidyup entry */
293 .ri_flags = ri_yestransport
296 #ifdef ROUTER_IPLITERAL
298 .driver_name = US"ipliteral",
299 .options = ipliteral_router_options,
300 .options_count = &ipliteral_router_options_count,
301 .options_block = &ipliteral_router_option_defaults,
302 .options_len = sizeof(ipliteral_router_options_block),
303 .init = ipliteral_router_init,
304 .code = ipliteral_router_entry,
305 .tidyup = NULL, /* no tidyup entry */
306 .ri_flags = ri_yestransport
309 #ifdef ROUTER_IPLOOKUP
311 .driver_name = US"iplookup",
312 .options = iplookup_router_options,
313 .options_count = &iplookup_router_options_count,
314 .options_block = &iplookup_router_option_defaults,
315 .options_len = sizeof(iplookup_router_options_block),
316 .init = iplookup_router_init,
317 .code = iplookup_router_entry,
318 .tidyup = NULL, /* no tidyup entry */
319 .ri_flags = ri_notransport
322 #ifdef ROUTER_MANUALROUTE
324 .driver_name = US"manualroute",
325 .options = manualroute_router_options,
326 .options_count = &manualroute_router_options_count,
327 .options_block = &manualroute_router_option_defaults,
328 .options_len = sizeof(manualroute_router_options_block),
329 .init = manualroute_router_init,
330 .code = manualroute_router_entry,
331 .tidyup = NULL, /* no tidyup entry */
335 #ifdef ROUTER_QUERYPROGRAM
337 .driver_name = US"queryprogram",
338 .options = queryprogram_router_options,
339 .options_count = &queryprogram_router_options_count,
340 .options_block = &queryprogram_router_option_defaults,
341 .options_len = sizeof(queryprogram_router_options_block),
342 .init = queryprogram_router_init,
343 .code = queryprogram_router_entry,
344 .tidyup = NULL, /* no tidyup entry */
348 #ifdef ROUTER_REDIRECT
350 .driver_name = US"redirect",
351 .options = redirect_router_options,
352 .options_count = &redirect_router_options_count,
353 .options_block = &redirect_router_option_defaults,
354 .options_len = sizeof(redirect_router_options_block),
355 .init = redirect_router_init,
356 .code = redirect_router_entry,
357 .tidyup = NULL, /* no tidyup entry */
358 .ri_flags = ri_notransport
366 transport_info transports_available[] = {
367 #ifdef TRANSPORT_APPENDFILE
369 .driver_name = US"appendfile",
370 .options = appendfile_transport_options,
371 .options_count = &appendfile_transport_options_count,
372 .options_block = &appendfile_transport_option_defaults, /* private options defaults */
373 .options_len = sizeof(appendfile_transport_options_block),
374 .init = appendfile_transport_init,
375 .code = appendfile_transport_entry,
381 #ifdef TRANSPORT_AUTOREPLY
383 .driver_name = US"autoreply",
384 .options = autoreply_transport_options,
385 .options_count = &autoreply_transport_options_count,
386 .options_block = &autoreply_transport_option_defaults,
387 .options_len = sizeof(autoreply_transport_options_block),
388 .init = autoreply_transport_init,
389 .code = autoreply_transport_entry,
395 #ifdef TRANSPORT_LMTP
397 .driver_name = US"lmtp",
398 .options = lmtp_transport_options,
399 .options_count = &lmtp_transport_options_count,
400 .options_block = &lmtp_transport_option_defaults,
401 .options_len = sizeof(lmtp_transport_options_block),
402 .init = lmtp_transport_init,
403 .code = lmtp_transport_entry,
409 #ifdef TRANSPORT_PIPE
411 .driver_name = US"pipe",
412 .options = pipe_transport_options,
413 .options_count = &pipe_transport_options_count,
414 .options_block = &pipe_transport_option_defaults,
415 .options_len = sizeof(pipe_transport_options_block),
416 .init = pipe_transport_init,
417 .code = pipe_transport_entry,
423 #ifdef EXPERIMENTAL_QUEUEFILE
425 .driver_name = US"queuefile",
426 .options = queuefile_transport_options,
427 .options_count = &queuefile_transport_options_count,
428 .options_block = &queuefile_transport_option_defaults,
429 .options_len = sizeof(queuefile_transport_options_block),
430 .init = queuefile_transport_init,
431 .code = queuefile_transport_entry,
437 #ifdef TRANSPORT_SMTP
439 .driver_name = US"smtp",
440 .options = smtp_transport_options,
441 .options_count = &smtp_transport_options_count,
442 .options_block = &smtp_transport_option_defaults,
443 .options_len = sizeof(smtp_transport_options_block),
444 .init = smtp_transport_init,
445 .code = smtp_transport_entry,
447 .closedown = smtp_transport_closedown,
457 auth_show_supported(gstring * g)
459 g = string_cat(g, US"Authenticators:");
460 for (auth_info * ai = auths_available; ai->driver_name[0]; ai++)
461 g = string_fmt_append(g, " %s", ai->driver_name);
462 return string_cat(g, US"\n");
466 route_show_supported(gstring * g)
468 g = string_cat(g, US"Routers:");
469 for (router_info * rr = routers_available; rr->driver_name[0]; rr++)
470 g = string_fmt_append(g, " %s", rr->driver_name);
471 return string_cat(g, US"\n");
475 transport_show_supported(gstring * g)
477 g = string_cat(g, US"Transports:");
478 #ifdef TRANSPORT_APPENDFILE
479 g = string_cat(g, US" appendfile");
480 #ifdef SUPPORT_MAILDIR
481 g = string_cat(g, US"/maildir"); /* damn these subclasses */
483 #ifdef SUPPORT_MAILSTORE
484 g = string_cat(g, US"/mailstore");
487 g = string_cat(g, US"/mbx");
490 #ifdef TRANSPORT_AUTOREPLY
491 g = string_cat(g, US" autoreply");
493 #ifdef TRANSPORT_LMTP
494 g = string_cat(g, US" lmtp");
496 #ifdef TRANSPORT_PIPE
497 g = string_cat(g, US" pipe");
499 #ifdef EXPERIMENTAL_QUEUEFILE
500 g = string_cat(g, US" queuefile");
502 #ifdef TRANSPORT_SMTP
503 g = string_cat(g, US" smtp");
505 return string_cat(g, US"\n");
510 struct lookupmodulestr
513 struct lookup_module_info *info;
514 struct lookupmodulestr *next;
517 static struct lookupmodulestr *lookupmodules = NULL;
520 addlookupmodule(void *dl, struct lookup_module_info *info)
522 struct lookupmodulestr *p = store_get(sizeof(struct lookupmodulestr), GET_UNTAINTED);
526 p->next = lookupmodules;
528 lookup_list_count += info->lookupcount;
531 /* only valid after lookup_list and lookup_list_count are assigned */
533 add_lookup_to_list(lookup_info *info)
535 /* need to add the lookup to lookup_list, sorted */
538 /* strategy is to go through the list until we find
539 either an empty spot or a name that is higher.
540 this can't fail because we have enough space. */
542 while (lookup_list[pos] && (Ustrcmp(lookup_list[pos]->name, info->name) <= 0))
545 if (lookup_list[pos])
547 /* need to insert it, so move all the other items up
548 (last slot is still empty, of course) */
550 memmove(&lookup_list[pos+1], &lookup_list[pos],
551 sizeof(lookup_info *) * (lookup_list_count-pos-1));
553 lookup_list[pos] = info;
557 /* These need to be at file level for old versions of gcc (2.95.2 reported),
558 * which give parse errors on an extern in function scope. Each entry needs
559 * to also be invoked in init_lookup_list() below */
561 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
562 extern lookup_module_info cdb_lookup_module_info;
564 #if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
565 extern lookup_module_info dbmdb_lookup_module_info;
567 #if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
568 extern lookup_module_info dnsdb_lookup_module_info;
570 #if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
571 extern lookup_module_info dsearch_lookup_module_info;
573 #if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
574 extern lookup_module_info ibase_lookup_module_info;
576 #if defined(LOOKUP_JSON)
577 extern lookup_module_info json_lookup_module_info;
579 #if defined(LOOKUP_LDAP)
580 extern lookup_module_info ldap_lookup_module_info;
582 #if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
583 extern lookup_module_info lsearch_lookup_module_info;
585 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
586 extern lookup_module_info mysql_lookup_module_info;
588 #if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
589 extern lookup_module_info nis_lookup_module_info;
591 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
592 extern lookup_module_info nisplus_lookup_module_info;
594 #if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
595 extern lookup_module_info oracle_lookup_module_info;
597 #if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
598 extern lookup_module_info passwd_lookup_module_info;
600 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
601 extern lookup_module_info pgsql_lookup_module_info;
603 #if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
604 extern lookup_module_info redis_lookup_module_info;
606 #if defined(LOOKUP_LMDB)
607 extern lookup_module_info lmdb_lookup_module_info;
609 #if defined(SUPPORT_SPF)
610 extern lookup_module_info spf_lookup_module_info;
612 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
613 extern lookup_module_info sqlite_lookup_module_info;
615 #if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
616 extern lookup_module_info testdb_lookup_module_info;
618 #if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
619 extern lookup_module_info whoson_lookup_module_info;
622 extern lookup_module_info readsock_lookup_module_info;
626 init_lookup_list(void)
628 #ifdef LOOKUP_MODULE_DIR
631 int countmodules = 0;
632 int moduleerrors = 0;
634 static BOOL lookup_list_init_done = FALSE;
637 if (lookup_list_init_done)
639 reset_point = store_mark();
640 lookup_list_init_done = TRUE;
642 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
643 addlookupmodule(NULL, &cdb_lookup_module_info);
646 #if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
647 addlookupmodule(NULL, &dbmdb_lookup_module_info);
650 #if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
651 addlookupmodule(NULL, &dnsdb_lookup_module_info);
654 #if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
655 addlookupmodule(NULL, &dsearch_lookup_module_info);
658 #if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
659 addlookupmodule(NULL, &ibase_lookup_module_info);
663 addlookupmodule(NULL, &ldap_lookup_module_info);
667 addlookupmodule(NULL, &json_lookup_module_info);
670 #if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
671 addlookupmodule(NULL, &lsearch_lookup_module_info);
674 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
675 addlookupmodule(NULL, &mysql_lookup_module_info);
678 #if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
679 addlookupmodule(NULL, &nis_lookup_module_info);
682 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
683 addlookupmodule(NULL, &nisplus_lookup_module_info);
686 #if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
687 addlookupmodule(NULL, &oracle_lookup_module_info);
690 #if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
691 addlookupmodule(NULL, &passwd_lookup_module_info);
694 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
695 addlookupmodule(NULL, &pgsql_lookup_module_info);
698 #if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
699 addlookupmodule(NULL, &redis_lookup_module_info);
703 addlookupmodule(NULL, &lmdb_lookup_module_info);
707 addlookupmodule(NULL, &spf_lookup_module_info);
710 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
711 addlookupmodule(NULL, &sqlite_lookup_module_info);
714 #if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
715 addlookupmodule(NULL, &testdb_lookup_module_info);
718 #if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
719 addlookupmodule(NULL, &whoson_lookup_module_info);
722 addlookupmodule(NULL, &readsock_lookup_module_info);
724 #ifdef LOOKUP_MODULE_DIR
725 if (!(dd = exim_opendir(LOOKUP_MODULE_DIR)))
727 DEBUG(D_lookup) debug_printf("Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
728 log_write(0, LOG_MAIN, "Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
732 const pcre2_code * regex_islookupmod = regex_must_compile(
733 US"\\." DYNLIB_FN_EXT "$", MCS_NOFLAGS, TRUE);
735 DEBUG(D_lookup) debug_printf("Loading lookup modules from %s\n", LOOKUP_MODULE_DIR);
736 while ((ent = readdir(dd)))
738 char * name = ent->d_name;
739 int len = (int)strlen(name);
740 if (regex_match(regex_islookupmod, US name, len, NULL))
742 int pathnamelen = len + (int)strlen(LOOKUP_MODULE_DIR) + 2;
744 struct lookup_module_info *info;
745 const char *errormsg;
747 /* SRH: am I being paranoid here or what? */
748 if (pathnamelen > big_buffer_size)
750 fprintf(stderr, "Loading lookup modules: %s/%s: name too long\n", LOOKUP_MODULE_DIR, name);
751 log_write(0, LOG_MAIN|LOG_PANIC, "%s/%s: name too long\n", LOOKUP_MODULE_DIR, name);
755 /* SRH: snprintf here? */
756 sprintf(CS big_buffer, "%s/%s", LOOKUP_MODULE_DIR, name);
758 if (!(dl = dlopen(CS big_buffer, RTLD_NOW)))
760 errormsg = dlerror();
761 fprintf(stderr, "Error loading %s: %s\n", name, errormsg);
762 log_write(0, LOG_MAIN|LOG_PANIC, "Error loading lookup module %s: %s\n", name, errormsg);
767 /* FreeBSD nsdispatch() can trigger dlerror() errors about
768 _nss_cache_cycle_prevention_function; we need to clear the dlerror()
769 state before calling dlsym(), so that any error afterwards only comes
772 errormsg = dlerror();
774 info = (struct lookup_module_info*) dlsym(dl, "_lookup_module_info");
775 if ((errormsg = dlerror()))
777 fprintf(stderr, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
778 log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
783 if (info->magic != LOOKUP_MODULE_INFO_MAGIC)
785 fprintf(stderr, "Lookup module %s is not compatible with this version of Exim\n", name);
786 log_write(0, LOG_MAIN|LOG_PANIC, "Lookup module %s is not compatible with this version of Exim\n", name);
792 addlookupmodule(dl, info);
793 DEBUG(D_lookup) debug_printf("Loaded \"%s\" (%d lookup types)\n", name, info->lookupcount);
797 store_free((void*)regex_islookupmod);
801 DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules);
804 DEBUG(D_lookup) debug_printf("Total %d lookups\n", lookup_list_count);
806 lookup_list = store_malloc(sizeof(lookup_info *) * lookup_list_count);
807 memset(lookup_list, 0, sizeof(lookup_info *) * lookup_list_count);
809 /* now add all lookups to the real list */
810 for (struct lookupmodulestr * p = lookupmodules; p; p = p->next)
811 for (int j = 0; j < p->info->lookupcount; j++)
812 add_lookup_to_list(p->info->lookups[j]);
813 store_reset(reset_point);
814 /* just to be sure */
815 lookupmodules = NULL;
818 #endif /*!MACRO_PREDEF*/
819 /* End of drtables.c */