eacces-error.c revision e59faf65ce864fe95dc00f5d52b8323cdbd0608a
e59faf65ce864fe95dc00f5d52b8323cdbd0608aTimo Sirainen/* Copyright (c) 2007-2010 Dovecot authors, see the included COPYING file */
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen#include "lib.h"
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen#include "str.h"
60e4a32f9668ff512192e54929ad71a9f2066b6fTimo Sirainen#include "abspath.h"
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen#include "restrict-access.h"
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen#include "eacces-error.h"
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen#include <sys/stat.h>
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen#include <unistd.h>
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen#include <pwd.h>
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen#include <grp.h>
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainenstatic bool is_in_group(gid_t gid)
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen{
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen const gid_t *gids;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen unsigned int i, count;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (getegid() == gid)
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return TRUE;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen gids = restrict_get_groups_list(&count);
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen for (i = 0; i < count; i++) {
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (gids[i] == gid)
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return TRUE;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen }
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return FALSE;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen}
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainenstatic int test_access(const char *path, int mode, string_t *errmsg)
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen{
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen struct stat st;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (getuid() == geteuid()) {
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (access(path, mode) == 0)
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return 0;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (errno != EACCES) {
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen str_printfa(errmsg, " access(%s, %d) failed: %m",
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen path, mode);
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen }
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return -1;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen }
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen /* access() uses real uid, not effective uid.
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen we'll have to do these checks manually. */
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen switch (mode) {
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen case X_OK:
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (stat(t_strconcat(path, "/test", NULL), &st) == 0)
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return 0;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (errno == ENOENT || errno == ENOTDIR)
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return 0;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (errno != EACCES)
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen str_printfa(errmsg, " stat(%s/test) failed: %m", path);
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return -1;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen case R_OK:
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen mode = 04;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen break;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen case W_OK:
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen mode = 02;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen break;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen default:
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen i_unreached();
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen }
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (stat(path, &st) < 0) {
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen str_printfa(errmsg, " stat(%s) failed: %m", path);
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return -1;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen }
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (st.st_uid == geteuid())
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen st.st_mode = (st.st_mode & 0700) >> 6;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen else if (is_in_group(st.st_gid))
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen st.st_mode = (st.st_mode & 0070) >> 3;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen else
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen st.st_mode = (st.st_mode & 0007);
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if ((st.st_mode & mode) != 0)
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return 0;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen errno = EACCES;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen return -1;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen}
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainenstatic const char *
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Siraineneacces_error_get_full(const char *func, const char *path, bool creating)
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen{
761c440316cc003817b3375b627287eceb6f6dceTimo Sirainen const char *prev_path = path, *dir, *p;
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen const char *pw_name = NULL, *gr_name = NULL;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen const struct passwd *pw;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen const struct group *group;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen string_t *errmsg;
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen struct stat st, dir_st;
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen int orig_errno, ret = -1;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen orig_errno = errno;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen errmsg = t_str_new(256);
761c440316cc003817b3375b627287eceb6f6dceTimo Sirainen str_printfa(errmsg, "%s(%s)", func, path);
761c440316cc003817b3375b627287eceb6f6dceTimo Sirainen if (*path != '/') {
60e4a32f9668ff512192e54929ad71a9f2066b6fTimo Sirainen if (t_get_current_dir(&dir) == 0)
761c440316cc003817b3375b627287eceb6f6dceTimo Sirainen str_printfa(errmsg, " in directory %s", dir);
761c440316cc003817b3375b627287eceb6f6dceTimo Sirainen }
761c440316cc003817b3375b627287eceb6f6dceTimo Sirainen str_printfa(errmsg, " failed: Permission denied (euid=%s",
761c440316cc003817b3375b627287eceb6f6dceTimo Sirainen dec2str(geteuid()));
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen pw = getpwuid(geteuid());
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen if (pw != NULL) {
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen pw_name = t_strdup(pw->pw_name);
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen str_printfa(errmsg, "(%s)", pw_name);
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen }
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen str_printfa(errmsg, " egid=%s", dec2str(getegid()));
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen group = getgrgid(getegid());
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen if (group != NULL) {
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen gr_name = t_strdup(group->gr_name);
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen str_printfa(errmsg, "(%s)", gr_name);
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen }
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen dir = "/"; memset(&dir_st, 0, sizeof(dir_st));
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen while ((p = strrchr(prev_path, '/')) != NULL) {
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen dir = t_strdup_until(prev_path, p);
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen ret = stat(dir, &st);
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen if (ret == 0)
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen break;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen if (errno == EACCES) {
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen /* see if we have access to parent directory */
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen } else if (errno == ENOENT && creating) {
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen /* probably mkdir_parents() failed here, find the first
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen parent directory we couldn't create */
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen } else {
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen /* some other error, can't handle it */
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen str_printfa(errmsg, " stat(%s) failed: %m", dir);
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen break;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen }
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen prev_path = dir;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen dir = "/";
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen dir_st = st;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen }
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen if (ret == 0) {
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen /* dir is the first parent directory we can stat() */
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen if (test_access(dir, X_OK, errmsg) < 0) {
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen if (errno == EACCES)
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen str_printfa(errmsg, " missing +x perm: %s", dir);
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen } else if (creating && test_access(dir, W_OK, errmsg) < 0) {
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen if (errno == EACCES)
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen str_printfa(errmsg, " missing +w perm: %s", dir);
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen } else if (prev_path == path &&
5e9a39da0a9aba60b50e2c1d401f102703431b73Timo Sirainen test_access(path, R_OK, errmsg) < 0) {
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen if (errno == EACCES)
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen str_printfa(errmsg, " missing +r perm: %s", path);
826d9b7a1dec011e4777b334af5f4dc4feebb64bTimo Sirainen } else if (!creating && test_access(path, W_OK, errmsg) < 0) {
826d9b7a1dec011e4777b334af5f4dc4feebb64bTimo Sirainen /* this produces a wrong error if the operation didn't
826d9b7a1dec011e4777b334af5f4dc4feebb64bTimo Sirainen actually need write permissions, but we don't know
826d9b7a1dec011e4777b334af5f4dc4feebb64bTimo Sirainen it here.. */
826d9b7a1dec011e4777b334af5f4dc4feebb64bTimo Sirainen if (errno == EACCES)
826d9b7a1dec011e4777b334af5f4dc4feebb64bTimo Sirainen str_printfa(errmsg, " missing +w perm: %s", path);
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen } else
75804457bc8837885b3b6200e7fd680264872014Timo Sirainen str_printfa(errmsg, " UNIX perms appear ok, "
75804457bc8837885b3b6200e7fd680264872014Timo Sirainen "some security policy wrong?");
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen }
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen /* check and warn if another uid has the same name */
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen if (pw_name != NULL && dir_st.st_uid != geteuid()) {
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen pw = getpwuid(dir_st.st_uid);
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen if (pw != NULL && strcmp(pw->pw_name, pw_name) == 0) {
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen str_printfa(errmsg, ", dir uid=%s(%s)",
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen dec2str(dir_st.st_uid), pw_name);
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen }
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen }
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen if (gr_name != NULL && dir_st.st_gid != getegid()) {
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen group = getgrgid(dir_st.st_gid);
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen if (group != NULL && strcmp(group->gr_name, gr_name) == 0) {
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen str_printfa(errmsg, ", dir gid=%s(%s)",
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen dec2str(dir_st.st_gid), gr_name);
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen }
7ca397e910d2b267bcfaecbcdf9b23523c639776Timo Sirainen }
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen str_append_c(errmsg, ')');
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen errno = orig_errno;
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen return str_c(errmsg);
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen}
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainenconst char *eacces_error_get(const char *func, const char *path)
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen{
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen return eacces_error_get_full(func, path, FALSE);
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen}
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainenconst char *eacces_error_get_creating(const char *func, const char *path)
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen{
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen return eacces_error_get_full(func, path, TRUE);
5cdd1691e5185ecfe424f5de7b6f697813b88ba2Timo Sirainen}
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainenconst char *eperm_error_get_chgrp(const char *func, const char *path,
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen gid_t gid, const char *gid_origin)
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen{
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen string_t *errmsg;
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen const struct group *group;
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen int orig_errno = errno;
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen errmsg = t_str_new(256);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_printfa(errmsg, "%s(%s, -1, %s", func, path, dec2str(gid));
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen group = getgrgid(gid);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen if (group != NULL)
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_printfa(errmsg, "(%s)", group->gr_name);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_printfa(errmsg, ") failed: Operation not permitted (egid=%s",
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen dec2str(getegid()));
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen group = getgrgid(getegid());
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen if (group != NULL)
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_printfa(errmsg, "(%s)", group->gr_name);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen if (gid_origin != NULL)
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_printfa(errmsg, ", group based on %s", gid_origin);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_append_c(errmsg, ')');
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen errno = orig_errno;
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen return str_c(errmsg);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen}