Exemplo n.º 1
0
class ShodanIO(Module):
    def __init__(self, b):
        super().__init__(b)
        self.key = self.bot.config.get('apikeys', 'shodan')
        if not self.key:
            raise SakariException('Couln\'t find api key for Shodan.')
        self.shodan = Shodan(self.key)

    @Command('shodan')
    def query(self, c, e, args):
        try:
            result = self.shodan.count(' '.join(args))
            c.privmsg(get_target(c, e), 'Found {} results.'.format(result))
        except APIError as er:
            c.privmsg(get_target(c, e), '\x02Error:\x0f {}'.format(er.value))

    @Command('shost', 'shodanhost')
    def host(self, c, e, args):
        try:
            result = self.shodan.host(args[0])
            if result:
                c.privmsg(get_target(c, e),
                          'Available ports: {' + ', '.join([str(n['port']) for n in result['data']]) + '}.')
        except APIError as er:
            c.privmsg(get_target(c, e), '\x02Error:\x0f {}'.format(er.value))
Exemplo n.º 2
0
class ShodanConnector:
    @exception_handler(expected_exception=ShodanConnectorInitError)
    def __init__(self, api_key=DefaultValues.SHODAN_API_KEY):
        self.api = Shodan(api_key)
        self.results: list = []
        self.shodan_results_count: int = 0
        self.real_results_count: int = 0

    @exception_handler(expected_exception=ShodanConnectorSearchError)
    def search(
        self, query: str, max_records=DefaultValues.SHODAN_DEFAULT_RESULTS_QUANTITY
    ) -> None:
        try:
            self.results = list(self.api.search_cursor(query))[:max_records]
            self.shodan_results_count = self.api.count(query).get("total")
        except (APIError, APITimeout) as api_error:
            print(f"Shodan API error: {api_error}")
        self.real_results_count = len(list(self.results))

    def get_results(self) -> list:
        return self.results

    def get_shodan_count(self) -> int:
        return self.shodan_results_count

    def get_real_count(self) -> int:
        return self.real_results_count

    def get_vulnerabilities(self) -> dict:
        return {
            host["ip_str"]: host["vulns"] for host in self.results if host.get("vulns")
        }
Exemplo n.º 3
0
def search(db_file, keyword, api_key):
    api = Shodan(api_key)
    #query = 'product:elastic port:9200'
    query = 'product:elastic'
    if keyword is not None:
        query += ' ' + keyword
    count_results = api.count(query)
    print(f'Total results for keyword "{keyword}": {count_results["total"]}')

    with sqlite3.connect(str(db_file)) as conn:
        cur = conn.cursor()
        try:
            existing_r = cur.execute('SELECT COUNT(*) FROM IP_SEARCH_RESULT').fetchone()
            existing = existing_r[0] if len(existing_r) > 0 else 0

            results = []
            for result in api.search_cursor(query):
                
                ip = result['ip_str']
                port = result['port']
                org = result['org']
                cntry = result['location']['country_code3']
                loc = f"{result['location']['country_name']} ({result['location']['country_code']})"
                lat = result['location']['latitude']
                lon = result['location']['longitude']
                date = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

                results.append((ip, port, org, cntry, loc, lat, lon, query, date))
            cur.executemany(
                    'INSERT OR REPLACE INTO IP_SEARCH_RESULT '
                    '(IP_ADDRESS, PORT, ORGANIZATION, COUNTRY_CODE, LOCATION, '
                    'LATITUDE, LONGITUDE, ORIGINAL_SEARCH_QUERY, UPDATED_DATE) '
                    'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
                    results)
            conn.commit()

            now_r = cur.execute('SELECT COUNT(*) FROM IP_SEARCH_RESULT').fetchone()
            now = now_r[0] if len(now_r) > 0 else 0
            print(f'New IP addresses added: {now - existing}')

        finally:
            conn.commit()
            cur.close()
# Input validation
if len(sys.argv) == 1:
	print 'Usage:%s<search query>' %sys.argv[0]
	sys.exit(1)

query = ' '.join(sys.argv[1:])

result = query_Shodan(query)

shodancost = 19.00
ipcost = shodancost/4294967296

print 'Shodan Summary Information'
print 'Query:%s' % query
print 'Total Results: %s\n' % result['total']
if result['total'] == 0:
	IPv4lwcost = float('Inf')
else:
	IPv4lwcost = (Decimal(shodancost)/Decimal(result['total']))
print 'All-IPv4 L-W cost:  $%.10f\n' % IPv4lwcost
# Print the summary info from the facets
for facet in result['facets']:
	print FACET_TITLES[facet]
	for term in result['facets'][facet]:
		print term['value']+'\n'
		query = facet+':\"%s\"' % term['value']
		vips = api.count(query)
		print '%.10f' % (Decimal(ipcost*vips['total'])/Decimal(term['count']))
		print ''
