tell ceod when it is a club rep; club reps don't need the new member email
[mspang/pyceo.git] / src / op-adduser.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 <sys/wait.h>
14
15 #include "util.h"
16 #include "net.h"
17 #include "ceo.pb-c.h"
18 #include "config.h"
19 #include "gss.h"
20 #include "krb5.h"
21 #include "ldap.h"
22 #include "homedir.h"
23 #include "kadm.h"
24 #include "daemon.h"
25 #include "strbuf.h"
26
27 char *prog;
28
29 static const int MAX_MESSAGES = 32;
30 static const int MAX_MESGSIZE = 512;
31
32 char *user_types[] = {
33     [CEO__ADD_USER__TYPE__MEMBER] = "member",
34     [CEO__ADD_USER__TYPE__CLUB] = "club",
35     [CEO__ADD_USER__TYPE__CLUB_REP] = "clubrep",
36 };
37
38 Ceo__AddUserResponse *response_create(void) {
39     Ceo__AddUserResponse *r = xmalloc(sizeof(Ceo__AddUserResponse));
40     ceo__add_user_response__init(r);
41     r->n_messages = 0;
42     r->messages = xmalloc(MAX_MESSAGES *  sizeof(Ceo__StatusMessage *));
43     return r;
44 }
45
46 PRINTF_LIKE(2)
47 int32_t response_message(Ceo__AddUserResponse *r, int32_t status, char *fmt, ...) {
48     va_list args;
49     Ceo__StatusMessage *statusmsg = xmalloc(sizeof(Ceo__StatusMessage));
50     char *message = xmalloc(MAX_MESGSIZE);
51
52     va_start(args, fmt);
53     vsnprintf(message, MAX_MESGSIZE, fmt, args);
54     va_end(args);
55
56     ceo__status_message__init(statusmsg);
57     statusmsg->status = status;
58     statusmsg->message = message;
59
60     if (r->n_messages >= MAX_MESSAGES)
61         fatal("too many messages");
62     r->messages[r->n_messages++] = statusmsg;
63
64     if (status)
65         error("%s", message);
66     else
67         notice("%s", message);
68
69     return status;
70 }
71
72 void response_delete(Ceo__AddUserResponse *r) {
73     int i;
74
75     for (i = 0; i < r->n_messages; i++) {
76         free(r->messages[i]->message);
77         free(r->messages[i]);
78     }
79     free(r->messages);
80     free(r);
81 }
82
83
84 static int check_adduser(Ceo__AddUser *in, Ceo__AddUserResponse *out, char *client) {
85     int office = check_group(client, "office");
86     int syscom = check_group(client, "syscom");
87
88     notice("adding uid=%s cn=%s by %s", in->username, in->realname, client);
89
90     if (!office && !syscom)
91         return response_message(out, EPERM, "%s not authorized to create users", client);
92
93     if (!in->username)
94         return response_message(out, EINVAL, "missing required argument: username");
95     if (!in->realname)
96         return response_message(out, EINVAL, "missing required argument: realname");
97
98     switch (in->type) {
99         case CEO__ADD_USER__TYPE__MEMBER:
100         case CEO__ADD_USER__TYPE__CLUB_REP:
101             if (!in->password)
102                 return response_message(out, EINVAL, "missing required argument: password");
103             break;
104
105         case CEO__ADD_USER__TYPE__CLUB:
106             if (in->password)
107                 return response_message(out, EINVAL, "club accounts cannot have passwords");
108             if (in->program)
109                 return response_message(out, EINVAL, "club accounts cannot have programs");
110             break;
111
112         default:
113             return response_message(out, EINVAL, "invalid user type: %d", in->type);
114     }
115
116     if (getpwnam(in->username) != NULL)
117         return response_message(out, EEXIST, "user %s already exists", in->username);
118
119     if (getgrnam(in->username) != NULL)
120         return response_message(out, EEXIST, "group %s already exists", in->username);
121
122     if (ceo_user_exists(in->username))
123         return response_message(out, EEXIST, "user %s already exists in LDAP", in->username);
124
125     if (ceo_group_exists(in->username))
126         return response_message(out, EEXIST, "group %s already exists in LDAP", in->username);
127
128     return 0;
129 }
130
131 static void adduser_spam(Ceo__AddUser *in, Ceo__AddUserResponse *out, char *client, char *prog, int status) {
132     char *argv[] = {
133         notify_hook, prog, client,
134         in->username, in->realname, in->program ?: "",
135         status ? "failure" : "success", NULL
136     };
137
138     struct strbuf message = STRBUF_INIT;
139     for (int i = 0; i < out->n_messages; i++)
140         strbuf_addf(&message, "%s\n", out->messages[i]->message);
141
142     spawnv_msg(notify_hook, argv, &message);
143     strbuf_release(&message);
144 }
145
146 static int32_t addmember(Ceo__AddUser *in, Ceo__AddUserResponse *out) {
147     char homedir[1024];
148     char principal[1024];
149     int user_stat, group_stat, krb_stat, home_stat, quota_stat;
150     int id;
151
152     if (snprintf(principal, sizeof(principal), "%s@%s",
153                 in->username, krb5_realm) >= sizeof(principal))
154         fatal("principal overflow");
155
156     if (snprintf(homedir, sizeof(homedir), "%s/%s",
157                  member_home, in->username) >= sizeof(homedir))
158         fatal("homedir overflow");
159
160     if ((id = ceo_new_uid(member_min_id, member_max_id)) <= 0)
161         fatal("no available uids in range [%ld, %ld]", member_min_id, member_max_id);
162
163     if ((krb_stat = ceo_del_princ(in->username)))
164         return response_message(out, EEXIST, "unable to overwrite orphaned kerberos principal %s", in->username);
165
166     if ((user_stat = ceo_add_user(in->username, ldap_users_base, "member", in->realname, homedir, principal,
167             member_shell, id, "program", in->program, NULL)))
168         return response_message(out, ELDAP, "unable to create ldap account %s", in->username);
169     response_message(out, 0, "successfully created ldap account");
170
171     /* errors that occur after this point are not fatal  */
172
173     if ((krb_stat = ceo_add_princ(in->username, in->password)))
174         return response_message(out, EKERB, "unable to create kerberos principal %s", in->username);
175     response_message(out, 0, "successfully created principal");
176
177     if ((group_stat = ceo_add_group(in->username, ldap_groups_base, id)))
178         response_message(out, ELDAP, "unable to create ldap group %s", in->username);
179     else
180         response_message(out, 0, "successfully created ldap group");
181
182     if ((home_stat = ceo_create_home(homedir, member_home_skel, id, id, NULL, NULL, in->email)))
183         response_message(out, EHOME, "unable to create home directory for %s", in->username);
184     else
185         response_message(out, 0, "successfully created home directory");
186
187     if ((quota_stat = ceo_set_quota("ctdalek", id)))
188         response_message(out, EQUOTA, "unable to set quota for %s", in->username);
189     else
190         response_message(out, 0, "successfully set quota");
191
192     return krb_stat || user_stat || group_stat || home_stat || quota_stat;
193 }
194
195 static int32_t addclub(Ceo__AddUser *in, Ceo__AddUserResponse *out) {
196     char homedir[1024];
197     char acl[64];
198     int krb_stat, user_stat, group_stat, sudo_stat, home_stat, quota_stat;
199     int id;
200
201     if (snprintf(homedir, sizeof(homedir), "%s/%s", club_home, in->username) >= sizeof(homedir))
202         fatal("homedir overflow");
203
204     if ((id = ceo_new_uid(club_min_id, club_max_id)) <= 0)
205         fatal("no available uids in range [%ld, %ld]", club_min_id, club_max_id);
206
207     if (snprintf(acl, sizeof(acl), CLUB_ACL, id) >= sizeof(acl))
208         fatal("acl overflow");
209
210     if ((krb_stat = ceo_del_princ(in->username)))
211         return response_message(out, EKERB, "unable to clear principal %s", in->username);
212
213     if ((user_stat = ceo_add_user(in->username, ldap_users_base, "club", in->realname, homedir,
214             NULL, club_shell, id, NULL)))
215         return response_message(out, ELDAP, "unable to create ldap account %s", in->username);
216     response_message(out, 0, "successfully created ldap account");
217
218     /* errors that occur after this point are not fatal  */
219
220     if ((group_stat = ceo_add_group(in->username, ldap_groups_base, id)))
221         response_message(out, ELDAP, "unable to create ldap group %s", in->username);
222     else
223         response_message(out, 0, "successfully created ldap group");
224
225     if ((sudo_stat = ceo_add_group_sudo(in->username, ldap_sudo_base)))
226         response_message(out, ELDAP, "unable to create ldap sudoers %s", in->username);
227     else
228         response_message(out, 0, "successfully created ldap sudoers");
229
230     if ((home_stat = ceo_create_home(homedir, club_home_skel, id, id, acl, acl, NULL)))
231         response_message(out, EHOME, "unable to create home directory for %s", in->username);
232     else
233         response_message(out, 0, "successfully created home directory");
234
235     if ((quota_stat = ceo_set_quota("csc", id)))
236         response_message(out, EQUOTA, "unable to set quota for %s", in->username);
237     else
238         response_message(out, 0, "successfully set quota");
239
240     return user_stat || group_stat || sudo_stat || home_stat || quota_stat;
241 }
242
243 static int32_t adduser(Ceo__AddUser *in, Ceo__AddUserResponse *out, char *client) {
244     int32_t chk_stat, status;
245     char *prog;
246
247     chk_stat = check_adduser(in, out, client);
248     if (chk_stat)
249         return chk_stat;
250
251     if (in->type == CEO__ADD_USER__TYPE__MEMBER) {
252         status = addmember(in, out);
253         prog = "addmember";
254     } else if (in->type == CEO__ADD_USER__TYPE__CLUB_REP) {
255         status = addmember(in, out);
256         prog = "addclubrep";
257     } else if (in->type == CEO__ADD_USER__TYPE__CLUB) {
258         status = addclub(in, out);
259         prog = "addclub";
260     } else {
261         fatal("unknown user type %d", in->type);
262     }
263
264     if (status)
265         response_message(out, 0, "there were failures, please contact systems committee");
266
267     adduser_spam(in, out, client, prog, status);
268
269     return status;
270 }
271
272 void cmd_adduser(void) {
273     Ceo__AddUser *in_proto;
274     Ceo__AddUserResponse *out_proto = response_create();
275     struct strbuf in = STRBUF_INIT;
276     struct strbuf out = STRBUF_INIT;
277
278     if (strbuf_read(&in, STDIN_FILENO, 0) < 0)
279         fatalpe("read");
280
281     in_proto = ceo__add_user__unpack(&protobuf_c_default_allocator,
282             in.len, (uint8_t *)in.buf);
283     if (!in_proto)
284         fatal("malformed add user message");
285
286     char *client = getenv("CEO_USER");
287     if (!client)
288         fatal("environment variable CEO_USER is not set");
289
290     adduser(in_proto, out_proto, client);
291
292     strbuf_grow(&out, ceo__add_user_response__get_packed_size(out_proto));
293     strbuf_setlen(&out, ceo__add_user_response__pack(out_proto, (uint8_t *)out.buf));
294
295     if (full_write(STDOUT_FILENO, out.buf, out.len))
296         fatalpe("write: stdout");
297
298     ceo__add_user__free_unpacked(in_proto, &protobuf_c_default_allocator);
299     response_delete(out_proto);
300
301     strbuf_release(&in);
302     strbuf_release(&out);
303 }
304
305 int main(int argc, char *argv[]) {
306     prog = xstrdup(basename(argv[0]));
307     init_log(prog, LOG_PID, LOG_AUTHPRIV, 0);
308
309     configure();
310
311     if (setenv("KRB5CCNAME", "MEMORY:adduser", 1))
312         fatalpe("setenv");
313
314     ceo_krb5_init();
315     ceo_krb5_auth(ldap_admin_principal);
316     ceo_ldap_init();
317     ceo_kadm_init();
318
319     cmd_adduser();
320
321     ceo_kadm_cleanup();
322     ceo_ldap_cleanup();
323     ceo_krb5_deauth();
324     ceo_krb5_cleanup();
325
326     free_config();
327     free(prog);
328
329     return 0;
330 }