]> mj.ucw.cz Git - subauth.git/blob - client/subauth.c
Debian: Re-packaged for Bookworm
[subauth.git] / client / subauth.c
1 /*
2  *      Sub-authentication Client
3  *
4  *      (c) 2017 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/log.h>
9 #include <ucw/opt.h>
10 #include <ucw/strtonum.h>
11 #include <ucw-json/json.h>
12
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <pwd.h>
16 #include <sys/socket.h>
17 #include <sys/un.h>
18 #include <time.h>
19 #include <unistd.h>
20
21 #include "autoconf.h"
22
23 /*** Arguments ***/
24
25 static char *socket_path = INSTALL_RUN_DIR "/subauthd/subauthd.socket";
26
27 static char *arg_user;
28 static char *arg_zone;
29 static char *arg_ident;
30 static char *arg_comment;
31 static int debug;
32 static char *arg_expire;
33 static int arg_auth_passwd;
34
35 /*** Communication with the daemon ***/
36
37 static struct json_context *js;
38 static struct json_node *rq;
39 static struct json_node *rp;
40
41 static void set_string(struct json_node *n, const char *key, const char *val)
42 {
43   if (val)
44     json_object_set(n, key, json_new_string(js, val));
45 }
46
47 static void set_uint(struct json_node *n, const char *key, uint val)
48 {
49   if (val)
50     json_object_set(n, key, json_new_number(js, val));
51 }
52
53 static struct json_node *get_child(struct json_node *obj, const char *key, uint type)
54 {
55   struct json_node *n = json_object_get(obj, key);
56   if (n && n->type != type)
57     die("Malformed reply: %s has wrong type", key);
58   return n;
59 }
60
61 static struct json_node *need_child(struct json_node *obj, const char *key, uint type)
62 {
63   struct json_node *n = get_child(obj, key, type);
64   if (!n)
65     die("Malformed reply: missing %s", key);
66   return n;
67 }
68
69 static void op_new(const char *op)
70 {
71   js = json_new();
72   rq = json_new_object(js);
73   set_string(rq, "cmd", op);
74   if (arg_user)
75     set_string(rq, "login", arg_user);
76   if (arg_auth_passwd)
77     {
78       char *auth_passwd = xstrdup(getpass("Current password: "));
79       set_string(rq, "auth-passwd", auth_passwd);
80     }
81 }
82
83 static void op_debug(const char *msg, struct json_node *n)
84 {
85   if (!debug)
86     return;
87
88   struct fastbuf *out = bfdopen_shared(1, 4096);
89   bprintf(out, ">>> %s\n", msg);
90   js->format_options = JSON_FORMAT_INDENT;
91   json_write(js, out, n);
92   js->format_options = 0;
93   bclose(out);
94 }
95
96 static void op_run(void)
97 {
98   int sk = socket(PF_UNIX, SOCK_SEQPACKET, 0);
99   if (sk < 0)
100     die("socket(PF_UNIX, SOCK_SEQPACKET): %m");
101
102   struct sockaddr_un sun;
103   sun.sun_family = AF_UNIX;
104   if (strlen(socket_path) >= sizeof(sun.sun_path))
105     die("Socket path too long");
106   strcpy(sun.sun_path, socket_path);
107
108   if (connect(sk, (struct sockaddr *) &sun, sizeof(sun)) < 0)
109     die("Cannot connect to %s: %m", socket_path);
110
111   op_debug("Request", rq);
112
113   struct fastbuf *rq_fb = fbgrow_create(4096);
114   json_write(js, rq_fb, rq);
115   byte *rq_buf;
116   uint rq_len = fbgrow_get_buf(rq_fb, &rq_buf);
117   if (send(sk, rq_buf, rq_len, 0) < 0)
118     die("Cannot send request: %m");
119   bclose(rq_fb);
120
121   byte rp_buf[16384];
122   int rp_len = recv(sk, rp_buf, sizeof(rp_buf), 0);
123   if (rp_len < 0)
124     die("Cannot receive reply: %m");
125
126   struct fastbuf rp_fb;
127   fbbuf_init_read(&rp_fb, rp_buf, rp_len, 0);
128   rp = json_parse(js, &rp_fb);
129   op_debug("Reply", rp);
130
131   close(sk);
132
133   if (rp->type != JSON_OBJECT)
134     die("Malformed reply: Top-level node is not an object");
135   struct json_node *err = need_child(rp, "error", JSON_STRING);
136   if (strcmp(err->string, ""))
137     die("Error: %s", err->string);
138 }
139
140 /*** Commands ***/
141
142 static void cmd_set_passwd(void)
143 {
144   if (!arg_zone)
145     opt_failure("--zone must be given");
146
147   op_new("set-passwd");
148   char *passwd = xstrdup(getpass("New password: "));
149   char *again = xstrdup(getpass("Confirm password: "));
150   if (strcmp(passwd, again))
151     die("Passwords do not match");
152
153   set_string(rq, "zone", arg_zone);
154   set_string(rq, "passwd", passwd);
155
156   op_run();
157 }
158
159 static void cmd_delete_passwd(void)
160 {
161   if (!arg_zone)
162     opt_failure("--zone must be given");
163
164   op_new("delete-passwd");
165   set_string(rq, "zone", arg_zone);
166
167   op_run();
168 }
169
170 static void cmd_create_token(void)
171 {
172   if (!arg_zone)
173     opt_failure("--zone must be given");
174
175   op_new("create-token");
176   set_string(rq, "zone", arg_zone);
177   set_string(rq, "comment", arg_comment);
178
179   op_run();
180   struct json_node *jt = need_child(rp, "token", JSON_STRING);
181   printf("%s\n", jt->string);
182 }
183
184 static void cmd_delete_token(void)
185 {
186   if (!arg_zone || !arg_ident)
187     opt_failure("--zone and --ident must be given");
188
189   op_new("delete-token");
190   set_string(rq, "zone", arg_zone);
191   set_string(rq, "ident", arg_ident);
192
193   op_run();
194 }
195
196 static void cmd_change_token(void)
197 {
198   if (!arg_zone || !arg_ident)
199     opt_failure("--zone and --ident must be given");
200
201   op_new("change-token");
202   set_string(rq, "zone", arg_zone);
203   set_string(rq, "ident", arg_ident);
204   set_string(rq, "comment", arg_comment);
205
206   op_run();
207 }
208
209 static void cmd_create_acct(void)
210 {
211   if (!arg_zone || !arg_user)
212     opt_failure("--zone and --user must be given");
213
214   op_new("create-acct");
215   set_string(rq, "zone", arg_zone);
216   set_string(rq, "login", arg_user);
217
218   op_run();
219 }
220
221 static void cmd_delete_acct(void)
222 {
223   if (!arg_zone || !arg_user)
224     opt_failure("--zone and --user must be given");
225
226   op_new("delete-acct");
227   set_string(rq, "zone", arg_zone);
228   set_string(rq, "login", arg_user);
229
230   op_run();
231 }
232
233 static void cmd_delete_user(void)
234 {
235   if (!arg_user)
236     opt_failure("--user must be given");
237
238   op_new("delete-acct");
239   set_string(rq, "zone", "*");
240   set_string(rq, "login", arg_user);
241
242   op_run();
243 }
244
245 static void cmd_list(void)
246 {
247   op_new("list-accts");
248   op_run();
249
250   struct json_node **jas = need_child(rp, "accounts", JSON_ARRAY)->elements;
251   if (!GARY_SIZE(jas))
252     {
253       puts("No accounts defined");
254       return;
255     }
256
257   printf("%-16s %-6s %-5s %-19s %-8s  %s\n",
258     "Zone", "Type", "Ident", "Last modified", "PassAuth", "Comment");
259   for (uint i=0; i < GARY_SIZE(jas); i++)
260     {
261       struct json_node *ja = jas[i];
262       struct json_node *jz = need_child(ja, "zone", JSON_STRING);
263       struct json_node **jts = need_child(ja, "tokens", JSON_ARRAY)->elements;
264       struct json_node *jpassauth = get_child(ja, "allow-passwd-auth", JSON_NUMBER);
265
266       if (!GARY_SIZE(jts))
267         {
268           printf("%-16s (no tokens defined%s)\n",
269             jz->string,
270             jpassauth ? (jpassauth->number ? ", password auth allowed" : ", password auth disabled") : "");
271           continue;
272         }
273
274       for (uint j=0; j < GARY_SIZE(jts); j++)
275         {
276           struct json_node *lt = jts[j];
277           struct json_node *jtype = need_child(lt, "type", JSON_STRING);
278           struct json_node *jident = get_child(lt, "ident", JSON_STRING);
279           struct json_node *jcomment = get_child(lt, "comment", JSON_STRING);
280           struct json_node *jlastmod = need_child(lt, "lastmod", JSON_NUMBER);
281           time_t lm = jlastmod->number;
282           struct tm *tm = localtime(&lm);
283           printf("%-16s %-6s %-5s %04d-%02d-%02d %02d:%02d:%02d %8s  %s\n",
284             jz->string,
285             jtype->string,
286             (jident ? jident->string : "-"),
287             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
288             tm->tm_hour, tm->tm_min, tm->tm_sec,
289             (jpassauth && jpassauth->number ? "yes" : "-"),
290             (jcomment ? jcomment->string : ""));
291         }
292     }
293 }
294
295 static void cmd_zones(void)
296 {
297   op_new("list-zones");
298   op_run();
299
300   struct json_node **jzs = need_child(rp, "zones", JSON_ARRAY)->elements;
301   printf("%-16s %6s %6s %6s %8s  %s\n", "Zone", "Passwd", "Tokens", "MaxTmp", "PassAuth", "Description");
302   for (uint i=0; i < GARY_SIZE(jzs); i++)
303     {
304       struct json_node *jz = jzs[i];
305       struct json_node *jname = need_child(jz, "name", JSON_STRING);
306       struct json_node *jdesc = get_child(jz, "desc", JSON_STRING);
307       struct json_node *jpass = get_child(jz, "allow-passwd", JSON_NUMBER);
308       struct json_node *jtokens = get_child(jz, "allow-tokens", JSON_NUMBER);
309       struct json_node *jpassauth = get_child(jz, "allow-passwd-auth", JSON_NUMBER);
310       struct json_node *jtemp = get_child(jz, "max-temp-validity", JSON_NUMBER);
311       printf("%-16s %6s %6d %6d %8s  %s\n",
312         jname->string,
313         (jpass && jpass->number ? "yes" : "-"),
314         (jtokens ? (uint) jtokens->number : 0),
315         (jtemp ? (uint) jtemp->number : 0),
316         (jpassauth && jpassauth->number ? "yes" : "-"),
317         (jdesc ? jdesc->string : "(no description)"));
318     }
319 }
320
321 static void cmd_login(void)
322 {
323   if (!arg_zone)
324     opt_failure("--zone must be given");
325
326   if (!arg_user)
327     {
328       uid_t uid = getuid();
329       struct passwd *pw = getpwuid(uid);
330       if (!pw)
331         die("You do not exist");
332       arg_user = xstrdup(pw->pw_name);
333     }
334
335   char *passwd = xstrdup(getpass("Password: "));
336
337   op_new("login");
338   set_string(rq, "zone", arg_zone);
339   set_string(rq, "passwd", passwd);
340
341   op_run();
342   printf("OK\n");
343 }
344
345 static uint parse_expire(void)
346 {
347   if (!arg_expire)
348     arg_expire = "5m";
349
350   uint expire;
351   const char *next;
352   const char *err = str_to_uint(&expire, arg_expire, &next, 10);
353   if (err || !next[0] || next[1])
354     opt_failure("Invalid syntax of --expire");
355
356   switch (next[0])
357     {
358     case 'h':
359       return expire * 3600;
360     case 'm':
361       return expire * 60;
362     case 's':
363       return expire;
364     default:
365       opt_failure("--expire uses an unknown unit");
366     }
367 }
368
369 static void cmd_temp_token(void)
370 {
371   if (!arg_zone)
372     opt_failure("--zone must be given");
373
374   op_new("create-temp");
375   set_string(rq, "zone", arg_zone);
376   set_uint(rq, "validity", parse_expire());
377
378   op_run();
379   struct json_node *jt = need_child(rp, "token", JSON_STRING);
380   printf("%s\n", jt->string);
381 }
382
383 static void cmd_allow_passwd_auth(void)
384 {
385   if (!arg_zone)
386     opt_failure("--zone must be given");
387
388   op_new("allow-passwd-auth");
389   set_string(rq, "zone", arg_zone);
390   set_uint(rq, "allow", 1);
391
392   op_run();
393 }
394
395 static void cmd_disallow_passwd_auth(void)
396 {
397   if (!arg_zone)
398     opt_failure("--zone must be given");
399
400   op_new("allow-passwd-auth");
401   set_string(rq, "zone", arg_zone);
402   json_object_set(rq, "allow", json_new_number(js, 0));
403
404   op_run();
405 }
406
407 static void cmd_raw(void)
408 {
409   op_new("");
410
411   struct fastbuf *in = bfdopen_shared(0, 4096);
412   rq = json_parse(js, in);
413   bclose(in);
414
415   op_run();
416 }
417
418 enum command {
419   CMD_SET_PASSWD,
420   CMD_DELETE_PASSWD,
421   CMD_CREATE_TOKEN,
422   CMD_DELETE_TOKEN,
423   CMD_CHANGE_TOKEN,
424   CMD_CREATE_ACCT,
425   CMD_DELETE_ACCT,
426   CMD_DELETE_USER,
427   CMD_LIST,
428   CMD_ZONES,
429   CMD_LOGIN,
430   CMD_TEMP_TOKEN,
431   CMD_ALLOW_PASSWD_AUTH,
432   CMD_DISALLOW_PASSWD_AUTH,
433   CMD_RAW,
434   CMD_MAX
435 };
436
437 void (* const command_handlers[CMD_MAX])(void) = {
438   [CMD_SET_PASSWD] = cmd_set_passwd,
439   [CMD_DELETE_PASSWD] = cmd_delete_passwd,
440   [CMD_CREATE_TOKEN] = cmd_create_token,
441   [CMD_DELETE_TOKEN] = cmd_delete_token,
442   [CMD_CHANGE_TOKEN] = cmd_change_token,
443   [CMD_CREATE_ACCT] = cmd_create_acct,
444   [CMD_DELETE_ACCT] = cmd_delete_acct,
445   [CMD_DELETE_USER] = cmd_delete_user,
446   [CMD_LIST] = cmd_list,
447   [CMD_ZONES] = cmd_zones,
448   [CMD_LOGIN] = cmd_login,
449   [CMD_TEMP_TOKEN] = cmd_temp_token,
450   [CMD_ALLOW_PASSWD_AUTH] = cmd_allow_passwd_auth,
451   [CMD_DISALLOW_PASSWD_AUTH] = cmd_disallow_passwd_auth,
452   [CMD_RAW] = cmd_raw,
453 };
454
455 static int command = -1;
456
457 /*** Main ***/
458
459 static const struct opt_section options = {
460   OPT_ITEMS {
461     OPT_HELP("A client to the sub-authentication daemon."),
462     OPT_HELP("Usage: subauth [general options] <command> [options]"),
463     OPT_HELP(""),
464     OPT_HELP("General options:"),
465     OPT_STRING('s', "socket", socket_path, OPT_REQUIRED_VALUE, "path\tPath to daemon control socket"),
466     OPT_BOOL(0, "debug", debug, 0, "\tDump raw communication with the daemon"),
467     OPT_HELP_OPTION,
468     OPT_HELP(""),
469     OPT_HELP("User commands:"),
470     OPT_SWITCH('l', "list",                     command, CMD_LIST,                      OPT_SINGLE, "\tList tokens for all zones"),
471     OPT_SWITCH(0,   "zones",                    command, CMD_ZONES,                     OPT_SINGLE, "\tList all known zones"),
472     OPT_SWITCH(0,   "passwd",                   command, CMD_SET_PASSWD,                OPT_SINGLE, "\tSet password"),
473     OPT_SWITCH(0,   "delete-passwd",            command, CMD_DELETE_PASSWD,             OPT_SINGLE, "\tRemove password"),
474     OPT_SWITCH(0,   "create-token",             command, CMD_CREATE_TOKEN,              OPT_SINGLE, "\tCreate a new token"),
475     OPT_SWITCH(0,   "delete-token",             command, CMD_DELETE_TOKEN,              OPT_SINGLE, "\tRemove an existing token"),
476     OPT_SWITCH(0,   "change-token",             command, CMD_CHANGE_TOKEN,              OPT_SINGLE, "\tChange the comment of an existing token"),
477     OPT_SWITCH(0,   "login",                    command, CMD_LOGIN,                     OPT_SINGLE, "\tTry to log in"),
478     OPT_SWITCH(0,   "temp-token",               command, CMD_TEMP_TOKEN,                OPT_SINGLE, "\tCreate a temporary token"),
479     OPT_SWITCH(0,   "allow-passwd-auth",        command, CMD_ALLOW_PASSWD_AUTH,         OPT_SINGLE, "\tAllow management using password authentication"),
480     OPT_SWITCH(0,   "disallow-passwd-auth",     command, CMD_DISALLOW_PASSWD_AUTH,      OPT_SINGLE, "\tDisallow account management"),
481     OPT_HELP(""),
482     OPT_HELP("Administrative commands:"),
483     OPT_SWITCH(0,   "create-acct",      command, CMD_CREATE_ACCT,       OPT_SINGLE, "\tCreate an account"),
484     OPT_SWITCH(0,   "delete-acct",      command, CMD_DELETE_ACCT,       OPT_SINGLE, "\tDelete an account"),
485     OPT_SWITCH(0,   "delete-user",      command, CMD_DELETE_USER,       OPT_SINGLE, "\tDelete a user with all his accounts"),
486     OPT_SWITCH(0,   "raw",              command, CMD_RAW,               OPT_SINGLE, "\tSend raw JSON message to the daemon"),
487     OPT_HELP(""),
488     OPT_HELP("Command options:"),
489     OPT_STRING('u', "user", arg_user, OPT_REQUIRED_VALUE, "login\tUser to act on (default: whoever calls it)"),
490     OPT_STRING('z', "zone", arg_zone, OPT_REQUIRED_VALUE, "zone\tAuthentication zone"),
491     OPT_STRING('i', "ident", arg_ident, OPT_REQUIRED_VALUE, "id\tToken ID ('*' for all tokens in zone)"),
492     OPT_STRING('c', "comment", arg_comment, OPT_REQUIRED_VALUE, "string\tHuman-readable description of token"),
493     OPT_STRING('x', "expire", arg_expire, OPT_REQUIRED_VALUE, "n(h|m|s)\tSet expiration time of a temporary token (default: 5m)"),
494     OPT_BOOL(0, "auth-passwd", arg_auth_passwd, 0, "\tAsk for current password to authorize the operation"),
495     OPT_END
496   }
497 };
498
499 int main(int argc UNUSED, char **argv)
500 {
501   log_set_format(log_default_stream(), 0, 0);
502   opt_parse(&options, argv+1);
503
504   if (command < 0)
505     opt_failure("No command given");
506   if (!command_handlers[command])
507     opt_failure("Command not implemented");
508   command_handlers[command]();
509
510   return 0;
511 }