Exemplo n.º 5
0
class ShodanConnector:
    @exception_handler(expected_exception=ShodanConnectorInitError)
    def __init__(self, api_key=DefaultValues.SHODAN_API_KEY):
        self.api = Shodan(api_key)
        self.results: list = []
        self.shodan_results_count: int = 0
        self.real_results_count: int = 0

    def _remove_unused_fields_in_vulns(
        self,
        max_references: int = DefaultValues.
        SHODAN_MAX_VULNERABILITIES_REFERENCES
    ) -> None:
        """
        Remove fields that not useful from vulnerabilities.
        :param max_references: decrease quantity of reference to this number
        :return: None
        """
        for host in self.results:
            if not host.get("vulns"):
                continue
            for cve, cve_information in host.get("vulns", {}).items():
                if cve_information.get("references"):
                    cve_information["references"] = cve_information[
                        "references"][:max_references]
                if "verified" in cve_information.keys():
                    cve_information.pop("verified")

    @exception_handler(expected_exception=ShodanConnectorSearchError)
    def search(
            self,
            query: str,
            max_records=DefaultValues.SHODAN_DEFAULT_RESULTS_QUANTITY) -> None:
        """
        Search for defined query in Shodan database
        :param query: query to search for
        :param max_records: quantity of max records to search
        :return: None
        """
        try:
            self.results = list(self.api.search_cursor(query))[:max_records]
            self._remove_unused_fields_in_vulns()
            self.shodan_results_count = self.api.count(query).get("total")
        except (APIError, APITimeout) as api_error:
            print(f"Shodan API error: {api_error}")
        self.real_results_count = len(list(self.results))

    def get_results(self) -> list:
        """
        Return Shodan results
        :return: list of results
        """
        return self.results

    def get_shodan_count(self) -> int:
        """
        Return quantity of results from Shodan database
        :return: quantity of results
        """
        return self.shodan_results_count

    def get_real_count(self) -> int:
        """
        Return real quantity of results that
        was successfully gained from Shodan
        :return: quantity of real results that we get
        """
        return self.real_results_count

    def get_vulnerabilities(self) -> dict:
        """
        Return dictionary with vulnerabilities,
        {host: vulnerabilities}
        :return: dictionary with vulnerabilities
        """
        return {
            host["ip_str"]: host["vulns"]
            for host in self.results if host.get("vulns")
        }
Exemplo n.º 6
0
from shodan import Shodan

API_KEY = ""
api = Shodan(API_KEY)

# Search for websites that have been "hacked"
for banner in api.search_cursor('http.title:"hacked by"'):
    print(banner)

# Get the total number of industrial control systems services on the Internet
ics_services = api.count('tag:ics')
print('Industrial Control Systems: {}'.format(ics_services['total']))

# Get the total number of scada services on the Internet
scada_services = api.count('tag:scada')
print('Supervisory Control and Data Acquisition: {}'.format(
    scada_services['total']))

# Get the total number of plc services on the Internet
plc_services = api.count('tag:plc')
print('Programmable Logic Controller: {}'.format(plc_services['total']))

