/*-*-linux-c-*-*/

/*
 *  gnewtellium - Newtella for Unix
 *  Copyright (C) 2001 Elias Athanasopoulos
 *
 *  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
 */


/* These includes are needed in the specified
   order by the BSD family. It maybe better to
   add an #ifdef BSD or something here...
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>

#include "global.h"
#include "newtella.h"
#include "gnutella.h"
#include "gui.h"
#include "config.h"
#include "file.h"
#include "uploads.h"
#include "downloads.h"
#include "hosts.h"
#include "route.h"

extern struct newtella_options *gl_options;
extern int gl_server, gl_read_tag;
extern struct host_info *gl_my_host_info;

gchar *ip2str(uint8_t *ip)
{
	char str[INET6_ADDRSTRLEN]="\0";
	return (gchar *)inet_ntop(AF_INET6, ip, str, sizeof(str));
}


uint8_t *str2ip(gchar *str)
{
        uint8_t ip[16]="\0";
        gint r;

        r = inet_pton(AF_INET6, str, &ip);
	if (r != 1){
	   memset(ip, 0, 16);
	}
        return ip;
}

/*
gchar *ip2str(guint32 ip)
{
	// taken from gtk-gnutella
        struct in_addr ia;
        ia.s_addr = g_htonl(ip);
        return inet_ntoa(ia);
}
*/

/*
guint32 str2ip(gchar *str)
{
        // taken from gtk-gnutella
        struct in_addr ia;
        gint r;
        r = inet_aton(str, &ia);
        if (r) return g_ntohl(ia.s_addr);
        return 0;
}
*/

int is_mp3(gchar *str)
{
	return (!g_strncasecmp(str+strlen(str)-4, ".mp3", 4));
}

int has_ending(gchar *str)
{
	return (!g_strncasecmp(str+strlen(str)-2, ".", 1)||!g_strncasecmp(str+strlen(str)-3, ".", 1)||!g_strncasecmp(str+strlen(str)-4, ".", 1)||!g_strncasecmp(str+strlen(str)-5, ".", 1)||!g_strncasecmp(str+strlen(str)-6, ".", 1));
}

int newtella_server_old(int port)
{
	int sock;
	struct sockaddr_in6 server;
	size_t option;

	sock = socket(AF_INET6, SOCK_STREAM, 0);

	if (sock < 0) {
	   perror("server socket");
	   return 0;
	}

	/* get rid of "bind: address already used" */
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &option, sizeof(option)) == -1){
	   perror("setsockopt(SO_REUSEADDR)");
	   close(sock);
	   return 0;
	}

	/* non blocked */
	fcntl(sock, F_SETFL, O_NONBLOCK);

	server.sin6_family = AF_INET6;
	server.sin6_port = htons(port);
	server.sin6_addr = in6addr_any;

	if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0){
	   perror("server bind");
	   return 0;
	}

	return sock;
}


int newtella_server(int port)
{
	int sock, error;
	size_t option;
        struct addrinfo hints, *aip, *aipsave;
	char strport[255];

	bzero(&hints, sizeof(hints));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = AF_INET6;
	hints.ai_socktype = SOCK_STREAM;

	sprintf(strport, "%i", port);
	error = getaddrinfo(NULL, strport, &hints, &aip);
	if (error != 0){
	   fprintf(stderr, "getaddrinfo: %s for service %d\n", gai_strerror(error), htons(port));
	   return 0;
	}

	aipsave = aip;

	sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);

	if (sock < 0) {
	   perror("server socket");
	   return 0;
	}

	// get rid of "bind: address already used"
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &option, sizeof(option)) == -1){
	   perror("setsockopt(SO_REUSEADDR)");
	   close(sock);
	   return 0;
	}

	// non blocked
	fcntl(sock, F_SETFL, O_NONBLOCK);


	if (bind(sock, aip->ai_addr, aip->ai_addrlen) < 0){
	   perror("server bind");
	   close(sock);
	   return 0;
	}

	freeaddrinfo(aipsave);
	return sock;
}


