def join(self, ircd, channel): mod = next((m for m in ircd.channel_mode_class if m.mode == chmode), None) if not mod: logging.error(f"Module for channele mode '{chmode}' not found.") return if not hasattr(channel, mod.list_name): setattr(channel, mod.list_name, {}) last_level = 0 total_modes, total_params = '+', '' for entry in channel.whitelist: level = int(entry.split(':')[0]) if level > last_level: # Found higher level. last_level = level mask = entry.split(':')[1] modes = '' if match(mask, self.fullmask()): if level >= 9999: modes += 'oq' elif level >= 10: modes += 'oa' elif level >= 5: modes += 'o' elif level >= 4: modes += 'h' elif level >= 1: modes += 'v' if modes: nicks = '{} '.format(self.nickname) * len(modes) total_modes += modes total_params += nicks if total_modes and total_params: ircd.handle('MODE', '{} {} {}'.format(channel.name, total_modes, total_params))
def execute(self, client, recv): if type(client).__name__ == 'Server': source = [s for s in self.ircd.servers if s.sid == recv[0][1:]] if not source: logging.error('{}ERROR: source for SID {} could not be found. Was it already removed?{}'.format(R, recv[0][1:], W)) source = client else: source = source[0] server = list(filter(lambda s: s.sid.lower() == recv[2].lower() or s.hostname.lower() == recv[2].lower(), self.ircd.servers)) if not server: logging.error('{}ERROR: server for {} could not be found. Was it already removed?{}'.format(R, recv[2], W)) return server = server[0] self.ircd.new_sync(self.ircd, client, ' '.join(recv)) server.quit(' '.join(recv[3:]), source=source, squit=False) return reason = '[{}] {}'.format(client.nickname, ' '.join(recv[2:])) name = recv[1] server = list(filter(lambda s: s.hostname.lower() == name.lower(), self.ircd.servers)) if server: server = server[0] if not [server for server in self.ircd.servers if server.hostname == name]: self.ircd.notice(client, '*** Currently not connected to {}'.format(name)) return msg = '*** {} ({}@{}) used SQUIT command for {}: {}'.format(client.nickname, client.ident, client.hostname, server.hostname, reason) self.ircd.snotice('s', msg) server.quit(reason)
def init(ircd, reload=False): mod = next((m for m in ircd.channel_mode_class if m.mode == chmode), None) if not mod: logging.error(f"Module for channele mode '{chmode}' not found.") return for chan in [ chan for chan in ircd.channels if not hasattr(chan, mod.list_name) ]: setattr(chan, mod.list_name, {})
def validate(self): mode_class_list = None if issubclass(self.__class__, UserMode): mode_class_list = self.ircd.user_mode_class elif issubclass(self.__class__, ChannelMode): mode_class_list = self.ircd.channel_mode_class # Index 0 beI, index 1 kLf, index 2 l, index 3 imnjprstzCNOQRTV if str(self.type) == '0': if not hasattr(self, 'list_name') or not self.list_name or not self.list_name.isalpha(): error = 'Invalid list mode in {}: missing or invalid "list_name"'.format(self) logging.error(error) return error if not hasattr(self, 'mode_prefix') or not self.mode_prefix: error = 'Invalid list mode in {}: missing "mode_prefix"'.format(self) logging.error(error) return error if self.mode_prefix.isalpha() or self.mode_prefix.isdigit() or len(self.mode_prefix) > 1: error = 'Invalid list mode in {}: invalid "mode_prefix", must be a special char'.format(self) logging.error(error) return error if self.mode_prefix in ":&\"'*~@%+#": error = 'Invalid list mode in {}: invalid "mode_prefix", reserved for core'.format(self) logging.error(error) return error if [m for m in self.ircd.modules if hasattr(m, 'mode_prefix') and self.mode_prefix == m.mode_prefix]: error = 'Invalid list mode in {}: invalid "mode_prefix", already in use'.format(self) logging.error(error) return error # Remove duplicate instances of the same class. for c in mode_class_list: if type(c).__name__ == type(self).__name__ and c != self: c.unload() if not self.mode: self.error("Missing or invalid modebar") if self.mode in mode_class_list: self.error(f"Mode '{self.mode}' is already in use.") if not self.desc: self.error(f"Mode '{self.mode}' is missing a description.") if self.support: if self.support[0] in self.ircd.support: self.error("Support is conflicting with another module") if type(self.support) != list: self.error("Invalid SUPPORT type: must be a list containing one or more tuples") for s in [s for s in self.support if type(s) != tuple]: self.error("Invalid SUPPORT entry: {} (must be a tuple)".format(s))
def give_mode(self, user): if self.mode in user.modes: logging.error(f'Usermode "{self.mode}" is already active on user {user.nickname}') return 0 if self.req_flag == 1 and 'o' not in user.modes: logging.error(f'User {user} is not allowed to set this mode: {self.req_flag}') return 0 user.modes += self.mode self.modebuf.append(self.mode) logging.debug('Usermode of {} is now: {} (+{})'.format(user.nickname, user.modes, self.mode)) return 1
def hide_kick(self, ircd, user, channel, reason): if chmode in channel.modes: global can_see if channel not in can_see: logging.error( '/KICK: CRITICAL ERROR: channel {} is not found in the can_see dict!' .format(channel.name)) return can_see[channel][user] = [] for u in [ u for u in channel.users if u in can_see[channel] and user in can_see[channel][u] ]: logging.debug('/kick: User {} can not see {} anymore.'.format( u.nickname, user.nickname)) can_see[channel][u].remove(user)
def hidepart(self, localServer, channel): if chmode in channel.modes: global can_see if channel not in can_see: logging.error( '/PART: CRITICAL ERROR: channel {} is not found in the can_see dict!' .format(channel.name)) return can_see[channel][self] = [] for user in [ user for user in channel.users if user in can_see[channel] and self in can_see[channel][user] and user.chlevel(channel) < 2 ]: logging.debug('/part: User {} can not see {} anymore.'.format( user.nickname, self.nickname)) can_see[channel][user].remove(self)
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 setinfo(self, info, t='', source=None): try: if not info or not type: return if not source: return logging.error('No source provided in setinfo()!') if type(source) == str or type(source).__name__ != 'Server': return logging.error( 'Wrong source type provided in setinfo(): {}'.format( source)) if t not in ['host', 'ident']: return logging.error( 'Incorrect type received in setinfo(): {}'.format(t)) valid = 'abcdefghijklmnopqrstuvwxyz0123456789.-' for c in str(info): if c.lower() not in valid: info = info.replace(c, '') if not info: return updated = [] if self.registered: for user in iter([ user for user in self.ircd.users if 'chghost' in user.caplist and user not in updated and user.socket ]): common_chan = list( filter(lambda c: user in c.users and self in c.users, self.ircd.channels)) if not common_chan: continue user._send(':{} CHGHOST {} {}'.format( self.fullmask(), info if t == 'ident' else self.ident, info if t == 'host' else self.cloakhost)) updated.append(user) data = ':{} {} {}'.format( self.uid, 'SETHOST' if t == 'host' else 'SETIDENT', info) self.ircd.new_sync(self.ircd, source, data) if t == 'host': self.cloakhost = info elif t == 'ident': self.ident = info except Exception as ex: logging.exception(ex)
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 new_sync(self, ircd, skip, data, direct=None): try: if type(skip) != list: skip = [skip] for t in [t for t in skip if type(t).__name__ != 'Server']: logging.error( '{}HALT: wrong source type in new_sync(): {} with data: {}{}' .format(R2, t, data, W)) return if data.split()[1] in ['UID', 'SID']: data = data.split() data = '{} {} {}'.format(' '.join(data[:3]), str(int(data[3]) + 1), ' '.join(data[4:])) if direct: # Private messages and notices. direct represents the target.server dest = direct if direct.socket else direct.uplink # if direct.socket: # logging.debug('Directly linked to us, no more hops needed.') if not direct.socket: logging.debug( 'Server has hopcount of {d.hopcount}, sending to {d.uplink} first.' .format(d=direct)) dest._send(data) return for server in [ server for server in ircd.servers if server and server.socket and server not in skip ]: if not server.eos: if server not in ircd.sync_queue: ircd.sync_queue[server] = [] ircd.sync_queue[server].append(data) logging.debug( '{}Added to {} sync queue because they are not done syncing: {}{}' .format(R2, server, data, W)) continue server._send(data) except Exception as ex: logging.exception(ex)
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 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 chmode_D2(self, ircd, channel, modebuf, parambuf, action, m, param): global can_see if m == chmode: if 'u' in channel.modes: ircd.notice(self, 'Mode +D cannot be set: channel has +u') return 0 if not hasattr(channel, 'delayjoins') or not channel.delayjoins: channel.delayjoins = [] if action == '-': # Show user joins to whoever needs them. for user in channel.users: for user2 in [ user2 for user2 in channel.users if user2 not in can_see[channel][user] and user != user2 ]: logging.debug( '/MODE unset: Showing join from {} to {}'.format( user2.nickname, user.nickname)) data = ':{}!{}@{} JOIN {}{}'.format( user2.nickname, user2.ident, user2.cloakhost, channel.name, ' {} :{}'.format(user2.svid, user2.realname) if 'extended-join' in user.caplist else '') user._send(data) can_see[channel][user].append(user2) return if m not in ircd.chstatus: return user = [ user for user in channel.users if user.nickname == param or user.uid == param ] if not user: return logging.error('No class found for {}{} param {}'.format( action, m, param)) user = user[0] if action == '+' or action == '-': # <user> should be visible to all users on <channel> logging.debug('/MODE: current state for {} {}: {}'.format( channel, user, can_see[channel][user])) for u in channel.users: # Send JOIN to <u> if not already known. if user == u: continue if user not in can_see[channel][u]: logging.debug('/MODE: Show join {} from {} to {}'.format( channel.name, user.nickname, u.nickname)) data = ':{}!{}@{} JOIN {}{}'.format( user.nickname, user.ident, user.cloakhost, channel.name, ' {} :{}'.format(user.svid, user.realname) if 'extended-join' in u.caplist else '') u._send(data) can_see[channel][u].append(user) # Can <user> see <u> too? if user.chlevel(channel) > 2 and u not in can_see[channel][user]: # Yes. logging.debug('/MODE2: Show join {} from {} to {}'.format( channel.name, u.nickname, user.nickname)) data = ':{}!{}@{} JOIN {}{}'.format( u.nickname, u.ident, u.cloakhost, channel.name, ' {} :{}'.format(u.svid, u.realname) if 'extended-join' in user.caplist else '') user._send(data) can_see[channel][user].append(u) if action == '-': # Not tested. for u in [u for u in channel.users if u != user]: logging.debug('User {} got status removed. Hiding {}?'.format( user.nickname, u.nickname)) if user.chlevel(channel) < 3 and u in can_see[channel][user]: # Hide <u> from <user> u.broadcast([user], 'PART :{}'.format(channel.name)) can_see[channel][user].remove(u)
def destroy(self, ircd, channel): mod = next((m for m in ircd.channel_mode_class if m.mode == chmode), None) if not mod: logging.error(f"Module for channele mode '{chmode}' not found.") return setattr(channel, mod.list_name, {})
def execute(self, client, recv): sid = recv[0][1:] # source = list(filter(lambda s: s.sid == recv[0][1:], self.ircd.servers))[0] source = [s for s in self.ircd.servers if s.sid == sid] if not source: logging.error( 'Received NETINFO from unknown server with SID: {}'.format( sid)) logging.error('Uplink: {}'.format(client)) logging.error('Data: {}'.format(recv)) return source = source[0] maxglobal = int(recv[2]) remotetime = int(recv[3]) version = recv[4] cloakhash = recv[5] creation = int(recv[6]) remotename = recv[9][1:] if maxglobal > self.ircd.maxgusers: self.ircd.maxgusers = maxglobal currenttime = int(time.time()) if not source.socket: source.netinfo = True # self.ircd.replyPing[source] = True # print('Server {} will now reply to PING requests from {} (NETINFO)'.format(self.ircd.hostname,source.hostname)) return remotehost = source.hostname if abs(remotetime - currenttime) > 10: if abs(remotetime - currenttime) > 120: err = 'ERROR :Link denied due to incorrect clocks. Please make sure both clocks are synced!' client._send(err) client.quit(err) return if remotetime > currenttime: self.ircd.snotice( 's', '*** (warning) Remote server {}\'s clock is ~{}s ahead on ours, this can cause issues and should be fixed!' .format(remotehost, abs(remotetime - currenttime)), local=True) elif remotetime < currenttime: self.ircd.snotice( 's', '*** (warning) Remote server {}\'s clock is ~{}s behind on ours, this can cause issues and should be fixed!' .format(remotehost, abs(remotetime - currenttime)), local=True) if remotename != self.ircd.hostname and source.hostname == remotename: self.ircd.snotice( 's', '*** Network name mismatch from {} ({} != {})'.format( source.hostname, remotename, self.ircd.hostname), local=True) if version != self.ircd.versionnumber.replace( '.', '') and remotehost not in self.ircd.conf['settings'][ 'ulines'] and source.name == remotename: self.ircd.snotice( 's', '*** Remote server {} is using version {}, and we are using version {}, but this should not cause issues.' .format(remotehost, version, self.ircd.versionnumber.replace('.', '')), local=True) if cloakhash.split(':')[1] != hashlib.md5( self.ircd.conf['settings']['cloak-key'].encode( 'utf-8')).hexdigest(): self.ircd.snotice( 's', '*** (warning) Network wide cloak keys are not the same! This will affect channel bans and must be fixed!', local=True) if creation: source.creationtime = creation if not source.netinfo: source.netinfo = True if source not in self.ircd.linkrequester: ip, port = source.socket.getpeername() try: port = self.ircd.conf['link'][ source.hostname]['outgoing']['port'] except: pass else: # When you requested the link. ip, port = source.socket.getpeername() del self.ircd.linkrequester[source] string = 'Secure ' if source.is_ssl else '' msg = '*** (link) {}Link {} -> {}[@{}.{}] successfully established'.format( string, self.ircd.hostname, source.hostname, ip, port) self.ircd.snotice('s', msg, local=True) self.ircd.new_sync(self.ircd, client, ' '.join(recv))
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 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 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 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 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 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)