/*

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 bans;
	int deops;
	int modetype;
	int msgcount;
	int ppos;
	int restpos;
	int nrestpos;
	int tpos;
	int do_syncbans;
	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 modeline[CHANLEN + 1];
	char user[USERHOSTLEN + 1];
	char host[USERHOSTLEN + 1];
	char userhost[USERHOSTLEN + 1];
	char nick[NICKLEN + 1];
	char nick2[NICKLEN + 1];
	char temp_nick[STRINGLEN + 1];
	char target[STRINGLEN + 1];
	char m;
	char *restptr;
	char *ptr;
	struct channel *this;
	struct chanuser *cu;
	struct timeval tv1;
	struct timeval tv2;

	striplf(serveroutput);
#ifdef DEBUG
        oer_debug(OER_DEBUG_NOISE, "parseirc(\"%s\")\n", serveroutput);
#endif
	if(serveroutput[0] != ':') {
		/* must be a command */
		return;
	}
	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.done = 1;
		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, nick)) {
						changeuser(this, nick, 1, -1, -1, -1);
					}
				}
			}
			/* userhost is at outstring + tpos + 1 (first char is '+' or '-') */
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(302)->nick %s has userhost %s\n", nick, outstring + tpos + 1);
#endif
			setuserhost(nick, outstring + tpos + 1);
		}
		return;
	}
	if(!strcasecmp(command, "303")) {
		/* this is the response to our ison query */
		return;
	}
	if(!strcasecmp(command, "315")) {
		/* end of WHO */
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		this->whoquery = 1;
		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(this, "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(this, restptr + restpos + 1);
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(332)->%s topic is %s\n", channel, restptr + restpos + 1);
#endif
		return;
	}
	if(!strcasecmp(command, "333")) {
		return;
	}
	if(!strcasecmp(command, "341")) {
		/* returned when inviting someone */
		return;
	}
	if(!strcasecmp(command, "352")) {
		/* returned by the WHO query initiated in isjoined() */
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		if((restpos = parse(restptr, restpos, " ", user, USERHOSTLEN, 0)) < 0) {
			return;
		}
		if((restpos = parse(restptr, restpos, " ", host, USERHOSTLEN, 0)) < 0) {
			return;
		}
		if((restpos = parse(restptr, restpos, " ", outstring, STRINGLEN, 0)) < 0) {
			return;
		}
		if((restpos = parse(restptr, restpos, " ", nick, NICKLEN, 0)) < 0) {
			return;
		}
		if((restpos = parse(restptr, restpos, " ", mode, CHANLEN, 0)) < 0) {
			return;
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		snprintf(userhost, USERHOSTLEN, "%s@%s", user, host);
		setuserhost(nick, userhost);
		if(index(mode, (int)'@')) {
			changeuser(this, nick, -1, 1, -1, -1);
		}
		if(index(mode, (int)'*')) {
			changeuser(this, nick, 1, -1, -1, -1);
		}
		if(index(mode, (int)'+')) {
			changeuser(this, nick, -1, -1, 1, -1);
		}
		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;
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(353)->%s\n", this->name);
#endif
		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);
			}
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(353)->%s\n", nick);
#endif
			m = '\0';
			if(nick[0] == '*' || nick[0] == '@' || nick[0] == '+') {
				m = nick[0];
				strncpy(nick2, nick + 1, NICKLEN);
				strncpy(nick, nick2, NICKLEN);
			}
			if(userjoined(this, nick, NULL, (m == '*') ? 1 : 0, (m == '@') ? 1 : 0, (m == '+') ? 1 : 0, 0) == NULL) {
#ifdef DEBUG
				oer_debug(OER_DEBUG_INFO, "parseirc(353)->userjoined() returned NULL\n");
#endif
			}
			if(isme(nick) && m == '@') {
				this->i_am_op = 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;
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(366)->channel %s\n", this->name);
#endif
		isjoined(this);
		return;
	}
	if(!strcasecmp(command, "372")) {
		return;
	}
	if(!strcasecmp(command, "375")) {
		return;
	}
	if(!strcasecmp(command, "376")) {
		return;
	}
	if(!strcasecmp(command, "401")) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(401)->%s\n", restptr);
#endif
		return;
	}
	if(!strcasecmp(command, "404")) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(404)->%s\n", restptr);
#endif
		return;
	}
	if(!strcasecmp(command, "405")) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(405)->%s\n", restptr);
#endif
		/* ERR_TOOMANYCHANNELS */
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		/* reset joining flag, we will try again in OER_DELAY_BETWEEN_REJOINS seconds */
		this->rejoin_at = mystate->now + OER_DELAY_BETWEEN_REJOINS;
		this->joining = 0;
		return;
	}
	if(!strcasecmp(command, "422")) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(422)->%s\n", restptr);
#endif
		return;
	}
	if(!strcasecmp(command, "432") || !strcasecmp(command, "433")) {
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", nick, NICKLEN, 0)) < 0) {
			return;
		}
		/* check if we are still registering */
                if(!mystate->current_server->registered.done) {
			mystate->current_server->registered.nick = 0;
			if(mystate->use_altnick) {
#ifdef DEBUG
				oer_debug(OER_DEBUG_INFO, "parseirc(%s)->both our primary and secondary nicknames are taken, generating a random nick\n", command);
#endif
				snprintf(mystate->nick, NICKLEN, "oer-%d", getrandom(9999));
				mystate->use_altnick = 0;
			} else {
#ifdef DEBUG
				oer_debug(OER_DEBUG_INFO, "parseirc(%s)->our nickname %s is already in use, will try altnick %s\n", command, nick, mystate->altnick);
#endif
				strncpy(mystate->nick, mystate->altnick, NICKLEN);
				mystate->use_altnick = 1;
			}
			return;
		}
		/* no, must be a nick change that failed */
		strncpy(mystate->nick, params, NICKLEN);
		return;
	}
	if(!strcasecmp(command, "437")) {
		/* ERR_UNAVAILRESOURCE, handle both cases */
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(%s)->%s\n", command, channel);
#endif
		if((this = getchptr(channel)) == NULL) {
			/* nick unavailable, check if we are still registering */
			if(!mystate->current_server->registered.done) {
				mystate->current_server->registered.nick = 0;
				if(mystate->use_altnick) {
#ifdef DEBUG
					oer_debug(OER_DEBUG_INFO, "parseirc(%s)->both our primary and secondary nicknames are taken, generating a random nick\n", command);
#endif
					snprintf(mystate->nick, NICKLEN, "oer-%d", getrandom(9999));
					mystate->use_altnick = 0;
				} else {
#ifdef DEBUG
					oer_debug(OER_DEBUG_INFO, "parseirc(%s)->our nickname %s is already in use, will try altnick %s\n", command, nick, mystate->altnick);
#endif
					strncpy(mystate->nick, mystate->altnick, NICKLEN);
					mystate->use_altnick = 1;
				}
				return;
			}
			/* no, must be a nick change that failed */
			strncpy(mystate->nick, params, NICKLEN);
			return;
		}
		/* we got channel, reset joining flag */
		this->rejoin_at = mystate->now + OER_DELAY_BETWEEN_REJOINS;
		this->joining = 0;
                return;
        }
	if(!strcasecmp(command, "439")) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(439)->%s\n", restptr);
