/*
 * netbios dial, read, write
 */

#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "cifs.h"

enum {
	MAXNBPKT		= 8096,		/* max netbios packet size */
	NBquery			= 0,		/* packet type - query */

	NBAdapterStatus		= 0x21,		/* get host interface info */
	NBInternet		= 1,		/* scope for info */

	NBmessage 		= 0x00,		/* Netbios packet types */
	NBrequest 		= 0x81,
	NBpositive,
	NBnegative,
	NBretarget,
	NBkeepalive,

	ISgroup			= 0x8000,
};


static char *NBerr[] = {
	[0]	"not listening on called name",
	[1]	"not listening for calling name",
	[2]	"called name not present",
	[3]	"insufficient resources",
	[15]	"unspecified error"
};


static ulong
GL32(uchar **p)
{
	ulong n;

	n  = *(*p)++;
	n |= *(*p)++ << 8;
	n |= *(*p)++ << 16;
	n |= *(*p)++ << 24;
	return n;
}

static ushort
GL16(uchar **p)
{
	ushort n;

	n  = *(*p)++;
	n |= *(*p)++ << 8;
	return n;
}

void
Gmem(uchar **p, void *v, int n)
{
	uchar *str = v;

	while(n--)
		*str++ = *(*p)++;
}


static ulong
GB32(uchar **p)
{
	ulong n;

	n  = *(*p)++ << 24;
	n |= *(*p)++ << 16;
	n |= *(*p)++ << 8;
	n |= *(*p)++;
	return n;
}

static ushort
GB16(uchar **p)
{
	ushort n;

	n  = *(*p)++ << 8;
	n |= *(*p)++;
	return n;
}

static uchar
G8(uchar **p)
{
	return *(*p)++;
}

static void
PB16(uchar **p, uint n)
{
	*(*p)++ = n >> 8;
	*(*p)++ = n;
}

static void
P8(uchar **p, uint n)
{
	*(*p)++ = n;
}


static void
nbname(uchar **p, char *name, char pad)
{
	char c;
	int i;
	int done = 0;

	*(*p)++ = 0x20;
	for(i = 0; i < 16; i++) {
		c = pad;
		if(!done && name[i] == '\0')
			done = 1;
		if(!done)
			c = toupper(name[i]);
		*(*p)++ = ((uchar)c >> 4) + 'A';
		*(*p)++ = (c & 0xf) + 'A';
	}
	*(*p)++ = 0;
}

int
calledname(char *host, char *name)
{
	char *addr;
	uchar buf[1024], *p;
	static char tmp[20];
	int num, flg, svs, j, i, fd, trn;

	trn = (getpid() ^ time(0)) & 0xffff;
	if((addr = netmkaddr(host, "udp", "137")) == nil)
		return -1;

	if((fd = dial(addr, "137", 0, 0)) < 0)
		return -1;
	p = buf;

	PB16(&p, trn);			/* TRNid */
	P8(&p, 0);			/* flags */
	P8(&p, 0x10);			/* type */
	PB16(&p, 1);			/* # questions */
	PB16(&p, 0);			/* # answers */
	PB16(&p, 0);			/* # authority RRs */
	PB16(&p, 0);			/* # Aditional RRs */
	nbname(&p, "*", 0);
	PB16(&p, NBAdapterStatus);
	PB16(&p, NBInternet);

	if(Debug && strstr(Debug, "dump"))
		xd(nil, buf, p-buf);

	if(write(fd, buf, p-buf) != p-buf)
		return -1;

	p = buf;
	for(i = 0; i < 3; i++){
		memset(buf, 0, sizeof(buf));
		alarm(NBNSTOUT);
		read(fd, buf, sizeof(buf));
		alarm(0);
		if(GB16(&p) == trn)
			break;
	}
	close(fd);
	if(i >= 3)
		return -1;

	p = buf +56;
	num = G8(&p);			/* number of names */

	for(i = 0; i < num; i++){
		memset(tmp, 0, sizeof(tmp));
		Gmem(&p, tmp, 15);
		svs = G8(&p);
		flg = GB16(&p);
		for(j = 14; j >= 0 && tmp[j] == ' '; j--)
			tmp[j] = 0;
		if(svs == 0 && !(flg & ISgroup))
			strcpy(name, tmp);
	}
	return 0;
}


