# infobot :: Kevin Lenzo & Patrick Cole   (c) 1997

use Socket;

sub srvConnect {
    my ($server, $port) = @_;
    my ($iaddr, $paddr, $proto);
    select(STDOUT);
    $| = 1;

    $iaddr  = inet_aton($server);
    $ip_num = inet_ntoa($iaddr);
    if (not $ip_num) {
	die "can't get the address of $server ($ip_num)!\n";
    }
    &status("Connecting to port $port of server $server ($ip_num)...");
    $paddr = sockaddr_in($port, $iaddr);
    $proto = getprotobyname('tcp');
    socket(SOCK, PF_INET, SOCK_STREAM, $proto) or die "socket failed: $!";
    $sockaddr = 'S n a4 x8';
    if ($param{'vhost_name'}) {
	my $hostname = $param{'vhost_name'};
	$this = pack($sockaddr, AF_INET,  0, inet_aton($hostname));
	&status("trying to bind as $hostname"); 
	bind(SOCK, $this) || die "bind: $!";
    }
    connect(SOCK, $paddr) or die "connect failed: $!";

    &status(" connected.");
}

sub procservmode {
    my ($server, $e, $f) = @_;
    my @parts = split (/ /, $f);
    $cnt=0;
    my $mode="";
    my $chan="";
    foreach (@parts) {
	if ($cnt == 0) {
	    $chan = $_;
	} else {
	    $mode .= $_;
	    $mode .= " ";
	}
	++$cnt;
    }
    chop $mode;
    $mode=~s/://;

    if ($server eq $chan) {
	if ($params{ansi_control}) {
	    &status(">>> $b$server$ob sets user mode: $b$mode$ob");
	} else {
	    &status(">>> $server sets mode: $mode");
	}

    } else {
	if ($params{ansi_control}) {
	    &status(">>> $b$server$ob/$b$chan$ob sets server mode: $b$mode$ob");
	} else {
	    &status(">>> $server/$chan sets mode: $mode");
	}
    }
}

### added by the xk.
# Usage: &nickServ(text);
sub nickServ {
    my $text	= shift;
    return if !defined $param{'nickServ_pass'};

    &status("NickServ: <= '$text'");

    if ($text =~ /Password incorrect/i) {
	&status("NickServ: ** identify failed.");
	return;
    }

    if ($text =~ /Password accepted/i) {
	&status("NickServ: ** identify success.");
	return;
    }

    if ($nickserv_try) { return; }

    &status("NickServ: => Identifying to NickServ.");
    rawout("PRIVMSG NickServ :IDENTIFY $param{'nickServ_pass'}");
    $nickserv_try++;
}

###
# Usage: &chanServ(text);
sub chanServ {
    my $text	= shift;
#    return if !defined $param{'chanServ_ops'};
    &status("chanServ_ops => '$param{'chanServ_ops'}'.");

    &status("ChanServ: <= '$text'");

    # to be continued...
    return;
}
# end of xk functions.

