diff options
Diffstat (limited to 'src/core/privilege.c')
| -rw-r--r-- | src/core/privilege.c | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/src/core/privilege.c b/src/core/privilege.c new file mode 100644 index 0000000..254dbc6 --- /dev/null +++ b/src/core/privilege.c @@ -0,0 +1,233 @@ +#if defined(__linux__) +#define _DEFAULT_SOURCE +#endif + +#include "hybbx/privilege.h" +#include "hybbx/config.h" +#include "hybbx/log.h" +#include "hybbx/types.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if !defined(_WIN32) +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <sys/types.h> +#include <unistd.h> +#endif + +#if !defined(_WIN32) + +static int parse_optional_uid(const char *raw, uid_t *out) +{ + char *end; + unsigned long value; + + if (raw == NULL || raw[0] == '\0') { + return 0; + } + errno = 0; + value = strtoul(raw, &end, 10); + if (end == raw || *end != '\0' || errno != 0) { + return -1; + } + *out = (uid_t)value; + return 1; +} + +static int parse_optional_gid(const char *raw, gid_t *out) +{ + char *end; + unsigned long value; + + if (raw == NULL || raw[0] == '\0') { + return 0; + } + errno = 0; + value = strtoul(raw, &end, 10); + if (end == raw || *end != '\0' || errno != 0) { + return -1; + } + *out = (gid_t)value; + return 1; +} + +static hybbx_result_t resolve_target( + const hybbx_config_t *config, + uid_t *uid_out, + gid_t *gid_out, + char name_buf[64], + size_t name_buf_len) +{ + const char *user_name; + const char *group_name; + const char *uid_raw; + const char *gid_raw; + struct passwd *pw = NULL; + struct group *gr = NULL; + uid_t uid_override = 0; + gid_t gid_override = 0; + int have_uid = 0; + int have_gid = 0; + + user_name = hybbx_config_get(config, "service", "user", NULL); + group_name = hybbx_config_get(config, "service", "group", NULL); + uid_raw = hybbx_config_get(config, "service", "uid", NULL); + gid_raw = hybbx_config_get(config, "service", "gid", NULL); + + have_uid = parse_optional_uid(uid_raw, &uid_override); + if (have_uid < 0) { + hybbx_log_warn("[service] invalid uid=%s", uid_raw); + return HYBBX_ERR_INVALID; + } + have_gid = parse_optional_gid(gid_raw, &gid_override); + if (have_gid < 0) { + hybbx_log_warn("[service] invalid gid=%s", gid_raw); + return HYBBX_ERR_INVALID; + } + + if (user_name != NULL && user_name[0] != '\0') { + pw = getpwnam(user_name); + if (pw == NULL) { + hybbx_log_warn("[service] unknown user=%s", user_name); + return HYBBX_ERR_INVALID; + } + *uid_out = pw->pw_uid; + *gid_out = pw->pw_gid; + if (name_buf_len > 0) { + snprintf(name_buf, name_buf_len, "%s", pw->pw_name); + } + } else if (have_uid) { + pw = getpwuid(uid_override); + if (pw == NULL) { + hybbx_log_warn("[service] unknown uid=%s", uid_raw); + return HYBBX_ERR_INVALID; + } + *uid_out = pw->pw_uid; + *gid_out = pw->pw_gid; + if (name_buf_len > 0) { + snprintf(name_buf, name_buf_len, "%s", pw->pw_name); + } + } else { + return HYBBX_ERR_INVALID; + } + + if (have_uid) { + *uid_out = uid_override; + } + if (group_name != NULL && group_name[0] != '\0') { + gr = getgrnam(group_name); + if (gr == NULL) { + hybbx_log_warn("[service] unknown group=%s", group_name); + return HYBBX_ERR_INVALID; + } + *gid_out = gr->gr_gid; + } else if (have_gid) { + if (getgrgid(gid_override) == NULL) { + hybbx_log_warn("[service] unknown gid=%s", gid_raw); + return HYBBX_ERR_INVALID; + } + *gid_out = gid_override; + } + + return HYBBX_OK; +} + +static hybbx_result_t drop_to(uid_t uid, gid_t gid, const char *name) +{ + if (getuid() != 0) { + hybbx_log_warn("[service] privilege drop requires root start (euid=0)"); + return HYBBX_ERR_DENIED; + } + + if (name != NULL && name[0] != '\0') { + if (initgroups(name, gid) != 0) { + hybbx_log_warn("[service] initgroups(%s) failed: %s", + name, strerror(errno)); + return HYBBX_ERR_IO; + } + } + + if (setgid(gid) != 0) { + hybbx_log_warn("[service] setgid(%u) failed: %s", + (unsigned)gid, strerror(errno)); + return HYBBX_ERR_IO; + } + + if (setuid(uid) != 0) { + hybbx_log_warn("[service] setuid(%u) failed: %s", + (unsigned)uid, strerror(errno)); + return HYBBX_ERR_IO; + } + + if (setuid(0) == 0 || seteuid(0) == 0) { + hybbx_log_warn("[service] privilege drop incomplete — still root"); + return HYBBX_ERR_IO; + } + + hybbx_log_info("[service] dropped privileges to uid=%u gid=%u (%s)", + (unsigned)uid, (unsigned)gid, + name != NULL && name[0] != '\0' ? name : "?"); + return HYBBX_OK; +} + +#endif /* !_WIN32 */ + +hybbx_result_t hybbx_privilege_apply_from_config(const hybbx_config_t *config) +{ +#if defined(_WIN32) + (void)config; + return HYBBX_OK; +#else + const char *user_name; + uid_t target_uid = 0; + gid_t target_gid = 0; + char resolved_name[64]; + hybbx_result_t rc; + + if (config == NULL) { + return HYBBX_ERR_INVALID; + } + + resolved_name[0] = '\0'; + user_name = hybbx_config_get(config, "service", "user", NULL); + + if (geteuid() == 0) { + if ((user_name == NULL || user_name[0] == '\0') && + hybbx_config_get(config, "service", "uid", NULL) == NULL) { + hybbx_log_warn( + "[service] refusing to run as root — set [service] user= " + "(and optional group=)"); + return HYBBX_ERR_DENIED; + } + + rc = resolve_target(config, &target_uid, &target_gid, + resolved_name, sizeof(resolved_name)); + if (rc != HYBBX_OK) { + return rc; + } + + return drop_to(target_uid, target_gid, + resolved_name[0] != '\0' ? resolved_name : user_name); + } + + if (user_name != NULL && user_name[0] != '\0') { + struct passwd *pw = getpwnam(user_name); + + if (pw == NULL) { + hybbx_log_warn("[service] unknown configured user=%s", user_name); + return HYBBX_OK; + } + if ((uid_t)geteuid() != pw->pw_uid) { + hybbx_log_warn( + "[service] running as uid=%u but [service] user=%s (uid=%u)", + (unsigned)geteuid(), user_name, (unsigned)pw->pw_uid); + } + } + + return HYBBX_OK; +#endif +} |
