]> source.dussan.org Git - gitblit.git/commitdiff
Fix set-gid bit clearing under Linux when effective gid is different from file gid. 112/head
authorFlorian Zschocke <florian.zschocke@cycos.com>
Wed, 21 Aug 2013 08:39:43 +0000 (10:39 +0200)
committerFlorian Zschocke <florian.zschocke@cycos.com>
Mon, 26 Aug 2013 10:39:57 +0000 (12:39 +0200)
src/main/java/com/gitblit/utils/JGitUtils.java
src/main/java/com/gitblit/utils/JnaUtils.java
src/test/java/com/gitblit/tests/JGitUtilsTest.java
src/test/java/com/gitblit/tests/JnaUtilsTest.java

index 66dbd60d794af0587e04a702169e3dc3cc55b99d..2e448c36a65757b7567a6f94c30aa68d21246527 100644 (file)
@@ -403,9 +403,23 @@ public class JGitUtils {
                if (! path.exists()) return -1;\r
 \r
                int perm = configShared.getPerm();\r
-               int mode = JnaUtils.getFilemode(path);\r
+               JnaUtils.Filestat stat = JnaUtils.getFilestat(path);\r
+               if (stat == null) return -1;\r
+               int mode = stat.mode;\r
                if (mode < 0) return -1;\r
 \r
+               // Now, here is the kicker: Under Linux, chmod'ing a sgid file whose guid is different from the process'\r
+               // effective guid will reset the sgid flag of the file. Since there is no way to get the sgid flag back in\r
+               // that case, we decide to rather not touch is and getting the right permissions will have to be achieved\r
+               // in a different way, e.g. by using an appropriate umask for the Gitblit process.\r
+               if (System.getProperty("os.name").toLowerCase().startsWith("linux")) {\r
+                       if ( ((mode & (JnaUtils.S_ISGID | JnaUtils.S_ISUID)) != 0)\r
+                               && stat.gid != JnaUtils.getegid() ) {\r
+                               LOGGER.debug("Not adjusting permissions to prevent clearing suid/sgid bits for '" + path + "'" );\r
+                               return 0;\r
+                       }\r
+               }\r
+\r
                // If the owner has no write access, delete it from group and other, too.\r
                if ((mode & JnaUtils.S_IWUSR) == 0) perm &= ~0222;\r
                // If the owner has execute access, set it for all blocks that have read access.\r
index 3bf1f73aaf9c233f2ac061fbc298568988197bc0..4009342a07a5d38aea996e2ed75e4f9e7bd465b3 100644 (file)
@@ -79,6 +79,28 @@ public class JnaUtils {
 
        private interface UnixCLibrary extends Library {
                public int chmod(String path, int mode);
+               public int getgid();
+               public int getegid();
+       }
+
+
+       public static int getgid()
+       {
+               if (isWindows()) {
+                       throw new UnsupportedOperationException("The method JnaUtils.getgid is not supported under Windows.");
+               }
+
+               return getUnixCLibrary().getgid();
+       }
+
+
+       public static int getegid()
+       {
+               if (isWindows()) {
+                       throw new UnsupportedOperationException("The method JnaUtils.getegid is not supported under Windows.");
+               }
+
+               return getUnixCLibrary().getegid();
        }
 
 
@@ -157,21 +179,77 @@ public class JnaUtils {
                        throw new UnsupportedOperationException("The method JnaUtils.getFilemode is not supported under Windows.");
                }
 
+               Filestat stat = getFilestat(path);
+               if ( stat == null ) return -1;
+               return stat.mode;
+       }
+
+
+       /**
+        * Status information of a file.
+        */
+       public static class Filestat
+       {
+               public int mode;  // file mode, permissions, type
+               public int uid;   // user Id of owner
+               public int gid;   // group Id of owner
+
+               Filestat(int mode, int uid, int gid) {
+                       this.mode = mode; this.uid = uid; this.gid = gid;
+               }
+       }
+
+
+       /**
+        * Get Unix file status information for a file.
+        *
+        * This method is only implemented for OSes of the Unix family. It returns file status
+        * information for a file. Currently this is the file mode, the user id and group id of the owner.
+        *
+        * @param path
+        *                      File/directory to get the file status from.
+        * @return      Upon successful completion, a Filestat object containing the file information is returned.
+        *                      Otherwise, null is returned.
+        */
+       public static Filestat getFilestat(File path)
+       {
+               return getFilestat(path.getAbsolutePath());
+       }
+
+
+       /**
+        * Get Unix file status information for a file.
+        *
+        * This method is only implemented for OSes of the Unix family. It returns file status
+        * information for a file. Currently this is the file mode, the user id and group id of the owner.
+        *
+        * @param path
+        *                      Path to a file/directory to get the file status from.
+        * @return      Upon successful completion, a Filestat object containing the file information is returned.
+        *                      Otherwise, null is returned.
+        */
+       public static Filestat getFilestat(String path)
+       {
+               if (isWindows()) {
+                       throw new UnsupportedOperationException("The method JnaUtils.getFilestat is not supported under Windows.");
+               }
+
 
                int mode = 0;
 
                // Use a Runtime, because implementing stat() via JNA is just too much trouble.
+               // This could be done with the 'stat' command, too. But that may have a shell specific implementation, so we use 'ls' instead.
                String lsLine = runProcessLs(path);
                if (lsLine == null) {
                        LOGGER.debug("Could not get file information for path " + path);
-                       return -1;
+                       return null;
                }
 
-               Pattern p = Pattern.compile("^(([-bcdlsp])([-r][-w][-xSs])([-r][-w][-xSs])([-r][-w][-xTt])) ");
+               Pattern p = Pattern.compile("^(([-bcdlspCDMnP?])([-r][-w][-xSs])([-r][-w][-xSs])([-r][-w][-xTt]))[@+.]? +[0-9]+ +([0-9]+) +([0-9]+) ");
                Matcher m = p.matcher(lsLine);
                if ( !m.lookingAt() ) {
                        LOGGER.debug("Could not parse valid file mode information for path " + path);
-                       return -1;
+                       return null;
                }
 
                // Parse mode string to mode bits
@@ -227,12 +305,12 @@ public class JnaUtils {
                        }
                }
 
-               return mode;
+               return new Filestat(mode, Integer.parseInt(m.group(6)), Integer.parseInt(m.group(7)));
        }
 
 
        /**
-        * Run the unix command 'ls -ldO' on a single file and return the resulting output line.
+        * Run the unix command 'ls -ldn' on a single file and return the resulting output line.
         *
         * @param path
         *                      Path to a single file or directory.
@@ -240,7 +318,7 @@ public class JnaUtils {
         */
        private static String runProcessLs(String path)
        {
-               String cmd = "ls -ld " + path;
+               String cmd = "ls -ldn " + path;
                Runtime rt = Runtime.getRuntime();
                Process pr = null;
                InputStreamReader ir = null;
index cdf698d6da536b849246264e2227f763c1fd97ad..463c0a848578323fd6e95a98f4b9909264b33515 100644 (file)
@@ -218,6 +218,70 @@ public class JGitUtilsTest {
                }\r
        }\r
 \r
