/*
 * -------------------------------------------------------
 * Copyright (C) 2003-2007 Tommi Saviranta <wnd@iki.fi>
 *      (C) 2002 Lee Hardy <lee@leeh.co.uk>
 *      (C) 1998-2002 Sebastian Kienzl <zap@riot.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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* ifdef HAVE_CONFIG_H */

#include "client.h"
#include "conntype.h"
#include "error.h"
#include "messages.h"
#include "miau.h"
#include "irc.h"
#include "qlog.h"
#include "tools.h"
#include "channels.h"
#include "dcc.h"
#include "privlog.h"
#include "commands.h"
#include "chanlog.h"
#include "log.h"
#include "common.h"

#include <string.h>



client_info	i_client;
clientlist_type	c_clients;


void client_drop_real(connection_type *client, char *reason,
		const int echo, const int dying);



/*
 * "work" must be global so when miau_command is called, "work" can be freed
 * if users want to quit miau.
 */
static char *work = NULL;	/* Temporary copy of input. */



/*
 * Drop client from bouncer.
 *
 * If client == NULL, drop all clients.
 */
void
client_drop(connection_type *client, char *reason, const int msgtype,
		const int echo, const char *awaymsg)
{
	if (client == NULL) {
		/* Drop all clients. */
		while (c_clients.clients->head != NULL) {
			client_drop_real((connection_type *)
					c_clients.clients->head->data, reason,
					echo, msgtype == DISCONNECT_DYING);
		}
	} else {
		client_drop_real(client, reason, echo,
				msgtype == DISCONNECT_DYING);
	}

	switch (msgtype) {
		case DISCONNECT_ERROR:
			error("%s", reason);
			break;
		case DISCONNECT_DYING:
			report("%s%s", CLNT_DIE, reason);
			break;
		case DISCONNECT_REPORT:
			report("%s", reason);
			break;
	}

	/* Reporting number of clients is irrevelant if only one is allowed. */
	if (cfg.maxclients != 1) {
		report(CLNT_CLIENTS, c_clients.connected);
	}

	if (c_clients.connected == 0) {
		clients_left(cfg.usequitmsg ? awaymsg : NULL);
	} else if (cfg.autoaway == 1) {
		if (cfg.usequitmsg == 1 && awaymsg != NULL){
			set_away(awaymsg);
		} else {
			set_away(cfg.awaymsg);
		}
	}
} /* void client_drop(connection_type *, char *, const int, const int,
	const char *) */



/*
 * Drop client from bouncer.
 */
void
client_drop_real(connection_type *client, char *reason, const int echo,
		const int dying)
{
	llist_node	*node;
	/* We could skip testing reason, but lets play safe. */
	if (echo && reason != NULL) {
		/* Set back to blocking. */
		sock_setblock(c_server.socket);
		if (reason[0] == ':') {
			irc_write(client, "ERROR %s", reason);
		} else {
			irc_write(client, dying
					? MIAU_USERKILLED : MIAU_CLOSINGLINK,
					reason);
		}
	}
	sock_close(client);
	node = llist_find((void *) client, c_clients.clients);
	xfree(node->data);
	llist_delete(node, c_clients.clients);
	c_clients.connected--;
} /* void client_drop_real(connection_type *, char *, const int, const int) */



static void
cmd_part_action(channel_type *chan, char *reason)
{
	/*
	 * ":" is always there after channel name so this works even with
	 * mIRC when connected to Undernet.
	 */
	irc_mwrite(&c_clients, ":%s!%s@%s PART %s :%s",
			status.nickname, i_client.username, i_client.hostname,
			chan->name, reason);
	channel_rem(chan, LIST_PASSIVE);
}