#endif
                return;
        }
	if(!strcasecmp(command, "441")) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(441)->%s\n", restptr);
#endif
                return;
        }
	if(!strcasecmp(command, "464")) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(464)->server reports bad password (%s)\n", (strlen(mystate->current_server->password)) ? mystate->current_server->password : "null");
#endif
                memset(&mystate->current_server->registered, 0, sizeof(struct registered));
		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;
		}
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(%s)->%s\n", command, channel);
#endif
		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;
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(484)->nick %s on %s is a IRC operator\n", nick, this->name);
#endif
		changeuser(this, nick, 1, -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);
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, USERHOSTLEN);
		if(isme(nick)) {
			/* strip user & host */
			strncpy(user, prefix + 1 + tpos, USERHOSTLEN);
			ptr = index(user, '@');
			memset(ptr, 0, USERHOSTLEN - strlen(user) + 1);
			ptr = index(prefix + 1 + tpos, '@');
			strncpy(host, ptr + 1, USERHOSTLEN);
			/* check if the server agrees with our perception
			   of our host */
			if(strcasecmp(mystate->host, host)) {
				strncpy(mystate->host, host, HOSTLEN);
#ifdef DEBUG
				oer_debug(OER_DEBUG_INFO, "parseirc(JOIN)->the server says our host is %s\n", mystate->host);
#endif
			}
			/* check if the server agrees with our perception
			   of our user id */
			if(strcasecmp(mystate->user, user)) {
				strncpy(mystate->user, user, USERLEN);
#ifdef DEBUG
				oer_debug(OER_DEBUG_INFO, "parseirc(JOIN)->the server says our user id is %s\n", mystate->user);
#endif
			}
			/* we are not interested in our own joins */
			return;
		}
		if(userjoined(this, nick, userhost, 0, 0, 0, isfriend(this, nick, userhost)) == NULL) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(JOIN)->userjoined() returned NULL\n");
#endif
		}
		addnewjoin(this, mystate->now, nick, userhost);
		if(haschanflags(this, "S")) {
			updateseen(this, nick, userhost);
		}
		if(mystate->netjoining) {
			return;
		}
		if(burstjoins(this) && !mystate->netjoining) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(JOIN)->net join detected, scheduling netjoin jobs at %lu\n", mystate->now + 30);
#endif
			mystate->netjoining = 1;
			mystate->postnj_checks_at = mystate->now + OER_NETJOIN_DELAY;
			return;
		}
		listchanusers();
		/* the on-join checks, they require that oer is opped */
		if(this->i_am_op) {
			if(!gettobek(this, nick) && checkforautorejoin(this, nick, userhost)) {
				changetobek(this, nick, 1);
			}
			if(!gettobek(this, nick) && checkforpartrejoin(this, nick, userhost)) {
				changetobek(this, nick, 1);
				resetparts(this, nick, userhost);
			}
			if(!gettobek(this, nick) && checkforbadnick(this, nick, userhost)) {
				changetobek(this, nick, 1);
			}
			if(!gettobek(this, nick) && haschanflags(this, "V") && isopa(userhost)) {
				mmode_new(this, mystate->now, "+v", nick);
			}
			if(!gettobek(this, nick) && haschanflags(this, "v") && isvoice(this, userhost)) {
				mmode_new(this, mystate->now, "+v", nick);
			}
			if(!gettobek(this, nick) && haschanflags(this, "O") && isopa(userhost)) {
				mmode_new(this, mystate->now, "+o", nick);
			}
			if(!gettobek(this, nick) && haschanflags(this, "o") && isop(this, userhost)) {
				mmode_new(this, mystate->now, "+o", nick);
			}
		}
		/* if the user will be kicked, skip the rest */
		if(gettobek(this, nick)) {
			return;
		}
		if(haschanflags(this, "A")) {
			sendadverts(this, nick, userhost);
		}
		/* then possibly the on-join quote */
		if(!haschanflags(this, "q")) {
			return;
		}
		if(hasuserflags(this, userhost, "x", "")) {
			return;
		}
		if((mystate->now - this->last_quote) < OER_QUOTE_INTERVAL) {
			return;
		}
		if(getjoincount(this, nick) > 1) {
			return;
		}
		if((msgcount = getondiskmsgcount(this, nick)) <= 0) {
			return;
		}
		strncpy(nick2, nick, NICKLEN);
		gettimeofday(&tv1, NULL);
		if(getrandommsg(this, nick2, NICKLEN, getrandom(msgcount), outstring2, HUGESTRINGLEN) < 0) {
			return;
		}
		if(!isgoodquote(outstring2)) {
			return;
		}
		gettimeofday(&tv2, NULL);
		snprintf(outstring3, HUGESTRINGLEN, "\"%s\" (c) %s", outstring2, nick2);
		sendreply(channel, 1, 0, OER_TIMED_PRIORITY_NORMAL, outstring3);
		this->last_quote = mystate->now;
		snprintf(outstring3, HUGESTRINGLEN, "%ld secs and %ld usecs", tv2.tv_sec - tv1.tv_sec, ABS(tv2.tv_usec - tv1.tv_usec));
		if(tv2.tv_sec == tv1.tv_sec) {
			snprintf(outstring3, HUGESTRINGLEN, "%ld usecs", tv2.tv_usec - tv1.tv_usec);
		}
#ifdef DEBUG
		oer_debug(OER_DEBUG_WARNING, "parseirc(JOIN)->getrandommsg() took %s\n", outstring3);
#endif
		if(ABS(tv2.tv_usec - tv1.tv_usec) + ((tv2.tv_sec - tv1.tv_sec) * 1000000) > OER_ALLOWED_QUOTE_TIME_USEC) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_WARNING, "parseirc(JOIN)->getrandommsg() took %s (longer than OER_ALLOWED_QUOTE_TIME_USEC)\n", outstring3);