int newtella_init_server(guint16 port)
{
	gl_server = newtella_server(port);

	if (!gl_server)
		return 0;

	//if (listen(gl_server, 5) < 0)
	if (listen(gl_server, 10) < 0)
                perror("server listen");


	gl_read_tag = gdk_input_add(gl_server, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
				    (GdkInputFunction) newtella_welcome_client, NULL);

	return 1;
}


int newtella_kill_server(void)
{
	/* try to kill server */
	if (gl_server) {
		gdk_input_remove(gl_read_tag);
		close(gl_server);
	}

	return 1;
}

int newtella_guess_ip(gint s)
{
	struct sockaddr_in6 servent;
	//size_t len = sizeof(struct sockaddr_in6);
	size_t len = sizeof(servent);

        /* kludge: if our local IP is not in the Internet
	   space, guess it using a random connection. */

	getsockname(s, (struct sockaddr *) &servent, &len);

	if (!host_is_private(&servent.sin6_addr, gl_my_host_info->port)){
	   bzero(gl_my_host_info->ip, sizeof(gl_my_host_info->ip));
	   memcpy(gl_my_host_info->ip, &servent.sin6_addr, sizeof(servent.sin6_addr));
	}

	return 1;
}

int newtella_force_connect_old(uint8_t *ip, guint16 port)
{
	struct sockaddr_in6 server;

	int sock = socket(AF_INET6, SOCK_STREAM, 0);

	fcntl(sock, F_SETFL, O_NONBLOCK);

	server.sin6_family = AF_INET6;
	server.sin6_port = g_htons(port);
	bzero(&server.sin6_addr, sizeof(server.sin6_addr));
	memcpy(&server.sin6_addr, ip, sizeof(server.sin6_addr));

	if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
		if (errno != EINPROGRESS) {
			perror("connect (client)");
			return (-1);
		}
	}

	return sock;
}

int newtella_force_connect(uint8_t *ip, guint16 port)
{
	struct addrinfo *aip, hints;
	char strport[10];
	int error;
	int sock;
	char addresse[INET6_ADDRSTRLEN]="\0";

	bzero(&hints, sizeof(hints));
	hints.ai_family = AF_INET6;
	hints.ai_socktype = SOCK_STREAM;
	sprintf(strport, "%i", port);


	bzero(addresse, sizeof(addresse));
 	memcpy(addresse, ip2str(ip), sizeof(addresse));

	error = getaddrinfo(addresse, strport, &hints, &aip);
	if (error != 0){
	   perror("getaddrinfo");
	   return(-1);
	}
	sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
	fcntl(sock, F_SETFL, O_NONBLOCK);

	if (connect(sock, aip->ai_addr, aip->ai_addrlen) < 0) {
		if (errno != EINPROGRESS) {
			perror("connect (client)");
			return (-1);
		}
	}

	return sock;
}

int newtella_welcome_client(gpointer gdata, gint s, GdkInputCondition cond)
{
	struct sockaddr_in6 addr;
	int sock, len = sizeof(struct sockaddr_in6);
	struct newtella_connection *in_con;

	if (cond == GDK_INPUT_EXCEPTION)
		return -1;

	sock = accept(s, (struct sockaddr *) &addr, &len);
 	if (sock == -1)
		return -1;

	/* make it non-block */
	fcntl(sock, F_SETFL, O_NONBLOCK);

	/* create a new incoming connection */
	in_con = newtella_malloc(sizeof(struct newtella_connection));
	in_con->s = sock;
	bzero(in_con->ip, sizeof(in_con->ip));
	memcpy(in_con->ip, &addr.sin6_addr, sizeof(in_con->ip));
        in_con->port = g_ntohs(addr.sin6_port);
	in_con->type = CT_INCOMING;

	con_zero_stats(in_con);
	con_init_queue(in_con);

	in_con->state = GS_HANDSHAKE;

	// Igor: Der Wert wurde von 1 auf 0 geändert, da sonst die erste globale Connection aus der Liste entfernt wird.
	//	 --> es war keine Suche mehr auf dem Client möglich, der den Upload gestartet hat.
        //if (gl_options->cons == 1){
	if (gl_options->cons == 0){
                gl_con = g_list_append(NULL, in_con);
        }else{
                gl_con = g_list_append(gl_con, in_con);
	}
	gui_add_con_to_list(in_con);

        in_con->read_tag = gdk_input_add(in_con->s, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
					 (GdkInputFunction) newtella_read, (gpointer)in_con);
        in_con->write_tag = 0;

	/* update time */
        in_con->last_time = time((time_t)NULL);

	return 1;
}

