ref: 8e3d8120a945bad722e218963699f0a17a853d86
author: sirjofri <sirjofri@sirjofri.de>
date: Fri Sep 8 09:43:43 EDT 2023
first version, with time, snarf and update
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,6 @@
+</$objtype/mkfile
+
+TARG=totp
+OFILES=totp.$O
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/totp.c
@@ -1,0 +1,435 @@
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+#include <auth.h>
+#include <bio.h>
+#include <draw.h>
+#include <event.h>
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-D] [-t]\n", argv0);
+ exits(nil);
+}
+
+int chatty;
+
+typedef struct Account Account;
+struct Account {
+ char *user;
+ char code[12];
+ int remaining;
+ int expanded;
+ int hitstart;
+ int hitend;
+ Account *next;
+};
+
+Account *accounts;
+int numaccounts;
+
+// return seconds, write buf with OTP
+int
+readtotp(char *buf, char *user)
+{
+ AuthRpc *rpc;
+ char *s;
+ int n, fd, rem;
+ char *toks[2];
+
+ fd = open("/mnt/factotum/rpc", ORDWR);
+ if (fd < 0)
+ sysfatal("error accessing factotum: %r");
+
+ rpc = auth_allocrpc(fd);
+ if (!rpc)
+ sysfatal("error: %r");
+
+ s = smprint("proto=totp user=%q role=client", user);
+ n = strlen(s);
+
+ if (chatty)
+ print("rpc: %s\n", s);
+
+ if (auth_rpc(rpc, "start", s, n) != ARok)
+ goto Err;
+
+ free(s);
+
+ auth_rpc(rpc, "read", nil, 0);
+
+ if (tokenize(rpc->arg, toks, 2) != 2)
+ goto Err1;
+
+ snprint(buf, 10, "%s", toks[0]);
+ rem = atoi(toks[1]);
+
+ if (chatty)
+ print("code: %s, remaining: %d\n", buf, rem);
+
+ auth_freerpc(rpc);
+ close(fd);
+ return rem;
+
+Err:
+ close(fd);
+ free(s);
+Err1:
+ auth_freerpc(rpc);
+ *buf = 0;
+ return -1;
+}
+
+void
+readaccount(Account *acc)
+{
+ int rem = readtotp(acc->code, acc->user);
+
+ if (rem < 0) {
+ print("error reading data for account '%s'", acc->user);
+ return;
+ }
+
+ if (chatty)
+ print("readaccount: %s\n", acc->user);
+
+ acc->remaining = rem;
+}
+
+void
+foraccount(void (*fn)(Account*))
+{
+ Account *a = accounts;
+
+ if (a == nil)
+ return;
+
+ while (a != nil) {
+ fn(a);
+ a = a->next;
+ }
+}
+
+void
+printaccount(Account *a)
+{
+ if (chatty)
+ print("print account '%s'\n", a->user);
+
+ print("%s: %s (%d)\n", a->user, a->code, a->remaining);
+}
+
+char*
+parseuser(char **toks, int ntoks)
+{
+ int i = 2, n;
+ char *s;
+
+ while (i < ntoks && strncmp("user=", toks[i], 5))
+ i++;
+
+ s = toks[i] + 5; // get rid of "user="
+
+ // unquote, if needed
+ if (*s == '\'') {
+ s += 1;
+ n = strlen(s);
+ s[n-1] = 0;
+ }
+
+ return strdup(s);
+}
+
+int
+addaccount(char *s)
+{
+ Account *a;
+ char *toks[16];
+ int ntoks;
+
+ ntoks = tokenize(s, toks, 16);
+
+ if (ntoks < 3) {
+ sysfatal("bad response from factotum!");
+ }
+
+ if (strcmp("key", toks[0]) != 0 || strcmp("proto=totp", toks[1]) != 0)
+ return 0;
+
+
+ int valid = 0;
+ for (int i = 0; i < ntoks; i++) {
+ if (strcmp("role=client", toks[i]) == 0) {
+ valid = 1;
+ break;
+ }
+ }
+ if (!valid)
+ return 0;
+
+
+ if (!accounts) {
+ accounts = malloc(sizeof(Account));
+ if (!accounts)
+ sysfatal("err: %r");
+ a = accounts;
+ } else {
+ a = accounts;
+ while (a->next != nil)
+ a = a->next;
+
+ a->next = malloc(sizeof(Account));
+ if (!a->next)
+ sysfatal("err: %r");
+ a = a->next;
+ }
+ a->hitstart = a->hitend = -1;
+ *(a->code) = 0;
+ a->next = nil;
+ a->expanded = 0;
+
+ a->user = parseuser(toks, ntoks);
+
+ return 1;
+}
+
+void
+freeaccount(Account* acc)
+{
+ Account* c = acc->next;
+
+ if (acc->user)
+ free(acc->user);
+
+ free(acc);
+ if (c)
+ freeaccount(c);
+}
+
+int
+readaccounts(void)
+{
+ Biobuf *bfd;
+ char *s;
+ int num = 0;
+
+ if (accounts)
+ freeaccount(accounts);
+
+ bfd = Bopen("/mnt/factotum/ctl", OREAD);
+ if (!bfd)
+ sysfatal("error reading factotum: %r");
+
+ while (s = Brdstr(bfd, '\n', 1)) {
+ num += addaccount(s);
+ free(s);
+ }
+
+ Bterm(bfd);
+
+ if (chatty)
+ print("%d accounts added\n", num);
+ return num;
+}
+
+/* images */
+Image *back;
+Image *linecol;
+
+int lineheight;
+
+/* draw params */
+Image *dp_screen;
+int dp_num; /* number of accounts */
+int dp_id; /* ID of this account */
+int dp_numexpanded; /* number of expanded elements to consider */
+
+void
+drawaccount(Account *account)
+{
+ Point start, end, l1, l2;
+ int numnormal;
+ int margin_l = 10;
+ char rem[16];
+
+ numnormal = dp_id - dp_numexpanded;
+
+ l1.x = margin_l;
+ l1.y = numnormal*(lineheight+15) + dp_numexpanded*(lineheight*2+20) + 5;
+ l1 = addpt(l1, dp_screen->r.min);
+ string(dp_screen, l1, linecol, ZP, font, account->user);
+
+ account->hitstart = numnormal*(lineheight+15) + dp_numexpanded*(lineheight*2+20);
+
+ if (account->expanded) {
+ l2.x = margin_l;
+ l2.y = numnormal*(lineheight+15) + dp_numexpanded*(lineheight*2+20) + 5 + lineheight + 5;
+ l2 = addpt(l2, dp_screen->r.min);
+ string(dp_screen, l2, linecol, ZP, font, account->code);
+
+ snprint(rem, 16, "%d", account->remaining);
+ l2.x = screen->r.max.x - margin_l - stringwidth(font, rem);
+ string(dp_screen, l2, linecol, ZP, font, rem);
+
+ dp_numexpanded++;
+ } else {
+ numnormal++;
+ }
+
+ account->hitend = numnormal*(lineheight+15) + dp_numexpanded*(lineheight*2+20);
+
+ start.x = 0;
+ start.y = numnormal*(lineheight+15) + dp_numexpanded*(lineheight*2+20);
+ end.x = dp_screen->r.max.x - dp_screen->r.min.x;
+ end.y = start.y;
+
+ start = addpt(start, dp_screen->r.min);
+ end = addpt(end, dp_screen->r.min);
+
+ line(dp_screen, start, end, 0, 0, 0, linecol, ZP);
+
+ dp_id++;
+}
+
+/* check hit account params */
+Point ch_point;
+Account *ch_clicked;
+
+void
+checkhitaccount(Account *account)
+{
+ int ref;
+
+ if (account->hitend < 0 || account->hitstart < 0)
+ return;
+
+ ref = ch_point.y;
+ if (ref < account->hitend && ref > account->hitstart) {
+ ch_clicked = account;
+ if (chatty)
+ print("clicked element %s\n", account->user);
+ }
+}
+
+Account*
+accounthit(Point p)
+{
+ ch_point = subpt(p, screen->r.min);
+ ch_clicked = nil;
+ foraccount(checkhitaccount);
+
+ return ch_clicked;
+}
+
+void
+snarf(Account *account)
+{
+ int fd = open("/dev/snarf", OWRITE);
+ if (fd < 0)
+ return;
+
+ fprint(fd, "%s", account->code);
+ close(fd);
+}
+
+void
+redraw(Image *screen)
+{
+ draw(screen, screen->r, back, nil, ZP);
+ dp_screen = screen;
+ dp_num = numaccounts;
+ dp_id = 0;
+ dp_numexpanded = 0;
+ foraccount(drawaccount);
+}
+
+void
+eresized(int new)
+{
+ if (new && getwindow(display, Refnone) < 0)
+ fprint(2, "can't reattach to window");
+ redraw(screen);
+}
+
+void
+main(int argc, char **argv)
+{
+ int textbased = 0;
+ chatty = 0;
+ accounts = nil;
+
+ Event e;
+ Mouse m;
+ Menu menu;
+ char *mstr[] = {" update ", "exit", 0};
+ int key, timer, menuhit;
+
+ Account *selected;
+
+ ARGBEGIN {
+ case 't':
+ textbased++;
+ break;
+ case 'D':
+ chatty++;
+ break;
+ } ARGEND;
+
+ quotefmtinstall();
+
+ numaccounts = readaccounts();
+
+ if (textbased) {
+ foraccount(readaccount);
+ foraccount(printaccount);
+ exits(nil);
+ }
+
+ if (initdraw(0, 0, "totp") < 0)
+ sysfatal("initdraw failed: %r");
+
+ back = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DWhite);
+ linecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DBlack);
+ lineheight = stringsize(font, "M").y;
+
+ redraw(screen);
+
+ einit(Emouse);
+ timer = etimer(0, 1000);
+
+ menu.item = mstr;
+ menu.lasthit = 0;
+ for (;;) {
+ key = event(&e);
+ if (key == Emouse) {
+ m = e.mouse;
+ if (m.buttons & 4) {
+ menuhit = emenuhit(3, &m, &menu);
+ if (menuhit == 0) {
+ numaccounts = readaccounts();
+ foraccount(readaccount);
+ }
+ if (menuhit == 1)
+ exits(nil);
+ }
+ if (m.buttons & 1) {
+ selected = accounthit(m.xy);
+ if (selected) {
+ selected->expanded = !selected->expanded;
+ redraw(screen);
+ }
+ }
+ if (m.buttons & 2) {
+ selected = accounthit(m.xy);
+ if (selected) {
+ snarf(selected);
+ }
+ }
+ }
+ if (key == timer) {
+ foraccount(readaccount);
+ redraw(screen);
+ }
+ }
+}