def withoutBlockInCountry(self, data): """Determine which countries the bridges for this **request** should not be blocked in. If :data:`addClientCountryCode` is ``True``, the the client's own geolocated country code will be added to the to the :data:`notBlockedIn` list. :param dict data: The decoded data from the JSON API request. """ countryCodes = data.get("unblocked", list()) for countryCode in countryCodes: try: country = UNBLOCKED_PATTERN.match(countryCode).group() except (TypeError, AttributeError): pass else: if country: self.notBlockedIn.append(country.lower()) logging.info("Moat request for bridges not blocked in: %r" % country) if self.addClientCountryCode: # Look up the country code of the input IP, and request bridges # not blocked in that country. if addr.isIPAddress(self.client): country = geo.getCountryCode(ipaddr.IPAddress(self.client)) if country: self.notBlockedIn.append(country.lower()) logging.info( ("Moat client's bridges also shouldn't be blocked " "in their GeoIP country code: %s") % country)
def parseData(self): """Parse all data received so far into our :class:`<bridgedb.proxy.ProxySet> exitlist`. """ unparseable = [] data = ''.join(self.data).split('\n') for line in data: line.strip() if not line: continue # If it reached an errorpage, then we grabbed raw HTML that starts # with an HTML tag: if line.startswith('<'): break if line.startswith('#'): continue ip = isIPAddress(line) if ip: logging.info("Discovered Tor exit relay: %s" % ip) self.exitlist.add(ip) else: logging.debug("Got exitlist line that wasn't an IP: %s" % line) unparseable.append(line) if unparseable: logging.warn(("There were unparseable lines in the downloaded " "list of Tor exit relays: %r") % unparseable)
def loadProxiesFromFile(filename, proxySet=None, removeStale=False): """Load proxy IP addresses from a list of files. :param str filename: A filename whose path can be either absolute or relative to the current working directory. The file should contain the IP addresses of various open proxies, one per line, for example:: 11.11.11.11 22.22.22.22 123.45.67.89 :type proxySet: None or :class:`~bridgedb.proxy.ProxySet`. :param proxySet: If given, load the addresses read from the files into this ``ProxySet``. :param bool removeStale: If ``True``, remove proxies from the **proxySet** which were not listed in any of the **files**. (default: ``False``) :returns: A list of all the proxies listed in the **files* (regardless of whether they were added or removed). """ logging.info("Reloading proxy lists...") addresses = [] # We have to check the instance because, if the ProxySet was newly # created, it will likely be empty, causing it to evaluate to False: if isinstance(proxySet, ProxySet): oldProxySet = proxySet.copy() try: with open(filename, 'r') as proxyFile: for line in proxyFile.readlines(): line = line.strip() if isinstance(proxySet, ProxySet): # ProxySet.add() will validate the IP address if proxySet.add(line, tag=filename): addresses.append(line) else: ip = isIPAddress(line) if ip: addresses.append(ip) except Exception as error: logging.warn("Error while reading a proxy list file: %s" % str(error)) if isinstance(proxySet, ProxySet): stale = list(oldProxySet.difference(addresses)) if removeStale: for ip in stale: if proxySet.getTag(ip) == filename: logging.info("Removing stale IP %s from proxy list." % ip) proxySet.remove(ip) else: logging.info("Tag %s didn't match %s" % (proxySet.getTag(ip), filename)) return addresses
def test_returnUncompressedIP(self): """Test returning a :class:`ipaddr.IPAddress`.""" testAddress = '86.59.30.40' result = addr.isIPAddress(testAddress, compressed=False) log.msg("addr.isIPAddress(%r, compressed=False) => %r" % (testAddress, result)) self.assertTrue( isinstance(result, ipaddr.IPv4Address), "Expected %r result from isIPAddress(%r, compressed=False): %r %r" % (ipaddr.IPv4Address, testAddress, result, type(result)))
def withIPversion(self): """Determine if the request **parameters** were for bridges with IPv6 addresses or not. .. note:: If the client's forwarded IP address was IPv6, then we assume the client wanted IPv6 bridges. """ if addr.isIPAddress(self.client): if self.client.version == 6: logging.info("Moat request for bridges with IPv6 addresses.") self.withIPv6()
def __contains__(self, ip): """x.__contains__(y) <==> y in x. :type ip: basestring or int :param ip: The IP address to check. :rtype: boolean :returns: True if ``ip`` is in this set; False otherwise. """ ipset = [isIPAddress(ip),] if ipset and len(self._proxies.intersection(ipset)) == len(ipset): return True return False
def test_isIPAddress_randomIP6(self): """Test :func:`addr.isIPAddress` with a random IPv6 address. This test asserts that the returned IP address is not None (because the IP being tested is random, it *could* randomly be an invalid IP address and thus :func:`~bridgdb.addr.isIPAddress` would return ``False``). """ randomAddress = ipaddr.IPv6Address(random.getrandbits(128)) result = addr.isIPAddress(randomAddress) log.msg("Got addr.isIPAddress() result for random IPv6 address %r: %s" % (randomAddress, result)) self.assertTrue(result is not None)
def runTestForAddr(self, testAddress): """Test :func:`addr.isIPAddress` with the specified ``testAddress``. :param str testAddress: A string which specifies either an IPv4 or IPv6 address to test. """ result = addr.isIPAddress(testAddress) log.msg("addr.isIPAddress(%r) => %s" % (testAddress, result)) self.assertTrue(result is not None, "Got a None for testAddress: %r" % testAddress) self.assertFalse(isinstance(result, basestring), "Expected %r result from isIPAddress(%r): %r %r" % (bool, testAddress, result, type(result)))
def getClientIP(self, request): """Get the client's IP address from the :header:`X-Forwarded-For` header, or from the :api:`request <twisted.web.server.Request>`. :rtype: None or str :returns: The client's IP address, if it was obtainable. """ ip = None if self.useForwardedHeader: h = request.getHeader("X-Forwarded-For") if h: ip = h.split(",")[-1].strip() if not isIPAddress(ip): logging.warn("Got weird X-Forwarded-For value %r" % h) ip = None else: ip = request.getClientIP() return ip
def withoutBlockInCountry(self, request): """Determine which countries the bridges for this **request** should not be blocked in. .. note:: Currently, a **request** for unblocked bridges is recognized if it contains an HTTP GET parameter ``unblocked=`` whose value is a comma-separater list of two-letter country codes. Any two-letter country code found in the :api:`request <twisted.web.http.Request>` ``unblocked=`` HTTP GET parameter will be added to the :data:`notBlockedIn` list. If :data:`addClientCountryCode` is ``True``, the the client's own geolocated country code will be added to the to the :data`notBlockedIn` list. :type request: :api:`twisted.web.http.Request` :param request: A ``Request`` object containing the HTTP method, full URI, and any URL/POST arguments and headers present. """ countryCodes = request.args.get("unblocked", list()) for countryCode in countryCodes: try: country = UNBLOCKED_PATTERN.match(countryCode).group() except (TypeError, AttributeError): pass else: if country: self.notBlockedIn.append(country.lower()) logging.info( "HTTPS request for bridges not blocked in: %r" % country) if self.addClientCountryCode: # Look up the country code of the input IP, and request bridges # not blocked in that country. if addr.isIPAddress(self.client): country = geo.getCountryCode(ipaddr.IPAddress(self.client)) if country: self.notBlockedIn.append(country.lower()) logging.info( ("HTTPS client's bridges also shouldn't be blocked " "in their GeoIP country code: %s") % country)
def withoutBlockInCountry(self, request): """Determine which countries the bridges for this **request** should not be blocked in. .. note:: Currently, a **request** for unblocked bridges is recognized if it contains an HTTP GET parameter ``unblocked=`` whose value is a comma-separater list of two-letter country codes. Any two-letter country code found in the :api:`request <twisted.web.http.Request>` ``unblocked=`` HTTP GET parameter will be added to the :data:`notBlockedIn` list. If :data:`addClientCountryCode` is ``True``, the the client's own geolocated country code will be added to the to the :data`notBlockedIn` list. :type request: :api:`twisted.web.http.Request` :param request: A ``Request`` object containing the HTTP method, full URI, and any URL/POST arguments and headers present. """ countryCodes = request.args.get("unblocked", list()) for countryCode in countryCodes: try: country = UNBLOCKED_PATTERN.match(countryCode).group() except (TypeError, AttributeError): pass else: if country: self.notBlockedIn.append(country.lower()) logging.info("HTTPS request for bridges not blocked in: %r" % country) if self.addClientCountryCode: # Look up the country code of the input IP, and request bridges # not blocked in that country. if addr.isIPAddress(self.client): country = geo.getCountryCode(ipaddr.IPAddress(self.client)) if country: self.notBlockedIn.append(country.lower()) logging.info( ("HTTPS client's bridges also shouldn't be blocked " "in their GeoIP country code: %s") % country)
def __add__(self, ip=None, tag=None): """Add an **ip** to this set, with an optional **tag**. This has no effect if the **ip** is already present. The **ip** is only added if it passes the checks in :func:`~bridgedb.parse.addr.isIPAddress`. :type ip: basestring or int :param ip: The IP address to add. :param tag: An optional value to link to **ip**. If not given, it will be a timestamp (seconds since epoch, as a float) for when **ip** was first added to this set. :rtype: bool :returns: ``True`` if **ip** is in this set; ``False`` otherwise. """ ip = isIPAddress(ip) if ip: if self._proxies.isdisjoint(set(ip)): logging.debug("Adding %s to proxy list..." % ip) self._proxies.add(ip) self._proxydict[ip] = tag if tag else time.time() return True return False
def getClientIP(request, useForwardedHeader=False, skipLoopback=False): """Get the client's IP address from the ``'X-Forwarded-For:'`` header, or from the :api:`request <twisted.web.server.Request>`. :type request: :api:`twisted.web.http.Request` :param request: A ``Request`` for a :api:`twisted.web.resource.Resource`. :param bool useForwardedHeader: If ``True``, attempt to get the client's IP address from the ``'X-Forwarded-For:'`` header. :param bool skipLoopback: If ``True``, and ``useForwardedHeader`` is also ``True``, then skip any loopback addresses (127.0.0.1/8) when parsing the X-Forwarded-For header. :rtype: ``None`` or :any:`str` :returns: The client's IP address, if it was obtainable. """ ip = None if useForwardedHeader: header = request.getHeader("X-Forwarded-For") if header: index = -1 ip = header.split(",")[index].strip() if skipLoopback: logging.info(("Parsing X-Forwarded-For again, ignoring " "loopback addresses...")) while isLoopback(ip): index -= 1 ip = header.split(",")[index].strip() if not skipLoopback and isLoopback(ip): logging.warn("Accepting loopback address: %s" % ip) else: if not isIPAddress(ip): logging.warn("Got weird X-Forwarded-For value %r" % header) ip = None else: ip = request.getClientIP() return ip
def getClientIP(request, useForwardedHeader=False): """Get the client's IP address from the ``'X-Forwarded-For:'`` header, or from the :api:`request <twisted.web.server.Request>`. :type request: :api:`twisted.web.http.Request` :param request: A ``Request`` for a :api:`twisted.web.resource.Resource`. :param bool useForwardedHeader: If ``True``, attempt to get the client's IP address from the ``'X-Forwarded-For:'`` header. :rtype: ``None`` or :any:`str` :returns: The client's IP address, if it was obtainable. """ ip = None if useForwardedHeader: header = request.getHeader("X-Forwarded-For") if header: ip = header.split(",")[-1].strip() if not isIPAddress(ip): logging.warn("Got weird X-Forwarded-For value %r" % header) ip = None else: ip = request.getClientIP() return ip
def wrapper(): ip = None while not isIPAddress(ip): ip = func() return ip
def getBridgeRequestAnswer(self, request): """Respond to a client HTTP request for bridges. :type request: :api:`twisted.web.http.Request` :param request: A ``Request`` object containing the HTTP method, full URI, and any URL/POST arguments and headers present. :rtype: str :returns: A plaintext or HTML response to serve. """ # XXX why are we getting the interval if our distributor might be # using bridgedb.schedule.Unscheduled? interval = self.schedule.intervalStart(time.time()) bridges = ( ) ip = None countryCode = None # XXX this code is duplicated in CaptchaProtectedResource if self.useForwardedHeader: h = request.getHeader("X-Forwarded-For") if h: ip = h.split(",")[-1].strip() if not isIPAddress(ip): logging.warn("Got weird forwarded-for value %r",h) ip = None else: ip = request.getClientIP() # Look up the country code of the input IP if isIPAddress(ip): countryCode = bridgedb.geo.getCountryCode(IPAddress(ip)) else: logging.warn("Invalid IP detected; skipping country lookup.") countryCode = None # XXX separate function again format = request.args.get("format", None) if format and len(format): format = format[0] # choose the first arg # do want any options? transport = ipv6 = unblocked = False ipv6 = request.args.get("ipv6", False) if ipv6: ipv6 = True # if anything after ?ipv6= # XXX oh dear hell. why not check for the '?transport=' arg before # regex'ing? And why not compile the regex once, somewhere outside # this function and class? try: # validate method name transport = re.match('[_a-zA-Z][_a-zA-Z0-9]*', request.args.get("transport")[0]).group() except (TypeError, IndexError, AttributeError): transport = None try: unblocked = re.match('[a-zA-Z]{2,4}', request.args.get("unblocked")[0]).group() except (TypeError, IndexError, AttributeError): unblocked = False logging.info("Replying to web request from %s. Parameters were %r" % (ip, request.args)) rules = [] bridgeLines = None if ip: if ipv6: rules.append(filterBridgesByIP6) addressClass = IPv6Address else: rules.append(filterBridgesByIP4) addressClass = IPv4Address if transport: #XXX: A cleaner solution would differentiate between # addresses by protocol rather than have separate lists # Tor to be a transport, and selecting between them. rules = [filterBridgesByTransport(transport, addressClass)] if unblocked: rules.append(filterBridgesByNotBlockedIn(unblocked, addressClass, transport)) bridges = self.distributor.getBridgesForIP(ip, interval, self.nBridgesToGive, countryCode, bridgeFilterRules=rules) bridgeLines = "".join("%s\n" % b.getConfigLine( includeFingerprint=self.includeFingerprints, addressClass=addressClass, transport=transport, request=bridgedb.Dist.uniformMap(ip) ) for b in bridges) answer = self.renderAnswer(request, bridgeLines, format) return answer
def parseALine(line, fingerprint=None): """Parse an 'a'-line of a bridge networkstatus document. From torspec.git/dir-spec.txt, commit 36761c7d553d L1499-1512: | | "a" SP address ":" port NL | | [Any number.] | | Present only if the OR has at least one IPv6 address. | | Address and portlist are as for "or-address" as specified in | 2.1. | | (Only included when the vote or consensus is generated with | consensus-method 14 or later.) :param string line: An 'a'-line from an bridge-network-status descriptor. :type fingerprint: string or None :param fingerprint: A string which identifies which OR the descriptor we're parsing came from (since the 'a'-line doesn't tell us, this can help make the log messages clearer). :raises: :exc:`NetworkstatusParsingError` :rtype: tuple :returns: A 2-tuple of a string respresenting the IP address and a :class:`bridgedb.parse.addr.PortList`. """ ip = None portlist = None if line.startswith('a '): line = line[2:] # Chop off the 'a ' else: logging.warn("Networkstatus parser received non 'a'-line for %r:"\ " %r" % (fingerprint or 'Unknown', line)) try: ip, portlist = line.rsplit(':', 1) except ValueError as error: logging.error("Bad separator in networkstatus 'a'-line: %r" % line) return (None, None) if ip.startswith('[') and ip.endswith(']'): ip = ip.strip('[]') try: if not addr.isIPAddress(ip): raise NetworkstatusParsingError( "Got invalid IP Address in networkstatus 'a'-line for %r: %r" % (fingerprint or 'Unknown', line)) if addr.isIPv4(ip): warnings.warn(FutureWarning( "Got IPv4 address in networkstatus 'a'-line! "\ "Networkstatus document format may have changed!")) except NetworkstatusParsingError as error: logging.error(error) ip, portlist = None, None try: portlist = addr.PortList(portlist) if not portlist: raise NetworkstatusParsingError( "Got invalid portlist in 'a'-line for %r!\n Line: %r" % (fingerprint or 'Unknown', line)) except (addr.InvalidPort, NetworkstatusParsingError) as error: logging.error(error) portlist = None else: logging.debug("Parsed networkstatus ORAddress line for %r:"\ "\n Address: %s \tPorts: %s" % (fingerprint or 'Unknown', ip, portlist)) finally: return (ip, portlist)