Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

daemon-manager as of 2010-10-20

Mostly works. Not extensively tested.
Doesn't daemonize yet.
Not sure about what to do about stdout/err on children.
No user logs yet.
  • Loading branch information...
commit 081ab8d01dc00f08ee483264f176011df9010731 1 parent 8b6df05
@caldwell authored
View
14 Makefile
@@ -0,0 +1,14 @@
+all: daemon-manager dmctl
+
+daemon-manager: daemon-manager.o user.o strprintf.o permissions.o config.cc passwd.o daemon.o log.o
+
+dmctl daemon-manager: CC=g++
+dmctl daemon-manager: CXXFLAGS += -MMD -g -Wall -Wextra -Wno-parentheses
+dmctl daemon-manager: LDFLAGS += -g
+
+dmctl: dmctl.o user.o strprintf.o permissions.o passwd.o
+
+clean:
+ rm *.o *.d daemon-manager dmctl
+
+-include *.d
View
95 config.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+
+#include "config.h"
+#include "strprintf.h"
+#include <iostream>
+#include <fstream>
+#include <string.h>
+
+using namespace std;
+
+static char *trim(char *s)
+{
+ while (*s && isspace(*s)) // Front
+ s++;
+ for (int i=strlen(s)-1; i>=0; i--) // Back
+ if (!isspace(s[i])) break;
+ else s[i] = '\0';
+ return s;
+}
+
+struct master_config parse_master_config(string path)
+{
+ ifstream in(path.c_str(), ifstream::in);
+
+ struct master_config config;
+ map<string,vector<string> > *section = NULL;
+
+ int n=0;
+ char line[1000];
+ while (in.good()) {
+ in.getline(line, sizeof(line));
+ n++;
+
+ char *comment = strchr(line, '#');
+ if (comment) *comment = '\0';
+
+ char *l = trim(line);
+ if (*l == '\0') continue;
+
+ if (*l == '[') {
+ char *sect = l+1;
+ char *end = strchr(sect, ']');
+ if (!end) throw strprintf("Bad config file %s:%d missing ']' from section header", path.c_str(), n);
+ *end = '\0';
+ l = end+1;
+ while (*l && isspace(*l)) l++;
+ if (*l != '\0') throw strprintf("Bad config file %s:%d Junk at end of section header line", path.c_str(), n);
+
+ if (strcmp(sect, "runs_as") == 0) section = &config.runs_as;
+ else if (strcmp(sect, "manages") == 0) section = &config.manages;
+ else throw strprintf("Bad config file %s:%d Illegal section \"%s\"", path.c_str(), n, sect);
+ continue;
+ }
+
+ if (!section) throw strprintf("Bad config file %s:%d Line before section header", path.c_str(), n);
+
+ char *key = strsep(&l, "=");
+ if (!l) throw strprintf("Bad config file %s:%d Missing '=' after key", path.c_str(), n);
+ key = trim(key);
+
+ vector<string> list;
+ for (char *val; val = strsep(&l, ",");)
+ if (trim(val)[0] != '\0') // Allow "user = " type lines.
+ list.push_back(string(trim(val)));
+
+ (*section)[string(key)] = list;
+ }
+
+ return config;
+}
+
+map<string,string> parse_daemon_config(string path)
+{
+ ifstream in(path.c_str(), ifstream::in);
+ map<string,string> config;
+
+ int n=0;
+ char line[1000];
+ while (in.good()) {
+ in.getline(line, sizeof(line));
+ n++;
+
+ char *comment = strchr(line, '#');
+ if (comment) *comment = '\0';
+
+ char *l = trim(line);
+ if (*l == '\0') continue;
+
+ char *key = strsep(&l, "=");
+ if (!l) throw strprintf("Bad config file %s:%d Missing '=' after key", path.c_str(), n);
+
+ config[trim(key)] = trim(l);
+ }
+ return config;
+}
View
23 config.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved. -*- c++ -*-
+#ifndef __MASTER_CONFIG_H__
+#define __MASTER_CONFIG_H__
+
+#include <string>
+#include <map>
+#include <vector>
+
+using namespace std;
+
+struct master_config {
+ map<string,vector<string> > runs_as;
+ map<string,vector<string> > manages;
+};
+
+typedef map<string, vector<string> >::iterator config_it;
+typedef vector<string>::iterator config_list_it;
+
+struct master_config parse_master_config(string path);
+map<string,string> parse_daemon_config(string path);
+
+#endif /* __MASTER_CONFIG_H__ */
+
View
305 daemon-manager.cc
@@ -0,0 +1,305 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+
+#include <getopt.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <algorithm>
+#include <sys/wait.h>
+#include <poll.h>
+#include <signal.h>
+#include "config.h"
+#include "user.h"
+#include "daemon.h"
+#include "passwd.h"
+#include "permissions.h"
+#include "lengthof.h"
+#include "strprintf.h"
+#include "log.h"
+
+#include <vis.h>
+
+using namespace std;
+
+static void usage(char *me, int exit_code)
+{
+ printf("Usage:\n\t%s [-h | --help] [-c | --config=<config-file>] [-v | --verbose] [-f | --foreground] [-d | --debug]\n", me);
+ exit(exit_code);
+}
+
+static vector<user*> user_list_from_config(struct master_config config);
+static vector<class daemon*> load_daemons(vector<user*> user_list);
+static void select_loop(vector<user*> users, vector<class daemon*> daemons);
+static vector<class daemon*> manageable_by_user(user *user, vector<class daemon*>daemons);
+static string do_command(string command_line, vector<class daemon*> manageable);
+static void dump_config(struct master_config config);
+
+int main(int argc, char **argv)
+{
+ string config_path("/etc/weblet/daemon-manager.conf");
+ int verbose;
+ bool foreground;
+ bool debug;
+
+ struct option opts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "config", required_argument, NULL, 'c' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "foreground", no_argument, NULL, 'f' },
+ { "debug", no_argument, NULL, 'd' },
+ {0,0,0,0}
+ };
+
+ char shortopts[100];
+ size_t si=0;
+ for (int i=0; opts[i].name; i++)
+ if (si < sizeof(shortopts)-3 &&
+ !opts[i].flag && isprint(opts[i].val)) {
+ shortopts[si++] = opts[i].val;
+ if (opts[i].has_arg == required_argument)
+ shortopts[si++] = ':';
+ }
+ shortopts[si] = '\0';
+
+ for (int ch; (ch = getopt_long(argc, argv, shortopts, opts, NULL)) != -1;) {
+ switch (ch) {
+ case 'h': usage(argv[0], 0); break;
+ case 'c': config_path = string(optarg); break;
+ case 'v': verbose++; break;
+ case 'f': foreground = true; break;
+ case 'd': debug = true; break;
+ default: usage(argv[0], 1); break;
+ }
+ }
+
+ if (argc != optind) {
+ fprintf(stderr, "Too many arguments.\n");
+ usage(argv[0], 1);
+ }
+
+ init_log(!debug, min(LOG_DEBUG, LOG_NOTICE + verbose));
+
+ struct master_config config;
+ try {
+ permissions::check(config_path, 0113, 0, 0);
+ config = parse_master_config(config_path);
+ } catch(std::string e) {
+ log(LOG_ERR, "Couldn't load config file: %s\n", e.c_str());
+ exit(1);
+ }
+
+ vector<user*> users = user_list_from_config(config);
+
+ vector<class daemon*> daemons = load_daemons(users);
+
+ // Now start all the daemons marked "autostart"
+ for (vector<class daemon*>::iterator d = daemons.begin(); d != daemons.end(); d++)
+ if ((*d)->autostart)
+ try { (*d)->start(); }
+ catch(string e) { log(LOG_ERR, "Couldn't start %s: %s\n", (*d)->id().c_str(), e.c_str()); }
+
+ select_loop(users, daemons);
+
+ return 0;
+}
+
+static vector<user*> user_list_from_config(struct master_config config)
+{
+ vector<string> unique_users;
+ for (config_it it = config.runs_as.begin(); it != config.runs_as.end(); it++)
+ unique_users.push_back(it->first);
+ for (config_it it = config.manages.begin(); it != config.manages.end(); it++)
+ unique_users.push_back(it->first);
+ unique_users.push_back(name_from_uid(0));
+
+ sort(unique_users.begin(), unique_users.end());
+
+ vector<string>::iterator it = unique(unique_users.begin(), unique_users.end());
+ unique_users.resize(it - unique_users.begin());
+
+ dump_config(config);
+
+ map<string,user*> users;
+ vector<user*> user_list;
+
+ for (vector<string>::iterator u = unique_users.begin(); u != unique_users.end(); u++) {
+ try {
+ user_list.push_back(users[*u] = new user(*u));
+ users[*u]->create_files();
+ } catch (string e) {
+ log(LOG_WARNING, "Ignoring %s: %s\n", u->c_str(), e.c_str());
+ }
+ }
+
+ for (vector<user*>::iterator u = user_list.begin(); u != user_list.end(); u++) {
+ (*u)->can_run_as_uid[(*u)->uid] = true; // You can always run as yourself, however ill-advised.
+ if (config.runs_as.find((*u)->name) != config.runs_as.end()) {
+ for (config_list_it name = config.runs_as[(*u)->name].begin(); name != config.runs_as[(*u)->name].end(); name++) {
+ int uid = uid_from_name(*name);
+ if (uid < 0)
+ log(LOG_ERR, "%s can't run as non-existant user \"%s\"\n", (*u)->name.c_str(), name->c_str());
+ else
+ (*u)->can_run_as_uid[uid] = true;
+ }
+ }
+ if (config.manages.find((*u)->name) != config.manages.end()) {
+ for (config_list_it name = config.manages[(*u)->name].begin(); name != config.manages[(*u)->name].end(); name++) {
+ if (!users[*name])
+ log(LOG_ERR, "%s can't manage non-existant user \"%s\"\n", (*u)->name.c_str(), name->c_str());
+ else
+ (*u)->manages.push_back(users[*name]);
+ }
+ }
+ }
+ return user_list;
+}
+
+static vector<class daemon*> load_daemons(vector<user*> user_list)
+{
+ vector<class daemon*> daemons;
+ // Now load up all the daemon config files for each user.
+ for (vector<user*>::iterator u = user_list.begin(); u != user_list.end(); u++) {
+ try {
+ vector<string> confs = (*u)->config_files();
+ for (vector<string>::iterator conf = confs.begin(); conf != confs.end(); conf++) {
+ try {
+ class daemon *d = new class daemon(*conf, *u);
+ daemons.push_back(d);
+ log(LOG_INFO, "Loaded daemon %s for %s\n", conf->c_str(), (*u)->name.c_str());
+ } catch(string e) {
+ log(LOG_ERR, "Skipping %s's config file %s: %s", (*u)->name.c_str(), conf->c_str(), e.c_str());
+ }
+ }
+ } catch (string e) {
+ log(LOG_ERR, "Skipping %s's config files: %s\n", (*u)->name.c_str(), e.c_str());
+ }
+ }
+ return daemons;
+}
+
+static void handle_sig_child(int)
+{
+ log(LOG_DEBUG, "SIGCHLD\n");
+}
+
+static void select_loop(vector<user*> users, vector<class daemon*> daemons)
+{
+ signal(SIGCHLD, handle_sig_child);
+ struct pollfd fd[users.size()];
+ for (size_t i=0; i<lengthof(fd); i++) {
+ fd[i].fd = users[i]->fifo_req;
+ fd[i].events = POLLIN;
+ //fds[i].revents = 0;
+ }
+
+ while (1) {
+ int got = poll(fd, lengthof(fd), -1);
+ if (got > 0) {
+ for (size_t i=0; i<lengthof(fd); i++)
+ if (fd[i].revents & POLLIN) {
+ char buf[1000];
+ int red = read(fd[i].fd, buf, sizeof(buf)-1);
+ if (red) {
+ if (buf[red-1] == '\n') red--;
+ buf[red] = '\0';
+ for (char *r = buf, *cmd; cmd = strsep(&r, "\n"); ) {
+ string resp = do_command(cmd, manageable_by_user(users[i], daemons));
+ int wrote = write(users[i]->fifo_resp, resp.c_str(), resp.length());
+ log(LOG_DEBUG, "Wrote %d bytes of response: %s\n", wrote, resp.c_str());
+ }
+ }
+ }
+ }
+ for (int kid; (kid = waitpid(-1, NULL, WNOHANG)) > 0;) {
+ log(LOG_NOTICE, "Child %d exited\n", kid);
+ for (vector<class daemon*>::iterator d = daemons.begin(); d != daemons.end(); d++)
+ if ((*d)->pid == kid)
+ try { (*d)->start(true); }
+ catch(string e) { log(LOG_ERR, "Couldn't respawn %s: %s\n", (*d)->id().c_str(), e.c_str()); }
+ }
+ }
+}
+
+static vector<class daemon*> manageable_by_user(user *user, vector<class daemon*>daemons)
+{
+ vector<class daemon*> manageable;
+ for (vector<class daemon*>::iterator d = daemons.begin(); d != daemons.end(); d++) {
+ if ((*d)->user == user)
+ goto push;
+ for (vector<class user*>::iterator u = user->manages.begin(); u != user->manages.end(); u++)
+ if ((*d)->user == *u)
+ goto push;
+ continue;
+ push:
+ manageable.push_back(*d);
+ }
+ return manageable;
+}
+
+static string do_command(string command_line, vector<class daemon*> manageable)
+{
+ size_t space = command_line.find_first_of(" ");
+ string cmd = command_line.substr(0, space);
+ string arg = space != command_line.npos ? command_line.substr(space+1, command_line.length()) : "";
+ log(LOG_DEBUG, "line: \"%s\" cmd: \"%s\", arg: \"%s\"\n", command_line.c_str(), cmd.c_str(), arg.c_str());
+
+ if (cmd == "list") {
+ string resp = "";
+ for (vector<class daemon*>::iterator d = manageable.begin(); d != manageable.end(); d++)
+ resp += (resp.length() ? "," : "") + (*d)->id();
+ return "OK: " + resp + "\n";
+ }
+
+ if (cmd == "status") {
+ string resp = strprintf("%-30s %6s %8s %6s %6s\n", "daemon-id", "pid", "respawns", "uptime", "total");
+ for (vector<class daemon*>::iterator d = manageable.begin(); d != manageable.end(); d++)
+ resp += strprintf("%-30s %6d %8d %6d %6d\n",
+ (*d)->id().c_str(),
+ (*d)->pid,
+ (*d)->respawns,
+ (*d)->pid ? time(NULL) - (*d)->respawn_time : 0,
+ (*d)->pid ? time(NULL) - (*d)->start_time : 0);
+ return "OK: " + resp;
+ }
+
+ try {
+ class daemon *daemon;
+ for (vector<class daemon*>::iterator d = manageable.begin(); d != manageable.end(); d++)
+ if ((*d)->id() == arg) {
+ daemon = *d;
+ goto legit;
+ }
+ throw strprintf("unknown id \"%s\"", arg.c_str());
+ legit:
+ if (cmd == "start") if (daemon->pid) throw strprintf("Already running \"%s\"", daemon->id().c_str());
+ else daemon->start();
+ else if (cmd == "stop") daemon->stop();
+ else if (cmd == "restart") { daemon->stop(); daemon->start(); }
+ else throw strprintf("bad command \"%s\"", cmd.c_str());
+ } catch (string e) {
+ return "ERR: " + e + "\n";
+ }
+ return "OK\n";
+}
+
+static void dump_config(struct master_config config)
+{
+ log(LOG_DEBUG, "Config:\n");
+ log(LOG_DEBUG, " Runs as:\n");
+ for (config_it it = config.runs_as.begin(); it != config.runs_as.end(); it++) {
+ string s = " "+it->first+": ";
+ for (config_list_it lit = it->second.begin(); lit != it->second.end(); lit++) {
+ s += " \""+*lit+"\"";
+ }
+ log(LOG_DEBUG, "%s\n", s.c_str());
+ }
+
+ log(LOG_DEBUG," Manages:\n");
+ for (config_it it = config.manages.begin(); it != config.manages.end(); it++) {
+ string s = " "+it->first+": ";
+ for (config_list_it lit = it->second.begin(); lit != it->second.end(); lit++) {
+ s += " \""+*lit+"\"";
+ }
+ log(LOG_DEBUG, "%s\n", s.c_str());
+ }
+}
View
128 daemon.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+
+#include "daemon.h"
+#include "permissions.h"
+#include "config.h"
+#include "passwd.h"
+#include "strprintf.h"
+#include "log.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+
+using namespace std;
+
+daemon::daemon(string config_file, class user *user) : config_file(config_file), config_file_stamp(-1), user(user)
+{
+ load_config();
+}
+
+template<class T,class U>
+bool exists(map<T,U> m, T k)
+{
+ return m.find(k) != m.end();
+}
+
+void daemon::load_config()
+{
+ struct stat st = permissions::check(config_file, 0113, user->uid);
+ if (st.st_mtime == config_file_stamp) return;
+
+ map<string,string> config = parse_daemon_config(config_file);
+
+ working_dir = exists(config, string("dir")) ? config["dir"] : "/";
+ int uid = exists(config, string("dir")) ? uid_from_name(config["user"]) : user->uid;
+ if (uid < 0) throw strprintf("%s is not allowed to run as unknown user %s in %s\n", user->name.c_str(), config["user"].c_str(), config_file.c_str());
+ if (!user->can_run_as_uid[uid]) throw strprintf("%s is not allowed to run as %s in %s\n", user->name.c_str(), config["user"].c_str(), config_file.c_str());
+ run_as_uid = uid;
+ if (!exists(config, string("start"))) throw strprintf("Missing \"start\" in %s\n", config_file.c_str());
+ start_command = config["start"];
+ autostart = !exists(config, string("autostart")) || strchr("YyTt1Oo", config["autostart"].c_str()[0]);
+
+
+ config_file_stamp = st.st_mtime;
+}
+
+#include <libgen.h>
+string daemon::id()
+{
+ const char *stem = basename((char*)config_file.c_str());
+ const char *ext = strstr(stem, ".conf");
+ return user->name + "/" + string(stem, ext ? ext - stem : strlen(stem));
+}
+
+string daemon::sock_file()
+{
+ return "/var/run/daemon-manager/" + name_from_uid(run_as_uid) + "/" + id() + ".socket";
+}
+
+static void mkdirs(string path, mode_t mode, int uid=-1, int gid=-1)
+{
+ mkdir(path.c_str(), mode);
+ chown(path.c_str(), uid, gid);
+}
+
+void daemon::create_sock_dir()
+{
+ mkdirs("/var/run/daemon-manager/", 0755);
+ mkdirs("/var/run/daemon-manager/" + name_from_uid(run_as_uid) + "/", 0770, run_as_uid);
+ mkdirs("/var/run/daemon-manager/" + name_from_uid(run_as_uid) + "/" + user->name + "/", 0770, run_as_uid, user->gid);
+}
+
+void daemon::start(bool respawn)
+{
+ log(LOG_INFO, "Starting %s\n", id().c_str());
+
+ int fd[2];
+ if (pipe(fd) <0) throw strprintf("Couldn't pipe: %s", strerror(errno));
+ fcntl(fd[0], F_SETFD, 1);
+ fcntl(fd[1], F_SETFD, 1);
+ int child = fork();
+ if (child == -1) throw strprintf("Fork failed: %s\n", strerror(errno));
+ if (child) {
+ close(fd[1]);
+ char err[1000]="";
+ int red = read(fd[0], &err, sizeof(err));
+ close(fd[0]);
+ if(red > 0)
+ throw string(err);
+ pid = child; // Parent
+ log(LOG_INFO, "Started %s. pid=%d\n", id().c_str(), pid);
+ respawn_time = time(NULL);
+ if (respawn)
+ respawns++;
+ else
+ start_time = time(NULL);
+ return;
+ }
+
+ // Child
+ try {
+ close(fd[0]);
+ create_sock_dir();
+ setuid(run_as_uid);
+ seteuid(run_as_uid);
+ setgid(user->gid);
+ setegid(user->gid);
+ if (chdir(working_dir.c_str())) throw strprintf("Couldn't change to directory %s: %s", working_dir.c_str(), strerror(errno));
+ const char *const env[] = { (string("SOCK_FILE=")+sock_file()).c_str(), NULL };
+ execle("/bin/sh", "/bin/sh", "-c", start_command.c_str(), (char*)NULL, env);
+ throw strprintf("Couldn't exec: %s", strerror(errno));
+ } catch (string e) {
+ write(fd[1], e.c_str(), e.length()+1/*NULL*/);
+ close(fd[1]);
+ exit(0);
+ }
+}
+
+void daemon::stop()
+{
+ if (pid) {
+ log(LOG_INFO, "Stopping [%d] %s\n", pid, id().c_str());
+ kill(pid, SIGTERM);
+ }
+ pid = 0;
+ respawns = 0;
+}
View
41 daemon.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved. -*- c++ -*-
+#ifndef __DAEMON_H__
+#define __DAEMON_H__
+
+#include "user.h"
+#include <string>
+#include <time.h>
+
+class daemon {
+ public:
+ std::string config_file;
+ time_t config_file_stamp;
+ //int socket;
+ int pid;
+ class user *user;
+
+ // From config file:
+ std::string working_dir;
+ int run_as_uid;
+ std::string start_command;
+ bool autostart;
+
+ // stats:
+ size_t respawns;
+ time_t start_time;
+ time_t respawn_time;
+
+ daemon(std::string config_file, class user *user);
+
+ void load_config();
+ std::string id();
+ std::string sock_file();
+ void create_sock_dir();
+
+ void start(bool respawn=false);
+ void stop();
+};
+
+
+#endif /* __DAEMON_H__ */
+
View
65 dmctl.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <err.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <poll.h>
+#include "user.h"
+
+using namespace std;
+
+static void usage(char *me, int exit_code)
+{
+ printf("Usage:\n"
+ "\t%s list|status\n"
+ "\t%s start|stop|restart <daemon-id>\n", me, me);
+ exit(exit_code);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2 || argc > 3) usage(argv[0], 1);
+ user *me;
+ try {
+ me = new user(getuid());
+ me->open_fifos(true);
+ } catch (string e) {
+ errc(1, 0, "Error: %s\n", e.c_str());
+ }
+
+ string command = argv[1];
+ if (argc == 3)
+ command += string(" ") + argv[2];
+ int wrote = write(me->fifo_req_wr, command.c_str(), command.length());
+ if (wrote < 0) err(1, "daemon-manager does not appear to be running.");
+ if (!wrote) errc(1, 0, "daemon-manager does not appear to be running.");
+
+ // Drain the FIFO
+ char buf[1000];
+ while (read(me->fifo_resp_rd, buf, sizeof(buf)-1) > 0) {}
+
+ // Wait for our response:
+ struct pollfd fd[1];
+ fd[0].fd = me->fifo_resp_rd;
+ fd[0].events = POLLIN;
+ int got = poll(fd, 1, -1);
+ if (got < 0) err(1, "Poll failed.");
+ if (got == 0) err(1, "Poll timed out.");
+
+ int red = read(me->fifo_resp_rd, buf, sizeof(buf)-1);
+ if (red < 0) err(1, "no response from daemon-manager");
+ if (red == 0) errc(1, 0, "no response from daemon-manager.");
+
+ buf[red] = '\0';
+ if (strcmp(buf, "OK\n") == 0) exit(0);
+ if (strncmp(buf, "OK: ", 4) == 0) {
+ printf("%s", buf+4);
+ exit(0);
+ }
+ printf("%s", buf);
+ exit(1);
+}
+
View
17 lengthof.h
@@ -0,0 +1,17 @@
+//-------=====================<<<< COPYRIGHT >>>>========================-------
+// Copyright (c) 2004-2005 David Caldwell, No Rights Reserved. Feel Free.
+//-------================================================================-------
+
+#ifndef __LENGTHOF_H__
+#define __LENGTHOF_H__
+
+#ifdef __cplusplus
+#define lengthof(x) (sizeof(x)/sizeof(*(x)))
+#else
+
+#define __compile_assert(e) sizeof(char[1-2*!(e)]) // expression version of compile_assert()
+#define lengthof(x) (sizeof(x)/sizeof(*(x)) + \
+ 0*__compile_assert(!__builtin_types_compatible_p(typeof(x),typeof(&(x)[0]))))
+#endif
+
+#endif /* __LENGTHOF_H__ */
View
31 log.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+
+#include <syslog.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include "log.h"
+
+static const char *const priority_name[8] = { "Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Info", "Debug" };
+
+static bool use_syslog;
+static int log_level;
+void init_log(bool _use_syslog, int minimum_log_level)
+{
+ use_syslog = _use_syslog;
+ log_level = minimum_log_level;
+ openlog("daemon-manager", LOG_NDELAY | LOG_PID, LOG_DAEMON);
+ setlogmask(LOG_UPTO(log_level));
+}
+
+void log(int priority, const char *message, ...)
+{
+ va_list ap;
+ va_start(ap, message);
+ if (use_syslog)
+ vsyslog(priority, message, ap);
+ else if (priority <= log_level) {
+ fprintf(stderr, "[%s] ", priority_name[priority]);
+ vfprintf(stderr, message, ap);
+ }
+ va_end(ap);
+}
View
11 log.h
@@ -0,0 +1,11 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+#ifndef __LOG_H__
+#define __LOG_H__
+
+#include <syslog.h>
+
+void init_log(bool _use_syslog, int minimum_log_level);
+void log(int priority, const char *message, ...);
+
+#endif /* __LOG_H__ */
+
View
32 passwd.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+
+#include "passwd.h"
+#include "strprintf.h"
+#include <string>
+#include <pwd.h>
+#include <grp.h>
+
+using namespace std;
+
+int uid_from_name(string name)
+{
+ struct passwd *p = getpwnam(name.c_str());
+ if (!p) return -1;
+ return p->pw_uid;
+}
+
+string name_from_uid(int uid)
+{
+ struct passwd *p = getpwuid(uid);
+ if (!p) return strprintf("%d", uid);
+ return string(p->pw_name);
+}
+
+string name_from_gid(int gid)
+{
+ struct group *g = getgrgid(gid);
+ if (!g) return strprintf("%d", gid);
+ return string(g->gr_name);
+}
+
+
View
12 passwd.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved. -*- c++ -*-
+#ifndef __PASSWD_H__
+#define __PASSWD_H__
+
+#include <string>
+
+int uid_from_name(std::string name);
+std::string name_from_uid(int uid);
+std::string name_from_gid(int gid);
+
+#endif /* __PASSWD_H__ */
+
View
21 permissions.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+
+#include "permissions.h"
+#include "strprintf.h"
+#include "passwd.h"
+#include <sys/stat.h>
+#include <string>
+
+using namespace std;
+
+struct stat permissions::check(string file, int bad_modes, uid_t required_uid, gid_t required_gid)
+{
+ const char *path = file.c_str();
+ struct stat st;
+ if (stat(path, &st)) throw strprintf("%s doesn't exist", path);
+ if (st.st_mode & bad_modes & 0002) throw strprintf("%s can't be world writable", path);
+ if (st.st_mode & bad_modes & 0111) throw strprintf("%s can't be executable", path);
+ if (required_uid != (uid_t)-1 && st.st_uid != required_uid) throw strprintf("%s must be owned by \"%s\"", path, name_from_uid(required_uid).c_str());
+ if (required_gid != (gid_t)-1 && st.st_gid != required_gid) throw strprintf("%s must have group \"%s\"",path, name_from_gid(required_gid).c_str());
+ return st;
+}
View
14 permissions.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+#ifndef __PERMISSIONS_H__
+#define __PERMISSIONS_H__
+
+#include <string>
+#include <sys/stat.h>
+
+namespace permissions {
+
+ struct stat check(std::string file, int bad_modes, uid_t required_uid=-1, gid_t required_gid=-1);
+
+}
+#endif /* __PERMISSIONS_H__ */
+
View
22 strprintf.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+
+#include <string>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include "strprintf.h"
+
+using std::string;
+
+string strprintf(const char *format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ char *buf;
+ vasprintf(&buf, format, ap);
+ if (!buf)
+ return string("");
+ string s(buf);
+ free(buf);
+ return s;
+}
View
10 strprintf.h
@@ -0,0 +1,10 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved. -*- c++ -*-
+#ifndef __STRPRINTF_H__
+#define __STRPRINTF_H__
+
+#include <string>
+
+std::string strprintf(const char *format, ...);
+
+#endif /* __STRPRINTF_H__ */
+
View
134 user.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+
+#include "user.h"
+//#include "except.h"
+#include "strprintf.h"
+#include "permissions.h"
+#include <string>
+#include <sstream>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <glob.h>
+
+using namespace std;
+
+map<string,user*> users;
+
+user::user(string name)
+{
+ struct passwd *p = getpwnam(name.c_str());
+ if (!p) throw strprintf("No user named \"%s\"", name.c_str());
+ init(p);
+}
+
+user::user(uid_t uid)
+{
+ struct passwd *p = getpwuid(uid);
+ if (!p) throw strprintf("No user with uid %d", uid);
+ init(p);
+}
+
+void user::init(struct passwd *p)
+{
+ name = string(p->pw_name);
+ uid = p->pw_uid;
+ gid = p->pw_gid;
+ homedir = string(p->pw_dir);
+}
+
+void user::create_files()
+{
+ create_dirs();
+ create_fifos();
+ open_fifos();
+}
+
+void user::create_dirs()
+{
+ struct stat st;
+ if (stat(fifo_dir().c_str(), &st) != 0) {
+ mkdir(fifo_dir().c_str(), 0750);
+ chown(fifo_dir().c_str(), uid, gid);
+ }
+ if (uid != 0 && // Don't create /etc/daemon-manager. Package manager should do that.
+ stat(config_path().c_str(), &st) != 0) {
+ mkdir(config_path().c_str(), 0750);
+ chown(config_path().c_str(), uid, gid);
+ }
+}
+
+static void create_fifo(string path_str, uid_t uid, gid_t gid)
+{
+ const char *path = path_str.c_str();
+ struct stat st;
+ if (stat(path, &st) == 0 && unlink(path)) throw strprintf("Couldn't remove old FIFO @ %s: %s", path, strerror(errno));
+ if (mkfifo(path, 0700)) throw strprintf("mkfifo %s failed: %s", path, strerror(errno));
+ chown(path, uid, gid);
+}
+
+void user::create_fifos()
+{
+ create_fifo(fifo_path(true), uid, gid);
+ create_fifo(fifo_path(false), uid, gid);
+}
+
+
+static int open_fifo(string path, int oflags)
+{
+ int fifo = open(path.c_str(), oflags);
+ if (fifo < 0) throw strprintf("open %s (%s) failed: %s", path.c_str(), oflags & O_RDONLY ? "read" : "write", strerror(errno));
+ fcntl(fifo, F_SETFD, 1);
+ return fifo;
+}
+
+void user::open_fifos(bool client)
+{
+ // We don't use this end of the FIFO, we just hold it open because otherwise Linux craps its pants when the
+ // number of writers go from 1 to 0 (select always returns the fd to tell us it's EOF and there's no way to
+ // clear the condition short of closing the file and re-opening it which is gross). Not needed on Mac OS X.
+ if (!client) fifo_req = open_fifo(fifo_path(true), O_RDONLY | O_NONBLOCK);
+ fifo_req_wr = open_fifo(fifo_path(true), O_WRONLY | O_NONBLOCK);
+ fifo_resp_rd = open_fifo(fifo_path(false), O_RDONLY | O_NONBLOCK);
+ if (!client) fifo_resp = open_fifo(fifo_path(false), O_WRONLY | O_NONBLOCK);
+}
+
+string user::fifo_dir()
+{
+ return uid == 0 ? "/var/run/daemon-manager/"
+ : homedir + "/.daemon-manager/";
+}
+string user::fifo_path(bool request)
+{
+ return fifo_dir() + (request ? "req" : "resp")+".fifo";
+}
+
+string user::config_path()
+{
+ return uid == 0 ? "/etc/daemon-manager/daemons"
+ : homedir + "/.daemon-manager/daemons";
+}
+
+vector<string> user::config_files()
+{
+ permissions::check(config_path(), 0002, uid);
+ // check permissions
+
+ vector<string> files;
+ glob_t gl;
+ int status = glob((config_path() + "/*.conf").c_str(), 0, NULL, &gl);
+
+ for (size_t i=0; i<gl.gl_pathc; i++)
+ files.push_back(string(gl.gl_pathv[i]));
+
+ globfree(&gl);
+ if (status != GLOB_NOMATCH && status != 0)
+ throw strprintf("Glob failed: %d/%s", status, strerror(errno));
+
+ return files;
+}
+
View
39 user.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2010 David Caldwell, All Rights Reserved.
+#ifndef __USER_H__
+#define __USER_H__
+
+#include <string>
+#include <map>
+#include <vector>
+
+using namespace std;
+
+class user {
+ public:
+ string name;
+ int uid;
+ int gid;
+ string homedir;
+ int fifo_req;
+ int fifo_resp;
+ int fifo_req_wr;
+ int fifo_resp_rd;
+ map<int,bool> can_run_as_uid;
+ vector<user*> manages;
+
+ user(string name);
+ user(uid_t uid);
+ void create_files();
+ void open_fifos(bool client = false);
+ string fifo_dir();
+ string fifo_path(bool request);
+ string config_path();
+ vector<string> config_files();
+ private:
+ void create_dirs();
+ void create_fifos();
+ void init(struct passwd *);
+};
+
+#endif /* __USER_H__ */
+
Please sign in to comment.
Something went wrong with that request. Please try again.