# Get the total number of dcs services on the Internet
dcs_services = api.count('tag:dcs')
print('Distributed Control System: {}'.format(dcs_services['total']))
Exemplo n.º 7
0
class CamScan:
    def __init__(self,
                 dirname='Images',
                 search=None,
                 path=None,
                 timeout=4,
                 pages=0,
                 verbose=False):

        self.search = search
        self.path = path
        self.dirname = dirname
        self.timeout = timeout
        self.pages = pages
        self.verbose = verbose
        self.api = None

        try:

            keyfile = open('shodan_api_key', 'r')
            key = keyfile.readline()
            keyfile.close()
            self.api = Shodan(key)

        except FileNotFoundError:

            print('Key file not found')

        DIR_NUMBER = 2
        while os.path.exists(self.dirname):
            self.dirname = self.dirname.strip('0987654321') + str(DIR_NUMBER)
            DIR_NUMBER += 1

    def initShodan(self, key):

        with open('shodan_api_key', 'w') as file:
            file.write(key)

        self.api = Shodan(key)

    def chooseFromCSV(self, file):

        if os.path.exists(file):

            f = open(file, newline='')
            data = csv.DictReader(f)

            searches = {}

            for x in data:
                searches[x['searchQuery']] = x['imagePath']

            f.close()

            print('CSV file input. Select search from below:\n')

            y = 0
            for search in searches:
                print(str(y) + ') ' + search)
                y += 1

            choice = int(input('\nChoose search: '))
            self.search = list(searches.keys())[choice]
            self.path = list(searches.values())[choice]

        else:

            raise FileNotFoundError

    def pagesCount(self):

        hosts = self.api.count(self.search)['total']

        return int(hosts / 100) + 1

    def setPages(self, pages):

        if type(pages) in [int, range, type(None)]:
            self.pages = pages

        else:
            raise Exception(
                'Wrong type. pages value can be set to int, range, or None')

    def requestAndDownload(self, url):

        try:

            r = requests.get(url, timeout=self.timeout)

            if r.status_code == 200:

                if self.verbose:
                    print(url, ' - Success')

                filename = urlparse(url).netloc.replace(':', '-') + '.png'

                with open(filename, 'wb') as img:
                    img.write(r.content)

            else:
                if self.verbose:
                    print(url, r.status_code, 'Error')

        except requests.exceptions.ReadTimeout:
            if self.verbose:
                print(url, '- Timed out')

        except Exception as e:
            #print(e)
            if self.verbose:
                print(url, '- Connection Error')

    def runOnPage(self, pagenumber):

        results = None
        tries = 0

        while results is None and tries < 10:

            try:
                results = self.api.search(self.search, page=pagenumber)

            except Exception as e:
                tries += 1
                print('Shodan error')
                if tries == 10:
                    print('Giving up')
                    raise Exception(e.args[0])

        threads = []

        for result in results['matches']:

            url = 'http://' + str(result['ip_str']) + ':' + str(
                result['port']) + self.path
            x = threading.Thread(target=self.requestAndDownload, args=(url, ))
            threads.append(x)
            x.start()

        for thread in threads:
            thread.join()

    def run(self):

        if self.api == None:
            raise Exception('No Shodan key')

        os.mkdir(self.dirname)
        os.chdir(self.dirname)

        print('Saving images to', os.getcwd(), '\n')

        if self.pages is None:

            print('Running every page')

            for page in range(self.pagesCount()):
                print('Starting page:', page)
                self.runOnPage(page)

        elif type(self.pages) is int:

            print('Running page', self.pages)

            self.runOnPage(self.pages)

        elif type(self.pages) is range:

            for page in self.pages:
                print('Starting page:', page)
                self.runOnPage(page)

    def generatePage(self):

        html = '''
<!DOCTYPE html>
<html>
<head>
    <title>Saved Images</title>
    <script>
        let emptyImages = [];

    	function removeEmpty() {
    		let images = document.getElementsByTagName('img');

    		for (let i = 0; i < images.length; i++) {
    			
    			if (images[i].naturalHeight == 0) {
    				emptyImages.push(images[i]);
    			}
    		}

    		for (let i = 0; i < emptyImages.length; i++) {
    			emptyImages[i].remove();
    		}
    	}
    </script>
</head>
<body style="background-color:black">
    <button onclick="removeEmpty()">Remove Empty Images</button>
    <p style="color:white;">Click on an image to open stream</p>
'''

        with open('images.html', 'w') as page:

            page.write(html)

            for name_of_file in os.listdir():

                if '.png' in name_of_file:

                    link = 'http://' + name_of_file.replace('-',
                                                            ':').strip('.png')

                    page.write(
                        '\n\t<a href="{}" target="_blank">'.format(link))
                    page.write(
                        '\n\t\t<img src="{}" height="480" width="720">'.format(
                            name_of_file))
                    page.write('\n\t</a>')

            page.write('\n</body>\n</html>')

    def showImages(self):
        webbrowser.open('images.html')

    def info(self):

        print('search:', self.search)
        print('path:', self.path)
        print('dirname', self.dirname)
        print('timeout:', self.timeout)
        print('pages:', self.pages)
