#include <time.h>
#include <grp.h>
+#define HARD_MAX_GROUPS 10240
+
+#ifndef NGROUPS_MAX
+# define NGROUPS_MAX 128
+#endif
+
void restrict_access_set_env(const char *user, uid_t uid, gid_t gid,
- const char *chroot_dir)
+ const char *chroot_dir,
+ gid_t first_valid_gid, gid_t last_valid_gid)
{
if (user != NULL && *user != '\0')
env_put(t_strconcat("RESTRICT_USER=", user, NULL));
env_put(t_strdup_printf("RESTRICT_SETUID=%s", dec2str(uid)));
env_put(t_strdup_printf("RESTRICT_SETGID=%s", dec2str(gid)));
+
+ if (first_valid_gid != 0) {
+ env_put(t_strdup_printf("RESTRICT_GID_FIRST=%s",
+ dec2str(first_valid_gid)));
+ }
+ if (last_valid_gid != 0) {
+ env_put(t_strdup_printf("RESTRICT_GID_LAST=%s",
+ dec2str(last_valid_gid)));
+ }
}
-void restrict_access_by_env(int disallow_root)
+static void drop_restricted_groups(void)
{
+ /* @UNSAFE */
const char *env;
- gid_t gid;
- uid_t uid;
-
- /* chrooting */
- env = getenv("RESTRICT_CHROOT");
- if (env != NULL) {
- /* kludge: localtime() must be called before chroot(),
- or the timezone isn't known */
- time_t t = 0;
- (void)localtime(&t);
+ gid_t *gid_list, first_valid_gid, last_valid_gid;
+ int ret, i, gid_count;
+
+ env = getenv("RESTRICT_GID_FIRST");
+ first_valid_gid = env == NULL ? 0 : (gid_t)atol(env);
+ env = getenv("RESTRICT_GID_LAST");
+ last_valid_gid = env == NULL ? 0 : (gid_t)atol(env);
+
+ if (first_valid_gid == 0 && last_valid_gid == 0)
+ return;
+
+ gid_count = NGROUPS_MAX;
+ gid_list = t_buffer_get(sizeof(gid_t) * gid_count);
+ while ((ret = getgroups(gid_count, gid_list)) < 0) {
+ if (errno != EINVAL ||
+ gid_count < HARD_MAX_GROUPS)
+ i_fatal("getgroups() failed: %m");
+
+ gid_count *= 2;
+ gid_list = t_buffer_reget(gid_list, sizeof(gid_t) * gid_count);
+ }
- if (chroot(env) != 0)
- i_fatal("chroot(%s) failed: %m", env);
+ gid_count = 0;
+ for (i = 0; i < ret; i++) {
+ if (gid_list[i] >= first_valid_gid &&
+ (last_valid_gid == 0 || gid_list[i] <= last_valid_gid))
+ gid_list[gid_count++] = gid_list[i];
+ }
- if (chdir("/") != 0)
- i_fatal("chdir(/) failed: %m");
+ if (ret != gid_count) {
+ /* it did contain 0, remove it */
+ if (setgroups(gid_count, gid_list) < 0)
+ i_fatal("setgroups() failed: %m");
}
+}
+
+void restrict_access_by_env(int disallow_root)
+{
+ const char *env;
+ gid_t gid;
+ uid_t uid;
/* groups - the getgid() checks are just so we don't fail if we're
- not running as root and try to just use our own GID. */
+ not running as root and try to just use our own GID. Do this
+ before chrooting so initgroups() actually works. */
env = getenv("RESTRICT_SETGID");
- gid = env == NULL ? 0 : (gid_t) atol(env);
+ gid = env == NULL ? 0 : (gid_t)atol(env);
if (gid != 0 && (gid != getgid() || gid != getegid())) {
if (setgid(gid) != 0)
i_fatal("setgid(%s) failed: %m", dec2str(gid));
env = getenv("RESTRICT_USER");
if (env == NULL) {
/* user not known, use only this one group */
- (void)setgroups(1, &gid);
+ if (setgroups(1, &gid) < 0) {
+ i_fatal("setgroups(%s) failed: %m",
+ dec2str(gid));
+ }
} else {
if (initgroups(env, gid) != 0) {
i_fatal("initgroups(%s, %s) failed: %m",
env, dec2str(gid));
}
+
+ drop_restricted_groups();
}
}
+ /* chrooting */
+ env = getenv("RESTRICT_CHROOT");
+ if (env != NULL) {
+ /* kludge: localtime() must be called before chroot(),
+ or the timezone isn't known */
+ time_t t = 0;
+ (void)localtime(&t);
+
+ if (chroot(env) != 0)
+ i_fatal("chroot(%s) failed: %m", env);
+
+ if (chdir("/") != 0)
+ i_fatal("chdir(/) failed: %m");
+ }
+
/* uid last */
env = getenv("RESTRICT_SETUID");
- uid = env == NULL ? 0 : (uid_t) atol(env);
+ uid = env == NULL ? 0 : (uid_t)atol(env);
if (uid != 0) {
if (setuid(uid) != 0)
i_fatal("setuid(%s) failed: %m", dec2str(uid));
return FALSE;
}
- if (uid != 0 && gid == 0) {
- i_error("mail process isn't allowed to be in group 0");
- return FALSE;
- }
-
if (uid < (uid_t)set->first_valid_uid ||
(set->last_valid_uid != 0 && uid > (uid_t)set->last_valid_uid)) {
i_error("mail process isn't allowed to use UID %s "
if (gid < (gid_t)set->first_valid_gid ||
(set->last_valid_gid != 0 && gid > (gid_t)set->last_valid_gid)) {
- i_error("mail process isn't allowed to use "
- "GID %s (UID is %s)", dec2str(gid), dec2str(uid));
+ i_error("mail process isn't allowed to use primary group ID %s "
+ "with UID %s (see first_valid_gid in config file).",
+ dec2str(gid), dec2str(uid));
return FALSE;
}
/* setup environment - set the most important environment first
(paranoia about filling up environment without noticing) */
restrict_access_set_env(data + reply->system_user_idx,
- reply->uid, reply->gid, chroot_dir);
+ reply->uid, reply->gid, chroot_dir,
+ set->first_valid_gid, set->last_valid_gid);
restrict_process_size(process_size, (unsigned int)-1);