#include "chroot.hpp"
#include <grp.h>
#include <pwd.h>
#include <sys/prctl.h>
#if (HAVE_SYS_CAPABILITY_H)
#include <sys/capability.h>
#else
MESSAGE(capabilities disabled - root might be needed at runtime)
#endif


static bool goto_jail(void*&, char* value) {
    char* p = strchr(value, ':'); // so its "[user:]path"
    if (p) {
        *p = '\0';
        ++p;
        return jail(p, value);
    } else {
        return jail(value, NULL);
    }
}


CONF_DEF(config) {
    ConfigKey jail;
};
CONF_INIT(config) {
    CONF_KEY_INIT(jail, false, false, &goto_jail);
}


bool jail(const char* path, const char* user) {
    if ((user || path) && (geteuid() != 0)) {
        log(error, "!r00t");
        return false;
    }

    uid_t uid = 0;
    gid_t gid = 0;
    if (user) {
        errno = 0;
        struct passwd* pass = getpwnam(user);
        if (!pass) {
            log_errno(error, "getpwnam(%s)", user);
            return false;
        }
        uid = pass->pw_uid;
        gid = pass->pw_gid;
    }

    #if (HAVE_SYS_CAPABILITY_H)
        cap_t curr_caps;
        cap_value_t wanted_caps[] = { CAP_NET_BIND_SERVICE, CAP_NET_ADMIN };
        if (user) {
            curr_caps = cap_get_proc();
            if (cap_set_flag(curr_caps, CAP_PERMITTED, 1, wanted_caps, CAP_SET) != 0) {
                log_errno(error, "cap_set_flag(CAP_PERMITTED)");
                return false; // TODO: need cleanup in case of errors?
            } else if (cap_set_proc(curr_caps) != 0) {
                log_errno(error, "cap_set_proc(CAP_PERMITTED)");
                return false;
            } else if (prctl(PR_SET_KEEPCAPS, 1) == -1) {
                log_errno(error, "prctl(PR_SET_KEEPCAPS)");
                return false;
            } else if (cap_free(curr_caps) != 0) {
                log_errno(error, "cap_free");
                return false;
            }
        }
    #endif

    if (path) {
        if (chdir(path) != 0) {
            log_errno(error, "chdir(%s)", path);
            return false;
        } else if (chroot(path) != 0) {
            log_errno(error, "chroot(%s)", path);
            return false;
        } else if (chdir("/") != 0) {
            log_errno(error, "chdir(%s,/)", path);
            return false;
        }
    }

    if (user) {
        if (setgid(gid) != 0) {
            log_errno(error, "setgid(%s,%d)", user, (int)gid);
            return false;
        } else if (setuid(uid) != 0) {
            log_errno(error, "setuid(%s,%d)", user, (int)uid);
            return false;
        }
    }

    #if (HAVE_SYS_CAPABILITY_H)
        if (user) {
            curr_caps = cap_get_proc();
            if (cap_set_flag(curr_caps, CAP_EFFECTIVE, 1, wanted_caps, CAP_SET) != 0) {
                log_errno(error, "cap_set_flag(CAP_EFFECTIVE)");
                return false;
            } else if (cap_set_proc(curr_caps) != 0) {
                log_errno(error, "cap_set_proc(CAP_EFFECTIVE)");
                return false;
            } else if (cap_free(curr_caps) != 0) {
                log_errno(error, "cap_free");
                return false;
            }
        }
    #endif

    if (user) {
        if (prctl(PR_SET_DUMPABLE, 1) != 0) {
            log_errno(error, "prctl(PR_SET_DUMPABLE)");
            return false;
        }
    }

    log(info, "chrooted to '%s' as user '%s'", path?:"/", user?:"-");
    return true;
}