ref: d95c83d30d3a9a4d5cba5cc9f803865931333bee
dir: /totp.c/
#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);
}
}
}