Exemplo n.º 8
0
class CamScan:
    def __init__(self,
                 dirname='Images',
                 search=None,
                 path=None,
                 timeout=4,
                 pages=0,
                 verbose=False):

        self.search = search
        self.path = path
        self.dirname = dirname
        self.timeout = timeout
        self.pages = pages
        self.verbose = verbose
        self.api = None
        self.live_hosts = []

        try:

            keyfile = open('shodan_api_key', 'r')
            key = keyfile.readline()
            keyfile.close()
            self.api = Shodan(key)

        except FileNotFoundError:

            print('Key file not found')

        DIR_NUMBER = 2
        while os.path.exists(self.dirname):
            self.dirname = self.dirname.strip('0987654321') + str(DIR_NUMBER)
            DIR_NUMBER += 1

    def initShodan(self, key):

        with open('shodan_api_key', 'w') as file:
            file.write(key)

        self.api = Shodan(key)

    def chooseFromCSV(self, file):

        if os.path.exists(file):

            f = open(file, newline='')
            data = csv.DictReader(f)

            searches = {}

            for x in data:
                searches[x['searchQuery']] = x['imagePath']

            f.close()

            print('Select search from below:\n')

            y = 0
            for search in searches:
                print(str(y) + ') ' + search)
                y += 1

            choice = int(input('\nChoose search: '))
            self.search = list(searches.keys())[choice]
            self.path = list(searches.values())[choice]

        else:

            raise FileNotFoundError

    def pagesCount(self):

        hosts = self.api.count(self.search)['total']

        return int(hosts / 100) + 1

    def setPages(self, pages):

        if type(pages) in [int, range, type(None)]:
            self.pages = pages

        else:
            raise Exception(
                'Wrong type. pages value can be set to int, range, or None')

    def requestAndDownload(self, shodan_result):

        host = str(shodan_result['ip_str'])
        port = str(shodan_result['port'])
        url = 'http://{}:{}'.format(host, port) + self.path

        try:

            r = requests.get(url, timeout=self.timeout)

            if r.status_code == 200:

                if self.verbose:
                    print(url, ' - Success')

                filename = '{}-{}'.format(host, port) + '.png'

                with open(filename, 'wb') as img:
                    img.write(r.content)

                self.live_hosts.append([filename, shodan_result])

            else:
                if self.verbose:
                    print(url, r.status_code, 'Error')

        except requests.exceptions.ReadTimeout:
            if self.verbose:
                print(url, '- Timed out')

        except Exception as e:
            #print(e)
            if self.verbose:
                print(url, '- Connection Error')

    def runOnPage(self, pagenumber):

        results = None
        tries = 0

        while results is None:

            try:
                results = self.api.search(self.search, page=pagenumber)

            except Exception as e:
                tries += 1
                sleep(1)
                print(e.args[0])
                print('Retrying...')
                if tries == 120:
                    print('Giving up')
                    raise Exception(e.args[0])

        threads = []

        for result in results['matches']:

            x = threading.Thread(target=self.requestAndDownload,
                                 args=(result, ))
            threads.append(x)
            x.start()

        for thread in threads:
            thread.join()

    def run(self):

        if self.api == None:
            raise Exception('No Shodan key')

        os.mkdir(self.dirname)
        os.chdir(self.dirname)

        print('Saving images to', os.getcwd(), '\n')

        if self.pages is None:

            print('Running every page')

            for page in range(self.pagesCount() + 1):
                print('Starting page:', page)
                self.runOnPage(page)

        elif type(self.pages) is int:

            print('Running page', self.pages)

            self.runOnPage(self.pages)

        elif type(self.pages) is range:

            for page in self.pages:
                print('Starting page:', page)
                self.runOnPage(page)

    def generatePage(self):

        html = '''
<!DOCTYPE html>
<html>
<head>
    <title>Saved Images</title>
        <script>
            function changeColumns() {
                let columns = parseInt(document.getElementById('cols').value);
                let gallery = document.getElementsByClassName("gallery")[0];
		let images = document.getElementsByTagName("img");
		let s = "";
		let h;
			
		for (let i = 0; i < columns; i++ ) {
		    s += "auto ";
		}
		gallery.style.gridTemplateColumns = s;
			
		switch (columns) {
                    case 2:
                        h = 700;
			break;
		    case 3:
			h = 480;
			break;
		    case 4:
			h = 300;
			break;
		}

		for (let i = 0; i < images.length; i++) {
                    images[i].height = h;
		}
	    }
        </script>
    <style>
	button {
		border: none;
		color: white;
		padding: 5px 40px;
		text-align: center;
		text-decoration: none;
		display: inline-block;
		font-size: 16px;
		margin: 5px 2px;
		cursor: pointer;
		background-color: #386fc2;
	}

	
    .gallery {
        display: grid;
        grid-template-columns: auto auto auto auto;
	grid-template-rows: auto;
        grid-gap: 10px;
    }
    .gallery img {
        width: 100%;
    }
	.gallery .item {
		position: relative;
		overflow: hidden;
	}
	.gallery .item img {
		vertical-align: middle;
	}
	
	.gallery .caption {
		margin: 0;
		padding: .5em;
		position: absolute;
		z-index: 1;
		bottom: 0;
		left: 0;
		width: 100%;
		max-height: 100%;
		overflow: auto;
		box-sizing: border-box;
		transition: transform 0.2s;
		transform: translateY(100%);
		background: rgba(0, 0, 0, 0.4);
		color:white;
		font-family: Arial;
	}
	
	.gallery .item:hover .caption {
		transform: translateY(0%);
	}

    h1 {
        text-align: center;
        color:#919191;
        font-family: Arial;
    }
    label {
        color:#919191;
		font-family: Arial;
    }
    </style>
</head>
<body style="background-color:black" onload="changeColumns()">
    <h1>Saved Images:</h1>
	<label for="cols">Columns:</label>
	<select id="cols" onchange="changeColumns()">
	  <option value="2">2</option>
	  <option value="3">3</option>
	  <option value="4" selected="selected">4</option>
	</select>
	<hr style="width:70%;">

    <div class=gallery>
'''

        with open('images.html', 'w') as page:

            page.write(html)
            no_dupes = []
            for h in self.live_hosts:
                if h not in no_dupes:
                    no_dupes.append(h)

            for host in no_dupes:

                if os.path.getsize(host[0]) > 0:

                    link = 'http://' + host[1]['ip_str'] + ':' + str(
                        host[1]['port'])
                    data = (host[0], host[1]['ip_str'],
                            host[1]['location']['city'],
                            host[1]['location']['country_name'],
                            host[1]['org'], link, host[1]['ip_str'])

                    element = f'''
                <div class="item">
			<img src="%s">
			
			<span class="caption">
				
				<table style="margin: auto;font-weight: bold;color:white;">
				  <tr>
					<td>IP Address:</td>
					<td>%s</td>
				  </tr>
				  <tr>
					<td>City:</td>
					<td>%s</td>
				  </tr>
				  <tr>
					<td>Country:</td>
					<td>%s</td>
				  </tr>
				  <tr>
					<td>Organization:</td>
					<td>%s</td>
				  </tr>
				</table>
				
				<div style="text-align: center;">
				<a href="%s" target="_blank" style="text-decoration: none">
					<button type="submit">Open stream in new tab</button>
				</a>
                                <a href="https://www.shodan.io/host/%s" target="_blank" style="text-decoration: none">
                                    <button style="background-color: #be473c">Shodan Page</button>
                                </a>
				</div>
				
			</span>
		</div>
''' % data

                    try:
                        page.write(element)

                    except UnicodeEncodeError:
                        if self.verbose:
                            print(
                                "That was wierd. UnicodeEncodeError for host",
                                host[1]['ip_str'])
                        pass

            page.write('\n\t</div>\n</body>\n</html>')

    def showImages(self):
        webbrowser.open('images.html')

    def info(self):

        print('search:', self.search)
        print('path:', self.path)
        print('dirname', self.dirname)
        print('timeout:', self.timeout)
        print('pages:', self.pages)
