* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* 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"
+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);
+
+
#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_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 },
{ "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 },
/*********************************************************/
-#ifdef EXPERIMENTAL_DSN
{ "dsn_advertise_hosts", opt_stringptr, &dsn_advertise_hosts },
-#endif
{ "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 },
{ "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 },
{ "print_topbitchars", opt_bool, &print_topbitchars },
{ "process_log_path", opt_stringptr, &process_log_path },
{ "prod_requires_admin", opt_bool, &prod_requires_admin },
-#ifdef EXPERIMENTAL_PROXY
- { "proxy_required_hosts", opt_stringptr, &proxy_required_hosts },
-#endif
{ "qualify_domain", opt_stringptr, &qualify_domain_sender },
{ "qualify_recipient", opt_stringptr, &qualify_domain_recipient },
{ "queue_domains", opt_stringptr, &queue_domains },
{ "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
+#ifdef LOOKUP_REDIS
{ "redis_servers", opt_stringptr, &redis_servers },
#endif
{ "remote_max_parallel", opt_int, &remote_max_parallel },
{ "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 },
+ { "tls_eccurve", opt_stringptr, &tls_eccurve },
# ifndef DISABLE_OCSP
{ "tls_ocsp_file", opt_stringptr, &tls_ocsp_file },
# endif
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 *
*************************************************/
}
/* 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 != '='))
}
else if (ol->type & opt_rep_str)
{
- uschar sep = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':';
- uschar * cp;
-
- /* Strip trailing whitespace and seperators */
- for (cp = sptr + Ustrlen(sptr) - 1;
- cp >= sptr && (*cp == '\n' || *cp == '\t' || *cp == ' ' || *cp == sep);
- cp--) *cp = '\0';
-
- if (cp >= sptr)
- *str_target = string_copy_malloc(
- *str_target ? string_sprintf("%s%c%s", *str_target, sep, sptr)
- : sptr);
+ 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
{
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;
if (!admin_user && (ol->type & opt_secure) != 0)
{
- const char * const hidden = "<value not displayable>";
if (no_labels)
printf("%s\n", hidden);
else
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
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;
return;
}
+ if (Ustrcmp(name, "config") == 0)
+ {
+ print_config(admin_user);
+ return;
+ }
+
if (Ustrcmp(name, "routers") == 0)
{
type = US"router";
names_only = TRUE;
}
+ else if (Ustrcmp(name, "environment") == 0)
+ {
+ if (environ)
+ {
+ uschar **p;
+ size_t n;
+ for (p = USS environ; *p; p++) ;
+ n = p - USS environ;
+ qsort(environ, p - USS environ, sizeof(*p), string_compare_by_pointer);
+
+ for (p = USS environ; *p; p++)
+ {
+ if (no_labels) *(Ustrchr(*p, '=')) = '\0';
+ puts(*p);
+ }
+ }
+ return;
+ }
+
else
{
print_ol(find_option(name, optionlist_config, optionlist_config_size),
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);
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)
{
+
+ /* To avoid confusion: Exim changes to / at the very beginning and
+ * and to $spool_directory later. */
+ if (filename[0] != '/')
+ {
+ fprintf(stderr, "-C %s: only absolute names are allowed\n", filename);
+ exit(EXIT_FAILURE);
+ }
+
/* Cut out all the fancy processing unless specifically wanted */
#if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID)
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)
"openssl_options parse error: %s", openssl_options);
# endif
}
-#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*/
+
+if ((!add_environment || *add_environment == '\0') && !keep_environment)
+ log_write(0, LOG_MAIN,
+ "WARNING: purging the environment.\n"
+ " Suggested action: use keep_environment and add_environment.\n");
}
*/
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;
/* 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 ||
(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)
+ {
+ 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]))
+ {
+ puts("");
+ puts(CCS current);
+ indent = TS;
+ }
+
+ /* router/acl/transport block names */
+ else if (current[Ustrlen(current)-1] == ':' && !Ustrchr(current, '='))
+ {
+ printf("\n%*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 */