/* $Id: connectionManager.cpp,v 1.16 2004/01/02 03:54:01 fesnel Exp $ */
/*******************************************************************************
 *   This program is part of a library used by the Archimedes email client     * 
 *                                                                             *
 *   Copyright : (C) 1995-1998 Gennady B. Sorokopud (gena@NetVision.net.il)    *
 *               (C) 1995 Ugen. J. S. Antsilevich (ugen@latte.worldbank.org)   *
 *               (C) 1998-2004 by the Archimedes Project                       *
 *                   http://sourceforge.net/projects/archimedes                *
 *                                                                             *
 *             --------------------------------------------                    *
 *                                                                             *
 *   This program is free software; you can redistribute it and/or modify      *
 *   it under the terms of the GNU Library 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 Library General Public License for more details.                      *
 *                                                                             *
 *   You should have received a copy of the GNU Library 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.  *
 *                                                                             *
 ******************************************************************************/

#include "fmail.h"
#include "openssl.h"
#include "connection.h"
#include "connection_ssl.h"
#include "connectionManager.h"

connectionManager::connectionManager()
{
#if defined WITH_SSL
    init_SSL();
#endif
}

connectionManager::~connectionManager()
{
	list<connection *>::iterator from;

	for (from = Connections.begin(); from != Connections.end(); ++from)
		delete (*from)->get();
}

class connection * connectionManager::get_conn(int sock) {

	list<connection *>::iterator from;
	for(from = Connections.begin(); from != Connections.end(); ++from) {
		if((*from)->getSock() == sock) {
			return (*from)->get();
		}
	}
	return NULL;
}

class connection * connectionManager::new_cinfo(int sock, char * host) {
	class connection * conn = get_conn(sock);

	if(conn != NULL) {
		return conn;
	} else {
		conn = new connection(sock,host);
		if(conn != NULL) {
			Connections.push_front(conn);
		} else {
			display_msg(MSG_FATAL, "account", "Can not create a new connection");
		}
	}
	return conn;
}

void connectionManager::del_cinfo(int sock) {
	class connection * conn;

	if(!sock)
		return;

	if((conn = get_conn(sock)) != NULL) {
		if(logging & LOG_NET)
			display_msg(MSG_LOG, "connect", "Disconnected from %s", conn->getHost().c_str());
		Connections.remove(conn);
		delete conn;
	} else {
		fprintf(stderr,"Internal Error del_cinfo: tried to delete a nonexistant connection\n");
	}
}

int connectionManager::host_connect(char hostname[MAX_HOST], char *service, char *protocol) {
	int sock;
	struct servent *srv;
	struct hostent *hp;
	struct sockaddr_in in_socket;
	struct sockaddr_in *isock = &in_socket;

	char c_host[MAX_HOST];
	char c_service[10];
	char c_prot[6];
	char *p;
	int pport, res;
    class connection_ssl * conn = NULL;

	if(!hostname)
		strcpy(c_host, "127.0.0.1");
	else
		strncpy(c_host, hostname, MAX_HOST);

	if(!service)
		strcpy(c_service, "110");
	else
		strncpy(c_service, service, 10);

	if(!protocol)
		strcpy(c_prot, "tcp");
	else
		strncpy(c_prot, protocol, 6);

	p = c_service;
	while(*p && isdigit(*p))
		p++;

	if(*p) {
		srv = getservbyname(c_service, c_prot);
		if(srv == NULL) {
			display_msg(MSG_WARN, "connect", "Unknown service %s/%s",
						c_service, c_prot);
			return -1;
		}
		pport = srv->s_port;
	} else {
		pport = atoi(c_service);
		pport = htons(pport);
	}

	if((hp = gethostbystring(c_host)) == NULL) {
		display_msg(MSG_WARN, "connect", "Unknown host %s", c_host);
		return -1;
	}

	if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		display_msg(MSG_WARN, "connect", "Can not open socket");
		return -1;
	}

	conn = (connection_ssl *) new_cinfo(sock, c_host);
	if(conn == NULL) {
		close(sock);
		return -1;
	}

	if((res = fcntl(sock, F_GETFL)) == -1) {
		display_msg(MSG_WARN, "connect", "fcntl F_GETFL failed");
		close(sock);
		return -1;
	}

	if(fcntl(sock, F_SETFL, res | O_NONBLOCK) == -1) {
		display_msg(MSG_WARN, "connect",
					"fcntl F_SETFL, O_NONBLOCK failed");
		close(sock);
		return -1;
	}

	bzero((void *) isock, sizeof(struct sockaddr_in *));

	isock->sin_family = hp->h_addrtype;

	memcpy(&isock->sin_addr, hp->h_addr_list[0], hp->h_length);
	isock->sin_port = pport;

	display_msg(MSG_STAT, NULL, "Connecting to %s ...", c_host);
	if(logging & LOG_NET)
		display_msg(MSG_LOG, "connect", "Connecting to %s", c_host);

	if(connect(sock, (struct sockaddr *) isock, sizeof *isock) == -1) {
		if(errno != EINPROGRESS) {
			display_msg(MSG_WARN, "connect", "Connect to %s failed",
						c_host);
			if(logging & LOG_NET)
				display_msg(MSG_LOG, "connect", "Connect to %s failed",
							c_host);
			display_msg(MSG_STAT, NULL, "");
			close(sock);
			return -1;
		}
	}

	conwait:
	if((res = my_check_io_forms(sock, 1, SOCKET_TIMEOUT)) < 0) {
		if(res == -2)
			display_msg(MSG_LOG, "connect",
						"Connect to %s aborted by user", c_host);
		else {
			display_msg(MSG_WARN, "connect", "Connect to %s failed",
						c_host);
			if(logging & LOG_NET)
				display_msg(MSG_LOG, "connect", "Connect to %s failed",
							c_host);
		}
		display_msg(MSG_STAT, NULL, "");
		close(sock);
		return -1;
	}

	if(connect(sock, (struct sockaddr *) isock, sizeof *isock) == -1) {
		switch(errno) {
			case EISCONN:
				break;

			case EINPROGRESS:
			case EALREADY:
				goto conwait;
				break;

			default:
				display_msg(MSG_WARN, "connect", "Connect to %s failed",
							c_host);
				if(logging & LOG_NET)
					display_msg(MSG_LOG, "connect", "Connect to %s failed",
								c_host);
				display_msg(MSG_STAT, NULL, "");
				close(sock);
				return -1;
				break;
		}
	}

	if(logging & LOG_NET)
		display_msg(MSG_LOG, "connect", "Connected to %s", c_host);
	display_msg(MSG_STAT, NULL, "");

	return sock;
}

int connectionManager::get_ipc_sock(struct sockaddr_in *sin) {
	int sock;
#ifdef  _linuxalpha_
	long sinlen;
#else
	int sinlen;
#endif

#if defined(FreeBSD) || defined(NetBSD) || defined(Solaris) || defined(HPUX10)
	typedef int socklen_t;
#endif

	if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		display_msg(MSG_LOG, "ipc", "can not get socket");
		return -1;
	}

	sinlen = sizeof(struct sockaddr_in);
	bzero((char *) sin, sinlen);

	sin->sin_port = htons(0);

	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = INADDR_ANY;
	if(bind(sock, (struct sockaddr *) sin,
			sizeof(struct sockaddr_in)) < 0) {
		display_msg(MSG_LOG, "ipc", "bind failed");
		return -1;
	}

	if(getsockname(sock, (struct sockaddr *) sin, (unsigned int *) & sinlen)
	   < 0) {
		display_msg(MSG_LOG, "ipc", "getsockname failed");
		return -1;
	}
	return sock;
}
