/*

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 "ds.h"
#include "misc.h"
#include "reg.h"
#include "network.h"

/* prototype definitions */
void parseirc(char *);
void parsecommand(char *, char *, char *, char *);
void parsemsg(char *, char *, char *, char *);

void parseirc(char *serveroutput)
{
	int i;
	int kicked;
	int bans;
	int deops;
	int modetype;
	int msgcount;
	int ppos;
	int restpos;
	int nrestpos;
	int tpos;
	char prefix[STRINGLEN + 1];
	char command[TINYSTRINGLEN + 1];
	char params[STRINGLEN + 1];
	char outstring[STRINGLEN + 1];
	char outstring2[WRITE_BUFFER_LENGTH + 1];
	char outstring3[WRITE_BUFFER_LENGTH + 1];
	char channel[CHANLEN + 1];
	char mode[CHANLEN + 1];
	char user[HOSTLEN + 1];
	char host[HOSTLEN + 1];
	char userhost[HOSTLEN + 1];
	char nick[NICKLEN + 1];
	char nick2[NICKLEN + 1];
	char target[NICKLEN + 1];
	char m;
	char *restptr;
	char *ptr;
	struct channel *this;
	struct chanuser *cu;
	struct timeval tv1;
	struct timeval tv2;

	if(serveroutput[0] != ':') {
		/* must be a command */
		return;
	}
	striplf(serveroutput);
	ppos = 0;
	if((ppos = parse(serveroutput, ppos, " ", prefix, STRINGLEN, 0)) < 0) {
		return;
	}
	if((ppos = parse(serveroutput, ppos, " ", command, TINYSTRINGLEN, 0)) < 0) {
		return;
	}
	if((ppos = parse(serveroutput, ppos, " ", params, STRINGLEN, 0)) < 0) {
		return;
	}
	/* the rest of the serveroutput is serveroutput + ppos */
	restptr = serveroutput + ppos;
	/* the rest of the line depends on the command/numeric */
	if(!strcasecmp(command, "001")) {
		mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_DONE;
		return;
	}
	if(!strcasecmp(command, "002")) {
		return;
	}
	if(!strcasecmp(command, "003")) {
		return;
	}
	if(!strcasecmp(command, "004")) {
		return;
	}
	if(!strcasecmp(command, "005")) {
		/* at least undernet and ircnet return the
		   server flags this way */
		return;
	}
	if(!strcasecmp(command, "221")) {
		return;
	}
	if(!strcasecmp(command, "250")) {
		/* efnet local clients */
		return;
	}
	if(!strcasecmp(command, "251")) {
		return;
	}
	if(!strcasecmp(command, "252")) {
		return;
	}
	if(!strcasecmp(command, "253")) {
		return;
	}
	if(!strcasecmp(command, "254")) {
		return;
	}
	if(!strcasecmp(command, "255")) {
		return;
	}
	if(!strcasecmp(command, "265")) {
		/* efnet extension, local users */
		return;
	}
	if(!strcasecmp(command, "266")) {
		/* efnet extension, global users */
		return;
	}
	if(!strcasecmp(command, "302")) {
		/* first char is ':' */
		restptr++;
		restpos = 0;
		while(1) {
			nrestpos = parse(restptr, restpos, " ", outstring, STRINGLEN, 0);
			if(nrestpos == restpos || nrestpos < 0) {
				break;
			}
			restpos = nrestpos;
			memset(nick, 0, NICKLEN + 1);
			tpos = 0;
			if((tpos = parse(outstring, tpos, "=", nick, NICKLEN, 1)) < 0) {
				return;
			}
			if(!strlen(nick)) {
				/* USERHOST returns emptyline for invalid nicks */
				return;
			}
			/* check for ircop, remove trailing star */
			if(nick[strlen(nick) - 1] == '*') {
				nick[strlen(nick) - 1] = '\0';
				/* isoper */
				for(this = mystate->channels; this != NULL; this = this->next) {
					if(isonchan(this->name, nick)) {
						changeuser(this->name, nick, 1, -1, -1);
					}
				}
			}
			/* userhost is at outstring + tpos + 1 (first char is '+' or '-') */
			oer_debug(OER_DEBUG_INFO, "parseirc(302)->nick %s has userhost %s\n", nick, outstring + tpos + 1);
			setuserhost(nick, outstring + tpos + 1);
		}
		return;
	}
	if(!strcasecmp(command, "303")) {
		/* this is the response to our ison query */
		return;
	}
	if(!strcasecmp(command, "332")) {
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
		if(this->topics != NULL) {
			/* only parse topic if oer has none for this channel.
			   this is a kludge to avoid empty topics in undernet */
			/* we could add a check: if server's topics differs from
			   ours, we reset it to our's */
			return;
		}
		/* the topic is at restptr + restpos + 1 (first char is ':') */
		gettopic(channel, restptr + restpos + 1);
		oer_debug(OER_DEBUG_INFO, "parseirc(332)->%s topic is %s\n", channel, restptr + restpos + 1);
		return;
	}
	if(!strcasecmp(command, "333")) {
		return;
	}
	if(!strcasecmp(command, "341")) {
		/* returned when inviting someone */
		return;
	}
	if(!strcasecmp(command, "353")) {
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", mode, CHANLEN, 0)) < 0) {
			return;
		}
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		oer_debug(OER_DEBUG_INFO, "parseirc(353)->%s\n", channel);
		while(1) {
			nrestpos = parse(restptr, restpos, " ", nick, NICKLEN, 0);
			if(nrestpos == restpos || nrestpos < 0) {
				break;
			}
			restpos = nrestpos;
			if(nick[0] == ':') {
				strncpy(nick2, nick + 1, NICKLEN);
				strncpy(nick, nick2, NICKLEN);
			}
			oer_debug(OER_DEBUG_INFO, "parseirc(353)->%s\n", nick);
			m = '\0';
			if(nick[0] == '*' || nick[0] == '@' || nick[0] == '+') {
				m = nick[0];
				strncpy(nick2, nick + 1, NICKLEN);
				strncpy(nick, nick2, NICKLEN);
			}
			if(userjoined(channel, nick, NULL, (m == '*') ? 1 : 0, (m == '@') ? 1 : 0, (m == '+') ? 1 : 0) == NULL) {
				oer_debug(OER_DEBUG_INFO, "parseirc(353)->userjoined() returned NULL\n");
			}
			if(isme(nick) && m == '@') {
				iamop(channel, 1);
			}
		}
		listchanusers();
		return;
	}
	if(!strcasecmp(command, "366")) {
		/* 366 is sent after /NAMES, means the join is complete */
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		oer_debug(OER_DEBUG_INFO, "parseirc(366)->channel %s\n", channel);
		isjoined(channel);
		return;
	}
	if(!strcasecmp(command, "372")) {
		return;
	}
	if(!strcasecmp(command, "375")) {
		return;
	}
	if(!strcasecmp(command, "376")) {
		return;
	}
	if(!strcasecmp(command, "401")) {
		oer_debug(OER_DEBUG_INFO, "parseirc(401)->%s\n", restptr);
		return;
	}
	if(!strcasecmp(command, "404")) {
		oer_debug(OER_DEBUG_INFO, "parseirc(404)->%s\n", restptr);
		return;
	}
	if(!strcasecmp(command, "422")) {
		oer_debug(OER_DEBUG_INFO, "parseirc(422)->%s\n", restptr);
		return;
	}
	if(!strcasecmp(command, "433")) {
		if(mystate->use_alt_nick) {
                        oer_debug(OER_DEBUG_INFO, "parseirc(%s)->both our primary and secondary nicknames are taken, generating a random nick\n", command);
                        snprintf(mystate->nick, NICKLEN, "oer-%d", getrandom(9999));
                        mystate->use_alt_nick = 0;
			mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_BEGIN;
                        return;
                }
		oer_debug(OER_DEBUG_INFO, "parseirc(%s)->our nickname %s is already in use, will try altnick %s\n", command, mystate->nick, mystate->altnick);
		mystate->use_alt_nick = 1;
		mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_BEGIN;
		return;
	}
	if(!strcasecmp(command, "437")) {
		/* ERR_UNAVAILRESOURCE, handle both cases */
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		oer_debug(OER_DEBUG_INFO, "parseirc(%s)->%s\n", command, channel);
		if((this = getchptr(channel)) == NULL) {
			/* no channel meaning nick is unavailable */
			if(mystate->use_alt_nick) {
				oer_debug(OER_DEBUG_INFO, "parseirc(%s)->both our primary and secondary nicknames are taken, generating a random nick\n", command);
				snprintf(mystate->nick, NICKLEN, "oer-%d", getrandom(9999));
				mystate->use_alt_nick = 0;
				mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_BEGIN;
				return;
			}
			oer_debug(OER_DEBUG_INFO, "parseirc(%s)->our nickname %s is already in use, will try altnick %s\n", command, mystate->nick, mystate->altnick);
			mystate->use_alt_nick = 1;
			mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_BEGIN;
			return;
		}
		/* we got channel, reset joining flag */
		this->rejoin_at = mystate->now + OER_DELAY_BETWEEN_REJOINS;
		this->joining = 0;
                return;
        }
	if(!strcasecmp(command, "439")) {
		oer_debug(OER_DEBUG_INFO, "parseirc(439)->%s\n", restptr);
                return;
        }
	if(!strcasecmp(command, "441")) {
		oer_debug(OER_DEBUG_INFO, "parseirc(441)->%s\n", restptr);
                return;
        }
	if(!strcasecmp(command, "464")) {
		oer_debug(OER_DEBUG_INFO, "parseirc(464)->server reports bad password (%s)\n", (strlen(mystate->current_server->password)) ? mystate->current_server->password : "null");
		mystate->current_server->registered = OER_REGISTERCONNECTION_STATUS_BEGIN;
		return;
	}
	if(!strcasecmp(command, "467")) {
		/* channel key already set */
		return;
	}
	/* couldn't join channel, reset joining flag */
	if(!strcasecmp(command, "471") || !strcasecmp(command, "473") \
	   || !strcasecmp(command, "474") || !strcasecmp(command, "475")) {
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		oer_debug(OER_DEBUG_INFO, "parseirc(%s)->%s\n", command, channel);
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		this->rejoin_at = mystate->now + OER_DELAY_BETWEEN_REJOINS;
		this->joining = 0;
                return;
	}
	if(!strcasecmp(command, "472")) {
		/* unknown channel mode */
		return;
	}
	if(!strcasecmp(command, "482")) {
		/* you are not op */
		return;
	}
	if(!strcasecmp(command, "484")) {
		/* undernet extension, protected user */
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", nick, NICKLEN, 0)) < 0) {
			return;
		}
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		oer_debug(OER_DEBUG_INFO, "parseirc(484)->nick %s on %s is a IRC operator\n", nick, channel);
		changeuser(channel, nick, 1, -1, -1);
		return;
	}
	if(!strcasecmp(command, "513")) {
		/* bad ping, this probably means that it took
		   us too long to answer the server ping */
		mystate->reconnect = OER_RECONNECT_ERROR;
		return;
	}
	if(!strcasecmp(command, "JOIN")) {
		/* build the necessary strings */
		if(!strncasecmp(params, ":", 1)) {
			strncpy(channel, params + 1, CHANLEN);
		} else {
			/* IRCNET doesn't put the preceeding ":" on
			   netjoins */
			strncpy(channel, params, CHANLEN);
		}
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, HOSTLEN);
		if(isme(nick)) {
			/* strip user & host */
			strncpy(user, prefix + 1 + tpos, HOSTLEN);
			ptr = index(user, '@');
			memset(ptr, 0, HOSTLEN - strlen(user) + 1);
			ptr = index(prefix + 1 + tpos, '@');
			strncpy(host, ptr + 1, HOSTLEN);
			/* check if the server agrees with our perception
			   of our host */
			if(strcasecmp(mystate->host, host)) {
				strncpy(mystate->host, host, HOSTLEN);
				oer_debug(OER_DEBUG_INFO, "parseirc(JOIN)->the server says our host is %s\n", mystate->host);
			}
			/* check if the server agrees with our perception
			   of our user id */
			if(strcasecmp(mystate->user, user)) {
				strncpy(mystate->user, user, USERLEN);
				oer_debug(OER_DEBUG_INFO, "parseirc(JOIN)->the server says our user id is %s\n", mystate->user);
			}
			/* we are not interested in our own joins */
			return;
		}
		if(userjoined(channel, nick, userhost, 0, 0, 0) == NULL) {
			oer_debug(OER_DEBUG_INFO, "parseirc(JOIN)->userjoined() returned NULL\n");
		}
		addnewjoin(channel, mystate->now, nick, userhost);
		if(haschanflags(channel, "S")) {
			updateseen(channel, nick, userhost);
		}
		if(mystate->netjoining) {
			return;
		}
		if(burstjoins(channel) && !mystate->netjoining) {
			oer_debug(OER_DEBUG_INFO, "parseirc(JOIN)->net join detected, scheduling netjoin jobs at %lu\n", mystate->now + 30);
			mystate->netjoining = 1;
			mystate->postnj_checks_at = mystate->now + OER_NETJOIN_DELAY;
			return;
		}
		listchanusers();
		kicked = 0;
		/* let's do the on join checks, first auto rejoin */
		if(amiop(channel) && checkforautorejoin(channel, nick, userhost)) {
			kicked = 1;
			changetobek(channel, nick, 1);
		}
		if(!kicked) {
			/* then part-rejoin */
			if(amiop(channel) && checkforpartrejoin(channel, nick, userhost)) {
				kicked = 1;
				changetobek(channel, nick, 1);
				resetparts(channel, nick, userhost);
			}
		}
		if(!kicked) {
			/* finally bad nick */
			if(amiop(channel) && checkforbadnick(channel, nick, userhost)) {
				changetobek(channel, nick, 1);
				kicked = 1;
			}
		}
		if(amiop(channel) && !kicked && haschanflags(channel, "v") && hasuserflags(channel, userhost, "v")) {
			addnewmm(channel, mystate->now,  "+v", nick);
		}
		if(amiop(channel) && !kicked && haschanflags(channel, "O") && isopa(userhost)) {
			addnewmm(channel, mystate->now, "+o", nick);
		}
		if(amiop(channel) && !kicked && haschanflags(channel, "o") && isop(channel, userhost)) {
			addnewmm(channel, mystate->now, "+o", nick);
		}
		if(!kicked && haschanflags(channel, "A")) {
			sendadverts(channel, nick, userhost);
		}
		if(lastquote(channel) < OER_QUOTE_INTERVAL) {
			return;
		}
		if((getjoincount(channel, nick) > 1) || !haschanflags(channel, "q") || hasuserflags(channel, userhost, "x")) {
			return;
		}
		msgcount = getondiskmsgcount(channel, nick);
		if(!kicked && msgcount > 0) {
			strncpy(nick2, nick, NICKLEN);
			gettimeofday(&tv1, NULL);
			if(getrandommsg(channel, nick2, NICKLEN, getrandom(msgcount), outstring2, HUGESTRINGLEN) < 0) {
				return;
			}
			gettimeofday(&tv2, NULL);
			snprintf(outstring3, HUGESTRINGLEN, "\"%s\" (c) %s", outstring2, nick2);
			sendreply(channel, 1, 0, 0, outstring3);
			setlastquote(channel, mystate->now);
			if(tv2.tv_sec == tv1.tv_sec) {
				snprintf(outstring3, HUGESTRINGLEN, "%ld usecs", tv2.tv_usec - tv1.tv_usec);
			} else {
				/* tv2 > tv1, thats for sure */
				snprintf(outstring3, HUGESTRINGLEN, "%ld secs and %ld usecs", tv2.tv_sec - tv1.tv_sec, ABS(tv2.tv_usec - tv1.tv_usec));
			}
			if(ABS(tv2.tv_usec - tv1.tv_usec) + ((tv2.tv_sec - tv1.tv_sec) * 1000000) > OER_ALLOWED_QUOTE_TIME_USEC) {
				oer_debug(OER_DEBUG_WARNING, "parseirc(JOIN)->getrandommsg() took %s (longer than OER_ALLOWED_QUOTE_TIME_USEC)\n", outstring3);
			} else {
				oer_debug(OER_DEBUG_WARNING, "parseirc(JOIN)->getrandommsg() took %s\n", outstring3);
			}
		}
		return;
	}
	if(!strcasecmp(command, "PRIVMSG")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, HOSTLEN);
		strncpy(channel, params, CHANLEN);
		/* the message starts at pos + 1 ... */
		parsemsg(nick, userhost, channel, restptr + 1);
		return;
	}
	if(!strcasecmp(command, "QUIT")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, HOSTLEN);
		/* quit doesn't provide the channel, we have to check all channels */
		for(this = mystate->channels; this != NULL; this = this->next) {
			if(!isonchan(this->name, nick)) {
				continue;
			}
			addnewpart(this->name, mystate->now, nick, userhost);
			if(!userleft(this->name, nick, userhost)) {
				oer_debug(OER_DEBUG_INFO, "parseirc(QUIT)->userleft() returned NULL\n");
			}
			if(usersonchan(this->name) == 1 && !amiop(this->name)) {
				oer_debug(OER_DEBUG_INFO, "parseirc(QUIT)->cycling %s to gain ops\n", this->name);
				snprintf(outstring2, WRITE_BUFFER_LENGTH, "PART %s\n", this->name);
				addnewtimed(mystate->now, OER_TIMED_IRC_COMMAND, OER_TIMED_IRC_COMMAND_DUMMY, outstring2, NULL, NULL, NULL);
			}
		}
		listchanusers();
		return;
	}
	if(!strcasecmp(command, "PART")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, HOSTLEN);
		strncpy(channel, params, CHANLEN);
		if(isme(nick)) {
			/* no reason to remove from channel, we will just
			   init it */
			initchannel(channel);
			iwaskicked(channel);
			return;
		}
		addnewpart(channel, mystate->now, nick, userhost);
		if(!userleft(channel, nick, userhost)) {
			oer_debug(OER_DEBUG_INFO, "parseirc(PART)->userleft() returned NULL\n");
		}
		if(usersonchan(channel) == 1 && !amiop(channel)) {
			oer_debug(OER_DEBUG_INFO, "parseirc(PART)->cycling %s to gain ops\n", channel);
			snprintf(outstring, STRINGLEN, "PART %s\n", channel);
			addnewtimed(mystate->now, OER_TIMED_IRC_COMMAND, OER_TIMED_IRC_COMMAND_DUMMY, outstring, NULL, NULL, NULL);
		}
		listchanusers();
		return;
	}
	if(!strcasecmp(command, "NICK")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, HOSTLEN);
		oer_debug(OER_DEBUG_INFO, "nickchange->%s is now known as %s, changing on all channels\n", nick, params + 1);
		/* if user changes nick while we are doing HOSTNAME queries
		   on channel join, we will end up with userhost = NULL for the user */
		if(ishostless(nick) == 1) {
			sethostquerystatus(nick, 0);
		}
		/* nick doesn't provide the channel, we have to check all channels */
		for(this = mystate->channels; this != NULL; this = this->next) {
			nickchange(this->name, nick, params + 1);
			/* we change all channels but check only those that are needed (why?) */
			if(isonchan(this->name, params + 1)) {
				if(amiop(this->name) && checkforbadnick(this->name, params + 1, userhost)) {
					changetobek(this->name, params + 1, 1);
				} else {
					/* this is a preventative measure. if a user tries
					   to avoid getting kicked on !lock by changing nick,
					   we will adapt */
					changetobek(this->name, params + 1, 0);
				}
			}
		}
		return;
	}
	if(!strcasecmp(command, "KILL")) {
		if(isme(params)) {
			oer_debug(OER_DEBUG_INFO, "parseirc(KILL)->I was killed by %s path %s\n", prefix + 1, restptr + 1);
		}
		return;
	}
	if(!strcasecmp(command, "KICK")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, HOSTLEN);
		strncpy(channel, params, CHANLEN);
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", target, NICKLEN, 0)) < 0) {
			return;
		}
		if(isme(target)) {
			oer_debug(OER_DEBUG_INFO, "parseirc(KICK)->I was kicked off %s by %s!%s\n", channel, nick, userhost);
			initchannel(channel);
			iwaskicked(channel);
			return;
		}
		/* KICK doesn't tell the host for the person that was kicked */
		if((cu = getcuptr(channel, target)) == NULL) {
                        /* we don't know the host, this is necessary because of
                           bogus/lagged server/service messages */
                        return;
                }
		oer_debug(OER_DEBUG_INFO, "parseirc(KICK)->%s!%s off %s by %s!%s\n", target, (cu->userhost == NULL) ? "(null)" : cu->userhost, channel, nick, userhost);
		addnewpart(channel, mystate->now, target, cu->userhost);
		if(!userleft(channel, target, cu->userhost)) {
			oer_debug(OER_DEBUG_INFO, "parseirc(KICK)->userleft() returned NULL\n");
		}
		if(usersonchan(channel) == 1 && !amiop(channel)) {
			oer_debug(OER_DEBUG_INFO, "parseirc(KICK)->cycling %s to gain ops\n", channel);
			snprintf(outstring2, WRITE_BUFFER_LENGTH, "PART %s\n", channel);
			addnewtimed(mystate->now, OER_TIMED_IRC_COMMAND, OER_TIMED_IRC_COMMAND_DUMMY, outstring2, NULL, NULL, NULL);
		}
		listchanusers();
		return;
	}
	if(!strcasecmp(command, "NOTICE")) {
		return;
	}
	if(!strcasecmp(command, "MODE")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		if(strlen(prefix + 1 + tpos)) {
			strncpy(userhost, prefix + 1 + tpos, HOSTLEN);
		} else {
			memset(userhost, 0, HOSTLEN);
		}
		strncpy(channel, params, CHANLEN);
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", mode, CHANLEN, 0)) < 0) {
			return;
		}
		if(mode[0] == ':') {
			/* our usermode */
			oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->usermode is %s\n", mode + 1);
			return;
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		/* channel modes have no target */
		if(strlen(restptr + restpos) > 0) {
			oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->%s %s on %s by %s!%s\n", mode, restptr + restpos, channel, nick, userhost);
		} else {
			oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->%s on %s by %s!%s\n", mode, channel, nick, userhost);
		}
		/* lame way to keep chanmodes, i need to code a proper modeline parser */
		if(!isme(nick) && (index(mode, (int)'p') != NULL || index(mode, (int)'s') != NULL || index(mode, (int)'i') != NULL || index(mode, (int)'t') != NULL || index(mode, (int)'n') != NULL || index(mode, (int)'m') != NULL || index(mode, (int)'l') != NULL || index(mode, (int)'k') != NULL)) {
			this->setchanmode = 1;			
		}
		i = 0;
		bans = 0;
		deops = 0;
		while(1) {
			nrestpos = parse(restptr, restpos, " ", target, NICKLEN, 0);
			if(nrestpos == restpos || nrestpos < 0) {
				break;
			}
			restpos = nrestpos;
			i++;
			modetype = nthmode(mode, i);
			switch(modetype) {
			case OER_NTHMODE_OP:
				changeuser(channel, target, -1, 1, -1);
				if(isme(target)) {
					iamop(channel, 1);
					channelsync(channel);
					continue;
				}
				if(!amiop(channel)) {
					continue;
				}
				if(haschanflags(channel, "u") && !isallowedop(channel, target)) {
					if(isq(nick)) {
						/* allow Q to set +o freely */
						continue;
					}
					oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->non-allowed op of nick %s on channel %s by %s!%s\n", target, channel, nick, userhost);
					addnewmm(channel, mystate->now, "-o", target);
				}
				break;
			case OER_NTHMODE_DOP:
				changeuser(channel, target, -1, 0, -1);
				if(isme(target)) {
					iamop(channel, 0);
				}
				deops++;
				break;
			case OER_NTHMODE_VOICE:
				changeuser(channel, target, -1, -1, 1);
				break;
			case OER_NTHMODE_DVOICE:
				changeuser(channel, target, -1, -1, 0);
				break;
			case OER_NTHMODE_BAN:
				snprintf(outstring, STRINGLEN, "%s!%s@%s", (mystate->use_alt_nick) ? mystate->altnick : mystate->nick, mystate->user, mystate->host);
				if(wild_match(target, outstring)) {
					oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->ban detected on me (%s)\n", target);
					if(isq(nick)) {
						/* allow Q to ban us */
						continue;
					}
					if(amiop(channel) && haschanflags(channel, "b")) {
						/* i am being banned, first kick then unban */
						if(!isme(nick) && !isserver(nick)) {
							snprintf(outstring, STRINGLEN, "KICK %s %s :do NOT ban %s, that's me!\n", channel, nick, target);
							addnewtimed(mystate->now, OER_TIMED_IRC_COMMAND, OER_TIMED_IRC_COMMAND_DUMMY, outstring, NULL, NULL, NULL);
						}
						addnewmm(channel, mystate->now, "-b", target);
					}
				}
				bans++;
				break;
			case OER_NTHMODE_UNBAN:
				if(amiop(channel) && haschanflags(channel, "e") && ispermban(channel, target)) {
					if(isq(nick)) {
						/* allow Q to unban */
						continue;
					}
					/* re-set the ban */
					oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->%s on channel %s tried to remove the permban %s\n", nick, channel, target);
					addnewmm(channel, mystate->now, "+b", target);
					if(!isserver(nick)) {
						snprintf(outstring, STRINGLEN, "do NOT remove %s ban from %s, it is in my permban list", target, channel);
						sendreply(nick, 0, 3, 0, outstring);

					}
				}
				/* sync bans. if a user has set ban *@*.se and there is a
				   permban *@*.swipnet.se, the latter has to be re-insert when
				   the former is manually removed */
				syncbans(channel);
				break;
			}
			if((deops > OER_MASSMODE_LIMIT) && (index(userhost, '@') != NULL) && !isme(nick)) {
				oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->mass modes detected on %s from %s\n", channel, nick);
				if(isq(nick)) {
					/* allow Q to do massmodes */
					continue;
				}
				if(amiop(channel) && haschanflags(channel, "m") && !isserver(nick)) {
					addnewmm(channel, mystate->now, "-o", nick);
					sendreply(nick, 0, 3, 0, "do NOT deop >3 users at a time");
					deops = 0;
					bans = 0;
				}
			}
		}
		return;
	}
	if(!strcasecmp(command, "TOPIC")) {
		return;
	}
	if(!strcasecmp(command, "INVITE")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, HOSTLEN);
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		oer_debug(OER_DEBUG_INFO, "parseirc(INVITE)->%s invited me to %s\n", nick, channel + 1);
		return;
	}
	oer_debug(OER_DEBUG_INFO, "parseirc(UNKNOWN)->%s\n", prefix);
	oer_debug(OER_DEBUG_INFO, "parseirc(UNKNOWN)->%s\n", command);
	oer_debug(OER_DEBUG_INFO, "parseirc(UNKNOWN)->%s\n", params);
	oer_debug(OER_DEBUG_INFO, "parseirc(UNKNOWN)->%s\n", restptr);
}

