/* Link Finder -- version 1.00b
 * by Crypt Keeper [ckeeper@axiom.access.one.net]
 *
 * Polls all IRC servers on an IRC network to find those that you are
 * allowed to connect to.
 *
 * This program is free software.  It is distributed as-is with no
 * warranties of any kind, and may be freely copied or modified as long
 * as you give me credit. :) */

/* Revision history:
 *    1.00b - First release */

/* Here's a bit of a FAQ.. a short one:
 *
 * Q: Why does it hang when it's getting the list of server names?
 * A: This can be slow.  When you run link finder, try to pick a fast
 *    server to get the list from.  Even then, this can take a while
 *    and netsplits can interfere.  Try different servers, or try re-running
 *    the program.  Remember that you can write your list to a logfile, so
 *    you really only need this once.  The list of servers that you can
 *    connect to is not going to change very often.  Also, if you aren't
 *    worried about polling _every_ server you could try -notunnel.
 *
 * Q: I use -fork and I don't see anything
 * A: -fork allows link finder to run in the background, silently, and
 *    log to a file.  This means that you could even leave it running on
 *    a system and log out.  -fork requires either (or both) -l and -a to
 *    be specified.  The program does not fork until it starts polling
 *    servers.
 *
 * Q: It won't compile!
 * A: Port it to whatever you're on or find someone who can, and mail me
 *    the ported copy, or a diff, or something. */

/* To compile:
 *
 * Linux (and probably most Posix-compliant systems with gcc):
 *   gcc -O2 -fomit-frame-pointer -s -pipe -o linkfind linkfind.c
 *
 * Other systems try:
 *   gcc -O -s -o linkfind linkfind.c
 *   gcc -o linkfind linkfind.c
 *   cc -o linkfind linkfind.c
 *
 * This (or some variation of it) might work on HP/UX:
 *   cc +O3 -Aa -s -D__HPUX__ -o linkfind linkfind.c
 *
 * On some systems you may need to compile with one or more of these:
 *   -lsocket -lresolv -lucb -lnsl
 *
 * You can ignore warnings as long as you get a working binary.
 *
 * I can only personally test this on Linux, so I can't guarantee it'll
 * compile on anything else.  If someone else wants to add some more
 * defines to get this to compile on other systems, be my guest.  If
 * you do, I'd appreciate if you mail me the changes so I can release a
 * new version. */

/* Comment this out if your system doesn't have strerror() */
#define HAS_STRERROR

#ifdef __HPUX__
#define _INCLUDE_HPUX_SOURCE
#define _INCLUDE_XOPEN_SOURCE
#define _INCLUDE_POSIX_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>

/* Prototypes */
void do_help(char *whoami);
int connectout(char *hname,int portnum,int timeout);
char *my_strerror(int errornum);
void access_denied(char *servername,char *reason,char *tag);
void access_accepted(char *servername);
void process_motd(char *motd_line,char *servername);
void parse_server_line(int plug,char *servername,char *line_from_server);
char *randjunk(int len);
void try_server(char *servername);
int get_links(char *servername,int serverport);
void signal_handler(int signum);
void main(int argc,char *argv[]);
int _wild_match(unsigned char *mask,unsigned char *string);
int wild_match(char *pattern,char *str);

/* Global variable init */
int motd_pointer = 0;                         /* Used in process_motd() */
struct configstruct {
	int serverport;                    /* Port for test connections */
	int connect_timeout;      /* Timeout for test connections (sec) */
	char tunnel_foreign;                  /* Tunnel *.fi type stuff */
	char do_motd_writes;                        /* Write motd files */
	FILE *logfile;                                       /* Logfile */
	char logging;                                       /* Logging? */
	FILE *rawfile;                          /* Raw server list file */
	char raw_logging;                   /* Writing raw server list? */
	char run_background;                      /* Run in background? */
} my_config;
struct servstats {
	int naccepted;
	int ndenied;
	int nerror;                        /* For statistics at the end */
} my_stats;
char serverlist[512][128];           /* List of all server names on net */
char *my_username;        /* Whatever your username is determined to be */
unsigned char no_show_error = 0;      /* Used in try_server and parsing */

#ifndef errno
extern int errno;
#endif

/* Macro that sends text to a server */
#define outserv(p,s) send(p,s,strlen(s),0)

