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

Annotation 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 - (hide 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 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;

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