/src/dovecot/src/lib/eacces-error.c
Line | Count | Source |
1 | | /* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "str.h" |
5 | | #include "path-util.h" |
6 | | #include "ipwd.h" |
7 | | #include "restrict-access.h" |
8 | | #include "eacces-error.h" |
9 | | #include "doc.h" |
10 | | |
11 | | #include <sys/stat.h> |
12 | | #include <unistd.h> |
13 | | |
14 | | static bool is_in_group(gid_t gid) |
15 | 0 | { |
16 | 0 | const gid_t *gids; |
17 | 0 | unsigned int i, count; |
18 | |
|
19 | 0 | if (getegid() == gid) |
20 | 0 | return TRUE; |
21 | | |
22 | 0 | gids = restrict_get_groups_list(&count); |
23 | 0 | for (i = 0; i < count; i++) { |
24 | 0 | if (gids[i] == gid) |
25 | 0 | return TRUE; |
26 | 0 | } |
27 | 0 | return FALSE; |
28 | 0 | } |
29 | | |
30 | | static void write_eacces_error(string_t *errmsg, const char *path, int mode) |
31 | 0 | { |
32 | 0 | char c; |
33 | |
|
34 | 0 | switch (mode) { |
35 | 0 | case R_OK: |
36 | 0 | c = 'r'; |
37 | 0 | break; |
38 | 0 | case W_OK: |
39 | 0 | c = 'w'; |
40 | 0 | break; |
41 | 0 | case X_OK: |
42 | 0 | c = 'x'; |
43 | 0 | break; |
44 | 0 | default: |
45 | 0 | i_unreached(); |
46 | 0 | } |
47 | 0 | str_printfa(errmsg, " missing +%c perm: %s", c, path); |
48 | 0 | } |
49 | | |
50 | | static int |
51 | | test_manual_access(const char *path, int access_mode, bool write_eacces, |
52 | | string_t *errmsg) |
53 | 0 | { |
54 | 0 | const struct group *group; |
55 | 0 | bool user_not_in_group = FALSE; |
56 | 0 | struct stat st; |
57 | 0 | int mode; |
58 | |
|
59 | 0 | if (stat(path, &st) < 0) { |
60 | 0 | str_printfa(errmsg, " stat(%s) failed: %m", path); |
61 | 0 | return -1; |
62 | 0 | } |
63 | | |
64 | 0 | switch (access_mode) { |
65 | 0 | case R_OK: |
66 | 0 | mode = 04; |
67 | 0 | break; |
68 | 0 | case W_OK: |
69 | 0 | mode = 02; |
70 | 0 | break; |
71 | 0 | case X_OK: |
72 | 0 | mode = 01; |
73 | 0 | break; |
74 | 0 | default: |
75 | 0 | i_unreached(); |
76 | 0 | } |
77 | | |
78 | 0 | if (st.st_uid == geteuid()) |
79 | 0 | st.st_mode = (st.st_mode & 0700) >> 6; |
80 | 0 | else if (is_in_group(st.st_gid)) |
81 | 0 | st.st_mode = (st.st_mode & 0070) >> 3; |
82 | 0 | else { |
83 | 0 | if ((((st.st_mode & 0070) >> 3) & mode) != 0) |
84 | 0 | user_not_in_group = TRUE; |
85 | 0 | st.st_mode = (st.st_mode & 0007); |
86 | 0 | } |
87 | |
|
88 | 0 | if ((st.st_mode & mode) != 0) |
89 | 0 | return 0; |
90 | | |
91 | 0 | if (write_eacces) |
92 | 0 | write_eacces_error(errmsg, path, access_mode); |
93 | 0 | if (user_not_in_group) { |
94 | | /* group would have had enough permissions, |
95 | | but we don't belong to the group */ |
96 | 0 | str_printfa(errmsg, ", we're not in group %s", |
97 | 0 | dec2str(st.st_gid)); |
98 | 0 | group = getgrgid(st.st_gid); |
99 | 0 | if (group != NULL) |
100 | 0 | str_printfa(errmsg, "(%s)", group->gr_name); |
101 | 0 | } |
102 | 0 | errno = EACCES; |
103 | 0 | return -1; |
104 | 0 | } |
105 | | |
106 | | static int test_access(const char *path, int access_mode, string_t *errmsg) |
107 | 0 | { |
108 | 0 | struct stat st; |
109 | |
|
110 | 0 | if (getuid() == geteuid()) { |
111 | 0 | if (access(path, access_mode) == 0) |
112 | 0 | return 0; |
113 | 0 | if (errno == EACCES) { |
114 | 0 | write_eacces_error(errmsg, path, access_mode); |
115 | 0 | if (test_manual_access(path, access_mode, |
116 | 0 | FALSE, errmsg) == 0) { |
117 | 0 | str_append(errmsg, ", UNIX perms appear ok " |
118 | 0 | "(ACL/MAC wrong?)"); |
119 | 0 | } |
120 | 0 | errno = EACCES; |
121 | 0 | } else { |
122 | 0 | str_printfa(errmsg, ", access(%s, %d) failed: %m", |
123 | 0 | path, access_mode); |
124 | 0 | } |
125 | 0 | return -1; |
126 | 0 | } |
127 | | |
128 | | /* access() uses real uid, not effective uid. |
129 | | we'll have to do these checks manually. */ |
130 | 0 | switch (access_mode) { |
131 | 0 | case X_OK: |
132 | 0 | if (stat(t_strconcat(path, "/test", NULL), &st) == 0) |
133 | 0 | return 0; |
134 | 0 | if (errno == ENOENT || errno == ENOTDIR) |
135 | 0 | return 0; |
136 | 0 | if (errno == EACCES) |
137 | 0 | write_eacces_error(errmsg, path, access_mode); |
138 | 0 | else |
139 | 0 | str_printfa(errmsg, ", stat(%s/test) failed: %m", path); |
140 | 0 | return -1; |
141 | 0 | case R_OK: |
142 | 0 | case W_OK: |
143 | 0 | break; |
144 | 0 | default: |
145 | 0 | i_unreached(); |
146 | 0 | } |
147 | | |
148 | 0 | return test_manual_access(path, access_mode, TRUE, errmsg); |
149 | 0 | } |
150 | | |
151 | | static const char * |
152 | | eacces_error_get_full(const char *func, const char *path, bool creating) |
153 | 0 | { |
154 | 0 | const char *prev_path, *dir = NULL, *p; |
155 | 0 | const char *pw_name = NULL, *gr_name = NULL; |
156 | 0 | struct passwd pw; |
157 | 0 | struct group group; |
158 | 0 | string_t *errmsg; |
159 | 0 | struct stat st; |
160 | 0 | int orig_errno, ret, missing_mode = 0; |
161 | |
|
162 | 0 | orig_errno = errno; |
163 | 0 | errmsg = t_str_new(256); |
164 | 0 | str_printfa(errmsg, "%s(%s)", func, path); |
165 | 0 | if (*path != '/') { |
166 | 0 | const char *error; |
167 | 0 | if (t_get_working_dir(&dir, &error) < 0) { |
168 | 0 | i_error("eacces_error_get_full: %s", error); |
169 | 0 | str_printfa(errmsg, " in an unknown directory"); |
170 | 0 | } else { |
171 | 0 | str_printfa(errmsg, " in directory %s", dir); |
172 | 0 | path = t_strconcat(dir, "/", path, NULL); |
173 | 0 | } |
174 | 0 | } |
175 | 0 | str_printfa(errmsg, " failed: Permission denied (euid=%s", |
176 | 0 | dec2str(geteuid())); |
177 | |
|
178 | 0 | switch (i_getpwuid(geteuid(), &pw)) { |
179 | 0 | case -1: |
180 | 0 | str_append(errmsg, "(<getpwuid() error>)"); |
181 | 0 | break; |
182 | 0 | case 0: |
183 | 0 | str_append(errmsg, "(<unknown>)"); |
184 | 0 | break; |
185 | 0 | default: |
186 | 0 | pw_name = t_strdup(pw.pw_name); |
187 | 0 | str_printfa(errmsg, "(%s)", pw_name); |
188 | 0 | break; |
189 | 0 | } |
190 | | |
191 | 0 | str_printfa(errmsg, " egid=%s", dec2str(getegid())); |
192 | 0 | switch (i_getgrgid(getegid(), &group)) { |
193 | 0 | case -1: |
194 | 0 | str_append(errmsg, "(<getgrgid() error>)"); |
195 | 0 | break; |
196 | 0 | case 0: |
197 | 0 | str_append(errmsg, "(<unknown>)"); |
198 | 0 | break; |
199 | 0 | default: |
200 | 0 | gr_name = t_strdup(group.gr_name); |
201 | 0 | str_printfa(errmsg, "(%s)", gr_name); |
202 | 0 | break; |
203 | 0 | } |
204 | | |
205 | 0 | if (orig_errno == EROFS) { |
206 | 0 | str_append(errmsg, " Read-only file system"); |
207 | 0 | str_append_c(errmsg, ')'); |
208 | 0 | errno = orig_errno; |
209 | 0 | return str_c(errmsg); |
210 | 0 | } |
211 | | |
212 | 0 | prev_path = path; ret = -1; |
213 | 0 | while (strcmp(prev_path, "/") != 0) { |
214 | 0 | if ((p = strrchr(prev_path, '/')) == NULL) |
215 | 0 | break; |
216 | | |
217 | 0 | dir = t_strdup_until(prev_path, p); |
218 | 0 | ret = stat(dir, &st); |
219 | 0 | if (ret == 0) |
220 | 0 | break; |
221 | 0 | if (errno == EACCES && strcmp(dir, "/") != 0) { |
222 | | /* see if we have access to parent directory */ |
223 | 0 | } else if (errno == ENOENT && creating && |
224 | 0 | strcmp(dir, "/") != 0) { |
225 | | /* probably mkdir_parents() failed here, find the first |
226 | | parent directory we couldn't create */ |
227 | 0 | } else { |
228 | | /* some other error, can't handle it */ |
229 | 0 | str_printfa(errmsg, " stat(%s) failed: %m", dir); |
230 | 0 | break; |
231 | 0 | } |
232 | 0 | prev_path = dir; |
233 | 0 | } |
234 | |
|
235 | 0 | if (ret == 0) { |
236 | | /* dir is the first parent directory we can stat() */ |
237 | 0 | if (test_access(dir, X_OK, errmsg) < 0) { |
238 | 0 | if (errno == EACCES) |
239 | 0 | missing_mode = 1; |
240 | 0 | } else if (creating && test_access(dir, W_OK, errmsg) < 0) { |
241 | 0 | if (errno == EACCES) |
242 | 0 | missing_mode = 2; |
243 | 0 | } else if (prev_path == path && |
244 | 0 | test_access(path, R_OK, errmsg) < 0) { |
245 | 0 | } else if (!creating && test_access(path, W_OK, errmsg) < 0) { |
246 | | /* this produces a wrong error if the operation didn't |
247 | | actually need write permissions, but we don't know |
248 | | it here.. */ |
249 | 0 | if (errno == EACCES) |
250 | 0 | missing_mode = 4; |
251 | 0 | } else { |
252 | 0 | str_append(errmsg, " UNIX perms appear ok " |
253 | 0 | "(ACL/MAC wrong?)"); |
254 | 0 | } |
255 | 0 | } |
256 | 0 | if (ret < 0) |
257 | 0 | ; |
258 | 0 | else if (st.st_uid != geteuid()) { |
259 | 0 | if (pw_name != NULL && i_getpwuid(st.st_uid, &pw) > 0 && |
260 | 0 | strcmp(pw.pw_name, pw_name) == 0) { |
261 | 0 | str_printfa(errmsg, ", conflicting dir uid=%s(%s)", |
262 | 0 | dec2str(st.st_uid), pw_name); |
263 | 0 | } else { |
264 | 0 | str_printfa(errmsg, ", dir owned by %s:%s mode=0%o", |
265 | 0 | dec2str(st.st_uid), dec2str(st.st_gid), |
266 | 0 | (unsigned int)(st.st_mode & 0777)); |
267 | 0 | } |
268 | 0 | } else if (missing_mode != 0 && |
269 | 0 | (((st.st_mode & 0700) >> 6) & missing_mode) == 0) { |
270 | 0 | str_append(errmsg, ", dir owner missing perms"); |
271 | 0 | } |
272 | 0 | if (ret == 0 && gr_name != NULL && st.st_gid != getegid()) { |
273 | 0 | if (i_getgrgid(st.st_gid, &group) > 0 && |
274 | 0 | strcmp(group.gr_name, gr_name) == 0) { |
275 | 0 | str_printfa(errmsg, ", conflicting dir gid=%s(%s)", |
276 | 0 | dec2str(st.st_gid), gr_name); |
277 | 0 | } |
278 | 0 | } |
279 | 0 | str_append_c(errmsg, ')'); |
280 | 0 | errno = orig_errno; |
281 | 0 | return str_c(errmsg); |
282 | 0 | } |
283 | | |
284 | | const char *eacces_error_get(const char *func, const char *path) |
285 | 0 | { |
286 | 0 | return eacces_error_get_full(func, path, FALSE); |
287 | 0 | } |
288 | | |
289 | | const char *eacces_error_get_creating(const char *func, const char *path) |
290 | 0 | { |
291 | 0 | return eacces_error_get_full(func, path, TRUE); |
292 | 0 | } |
293 | | |
294 | | const char *eperm_error_get_chgrp(const char *func, const char *path, |
295 | | gid_t gid, const char *gid_origin) |
296 | 0 | { |
297 | 0 | string_t *errmsg; |
298 | 0 | const struct group *group; |
299 | 0 | int orig_errno = errno; |
300 | |
|
301 | 0 | errmsg = t_str_new(256); |
302 | |
|
303 | 0 | str_printfa(errmsg, "%s(%s, group=%s", func, path, dec2str(gid)); |
304 | 0 | group = getgrgid(gid); |
305 | 0 | if (group != NULL) |
306 | 0 | str_printfa(errmsg, "(%s)", group->gr_name); |
307 | |
|
308 | 0 | str_printfa(errmsg, ") failed: Operation not permitted (egid=%s", |
309 | 0 | dec2str(getegid())); |
310 | 0 | group = getgrgid(getegid()); |
311 | 0 | if (group != NULL) |
312 | 0 | str_printfa(errmsg, "(%s)", group->gr_name); |
313 | 0 | if (gid_origin != NULL) |
314 | 0 | str_printfa(errmsg, ", group based on %s", gid_origin); |
315 | 0 | str_append(errmsg, |
316 | 0 | " - see " DOC_LINK("core/admin/errors.html#change-group-operation-not-permitted") ")"); |
317 | | errno = orig_errno; |
318 | 0 | return str_c(errmsg); |
319 | 0 | } |