Allow relative file names in .include lines (Closes 1971)
authorHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Fri, 9 Dec 2016 23:15:47 +0000 (23:15 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Thu, 29 Dec 2016 19:38:42 +0000 (19:38 +0000)
doc/doc-docbook/spec.xfpt
src/src/readconf.c
test/confs/0903 [new file with mode: 0644]
test/confs/0903./aaa [new file with mode: 0644]
test/confs/0903./bbb [new file with mode: 0644]
test/scripts/0000-Basic/0290
test/scripts/0000-Basic/0903 [new file with mode: 0644]
test/stdout/0903 [new file with mode: 0644]

index 835893d1b1286e3f3bc1018a10c82a3c63ab1654..02ffec9d1641ec18f1398cce6dfa3235955c8f12 100644 (file)
@@ -4937,6 +4937,11 @@ Include processing happens after macro processing (see below). Its effect is to
 process the lines of the included file as if they occurred inline where the
 inclusion appears.
 
 process the lines of the included file as if they occurred inline where the
 inclusion appears.
 
+Relative names are allowed with &`.include`&, and are resolved
+relative to the directory of the including file. For security reasons
+this is not allowed with &`.include_if_exists`&. To avoid confusion, it
+is strongly recommended to use absolute names only.
+
 
 
 .section "Macros in the configuration file" "SECTmacrodefs"
 
 
 .section "Macros in the configuration file" "SECTmacrodefs"
index 858496b6be89c40becf3f04a9d6adddf59a581f3..c5bd41d4798da5ff17603a03159051706c17112a 100644 (file)
@@ -22,12 +22,15 @@ static void readconf_options_auths(void);
 
 #define CSTATE_STACK_SIZE 10
 
 
 #define CSTATE_STACK_SIZE 10
 
+const uschar *config_directory = NULL;
+
 
 /* Structure for chain (stack) of .included files */
 
 typedef struct config_file_item {
   struct config_file_item *next;
   const uschar *filename;
 
 /* Structure for chain (stack) of .included files */
 
 typedef struct config_file_item {
   struct config_file_item *next;
   const uschar *filename;
+  const uschar *directory;
   FILE *file;
   int lineno;
 } config_file_item;
   FILE *file;
   int lineno;
 } config_file_item;
@@ -944,6 +947,7 @@ for (;;)
       (void)fclose(config_file);
       config_file = config_file_stack->file;
       config_filename = config_file_stack->filename;
       (void)fclose(config_file);
       config_file = config_file_stack->file;
       config_filename = config_file_stack->filename;
+      config_directory = config_file_stack->directory;
       config_lineno = config_file_stack->lineno;
       config_file_stack = config_file_stack->next;
       if (config_lines)
       config_lineno = config_file_stack->lineno;
       config_file_stack = config_file_stack->next;
       if (config_lines)
@@ -1166,9 +1170,19 @@ for (;;)
       }
     *t = 0;
 
       }
     *t = 0;
 
+    /* We allow relative file names. For security reasons currently
+    relative names not allowed with .include_if_exists. For .include_if_exists
+    we need to check the permissions/ownership of the containing folder */
     if (*ss != '/')
     if (*ss != '/')
-      log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, ".include specifies a non-"
-        "absolute path \"%s\"", ss);
+      if (include_if_exists) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, ".include specifies a non-"
+          "absolute path \"%s\"", ss);
+      else
+        {
+        int offset = 0;
+        int size = 0;
+        ss = string_append(NULL, &size, &offset, 3, config_directory, "/", ss);
+        ss[offset] = '\0';  /* string_append() does not zero terminate the string! */
+        }
 
     if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
 
 
     if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
 
@@ -1179,6 +1193,7 @@ for (;;)
     config_file_stack = save;
     save->file = config_file;
     save->filename = config_filename;
     config_file_stack = save;
     save->file = config_file;
     save->filename = config_filename;
+    save->directory = config_directory;
     save->lineno = config_lineno;
 
     if (!(config_file = Ufopen(ss, "rb")))
     save->lineno = config_lineno;
 
     if (!(config_file = Ufopen(ss, "rb")))
@@ -1186,6 +1201,7 @@ for (;;)
         "configuration file %s", ss);
 
     config_filename = string_copy(ss);
         "configuration file %s", ss);
 
     config_filename = string_copy(ss);
+    config_directory = string_copyn(ss, (const uschar*) strrchr(ss, '/') - ss);
     config_lineno = 0;
     continue;
     }
     config_lineno = 0;
     continue;
     }
@@ -3356,15 +3372,6 @@ while((filename = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
   if (config_file != NULL || errno != ENOENT) break;
   }
 
   if (config_file != NULL || errno != ENOENT) break;
   }
 
