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