/*
 * botscan.c -- written by cmwagner -- 08/25/96
 *
 * This handy little utility that I decided to write that would scan a
 * designated number of ports looking for eggdrop bots listening on the
 * ports and report the handle/version string and also use identd to get
 * the users login name.  Handy little tool, deadly in an IRCops hand.  :)
 *
*/

/* 1.1 cmwagner modified call_identd_server() function to parse the identd
 *              response differently to take the 3rd argument no matter what
 *              the OS type.  Previous versions wouldn't respond properly if
 *              OS type was not UNIX.
 * 1.2 cmwagner modified parse_eggdrop_version() function, which should fix
 *              the reminants of old version strings that were stuffed from
 *              past reads.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define DEFAULT_TARGET_LOW 3333			/* default starting port */
#define DEFAULT_TARGET_HIGH 3433		/* default ending port   */
#define DEFAULT_READ_TIMEOUT 2			/* default read timeout  */
#define MAX_BUFFER_LENGTH 1024			/* maximum buffer length */

static char sccsid[] = "@(#) botscan.c  1.2    (cmwagner) 09/13/96";

/* read in data from socket */
char *read_socket_data(int soc)
{
	fd_set fd;
	struct timeval t;
	static char buffer[MAX_BUFFER_LENGTH];
	int i;

	buffer[0]=0;

	/* use select and wait for up to DEFAULT_READ_TIMEOUT seconds */
	/* for data */
	t.tv_usec=0; t.tv_sec=DEFAULT_READ_TIMEOUT;
	FD_ZERO(&fd); FD_SET(soc,&fd);

	if (select(getdtablesize(),&fd,NULL,NULL,&t) > 0) {
		/* fd is set, must have something */
		if (FD_ISSET(soc,&fd)) {
			/* read data from socket */
			i=read(soc,buffer,MAX_BUFFER_LENGTH - 1);

			/* we definately have data */
			if (i) {
				buffer[i]=0;
				return buffer;
			}
		}
	}

	return NULL;
}

/* resolve hostname into IP address */
char *resolve_address(char *host)
{
	static char host_ip[32];
	struct hostent *hent;
	unsigned int a,b,c,d,n;
	char addr[5];

	host_ip[0]=0;

	/* couldn't resolve?  Maybe they specified an IP address? */
	if ((hent = gethostbyname(host)) == NULL) {
		n = sscanf(host,"%u.%u.%u.%u",&a,&b,&c,&d);

		/* nope, not an IP in quad-notation format */
		if (n != 4) {
			fprintf(stderr,"error: host '%s' not found.\n",
			host);

			exit(0);
		}

		addr[0] = a; addr[1] = b; addr[2] = c; addr[3] = d;

		/* okay, we have an IP, let's check it out */
		if ((hent = gethostbyaddr(addr,4,AF_INET)) == NULL) {
			fprintf(stderr,"error: host '%s' not found.\n",
			host);

			exit(0);
		}

		/* ahh, they specified an IP, and I bet you thought it */
		/* would be easier if an IP was specified */
		sprintf(host_ip,"%u.%u.%u.%u",a,b,c,d);
	} else {
		/* it's a host name, lookup the IP address */
		sprintf(host_ip,"%u.%u.%u.%u",
		(unsigned char)hent->h_addr_list[0][0],
		(unsigned char)hent->h_addr_list[0][1],
		(unsigned char)hent->h_addr_list[0][2],
		(unsigned char)hent->h_addr_list[0][3]);
	}

	return host_ip;
}

