Update to v0.4.2

This commit is contained in:
Jan Rude
2015-08-21 12:13:52 +02:00
parent 71b1275c86
commit 57af1bb3a2
16 changed files with 6139 additions and 5817 deletions

View File

@@ -1,3 +1,9 @@
## Version 0.4.2
* Extensions installed with core are searched too
* Added new algorithms for Typo3 installation and used path
* Bugfixes
## Version 0.4.1
* Fixed link to socksipy for python 3

View File

@@ -5,5 +5,4 @@
* Stop extension enumeration with ctrl-c
* Privoxy check
* --threads
* --user-agent (default is Mozilla/5.0)
* --header (customize header fields)
* --user-agent (default is Mozilla/5.0)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -59,8 +59,8 @@ rhu_excelexplorer
felib
eim2mvc
cherries
mvwa_fortune
sm_charsethelper
mvwa_fortune
ws_test
survey
masi_utf8fs
@@ -95,15 +95,15 @@ jhe_dam_extender
dbreplace
spriteiconoverview
eventmanagement
ms_fluid
abcconfig
bb_easyforms
abcconfig
ms_fluid
ajax_report
smu_chc_ext
ch_flash_carrousel
tcaobjects_demo
jr_webmail
wsefs
jr_webmail
rhu_csvimport
pb_rsslaufschrift
ch_bramacroofsimulator
@@ -112,20 +112,20 @@ p2_langfix_42
ter_tests
clanbase
meta_openoffice
st_validation_lpl
rhu_events
st_validation_lpl
t3info
ch_bramacproducts
sort_table
bonus
alumnos
organizacionacademica
alumnos
maja_condrequired
bonus
hh_multipageform_example
dsxsyndication
lz_lp_dm_log_fe
ba_company
dsxsyndication
zitatdt
ba_company
svq_ebay
automator
rm_staticfile
@@ -135,8 +135,8 @@ audio_conversion
error
mbbrowserid
wow_raid
rg_usuarios
mf_trainmanagement
rg_usuarios
rg_patrocinio
sp_newsteaserbox_hookexample
redirectlog
@@ -147,30 +147,31 @@ belink_syslang
buildtools
rg_empresas
tc_fbconnect
treppenpfosten_katalog
rf_library
treppenpfosten_katalog
ffunews
dre_besearch
elnews
moox_template_free017
lo_backendhelper
ter_upload_test
ctefan_test
moox_news_geoinfo
moox_news_twitter
ckeditor
air_table
ft3_empty
dbal_utility
og_base
tgm_kickstart
tagger
femanagerextended
boards
simplemvc_helloworld
downloads
ecs_steam
jh_extstatus
jh_pwcomments_plugin
visitorlist
xdbmysql
visitorlist
moox_news_twitter
air_table
moox_news_geoinfo
simplemvc_helloworld
jh_extstatus
ecs_steam
boards
tgm_kickstart
dbal_utility
ft3_empty
moox_template_free017
jh_pwcomments_plugin
ter_upload_test
ckeditor
downloads
og_base
ctefan_test
start
femanagerextended

View File