Exemplo n.º 9
0
# Lookup an IP
# ipinfo = api.host('217.76.128.8')
# print(ipinfo)

# Search for websites that have been "hacked"
# for banner in api.search_cursor('http.title:"hacked by"'):
#     print(banner)

# Get the total number of industrial control systems services on the Internet
# ics_services = api.count('tag:ics')
# print('Industrial Control Systems: {}'.format(ics_services['total']))

# list hosts for port
query = 'port:8009 asn:AS8560 country:es'
result = api.count(query)
print("Results: {}".format(result['total']))
print("\n")

result = api.search(query)
print(result)

for data in result['matches']:
    print("{:15}: {}".format(data['ip_str'], data['hostnames']))

# for data in result['matches']:
#    ip_address = data['ip_str']
#    try:
#        hostname = socket.gethostbyaddr(ip_address)
#        print("{:15}: {}".format(ip_address, hostname[0]))
#    except:
Exemplo n.º 10
0
class CamScan:
    
    def __init__(self, dirname='Images', search=None,
                 path=None, timeout=7, verbose=False):

        self.search = search
        self.path = path
        self.dirname = dirname
        self.timeout = timeout
        self.pages = {0: None}
        self.verbose = verbose
        self.api = None
        self.live_hosts = []
        self.checkPTZ = False
        self.checkPTZPath = None
        self.store_offline = True

        try:
            
            keyfile = open('shodan_api_key','r')
            key = keyfile.readline()
            keyfile.close()
            self.api = Shodan(key)
            
        except FileNotFoundError:
            
            print('Key file not found')
        

        DIR_NUMBER = 2
        while os.path.exists(self.dirname):
            self.dirname = self.dirname.strip('0987654321') + str(DIR_NUMBER)
            DIR_NUMBER += 1
        

    def initShodan(self, key):

        with open('shodan_api_key','w') as file:
            file.write(key)

        self.api = Shodan(key)


    def chooseFromCSV(self, file):

        if os.path.exists(file):
            
            f = open(file, newline='')
            data = csv.DictReader(f)

            searches = []
            print('Select search from below:\n')
            y = 0
            for x in data:
                item = []
                item.append(x['searchQuery'])
                item.append(x['imagePath'])
                item.append(x['ptzCheckPath'])
                item.append(x['friendlyName'])
                print(str(y) + ") " + x['friendlyName'])
                searches.append(item)
                y += 1

            f.close()

            print("\nSearches marked with (Free) don't require a paid Shodan account to use")
            print("Searches marked with [PTZ] support checking for locked PTZ controls")
            
            try:
                choice = int(input('Choose search: '))
                self.search = searches[choice][0]
                self.path = searches[choice][1]
                self.checkPTZPath = searches[choice][2]
                self.friendly_name = searches[choice][3]
            except ValueError:
                print("That's not a number...")
            except IndexError:
                print("That's not one of the choices...")
            except Exception:
                print("You're an idiot...")


        else:

            raise FileNotFoundError


    def pagesCount(self):
        
        hosts = self.api.count(self.search)['total']

        return int(hosts / 100) + 1


    def setPages(self, pages_str):

        self.pages = {}

        if type(pages_str) == str:
            for num in pages_str.split(','):
                if '-' in num:
                    r = num.split('-')
                    for number in range(int(r[0]),int(r[1]) + 1):
                        self.pages[int(number)] = None

                else:
                    self.pages[int(num)] = None

        elif pages_str == None:
            self.pages = None

        else:
            raise Exception("Page value needs to be a string, or None")

        
    def requestAndDownload(self, shodan_result):
        
        host = str(shodan_result['ip_str'])
        port = str(shodan_result['port'])
        url = 'http://{}:{}'.format(host,port) + self.path
        self.total += 1

        try:

            r = requests.get(url, timeout=self.timeout)

            if r.status_code == 200:

                filename = '{}-{}'.format(host,port) + '.png'

                if self.store_offline == True:

                    with open(filename, 'wb') as img:
                        img.write(r.content)

                if self.checkPTZ and self.checkPTZPath:

                    ptz_url = 'http://{}:{}'.format(host,port) + self.checkPTZPath
                    ptz_request = requests.get(ptz_url, timeout=self.timeout)
                    bad_codes = [x for x in range(400,512)]
                    
                    if ptz_request.status_code not in bad_codes:
                        if self.verbose:
                            print('[Info] Connection to {}:{} successfull, camera possibly controllable'.format(host,port))

                        self.ptz_count += 1
                        self.live_hosts.append([filename,shodan_result,True])

                    else:
                        if self.verbose:
                            print('[Info] Connection to {}:{} successfull, camera controls locked'.format(host,port))

                        self.live_hosts.append([filename,shodan_result,False])

                else:
                    if self.verbose:
                        print('[Info] Connection to {}:{} successfull'.format(host,port))

                    self.live_hosts.append([filename,shodan_result,False])
                
                self.success_count += 1

            else:
                self.failed_count += 1
                if self.verbose:
                    print('[HTTP {} Error] Connection to {}:{} failed'.format(r.status_code,host,port))

        except requests.exceptions.ReadTimeout:
            self.failed_count += 1
            if self.verbose:
                print('[Network Error] Connection to {}:{} timed out'.format(host,port))

        except Exception as e:
            self.failed_count += 1
            #print(e)
            if self.verbose:
                print('[Network Error] Connection to {}:{} failed'.format(host,port))


    def _runOnPage(self, pagenumber):

        r = self.pages[pagenumber]

        if self.verbose:
            print("[Info] Contacting hosts on page",pagenumber)
            
        for result in r['matches']:

            x = threading.Thread(target=self.requestAndDownload, args=(result,))
            self.threads.append(x)
            x.start()

        #for thread in threads:
        #    thread.join()
                    

    def shodanSearch(self):

        if self.api == None:
            raise Exception('Shodan API key not set')
        
        else:
            for pageNum in self.pages:

                if self.verbose:
                    print("[Info] Searching shodan on page",pageNum)
                
                tries = 0
                while self.pages[pageNum] == None:
                    try:
                        self.pages[pageNum] = self.api.search(self.search, page=pageNum)
                    except Exception as e:
                        tries += 1

                        if "upgrade your API plan" in e.args[0]:
                            print("[Fatal error] Paid Shodan account required for pages and search filters.")
                            self.end = True
                            break
                            
                        if tries == 35:
                            print("[Fatal Error] Shodan not responding correctly, giving up")
                            self.end = True
                            break

                        print("[API Error]", e, "- Retrying...")

                    sleep(1.5)


    def run(self):

        self.success_count = 0
        self.failed_count = 0
        self.ptz_count = 0
        self.total = 0
        self.end = False
        self.threads = []

        if self.pages == None:
            self.pages = {}
            for page in range(1,self.pagesCount() + 1):
                self.pages[page] = None

        os.mkdir(self.dirname)
        os.chdir(self.dirname)

        print('Saving images to', os.getcwd(), '\n')
        threading.Thread(target=self.shodanSearch).start()

        print("[Info] Starting...")
        start_time = time()
        t = localtime()
        self.start_time_str = "{}/{}/{} {}:{}:{}".format(t[1],t[2],t[0],t[3],t[4],t[5])

        for page in self.pages:

            while self.pages[page] == None and not self.end:
                sleep(.1)

            if not self.end:
                self._runOnPage(page)

        for thread in self.threads:
            thread.join()

        if self.verbose:
            print("[Info] Completed")
            
        self.time_elapsed = time() - start_time


    def generatePage(self,open_on_completion=True):

        if self.checkPTZ and self.checkPTZPath:
            ptz_box = '''
            <div class="thing">
                <label for="ptz_box">Hide hosts with closed PTZ controls:</label>
                <input id="ptz_box" type="checkbox" onchange="ptzCheckBox()">
            </div>'''

        else:
            ptz_box = ""

        html = '''<!DOCTYPE html>
<html>
<head>
    <title>'''+self.friendly_name+'''</title>
        <script>
            function changeColumns() {
                let columns = parseInt(document.getElementById('cols').value);
                let gallery = document.getElementsByClassName("gallery")[0];
		let images = document.getElementsByTagName("img");
		let s = "";
		let h;
			
		for (let i = 0; i < columns; i++ ) {
		    s += "auto ";
		}
		gallery.style.gridTemplateColumns = s;
			
		switch (columns) {
                    case 2:
                        h = 700;
			break;
		    case 3:
			h = 480;
			break;
		    case 4:
			h = 300;
			break;
		}

		for (let i = 0; i < images.length; i++) {
                    images[i].height = h;
		}
	    }
        let nonptz_list = [];
        let all_items = [];
        let not_ran = true;

        function ptzCheckBox() {
            const gal = document.getElementsByClassName("gallery")[0];
            let all = gal.getElementsByClassName("item");
            let box = document.getElementById("ptz_box");
            let nonptz_items = gal.getElementsByClassName("nonptz");

            if (not_ran) {
                for (let i = 0; i < gal.childElementCount; i++) {
                    all_items.push(all[i]);
                }
                not_ran = false;
            }

            for (let i = 0; i < nonptz_items.length; i++) {
                nonptz_list.push(nonptz_items[i]);
            }   

            if (box.checked) {
                for (let i = 0; i < nonptz_list.length; i++) {
                    nonptz_list[i].remove()
                }
            } else {
                for (let i = 0; i < all_items.length; i++) {
                    all_items[i].remove();
                }
                for (let i = 0; i < all_items.length; i++) {
                    gal.appendChild(all_items[i]);
                }
            }
        }
        </script>
    <style>
	button {
		border: none;
		border-radius: 5px;
		outline: none;
		color: white;
		padding: 5px 40px;
		text-align: center;
		text-decoration: none;
		display: inline-block;
		font-size: 16px;
		margin: 5px 2px;
		cursor: pointer;
		transition-duration: 0.2s;
		background-color: #386fc2;
	}
	
	button.shodan_button {
		background-color: #be473c
	}
	
        button:hover {
            background-color: #305896;
        }

        .shodan_button:hover {
            background-color: #a63c32;
        }
	
    .gallery {
        display: grid;
        grid-template-columns: auto auto auto auto;
	grid-template-rows: auto;
        grid-gap: 10px;
    }
    .gallery img {
        width: 100%;
    }
	.gallery .item {
		position: relative;
		overflow: hidden;
	}
	.gallery .item img {
		vertical-align: middle;
	}
	
	.gallery .caption {
		margin: 0;
		padding: .5em;
		position: absolute;
		z-index: 1;
		bottom: 0;
		left: 0;
		width: 100%;
		max-height: 100%;
		overflow: auto;
		box-sizing: border-box;
		transition: transform 0.2s;
		transform: translateY(100%);
		background: rgba(0, 0, 0, 0.4);
		color:white;
		font-family: Arial;
	}
	
	.gallery .item:hover .caption {
		transform: translateY(0%);
	}

    h1 {
        font-family: Arial;
        color: white;
    }
    label {
        font-family: Courier New;
        color: white;
    }
        div.container{
        display: flex;
        background: linear-gradient(#8f8f8f, #757575);
        padding: 10px;
        border-radius: 17px;
        margin-bottom: 8px;
        margin: 20px;
    }
    div.section {
        flex: auto;
        width: 33%;
    }
    div.thing {
        margin: 5px;
    }

    .stats_table {
        float: right;
        font-family: Courier New;
        color: white;
    }
    </style>
</head>
<body style="background-color:black" onload="changeColumns()">
    <div class="container">
        <div class="section">
            <div class="thing">
                <label for="cols">Columns:</label>
                <select id="cols" onchange="changeColumns()">
                    <option value="2">2</option>
                    <option value="3">3</option>
                    <option value="4" selected="selected">4</option>
                </select>
            </div>'''+ptz_box+'''
        </div>

        <div class="section" style="text-align: center;">
            <h1>'''+self.friendly_name+'''</h1>
        </div>

        <div class="section">
            <table class="stats_table">
                <tr>
                    <td>Query:</td>
                    <td>'''+self.search+'''</td>
                </tr>
                <tr>
                    <td>Count:</td>
                    <td>'''+str(self.success_count)+''' open, '''+str(self.failed_count)+''' closed</td>
                </tr>
                <tr>
                    <td>Ran at:</td>
                    <td>'''+self.start_time_str+'''</td>
                </tr>
            </table>
        </div>

    </div>

    <div class=gallery>
'''

        with open('images.html', 'w') as page:

            page.write(html)
            no_dupes = []
            for h in self.live_hosts:
                if h not in no_dupes:
                    no_dupes.append(h)
            
            for host in no_dupes:

                link = 'http://' + host[1]['ip_str'] + ':' + str(host[1]['port'])
                
                if self.store_offline == True:
                    img_src = host[0]
                    if os.path.getsize(host[0]) <= 1:
                        continue
                else:
                    img_src = link + self.path


                if host[2]:
                    classname = "item ptz"
                else:
                    classname = "item nonptz"

                if self.checkPTZ and self.checkPTZPath:
                    if host[2]:
                        ptz_controls_tr = '''
                  <tr>
                    <td>PTZ Controls:</td>
                    <td style="font-weight: bold;">Authorized</td>
                  </tr>'''
                    else:
                        ptz_controls_tr = '''
                  <tr>
                    <td>PTZ Controls:</td>
                    <td style="font-weight: bold;">Unauthorized</td>
                  </tr>'''
                else:
                    ptz_controls_tr = ""
                    
                data = (classname,
                        img_src,
                        host[1]['ip_str'],
                        host[1]['location']['city'],
                        host[1]['location']['country_name'],
                        host[1]['org'],
                        ptz_controls_tr,
                        link,
                        host[1]['ip_str'])

                element = f'''
                <div class="%s">
			<img src="%s" onerror="this.parentElement.remove()">
			
			<span class="caption">
				
				<table style="margin: auto;color:white;">
				  <tr>
					<td>IP Address:</td>
					<td style="font-weight: bold;">%s</td>
				  </tr>
				  <tr>
					<td>City:</td>
					<td style="font-weight: bold;">%s</td>
				  </tr>
				  <tr>
					<td>Country:</td>
					<td style="font-weight: bold;">%s</td>
				  </tr>
				  <tr>
					<td>Organization:</td>
					<td style="font-weight: bold;">%s</td>
				  </tr>%s
				</table>
				
				<div style="text-align: center;">
				<a href="%s" target="_blank" style="text-decoration: none">
					<button type="submit" class="stream_button">Open stream in new tab</button>
				</a>
                    <a href="https://www.shodan.io/host/%s" target="_blank" style="text-decoration: none">
                        <button class="shodan_button">Shodan Page</button>
                    </a>
				</div>
				
			</span>
		</div>
''' % data

                try:
                    page.write(element)

                except UnicodeEncodeError:
                    if self.verbose:
                        print("[Unicode Error] That was wierd. UnicodeEncodeError for host", host[1]['ip_str'])
                    pass
                        
            page.write('\n\t</div>\n</body>\n</html>')

        if open_on_completion:
            webbrowser.open('images.html')
            
        
    def info(self):

        print('search:', self.search)
        print('path:', self.path)
        print('dirname', self.dirname)
        print('timeout:', self.timeout)
        try:
            print('pages:', len(self.pages))
        except TypeError:
            print('pages:', None)

        print("checkPTZ:", self.checkPTZ)
        print("checkPTZPath:", self.checkPTZPath)


    def stats(self):

        if self.total != 0:
            percent_success = int((self.success_count / self.total) * 100)
            percent_failure = int((self.failed_count / self.total) * 100)
        else:
            percent_success = 0
            percent_failure = 0

        s = "{} out of {} hosts are viewable, {}% success rate".format(self.success_count,self.total,percent_success)
        t = "Time elapsed: " + str(int(self.time_elapsed)) + " seconds"
        u = "{} hosts found with potentially open PTZ controls".format(self.ptz_count)

        return [s,t,u,percent_success,percent_failure]