On Fri, 1 Oct 1999, Philip Hazel wrote:
> Here's a patch for Exim 3.03 that fixes this problem. Please don't get
> to rely on such fast service. :-)
Here's another patch which fixes the problem in a better way, also
picking up another problem I had overlooked (when not to create
containing directories - and a bug fix when you do), and uses realpath()
to attempt to catch people playing games with symbolic links.
All the OS I have looked at have realpath(), but I have put an #ifdef
round it so it can be cut out if necessary.
--
Philip Hazel University of Cambridge Computing Service,
ph10@??? Cambridge, England. Phone: +44 1223 334714.
*** ../Releases/exim-3.03/src/transports/appendfile.c Mon Aug 2 16:43:08 1999
--- transports/appendfile.c Tue Oct 5 15:27:42 1999
***************
*** 981,995 ****
dataname = filename = path;
/* If ob->create_directory is set, attempt to create the directories in
! which this mailbox lives. We know we are dealing with an absolute path,
! because this has been checked above. */
! if (ob->create_directory)
{
char *p = strrchr(path, '/');
*p = '\0';
! if (directory_make("/", path, ob->dirmode, FALSE) < 0)
{
addr->basic_errno = errno;
addr->message =
--- 1081,1163 ----
dataname = filename = path;
+ /* If file creation is permitted in certain directories only, check that
+ this is actually the case and unset the flag if it is not. The actual
+ triggering of the error happens only if we find that the file does not in
+ fact exist. Current checks are for in or below the home directory. */
+
+ if (ob->create_file != create_anywhere)
+ {
+ if (deliver_home != NULL)
+ {
+ int len = (int)strlen(deliver_home);
+ char *file = filename;
+
+ while (file[0] == '/' && file[1] == '/') file++;
+ if (strncmp(file, deliver_home, len) != 0 || file[len] != '/' ||
+ ( strchr(file+len+2, '/') != NULL &&
+ (
+ ob->create_file != create_belowhome ||
+ strstr(file+len, "/../") != NULL
+ )
+ )
+ ) allow_creation_here = FALSE;
+
+ /* If allow_creation_here is TRUE, the file name starts with the home
+ directory, and does not contain any instances of "/../" in the
+ "belowhome" case. However, it may still contain symbolic links. We can
+ check for this by making use of realpath(), which most Unixes seem to
+ have (but make it possible to cut this out). We can't just use realpath()
+ on the whole file name, because we know the file itself doesn't exist,
+ and intermediate directories may also not exist. What we want to know is
+ the real path of the longest existing part of the path. That must match
+ the home directory's beginning, which ever is the shorter. */
+
+ #ifndef NO_REALPATH
+ if (allow_creation_here && ob->create_file == create_belowhome)
+ {
+ char *slash, *next;
+ char *rp = NULL;
+ for (slash = strrchr(file, '/'); /* There is known to be one */
+ rp == NULL && slash > file; /* Stop if reached beginning */
+ slash = next)
+ {
+ *slash = 0;
+ rp = realpath(file, big_buffer);
+ next = strrchr(file, '/');
+ *slash = '/';
+ }
+
+ /* If rp == NULL it means that none of the relevant directories exist.
+ This is not a problem here - it means that no symbolic links can exist,
+ which is all we are worried about. */
+
+ if (rp != NULL)
+ {
+ int rlen = (int)strlen(big_buffer);
+ if (rlen > len) rlen = len;
+ if (strncmp(deliver_home, big_buffer, rlen) != 0)
+ {
+ allow_creation_here = FALSE;
+ DEBUG(9) debug_printf("Real path \"%s\" does not match \"%s\"\n",
+ big_buffer, deliver_home);
+ }
+ }
+ }
+ #endif
+ }
+ }
+
/* If ob->create_directory is set, attempt to create the directories in
! which this mailbox lives, but only if we are permitted to create the file
! itself. We know we are dealing with an absolute path, because this has been
! checked above. */
! if (ob->create_directory && allow_creation_here)
{
char *p = strrchr(path, '/');
*p = '\0';
! if (!directory_make("/", path, ob->dirmode, FALSE))
{
addr->basic_errno = errno;
addr->message =
***************
*** 1227,1258 ****
goto RETURN;
}
! /* If file creation is permitted in certain directories only, check that
! this is actually the case. Current checks are for in or below the
! home directory. */
! if (ob->create_file != create_anywhere)
{
! BOOL OK = FALSE;
! if (deliver_home != NULL)
! {
! int len = (int)strlen(deliver_home);
! char *file = filename;
! while (file[0] == '/' && file[1] == '/') file++;
! if (strncmp(file, deliver_home, len) == 0 && file[len] == '/' &&
! (ob->create_file == create_belowhome ||
! strchr(file+len+2, '/') == NULL)) OK = TRUE;
! }
!
! if (!OK)
! {
! addr->basic_errno = errno;
! addr->message = string_sprintf("mailbox %s does not exist, "
! "but creation outside the home directory is not permitted",
! filename);
! addr->special_action = SPECIAL_FREEZE;
! goto RETURN;
! }
}
/* Attempt to create and open the file. If open fails because of
--- 1429,1445 ----
goto RETURN;
}
! /* If not permitted to create this file because it isn't in or below
! the home directory, generate an error. */
! if (!allow_creation_here)
{
! addr->basic_errno = ERRNO_BADCREATE;
! addr->message = string_sprintf("mailbox %s does not exist, "
! "but creation outside the home directory is not permitted",
! filename);
! addr->special_action = SPECIAL_FREEZE;
! goto RETURN;
}
/* Attempt to create and open the file. If open fails because of