1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2  *
3  * Copyright (C) 2017 Red Hat, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Author: Vratislav Podzimek <vpodzime@redhat.com>
20  *
21  */
22 
23 #include <glib.h>
24 #include <stdio.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <dirent.h>
31 #include <blockdev/fs.h>
32 
33 #include "udiskslinuxfilesystemhelpers.h"
34 #include "udiskslogging.h"
35 
36 
37 static gboolean
recursive_chown(const gchar * path,uid_t caller_uid,gid_t caller_gid,gboolean recursive,GError ** error)38 recursive_chown (const gchar *path,
39                  uid_t        caller_uid,
40                  gid_t        caller_gid,
41                  gboolean     recursive,
42                  GError     **error)
43 {
44   int dirfd;
45   DIR *dir;
46   struct dirent *dirent;
47   GSList *list, *l;
48 
49   g_return_val_if_fail (path != NULL, FALSE);
50 
51   if (lchown (path, caller_uid, caller_gid) != 0)
52     {
53       g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
54                    "Error changing ownership of %s to uid=%u and gid=%u: %m",
55                    path, caller_uid, caller_gid);
56       return FALSE;
57     }
58 
59   if (! recursive)
60     return TRUE;
61 
62   /* read and traverse through the directory */
63   dirfd = open (path, O_DIRECTORY | O_NOFOLLOW);
64   if (dirfd < 0)
65     {
66       if (errno == ENOTDIR)
67         return TRUE;
68       g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
69                    "Error opening directory %s: %m", path);
70       return FALSE;
71     }
72 
73   dir = fdopendir (dirfd);
74   if (! dir)
75     {
76       g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
77                    "Error opening directory %s: %m", path);
78       close (dirfd);
79       return FALSE;
80     }
81 
82   /* build a list of filenames to prevent fd exhaustion */
83   list = NULL;
84   while ((errno = 0, dirent = readdir (dir)))
85     if (g_strcmp0 (dirent->d_name, ".") != 0 && g_strcmp0 (dirent->d_name, "..") != 0)
86       list = g_slist_append (list, g_strdup (dirent->d_name));
87   if (!dirent && errno != 0)
88     {
89       g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
90                    "Error reading directory %s: %m", path);
91       closedir (dir);
92       g_slist_free_full (list, g_free);
93       return FALSE;
94     }
95   closedir (dir);
96 
97   /* recurse into parents */
98   for (l = list; l; l = g_slist_next (l))
99     {
100       gchar *newpath;
101 
102       newpath = g_build_filename (path, l->data, NULL);
103       if (! recursive_chown (newpath, caller_uid, caller_gid, TRUE, error))
104         {
105           g_free (newpath);
106           g_slist_free_full (list, g_free);
107           return FALSE;
108         }
109       g_free (newpath);
110     }
111   g_slist_free_full (list, g_free);
112 
113   return TRUE;
114 }
115 
116 gboolean
take_filesystem_ownership(const gchar * device,const gchar * fstype,uid_t caller_uid,gid_t caller_gid,gboolean recursive,GError ** error)117 take_filesystem_ownership (const gchar  *device,
118                            const gchar  *fstype,
119                            uid_t         caller_uid,
120                            gid_t         caller_gid,
121                            gboolean      recursive,
122                            GError      **error)
123 
124 {
125   gchar *mountpoint = NULL;
126   GError *local_error = NULL;
127   gboolean unmount = FALSE;
128   gboolean success = TRUE;
129 
130   mountpoint = bd_fs_get_mountpoint (device, &local_error);
131   if (mountpoint == NULL)
132     {
133       if (local_error != NULL)
134         {
135           g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
136                        "Error when getting mountpoint for %s: %s.",
137                        device, local_error->message);
138           g_clear_error (&local_error);
139           success = FALSE;
140           goto out;
141         }
142       else
143         {
144           /* device is not mounted, we need to mount it */
145           mountpoint = g_mkdtemp (g_strdup ("/run/udisks2/temp-mount-XXXXXX"));
146           if (mountpoint == NULL)
147             {
148               g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
149                            "Cannot create temporary mountpoint.");
150               success = FALSE;
151               goto out;
152             }
153 
154           /* TODO: mount to a private mount namespace */
155           if (!bd_fs_mount (device, mountpoint, fstype, NULL, NULL, &local_error))
156             {
157               g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
158                            "Cannot mount %s at %s: %s",
159                            device, mountpoint, local_error->message);
160               g_clear_error (&local_error);
161               if (g_rmdir (mountpoint) != 0)
162                 udisks_warning ("Error removing temporary mountpoint directory %s.", mountpoint);
163               success = FALSE;
164               goto out;
165             }
166           else
167             unmount = TRUE;  /* unmount during cleanup */
168         }
169     }
170 
171   /* actual chown */
172   success = recursive_chown (mountpoint, caller_uid, caller_gid, recursive, error);
173   if (! success)
174     goto out;
175 
176   if (chmod (mountpoint, 0700) != 0)
177     {
178       g_set_error (error, UDISKS_ERROR, UDISKS_ERROR_FAILED,
179                    "Cannot chmod %s to mode 0700: %m",
180                    mountpoint);
181       success = FALSE;
182       goto out;
183     }
184 
185  out:
186   if (unmount)
187     {
188       if (! bd_fs_unmount (mountpoint, FALSE, FALSE, NULL, &local_error))
189         {
190           udisks_warning ("Error unmounting temporary mountpoint %s: %s",
191                           mountpoint, local_error->message);
192           g_clear_error (&local_error);
193         }
194       if (g_rmdir (mountpoint) != 0)
195         udisks_warning ("Error removing temporary mountpoint directory %s.", mountpoint);
196     }
197 
198   g_free (mountpoint);
199 
200   return success;
201 }
202