/*

oer - IRC bot

See ftp://nic.funet.fi/pub/unix/irc/docs/FAQ.gz section 11 for the
definition of the word bot.

Copyright (C) 2000-2001 EQU <equ@equnet.org>

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.

*/

#include "oer-common.h"
#include "oer.h"
#include "parse.h"
#include "ds.h"
#include "misc.h"

/* prototype definitions */
int serverconnection(void);
int registerconnection(void);
int handleserverdata(void);
int processtimeds(void);
void establishconnection(void);
void reconnect(void);

void reconnect()
{
	char stringbuffer[WRITE_BUFFER_LENGTH + 1];
#ifdef DEBUG
        oer_debug(OER_DEBUG_FLOOD, "reconnect()\n");
#endif
	/* check if connected */
	if(mystate->current_server == NULL) {
		return;
	}
	if(mystate->reconnect == OER_RECONNECT_NONE) {
		return;
	}
	switch(mystate->reconnect) {
	case OER_RECONNECT_ADMIN:
		snprintf(stringbuffer, WRITE_BUFFER_LENGTH, "QUIT :%s\n", mystate->signoff);
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "reconnect->%s", stringbuffer);
#endif
		if(write(mystate->sockfd, stringbuffer, strlen(stringbuffer)) < 0) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "reconnect->write failed: %s\n", strerror(errno));
#endif
		}
		mystate->current_server->tx += strlen(stringbuffer);
		close(mystate->sockfd);
		mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_BEGIN;
		mystate->current_server->connected = 0;
		mystate->current_server = NULL;
		initall();
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "reconnect->sleeping 30 seconds to avoid throttling\n");
#endif
		sleep(OER_RECONNECT_DELAY);
		establishconnection();
		if(mystate->bailout) {
			exit(EXIT_FAILURE);
		}
		mystate->reconnect = OER_RECONNECT_NONE;
		break;
	case OER_RECONNECT_STONED:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "main->current server is stoned, jumping to next one if available\n");
#endif
		close(mystate->sockfd);
		mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_BEGIN;
		mystate->current_server->connected = 0;
		mystate->current_server = NULL;
		initall();
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "reconnect->sleeping 30 seconds to avoid throttling\n");
#endif
		sleep(OER_RECONNECT_DELAY);
		establishconnection();
		if(mystate->bailout) {
			exit(EXIT_FAILURE);
		}
		mystate->reconnect = OER_RECONNECT_NONE;
		break;
	case OER_RECONNECT_ERROR:
		if(mystate->quitting) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "reconnect->%s exiting\n", mystate->progname);
#endif
			close(mystate->sockfd);
			exit(EXIT_SUCCESS);
		}
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "reconnect->failed to read/write from/to current server\n");
		oer_debug(OER_DEBUG_INFO, "reconnect->jumping to next server if available\n");
#endif
		close(mystate->sockfd);
		mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_BEGIN;
		mystate->current_server->connected = 0;
		mystate->current_server = NULL;
		initall();
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "reconnect->sleeping 30 seconds to avoid throttling\n");
#endif
		sleep(OER_RECONNECT_DELAY);
		establishconnection();
		if(mystate->bailout) {
			exit(EXIT_FAILURE);
		}
		mystate->reconnect = OER_RECONNECT_NONE;
		break;
	}
}

