try:
    from json       import dumps, loads
except:
    from simplejson import dumps, loads

try:
    # Python 2
    from urllib2    import urlopen
    from urllib     import urlencode
except:
    # Python 3
    from urllib.request     import urlopen
    from urllib.parse       import urlencode

__all__ = ['WebAPI']

class WebAPIError(Exception):
    def __init__(self, value):
        self.value = value
    
    def __str__(self):
        return self.value


class WebAPI:
    """Wrapper around the SHODAN webservices API"""
    
    class Exploits:
        
        def __init__(self, parent):
            self.parent = parent
            
        def search(self, query, sources=[], cve=None, osvdb=None, msb=None, bid=None):
            """Search the entire Shodan Exploits archive using the same query syntax
            as the website.
            
            Arguments:
            query    -- exploit search query; same syntax as website
            
            Optional arguments:
            sources  -- metasploit, cve, osvdb, exploitdb, or packetstorm
            cve      -- CVE identifier (ex. 2010-0432)
            osvdb    -- OSVDB identifier (ex. 11666)
            msb      -- Microsoft Security Bulletin ID (ex. MS05-030)
            bid      -- Bugtraq identifier (ex. 13951)
            
            """
            if sources:
                query += ' source:' + ','.join(sources)
            if cve:
                query += ' cve:%s' % (str(cve).strip())
            if osvdb:
                query += ' osvdb:%s' % (str(osvdb).strip())
            if msb:
                query += ' msb:%s' % (str(msb).strip())
            if bid:
                query += ' bid:%s' % (str(bid).strip())
            return self.parent._request('search_exploits', {'q': query})
    
    class ExploitDb:
        
        def __init__(self, parent):
            self.parent = parent
        
        def download(self, id):
            """Download the exploit code from the ExploitDB archive.
    
            Arguments:
            id    -- ID of the ExploitDB entry
    
            Returns:
            A dictionary with the following fields:
            filename        -- Name of the file
            content-type    -- Mimetype
            data            -- Contents of the file
    
            """
            return self.parent._request('exploitdb/download', {'id': id})
        
        def search(self, query, **kwargs):
            """Search the ExploitDB archive.
    
            Arguments:
            query     -- Search terms
            
            Optional arguments:
            author    -- Name of the exploit submitter
            platform  -- Target platform (e.g. windows, linux, hardware etc.)
            port      -- Service port number
            type      -- Any, dos, local, papers, remote, shellcode and webapps
    
            Returns:
            A dictionary with 2 main items: matches (list) and total (int).
            Each item in 'matches' is a dictionary with the following elements:
            
            id
            author
            date
            description
            platform
            port
            type
    
            """
            return self.parent._request('exploitdb/search', dict(q=query, **kwargs))
    
    class Msf:
        
        def __init__(self, parent):
            self.parent = parent
            
        def download(self, id):
            """Download a metasploit module given the fullname (id) of it.
            
            Arguments:
            id        -- fullname of the module (ex. auxiliary/admin/backupexec/dump)
            
            Returns:
            A dictionary with the following fields:
            filename        -- Name of the file
            content-type    -- Mimetype
            data            -- File content
            """
            return self.parent._request('msf/download', {'id': id})
        
        def search(self, query, **kwargs):
            """Search for a Metasploit module.
            """
            return self.parent._request('msf/search', dict(q=query, **kwargs))
    
    def __init__(self, key):
        """Initializes the API object.
        
        Arguments:
        key -- your API key
        
        """
        self.api_key = key
        self.base_url = 'http://www.shodanhq.com/api/'
        self.exploits = self.Exploits(self)
        self.exploitdb = self.ExploitDb(self)
        self.msf = self.Msf(self)
    
    def _request(self, function, params):
        """General-purpose function to create web requests to SHODAN.
        
        Arguments:
        function  -- name of the function you want to execute
        params      -- dictionary of parameters for the function
        
        Returns
        A JSON string containing the function's results.
        
        """
        # Add the API key parameter automatically
        params['key'] = self.api_key
        
        # Send the request
        data = urlopen(self.base_url + function + '?' + urlencode(params)).read().decode('utf-8')
        
        # Parse the text into JSON
        data = loads(data)
        
        # Raise an exception if an error occurred
        if data.get('error', None):
            raise WebAPIError(data['error'])
        
        # Return the data
        return data
    
    def count(self, query):
        """Returns the total number of search results for the query.
        """
        return self._request('count', {'q': query})
    
    def locations(self, query):
        """Return a break-down of all the countries and cities that the results for
        the given search are located in.
        """
        return self._request('locations', {'q': query})
    
    def fingerprint(self, banner):
        """Determine the software based on the banner.
        
        Arguments:
        banner  - HTTP banner
        
        Returns:
        A list of software that matched the given banner.
        """
        return self._request('fingerprint', {'banner': banner})
    
    def host(self, ip):
        """Get all available information on an IP.

        Arguments:
        ip    -- IP of the computer

        Returns:
        All available information SHODAN has on the given IP,
        subject to API key restrictions.

        """
        return self._request('host', {'ip': ip})
    
    def info(self):
        """Returns information about the current API key, such as a list of add-ons
        and other features that are enabled for the current user's API plan.
        """
        return self._request('info', {})
    
    def search(self, query, page=1, limit=None, offset=None):
        """Search the SHODAN database.
        
        Arguments:
        query    -- search query; identical syntax to the website
        
        Optional arguments:
        page     -- page number of the search results 
        limit    -- number of results to return
        offset   -- search offset to begin getting results from
        
        Returns:
        A dictionary with 3 main items: matches, countries and total.
        Visit the website for more detailed information.
        
        """
        args = {
            'q': query,
            'p': page,
        }
        if limit:
            args['l'] = limit
            if offset:
                args['o'] = offset
        
        return self._request('search', args)

