]> mj.ucw.cz Git - subauth.git/blob - server/cmd.c
Admin is root
[subauth.git] / server / cmd.c
1 /*
2  *      Sub-authentication Daemon: Commands
3  *
4  *      (c) 2017 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/trans.h>
9
10 #include <pwd.h>
11 #include <stdarg.h>
12 #include <stdio.h>
13
14 #include "subauthd.h"
15
16 static void NONRET cmd_error(struct client *c, const char *err, ...)
17 {
18   va_list args;
19   char msg[1024];
20   va_start(args, err);
21   vsnprintf(msg, sizeof(msg), err, args);
22   json_object_set(c->reply, "error", json_new_string(c->json, msg));
23   trans_throw("adhoc", NULL, "Error caught");
24   va_end(args);
25 }
26
27 static void cmd_ok(struct client *c)
28 {
29   json_object_set(c->reply, "error", json_new_string_ref(c->json, ""));
30 }
31
32 const char *get_string(struct json_node *n, const char *key)
33 {
34   struct json_node *s = json_object_get(n, key);
35   if (s && s->type == JSON_STRING)
36     return s->string;
37   else
38     return NULL;
39 }
40
41 bool get_uint(struct json_node *n, const char *key, uint *dest)
42 {
43   struct json_node *s = json_object_get(n, key);
44   if (s && s->type == JSON_NUMBER)
45     {
46       uint u = (uint) s->number;
47       if ((double) u == s->number)
48         {
49           *dest = u;
50           return 1;
51         }
52     }
53   *dest = 0;
54   return 0;
55 }
56
57 struct json_node **get_array(struct json_node *n, const char *key)
58 {
59   struct json_node *s = json_object_get(n, key);
60   if (s && s->type == JSON_ARRAY)
61     return s->elements;
62   else
63     return NULL;
64 }
65
66 struct json_node *get_object(struct json_node *n, const char *key)
67 {
68   struct json_node *s = json_object_get(n, key);
69   if (s && s->type == JSON_OBJECT)
70     return s;
71   else
72     return NULL;
73 }
74
75 static const char *cmd_need_string(struct client *c, const char *key)
76 {
77   const char *val = get_string(c->request, key);
78   if (!val)
79     cmd_error(c, "Missing %s", key);
80   return val;
81 }
82
83 static struct auth_zone *cmd_need_zone(struct client *c)
84 {
85   const char *name = cmd_need_string(c, "zone");
86   struct auth_zone *az = auth_find_zone(name);
87   if (!az)
88     cmd_error(c, "No such zone");
89   return az;
90 }
91
92 static bool cmd_is_admin(struct client *c)
93 {
94   return (c->uid == 0);
95 }
96
97 static void cmd_require_admin(struct client *c)
98 {
99   if (!cmd_is_admin(c))
100     cmd_error(c, "Permission denied");
101 }
102
103 static const char *cmd_need_target_login(struct client *c)
104 {
105   const char *l = get_string(c->request, "login");
106   if (l)
107     {
108       cmd_require_admin(c);
109       return l;
110     }
111   else
112     {
113       struct passwd *pw = getpwuid(c->uid);
114       if (!pw)
115         cmd_error(c, "You do not exist");
116       return json_strdup(c->json, pw->pw_name);
117     }
118 }
119
120 static struct auth_acct *cmd_need_target_acct(struct client *c)
121 {
122   const char *login = cmd_need_target_login(c);
123   struct auth_zone *az = cmd_need_zone(c);
124
125   struct auth_user *au = auth_find_user(login, 0);
126   struct auth_acct *aa = au ? auth_find_acct(au, az, 0) : NULL;
127   if (aa)
128     return aa;
129
130   if (!az->auto_create_acct)
131     cmd_error(c, "No such account");
132
133   if (!au)
134     {
135       msg(L_INFO, "Automatically creating user: login=<%s>", login);
136       au = auth_find_user(login, 1);
137     }
138   msg(L_INFO, "Automatically creating account: login=<%s> zone=<%s>", login, az->name);
139   return auth_find_acct(au, az, 1);
140 }
141
142 static void cmd_nop(struct client *c)
143 {
144   cmd_ok(c);
145 }
146
147 static void cmd_create_acct(struct client *c)
148 {
149   cmd_require_admin(c);
150
151   const char *login = get_string(c->request, "login");
152   if (!login)
153     cmd_error(c, "Login required");
154
155   struct auth_zone *az = cmd_need_zone(c);
156   struct auth_user *au = auth_find_user(login, 1);
157   if (!auth_find_acct(au, az, 0))
158     {
159       msg(L_INFO, "Creating account: login=<%s> zone=<%s>", login, az->name);
160       auth_find_acct(au, az, 1);
161       db_write();
162     }
163
164   cmd_ok(c);
165 }
166
167 static void cmd_delete_acct(struct client *c)
168 {
169   cmd_require_admin(c);
170
171   const char *login = get_string(c->request, "login");
172   if (!login)
173     cmd_error(c, "Login required");
174   const char *zone_name = get_string(c->request, "zone");
175
176   struct auth_user *au = auth_find_user(login, 0);
177   if (!au)
178     return cmd_ok(c);
179
180   if (zone_name && !strcmp(zone_name, "*"))
181     {
182       msg(L_INFO, "Deleting user: login=<%s>", login);
183       auth_delete_user(au);
184     }
185   else
186     {
187       struct auth_zone *az = cmd_need_zone(c);
188       struct auth_acct *aa = auth_find_acct(au, az, 0);
189       if (!aa)
190         return cmd_ok(c);
191       msg(L_INFO, "Deleting account: login=<%s> zone=<%s>", login, az->name);
192       auth_delete_acct(aa);
193       if (!clist_head(&au->accounts))
194         {
195           msg(L_INFO, "Deleting user with no accounts: login=<%s>", login);
196           auth_delete_user(au);
197         }
198     }
199
200   db_write();
201   cmd_ok(c);
202 }
203
204 static void cmd_set_passwd(struct client *c)
205 {
206   struct auth_acct *aa = cmd_need_target_acct(c);
207   const char *passwd = cmd_need_string(c, "passwd");
208
209   if (!aa->zone->allow_passwd)
210     cmd_error(c, "This zone does not allow authentication by password");
211
212   if (strchr(passwd, '-'))
213     cmd_error(c, "The minus sign is forbidden in passwords");
214
215   msg(L_INFO, "Set password: login=<%s> zone=<%s>", aa->user->login, aa->zone->name);
216
217   struct auth_token *at_old = auth_find_token_passwd(aa);
218   if (at_old)
219     auth_delete_token(at_old);
220
221   struct auth_token *at = auth_create_token(aa);
222   auth_set_token_passwd(at, passwd);
223
224   db_write();
225   cmd_ok(c);
226 }
227
228 static void cmd_create_token(struct client *c)
229 {
230   struct auth_acct *aa = cmd_need_target_acct(c);
231
232   if (!aa->zone->allow_tokens)
233     cmd_error(c, "This zone does not allow authentication by tokens");
234
235   if (clist_size(&aa->tokens) >= aa->zone->allow_tokens)
236     cmd_error(c, "Maximum number of tokens was reached");
237
238   struct auth_token *at = auth_create_token(aa);
239   char *tok = auth_set_token_generated(at, get_string(c->request, "comment"));
240   json_object_set(c->reply, "token", json_new_string(c->json, tok));
241   xfree(tok);
242
243   msg(L_INFO, "Created token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident);
244
245   db_write();
246   cmd_ok(c);
247 }
248
249 static void cmd_login_fake(struct client *c, const char *passwd)
250 {
251   auth_check_token(auth_fake_token, passwd);
252   cmd_error(c, "Invalid password");
253 }
254
255 static void cmd_login(struct client *c)
256 {
257   struct auth_zone *az = cmd_need_zone(c);
258   const char *given_passwd = cmd_need_string(c, "passwd");
259
260   // Split password string to token ident and the password proper
261   char passbuf[strlen(given_passwd) + 1];
262   strcpy(passbuf, given_passwd);
263   char *ident, *passwd;
264   char *sep = strchr(passbuf, '-');
265   if (sep)
266     {
267       *sep = 0;
268       ident = passbuf;
269       passwd = sep + 1;
270     }
271   else
272     {
273       ident = NULL;
274       passwd = passbuf;
275     }
276
277   const char *login = cmd_need_string(c, "login");
278   struct auth_user *au = auth_find_user(login, 0);
279   if (!au)
280     {
281       msg(L_INFO, "Login failed: No user=<%s>", login);
282       return cmd_login_fake(c, passwd);
283     }
284
285   struct auth_acct *aa = auth_find_acct(au, az, 0);
286   if (!aa)
287     {
288       msg(L_INFO, "Login failed: No account user=<%s> zone=<%s>", login, az->name);
289       return cmd_login_fake(c, passwd);
290     }
291
292   struct auth_token *at;
293   if (ident)
294     at = auth_find_token_generated(aa, ident);
295   else
296     at = auth_find_token_passwd(aa);
297   if (!at)
298     {
299       msg(L_INFO, "Login failed: No token user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
300       return cmd_login_fake(c, passwd);
301     }
302
303   if (!auth_check_token(at, passwd))
304     {
305       msg(L_INFO, "Login failed: Wrong password for user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
306       cmd_error(c, "Invalid password");
307     }
308
309   msg(L_INFO, "Login successful: user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
310   cmd_ok(c);
311 }
312
313 static void cmd_list(struct client *c)
314 {
315   const char *login = cmd_need_target_login(c);
316
317   struct auth_user *au = auth_find_user(login, 0);
318   if (!au)
319     cmd_error(c, "No such user");
320
321   struct json_context *js = c->json;
322   json_object_set(c->reply, "login", json_new_string(js, au->login));
323   struct json_node *jas = json_new_array(js);
324   json_object_set(c->reply, "accounts", jas);
325
326   CLIST_FOR_EACH(struct auth_acct *, aa, au->accounts)
327     {
328       struct json_node *ja = json_new_object(js);
329       json_array_append(jas, ja);
330       json_object_set(ja, "zone", json_new_string(js, aa->zone->name));
331
332       struct json_node *jts = json_new_array(js);
333       json_object_set(ja, "tokens", jts);
334
335       CLIST_FOR_EACH(struct auth_token *, at, aa->tokens)
336         {
337           struct json_node *jt = json_new_object(js);
338           json_array_append(jts, jt);
339
340           const char *type;
341           switch (at->type)
342             {
343             case TOKEN_PASSWORD:        type = "passwd"; break;
344             case TOKEN_GENERATED:       type = "token"; break;
345             default:                    type = "unknown"; break;
346             }
347           json_object_set(jt, "type", json_new_string(js, type));
348
349           if (at->ident)
350             json_object_set(jt, "ident", json_new_string(js, at->ident));
351           if (at->comment)
352             json_object_set(jt, "comment", json_new_string(js, at->comment));
353           json_object_set(jt, "lastmod", json_new_number(js, at->last_modified));
354         }
355     }
356
357   cmd_ok(c);
358 }
359
360 struct command {
361   const char *cmd;
362   void (*handler)(struct client *c);
363 };
364
365 static const struct command command_table[] = {
366   { "nop",              cmd_nop },
367   { "create-acct",      cmd_create_acct },
368   { "delete-acct",      cmd_delete_acct },
369   { "create-token",     cmd_create_token },
370   { "set-passwd",       cmd_set_passwd },
371   { "login",            cmd_login },
372   { "list",             cmd_list },
373 };
374
375 void cmd_dispatch(struct client *c)
376 {
377   struct json_node *rq = c->request;
378   const char *cmd;
379
380   if (rq->type != JSON_OBJECT || !(cmd = get_string(rq, "cmd")))
381     {
382       json_object_set(c->reply, "error", json_new_string_ref(c->json, "Malformed request"));
383       return;
384     }
385
386   const struct command *command = NULL;
387   for (uint i=0; i < ARRAY_SIZE(command_table); i++)
388     if (!strcmp(cmd, command_table[i].cmd))
389       {
390         command = &command_table[i];
391         break;
392       }
393   if (!command)
394     {
395       json_object_set(c->reply, "error", json_new_string_ref(c->json, "No such command"));
396       return;
397     }
398
399   TRANS_TRY
400     {
401       if (command)
402         command->handler(c);
403     }
404   TRANS_CATCH(x)
405     {
406     }
407   TRANS_END;
408 }