void establishconnection()
{
#ifdef DEBUG
        oer_debug(OER_DEBUG_FLOOD, "establishconnection()\n");
#endif
	/* loop until a fatal error */
	while(!mystate->bailout) {
		mystate->now = time(NULL);
		switch(serverconnection()) {
		case OER_SERVERCONNECTION_ERR_SOCKET:
#ifdef DEBUG
			oer_debug(OER_DEBUG_FATAL, "establishconnection->socket() failed, exiting.\n");
#endif
			mystate->bailout = 1;
			return;
		case OER_SERVERCONNECTION_ERR_GETHOSTNAME:
#ifdef DEBUG
			oer_debug(OER_DEBUG_FATAL, "establishconnection->gethostname() failed, exiting.\n");
#endif
			mystate->bailout = 1;
			return;
		case OER_SERVERCONNECTION_ERR_GETHOSTBYNAME_RETRY:
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "establishconnection->gethostbyname() temporary error, retrying.\n");
#endif
			sleep(OER_WAIT_BETWEEN_CONNECTION_ATTEMPTS);
			break;
		case OER_SERVERCONNECTION_ERR_GETSERVER_RETRY:
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "establishconnection->getserver() temporary error, retrying.\n");
#endif
			sleep(OER_WAIT_BETWEEN_CONNECTION_ATTEMPTS);
			break;
		case OER_SERVERCONNECTION_ERR_GETHOSTBYNAME:
#ifdef DEBUG
			oer_debug(OER_DEBUG_FATAL, "establishconnection->gethostbyname() fatal error, exiting.\n");
#endif
			mystate->bailout = 1;
			return;
		case OER_SERVERCONNECTION_ERR_BIND:
#ifdef DEBUG
			oer_debug(OER_DEBUG_FATAL, "establishconnection->bind() fatal error, exiting.\n");
#endif
			mystate->bailout = 1;
			return;
		case OER_SERVERCONNECTION_ERR_CONNECT_RETRY:
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "establishconnection->connect() temporary error, retrying.\n");
#endif
			sleep(OER_WAIT_BETWEEN_CONNECTION_ATTEMPTS);
			break;
		case OER_SERVERCONNECTION_ERR_CONNECT:
#ifdef DEBUG
			oer_debug(OER_DEBUG_FATAL, "establishconnection->connect() fatal error, exiting.\n");
#endif
			mystate->bailout = 1;
			return;
		case OER_SERVERCONNECTION_ALREADY_CONNECTED:
		case OER_SERVERCONNECTION_EVERYTHING_OK:
			return;
		}
	}
}

int serverconnection()
{
	struct hostent *hptr;
	struct sockaddr_in servaddr;
#ifdef DEBUG
        oer_debug(OER_DEBUG_FLOOD, "serverconnection()\n");
#endif
	/* check if we are already connected */
	if(mystate->current_server != NULL) {
		if(mystate->current_server->connected) {
			return OER_SERVERCONNECTION_ALREADY_CONNECTED;
		}
	}
	if((mystate->sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_ERROR, "serverconnection->socket() failed: %s\n", strerror(errno));
#endif
		return OER_SERVERCONNECTION_ERR_SOCKET;
	}
	memset(&servaddr, 0, sizeof(struct sockaddr_in));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = INADDR_ANY;
	/* if we don't have a vhost, just use the default system hostname */
	if(!mystate->vhost) {
		if(gethostname(mystate->host, HOSTLEN) < 0) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "serverconnection->gethostname() failed: %s\n", strerror(errno));
#endif
			close(mystate->sockfd);
			return OER_SERVERCONNECTION_ERR_GETHOSTNAME;
		}
	}
	if((hptr = (struct hostent *) gethostbyname(mystate->host)) == NULL) {
#ifndef _NO_H_ERRNO
#ifdef DEBUG
		oer_debug(OER_DEBUG_ERROR, "serverconnection->gethostbyname() for %s failed (h_errono = %d)\n", mystate->host, h_errno);
#endif
		if(h_errno == TRY_AGAIN) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "serverconnection->gethostbyname() non-fatal error\n");
#endif
			close(mystate->sockfd);
			return OER_SERVERCONNECTION_ERR_GETHOSTBYNAME_RETRY;
		} else {
			close(mystate->sockfd);
			return OER_SERVERCONNECTION_ERR_GETHOSTBYNAME;
		}
#else
		/* i'm feeling lucky, and lazy too :) */
#ifdef DEBUG
		oer_debug(OER_DEBUG_ERROR, "serverconnection->gethostbyname() unknown error\n");
