Allow relative file names in .include lines (Closes 1971)
authorHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Fri, 9 Dec 2016 23:15:47 +0000 (00:15 +0100)
committerHeiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
Mon, 19 Dec 2016 22:30:08 +0000 (23:30 +0100)
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/0903 [new file with mode: 0644]

index 0dc1bd8639a18199353c48afb18832e145bcab67..0ab13c70e8eb2828634d38373d593c0123ad42c4 100644 (file)
@@ -4945,6 +4945,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 9c3f1a4bc86bf7d8d22d5f7c8513382cf455de08..46dc7ced2939b33158dbd5b569616cd07b479e93 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;
@@ -941,6 +944,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)
@@ -1163,9 +1167,18 @@ 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);
+        }
 
     if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
 
 
     if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
 
@@ -1176,6 +1189,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")))
@@ -1183,6 +1197,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;
     }
@@ -3353,15 +3368,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
@@ -3369,12 +3375,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
   {
@@ -3386,6 +3421,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..6005468
--- /dev/null
@@ -0,0 +1 @@
+.include 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
diff --git a/test/scripts/0000-Basic/0903 b/test/scripts/0000-Basic/0903
new file mode 100644 (file)
index 0000000..a29e949
--- /dev/null
@@ -0,0 +1,2 @@
+# Test different variants of .includes
+exim