@@ -30,7 +30,6 @@ newloginbox_tmplable
bzd_staff_directory
cc_devlog
sr_language_detect
multicolumn
rte_chooser
sp_news_catimgs
doc_core_tsref
@@ -46,6 +45,7 @@ cc_metaexif
sr_rtehtmlarea_bluelook
jf_headerslide
doc_core_tsconfig
ml_links
imailframe
kb_cont_slide
cc_metaexec
@@ -124,8 +124,8 @@ csh_hk
csh_br
dubletfinder
prototypejs
wa_contentrenderinghook
hsapp_longerfeusername
wa_contentrenderinghook
de_contentorganizer
danp_skinsupport
alt_forms_field_title
@@ -181,8 +181,8 @@ sp_betterflex
localphpinclude
tm_classes
danp_userlisttemplate
cobweb_protector
tebay
cobweb_protector
rtehtmlarea_definitionlist
yag_theme_perfectlightbox
eco_content
@@ -191,8 +191,8 @@ csh_vn
tm_minijoboffers
paysuite
idaa_fe_utilies
go_maps_ap
mailformplusplus
go_maps_ap
ak_mobile_device
iwbase
eu_correcturls
@@ -235,13 +235,13 @@ mpr
displaycontroller_advanced
smile_form_archive
tagpackprovider
dfluess
doc_core_tca
dfluess
jhe_adventcalender
redirection
sav_library_example5
xliff
maag_imagerotator
xliff
metadata_ts
remote_server
doc_tut_ts45
@@ -251,26 +251,26 @@ st_readmore
mak_randlistnum
extended_sys_note
static_info_tables_ga
advancedform
delete_staticfile_by_3party
ics_errorhandler
advancedform
ods_workspace_mail
ics_errorhandler
extend_dcdgooglemap
tm_gallery
ttnews_href_marker
sav_library_mvc_example0
doc_tut_editors
st_metatags
doc_guide_security
ics_templavoila_mirgation_tool
doc_guide_security
doc_core_skinning
ttnewscacheexpire
form4_contentpagination
realurl_autoconf_autodelete
paymentlib_dibs
paymentlib_quickpay_dk
tgm_gallery
smile_jumpurl_fix
tgm_gallery
tm_cssfilelinks
tsincludeorder
tgmv_gallery
@@ -282,18 +282,19 @@ dialogcentral
dscentral
jb_metaexec_doc
maag_cenoshop
wt_spamshield_formhandler
mm_forum_blog
form4_pages_counter
fluidcontent_fed
form4_filecache
uploadtest
coo_facebook
browser_tut_map_en
filedeletion
coreupdate
attachmentdelete
view
external_link_parameter
browser_manual_ootb_en
form4_faq
filedeletion
wt_spamshield_formhandler
form4_pages_counter
barscheduler
browser_manual_ootb_en
attachmentdelete
browser_tut_map_en
view
mm_forum_blog
coo_facebook
form4_filecache
external_link_parameter
fluidcontent_fed
uploadtest

File diff suppressed because it is too large Load Diff

View File

@@ -26,52 +26,119 @@ from lib.output import Output
class Typo3_Installation:
"""
This class checks, if Typo3 is used on the domain.
This class checks, if Typo3 is used on the domain with different approaches.
If Typo3 is used, a link to the login page is shown.
name: URL of the domain
extensions: Extensions to check for
typo3: If Typo3 is installed
"""
@staticmethod
def run(domain):
login_found = Typo3_Installation.search_login(domain)
if not login_found:
Typo3_Installation.check(domain)
root = Typo3_Installation.check_root(domain)
check_installation = Typo3_Installation.check_installation(domain)
if not root:
default_files = Typo3_Installation.check_default_files(domain)
if not default_files:
typo = Typo3_Installation.check_404(domain)
# Searching for Typo3 references in HTML comments
"""
This method requests the root page
and searches for a specific string in the response.
Usually there are some TYPO3 notes in the HTML comments.
"""
@staticmethod
def check(domain):
response = Request.get_request(domain.get_name(), '/')
Request.interesting_headers(domain, response[1], response[2])
def check_root(domain):
try:
regex = re.compile('[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.[0-9][0-9]?)')
response = Request.get_request(domain.get_name(), '/')
regex = re.compile('[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)')
searchVersion = regex.search(response[0])
version = searchVersion.groups()
version = searchVersion.groups()[0]
domain.set_typo3()
domain.set_typo3_version(version[0].split()[0])
domain.set_typo3_version(version)
return True
except:
return False
"""
This method requests the homepage
and searches for a Typo3 path reference
in order to determine the Typo3 installation path.
"""
@staticmethod
def check_installation(domain):
response = Request.get_request(domain.get_name(), '/')
if not response:
exit(-1)
headers = Request.interesting_headers(response[1], response[2])
for key in headers:
domain.set_interesting_headers(key, headers[key])
try:
path = re.search('(href|src|content)=(.{0,35})(typo3temp/|typo3conf/)', response[0])
if not (path.groups()[1] == '"' or '"../' in path.groups()[1]):
real_path = (path.groups()[1].split('"')[1])
if 'http' in real_path:
domain.set_name(real_path)
else:
domain.set_name(domain.get_name() + real_path)
domain.set_path(real_path)
domain.set_typo3()
return True
except:
return False
"""
This method requests different files, which are generated on installation.
Usually they are not deleted by admins
and can be used as an idicator of a TYPO3 installation.
"""
@staticmethod
def check_default_files(domain):
files = {'/README.md':'[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss]',
'/README.txt':'[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss]',
'/INSTALL.txt':'INSTALLING [Tt][Yy][Pp][Oo]3',
'/typo3_src/LICENSE.txt':'The [Tt][Yy][Pp][Oo]3 licensing conditions'
}
for path, regex in files.items():
try:
regex = re.compile('TYPO3 (\d{1,2}\.\d{1,2}) CMS')
searchHTML = regex.search(response[0])
version = searchHTML.groups()
response = Request.get_request(domain.get_name(), path)
regex = re.compile(regex)
searchInstallation = regex.search(response[0])
installation = searchInstallation.groups()
domain.set_typo3()
domain.set_typo3_version(version[0].split()[0])
return True
except:
return False
pass
return False
# Searching Typo3 login page
"""
This method requests a site which is not available.
TYPO3 installations usually generate a default error page,
which can be used as an indicator.
"""
@staticmethod
def check_404(domain):
domain_name = domain.get_name()
response = Request.get_request((domain_name.split('/')[0] + '//' + domain_name.split('/')[2]), '/idontexist')
try:
regex = re.compile('[Tt][Yy][Pp][Oo]3 CMS')
searchInstallation = regex.search(response[0])
installation = searchInstallation.groups()
domain.set_typo3()
return True
except:
return False
"""
This method requests the default login page
and searches for a specific string in the title or the response.
If the access is forbidden (403), extension search is still possible.
"""
@staticmethod
def search_login(domain):
try:
response = Request.get_request(domain.get_name(), '/typo3/index.php')
Request.interesting_headers(domain, response[1], response[2])
regex = re.compile('<title>(.*)</title>', re.IGNORECASE)
searchTitle = regex.search(response[0])
title = searchTitle.groups()[0]
if 'TYPO3' in title or 'TYPO3 SVN ID:' in response[0]:
if ('TYPO3' in title) or ('TYPO3 CMS' in response[0]) or (response[3] == 403):
domain.set_typo3()
domain.set_login_found()
return True