#endif
		close(mystate->sockfd);
		return OER_SERVERCONNECTION_ERR_GETHOSTBYNAME_RETRY;
#endif
	}
	memcpy((char *) &servaddr.sin_addr, hptr->h_addr, hptr->h_length);
	if(mystate->vhost) {
		/* bind is only necessary for vhost */
		if(bind(mystate->sockfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) < 0) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "serverconnection->bind() failed: %s (check your vhost setting)\n", strerror(errno));
#endif
			close(mystate->sockfd);
			return OER_SERVERCONNECTION_ERR_BIND;
		}
	}
	if((mystate->current_server = getserver()) == NULL) {
		/* we didn't get a server this time but we will eventually
		   once the used & stoned flags are cleared. Unless
		   ofcourse the servers are permanently unreachable. */
		close(mystate->sockfd);
		return OER_SERVERCONNECTION_ERR_GETSERVER_RETRY;
	}
	mystate->current_server->used = 1;
#ifdef DEBUG
	oer_debug(OER_DEBUG_INFO, "serverconnection->using server %s port %d\n", mystate->current_server->serverhost, mystate->current_server->serverport);
#endif
	if((hptr = (struct hostent *) gethostbyname(mystate->current_server->serverhost)) == NULL) {
#ifndef _NO_H_ERRNO
#ifdef DEBUG
		oer_debug(OER_DEBUG_ERROR, "serverconnection->gethostbyname() for %s failed (h_errno = %d)\n", mystate->current_server->serverhost, h_errno);
#endif
		if(h_errno == NO_ADDRESS || h_errno == HOST_NOT_FOUND || h_errno == TRY_AGAIN) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "serverconnection->switching to next server if available\n");
#endif
			close(mystate->sockfd);
			return OER_SERVERCONNECTION_ERR_GETHOSTBYNAME_RETRY;
		}
#else
		/* i'm feeling lucky, and lazy too :) */
#ifdef DEBUG
		oer_debug(OER_DEBUG_ERROR, "serverconnection->gethostbyname() unknown error\n");
#endif
		close(mystate->sockfd);
		return OER_SERVERCONNECTION_ERR_GETHOSTBYNAME_RETRY;
#endif
        }
	memcpy(&servaddr.sin_addr, hptr->h_addr_list[0], sizeof(struct in_addr));
	servaddr.sin_port = htons(mystate->current_server->serverport);
	if(connect(mystate->sockfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr_in)) < 0) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_ERROR, "serverconnection->connect() failed: %s\n", strerror(errno));
#endif
		if(errno == ECONNREFUSED || errno == ETIMEDOUT || errno == ENETUNREACH || errno == EHOSTUNREACH) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "serverconnection->connect() non-fatal error\n");
			oer_debug(OER_DEBUG_INFO, "serverconnection->switching to next server if available\n");
#endif
			close(mystate->sockfd);
			return OER_SERVERCONNECTION_ERR_CONNECT_RETRY;
		}
		close(mystate->sockfd);
		return OER_SERVERCONNECTION_ERR_CONNECT;
	}
	mystate->current_server->connected = 1;
        mystate->current_server->rx = 0;
        mystate->current_server->tx = 0;
	mystate->current_server->linkup = mystate->now;
	return OER_SERVERCONNECTION_EVERYTHING_OK;
}

int registerconnection()
{
	char stringbuffer[WRITE_BUFFER_LENGTH + 1];
        char newuser[USERLEN + 1];
	char *ptr;
	struct passwd *pwd;
#ifdef DEBUG
        oer_debug(OER_DEBUG_FLOOD, "registerconnection()\n");
#endif
	/* check if connected */
	if(mystate->current_server == NULL) {
		return OER_REGISTERCONNECTION_EVERYTHING_OK;
	}
	/* check if registered */
	if(mystate->current_server->registered == OER_REGISTERCONNECTION_STATUS_DONE) {
		return OER_REGISTERCONNECTION_EVERYTHING_OK;
	}
	if(mystate->current_server->registered == OER_REGISTERCONNECTION_STATUS_BEGIN) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "registerconnection->registering on the server\n");
