# Author: Chris "BinGOs" Williams
#
# This module may be used, modified, and distributed under the same
# terms as Perl itself. Please see the license that came with your Perl
# distribution for details.
#
package POE::Component::Server::IRC;

use strict;
use warnings FATAL => 'all';
use base qw(POE::Component::Server::IRC::Backend);
use POE;
use POE::Component::Server::IRC::Common qw(:ALL);
use POE::Component::Server::IRC::Plugin qw(:ALL);
use POSIX qw(strftime);
use vars qw($VERSION $REVISION);

$VERSION = '1.36';
($REVISION) = (q$LastChangedRevision: 163 $=~/(\d+)/g);

sub spawn {
  my $package = shift;
  my $self = $package->create(@_);
  $self->{prefix} = 'ircd_';
  $self->{config}->{ uc $_ } = delete $self->{config}->{$_} for keys %{ $self->{config} };
  $self->configure();
  $self->_state_create();
  $self->{ircd} = $self;
  return $self;
}

sub _load_our_plugins {
  my $self = shift;
  $poe_kernel->state( 'add_spoofed_nick', $self );
  $poe_kernel->state( 'del_spoofed_nick', $self );
  $poe_kernel->state( "daemon_cmd_$_", $self, '_spoofed_command' ) for qw(join part mode kick topic nick privmsg notice gline kline unkline rkline sjoin locops wallops operwall);
}

sub IRCD_connection {
  my ($self,$ircd) = splice @_,0 ,2;
  my ($conn_id,$peeraddr,$peerport,$sockaddr,$sockport) = map { ${ $_ } } @_;

  delete $self->{state}->{conns}->{ $conn_id } if $self->_connection_exists( $conn_id );
  $self->{state}->{conns}->{ $conn_id }->{registered} = 0;
  $self->{state}->{conns}->{ $conn_id }->{type} = 'u';
  $self->{state}->{conns}->{ $conn_id }->{seen} = time();
  $self->{state}->{conns}->{ $conn_id }->{socket} = [ $peeraddr, $peerport, $sockaddr, $sockport ];
  $self->_state_conn_stats();
  return PCSI_EAT_ALL;
}

sub IRCD_connected {
  my ($self,$ircd) = splice @_,0 ,2;
  my ($conn_id,$peeraddr,$peerport,$sockaddr,$sockport,$name) = map { ${ $_ } } @_;

  delete $self->{state}->{conns}->{ $conn_id } if $self->_connection_exists( $conn_id );
  $self->{state}->{conns}->{ $conn_id }->{registered} = 0;
  $self->{state}->{conns}->{ $conn_id }->{cntr} = 1;
  $self->{state}->{conns}->{ $conn_id }->{type} = 'u';
  $self->{state}->{conns}->{ $conn_id }->{seen} = time();
  $self->{state}->{conns}->{ $conn_id }->{socket} = [ $peeraddr, $peerport, $sockaddr, $sockport ];
  $self->_state_conn_stats();
  $self->_state_send_credentials( $conn_id, $name );
  return PCSI_EAT_ALL;
}

sub IRCD_connection_flood {
  my ($self,$ircd) = splice @_,0 ,2;
  my ($conn_id) = map { ${ $_ } } @_;
  $self->_terminate_conn_error( $conn_id, 'Excess Flood' );
  return PCSI_EAT_ALL;
}

sub IRCD_connection_idle {
  my ($self,$ircd) = splice @_,0 ,2;
  my ($conn_id,$interval) = map { ${ $_ } } @_;
  return PCSI_EAT_NONE unless $self->_connection_exists( $conn_id );
  my $conn = $self->{state}->{conns}->{ $conn_id };
  if ( $conn->{type} eq 'u' ) {
  	$self->_terminate_conn_error( $conn_id, 'Connection Timeout' );
  	return PCSI_EAT_ALL;
  }
  if ( $conn->{pinged} ) {
	my $msg = 'Ping timeout: ' . ( time() - $conn->{seen} ) . ' seconds';
  	$self->_terminate_conn_error( $conn_id, $msg );
  	return PCSI_EAT_ALL;
  }
  $conn->{pinged} = 1;
  $self->{ircd}->send_output( { command => 'PING', params => [ $self->server_name() ] }, $conn_id );
  return PCSI_EAT_ALL;
}

sub IRCD_auth_done {
  my ($self,$ircd) = splice @_,0 ,2;
  my ($conn_id,$ref) = map { ${ $_ } } @_;
  return PCSI_EAT_ALL unless $self->_connection_exists( $conn_id );
  $self->{state}->{conns}->{ $conn_id }->{auth} = $ref;
  $self->_client_register( $conn_id );
  return PCSI_EAT_ALL;
}

sub IRCD_disconnected {
  my ($self,$ircd) = splice @_,0 ,2;
  my ($conn_id,$errstr) = map { ${ $_ } } @_;
  return PCSI_EAT_ALL unless $self->_connection_exists( $conn_id );

  SWITCH: {
    unless ( $self->_connection_registered( $conn_id ) ) {
	delete $self->{state}->{conns}->{ $conn_id };
	last SWITCH;
    }
    if ( $self->_connection_is_peer( $conn_id ) ) {
	my $peer = $self->{state}->{conns}->{ $conn_id }->{name};
	$self->{ircd}->send_output( @{ $self->_daemon_peer_squit( $conn_id, $peer, $errstr ) } );
	delete $self->{state}->{conns}->{ $conn_id };
	last SWITCH;
    }
    if ( $self->_connection_is_client( $conn_id ) ) {
	$self->{ircd}->send_output( @{ $self->_daemon_cmd_quit( $self->_client_nickname( $conn_id, $errstr ), $errstr ) } );
	delete $self->{state}->{conns}->{ $conn_id };
	last SWITCH;
    }
  }
  return PCSI_EAT_ALL;
}

sub IRCD_compressed_conn {
  my ($self,$ircd) = splice @_,0 ,2;
  my ($conn_id) = map { ${ $_ } } @_;
  $self->_state_send_burst( $conn_id );
  return PCSI_EAT_ALL;
}

sub _default {
  my ($self,$ircd,$event) = splice @_, 0, 3;
  return PCSI_EAT_NONE unless $event =~ /^IRCD_cmd_/;
  my ($conn_id,$input) = map { ${ $_ } } @_;

  return PCSI_EAT_ALL unless $self->_connection_exists( $conn_id );
  $self->{state}->{conns}->{ $conn_id }->{seen} = time();
  SWITCH: {
	unless ( $self->_connection_registered( $conn_id ) ) {
		$self->_cmd_from_unknown( $conn_id, $input );
		last SWITCH;
	}
	if ( $self->_connection_is_peer( $conn_id ) ) {
		$self->_cmd_from_peer( $conn_id, $input );
		last SWITCH;
	}
	if ( $self->_connection_is_client( $conn_id ) ) {
		delete $input->{prefix};
		$self->_cmd_from_client( $conn_id, $input );
		last SWITCH;
	}
  };

  return PCSI_EAT_ALL;
}

sub _auth_finished {
  my $self = shift;
  my $conn_id = shift || return undef;
  return unless $self->_connection_exists( $conn_id );
  return $self->{state}->{conns}->{ $conn_id }->{auth};
}

sub _connection_exists {
  my $self = shift;
  my $conn_id = shift || return 0;
  return 0 unless defined $self->{state}->{conns}->{ $conn_id };
  return 1;
}

sub _client_register {
  my $self = shift;
  my $conn_id = shift || return undef;
  return unless $self->_connection_exists( $conn_id );
  return unless $self->{state}->{conns}->{ $conn_id }->{nick};
  return unless $self->{state}->{conns}->{ $conn_id }->{user};
  my $auth = $self->_auth_finished( $conn_id );
  return unless $auth;
  # pass required for link
  unless ( $self->_state_auth_client_conn( $conn_id ) ) {
    $self->_terminate_conn_error( $conn_id, 'You are not authorized to use this server' );
    return;
  }
  if ( $self->_state_user_matches_gline( $conn_id ) ) {
    $self->_terminate_conn_error( $conn_id, 'G-Lined' );
    return;
  }
  if ( $self->_state_user_matches_kline( $conn_id ) ) {
    $self->_terminate_conn_error( $conn_id, 'K-Lined' );
    return;
  }
  if ( $self->_state_user_matches_rkline( $conn_id ) ) {
    $self->_terminate_conn_error( $conn_id, 'K-Lined' );
    return;
  }
  # Add new nick
  $self->_state_register_client( $conn_id );
  my $server = $self->server_name();
  my $nick = $self->_client_nickname( $conn_id );
  my $port = $self->{state}->{conns}->{ $conn_id }->{socket}->[3];
  my $version = $self->server_version();
  my $network = $self->server_config('NETWORK');
  my $server_is = $server . '[' . $server . '/' . $port . ']';
  $self->_send_output_to_client( $conn_id => { prefix => $server, command => '001', params => [ $nick, "Welcome to the $network Internet Relay Chat network $nick" ] } );
  $self->_send_output_to_client( $conn_id => { prefix => $server, command => '002', params => [ $nick, "Your host is $server_is, running version $version" ] } );
  $self->_send_output_to_client( $conn_id => { prefix => $server, command => '003', params => [ $nick, $self->server_created() ] } );
  $self->_send_output_to_client( $conn_id => { prefix => $server, command => '004', params => [ $nick, $server, $version, 'Dilowz', 'biklmnopstveIh', 'bkloveIh' ], colonify => 0 } );
  $self->_send_output_to_client( $conn_id => $_ ) for @{ $self->_daemon_cmd_isupport( $nick ) };
  $self->{state}->{conns}->{ $conn_id }->{registered} = 1;
  $self->{state}->{conns}->{ $conn_id }->{type} = 'c';
  $self->{ircd}->send_event( 'cmd_lusers' => $conn_id => { command => 'LUSERS' } );
  $self->{ircd}->send_event( 'cmd_motd' => $conn_id => { command => 'MOTD' } );
  $self->{ircd}->send_event( 'cmd_mode' => $conn_id => { command => 'MODE', params => [ $nick, '+i' ] } );
  return 1;
}

sub _connection_registered {
  my $self = shift;
  my $conn_id = shift || return undef;
  return unless $self->_connection_exists( $conn_id );
  return $self->{state}->{conns}->{ $conn_id }->{registered};
}

sub _connection_is_peer {
  my $self = shift;
  my $conn_id = shift || return undef;

  return unless $self->_connection_exists( $conn_id );
  return unless $self->{state}->{conns}->{ $conn_id }->{registered};
  return 1 if $self->{state}->{conns}->{ $conn_id }->{type} eq 'p';
  return 0;
}

sub _connection_is_client {
  my $self = shift;
  my $conn_id = shift || return undef;

  return unless $self->_connection_exists( $conn_id );
  return unless $self->{state}->{conns}->{ $conn_id }->{registered};
  return 1 if $self->{state}->{conns}->{ $conn_id }->{type} eq 'c';
  return 0;
}

sub _cmd_from_unknown {
  my ($self,$wheel_id,$input) = splice @_, 0, 3;

  my $cmd = uc $input->{command};
  my $params = $input->{params} || [ ];
  my $pcount = scalar @{ $params };
  my $invalid = 0;
  SWITCH: {
    if ( $cmd eq 'QUIT' ) {
	$self->_terminate_conn_error( $wheel_id, 'Client Quit' );
	last SWITCH;
    }
    # PASS or NICK cmd but no parameters.
    if ( $cmd =~ /^(PASS|NICK|SERVER)$/ and !$pcount ) {
	$self->_send_output_to_client( $wheel_id => '461' => $cmd );
	last SWITCH;
    }
    # PASS or NICK cmd with one parameter, connection from client
    if ( $cmd eq 'PASS' and $pcount ) {
	$self->{state}->{conns}->{ $wheel_id }->{ lc $cmd } = $params->[0];
	if ( $params->[1] and $params->[1] =~ /TS$/ ) {
	  $self->{state}->{conns}->{ $wheel_id }->{ts_server} = 1;
	  $self->{ircd}->antiflood( $wheel_id => 0 );
	}
	last SWITCH;
    }
    # SERVER stuff.
    if ( $cmd eq 'CAPAB' and $pcount ) {
	$self->{state}->{conns}->{ $wheel_id }->{capab} = [ split /\s+/, $params->[0] ];
	last SWITCH;
    }
    if ( $cmd eq 'SERVER' and $pcount < 2 ) {
	$self->_send_output_to_client( $wheel_id => '461' => $cmd );
	last SWITCH;
    }
    if ( $cmd eq 'SERVER' ) {
	my $conn = $self->{state}->{conns}->{ $wheel_id };
	$conn->{name} = $params->[0];
	$conn->{hops} = $params->[1] || 1;
	$conn->{desc} = $params->[2] || '';
	if ( !$conn->{ts_server} ) {
	   $self->_terminate_conn_error( $wheel_id, 'Non-TS server.' );
	   last SWITCH;
	}
	if ( !$self->_state_auth_peer_conn( $wheel_id, $conn->{name}, $conn->{pass} ) ) {
	   $self->_terminate_conn_error( $wheel_id, 'Unauthorised server.' );
	   last SWITCH;
	}
	if ( $self->state_peer_exists( $conn->{name} ) ) {
	   $self->_terminate_conn_error( $wheel_id, 'Server exists.' );
	   last SWITCH;
	}
	$self->_state_register_peer( $wheel_id );
	if ( $conn->{zip} and scalar grep { $_ eq 'ZIP' } @{ $conn->{capab} } ) {
	   $self->{ircd}->compressed_link( $wheel_id, 1, $conn->{cntr} );
	} else {
	   $self->_state_send_burst( $wheel_id );
	}
  	$self->{ircd}->send_event( "daemon_capab", $conn->{name}, @{ $conn->{capab} } );
	last SWITCH;
    }
    if ( $cmd eq 'NICK' and $pcount ) {
	if ( !validate_nick_name( $params->[0] ) ) {
	  $self->_send_output_to_client( $wheel_id => '432' => $params->[0] );
	  last SWITCH;
	}
	if ( $self->state_nick_exists( $params->[0] ) ) {
	  $self->_send_output_to_client( $wheel_id => '433' => $params->[0] );
	  last SWITCH;
	}
	my $nicklen = $self->server_config('NICKLEN');
	$params->[0] = substr($params->[0],0,$nicklen) if length( $params->[0] ) > $nicklen;
	$self->{state}->{conns}->{ $wheel_id }->{ lc $cmd } = $params->[0];
	$self->{state}->{pending}->{ u_irc $params->[0] } = $wheel_id;
	$self->_client_register( $wheel_id );
	last SWITCH;
    }
    if ( $cmd eq 'USER' and $pcount < 4 ) {
	$self->_send_output_to_client( $wheel_id => '461' => $cmd );
	last SWITCH;
    }
    if ( $cmd eq 'USER' ) {
	$self->{state}->{conns}->{ $wheel_id }->{user} = $params->[0];
	$self->{state}->{conns}->{ $wheel_id }->{ircname} = $params->[3] || '';
	$self->_client_register( $wheel_id );
	last SWITCH;
    }
    last SWITCH if $self->{state}->{conns}->{ $wheel_id }->{cntr};
    $invalid = 1;
    $self->_send_output_to_client( $wheel_id => '451' );
  }
  return 1 if $invalid;
  $self->_state_cmd_stat( $cmd, $input->{raw_line} );
  return 1;
}

