diff --git a/etc/ops/mail b/etc/ops/mail new file mode 100644 index 0000000..7666d8e --- /dev/null +++ b/etc/ops/mail @@ -0,0 +1 @@ +ginseng mail 0x02 diff --git a/src/.gitignore b/src/.gitignore index a7b37bf..cabaea7 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -5,6 +5,7 @@ /addclub /adduser /op-adduser +/op-mail /zfsaddhomedir /config-test /ceod diff --git a/src/Makefile b/src/Makefile index 24471fa..d8f133b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,7 +7,7 @@ DESTDIR := PREFIX := /usr/local BIN_PROGS := addmember addclub ceod -LIB_PROGS := ceoc op-adduser +LIB_PROGS := ceoc op-adduser op-mail EXT_PROGS := config-test LDAP_OBJECTS := ldap.o @@ -24,10 +24,10 @@ NET_LIBS := -lsctp $(shell krb5-config --libs gssapi) NET_PROGS := ceod ceoc PROTO_OBJECTS := ceo.pb-c.o PROTO_LIBS := -lprotobuf-c -PROTO_PROGS := op-adduser addmember addclub +PROTO_PROGS := op-adduser op-mail addmember addclub CONFIG_OBJECTS := config.o parser.o CONFIG_LIBS := -CONFIG_PROGS := $(LDAP_PROGS) $(KRB5_PROGS) $(NET_PROGS) +CONFIG_PROGS := $(LDAP_PROGS) $(KRB5_PROGS) $(NET_PROGS) $(PROTO_PROGS) UTIL_OBJECTS := util.o strbuf.o UTIL_PROGS := config-test $(CONFIG_PROGS) @@ -63,6 +63,7 @@ install_daemon: install -d $(DESTDIR)$(PREFIX)/sbin $(DESTDIR)$(PREFIX)/lib/ceod install ceod $(DESTDIR)$(PREFIX)/sbin install op-adduser $(DESTDIR)$(PREFIX)/lib/ceod + install op-mail $(DESTDIR)$(PREFIX)/lib/ceod install: install_clients install_daemon diff --git a/src/ceo.proto b/src/ceo.proto index 9507583..701055c 100644 --- a/src/ceo.proto +++ b/src/ceo.proto @@ -22,3 +22,12 @@ message AddUser { message AddUserResponse { repeated StatusMessage messages = 1; } + +message UpdateMail { + required string username = 1; + optional string forward = 2; +} + +message UpdateMailResponse { + repeated StatusMessage messages = 1; +} diff --git a/src/op-adduser.c b/src/op-adduser.c index df7eefd..58c8717 100644 --- a/src/op-adduser.c +++ b/src/op-adduser.c @@ -42,6 +42,7 @@ Ceo__AddUserResponse *response_create(void) { return r; } +PRINTF_LIKE(2) int32_t response_message(Ceo__AddUserResponse *r, int32_t status, char *fmt, ...) { va_list args; Ceo__StatusMessage *statusmsg = xmalloc(sizeof(Ceo__StatusMessage)); diff --git a/src/op-mail.c b/src/op-mail.c new file mode 100644 index 0000000..2d10cf7 --- /dev/null +++ b/src/op-mail.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "net.h" +#include "ceo.pb-c.h" +#include "config.h" +#include "strbuf.h" + +char *prog; + +static const int MAX_MESSAGES = 32; +static const int MAX_MESGSIZE = 512; + +Ceo__UpdateMailResponse *response_create(void) { + Ceo__UpdateMailResponse *r = xmalloc(sizeof(Ceo__UpdateMailResponse)); + ceo__update_mail_response__init(r); + r->n_messages = 0; + r->messages = xmalloc(MAX_MESSAGES * sizeof(Ceo__StatusMessage *)); + return r; +} + +PRINTF_LIKE(2) +int32_t response_message(Ceo__UpdateMailResponse *r, int32_t status, char *fmt, ...) { + va_list args; + Ceo__StatusMessage *statusmsg = xmalloc(sizeof(Ceo__StatusMessage)); + char *message = xmalloc(MAX_MESGSIZE); + + va_start(args, fmt); + vsnprintf(message, MAX_MESGSIZE, fmt, args); + va_end(args); + + ceo__status_message__init(statusmsg); + statusmsg->status = status; + statusmsg->message = message; + + if (r->n_messages >= MAX_MESSAGES) + fatal("too many messages"); + r->messages[r->n_messages++] = statusmsg; + + if (status) + error("%s", message); + else + notice("%s", message); + + return status; +} + +void response_delete(Ceo__UpdateMailResponse *r) { + int i; + + for (i = 0; i < r->n_messages; i++) { + free(r->messages[i]->message); + free(r->messages[i]); + } + free(r->messages); + free(r); +} + +static int check_update_mail(Ceo__UpdateMail *in, Ceo__UpdateMailResponse *out, char *client) { + int client_office = check_group(client, "office"); + int client_syscom = check_group(client, "syscom"); + + notice("update mail uid=%s mail=%s by %s", in->username, in->forward, client); + + if (!in->username) + return response_message(out, EINVAL, "missing required argument: username"); + + int recipient_syscom = check_group(in->username, "syscom"); + + if (!client_syscom && !client_office && strcmp(in->username, client)) + return response_message(out, EPERM, "%s not authorized to update mail", client); + + if (recipient_syscom && !client_syscom) + return response_message(out, EPERM, "denied, recipient is on systems committee"); + + /* don't allow office staff to set complicated forwards; in particular | is a security hole */ + if (in->forward) { + for (char *p = in->forward; *p; p++) { + switch (*p) { + case '"': + case '\'': + case ',': + case '|': + case '$': + case '/': + case '#': + case ':': + return response_message(out, EINVAL, "invalid character in forward: %c", *p); + default: + break; + } + + if (isspace(*p)) + return response_message(out, EINVAL, "invalid character in forward: %c", *p); + } + } + + return 0; +} + +static int32_t update_mail(Ceo__UpdateMail *in, Ceo__UpdateMailResponse *out, char *client) { + int32_t chk_stat; + mode_t mask; + + chk_stat = check_update_mail(in, out, client); + if (chk_stat) + return chk_stat; + + mask = umask(0); + + if (in->forward) { + struct passwd *user = getpwnam(in->username); + + if (!user) + return response_message(out, errno, "getpwnam: %s: %s", in->username, strerror(errno)); + + if (setregid(user->pw_gid, user->pw_gid)) + return response_message(out, errno, "setregid: %s: %s", in->username, strerror(errno)); + if (setreuid(user->pw_uid, user->pw_uid)) + return response_message(out, errno, "setreuid: %s: %s", in->username, strerror(errno)); + + char path[1024]; + + if (snprintf(path, sizeof(path), "%s/.forward", user->pw_dir) >= sizeof(path)) + return response_message(out, ENAMETOOLONG, "homedir is too long"); + + if (unlink(path) && errno != ENOENT) + return response_message(out, errno, "unlink: %s: %s", path, strerror(errno)); + + if (*in->forward) { + int fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644); + if (fd < 0) + return response_message(out, errno, "open: %s: %s", path, strerror(errno)); + + struct strbuf file_contents = STRBUF_INIT; + strbuf_addf(&file_contents, "%s\n", in->forward); + + full_write(fd, file_contents.buf, file_contents.len); + + strbuf_release(&file_contents); + + if (close(fd)) + return response_message(out, errno, "close: %s: %s", path, strerror(errno)); + + response_message(out, 0, "successfully updated forward for %s", in->username); + } else { + response_message(out, 0, "successfully cleared forward for %s", in->username); + } + } + + umask(mask); + + return response_message(out, 0, "finished updating mail for %s", in->username); +} + +void cmd_update_mail(void) { + Ceo__UpdateMail *in_proto; + Ceo__UpdateMailResponse *out_proto = response_create(); + struct strbuf in = STRBUF_INIT; + struct strbuf out = STRBUF_INIT; + + if (strbuf_read(&in, STDIN_FILENO, 0) < 0) + fatalpe("read"); + + in_proto = ceo__update_mail__unpack(&protobuf_c_default_allocator, + in.len, (uint8_t *)in.buf); + if (!in_proto) + fatal("malformed update mail message"); + + char *client = getenv("CEO_USER"); + if (!client) + fatal("environment variable CEO_USER is not set"); + + update_mail(in_proto, out_proto, client); + + strbuf_grow(&out, ceo__update_mail_response__get_packed_size(out_proto)); + strbuf_setlen(&out, ceo__update_mail_response__pack(out_proto, (uint8_t *)out.buf)); + full_write(STDOUT_FILENO, out.buf, out.len); + + ceo__update_mail__free_unpacked(in_proto, &protobuf_c_default_allocator); + response_delete(out_proto); + + strbuf_release(&in); + strbuf_release(&out); +} + +int main(int argc, char *argv[]) { + prog = xstrdup(basename(argv[0])); + init_log(prog, LOG_PID, LOG_AUTHPRIV, 0); + + configure(); + + cmd_update_mail(); + + free_config(); + free(prog); + + return 0; +}