int newtella_handshake_sucks(char *buf, int len)
{
        /* handshake/download/upload data */
	if (!memcmp(buf, "GNUTELLA OK\n\n", strlen("GNUTELLA OK\n\n")))
		return 0;
	if (!memcmp(buf, "GNUTELLA CONNECT/0.4\n\n", strlen("GNUTELLA CONNECT/0.4\n\n")))
		return 0;
	if (!memcmp(buf, "GET /get/", strlen("GET /get/")))
		return 0;
	if (!memcmp(buf, "GIV", strlen("GIV")))
		return 0;

	return 1;
}

int newtella_packet_sucks(struct gnutella_header *gh)
{
	if ((gh->length > MAX_PACKET_LENGTH) || (gh->length < 0))
		goto bad;

	if (gh->ttl > GNUTELLA_MAX_TTL || gh->hops > GNUTELLA_MAX_HOPS || gh->ttl == 0)
		goto bad;

	/* length check */
	switch (gh->f) {
	case GNUTELLA_INIT:
		if (gh->length != 0) goto bad;
		break;
	case GNUTELLA_INITRESPONSE://;
		if (gh->length != GN_PONG_SIZE) goto bad;
		break;
	case GNUTELLA_PUSHREQUEST:
		if (gh->length != GN_PUSH_SIZE) goto bad;
		break;
	case GNUTELLA_SEARCH:
		if (gh->length < (GN_QUERY_SIZE+1)) goto bad;
		if (gh->length > GNUTELLA_MAX_QUERY) goto bad;
		break;
	case GNUTELLA_SEARCHRESPONSE:
		break;
	default:
		/* ok, this is defenately a bad package */
		goto bad;
	}

	return 0;

 bad:
	g_print("Got a bad packet. Func: 0x%X Size: %d\n", gh->f, gh->length);

	return 1;
}


gint handle_connect(struct newtella_connection *con, gchar *buf)
{
	struct squery *query;
	struct host_info *host;
	GSList *l;
	GList  *iter;
	gint    hosts_sent = 0;

	if (memcmp(buf, "GNUTELLA CONNECT/0.4\n\n", strlen("GNUTELLA CONNECT/0.4\n\n")))
		return 0;

	con_send_packet(con, "GNUTELLA OK\n\n", strlen("GNUTELLA OK\n\n"), 0);

        /* if we are full on connections send some cached
	   hosts and drop the new connection */
	if (gl_options->connected >= gl_options->max_con) {

		iter = g_list_first(gl_hosts_in_cache);
		while (iter) {

			host = (struct host_info *) iter->data;

			gnutella_send_cached_host(con, host);
			hosts_sent++;

			if (hosts_sent > HOST_FEED)
				break;

			iter = iter->next;
		}

		con_mark(con, GS_HOSTFEED);

		return 1;
	}

        con_mark(con, GS_CONNECTED);

	/* send pending queries */
	for (l = gl_queries; l; l = l->next) {
		query = (struct squery *) l->data;
		gnutella_send_query(query, con, 0);
	}

	return 1;
}

int handle_connect_response(struct newtella_connection *con, char *buf)
{
	GSList *l;
	struct squery *query;

	if (memcmp(buf, "GNUTELLA OK\n\n", strlen("GNUTELLA OK\n\n")))
		return 0;

	con_mark(con, GS_CONNECTED);

	if (gl_options->hosts_in_cache < HOST_CACHE_LOW_LIMIT)
		gnutella_send_ping(con, 0);
	else
		gnutella_send_ping(con, 1);

	/* send pending queries */
	for (l = gl_queries; l; l = l->next) {
		query = (struct squery *) l->data;
		gnutella_send_query(query, con, 0);
	}

	return 1;
}

int handle_get_request(struct newtella_connection *con, char *buf)
{
	if (memcmp(buf, "GET /get/", 8))
		return 0;

	return 1;
}

