@@ -49,6 +49,12 @@ COLORIZE_FIELDS = {
4949# Make "-h" work like "--help"
5050CONTEXT_SETTINGS = dict (help_option_names = ['-h' , '--help' ])
5151
52+ # Define a basestring type if necessary for Python3 compatibility
53+ try :
54+ basestring
55+ except NameError :
56+ basestring = str
57+
5258# Utility methods
5359def get_api_key ():
5460 shodan_dir = os .path .expanduser (SHODAN_CONFIG_DIR )
@@ -60,7 +66,7 @@ def get_api_key():
6066 raise click .ClickException ('Please run "shodan init <api key>" before using this command' )
6167
6268 # Make sure it is a read-only file
63- os .chmod (keyfile , 0600 )
69+ os .chmod (keyfile , 0o600 )
6470
6571 with open (keyfile , 'r' ) as fin :
6672 return fin .read ().strip ()
@@ -74,7 +80,10 @@ def get_ip(banner):
7480
7581
7682def escape_data (args ):
77- return args .encode ('ascii' , 'replace' ).replace ('\n ' , '\\ n' ).replace ('\r ' , '\\ r' ).replace ('\t ' , '\\ t' )
83+ # Ensure the provided string isn't unicode data
84+ if not isinstance (args , str ):
85+ args = args .encode ('ascii' , 'replace' )
86+ return args .replace ('\n ' , '\\ n' ).replace ('\r ' , '\\ r' ).replace ('\t ' , '\\ t' )
7887
7988def timestr ():
8089 return datetime .datetime .utcnow ().strftime ('%Y-%m-%d' )
@@ -133,6 +142,10 @@ def match_filters(banner, filters):
133142
134143 return True
135144
145+ def write_banner (fout , banner ):
146+ line = simplejson .dumps (banner ) + '\n '
147+ fout .write (line .encode ('utf-8' ))
148+
136149
137150@click .group (context_settings = CONTEXT_SETTINGS )
138151def main ():
@@ -242,8 +255,8 @@ def convert(input, format):
242255 placemark += '<Point><coordinates>{},{}</coordinates></Point>' .format (lon , lat )
243256 placemark += '</Placemark>'
244257
245- fout .write (placemark )
246- except Exception , e :
258+ fout .write (placemark . encode ( 'utf-8' ) )
259+ except Exception as e :
247260 pass
248261
249262 # Get the basename for the input file
@@ -274,7 +287,7 @@ def convert(input, format):
274287
275288 hosts [ip ]['ports' ].append (banner ['port' ])
276289
277- for ip , host in hosts .iteritems ( ):
290+ for ip , host in iter ( hosts .items () ):
278291 kml_writer (fout , host )
279292
280293 fout .write (KML_FOOTER )
@@ -302,7 +315,7 @@ def init(key):
302315 try :
303316 api = shodan .Shodan (key )
304317 test = api .info ()
305- except shodan .APIError , e :
318+ except shodan .APIError as e :
306319 raise click .ClickException ('Invalid API key' )
307320
308321 # Store the API key in the user's directory
@@ -311,7 +324,7 @@ def init(key):
311324 fout .write (key .strip ())
312325 click .echo (click .style ('Successfully initialized' , fg = 'green' ))
313326
314- os .chmod (keyfile , 0600 )
327+ os .chmod (keyfile , 0o600 )
315328
316329
317330@main .group ()
@@ -331,7 +344,7 @@ def alert_clear():
331344 for alert in alerts :
332345 click .echo ('Removing {} ({})' .format (alert ['name' ], alert ['id' ]))
333346 api .delete_alert (alert ['id' ])
334- except shodan .APIError , e :
347+ except shodan .APIError as e :
335348 raise click .ClickException (e .value )
336349 click .echo ("Alerts deleted" )
337350
@@ -345,7 +358,7 @@ def alert_list(expired):
345358 api = shodan .Shodan (key )
346359 try :
347360 results = api .alerts (include_expired = expired )
348- except shodan .APIError , e :
361+ except shodan .APIError as e :
349362 raise click .ClickException (e .value )
350363
351364 if len (results ) > 0 :
@@ -379,7 +392,7 @@ def alert_remove(alert_id):
379392 api = shodan .Shodan (key )
380393 try :
381394 results = api .delete_alert (alert_id )
382- except shodan .APIError , e :
395+ except shodan .APIError as e :
383396 raise click .ClickException (e .value )
384397 click .echo ("Alert deleted" )
385398
@@ -401,7 +414,7 @@ def count(query):
401414 api = shodan .Shodan (key )
402415 try :
403416 results = api .count (query )
404- except shodan .APIError , e :
417+ except shodan .APIError as e :
405418 raise click .ClickException (e .value )
406419
407420 click .echo (results ['total' ])
@@ -458,7 +471,7 @@ def download(limit, filename, query):
458471 cursor = api .search_cursor (query )
459472 with click .progressbar (cursor , length = limit ) as bar :
460473 for banner in bar :
461- fout . write ( simplejson . dumps ( banner ) + ' \n ' )
474+ write_banner ( fout , banner )
462475 count += 1
463476
464477 if count >= limit :
@@ -544,7 +557,7 @@ def host(format, ip):
544557 click .echo ('\t \t {:15s}{}\n \t \t {:15s}{}' .format ('Bits:' , banner ['ssl' ]['dhparams' ]['bits' ], 'Generator:' , banner ['ssl' ]['dhparams' ]['generator' ]))
545558 if 'fingerprint' in banner ['ssl' ]['dhparams' ]:
546559 click .echo ('\t \t {:15s}{}' .format ('Fingerprint:' , banner ['ssl' ]['dhparams' ]['fingerprint' ]))
547- except shodan .APIError , e :
560+ except shodan .APIError as e :
548561 raise click .ClickException (e .value )
549562
550563
@@ -555,7 +568,7 @@ def info():
555568 api = shodan .Shodan (key )
556569 try :
557570 results = api .info ()
558- except shodan .APIError , e :
571+ except shodan .APIError as e :
559572 raise click .ClickException (e .value )
560573
561574 click .echo ("""Query credits available: {0}
@@ -602,7 +615,7 @@ def parse(color, fields, filters, filename, separator, filenames):
602615
603616 # Append the data
604617 if fout :
605- fout . write ( simplejson . dumps ( banner ) + ' \n ' )
618+ write_banner ( fout , banner )
606619
607620 # Loop over all the fields and print the banner as a row
608621 for field in fields :
@@ -638,7 +651,7 @@ def myip():
638651 api = shodan .Shodan (key )
639652 try :
640653 click .echo (api .tools .myip ())
641- except shodan .APIError , e :
654+ except shodan .APIError as e :
642655 raise click .ClickException (e .value )
643656
644657
@@ -685,7 +698,7 @@ def scan_internet(quiet, port, protocol):
685698 try :
686699 for banner in api .stream .ports ([port ], timeout = 30 ):
687700 counter += 1
688- fout . write ( simplejson . dumps ( banner ) + ' \n ' )
701+ write_banner ( fout , banner )
689702
690703 if not quiet :
691704 click .echo ('{0:<40} {1:<20} {2}' .format (
@@ -694,7 +707,7 @@ def scan_internet(quiet, port, protocol):
694707 ';' .join (banner ['hostnames' ])
695708 )
696709 )
697- except shodan .APIError , e :
710+ except shodan .APIError as e :
698711 # We stop waiting for results if the scan has been processed by the crawlers and
699712 # there haven't been new results in a while
700713 if done :
@@ -703,7 +716,7 @@ def scan_internet(quiet, port, protocol):
703716 scan = api .scan_status (scan ['id' ])
704717 if scan ['status' ] == 'DONE' :
705718 done = True
706- except socket .timeout , e :
719+ except socket .timeout as e :
707720 # We stop waiting for results if the scan has been processed by the crawlers and
708721 # there haven't been new results in a while
709722 if done :
@@ -712,10 +725,10 @@ def scan_internet(quiet, port, protocol):
712725 scan = api .scan_status (scan ['id' ])
713726 if scan ['status' ] == 'DONE' :
714727 done = True
715- except Exception , e :
728+ except Exception as e :
716729 raise click .ClickException (repr (e ))
717730 click .echo ('Scan finished: {0} devices found' .format (counter ))
718- except shodan .APIError , e :
731+ except shodan .APIError as e :
719732 raise click .ClickException (e .value )
720733
721734
@@ -727,9 +740,9 @@ def scan_protocols():
727740 try :
728741 protocols = api .protocols ()
729742
730- for name , description in protocols .iteritems ( ):
743+ for name , description in iter ( protocols .items () ):
731744 click .echo (click .style ('{0:<30}' .format (name ), fg = 'cyan' ) + description )
732- except shodan .APIError , e :
745+ except shodan .APIError as e :
733746 raise click .ClickException (e .value )
734747
735748
@@ -799,7 +812,7 @@ def scan_submit(wait, filename, netblocks):
799812 done = True
800813 break
801814
802- except shodan .APIError , e :
815+ except shodan .APIError as e :
803816 # If the connection timed out before the timeout, that means the streaming server
804817 # that the user tried to reach is down. In that case, lets wait briefly and try
805818 # to connect again!
@@ -814,15 +827,16 @@ def scan_submit(wait, filename, netblocks):
814827 scan = api .scan_status (scan ['id' ])
815828 if scan ['status' ] == 'DONE' :
816829 done = True
817- except socket .timeout , e :
830+ except socket .timeout as e :
818831 # If the connection timed out before the timeout, that means the streaming server
819832 # that the user tried to reach is down. In that case, lets wait a second and try
820833 # to connect again!
821834 if (time .time () - scan_start ) < wait :
822835 continue
823836
824837 done = True
825- except Exception , e :
838+ except Exception as e :
839+ raise
826840 finished_event .set ()
827841 progress_bar_thread .join ()
828842 raise click .ClickException (repr (e ))
@@ -862,7 +876,7 @@ def scan_submit(wait, filename, netblocks):
862876 click .echo ('\b ' )
863877
864878 for ip in sorted (hosts ):
865- host = hosts [ip ].items ()[ 0 ] [1 ]
879+ host = next ( iter ( hosts [ip ].items ())) [1 ]
866880
867881 click .echo (click .style (ip , fg = 'cyan' ), nl = False )
868882 if 'hostnames' in host and host ['hostnames' ]:
@@ -906,13 +920,13 @@ def scan_submit(wait, filename, netblocks):
906920
907921 # Save the banner in a file if necessary
908922 if fout :
909- fout . write ( simplejson . dumps ( hosts [ip ][port ]) + ' \n ' )
923+ write_banner ( fout , hosts [ip ][port ])
910924
911925 click .echo ('' )
912926 else :
913927 # Prepend a \b to remove the spinner
914928 click .echo ('\b No open ports found or the host has been recently crawled and cant get scanned again so soon.' )
915- except shodan .APIError , e :
929+ except shodan .APIError as e :
916930 raise click .ClickException (e .value )
917931 finally :
918932 # Remove any alert
@@ -951,7 +965,7 @@ def search(color, fields, limit, separator, query):
951965 api = shodan .Shodan (key )
952966 try :
953967 results = api .search (query , limit = limit )
954- except shodan .APIError , e :
968+ except shodan .APIError as e :
955969 raise click .ClickException (e .value )
956970
957971 # We buffer the entire output so we can use click's pager functionality
@@ -1011,7 +1025,7 @@ def stats(limit, facets, query):
10111025 api = shodan .Shodan (key )
10121026 try :
10131027 results = api .count (query , facets = facets )
1014- except shodan .APIError , e :
1028+ except shodan .APIError as e :
10151029 raise click .ClickException (e .value )
10161030
10171031 # Print the stats tables
@@ -1089,7 +1103,7 @@ def stream(color, fields, separator, limit, datadir, ports, quiet, timeout, stre
10891103 last_time = cur_time
10901104 fout .close ()
10911105 fout = open_file (datadir , last_time )
1092- fout . write ( simplejson . dumps ( banner ) + ' \n ' )
1106+ write_banner ( fout , banner )
10931107
10941108 # Print the banner information to stdout
10951109 if not quiet :
@@ -1132,7 +1146,7 @@ def stream(color, fields, separator, limit, datadir, ports, quiet, timeout, stre
11321146def async_spinner (finished ):
11331147 spinner = itertools .cycle (['-' , '/' , '|' , '\\ ' ])
11341148 while not finished .is_set ():
1135- sys .stdout .write ('\b {}' .format (spinner . next ()))
1149+ sys .stdout .write ('\b {}' .format (next (spinner )))
11361150 sys .stdout .flush ()
11371151 finished .wait (0.2 )
11381152
0 commit comments