/******************************************************************************
 * GYachI-Voiceclient
 * Copyright (C) 2006  Stefan Sikora <hoshy[at]schrauberstube.de>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *****************************************************************************/

/***********************
* Yahoo voice-protocol *
***********************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <gtk/gtk.h>
#include "support.h"				// for lookup_widget

#include "protocol.h"
#include "sound.h"

/* variables for debugger */
#define dbg_numbdisp 16
#define dbg_numboutp 10

/* variables for protocol */
#define YVserver "66.218.70.37"	// login-server v6.vc.scd.yahoo.com
#define YVtcpport 5001
#define YVudpport 5000

struct {
	char userlength;	// length of username
	char user[75];		// our nickname

	char roomlength;	// length of roomtype+roomname
	char roomtype[3];	// type of room: ch/ or me/
	char room[75];		// desired chatroom
	
	char seriallength;	// length of preserial+serial
	char preserial[2];	// "::" in regular rooms
	char serial[50];	// serial number of chatroom	

	char cookie[120];	// Yahoo cookie
	char vuser[4];		// assigned userid
} YVCconfig;

int logged_in = 0;
int tcpsocket = 0;
int udpsocket = 0;

int rtpsequence;
int rtptime;

int tcppacketlenr;
int tcppacketlens;
char udpserver[15];
struct sockaddr YVudpaddr;

//TODO: ingnore-status
typedef struct chatusers {
	unsigned char	voiceid[4];
	unsigned char	nickname[75];
	unsigned char	alias[75];
	int		ignored;
	GtkTreeIter	iter;
	int     	i;
} CHATUSER;

#define max_roommembers 100
struct chatusers roommembers[max_roommembers];
unsigned char actualvoice[4];			// who is speaking right now ?
unsigned char displvoice[4];			// who is displayed actually ?

struct aliasmap {
	char nickname[75];
	char alias[75];
};

struct aliasmap nickaliasmap[max_roommembers];
int numaliases = 0;

int playfirstpacket = 1;
pthread_t tcp_tid = 0;
pthread_t udp_tid = 0;
int tcp_thread_exit = 0;
int udp_thread_exit = 0;

/**********************
 * Debugging routines *
 *********************/

/* displays data in a hexmonitor-like way on stderr */
void dbg_data_show(unsigned char *ptr, int length) {
	int totlines, linerest, acline, acrow;
	unsigned char *ptr2;

	if (debugger) {
		ptr2 = ptr;

		totlines = length / dbg_numbdisp;
		linerest = length % dbg_numbdisp;

		// full lines
		for(acline=0; acline<totlines; acline++) {
	        	for(acrow=0; acrow<dbg_numbdisp; acrow++) fprintf(stderr,"%02x ", *ptr++);
			for(acrow=0; acrow<dbg_numbdisp; acrow++) {
				if (*ptr2 < 0x21 || (*ptr2 >= 0x80 && *ptr2 < 0xa1)) {
				  ptr2++; 
				  fprintf(stderr, ".");
				}
				else fprintf(stderr, "%1c", *ptr2++);
			}
			fprintf(stderr,"\n");
		}

		// rest of a line
		for(acrow=0; acrow<linerest; acrow++) fprintf(stderr, "%02x ", *ptr++);
		for(acrow=0; acrow<(dbg_numbdisp-linerest)*3; acrow++) fprintf(stderr, " ");
		for(acrow=0; acrow<linerest; acrow++) {
	        	if (*ptr2 < 0x21 || (*ptr2 >= 0x80 && *ptr2 < 0xa1)) {
				ptr2++; 
				fprintf(stderr, ".");
			}
			else fprintf(stderr, "%1c", *ptr2++);
		}
		fprintf(stderr,"\n");        
	}
}


/* prints out data in a C-source-usable way on stderr */
void dbg_data_Coutput(unsigned char *ptr, int length) {
	int totlines, linerest, acline, acrow;

	totlines = length / dbg_numboutp;
	linerest = length % dbg_numboutp;

	// full lines
	for(acline=0; acline<totlines; acline++) {
        	fprintf(stderr, "\"");
		for(acrow=0; acrow<dbg_numbdisp; acrow++) fprintf(stderr,"\\x%02x", *ptr++);
		fprintf(stderr,"\"\n");
	}

	// rest of a line
	fprintf(stderr, "\"");
	for(acrow=0; acrow<linerest; acrow++) fprintf(stderr, "\\x%02x", *ptr++);
	fprintf(stderr,"\"\n");        
}


/* prints out a message to stderr */
void dbg_print(char *message, char *arg) {
	if (debugger) fprintf(stderr, message, arg);
}


/**********************
 * protocol functions *
 *********************/

