aboutsummaryrefslogtreecommitdiffstats
path: root/FICS
diff options
context:
space:
mode:
Diffstat (limited to 'FICS')
-rw-r--r--FICS/addgroup.cpp198
-rw-r--r--FICS/addgroup.h17
-rw-r--r--FICS/build.mk8
-rw-r--r--FICS/ficsmain.c25
-rw-r--r--FICS/ficsmain.h5
-rw-r--r--FICS/prep_dir_for_privdrop.cpp158
-rw-r--r--FICS/prep_dir_for_privdrop.h11
-rw-r--r--FICS/settings.cpp4
-rw-r--r--FICS/utils.c2
-rw-r--r--FICS/utils.h2
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 *);