try:
    from json       import dumps, loads
except:
    from simplejson import dumps, loads

try:
    # Python 2
    from urllib2    import urlopen
    from urllib     import urlencode
except:
    # Python 3
    from urllib.request     import urlopen
    from urllib.parse       import urlencode

__all__ = ['WebAPI']

class WebAPIError(Exception):
    def __init__(self, value):
        self.value = value
    
    def __str__(self):
        return self.value


class WebAPI:
    """Wrapper around the SHODAN webservices API"""
    
    class Exploits:
        
        def __init__(self, parent):
            self.parent = parent
            
        def search(self, query, sources=[], cve=None, osvdb=None, msb=None, bid=None):
            """Search the entire Shodan Exploits archive using the same query syntax
            as the website.
            
            Arguments:
            query    -- exploit search query; same syntax as website
            
            Optional arguments:
            sources  -- metasploit, cve, osvdb, exploitdb, or packetstorm
            cve      -- CVE identifier (ex. 2010-0432)
            osvdb    -- OSVDB identifier (ex. 11666)
            msb      -- Microsoft Security Bulletin ID (ex. MS05-030)
            bid      -- Bugtraq identifier (ex. 13951)
            
            """
            if sources:
                query += ' source:' + ','.join(sources)
            if cve:
                query += ' cve:%s' % (str(cve).strip())
            if osvdb:
                query += ' osvdb:%s' % (str(osvdb).strip())
            if msb:
                query += ' msb:%s' % (str(msb).strip())
            if bid:
                query += ' bid:%s' % (str(bid).strip())
            return self.parent._request('search_exploits', {'q': query})
    
    class ExploitDb:
        
        def __init__(self, parent):
            self.parent = parent
        
        def download(self, id):
            """Download the exploit code from the ExploitDB archive.
    
            Arguments:
            id    -- ID of the ExploitDB entry
    
            Returns:
            A dictionary with the following fields:
            filename        -- Name of the file
            content-type    -- Mimetype
            data            -- Contents of the file
    
            """
            return self.parent._request('exploitdb/download', {'id': id})
        
        def search(self, query, **kwargs):
            """Search the ExploitDB archive.
    
            Arguments:
            query     -- Search terms
            
            Optional arguments:
            author    -- Name of the exploit submitter
            platform  -- Target platform (e.g. windows, linux, hardware etc.)
            port      -- Service port number
            type      -- Any, dos, local, papers, remote, shellcode and webapps
    
            Returns:
            A dictionary with 2 main items: matches (list) and total (int).
            Each item in 'matches' is a dictionary with the following elements:
            
            id
            author
            date
            description
            platform
            port
            type
    
            """
            return self.parent._request('exploitdb/search', dict(q=query, **kwargs))
    
    class Msf:
        
        def __init__(self, parent):
            self.parent = parent
            
        def download(self, id):
            """Download a metasploit module given the fullname (id) of it.
            
            Arguments:
            id        -- fullname of the module (ex. auxiliary/admin/backupexec/dump)
            
            Returns:
            A dictionary with the following fields:
            filename        -- Name of the file
            content-type    -- Mimetype
            data            -- File content
            """
            return self.parent._request('msf/download', {'id': id})
        
        def search(self, query, **kwargs):
            """Search for a Metasploit module.
            """
            return self.parent._request('msf/search', dict(q=query, **kwargs))
    
    def __init__(self, key):
        """Initializes the API object.
        
        Arguments:
        key -- your API key
        
        """
        self.api_key = key
        self.base_url = 'http://www.shodanhq.com/api/'
        self.exploits = self.Exploits(self)
        self.exploitdb = self.ExploitDb(self)
        self.msf = self.Msf(self)
    
    def _request(self, function, params):
        """General-purpose function to create web requests to SHODAN.
        
        Arguments:
        function  -- name of the function you want to execute
        params      -- dictionary of parameters for the function
        
        Returns
        A JSON string containing the function's results.
        
        """
        # Add the API key parameter automatically
        params['key'] = self.api_key
        
        # Send the request
        data = urlopen(self.base_url + function + '?' + urlencode(params)).read().decode('utf-8')
        
        # Parse the text into JSON
        data = loads(data)
        
        # Raise an exception if an error occurred
        if data.get('error', None):
            raise WebAPIError(data['error'])
        
        # Return the data
        return data
    
    def count(self, query):
        """Returns the total number of search results for the query.
        """
        return self._request('count', {'q': query})
    
    def locations(self, query):
        """Return a break-down of all the countries and cities that the results for
        the given search are located in.
        """
        return self._request('locations', {'q': query})
    
    def fingerprint(self, banner):
        """Determine the software based on the banner.
        
        Arguments:
        banner  - HTTP banner
        
        Returns:
        A list of software that matched the given banner.
        """
        return self._request('fingerprint', {'banner': banner})
    
    def host(self, ip):
        """Get all available information on an IP.

        Arguments:
        ip    -- IP of the computer

        Returns:
        All available information SHODAN has on the given IP,
        subject to API key restrictions.

        """
        return self._request('host', {'ip': ip})
    
    def info(self):
        """Returns information about the current API key, such as a list of add-ons
        and other features that are enabled for the current user's API plan.
        """
        return self._request('info', {})
    
    def search(self, query, page=1, limit=None, offset=None):
        """Search the SHODAN database.
        
        Arguments:
        query    -- search query; identical syntax to the website
        
        Optional arguments:
        page     -- page number of the search results 
        limit    -- number of results to return
        offset   -- search offset to begin getting results from
        
        Returns:
        A dictionary with 3 main items: matches, countries and total.
        Visit the website for more detailed information.
        
        """
        args = {
            'q': query,
            'p': page,
        }
        if limit:
            args['l'] = limit
            if offset:
                args['o'] = offset
        
        return self._request('search', args)

