/*
 * Check for open socks proxy.
 * http://akson.sgh.waw.pl/~chopin/ircd/
 *
 * Compile (at least on Solaris 2.x):
 * gcc -o checksocks -lnsl -lsocket checksocks.c
 *
 * Bug/comments welcome.
 *
 * For explanation of what paranoid/careful is,
 * consult iauth.conf(5) manual.
 *
 * Piotr 'Beeth' Kucharski <chopin@sgh.waw.pl>
 *  906958800 v.1
 *  930528000 v.2
 *  953964000 v.3
 * 1015006000 v.4
 *
 */

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

#define PORT 6667

#define DEBUG 1


int main (int argc, char * * argv)
{

	char SOCK4[14] = { 4, 1, 0, 0, 0, 0, 0, 0, 'u', 's', 'e', 'r', 0, 0 };
	char SOCK5a[4]={5, 1, 0, 0 };
	char SOCK5b[11]={5, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0 };

	int sock, proxyport, ircport;
	struct sockaddr_in src, dst;
	struct hostent *he;
	char *tmpport;
	char host[1024];
	char buf[64];
	char buf5[64];
	ssize_t rlen;
	int opt = 0;
	int args = 0;
	char *proxy, *srcad, *ircad;
#ifdef DEBUG
	int k;
#endif

void usage()
{
	printf("Usage:\n\t%s [-s srcaddr] proxy[:port] [ircserver[:port]]\n",
		argv[0]);
	printf("If proxy port is not given, default is 1080\n");
	printf("If ircserver is not given, default is your local host\n");
	printf("If ircserver port is not given, default is %d\n", PORT);
	printf("If you specify srcaddr, we will try to bind to it\n");
	printf("\n");
	exit(-1);
}

	if (argc < 2)
		usage();

	if (strlen(argv[1])>1 && argv[1][0] == '-' && argv[1][1] == 's')
		opt = 2;

	args = argc - opt - 1;

	if (args < 1 || args > 2)
		usage();

	proxy=argv[opt+1];
	ircad=argv[opt+2];
	srcad=argv[2];
	ircport = PORT;

	if (args == 1)
	{
		gethostname(host, sizeof(host));
		if ((he = gethostbyname(host)) == NULL)
		{
			perror("gethostbyname()");
			exit(-1);
		}
	}
	else
	{
		if ( (tmpport=index(ircad, ':')) != NULL)
		{
			ircport=atoi(tmpport+1);
			*tmpport='\0';
		}

		if ((he = gethostbyname(ircad)) != NULL) 
		{
			bcopy(he->h_addr, (char *)&dst.sin_addr, he->h_length);
		}
		else
		{
			if ((dst.sin_addr.s_addr = inet_addr(ircad)) < 0) 
				{
				perror("gethostbyname()");
				exit(-1);
				}
		}
	}

	if (!he)
		usage();

	SOCK5b[4]=he->h_addr_list[0][0];
	SOCK5b[5]=he->h_addr_list[0][1];
	SOCK5b[6]=he->h_addr_list[0][2];
	SOCK5b[7]=he->h_addr_list[0][3];
	SOCK5b[8]=(ircport & 0xff00) >> 8;
	SOCK5b[9]=(ircport & 0x00ff);

	SOCK4[2]=(ircport & 0xff00) >> 8;
	SOCK4[3]=(ircport & 0x00ff);
	SOCK4[4]=he->h_addr_list[0][0];
	SOCK4[5]=he->h_addr_list[0][1];
	SOCK4[6]=he->h_addr_list[0][2];
	SOCK4[7]=he->h_addr_list[0][3];

	if ( (tmpport=index(proxy, ':')) != NULL)
	{
		proxyport=atoi(tmpport+1);
		*tmpport='\0';
	}
	else
	{
		proxyport = 1080;
	}

	bzero((char *)&buf,sizeof(buf));
	bzero((char *)&buf5,sizeof(buf5));
	bzero((char *)&dst,sizeof(dst));

	dst.sin_family = AF_INET;
	dst.sin_addr.s_addr=inet_addr(proxy);
	dst.sin_port = htons(proxyport);

	if ((he = gethostbyname(proxy)) != NULL) {
		bcopy(he->h_addr, (char *)&dst.sin_addr, he->h_length);
	} else {
		if ((dst.sin_addr.s_addr = inet_addr(proxy)) < 0) {
			perror("proxy gethostbyname()");
			exit(-1);
		}
	}

	if (opt == 2)
	{
		bzero((char *)&src,sizeof(src));
		src.sin_family = AF_INET;
		src.sin_addr.s_addr=inet_addr(srcad);
		/* src.sin_port = htons(proxyport); */
		if ((he = gethostbyname(srcad)) != NULL) {
			bcopy(he->h_addr, (char *)&src.sin_addr, he->h_length);
		} else {
			if ((src.sin_addr.s_addr = inet_addr(srcad)) < 0) {
				perror("srcad gethostbyname()");
				exit(-1);
			}
		}
	}

	printf("Checking %s:%d for open proxy...\n",proxy,proxyport);

	/* socks4 */

	if (!(sock = socket(AF_INET, SOCK_STREAM, 0))) {
		perror("socket");
		exit(-1);
	}
	if (opt==2 && bind(sock, (struct sockaddr *)&src, 16) < 0) {
		perror("bind");
		exit(-1);
	}
	if (connect(sock, (struct sockaddr *)&dst, 16) < 0) {
		perror("connect");
		close(sock);
		exit(-1);
	}
	if (send(sock,SOCK4,sizeof(SOCK4)-1,0) < 0) {
		perror("send");
		exit(-1);
	}
	rlen = recv(sock,buf,sizeof(buf),0);
	if (rlen<0) {
		perror("recv");
		exit(-1);
	}
#ifdef DEBUG
	printf("SOCKS4\n");
	printf("<<<: ");
	for (k=0;k<sizeof(SOCK4)-1;k++)
		printf("%02d ", (unsigned char) SOCK4[k]);
	printf("\n");
	printf(">>>: ");
	if (rlen>0)
		for (k=0;k<rlen;k++)
			printf("%02d ", (unsigned char) buf[k]);
	else
		printf(" 0 bytes");
	printf("\n");
#endif
	printf("socks4 ");
	if (rlen>0)
	{
		if (buf[0]==0)
		{
			if (buf[1] < 90 || buf[1] > 93)
			{
				printf("closed (unexpected reply [%u,%u]).",
					buf[0], buf[1]);
			}
			else
			{
				if (buf[1]==90)
				{
					printf("open!");
				}
				else
				{
					if (buf[1] == 91)
					{
						printf("closed.");
					}
					else
					{
						printf("open if paranoid.");
					}
				}
			}
		}
		else
		{
			printf("closed: unexpected reply [%u,%u].\n",
				buf[0], buf[1]);
			printf("(iauth should be changed to consider such"
				" open, if paranoid)");
		}
	}
	else
	{
		printf("presumably no proxy.");
	}
	printf("\n");
	close(sock);

	if (!(sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) {
		perror("sock: socket");
		exit(-1);
	}
	if (opt==2 && bind(sock, (struct sockaddr *)&src, 16) < 0) {
		perror("bind");
		exit(-1);
	}
	if (connect(sock, (struct sockaddr *)&dst, 16) < 0) {
		perror("sock: connect");
		close(sock);
		exit(-1);
	}
	if (send(sock,SOCK5a,sizeof(SOCK5a)-1,0) < 0) { 
		perror("sock: send a");
		exit(-1);
	}
	rlen=recv(sock,buf5,sizeof(buf5),0);
	if (rlen<0) {
		perror("recv");
		exit(-1);
	}
#ifdef DEBUG
	printf("SOCKS5\n");
	printf("<<<: ");
	for (k=0;k<sizeof(SOCK5a)-1; k++)
		printf("%02d ", (unsigned char) SOCK5a[k]);
	printf("\n");
	printf(">>>: ");
	if (rlen>0)
	for (k=0;k<rlen;k++)
		printf("%02d ", (unsigned char) buf5[k]);
	else	
		printf("0 bytes.");
	printf("\n");
#endif
	if (buf5[0]==5)
	  if (buf5[1]==0) {
		bzero((char *)&buf5,sizeof(buf5));
		printf("socks5 open if \"careful\" option is not set...\n");
		if (send(sock,SOCK5b,sizeof(SOCK5b)-1,0) < 0) { 
			perror("sock: send b");
			exit(-1);
		} 
		rlen = recv(sock,buf5,sizeof(buf5),0);
		if (rlen < 0) {
			printf("failed.");
			perror("recv");
			exit(-1);
		}
#ifdef DEBUG
		printf("<<<: ");
		for (k=0;k<sizeof(SOCK5b)-1;k++)
			printf("%02d ",(unsigned char) SOCK5b[k]);
		printf("\n");
		printf(">>>: ");
		for (k=0;k<rlen;k++)
			printf("%02d ", (unsigned char) buf5[k]);
		if (rlen=0)
			printf("0 bytes");
		printf("\n");
#endif
		printf("socks5 ");
		if (buf5[0]==5) {
			if (buf5[1]==0)
				printf("damn open, whatever options you set!");
			else if (buf5[1]==2)
			    printf("closed if careful (forbidden by ruleset).");
			else
				printf("network? error [%u,%u])", buf5[0], buf5[1]);
		}
		else
			printf("closed (unexpected reply [%u,%u])", buf5[0], buf5[1]);

	} else {
		printf("socks5 ");
		switch ((unsigned char) buf5[1]) {
			case 1: printf("open if paranoid (wrong gssapi)"); break;
			case 2: printf("open if paranoid (wrong user/pass)"); break;
			case 255: printf("closed (no acceptable methods)"); break;
			default: printf("closed (unexpected reply [%u,%u])",buf5[0], buf5[1]);
		}
	}
	else
		printf("socks5 closed (unexpected reply [%u,%u])", buf5[0], buf5[1]);
	printf("\n");
	close(sock);
	exit(0);
}