View File

@@ -24,9 +24,10 @@ class Domain(object):
name: URL of the domain
typo3: If Typo3 is installed
typo3_version: Typo3 Version
login_found: determines of the default login page was found or not
login_found: Determines of the default login page was found or not
extensions: List of extensions to check for
installed_extensions: List of all installed extensions
interesting_header: List of interesting headers
"""
def __init__(self, name, ext_state, top=False):
if not ('http' in name):
@@ -36,6 +37,7 @@ class Domain(object):
self.__typo3 = False
self.__typo3_version = ''
self.__login_found = False
self.__path = '/'
self.__extension_config = [ext_state, top]
self.__extensions = None
self.__installed_extensions = {}
@@ -77,6 +79,12 @@ class Domain(object):
def get_typo3_version(self):
return self.__typo3_version
def set_path(self, path):
self.__path = path
def get_path(self):
return self.__path
def get_login_found(self):
return self.__login_found

View File

@@ -54,6 +54,8 @@ class Extensions:
thread_pool.add_job((Request.head_request, (domain.get_name(), '/typo3conf/ext/' + ext)))
# search global installation path
thread_pool.add_job((Request.head_request, (domain.get_name(), '/typo3/ext/' + ext)))
# search extensions shipped with core
thread_pool.add_job((Request.head_request, (domain.get_name(), '/typo3/sysext/' + ext)))
thread_pool.start(6)
for installed_extension in thread_pool.get_result():

View File

@@ -29,21 +29,28 @@ class Output:
def typo3_installation(domain):
print('')
print('[+] Typo3 default login:'.ljust(30) + Fore.GREEN + domain.get_name() + '/typo3/index.php' + Fore.RESET)
if domain.get_login_found():
if domain.get_name().endswith('/'):
print('[+] Typo3 backend login:'.ljust(30) + Fore.GREEN + domain.get_name() + 'typo3/index.php' + Fore.RESET)
else:
print('[+] Typo3 backend login:'.ljust(30) + Fore.GREEN + domain.get_name() + '/typo3/index.php' + Fore.RESET)
else:
print('[+] Typo3 backend login:'.ljust(30) + Fore.RED + 'not found' + Fore.RESET)
print('[+] Typo3 version:'.ljust(30) + Fore.GREEN + domain.get_typo3_version() + Fore.RESET)
print(' | known vulnerabilities:'.ljust(30) + Fore.GREEN + 'http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=' + domain.get_typo3_version() + Fore.RESET)
if (domain.get_typo3_version() != 'could not be determined'):
print(' | known vulnerabilities:'.ljust(30) + Fore.GREEN + 'http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=' + domain.get_typo3_version() + Fore.RESET)
print('')
def interesting_headers(name, value):
string = '[!] ' + name + ':'
print(string.ljust(30) + value)
def extension_output(extens):
def extension_output(path, extens):
if not extens:
print(Fore.RED + ' | No extension found' + Fore.RESET)
else:
for extension in extens:
print(Fore.BLUE + '\n[+] Name: ' + extension.split('/')[3] + '\n' + "-"* 25 + Fore.RESET)
print(' | Location:'.ljust(16) + extension)
print(Fore.BLUE + '\n[+] Name: ' + extension.split('/')[3] + '\n' + "-"* 31 + Fore.RESET)
print(' | Location:'.ljust(16) + path + extension[1:])
if not (extens[extension] == False):
print(' | ' + extens[extension].split('.')[0] + ':'.ljust(4) + (extension + '/'+ extens[extension]))
print(' | ' + extens[extension].split('.')[0] + ':'.ljust(4) + (path + extension[1:] + '/'+ extens[extension]))

View File

@@ -44,7 +44,7 @@ class Request:
except requests.exceptions.Timeout:
print(Fore.RED + '[x] Connection timed out' + Fore.RESET)
except requests.exceptions.ConnectionError as e:
print(Fore.RED + '[x] Connection aborted.\n Please make sure you provided the right URL' + Fore.RESET)
print(Fore.RED + '[x] Connection error\n | Please make sure you provided the right URL' + Fore.RESET)
except requests.exceptions.RequestException as e:
print(Fore.RED + str(e) + Fore.RESET)
@@ -65,32 +65,36 @@ class Request:
print(Fore.RED + str(e) + Fore.RESET)
@staticmethod
def interesting_headers(domain, headers, cookies):
def interesting_headers(headers, cookies):
found_headers = {}
for header in headers:
if header == 'server':
domain.set_interesting_headers('Server', headers.get('server'))
found_headers['Server'] = headers.get('server')
elif header == 'x-powered-by':
domain.set_interesting_headers('X-Powered-By', headers.get('x-powered-by'))
found_headers['X-Powered-By'] = headers.get('x-powered-by')
elif header == 'via':
domain.set_interesting_headers('Via', headers.get('via'))
found_headers['Via'] = headers.get('via')
try:
typo_cookie = cookies['be_typo_user']
domain.set_interesting_headers('be_typo_user',typo_cookie)
found_headers['be_typo_user'] = typo_cookie
except:
pass
try:
typo_cookie = cookies['fe_typo_user']
domain.set_interesting_headers('fe_typo_user', typo_cookie)
found_headers['fe_typo_user'] = typo_cookie
except:
pass
return found_headers
@staticmethod
# not used atm because unreliable
def version_information(domain_name, path, regex):
r = requests.get(domain_name + path, stream=True, timeout=timeout, headers=header, verify=False)
if r.status_code == 200:
for content in r.iter_content(chunk_size=400, decode_unicode=False):
regex = re.compile(regex)
search = regex.search(str(content))
version = search.groups()[0]
return version
try:
for content in r.iter_content(chunk_size=400, decode_unicode=False):
regex = re.compile(regex)
search = regex.search(str(content))
version = search.groups()[0]
return version
except:
return None

View File

@@ -30,13 +30,14 @@ class Update:
unpack it and sort the extensions in different files
"""
def __init__(self):
print('')
self.download_ext()
self.generate_list()
# Progressbar
def dlProgress(self, count, blockSize, totalSize):
percent = int(count*blockSize*100/totalSize)
sys.stdout.write("\r[+] Downloading extentions: " + "%d%%" % percent)
sys.stdout.write('\r[+] Downloading extentions: ' + '%d%%' % percent)
sys.stdout.flush()
# Download extensions from typo3 repository
@@ -46,7 +47,7 @@ class Update:
with gzip.open('extensions.gz', 'rb') as f:
file_content = f.read()
f.close()
outF = open("extensions.xml", 'wb')
outF = open('extensions.xml', 'wb')
outF.write(file_content)
outF.close()
os.remove('extensions.gz')
@@ -62,7 +63,7 @@ class Update:
outdated = {} # 'obsolete' and 'outdated'
allExt = {}
print ("\n[+] Parsing file...")
print ('\n[+] Parsing file...')
tree = ElementTree.parse('extensions.xml')
root = tree.getroot()
extension = 0
@@ -89,7 +90,7 @@ class Update:
extension+=1
# sorting lists according to number of downloads
print ("[+] Sorting according to number of downloads...")
print ('[+] Sorting according to number of downloads...')
sorted_experimental = sorted(experimental.items(), key=lambda x: int(x[1]), reverse=True)
sorted_alpha = sorted(alpha.items(), key=lambda x: int(x[1]), reverse=True)
sorted_beta = sorted(beta.items(), key=lambda x: int(x[1]), reverse=True)
@@ -97,7 +98,7 @@ class Update:
sorted_outdated = sorted(outdated.items(), key=lambda x: int(x[1]), reverse=True)
sorted_allExt = sorted(allExt.items(), key=lambda x: int(x[1]), reverse=True)
print ("[+] Generating files...")
print ('[+] Generating files...')
f = open(os.path.join('extensions', 'experimental_extensions'),'w')
for i in range(0,len(sorted_experimental)):
f.write(sorted_experimental[i][0]+'\n')
@@ -128,5 +129,5 @@ class Update:
f.write(sorted_allExt[i][0]+'\n')
f.close()
print ('[+] Loaded', len(sorted_allExt), 'extensions\n')
print ('[+] Loaded', len(sorted_allExt), 'extensions')
os.remove('extensions.xml')