-/* Now, once we found and opened our configuration file, we change the directory
-to a safe place. Later we change to $spool_directory. */
-
-if (Uchdir("/") < 0)
-  {
-  perror("exim: chdir `/': ");
-  exit(EXIT_FAILURE);
-  }
-
 /* On success, save the name for verification; config_filename is used when
 logging configuration errors (it changes for .included files) whereas
 config_main_filename is the name shown by -bP. Failure to open a configuration
 /* On success, save the name for verification; config_filename is used when
 logging configuration errors (it changes for .included files) whereas
 config_main_filename is the name shown by -bP. Failure to open a configuration
@@ -3372,12 +3379,41 @@ file is a serious disaster. */
 
 if (config_file != NULL)
   {
 
 if (config_file != NULL)
   {
-  uschar *p;
+  uschar *slash = Ustrrchr(filename, '/');
   config_filename = config_main_filename = string_copy(filename);
 
   config_filename = config_main_filename = string_copy(filename);
 
-  p = Ustrrchr(filename, '/');
-  config_main_directory = p ? string_copyn(filename, p - filename)
-                            : string_copy(US".");
+  /* the config_main_directory we need for the $config_dir expansion.
+  And config_dir is the directory of the current configuration, used for
+  relative .includes. We do need to know it's name, as we change our working
+  directory later. */
+
+  if (filename[0] == '/')
+    config_main_directory = slash > filename ? string_copyn(filename, slash - filename) : US"/";
+  else
+    {
+      /* relative configuration file name: working dir + / + basename(filename) */
+
+      char buf[PATH_MAX];
+      int offset = 0;
+      int size = 0;
+      const uschar *p = Ustrrchr(filename, '/');
+
+      if (getcwd(buf, PATH_MAX) == NULL)
+        {
+        perror("exim: getcwd");
+        exit(EXIT_FAILURE);
+        }
+      config_main_directory = string_cat(NULL, &size, &offset, buf);
+
+      /* If the dir does not end with a "/", append one */
+      if (config_main_directory[offset-1] != '/')
+        string_cat(config_main_directory, &size, &offset, US"/");
+
+      /* If the config file contains a "/", extract the directory part */
+      if (p)
+        string_catn(config_main_directory, &size, &offset, filename, p - filename);
+    }
+  config_directory = config_main_directory;
   }
 else
   {
   }
 else
   {
@@ -3389,6 +3425,15 @@ else
       "configuration file %s", filename));
   }
 
       "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. */
+
+if (Uchdir("/") < 0)
+  {
+  perror("exim: chdir `/': ");
+  exit(EXIT_FAILURE);
+  }
+
 /* Check the status of the file we have opened, if we have retained root
 privileges and the file isn't /dev/null (which *should* be 0666). */
 
 /* Check the status of the file we have opened, if we have retained root
 privileges and the file isn't /dev/null (which *should* be 0666). */
 
diff --git a/test/confs/0903 b/test/confs/0903
new file mode 100644 (file)
index 0000000..017424e
--- /dev/null
@@ -0,0 +1 @@
+.include confs/0903./aaa
diff --git a/test/confs/0903./aaa b/test/confs/0903./aaa
new file mode 100644 (file)
index 0000000..746f316
--- /dev/null
@@ -0,0 +1 @@
+.include bbb
diff --git a/test/confs/0903./bbb b/test/confs/0903./bbb
new file mode 100644 (file)
index 0000000..e69de29
index f4b63aa8ed03ec7a176144d5c1b13082858e052d..204fa1e1b3fe0b70f7143680762075b317b02dce 100644 (file)
@@ -8,5 +8,5 @@ exim -DOPT -bP receive_timeout
 exim '-D OPT = receive_timeout = 4s ' -bP receive_timeout
 ****
 1
 exim '-D OPT = receive_timeout = 4s ' -bP receive_timeout
 ****
 1
-exim -DINC='.include non/absolute' -bP receive_timeout
+exim -DINC='.include_if_exists non/absolute' -bP receive_timeout
 ****
 ****
diff --git a/test/scripts/0000-Basic/0903 b/test/scripts/0000-Basic/0903
new file mode 100644 (file)
index 0000000..1bd510a
--- /dev/null
@@ -0,0 +1,2 @@
+# Test different variants of .includes
+exim -bP config
diff --git a/test/stdout/0903 b/test/stdout/0903
new file mode 100644 (file)
index 0000000..a7bda45
--- /dev/null
@@ -0,0 +1,7 @@
+# Exim Configuration (X)
+# 1 "TESTSUITE/test-config"
+# 1 "TESTSUITE/test-config"
+# 1 "TESTSUITE/confs/0903./aaa"
+# 1 "TESTSUITE/confs/0903./aaa"
+# 1 "TESTSUITE/confs/0903./aaa"
+# 1 "TESTSUITE/test-config"