int handle_push(struct newtella_connection *con, struct gnutella_header *gh, char *buf)
{
	struct gnutella_push *gpush;
	struct message *rmsg, *msg;

        /* check if we have seen the ping */
	if (route_msg_find(gh->guid, GNUTELLA_INIT, gh->length)) {
		con->total_received_dropped++;
		return GN_PUSH_SIZE;
	} else
		con->total_handled++;

	gpush = newtella_malloc(sizeof *gpush);
	GNUTELLA_PUSH_GRAB(gpush, buf)

	/* is it for us? */
	if (guidcmp(gl_options->gl_guid, gpush->guid)) {
		upload_add(gpush->ip, gpush->port, gpush->index, gpush->guid);
		goto skip_routing;
	}

        /* route the push if we have seen the result set */
	msg = route_msg_rs_find(gpush->guid);
	if (!msg)
		goto skip_routing;

	/* ok, we have to route it */
	rmsg = newtella_malloc(sizeof *rmsg);

	rmsg->head = newtella_malloc(sizeof *(rmsg->head));
	*(rmsg->head) = *gh;

	rmsg->length = GN_HEADER_SIZE + gh->length;
	rmsg->policy = RP_ONLY_TO;
	rmsg->con    = msg->con;
	rmsg->time   = time((time_t *)NULL);

	/* route msg */
	route_msg_add(rmsg, buf);

 skip_routing:
	newtella_free(gpush);

 	return GN_PUSH_SIZE;
}

int handle_init(struct newtella_connection *con, struct gnutella_header *gh)
{
	struct message *rmsg;

	/* check if we have seen the ping */
	if (route_msg_find(gh->guid, GNUTELLA_INIT, gh->length)) {
		con->total_received_dropped++;
		return 0;
	} else
		con->total_handled++;

	/* send the reply (pong) */
	gnutella_send_pong(con, gh->guid, 7);

	/* remove packet from network if it has expired */
	if (gh->ttl == 1)
		return 0;

        /* store the new ping */
	rmsg = newtella_malloc(sizeof(struct message));

	rmsg->head = newtella_malloc(sizeof *(rmsg->head));
	*(rmsg->head) = *gh;

	rmsg->length = GN_HEADER_SIZE;
	rmsg->policy = RP_ALL_EXC;
	rmsg->con    = con;
	rmsg->time   = time((time_t *)NULL);

	/* route msg */
	route_msg_add(rmsg, NULL);

	return 0;
}

int handle_init_response(struct newtella_connection *con, gchar *buf, struct gnutella_header *gh)
{
	struct gnutella_pong_header *gpong;
	struct message *rmsg, *msg;

	/* check if we have seen the pong */
	if (route_msg_find(gh->guid, GNUTELLA_INITRESPONSE, gh->length)) {
		con->total_received_dropped++;
		return GN_PONG_SIZE;
	}
	else
		con->total_handled++;

        /* add it in cache only if we are low on hosts */
	if (gl_options->hosts_in_cache < gl_options->host_cache_length) {
		gpong = newtella_malloc(sizeof *gpong);
		GNUTELLA_PONG_GRAB(gpong, buf)
		host_add_in_cache(gpong);
		newtella_free(gpong);
	}

        /* remove packet from network if it has expired */
        if (gh->ttl == 1)
		return GN_PONG_SIZE;

	/* check if we haven't seen the ping related to the pong */
        msg = route_msg_find(gh->guid, GNUTELLA_INIT, gh->length);

	if (msg == NULL)
		return GN_PONG_SIZE;

        /* ok, we have to route it */
	rmsg = newtella_malloc(sizeof *rmsg);

	rmsg->head = newtella_malloc(sizeof *(rmsg->head));
	*(rmsg->head) = *gh;

	rmsg->length = GN_HEADER_SIZE + gh->length;
	rmsg->policy = RP_ONLY_TO;
	rmsg->con    = msg->con;
	rmsg->time   = time((time_t *)NULL);

	/* route msg */
	route_msg_add(rmsg, buf);

	return GN_PONG_SIZE;
}