sub _cmd_from_peer {
  my ($self,$conn_id,$input) = splice @_, 0, 3;

  my $cmd = $input->{command};
  my $params = $input->{params};
  my $prefix = $input->{prefix};
  my $invalid = 0;
  SWITCH: {
    my $method = '_daemon_peer_' . lc $cmd;
    if ( $cmd eq 'SQUIT' and !$prefix ) {
	$self->_daemon_peer_squit( $conn_id, @{ $params } );
	#$self->_send_output_to_client( $conn_id => $prefix => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for $self->_daemon_cmd_squit( $prefix, @{ $params } );
	last SWITCH;
    }
    if ( $cmd =~ /\d{3}/ ) {
	$self->{ircd}->send_output( $input, $self->_state_user_route( $params->[0] ) );
	last SWITCH;
    }
    if ( $cmd eq 'QUIT' ) {
	$self->{ircd}->send_output( @{ $self->_daemon_peer_quit( $prefix, @{ $params }, $conn_id ) } );
	last SWITCH;
    }
    if ( $cmd =~ /^(PRIVMSG|NOTICE)$/ ) {
	$self->_send_output_to_client( $conn_id => $prefix => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for $self->_daemon_peer_message( $conn_id, $prefix, $cmd, @{ $params } );
	last SWITCH;
    }
    if ( $cmd =~ /^(WHOIS|VERSION|TIME|NAMES|LINKS|ADMIN|INFO|MOTD|SQUIT)$/i ) {
	my $client_method = '_daemon_cmd_' . lc $cmd;
	$self->_send_output_to_client( $conn_id => $prefix => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for $self->$client_method( $prefix, @{ $params } );
	last SWITCH;
    }
    if ( $cmd =~ /^(PING|PONG)$/i and $self->can($method) ) {
	$self->$method( $conn_id, @{ $params } );
	last SWITCH;
    }
    if ( $cmd =~ /^SVINFO$/i and $self->can($method) ) {
	$self->$method( $conn_id, @{ $params } );
	my $conn = $self->{state}->{conns}->{ $conn_id };
  	$self->{ircd}->send_event( "daemon_svinfo", $conn->{name}, @{ $params } );
	last SWITCH;
    }
    $method = '_daemon_peer_umode' if $cmd eq 'MODE' and $self->state_nick_exists( $params->[0] );
    if ( $self->can($method) ) {
	$self->$method( $conn_id, $prefix, @{ $params } );
	last SWITCH;
    }
    $invalid = 1;
  }
  return 1 if $invalid;
  $self->_state_cmd_stat( $cmd, $input->{raw_line}, 1 );
  return 1;
}

sub _cmd_from_client {
  my ($self,$wheel_id,$input) = splice @_, 0, 3;

  my $cmd = uc $input->{command};
  my $params = $input->{params} || [ ];
  my $pcount = scalar @{ $params };
  my $server = $self->server_name();
  my $nick = $self->_client_nickname( $wheel_id );
  my $invalid = 0;
  SWITCH: {
    my $method = '_daemon_cmd_' . lc $cmd;
    if ( $cmd eq 'QUIT' ) {
	$self->_terminate_conn_error( $wheel_id, ( $pcount ? qq{"$params->[0]"} : 'Client Quit' ) );
	last SWITCH;
    }
    if ( $cmd =~ /^(USERHOST|MODE)$/ and !$pcount ) {
	$self->_send_output_to_client( $wheel_id => '461' => $cmd );
	last SWITCH;
    }
    if ( $cmd =~ /^(USERHOST)$/ ) {
	$self->_send_output_to_client( $wheel_id => $_ ) for $self->$method( $nick, ( $pcount <= 5 ? @{ $params } : @{ $params }[0..5] ) );
	last SWITCH;
    }
    if ( $cmd =~ /^(PRIVMSG|NOTICE)$/ ) {
	$self->{state}->{conns}->{ $wheel_id }->{idle_time} = time();
	$self->_send_output_to_client( $wheel_id => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for $self->_daemon_cmd_message( $nick, $cmd, @{ $params } );
	last SWITCH;
    }
    if ( $cmd eq 'MODE' and $self->state_nick_exists( $params->[0] ) ) {
	if ( ( u_irc $nick ) ne ( u_irc $params->[0] ) ) {
		$self->_send_output_to_client( $wheel_id => '502' );
		last SWITCH;
	}
	my $modestring = join('', @{ $params }[1..$#{ $params }] );
	$modestring =~ s/\s+//g;
	$modestring =~ s/[^a-zA-Z+-]+//g;
	$modestring =~ s/[^DGglwiozl+-]+//g;
	$modestring = unparse_mode_line $modestring;
	$self->_send_output_to_client( $wheel_id => $_ ) for $self->_daemon_cmd_umode( $nick, $modestring );
	last SWITCH;
    }
    if ( $self->can($method) ) {
	$self->_send_output_to_client( $wheel_id => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for $self->$method( $nick, @{ $params } );
	last SWITCH;
    }
    $invalid = 1;
    $self->_send_output_to_client( $wheel_id => '421' => $cmd );
  }
  return 1 if $invalid;
  $self->_state_cmd_stat( $cmd, $input->{raw_line} );
  return 1;
}

sub _daemon_cmd_message {
  my $self = shift;
  my $nick = shift || return;
  my $type = shift || return;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count ) {
	push @{ $ref }, [ '461', $type ];
	last SWITCH;
    }
    if ( $count < 2 or !$args->[1] ) {
	push @{ $ref }, [ '412' ];
	last SWITCH;
    }
    my $targets = 0;
    my $max_targets = $self->server_config('MAXTARGETS');
    my $full = $self->state_user_full( $nick );
    my $targs = $self->_state_parse_msg_targets( $args->[0] );
    LOOP: foreach my $target ( keys %{ $targs } ) {
	my $targ_type = shift @{ $targs->{$target} };
	if ( $targ_type =~ /(server|host)mask/ and !$self->state_user_is_operator( $nick ) ) {
	   push @{ $ref }, [ '481' ];
	   next LOOP;
	}
	if ( $targ_type =~ /(server|host)mask/ and $targs->{$target}->[0] !~ /\./ ) {
	  push @{ $ref }, [ '413', $target ];
	  next LOOP;
	}
	if ( $targ_type =~ /(server|host)mask/ and $targs->{$target}->[0] =~ /\x2E.*[\x2A\x3F]+.*$/ ) {
	  push @{ $ref }, [ '414', $target ];
	  next LOOP;
	}
	if ( $targ_type eq 'channel_ext' and !$self->state_chan_exists( $targs->{$target}->[1] ) ) {
	   push @{ $ref }, [ '401', $targs->{$target}->[1] ];
	   next LOOP;
	}
	if ( $targ_type eq 'channel' and !$self->state_chan_exists( $target ) ) {
	   push @{ $ref }, [ '401', $target ];
	   next LOOP;
	}
	if ( $targ_type eq 'nick' and !$self->state_nick_exists( $target ) ) {
	   push @{ $ref }, [ '401', $target ];
	   next LOOP;
	}
	if ( $targ_type eq 'nick_ext' and !$self->state_peer_exists( $targs->{$target}->[1] ) ) {
	   push @{ $ref }, [ '402', $targs->{$target}->[1] ];
	   next LOOP;
	}
	$targets++;
        if ( $targets > $max_targets ) {
	  push @{ $ref }, [ '407', $target ];
	  last SWITCH;
        }
	# $$whatever
	if ( $targ_type eq 'servermask' ) {
	  my $us = 0;
          my %targets;
	  my $ucserver = uc $self->server_name();
          foreach my $peer ( keys %{ $self->{state}->{peers} } ) {
	    if ( matches_mask( $targs->{$target}->[0], $peer ) ) {
	      if ( $ucserver eq $peer ) {
		$us = 1;
	      } else {
		$targets{ $self->_state_peer_route( $peer ) }++;
	      }
	    }
          }
	  $self->{ircd}->send_output( { prefix => $nick, command => $type, params => [ $target, $args->[1] ] }, keys %targets );
	  if ( $us ) {
	    my $local = $self->{state}->{peers}->{ uc $self->server_name() }->{users};
	    my @local; my $spoofed = 0;
	    foreach my $luser ( values %{ $local } ) {
		if ( $luser->{route_id} eq 'spoofed' ) {
		  $spoofed = 1;
		} else {
		  push @local, $luser->{route_id};
		}
	    }
	    $self->{ircd}->send_output( { prefix => $full, command => $type, params => [ $target, $args->[1] ] }, @local );
  	    $self->{ircd}->send_event( "daemon_" . lc $type, $full, $target, $args->[1] ) if $spoofed;
	  }
	  next LOOP;
	}
	# $#whatever
	if ( $targ_type eq 'hostmask' ) {
	  my $spoofed = 0;
	  my %targets; my @local;
	  HOST: foreach my $luser ( values %{ $self->{state}->{users} } ) {
	     next HOST unless matches_mask( $targs->{$target}->[0], $luser->{auth}->{hostname} );
	     if ( $luser->{route_id} eq 'spoofed' ) {
		$spoofed = 1;
	     } elsif ( $luser->{type} eq 'r' ) {
		$targets{ $luser->{route_id} }++;
	     } else {
		push @local, $luser->{route_id};
	     }
	  }
	  $self->{ircd}->send_output( { prefix => $nick, command => $type, params => [ $target, $args->[1] ] }, keys %targets );
	  $self->{ircd}->send_output( { prefix => $full, command => $type, params => [ $target, $args->[1] ] }, @local );
  	  $self->{ircd}->send_event( "daemon_" . lc $type, $full, $target, $args->[1] ) if $spoofed;
	  next LOOP;
	}
	if ( $targ_type eq 'nick_ext' ) {
	  $targs->{$target}->[1] = $self->_state_peer_name( $targs->{$target}->[1] );
	  if ( $targs->{$target}->[2] and !$self->state_user_is_operator( $nick ) ) {
	    push @{ $ref }, [ '481' ];
	    next LOOP;
	  }
	  if ( $targs->{$target}->[1] ne $self->server_name() ) {
	    $self->{ircd}->send_output( { prefix => $nick, command => $type, params => [ $target, $args->[1] ] }, $self->_state_peer_route( $targs->{$target}->[1] ) );
	    next LOOP;
	  }
	  if ( uc ( $targs->{$target}->[0] ) eq 'OPERS' ) {
	    unless ( $self->state_user_is_operator( $nick ) ) {
	      push @{ $ref }, [ '481' ];
	      next LOOP;
	    }
	    $self->{ircd}->send_output( { prefix => $full, command => $type, params => [ $target, $args->[1] ] }, keys %{ $self->{state}->{localops} } );
	    next LOOP;
	  }
	  my @local = $self->_state_find_user_host( $targs->{$target}->[0], $targs->{$target}->[2] );
	  if ( scalar @local == 1 ) {
	      my $ref = shift @local;
	      if ( $ref->[0] eq 'spoofed' ) {
	        $self->{ircd}->send_event( "daemon_" . lc $type, $full, $ref->[1], $args->[1] );
	      } else {
	        $self->{ircd}->send_output( { prefix => $full, command => $type, params => [ $target, $args->[1] ] }, $ref->[0] );
	      }
	  } else {
	      push @{ $ref }, [ '407', $target ];
	      next LOOP;
	  }
	}
	my $channel; my $status_msg;
	if ( $targ_type eq 'channel' ) {
	  $channel = $self->_state_chan_name( $target );
	}
	if ( $targ_type eq 'channel_ext' ) {
	  $channel = $self->_state_chan_name( $targs->{target}->[1] );
	  $status_msg = $targs->{target}->[0];
	}
	if ( $channel and $status_msg and !$self->state_user_chan_mode( $nick, $channel ) ) {
	  push @{ $ref }, [ '482', $target ];
	  next LOOP;
	}
	if ( $channel and $self->state_chan_mode_set( $channel, 'n' ) and !$self->state_is_chan_member( $nick, $channel ) ) {
	  push @{ $ref }, [ '404', $channel ];
	  next LOOP;
	}
	if ( $channel and $self->state_chan_mode_set( $channel, 'm' ) and !$self->state_user_chan_mode( $nick, $channel ) ) {
	  push @{ $ref }, [ '404', $channel ];
	  next LOOP;
	}
	if ( $channel and $self->_state_user_banned( $nick, $channel ) and !$self->state_user_chan_mode( $nick, $channel ) ) {
	  push @{ $ref }, [ '404', $channel ];
	  next LOOP;
	}
	if ( $channel ) {
	  my $common = { };
	  my $msg  = { command => $type, params => [ ( $status_msg ? $target : $channel ), $args->[1] ] };
	  foreach my $member ( $self->state_chan_list( $channel, $status_msg ) ) {
		next if $self->_state_user_is_deaf( $member );
		$common->{ $self->_state_user_route( $member ) }++;
	  }
	  delete $common->{ $self->_state_user_route( $nick ) };
	  foreach my $route_id ( keys %{ $common } ) {
		$msg->{prefix} = $nick;
		$msg->{prefix} = $full if $self->_connection_is_client( $route_id );
		unless ( $route_id eq 'spoofed' ) {
	  	  $self->{ircd}->send_output( $msg, $route_id );
		} else {
		  my $tmsg = $type eq 'PRIVMSG' ? 'public' : 'notice';
		  $self->{ircd}->send_event( "daemon_$tmsg", $full, $channel, $args->[1] );
		}
	  }
	  next LOOP;
	}
	my $server = $self->server_name();
	if ( $self->state_nick_exists( $target ) ) {
	  $target = $self->state_user_nick( $target );
	  if ( my $away = $self->_state_user_away_msg( $target ) ) {
	     push @{ $ref }, { prefix => $server, command => '301', params => [ $nick, $target, $away ] };
	  }
	  my $targ_umode = $self->state_user_umode( $target );
	  # Target user has CALLERID on
	  if ( $targ_umode and $targ_umode =~ /[Gg]/ ) {
	     my $targ_rec = $self->{state}->{users}->{ u_irc $target };
	     if ( ( $targ_umode =~ /G/ and ( !$self->state_users_share_chan( $target, $nick ) or !$targ_rec->{accepts}->{ u_irc $nick } ) ) or ( $targ_umode =~ /g/ and !$targ_rec->{accepts}->{ u_irc $nick } ) ) {
		push @{ $ref }, { prefix => $server, command => '716', params => [ $nick, $target, 'is in +g mode (server side ignore)' ] };
		if ( !$targ_rec->{last_caller} or ( time() - $targ_rec->{last_caller} ) >= 60 ) {
		   my ($n,$uh) = split /!/, $self->state_user_full( $nick );
		   $self->{ircd}->send_output( { prefix => $server, command => '718', params => [ $target, "$n\[$uh\]", 'is messaging you, and you are umode +g.'] }, $targ_rec->{route_id} ) unless $targ_rec->{route_id} eq 'spoofed';
		   push @{ $ref }, { prefix => $server, command => '717', params => [ $nick, $target, 'has been informed that you messaged them.' ] };
		}
		$targ_rec->{last_caller} = time();
		next LOOP;
	     }
	  }
	  my $msg = { prefix => $nick, command => $type, params => [ $target, $args->[1] ] };
	  my $route_id = $self->_state_user_route( $target );
	  if ( $route_id eq 'spoofed' ) {
	     $msg->{prefix} = $full;
	     $self->{ircd}->send_event( "daemon_" . lc $type, $full, $target, $args->[1] );
	  } else {
	     $msg->{prefix} = $full if $self->_connection_is_client( $route_id );
	     $self->{ircd}->send_output( $msg, $route_id );
	  }
	  next LOOP;
	}
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_accept {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count or !$args->[0] or $args->[0] eq '*' ) {
	my $record = $self->{state}->{users}->{ u_irc $nick };
	my @list;
	foreach my $accept ( keys %{ $record->{accepts} } ) {
	  unless ( $self->state_nick_exists( $accept ) ) {
	     delete $record->{accepts}->{ $accept };
	     next;
	  }
	  push @list, $self->state_user_nick( $accept );
	}
	push @{ $ref }, { prefix => $server, command => '281', params => [ $nick, join( ' ', @list ) ] } if @list;
	push @{ $ref }, { prefix => $server, command => '282', params => [ $nick, 'End of /ACCEPT list' ] };
	last SWITCH;
    }
  }
  my $record = $self->{state}->{users}->{ u_irc $nick };
  for ( keys %{ $record->{accepts} } ) {
    delete $record->{accepts}->{$_} unless $self->state_nick_exists( $_ );
  }
  OUTER: foreach my $target ( split /,/, $args->[0] ) {
    if ( my ($foo) = $target =~ /^\-(.+)$/ ) {
	my $dfoo = delete $record->{accepts}->{ u_irc $foo };
	unless ( $dfoo ) {
	  push @{ $ref }, { prefix => $server, command => '458', params => [ $nick, $foo, "doesn\'t exist" ] };
	}
	delete $self->{state}->{accepts}->{ u_irc $foo }->{ u_irc $nick };
	delete $self->{state}->{accepts}->{ u_irc $foo } unless keys %{ $self->{state}->{accepts}->{ u_irc $foo } };
	next OUTER;
    }
    unless ( $self->state_nick_exists( $target ) ) {
	push @{ $ref }, [ '401', $target ];
	next OUTER;
    }
    # 457 ERR_ACCEPTEXIST
    if ( $record->{accepts}->{ u_irc $target } ) {
	push @{ $ref }, { prefix => $server, command => '457', params => [ $nick, $self->state_user_nick( $target ), 'already exists' ] };
	next OUTER;
    }
    if ( $record->{umode} and $record->{umode} =~ /G/ and $self->_state_users_share_chan( $nick, $target ) ) {
	push @{ $ref }, { prefix => $server, command => '457', params => [ $nick, $self->state_user_nick( $target ), 'already exists' ] };
	next OUTER;
    }
    $self->{state}->{accepts}->{ u_irc $target }->{ u_irc $nick } = $record->{accepts}->{ u_irc $target } = time();
    my @list = map { $self->state_user_nick( $_ ) } keys %{ $record->{accepts} };
    push @{ $ref }, { prefix => $server, command => '281', params => [ $nick, join( ' ', @list ) ] } if @list;
    push @{ $ref }, { prefix => $server, command => '282', params => [ $nick, 'End of /ACCEPT list' ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_quit {
  my $self = shift;
  my $nick = shift || return;
  my $qmsg = shift || 'Client Quit';
  my $ref = [ ];
  my $full = $self->state_user_full( $nick );

  $nick = u_irc $nick;
  my $record = delete $self->{state}->{peers}->{ uc $self->server_name() }->{users}->{ $nick };
  $self->{ircd}->send_output( { prefix => $record->{nick}, command => 'QUIT', params => [ $qmsg ] }, $self->_state_connected_peers() ) unless $record->{killed};
  push @{ $ref }, { prefix => $full, command => 'QUIT', params => [ $qmsg ] };
  $self->{ircd}->send_event( "daemon_quit", $full, $qmsg );
  # Remove for peoples accept lists
  delete $self->{state}->{users}->{$_}->{accepts}->{ u_irc $nick } for keys %{ $record->{accepts} };
  # Okay, all 'local' users who share a common channel with user.
  my $common = { };
  foreach my $uchan ( keys %{ $record->{chans} } ) {
    delete $self->{state}->{chans}->{ $uchan }->{users}->{ $nick };
    foreach my $user ( $self->state_chan_list( $uchan ) ) {
	next unless $self->_state_is_local_user( $user );
	$common->{ $user } = $self->_state_user_route( $user );
    }
    unless ( scalar keys %{ $self->{state}->{chans}->{ $uchan  }->{users} } ) {
	delete $self->{state}->{chans}->{ $uchan  };
    }
  }
  push( @{ $ref }, $common->{$_} ) for keys %{ $common };
  $self->{state}->{stats}->{ops_online}-- if $record->{umode} =~ /o/;
  $self->{state}->{stats}->{invisible}-- if $record->{umode} =~ /i/;
  delete $self->{state}->{users}->{ $nick } unless $record->{nick_collision};
  delete $self->{state}->{localops}->{ $record->{route_id} };
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_ping {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $args = [ @_ ]; my $count = scalar @{ $args };
  my $ref = [ ];
  SWITCH: {
     if ( !$count ) {
	push @{ $ref }, [ '409' ];
	last SWITCH;
     }
     if ( $count >= 2 and !$self->state_peer_exists( $args->[1] ) ) {
	push @{ $ref }, [ '402', $args->[1] ];
	last SWITCH;
     }
     if ( $count >= 2 and ( uc $args->[1] ne uc $server ) ) {
	my $target = $self->_state_peer_name( $args->[1] );
	$self->{ircd}->send_output( { command => 'PING', params => [ $nick, $target ] }, $self->_state_peer_route( $args->[1] ) );
	last SWITCH;
     }
     push @{ $ref }, { prefix => $server, command => 'PONG', params => [ $server, $args->[0] ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_pong {
  my $self = shift;
  my $nick = shift || return;
  my $server = uc $self->server_name();
  my $args = [ @_ ]; my $count = scalar @{ $args };
  my $ref = [ ];
  SWITCH: {
     if ( !$count ) {
	push @{ $ref }, [ '409' ];
	last SWITCH;
     }
     if ( $count >= 2 and !$self->state_peer_exists( $args->[1] ) ) {
	push @{ $ref }, [ '402', $args->[1] ];
	last SWITCH;
     }
     if ( $count >= 2 and ( uc $args->[1] ne uc $server ) ) {
	my $target = $self->_state_peer_name( $args->[1] );
	$self->{ircd}->send_output( { command => 'PONG', params => [ $nick, $target ] }, $self->_state_peer_route( $args->[1] ) );
	last SWITCH;
     }
     delete $self->{state}->{users}->{ u_irc $nick }->{pinged};
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_pass {
  my $self = shift;
  my $nick = shift || return;
  my $server = uc $self->server_name();
  my $ref = [ [ '462' ] ];
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_user {
  my $self = shift;
  my $nick = shift || return;
  my $server = uc $self->server_name();
  my $ref = [ [ '462' ] ];
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_oper {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    last SWITCH if $self->state_user_is_operator( $nick );
    if ( !$count or $count < 2 ) {
	push @{ $ref }, [ '461', 'OPER' ];
	last SWITCH;
    }
    my $result = $self->_state_o_line( $nick, @{ $args } );
    if ( !$result or $result <= 0 ) {
	push @{ $ref }, [ '491' ];
	last SWITCH;
    }
    $self->{stats}->{ops}++;
    my $record = $self->{state}->{users}->{ u_irc $nick };
    $record->{umode} .= 'o';
    $self->{state}->{stats}->{ops_online}++;
    push @{ $ref }, { prefix => $server, command => '381', params => [ $nick, 'You are now an IRC operator' ] };
    my $reply = { prefix => $nick, command => 'MODE', params => [ $nick, '+o' ] };
    $self->{ircd}->send_output( $reply, $self->_state_connected_peers() );
    $self->{ircd}->send_event( "daemon_umode", $self->state_user_full( $nick ), '+o' );
    my $route_id = $self->_state_user_route( $nick );
    $self->{state}->{localops}->{ $route_id } = time();
    $self->{ircd}->antiflood( $route_id, 0 );
    push @{ $ref }, $reply;
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_die {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ];
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     $self->{ircd}->send_event( "daemon_die", $nick );
     $self->{ircd}->shutdown();
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_rehash {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ];
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     $self->{ircd}->send_event( "daemon_rehash", $nick );
     push @{ $ref }, { prefix => $server, command => '383', params => [ $nick, 'ircd.conf', 'Rehashing' ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_locops {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count ) {
	push @{ $ref }, [ '461', 'LOCOPS' ];
	last SWITCH;
     }
     my $full = $self->state_user_full( $nick );
     $self->{ircd}->send_output( { prefix => $full, command => 'WALLOPS', params => [ 'LOCOPS - ' . $args->[0] ] }, keys %{ $self->{state}->{locops} } );
     $self->{ircd}->send_event( "daemon_locops", $full, $args->[0] );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_wallops {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count ) {
	push @{ $ref }, [ '461', 'WALLOPS' ];
	last SWITCH;
     }
     my $full = $self->state_user_full( $nick );
     $self->{ircd}->send_output( { prefix => $nick, command => 'WALLOPS', params => [ $args->[0] ] }, $self->_state_connected_peers() );
     $self->{ircd}->send_output( { prefix => $full, command => 'WALLOPS', params => [ 'OPERWALL - ' . $args->[0] ] }, keys %{ $self->{state}->{operwall} } );
     $self->{ircd}->send_event( "daemon_operwall", $full, $args->[0] );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_operwall {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count ) {
	push @{ $ref }, [ '461', 'OPERWALL' ];
	last SWITCH;
     }
     my $full = $self->state_user_full( $nick );
     $self->{ircd}->send_output( { prefix => $nick, command => 'WALLOPS', params => [ $args->[0] ] }, $self->_state_connected_peers() );
     $self->{ircd}->send_output( { prefix => $full, command => 'WALLOPS', params => [ 'OPERWALL - ' . $args->[0] ] }, keys %{ $self->{state}->{operwall} } );
     $self->{ircd}->send_event( "daemon_operwall", $full, $args->[0] );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_connect {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count ) {
	push @{ $ref }, [ '461', 'CONNECT' ];
	last SWITCH;
     }
     if ( $count >= 3 and !$self->state_peer_exists( $args->[2] ) ) {
	push @{ $ref }, [ '402', $args->[2] ];
	last SWITCH;
     }
     if ( $count >= 3 and ( uc $server ne uc $args->[2] ) ) {
	$args->[2] = $self->_state_peer_name( $args->[2] );
	$self->{ircd}->send_output( { prefix => $nick, command => 'CONNECT', params => $args }, $self->_state_peer_route( $args->[2] ) );
	last SWITCH;
     }
     if ( !$self->{config}->{peers}->{ uc $args->[0] } or $self->{config}->{peers}->{ uc $args->[0] }->{type} ne 'r' ) {
	push @{ $ref }, { command => 'NOTICE', params => [ $nick, "Connect: Host $args->[0] is not listed in ircd.conf" ] };
	last SWITCH;
     }
     if ( my $peer_name = $self->_state_peer_name( $args->[0] ) ) {
	push @{ $ref }, { command => 'NOTICE', params => [ $nick, "Connect: Server $args->[0] already exists from $peer_name." ] };
	last SWITCH;
     }
     my $connector = $self->{config}->{peers}->{ uc $args->[0] };
     my $name = $connector->{name};
     my $rport = $args->[1] || $connector->{rport};
     my $raddr = $connector->{raddress};
     $self->{ircd}->add_connector( remoteaddress => $raddr, remoteport => $rport, name => $name );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_squit {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count ) {
	push @{ $ref }, [ '461', 'SQUIT' ];
	last SWITCH;
     }
     if ( !$self->state_peer_exists( $args->[0] ) or ( uc $server eq uc $args->[0] ) ) {
	push @{ $ref }, [ '402', $args->[0] ];
	last SWITCH;
     }
     my $peer = uc $args->[0];
     my $reason = $args->[1] || 'No Reason';
     $args->[0] = $self->_state_peer_name( $peer );
     $args->[1] = $reason;
     unless ( scalar grep { $_ eq $peer } keys %{ $self->{state}->{peers}->{ uc $server }->{peers} } ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'SQUIT', params => $args }, $self->_state_peer_route( $args->[0] ) );
	last SWITCH;
     }
     my $conn_id = $self->_state_peer_route( $peer );
     $self->{ircd}->disconnect( $conn_id, $reason );
     $self->{ircd}->send_output( { command => 'ERROR', params => [ join ' ', 'Closing Link:', $self->_client_ip( $conn_id ), $args->[0], "($nick)" ] }, $conn_id );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_rkline {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  # RKLINE [time] <mask> [ON <server>] :[reason]
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count or $count < 1 ) {
	push @{ $ref }, [ '461', 'RKLINE' ];
	last SWITCH;
     }
     my $duration = 0;
     if ( $args->[0] =~ /^\d+$/ ) {
	$duration = shift @{ $args };
	$duration = 14400 if $duration > 14400;
     }
     my $mask = shift @{ $args };
     unless ( $mask ) {
	push @{ $ref }, [ '461', 'RKLINE' ];
	last SWITCH;
     }
     my ($user,$host) = split /\@/, $mask;
     unless ( $user and $host ) {
	last SWITCH;
     }
     my $full = $self->state_user_full( $nick );
     my $us = 0;
     my $ucserver = uc $server;
     if ( $args->[0] and uc $args->[0] eq 'ON' and scalar @{ $args } < 2 ) {
	push @{ $ref }, [ '461', 'RKLINE' ];
	last SWITCH;
     }
     my ($target,$reason);
     if ( $args->[0] and uc $args->[0] eq 'ON' ) {
       $target = shift @{ $args };
       $reason = shift @{ $args } || 'No Reason';
       my %targets;
       foreach my $peer ( keys %{ $self->{state}->{peers} } ) {
	 if ( matches_mask( $target, $peer ) ) {
	   if ( $ucserver eq $peer ) {
		$us = 1;
	   } else {
		$targets{ $self->_state_peer_route( $peer ) }++;
	   }
	 }
       }
       $self->{ircd}->send_output( { prefix => $nick, command => 'RKLINE', params => [ $target, $duration, $user, $host, $reason ], colonify => 0 }, grep { $self->_state_peer_capab( $_, 'KLN' ) } keys %targets );
     } else {
	$us = 1;
     }
     if ( $us ) {
	$target = $server unless $target;
	unless ( $reason ) {
	  $reason = pop @{ $args } || 'No Reason';
	}
     	$self->{ircd}->send_event( "daemon_rkline", $full, $target, $duration, $user, $host, $reason );
	push @{ $self->{state}->{rklines} }, { setby => $full, setat => time(), target => $target, duration => $duration, user => $user, host => $host, reason => $reason };
	$self->_terminate_conn_error( $_, 'K-Lined' ) for $self->_state_local_users_match_rkline( $user, $host );
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_kline {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  # KLINE [time] <nick|user@host> [ ON <server> ] :[reason]
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count or $count < 1 ) {
	push @{ $ref }, [ '461', 'KLINE' ];
	last SWITCH;
     }
     my $duration = 0;
     if ( $args->[0] =~ /^\d+$/ ) {
	$duration = shift @{ $args };
	$duration = 14400 if $duration > 14400;
     }
     my $mask = shift @{ $args };
     unless ( $mask ) {
	push @{ $ref }, [ '461', 'KLINE' ];
	last SWITCH;
     }
     my ($user,$host);
     if ( $mask !~ /\@/ ) {
	if ( my $rogue = $self->_state_user_full( $mask ) ) {
	  ($user,$host) = ( split /[!\@]/, $rogue )[1..2]
	} else {
	  push @{ $ref }, [ '401', $mask ];
	  last SWITCH;
	}
     } else {
	($user,$host) = split /\@/, $mask;
     }
     my $full = $self->state_user_full( $nick );
     my $us = 0;
     my $ucserver = uc $server;
     if ( $args->[0] and uc $args->[0] eq 'ON' and scalar @{ $args } < 2 ) {
	push @{ $ref }, [ '461', 'KLINE' ];
	last SWITCH;
     }
     my ($target,$reason);
     if ( $args->[0] and uc $args->[0] eq 'ON' ) {
       $target = shift @{ $args };
       $reason = shift @{ $args } || 'No Reason';
       my %targets;
       foreach my $peer ( keys %{ $self->{state}->{peers} } ) {
	 if ( matches_mask( $target, $peer ) ) {
	   if ( $ucserver eq $peer ) {
		$us = 1;
	   } else {
		$targets{ $self->_state_peer_route( $peer ) }++;
	   }
	 }
       }
       $self->{ircd}->send_output( { prefix => $nick, command => 'KLINE', params => [ $target, $duration, $user, $host, $reason ], colonify => 0 }, grep { $self->_state_peer_capab( $_, 'KLN' ) } keys %targets );
     } else {
	$us = 1;
     }
     if ( $us ) {
	$target = $server unless $target;
	unless ( $reason ) {
	  $reason = pop @{ $args } || 'No Reason';
	}
     	$self->{ircd}->send_event( "daemon_kline", $full, $target, $duration, $user, $host, $reason );
	push @{ $self->{state}->{klines} }, { setby => $full, setat => time(), target => $target, duration => $duration, user => $user, host => $host, reason => $reason };
	$self->_terminate_conn_error( $_, 'K-Lined' ) for $self->_state_local_users_match_gline( $user, $host );
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_unkline {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  # UNKLINE <user@host> [ ON <target_mask> ]
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count or $count < 1 ) {
	push @{ $ref }, [ '461', 'UNKLINE' ];
	last SWITCH;
     }
     my ($user,$host);
     if ( $args->[0] !~ /\@/ ) {
	if ( my $rogue = $self->state_user_full( $args->[0] ) ) {
	  ($user,$host) = ( split /[!\@]/, $rogue )[1..2]
	} else {
	  push @{ $ref }, [ '401', $args->[0] ];
	  last SWITCH;
	}
     } else {
	($user,$host) = split /\@/, $args->[0];
     }
     my $full = $self->state_user_full( $nick );
     my $us = 0;
     my $ucserver = uc $server;
     if ( $count > 1 and uc $args->[2] eq 'ON' and $count < 3 ) {
	push @{ $ref }, [ '461', 'UNKLINE' ];
	last SWITCH;
     }
     if ( $count > 1 and $args->[2] and uc $args->[2] eq 'ON' ) {
       my $target = $args->[2];
       my %targets;
       foreach my $peer ( keys %{ $self->{state}->{peers} } ) {
	 if ( matches_mask( $target, $peer ) ) {
	   if ( $ucserver eq $peer ) {
		$us = 1;
	   } else {
		$targets{ $self->_state_peer_route( $peer ) }++;
	   }
	 }
       }
       $self->{ircd}->send_output( { prefix => $nick, command => 'UNKLINE', params => [ $target, $user, $host ], colonify => 0 }, grep { $self->_state_peer_capab( $_, 'UNKLN' ) } keys %targets );
     } else {
	$us = 1;
     }
     if ( $us ) {
	my $target = $args->[3] || $server;
     	$self->{ircd}->send_event( "daemon_unkline", $full, $target, $user, $host );
	my $i = 0;
	for ( @{ $self->{state}->{klines} } ) {
	  splice ( @{ $self->{state}->{klines} }, $i, 1), last
		if $_->{user} eq $user and $_->{host} eq $host;
	  ++$i;
	}
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_gline {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  # :klanker GLINE * meep.com :Fuckers
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count or $count < 2 ) {
	push @{ $ref }, [ '461', 'GLINE' ];
	last SWITCH;
     }
     if ( $args->[0] !~ /\@/ and !$self->state_nick_exists( $args->[0] ) ) {
	push @{ $ref }, [ '401', $args->[0] ];
	last SWITCH;
     }
     my ($user_part,$host_part);
     if ( $args->[0] =~ /\@/ ) {
	($user_part,$host_part) = ( split /[!@]/, $self->state_user_full( $args->[0] ) )[1..2];
     } else {
	($user_part,$host_part) = split /\@/, $args->[0];
     }
     my $time = time();
     my $reason = join ' ', $args->[1], strftime("(%c)", localtime($time) );
     my $full = $self->state_user_full( $nick );
     push @{ $self->{state}->{glines} }, { setby => $full, setat => time(), user => $user_part, host => $host_part, reason => $reason };
     $self->{ircd}->send_output( { prefix => $nick, command => 'GLINE', params => [ $user_part, $host_part, $reason ], colonify => 0 }, grep { $self->_state_peer_capab( $_, 'GLN' ) } $self->_state_connected_peers() );
     $self->{ircd}->send_event( "daemon_gline", $full, $user_part, $host_part, $reason );
     $self->_terminate_conn_error( $_, 'G-Lined' ) for $self->_state_local_users_match_gline( $user_part, $host_part );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_kill {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     if ( !$self->state_user_is_operator( $nick ) ) {
	push @{ $ref }, [ '481' ];
	last SWITCH;
     }
     if ( !$count ) {
	push @{ $ref }, [ '461', 'KILL' ];
	last SWITCH;
     }
     if ( $self->state_peer_exists( $args->[0] ) ) {
	push @{ $ref }, [ '483' ];
	last SWITCH;
     }
     if ( !$self->state_nick_exists( $args->[0] ) ) {
	push @{ $ref }, [ '401', $args->[0] ];
	last SWITCH;
     }
     my $target = $self->state_user_nick( $args->[0] );
     my $comment = $args->[1] || '<No reason given>';
     if ( $self->_state_is_local_user( $target ) ) {
	my $route_id = $self->_state_user_route( $target );
        $self->{ircd}->send_output( { prefix => $nick, command => 'KILL', params => [ $target, join('!', $server, $nick ) . " ($comment)" ] }, $self->_state_connected_peers() );
	$self->{ircd}->send_output( { prefix => $self->state_user_full( $nick ), command => 'KILL', params => [ $target, $comment ] }, $route_id );
	if ( $route_id eq 'spoofed' ) {
	  $self->call( 'del_spoofed_nick', $target, "Killed ($comment)" );
	} else {
	  $self->{state}->{conns}->{ $route_id }->{killed} = 1;
	  $self->_terminate_conn_error( $route_id, "Killed ($comment)" );
	}
     } else {
	$self->{state}->{users}->{ u_irc $target }->{killed} = 1;
        $self->{ircd}->send_output( { prefix => $nick, command => 'KILL', params => [ $target, join('!', $server, $nick ) . " ($comment)" ] }, $self->_state_connected_peers() );
	$self->{ircd}->send_output( @{ $self->_daemon_peer_quit( $target, "Killed ($nick ($comment))" ) } );
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_nick {
  my $self = shift;
  my $nick = shift || return;
  my $new = shift;
  my $server = uc $self->server_name();
  my $ref = [ ];
  SWITCH: {
    if ( !$new ) {
	push @{ $ref }, [ '431' ];
	last SWITCH;
    }
    my $nicklen = $self->server_config('NICKLEN');
    $new = substr($new,0,$nicklen) if length($new) > $nicklen;
    if ( $nick eq $new ) {
	last SWITCH;
    }
    if ( !validate_nick_name( $new ) ) {
	push @{ $ref }, [ '432', $new ];
	last SWITCH;
    }
    my $unick = u_irc $nick;
    my $unew = u_irc $new;
    if ( $self->state_nick_exists( $new ) and $unick ne $unew ) {
	push @{ $ref }, [ '433', $new ];
	last SWITCH;
    }
    my $full = $self->state_user_full( $nick );
    my $record = $self->{state}->{users}->{ $unick };
    my $common = { $nick => $record->{route_id} };
    foreach my $chan ( keys %{ $record->{chans} } ) {
      foreach my $user ( $self->state_chan_list( $chan ) ) {
	next unless $self->_state_is_local_user( $user );
	$common->{ $user } = $self->_state_user_route( $user );
      }
    }
    if ( $unick eq $unew ) {
	$record->{nick} = $new;
	$record->{ts} = time();
    } else {
	$record->{nick} = $new;
	$record->{ts} = time();
  	# Remove from peoples accept lists
  	delete $self->{state}->{users}->{$_}->{accepts}->{ $unick } for keys %{ $record->{accepts} };
	delete $record->{accepts};
	delete $self->{state}->{users}->{ $unick };
	$self->{state}->{users}->{ $unew } = $record;
	delete $self->{state}->{peers}->{ $server }->{users}->{ $unick };
	$self->{state}->{peers}->{ $server }->{users}->{ $unew } = $record;
	foreach my $chan ( keys %{ $record->{chans} } ) {
	   $self->{state}->{chans}->{ $chan }->{users}->{ $unew } = delete $self->{state}->{chans}->{ $chan }->{users}->{ $unick };
	}
    }
    my @peers = $self->_state_connected_peers();
    $self->{ircd}->send_output( { prefix => $nick, command => 'NICK', params => [ $new, $record->{ts} ] }, @peers );
    $self->{ircd}->send_output( { prefix => $full, command => 'NICK', params => [ $new ] }, map{ $common->{$_} } keys %{ $common } );
    $self->{ircd}->send_event( "daemon_nick", $full, $new );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_away {
  my $self = shift;
  my $nick = shift || return;
  my $msg = shift;
  my $server = $self->server_name();
  my $ref = [ ];
  SWITCH: {
     my $record = $self->{state}->{users}->{ u_irc $nick };
     if ( !$msg ) {
	delete $record->{away};
        $self->{ircd}->send_output( { prefix => $nick, command => 'AWAY', colonify => 0 }, $self->_state_connected_peers() );
	push @{ $ref }, { prefix => $server, command => '305', params => [ 'You are no longer marked as being away' ] };
	last SWITCH;
     }
     $record->{away} = $msg;
     $self->{ircd}->send_output( { prefix => $nick, command => 'AWAY', params => [ $msg ], colonify => 0 }, $self->_state_connected_peers() );
     push @{ $ref }, { prefix => $server, command => '306', params => [ 'You have been marked as being away' ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

# Pseudo cmd for ISupport 005 numerics
sub _daemon_cmd_isupport {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ];
  push @{ $ref }, { prefix => $server, command => '005', params => [ $nick, join(' ', map { ( defined ( $self->{config}->{isupport}->{$_} ) ? join('=', $_, $self->{config}->{isupport}->{$_} ) : $_ ) } qw(CALLERID EXCEPTS INVEX MAXCHANNELS MAXBANS MAXTARGETS NICKLEN TOPICLEN KICKLEN) ), 'are supported by this server' ] };
  push @{ $ref }, { prefix => $server, command => '005', params => [ $nick, join(' ', map { ( defined ( $self->{config}->{isupport}->{$_} ) ? join('=', $_, $self->{config}->{isupport}->{$_} ) : $_ ) } qw(CHANTYPES PREFIX CHANMODES NETWORK CASEMAPPING DEAF) ), 'are supported by this server' ] };
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_info {
  my $self = shift;
  my $nick = shift || return;
  my $target = shift;
  my $server = $self->server_name();
  my $ref = [ ];
  SWITCH: {
    if ( $target and !$self->state_peer_exists( $target ) ) {
	push @{ $ref }, [ '402', $target ];
	last SWITCH;
    }
    if ( $target and ( uc $server ne uc $target ) ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'INFO', params => [ $self->_state_peer_name( $target ) ] }, $self->_state_peer_route( $target ) );
	last SWITCH;
    }
    push( @{ $ref }, { prefix => $server, command => '371', params => [ $nick, $_ ] } ) for @{ $self->server_config('Info') };
    push( @{ $ref }, { prefix => $server, command => '374', params => [ $nick, 'End of /INFO list.' ] } );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_version {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ];
  my $target = shift;
  SWITCH: {
    if ( $target and !$self->state_peer_exists( $target ) ) {
	push @{ $ref }, [ '402', $target ];
	last SWITCH;
    }
    if ( $target and ( uc $server ne uc $target ) ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'VERSION', params => [ $self->_state_peer_name( $target ) ] }, $self->_state_peer_route( $target ) );
	last SWITCH;
    }
    push @{ $ref }, { prefix => $server, command => '351', params => [ $nick, $self->server_version(), $server, 'eGHIMZ TS5ow' ] };
    push @{ $ref }, $_ for @{ $self->_daemon_cmd_isupport( $nick ) };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_admin {
  my $self = shift;
  my $nick = shift || return;
  my $target = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $admin = $self->server_config('Admin');
  SWITCH: {
    if ( $target and !$self->state_peer_exists( $target ) ) {
	push @{ $ref }, [ '402', $target ];
	last SWITCH;
    }
    if ( $target and ( uc $server ne uc $target ) ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'ADMIN', params => [ $self->_state_peer_name( $target ) ] }, $self->_state_peer_route( $target ) );
	last SWITCH;
    }
    push @{ $ref }, { prefix => $server, command => '256', params => [ $nick, $server, 'Administrative Info' ] };
    push @{ $ref }, { prefix => $server, command => '257', params => [ $nick, $admin->[0] ] };
    push @{ $ref }, { prefix => $server, command => '258', params => [ $nick, $admin->[1] ] };
    push @{ $ref }, { prefix => $server, command => '259', params => [ $nick, $admin->[2] ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_summon {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ];
  push @{ $ref }, '445';
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_time {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $target = shift;
  my $ref = [ ];
  SWITCH: {
    if ( $target and !$self->state_peer_exists( $target ) ) {
	push @{ $ref }, [ '402', $target ];
	last SWITCH;
    }
    if ( $target and ( uc $server ne uc $target ) ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'TIME', params => [ $self->_state_peer_name( $target ) ] }, $self->_state_peer_route( $target ) );
	last SWITCH;
    }
    push @{ $ref }, { prefix => $server, command => '391', params => [ $nick, $server, strftime( "%A %B %m %Y -- %T %z", localtime ) ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_users {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ];
  my $global = scalar keys %{ $self->{state}->{users} };
  my $local = scalar keys %{ $self->{state}->{peers}->{ uc $server }->{users} };
  push @{ $ref }, { prefix => $server, command => '265', params => [ $nick, "Current local  users: $local  Max: " . $self->{state}->{stats}->{maxlocal} ] };
  push @{ $ref }, { prefix => $server, command => '266', params => [ $nick, "Current global users: $global  Max: " . $self->{state}->{stats}->{maxglobal} ] };
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_lusers {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ];
  my $invisible = $self->{state}->{stats}->{invisible};
  my $users = scalar ( keys %{ $self->{state}->{users} } ) - $invisible;
  my $servers = scalar keys %{ $self->{state}->{peers} };
  my $chans = scalar keys %{ $self->{state}->{chans} };
  my $local = scalar keys %{ $self->{state}->{peers}->{ uc $server }->{users} };
  my $peers = scalar keys %{ $self->{state}->{peers}->{ uc $server }->{peers} };
  my $totalconns = $self->{state}->{stats}->{conns_cumlative};
  my $mlocal = $self->{state}->{stats}->{maxlocal};
  my $conns = $self->{state}->{stats}->{maxconns};
  push( @{ $ref }, { prefix => $server, command => '251', params =>[ $nick, "There are $users users and $invisible invisible on $servers servers" ] } );
  $servers--;
  push @{ $ref }, { prefix => $server, command => '252', params => [ $nick, $self->{state}->{stats}->{ops_online}, "IRC Operators online" ] } if $self->{state}->{stats}->{ops_online};
  push( @{ $ref }, { prefix => $server, command => '254', params =>[ $nick, $chans, "channels formed" ] } ) if $chans;
  push( @{ $ref }, { prefix => $server, command => '255', params =>[ $nick, "I have $local clients and $peers servers" ] } );
  push @{ $ref }, $_ for $self->_daemon_cmd_users( $nick );
  push @{ $ref }, { prefix => $server, command => '250', params => [ $nick, "Highest connection count: $conns ($mlocal clients) ($totalconns connections received)" ] };
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_motd {
  my $self = shift;
  my $nick = shift || return;
  my $target = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $motd = $self->server_config('MOTD');
  SWITCH: {
    if ( $target and !$self->state_peer_exists( $target ) ) {
	push @{ $ref }, [ '402', $target ];
	last SWITCH;
    }
    if ( $target and ( uc $server ne uc $target ) ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'MOTD', params => [ $self->_state_peer_name( $target ) ] }, $self->_state_peer_route( $target ) );
	last SWITCH;
    }
    if ( $motd and ref $motd eq 'ARRAY' ) {
      push @{ $ref }, { prefix => $server, command => '375', params => [ $nick, "- $server Message of the day - " ] };
      push @{ $ref }, { prefix => $server, command => '372', params => [ $nick, "- $_" ] } for @{ $motd };
      push @{ $ref }, { prefix => $server, command => '376', params => [ $nick, "End of MOTD command" ] };
    } else {
      push @{ $ref }, '422';
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_stats {
  my $self = shift;
  my $nick = shift || return;
  my $char = shift;
  my $target = shift;
  my $server = $self->server_name();
  my $ref = [ ];
  SWITCH: {
    unless ( $char ) {
	push @{ $ref }, [ '461', 'STATS' ];
	last SWITCH;
    }
    $char = substr $char, 0, 1;
    unless ( $char =~ /[ump]/ ) {
	push @{ $ref }, { prefix => $server, command => '263', params => [ $nick, 'Server load is temporarily too heavy. Please wait a while and try again.' ] };
	last SWITCH;
    }
    if ( $target and !$self->state_peer_exists( $target ) ) {
	push @{ $ref }, [ '402', $target ];
	last SWITCH;
    }
    if ( $target and ( uc $server ne uc $target ) ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'STATS', params => [ $char, $self->_state_peer_name( $target ) ] }, $self->_state_peer_route( $target ) );
	last SWITCH;
    }
    SWITCH2: {
	if ( $char eq 'u' ) {
	  my $uptime = time() - $self->server_config('created');
	  my $days = int $uptime / 86400;
	  my $remain = $uptime % 86400;
      	  my $hours = int $remain / 3600;
      	  $remain %= 3600;
      	  my $mins = int $remain / 60;
      	  $remain %= 60;
	  push @{ $ref }, { prefix => $server, command => '242', params => [ $nick, sprintf("Server Up %d days, %2.2d:%2.2d:%2.2d",$days,$hours,$mins,$remain) ] };
	  my $totalconns = $self->{state}->{stats}->{conns_cumlative};
	  my $local = $self->{state}->{stats}->{maxlocal};
	  my $conns = $self->{state}->{stats}->{maxconns};
	  push @{ $ref }, { prefix => $server, command => '250', params => [ $nick, "Highest connection count: $conns ($local clients) ($totalconns connections received)" ] };
	  last SWITCH2;
	}
	if ( $char eq 'm' ) {
	  my $cmds = $self->{state}->{stats}->{cmds};
	  push @{ $ref }, { prefix => $server, command => '212', params => [ $nick, $_, $cmds->{$_}->{local}, $cmds->{$_}->{bytes}, $cmds->{$_}->{remote} ] } for sort keys %{ $cmds };
	  last SWITCH2;
	}
	if ( $char eq 'p' ) {
	  my @ops = map { $self->_client_nickname( $_ ) } keys %{ $self->{state}->{localops} };
	  foreach my $op ( sort @ops ) {
	    my $record = $self->{state}->{users}->{ u_irc $op };
	    push @{ $ref }, { prefix => $server, command => '249', params => [ $nick, sprintf("[O] %s (%s\@%s) Idle: %u", $record->{nick}, $record->{auth}->{ident}, $record->{auth}->{hostname}, time() - $record->{idle_time} ) ] };
	  }
	  push @{ $ref }, { prefix => $server, command => '249', params => [ $nick, scalar @ops . " OPER(s)" ] };
	  last SWITCH2;
	}
    }
    push @{ $ref }, { prefix => $server, command => '219', params => [ $nick, $char, 'End of /STATS report' ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_userhost {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $str = '';

  foreach my $query ( @_ ) {
    my ($proper,$userhost) = split /!/, $self->state_user_full( $query );
    $str = join(' ', $str, $proper . ( $self->state_user_is_operator($proper) ? '*' : '' ) . '=' . ( $self->_state_user_away($proper) ? '-' : '+' ) . $userhost ) if $proper and $userhost;
  }

  push @{ $ref }, { prefix => $server, command => '302', params => [ $nick, ( $str ? $str : ':' ) ] };

  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_ison {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count ) {
	push @{ $ref }, [ '461', 'ISON' ];
	last SWITCH;
    }
    my $string = '';
    $string = join ' ', map { $self->{state}->{users}->{ u_irc $_ }->{nick} } grep { $self->state_nick_exists($_) } @{ $args };
    push @{ $ref }, { prefix => $server, command => '303', params => [ $nick, ( $string =~ /\s+/ ? $string : ":$string" ) ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_list {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    my @chans;
    if ( !$count ) {
	@chans = map { $self->_state_chan_name($_) } keys %{ $self->{state}->{chans} };
    }
    my $last = pop @{ $args };
    if ( $count and $last !~ /^(\x23|\x26)/ and !$self->state_peer_exists( $last ) ) {
        push @{ $ref }, [ '401', $last ];
        last SWITCH;
    }
    if ( $count and $last !~ /^(\x23|\x26)/ and ( uc $last ne uc $server ) ) {
        $self->{ircd}->send_output( { prefix => $self->state_user_full( $nick ), command => 'LIST', params => [ @{ $args }, $self->_state_peer_name( $last ) ] }, $self->_state_peer_route( $last ) );
        last SWITCH;
    }
    if ( $count and $last !~ /^(\x23|\x26)/ and scalar @{ $args } == 0 ) {
	@chans = map { $self->_state_chan_name($_) } keys %{ $self->{state}->{chans} };
    }
    if ( $count and $last !~ /^(\x23|\x26)/ and scalar @{ $args } == 1 ) {
        $last = pop @{ $args };
    }
    if ( $count and $last =~ /^(\x23|\x26)/ ) {
	@chans = split /,/, $last;
    }
    push @{ $ref }, { prefix => $server, command => '321', params => [ $nick, 'Channel', 'Users  Name' ] };
    my $count = 0;
    INNER: foreach my $chan (@chans) {
	unless ( validate_chan_name( $chan ) and $self->state_chan_exists( $chan ) ) {
	  unless ( $count ) {
		push @{ $ref }, [ '401', $chan ];
	  	last INNER;
	  }
	  $count++;
	  next INNER;
	}
	$count++;
	next INNER if $self->state_chan_mode_set( $chan, 'p' ) or $self->state_chan_mode_set( $chan, 's' ) and !$self->state_is_chan_member( $nick, $chan );
	my $record = $self->{state}->{chans}->{ u_irc $chan };
	push @{ $ref }, { prefix => $server, command => '322', params => [ $nick, $record->{name}, scalar keys %{ $record->{users} }, ( defined $record->{topic} ? $record->{topic}->[0] : '' ) ] };
    }
    push @{ $ref }, { prefix => $server, command => '323', params => [ $nick, 'End of /LIST' ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_names {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    my @chans; my $query;
    if ( !$count ) {
	@chans = $self->state_user_chans( $nick );
	$query = '*';
    }
    my $last = pop @{ $args };
    if ( $count and $last !~ /^(\x23|\x26)/ and !$self->state_peer_exists( $last ) ) {
	push @{ $ref }, [ '401', $last ];
	last SWITCH;
    }
    if ( $count and $last !~ /^(\x23|\x26)/ and ( uc $last ne uc $server ) ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'NAMES', params => [ @{ $args }, $self->_state_peer_name( $last ) ] }, $self->_state_peer_route( $last ) );
	last SWITCH;
    }
    if ( $count and $last !~ /^(\x23|\x26)/ and scalar @{ $args } == 0 ) {
	@chans = $self->state_user_chans( $nick );
	$query = '*';
    }
    if ( $count and $last !~ /^(\x23|\x26)/ and scalar @{ $args } == 1 ) {
	$last = pop @{ $args };
    }
    if ( $count and $last =~ /^(\x23|\x26)/ ) {
	my ($chan) = grep { $_ &&
			    $self->state_chan_exists( $_ ) &&
			    $self->state_is_chan_member( $nick, $_ )
			  } split /,/, $last;
	@chans = ();
	if ( $chan ) {
	  push @chans, $chan;
	  $query = $self->_state_chan_name( $chan );
	} else {
	  $query = '*';
	}
    }
    foreach my $chan ( @chans ) {
	my $record = $self->{state}->{chans}->{ u_irc $chan };
	my $type = '=';
	$type = '@' if $record->{mode} =~ /s/;
	$type = '*' if $record->{mode} =~ /p/;
	my $length = length($server) + 3 + length($chan) + length($nick) + 7;
	my $buffer = '';
	foreach my $name ( sort $self->state_chan_list_prefixed( $record->{name} ) ) {
	  if ( length( join ' ', $buffer, $name ) + $length > 510 ) {
	    push @{ $ref }, { prefix => $server, command => '353', params => [ $nick, $type, $record->{name}, $buffer ] };
	    $buffer = $name;
	    next;
	  }
	  if ( $buffer ) {
	    $buffer = join ' ', $buffer, $name;
	  } else {
	    $buffer = $name;
	  }
	}
	push @{ $ref }, { prefix => $server, command => '353', params => [ $nick, $type, $record->{name}, $buffer ] };
    }
    push @{ $ref }, { prefix => $server, command => '366', params => [ $nick, $query, 'End of NAMES list' ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_whois {
  my $self = shift;
  my $nick = shift || return;
  my ($first,$second) = @_;
  my $server = $self->server_name();
  my $ref = [ ];
  SWITCH: {
    if ( !$first and !$second ) {
	push @{ $ref }, [ '431' ];
	last SWITCH;
    }
    if ( !$second and $first ) {
	$second = ( split /,/, $first )[0];
	$first = $server;
    }
    if ( $first and $second ) {
	$second = ( split /,/, $second )[0];
    }
    if ( u_irc( $first ) eq u_irc( $second ) and $self->state_nick_exists( $second ) ) {
	$first = $self->state_user_server( $second );
    }
    my $query;
    my $target;
    $query = $first unless $second;
    $query = $second if $second;
    $target = $first if $second and uc( $first ) ne uc( $server );
    if ( $target and !$self->state_peer_exists( $target ) ) {
	push @{ $ref }, [ '402', $target ];
	last SWITCH;
    }
    if ( $target ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'WHOIS', params => [ $self->_state_peer_name( $target ), $second ] }, $self->_state_peer_route( $target ) );
	last SWITCH;
    }
    # Okay we got here *phew*
    if ( !$self->state_nick_exists( $query ) ) {
	push @{ $ref }, [ '401', $query ];
    } else {
	my $record = $self->{state}->{users}->{ u_irc $query };
	push @{ $ref }, { prefix => $server, command => '311', params => [ $nick, $record->{nick}, $record->{auth}->{ident}, $record->{auth}->{hostname}, '*', $record->{ircname} ] };
        my @chans;
	LOOP: foreach my $chan ( keys %{ $record->{chans} } ) {
	  next LOOP if $self->{state}->{chans}->{ $chan }->{mode} =~ /[ps]/ and !$self->state_is_chan_member( $nick, $chan );
	  my $prefix = '';
	  $prefix .= '@' if $record->{chans}->{ $chan } =~ /o/;
	  $prefix .= '%' if $record->{chans}->{ $chan } =~ /h/;
	  $prefix .= '+' if $record->{chans}->{ $chan } =~ /v/;
	  push @chans, $prefix . $self->{state}->{chans}->{ $chan }->{name};
        }
	if ( @chans ) {
	  my $buffer = '';
	  my $length = length( $server ) + 3 + length( $nick ) + length( $record->{nick} ) + 7;
	  LOOP2: foreach my $chan ( @chans ) {
	    if ( length( join ' ', $buffer, $chan ) + $length > 510 ) {
	  	push @{ $ref }, { prefix => $server, command => '319', params => [ $nick, $record->{nick}, $buffer ] };
		$buffer = $chan;
		next LOOP2;
	    }
	    if ( $buffer ) {
		$buffer = join ' ', $buffer, $chan;
	    } else {
		$buffer = $chan;
	    }
	  }
	  push @{ $ref }, { prefix => $server, command => '319', params => [ $nick, $record->{nick}, $buffer ] };
	}
	push @{ $ref }, { prefix => $server, command => '312', params => [ $nick, $record->{nick}, $record->{server}, $self->_state_peer_desc( $record->{server} ) ] };
	push @{ $ref }, { prefix => $server, command => '301', params => [ $nick, $record->{nick}, $record->{away} ] } if $record->{type} eq 'c' and $record->{away};
	push @{ $ref }, { prefix => $server, command => '313', params => [ $nick, $record->{nick}, 'is an IRC Operator' ] } if $record->{umode} and $record->{umode} =~ /o/;
	push @{ $ref }, { prefix => $server, command => '338', params => [ $nick, $record->{nick}, $record->{socket}->[0], 'actually using host' ] } if $record->{type} eq 'c' and ( $self->server_config('whoisactually') or $self->state_user_is_operator( $nick ) );
	push @{ $ref }, { prefix => $server, command => '317', params => [ $nick, $record->{nick}, ( time() - $record->{idle_time} ), $record->{conn_time}, 'seconds idle, signon time' ] } if $record->{type} eq 'c';
    }
    push @{ $ref }, { prefix => $server, command => '318', params => [ $nick, $query, 'End of /WHOIS list.' ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_who {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my ($who,$op_only) = splice @_, 0, 2;
  my $ref = [ ];
  my $orig = $who;
  SWITCH: {
    if ( !$who ) {
	push @{ $ref }, [ '461', 'WHO' ];
	last SWITCH;
    }
    if ( $self->state_chan_exists( $who ) and $self->state_is_chan_member( $nick, $who ) ) {
	my $record = $self->{state}->{chans}->{ u_irc $who };
	$who = $record->{name};
	foreach my $member ( keys %{ $record->{users} } ) {
	  my $rpl_who = { prefix => $server, command => '352', params => [ $nick, $who ] };
	  my $memrec = $self->{state}->{users}->{ $member };
	  push @{ $rpl_who->{params} }, $memrec->{auth}->{ident};
	  push @{ $rpl_who->{params} }, $memrec->{auth}->{hostname};
	  push @{ $rpl_who->{params} }, $memrec->{server};
	  push @{ $rpl_who->{params} }, $memrec->{nick};
          my $status = ( $memrec->{away} ? 'G' : 'H' );
	  $status .= '*' if $memrec->{umode} =~ /o/;
	  $status .= '@' if $record->{users}->{ $member } =~ /o/;
	  $status .= '%' if $record->{users}->{ $member } =~ /h/;
	  $status .= '+' if $record->{users}->{ $member } !~ /o/ and $record->{users}->{ $member } =~ /v/;
	  push @{ $rpl_who->{params} }, $status;
	  push @{ $rpl_who->{params} }, $memrec->{hops} . ' ' . $memrec->{ircname};
	  push @{ $ref }, $rpl_who;
	}
    }
    if ( $self->state_nick_exists( $who ) ) {
	my $nickrec = $self->{state}->{users}->{ u_irc $who };
	$who = $nickrec->{nick};
	my $rpl_who = { prefix => $server, command => '352', params => [ $nick, '*' ] };
	push @{ $rpl_who->{params} }, $nickrec->{auth}->{ident};
        push @{ $rpl_who->{params} }, $nickrec->{auth}->{hostname};
        push @{ $rpl_who->{params} }, $nickrec->{server};
        push @{ $rpl_who->{params} }, $nickrec->{nick};
        my $status = ( $nickrec->{away} ? 'G' : 'H' );
	$status .= '*' if $nickrec->{umode} =~ /o/;
	push @{ $rpl_who->{params} }, $status;
	push @{ $rpl_who->{params} }, $nickrec->{hops} . ' ' . $nickrec->{ircname};
	push @{ $ref }, $rpl_who;
    }
    push @{ $ref }, { prefix => $server, command => '315', params => [ $nick, $orig, 'End of WHO list' ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_mode {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift;
  my $server = $self->server_name();
  my $maxmodes = $self->server_config('MODES');
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$self->state_chan_exists( $chan ) ) {
	push @{ $ref }, [ '403', $chan ];
	last SWITCH;
    }
    my $record = $self->{state}->{chans}->{ u_irc $chan };
    $chan = $record->{name};
    if ( !$count and !$self->state_is_chan_member( $nick, $chan ) ) {
	push @{ $ref }, { prefix => $server, command => '324', params => [ $nick, $chan, '+' . $record->{mode} ], colonify => 0 };
	push @{ $ref }, { prefix => $server, command => '329', params => [ $nick, $chan, $record->{ts} ], colonify => 0 };
	last SWITCH;
    }
    if ( !$count ) {
	push @{ $ref }, { prefix => $server, command => '324', params => [ $nick, $chan, '+' . $record->{mode}, ( $record->{ckey} || () ), ( $record->{climit} || () ) ], colonify => 0 };
	push @{ $ref }, { prefix => $server, command => '329', params => [ $nick, $chan, $record->{ts} ], colonify => 0 };
	last SWITCH;
    }
    my $unknown = 0;
    my $notop = 0;
    my $nick_is_op = $self->state_is_chan_op( $nick, $chan );
    my $nick_is_hop = $self->state_is_chan_hop( $nick, $chan );
    my $reply; my @reply_args;
    my $parsed_mode = parse_mode_line( @{ $args } );
    my $mode_count = 0;
    while( my $mode = shift @{ $parsed_mode->{modes} } ) {
      if ( $mode !~ /[eIbklimnpstohv]/ ) {
	push @{ $ref }, [ '472', ( split //, $mode )[1], $chan ] unless $unknown;
	$unknown++;
	next;
      }
      my $arg;
      $arg = shift @{ $parsed_mode->{args} } if $mode =~ /^(\+[ohvklbIe]|-[ohvbIe])/;
      if ( $mode =~ /(\+|-)b/ and !defined $arg ) {
	push @{ $ref }, { prefix => $server, command => '367', params => [ $nick, $chan, @{ $record->{bans}->{$_} } ] } for keys %{ $record->{bans} };
	push @{ $ref }, { prefix => $server, command => '368', params => [ $nick, $chan, 'End of Channel Ban List' ] };
	next;
      }
      unless ( $nick_is_op or $nick_is_hop ) {
	push @{ $ref }, [ '482', $chan ] unless $notop;
	$notop++;
	next;
      }
      if ( $mode =~ /(\+|-)I/ and !defined $arg ) {
	push @{ $ref }, { prefix => $server, command => '346', params => [ $nick, $chan, @{ $record->{invex}->{$_} } ] } for keys %{ $record->{invex} };
	push @{ $ref }, { prefix => $server, command => '347', params => [ $nick, $chan, 'End of Channel Invite List' ] };
	next;
      }
      if ( $mode =~ /(\+|-)e/ and !defined $arg ) {
	push @{ $ref }, { prefix => $server, command => '348', params => [ $nick, $chan, @{ $record->{excepts}->{$_} } ] } for keys %{ $record->{excepts} };
	push @{ $ref }, { prefix => $server, command => '349', params => [ $nick, $chan, 'End of Channel Exception List' ] };
	next;
      }
      if ( !$nick_is_op and $nick_is_hop and $mode =~ /[op]/ ) {
	push @{ $ref }, [ '482', $chan ] unless $notop;
	$notop++;
	next;
      }
      if ( !$nick_is_op and $nick_is_hop and $record->{mode} =~ /p/ and $mode =~ /h/ ) {
	push @{ $ref }, [ '482', $chan ] unless $notop;
	$notop++;
	next;
      }
      if ( ( $mode =~ /^(\+|-)([ohv])/ or $mode =~ /^\+[lk]/ ) and !defined $arg ) {
	next;
      }
      if ( $mode =~ /^(\+|-)([ohv])/ and !$self->state_nick_exists($arg) ) {
	next if ++$mode_count > $maxmodes;
	push @{ $ref }, [ '401', $arg ];
	next;
      }
      if ( $mode =~ /^(\+|-)([ohv])/ and !$self->state_is_chan_member( $arg, $chan ) ) {
	next if ++$mode_count > $maxmodes;
	push @{ $ref }, [ '441', $chan, $self->state_user_nick( $arg ) ];
	next;
      }
      if ( my ($flag,$char) = $mode =~ /^(\+|-)([ohv])/ ) {
	next if ++$mode_count > $maxmodes;
	if ( $flag eq '+' and $record->{users}->{ u_irc $arg } !~ /$char/ ) {
	  # Update user and chan record
	  $arg = u_irc $arg;
	  next if $mode eq '+h' and $record->{users}->{ $arg } =~ /o/;
	  if ( $char eq 'h' and $record->{users}->{ $arg } =~ /v/ ) {
	     $record->{users}->{ $arg } =~ s/v//g;
	     $reply .= '-v';
	     push @reply_args, $self->state_user_nick( $arg );
	  }
	  if ( $char eq 'o' and $record->{users}->{ $arg } =~ /h/ ) {
	     $record->{users}->{ $arg } =~ s/h//g;
	     $reply .= '-h';
	     push @reply_args, $self->state_user_nick( $arg );
	  }
	  $record->{users}->{ $arg }  = join('', sort split //, $record->{users}->{ $arg } . $char );
	  $self->{state}->{users}->{ $arg }->{chans}->{ u_irc $chan } = $record->{users}->{ $arg };
	  $reply .= $mode;
	  push @reply_args, $self->state_user_nick( $arg );
        }
	if ( $flag eq '-' and $record->{users}->{ u_irc $arg } =~ /$char/ ) {
	  # Update user and chan record
	  $arg = u_irc $arg;
	  $record->{users}->{ $arg } =~ s/$char//g;
	  $self->{state}->{users}->{ $arg }->{chans}->{ u_irc $chan } = $record->{users}->{ $arg };
	  $reply .= $mode;
	  push @reply_args, $self->state_user_nick( $arg );
        }
	next;
      }
      if ( $mode eq '+l' and $arg =~ /^\d+$/ and $arg > 0 ) {
	next if ++$mode_count > $maxmodes;
	$reply .= $mode;
	push @reply_args, $arg;
	$record->{mode} = join('', sort split //, $record->{mode} . 'l' ) unless $record->{mode} =~ /l/;
	$record->{climit} = $arg;
	next;
      }
      if ( $mode eq '-l' and $record->{mode} =~ /l/ ) {
	$record->{mode} =~ s/l//g;
	delete $record->{climit};
	$reply .= $mode;
	next;
      }
      if ( $mode eq '+k' and $arg ) {
	next if ++$mode_count > $maxmodes;
	$reply .= $mode;
	push @reply_args, $arg;
	$record->{mode} = join('', sort split //, $record->{mode} . 'k' ) unless $record->{mode} =~ /k/;
	$record->{ckey} = $arg;
	next;
      }
      if ( $mode eq '-k' and $record->{mode} =~ /k/ ) {
	$reply .= $mode;
	push @reply_args, '*';
	$record->{mode} =~ s/k//g;
	delete $record->{ckey};
	next;
      }
      # Bans
      if ( my ($flag) = $mode =~ /(\+|-)b/ ) {
	next if ++$mode_count > $maxmodes;
	my $mask = parse_ban_mask( $arg );
	my $umask = u_irc $mask;
	if ( $flag eq '+' and !$record->{bans}->{ $umask } ) {
	  $record->{bans}->{ $umask } = [ $mask, $self->state_user_full( $nick ), time() ];
	  $reply .= $mode;
	  push @reply_args, $mask;
	}
	if ( $flag eq '-' and $record->{bans}->{ $umask } ) {
	  delete $record->{bans}->{ $umask };
	  $reply .= $mode;
	  push @reply_args, $mask;
	}
	next;
      }
      # Invex
      if ( my ($flag) = $mode =~ /(\+|-)I/ ) {
	next if ++$mode_count > $maxmodes;
	my $mask = parse_ban_mask( $arg );
	my $umask = u_irc $mask;
	if ( $flag eq '+' and !$record->{invex}->{ $umask } ) {
	  $record->{invex}->{ $umask } = [ $mask, $self->state_user_full( $nick ), time() ];
	  $reply .= $mode;
	  push @reply_args, $mask;
	}
	if ( $flag eq '-' and $record->{invex}->{ $umask } ) {
	  delete $record->{invex}->{ $umask };
	  $reply .= $mode;
	  push @reply_args, $mask;
	}
	next;
      }
      # Exceptions
      if ( my ($flag) = $mode =~ /(\+|-)e/ ) {
	next if ++$mode_count > $maxmodes;
	my $mask = parse_ban_mask( $arg );
	my $umask = u_irc $mask;
	if ( $flag eq '+' and !$record->{excepts}->{ $umask } ) {
	  $record->{excepts}->{ $umask } = [ $mask, $self->state_user_full( $nick ), time() ];
	  $reply .= $mode;
	  push @reply_args, $mask;
	}
	if ( $flag eq '-' and $record->{excepts}->{ $umask } ) {
	  delete $record->{excepts}->{ $umask };
	  $reply .= $mode;
	  push @reply_args, $mask;
	}
	next;
      }
      # The rest should be argumentless.
      my ($flag,$char) = split //, $mode;
      if ( $flag eq '+' and $record->{mode} !~ /$char/ ) {
	$reply .= $mode;
	$record->{mode} = join('', sort split //, $record->{mode} . $char );
	next;
      }
      if ( $flag eq '-' and $record->{mode} =~ /$char/ ) {
	$reply .= $mode;
	$record->{mode} =~ s/$char//g;
	next;
      }
    } # while
    if ( $reply ) {
	$reply = unparse_mode_line( $reply );
	my $output = { prefix => $self->state_user_full( $nick ), command => 'MODE', params => [ $chan, $reply, @reply_args ], colonify => 0 };
	$self->_send_output_to_channel( $chan, $output );
    }
  } # SWITCH
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_join {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  my $route_id = $self->_state_user_route( $nick );
  my $unick = u_irc $nick;
  SWITCH: {
    my @channels; my @chankeys;
    if ( !$count ) {
	push @{ $ref }, [ '461', 'JOIN' ];
	last SWITCH;
    }
    @channels = split /,/, $args->[0];
    @chankeys = split /,/, $args->[1] if ( $args->[1] );
    my $channel_length = $self->server_config('CHANNELLEN');
    LOOP: foreach my $channel ( @channels ) {
      my $uchannel = u_irc $channel;
      if ( $channel eq '0' and my @chans = $self->state_user_chans( $nick ) ) {
	$self->_send_output_to_client( $route_id => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for ( map { $self->_daemon_cmd_part( $nick, $_ ) } @chans );
	next LOOP;
      }
      # Channel isn't valid
      if ( !validate_chan_name( $channel ) or length( $channel ) > $channel_length ) {
	$self->_send_output_to_client( $route_id => '403' => $channel );
	next LOOP;
      }
      # Too many channels
      if ( scalar $self->state_user_chans( $nick ) >= $self->server_config('MAXCHANNELS') and !$self->state_user_is_operator( $nick ) ) {
	$self->_send_output_to_client( $route_id => '405' => $channel );
	next LOOP;
      }
      # Channel doesn't exist
      unless ( $self->state_chan_exists( $channel ) ) {
	my $record = { name => $channel, ts => time(), mode => 'nt', users => { $unick => 'o' }, };
	$self->{state}->{chans}->{ $uchannel } = $record;
	$self->{state}->{users}->{ $unick }->{chans}->{ $uchannel } = 'o';
	my @peers = $self->_state_connected_peers();
	$self->{ircd}->send_output( { command => 'SJOIN', params => [ $record->{ts}, $channel, '+' . $record->{mode}, '@' . $nick ] }, @peers ) unless $channel =~ /^\&/;
	my $output = { prefix => $self->state_user_full( $nick ), command => 'JOIN', params => [ $channel ] };
	$self->{ircd}->send_output( $output, $route_id );
	$self->{ircd}->send_event( "daemon_join", $output->{prefix}, $channel );
	$self->{ircd}->send_output( { prefix => $server, command => 'MODE', params => [ $channel, '+' . $record->{mode} ] }, $route_id );
	$self->_send_output_to_client( $route_id => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for $self->_daemon_cmd_names( $nick, $channel );
	$self->_send_output_to_client( $route_id => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for $self->_daemon_cmd_topic( $nick, $channel );
	next LOOP;
      }
      # Numpty user is already on channel
      if ( $self->state_is_chan_member( $nick, $channel ) ) {
	next LOOP;
      }
      my $chanrec = $self->{state}->{chans}->{ $uchannel };
      my $bypass;
      if ( $self->state_user_is_operator( $nick ) and $self->{config}->{OPHACKS} ) {
	$bypass = 1;
      }
      # Channel is full
      if ( !$bypass and $chanrec->{mode} =~ /l/ and scalar keys %{ $chanrec } >= $chanrec->{climit} ) {
	$self->_send_output_to_client( $route_id => '471' => $channel );
        next LOOP;
      }
      my $chankey;
      $chankey = shift @chankeys if $chanrec->{mode} =~ /k/;
      # Channel +k and no key or invalid key provided
      if ( !$bypass and $chanrec->{mode} =~ /k/ and ( !$chankey or ( $chankey ne $chanrec->{ckey} ) ) ) {
	$self->_send_output_to_client( $route_id => '475' => $channel );
        next LOOP;
      }
      # Channel +i and not INVEX
      if ( !$bypass and $chanrec->{mode} =~ /i/ and !$self->_state_user_invited( $nick, $channel ) ) {
	$self->_send_output_to_client( $route_id => '473' => $channel );
        next LOOP;
      }
      # Channel +b and no exception
      if ( !$bypass and $self->_state_user_banned( $nick, $channel ) ) {
	$self->_send_output_to_client( $route_id => '474' => $channel );
	next LOOP;
      }
      # JOIN the channel
      delete $self->{state}->{users}->{ $unick }->{invites}->{ $uchannel };
      # Add user
      $self->{state}->{users}->{ $unick }->{chans}->{ $uchannel } = '';
      $self->{state}->{chans}->{ $uchannel }->{users}->{ $unick } = '';
      # Send JOIN message to peers and local users.
      $self->{ircd}->send_output( { prefix => $server, command => 'SJOIN', params => [ $chanrec->{ts}, $channel, '+', $nick ] }, $self->_state_connected_peers() ) unless $channel =~ /^\&/;
      my $output = { prefix => $self->state_user_full( $nick ), command => 'JOIN', params => [ $channel ] };
      $self->_send_output_to_client( $route_id => $output );
      $self->_send_output_to_channel( $channel, $output, $route_id );
      # Send NAMES and TOPIC to client
      $self->_send_output_to_client( $route_id => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for $self->_daemon_cmd_names( $nick, $channel );
      $self->_send_output_to_client( $route_id => ( ref $_ eq 'ARRAY' ? @{ $_ } : $_ ) ) for $self->_daemon_cmd_topic( $nick, $channel );
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_part {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$chan ) {
	push @{ $ref }, [ '461', 'PART' ];
	last SWITCH;
    }
    if ( !$self->state_chan_exists( $chan ) ) {
	push @{ $ref }, [ '403', $chan ];
	last SWITCH;
    }
    if ( !$self->state_is_chan_member( $nick, $chan ) ) {
	push @{ $ref }, [ '442', $chan ];
	last SWITCH;
    }
    $self->_send_output_to_channel( $chan, { prefix => $self->state_user_full( $nick ), command => 'PART', params => [ $chan, ( $args->[0] || $nick ) ] } );
    $nick = u_irc $nick;
    $chan = u_irc $chan;
    delete $self->{state}->{chans}->{ $chan }->{users}->{ $nick };
    delete $self->{state}->{users}->{ $nick }->{chans}->{ $chan };
    unless ( scalar keys %{ $self->{state}->{chans}->{ $chan  }->{users} } ) {
	delete $self->{state}->{chans}->{ $chan  };
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_kick {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count or $count < 2 ) {
	push @{ $ref }, [ '461', 'KICK' ];
	last SWITCH;
    }
    my $chan = ( split /,/, $args->[0] )[0];
    my $who = ( split /,/, $args->[1] )[0];
    if ( !$self->state_chan_exists( $chan ) ) {
	push @{ $ref }, [ '403', $chan ];
	last SWITCH;
    }
    $chan = $self->_state_chan_name( $chan );
    if ( !$self->state_nick_exists( $who ) ) {
	push @{ $ref }, [ '401', $who ];
	last SWITCH;
    }
    $who = $self->state_user_nick( $who );
    if ( !$self->state_is_chan_op( $nick, $chan ) ) {
	push @{ $ref }, [ '482', $chan ];
	last SWITCH;
    }
    if ( !$self->state_is_chan_member( $who, $chan ) ) {
	push @{ $ref }, [ '441', $who, $chan ];
	last SWITCH;
    }
    my $comment = $args->[2] || $who;
    $self->_send_output_to_channel( $chan, { prefix => $self->state_user_full( $nick ), command => 'KICK', params => [ $chan, $who, $comment ] } );
    $who = u_irc $who; $chan = u_irc $chan;
    delete $self->{state}->{chans}->{ $chan }->{users}->{ $who };
    delete $self->{state}->{users}->{ $who }->{chans}->{ $chan };
    unless ( scalar keys %{ $self->{state}->{chans}->{ $chan  }->{users} } ) {
	delete $self->{state}->{chans}->{ $chan  };
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_remove {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count or $count < 2 ) {
	push @{ $ref }, [ '461', 'REMOVE' ];
	last SWITCH;
    }
    my $chan = ( split /,/, $args->[0] )[0];
    my $who = ( split /,/, $args->[1] )[0];
    if ( !$self->state_chan_exists( $chan ) ) {
	push @{ $ref }, [ '403', $chan ];
	last SWITCH;
    }
    $chan = $self->_state_chan_name( $chan );
    if ( !$self->state_nick_exists( $who ) ) {
	push @{ $ref }, [ '401', $who ];
	last SWITCH;
    }
    my $fullwho = $self->state_user_full( $who );
    $who = ( split /!/, $fullwho )[0];
    if ( !$self->state_is_chan_op( $nick, $chan ) ) {
	push @{ $ref }, [ '482', $chan ];
	last SWITCH;
    }
    if ( !$self->state_is_chan_member( $who, $chan ) ) {
	push @{ $ref }, [ '441', $who, $chan ];
	last SWITCH;
    }
    my $comment = "Requested by $nick";
    $comment .= " \"$args->[2]\"" if $args->[2];
    $self->_send_output_to_channel( $chan, { prefix => $fullwho, command => 'PART', params => [ $chan, $comment ] } );
    $who = u_irc $who; $chan = u_irc $chan;
    delete $self->{state}->{chans}->{ $chan }->{users}->{ $who };
    delete $self->{state}->{users}->{ $who }->{chans}->{ $chan };
    unless ( scalar keys %{ $self->{state}->{chans}->{ $chan  }->{users} } ) {
	delete $self->{state}->{chans}->{ $chan  };
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_invite {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count or $count < 2 ) {
	push @{ $ref }, [ '461', 'INVITE' ];
	last SWITCH;
    }
    my ($who,$chan) = @{ $args };
    if ( !$self->state_nick_exists( $who ) ) {
	push @{ $ref }, [ '401', $who ];
	last SWITCH;
    }
    $who = $self->state_user_nick( $who );
    if ( !$self->state_chan_exists( $chan ) ) {
	push @{ $ref }, [ '403', $chan ];
	last SWITCH;
    }
    $chan = $self->_state_chan_name( $chan );
    if ( !$self->state_is_chan_member( $nick, $chan ) ) {
	push @{ $ref }, [ '442', $chan ];
	last SWITCH;
    }
    if ( $self->state_is_chan_member( $who, $chan ) ) {
	push @{ $ref }, [ '443', $who, $chan ];
	last SWITCH;
    }
    if ( $self->state_chan_mode_set( $chan, 'i' ) and !$self->state_is_chan_op( $nick, $chan ) ) {
	push @{ $ref }, [ '482', $chan ];
	last SWITCH;
    }
    my $local;
    if ( $self->_state_is_local_user( $who ) ) {
	my $record = $self->{state}->{users}->{ u_irc $who };
	$record->{invites}->{ u_irc $chan } = time();
        $local = 1;
    }
    my $away = $self->_state_user_away_msg($who);
    my $route_id = $self->_state_user_route( $who );
    my $output = { prefix => $self->state_user_full( $nick ), command => 'INVITE', params => [ $who, $chan ], colonify => 0 };
    if ( $route_id eq 'spoofed' ) {
	$self->{ircd}->send_event( "daemon_invite", $output->{prefix}, @{ $output->{params} } );
    } else {
	unless ( $local ) {
	  $output->{prefix} = $nick;
	  push @{ $output->{params} }, time();
	}
    	$self->{ircd}->send_output( $output, $route_id );
    }
    push @{ $ref }, { prefix => $server, command => '341', params => [ $chan, $who ] };
    if ( defined $away ) {
	push @{ $ref }, { prefix => $server, command => '301', params => [ $nick, $who, $away ] };
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_umode {
  my $self = shift;
  my $nick = shift || return;
  my $umode = shift;
  my $server = $self->server_name();
  my $ref = [ ];
  my $record = $self->{state}->{users}->{ u_irc $nick };
  unless ( $umode ) {
    push @{ $ref }, { prefix => $server, command => '221', params => [ $nick, '+' . $record->{umode} ] };
  } else {
    my $peer_ignore;
    my $parsed_mode = parse_mode_line( $umode );
    my $route_id = $self->_state_user_route( $nick );
    my $previous = $record->{umode};
    while ( my $mode = shift @{ $parsed_mode->{modes} } ) {
	next if $mode eq '+o';
	my ($action,$char) = split //, $mode;
	if ( $action eq '+' and $record->{umode} !~ /$char/ ) {
	  next if $char =~ /[wzl]/ and $record->{umode} !~ /o/;
	  $record->{umode} .= $char;
	  if ( $char eq 'i' ) {
	    $self->{state}->{stats}->{invisible}++;
	    $peer_ignore = delete $record->{_ignore_i_umode};
	  }
	  if ( $char eq 'w' ) {
	    $self->{state}->{wallops}->{ $route_id } = time();
	  }
	  if ( $char eq 'z' ) {
	    $self->{state}->{operwall}->{ $route_id } = time();
	  }
	  if ( $char eq 'l' ) {
	    $self->{state}->{locops}->{ $route_id } = time();
	  }
	}
	if ( $action eq '-' and $record->{umode} =~ /$char/ ) {
	  $record->{umode} =~ s/$char//g;
	  $self->{state}->{stats}->{invisible}-- if $char eq 'i';
          if ( $char eq 'o' ) {
    	    $self->{state}->{stats}->{ops_online}--;
	    delete $self->{state}->{localops}->{ $route_id };
            $self->{ircd}->antiflood( $route_id, 1 );
          }
	  if ( $char eq 'w' ) {
	    delete $self->{state}->{wallops}->{ $route_id };
	  }
	  if ( $char eq 'z' ) {
	    delete $self->{state}->{operwall}->{ $route_id };
	  }
	  if ( $char eq 'l' ) {
	    delete $self->{state}->{locops}->{ $route_id };
	  }
	}
    }
    $record->{umode} = join '', sort split //, $record->{umode};
    my $peerprev = $previous;
    my $peerumode = $record->{umode};
    $peerprev =~ s/[^aiow]//g; $peerumode =~ s/[^aiow]//g;
    my $pset = gen_mode_change( $peerprev, $peerumode );
    my $set = gen_mode_change( $previous, $record->{umode} );
    if ( $pset and !$peer_ignore ) {
      my $hashref = { prefix => $nick, command => 'MODE', params => [ $nick, $pset ] };
      $self->{ircd}->send_output( $hashref, $self->_state_connected_peers() );
    }
    if ( $set ) {
      my $hashref = { prefix => $nick, command => 'MODE', params => [ $nick, $set ] };
      $self->{ircd}->send_event( "daemon_umode", $self->state_user_full( $nick ), $set ) unless $peer_ignore;
      push @{ $ref }, $hashref;
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_topic {
  my $self = shift;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH:{
    if ( !$count ) {
	push @{ $ref }, [ '461', 'TOPIC' ];
	last SWITCH;
    }
    if ( !$self->state_chan_exists( $args->[0] ) ) {
	push @{ $ref }, [ '403', $args->[0] ];
	last SWITCH;
    }
    if ( $self->state_chan_mode_set( $args->[0], 's' ) and !$self->state_is_chan_member( $nick, $args->[0] ) ) {
	push @{ $ref }, [ '442', $args->[0] ];
	last SWITCH;
    }
    my $chan_name = $self->_state_chan_name( $args->[0] );
    if ( $count == 1 and my $topic = $self->state_chan_topic( $args->[0] ) ) {
	push @{ $ref }, { prefix => $server, command => '332', params => [ $nick, $chan_name, $topic->[0] ] };
	push @{ $ref }, { prefix => $server, command => '333', params => [ $nick, $chan_name, @{ $topic }[1..2] ] };
	last SWITCH;
    }
    if ( $count == 1 ) {
	push @{ $ref }, { prefix => $server, command => '331', params => [ $nick, $chan_name, 'No topic is set' ] };
	last SWITCH;
    }
    if ( !$self->state_is_chan_member( $nick, $args->[0] ) ) {
	push @{ $ref }, [ '442', $args->[0] ];
	last SWITCH;
    }
    if ( $self->state_chan_mode_set( $args->[0], 't' ) and !$self->state_is_chan_op( $nick, $args->[0] ) ) {
	push @{ $ref }, [ '482', $args->[0] ];
	last SWITCH;
    }
    my $record = $self->{state}->{chans}->{ u_irc $args->[0] };
    my $topic_length = $self->server_config('TOPICLEN');
    $args->[1] = substr( $args->[0],0,$topic_length) if length( $args->[0] ) > $topic_length;
    if ( $args->[1] eq '' ) {
	delete $record->{topic};
    }
    else {
        $record->{topic} = [ $args->[1], $self->state_user_full( $nick ), time() ];
    }
    $self->_send_output_to_channel( $args->[0], { prefix => $self->state_user_full( $nick ), command => 'TOPIC', params => [ $chan_name, $args->[1] ] } );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_cmd_links {
  my $self = shift;
  my $nick = shift || return;
  my $target = shift;
  my $server = $self->server_name();
  my $ref = [ ];
  SWITCH:{
    if ( $target and !$self->state_peer_exists( $target ) ) {
	push @{ $ref }, [ '402', $target ];
	last SWITCH;
    }
    if ( $target and ( uc $server ne uc $target ) ) {
	$self->{ircd}->send_output( { prefix => $nick, command => 'LINKS', params => [ $self->_state_peer_name( $target ) ] }, $self->_state_peer_route( $target ) );
	last SWITCH;
    }
    push @{ $ref }, $_ for $self->_state_server_links( $server, $server, $nick );
    push @{ $ref }, { prefix => $server, command => '364', params => [ $nick, $server, $server, join( ' ', '0', $self->server_config('serverdesc') ) ] };
    push @{ $ref }, { prefix => $server, command => '365', params => [ $nick, '*', 'End of /LINKS list.' ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_squit {
  my $self = shift;
  my $peer_id = shift || return;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  return unless $self->state_peer_exists( $args->[0] );
  SWITCH: {
    if ( $peer_id ne $self->_state_peer_route( $args->[0] ) ) {
      $self->{ircd}->send_output( { command => 'SQUIT', params => $args }, $self->_state_peer_route( $args->[0] ) );
      last SWITCH;
    }
    if ( $peer_id eq $self->_state_peer_route( $args->[0] ) ) {
      $self->{ircd}->send_output( { command => 'SQUIT', params => $args }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
      $self->{ircd}->send_event( "daemon_squit", @{ $args } );
      my $quit_msg = join ' ', $self->_state_peer_for_peer( $args->[0] ), $args->[0];
      foreach my $nick ( $self->_state_server_squit( $args->[0] ) ) {
        my $output = { prefix => $self->state_user_full( $nick ), command => 'QUIT', params => [ $quit_msg ] };
        my $common = { };
        foreach my $uchan ( $self->state_user_chans( $nick ) ) {
          $uchan = u_irc $uchan;
          delete $self->{state}->{chans}->{ $uchan }->{users}->{ $nick };
          foreach my $user ( $self->state_chan_list( $uchan ) ) {
	    next unless $self->_state_is_local_user( $user );
	    $common->{ $user } = $self->_state_user_route( $user );
          }
          unless ( scalar keys %{ $self->{state}->{chans}->{ $uchan  }->{users} } ) {
	    delete $self->{state}->{chans}->{ $uchan  };
          }
        }
        $self->{ircd}->send_output( $output, values %{ $common } );
        $self->{ircd}->send_event( "daemon_quit", $output->{prefix}, $output->{params}->[0] );
        my $record = delete $self->{state}->{users}->{ $nick };
        $self->{state}->{stats}->{ops_online}-- if $record->{umode} =~ /o/;
        $self->{state}->{stats}->{invisible}-- if $record->{umode} =~ /i/;
      }
      last SWITCH;
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_rkline {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  # :klanker RKLINE logserv.gumbynet.org.uk 600 ^m.*\ foo\.(com|uk|net)$ :Foo
  SWITCH: {
     if ( !$count or $count < 5 ) {
	last SWITCH;
     }
     my $full = $self->state_user_full( $nick );
     my $target = $args->[0];
     my $us = 0;
     my $ucserver = uc $server;
     my %targets;
     foreach my $peer ( keys %{ $self->{state}->{peers} } ) {
	if ( matches_mask( $target, $peer ) ) {
	   if ( $ucserver eq $peer ) {
		$us = 1;
	   } else {
		$targets{ $self->_state_peer_route( $peer ) }++;
	   }
	}
     }
     delete $targets{ $peer_id };
     $self->{ircd}->send_output( { prefix => $nick, command => 'RKLINE', params => $args, colonify => 0 }, grep { $self->_state_peer_capab( $_, 'KLN' ) } keys %targets );
     if ( $us ) {
     	$self->{ircd}->send_event( "daemon_rkline", $full, @{ $args } );
	push @{ $self->{state}->{rklines} }, { setby => $full, setat => time(), target => $args->[0], duration => $args->[1], user => $args->[2], host => $args->[3], reason => $args->[4] };
	$self->_terminate_conn_error( $_, 'K-Lined' ) for $self->_state_local_users_match_rkline( $args->[2], $args->[3] );
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_kline {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     if ( !$count or $count < 5 ) {
	last SWITCH;
     }
     my $full = $self->state_user_full( $nick );
     my $target = $args->[0];
     my $us = 0;
     my $ucserver = uc $server;
     my %targets;
     foreach my $peer ( keys %{ $self->{state}->{peers} } ) {
	if ( matches_mask( $target, $peer ) ) {
	   if ( $ucserver eq $peer ) {
		$us = 1;
	   } else {
		$targets{ $self->_state_peer_route( $peer ) }++;
	   }
	}
     }
     delete $targets{ $peer_id };
     $self->{ircd}->send_output( { prefix => $nick, command => 'KLINE', params => $args, colonify => 0 }, grep { $self->_state_peer_capab( $_, 'KLN' ) } keys %targets );
     if ( $us ) {
     	$self->{ircd}->send_event( "daemon_kline", $full, @{ $args } );
	push @{ $self->{state}->{klines} }, { setby => $full, setat => time(), target => $args->[0], duration => $args->[1], user => $args->[2], host => $args->[3], reason => $args->[4] };
	$self->_terminate_conn_error( $_, 'K-Lined' ) for $self->_state_local_users_match_gline( $args->[2], $args->[3] );
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_unkline {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  # :klanker UNKLINE logserv.gumbynet.org.uk * moos.loud.me.uk
  SWITCH: {
     if ( !$count or $count < 3 ) {
	last SWITCH;
     }
     my $full = $self->state_user_full( $nick );
     my $target = $args->[0];
     my $us = 0;
     my $ucserver = uc $server;
     my %targets;
     foreach my $peer ( keys %{ $self->{state}->{peers} } ) {
	if ( matches_mask( $target, $peer ) ) {
	   if ( $ucserver eq $peer ) {
		$us = 1;
	   } else {
		$targets{ $self->_state_peer_route( $peer ) }++;
	   }
	}
     }
     delete $targets{ $peer_id };
     $self->{ircd}->send_output( { prefix => $nick, command => 'UNKLINE', params => $args, colonify => 0 }, grep { $self->_state_peer_capab( $_, 'UNKLN' ) } keys %targets );
     if ( $us ) {
     	$self->{ircd}->send_event( "daemon_unkline", $full, @{ $args } );
	my $i = 0;
	for ( @{ $self->{state}->{klines} } ) {
	  splice ( @{ $self->{state}->{klines} }, $i, 1), last
		if $_->{user} eq $args->[1] and $_->{host} eq $args->[2];
	  ++$i;
	}
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_gline {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  # :klanker GLINE * meep.com :Fuckers
  SWITCH: {
     if ( !$count or $count < 3 ) {
	last SWITCH;
     }
     my $full = $self->state_user_full( $nick );
     push @{ $self->{state}->{glines} }, { setby => $full, setat => time(), user => $args->[0], host => $args->[1], reason => $args->[2] };
     $self->{ircd}->send_output( { prefix => $nick, command => 'GLINE', params => $args, colonify => 0 }, grep { $_ ne $peer_id and $self->_state_peer_capab( $_, 'GLN' ) } $self->_state_connected_peers() );
     $self->{ircd}->send_event( "daemon_gline", $full, @{ $args } );
     $self->_terminate_conn_error( $_, 'G-Lined' ) for $self->_state_local_users_match_gline( $args->[0], $args->[1] );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_wallops {
  my $self = shift;
  my $peer_id = shift || return;
  my $prefix = shift || return;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     my $full = $self->state_user_full( $prefix ) || $prefix;
     $self->{ircd}->send_output( { prefix => $prefix, command => 'WALLOPS', params => [ $args->[0] ] }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
     if ( $self->state_peer_exists( $full ) ) {
       $self->{ircd}->send_output( { prefix => $full, command => 'WALLOPS', params => [ 'OPERWALL - ' . $args->[0] ] }, keys %{ $self->{state}->{wallops} } );
       $self->{ircd}->send_event( "daemon_wallops", $full, $args->[0] );
     } else {
       $self->{ircd}->send_output( { prefix => $full, command => 'WALLOPS', params => [ 'OPERWALL - ' . $args->[0] ] }, keys %{ $self->{state}->{operwall} } );
       $self->{ircd}->send_event( "daemon_operwall", $full, $args->[0] );
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_operwall {
  my $self = shift;
  my $peer_id = shift || return;
  my $prefix = shift || return;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     my $full = $self->state_user_full( $prefix ) || $prefix;
     $self->{ircd}->send_output( { prefix => $prefix, command => 'WALLOPS', params => [ $args->[0] ] }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
     if ( $self->state_peer_exists( $full ) ) {
       $self->{ircd}->send_output( { prefix => $full, command => 'WALLOPS', params => [ 'OPERWALL - ' . $args->[0] ] }, keys %{ $self->{state}->{wallops} } );
       $self->{ircd}->send_event( "daemon_wallops", $full, $args->[0] );
     } else {
       $self->{ircd}->send_output( { prefix => $full, command => 'WALLOPS', params => [ 'OPERWALL - ' . $args->[0] ] }, keys %{ $self->{state}->{operwall} } );
       $self->{ircd}->send_event( "daemon_operwall", $full, $args->[0] );
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_eob {
  my $self = shift;
  my $peer_id = shift || return;
  my $peer = shift || return;
  my $ref = [ ];
  $self->{ircd}->send_event( "daemon_eob", $peer );
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_kill {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     if ( $self->state_peer_exists( $args->[0] ) ) {
	last SWITCH;
     }
     if ( !$self->state_nick_exists( $args->[0] ) ) {
	last SWITCH;
     }
     my $target = $self->state_user_nick( $args->[0] );
     my $comment = $args->[1];
     if ( $self->_state_is_local_user( $target ) ) {
	my $route_id = $self->_state_user_route( $target );
        $self->{ircd}->send_output( { prefix => $nick, command => 'KILL', params => [ $target, join('!', $server, $comment ) ] }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
	$self->{ircd}->send_output( { prefix => $self->state_user_full( $nick ), command => 'KILL', params => [ $target, join('!', $server, $comment ) ] }, $route_id );
	if ( $route_id eq 'spoofed' ) {
	  $self->call( 'del_spoofed_nick', $target, "Killed ($comment)" );
	} else {
	  $self->{state}->{conns}->{ $route_id }->{killed} = 1;
	  $self->_terminate_conn_error( $route_id, "Killed ($comment)" );
	}
     } else {
	$self->{state}->{users}->{ u_irc $target }->{killed} = 1;
        $self->{ircd}->send_output( { prefix => $nick, command => 'KILL', params => [ $target, join('!', $server, $comment ) ] }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
	$self->{ircd}->send_output( @{ $self->_daemon_peer_quit( $target, "Killed ($nick ($comment))" ) } );
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_svinfo {
  my $self = shift;
  my $peer_id = shift || return;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  $self->{state}->{conns}->{ $peer_id }->{svinfo} = $args;
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_ping {
  my $self = shift;
  my $peer_id = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count ) {
	last SWITCH;
    }
    if ( $count >= 2 and ( uc $server ne uc $args->[1] ) ) {
	$self->{ircd}->send_output( { command => 'PING', params => $args }, $self->_state_peer_route( $args->[1] ) ) if $self->state_peer_exists( $args->[1] );
	$self->{ircd}->send_output( { command => 'PING', params => $args }, $self->_state_user_route( $args->[1] ) ) if $self->state_nick_exists( $args->[1] );
	last SWITCH;
    }
    $self->{ircd}->send_output( { command => 'PONG', params => [ $server, $args->[0] ] }, $peer_id );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_pong {
  my $self = shift;
  my $peer_id = shift || return;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count ) {
	last SWITCH;
    }
    if ( $count >= 2 and ( uc $self->server_name() ne uc $args->[1] ) ) {
	$self->{ircd}->send_output( { command => 'PONG', params => $args }, $self->_state_peer_route( $args->[1] ) ) if $self->state_peer_exists( $args->[1] );
	$self->{ircd}->send_output( { command => 'PONG', params => $args }, $self->_state_user_route( $args->[1] ) ) if $self->state_nick_exists( $args->[1] );
	last SWITCH;
    }
    delete $self->{state}->{conns}->{ $peer_id }->{pinged};
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_server {
  my $self = shift;
  my $peer_id = shift || return;
  my $prefix = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  my $peer = $self->{state}->{conns}->{ $peer_id }->{name};
  SWITCH: {
    if ( !$count or $count < 2 ) {
	last SWITCH;
    }
    if ( $self->state_peer_exists( $args->[0] ) ) {
	$self->_terminate_conn_error( $peer_id, 'Server exists' );
	last SWITCH;
    }
    my $record = {
		   name => $args->[0],
		   hops => $args->[1],
		   desc => ( $args->[2] || '' ),
		   route_id => $peer_id,
		   type => 'r',
		   peer => $prefix,
		   peers => { },
		   users => { },
    		 };
    my $uname = uc $record->{name};
    $self->{state}->{peers}->{ $uname } = $record;
    $self->{state}->{peers}->{ uc $prefix }->{peers}->{ $uname } = $record;
    $self->{ircd}->send_output( { prefix => $prefix, command => 'SERVER', params => [ $record->{name}, $record->{hops} + 1, $record->{desc} ] }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
    $self->{ircd}->send_event( "daemon_server", $record->{name}, $prefix, $record->{hops}, $record->{desc} );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_quit {
  my $self = shift;
  my $nick = shift || return;
  my $qmsg = shift || 'Client Quit';
  my $conn_id = shift;
  my $ref = [ ];
  my $full = $self->state_user_full( $nick );

  $nick = u_irc $nick;
  my $record = delete $self->{state}->{users}->{ $nick };
  return $ref unless $record;
  $self->{ircd}->send_output( { prefix => $record->{nick}, command => 'QUIT', params => [ $qmsg ] }, grep { !$conn_id or $_ ne $conn_id } $self->_state_connected_peers() ) unless $record->{killed};
  push @{ $ref }, { prefix => $full, command => 'QUIT', params => [ $qmsg ] };
  $self->{ircd}->send_event( "daemon_quit", $full, $qmsg );
  # Remove for peoples accept lists
  delete $self->{state}->{users}->{$_}->{accepts}->{ u_irc $nick } for keys %{ $record->{accepts} };
  # Okay, all 'local' users who share a common channel with user.
  my $common = { };
  foreach my $uchan ( keys %{ $record->{chans} } ) {
    delete $self->{state}->{chans}->{ $uchan }->{users}->{ $nick };
    foreach my $user ( $self->state_chan_list( $uchan ) ) {
	next unless $self->_state_is_local_user( $user );
	$common->{ $user } = $self->_state_user_route( $user );
    }
    unless ( scalar keys %{ $self->{state}->{chans}->{ $uchan  }->{users} } ) {
	delete $self->{state}->{chans}->{ $uchan  };
    }
  }
  push( @{ $ref }, $common->{$_} ) for keys %{ $common };
  $self->{state}->{stats}->{ops_online}-- if $record->{umode} =~ /o/;
  $self->{state}->{stats}->{invisible}-- if $record->{umode} =~ /i/;
  delete $self->{state}->{peers}->{ uc $record->{server} }->{users}->{ $nick };
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_nick {
  my $self = shift;
  my $peer_id = shift || return;
  my $prefix = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  my $peer = $self->{state}->{conns}->{ $peer_id }->{name};
  my $nicklen = $self->server_config('NICKLEN');
  SWITCH: {
    if ( !$count or ( $count < 8 and !$prefix ) ) {
	$self->_terminate_conn_error( $peer_id, 'Not enough arguments to server command.' );
	last SWITCH;
    }
    if ( $prefix and $self->state_nick_exists( $args->[0] ) ) {
        $self->{ircd}->send_output( { prefix => $server, command => 'KILL', params => [ $args->[0], "$server (Nick exists)" ] }, $peer_id );
	my $unick = u_irc $prefix;
	$self->{state}->{users}->{ $unick }->{nick_collision} = 1;
	$self->daemon_server_kill( $prefix, 'Nick Collision', $peer_id );
	last SWITCH;
    }
    if ( $prefix and length( $args->[0] ) > $nicklen ) {
        $self->{ircd}->send_output( { prefix => $server, command => 'KILL', params => [ $args->[0], "$server (Bad nickname)" ] }, $peer_id );
	my $unick = u_irc $prefix;
	$self->{state}->{users}->{ $unick }->{nick_collision} = 1;
	$self->daemon_server_kill( $prefix, 'Nick Collision', $peer_id );
	last SWITCH;
    }
    if ( $prefix ) {
	my $full = $self->state_user_full( $prefix );
	my $unick = u_irc $prefix;
	my $new = $args->[0]; my $unew = u_irc $new;
	my $ts = $args->[1] || time();
	my $record = $self->{state}->{users}->{ $unick };
	my $server = uc $record->{server};
        if ( $unick eq $unew ) {
	  $record->{nick} = $new;
	  $record->{ts} = $ts;
    	} else {
	  $record->{nick} = $new;
	  $record->{ts} = $ts;
  	  # Remove from peoples accept lists
  	  delete $self->{state}->{users}->{$_}->{accepts}->{ $unick } for keys %{ $record->{accepts} };
	  delete $record->{accepts};
	  delete $self->{state}->{users}->{ $unick };
	  $self->{state}->{users}->{ $unew } = $record;
	  delete $self->{state}->{peers}->{ $server }->{users}->{ $unick };
	  $self->{state}->{peers}->{ $server }->{users}->{ $unew } = $record;
	  foreach my $chan ( keys %{ $record->{chans} } ) {
	    $self->{state}->{chans}->{ $chan }->{users}->{ $unew } = delete $self->{state}->{chans}->{ $chan }->{users}->{ $unick };
	  }
    	}
    	my $common = { };
    	foreach my $chan ( keys %{ $record->{chans} } ) {
      	  foreach my $user ( $self->state_chan_list( $chan ) ) {
		next unless $self->_state_is_local_user( $user );
		$common->{ $user } = $self->_state_user_route( $user );
      	  }
    	}
        $self->{ircd}->send_output( { prefix => $prefix, command => 'NICK', params => $args }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
    	$self->{ircd}->send_output( { prefix => $full, command => 'NICK', params => [ $new ] }, map{ $common->{$_} } keys %{ $common } );
    	$self->{ircd}->send_event( "daemon_nick", $full, $new );
	last SWITCH;
    }
    if ( $self->state_nick_exists( $args->[0] ) and my ($nick,$userhost) = split /!/, $self->state_user_full( $args->[0] ) ) {
	my $unick = u_irc $nick;
	my $incoming = join '@', @{ $args }[4..5];
	if ( $userhost eq $incoming ) {
	  my $ts = $self->{state}->{users}->{ $unick }->{ts};
	  if ( $args->[2] > $ts ) {
		$self->{state}->{users}->{ $unick }->{nick_collision} = 1;
		$self->daemon_server_kill( $nick, 'Nick Collision', $peer_id );
	  } else {
		last SWITCH;
	  }
	} else {
	  my $ts = $self->{state}->{users}->{ $unick }->{ts};
	  if ( $args->[2] < $ts ) {
		$self->{state}->{users}->{ $unick }->{nick_collision} = 1;
		$self->daemon_server_kill( $nick, 'Nick Collision', $peer_id );
	  } else {
		last SWITCH;
	  }
	}
    }
    if ( !$self->state_peer_exists( $args->[6] ) ) {
	last SWITCH;
    }
    if ( length( $args->[0] ) > $nicklen ) {
        $self->{ircd}->send_output( { prefix => $server, command => 'KILL', params => [ $args->[0], "$server (Bad nickname)" ] }, $peer_id );
	last SWITCH;
    }
    my $unick = u_irc $args->[0];
    $args->[3] =~ s/^\+//g;
    my $record = {
			nick => $args->[0],
			hops => $args->[1],
			ts   => $args->[2],
			type => 'r',
			umode => $args->[3],
			auth => { ident => $args->[4], hostname => $args->[5] },
			route_id => $peer_id,
			server => $args->[6],
			ircname => ( $args->[7] || '' ),
		  };
    $self->{state}->{users}->{ $unick } = $record;
    $self->{state}->{stats}->{ops_online}++ if $record->{umode} =~ /o/;
    $self->{state}->{stats}->{invisible}++ if $record->{umode} =~ /i/;
    $self->{state}->{peers}->{ uc $record->{server} }->{users}->{ $unick } = $record;
    $self->_state_update_stats();
    $self->{ircd}->send_output( { command => 'NICK', params => $args }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
    $self->{ircd}->send_event( "daemon_nick", @{ $args } );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_part {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $chan = shift;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$chan ) {
	last SWITCH;
    }
    if ( !$self->state_chan_exists( $chan ) ) {
	last SWITCH;
    }
    if ( !$self->state_is_chan_member( $nick, $chan ) ) {
	last SWITCH;
    }
    $self->_send_output_to_channel( $chan, { prefix => $self->state_user_full( $nick ), command => 'PART', params => [ $chan, ( $args->[0] || $nick ) ] }, $peer_id );
    $nick = u_irc $nick;
    $chan = u_irc $chan;
    delete $self->{state}->{chans}->{ $chan }->{users}->{ $nick };
    delete $self->{state}->{users}->{ $nick }->{chans}->{ $chan };
    unless ( scalar keys %{ $self->{state}->{chans}->{ $chan  }->{users} } ) {
	delete $self->{state}->{chans}->{ $chan  };
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_kick {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count or $count < 2 ) {
	last SWITCH;
    }
    my $chan = ( split /,/, $args->[0] )[0];
    my $who = ( split /,/, $args->[1] )[0];
    if ( !$self->state_chan_exists( $chan ) ) {
	last SWITCH;
    }
    $chan = $self->_state_chan_name( $chan );
    if ( !$self->state_nick_exists( $who ) ) {
	last SWITCH;
    }
    $who = $self->state_user_nick( $who );
    if ( !$self->state_is_chan_op( $nick, $chan ) ) {
	last SWITCH;
    }
    if ( !$self->state_is_chan_member( $who, $chan ) ) {
	last SWITCH;
    }
    my $comment = $args->[2] || $who;
    $self->_send_output_to_channel( $chan, { prefix => $self->state_user_full( $nick ), command => 'KICK', params => [ $chan, $who, $comment ] }, $peer_id );
    $who = u_irc $who; $chan = u_irc $chan;
    delete $self->{state}->{chans}->{ $chan }->{users}->{ $who };
    delete $self->{state}->{users}->{ $who }->{chans}->{ $chan };
    unless ( scalar keys %{ $self->{state}->{chans}->{ $chan  }->{users} } ) {
	delete $self->{state}->{chans}->{ $chan  };
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_sjoin {
  my $self = shift;
  my $peer_id = shift || return;
  my $prefix = shift;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  #my $peer = $self->{state}->{conns}->{ $peer_id }->{name};
  SWITCH: {
    if ( !$count or $count < 4 ) {
	last SWITCH;
    }
    my $ts = $args->[0];
    my $chan = $args->[1];
    my $nicks = pop @{ $args };
    my $ignore_modes = 0;
    if ( !$self->state_chan_exists( $chan ) ) {
	my $server = $self->server_name();
	my $chanrec = { name => $chan, ts => $ts };
	my @args = @{ $args }[2..$#{ $args }];
	my $cmode = shift @args;
	$cmode =~ s/^\+//g;
	$chanrec->{mode} = $cmode;
	foreach my $mode ( split //, $cmode ) {
	  my $arg;
	  $arg = shift @args if $mode =~ /[lk]/;
	  $chanrec->{climit} = $arg if $mode eq 'l';
	  $chanrec->{ckey} = $arg if $mode eq 'k';
	}
	push @{ $args }, $nicks;
	my $uchan = u_irc $chanrec->{name};
	foreach my $nick ( split /\s+/, $nicks ) {
	  my $umode = '';
	  $umode .= 'o' if $nick =~ s/\@//g;
	  $umode = 'h' if $nick =~ s/\%//g;
	  $umode .= 'v' if $nick =~ s/\+//g;
	  my $unick = u_irc $nick;
	  $chanrec->{users}->{ $unick } = $umode;
	  $self->{state}->{users}->{ $unick }->{chans}->{ $uchan } = $umode;
	  $self->{ircd}->send_event( "daemon_join", $self->state_user_full( $nick ), $chan );
	  $self->{ircd}->send_event( "daemon_mode", $server, $chan, '+' . $umode, $nick ) if $umode;
	}
	$self->{state}->{chans}->{ $uchan } = $chanrec;
	$self->{ircd}->send_output( { prefix => $prefix, command => 'SJOIN', params => $args }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
	last SWITCH;
    }
    my $chanrec = $self->{state}->{chans}->{ u_irc $chan };
    my @local_users = map { $self->_state_user_route($_) } grep { $self->_state_is_local_user($_) } keys %{ $chanrec->{users} };
    if ( $ts < $chanrec->{ts} ) {
	  # Incoming is older
	  if ( $nicks =~ /^\@/ ) {
	     # Remove all modes expect bans/invex/excepts
	     # deop/dehalfop/devoice all existing users
	     my @deop; my @deop_list;
	     my $common = { };
	     foreach my $user ( keys %{ $chanrec->{users} } ) {
		$common->{ $user } = $self->_state_user_route( $user ) if $self->_state_is_local_user( $user );
		next unless $chanrec->{users}->{ $user };
		my $current = $chanrec->{users}->{ $user };
		my $proper = $self->state_user_nick( $user );
		$chanrec->{users}->{ $user } = '';
		$self->{state}->{users}->{ $user }->{chans}->{ u_irc $chanrec->{name} } = '';
		push @deop, "-$current";
		push @deop_list, $proper for split //, $current;
	     }
	     if ( scalar keys %{ $common } and scalar @deop ) {
		my $server = $self->server_name();
		$self->{ircd}->send_event( "daemon_mode", $server, $chanrec->{name}, unparse_mode_line( join '', @deop ), @deop_list );
		my @output_modes;
		my $length = length($server) + 4 + length($chan) + 4;
		my @buffer = ( '', '' );
		foreach my $deop ( @deop ) {
		  my $arg = shift @deop_list;
		  my $mode_line = unparse_mode_line( $buffer[0] . $deop );
		  if ( length( join ' ', $mode_line, $buffer[1], $arg ) + $length > 510 ) {
		    push @output_modes, { prefix => $server, command => 'MODE', params => [ $chanrec->{name}, $buffer[0], split /\s+/, $buffer[1] ], colonify => 0 };
		    $buffer[0] = $deop;
		    $buffer[1] = $arg;
		    next;
		  }
		  $buffer[0] = $mode_line;
		  if ( $buffer[1] ) {
		    $buffer[1] = join ' ', $buffer[1], $arg;
		  } else {
		    $buffer[1] = $arg;
		  }
		}
		push @output_modes, { prefix => $server, command => 'MODE', params => [ $chanrec->{name}, $buffer[0], split /\s+/, $buffer[1] ], colonify => 0 };
		$self->{ircd}->send_output( $_, values %{ $common } ) for @output_modes;
	     }
	     my $origmode = $chanrec->{mode};
	     my @args = @{ $args }[2..$#{ $args }];
	     my $chanmode = shift @args;
	     my $reply = ''; my @reply_args;
	     foreach my $mode ( grep { $_ ne '+' } split //, $chanmode ) {
		my $arg;
		$arg = shift @args if $mode =~ /[lk]/;
		if ( $mode eq 'l' and ( $chanrec->{mode} !~ /l/ or $arg ne $chanrec->{climit} ) ) {
		  $reply .= '+' . $mode;
		  push @reply_args, $arg;
		  $chanrec->{mode} .= $mode unless $chanrec->{mode} =~ /$mode/;
		  $chanrec->{mode} = join '', sort split //, $chanrec->{mode};
		  $chanrec->{climit} = $arg;
		} elsif ( $mode eq 'k' and ( $chanrec->{mode} !~ /k/ or $arg ne $chanrec->{ckey} ) ) {
		  $reply .= '+' . $mode;
		  push @reply_args, $arg;
		  $chanrec->{mode} .= $mode unless $chanrec->{mode} =~ /$mode/;
		  $chanrec->{mode} = join '', sort split //, $chanrec->{mode};
		  $chanrec->{ckey} = $arg;
		} elsif ( $chanrec->{mode} !~ /$mode/ ) {
		  $reply .= '+' . $mode;
		  $chanrec->{mode} .= $mode unless $chanrec->{mode} =~ /$mode/;
		  $chanrec->{mode} = join '', sort split //, $chanrec->{mode};
		}
	     }
	     if ( scalar keys %{ $common } and ( $reply or $origmode ) ) {
		$origmode = join '', grep { $chanmode !~ /$_/ } split //, ( $origmode || '' );
		$chanrec->{mode} =~ s/[$origmode]//g if $origmode;
		$reply = '-' . $origmode . $reply if $origmode;
		if ( $origmode and $origmode =~ /k/ ) {
		  unshift @reply_args, '*';
		  delete $chanrec->{ckey};
		}
		delete $chanrec->{climit} if $origmode and $origmode =~ /l/;
		$self->{ircd}->send_output( { prefix => $self->server_name(), command => 'MODE', params => [ $chanrec->{name}, unparse_mode_line( $reply ), @reply_args ], colonify => 0 }, values %{ $common } ) if $reply;
	     }
	     # NOTICE HERE
	     $self->{ircd}->send_output( { prefix => $self->server_name(), command => 'NOTICE', params => [ $chanrec->{name}, "*** Notice -- TS for " . $chanrec->{name} . " changed from " . $chanrec->{ts} . " to $ts" ] }, @local_users );
	     $chanrec->{ts} = $ts;
	  } elsif ( scalar grep { /^\@/ } $self->state_chan_list_prefixed( $chan ) ) {
	     $args->[0] = $chanrec->{ts};
	  } else {
	     # NOTICE HERE
	     $self->{ircd}->send_output( { prefix => $self->server_name(), command => 'NOTICE', params => [ $chanrec->{name}, "*** Notice -- TS for " . $chanrec->{name} . " changed from " . $chanrec->{ts} . " to $ts" ] }, @local_users );
	     $chanrec->{ts} = $ts;
	  }
    } elsif ( $ts > $chanrec->{ts} ) {
	  # Incoming is younger
	  if ( $nicks !~ /^\@/ ) {
	    $args->[0] = $chanrec->{ts};
	  } elsif ( scalar grep { /^\@/ } $self->state_chan_list_prefixed( $chan ) ) {
	    pop @{ $args } while $#{ $args } > 2;
	    $args->[2] = '+';
	    $args->[0] = $chanrec->{ts};
	    $nicks = join ' ', map { s/[@%+]//g; $_; } split /\s+/, $nicks;
	  } else {
	     $chanrec->{ts} = $ts;
	  }
    }
    # Propagate SJOIN to connected peers except the one that told us.
    push @{ $args }, $nicks;
    $self->{ircd}->send_output( { prefix => $prefix, command => 'SJOIN', params => $args }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
    # Generate appropriate JOIN messages for all local channel members
    my $uchan = u_irc $chanrec->{name};
    #my @local_users = map { $self->_state_user_route($_) } grep { $self->_state_is_local_user($_) } keys %{ $chanrec->{users} };
    my $modes; my @mode_parms;
    foreach my $nick ( split /\s+/, $nicks ) {
	  my $proper = $nick;
	  $proper =~ s/[@%+]//g;
	  $nick = u_irc $nick;
	  my $umode = ''; my @op_list;
	  $umode .= 'o' if $nick =~ s/\@//g;
	  $umode = 'h' if $nick =~ s/\%//g;
	  $umode .= 'v' if $nick =~ s/\+//g;
	  $chanrec->{users}->{ $nick } = $umode;
	  $self->{state}->{users}->{ $nick }->{chans}->{ $uchan } = $umode;
          push @op_list, $proper for split //, $umode;
	  my $output = { prefix => $self->state_user_full( $nick ), command => 'JOIN', params => [ $chanrec->{name} ] };
	  $self->{ircd}->send_output( $output, @local_users );
	  $self->{ircd}->send_event( "daemon_join", $output->{prefix}, $chanrec->{name} );
	  if ( $umode ) {
		$modes .= $umode;
		push @mode_parms, @op_list;
	  }
    }
    if ( $modes ) {
      my $server = $self->server_name();
      $self->{ircd}->send_event( "daemon_mode", $server, $chanrec->{name}, '+' . $modes, @mode_parms );
      my @output_modes;
      my $length = length($server) + 4 + length($chan) + 4;
      my @buffer = ( '+', '' );
      foreach my $umode ( split //, $modes ) {
	my $arg = shift @mode_parms;
	if ( length( join ' ', @buffer, $arg ) + $length > 510 ) {
	  push @output_modes, { prefix => $server, command => 'MODE', params => [ $chanrec->{name}, $buffer[0], split /\s+/, $buffer[1] ], colonify => 0 };
	  $buffer[0] = "+$umode";
	  $buffer[1] = $arg;
	  next;
	}
	$buffer[0] .= $umode;
	if ( $buffer[1] ) {
	  $buffer[1] = join ' ', $buffer[1], $arg;
	} else {
	  $buffer[1] = $arg;
	}
      }
      push @output_modes, { prefix => $server, command => 'MODE', params => [ $chanrec->{name}, $buffer[0], split /\s+/, $buffer[1] ], colonify => 0 };
      $self->{ircd}->send_output( $_, @local_users ) for @output_modes;
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_mode {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $chan = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$self->state_chan_exists( $chan ) ) {
	last SWITCH;
    }
    my $record = $self->{state}->{chans}->{ u_irc $chan };
    $chan = $record->{name};
    my $full;
    $full = $self->state_user_full( $nick ) if $self->state_nick_exists( $nick );
    my $reply; my @reply_args;
    my $parsed_mode = parse_mode_line( @{ $args } );
    while( my $mode = shift ( @{ $parsed_mode->{modes} } ) ) {
      my $arg;
      $arg = shift ( @{ $parsed_mode->{args} } ) if ( $mode =~ /^(\+[ohvklbIe]|-[ohvbIe])/ );
      if ( my ($flag,$char) = $mode =~ /^(\+|-)([ohv])/ ) {
	if ( $flag eq '+' and $record->{users}->{ u_irc $arg } !~ /$char/ ) {
	  # Update user and chan record
	  $arg = u_irc $arg;
	  next if ( $mode eq '+h' and $record->{users}->{ $arg } =~ /o/ );
	  if ( $char eq 'h' and $record->{users}->{ $arg } =~ /v/ ) {
	     $record->{users}->{ $arg } =~ s/v//g;
	     $reply .= '-v';
	     push @reply_args, $self->state_user_nick( $arg );
	  }
	  if ( $char eq 'o' and $record->{users}->{ $arg } =~ /h/ ) {
	     $record->{users}->{ $arg } =~ s/h//g;
	     $reply .= '-h';
	     push @reply_args, $self->state_user_nick( $arg );
	  }
	  $record->{users}->{ $arg }  = join('', sort split //, $record->{users}->{ $arg } . $char );
	  $self->{state}->{users}->{ $arg }->{chans}->{ u_irc $chan } = $record->{users}->{ $arg };
	  $reply .= "+$char";
	  push @reply_args, $self->state_user_nick( $arg );
        }
	if ( $flag eq '-' and $record->{users}->{ u_irc $arg } =~ /$char/ ) {
	  # Update user and chan record
	  $arg = u_irc $arg;
	  $record->{users}->{ $arg } =~ s/$char//g;
	  $self->{state}->{users}->{ $arg }->{chans}->{ u_irc $chan } = $record->{users}->{ $arg };
	  $reply .= "-$char";
	  push @reply_args, $self->state_user_nick( $arg );
        }
	next;
      }
      if ( $mode eq '+l' and $arg =~ /^\d+$/ and $arg > 0 ) {
	$record->{mode} = join('', sort split //, $record->{mode} . 'l' ) unless $record->{mode} =~ /l/;
	$record->{climit} = $arg;
	$reply .= '+l';
	push @reply_args, $arg;
	next;
      }
      if ( $mode eq '-l' and $record->{mode} =~ /l/ ) {
	$record->{mode} =~ s/l//g;
	delete $record->{climit};
	$reply .= '-l';
	next;
      }
      if ( $mode eq '+k' and $arg ) {
	$record->{mode} = join('', sort split //, $record->{mode} . 'k' ) unless $record->{mode} =~ /k/;
	$record->{ckey} = $arg;
	$reply .= '+k';
	push @reply_args, $arg;
	next;
      }
      if ( $mode eq '-k' and $record->{mode} =~ /k/ ) {
	$record->{mode} =~ s/k//g;
	delete $record->{ckey};
	$reply .= '-k';
	next;
      }
      # Bans
      if ( my ($flag) = $mode =~ /(\+|-)b/ ) {
	my $mask = parse_ban_mask( $arg );
	my $umask = u_irc $mask;
	if ( $flag eq '+' and !$record->{bans}->{ $umask } ) {
	  $record->{bans}->{ $umask } = [ $mask, ( $full || $server ), time() ];
	  $reply .= '+b';
	  push @reply_args, $mask;
	}
	if ( $flag eq '-' and $record->{bans}->{ $umask } ) {
	  delete $record->{bans}->{ $umask };
	  $reply .= '-b';
	  push @reply_args, $mask;
	}
	next;
      }
      # Invex
      if ( my ($flag) = $mode =~ /(\+|-)I/ ) {
	my $mask = parse_ban_mask( $arg );
	my $umask = u_irc $mask;
	if ( $flag eq '+' and !$record->{invex}->{ $umask } ) {
	   $record->{invex}->{ $umask } = [ $mask, ( $full || $server ), time() ];
	  $reply .= '+I';
	  push @reply_args, $mask;
	}
	if ( $flag eq '-' and $record->{invex}->{ $umask } ) {
	  delete $record->{invex}->{ $umask };
	  $reply .= '-I';
	  push @reply_args, $mask;
	}
	next;
      }
      # Exceptions
      if ( my ($flag) = $mode =~ /(\+|-)e/ ) {
	my $mask = parse_ban_mask( $arg );
	my $umask = u_irc $mask;
	if ( $flag eq '+' and !$record->{excepts}->{ $umask } ) {
	  $record->{excepts}->{ $umask } = [ $mask, ( $full || $server ), time() ];
	  $reply .= '+e';
	  push @reply_args, $mask;
	}
	if ( $flag eq '-' and $record->{excepts}->{ $umask } ) {
	  delete $record->{excepts}->{ $umask };
	  $reply .= '-e';
	  push @reply_args, $mask;
	}
	next;
      }
      # The rest should be argumentless.
      my ($flag,$char) = split //, $mode;
      if ( $flag eq '+' and $record->{mode} !~ /$char/ ) {
	$record->{mode} = join('', sort split //, $record->{mode} . $char );
	$reply .= "+$char";
	next;
      }
      if ( $flag eq '-' and $record->{mode} =~ /$char/ ) {
	$record->{mode} =~ s/$char//g;
	$reply .= "-$char";
	next;
      }
    } # while
    unshift @{ $args }, $record->{name};
    if ( $reply ) {
      my $parsed_line = unparse_mode_line $reply;
      $self->{ircd}->send_output( { prefix => $nick, command => 'MODE', params => [ $record->{name}, $parsed_line, @reply_args ], colonify => 0 }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
      $self->{ircd}->send_output( { prefix => ( $full || $server ), command => 'MODE', params => [ $record->{name}, $parsed_line, @reply_args ], colonify => 0 }, map { $self->_state_user_route($_) } grep { $self->_state_is_local_user($_) } keys %{ $record->{users} } );
      $self->{ircd}->send_event( "daemon_mode", ( $full || $server ), $record->{name}, $parsed_line, @reply_args );
    }
  } # SWITCH
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_umode {
  my $self = shift;
  my $peer_id = shift || return;
  my $prefix = shift || return;
  my $nick = shift || return;
  my $umode = shift;
  my $server = $self->server_name();
  my $ref = [ ];
  my $record = $self->{state}->{users}->{ u_irc $nick };
  my $parsed_mode = parse_mode_line( $umode );
  while ( my $mode = shift @{ $parsed_mode->{modes} } ) {
	my ($action,$char) = split //, $mode;
	if ( $action eq '+' and $record->{umode} !~ /$char/ ) {
	  $record->{umode} .= $char;
	  $self->{state}->{stats}->{invisible}++ if $char eq 'i';
          if ( $char eq 'o' ) {
    	    $self->{state}->{stats}->{ops_online}++;
          }
	}
	if ( $action eq '-' and $record->{umode} =~ /$char/ ) {
	  $record->{umode} =~ s/$char//g;
	  $self->{state}->{stats}->{invisible}-- if $char eq 'i';
          if ( $char eq 'o' ) {
    	    $self->{state}->{stats}->{ops_online}--;
          }
	}
  }
  $self->{ircd}->send_output( { prefix => $prefix, command => 'MODE', params => [ $nick, $umode ] }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
  $self->{ircd}->send_event( "daemon_umode", $self->state_user_full( $nick ), $umode );
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_message {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $type = shift || return;
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count ) {
	push @{ $ref }, [ '461', $type ];
	last SWITCH;
    }
    if ( $count < 2 or !$args->[1] ) {
	push @{ $ref }, [ '412' ];
	last SWITCH;
    }
    my $targets = 0;
    my $max_targets = $self->server_config('MAXTARGETS');
    my $full = $self->state_user_full( $nick );
    my $targs = $self->_state_parse_msg_targets( $args->[0] );
    LOOP: foreach my $target ( keys %{ $targs } ) {
	my $targ_type = shift @{ $targs->{$target} };
	if ( $targ_type =~ /(server|host)mask/ and !$self->state_user_is_operator( $nick ) ) {
	   push @{ $ref }, [ '481' ];
	   next LOOP;
	}
	if ( $targ_type =~ /(server|host)mask/ and $targs->{$target}->[0] !~ /\./ ) {
	  push @{ $ref }, [ '413', $target ];
	  next LOOP;
	}
	if ( $targ_type =~ /(server|host)mask/ and $targs->{$target}->[0] !~ /\x2E.*[\x2A\x3F]+.*$/ ) {
	  push @{ $ref }, [ '414', $target ];
	  next LOOP;
	}
	if ( $targ_type eq 'channel_ext' and !$self->state_chan_exists( $targs->{$target}->[1] ) ) {
	   push @{ $ref }, [ '401', $targs->{$target}->[1] ];
	   next LOOP;
	}
	if ( $targ_type eq 'channel' and !$self->state_chan_exists( $target ) ) {
	   push @{ $ref }, [ '401', $target ];
	   next LOOP;
	}
	if ( $targ_type eq 'nick' and !$self->state_nick_exists( $target ) ) {
	   push @{ $ref }, [ '401', $target ];
	   next LOOP;
	}
	if ( $targ_type eq 'nick_ext' and !$self->state_peer_exists( $targs->{$target}->[1] ) ) {
	   push @{ $ref }, [ '402', $targs->{$target}->[1] ];
	   next LOOP;
	}
	$targets++;
        if ( $targets > $max_targets ) {
	  push @{ $ref }, [ '407', $target ];
	  last SWITCH;
        }
	# $$whatever
	if ( $targ_type eq 'servermask' ) {
	  my $us = 0;
          my %targets;
	  my $ucserver = uc $self->server_name();
          foreach my $peer ( keys %{ $self->{state}->{peers} } ) {
	    if ( matches_mask( $targs->{$target}->[0], $peer ) ) {
	      if ( $ucserver eq $peer ) {
		$us = 1;
	      } else {
		$targets{ $self->_state_peer_route( $peer ) }++;
	      }
	    }
          }
	  delete $targets{ $peer_id };
	  $self->{ircd}->send_output( { prefix => $nick, command => $type, params => [ $target, $args->[1] ] }, keys %targets );
	  if ( $us ) {
	    my $local = $self->{state}->{peers}->{ uc $self->server_name() }->{users};
	    my @local; my $spoofed = 0;
	    foreach my $luser ( values %{ $local } ) {
		if ( $luser->{route_id} eq 'spoofed' ) {
		  $spoofed = 1;
		} else {
		  push @local, $luser->{route_id};
		}
	    }
	    $self->{ircd}->send_output( { prefix => $full, command => $type, params => [ $target, $args->[1] ] }, @local );
  	    $self->{ircd}->send_event( "daemon_" . lc $type, $full, $target, $args->[1] ) if $spoofed;
	  }
	  next LOOP;
	}
	# $#whatever
	if ( $targ_type eq 'hostmask' ) {
	  my $spoofed = 0;
	  my %targets; my @local;
	  HOST: foreach my $luser ( values %{ $self->{state}->{users} } ) {
	     next HOST unless matches_mask( $targs->{$target}->[0], $luser->{auth}->{hostname} );
	     if ( $luser->{route_id} eq 'spoofed' ) {
		$spoofed = 1;
	     } elsif ( $luser->{type} eq 'r' ) {
		$targets{ $luser->{route_id} }++;
	     } else {
		push @local, $luser->{route_id};
	     }
	  }
	  delete $targets{ $peer_id };
	  $self->{ircd}->send_output( { prefix => $nick, command => $type, params => [ $target, $args->[1] ] }, keys %targets );
	  $self->{ircd}->send_output( { prefix => $full, command => $type, params => [ $target, $args->[1] ] }, @local );
  	  $self->{ircd}->send_event( "daemon_" . lc $type, $full, $target, $args->[1] ) if $spoofed;
	  next LOOP;
	}
	if ( $targ_type eq 'nick_ext' ) {
	  $targs->{$target}->[1] = $self->_state_peer_name( $targs->{$target}->[1] );
	  if ( $targs->{$target}->[2] and !$self->state_user_is_operator( $nick ) ) {
	    push @{ $ref }, [ '481' ];
	    next LOOP;
	  }
	  if ( $targs->{$target}->[1] ne $self->server_name() ) {
	    $self->{ircd}->send_output( { prefix => $nick, command => $type, params => [ $target, $args->[1] ] }, $self->_state_peer_route( $targs->{$target}->[1] ) );
	    next LOOP;
	  }
	  if ( uc ( $targs->{$target}->[0] ) eq 'OPERS' ) {
	    unless ( $self->state_user_is_operator( $nick ) ) {
	      push @{ $ref }, [ '481' ];
	      next LOOP;
	    }
	    $self->{ircd}->send_output( { prefix => $full, command => $type, params => [ $target, $args->[1] ] }, keys %{ $self->{state}->{localops} } );
	    next LOOP;
	  }
	  my @local = $self->_state_find_user_host( $targs->{$target}->[0], $targs->{$target}->[2] );
	  if ( scalar @local == 1 ) {
	      my $ref = shift @local;
	      if ( $ref->[0] eq 'spoofed' ) {
	        $self->{ircd}->send_event( "daemon_" . lc $type, $full, $ref->[1], $args->[1] );
	      } else {
	        $self->{ircd}->send_output( { prefix => $full, command => $type, params => [ $target, $args->[1] ] }, $ref->[0] );
	      }
	  } else {
	      push @{ $ref }, [ '407', $target ];
	      next LOOP;
	  }
	}
	my $channel; my $status_msg;
	if ( $targ_type eq 'channel' ) {
	  $channel = $self->_state_chan_name( $target );
	}
	if ( $targ_type eq 'channel_ext' ) {
	  $channel = $self->_state_chan_name( $targs->{target}->[1] );
	  $status_msg = $targs->{target}->[0];
	}
	if ( $channel and $status_msg and !$self->state_user_chan_mode( $nick, $channel ) ) {
	  push @{ $ref }, [ '482', $target ];
	  next LOOP;
	}
	if ( $channel and $self->state_chan_mode_set( $channel, 'n' ) and !$self->state_is_chan_member( $nick, $channel ) ) {
	  push @{ $ref }, [ '404', $channel ];
	  next LOOP;
	}
	if ( $channel and $self->state_chan_mode_set( $channel, 'm' ) and !$self->state_user_chan_mode( $nick, $channel ) ) {
	  push @{ $ref }, [ '404', $channel ];
	  next LOOP;
	}
	if ( $channel and $self->_state_user_banned( $nick, $channel ) and !$self->state_user_chan_mode( $nick, $channel ) ) {
	  push @{ $ref }, [ '404', $channel ];
	  next LOOP;
	}
	if ( $channel ) {
	  my $common = { };
	  my $msg  = { command => $type, params => [ ( $status_msg ? $target : $channel ), $args->[1] ] };
	  foreach my $member ( $self->state_chan_list( $channel, $status_msg ) ) {
		next if $self->_state_user_is_deaf( $member );
		$common->{ $self->_state_user_route( $member ) }++;
	  }
	  delete $common->{ $peer_id };
	  foreach my $route_id ( keys %{ $common } ) {
		$msg->{prefix} = $nick;
		$msg->{prefix} = $full if $self->_connection_is_client( $route_id );
		unless ( $route_id eq 'spoofed' ) {
	  	  $self->{ircd}->send_output( $msg, $route_id );
		} else {
		  my $tmsg = $type eq 'PRIVMSG' ? 'public' : 'notice';
		  $self->{ircd}->send_event( "daemon_$tmsg", $full, $channel, $args->[1] );
		}
	  }
	  next LOOP;
	}
	my $server = $self->server_name();
	if ( $self->state_nick_exists( $target ) ) {
	  $target = $self->state_user_nick( $target );
	  if ( my $away = $self->_state_user_away_msg( $target ) ) {
	     push @{ $ref }, { prefix => $server, command => '301', params => [ $nick, $target, $away ] };
	  }
	  my $targ_umode = $self->state_user_umode( $target );
	  # Target user has CALLERID on
	  if ( $targ_umode and $targ_umode =~ /[Gg]/ ) {
	     my $targ_rec = $self->{state}->{users}->{ u_irc $target };
	     if ( ( $targ_umode =~ /G/ and ( !$self->state_users_share_chan( $target, $nick ) or !$targ_rec->{accepts}->{ u_irc $nick } ) ) or ( $targ_umode =~ /g/ and !$targ_rec->{accepts}->{ u_irc $nick } ) ) {
		push @{ $ref }, { prefix => $server, command => '716', params => [ $nick, $target, 'is in +g mode (server side ignore)' ] };
		if ( !$targ_rec->{last_caller} or ( time() - $targ_rec->{last_caller} ) >= 60 ) {
		   my ($n,$uh) = split /!/, $self->state_user_full( $nick );
		   $self->{ircd}->send_output( { prefix => $server, command => '718', params => [ $target, "$n\[$uh\]", 'is messaging you, and you are umode +g.'] }, $targ_rec->{route_id} ) unless $targ_rec->{route_id} eq 'spoofed';
		   push @{ $ref }, { prefix => $server, command => '717', params => [ $nick, $target, 'has been informed that you messaged them.' ] };
		}
		$targ_rec->{last_caller} = time();
		next LOOP;
	     }
	  }
	  my $msg = { prefix => $nick, command => $type, params => [ $target, $args->[1] ] };
	  my $route_id = $self->_state_user_route( $target );
	  if ( $route_id eq 'spoofed' ) {
	     $msg->{prefix} = $full;
	     $self->{ircd}->send_event( "daemon_" . lc $type, $full, $target, $args->[1] );
	  } else {
	     $msg->{prefix} = $full if $self->_connection_is_client( $route_id );
	     $self->{ircd}->send_output( $msg, $route_id );
	  }
	  next LOOP;
	}
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_topic {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH:{
    if ( !$count ) {
	last SWITCH;
    }
    if ( !$self->state_chan_exists( $args->[0] ) ) {
	last SWITCH;
    }
    my $chan_name = $self->_state_chan_name( $args->[0] );
    my $record = $self->{state}->{chans}->{ u_irc $args->[0] };
    $record->{topic} = [ $args->[1], $self->state_user_full( $nick ), time() ];
    $self->_send_output_to_channel( $args->[0], { prefix => $self->state_user_full( $nick ), command => 'TOPIC', params => [ $chan_name, $args->[1] ] }, $peer_id );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_invite {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count or $count < 3 ) {
	last SWITCH;
    }
    my ($who,$chan) = @{ $args };
    $who = $self->state_user_nick( $who );
    $chan = $self->_state_chan_name( $chan );
    my $local;
    if ( $self->_state_is_local_user( $who ) ) {
	my $record = $self->{state}->{users}->{ u_irc $who };
	$record->{invites}->{ u_irc $chan } = time();
        $local = 1;
    }
    my $route_id = $self->_state_user_route( $who );
    my $output = { prefix => $self->state_user_full( $nick ), command => 'INVITE', params => [ $who, $chan ], colonify => 0 };
    if ( $route_id eq 'spoofed' ) {
	$self->{ircd}->send_event( "daemon_invite", $output->{prefix}, @{ $output->{params} } );
    } else {
	unless ( $local ) {
	  $output->{prefix} = $nick;
	  push @{ $output->{params} }, $args->[2];
	}
    	$self->{ircd}->send_output( $output, $route_id );
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _daemon_peer_away {
  my $self = shift;
  my $peer_id = shift || return;
  my $nick = shift || return;
  my $msg = shift;
  my $server = $self->server_name();
  my $ref = [ ];
  SWITCH: {
     my $record = $self->{state}->{users}->{ u_irc $nick };
     if ( !$msg ) {
	delete $record->{away};
        $self->{ircd}->send_output( { prefix => $nick, command => 'AWAY', colonify => 0 }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
	last SWITCH;
     }
     $record->{away} = $msg;
     $self->{ircd}->send_output( { prefix => $nick, command => 'AWAY', params => [ $msg ], colonify => 0 }, grep { $_ ne $peer_id } $self->_state_connected_peers() );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

#################
# State methods #
#################

sub _state_create {
  my $self = shift;

  $self->_state_delete();
  # Connection specific tables
  $self->{state}->{conns} = { };
  # IRC State specific
  $self->{state}->{users} = { };
  $self->{state}->{peers} = { };
  $self->{state}->{chans} = { };
  # Register ourselves as a peer.
  $self->{state}->{peers}->{ uc $self->server_name() } = { name => $self->server_name(), hops => 0, desc => $self->{config}->{SERVERDESC} };
  $self->{state}->{stats} = {
				maxconns   => 0,
				maxlocal   => 0,
				maxglobal  => 0,
				ops_online => 0,
				invisible  => 0,
				cmds       => { },
  			    };
  return 1;
}

sub _state_delete {
  my $self = shift;
  delete $self->{state};
  return 1;
}

sub _state_update_stats {
  my $self = shift;
  my $server = $self->server_name();
  my $global = scalar keys %{ $self->{state}->{users} };
  my $local = scalar keys %{ $self->{state}->{peers}->{ uc $server }->{users} };
  $self->{state}->{stats}->{maxglobal} = $global if $global > $self->{state}->{stats}->{maxglobal};
  $self->{state}->{stats}->{maxlocal} = $local if $local > $self->{state}->{stats}->{maxlocal};
  return 1;
}

sub _state_conn_stats {
  my $self = shift;
  $self->{state}->{stats}->{conns_cumlative}++;
  my $conns = scalar keys %{ $self->{state}->{conns} };
  $self->{state}->{stats}->{maxconns} = $conns if $conns > $self->{state}->{stats}->{maxconns};
  return 1;
}

sub _state_cmd_stat {
  my $self = shift;
  my $cmd = shift || return;
  my $line = shift || return;
  my $remote = shift;
  my $record = $self->{state}->{stats}->{cmds}->{ $cmd } || { remote => 0, local => 0, bytes => 0 };
  $record->{local}++ unless $remote;
  $record->{remote}++ if $remote;
  $record->{bytes} += length $line;
  $self->{state}->{stats}->{cmds}->{ $cmd } = $record;
  return 1;
}

sub _state_find_user_host {
  my $self = shift;
  my $luser = shift || return;
  my $host = shift || '*';
  my $local = $self->{state}->{peers}->{ uc $self->server_name() }->{users};
  my @conns;
  foreach my $user ( values %{ $local } ) {
    push @conns, [ $user->{route_id}, $user->{nick} ] if matches_mask( $host, $user->{auth}->{hostname} ) and matches_mask( $luser, $user->{auth}->{ident} );
  }
  return @conns;
}

sub _state_local_users_match_rkline {
  my $self = shift;
  my $luser = shift || return;
  my $host = shift || return;
  my $local = $self->{state}->{peers}->{ uc $self->server_name() }->{users};
  my @conns;
  foreach my $user ( values %{ $local } ) {
	next if $user->{route_id} eq 'spoofed';
	next if $user->{umode} and $user->{umode} =~ /o/;
	eval {
	   push @conns, $user->{route_id} if ( $user->{socket}->[0] =~ /$host/ or $user->{auth}->{hostname} =~ /$host/ ) and $user->{auth}->{ident} =~ /$luser/;
	};
  }
  return @conns;
}

sub _state_local_users_match_gline {
  my $self = shift;
  my $luser = shift || return;
  my $host = shift || return;
  my $local = $self->{state}->{peers}->{ uc $self->server_name() }->{users};
  my @conns;
  if ( my $netmask = Net::Netmask->new2($host) ) {
    foreach my $user ( values %{ $local } ) {
	next if $user->{route_id} eq 'spoofed';
	next if $user->{umode} and $user->{umode} =~ /o/;
	push @conns, $user->{route_id} if $netmask->match($user->{socket}->[0]) and matches_mask( $luser, $user->{auth}->{ident} );
    }
  } else {
    foreach my $user ( values %{ $local } ) {
	next if $user->{route_id} eq 'spoofed';
	next if $user->{umode} and $user->{umode} =~ /o/;
	push @conns, $user->{route_id} if ( matches_mask( $host, $user->{socket}->[0] ) or matches_mask( $host, $user->{auth}->{hostname} ) ) and matches_mask( $luser, $user->{auth}->{ident} );
    }
  }
  return @conns;
}

sub _state_user_matches_rkline {
  my $self = shift;
  my $conn_id = shift || return;
  my $record = $self->{state}->{conns}->{ $conn_id };
  my $host = $record->{auth}->{hostname} || $record->{socket}->[0];
  my $user = $record->{auth}->{ident} || "~" . $record->{user};
  my $ip = $record->{socket}->[0];
  foreach my $gline ( @{ $self->{state}->{rklines} } ) {
	eval {
	   return 1 if ( $host =~ /$gline->{host}/ or $ip =~ /$gline->{host}/ ) and $user =~ /$gline->{user}/;
	};
  }
  return 0;
}

sub _state_user_matches_kline {
  my $self = shift;
  my $conn_id = shift || return;
  my $record = $self->{state}->{conns}->{ $conn_id };
  my $host = $record->{auth}->{hostname} || $record->{socket}->[0];
  my $user = $record->{auth}->{ident} || "~" . $record->{user};
  my $ip = $record->{socket}->[0];
  foreach my $gline ( @{ $self->{state}->{klines} } ) {
	if ( my $netmask = Net::Netmask->new2($gline->{host}) ) {
	   return 1 if $netmask->match($ip) and matches_mask( $gline->{user}, $user );
	} else {
	   return 1 if ( matches_mask( $gline->{host}, $host ) or matches_mask( $gline->{host}, $ip ) ) and matches_mask( $gline->{user}, $user );
	}
  }
  return 0;
}

sub _state_user_matches_gline {
  my $self = shift;
  my $conn_id = shift || return;
  my $record = $self->{state}->{conns}->{ $conn_id };
  my $host = $record->{auth}->{hostname} || $record->{socket}->[0];
  my $user = $record->{auth}->{ident} || "~" . $record->{user};
  my $ip = $record->{socket}->[0];
  foreach my $gline ( @{ $self->{state}->{glines} } ) {
	if ( my $netmask = Net::Netmask->new2($gline->{host}) ) {
	   return 1 if $netmask->match($ip) and matches_mask( $gline->{user}, $user );
	} else {
	   return 1 if ( matches_mask( $gline->{host}, $host ) or matches_mask( $gline->{host}, $ip ) ) and matches_mask( $gline->{user}, $user );
	}
  }
  return 0;
}

sub _state_auth_client_conn {
  my $self = shift;
  my $conn_id = shift || return;
  return 1 unless $self->{config}->{auth} and scalar @{ $self->{config}->{auth} };
  my $record = $self->{state}->{conns}->{ $conn_id };
  my $host = $record->{auth}->{hostname} || $record->{socket}->[0];
  my $user = $record->{auth}->{ident} || "~" . $record->{user};
  my $uh = join '@', $user, $host;
  my $ui = join '@', $user, $record->{socket}->[0];
  foreach my $auth ( @{ $self->{config}->{auth} } ) {
	if ( matches_mask( $auth->{mask}, $uh ) or matches_mask( $auth->{mask}, $ui ) ) {
	  return 0 if $auth->{password} and ( !$record->{pass} or $auth->{password} ne $record->{pass} );
	  $record->{auth}->{hostname} = $auth->{spoof} if $auth->{spoof};
	  $record->{auth}->{ident} = $record->{user} if !$record->{auth}->{ident} and $auth->{no_tilde};
	  return 1;
	}
  }
  return 0;
}

sub _state_auth_peer_conn {
  my $self = shift;
  my ($conn_id,$name,$pass) = @_;
  return unless $conn_id and $self->_connection_exists( $conn_id );
  return unless $name and $pass;
  my $peers = $self->{config}->{peers};
  return 0 unless $peers->{ uc $name } or $peers->{ uc $name }->{pass} ne $pass;
  my $conn = $self->{state}->{conns}->{ $conn_id };
  return 1 if !$peers->{ uc $name }->{ipmask} and $conn->{socket}->[0] =~ /^127\./;
  return 0 unless $peers->{ uc $name }->{ipmask};
  my $client_ip = $conn->{socket}->[0];
  if ( ref $peers->{ uc $name }->{ipmask} eq 'ARRAY' ) {
    foreach my $block ( grep { $_->isa('Net::Netmask') } @{ $peers->{ uc $name }->{ipmask} } ) {
    	return 1 if $block->match( $client_ip );
    }
  }
  return 1 if matches_mask( $peers->{ uc $name }->{ipmask}, $client_ip );
  return 0;
}

sub _state_send_credentials {
  my $self = shift;
  my $conn_id = shift || return;
  my $name = shift || return;
  return unless $self->_connection_exists( $conn_id );
  return unless $self->{config}->{peers}->{ uc $name };
  my $peer = $self->{config}->{peers}->{ uc $name };
  $self->{ircd}->send_output( { command => 'PASS', params => [ $peer->{rpass}, 'TS' ] }, $conn_id );
  $self->{ircd}->send_output( { command => 'CAPAB', params => [ join ( ' ', @{ $self->{config}->{capab} }, ( $peer->{zip} ? 'ZIP' : () ) ) ] }, $conn_id );
  my $rec = $self->{state}->{peers}->{ uc $self->server_name() };
  $self->{ircd}->send_output( { command => 'SERVER', params => [ $rec->{name}, $rec->{hops} + 1, $rec->{desc} ] }, $conn_id );
  $self->{ircd}->send_output( { command => 'SVINFO', params => [ 5, 5, 0, time() ] }, $conn_id );
  $self->{state}->{conns}->{ $conn_id }->{zip} = $peer->{zip};
  return 1;
}

sub _state_send_burst {
  my $self = shift;
  my $conn_id = shift || return;
  return unless $self->_connection_exists( $conn_id );
  my $server = $self->server_name();
  my $conn = $self->{state}->{conns}->{ $conn_id };
  my $burst = scalar grep { /^EOB$/i } @{ $conn->{capab} };
  my $invex = scalar grep { /^IE$/i } @{ $conn->{capab} };
  my $excepts = scalar grep { /^EX$/i } @{ $conn->{capab} };
  my %map = qw(bans b excepts e invex I);
  my @lists = qw(bans);
  push @lists, 'excepts' if $excepts;
  push @lists, 'invex' if $invex;
  # Send SERVER burst
  $self->{ircd}->send_output( $_, $conn_id ) for $self->_state_server_burst( $server, $conn->{name} );
  # Send NICK burst
  foreach my $nick ( keys %{ $self->{state}->{users} } ) {
    my $record = $self->{state}->{users}->{ $nick };
    next if $record->{route_id} eq $conn_id;
    my $umode_fixed = $record->{umode};
    $umode_fixed =~ s/[^aiow]//g;
    my $arrayref = [ $record->{nick}, $record->{hops} + 1, $record->{ts}, ( '+' . $umode_fixed ), $record->{auth}->{ident}, $record->{auth}->{hostname}, $record->{server}, $record->{ircname} ];
    $self->{ircd}->send_output( { command => 'NICK', params => $arrayref }, $conn_id );
  }
  # Send SJOIN+MODE burst
  foreach my $chan ( keys %{ $self->{state}->{chans} } ) {
    next if $chan =~ /^\&/;
    my $chanrec = $self->{state}->{chans}->{ $chan };
    my @nicks = map { $_->[1] }
		sort { $a->[0] cmp $b->[0] }
		map { my $w = $_; $w =~ tr/@%+/ABC/; [ $w, $_ ]; } $self->state_chan_list_prefixed( $chan );
    my $arrayref2 = [ $chanrec->{ts}, $chanrec->{name}, '+' . $chanrec->{mode}, ( $chanrec->{ckey} || () ), ( $chanrec->{climit} || () ), join ' ', @nicks ];
    $self->{ircd}->send_output( { prefix => $server, command => 'SJOIN', params => $arrayref2 }, $conn_id );
    # TODO: MODE burst
    # Banlist|Exceptions|Invex
    my @output_modes;
    OUTER: foreach my $type ( @lists ) {
      my $length = length($server) + 4 + length($chan) + 4;
      my @buffer = ( '', '' );
      INNER: foreach my $thing ( keys %{ $chanrec->{ $type } } ) {
	$thing = $chanrec->{ $type }->{ $thing }->[0];
        if ( length( join ' ', @buffer, $thing ) + $length + 1 > 510 ) {
	  $buffer[0] = '+' . $buffer[0];
	  push @output_modes, { prefix => $server, command => 'MODE', params => [ $chanrec->{name}, $buffer[0], split /\s+/, $buffer[1] ], colonify => 0 };
	  $buffer[0] = '+' . $map{$type};
	  $buffer[1] = $thing;
	  next INNER;
	}
        if ( $buffer[1] ) {
	  $buffer[0] .= $map{$type};
	  $buffer[1] = join ' ', $buffer[1], $thing;
	} else {
	  $buffer[0] = '+' . $map{$type};
	  $buffer[1] = $thing;
	}
      }
      push @output_modes, { prefix => $server, command => 'MODE', params => [ $chanrec->{name}, $buffer[0], split /\s+/, $buffer[1] ], colonify => 0 } if $buffer[0];
    }
    $self->{ircd}->send_output( $_, $conn_id ) for @output_modes;
  }
  $self->{ircd}->send_output( { prefix => $server, command => 'EOB' }, $conn_id ) if $burst;
  return 1;
}

sub _state_server_burst {
  my $self = shift;
  my $peer = shift || return;
  my $targ = shift || return;
  return unless $self->state_peer_exists( $peer ) and $self->state_peer_exists( $targ );
  my $ref = [ ];
  $peer = $self->_state_peer_name( $peer );
  my $upeer = uc $peer;
  my $utarg = uc $targ;
  foreach my $server ( keys %{ $self->{state}->{peers}->{ $upeer }->{peers} } ) {
	next if $server eq $utarg;
	my $rec = $self->{state}->{peers}->{ $server };
  	push @{ $ref }, { prefix => $peer, command => 'SERVER', params => [ $rec->{name}, $rec->{hops} + 1, $rec->{desc} ] };
	push @{ $ref }, $_ for $self->_state_server_burst( $rec->{name}, $targ );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _state_server_links {
  my $self = shift;
  my $peer = shift || return;
  my $orig = shift || return;
  my $nick = shift || return;
  return unless $self->state_peer_exists( $peer );
  my $ref = [ ];
  $peer = $self->_state_peer_name( $peer );
  my $upeer = uc $peer;
  foreach my $server ( keys %{ $self->{state}->{peers}->{ $upeer }->{peers} } ) {
	my $rec = $self->{state}->{peers}->{ $server };
	push @{ $ref }, $_ for $self->_state_server_links( $rec->{name}, $orig, $nick );
  	push @{ $ref }, { prefix => $orig, command => '364', params => [ $nick, $rec->{name}, $peer, join( ' ', $rec->{hops}, $rec->{desc} ) ] };
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub _state_peer_for_peer {
  my $self = shift;
  my $peer = shift || return;
  return unless $self->state_peer_exists( $peer );
  $peer = uc $peer;
  return $self->{state}->{peers}->{ $peer }->{peer};
}

sub _state_server_squit {
  my $self = shift;
  my $peer = shift || return;
  return unless $self->state_peer_exists( $peer );
  my $ref = [ ];
  my $upeer = uc $peer;
  push @{ $ref }, $_ for keys %{ $self->{state}->{peers}->{ $upeer }->{users} };
  foreach my $server ( keys %{ $self->{state}->{peers}->{ $upeer }->{peers} } ) {
    push @{ $ref }, $_ for $self->_state_server_squit( $server );
  }
  delete $self->{state}->{peers}->{ $upeer };
  delete $self->{state}->{peers}->{ uc $self->server_name() }->{peers}->{ $upeer };
  return @{ $ref } if wantarray();
  return $ref;
}

sub _state_register_peer {
  my $self = shift;
  my $conn_id = shift || return;
  return unless $self->_connection_exists( $conn_id );
  my $server = $self->server_name();
  my $record = $self->{state}->{conns}->{ $conn_id };
  $self->_state_send_credentials( $conn_id, $record->{name} ) unless $record->{cntr};
  $record->{burst} = $record->{registered} = 1;
  $record->{type} = 'p';
  $record->{route_id} = $conn_id;
  $record->{peer} = $server;
  $record->{users} = { };
  $record->{peers} = { };
  $self->{state}->{peers}->{ uc $server }->{peers}->{ uc $record->{name} } = $record;
  $self->{state}->{peers}->{ uc $record->{name} } = $record;
  $self->{ircd}->antiflood( $conn_id => 0 );
  $self->{ircd}->send_output( { prefix => $server, command => 'SERVER', params => [ $record->{name}, $record->{hops} + 1, $record->{desc} ] }, grep { $_ ne $conn_id } $self->_state_connected_peers() );
  $self->{ircd}->send_event( "daemon_server", $record->{name}, $server, $record->{hops}, $record->{desc} );
  return 1;
}

sub _state_register_client {
  my $self = shift;
  my $conn_id = shift || return;
  return unless $self->_connection_exists( $conn_id );
  my $record = $self->{state}->{conns}->{ $conn_id };
  $record->{server} = $self->server_name();
  $record->{hops} = 0;
  $record->{route_id} = $conn_id;
  $record->{umode} = '';
  $record->{_ignore_i_umode} = 1;
  $record->{ts} = $record->{idle_time} = $record->{conn_time} = time();
  $record->{auth}->{ident} = '~' . $record->{user} unless $record->{auth}->{ident};
  $record->{auth}->{hostname} = $self->server_name() if $record->{auth}->{hostname} eq 'localhost' or ( !$record->{auth}->{hostname} and $record->{socket}->[0] =~ /^127\./ );
  $record->{auth}->{hostname} = $record->{socket}->[0] unless $record->{auth}->{hostname};
  $self->{state}->{users}->{ u_irc $record->{nick} } = $record;
  $self->{state}->{peers}->{ uc $record->{server} }->{users}->{ u_irc $record->{nick} } = $record;
  my $arrayref = [ $record->{nick}, $record->{hops} + 1, $record->{ts}, '+i', $record->{auth}->{ident}, $record->{auth}->{hostname}, $record->{server}, $record->{ircname} ];
  delete $self->{state}->{pending}->{ u_irc $record->{nick} };
  $self->{ircd}->send_output( { command => 'NICK', params => $arrayref }, $self->_state_connected_peers() );
  $self->{ircd}->send_event( "daemon_nick", @{ $arrayref } );
  $self->_state_update_stats();
  return 1;
}

sub state_nicks {
  my $self = shift;
  return map { $self->{state}->{users}->{$_}->{nick} } keys %{ $self->{state}->{users} };
}

sub state_nick_exists {
  my $self = shift;
  my $nick = shift || return 1;
  $nick = u_irc $nick;
  return 0 unless defined $self->{state}->{users}->{ $nick } or defined $self->{state}->{pending}->{ $nick };
  return 1;
}

sub state_chans {
  my $self = shift;
  return map { $self->{state}->{chans}->{$_}->{name} } keys %{ $self->{state}->{chans} };
}

sub state_chan_exists {
  my $self = shift;
  my $chan = shift || return;
  return 0 unless defined $self->{state}->{chans}->{ u_irc $chan };
  return 1;
}

sub state_peers {
  my $self = shift;
  return map { $self->{state}->{peers}->{$_}->{name} } keys %{ $self->{state}->{peers} };
}

sub state_peer_exists {
  my $self = shift;
  my $peer = shift || return;
  return 0 unless defined $self->{state}->{peers}->{ uc $peer };
  return 1;
}

sub _state_peer_name {
  my $self = shift;
  my $peer = shift || return;
  return unless $self->state_peer_exists( $peer );
  return $self->{state}->{peers}->{ uc $peer }->{name};
}

sub _state_peer_desc {
  my $self = shift;
  my $peer = shift || return;
  return unless $self->state_peer_exists( $peer );
  return $self->{state}->{peers}->{ uc $peer }->{desc};
}

sub _state_peer_capab {
  my $self = shift;
  my $conn_id = shift || return;
  my $capab = shift || return;
  $capab = uc $capab;
  return unless $self->_connection_is_peer( $conn_id );
  my $conn = $self->{state}->{conns}->{ $conn_id };
  return scalar grep { $_ eq $capab } @{ $conn->{capab} };
}

sub state_user_full {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  my $record = $self->{state}->{users}->{ u_irc $nick };
  return $record->{nick} . '!' . $record->{auth}->{ident} . '@' . $record->{auth}->{hostname};
}

sub state_user_nick {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  return $self->{state}->{users}->{ u_irc $nick }->{nick};
}

sub _state_user_ip {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick ) and $self->_state_is_local_user( $nick );
  my $record = $self->{state}->{users}->{ u_irc $nick };
  return $record->{socket}->[0];
}

sub _state_user_away {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  return 1 if defined $self->{state}->{users}->{ u_irc $nick }->{away};
  return 0;
}

sub _state_user_away_msg {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  return $self->{state}->{users}->{ u_irc $nick }->{away};
}

sub state_user_umode {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  return $self->{state}->{users}->{ u_irc $nick }->{umode};
}

sub state_user_is_operator {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  return 0 unless $self->{state}->{users}->{ u_irc $nick }->{umode} =~ /o/;
  return 1;
}

sub _state_user_is_deaf {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  return 0 unless $self->{state}->{users}->{ u_irc $nick }->{umode} =~ /D/;
  return 1;
}

sub state_user_chans {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  my $record = $self->{state}->{users}->{ u_irc( $nick ) };
  return map { $self->{state}->{chans}->{ $_ }->{name} } keys %{ $record->{chans} };
}

sub _state_user_route {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  my $record = $self->{state}->{users}->{ u_irc( $nick ) };
  return $record->{route_id};
}

sub state_user_server {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  my $record = $self->{state}->{users}->{ u_irc( $nick ) };
  return $record->{server};
}

sub _state_peer_route {
  my $self = shift;
  my $peer = shift || return;
  return unless $self->state_peer_exists( $peer );
  my $record = $self->{state}->{peers}->{ uc $peer };
  return $record->{route_id};
}

sub _state_connected_peers {
  my $self = shift;
  my $server = uc $self->server_name();
  return unless scalar keys %{ $self->{state}->{peers} } > 1;
  my $record = $self->{state}->{peers}->{ $server };
  return map { $record->{peers}->{$_}->{route_id} } keys %{ $record->{peers} };
}

sub state_chan_list {
  my $self = shift;
  my $chan = shift || return;
  my $status_msg = shift || '';
  return unless $self->state_chan_exists( $chan );
  $status_msg =~ s/[^@%+]//g;
  my $record = $self->{state}->{chans}->{ u_irc $chan };
  return map { $self->{state}->{users}->{ $_ }->{nick} } keys %{ $record->{users} } unless $status_msg;
  my %map = qw(o 3 h 2 v 1);
  my %sym = qw(@ 3 % 2 + 1);
  my $lowest = ( sort map { $sym{ $_ } } split //, $status_msg )[0];
  return map { $self->{state}->{users}->{ $_ }->{nick} }
	 grep { $record->{users}->{ $_ }
	 and ( reverse sort map { $map{ $_ } } split //, $record->{users}->{ $_ } )[0] >= $lowest }
	 keys %{ $record->{users} };
}

sub state_chan_list_prefixed {
  my $self = shift;
  my $chan = shift || return;
  return unless $self->state_chan_exists( $chan );
  my $record = $self->{state}->{chans}->{ u_irc( $chan ) };
  return map {
		my $n = $self->{state}->{users}->{ $_ }->{nick};
		my $m = $record->{users}->{$_};
		my $p = '';
		$p = '@' if $m =~ /o/;
		$p = '%' if $m =~ /h/ and !$p;
		$p = '+' if $m =~ /v/ and !$p;
		$p . $n;
	     } keys %{ $record->{users} };
}

sub _state_chan_timestamp {
  my $self = shift;
  my $chan = shift || return;
  return unless $self->state_chan_exists( $chan );
  return $self->{state}->{chans}->{ u_irc $chan }->{ts};
}

sub state_chan_topic {
  my $self = shift;
  my $chan = shift || return;
  return unless $self->state_chan_exists( $chan );
  my $record = $self->{state}->{chans}->{ u_irc( $chan ) };
  return unless $record->{topic};
  return [ @{ $record->{topic} } ];
}

sub _state_is_local_user {
  my $self = shift;
  my $nick = shift || return;
  return unless $self->state_nick_exists( $nick );
  my $record = $self->{state}->{peers}->{ uc $self->server_name() };
  return 1 if defined $record->{users}->{ u_irc $nick };
  return 0;
}

sub _state_chan_name {
  my $self = shift;
  my $chan = shift || return;
  return unless $self->state_chan_exists( $chan );
  return $self->{state}->{chans}->{ u_irc $chan }->{name};
}

sub state_chan_mode_set {
  my $self = shift;
  my $chan = shift || return;
  my $mode = shift || return;
  return unless $self->state_chan_exists( $chan );
  $mode =~ s/[^a-zA-Z]+//g;
  $mode = ( split //, $mode )[0] if length $mode > 1;
  my $record = $self->{state}->{chans}->{ u_irc $chan };
  return 1 if $record->{mode} =~ /$mode/;
  return 0;
}

sub _state_user_invited {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift || return;
  return unless $self->state_nick_exists( $nick );
  return 0 unless $self->state_chan_exists( $chan );
  my $nickrec = $self->{state}->{users}->{ u_irc $nick };
  return 1 if $nickrec->{invites}->{ u_irc $chan };
  # Check if user matches INVEX
  return 1 if $self->_state_user_matches_list( $nick, $chan, 'invex' );
  return 0;
}

sub _state_user_banned {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift || return;
  return 0 unless $self->_state_user_matches_list( $nick, $chan, 'bans' );
  return 1 unless $self->_state_user_matches_list( $nick, $chan, 'excepts' );
  return 0;
}

sub _state_user_matches_list {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift || return;
  my $list = shift || 'bans';
  return unless $self->state_nick_exists( $nick );
  return 0 unless $self->state_chan_exists( $chan );
  my $full = $self->state_user_full( $nick );
  my $record = $self->{state}->{chans}->{ u_irc $chan };
  foreach my $mask ( keys %{ $record->{ $list } } ) {
	return 1 if matches_mask( $mask, $full );
  }
  return 0;
}

sub state_is_chan_member {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift || return;
  return unless $self->state_nick_exists( $nick );
  return 0 unless $self->state_chan_exists( $chan );
  my $record = $self->{state}->{users}->{ u_irc $nick };
  return 1 if defined ( $record->{chans}->{ u_irc $chan } );
  return 0;
}

sub state_user_chan_mode {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift || return;
  return unless $self->state_is_chan_member( $nick, $chan );
  return $self->{state}->{users}->{ u_irc $nick }->{chans}->{ u_irc $chan };
}

sub state_is_chan_op {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift || return;
  return unless $self->state_is_chan_member( $nick, $chan );
  my $record = $self->{state}->{users}->{ u_irc $nick };
  return 1 if $record->{chans}->{ u_irc $chan } =~ /o/;
  return 1 if $self->{config}->{OPHACKS} and $record->{umode} =~ /o/;
  return 0;
}

sub state_is_chan_hop {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift || return;
  return unless $self->state_is_chan_member( $nick, $chan );
  my $record = $self->{state}->{users}->{ u_irc $nick };
  return 1 if $record->{chans}->{ u_irc $chan } =~ /h/;
  return 0;
}

sub state_has_chan_voice {
  my $self = shift;
  my $nick = shift || return;
  my $chan = shift || return;
  return unless $self->state_is_chan_member( $nick, $chan );
  my $record = $self->{state}->{users}->{ u_irc $nick };
  return 1 if $record->{chans}->{ u_irc $chan } =~ /v/;
  return 0;
}

sub _state_o_line {
  my $self = shift;
  my $nick = shift || return;
  my ($user,$pass) = @_;
  return unless $self->state_nick_exists( $nick );
  return unless $user and $pass;
  my $ops = $self->{config}->{ops};
  return unless $ops->{ $user };
  return -1 unless chkpasswd ( $pass, $ops->{ $user }->{password} );
  my $client_ip = $self->_state_user_ip( $nick );
  return unless $client_ip;
  return 1 if ( !$ops->{ $user }->{ipmask} and ( $client_ip and $client_ip =~ /^127\./ ) );
  return 0 unless $ops->{ $user }->{ipmask};
  if ( ref $ops->{ $user }->{ipmask} eq 'ARRAY' ) {
    foreach my $block ( grep { $_->isa('Net::Netmask') } @{ $ops->{ $user }->{ipmask} } ) {
    	return 1 if $block->match( $client_ip );
    }
  }
  return 1 if matches_mask( $ops->{ $user }->{ipmask}, $client_ip );
  return 0;
}

sub _state_users_share_chan {
  my $self = shift;
  my $nick1 = shift || return;
  my $nick2 = shift || return;
  return unless $self->state_nick_exists( $nick1 ) and $self->state_nick_exists( $nick2 );
  my $rec1 = $self->{state}->{users}->{ u_irc $nick1 };
  my $rec2 = $self->{state}->{users}->{ u_irc $nick2 };
  foreach my $chan ( keys %{ $rec1->{chans} } ) {
	return 1 if $rec2->{chans}->{ $chan };
  }
  return 0;
}

sub _state_parse_msg_targets {
  my $self = shift;
  my $targets = shift || return;
  my %results;
  foreach my $target ( split /,/, $targets ) {
    if ( $target =~ /^(\x23|\x26)/ ) {
	$results{$target} = [ 'channel' ];
	next;
    }
    if ( $target =~ /^([\x40\x25\x2B]+)([\x23\x26].+)$/ ) {
	$results{$target} = [ 'channel_ext', $1, $2 ];
	next;
    }
    if ( $target =~ /^\x24{2}(.+)$/ ) {
	$results{$target} = [ 'servermask', $1 ];
	next;
    }
    if ( $target =~ /^\x24\x23(.+)$/ ) {
	$results{$target} = [ 'hostmask', $1 ];
	next;
    }
    if ( $target =~ /\x40/ ) {
	my ($nick,$server) = split /\x40/, $target, 2;
	my $host;
	($nick,$host) = split ( /\x25/, $nick, 2 ) if $nick =~ /\x25/;
	$results{$target} = [ 'nick_ext', $nick, $server, $host ];
	next;
    }
    $results{$target} = [ 'nick' ];
  }
  return \%results;
}

sub server_name {
  return $_[0]->server_config('ServerName');
}

sub server_version {
  return $_[0]->server_config('Version');
}

sub server_created {
  return strftime("This server was created %a %b %d %Y at %H:%M:%S %Z",localtime($_[0]->server_config('created')));
}

sub _client_nickname {
  my $self = shift;
  my $wheel_id = $_[0] || return undef;
  return '*' unless $self->{state}->{conns}->{ $wheel_id }->{nick};
  return $self->{state}->{conns}->{ $wheel_id }->{nick};
}


sub _client_ip {
  my $self = shift;
  my $wheel_id = shift || return '';
  return $self->{state}->{conns}->{ $wheel_id }->{socket}->[0];
}

sub server_config {
  my $self = shift;
  my $value = shift || return;
  return $self->{config}->{ uc $value };
}

sub configure {
  my $self = shift;
  my $options;

  if ( ref $_[0] eq 'HASH' ) {
    $options = $_[0];
  } else {
    $options = { @_ };
  }

  $self->{config}->{ uc $_ } = $options->{ $_ } for keys %{ $options };

  $self->{config}->{CREATED} = time();
  $self->{config}->{CASEMAPPING} = 'rfc1459';
  $self->{config}->{SERVERNAME} = 'poco.server.irc' unless $self->{config}->{SERVERNAME};
  $self->{config}->{SERVERNAME} =~ s/[^a-zA-Z0-9\-.]//g;
  $self->{config}->{SERVERNAME} .= '.' unless $self->{config}->{SERVERNAME} =~ /\./;
  $self->{config}->{SERVERDESC} = 'Poco? POCO? POCO!' unless $self->{config}->{SERVERDESC};
  $self->{config}->{VERSION} = ref ( $self ) . '-' . $VERSION unless $self->{config}->{VERSION};
  $self->{config}->{NETWORK} = 'poconet' unless $self->{config}->{NETWORK};
  $self->{config}->{HOSTLEN} = 63 unless ( defined ( $self->{config}->{HOSTLEN} ) and $self->{config}->{HOSTLEN} > 63 );
  $self->{config}->{NICKLEN} = 9 unless ( defined ( $self->{config}->{NICKLEN} ) and $self->{config}->{NICKLEN} > 9 );
  $self->{config}->{KICKLEN} = 120 unless ( defined ( $self->{config}->{KICKLEN} ) and $self->{config}->{KICKLEN} < 120 );
  $self->{config}->{USERLEN} = 10 unless ( defined ( $self->{config}->{USERLEN} ) and $self->{config}->{USERLEN} > 10 );
  $self->{config}->{REALLEN} = 50 unless ( defined ( $self->{config}->{REALLEN} ) and $self->{config}->{REALLEN} > 50 );
  $self->{config}->{TOPICLEN} = 80 unless ( defined ( $self->{config}->{TOPICLEN} ) and $self->{config}->{TOPICLEN} > 80 );
  $self->{config}->{AWAYLEN} = 160 unless ( defined ( $self->{config}->{AWAYLEN} ) and $self->{config}->{AWAYLEN} < 160 );
  $self->{config}->{CHANNELLEN} = 50 unless ( defined ( $self->{config}->{CHANNELLEN} ) and $self->{config}->{CHANNELLEN} > 50 );
  $self->{config}->{PASSWDLEN} = 20 unless ( defined ( $self->{config}->{PASSWDLEN} ) and $self->{config}->{PASSWDLEN} > 20 );
  $self->{config}->{KEYLEN} = 23 unless ( defined ( $self->{config}->{KEYLEN} ) and $self->{config}->{KEYLEN} > 23 );
  $self->{config}->{MAXCHANNELS} = 15 unless ( defined ( $self->{config}->{MAXCHANNELS} ) and $self->{config}->{MAXCHANNELS} > 15 );
  $self->{config}->{MAXACCEPT} = 20 unless ( defined ( $self->{config}->{MAXACCEPT} ) and $self->{config}->{MAXACCEPT} > 20 );
  $self->{config}->{MODES} = 4 unless ( defined ( $self->{config}->{MODES} ) and $self->{config}->{MODES} > 4 );
  $self->{config}->{MAXTARGETS} = 4 unless ( defined ( $self->{config}->{MAXTARGETS} ) and $self->{config}->{MAXTARGETS} > 4 );
  $self->{config}->{MAXBANS} = 25 unless ( defined ( $self->{config}->{MAXBANS} ) and $self->{config}->{MAXBANS} > 30 );
  $self->{config}->{MAXBANLENGTH} = 1024 unless ( defined ( $self->{config}->{MAXBANLENGTH} ) and $self->{config}->{MAXBANLENGTH} < 1024 );
  $self->{config}->{BANLEN} = $self->{config}->{USERLEN} + $self->{config}->{NICKLEN} + $self->{config}->{HOSTLEN} + 3;
  $self->{config}->{USERHOST_REPLYLEN} = $self->{config}->{USERLEN} + $self->{config}->{NICKLEN} + $self->{config}->{HOSTLEN} + 5;
  # TODO: Find some way to disable requirement for PoCo-Client-DNS and PoCo-Client-Ident
  $self->{config}->{AUTH} = 1 unless ( defined ( $self->{config}->{AUTH} ) and $self->{config}->{AUTH} eq '0' );
  $self->{config}->{ANTIFLOOD} = 1 unless ( defined ( $self->{config}->{ANTIFLOOD} ) and $self->{config}->{ANTIFLOOD} eq '0' );
  if ( ( not defined ( $self->{config}->{ADMIN} ) ) or ( ref $self->{config}->{ADMIN} ne 'ARRAY' ) or ( scalar ( @{ $self->{config}->{ADMIN} } ) != 3 ) ) {
    $self->{config}->{ADMIN}->[0] = 'Somewhere, Somewhere, Somewhere';
    $self->{config}->{ADMIN}->[1] = 'Some Institution';
    $self->{config}->{ADMIN}->[2] = 'someone@somewhere';
  }
  if ( ( not defined ( $self->{config}->{INFO} ) ) or ( ref $self->{config}->{INFO} eq 'ARRAY' ) or ( scalar ( @{ $self->{config}->{INFO} } ) >= 1 ) ) {
    $self->{config}->{INFO}->[0] = '# POE::Component::Server::IRC';
    $self->{config}->{INFO}->[1] = '#';
    $self->{config}->{INFO}->[2] = '# Author: Chris "BinGOs" Williams';
    $self->{config}->{INFO}->[3] = '#';
    $self->{config}->{INFO}->[4] = '# Filter-IRCD Written by Hachi';
    $self->{config}->{INFO}->[5] = '#';
    $self->{config}->{INFO}->[6] = '# This module may be used, modified, and distributed under the same';
    $self->{config}->{INFO}->[7] = '# terms as Perl itself. Please see the license that came with your Perl';
    $self->{config}->{INFO}->[8] = '# distribution for details.';
    $self->{config}->{INFO}->[9] = '#';
  }

  $self->{config}->{WHOISACTUALLY} = 1 unless defined $self->{config}->{WHOISACTUALLY} and $self->{config}->{WHOISACTUALLY} eq '0';

  # OPER hacks
  $self->{config}->{OPHACKS} = 0 unless $self->{config}->{OPHACKS};

  $self->{Error_Codes} = {
			401 => [ 1, "No such nick/channel" ],
			402 => [ 1, "No such server" ],
			403 => [ 1, "No such channel" ],
			404 => [ 1, "Cannot send to channel" ],
			405 => [ 1, "You have joined too many channels" ],
			406 => [ 1, "There was no such nickname" ],
			407 => [ 1, "Too many targets" ],
			408 => [ 1, "No such service" ],
			409 => [ 1, "No origin specified" ],
			411 => [ 0, "No recipient given (%s)" ],
			412 => [ 0, "No text to send" ],
			413 => [ 1, "No toplevel domain specified" ],
			414 => [ 1, "Wildcard in toplevel domain" ],
			415 => [ 1, "Bad server/host mask" ],
			421 => [ 1, "Unknown command" ],
			422 => [ 0, "MOTD File is missing" ],
			423 => [ 1, "No administrative info available" ],
			424 => [ 1, "File error doing % on %" ],
			431 => [ 1, "No nickname given" ],
			432 => [ 1, "Erroneous nickname" ],
			433 => [ 1, "Nickname is already in use" ],
			436 => [ 1, "Nickname collision KILL from %s\@%s" ],
			437 => [ 1, "Nick/channel is temporarily unavailable" ],
			441 => [ 1, "They aren\'t on that channel" ],
			442 => [ 1, "You\'re not on that channel" ],
			443 => [ 2, "is already on channel" ],
			444 => [ 1, "User not logged in" ],
			445 => [ 0, "SUMMON has been disabled" ],
			446 => [ 0, "USERS has been disabled" ],
			451 => [ 0, "You have not registered" ],
			461 => [ 1, "Not enough parameters" ],
			462 => [ 0, "Unauthorised command (already registered)" ],
			463 => [ 0, "Your host isn\'t among the privileged" ],
			464 => [ 0, "Password mismatch" ],
			465 => [ 0, "You are banned from this server" ],
			466 => [ 0, "You will be banned from this server" ],
			467 => [ 1, "Channel key already set" ],
			471 => [ 1, "Cannot join channel (+l)" ],
			472 => [ 1, "is unknown mode char to me for %s" ],
			473 => [ 1, "Cannot join channel (+i)" ],
			474 => [ 1, "Cannot join channel (+b)" ],
			475 => [ 1, "Cannot join channel (+k)" ],
			476 => [ 1, "Bad Channel Mask" ],
			477 => [ 1, "Channel doesn\'t support modes" ],
			478 => [ 2, "Channel list is full" ],
			481 => [ 0, "Permission Denied- You\'re not an IRC operator" ],
			482 => [ 1, "You\'re not channel operator" ],
			483 => [ 0, "You can\'t kill a server!" ],
			484 => [ 0, "Your connection is restricted!" ],
			485 => [ 0, "You\'re not the original channel operator" ],
			491 => [ 0, "No O-lines for your host" ],
			501 => [ 0, "Unknown MODE flag" ],
			502 => [ 0, "Cannot change mode for other users" ],
  };

  $self->{config}->{isupport} = {
    INVEX => undef,
    EXCEPT => undef,
    CALLERID => undef,
    CHANTYPES => '#&',
    PREFIX => '(ohv)@%+',
    CHANMODES => 'eIb,k,l,imnpst',
    STATUSMSG => '@%+',
    DEAF => 'D',
    MAXLIST => 'beI:' . $self->{config}->{MAXBANS},
    map { ( uc $_, $self->{config}->{$_} ) } qw(MAXCHANNELS MAXTARGETS NICKLEN TOPICLEN KICKLEN CASEMAPPING NETWORK MODES AWAYLEN),
  };

  $self->{config}->{capab} = [ qw(QS EX CHW IE HOPS UNKLN KLN GLN EOB) ];

  return 1;
}

sub _send_output_to_client {
  my $self = shift;
  my $wheel_id = shift || return 0;
  my $nick = $self->_client_nickname( $wheel_id );
  $nick = shift if $self->_connection_is_peer( $wheel_id );
  my $err = shift || return 0;
  return unless $self->_connection_exists( $wheel_id );
  SWITCH: {
    if ( ref $err eq 'HASH' ) {
	$self->{ircd}->send_output( $err, $wheel_id );
	last SWITCH;
    }
    if ( defined ( $self->{Error_Codes}->{ $err } ) ) {
	my $input = { command => $err, prefix => $self->server_name(), params => [ $nick ] };
	if ( $self->{Error_Codes}->{ $err }->[0] > 0 ) {
	   for ( my $i = 1; $i <= $self->{Error_Codes}->{ $err }->[0]; $i++ ) {
		push @{ $input->{params} }, shift;
	   }
	}
	if ( $self->{Error_Codes}->{ $err }->[1] =~ /%/ ) {
	  push ( @{ $input->{params} }, sprintf($self->{Error_Codes}->{ $err }->[1],@_) );
        } else {
	  push ( @{ $input->{params} }, $self->{Error_Codes}->{ $err }->[1] );
        }
	$self->{ircd}->send_output( $input, $wheel_id );
    }
  }
  return 1;
}

sub _send_output_to_channel {
  my $self = shift;
  my $channel = shift || return;
  my $output = shift || return;
  my $conn_id = shift || '';
  return unless $self->state_chan_exists( $channel );
  # Get conn_ids for each of our peers.
  my $ref = [ ]; my $peers = { };
  $peers->{ $_ }++ for $self->_state_connected_peers();
  delete $peers->{ $conn_id } if $conn_id;
  push @{ $ref }, $self->_state_user_route( $_ ) for grep { $self->_state_is_local_user( $_ ) } $self->state_chan_list( $channel );
  @{ $ref } = grep { $_ ne $conn_id } @{ $ref };
  if ( $channel !~ /^\&/ and scalar( keys %{ $peers} ) and $output->{command} ne 'JOIN' ) {
    my $full = $output->{prefix};
    my $nick = ( split /!/, $full )[0];
    my $output2 = { %{ $output } };
    $output2->{prefix} = $nick;
    $self->{ircd}->send_output( $output2, keys %{ $peers } );
  }
  $self->{ircd}->send_output( $output, @{ $ref } );
  $self->{ircd}->send_event( "daemon_" . lc $output->{command}, $output->{prefix}, @{ $output->{params} } );
  return 1;
}

sub add_operator {
  my $self = shift;
  my $ref;
  if ( ref $_[0] eq 'HASH' ) {
    $ref = $_[0];
  } else {
    $ref = { @_ };
  }
  $ref->{ lc $_ } = delete $ref->{ $_ } for keys %{ $ref };

  unless ( $ref->{username} and $ref->{password} ) {
    warn "Not enough parameters\n";
    return;
  }

  my $record = $self->{state}->{peers}->{ uc $self->server_name() };
  my $user = delete $ref->{username};
  $self->{config}->{ops}->{ $user } = $ref;
  return 1;
}

sub del_operator {
  my $self = shift;
  my $user = shift || return;
  return unless defined $self->{config}->{ops}->{ $user };
  delete $self->{config}->{ops}->{ $user };
}

sub add_auth {
  my $self = shift;
  my $parms;
  if ( ref $_[0] eq 'HASH' ) {
     $parms = $_[0];
  } else {
     $parms = { @_ };
  }
  $parms->{ lc $_ } = delete $parms->{ $_ } for keys %{ $parms };
  unless ( $parms->{mask} ) {
     warn "Not enough parameters specified\n";
     return;
  }
  push @{ $self->{config}->{auth} }, $parms;
  return 1;
}

sub del_auth {
  my $self = shift;
  my $mask = shift || return;
  my $i = 0;
  for ( @{ $self->{config}->{auth} } ) {
    splice( @{ $self->{config}->{auth} }, $i, 1 ), last if $_->{mask} eq $mask;
    ++$i;
  }
}

sub add_peer {
  my $self = shift;
  my $parms;
  if ( ref $_[0] eq 'HASH' ) {
     $parms = $_[0];
  } else {
     $parms = { @_ };
  }
  $parms->{ lc $_ } = delete $parms->{ $_ } for keys %{ $parms };
  unless ( $parms->{name} and $parms->{pass} and $parms->{rpass} ) {
     warn "Not enough parameters specified\n";
     return;
  }
  $parms->{type} = 'c' unless $parms->{type} and lc( $parms->{type} ) eq 'r';
  $parms->{type} = lc $parms->{type};
  $parms->{rport} = 6667 if $parms->{type} eq 'r' and !$parms->{rport};
  foreach ( qw(sockport sockaddr) ) {
	$parms->{ $_ } = '*' unless $parms->{ $_ };
  }
  $parms->{ipmask} = $parms->{raddress} if $parms->{raddress};
  $parms->{zip} = 0 unless $parms->{zip};
  my $name = $parms->{name};
  $self->{config}->{peers}->{ uc $name } = $parms;
  $self->{ircd}->add_connector( remoteaddress => $parms->{raddress}, remoteport => $parms->{rport}, name => $name ) if $parms->{type} eq 'r' and $parms->{auto};
  return 1;
}

sub del_peer {
  my $self = shift;
  my $name = shift || return;
  return unless defined $self->{config}->{peers}->{ uc $name };
  delete $self->{config}->{peers}->{ uc $name };
}

sub _terminate_conn_error {
  my $self = shift;
  my $conn_id = shift || return;
  return unless $self->_connection_exists( $conn_id );
  my $msg = shift;
  $self->{ircd}->disconnect( $conn_id, $msg );
  $self->{ircd}->send_output( { command => 'ERROR', params => [ 'Closing Link: ' . $self->_client_ip( $conn_id ) . ' (' . $msg . ')' ] }, $conn_id );
  return 1;
}

#####################
### API #############
#####################

sub daemon_server_kill {
  my $self = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
     if ( !$count ) {
	last SWITCH;
     }
     if ( $self->state_peer_exists( $args->[0] ) ) {
	last SWITCH;
     }
     if ( !$self->state_nick_exists( $args->[0] ) ) {
	last SWITCH;
     }
     my $target = $self->state_user_nick( $args->[0] );
     my $comment = $args->[1] || '<No reason given>';
     my $conn_id = ( $args->[2] and $self->_connection_exists( $args->[2] ) ? $args->[2] : '' );
     if ( $self->_state_is_local_user( $target ) ) {
	my $route_id = $self->_state_user_route( $target );
	$self->{ircd}->send_output( { prefix => $server, command => 'KILL', params => [ $target, $comment ] }, $route_id );
	$self->_terminate_conn_error( $route_id, "Killed ($server ($comment))" );
	if ( $route_id eq 'spoofed' ) {
	  $self->call( 'del_spoofed_nick', $target, "Killed ($server ($comment))" );
	} else {
	  $self->{state}->{conns}->{ $route_id }->{killed} = 1;
	  $self->_terminate_conn_error( $route_id, "Killed ($server ($comment))" );
	}
     } else {
	$self->{state}->{users}->{ u_irc $target }->{killed} = 1;
        $self->{ircd}->send_output( { prefix => $server, command => 'KILL', params => [ $target, "$server ($comment)" ] }, grep { !$conn_id || $_ ne $conn_id } $self->_state_connected_peers() );
	$self->{ircd}->send_output( @{ $self->_daemon_peer_quit( $target, "Killed ($server ($comment))" ) } );
     }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub daemon_server_mode {
  my $self = shift;
  my $chan = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$self->state_chan_exists( $chan ) ) {
	last SWITCH;
    }
    my $record = $self->{state}->{chans}->{ u_irc $chan };
    $chan = $record->{name};
    my $full = $server;
    my $parsed_mode = parse_mode_line( @{ $args } );
    while( my $mode = shift ( @{ $parsed_mode->{modes} } ) ) {
      my $arg;
      $arg = shift ( @{ $parsed_mode->{args} } ) if ( $mode =~ /^(\+[ohvklbIe]|-[ohvbIe])/ );
      if ( my ($flag,$char) = $mode =~ /^(\+|-)([ohv])/ ) {
	next unless $self->state_is_chan_member( $arg, $chan );
	if ( $flag eq '+' and $record->{users}->{ u_irc $arg } !~ /$char/ ) {
	  # Update user and chan record
	  $arg = u_irc $arg;
	  next if ( $mode eq '+h' and $record->{users}->{ $arg } =~ /o/ );
	  if ( $char eq 'h' and $record->{users}->{ $arg } =~ /v/ ) {
	     $record->{users}->{ $arg } =~ s/v//g;
	  }
	  if ( $char eq 'o' and $record->{users}->{ $arg } =~ /h/ ) {
	     $record->{users}->{ $arg } =~ s/h//g;
	  }
	  $record->{users}->{ $arg }  = join('', sort split //, $record->{users}->{ $arg } . $char );
	  $self->{state}->{users}->{ $arg }->{chans}->{ u_irc $chan } = $record->{users}->{ $arg };
        }
	if ( $flag eq '-' and $record->{users}->{ u_irc $arg } =~ /$char/ ) {
	  # Update user and chan record
	  $arg = u_irc $arg;
	  $record->{users}->{ $arg } =~ s/$char//g;
	  $self->{state}->{users}->{ $arg }->{chans}->{ u_irc $chan } = $record->{users}->{ $arg };
        }
	next;
      }
      if ( $mode eq '+l' and $arg =~ /^\d+$/ and $arg > 0 ) {
	$record->{mode} = join('', sort split //, $record->{mode} . 'l' ) unless $record->{mode} =~ /l/;
	$record->{climit} = $arg;
	next;
      }
      if ( $mode eq '-l' and $record->{mode} =~ /l/ ) {
	$record->{mode} =~ s/l//g;
	delete $record->{climit};
	next;
      }
      if ( $mode eq '+k' and $arg ) {
	$record->{mode} = join('', sort split //, $record->{mode} . 'k' ) unless $record->{mode} =~ /k/;
	$record->{ckey} = $arg;
	next;
      }
      if ( $mode eq '-k' and $record->{mode} =~ /k/ ) {
	$record->{mode} =~ s/k//g;
	delete $record->{ckey};
	next;
      }
      # Bans
      if ( my ($flag) = $mode =~ /(\+|-)b/ ) {
	my $mask = parse_ban_mask( $arg );
	my $umask = u_irc $mask;
	if ( $flag eq '+' and !$record->{bans}->{ $umask } ) {
	  $record->{bans}->{ $umask } = [ $mask, ( $full || $server ), time() ];
	}
	if ( $flag eq '-' and $record->{bans}->{ $umask } ) {
	  delete $record->{bans}->{ $umask };
	}
	next;
      }
      # Invex
      if ( my ($flag) = $mode =~ /(\+|-)I/ ) {
	my $mask = parse_ban_mask( $arg );
	my $umask = u_irc $mask;
	if ( $flag eq '+' and !$record->{invex}->{ $umask } ) {
	   $record->{invex}->{ $umask } = [ $mask, ( $full || $server ), time() ];
	}
	if ( $flag eq '-' and $record->{invex}->{ $umask } ) {
	  delete $record->{invex}->{ $umask };
	}
	next;
      }
      # Exceptions
      if ( my ($flag) = $mode =~ /(\+|-)e/ ) {
	my $mask = parse_ban_mask( $arg );
	my $umask = u_irc $mask;
	if ( $flag eq '+' and !$record->{excepts}->{ $umask } ) {
	  $record->{excepts}->{ $umask } = [ $mask, ( $full || $server ), time() ];
	}
	if ( $flag eq '-' and $record->{excepts}->{ $umask } ) {
	  delete $record->{excepts}->{ $umask };
	}
	next;
      }
      # The rest should be argumentless.
      my ($flag,$char) = split //, $mode;
      if ( $flag eq '+' and $record->{mode} !~ /$char/ ) {
	$record->{mode} = join('', sort split //, $record->{mode} . $char );
	next;
      }
      if ( $flag eq '-' and $record->{mode} =~ /$char/ ) {
	$record->{mode} =~ s/$char//g;
	next;
      }
    } # while
    unshift @{ $args }, $record->{name};
    $self->{ircd}->send_output( { prefix => $server, command => 'MODE', params => $args, colonify => 0 }, $self->_state_connected_peers() );
    $self->{ircd}->send_output( { prefix => ( $full || $server ), command => 'MODE', params => $args, colonify => 0 }, map { $self->_state_user_route($_) } grep { $self->_state_is_local_user($_) } keys %{ $record->{users} } );
    $self->{ircd}->send_event( "daemon_mode", $server, @{ $args } );
  } # SWITCH
  return @{ $ref } if wantarray();
  return $ref;
}

sub daemon_server_kick {
  my $self = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count or $count < 2 ) {
	last SWITCH;
    }
    my $chan = ( split /,/, $args->[0] )[0];
    my $who = ( split /,/, $args->[1] )[0];
    if ( !$self->state_chan_exists( $chan ) ) {
	last SWITCH;
    }
    $chan = $self->_state_chan_name( $chan );
    if ( !$self->state_nick_exists( $who ) ) {
	last SWITCH;
    }
    $who = $self->state_user_nick( $who );
    if ( !$self->state_is_chan_member( $who, $chan ) ) {
	last SWITCH;
    }
    my $comment = $args->[2] || $who;
    $self->_send_output_to_channel( $chan, { prefix => $server, command => 'KICK', params => [ $chan, $who, $comment ] } );
    $who = u_irc $who; $chan = u_irc $chan;
    delete $self->{state}->{chans}->{ $chan }->{users}->{ $who };
    delete $self->{state}->{users}->{ $who }->{chans}->{ $chan };
    unless ( scalar keys %{ $self->{state}->{chans}->{ $chan  }->{users} } ) {
	delete $self->{state}->{chans}->{ $chan  };
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub daemon_server_remove {
  my $self = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count or $count < 2 ) {
	last SWITCH;
    }
    my $chan = ( split /,/, $args->[0] )[0];
    my $who = ( split /,/, $args->[1] )[0];
    if ( !$self->state_chan_exists( $chan ) ) {
	last SWITCH;
    }
    $chan = $self->_state_chan_name( $chan );
    if ( !$self->state_nick_exists( $who ) ) {
	last SWITCH;
    }
    my $fullwho = $self->state_user_full( $who );
    $who = ( split /!/, $who )[0];
    if ( !$self->state_is_chan_member( $who, $chan ) ) {
	last SWITCH;
    }
    my $comment = 'Enforced PART';
    $comment .= " \"$args->[2]\"" if $args->[2];
    $self->_send_output_to_channel( $chan, { prefix => $fullwho, command => 'PART', params => [ $chan, $comment ] } );
    $who = u_irc $who; $chan = u_irc $chan;
    delete $self->{state}->{chans}->{ $chan }->{users}->{ $who };
    delete $self->{state}->{users}->{ $who }->{chans}->{ $chan };
    unless ( scalar keys %{ $self->{state}->{chans}->{ $chan  }->{users} } ) {
	delete $self->{state}->{chans}->{ $chan  };
    }
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub daemon_server_wallops {
  my $self = shift;
  my $server = $self->server_name();
  my $ref = [ ]; my $args = [ @_ ]; my $count = scalar @{ $args };
  SWITCH: {
    if ( !$count ) {
	last SWITCH;
    }
    $self->{ircd}->send_output( { prefix => $server, command => 'WALLOPS', params => [ $args->[0] ] }, $self->_state_connected_peers(), keys %{ $self->{state}->{operwall} } );
    $self->{ircd}->send_event( "daemon_wallops", $server, $args->[0] );
  }
  return @{ $ref } if wantarray();
  return $ref;
}

sub add_spoofed_nick {
  my ($kernel,$self) = @_[KERNEL,OBJECT];
  my $ref;
  if ( ref $_[ARG0] eq 'HASH' ) {
	$ref = $_[ARG0];
  }
  else {
	$ref = { @_[ARG0..$#_] };
  }
  $ref->{ lc $_ } = delete $ref->{$_} for keys %{ $ref };
  return unless $ref->{nick};
  return if $self->state_nick_exists( $ref->{nick} );
  my $record = $ref;
  $record->{ts} = time() unless $record->{ts};
  $record->{type} = 's';
  $record->{server} = $self->server_name();
  $record->{hops} = 0;
  $record->{route_id} = 'spoofed';
  $record->{umode} = 'i' unless $record->{umode};
  $record->{ircname} = "* I'm too lame to read the documentation *" unless $record->{ircname};
  $self->{state}->{stats}->{invisible}++ if $record->{umode} =~ /i/;
  $self->{state}->{stats}->{ops_online}++ if $record->{umode} =~ /o/;
  $record->{idle_time} = $record->{conn_time} = $record->{ts};
  $record->{auth}->{ident} = delete $record->{user} || $record->{nick};
  $record->{auth}->{hostname} = delete $record->{hostname} || $self->server_name();
  $self->{state}->{users}->{ u_irc $record->{nick} } = $record;
  $self->{state}->{peers}->{ uc $record->{server} }->{users}->{ u_irc $record->{nick} } = $record;
  my $arrayref = [ $record->{nick}, $record->{hops} + 1, $record->{ts}, '+' . $record->{umode}, $record->{auth}->{ident}, $record->{auth}->{hostname}, $record->{server}, $record->{ircname} ];
  $self->{ircd}->send_output( { command => 'NICK', params => $arrayref }, $self->_state_connected_peers() );
  $self->{ircd}->send_event( "daemon_nick", @{ $arrayref } );
  $self->_state_update_stats();
  undef;
}

sub del_spoofed_nick {
  my ($kernel,$self,$nick) = @_[KERNEL,OBJECT,ARG0];
  return unless $self->state_nick_exists( $nick );
  return unless $self->_state_user_route( $nick ) eq 'spoofed';
  my $message = $_[ARG1] || 'Client Quit';
  $self->{ircd}->send_output( @{ $self->_daemon_cmd_quit( $nick, qq{"$message"} ) }, qq{"$message"} );
  undef;
}

sub _spoofed_command {
  my ($kernel,$self,$state,$nick) = @_[KERNEL,OBJECT,STATE,ARG0];
  return unless $self->state_nick_exists( $nick );
  return unless $self->_state_user_route( $nick ) eq 'spoofed';
  $nick = $self->state_user_nick( $nick );
  $state =~ s/daemon_cmd_//;
  my $command = "_daemon_cmd_" . $state;
  if ( $state =~ /^(privmsg|notice)$/ ) {
	my $type = uc $1;
	$self->_daemon_cmd_message( $nick, $type, @_[ARG1 .. $#_] );
	return;
  }
  if ( $state eq 'sjoin' ) {
	my $chan = $_[ARG1];
	return unless $chan and $self->state_chan_exists($chan);
	return if $self->state_is_chan_member( $nick, $chan );
	$chan = $self->_state_chan_name( $chan );
	my $ts = $self->_state_chan_timestamp( $chan ) - 10;
	$self->_daemon_peer_sjoin( 'spoofed', $self->server_name(), $ts, $chan, '+nt', '@' . $nick );
	return;
  }
  $self->$command( $nick, @_[ARG1 .. $#_] ) if $self->can($command);
  undef;
}

1;
__END__