/* $Id: mothra.c,v 1.190 2002/03/04 20:50:38 dmuz Exp $ */

/* AngryPacket Security - http://sec.angrypacket.com */
/* mothra - a monstrous yet graceful banner graber */

/* this is mothra version 1 */

/* feel free to send any bugfixes, patches, comments, suggestions, etc */

/*
            o  o
      xo__  \__/  __ox
      \_  \ (__) /  _/
        \_-[xxxx]-_/
          \ -  - /
        _/ - -- - \_
      _/   { AP }   \_
     /----/\{  }/\----\
             \/
*/

/* by dmuz - dmuz@angrypacket.com */
/* with massive amounts of leet code from methodic */
/* also some "pointers" by freek... heh */

/* mothra was developed on OpenBSD for *BSD. It's been tested on OpenBSD,
 * NetBSD and FreeBSD. It should work on most Linux distributions too. */

/* Add -DDEBUG for debug mode (not recommened) */

#include <stdio.h>

#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/fcntl.h>
#include <unistd.h>

#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define IPSIZE 17 /* enough to hold 12 digits, 4 dots, and a NULL */
#define MAX_BANNER_SIZE	512
#define MAX_PORTS	65535

#define FTP		21
#define TELNET	23
#define SMTP	25
#define TIME	37
#define HTTP	80
#define POP3PW	106
#define POP3	110
#define IDENT	113
#define IMAP4	143

#define RED		"\E[1;31;40m"
#define YELLOW	"\E[1;33;40m"
#define WHITE	"\E[0;38;40m"

#define ANGRYPACKET_OWNZ_YOU	1


/* global buffer to store data */
char buf[MAX_BANNER_SIZE+1];
/* keep track of connect state */
int state=0;
/* recv() timeout variable, default is 15 seconds */
int u_timeout=15;
/* verbose output variable */
int verbose=0;
extern int errno;

/* usage */ //XXX add EX: or example usages here
void usage(void) {
    fprintf(stderr,"\nUSAGE: mothra -H <host_range> -h <host> -p <ports> -r <ports>" 
		" -t <timeout> -f <logfile> -cv\n");
    fprintf(stderr,"\t-H\t\thost IP range to scan\n");
    fprintf(stderr,"\t-h\t\tsingle hostname to scan\n");
    fprintf(stderr,"\t-p\t\tcomma seperated ports to grab\n");
    fprintf(stderr,"\t-r\t\trange of ports (ex. 1-10)\n");
    fprintf(stderr,"\t-t\t\ttimeout in seconds for a connection to a port\n");
    fprintf(stderr,"\t-f\t\tlogfile to record banners in\n");
    fprintf(stderr,"\t-c\t\tprint banners in color\n");
	fprintf(stderr,"\t-v\t\tverbose output\n\n");
    exit(0);
}

/* omg... ph33r!!! */
void ph33r(int color_out) {
	if(color_out) {
		fprintf(stderr,
		RED "\nmothra by dmuz and methodic - AngryPacket Security\n\n" WHITE );
	} else {
		fprintf(stderr,"\nmothra by dmuz and methodic - AngryPacket Security\n\n");
	}
};

/* timer signal function */
void catchsignal() {
	state = 1;
	if(verbose)
		printf("timed out.\n");
}

int parse_banner(char *banner, const char *delim) {
	int len;
	char *buf_p, *banner_p, *p;

	len = strlen(delim);

	banner_p = (char *)malloc(strlen(banner)+1);
	bzero(banner_p, strlen(banner)+1);

	for(buf_p = strtok(banner, "\n"); buf_p != NULL;) {
		p = strstr(buf_p, delim);
		if(p) {
			memmove(banner_p, (p+len), strlen(buf_p)-len);
		}
		buf_p = strtok(NULL, "\n");
	}
	if(banner_p) {
		strncpy(banner, banner_p, MAX_BANNER_SIZE);
	} else {
		strncpy(banner, "no banner available", MAX_BANNER_SIZE);
	}
	free(banner_p);

	return(0);
}

int do_port_hook(int port, int sock) {
	char *http_s = "HEAD / HTTP/1.0\n\n";
	char *identd_s = "VERSION\n";

	if(port == HTTP) {
		send(sock, http_s, strlen(http_s), 0);
	}

	if(port == IDENT) {
		send(sock, identd_s, strlen(identd_s), 0);
	}

	return(0);
}

