From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

# 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.
#
use strict;
use warnings FATAL => 'all';
use POE;
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__