#endif
		}
		return;
	}
	if(!strcasecmp(command, "PRIVMSG")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, USERHOSTLEN);
		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, USERHOSTLEN);
		/* quit doesn't provide the channel, we have to check all channels */
		for(this = mystate->channels; this != NULL; this = this->next) {
			if(!isonchan(this, nick)) {
				continue;
			}
			addnewpart(this, mystate->now, nick, userhost);
			if(!userleft(this, nick, userhost)) {
#ifdef DEBUG
				oer_debug(OER_DEBUG_INFO, "parseirc(QUIT)->userleft() returned NULL\n");
#endif
			}
			if(this->nickcount == 1 && !this->i_am_op) {
#ifdef DEBUG
				oer_debug(OER_DEBUG_INFO, "parseirc(QUIT)->cycling %s to gain ops\n", this->name);
#endif
				snprintf(timed_str, WRITE_BUFFER_LENGTH, "PART %s", this->name);
				timed_new(NULL, mystate->now, OER_TIMED_TYPE_NORMAL, OER_TIMED_PRIORITY_CHANNEL_HANDLING, timed_str);
			}
		}
		listchanusers();
		return;
	}
	if(!strcasecmp(command, "PART")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, USERHOSTLEN);
		strncpy(channel, params, CHANLEN);
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(isme(nick)) {
			this->rejoin_at = mystate->now + OER_DELAY_BETWEEN_REJOINS;
			if(this->nickcount == 1 && !this->i_am_op) {
				/* we are going to cycle */
				this->rejoin_at = mystate->now + OER_DELAY_BETWEEN_REJOINS_CYCLE;
			}
			/* no reason to remove from channel, we will just init it */
			initchannel(this);
			return;
		}
		addnewpart(this, mystate->now, nick, userhost);
		if(!userleft(this, nick, userhost)) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(PART)->userleft() returned NULL\n");
#endif
		}
		if(this->nickcount == 1 && !this->i_am_op) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(PART)->cycling %s to gain ops\n", channel);
#endif
			snprintf(timed_str, WRITE_BUFFER_LENGTH, "PART %s", channel);
			timed_new(NULL, mystate->now, OER_TIMED_TYPE_NORMAL, OER_TIMED_PRIORITY_CHANNEL_HANDLING, timed_str);
		}
		listchanusers();
		return;
	}
	if(!strcasecmp(command, "NICK")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, USERHOSTLEN);
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "nickchange->%s is now known as %s, changing on all channels\n", nick, params + 1);
#endif
		/* 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, nick, params + 1);
			/* we change all channels but check only those that are needed (why?) */
			if(isonchan(this, params + 1)) {
				addnewnickchange(this, userhost);
				if(this->i_am_op) {
					if(checkforbadnick(this, params + 1, userhost) || checkfornickflood(this, params + 1, userhost)) {
						changetobek(this, 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, params + 1, 0);
				}
			}
		}
		return;
	}
	if(!strcasecmp(command, "KILL")) {
		if(isme(params)) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(KILL)->I was killed by %s path %s\n", prefix + 1, restptr + 1);
#endif
		}
		return;
	}
	if(!strcasecmp(command, "KICK")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, USERHOSTLEN);
		strncpy(channel, params, CHANLEN);
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", target, STRINGLEN, 0)) < 0) {
			return;
		}
		if(isme(target)) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(KICK)->I was kicked off %s by %s!%s\n", channel, nick, userhost);
#endif
			initchannel(this);
			this->rejoin_at = mystate->now + OER_DELAY_BETWEEN_REJOINS;
			return;
		}
		/* KICK doesn't tell the host for the person that was kicked */
		if((cu = getcuptr(this, target)) == NULL) {
                        /* we don't know the host, this is necessary because of
                           bogus/lagged server/service messages */
                        return;
                }
#ifdef DEBUG
		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);
#endif
		addnewpart(this, mystate->now, target, cu->userhost);
		if(!userleft(this, target, cu->userhost)) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(KICK)->userleft() returned NULL\n");
#endif
		}
		if(this->nickcount == 1 && !this->i_am_op) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(KICK)->cycling %s to gain ops\n", channel);