void parsemsg(char *nick, char *userhost, char *target, char *message)
{
	int ctcp;
	int ppos;
	int is_command;
	char active_nick[NICKLEN + 1];

	oer_debug(OER_DEBUG_INFO, "parsemsg->%s!%s target: %s message: %s\n", nick, userhost, target, message);
	ctcp = 0;
	if(isctcp(message)) {
		ctcp = whichctcp(message);
		switch(ctcp) {
		case OER_WHICHCTCP_ACTION:
			if(isme(target)) {
				/* bogus action */
				return;
			}
			oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = ACTION\n");
			break;
		case OER_WHICHCTCP_FINGER:
			oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = FINGER\n");
			parsectcp(nick, userhost, ctcp, message);
			break;
		case OER_WHICHCTCP_PING:
			oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = PING\n");
			parsectcp(nick, userhost, ctcp, message);
			break;
		case OER_WHICHCTCP_USERINFO:
                        oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = USERINFO\n");
                        parsectcp(nick, userhost, ctcp, message);
                        break;
		case OER_WHICHCTCP_VERSION:
			oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = VERSION\n");
			parsectcp(nick, userhost, ctcp, message);
			break;
		case OER_WHICHCTCP_INVALID:
			oer_debug(OER_DEBUG_INFO, "parsemsg->invalid CTCP from %s!%s target: %s\n", nick, userhost, target);
			break;
		}
	}
	if(ctcp && isme(target)) {
		/* we have already handled all personal CTCP messages */
		return;
	}
	if(isme(target)) {
		/* the message is sent to me, not to a channel */
		parsecommand(target, nick, userhost, message);
		return;
	}
	if(ctcp && ctcp != OER_WHICHCTCP_ACTION) {
		/* only ACTIONS are sent to channel & checked */
		return;
	}
	/* the message is a public message sent to a channel */
	if(amiop(target) && checkforansi(target, nick, userhost, message)) {
		return;
	}
	if(amiop(target) && checkforflood(target, nick, userhost, message)) {
		return;
	}
	if(amiop(target) && checkforbadword(target, nick, userhost, message)) {
		return;
	}
	/* only add this consequetive line iff the user
	   isn't being kicked already (lag between kick and last message) */
	if(!gettobek(target, nick)) {
		addnewpubmsg(target, mystate->now, nick, userhost, message);
	}
	if(ctcp) {
		/* we don't log ctcp (not even ACTIONS) */
		return;
	}
	/* check for command */
	is_command = 0;
	ppos = 0;
	strncpy(active_nick, mystate->nick, NICKLEN);
	if(mystate->use_alt_nick) {
		strncpy(active_nick, mystate->altnick, NICKLEN);
	}
	if(!strncasecmp(message, active_nick, strlen(active_nick))) {
		is_command = 1;
                ppos = strlen(active_nick);
                if(isspace((int)message[ppos])) {
                        while(isspace((int)message[ppos])) {
                                ppos++;
                        }
                }
        }
	if(!strncasecmp(message, mystate->prefix, strlen(mystate->prefix))) {
		is_command = 1;
                ppos = strlen(mystate->prefix);
                if(isspace((int)message[ppos])) {
                        while(isspace((int)message[ppos])) {
                                ppos++;
                        }
                }
        }
	if(is_command) {
		/* here is when we parse all commands */
		parsecommand(target, nick, userhost, message + ppos);
	}
	if(hasuserflags(target, userhost, "x")) {
		/* user's messages won't be saved */
		oer_debug(OER_DEBUG_INFO, "parsemsg->messages from %s to %s won't be saved\n", nick, target);
		return;
	}
	/* strip mIRC colors */
	stripmirc(message);
	/* strip all control chars from message */
	stripcntrl(message);
	/* replace :: with : in the message */
	safequote(message);
	/* update the channel last information */
	if(haschanflags(target, "L")) {
		updatelast(target, nick, userhost, message);
	}
}

