def getFilesStreams(self, mtimes, oldmtimes, oldstreams, newsharedfiles, rebuild=False, yieldcall=None): streams = {} shared = self.config.sections["transfers"]["shared"] # noqa: F841 for directory in list(mtimes.keys()): virtualdir = self.real2virtual(directory) if self.hiddenCheck({'dir': directory}): continue if not rebuild and directory in oldmtimes: if mtimes[directory] == oldmtimes[directory]: if os.path.exists(directory): # No change try: streams[virtualdir] = oldstreams[virtualdir] continue except KeyError: log.addwarning(_("Inconsistent cache for '%(vdir)s', rebuilding '%(dir)s'") % { 'vdir': virtualdir, 'dir': directory }) else: log.adddebug(_("Dropping missing directory %(dir)s") % {'dir': directory}) continue streams[virtualdir] = self.getDirStream(newsharedfiles[virtualdir]) if yieldcall is not None: yieldcall() return streams
def ok_to_respond(self, room, nick, request, seconds_limit_min=30): self.room = room self.nick = nick self.request = request willing_to_respond = True current_time = time() if room not in list(self.plugin_usage.keys()): self.plugin_usage[room] = { 'last_time': 0, 'last_request': "", 'last_nick': "" } last_time = self.plugin_usage[room]['last_time'] last_nick = self.plugin_usage[room]['last_nick'] last_request = self.plugin_usage[room]['last_request'] port = False try: ip, port = self.frame.np.users[nick].addr except Exception: port = True if nick in self.frame.np.config.sections["server"]["ignorelist"]: willing_to_respond, reason = False, "The nick is ignored" elif self.frame.UserIpIsIgnored(nick): willing_to_respond, reason = False, "The nick's Ip is ignored" elif not port: willing_to_respond, reason = False, "Request likely from simple PHP based griefer bot" elif [nick, request] == [last_nick, last_request]: if (current_time - last_time) < 12 * seconds_limit_min: willing_to_respond, reason = False, "Too soon for same nick to request same resource in room" elif (request == last_request): if (current_time - last_time) < 3 * seconds_limit_min: willing_to_respond, reason = False, "Too soon for different nick to request same resource in room" else: recent_responses = 0 for responded_room in self.plugin_usage: if (current_time - self.plugin_usage[responded_room]['last_time'] ) < seconds_limit_min: recent_responses += 1 if responded_room == room: willing_to_respond, reason = False, "Responded in specified room too recently" break if recent_responses > 3: willing_to_respond, reason = False, "Responded in multiple rooms enough" if self.logging: if not willing_to_respond: base_log_msg = "{} plugin request rejected - room '{}', nick '{}'".format( self.plugin_name, room, nick) log.adddebug("{} - {}".format(base_log_msg, reason)) return willing_to_respond
def getFilesStreamsUnicode(self, mtimes, oldmtimes, oldstreams, newsharedfiles, rebuild=False, yieldcall=None): streams = {} shared = self.config.sections["transfers"]["shared"] # noqa: F841 for directory in list(mtimes.keys()): virtualdir = self.real2virtual(directory) # force Unicode for reading from disk u_directory = "%s" % directory str_directory = str(directory) # noqa: F841 if self.hiddenCheck({'dir': directory}): continue if directory in oldmtimes and directory not in oldstreams: # Partial information, happened with unicode paths that N+ couldn't handle properly del oldmtimes[directory] if not rebuild and directory in oldmtimes: if mtimes[directory] == oldmtimes[directory]: if os.path.exists(u_directory): # No change try: streams[virtualdir] = oldstreams[virtualdir] continue except KeyError: log.addwarning( _("Inconsistent cache for '%(vdir)s', rebuilding '%(dir)s'" ) % { 'vdir': virtualdir, 'dir': directory }) else: log.adddebug( _("Dropping missing directory %(dir)s") % {'dir': directory}) continue streams[virtualdir] = self.getDirStream(newsharedfiles[virtualdir]) if yieldcall is not None: yieldcall() return streams
def FindSuitableExternalWANPort(self): """Function to find a suitable external WAN port to map to the client. It will detect if a port mapping to the client already exist. """ # Output format: (ePort, protocol, (intClient, iPort), desc, enabled, # rHost, duration) log.adddebug( 'Existing Port Mappings: %s' % (sorted(self.existingportsmappings, key=lambda tup: tup[0]))) # Analyze ports mappings for m in sorted(self.existingportsmappings, key=lambda tup: tup[0]): (ePort, protocol, (intClient, iPort), desc, enabled, rhost, duration) = m # A Port Mapping is already in place with the client: we will # rewrite it to avoid a timeout on the duration of the mapping if protocol == "TCP" and \ str(intClient) == str(self.internalipaddress) and \ iPort == self.internallanport: log.adddebug('Port Mapping already in place: %s' % str(m)) self.externalwanport = ePort self.foundexistingmapping = True break # If no mapping already in place we try to found a suitable external # WAN port if not self.foundexistingmapping: # Find the first external WAN port > requestedwanport that's not # already reserved tcpportsreserved = [ x[0] for x in sorted(self.existingportsmappings) if x[1] == "TCP" ] while self.externalwanport in tcpportsreserved: if self.externalwanport + 1 <= 65535: self.externalwanport += 1 else: raise AssertionError( _('Failed to find a suitable external WAN port, ' + 'bailing out.'))
def getFilesStreams(self, mtimes, oldmtimes, oldstreams, newsharedfiles, rebuild=False, yieldcall=None): streams = {} for folder in mtimes: virtualdir = self.real2virtual(folder) if self.hiddenCheck(folder): continue if not rebuild and folder in oldmtimes: if mtimes[folder] == oldmtimes[folder]: if os.path.exists(folder): # No change try: streams[virtualdir] = oldstreams[virtualdir] continue except KeyError: log.adddebug( _("Inconsistent cache for '%(vdir)s', rebuilding '%(dir)s'" ) % { 'vdir': virtualdir, 'dir': folder }) else: log.adddebug( _("Dropping missing folder %(dir)s") % {'dir': folder}) continue streams[virtualdir] = self.getDirStream(newsharedfiles[virtualdir]) if yieldcall is not None: yieldcall() return streams
def get_files_streams(self, mtimes, oldmtimes, oldstreams, newsharedfiles, rebuild=False): """ Get streams of files """ streams = {} for folder in mtimes: virtualdir = self.real2virtual(folder) if not rebuild and folder in oldmtimes: if mtimes[folder] == oldmtimes[folder]: if os.path.exists(folder): # No change try: streams[virtualdir] = oldstreams[virtualdir] continue except KeyError: log.adddebug( _("Inconsistent cache for '%(vdir)s', rebuilding '%(dir)s'" ) % { 'vdir': virtualdir, 'dir': folder }) else: log.adddebug( _("Dropping missing folder %(dir)s") % {'dir': folder}) continue streams[virtualdir] = self.get_dir_stream( newsharedfiles[virtualdir]) return streams
def getFilesList(self, mtimes, oldmtimes, oldlist, yieldcall=None, progress=None, rebuild=False): """ Get a list of files with their filelength, bitrate and track length in seconds """ list = {} count = 0 lastpercent = 0.0 for folder in mtimes: try: count += 1 if progress: # Truncate the percentage to two decimal places to avoid sending data to the GUI thread too often percent = float("%.2f" % (float(count) / len(mtimes) * 0.75)) if percent > lastpercent and percent <= 1.0: GLib.idle_add(progress.set_fraction, percent) lastpercent = percent if self.hiddenCheck(folder): continue if not rebuild and folder in oldmtimes: if mtimes[folder] == oldmtimes[folder]: if os.path.exists(folder): try: virtualdir = self.real2virtual(folder) list[virtualdir] = oldlist[virtualdir] continue except KeyError: log.adddebug( _("Inconsistent cache for '%(vdir)s', rebuilding '%(dir)s'" ) % { 'vdir': virtualdir, 'dir': folder }) else: log.adddebug( _("Dropping missing folder %(dir)s") % {'dir': folder}) continue virtualdir = self.real2virtual(folder) list[virtualdir] = [] for entry in os.scandir(folder): if entry.is_file(): filename = entry.name if self.hiddenCheck(folder, filename): continue # Get the metadata of the file data = self.getFileInfo(filename, entry.path) if data is not None: list[virtualdir].append(data) if yieldcall is not None: yieldcall() except OSError as errtuple: message = _( "Error while scanning folder %(path)s: %(error)s") % { 'path': folder, 'error': errtuple } print(str(message)) self.logMessage(message) continue return list
def getFilesListUnicode(self, mtimes, oldmtimes, oldlist, yieldcall=None, progress=None, rebuild=False): """ Get a list of files with their filelength, bitrate and track length in seconds """ list = {} count = 0 for directory in mtimes: directory = os.path.expanduser(directory) virtualdir = self.real2virtual(directory) count += 1 if progress: percent = float(count) / len(mtimes) if percent <= 1.0: gobject.idle_add(progress.set_fraction, percent) # force Unicode for reading from disk u_directory = "%s" % directory str_directory = str(directory) # noqa: F841 if self.hiddenCheck({'dir': directory}): continue if directory in oldmtimes and directory not in oldlist: # Partial information, happened with unicode paths that N+ couldn't handle properly del oldmtimes[directory] if not rebuild and directory in oldmtimes: if mtimes[directory] == oldmtimes[directory]: if os.path.exists(directory): try: list[virtualdir] = oldlist[virtualdir] continue except KeyError: log.addwarning( _("Inconsistent cache for '%(vdir)s', rebuilding '%(dir)s'" ) % { 'vdir': virtualdir, 'dir': directory }) else: log.adddebug( _("Dropping missing directory %(dir)s") % {'dir': directory}) continue list[virtualdir] = [] try: contents = os.listdir(u_directory) except OSError as errtuple: print(str(errtuple)) self.logMessage(str(errtuple)) continue contents.sort() for filename in contents: if self.hiddenCheck({'dir': directory, 'file': filename}): continue path = os.path.join(directory, filename) s_path = str(path) ppath = str(path) s_filename = str(filename) try: # try to force Unicode for reading from disk isfile = os.path.isfile(ppath) except OSError as errtuple: message = _("Scanning Error: %(error)s Path: %(path)s") % { 'error': errtuple, 'path': path } print(str(message)) self.logMessage(message) displayTraceback(sys.exc_info()[2]) continue else: if isfile: # Get the metadata of the file via mutagen data = self.getFileInfoUnicode(s_filename, s_path) if data is not None: list[virtualdir].append(data) if yieldcall is not None: yieldcall() return list
def AddPortMappingModule(self): """Function to create a Port Mapping via the python binding: miniupnpc. IGDv1: If a Port Mapping already exist: It's updated with a new static port mapping that does not expire. IGDv2: If a Port Mapping already exist: It's updated with a new lease duration of 7 days. """ import miniupnpc u = miniupnpc.UPnP() u.discoverdelay = self.discoverdelay # Discovering devices log.adddebug('Discovering... delay=%sms' % u.discoverdelay) try: log.adddebug('%s device(s) detected' % u.discover()) except Exception as e: raise RuntimeError( _('UPnP exception (should never happen): %(error)s') % {'error': str(e)}) # Select an IGD try: u.selectigd() except Exception as e: raise RuntimeError( _('Cannot select an IGD : %(error)s') % {'error': str(e)}) self.externalipaddress = u.externalipaddress() log.adddebug('IGD selected : External IP address: %s' % (self.externalipaddress)) # Build existing ports mappings list log.adddebug('Listing existing Ports Mappings...') i = 0 while True: p = u.getgenericportmapping(i) if p is None: break self.existingportsmappings.append(p) i += 1 # Find a suitable external WAN port to map to based on the existing # mappings self.FindSuitableExternalWANPort() # Do the port mapping log.adddebug('Trying to redirect %s port %s TCP => %s port %s TCP' % ( self.externalipaddress, self.externalwanport, self.internalipaddress, self.internallanport ) ) try: u.addportmapping(self.externalwanport, 'TCP', self.internalipaddress, self.internallanport, 'Nicotine+', '') except Exception as e: log.adddebug('Failed') raise RuntimeError( _('Failed to map the external WAN port: %(error)s') % {'error': str(e)} ) log.adddebug('Success')
def AddPortMappingBinary(self): """Function to create a Port Mapping via MiniUPnPc binary: upnpc. It tries to reconstruct a datastructure identical to what the python module does by parsing the output of the binary. This help to have a bunch of common code to find a suitable external WAN port later. IGDv1: If a Port Mapping already exist: It's updated with a new static port mapping that does not expire. IGDv2: If a Port Mapping already exist: It's updated with a new lease duration of 7 days. """ # Listing existing ports mappings log.adddebug('Listing existing Ports Mappings...') command = [self.upnpcbinary, '-l'] try: output = self.run_binary(command) except Exception as e: raise RuntimeError( _('Failed to use UPnPc binary: %(error)s') % {'error': str(e)}) # Build a list of tuples of the mappings # with the same format as in the python module # (ePort, protocol, (intClient, iPort), desc, enabled, rHost, duration) # (15000, 'TCP', ('192.168.0.1', 2234), 'Nicotine+', '1', '', 0) # # Also get the external WAN IP # # Output format : # ... # ExternalIPAddress = X.X.X.X # ... # i protocol exPort->inAddr:inPort description remoteHost leaseTime # 0 TCP 15000->192.168.0.1:2234 'Nicotine+' '' 0 re_ip = re.compile(r""" ^ ExternalIPAddress \s+ = \s+ (?P<ip> \d+ \. \d+ \. \d+ \. \d+ )? $ """, re.VERBOSE) re_mapping = re.compile(r""" ^ \d+ \s+ (?P<protocol> \w+ ) \s+ (?P<ePort> \d+ ) -> (?P<intClient> \d+ \. \d+ \. \d+ \. \d+ ) : (?P<iPort> \d+ ) \s+ ' (?P<desc> .* ) ' \s+ ' (?P<rHost> .* ) ' \s+ (?P<duration> \d+ ) $ """, re.VERBOSE) for line in output.split('\n'): line = line.strip() ip_match = re.match(re_ip, line) mapping_match = re.match(re_mapping, line) if ip_match: self.externalipaddress = ip_match.group('ip') next if mapping_match: enabled = '1' self.existingportsmappings.append( ( int(mapping_match.group('ePort')), mapping_match.group('protocol'), (mapping_match.group('intClient'), int(mapping_match.group('iPort'))), mapping_match.group('desc'), enabled, mapping_match.group('rHost'), int(mapping_match.group('duration')) ) ) # Find a suitable external WAN port to map to based # on the existing mappings self.FindSuitableExternalWANPort() # Do the port mapping log.adddebug('Trying to redirect %s port %s TCP => %s port %s TCP' % ( self.externalipaddress, self.externalwanport, self.internalipaddress, self.internallanport ) ) command = [ self.upnpcbinary, '-e', '"Nicotine+"', '-a', str(self.internalipaddress), str(self.internallanport), str(self.externalwanport), 'TCP' ] try: output = self.run_binary(command) except Exception as e: raise RuntimeError( _('Failed to use UPnPc binary: %(error)s') % {'error': str(e)}) for line in output.split('\n'): if line.startswith("external ") and \ line.find(" is redirected to internal ") > -1: log.adddebug('Success') return if line.find(" failed with code ") > -1: log.adddebug('Failed') raise RuntimeError( _('Failed to map the external WAN port: %(error)s') % {'error': str(line)}) raise AssertionError( _('UPnPc binary failed, could not parse output: %(output)s') % {'output': str(output)})
def AddPortMapping(self, frame, np): """Wrapper to redirect the Port Mapping creation to either: - The MiniUPnPc binary: upnpc. - The python binding to the MiniUPnPc binary: miniupnpc. Both method support creating a Port Mapping via the UPnP IGDv1 and IGDv2 protocol. Need a reference to NicotineFrame to update the interface with the WAN external port chosen and connect to the slsk network. Also need a reference to the np object to extract the internal LAN local from the protothread socket. From the UPnP IGD reference: http://upnp.org/specs/gw/UPnP-gw-WANIPConnection-v2-Service.pdf IGDv1 and IGDV2: AddPortMapping: This action creates a new port mapping or overwrites an existing mapping with the same internal client. If the ExternalPort and PortMappingProtocol pair is already mapped to another internal client, an error is returned. IGDv1: NewLeaseDuration: This argument defines the duration of the port mapping. If the value of this argument is 0, it means it's a static port mapping that never expire. IGDv2: NewLeaseDuration: This argument defines the duration of the port mapping. The value of this argument MUST be greater than 0. A NewLeaseDuration with value 0 means static port mapping, but static port mappings can only be created through an out-of-band mechanism. If this parameter is set to 0, default value of 604800 MUST be used. BTW since we don't recheck periodically ports mappings while nicotine+ runs, any UPnP port mapping done with IGDv2 (any modern router does that) will expire after 7 days. The client won't be able to send/receive files anymore... """ log.add(_('Creating Port Mapping rule via UPnP...')) # Placeholder LAN IP address, updated in AddPortMappingBinary or AddPortMappingModule self.internalipaddress = "127.0.0.1" # Store the Local LAN port self.internallanport = np.protothread._p.getsockname()[1] self.externalwanport = self.internallanport # The function depends on what method of configuring port mapping is # available functiontocall = getattr(self, 'AddPortMapping' + self.mode) try: functiontocall() except Exception as e: log.addwarning(_('UPnP exception: %(error)s') % {'error': str(e)}) log.addwarning( _('Failed to automate the creation of ' + 'UPnP Port Mapping rule.')) return log.adddebug( _('Managed to map external WAN port %(externalwanport)s ' + 'on your external IP %(externalipaddress)s ' + 'to your local host %(internalipaddress)s ' + 'port %(internallanport)s.') % { 'externalwanport': self.externalwanport, 'externalipaddress': self.externalipaddress, 'internalipaddress': self.internalipaddress, 'internallanport': self.internallanport })