diff options
Diffstat (limited to 'FICS')
| -rw-r--r-- | FICS/addgroup.cpp | 198 | ||||
| -rw-r--r-- | FICS/addgroup.h | 17 | ||||
| -rw-r--r-- | FICS/build.mk | 8 | ||||
| -rw-r--r-- | FICS/ficsmain.c | 25 | ||||
| -rw-r--r-- | FICS/ficsmain.h | 5 | ||||
| -rw-r--r-- | FICS/prep_dir_for_privdrop.cpp | 158 | ||||
| -rw-r--r-- | FICS/prep_dir_for_privdrop.h | 11 | ||||
| -rw-r--r-- | FICS/settings.cpp | 4 | ||||
| -rw-r--r-- | FICS/utils.c | 2 | ||||
| -rw-r--r-- | FICS/utils.h | 2 |
10 files changed, 425 insertions, 5 deletions
diff --git a/FICS/addgroup.cpp b/FICS/addgroup.cpp new file mode 100644 index 0000000..59547d0 --- /dev/null +++ b/FICS/addgroup.cpp @@ -0,0 +1,198 @@ +// SPDX-FileCopyrightText: 2025 Markus Uhlin <maxxe@rpblc.net> +// SPDX-License-Identifier: ISC + +#include <string> +#include <vector> + +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "addgroup.h" + +#define SELF_TEST 0 + +struct group_info { + std::string name; + std::string password; + int gid; + std::string members; + + group_info() + { + this->name.assign(""); + this->password.assign(""); + this->gid = 0; + this->members.assign(""); + } + + group_info(const char *p_name, const char *p_password, const int p_gid, + const char *p_members) + { + this->name.assign(p_name); + this->password.assign(p_password); + this->gid = p_gid; + this->members.assign(p_members); + } +}; + +static std::vector<group_info> groups; + +static const int FIRST_SYSTEM_GID = 100; +static const int LAST_SYSTEM_GID = 999; + +static bool +is_free_gid(const int gid) +{ + for (auto it = groups.begin(); it != groups.end(); ++it) { + if ((*it).gid == gid) + return false; + } + return true; +} + +static int +get_free_gid(void) +{ + if (groups.empty()) + return -1; + for (int gid = FIRST_SYSTEM_GID; gid <= LAST_SYSTEM_GID; gid++) { + if (is_free_gid(gid)) + return gid; + } + return -1; +} + +int +fics_addgroup(const char *name) +{ + int fd; + int gid; + + if (name == nullptr || strcmp(name, "") == 0) + return -1; + else if (group_exists(name)) + return 0; + else if ((gid = get_free_gid()) == -1) + return -1; + fd = open("/etc/group", (O_RDWR|O_APPEND)); + if (fd < 0) + return -1; + struct group_info group(name, "*", gid, ""); + dprintf(fd, "%s:%s:%d:%s\n", group.name.c_str(), group.password.c_str(), + group.gid, group.members.c_str()); + close(fd); + groups.push_back(group); + return 0; +} + +bool +get_group_id(const char *name, int *gid) +{ + if (name == nullptr || strcmp(name, "") == 0 || gid == nullptr || + groups.empty()) { + if (gid) + *gid = 0; + return false; + } + + for (auto it = groups.begin(); it != groups.end(); ++it) { + if (strcmp((*it).name.c_str(), name) == 0) { + *gid = (*it).gid; + return true; + } + } + + *gid = 0; + return false; +} + +bool +get_next_line_from_file(FILE *fp, char **line) +{ + const int LINE_MAX_LEN = 2048; + + if (fp == nullptr || line == nullptr) + errx(1, "%s: invalid argument", __func__); + + if (*line) { + delete[] *line; + *line = nullptr; + } + + *line = new char[LINE_MAX_LEN]; + + return (fgets(*line, LINE_MAX_LEN, fp) ? true : false); +} + +bool +group_exists(const char *name) +{ + if (name == nullptr || strcmp(name, "") == 0 || groups.empty()) + return false; + for (auto it = groups.begin(); it != groups.end(); ++it) { + if (strcmp((*it).name.c_str(), name) == 0) + return true; + } + return false; +} + +int +read_the_group_permissions_file(const char *path) +{ + FILE *fp = nullptr; + bool read_ok = false; + char *line = nullptr; + const char *token[4]; + const char delim[] = ":"; + int gid = 0; + + if (!groups.empty()) + return 0; + if ((fp = fopen(path, "r")) == nullptr) + return -1; + while (get_next_line_from_file(fp, &line)) { + token[0] = strsep(&line, delim); + token[1] = strsep(&line, delim); + token[2] = strsep(&line, delim); + token[3] = strsep(&line, delim); + + if (token[0] == nullptr || + token[1] == nullptr || + token[2] == nullptr || + token[3] == nullptr) { + warnx("%s: too few tokens", __func__); + continue; + } else if (sscanf(token[2], "%d", &gid) != 1) { + warnx("%s: sscanf() error", __func__); + continue; + } + + struct group_info group(token[0], token[1], gid, token[3]); + groups.push_back(group); + } + + if (feof(fp)) + read_ok = true; + + fclose(fp); + delete[] line; + + return (read_ok ? 0 : -1); +} + +#if SELF_TEST +int +main(void) +{ + if (read_the_group_permissions_file("/etc/group") == -1) + errx(1, "failed to read the group permissions file"); + if (fics_addgroup("chess") == -1) + errx(1, "failed to add a group"); + puts("ok"); + return 0; +} +#endif diff --git a/FICS/addgroup.h b/FICS/addgroup.h new file mode 100644 index 0000000..e5b1906 --- /dev/null +++ b/FICS/addgroup.h @@ -0,0 +1,17 @@ +#ifndef ADDGROUP_H +#define ADDGROUP_H + +#include "common.h" + +#include <stdbool.h> +#include <stdio.h> + +__FICS_BEGIN_DECLS +int fics_addgroup(const char *); +bool get_group_id(const char *, int *); +bool get_next_line_from_file(FILE *, char **); // uses 'new[]' +bool group_exists(const char *); +int read_the_group_permissions_file(const char *); +__FICS_END_DECLS + +#endif diff --git a/FICS/build.mk b/FICS/build.mk index 5ace5c7..9db25e8 100644 --- a/FICS/build.mk +++ b/FICS/build.mk @@ -3,7 +3,8 @@ SRC_DIR := FICS/ -OBJS = $(SRC_DIR)adminproc.o\ +OBJS = $(SRC_DIR)addgroup.o\ + $(SRC_DIR)adminproc.o\ $(SRC_DIR)algcheck.o\ $(SRC_DIR)assert_error.o\ $(SRC_DIR)board.o\ @@ -29,6 +30,7 @@ OBJS = $(SRC_DIR)adminproc.o\ $(SRC_DIR)network.o\ $(SRC_DIR)obsproc.o\ $(SRC_DIR)playerdb.o\ + $(SRC_DIR)prep_dir_for_privdrop.o\ $(SRC_DIR)rating_conv.o\ $(SRC_DIR)ratings.o\ $(SRC_DIR)rmalloc.o\ @@ -40,7 +42,8 @@ OBJS = $(SRC_DIR)adminproc.o\ $(SRC_DIR)variable.o\ $(SRC_DIR)vers.o -SRCS = $(SRC_DIR)adminproc.c\ +SRCS = $(SRC_DIR)addgroup.cpp\ + $(SRC_DIR)adminproc.c\ $(SRC_DIR)algcheck.c\ $(SRC_DIR)assert_error.c\ $(SRC_DIR)board.c\ @@ -66,6 +69,7 @@ SRCS = $(SRC_DIR)adminproc.c\ $(SRC_DIR)network.c\ $(SRC_DIR)obsproc.c\ $(SRC_DIR)playerdb.c\ + $(SRC_DIR)prep_dir_for_privdrop.cpp\ $(SRC_DIR)rating_conv.c\ $(SRC_DIR)ratings.c\ $(SRC_DIR)rmalloc.c\ diff --git a/FICS/ficsmain.c b/FICS/ficsmain.c index 64ddd81..bf5aafb 100644 --- a/FICS/ficsmain.c +++ b/FICS/ficsmain.c @@ -41,6 +41,7 @@ #include <stdint.h> #include <string.h> +#include "addgroup.h" #include "board.h" #include "command.h" #include "comproc.h" @@ -54,6 +55,7 @@ #include "legal2.h" #include "network.h" #include "playerdb.h" +#include "prep_dir_for_privdrop.h" #include "ratings.h" #include "settings.h" #include "shutdown.h" @@ -193,6 +195,8 @@ unveil_doit(void) {SENDMAILPROG, "rx"}, #endif + {FICS_SETTINGS, "r"}, + {DEFAULT_ADHELP, ""}, // data/admin {DEFAULT_ADJOURNED, "rwc"}, // games/adjourned {DEFAULT_BOARDS, ""}, // data/boards @@ -209,7 +213,7 @@ unveil_doit(void) {DEFAULT_NEWS, ""}, // data/news {DEFAULT_PLAYERS, ""}, // players {DEFAULT_SOURCE, "r"}, // FICS - {DEFAULT_STATS, ""}, // data/stats + {DEFAULT_STATS, "rwc"}, // data/stats {DEFAULT_USAGE, ""}, // data/usage {DEFAULT_USCF, ""}, // data/uscf @@ -271,6 +275,12 @@ usage(char *progname) exit(EXIT_FAILURE); } +PUBLIC bool +is_super_user(void) +{ + return (geteuid() == UID_SUPER_USER); +} + PUBLIC int main(int argc, char *argv[]) { @@ -288,6 +298,19 @@ main(int argc, char *argv[]) settings_init(); settings_read_conf(FICS_SETTINGS); + if (is_super_user()) { + if (strncmp(FICS_PREFIX, "/home", 5) == 0) + errx(1, "Do not run as root"); + else if (read_the_group_permissions_file("/etc/group") != 0) + errx(1, "Failed to read the group permissions file"); + else if (fics_addgroup(settings_get("sysgroup")) != 0) + errx(1, "Failed to add the system group"); + else if (prep_dir_for_privdrop(FICS_PREFIX) != 0) + errx(1, "Dir preparation failed"); + else if (drop_root_privileges(FICS_PREFIX) != 0) + errx(1, "Privdrop failed"); + } + if (net_init(port)) { fprintf(stderr, "FICS: Network initialize failed on port %d.\n", port); diff --git a/FICS/ficsmain.h b/FICS/ficsmain.h index 071ce2f..024959e 100644 --- a/FICS/ficsmain.h +++ b/FICS/ficsmain.h @@ -30,6 +30,7 @@ #include <sys/stat.h> #include <fcntl.h> +#include <stdbool.h> #include <unistd.h> #include "common.h" @@ -65,6 +66,8 @@ #define STATS_GAMES "games" #define STATS_JOURNAL "journal" +#define UID_SUPER_USER 0 + enum { OPFL_APPEND, OPFL_WRITE, @@ -79,6 +82,8 @@ extern const mode_t g_open_modes; /* Arguments */ extern int port; extern int withConsole; + +bool is_super_user(void); __FICS_END_DECLS #endif /* _FICSMAIN_H */ diff --git a/FICS/prep_dir_for_privdrop.cpp b/FICS/prep_dir_for_privdrop.cpp new file mode 100644 index 0000000..4d4478c --- /dev/null +++ b/FICS/prep_dir_for_privdrop.cpp @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2025 Markus Uhlin <maxxe@rpblc.net> +// SPDX-License-Identifier: ISC + +#include <sys/types.h> +#include <sys/stat.h> + +#include <err.h> +#include <fcntl.h> +#include <pwd.h> +#include <string.h> +#include <unistd.h> + +#include <filesystem> +#include <string> + +#include "addgroup.h" +#include "prep_dir_for_privdrop.h" +#include "settings.h" +#include "utils.h" + +namespace fs = std::filesystem; + +static int +get_uid_and_gid(uid_t &uid, gid_t &gid) +{ + struct passwd *pw = nullptr; + int i = 0; + + if ((pw = getpwnam(settings_get("privdrop_user"))) == nullptr || + !get_group_id(settings_get("sysgroup"), &i)) { + uid = 0; + gid = 0; + return -1; + } + + uid = pw->pw_uid; + gid = static_cast<gid_t>(i); + return 0; +} + +static int +check_prep_done(const char *p_path) +{ + std::string path(p_path); + + path.append("/").append(".prep_done"); + + if (file_exists(path.c_str())) + return 0; + return -1; +} + +static void +prep_done(const char *p_path) +{ + int fd; + std::string path(p_path); + uid_t uid = 0; + gid_t gid = 0; + + path.append("/").append(".prep_done"); + + fd = open(path.c_str(), (O_RDWR|O_CREAT|O_TRUNC), (S_IRUSR|S_IWUSR| + S_IRGRP|S_IROTH)); + if (fd < 0) { + warn("%s: open", __func__); + return; + } + + dprintf(fd, "FICS dir preparation done.\n" + "Do not remove this file while FICS is installed.\n"); + close(fd); + + if (get_uid_and_gid(uid, gid) == 0) { + if (chown(path.c_str(), uid, gid) != 0) + warn("%s: chown", __func__); + } +} + +int +drop_root_privileges(const char *path) +{ + struct passwd *pw; + + if ((pw = getpwnam(settings_get("privdrop_user"))) == nullptr) { + warnx("%s: password database search failed", __func__); + return -1; + } else if (chdir(path) != 0) { + warn("%s: chdir(%s)", __func__, path); + return -1; + } else if (setgid(pw->pw_gid) == -1) { + warn("%s: setgid", __func__); + return -1; + } else if (setegid(pw->pw_gid) == -1) { + warn("%s: setegid", __func__); + return -1; + } else if (setuid(pw->pw_uid) == -1) { + warn("%s: setuid", __func__); + return -1; + } else if (seteuid(pw->pw_uid) == -1) { + warn("%s: seteuid", __func__); + return -1; + } + + return 0; +} + +int +prep_dir_for_privdrop(const char *path) +{ + if (path == nullptr || strcmp(path, "") == 0) + return -1; + else if (check_prep_done(path) == 0) + return 0; + try { + fs::path v_path = path; + fs::recursive_directory_iterator dir_it(v_path); + uid_t uid = 0; + gid_t gid = 0; + constexpr mode_t dir_mode = (S_IRWXU|S_IRGRP|S_IXGRP); + constexpr mode_t file_mode = (S_IRUSR|S_IWUSR|S_IRGRP); + + if (get_uid_and_gid(uid, gid) == -1) { + throw std::runtime_error("failed to get uid/gid"); + } else if (chown(path, uid, gid) != 0) { + std::string str("chown() error: "); + str.append(strerror(errno)); + throw std::runtime_error(str); + } else if (chmod(path, dir_mode) != 0) { + std::string str("chmod() error: "); + str.append(strerror(errno)); + throw std::runtime_error(str); + } + + for (auto const &dir_ent : dir_it) { + const std::string str(dir_ent.path().string()); + + if (chown(str.c_str(), uid, gid) != 0) { + warn("%s: chown(%s, ...)", __func__, + str.c_str()); + } + + if (dir_ent.is_directory()) { + if (chmod(str.c_str(), dir_mode) != 0) + warn("%s: chmod", __func__); + } else if (dir_ent.is_regular_file()) { + if (chmod(str.c_str(), file_mode) != 0) + warn("%s: chmod", __func__); + } + } + } catch (const std::exception &ex) { + warnx("%s: %s", __func__, ex.what()); + return -1; + } + + prep_done(path); + return 0; +} diff --git a/FICS/prep_dir_for_privdrop.h b/FICS/prep_dir_for_privdrop.h new file mode 100644 index 0000000..f2d8915 --- /dev/null +++ b/FICS/prep_dir_for_privdrop.h @@ -0,0 +1,11 @@ +#ifndef PREP_DIR_FOR_PRIVDROP_H +#define PREP_DIR_FOR_PRIVDROP_H + +#include "common.h" + +__FICS_BEGIN_DECLS +int drop_root_privileges(const char *); +int prep_dir_for_privdrop(const char *); +__FICS_END_DECLS + +#endif diff --git a/FICS/settings.cpp b/FICS/settings.cpp index 86adf2e..2541759 100644 --- a/FICS/settings.cpp +++ b/FICS/settings.cpp @@ -49,6 +49,8 @@ settings_init(void) struct setting s4("HADMINHANDLE", STYPE_STRING, ""); struct setting s5("HADMINEMAIL", STYPE_STRING, ""); struct setting s6("REGMAIL", STYPE_STRING, ""); + struct setting s7("privdrop_user", STYPE_STRING, "nobody"); + struct setting s8("sysgroup", STYPE_STRING, "chess"); SET_PB(s1); SET_PB(s2); @@ -56,6 +58,8 @@ settings_init(void) SET_PB(s4); SET_PB(s5); SET_PB(s6); + SET_PB(s7); + SET_PB(s8); } void diff --git a/FICS/utils.c b/FICS/utils.c index b3a37ab..dd07693 100644 --- a/FICS/utils.c +++ b/FICS/utils.c @@ -1007,7 +1007,7 @@ available_space(void) } PUBLIC int -file_exists(char *fname) +file_exists(const char *fname) { FILE *fp; diff --git a/FICS/utils.h b/FICS/utils.h index e376a03..416003b 100644 --- a/FICS/utils.h +++ b/FICS/utils.h @@ -83,7 +83,7 @@ extern int alphastring(char *); extern int available_space(void); extern int count_lines(FILE *); extern int display_directory(int, char **, int); -extern int file_exists(char *); +extern int file_exists(const char *); extern int file_has_pname(char *, char *); extern int iswhitespace(int); extern int lines_file(char *); |
