Esempio n. 1
0
    def getFilesStreams(self, mtimes, oldmtimes, oldstreams, newsharedfiles, rebuild=False, yieldcall=None):

        streams = {}
        shared = self.config.sections["transfers"]["shared"]

        for directory in 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:
            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
Esempio n. 3
0
    def getFilesStreamsUnicode(self,
                               mtimes,
                               oldmtimes,
                               oldstreams,
                               newsharedfiles,
                               rebuild=False,
                               yieldcall=None):

        streams = {}
        shared = self.config.sections["transfers"]["shared"]

        for directory in mtimes.keys():

            virtualdir = self.real2virtual(directory)

            # force Unicode for reading from disk
            u_directory = u"%s" % directory
            str_directory = str(directory)

            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 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:
            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
Esempio n. 5
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.'))
Esempio n. 6
0
    def getFilesStreams(self,
                        mtimes,
                        oldmtimes,
                        oldstreams,
                        newsharedfiles,
                        rebuild=False,
                        yieldcall=None):

        streams = {}
        shared = self.config.sections["transfers"]["shared"]

        for directory in 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
Esempio n. 7
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.'))
Esempio n. 8
0
    def getFilesStreamsUnicode(self, mtimes, oldmtimes, oldstreams, newsharedfiles, rebuild=False, yieldcall=None):

        streams = {}
        shared = self.config.sections["transfers"]["shared"]

        for directory in mtimes.keys():

            virtualdir = self.real2virtual(directory)

            # force Unicode for reading from disk
            u_directory = u"%s" % directory
            str_directory = str(directory)

            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
Esempio n. 9
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 = u"%s" % directory
            str_directory = str(directory)

            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, 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 = unicode(path)

                s_filename = str(filename)
                try:
                    # try to force Unicode for reading from disk
                    isfile = os.path.isfile(ppath)
                except OSError, 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()
Esempio n. 10
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')
Esempio n. 11
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)})
Esempio n. 12
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 = u"%s" % directory
            str_directory = str(directory)

            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, 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 = unicode(path)

                s_filename = str(filename)
                try:
                    # try to force Unicode for reading from disk
                    isfile = os.path.isfile(ppath)
                except OSError, 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()
Esempio n. 13
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')
Esempio n. 14
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)})