-/* $Cambridge: exim/src/src/readconf.c,v 1.4 2005/01/04 10:00:42 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* 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
#include "exim.h"
-#define CSTATE_STACK_SIZE 10
-
-
-/* Structure for chain (stack) of .included files */
-
-typedef struct config_file_item {
- struct config_file_item *next;
- uschar *filename;
- FILE *file;
- int lineno;
-} config_file_item;
-
-/* Structure of table of conditional words and their state transitions */
-
-typedef struct cond_item {
- uschar *name;
- int namelen;
- int action1;
- int action2;
- int pushpop;
-} cond_item;
-
-/* Structure of table of syslog facility names and values */
-
-typedef struct syslog_fac_item {
- uschar *name;
- int value;
-} syslog_fac_item;
-
-
-/* Static variables */
-
-static config_file_item *config_file_stack = NULL; /* For includes */
-
-static uschar *syslog_facility_str = NULL;
-static uschar next_section[24];
-static uschar time_buffer[24];
-
-/* State variables for conditional loading (.ifdef / .else / .endif) */
-
-static int cstate = 0;
-static int cstate_stack_ptr = -1;
-static int cstate_stack[CSTATE_STACK_SIZE];
-
-/* Table of state transitions for handling conditional inclusions. There are
-four possible state transitions:
-
- .ifdef true
- .ifdef false
- .elifdef true (or .else)
- .elifdef false
-
-.endif just causes the previous cstate to be popped off the stack */
-
-static int next_cstate[3][4] =
- {
- /* State 0: reading from file, or reading until next .else or .endif */
- { 0, 1, 2, 2 },
- /* State 1: condition failed, skipping until next .else or .endif */
- { 2, 2, 0, 1 },
- /* State 2: skipping until .endif */
- { 2, 2, 2, 2 },
- };
-
-/* Table of conditionals and the states to set. For each name, there are four
-values: the length of the name (to save computing it each time), the state to
-set if a macro was found in the line, the state to set if a macro was not found
-in the line, and a stack manipulation setting which is:
-
- -1 pull state value off the stack
- 0 don't alter the stack
- +1 push value onto stack, before setting new state
-*/
-
-static cond_item cond_list[] = {
- { US"ifdef", 5, 0, 1, 1 },
- { US"ifndef", 6, 1, 0, 1 },
- { US"elifdef", 7, 2, 3, 0 },
- { US"elifndef", 8, 3, 2, 0 },
- { US"else", 4, 2, 2, 0 },
- { US"endif", 5, 0, 0, -1 }
-};
-
-static int cond_list_size = sizeof(cond_list)/sizeof(cond_item);
-
-/* Table of syslog facility names and their values */
-
-static syslog_fac_item syslog_list[] = {
- { US"mail", LOG_MAIL },
- { US"user", LOG_USER },
- { US"news", LOG_NEWS },
- { US"uucp", LOG_UUCP },
- { US"local0", LOG_LOCAL0 },
- { US"local1", LOG_LOCAL1 },
- { US"local2", LOG_LOCAL2 },
- { US"local3", LOG_LOCAL3 },
- { US"local4", LOG_LOCAL4 },
- { US"local5", LOG_LOCAL5 },
- { US"local6", LOG_LOCAL6 },
- { US"local7", LOG_LOCAL7 },
- { US"daemon", LOG_DAEMON }
-};
-
-static int syslog_list_size = sizeof(syslog_list)/sizeof(syslog_fac_item);
-
-
+#ifdef MACRO_PREDEF
+# include "macro_predef.h"
+#endif
+static uschar * syslog_facility_str;
+static void fn_smtp_receive_timeout(const uschar *, const uschar *);
/*************************************************
* Main configuration options *
{ "*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 },
#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 },
{ "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 },
{ "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 },
{ "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 },
+ { "commandline_checks_require_admin", opt_bool,&commandline_checks_require_admin },
{ "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
+ { "debug_store", opt_bool, &debug_store },
{ "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 },
- /* This option is now a no-op, retained for compability */
+ { "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 compatibility */
{ "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 },
{ "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 },
{ "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 },
{ "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 },
{ "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 },
{ "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
#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 },
{ "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 },
{ "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 },
{ "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 },
{ "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 },
+ { "spool_wireformat", opt_bool, &spool_wireformat },
+#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 },
{ "syslog_facility", opt_stringptr, &syslog_facility_str },
+ { "syslog_pid", opt_bool, &syslog_pid },
{ "syslog_processname", opt_stringptr, &syslog_processname },
{ "syslog_timestamp", opt_bool, &syslog_timestamp },
{ "system_filter", opt_stringptr, &system_filter },
{ "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 },
{ "write_rejectlog", opt_bool, &write_rejectlog }
};
-static int optionlist_config_size =
- sizeof(optionlist_config)/sizeof(optionlist);
+#ifndef MACRO_PREDEF
+static int optionlist_config_size = nelem(optionlist_config);
+#endif
+
+
+#ifdef MACRO_PREDEF
+
+static void fn_smtp_receive_timeout(const uschar * name, const uschar * str) {/*Dummy*/}
+
+void
+options_main(void)
+{
+options_from_list(optionlist_config, nelem(optionlist_config), US"MAIN", NULL);
+}
+
+void
+options_auths(void)
+{
+struct auth_info * ai;
+uschar buf[64];
+
+options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL);
+
+for (ai = auths_available; ai->driver_name[0]; ai++)
+ {
+ spf(buf, sizeof(buf), US"_DRIVER_AUTHENTICATOR_%T", ai->driver_name);
+ builtin_macro_create(buf);
+ options_from_list(ai->options, (unsigned)*ai->options_count, US"AUTHENTICATOR", ai->driver_name);
+ }
+}
+
+
+#else /*!MACRO_PREDEF*/
+
+extern char **environ;
+
+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
+
+const uschar *config_directory = NULL;
+
+
+/* Structure for chain (stack) of .included files */
+
+typedef struct config_file_item {
+ struct config_file_item *next;
+ const uschar *filename;
+ const uschar *directory;
+ FILE *file;
+ 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 {
+ uschar *name;
+ int namelen;
+ int action1;
+ int action2;
+ int pushpop;
+} cond_item;
+
+/* Structure of table of syslog facility names and values */
+
+typedef struct syslog_fac_item {
+ uschar *name;
+ int value;
+} syslog_fac_item;
+
+/* constants */
+static const char * const hidden = "<value not displayable>";
+
+/* Static variables */
+
+static config_file_item *config_file_stack = NULL; /* For includes */
+
+static uschar *syslog_facility_str = NULL;
+static uschar next_section[24];
+static uschar time_buffer[24];
+
+/* State variables for conditional loading (.ifdef / .else / .endif) */
+
+static int cstate = 0;
+static int cstate_stack_ptr = -1;
+static int cstate_stack[CSTATE_STACK_SIZE];
+
+/* Table of state transitions for handling conditional inclusions. There are
+four possible state transitions:
+
+ .ifdef true
+ .ifdef false
+ .elifdef true (or .else)
+ .elifdef false
+
+.endif just causes the previous cstate to be popped off the stack */
+
+static int next_cstate[3][4] =
+ {
+ /* State 0: reading from file, or reading until next .else or .endif */
+ { 0, 1, 2, 2 },
+ /* State 1: condition failed, skipping until next .else or .endif */
+ { 2, 2, 0, 1 },
+ /* State 2: skipping until .endif */
+ { 2, 2, 2, 2 },
+ };
+
+/* Table of conditionals and the states to set. For each name, there are four
+values: the length of the name (to save computing it each time), the state to
+set if a macro was found in the line, the state to set if a macro was not found
+in the line, and a stack manipulation setting which is:
+
+ -1 pull state value off the stack
+ 0 don't alter the stack
+ +1 push value onto stack, before setting new state
+*/
+
+static cond_item cond_list[] = {
+ { US"ifdef", 5, 0, 1, 1 },
+ { US"ifndef", 6, 1, 0, 1 },
+ { US"elifdef", 7, 2, 3, 0 },
+ { US"elifndef", 8, 3, 2, 0 },
+ { US"else", 4, 2, 2, 0 },
+ { US"endif", 5, 0, 0, -1 }
+};
+
+static int cond_list_size = sizeof(cond_list)/sizeof(cond_item);
+
+/* Table of syslog facility names and their values */
+
+static syslog_fac_item syslog_list[] = {
+ { US"mail", LOG_MAIL },
+ { US"user", LOG_USER },
+ { US"news", LOG_NEWS },
+ { US"uucp", LOG_UUCP },
+ { US"local0", LOG_LOCAL0 },
+ { US"local1", LOG_LOCAL1 },
+ { US"local2", LOG_LOCAL2 },
+ { US"local3", LOG_LOCAL3 },
+ { US"local4", LOG_LOCAL4 },
+ { US"local5", LOG_LOCAL5 },
+ { US"local6", LOG_LOCAL6 },
+ { US"local7", LOG_LOCAL7 },
+ { US"daemon", LOG_DAEMON }
+};
+
+static int syslog_list_size = sizeof(syslog_list)/sizeof(syslog_fac_item);
+
router_instance *r;
transport_instance *t;
-for (i = 0; i < optionlist_config_size; i++)
+for (i = 0; i < nelem(optionlist_config); i++)
if (p == optionlist_config[i].value) return US optionlist_config[i].name;
-for (r = routers; r != NULL; r = r->next)
+for (r = routers; r; 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))
+ if (p == CS (r->options_block) + (long int)(ri->options[i].value))
return US ri->options[i].name;
}
}
-for (t = transports; t != NULL; t = t->next)
+for (t = transports; t; 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
+ ? CS t
+ : CS t->options_block
+ )
+ + (long int)op->value)
+ return US op->name;
}
}
+/*************************************************
+* Deal with an assignment to a macro *
+*************************************************/
+
+/* We have a new definition; append to the list.
+
+Args:
+ name Name of the macro. Must be in storage persistent past the call
+ val Expansion result for the macro. Ditto persistence.
+*/
+
+macro_item *
+macro_create(const uschar * name, const uschar * val, BOOL command_line)
+{
+macro_item * m = store_get(sizeof(macro_item));
+
+/* fprintf(stderr, "%s: '%s' '%s'\n", __FUNCTION__, name, val); */
+m->next = NULL;
+m->command_line = command_line;
+m->namelen = Ustrlen(name);
+m->replen = Ustrlen(val);
+m->name = name;
+m->replacement = val;
+mlast->next = m;
+mlast = m;
+return m;
+}
+
+
+/* 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;
+
+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. This is the requirement that make using a tree
+for macros hard; we must check all macros for the substring. Perhaps a
+sorted list, and a bsearch, would work?
+Note: it is documented that the other way round works. */
+
+for (m = macros; m; m = m->next)
+ {
+ 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 (m->namelen < 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 (m->namelen > 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);
+ */
+ }
+
+/* Check for an overriding command-line definition. */
+
+if (m && m->command_line) return;
+
+/* Redefinition must refer to an existing macro. */
+
+if (redef)
+ if (m)
+ {
+ m->replen = Ustrlen(s);
+ m->replacement = string_copy(s);
+ }
+ else
+ log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "can't redefine an undefined macro "
+ "\"%s\"", name);
+
+/* We have a new definition. */
+else
+ (void) macro_create(string_copy(name), string_copy(s), FALSE);
+}
+
+
+
+
/*************************************************
* Read configuration line *
{
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_directory = config_file_stack->directory;
config_lineno = config_file_stack->lineno;
config_file_stack = config_file_stack->next;
+ if (config_lines)
+ save_config_position(config_filename, config_lineno);
continue;
}
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. */
if (*s != '=') s = ss; /* Not a macro definition */
}
+ /* Skip leading chars which cannot start a macro name, to avoid multiple
+ pointless rescans in Ustrstr calls. */
+
+ while (*s && !isupper(*s) && *s != '_') s++;
+
/* For each defined macro, scan the line (from after XXX= if present),
replacing all occurrences of the macro. */
macro_found = FALSE;
- for (m = macros; m != NULL; m = m->next)
+ for (m = macros; m; m = m->next)
{
- uschar *p, *pp;
- uschar *t = s;
+ uschar * p, *pp;
+ uschar * t = s;
while ((p = Ustrstr(t, m->name)) != NULL)
{
int moveby;
- int namelen = Ustrlen(m->name);
- int replen = Ustrlen(m->replacement);
+/* fprintf(stderr, "%s: matched '%s' in '%s'\n", __FUNCTION__, m->name, ss); */
/* Expand the buffer if necessary */
- while (newlen - namelen + replen + 1 > big_buffer_size)
+ while (newlen - m->namelen + m->replen + 1 > big_buffer_size)
{
int newsize = big_buffer_size + BIG_BUFFER_SIZE;
uschar *newbuffer = store_malloc(newsize);
copying in the replacement text. Don't rescan the replacement for this
same macro. */
- pp = p + namelen;
- moveby = replen - namelen;
- if (moveby != 0)
+ pp = p + m->namelen;
+ if ((moveby = m->replen - m->namelen) != 0)
{
- memmove(p + replen, pp, (big_buffer + newlen) - pp + 1);
+ memmove(p + m->replen, pp, (big_buffer + newlen) - pp + 1);
newlen += moveby;
}
- Ustrncpy(p, m->replacement, replen);
- t = p + replen;
+ Ustrncpy(p, m->replacement, m->replen);
+ t = p + m->replen;
+ while (*t && !isupper(*t) && *t != '_') t++;
macro_found = TRUE;
}
}
}
*t = 0;
+ /* We allow relative file names. For security reasons currently
+ relative names not allowed with .include_if_exists. For .include_if_exists
+ we need to check the permissions/ownership of the containing folder */
+ if (*ss != '/')
+ if (include_if_exists) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, ".include specifies a non-"
+ "absolute path \"%s\"", ss);
+ else
+ {
+ int offset = 0;
+ int size = 0;
+ ss = string_append(NULL, &size, &offset, 3, config_directory, "/", ss);
+ ss[offset] = '\0'; /* string_append() does not zero terminate the string! */
+ }
+
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;
save->file = config_file;
save->filename = config_filename;
+ save->directory = config_directory;
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_directory = string_copyn(ss, CUstrrchr(ss, '/') - ss);
config_lineno = 0;
continue;
}
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;
*/
int
-readconf_readtime(uschar *s, int terminator, BOOL return_msec)
+readconf_readtime(const uschar *s, int terminator, BOOL return_msec)
{
int yield = 0;
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)
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);
*/
static int
-readconf_readfixed(uschar *s, int terminator)
+readconf_readfixed(const uschar *s, int terminator)
{
int yield = 0;
int value, count;
{
int middle = (first + last)/2;
int c = Ustrcmp(name, ol[middle].name);
+
if (c == 0) return ol + middle;
- else if (c > 0) first = middle + 1;
- else last = middle;
+ else if (c > 0) first = middle + 1;
+ else last = middle;
}
return NULL;
}
if (ol == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"Exim internal error: missing set flag for %s", name);
return (data_block == NULL)? (BOOL *)(ol->value) :
- (BOOL *)((uschar *)data_block + (long int)(ol->value));
+ (BOOL *)(US data_block + (long int)(ol->value));
}
*/
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)";
*/
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));
*/
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);
}
+/*************************************************
+* 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 *
*************************************************/
uschar *inttype = US"";
uschar *sptr;
uschar *s = buffer;
+uschar **str_target;
uschar name[64];
uschar name2[64];
/* Search the list for the given name. A non-existent name, or an option that
is set twice, is a disaster. */
-ol = find_option(name + offset, oltop, last);
-
-if (ol == NULL)
+if (!(ol = find_option(name + offset, oltop, last)))
{
if (unknown_txt == NULL) return FALSE;
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;
}
/* 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 != '='))
control block and flags word. */
case opt_stringptr:
+ str_target = data_block ? USS (US data_block + (long int)(ol->value))
+ : USS (ol->value);
+ if (ol->type & opt_rep_con)
+ {
+ uschar * saved_condition;
+ /* 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. */
+ *str_target = string_copy_malloc( (saved_condition = *str_target)
+ ? string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}",
+ saved_condition, sptr)
+ : sptr);
+ /* 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;
+ int size = 0, len = 0;
+
+ if (list_o)
+ size = (len = Ustrlen(list_o)) + 1;
+
+ while ((s = string_nextinlist(&list, &sep_i, NULL, 0)))
+ list_o = string_append_listele(list_o, &size, &len, 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;
+ if (data_block)
+ *USS (US data_block + (long int)(ol->value)) = sptr;
else
- *((uschar **)((uschar *)data_block + (long int)(ol->value))) = sptr;
+ *USS (ol->value) = sptr;
freesptr = FALSE;
if (type == opt_rewrite)
{
}
else
{
- chain = (rewrite_rule **)((uschar *)data_block + (long int)(ol2->value));
- flagptr = (int *)((uschar *)data_block + (long int)(ol3->value));
+ chain = (rewrite_rule **)(US data_block + (long int)(ol2->value));
+ flagptr = (int *)(US 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;
if (data_block == NULL)
*((uschar **)(ol2->value)) = ss;
else
- *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = ss;
+ *((uschar **)(US data_block + (long int)(ol2->value))) = ss;
if (ss != NULL)
{
if (data_block == NULL)
*((uid_t *)(ol->value)) = uid;
else
- *((uid_t *)((uschar *)data_block + (long int)(ol->value))) = uid;
+ *((uid_t *)(US data_block + (long int)(ol->value))) = uid;
/* Set the flag indicating a fixed value is set */
if (data_block == NULL)
*((gid_t *)(ol2->value)) = pw->pw_gid;
else
- *((gid_t *)((uschar *)data_block + (long int)(ol2->value))) = pw->pw_gid;
+ *((gid_t *)(US data_block + (long int)(ol2->value))) = pw->pw_gid;
*set_flag = TRUE;
}
}
if (data_block == NULL)
*((uschar **)(ol2->value)) = ss;
else
- *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = ss;
+ *((uschar **)(US data_block + (long int)(ol2->value))) = ss;
if (ss != NULL)
{
if (data_block == NULL)
*((gid_t *)(ol->value)) = gid;
else
- *((gid_t *)((uschar *)data_block + (long int)(ol->value))) = gid;
+ *((gid_t *)(US data_block + (long int)(ol->value))) = gid;
*(get_set_flag(name, oltop, last, data_block)) = TRUE;
break;
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);
if (data_block == NULL)
*((uid_t **)(ol->value)) = list;
else
- *((uid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
+ *((uid_t **)(US data_block + (long int)(ol->value))) = list;
- p = sptr;
+ p = op;
while (count-- > 1)
{
int sep = 0;
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);
if (data_block == NULL)
*((gid_t **)(ol->value)) = list;
else
- *((gid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
+ *((gid_t **)(US data_block + (long int)(ol->value))) = list;
- p = sptr;
+ p = op;
while (count-- > 1)
{
int sep = 0;
if (data_block == NULL)
*((uschar **)(ol2->value)) = sptr;
else
- *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = sptr;
+ *((uschar **)(US data_block + (long int)(ol2->value))) = sptr;
freesptr = FALSE;
break;
}
int bit = 1 << ((ol->type >> 16) & 31);
int *ptr = (data_block == NULL)?
(int *)(ol->value) :
- (int *)((uschar *)data_block + (long int)ol->value);
+ (int *)(US data_block + (long int)ol->value);
if (boolvalue) *ptr |= bit; else *ptr &= ~bit;
break;
}
if (data_block == NULL)
*((BOOL *)(ol->value)) = boolvalue;
else
- *((BOOL *)((uschar *)data_block + (long int)(ol->value))) = boolvalue;
+ *((BOOL *)(US data_block + (long int)(ol->value))) = boolvalue;
/* Verify fudge */
if (data_block == NULL)
*((BOOL *)(ol2->value)) = boolvalue;
else
- *((BOOL *)((uschar *)data_block + (long int)(ol2->value))) = boolvalue;
+ *((BOOL *)(US data_block + (long int)(ol2->value))) = boolvalue;
}
}
if (data_block == NULL)
*((BOOL *)(ol2->value)) = TRUE;
else
- *((BOOL *)((uschar *)data_block + (long int)(ol2->value))) = TRUE;
+ *((BOOL *)(US data_block + (long int)(ol2->value))) = TRUE;
}
}
break;
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)
*((int *)(ol->value)) = value;
else
- *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+ *((int *)(US 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:
{
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);
if (data_block == NULL)
*((int *)(ol->value)) = value;
else
- *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+ *((int *)(US data_block + (long int)(ol->value))) = value;
break;
/* Fixed-point number: held to 3 decimal places. */
if (value < 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
"integer \"%s\" is too large (overflow)", s);
+ /* We get a coverity error here for using count, as it derived
+ from the tainted buffer pointed to by s, as parsed by sscanf().
+ By the definition of sscanf we must be accessing between start
+ and end of s (assuming it is nul-terminated...) so ignore the error. */
+ /* coverity[tainted_data] */
if (s[count] == '.')
{
int d = 100;
if (data_block == NULL)
*((int *)(ol->value)) = value;
else
- *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+ *((int *)(US data_block + (long int)(ol->value))) = value;
break;
/* There's a special routine to read time values. */
if (data_block == NULL)
*((int *)(ol->value)) = value;
else
- *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+ *((int *)(US data_block + (long int)(ol->value))) = value;
break;
/* A time list is a list of colon-separated times, with the first
int count = 0;
int *list = (data_block == NULL)?
(int *)(ol->value) :
- (int *)((uschar *)data_block + (long int)(ol->value));
+ (int *)(US data_block + (long int)(ol->value));
if (*s != 0) for (count = 1; count <= list[0] - 2; count++)
{
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;
int s, m, h, d, w;
uschar *p = time_buffer;
+if (t < 0)
+ {
+ *p++ = '-';
+ t = -t;
+ }
+
s = t % 60;
t /= 60;
m = t % 60;
d = t % 7;
w = t/7;
-if (w > 0) { sprintf(CS p, "%dw", w); while (*p) p++; }
-if (d > 0) { sprintf(CS p, "%dd", d); while (*p) p++; }
-if (h > 0) { sprintf(CS p, "%dh", h); while (*p) p++; }
-if (m > 0) { sprintf(CS p, "%dm", m); while (*p) p++; }
+if (w > 0) p += sprintf(CS p, "%dw", w);
+if (d > 0) p += sprintf(CS p, "%dd", d);
+if (h > 0) p += sprintf(CS p, "%dh", h);
+if (m > 0) p += sprintf(CS p, "%dm", m);
if (s > 0 || p == time_buffer) sprintf(CS p, "%ds", s);
return time_buffer;
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;
if (!admin_user && (ol->type & opt_secure) != 0)
{
- printf("%s = <value not displayable>\n", name);
+ if (no_labels)
+ printf("%s\n", hidden);
+ else
+ printf("%s = %s\n", name, hidden);
return;
}
{
if ((ol->type & opt_public) == 0)
options_block = (void *)(((driver_instance *)options_block)->options_block);
- value = (void *)((uschar *)options_block + (long int)value);
+ value = (void *)(US options_block + (long int)value);
}
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:
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 */
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);
{
void *value2 = ol2->value;
if (options_block != NULL)
- value2 = (void *)((uschar *)options_block + (long int)value2);
+ value2 = (void *)(US 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;
}
}
/* 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;
{
void *value2 = ol2->value;
if (options_block != NULL)
- value2 = (void *)((uschar *)options_block + (long int)value2);
+ value2 = (void *)(US 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;
}
}
/* 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 = ':';
}
}
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 = ':';
}
}
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");
{
void *value2 = ol2->value;
if (options_block != NULL)
- value2 = (void *)((uschar *)options_block + (long int)value2);
+ value2 = (void *)(US options_block + (long int)value2);
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 */
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)
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);
}
}
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;
if (Ustrcmp(name, "all") == 0)
{
for (ol = optionlist_config;
- ol < optionlist_config + optionlist_config_size; ol++)
+ ol < optionlist_config + nelem(optionlist_config); 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, nelem(optionlist_config),
+ no_labels);
}
return;
}
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";
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)
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);
+ print_ol(find_option(name, optionlist_config, nelem(optionlist_config)),
+ name, NULL, optionlist_config, nelem(optionlist_config), no_labels);
return;
}
}
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; m = m->next)
+ if (!name || 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)
+ return;
+ }
+ if (name)
+ printf("%s %s not found\n", type, name);
+ return;
+ }
+
if (names_only)
{
for (; d != NULL; d = d->next) printf("%s\n", CS d->name);
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;
}
+/*************************************************
+* 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");
+
+ if ((errmsg = tls_validate_require_cipher()))
+ 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 */
+
+
+
+
/*************************************************
* Read main configuration options *
*************************************************/
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;
/* Loop through the possible file names */
-while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
- != NULL)
+while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
{
+
/* Cut out all the fancy processing unless specifically wanted */
#if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID)
config_main_filename is the name shown by -bP. Failure to open a configuration
file is a serious disaster. */
-if (config_file != NULL)
+if (config_file)
{
+ uschar *last_slash = Ustrrchr(filename, '/');
config_filename = config_main_filename = string_copy(filename);
+
+ /* The config_main_directory we need for the $config_dir expansion.
+ config_main_filename we need for $config_file expansion.
+ And config_dir is the directory of the current configuration, used for
+ relative .includes. We do need to know it's name, as we change our working
+ directory later. */
+
+ if (filename[0] == '/')
+ config_main_directory = last_slash == filename ? US"/" : string_copyn(filename, last_slash - filename);
+ else
+ {
+ /* relative configuration file name: working dir + / + basename(filename) */
+
+ uschar buf[PATH_MAX];
+ int offset = 0;
+ int size = 0;
+
+ if (os_getcwd(buf, PATH_MAX) == NULL)
+ {
+ perror("exim: getcwd");
+ exit(EXIT_FAILURE);
+ }
+ config_main_directory = string_cat(NULL, &size, &offset, buf);
+
+ /* If the dir does not end with a "/", append one */
+ if (config_main_directory[offset-1] != '/')
+ config_main_directory = string_catn(config_main_directory, &size, &offset, US"/", 1);
+
+ /* If the config file contains a "/", extract the directory part */
+ if (last_slash)
+ config_main_directory = string_catn(config_main_directory, &size, &offset, filename, last_slash - filename);
+
+ config_main_directory[offset] = '\0';
+ }
+ config_directory = config_main_directory;
}
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. */
+/* 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);
+ }
+
+/* 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 */
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. */
+ if (config_lineno == 1 && Ustrstr(s, "\xef\xbb\xbf") == s)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+ "found unexpected BOM (Byte Order Mark)");
- 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,
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
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");
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;
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)
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);
}
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. */
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,
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)
+ {
+# 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.");
}
driver_info *dd;
for (dd = drivers_available; dd->driver_name[0] != 0;
- dd = (driver_info *)(((uschar *)dd) + size_of_info))
+ dd = (driver_info *)((US dd) + size_of_info))
{
if (Ustrcmp(d->driver_name, dd->driver_name) == 0)
{
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
/* 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);
/* 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);
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 */
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
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);
}
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. */
/* 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);
int type = ol->type & opt_mask;
if (type != opt_stringptr) continue;
options_block = ((ol->type & opt_public) == 0)? d->options_block : (void *)d;
- value = *(uschar **)((uschar *)options_block + (long int)(ol->value));
+ value = *(uschar **)(US options_block + (long int)(ol->value));
if (value != NULL && (ss = Ustrstr(value, s)) != NULL)
{
if (ss <= value || (ss[-1] != '$' && ss[-1] != '{') ||
*/
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;
{
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" };
static int values[] =
{ 'A', 'M', RTEF_CTOUT, RTEF_CTOUT|'A', RTEF_CTOUT|'M' };
- for (i = 0; i < sizeof(extras)/sizeof(uschar *); i++)
- {
+ for (i = 0; i < nelem(extras); i++)
if (strncmpic(x, extras[i], xlen) == 0)
{
*more_errno = values[i];
break;
}
- }
- if (i >= sizeof(extras)/sizeof(uschar *))
- {
+ if (i >= nelem(extras))
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" */
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;
}
*/
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");
*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 */
}
{
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;
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
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");
auths_init(void)
{
auth_instance *au, *bu;
+
readconf_driver_init(US"authenticator",
(driver_instance **)(&auths), /* chain anchor */
(driver_info *)auths_available, /* available drivers */
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);
- }
- }
}
}
/* 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();
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);
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
*/
US"transports"};
void
-readconf_rest(BOOL skip_acl)
+readconf_rest(void)
{
int had = 0;
{
int bit;
int first = 0;
- int last = sizeof(section_list) / sizeof(uschar *);
+ int last = nelem(section_list);
int mid = last/2;
int n = Ustrlen(next_section);
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;
}
}
-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 <hide> 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 <hide> 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);
+ }
}
+#endif /*!MACRO_PREDEF*/
+/* vi: aw ai sw=2
+*/
/* End of readconf.c */