From ef6b18c7bb36ae8be36ee3fe8777035578ceba54 Mon Sep 17 00:00:00 2001 From: Michael Spang Date: Sat, 31 Jan 2009 01:40:18 -0500 Subject: [PATCH] Add ceod --- src/Makefile | 10 ++- src/daemon.h | 10 +++ src/dmaster.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++ src/dslave.c | 146 ++++++++++++++++++++++++++++++++++++++++ src/gss.c | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/gss.h | 11 +++ src/net.c | 141 ++++++++++++++++++++++++++++++++++++++ src/net.h | 45 +++++++++++++ src/ops.c | 110 ++++++++++++++++++++++++++++++ src/ops.h | 13 ++++ src/util.c | 127 +++++++++++++++++++++++++++++++++++ src/util.h | 17 ++++- 12 files changed, 984 insertions(+), 2 deletions(-) create mode 100644 src/daemon.h create mode 100644 src/dmaster.c create mode 100644 src/dslave.c create mode 100644 src/gss.c create mode 100644 src/gss.h create mode 100644 src/net.c create mode 100644 src/net.h create mode 100644 src/ops.c create mode 100644 src/ops.h diff --git a/src/Makefile b/src/Makefile index 6eab9be..4f07fc3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,7 +7,7 @@ override CFLAGS += -std=gnu99 $(INCLUDES) DESTDIR := PREFIX := /usr/local -BIN_PROGS := addmember addclub zfsaddhomedir +BIN_PROGS := addmember addclub zfsaddhomedir ceod EXT_PROGS := config-test LIBCEO_OBJECTS := common.o addhomedir.o @@ -19,6 +19,9 @@ LDAP_PROGS := addmember addclub KRB5_OBJECTS := krb5.o kadm.o KRB5_LDFLAGS := $(shell krb5-config --libs krb5 kadm-client) KRB5_PROGS := addmember addclub +NET_OBJECTS := net.o gss.o ops.o +NET_LDFLAGS := -lsctp $(shell krb5-config --libs gssapi) +NET_PROGS := ceod CONFIG_OBJECTS := config.o parser.o CONFIG_LDFLAGS := CONFIG_PROGS := $(OLDCEO_PROGS) $(LDAP_PROGS) $(KRB5_PROGS) $(NET_PROGS) @@ -30,6 +33,9 @@ all: $(BIN_PROGS) $(LIB_PROGS) $(EXT_PROGS) clean: rm -f $(ALL_PROGS) $(EXT_PROGS) *.o +ceod: dmaster.o dslave.o + $(CC) $(LDFLAGS) -o $@ $^ + config-test: config-test.o parser.o config.o: config.h config-vars.h @@ -38,6 +44,8 @@ install: install -d $(DESTDIR)$(PREFIX)/bin install addmember addclub $(DESTDIR)$(PREFIX)/bin +$(NET_PROGS): LDFLAGS += $(NET_LDFLAGS) +$(NET_PROGS): $(NET_OBJECTS) $(LIBCEO_PROGS): LDFLAGS += $(LIBCEO_LDFLAGS) $(LIBCEO_PROGS): $(LIBCEO_OBJECTS) $(LDAP_PROGS): LDFLAGS += $(LDAP_LDFLAGS) diff --git a/src/daemon.h b/src/daemon.h new file mode 100644 index 0000000..12bf804 --- /dev/null +++ b/src/daemon.h @@ -0,0 +1,10 @@ +/* dmain.c */ +extern int terminate; +extern int fatal_signal; + +/* dslave.c */ +void slave_main(int sock, struct sockaddr *addr); +void setup_slave(void); + +/* builtin-adduser.c */ +extern void builtin_adduser(struct sctp_meta *in, struct sctp_meta *out); diff --git a/src/dmaster.c b/src/dmaster.c new file mode 100644 index 0000000..d2d40f3 --- /dev/null +++ b/src/dmaster.c @@ -0,0 +1,173 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "net.h" +#include "config.h" +#include "gss.h" +#include "daemon.h" +#include "ldap.h" +#include "kadm.h" +#include "krb5.h" +#include "ops.h" + +static struct option opts[] = { + { "detach", 0, NULL, 'd' }, + { NULL, 0, NULL, '\0' }, +}; + +char *prog = NULL; + +int terminate = 0; +int fatal_signal; + +static int detach = 0; + +static void usage() { + fprintf(stderr, "Usage: %s [--detach]\n", prog); + exit(2); +} + +static void signal_handler(int sig) { + if (sig == SIGTERM || sig == SIGINT) { + const char *s = (sig == SIGTERM) ? "terminated" : "interrupt"; + notice("shutting down (%s)", s); + terminate = 1; + fatal_signal = sig; + signal(sig, SIG_DFL); + } else if (sig == SIGSEGV) { + error("segmentation fault"); + signal(sig, SIG_DFL); + raise(sig); + } else if (sig != SIGCHLD) { + fatal("unhandled signal %d", sig); + } +} + +static void setup_signals(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = signal_handler; + + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGSEGV, &sa, NULL); + + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, SIG_IGN); +} + +static void setup_daemon(void) { + if (chdir("/")) + fatalpe("chdir('/')"); + if (detach) { + pid_t pid = fork(); + if (pid < 0) + fatalpe("fork"); + if (pid) + exit(0); + if (setsid() < 0) + fatalpe("setsid"); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } +} + +static void setup_auth(void) { + if (setenv("KRB5CCNAME", "MEMORY:ceod", 1)) + fatalpe("setenv"); + server_acquire_creds("ceod"); +} + +static void accept_one_client(int server) { + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + memset(&addr, 0, addrlen); + + int client = accept(server, (sa *)&addr, &addrlen); + if (client < 0) { + if (errno == EINTR) + return; + fatalpe("accept"); + } + + pid_t pid = fork(); + if (!pid) { + close(server); + slave_main(client, (sa *)&addr); + exit(0); + } + + close(client); +} + +static int master_main(void) { + int sock; + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(9987); + addr.sin_addr.s_addr = INADDR_ANY; + + sock = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP); + if (sock < 0) + fatalpe("socket"); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr))) + fatalpe("bind"); + + if (listen(sock, 128)) + fatalpe("listen"); + + setup_daemon(); + setup_fqdn(); + setup_signals(); + setup_auth(); + setup_ops(); + + notice("now accepting connections"); + + while (!terminate) + accept_one_client(sock); + + return 0; +} + +int main(int argc, char *argv[]) { + int opt; + + prog = basename(argv[0]); + init_log(prog, LOG_PID, LOG_DAEMON); + + configure(); + + while ((opt = getopt_long(argc, argv, "", opts, NULL)) != -1) { + switch (opt) { + case 'd': + detach = 1; + break; + case '?': + usage(); + break; + default: + fatal("error parsing arguments"); + } + } + + if (argc != optind) + usage(); + + return master_main(); +} diff --git a/src/dslave.c b/src/dslave.c new file mode 100644 index 0000000..0001372 --- /dev/null +++ b/src/dslave.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "strbuf.h" +#include "net.h" +#include "config.h" +#include "gss.h" +#include "daemon.h" +#include "ldap.h" +#include "kadm.h" +#include "krb5.h" +#include "ops.h" + +static void signal_handler(int sig) { + if (sig == SIGSEGV) { + error("segmentation fault"); + signal(sig, SIG_DFL); + raise(sig); + } else if (sig != SIGCHLD) { + fatal("unhandled signal %d", sig); + } +} + +static void setup_slave_sigs(void) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = signal_handler; + + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGSEGV, &sa, NULL); + + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGPIPE, SIG_IGN); + + if (terminate) + raise(fatal_signal); +} + +static void setup_client(int client, struct sockaddr *addr) { + struct sctp_event_subscribe events; + memset(&events, 0, sizeof(events)); + events.sctp_data_io_event = 1; + events.sctp_address_event = 1; + events.sctp_send_failure_event = 1; + events.sctp_peer_error_event = 1; + events.sctp_partial_delivery_event = 1; + events.sctp_adaptation_layer_event = 1; + + if (setsockopt(client, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events))) + fatalpe("setsockopt(SCTP_EVENTS)"); +} + +static void handle_auth_message(struct strbuf *in, struct strbuf *out) { + gss_buffer_desc incoming_tok, outgoing_tok; + OM_uint32 maj_stat, min_stat; + + incoming_tok.value = in->buf; + incoming_tok.length = in->len; + + process_server_token(&incoming_tok, &outgoing_tok); + + strbuf_add(out, outgoing_tok.value, outgoing_tok.length); + + if (outgoing_tok.length) { + maj_stat = gss_release_buffer(&min_stat, &outgoing_tok); + if (maj_stat != GSS_S_COMPLETE) + gss_fatal("gss_release_buffer", maj_stat, min_stat); + } +} + +static void handle_op_message(uint32_t in_type, struct strbuf *in, struct strbuf *out) { + struct op *op = get_local_op(in_type); + char *envp[16]; + + debug("running op: %s", op->name); + + if (!op->name) + fatal("operation %x does not exist", in_type); + + make_env(envp, "LANG", "C", "CEO_USER", client_username(), + "CEO_CONFIG_DIR", config_dir, NULL); + char *argv[] = { op->path, NULL, }; + + if (spawnvem(op->path, argv, envp, in, out, 0)) + fatal("child %s failed", op->path); + + free_env(envp); +} + +static void handle_one_message(int sock, struct sctp_meta *in_meta, struct strbuf *in) { + struct strbuf out = STRBUF_INIT; + uint32_t ppid = in_meta->sinfo.sinfo_ppid; + + if (ppid == MSG_AUTH) + handle_auth_message(in, &out); + else + handle_op_message(ppid, in, &out); + + if (out.len && sctp_sendmsg(sock, out.buf, out.len, + (sa *)&in_meta->from, in_meta->fromlen, ppid, + 0, 0, 0, 0) < 0) + fatalpe("sctp_sendmsg"); + + strbuf_release(&out); +} + +void slave_main(int sock, struct sockaddr *addr) { + char addrstr[INET_ADDRSTRLEN]; + struct sockaddr_in *addr_in = (struct sockaddr_in *)addr; + struct sctp_meta msg_meta; + struct strbuf msg = STRBUF_INIT; + + if (addr->sa_family != AF_INET) + fatal("unsupported address family %d", addr->sa_family); + + if (!inet_ntop(AF_INET, &addr_in->sin_addr, addrstr, sizeof(addrstr))) + fatalpe("inet_ntop"); + + notice("accepted connection from %s", addrstr); + + setup_slave_sigs(); + setup_client(sock, addr); + + while (!terminate) { + if (!receive_one_message(sock, &msg_meta, &msg)) + break; + handle_one_message(sock, &msg_meta, &msg); + } + + notice("connection closed by peer %s", addrstr); + + strbuf_release(&msg); +} + diff --git a/src/gss.c b/src/gss.c new file mode 100644 index 0000000..9305ce6 --- /dev/null +++ b/src/gss.c @@ -0,0 +1,183 @@ +#include +#include +#include +#include + +#include "util.h" +#include "gss.h" +#include "net.h" +#include "strbuf.h" + +static gss_cred_id_t my_creds = GSS_C_NO_CREDENTIAL; +static gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT; +static gss_name_t peer_name = GSS_C_NO_NAME; +static gss_name_t imported_service = GSS_C_NO_NAME; +static gss_OID mech_type = GSS_C_NO_OID; +static gss_buffer_desc peer_principal; +static char *peer_username; +static OM_uint32 ret_flags; +static int complete; +char service_name[128]; + +static void display_status(char *prefix, OM_uint32 code, int type) { + OM_uint32 maj_stat, min_stat; + gss_buffer_desc msg; + OM_uint32 msg_ctx = 0; + + maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NULL_OID, + &msg_ctx, &msg); + logmsg(LOG_ERR, "%s: %s", prefix, (char *)msg.value); + gss_release_buffer(&min_stat, &msg); + + while (msg_ctx) { + maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NULL_OID, + &msg_ctx, &msg); + logmsg(LOG_ERR, "additional: %s", (char *)msg.value); + gss_release_buffer(&min_stat, &msg); + } +} + +void gss_fatal(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat) { + logmsg(LOG_ERR, "fatal: %s", msg); + display_status("major", maj_stat, GSS_C_GSS_CODE); + display_status("minor", min_stat, GSS_C_MECH_CODE); + exit(1); +} + +static void import_service(const char *service, const char *hostname) { + OM_uint32 maj_stat, min_stat; + gss_buffer_desc buf_desc; + + if (snprintf(service_name, sizeof(service_name), + "%s@%s", service, hostname) >= sizeof(service_name)) + fatal("service name too long"); + + buf_desc.value = service_name; + buf_desc.length = strlen(service_name); + + maj_stat = gss_import_name(&min_stat, &buf_desc, + GSS_C_NT_HOSTBASED_SERVICE, &imported_service); + if (maj_stat != GSS_S_COMPLETE) + gss_fatal("gss_import_name", maj_stat, min_stat); +} + +static void check_services(OM_uint32 flags) { + debug("gss services: %sconf %sinteg %smutual %sreplay %ssequence", + flags & GSS_C_CONF_FLAG ? "+" : "-", + flags & GSS_C_INTEG_FLAG ? "+" : "-", + flags & GSS_C_MUTUAL_FLAG ? "+" : "-", + flags & GSS_C_REPLAY_FLAG ? "+" : "-", + flags & GSS_C_SEQUENCE_FLAG ? "+" : "-"); + if (~flags & GSS_C_CONF_FLAG) + fatal("confidentiality service required"); + if (~flags & GSS_C_INTEG_FLAG) + fatal("integrity service required"); + if (~flags & GSS_C_MUTUAL_FLAG) + fatal("mutual authentication required"); +} + +void server_acquire_creds(const char *service) { + OM_uint32 maj_stat, min_stat; + OM_uint32 time_rec; + + if (!strlen(fqdn.buf)) + fatal("empty fqdn"); + + import_service(service, fqdn.buf); + + notice("acquiring credentials for %s", service_name); + + maj_stat = gss_acquire_cred(&min_stat, imported_service, GSS_C_INDEFINITE, + GSS_C_NULL_OID_SET, GSS_C_ACCEPT, &my_creds, + NULL, &time_rec); + if (maj_stat != GSS_S_COMPLETE) + gss_fatal("gss_acquire_cred", maj_stat, min_stat); + + if (time_rec != GSS_C_INDEFINITE) + fatal("credentials valid for %d seconds (oops)", time_rec); +} + +void client_acquire_creds(const char *service, const char *hostname) { + import_service(service, hostname); +} + +int process_server_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok) { + OM_uint32 maj_stat, min_stat; + OM_uint32 time_rec; + gss_OID name_type; + + if (complete) + fatal("unexpected %zd-byte token from peer", incoming_tok->length); + + maj_stat = gss_accept_sec_context(&min_stat, &context_handle, my_creds, + incoming_tok, GSS_C_NO_CHANNEL_BINDINGS, &peer_name, &mech_type, + outgoing_tok, &ret_flags, &time_rec, NULL); + if (maj_stat == GSS_S_COMPLETE) { + check_services(ret_flags); + + complete = 1; + + maj_stat = gss_display_name(&min_stat, peer_name, &peer_principal, &name_type); + if (maj_stat != GSS_S_COMPLETE) + gss_fatal("gss_display_name", maj_stat, min_stat); + + notice("client authenticated as %s", (char *)peer_principal.value); + debug("context expires in %d seconds",time_rec); + + } else if (maj_stat != GSS_S_CONTINUE_NEEDED) { + gss_fatal("gss_accept_sec_context", maj_stat, min_stat); + } + + return complete; +} + +int process_client_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok) { + OM_uint32 maj_stat, min_stat; + OM_uint32 time_rec; + gss_OID_desc krb5 = *gss_mech_krb5; + + if (complete) + fatal("unexpected token from peer"); + + maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &context_handle, + imported_service, &krb5, GSS_C_MUTUAL_FLAG | + GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, + GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, + incoming_tok, NULL, outgoing_tok, &ret_flags, + &time_rec); + if (maj_stat == GSS_S_COMPLETE) { + notice("server authenticated as %s", service_name); + notice("context expires in %d seconds", time_rec); + + check_services(ret_flags); + + complete = 1; + + } else if (maj_stat != GSS_S_CONTINUE_NEEDED) { + gss_fatal("gss_init_sec_context", maj_stat, min_stat); + } + + return complete; +} + +int initial_client_token(gss_buffer_t outgoing_tok) { + return process_client_token(GSS_C_NO_BUFFER, outgoing_tok); +} + +char *client_principal(void) { + return complete ? (char *)peer_principal.value : NULL; +} + +char *client_username(void) { + if (!peer_username) { + char *princ = client_principal(); + if (princ) { + peer_username = xstrdup(princ); + char *c = strchr(peer_username, '@'); + if (c) + *c = '\0'; + } + } + return peer_username; +} + diff --git a/src/gss.h b/src/gss.h new file mode 100644 index 0000000..7e7c290 --- /dev/null +++ b/src/gss.h @@ -0,0 +1,11 @@ +#include +#include + +void server_acquire_creds(const char *service); +void client_acquire_creds(const char *service, const char *hostname); +void gss_fatal(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat); +int process_server_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok); +int process_client_token(gss_buffer_t incoming_tok, gss_buffer_t outgoing_tok); +int initial_client_token(gss_buffer_t outgoing_tok); +char *client_principal(void); +char *client_username(void); diff --git a/src/net.c b/src/net.c new file mode 100644 index 0000000..825c917 --- /dev/null +++ b/src/net.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include + +#include "util.h" +#include "net.h" +#include "gss.h" +#include "strbuf.h" + +struct strbuf fqdn = STRBUF_INIT; + +const size_t MAX_MSGLEN = 65536; +const size_t MSG_BUFINC = 4096; + +void setup_fqdn(void) { + struct utsname uts; + struct hostent *lo; + + if (uname(&uts)) + fatalpe("uname"); + lo = gethostbyname(uts.nodename); + if (!lo) + fatalpe("gethostbyname"); + + strbuf_addstr(&fqdn, lo->h_name); +} + +static size_t recv_one_message(int sock, struct sctp_meta *msg_meta, struct strbuf *msg, int *notification) { + size_t len = 0; + int flags; + int bytes; + + strbuf_reset(msg); + + do { + msg_meta->fromlen = sizeof(msg_meta->from); + + bytes = sctp_recvmsg(sock, msg->buf + len, strbuf_avail(msg) - len, + (sa *)&msg_meta->from, &msg_meta->fromlen, &msg_meta->sinfo, &flags); + + if (bytes < 0) + fatalpe("sctp_recvmsg"); + if (!bytes) + break; + len += bytes; + + if (msg->len > MAX_MSGLEN) + fatal("oversized message received"); + if (strbuf_avail(msg) < MSG_BUFINC) + strbuf_grow(msg, MSG_BUFINC); + + } while (~flags & MSG_EOR); + + if (!bytes && len) + fatalpe("EOF in the middle of a message"); + + *notification = flags & MSG_NOTIFICATION; + if (*notification) + notification_dbg(msg->buf); + + strbuf_setlen(msg, len); + return len; +} + +int receive_one_message(int sock, struct sctp_meta *msg_meta, struct strbuf *msg) { + int notification = 0; + + do { + recv_one_message(sock, msg_meta, msg, ¬ification); + } while (notification); + + return msg->len > 0; +} + +void notification_dbg(char *notification) { + union sctp_notification *sn = (union sctp_notification *) notification; + char *extra; + + switch (sn->sn_header.sn_type) { + case SCTP_ASSOC_CHANGE: + extra = "unknown state"; + switch (sn->sn_assoc_change.sac_state) { + case SCTP_COMM_UP: extra = "established"; break; + case SCTP_COMM_LOST: extra = "lost"; break; + case SCTP_RESTART: extra = "restarted"; break; + case SCTP_SHUTDOWN_COMP: extra = "completed shutdown"; break; + case SCTP_CANT_STR_ASSOC: extra = "cannot start"; break; + } + debug("association changed: association 0x%x %s", sn->sn_assoc_change.sac_assoc_id, extra); + break; + case SCTP_PEER_ADDR_CHANGE: + extra = "unknown state"; + switch (sn->sn_paddr_change.spc_state) { + case SCTP_ADDR_AVAILABLE: extra = "unavailable"; break; + case SCTP_ADDR_UNREACHABLE: extra = "unreachable"; break; + case SCTP_ADDR_REMOVED: extra = "removed"; break; + case SCTP_ADDR_ADDED: extra = "added"; break; + case SCTP_ADDR_MADE_PRIM: extra = "made primary"; break; +#ifdef SCTP_ADDR_CONFIRMED + case SCTP_ADDR_CONFIRMED: extra = "confirmed"; break; +#endif + } + + struct sockaddr_in *sa = (struct sockaddr_in *) &sn->sn_paddr_change.spc_aaddr; + char addr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &sa->sin_addr, addr, sizeof(addr)); + debug("peer address change: remote address %s %s", addr, extra); + break; + case SCTP_REMOTE_ERROR: + debug("remote error: association=0x%x error=0x%x", + sn->sn_remote_error.sre_assoc_id, + sn->sn_remote_error.sre_error); + break; + case SCTP_SEND_FAILED: + debug("send failed: association=0x%x error=0x%x", + sn->sn_send_failed.ssf_assoc_id, + sn->sn_send_failed.ssf_error); + break; + case SCTP_ADAPTATION_INDICATION: + debug("adaptation indication: 0x%x", + sn->sn_adaptation_event.sai_adaptation_ind); + break; + case SCTP_PARTIAL_DELIVERY_EVENT: + extra = "unknown indication"; + switch (sn->sn_pdapi_event.pdapi_indication) { + case SCTP_PARTIAL_DELIVERY_ABORTED: + extra = "partial delivery aborted"; + break; + } + debug("partial delivery event: %s", extra); + break; + case SCTP_SHUTDOWN_EVENT: + debug("association 0x%x was shut down", + sn->sn_shutdown_event.sse_assoc_id); + break; + default: + debug("unknown sctp notification type 0x%x\n", + sn->sn_header.sn_type); + } +} diff --git a/src/net.h b/src/net.h new file mode 100644 index 0000000..0e6ac9f --- /dev/null +++ b/src/net.h @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include + +#if ! defined(SCTP_ADDR_CONFIRMED) && defined(__linux__) +#define SCTP_ADDR_CONFIRMED 5 +#endif + +void notification_dbg(char *); + +#ifdef SCTP_ADAPTION_LAYER +#define sctp_adaptation_layer_event sctp_adaption_layer_event +#define sn_adaptation_event sn_adaption_event +#define sai_adaptation_ind sai_adaption_ind +#define SCTP_ADAPTATION_INDICATION SCTP_ADAPTION_INDICATION +#endif + +typedef struct sockaddr sa; + +extern struct strbuf fqdn; +extern void setup_fqdn(void); + +struct sctp_meta { + struct sockaddr_storage from; + socklen_t fromlen; + struct sctp_sndrcvinfo sinfo; +}; + +enum { + MSG_AUTH = 0x8000000, + MSG_EXPLODE = 0x8000001, +}; + +#ifdef MSG_ABORT +#define SCTP_ABORT MSG_ABORT +#define SCTP_EOF MSG_EOF +#endif + +#define EKERB -2 +#define ELDAP -3 + +int receive_one_message(int sock, struct sctp_meta *msg_meta, struct strbuf *msg); diff --git a/src/ops.c b/src/ops.c new file mode 100644 index 0000000..9cf53c3 --- /dev/null +++ b/src/ops.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include + +#include "strbuf.h" +#include "ops.h" +#include "net.h" +#include "util.h" +#include "config.h" + +static struct op *ops; + +static const char *default_op_dir = "/usr/lib/ceod"; +static const char *op_dir; + +static void add_op(char *host, char *name, uint32_t id) { + struct op *new = xmalloc(sizeof(struct op)); + errno = 0; + new->next = ops; + new->name = xstrdup(name); + new->id = id; + new->path = NULL; + + struct hostent *hostent = gethostbyname(host); + if (!hostent) + badconf("cannot add op %s: %s: %s", name, host, hstrerror(h_errno)); + new->hostname = strdup(hostent->h_name); + new->local = !strcmp(fqdn.buf, hostent->h_name); + new->addr = *(struct in_addr *)hostent->h_addr_list[0]; + + if (new->local) { + new->path = xmalloc(strlen(op_dir) + strlen("/op-") + strlen(name) + 1); + sprintf(new->path, "%s/op-%s", op_dir, name); + if (access(new->path, X_OK)) + fatalpe("cannot add op: %s: %s", name, new->path); + } + + ops = new; + debug("added op %s (%s%s)", new->name, new->local ? "" : "on ", + new->local ? "local" : host); +} + +struct op *get_local_op(uint32_t id) { + for (struct op *op = ops; op; op = op->next) { + if (op->local && op->id == id) + return op; + } + return NULL; +} + +struct op *find_op(const char *name) { + for (struct op *op = ops; op; op = op->next) { + if (!strcmp(name, op->name)) + return op; + } + return NULL; +} + +void setup_ops(void) { + char op_config_dir[1024]; + DIR *dp; + struct dirent *de; + struct strbuf line = STRBUF_INIT; + unsigned lineno = 0; + unsigned op_count = 0; + + op_dir = getenv("CEO_LIB_DIR") ?: default_op_dir; + + if (snprintf(op_config_dir, sizeof(op_config_dir), "%s/%s", config_dir, "ops.d") >= sizeof(op_config_dir)) + fatal("ops dir path too long"); + + dp = opendir(op_config_dir); + if (!dp) + fatalpe("opendir: %s", op_config_dir); + + while ((de = readdir(dp))) { + FILE *fp = fopenat(dp, de->d_name, O_RDONLY); + if (!fp) + warnpe("open: %s/%s", op_config_dir, de->d_name); + while (strbuf_getline(&line, fp, '\n') != EOF) { + lineno++; + strbuf_trim(&line); + + if (!line.len || line.buf[0] == '#') + continue; + + struct strbuf **words = strbuf_splitws(&line); + + if (strbuf_list_len(words) != 3) + badconf("%s/%s: expected three words on line %d", op_config_dir, de->d_name, lineno); + + errno = 0; + char *end; + int id = strtol(words[2]->buf, &end, 0); + if (errno || *end) + badconf("%s/%s: invalid id '%s' on line %d", op_config_dir, de->d_name, words[2]->buf, lineno); + + add_op(words[0]->buf, words[1]->buf, id); + op_count++; + + strbuf_list_free(words); + } + } + + closedir(dp); + strbuf_release(&line); +} + diff --git a/src/ops.h b/src/ops.h new file mode 100644 index 0000000..21ee45a --- /dev/null +++ b/src/ops.h @@ -0,0 +1,13 @@ +struct op { + char *name; + uint32_t id; + int local; + char *hostname; + char *path; + struct in_addr addr; + struct op *next; +}; + +void setup_ops(void); +struct op *find_op(const char *name); +struct op *get_local_op(uint32_t id); diff --git a/src/util.c b/src/util.c index 656b587..3078d23 100644 --- a/src/util.c +++ b/src/util.c @@ -1,10 +1,13 @@ +#define _ATFILE_SOURCE #include #include #include #include #include +#include #include #include +#include #include "util.h" #include "strbuf.h" @@ -148,3 +151,127 @@ int spawnv(const char *path, char *const argv[]) { exit(execv(path, argv)); return status; } + +void full_write(int fd, const void *buf, size_t count) { + ssize_t total = 0; + + while (total < count) { + ssize_t wcount = write(fd, (char *)buf + total, count - total); + if (wcount < 0) + fatalpe("write"); + total += wcount; + } +} + +int spawnvem(const char *path, char *const *argv, char *const *envp, const struct strbuf *output, struct strbuf *input, int cap_stderr) { + int pid, wpid, status; + int tochild[2]; + int fmchild[2]; + + if (pipe(tochild)) + fatalpe("pipe"); + if (pipe(fmchild)) + fatalpe("pipe"); + + fflush(stdout); + fflush(stderr); + + pid = fork(); + if (pid < 0) + fatalpe("fork"); + if (!pid) { + dup2(tochild[0], STDIN_FILENO); + dup2(fmchild[1], STDOUT_FILENO); + if (cap_stderr) + dup2(STDOUT_FILENO, STDERR_FILENO); + close(tochild[0]); + close(tochild[1]); + close(fmchild[0]); + close(fmchild[1]); + execve(path, argv, envp); + fatalpe("execve"); + } else { + close(tochild[0]); + close(fmchild[1]); + full_write(tochild[1], output->buf, output->len); + close(tochild[1]); + + if (input) + strbuf_read(input, fmchild[0], 0); + close(fmchild[0]); + } + + wpid = waitpid(pid, &status, 0); + if (wpid < 0) + fatalpe("waitpid"); + else if (wpid != pid) + fatal("waitpid is broken"); + + if (WIFEXITED(status) && WEXITSTATUS(status)) + notice("child %s exited with status %d", path, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + notice("child %s killed by signal %d", path, WTERMSIG(status)); + + return status; +} + +int spawnv_msg(const char *path, char *const *argv, const struct strbuf *output) { + return spawnvem(path, argv, environ, output, NULL, 0); +} + +int check_group(char *username, char *group) { + struct group *grp = getgrnam(group); + char **members; + + if (grp) + for (members = grp->gr_mem; *members; members++) + if (!strcmp(username, *members)) + return 1; + + return 0; +} + +FILE *fopenat(DIR *d, const char *path, int flags) { + int dfd = dirfd(d); + if (dfd < 0) + return NULL; + int fd = openat(dfd, path, flags); + if (fd < 0) + return NULL; + return fdopen(fd, flags & O_RDWR ? "r+" : + flags & O_WRONLY ? "w" : + "r"); +} + +void make_env(char **envp, ...) { + const size_t len = 4096; + size_t used = 0; + int args = 0; + char *buf = xmalloc(len); + va_list ap; + va_start(ap, envp); + char *name, *val; + + while ((name = va_arg(ap, char *))) { + val = va_arg(ap, char *); + if (!val) + continue; + int n = snprintf(buf + used, len - used, "%s=%s", name, val); + if (n < 0) + fatalpe("snprintf"); + if (n >= len - used) + fatal("environment too big"); + + envp[args++] = buf + used; + used += n + 1; + } + + if (!args) + free(buf); + + envp[args] = NULL; +} + +void free_env(char **envp) { + free(*envp); +} diff --git a/src/util.h b/src/util.h index 5d2757f..70fc82d 100644 --- a/src/util.h +++ b/src/util.h @@ -2,8 +2,14 @@ #define CEO_UTIL_H #include +#include #include #include +#include +#include +#include + +#include "strbuf.h" #ifdef __GNUC__ #define NORETURN __attribute__((__noreturn__)) @@ -17,8 +23,18 @@ #define LOG_AUTHPRIV LOG_AUTH #endif +extern char **environ; + int spawnv(const char *path, char *const *argv); +int spawnv_msg(const char *path, char *const *argv, const struct strbuf *output); +int spawnvem(const char *path, char *const *argv, char *const *envp, const struct strbuf *output, struct strbuf *input, int cap_stderr); +void full_write(int fd, const void *buf, size_t count); +ssize_t full_read(int fd, void *buf, size_t len); +FILE *fopenat(DIR *d, const char *path, int flags); +void make_env(char **envp, ...); +void free_env(char **envp); void init_log(const char *ident, int option, int facility); +int check_group(char *username, char *group); PRINTF_LIKE(0) NORETURN void fatal(const char *, ...); PRINTF_LIKE(0) NORETURN void fatalpe(const char *, ...); @@ -59,7 +75,6 @@ static inline void *xcalloc(size_t nmemb, size_t size) { return alloc; } - static inline char *xstrdup(const char *s) { char *dup = strdup(s);