Add ceod
authorMichael Spang <mspang@csclub.uwaterloo.ca>
Sat, 31 Jan 2009 06:40:18 +0000 (01:40 -0500)
committerMichael Spang <mspang@csclub.uwaterloo.ca>
Sat, 31 Jan 2009 06:57:06 +0000 (01:57 -0500)
12 files changed:
src/Makefile
src/daemon.h [new file with mode: 0644]
src/dmaster.c [new file with mode: 0644]
src/dslave.c [new file with mode: 0644]
src/gss.c [new file with mode: 0644]
src/gss.h [new file with mode: 0644]
src/net.c [new file with mode: 0644]
src/net.h [new file with mode: 0644]
src/ops.c [new file with mode: 0644]
src/ops.h [new file with mode: 0644]
src/util.c
src/util.h

index 6eab9be..4f07fc3 100644 (file)
@@ -7,7 +7,7 @@ override CFLAGS  += -std=gnu99 $(INCLUDES)
 DESTDIR :=
 PREFIX  := /usr/local
 
 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
 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
 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)
 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
 
 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
 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
 
        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)
 $(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 (file)
index 0000000..12bf804
--- /dev/null
@@ -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 (file)
index 0000000..d2d40f3
--- /dev/null
@@ -0,0 +1,173 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <syslog.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <errno.h>
+#include <netdb.h>
+#include <alloca.h>
+
+#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 (file)
index 0000000..0001372
--- /dev/null
@@ -0,0 +1,146 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <syslog.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <errno.h>
+#include <netdb.h>
+#include <alloca.h>
+
+#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 (file)
index 0000000..9305ce6
--- /dev/null
+++ b/src/gss.c
@@ -0,0 +1,183 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <grp.h>
+
+#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 (file)
index 0000000..7e7c290
--- /dev/null
+++ b/src/gss.h
@@ -0,0 +1,11 @@
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+
+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 (file)
index 0000000..825c917
--- /dev/null
+++ b/src/net.c
@@ -0,0 +1,141 @@
+#include <stdio.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#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, &notification);
+    } 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 (file)
index 0000000..0e6ac9f
--- /dev/null
+++ b/src/net.h
@@ -0,0 +1,45 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/sctp.h>
+#include <arpa/inet.h>
+#include <gssapi/gssapi.h>
+
+#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 (file)
index 0000000..9cf53c3
--- /dev/null
+++ b/src/ops.c
@@ -0,0 +1,110 @@
+#include <errno.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netdb.h>
+
+#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 (file)
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);
index 656b587..3078d23 100644 (file)
@@ -1,10 +1,13 @@
+#define _ATFILE_SOURCE
 #include <unistd.h>
 #include <sys/wait.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
 #include <unistd.h>
 #include <sys/wait.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <fcntl.h>
 #include <syslog.h>
 #include <errno.h>
 #include <syslog.h>
 #include <errno.h>
+#include <grp.h>
 
 #include "util.h"
 #include "strbuf.h"
 
 #include "util.h"
 #include "strbuf.h"
@@ -148,3 +151,127 @@ int spawnv(const char *path, char *const argv[]) {
         exit(execv(path, argv));
     return status;
 }
         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);
+}
index 5d2757f..70fc82d 100644 (file)
@@ -2,8 +2,14 @@
 #define CEO_UTIL_H
 
 #include <stdlib.h>
 #define CEO_UTIL_H
 
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 #include <stdarg.h>
 #include <string.h>
 #include <stdarg.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "strbuf.h"
 
 #ifdef __GNUC__
 #define NORETURN __attribute__((__noreturn__))
 
 #ifdef __GNUC__
 #define NORETURN __attribute__((__noreturn__))
 #define LOG_AUTHPRIV LOG_AUTH
 #endif
 
 #define LOG_AUTHPRIV LOG_AUTH
 #endif
 
+extern char **environ;
+
 int spawnv(const char *path, char *const *argv);
 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);
 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 *, ...);
 
 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;
 }
 
     return alloc;
 }
 
-
 static inline char *xstrdup(const char *s) {
     char *dup = strdup(s);
 
 static inline char *xstrdup(const char *s) {
     char *dup = strdup(s);