/* Help message */
void do_help(char *whoami)
{
	printf("Usage: %s [options] server[:port#]\n\n",whoami);
	printf("Available options:\n");
	printf("   -l filename                   - Output all findings to log file\n");
	printf("   -a filename                   - Output plain list of servers\n");
	printf("                                   that accept you to a file\n");
	printf("   -[no]tunnel    [default=on]   - Tunnel into *.domain links\n");
	printf("   -[no]motd      [default=off]  - Write working server MOTDs to\n");
	printf("                                   files named after each server\n");
	printf("   -[no]fork      [default=off]  - Run in background (-l and/or -a reqd)\n");
	printf("   -t timeout     [default=60]   - Set trial connection timeout (secs)\n");
	printf("   -p port#       [default=6667] - Set trial connection port number\n\n");
	printf("The server is any server you have access to use.  If no port is specified\n");
	printf("port 6667 is used.  The -a and -l options may be used together.\n");
	exit(0);
}

/* Connects to a remote host on the net */
int connectout(char *hname,int portnum,int timeout)
{
	struct hostent *host_name;
	struct sockaddr_in socketname;
	int plug;
	
	if ((plug = socket(AF_INET,SOCK_STREAM,0)) < 0)
		return -1;
	socketname.sin_family = AF_INET;
	if ((host_name = gethostbyname(hname)) == (struct hostent *)NULL)
		return -2;
	socketname.sin_port = htons(portnum);
	memcpy(&socketname.sin_addr,host_name->h_addr,host_name->h_length);
	alarm(timeout);
	if ((connect(plug,(struct sockaddr *)&socketname,sizeof(socketname))) < 0)
	{
		if (errno == EINTR)
			errno = ETIMEDOUT;
		alarm(0);
		return -3;
	}
	alarm(0);
	return plug;
}

/* To make it easy to compile on systems without strerror() */
char *my_strerror(int errornum)
{
#ifdef HAS_STRERROR
	return strerror(errornum);
#else
#ifdef sys_errlist
	return sys_errlist[errornum];
#else
	return "Unknown error";
#endif
#endif
}

/* These two do the logging and/or output for each test */
void access_denied(char *servername,char *reason,char *tag)
{
	char buff[128];
	
	if (strlen(tag) == 6)
		sprintf(buff,"[%s]   %s (%s)\n",tag,servername,reason);
	if (strlen(tag) == 5)
		sprintf(buff,"[%s]    %s (%s)\n",tag,servername,reason);

	if (my_config.logging) {
		fprintf(my_config.logfile,buff);
		fflush(my_config.logfile);
	}
	if (!my_config.run_background) {
		printf(buff);
		fflush(stdout);
	}
}
void access_accepted(char *servername)
{
	if (my_config.logging) {
		fprintf(my_config.logfile,"[ACCEPTED] %s\n",servername);
		fflush(my_config.logfile);
	}
	if (my_config.raw_logging) {
		fprintf(my_config.rawfile,"%s\n",servername);
		fflush(my_config.rawfile);
	}
	if (!my_config.run_background) {
		printf("[ACCEPTED] %s\n",servername);
		fflush(stdout);
	}
	my_stats.naccepted++;
}

/* This procedure handles motd files
 * if motd_line==NULL and servername==name, write file.. else add to
 * motd queue */
void process_motd(char *motd_line,char *servername)
{
	static char motd[300][255];
	char *tmp;
	int i;
	FILE *motd_file;
	
	if (my_config.do_motd_writes) {
		if (motd_line) {
			tmp = motd_line; tmp++;
			while (*tmp != ':') tmp++;
			tmp++;
			strcpy(motd[motd_pointer++],tmp);
		} else if (servername) {
			if ((motd_file = fopen(servername,"w+")) == (FILE *)NULL) {
				fprintf(stderr,"Could not open file %s to write motd: %s\n",servername,my_strerror(errno));
				fflush(stderr);
			} else {
				for(i=0;i<motd_pointer;i++)
					fprintf(motd_file,"%s\n",motd[i]);
				fclose(motd_file);
				motd_pointer = 0;
			}
		}
	}
}

/* This parses a line of text from a server we are checking out.  It does
 * not handle data from the server while we're getting links... that's
 * another procedure.  The 'no_show_error' global variable is to make sure
 * we don't print 'banned from server' and then print the 'k-lined' error
 * message too. */
