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