void parsecommand(char *target, char *nick, char *userhost, char *commandline)
{
	int proceed;
	int which;
	int tochan;
        int counter;
	int msgcount;
	int ppos;
	int nppos;
        int prevppos;
	time_t uptime_seconds;
	time_t idle_seconds;
        char p1[STRINGLEN + 1];
        char p2[STRINGLEN + 1];
	char outstring[WRITE_BUFFER_LENGTH + 1];
	char outstring2[WRITE_BUFFER_LENGTH + 1];
	char command[STRINGLEN + 1];
	char channel[CHANLEN + 1];
	char topic[TOPICLEN + 1];
	char uptimestring[STRINGLEN + 1];
	char modeline[CHANLEN + 1];
	char *restptr;
	struct channel *this;
	struct chanuser *u;

	oer_debug(OER_DEBUG_INFO, "parsecommand->message from %s!%s target is %s and command is %s\n", nick, userhost, target, commandline);
	/* command line parsing begins here, first parameter = the command */
        ppos = 0;
        if((ppos = parse(commandline, ppos, " ", command, STRINGLEN, 0)) < 0) {
                return;
        }
	prevppos = ppos;
	/* second is either the channel or first parameter to the command */
        if((ppos = parse(commandline, ppos, " ", channel, CHANLEN, 0)) < 0) {
                return;
        }
	if(isme(target)) {
		tochan = 0;
		if(!strlen(channel) || channel[0] != '#') {
			/* no channel given, continue from here */
			strncpy(channel, "<NONE>", CHANLEN);
			restptr = commandline + prevppos;
		} else {
			/* advance to next parameter, skip channel */
			restptr = commandline + ppos;
		}
	} else {
		tochan = 1;
		strncpy(channel, target, CHANLEN);
		restptr = commandline + prevppos;
	}
	/* command line parsing continues at restptr */
	oer_debug(OER_DEBUG_INFO, "parsecommand->command is %s and channel is %s\n", command, channel);
	if(strlen(restptr) > 0) {
		oer_debug(OER_DEBUG_INFO, "parsecommand->rest of the commandline is %s\n", restptr);
	}
	switch((which = whichcommand(command, wordcount(restptr)))) {
	case OER_WHICHCOMMAND_TOPIC_SET:
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_SET\n");
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
		if(!isatleastopnow(channel, nick, userhost)) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		if(amiop(channel)) {
			delalltopics(channel);
			addnewtopic(channel, nick, restptr);
		}
		return;
	case OER_WHICHCOMMAND_TOPIC_ADD:
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_ADD\n");
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
		if(!isatleastopnow(channel, nick, userhost)) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		if(amiop(channel)) {
			addnewtopic(channel, nick, restptr);
		}
		return;
	case OER_WHICHCOMMAND_TOPIC_DEL:
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_DEL\n");
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			return;
		}
		if(amiop(channel)) {
			if(deltopic(channel, atoi(restptr), topic) != NULL) {
				snprintf(outstring, WRITE_BUFFER_LENGTH, "deleted Topic #%d: %s", atoi(restptr), topic);
				sendreply((tochan) ? channel : nick, tochan, 0, 0, outstring);
			}
		}
		return;
	case OER_WHICHCOMMAND_TOPIC_EDIT:
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_EDIT\n");
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		if(!strlen(p1) || !strlen(restptr + ppos)) {
			return;
		}
		if(amiop(channel)) {
			edittopic(channel, nick, restptr + ppos, atoi(p1));
		}
		return;
	case OER_WHICHCOMMAND_TOPIC_INS:
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_INS\n");
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		if(!strlen(p1) || !strlen(restptr + ppos)) {
			return;
		}
		if(amiop(channel)) {
			insertnewtopic(channel, nick, restptr + ppos, atoi(p1));
		}
		return;
	case OER_WHICHCOMMAND_TOPIC_LIST:
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_LIST\n");
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		listtopic((tochan) ? channel : nick, tochan, channel);
		return;
	case OER_WHICHCOMMAND_TOPIC_GET:
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_GET\n");
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		delalltopics(channel);
		snprintf(outstring, WRITE_BUFFER_LENGTH, "TOPIC %s\n", channel);
		addnewtimed(mystate->now, OER_TIMED_IRC_COMMAND, OER_TIMED_IRC_COMMAND_DUMMY, outstring, NULL, NULL, NULL);
		return;
	case OER_WHICHCOMMAND_TOPIC_REFRESH:
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_REFRESH\n");
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(amiop(channel)) {
			refreshtopic(channel);
		}
		return;
	case OER_WHICHCOMMAND_TOPIC_SWAP:
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_SWAP\n");
                if(haschanflags(channel, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		if((ppos = parse(restptr, ppos, " ", p2, STRINGLEN, 0)) < 0) {
			return;
		}
		if(!strlen(p1) || !strlen(p2)) {
			return;
		}
		if(amiop(channel)) {
			swaptopic(channel, atoi(p1), atoi(p2));
		}
		return;
	case OER_WHICHCOMMAND_LOCK:
	case OER_WHICHCOMMAND_LOCKU:
		oer_debug(OER_DEBUG_INFO, "parsecommand->LOCK/LOCKU\n");
		proceed = 0;
		if(isadmin(channel, userhost)) {
			proceed = 1;
		}
		if(isop(channel, userhost) && haschanflags(channel, "l")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		if(!amiop(channel)) {
			return;
		}
		lockchan(channel, restptr, (which == OER_WHICHCOMMAND_LOCK) ? 0 : 1, nick, userhost);
		return;
	case OER_WHICHCOMMAND_UNLOCK:
		oer_debug(OER_DEBUG_INFO, "parsecommand->UNLOCK\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		unlockchan(channel, nick, userhost);
		return;
	case OER_WHICHCOMMAND_RANDOM_KICK:
		oer_debug(OER_DEBUG_INFO, "parsecommand->RANDOM_KICK\n");
		proceed = 0;
		if(isadmin(channel, userhost)) {
			proceed = 1;
		}
		if(isop(channel, userhost) && hasuserflags(channel, userhost, "r")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		if(amiop(channel)) {
			u = getrandomuser(channel);
			if(u == NULL) {
				return;
			}
			snprintf(outstring, WRITE_BUFFER_LENGTH, "random kick by %s\n", nick);
			kickuser(channel, u->nick, outstring);
		}
		return;
	case OER_WHICHCOMMAND_RANDOM_BANKICK:
		oer_debug(OER_DEBUG_INFO, "parsecommand->RANDOM_BANKICK\n");
		proceed = 0;
		if(isadmin(channel, userhost)) {
			proceed = 1;
		}
		if(isop(channel, userhost) && hasuserflags(channel, userhost, "r")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		if(amiop(channel)) {
			u = getrandomuser(channel);
			if(u == NULL) {
				return;
			}
			snprintf(outstring, WRITE_BUFFER_LENGTH, "random ban kick by %s\n", nick);
			banuser(channel, u->nick);
			kickuser(channel, u->nick, outstring);
		}
		return;
	case OER_WHICHCOMMAND_SYNC:
		oer_debug(OER_DEBUG_INFO, "parsecommand->SYNC\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		syncvoices(channel);
		syncops(channel);
		syncbans(channel);
		syncnickbks(channel);
		return;
	case OER_WHICHCOMMAND_SYNCALL:
		oer_debug(OER_DEBUG_INFO, "parsecommand->SYNCALL\n");
		if(!isopa(userhost)) {
			return;
		}
		for(this = mystate->channels; this != NULL; this = this->next) {
			if(!amiop(this->name)) {
				continue;
			}
			syncvoices(this->name);
			syncops(this->name);
			syncbans(this->name);
			syncnickbks(this->name);
		}
		return;
	case OER_WHICHCOMMAND_JUMP:
		oer_debug(OER_DEBUG_INFO, "parsecommand->JUMP\n");
		if(!isopa(userhost)) {
			return;
		}
		if(!strlen(restptr)) {
			snprintf(mystate->signoff, STRINGLEN, "jump to next IRC server ordered by %s (%s)", nick, userhost);
		} else {
			snprintf(mystate->signoff, STRINGLEN, "jump to server %s ordered by %s (%s)", restptr, nick, userhost);
			strncpy(mystate->preferredserver, restptr, HOSTLEN);
		}
		mystate->reconnect = OER_RECONNECT_ADMIN;
		return;
	case OER_WHICHCOMMAND_QUIT:
		oer_debug(OER_DEBUG_INFO, "parsecommand->QUIT\n");
		if(!isopa(userhost)) {
			return;
		}
		if(!strlen(restptr)) {
			snprintf(mystate->signoff, STRINGLEN, "quit ordered by %s (%s)", nick, userhost);
		} else {
			strncpy(mystate->signoff, restptr, STRINGLEN);
		}
		quit();
		return;
	case OER_WHICHCOMMAND_OP:
		oer_debug(OER_DEBUG_INFO, "parsecommand->OP\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		if(!strlen(restptr)) {
			/* the messager wants to be opped */
			if(!isopnow(channel, nick)) {
				addnewmm(channel, mystate->now, "+o", nick);
			}
			return;
		}
		/* op all given nicks */
		ppos = 0;
		while(1) {
			nppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0);
			if(nppos == ppos || nppos < 0) {
				break;
			}
			ppos = nppos;
			if(!isopnow(channel, p1)) {
				if(haschanflags(channel, "u") && !isallowedop(channel, p1)) {
					continue;
				}
				addnewmm(channel, mystate->now, "+o", p1);
			}
		}
		return;
	case OER_WHICHCOMMAND_DOP:
		oer_debug(OER_DEBUG_INFO, "parsecommand->DOP\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		if(!strlen(restptr)) {
			/* the messager wants to be de-opped */
			if(isopnow(channel, nick)) {
				addnewmm(channel, mystate->now, "-o", nick);
			}
			return;
		}
		/* de-op all given nicks */
		ppos = 0;
		while(1) {
			nppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0);
			if(nppos == ppos || nppos < 0) {
				break;
			}
			ppos = nppos;
			if(isopnow(channel, p1)) {
				addnewmm(channel, mystate->now, "-o", p1);
			}
		}
		return;
	case OER_WHICHCOMMAND_VOICE:
		oer_debug(OER_DEBUG_INFO, "parsecommand->VOICE\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		if(!strlen(restptr)) {
			/* the messager wants to be voiced */
			if(!isvoicenow(channel, nick)) {
				addnewmm(channel, mystate->now, "+v", nick);
			}
			return;
		}
		/* voice all given nicks */
		ppos = 0;
		while(1) {
			nppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0);
			if(nppos == ppos || nppos < 0) {
				break;
			}
			ppos = nppos;
			if(!isvoicenow(channel, p1)) {
				addnewmm(channel, mystate->now, "+v", p1);
			}
		}
		return;
	case OER_WHICHCOMMAND_BAN:
		oer_debug(OER_DEBUG_INFO, "parsecommand->BAN\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		/* ban all given nicks */
		ppos = 0;
		while(1) {
			nppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0);
			if(nppos == ppos || nppos < 0) {
				break;
			}
			ppos = nppos;
			banuser(channel, p1);
		}
		return;
	case OER_WHICHCOMMAND_UNBAN:
		oer_debug(OER_DEBUG_INFO, "parsecommand->UNBAN\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		/* unban all given nicks */
		ppos = 0;
		while(1) {
			nppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0);
			if(nppos == ppos || nppos < 0) {
				break;
			}
			ppos = nppos;
			unbanuser(channel, p1);
		}
		return;
	case OER_WHICHCOMMAND_BANKICK:
		oer_debug(OER_DEBUG_INFO, "parsecommand->BANKICK\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		banuser(channel, p1);
		kickuser(channel, p1, (strlen(restptr + ppos) > 0) ? restptr + ppos : NULL);
		return;
	case OER_WHICHCOMMAND_KICK:
		oer_debug(OER_DEBUG_INFO, "parsecommand->KICK\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		kickuser(channel, p1, (strlen(restptr + ppos) > 0) ? restptr + ppos : NULL);
		return;
	case OER_WHICHCOMMAND_INVITE:
		oer_debug(OER_DEBUG_INFO, "parsecommand->INVITE\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		if(tochan) {
			/* this makes no sense */
			return;
		}
		snprintf(outstring, WRITE_BUFFER_LENGTH, "INVITE %s %s\n", nick, channel);
		addnewtimed(mystate->now, OER_TIMED_IRC_COMMAND, OER_TIMED_IRC_COMMAND_DUMMY, outstring, NULL, NULL, NULL);
		return;
	case OER_WHICHCOMMAND_MASSMESSAGE:
		oer_debug(OER_DEBUG_INFO, "parsecommand->MASSMESSAGE\n");
		if(!isopa(userhost)) {
			return;
		}
		if(strlen(restptr) > 0) {
			massmessage(nick, restptr);
		}
		return;
	case OER_WHICHCOMMAND_MODE:
		oer_debug(OER_DEBUG_INFO, "parsecommand->MODE\n");
                if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!amiop(channel)) {
			return;
		}
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		if(!strlen(p1)) {
			return;
		}
		if(!strlen(restptr + ppos)) {
			/* only a mode line, no target */
			addnewmm(channel, mystate->now, p1, "");
			return;
		}
		/* process all modes */
		counter = 1;
		while(1) {
			nppos = parse(restptr, ppos, " ", p2, STRINGLEN, 0);
			if(nppos == ppos || nppos < 0) {
				break;
			}
			ppos = nppos;
			getnthmode(p1, counter, modeline);
			oer_debug(OER_DEBUG_INFO, "parsecommand->mode %s %s %s\n", channel, modeline, p2);
			addnewmm(channel, mystate->now, modeline, p2);
			counter++;
		}
		return;
	case OER_WHICHCOMMAND_HELP:
		oer_debug(OER_DEBUG_INFO, "parsecommand->HELP\n");
		proceed = 0;
		for(this = mystate->channels; this != NULL && !proceed; this = this->next) {
			if(haschanflags(this->name, "U") && !isatleastop(this->name, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(this->name, nick, userhost)) {
				proceed = 1;
			}
		}
		if(!proceed) {
			return;
		}
		strncpy(outstring, OER_HELP, WRITE_BUFFER_LENGTH);
		sendreply((tochan) ? channel : nick, tochan, 0, 0, outstring);
		return;
	case OER_WHICHCOMMAND_LIST:
		oer_debug(OER_DEBUG_INFO, "parsecommand->LIST\n");
		/* we are satisfied if the user is atleastopnow() on one of
		   our channels. commands requiring channel specific authorization
		   are checked in listcommand() */
		proceed = 0;
		for(this = mystate->channels; this != NULL && !proceed; this = this->next) {
			if(haschanflags(this->name, "U") && !isatleastop(this->name, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(this->name, nick, userhost)) {
				proceed = 1;
			}
		}
		if(!proceed) {
			return;
		}
		listcommand((tochan) ? channel : nick, tochan, channel, nick, userhost, restptr);
		return;
	case OER_WHICHCOMMAND_LOGOFF:
		oer_debug(OER_DEBUG_INFO, "parsecommand->LOGOFF\n");
		proceed = 0;
		if(isopa(userhost)) {
			proceed = 1;
		}
		if(hasuserflags(channel, userhost, "d")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		if(strlen(restptr) > 0) {
			logoffcommand((tochan) ? channel : nick, tochan, channel, nick, userhost, restptr);
		}
		return;
	case OER_WHICHCOMMAND_LOGON:
		oer_debug(OER_DEBUG_INFO, "parsecommand->LOGON\n");
		proceed = 0;
		if(isopa(userhost)) {
			proceed = 1;
		}
		if(hasuserflags(channel, userhost, "d")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		if(strlen(restptr) > 0) {
			logoncommand((tochan) ? channel : nick, tochan, channel, nick, userhost, restptr);
		}
		return;
	case OER_WHICHCOMMAND_ADD:
		oer_debug(OER_DEBUG_INFO, "parsecommand->ADD\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(strlen(restptr) > 0) {
			addcommand((tochan) ? channel : nick, tochan, channel, nick, userhost, restptr);
		}
		return;
	case OER_WHICHCOMMAND_DEL:
		oer_debug(OER_DEBUG_INFO, "parsecommand->DEL\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(strlen(restptr) > 0) {
			delcommand((tochan) ? channel : nick, tochan, channel, nick, userhost, restptr);
		}
		return;
	case OER_WHICHCOMMAND_EDIT:
		oer_debug(OER_DEBUG_INFO, "parsecommand->EDIT\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(strlen(restptr) > 0) {
			editcommand((tochan) ? channel : nick, tochan, channel, nick, userhost, restptr);
		}
		return;
	case OER_WHICHCOMMAND_SAVE:
		oer_debug(OER_DEBUG_INFO, "parsecommand->SAVE\n");
		proceed = 0;
		for(this = mystate->channels; this != NULL && !proceed; this = this->next) {
			if(haschanflags(this->name, "U") && !isatleastop(this->name, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(this->name, nick, userhost)) {
				proceed = 1;
			}
		}
		if(!proceed) {
			return;
		}
		saveall();
		mystate->last_saved = mystate->now;
		return;
	case OER_WHICHCOMMAND_FLUSH:
		oer_debug(OER_DEBUG_INFO, "parsecommand->FLUSH\n");
		proceed = 0;
		for(this = mystate->channels; this != NULL && !proceed; this = this->next) {
			if(haschanflags(this->name, "U") && !isatleastop(this->name, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(this->name, nick, userhost)) {
				proceed = 1;
			}
		}
		if(!proceed) {
			return;
		}
		flushall();
		return;
	case OER_WHICHCOMMAND_INFO:
		oer_debug(OER_DEBUG_INFO, "parsecommand->INFO\n");
		proceed = 0;
		for(this = mystate->channels; this != NULL && !proceed; this = this->next) {
			if(haschanflags(this->name, "U") && !isatleastop(this->name, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(this->name, nick, userhost)) {
				proceed = 1;
			}
		}
		if(!proceed) {
			return;
		}
		snprintf(outstring, WRITE_BUFFER_LENGTH, "%s -- oer was written in March of 2000. This version of oer was compiled %s %s. The C source of this oer version consists of %d lines totalling %d bytes.", OER_COPYRIGHT1, __DATE__, __TIME__, SOURCE_LINES, SOURCE_CHARS);
		sendreply((tochan) ? channel : nick, tochan, 0, 1, outstring);
		return;
	case OER_WHICHCOMMAND_UPTIME:
		oer_debug(OER_DEBUG_INFO, "parsecommand->UPTIME\n");
		proceed = 0;
		for(this = mystate->channels; this != NULL && !proceed; this = this->next) {
			if(haschanflags(this->name, "U") && !isatleastop(this->name, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(this->name, nick, userhost)) {
				proceed = 1;
			}
		}
		if(!proceed) {
			return;
		}
		secondstostring(mystate->now - mystate->startup, uptimestring, STRINGLEN);
		snprintf(outstring, WRITE_BUFFER_LENGTH, "oer uptime: %s", uptimestring);
		if(uptime(&uptime_seconds, &idle_seconds)) {
			secondstostring(uptime_seconds, uptimestring, STRINGLEN);
#ifdef OER_UPTIME_SHOW_IDLE
			snprintf(outstring2, WRITE_BUFFER_LENGTH, ", server uptime: %s (idle %.2f%%)", uptimestring, ((float)((float)idle_seconds/(float)uptime_seconds) * 100.0));
#else
			snprintf(outstring2, WRITE_BUFFER_LENGTH, ", server uptime: %s", uptimestring);
#endif
			strncat(outstring, outstring2, WRITE_BUFFER_LENGTH - strlen(outstring));
		}
		sendreply((tochan) ? channel : nick, tochan, 0, 1, outstring);
		return;
	case OER_WHICHCOMMAND_ACTION:
		oer_debug(OER_DEBUG_INFO, "parsecommand->ACTION\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(strlen(restptr) > 0) {
			snprintf(outstring, WRITE_BUFFER_LENGTH, "%cACTION %s%c", 1, restptr, 1);
			sendreply(channel, 1, 0, 0, outstring);
		}
		return;
	case OER_WHICHCOMMAND_SAY:
		oer_debug(OER_DEBUG_INFO, "parsecommand->SAY\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(strlen(restptr) > 0) {
			sendreply(channel, 1, 0, 0, restptr);
		}
		return;
	case OER_WHICHCOMMAND_RAW:
		oer_debug(OER_DEBUG_INFO, "parsecommand->RAW\n");
		if(!isopa(userhost)) {
			return;
		}
		if(strlen(restptr) > 0) {
			snprintf(outstring, WRITE_BUFFER_LENGTH, "%s\n", restptr);
                        addnewtimed(mystate->now, OER_TIMED_IRC_COMMAND, OER_TIMED_IRC_COMMAND_DUMMY, outstring, NULL, NULL, NULL);
		}
		return;
	case OER_WHICHCOMMAND_LAST:
		oer_debug(OER_DEBUG_INFO, "parsecommand->LAST\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		showlast((tochan) ? channel : nick, tochan, channel, restptr);
		return;
	case OER_WHICHCOMMAND_QUOTE:
		oer_debug(OER_DEBUG_INFO, "parsecommand->QUOTE\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			return;
		}
		msgcount = getondiskmsgcount(channel, restptr);
		if(msgcount > 0) {
			if(getrandommsg(channel, restptr, NICKLEN, getrandom(msgcount), outstring, WRITE_BUFFER_LENGTH) < 0) {
				return;
			}
			snprintf(outstring2, WRITE_BUFFER_LENGTH, "\"%s\" (c) %s", outstring, restptr);
			sendreply((tochan) ? channel : nick, tochan, 0, 1, outstring2);
		}
		return;
	case OER_WHICHCOMMAND_SEEN:
		oer_debug(OER_DEBUG_INFO, "parsecommand->SEEN\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		showseen((tochan) ? channel : nick, tochan, channel, restptr);
		return;
	case OER_WHICHCOMMAND_NSTATS:
		oer_debug(OER_DEBUG_INFO, "parsecommand->NSTATS\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			return;
		}
		nstats((tochan) ? channel : nick, tochan, channel, restptr);
		return;
	case OER_WHICHCOMMAND_WALL:
		oer_debug(OER_DEBUG_INFO, "parsecommand->WALL\n");
		if(haschanflags(channel, "U") && !isatleastop(channel, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(channel, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			return;
		}
		if(tochan) {
			/* makes no sense */
			return;
		}
		wall(channel, nick, userhost, restptr);
		return;
        case OER_WHICHCOMMAND_INVALID:
		oer_debug(OER_DEBUG_INFO, "parsecommand->invalid or malformed command %s\n", command);
		break;
	}
}