static void
cmd_part(char *par0, char *par1)
{
	/* User wants to leave list of channels. */
	channel_type *chan;
	char *name;

	name = strtok(par0, ":");	/* Set EOS for channels. */
	name = strtok(par0, ",");
	while (name != NULL) {
		chan = channel_find(name, LIST_PASSIVE);
		if (chan == NULL) {
			irc_mwrite(&c_clients, ":miau %d %s %s :%s",
					ERR_NOSUCHCHANNEL,
					status.nickname,
					name,
					IRC_NOSUCHCHAN);
		} else {
			cmd_part_action(chan, par1 + 1);
		}
		name = strtok(NULL, ",");
	}
} /* static void cmd_part(char *par0, char *par1) */



static void
cmd_join(char *par0, char *par1)
{
	/* user wants to join a list of channels */
	char *chan;	/* channel name */
	char *key;	/* channel key */
	char *chanseek;
	char *keyseek;
	
	if (par0 != NULL && xstrcmp(par0, "0") == 0) {
		/* user want to leave all channels */
		LLIST_WALK_H(passive_channels.head, channel_type *);
			cmd_part_action(data, "");
		LLIST_WALK_F;

		return;
	}

	chan = par0;
	key = par1;
	keyseek = NULL;

	while (chan != NULL && *chan != '\0') {
		chanseek = strchr(chan, ',');
		if (chanseek != NULL) {
			*chanseek = '\0';
			chanseek++;
		}
		if (key != NULL && *key != '\0') {
			keyseek = strchr(key, ',');
			if (keyseek != NULL) {
				*keyseek = '\0';
				keyseek++;
			}
		}
		irc_mnotice(&c_clients, status.nickname,
				MIAU_JOIN_QUEUE, chan);
		channel_add(chan, key, LIST_PASSIVE);
		chan = chanseek;
		key = keyseek;
	}
} /* static void cmd_join(char *par0, char *par1) */



#ifdef DCCBOUNCE
static int
cmd_privmsg_ctcp(char *par0, char *par1)
{
	char dcct[IRC_MSGLEN];
	char *ret;

	if (cfg.dccbounce == 0) {
		return 1;
	}

	upcase(par1);
	if (xstrncmp(par1, ":\1DCC", 5) != 0) {
		return 1;
	}

	strncpy(dcct, par1 + 1, IRC_MSGLEN);
	ret = dcc_initiate(dcct, IRC_MSGLEN, 1);

	if (ret != NULL) {
		irc_write(&c_server, "PRIVMSG %s %s", par0, dcct);
		return 0;
	} else {
		return 1;
	}
} /* static int cmd_privmsg_dcc(char *par0, char *par1) */
#endif /* ifdef DCCBOUNCE */



#ifdef NEED_LOGGING
static int
cmd_privmsg(char *par0, char *par1)
{
	int is_chan;
	if (par0 == NULL) {
		return 1;
	}

	is_chan = channel_is_name(par0);

#ifdef PRIVLOG
	if (is_chan == 0) {
		/*
		 * We could say
		 * 
		if ((c_clients.connected > 0 && (cfg.privlog & 0x02))
				|| (c_clients.connected == 0
					&& cfg.privlog == PRIVLOG_DETACHED)) {
		 *
		 * ...but do disconnected clients really talk?-)
		 *
		 * Therefore we say the following instead:
		 */
		if (cfg.privlog != 0) {
			privlog_write(par0, PRIVLOG_OUT, 
					CMD_PRIVMSG + MINCOMMANDVALUE,
					par1 + 1);
		}
	}
#endif /* PRIVLOG */

#ifdef CHANLOG
	if (is_chan != 0) {
		channel_type *chan;
		chan = channel_find(par0, LIST_ACTIVE);
		if (chan != NULL && chanlog_has_log(chan, LOG_MESSAGE)) {
			char *t;
			t = log_prepare_entry(status.nickname, par1 + 1);
			if (t == NULL) {
				chanlog_write_entry(chan, LOGM_MESSAGE,
						get_short_localtime(),
						status.nickname, par1 + 1);
			} else {
				chanlog_write_entry(chan, "%s", t);
			}
		}
	}
#endif /* CHANLOG */

	return 1;
} /* static int cmd_privmsg(char *par0, char *par1) */
#endif /* ifdef NEED_LOGGING */



