1# Copyright (c) 2013-2020 Philip Hane
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7# 1. Redistributions of source code must retain the above copyright notice,
8#    this list of conditions and the following disclaimer.
9# 2. Redistributions in binary form must reproduce the above copyright notice,
10#    this list of conditions and the following disclaimer in the documentation
11#    and/or other materials provided with the distribution.
12#
13# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
17# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23# POSSIBILITY OF SUCH DAMAGE.
24
25from . import Net
26from .asn import IPASN
27from .nir import NIRWhois
28import logging
29
30log = logging.getLogger(__name__)
31
32
33class IPWhois:
34    """
35    The wrapper class for performing whois/RDAP lookups and parsing for
36    IPv4 and IPv6 addresses.
37
38    Args:
39        address (:obj:`str`/:obj:`int`/:obj:`IPv4Address`/:obj:`IPv6Address`):
40            An IPv4 or IPv6 address
41        timeout (:obj:`int`): The default timeout for socket connections in
42            seconds. Defaults to 5.
43        proxy_opener (:obj:`urllib.request.OpenerDirector`): The request for
44            proxy support. Defaults to None.
45    """
46
47    def __init__(self, address, timeout=5, proxy_opener=None):
48
49        self.net = Net(
50            address=address, timeout=timeout, proxy_opener=proxy_opener
51        )
52        self.ipasn = IPASN(self.net)
53
54        self.address = self.net.address
55        self.timeout = self.net.timeout
56        self.address_str = self.net.address_str
57        self.version = self.net.version
58        self.reversed = self.net.reversed
59        self.dns_zone = self.net.dns_zone
60
61    def __repr__(self):
62
63        return 'IPWhois({0}, {1}, {2})'.format(
64            self.address_str, str(self.timeout), repr(self.net.opener)
65        )
66
67    def lookup_whois(self, inc_raw=False, retry_count=3, get_referral=False,
68                     extra_blacklist=None, ignore_referral_errors=False,
69                     field_list=None, extra_org_map=None,
70                     inc_nir=True, nir_field_list=None, asn_methods=None,
71                     get_asn_description=True):
72        """
73        The function for retrieving and parsing whois information for an IP
74        address via port 43 (WHOIS).
75
76        Args:
77            inc_raw (:obj:`bool`): Whether to include the raw whois results in
78                the returned dictionary. Defaults to False.
79            retry_count (:obj:`int`): The number of times to retry in case
80                socket errors, timeouts, connection resets, etc. are
81                encountered. Defaults to 3.
82            get_referral (:obj:`bool`): Whether to retrieve referral whois
83                information, if available. Defaults to False.
84            extra_blacklist (:obj:`list`): Blacklisted whois servers in
85                addition to the global BLACKLIST. Defaults to None.
86            ignore_referral_errors (:obj:`bool`): Whether to ignore and
87                continue when an exception is encountered on referral whois
88                lookups. Defaults to False.
89            field_list (:obj:`list`): If provided, a list of fields to parse:
90                ['name', 'handle', 'description', 'country', 'state', 'city',
91                'address', 'postal_code', 'emails', 'created', 'updated']
92                If None, defaults to all.
93            extra_org_map (:obj:`dict`): Dictionary mapping org handles to
94                RIRs. This is for limited cases where ARIN REST (ASN fallback
95                HTTP lookup) does not show an RIR as the org handle e.g., DNIC
96                (which is now the built in ORG_MAP) e.g., {'DNIC': 'arin'}.
97                Valid RIR values are (note the case-sensitive - this is meant
98                to match the REST result):
99                'ARIN', 'RIPE', 'apnic', 'lacnic', 'afrinic'
100                Defaults to None.
101            inc_nir (:obj:`bool`): Whether to retrieve NIR (National Internet
102                Registry) information, if registry is JPNIC (Japan) or KRNIC
103                (Korea). If True, extra network requests will be required.
104                If False, the information returned for JP or KR IPs is
105                severely restricted. Defaults to True.
106            nir_field_list (:obj:`list`): If provided and inc_nir, a list of
107                fields to parse:
108                ['name', 'handle', 'country', 'address', 'postal_code',
109                'nameservers', 'created', 'updated', 'contacts']
110                If None, defaults to all.
111            asn_methods (:obj:`list`): ASN lookup types to attempt, in order.
112                If None, defaults to all ['dns', 'whois', 'http'].
113            get_asn_description (:obj:`bool`): Whether to run an additional
114                query when pulling ASN information via dns, in order to get
115                the ASN description. Defaults to True.
116
117        Returns:
118            dict: The IP whois lookup results
119
120            ::
121
122                {
123                    'query' (str) - The IP address
124                    'asn' (str) - The Autonomous System Number
125                    'asn_date' (str) - The ASN Allocation date
126                    'asn_registry' (str) - The assigned ASN registry
127                    'asn_cidr' (str) - The assigned ASN CIDR
128                    'asn_country_code' (str) - The assigned ASN country code
129                    'asn_description' (str) - The ASN description
130                    'nets' (list) - Dictionaries containing network
131                        information which consists of the fields listed in the
132                        ipwhois.whois.RIR_WHOIS dictionary.
133                    'raw' (str) - Raw whois results if the inc_raw parameter
134                        is True.
135                    'referral' (dict) - Referral whois information if
136                        get_referral is True and the server is not blacklisted.
137                        Consists of fields listed in the ipwhois.whois.RWHOIS
138                        dictionary.
139                    'raw_referral' (str) - Raw referral whois results if the
140                        inc_raw parameter is True.
141                    'nir' (dict) - ipwhois.nir.NIRWhois() results if inc_nir
142                        is True.
143                }
144        """
145
146        from .whois import Whois
147
148        # Create the return dictionary.
149        results = {'nir': None}
150
151        # Retrieve the ASN information.
152        log.debug('ASN lookup for {0}'.format(self.address_str))
153
154        asn_data = self.ipasn.lookup(
155            inc_raw=inc_raw, retry_count=retry_count,
156            extra_org_map=extra_org_map, asn_methods=asn_methods,
157            get_asn_description=get_asn_description
158        )
159
160        # Add the ASN information to the return dictionary.
161        results.update(asn_data)
162
163        # Retrieve the whois data and parse.
164        whois = Whois(self.net)
165        log.debug('WHOIS lookup for {0}'.format(self.address_str))
166        whois_data = whois.lookup(
167            inc_raw=inc_raw, retry_count=retry_count, response=None,
168            get_referral=get_referral, extra_blacklist=extra_blacklist,
169            ignore_referral_errors=ignore_referral_errors, asn_data=asn_data,
170            field_list=field_list
171        )
172
173        # Add the WHOIS information to the return dictionary.
174        results.update(whois_data)
175
176        if inc_nir:
177
178            nir = None
179            if 'JP' == asn_data['asn_country_code']:
180                nir = 'jpnic'
181            elif 'KR' == asn_data['asn_country_code']:
182                nir = 'krnic'
183
184            if nir:
185
186                nir_whois = NIRWhois(self.net)
187                nir_data = nir_whois.lookup(
188                    nir=nir, inc_raw=inc_raw, retry_count=retry_count,
189                    response=None,
190                    field_list=nir_field_list, is_offline=False
191                )
192
193                # Add the NIR information to the return dictionary.
194                results['nir'] = nir_data
195
196        return results
197
198    def lookup_rdap(self, inc_raw=False, retry_count=3, depth=0,
199                    excluded_entities=None, bootstrap=False,
200                    rate_limit_timeout=120, extra_org_map=None,
201                    inc_nir=True, nir_field_list=None, asn_methods=None,
202                    get_asn_description=True, root_ent_check=True):
203        """
204        The function for retrieving and parsing whois information for an IP
205        address via HTTP (RDAP).
206
207        **This is now the recommended method, as RDAP contains much better
208        information to parse.**
209
210        Args:
211            inc_raw (:obj:`bool`): Whether to include the raw whois results in
212                the returned dictionary. Defaults to False.
213            retry_count (:obj:`int`): The number of times to retry in case
214                socket errors, timeouts, connection resets, etc. are
215                encountered. Defaults to 3.
216            depth (:obj:`int`): How many levels deep to run queries when
217                additional referenced objects are found. Defaults to 0.
218            excluded_entities (:obj:`list`): Entity handles to not perform
219                lookups. Defaults to None.
220            bootstrap (:obj:`bool`): If True, performs lookups via ARIN
221                bootstrap rather than lookups based on ASN data. ASN lookups
222                are not performed and no output for any of the asn* fields is
223                provided. Defaults to False.
224            rate_limit_timeout (:obj:`int`): The number of seconds to wait
225                before retrying when a rate limit notice is returned via
226                rdap+json. Defaults to 120.
227            extra_org_map (:obj:`dict`): Dictionary mapping org handles to
228                RIRs. This is for limited cases where ARIN REST (ASN fallback
229                HTTP lookup) does not show an RIR as the org handle e.g., DNIC
230                (which is now the built in ORG_MAP) e.g., {'DNIC': 'arin'}.
231                Valid RIR values are (note the case-sensitive - this is meant
232                to match the REST result):
233                'ARIN', 'RIPE', 'apnic', 'lacnic', 'afrinic'
234                Defaults to None.
235            inc_nir (:obj:`bool`): Whether to retrieve NIR (National Internet
236                Registry) information, if registry is JPNIC (Japan) or KRNIC
237                (Korea). If True, extra network requests will be required.
238                If False, the information returned for JP or KR IPs is
239                severely restricted. Defaults to True.
240            nir_field_list (:obj:`list`): If provided and inc_nir, a list of
241                fields to parse:
242                ['name', 'handle', 'country', 'address', 'postal_code',
243                'nameservers', 'created', 'updated', 'contacts']
244                If None, defaults to all.
245            asn_methods (:obj:`list`): ASN lookup types to attempt, in order.
246                If None, defaults to all ['dns', 'whois', 'http'].
247            get_asn_description (:obj:`bool`): Whether to run an additional
248                query when pulling ASN information via dns, in order to get
249                the ASN description. Defaults to True.
250            root_ent_check (:obj:`bool`): If True, will perform
251                additional RDAP HTTP queries for missing entity data at the
252                root level. Defaults to True.
253
254        Returns:
255            dict: The IP RDAP lookup results
256
257            ::
258
259                {
260                    'query' (str) - The IP address
261                    'asn' (str) - The Autonomous System Number
262                    'asn_date' (str) - The ASN Allocation date
263                    'asn_registry' (str) - The assigned ASN registry
264                    'asn_cidr' (str) - The assigned ASN CIDR
265                    'asn_country_code' (str) - The assigned ASN country code
266                    'asn_description' (str) - The ASN description
267                    'entities' (list) - Entity handles referred by the top
268                        level query.
269                    'network' (dict) - Network information which consists of
270                        the fields listed in the ipwhois.rdap._RDAPNetwork
271                        dict.
272                    'objects' (dict) - Mapping of entity handle->entity dict
273                        which consists of the fields listed in the
274                        ipwhois.rdap._RDAPEntity dict. The raw result is
275                        included for each object if the inc_raw parameter
276                        is True.
277                    'raw' (dict) - Whois results in json format if the inc_raw
278                        parameter is True.
279                    'nir' (dict) - ipwhois.nir.NIRWhois results if inc_nir is
280                        True.
281                }
282        """
283
284        from .rdap import RDAP
285
286        # Create the return dictionary.
287        results = {'nir': None}
288
289        asn_data = None
290        response = None
291        if not bootstrap:
292
293            # Retrieve the ASN information.
294            log.debug('ASN lookup for {0}'.format(self.address_str))
295            asn_data = self.ipasn.lookup(
296                inc_raw=inc_raw, retry_count=retry_count,
297                extra_org_map=extra_org_map, asn_methods=asn_methods,
298                get_asn_description=get_asn_description
299            )
300
301            # Add the ASN information to the return dictionary.
302            results.update(asn_data)
303
304        # Retrieve the RDAP data and parse.
305        rdap = RDAP(self.net)
306        log.debug('RDAP lookup for {0}'.format(self.address_str))
307        rdap_data = rdap.lookup(
308            inc_raw=inc_raw, retry_count=retry_count, asn_data=asn_data,
309            depth=depth, excluded_entities=excluded_entities,
310            response=response, bootstrap=bootstrap,
311            rate_limit_timeout=rate_limit_timeout,
312            root_ent_check=root_ent_check
313        )
314
315        # Add the RDAP information to the return dictionary.
316        results.update(rdap_data)
317
318        if inc_nir:
319
320            nir = None
321            if 'JP' == asn_data['asn_country_code']:
322                nir = 'jpnic'
323            elif 'KR' == asn_data['asn_country_code']:
324                nir = 'krnic'
325
326            if nir:
327                nir_whois = NIRWhois(self.net)
328                nir_data = nir_whois.lookup(
329                    nir=nir, inc_raw=inc_raw, retry_count=retry_count,
330                    response=None,
331                    field_list=nir_field_list, is_offline=False
332                )
333
334                # Add the NIR information to the return dictionary.
335                results['nir'] = nir_data
336
337        return results
338