/* preprocess data to port if we have a hook */
int process_port(int port, int sock) {
	int x, i=0, j=0, result=0;

	unsigned int y;
	unsigned long epoch = 2208988800ul;
	unsigned char tmpbuf[MAX_BANNER_SIZE+1], *p=NULL, obuf[4];
	time_t timedate;

	struct timeval timeout;
	fd_set fds;

	/* set timeout values for recv() */
	timeout.tv_sec = u_timeout;
	timeout.tv_usec = 0;
	FD_ZERO(&fds);
	FD_SET(sock, &fds);
	fcntl(sock, F_SETFL, O_NONBLOCK);
	select(sock+1, &fds, NULL, NULL, &timeout);

	if(port == FTP) {
		/* loop to strip out ftp "messages" */
		do {
			bzero(tmpbuf, MAX_BANNER_SIZE+1);
			result = recv(sock, tmpbuf, MAX_BANNER_SIZE, 0);
			if( result == -1 ) {
				if(verbose)
					printf("  * recv timed out for port %d, skipping\n", port);
				return(result);
			}
		}while(strstr(tmpbuf, "220-"));
		strncpy(buf, tmpbuf, MAX_BANNER_SIZE);
	}

	if(port == TELNET) {
		do {
			sleep(1); /* i cant fscking believe we need this */
			bzero(tmpbuf, MAX_BANNER_SIZE+1);
			result = recv(sock, tmpbuf, MAX_BANNER_SIZE, 0);
			if( result == -1 ) {
				if(verbose)
					printf("  * recv timed out for port %d, skipping\n", port);
				return(result);
			}
			for(i = 0; i < result; i++) {
				/* we need to check every 3rd response, ie 0, 3, 9, 12, etc */
				if((i % 3) == 0 && i > 0) {
#if DEBUG
					printf("recv(%3u, %3u, %3u) [num=%2d] [seq=%2d]\n",
								tmpbuf[i-3], tmpbuf[i-2], tmpbuf[i-1], ++j, i);
#endif
					if(tmpbuf[i-3] != 255) { /* not a telnet IAC */
						bzero(buf, MAX_BANNER_SIZE+1);
						i=0;
						for(j = 0; j < result; j++) {
#if DEBUG
							printf("%u,", tmpbuf[j]);
#endif
							/* skip any telnet commands we still may have in
							 * the buffer */
							if(tmpbuf[j] == 255) {
								j++; j++;
							} else if(tmpbuf[j] != 0 && tmpbuf[j] != 13) {
								buf[i] = tmpbuf[j];
								i++;
							}
						}
						buf[i] = '\0';
						return(strlen(buf));
					}
				}
			}
			/* if we recieved a telnet command, respond to it */
			/* most of code ripped from netcat */
			p = tmpbuf;
			x = strlen(tmpbuf);
			while(x > 0) {
				obuf[0] = 255;
				p++; x--;
				if( (*p == 251) || (*p == 252))
					y = 254;
				if( (*p == 253) || (*p == 254))
					y = 252;
				if(y) {
					obuf[1] = y;
					p++; x--;
					obuf[2] = *p;
					send(sock, obuf, 3, 0);
					y = 0;
				}
				p++; x--;
			}
		}while(p != NULL);
#if DEBUG
		printf("telnet timed out.. cleaning up\n");
#endif
	}

	if(port == TIME) {
		bzero(tmpbuf, MAX_BANNER_SIZE+1);
		result = recv(sock, &timedate, sizeof(timedate), 0);
		if( result == -1 ) {
			if(verbose)
				printf("  * recv timed out for port %d, skipping\n", port);
			return(result);
		}
		timedate = ntohl((u_long)timedate) - epoch;
		snprintf(buf, MAX_BANNER_SIZE, "%s", ctime(&timedate));
	}

	if(result == 0) {
		bzero(buf, MAX_BANNER_SIZE+1);
		result = recv(sock, buf, MAX_BANNER_SIZE, 0);
		if( result == -1 ) {
			if(verbose)
				printf("  * recv timed out for port %d, skipping\n", port);
		}
	}

	return result;
}