sub procmode {
    my ($nick, $user, $host, $e, $f) = @_;
    my @parts = split (/ /, $f);
    $cnt=0;
    my $mode="";
    my $chan="";
    foreach (@parts) {
	if ($cnt == 0) {
	    $chan = $_;
	} else {
	    $mode .= $_;
	    $mode .= " ";
	}
	++$cnt;
    }
    $mode =~ s/\s$//;


    if ($param{ansi_control}) {
	&status(">>> mode/$b$chan$ob [$b$mode$ob] by $b$nick$ob");
    } else {
	&status(">>> mode/$chan [$mode] by $nick");
    }

    if ($chan =~ /^[\#\&]/) {
	my ($modes, $targets) = ($mode =~ /^(\S+)\s+(.*)/);
	my @m = ($modes =~ /([+-]*\w)/g);
	my @t = split /\s+/, $targets;
	if (@m != @t) {
	    &status("number of modes does not match number of targets: @m / @t");
	} else {
	    my $parity = 0;
	    foreach (0..$#m) {
		if ($m[$_] =~ s/^([-+])//) {
		    $sign = $1;
		    if ($sign eq '-') {
			$parity = -1;
		    } else {
			$parity = 1;
		    }
		}
		if ($parity == 0) {
		    &status("zero parity mode change... ignored");
		} else {
		    if ($parity > 0) {
			$channels{$chan}{$m}{$t} = '+';
		    } else {
			delete $channels{$chan}{$mode}{$t};
		    }
		}
	    }
	}
    }
}

sub entryEvt {
    my ($nick, $user, $host, $type, $chan) = @_;
    if ($type=~/PART/) {
	if ($param{ansi_control}) {
	    &status(">>> $nick ($user\@$host) has left $chan");
	} else {
	    &status(">>> $nick ($user\@$host) has left $chan");
	}
    } elsif ($type=~/JOIN/) {
	if ($netsplit) {
	    foreach (keys(%snick)) {
		if ($nick eq $snick{$_}) {
		    @be = split (/ /);
		    if ($param{ansi_control}) {
			&status(">>> ${b}Netjoined$ob: $be[0] $be[1]");
		    } else {
			&status(">>> ${b}Netjoined$ob: $be[0] $be[1]");
		    }
		    $netsplit--;
		}
	    }
	}
	if ($param{ansi_control}) {
	    &status(">>> $nick ($user\@$host) has joined $chan");
	} else {
	    &status(">>> $nick ($user\@$host) has joined $chan");
	}
    } elsif ($type=~/QUIT/) {
	$chan=~s/\r//;
	if ($chan=~/^([\d\w\_\-\/]+\.[\.\d\w\_\-\/]+)\s([\d\w\_\-\/]+\.[\.\d\w\_\-\/]+)$/) {
	    $i=0;
	    while (0 and ($i < $netsplit || !$netsplit)) {
#	    while ($i < $netsplit || !$netsplit) {
		$i++;
		if (($prevsplit1{$i} ne $2) && ($prevsplit2{$i} ne $1)) {
		    &status("Netsplit: $2 split from $1");
		    $netsplit++;
		    $prevsplit1{$netsplit} = $2;
		    $prevsplit2{$netsplit} = $1;
		    $snick{"$2 $1"}=$nick;
		    $schan{"$2 $1"}=$chan;
		}
	    }
	} else {
	    if ($param{ansi_control}) {
		&status(">>> $b$nick$ob has signed off IRC ($b$chan$ob)");
	    } else {
		&status(">>> $b$nick$ob has signed off IRC ($b$chan$ob)");
	    }
	}
    } elsif ($type=~/NICK/) {
	if ($param{ansi_control}) {
	    &status(">>> ".c($nick,'bold green').
		    " materializes into ".c($chan,'bold green'));
       } else {
           &status(">>> $b$nick$ob materializes into $b$chan$ob");
       }
    }
}

sub procevent {
    my ($nick, $user, $host, $type, $chan, $msg) = @_;

    # support global $nuh, $who
    $nuh = "$nick!$user\@$host";

    if ($type=~/PRIVMSG/) {
	if ($chan =~ /^$ischan/) {
	    ## It's a public message on the channel##
	    $chan =~ tr/A-Z/a-z/;

	    if ($msg =~ /\001(.*)\001/ && $msg !~ /ACTION/) {
		#### Client To Client Protocol ####
		parsectcp($nick, $user, $host, $1, $chan);
	    } elsif ($msg !~ /ACTION\s(.+)/) {
		#### Public Channel Message ####
 		&IrcMsgHook('public', $chan, $nick, $msg);
	    } else {
		#### Public Action ####
		&IrcActionHook($nick, $chan, $1);
	    }
	} else {
	    ## Is Private ##
	    if ($msg=~/\001(.*)\001/) {
		#### Client To Client Protocol ####
		parsectcp($nick, $user, $host, $1, $chan);
	    } else {
		#### Is a Private Message ##
		&IrcMsgHook('private', $chan, $nick, $msg);
	    }
	}
    } elsif ($type=~/NOTICE/) {
	if ($chan =~ /^$ischan/) {
	    $chan =~ tr/A-Z/a-z/;
	    if ($msg !~ /ACTION (.*)/) {
		&status("-$nick/$chan- $msg");
	    } else {
		&status("* $nick/$chan $1");
	    }
	} else {
	    if ($msg=~/\001([A-Z]*)\s(.*)\001/) {
		ctcpReplyParse($nick, $user, $host, $1, $2);
	    } else {
		&status("-$nick($user\@$host)- $msg");
	    }
	}
    }
}

sub servmsg {
    my $msg=$_[0];
    my ($ucount, $uc) = (0, 0);
    if ($msg=~/^001/) {
# joinChan(split/\s+/, $param{'join_channels'});
# Line in infobot.config:
#   join_channels #chan,key #chan_with_no_key
#
# since , is not allowed in channels, we'll use it to specify keys
# without breaking current join_channels format
	for (split /\s+/, $param{'join_channels'}) {
            # if it's a keyed chan, replace the comma with a space so it'll
	    # work as per the RFC (i.e. JOIN #chan key)
	    s/,/ /; 
	    joinChan ($_);
	}
 	$nicktries=0;
    } elsif ($msg=~/^NOTICE ($ident) :(.*)/) {
	serverNotice($1,$2);
    } elsif ($msg=~/^332 $ident ($ischan) :(.*)/) {
	if ($param{ansi_control}) {
	    &status(">>> topic for $b$1$ob: $2");
	} else {
	    &status(">>> topic for $1: $2");
	}
    } elsif ($msg=~/^333 $ident $ischan (.*) (.*)$/) {
       if ($param{ansi_control}) {
           &status(">>> set by $b$1$ob at $b$2$ob");
       } else {
           &status(">>> set by $1 at $2");
       }
    } elsif ($msg=~/^433/) {
	++$nicktries;
	if (length($param{wantNick}) > 9) {
	    $ident = chop $param{wantNick};
	    $ident .= $nicktries;
	} else {
	    $ident = $param{wantNick}.$nicktries;
	}
	if ($param{'opername'}) {
	    &rawout("OPER $param{opername} $param{operpass}");
	}
	$param{nick} = $ident;
	&status("*** Nickname $param{wantNick} in use, trying $ident");
	rawout("NICK $ident");

    } elsif ($msg=~/[0-9]+ $ident . ($ischan) :(.*)/) {
	my ($chan, $users) = ($1, $2);
	&status("NAMES $chan: $users");
	my $u;
	foreach $u (split /\s+/, $users) {
	    if (s/\@//) {
		$channels{$chan}{o}{$u}++;
	    }
	    if (s/\+//) {
		$channels{$chan}{v}{$u}++;
	    }
	}
    } elsif ($msg=~/[0-9]{3} $ident(\s$ischan)*?\s:(.*)/) {
	&status("$2");
    }
}

sub serverNotice {
    ($type, $msg) = @_;
    if ($type=~/AUTH/) {
	&status("!$param{server}! $msg");
    } else {
	$msg =~ s/\*\*\* Notice -- //;
	&status("-!$param{server}!- $msg");
    }
}

sub OperWall {
    my ($nick, $msg) = @_;
    $msg=~s/\*\*\* Notice -- //;
    &status("[wallop($nick)] $msg");
}

sub prockick {
    my ($kicker, $chan, $knick, $why) = @_;

     if ($param{ansi_control}) {
	 &status(">>> $b$knick$ob was kicked off $b$chan$ob by $b$kicker$ob ($b$why$ob)");
     } else {
	 &status(">>> $b$knick$ob was kicked off $b$chan$ob by $b$kicker$ob ($b$why$ob)");
     }
    if ($knick eq $ident) {
	&status("SELF attempting to rejoin lost channel $chan");
	&joinChan($chan);
    }
}

sub prockill {
    my ($killer, $knick, $kserv, $killnick, $why) = @_;
    if ($knick eq $ident) {
	&status("KILLED by $killnick ($why)");	
    } else {
	&status("KILL $knick by $killnick ($why)");
    }
}

sub fhbits {
    local (@fhlist) = split(' ',$_[0]);
    local ($bits);
    for (@fhlist) {
	vec($bits,fileno($_),1) = 1;
    }
    $bits;
}

sub irc {
    local ($rin, $rout);
    local ($buf, $line);

    $nicktries=0;
    $connected=1;
    while ($connected) {
	srvConnect($param{server}, $param{port});

        if ($param{server_pass}) { # ksiero++
            rawout("PASS $param{server_pass}");
        }

	rawout("NICK $param{wantNick}");
	rawout("USER $param{ircuser} $param{ident} $param{server} :$param{realname}");
	if ($param{operator}) {
	    rawout("OPER $param{operName} $param{operPass}\n");
	}
	$param{nick} = $param{wantNick};
	$ident = $param{wantNick};

	$/ = "\015" if $^O eq "MacOS";

	$rin = fhbits('SOCK');
	while (1) {
	    ($nfound,$timeleft) = select($rout=$rin, undef, undef, 0);
	    if ($rout & SOCK) {
		if (sysread(SOCK,$buf,1) <= 0) {
		    last;
		}
		if ($buf=~/\n/) {
		    $line.=$buf;
		    sparse($line);
		    undef $line;
		} else {
		    $line.=$buf;
		}
	    }
	}
    }
}

#Corion++
# Fixed baaaad backdor in server parsing (regexes are a bitch :( )
# /msg infobot :a@b.c PRIVMSG #channel seen <nick>
# can be used to flood a channel witout getting ever caught !

# Affected lines were
# PRIVMSG / NOTICE / TOPIC / KICK

sub sparse {
    $_ = $_[0];
    s/\r//;

    if (/^PING :(\S+)/) {	# Pings are important
	rawout("PONG :$1");
	&status("SELF replied to server PING") if $param{VERBOSITY} > 2;
    } elsif (/^:\S+ ([\d]{3} .*)/) {
	servmsg($1);
    } elsif (/^:([\d\w\_\-\/]+\.[\.\d\w\_\-\/]+) NOTICE ($ident) :(.*)/) {
	&status("\-\[$1\]- $3");
    } elsif (/^NOTICE (.*) :(.*)/) {
	serverNotice($1, $2);
    } elsif (/^:NickServ!s\@NickServ NOTICE \S+ :(.*)/i) {
	&nickServ($1);		# added by the xk.
    } elsif (/^:ChanServ!s\@ChanServ NOTICE \S+ :(.*)/i) {
	&chanServ($1);		# added by the xk.
    } elsif (/^:(\S+)!(\S+)@(\S+)\s(PRIVMSG|NOTICE)\s([\#\&]?\S+)\s:(.*)/) {
	procevent($1,$2,$3,$4,$5,$6);
    } elsif (/^:(\S+)!(\S+)@(\S+)\s(PART|JOIN|NICK|QUIT)\s:?(.*)/) {
	entryEvt($1,$2,$3,$4,$5);
    } elsif (/^:(.*) WALLOPS :(.*)/) {
	OperWall($1,$2);
    } elsif (/^:(.*)!(.*)@(.*) (MODE) (.*)/) {
	procmode($1,$2,$3,$4,$5);
    } elsif (/^:(.*) (MODE) (.*)/) {
	procservmode($1,$2,$3);
    } elsif (/^:(\S+)!(?:\S+)@(?:\S+) KICK ((\#|&).+) (.*) :(.*)/) {
	prockick($1,$2,$4,$5);
    } elsif (/^ERROR :(.*)/) {
	&status("ERROR $1");
    } elsif (/^:([^! ]+)!\S+@\S+ TOPIC (\#.+) :(.*)/) {
	if ($param{ansi_control}) {
	    &status(">>> $1$b\[$ob$2$b\]$ob set the topic: $3");
	} else {
	    &status(">>> $1\[$2\] set the topic: $3");
	}
    } elsif (/^:(\S+)!\S+@\S+ KILL (.*) :(.*)!(.*) \((.*)\)/) {
	prockill($1,$2,$3,$4,$5);
    } else {
	&status("UNKNOWN $_");
    }
#Corion--
}

	     1;