void parse_server_line(int plug,char *servername,char *line_from_server)
{
	char *tmp,buff[512];
	
	if (wild_match("% 465 *",line_from_server)) {
		access_denied(servername,"Banned from server","Denied");
		no_show_error = 1;
		my_stats.ndenied++;
		return;
	} else if ((wild_match("ERROR :*",line_from_server))&&(!no_show_error)) {
		strcpy(buff,line_from_server);
		if (strtok(buff,"()") == NULL)
			access_denied(servername,"Unknown Error","Denied");
		else {
			if ((tmp = strtok(NULL,"()")) == NULL)
				access_denied(servername,"Unknown Error","Denied");
			else
				access_denied(servername,tmp,"Denied");
		}
		my_stats.ndenied++;
		return;
	} else if (wild_match("% 001 *",line_from_server)) {
		access_accepted(servername);
		outserv(plug,"QUIT :Leaving\n");
		no_show_error = 1;
		return;
	} else if ((wild_match("% 375 *",line_from_server))||(wild_match("% 372 *",line_from_server))) {
		process_motd(line_from_server,(char *)0);
		return;
	} else if (wild_match("% 376 *",line_from_server)) {
		process_motd((char *)0,servername);
		return;
	}
}

/* Returns a random junk string, no mixed case since that is a clonebot
 * giveaway */
char *randjunk(int len)
{
	int i;
	static char junk[64];
	char junkpick[26] = { 'a','b','c','d','e','f','g','h','i','j','k','l',
	                      'm','n','o','p','q','r','s','t','u','v','w','x',
	                      'y','z' };

	for(i=0;i<len;i++)
		junk[i] = junkpick[rand() % 26];
	junk[len] = '\0';

	return (char *)&junk;
}

/* Checks out a server */
void try_server(char *servername)
{
	int plug,nread,i,lbptr = 0;
	char inbuf[1024],linebuf[1024];
	
	no_show_error = 0;
	if ((plug = connectout(servername,my_config.serverport,my_config.connect_timeout)) > 0) {
		sprintf(linebuf,"USER %s . . :%s %s\n",my_username,randjunk(rand() % 15),randjunk(rand() % 15));
		outserv(plug,linebuf);
		sprintf(linebuf,"NICK %s\n",randjunk(7));
		outserv(plug,linebuf);
		alarm(my_config.connect_timeout);
		while ((nread = recv(plug,&inbuf,sizeof(inbuf),0)) > 0) {
			for(i=0;i<nread;i++) {
				if (inbuf[i] == '\n') {
					linebuf[lbptr] = '\0';
					parse_server_line(plug,servername,linebuf);
					lbptr = 0;
				} else linebuf[lbptr++] = inbuf[i];
			}
			alarm(my_config.connect_timeout);
		}
		if (errno == EINTR)
			access_denied(servername,"No response from server","Error");
		alarm(0);
		close(plug);
	} else {
		if (plug == -2)
			access_denied(servername,"Could not resolve hostname","Error");
		else
			access_denied(servername,my_strerror(errno),"Error");
		my_stats.nerror++;
	}
}
		
/* Gets the links list from a server, returns the number of links */
int get_links(char *servername,int serverport)
{
	int plug,nread,i,lcount = 0,tunnels_left = 0,lbptr = 0;
	char inbuf[1024],linebuf[1024],buff[128],*tmp,tunnels[64][128];
	
	printf("Connecting to %s (port %d)...",servername,serverport);
	fflush(stdout);

	if ((plug = connectout(servername,serverport,120)) < 1) {
		if (plug == -2)
			printf(" Could not resolve hostname\n");
		else
			printf(" %s\n",my_strerror(errno));
		exit(0);
	}

	printf("\nLogging in... ");
	fflush(stdout);
	
	sprintf(linebuf,"USER %s . . :%s %s\n",my_username,randjunk(rand() % 15),randjunk(rand() % 15));
	outserv(plug,linebuf);
	sprintf(linebuf,"NICK %s\n",randjunk(7));
	outserv(plug,linebuf);
	while ((nread = recv(plug,&inbuf,sizeof(inbuf),0)) > 0) {
		for (i=0;i<nread;i++) {
			if (inbuf[i] == '\n') {
				linebuf[lbptr] = '\0';
				if (wild_match("% 001 *",linebuf)) {
					outserv(plug,"LINKS\n");
					printf("Established! Retrieving server list...");
					fflush(stdout);
				} else if ((wild_match("% 365 *",linebuf))||(wild_match("% 402 *",linebuf))) {
					if (tunnels_left > 0) {
						printf(".[%s]",tunnels[tunnels_left-1]);
						fflush(stdout);
						sprintf(buff,"LINKS %s %s\n",tunnels[tunnels_left-1],tunnels[tunnels_left-1]);
						outserv(plug,buff);
						tunnels_left--;
					} else close(plug);
				} else if (wild_match("% 364 *",linebuf)) {
					if (strtok(linebuf," ") != (char *)NULL) {
						strtok(NULL," ");
						strtok(NULL," ");
						if ((tmp = strtok(NULL," ")) != (char *)NULL) {
							if (strchr(tmp,'*')) {
								if (my_config.tunnel_foreign)
									strcpy(tunnels[tunnels_left++],tmp);
							} else strcpy(serverlist[lcount++],tmp);
						}
					}
				} else if (!strncmp("PING",linebuf,4)) {
					close(plug);
					if (tunnels_left > 0) {
						printf("\n%d tunneling requests timed out",tunnels_left);
						fflush(stdout);
					}
				} else if (wild_match("% 465 *",linebuf)) { 
					printf("Banned from server\n");
					close(plug);
					exit(0);
				} else if (wild_match("ERROR :*",linebuf)) {
					printf("\n%s\n",linebuf);
					close(plug);
					exit(0);
				}
				lbptr = 0;
			} else linebuf[lbptr++] = inbuf[i];
		}
	}
	close(plug);
	
	printf("\n\n%d server names in list; beginning scan...\n",lcount);
	fflush(stdout);
	return lcount;
}