#endif
		if(strlen(mystate->current_server->password)) {
			snprintf(stringbuffer, WRITE_BUFFER_LENGTH, "PASS %s\n", mystate->current_server->password);
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "registerconnection->%s", stringbuffer);
#endif
			if(write(mystate->sockfd, stringbuffer, strlen(stringbuffer)) < 0) {
#ifdef DEBUG
				oer_debug(OER_DEBUG_ERROR, "registerconnection->write failed: %s\n", strerror(errno));
#endif
				return OER_REGISTERCONNECTION_ERR_WRITE;
			}
			mystate->current_server->tx += strlen(stringbuffer);
		}
		snprintf(stringbuffer, WRITE_BUFFER_LENGTH, "NICK %s\n", (mystate->use_alt_nick) ? mystate->altnick : mystate->nick);
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "registerconnection->%s", stringbuffer);
#endif
		if(write(mystate->sockfd, stringbuffer, strlen(stringbuffer)) < 0) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "registerconnection->write failed: %s\n", strerror(errno));
#endif
			return OER_REGISTERCONNECTION_ERR_WRITE;
		}
		mystate->current_server->tx += strlen(stringbuffer);
		mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_NICK;
		return OER_REGISTERCONNECTION_EVERYTHING_OK;
	}
	if(mystate->current_server->registered == OER_REGISTERCONNECTION_STATUS_NICK) {
		if(!mystate->customuser) {
			if((pwd = getpwuid(getuid())) == NULL) {
				return OER_REGISTERCONNECTION_ERR_GETPWUID;
			}
			strncpy(mystate->user, pwd->pw_name, USERLEN);
		}
		ptr = mystate->user;
		while(*ptr == '~') {
			/* server has prefixed our ident with ~
			   strip ~ before saving to avoid double ~ */
			ptr++;
		}
		strncpy(newuser, ptr, USERLEN);
		strncpy(mystate->user, newuser, USERLEN);
		snprintf(stringbuffer, WRITE_BUFFER_LENGTH, "USER %s %s %s :%s\n", mystate->user, mystate->host, mystate->current_server->serverhost, mystate->realname);
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "registerconnection->%s", stringbuffer);
#endif
		if(write(mystate->sockfd, stringbuffer, strlen(stringbuffer)) < 0) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "registerconnection->write failed: %s\n", strerror(errno));
#endif
			return OER_REGISTERCONNECTION_ERR_WRITE;
		}
		mystate->current_server->tx += strlen(stringbuffer);
		mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_USER;
		return OER_REGISTERCONNECTION_EVERYTHING_OK;
	}
	return OER_REGISTERCONNECTION_STATUS_ERROR;
}

int handleserverdata()
{
	int cont;
	int i;
	char chars[2];
	char stringbuffer[READ_BUFFER_LENGTH + 1];
        char pong[STRINGLEN + 1];
        size_t br;
        char *ptr;
#ifdef DEBUG
        oer_debug(OER_DEBUG_FLOOD, "handleserverdata()\n");
#endif
	i = 0;
	cont = 1;
        do {
                if((br = read(mystate->sockfd, chars, 1)) < 0) {
			/* error while reading from server socket */
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "handleserverdata->read failed: %s\n", strerror(errno));
#endif
			return OER_HANDLESERVERDATA_ERR_READ;
                }
		if(br == 0) {
			/* nothing/eof was returned, bail out */
			cont = 0;
		}
		if(br > 0 && i < READ_BUFFER_LENGTH) {
			/* check for newline */
			chars[1] = '\0';
			stringbuffer[i++] = chars[0];
			if(chars[0] == '\n') {
				cont = 0;
			}
		}
	} while(cont);
	if(!i) {
		/* zero bytes was read, bail out */
		return OER_HANDLESERVERDATA_ERR_NOTHING_READ;
	}
	stringbuffer[i] = '\0';
	/* only count reads from the server as link activity */
	mystate->current_server->lastping = mystate->now;
	mystate->current_server->rx += strlen(stringbuffer);
	/* here we log all the server traffic, prefix with timestamp */
	if(index(mystate->flags, (int)'l') != NULL) {
		fprintf(mystate->logfp, "%lu %s", (unsigned long) mystate->now, stringbuffer);
	}
	if((ptr = (char *) strstr(stringbuffer, "PING :")) != NULL) {
		snprintf(pong, STRINGLEN, "PONG :%s", ptr + 6);
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "handleserverdata->%s", pong);
#endif
		if(write(mystate->sockfd, pong, strlen(pong)) < 0) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_ERROR, "handleserverdata->write failed: %s\n", strerror(errno));