static int
cmd_away(const char *orig, char *par0, char *par1)
{
	if (par0 == NULL) {
		FREE(status.awaymsg);
		/* Not away / no custom message. */
		status.awaystate &= ~AWAY & ~CUSTOM;
	} else {
		/* This should be safe by now. */
		char *t = strchr(orig, (int) ' ') + 2;
#ifdef EMPTYAWAY
		xfree(status.awaymsg);
		status.awaymsg = xstrdup(t);
		/* Away / custom message. */
		status.awaystate |= AWAY | CUSTOM;
#else /* EMPTYAWAY */
		FREE(status.awaymsg);
		if (*t != '\0') {
			status.awaymsg = xstrdup(t);
			/* Away / custom message. */
			status.awaystate |= AWAY | CUSTOM;
		} else {
			/* Not away / no custom message. */
			status.awaystate &= ~AWAY & ~CUSTOM;
		}
#endif /* EMPTYAWAY */
	}

	return 1;
} /* static int cmd_away(char *par0, char *par1) */



static void
pass_cmd(connection_type *client, char *cmd, char *par0, char *par1,
		const char *bufbu)
{
	const char *buf;
	/*
	 * Use buffer from client unless bufbu is set. Having bufbu set
	 * means client is invalid and should not be used.
	 */
	if (bufbu != NULL) {
		buf = bufbu;
	} else {
		buf = client->buffer;
	}

	if (i_server.connected == 2) {
		int msg;

		/* Pass the message to server. */
		irc_write(&c_server, "%s", buf);

		msg = ((xstrcmp(cmd, "PRIVMSG") == 0) ||
			(xstrcmp(cmd, "NOTICE") == 0)) ? 1 : 0;
		/* Echo the meesage to other clients. */
		if (msg == 1) {
			char tmp[IRC_MSGLEN + 1];
			const char *cl_out = NULL;
			llist_node *iter;

			if (cfg.privmsg_fmt != NULL &&
					channel_is_name(par0) == 0) {
				int i;
				/* if the message is too long to fit the buffer
				 * then it's a shame. sorry. */
				i = snprintf(tmp, IRC_MSGLEN,
						cfg.privmsg_fmt,
						status.nickname);
				tmp[i] = tmp[IRC_MSGLEN] = '\0';
				strncat(tmp, par1 + 1, 510 - i);
				cl_out = tmp;
			}

			for (iter = c_clients.clients->head; iter != NULL;
					iter = iter->next) {
				connection_type *conn =
					(connection_type *) iter->data;
				if (iter->data == client) {
					continue;
				}

				if (cl_out == NULL) {
					irc_write(conn, ":%s!%s %s",
							status.nickname,
							status.idhostname,
							buf);
				} else {
					irc_write(conn, ":%s!%s %s %s %s",
							par0,
							"dummy",
							cmd,
							par0,
							cl_out);
				}
			}
		}

#ifdef QUICKLOG
		/* Also put it in quicklog. If necessary, or course. */
		if (cfg.flushqlog == 0 && par0 != NULL && msg == 1) {
			qlog_write(0, ":%s!%s@%s %s",
					status.nickname,
					i_client.username,
					i_client.hostname,
					buf);
		}
#endif /* QUICKLOG */
	} else {
		irc_notice(client, status.nickname, CLNT_NOTCONNECT);
	}
} /* static void pass_cmd(connection_type *client, char *cmd,
		char *par0, char *par1, const char *bufbu) */