# Python 2 # Python 3 Wrapper around the SHODAN webservices API Search the entire Shodan Exploits archive using the same query syntax             as the website.                          Arguments:             query    -- exploit search query; same syntax as website                          Optional arguments:             sources  -- metasploit, cve, osvdb, exploitdb, or packetstorm             cve      -- CVE identifier (ex. 2010-0432)             osvdb    -- OSVDB identifier (ex. 11666)             msb      -- Microsoft Security Bulletin ID (ex. MS05-030)             bid      -- Bugtraq identifier (ex. 13951) Download the exploit code from the ExploitDB archive.                  Arguments:             id    -- ID of the ExploitDB entry                  Returns:             A dictionary with the following fields:             filename        -- Name of the file             content-type    -- Mimetype             data            -- Contents of the file Search the ExploitDB archive.                  Arguments:             query     -- Search terms                          Optional arguments:             author    -- Name of the exploit submitter             platform  -- Target platform (e.g. windows, linux, hardware etc.)             port      -- Service port number             type      -- Any, dos, local, papers, remote, shellcode and webapps                  Returns:             A dictionary with 2 main items: matches (list) and total (int).             Each item in 'matches' is a dictionary with the following elements:                          id             author             date             description             platform             port             type Download a metasploit module given the fullname (id) of it.                          Arguments:             id        -- fullname of the module (ex. auxiliary/admin/backupexec/dump)                          Returns:             A dictionary with the following fields:             filename        -- Name of the file             content-type    -- Mimetype             data            -- File content Search for a Metasploit module. Initializes the API object.                  Arguments:         key -- your API key General-purpose function to create web requests to SHODAN.                  Arguments:         function  -- name of the function you want to execute         params      -- dictionary of parameters for the function                  Returns         A JSON string containing the function's results. # Add the API key parameter automatically # Send the request # Parse the text into JSON # Raise an exception if an error occurred # Return the data Returns the total number of search results for the query. Return a break-down of all the countries and cities that the results for         the given search are located in. Determine the software based on the banner.                  Arguments:         banner  - HTTP banner                  Returns:         A list of software that matched the given banner. Get all available information on an IP.          Arguments:         ip    -- IP of the computer          Returns:         All available information SHODAN has on the given IP,         subject to API key restrictions. Returns information about the current API key, such as a list of add-ons         and other features that are enabled for the current user's API plan. Search the SHODAN database.                  Arguments:         query    -- search query; identical syntax to the website                  Optional arguments:         page     -- page number of the search results          limit    -- number of results to return         offset   -- search offset to begin getting results from                  Returns:         A dictionary with 3 main items: matches, countries and total.         Visit the website for more detailed information.