+       @Test\r
+       public void testCreateRepositorySharedSgidParent() throws Exception {\r
+               if (! JnaUtils.isWindows()) {\r
+                       String repositoryAll = "NewTestRepositoryAll.git";\r
+                       String repositoryUmask = "NewTestRepositoryUmask.git";\r
+                       String sgidParent = "sgid";\r
+                       \r
+                       File parent = new File(GitBlitSuite.REPOSITORIES, sgidParent);\r
+                       File folder = null;\r
+                       boolean parentExisted = parent.exists();\r
+                       try {\r
+                               if (!parentExisted) {\r
+                                       assertTrue("Could not create SGID parent folder.", parent.mkdir());\r
+                               }\r
+                               int mode = JnaUtils.getFilemode(parent);\r
+                               assertTrue(mode > 0);\r
+                               assertEquals(0, JnaUtils.setFilemode(parent, mode | JnaUtils.S_ISGID | JnaUtils.S_IWGRP));\r
+\r
+                               Repository repository = JGitUtils.createRepository(parent, repositoryAll, "all");\r
+                               folder = FileKey.resolve(new File(parent, repositoryAll), FS.DETECTED);\r
+                               assertNotNull(repository);\r
+               \r
+                               assertEquals("2", repository.getConfig().getString("core", null, "sharedRepository"));\r
+               \r
+                               assertTrue(folder.exists());\r
+                               mode = JnaUtils.getFilemode(folder);\r
+                               assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID);\r
+       \r
+                               mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/HEAD");\r
+                               assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG);\r
+                               assertEquals(JnaUtils.S_IROTH, mode & JnaUtils.S_IRWXO);\r
+       \r
+                               mode = JnaUtils.getFilemode(folder.getAbsolutePath() + "/config");\r
+                               assertEquals(JnaUtils.S_IRGRP | JnaUtils.S_IWGRP, mode & JnaUtils.S_IRWXG);\r
+                               assertEquals(JnaUtils.S_IROTH, mode & JnaUtils.S_IRWXO);\r
+       \r
+                               repository.close();\r
+                               RepositoryCache.close(repository);\r
+\r
+\r
+\r
+                               repository = JGitUtils.createRepository(parent, repositoryUmask, "umask");\r
+                               folder = FileKey.resolve(new File(parent, repositoryUmask), FS.DETECTED);\r
+                               assertNotNull(repository);\r
+               \r
+                               assertEquals(null, repository.getConfig().getString("core", null, "sharedRepository"));\r
+               \r
+                               assertTrue(folder.exists());\r
+                               mode = JnaUtils.getFilemode(folder);\r
+                               assertEquals(JnaUtils.S_ISGID, mode & JnaUtils.S_ISGID);\r
+       \r
+                               repository.close();\r
+                               RepositoryCache.close(repository);\r
+                       }\r
+                       finally {\r
+                               FileUtils.delete(new File(parent, repositoryAll), FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);\r
+                               FileUtils.delete(new File(parent, repositoryUmask), FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);\r
+                               if (!parentExisted) {\r
+                                       FileUtils.delete(parent, FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
        @Test\r
        public void testRefs() throws Exception {\r
                Repository repository = GitBlitSuite.getJGitRepository();\r
index 2430b6eaffc75ecfe94c501bbfa7cebb74d94fe9..25d1ccf5bb09e258a58a65030a39d6b8b8004424 100644 (file)
@@ -20,6 +20,7 @@ import com.gitblit.utils.JnaUtils;
 import java.io.File;
 import java.io.IOException;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import org.apache.commons.io.FileUtils;
@@ -35,6 +36,24 @@ import org.junit.Test;
  */
 public class JnaUtilsTest {
 
+       @Test
+       public void testGetgid() {
+               if (JnaUtils.isWindows()) {
+                       try {
+                               JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES);
+                       } catch(UnsupportedOperationException e) {}
+               }
+               else {
+                       int gid = JnaUtils.getgid();
+                       assertTrue(gid >= 0);
+                       int egid = JnaUtils.getegid();
+                       assertTrue(egid >= 0);
+                       assertTrue("Really? You're running unit tests as root?!", gid > 0);
+                       System.out.println("gid: " + gid + "  egid: " + egid);
+               }
+       }
+
+
        @Test
        public void testGetFilemode() throws IOException {
                if (JnaUtils.isWindows()) {
@@ -111,4 +130,23 @@ public class JnaUtilsTest {
                        FileUtils.deleteDirectory(repository.getDirectory());
                }
        }
+
+
+       @Test
+       public void testGetFilestat() {
+               if (JnaUtils.isWindows()) {
+                       try {
+                               JnaUtils.getFilemode(GitBlitSuite.REPOSITORIES);
+                       } catch(UnsupportedOperationException e) {}
+               }
+               else {
+                       JnaUtils.Filestat stat = JnaUtils.getFilestat(GitBlitSuite.REPOSITORIES);
+                       assertNotNull(stat);
+                       assertTrue(stat.mode > 0);
+                       assertTrue(stat.uid > 0);
+                       assertTrue(stat.gid > 0);
+               }
+       }
+
+
 }