* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* 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"
+extern char **environ;
+
+static void fn_smtp_receive_timeout(const uschar * name, const uschar * str);
+static void save_config_line(const uschar* line);
+static void save_config_position(const uschar *file, int line);
+static void print_config(BOOL admin, BOOL terse);
+
+
#define CSTATE_STACK_SIZE 10
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_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 },
{ "disable_ipv6", opt_bool, &disable_ipv6 },
#ifndef DISABLE_DKIM
{ "dkim_verify_signers", opt_stringptr, &dkim_verify_signers },
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ { "dmarc_forensic_sender", opt_stringptr, &dmarc_forensic_sender },
+ { "dmarc_history_file", opt_stringptr, &dmarc_history_file },
+ { "dmarc_tld_file", opt_stringptr, &dmarc_tld_file },
#endif
{ "dns_again_means_nonexist", opt_stringptr, &dns_again_means_nonexist },
{ "dns_check_names_pattern", opt_stringptr, &check_dns_names_pattern },
{ "dns_csa_search_limit", opt_int, &dns_csa_search_limit },
{ "dns_csa_use_reverse", opt_bool, &dns_csa_use_reverse },
+ { "dns_dnssec_ok", opt_int, &dns_dnssec_ok },
{ "dns_ipv4_lookup", opt_stringptr, &dns_ipv4_lookup },
{ "dns_retrans", opt_time, &dns_retrans },
{ "dns_retry", opt_int, &dns_retry },
+ { "dns_trust_aa", opt_stringptr, &dns_trust_aa },
{ "dns_use_edns0", opt_int, &dns_use_edns0 },
/* This option is now a no-op, retained for compability */
{ "drop_cr", opt_bool, &drop_cr },
/*********************************************************/
+ { "dsn_advertise_hosts", opt_stringptr, &dsn_advertise_hosts },
{ "dsn_from", opt_stringptr, &dsn_from },
{ "envelope_to_remove", opt_bool, &envelope_to_remove },
{ "errors_copy", opt_stringptr, &errors_copy },
{ "errors_reply_to", opt_stringptr, &errors_reply_to },
+#ifndef DISABLE_EVENT
+ { "event_action", opt_stringptr, &event_action },
+#endif
{ "exim_group", opt_gid, &exim_gid },
{ "exim_path", opt_stringptr, &exim_path },
{ "exim_user", opt_uid, &exim_uid },
{ "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 */
- { "gnutls_require_kx", opt_stringptr, &gnutls_require_kx },
- { "gnutls_require_mac", opt_stringptr, &gnutls_require_mac },
- { "gnutls_require_protocols", opt_stringptr, &gnutls_require_proto },
#endif
{ "header_line_maxsize", opt_int, &header_line_maxsize },
{ "header_maxsize", opt_int, &header_maxsize },
{ "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 },
#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 },
{ "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
#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 },
-#if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS)
+ { "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_on_connect_ports },
+# 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;
}
}
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 *
*************************************************/
gid_t gid;
BOOL boolvalue = TRUE;
BOOL freesptr = TRUE;
-BOOL extra_condition = FALSE;
optionlist *ol, *ol2;
struct passwd *pw;
void *reset_point;
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 (Ustrcmp(mname, "condition") == 0)
- extra_condition = TRUE;
- else
- log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
- "\"%s\" option set for the second time", mname);
- }
+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", 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 != '='))
str_target = (uschar **)(ol->value);
else
str_target = (uschar **)((uschar *)data_block + (long int)(ol->value));
- if (extra_condition)
+ 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
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 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.
+ /* 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
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;
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",
/* Integer held in K: again, allow octal and hex formats, and suffixes K and
M. */
+ /*XXX consider moving to int_eximarith_t (but mind the overflow test 0415) */
case opt_Kint:
{
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
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",
"authenticator" or "macro" in which case the first argument identifies the
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;
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, no_labels);
+ return;
+ }
+
if (Ustrcmp(name, "routers") == 0)
{
type = US"router";
names_only = TRUE;
}
+ else if (Ustrcmp(name, "environment") == 0)
+ {
+ if (environ)
+ {
+ uschar ** p;
+ for (p = USS environ; *p; p++) ;
+ qsort(environ, p - USS environ, sizeof(*p), string_compare_by_pointer);
+
+ for (p = USS environ; *p; p++)
+ {
+ uschar * q;
+ if (no_labels && (q = Ustrchr(*p, '='))) *q = '\0';
+ puts(CS *p);
+ }
+ }
+ return;
+ }
+
else
{
print_ol(find_option(name, optionlist_config, optionlist_config_size),
- name, NULL, optionlist_config, optionlist_config_size);
+ name, NULL, optionlist_config, optionlist_config_size, no_labels);
return;
}
}
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");
+
+ 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 *
*************************************************/
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)
{
+
/* Cut out all the fancy processing unless specifically wanted */
#if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID)
if (config_file != NULL || errno != ENOENT) break;
}
+/* Now, once we found and opened our configuration file, we change the directory
+to a safe place. Later we change to $spool_directory. */
+
+if (Uchdir("/") < 0)
+ {
+ perror("exim: chdir `/': ");
+ exit(EXIT_FAILURE);
+ }
+
/* On success, save the name for verification; config_filename is used when
logging configuration errors (it changes for .included files) whereas
config_main_filename is the name shown by -bP. Failure to open a configuration
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
{
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;
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)
"tls_%sverify_hosts is set, but tls_verify_certificates is not set",
(tls_verify_hosts != NULL)? "" : "try_");
+/* This also checks that the library linkage is working and we can call
+routines in it, so call even if tls_require_ciphers is unset */
+if (!tls_dropprivs_validate_require_cipher(nowarn))
+ exit(1);
+
+/* Magic number: at time of writing, 1024 has been the long-standing value
+used by so many clients, and what Exim used to use always, that it makes
+sense to just min-clamp this max-clamp at that. */
+if (tls_dh_max_bits < 1024)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+ "tls_dh_max_bits is too small, must be at least 1024 for interop");
+
/* If openssl_options is set, validate it */
if (openssl_options != NULL)
{
"openssl_options parse error: %s", openssl_options);
# endif
}
-#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.");
}
if (isupper(*name) && *s == '=')
{
- 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);
/* 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 */
/* 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);
*/
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\"", (int) (p-pp), pp);
*/
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 ||
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. 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, BOOL terse)
+{
+config_line_item *i;
+const int TS = terse ? 0 : 2;
+int indent = 0;
+
+for (i = config_lines; i; i = i->next)
+ {
+ uschar *current;
+ uschar *p;
+
+ /* skip over to the first non-space */
+ for (current = i->line; *current && isspace(*current); ++current)
+ ;
+
+ if (*current == '\0')
+ continue;
+
+ /* Collapse runs of spaces. We stop this if we encounter one of the
+ * following characters: "'$, as this may indicate careful formatting */
+ for (p = current; *p; ++p)
+ {
+ uschar *next;
+ if (!isspace(*p)) continue;
+ if (*p != ' ') *p = ' ';
+
+ for (next = p; isspace(*next); ++next)
+ ;
+
+ if (next - p > 1)
+ memmove(p+1, next, Ustrlen(next)+1);
+
+ if (*next == '"' || *next == '\'' || *next == '$')
+ break;
+ }
+
+ /* # lines */
+ if (current[0] == '#')
+ puts(CCS current);
+
+ /* begin lines are left aligned */
+ else if (Ustrncmp(current, "begin", 5) == 0 && isspace(current[5]))
+ {
+ if (!terse) puts("");
+ puts(CCS current);
+ indent = TS;
+ }
+
+ /* router/acl/transport block names */
+ else if (current[Ustrlen(current)-1] == ':' && !Ustrchr(current, '='))
+ {
+ if (!terse) puts("");
+ printf("%*s%s\n", TS, "", current);
+ indent = 2 * TS;
+ }
+
+ /* hidden lines (all MACROS or lines prefixed with "hide") */
+ else if ( !admin
+ && ( isupper(*current)
+ || Ustrncmp(current, "hide", 4) == 0 && isspace(current[4])
+ )
+ )
+ {
+ if ((p = Ustrchr(current, '=')))
+ {
+ *p = '\0';
+ printf("%*s%s= %s\n", indent, "", current, hidden);
+ }
+ /* e.g.: hide split_spool_directory */
+ else
+ printf("%*s\n", indent, hidden);
+ }
+
+ else
+ /* rest is public */
+ printf("%*s%s\n", indent, "", current);
+ }
+}
+
+/* vi: aw ai sw=2
+*/
/* End of readconf.c */