diff -ur bfilter-0.9.4/main/main_unix.cpp bfilter-0.9.4-privdrop/main/main_unix.cpp --- bfilter-0.9.4/main/main_unix.cpp 2003-12-19 19:27:32.000000000 +0000 +++ bfilter-0.9.4-privdrop/main/main_unix.cpp 2005-08-27 19:43:13.000000000 +0100 @@ -26,13 +26,16 @@ #include #include "network.h" #include +#include +#include +#include // some older versions of popt don't define POPT_TABLEEND #ifndef POPT_TABLEEND #define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL } #endif -int main_unix_standalone(const std::string& confdir, bool nodaemon); +int main_unix_standalone(const std::string& confdir, bool nodaemon, char *chroot, uid_t user, gid_t group); int main_unix_backend(Network::Socket csock, IPCPortal* portal); int main(int argc, char *argv[]) @@ -40,16 +43,27 @@ enum { ARG_VERSION = 1, ARG_CONFDIR, + ARG_CHROOT, + ARG_USER, + ARG_GROUP, ARG_NODAEMON, ARG_BACKEND }; bool backend = false; bool nodaemon = false; char *cdir = 0; + char *chroot = 0; + char *user = 0; + char *group = 0; + uid_t uid = 0; + uid_t gid = 0; std::string confdir = CONFDIR; struct poptOption options[] = { { "version", 'v', POPT_ARG_NONE, NULL, ARG_VERSION, "Print version and exit" }, { "confdir", 'c', POPT_ARG_STRING, &cdir, ARG_CONFDIR, "Set custom config directory", "dir" }, + { "chroot", 'r', POPT_ARG_STRING, &chroot, ARG_CHROOT, "Set chroot directory", "dir" }, + { "user", 'u', POPT_ARG_STRING, &user, ARG_USER, "Set unprivileged user", "name" }, + { "group", 'g', POPT_ARG_STRING, &group, ARG_GROUP, "Set unprivileged group", "name" }, { "nodaemon", 'n', POPT_ARG_NONE, NULL, ARG_NODAEMON, "Disable background daemon mode" }, { "backend", '\0', POPT_ARG_NONE|POPT_ARGFLAG_DOC_HIDDEN, NULL, ARG_BACKEND }, POPT_AUTOHELP @@ -78,13 +92,49 @@ << ": " << poptStrerror(arg) << std::endl; return 1; } + if (!backend && (chroot || user || group)) { + struct stat stat_r; + struct passwd *user_r; + struct group *group_r; + + if (getuid()) { + std::cerr << "Cannot lower privileges, not running as root" << std::endl; + return 1; + } + + if (chroot && stat(chroot, &stat_r)) { + if (!S_ISDIR(stat_r.st_mode)){ + std::cerr << "Cannot lower privileges, chroot directory does not exist" << std::endl; + return 1; + } + } + if (user) { + user_r = getpwnam(user); + if (user_r) + uid = user_r->pw_uid; + else { + std::cerr << "Cannot lower privileges, unknown user" << std::endl; + return 1; + } + } + + if (group) { + group_r = getgrnam(group); + if (group_r) + gid = group_r->gr_gid; + else { + std::cerr << "Cannot lower privileges, unknown group" << std::endl; + return 1; + } + } + } poptFreeContext(context); if (backend) { IPCPortal portal(0, 1); return main_unix_backend(3, &portal); } else { - return main_unix_standalone(confdir, nodaemon); + return main_unix_standalone(confdir, nodaemon, chroot, uid, gid); } } diff -ur bfilter-0.9.4/main/main_unix_standalone.cpp bfilter-0.9.4-privdrop/main/main_unix_standalone.cpp --- bfilter-0.9.4/main/main_unix_standalone.cpp 2003-12-11 03:34:51.000000000 +0000 +++ bfilter-0.9.4-privdrop/main/main_unix_standalone.cpp 2005-08-28 13:03:29.000000000 +0100 @@ -26,6 +26,9 @@ #include "state.h" #include #include "syscall.h" +#include +#include +#include class StandaloneState : public State { @@ -175,7 +178,34 @@ return strm.str(); } -int main_unix_standalone(const std::string& confdir, bool nodaemon) +static int drop_privileges(char *dir, uid_t uid, gid_t gid) +{ + if (dir) { + // Using gethostbyname before chrooting means that the chroot + // directory can be empty (no etc/resolv.conf or dynamically + // loaded lib/libnss* libraries). Unfortunately simply using + // gethostbyname once in the parent process does not apply to + // forked children. Using localhost here to prevent remote + // name resolution also does not work. + gethostbyname("slashdot.org"); + if (chroot(dir)) { + std::cerr << "Cannot lower privileges, chroot directory no longer exists" << std::endl; + return 1; + } + chdir("/"); + } + if (gid) { + setgroups(0, NULL); + setgid(gid); + } + if (uid) { + setuid(uid); + } + + return 0; +} + +int main_unix_standalone(const std::string& confdir, bool nodaemon, char *chroot, uid_t uid, gid_t gid) { Network::Socket serv_sock = Network::INVALID_SOCK; Network::Socket clnt_sock = Network::INVALID_SOCK; @@ -249,6 +279,9 @@ daemon(1, 0); } setup_parent_signals(); + if (!chroot) { + drop_privileges(NULL, uid, gid); + } while (true) { clnt_sock = Network::tcpServerWaitConn(serv_sock, &client); @@ -261,6 +294,10 @@ setup_child_signals(); Network::closeSocket(serv_sock); Network::sockSetNodelay(clnt_sock, true); + if (drop_privileges(chroot, uid, gid)) { + Network::disconnectAndCloseSocket(clnt_sock, 10); + return 1; + } BFilter filter(clnt_sock, &state); filter.run(); Network::disconnectAndCloseSocket(clnt_sock, 10);