From: Jeremy Harris Date: Sun, 27 Jun 2021 23:35:57 +0000 (+0100) Subject: Merge branch 'readonly_config' X-Git-Tag: exim-4.95-RC0~10 X-Git-Url: https://git.netwichtig.de/gitweb/?a=commitdiff_plain;h=753739fdef6d9753ee4a7e89afd959a4034d2ad9;hp=d8c9f31a3ec7a424ac9465604c397f1882b05567;p=user%2Fhenk%2Fcode%2Fexim.git Merge branch 'readonly_config' --- diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 90645e005..3311ae8f5 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -316,6 +316,9 @@ JH/54 DMARC: recent versions of the OpenDMARC library appear to have broken JH/55 TLS: as server, reject connections with ALPN indicating non-smtp use. +JH/56 Make the majority of info read from config files readonly, for defence-in- + depth against exploits. Suggestion by Qualsy. + Exim version 4.94 ----------------- diff --git a/src/src/auths/gsasl_exim.c b/src/src/auths/gsasl_exim.c index 7f9cc3295..479d01a29 100644 --- a/src/src/auths/gsasl_exim.c +++ b/src/src/auths/gsasl_exim.c @@ -757,7 +757,7 @@ switch (prop) for memory wiping, so expanding strings will leave stuff laying around. But no need to compound the problem, so get rid of the one we can. */ - memset(tmps, '\0', strlen(tmps)); + if (US tmps != s) memset(tmps, '\0', strlen(tmps)); cbrc = GSASL_OK; break; diff --git a/src/src/deliver.c b/src/src/deliver.c index f9f674643..e931d22e9 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -6554,14 +6554,19 @@ while (addr_new) /* Loop until all addresses dealt with */ /* Treat /dev/null as a special case and abandon the delivery. This avoids having to specify a uid on the transport just for this case. - Arrange for the transport name to be logged as "**bypassed**". */ + Arrange for the transport name to be logged as "**bypassed**". + Copy the transport for this fairly unusual case rather than having + to make all transports mutable. */ if (Ustrcmp(addr->address, "/dev/null") == 0) { - uschar *save = addr->transport->name; - addr->transport->name = US"**bypassed**"; + transport_instance * save_t = addr->transport; + transport_instance * t = store_get(sizeof(*t), is_tainted(save_t)); + *t = *save_t; + t->name = US"**bypassed**"; + addr->transport = t; (void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '='); - addr->transport->name = save; + addr->transport= save_t; continue; /* with the next new address */ } diff --git a/src/src/exim.c b/src/src/exim.c index 6a9a1477f..a42c48b2c 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -211,6 +211,19 @@ exit(1); } +/*********************************************** +* Handler for SIGSEGV * +***********************************************/ + +static void +segv_handler(int sig) +{ +log_write(0, LOG_MAIN|LOG_PANIC, "SIGSEGV (maybe attempt to write to immutable memory)"); +signal(SIGSEGV, SIG_DFL); +kill(getpid(), sig); +} + + /************************************************* * Handler for SIGUSR1 * *************************************************/ @@ -1805,7 +1818,8 @@ descriptive text. */ process_info = store_get(PROCESS_INFO_SIZE, TRUE); /* tainted */ set_process_info("initializing"); -os_restarting_signal(SIGUSR1, usr1_handler); +os_restarting_signal(SIGUSR1, usr1_handler); /* exiwhat */ +signal(SIGSEGV, segv_handler); /* log faults */ /* If running in a dockerized environment, the TERM signal is only delegated to the PID 1 if we request it by setting an signal handler */ @@ -3688,7 +3702,7 @@ else { struct rlimit rlp; - #ifdef RLIMIT_NOFILE +#ifdef RLIMIT_NOFILE if (getrlimit(RLIMIT_NOFILE, &rlp) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NOFILE) failed: %s", @@ -3711,9 +3725,9 @@ else strerror(errno)); } } - #endif +#endif - #ifdef RLIMIT_NPROC +#ifdef RLIMIT_NPROC if (getrlimit(RLIMIT_NPROC, &rlp) < 0) { log_write(0, LOG_MAIN|LOG_PANIC, "getrlimit(RLIMIT_NPROC) failed: %s", @@ -3721,20 +3735,20 @@ else rlp.rlim_cur = rlp.rlim_max = 0; } - #ifdef RLIM_INFINITY +# ifdef RLIM_INFINITY if (rlp.rlim_cur != RLIM_INFINITY && rlp.rlim_cur < 1000) { rlp.rlim_cur = rlp.rlim_max = RLIM_INFINITY; - #else +# else if (rlp.rlim_cur < 1000) { rlp.rlim_cur = rlp.rlim_max = 1000; - #endif +# endif if (setrlimit(RLIMIT_NPROC, &rlp) < 0) log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_NPROC) failed: %s", strerror(errno)); } - #endif +#endif } /* Exim is normally entered as root (but some special configurations are @@ -3857,6 +3871,7 @@ is equivalent to the ability to modify a setuid binary! This needs to happen before we read the main configuration. */ init_lookup_list(); +/*XXX this excrescence could move to the testsuite standard config setup file */ #ifdef SUPPORT_I18N if (f.running_in_test_harness) smtputf8_advertise_hosts = NULL; #endif @@ -3895,19 +3910,21 @@ issues (currently about tls_advertise_hosts and keep_environment not being defined) */ { + int old_pool = store_pool; #ifdef MEASURE_TIMING struct timeval t0, diff; (void)gettimeofday(&t0, NULL); #endif + store_pool = POOL_CONFIG; readconf_main(checking || list_options); + store_pool = old_pool; #ifdef MEASURE_TIMING report_time_since(&t0, US"readconf_main (delta)"); #endif } - /* Now in directory "/" */ if (cleanup_environment() == FALSE) @@ -4494,7 +4511,13 @@ if (msg_action_arg > 0 && msg_action != MSG_DELIVER && msg_action != MSG_LOAD) event_action gets expanded */ if (msg_action == MSG_REMOVE) + { + int old_pool = store_pool; + store_pool = POOL_CONFIG; readconf_rest(); + store_pool = old_pool; + store_writeprotect(POOL_CONFIG); + } if (!one_msg_action) { @@ -4519,12 +4542,16 @@ Now, since the intro of the ${acl } expansion, ACL definitions may be needed in transports so we lost the optimisation. */ { + int old_pool = store_pool; #ifdef MEASURE_TIMING struct timeval t0, diff; (void)gettimeofday(&t0, NULL); #endif + store_pool = POOL_CONFIG; readconf_rest(); + store_pool = old_pool; + store_writeprotect(POOL_CONFIG); #ifdef MEASURE_TIMING report_time_since(&t0, US"readconf_rest (delta)"); @@ -5451,15 +5478,15 @@ that SIG_IGN works. */ if (!f.synchronous_delivery) { - #ifdef SA_NOCLDWAIT +#ifdef SA_NOCLDWAIT struct sigaction act; act.sa_handler = SIG_IGN; sigemptyset(&(act.sa_mask)); act.sa_flags = SA_NOCLDWAIT; sigaction(SIGCHLD, &act, NULL); - #else +#else signal(SIGCHLD, SIG_IGN); - #endif +#endif } /* Save the current store pool point, for resetting at the start of diff --git a/src/src/functions.h b/src/src/functions.h index e34972170..0744697f9 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -372,9 +372,9 @@ extern int open_cutthrough_connection( address_item * addr ); extern uschar *parse_extract_address(const uschar *, uschar **, int *, int *, int *, BOOL); -extern int parse_forward_list(uschar *, int, address_item **, uschar **, +extern int parse_forward_list(const uschar *, int, address_item **, uschar **, const uschar *, uschar *, error_block **); -extern uschar *parse_find_address_end(uschar *, BOOL); +extern uschar *parse_find_address_end(const uschar *, BOOL); extern const uschar *parse_find_at(const uschar *); extern const uschar *parse_fix_phrase(const uschar *, int); extern const uschar *parse_message_id(const uschar *, uschar **, uschar **); @@ -527,6 +527,7 @@ extern int stdin_ungetc(int); extern void store_exit(void); extern void store_init(void); +extern void store_writeprotect(int); extern gstring *string_append(gstring *, int, ...) WARN_UNUSED_RESULT; extern gstring *string_append_listele(gstring *, uschar, const uschar *) WARN_UNUSED_RESULT; diff --git a/src/src/parse.c b/src/src/parse.c index 896d00f30..58f894199 100644 --- a/src/src/parse.c +++ b/src/src/parse.c @@ -65,7 +65,7 @@ Returns: pointer past the end of the address */ uschar * -parse_find_address_end(uschar *s, BOOL nl_ends) +parse_find_address_end(const uschar *s, BOOL nl_ends) { BOOL source_routing = *s == '@'; int no_term = source_routing? 1 : 0; @@ -121,7 +121,7 @@ while (*s != 0 && (*s != ',' || no_term > 0) && (*s != '\n' || !nl_ends)) } } -return s; +return US s; } @@ -1233,7 +1233,7 @@ Returns: FF_DELIVERED addresses extracted */ int -parse_forward_list(uschar *s, int options, address_item **anchor, +parse_forward_list(const uschar *s, int options, address_item **anchor, uschar **error, const uschar *incoming_domain, uschar *directory, error_block **syntax_errors) { @@ -1247,7 +1247,7 @@ for (;;) int special = 0; int specopt = 0; int specbit = 0; - uschar *ss, *nexts; + const uschar *ss, *nexts; address_item *addr; BOOL inquote = FALSE; @@ -1275,7 +1275,7 @@ for (;;) syntax error has been skipped. I now think it is the wrong approach, but have left this here just in case, and for the record. */ - #ifdef NEVER +#ifdef NEVER if (count > 0) return FF_DELIVERED; /* Something was generated */ if (syntax_errors == NULL || /* Not skipping syntax errors, or */ @@ -1285,8 +1285,7 @@ for (;;) *error = string_sprintf("no addresses generated: syntax error in %s: %s", (*syntax_errors)->text2, (*syntax_errors)->text1); return FF_ERROR; - #endif - +#endif } /* Find the end of the next address. Quoted strings in addresses may contain @@ -1323,13 +1322,7 @@ for (;;) len = ss - s; - DEBUG(D_route) - { - int save = s[len]; - s[len] = 0; - debug_printf("extract item: %s\n", s); - s[len] = save; - } + DEBUG(D_route) debug_printf("extract item: %.*s\n", len, s); /* Handle special addresses if permitted. If the address is :unknown: ignore it - this is for backward compatibility with old alias files. You @@ -1350,7 +1343,7 @@ for (;;) else if (Ustrncmp(s, ":fail:", 6) == 0) { special = FF_FAIL; specopt = RDO_FAIL; } /* specbit is 0 */ - if (special != 0) + if (special) { uschar *ss = Ustrchr(s+1, ':') + 1; if ((options & specopt) == specbit) @@ -1358,10 +1351,9 @@ for (;;) *error = string_sprintf("\"%.*s\" is not permitted", len, s); return FF_ERROR; } - while (*ss != 0 && isspace(*ss)) ss++; - while (s[len] != 0 && s[len] != '\n') len++; - s[len] = 0; - *error = string_copy(ss); + while (*ss && isspace(*ss)) ss++; + while (s[len] && s[len] != '\n') len++; + *error = string_copyn(ss, s + len - ss); return special; } @@ -1374,7 +1366,7 @@ for (;;) { uschar *filebuf; uschar filename[256]; - uschar *t = s+9; + const uschar * t = s+9; int flen = len - 9; int frc; struct stat statbuf; @@ -1587,18 +1579,17 @@ for (;;) { int start, end, domain; const uschar *recipient = NULL; - int save = s[len]; - s[len] = 0; + uschar * s_ltd = string_copyn(s, len); /* If it starts with \ and the rest of it parses as a valid mail address without a domain, carry on with that address, but qualify it with the incoming domain. Otherwise arrange for the address to fall through, causing an error message on the re-parse. */ - if (*s == '\\') + if (*s_ltd == '\\') { recipient = - parse_extract_address(s+1, error, &start, &end, &domain, FALSE); + parse_extract_address(s_ltd+1, error, &start, &end, &domain, FALSE); if (recipient) recipient = domain != 0 ? NULL : string_sprintf("%s@%s", recipient, incoming_domain); @@ -1607,17 +1598,17 @@ for (;;) /* Try parsing the item as an address. */ if (!recipient) recipient = - parse_extract_address(s, error, &start, &end, &domain, FALSE); + parse_extract_address(s_ltd, error, &start, &end, &domain, FALSE); /* If item starts with / or | and is not a valid address, or there is no domain, treat it as a file or pipe. If it was a quoted item, remove the quoting occurrences of \ within it. */ - if ((*s == '|' || *s == '/') && (recipient == NULL || domain == 0)) + if ((*s_ltd == '|' || *s_ltd == '/') && (recipient == NULL || domain == 0)) { - uschar *t = store_get(Ustrlen(s) + 1, is_tainted(s)); + uschar *t = store_get(Ustrlen(s_ltd) + 1, is_tainted(s_ltd)); uschar *p = t; - uschar *q = s; + uschar *q = s_ltd; while (*q != 0) { if (inquote) @@ -1630,7 +1621,7 @@ for (;;) *p = 0; addr = deliver_make_addr(t, TRUE); setflag(addr, af_pfr); /* indicates pipe/file/reply */ - if (*s != '|') setflag(addr, af_file); /* indicates file */ + if (*s_ltd != '|') setflag(addr, af_file); /* indicates file */ } /* Item must be an address. Complain if not, else qualify, rewrite and set @@ -1642,36 +1633,33 @@ for (;;) else { - if (recipient == NULL) + if (!recipient) { if (Ustrcmp(*error, "empty address") == 0) { *error = NULL; - s[len] = save; s = nexts; continue; } - if (syntax_errors != NULL) + if (syntax_errors) { error_block *e = store_get(sizeof(error_block), FALSE); error_block *last = *syntax_errors; - if (last == NULL) *syntax_errors = e; else + if (!last) *syntax_errors = e; else { - while (last->next != NULL) last = last->next; + while (last->next) last = last->next; last->next = e; } e->next = NULL; e->text1 = *error; - e->text2 = string_copy(s); - s[len] = save; + e->text2 = s_ltd; s = nexts; continue; } else { - *error = string_sprintf("%s in \"%s\"", *error, s); - s[len] = save; /* _after_ using it for *error */ + *error = string_sprintf("%s in \"%s\"", *error, s_ltd); return FF_ERROR; } } @@ -1686,10 +1674,8 @@ for (;;) addr = deliver_make_addr(US recipient, TRUE); /* TRUE => copy recipient, so deconst ok */ } - /* Restore the final character in the original data, and add to the - output chain. */ + /* Add the original data to the output chain. */ - s[len] = save; addr->next = *anchor; *anchor = addr; count++; diff --git a/src/src/readconf.c b/src/src/readconf.c index 34ebf8769..01e85a329 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -3011,7 +3011,7 @@ read_named_list(tree_node **anchorp, int *numberp, int max, uschar *s, BOOL forcecache = FALSE; uschar *ss; tree_node *t; -namedlist_block * nb = store_get(sizeof(namedlist_block), FALSE); +namedlist_block * nb = store_get_perm(sizeof(namedlist_block), FALSE); if (Ustrncmp(s, "_cache", 6) == 0) { @@ -3142,55 +3142,54 @@ while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) /* Cut out all the fancy processing unless specifically wanted */ - #if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID) +#if defined(CONFIGURE_FILE_USE_NODE) || defined(CONFIGURE_FILE_USE_EUID) uschar *suffix = filename + Ustrlen(filename); /* Try for the node-specific file if a node name exists */ - #ifdef CONFIGURE_FILE_USE_NODE +# ifdef CONFIGURE_FILE_USE_NODE struct utsname uts; if (uname(&uts) >= 0) { - #ifdef CONFIGURE_FILE_USE_EUID +# ifdef CONFIGURE_FILE_USE_EUID sprintf(CS suffix, ".%ld.%.256s", (long int)original_euid, uts.nodename); - config_file = Ufopen(filename, "rb"); - if (config_file == NULL) - #endif /* CONFIGURE_FILE_USE_EUID */ + if (!(config_file = Ufopen(filename, "rb"))) +# endif /* CONFIGURE_FILE_USE_EUID */ { sprintf(CS suffix, ".%.256s", uts.nodename); config_file = Ufopen(filename, "rb"); } } - #endif /* CONFIGURE_FILE_USE_NODE */ +# endif /* CONFIGURE_FILE_USE_NODE */ /* Otherwise, try the generic name, possibly with the euid added */ - #ifdef CONFIGURE_FILE_USE_EUID - if (config_file == NULL) +# ifdef CONFIGURE_FILE_USE_EUID + if (!config_file) { sprintf(CS suffix, ".%ld", (long int)original_euid); config_file = Ufopen(filename, "rb"); } - #endif /* CONFIGURE_FILE_USE_EUID */ +# endif /* CONFIGURE_FILE_USE_EUID */ /* Finally, try the unadorned name */ - if (config_file == NULL) + if (!config_file) { *suffix = 0; config_file = Ufopen(filename, "rb"); } - #else /* if neither defined */ +#else /* if neither defined */ /* This is the common case when the fancy processing is not included. */ config_file = Ufopen(filename, "rb"); - #endif +#endif /* If the file does not exist, continue to try any others. For any other error, break out (and die). */ - if (config_file != NULL || errno != ENOENT) break; + if (config_file || errno != ENOENT) break; } /* On success, save the name for verification; config_filename is used when @@ -3213,39 +3212,37 @@ if (config_file) config_main_directory = last_slash == filename ? US"/" : string_copyn(filename, last_slash - filename); else { - /* relative configuration file name: working dir + / + basename(filename) */ + /* relative configuration file name: working dir + / + basename(filename) */ - uschar buf[PATH_MAX]; - gstring * g; + uschar buf[PATH_MAX]; + gstring * g; - if (os_getcwd(buf, PATH_MAX) == NULL) - { - perror("exim: getcwd"); - exit(EXIT_FAILURE); - } - g = string_cat(NULL, buf); + if (os_getcwd(buf, PATH_MAX) == NULL) + { + perror("exim: getcwd"); + exit(EXIT_FAILURE); + } + g = string_cat(NULL, buf); - /* If the dir does not end with a "/", append one */ - if (g->s[g->ptr-1] != '/') - g = string_catn(g, US"/", 1); + /* If the dir does not end with a "/", append one */ + if (g->s[g->ptr-1] != '/') + g = string_catn(g, US"/", 1); - /* If the config file contains a "/", extract the directory part */ - if (last_slash) - g = string_catn(g, filename, last_slash - filename); + /* If the config file contains a "/", extract the directory part */ + if (last_slash) + g = string_catn(g, filename, last_slash - filename); - config_main_directory = string_from_gstring(g); + config_main_directory = string_from_gstring(g); } config_directory = config_main_directory; } else - { if (!filename) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "non-existent configuration file(s): " "%s", config_main_filelist); else log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", string_open_failed("configuration file %s", filename)); - } /* Now, once we found and opened our configuration file, we change the directory to a safe place. Later we change to $spool_directory. */ @@ -3265,19 +3262,19 @@ if (f.trusted_config && Ustrcmp(filename, US"/dev/null")) 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 */ - #ifdef CONFIGURE_OWNER - && statbuf.st_uid != config_uid /* owner not the special one */ - #endif - ) || /* or */ - (statbuf.st_gid != root_gid /* group not root & */ - #ifdef CONFIGURE_GROUP - && statbuf.st_gid != config_gid /* group not the special one */ - #endif - && (statbuf.st_mode & 020) != 0) || /* group writeable */ - /* or */ - ((statbuf.st_mode & 2) != 0)) /* world writeable */ - + 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 != root_gid /* group not root & */ +#ifdef CONFIGURE_GROUP + && statbuf.st_gid != config_gid /* group not the special one */ +#endif + && (statbuf.st_mode & 020) != 0 /* group writeable */ + || /* or */ + (statbuf.st_mode & 2) != 0 /* world writeable */ + ) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Exim configuration file %s has the " "wrong owner, group, or mode", big_buffer); @@ -3324,11 +3321,11 @@ while ((s = get_config_line())) read_named_list(&hostlist_anchor, &hostlist_count, MAX_NAMED_LIST, t+8, US"host list", hide); - else if (Ustrncmp(t, US"addresslist", 11) == 0) + else if (Ustrncmp(t, "addresslist", 11) == 0) read_named_list(&addresslist_anchor, &addresslist_count, MAX_NAMED_LIST, t+11, US"address list", hide); - else if (Ustrncmp(t, US"localpartlist", 13) == 0) + else if (Ustrncmp(t, "localpartlist", 13) == 0) read_named_list(&localpartlist_anchor, &localpartlist_count, MAX_NAMED_LIST, t+13, US"local part list", hide); @@ -3347,7 +3344,7 @@ if (local_sender_retain && local_from_check) /* If the timezone string is empty, set it to NULL, implying no TZ variable wanted. */ -if (timezone_string != NULL && *timezone_string == 0) timezone_string = NULL; +if (timezone_string && !*timezone_string) timezone_string = NULL; /* The max retry interval must not be greater than 24 hours. */ @@ -3492,7 +3489,7 @@ if (syslog_facility_str) /* Expand pid_file_path */ -if (*pid_file_path != 0) +if (*pid_file_path) { if (!(s = expand_string(pid_file_path))) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand pid_file_path " @@ -3502,7 +3499,7 @@ if (*pid_file_path != 0) /* Set default value of process_log_path */ -if (!process_log_path || *process_log_path =='\0') +if (!process_log_path || !*process_log_path) process_log_path = string_sprintf("%s/exim-process.info", spool_directory); /* Compile the regex for matching a UUCP-style "From_" line in an incoming @@ -3554,7 +3551,7 @@ if (errors_reply_to) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "error in errors_reply_to (%s): %s", errors_reply_to, errmess); - if (domain == 0) + if (!domain) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "errors_reply_to (%s) does not contain a domain", errors_reply_to); } @@ -3562,8 +3559,7 @@ if (errors_reply_to) /* If smtp_accept_queue or smtp_accept_max_per_host is set, then smtp_accept_max must also be set. */ -if (smtp_accept_max == 0 && - (smtp_accept_queue > 0 || smtp_accept_max_per_host != NULL)) +if (smtp_accept_max == 0 && (smtp_accept_queue > 0 || smtp_accept_max_per_host)) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "smtp_accept_max must be set if smtp_accept_queue or " "smtp_accept_max_per_host is set"); @@ -3584,7 +3580,7 @@ if (host_number_string) host_number_string, expand_string_message); n = Ustrtol(s, &end, 0); while (isspace(*end)) end++; - if (*end != 0) + if (*end) log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "localhost_number value is not a number: %s", s); if (n > LOCALHOST_MAX) @@ -3661,7 +3657,7 @@ for (driver_info * dd = drivers_available; dd->driver_name[0] != 0; { int len = dd->options_len; d->info = dd; - d->options_block = store_get(len, FALSE); + d->options_block = store_get_perm(len, FALSE); memcpy(d->options_block, dd->options_block, len); for (int i = 0; i < *(dd->options_count); i++) dd->options[i].type &= ~opt_set; @@ -3677,6 +3673,16 @@ return NULL; /* never obeyed */ +static void +driver_init_fini(driver_instance * d, const uschar * class) +{ +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); +} + + /************************************************* * Initialize driver list * *************************************************/ @@ -3737,11 +3743,8 @@ while ((buffer = get_config_line())) { if (d) { - if (!d->driver_name) - log_write(0, LOG_PANIC_DIE|LOG_CONFIG, - "no driver defined for %s \"%s\"", class, d->name); /* s is using big_buffer, so this call had better not */ - (d->info->init)(d); + driver_init_fini(d, class); d = NULL; } if (!macro_read_assignment(buffer)) exim_exit(EXIT_FAILURE); @@ -3757,12 +3760,7 @@ while ((buffer = get_config_line())) /* Finish off initializing the previous driver. */ if (d) - { - if (!d->driver_name) - log_write(0, LOG_PANIC_DIE|LOG_CONFIG, - "no driver defined for %s \"%s\"", class, d->name); - (d->info->init)(d); - } + driver_init_fini(d, class); /* Check that we haven't already got a driver of this name */ @@ -3774,7 +3772,7 @@ while ((buffer = get_config_line())) /* Set up a new driver instance data block on the chain, with its default values installed. */ - d = store_get(instance_size, FALSE); + d = store_get_perm(instance_size, FALSE); memcpy(d, instance_default, instance_size); *p = d; p = &d->next; @@ -3826,12 +3824,7 @@ while ((buffer = get_config_line())) /* Run the initialization function for the final driver. */ if (d) - { - if (!d->driver_name) - log_write(0, LOG_PANIC_DIE|LOG_CONFIG, - "no driver defined for %s \"%s\"", class, d->name); - (d->info->init)(d); - } + driver_init_fini(d, class); } @@ -4264,7 +4257,7 @@ while(acl_line) if (*p != ':' || name[0] == 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "missing or malformed ACL name"); - node = store_get(sizeof(tree_node) + Ustrlen(name), is_tainted(name)); + node = store_get_perm(sizeof(tree_node) + Ustrlen(name), is_tainted(name)); Ustrcpy(node->name, name); if (!tree_insertnode(&acl_anchor, node)) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, @@ -4426,25 +4419,28 @@ print_config(BOOL admin, BOOL terse) { const int TS = terse ? 0 : 2; int indent = 0; +rmark r = NULL; -for (config_line_item * i = config_lines; i; i = i->next) +for (const config_line_item * i = config_lines; i; i = i->next) { - uschar *current; - uschar *p; + uschar * current, * p; + + if (r) store_reset(r); + r = store_mark(); /* skip over to the first non-space */ - for (current = i->line; *current && isspace(*current); ++current) + for (current = string_copy(i->line); *current && isspace(*current); ++current) ; - if (*current == '\0') + if (!*current) 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) + following characters: "'$, as this may indicate careful formatting */ + + for (p = current; *p; p++) if (isspace(*p)) { uschar *next; - if (!isspace(*p)) continue; if (*p != ' ') *p = ' '; for (next = p; isspace(*next); ++next) @@ -4498,6 +4494,7 @@ for (config_line_item * i = config_lines; i; i = i->next) /* rest is public */ printf("%*s%s\n", indent, "", current); } +if (r) store_reset(r); } #endif /*!MACRO_PREDEF*/ diff --git a/src/src/route.c b/src/src/route.c index 1d87b079a..5aed06b4f 100644 --- a/src/src/route.c +++ b/src/src/route.c @@ -288,7 +288,12 @@ for (router_instance * r = routers; r; r = r->next) /* Build a host list if fallback hosts is set. */ - host_build_hostlist(&(r->fallback_hostlist), r->fallback_hosts, FALSE); + { + int old_pool = store_pool; + store_pool = POOL_PERM; + host_build_hostlist(&r->fallback_hostlist, r->fallback_hosts, FALSE); + store_pool = old_pool; + } /* Check redirect_router and pass_router are valid */ diff --git a/src/src/routers/manualroute.c b/src/src/routers/manualroute.c index 471b38566..01802714f 100644 --- a/src/src/routers/manualroute.c +++ b/src/src/routers/manualroute.c @@ -259,7 +259,7 @@ if (ob->route_list) int sep = -(';'); /* Default is semicolon */ listptr = ob->route_list; - while ((route_item = string_nextinlist(&listptr, &sep, NULL, 0)) != NULL) + while ((route_item = string_nextinlist(&listptr, &sep, NULL, 0))) { int rc; @@ -468,7 +468,7 @@ if (!addr->host_list) defined for these hosts. It will be a remote one, as a local transport is dealt with above. However, we don't need one if verifying only. */ -if (transport == NULL && verify == v_none) +if (!transport && verify == v_none) { log_write(0, LOG_MAIN, "Error in %s router: no transport defined", rblock->name); diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 1d7b22254..78c86e7c9 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -3015,7 +3015,7 @@ else p = s + Ustrlen(s); while (p > s && isspace(p[-1])) p--; -*p = 0; +s = string_copyn(s, p-s); /* It seems that CC:Mail is braindead, and assumes that the greeting message is all contained in a single IP packet. The original code wrote out the diff --git a/src/src/store.c b/src/src/store.c index 2a32e9b5c..ad4da3c2e 100644 --- a/src/src/store.c +++ b/src/src/store.c @@ -41,6 +41,9 @@ The following different types of store are recognized: a single message transaction but needed for longer than the use of the main pool permits. Currently this means only receive-time DKIM information. +- There is a dedicated pool for configuration data read from the config file(s). + Once complete, it is made readonly. + . Orthogonal to the three pool types, there are two classes of memory: untainted and tainted. The latter is used for values derived from untrusted input, and the string-expansion mechanism refuses to operate on such values (obviously, @@ -165,21 +168,24 @@ static int max_nonpool_malloc; /* max value for nonpool_malloc */ static const uschar * pooluse[NPOOLS] = { [POOL_MAIN] = US"main", [POOL_PERM] = US"perm", +[POOL_CONFIG] = US"config", [POOL_SEARCH] = US"search", [POOL_MESSAGE] = US"message", [POOL_TAINT_MAIN] = US"main", [POOL_TAINT_PERM] = US"perm", -[POOL_TAINT_SEARCH] = US"search", +[POOL_TAINT_CONFIG] = US"config", [POOL_TAINT_SEARCH] = US"search", [POOL_TAINT_MESSAGE] = US"message", }; static const uschar * poolclass[NPOOLS] = { [POOL_MAIN] = US"untainted", [POOL_PERM] = US"untainted", +[POOL_CONFIG] = US"untainted", [POOL_SEARCH] = US"untainted", [POOL_MESSAGE] = US"untainted", [POOL_TAINT_MAIN] = US"tainted", [POOL_TAINT_PERM] = US"tainted", +[POOL_TAINT_CONFIG] = US"tainted", [POOL_TAINT_SEARCH] = US"tainted", [POOL_TAINT_MESSAGE] = US"tainted", }; @@ -245,6 +251,22 @@ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Taint mismatch, %s: %s %d\n", +/******************************************************************************/ +void +store_writeprotect(int pool) +{ +for (storeblock * b = chainbase[pool]; b; b = b->next) + { +#ifndef COMPILE_UTILITY + if (mprotect(b, ALIGNED_SIZEOF_STOREBLOCK + b->length, PROT_READ) != 0) + DEBUG(D_any) debug_printf("config block mprotect: (%d) %s\n", errno, strerror(errno)) +#endif + ; + } +} + +/******************************************************************************/ + /************************************************* * Get a block from the current pool * *************************************************/ @@ -324,7 +346,13 @@ if (size > yield_length[pool]) if (++nblocks[pool] > maxblocks[pool]) maxblocks[pool] = nblocks[pool]; - newblock = internal_store_malloc(mlength, func, linenumber); + if (pool == POOL_CONFIG) + { + long pgsize = sysconf(_SC_PAGESIZE); + posix_memalign((void **)&newblock, pgsize, (mlength + pgsize - 1) & ~(pgsize - 1)); + } + else + newblock = internal_store_malloc(mlength, func, linenumber); newblock->next = NULL; newblock->length = length; #ifndef RESTRICTED_MEMORY @@ -483,7 +511,8 @@ not call with a pointer returned by store_get(). Both the untainted and tainted pools corresposding to store_pool are reset. Arguments: - r place to back up to + ptr place to back up to + pool pool holding the pointer func function from which called linenumber line number in source file @@ -561,7 +590,8 @@ if ( yield_length[pool] < STOREPOOL_MIN_SIZE } bb = b->next; -b->next = NULL; +if (pool != POOL_CONFIG) + b->next = NULL; while ((b = bb)) { @@ -576,7 +606,8 @@ while ((b = bb)) nbytes[pool] -= siz; pool_malloc -= siz; nblocks[pool]--; - internal_store_free(b, func, linenumber); + if (pool != POOL_CONFIG) + internal_store_free(b, func, linenumber); #ifndef RESTRICTED_MEMORY if (store_block_order[pool] > 13) store_block_order[pool]--; diff --git a/src/src/store.h b/src/src/store.h index 92deabf9b..58561ac28 100644 --- a/src/src/store.h +++ b/src/src/store.h @@ -13,9 +13,10 @@ /* Define symbols for identifying the store pools. */ -#define NPOOLS 8 +#define NPOOLS 10 enum { POOL_MAIN, POOL_PERM, + POOL_CONFIG, POOL_SEARCH, POOL_MESSAGE, @@ -23,6 +24,7 @@ enum { POOL_MAIN, POOL_TAINT_MAIN = POOL_TAINT_BASE, POOL_TAINT_PERM, + POOL_TAINT_CONFIG, POOL_TAINT_SEARCH, POOL_TAINT_MESSAGE }; diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 9692033f0..4636f3c32 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -4529,7 +4529,6 @@ tls_openssl_options_parse(uschar *option_spec, long *results) { long result, item; uschar * exp, * end; -uschar keep_c; BOOL adding, item_parsed; /* Server: send no (<= TLS1.2) session tickets */ @@ -4571,11 +4570,8 @@ for (uschar * s = exp; *s; /**/) return FALSE; } adding = *s++ == '+'; - for (end = s; (*end != '\0') && !isspace(*end); ++end) /**/ ; - keep_c = *end; - *end = '\0'; - item_parsed = tls_openssl_one_option_parse(s, &item); - *end = keep_c; + for (end = s; *end && !isspace(*end); ) end++; + item_parsed = tls_openssl_one_option_parse(string_copyn(s, end-s), &item); if (!item_parsed) { DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s); diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c index 5d957b62e..2d008d97d 100644 --- a/src/src/transports/appendfile.c +++ b/src/src/transports/appendfile.c @@ -1368,8 +1368,8 @@ if (!isdirectory) if (ob->create_directory && allow_creation_here) { uschar *p = Ustrrchr(path, '/'); - *p = '\0'; - if (!directory_make(NULL, path, ob->dirmode, FALSE)) + p = string_copyn(path, p - path); + if (!directory_make(NULL, p, ob->dirmode, FALSE)) { addr->basic_errno = errno; addr->message = @@ -1378,7 +1378,6 @@ if (!isdirectory) DEBUG(D_transport) debug_printf("%s transport: %s\n", tblock->name, path); return FALSE; } - *p = '/'; } /* If file_format is set we must check that any existing file matches one of diff --git a/src/src/transports/autoreply.c b/src/src/transports/autoreply.c index 80c7c0db0..864257b46 100644 --- a/src/src/transports/autoreply.c +++ b/src/src/transports/autoreply.c @@ -175,18 +175,21 @@ return ss; list. Any that are found are removed. Arguments: - listptr points to the list of addresses + list list of addresses to be checked never_mail an address list, already expanded -Returns: nothing +Returns: edited replacement address list, or NULL, or original */ -static void -check_never_mail(uschar **listptr, const uschar *never_mail) +static uschar * +check_never_mail(uschar * list, const uschar * never_mail) { -uschar *s = *listptr; +rmark reset_point = store_mark(); +uschar * newlist = string_copy(list); +uschar * s = newlist; +BOOL hit = FALSE; -while (*s != 0) +while (*s) { uschar *error, *next; uschar *e = parse_find_address_end(s, FALSE); @@ -220,6 +223,7 @@ while (*s != 0) { DEBUG(D_transport) debug_printf("discarding recipient %s (matched never_mail)\n", next); + hit = TRUE; if (terminator == ',') e++; memmove(s, e, Ustrlen(e) + 1); } @@ -230,18 +234,31 @@ while (*s != 0) } } +/* If no addresses were removed, retrieve the memory used and return +the original. */ + +if (!hit) + { + store_reset(reset_point); + return list; + } + /* Check to see if we removed the last address, leaving a terminating comma that needs to be removed */ -s = *listptr + Ustrlen(*listptr); -while (s > *listptr && (isspace(s[-1]) || s[-1] == ',')) s--; +s = newlist + Ustrlen(newlist); +while (s > newlist && (isspace(s[-1]) || s[-1] == ',')) s--; *s = 0; -/* Check to see if there any addresses left; if not, set NULL */ +/* Check to see if there any addresses left; if not, return NULL */ + +s = newlist; +while (s && isspace(*s)) s++; +if (*s) + return newlist; -s = *listptr; -while (s != 0 && isspace(*s)) s++; -if (*s == 0) *listptr = NULL; +store_reset(reset_point); +return NULL; } @@ -372,9 +389,9 @@ if (ob->never_mail) return FALSE; } - if (to) check_never_mail(&to, never_mail); - if (cc) check_never_mail(&cc, never_mail); - if (bcc) check_never_mail(&bcc, never_mail); + if (to) to = check_never_mail(to, never_mail); + if (cc) cc = check_never_mail(cc, never_mail); + if (bcc) bcc = check_never_mail(bcc, never_mail); if (!to && !cc && !bcc) { diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index e2b2250ad..dfb4a9284 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -355,6 +355,7 @@ void smtp_transport_init(transport_instance *tblock) { smtp_transport_options_block *ob = SOB tblock->options_block; +int old_pool = store_pool; /* Retry_use_local_part defaults FALSE if unset */ @@ -390,7 +391,9 @@ if (ob->hosts_override && ob->hosts) tblock->overrides_hosts = TRUE; /* If there are any fallback hosts listed, build a chain of host items for them, but do not do any lookups at this time. */ -host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE); +store_pool = POOL_PERM; +host_build_hostlist(&ob->fallback_hostlist, ob->fallback_hosts, FALSE); +store_pool = old_pool; } diff --git a/test/log/0224 b/test/log/0224 index a03b4cd9b..317f22cb6 100644 --- a/test/log/0224 +++ b/test/log/0224 @@ -8,11 +8,11 @@ 1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@test.ex U=CALLER P=local S=sss 1999-03-02 09:44:33 10HmaZ-0005vi-00 == hdefer@test.ex R=halias defer (-1): not just yet 1999-03-02 09:44:33 10HmaZ-0005vi-00 == defer@test.ex R=alias defer (-1): not just yet -1999-03-02 09:44:33 10HmaZ-0005vi-00 == /no/such/file R=alias T=address_file defer (EEE): Permission denied: failed to create directories for /no/such: Permission denied +1999-03-02 09:44:33 10HmaZ-0005vi-00 == /no/such/file R=alias T=address_file defer (EEE): Permission denied: failed to create directories for /no/such/file: Permission denied 1999-03-02 09:44:33 Start queue run: pid=pppp -qf 1999-03-02 09:44:33 10HmaZ-0005vi-00 == hdefer@test.ex R=halias defer (-1): not just yet 1999-03-02 09:44:33 10HmaZ-0005vi-00 == defer@test.ex R=alias defer (-1): not just yet -1999-03-02 09:44:33 10HmaZ-0005vi-00 == /no/such/file R=alias T=address_file defer (EEE): Permission denied: failed to create directories for /no/such: Permission denied +1999-03-02 09:44:33 10HmaZ-0005vi-00 == /no/such/file R=alias T=address_file defer (EEE): Permission denied: failed to create directories for /no/such/file: Permission denied 1999-03-02 09:44:33 10HmbA-0005vi-00 <= <> R=10HmaZ-0005vi-00 U=EXIMUSER P=local S=sss 1999-03-02 09:44:33 10HmbA-0005vi-00 => CALLER R=localuser T=local_delivery 1999-03-02 09:44:33 10HmbA-0005vi-00 Completed