/* read configuration file created by the chatclient */
int ReadConfiguration() {
	char *user="user";
	char conf_file[72];
	FILE *cf;
	char buf[512];
	GtkWidget *label;
		
	if (getenv("USER")) user=getenv("USER");
	snprintf(conf_file, 70, "/tmp/pyvoice_chat_start_%s", user);
	strdup(conf_file);
	
	if (!(cf = fopen(conf_file, "rb"))) {
		fprintf(stderr, "Could not open %s\n", conf_file);
		return(1);
	}
	
	memset(buf, 0, sizeof(buf));
	fread(buf, 1, 512, cf);
	fclose(cf);
	
	memset(&YVCconfig.user, 0, sizeof(YVCconfig));
	strncpy(YVCconfig.user, (char *)strtok((char*)&buf, "\n"), sizeof(YVCconfig.user));
	strncpy(YVCconfig.room, (char *)strtok(NULL, "\n"), sizeof(YVCconfig.room));
	strncpy(YVCconfig.cookie, (char *)strtok(NULL, "\n"), sizeof(YVCconfig.cookie));
	strncpy(YVCconfig.serial, (char *)strtok(NULL, "\n"), sizeof(YVCconfig.serial));

	if (!strcmp(YVCconfig.serial, "[PYCONFERENCE]")) memcpy(&YVCconfig.roomtype, "me/", 3);
	else memcpy(&YVCconfig.roomtype, "ch/", 3);

	YVCconfig.roomlength = strlen(YVCconfig.room)+sizeof(YVCconfig.roomtype);	
	YVCconfig.userlength = strlen(YVCconfig.user);
	YVCconfig.seriallength = strlen(YVCconfig.serial)+sizeof(YVCconfig.preserial);

	label = lookup_widget(gyv_mainwindow, "gyv_label_room");
	gtk_label_set_text(GTK_LABEL(label), YVCconfig.room);
	gtk_widget_queue_draw(label);

	return(0);	
}


int sort_aliasmap(const void *a, const void *b)
{
	return (strcasecmp(((struct aliasmap *)a)->alias, ((struct aliasmap *)b)->alias));
}

/* read aliasmap from chatclient */
int ReadAliasMap() {
	char *user="user";
	char conf_file[72];
	int fd;
	struct stat st_buf;
	char *file, *ptr, *end_ptr;
	int len;
	int rv;

	if (getenv("USER")) user=getenv("USER");
	snprintf(conf_file, 70, "/tmp/pyvoice_aliases_%s", user);
	strdup(conf_file);

	numaliases = 0;
	memset(&nickaliasmap, 0, sizeof(nickaliasmap));

	if (!(fd = open(conf_file,O_RDONLY))) {
		fprintf(stderr, "Could not open %s\n", conf_file);
		return(1);
	}

	if (fstat(fd, &st_buf)) {
		rv=errno;
		fprintf(stderr, "Could not stat %s\n", conf_file);
		fprintf(stderr, "error: %s\n", strerror(rv)); 
		return(1);
	}

	file = malloc(st_buf.st_size+1);
	rv = read(fd, file, st_buf.st_size);
	if (rv <= 0) {
		fprintf(stderr, "Could not read %s\n", conf_file);
		return(1);
	}

	close(fd);
	file[rv] = 0;

	// reads in captured nickname/alias-combinations, [pY!] splits
	ptr = file;
	end_ptr = ptr;
	while (*ptr && end_ptr) {
		end_ptr = strstr(ptr, "[pY!]");
		if (end_ptr) {
			len = end_ptr - ptr;
			if (len > (sizeof(nickaliasmap[0].nickname) -1)) {
				len = sizeof(nickaliasmap[0].nickname) -1;
			}
			memcpy(nickaliasmap[numaliases].nickname, ptr, len);
			ptr = end_ptr + 5;
			end_ptr = strchr(ptr, '\n');
			if (end_ptr) {
				len = end_ptr - ptr;
				if (len > (sizeof(nickaliasmap[0].alias) -1)) {
					len = sizeof(nickaliasmap[0].alias) -1;
				}
				memcpy(nickaliasmap[numaliases].alias, ptr, len);
				numaliases++;
				ptr = end_ptr + 1;
			}
		}
	}

	free(file);
	return(0);
}


/* find alias of a screenname, return screenname if not found */
char *findalias(char *nickname) {
	int i;
	
	for(i=0; i<numaliases; i++) {
		if (!strcmp(nickaliasmap[i].nickname, nickname)) {
			return((char*)&nickaliasmap[i].alias);
		}
	}	

	return(nickname);
}


