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/prep_dir_for_privdrop.cpp150
-rw-r--r--FICS/prep_dir_for_privdrop.h11
4 files changed, 376 insertions, 0 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/prep_dir_for_privdrop.cpp b/FICS/prep_dir_for_privdrop.cpp
new file mode 100644
index 0000000..399fdb4
--- /dev/null
+++ b/FICS/prep_dir_for_privdrop.cpp
@@ -0,0 +1,150 @@
+// 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::directory_iterator dir_it(v_path);
+ uid_t uid = 0;
+ gid_t gid = 0;
+ constexpr mode_t dir_mode = (S_IRUSR|S_IWUSR|
+ S_IRGRP|S_IWGRP|S_IROTH);
+ constexpr mode_t file_mode = (S_IRUSR|S_IWUSR|S_IRGRP|
+ S_IROTH);
+
+ if (get_uid_and_gid(uid, gid) == -1)
+ throw std::runtime_error("failed to get uid/gid");
+ 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