Add mail changing
authorMichael Spang <mspang@csclub.uwaterloo.ca>
Sun, 23 Aug 2009 00:21:28 +0000 (20:21 -0400)
committerMichael Spang <mspang@csclub.uwaterloo.ca>
Sun, 23 Aug 2009 17:30:24 +0000 (13:30 -0400)
This allows office staff to update people's .forward files via ceod.

etc/ops/mail [new file with mode: 0644]
src/.gitignore
src/Makefile
src/ceo.proto
src/op-adduser.c
src/op-mail.c [new file with mode: 0644]

diff --git a/etc/ops/mail b/etc/ops/mail
new file mode 100644 (file)
index 0000000..7666d8e
--- /dev/null
@@ -0,0 +1 @@
+ginseng mail 0x02
index a7b37bf..cabaea7 100644 (file)
@@ -5,6 +5,7 @@
 /addclub
 /adduser
 /op-adduser
+/op-mail
 /zfsaddhomedir
 /config-test
 /ceod
index 24471fa..d8f133b 100644 (file)
@@ -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
 
index 9507583..701055c 100644 (file)
@@ -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;
+}
index df7eefd..58c8717 100644 (file)
@@ -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 (file)
index 0000000..2d10cf7
--- /dev/null
@@ -0,0 +1,214 @@
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <syslog.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <errno.h>
+#include <netdb.h>
+#include <alloca.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#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;
+}