/* post process a banner if we want */
int post_process(int port, char *banner) {
	int i=1;
	char *banner_p;

	banner_p = (char *)malloc(strlen(banner)+1);
	bzero(banner_p, strlen(banner)+1);

	if(port == FTP) {
		if(strstr(banner, "220 "))
			parse_banner(banner, "220 ");
	}

	if(port == SMTP) {
		parse_banner(banner, "220 ");
	}

	if(port == HTTP) {
		parse_banner(banner, "Server: ");
	}

	if(port == POP3PW) {
		parse_banner(banner, "200 ");
	}

	if(port == POP3) {
		parse_banner(banner, "+OK ");
	}

	if(port == IMAP4) {
		parse_banner(banner, "* OK ");
	}

	/* chop off any newline chars */
	i = (strlen(banner)) - 1 ;
	while(banner[i] == '\r' || banner[i] == '\n') {
		banner[i] = '\0';
		i--;
	}
	
	free(banner_p);

	return 0;
}

/* record open port banners to a file */
void write_log(int port, char *logfile, char *banner) {
	FILE *log_p;

	if ((log_p = fopen(logfile, "a+")) == NULL) {
		fprintf(stderr,"\ncould not open log file %s\n", logfile);
	} else {
		fprintf(log_p, "port %i: %s\n", port, banner);
		fclose(log_p);
	}
}