/* Exits gracefully when a signal is recieved */
void signal_handler(int signum)
{
	if (signum != SIGALRM) {
		if (signum == SIGINT)
			printf("\nSIGINT - CTRL+C pressed\n");
		else
			printf("\nLink Finder terminated\n");
		if (my_config.logging)
			fclose(my_config.logfile);
		if (my_config.raw_logging)
			fclose(my_config.rawfile);
		exit(0);
	}
	signal(signum,signal_handler);
}

void main(int argc,char *argv[])
{
	static struct passwd *my_pwent;
	int i,aval,numlinks,serverport = 6667;
	char buff[128],*tmp = (char *)0,*logfilename = (char *)0,*rawfilename = (char *)0,*servername = (char *)0;

	printf("Link Finder version 1.00b  (c)1996 by CKeeper [ckeeper@axiom.access.one.net]\nThis program is free software\n\n");
	fflush(stdout);
	
	if (argc < 2)
		do_help(argv[0]);

	/* Init stuff, defaults */
	srand(getpid());
	my_config.serverport = 6667;
	my_config.connect_timeout = 60;
	my_config.tunnel_foreign = 1;
	my_config.do_motd_writes = 0;
	my_config.logging = 0;
	my_config.raw_logging = 0;
	my_config.run_background = 0;
	servername = (char *)0;
	
	/* Set up signals */
	signal(SIGALRM,signal_handler);
	signal(SIGPIPE,SIG_IGN);
	signal(SIGTERM,signal_handler);
	signal(SIGQUIT,signal_handler);
	signal(SIGINT,signal_handler);

	/* Who am I? */
	if ((my_pwent = getpwuid(getuid())) == (struct passwd *)NULL) {
		fprintf(stderr,"Could not determine your username: %s\n",my_strerror(errno));
		exit(0);
	}
	my_username = my_pwent->pw_name;

	/* Get command line options (I'd like to use getopt(), but I think it
	 * isn't portable to some systems :P) */
	for (i=1;i<argc;i++) {
		aval = 0;
		if (*argv[i] == '-') {
			if (!strcmp("-motd",argv[i]))
				my_config.do_motd_writes = 1;
			else if (!strcmp("-nomotd",argv[i]))
				my_config.do_motd_writes = 0;
			else if (!strcmp("-tunnel",argv[i]))
				my_config.tunnel_foreign = 1;
			else if (!strcmp("-notunnel",argv[i]))
				my_config.tunnel_foreign = 0;
			else if (!strcmp("-fork",argv[i]))
				my_config.run_background = 1;
			else if (!strcmp("-nofork",argv[i]))
				my_config.run_background = 0;
			else {
				tmp = argv[i]; tmp++;
				while (*tmp)
				{
					switch (*tmp) {
						case 'l':
							if (i < argc) {
								my_config.logging = 1;
								logfilename = argv[i+aval+1];
								aval++;
							} else do_help(argv[0]);
							break;
						case 'a':
							if (i < argc) {
								my_config.raw_logging = 1;
								rawfilename = argv[i+aval+1];
								aval++;
							} else do_help(argv[0]);
							break;
						case 't':
							if (i < argc) {
								my_config.connect_timeout = atoi(argv[i+aval+1]);
								aval++;
							} else do_help(argv[0]);
							break;
						case 'p':
							if (i < argc) {
								my_config.serverport = atoi(argv[i+aval+1]);
								aval++;
							} else do_help(argv[0]);
							break;
						default:
							do_help(argv[0]);
					}
					tmp++;
				}
			}
		} else {
			if (strchr(argv[i],':')) {
				strcpy(buff,argv[i]);
				servername = strtok(buff,":");
				serverport = atoi(strtok(NULL,":"));
			} else servername = argv[i];
		}
		i=i+aval;
	}
	if (!servername)
		do_help(argv[0]);
	if (my_config.run_background) {
		if ((!my_config.logging)&&(!my_config.raw_logging)) {
			fprintf(stderr,"-fork requires some type of output file to be specified\n\n");
			do_help(argv[0]);
		}
	}

	/* Open any output files */
	if (my_config.logging) {
		if ((my_config.logfile = fopen(logfilename,"w+")) == (FILE *)NULL) {
			fprintf(stderr,"Error opening logfile: %s\n",my_strerror(errno));
			exit(0);
		} else printf("Opened %s for log output\n",logfilename);
	}
	if (my_config.raw_logging) {
		if ((my_config.rawfile = fopen(rawfilename,"w+")) == (FILE *)NULL) {
			fprintf(stderr,"Error opening accepted list output file: %s\n",my_strerror(errno));
			exit(0);
		} else printf("Opened %s for accepted server list output\n",rawfilename);
	}
	
	/* Zero stats */
	my_stats.naccepted = 0;
	my_stats.ndenied = 0;
	my_stats.nerror = 0;
	
	/* Get links list */
	numlinks = get_links(servername,serverport);
	
	/* Run in background if background option is set */
	if (my_config.run_background) {
		if ((i = fork()) > 0) {
			printf("Running in background as pid# %d\nType 'kill -TERM %d' to kill\n",i,i);
			exit(0);
		}
		if (i < 0) {
			fprintf(stderr,"Cannot fork!\n");
			exit(0);
		}
	}
	
	/* Try all the servers */
	for(i=0;i<numlinks;i++)
		try_server(serverlist[i]);
	
	if (!my_config.run_background)
		printf("\nTotal number tested:[%d]  Accepted:[%d]  Denied:[%d]  Error:[%d]\n",numlinks,my_stats.naccepted,my_stats.ndenied,my_stats.nerror);

	if (my_config.logging)
		fclose(my_config.logfile);
	if (my_config.raw_logging)
		fclose(my_config.rawfile);
	
	exit(0);
}

