Merge branch 'readonly_config'
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 27 Jun 2021 23:35:57 +0000 (00:35 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 27 Jun 2021 23:35:57 +0000 (00:35 +0100)
17 files changed:
doc/doc-txt/ChangeLog
src/src/auths/gsasl_exim.c
src/src/deliver.c
src/src/exim.c
src/src/functions.h
src/src/parse.c
src/src/readconf.c
src/src/route.c
src/src/routers/manualroute.c
src/src/smtp_in.c
src/src/store.c
src/src/store.h
src/src/tls-openssl.c
src/src/transports/appendfile.c
src/src/transports/autoreply.c
src/src/transports/smtp.c
test/log/0224

index 90645e005e712bb23095f8663d6280a6d3227de8..3311ae8f5cfa9272afafd8ec9cca79d011cf36cb 100644 (file)
@@ -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
 -----------------
index 7f9cc32957c072ca30f518a1a5d728aa90692f19..479d01a29daa1a9a4cd0996f528ad3240e2cc877 100644 (file)
@@ -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;
 
index f9f6746436ca0529ecb3194ee1b99dab85700670..e931d22e92e975d30ad5bcc30b6e9e1087bcb7ef 100644 (file)
@@ -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 */
         }
 
index 6a9a1477f21e5774fe315050eab5e0373ce00ef3..a42c48b2ca81e488cf556ac54353b7a39c0c3a61 100644 (file)
@@ -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
index e349721706a0c2f65ab97ccd2f293227e9f1be33..0744697f9d47ab2c47cf80d0ca25073b74afa11e 100644 (file)
@@ -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;
index 896d00f30bdfe41b74eb1df5f3d77f734e792030..58f8941990403e6602a6179d18d5995b41fd3ab1 100644 (file)
@@ -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++;
index 34ebf87690777ce927f7a0ff37bc6f5e3c77e09d..01e85a329f9a1b2265c2b22f35e394bf6c7a90e8 100644 (file)
@@ -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*/
index 1d87b079a7b3c6a2ecd552092581a5c71618205d..5aed06b4f4fbc2f9b1dbf0563d300fad581c0e7c 100644 (file)
@@ -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 */
 
index 471b3856633d4c71b7ab01ea421c843553714d25..01802714ffffa5188fb660904ed1d4eccdab993b 100644 (file)
@@ -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);
index 1d7b222547485f2486d78405bbf776203f78b103..78c86e7c9bda109717709a1125c32f72a26990a5 100644 (file)
@@ -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
index 2a32e9b5c40caa50cf7743ce8328ab29d918ff84..ad4da3c2e5faf4a794f33c7b6d2972f7be2b2745 100644 (file)
@@ -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]--;
index 92deabf9bbe0e228a4a220f85042ac4ebd9c641c..58561ac2832e6e87344489b55a139cc5b0463eb3 100644 (file)
 
 /* 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 };
 
index 9692033f0910e4ec185368938076fd58324c41f3..4636f3c320ff44ff669d18258f4175d8b965c70e 100644 (file)
@@ -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);
index 5d957b62e0b833358abc6ec608a496d2df3a5b1c..2d008d97d7915db65dd5fe6b7211e8e9478a892c 100644 (file)
@@ -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
index 80c7c0db0c655306709facc766c9070b5dabadcf..864257b46792e06b429bbb53c99b70d09e08bc94 100644 (file)
@@ -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)
     {
index e2b2250adcb6b392c1fded04489f5bd8c64452be..dfb4a928492ddcf71aabf84e59e3efc428492365 100644 (file)
@@ -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;
 }
 
 
index a03b4cd9b79b316cec64ad1c018fd0c019c103bf..317f22cb6e2804c1a1c9aa0c365cb7b90d9ce923 100644 (file)
@@ -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 <useryy@test.ex> R=halias defer (-1): not just yet
 1999-03-02 09:44:33 10HmaZ-0005vi-00 == defer@test.ex <userxy@test.ex> R=alias defer (-1): not just yet
-1999-03-02 09:44:33 10HmaZ-0005vi-00 == /no/such/file <file@test.ex> 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 <file@test.ex> 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 <useryy@test.ex> R=halias defer (-1): not just yet
 1999-03-02 09:44:33 10HmaZ-0005vi-00 == defer@test.ex <userxy@test.ex> R=alias defer (-1): not just yet
-1999-03-02 09:44:33 10HmaZ-0005vi-00 == /no/such/file <file@test.ex> 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 <file@test.ex> 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 <CALLER@test.ex> R=localuser T=local_delivery
 1999-03-02 09:44:33 10HmbA-0005vi-00 Completed