int handle_search(struct newtella_connection *con, struct gnutella_header *gh, gchar *buf)
{
	struct gnutella_query_header *gquery;
	struct message *rmsg;
	GList *keywords, *iter, *found_hits;
	gint query_length;

        /* check if we have seen the query */
	if (route_msg_find(gh->guid, GNUTELLA_SEARCH, gh->length)) {
		con->total_received_dropped++;
		return gh->length;
	}

	query_length = strlen(buf+2);

	if (query_length > GNUTELLA_MAX_QUERY) {
		con->total_bad_received++;
		return gh->length;
	}

	con->total_handled++;

	gquery = newtella_malloc(sizeof *gquery);
	GNUTELLA_QUERY_GRAB(gquery, buf);

	keywords = file_extract_keywords(gquery->name);

	if (keywords) {
		GList *l;

		iter = g_list_first(keywords);
		while (iter) {
			found_hits = file_scan_db((gchar *)iter->data);

			/* ok we have what he/she wants */
			if (found_hits) {
				gnutella_send_query_response(con, found_hits, gh->guid);
				g_list_free(found_hits);
			}

			iter = iter->next;
		}

                /* clean up */
		l = g_list_first(keywords);
		while (l) {
			newtella_free(l->data);
			l = l->next;
		}

		g_list_free(keywords);
	}

        /* remove packet from network if it has expired */
	if (gh->ttl == 1)
		goto skip_query;

        /* store the new packet */
	rmsg = newtella_malloc(sizeof *rmsg);

	rmsg->head = newtella_malloc(sizeof *(rmsg->head));
	*(rmsg->head) = *gh;

	rmsg->length = GN_HEADER_SIZE + gh->length;
	rmsg->policy = RP_ALL_EXC;
	rmsg->con    = con;
	rmsg->time   = time((time_t *)NULL);

	/* route msg */
	route_msg_add(rmsg, buf);

 skip_query:
	newtella_free(gquery->name);
 	newtella_free(gquery);

	return gh->length;
}

int handle_search_response(struct newtella_connection *con, struct gnutella_header *gh, char *buf)
{
	struct gnutella_hits *ghits;
	struct gnutella_hit  *ghit;
	struct file_found    *found;
	struct squery        *query;
	struct message       *msg;
	struct message       *rmsg;

	gint    i;
	gint    last_found      = gl_options->files_found;
	gint    forus           = 0;
	gchar   guid_for_push[GUID_LEN];
	gchar   vendor_id[4];
	gchar   open_data_size;
	gchar   open_data_byte[2];
	GSList *l;

	/* GTK stuff */
	gchar *dummy[6] = { "", "", "", "", "", "" };

	/* check if we have seen the response */
	if (route_msg_find(gh->guid, GNUTELLA_SEARCHRESPONSE, gh->length)) {
		con->total_received_dropped++;
		return gh->length;
	} else
		con->total_handled++;

	/* is it for us? */
	for (l = gl_queries; l; l = l->next) {
		query = (struct squery *) l->data;
		if (guidcmp(query->guid, gh->guid))
			forus = 1;
	}

	if (!forus)
		goto notforus;

    	/* store the guid to use it for push */
	//guid_for_push = newtella_malloc(GUID_LEN);
	memcpy(&guid_for_push, buf+gh->length-16, 16);

	ghits = newtella_malloc(sizeof *ghits);
	GNUTELLA_HITS_GRAB(ghits, buf)

	buf += GN_HITS_SIZE;
	ghit = newtella_malloc(sizeof *ghit);

	for (i=0; i<ghits->n; i++) {

		/* check if we reached the maximum results limit */
		if (gl_options->max_search_results == gl_options->files_found)
			goto skip;

		GNUTELLA_HIT_GRAB(ghit, buf)

		/* check if we really have an ending */
			if (file_has_ending(ghit->name)) {
			found = newtella_malloc(sizeof(struct file_found));
			found->file_name = newtella_malloc(strlen(ghit->name)+1);
			strcpy(found->file_name, ghit->name);

			guidcpy(found->guid, guid_for_push);
			found->index = ghit->index;
			found->size  = ghit->size;
			found->port  = ghits->port;
			bzero(found->ip, sizeof(found->ip));
			memcpy(found->ip, ghits->ip, sizeof(found->ip));
			found->speed = ghits->speed;

			if (host_is_private(found->ip, found->port))
				found->push = 1;
			else
				found->push = 0;

			gl_results = g_list_append(gl_results, found);
			gl_options->files_found++;

			gtk_clist_append(GTK_CLIST(search_list), dummy);

		}

		/* go to next hit */
		buf += GN_HIT_SIZE + strlen(ghit->name);
		while (memcmp(buf++, "\0", 1));

		/* data between the 2 '\n's */
		while (memcmp(buf++, "\0", 1));

		newtella_free(ghit->name);
	}

	/* no trailer */
	if (guidcmp(buf, guid_for_push))
		goto skip;

	/* parse the Trailer of the extended QHD */
	memcpy(&vendor_id, buf, 4);
	vendor_id[4] = '\0';

	buf += 4;

	memcpy(&open_data_size, buf, 1);
	if (open_data_size == 2) {
		memcpy(&open_data_byte[0], buf+1, 1);
		memcpy(&open_data_byte[1], buf+2, 1);

		rate_result_set(last_found, open_data_byte, vendor_id);
	}

 skip:
	newtella_free(ghit);
	newtella_free(ghits);

	gui_update_found_results(last_found, translate_vendor_id(vendor_id));

	return gh->length;
 notforus:

        /* remove packet from network if it has expired */
	if (gh->ttl == 1)
		return gh->length;

	/* check if we haven't seen the search related to the result set */
        msg = route_msg_find(gh->guid, GNUTELLA_SEARCH, 0);

	if (msg == NULL || msg->con == NULL)
		return gh->length;

        /* ok, we have to route it */
	rmsg = newtella_malloc(sizeof *rmsg);

	rmsg->head = newtella_malloc(sizeof *(rmsg->head));
	*(rmsg->head) = *gh;

	guidcpy(msg->guid, buf+gh->length-16);
	rmsg->length = GN_HEADER_SIZE + gh->length;
	rmsg->policy = RP_ONLY_TO;
	rmsg->con    = msg->con;
	rmsg->time   = time((time_t *)NULL);

	/* route msg */
	route_msg_add(rmsg, buf);

	return gh->length;
}