int
nbtdial(char *addr, char *called, char *sysname)
{
	char redir[20];
	uchar *p, *lenp, buf[1024];
	int type, len, err, fd, nkeepalive, nretarg;

	nretarg = 0;
	nkeepalive = 0;
Redial:
	if((addr = netmkaddr(addr, "tcp", "139")) == nil ||
	    (fd = dial(addr, 0, 0, 0)) < 0)
		return -1;

	memset(buf, 0, sizeof(buf));

	p = buf;
	P8(&p, NBrequest);		/* type */
	P8(&p, 0);			/* flags */
	lenp = p; PB16(&p, 0);		/* length placeholder */
	nbname(&p, called, ' ');	/* remote NetBios name */
	nbname(&p, sysname, ' ');	/* our machine name */
	PB16(&lenp, p-lenp -2);		/* length re-write */

	if(Debug && strstr(Debug, "dump"))
		xd(nil, buf, p-buf);
	if(write(fd, buf, p-buf) != p-buf)
		goto Error;
Reread:
	p = buf;
	memset(buf, 0, sizeof(buf));
	if(readn(fd, buf, 4) < 4)
		goto Error;

	type = G8(&p);
	G8(&p);				/* flags */
	len = GB16(&p);

	if(readn(fd, buf +4, len -4) < len -4)
		goto Error;

	if(Debug && strstr(Debug, "dump"))
		xd(nil, buf, len+4);

	switch(type) {
	case NBpositive:
		return fd;
	case NBnegative:
		if(len < 1) {
			werrstr("nbdial: bad error pkt");
			goto Error;
		}
		err = G8(&p);
		if(err < 0 || err > nelem(NBerr) || NBerr[err] == nil)
			werrstr("NBT: %d - unknown error", err);
		else
			werrstr("NBT: %s", NBerr[err]);

		goto Error;
	case NBkeepalive:
		if(++nkeepalive >= 16){
			werrstr("nbdial: too many keepalives");
			goto Error;
		}
		goto Reread;

	case NBretarget:
		if(++nretarg >= 16) {
			werrstr("nbdial: too many redirects");
			goto Error;
		}
		if(len < 4) {
			werrstr("nbdial: bad redirect pkt");
			goto Error;
		}
		sprint(redir, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
		addr = redir;
		goto Redial;

	default:
		werrstr("nbdial: 0x%x - unknown packet in netbios handshake", type);
		goto Error;
	}
Error:
	close(fd);
	return -1;
}

void
nbthdr(Pkt *p)
{
	p->pos = p->buf;
	memset(p->buf, 0xa5, MTU);

	p8(p, NBmessage);		/* type */
	p8(p, 0);			/* flags */
	pb16(p, 0);			/* length (filled in later) */
}

int
nbtrpc(Pkt *p)
{
	int len, got, type, nkeep;

	len = p->pos - p->buf;

	p->pos = p->buf +2;
	pb16(p, len - NBHDRLEN);	/* length */

	if(Debug && strstr(Debug, "dump"))
		xd("tx", p->buf, len);

	alarm(NBRPCTOUT);
	if(write(p->s->fd, p->buf, len) != len){
		werrstr("nbtrpc: write failed - %r");
		alarm(0);
		return -1;
	}

	nkeep = 0;
retry:
	p->pos = p->buf;
	memset(p->buf, 0xa5, MTU);

	got = readn(p->s->fd, p->buf, NBHDRLEN);

	if(got < NBHDRLEN){
		werrstr("nbtrpc: short read - %r");
		alarm(0);
		return -1;
	}
	p->eop = p->buf + got;

	type = g8(p);			/* NBT type (session) */
	if(type == NBkeepalive){
		if(++nkeep > 16) {
			werrstr("nbtrpc: too many keepalives (%d attempts)", nkeep);
			alarm(0);
			return -1;
		}
		goto retry;
	}

	g8(p);				/* NBT flags (none) */

	len = gb16(p);			/* NBT payload length */
	if((len +NBHDRLEN) > MTU){
		werrstr("nbtrpc: packet bigger than MTU, (%d > %d)", len, MTU);
		alarm(0);
		return -1;
	}

	got = readn(p->s->fd, p->buf +NBHDRLEN, len);
	alarm(0);

	if(Debug && strstr(Debug, "dump"))
		xd("rx", p->buf, got +NBHDRLEN);

	if(got < 0)
		return -1;
	p->eop = p->buf + got +NBHDRLEN;
	return got+NBHDRLEN;
}


void
xd(char *str, void *buf, int n)
{
	int fd, flg, flags2, cmd;
	uint sum;
	long err;
	uchar *p, *end;

	if(n == 0)
		return;

	p = buf;
	end = (uchar *)buf +n;

	if(Debug && strstr(Debug, "log") != nil){
		if((fd = open("pkt.log", ORDWR)) == -1)
			return;
		seek(fd, 0, 2);
		fprint(fd, "%d	", 0);
		while(p < end)
			fprint(fd, "%02x ", *p++);
		fprint(fd, "\n");
		close(fd);
		return;
	}

	if(!str)
		goto Raw;

	p = (uchar *)buf + 4;
	if(GL32(&p) == 0x424d53ff){
		buf = (uchar *)buf + 4;
		n -= 4;
	}
	end = (uchar *)buf + n;

	sum = 0;
	p = buf;
	while(p < end)
		sum += *p++;
	p = buf;

	fprint(2, "%s : len=%ud sum=%d\n", str, n, sum);

	fprint(2, "mag=0x%ulx ", GL32(&p));
	fprint(2, "cmd=0x%ux ", cmd = G8(&p));
	fprint(2, "err=0x%ulx ", err=GL32(&p));
	fprint(2, "flg=0x%02ux ", flg = G8(&p));
	fprint(2, "flg2=0x%04ux\n", flags2= GL16(&p));
	fprint(2, "dfs=%s\n", (flags2 & FL2_DFS)? "y": "n");

	fprint(2, "pidl=%ud ", GL16(&p));
	fprint(2, "res=%uld ", GL32(&p));
	fprint(2, "sid=%ud ", GL16(&p));
	fprint(2, "seq=0x%ux ", GL16(&p));
	fprint(2, "pad=%ud ", GL16(&p));

	fprint(2, "tid=%ud ", GL16(&p));
	fprint(2, "pid=%ud ", GL16(&p));
	fprint(2, "uid=%ud ", GL16(&p));
	fprint(2, "mid=%ud\n", GL16(&p));

	if(cmd == 0x32 && (flg & 0x80) == 0){		/* TRANS 2, TX */
		fprint(2, "words=%ud ", G8(&p));
		fprint(2, "totparams=%ud ", GL16(&p));
		fprint(2, "totdata=%ud ", GL16(&p));
		fprint(2, "maxparam=%ud ", GL16(&p));
		fprint(2, "maxdata=%ud\n", GL16(&p));
		fprint(2, "maxsetup=%ud ", G8(&p));
		fprint(2, "reserved=%ud ", G8(&p));
		fprint(2, "flags=%ud ", GL16(&p));
		fprint(2, "timeout=%uld\n", GL32(&p));
		fprint(2, "reserved=%ud ", GL16(&p));
		fprint(2, "paramcnt=%ud ", GL16(&p));
		fprint(2, "paramoff=%ud ", GL16(&p));
		fprint(2, "datacnt=%ud ", GL16(&p));
		fprint(2, "dataoff=%ud ", GL16(&p));
		fprint(2, "setupcnt=%ud ", G8(&p));
		fprint(2, "reserved=%ud\n", G8(&p));
		fprint(2, "trans2=0x%02x ", GL16(&p));
		fprint(2, "data-words=%d ", G8(&p));
		fprint(2, "padding=%d\n", G8(&p));
	}
	if(cmd == 0x32 && (flg & 0x80) == 0x80){	/* TRANS 2, RX */
		fprint(2, "words=%ud ", G8(&p));
		fprint(2, "totparams=%ud ", GL16(&p));
		fprint(2, "totdata=%ud ", GL16(&p));
		fprint(2, "reserved=%ud ", GL16(&p));
		fprint(2, "paramcnt=%ud\n", GL16(&p));
		fprint(2, "paramoff=%ud ", GL16(&p));
		fprint(2, "paramdisp=%ud ", GL16(&p));
		fprint(2, "datacnt=%ud\n", GL16(&p));
		fprint(2, "dataoff=%ud ", GL16(&p));
		fprint(2, "datadisp=%ud ", GL16(&p));
		fprint(2, "setupcnt=%ud ", G8(&p));
		fprint(2, "reserved=%ud\n", G8(&p));
	}
	if(err)
		if(flags2 & FL2_NT_ERRCODES)
			fprint(2, "err=%s\n", nterrstr(err));
		else
			fprint(2, "err=%s\n", doserrstr(err));
Raw:
	fprint(2, "\n");
	for(; p < end; p++){
		if((p - (uchar *)buf) % 16 == 0)
			fprint(2, "\n%06zx\t", p - (uchar *)buf);
		if(isprint((char)*p))
			fprint(2, "%c  ", (char )*p);
		else
			fprint(2, "%02ux ", *p);
	}
	fprint(2, "\n");
}