/* open socket, connect to host */
int open_socket(char *host,int port)
{
	int soc,opt;
	struct sockaddr_in addr;

	/* open up an TCP socket */
	if ((soc = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) {
		fprintf(stderr,"error: socket() failed\n");
		return -1;
	}

	setsockopt(soc,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(resolve_address(host));
	addr.sin_port = htons(port);

	/* let's try to connect now */
	if ((connect(soc,(struct sockaddr *)&addr,sizeof(addr))) < 0) {
		close(soc);
		return -1;
	}

	return soc;
}

/* parse data read for eggdrop version */
char *parse_eggdrop_version(char *s)
{
	static char vs[MAX_BUFFER_LENGTH];
	char *p,*vp;
	int i=0;

	vs[0]=0; vp=vs;

	/* hmm, what's the deal, you didn't give me anything? */
	if (s == NULL) return vs;

	/* strip out CR's and LF's, if there are any parenthesis we know */
	/* we have the eggdrop version string, so on the next CR or LF chop */
        /* it and return */
	for (p=s; *p; p++) {
		if ((*p == 40) || (*p == 41)) i=1;
		if ((*p != 13) && (*p != 10)) {
			*vp=*p; *(++vp)=0;
		} else if (i) {
			*vp=0; break;
		}
	}

	return vs;
}

/* call identd server to get username owned by port -- must have an */
/* existing data socket */
char *call_identd_service(char *host,int port,int dsoc)
{
	int soc,i;
	static char user[32];
	char s[256],*re,*p,*q;
	struct sockaddr_in addr;

	/* lookup the port used by the data socket */
	i=sizeof(addr);
	if (getsockname(dsoc,(struct sockaddr *)&addr,&i) < 0) {
		return NULL;
	}

	/* open a connect to identd server */
	if ((soc = open_socket(host,113)) >= 0) {
		/* send out remote-port,local-port so we can get a username */
		sprintf(s,"%d,%d\n",port,ntohs(addr.sin_port));
		write(soc,s,strlen(s));

		/* look out for any data coming from the identd server */
		for (i=0; i<2; i++) {
			if ((re = read_socket_data(soc)) != NULL) break;
		}

		/* cut off the identd server, we got atleast 2 reads */
		close(soc);

		/* damn, we didn't get anything */
		if (re == NULL) return NULL;

		/* set i to 0 -- will be our arg counter */
		i=0;
		/* we have something, scan up to the first colon */
		for (p=re; *p;) {
			if (*p == ':') {
				i++;
				while (*++p == ' ');
				/* ahh third argument, must be the username */
				if (i==3) break;
				/* okay, is it and error or a userid */
				/* response, if neither, then ? */
				if (*p == 'E' && i==1) return NULL;
				else if (*p != 'U' && i==1) return NULL;
			} else p++;
		}

		/* remove CR's and LF's */
		for (q=p; *q;) {
			if (*q == 13 || *q == 10) {
				strcpy(q,q+1);
			} else q++;
		}

		strcpy(user,p);
		return user;
	}

	return NULL;
}

/* start scanning ports for host */
void start_scan(char *host,int low,int high)
{
	int soc,port,i;
	char *ve,*re,*id;

	for (port=low; port<high; port++) {
		/* open socket to port */
		if ((soc = open_socket(host,port)) >= 0) {
			/* we got connected */

			/* call identd server and get username */
			id = call_identd_service(host,port,soc);

			/* is there a eggdrop bot listening? */
			for (i=0; i<2; i++) {
				re = read_socket_data(soc);
				ve = parse_eggdrop_version(re);
				if (*ve) break;
			}

			/* show results */
			printf("%-5d %-9s: %s\n",port,
			(id == NULL)?"(failed)":id,
			(*ve == 0)?"(recv timeout)":ve);

			/* close socket */
			close(soc);
		}
	}
}

/* main function, argument parser */
int main(int argc,char *argv[])
{
	char target_host[512];
	int target_low=DEFAULT_TARGET_LOW,target_high=DEFAULT_TARGET_HIGH;

	if (argc>1)
		strcpy(target_host,argv[1]);
	else {
		fprintf(stderr,"usage: %s <host> [<low port> [<high port>]]\n",
		argv[0]);

		exit(0);
	}
	if (argc>2)
		target_low = atoi(argv[2]);
	if (argc>3)
		target_high = atoi(argv[3]);

	printf("scanning %s's ports starting at %d to %d:\n",
	target_host,target_low,target_high);

	start_scan(target_host,target_low,target_high);

	exit(0);
}

