Re: [Exim] create_file = belowhome: unexpected behaviour

Top Page
Delete this message
Reply to this message
Author: Philip Hazel
Date:  
To: Ray Miller
CC: exim-users
Subject: Re: [Exim] create_file = belowhome: unexpected behaviour
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