/* sends data over a udp-socket, handles partial sends */
int udpsend(int socket, unsigned char *buf, int *len) {
	int total = 0;        // how many bytes we've sent
	int bytesleft = *len; // how many we have left to send
	int n=0;

	while (total < *len) {
		n = send(socket, buf+total, bytesleft, 0);
		if (n == -1) break; 
		total += n;
		bytesleft -= n;
	}
	*len = total; 		// return number actually sent here
	return n==-1?-1:0; // return -1 on failure, 0 on success
} 
 
 
/* receives data from a udp-socket */
int udprecv(int socket, unsigned char *buf, int *len)
{
	int n;
	int waiting;
	fd_set fdset;
	struct timeval timeout;

	waiting = 1;
	while (waiting) {
		timeout.tv_sec = 0;
		timeout.tv_usec = 100000; /* tenth a sec */
		FD_ZERO(&fdset);
		FD_SET(socket, &fdset);
		n = select(socket+1, &fdset, NULL, NULL, &timeout);
		if ((n == 1) && (FD_ISSET(socket, &fdset))) {
			waiting = 0;
		}
		if (udp_thread_exit) {
			*len=0;
			return -1;
		}
	}

	n = recv(socket, buf, (int)len, 0);

	*len = n; 			// return number actually received
	return n==-1?-1:0; // return -1 on failure, 0 on success
}
 

