This commit is contained in:
Michael Spang 2009-01-31 01:40:18 -05:00
parent 7de577ab32
commit ef6b18c7bb
12 changed files with 984 additions and 2 deletions

View File

@ -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)

10
src/daemon.h Normal file
View File

@ -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);

173
src/dmaster.c Normal file
View File

@ -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();
}

146
src/dslave.c Normal file
View File

@ -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);
}

183
src/gss.c Normal file
View File

@ -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;
}

11
src/gss.h Normal file
View File

@ -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);

141
src/net.c Normal file
View File

@ -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);
}
}

45
src/net.h Normal file
View File

@ -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);

110
src/ops.c Normal file
View File

@ -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);
}

13
src/ops.h Normal file
View File

@ -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);

View 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 <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <grp.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;
}
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);
}

View File

@ -2,8 +2,14 @@
#define CEO_UTIL_H
#include <stdlib.h>
#include <stdio.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__))
@ -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);