]> mj.ucw.cz Git - subauth.git/blob - server/cmd.c
Account management using password authentication
[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 uint cmd_need_uint(struct client *c, const char *key)
95 {
96   uint val = 0;
97   if (!get_uint(c->request, key, &val))
98     cmd_error(c, "Missing %s", key);
99   return val;
100 }
101
102 static struct auth_zone *cmd_need_zone(struct client *c)
103 {
104   const char *name = cmd_need_string(c, "zone");
105   struct auth_zone *az = auth_find_zone(name);
106   if (!az)
107     cmd_error(c, "No such zone");
108   return az;
109 }
110
111 static bool cmd_is_admin(struct client *c)
112 {
113   return (c->uid == 0);
114 }
115
116 static void cmd_require_admin(struct client *c)
117 {
118   if (!cmd_is_admin(c))
119     cmd_error(c, "Permission denied");
120 }
121
122 static const char *cmd_need_target_login(struct client *c)
123 {
124   const char *l = get_string(c->request, "login");
125   if (l)
126     {
127       cmd_require_admin(c);
128       return l;
129     }
130   else
131     {
132       struct passwd *pw = getpwuid(c->uid);
133       if (!pw)
134         cmd_error(c, "You do not exist");
135       return json_strdup(c->json, pw->pw_name);
136     }
137 }
138
139 static void cmd_passwd_auth_fake(struct client *c, const char *passwd)
140 {
141   auth_check_token(auth_fake_token, passwd);
142   cmd_error(c, "Invalid password");
143 }
144
145 static struct auth_acct *cmd_need_passwd_auth_acct(struct client *c, const char *passwd)
146 {
147   struct auth_zone *az = cmd_need_zone(c);
148   const char *login = cmd_need_string(c, "login");
149   if (!az->allow_passwd_auth || !az->allow_passwd)
150     {
151       msg(L_INFO, "Password authentication denied by zone configuration: login=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
152       cmd_error(c, "This zone does not allow password authentication for management");
153     }
154
155   struct auth_user *au = auth_find_user(login, 0);
156   if (!au)
157     {
158       msg(L_INFO, "Password authentication failed: No user=<%s> uid=<%u>", login, c->uid);
159       cmd_passwd_auth_fake(c, passwd);
160     }
161
162   struct auth_acct *aa = auth_find_acct(au, az, 0);
163   if (!aa)
164     {
165       msg(L_INFO, "Password authentication failed: No account user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
166       cmd_passwd_auth_fake(c, passwd);
167     }
168   if (!aa->allow_passwd_auth)
169     {
170       msg(L_INFO, "Password authentication failed: Not allowed by user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
171       cmd_passwd_auth_fake(c, passwd);
172     }
173
174   struct auth_token *at = auth_find_token_passwd(aa);
175   if (!at)
176     {
177       msg(L_INFO, "Password authentication failed: No password user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
178       cmd_passwd_auth_fake(c, passwd);
179     }
180
181   if (!auth_check_token(at, passwd))
182     {
183       msg(L_INFO, "Password authentication failed: Wrong password for user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
184       cmd_error(c, "Invalid password");
185     }
186
187   msg(L_INFO, "Password authentication successful: user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
188   return aa;
189 }
190
191 static struct auth_acct *cmd_need_target_acct(struct client *c)
192 {
193   const char *auth_passwd = get_string(c->request, "auth-passwd");
194   if (auth_passwd)
195     return cmd_need_passwd_auth_acct(c, auth_passwd);
196
197   const char *login = cmd_need_target_login(c);
198   struct auth_zone *az = cmd_need_zone(c);
199
200   struct auth_user *au = auth_find_user(login, 0);
201   struct auth_acct *aa = au ? auth_find_acct(au, az, 0) : NULL;
202   if (aa)
203     return aa;
204
205   if (!az->auto_create_acct)
206     cmd_error(c, "No such account");
207
208   if (!au)
209     {
210       msg(L_INFO, "Automatically creating user: login=<%s>", login);
211       au = auth_find_user(login, 1);
212     }
213   msg(L_INFO, "Automatically creating account: login=<%s> zone=<%s>", login, az->name);
214   return auth_find_acct(au, az, 1);
215 }
216
217 static void cmd_nop(struct client *c)
218 {
219   cmd_ok(c);
220 }
221
222 static void cmd_create_acct(struct client *c)
223 {
224   cmd_require_admin(c);
225
226   const char *login = get_string(c->request, "login");
227   if (!login)
228     cmd_error(c, "Login required");
229
230   struct auth_zone *az = cmd_need_zone(c);
231   struct auth_user *au = auth_find_user(login, 1);
232   if (!auth_find_acct(au, az, 0))
233     {
234       msg(L_INFO, "Creating account: login=<%s> zone=<%s>", login, az->name);
235       auth_find_acct(au, az, 1);
236       db_write();
237     }
238
239   cmd_ok(c);
240 }
241
242 static void cmd_delete_acct(struct client *c)
243 {
244   cmd_require_admin(c);
245
246   const char *login = get_string(c->request, "login");
247   if (!login)
248     cmd_error(c, "Login required");
249   const char *zone_name = get_string(c->request, "zone");
250
251   struct auth_user *au = auth_find_user(login, 0);
252   if (!au)
253     return cmd_ok(c);
254
255   if (zone_name && !strcmp(zone_name, "*"))
256     {
257       msg(L_INFO, "Deleting user: login=<%s>", login);
258       auth_delete_user(au);
259     }
260   else
261     {
262       struct auth_zone *az = cmd_need_zone(c);
263       struct auth_acct *aa = auth_find_acct(au, az, 0);
264       if (!aa)
265         return cmd_ok(c);
266       msg(L_INFO, "Deleting account: login=<%s> zone=<%s>", login, az->name);
267       auth_delete_acct(aa);
268       if (!clist_head(&au->accounts))
269         {
270           msg(L_INFO, "Deleting user with no accounts: login=<%s>", login);
271           auth_delete_user(au);
272         }
273     }
274
275   db_write();
276   cmd_ok(c);
277 }
278
279 static void cmd_set_passwd(struct client *c)
280 {
281   struct auth_acct *aa = cmd_need_target_acct(c);
282   const char *passwd = cmd_need_string(c, "passwd");
283
284   if (!aa->zone->allow_passwd)
285     cmd_error(c, "This zone does not allow authentication by password");
286
287   if (strchr(passwd, '-'))
288     cmd_error(c, "The minus sign is forbidden in passwords");
289
290   msg(L_INFO, "Set password: login=<%s> zone=<%s>", aa->user->login, aa->zone->name);
291
292   struct auth_token *at_old = auth_find_token_passwd(aa);
293   if (at_old)
294     auth_delete_token(at_old);
295
296   struct auth_token *at = auth_create_token(aa);
297   auth_set_token_passwd(at, passwd);
298
299   db_write();
300   cmd_ok(c);
301 }
302
303 static void cmd_delete_passwd(struct client *c)
304 {
305   struct auth_acct *aa = cmd_need_target_acct(c);
306   struct auth_token *at = auth_find_token_passwd(aa);
307   if (!at)
308     cmd_error(c, "No password set");
309
310   msg(L_INFO, "Deleted password: login=<%s> zone=<%s>", aa->user->login, aa->zone->name);
311   auth_delete_token(at);
312
313   db_write();
314   cmd_ok(c);
315 }
316
317 static void cmd_create_token(struct client *c)
318 {
319   struct auth_acct *aa = cmd_need_target_acct(c);
320
321   if (!aa->zone->allow_tokens)
322     cmd_error(c, "This zone does not allow authentication by tokens");
323
324   if (clist_size(&aa->tokens) >= aa->zone->allow_tokens)
325     cmd_error(c, "Maximum number of tokens was reached");
326
327   const char *comment = get_string(c->request, "comment");
328   if (comment && strlen(comment) > max_comment_size)
329     cmd_error(c, "Comment too long");
330
331   struct auth_token *at = auth_create_token(aa);
332   char *tok = auth_set_token_generated(at, comment, c->pool);
333   set_string(c, c->reply, "token", tok);
334   set_string(c, c->reply, "ident", at->ident);
335
336   msg(L_INFO, "Created token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident);
337
338   db_write();
339   cmd_ok(c);
340 }
341
342 static void cmd_delete_token(struct client *c)
343 {
344   struct auth_acct *aa = cmd_need_target_acct(c);
345   const char *ident = cmd_need_string(c, "ident");
346   bool all = !strcmp(ident, "*");
347   bool matched = 0;
348
349   struct auth_token *tmp;
350   CLIST_FOR_EACH_DELSAFE(struct auth_token *, at, aa->tokens, tmp)
351     if (at->type == TOKEN_GENERATED && (all || !strcmp(at->ident, ident)))
352       {
353         matched = 1;
354         msg(L_INFO, "Deleted token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident);
355         auth_delete_token(at);
356       }
357
358   if (!all && !matched)
359     cmd_error(c, "No such token");
360
361   db_write();
362   cmd_ok(c);
363 }
364
365 static void cmd_change_token(struct client *c)
366 {
367   struct auth_acct *aa = cmd_need_target_acct(c);
368   const char *ident = cmd_need_string(c, "ident");
369   struct auth_token *at = auth_find_token_generated(aa, ident);
370   if (!at)
371     cmd_error(c, "No such token");
372
373   const char *comment = get_string(c->request, "comment");
374   if (comment && !strcmp(comment, ""))
375     comment = NULL;
376   auth_change_token_comment(at, comment);
377
378   msg(L_INFO, "Changed token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident);
379
380   db_write();
381   cmd_ok(c);
382 }
383
384 static void cmd_create_temp(struct client *c)
385 {
386   struct auth_acct *aa = cmd_need_target_acct(c);
387
388   uint validity;
389   if (!get_uint(c->request, "validity", &validity))
390     cmd_error(c, "Validity must be given");
391
392   if (!aa->zone->max_temp_validity)
393     cmd_error(c, "This zone does not allow temporary tokens");
394
395   if (validity > aa->zone->max_temp_validity)
396     cmd_error(c, "This zone limits temporary token validity to %d seconds", aa->zone->max_temp_validity);
397
398   char *tok = temp_generate(aa->zone->name, aa->user->login, validity, c->pool);
399   set_string(c, c->reply, "token", tok);
400
401   const char *shortened = temp_shorten(tok, c->pool);
402
403   msg(L_INFO, "Created temp token: login=<%s> zone=<%s> temp=<%s> validity=%u", aa->user->login, aa->zone->name, shortened, validity);
404
405   cmd_ok(c);
406 }
407
408 static void cmd_login_fake(struct client *c, const char *passwd)
409 {
410   auth_check_token(auth_fake_token, passwd);
411   cmd_error(c, "Invalid password");
412 }
413
414 static void cmd_login_by_temp(struct client *c, struct auth_zone *az, const char *given_passwd)
415 {
416   const char *login = cmd_need_string(c, "login");
417   const char *shortened = temp_shorten(given_passwd, c->pool);
418
419   const char *reason = temp_check(az->name, login, given_passwd, c->pool);
420   if (reason)
421     {
422       msg(L_INFO, "Login failed: %s user=<%s> zone=<%s> temp=<%s>", reason, login, az->name, shortened);
423       goto reject;
424     }
425
426   /*
427    * The following checks test for improbable things like user
428    * disappearing since the token has been issued.
429    */
430
431   if (!az->max_temp_validity)
432     {
433       msg(L_INFO, "Login failed: Temporary tokens no longer accepted for zone=<%s>", az->name);
434       goto reject;
435     }
436
437   struct auth_user *au = auth_find_user(login, 0);
438   if (!au)
439     {
440       msg(L_INFO, "Login failed: No user=<%s> temp=<%s>", login, shortened);
441       goto reject;
442     }
443
444   struct auth_acct *aa = auth_find_acct(au, az, 0);
445   if (!aa)
446     {
447       msg(L_INFO, "Login failed: No account user=<%s> zone=<%s> temp=<%s>", login, az->name, shortened);
448       goto reject;
449     }
450
451   msg(L_INFO, "Login successful: user=<%s> zone=<%s> temp=<%s>", login, az->name, shortened);
452   cmd_ok(c);
453   return;
454
455 reject:
456   cmd_error(c, "Temporary token refused");
457 }
458
459 static void cmd_login(struct client *c)
460 {
461   struct auth_zone *az = cmd_need_zone(c);
462   const char *given_passwd = cmd_need_string(c, "passwd");
463
464   // Split password string to token ident and the password proper
465   char passbuf[strlen(given_passwd) + 1];
466   strcpy(passbuf, given_passwd);
467   char *ident, *passwd;
468   char *sep = strchr(passbuf, '-');
469   if (sep)
470     {
471       *sep = 0;
472       ident = passbuf;
473       passwd = sep + 1;
474     }
475   else
476     {
477       ident = NULL;
478       passwd = passbuf;
479     }
480
481   if (ident && !strcmp(ident, "t"))
482     return cmd_login_by_temp(c, az, given_passwd);
483
484   const char *login = cmd_need_string(c, "login");
485   struct auth_user *au = auth_find_user(login, 0);
486   if (!au)
487     {
488       msg(L_INFO, "Login failed: No user=<%s>", login);
489       return cmd_login_fake(c, passwd);
490     }
491
492   struct auth_acct *aa = auth_find_acct(au, az, 0);
493   if (!aa)
494     {
495       msg(L_INFO, "Login failed: No account user=<%s> zone=<%s>", login, az->name);
496       return cmd_login_fake(c, passwd);
497     }
498
499   struct auth_token *at;
500   if (ident)
501     at = auth_find_token_generated(aa, ident);
502   else
503     at = auth_find_token_passwd(aa);
504   if (!at)
505     {
506       msg(L_INFO, "Login failed: No token user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
507       return cmd_login_fake(c, passwd);
508     }
509
510   if (!auth_check_token(at, passwd))
511     {
512       msg(L_INFO, "Login failed: Wrong password for user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
513       cmd_error(c, "Invalid password");
514     }
515
516   msg(L_INFO, "Login successful: user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
517   cmd_ok(c);
518 }
519
520 static void cmd_allow_passwd_auth(struct client *c)
521 {
522   struct auth_acct *aa = cmd_need_target_acct(c);
523   uint allow = cmd_need_uint(c, "allow");
524   if (!aa->zone->allow_passwd_auth)
525     cmd_error(c, "This zone does not allow password authentication for management");
526
527   msg(L_INFO, "Setting account password authentication: allow-passwd-auth=<%u> login=<%s> zone=<%s>", !!allow, aa->user->login, aa->zone->name);
528
529   aa->allow_passwd_auth = !!allow;
530
531   db_write();
532   cmd_ok(c);
533 }
534
535 static void cmd_list_accts(struct client *c)
536 {
537   const char *login = cmd_need_target_login(c);
538
539   struct json_context *js = c->json;
540   set_string(c, c->reply, "login", login);
541   struct json_node *jas = json_new_array(js);
542   json_object_set(c->reply, "accounts", jas);
543
544   struct auth_user *au = auth_find_user(login, 0);
545   if (!au)
546     {
547       cmd_ok(c);
548       return;
549     }
550
551   CLIST_FOR_EACH(struct auth_acct *, aa, au->accounts)
552     {
553       struct json_node *ja = json_new_object(js);
554       json_array_append(jas, ja);
555       set_string(c, ja, "zone", aa->zone->name);
556       set_uint(c, ja, "allow-passwd-auth", aa->zone->allow_passwd_auth && aa->allow_passwd_auth);
557
558       struct json_node *jts = json_new_array(js);
559       json_object_set(ja, "tokens", jts);
560
561       CLIST_FOR_EACH(struct auth_token *, at, aa->tokens)
562         {
563           struct json_node *jt = json_new_object(js);
564           json_array_append(jts, jt);
565
566           const char *type;
567           switch (at->type)
568             {
569             case TOKEN_PASSWORD:        type = "passwd"; break;
570             case TOKEN_GENERATED:       type = "token"; break;
571             default:                    type = "unknown"; break;
572             }
573           set_string(c, jt, "type", type);
574
575           set_string(c, jt, "ident", at->ident);
576           set_string(c, jt, "comment", at->comment);
577           set_uint(c, jt, "lastmod", at->last_modified);
578         }
579     }
580
581   cmd_ok(c);
582 }
583
584 static void cmd_list_zones(struct client *c)
585 {
586   struct json_context *js = c->json;
587
588   struct json_node *jzs = json_new_array(js);
589   json_object_set(c->reply, "zones", jzs);
590
591   CLIST_FOR_EACH(struct auth_zone *, az, zone_list)
592     {
593       struct json_node *jz = json_new_object(js);
594       json_array_append(jzs, jz);
595       set_string(c, jz, "name", az->name);
596       set_string(c, jz, "desc", az->desc);
597       set_uint(c, jz, "allow-passwd", az->allow_passwd);
598       set_uint(c, jz, "allow-tokens", az->allow_tokens);
599       set_uint(c, jz, "allow-passwd-auth", az->allow_passwd_auth);
600       set_uint(c, jz, "max-temp-validity", az->max_temp_validity);
601     }
602
603   cmd_ok(c);
604 }
605
606 struct command {
607   const char *cmd;
608   void (*handler)(struct client *c);
609 };
610
611 static const struct command command_table[] = {
612   { "nop",                      cmd_nop },
613   { "create-acct",              cmd_create_acct },
614   { "delete-acct",              cmd_delete_acct },
615   { "create-token",             cmd_create_token },
616   { "delete-token",             cmd_delete_token },
617   { "change-token",             cmd_change_token },
618   { "set-passwd",               cmd_set_passwd },
619   { "delete-passwd",            cmd_delete_passwd },
620   { "create-temp",              cmd_create_temp },
621   { "login",                    cmd_login },
622   { "allow-passwd-auth",        cmd_allow_passwd_auth },
623   { "list-accts",               cmd_list_accts },
624   { "list-zones",               cmd_list_zones },
625 };
626
627 void cmd_dispatch(struct client *c)
628 {
629   struct json_node *rq = c->request;
630   const char *cmd;
631
632   if (rq->type != JSON_OBJECT || !(cmd = get_string(rq, "cmd")))
633     {
634       set_string(c, c->reply, "error", "Malformed request");
635       return;
636     }
637
638   const struct command *command = NULL;
639   for (uint i=0; i < ARRAY_SIZE(command_table); i++)
640     if (!strcmp(cmd, command_table[i].cmd))
641       {
642         command = &command_table[i];
643         break;
644       }
645   if (!command)
646     {
647       set_string(c, c->reply, "error", "No such command");
648       return;
649     }
650
651   TRANS_TRY
652     {
653       if (command)
654         command->handler(c);
655     }
656   TRANS_CATCH(x)
657     {
658     }
659   TRANS_END;
660 }