# $Id: inspircd12.pm 4269 2006-07-10 13:34:24Z brain $ # This file was originally inspircd11.pm # Modified to work with InspIRCd 1.2 by Thunderhacker # ----- BEGIN RELEASE NOTES ----- # Date: November 26, 2011 # This version of the inspircd12 module supersedes all other versions of this # module in all forks of this project. Merge your changes into this version. # In all previous versions of this module there exists a security hole that # allows arbitrary remote code execution. There is currently an in the wild # exploit for this bug. This version of the module is patched against the # November 15, 2011 exploit. # # -Thunderhacker # ----- END RELEASE NOTES ----- my %hosts = (); my $sid = $main::sid; # create hash to track uuid <-> nick pairs # give uuid, return nick our %uuidnick = (); # give nick, return uuid our %nickuuid = (); $uuid = $sid . "AAAAAA"; # create hash to track channel timestamps # give channel, get timestamp my %chants = (); # ugly hack to get the timestamp of the control channel # since we can't put a variable inside a regex we must build the regex into a variable # if this is not true please clean this ugly hack up a little # FIXME: Is this really needed now that we track Channel TS? my $mychanregex = "^:.+?\\sFJOIN\\s$mychan" . "\\s(.+?)\\s.+?\$"; # timestamp of the control channel (used to op the psedoclient) my $mychants = 0; # flag used to determine if the globops module is loaded my $capabglobops = 0; sub link_init { srand(time); if (!main::depends("core-v1")) { print "This module requires version 1.x of defender.\n"; exit(0); } main::provides("server","inspircd-server","native-gline","native-globops","uuidnick","nickuuid"); } sub rawirc { my $out = $_[0]; my $first = "$out\r\n"; syswrite(SH, $first, length($first)); print ">> $out\n" if $debug; } sub mode { my ($dest,$line) = @_; my $time = time(); $line = ":$sid FMODE $dest $chants{$dest} $line"; &rawirc($line); } sub privmsg { my $nick = $_[0]; my $msg = $_[1]; my $first = ":\$uuid PRIVMSG $nick :$msg\r\n"; syswrite(SH, $first, length($first)); } sub notice { my $nick = $_[0]; my $msg = $_[1]; my $first = ":$uuid NOTICE $nick :$msg\r\n"; syswrite(SH, $first, length($first)); } sub message { my $line = $_[0]; $line = ":$uuid PRIVMSG $mychan :$line"; &rawirc($line); } sub globops { my $msg = $_[0]; &rawirc(":$uuid SNONOTICE g :$msg"); } sub message_to { my ($dest,$line) = @_; $line = ":$uuid PRIVMSG $dest :$line"; &rawirc($line); } sub killuser { my($nick,$reason) = @_; &rawirc(":$uuid KILL $nickuuid{$nick} :Killed ($botnick ($reason))"); $KILLED++; } sub gline { my($hostname,$duration,$reason) = @_; &rawirc(":$uuid GLINE $hostname $duration :$reason"); $KILLED++; } sub gethost { my($nick) = @_; $nick = lc($nick); return $hosts{$nick}; } sub isoper { my($nick) = @_; if ($hosts{lc($nick)}{isoper}) { return 1; } else { return 0; } } sub getmatching { my @results = (); my($re) = @_; foreach my $mask (%hosts) { if (defined($hosts{$mask})) { if ($hosts{$mask} =~ /$re/i) { push @results, $mask; } } } return @results; } sub connect { $CONNECT_TYPE = "Server"; print ("Creating socket...\n"); socket(SH, PF_INET, SOCK_STREAM, getprotobyname('tcp')) || print "socket() failed: $!\n"; if (defined($main::dataValues{"bind"})) { print "Bound to ip address: " . $main::dataValues{"bind"} . "\n"; bind(SH, sockaddr_in(0, inet_aton($main::dataValues{"bind"}))); } else { bind(SH, sockaddr_in(0, INADDR_ANY)); } print ("Connecting to $server\:$port...\n"); my $sin = sockaddr_in ($port,inet_aton($server)); connect(SH,$sin) || print "Could not connect to server: $!\n"; print ("Logging in...\n"); &rawirc("SERVER $servername $password 0 $sid :$serverdesc"); } sub finishburst { $now = time(); &rawirc(":$sid BURST $now"); &rawirc(":$sid VERSION :IRC Defender $VERSION"); print ("Introducing pseudoclient: $botnick...\n"); my $now = time; &rawirc(":$sid UID $uuid $now $botnick $domain $domain $botnick 0.0.0.0 $now +oi :$botname"); print ("Joining channel...\n"); $t = time(); &rawirc(":$sid FJOIN $mychan $t +nt :o,$uuid"); $njservername = $servername; $njtime = time+10; $NETJOIN = 1; $now = time(); &rawirc(":$sid ENDBURST $now"); } sub pingreply { &rawirc(":$sid PONG " . $_[0]); } sub reconnect { close SH; &connect; } my $njtime = time+10; sub checkmodes { # this sub checks a nick's modes to see if theyre an oper or not # if they have +o theyre judged as being oper, and are inserted # into an @opers list which is used by non-native globops. my ($nick,$modes) = @_; if ($modes =~ /^-/) { # taking modes if ($modes =~ /^-.*o.*$/) { $hosts{lc($nick)}{isoper} = 0; } } } sub poll { $KILLED = 0; $CONNECTS = 0; while (chomp($buffer = )) { chop($buffer); print "<< $buffer\n" if $debug; if (($NETJOIN != 0) && (time > $njtime)) { $NETJOIN = 0; print "$njservername completed NETJOIN state (merge time exceeded)\n"; } if ($buffer =~ /^ERROR\s:(.+?)$/) { print "ERROR received from ircd: $1\n"; print "You might need to check your C/N lines or link block on the ircd, or port number you are using.\n"; exit(0); } if ($buffer =~ /^CAPAB\s(.+?)$/) # Read the CAPAB line { if ($1 =~ /^MODULES\s.*?m_globops\.so.*?$/) # Set the globops flag if the module is loaded { $capabglobops = 1; } elsif ($1 =~ /^END$/) # End of CAPAB lines, check if the module is loaded, print error and die if not { if ($capabglobops == 0) { print "Defender requires the module m_globops.so to be loaded in the IRCd.\n"; exit(0); } } } # continued ugly hack to get the timestamp of the control channel # now we use the regex to extract the timestamp and op the pseudoclient elsif ($buffer =~ /$mychanregex/ && $mychants eq 0) { $mychants = $1; &rawirc(":$uuid FMODE $mychan $mychants +o $uuid"); } # set netjoin state again to squelch possible noise from verbose modules elsif ($buffer =~ /^:.+? SERVER .+? .+? .+? .+? :.+?$/) { print "Server connection detected, setting NETJOIN\n"; $NETJOIN = 1; $njtime = time+10; } elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) PRIVMSG (.+?) :(.+?)$/) { &msghandler($buffer); } elsif ($buffer =~ /^SERVER .+? .+? (.+) :.+?$/) { finishburst(); } elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) NICK (.+?) \d{1,10}$/) { $oldnick = $uuidnick{quotemeta($1)}; $newnick = quotemeta($2); $uuidnick{quotemeta($1)}=$newnick; delete $nickuuid{$oldnick}; $nickuuid{$newnick}=quotemeta($1); $hosts{lc(quotemeta($2))} = $hosts{lc(quotemeta($1))}; foreach $mod (@modlist) { # FIXME: Eval is bad. If you don't believe me, ask anonops. --Thunderhacker eval ("Modules::Scan::" . $mod ."::handle_nick(\"$oldnick\",\"$newnick\")"); } } # << :144 UID 144AAAAAB 1234614139 User 10.0.0.2 10.0.0.2 TH 10.0.0.2 1234614144 + :Thunderhacker # SID UID TS NICK HOST VHST IDENT IP SIGN MODES PARAMS GECOS elsif ($buffer =~ /^:([0-9][A-Z0-9]{2}) UID ([0-9][A-Z0-9]{8}) \d{1,10} (.{1,63}) ([a-zA-Z0-9\-\.]+?) [a-zA-Z0-9\-\.]+? ([a-zA-Z0-9]{1,13}) .+? \d{1,10} \+[A-Za-z0-9 ]? :(.+?)$/) # 1 2 3 4 5 6 # New client introduced. Parse the UID line and extract the parts we need { $theuuid = quotemeta($2); $thenick = quotemeta($3); $thehost = quotemeta($4); $theident = quotemeta($5); $theserver = quotemeta($1); $thegecos = quotemeta($6); $CONNECTS++; # update uuid <-> nick tracking tables $uuidnick{$theuuid} = $thenick; $nickuuid{$thenick} = $theuuid; $hosts{lc($thenick)} = "$theident\@$thehost"; foreach $mod (@modlist) { my $func = ("Modules::Scan::" . $mod . "::scan_user(\"$theident\",\"$thehost\",\"$theserver\",\"$thenick\",\"$thegecos\",0)"); # FIXME: Eval is bad. If you don't believe me, ask anonops. --Thunderhacker eval $func; print $@ if $@; } } # :787AAAAAK TOPIC #Testing :"New topic!" elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) TOPIC ([#|\$].+?) :(.+?)$/) { $thenick = $uuidnick{quotemeta($1)}; $thetarget = quotemeta($2); $params = $3; $params =~ s/^\://; $params = quotemeta($params); foreach $mod (@modlist) { my $func = ("Modules::Scan::" . $mod . "::handle_topic(\"$thenick\",\"$thetarget\",\"$params\")"); # FIXME: Eval is bad. If you don't believe me, ask anonops. --Thunderhacker eval $func; } } # :787AAAAAK KILL 000AAAAAA :Killed (Thunderhacker (kill test)) elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) KILL ([0-9][A-Z0-9]{8}) :(.+?)$/) { my $killedby = quotemeta($1); my $killnick = quotemeta($2); my $killreason = quotemeta($3); my $killregex = "$uuid"; if ($killnick =~ /^$killregex$/i) { $now = time(); &rawirc(":$sid UID $uuid $now $botnick $domain $domain $botnick 0.0.0.0 $now +oi :$botname"); &rawirc(":$sid FJOIN $mychan $mychants * :o,$uuid"); &rawirc(":$killedby OPERQUIT :Killed ($botnick (DON'T KILL ME!))"); &rawirc(":$uuid KILL $killedby :Killed ($botnick (DON'T KILL ME!))"); } } elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) QUIT :(.+?)$/) { my $quituuid = quotemeta($1); my $quitreason = quotemeta($2); # delete uuid|nick pair $quitnick = $uuidnick{quotemeta($1)}; delete $uuidnick{$quituuid}; delete $nickuuid{$quitnick}; delete $hosts{$quitnick}; foreach $mod (@modlist) { my $func = ("Modules::Scan::" . $mod . "::handle_quit(\"$quitnick\",\"$quitreason\")"); # FIXME: Eval is bad. If you don't believe me, ask anonops. --Thunderhacker eval $func; } } elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) JOIN ([#|\$].+?) \d{1,10}$/) { $thenick = $uuidnick{quotameta($1)}; $thetarget = quotemeta($2); # deal effectively with multiple chan joins my @chanlist = split(' ',$thetarget); $chan = quotemeta($chanlist[0]); foreach $mod (@modlist) { my $func = ("Modules::Scan::" . $mod . "::handle_join(\"$thenick\",\"$chan\")"); # FIXME: Eval is bad. If you don't believe me, ask anonops. --Thunderhacker eval $func; } } elsif ($buffer =~ /^:[0-9][A-Z0-9]{2} FJOIN ([#|\$].+?) (\d{1,10}) \+.* (.+?)$/) { $channel = quotemeta($1); $nicklist = quotemeta($3); @nicks = split(' ',$nicklist); foreach my $nick (@nicks) { (undef,$nick) = split(',',$nick); foreach $mod (@modlist) { my $func = ("Modules::Scan::" . $mod . "::handle_join(\"$uuidnick{$nick}\",\"$channel\")"); # FIXME: Eval is bad. If you don't believe me, ask anonops. --Thunderhacker eval $func; } } $chants{quotemeta($1)} = quotemeta($2); } elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) KICK ([0-9][A-Z0-9]{8}) ([#|\$].+?) :(.+?)$/) { $nick = $uuidnick{quotemeta($1)}; $channel = quotemeta($2); $kicked = $uuidnick{quotemeta($3)}; $reason = quotemeta($4); foreach $mod (@modlist) { my $func = ("Modules::Scan::" . $mod . "::handle_kick(\"$nick\",\"$channel\",\"$kicked\",\"$reason\")"); # FIXME: Eval is bad. If you don't believe me, ask anonops. --Thunderhacker eval $func; } } # :787AAAAAE PART #Control :leaving elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) PART ([#|\$].+?)( :.+?)?$/) { $thenick = $uuidnick{quotemeta($1)}; $thetarget = $2; if ($thetarget =~ / /) { $thetarget = split(" ",$thetarget); } $thetarget = quotemeta($thetarget); my @chanlist = split(',',$thetarget); foreach my $chan (@chanlist) { $chan = quotemeta($chan); foreach $mod (@modlist) { my $func = ("Modules::Scan::" . $mod . "::handle_part(\"$thenick\",\"$thetarget\")"); # FIXME: Eval is bad. If you don't believe me, ask anonops. --Thunderhacker eval $func; } } $chants{$1} = quotemeta($2); } elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) NOTICE ([0-9][A-Z0-9]{8}) :(.+?)$/) { $buffer = ":" . $uuidnick{quotemeta($1)} . " NOTICE Defender :" . quotemeta($3); ¬icehandler($buffer); } elsif ($buffer =~ /^:[0-9][A-Z0-9]{2} PING [0-9][A-Z0-9]{2} [0-9][A-Z0-9]{2}$/) { $buffer=~/^:[0-9][A-Z0-9]{2} PING ([0-9][A-Z0-9]{2}) ([0-9][A-Z0-9]{2})$/; &pingreply("$2 $1"); } elsif ($buffer =~ /^:([0-9][A-Z0-9]{8}) OPERTYPE .+$/) # Fix by Wulf @ forums # Emulates the +o mode and sends to other modules { $opernick=$uuidnick{quotemeta($1)}; $hosts{lc($opernick)}{isoper} = 1; $thetarget = $opernick; $params = "+o"; &checkmodes($thetarget,$params); foreach $mod (@modlist) { my $func = ("Modules::Scan::" . $mod . "::handle_mode(\"$servername\",\"$thetarget\",\"$params\")"); # FIXME: Eval is bad. If you don't believe me, ask anonops. --Thunderhacker eval $func; } } } } # sig handler sub shutdown { #print "SIGINT caught\n"; &rawirc(":$botnick QUIT :Defender terminating"); print("Disconnecting from irc server (SIGINT)\n"); close SH; exit; } sub handle_alarm { } 1;