/* like, get some banners brah! most excellent! */
int main(int argc, char *argv[]) {
	/* network vars */
	int sock; 
	char *hostname=NULL, tmphost[IPSIZE];
	char *host_r[5];
	int start_addr=0, end_addr=0;
	int num_hosts=0;
	struct sockaddr_in s; /* inet socket address structure */
	struct hostent *host_ent; /* host info structure */
	/* var args */
	int ch;
	char *ap;
	int start_port=0;
	int ports[MAX_PORTS+1];
	int range[3];
	int i=0, nports =0;
	/* extra variables */
	int color_out=0;
	int result=0;
	/* connect() timeout struct */
	struct itimerval itv;
	/* signal structure */
	struct sigaction sig;
	/* file shit */
	FILE *log_p;
	char *logfile = NULL;

	bzero(ports, MAX_PORTS+1);
	bzero(range, 4);
	/* args */
	while ((ch = getopt(argc, argv, "h:H:p:r:t:f:cv")) != -1) {
		switch (ch) {
		case 'H':
			/* split the dotted quad */
			ap = strtok(optarg, ".");
			for(i = 0; i != 4; i++) {

				if(ap == NULL) {
					fprintf(stderr,"\nSyntax error in host range argument.\n");
					usage();
				}

				host_r[i] = ap;
				ap = strtok(NULL, ".");
			}

			/* split the range argument */
			ap = strtok(host_r[3], "-");
			for(i = 0; i != 2; i++) {
				host_r[4] = ap;
				if(ap == NULL) {
					fprintf(stderr,"\nSyntax error in host range argument.\n");
					usage();
				}
				ap = strtok(NULL, "-");
			}

			/* test each of member the dotted quad for correctness */
			for(i = 0; i != 5; i++) {
				if (atoi(host_r[3]) >= atoi(host_r[4])) {
					fprintf(stderr,"\nSyntax error in host range argument.\n");
					usage();
				}
				if (host_r[i] == NULL || atoi(host_r[i]) > 255 || atoi(host_r[i]) < 0) {
					fprintf(stderr,"\nSyntax error in host range argument.\n");
					usage();
				}
			}

			start_addr = atoi(host_r[3]);
			end_addr = atoi(host_r[4]);
			num_hosts = end_addr - start_addr;

			break;
		case 'h':
			hostname = optarg;
			break;
		case 'p':
			ap = strtok(optarg, ",");

			for(i = 0; ap; i++) {
				ports[i] = atoi(ap);
				if((ports[i] == 0) || (ports[i] > 65535) || (ports[i] < 0)) {
					fprintf(stderr,"\nSyntax error in port(s) list argument.\n");
					usage();
				}
				ap = strtok(NULL, ",");
			}

			nports = i; /* number of ports to check */
			break;
		case 'r':
			ap = strtok(optarg, "-");
			for(i = 0; i != 2; i++) {
				if(ap == NULL) {
					fprintf(stderr,"\nSyntax error in port range argument.\n");
					usage();
				}
				range[i] = atoi(ap);
				if(range[i] == 0) {
					fprintf(stderr,"\nSyntax error in port range argument.\n");
					usage();
				}
				ap = strtok(NULL, "-");
			}
			if(range[0] >= range[1]) {
				fprintf(stderr,"\nSyntax error in port range argument.\n");
				usage();
			}
			start_port=range[0];
			break;
		case 't':
			u_timeout = atoi(optarg);
			break;
		case 'f':
			logfile = optarg;
			break;
		case 'c':
			color_out = 1;
			break;
		case 'v':
			verbose = 1;
			break;
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;

	/* -p and -r can't both be specified */
	if(range[0] && nports) {
		printf("\n-p and -r are mutually exclusive dude\n");
		usage();
	} 

	/* build our ports[] array if we are using a range */
	if(range[0]) {
		for(i = range[0]; i < range[1]+1; i++) {
			ports[i] = i; 
		}
		nports = i;
		start_port = range[0];
	} else {
		start_port = 0;
	}

	// if(!hostname || !nports) {
	if(!nports) {
		usage();
	}

	/* print out our mad greetz */
	ph33r(color_out);

	num_hosts++;

	while(num_hosts > 0) {
		if(start_addr && end_addr) {
			/* build our hostname */
			snprintf(tmphost, IPSIZE, "%s.%s.%s.%d", host_r[0], host_r[1], host_r[2], start_addr);
			hostname = (char *)malloc(IPSIZE);
			strncpy(hostname, tmphost, IPSIZE);
			start_addr++;
		}

		/* attempt to open the logfile */
		if(logfile) {
			if((log_p = fopen(logfile, "a+")) == NULL) {
				fprintf(stderr, "could not open log file %s\n", logfile);
			} else {
				fprintf(log_p, "-- - banner scan for [%s] - --\n", hostname);
				fclose(log_p);
			}
		}
		printf("-> starting banner scan for %s\n", hostname);

		/* zero socket structure */
		bzero(&s, sizeof(s));

		if((host_ent = gethostbyname(hostname)) == NULL) {
			fprintf(stderr, "  * unknown host: %s\n", hostname);
			break;
		} else {
			memcpy((char*)&s.sin_addr, host_ent->h_addr, host_ent->h_length);
		}


		if(start_port) {
			i = start_port;
		} else {
			i = 0;
		}
		for(; i < nports; i++) {
			/* protocol and port information */
			s.sin_family = AF_INET;
			s.sin_port = htons(ports[i]);
			/* setup the socket */
			sock = socket(AF_INET, SOCK_STREAM, 0);

			/* create timeout values for connect() */
			itv.it_value.tv_sec = u_timeout;
			itv.it_value.tv_usec = 0;
			itv.it_interval = itv.it_value;
			setitimer(ITIMER_REAL, &itv, NULL);

			sig.sa_handler = catchsignal;
			sigemptyset(&sig.sa_mask);
			sig.sa_flags = 0;

			sigaction(SIGALRM, &sig, 0);
		
			if(verbose)
				printf("-> connecting to port [%d] on %s.. ", ports[i], hostname);
			fflush(stdout);

			if(connect(sock, (struct sockaddr*)&s, sizeof(s)) == 0) {
				itv.it_value.tv_sec=0;
				itv.it_value.tv_usec=0;
				itv.it_interval=itv.it_value;
				setitimer(ITIMER_REAL,&itv,NULL);
			    if(verbose)
					printf("connected!\n");
				do_port_hook(ports[i], sock);
				if((result = process_port(ports[i], sock)) > 0){
					post_process(ports[i], buf);
					if(logfile) {
						write_log(ports[i], logfile, buf); 
					} else {
						if(color_out) {
							printf(YELLOW "port %d: %s\n" WHITE, ports[i], buf);
						} else {
			    	    	printf("port %d: %s\n", ports[i], buf);
						}
					}
				}
			} else if (state == 1) {
				/* timed out */
				state=0;
			} else {
				/* couldn't connect */
				if(verbose) {
					printf("connect failed.\n");
#if DEBUG
					printf("DEBUG ERROR: %d\n", errno);
#endif
				}
			}
		    close(sock);
		}

		if(logfile) {
			if((log_p = fopen(logfile, "a+")) == NULL) {
				fprintf(stderr, "could not open log file %s\n", logfile);
			} else {
				fprintf(log_p, "\n");
				fclose(log_p);
			}
		}

		printf("-> finished banner scan for %s\n", hostname);
		num_hosts--;

		if(start_addr && end_addr) {
			free(hostname);
		}
	}
	printf("-> visit http://sec.angrypacket.com for security tools and info\n");
	return EX_OK;
}

