1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
13 /* This module contains tables that define the lookup methods and drivers
14 that are actually included in the binary. Its contents are controlled by
15 various macros in config.h that ultimately come from Local/Makefile. They are
16 all described in src/EDITME. */
19 lookup_info **lookup_list;
20 int lookup_list_count = 0;
22 /* Table of information about all possible authentication mechanisms. All
23 entries are always present if any mechanism is declared, but the functions are
24 set to NULL for those that are not compiled into the binary. */
27 #include "auths/cram_md5.h"
30 #ifdef AUTH_CYRUS_SASL
31 #include "auths/cyrus_sasl.h"
35 #include "auths/dovecot.h"
39 #include "auths/external.h"
43 #include "auths/gsasl_exim.h"
46 #ifdef AUTH_HEIMDAL_GSSAPI
47 #include "auths/heimdal_gssapi.h"
51 #include "auths/plaintext.h"
55 #include "auths/spa.h"
59 #include "auths/tls.h"
62 auth_info auths_available[] = {
64 /* Checking by an expansion condition on plain text */
68 .driver_name = US"cram_md5", /* lookup name */
69 .options = auth_cram_md5_options,
70 .options_count = &auth_cram_md5_options_count,
71 .options_block = &auth_cram_md5_option_defaults,
72 .options_len = sizeof(auth_cram_md5_options_block),
73 .init = auth_cram_md5_init,
74 .servercode = auth_cram_md5_server,
75 .clientcode = auth_cram_md5_client,
76 .version_report = NULL,
77 .macros_create = NULL,
81 #ifdef AUTH_CYRUS_SASL
83 .driver_name = US"cyrus_sasl",
84 .options = auth_cyrus_sasl_options,
85 .options_count = &auth_cyrus_sasl_options_count,
86 .options_block = &auth_cyrus_sasl_option_defaults,
87 .options_len = sizeof(auth_cyrus_sasl_options_block),
88 .init = auth_cyrus_sasl_init,
89 .servercode = auth_cyrus_sasl_server,
91 .version_report = auth_cyrus_sasl_version_report,
92 .macros_create = NULL,
98 .driver_name = US"dovecot",
99 .options = auth_dovecot_options,
100 .options_count = &auth_dovecot_options_count,
101 .options_block = &auth_dovecot_option_defaults,
102 .options_len = sizeof(auth_dovecot_options_block),
103 .init = auth_dovecot_init,
104 .servercode = auth_dovecot_server,
106 .version_report = NULL,
107 .macros_create = NULL,
113 .driver_name = US"external",
114 .options = auth_external_options,
115 .options_count = &auth_external_options_count,
116 .options_block = &auth_external_option_defaults,
117 .options_len = sizeof(auth_external_options_block),
118 .init = auth_external_init,
119 .servercode = auth_external_server,
120 .clientcode = auth_external_client,
121 .version_report = NULL,
122 .macros_create = NULL,
128 .driver_name = US"gsasl",
129 .options = auth_gsasl_options,
130 .options_count = &auth_gsasl_options_count,
131 .options_block = &auth_gsasl_option_defaults,
132 .options_len = sizeof(auth_gsasl_options_block),
133 .init = auth_gsasl_init,
134 .servercode = auth_gsasl_server,
135 .clientcode = auth_gsasl_client,
136 .version_report = auth_gsasl_version_report,
137 .macros_create = auth_gsasl_macros,
141 #ifdef AUTH_HEIMDAL_GSSAPI
143 .driver_name = US"heimdal_gssapi",
144 .options = auth_heimdal_gssapi_options,
145 .options_count = &auth_heimdal_gssapi_options_count,
146 .options_block = &auth_heimdal_gssapi_option_defaults,
147 .options_len = sizeof(auth_heimdal_gssapi_options_block),
148 .init = auth_heimdal_gssapi_init,
149 .servercode = auth_heimdal_gssapi_server,
151 .version_report = auth_heimdal_gssapi_version_report,
152 .macros_create = NULL,
156 #ifdef AUTH_PLAINTEXT
158 .driver_name = US"plaintext",
159 .options = auth_plaintext_options,
160 .options_count = &auth_plaintext_options_count,
161 .options_block = &auth_plaintext_option_defaults,
162 .options_len = sizeof(auth_plaintext_options_block),
163 .init = auth_plaintext_init,
164 .servercode = auth_plaintext_server,
165 .clientcode = auth_plaintext_client,
166 .version_report = NULL,
167 .macros_create = NULL,
173 .driver_name = US"spa",
174 .options = auth_spa_options,
175 .options_count = &auth_spa_options_count,
176 .options_block = &auth_spa_option_defaults,
177 .options_len = sizeof(auth_spa_options_block),
178 .init = auth_spa_init,
179 .servercode = auth_spa_server,
180 .clientcode = auth_spa_client,
181 .version_report = NULL,
182 .macros_create = NULL,
188 .driver_name = US"tls",
189 .options = auth_tls_options,
190 .options_count = &auth_tls_options_count,
191 .options_block = &auth_tls_option_defaults,
192 .options_len = sizeof(auth_tls_options_block),
193 .init = auth_tls_init,
194 .servercode = auth_tls_server,
196 .version_report = NULL,
197 .macros_create = NULL,
201 { .driver_name = US"" } /* end marker */
205 /* Tables of information about which routers and transports are included in the
208 /* Pull in the necessary header files */
210 #include "routers/rf_functions.h"
213 #include "routers/accept.h"
216 #ifdef ROUTER_DNSLOOKUP
217 #include "routers/dnslookup.h"
220 #ifdef ROUTER_MANUALROUTE
221 #include "routers/manualroute.h"
224 #ifdef ROUTER_IPLITERAL
225 #include "routers/ipliteral.h"
228 #ifdef ROUTER_IPLOOKUP
229 #include "routers/iplookup.h"
232 #ifdef ROUTER_QUERYPROGRAM
233 #include "routers/queryprogram.h"
236 #ifdef ROUTER_REDIRECT
237 #include "routers/redirect.h"
240 #ifdef TRANSPORT_APPENDFILE
241 #include "transports/appendfile.h"
244 #ifdef TRANSPORT_AUTOREPLY
245 #include "transports/autoreply.h"
248 #ifdef TRANSPORT_LMTP
249 #include "transports/lmtp.h"
252 #ifdef TRANSPORT_PIPE
253 #include "transports/pipe.h"
256 #ifdef EXPERIMENTAL_QUEUEFILE
257 #include "transports/queuefile.h"
260 #ifdef TRANSPORT_SMTP
261 #include "transports/smtp.h"
265 /* Now set up the structures, terminated by an entry with a null name. */
267 router_info routers_available[] = {
270 .driver_name = US"accept",
271 .options = accept_router_options,
272 .options_count = &accept_router_options_count,
273 .options_block = &accept_router_option_defaults,
274 .options_len = sizeof(accept_router_options_block),
275 .init = accept_router_init,
276 .code = accept_router_entry,
277 .tidyup = NULL, /* no tidyup entry */
278 .ri_flags = ri_yestransport
281 #ifdef ROUTER_DNSLOOKUP
283 .driver_name = US"dnslookup",
284 .options = dnslookup_router_options,
285 .options_count = &dnslookup_router_options_count,
286 .options_block = &dnslookup_router_option_defaults,
287 .options_len = sizeof(dnslookup_router_options_block),
288 .init = dnslookup_router_init,
289 .code = dnslookup_router_entry,
290 .tidyup = NULL, /* no tidyup entry */
291 .ri_flags = ri_yestransport
294 #ifdef ROUTER_IPLITERAL
296 .driver_name = US"ipliteral",
297 .options = ipliteral_router_options,
298 .options_count = &ipliteral_router_options_count,
299 .options_block = &ipliteral_router_option_defaults,
300 .options_len = sizeof(ipliteral_router_options_block),
301 .init = ipliteral_router_init,
302 .code = ipliteral_router_entry,
303 .tidyup = NULL, /* no tidyup entry */
304 .ri_flags = ri_yestransport
307 #ifdef ROUTER_IPLOOKUP
309 .driver_name = US"iplookup",
310 .options = iplookup_router_options,
311 .options_count = &iplookup_router_options_count,
312 .options_block = &iplookup_router_option_defaults,
313 .options_len = sizeof(iplookup_router_options_block),
314 .init = iplookup_router_init,
315 .code = iplookup_router_entry,
316 .tidyup = NULL, /* no tidyup entry */
317 .ri_flags = ri_notransport
320 #ifdef ROUTER_MANUALROUTE
322 .driver_name = US"manualroute",
323 .options = manualroute_router_options,
324 .options_count = &manualroute_router_options_count,
325 .options_block = &manualroute_router_option_defaults,
326 .options_len = sizeof(manualroute_router_options_block),
327 .init = manualroute_router_init,
328 .code = manualroute_router_entry,
329 .tidyup = NULL, /* no tidyup entry */
333 #ifdef ROUTER_QUERYPROGRAM
335 .driver_name = US"queryprogram",
336 .options = queryprogram_router_options,
337 .options_count = &queryprogram_router_options_count,
338 .options_block = &queryprogram_router_option_defaults,
339 .options_len = sizeof(queryprogram_router_options_block),
340 .init = queryprogram_router_init,
341 .code = queryprogram_router_entry,
342 .tidyup = NULL, /* no tidyup entry */
346 #ifdef ROUTER_REDIRECT
348 .driver_name = US"redirect",
349 .options = redirect_router_options,
350 .options_count = &redirect_router_options_count,
351 .options_block = &redirect_router_option_defaults,
352 .options_len = sizeof(redirect_router_options_block),
353 .init = redirect_router_init,
354 .code = redirect_router_entry,
355 .tidyup = NULL, /* no tidyup entry */
356 .ri_flags = ri_notransport
364 transport_info transports_available[] = {
365 #ifdef TRANSPORT_APPENDFILE
367 .driver_name = US"appendfile",
368 .options = appendfile_transport_options,
369 .options_count = &appendfile_transport_options_count,
370 .options_block = &appendfile_transport_option_defaults, /* private options defaults */
371 .options_len = sizeof(appendfile_transport_options_block),
372 .init = appendfile_transport_init,
373 .code = appendfile_transport_entry,
379 #ifdef TRANSPORT_AUTOREPLY
381 .driver_name = US"autoreply",
382 .options = autoreply_transport_options,
383 .options_count = &autoreply_transport_options_count,
384 .options_block = &autoreply_transport_option_defaults,
385 .options_len = sizeof(autoreply_transport_options_block),
386 .init = autoreply_transport_init,
387 .code = autoreply_transport_entry,
393 #ifdef TRANSPORT_LMTP
395 .driver_name = US"lmtp",
396 .options = lmtp_transport_options,
397 .options_count = &lmtp_transport_options_count,
398 .options_block = &lmtp_transport_option_defaults,
399 .options_len = sizeof(lmtp_transport_options_block),
400 .init = lmtp_transport_init,
401 .code = lmtp_transport_entry,
407 #ifdef TRANSPORT_PIPE
409 .driver_name = US"pipe",
410 .options = pipe_transport_options,
411 .options_count = &pipe_transport_options_count,
412 .options_block = &pipe_transport_option_defaults,
413 .options_len = sizeof(pipe_transport_options_block),
414 .init = pipe_transport_init,
415 .code = pipe_transport_entry,
421 #ifdef EXPERIMENTAL_QUEUEFILE
423 .driver_name = US"queuefile",
424 .options = queuefile_transport_options,
425 .options_count = &queuefile_transport_options_count,
426 .options_block = &queuefile_transport_option_defaults,
427 .options_len = sizeof(queuefile_transport_options_block),
428 .init = queuefile_transport_init,
429 .code = queuefile_transport_entry,
435 #ifdef TRANSPORT_SMTP
437 .driver_name = US"smtp",
438 .options = smtp_transport_options,
439 .options_count = &smtp_transport_options_count,
440 .options_block = &smtp_transport_option_defaults,
441 .options_len = sizeof(smtp_transport_options_block),
442 .init = smtp_transport_init,
443 .code = smtp_transport_entry,
445 .closedown = smtp_transport_closedown,
455 auth_show_supported(gstring * g)
457 g = string_cat(g, US"Authenticators:");
458 for (auth_info * ai = auths_available; ai->driver_name[0]; ai++)
459 g = string_fmt_append(g, " %s", ai->driver_name);
460 return string_cat(g, US"\n");
464 route_show_supported(gstring * g)
466 g = string_cat(g, US"Routers:");
467 for (router_info * rr = routers_available; rr->driver_name[0]; rr++)
468 g = string_fmt_append(g, " %s", rr->driver_name);
469 return string_cat(g, US"\n");
473 transport_show_supported(gstring * g)
475 g = string_cat(g, US"Transports:");
476 #ifdef TRANSPORT_APPENDFILE
477 g = string_cat(g, US" appendfile");
478 #ifdef SUPPORT_MAILDIR
479 g = string_cat(g, US"/maildir"); /* damn these subclasses */
481 #ifdef SUPPORT_MAILSTORE
482 g = string_cat(g, US"/mailstore");
485 g = string_cat(g, US"/mbx");
488 #ifdef TRANSPORT_AUTOREPLY
489 g = string_cat(g, US" autoreply");
491 #ifdef TRANSPORT_LMTP
492 g = string_cat(g, US" lmtp");
494 #ifdef TRANSPORT_PIPE
495 g = string_cat(g, US" pipe");
497 #ifdef EXPERIMENTAL_QUEUEFILE
498 g = string_cat(g, US" queuefile");
500 #ifdef TRANSPORT_SMTP
501 g = string_cat(g, US" smtp");
503 return string_cat(g, US"\n");
508 struct lookupmodulestr
511 struct lookup_module_info *info;
512 struct lookupmodulestr *next;
515 static struct lookupmodulestr *lookupmodules = NULL;
518 addlookupmodule(void *dl, struct lookup_module_info *info)
520 struct lookupmodulestr *p = store_get(sizeof(struct lookupmodulestr), FALSE);
524 p->next = lookupmodules;
526 lookup_list_count += info->lookupcount;
529 /* only valid after lookup_list and lookup_list_count are assigned */
531 add_lookup_to_list(lookup_info *info)
533 /* need to add the lookup to lookup_list, sorted */
536 /* strategy is to go through the list until we find
537 either an empty spot or a name that is higher.
538 this can't fail because we have enough space. */
540 while (lookup_list[pos] && (Ustrcmp(lookup_list[pos]->name, info->name) <= 0))
543 if (lookup_list[pos])
545 /* need to insert it, so move all the other items up
546 (last slot is still empty, of course) */
548 memmove(&lookup_list[pos+1], &lookup_list[pos],
549 sizeof(lookup_info *) * (lookup_list_count-pos-1));
551 lookup_list[pos] = info;
555 /* These need to be at file level for old versions of gcc (2.95.2 reported),
556 * which give parse errors on an extern in function scope. Each entry needs
557 * to also be invoked in init_lookup_list() below */
559 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
560 extern lookup_module_info cdb_lookup_module_info;
562 #if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
563 extern lookup_module_info dbmdb_lookup_module_info;
565 #if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
566 extern lookup_module_info dnsdb_lookup_module_info;
568 #if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
569 extern lookup_module_info dsearch_lookup_module_info;
571 #if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
572 extern lookup_module_info ibase_lookup_module_info;
574 #if defined(LOOKUP_JSON)
575 extern lookup_module_info json_lookup_module_info;
577 #if defined(LOOKUP_LDAP)
578 extern lookup_module_info ldap_lookup_module_info;
580 #if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
581 extern lookup_module_info lsearch_lookup_module_info;
583 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
584 extern lookup_module_info mysql_lookup_module_info;
586 #if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
587 extern lookup_module_info nis_lookup_module_info;
589 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
590 extern lookup_module_info nisplus_lookup_module_info;
592 #if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
593 extern lookup_module_info oracle_lookup_module_info;
595 #if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
596 extern lookup_module_info passwd_lookup_module_info;
598 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
599 extern lookup_module_info pgsql_lookup_module_info;
601 #if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
602 extern lookup_module_info redis_lookup_module_info;
604 #if defined(EXPERIMENTAL_LMDB)
605 extern lookup_module_info lmdb_lookup_module_info;
607 #if defined(SUPPORT_SPF)
608 extern lookup_module_info spf_lookup_module_info;
610 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
611 extern lookup_module_info sqlite_lookup_module_info;
613 #if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
614 extern lookup_module_info testdb_lookup_module_info;
616 #if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
617 extern lookup_module_info whoson_lookup_module_info;
620 extern lookup_module_info readsock_lookup_module_info;
624 init_lookup_list(void)
626 #ifdef LOOKUP_MODULE_DIR
629 int countmodules = 0;
630 int moduleerrors = 0;
632 static BOOL lookup_list_init_done = FALSE;
635 if (lookup_list_init_done)
637 reset_point = store_mark();
638 lookup_list_init_done = TRUE;
640 #if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
641 addlookupmodule(NULL, &cdb_lookup_module_info);
644 #if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
645 addlookupmodule(NULL, &dbmdb_lookup_module_info);
648 #if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
649 addlookupmodule(NULL, &dnsdb_lookup_module_info);
652 #if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
653 addlookupmodule(NULL, &dsearch_lookup_module_info);
656 #if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
657 addlookupmodule(NULL, &ibase_lookup_module_info);
661 addlookupmodule(NULL, &ldap_lookup_module_info);
665 addlookupmodule(NULL, &json_lookup_module_info);
668 #if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
669 addlookupmodule(NULL, &lsearch_lookup_module_info);
672 #if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
673 addlookupmodule(NULL, &mysql_lookup_module_info);
676 #if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
677 addlookupmodule(NULL, &nis_lookup_module_info);
680 #if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
681 addlookupmodule(NULL, &nisplus_lookup_module_info);
684 #if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
685 addlookupmodule(NULL, &oracle_lookup_module_info);
688 #if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
689 addlookupmodule(NULL, &passwd_lookup_module_info);
692 #if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
693 addlookupmodule(NULL, &pgsql_lookup_module_info);
696 #if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
697 addlookupmodule(NULL, &redis_lookup_module_info);
700 #ifdef EXPERIMENTAL_LMDB
701 addlookupmodule(NULL, &lmdb_lookup_module_info);
705 addlookupmodule(NULL, &spf_lookup_module_info);
708 #if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
709 addlookupmodule(NULL, &sqlite_lookup_module_info);
712 #if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
713 addlookupmodule(NULL, &testdb_lookup_module_info);
716 #if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
717 addlookupmodule(NULL, &whoson_lookup_module_info);
720 addlookupmodule(NULL, &readsock_lookup_module_info);
722 #ifdef LOOKUP_MODULE_DIR
723 if (!(dd = exim_opendir(LOOKUP_MODULE_DIR)))
725 DEBUG(D_lookup) debug_printf("Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
726 log_write(0, LOG_MAIN, "Couldn't open %s: not loading lookup modules\n", LOOKUP_MODULE_DIR);
730 const pcre *regex_islookupmod = regex_must_compile(
731 US"\\." DYNLIB_FN_EXT "$", FALSE, TRUE);
733 DEBUG(D_lookup) debug_printf("Loading lookup modules from %s\n", LOOKUP_MODULE_DIR);
734 while ((ent = readdir(dd)))
736 char *name = ent->d_name;
737 int len = (int)strlen(name);
738 if (pcre_exec(regex_islookupmod, NULL, name, len, 0, PCRE_EOPT, NULL, 0) >= 0)
740 int pathnamelen = len + (int)strlen(LOOKUP_MODULE_DIR) + 2;
742 struct lookup_module_info *info;
743 const char *errormsg;
745 /* SRH: am I being paranoid here or what? */
746 if (pathnamelen > big_buffer_size)
748 fprintf(stderr, "Loading lookup modules: %s/%s: name too long\n", LOOKUP_MODULE_DIR, name);
749 log_write(0, LOG_MAIN|LOG_PANIC, "%s/%s: name too long\n", LOOKUP_MODULE_DIR, name);
753 /* SRH: snprintf here? */
754 sprintf(CS big_buffer, "%s/%s", LOOKUP_MODULE_DIR, name);
756 if (!(dl = dlopen(CS big_buffer, RTLD_NOW)))
758 errormsg = dlerror();
759 fprintf(stderr, "Error loading %s: %s\n", name, errormsg);
760 log_write(0, LOG_MAIN|LOG_PANIC, "Error loading lookup module %s: %s\n", name, errormsg);
765 /* FreeBSD nsdispatch() can trigger dlerror() errors about
766 * _nss_cache_cycle_prevention_function; we need to clear the dlerror()
767 * state before calling dlsym(), so that any error afterwards only
768 * comes from dlsym().
770 errormsg = dlerror();
772 info = (struct lookup_module_info*) dlsym(dl, "_lookup_module_info");
773 if ((errormsg = dlerror()))
775 fprintf(stderr, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
776 log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be a lookup module (%s)\n", name, errormsg);
781 if (info->magic != LOOKUP_MODULE_INFO_MAGIC)
783 fprintf(stderr, "Lookup module %s is not compatible with this version of Exim\n", name);
784 log_write(0, LOG_MAIN|LOG_PANIC, "Lookup module %s is not compatible with this version of Exim\n", name);
790 addlookupmodule(dl, info);
791 DEBUG(D_lookup) debug_printf("Loaded \"%s\" (%d lookup types)\n", name, info->lookupcount);
795 store_free((void*)regex_islookupmod);
799 DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules);
802 DEBUG(D_lookup) debug_printf("Total %d lookups\n", lookup_list_count);
804 lookup_list = store_malloc(sizeof(lookup_info *) * lookup_list_count);
805 memset(lookup_list, 0, sizeof(lookup_info *) * lookup_list_count);
807 /* now add all lookups to the real list */
808 for (struct lookupmodulestr * p = lookupmodules; p; p = p->next)
809 for (int j = 0; j < p->info->lookupcount; j++)
810 add_lookup_to_list(p->info->lookups[j]);
811 store_reset(reset_point);
812 /* just to be sure */
813 lookupmodules = NULL;
816 #endif /*!MACRO_PREDEF*/
817 /* End of drtables.c */