int handle_packet(struct newtella_connection *con, struct gnutella_header *gh, char *buf)
{
	switch (gh->f) {
	case GNUTELLA_INIT:
		return handle_init(con, gh);
		break;
	case GNUTELLA_INITRESPONSE:
		return handle_init_response(con, buf, gh);
		break;
	case GNUTELLA_PUSHREQUEST:
		return handle_push(con, gh, buf);
		break;
	case GNUTELLA_SEARCH:
		return handle_search(con, gh, buf);
		break;
	case GNUTELLA_SEARCHRESPONSE:
		return handle_search_response(con, gh, buf);
		break;
	default:
		break;
	}

	return 0;
}

void newtella_connect(gpointer gdata, gint s, GdkInputCondition cond)
{
	struct newtella_connection *con = (struct newtella_connection *)gdata;
	gint r, option, size;

	g_return_if_fail(con);

	if (con->write_tag)
		gdk_input_remove(con->write_tag);

	if (cond == GDK_INPUT_EXCEPTION) {
		con_mark(con, GS_FAILED);
		return;
	}

	r = getsockopt(con->s, SOL_SOCKET, SO_ERROR, (void *) &option, &size);

	if (r == -1 || option) {
		/* failed to connect */
		con_mark(con, GS_FAILED);
		return;
	} else {
		con->state = GS_HANDSHAKE;
		if (con->type != CT_UPLOAD)
			gui_update_con_status(con);

		con->write_tag = gdk_input_add(con->s,
					       GDK_INPUT_WRITE | GDK_INPUT_EXCEPTION,
					       (GdkInputFunction) newtella_write,
					       (gpointer)con);

		/* try to guess our ip */
		if (host_is_private(gl_my_host_info->ip, gl_my_host_info->port))
			newtella_guess_ip(con->s);
	}

}