View File

@@ -19,32 +19,41 @@
#-------------------------------------------------------------------------------
import re
import time
from lib.request import Request
class VersionInformation:
"""
This class will search for version information
This class will search for version information.
The exact version can be found in the ChangeLog,
less specific version information can be found in the NEWS or INSTALL file.
"""
def search_typo3_version(self, domain):
ChangeLog = {'/typo3_src/ChangeLog':'(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)',
'/ChangeLog':'(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)'
changelog = {'/typo3_src/ChangeLog':'[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)',
'/ChangeLog':'[Tt][Yy][Pp][Oo]3 (\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?)'
}
News = {'/typo3_src/NEWS.txt':'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})',
'/typo3_src/NEWS.md':"TYPO3 CMS (\d{1,2}\.\d{1,2}) - WHAT'S NEW",
news = {'/typo3_src/NEWS.txt':'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})',
'/typo3_src/NEWS.md':'[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss] (\d{1,2}\.\d{1,2}) - WHAT\'S NEW',
'/NEWS.txt':'http://wiki.typo3.org/TYPO3_(\d{1,2}\.\d{1,2})',
'/NEWS.md':"TYPO3 CMS (\d{1,2}\.\d{1,2}) - WHAT'S NEW"
'/NEWS.md':'[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss] (\d{1,2}\.\d{1,2}) - WHAT\'S NEW',
'/INSTALL.md':'[Tt][Yy][Pp][Oo]3 [Cc][Mm][Ss] (\d{1,2}\.\d{1,2}) [Ll][Tt][Ss]'
}
version = 'could not determine'
for path, regex in ChangeLog.items():
version = 'could not be determined'
for path, regex in changelog.items():
response = Request.version_information(domain.get_name(), path, regex)
if not(response is None):
if not (response is None):
version = response
break
if version == 'could not determine':
for path, regex in News.items():
domain.set_typo3_version(version)
return True
if version == 'could not be determined':
for path, regex in news.items():
response = Request.version_information(domain.get_name(), path, regex)
if not(response is None):
version = response
break
if not (response is None):
if len(response) > len(domain.get_typo3_version()):
domain.set_typo3_version(version)
return True
domain.set_typo3_version(version)

View File

@@ -18,10 +18,10 @@
# along with this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/)
#-------------------------------------------------------------------------------
__version__ = "0.4.1"
__program__ = "Typo-Enumerator"
__version__ = '0.4.2'
__program__ = 'Typo-Enumerator'
__description__ = 'Automatic Typo3 enumeration tool'
__author__ = "https://github.com/whoot"
__author__ = 'https://github.com/whoot'
import sys
import os.path
@@ -55,9 +55,9 @@ class Typo3:
anonGroup.add_argument('--tp', help='using TOR and Privoxy for connections', action='store_true')
parser.add_argument('-p', '--port', dest='port', help='Port for TOR/Privoxy (default: 9050/8118)', type=int)
parser.add_argument( "-h", "--help", action="help")
parser.add_argument( '-h', '--help', action='help')
args = parser.parse_args()
force = 'n'
try:
if args.update:
Update()
@@ -95,27 +95,31 @@ class Typo3:
self.__domain_list.append(Domain(dom, args.ext_state, args.top))
elif args.file:
if not os.path.isfile(args.file):
print(Fore.RED + "\n[x] File not found: " + args.file + "\n | Aborting..." + Fore.RESET)
sys.exit(-2)
print(Fore.RED + '\n[x] File not found: ' + args.file + '\n | Aborting...' + Fore.RESET)
sys.exit(-1)
else:
with open(args.file, 'r') as f:
for line in f:
self.__domain_list.append(Domain(line.strip('\n'), args.ext_state, args.top))
for domain in self.__domain_list:
print('\n\n' + Fore.CYAN + Style.BRIGHT + '[ Checking ' + domain.get_name() + ' ]' + '\n' + "-"* 73 + Fore.RESET + Style.RESET_ALL)
print('\n\n' + Fore.CYAN + Style.BRIGHT + '[ Checking ' + domain.get_name() + ' ]' + '\n' + '-'* 73 + Fore.RESET + Style.RESET_ALL)
Typo3_Installation.run(domain)
for key, value in domain.get_interesting_headers().items():
Output.interesting_headers(key, value)
if not domain.get_typo3():
print(Fore.RED + '\n[x] Typo3 is not used on this domain' + Fore.RESET)
print(Fore.RED + '\n[x] It seems that Typo3 is not used on this domain' + Fore.RESET)
else:
if domain.get_login_found():
# search version info
if len(domain.get_typo3_version()) <= 3:
version = VersionInformation()
version.search_typo3_version(domain)
login = Typo3_Installation.search_login(domain)
Output.typo3_installation(domain)
if not domain.get_login_found():
print(Fore.YELLOW + '[!] Backend login not found\n | Extension enumeration would be failing\n | Skipping...' + Fore.RESET)
if not login:
print(Fore.YELLOW + '[!] Backend login not found')
print(' | Extension search would fail')
print(' | Skipping...')
print(Fore.RESET)
else:
# Loading extensions
if (self.__extensions is None):
@@ -128,19 +132,20 @@ class Typo3:
print ('\n[ Searching', len(self.__extensions), 'extensions ]')
ext.search_extension(domain, self.__extensions)
ext.search_ext_version(domain, domain.get_installed_extensions())
Output.extension_output(domain.get_installed_extensions())
Output.extension_output(domain.get_path(), domain.get_installed_extensions())
except KeyboardInterrupt:
print("\nReceived keyboard interrupt.\nQuitting...")
print('\nReceived keyboard interrupt.\nQuitting...')
exit(-1)
finally:
deinit()
now = datetime.datetime.now()
print('\n\n' + __program__ + ' finished at ' + now.strftime("%Y-%m-%d %H:%M:%S") + '\n')
print('\n\n' + __program__ + ' finished at ' + now.strftime('%Y-%m-%d %H:%M:%S') + '\n')
if __name__ == "__main__":
if __name__ == '__main__':
print('\n' + 73*'=' + Style.BRIGHT)
print(Fore.BLUE + ' _______ ______ '.center(73))
print(Fore.BLUE)
print(' _______ ______ '.center(73))
print('|_ _|.--.--.-----.-----.|__ |'.center(73))
print(' | | | | | _ | _ ||__ |'.center(73))
print(' |___| |___ | __|_____||______|'.center(73))