/* opens udp socket to Yahoo voiceserver */
int openYVoicesocketudp(char *server) {
	int yahoosocket;
	struct sockaddr_in yahoo_addr;

	if ((yahoosocket = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        	fprintf(stderr, "Could not open socket !\n");
		return(1);
	}

	yahoo_addr.sin_family = AF_INET;			// host byte order 
	yahoo_addr.sin_port = htons(YVudpport);		// short, network byte order 
	inet_aton(server, &(yahoo_addr.sin_addr));
	memset(&(yahoo_addr.sin_zero), '\0', 8);	// zero the rest of the struct 

	// connected dgram sockets allow to use send/recv instead of sendtp/recvfrom
	if (connect(yahoosocket, (struct sockaddr *)&yahoo_addr, 
		    sizeof(struct sockaddr)) == -1) {
		fprintf(stderr, "Could not connect to Yahoo UDP-Voicechat-server\n");
		return(0);
	}

	dbg_print("New udp-server connection: %s\n", server);
	return(yahoosocket);
}


/* send udp-login to yahoo-server */
int loginYVoiceudp() {
	int packetlen;
		
	struct {
		unsigned char header[2];
		unsigned char rtpseq[2];
		unsigned char rtptim[4];
		unsigned char vuser[4];
	} packet;
	
	memcpy(&packet.header, "\x80\xa2\x00\x01\x00\x00\x00\x00", 8);
	memcpy(&packet.vuser, &YVCconfig.vuser, 4);
	packetlen = sizeof(packet);
	
	rtpsequence = 2;			// first packet of sequence
	rtptime = 0x2d0;			// time for the first real packet (sequence 2)
	
	dbg_print("Sending udp-login.\n", NULL);
	dbg_data_show((unsigned char*)&packet, packetlen);
	
	return udpsend(udpsocket, (unsigned char*)&packet, &packetlen);
}


/* send the keep alive packet */
void yudpping(unsigned char *packet, int *packetlen) {
	// we'll only answer to pings not including the voiceid
	if (!(packet[8] == YVCconfig.vuser[0] && 
	     packet[9] == YVCconfig.vuser[1] && 
	     packet[10] == YVCconfig.vuser[2] && 
	     packet[11] == YVCconfig.vuser[3])) {
	 
	 	packet[1] = 0x22;	// setting ACK
	 	udpsend(udpsocket, packet, packetlen);  
	 }
}


/* craft tsp-packet from sounddata and send it via udp */
int ytspcraft() {
	int packetlen;
	
	struct {
		unsigned char header[2];
		unsigned char rtpseq[2];
		unsigned char rtptim[4];
		unsigned char vuser[4];
		unsigned char header2[2];
		unsigned char tspdata[96];
		unsigned char footer[2];	
	} packet;
	
	if (playfirstpacket) {
		playfirstpacket = 0;
		memcpy(&packet.header, "\x80\xa2", 2);
	}
	else memcpy(&packet.header, "\x80\x22", 2);
	memcpy(&packet.header2, "\x00\x02", 2);		// 0x01 0x02 earlier ?
	memcpy(&packet.footer, "\xff\xfe", 2);
	packet.rtpseq[0] = (rtpsequence >> 8) & 0xff;
	packet.rtpseq[1] = rtpsequence & 0xff;
	rtpsequence++;
	packet.rtptim[0] = (rtptime >> 24) & 0xff;
	packet.rtptim[1] = (rtptime >> 16) & 0xff;
	packet.rtptim[2] = (rtptime >> 8) & 0xff;
	packet.rtptim[3] = rtptime & 0xff;
	rtptime += 0x2d0;							// 720 Bytes of speech
	
	memcpy(&packet.vuser, &YVCconfig.vuser, 4);
	packetlen = sizeof(packet);
	tsp_conversion((unsigned char*)(&recordbuffer), (unsigned char*)&packet.tspdata, 1440, 96);

	dbg_print("Sending udp-tsp-packet.\n", NULL);
	dbg_data_show((unsigned char*)&packet, packetlen);
	
	return udpsend(udpsocket, (unsigned char*)&packet, &packetlen);	
}


/* send stopped-talking packet to server */
int ytspstop() {
	int packetlen;
		
	struct {
		unsigned char header[2];
		unsigned char rtpseq[2];
		unsigned char rtptim[4];
		unsigned char vuser[4];
	} packet;
	
	memcpy(&packet.header, "\x80\x22", 2);
	packet.rtpseq[0] = (rtpsequence >> 8) & 0xff;
	packet.rtpseq[1] = rtpsequence & 0xff;
	rtpsequence++;
	rtptime = 0xf0;						// init for next time
	memcpy(&packet.rtptim, "\x00\x00\x00\x00", 4);
	memcpy(&packet.vuser, &YVCconfig.vuser, 4);
	packetlen = sizeof(packet);

	playfirstpacket = 1;			// for the next time speaking	
	dbg_print("Sending udp-stoptalk.\n", NULL);
	dbg_data_show((unsigned char*)&packet, packetlen);
	return udpsend(udpsocket, (unsigned char*)&packet, &packetlen);
}


/* extract tsp-data from packet and add to soundbuffer */
void ytspextract(unsigned char *packet) {
	GtkWidget *label1;
	GtkWidget *label2;
	int mempos;
	char *chatnick=NULL, *chatalias=NULL;
	
	memcpy(&actualvoice, packet+8, 4);				// which voiceid
	
	if (memcmp(&actualvoice, &displvoice, 4)) {			// display talker
		char voice_id[20];
		sprintf(voice_id, "%02x %02x %02x %02x",
			actualvoice[0],
			actualvoice[1],
			actualvoice[2],
			actualvoice[3]);
		dbg_print("ytspextract: searching for voiceid: %s\n", voice_id);

		memcpy(&displvoice, &actualvoice, 4);
		// find nickname and set widget
		mempos = 0;
		while (mempos < max_roommembers && mempos != -1) {
			if (!memcmp(&displvoice, &roommembers[mempos].voiceid, 4)) {
				chatnick = (char*)&roommembers[mempos].nickname;
				chatalias = findalias((char*)&roommembers[mempos].nickname);
				mempos = -1;	// found!
			}
			else mempos++;
		}

		label1 = lookup_widget(gyv_mainwindow, "gyv_label_name");
		label2 = lookup_widget(gyv_mainwindow, "gyv_label_alias");
		if (mempos != -1) {
			gtk_label_set_text(GTK_LABEL(label1), "NAME NOT FOUND!");
			gtk_label_set_text(GTK_LABEL(label2), "NAME NOT FOUND!");
			memset(displvoice, 0, sizeof(displvoice));
		}
		else {
			gtk_label_set_text(GTK_LABEL(label1), chatnick);
			gtk_label_set_text(GTK_LABEL(label2), chatalias);
		}
		gtk_widget_queue_draw(label1);
		gtk_widget_queue_draw(label2);
	}
	
	// extract 96 Bytes of tsp-data and convert to pcm
	if (playing) {
		tsp_conversion((unsigned char*)packet+14, (unsigned char*)(&soundbuffer)+1440*recvchunk, 96, 1440);
		recvchunk++;

		pthread_mutex_lock(&data_mutex);
		playchunk++;
		if ((playchunk >= tspdelay) && (silence == 0)) {
			pthread_cond_signal(&data_cond);
		}
		pthread_mutex_unlock(&data_mutex);

		if (recvchunk >= max_tspchunks) recvchunk = 0;		// wrap around
	}
}


/* opening udp socket, and udp-polling */
void udppollthread() {
	unsigned char udpbuffer[2000];
	int udppacketlen;
		
	if (!(udpsocket=openYVoicesocketudp((char*)&udpserver))) pthread_exit(NULL);
	if (loginYVoiceudp()) {
		dbg_print("UDP-Login failed!\n", NULL);
		close(udpsocket);
		udpsocket = 0;
		pthread_exit(NULL);	
	}
	
	gtk_widget_set_sensitive(lookup_widget(gyv_mainwindow, "gyv_button_talk"), TRUE);

	while (logged_in && !udp_thread_exit) {
		udppacketlen = 2000;
		memset(&udpbuffer, 0, 2000);	
		
		if (!udprecv(udpsocket, (unsigned char*)&udpbuffer, (int*)&udppacketlen)) {
			dbg_print("Received UDP packet:\n", NULL);
//			dbg_print("packlen: %i\n", itoa(udppacketlen));
			dbg_data_show((unsigned char*)&udpbuffer, udppacketlen);

			switch(udppacketlen) {
				case 12:
					dbg_print("keep alive packet\n", NULL);	
					yudpping((unsigned char*)&udpbuffer, &udppacketlen);
					break;

				case 112:
					dbg_print("tsp-packet\n", NULL);
					ytspextract((unsigned char*)&udpbuffer);
					break;

				default:
					dbg_print("Unknown udp-packet: packet_len: %d\n", (char *)udppacketlen);
			}
		}	
	}

	dbg_print("Leaving udp-thread\n", NULL);		
	pthread_exit(NULL);
}


/* sends data over a tcp-socket, handles partial sends */
int tcpsend(int socket, unsigned char *buf, int *len) {
	int total = 0;        // how many bytes we've sent
	int bytesleft = *len; // how many we have left to send
	int n=0;

	while (total < *len) {
		n = send(socket, buf+total, bytesleft, 0);
		if (n == -1) break; 
		total += n;
		bytesleft -= n;
	}
	*len = total; 		// return number actually sent here
	return n==-1?-1:0; // return -1 on failure, 0 on success
} 
 
 
/* receives data from a tcp-socket */
int tcprecv(int socket, unsigned char *buf, int *len)
{
	int n;
	int waiting;
	fd_set fdset;
	struct timeval timeout;

	waiting = 1;
	while (waiting) {
		timeout.tv_sec = 0;
		timeout.tv_usec = 100000; /* tenth a sec */
		FD_ZERO(&fdset);
		FD_SET(socket, &fdset);
		n = select(socket+1, &fdset, NULL, NULL, &timeout);
		if ((n == 1) && (FD_ISSET(socket, &fdset))) {
			waiting = 0;
		}
		if (tcp_thread_exit) {
			*len=0;
			return -1;
		}
	}
    
	n = recv(socket, buf, (int)len, 0);

	*len = n; 			// return number actually received
	return n==-1?-1:0; // return -1 on failure, 0 on success
}
 

/* open TCP socket to a Yahoo voice server */
int openYVoicesocket(char *server) {
	int yahoosocket;
	struct sockaddr_in yahoo_addr;

	if ((yahoosocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        	fprintf(stderr, "Could not open socket !\n");
		return(1);
	}

	yahoo_addr.sin_family = AF_INET;			// host byte order 
	yahoo_addr.sin_port = htons(YVtcpport);		// short, network byte order 
	inet_aton(server, &(yahoo_addr.sin_addr));
	memset(&(yahoo_addr.sin_zero), '\0', 8);	// zero the rest of the struct 

	if (connect(yahoosocket, (struct sockaddr *)&yahoo_addr, 
		    sizeof(struct sockaddr)) == -1) {
        	fprintf(stderr, "Could not connect to Yahoo Voicechat-server\n");
		return(0);
	}

	dbg_print("New tcp-server connection: %s\n", server);
	return(yahoosocket);
}


/* sends login string to Yahoo */
int loginYVoice(char phase) {
	struct {
		unsigned char totlen[2];	
		unsigned char head[52];
		unsigned char remlen[2];
		unsigned char head2[25];
		unsigned char packbuf[230];
	} packet;
	
	char dummy[100];
	int dumlen, totlen;
	char *ptr;
			
	memcpy(&packet.head, "\x00\x00\x81\xc9\x00\x30\x00\x00\x00\x00"
						 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
						 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
						 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
						 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
						 "\x80\xcc", 52);
	memcpy(&packet.head2, "\x00\x00\x00\x00\x43\x41\x21\x59\x00\x01"
						  "\x00\x00\x00\x00\x00\x01\x64\x02\x00\x02"
						  "\x65\x02\x00\x03\x02", 25);
	memset(&packet.packbuf, 0, sizeof(packet.packbuf));
	totlen = sizeof(packet.head)+sizeof(packet.head2)+4;
	ptr = (char *)&packet.packbuf;

	// set login-phase 1 or 2
	packet.head2[15] = phase;	
	
	// user
	dumlen = strlen(YVCconfig.user);
	sprintf(dummy, "%c%s\x03", (unsigned char)dumlen, YVCconfig.user);
	dumlen += 2;
	memcpy(ptr, dummy, dumlen);
	totlen += dumlen;
	ptr += dumlen;
	
	// room
	if (YVCconfig.serial == "[PYCONFERENCE]") {				// PM and conference
		dumlen = strlen(YVCconfig.room)+3;
		sprintf(dummy, "%cme/%s", dumlen, YVCconfig.room);
	}
	else {									// regular chat rooms
		dumlen = strlen(YVCconfig.room)+strlen(YVCconfig.serial)+5;
		sprintf(dummy, "%cch/%s::%s", dumlen, YVCconfig.room, YVCconfig.serial);
	}
	dumlen += 1;
	memcpy(ptr, &dummy, dumlen);
	totlen += dumlen;
	ptr += dumlen;

	// cookie
	dumlen = strlen(YVCconfig.cookie)+2;
	sprintf(dummy, "\x07\"%s", YVCconfig.cookie);			// difference to PM \x00 instead of \" ??
	memcpy(ptr, &dummy, dumlen);
	ptr += dumlen;
	memcpy(ptr, "\x06\x00\x05\x02\x00\x19\x08\x04\xac", 9);
	totlen += (dumlen+9);
		
	// padding dword
	dumlen = totlen % 4;
	if (dumlen != 0)	totlen += (4-dumlen);
	packet.totlen[0] = (char)(totlen >> 8);
	packet.totlen[1] = (char)(totlen & 0xff);
	
	dumlen = totlen-52;
	packet.remlen[0] = (char)(dumlen >> 8);
	packet.remlen[1] = (char)(dumlen & 0xff);

	dbg_print("Sending login-string:\n", NULL);
	dbg_data_show((unsigned char*)&packet.totlen, totlen);
	tcpsend(tcpsocket, (unsigned char*)&packet, &totlen);	
	return(0);
}


/* sends the logout-string to Yahoo (exactly 60 Bytes!) */
int logoutYVoice() {
	unsigned char header[8]	= "\x00\x3c\x00\x00\x81\xc9\x00\x30";
	unsigned char tail[4] 	= "\x81\xcb\x00\x08";
	unsigned char packet[60];
	
	memset(packet, 0, sizeof(packet));
	memcpy(packet, header, 8);	
	sprintf(packet+8, "%s", YVCconfig.vuser);
	memcpy(packet+60-8, tail, 4);
	memcpy(packet+60-4, &YVCconfig.vuser, 4);			

	dbg_print("Sending logout-string:\n", NULL);
	dbg_data_show((unsigned char*)&packet, sizeof(packet));
	tcppacketlens = sizeof(packet);
	tcpsend(tcpsocket, (unsigned char*)&packet, &tcppacketlens);	
	return(0);	
}


/* handles the yahoo server-redirection */
void yserver_redirect(unsigned char *packet) {
	close(tcpsocket);
	tcpsocket = 0;
	sprintf(udpserver, "%i.%i.%i.%i", (unsigned char) packet[94], 
					  (unsigned char) packet[95], 
					  (unsigned char) packet[96], 
					  (unsigned char) packet[97]);
	if (!(tcpsocket = openYVoicesocket((char*)&udpserver))) return;
	loginYVoice(2);	
}


/* gets the vuser id for udp/rtp and starts udp-thread */
void yget_voiceid(unsigned char *packet) {
	
	// getting our vuser-id for udp and rtp
	memcpy(&YVCconfig.vuser, packet+76, 4);
	
	dbg_print("Entering udp-thread\n", NULL);		
	// udp-handling in own thread
	udp_thread_exit=0;
	pthread_create(&udp_tid, NULL, (void *)udppollthread, NULL);
//	pthread_detach(udp_tid);
	
	//TODO: in this packet are offsets and length for generating the signature
	//those are not used, maybe one time we are in need of them
}


/* sends authentification for udp-server */
void ypager_auth() {
	int packetlen;
	int dumlen;
		
	struct {
		unsigned char total[2];
		unsigned char header1[6];
		unsigned char voiceid[4];
		unsigned char header2[42];
		unsigned char remlen[2];
		unsigned char header3[17];
		unsigned char authstrl;	
		unsigned char authstr[200];
	} packet;
	
	memset(&packet.authstr, 0, sizeof(packet.authstr));
	memcpy(&packet.header1, "\x00\x00\x81\xc9\x00\x30", 6);	
	memcpy(&packet.voiceid, &YVCconfig.vuser, 4);
	memcpy(&packet.header2, "\x00\x00\x00\x00\x00\x00\x00\x00"
							"\x00\x00\x00\x00\x00\x00\x00\x00"
							"\x00\x00\x00\x00\x00\x00\x00\x00"
							"\x00\x00\x00\x00\x00\x00\x00\x00"
							"\x00\x00\x00\x00\x00\x00\x00\x00"
							"\x80\xcc", 42);
	memcpy(&packet.header3, "\x00\x00\x00\x00\x43\x41\x21\x59"
							"\x00\x07\x00\x00\x00\x00\x00\x00"
							"\x0b", 17);
	
	//TODO: signature calculation, application, lib set by defines
	sprintf(packet.authstr, "exe=YPAGER&sig=3386b4c04f61f861f5beaa04"
							"&app=mc(6,0,0,1671)&u=%s&ia=&us&lib=yacscom(45)"
							"&in=3,1,104,5.0,ESS 1869&out=3,1,104,5.0,ESS 1869", YVCconfig.user);
	dumlen = strlen(packet.authstr);
	packetlen = 2+6+4+42+2+18+dumlen;
	packet.authstrl = (char)(dumlen & 0xff);

	dumlen = packetlen % 4;
	if (dumlen != 0)	packetlen += (4-dumlen);
	packet.total[0] = (char)(packetlen >> 8);
	packet.total[1] = (char)(packetlen & 0xff);

	dumlen = packetlen-52;
	packet.remlen[0] = (char)(dumlen >> 8);
	packet.remlen[1] = (char)(dumlen & 0xff);

	dbg_print("Sending auth-string:\n", NULL);
	dbg_data_show((unsigned char*)&packet.total, packetlen);
	tcpsend(tcpsocket, (unsigned char*)&packet, &packetlen);
}

int compare_roommember(const void *a, const void *b)
{
	CHATUSER *member1 = (CHATUSER *)a;
	CHATUSER *member2 = (CHATUSER *)b;

	if (member1->alias[0] == 0) {
		if (member2->alias[0] == 0) {
			return 0;
		}
		return 1;
	}

	if (member2->alias[0] == 0) {
		return -1;
	}

	/* Below is really the correct way to sort. It
	   handles foreign languages just fine.
	   It will ignore any leading special chacters, which may take
	   some getting used to, but this is the accepted sort method these days. */
	return(g_utf8_collate(member1->alias, member2->alias));
}

/* parses the member-joins */
void ymember_list(unsigned char *packet, int packetlen) {
	int ptr;
	unsigned char namelen;
	int mempos;
	GtkTreeIter iter;
	int sorted_members[max_roommembers];
	int i;

	ptr = 76;					// first room member
	mempos = 0;
	while (ptr < packetlen) {
		// find an empty position in memberlist
		while (mempos < max_roommembers && roommembers[mempos].nickname[0] != 0) mempos++;
		if (mempos < max_roommembers) {
			memcpy(&roommembers[mempos].voiceid, packet+ptr, 4);
			ptr += 5;
			namelen = *(packet+ptr++);
			memcpy(roommembers[mempos].nickname, packet+ptr, namelen-1);
			roommembers[mempos].nickname[namelen]=0;		// end of string, replaces "@"
			strncpy(roommembers[mempos].alias, findalias((char*)&roommembers[mempos].nickname), namelen-1);
			roommembers[mempos].alias[namelen]=0;
			ptr += namelen;
			ptr+= 4-(ptr % 4);	// the list is padded by 4 (a suggestion!)

			//TODO: Ignores are missing
			gtk_list_store_insert(store, &iter, mempos);
			gtk_list_store_set(GTK_LIST_STORE(store), &iter,
					   COL_NICK,  roommembers[mempos].nickname,
					   COL_ALIAS, roommembers[mempos].alias,
					   COL_IGN,   0,
					   -1);
			roommembers[mempos].iter = iter;		// save for later removal
		}
		else dbg_print("Member-list is full!!\n", NULL);
	}	

	for (i=0; i<max_roommembers; i++) {
		roommembers[i].i = i;
	}
	qsort(roommembers, max_roommembers, sizeof(CHATUSER), compare_roommember);
	for (i=0; i<max_roommembers; i++) {
		sorted_members[i] = roommembers[i].i;
	}
	gtk_list_store_reorder(GTK_LIST_STORE(store), sorted_members);

	dbg_print("Member list:\n", NULL);
	for(ptr=0; ptr<max_roommembers; ptr++) {
		if (roommembers[ptr].nickname[0]!=0) {
			char voice_id[20];
			sprintf(voice_id, "%02x %02x %02x %02x, %%s\n",
				roommembers[ptr].voiceid[0],
				roommembers[ptr].voiceid[1],
				roommembers[ptr].voiceid[2],
				roommembers[ptr].voiceid[3]);
			dbg_print(voice_id, roommembers[ptr].nickname);
		}
	}
}


/* handling leaving users */
void ymember_leave(unsigned char *packet) {
	int mempos;
	int i;
	
	
	for (mempos = 0; mempos < max_roommembers; mempos++) {
		// search position and free
		if (roommembers[mempos].voiceid[0] == packet[76] &&
		    roommembers[mempos].voiceid[1] == packet[77] &&
		    roommembers[mempos].voiceid[2] == packet[78] &&
		    roommembers[mempos].voiceid[3] == packet[79]) {
	
			dbg_print("User %s left\n", roommembers[mempos].nickname);
			gtk_list_store_remove(store, &roommembers[mempos].iter);
			for (i=mempos; i<max_roommembers-1; i++) {
				roommembers[i] = roommembers[i+1];
			}
			memset(&roommembers[max_roommembers-1], 0, sizeof(CHATUSER));
			break;
		}
	}
	if (mempos == max_roommembers) {
		dbg_print("Leaving user not found!\n", NULL);
	}
}

int click_off()
{
	GtkWidget *gyv_button_off = lookup_widget(gyv_mainwindow, "gyv_button_off");
	gtk_button_clicked(GTK_BUTTON(gyv_button_off));
	return(FALSE);
}

/* opening first socket, polling tcp-packets and interpreting them */
void tcppollthread() {
	unsigned char tcpbuffer[2000];
	int yptype;
	int packetknown;
			
	if (!(tcpsocket=openYVoicesocket((char *)&YVserver))) pthread_exit(NULL);
	if (loginYVoice(1)) {
		close(tcpsocket);
		tcpsocket = 0;
		pthread_exit(NULL);
	}
	logged_in = 1;
	memset(&tcpbuffer, 0, 2000);
	
	while (logged_in && !tcp_thread_exit) {
		tcppacketlenr = 2000;			// Maximum
		if (!tcprecv(tcpsocket, (unsigned char*)&tcpbuffer, &tcppacketlenr)) {
			dbg_print("Received TCP packet:\n", NULL);
//			dbg_print("packlen: %i\n", itoa(tcppacketlenr));
			dbg_data_show((unsigned char*)&tcpbuffer, tcppacketlenr);
			if (tcppacketlenr == 0) {
				dbg_print("Remote socket was closed!\n", NULL);
				logged_in = 0;
				g_idle_add(click_off, NULL);
			}

			packetknown = 0;

			yptype = tcpbuffer[72]*256 + tcpbuffer[73];
			switch(yptype) {
				case 0x80cc:
					yptype = tcpbuffer[84]*256 + tcpbuffer[85];
					switch(yptype) {
					    case 0x000a:
						dbg_print("server redirection\n", NULL);
						yserver_redirect((unsigned char*)&tcpbuffer);
						packetknown = 1;
						break;
						
					    case 0x0004:
						dbg_print("getting voiceid and signature-infos\n", NULL);
						yget_voiceid((unsigned char*)&tcpbuffer);
						packetknown = 1;
						break;

					    case 0x0007:
						dbg_print("udp-server hooked, pager-auth neccessary\n", NULL);
						ypager_auth();
						packetknown = 1;
						break;

					    case 0x0000:
						dbg_print("BAD login\n", NULL);
						packetknown = 1;
						break;
					}					
					break;

				case 0x82cb:
					dbg_print("User left\n", NULL);
					ymember_leave((unsigned char*)&tcpbuffer);
					packetknown = 1;
					break;

				default:
					if (tcpbuffer[73] == 0xca) {
						dbg_print("got member list\n", NULL);
						ymember_list((unsigned char*)&tcpbuffer, tcppacketlenr);
						packetknown = 1;
					}
					break;
			}

			if (!packetknown) {
				dbg_print("Unknown packet! id: %04x\n", (char *)yptype);
			}
			memset(&tcpbuffer, 0, 2000);
		}
	}

	close(tcpsocket);
	tcpsocket = 0;
	
	dbg_print("Leaving tcp-thread\n", NULL);		
	pthread_exit(NULL);
}


/* this one starts the whole thing */
void StartYVoice() {
	dbg_print("StartYVoice: logged_in: %d\n", (char *)logged_in);
	if (!logged_in) {
		if (init_sound()) return;		// do nothing without sound
		// get the actual nickname and room
		if (ReadConfiguration()) return;

		// get screennames to the nicknames
		if (ReadAliasMap()) return;
		
		// start with clean list
		memset(&roommembers, 0, sizeof(roommembers));
		memset(&actualvoice, 0, sizeof(actualvoice));
		gtk_list_store_clear(store);
		
		dbg_print("Entering tcp-thread\n", NULL);		
		// tcp-handling in own thread
		tcp_thread_exit=0;
		pthread_create(&tcp_tid, NULL, (void *)tcppollthread, NULL);
//		pthread_detach(tcp_tid);
		return;
	}
}


/* stops everything and closes socket */
void StopYVoice() {
	int status;

	dbg_print("StopYVoice: logged_in: %d\n", (char *)logged_in);
	if (logged_in) {
		logoutYVoice();		
		logged_in = 0;
	}

	if (tcp_tid) {
		tcp_thread_exit = 1;
		pthread_join(tcp_tid, (void*)&status);
		tcp_tid = 0;
	}

	if (udp_tid) {
		udp_thread_exit = 1;
		pthread_join(udp_tid, (void*)&status);
		udp_tid = 0;
	}

	uninit_sound();

	gtk_list_store_clear(store);
}


/* stops client and restarts if room changed */
gboolean RoomChange() {
	FILE *cf;
	char conf_file[72];
	char *user="user";
	char buf[512];
	struct {
		char user[75];		// our nickname
		char room[75];		// desired chatroom
		char serial[50];	// serial number of chatroom	
		char cookie[120];	// Yahoo cookie
	} actconf;

	if (logged_in) {			// we need to be online			
		if (getenv("USER")) user=getenv("USER");
		snprintf(conf_file, 70, "/tmp/pyvoice_chat_start_%s", user);
		strdup(conf_file);
		
		if (!(cf = fopen(conf_file, "rb"))) {
			fprintf(stderr, "Could not open %s\n", conf_file);
			return(TRUE);
		}
		
		memset(buf, 0, sizeof(buf));
		fread(buf, 1, 512, cf);
		fclose(cf);
		
		memset(&actconf.user, 0, sizeof(actconf));
		strncpy(actconf.user, (char *)strtok((char*)&buf, "\n"), sizeof(actconf.user));
		strncpy(actconf.room, (char *)strtok(NULL, "\n"), sizeof(actconf.room));
		strncpy(actconf.cookie, (char *)strtok(NULL, "\n"), sizeof(actconf.cookie));
		strncpy(actconf.serial, (char *)strtok(NULL, "\n"), sizeof(actconf.serial));
	
		if (strcmp(YVCconfig.room, actconf.room)) {		// room changed
			StopYVoice();
			StartYVoice();
		}

		// update chat-aliases
		ReadAliasMap();
	}
	return(TRUE);		// we need to return TRUE
}