void newtella_write(gpointer gdata, gint s, GdkInputCondition cond)
{
	struct gpacket *packet;
	struct newtella_connection *con = (struct newtella_connection *)gdata;
	gint r;

	g_return_if_fail(con);

	if (con->write_tag)
		gdk_input_remove(con->write_tag);

	if (cond == GDK_INPUT_EXCEPTION) {
		con_mark(con, GS_FAILED);
		return;
	}

	switch (con->state) {
	case GS_HANDSHAKE:
		if (con->type == CT_OUTGOING) {

			con->write_tag = 0;

                        /* send outgoing handshake */
			r = send(con->s, "GNUTELLA CONNECT/0.4\n\n",
				 strlen("GNUTELLA CONNECT/0.4\n\n"), 0);

			if (r < 0)
				con_mark(con, GS_FAILED);

		}
		if (con->type == CT_UPLOAD) {

			packet = (struct gpacket *) g_list_last(con->queue)->data;

			r = send(con->s, packet->data, packet->len, 0);

			if (r == packet->len) {
				con->read_tag = gdk_input_add(con->s,
							      GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
							      (GdkInputFunction) newtella_read,
							      (gpointer)con);

			} else
				con_mark(con, GS_FAILED);


			con->queue = g_list_remove(con->queue, packet);
			newtella_free(packet->data);
			newtella_free(packet);
		}
		break;
	case GS_HOSTFEED:
	case GS_CONNECTED:
		while (g_list_length(con->queue)) {
			packet = (struct gpacket *) g_list_last(con->queue)->data;

			r = send(con->s, packet->data, packet->len, 0);

			if (r > 0)
				con->total_sent++;
			else {
				con->total_dropped_to_send++;
				break;
			}

			if (r < packet->len && r > 0) {
				memcpy(packet->data, packet->data+r, packet->len-r);
				packet->len -= r;
				break;
			}
			else
				con->queue = g_list_remove(con->queue, packet);

			newtella_free(packet->data);
			newtella_free(packet);

			con->last_time = time((time_t)NULL);
		};

		gui_update_con_status(con);

		con->write_tag = 0;

		break;
	case GS_FAILED:
		break;
	case GS_TIMEOUT:
		break;
	default:
		break;
	}

}

