-/* $Cambridge: exim/src/src/readconf.c,v 1.35 2008/02/12 12:52:51 nm4 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for reading the configuration file, and for displaying
#include "exim.h"
+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);
+
#define CSTATE_STACK_SIZE 10
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 {
int value;
} syslog_fac_item;
+/* constants */
+static const char * const hidden = "<value not displayable>";
/* Static variables */
{ "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 },
{ "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 },
+#ifdef EXPERIMENTAL_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 },
{ "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 },
+ /* These three gnutls_require_* options stopped working in Exim 4.80 */
+ /* From 4.83 we log a warning; a future relase will remove them */
{ "gnutls_require_kx", opt_stringptr, &gnutls_require_kx },
{ "gnutls_require_mac", opt_stringptr, &gnutls_require_mac },
{ "gnutls_require_protocols", opt_stringptr, &gnutls_require_proto },
{ "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_fromline_local", opt_bool, &ignore_fromline_local },
{ "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 },
{ "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
#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 },
{ "recipient_unqualified_hosts", opt_stringptr, &recipient_unqualified_hosts },
{ "recipients_max", opt_int, &recipients_max },
{ "recipients_max_reject", opt_bool, &recipients_max_reject },
+#ifdef EXPERIMENTAL_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
{ "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 },
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))
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;
}
}
{
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);
+ "macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
name[namelen++] = *s++;
}
name[namelen] = 0;
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;
}
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 (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;
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;
*/
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 *saved_condition, *strtemp;
+uschar **str_target;
uschar name[64];
uschar name2[64];
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:
+ 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;
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;
int count = 1;
uid_t *list;
int ptr = 0;
- uschar *p;
- uschar *op = expand_string (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",
int count = 1;
gid_t *list;
int ptr = 0;
- uschar *p;
- uschar *op = expand_string (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);
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;
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;
}
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);
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;
}
}
/* 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;
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;
}
}
/* 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");
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
-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;
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;
}
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);
+ 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
{
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;
}
}
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);
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(void)
+{
+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 (!tls_certificate)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "Warning: No server certificate defined; TLS connections will fail.\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 */
+
+
+
+
/*************************************************
* Read main configuration options *
*************************************************/
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 */
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
{
"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
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");
#if HAVE_IPV6
if (!disable_ipv6 && (dns_ipv4_lookup == NULL ||
- match_isinlist(hostname, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN,
- TRUE, NULL) != OK))
+ 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())
+ 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
+ }
+
+if (gnutls_require_kx || gnutls_require_mac || gnutls_require_proto)
+ log_write(0, LOG_MAIN, "WARNING: main options"
+ " gnutls_require_kx, gnutls_require_mac and gnutls_require_protocols"
+ " are obsolete\n");
+#endif /*SUPPORT_TLS*/
}
*/
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" };
{ '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\"";
}
}
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 = (*pp == 'm')? ERRNO_MAIL4XX :
- (*pp == 'r')? ERRNO_RCPT4XX : ERRNO_DATA4XX;
+ *basic_errno = *pp == 'm' ? ERRNO_MAIL4XX :
+ *pp == 'r' ? ERRNO_RCPT4XX : ERRNO_DATA4XX;
*more_errno = x << 8;
}
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 */
case 'H': /* Ditto, but with randomness */
- rule->p1 = retry_arg(&p, 0);
- rule->p2 = retry_arg(&p, 1);
- break;
+ 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 ||
/* 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. Macro (re)definitions are allowed
between ACLs. */
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;
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;
(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)
+{
+config_line_item *i;
+const int TS = 2;
+int indent = 0;
+
+for (i = config_lines; i; i = i->next)
+ {
+ const uschar *current;
+ uschar *p;
+
+ /* skip over to the first non-space */
+ for (current = i->line; *current && isspace(*current); ++current)
+ ;
+
+ if (*current == '\0')
+ continue;
+
+ /* TODO: Collapse or insert spaces around the first '=' */
+
+ /* # lines */
+ if (current[0] == '#')
+ {
+ puts(current);
+ continue;
+ }
+
+ /* begin lines are left aligned */
+ if (strncmp(current, "begin", 5) == 0 && isspace(current[5]))
+ {
+ puts(current);
+ indent = TS;
+ continue;
+ }
+
+ /* router/acl/transport block names */
+ if (current[strlen(current)-1] == ':' && !strchr(current, '='))
+ {
+ printf("%*s%s\n", TS, "", current);
+ indent = 2 * TS;
+ continue;
+ }
+
+ /* hidden lines (MACROS or prefixed with hide) */
+ if (!admin && (isupper(*current)
+ || (strncmp(current, "hide", 4) == 0 && isspace(current[4]))))
+ {
+ if (p = strchr(current, '='))
+ {
+ *p = '\0';
+ printf("%*s%s = %s\n", indent, "", current, hidden);
+ }
+ /* e.g.: hide split_spool_directory */
+ else printf("%*s\n", indent, hidden);
+ continue;
+ }
+
+ /* rest is public */
+ printf("%*s%s\n", indent, "", current);
+ continue;
+ }
+}
+
+/* vi: aw ai sw=2
+*/
/* End of readconf.c */