X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fsrc%2Freadconf.c;h=6b8f8d23e4a091c8d665d1cc0a06bc3cdde6d796;hb=dcb72db9ece0902199a95f6a06fa56ce10587dd4;hp=caa78ee90925f3c7817315f4bc22e92818cf93bb;hpb=35edf2ff67ad9fa5fc0e83bde865d807c297864f;p=user%2Fhenk%2Fcode%2Fexim.git diff --git a/src/src/readconf.c b/src/src/readconf.c index caa78ee90..fb9d47a09 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/readconf.c,v 1.2 2004/10/18 09:16:57 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2004 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for reading the configuration file, and for displaying @@ -13,6 +11,14 @@ implementation of the conditional .ifdef etc. */ #include "exim.h" +extern char **environ; + +static void fn_smtp_receive_timeout(const uschar * name, const uschar * str); +static void save_config_line(const uschar* line); +static void save_config_position(const uschar *file, int line); +static void print_config(BOOL admin, BOOL terse); + + #define CSTATE_STACK_SIZE 10 @@ -25,6 +31,15 @@ typedef struct config_file_item { int lineno; } config_file_item; +/* Structure for chain of configuration lines (-bP config) */ + +typedef struct config_line_item { + struct config_line_item *next; + uschar *line; +} config_line_item; + +static config_line_item* config_lines; + /* Structure of table of conditional words and their state transitions */ typedef struct cond_item { @@ -42,6 +57,8 @@ typedef struct syslog_fac_item { int value; } syslog_fac_item; +/* constants */ +static const char * const hidden = ""; /* Static variables */ @@ -135,14 +152,28 @@ static optionlist optionlist_config[] = { { "*set_system_filter_user", opt_bool|opt_hidden, &system_filter_uid_set }, { "accept_8bitmime", opt_bool, &accept_8bitmime }, { "acl_not_smtp", opt_stringptr, &acl_not_smtp }, +#ifdef WITH_CONTENT_SCAN + { "acl_not_smtp_mime", opt_stringptr, &acl_not_smtp_mime }, +#endif + { "acl_not_smtp_start", opt_stringptr, &acl_not_smtp_start }, { "acl_smtp_auth", opt_stringptr, &acl_smtp_auth }, { "acl_smtp_connect", opt_stringptr, &acl_smtp_connect }, { "acl_smtp_data", opt_stringptr, &acl_smtp_data }, +#ifndef DISABLE_PRDR + { "acl_smtp_data_prdr", opt_stringptr, &acl_smtp_data_prdr }, +#endif +#ifndef DISABLE_DKIM + { "acl_smtp_dkim", opt_stringptr, &acl_smtp_dkim }, +#endif { "acl_smtp_etrn", opt_stringptr, &acl_smtp_etrn }, { "acl_smtp_expn", opt_stringptr, &acl_smtp_expn }, { "acl_smtp_helo", opt_stringptr, &acl_smtp_helo }, { "acl_smtp_mail", opt_stringptr, &acl_smtp_mail }, { "acl_smtp_mailauth", opt_stringptr, &acl_smtp_mailauth }, +#ifdef WITH_CONTENT_SCAN + { "acl_smtp_mime", opt_stringptr, &acl_smtp_mime }, +#endif + { "acl_smtp_notquit", opt_stringptr, &acl_smtp_notquit }, { "acl_smtp_predata", opt_stringptr, &acl_smtp_predata }, { "acl_smtp_quit", opt_stringptr, &acl_smtp_quit }, { "acl_smtp_rcpt", opt_stringptr, &acl_smtp_rcpt }, @@ -150,16 +181,24 @@ static optionlist optionlist_config[] = { { "acl_smtp_starttls", opt_stringptr, &acl_smtp_starttls }, #endif { "acl_smtp_vrfy", opt_stringptr, &acl_smtp_vrfy }, + { "add_environment", opt_stringptr, &add_environment }, { "admin_groups", opt_gidlist, &admin_groups }, { "allow_domain_literals", opt_bool, &allow_domain_literals }, { "allow_mx_to_ip", opt_bool, &allow_mx_to_ip }, { "allow_utf8_domains", opt_bool, &allow_utf8_domains }, { "auth_advertise_hosts", opt_stringptr, &auth_advertise_hosts }, { "auto_thaw", opt_time, &auto_thaw }, +#ifdef WITH_CONTENT_SCAN + { "av_scanner", opt_stringptr, &av_scanner }, +#endif { "bi_command", opt_stringptr, &bi_command }, +#ifdef EXPERIMENTAL_BRIGHTMAIL + { "bmi_config_file", opt_stringptr, &bmi_config_file }, +#endif { "bounce_message_file", opt_stringptr, &bounce_message_file }, { "bounce_message_text", opt_stringptr, &bounce_message_text }, { "bounce_return_body", opt_bool, &bounce_return_body }, + { "bounce_return_linesize_limit", opt_mkint, &bounce_return_linesize_limit }, { "bounce_return_message", opt_bool, &bounce_return_message }, { "bounce_return_size_limit", opt_mkint, &bounce_return_size_limit }, { "bounce_sender_authentication",opt_stringptr,&bounce_sender_authentication }, @@ -170,26 +209,57 @@ static optionlist optionlist_config[] = { { "callout_random_local_part",opt_stringptr, &callout_random_local_part }, { "check_log_inodes", opt_int, &check_log_inodes }, { "check_log_space", opt_Kint, &check_log_space }, + { "check_rfc2047_length", opt_bool, &check_rfc2047_length }, { "check_spool_inodes", opt_int, &check_spool_inodes }, { "check_spool_space", opt_Kint, &check_spool_space }, + { "chunking_advertise_hosts", opt_stringptr, &chunking_advertise_hosts }, { "daemon_smtp_port", opt_stringptr|opt_hidden, &daemon_smtp_port }, { "daemon_smtp_ports", opt_stringptr, &daemon_smtp_port }, + { "daemon_startup_retries", opt_int, &daemon_startup_retries }, + { "daemon_startup_sleep", opt_time, &daemon_startup_sleep }, +#ifdef EXPERIMENTAL_DCC + { "dcc_direct_add_header", opt_bool, &dcc_direct_add_header }, + { "dccifd_address", opt_stringptr, &dccifd_address }, + { "dccifd_options", opt_stringptr, &dccifd_options }, +#endif { "delay_warning", opt_timelist, &delay_warning }, { "delay_warning_condition", opt_stringptr, &delay_warning_condition }, { "deliver_drop_privilege", opt_bool, &deliver_drop_privilege }, { "deliver_queue_load_max", opt_fixed, &deliver_queue_load_max }, { "delivery_date_remove", opt_bool, &delivery_date_remove }, +#ifdef ENABLE_DISABLE_FSYNC + { "disable_fsync", opt_bool, &disable_fsync }, +#endif + { "disable_ipv6", opt_bool, &disable_ipv6 }, +#ifndef DISABLE_DKIM + { "dkim_verify_signers", opt_stringptr, &dkim_verify_signers }, +#endif +#ifdef EXPERIMENTAL_DMARC + { "dmarc_forensic_sender", opt_stringptr, &dmarc_forensic_sender }, + { "dmarc_history_file", opt_stringptr, &dmarc_history_file }, + { "dmarc_tld_file", opt_stringptr, &dmarc_tld_file }, +#endif { "dns_again_means_nonexist", opt_stringptr, &dns_again_means_nonexist }, { "dns_check_names_pattern", opt_stringptr, &check_dns_names_pattern }, + { "dns_csa_search_limit", opt_int, &dns_csa_search_limit }, + { "dns_csa_use_reverse", opt_bool, &dns_csa_use_reverse }, + { "dns_dnssec_ok", opt_int, &dns_dnssec_ok }, { "dns_ipv4_lookup", opt_stringptr, &dns_ipv4_lookup }, { "dns_retrans", opt_time, &dns_retrans }, { "dns_retry", opt_int, &dns_retry }, + { "dns_trust_aa", opt_stringptr, &dns_trust_aa }, + { "dns_use_edns0", opt_int, &dns_use_edns0 }, /* This option is now a no-op, retained for compability */ { "drop_cr", opt_bool, &drop_cr }, /*********************************************************/ + { "dsn_advertise_hosts", opt_stringptr, &dsn_advertise_hosts }, + { "dsn_from", opt_stringptr, &dsn_from }, { "envelope_to_remove", opt_bool, &envelope_to_remove }, { "errors_copy", opt_stringptr, &errors_copy }, { "errors_reply_to", opt_stringptr, &errors_reply_to }, +#ifndef DISABLE_EVENT + { "event_action", opt_stringptr, &event_action }, +#endif { "exim_group", opt_gid, &exim_gid }, { "exim_path", opt_stringptr, &exim_path }, { "exim_user", opt_uid, &exim_uid }, @@ -199,6 +269,10 @@ static optionlist optionlist_config[] = { { "freeze_tell", opt_stringptr, &freeze_tell }, { "gecos_name", opt_stringptr, &gecos_name }, { "gecos_pattern", opt_stringptr, &gecos_pattern }, +#ifdef SUPPORT_TLS + { "gnutls_allow_auto_pkcs11", opt_bool, &gnutls_allow_auto_pkcs11 }, + { "gnutls_compat_mode", opt_bool, &gnutls_compat_mode }, +#endif { "header_line_maxsize", opt_int, &header_line_maxsize }, { "header_maxsize", opt_int, &header_maxsize }, { "headers_charset", opt_stringptr, &headers_charset }, @@ -212,6 +286,9 @@ static optionlist optionlist_config[] = { { "host_lookup_order", opt_stringptr, &host_lookup_order }, { "host_reject_connection", opt_stringptr, &host_reject_connection }, { "hosts_connection_nolog", opt_stringptr, &hosts_connection_nolog }, +#ifdef SUPPORT_PROXY + { "hosts_proxy", opt_stringptr, &hosts_proxy }, +#endif { "hosts_treat_as_local", opt_stringptr, &hosts_treat_as_local }, #ifdef LOOKUP_IBASE { "ibase_servers", opt_stringptr, &ibase_servers }, @@ -219,9 +296,17 @@ static optionlist optionlist_config[] = { { "ignore_bounce_errors_after", opt_time, &ignore_bounce_errors_after }, { "ignore_fromline_hosts", opt_stringptr, &ignore_fromline_hosts }, { "ignore_fromline_local", opt_bool, &ignore_fromline_local }, + { "keep_environment", opt_stringptr, &keep_environment }, { "keep_malformed", opt_time, &keep_malformed }, #ifdef LOOKUP_LDAP + { "ldap_ca_cert_dir", opt_stringptr, &eldap_ca_cert_dir }, + { "ldap_ca_cert_file", opt_stringptr, &eldap_ca_cert_file }, + { "ldap_cert_file", opt_stringptr, &eldap_cert_file }, + { "ldap_cert_key", opt_stringptr, &eldap_cert_key }, + { "ldap_cipher_suite", opt_stringptr, &eldap_cipher_suite }, { "ldap_default_servers", opt_stringptr, &eldap_default_servers }, + { "ldap_require_cert", opt_stringptr, &eldap_require_cert }, + { "ldap_start_tls", opt_bool, &eldap_start_tls }, { "ldap_version", opt_int, &eldap_version }, #endif { "local_from_check", opt_bool, &local_from_check }, @@ -236,6 +321,7 @@ static optionlist optionlist_config[] = { { "log_timezone", opt_bool, &log_timezone }, { "lookup_open_max", opt_int, &lookup_open_max }, { "max_username_length", opt_int, &max_username_length }, + { "message_body_newlines", opt_bool, &message_body_newlines }, { "message_body_visible", opt_mkint, &message_body_visible }, { "message_id_header_domain", opt_stringptr, &message_id_domain }, { "message_id_header_text", opt_stringptr, &message_id_text }, @@ -249,6 +335,9 @@ static optionlist optionlist_config[] = { { "mysql_servers", opt_stringptr, &mysql_servers }, #endif { "never_users", opt_uidlist, &never_users }, +#ifdef SUPPORT_TLS + { "openssl_options", opt_stringptr, &openssl_options }, +#endif #ifdef LOOKUP_ORACLE { "oracle_servers", opt_stringptr, &oracle_servers }, #endif @@ -256,12 +345,16 @@ static optionlist optionlist_config[] = { #ifdef EXIM_PERL { "perl_at_start", opt_bool, &opt_perl_at_start }, { "perl_startup", opt_stringptr, &opt_perl_startup }, + { "perl_taintmode", opt_bool, &opt_perl_taintmode }, #endif #ifdef LOOKUP_PGSQL { "pgsql_servers", opt_stringptr, &pgsql_servers }, #endif { "pid_file_path", opt_stringptr, &pid_file_path }, { "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts }, +#ifndef DISABLE_PRDR + { "prdr_enable", opt_bool, &prdr_enable }, +#endif { "preserve_message_logs", opt_bool, &preserve_message_logs }, { "primary_hostname", opt_stringptr, &primary_hostname }, { "print_topbitchars", opt_bool, &print_topbitchars }, @@ -274,9 +367,10 @@ static optionlist optionlist_config[] = { { "queue_only", opt_bool, &queue_only }, { "queue_only_file", opt_stringptr, &queue_only_file }, { "queue_only_load", opt_fixed, &queue_only_load }, + { "queue_only_load_latch", opt_bool, &queue_only_load_latch }, { "queue_only_override", opt_bool, &queue_only_override }, { "queue_run_in_order", opt_bool, &queue_run_in_order }, - { "queue_run_max", opt_int, &queue_run_max }, + { "queue_run_max", opt_stringptr, &queue_run_max }, { "queue_smtp_domains", opt_stringptr, &queue_smtp_domains }, { "receive_timeout", opt_time, &receive_timeout }, { "received_header_text", opt_stringptr, &received_header_text }, @@ -284,6 +378,9 @@ static optionlist optionlist_config[] = { { "recipient_unqualified_hosts", opt_stringptr, &recipient_unqualified_hosts }, { "recipients_max", opt_int, &recipients_max }, { "recipients_max_reject", opt_bool, &recipients_max_reject }, +#ifdef LOOKUP_REDIS + { "redis_servers", opt_stringptr, &redis_servers }, +#endif { "remote_max_parallel", opt_int, &remote_max_parallel }, { "remote_sort_domains", opt_stringptr, &remote_sort_domains }, { "retry_data_expire", opt_time, &retry_data_expire }, @@ -293,6 +390,7 @@ static optionlist optionlist_config[] = { { "rfc1413_hosts", opt_stringptr, &rfc1413_hosts }, { "rfc1413_query_timeout", opt_time, &rfc1413_query_timeout }, { "sender_unqualified_hosts", opt_stringptr, &sender_unqualified_hosts }, + { "slow_lookup_log", opt_int, &slow_lookup_log }, { "smtp_accept_keepalive", opt_bool, &smtp_accept_keepalive }, { "smtp_accept_max", opt_int, &smtp_accept_max }, { "smtp_accept_max_nonmail", opt_int, &smtp_accept_max_nonmail }, @@ -315,11 +413,33 @@ static optionlist optionlist_config[] = { { "smtp_ratelimit_hosts", opt_stringptr, &smtp_ratelimit_hosts }, { "smtp_ratelimit_mail", opt_stringptr, &smtp_ratelimit_mail }, { "smtp_ratelimit_rcpt", opt_stringptr, &smtp_ratelimit_rcpt }, - { "smtp_receive_timeout", opt_time, &smtp_receive_timeout }, + { "smtp_receive_timeout", opt_func, &fn_smtp_receive_timeout }, { "smtp_reserve_hosts", opt_stringptr, &smtp_reserve_hosts }, { "smtp_return_error_details",opt_bool, &smtp_return_error_details }, +#ifdef SUPPORT_I18N + { "smtputf8_advertise_hosts", opt_stringptr, &smtputf8_advertise_hosts }, +#endif +#ifdef WITH_CONTENT_SCAN + { "spamd_address", opt_stringptr, &spamd_address }, +#endif +#ifdef EXPERIMENTAL_SPF + { "spf_guess", opt_stringptr, &spf_guess }, +#endif { "split_spool_directory", opt_bool, &split_spool_directory }, { "spool_directory", opt_stringptr, &spool_directory }, +#ifdef LOOKUP_SQLITE + { "sqlite_lock_timeout", opt_int, &sqlite_lock_timeout }, +#endif +#ifdef EXPERIMENTAL_SRS + { "srs_config", opt_stringptr, &srs_config }, + { "srs_hashlength", opt_int, &srs_hashlength }, + { "srs_hashmin", opt_int, &srs_hashmin }, + { "srs_maxage", opt_int, &srs_maxage }, + { "srs_secrets", opt_stringptr, &srs_secrets }, + { "srs_usehash", opt_bool, &srs_usehash }, + { "srs_usetimestamp", opt_bool, &srs_usetimestamp }, +#endif + { "strict_acl_vars", opt_bool, &strict_acl_vars }, { "strip_excess_angle_brackets", opt_bool, &strip_excess_angle_brackets }, { "strip_trailing_dot", opt_bool, &strip_trailing_dot }, { "syslog_duplication", opt_bool, &syslog_duplication }, @@ -334,14 +454,22 @@ static optionlist optionlist_config[] = { { "system_filter_reply_transport",opt_stringptr,&system_filter_reply_transport }, { "system_filter_user", opt_uid, &system_filter_uid }, { "tcp_nodelay", opt_bool, &tcp_nodelay }, +#ifdef USE_TCP_WRAPPERS + { "tcp_wrappers_daemon_name", opt_stringptr, &tcp_wrappers_daemon_name }, +#endif { "timeout_frozen_after", opt_time, &timeout_frozen_after }, { "timezone", opt_stringptr, &timezone_string }, -#ifdef SUPPORT_TLS { "tls_advertise_hosts", opt_stringptr, &tls_advertise_hosts }, +#ifdef SUPPORT_TLS { "tls_certificate", opt_stringptr, &tls_certificate }, { "tls_crl", opt_stringptr, &tls_crl }, + { "tls_dh_max_bits", opt_int, &tls_dh_max_bits }, { "tls_dhparam", opt_stringptr, &tls_dhparam }, - { "tls_on_connect_ports", opt_stringptr, &tls_on_connect_ports }, + { "tls_eccurve", opt_stringptr, &tls_eccurve }, +# ifndef DISABLE_OCSP + { "tls_ocsp_file", opt_stringptr, &tls_ocsp_file }, +# endif + { "tls_on_connect_ports", opt_stringptr, &tls_in.on_connect_ports }, { "tls_privatekey", opt_stringptr, &tls_privatekey }, { "tls_remember_esmtp", opt_bool, &tls_remember_esmtp }, { "tls_require_ciphers", opt_stringptr, &tls_require_ciphers }, @@ -392,7 +520,7 @@ for (i = 0; i < optionlist_config_size; i++) for (r = routers; r != NULL; r = r->next) { router_info *ri = r->info; - for (i = 0; i < ri->options_count[0]; i++) + for (i = 0; i < *ri->options_count; i++) { if ((ri->options[i].type & opt_mask) != opt_stringptr) continue; if (p == (char *)(r->options_block) + (long int)(ri->options[i].value)) @@ -403,11 +531,16 @@ for (r = routers; r != NULL; r = r->next) for (t = transports; t != NULL; t = t->next) { transport_info *ti = t->info; - for (i = 0; i < ti->options_count[0]; i++) + for (i = 0; i < *ti->options_count; i++) { - if ((ti->options[i].type & opt_mask) != opt_stringptr) continue; - if (p == (char *)(t->options_block) + (long int)(ti->options[i].value)) - return US ti->options[i].name; + optionlist * op = &ti->options[i]; + if ((op->type & opt_mask) != opt_stringptr) continue; + if (p == ( op->type & opt_public + ? (char *)t + : (char *)t->options_block + ) + + (long int)op->value) + return US op->name; } } @@ -417,6 +550,122 @@ return US""; +/************************************************* +* Deal with an assignment to a macro * +*************************************************/ + +/* This function is called when a line that starts with an upper case letter is +encountered. The argument "line" should contain a complete logical line, and +start with the first letter of the macro name. The macro name and the +replacement text are extracted and stored. Redefinition of existing, +non-command line, macros is permitted using '==' instead of '='. + +Arguments: + s points to the start of the logical line + +Returns: nothing +*/ + +static void +read_macro_assignment(uschar *s) +{ +uschar name[64]; +int namelen = 0; +BOOL redef = FALSE; +macro_item *m; +macro_item *mlast = NULL; + +while (isalnum(*s) || *s == '_') + { + if (namelen >= sizeof(name) - 1) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, + "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1); + name[namelen++] = *s++; + } +name[namelen] = 0; + +while (isspace(*s)) s++; +if (*s++ != '=') + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "malformed macro definition"); + +if (*s == '=') + { + redef = TRUE; + s++; + } +while (isspace(*s)) s++; + +/* If an existing macro of the same name was defined on the command line, we +just skip this definition. It's an error to attempt to redefine a macro without +redef set to TRUE, or to redefine a macro when it hasn't been defined earlier. +It is also an error to define a macro whose name begins with the name of a +previously defined macro. Note: it is documented that the other way round +works. */ + +for (m = macros; m != NULL; m = m->next) + { + int len = Ustrlen(m->name); + + if (Ustrcmp(m->name, name) == 0) + { + if (!m->command_line && !redef) + log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "macro \"%s\" is already " + "defined (use \"==\" if you want to redefine it", name); + break; + } + + if (len < namelen && Ustrstr(name, m->name) != NULL) + log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as " + "a macro because previously defined macro \"%s\" is a substring", + name, m->name); + + /* We cannot have this test, because it is documented that a substring + macro is permitted (there is even an example). + * + * if (len > namelen && Ustrstr(m->name, name) != NULL) + * log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as " + * "a macro because it is a substring of previously defined macro \"%s\"", + * name, m->name); + */ + + mlast = m; + } + +/* Check for an overriding command-line definition. */ + +if (m != NULL && m->command_line) return; + +/* Redefinition must refer to an existing macro. */ + +if (redef) + { + if (m == NULL) + log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "can't redefine an undefined macro " + "\"%s\"", name); + } + +/* We have a new definition. The macro_item structure includes a final vector +called "name" which is one byte long. Thus, adding "namelen" gives us enough +room to store the "name" string. */ + +else + { + m = store_get(sizeof(macro_item) + namelen); + if (macros == NULL) macros = m; else mlast->next = m; + Ustrncpy(m->name, name, namelen); + m->name[namelen] = 0; + m->next = NULL; + m->command_line = FALSE; + } + +/* Set the value of the new or redefined macro */ + +m->replacement = string_copy(s); +} + + + + /************************************************* * Read configuration line * @@ -459,11 +708,13 @@ for (;;) { if (config_file_stack != NULL) /* EOF inside .include */ { - fclose(config_file); + (void)fclose(config_file); config_file = config_file_stack->file; config_filename = config_file_stack->filename; config_lineno = config_file_stack->lineno; config_file_stack = config_file_stack->next; + if (config_lines) + save_config_position(config_filename, config_lineno); continue; } @@ -481,6 +732,9 @@ for (;;) config_lineno++; newlen = len + Ustrlen(big_buffer + len); + if (config_lines && config_lineno == 1) + save_config_position(config_filename, config_lineno); + /* Handle pathologically long physical lines - yes, it did happen - by extending big_buffer at this point. The code also copes with very long logical lines. */ @@ -663,8 +917,14 @@ for (;;) } *t = 0; + if (*ss != '/') + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, ".include specifies a non-" + "absolute path \"%s\"", ss); + if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue; + if (config_lines) + save_config_position(config_filename, config_lineno); save = store_get(sizeof(config_file_item)); save->next = config_file_stack; config_file_stack = save; @@ -672,10 +932,10 @@ for (;;) save->filename = config_filename; save->lineno = config_lineno; - config_file = Ufopen(ss, "rb"); - if (config_file == NULL) + if (!(config_file = Ufopen(ss, "rb"))) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to open included " "configuration file %s", ss); + config_filename = string_copy(ss); config_lineno = 0; continue; @@ -721,6 +981,10 @@ next_section, truncate it. It will be unrecognized later, because all the known section names do fit. Leave space for pluralizing. */ s = big_buffer + startoffset; /* First non-space character */ + +if (config_lines) + save_config_line(s); + if (strncmpic(s, US"begin ", 6) == 0) { s += 6; @@ -799,7 +1063,7 @@ Returns: the time value, or -1 on syntax error */ int -readconf_readtime(uschar *s, int terminator, BOOL return_msec) +readconf_readtime(const uschar *s, int terminator, BOOL return_msec) { int yield = 0; for (;;) @@ -808,7 +1072,7 @@ for (;;) double fraction; if (!isdigit(*s)) return -1; - (void)sscanf(CS s, "%d%n", &value, &count); + (void)sscanf(CCS s, "%d%n", &value, &count); s += count; switch (*s) @@ -822,7 +1086,7 @@ for (;;) case '.': if (!return_msec) return -1; - (void)sscanf(CS s, "%lf%n", &fraction, &count); + (void)sscanf(CCS s, "%lf%n", &fraction, &count); s += count; if (*s++ != 's') return -1; yield += (int)(fraction * 1000.0); @@ -854,7 +1118,7 @@ Returns: the value, or -1 on error */ static int -readconf_readfixed(uschar *s, int terminator) +readconf_readfixed(const uschar *s, int terminator) { int yield = 0; int value, count; @@ -960,7 +1224,7 @@ Returns: doesn't return; dies */ static void -extra_chars_error(uschar *s, uschar *t1, uschar *t2, uschar *t3) +extra_chars_error(const uschar *s, const uschar *t1, const uschar *t2, const uschar *t3) { uschar *comment = US""; if (*s == '#') comment = US" (# is comment only at line start)"; @@ -996,7 +1260,7 @@ Returns: the control block for the parsed rule. */ static rewrite_rule * -readconf_one_rewrite(uschar *p, int *existflags, BOOL isglobal) +readconf_one_rewrite(const uschar *p, int *existflags, BOOL isglobal) { rewrite_rule *next = store_get(sizeof(rewrite_rule)); @@ -1102,10 +1366,10 @@ Returns: pointer to the string */ static uschar * -read_string(uschar *s, uschar *name) +read_string(const uschar *s, const uschar *name) { uschar *yield; -uschar *ss; +const uschar *ss; if (*s != '\"') return string_copy(s); @@ -1122,6 +1386,24 @@ return yield; } +/************************************************* +* Custom-handler options * +*************************************************/ +static void +fn_smtp_receive_timeout(const uschar * name, const uschar * str) +{ +if (*str == '$') + smtp_receive_timeout_s = string_copy(str); +else + { + /* "smtp_receive_timeout", opt_time, &smtp_receive_timeout */ + smtp_receive_timeout = readconf_readtime(str, 0, FALSE); + if (smtp_receive_timeout < 0) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "invalid time value for %s", + name); + } +} + /************************************************* * Handle option line * *************************************************/ @@ -1180,6 +1462,8 @@ int intbase = 0; uschar *inttype = US""; uschar *sptr; uschar *s = buffer; +uschar *saved_condition, *strtemp; +uschar **str_target; uschar name[64]; uschar name2[64]; @@ -1233,13 +1517,9 @@ if (ol == NULL) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, CS unknown_txt, name); } -if ((ol->type & opt_set) != 0) - { - uschar *mname = name; - if (Ustrncmp(mname, "no_", 3) == 0) mname += 3; +if ((ol->type & opt_set) && !(ol->type & (opt_rep_con | opt_rep_str))) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, - "\"%s\" option set for the second time", mname); - } + "\"%s\" option set for the second time", name); ol->type |= opt_set | issecure; type = ol->type & opt_mask; @@ -1260,7 +1540,7 @@ if (type < opt_bool || type > opt_bool_last) } /* If a boolean wasn't preceded by "no[t]_" it can be followed by = and -true/false/yes/no, or, in the case of opt_expanded_bool, a general string that +true/false/yes/no, or, in the case of opt_expand_bool, a general string that ultimately expands to one of those values. */ else if (*s != 0 && (offset != 0 || *s != '=')) @@ -1319,6 +1599,53 @@ switch (type) control block and flags word. */ case opt_stringptr: + if (data_block == NULL) + str_target = (uschar **)(ol->value); + else + str_target = (uschar **)((uschar *)data_block + (long int)(ol->value)); + if (ol->type & opt_rep_con) + { + /* We already have a condition, we're conducting a crude hack to let + multiple condition rules be chained together, despite storing them in + text form. */ + saved_condition = *str_target; + strtemp = string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}", + saved_condition, sptr); + *str_target = string_copy_malloc(strtemp); + /* TODO(pdp): there is a memory leak here and just below + when we set 3 or more conditions; I still don't + understand the store mechanism enough to know + what's the safe way to free content from an earlier store. + AFAICT, stores stack, so freeing an early stored item also stores + all data alloc'd after it. If we knew conditions were adjacent, + we could survive that, but we don't. So I *think* we need to take + another bit from opt_type to indicate "malloced"; this seems like + quite a hack, especially for this one case. It also means that + we can't ever reclaim the store from the *first* condition. + + Because we only do this once, near process start-up, I'm prepared to + let this slide for the time being, even though it rankles. */ + } + else if (ol->type & opt_rep_str) + { + uschar sep_o = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':'; + int sep_i = -(int)sep_o; + const uschar * list = sptr; + uschar * s; + uschar * list_o = *str_target; + + while ((s = string_nextinlist(&list, &sep_i, NULL, 0))) + list_o = string_append_listele(list_o, sep_o, s); + if (list_o) + *str_target = string_copy_malloc(list_o); + } + else + { + *str_target = sptr; + freesptr = FALSE; + } + break; + case opt_rewrite: if (data_block == NULL) *((uschar **)(ol->value)) = sptr; @@ -1353,8 +1680,7 @@ switch (type) flagptr = (int *)((uschar *)data_block + (long int)(ol3->value)); } - while ((p = string_nextinlist(&sptr, &sep, big_buffer, BIG_BUFFER_SIZE)) - != NULL) + while ((p = string_nextinlist(CUSS &sptr, &sep, big_buffer, BIG_BUFFER_SIZE))) { rewrite_rule *next = readconf_one_rewrite(p, flagptr, FALSE); *chain = next; @@ -1478,10 +1804,16 @@ switch (type) int count = 1; uid_t *list; int ptr = 0; - uschar *p = sptr; + const uschar *p; + const uschar *op = expand_string (sptr); + if (op == NULL) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s", + name, expand_string_message); + + p = op; if (*p != 0) count++; - while (*p != 0) if (*p++ == ':') count++; + while (*p != 0) if (*p++ == ':' && *p != 0) count++; list = store_malloc(count*sizeof(uid_t)); list[ptr++] = (uid_t)(count - 1); @@ -1490,7 +1822,7 @@ switch (type) else *((uid_t **)((uschar *)data_block + (long int)(ol->value))) = list; - p = sptr; + p = op; while (count-- > 1) { int sep = 0; @@ -1513,10 +1845,16 @@ switch (type) int count = 1; gid_t *list; int ptr = 0; - uschar *p = sptr; + const uschar *p; + const uschar *op = expand_string (sptr); + if (op == NULL) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "failed to expand %s: %s", + name, expand_string_message); + + p = op; if (*p != 0) count++; - while (*p != 0) if (*p++ == ':') count++; + while (*p != 0) if (*p++ == ':' && *p != 0) count++; list = store_malloc(count*sizeof(gid_t)); list[ptr++] = (gid_t)(count - 1); @@ -1525,7 +1863,7 @@ switch (type) else *((gid_t **)((uschar *)data_block + (long int)(ol->value))) = list; - p = sptr; + p = op; while (count-- > 1) { int sep = 0; @@ -1647,42 +1985,52 @@ switch (type) inttype = US"octal "; /* Integer: a simple(ish) case; allow octal and hex formats, and - suffixes K and M. The different types affect output, not input. */ + suffixes K, M and G. The different types affect output, not input. */ case opt_mkint: case opt_int: { uschar *endptr; + long int lvalue; + errno = 0; - value = strtol(CS s, CSS &endptr, intbase); + lvalue = strtol(CS s, CSS &endptr, intbase); if (endptr == s) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%sinteger expected for %s", inttype, name); if (errno != ERANGE) - { if (tolower(*endptr) == 'k') { - if (value > INT_MAX/1024 || value < INT_MIN/1024) errno = ERANGE; - else value *= 1024; + if (lvalue > INT_MAX/1024 || lvalue < INT_MIN/1024) errno = ERANGE; + else lvalue *= 1024; endptr++; } else if (tolower(*endptr) == 'm') { - if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024)) + if (lvalue > INT_MAX/(1024*1024) || lvalue < INT_MIN/(1024*1024)) errno = ERANGE; - else value *= 1024*1024; + else lvalue *= 1024*1024; + endptr++; + } + else if (tolower(*endptr) == 'g') + { + if (lvalue > INT_MAX/(1024*1024*1024) || lvalue < INT_MIN/(1024*1024*1024)) + errno = ERANGE; + else lvalue *= 1024*1024*1024; endptr++; } - } - if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, - "absolute value of integer \"%s\" is too large (overflow)", s); + if (errno == ERANGE || lvalue > INT_MAX || lvalue < INT_MIN) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, + "absolute value of integer \"%s\" is too large (overflow)", s); while (isspace(*endptr)) endptr++; if (*endptr != 0) extra_chars_error(endptr, inttype, US"integer value for ", name); + + value = (int)lvalue; } if (data_block == NULL) @@ -1691,8 +2039,9 @@ switch (type) *((int *)((uschar *)data_block + (long int)(ol->value))) = value; break; - /* Integer held in K: again, allow octal and hex formats, and suffixes K and - M. */ + /* Integer held in K: again, allow octal and hex formats, and suffixes K, M + and G. */ + /*XXX consider moving to int_eximarith_t (but mind the overflow test 0415) */ case opt_Kint: { @@ -1705,22 +2054,26 @@ switch (type) inttype, name); if (errno != ERANGE) - { - if (tolower(*endptr) == 'm') + if (tolower(*endptr) == 'g') { - if (value > INT_MAX/1024 || value < INT_MIN/1024) errno = ERANGE; - else value *= 1024; + if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024)) + errno = ERANGE; + else + value *= 1024*1024; endptr++; } - else if (tolower(*endptr) == 'k') + else if (tolower(*endptr) == 'm') { + if (value > INT_MAX/1024 || value < INT_MIN/1024) + errno = ERANGE; + else + value *= 1024; endptr++; } + else if (tolower(*endptr) == 'k') + endptr++; else - { value = (value + 512)/1024; - } - } if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "absolute value of integer \"%s\" is too large (overflow)", s); @@ -1824,9 +2177,15 @@ switch (type) name); if (count > 0 && list[2] == 0) count = 0; list[1] = count; + break; } - break; + case opt_func: + { + void (*fn)() = ol->value; + fn(name, s); + break; + } } return TRUE; @@ -1850,6 +2209,12 @@ readconf_printtime(int t) int s, m, h, d, w; uschar *p = time_buffer; +if (t < 0) + { + *p++ = '-'; + t = -t; + } + s = t % 60; t /= 60; m = t % 60; @@ -1889,13 +2254,14 @@ Arguments: resides. oltop points to the option list in which ol exists last one more than the offset of the last entry in optop + no_labels do not show "foo = " at the start. Returns: nothing */ static void print_ol(optionlist *ol, uschar *name, void *options_block, - optionlist *oltop, int last) + optionlist *oltop, int last, BOOL no_labels) { struct passwd *pw; struct group *gr; @@ -1917,7 +2283,10 @@ if (ol == NULL) if (!admin_user && (ol->type & opt_secure) != 0) { - printf("%s = \n", name); + if (no_labels) + printf("%s\n", hidden); + else + printf("%s = %s\n", name, hidden); return; } @@ -1936,11 +2305,13 @@ switch(ol->type & opt_mask) case opt_stringptr: case opt_rewrite: /* Show the text value */ s = *((uschar **)value); - printf("%s = %s\n", name, (s == NULL)? US"" : string_printing2(s, FALSE)); + if (!no_labels) printf("%s = ", name); + printf("%s\n", (s == NULL)? US"" : string_printing2(s, FALSE)); break; case opt_int: - printf("%s = %d\n", name, *((int *)value)); + if (!no_labels) printf("%s = ", name); + printf("%d\n", *((int *)value)); break; case opt_mkint: @@ -1955,23 +2326,30 @@ switch(ol->type & opt_mask) c = 'M'; x >>= 10; } - printf("%s = %d%c\n", name, x, c); + if (!no_labels) printf("%s = ", name); + printf("%d%c\n", x, c); + } + else + { + if (!no_labels) printf("%s = ", name); + printf("%d\n", x); } - else printf("%s = %d\n", name, x); } break; case opt_Kint: { int x = *((int *)value); - if (x == 0) printf("%s = 0\n", name); - else if ((x & 1023) == 0) printf("%s = %dM\n", name, x >> 10); - else printf("%s = %dK\n", name, x); + if (!no_labels) printf("%s = ", name); + if (x == 0) printf("0\n"); + else if ((x & 1023) == 0) printf("%dM\n", x >> 10); + else printf("%dK\n", x); } break; case opt_octint: - printf("%s = %#o\n", name, *((int *)value)); + if (!no_labels) printf("%s = ", name); + printf("%#o\n", *((int *)value)); break; /* Can be negative only when "unset", in which case integer */ @@ -1983,7 +2361,8 @@ switch(ol->type & opt_mask) int d = 100; if (x < 0) printf("%s =\n", name); else { - printf("%s = %d.", name, x/1000); + if (!no_labels) printf("%s = ", name); + printf("%d.", x/1000); do { printf("%d", f/d); @@ -2009,7 +2388,8 @@ switch(ol->type & opt_mask) if (options_block != NULL) value2 = (void *)((uschar *)options_block + (long int)value2); s = *((uschar **)value2); - printf("%s = %s\n", name, (s == NULL)? US"" : string_printing(s)); + if (!no_labels) printf("%s = ", name); + printf("%s\n", (s == NULL)? US"" : string_printing(s)); break; } } @@ -2017,14 +2397,15 @@ switch(ol->type & opt_mask) /* Else fall through */ case opt_uid: + if (!no_labels) printf("%s = ", name); if (! *get_set_flag(name, oltop, last, options_block)) - printf("%s =\n", name); + printf("\n"); else { pw = getpwuid(*((uid_t *)value)); if (pw == NULL) - printf("%s = %ld\n", name, (long int)(*((uid_t *)value))); - else printf("%s = %s\n", name, pw->pw_name); + printf("%ld\n", (long int)(*((uid_t *)value))); + else printf("%s\n", pw->pw_name); } break; @@ -2041,7 +2422,8 @@ switch(ol->type & opt_mask) if (options_block != NULL) value2 = (void *)((uschar *)options_block + (long int)value2); s = *((uschar **)value2); - printf("%s = %s\n", name, (s == NULL)? US"" : string_printing(s)); + if (!no_labels) printf("%s = ", name); + printf("%s\n", (s == NULL)? US"" : string_printing(s)); break; } } @@ -2049,31 +2431,34 @@ switch(ol->type & opt_mask) /* Else fall through */ case opt_gid: + if (!no_labels) printf("%s = ", name); if (! *get_set_flag(name, oltop, last, options_block)) - printf("%s =\n", name); + printf("\n"); else { gr = getgrgid(*((int *)value)); if (gr == NULL) - printf("%s = %ld\n", name, (long int)(*((int *)value))); - else printf("%s = %s\n", name, gr->gr_name); + printf("%ld\n", (long int)(*((int *)value))); + else printf("%s\n", gr->gr_name); } break; case opt_uidlist: uidlist = *((uid_t **)value); - printf("%s =", name); + if (!no_labels) printf("%s =", name); if (uidlist != NULL) { int i; uschar sep = ' '; + if (no_labels) sep = '\0'; for (i = 1; i <= (int)(uidlist[0]); i++) { uschar *name = NULL; pw = getpwuid(uidlist[i]); if (pw != NULL) name = US pw->pw_name; - if (name != NULL) printf("%c%s", sep, name); - else printf("%c%ld", sep, (long int)(uidlist[i])); + if (sep != '\0') printf("%c", sep); + if (name != NULL) printf("%s", name); + else printf("%ld", (long int)(uidlist[i])); sep = ':'; } } @@ -2082,18 +2467,20 @@ switch(ol->type & opt_mask) case opt_gidlist: gidlist = *((gid_t **)value); - printf("%s =", name); + if (!no_labels) printf("%s =", name); if (gidlist != NULL) { int i; uschar sep = ' '; + if (no_labels) sep = '\0'; for (i = 1; i <= (int)(gidlist[0]); i++) { uschar *name = NULL; gr = getgrgid(gidlist[i]); if (gr != NULL) name = US gr->gr_name; - if (name != NULL) printf("%c%s", sep, name); - else printf("%c%ld", sep, (long int)(gidlist[i])); + if (sep != '\0') printf("%c", sep); + if (name != NULL) printf("%s", name); + else printf("%ld", (long int)(gidlist[i])); sep = ':'; } } @@ -2101,14 +2488,15 @@ switch(ol->type & opt_mask) break; case opt_time: - printf("%s = %s\n", name, readconf_printtime(*((int *)value))); + if (!no_labels) printf("%s = ", name); + printf("%s\n", readconf_printtime(*((int *)value))); break; case opt_timelist: { int i; int *list = (int *)value; - printf("%s = ", name); + if (!no_labels) printf("%s = ", name); for (i = 0; i < list[1]; i++) printf("%s%s", (i == 0)? "" : ":", readconf_printtime(list[i+2])); printf("\n"); @@ -2131,7 +2519,8 @@ switch(ol->type & opt_mask) s = *((uschar **)value2); if (s != NULL) { - printf("%s = %s\n", name, string_printing(s)); + if (!no_labels) printf("%s = ", name); + printf("%s\n", string_printing(s)); break; } /* s == NULL => string not set; fall through */ @@ -2158,34 +2547,42 @@ causes the value of any main configuration variable to be output if the second argument is NULL. There are some special values: all print all main configuration options - configure_file print the name of the configuration file + config_file print the name of the configuration file + (configure_file will still work, for backward + compatibility) routers print the routers' configurations transports print the transports' configuration authenticators print the authenticators' configuration + macros print the macros' configuration router_list print a list of router names transport_list print a list of transport names authenticator_list print a list of authentication mechanism names + macro_list print a list of macro names +name print a named list item local_scan print the local_scan options + config print the configuration as it is parsed + environment print the used execution environment -If the second argument is not NULL, it must be one of "router", "transport", or -"authenticator" in which case the first argument identifies the driver whose -options are to be printed. +If the second argument is not NULL, it must be one of "router", "transport", +"authenticator" or "macro" in which case the first argument identifies the +driver whose options are to be printed. Arguments: name option name if type == NULL; else driver name type NULL or driver type name, as described above + no_labels avoid the "foo = " at the start of an item Returns: nothing */ void -readconf_print(uschar *name, uschar *type) +readconf_print(uschar *name, uschar *type, BOOL no_labels) { BOOL names_only = FALSE; optionlist *ol; optionlist *ol2 = NULL; driver_instance *d = NULL; +macro_item *m; int size = 0; if (type == NULL) @@ -2206,8 +2603,11 @@ if (type == NULL) if (t != NULL) { found = TRUE; - printf("%slist %s = %s\n", types[i], name+1, - ((namedlist_block *)(t->data.ptr))->string); + if (no_labels) + printf("%s\n", ((namedlist_block *)(t->data.ptr))->string); + else + printf("%slist %s = %s\n", types[i], name+1, + ((namedlist_block *)(t->data.ptr))->string); } } @@ -2218,7 +2618,8 @@ if (type == NULL) return; } - if (Ustrcmp(name, "configure_file") == 0) + if ( Ustrcmp(name, "configure_file") == 0 + ||Ustrcmp(name, "config_file") == 0) { printf("%s\n", CS config_main_filename); return; @@ -2230,7 +2631,9 @@ if (type == NULL) ol < optionlist_config + optionlist_config_size; ol++) { if ((ol->type & opt_hidden) == 0) - print_ol(ol, US ol->name, NULL, optionlist_config, optionlist_config_size); + print_ol(ol, US ol->name, NULL, + optionlist_config, optionlist_config_size, + no_labels); } return; } @@ -2244,12 +2647,18 @@ if (type == NULL) ol < local_scan_options + local_scan_options_count; ol++) { print_ol(ol, US ol->name, NULL, local_scan_options, - local_scan_options_count); + local_scan_options_count, no_labels); } #endif return; } + if (Ustrcmp(name, "config") == 0) + { + print_config(admin_user, no_labels); + return; + } + if (Ustrcmp(name, "routers") == 0) { type = US"router"; @@ -2267,11 +2676,10 @@ if (type == NULL) name = NULL; } - else if (Ustrcmp(name, "authenticator_list") == 0) + else if (Ustrcmp(name, "macros") == 0) { - type = US"authenticator"; + type = US"macro"; name = NULL; - names_only = TRUE; } else if (Ustrcmp(name, "router_list") == 0) @@ -2280,16 +2688,50 @@ if (type == NULL) name = NULL; names_only = TRUE; } + else if (Ustrcmp(name, "transport_list") == 0) { type = US"transport"; name = NULL; names_only = TRUE; } + + else if (Ustrcmp(name, "authenticator_list") == 0) + { + type = US"authenticator"; + name = NULL; + names_only = TRUE; + } + + else if (Ustrcmp(name, "macro_list") == 0) + { + type = US"macro"; + name = NULL; + names_only = TRUE; + } + + else if (Ustrcmp(name, "environment") == 0) + { + if (environ) + { + uschar ** p; + for (p = USS environ; *p; p++) ; + qsort(environ, p - USS environ, sizeof(*p), string_compare_by_pointer); + + for (p = USS environ; *p; p++) + { + uschar * q; + if (no_labels && (q = Ustrchr(*p, '='))) *q = '\0'; + puts(CS *p); + } + } + return; + } + else { print_ol(find_option(name, optionlist_config, optionlist_config_size), - name, NULL, optionlist_config, optionlist_config_size); + name, NULL, optionlist_config, optionlist_config_size, no_labels); return; } } @@ -2319,6 +2761,32 @@ else if (Ustrcmp(type, "authenticator") == 0) size = optionlist_auths_size; } +else if (Ustrcmp(type, "macro") == 0) + { + /* People store passwords in macros and they were previously not available + for printing. So we have an admin_users restriction. */ + if (!admin_user) + { + fprintf(stderr, "exim: permission denied\n"); + exit(EXIT_FAILURE); + } + for (m = macros; m != NULL; m = m->next) + { + if (name == NULL || Ustrcmp(name, m->name) == 0) + { + if (names_only) + printf("%s\n", CS m->name); + else + printf("%s=%s\n", CS m->name, CS m->replacement); + if (name != NULL) + return; + } + } + if (name != NULL) + printf("%s %s not found\n", type, name); + return; + } + if (names_only) { for (; d != NULL; d = d->next) printf("%s\n", CS d->name); @@ -2336,14 +2804,14 @@ for (; d != NULL; d = d->next) for (ol = ol2; ol < ol2 + size; ol++) { if ((ol->type & opt_hidden) == 0) - print_ol(ol, US ol->name, d, ol2, size); + print_ol(ol, US ol->name, d, ol2, size, no_labels); } for (ol = d->info->options; ol < d->info->options + *(d->info->options_count); ol++) { if ((ol->type & opt_hidden) == 0) - print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count)); + print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count), no_labels); } if (name != NULL) return; } @@ -2467,6 +2935,311 @@ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malformed ratelimit data: %s", s); +/************************************************* +* Drop privs for checking TLS config * +*************************************************/ + +/* We want to validate TLS options during readconf, but do not want to be +root when we call into the TLS library, in case of library linkage errors +which cause segfaults; before this check, those were always done as the Exim +runtime user and it makes sense to continue with that. + +Assumes: tls_require_ciphers has been set, if it will be + exim_user has been set, if it will be + exim_group has been set, if it will be + +Returns: bool for "okay"; false will cause caller to immediately exit. +*/ + +#ifdef SUPPORT_TLS +static BOOL +tls_dropprivs_validate_require_cipher(BOOL nowarn) +{ +const uschar *errmsg; +pid_t pid; +int rc, status; +void (*oldsignal)(int); + +/* If TLS will never be used, no point checking ciphers */ + +if ( !tls_advertise_hosts + || !*tls_advertise_hosts + || Ustrcmp(tls_advertise_hosts, ":") == 0 + ) + return TRUE; +else if (!nowarn && !tls_certificate) + log_write(0, LOG_MAIN, + "Warning: No server certificate defined; will use a selfsigned one.\n" + " Suggested action: either install a certificate or change tls_advertise_hosts option"); + +oldsignal = signal(SIGCHLD, SIG_DFL); + +fflush(NULL); +if ((pid = fork()) < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "fork failed for TLS check"); + +if (pid == 0) + { + /* in some modes, will have dropped privilege already */ + if (!geteuid()) + exim_setugid(exim_uid, exim_gid, FALSE, + US"calling tls_validate_require_cipher"); + + errmsg = tls_validate_require_cipher(); + if (errmsg) + { + log_write(0, LOG_PANIC_DIE|LOG_CONFIG, + "tls_require_ciphers invalid: %s", errmsg); + } + fflush(NULL); + _exit(0); + } + +do { + rc = waitpid(pid, &status, 0); +} while (rc < 0 && errno == EINTR); + +DEBUG(D_tls) + debug_printf("tls_validate_require_cipher child %d ended: status=0x%x\n", + (int)pid, status); + +signal(SIGCHLD, oldsignal); + +return status == 0; +} +#endif /* SUPPORT_TLS */ + + + + +/*************************************************/ +/* Create compile-time feature macros */ +static void +readconf_features(void) +{ +/* Probably we could work out a static initialiser for wherever +macros are stored, but this will do for now. Some names are awkward +due to conflicts with other common macros. */ + +#ifdef SUPPORT_CRYPTEQ + read_macro_assignment("_HAVE_CRYPTEQ=y"); +#endif +#if HAVE_ICONV + read_macro_assignment("_HAVE_ICONV=y"); +#endif +#if HAVE_IPV6 + read_macro_assignment("_HAVE_IPV6=y"); +#endif +#ifdef HAVE_SETCLASSRESOURCES + read_macro_assignment("_HAVE_SETCLASSRESOURCES=y"); +#endif +#ifdef SUPPORT_PAM + read_macro_assignment("_HAVE_PAM=y"); +#endif +#ifdef EXIM_PERL + read_macro_assignment("_HAVE_PERL=y"); +#endif +#ifdef EXPAND_DLFUNC + read_macro_assignment("_HAVE_DLFUNC=y"); +#endif +#ifdef USE_TCP_WRAPPERS + read_macro_assignment("_HAVE_TCPWRAPPERS=y"); +#endif +#ifdef SUPPORT_TLS + read_macro_assignment("_HAVE_TLS=y"); +# ifdef USE_GNUTLS + read_macro_assignment("_HAVE_GNUTLS=y"); +# else + read_macro_assignment("_HAVE_OPENSSL=y"); +# endif +#endif +#ifdef SUPPORT_TRANSLATE_IP_ADDRESS + read_macro_assignment("_HAVE_TRANSLATE_IP_ADDRESS=y"); +#endif +#ifdef SUPPORT_MOVE_FROZEN_MESSAGES + read_macro_assignment("_HAVE_MOVE_FROZEN_MESSAGES=y"); +#endif +#ifdef WITH_CONTENT_SCAN + read_macro_assignment("_HAVE_CONTENT_SCANNING=y"); +#endif +#ifndef DISABLE_DKIM + read_macro_assignment("_HAVE_DKIM=y"); +#endif +#ifndef DISABLE_DNSSEC + read_macro_assignment("_HAVE_DNSSEC=y"); +#endif +#ifndef DISABLE_EVENT + read_macro_assignment("_HAVE_Event=y"); +#endif +#ifdef SUPPORT_I18N + read_macro_assignment("_HAVE_I18N=y"); +#endif +#ifndef DISABLE_OCSP + read_macro_assignment("_HAVE_OCSP=y"); +#endif +#ifndef DISABLE_PRDR + read_macro_assignment("_HAVE_PRDR=y"); +#endif +#ifdef SUPPORT_PROXY + read_macro_assignment("_HAVE_PROXY=y"); +#endif +#ifdef SUPPORT_SOCKS + read_macro_assignment("_HAVE_SOCKS=y"); +#endif +#ifdef EXPERIMENTAL_LMDB + read_macro_assignment("_HAVE_LMDB=y"); +#endif +#ifdef EXPERIMENTAL_SPF + read_macro_assignment("_HAVE_SPF=y"); +#endif +#ifdef EXPERIMENTAL_SRS + read_macro_assignment("_HAVE_SRS=y"); +#endif +#ifdef EXPERIMENTAL_BRIGHTMAIL + read_macro_assignment("_HAVE_BRIGHTMAIL=y"); +#endif +#ifdef EXPERIMENTAL_DANE + read_macro_assignment("_HAVE_DANE=y"); +#endif +#ifdef EXPERIMENTAL_DCC + read_macro_assignment("_HAVE_DCC=y"); +#endif +#ifdef EXPERIMENTAL_DMARC + read_macro_assignment("_HAVE_DMARC=y"); +#endif +#ifdef EXPERIMENTAL_DSN_INFO + read_macro_assignment("_HAVE_DSN_INFO=y"); +#endif + +#ifdef LOOKUP_LSEARCH + read_macro_assignment("_HAVE_LKUP_LSEARCH=y"); +#endif +#ifdef LOOKUP_CDB + read_macro_assignment("_HAVE_LKUP_CDB=y"); +#endif +#ifdef LOOKUP_DBM + read_macro_assignment("_HAVE_LKUP_DBM=y"); +#endif +#ifdef LOOKUP_DNSDB + read_macro_assignment("_HAVE_LKUP_DNSDB=y"); +#endif +#ifdef LOOKUP_DSEARCH + read_macro_assignment("_HAVE_LKUP_DSEARCH=y"); +#endif +#ifdef LOOKUP_IBASE + read_macro_assignment("_HAVE_LKUP_IBASE=y"); +#endif +#ifdef LOOKUP_LDAP + read_macro_assignment("_HAVE_LKUP_LDAP=y"); +#endif +#ifdef EXPERIMENTAL_LMDB + read_macro_assignment("_HAVE_LKUP_LMDB=y"); +#endif +#ifdef LOOKUP_MYSQL + read_macro_assignment("_HAVE_LKUP_MYSQL=y"); +#endif +#ifdef LOOKUP_NIS + read_macro_assignment("_HAVE_LKUP_NIS=y"); +#endif +#ifdef LOOKUP_NISPLUS + read_macro_assignment("_HAVE_LKUP_NISPLUS=y"); +#endif +#ifdef LOOKUP_ORACLE + read_macro_assignment("_HAVE_LKUP_ORACLE=y"); +#endif +#ifdef LOOKUP_PASSWD + read_macro_assignment("_HAVE_LKUP_PASSWD=y"); +#endif +#ifdef LOOKUP_PGSQL + read_macro_assignment("_HAVE_LKUP_PGSQL=y"); +#endif +#ifdef LOOKUP_REDIS + read_macro_assignment("_HAVE_LKUP_REDIS=y"); +#endif +#ifdef LOOKUP_SQLITE + read_macro_assignment("_HAVE_LKUP_SQLITE=y"); +#endif +#ifdef LOOKUP_TESTDB + read_macro_assignment("_HAVE_LKUP_TESTDB=y"); +#endif +#ifdef LOOKUP_WHOSON + read_macro_assignment("_HAVE_LKUP_WHOSON=y"); +#endif + +#ifdef AUTH_CRAM_MD5 + read_macro_assignment("_HAVE_AUTH_CRAM_MD5=y"); +#endif +#ifdef AUTH_CYRUS_SASL + read_macro_assignment("_HAVE_AUTH_CYRUS_SASL=y"); +#endif +#ifdef AUTH_DOVECOT + read_macro_assignment("_HAVE_AUTH_DOVECOT=y"); +#endif +#ifdef AUTH_GSASL + read_macro_assignment("_HAVE_AUTH_GSASL=y"); +#endif +#ifdef AUTH_HEIMDAL_GSSAPI + read_macro_assignment("_HAVE_AUTH_HEIMDAL_GSSAPI=y"); +#endif +#ifdef AUTH_PLAINTEXT + read_macro_assignment("_HAVE_AUTH_PLAINTEXT=y"); +#endif +#ifdef AUTH_SPA + read_macro_assignment("_HAVE_AUTH_SPA=y"); +#endif +#ifdef AUTH_TLS + read_macro_assignment("_HAVE_AUTH_TLS=y"); +#endif + +#ifdef ROUTER_ACCEPT + read_macro_assignment("_HAVE_RTR_ACCEPT=y"); +#endif +#ifdef ROUTER_DNSLOOKUP + read_macro_assignment("_HAVE_RTR_DNSLOOKUP=y"); +#endif +#ifdef ROUTER_IPLITERAL + read_macro_assignment("_HAVE_RTR_IPLITERAL=y"); +#endif +#ifdef ROUTER_IPLOOKUP + read_macro_assignment("_HAVE_RTR_IPLOOKUP=y"); +#endif +#ifdef ROUTER_MANUALROUTE + read_macro_assignment("_HAVE_RTR_MANUALROUTE=y"); +#endif +#ifdef ROUTER_QUERYPROGRAM + read_macro_assignment("_HAVE_RTR_QUERYPROGRAM=y"); +#endif +#ifdef ROUTER_REDIRECT + read_macro_assignment("_HAVE_RTR_REDRCT=y"); +#endif + +#ifdef TRANSPORT_APPENDFILE + read_macro_assignment("_HAVE_TPT_APPENDFILE=y"); +# ifdef SUPPORT_MAILDIR + read_macro_assignment("_HAVE_TPT_APPEND_MAILDR=y"); +# endif +# ifdef SUPPORT_MAILSTORE + read_macro_assignment("_HAVE_TPT_APPEND_MAILSTORE=y"); +# endif +# ifdef SUPPORT_MBX + read_macro_assignment("_HAVE_TPT_APPEND_MBX=y"); +# endif +#endif +#ifdef TRANSPORT_AUTOREPLY + read_macro_assignment("_HAVE_TPT_AUTOREPLY=y"); +#endif +#ifdef TRANSPORT_LMTP + read_macro_assignment("_HAVE_TPT_LMTP=y"); +#endif +#ifdef TRANSPORT_PIPE + read_macro_assignment("_HAVE_TPT_PIPE=y"); +#endif +#ifdef TRANSPORT_SMTP + read_macro_assignment("_HAVE_TPT_SMTP=y"); +#endif +} + + /************************************************* * Read main configuration options * *************************************************/ @@ -2496,18 +3269,22 @@ systems. Therefore they are available only when requested by compile-time options. */ void -readconf_main(void) +readconf_main(BOOL nowarn) { int sep = 0; struct stat statbuf; uschar *s, *filename; -uschar *list = config_main_filelist; +const uschar *list = config_main_filelist; + +/* First create compile-time feature macros */ +readconf_features(); /* Loop through the possible file names */ while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)) != NULL) { + /* Cut out all the fancy processing unless specifically wanted */ #if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID) @@ -2561,6 +3338,15 @@ while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)) if (config_file != NULL || errno != ENOENT) break; } +/* Now, once we found and opened our configuration file, we change the directory +to a safe place. Later we change to $spool_directory. */ + +if (Uchdir("/") < 0) + { + perror("exim: chdir `/': "); + exit(EXIT_FAILURE); + } + /* On success, save the name for verification; config_filename is used when logging configuration errors (it changes for .included files) whereas config_main_filename is the name shown by -bP. Failure to open a configuration @@ -2568,7 +3354,12 @@ file is a serious disaster. */ if (config_file != NULL) { + uschar *p; config_filename = config_main_filename = string_copy(filename); + + p = Ustrrchr(filename, '/'); + config_main_directory = p ? string_copyn(filename, p - filename) + : string_copy(US"."); } else { @@ -2580,25 +3371,24 @@ else "configuration file %s", filename)); } -/* Check the status of the file we have opened, unless it was specified on -the command line, in which case privilege was given away at the start. */ +/* Check the status of the file we have opened, if we have retained root +privileges and the file isn't /dev/null (which *should* be 0666). */ -if (!config_changed) +if (trusted_config && Ustrcmp(filename, US"/dev/null")) { if (fstat(fileno(config_file), &statbuf) != 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to stat configuration file %s", big_buffer); - if ((statbuf.st_uid != root_uid && /* owner not root */ - statbuf.st_uid != exim_uid /* owner not exim */ + if ((statbuf.st_uid != root_uid /* owner not root */ #ifdef CONFIGURE_OWNER && statbuf.st_uid != config_uid /* owner not the special one */ #endif ) || /* or */ - (statbuf.st_gid != exim_gid /* group not exim & */ + (statbuf.st_gid != root_gid /* group not root & */ #ifdef CONFIGURE_GROUP && statbuf.st_gid != config_gid /* group not the special one */ - #endif + #endif && (statbuf.st_mode & 020) != 0) || /* group writeable */ /* or */ ((statbuf.st_mode & 2) != 0)) /* world writeable */ @@ -2613,65 +3403,7 @@ a macro definition. */ while ((s = get_config_line()) != NULL) { - if (isupper(s[0])) - { - macro_item *m; - macro_item *mlast = NULL; - uschar name[64]; - int namelen = 0; - - while (isalnum(*s) || *s == '_') - { - if (namelen >= sizeof(name) - 1) - log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, - "macro name too long (maximum is %d characters)", sizeof(name) - 1); - name[namelen++] = *s++; - } - name[namelen] = 0; - while (isspace(*s)) s++; - if (*s++ != '=') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, - "malformed macro definition"); - while (isspace(*s)) s++; - - /* If an existing macro of the same name was defined on the command line, - we just skip this definition. Otherwise it's an error to attempt to - redefine a macro. It is also an error to define a macro whose name begins - with the name of a previously-defined macro. */ - - for (m = macros; m != NULL; m = m->next) - { - int len = Ustrlen(m->name); - - if (Ustrcmp(m->name, name) == 0) - { - if (m->command_line) break; - log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "macro \"%s\" is already " - "defined", name); - } - - if (len < namelen && Ustrstr(name, m->name) != NULL) - log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as " - "a macro because previously defined macro \"%s\" is a substring", - name, m->name); - - mlast = m; - } - if (m != NULL) continue; /* Found an overriding command-line definition */ - - m = store_get(sizeof(macro_item) + namelen); - m->next = NULL; - m->command_line = FALSE; - if (mlast == NULL) macros = m; else mlast->next = m; - - /* This use of strcpy() is OK because we have obtained a block of store - whose size is based on the length of "name". The definition of the - macro_item structure includes a final vector called "name" which is one - byte long. Thus, adding "namelen" gives us enough room to store the "name" - string. */ - - Ustrcpy(m->name, name); - m->replacement = string_copy(s); - } + if (isupper(s[0])) read_macro_assignment(s); else if (Ustrncmp(s, "domainlist", 10) == 0) read_named_list(&domainlist_anchor, &domainlist_count, @@ -2706,10 +3438,19 @@ wanted. */ if (timezone_string != NULL && *timezone_string == 0) timezone_string = NULL; +/* The max retry interval must not be greater than 24 hours. */ + +if (retry_interval_max > 24*60*60) retry_interval_max = 24*60*60; + /* remote_max_parallel must be > 0 */ if (remote_max_parallel <= 0) remote_max_parallel = 1; +/* Save the configured setting of freeze_tell, so we can re-instate it at the +start of a new SMTP message. */ + +freeze_tell_config = freeze_tell; + /* The primary host name may be required for expansion of spool_directory and log_file_path, so make sure it is set asap. It is obtained from uname(), but if that yields an unqualified value, make a FQDN by using gethostbyname to @@ -2718,7 +3459,7 @@ don't force the case. */ if (primary_hostname == NULL) { - uschar *hostname; + const uschar *hostname; struct utsname uts; if (uname(&uts) < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "uname() failed to yield host name"); @@ -2730,9 +3471,9 @@ if (primary_hostname == NULL) struct hostent *hostdata; #if HAVE_IPV6 - if (dns_ipv4_lookup == NULL || - match_isinlist(hostname, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN, - TRUE, NULL) != OK) + if (!disable_ipv6 && (dns_ipv4_lookup == NULL || + match_isinlist(hostname, CUSS &dns_ipv4_lookup, 0, NULL, NULL, + MCL_DOMAIN, TRUE, NULL) != OK)) af = AF_INET6; #else af = AF_INET; @@ -2787,12 +3528,12 @@ if (s == NULL) spool_directory = s; /* Expand log_file_path, which must contain "%s" in any component that isn't -the null string or "syslog". It is also allowed to contain one instance of %D. -However, it must NOT contain % followed by anything else. */ +the null string or "syslog". It is also allowed to contain one instance of %D +or %M. However, it must NOT contain % followed by anything else. */ if (*log_file_path != 0) { - uschar *ss, *sss; + const uschar *ss, *sss; int sep = ':'; /* Fixed for log file path */ s = expand_string(log_file_path); if (s == NULL) @@ -2812,7 +3553,7 @@ if (*log_file_path != 0) t = Ustrchr(sss, '%'); if (t != NULL) { - if (t[1] != 'D' || Ustrchr(t+2, '%') != NULL) + if ((t[1] != 'D' && t[1] != 'M') || Ustrchr(t+2, '%') != NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "log_file_path \"%s\" contains " "unexpected \"%%\" character", s); } @@ -2861,6 +3602,11 @@ if (*pid_file_path != 0) pid_file_path = s; } +/* Set default value of process_log_path */ + +if (process_log_path == NULL || *process_log_path =='\0') + process_log_path = string_sprintf("%s/exim-process.info", spool_directory); + /* Compile the regex for matching a UUCP-style "From_" line in an incoming message. */ @@ -2934,9 +3680,14 @@ so as to ensure that everything else is set up before the expansion. */ if (host_number_string != NULL) { + long int n; uschar *end; uschar *s = expand_string(host_number_string); - long int n = Ustrtol(s, &end, 0); + if (s == NULL) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "failed to expand localhost_number \"%s\": %s", + host_number_string, expand_string_message); + n = Ustrtol(s, &end, 0); while (isspace(*end)) end++; if (*end != 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, @@ -2956,7 +3707,38 @@ if ((tls_verify_hosts != NULL || tls_try_verify_hosts != NULL) && log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "tls_%sverify_hosts is set, but tls_verify_certificates is not set", (tls_verify_hosts != NULL)? "" : "try_"); -#endif + +/* This also checks that the library linkage is working and we can call +routines in it, so call even if tls_require_ciphers is unset */ +if (!tls_dropprivs_validate_require_cipher(nowarn)) + exit(1); + +/* Magic number: at time of writing, 1024 has been the long-standing value +used by so many clients, and what Exim used to use always, that it makes +sense to just min-clamp this max-clamp at that. */ +if (tls_dh_max_bits < 1024) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG, + "tls_dh_max_bits is too small, must be at least 1024 for interop"); + +/* If openssl_options is set, validate it */ +if (openssl_options != NULL) + { +# ifdef USE_GNUTLS + log_write(0, LOG_PANIC_DIE|LOG_CONFIG, + "openssl_options is set but we're using GnuTLS"); +# else + long dummy; + if (!(tls_openssl_options_parse(openssl_options, &dummy))) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG, + "openssl_options parse error: %s", openssl_options); +# endif + } +#endif /*SUPPORT_TLS*/ + +if (!nowarn && !keep_environment && environ && *environ) + log_write(0, LOG_MAIN, + "Warning: purging the environment.\n" + " Suggested action: use keep_environment."); } @@ -3053,16 +3835,33 @@ driver_instance **p = anchor; driver_instance *d = NULL; uschar *buffer; -/* Now process the configuration lines */ - while ((buffer = get_config_line()) != NULL) { uschar name[64]; + uschar *s; - /* Read the first name on the line and test for the start of a new driver. - If this isn't the start of a new driver, the line will be re-read. */ + /* Read the first name on the line and test for the start of a new driver. A + macro definition indicates the end of the previous driver. If this isn't the + start of a new driver, the line will be re-read. */ - uschar *s = readconf_readname(name, sizeof(name), buffer); + s = readconf_readname(name, sizeof(name), buffer); + + /* Handle macro definition, first finishing off the initialization of the + previous driver, if any. */ + + if (isupper(*name) && *s == '=') + { + if (d) + { + if (!d->driver_name) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG, + "no driver defined for %s \"%s\"", class, d->name); + (d->info->init)(d); + d = NULL; + } + read_macro_assignment(buffer); + continue; + } /* If the line starts with a name terminated by a colon, we are at the start of the definition of a new driver. The rest of the line must be @@ -3074,9 +3873,9 @@ while ((buffer = get_config_line()) != NULL) /* Finish off initializing the previous driver. */ - if (d != NULL) + if (d) { - if (d->driver_name == NULL) + if (!d->driver_name) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "no driver defined for %s \"%s\"", class, d->name); (d->info->init)(d); @@ -3084,7 +3883,7 @@ while ((buffer = get_config_line()) != NULL) /* Check that we haven't already got a driver of this name */ - for (d = *anchor; d != NULL; d = d->next) + for (d = *anchor; d; d = d->next) if (Ustrcmp(name, d->name) == 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "there are two %ss called \"%s\"", class, name); @@ -3095,7 +3894,7 @@ while ((buffer = get_config_line()) != NULL) d = store_get(instance_size); memcpy(d, instance_default, instance_size); *p = d; - p = &(d->next); + p = &d->next; d->name = string_copy(name); /* Clear out the "set" bits in the generic options */ @@ -3110,10 +3909,11 @@ while ((buffer = get_config_line()) != NULL) continue; } - /* Give an error if we have not set up a current driver yet. */ + /* Not the start of a new driver. Give an error if we have not set up a + current driver yet. */ - if (d == NULL) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, - "%s name missing", class); + if (!d) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%s name missing", class); /* First look to see if this is a generic option; if it is "driver", initialize the driver. If is it not a generic option, we can look for a @@ -3122,7 +3922,7 @@ while ((buffer = get_config_line()) != NULL) if (readconf_handle_option(buffer, driver_optionlist, driver_optionlist_count, d, NULL)) { - if (d->info == NULL && d->driver_name != NULL) + if (!d->info && d->driver_name) init_driver(d, drivers_available, size_of_info, class); } @@ -3130,11 +3930,9 @@ while ((buffer = get_config_line()) != NULL) live therein. A flag with each option indicates if it is in the public block. */ - else if (d->info != NULL) - { + else if (d->info) readconf_handle_option(buffer, d->info->options, *(d->info->options_count), d, US"option \"%s\" unknown"); - } /* The option is not generic and the driver name has not yet been given. */ @@ -3144,9 +3942,9 @@ while ((buffer = get_config_line()) != NULL) /* Run the initialization function for the final driver. */ -if (d != NULL) +if (d) { - if (d->driver_name == NULL) + if (!d->driver_name) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "no driver defined for %s \"%s\"", class, d->name); (d->info->init)(d); @@ -3220,10 +4018,11 @@ Returns: NULL if decoded correctly; else points to error text */ uschar * -readconf_retry_error(uschar *pp, uschar *p, int *basic_errno, int *more_errno) +readconf_retry_error(const uschar *pp, const uschar *p, + int *basic_errno, int *more_errno) { int len; -uschar *q = pp; +const uschar *q = pp; while (q < p && *q != '_') q++; len = q - pp; @@ -3252,7 +4051,7 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0) { int i; int xlen = p - q - 1; - uschar *x = q + 1; + const uschar *x = q + 1; static uschar *extras[] = { US"A", US"MX", US"connect", US"connect_A", US"connect_MX" }; @@ -3260,28 +4059,25 @@ else if (len == 7 && strncmpic(pp, US"timeout", len) == 0) { 'A', 'M', RTEF_CTOUT, RTEF_CTOUT|'A', RTEF_CTOUT|'M' }; for (i = 0; i < sizeof(extras)/sizeof(uschar *); i++) - { if (strncmpic(x, extras[i], xlen) == 0) { *more_errno = values[i]; break; } - } if (i >= sizeof(extras)/sizeof(uschar *)) - { if (strncmpic(x, US"DNS", xlen) == 0) - { log_write(0, LOG_MAIN|LOG_PANIC, "\"timeout_dns\" is no longer " "available in retry rules (it has never worked) - treated as " "\"timeout\""); - } - else return US"\"A\", \"MX\", or \"connect\" expected after \"timeout\""; - } + else + return US"\"A\", \"MX\", or \"connect\" expected after \"timeout\""; } } -else if (strncmpic(pp, US"rcpt_4", 6) == 0) +else if (strncmpic(pp, US"mail_4", 6) == 0 || + strncmpic(pp, US"rcpt_4", 6) == 0 || + strncmpic(pp, US"data_4", 6) == 0) { BOOL bad = FALSE; int x = 255; /* means "any 4xx code" */ @@ -3298,21 +4094,30 @@ else if (strncmpic(pp, US"rcpt_4", 6) == 0) else if (a != 'x' || b != 'x') bad = TRUE; } - if (bad) return US"rcpt_4 must be followed by xx, dx, or dd, where " - "x is literal and d is any digit"; + if (bad) + return string_sprintf("%.4s_4 must be followed by xx, dx, or dd, where " + "x is literal and d is any digit", pp); - *basic_errno = ERRNO_RCPT4XX; + *basic_errno = *pp == 'm' ? ERRNO_MAIL4XX : + *pp == 'r' ? ERRNO_RCPT4XX : ERRNO_DATA4XX; *more_errno = x << 8; } else if (len == 4 && strncmpic(pp, US"auth", len) == 0 && strncmpic(q+1, US"failed", p-q-1) == 0) - { *basic_errno = ERRNO_AUTHFAIL; - } + +else if (strncmpic(pp, US"lost_connection", p - pp) == 0) + *basic_errno = ERRNO_SMTPCLOSED; + +else if (strncmpic(pp, US"tls_required", p - pp) == 0) + *basic_errno = ERRNO_TLSREQUIRED; + +else if (strncmpic(pp, US"lookup", p - pp) == 0) + *basic_errno = ERRNO_UNKNOWNHOST; else if (len != 1 || Ustrncmp(pp, "*", 1) != 0) - return string_sprintf("unknown or malformed retry error \"%.*s\"", p-pp, pp); + return string_sprintf("unknown or malformed retry error \"%.*s\"", (int) (p-pp), pp); return NULL; } @@ -3348,10 +4153,10 @@ Returns: time in seconds or fixed point number * 1000 */ static int -retry_arg(uschar **paddr, int type) +retry_arg(const uschar **paddr, int type) { -uschar *p = *paddr; -uschar *pp; +const uschar *p = *paddr; +const uschar *pp; if (*p++ != ',') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "comma expected"); @@ -3365,10 +4170,8 @@ if (*p != 0 && !isspace(*p) && *p != ',' && *p != ';') *paddr = p; switch (type) { - case 0: - return readconf_readtime(pp, *p, FALSE); - case 1: - return readconf_readfixed(pp, *p); + case 0: return readconf_readtime(pp, *p, FALSE); + case 1: return readconf_readfixed(pp, *p); } return 0; /* Keep picky compilers happy */ } @@ -3380,12 +4183,13 @@ readconf_retries(void) { retry_config **chain = &retries; retry_config *next; -uschar *p; +const uschar *p; -while ((p = get_config_line()) != NULL) +while ((p = get_config_line())) { retry_rule **rchain; - uschar *pp, *error; + const uschar *pp; + uschar *error; next = store_get(sizeof(retry_config)); next->next = NULL; @@ -3401,12 +4205,12 @@ while ((p = get_config_line()) != NULL) pp = p; while (mac_isgraph(*p)) p++; if (p - pp <= 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, - "missing error type"); + "missing error type in retry rule"); /* Test error names for things we understand. */ - if ((error = readconf_retry_error(pp, p, &(next->basic_errno), - &(next->more_errno))) != NULL) + if ((error = readconf_retry_error(pp, p, &next->basic_errno, + &next->more_errno))) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%s", error); /* There may be an optional address list of senders to be used as another @@ -3443,21 +4247,22 @@ while ((p = get_config_line()) != NULL) switch (rule->rule) { case 'F': /* Fixed interval */ - rule->p1 = retry_arg(&p, 0); - break; + rule->p1 = retry_arg(&p, 0); + break; case 'G': /* Geometrically increasing intervals */ - rule->p1 = retry_arg(&p, 0); - rule->p2 = retry_arg(&p, 1); - break; + case 'H': /* Ditto, but with randomness */ + rule->p1 = retry_arg(&p, 0); + rule->p2 = retry_arg(&p, 1); + break; default: - log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "unknown retry rule letter"); - break; + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "unknown retry rule letter"); + break; } if (rule->timeout <= 0 || rule->p1 <= 0 || - (rule->rule == 'G' && rule->p2 < 1000)) + (rule->rule != 'F' && rule->p2 < 1000)) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "bad parameters for retry rule"); @@ -3498,22 +4303,19 @@ readconf_driver_init(US"authenticator", optionlist_auths, /* generic options */ optionlist_auths_size); -for (au = auths; au != NULL; au = au->next) +for (au = auths; au; au = au->next) { - if (au->public_name == NULL) + if (!au->public_name) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "no public name specified for " "the %s authenticator", au->name); - for (bu = au->next; bu != NULL; bu = bu->next) - { + + for (bu = au->next; bu; bu = bu->next) if (strcmpic(au->public_name, bu->public_name) == 0) - { if ((au->client && bu->client) || (au->server && bu->server)) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "two %s authenticators " "(%s and %s) have the same public name (%s)", - (au->client)? US"client" : US"server", au->name, bu->name, + au->client ? US"client" : US"server", au->name, bu->name, au->public_name); - } - } } } @@ -3543,28 +4345,17 @@ return acl_line; /* Now the main function: -Arguments: - skip TRUE when this Exim process is doing something that will - not need the ACL data - +Arguments: none Returns: nothing */ static void -readconf_acl(BOOL skip) +readconf_acl(void) { uschar *p; -/* Not receiving messages, don't need to parse the ACL data */ - -if (skip) - { - DEBUG(D_acl) debug_printf("skipping ACL configuration - not needed\n"); - while ((p = get_config_line()) != NULL); - return; - } - -/* Read each ACL and add it into the tree */ +/* Read each ACL and add it into the tree. Macro (re)definitions are allowed +between ACLs. */ acl_line = get_config_line(); @@ -3575,8 +4366,15 @@ while(acl_line != NULL) uschar *error; p = readconf_readname(name, sizeof(name), acl_line); - if (*p != ':') - log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "missing ACL name"); + if (isupper(*name) && *p == '=') + { + read_macro_assignment(acl_line); + acl_line = get_config_line(); + continue; + } + + if (*p != ':' || name[0] == 0) + log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "missing or malformed ACL name"); node = store_get(sizeof(tree_node) + Ustrlen(name)); Ustrcpy(node->name, name); @@ -3640,9 +4438,7 @@ Because it may confuse people as to whether the names are singular or plural, we add "s" if it's missing. There is always enough room in next_section for this. This function is basically just a switch. -Arguments: - skip_acl TRUE if ACL information is not needed - +Arguments: none Returns: nothing */ @@ -3656,7 +4452,7 @@ static uschar *section_list[] = { US"transports"}; void -readconf_rest(BOOL skip_acl) +readconf_rest(void) { int had = 0; @@ -3689,7 +4485,7 @@ while(next_section[0] != 0) switch(mid) { - case 0: readconf_acl(skip_acl); break; + case 0: readconf_acl(); break; case 1: auths_init(); break; case 2: local_scan_init(); break; case 3: readconf_retries(); break; @@ -3699,7 +4495,125 @@ while(next_section[0] != 0) } } -fclose(config_file); +(void)fclose(config_file); +} + +/* Init the storage for the pre-parsed config lines */ +void +readconf_save_config(const uschar *s) +{ + save_config_line(string_sprintf("# Exim Configuration (%s)", + running_in_test_harness ? US"X" : s)); +} + +static void +save_config_position(const uschar *file, int line) +{ + save_config_line(string_sprintf("# %d \"%s\"", line, file)); +} + +/* Append a pre-parsed logical line to the config lines store, +this operates on a global (static) list that holds all the pre-parsed +config lines, we do no further processing here, output formatting and +honouring of or macros will be done during output */ +static void +save_config_line(const uschar* line) +{ +static config_line_item *current; +config_line_item *next; + +next = (config_line_item*) store_get(sizeof(config_line_item)); +next->line = string_copy(line); +next->next = NULL; + +if (!config_lines) config_lines = next; +else current->next = next; + +current = next; +} + +/* List the parsed config lines, care about nice formatting and +hide the values unless we're the admin user */ +void +print_config(BOOL admin, BOOL terse) +{ +config_line_item *i; +const int TS = terse ? 0 : 2; +int indent = 0; + +for (i = config_lines; i; i = i->next) + { + uschar *current; + uschar *p; + + /* skip over to the first non-space */ + for (current = i->line; *current && isspace(*current); ++current) + ; + + if (*current == '\0') + continue; + + /* Collapse runs of spaces. We stop this if we encounter one of the + * following characters: "'$, as this may indicate careful formatting */ + for (p = current; *p; ++p) + { + uschar *next; + if (!isspace(*p)) continue; + if (*p != ' ') *p = ' '; + + for (next = p; isspace(*next); ++next) + ; + + if (next - p > 1) + memmove(p+1, next, Ustrlen(next)+1); + + if (*next == '"' || *next == '\'' || *next == '$') + break; + } + + /* # lines */ + if (current[0] == '#') + puts(CCS current); + + /* begin lines are left aligned */ + else if (Ustrncmp(current, "begin", 5) == 0 && isspace(current[5])) + { + if (!terse) puts(""); + puts(CCS current); + indent = TS; + } + + /* router/acl/transport block names */ + else if (current[Ustrlen(current)-1] == ':' && !Ustrchr(current, '=')) + { + if (!terse) puts(""); + printf("%*s%s\n", TS, "", current); + indent = 2 * TS; + } + + /* hidden lines (all MACROS or lines prefixed with "hide") */ + else if ( !admin + && ( isupper(*current) + || Ustrncmp(current, "hide", 4) == 0 && isspace(current[4]) + ) + ) + { + if ((p = Ustrchr(current, '='))) + { + *p = '\0'; + printf("%*s%s= %s\n", indent, "", current, hidden); + } + /* e.g.: hide split_spool_directory */ + else + printf("%*s\n", indent, hidden); + } + + else + /* rest is public */ + printf("%*s%s\n", indent, "", current); + } } +/* vi: aw ai sw=2 +*/ /* End of readconf.c */