#endif
			snprintf(timed_str, WRITE_BUFFER_LENGTH, "PART %s", channel);
			timed_new(NULL, mystate->now, OER_TIMED_TYPE_NORMAL, OER_TIMED_PRIORITY_CHANNEL_HANDLING, timed_str);
		}
		listchanusers();
		return;
	}
	if(!strcasecmp(command, "NOTICE")) {
		return;
	}
	if(!strcasecmp(command, "MODE")) {
                /* NOTICE! Nick can also be a host (server/service modes) */
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", temp_nick, STRINGLEN, 1)) < 0) {
			return;
		}
		if(strlen(prefix + 1 + tpos)) {
			strncpy(userhost, prefix + 1 + tpos, USERHOSTLEN);
		} else {
			memset(userhost, 0, USERHOSTLEN);
		}
		strncpy(channel, params, CHANLEN);
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", mode, CHANLEN, 0)) < 0) {
			return;
		}
		if(mode[0] == ':') {
			/* our usermode */
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->usermode is %s\n", mode + 1);
#endif
			return;
		}
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		/* channel modes have no target */
		if(strlen(restptr + restpos) > 0) {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->%s %s on %s by %s!%s\n", mode, restptr + restpos, channel, temp_nick, userhost);
#endif
		} else {
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->%s on %s by %s!%s\n", mode, channel, temp_nick, userhost);
#endif
		}
		i = 0;
		bans = 0;
		deops = 0;
		do_syncbans = 0;
		while(1) {
			switch((modetype = nthmode(mode, i))) {
			case OER_NTHMODE_OP:
			case OER_NTHMODE_DOP:
			case OER_NTHMODE_VOICE:
			case OER_NTHMODE_DVOICE:
			case OER_NTHMODE_BAN:
			case OER_NTHMODE_UNBAN:
			case OER_NTHMODE_CHANMODE_WITH_PARAMS:
				nrestpos = parse(restptr, restpos, " ", target, STRINGLEN, 0);
				if(nrestpos == restpos || nrestpos < 0) {
					return;
				}
				restpos = nrestpos;
				getnthmode(mode, i + 1, modeline);
				if(!strcasecmp(modeline, "+k")) {
					strncpy(this->key, target, CHANLEN);
					snprintf(outstring, STRINGLEN, "%s channel key is now %s", this->name, this->key);
					sendreply(this->name, 1, 0, OER_TIMED_PRIORITY_NORMAL, outstring);
				}
				if(!strcasecmp(modeline, "-k")) {
					memset(this->key, 0, CHANLEN);
					snprintf(outstring, STRINGLEN, "deleted channel key from %s", this->name);
					sendreply(this->name, 1, 0, OER_TIMED_PRIORITY_NORMAL, outstring);
				}
#ifdef DEBUG
				oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->target is %s\n", target);
#endif
				break;
			case OER_NTHMODE_UNKNOWN:
				return;
			}
			switch(modetype) {
			case OER_NTHMODE_OP:
#ifdef DEBUG
				oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->OER_NTHMODE_OP\n");
#endif
				changeuser(this, target, -1, 1, -1, -1);
				if(isme(target)) {
					this->i_am_op = 1;
					if(this->allhostsknown) {
						channelsync(this);
					}
					continue;
				}
				if(!this->i_am_op) {
					continue;
				}
				if(haschanflags(this, "u") && !isallowedop(this, target)) {
					if(isq(temp_nick) || isservice(temp_nick)) {
						/* allow Q/service to op */
						continue;
					}
#ifdef DEBUG
					oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->non-allowed op of nick %s on channel %s by %s!%s\n", target, channel, temp_nick, userhost);
#endif
					mmode_new(this, mystate->now, "-o", target);
				}
				break;
			case OER_NTHMODE_DOP:
#ifdef DEBUG
				oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->OER_NTHMODE_DOP\n");
#endif
				changeuser(this, target, -1, 0, -1, -1);
				if(isme(target)) {
					this->i_am_op = 0;
				}
				if(isq(temp_nick) || isservice(temp_nick)) {
					/* allow Q/service to de-op */
					continue;
				}
				if(haschanflags(this, "D") && isallowedop(this, target)) {
					/* re-op */
					mmode_new(this, mystate->now, "+o", target);
				}
				deops++;
				break;
			case OER_NTHMODE_VOICE:
#ifdef DEBUG
				oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->OER_NTHMODE_VOICE\n");
#endif
				changeuser(this, target, -1, -1, 1, -1);
				break;
			case OER_NTHMODE_DVOICE:
#ifdef DEBUG
				oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->OER_NTHMODE_DVOICE\n");
#endif
				changeuser(this, target, -1, -1, 0, -1);
				break;
			case OER_NTHMODE_BAN:
#ifdef DEBUG
				oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->OER_NTHMODE_BAN\n");
#endif
				snprintf(outstring, STRINGLEN, "%s!%s@%s", mystate->nick, mystate->user, mystate->host);
				if(wild_match(target, outstring)) {
#ifdef DEBUG
					oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->ban detected on me (%s)\n", target);
#endif
					if(isq(temp_nick) || isservice(temp_nick)) {
						/* allow Q/service to ban us */
						continue;
					}
					if(this->i_am_op && haschanflags(this, "b")) {
						/* i am being banned, first kick then unban */
						if(!isme(temp_nick) && !isserver(temp_nick)) {
							snprintf(timed_str, WRITE_BUFFER_LENGTH, "KICK %s %s :do NOT ban %s, that's me!", channel, temp_nick, target);
							timed_new(NULL, mystate->now, OER_TIMED_TYPE_NORMAL, OER_TIMED_PRIORITY_CHANNEL_PROTECTION, timed_str);
						}
						mmode_new(this, mystate->now, "-b", target);
					}
				}
				bans++;
				break;
			case OER_NTHMODE_UNBAN:
#ifdef DEBUG
				oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->OER_NTHMODE_UNBAN\n");
#endif
				if(this->i_am_op && haschanflags(this, "e") && ispermban(this, target)) {
					if(isq(temp_nick) || isservice(temp_nick)) {
						/* allow Q/service to unban */
						continue;
					}
					/* re-set the ban */
#ifdef DEBUG
					oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->%s on channel %s tried to remove the permban %s\n", temp_nick, channel, target);
#endif
					mmode_new(this, mystate->now, "+b", target);
					do_syncbans = 1;
				}
				break;
			case OER_NTHMODE_CHANMODE_WITHOUT_PARAMS:
#ifdef DEBUG
				oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->OER_NTHMODE_CHANMODE_WITHOUT_PARAMS\n");
#endif
				if(!isme(temp_nick)) {
					this->setchanmode = 1;
				}
				break;
			case OER_NTHMODE_CHANMODE_WITH_PARAMS:
#ifdef DEBUG
				oer_debug(OER_DEBUG_NOISE, "parseirc(MODE)->OER_NTHMODE_CHANMODE_WITH_PARAMS\n");
#endif
				if(!isme(temp_nick)) {
					this->setchanmode = 1;
				}
				break;
			}
			if((deops > OER_MASSMODE_LIMIT) && (index(userhost, '@') != NULL) && !isme(temp_nick)) {
#ifdef DEBUG
				oer_debug(OER_DEBUG_INFO, "parseirc(MODE)->mass modes detected on %s from %s\n", channel, temp_nick);
#endif
				if(isq(temp_nick) || isservice(temp_nick)) {
					/* allow Q/service to do massmodes */
					continue;
				}
				if(this->i_am_op && haschanflags(this, "m") && !isserver(temp_nick)) {
					mmode_new(this, mystate->now, "-o", temp_nick);
					sendreply(temp_nick, 0, 3, OER_TIMED_PRIORITY_NORMAL, "do NOT deop >3 users at a time");
					deops = 0;
					bans = 0;
				}
			}
			i++;
		}
		if(do_syncbans) {
			/* 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(this);
		}
		return;
	}
	if(!strcasecmp(command, "TOPIC")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(channel, params, CHANLEN);
		if((this = getchptr(channel)) == NULL) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "t")) {
			/* only allow topic changes through us */
			if(!isme(nick)) {
				this->topic_change = 1;
			}
		}
		return;
	}
	if(!strcasecmp(command, "INVITE")) {
		tpos = 0;
		if((tpos = parse(prefix + 1, tpos, "!", nick, NICKLEN, 1)) < 0) {
			return;
		}
		strncpy(userhost, prefix + 1 + tpos, USERHOSTLEN);
		restpos = 0;
		if((restpos = parse(restptr, restpos, " ", channel, CHANLEN, 0)) < 0) {
			return;
		}
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parseirc(INVITE)->%s invited me to %s\n", nick, channel + 1);
#endif
		return;
	}
#ifdef DEBUG
	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);
#endif
}

