1 |
slords |
1.1 |
diff --git a/dovecot-example.conf b/dovecot-example.conf |
2 |
|
|
index 4dccda8..55c9be0 100644 |
3 |
|
|
--- a/dovecot-example.conf |
4 |
|
|
+++ b/dovecot-example.conf |
5 |
|
|
@@ -252,9 +252,17 @@ |
6 |
|
|
#hidden = yes |
7 |
|
|
#} |
8 |
|
|
|
9 |
|
|
-# Grant access to these extra groups for mail processes. Typical use would be |
10 |
|
|
-# to give "mail" group write access to /var/mail to be able to create dotlocks. |
11 |
|
|
-#mail_extra_groups = |
12 |
|
|
+# Group to enable temporarily for privileged operations. Currently this is |
13 |
|
|
+# used only for creating mbox dotlock files when creation fails for INBOX. |
14 |
|
|
+# Typically this is set to "mail" to give access to /var/mail. |
15 |
|
|
+#mail_privileged_group = |
16 |
|
|
+ |
17 |
|
|
+# Grant access to these supplementary groups for mail processes. Typically |
18 |
|
|
+# these are used to set up access to shared mailboxes. Note that it may be |
19 |
|
|
+# dangerous to set these if users can create symlinks (e.g. if "mail" group is |
20 |
|
|
+# set here, ln -s /var/mail ~/mail/var could allow a user to delete others' |
21 |
|
|
+# mailboxes, or ln -s /secret/shared/box ~/mail/mybox would allow reading it). |
22 |
|
|
+#mail_access_groups = |
23 |
|
|
|
24 |
|
|
# Allow full filesystem access to clients. There's no access checks other than |
25 |
|
|
# what the operating system does for the active UID/GID. It works with both |
26 |
|
|
diff --git a/src/lib-storage/index/mbox/mbox-lock.c b/src/lib-storage/index/mbox/mbox-lock.c |
27 |
|
|
index 1950b38..9e66f5f 100644 |
28 |
|
|
--- a/src/lib-storage/index/mbox/mbox-lock.c |
29 |
|
|
+++ b/src/lib-storage/index/mbox/mbox-lock.c |
30 |
|
|
@@ -1,6 +1,7 @@ |
31 |
|
|
/* Copyright (C) 2002 Timo Sirainen */ |
32 |
|
|
|
33 |
|
|
#include "lib.h" |
34 |
|
|
+#include "restrict-access.h" |
35 |
|
|
#include "mail-index-private.h" |
36 |
|
|
#include "mbox-storage.h" |
37 |
|
|
#include "mbox-file.h" |
38 |
|
|
@@ -36,6 +37,12 @@ enum mbox_lock_type { |
39 |
|
|
MBOX_LOCK_COUNT |
40 |
|
|
}; |
41 |
|
|
|
42 |
|
|
+enum mbox_dotlock_op { |
43 |
|
|
+ MBOX_DOTLOCK_OP_LOCK, |
44 |
|
|
+ MBOX_DOTLOCK_OP_UNLOCK, |
45 |
|
|
+ MBOX_DOTLOCK_OP_TOUCH |
46 |
|
|
+}; |
47 |
|
|
+ |
48 |
|
|
struct mbox_lock_context { |
49 |
|
|
struct mbox_mailbox *mbox; |
50 |
|
|
int lock_status[MBOX_LOCK_COUNT]; |
51 |
|
|
@@ -43,6 +50,7 @@ struct mbox_lock_context { |
52 |
|
|
|
53 |
|
|
int lock_type; |
54 |
|
|
bool dotlock_last_stale; |
55 |
|
|
+ bool using_privileges; |
56 |
|
|
}; |
57 |
|
|
|
58 |
|
|
struct mbox_lock_data { |
59 |
|
|
@@ -190,6 +198,9 @@ static bool dotlock_callback(unsigned int secs_left, bool stale, void *context) |
60 |
|
|
enum mbox_lock_type *lock_types; |
61 |
|
|
int i; |
62 |
|
|
|
63 |
|
|
+ if (ctx->using_privileges) |
64 |
|
|
+ restrict_access_drop_priv_gid(); |
65 |
|
|
+ |
66 |
|
|
if (stale && !ctx->dotlock_last_stale) { |
67 |
|
|
/* get next index we wish to try locking. it's the one after |
68 |
|
|
dotlocking. */ |
69 |
|
|
@@ -221,9 +232,92 @@ static bool dotlock_callback(unsigned int secs_left, bool stale, void *context) |
70 |
|
|
MAILBOX_LOCK_NOTIFY_MAILBOX_OVERRIDE : |
71 |
|
|
MAILBOX_LOCK_NOTIFY_MAILBOX_ABORT, |
72 |
|
|
secs_left); |
73 |
|
|
+ if (ctx->using_privileges) { |
74 |
|
|
+ if (restrict_access_use_priv_gid() < 0) { |
75 |
|
|
+ /* shouldn't get here */ |
76 |
|
|
+ return FALSE; |
77 |
|
|
+ } |
78 |
|
|
+ } |
79 |
|
|
return TRUE; |
80 |
|
|
} |
81 |
|
|
|
82 |
|
|
+static int mbox_dotlock_privileged_op(struct mbox_mailbox *mbox, |
83 |
|
|
+ struct dotlock_settings *set, |
84 |
|
|
+ enum mbox_dotlock_op op) |
85 |
|
|
+{ |
86 |
|
|
+ const char *dir, *fname; |
87 |
|
|
+ int ret = -1, orig_dir_fd; |
88 |
|
|
+ |
89 |
|
|
+ orig_dir_fd = open(".", O_RDONLY); |
90 |
|
|
+ if (orig_dir_fd == -1) { |
91 |
|
|
+ i_error("open(.) failed: %m"); |
92 |
|
|
+ return -1; |
93 |
|
|
+ } |
94 |
|
|
+ |
95 |
|
|
+ /* allow dotlocks to be created only for files we can read while we're |
96 |
|
|
+ unprivileged. to make sure there are no race conditions we first |
97 |
|
|
+ have to chdir to the mbox file's directory and then use relative |
98 |
|
|
+ paths. unless this is done, users could: |
99 |
|
|
+ - create *.lock files to any directory writable by the |
100 |
|
|
+ privileged group |
101 |
|
|
+ - DoS other users by dotlocking their mailboxes infinitely |
102 |
|
|
+ */ |
103 |
|
|
+ fname = strrchr(mbox->path, '/'); |
104 |
|
|
+ if (fname == NULL) { |
105 |
|
|
+ /* already relative */ |
106 |
|
|
+ fname = mbox->path; |
107 |
|
|
+ } else { |
108 |
|
|
+ dir = t_strdup_until(mbox->path, fname); |
109 |
|
|
+ if (chdir(dir) < 0) { |
110 |
|
|
+ i_error("chdir(%s) failed: %m", dir); |
111 |
|
|
+ (void)close(orig_dir_fd); |
112 |
|
|
+ return -1; |
113 |
|
|
+ } |
114 |
|
|
+ fname++; |
115 |
|
|
+ } |
116 |
|
|
+ if (op == MBOX_DOTLOCK_OP_LOCK) { |
117 |
|
|
+ if (access(fname, R_OK) < 0) { |
118 |
|
|
+ i_error("access(%s) failed: %m", mbox->path); |
119 |
|
|
+ return -1; |
120 |
|
|
+ } |
121 |
|
|
+ } |
122 |
|
|
+ |
123 |
|
|
+ if (restrict_access_use_priv_gid() < 0) { |
124 |
|
|
+ (void)close(orig_dir_fd); |
125 |
|
|
+ return -1; |
126 |
|
|
+ } |
127 |
|
|
+ |
128 |
|
|
+ switch (op) { |
129 |
|
|
+ case MBOX_DOTLOCK_OP_LOCK: |
130 |
|
|
+ /* we're now privileged - avoid doing as much as possible */ |
131 |
|
|
+ ret = file_dotlock_create(set, fname, 0, &mbox->mbox_dotlock); |
132 |
|
|
+ if (ret > 0) |
133 |
|
|
+ mbox->mbox_used_privileges = TRUE; |
134 |
|
|
+ break; |
135 |
|
|
+ case MBOX_DOTLOCK_OP_UNLOCK: |
136 |
|
|
+ /* we're now privileged - avoid doing as much as possible */ |
137 |
|
|
+ ret = file_dotlock_delete(&mbox->mbox_dotlock); |
138 |
|
|
+ mbox->mbox_used_privileges = FALSE; |
139 |
|
|
+ break; |
140 |
|
|
+ case MBOX_DOTLOCK_OP_TOUCH: |
141 |
|
|
+ if (!file_dotlock_is_locked(mbox->mbox_dotlock)) { |
142 |
|
|
+ file_dotlock_delete(&mbox->mbox_dotlock); |
143 |
|
|
+ mbox->mbox_used_privileges = TRUE; |
144 |
|
|
+ ret = -1; |
145 |
|
|
+ } else { |
146 |
|
|
+ ret = file_dotlock_touch(mbox->mbox_dotlock); |
147 |
|
|
+ } |
148 |
|
|
+ break; |
149 |
|
|
+ } |
150 |
|
|
+ |
151 |
|
|
+ restrict_access_drop_priv_gid(); |
152 |
|
|
+ |
153 |
|
|
+ if (fchdir(orig_dir_fd) < 0) |
154 |
|
|
+ i_error("fchdir() failed: %m"); |
155 |
|
|
+ (void)close(orig_dir_fd); |
156 |
|
|
+ return ret; |
157 |
|
|
+} |
158 |
|
|
+ |
159 |
|
|
static int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type, |
160 |
|
|
time_t max_wait_time __attr_unused__) |
161 |
|
|
{ |
162 |
|
|
@@ -235,7 +329,15 @@ static int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type, |
163 |
|
|
if (!mbox->mbox_dotlocked) |
164 |
|
|
return 1; |
165 |
|
|
|
166 |
|
|
- if (file_dotlock_delete(&mbox->mbox_dotlock) <= 0) { |
167 |
|
|
+ if (!mbox->mbox_used_privileges) |
168 |
|
|
+ ret = file_dotlock_delete(&mbox->mbox_dotlock); |
169 |
|
|
+ else { |
170 |
|
|
+ ctx->using_privileges = TRUE; |
171 |
|
|
+ ret = mbox_dotlock_privileged_op(mbox, NULL, |
172 |
|
|
+ MBOX_DOTLOCK_OP_UNLOCK); |
173 |
|
|
+ ctx->using_privileges = FALSE; |
174 |
|
|
+ } |
175 |
|
|
+ if (ret <= 0) { |
176 |
|
|
mbox_set_syscall_error(mbox, "file_dotlock_delete()"); |
177 |
|
|
ret = -1; |
178 |
|
|
} |
179 |
|
|
@@ -257,6 +359,13 @@ static int mbox_lock_dotlock(struct mbox_lock_context *ctx, int lock_type, |
180 |
|
|
set.context = ctx; |
181 |
|
|
|
182 |
|
|
ret = file_dotlock_create(&set, mbox->path, 0, &mbox->mbox_dotlock); |
183 |
|
|
+ if (ret < 0 && errno == EACCES && restrict_access_have_priv_gid() && |
184 |
|
|
+ mbox->mbox_privileged_locking) { |
185 |
|
|
+ /* try again, this time with extra privileges */ |
186 |
|
|
+ ret = mbox_dotlock_privileged_op(mbox, &set, |
187 |
|
|
+ MBOX_DOTLOCK_OP_LOCK); |
188 |
|
|
+ } |
189 |
|
|
+ |
190 |
|
|
if (ret < 0) { |
191 |
|
|
mbox_set_syscall_error(mbox, "file_lock_dotlock()"); |
192 |
|
|
return -1; |
193 |
|
|
@@ -601,3 +710,16 @@ int mbox_unlock(struct mbox_mailbox *mbox, unsigned int lock_id) |
194 |
|
|
|
195 |
|
|
return mbox_unlock_files(&ctx); |
196 |
|
|
} |
197 |
|
|
+ |
198 |
|
|
+void mbox_dotlock_touch(struct mbox_mailbox *mbox) |
199 |
|
|
+{ |
200 |
|
|
+ if (mbox->mbox_dotlock == NULL) |
201 |
|
|
+ return; |
202 |
|
|
+ |
203 |
|
|
+ if (!mbox->mbox_used_privileges) |
204 |
|
|
+ (void)file_dotlock_touch(mbox->mbox_dotlock); |
205 |
|
|
+ else { |
206 |
|
|
+ (void)mbox_dotlock_privileged_op(mbox, NULL, |
207 |
|
|
+ MBOX_DOTLOCK_OP_TOUCH); |
208 |
|
|
+ } |
209 |
|
|
+} |
210 |
|
|
diff --git a/src/lib-storage/index/mbox/mbox-lock.h b/src/lib-storage/index/mbox/mbox-lock.h |
211 |
|
|
index 19c51dd..baa1843 100644 |
212 |
|
|
--- a/src/lib-storage/index/mbox/mbox-lock.h |
213 |
|
|
+++ b/src/lib-storage/index/mbox/mbox-lock.h |
214 |
|
|
@@ -7,4 +7,6 @@ int mbox_lock(struct mbox_mailbox *mbox, int lock_type, |
215 |
|
|
unsigned int *lock_id_r); |
216 |
|
|
int mbox_unlock(struct mbox_mailbox *mbox, unsigned int lock_id); |
217 |
|
|
|
218 |
|
|
+void mbox_dotlock_touch(struct mbox_mailbox *mbox); |
219 |
|
|
+ |
220 |
|
|
#endif |
221 |
|
|
diff --git a/src/lib-storage/index/mbox/mbox-storage.c b/src/lib-storage/index/mbox/mbox-storage.c |
222 |
|
|
index 330d0c1..cdfd2e0 100644 |
223 |
|
|
--- a/src/lib-storage/index/mbox/mbox-storage.c |
224 |
|
|
+++ b/src/lib-storage/index/mbox/mbox-storage.c |
225 |
|
|
@@ -443,6 +443,13 @@ bool mbox_is_valid_mask(struct mail_storage *storage, const char *mask) |
226 |
|
|
return TRUE; |
227 |
|
|
} |
228 |
|
|
|
229 |
|
|
+static bool mbox_name_is_dotlock(const char *name) |
230 |
|
|
+{ |
231 |
|
|
+ unsigned int len = strlen(name); |
232 |
|
|
+ |
233 |
|
|
+ return len >= 5 && strcmp(name + len - 5, ".lock") == 0; |
234 |
|
|
+} |
235 |
|
|
+ |
236 |
|
|
static bool mbox_is_valid_create_name(struct mail_storage *storage, |
237 |
|
|
const char *name) |
238 |
|
|
{ |
239 |
|
|
@@ -458,7 +465,7 @@ static bool mbox_is_valid_create_name(struct mail_storage *storage, |
240 |
|
|
return FALSE; |
241 |
|
|
} |
242 |
|
|
|
243 |
|
|
- return mbox_is_valid_mask(storage, name); |
244 |
|
|
+ return mbox_is_valid_mask(storage, name) && !mbox_name_is_dotlock(name); |
245 |
|
|
} |
246 |
|
|
|
247 |
|
|
static bool mbox_is_valid_existing_name(struct mail_storage *storage, |
248 |
|
|
@@ -470,7 +477,7 @@ static bool mbox_is_valid_existing_name(struct mail_storage *storage, |
249 |
|
|
if (name[0] == '\0' || name[len-1] == '/') |
250 |
|
|
return FALSE; |
251 |
|
|
|
252 |
|
|
- return mbox_is_valid_mask(storage, name); |
253 |
|
|
+ return mbox_is_valid_mask(storage, name) && !mbox_name_is_dotlock(name); |
254 |
|
|
} |
255 |
|
|
|
256 |
|
|
static const char *mbox_get_index_dir(struct index_storage *storage, |
257 |
|
|
@@ -597,7 +604,7 @@ static void mbox_lock_touch_timeout(void *context) |
258 |
|
|
{ |
259 |
|
|
struct mbox_mailbox *mbox = context; |
260 |
|
|
|
261 |
|
|
- (void)file_dotlock_touch(mbox->mbox_dotlock); |
262 |
|
|
+ mbox_dotlock_touch(mbox); |
263 |
|
|
} |
264 |
|
|
|
265 |
|
|
static struct mbox_mailbox * |
266 |
|
|
@@ -697,6 +704,12 @@ mbox_open(struct mbox_storage *storage, const char *name, |
267 |
|
|
} |
268 |
|
|
} |
269 |
|
|
|
270 |
|
|
+ if (strcmp(name, "INBOX") == 0) { |
271 |
|
|
+ /* if INBOX isn't under the root directory, it's probably in |
272 |
|
|
+ /var/mail and we want to allow privileged dotlocking */ |
273 |
|
|
+ if (strncmp(path, istorage->dir, strlen(istorage->dir)) != 0) |
274 |
|
|
+ mbox->mbox_privileged_locking = TRUE; |
275 |
|
|
+ } |
276 |
|
|
return &mbox->ibox.box; |
277 |
|
|
} |
278 |
|
|
|
279 |
|
|
diff --git a/src/lib-storage/index/mbox/mbox-storage.h b/src/lib-storage/index/mbox/mbox-storage.h |
280 |
|
|
index fe1dc4c..127df6a 100644 |
281 |
|
|
--- a/src/lib-storage/index/mbox/mbox-storage.h |
282 |
|
|
+++ b/src/lib-storage/index/mbox/mbox-storage.h |
283 |
|
|
@@ -48,6 +48,8 @@ struct mbox_mailbox { |
284 |
|
|
unsigned int mbox_very_dirty_syncs:1; |
285 |
|
|
unsigned int mbox_save_md5:1; |
286 |
|
|
unsigned int mbox_dotlocked:1; |
287 |
|
|
+ unsigned int mbox_used_privileges:1; |
288 |
|
|
+ unsigned int mbox_privileged_locking:1; |
289 |
|
|
}; |
290 |
|
|
|
291 |
|
|
struct mbox_transaction_context { |
292 |
|
|
diff --git a/src/lib/file-dotlock.c b/src/lib/file-dotlock.c |
293 |
|
|
index 1ae16ab..26e762d 100644 |
294 |
|
|
--- a/src/lib/file-dotlock.c |
295 |
|
|
+++ b/src/lib/file-dotlock.c |
296 |
|
|
@@ -262,7 +262,8 @@ static int create_temp_file(string_t *path, bool write_pid) |
297 |
|
|
break; |
298 |
|
|
|
299 |
|
|
if (errno != EEXIST) { |
300 |
|
|
- i_error("open(%s) failed: %m", str_c(path)); |
301 |
|
|
+ if (errno != EACCES) |
302 |
|
|
+ i_error("open(%s) failed: %m", str_c(path)); |
303 |
|
|
return -1; |
304 |
|
|
} |
305 |
|
|
} |
306 |
|
|
@@ -319,8 +320,10 @@ static int try_create_lock_hardlink(struct lock_info *lock_info, bool write_pid, |
307 |
|
|
if (errno == EEXIST) |
308 |
|
|
return 0; |
309 |
|
|
|
310 |
|
|
- i_error("link(%s, %s) failed: %m", |
311 |
|
|
- lock_info->temp_path, lock_info->lock_path); |
312 |
|
|
+ if (errno != EACCES) { |
313 |
|
|
+ i_error("link(%s, %s) failed: %m", |
314 |
|
|
+ lock_info->temp_path, lock_info->lock_path); |
315 |
|
|
+ } |
316 |
|
|
return -1; |
317 |
|
|
} |
318 |
|
|
|
319 |
|
|
@@ -342,7 +345,8 @@ static int try_create_lock_excl(struct lock_info *lock_info, bool write_pid) |
320 |
|
|
if (errno == EEXIST) |
321 |
|
|
return 0; |
322 |
|
|
|
323 |
|
|
- i_error("open(%s) failed: %m", lock_info->lock_path); |
324 |
|
|
+ if (errno != EACCES) |
325 |
|
|
+ i_error("open(%s) failed: %m", lock_info->lock_path); |
326 |
|
|
return -1; |
327 |
|
|
} |
328 |
|
|
|
329 |
|
|
@@ -633,7 +637,6 @@ int file_dotlock_replace(struct dotlock **dotlock_p, |
330 |
|
|
enum dotlock_replace_flags flags) |
331 |
|
|
{ |
332 |
|
|
struct dotlock *dotlock; |
333 |
|
|
- struct stat st, st2; |
334 |
|
|
const char *lock_path; |
335 |
|
|
int fd; |
336 |
|
|
|
337 |
|
|
@@ -645,28 +648,14 @@ int file_dotlock_replace(struct dotlock **dotlock_p, |
338 |
|
|
dotlock->fd = -1; |
339 |
|
|
|
340 |
|
|
lock_path = file_dotlock_get_lock_path(dotlock); |
341 |
|
|
- if ((flags & DOTLOCK_REPLACE_FLAG_VERIFY_OWNER) != 0) { |
342 |
|
|
- if (fstat(fd, &st) < 0) { |
343 |
|
|
- i_error("fstat(%s) failed: %m", lock_path); |
344 |
|
|
- file_dotlock_free(dotlock); |
345 |
|
|
- return -1; |
346 |
|
|
- } |
347 |
|
|
- |
348 |
|
|
- if (lstat(lock_path, &st2) < 0) { |
349 |
|
|
- i_error("lstat(%s) failed: %m", lock_path); |
350 |
|
|
- file_dotlock_free(dotlock); |
351 |
|
|
- return -1; |
352 |
|
|
- } |
353 |
|
|
- |
354 |
|
|
- if (st.st_ino != st2.st_ino || |
355 |
|
|
- !CMP_DEV_T(st.st_dev, st2.st_dev)) { |
356 |
|
|
- i_warning("Our dotlock file %s was overridden " |
357 |
|
|
- "(kept it %d secs)", lock_path, |
358 |
|
|
- (int)(time(NULL) - dotlock->lock_time)); |
359 |
|
|
- errno = EEXIST; |
360 |
|
|
- file_dotlock_free(dotlock); |
361 |
|
|
- return 0; |
362 |
|
|
- } |
363 |
|
|
+ if ((flags & DOTLOCK_REPLACE_FLAG_VERIFY_OWNER) != 0 && |
364 |
|
|
+ !file_dotlock_is_locked(dotlock)) { |
365 |
|
|
+ i_warning("Our dotlock file %s was overridden " |
366 |
|
|
+ "(kept it %d secs)", lock_path, |
367 |
|
|
+ (int)(time(NULL) - dotlock->lock_time)); |
368 |
|
|
+ errno = EEXIST; |
369 |
|
|
+ file_dotlock_free(dotlock); |
370 |
|
|
+ return 0; |
371 |
|
|
} |
372 |
|
|
|
373 |
|
|
if (rename(lock_path, dotlock->path) < 0) { |
374 |
|
|
@@ -701,6 +690,24 @@ int file_dotlock_touch(struct dotlock *dotlock) |
375 |
|
|
return ret; |
376 |
|
|
} |
377 |
|
|
|
378 |
|
|
+bool file_dotlock_is_locked(struct dotlock *dotlock) |
379 |
|
|
+{ |
380 |
|
|
+ struct stat st, st2; |
381 |
|
|
+ const char *lock_path; |
382 |
|
|
+ |
383 |
|
|
+ lock_path = file_dotlock_get_lock_path(dotlock); |
384 |
|
|
+ if (fstat(dotlock->fd, &st) < 0) { |
385 |
|
|
+ i_error("fstat(%s) failed: %m", lock_path); |
386 |
|
|
+ return FALSE; |
387 |
|
|
+ } |
388 |
|
|
+ |
389 |
|
|
+ if (lstat(lock_path, &st2) < 0) { |
390 |
|
|
+ i_error("lstat(%s) failed: %m", lock_path); |
391 |
|
|
+ return FALSE; |
392 |
|
|
+ } |
393 |
|
|
+ return st.st_ino == st2.st_ino && CMP_DEV_T(st.st_dev, st2.st_dev); |
394 |
|
|
+} |
395 |
|
|
+ |
396 |
|
|
const char *file_dotlock_get_lock_path(struct dotlock *dotlock) |
397 |
|
|
{ |
398 |
|
|
if (dotlock->lock_path == NULL) { |
399 |
|
|
diff --git a/src/lib/file-dotlock.h b/src/lib/file-dotlock.h |
400 |
|
|
index f29a5ac..7e88c45 100644 |
401 |
|
|
--- a/src/lib/file-dotlock.h |
402 |
|
|
+++ b/src/lib/file-dotlock.h |
403 |
|
|
@@ -70,6 +70,8 @@ int file_dotlock_replace(struct dotlock **dotlock, |
404 |
|
|
it's a good idea to update it once in a while so others won't override it. |
405 |
|
|
If the timestamp is less than a second old, it's not updated. */ |
406 |
|
|
int file_dotlock_touch(struct dotlock *dotlock); |
407 |
|
|
+/* Returns TRUE if the lock is still ok, FALSE if it's been overridden. */ |
408 |
|
|
+bool file_dotlock_is_locked(struct dotlock *dotlock); |
409 |
|
|
|
410 |
|
|
/* Returns the lock file path. */ |
411 |
|
|
const char *file_dotlock_get_lock_path(struct dotlock *dotlock); |
412 |
|
|
diff --git a/src/lib/restrict-access.c b/src/lib/restrict-access.c |
413 |
|
|
index 5d6692d..f668f97 100644 |
414 |
|
|
--- a/src/lib/restrict-access.c |
415 |
|
|
+++ b/src/lib/restrict-access.c |
416 |
|
|
@@ -1,15 +1,22 @@ |
417 |
|
|
-/* Copyright (c) 2002-2004 Timo Sirainen */ |
418 |
|
|
+/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */ |
419 |
|
|
+ |
420 |
|
|
+#define _GNU_SOURCE /* setresgid() */ |
421 |
|
|
+#include <sys/types.h> |
422 |
|
|
+#include <unistd.h> |
423 |
|
|
|
424 |
|
|
#include "lib.h" |
425 |
|
|
#include "restrict-access.h" |
426 |
|
|
#include "env-util.h" |
427 |
|
|
|
428 |
|
|
#include <stdlib.h> |
429 |
|
|
-#include <unistd.h> |
430 |
|
|
#include <time.h> |
431 |
|
|
#include <grp.h> |
432 |
|
|
|
433 |
|
|
-void restrict_access_set_env(const char *user, uid_t uid, gid_t gid, |
434 |
|
|
+static gid_t primary_gid = (gid_t)-1, privileged_gid = (gid_t)-1; |
435 |
|
|
+static bool using_priv_gid = FALSE; |
436 |
|
|
+ |
437 |
|
|
+void restrict_access_set_env(const char *user, uid_t uid, |
438 |
|
|
+ gid_t gid, gid_t privileged_gid, |
439 |
|
|
const char *chroot_dir, |
440 |
|
|
gid_t first_valid_gid, gid_t last_valid_gid, |
441 |
|
|
const char *extra_groups) |
442 |
|
|
@@ -21,6 +28,10 @@ void restrict_access_set_env(const char *user, uid_t uid, gid_t gid, |
443 |
|
|
|
444 |
|
|
env_put(t_strdup_printf("RESTRICT_SETUID=%s", dec2str(uid))); |
445 |
|
|
env_put(t_strdup_printf("RESTRICT_SETGID=%s", dec2str(gid))); |
446 |
|
|
+ if (privileged_gid != (gid_t)-1) { |
447 |
|
|
+ env_put(t_strdup_printf("RESTRICT_SETGID_PRIV=%s", |
448 |
|
|
+ dec2str(privileged_gid))); |
449 |
|
|
+ } |
450 |
|
|
if (extra_groups != NULL && *extra_groups != '\0') { |
451 |
|
|
env_put(t_strconcat("RESTRICT_SETEXTRAGROUPS=", |
452 |
|
|
extra_groups, NULL)); |
453 |
|
|
@@ -36,7 +47,54 @@ void restrict_access_set_env(const char *user, uid_t uid, gid_t gid, |
454 |
|
|
} |
455 |
|
|
} |
456 |
|
|
|
457 |
|
|
-static gid_t *get_groups_list(int *gid_count_r) |
458 |
|
|
+static void restrict_init_groups(gid_t primary_gid, gid_t privileged_gid) |
459 |
|
|
+{ |
460 |
|
|
+ if (privileged_gid == (gid_t)-1) { |
461 |
|
|
+ if (primary_gid == getgid() && primary_gid == getegid()) { |
462 |
|
|
+ /* everything is already set */ |
463 |
|
|
+ return; |
464 |
|
|
+ } |
465 |
|
|
+ |
466 |
|
|
+ if (setgid(primary_gid) != 0) { |
467 |
|
|
+ i_fatal("setgid(%s) failed with euid=%s, " |
468 |
|
|
+ "gid=%s, egid=%s: %m", |
469 |
|
|
+ dec2str(primary_gid), dec2str(geteuid()), |
470 |
|
|
+ dec2str(getgid()), dec2str(getegid())); |
471 |
|
|
+ } |
472 |
|
|
+ return; |
473 |
|
|
+ } |
474 |
|
|
+ |
475 |
|
|
+ if (getegid() != 0 && primary_gid == getgid() && |
476 |
|
|
+ primary_gid == getegid()) { |
477 |
|
|
+ /* privileged_gid is hopefully in saved ID. if not, |
478 |
|
|
+ there's nothing we can do about it. */ |
479 |
|
|
+ return; |
480 |
|
|
+ } |
481 |
|
|
+ |
482 |
|
|
+#ifdef HAVE_SETRESGID |
483 |
|
|
+ if (setresgid(primary_gid, primary_gid, privileged_gid) != 0) { |
484 |
|
|
+ i_fatal("setresgid(%s,%s,%s) failed with euid=%s: %m", |
485 |
|
|
+ dec2str(primary_gid), dec2str(primary_gid), |
486 |
|
|
+ dec2str(privileged_gid), dec2str(geteuid())); |
487 |
|
|
+ } |
488 |
|
|
+#else |
489 |
|
|
+ /* real: primary_gid |
490 |
|
|
+ effective: privileged_gid |
491 |
|
|
+ saved: privileged_gid */ |
492 |
|
|
+ if (setregid(primary_gid, privileged_gid) != 0) { |
493 |
|
|
+ i_fatal("setregid(%s,%s) failed with euid=%s: %m", |
494 |
|
|
+ dec2str(primary_gid), dec2str(privileged_gid), |
495 |
|
|
+ dec2str(geteuid())); |
496 |
|
|
+ } |
497 |
|
|
+ /* effective: privileged_gid -> primary_gid */ |
498 |
|
|
+ if (setegid(privileged_gid) != 0) { |
499 |
|
|
+ i_fatal("setegid(%s) failed with euid=%s: %m", |
500 |
|
|
+ dec2str(privileged_gid), dec2str(geteuid())); |
501 |
|
|
+ } |
502 |
|
|
+#endif |
503 |
|
|
+} |
504 |
|
|
+ |
505 |
|
|
+static gid_t *get_groups_list(unsigned int *gid_count_r) |
506 |
|
|
{ |
507 |
|
|
gid_t *gid_list; |
508 |
|
|
int ret, gid_count; |
509 |
|
|
@@ -53,39 +111,28 @@ static gid_t *get_groups_list(int *gid_count_r) |
510 |
|
|
return gid_list; |
511 |
|
|
} |
512 |
|
|
|
513 |
|
|
-static void drop_restricted_groups(bool *have_root_group) |
514 |
|
|
+static void drop_restricted_groups(gid_t *gid_list, unsigned int *gid_count, |
515 |
|
|
+ bool *have_root_group) |
516 |
|
|
{ |
517 |
|
|
/* @UNSAFE */ |
518 |
|
|
+ gid_t first_valid, last_valid; |
519 |
|
|
const char *env; |
520 |
|
|
- gid_t *gid_list, first_valid_gid, last_valid_gid; |
521 |
|
|
- int i, used, gid_count; |
522 |
|
|
+ unsigned int i, used; |
523 |
|
|
|
524 |
|
|
env = getenv("RESTRICT_GID_FIRST"); |
525 |
|
|
- first_valid_gid = env == NULL ? 0 : (gid_t)strtoul(env, NULL, 10); |
526 |
|
|
+ first_valid = env == NULL ? 0 : (gid_t)strtoul(env, NULL, 10); |
527 |
|
|
env = getenv("RESTRICT_GID_LAST"); |
528 |
|
|
- last_valid_gid = env == NULL ? 0 : (gid_t)strtoul(env, NULL, 10); |
529 |
|
|
- |
530 |
|
|
- if (first_valid_gid == 0 && last_valid_gid == 0) |
531 |
|
|
- return; |
532 |
|
|
+ last_valid = env == NULL ? (gid_t)-1 : (gid_t)strtoul(env, NULL, 10); |
533 |
|
|
|
534 |
|
|
- t_push(); |
535 |
|
|
- gid_list = get_groups_list(&gid_count); |
536 |
|
|
- |
537 |
|
|
- for (i = 0, used = 0; i < gid_count; i++) { |
538 |
|
|
- if (gid_list[i] >= first_valid_gid && |
539 |
|
|
- (last_valid_gid == 0 || gid_list[i] <= last_valid_gid)) { |
540 |
|
|
+ for (i = 0, used = 0; i < *gid_count; i++) { |
541 |
|
|
+ if (gid_list[i] >= first_valid && |
542 |
|
|
+ (last_valid == (gid_t)-1 || gid_list[i] <= last_valid)) { |
543 |
|
|
if (gid_list[i] == 0) |
544 |
|
|
*have_root_group = TRUE; |
545 |
|
|
gid_list[used++] = gid_list[i]; |
546 |
|
|
} |
547 |
|
|
} |
548 |
|
|
- |
549 |
|
|
- if (used != gid_count) { |
550 |
|
|
- /* it did contain restricted groups, remove it */ |
551 |
|
|
- if (setgroups(used, gid_list) < 0) |
552 |
|
|
- i_fatal("setgroups() failed: %m"); |
553 |
|
|
- } |
554 |
|
|
- t_pop(); |
555 |
|
|
+ *gid_count = used; |
556 |
|
|
} |
557 |
|
|
|
558 |
|
|
static gid_t get_group_id(const char *name) |
559 |
|
|
@@ -101,68 +148,115 @@ static gid_t get_group_id(const char *name) |
560 |
|
|
return group->gr_gid; |
561 |
|
|
} |
562 |
|
|
|
563 |
|
|
-static void grant_extra_groups(const char *groups) |
564 |
|
|
+static void fix_groups_list(const char *extra_groups, |
565 |
|
|
+ bool preserve_existing, bool *have_root_group) |
566 |
|
|
{ |
567 |
|
|
- const char *const *tmp; |
568 |
|
|
- gid_t *gid_list; |
569 |
|
|
- int gid_count; |
570 |
|
|
- |
571 |
|
|
- t_push(); |
572 |
|
|
- tmp = t_strsplit(groups, ", "); |
573 |
|
|
- gid_list = get_groups_list(&gid_count); |
574 |
|
|
- for (; *tmp != NULL; tmp++) { |
575 |
|
|
- if (**tmp == '\0') |
576 |
|
|
- continue; |
577 |
|
|
- |
578 |
|
|
- if (!t_try_realloc(gid_list, (gid_count+1) * sizeof(gid_t))) |
579 |
|
|
- i_unreached(); |
580 |
|
|
- gid_list[gid_count++] = get_group_id(*tmp); |
581 |
|
|
+ gid_t gid, *gid_list, *gid_list2; |
582 |
|
|
+ const char *const *tmp, *empty = NULL; |
583 |
|
|
+ unsigned int i, gid_count; |
584 |
|
|
+ bool add_primary_gid; |
585 |
|
|
+ |
586 |
|
|
+ /* if we're using a privileged GID, we can temporarily drop our |
587 |
|
|
+ effective GID. we still want to be able to use its privileges, |
588 |
|
|
+ so add it to supplementary groups. */ |
589 |
|
|
+ add_primary_gid = privileged_gid != (gid_t)-1; |
590 |
|
|
+ |
591 |
|
|
+ tmp = extra_groups == NULL ? &empty : |
592 |
|
|
+ t_strsplit_spaces(extra_groups, ", "); |
593 |
|
|
+ |
594 |
|
|
+ if (preserve_existing) { |
595 |
|
|
+ gid_list = get_groups_list(&gid_count); |
596 |
|
|
+ drop_restricted_groups(gid_list, &gid_count, |
597 |
|
|
+ have_root_group); |
598 |
|
|
+ /* see if the list already contains the primary GID */ |
599 |
|
|
+ for (i = 0; i < gid_count; i++) { |
600 |
|
|
+ if (gid_list[i] == primary_gid) { |
601 |
|
|
+ add_primary_gid = FALSE; |
602 |
|
|
+ break; |
603 |
|
|
+ } |
604 |
|
|
+ } |
605 |
|
|
+ } else { |
606 |
|
|
+ gid_list = NULL; |
607 |
|
|
+ gid_count = 0; |
608 |
|
|
+ } |
609 |
|
|
+ if (gid_count == 0) { |
610 |
|
|
+ /* Some OSes don't like an empty groups list, |
611 |
|
|
+ so use the primary GID as the only one. */ |
612 |
|
|
+ gid_list = t_new(gid_t, 2); |
613 |
|
|
+ gid_list[0] = primary_gid; |
614 |
|
|
+ gid_count = 1; |
615 |
|
|
+ add_primary_gid = FALSE; |
616 |
|
|
} |
617 |
|
|
|
618 |
|
|
- if (setgroups(gid_count, gid_list) < 0) |
619 |
|
|
- i_fatal("setgroups() failed: %m"); |
620 |
|
|
+ if (*tmp != NULL || add_primary_gid) { |
621 |
|
|
+ /* @UNSAFE: add extra groups and/or primary GID to gids list */ |
622 |
|
|
+ gid_list2 = t_new(gid_t, gid_count + strarray_length(tmp) + 1); |
623 |
|
|
+ memcpy(gid_list2, gid_list, gid_count * sizeof(gid_t)); |
624 |
|
|
+ for (; *tmp != NULL; tmp++) { |
625 |
|
|
+ gid = get_group_id(*tmp); |
626 |
|
|
+ if (gid != primary_gid) |
627 |
|
|
+ gid_list2[gid_count++] = gid; |
628 |
|
|
+ } |
629 |
|
|
+ if (add_primary_gid) |
630 |
|
|
+ gid_list2[gid_count++] = primary_gid; |
631 |
|
|
+ gid_list = gid_list2; |
632 |
|
|
+ } |
633 |
|
|
|
634 |
|
|
- t_pop(); |
635 |
|
|
+ if (setgroups(gid_count, gid_list) < 0) { |
636 |
|
|
+ if (errno == EINVAL) { |
637 |
|
|
+ i_fatal("setgroups(%s) failed: Too many extra groups", |
638 |
|
|
+ extra_groups == NULL ? "" : extra_groups); |
639 |
|
|
+ } else { |
640 |
|
|
+ i_fatal("setgroups() failed: %m"); |
641 |
|
|
+ } |
642 |
|
|
+ } |
643 |
|
|
} |
644 |
|
|
|
645 |
|
|
void restrict_access_by_env(bool disallow_root) |
646 |
|
|
{ |
647 |
|
|
const char *env; |
648 |
|
|
- gid_t gid; |
649 |
|
|
uid_t uid; |
650 |
|
|
- bool have_root_group; |
651 |
|
|
+ bool is_root, have_root_group, preserve_groups = FALSE; |
652 |
|
|
+ bool allow_root_gid; |
653 |
|
|
+ |
654 |
|
|
+ is_root = geteuid() == 0; |
655 |
|
|
|
656 |
|
|
- /* groups - the getgid() checks are just so we don't fail if we're |
657 |
|
|
- not running as root and try to just use our own GID. Do this |
658 |
|
|
- before chrooting so initgroups() actually works. */ |
659 |
|
|
+ /* set the primary/privileged group */ |
660 |
|
|
env = getenv("RESTRICT_SETGID"); |
661 |
|
|
- gid = env == NULL ? 0 : (gid_t)strtoul(env, NULL, 10); |
662 |
|
|
- have_root_group = gid == 0; |
663 |
|
|
- if (gid != 0 && (gid != getgid() || gid != getegid())) { |
664 |
|
|
- if (setgid(gid) != 0) |
665 |
|
|
- i_fatal("setgid(%s) failed: %m", dec2str(gid)); |
666 |
|
|
- |
667 |
|
|
- env = getenv("RESTRICT_USER"); |
668 |
|
|
- if (env == NULL) { |
669 |
|
|
- /* user not known, use only this one group */ |
670 |
|
|
- if (setgroups(1, &gid) < 0) { |
671 |
|
|
- i_fatal("setgroups(%s) failed: %m", |
672 |
|
|
- dec2str(gid)); |
673 |
|
|
- } |
674 |
|
|
- } else { |
675 |
|
|
- if (initgroups(env, gid) != 0) { |
676 |
|
|
- i_fatal("initgroups(%s, %s) failed: %m", |
677 |
|
|
- env, dec2str(gid)); |
678 |
|
|
- } |
679 |
|
|
+ primary_gid = env == NULL || *env == '\0' ? (gid_t)-1 : |
680 |
|
|
+ (gid_t)strtoul(env, NULL, 10); |
681 |
|
|
+ env = getenv("RESTRICT_SETGID_PRIV"); |
682 |
|
|
+ privileged_gid = env == NULL || *env == '\0' ? (gid_t)-1 : |
683 |
|
|
+ (gid_t)strtoul(env, NULL, 10); |
684 |
|
|
+ |
685 |
|
|
+ have_root_group = primary_gid == 0; |
686 |
|
|
+ if (primary_gid != (gid_t)-1 || privileged_gid != (gid_t)-1) { |
687 |
|
|
+ if (primary_gid == (gid_t)-1) |
688 |
|
|
+ primary_gid = getegid(); |
689 |
|
|
+ restrict_init_groups(primary_gid, privileged_gid); |
690 |
|
|
+ } else { |
691 |
|
|
+ if (primary_gid == (gid_t)-1) |
692 |
|
|
+ primary_gid = getegid(); |
693 |
|
|
+ } |
694 |
|
|
|
695 |
|
|
- drop_restricted_groups(&have_root_group); |
696 |
|
|
+ /* set system user's groups */ |
697 |
|
|
+ env = getenv("RESTRICT_USER"); |
698 |
|
|
+ if (env != NULL && *env != '\0' && is_root) { |
699 |
|
|
+ if (initgroups(env, primary_gid) < 0) { |
700 |
|
|
+ i_fatal("initgroups(%s, %s) failed: %m", |
701 |
|
|
+ env, dec2str(primary_gid)); |
702 |
|
|
} |
703 |
|
|
+ preserve_groups = TRUE; |
704 |
|
|
} |
705 |
|
|
|
706 |
|
|
- /* grant additional groups to process */ |
707 |
|
|
+ /* add extra groups. if we set system user's groups, drop the |
708 |
|
|
+ restricted groups at the same time. */ |
709 |
|
|
env = getenv("RESTRICT_SETEXTRAGROUPS"); |
710 |
|
|
- if (env != NULL && *env != '\0') |
711 |
|
|
- grant_extra_groups(env); |
712 |
|
|
+ if (is_root) { |
713 |
|
|
+ t_push(); |
714 |
|
|
+ fix_groups_list(env, preserve_groups, &have_root_group); |
715 |
|
|
+ t_pop(); |
716 |
|
|
+ } |
717 |
|
|
|
718 |
|
|
/* chrooting */ |
719 |
|
|
env = getenv("RESTRICT_CHROOT"); |
720 |
|
|
@@ -190,7 +284,7 @@ void restrict_access_by_env(bool disallow_root) |
721 |
|
|
|
722 |
|
|
/* uid last */ |
723 |
|
|
env = getenv("RESTRICT_SETUID"); |
724 |
|
|
- uid = env == NULL ? 0 : (uid_t)strtoul(env, NULL, 10); |
725 |
|
|
+ uid = env == NULL || *env == '\0' ? 0 : (uid_t)strtoul(env, NULL, 10); |
726 |
|
|
if (uid != 0) { |
727 |
|
|
if (setuid(uid) != 0) |
728 |
|
|
i_fatal("setuid(%s) failed: %m", dec2str(uid)); |
729 |
|
|
@@ -206,12 +300,20 @@ void restrict_access_by_env(bool disallow_root) |
730 |
|
|
} |
731 |
|
|
|
732 |
|
|
env = getenv("RESTRICT_GID_FIRST"); |
733 |
|
|
- if ((!have_root_group || (env != NULL && atoi(env) != 0)) && uid != 0) { |
734 |
|
|
+ if (env != NULL && atoi(env) != 0) |
735 |
|
|
+ allow_root_gid = FALSE; |
736 |
|
|
+ else if (primary_gid == 0 || privileged_gid == 0) |
737 |
|
|
+ allow_root_gid = TRUE; |
738 |
|
|
+ else |
739 |
|
|
+ allow_root_gid = FALSE; |
740 |
|
|
+ |
741 |
|
|
+ if (!allow_root_gid && uid != 0) { |
742 |
|
|
if (getgid() == 0 || getegid() == 0 || setgid(0) == 0) { |
743 |
|
|
- if (gid == 0) |
744 |
|
|
+ if (primary_gid == 0) |
745 |
|
|
i_fatal("GID 0 isn't permitted"); |
746 |
|
|
i_fatal("We couldn't drop root group privileges " |
747 |
|
|
- "(wanted=%s, gid=%s, egid=%s)", dec2str(gid), |
748 |
|
|
+ "(wanted=%s, gid=%s, egid=%s)", |
749 |
|
|
+ dec2str(primary_gid), |
750 |
|
|
dec2str(getgid()), dec2str(getegid())); |
751 |
|
|
} |
752 |
|
|
} |
753 |
|
|
@@ -220,8 +322,43 @@ void restrict_access_by_env(bool disallow_root) |
754 |
|
|
env_put("RESTRICT_USER="); |
755 |
|
|
env_put("RESTRICT_CHROOT="); |
756 |
|
|
env_put("RESTRICT_SETUID="); |
757 |
|
|
- env_put("RESTRICT_SETGID="); |
758 |
|
|
+ if (privileged_gid == (gid_t)-1) { |
759 |
|
|
+ /* if we're dropping privileges before executing and |
760 |
|
|
+ a privileged group is set, the groups must be fixed |
761 |
|
|
+ after exec */ |
762 |
|
|
+ env_put("RESTRICT_SETGID="); |
763 |
|
|
+ env_put("RESTRICT_SETGID_PRIV="); |
764 |
|
|
+ } |
765 |
|
|
env_put("RESTRICT_SETEXTRAGROUPS="); |
766 |
|
|
env_put("RESTRICT_GID_FIRST="); |
767 |
|
|
env_put("RESTRICT_GID_LAST="); |
768 |
|
|
} |
769 |
|
|
+ |
770 |
|
|
+int restrict_access_use_priv_gid(void) |
771 |
|
|
+{ |
772 |
|
|
+ i_assert(!using_priv_gid); |
773 |
|
|
+ |
774 |
|
|
+ if (privileged_gid == (gid_t)-1) |
775 |
|
|
+ return 0; |
776 |
|
|
+ if (setegid(privileged_gid) < 0) { |
777 |
|
|
+ i_error("setegid(privileged) failed: %m"); |
778 |
|
|
+ return -1; |
779 |
|
|
+ } |
780 |
|
|
+ using_priv_gid = TRUE; |
781 |
|
|
+ return 0; |
782 |
|
|
+} |
783 |
|
|
+ |
784 |
|
|
+void restrict_access_drop_priv_gid(void) |
785 |
|
|
+{ |
786 |
|
|
+ if (!using_priv_gid) |
787 |
|
|
+ return; |
788 |
|
|
+ |
789 |
|
|
+ if (setegid(primary_gid) < 0) |
790 |
|
|
+ i_fatal("setegid(primary) failed: %m"); |
791 |
|
|
+ using_priv_gid = FALSE; |
792 |
|
|
+} |
793 |
|
|
+ |
794 |
|
|
+bool restrict_access_have_priv_gid(void) |
795 |
|
|
+{ |
796 |
|
|
+ return privileged_gid != (gid_t)-1; |
797 |
|
|
+} |
798 |
|
|
diff --git a/src/lib/restrict-access.h b/src/lib/restrict-access.h |
799 |
|
|
index 69103a8..a72ff90 100644 |
800 |
|
|
--- a/src/lib/restrict-access.h |
801 |
|
|
+++ b/src/lib/restrict-access.h |
802 |
|
|
@@ -2,8 +2,10 @@ |
803 |
|
|
#define __RESTRICT_ACCESS_H |
804 |
|
|
|
805 |
|
|
/* set environment variables so they can be read with |
806 |
|
|
- restrict_access_by_env() */ |
807 |
|
|
-void restrict_access_set_env(const char *user, uid_t uid, gid_t gid, |
808 |
|
|
+ restrict_access_by_env(). If privileged_gid != (gid_t)-1, |
809 |
|
|
+ the privileged GID can be temporarily enabled/disabled. */ |
810 |
|
|
+void restrict_access_set_env(const char *user, uid_t uid, |
811 |
|
|
+ gid_t gid, gid_t privileged_gid, |
812 |
|
|
const char *chroot_dir, |
813 |
|
|
gid_t first_valid_gid, gid_t last_valid_gid, |
814 |
|
|
const char *extra_groups); |
815 |
|
|
@@ -13,4 +15,11 @@ void restrict_access_set_env(const char *user, uid_t uid, gid_t gid, |
816 |
|
|
environment settings and we have root uid or gid. */ |
817 |
|
|
void restrict_access_by_env(bool disallow_root); |
818 |
|
|
|
819 |
|
|
+/* If privileged_gid was set, these functions can be used to temporarily |
820 |
|
|
+ gain access to the group. */ |
821 |
|
|
+int restrict_access_use_priv_gid(void); |
822 |
|
|
+void restrict_access_drop_priv_gid(void); |
823 |
|
|
+/* Returns TRUE if privileged GID exists for this process. */ |
824 |
|
|
+bool restrict_access_have_priv_gid(void); |
825 |
|
|
+ |
826 |
|
|
#endif |
827 |
|
|
diff --git a/src/master/auth-process.c b/src/master/auth-process.c |
828 |
|
|
index 56f8499..4d00dfc 100644 |
829 |
|
|
--- a/src/master/auth-process.c |
830 |
|
|
+++ b/src/master/auth-process.c |
831 |
|
|
@@ -413,8 +413,8 @@ static void auth_set_environment(struct auth_settings *set) |
832 |
|
|
int i; |
833 |
|
|
|
834 |
|
|
/* setup access environment */ |
835 |
|
|
- restrict_access_set_env(set->user, set->uid, set->gid, set->chroot, |
836 |
|
|
- 0, 0, NULL); |
837 |
|
|
+ restrict_access_set_env(set->user, set->uid, set->gid, |
838 |
|
|
+ (gid_t)-1, set->chroot, 0, 0, NULL); |
839 |
|
|
|
840 |
|
|
/* set other environment */ |
841 |
|
|
env_put("DOVECOT_MASTER=1"); |
842 |
|
|
diff --git a/src/master/login-process.c b/src/master/login-process.c |
843 |
|
|
index 7e4f5ea..b616d0a 100644 |
844 |
|
|
--- a/src/master/login-process.c |
845 |
|
|
+++ b/src/master/login-process.c |
846 |
|
|
@@ -519,7 +519,7 @@ static void login_process_init_env(struct login_group *group, pid_t pid) |
847 |
|
|
parameter since we don't want to call initgroups() for login |
848 |
|
|
processes. */ |
849 |
|
|
restrict_access_set_env(NULL, set->login_uid, |
850 |
|
|
- set->server->login_gid, |
851 |
|
|
+ set->server->login_gid, (gid_t)-1, |
852 |
|
|
set->login_chroot ? set->login_dir : NULL, |
853 |
|
|
0, 0, NULL); |
854 |
|
|
|
855 |
|
|
diff --git a/src/master/mail-process.c b/src/master/mail-process.c |
856 |
|
|
index 38c48e8..5948b5b 100644 |
857 |
|
|
--- a/src/master/mail-process.c |
858 |
|
|
+++ b/src/master/mail-process.c |
859 |
|
|
@@ -589,9 +589,10 @@ bool create_mail_process(enum process_type process_type, struct settings *set, |
860 |
|
|
|
861 |
|
|
/* setup environment - set the most important environment first |
862 |
|
|
(paranoia about filling up environment without noticing) */ |
863 |
|
|
- restrict_access_set_env(system_user, uid, gid, chroot_dir, |
864 |
|
|
+ restrict_access_set_env(system_user, uid, gid, set->mail_priv_gid_t, |
865 |
|
|
+ chroot_dir, |
866 |
|
|
set->first_valid_gid, set->last_valid_gid, |
867 |
|
|
- set->mail_extra_groups); |
868 |
|
|
+ set->mail_access_groups); |
869 |
|
|
|
870 |
|
|
restrict_process_size(set->mail_process_size, (unsigned int)-1); |
871 |
|
|
|
872 |
|
|
@@ -699,8 +700,13 @@ bool create_mail_process(enum process_type process_type, struct settings *set, |
873 |
|
|
any errors above will be logged */ |
874 |
|
|
closelog(); |
875 |
|
|
|
876 |
|
|
- if (set->mail_drop_priv_before_exec) |
877 |
|
|
+ if (set->mail_drop_priv_before_exec) { |
878 |
|
|
restrict_access_by_env(TRUE); |
879 |
|
|
+ /* privileged GID is now only in saved-GID. if we want to |
880 |
|
|
+ preserve it accross exec, it needs to be temporarily |
881 |
|
|
+ in effective gid */ |
882 |
|
|
+ restrict_access_use_priv_gid(); |
883 |
|
|
+ } |
884 |
|
|
|
885 |
|
|
client_process_exec(set->mail_executable, title); |
886 |
|
|
i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", |
887 |
|
|
diff --git a/src/master/master-settings-defs.c b/src/master/master-settings-defs.c |
888 |
|
|
index 5f051fb..56d0c12 100644 |
889 |
|
|
--- a/src/master/master-settings-defs.c |
890 |
|
|
+++ b/src/master/master-settings-defs.c |
891 |
|
|
@@ -58,6 +58,8 @@ static struct setting_def setting_defs[] = { |
892 |
|
|
DEF(SET_INT, first_valid_gid), |
893 |
|
|
DEF(SET_INT, last_valid_gid), |
894 |
|
|
DEF(SET_STR, mail_extra_groups), |
895 |
|
|
+ DEF(SET_STR, mail_access_groups), |
896 |
|
|
+ DEF(SET_STR, mail_privileged_group), |
897 |
|
|
|
898 |
|
|
DEF(SET_STR, default_mail_env), |
899 |
|
|
DEF(SET_STR, mail_location), |
900 |
|
|
diff --git a/src/master/master-settings.c b/src/master/master-settings.c |
901 |
|
|
index 08b2b70..c3bff8c 100644 |
902 |
|
|
--- a/src/master/master-settings.c |
903 |
|
|
+++ b/src/master/master-settings.c |
904 |
|
|
@@ -21,6 +21,7 @@ |
905 |
|
|
#include <sys/stat.h> |
906 |
|
|
#include <sys/wait.h> |
907 |
|
|
#include <pwd.h> |
908 |
|
|
+#include <grp.h> |
909 |
|
|
|
910 |
|
|
enum settings_type { |
911 |
|
|
SETTINGS_TYPE_ROOT, |
912 |
|
|
@@ -207,6 +208,8 @@ struct settings default_settings = { |
913 |
|
|
MEMBER(first_valid_gid) 1, |
914 |
|
|
MEMBER(last_valid_gid) 0, |
915 |
|
|
MEMBER(mail_extra_groups) "", |
916 |
|
|
+ MEMBER(mail_access_groups) "", |
917 |
|
|
+ MEMBER(mail_privileged_group) "", |
918 |
|
|
|
919 |
|
|
MEMBER(default_mail_env) "", |
920 |
|
|
MEMBER(mail_location) "", |
921 |
|
|
@@ -365,6 +368,25 @@ static bool get_login_uid(struct settings *set) |
922 |
|
|
return TRUE; |
923 |
|
|
} |
924 |
|
|
|
925 |
|
|
+static bool parse_gid(const char *str, gid_t *gid_r) |
926 |
|
|
+{ |
927 |
|
|
+ struct group *gr; |
928 |
|
|
+ char *p; |
929 |
|
|
+ |
930 |
|
|
+ if (*str >= '0' && *str <= '9') { |
931 |
|
|
+ *gid_r = (gid_t)strtoul(str, &p, 10); |
932 |
|
|
+ if (*p == '\0') |
933 |
|
|
+ return TRUE; |
934 |
|
|
+ } |
935 |
|
|
+ |
936 |
|
|
+ gr = getgrnam(str); |
937 |
|
|
+ if (gr == NULL) |
938 |
|
|
+ return FALSE; |
939 |
|
|
+ |
940 |
|
|
+ *gid_r = gr->gr_gid; |
941 |
|
|
+ return TRUE; |
942 |
|
|
+} |
943 |
|
|
+ |
944 |
|
|
static bool auth_settings_verify(struct auth_settings *auth) |
945 |
|
|
{ |
946 |
|
|
struct passwd *pw; |
947 |
|
|
@@ -628,9 +650,35 @@ static bool settings_verify(struct settings *set) |
948 |
|
|
const char *dir; |
949 |
|
|
int facility; |
950 |
|
|
|
951 |
|
|
+ set->mail_priv_gid_t = (gid_t)-1; |
952 |
|
|
+ |
953 |
|
|
if (!get_login_uid(set)) |
954 |
|
|
return FALSE; |
955 |
|
|
|
956 |
|
|
+ if (*set->mail_privileged_group != '\0') { |
957 |
|
|
+ if (!parse_gid(set->mail_privileged_group, |
958 |
|
|
+ &set->mail_priv_gid_t)) { |
959 |
|
|
+ i_error("Non-existing mail_privileged_group: %s", |
960 |
|
|
+ set->mail_privileged_group); |
961 |
|
|
+ return FALSE; |
962 |
|
|
+ } |
963 |
|
|
+ } |
964 |
|
|
+ if (*set->mail_extra_groups != '\0') { |
965 |
|
|
+ if (*set->mail_access_groups != '\0') { |
966 |
|
|
+ i_error("Can't set both mail_extra_groups " |
967 |
|
|
+ "and mail_access_groups"); |
968 |
|
|
+ return FALSE; |
969 |
|
|
+ } |
970 |
|
|
+ if (!set->server->warned_mail_extra_groups) { |
971 |
|
|
+ set->server->warned_mail_extra_groups = TRUE; |
972 |
|
|
+ i_warning("mail_extra_groups setting was often used " |
973 |
|
|
+ "insecurely so it is now deprecated, " |
974 |
|
|
+ "use mail_access_groups or " |
975 |
|
|
+ "mail_privileged_group instead"); |
976 |
|
|
+ } |
977 |
|
|
+ set->mail_access_groups = set->mail_extra_groups; |
978 |
|
|
+ } |
979 |
|
|
+ |
980 |
|
|
if (set->protocol == MAIL_PROTOCOL_POP3 && |
981 |
|
|
*set->pop3_uidl_format == '\0') { |
982 |
|
|
i_error("POP3 enabled but pop3_uidl_format not set"); |
983 |
|
|
diff --git a/src/master/master-settings.h b/src/master/master-settings.h |
984 |
|
|
index a539035..e7be6e1 100644 |
985 |
|
|
--- a/src/master/master-settings.h |
986 |
|
|
+++ b/src/master/master-settings.h |
987 |
|
|
@@ -66,6 +66,8 @@ struct settings { |
988 |
|
|
unsigned int first_valid_uid, last_valid_uid; |
989 |
|
|
unsigned int first_valid_gid, last_valid_gid; |
990 |
|
|
const char *mail_extra_groups; |
991 |
|
|
+ const char *mail_access_groups; |
992 |
|
|
+ const char *mail_privileged_group; |
993 |
|
|
|
994 |
|
|
const char *default_mail_env; |
995 |
|
|
const char *mail_location; |
996 |
|
|
@@ -125,6 +127,7 @@ struct settings { |
997 |
|
|
int listen_fd, ssl_listen_fd; |
998 |
|
|
|
999 |
|
|
uid_t login_uid; |
1000 |
|
|
+ gid_t mail_priv_gid_t; |
1001 |
|
|
|
1002 |
|
|
struct ip_addr listen_ip, ssl_listen_ip; |
1003 |
|
|
unsigned int listen_port, ssl_listen_port; |
1004 |
|
|
@@ -235,6 +238,7 @@ struct server_settings { |
1005 |
|
|
array_t ARRAY_DEFINE(dicts, const char *); |
1006 |
|
|
|
1007 |
|
|
gid_t login_gid; |
1008 |
|
|
+ unsigned int warned_mail_extra_groups:1; |
1009 |
|
|
}; |
1010 |
|
|
|
1011 |
|
|
extern struct server_settings *settings_root; |