private static bool DetermineCanModifyStores()
        {
            // Check the directory permissions and whether the filesystem supports chmod.
            // The only real expected failure from this method is that at the very end
            // `stat.Mode == mode` will fail, because fuseblk (NTFS) returns success on chmod,
            // but is a no-op.

            uint   userId = Interop.Sys.GetEUid();
            string certStoresFeaturePath = PersistedFiles.GetUserFeatureDirectory("cryptography", "x509stores");

            Directory.CreateDirectory(certStoresFeaturePath);

            // Check directory permissions:

            Interop.Sys.FileStatus dirStat;
            if (Interop.Sys.Stat(certStoresFeaturePath, out dirStat) != 0)
            {
                return(false);
            }

            if (dirStat.Uid != userId)
            {
                return(false);
            }

            if ((dirStat.Mode & (int)Interop.Sys.Permissions.S_IRWXU) != (int)Interop.Sys.Permissions.S_IRWXU)
            {
                return(false);
            }

            string probeFilename =
                Path.Combine(certStoresFeaturePath, $"{Guid.NewGuid().ToString("N")}.chmod");

            try
            {
                using (FileStream stream = new FileStream(probeFilename, FileMode.Create))
                {
                    Interop.Sys.FileStatus stat;
                    if (Interop.Sys.FStat(stream.SafeFileHandle, out stat) != 0)
                    {
                        return(false);
                    }

                    if (stat.Uid != userId)
                    {
                        return(false);
                    }

                    // The product code here has a lot of stuff it does.
                    // This capabilities probe will just check that chmod works.
                    int mode = stat.Mode;

                    // Flip all of the O bits.
                    mode ^= (int)Interop.Sys.Permissions.S_IRWXO;

                    if (Interop.Sys.FChMod(stream.SafeFileHandle, mode) < 0)
                    {
                        return(false);
                    }

                    // Verify the chmod applied.
                    if (Interop.Sys.FStat(stream.SafeFileHandle, out stat) != 0)
                    {
                        return(false);
                    }

                    // On fuseblk (NTFS) this will return false, because the fchmod
                    // call returned success without being able to actually apply
                    // mode-bits.
                    return(stat.Mode == mode);
                }
            }
            finally
            {
                try
                {
                    File.Delete(probeFilename);
                }
                catch
                {
                    // Ignore any failure on delete.
                }
            }
        }
Example #2
0
        private static string GetFolderPathCoreWithoutValidation(SpecialFolder folder)
        {
            // First handle any paths that involve only static paths, avoiding the overheads of getting user-local paths.
            // https://www.freedesktop.org/software/systemd/man/file-hierarchy.html
            switch (folder)
            {
            case SpecialFolder.CommonApplicationData: return("/usr/share");

            case SpecialFolder.CommonTemplates: return("/usr/share/templates");
            }
            if (IsMac)
            {
                switch (folder)
                {
                case SpecialFolder.ProgramFiles: return("/Applications");

                case SpecialFolder.System: return("/System");
                }
            }

            // All other paths are based on the XDG Base Directory Specification:
            // https://specifications.freedesktop.org/basedir-spec/latest/
            string home;

            try
            {
                home = PersistedFiles.GetHomeDirectory();
            }
            catch (Exception exc)
            {
                Debug.Fail($"Unable to get home directory: {exc}");
                home = Path.GetTempPath();
            }
            Debug.Assert(!string.IsNullOrEmpty(home), "Expected non-null or empty HOME");

            // TODO: Consider caching (or precomputing and caching) all subsequent results.
            // This would significantly improve performance for repeated access, at the expense
            // of not being responsive to changes in the underlying environment variables,
            // configuration files, etc.

            switch (folder)
            {
            case SpecialFolder.UserProfile:
            case SpecialFolder.MyDocuments:     // same value as Personal
                return(home);

            case SpecialFolder.ApplicationData:
                return(GetXdgConfig(home));

            case SpecialFolder.LocalApplicationData:
                // "$XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored."
                // "If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used."
                string data = GetEnvironmentVariable("XDG_DATA_HOME");
                if (string.IsNullOrEmpty(data) || data[0] != '/')
                {
                    data = Path.Combine(home, ".local", "share");
                }
                return(data);

            case SpecialFolder.Desktop:
            case SpecialFolder.DesktopDirectory:
                return(ReadXdgDirectory(home, "XDG_DESKTOP_DIR", "Desktop"));

            case SpecialFolder.Templates:
                return(ReadXdgDirectory(home, "XDG_TEMPLATES_DIR", "Templates"));

            case SpecialFolder.MyVideos:
                return(ReadXdgDirectory(home, "XDG_VIDEOS_DIR", "Videos"));

            case SpecialFolder.MyMusic:
                return(IsMac ? Path.Combine(home, "Music") : ReadXdgDirectory(home, "XDG_MUSIC_DIR", "Music"));

            case SpecialFolder.MyPictures:
                return(IsMac ? Path.Combine(home, "Pictures") : ReadXdgDirectory(home, "XDG_PICTURES_DIR", "Pictures"));

            case SpecialFolder.Fonts:
                return(IsMac ? Path.Combine(home, "Library", "Fonts") : Path.Combine(home, ".fonts"));

            case SpecialFolder.Favorites:
                if (IsMac)
                {
                    return(Path.Combine(home, "Library", "Favorites"));
                }
                break;

            case SpecialFolder.InternetCache:
                if (IsMac)
                {
                    return(Path.Combine(home, "Library", "Caches"));
                }
                break;
            }

            // No known path for the SpecialFolder
            return(string.Empty);
        }
Example #3
0
        private static string?GetSpecialFolder(SpecialFolder folder)
        {
            string?home = null;

            try
            {
                home = PersistedFiles.GetHomeDirectory();
            }
            catch (Exception exc)
            {
                Debug.Fail($"Unable to get home directory: {exc}");
            }

            // Fall back to '/' when we can't determine the home directory.
            // This location isn't writable by non-root users which provides some safeguard
            // that the application doesn't write data which is meant to be private.
            if (string.IsNullOrEmpty(home))
            {
                home = "/";
            }

            switch (folder)
            {
            case SpecialFolder.Personal:
            case SpecialFolder.LocalApplicationData:
                return(home);

            case SpecialFolder.ApplicationData:
                return(Path.Combine(home, ".config"));

            case SpecialFolder.Desktop:
            case SpecialFolder.DesktopDirectory:
                return(Path.Combine(home, "Desktop"));

            case SpecialFolder.MyMusic:
                return(Path.Combine(home, "Music"));

            case SpecialFolder.MyPictures:
                return(Path.Combine(home, "Pictures"));

            case SpecialFolder.Templates:
                return(Path.Combine(home, "Templates"));

            case SpecialFolder.MyVideos:
                return(Path.Combine(home, "Videos"));

            case SpecialFolder.CommonTemplates:
                return("/usr/share/templates");

            case SpecialFolder.Fonts:
                return(Path.Combine(home, ".fonts"));

            case SpecialFolder.UserProfile:
                return(GetEnvironmentVariable("HOME"));

            case SpecialFolder.CommonApplicationData:
                return("/usr/share");

            default:
                return(string.Empty);
            }
        }