#endif
			return OER_HANDLESERVERDATA_ERR_WRITE;
		}
		mystate->current_server->tx += strlen(pong);
	}
	else {
		/* everything else but pings */
		parseirc(stringbuffer);
	}
        return OER_HANDLESERVERDATA_EVERYTHING_OK;
}

int processtimeds()
{
	int got_timed;
	int actions;
	struct channel *this;
	struct timed *t1;
	struct timed *t2;
	char stringbuffer[WRITE_BUFFER_LENGTH + 1];
#ifdef DEBUG
        oer_debug(OER_DEBUG_FLOOD, "processtimeds()\n");
#endif
	/* check if connected */
	if(mystate->current_server == NULL) {
		return OER_PROCESSTIMEDS_EVERYTHING_OK;
	}
	/* check if registered */
	if(mystate->current_server->registered != OER_REGISTERCONNECTION_STATUS_DONE) {
		return OER_PROCESSTIMEDS_EVERYTHING_OK;
	}
	if(mystate->netjoining) {
		/* don't do any actions while net is joining */
		return OER_PROCESSTIMEDS_EVERYTHING_OK;
	}
        for(t1 = mystate->timeds, actions = 0; t1 != NULL; t1 = t2) {
		if(t1->at > mystate->now) {
			/* DO NOT REMOVE */
			t2 = t1->next;
			continue;
		}
		if(actions >= OER_ACTIONS_PER_MAINLOOP) {
			return OER_PROCESSTIMEDS_EVERYTHING_OK;
		}
		got_timed = 0;
		switch(t1->type) {
		case OER_TIMED_TYPE_KICK:
			if((this = getchptr(t1->channel)) == NULL) {
				return OER_PROCESSTIMEDS_EVERYTHING_OK;
			}
			/* this check is necessary because KICK messages are
			   scheduled before executed and it is possible that
			   there was manual prevention (!unlock) */
			if(!this->locked.unlocked) {
				snprintf(stringbuffer, WRITE_BUFFER_LENGTH, "%s\n", t1->command);
				got_timed = 1;
			}
			break;
		case OER_TIMED_TYPE_NORMAL:
			snprintf(stringbuffer, WRITE_BUFFER_LENGTH, "%s\n", t1->command);
			got_timed = 1;
			break;
		}
		if(got_timed) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "processtimeds->%s", stringbuffer);
#endif
			if(write(mystate->sockfd, stringbuffer, strlen(stringbuffer)) < 0) {
#ifdef DEBUG
				oer_debug(OER_DEBUG_ERROR, "processtimeds->write failed: %s\n", strerror(errno));
#endif
				return OER_PROCESSTIMEDS_ERR_WRITE;
			}
			actions++;
			mystate->current_server->tx += strlen(stringbuffer);
		}
		t2 = t1->next;
		timed_del(t1);
		t1 = t2;
	}
	return OER_PROCESSTIMEDS_EVERYTHING_OK;
}
