Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #3
0
    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
Beispiel #4
0
    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.'))
Beispiel #5
0
    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
Beispiel #6
0
    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
Beispiel #7
0
    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
Beispiel #8
0
    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
Beispiel #9
0
    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')
Beispiel #10
0
    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)})
Beispiel #11
0
    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
              })