void parsemsg(char *nick, char *userhost, char *target, char *message)
{
	int ctcp;
	int ppos;
	int is_command;
	struct channel *this;
	char np[NICKLEN + 1];
#ifdef DEBUG
        oer_debug(OER_DEBUG_INFO, "parsemsg(\"%s\", \"%s\", \"%s\", \"%s\")\n", nick, userhost, target, message);
#endif
	ctcp = 0;
	if(isctcp(message)) {
		ctcp = whichctcp(message);
		switch(ctcp) {
		case OER_WHICHCTCP_ACTION:
			if(isme(target)) {
				/* bogus action */
				return;
			}
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = ACTION\n");
#endif
			break;
		case OER_WHICHCTCP_FINGER:
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = FINGER\n");
#endif
			parsectcp(nick, userhost, ctcp, message);
			break;
		case OER_WHICHCTCP_PING:
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = PING\n");
#endif
			parsectcp(nick, userhost, ctcp, message);
			break;
		case OER_WHICHCTCP_USERINFO:
#ifdef DEBUG
                        oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = USERINFO\n");
#endif
                        parsectcp(nick, userhost, ctcp, message);
                        break;
		case OER_WHICHCTCP_VERSION:
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parsemsg->isctcp = 1 and ctcp = VERSION\n");
#endif
			parsectcp(nick, userhost, ctcp, message);
			break;
		case OER_WHICHCTCP_INVALID:
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parsemsg->invalid CTCP from %s!%s target: %s\n", nick, userhost, target);
#endif
			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;
	}
	/* the message is a public message sent to a channel */
	if((this = getchptr(target)) == NULL) {
		return;
	}
	if(this->i_am_op && checkforansi(this, nick, userhost, message)) {
		return;
	}
	if(this->i_am_op && checkforflood(this, nick, userhost, message)) {
		return;
	}
	if(this->i_am_op && checkforbadword(this, nick, userhost, message)) {
		return;
	}
	if(this->i_am_op && checkforaction(this, nick, userhost, ctcp)) {
		return;
	}
	/* only add this consequetive line iff the user
	   isn't being kicked already (lag between kick and last message) */
	if(!gettobek(this, nick)) {
		addnewpubmsg(this, mystate->now, nick, userhost, message);
	}
	if(ctcp) {
		/* we don't log ctcp (not even ACTIONS) */
		return;
	}
	/* check for command */
	ppos = 0;
	is_command = 0;
	if((ppos = parse(message, ppos, " ", np, NICKLEN, 0)) >= 0) {
		/* it is possible that parse() will fail if message
		   has just one word and it is longer than NICKLEN */
		if(!strcasecmp(np, mystate->nick)) {
			is_command = 1;
		}
		if(!strncasecmp(np, mystate->prefix, strlen(mystate->prefix))) {
			is_command = 1;
			ppos = strlen(mystate->prefix);
		}
	}
	if(is_command) {
		/* here is when we parse all commands */
		parsecommand(target, nick, userhost, message + ppos);
	}
	/* it is possible that the channel was deleted */
	if((this = getchptr(target)) == NULL) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsemsg->channel %s was deleted\n", target);
#endif
		return;
	}
	if(hasuserflags(this, userhost, "x", "")) {
		/* user's messages won't be saved */
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsemsg->messages from %s to %s won't be saved\n", nick, target);
#endif
		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(this, "L")) {
		updatelast(this, 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;
	time_t idle_per;
        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 ts[TINYSTRINGLEN + 1];
	char *restptr;
	struct channel *this;
	struct channel *that;
	struct chanuser *u;
#ifdef DEBUG
        oer_debug(OER_DEBUG_INFO, "parsecommand(\"%s\", \"%s\", \"%s\", \"%s\")\n", target, nick, userhost, commandline);
#endif
	/* 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, " ", outstring, WRITE_BUFFER_LENGTH, 0)) < 0) {
                return;
        }
	tochan = (isme(target)) ? 0 : 1;
	if(!tochan) {
		if(!isvalidchannel(outstring)) {
			/* no channel given, continue from here */
			strncpy(channel, "<NONE>", CHANLEN);
			restptr = commandline + prevppos;
		} else {
			/* advance to next parameter, skip channel */
			strncpy(channel, outstring, CHANLEN);
			restptr = commandline + ppos;
		}
	} else {
		strncpy(channel, target, CHANLEN);
		restptr = commandline + prevppos;
	}
#ifdef DEBUG
	oer_debug(OER_DEBUG_INFO, "parsecommand->command is %s and channel is %s\n", command, channel);
#endif
	/* command line parsing continues at restptr */
	if(strlen(restptr) > 0) {
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->rest of the commandline is %s\n", restptr);
#endif
	}
	switch((which = whichcommand(command, wordcount(restptr)))) {
	case OER_WHICHCOMMAND_TOPIC_SET:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_SET\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
		if(!isatleastopnow(this, nick, userhost)) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		delalltopics(this);
		addnewtopic(this, nick, restptr);
		return;
	case OER_WHICHCOMMAND_TOPIC_ADD:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_ADD\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
		if(!isatleastopnow(this, nick, userhost)) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		addnewtopic(this, nick, restptr);
		return;
	case OER_WHICHCOMMAND_TOPIC_DEL:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_DEL\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			return;
		}
		if(deltopic(this, atoi(restptr), topic) == NULL) {
			return;
		}
		snprintf(outstring, WRITE_BUFFER_LENGTH, "deleted Topic #%d: %s", atoi(restptr), topic);
		sendreply((tochan) ? this->name : nick, tochan, 0, OER_TIMED_PRIORITY_NORMAL, outstring);
		return;
	case OER_WHICHCOMMAND_TOPIC_EDIT:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_EDIT\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		if(!strlen(p1) || !strlen(restptr + ppos)) {
			return;
		}
		edittopic(this, nick, restptr + ppos, atoi(p1));
		return;
	case OER_WHICHCOMMAND_TOPIC_INS:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_INS\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		if(!strlen(p1) || !strlen(restptr + ppos)) {
			return;
		}
		insertnewtopic(this, nick, restptr + ppos, atoi(p1));
		return;
	case OER_WHICHCOMMAND_TOPIC_LIST:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_LIST\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		listtopic(this, (tochan) ? this->name : nick, tochan);
		return;
	case OER_WHICHCOMMAND_TOPIC_GET:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_GET\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		delalltopics(this);
		snprintf(timed_str, WRITE_BUFFER_LENGTH, "TOPIC %s", this->name);
		timed_new(NULL, mystate->now, OER_TIMED_TYPE_NORMAL, OER_TIMED_PRIORITY_CHANNEL_HANDLING, timed_str);
		return;
	case OER_WHICHCOMMAND_TOPIC_REFRESH:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_REFRESH\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		this->topic_change = 1;
		return;
	case OER_WHICHCOMMAND_TOPIC_SWAP:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->TOPIC_SWAP\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "T")) {
                        /* skip topic handling for this channel */
                        return;
                }
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, 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;
		}
		swaptopic(this, atoi(p1), atoi(p2));
		return;
	case OER_WHICHCOMMAND_LOCK:
	case OER_WHICHCOMMAND_LOCKU:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->LOCK/LOCKU\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
		/* allow admins to lock even if not "l" chanflag */
                if(!isadmin(this, userhost)) {
                        if(!haschanflags(this, "l")) {
                                return;
                        }
                        if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                                return;
                        }
                        if(!isatleastopnow(this, nick, userhost)) {
                                return;
                        }
                }
		lockchan(this, restptr, (which == OER_WHICHCOMMAND_LOCK) ? 0 : 1, nick, userhost);
		return;
	case OER_WHICHCOMMAND_UNLOCK:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->UNLOCK\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		unlockchan(this, nick, userhost);
		return;
	case OER_WHICHCOMMAND_RANDOM_KICK:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->RANDOM_KICK\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
		if(!isadmin(this, userhost)) {
			if(!hasuserflags(this, userhost, "or", "")) {
				return;
			}
		}
		if((u = getrandomuser(this)) == NULL) {
			return;
		}
		snprintf(outstring, WRITE_BUFFER_LENGTH, "random kick by %s", nick);
		kickuser(this, mystate->now, u->nick, outstring);
		return;
	case OER_WHICHCOMMAND_RANDOM_BANKICK:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->RANDOM_BANKICK\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
		if(!isadmin(this, userhost)) {
			if(!hasuserflags(this, userhost, "or", "")) {
				return;
			}
		}
		if((u = getrandomuser(this)) == NULL) {
			return;
		}
		snprintf(outstring, WRITE_BUFFER_LENGTH, "random ban kick by %s", nick);
		banuser(this, mystate->now - 1, u->nick);
		kickuser(this, mystate->now, u->nick, outstring);
		return;
	case OER_WHICHCOMMAND_SYNC:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->SYNC\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		syncvoices(this);
		syncops(this);
		syncbans(this);
		syncnickbks(this);
		return;
	case OER_WHICHCOMMAND_SYNCALL:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->SYNCALL\n");
