def execute(self, client, recv): # :asdf UMODE2 +ot target = next((u for u in self.ircd.users if u.uid == recv[0][1:] or u.nickname == recv[0][1:]), None) if not target: logging.info( f'Could not set umode for {recv[0][1:]}: maybe it got SVSKILLed?' ) return modeset = None for m in recv[2]: if m in '+-': modeset = m continue if modeset == '+': if m not in target.modes: target.modes += m elif modeset == '-': target.modes = target.modes.replace(m, '') if m == 'o': target.operflags = [] target.swhois = [] target.opermodes = '' elif m == 's': target.snomasks = '' self.ircd.new_sync(self.ircd, client, ' '.join(recv))
def execute(self, client, recv): source = recv[0][1:] if type(client).__name__ == 'User': if client.registered: return client.sendraw(462, ':You may not reregister') # Check for server password. if client.cls and 'password' in self.ircd.conf['allow'][ client.cls]: if recv[1] == self.ircd.conf['allow'][client.cls]['password']: client.server_pass_accepted = 1 logging.info( 'Server password accepted for {}'.format(client)) return else: return client.quit('Invalid password') if type(client).__name__ == 'Server' and 'link' not in self.ircd.conf: return client.quit('Target has no links configured') if len(recv) < 3: return client.linkpass = recv[2][1:] logging.info('Password for {} set: {}'.format(client, client.linkpass)) ip, port = client.socket.getpeername() ip2, port2 = client.socket.getsockname() if client.hostname: validate_server_info(self, client)
def execute(self, client, recv): if type(client).__name__ == 'Server': # Command USER is not being used by any server. Assume normal user. return client.quit('This port is for servers only') if client.ident: return client.sendraw(462, ':You may not reregister') if 'nmap' in ''.join(recv).lower(): return client.quit('Connection reset by peer') ident = str(recv[1][:12]).strip() realname = recv[4][:48] valid = "abcdefghijklmnopqrstuvwxyz0123456789-_" for c in ident: if c.lower() not in valid: ident = ident.replace(c, '') block = 0 for cls in iter([ cls for cls in self.ircd.conf['allow'] if cls in self.ircd.conf['class'] ]): t = self.ircd.conf['allow'][cls] isMatch = False if 'ip' in t: clientmask = '{}@{}'.format(client.ident, client.ip) isMatch = match(t['ip'], clientmask) if 'hostname' in t and not isMatch: # Try with hostname. IP has higher priority. clientmask = '{}@{}'.format(client.ident, client.hostname) isMatch = match(t['hostname'], clientmask) if isMatch: if 'options' in t: if 'ssl' in t['options'] and not client.ssl: continue client.cls = cls if 'block' in t: for entry in t['block']: clientmask_ip = '{}@{}'.format(client.ident, client.ip) clientmask_host = '{}@{}'.format( client.ident, client.hostname) block = match(entry, clientmask_ip) or match( entry, clientmask_host) if block: logging.info('Client {} blocked by {}: {}'.format( client, cls, entry)) break break client.ident = ident client.realname = realname if client.nickname != '*' and client.validping and ( client.cap_end or not client.sends_cap): client.welcome()
def syncData(localServer, newServer, selfRequest=True, local_only=False): if localServer.users: syncUsers(localServer, newServer, local_only=local_only) if localServer.channels: syncChannels(localServer, newServer) try: for type in localServer.tkl: for entry in localServer.tkl[type]: if not localServer.tkl[type][entry]['global']: continue mask = '{} {}'.format(entry.split('@')[0], entry.split('@')[1]) setter = localServer.tkl[type][entry]['setter'] try: source = list( filter(lambda s: s.hostname == setter, localServer.servers)) if source: if source[0].hostname == newServer.hostname or source[ 0].introducedBy == newServer: continue except: pass expire = localServer.tkl[type][entry]['expire'] ctime = localServer.tkl[type][entry]['ctime'] reason = localServer.tkl[type][entry]['reason'] data = ':{} TKL + {} {} {} {} {} :{}'.format( localServer.sid, type, mask, setter, expire, ctime, reason) newServer._send(data) except Exception as ex: logging.exception(ex) logging.info('{}Server {} is done syncing to {}, sending EOS.{}'.format( Y, localServer.hostname, newServer.hostname, W)) newServer._send(':{} EOS'.format(localServer.sid)) if newServer not in localServer.syncDone: cloakhash = localServer.conf['settings']['cloak-key'] cloakhash = hashlib.md5(cloakhash.encode('utf-8')).hexdigest() data = ':{} NETINFO {} {} {} MD5:{} {} 0 0 :{}'.format( localServer.sid, localServer.maxgusers, int(time.time()), localServer.versionnumber.replace('.', ''), cloakhash, localServer.creationtime, localServer.name) newServer._send(data) localServer.syncDone.append(newServer) if not hasattr(newServer, 'outgoing') or not newServer.outgoing: newServer._send(':{} PONG {} {}'.format(localServer.sid, newServer.hostname, localServer.hostname)) else: newServer._send(':{} PING {} {}'.format(localServer.sid, localServer.hostname, newServer.hostname)) return
def run(self): try: exists = list( filter(lambda s: s.hostname == self.name, self.localServer.servers + [self.localServer])) if exists: logging.error( 'Server {} already exists on this network'.format( exists[0].hostname)) return serv = None if not self.host.replace('.', '').isdigit(): self.host = socket.gethostbyname(self.host) self.socket = socket.socket() if self.tls: self.socket = self.localServer.default_sslctx.wrap_socket( self.socket, server_side=False) logging.info('Wrapped outgoing socket {} in TLS'.format( self.socket)) from ircd import Server serv = Server(origin=self.localServer, serverLink=True, sock=self.socket, is_ssl=self.tls) serv.hostname = self.name serv.ip = self.host serv.port = self.port serv.outgoing = True if self.origin or self.autoLink: self.localServer.linkrequester[serv] = self.origin self.socket.settimeout(10) self.socket.connect((self.host, self.port)) selfIntroduction(self.localServer, serv, outgoing=True) if serv not in self.localServer.introducedTo: self.localServer.introducedTo.append(serv) except Exception as ex: logging.exception(ex) # Outgoing link timed out. if serv: serv.quit(str(ex))
def syncUsers(localServer, newServer, local_only): try: totalServers = [localServer] if not local_only: totalServers.extend(localServer.servers) for server in [ server for server in totalServers if server != newServer and server.introducedBy != newServer and newServer.introducedBy != server and server not in newServer.syncDone and newServer.socket ]: newServer.syncDone.append(server) logging.info('{}Syncing info from {} to {}{}'.format( Y, server.hostname, newServer.hostname, W)) for u in [ u for u in localServer.users if u.server == server and u.registered ]: ip = IPtoBase64(u.ip) if u.ip.replace('.', '').isdigit() else u.ip if not ip: ip = '*' hopcount = str(u.server.hopcount + 1) data = ':{} UID {} {} {} {} {} {} 0 +{} {} {} {} :{}'.format( server.sid, u.nickname, hopcount, u.signon, u.ident, u.hostname, u.uid, u.modes, u.cloakhost, u.cloakhost, ip, u.realname) newServer._send(data) if u.fingerprint: data = 'MD client {} certfp :{}'.format( u.uid, u.fingerprint) newServer._send(':{} {}'.format(server.sid, data)) if u.operaccount: data = 'MD client {} operaccount :{}'.format( u.uid, u.operaccount) newServer._send(':{} {}'.format(server.sid, data)) if u.snomasks: newServer._send(':{} BV +{}'.format(u.uid, u.snomasks)) if 'o' in u.modes: for line in u.swhois: newServer._send(':{} SWHOIS {} :{}'.format( server.sid, u.uid, line)) if u.away: newServer._send(':{} AWAY :{}'.format(u.uid, u.away)) except Exception as ex: logging.exception(ex)
def selfIntroduction(localServer, newServer, outgoing=False): try: if newServer not in localServer.introducedTo: if outgoing: destPass = localServer.conf['link'][newServer.hostname]['pass'] newServer._send(':{} PASS :{}'.format(localServer.sid, destPass)) info = [] for row in localServer.server_support: value = localServer.support[row] info.append('{}{}'.format( row, '={}'.format(value) if value else '')) newServer._send(':{} PROTOCTL EAUTH={} SID={} {}'.format( localServer.sid, localServer.hostname, localServer.sid, ' '.join(info))) newServer._send( ':{} PROTOCTL NOQUIT EAUTH SID NICKv2 CLK SJOIN SJOIN2 UMODE2 VL SJ3 TKLEXT TKLEXT2 NICKIP ESVID EXTSWHOIS' .format(localServer.sid)) version = 'P{}-{}'.format( localServer.versionnumber.replace('.', ''), localServer.sid) local_modules = [m.__name__ for m in localServer.modules] modlist = [] for entry in local_modules: totlen = len(' '.join(modlist)) if totlen >= 400: newServer._send('MODLIST :{}'.format(' '.join(modlist))) modlist = [] modlist.append(entry) if modlist: newServer._send('MODLIST :{}'.format(' '.join(modlist))) if outgoing: newServer._send( f':{localServer.sid} SID {localServer.hostname} 1 {localServer.sid} :{localServer.name}' ) else: newServer._send('SERVER {} 1 :{} {}'.format( localServer.hostname, version, localServer.name)) logging.info( '{}Introduced myself to {}. Expecting remote sync sequence...{}' .format(Y, newServer.hostname, W)) localServer.introducedTo.append(newServer) except Exception as ex: logging.exception(ex)
def _send(self, data): try: if self.socket: self.sendbuffer += data + '\r\n' if self.localServer.use_poll: logging.debug( 'Flag for {} set to READ_WRITE (_send())'.format(self)) self.localServer.pollerObject.modify( self.socket, READ_WRITE) ignore = ['PRIVMSG', 'NOTICE', 'PING', 'PONG'] try: if data.split()[0] not in ['PING', 'PONG']: if len(data) > 1 and data.split()[1] not in ignore: # pass logging.info('{}{} <<<-- {}{}'.format( B, self.hostname if self.hostname != '' else self, data, W)) except: pass except Exception as ex: logging.exception(ex)
def execute(self, client, recv): nick = recv[2] params = [] allow = 1 for p in recv: params.append(p) for user in [ user for user in self.ircd.users if user.nickname.lower() == nick.lower() ]: logging.error('Found double user in UID: {}'.format(user)) if not user.server: logging.error( 'Quitting {} because their server could not be found (UID)' .format(user)) user.quit('Unknown or corrupted connection with the same nick') continue logging.error( '{}ERROR: user {} already found on the network{}'.format( R, user, W)) localTS = int(user.signon) remoteTS = int(recv[4]) if remoteTS <= localTS: logging.info( '{}Local user {} disconnected from local server.{}'.format( R, user, W)) user.quit('Local Nick Collision', silent=True) continue else: allow = 0 logging.debug('Disallowing remote user {}'.format(user)) return if allow: u = ircd.User(client, server_class=self.ircd, params=params) cmd = ' '.join(recv) self.ircd.new_sync(self.ircd, client, cmd)
def execute(self, client, recv): if type(client).__name__ == 'Server': dest = list(filter(lambda s: s.sid == recv[3] or s.hostname == recv[3], self.ircd.servers + [self.ircd])) if not dest: logging.error('Server {} requested a PING to unknown server {}'.format(client, recv[3])) return source = list(filter(lambda s: s.sid == recv[2] or s.hostname == recv[2], self.ircd.servers + [self.ircd]))[0] if source not in self.ircd.syncDone: local_only = False if source in self.ircd.sync_queue: local_only = True logging.info('Syncing only local users to {}'.format(source)) del self.ircd.sync_queue[source] syncData(self.ircd, source, local_only=local_only) return ### Old: data = ':{} PONG {} {}'.format(dest[0].sid, dest[0].hostname, recv[2]) if client.eos and (dest[0].eos or dest[0] == self.ircd): data = ':{} PONG {} {}'.format(dest[0].sid, dest[0].hostname, recv[2]) client._send(data) else: client._send(':{} PONG {} :{}'.format(self.ircd.hostname, self.ircd.hostname, recv[1]))
def LoadModule(self, name, path, reload=False, module=None): package = name.replace('/', '.') try: if reload: module = importlib.reload(module) logging.debug('Requesting reload from importlib') else: module = importlib.import_module(package) importlib.reload(module) if not module.__doc__: logging.info('Invalid module.') return 'Invalid module' callables = FindCallables(module) hook_fail = HookToCore( self, callables, reload=reload) # If None is returned, assume success. if hook_fail: logging.debug('Hook failed: {}'.format(hook_fail)) UnloadModule(self, name) return hook_fail self.modules[module] = callables name = module.__name__ update_support(self) logging.info('Loaded: {}'.format(name)) except FileNotFoundError as ex: return ex except Exception as ex: logging.exception(ex) UnloadModule(self, name) # if not reload: if not self.running: print( 'Server could not be started due to an error in {}: {}'.format( name, ex)) sys.exit() raise
def execute(self, client, recv): source = list(filter(lambda s: s.sid == recv[0][1:], self.ircd.servers)) if not source: logging.error('ERROR: could not find server for {}'.format( recv[0][1:])) return source = source[0] if source.eos: logging.error('ERROR: remote server sent EOS twice!') return self.ircd.new_sync(self.ircd, client, ' '.join(recv)) for server in [server for server in self.ircd.servers if server.eos]: data = ':{} PONG {} {}'.format(source.sid, source.hostname, server.hostname) server._send(data) data = ':{} PONG {} {}'.format(server.sid, server.hostname, source.hostname) source._send(data) logging.info('{}EOS received by: {}{}'.format(Y, source.hostname, W)) if source.socket: for callable in [ callable for callable in self.ircd.hooks if callable[0].lower() == 'server_link' ]: try: callable[2](client, self.ircd, source) except Exception as ex: logging.exception(ex) for s in [s for s in self.ircd.servers if s.introducedBy == source]: logging.info('Also setting EOS for {} to be true'.format(s)) s.eos = True if source.hostname.lower() in self.ircd.pendingLinks: self.ircd.pendingLinks.remove(source.hostname.lower()) source.eos = True if source in self.ircd.sync_queue: for e in self.ircd.sync_queue[source]: logging.info('Sending queued data to {}: {}'.format(source, e)) source._send(e)
def sock_accept(ircd, s): if ircd.listen_socks[s] == 'clients': try: conn, addr = s.accept() if ircd.use_poll: ircd.pollerObject.register(conn, READ_ONLY) port = conn.getsockname()[1] tls = is_sslport(ircd, port) conn_backlog = [ user for user in ircd.users if user.socket and not user.registered ] logging.info('Accepting client on {} -- fd: {}, with IP {}'.format( s, conn.fileno(), addr[0])) if len(conn_backlog) > 100: logging.warning( 'Current connection backlog is >{}, so not allowing any more connections for now. Bye.' .format(len(conn_backlog))) conn.close() return # conn.settimeout(15) if tls and not ircd.pre_wrap: conn = ircd.sslctx[str(port)].wrap_socket(conn, server_side=True) tls = 1 logging.info( 'Wrapped incoming user socket {} in TLS'.format(conn)) try: fp = conn.getpeercert(True) if fp: tls_fingerprint = hashlib.sha256( repr(fp).encode('utf-8')).hexdigest() logging.info('Fingerprint: {}'.format(tls_fingerprint)) except Exception as ex: logging.exception(ex) u = User(ircd, conn, addr, tls) if ircd.use_poll: ircd.fd_to_socket[u.fileno()] = (u.socket, u) ''' try: u.socket.setblocking(1) ### Uncomment this. Why? I don't remember. except OSError as ex: logging.debug(R+'Client {} probably refused the connection due to self-signed cert (ZNC?). This can cause memory leaks.'.format(u)+W) logging.debug(R+'Gently disconnecting user. IP: {}'.format(u.ip)+W) #logging.exception(ex) u.quit(ex) return ''' gc.collect() if u.fileno() == -1: logging.error('{}Invalid fd for {} -- quit() on user{}'.format( R, u, W)) u.quit('Invalid fd') return try: random_ping = ''.join( random.choice(string.ascii_uppercase + string.digits) for _ in range(8)) ircd.pings[u] = random_ping u._send('PING :{}'.format(random_ping)) except Exception as ex: # ircd.snotice('t', '[{}](1) {}'.format(addr[0], ex)) logging.exception(ex) u.quit(ex) return except Exception as ex: logging.exception(ex) conn.close() return elif ircd.listen_socks[s] == 'servers': try: conn, addr = s.accept() if ircd.use_poll: ircd.pollerObject.register(conn, READ_ONLY) port = conn.getsockname()[1] tls = is_sslport(ircd, port) # conn.settimeout(15) if tls and not ircd.pre_wrap: tls = 0 version = '{}{}'.format(sys.version_info[0], sys.version_info[1]) conn = ircd.sslctx[str(port)].wrap_socket(conn, server_side=True) tls = 1 logging.info( 'Wrapped incoming server socket {} in TLS'.format(conn)) s = Server(origin=ircd, serverLink=True, sock=conn, is_ssl=tls) except ssl.SSLError as ex: logging.exception(ex) return except Exception as ex: logging.exception(ex) return
def checkConf(ircd, user, confdir, conffile, rehash=False): global errors global confOpers global confLinks confOpers = [] confLinks = [] errors = [] main_conf = conffile if rehash: user.sendraw(382, '{} :Rehashing'.format(confdir + conffile)) try: with open(confdir + conffile) as j: j = j.read().split('\n') j = json_preprocess(j) tempconf = json.loads(j, object_pairs_hook=collections.OrderedDict) if 'me' not in tempconf: conferr('\'me\' block not found!') else: if 'server' not in tempconf['me']: conferr('\'me::server\' not found!') if 'name' not in tempconf['me']: conferr('\'me::name\' not found!') else: ircd.name = tempconf['me']['name'] if 'sid' not in tempconf['me']: conferr('\'me::sid\' not found!') if tempconf['me']['sid'] == '000': conferr( '\'me::sid\' must be a unique number. Please change this to a random number between 001-999' ) if not tempconf['me']['sid'].isdigit() or len( tempconf['me']['sid']) != 3: conferr('\'me::sid\' must be a 3 length number') if isinstance(tempconf['me']['sid'], int): tempconf['me']['sid'] = str(tempconf['me']['sid']) ircd.hostname = tempconf['me']['server'] ircd.name = tempconf['me']['name'] ircd.sid = tempconf['me']['sid'] if not os.path.isfile("conf/ircd.motd"): conferr("conf/ircd.motd not found") if 'admin' not in tempconf: conferr('\'admin\' block not found!') if 'listen' not in tempconf: conferr('\'listen\' block not found') else: for port in tempconf['listen']: if not str(port).isdigit(): conferr( 'invalid port in \'listen\' block: {} -- ports must be numeric.' .format(port)) continue if int(port) <= 1024: conferr( 'invalid port in \'listen\' block: {} -- port cannot be lower than 1024.' .format(port)) continue if 'options' not in tempconf['listen'][port]: conferr( '\'options\' missing in \'listen\' block for port {}.'. format(port)) elif 'clients' not in tempconf['listen'][port][ 'options'] and 'servers' not in tempconf['listen'][ port]['options']: conferr( "Port {} does not have a 'servers' or 'clients' option in their listen-block." .format(port)) elif 'clients' in tempconf['listen'][port][ 'options'] and 'servers' in tempconf['listen'][port][ 'options']: conferr( "'options' can not contain both 'servers' and 'clients' in 'listen' block for port {}." .format(port)) default_cert, default_key = ircd.rootdir + '/ssl/server.cert.pem', ircd.rootdir + '/ssl/server.key.pem' if not os.path.isfile(default_cert) or not os.path.isfile( default_key): conferr( "You have one or more SSL ports listening but there are files missing in {}/ssl/ folder. Make sure you have 'server.cert.pem' and 'server.key.pem' present!" .format(ircd.rootdir), noConf=True) conferr( "You can create self-signed certs (not recommended) by issuing the following command in your terminal: openssl req -x509 -nodes -newkey rsa:4096 -keyout server.key.pem -out server.cert.pem", noConf=True) conferr( "or you can get a free CA cert from Let's Encrypt: https://letsencrypt.org", noConf=True) ircd.default_cert, ircd.default_key = default_cert, default_key ircd.default_ca_file = 'ssl/curl-ca-bundle.crt' for port in tempconf['listen']: ircd.tls_files[port] = {} ircd.tls_files[port]['keypass'] = None if 'ssl' in tempconf['listen'][port]['options']: ircd.tls_files[port]['cert'] = default_cert ircd.tls_files[port]['key'] = default_key if 'ssl-options' not in tempconf['listen'][port]: if not os.path.isfile( default_cert) or not os.path.isfile( default_key): conferr( "You have one or more SSL ports listening but there are files missing in {}/ssl/ folder. Make sure you have 'server.cert.pem' and 'server.key.pem' present!" .format(ircd.rootdir), noConf=True) conferr( "You can create self-signed certs (not recommended) by issuing the following command in your terminal: openssl req -x509 -newkey rsa:4096 -keyout server.key.pem -out server.cert.pem", noConf=True) conferr( "or you can get a free CA cert from Let's Encrypt: https://letsencrypt.org", noConf=True) break if 'ssl-options' in tempconf['listen'][port]: t = tempconf['listen'][port]['ssl-options'] if 'certificate' not in t: logging.warning( f"Certificate path is missing in 'ssl-options' for TLS port {port}" ) logging.warning("Falling back to default.") elif not os.path.realpath(t['certificate']): logging.warning( f"Certificate for port {port} could not be found: {t['certificate']}." ) logging.warning( "Make sure the file exists and is accessible by the current user. Falling back to default." ) else: ircd.tls_files[port]['cert'] = os.path.realpath( t['certificate']) if 'key' not in t: logging.warning( f"Key path is missing in 'ssl-options' for TLS port {port}" ) logging.warning("Falling back to default.") elif not os.path.realpath(t['key']): logging.warning( f"Key could for port {port} not be found: {t['key']}." ) logging.warning( "Make sure the file exists and is accessible by the current user. Falling back to default." ) else: ircd.tls_files[port]['key'] = os.path.realpath( t['key']) if 'keypass' in t and t['keypass']: if len(t['keypass']) < 6: logging.warning( f"Insecure TLS key password for file: '{ircd.tls_files[port]['key']}'" ) ircd.tls_files[port]['keypass'] = t['keypass'] if 'verify-certs' in t: ircd.tls_files[port]['verify-certs'] = t[ 'verify-certs'] if 'class' not in tempconf: conferr('\'class\' block not found') else: for cls in tempconf['class']: if 'sendq' not in tempconf['class'][cls]: conferr('\'sendq\' missing for class \'{}\''.format(cls)) if 'recvq' not in tempconf['class'][cls]: conferr('\'recvq\' missing for class \'{}\''.format(cls)) if 'max' not in tempconf['class'][cls]: conferr('\'max\' missing for class \'{}\''.format(cls)) if 'allow' not in tempconf: conferr('\'allow\' block not found') else: for cls in tempconf['allow']: if cls not in tempconf['class']: conferr( 'Class \'{}\' found in allow-block, but it does not exist' .format(cls)) else: if 'ip' not in tempconf['allow'][ cls] and 'hostname' not in tempconf['allow'][cls]: conferr( '\'ip\' or \'hostname\' missing for allow-class \'{}\'' .format(cls)) if 'maxperip' not in tempconf['allow'][cls]: conferr('\'maxperip\' missing for allow-class \'{}\''. format(cls)) if 'settings' not in tempconf: conferr('\'settings\' block not found!') else: reqvalues = [ 'cloak-key', 'throttle', 'nickflood', 'regtimeout', 'restartpass', 'diepass' ] for v in reqvalues: if v not in tempconf['settings']: conferr('\'{}\' missing in settings-block'.format(v)) if 'throttle' in tempconf['settings']: try: tempconf['settings']['throttle'].split(':')[1] except: conferr( 'invalid \'throttle\' in settings-block: must be connections:time in integer format' ) if not tempconf['settings']['throttle'].split(':')[0].isdigit( ) or not tempconf['settings']['throttle'].split( ':')[1].isdigit(): conferr( 'invalid \'throttle\' in settings-block: must be connections:time in integer format' ) if 'nickflood' in tempconf['settings']: try: tempconf['settings']['nickflood'].split(':')[1] except: conferr( 'invalid \'nickflood\' in settings-block: must be nickchanges:time in integer format' ) if not tempconf['settings']['nickflood'].split(':')[0].isdigit( ) or not tempconf['settings']['nickflood'].split( ':')[1].isdigit(): conferr( 'invalid \'nickflood\' in settings-block: must be nickchanges:time in integer format' ) if 'regtimeout' in tempconf['settings']: if not str(tempconf['settings']['regtimeout']).isdigit( ) or not isinstance(tempconf['settings']['regtimeout'], int): conferr( 'invalid \'regtimeout\' in settings-block: must be an integer' ) if 'cloak-prefix' in tempconf['settings'] and tempconf['settings'][ 'cloak-prefix']: prefix = tempconf['settings']['cloak-prefix'] if len(prefix) > 5: conferr( '\'cloak-prefix\' is set, but exceeds the maximum length, which is 5' ) else: p = re.compile("(^[a-zA-Z0-9]{1,5}$)") match = p.search(prefix) if not match: conferr( 'invalid \'cloak-prefix\': only AZ-az0-9 characters are allowed, up to 5' ) if 'ulines' in tempconf['settings'] and not isinstance( tempconf['settings']['ulines'], list): conferr('invalid \'ulines\' in settings-block: must be a list') if 'services' in tempconf['settings'] and not isinstance( tempconf['settings']['services'], str): conferr('invalid \'ulines\' in settings-block: must be a string') if 'restartpass' in tempconf['settings'] and not isinstance( tempconf['settings']['restartpass'], str): conferr( 'invalid \'restartpass\' in settings-block: must be a string') else: if 'restartpass' in tempconf['settings'] and len( tempconf['settings']['restartpass']) < 8: conferr( 'Restart password is too short in settings-block: minimum 8 characters' ) if 'diepass' in tempconf['settings'] and not isinstance( tempconf['settings']['diepass'], str): conferr('invalid \'diepass\' in settings-block: must be a string') else: if 'diepass' in tempconf['settings'] and len( tempconf['settings']['diepass']) < 8: conferr( 'Die password is too short in settings-block: minimum 8 characters' ) if 'dnsbl' in tempconf: if 'list' not in tempconf['dnsbl']: conferr('\'list\' containing DNSBL\'s missing') if 'iplist' in tempconf['dnsbl']: try: with open(confdir + tempconf['dnsbl']['iplist'], 'r') as f: ircd.bannedList = f.read().splitlines() logging.debug( 'Added {} entries to bannedList: {}'.format( len(ircd.bannedList), ircd.bannedList)) except Exception as ex: pass if 'opers' in tempconf: oper_conf = main_conf if 'link' in tempconf: link_conf = main_conf if 'include' in tempconf: for include in tempconf['include']: noUpdate = False conffile = include try: with open(confdir + conffile) as j: t = j.read().split('\n') t = json_preprocess(t) t = json.loads(t) for entry in dict(t): if entry in tempconf: tempconf[entry].update(t[entry]) noUpdate = True continue if not noUpdate: tempconf.update(t) if 'opers' in t: oper_conf = include if 'link' in t: link_conf = include if 'dnsbl' in t: if 'list' not in t['dnsbl']: conferr('\'list\' containing DNSBL\'s missing') except Exception as ex: logging.exception(ex) conferr(ex, err_conf=include) if 'opers' in tempconf: check_opers(tempconf, err_conf=oper_conf) if 'link' in tempconf: check_links(tempconf, err_conf=link_conf) ircd.excepts = {} if 'except' in tempconf: for type in tempconf['except']: if type not in ircd.excepts: ircd.excepts[type] = [] for entry in tempconf['except'][type]: ircd.excepts[type].append(entry) ircd.deny = {} if 'deny' in tempconf: for entry in tempconf['deny']: ircd.deny[entry] = tempconf['deny'][entry] logging.debug('Added deny for {}: {}'.format(entry, ircd.deny)) # Checking optional modules. if 'modules' not in tempconf: conferr( 'You don\'t seem to have any modules loaded. ProvisionIRCd will not function without them.' ) if 'modules' in tempconf: local_modules = [m for m in ircd.modules] for m in dict(ircd.modules): # logging.info('Unloading module {}'.format(m.__name__)) Modules.UnloadModule(ircd, m.__name__) modules = Modules.ListModules(ircd) for m in [ m for m in local_modules if m not in tempconf['modules'] ]: tempconf['modules'].append(m.__name__) for m in [ m for m in tempconf['modules'] if not m.startswith('modules.') ]: m = 'modules.' + m.replace('/', '.') try: reload = 1 if m in [mod.__name__ for mod in local_modules] else 0 module = [ mod for mod in local_modules if mod.__name__ == m ] if module: module = module[0] try: result = Modules.LoadModule(ircd, m, modules[m], reload=reload, module=module) if result: raise Exception(result) except Exception as ex: err = 'Unable to load module \'{}\': {}'.format(m, ex) logging.error(err) if rehash: ircd.broadcast( [user], 'NOTICE {} :*** [info] -- {}'.format( user.nickname, err)) else: conferr(err) continue except Exception as ex: logging.exception(ex) ircd.default_sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS) tls_cert, tls_key = ircd.default_cert, ircd.default_key ircd.default_sslctx.load_cert_chain(certfile=tls_cert, keyfile=tls_key) ircd.default_sslctx.load_default_certs( purpose=ssl.Purpose.CLIENT_AUTH) ircd.default_sslctx.load_verify_locations( cafile=ircd.default_ca_file) ircd.default_sslctx.verify_mode = ssl.CERT_NONE ircd.sslctx = {} for port in [ p for p in tempconf['listen'] if 'ssl' in tempconf['listen'][p]['options'] ]: try: ircd.sslctx[port] = ssl.SSLContext(ssl.PROTOCOL_TLS) tls_cert = ircd.tls_files[port]['cert'] tls_key = ircd.tls_files[port]['key'] tls_key_pass = ircd.tls_files[port]['keypass'] ircd.sslctx[port].load_cert_chain(certfile=tls_cert, keyfile=tls_key, password=tls_key_pass) logging.info( f'Using on port {port}: cert {tls_cert} and key {tls_key}' ) logging.info( f"Password protected key: {'yes' if tls_key_pass else 'no'}" ) ircd.sslctx[port].load_default_certs( purpose=ssl.Purpose.CLIENT_AUTH) ircd.sslctx[port].load_verify_locations( cafile=ircd.default_ca_file) ircd.sslctx[port].verify_mode = ssl.CERT_NONE if 'verify-certs' in ircd.tls_files[ port] and ircd.tls_files[port]['verify-certs']: ircd.sslctx[port].verify_mode = ssl.CERT_OPTIONAL logging.warning( f"TLS port {port} will only accept validatable certificates." ) # localServer.sslctx = temp_sslctx except PermissionError as ex: err = f'Reloading TLS certificates for port {port} failed with PermissionError. Make sure the files can be read by the current user.' logging.exception(ex) if rehash: ircd.notice(user, '*** {}'.format(err)) else: conferr(err) except FileNotFoundError as ex: err = "One or more required SSL files could not be found." err += "\nCheck to see if the following files are present and valid:" err += "\n" + tls_key err += "\n" + tls_cert conferr(err) logging.exception(ex) except Exception as ex: logging.exception(ex) if rehash: ircd.notice(user, '*** [ssl] -- Error: {}'.format(ex)) except KeyError as ex: exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] e = 'EXCEPTION: {} in file {} line {}: {}'.format( exc_type.__name__, fname, exc_tb.tb_lineno, exc_obj) conferr('Missing conf block: {}'.format(ex), err_conf=main_conf) except json.JSONDecodeError as ex: s = 'Invalid conf format. Make sure the JSON format is correct: {}'.format( ex) conferr(s) logging.exception(ex) except Exception as ex: # pass exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] e = 'EXCEPTION: {} in file {} line {}: {}'.format( exc_type.__name__, fname, exc_tb.tb_lineno, exc_obj) conferr(e) if errors: for e in errors: s = 'ERROR: ' + e if ircd.running: logging.error(e) else: print(s) if rehash: ircd.broadcast([user], 'NOTICE {} :*** [error] -- {}'.format( user.nickname, e)) if rehash: ircd.broadcast( [user], 'NOTICE {} :*** Configuration failed to reload.'.format( user.nickname)) return 0 else: # Open new ports? currently_listening = [] new_ports = [] for sock in ircd.listen_socks: try: ip, port = sock.getsockname() currently_listening.append(str(port)) except Exception as ex: logging.exception(ex) for port in tempconf['listen']: new_ports.append(port) ircd.conf = tempconf for p in [ p for p in ircd.conf['listen'] if str(p) not in currently_listening ]: if 'clients' in set(ircd.conf['listen'][p]['options']): try: ircd.listen_socks[ircd.listenToPort(int(p), 'clients')] = 'clients' except Exception as ex: logging.warning('Unable to listen on port {}: {}'.format( p, ex)) elif 'servers' in set(ircd.conf['listen'][p]['options']): try: ircd.listen_socks[ircd.listenToPort(int(p), 'servers')] = 'servers' except Exception as ex: logging.warning('Unable to listen on port {}: {}'.format( p, ex)) # Now close ports. for p in [ p for p in ircd.listen_socks if str(p.getsockname()[1]) not in new_ports ]: try: logging.info('Closing port {}'.format(p)) del ircd.listen_socks[p] p.close() except Exception as ex: print(ex) if rehash: ircd.broadcast( [user], 'NOTICE {} :*** Configuration reloaded without any problems.'. format(user.nickname)) del j, t, tempconf # Load +P channels from db/chans.db with their modes/settings. if os.path.exists(ircd.rootdir + '/db/chans.db'): perm_data = {} try: with open(ircd.rootdir + '/db/chans.db') as f: perm_data = f.read().split('\n')[0] perm_data = json.loads(perm_data) # Restoring permanent channels. for chan in [ chan for chan in perm_data if chan.lower() not in [c.name.lower() for c in ircd.channels] ]: c = Channel(chan) ircd.channels.append(c) ircd.chan_params[c] = {} for callable in [ callable for callable in ircd.hooks if callable[0].lower() == 'channel_create' ]: try: callable[2](ircd, ircd, c) except Exception as ex: logging.exception(ex) if 'creation' in perm_data[chan]: c.creation = perm_data[chan]['creation'] params = [] for m in [ m for m in perm_data[chan]['modes'] if m in perm_data[chan]['modeparams'] ]: params.append(perm_data[chan]['modeparams'][m]) # params = ' '.join([perm_data[chan]['modeparams'][key] for key in perm_data[chan]['modeparams']]) modestring = '+{} {}'.format(perm_data[chan]['modes'], ' '.join(params)) logging.debug('Sending: {}'.format(modestring)) ircd.handle('MODE', c.name + ' ' + modestring) logging.debug('Sent: {}'.format(modestring)) c.bans = perm_data[chan]['bans'] c.excepts = perm_data[chan]['excepts'] c.invex = perm_data[chan]['invex'] if perm_data[chan]['topic']: c.topic = perm_data[chan]['topic'][0] c.topic_author = perm_data[chan]['topic'][1] c.topic_time = perm_data[chan]['topic'][2] logging.debug('Restored: {}'.format(c)) logging.debug('Modes: {}'.format(c.modes)) logging.debug('Params: {}'.format(ircd.chan_params[c])) except Exception as ex: logging.exception(ex) # Restore TKL. if os.path.exists(ircd.rootdir + '/db/tkl.db') and not ircd.tkl: try: with open(ircd.rootdir + '/db/tkl.db') as f: tkl_data = f.read().split('\n')[0] tkl_data = json.loads(tkl_data) ircd.tkl = tkl_data num = 0 for key in ircd.tkl: for item in ircd.tkl[key]: num += 1 logging.debug( f'Restored {(num)} TKL entr{"y" if num == 1 else "ies"}.' ) except Exception as ex: logging.exception(ex) return 1 gc.collect() return 1
def execute(self, client, recv): exists = [ s for s in self.ircd.servers + [self.ircd] if s.hostname.lower() == recv[2].lower() ] if exists and client != exists[0]: logging.error('Server {} already exists on this network2'.format( recv[2])) client.quit('Server already exists on this network') return if not client.sid: logging.error( 'Direct link with {} denied because their SID is unknown to me' .format(recv[2])) client.quit('No SID received') return # SERVER irc.example.com 1 :versionstring Server name goes here. if not client.linkAccept and not client.eos: # Information is gathered backwards from recv. client.linkAccept = True tempName = ' '.join(recv).split(':')[-2] client.hostname = tempName.split()[-2].strip() client.hopcount = int(tempName.split()[-1]) client.name = ' '.join( recv[1:]).split(':')[1] # ' '.join(recv[4:]) # Snowflake fix. if client.hostname not in self.ircd.conf['settings']['ulines'] + [ self.ircd.conf['settings']['services'] ]: client.name = ' '.join(client.name.split()[1:]) if client.name.startswith(':'): client.name = client.name[1:] logging.info('{}Hostname for {} set: {}{}'.format( G, client, client.hostname, W)) if [ s for s in self.ircd.servers + [self.ircd] if s.hostname.lower() == client.hostname.lower() and s != client ]: logging.error( 'Server {} already exists on this network'.format( client.hostname)) error = 'Error connecting to server {}[{}:{}]: server {} already exists on remote network'.format( client.hostname, ip, port, client.hostname) client._send(':{} ERROR :{}'.format(self.ircd.sid, error)) client.quit('server {} already exists on this network'.format( client.hostname)) return logging.info('{}Server name for {} set: {}{}'.format( G, client, client.name, W)) logging.info('{}Hopcount for {} set: {}{}'.format( G, client, client.hopcount, W)) logging.info('{}SID for {} set: {}{}'.format( G, client, client.sid, W)) if validate_server_info(self, client): selfIntroduction(self.ircd, client) data = ':{} SID {} 1 {} :{}'.format(self.ircd.sid, client.hostname, client.sid, client.name) self.ircd.new_sync(self.ircd, client, data) for server in [ server for server in self.ircd.servers if server.sid and server != client and server.eos ]: logging.info('Introducing {} to {}'.format( server.hostname, client.hostname)) sid = self.ircd.sid if server.socket else server.uplink.sid data = ':{} SID {} {} {} :{}'.format( sid, server.hostname, int(server.hopcount) + 1, server.sid, server.name) client._send(data) if hasattr(client, 'outgoing') and client.outgoing: syncData(self.ircd, client)
def execute(self, client, recv): # :420 SID link1.provisionweb.org 1 420 :ProvisionDev uplink = [s for s in self.ircd.servers if s.sid == recv[0][1:]] if not uplink: client._send(':{} ERROR :Could not find uplink for {}'.format( self.ircd.sid, recv[0][1:])) client.quit() return uplink = uplink[0] sid = recv[4] hostname = recv[2] name = ' '.join(recv[5:])[1:] hopcount = int(recv[3]) if client.hostname == hostname: logging.debug(f'New incoming link: {hostname}') if validate_server_info(self, client): client.name = name selfIntroduction(self.ircd, client) data = ':{} SID {} 1 {} :{}'.format(self.ircd.sid, client.hostname, client.sid, client.name) self.ircd.new_sync(self.ircd, client, data) for server in [ server for server in self.ircd.servers if server.sid and server != client and server.eos ]: logging.info('Introducing {} to {}'.format( server.hostname, client.hostname)) sid = self.ircd.sid if server.socket else server.uplink.sid data = ':{} SID {} {} {} :{}'.format( sid, server.hostname, int(server.hopcount) + 1, server.sid, server.name) client._send(data) if hasattr(client, 'outgoing') and client.outgoing: syncData(self.ircd, client) return for server in [ server for server in self.ircd.servers if server.sid == sid and server != client ]: client._send( ':{} ERROR :SID {} is already in use on that network'.format( self.ircd.sid, sid)) client.quit('SID {} is already in use on that network'.format(sid)) return for server in [ server for server in self.ircd.servers if server.hostname.lower() == hostname.lower() and server != client ]: client._send( ':{} ERROR :Hostname {} is already in use on that network'. format(self.ircd.sid, hostname)) client.quit( 'Server {} is already in use on that network'.format(hostname)) return newServer = ircd.Server(origin=self.ircd, serverLink=True) newServer.hostname = hostname newServer.hopcount = hopcount newServer.name = ' '.join(recv[5:])[1:] newServer.introducedBy = client newServer.uplink = uplink newServer.sid = sid logging.info( f'{G}New server added to the network: {newServer.hostname} ({newServer.name}{W})' ) logging.info('{}SID: {}{}'.format(G, newServer.sid, W)) logging.info('{}Introduced by: {} ({}) {}'.format( G, newServer.introducedBy.hostname, newServer.introducedBy.sid, W)) logging.info('{}Uplinked to: {} ({}) {}'.format( G, newServer.uplink.hostname, newServer.uplink.sid, W)) logging.info('{}Hopcount: {}{}'.format(G, newServer.hopcount, W)) self.ircd.new_sync(self.ircd, client, ' '.join(recv))
def extbans(self, localServer, channel, modebuf, parambuf, action, modebar, param): try: if modebar not in 'beI' or action != '+': return if not param: logging.error('ERROR: invalid param received for {}{}: {}'.format( action, modebar, param)) return if not re.findall("(^{}[{}]):(.*)".format(prefix, ''.join(ext_bans)), param): # logging.info('Param {} is invalid for {}{}'.format(param, action, modebar)) return logging.info('Param for {}{} set: {}'.format(action, modebar, param)) try: setter = self.fullmask() except: setter = self.hostname if modebar == 'b': if param[:2] not in ['~L', '~T', '~c', '~t', '~b']: return if param[:2] == '~L': # Channel redirect. ~L:host:#chan if len(param.split(':')) < 3: return redirect_mask = make_mask(localServer, param.split(':')[1]) redirect_chan = param.split(':')[2] param = param[:2] + ':' + redirect_mask + ':' + redirect_chan if redirect_chan[0] not in localServer.chantypes: logging.info('Channel {} is invalid for {}{} {}'.format( redirect_chan, action, modebar, param)) return redirect_chan = next( (c for c in localServer.channels if c.name.lower() == redirect_chan.lower()), None) if redirect_chan and 'L' in redirect_chan.modes: logging.info( 'Channel {} is invalid for {}{} {} (target has +L set)' .format(redirect_chan.name, action, modebar, param)) self.sendraw(690, ':Destination channel already has +L.') return elif param[:2] == '~T': # Text block. if param.split(':')[1] not in ['block', 'replace' ] or len(param.split(':')) < 3: return bAction = param.split(':')[1] if not param.split(':')[2:][0]: return if bAction == 'replace': # Replace requires an additional parameter: ~T:replace:match:replacement if len(param.split(':')) < 4: return if not param.split(':')[3]: return elif param[:2] == '~c': ### Channel block. if len(param.split(':')) < 2: return chanBan = param.split(':')[1] if chanBan[0] not in localServer.chantypes or chanBan[ 0] not in '+%@&~': logging.info('Channel {} is invalid for {}{} {}'.format( chanBan, action, modebar, param)) return tempchan = list( filter(lambda c: c.name.lower() == chanBan.lower(), localServer.channels)) if tempchan and len(channel.users) > 2: tempchan = tempchan[0] # tempchan users are forbidden on channel. for user in [ user for user in channel.users if tempchan in user.channels and user.chlevel(channel) < 2 and not user.ocheck('o', 'override') and not checkMatch(user, localServer, 'e', channel) ]: cmd = ( 'KICK', '{} {} :Users from {} are not welcome here'.format( channel.name, user.nickname, tempchan.name)) commandQueue.append(cmd) elif param[:2] == '~t': # Timed bans. if len(param.split(':')) < 3: return bTime = param.split(':')[1] if not bTime.isdigit(): return banmask = make_mask(localServer, param.split(':')[2]) param = '{}:{}'.format(':'.join(param.split(':')[:2]), banmask) elif param[:2] == '~b': # Extension on normal bans. if len(param.split(':')) < 3: return bParam = param.split(':')[1] if bParam not in ['R']: return banmask = make_mask(localServer, param.split(':')[2]) param = '{}:{}'.format(':'.join(param.split(':')[:2]), banmask) elif modebar == 'I': if param[:2] == '~O': if len(param.split(':')) < 2: return if modebar == 'b': c = channel.bans elif modebar == 'I': c = channel.invex elif modebar == 'e': c = channel.excepts if param not in c: modebuf.append(modebar) parambuf.append(param) c[param] = {} c[param]['setter'] = setter c[param]['ctime'] = int(time.time()) except Exception as ex: logging.exception(ex)
def __init__(self, conffile=None, forked=False, origin=None, serverLink=False, sock=None, is_ssl=False): self.ctime = int(time.time()) self.syncDone = [] self.eos = False self.sendbuffer = '' self.hopcount = 0 if not serverLink: try: self.forked = forked self.hostname = '*' from handle.functions import initlogging initlogging(self) self.listen_socks = {} self.rootdir = dir_path self.confdir = dir_path + '/conf/' self.modules_dir = dir_path + '/modules/' self.tls_files = {} self.conffile = conffile self.ERR = ERR self.RPL = RPL self.modules = {} self.commands = [] self.user_modes = {} self.channel_modes = {} self.hooks = [] self.api = [] self.support = {} self.command_class = [] self.user_mode_class = [] self.channel_mode_class = [] self.localServer = self self.linkRequests = {} self.sync_queue = {} self.creationtime = int(time.time()) self.versionnumber = '2.0' self.version = 'ProvisionIRCd-{}-beta'.format( self.versionnumber) self.hostinfo = 'Python {}'.format( sys.version.split('\n')[0].strip()) # Polling does not work. self.use_poll = 0 # Polling does not work. self.pre_wrap = 0 # Polling does not work. Also pre-wrapping may cause memleak? Not sure, needs checking. It will prevent you from reloading certs. if self.use_poll: self.pollerObject = select.poll() self.fd_to_socket = {} # Polling does not work. self.socket = None self.introducedBy = None self.uplink = None self.users = [] self.channels = [] self.dnsblCache = {} self.hostcache = {} self.deny_cache = {} self.throttle = {} self.tkl = {} self.bannedList = [] self.user_modes = { "i": (0, "User does not show up in outside /who"), "o": (2, "IRC Operator"), "x": (0, "Hides real host with cloaked host"), "q": (1, "Protected on all channels"), "r": (2, "Identifies the nick as being logged in"), "s": (1, "Can receive server notices"), "z": (2, "User is using a secure connection"), "B": (0, "Marks the client as a bot"), "H": (1, "Hide IRCop status"), "S": (2, "Marks the client as a network service"), } self.channel_modes = { # +v = 1 # +h = 2 # +o = 3 # +a = 4 # +q = 5 # oper = 6 # server = 7 0: { "b": (2, "Bans the given hostmask from the channel", "<nick!ident@host>"), "e": (2, "Users matching an except can go through channel bans", "<nick!ident@host>"), "I": (2, "Matching users can go through channel mode +i", "<nick!ident@host>"), }, 1: { "k": (2, "User must give a key in order to join the channel", "<key>"), "L": (5, "When the channel is full, redirect users to another channel (requires +l)", "<chan>"), }, 2: { "l": (2, "Set a channel user limit", "[number]"), }, 3: { "m": (2, "Moderated channel, need +v or higher to talk"), "n": (2, "No outside messages allowed"), "j": (3, "Quits appear as parts"), "p": (3, "Private channel"), "r": (7, "Channel is registered"), "s": (3, "Channel is secret"), "t": (3, "Only +h or higher can change topic"), "z": (3, "Requires SSL to join the channel"), "C": (2, "CTCPs are not allowed in the channel"), "N": (4, "Nickchanges are not allowed in the channel"), "O": (6, "Only IRCops can join"), "P": (6, "Permanent channel"), "Q": (4, "No kicks allowed"), "R": (3, "You must be registered to join the channel"), "T": (2, "Notices are not allowed in the channel"), "V": (3, "Invite is not permitted on the channel"), }, } self.core_chmodes = 'vhoaq' chmodes_string = '' for t in self.channel_modes: for m in self.channel_modes[t]: chmodes_string += m if t > 0: self.core_chmodes += m chmodes_string += ',' logging.info('Core modes set: {}'.format(self.core_chmodes)) self.chmodes_string = chmodes_string[:-1] # self.snomasks = 'cdfjkostzCFGNQS' # TODO: { "c": (desc, flags) }, self.snos = { # 1 = local "d": ("Can read local connect/disconnect notices", (1, 3)), "o": ("See oper-up notices", (0, 2)), } self.snomasks = { "c": "Can read local connect/disconnect notices", "d": "Can see DNSNL hits", "f": "See flood alerts", "k": "View kill notices", "o": "See oper-up notices", "s": "General server notices", "t": "Trash notices (unimportant stuff)", "C": "Can read global connect/disconnect notices", "F": "View spamfilter matches", "G": "View TKL usages", "N": "Can see nick changes", "Q": "View Q:line rejections", "S": "Can see /sanick, /sajoin, and /sapart usage", } self.chstatus = 'yqaohv' self.chprefix = OrderedDict([('y', '!'), ('q', '~'), ('a', '&'), ('o', '@'), ('h', '%'), ('v', '+')]) self.chprefix = OrderedDict(self.chprefix) first = '(' second = '' for key in self.chprefix: first += key second += self.chprefix[key] first += ')' self.chprefix_string = '{}{}'.format(first, second) self.parammodes = self.chstatus for x in range(0, 4): for m in [ m for m in self.channel_modes[x] if str(x) in '012' and m not in self.parammodes ]: self.parammodes += m self.chan_params = {} self.maxlist = {'b': 500, 'e': 500, 'I': 500} self.maxlist_string = "b:{s[b]},e:{s[e]},I:{s[I]}".format( s=self.maxlist) self.servers = [] self.running = 0 self.caps = [ 'account-notify', 'away-notify', 'server-time', 'chghost', 'echo-message', 'userhost-in-names', 'extended-join', 'operwatch' ] validconf = handle.handleConf.checkConf( self, None, self.confdir, self.conffile) if not validconf: exit() return except Exception as ex: logging.exception(ex) exit() return self.totalcons = 0 self.gusers = [] self.linkrequester = {} self.pendingLinks = [] self.introducedTo = [] self.maxusers = 0 self.maxgusers = 0 self.pings = {} update_support(self) return if serverLink: self.localServer = origin self.socket = sock if self.localServer.use_poll: fd = self.fileno() self.localServer.pollerObject.register(self.socket, READ_WRITE) self.localServer.fd_to_socket[fd] = (self.socket, self) logging.debug('Added {} to fd dict with fd {}'.format( self, fd)) self.creationtime = int(time.time()) self.introducedBy = None self.uplink = None self.introducedTo = [] self.sid = None self.netinfo = False self.linkAccept = False self.linkpass = None self.cls = None self.is_ssl = is_ssl self.recvbuffer = '' self.name = '' self.hostname = '' self.ping = int(time.time()) self.lastPingSent = time.time() * 1000 self.lag = int((time.time() * 1000) - self.lastPingSent) self.origin = origin self.localServer.servers.append(self)
def run(self): ircd = self.ircd while ircd.running: try: if ircd.use_poll: fdVsEvent = ircd.pollerObject.poll(1000) for fd, Event in fdVsEvent: try: s = ircd.fd_to_socket[fd][0] c = ircd.fd_to_socket[fd][1] t = type(c).__name__ if Event & (select.POLLIN | select.POLLPRI): logging.debug('POLLIN for {}'.format(c)) if s in self.listen_socks: logging.debug( 'Incoming connection on {}'.format(s)) threading.Thread(target=sock_accept, args=([ircd, s])).start() elif t in ['User', 'Server']: logging.debug( 'Reading data from {}'.format(c)) read_socket(ircd, c) try: ircd.pollerObject.modify(s, READ_WRITE) logging.debug( 'Flag for {} set to READ_WRITE'.format( c)) except FileNotFoundError: # Already closed. pass continue elif Event & select.POLLOUT: logging.debug('POLLOUT for {} ({})'.format( s, c)) if c.sendbuffer: logging.debug( 'Sending data to {}'.format(c)) check_flood(ircd, c) try: sent = s.send( bytes(c.sendbuffer, 'utf-8')) c.sendbuffer = c.sendbuffer[sent:] except Exception as ex: logging.exception(ex) c.quit('Write error') time.sleep(1000) logging.debug( 'Flag for {} set to READ_ONLY'.format(c)) ircd.pollerObject.modify(s, READ_ONLY) continue elif Event & select.POLLHUP: # ircd.pollerObject.unregister(s) c.quit('Hung up poll') elif Event & select.POLLERR: # ircd.pollerObject.unregister(s) c.quit('Polling error') except Exception as ex: logging.exception(ex) time.sleep(1000) check_loops(ircd) continue else: read_clients = itertools.chain(listen_socks(ircd), users(ircd), servers(ircd)) write_clients = itertools.chain(users(ircd, 'w'), servers(ircd, 'w')) # print(f"Size of read_clients: {sys.getsizeof(read_clients)}") try: read, write, error = select.select( read_clients, write_clients, read_clients, 1.0) # read and error need the same iteratable. # read, write, error = select.select(list(self.listen_socks) + read_users + read_servers, write_users + write_servers, read_users + #read_servers + write_users + write_servers + list(self.listen_socks), 1.0) except ValueError as ex: for fd in iter([ fd for fd in iter(ircd.users) if fd.socket and not fd.registered ]): fd.quit('Limit reached') logging.info('Cleanup done') logging.exception(ex) continue for s in error: logging.error('Error occurred in {}'.format(s)) for s in read: if s in write: # Write first. continue if type(s).__name__ in ['User', 'Server']: read_socket(ircd, s) continue if self.listen_socks[s] in ['clients', 'servers']: threading.Thread(target=sock_accept, args=([ircd, s])).start() continue for s in write: check_flood(ircd, s) if type(s).__name__ == 'User' or type( s).__name__ == 'Server': try: sent = s.socket.send( bytes(s.sendbuffer, 'utf-8')) s.sendbuffer = s.sendbuffer[sent:] if type(s).__name__ == 'User' and (hasattr( s, 'flood_safe') and s.flood_safe): s.flood_safe = False logging.debug( 'Flood_safe for {} unset.'.format(s)) except Exception as ex: s.quit('Write error: {}'.format(str(ex))) continue check_loops(ircd) except KeyboardInterrupt as ex: # cleanup(ircd) os._exit(0) return except Exception as ex: logging.exception(ex) break os._exit(0) logging.warning( 'data_handler loop broke! This should only happen after /restart.')
def welcome(self): if not self.registered: for callable in [ callable for callable in self.server.hooks if callable[0].lower() == 'pre_local_connect' ]: try: success = callable[2](self, self.server) if not success and success is not None: # Modules need to explicitly return False or 0, not the default None. logging.debug( f"Connection process denied for user {self} by module: {callable}" ) return except Exception as ex: logging.exception(ex) deny, reason = 0, '' if self.ip in self.server.deny_cache: deny = 1 if 'reason' in self.server.deny_cache[self.ip]: reason = self.server.deny_cache[self.ip]['reason'] deny_except = False if 'except' in self.server.conf and 'deny' in self.server.conf[ 'except']: for e in self.server.conf['except']['deny']: if match(e, self.ident + '@' + self.ip) or match( e, self.ident + '@' + self.hostname): deny_except = True break if not deny_except and not deny: for entry in self.server.deny: if match(entry, self.ident + '@' + self.ip) or match( entry, self.ident + '@' + self.hostname): self.server.deny_cache[self.ip] = {} self.server.deny_cache[self.ip]['ctime'] = int( time.time()) reason = self.server.deny[entry] if self.server.deny[ entry] else '' self.server.deny_cache[self.ip]['reason'] = reason logging.info( 'Denied client {} with match: {} [{}]'.format( self, entry, reason)) deny = 1 break if deny: if reason: self.server.notice( self, '* Connection denied: {}'.format(reason)) return self.quit( 'Your host matches a deny block, and is therefore not allowed.' ) block = 0 for cls in iter([ cls for cls in self.server.conf['allow'] if cls in self.server.conf['class'] ]): t = self.server.conf['allow'][cls] isMatch = False if 'ip' in t: clientmask = '{}@{}'.format(self.ident, self.ip) isMatch = match(t['ip'], clientmask) if 'hostname' in t and not isMatch: # Try with hostname. IP has higher priority. clientmask = '{}@{}'.format(self.ident, self.hostname) isMatch = match(t['hostname'], clientmask) if isMatch: if 'options' in t: if 'ssl' in t['options'] and not self.ssl: continue self.cls = cls if 'block' in t: for entry in t['block']: clientmask_ip = '{}@{}'.format(self.ident, self.ip) clientmask_host = '{}@{}'.format( self.ident, self.hostname) block = match(entry, clientmask_ip) or match( entry, clientmask_host) if block: logging.info( 'Client {} blocked by {}: {}'.format( self, cls, entry)) break break if not self.cls or block: return self.quit( 'You are not authorized to connect to this server') totalClasses = list( filter(lambda u: u.server == self.server and u.cls == self.cls, self.server.users)) if len(totalClasses) > int( self.server.conf['class'][self.cls]['max']): return self.quit('Maximum connections for this class reached') clones = [ u for u in self.server.users if u.socket and u.ip == self.ip ] if len(clones) >= int( self.server.conf['allow'][self.cls]['maxperip']): return self.quit('Maximum connections from your IP') # Resolve IP in the background to test against deny-block matches, if host resolution is disabled. if 'dontresolve' in self.server.conf['settings']: threading.Thread(target=resolve_ip, args=([self])).start() if not hasattr(self, 'socket'): return self.sendraw( RPL.WELCOME, ':Welcome to the {} IRC Network {}!{}@{}'.format( self.server.name, self.nickname, self.ident, self.hostname)) self.sendraw( RPL.YOURHOST, ':Your host is {}, running version {}'.format( self.server.hostname, self.server.version)) d = datetime.datetime.fromtimestamp( self.server.creationtime).strftime('%a %b %d %Y') t = datetime.datetime.fromtimestamp( self.server.creationtime).strftime('%H:%M:%S %Z') self.sendraw(RPL.CREATED, ':This server was created {} at {}'.format(d, t)) umodes, chmodes = '', '' for m in iter([ m for m in self.server.user_modes if m.isalpha() and m not in umodes ]): umodes += m for t in self.server.channel_modes: for m in iter([ m for m in self.server.channel_modes[t] if m.isalpha() and m not in chmodes ]): chmodes += m umodes = ''.join(sorted(set(umodes))) chmodes = ''.join(sorted(set(chmodes))) self.sendraw( RPL.MYINFO, '{} {} {} {}'.format(self.server.hostname, self.server.version, umodes, chmodes)) show_support(self, self.server) cipher = None if self.ssl and hasattr(self.socket, 'cipher') and self.socket.cipher: if self.socket.cipher(): cipher = self.socket.cipher()[0] self.send( 'NOTICE', ':*** You are connected to {} with {}-{}'.format( self.server.hostname, self.socket.version(), cipher)) msg = '*** Client connecting: {u.nickname} ({u.ident}@{u.hostname}) {{{u.cls}}} [{0}{1}]'.format( 'secure' if self.ssl else 'plain', '' if not cipher else ' ' + cipher, u=self) self.server.snotice('c', msg) binip = IPtoBase64(self.ip) if self.ip.replace( '.', '').isdigit() else self.ip data = '{s.nickname} {s.server.hopcount} {s.signon} {s.ident} {s.hostname} {s.uid} 0 +{s.modes} {s.cloakhost} {s.cloakhost} {0} :{s.realname}'.format( binip, s=self) self.server.new_sync(self.server, self.server, ':{} UID {}'.format(self.server.sid, data)) self.registered = True current_lusers = len([ user for user in self.server.users if user.server == self.server and user.registered ]) if current_lusers > self.server.maxusers: self.server.maxusers = current_lusers if len(self.server.users) > self.server.maxgusers: self.server.maxgusers = len(self.server.users) if self.server.maxgusers % 10 == 0: self.server.snotice( 's', '*** New global user record: {}'.format( self.server.maxgusers)) self.handle('lusers') self.handle('motd') for callable in [ callable for callable in self.server.hooks if callable[0].lower() == 'welcome' ]: try: callable[2](self, self.server) except Exception as ex: logging.exception(ex) if self.fingerprint: self.send( 'NOTICE', ':*** Your TLS fingerprint is {}'.format(self.fingerprint)) data = 'MD client {} certfp :{}'.format( self.uid, self.fingerprint) self.server.new_sync(self.server, self.server, ':{} {}'.format(self.server.sid, data)) modes = [] for mode in self.server.conf['settings']['modesonconnect']: if mode in self.server.user_modes and mode not in 'oqrzS': modes.append(mode) if self.ssl and hasattr(self.socket, 'cipher'): modes.append('z') if len(modes) > 0: p = {'override': True} self.handle('mode', '{} +{}'.format(self.nickname, ''.join(modes)), params=p) watch_notify = iter([ user for user in self.server.users if self.nickname.lower() in [x.lower() for x in user.watchlist] ]) for user in watch_notify: user.sendraw( RPL.LOGON, '{} {} {} {} :logged online'.format( self.nickname, self.ident, self.cloakhost, self.signon)) for callable in [ callable for callable in self.server.hooks if callable[0].lower() == 'local_connect' ]: try: callable[2](self, self.server) except Exception as ex: logging.exception(ex) gc.collect()
def UnloadModule(self, name): try: for module in [ module for module in list(self.modules) if module.__name__ == name ]: # Tuple: commands, user_modes, channel_modes, hooks, support, api, module m = module.__name__ if m == name: core_classes = self.user_mode_class + self.channel_mode_class + self.command_class for m in [m for m in core_classes if m.module == module]: m.unload() if hasattr(module, 'unload'): try: module.unload(self) except Exception as ex: logging.exception(ex) for function in [ function for function in self.modules[module][3] if hasattr(function, 'hooks') ]: for h in list(function.hooks): info = (h[0], h[1], function, module) function.hooks.remove(h) if info in self.hooks: self.hooks.remove(info) # logging.info('Unhooked {}'.format(info)) else: logging.error( 'Unable to remove hook {}: not found in hooks list' .format(info)) for function in [ function for function in self.modules[module][5] if hasattr(function, 'api') ]: for a in list(function.api): function.api.remove(a) api_cmd = a[0] api_host = None if len(a) < 2 else a[1] api_password = None if len(a) < 3 else a[2] info = (api_cmd, function, api_host, api_password, module) try: self.api.remove(info) except ValueError: logging.error( 'Callable {} not found in API list.'.format(a)) # Leftover hooks. for h in [h for h in list(self.hooks) if h[2] == module]: logging.error( 'Hook {} was not properly removed (or added double). Removing now.' .format(h)) self.hooks.remove(h) del self.modules[module] logging.info('Unloaded: {}'.format(m)) update_support(self) return 1 except Exception as ex: logging.exception(ex) return str(ex)
def execute(self, client, recv): raw = ' '.join(recv) source = list( filter(lambda s: s.sid == recv[0][1:] or s.hostname == recv[0][1:], self.ircd.servers)) if not source: logging.error('/SJOIN source not found!') return source = source[0] channel = recv[3] if channel[0] == '&': logging.error( '{}ERROR: received a local channel from remote server: {}{}'. format(R, channel, W)) return client.squit( 'Sync error! Remote server tried to link local channels.') if not client.eos: self.ircd.new_sync(self.ircd, client, raw) self.ircd.parammodes = self.ircd.chstatus for x in range(0, 4): for m in [ m for m in self.ircd.channel_modes[x] if str(x) in '012' and m not in self.ircd.parammodes ]: self.ircd.parammodes += m memberlist = [] banlist = [] excepts = [] invex = [] mod_list_data = [] # Store temp data from mods list types. c = 0 if recv[4].startswith('+'): modes = recv[4].replace('+', '') else: modes = '' for pos in [pos for pos in recv[1:] if pos]: c += 1 if pos.startswith(':'): memberlist = ' '.join(recv[c:]).split('&')[0].split( '"')[0].split("'")[0][1:].split() continue if pos.startswith('&'): banlist.append(pos[1:]) elif pos.startswith('"'): excepts.append(pos[1:]) elif pos.startswith("'"): invex.append(pos[1:]) elif c > 4 and pos and not pos[0].isalpha() and not pos[0].isdigit( ) and pos[0] not in ":&\"'*~@%+": if pos in memberlist: memberlist.remove(pos) # Unrecognized mode, checking modules. # Loop over modules to check if they have a 'mode_prefix' attr. try: for m in [ m for m in self.ircd.modules if hasattr(m, 'mode_prefix') and pos[0] == m.mode_prefix ]: mod_list_data.append((m.chmode, pos[1:])) except Exception as ex: logging.exception(ex) # Custom list. In the beginning of SJOIN args. custom_mode_list = {} list_prefix = pos[0] # Like ^ for m in [ m for m in self.ircd.channel_mode_class if m.type == 0 and m.mode_prefix == list_prefix ]: # 2020/02/29 05:31:20 DEBUG [m_sjoin]: Set lokale <ChannelMode 'w'> mode = m.mode custom_mode_list[mode] = [ ] # Params for mode, like +w (whitelist) p = pos[1:] custom_mode_list[mode].append(p) logging.debug( f"Appended {p} to '{mode}' (list_name={m.list_name}) custom mode list." ) continue data = [] giveModes = [] giveParams = [] removeModes = [] removeParams = [] timestamp = int(recv[2]) for member in memberlist: membernick = [] for c in member: if c not in ':*~@%+': membernick.append(c) membernick = ''.join(membernick) # userClass = list(filter(lambda c: c.nickname.lower() == membernick.lower() or c.uid == membernick, self.ircd.users)) user_class = next((c for c in self.ircd.users if c.nickname.lower() == membernick.lower() or c.uid == membernick), None) if not user_class: logging.error( '{}ERROR: could not fetch userclass for remote user {}. Looks like the user did not sync correctly. Maybe nick collision, or remote leftover from a netsplit.{}' .format(R, membernick, W)) # continue # source.quit('ERROR: could not fetch userclass for remote user {}. Looks like the user did not sync correctly. Maybe nick collision, or remote leftover from a netsplit.'.format(membernick)) continue p = {'override': True, 'sourceServer': client} # Making the remote client join local channel, creating if needed. user_class.handle('join', channel, params=p) localChan = list( filter(lambda c: c.name.lower() == channel.lower(), self.ircd.channels))[0] local_chan = next( (c for c in self.ircd.channels if c.name == channel), None) if not local_chan: logging.error( f"ERROR: Could not find or create local channel: {channel}" ) return 0 if len(local_chan.users) == 1: # Channel did not exist on self.ircd. Hook channel_create? Sure, why not. pass if user_class.server != self.ircd: logging.info( '{}External user {} joined {} on local server.{}'.format( G, user_class.nickname, channel, W)) if timestamp < local_chan.creation and not source.eos: if '*' in member: giveModes.append('q') giveParams.append(user_class.nickname) if '~' in member: giveModes.append('a') giveParams.append(user_class.nickname) if '@' in member: giveModes.append('o') giveParams.append(user_class.nickname) if '%' in member: giveModes.append('h') giveParams.append(user_class.nickname) if '+' in member: giveModes.append('v') giveParams.append(user_class.nickname) if timestamp < local_chan.creation and not source.eos: # Remote channel is dominant. Replacing modes with remote channel # Clear the local modes. logging.info( 'Remote channel {} is dominant. Replacing modes with remote channels\'' .format(channel)) local_chan.creation = timestamp local_chan.name = channel pc = 5 for m in local_chan.modes: if m not in modes and m in list( self.ircd.channel_modes[2]) + list( self.ircd.channel_modes[3]): removeModes.append(m) continue # Remote info is different, remove old one first. if m in self.ircd.channel_modes[1] and self.ircd.chan_params[ local_chan][m] != recv[pc]: removeParams.append(self.ircd.chan_params[local_chan][m]) removeModes.append(m) if m in self.ircd.parammodes: pc += 1 pc = 5 for m in modes: if m not in local_chan.modes and m in self.ircd.channel_modes[ 3]: giveModes.append(m) continue if m in self.ircd.parammodes: giveModes.append(m) giveParams.append(recv[pc]) logging.debug('SJOIN: Mode {} has param: {}'.format( m, recv[pc])) pc += 1 # Removing local channel user modes. for user in local_chan.users: for m in local_chan.usermodes[user]: removeModes.append(m) removeParams.append(user.nickname) for b in [b for b in local_chan.bans if b not in banlist]: removeModes.append('b') removeParams.append(b) for e in [e for e in local_chan.excepts if e not in excepts]: removeModes.append('e') removeParams.append(e) for I in [I for I in local_chan.invex if I not in invex]: removeModes.append('I') removeParams.append(I) # Remove modulair lists. for m in [m for m in self.ircd.channel_mode_class if m.type == 0]: # Remove modulair lists. mode = m.mode list_name = getattr(m, 'list_name') logging.debug( f"Remote takeover, clearing local list: '{list_name}' (if any)" ) l = getattr(local_chan, list_name) for local_modulair_mode in l: param = local_modulair_mode logging.debug( f"[SJOIN RT] Removing from local: -{mode} {param}") removeModes.append(mode) removeParams.append(param) # Send all remote modes to local_chan for b in [b for b in banlist if b not in local_chan.bans]: giveModes.append('b') giveParams.append(b) for e in [e for e in excepts if e not in local_chan.excepts]: giveModes.append('e') giveParams.append(e) for m in custom_mode_list: for p in custom_mode_list[m]: logging.debug(f"[SJOIN RT] Syncing from remote: +{m} {p}") giveModes.append(m) giveParams.append(p) # ??? for I in [I for I in invex if I not in local_chan.invex]: giveModes.append('I') giveParams.append(I) for m in [ m for m in self.ircd.modules if hasattr(m, 'list_name') and hasattr(local_chan, m.list_name) ]: remote_temp = [] for e in mod_list_data: remote_temp.append(e[1]) for entry in [ entry for entry in getattr(local_chan, m.list_name) if entry not in remote_temp ]: logging.debug( 'Local list entry +{} {} not found in remote data, so removing.' .format(m.chmode, entry, remote_temp)) removeModes.append(m.chmode) removeParams.append(entry) for entry in [ entry for entry in mod_list_data if entry[1] not in getattr(local_chan, m.list_name) ]: giveModes.append(entry[0]) giveParams.append(entry[1]) data = [] data.append(local_chan.name) modes = '{}{}'.format( '-' + ''.join(removeModes) if removeModes else '', '+' + ''.join(giveModes) if giveModes else '') data.append(modes) for p in removeParams: data.append(p) for p in giveParams: data.append(p) elif timestamp == local_chan.creation and not source.eos: if modes: logging.info( '{}Equal timestamps for remote channel {} -- merging modes.{}' .format(Y, local_chan.name, W)) logging.debug(f"Modes: {modes}") for member in memberlist: rawUid = re.sub('[:*!~&@%+]', '', member) if '*' in member: giveModes.append('q') giveParams.append(rawUid) if '~' in member: giveModes.append('a') giveParams.append(rawUid) if '@' in member: giveModes.append('o') giveParams.append(rawUid) if '%' in member: giveModes.append('h') giveParams.append(rawUid) if '+' in member: giveModes.append('v') giveParams.append(rawUid) pc = 5 for m in modes: if m not in local_chan.modes: giveModes.append(m) if m in self.ircd.parammodes: giveParams.append(recv[pc]) pc += 1 continue for b in [b for b in banlist if b not in local_chan.bans]: giveModes.append('b') giveParams.append(b) for e in [e for e in excepts if e not in local_chan.excepts]: giveModes.append('e') giveParams.append(e) for I in [I for I in invex if I not in local_chan.invex]: giveModes.append('I') giveParams.append(I) for m in custom_mode_list: for p in custom_mode_list[m]: logging.debug( f"[SJOIN merge] Appending modes: +{m} {p}") giveModes.append(m) giveParams.append(p) data = [local_chan.name] modes = '{}'.format('+' + ''.join(giveModes) if giveModes else '') data.append(modes) for p in removeParams: data.append(p) for p in giveParams: data.append(p) if modes and data: processModes(client, self.ircd, local_chan, data, sync=1, sourceServer=client, sourceUser=client)
def check_loops(ircd): """ Checks related to users """ pingfreq = 120 users = iter([user for user in iter(ircd.users) if user.socket]) for user in iter([ user for user in users if time.time() - user.ping > pingfreq and time.time() * 1000 - user.lastPingSent > pingfreq / 2 ]): user.lastPingSent = time.time() * 1000 user.lag_measure = user.lastPingSent user._send('PING :{}'.format(ircd.hostname)) ping_users = [user for user in users if time.time() - user.ping >= 180.0] for user in list(ping_users): user.quit('Ping timeout: {} seconds'.format( int(time.time() - user.ping))) for t in iter(ircd.tkl): for mask in dict(ircd.tkl[t]): expire = ircd.tkl[t][mask]['expire'] if expire == '0': continue if int(time.time()) > expire: mask = '{} {}'.format(mask.split('@')[0], mask.split('@')[1]) data = '- {} {}'.format(t, mask) p = {'expire': True} ircd.handle('tkl', data, params=p) # Request links if ircd.users: linkServers = iter([ link for link in ircd.conf['link'] if link.lower() != ircd.hostname.lower() and 'outgoing' in ircd.conf['link'][link] and 'options' in ircd.conf['link'][link] and not list(filter(lambda s: s.hostname == link, ircd.servers)) ]) servers = iter( [link for link in linkServers if link not in ircd.linkRequests]) for link in servers: ircd.linkRequests[link] = {} ircd.linkRequests[link]['ctime'] = int(time.time()) servers = iter([ link for link in linkServers if 'autoconnect' in ircd.conf['link'][link]['options'] ]) for link in iter([ link for link in servers if time.time() - ircd.linkRequests[link]['ctime'] > 900.0 ]): ircd.linkRequests[link] = {} ircd.linkRequests[link]['ctime'] = int(time.time()) logging.info('Auto connecting to {}'.format(link)) connectTo(None, ircd, link, autoLink=True) if len(ircd.dnsblCache) >= 1024: del ircd.dnsblCache[ircd.dnsblCache[0]] if len(ircd.hostcache) >= 1024: del ircd.hostcache[ircd.hostcache[0]] if len(ircd.deny_cache) >= 1024: del ircd.deny_cache[ircd.deny_cache[0]] # Remove cached host look-ups after 6 hours. for host in iter([ host for host in dict(ircd.hostcache) if int(time.time()) - ircd.hostcache[host]['ctime'] > 3600.0 * 6 ]): del ircd.hostcache[host] # Remove cached DNSBL after 1 day. for host in iter([ host for host in dict(ircd.dnsblCache) if int(time.time()) - ircd.dnsblCache[host]['ctime'] > 3600.0 * 24 ]): del ircd.dnsblCache[host] # Remove cached Deny entries after 1 day. for host in iter([ host for host in dict(ircd.deny_cache) if int(time.time()) - ircd.deny_cache[host]['ctime'] > 3600.0 * 24 ]): del ircd.deny_cache[host] # Check for unregistered connections. for user in iter([ user for user in list(ircd.users) if user.socket and not user.registered ]): if time.time() - user.signon >= int( ircd.conf['settings']['regtimeout']): user.quit('Registration timed out') continue for throttle in iter( throttle for throttle in dict(ircd.throttle) if int(time.time()) - ircd.throttle[throttle]['ctime'] > int( ircd.conf['settings']['throttle'].split(':')[1])): del ircd.throttle[throttle] continue # Check for timed channels status. modify_status = {} for chan in channels(ircd): try: for user in dict(chan.temp_status): for mode in chan.temp_status[user]: exp = chan.temp_status[user][mode]['ctime'] if int(time.time()) >= exp: param = '{}{} {}'.format( chan.temp_status[user][mode]['action'], mode, user.nickname) if chan not in modify_status: modify_status[chan] = [] modify_status[chan].append(param) del chan.temp_status[user] except: pass if chan in modify_status: modes = [] for mode in modify_status[chan]: modes.append(mode) ircd.handle('MODE', '{} {} 0'.format(chan.name, ' '.join(modes))) ''' Checks related to servers ''' # Send out pings pingfreq = 60 valid_servers = iter([ server for server in ircd.servers if server.socket and server.hostname ]) for server in iter([ server for server in valid_servers if time.time() - server.ping > pingfreq and time.time() * 1000 - server.lastPingSent > pingfreq / 2 ]): server.lastPingSent = time.time() * 1000 # server.lag_measure = server.lastPingSent try: server._send(':{} PING {} {}'.format(ircd.sid, ircd.hostname, server.hostname)) except OSError as ex: server.quit('Write error: {}'.format(str(ex))) # Ping timeouts (only for direct links) for server in iter([ server for server in valid_servers if time.time() - server.ping >= 120.0 ]): server.quit('Ping timeout: {} seconds'.format( int(time.time() - server.ping))) # Registration timeouts for server in iter([ server for server in ircd.servers if not server.eos and ( (server.introducedBy and not server.introducedBy.eos) or server.socket) and time.time() - server.ping >= 10.0 ]): is_silent = False if server.socket else True server.quit('Server registration timed out', silent=is_silent) # Check for unknown or timed out servers (non-sockets) for server in iter([ server for server in ircd.servers if not server.socket and server.uplink and server.uplink.socket and time.time() - server.uplink.ping >= 120.0 ]): is_silent = False if server.socket else True server.quit('Server uplink ping timed out: {} seconds'.format( int(time.time() - server.uplink.ping))) for callable in [ callable for callable in ircd.hooks if callable[0].lower() == 'loop' ]: try: callable[2](ircd) except Exception as ex: logging.exception(ex) if not os.path.exists('logs'): os.mkdir('logs') if not os.path.exists('db'): os.mkdir('db')
def validate_server_info(self, client): try: ip, port = client.socket.getpeername() ip2, port2 = client.socket.getsockname() if client.hostname not in self.ircd.conf['link']: error = 'Error connecting to server {}[{}:{}]: no matching link configuration'.format( self.ircd.hostname, ip2, port2) client._send(':{} ERROR :{}'.format(self.ircd.sid, error)) client.quit('no matching link configuration') logging.info( f'Link denied for {client.hostname}: server not found in conf') return 0 client.cls = self.ircd.conf['link'][client.hostname]['class'] logging.info('{}Class: {}{}'.format(G, client.cls, W)) if not client.cls: error = 'Error connecting to server {}[{}:{}]: no matching link configuration'.format( self.ircd.hostname, ip2, port2) client._send(':{} ERROR :{}'.format(self.ircd.sid, error)) client.quit('no matching link configuration') logging.info( f'Link denied for {client.hostname}: unable to assign class to connection' ) return 0 totalClasses = list( filter(lambda s: s.cls == client.cls, self.ircd.servers)) if len(totalClasses) > int(self.ircd.conf['class'][client.cls]['max']): client.quit('Maximum server connections for this class reached') logging.info( f'Link denied for {client.hostname}: max connections for this class' ) return 0 if client.linkpass: if client.linkpass != self.ircd.conf['link'][ client.hostname]['pass']: error = 'Error connecting to server {}[{}:{}]: no matching link configuration'.format( self.ircd.hostname, ip2, port2) client._send(':{} ERROR :{}'.format(self.ircd.sid, error)) client.quit('no matching link configuration') logging.info( f'Link denied for {client.hostname}: incorrect password') return 0 if not match( self.ircd.conf['link'][client.hostname]['incoming']['host'], ip): error = 'Error connecting to server {}[{}:{}]: no matching link configuration'.format( self.ircd.hostname, ip2, port2) client._send(':{} ERROR :{}'.format(self.ircd.sid, error)) client.quit('no matching link configuration') logging.info( f'Link denied for {client.hostname}: incoming IP does not match conf' ) return 0 if client.hostname not in self.ircd.conf['settings']['ulines']: for cap in [cap.split('=')[0] for cap in self.ircd.server_support]: if cap in client.protoctl: logging.info( 'Cap {} is supported by both parties'.format(cap)) else: client._send( ':{} ERROR :Server {} is missing support for {}'. format(client.sid, client.hostname, cap)) client.quit('Server {} is missing support for {}'.format( client.hostname, cap)) logging.info( f'Link denied for {client.hostname}: no matching CAPs') return 0 if client.linkpass and client.linkpass != self.ircd.conf['link'][ client.hostname]['pass']: msg = 'Error connecting to server {}[{}:{}]: no matching link configuration'.format( client.hostname, ip, port) error = 'Error connecting to server {}[{}:{}]: no matching link configuration'.format( self.ircd.hostname, ip2, port2) if client not in self.ircd.linkrequester: client._send('ERROR :{}'.format(error)) elif self.ircd.linkrequester[client]['user']: self.ircd.linkrequester[client]['user'].send( 'NOTICE', '*** {}'.format(msg)) client.quit('no matching link configuration', silent=True) logging.info( f'Link denied for {client.hostname}: incorrect password') return 0 return 1 except Exception as ex: logging.exception(ex) return 0
def processModes(self, ircd, channel, recv, sync=True, sourceServer=None, sourceUser=None): logging.debug('processModes(): {} :: {}'.format(self, recv)) try: if sourceServer != ircd or (type(sourceUser).__name__ == 'User' and sourceUser.server != ircd): hook = 'remote_chanmode' else: hook = 'local_chanmode' rawModes = ' '.join(recv[1:]) if rawModes.startswith(':'): rawModes = rawModes[1:] if type(sourceUser).__name__ == 'User': displaySource = sourceUser.uid else: displaySource = sourceUser.sid # if not sourceServer.eos and sourceServer != ircd: # sync = False except Exception as ex: logging.exception(ex) try: # global modebuf, parambuf, action, prevaction, commandQueue modebuf, parambuf, commandQueue = [], [], [] action = '' prevaction = '' paramcount = -1 chmodes = ircd.chstatus ircd.parammodes = ircd.chstatus for x in range(0, 4): for m in [m for m in ircd.channel_modes[x] if m not in chmodes]: chmodes += m if str(x) in '012' and m not in ircd.parammodes: ircd.parammodes += m global oper_override extban_prefix = None if 'EXTBAN' in ircd.support: extban_prefix = ircd.support['EXTBAN'][0] # logging.info('Extban prefix set: {}'.format(extban_prefix)) # Setting some mode level shit. # +v = 1 # +h = 2 # +o = 3 # +a = 4 # +q = 5 # oper = 6 # server = 7 modeLevel = { # Channel statuses. Users with +h or higher can always remove their own status. # These numbers may seem off, but it's correct. This determines the level requirement to set levels. # For example, you can only give voice if you have 2 (+h) or higher, and only give ops with level 3 (+o) or higher. 'v': 2, 'h': 3, 'o': 3, 'a': 4, 'q': 5 } for t in ircd.channel_modes: for m in ircd.channel_modes[t]: level = ircd.channel_modes[t][m][0] modeLevel[m] = level # for m in [m for m in recv[1] if m in chmodes + '+-' or m in channel.modes]: warn = [] modes = recv[1][1:] if recv[1].startswith(':') else recv[1] for m in modes: if m not in chmodes + '+-' and m not in channel.modes and type( self).__name__ != 'Server': if m not in warn: self.sendraw(ircd.ERR.UNKNOWNMODE, f"{m} :unknown mode bar") warn.append(m) continue param_mode = None if m in ircd.parammodes: if (action == '+') or (action == '-' and m not in list( ircd.channel_modes[2]) + list(ircd.channel_modes[3])): # if (action == '+') or (action == '-' and m in list(ircd.channel_modes[0])+list(ircd.channel_modes[1])): paramcount += 1 if len(recv[2:]) > paramcount: param_mode = recv[2:][paramcount] logging.info('Param for {}{}: {}'.format( action, m, param_mode)) elif m not in ircd.channel_modes[0]: logging.warning( 'Received param mode {}{} without param'.format( action, m)) continue if m in '+-' and action != m: action = m # logging.debug('Action set: {}'.format(action)) if action != prevaction: if modebuf and modebuf[-1] in '-+': modebuf = modebuf[1:] modebuf.append(action) # logging.debug('Modebuf now: {}'.format(modebuf)) prevaction = action continue if not action: action = '+' if m in modeLevel and modeLevel[m] == 6 and ( type(self).__name__ != 'Server' and 'o' not in self.modes): continue if m in modeLevel and modeLevel[m] == 7 and type( self).__name__ != 'Server': continue if m not in '+-' and action != prevaction and ( (m in chmodes or m in ircd.chstatus) or (action in '+-' and m in channel.modes)): modebuf.append(action) prevaction = action if m not in ircd.chstatus and m not in '+-': if m in modeLevel and self.chlevel( channel) < modeLevel[m] and not self.ocheck( 'o', 'override'): continue elif m in modeLevel and self.chlevel( channel) < modeLevel[m] != 6: oper_override = True if m not in ircd.core_chmodes: c = next((x for x in ircd.channel_mode_class if x.mode == m), None) if c: if not c.check(channel, action, param_mode): continue c.modebuf = modebuf c.parambuf = parambuf if (action == '+' and c.set_mode(self, channel, param_mode) ) or (action == '-' and c.remove_mode(self, channel, param_mode)): pass else: # Modules like extbans do not have a mode, so we will check for hooks manually. for callable in [ callable for callable in ircd.hooks if callable[0].lower() == 'pre_' + hook and m in callable[1] ]: try: callable[2](self, ircd, channel, modebuf, parambuf, action, m, param_mode) except Exception as ex: logging.exception(ex) if action == '+' and (m in chmodes or type(self).__name__ == 'Server'): # SETTING CHANNEL MODES if m == 'l' and len(recv) > 2: if not param_mode.isdigit(): continue if int(param_mode) <= 0: continue if m in ircd.chan_params[channel] and int( ircd.chan_params[channel][m]) == int(param_mode): continue else: if m not in channel.modes: channel.modes += m modebuf.append(m) parambuf.append(param_mode) elif m == 'k' and m not in ircd.chan_params[channel]: if m not in channel.modes: channel.modes += m modebuf.append(m) parambuf.append(param_mode) elif m == 'L': param_mode = param_mode.split(',')[0] if param_mode[0] not in ircd.chantypes: continue redirect = None if 'L' not in channel.modes else ircd.chan_params[ channel]['L'] if redirect == param_mode or param_mode.lower( ) == channel.name.lower(): continue chan_exists = [ chan for chan in ircd.channels if chan.name.lower() == param_mode.lower() ] if not chan_exists: self.sendraw( 690, ':Target channel {} does not exist.'.format( param_mode)) continue if self.chlevel(chan_exists[0]) < 3 and not self.ocheck( 'o', 'override'): self.sendraw( 690, ':You must be opped on target channel {} to set it as redirect.' .format(chan_exists[0].name)) continue if 'L' in chan_exists[0].modes: self.sendraw(690, ':Destination channel already has +L.') continue elif self.chlevel(channel) < modeLevel[m]: oper_override = True if m not in channel.modes: channel.modes += m modebuf.append(m) parambuf.append(param_mode) elif m in 'beI': if extban_prefix and param_mode.startswith(extban_prefix): continue mask = make_mask(ircd, param_mode) if m == 'b': data = channel.bans s = 'ban' elif m == 'e': data = channel.excepts s = 'excepts' elif m == 'I': data = channel.invex s = 'invex' if mask not in data: if len(data) >= ircd.maxlist[m] and type( self).__name__ == 'User': self.sendraw( 478, '{} {} :Channel {} list is full'.format( channel.name, mask, s)) continue try: setter = self.fullmask() except: setter = self.hostname modebuf.append(m) parambuf.append(mask) data[mask] = {} data[mask]['setter'] = setter data[mask]['ctime'] = int(time.time()) continue elif m in ircd.chstatus: timed = False # + status temp_user = param_mode try: t = param_mode.split(':') temp_user = t[0] try: channel.temp_status except: channel.temp_status = {} if valid_expire(t[1]): timed = valid_expire(t[1]) except: pass user = list( filter( lambda u: u.uid == temp_user or u.nickname.lower() == temp_user.lower(), channel.users)) if not user: continue else: user = user[0] if m in channel.usermodes[user]: continue if type(self).__name__ != 'Server': if self.chlevel( channel) < modeLevel[m] and not self.ocheck( 'o', 'override'): continue elif self.chlevel(channel) < modeLevel[m]: oper_override = True channel.usermodes[user] += m modebuf.append(m) parambuf.append(user.nickname) if timed: channel.temp_status[user] = {} channel.temp_status[user][m] = {} channel.temp_status[user][m]['ctime'] = int( time.time()) + timed channel.temp_status[user][m]['action'] = '-' if m not in channel.modes and (m in list(ircd.channel_modes[3]) + list(ircd.channel_modes[2])): # If the mode is not handled by modules, do it here. if not next( (x for x in ircd.channel_mode_class if x.mode == m), None): modebuf.append(m) channel.modes += m logging.debug( 'Non-modulair mode "{}" has been handled by m_mode' .format(m)) if m == 'O' and len(channel.users) > 2: for user in [ user for user in channel.users if 'o' not in user.modes ]: cmd = ('KICK', '{} {} :Opers only'.format( channel.name, user.nickname)) commandQueue.append(cmd) elif action == '-' and ((m in chmodes or m in channel.modes) or type(self).__name__ == 'Server'): # REMOVING CHANNEL MODES if m in channel.modes: if m == 'l': # channel.limit = 0 if 'L' in channel.modes: # Also unset -L because we do not need it anymore. channel.modes = channel.modes.replace('L', '') modebuf.append('L') parambuf.append(ircd.chan_params[channel]['L']) elif m == 'k': if param_mode != ircd.chan_params[channel]['k']: continue parambuf.append(ircd.chan_params[channel][m]) elif m == 'L': parambuf.append(ircd.chan_params[channel]['L']) # channel.redirect = None elif m == 'P': if len(channel.users) == 0: ircd.channels.remove(channel) try: with open(ircd.rootdir + '/db/chans.db') as f: current_perm = f.read().split('\n')[0] current_perm = json.loads(current_perm) del current_perm[channel.name] with open(ircd.rootdir + '/db/chans.db', 'w+') as f: json.dump(current_perm, f) except Exception as ex: logging.debug(ex) if m in channel.modes: # Only remove mode if it's a core mode. ChannelMode class handles the rest. if m in ircd.core_chmodes: channel.modes = channel.modes.replace(m, '') modebuf.append(m) elif m in 'beI': mask = make_mask(ircd, param_mode) if m == 'b': data = channel.bans elif m == 'e': data = channel.excepts elif m == 'I': data = channel.invex if mask in data: del data[mask] parambuf.append(mask) modebuf.append(m) elif param_mode in data: del data[param_mode] parambuf.append(param_mode) modebuf.append(m) # continue elif m in ircd.chstatus: timed = False # -qaohv temp_user = param_mode try: t = param_mode.split(':') temp_user = t[0] try: channel.temp_status except: channel.temp_status = {} if valid_expire(t[1]): timed = valid_expire(t[1]) except: pass user = list( filter( lambda u: temp_user and u.uid == temp_user or u. nickname.lower() == temp_user.lower(), channel.users)) if not user: continue else: user = user[0] if m not in channel.usermodes[user]: continue if 'S' in user.modes and user.server.hostname in ircd.conf[ 'settings']['ulines'] and not self.ocheck( 'o', 'override'): self.sendraw( 974, '{} :{} is a protected service bot'.format( m, user.nickname)) continue elif 'S' in user.modes: oper_override = True if type(self).__name__ != 'Server': if self.chlevel( channel) < modeLevel[m] and not self.ocheck( 'o', 'override') and user != self: continue elif self.chlevel(channel) < modeLevel[m]: oper_override = True channel.usermodes[user] = channel.usermodes[user].replace( m, '') modebuf.append(m) parambuf.append(user.nickname) if timed: channel.temp_status[user] = {} channel.temp_status[user][m] = {} channel.temp_status[user][m]['ctime'] = int( time.time()) + timed channel.temp_status[user][m]['action'] = '+' if m in ircd.core_chmodes: # Finally, call modules for core modes. for callable in [ callable for callable in ircd.hooks if callable[0].lower() == 'pre_' + hook and m in callable[1] ]: try: callable[2](self, ircd, channel, modebuf, parambuf, action, m, param_mode) except Exception as ex: logging.exception(ex) continue if not re.sub('[+-]', '', ''.join(modebuf)): return while modebuf[-1] in '+-': modebuf = modebuf[:-1] if channel.name[0] == '&': sync = False modes = ''.join(modebuf) # total_modes, total_params = [], [] if len(modebuf) > 1: total_modes, total_params = [], [] paramcount = 0 action = '' for m in modes: if m in '+-': action = m total_modes.append(m) continue total_modes.append(m) if m in list(ircd.channel_modes[1]) + list( ircd.channel_modes[2]): # If a module handles a channel mode with a param, but for some reason forgets to add it to the chan_params dict, # we will add it here. It is really important that param-modes have their params saved. if action == '+': if m in ircd.core_chmodes: logging.debug( '[core] Storing param of {}: {}'.format( m, parambuf[paramcount])) ircd.chan_params[channel][m] = parambuf[paramcount] elif m not in ircd.chan_params[channel]: logging.debug( '[fallback] Storing param of {}: {}'.format( m, parambuf[paramcount])) ircd.chan_params[channel][m] = parambuf[paramcount] elif action == '-' and m in ircd.chan_params[channel]: logging.debug( '[fallback] Forgetting param of {}: {}'.format( m, ircd.chan_params[channel][m])) del ircd.chan_params[channel][m] for callable in [ callable for callable in ircd.hooks if callable[0].lower() == hook ]: try: callable[2](self, ircd, channel, modes, parambuf) except Exception as ex: logging.exception(ex) if m in ircd.parammodes and ( m not in ircd.channel_modes[2] or action == '+') and len(parambuf) > paramcount: # logging.debug(f"Paramcount: {paramcount}") # logging.debug(f"Parambuf: {action}{m} {parambuf}") total_params.append(parambuf[paramcount]) paramcount += 1 # logging.debug(f"Increased paramcount (now={paramcount}) - moving on") totalLength = len(''.join(total_modes) + ' ' + ' '.join(total_params)) mode_amount = len(re.sub('[+-]', '', ''.join(total_modes))) if mode_amount >= MAXMODES or totalLength >= 400: all_modes = ''.join(total_modes) + ' ' + ' '.join( total_params) if oper_override and type(self).__name__ != 'Server': sourceServer.snotice( 's', '*** OperOverride by {} ({}@{}) with MODE {} {}'. format(sourceUser.nickname, sourceUser.ident, sourceUser.hostname, channel.name, all_modes)) if sync: ircd.new_sync( ircd, sourceServer, ':{} MODE {} :{}'.format( displaySource, channel.name, all_modes if type(self).__name__ == 'User' else rawModes)) sourceUser.broadcast(channel.users, 'MODE {} {}'.format( channel.name, all_modes), source=sourceUser) total_modes, total_params = [action], [] continue if len(total_modes) > 1: all_modes = ''.join(total_modes) + ' ' + ' '.join(total_params) if oper_override and type(self).__name__ != 'Server': sourceServer.snotice( 's', '*** OperOverride by {} ({}@{}) with MODE {} {}'. format(sourceUser.nickname, sourceUser.ident, sourceUser.hostname, channel.name, all_modes)) if sync: ircd.new_sync( ircd, sourceServer, ':{} MODE {} :{}'.format( displaySource, channel.name, all_modes if type(self).__name__ == 'User' else rawModes)) sourceUser.broadcast(channel.users, 'MODE {} {}'.format( channel.name, all_modes), source=sourceUser) for cmd, data in commandQueue: ircd.handle(cmd, data) save_db(ircd) except Exception as ex: logging.exception(ex)
def handle_recv(self): while self.recvbuffer.find("\n") != -1: try: recv = self.recvbuffer[:self.recvbuffer.find("\n")] self.recvbuffer = self.recvbuffer[self.recvbuffer.find("\n") + 1:] recvNoStrip = recv.replace('\r', '').split(' ') recv = recv.split() if not recv: self.recvbuffer = '' continue raw = ' '.join(recv) command = recv[0].lower() prefix = command[:1] localServer = self.localServer try: ignore = ['ping', 'pong', 'privmsg', 'notice'] # ignore = [] if command.lower() not in ignore and recv[1].lower( ) not in ignore: logging.info('{}{} -->>> {}{}'.format( B, self.hostname if self.hostname != '' else self, ' '.join(recvNoStrip), W)) pass except Exception as ex: pass missing_mods = [] if recv[0].upper() == 'MODLIST': try: remote_modules except: remote_modules = [] remote_modules.extend(' '.join(recv[1:])[1:].split()) continue try: if remote_modules: local_modules = [ m.__name__ for m in localServer.modules ] for m in [ m for m in remote_modules if m not in local_modules ]: missing_mods.append(m) if missing_mods: ip, port = self.socket.getpeername() # The error is outgoing and will be displayed on the REMOTE server. error = 'Link denied for {}[{}:{}]: they are missing modules: {}'.format( self.hostname, ip, port, ', '.join(missing_mods)) string = ', '.join(missing_mods) self._send(':{} ERROR :{}'.format( localServer.sid, error)) self.quit('we are missing modules: {}'.format(string)) return except: pass if prefix == '@': # Server only. for s in [ s for s in localServer.servers if s != localServer and s != self and s.socket ]: s._send(raw) source = command[1:] serv = list( filter( lambda s: s.hostname == source or s.sid == source, localServer.servers)) if not serv: continue target = serv[0] token = recv[1] if token == 'AU': # Send PRIVMSG to all users with given usermode. users = list( filter(lambda u: recv[2] in u.modes, localServer.users)) for user in users: target.broadcast([user], 'PRIVMSG {} {}'.format( user.uid, ' '.join(recv[3:]))) elif token == 'Ss': if serv[0] and not serv[0].eos and not serv[ 0].introducedBy.eos: continue # Send NOTICE to all users with given snomask. msg = ' '.join(recv[3:])[1:] localServer.snotice(recv[2], msg, sync=False, source=serv[0]) elif prefix == ':': source = command[1:] command = recv[1] if command == 'BW' or command == 'BV' or command == 'SVSSNO': source = list( filter( lambda u: u.uid == recv[0][1:] or u.nickname == recv[0][1:], localServer.users)) if not source: continue if 's' not in source[0].modes: continue snoset = None for m in recv[2]: if m in '+-': snoset = m continue if snoset == '+' and 'm' not in source[0].snomasks: source[0].snomasks += m elif snoset == '-': source[0].snomasks = source[ 0].snomasks.replace(m, '') if command == 'BW': source[0]._send(':{} MODE +s :{}'.format( source[0].server.hostname, recv[2:])) source[0].sendraw( 8, 'Server notice mask (+{})'.format( source[0].snomasks)) localServer.new_sync(localServer, self, raw) c = next((x for x in localServer.command_class if command.upper() in list(x.command)), None) if c: if c.check(self, recvNoStrip): try: c.execute(self, recvNoStrip) except Exception as ex: logging.exception(ex) for callable in [ callable for callable in localServer.commands if callable[0].lower() == command.lower() ]: try: callable[1](self, localServer, recvNoStrip) except Exception as ex: logging.exception(ex) logging.error( 'Should we disconnect the server because of this issue?' ) continue else: c = next((x for x in localServer.command_class if command.upper() in list(x.command)), None) if c: if c.check(self, recvNoStrip): try: c.execute(self, recvNoStrip) except Exception as ex: logging.exception(ex) except Exception as ex: logging.exception(ex) self.quit(str(ex))
def quit(self, reason, silent=False, error=False, source=None, squit=True): localServer = self.localServer if not hasattr(self, 'socket') or self not in localServer.servers: return logging.info(f'Server QUIT self: {self} :: reason: {reason}') if self in localServer.servers: logging.info('Removing self {}'.format(self)) localServer.servers.remove(self) self.recvbuffer = '' logging.info('Source: {}'.format(source)) if self.uplink: logging.info('Server was uplinked to {}'.format(self.uplink)) reason = reason[1:] if reason.startswith(':') else reason if self in localServer.introducedTo: localServer.introducedTo.remove(self) try: if self.hostname and self.eos and self.netinfo: logging.info( '{}Lost connection to remote server {}: {}{}'.format( R2, self.hostname, reason, W)) if squit: skip = [self] if self.uplink: skip.append(self.uplink) localServer.new_sync( localServer, skip, ':{} SQUIT {} :{}'.format(localServer.sid, self.hostname, reason)) if not silent and self.hostname and self.socket: try: ip, port = self.socket.getpeername() except: ip, port = self.socket.getsockname() t = 0 placeholder = '' if self.eos and self.netinfo: placeholder = "Lost connection to" t = 1 elif self.hostname in localServer.pendingLinks: placeholder = "Unable to connect to" t = 2 elif not self.eos: # and 'link' in localServer.conf and self.hostname in localServer.conf['link']: placeholder = "Link denied for" t = 3 if placeholder: msg = '*** {} server {}[{}:{}]: {}'.format( placeholder, self.hostname, ip, port, reason) localServer.snotice('s', msg, local=True) if self.is_ssl and t == 2: localServer.snotice( 's', '*** Make sure TLS is enabled on both ends and ports are listening for TLS connections.', local=True) if self in localServer.linkrequester: del localServer.linkrequester[self] self.eos = False if self.hostname in localServer.linkRequests: del localServer.linkRequests[self.hostname] if self.hostname in set(localServer.pendingLinks): localServer.pendingLinks.remove(self.hostname) if self in localServer.sync_queue: del localServer.sync_queue[self] # if self.socket and reason and self.sid: # logging.debug(f"Sending ERROR from server quit()") # self._send(':{} ERROR :Closing link: [{}] ({})'.format(self.sid, self.socket.getpeername()[0] if not self.hostname else self.hostname, reason)) while self.sendbuffer: logging.debug('Server {} has sendbuffer remaining: {}'.format( self, self.sendbuffer.rstrip())) try: sent = self.socket.send( bytes(self.sendbuffer + '\n', 'utf-8')) self.sendbuffer = self.sendbuffer[sent:] except: break for user in [ user for user in localServer.users if not user.server ]: user.quit('Unknown connection') additional_servers = [ server for server in localServer.servers if server.introducedBy == self or server.uplink == self ] if additional_servers: logging.info('Also quitting additional servers: {}'.format( additional_servers)) users = [ user for user in localServer.users if user.server and ( user.server == self or user.server in additional_servers) ] for user in users: server1 = self.hostname server2 = source.hostname if source else localServer.hostname user.quit('{} {}'.format(server1, server2), squit=True) for server in additional_servers: logging.info('Quitting server {}'.format(server)) server.quit('{} {}'.format( self.hostname, source.hostname if source else localServer.hostname)) if self.socket: if localServer.use_poll: localServer.pollerObject.unregister(self.socket) try: self.socket.shutdown(socket.SHUT_WR) except: pass self.socket.close() gc.collect() del gc.garbage[:] if not localServer.forked: try: logging.debug( '[SERVER] Growth after self.quit() (if any):') objgraph.show_growth(limit=10) except: pass del self except Exception as ex: logging.exception(ex)
def execute(self, client, recv): if not hasattr(client, 'protoctl'): client.protoctl = [] try: for p in [p for p in recv[2:] if p not in client.protoctl]: try: cap = p.split('=')[0] param = None client.protoctl.append(cap) if '=' in p: param = p.split('=')[1] if cap == 'EAUTH' and param: client.hostname = param.split(',')[0] logging.info('Hostname set from EAUTH: {}'.format( client.hostname)) if [ s for s in self.ircd.servers + [self.ircd] if s.hostname.lower() == client.hostname.lower() and s != client ]: ip, port = client.socket.getpeername() error = 'Error connecting to server {}[{}:{}]: server already exists on remote network'.format( self.ircd.hostname, ip, port) client._send(':{} ERROR :{}'.format( self.ircd.sid, error)) client.quit( 'server already exists on this network') return elif cap == 'SID' and param: for server in [ server for server in self.ircd.servers if server.sid == param and server != client ]: client._send( ':{} ERROR :SID {} is already in use on that network' .format(self.ircd.sid, param)) client.quit( 'SID {} is already in use on that network'. format(param)) return client.sid = param elif cap == 'CHANMODES': remote_modes = param.split(',') local_modes = self.ircd.chmodes_string.split(',') missing_modes = [] for n in self.ircd.channel_modes: for m in [ m for m in remote_modes[n] if m not in local_modes[n] ]: missing_modes.append(m) if missing_modes: # The error is outgoing and will be displayed on the REMOTE server. ip, port = client.socket.getpeername() error = 'Link denied for {}[{}:{}]: they are missing channel modes: {}'.format( client.hostname, ip, port, ', '.join(missing_modes)) client._send(':{} ERROR :{}'.format( self.ircd.sid, error)) client.quit( 'we are missing channel modes: {}'.format( ', '.join(missing_modes))) return elif cap == 'EXTBAN': remote_prefix = param[0] remote_ban_types = param.split(',')[1] local_prefix = None if 'EXTBAN' in self.ircd.support: local_prefix = self.ircd.support['EXTBAN'][0] if remote_prefix != local_prefix: ip, port = client.socket.getpeername() error = 'Link denied for {}[{}:{}]: extban prefixes are not the same. We have: {} but they have: {}'.format( client.hostname, ip, port, remote_prefix, local_prefix) client._send( ':{} ERROR :extban prefixes are not the same. We have: {} but they have: {}' .format(self.ircd.sid, remote_prefix, local_prefix)) client.quit( 'extban prefixes are not the same. We have: {} but they have: {}' .format(local_prefix, remote_prefix)) return local_ban_types = self.ircd.support['EXTBAN'][1:] logging.info( 'We have bantypes: {}'.format(local_ban_types)) missing_ext_types = [] for m in [ m for m in remote_ban_types if m not in local_ban_types ]: missing_ext_types.append(m) if missing_ext_types: error = 'Link denied for {}[{}:{}]: they are missing ext bans: {}'.format( client.hostname, ip, port, ', '.join(missing_ext_types)) client._send(':{} ERROR :{}'.format( self.ircd.sid, error)) client.quit('we are missing ext bans: {}'.format( ', '.join(missing_ext_types))) return except Exception as ex: logging.exception(ex) client.quit(str(ex)) logging.info( '{}Added PROTOCTL support for {} for server {}{}'.format( P, p, client, W)) except Exception as ex: logging.exception(ex)
def whitelist_mode(self, localServer, channel, modebuf, parambuf, action, modebar, param): try: if (action == '+' or not action) and not param: # Requesting list. if self.chlevel(channel) < 3 and 'o' not in self.modes: return self.sendraw( 482, '{} :You are not allowed to view the whitelist'.format( channel.name)) for entry in OrderedDict(reversed(list( channel.whitelist.items()))): self.sendraw( 348, '{} {} {} {}'.format(channel.name, entry, channel.whitelist[entry]['setter'], channel.whitelist[entry]['ctime'])) self.sendraw(349, '{} :End of Channel Whitelist'.format(channel.name)) return 0 elif not param: return valid = re.findall("^([1-9][0-9]{0,3}):(.*)", param) if not valid: logging.info('Invalid param for {}{}: {}'.format( action, modebar, param)) return 0 mask = make_mask(localServer, param.split(':')[1]) logging.info('Param for {}{} set: {}'.format(action, modebar, param)) logging.info('Mask: {}'.format(mask)) raw_param = param param = '{}:{}'.format(':'.join(param.split(':')[:1]), mask) if action == '+': if param in channel.whitelist: return 0 try: setter = self.fullmask() except Exception as ex: setter = self.hostname channel.whitelist[param] = {} channel.whitelist[param]['setter'] = setter channel.whitelist[param]['ctime'] = int(time.time()) modebuf.append(modebar) parambuf.append(param) elif action == '-' and (param in channel.whitelist or raw_param in channel.whitelist): if param in channel.whitelist: del channel.whitelist[param] parambuf.append(param) else: del channel.whitelist[raw_param] parambuf.append(raw_param) modebuf.append(modebar) return 0 except Exception as ex: logging.exception(ex)