IRCP

- information gathering tool for irc servers
git clone git://git.acid.vegas/IRCP.git
Log | Files | Refs | Archive | README | LICENSE

ircp.py (23140B)

      1 #!/usr/bin/env python
      2 # internet relay chat probe for https://internetrelaychat.org/ - developed by acidvegas in python (https://git.acid.vegas/ircp)
      3 
      4 import asyncio
      5 import ipaddress
      6 import json
      7 import os
      8 import random
      9 import ssl
     10 import sys
     11 import tarfile
     12 import time
     13 
     14 class settings:
     15 	daemon      = False                        # Run in daemon mode (24/7 throttled scanning)
     16 	errors      = True                         # Show errors in console
     17 	errors_conn = False                        # Show connection errors in console
     18 	log_max     = 5000000 # 5mb                # Maximum log size (in bytes) before starting another
     19 	nickname    = 'IRCP'                       # None = random
     20 	username    = 'ircp'                       # None = random
     21 	realname    = 'scan@internetrelaychat.org' # None = random
     22 	ns_mail     = 'scan@internetrelaychat.org' # None = random@random.[com|net|org]
     23 	ns_pass     = None                         # None = random
     24 	vhost       = None                         # Bind to a specific IP address
     25 
     26 class throttle:
     27 	channels = 5   if not settings.daemon else 3    # Maximum number of channels to scan at once
     28 	commands = 1.5 if not settings.daemon else 3    # Delay bewteen multiple commands send to the same target
     29 	connect  = 15  if not settings.daemon else 60   # Delay between each connection attempt on a diffferent port
     30 	delay    = 300 if not settings.daemon else 600  # Delay before registering nick (if enabled) & sending /LIST
     31 	join     = 10  if not settings.daemon else 30   # Delay between channel JOINs
     32 	nick     = 900 if not settings.daemon else 1200 # Delay between every random NICK change
     33 	part     = 10  if not settings.daemon else 30   # Delay before PARTing a channel
     34 	seconds  = 300 if not settings.daemon else 600  # Maximum seconds to wait when throttled for JOIN or WHOIS
     35 	threads  = 500 if not settings.daemon else 300  # Maximum number of threads running
     36 	timeout  = 30  if not settings.daemon else 60   # Timeout for all sockets
     37 	whois    = 15  if not settings.daemon else 30   # Delay between WHOIS requests
     38 	ztimeout = 600 if not settings.daemon else 900  # Timeout for zero data from server
     39 
     40 class bad:
     41 	donotscan = (
     42 		'irc.dronebl.org',       'irc.alphachat.net',
     43 		'5.9.164.48',            '45.32.74.177',          '104.238.146.46',               '149.248.55.130',
     44 		'2001:19f0:6001:1dc::1', '2001:19f0:b001:ce3::1', '2a01:4f8:160:2501:48:164:9:5', '2001:19f0:6401:17c::1'
     45 	)
     46 	chan = {
     47 		'403' : 'ERR_NOSUCHCHANNEL',    '405' : 'ERR_TOOMANYCHANNELS',
     48 		'435' : 'ERR_BANONCHAN',        '442' : 'ERR_NOTONCHANNEL',
     49 		'448' : 'ERR_FORBIDDENCHANNEL', '470' : 'ERR_LINKCHANNEL',
     50 		'471' : 'ERR_CHANNELISFULL',    '473' : 'ERR_INVITEONLYCHAN',
     51 		'474' : 'ERR_BANNEDFROMCHAN',   '475' : 'ERR_BADCHANNELKEY',
     52 		'476' : 'ERR_BADCHANMASK',      '477' : 'ERR_NEEDREGGEDNICK',
     53 		'479' : 'ERR_BADCHANNAME',      '480' : 'ERR_THROTTLE',
     54 		'485' : 'ERR_CHANBANREASON',    '488' : 'ERR_NOSSL',
     55 		'489' : 'ERR_SECUREONLYCHAN',   '519' : 'ERR_TOOMANYUSERS',
     56 		'520' : 'ERR_OPERONLY',         '926' : 'ERR_BADCHANNEL'
     57 	}
     58 	error = {
     59 		'install identd'                 : 'Identd required',
     60 		'trying to reconnect too fast'   : 'Throttled',
     61 		'trying to (re)connect too fast' : 'Throttled',
     62 		'reconnecting too fast'          : 'Throttled',
     63 		'access denied'                  : 'Access denied',
     64 		'not authorized to'              : 'Not authorized',
     65 		'not authorised to'              : 'Not authorized',
     66 		'password mismatch'              : 'Password mismatch',
     67 		'dronebl'                        : 'DroneBL',
     68 		'dnsbl'                          : 'DNSBL',
     69 		'g:lined'                        : 'G:Lined',
     70 		'z:lined'                        : 'Z:Lined',
     71 		'timeout'                        : 'Timeout',
     72 		'closing link'                   : 'Banned',
     73 		'banned'                         : 'Banned',
     74 		'client exited'                  : 'QUIT',
     75 		'quit'                           : 'QUIT'
     76 	}
     77 
     78 def backup(name):
     79 	try:
     80 		with tarfile.open(f'backup/{name}.tar.gz', 'w:gz') as tar:
     81 			for log in os.listdir('logs'):
     82 				tar.add('logs/' + log)
     83 		debug('\033[1;32mBACKUP COMPLETE\033[0m')
     84 		for log in os.listdir('logs'):
     85 			os.remove('logs/' + log)
     86 	except Exception as ex:
     87 		error('\033[1;31mBACKUP FAILED\033[0m', ex)
     88 
     89 def debug(data):
     90 	print('{0} \033[1;30m|\033[0m [\033[35m~\033[0m] {1}'.format(time.strftime('%I:%M:%S'), data))
     91 
     92 def error(data, reason=None):
     93 	if settings.errors:
     94 		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))
     95 
     96 def rndnick():
     97 	prefix = random.choice(['st','sn','cr','pl','pr','fr','fl','qu','br','gr','sh','sk','tr','kl','wr','bl']+list('bcdfgklmnprstvwz'))
     98 	midfix = random.choice(('aeiou'))+random.choice(('aeiou'))+random.choice(('bcdfgklmnprstvwz'))
     99 	suffix = random.choice(['ed','est','er','le','ly','y','ies','iest','ian','ion','est','ing','led','inger']+list('abcdfgklmnprstvwz'))
    100 	return prefix+midfix+suffix
    101 
    102 def ssl_ctx():
    103 	ctx = ssl.create_default_context()
    104 	ctx.check_hostname = False
    105 	ctx.verify_mode = ssl.CERT_NONE
    106 	return ctx
    107 
    108 class probe:
    109 	def __init__(self, semaphore, server, port, family=2):
    110 		self.semaphore = semaphore
    111 		self.server    = server
    112 		self.port      = 6697
    113 		self.oport     = port
    114 		self.family    = family
    115 		self.display   = server.ljust(18)+' \033[1;30m|\033[0m unknown network           \033[1;30m|\033[0m '
    116 		self.nickname  = None
    117 		self.multi     = ''
    118 		self.snapshot  = dict()
    119 		self.channels  = {'all':list(), 'current':list(), 'users':dict()}
    120 		self.nicks     = {'all':list(), 'check':list()}
    121 		self.loops     = {'init':None, 'chan':None, 'nick':None, 'whois':None}
    122 		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'))}
    123 		self.services  = {'chanserv':True, 'nickserv':True}
    124 		self.jthrottle = throttle.join
    125 		self.nthrottle = throttle.whois
    126 		self.reader    = None
    127 		self.write     = None
    128 
    129 	async def sendmsg(self, target, msg):
    130 		await self.raw(f'PRIVMSG {target} :{msg}')
    131 
    132 	async def run(self):
    133 		async with self.semaphore:
    134 			try:
    135 				await self.connect() # 6697
    136 			except Exception as ex:
    137 				if settings.errors_conn:
    138 					error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect using SSL/TLS on port ' + str(self.port), ex)
    139 				if self.oport not in (6667,6697):
    140 					self.port = self.oport
    141 					await asyncio.sleep(throttle.connect)
    142 					try:
    143 						await self.connect() # Non-standard
    144 					except Exception as ex:
    145 						if settings.errors_conn:
    146 							error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect using SSL/TLS on port ' + str(self.port), ex)
    147 						self.port = 6667
    148 						await asyncio.sleep(throttle.connect)
    149 						try:
    150 							await self.connect(True) # 6667
    151 						except Exception as ex:
    152 							if settings.errors_conn:
    153 								error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect on port ' + str(self.port), ex)
    154 							self.port = self.oport
    155 							await asyncio.sleep(throttle.connect)
    156 							try:
    157 								await self.connect(True) # Non-standard
    158 							except Exception as ex:
    159 								if settings.errors_conn:
    160 									error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect on port ' + str(self.port), ex)
    161 				else:
    162 					self.port = 6667
    163 					await asyncio.sleep(throttle.connect)
    164 					try:
    165 						await self.connect(True) # 6667
    166 					except Exception as ex:
    167 						if settings.errors_conn:
    168 							error(self.display + '\033[1;31mdisconnected\033[0m - failed to connect on port ' + str(self.port), ex)
    169 
    170 	async def raw(self, data):
    171 		self.writer.write(data[:510].encode('utf-8') + b'\r\n')
    172 		await self.writer.drain()
    173 
    174 	async def connect(self, fallback=False):
    175 		options = {
    176 			'host'       : self.server,
    177 			'port'       : self.port,
    178 			'limit'      : 1024,
    179 			'ssl'        : None if fallback else ssl_ctx(),
    180 			'family'     : self.family,
    181 			'local_addr' : (settings.vhost, random.randint(5000,65000)) if settings.vhost else None
    182 		}
    183 		identity = {
    184 			'nick': settings.nickname if settings.nickname else rndnick(),
    185 			'user': settings.username if settings.username else rndnick(),
    186 			'real': settings.realname if settings.realname else rndnick()
    187 		}
    188 		self.nickname = identity['nick']
    189 		self.reader, self.writer = await asyncio.wait_for(asyncio.open_connection(**options), throttle.timeout)
    190 		self.snapshot['port'] = options['port']
    191 		del options
    192 		if not fallback:
    193 			self.snapshot['ssl'] = True
    194 		await self.raw('USER {0} 0 * :{1}'.format(identity['user'], identity['real']))
    195 		await self.raw('NICK ' + identity['nick'])
    196 		del identity
    197 		await self.listen()
    198 		for item in self.loops:
    199 			if self.loops[item]:
    200 				self.loops[item].cancel()
    201 		with open(f'logs/{self.server}.json{self.multi}', 'w') as fp:
    202 			json.dump(self.snapshot, fp)
    203 		debug(self.display + 'finished scanning')
    204 
    205 	async def loop_initial(self):
    206 		try:
    207 			await asyncio.sleep(throttle.delay)
    208 			cmds = ['ADMIN', 'CAP LS', 'COMMANDS', 'HELP', 'INFO', 'IRCOPS', 'LINKS', 'MAP', 'MODULES -all', 'SERVLIST', 'STATS p', 'VERSION']
    209 			random.shuffle(cmds)
    210 			cmds += ['PRIVMSG NickServ :REGISTER {0} {1}'.format(self.login['pass'], self.login['mail']), 'PRIVMSG ChanServ :LIST *', 'PRIVMSG NickServ :LIST *', 'LIST']
    211 			for command in cmds:
    212 				try:
    213 					await self.raw(command)
    214 				except:
    215 					break
    216 				else:
    217 					await asyncio.sleep(throttle.commands)
    218 			if not self.channels['all']:
    219 				error(self.display + '\033[31merror\033[0m - no channels found')
    220 				await self.raw('QUIT')
    221 		except asyncio.CancelledError:
    222 			pass
    223 		except Exception as ex:
    224 			error(self.display + '\033[31merror\033[0m - loop_initial', ex)
    225 
    226 	async def loop_channels(self):
    227 		try:
    228 			while self.channels['all']:
    229 				while len(self.channels['current']) >= throttle.channels:
    230 					await asyncio.sleep(1)
    231 				await asyncio.sleep(self.jthrottle)
    232 				chan = random.choice(self.channels['all'])
    233 				self.channels['all'].remove(chan)
    234 				try:
    235 					if self.services['chanserv']:
    236 						await self.sendmsg('ChanServ', 'INFO ' + chan)
    237 						await asyncio.sleep(throttle.commands)
    238 					await self.raw('JOIN ' + chan)
    239 				except:
    240 					break
    241 			self.loops['nick'].cancel()
    242 			while self.nicks['check']:
    243 				await asyncio.sleep(1)
    244 			self.loops['whois'].cancel()
    245 			self.loops['nick'].cancel()
    246 			await self.raw('QUIT')
    247 		except asyncio.CancelledError:
    248 			pass
    249 		except Exception as ex:
    250 			error(self.display + '\033[31merror\033[0m - loop_channels', ex)
    251 
    252 	async def loop_nick(self):
    253 		try:
    254 			while True:
    255 				await asyncio.sleep(throttle.nick+random.randint(60,90))
    256 				self.nickname = rndnick()
    257 				await self.raw('NICK ' + self.nickname)
    258 				debug(self.display + '\033[0;35mNICK\033[0m - new identity')
    259 		except asyncio.CancelledError:
    260 			pass
    261 		except Exception as ex:
    262 			error(self.display + '\033[31merror\033[0m - loop_nick', ex)
    263 
    264 	async def loop_whois(self):
    265 		try:
    266 			while True:
    267 				if self.nicks['check']:
    268 					nick = random.choice(self.nicks['check'])
    269 					self.nicks['check'].remove(nick)
    270 					try:
    271 						await self.raw('WHOIS ' + nick)
    272 						await asyncio.sleep(throttle.commands)
    273 						if self.services['nickserv']:
    274 							await self.sendmsg('NickServ', 'INFO ' + nick)
    275 							await asyncio.sleep(throttle.commands)
    276 						await self.raw(f'NOTICE {nick} \001VERSION\001') # TODO: check the database if we already have this information to speed things up
    277 						await asyncio.sleep(throttle.commands)
    278 						await self.raw(f'NOTICE {nick} \001TIME\001')
    279 						await asyncio.sleep(throttle.commands)
    280 						await self.raw(f'NOTICE {nick} \001CLIENTINFO\001')
    281 						await asyncio.sleep(throttle.commands)
    282 						await self.raw(f'NOTICE {nick} \001SOURCE\001')
    283 					except:
    284 						break
    285 					else:
    286 						del nick
    287 						await asyncio.sleep(throttle.whois)
    288 				else:
    289 					await asyncio.sleep(1)
    290 		except asyncio.CancelledError:
    291 			pass
    292 		except Exception as ex:
    293 			error(self.display + '\033[31merror\033[0m - loop_whois', ex)
    294 
    295 	async def db(self, event, data):
    296 		if event in self.snapshot:
    297 			if data not in self.snapshot[event]:
    298 				self.snapshot[event].append(data)
    299 		else:
    300 			self.snapshot[event] = [data,]
    301 
    302 	async def listen(self):
    303 		while True:
    304 			try:
    305 				if self.reader.at_eof():
    306 					break
    307 				data  = await asyncio.wait_for(self.reader.readuntil(b'\r\n'), throttle.ztimeout)
    308 				line  = data.decode('utf-8').strip()
    309 				args  = line.split()
    310 				event = args[1].upper()
    311 				if sys.getsizeof(self.snapshot) >= settings.log_max:
    312 					with open(f'logs/{self.server}.json{self.multi}', 'w') as fp:
    313 						json.dump(self.snapshot, fp)
    314 					self.snapshot = dict()
    315 					self.multi = '.1' if not self.multi else '.' + str(int(self.multi[1:])+1)
    316 				if args[0].upper() == 'ERROR':
    317 					await self.db('ERROR', line)
    318 				elif not event.isdigit() and event not in ('CAP','INVITE','JOIN','KICK','KILL','MODE','NICK','NOTICE','PART','PRIVMSG','QUIT','TOPIC','WHO'):
    319 					await self.db('RAW', line)
    320 				elif event != '401':
    321 					await self.db(event, line)
    322 				if event in bad.chan and len(args) >= 4:
    323 					chan = args[3]
    324 					if chan in self.channels['users']:
    325 						del self.channels['users'][chan]
    326 					error(f'{self.display}\033[31merror\033[0m - {chan}', bad.chan[event])
    327 				elif line.startswith('ERROR :'):
    328 					check = [check for check in bad.error if check in line.lower()]
    329 					if check:
    330 						if check[0] in ('dronebl','dnsbl'):
    331 							self.snapshot['proxy'] = True
    332 						raise Exception(bad.error[check[0]])
    333 				elif args[0] == 'PING':
    334 					await self.raw('PONG ' + args[1][1:])
    335 				elif event == 'KICK' and len(args) >= 4:
    336 					chan   = args[2]
    337 					kicked = args[3]
    338 					if kicked == self.nickname:
    339 						if chan in self.channels['current']:
    340 							self.channels['current'].remove(chan)
    341 				elif event == 'MODE' and len(args) == 4:
    342 					nick = args[2]
    343 					if nick == self.nickname:
    344 						mode = args[3][1:]
    345 						if mode == '+r':
    346 							self.snapshot['registered'] = self.login
    347 				elif event == '001': #RPL_WELCOME
    348 					host = args[0][1:]
    349 					self.snapshot['server'] = self.server
    350 					self.snapshot['host']   = host
    351 					if len(host) > 25:
    352 						self.display = f'{self.server.ljust(18)} \033[1;30m|\033[0m {host[:22]}... \033[1;30m|\033[0m '
    353 					else:
    354 						self.display = f'{self.server.ljust(18)} \033[1;30m|\033[0m {host.ljust(25)} \033[1;30m|\033[0m '
    355 					debug(self.display + f'\033[1;32mconnected\033[0m \033[1;30m(port {self.port})\033[0m')
    356 					self.loops['init'] = asyncio.create_task(self.loop_initial())
    357 				elif event == '005':
    358 					for item in args:
    359 						if item.startswith('SSL=') and item[4:]:
    360 							if not self.snapshot['ssl']:
    361 								self.snapshot['ssl'] = item[4:]
    362 							break
    363 				elif event == '311' and len(args) >= 4: # RPL_WHOISUSER
    364 					nick = args[3]
    365 					if 'open proxy' in line.lower() or 'proxy monitor' in line.lower():
    366 						self.snapshot['proxy'] = True
    367 						error(self.display + '\033[93mProxy Monitor detected\033[0m', nick)
    368 					else:
    369 						debug(f'{self.display}\033[34mWHOIS\033[0m {nick}')
    370 				elif event == 315 and len(args) >= 3: # RPL_ENDOFWHO
    371 					chan = args[3]
    372 					await self.raw(f'MODE {chan} +b')
    373 					await asyncio.sleep(throttle.commands)
    374 					await self.raw(f'MODE {chan} +e')
    375 					await asyncio.sleep(throttle.commands)
    376 					await self.raw(f'MODE {chan} +I')
    377 					await asyncio.sleep(throttle.commands)
    378 					await self.raw(f'NOTICE {chan} \001VERSION\001')
    379 					await asyncio.sleep(throttle.commands)
    380 					await self.raw(f'NOTICE {chan} \001TIME\001')
    381 					await asyncio.sleep(throttle.commands)
    382 					await self.raw(f'NOTICE {chan} \001CLIENTINFO\001')
    383 					await asyncio.sleep(throttle.commands)
    384 					await self.raw(f'NOTICE {chan} \001SOURCE\001')
    385 					await asyncio.sleep(throttle.part)
    386 					await self.raw('PART ' + chan)
    387 					self.channels['current'].remove(chan)
    388 				elif event == '322' and len(args) >= 4: # RPL_LIST
    389 					chan  = args[3]
    390 					users = args[4]
    391 					if users != '0': # no need to JOIN empty channels...
    392 						self.channels['all'].append(chan)
    393 						self.channels['users'][chan] = users
    394 				elif event == '323': # RPL_LISTEND
    395 					if self.channels['all']:
    396 						debug(self.display + '\033[36mLIST\033[0m found \033[93m{0}\033[0m channel(s)'.format(str(len(self.channels['all']))))
    397 						self.loops['chan']  = asyncio.create_task(self.loop_channels())
    398 						self.loops['nick']  = asyncio.create_task(self.loop_nick())
    399 						self.loops['whois'] = asyncio.create_task(self.loop_whois())
    400 				elif event == '352' and len(args) >= 8: # RPL_WHORPL
    401 					nick = args[7]
    402 					if nick not in self.nicks['all']+[self.nickname,]:
    403 						self.nicks['all'].append(nick)
    404 						self.nicks['check'].append(nick)
    405 				elif event == '366' and len(args) >= 4: # RPL_ENDOFNAMES
    406 					chan = args[3]
    407 					self.channels['current'].append(chan)
    408 					if chan in self.channels['users']:
    409 						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]))
    410 						del self.channels['users'][chan]
    411 					await self.raw('WHO ' + chan)
    412 				elif event == '401' and len(args) >= 4: # ERR_NOSUCHNICK
    413 					nick = args[3]
    414 					if nick == 'ChanServ':
    415 						self.services['chanserv'] = False
    416 					elif nick == 'NickServ':
    417 						self.services['nickserv'] = False
    418 					else:
    419 						await self.raw('WHOWAS ' + nick)
    420 				elif event == '421' and len(args) >= 3: # ERR_UNKNOWNCOMMAND
    421 					msg = ' '.join(args[2:])
    422 					if 'You must be connected for' in msg:
    423 						error(self.display + '\033[31merror\033[0m - delay found', msg)
    424 				elif event == '433': # ERR_NICKINUSE
    425 					self.nickname = rndnick()
    426 					await self.raw('NICK ' + self.nickname)
    427 				elif event == '439' and len(args) >= 11: # ERR_TARGETTOOFAST
    428 					target  = args[3]
    429 					msg     = ' '.join(args[4:])[1:]
    430 					seconds = args[10]
    431 					if target[:1] in ('#','&'):
    432 						self.channels['all'].append(target)
    433 						if seconds.isdigit():
    434 							self.jthrottle = throttle.seconds if int(seconds) > throttle.seconds else int(seconds)
    435 					else:
    436 						self.nicks['check'].append(target)
    437 						if seconds.isdigit():
    438 							self.nthrottle = throttle.seconds if int(seconds) > throttle.seconds else int(seconds)
    439 					error(self.display + '\033[31merror\033[0m - delay found for ' + target, msg)
    440 				elif event == '465' and len(args) >= 5: # ERR_YOUREBANNEDCREEP
    441 					check = [check for check in bad.error if check in line.lower()]
    442 					if check:
    443 						if check[0] in ('dronebl','dnsbl'):
    444 							self.snapshot['proxy'] = True
    445 						raise Exception(bad.error[check[0]])
    446 				elif event == '464': # ERR_PASSWDMISMATCH
    447 					raise Exception('Network has a password')
    448 				elif event == '487': # ERR_MSGSERVICES
    449 					if '"/msg NickServ" is no longer supported' in line:
    450 						await self.raw('/NickServ REGISTER {0} {1}'.format(self.login['pass'], self.login['mail']))
    451 				elif event == 'KILL':
    452 					nick = args[2]
    453 					if nick == self.nickname:
    454 						raise Exception('KILL')
    455 				elif event in ('NOTICE','PRIVMSG') and len(args) >= 4:
    456 					nick   = args[0].split('!')[1:]
    457 					target = args[2]
    458 					msg    = ' '.join(args[3:])[1:]
    459 					if target == self.nickname:
    460 						for i in ('proxy','proxys','proxies'):
    461 							if i in msg.lower():
    462 								self.snapshot['proxy'] = True
    463 								check = [x for x in ('bopm','hopm') if x in line]
    464 								if check:
    465 									error(f'{self.display}\033[93m{check[0].upper()} detected\033[0m')
    466 								else:
    467 									error(self.display + '\033[93mProxy Monitor detected\033[0m')
    468 						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'):
    469 							if i in msg:
    470 								error(self.display + '\033[31merror\033[0m - delay found', msg)
    471 								break
    472 						if msg[:8] == '\001VERSION':
    473 							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/'))
    474 							await self.raw(f'NOTICE {nick} \001VERSION {version}\001')
    475 						elif ('You are connected' in line or 'Connected securely via' in line) and ('SSL' in line or 'TLS' in line):
    476 							cipher = line.split()[-1:][0].replace('\'','').replace('"','')
    477 							self.snapshot['ssl_cipher'] = cipher
    478 						elif nick in ('ChanServ','NickServ'):
    479 							self.snapshot['services'] = True
    480 							if 'is now registered' in msg or f'Nickname {self.nickname} registered' in msg:
    481 								debug(self.display + '\033[35mNickServ\033[0m registered')
    482 								self.snapshot['registered'] = self.login
    483 						elif '!' not in args[0]:
    484 							if 'dronebl.org/lookup' in msg:
    485 								self.snapshot['proxy'] = True
    486 								error(self.display + '\033[93mDroneBL detected\033[0m')
    487 								raise Exception('DroneBL')
    488 							else:
    489 								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]:
    490 									raise Exception('K-Lined')
    491 			except (UnicodeDecodeError, UnicodeEncodeError):
    492 				pass
    493 			except Exception as ex:
    494 				error(self.display + '\033[1;31mdisconnected\033[0m', ex)
    495 				break
    496 
    497 async def main(targets):
    498 	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
    499 	jobs = list()
    500 	for target in targets:
    501 		server = ':'.join(target.split(':')[-1:])
    502 		if ':' not in target: # TODO: IPv6 addresses without a port wont get :6667 appeneded to it like this
    503 			port = 6697
    504 		else:
    505 			port  = int(':'.join(target.split(':')[:-1]))
    506 		try:
    507 			ipaddress.IPv4Address(server)
    508 			jobs.append(asyncio.ensure_future(probe(sema, server, port, 2).run()))
    509 		except:
    510 			try:
    511 				ipaddress.IPv6Address(server)
    512 				jobs.append(asyncio.ensure_future(probe(sema, server, port, 10).run()))
    513 			except:
    514 				error('invalid ip address', server)
    515 	await asyncio.gather(*jobs)
    516 
    517 # Main
    518 print('#'*56)
    519 print('#{:^54}#'.format(''))
    520 print('#{:^54}#'.format('Internet Relay Chat Probe (IRCP)'))
    521 print('#{:^54}#'.format('Developed by acidvegas in Python'))
    522 print('#{:^54}#'.format('https://git.acid.vegas/ircp'))
    523 print('#{:^54}#'.format(''))
    524 print('#'*56)
    525 if len(sys.argv) != 2:
    526 	raise SystemExit('error: invalid arguments')
    527 else:
    528 	targets_file = sys.argv[1]
    529 if not os.path.isfile(targets_file):
    530 	raise SystemExit('error: invalid file path')
    531 else:
    532 	try:
    533 		os.mkdir('logs')
    534 	except FileExistsError:
    535 		pass
    536 	targets = [line.rstrip() for line in open(targets_file).readlines() if line and line not in bad.donotscan]
    537 	found   = len(targets)
    538 	debug(f'loaded {found:,} targets')
    539 	if settings.daemon:
    540 		try:
    541 			os.mkdir('backup')
    542 		except FileExistsError:
    543 			pass
    544 	else:
    545 		targets = [target for target in targets if not os.path.isfile(f'logs/{target}.json')] # Do not scan targets we already have logged for
    546 	if len(targets) < found:
    547 		debug(f'removed {found-len(targets):,} targets we already have logs for already')
    548 	del found, targets_file
    549 	while True:
    550 		random.shuffle(targets)
    551 		asyncio.run(main(targets))
    552 		debug('IRCP has finished probing!')
    553 		if settings.daemon:
    554 			backup(time.strftime('%y%m%d-%H%M%S'))
    555 		else:
    556 			break