/* ------------------------------------------------------------------------ */

/* This is the wildcard match routine used in ircd and ircII... it makes the
 * parsing routines much less of a headache. (just like IRC script!) */

/*
 * Written By Douglas A. Lewis <dalewis@cs.Buffalo.EDU>
 *
 * This file is in the public domain.
 */

#define RETURN_FALSE -1
#define RETURN_TRUE count

unsigned char lower_tab[256] = 
{
  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
 64, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
112,113,114,115,116,117,118,119,120,121,122, 91, 92, 93, 94, 95,
 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,
112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
144,145,145,147,148,149,150,151,152,153,154,155,156,157,158,159,
160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 };

#undef tolower
#define tolower(x) lower_tab[x]

int _wild_match(unsigned char *mask,unsigned char *string)
{
	unsigned char *m = mask,*n = string,*ma = NULL,*na = NULL,*mp = NULL,*np = NULL;
	int just = 0,pcount = 0,acount = 0,count = 0;

	for (;;)
	{
		if (*m == '*')
		{
			ma = ++m;
			na = n;
			just = 1;
			mp = NULL;
			acount = count;
		}
		else if (*m == '%')
		{
			mp = ++m;
			np = n;
			pcount = count;
		}
		else if (*m == '?')
		{
			m++;
			if (!*n++)
				return RETURN_FALSE;
		}
		else
		{
			if (*m == '\\')
			{
				m++;
				if (!*m)
					return RETURN_FALSE;
			}
			if (!*m)
			{
				if (!*n)
					return RETURN_TRUE;
				if (just)
					return RETURN_TRUE;
				just = 0;
				goto not_matched;
			}
			just = 0;
			if (tolower(*m) == tolower(*n))
			{
				m++;
				if (*n == ' ')
					mp = NULL;
				count++;
				n++;
			}
			else
			{

	not_matched:
				if (!*n)
					return RETURN_FALSE;
				if (mp)
				{
					m = mp;
					if (*np == ' ')
					{
						mp = NULL;
						goto check_percent;
					}
					n = ++np;
					count = pcount;
				}
				else
	check_percent:

				if (ma)
				{
					m = ma;
					n = ++na;
					count = acount;
				}
				else
					return RETURN_FALSE;
			}
		}
	}
}

int wild_match(char *pattern,char *str)
{
	/* assuming a -1 return of false */
	return _wild_match((u_char *)pattern,(u_char *)str) + 1;
}

/* End stolen code -------------------------------------------------------- */
