shithub: totp

ref: d95c83d30d3a9a4d5cba5cc9f803865931333bee
dir: /totp.c/

View raw version
#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);
		}
	}
}