Improve error handling when writing
[mspang/pyceo.git] / src / op-mail.c
1 #include <string.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <signal.h>
5 #include <syslog.h>
6 #include <libgen.h>
7 #include <getopt.h>
8 #include <errno.h>
9 #include <netdb.h>
10 #include <alloca.h>
11 #include <pwd.h>
12 #include <grp.h>
13 #include <fcntl.h>
14 #include <sys/wait.h>
15 #include <sys/stat.h>
16 #include <ctype.h>
17
18 #include "util.h"
19 #include "net.h"
20 #include "ceo.pb-c.h"
21 #include "config.h"
22 #include "strbuf.h"
23
24 char *prog;
25
26 static const int MAX_MESSAGES = 32;
27 static const int MAX_MESGSIZE = 512;
28
29 Ceo__UpdateMailResponse *response_create(void) {
30     Ceo__UpdateMailResponse *r = xmalloc(sizeof(Ceo__UpdateMailResponse));
31     ceo__update_mail_response__init(r);
32     r->n_messages = 0;
33     r->messages = xmalloc(MAX_MESSAGES *  sizeof(Ceo__StatusMessage *));
34     return r;
35 }
36
37 PRINTF_LIKE(2)
38 int32_t response_message(Ceo__UpdateMailResponse *r, int32_t status, char *fmt, ...) {
39     va_list args;
40     Ceo__StatusMessage *statusmsg = xmalloc(sizeof(Ceo__StatusMessage));
41     char *message = xmalloc(MAX_MESGSIZE);
42
43     va_start(args, fmt);
44     vsnprintf(message, MAX_MESGSIZE, fmt, args);
45     va_end(args);
46
47     ceo__status_message__init(statusmsg);
48     statusmsg->status = status;
49     statusmsg->message = message;
50
51     if (r->n_messages >= MAX_MESSAGES)
52         fatal("too many messages");
53     r->messages[r->n_messages++] = statusmsg;
54
55     if (status)
56         error("%s", message);
57     else
58         notice("%s", message);
59
60     return status;
61 }
62
63 void response_delete(Ceo__UpdateMailResponse *r) {
64     int i;
65
66     for (i = 0; i < r->n_messages; i++) {
67         free(r->messages[i]->message);
68         free(r->messages[i]);
69     }
70     free(r->messages);
71     free(r);
72 }
73
74 static int check_update_mail(Ceo__UpdateMail *in, Ceo__UpdateMailResponse *out, char *client) {
75     int client_office = check_group(client, "office");
76     int client_syscom = check_group(client, "syscom");
77
78     notice("update mail uid=%s mail=%s by %s", in->username, in->forward, client);
79
80     if (!in->username)
81         return response_message(out, EINVAL, "missing required argument: username");
82
83     int recipient_syscom = check_group(in->username, "syscom");
84
85     if (!client_syscom && !client_office && strcmp(in->username, client))
86         return response_message(out, EPERM, "%s not authorized to update mail", client);
87
88     if (recipient_syscom && !client_syscom)
89         return response_message(out, EPERM, "denied, recipient is on systems committee");
90
91     /* don't allow office staff to set complicated forwards; in particular | is a security hole */
92     if (in->forward) {
93         for (char *p = in->forward; *p; p++) {
94             switch (*p) {
95                 case '"':
96                 case '\'':
97                 case ',':
98                 case '|':
99                 case '$':
100                 case '/':
101                 case '#':
102                 case ':':
103                     return response_message(out, EINVAL, "invalid character in forward: %c", *p);
104                 default:
105                     break;
106             }
107
108             if (isspace(*p))
109                 return response_message(out, EINVAL, "invalid character in forward: %c", *p);
110         }
111     }
112
113     return 0;
114 }
115
116 static int32_t update_mail(Ceo__UpdateMail *in, Ceo__UpdateMailResponse *out, char *client) {
117     int32_t chk_stat;
118     mode_t mask;
119
120     chk_stat = check_update_mail(in, out, client);
121     if (chk_stat)
122         return chk_stat;
123
124     mask = umask(0);
125
126     if (in->forward) {
127         struct passwd *user = getpwnam(in->username);
128
129         if (!user)
130             return response_message(out, errno, "getpwnam: %s: %s", in->username, strerror(errno));
131
132         if (setregid(user->pw_gid, user->pw_gid))
133             return response_message(out, errno, "setregid: %s: %s", in->username, strerror(errno));
134         if (setreuid(user->pw_uid, user->pw_uid))
135             return response_message(out, errno, "setreuid: %s: %s", in->username, strerror(errno));
136
137         char path[1024];
138
139         if (snprintf(path, sizeof(path), "%s/.forward", user->pw_dir) >= sizeof(path))
140             return response_message(out, ENAMETOOLONG, "homedir is too long");
141
142         if (unlink(path) && errno != ENOENT)
143             return response_message(out, errno, "unlink: %s: %s", path, strerror(errno));
144
145         if (*in->forward) {
146             int fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
147             if (fd < 0)
148                 return response_message(out, errno, "open: %s: %s", path, strerror(errno));
149
150             struct strbuf file_contents = STRBUF_INIT;
151             strbuf_addf(&file_contents, "%s\n", in->forward);
152
153             if (full_write(fd, file_contents.buf, file_contents.len))
154                 response_message(out, errno, "write: %s: %s", path, strerror(errno));
155
156             strbuf_release(&file_contents);
157
158             if (close(fd))
159                 return response_message(out, errno, "close: %s: %s", path, strerror(errno));
160
161             response_message(out, 0, "successfully updated forward for %s", in->username);
162         } else {
163             response_message(out, 0, "successfully cleared forward for %s", in->username);
164         }
165     }
166
167     umask(mask);
168
169     return response_message(out, 0, "finished updating mail for %s", in->username);
170 }
171
172 void cmd_update_mail(void) {
173     Ceo__UpdateMail *in_proto;
174     Ceo__UpdateMailResponse *out_proto = response_create();
175     struct strbuf in = STRBUF_INIT;
176     struct strbuf out = STRBUF_INIT;
177
178     if (strbuf_read(&in, STDIN_FILENO, 0) < 0)
179         fatalpe("read");
180
181     in_proto = ceo__update_mail__unpack(&protobuf_c_default_allocator,
182             in.len, (uint8_t *)in.buf);
183     if (!in_proto)
184         fatal("malformed update mail message");
185
186     char *client = getenv("CEO_USER");
187     if (!client)
188         fatal("environment variable CEO_USER is not set");
189
190     update_mail(in_proto, out_proto, client);
191
192     strbuf_grow(&out, ceo__update_mail_response__get_packed_size(out_proto));
193     strbuf_setlen(&out, ceo__update_mail_response__pack(out_proto, (uint8_t *)out.buf));
194
195     if (full_write(STDOUT_FILENO, out.buf, out.len))
196         fatalpe("write: stdout");
197
198     ceo__update_mail__free_unpacked(in_proto, &protobuf_c_default_allocator);
199     response_delete(out_proto);
200
201     strbuf_release(&in);
202     strbuf_release(&out);
203 }
204
205 int main(int argc, char *argv[]) {
206     prog = xstrdup(basename(argv[0]));
207     init_log(prog, LOG_PID, LOG_AUTHPRIV, 0);
208
209     configure();
210
211     cmd_update_mail();
212
213     free_config();
214     free(prog);
215
216     return 0;
217 }