]> mj.ucw.cz Git - subauth.git/blob - server/cmd.c
Server: create_token returns the ident of the new token
[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 set_string(struct client *c, struct json_node *n, const char *key, const char *val)
17 {
18   if (val)
19     json_object_set(n, key, json_new_string(c->json, val));
20 }
21
22 static void set_uint(struct client *c, struct json_node *n, const char *key, uint val)
23 {
24   json_object_set(n, key, json_new_number(c->json, val));
25 }
26
27 static void NONRET cmd_error(struct client *c, const char *err, ...)
28 {
29   va_list args;
30   char msg[1024];
31   va_start(args, err);
32   vsnprintf(msg, sizeof(msg), err, args);
33   set_string(c, c->reply, "error", msg);
34   trans_throw("adhoc", NULL, "Error caught");
35   va_end(args);
36 }
37
38 static void cmd_ok(struct client *c)
39 {
40   set_string(c, c->reply, "error", "");
41 }
42
43 const char *get_string(struct json_node *n, const char *key)
44 {
45   struct json_node *s = json_object_get(n, key);
46   if (s && s->type == JSON_STRING)
47     return s->string;
48   else
49     return NULL;
50 }
51
52 bool get_uint(struct json_node *n, const char *key, uint *dest)
53 {
54   struct json_node *s = json_object_get(n, key);
55   if (s && s->type == JSON_NUMBER)
56     {
57       uint u = (uint) s->number;
58       if ((double) u == s->number)
59         {
60           *dest = u;
61           return 1;
62         }
63     }
64   *dest = 0;
65   return 0;
66 }
67
68 struct json_node **get_array(struct json_node *n, const char *key)
69 {
70   struct json_node *s = json_object_get(n, key);
71   if (s && s->type == JSON_ARRAY)
72     return s->elements;
73   else
74     return NULL;
75 }
76
77 struct json_node *get_object(struct json_node *n, const char *key)
78 {
79   struct json_node *s = json_object_get(n, key);
80   if (s && s->type == JSON_OBJECT)
81     return s;
82   else
83     return NULL;
84 }
85
86 static const char *cmd_need_string(struct client *c, const char *key)
87 {
88   const char *val = get_string(c->request, key);
89   if (!val)
90     cmd_error(c, "Missing %s", key);
91   return val;
92 }
93
94 static struct auth_zone *cmd_need_zone(struct client *c)
95 {
96   const char *name = cmd_need_string(c, "zone");
97   struct auth_zone *az = auth_find_zone(name);
98   if (!az)
99     cmd_error(c, "No such zone");
100   return az;
101 }
102
103 static bool cmd_is_admin(struct client *c)
104 {
105   return (c->uid == 0);
106 }
107
108 static void cmd_require_admin(struct client *c)
109 {
110   if (!cmd_is_admin(c))
111     cmd_error(c, "Permission denied");
112 }
113
114 static const char *cmd_need_target_login(struct client *c)
115 {
116   const char *l = get_string(c->request, "login");
117   if (l)
118     {
119       cmd_require_admin(c);
120       return l;
121     }
122   else
123     {
124       struct passwd *pw = getpwuid(c->uid);
125       if (!pw)
126         cmd_error(c, "You do not exist");
127       return json_strdup(c->json, pw->pw_name);
128     }
129 }
130
131 static struct auth_acct *cmd_need_target_acct(struct client *c)
132 {
133   const char *login = cmd_need_target_login(c);
134   struct auth_zone *az = cmd_need_zone(c);
135
136   struct auth_user *au = auth_find_user(login, 0);
137   struct auth_acct *aa = au ? auth_find_acct(au, az, 0) : NULL;
138   if (aa)
139     return aa;
140
141   if (!az->auto_create_acct)
142     cmd_error(c, "No such account");
143
144   if (!au)
145     {
146       msg(L_INFO, "Automatically creating user: login=<%s>", login);
147       au = auth_find_user(login, 1);
148     }
149   msg(L_INFO, "Automatically creating account: login=<%s> zone=<%s>", login, az->name);
150   return auth_find_acct(au, az, 1);
151 }
152
153 static void cmd_nop(struct client *c)
154 {
155   cmd_ok(c);
156 }
157
158 static void cmd_create_acct(struct client *c)
159 {
160   cmd_require_admin(c);
161
162   const char *login = get_string(c->request, "login");
163   if (!login)
164     cmd_error(c, "Login required");
165
166   struct auth_zone *az = cmd_need_zone(c);
167   struct auth_user *au = auth_find_user(login, 1);
168   if (!auth_find_acct(au, az, 0))
169     {
170       msg(L_INFO, "Creating account: login=<%s> zone=<%s>", login, az->name);
171       auth_find_acct(au, az, 1);
172       db_write();
173     }
174
175   cmd_ok(c);
176 }
177
178 static void cmd_delete_acct(struct client *c)
179 {
180   cmd_require_admin(c);
181
182   const char *login = get_string(c->request, "login");
183   if (!login)
184     cmd_error(c, "Login required");
185   const char *zone_name = get_string(c->request, "zone");
186
187   struct auth_user *au = auth_find_user(login, 0);
188   if (!au)
189     return cmd_ok(c);
190
191   if (zone_name && !strcmp(zone_name, "*"))
192     {
193       msg(L_INFO, "Deleting user: login=<%s>", login);
194       auth_delete_user(au);
195     }
196   else
197     {
198       struct auth_zone *az = cmd_need_zone(c);
199       struct auth_acct *aa = auth_find_acct(au, az, 0);
200       if (!aa)
201         return cmd_ok(c);
202       msg(L_INFO, "Deleting account: login=<%s> zone=<%s>", login, az->name);
203       auth_delete_acct(aa);
204       if (!clist_head(&au->accounts))
205         {
206           msg(L_INFO, "Deleting user with no accounts: login=<%s>", login);
207           auth_delete_user(au);
208         }
209     }
210
211   db_write();
212   cmd_ok(c);
213 }
214
215 static void cmd_set_passwd(struct client *c)
216 {
217   struct auth_acct *aa = cmd_need_target_acct(c);
218   const char *passwd = cmd_need_string(c, "passwd");
219
220   if (!aa->zone->allow_passwd)
221     cmd_error(c, "This zone does not allow authentication by password");
222
223   if (strchr(passwd, '-'))
224     cmd_error(c, "The minus sign is forbidden in passwords");
225
226   msg(L_INFO, "Set password: login=<%s> zone=<%s>", aa->user->login, aa->zone->name);
227
228   struct auth_token *at_old = auth_find_token_passwd(aa);
229   if (at_old)
230     auth_delete_token(at_old);
231
232   struct auth_token *at = auth_create_token(aa);
233   auth_set_token_passwd(at, passwd);
234
235   db_write();
236   cmd_ok(c);
237 }
238
239 static void cmd_delete_passwd(struct client *c)
240 {
241   struct auth_acct *aa = cmd_need_target_acct(c);
242   struct auth_token *at = auth_find_token_passwd(aa);
243   if (!at)
244     cmd_error(c, "No password set");
245
246   msg(L_INFO, "Deleted password: login=<%s> zone=<%s>", aa->user->login, aa->zone->name);
247   auth_delete_token(at);
248
249   db_write();
250   cmd_ok(c);
251 }
252
253 static void cmd_create_token(struct client *c)
254 {
255   struct auth_acct *aa = cmd_need_target_acct(c);
256
257   if (!aa->zone->allow_tokens)
258     cmd_error(c, "This zone does not allow authentication by tokens");
259
260   if (clist_size(&aa->tokens) >= aa->zone->allow_tokens)
261     cmd_error(c, "Maximum number of tokens was reached");
262
263   const char *comment = get_string(c->request, "comment");
264   if (comment && strlen(comment) > max_comment_size)
265     cmd_error(c, "Comment too long");
266
267   struct auth_token *at = auth_create_token(aa);
268   char *tok = auth_set_token_generated(at, comment, c->pool);
269   set_string(c, c->reply, "token", tok);
270   set_string(c, c->reply, "ident", at->ident);
271
272   msg(L_INFO, "Created token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident);
273
274   db_write();
275   cmd_ok(c);
276 }
277
278 static void cmd_delete_token(struct client *c)
279 {
280   struct auth_acct *aa = cmd_need_target_acct(c);
281   const char *ident = cmd_need_string(c, "ident");
282   bool all = !strcmp(ident, "*");
283   bool matched = 0;
284
285   struct auth_token *tmp;
286   CLIST_FOR_EACH_DELSAFE(struct auth_token *, at, aa->tokens, tmp)
287     if (at->type == TOKEN_GENERATED && (all || !strcmp(at->ident, ident)))
288       {
289         matched = 1;
290         msg(L_INFO, "Deleted token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident);
291         auth_delete_token(at);
292       }
293
294   if (!all && !matched)
295     cmd_error(c, "No such token");
296
297   db_write();
298   cmd_ok(c);
299 }
300
301 static void cmd_change_token(struct client *c)
302 {
303   struct auth_acct *aa = cmd_need_target_acct(c);
304   const char *ident = cmd_need_string(c, "ident");
305   struct auth_token *at = auth_find_token_generated(aa, ident);
306   if (!at)
307     cmd_error(c, "No such token");
308
309   const char *comment = get_string(c->request, "comment");
310   if (comment && !strcmp(comment, ""))
311     comment = NULL;
312   auth_change_token_comment(at, comment);
313
314   msg(L_INFO, "Changed token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident);
315
316   db_write();
317   cmd_ok(c);
318 }
319
320 static void cmd_create_temp(struct client *c)
321 {
322   struct auth_acct *aa = cmd_need_target_acct(c);
323
324   uint validity;
325   if (!get_uint(c->request, "validity", &validity))
326     cmd_error(c, "Validity must be given");
327
328   if (!aa->zone->max_temp_validity)
329     cmd_error(c, "This zone does not allow temporary tokens");
330
331   if (validity > aa->zone->max_temp_validity)
332     cmd_error(c, "This zone limits temporary token validity to %d seconds", aa->zone->max_temp_validity);
333
334   char *tok = temp_generate(aa->zone->name, aa->user->login, validity, c->pool);
335   set_string(c, c->reply, "token", tok);
336
337   const char *shortened = temp_shorten(tok, c->pool);
338
339   msg(L_INFO, "Created temp token: login=<%s> zone=<%s> temp=<%s> validity=%u", aa->user->login, aa->zone->name, shortened, validity);
340
341   cmd_ok(c);
342 }
343
344 static void cmd_login_fake(struct client *c, const char *passwd)
345 {
346   auth_check_token(auth_fake_token, passwd);
347   cmd_error(c, "Invalid password");
348 }
349
350 static void cmd_login_by_temp(struct client *c, struct auth_zone *az, const char *given_passwd)
351 {
352   const char *login = cmd_need_string(c, "login");
353   const char *shortened = temp_shorten(given_passwd, c->pool);
354
355   const char *reason = temp_check(az->name, login, given_passwd, c->pool);
356   if (reason)
357     {
358       msg(L_INFO, "Login failed: %s user=<%s> zone=<%s> temp=<%s>", reason, login, az->name, shortened);
359       goto reject;
360     }
361
362   /*
363    * The following checks test for improbable things like user
364    * disappearing since the token has been issued.
365    */
366
367   if (!az->max_temp_validity)
368     {
369       msg(L_INFO, "Login failed: Temporary tokens no longer accepted for zone=<%s>", az->name);
370       goto reject;
371     }
372
373   struct auth_user *au = auth_find_user(login, 0);
374   if (!au)
375     {
376       msg(L_INFO, "Login failed: No user=<%s> temp=<%s>", login, shortened);
377       goto reject;
378     }
379
380   struct auth_acct *aa = auth_find_acct(au, az, 0);
381   if (!aa)
382     {
383       msg(L_INFO, "Login failed: No account user=<%s> zone=<%s> temp=<%s>", login, az->name, shortened);
384       goto reject;
385     }
386
387   msg(L_INFO, "Login successful: user=<%s> zone=<%s> temp=<%s>", login, az->name, shortened);
388   cmd_ok(c);
389   return;
390
391 reject:
392   cmd_error(c, "Temporary token refused");
393 }
394
395 static void cmd_login(struct client *c)
396 {
397   struct auth_zone *az = cmd_need_zone(c);
398   const char *given_passwd = cmd_need_string(c, "passwd");
399
400   // Split password string to token ident and the password proper
401   char passbuf[strlen(given_passwd) + 1];
402   strcpy(passbuf, given_passwd);
403   char *ident, *passwd;
404   char *sep = strchr(passbuf, '-');
405   if (sep)
406     {
407       *sep = 0;
408       ident = passbuf;
409       passwd = sep + 1;
410     }
411   else
412     {
413       ident = NULL;
414       passwd = passbuf;
415     }
416
417   if (ident && !strcmp(ident, "t"))
418     return cmd_login_by_temp(c, az, given_passwd);
419
420   const char *login = cmd_need_string(c, "login");
421   struct auth_user *au = auth_find_user(login, 0);
422   if (!au)
423     {
424       msg(L_INFO, "Login failed: No user=<%s>", login);
425       return cmd_login_fake(c, passwd);
426     }
427
428   struct auth_acct *aa = auth_find_acct(au, az, 0);
429   if (!aa)
430     {
431       msg(L_INFO, "Login failed: No account user=<%s> zone=<%s>", login, az->name);
432       return cmd_login_fake(c, passwd);
433     }
434
435   struct auth_token *at;
436   if (ident)
437     at = auth_find_token_generated(aa, ident);
438   else
439     at = auth_find_token_passwd(aa);
440   if (!at)
441     {
442       msg(L_INFO, "Login failed: No token user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
443       return cmd_login_fake(c, passwd);
444     }
445
446   if (!auth_check_token(at, passwd))
447     {
448       msg(L_INFO, "Login failed: Wrong password for user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
449       cmd_error(c, "Invalid password");
450     }
451
452   msg(L_INFO, "Login successful: user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
453   cmd_ok(c);
454 }
455
456 static void cmd_list_accts(struct client *c)
457 {
458   const char *login = cmd_need_target_login(c);
459
460   struct json_context *js = c->json;
461   set_string(c, c->reply, "login", login);
462   struct json_node *jas = json_new_array(js);
463   json_object_set(c->reply, "accounts", jas);
464
465   struct auth_user *au = auth_find_user(login, 0);
466   if (!au)
467     {
468       cmd_ok(c);
469       return;
470     }
471
472   CLIST_FOR_EACH(struct auth_acct *, aa, au->accounts)
473     {
474       struct json_node *ja = json_new_object(js);
475       json_array_append(jas, ja);
476       set_string(c, ja, "zone", aa->zone->name);
477
478       struct json_node *jts = json_new_array(js);
479       json_object_set(ja, "tokens", jts);
480
481       CLIST_FOR_EACH(struct auth_token *, at, aa->tokens)
482         {
483           struct json_node *jt = json_new_object(js);
484           json_array_append(jts, jt);
485
486           const char *type;
487           switch (at->type)
488             {
489             case TOKEN_PASSWORD:        type = "passwd"; break;
490             case TOKEN_GENERATED:       type = "token"; break;
491             default:                    type = "unknown"; break;
492             }
493           set_string(c, jt, "type", type);
494
495           set_string(c, jt, "ident", at->ident);
496           set_string(c, jt, "comment", at->comment);
497           set_uint(c, jt, "lastmod", at->last_modified);
498         }
499     }
500
501   cmd_ok(c);
502 }
503
504 static void cmd_list_zones(struct client *c)
505 {
506   struct json_context *js = c->json;
507
508   struct json_node *jzs = json_new_array(js);
509   json_object_set(c->reply, "zones", jzs);
510
511   CLIST_FOR_EACH(struct auth_zone *, az, zone_list)
512     {
513       struct json_node *jz = json_new_object(js);
514       json_array_append(jzs, jz);
515       set_string(c, jz, "name", az->name);
516       set_string(c, jz, "desc", az->desc);
517       set_uint(c, jz, "allow-passwd", az->allow_passwd);
518       set_uint(c, jz, "allow-tokens", az->allow_tokens);
519       set_uint(c, jz, "max-temp-validity", az->max_temp_validity);
520     }
521
522   cmd_ok(c);
523 }
524
525 struct command {
526   const char *cmd;
527   void (*handler)(struct client *c);
528 };
529
530 static const struct command command_table[] = {
531   { "nop",              cmd_nop },
532   { "create-acct",      cmd_create_acct },
533   { "delete-acct",      cmd_delete_acct },
534   { "create-token",     cmd_create_token },
535   { "delete-token",     cmd_delete_token },
536   { "change-token",     cmd_change_token },
537   { "set-passwd",       cmd_set_passwd },
538   { "delete-passwd",    cmd_delete_passwd },
539   { "create-temp",      cmd_create_temp },
540   { "login",            cmd_login },
541   { "list-accts",       cmd_list_accts },
542   { "list-zones",       cmd_list_zones },
543 };
544
545 void cmd_dispatch(struct client *c)
546 {
547   struct json_node *rq = c->request;
548   const char *cmd;
549
550   if (rq->type != JSON_OBJECT || !(cmd = get_string(rq, "cmd")))
551     {
552       set_string(c, c->reply, "error", "Malformed request");
553       return;
554     }
555
556   const struct command *command = NULL;
557   for (uint i=0; i < ARRAY_SIZE(command_table); i++)
558     if (!strcmp(cmd, command_table[i].cmd))
559       {
560         command = &command_table[i];
561         break;
562       }
563   if (!command)
564     {
565       set_string(c, c->reply, "error", "No such command");
566       return;
567     }
568
569   TRANS_TRY
570     {
571       if (command)
572         command->handler(c);
573     }
574   TRANS_CATCH(x)
575     {
576     }
577   TRANS_END;
578 }