void newtella_read(gpointer gdata, gint s, GdkInputCondition cond)
{
	struct newtella_connection *con = (struct newtella_connection *)gdata;
	struct gnutella_header *gh;
	guint16 bytes_readed = 0, bytes_to_read = 0,
		bytes_parsed = 0, bytes_left = 0,
		bytes_done = 0;
	char *p;

	g_return_if_fail(con);

	if (cond == GDK_INPUT_EXCEPTION) {
		con_mark(con, GS_FAILED);
		gdk_input_remove(con->read_tag);
		return;
	}

	bytes_to_read = bytes_avail(con->s);

	if (!bytes_to_read || bytes_to_read > MAX_PACKET_LENGTH) {
		con_mark(con, GS_FAILED);
		gdk_input_remove(con->read_tag);
		return;
	}

	con->b = con->buffer + con->buffer_len;
	if (((con->b-con->buffer)+bytes_to_read) > MAX_PACKET_LENGTH) {
		/* FIXME */
		con_mark(con, GS_FAILED);
		gdk_input_remove(con->read_tag);
		return;
	}

	con->buffer_len += bytes_to_read;
	bytes_readed = con_recv_packet(con, bytes_to_read);

	if (bytes_readed != bytes_to_read) {
		con_mark(con, GS_FAILED);
		gdk_input_remove(con->read_tag);
		return;
	}

	con->last_time = time((time_t) NULL);

	p = con->buffer;
	gh = newtella_malloc(sizeof *gh);

	do {
		if ((con->buffer_len - bytes_parsed) < 0)
			g_print("CRASH again: %d\n", bytes_left);

		bytes_left = con->buffer_len - bytes_parsed;

		switch (con->state) {
		case GS_FAILED:
			gdk_input_remove(con->read_tag);
			newtella_free(gh);
			return;
		case GS_TIMEOUT:
			gdk_input_remove(con->read_tag);
			newtella_free(gh);
			return;
		case GS_HOSTFEED:
			newtella_free(gh);
			if (g_list_length(con->queue) == 0)
				con_mark(con, GS_FAILED);
			return;
		case GS_CONNECTED:
			if (bytes_left >= GN_HEADER_SIZE)
				GNUTELLA_HDR_GRAB(gh, p)
			else {
				memcpy(con->buffer, p, bytes_left);
				con->buffer_len = bytes_left;
				goto skip;
			}

			if (gh->length > (bytes_left-GN_HEADER_SIZE)) {
				memcpy(con->buffer, p, bytes_left);
				con->buffer_len = bytes_left;
				goto skip;
			}

			if (gh->length > (MAX_PACKET_LENGTH  - con->buffer_len)) {
				g_print("DANGER! plen: %d buflen: %d\n",
					gh->length, con->buffer_len);

				con_mark(con, GS_FAILED);
				gdk_input_remove(con->read_tag);
				return;
			}

			p += GN_HEADER_SIZE;
			bytes_parsed += GN_HEADER_SIZE;

			if (newtella_packet_sucks(gh)) {
				con->total_bad_received++;
				if (gh->length + (p - con->buffer) < MAX_PACKET_LENGTH)
					bytes_done += gh->length;
				else {
					con_mark(con, GS_FAILED);
					gdk_input_remove(con->read_tag);
					return;
				}
			}

			if (bytes_left > gl_options->con_speed*100 &&
			    (gh->f == GNUTELLA_INIT || gh->f == GNUTELLA_SEARCH)) {
				con->total_received_dropped++;
				bytes_done += gh->length;
			} else if (bytes_left > MAX_BYTES_PER_SECOND*10) {
				con->total_received_dropped++;
				bytes_done += gh->length;
			} else
				bytes_done = handle_packet(con, gh, p);

			p += bytes_done;
			bytes_parsed += bytes_done;

			break;
		case GS_HANDSHAKE:
			if (newtella_handshake_sucks(p, bytes_left)) {
				con_mark(con, GS_FAILED);
				return;
			}
			/* outgoing connection waiting to establish */
			if (handle_connect_response(con, con->buffer)) {
				p += strlen("GNUTELLA OK\n\n");
				bytes_parsed += strlen("GNUTELLA OK\n\n");
				break;
			} /* incoming connection waiting to establish */
			else if (handle_connect(con, con->buffer)) {
				p += strlen("GNUTELLA CONNECT/0.4\n\n");
				bytes_parsed += strlen("GNUTELLA CONNECT/0.4\n\n");
				break;
			} /* download connection waiting to establish */
			else if (handle_get_request(con, con->buffer)) {
				upload_start(con, p);
				return;
			}

			bytes_parsed = con->buffer_len;
			break;
		default:
			break;
		}

	} while ((bytes_parsed < con->buffer_len) && bytes_parsed && bytes_left);

	if (bytes_parsed == con->buffer_len)
		con_reset_buffer(con);

	/* gui update */
	gui_update_con_status(con);

 skip:

	newtella_free(gh);
}

int newtella_sched(void)
{
	GList *iter;
	struct newtella_connection *con;

	gint now = time((time_t)NULL);

 	iter = g_list_first(gl_con);
	while (iter) {
		con = iter->data;
		if (!con)
			break;

		switch (con->state) {
		case GS_FAILED:
			/* remove failed connections */
			if (now - con->last_time > CON_REMOVE)
				con_remove(con);
			break;
		case GS_HANDSHAKE:
			if (now - con->last_time > 2*CON_REMOVE)
				con_remove(con);
			break;
		case GS_TIMEOUT:
			/* remove timout connections */
			if (now - con->last_time > CON_REMOVE)
				con_remove(con);
			break;
		case GS_WAITINGCONNECT:
			/* mark timeout connections */
			if (now - con->last_time > CON_REMOVE)
				con_mark(con, GS_TIMEOUT);
			break;
		case GS_CONNECTED:
			/* send ping to idle connections */
			if (now - con->last_time > CON_IDLE) {
				if (g_list_length(con->queue))
					con_mark(con, GS_TIMEOUT);
				else
					/* try to wake up */
					gnutella_send_ping(con, 1);
			}
			/* kill too bad connections */
			if (con->total_bad_received > (guint32)(con->total_handled/2))
				con_mark(con, GS_FAILED);
			break;
		default:
			break;
		}

		iter = g_list_next(iter);
	}

	if (gl_options->connected < gl_options->min_con && gl_options->auto_connect){
  		con_activate();
	}

	return 1;
}
