2023-05-25 20:03:39 +00:00
#!/usr/bin/env python
# internet relay chat probe for https://internetrelaychat.org/ - developed by acidvegas in python (https://git.acid.vegas/ircp)
import asyncio
2023-06-18 21:19:52 +00:00
import ipaddress
2023-05-25 20:03:39 +00:00
import json
import os
import random
import ssl
import sys
2023-07-24 02:45:17 +00:00
import tarfile
2023-05-25 20:03:39 +00:00
import time
class settings :
2023-05-30 20:59:24 +00:00
daemon = False # Run in daemon mode (24/7 throttled scanning)
2023-05-30 00:59:17 +00:00
errors = True # Show errors in console
errors_conn = False # Show connection errors in console
2023-06-29 05:02:41 +00:00
log_max = 5000000 # 5mb # Maximum log size (in bytes) before starting another
2023-05-30 00:59:17 +00:00
nickname = ' IRCP ' # None = random
username = ' ircp ' # None = random
realname = ' scan@internetrelaychat.org ' # None = random
ns_mail = ' scan@internetrelaychat.org ' # None = random@random.[com|net|org]
2023-06-29 05:02:41 +00:00
ns_pass = None # None = random
2023-05-30 00:59:17 +00:00
vhost = None # Bind to a specific IP address
2023-05-25 20:03:39 +00:00
class throttle :
2023-06-29 05:02:41 +00:00
channels = 5 if not settings . daemon else 3 # Maximum number of channels to scan at once
commands = 1.5 if not settings . daemon else 3 # Delay bewteen multiple commands send to the same target
connect = 15 if not settings . daemon else 60 # Delay between each connection attempt on a diffferent port
delay = 300 if not settings . daemon else 600 # Delay before registering nick (if enabled) & sending /LIST
join = 10 if not settings . daemon else 30 # Delay between channel JOINs
nick = 900 if not settings . daemon else 1200 # Delay between every random NICK change
part = 10 if not settings . daemon else 30 # Delay before PARTing a channel
seconds = 300 if not settings . daemon else 600 # Maximum seconds to wait when throttled for JOIN or WHOIS
threads = 500 if not settings . daemon else 300 # Maximum number of threads running
timeout = 30 if not settings . daemon else 60 # Timeout for all sockets
whois = 15 if not settings . daemon else 30 # Delay between WHOIS requests
ztimeout = 600 if not settings . daemon else 900 # Timeout for zero data from server
2023-05-25 20:03:39 +00:00
2023-06-06 05:17:01 +00:00
class bad :
donotscan = (
' irc.dronebl.org ' , ' irc.alphachat.net ' ,
' 5.9.164.48 ' , ' 45.32.74.177 ' , ' 104.238.146.46 ' , ' 149.248.55.130 ' ,
' 2001:19f0:6001:1dc::1 ' , ' 2001:19f0:b001:ce3::1 ' , ' 2a01:4f8:160:2501:48:164:9:5 ' , ' 2001:19f0:6401:17c::1 '
)
chan = {
' 403 ' : ' ERR_NOSUCHCHANNEL ' , ' 405 ' : ' ERR_TOOMANYCHANNELS ' ,
' 435 ' : ' ERR_BANONCHAN ' , ' 442 ' : ' ERR_NOTONCHANNEL ' ,
' 448 ' : ' ERR_FORBIDDENCHANNEL ' , ' 470 ' : ' ERR_LINKCHANNEL ' ,
' 471 ' : ' ERR_CHANNELISFULL ' , ' 473 ' : ' ERR_INVITEONLYCHAN ' ,
' 474 ' : ' ERR_BANNEDFROMCHAN ' , ' 475 ' : ' ERR_BADCHANNELKEY ' ,
' 476 ' : ' ERR_BADCHANMASK ' , ' 477 ' : ' ERR_NEEDREGGEDNICK ' ,
' 479 ' : ' ERR_BADCHANNAME ' , ' 480 ' : ' ERR_THROTTLE ' ,
' 485 ' : ' ERR_CHANBANREASON ' , ' 488 ' : ' ERR_NOSSL ' ,
' 489 ' : ' ERR_SECUREONLYCHAN ' , ' 519 ' : ' ERR_TOOMANYUSERS ' ,
' 520 ' : ' ERR_OPERONLY ' , ' 926 ' : ' ERR_BADCHANNEL '
}
error = {
' install identd ' : ' Identd required ' ,
' trying to reconnect too fast ' : ' Throttled ' ,
' trying to (re)connect too fast ' : ' Throttled ' ,
' reconnecting too fast ' : ' Throttled ' ,
' access denied ' : ' Access denied ' ,
' not authorized to ' : ' Not authorized ' ,
' not authorised to ' : ' Not authorized ' ,
' password mismatch ' : ' Password mismatch ' ,
' dronebl ' : ' DroneBL ' ,
' dnsbl ' : ' DNSBL ' ,
' g:lined ' : ' G:Lined ' ,
' z:lined ' : ' Z:Lined ' ,
' timeout ' : ' Timeout ' ,
' closing link ' : ' Banned ' ,
' banned ' : ' Banned ' ,
' client exited ' : ' QUIT ' ,
' quit ' : ' QUIT '
}
2023-06-06 01:36:25 +00:00
2023-05-30 20:59:24 +00:00
def backup ( name ) :
try :
with tarfile . open ( f ' backup/ { name } .tar.gz ' , ' w:gz ' ) as tar :
for log in os . listdir ( ' logs ' ) :
tar . add ( ' logs/ ' + log )
debug ( ' \033 [1;32mBACKUP COMPLETE \033 [0m ' )
for log in os . listdir ( ' logs ' ) :
os . remove ( ' logs/ ' + log )
except Exception as ex :
error ( ' \033 [1;31mBACKUP FAILED \033 [0m ' , ex )
2023-05-25 20:03:39 +00:00
def debug ( data ) :
2023-06-26 03:27:43 +00:00
print ( ' {0} \033 [1;30m| \033 [0m [ \033 [35m~ \033 [0m] {1} ' . format ( time . strftime ( ' % I: % M: % S ' ) , data ) )
2023-05-25 20:03:39 +00:00
def error ( data , reason = None ) :
if settings . errors :
2023-06-26 03:27:43 +00:00
print ( ' {0} \033 [1;30m| \033 [0m [ \033 [31m! \033 [0m] {1} \033 [1;30m( {2} ) \033 [0m ' . format ( time . strftime ( ' % I: % M: % S ' ) , data , str ( reason ) ) ) if reason else print ( ' {0} \033 [1;30m| \033 [0m [ \033 [31m! \033 [0m] {1} ' . format ( time . strftime ( ' % I: % M: % S ' ) , data ) )
2023-05-25 20:03:39 +00:00
def rndnick ( ) :
prefix = random . choice ( [ ' st ' , ' sn ' , ' cr ' , ' pl ' , ' pr ' , ' fr ' , ' fl ' , ' qu ' , ' br ' , ' gr ' , ' sh ' , ' sk ' , ' tr ' , ' kl ' , ' wr ' , ' bl ' ] + list ( ' bcdfgklmnprstvwz ' ) )
midfix = random . choice ( ( ' aeiou ' ) ) + random . choice ( ( ' aeiou ' ) ) + random . choice ( ( ' bcdfgklmnprstvwz ' ) )
suffix = random . choice ( [ ' ed ' , ' est ' , ' er ' , ' le ' , ' ly ' , ' y ' , ' ies ' , ' iest ' , ' ian ' , ' ion ' , ' est ' , ' ing ' , ' led ' , ' inger ' ] + list ( ' abcdfgklmnprstvwz ' ) )
return prefix + midfix + suffix
def ssl_ctx ( ) :
ctx = ssl . create_default_context ( )
ctx . check_hostname = False
ctx . verify_mode = ssl . CERT_NONE
return ctx
class probe :
2023-06-05 22:41:08 +00:00
def __init__ ( self , semaphore , server , port , family = 2 ) :
2023-06-18 04:03:18 +00:00
self . semaphore = semaphore
2023-05-25 20:03:39 +00:00
self . server = server
2023-06-06 05:17:01 +00:00
self . port = 6697
self . oport = port
2023-06-18 04:03:18 +00:00
self . family = family
2023-06-26 03:27:43 +00:00
self . display = server . ljust ( 18 ) + ' \033 [1;30m| \033 [0m unknown network \033 [1;30m| \033 [0m '
2023-05-27 22:47:43 +00:00
self . nickname = None
2023-05-30 00:59:17 +00:00
self . multi = ' '
2023-06-18 04:03:18 +00:00
self . snapshot = dict ( )
2023-05-27 22:47:43 +00:00
self . channels = { ' all ' : list ( ) , ' current ' : list ( ) , ' users ' : dict ( ) }
2023-05-30 19:08:52 +00:00
self . nicks = { ' all ' : list ( ) , ' check ' : list ( ) }
self . loops = { ' init ' : None , ' chan ' : None , ' nick ' : None , ' whois ' : None }
2023-06-17 19:45:48 +00:00
self . login = { ' pass ' : settings . ns_pass if settings . ns_pass else rndnick ( ) , ' mail ' : settings . ns_mail if settings . ns_mail else f ' { rndnick ( ) } @ { rndnick ( ) } . ' + random . choice ( ( ' com ' , ' net ' , ' org ' ) ) }
2023-06-17 19:54:43 +00:00
self . services = { ' chanserv ' : True , ' nickserv ' : True }
2023-06-18 04:03:18 +00:00
self . jthrottle = throttle . join
2023-06-18 23:02:57 +00:00
self . nthrottle = throttle . whois
2023-05-25 20:03:39 +00:00
self . reader = None
2023-05-29 20:25:38 +00:00
self . write = None
2023-05-25 20:03:39 +00:00
2023-06-18 22:02:22 +00:00
async def sendmsg ( self , target , msg ) :
await self . raw ( f ' PRIVMSG { target } : { msg } ' )
2023-05-25 20:03:39 +00:00
async def run ( self ) :
async with self . semaphore :
try :
2023-06-06 05:17:01 +00:00
await self . connect ( ) # 6697
2023-05-25 20:03:39 +00:00
except Exception as ex :
2023-05-30 01:27:18 +00:00
if settings . errors_conn :
2023-06-06 05:17:01 +00:00
error ( self . display + ' \033 [1;31mdisconnected \033 [0m - failed to connect using SSL/TLS on port ' + str ( self . port ) , ex )
if self . oport not in ( 6667 , 6697 ) :
self . port = self . oport
await asyncio . sleep ( throttle . connect )
try :
await self . connect ( ) # Non-standard
except Exception as ex :
if settings . errors_conn :
error ( self . display + ' \033 [1;31mdisconnected \033 [0m - failed to connect using SSL/TLS on port ' + str ( self . port ) , ex )
self . port = 6667
await asyncio . sleep ( throttle . connect )
try :
await self . connect ( True ) # 6667
except Exception as ex :
if settings . errors_conn :
error ( self . display + ' \033 [1;31mdisconnected \033 [0m - failed to connect on port ' + str ( self . port ) , ex )
self . port = self . oport
await asyncio . sleep ( throttle . connect )
try :
await self . connect ( True ) # Non-standard
except Exception as ex :
if settings . errors_conn :
error ( self . display + ' \033 [1;31mdisconnected \033 [0m - failed to connect on port ' + str ( self . port ) , ex )
else :
self . port = 6667
await asyncio . sleep ( throttle . connect )
try :
await self . connect ( True ) # 6667
except Exception as ex :
if settings . errors_conn :
error ( self . display + ' \033 [1;31mdisconnected \033 [0m - failed to connect on port ' + str ( self . port ) , ex )
2023-05-25 20:03:39 +00:00
async def raw ( self , data ) :
self . writer . write ( data [ : 510 ] . encode ( ' utf-8 ' ) + b ' \r \n ' )
await self . writer . drain ( )
async def connect ( self , fallback = False ) :
options = {
' host ' : self . server ,
2023-06-06 05:17:01 +00:00
' port ' : self . port ,
2023-05-25 20:03:39 +00:00
' limit ' : 1024 ,
' ssl ' : None if fallback else ssl_ctx ( ) ,
2023-06-05 22:41:08 +00:00
' family ' : self . family ,
2023-06-29 04:31:51 +00:00
' local_addr ' : ( settings . vhost , random . randint ( 5000 , 65000 ) ) if settings . vhost else None
2023-05-25 20:03:39 +00:00
}
identity = {
' nick ' : settings . nickname if settings . nickname else rndnick ( ) ,
' user ' : settings . username if settings . username else rndnick ( ) ,
' real ' : settings . realname if settings . realname else rndnick ( )
}
2023-05-28 02:16:31 +00:00
self . nickname = identity [ ' nick ' ]
2023-05-25 20:03:39 +00:00
self . reader , self . writer = await asyncio . wait_for ( asyncio . open_connection ( * * options ) , throttle . timeout )
2023-06-26 03:27:43 +00:00
self . snapshot [ ' port ' ] = options [ ' port ' ]
2023-05-30 00:59:17 +00:00
del options
2023-05-26 23:59:31 +00:00
if not fallback :
self . snapshot [ ' ssl ' ] = True
2023-05-25 20:03:39 +00:00
await self . raw ( ' USER {0} 0 * : {1} ' . format ( identity [ ' user ' ] , identity [ ' real ' ] ) )
await self . raw ( ' NICK ' + identity [ ' nick ' ] )
2023-05-30 00:59:17 +00:00
del identity
2023-05-25 20:03:39 +00:00
await self . listen ( )
2023-05-26 07:26:45 +00:00
for item in self . loops :
if self . loops [ item ] :
self . loops [ item ] . cancel ( )
2023-05-30 00:59:17 +00:00
with open ( f ' logs/ { self . server } .json { self . multi } ' , ' w ' ) as fp :
2023-05-25 20:03:39 +00:00
json . dump ( self . snapshot , fp )
2023-05-27 22:47:43 +00:00
debug ( self . display + ' finished scanning ' )
2023-05-25 20:03:39 +00:00
async def loop_initial ( self ) :
try :
await asyncio . sleep ( throttle . delay )
2023-06-22 04:21:08 +00:00
cmds = [ ' ADMIN ' , ' CAP LS ' , ' COMMANDS ' , ' HELP ' , ' INFO ' , ' IRCOPS ' , ' LINKS ' , ' MAP ' , ' MODULES -all ' , ' SERVLIST ' , ' STATS p ' , ' VERSION ' ]
2023-05-30 06:35:57 +00:00
random . shuffle ( cmds )
2023-06-18 19:47:38 +00:00
cmds + = [ ' PRIVMSG NickServ :REGISTER {0} {1} ' . format ( self . login [ ' pass ' ] , self . login [ ' mail ' ] ) , ' PRIVMSG ChanServ :LIST * ' , ' PRIVMSG NickServ :LIST * ' , ' LIST ' ]
2023-05-30 06:35:57 +00:00
for command in cmds :
2023-05-25 20:03:39 +00:00
try :
await self . raw ( command )
except :
break
else :
2023-06-26 03:27:43 +00:00
await asyncio . sleep ( throttle . commands )
2023-05-25 20:03:39 +00:00
if not self . channels [ ' all ' ] :
2023-05-30 19:08:52 +00:00
error ( self . display + ' \033 [31merror \033 [0m - no channels found ' )
2023-05-25 20:03:39 +00:00
await self . raw ( ' QUIT ' )
except asyncio . CancelledError :
pass
2023-05-27 23:15:08 +00:00
except Exception as ex :
2023-05-30 19:08:52 +00:00
error ( self . display + ' \033 [31merror \033 [0m - loop_initial ' , ex )
2023-05-25 20:03:39 +00:00
async def loop_channels ( self ) :
try :
while self . channels [ ' all ' ] :
while len ( self . channels [ ' current ' ] ) > = throttle . channels :
await asyncio . sleep ( 1 )
2023-05-29 20:25:38 +00:00
await asyncio . sleep ( self . jthrottle )
2023-05-30 00:59:17 +00:00
chan = random . choice ( self . channels [ ' all ' ] )
2023-05-25 20:03:39 +00:00
self . channels [ ' all ' ] . remove ( chan )
try :
2023-06-17 19:54:43 +00:00
if self . services [ ' chanserv ' ] :
2023-06-17 19:45:48 +00:00
await self . sendmsg ( ' ChanServ ' , ' INFO ' + chan )
2023-06-26 03:27:43 +00:00
await asyncio . sleep ( throttle . commands )
2023-05-25 20:03:39 +00:00
await self . raw ( ' JOIN ' + chan )
except :
break
2023-05-28 03:43:19 +00:00
self . loops [ ' nick ' ] . cancel ( )
2023-05-25 20:03:39 +00:00
while self . nicks [ ' check ' ] :
await asyncio . sleep ( 1 )
2023-05-28 03:43:19 +00:00
self . loops [ ' whois ' ] . cancel ( )
2023-07-24 02:45:17 +00:00
self . loops [ ' nick ' ] . cancel ( )
2023-05-25 20:03:39 +00:00
await self . raw ( ' QUIT ' )
except asyncio . CancelledError :
pass
2023-05-27 23:15:08 +00:00
except Exception as ex :
2023-05-30 19:08:52 +00:00
error ( self . display + ' \033 [31merror \033 [0m - loop_channels ' , ex )
2023-05-25 20:03:39 +00:00
2023-05-28 03:43:19 +00:00
async def loop_nick ( self ) :
try :
while True :
2023-06-29 05:02:41 +00:00
await asyncio . sleep ( throttle . nick + random . randint ( 60 , 90 ) )
2023-05-28 03:43:19 +00:00
self . nickname = rndnick ( )
2023-05-29 05:23:42 +00:00
await self . raw ( ' NICK ' + self . nickname )
2023-06-26 03:27:43 +00:00
debug ( self . display + ' \033 [0;35mNICK \033 [0m - new identity ' )
2023-05-28 03:43:19 +00:00
except asyncio . CancelledError :
pass
except Exception as ex :
2023-05-30 19:08:52 +00:00
error ( self . display + ' \033 [31merror \033 [0m - loop_nick ' , ex )
2023-05-28 03:43:19 +00:00
2023-05-25 20:03:39 +00:00
async def loop_whois ( self ) :
try :
while True :
if self . nicks [ ' check ' ] :
nick = random . choice ( self . nicks [ ' check ' ] )
self . nicks [ ' check ' ] . remove ( nick )
try :
2023-06-22 05:02:43 +00:00
await self . raw ( ' WHOIS ' + nick )
2023-06-26 03:27:43 +00:00
await asyncio . sleep ( throttle . commands )
2023-06-17 19:54:43 +00:00
if self . services [ ' nickserv ' ] :
await self . sendmsg ( ' NickServ ' , ' INFO ' + nick )
2023-06-26 03:27:43 +00:00
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' NOTICE { nick } \001 VERSION \001 ' ) # TODO: check the database if we already have this information to speed things up
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' NOTICE { nick } \001 TIME \001 ' )
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' NOTICE { nick } \001 CLIENTINFO \001 ' )
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' NOTICE { nick } \001 SOURCE \001 ' )
2023-05-25 20:03:39 +00:00
except :
break
else :
2023-05-30 00:59:17 +00:00
del nick
2023-05-25 20:03:39 +00:00
await asyncio . sleep ( throttle . whois )
else :
await asyncio . sleep ( 1 )
except asyncio . CancelledError :
pass
2023-05-27 23:15:08 +00:00
except Exception as ex :
2023-05-30 19:08:52 +00:00
error ( self . display + ' \033 [31merror \033 [0m - loop_whois ' , ex )
2023-05-25 20:03:39 +00:00
2023-06-22 05:02:43 +00:00
async def db ( self , event , data ) :
if event in self . snapshot :
if data not in self . snapshot [ event ] :
self . snapshot [ event ] . append ( data )
else :
self . snapshot [ event ] = [ data , ]
2023-05-25 20:03:39 +00:00
async def listen ( self ) :
2023-05-27 23:15:08 +00:00
while True :
2023-05-26 07:26:45 +00:00
try :
2023-05-31 23:37:46 +00:00
if self . reader . at_eof ( ) :
2023-05-27 23:15:08 +00:00
break
2023-05-31 23:37:46 +00:00
data = await asyncio . wait_for ( self . reader . readuntil ( b ' \r \n ' ) , throttle . ztimeout )
line = data . decode ( ' utf-8 ' ) . strip ( )
args = line . split ( )
event = args [ 1 ] . upper ( )
if sys . getsizeof ( self . snapshot ) > = settings . log_max :
2023-05-30 00:59:17 +00:00
with open ( f ' logs/ { self . server } .json { self . multi } ' , ' w ' ) as fp :
json . dump ( self . snapshot , fp )
2023-05-31 23:37:46 +00:00
self . snapshot = dict ( )
2023-05-30 00:59:17 +00:00
self . multi = ' .1 ' if not self . multi else ' . ' + str ( int ( self . multi [ 1 : ] ) + 1 )
2023-05-31 23:37:46 +00:00
if args [ 0 ] . upper ( ) == ' ERROR ' :
2023-06-22 05:02:43 +00:00
await self . db ( ' ERROR ' , line )
2023-05-31 23:37:46 +00:00
elif not event . isdigit ( ) and event not in ( ' CAP ' , ' INVITE ' , ' JOIN ' , ' KICK ' , ' KILL ' , ' MODE ' , ' NICK ' , ' NOTICE ' , ' PART ' , ' PRIVMSG ' , ' QUIT ' , ' TOPIC ' , ' WHO ' ) :
2023-06-22 05:02:43 +00:00
await self . db ( ' RAW ' , line )
2023-06-18 05:15:25 +00:00
elif event != ' 401 ' :
2023-06-22 05:02:43 +00:00
await self . db ( event , line )
2023-06-06 05:17:01 +00:00
if event in bad . chan and len ( args ) > = 4 :
2023-05-29 20:25:38 +00:00
chan = args [ 3 ]
if chan in self . channels [ ' users ' ] :
del self . channels [ ' users ' ] [ chan ]
2023-06-06 05:17:01 +00:00
error ( f ' { self . display } \033 [31merror \033 [0m - { chan } ' , bad . chan [ event ] )
2023-05-31 23:37:46 +00:00
elif line . startswith ( ' ERROR : ' ) :
2023-06-06 05:17:01 +00:00
check = [ check for check in bad . error if check in line . lower ( ) ]
2023-06-06 01:36:25 +00:00
if check :
if check [ 0 ] in ( ' dronebl ' , ' dnsbl ' ) :
2023-05-31 23:37:46 +00:00
self . snapshot [ ' proxy ' ] = True
2023-06-06 05:17:01 +00:00
raise Exception ( bad . error [ check [ 0 ] ] )
2023-05-25 20:03:39 +00:00
elif args [ 0 ] == ' PING ' :
await self . raw ( ' PONG ' + args [ 1 ] [ 1 : ] )
2023-06-18 23:31:53 +00:00
elif event == ' KICK ' and len ( args ) > = 4 :
chan = args [ 2 ]
kicked = args [ 3 ]
if kicked == self . nickname :
if chan in self . channels [ ' current ' ] :
self . channels [ ' current ' ] . remove ( chan )
2023-06-17 19:45:48 +00:00
elif event == ' MODE ' and len ( args ) == 4 :
nick = args [ 2 ]
2023-06-26 03:27:43 +00:00
if nick == self . nickname :
2023-06-17 19:45:48 +00:00
mode = args [ 3 ] [ 1 : ]
if mode == ' +r ' :
self . snapshot [ ' registered ' ] = self . login
2023-05-31 23:37:46 +00:00
elif event == ' 001 ' : #RPL_WELCOME
2023-05-25 20:03:39 +00:00
host = args [ 0 ] [ 1 : ]
self . snapshot [ ' server ' ] = self . server
self . snapshot [ ' host ' ] = host
if len ( host ) > 25 :
2023-06-26 03:27:43 +00:00
self . display = f ' { self . server . ljust ( 18 ) } \033 [1;30m| \033 [0m { host [ : 22 ] } ... \033 [1;30m| \033 [0m '
2023-05-25 20:03:39 +00:00
else :
2023-06-26 03:27:43 +00:00
self . display = f ' { self . server . ljust ( 18 ) } \033 [1;30m| \033 [0m { host . ljust ( 25 ) } \033 [1;30m| \033 [0m '
debug ( self . display + f ' \033 [1;32mconnected \033 [0m \033 [1;30m(port { self . port } ) \033 [0m ' )
2023-05-25 20:03:39 +00:00
self . loops [ ' init ' ] = asyncio . create_task ( self . loop_initial ( ) )
2023-06-01 00:59:30 +00:00
elif event == ' 005 ' :
for item in args :
if item . startswith ( ' SSL= ' ) and item [ 4 : ] :
if not self . snapshot [ ' ssl ' ] :
self . snapshot [ ' ssl ' ] = item [ 4 : ]
break
2023-05-31 23:37:46 +00:00
elif event == ' 311 ' and len ( args ) > = 4 : # RPL_WHOISUSER
2023-05-30 19:08:52 +00:00
nick = args [ 3 ]
2023-05-30 21:48:13 +00:00
if ' open proxy ' in line . lower ( ) or ' proxy monitor ' in line . lower ( ) :
self . snapshot [ ' proxy ' ] = True
2023-06-06 01:36:25 +00:00
error ( self . display + ' \033 [93mProxy Monitor detected \033 [0m ' , nick )
2023-05-30 21:48:13 +00:00
else :
debug ( f ' { self . display } \033 [34mWHOIS \033 [0m { nick } ' )
2023-06-29 03:09:59 +00:00
elif event == 315 and len ( args ) > = 3 : # RPL_ENDOFWHO
2023-06-26 03:27:43 +00:00
chan = args [ 3 ]
await self . raw ( f ' MODE { chan } +b ' )
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' MODE { chan } +e ' )
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' MODE { chan } +I ' )
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' NOTICE { chan } \001 VERSION \001 ' )
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' NOTICE { chan } \001 TIME \001 ' )
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' NOTICE { chan } \001 CLIENTINFO \001 ' )
await asyncio . sleep ( throttle . commands )
await self . raw ( f ' NOTICE { chan } \001 SOURCE \001 ' )
await asyncio . sleep ( throttle . part )
await self . raw ( ' PART ' + chan )
self . channels [ ' current ' ] . remove ( chan )
2023-05-31 23:37:46 +00:00
elif event == ' 322 ' and len ( args ) > = 4 : # RPL_LIST
2023-05-25 20:03:39 +00:00
chan = args [ 3 ]
2023-05-31 04:33:36 +00:00
users = args [ 4 ]
if users != ' 0 ' : # no need to JOIN empty channels...
self . channels [ ' all ' ] . append ( chan )
2023-05-30 00:59:17 +00:00
self . channels [ ' users ' ] [ chan ] = users
2023-05-31 23:37:46 +00:00
elif event == ' 323 ' : # RPL_LISTEND
2023-05-25 20:03:39 +00:00
if self . channels [ ' all ' ] :
2023-05-31 04:23:03 +00:00
debug ( self . display + ' \033 [36mLIST \033 [0m found \033 [93m {0} \033 [0m channel(s) ' . format ( str ( len ( self . channels [ ' all ' ] ) ) ) )
2023-05-28 03:43:19 +00:00
self . loops [ ' chan ' ] = asyncio . create_task ( self . loop_channels ( ) )
self . loops [ ' nick ' ] = asyncio . create_task ( self . loop_nick ( ) )
self . loops [ ' whois ' ] = asyncio . create_task ( self . loop_whois ( ) )
2023-05-31 23:37:46 +00:00
elif event == ' 352 ' and len ( args ) > = 8 : # RPL_WHORPL
2023-05-25 20:03:39 +00:00
nick = args [ 7 ]
2023-05-31 05:46:53 +00:00
if nick not in self . nicks [ ' all ' ] + [ self . nickname , ] :
2023-05-25 20:03:39 +00:00
self . nicks [ ' all ' ] . append ( nick )
self . nicks [ ' check ' ] . append ( nick )
2023-05-31 23:37:46 +00:00
elif event == ' 366 ' and len ( args ) > = 4 : # RPL_ENDOFNAMES
2023-05-25 20:03:39 +00:00
chan = args [ 3 ]
self . channels [ ' current ' ] . append ( chan )
2023-06-18 21:56:47 +00:00
if chan in self . channels [ ' users ' ] :
2023-06-26 03:27:43 +00:00
debug ( ' {0} \033 [32mJOIN \033 [0m {1} \033 [1;30m(found \033 [93m {2} \033 [1;30m users) \033 [0m ' . format ( self . display , chan , self . channels [ ' users ' ] [ chan ] ) )
2023-06-18 21:56:47 +00:00
del self . channels [ ' users ' ] [ chan ]
2023-05-25 20:03:39 +00:00
await self . raw ( ' WHO ' + chan )
2023-06-17 19:45:48 +00:00
elif event == ' 401 ' and len ( args ) > = 4 : # ERR_NOSUCHNICK
nick = args [ 3 ]
if nick == ' ChanServ ' :
2023-06-17 19:54:43 +00:00
self . services [ ' chanserv ' ] = False
2023-06-17 20:09:37 +00:00
elif nick == ' NickServ ' :
2023-06-17 19:54:43 +00:00
self . services [ ' nickserv ' ] = False
2023-06-17 20:09:37 +00:00
else :
await self . raw ( ' WHOWAS ' + nick )
2023-05-31 23:37:46 +00:00
elif event == ' 421 ' and len ( args ) > = 3 : # ERR_UNKNOWNCOMMAND
2023-05-25 20:03:39 +00:00
msg = ' ' . join ( args [ 2 : ] )
if ' You must be connected for ' in msg :
2023-05-30 19:08:52 +00:00
error ( self . display + ' \033 [31merror \033 [0m - delay found ' , msg )
2023-05-31 23:37:46 +00:00
elif event == ' 433 ' : # ERR_NICKINUSE
2023-06-26 03:27:43 +00:00
self . nickname = rndnick ( )
2023-06-18 03:24:35 +00:00
await self . raw ( ' NICK ' + self . nickname )
2023-06-18 23:06:54 +00:00
elif event == ' 439 ' and len ( args ) > = 11 : # ERR_TARGETTOOFAST
target = args [ 3 ]
msg = ' ' . join ( args [ 4 : ] ) [ 1 : ]
seconds = args [ 10 ]
2023-06-18 17:44:32 +00:00
if target [ : 1 ] in ( ' # ' , ' & ' ) :
2023-06-18 17:45:41 +00:00
self . channels [ ' all ' ] . append ( target )
2023-06-18 23:06:54 +00:00
if seconds . isdigit ( ) :
self . jthrottle = throttle . seconds if int ( seconds ) > throttle . seconds else int ( seconds )
2023-06-18 17:44:32 +00:00
else :
2023-06-18 17:45:41 +00:00
self . nicks [ ' check ' ] . append ( target )
2023-05-29 20:25:38 +00:00
if seconds . isdigit ( ) :
2023-06-18 23:06:54 +00:00
self . nthrottle = throttle . seconds if int ( seconds ) > throttle . seconds else int ( seconds )
2023-06-18 22:12:37 +00:00
error ( self . display + ' \033 [31merror \033 [0m - delay found for ' + target , msg )
2023-06-18 19:47:38 +00:00
elif event == ' 465 ' and len ( args ) > = 5 : # ERR_YOUREBANNEDCREEP
2023-06-06 05:17:01 +00:00
check = [ check for check in bad . error if check in line . lower ( ) ]
2023-06-06 01:36:25 +00:00
if check :
if check [ 0 ] in ( ' dronebl ' , ' dnsbl ' ) :
self . snapshot [ ' proxy ' ] = True
2023-06-06 05:17:01 +00:00
raise Exception ( bad . error [ check [ 0 ] ] )
2023-05-31 23:37:46 +00:00
elif event == ' 464 ' : # ERR_PASSWDMISMATCH
2023-05-27 22:47:43 +00:00
raise Exception ( ' Network has a password ' )
2023-05-31 23:37:46 +00:00
elif event == ' 487 ' : # ERR_MSGSERVICES
2023-05-31 04:59:18 +00:00
if ' " /msg NickServ " is no longer supported ' in line :
2023-06-17 19:45:48 +00:00
await self . raw ( ' /NickServ REGISTER {0} {1} ' . format ( self . login [ ' pass ' ] , self . login [ ' mail ' ] ) )
2023-05-31 23:37:46 +00:00
elif event == ' KILL ' :
2023-05-30 19:08:52 +00:00
nick = args [ 2 ]
if nick == self . nickname :
raise Exception ( ' KILL ' )
2023-05-31 23:37:46 +00:00
elif event in ( ' NOTICE ' , ' PRIVMSG ' ) and len ( args ) > = 4 :
2023-05-27 22:47:43 +00:00
nick = args [ 0 ] . split ( ' ! ' ) [ 1 : ]
target = args [ 2 ]
msg = ' ' . join ( args [ 3 : ] ) [ 1 : ]
if target == self . nickname :
2023-05-30 21:38:20 +00:00
for i in ( ' proxy ' , ' proxys ' , ' proxies ' ) :
if i in msg . lower ( ) :
self . snapshot [ ' proxy ' ] = True
2023-05-31 23:37:46 +00:00
check = [ x for x in ( ' bopm ' , ' hopm ' ) if x in line ]
2023-05-30 21:38:20 +00:00
if check :
2023-06-29 04:31:51 +00:00
error ( f ' { self . display } \033 [93m { check [ 0 ] . upper ( ) } detected \033 [0m ' )
2023-05-30 21:38:20 +00:00
else :
2023-06-05 22:07:36 +00:00
error ( self . display + ' \033 [93mProxy Monitor detected \033 [0m ' )
2023-05-27 21:33:44 +00:00
for i in ( ' You must have been using this nick for ' , ' You must be connected for ' , ' not connected long enough ' , ' Please wait ' , ' You cannot list within the first ' ) :
if i in msg :
2023-05-30 19:08:52 +00:00
error ( self . display + ' \033 [31merror \033 [0m - delay found ' , msg )
2023-05-27 22:47:43 +00:00
break
if msg [ : 8 ] == ' \001 VERSION ' :
2023-05-28 02:19:37 +00:00
version = random . choice ( ( ' http://www.mibbit.com ajax IRC Client ' , ' mIRC v6.35 Khaled Mardam-Bey ' , ' xchat 0.24.1 Linux 2.6.27-8-eeepc i686 ' , ' rZNC Version 1.0 [02/01/11] - Built from ZNC ' , ' thelounge v3.0.0 -- https://thelounge.chat/ ' ) )
2023-05-27 22:47:43 +00:00
await self . raw ( f ' NOTICE { nick } \001 VERSION { version } \001 ' )
2023-06-26 03:27:43 +00:00
elif ( ' You are connected ' in line or ' Connected securely via ' in line ) and ( ' SSL ' in line or ' TLS ' in line ) :
cipher = line . split ( ) [ - 1 : ] [ 0 ] . replace ( ' \' ' , ' ' ) . replace ( ' " ' , ' ' )
2023-06-06 01:36:25 +00:00
self . snapshot [ ' ssl_cipher ' ] = cipher
2023-06-17 19:45:48 +00:00
elif nick in ( ' ChanServ ' , ' NickServ ' ) :
2023-05-27 22:47:43 +00:00
self . snapshot [ ' services ' ] = True
2023-05-31 05:21:01 +00:00
if ' is now registered ' in msg or f ' Nickname { self . nickname } registered ' in msg :
debug ( self . display + ' \033 [35mNickServ \033 [0m registered ' )
2023-06-17 19:45:48 +00:00
self . snapshot [ ' registered ' ] = self . login
2023-05-27 22:47:43 +00:00
elif ' ! ' not in args [ 0 ] :
if ' dronebl.org/lookup ' in msg :
2023-05-30 21:38:20 +00:00
self . snapshot [ ' proxy ' ] = True
2023-05-30 22:51:09 +00:00
error ( self . display + ' \033 [93mDroneBL detected \033 [0m ' )
2023-05-27 22:47:43 +00:00
raise Exception ( ' DroneBL ' )
else :
2023-06-06 01:36:25 +00:00
if [ i for i in ( ' You \' re banned ' , ' You are permanently banned ' , ' You are banned ' , ' You are not welcome ' , ' Temporary K-line ' ) if i in msg ] :
2023-05-27 22:47:43 +00:00
raise Exception ( ' K-Lined ' )
2023-05-26 07:26:45 +00:00
except ( UnicodeDecodeError , UnicodeEncodeError ) :
pass
except Exception as ex :
2023-05-30 19:08:52 +00:00
error ( self . display + ' \033 [1;31mdisconnected \033 [0m ' , ex )
2023-05-26 07:26:45 +00:00
break
2023-05-25 20:03:39 +00:00
async def main ( targets ) :
sema = asyncio . BoundedSemaphore ( throttle . threads ) # B O U N D E D S E M A P H O R E G A N G
jobs = list ( )
for target in targets :
2023-06-05 22:41:08 +00:00
server = ' : ' . join ( target . split ( ' : ' ) [ - 1 : ] )
2023-06-06 05:17:01 +00:00
if ' : ' not in target : # TODO: IPv6 addresses without a port wont get :6667 appeneded to it like this
port = 6697
else :
port = int ( ' : ' . join ( target . split ( ' : ' ) [ : - 1 ] ) )
2023-06-05 22:41:08 +00:00
try :
ipaddress . IPv4Address ( server )
jobs . append ( asyncio . ensure_future ( probe ( sema , server , port , 2 ) . run ( ) ) )
except :
try :
ipaddress . IPv6Address ( server )
jobs . append ( asyncio . ensure_future ( probe ( sema , server , port , 10 ) . run ( ) ) )
except :
2023-06-06 05:17:01 +00:00
error ( ' invalid ip address ' , server )
2023-05-25 20:03:39 +00:00
await asyncio . gather ( * jobs )
# Main
print ( ' # ' * 56 )
print ( ' # {:^54} # ' . format ( ' ' ) )
print ( ' # {:^54} # ' . format ( ' Internet Relay Chat Probe (IRCP) ' ) )
print ( ' # {:^54} # ' . format ( ' Developed by acidvegas in Python ' ) )
print ( ' # {:^54} # ' . format ( ' https://git.acid.vegas/ircp ' ) )
print ( ' # {:^54} # ' . format ( ' ' ) )
print ( ' # ' * 56 )
if len ( sys . argv ) != 2 :
raise SystemExit ( ' error: invalid arguments ' )
else :
targets_file = sys . argv [ 1 ]
if not os . path . isfile ( targets_file ) :
raise SystemExit ( ' error: invalid file path ' )
else :
2023-05-30 00:59:17 +00:00
try :
os . mkdir ( ' logs ' )
except FileExistsError :
pass
2023-06-06 05:17:01 +00:00
targets = [ line . rstrip ( ) for line in open ( targets_file ) . readlines ( ) if line and line not in bad . donotscan ]
2023-05-25 20:03:39 +00:00
found = len ( targets )
debug ( f ' loaded { found : , } targets ' )
2023-05-30 20:59:24 +00:00
if settings . daemon :
try :
os . mkdir ( ' backup ' )
except FileExistsError :
pass
else :
targets = [ target for target in targets if not os . path . isfile ( f ' logs/ { target } .json ' ) ] # Do not scan targets we already have logged for
2023-05-25 20:03:39 +00:00
if len ( targets ) < found :
debug ( f ' removed { found - len ( targets ) : , } targets we already have logs for already ' )
2023-05-30 00:59:17 +00:00
del found , targets_file
2023-05-30 20:59:24 +00:00
while True :
random . shuffle ( targets )
2023-06-19 07:03:21 +00:00
asyncio . run ( main ( targets ) )
2023-05-30 20:59:24 +00:00
debug ( ' IRCP has finished probing! ' )
if settings . daemon :
backup ( time . strftime ( ' % y % m %d - % H % M % S ' ) )
else :
2023-06-29 03:09:59 +00:00
break