int
client_read(connection_type *client)
{
	int c_status;
	char *command, *param1, *param2;
	char *orig;
	int pass;

	c_status = irc_read(client);

	if (c_status <= 0) {
		/* Something went unexpected. */
		return c_status;
	}

	/* Ok, got something. */
	if (client->buffer[0] == '\0') {
		/* Darn, got nothing after all. */
		return 0;
	}

	work = xstrdup(client->buffer);
	orig = NULL;

	command = strtok(work, " ");

	if (command == NULL) {
		xfree(work);
		work = NULL;
		return 0;
	}

	param1 = strtok(NULL, " ");
	param2 = strtok(NULL, "\0");
	if (param1 != NULL && *param1 == ':') {
		param1++;
	}

	upcase(command);

	/* Commands from client when not connected to IRC-server. */
	if (i_server.connected != 2) {
		/* Willing to leave channels. */
		if (xstrcmp(command, "PART") == 0) {
			cmd_part(param1, param2);
		}
		/* Willing to join or leave channels. */
		else if (xstrcmp(command, "JOIN") == 0) {
			cmd_join(param1, param2);
		}
	}

	pass = 1; /* pass by default */

	if (xstrcmp(command, "PRIVMSG") == 0) {
		if (param2 == NULL) {
#ifdef ENDUSERDEBUG
			enduserdebug("client_read(): PRIVMSG, param2 = NULL");
#endif /* ifdef ENDUSERDEBUG */
		}
#ifdef DCCBOUNCE
		else if (param2[0] == ':' && param2[1] == '\1') {
			pass = cmd_privmsg_ctcp(param1, param2);
		}
#endif /* ifdef DCCBOUNCE */
#ifdef NEED_LOGGING
		else {
			pass = cmd_privmsg(param1, param2);
		}
#endif /* ifdef NEED_LOGGING */
	}

	else if (xstrcmp(command, "PONG") == 0) {
		pass = 0;	/* Munch munch munch. */
	}

	else if (xstrcmp(command, "PING") == 0) {
		irc_write(client, ":%s PONG %s :%s",
				i_server.realname,
				param2 != NULL ? param2 : i_server.realname,
				param1 != NULL ? param1 : status.nickname);
		pass = 0;
	}

	else if (xstrcmp(command, "MIAU") == 0) {
		miau_commands(param1, param2, client);
		pass = 0;
	}

	else if (xstrcmp(command, "QUIT") == 0) {
		const char *reason;

		orig = xstrdup(client->buffer);

		if (param1 != NULL) {
			reason = orig + (param1 - work);
		} else {
			reason = NULL;
		}
		/* (single client, message, report, echo) */
		client_drop(client, CLNT_LEFT, DISCONNECT_REPORT, 0, reason);
		pass = 0;
	}

	else if (xstrcmp(command, "AWAY") == 0) {
		pass = cmd_away(client->buffer, param1, param2);
	}

	else if (cfg.no_identify_capab == 1 && xstrcmp(command, "CAPAB") == 0) {
		if (xstrncasecmp(param1, "IDENTIFY-", 9) == 0 &&
				strlen(param1) >= 9) {
			/*
			 * freenode responds with an empty 290 if capability is
			 * not supported. I suppose I can do the same.
			 */
			irc_write(client, ":%s 290 %s :",
					i_server.realname,
					status.nickname);
			pass = 0;
		}
	}

	else if (i_server.connected != 2) {
		pass = 0;
	}

	if (pass == 1) {
		/* orig is set only if client was detached */
		if (orig != NULL) {
			pass_cmd(client, command, param1, param2, orig);
		} else {
			pass_cmd(client, command, param1, param2,
					client->buffer);
		}
	}
	xfree(orig);
	xfree(work);
	work = NULL;

	/* All ok. */
	return 0;
} /* int client_read(connection_type *) */



/*
 * Free copy of user's command.
 *
 * This function is a bit ugly and could be considered useless. When
 * client_read() is called, a copy of user's command is made, and call to
 * miau_command() may be made. If command was "DIE", this memory would leave
 * unfreed before quitting miau. So, this function is needed so that miau.c
 * can ask this memory to be freed without returning to client_read().
 */
void
client_free(void)
{
	if (work != NULL) {
		xfree(work);
		work = NULL;
	}
} /* void_client_free(void) */


syntax highlighted by Code2HTML, v. 0.9.1