/[smeserver]/rpms/dovecot/sme7/dovecot-1.0-CVE-2008-1199.patch
ViewVC logotype

Contents of /rpms/dovecot/sme7/dovecot-1.0-CVE-2008-1199.patch

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download)
Fri Sep 19 21:26:12 2008 UTC (16 years, 1 month ago) by slords
Branch: MAIN
CVS Tags: dovecot-1_0_7-2_el4_sme, HEAD
Import COS5 version of dovecot

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;

admin@koozali.org
ViewVC Help
Powered by ViewVC 1.2.1 RSS 2.0 feed