2 * Sub-authentication Client
4 * (c) 2017 Martin Mares <mj@ucw.cz>
10 #include <ucw/strtonum.h>
11 #include <ucw-json/json.h>
16 #include <sys/socket.h>
25 static char *socket_path = INSTALL_RUN_DIR "/subauthd/subauthd.socket";
27 static char *arg_user;
28 static char *arg_zone;
29 static char *arg_ident;
30 static char *arg_comment;
32 static char *arg_expire;
33 static int arg_auth_passwd;
35 /*** Communication with the daemon ***/
37 static struct json_context *js;
38 static struct json_node *rq;
39 static struct json_node *rp;
41 static void set_string(struct json_node *n, const char *key, const char *val)
44 json_object_set(n, key, json_new_string(js, val));
47 static void set_uint(struct json_node *n, const char *key, uint val)
50 json_object_set(n, key, json_new_number(js, val));
53 static struct json_node *get_child(struct json_node *obj, const char *key, uint type)
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);
61 static struct json_node *need_child(struct json_node *obj, const char *key, uint type)
63 struct json_node *n = get_child(obj, key, type);
65 die("Malformed reply: missing %s", key);
69 static void op_new(const char *op)
72 rq = json_new_object(js);
73 set_string(rq, "cmd", op);
75 set_string(rq, "login", arg_user);
78 char *auth_passwd = xstrdup(getpass("Current password: "));
79 set_string(rq, "auth-passwd", auth_passwd);
83 static void op_debug(const char *msg, struct json_node *n)
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;
96 static void op_run(void)
98 int sk = socket(PF_UNIX, SOCK_SEQPACKET, 0);
100 die("socket(PF_UNIX, SOCK_SEQPACKET): %m");
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);
108 if (connect(sk, (struct sockaddr *) &sun, sizeof(sun)) < 0)
109 die("Cannot connect to %s: %m", socket_path);
111 op_debug("Request", rq);
113 struct fastbuf *rq_fb = fbgrow_create(4096);
114 json_write(js, rq_fb, rq);
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");
122 int rp_len = recv(sk, rp_buf, sizeof(rp_buf), 0);
124 die("Cannot receive reply: %m");
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);
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);
142 static void cmd_set_passwd(void)
145 opt_failure("--zone must be given");
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");
153 set_string(rq, "zone", arg_zone);
154 set_string(rq, "passwd", passwd);
159 static void cmd_delete_passwd(void)
162 opt_failure("--zone must be given");
164 op_new("delete-passwd");
165 set_string(rq, "zone", arg_zone);
170 static void cmd_create_token(void)
173 opt_failure("--zone must be given");
175 op_new("create-token");
176 set_string(rq, "zone", arg_zone);
177 set_string(rq, "comment", arg_comment);
180 struct json_node *jt = need_child(rp, "token", JSON_STRING);
181 printf("%s\n", jt->string);
184 static void cmd_delete_token(void)
186 if (!arg_zone || !arg_ident)
187 opt_failure("--zone and --ident must be given");
189 op_new("delete-token");
190 set_string(rq, "zone", arg_zone);
191 set_string(rq, "ident", arg_ident);
196 static void cmd_change_token(void)
198 if (!arg_zone || !arg_ident)
199 opt_failure("--zone and --ident must be given");
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);
209 static void cmd_create_acct(void)
211 if (!arg_zone || !arg_user)
212 opt_failure("--zone and --user must be given");
214 op_new("create-acct");
215 set_string(rq, "zone", arg_zone);
216 set_string(rq, "login", arg_user);
221 static void cmd_delete_acct(void)
223 if (!arg_zone || !arg_user)
224 opt_failure("--zone and --user must be given");
226 op_new("delete-acct");
227 set_string(rq, "zone", arg_zone);
228 set_string(rq, "login", arg_user);
233 static void cmd_delete_user(void)
236 opt_failure("--user must be given");
238 op_new("delete-acct");
239 set_string(rq, "zone", "*");
240 set_string(rq, "login", arg_user);
245 static void cmd_list(void)
247 op_new("list-accts");
250 struct json_node **jas = need_child(rp, "accounts", JSON_ARRAY)->elements;
253 puts("No accounts defined");
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++)
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);
268 printf("%-16s (no tokens defined%s)\n",
270 jpassauth ? (jpassauth->number ? ", password auth allowed" : ", password auth disabled") : "");
274 for (uint j=0; j < GARY_SIZE(jts); j++)
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",
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 : ""));
295 static void cmd_zones(void)
297 op_new("list-zones");
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++)
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",
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)"));
321 static void cmd_login(void)
324 opt_failure("--zone must be given");
328 uid_t uid = getuid();
329 struct passwd *pw = getpwuid(uid);
331 die("You do not exist");
332 arg_user = xstrdup(pw->pw_name);
335 char *passwd = xstrdup(getpass("Password: "));
338 set_string(rq, "zone", arg_zone);
339 set_string(rq, "passwd", passwd);
345 static uint parse_expire(void)
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");
359 return expire * 3600;
365 opt_failure("--expire uses an unknown unit");
369 static void cmd_temp_token(void)
372 opt_failure("--zone must be given");
374 op_new("create-temp");
375 set_string(rq, "zone", arg_zone);
376 set_uint(rq, "validity", parse_expire());
379 struct json_node *jt = need_child(rp, "token", JSON_STRING);
380 printf("%s\n", jt->string);
383 static void cmd_allow_passwd_auth(void)
386 opt_failure("--zone must be given");
388 op_new("allow-passwd-auth");
389 set_string(rq, "zone", arg_zone);
390 set_uint(rq, "allow", 1);
395 static void cmd_disallow_passwd_auth(void)
398 opt_failure("--zone must be given");
400 op_new("allow-passwd-auth");
401 set_string(rq, "zone", arg_zone);
402 json_object_set(rq, "allow", json_new_number(js, 0));
407 static void cmd_raw(void)
411 struct fastbuf *in = bfdopen_shared(0, 4096);
412 rq = json_parse(js, in);
431 CMD_ALLOW_PASSWD_AUTH,
432 CMD_DISALLOW_PASSWD_AUTH,
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,
455 static int command = -1;
459 static const struct opt_section options = {
461 OPT_HELP("A client to the sub-authentication daemon."),
462 OPT_HELP("Usage: subauth [general options] <command> [options]"),
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"),
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"),
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"),
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"),
499 int main(int argc UNUSED, char **argv)
501 log_set_format(log_default_stream(), 0, 0);
502 opt_parse(&options, argv+1);
505 opt_failure("No command given");
506 if (!command_handlers[command])
507 opt_failure("Command not implemented");
508 command_handlers[command]();