#endif
		if(!isopa(userhost)) {
			return;
		}
		for(that = mystate->channels; that != NULL; that = that->next) {
			if(!that->i_am_op) {
				continue;
			}
			syncvoices(that);
			syncops(that);
			syncbans(that);
			syncnickbks(that);
		}
		return;
	case OER_WHICHCOMMAND_JUMP:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->JUMP\n");
#endif
		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:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->QUIT\n");
#endif
		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:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->OP\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			/* the messager wants to be opped */
			if(isopnow(this, nick)) {
				return;
			}
			if(haschanflags(this, "u") && !isallowedop(this, nick)) {
				return;
			}
			mmode_new(this, 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(this, p1)) {
				continue;
			}
			if(haschanflags(this, "u") && !isallowedop(this, p1)) {
				continue;
			}
			mmode_new(this, mystate->now, "+o", p1);
		}
		return;
	case OER_WHICHCOMMAND_DOP:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->DOP\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			/* the messager wants to be de-opped */
			if(!isopnow(this, nick)) {
				return;
			}
			if(haschanflags(this, "D") && isallowedop(this, nick)) {
				return;
			}
			mmode_new(this, 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(this, p1)) {
				continue;
			}
			if(haschanflags(this, "D") && isallowedop(this, p1)) {
				continue;
			}
			mmode_new(this, mystate->now, "-o", p1);
		}
		return;
	case OER_WHICHCOMMAND_VOICE:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->VOICE\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			/* the messager wants to be voiced */
			if(!isvoicenow(this, nick)) {
				mmode_new(this, 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(this, p1)) {
				mmode_new(this, mystate->now, "+v", p1);
			}
		}
		return;
	case OER_WHICHCOMMAND_DEVOICE:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->DEVOICE\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			/* the messager wants to be devoiced */
			if(isvoicenow(this, nick)) {
				mmode_new(this, mystate->now, "-v", nick);
			}
			return;
		}
		/* devoice all given nicks */
		ppos = 0;
		while(1) {
			nppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0);
			if(nppos == ppos || nppos < 0) {
				break;
			}
			ppos = nppos;
			if(isvoicenow(this, p1)) {
				mmode_new(this, mystate->now, "-v", p1);
			}
		}
		return;
	case OER_WHICHCOMMAND_BAN:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->BAN\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        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(this, mystate->now, p1);
		}
		return;
	case OER_WHICHCOMMAND_UNBAN:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->UNBAN\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        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(this, mystate->now, p1);
		}
		return;
	case OER_WHICHCOMMAND_BANKICK:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->BANKICK\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			return;
		}
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		banuser(this, mystate->now - 1, p1);
		kickuser(this, mystate->now, p1, (strlen(restptr + ppos) > 0) ? restptr + ppos : NULL);
		return;
	case OER_WHICHCOMMAND_KICK:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->KICK\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			return;
		}
		ppos = 0;
		if((ppos = parse(restptr, ppos, " ", p1, STRINGLEN, 0)) < 0) {
			return;
		}
		kickuser(this, mystate->now, p1, (strlen(restptr + ppos) > 0) ? restptr + ppos : NULL);
		return;
	case OER_WHICHCOMMAND_INVITE:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->INVITE\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(tochan) {
			/* this makes no sense */
			return;
		}
		snprintf(timed_str, WRITE_BUFFER_LENGTH, "INVITE %s %s", nick, this->name);
		timed_new(NULL, mystate->now, OER_TIMED_TYPE_NORMAL, OER_TIMED_PRIORITY_CHANNEL_HANDLING, timed_str);
		return;
	case OER_WHICHCOMMAND_MASSMESSAGE:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->MASSMESSAGE\n");
#endif
		if(!isopa(userhost)) {
			return;
		}
		if(strlen(restptr) > 0) {
			massmessage(nick, restptr);
		}
		return;
	case OER_WHICHCOMMAND_MODE:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->MODE\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(!this->i_am_op) {
			return;
		}
                if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        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 */
			mmode_new(this, mystate->now, p1, NULL);
			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);
#ifdef DEBUG
			oer_debug(OER_DEBUG_INFO, "parsecommand->mode %s %s %s\n", this->name, modeline, p2);
