dsasign: new command
diff --git a/src/cmd/auth/dsasign.c b/src/cmd/auth/dsasign.c
new file mode 100644
index 0000000..1387dab
--- /dev/null
+++ b/src/cmd/auth/dsasign.c
@@ -0,0 +1,180 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include <auth.h>
+#include <thread.h>
+#include <9pclient.h>
+#include <bio.h>
+
+void
+usage(void)
+{
+	fprint(2, "usage: 9 dsasign [-i id] [-v] key <data\n");
+	threadexitsall("usage");
+}
+
+static void doVerify(void);
+static char *getline(int*);
+
+char *id;
+Biobuf b;
+int nid;
+char *key;
+
+void
+threadmain(int argc, char **argv)
+{
+	int n, verify;
+	char *text, *p;
+	uchar digest[SHA1dlen];
+	AuthRpc *rpc;
+	Fmt fmt;
+	
+	fmtinstall('[', encodefmt);
+	fmtinstall('H', encodefmt);
+	
+	verify = 0;
+	id = "";
+	ARGBEGIN{
+	case 'i':
+		id = EARGF(usage());
+		break;
+	case 'v':
+		verify = 1;
+		break;
+	default:
+		usage();
+	}ARGEND
+	
+	if(argc != 1)
+		usage();
+	key = argv[0];
+	nid = strlen(id);
+
+	Binit(&b, 0, OREAD);
+	if(verify) {
+		doVerify();
+		threadexitsall(nil);
+	}
+
+	if((rpc = auth_allocrpc()) == nil){
+		fprint(2, "dsasign: auth_allocrpc: %r\n");
+		threadexits("rpc");
+	}
+	key = smprint("proto=dsa role=sign %s", key);
+	if(auth_rpc(rpc, "start", key, strlen(key)) != ARok){
+		fprint(2, "dsasign: auth 'start' failed: %r\n");
+		auth_freerpc(rpc);
+		threadexits("rpc");
+	}
+	
+	print("+%s\n", id);
+
+	Binit(&b, 0, OREAD);
+	fmtstrinit(&fmt);
+	while((p = getline(&n)) != nil) {
+		if(p[0] == '-' || p[0] == '+')
+			print("+");
+		print("%s\n", p);
+		fmtprint(&fmt, "%s\n", p);
+	}
+	text = fmtstrflush(&fmt);
+	sha1((uchar*)text, strlen(text), digest, nil);
+
+	if(auth_rpc(rpc, "write", digest, SHA1dlen) != ARok)
+		sysfatal("auth write in sign failed: %r");
+	if(auth_rpc(rpc, "read", nil, 0) != ARok)
+		sysfatal("auth read in sign failed: %r");
+
+	print("-%s %.*H\n", id, rpc->narg, rpc->arg);
+	threadexits(nil);
+}
+
+static mpint*
+keytomp(Attr *a, char *name)
+{
+	char *p;
+	mpint *m;
+	
+	p = _strfindattr(a, name);
+	if(p == nil)
+		sysfatal("missing key attribute %s", name);
+	m = strtomp(p, nil, 16, nil);
+	if(m == nil)
+		sysfatal("malformed key attribute %s=%s", name, p);
+	return m;
+}
+
+static void
+doVerify(void)
+{
+	char *p;
+	int n, nsig;
+	Fmt fmt;
+	uchar digest[SHA1dlen], sig[1024];
+	char *text;
+	Attr *a;
+	DSAsig dsig;
+	DSApub dkey;
+	
+	a = _parseattr(key);
+	if(a == nil)
+		sysfatal("invalid key");
+	dkey.alpha = keytomp(a, "alpha");
+	dkey.key = keytomp(a, "key");
+	dkey.p = keytomp(a, "p");
+	dkey.q = keytomp(a, "q");
+	if(!probably_prime(dkey.p, 20) && !probably_prime(dkey.q, 20))
+		sysfatal("p or q not prime");
+
+	while((p = getline(&n)) != nil)
+		if(p[0] == '+' && strcmp(p+1, id) == 0)
+			goto start;
+	sysfatal("no message found");
+
+start:
+	fmtstrinit(&fmt);
+	while((p = getline(&n)) != nil) {
+		if(n >= 1+nid+1+16 && p[0] == '-' && strncmp(p+1, id, nid) == 0 && p[1+nid] == ' ') {
+			if((nsig = dec16(sig, sizeof sig, p+1+nid+1, n-(1+nid+1))) != 20+20)
+				sysfatal("malformed signture");
+			goto end;
+		}
+		if(p[0] == '+')
+			p++;
+		fmtprint(&fmt, "%s\n", p);
+	}
+	sysfatal("did not find end of message");
+
+end:
+	text = fmtstrflush(&fmt);
+	sha1((uchar*)text, strlen(text), digest, nil);
+
+	if(nsig != 40)
+		sysfatal("malformed signature");
+	dsig.r = betomp(sig, 20, nil);
+	dsig.s = betomp(sig+20, 20, nil);
+
+	if(dsaverify(&dkey, &dsig, betomp(digest, sizeof digest, nil)) < 0)
+		sysfatal("signature failed to verify: %r");
+	
+	write(1, text, strlen(text));
+	threadexitsall(0);
+}
+
+char*
+getline(int *np)
+{
+	char *p;
+	int n;
+	
+	if((p = Brdline(&b, '\n')) == nil)
+		return nil;
+	n = Blinelen(&b);
+	while(n > 0 && (p[n-1] == '\n' || p[n-1] == ' ' || p[n-1] == '\t'))
+		n--;
+	p[n] = '\0';
+	*np = n;
+	return p;
+}
diff --git a/src/cmd/auth/mkfile b/src/cmd/auth/mkfile
index af08e4a..6178c5c 100644
--- a/src/cmd/auth/mkfile
+++ b/src/cmd/auth/mkfile
@@ -6,6 +6,7 @@
 	asn12dsa\
 	asn12rsa\
 	dsagen\
+	dsasign\
 	dsa2pub\
 	dsa2ssh\
 	passwd\