#endif
			mmode_new(this, mystate->now, modeline, p2);
			counter++;
		}
		return;
	case OER_WHICHCOMMAND_HELP:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->HELP\n");
#endif
		/* may be NULL */
		this = getchptr(channel);
		proceed = (isopa(userhost)) ? 1 : 0;
		for(that = mystate->channels; that != NULL && !proceed; that = that->next) {
			if(haschanflags(that, "U") && !isatleastop(that, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(that, nick, userhost)) {
				proceed = 1;
			}
		}
		/* check if message was sent to a channel and if it was
		   is "s" flag set */
		if(tochan && haschanflags(this, "s")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		strncpy(outstring, OER_HELP, WRITE_BUFFER_LENGTH);
		sendreply((tochan) ? channel : nick, tochan, 0, OER_TIMED_PRIORITY_NORMAL, outstring);
		return;
	case OER_WHICHCOMMAND_LIST:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->LIST\n");
#endif
		/* may be NULL */
		this = getchptr(channel);
                proceed = (isopa(userhost)) ? 1 : 0;
		/* for some lists it is sufficient that the user
		   is on one of our channels */
		for(that = mystate->channels; that != NULL && !proceed; that = that->next) {
			if(haschanflags(that, "U") && !isatleastop(that, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(that, nick, userhost)) {
				proceed = 1;
			}
		}
		if(!proceed || !strlen(restptr)) {
			return;
		}
		listcommand(this, (tochan) ? this->name : nick, tochan, nick, userhost, restptr);
		return;
	case OER_WHICHCOMMAND_ADD:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->ADD\n");
#endif
		/* may be NULL */
		this = getchptr(channel);
		if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
                if(!strlen(restptr)) {
                        return;
                }
		addcommand(this, (tochan) ? this->name : nick, tochan, nick, userhost, restptr);
		return;
	case OER_WHICHCOMMAND_DEL:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->DEL\n");
#endif
		/* may be NULL */
		this = getchptr(channel);
		if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
                if(!strlen(restptr)) {
                        return;
                }
		delcommand(this, (tochan) ? this->name : nick, tochan, nick, userhost, restptr);
		return;
	case OER_WHICHCOMMAND_EDIT:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->EDIT\n");
#endif
		/* may be NULL */
		this = getchptr(channel);
		if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
                        return;
                }
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
                if(!strlen(restptr)) {
                        return;
                }
		editcommand(this, (tochan) ? this->name : nick, tochan, nick, userhost, restptr);
		return;
	case OER_WHICHCOMMAND_LOGOFF:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->LOGOFF\n");
#endif
		if(!strlen(restptr)) {
			return;
		}
		this = getchptr(channel);
		logoffcommand(this, (tochan) ? this->name : nick, tochan, nick, userhost, restptr);
		return;
	case OER_WHICHCOMMAND_LOGON:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->LOGON\n");
#endif
		if(!strlen(restptr)) {
			return;
		}
		this = getchptr(channel);
		logoncommand(this, (tochan) ? this->name : nick, tochan, nick, userhost, restptr);
		return;
	case OER_WHICHCOMMAND_SAVE:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->SAVE\n");
#endif
                proceed = (isopa(userhost)) ? 1 : 0;
		for(that = mystate->channels; that != NULL && !proceed; that = that->next) {
			if(haschanflags(that, "U") && !isatleastop(that, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(that, nick, userhost)) {
				proceed = 1;
			}
		}
		if(!proceed) {
			return;
		}
		saveall();
		mystate->last_saved = mystate->now;
		return;
	case OER_WHICHCOMMAND_FLUSH:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->FLUSH\n");
#endif
                proceed = (isopa(userhost)) ? 1 : 0;
		for(that = mystate->channels; that != NULL && !proceed; that = that->next) {
			if(haschanflags(that, "U") && !isatleastop(that, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(that, nick, userhost)) {
				proceed = 1;
			}
		}
		if(!proceed) {
			return;
		}
		flushall();
		return;
	case OER_WHICHCOMMAND_INFO:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->INFO\n");
#endif
		/* may be NULL */
		this = getchptr(channel);
                proceed = (isopa(userhost)) ? 1 : 0;
		for(that = mystate->channels; that != NULL && !proceed; that = that->next) {
			if(haschanflags(that, "U") && !isatleastop(that, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(that, nick, userhost)) {
				proceed = 1;
			}
		}
		/* check if message was sent to a channel and if it was
		   is "s" flag set */
		if(tochan && haschanflags(this, "s")) {
			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, OER_TIMED_PRIORITY_NORMAL, outstring);
		return;
	case OER_WHICHCOMMAND_UPTIME:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->UPTIME\n");
#endif
		/* may be NULL */
		this = getchptr(channel);
                proceed = (isopa(userhost)) ? 1 : 0;
		for(that = mystate->channels; that != NULL && !proceed; that = that->next) {
			if(haschanflags(that, "U") && !isatleastop(that, nick, userhost)) {
				continue;
			}
			if(isatleastopnow(that, nick, userhost)) {
				proceed = 1;
			}
		}
		/* check if message was sent to a channel and if it was
		   is "s" flag set */
		if(tochan && haschanflags(this, "s")) {
			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);
			idle_per = idle_seconds % uptime_seconds;
			idle_per = uptime_seconds - idle_per;
			idle_per *= 100;
			idle_per /= uptime_seconds;
			idle_per = 100 - idle_per;
#ifdef OER_UPTIME_SHOW_IDLE
			snprintf(outstring2, WRITE_BUFFER_LENGTH, ", host uptime: %s (host idle: %lu%%)", uptimestring, idle_per);
#else
			snprintf(outstring2, WRITE_BUFFER_LENGTH, ", host uptime: %s", uptimestring);
#endif
			strncat(outstring, outstring2, WRITE_BUFFER_LENGTH - strlen(outstring));
		}
		sendreply((tochan) ? channel : nick, tochan, 0, OER_TIMED_PRIORITY_NORMAL, outstring);
		return;
	case OER_WHICHCOMMAND_ACTION:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->ACTION\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(strlen(restptr) > 0) {
			snprintf(outstring, WRITE_BUFFER_LENGTH, "%cACTION %s%c", 1, restptr, 1);
			sendreply(channel, 1, 0, OER_TIMED_PRIORITY_NORMAL, outstring);
		}
		return;
	case OER_WHICHCOMMAND_SAY:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->SAY\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(strlen(restptr) > 0) {
			sendreply(channel, 1, 0, OER_TIMED_PRIORITY_NORMAL, restptr);
		}
		return;
	case OER_WHICHCOMMAND_RAW:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->RAW\n");
#endif
		if(!isopa(userhost)) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		snprintf(timed_str, WRITE_BUFFER_LENGTH, "%s", restptr);
		timed_new(NULL, mystate->now, OER_TIMED_TYPE_NORMAL, OER_TIMED_PRIORITY_CHANNEL_PROTECTION, timed_str);
		return;
	case OER_WHICHCOMMAND_LAST:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->LAST\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
                proceed = (isopa(userhost)) ? 1 : 0;
		if(haschanflags(this, "U")) {
			if(isatleastop(this, nick, userhost)) {
				proceed = 1;
			}
		} else if(isatleastopnow(this, nick, userhost)) {
			proceed = 1;
		}
		/* check if message was sent to a channel and if it was
		   is "s" flag set */
		if(tochan && haschanflags(this, "s")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		showlast(this, (tochan) ? this->name : nick, tochan, restptr);
		return;
	case OER_WHICHCOMMAND_QUOTE:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->QUOTE\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
                proceed = (isopa(userhost)) ? 1 : 0;
		if(haschanflags(this, "U")) {
			if(isatleastop(this, nick, userhost)) {
				proceed = 1;
			}
		} else if(isatleastopnow(this, nick, userhost)) {
			proceed = 1;
		}
		/* check if message was sent to a channel and if it was
		   is "s" flag set */
		if(tochan && haschanflags(this, "s")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		if((msgcount = getondiskmsgcount(this, restptr)) <= 0) {
			return;
		}
		if(getrandommsg(this, 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, OER_TIMED_PRIORITY_NORMAL, outstring2);
		return;
	case OER_WHICHCOMMAND_SEEN:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->SEEN\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
                proceed = (isopa(userhost)) ? 1 : 0;
		if(haschanflags(this, "U")) {
			if(isatleastop(this, nick, userhost)) {
				proceed = 1;
			}
		} else if(isatleastopnow(this, nick, userhost)) {
			proceed = 1;
		}
		/* check if message was sent to a channel and if it was
		   is "s" flag set */
		if(tochan && haschanflags(this, "s")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		showseen(this, (tochan) ? channel : nick, tochan, restptr);
		return;
	case OER_WHICHCOMMAND_NSTATS:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->NSTATS\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
                proceed = (isopa(userhost)) ? 1 : 0;
		if(haschanflags(this, "U")) {
			if(isatleastop(this, nick, userhost)) {
				proceed = 1;
			}
		} else if(isatleastopnow(this, nick, userhost)) {
			proceed = 1;
		}
		/* check if message was sent to a channel and if it was
		   is "s" flag set */
		if(tochan && haschanflags(this, "s")) {
			proceed = 1;
		}
		if(!proceed) {
			return;
		}
		if(!strlen(restptr)) {
			return;
		}
		nstats(this, (tochan) ? channel : nick, tochan, restptr);
		return;
	case OER_WHICHCOMMAND_WALL:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->WALL\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
		if(haschanflags(this, "U") && !isatleastop(this, nick, userhost)) {
			return;
		}
                if(!isatleastopnow(this, nick, userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			return;
		}
		if(tochan) {
			/* makes no sense */
			return;
		}
		wall(this, nick, userhost, restptr);
		return;
	case OER_WHICHCOMMAND_CLONECHANNEL:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->CLONECHANNEL\n");
#endif
		if((this = getchptr(channel)) == NULL) {
			return;
		}
                if(!isopa(userhost)) {
                        return;
                }
		if(!strlen(restptr)) {
			return;
		}
		if(!clonechannel(channel, restptr)) {
			return;
		}
		snprintf(outstring, WRITE_BUFFER_LENGTH, "cloned channel %s to %s", channel, restptr);
		sendreply((tochan) ? channel : nick, tochan, 0, OER_TIMED_PRIORITY_NORMAL, outstring);
		return;
	case OER_WHICHCOMMAND_SSTATS:
#ifdef DEBUG
                oer_debug(OER_DEBUG_INFO, "parsecommand->SSTATS\n");
#endif
		/* may be NULL */
		this = getchptr(channel);
                proceed = (isopa(userhost)) ? 1 : 0;
                for(that = mystate->channels; that != NULL && !proceed; that = that->next) {
                        if(haschanflags(that, "U") && !isatleastop(that, nick, userhost)) {
                                continue;
                        }
                        if(isatleastopnow(that, nick, userhost)) {
                                proceed = 1;
                        }
                }
		/* check if message was sent to a channel and if it was
		   is "s" flag set */
		if(tochan && haschanflags(this, "s")) {
			proceed = 1;
		}
                if(!proceed) {
                        return;
                }
                secondstostring(mystate->now - mystate->current_server->linkup, uptimestring, STRINGLEN);
                snprintf(outstring, WRITE_BUFFER_LENGTH, "%s RX/TX bytes: %d/%d, link connected for %s", mystate->current_server->serverhost, mystate->current_server->rx, mystate->current_server->tx, uptimestring);
                sendreply((tochan) ? channel : nick, tochan, 0, OER_TIMED_PRIORITY_NORMAL, outstring);
                return;
	case OER_WHICHCOMMAND_DATE:
#ifdef DEBUG
                oer_debug(OER_DEBUG_INFO, "parsecommand->DATE\n");
#endif
		/* may be NULL */
		this = getchptr(channel);
                proceed = (isopa(userhost)) ? 1 : 0;
                for(that = mystate->channels; that != NULL && !proceed; that = that->next) {
                        if(haschanflags(that, "U") && !isatleastop(that, nick, userhost)) {
                                continue;
                        }
                        if(isatleastopnow(that, nick, userhost)) {
                                proceed = 1;
                        }
                }
		/* check if message was sent to a channel and if it was
		   is "s" flag set */
		if(tochan && haschanflags(this, "s")) {
			proceed = 1;
		}
                if(!proceed) {
                        return;
                }
		strncpy(ts, ctime(&mystate->now), TINYSTRINGLEN);
                striplf(ts);
                tzset();
                snprintf(outstring, WRITE_BUFFER_LENGTH, "my date is %s %s %s", ts, tzname[0], tzname[1]);
                sendreply((tochan) ? channel : nick, tochan, 0, OER_TIMED_PRIORITY_NORMAL, outstring);
                return;
        case OER_WHICHCOMMAND_INVALID:
#ifdef DEBUG
		oer_debug(OER_DEBUG_INFO, "parsecommand->invalid or malformed command %s\n", command);
#endif
		break;
	}
}
