Update to v0.3.3
This commit is contained in:
@@ -19,15 +19,17 @@ Typo-Enumerator works with [Python](http://www.python.org/download/) version **2
|
|||||||
|
|
||||||
On Windows you might need to install following packages:
|
On Windows you might need to install following packages:
|
||||||
|
|
||||||
* [Colorama](https://pypi.python.org/pypi/colorama)
|
|
||||||
* [Requests](https://pypi.python.org/pypi/requests/2.3.0)
|
* [Requests](https://pypi.python.org/pypi/requests/2.3.0)
|
||||||
|
|
||||||
On Redhat you can install all needed packages with easy_install:
|
On Redhat you can install all needed packages with easy_install:
|
||||||
|
|
||||||
easy_install argparse
|
easy_install argparse
|
||||||
easy_install colorama
|
|
||||||
easy_install requests
|
easy_install requests
|
||||||
|
|
||||||
|
To make it look pretty, you may want to install colorama:
|
||||||
|
|
||||||
|
* [Colorama](https://pypi.python.org/pypi/colorama)
|
||||||
|
|
||||||
If you want to use Typo-Enumerator with TOR, you need the [SocksiPy](http://socksipy.sourceforge.net/) module.
|
If you want to use Typo-Enumerator with TOR, you need the [SocksiPy](http://socksipy.sourceforge.net/) module.
|
||||||
On Debian/Ubuntu you can install it with apt-get:
|
On Debian/Ubuntu you can install it with apt-get:
|
||||||
|
|
||||||
@@ -63,4 +65,4 @@ Links
|
|||||||
* Download: [.tar.gz](https://github.com/whoot/Typo-Enumerator/tarball/master) or [.zip](https://github.com/whoot/Typo-Enumerator/archive/master)
|
* Download: [.tar.gz](https://github.com/whoot/Typo-Enumerator/tarball/master) or [.zip](https://github.com/whoot/Typo-Enumerator/archive/master)
|
||||||
* Changelog: [Here](https://github.com/whoot/Typo-Enumerator/blob/master/doc/CHANGELOG.md)
|
* Changelog: [Here](https://github.com/whoot/Typo-Enumerator/blob/master/doc/CHANGELOG.md)
|
||||||
* TODO: [Here](https://github.com/whoot/Typo-Enumerator/blob/master/doc/TODO.md)
|
* TODO: [Here](https://github.com/whoot/Typo-Enumerator/blob/master/doc/TODO.md)
|
||||||
* Issue tracker: https://github.com/whoot/Typo-Enumerator/issues
|
* Issue tracker: https://github.com/whoot/Typo-Enumerator/issues
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
## Version 0.3.3
|
||||||
|
|
||||||
|
* Extensions are now saved into different files, separated by state (experimental | alpha | beta | stable | outdated | all). This makes it possible to check more specific ones.
|
||||||
|
* Colorama is only used if installed. It doesn't need to be installed anymore.
|
||||||
|
* Installed extensions are shown immediately
|
||||||
|
|
||||||
## Version 0.3.2
|
## Version 0.3.2
|
||||||
|
|
||||||
* Added support for Windows and Red Hat systems
|
* Added support for Windows and Red Hat systems
|
||||||
@@ -8,7 +14,7 @@
|
|||||||
* Domains must be specified with 'http://' or 'https://' (for example: https://127.0.0.1).
|
* Domains must be specified with 'http://' or 'https://' (for example: https://127.0.0.1).
|
||||||
* Login page redirections can be followed or not.
|
* Login page redirections can be followed or not.
|
||||||
* Fixed the 'all extensions are installed' bug in the summary when using verbose mode.
|
* Fixed the 'all extensions are installed' bug in the summary when using verbose mode.
|
||||||
* Set sleep between threads to 0.5, to fix time out errors when checking a huge amount of extensions.
|
* Set sleep between threads to 0.5 to fix time out errors when checking a huge amount of extensions.
|
||||||
|
|
||||||
## Version 0.3
|
## Version 0.3
|
||||||
|
|
||||||
@@ -68,4 +74,4 @@
|
|||||||
|
|
||||||
## Version 0.1
|
## Version 0.1
|
||||||
|
|
||||||
* Prototype
|
* Prototype
|
||||||
@@ -5,4 +5,5 @@
|
|||||||
* Some extensions don't have any version information. These extensions must be listed in settings.NO_VERSIONINFO.
|
* Some extensions don't have any version information. These extensions must be listed in settings.NO_VERSIONINFO.
|
||||||
* Maybe use one library for all requests
|
* Maybe use one library for all requests
|
||||||
* Add screenshot
|
* Add screenshot
|
||||||
* Give the possibility to use POST instead of GET requests
|
|
||||||
|
* verbose gruscht gscheid behandeln
|
||||||
6414
extensions
6414
extensions
File diff suppressed because it is too large
Load Diff
@@ -5,27 +5,37 @@ Copyright (c) 2014 Jan Rude
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib2
|
import urllib2
|
||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
from colorama import Fore
|
try:
|
||||||
|
from colorama import Fore
|
||||||
|
except:
|
||||||
|
pass
|
||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
from threading import Thread, Lock
|
from threading import Thread, Lock
|
||||||
from lib import settings
|
from lib import settings
|
||||||
|
|
||||||
def generate_list():
|
def generate_list():
|
||||||
if not isfile('extensions'):
|
print ''
|
||||||
print(Fore.RED + "\nExtensionfile not found!\nPlease update Typo-Enumerator (python typoenum.py -u)" + Fore.RESET)
|
for ext_file in settings.EXTENSION_FILE:
|
||||||
sys.exit(-2)
|
if not isfile(os.path.join('extensions', ext_file)):
|
||||||
with open('extensions', 'r') as f:
|
output("Could not find extension file " + ext_file
|
||||||
count = 0
|
+ "\nPossible values are: experimental | alpha | beta | stable | outdated | all", False)
|
||||||
for extension in f:
|
sys.exit(-1)
|
||||||
if settings.TOP_EXTENSION > count:
|
|
||||||
settings.EXTENSION_LIST.append(extension.split('\n')[0])
|
with open(os.path.join('extensions', ext_file), 'r') as f:
|
||||||
count += 1
|
count = 0
|
||||||
else:
|
print "[+] Loading:", ext_file
|
||||||
f.close()
|
for extension in f:
|
||||||
return
|
if settings.TOP_EXTENSION > count:
|
||||||
|
settings.EXTENSION_LIST.append(extension.split('\n')[0])
|
||||||
|
count += 1
|
||||||
|
else:
|
||||||
|
f.close()
|
||||||
|
return
|
||||||
|
|
||||||
def copy():
|
def copy():
|
||||||
for extension in settings.EXTENSION_LIST:
|
for extension in settings.EXTENSION_LIST:
|
||||||
@@ -56,17 +66,18 @@ def check_extension():
|
|||||||
# settings.in_queue.put(extension)
|
# settings.in_queue.put(extension)
|
||||||
# if extension is not in any given path, it's not installed
|
# if extension is not in any given path, it's not installed
|
||||||
if settings.verbose:
|
if settings.verbose:
|
||||||
settings.out_queue.put(extension.ljust(32) + Fore.RED + 'not installed' + Fore.RESET)
|
output(extension.ljust(32) + 'not installed', False)
|
||||||
settings.in_queue.task_done()
|
settings.in_queue.task_done()
|
||||||
|
|
||||||
# Searching version of installed extension
|
# Searching version of installed extension
|
||||||
def check_extension_version(path, extension):
|
def check_extension_version(path, extension):
|
||||||
|
settings.EXTENSIONS_FOUND += 1
|
||||||
# if no version information is available, skip version search
|
# if no version information is available, skip version search
|
||||||
if extension in settings.NO_VERSIONINFO:
|
if extension in settings.NO_VERSIONINFO:
|
||||||
if settings.verbose:
|
if settings.verbose:
|
||||||
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + 'installed' + Fore.RESET + ' (no version information available)')
|
output(extension.ljust(32) + 'installed (no version information available)', True)
|
||||||
else:
|
else:
|
||||||
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + 'installed' + Fore.RESET)
|
output(extension.ljust(32) + 'installed', True)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
request = urllib2.Request(settings.DOMAIN + path + extension +'/ChangeLog', None, settings.user_agent)
|
request = urllib2.Request(settings.DOMAIN + path + extension +'/ChangeLog', None, settings.user_agent)
|
||||||
@@ -77,17 +88,26 @@ def check_extension_version(path, extension):
|
|||||||
regex = re.compile("(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?[' '\n])")
|
regex = re.compile("(\d{1,2}\.\d{1,2}\.?[0-9]?[0-9]?[' '\n])")
|
||||||
searchVersion = regex.search(changelog)
|
searchVersion = regex.search(changelog)
|
||||||
version = searchVersion.groups()
|
version = searchVersion.groups()
|
||||||
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + 'installed (v' + version[0].split()[0] + ')' + Fore.RESET)
|
output(extension.ljust(32) + 'installed (v' + version[0].split()[0] + ')', True)
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
regex = re.compile("(\d{2,4}[\.\-]\d{1,2}[\.\-]\d{1,4})")
|
regex = re.compile("(\d{2,4}[\.\-]\d{1,2}[\.\-]\d{1,4})")
|
||||||
search = regex.search(changelog)
|
search = regex.search(changelog)
|
||||||
version = search.groups()
|
version = search.groups()
|
||||||
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + 'installed (last entry from ' + version[0] + ')' + Fore.RESET)
|
output(extension.ljust(32) + 'installed (last entry from ' + version[0] + ')', True)
|
||||||
except:
|
except:
|
||||||
if settings.verbose:
|
if settings.verbose:
|
||||||
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET + " (no version information found)")
|
output(extension.ljust(32) + 'installed (no version information found)', True)
|
||||||
else:
|
else:
|
||||||
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET)
|
output(extension.ljust(32) + 'installed', True)
|
||||||
except:
|
except:
|
||||||
settings.out_queue.put(extension.ljust(32) + Fore.GREEN + "installed" + Fore.RESET)
|
output(extension.ljust(32) + "installed", True)
|
||||||
|
|
||||||
|
def output(message, status):
|
||||||
|
if settings.COLORAMA:
|
||||||
|
if status:
|
||||||
|
print Fore.GREEN + message + Fore.RESET
|
||||||
|
else:
|
||||||
|
print Fore.RED + message + Fore.RESET
|
||||||
|
else:
|
||||||
|
print message
|
||||||
22
lib/login.py
22
lib/login.py
@@ -40,11 +40,11 @@ def search_login():
|
|||||||
else:
|
else:
|
||||||
print 'Oops! Got unhandled code:'.ljust(32) + str(statusCode) + ': ' + str(r.raise_for_status())
|
print 'Oops! Got unhandled code:'.ljust(32) + str(statusCode) + ': ' + str(r.raise_for_status())
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
print Fore.RED + 'Connection timed out' + Fore.RESET
|
output('Connection timed out')
|
||||||
except requests.exceptions.TooManyRedirects:
|
except requests.exceptions.TooManyRedirects:
|
||||||
print Fore.RED + 'Too many redirects' + Fore.RESET
|
output('Too many redirects')
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
print Fore.RED + str(e) + Fore.RESET
|
output(str(e))
|
||||||
|
|
||||||
# Searching for Typo3 references in title
|
# Searching for Typo3 references in title
|
||||||
def check_title(response, url):
|
def check_title(response, url):
|
||||||
@@ -72,7 +72,7 @@ def check_main_page():
|
|||||||
if 'fe_typo_user' in cookie:
|
if 'fe_typo_user' in cookie:
|
||||||
return bad_url()
|
return bad_url()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print Fore.RED + '\nReceived keyboard interrupt.\nQuitting...' + Fore.RESET
|
output('\nReceived keyboard interrupt.\nQuitting...')
|
||||||
exit(-1)
|
exit(-1)
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
@@ -91,14 +91,14 @@ def check_main_page():
|
|||||||
pass
|
pass
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
if '404' in str(e):
|
if '404' in str(e):
|
||||||
print Fore.RED + str(e) + '\nPlease ensure you entered the right url' + Fore.RESET
|
output(str(e) + '\nPlease ensure you entered the right url')
|
||||||
else:
|
else:
|
||||||
print Fore.RED + str(e) + Fore.RESET
|
output(str(e))
|
||||||
return 'skip'
|
return 'skip'
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def bad_url():
|
def bad_url():
|
||||||
print 'Typo3 Login:'.ljust(32) + Fore.GREEN + 'Typo3 is used, but could not find login' + Fore.RESET
|
print 'Typo3 Login:'.ljust(32) + 'Typo3 is used, but could not find login'
|
||||||
print ''.ljust(32) + 'This could result in \'no extensions are installed\'.'
|
print ''.ljust(32) + 'This could result in \'no extensions are installed\'.'
|
||||||
print ''.ljust(32) + 'Seems like something is wrong with the given url.'
|
print ''.ljust(32) + 'Seems like something is wrong with the given url.'
|
||||||
answer = ''
|
answer = ''
|
||||||
@@ -110,3 +110,11 @@ def bad_url():
|
|||||||
if answer is 'y':
|
if answer is 'y':
|
||||||
return True
|
return True
|
||||||
return 'skip'
|
return 'skip'
|
||||||
|
|
||||||
|
# printing error messages
|
||||||
|
def output(message):
|
||||||
|
if settings.COLORAMA:
|
||||||
|
print Fore.RED
|
||||||
|
print message
|
||||||
|
if settings.COLORAMA:
|
||||||
|
print Fore.RESET
|
||||||
@@ -2,7 +2,7 @@ import socket
|
|||||||
import urllib2
|
import urllib2
|
||||||
import os, sys
|
import os, sys
|
||||||
import re
|
import re
|
||||||
from colorama import Fore
|
|
||||||
try:
|
try:
|
||||||
import socks
|
import socks
|
||||||
except:
|
except:
|
||||||
@@ -16,7 +16,7 @@ except:
|
|||||||
def start_daemon():
|
def start_daemon():
|
||||||
if sys.platform.startswith('linux'):
|
if sys.platform.startswith('linux'):
|
||||||
os.system('service privoxy start')
|
os.system('service privoxy start')
|
||||||
print '[' + Fore.GREEN + ' ok ' + Fore.RESET + '] Starting privoxy daemon...done.'
|
print '[ ok ] Starting privoxy daemon...done.'
|
||||||
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
||||||
print "Please make sure Privoxy is running..."
|
print "Please make sure Privoxy is running..."
|
||||||
else:
|
else:
|
||||||
@@ -34,7 +34,7 @@ def connect(port):
|
|||||||
response = torcheck.read()
|
response = torcheck.read()
|
||||||
torcheck.close()
|
torcheck.close()
|
||||||
except:
|
except:
|
||||||
print Fore.RED + "Failed to connect through Privoxy!" + Fore.RESET
|
print "Failed to connect through Privoxy!"
|
||||||
print "Please make sure your configuration is right!\n"
|
print "Please make sure your configuration is right!\n"
|
||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
try:
|
try:
|
||||||
@@ -51,6 +51,6 @@ def stop():
|
|||||||
print "\n"
|
print "\n"
|
||||||
if sys.platform.startswith('linux'):
|
if sys.platform.startswith('linux'):
|
||||||
os.system('service privoxy stop')
|
os.system('service privoxy stop')
|
||||||
print '[' + Fore.GREEN + ' ok ' + Fore.RESET + '] Stopping privoxy daemon...done.'
|
print '[ ok ] Stopping privoxy daemon...done.'
|
||||||
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
||||||
print "You can close Privoxy now..."
|
print "You can close Privoxy now..."
|
||||||
@@ -8,8 +8,7 @@ from Queue import Queue
|
|||||||
from threading import Thread, Lock
|
from threading import Thread, Lock
|
||||||
|
|
||||||
# Domain to check
|
# Domain to check
|
||||||
# Valid: string
|
DOMAIN = ''
|
||||||
DOMAIN = ""
|
|
||||||
|
|
||||||
# Maximum number of threads (avoiding connection issues and/or DoS)
|
# Maximum number of threads (avoiding connection issues and/or DoS)
|
||||||
MAX_NUMBER_OF_THREADS = 10
|
MAX_NUMBER_OF_THREADS = 10
|
||||||
@@ -20,6 +19,10 @@ DEFAULT_TOR_PORT = 9050
|
|||||||
# Default ports used in Tor proxy bundles
|
# Default ports used in Tor proxy bundles
|
||||||
DEFAULT_PRIVOXY_PORT = 8118
|
DEFAULT_PRIVOXY_PORT = 8118
|
||||||
|
|
||||||
|
# Default extension file
|
||||||
|
# Default: all available Typo3 extensions
|
||||||
|
EXTENSION_FILE = ['all_extensions']
|
||||||
|
|
||||||
# List with selected extensions
|
# List with selected extensions
|
||||||
EXTENSION_LIST = []
|
EXTENSION_LIST = []
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@ EXTENSION_LIST = []
|
|||||||
NO_VERSIONINFO = ['wt_spamshield', 'introduction'] #introduction has ChangeLog, but will use "Typo3 4.5.0" as version info!
|
NO_VERSIONINFO = ['wt_spamshield', 'introduction'] #introduction has ChangeLog, but will use "Typo3 4.5.0" as version info!
|
||||||
|
|
||||||
# Check only top X extensions
|
# Check only top X extensions
|
||||||
# Default: all
|
# Default: all extensions
|
||||||
TOP_EXTENSION = 'all'
|
TOP_EXTENSION = 'all'
|
||||||
|
|
||||||
# HTTP User-Agent header value. Useful to fake the HTTP User-Agent header value at each HTTP request
|
# HTTP User-Agent header value. Useful to fake the HTTP User-Agent header value at each HTTP request
|
||||||
@@ -35,19 +38,16 @@ TOP_EXTENSION = 'all'
|
|||||||
user_agent = {'User-Agent' : "Mozilla/5.0"}
|
user_agent = {'User-Agent' : "Mozilla/5.0"}
|
||||||
|
|
||||||
# Maximum number of concurrent HTTP(s) requests (handled with Python threads)
|
# Maximum number of concurrent HTTP(s) requests (handled with Python threads)
|
||||||
# Valid: integer
|
|
||||||
# Default: 7
|
# Default: 7
|
||||||
THREADS = 7
|
THREADS = 7
|
||||||
|
|
||||||
# Verbosity.
|
# Verbosity.
|
||||||
verbose = False
|
verbose = False
|
||||||
|
|
||||||
#Input and output queues
|
#Input queue
|
||||||
in_queue = ""
|
in_queue = ''
|
||||||
out_queue = ""
|
|
||||||
|
|
||||||
# Seconds to wait before timeout connection.
|
# Seconds to wait before timeout connection.
|
||||||
# Valid: int
|
|
||||||
# Default: 20
|
# Default: 20
|
||||||
TIMEOUT = 20
|
TIMEOUT = 20
|
||||||
|
|
||||||
@@ -67,8 +67,12 @@ EXTENSION_PATHS = ('/typo3conf/ext/', '/typo3/sysext/')
|
|||||||
# Possible version info file
|
# Possible version info file
|
||||||
EXTENSION_VERSION_INFO = ('ChangeLog', 'README.txt')
|
EXTENSION_VERSION_INFO = ('ChangeLog', 'README.txt')
|
||||||
|
|
||||||
|
# Installed extensions on targed domain
|
||||||
EXTENSIONS_FOUND = 0
|
EXTENSIONS_FOUND = 0
|
||||||
|
|
||||||
|
# Colorama is not used, if not installed
|
||||||
|
COLORAMA = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Not used atm ##
|
## Not used atm ##
|
||||||
|
|||||||
49
lib/start.py
49
lib/start.py
@@ -8,7 +8,10 @@ import time
|
|||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
from threading import Thread, Lock
|
from threading import Thread, Lock
|
||||||
from colorama import Fore, Back
|
try:
|
||||||
|
from colorama import Fore, Back
|
||||||
|
except:
|
||||||
|
pass
|
||||||
from lib import settings
|
from lib import settings
|
||||||
from lib import versioninfo
|
from lib import versioninfo
|
||||||
from lib import login
|
from lib import login
|
||||||
@@ -18,7 +21,12 @@ from lib import extensions
|
|||||||
# Startmethod
|
# Startmethod
|
||||||
def check_typo_installation(domain):
|
def check_typo_installation(domain):
|
||||||
settings.DOMAIN = domain
|
settings.DOMAIN = domain
|
||||||
print '\n\n' + Fore.CYAN + '[ Checking ' + domain + ' ]' + '\n' + "-"* 70 + Fore.RESET
|
settings.EXTENSIONS_FOUND = 0
|
||||||
|
if settings.COLORAMA:
|
||||||
|
output = Fore.CYAN + '[ Checking ' + domain + ' ]' + '\n' + "-"* 70 + Fore.RESET
|
||||||
|
else:
|
||||||
|
output = '[ Checking ' + domain + ' ]' + '\n' + "-"* 70
|
||||||
|
print '\n\n' + output
|
||||||
|
|
||||||
check = login.search_login()
|
check = login.search_login()
|
||||||
if check is "redirect":
|
if check is "redirect":
|
||||||
@@ -31,21 +39,20 @@ def check_typo_installation(domain):
|
|||||||
if mainpage is True:
|
if mainpage is True:
|
||||||
init_extension_search()
|
init_extension_search()
|
||||||
elif mainpage is not "skip":
|
elif mainpage is not "skip":
|
||||||
print "Typo3 Login:".ljust(32) + Fore.RED + "Typo3 is not used on this domain" + Fore.RESET
|
output("Typo3 Login:".ljust(32) + "Typo3 is not used on this domain", False)
|
||||||
|
|
||||||
def init_extension_search():
|
def init_extension_search():
|
||||||
settings.in_queue = Queue()
|
settings.in_queue = Queue()
|
||||||
settings.out_queue = Queue()
|
|
||||||
versioninfo.search_version_info()
|
versioninfo.search_version_info()
|
||||||
versioninfo.output()
|
versioninfo.output()
|
||||||
|
|
||||||
if not settings.EXTENSION_LIST:
|
if settings.TOP_EXTENSION != 0:
|
||||||
extensions.generate_list()
|
if not settings.EXTENSION_LIST:
|
||||||
|
extensions.generate_list()
|
||||||
|
|
||||||
extensions.copy()
|
extensions.copy()
|
||||||
extensions_to_check = settings.in_queue.qsize()
|
extensions_to_check = settings.in_queue.qsize()
|
||||||
|
|
||||||
if extensions_to_check is not 0:
|
|
||||||
print '\nChecking', extensions_to_check, 'extension(s)...'
|
print '\nChecking', extensions_to_check, 'extension(s)...'
|
||||||
# Thanks to 'RedSparrow': http://stackoverflow.com/questions/17991033/python-cant-kill-main-thread-with-keyboardinterrupt
|
# Thanks to 'RedSparrow': http://stackoverflow.com/questions/17991033/python-cant-kill-main-thread-with-keyboardinterrupt
|
||||||
try:
|
try:
|
||||||
@@ -59,18 +66,24 @@ def init_extension_search():
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print Fore.RED + "\nReceived keyboard interrupt.\nQuitting..." + Fore.RESET
|
output("\nReceived keyboard interrupt.\nQuitting...", False)
|
||||||
exit(-1)
|
exit(-1)
|
||||||
settings.in_queue.join()
|
settings.in_queue.join()
|
||||||
|
|
||||||
installed_ext = settings.out_queue.qsize()
|
installed_ext = settings.EXTENSIONS_FOUND
|
||||||
if installed_ext is 0:
|
if installed_ext == 0:
|
||||||
print Fore.RED + "No extensions installed" + Fore.RESET
|
output("No extensions installed", False)
|
||||||
else:
|
else:
|
||||||
t = Thread(target=output.thread, args=())
|
output('\n' + str(settings.EXTENSIONS_FOUND) + '/' + str(extensions_to_check) + ' extension(s) installed', True)
|
||||||
t.daemon = True
|
|
||||||
t.start()
|
|
||||||
settings.out_queue.join()
|
|
||||||
print Fore.GREEN + '\n', str(settings.EXTENSIONS_FOUND) + '/' + str(extensions_to_check),'extension(s) installed' + Fore.RESET
|
|
||||||
else:
|
else:
|
||||||
print '\nSkipping check for extensions...'
|
print '\nSkipping check for extensions...'
|
||||||
|
|
||||||
|
# print error messages
|
||||||
|
def output(message, setting):
|
||||||
|
if settings.COLORAMA:
|
||||||
|
if not setting:
|
||||||
|
print Fore.RED + message + Fore.RESET
|
||||||
|
if setting:
|
||||||
|
print Fore.GREEN + message + Fore.RESET
|
||||||
|
else:
|
||||||
|
print message
|
||||||
BIN
lib/tor.pyc
BIN
lib/tor.pyc
Binary file not shown.
@@ -2,7 +2,7 @@ import socket
|
|||||||
import urllib2
|
import urllib2
|
||||||
import os, sys
|
import os, sys
|
||||||
import re
|
import re
|
||||||
from colorama import Fore
|
|
||||||
try:
|
try:
|
||||||
import socks
|
import socks
|
||||||
except:
|
except:
|
||||||
@@ -34,14 +34,14 @@ def connect(port):
|
|||||||
torcheck.close()
|
torcheck.close()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print e
|
print e
|
||||||
print Fore.RED + "Failed to connect through TOR!" + Fore.RESET
|
print "Failed to connect through TOR!"
|
||||||
print "Please make sure your configuration is right!\n"
|
print "Please make sure your configuration is right!\n"
|
||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
try:
|
try:
|
||||||
regex = re.compile('Congratulations. This browser is configured to use Tor.')
|
regex = re.compile('Congratulations. This browser is configured to use Tor.')
|
||||||
searchVersion = regex.search(response)
|
searchVersion = regex.search(response)
|
||||||
version = searchVersion.groups()
|
version = searchVersion.groups()
|
||||||
print Fore.GREEN + "Connection to TOR established" + Fore.RESET
|
print "Connection to TOR established"
|
||||||
regex = re.compile("(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})")
|
regex = re.compile("(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})")
|
||||||
searchIP = regex.search(response)
|
searchIP = regex.search(response)
|
||||||
IP = searchIP.groups()[0]
|
IP = searchIP.groups()[0]
|
||||||
@@ -55,4 +55,4 @@ def stop():
|
|||||||
if sys.platform.startswith('linux'):
|
if sys.platform.startswith('linux'):
|
||||||
os.system('service tor stop')
|
os.system('service tor stop')
|
||||||
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
||||||
print "You can close TOR now..."
|
print "You can close TOR now..."
|
||||||
@@ -2,7 +2,7 @@ import socket
|
|||||||
import urllib2
|
import urllib2
|
||||||
import os, sys
|
import os, sys
|
||||||
import re
|
import re
|
||||||
from colorama import Fore
|
|
||||||
try:
|
try:
|
||||||
import socks
|
import socks
|
||||||
except:
|
except:
|
||||||
@@ -17,7 +17,7 @@ def start_daemon():
|
|||||||
if sys.platform.startswith('linux'):
|
if sys.platform.startswith('linux'):
|
||||||
os.system('service tor start')
|
os.system('service tor start')
|
||||||
os.system('service privoxy start')
|
os.system('service privoxy start')
|
||||||
print '[' + Fore.GREEN + ' ok ' + Fore.RESET + '] Starting privoxy daemon...done.'
|
print '[ ok ] Starting privoxy daemon...done.'
|
||||||
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
||||||
print "Please make sure TOR and Privoxy are running..."
|
print "Please make sure TOR and Privoxy are running..."
|
||||||
else:
|
else:
|
||||||
@@ -35,14 +35,14 @@ def connect(port):
|
|||||||
response = torcheck.read()
|
response = torcheck.read()
|
||||||
torcheck.close()
|
torcheck.close()
|
||||||
except:
|
except:
|
||||||
print Fore.RED + "Failed to connect through Privoxy and/or TOR!" + Fore.RESET
|
print "Failed to connect through Privoxy and/or TOR!"
|
||||||
print "Please make sure your configuration is right!\n"
|
print "Please make sure your configuration is right!\n"
|
||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
try:
|
try:
|
||||||
regex = re.compile('Congratulations. This browser is configured to use Tor.')
|
regex = re.compile('Congratulations. This browser is configured to use Tor.')
|
||||||
searchVersion = regex.search(response)
|
searchVersion = regex.search(response)
|
||||||
version = searchVersion.groups()
|
version = searchVersion.groups()
|
||||||
print Fore.GREEN + "Connection to TOR established" + Fore.RESET
|
print "Connection to TOR established"
|
||||||
regex = re.compile("(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})")
|
regex = re.compile("(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})")
|
||||||
searchIP = regex.search(response)
|
searchIP = regex.search(response)
|
||||||
IP = searchIP.groups()[0]
|
IP = searchIP.groups()[0]
|
||||||
@@ -57,6 +57,6 @@ def stop():
|
|||||||
if sys.platform.startswith('linux'):
|
if sys.platform.startswith('linux'):
|
||||||
os.system('service tor stop')
|
os.system('service tor stop')
|
||||||
os.system('service privoxy stop')
|
os.system('service privoxy stop')
|
||||||
print '[' + Fore.GREEN + ' ok ' + Fore.RESET + '] Stopping privoxy daemon...done.'
|
print '[ ok ] Stopping privoxy daemon...done.'
|
||||||
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
||||||
print "You can close TOR and Privoxy now..."
|
print "You can close TOR and Privoxy now..."
|
||||||
@@ -8,12 +8,13 @@ import xml.etree.ElementTree as ElementTree
|
|||||||
# Progressbar
|
# Progressbar
|
||||||
def dlProgress(count, blockSize, totalSize):
|
def dlProgress(count, blockSize, totalSize):
|
||||||
percent = int(count*blockSize*100/totalSize)
|
percent = int(count*blockSize*100/totalSize)
|
||||||
sys.stdout.write("\rDownloading extentions: " + "%d%%" % percent)
|
sys.stdout.write("\r[+] Downloading extentions: " + "%d%%" % percent)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
# Download extensions from typo3 repository
|
# Download extensions from typo3 repository
|
||||||
def download_ext():
|
def download_ext():
|
||||||
try:
|
try:
|
||||||
|
urllib.URLopener.version = 'TYPO3/7.0.0'
|
||||||
urllib.urlretrieve('http://ter.sitedesign.dk/ter/extensions.xml.gz', 'extensions.gz', reporthook=dlProgress)
|
urllib.urlretrieve('http://ter.sitedesign.dk/ter/extensions.xml.gz', 'extensions.gz', reporthook=dlProgress)
|
||||||
inf = gzip.open('extensions.gz', 'rb')
|
inf = gzip.open('extensions.gz', 'rb')
|
||||||
file_content = inf.read()
|
file_content = inf.read()
|
||||||
@@ -23,22 +24,82 @@ def download_ext():
|
|||||||
outF.close()
|
outF.close()
|
||||||
os.remove('extensions.gz')
|
os.remove('extensions.gz')
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print "Oops! Got:".ljust(32), e
|
print "\nOops! Got:", e
|
||||||
|
|
||||||
# Parse extensions.xml and save extensions in file
|
# Parse extensions.xml and save extensions in files
|
||||||
def generate_list():
|
def generate_list():
|
||||||
extension = 'extensions.xml'
|
experimental = {} # everything with 'experimental' and 'test'
|
||||||
print "\nParsing file..."
|
alpha = {}
|
||||||
tree = ElementTree.parse(extension)
|
beta = {}
|
||||||
tag_dict = tree.getroot()
|
stable = {}
|
||||||
exten_Dict = {}
|
outdated = {} # everything with 'obsolete' and 'outdated'
|
||||||
for extensions in tag_dict.getchildren():
|
allExt = {}
|
||||||
ext = {extensions.get('extensionkey'):extensions[0].text}
|
|
||||||
exten_Dict.update(ext)
|
print "\n[+] Parsing file..."
|
||||||
sorted_dict = sorted(exten_Dict.iteritems(), key=lambda x: int(x[1]), reverse=True)
|
tree = ElementTree.parse('extensions.xml')
|
||||||
f = open('extensions','w')
|
root = tree.getroot()
|
||||||
for i in xrange(0,len(exten_Dict)):
|
extension = 0
|
||||||
f.write(sorted_dict[i][0]+'\n')
|
# for every extension in file
|
||||||
|
for child in root:
|
||||||
|
# insert every extension in "allExt" dictionary
|
||||||
|
allExt.update({child.get('extensionkey'):child[0].text})
|
||||||
|
# and search the last version entry
|
||||||
|
version = 0
|
||||||
|
for version_entry in root[extension].iter('version'):
|
||||||
|
version +=1
|
||||||
|
# get the state of the latest version
|
||||||
|
state = (str(root[extension][version][2].text)).lower()
|
||||||
|
if state == 'experimental' or state == 'test':
|
||||||
|
experimental.update({child.get('extensionkey'):child[0].text})
|
||||||
|
elif state == 'alpha':
|
||||||
|
alpha.update({child.get('extensionkey'):child[0].text})
|
||||||
|
elif state == 'beta':
|
||||||
|
beta.update({child.get('extensionkey'):child[0].text})
|
||||||
|
elif state == 'stable':
|
||||||
|
stable.update({child.get('extensionkey'):child[0].text})
|
||||||
|
elif state == 'obsolete' or state == 'outdated':
|
||||||
|
outdated.update({child.get('extensionkey'):child[0].text})
|
||||||
|
extension+=1
|
||||||
|
|
||||||
|
# sorting lists according to number of downloads
|
||||||
|
print "[+] Sorting according to number of downloads..."
|
||||||
|
sorted_experimental = sorted(experimental.iteritems(), key=lambda x: int(x[1]), reverse=True)
|
||||||
|
sorted_alpha = sorted(alpha.iteritems(), key=lambda x: int(x[1]), reverse=True)
|
||||||
|
sorted_beta = sorted(beta.iteritems(), key=lambda x: int(x[1]), reverse=True)
|
||||||
|
sorted_stable = sorted(stable.iteritems(), key=lambda x: int(x[1]), reverse=True)
|
||||||
|
sorted_outdated = sorted(outdated.iteritems(), key=lambda x: int(x[1]), reverse=True)
|
||||||
|
sorted_allExt = sorted(allExt.iteritems(), key=lambda x: int(x[1]), reverse=True)
|
||||||
|
|
||||||
|
print "[+] Generating files..."
|
||||||
|
f = open(os.path.join('extensions', 'experimental_extensions'),'w')
|
||||||
|
for i in xrange(0,len(sorted_experimental)):
|
||||||
|
f.write(sorted_experimental[i][0]+'\n')
|
||||||
f.close()
|
f.close()
|
||||||
print 'Loaded', len(exten_Dict), 'extensions\n'
|
|
||||||
|
f = open(os.path.join('extensions', 'alpha_extensions'),'w')
|
||||||
|
for i in xrange(0,len(sorted_alpha)):
|
||||||
|
f.write(sorted_alpha[i][0]+'\n')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
f = open(os.path.join('extensions', 'beta_extensions'),'w')
|
||||||
|
for i in xrange(0,len(sorted_beta)):
|
||||||
|
f.write(sorted_beta[i][0]+'\n')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
f = open(os.path.join('extensions', 'stable_extensions'),'w')
|
||||||
|
for i in xrange(0,len(sorted_stable)):
|
||||||
|
f.write(sorted_stable[i][0]+'\n')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
f = open(os.path.join('extensions', 'outdated_extensions'),'w')
|
||||||
|
for i in xrange(0,len(sorted_outdated)):
|
||||||
|
f.write(sorted_outdated[i][0]+'\n')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
f = open(os.path.join('extensions', 'all_extensions'),'w')
|
||||||
|
for i in xrange(0,len(sorted_allExt)):
|
||||||
|
f.write(sorted_allExt[i][0]+'\n')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
print '[+] Loaded', len(sorted_allExt), 'extensions\n'
|
||||||
os.remove('extensions.xml')
|
os.remove('extensions.xml')
|
||||||
@@ -6,8 +6,11 @@ Copyright (c) 2014 Jan Rude
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import urllib2
|
import urllib2
|
||||||
from colorama import Fore
|
|
||||||
from lib import settings
|
from lib import settings
|
||||||
|
try:
|
||||||
|
from colorama import Fore
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# Searching for Typo3 version
|
# Searching for Typo3 version
|
||||||
def search_version_info():
|
def search_version_info():
|
||||||
@@ -29,7 +32,16 @@ def search_version_info():
|
|||||||
# Output of Typo3 version
|
# Output of Typo3 version
|
||||||
def output():
|
def output():
|
||||||
if settings.TYPO_VERSION is None:
|
if settings.TYPO_VERSION is None:
|
||||||
print "Typo3 Version:".ljust(32) + Fore.RED + "Not found" + Fore.RESET
|
print "Typo3 Version:".ljust(32)
|
||||||
|
if settings.COLORAMA:
|
||||||
|
print Fore.RED
|
||||||
|
print "Not found"
|
||||||
|
if settings.COLORAMA:
|
||||||
|
Fore.RESET
|
||||||
else:
|
else:
|
||||||
print "Typo3 Version:".ljust(32) + Fore.GREEN + settings.TYPO_VERSION + Fore.RESET
|
if settings.COLORAMA:
|
||||||
|
output = Fore.GREEN + settings.TYPO_VERSION + Fore.RESET
|
||||||
|
else:
|
||||||
|
output = settings.TYPO_VERSION
|
||||||
|
print "Typo3 Version:".ljust(32) + output
|
||||||
print "Link to vulnerabilities:".ljust(32) + "http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=" + settings.TYPO_VERSION.split()[1]
|
print "Link to vulnerabilities:".ljust(32) + "http://www.cvedetails.com/version-search.php?vendor=&product=Typo3&version=" + settings.TYPO_VERSION.split()[1]
|
||||||
37
typoenum.py
37
typoenum.py
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
############ Version information ##############
|
############ Version information ##############
|
||||||
__version__ = "0.3.2"
|
__version__ = "0.3.3"
|
||||||
__program__ = "Typo-Enumerator v" + __version__
|
__program__ = "Typo-Enumerator v" + __version__
|
||||||
__description__ = 'Automatic Typo3 and Typo3 extensions enumeration tool'
|
__description__ = 'Automatic Typo3 and Typo3 extensions enumeration tool'
|
||||||
__author__ = "Jan Rude"
|
__author__ = "Jan Rude"
|
||||||
@@ -14,12 +14,15 @@ import os
|
|||||||
import datetime
|
import datetime
|
||||||
import argparse
|
import argparse
|
||||||
import warnings
|
import warnings
|
||||||
from colorama import init, Fore, Style
|
|
||||||
warnings.filterwarnings(action="ignore", message=".*was already imported", category=UserWarning)
|
warnings.filterwarnings(action="ignore", message=".*was already imported", category=UserWarning)
|
||||||
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
|
|
||||||
from lib import settings
|
from lib import settings
|
||||||
from lib import update
|
from lib import update
|
||||||
from lib import start
|
from lib import start
|
||||||
|
try:
|
||||||
|
from colorama import init, Fore
|
||||||
|
settings.COLORAMA = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
init()
|
init()
|
||||||
|
|
||||||
|
|
||||||
@@ -35,6 +38,7 @@ def main(argv):
|
|||||||
parser.add_argument('--user_agent', dest='user_agent', metavar='USER-AGENT (default: Mozilla/5.0)')
|
parser.add_argument('--user_agent', dest='user_agent', metavar='USER-AGENT (default: Mozilla/5.0)')
|
||||||
extensionGroup.add_argument('--top', type=int, dest='top', metavar='VALUE', help='Check only most X downloaded extensions (default: all)')
|
extensionGroup.add_argument('--top', type=int, dest='top', metavar='VALUE', help='Check only most X downloaded extensions (default: all)')
|
||||||
extensionGroup.add_argument('-e', '--extension', type=str, dest='ext', metavar='EXTENSION', help='Check only for this extension(s)', nargs='+')
|
extensionGroup.add_argument('-e', '--extension', type=str, dest='ext', metavar='EXTENSION', help='Check only for this extension(s)', nargs='+')
|
||||||
|
parser.add_argument('--state', dest='ext_state', help='Check only (experimental | alpha | beta | stable | outdated) extensions', nargs='+')
|
||||||
anonGroup.add_argument('--tor', help='using only TOR for connections', action='store_true')
|
anonGroup.add_argument('--tor', help='using only TOR for connections', action='store_true')
|
||||||
anonGroup.add_argument('--privoxy', help='using only Privoxy for connections', action='store_true')
|
anonGroup.add_argument('--privoxy', help='using only Privoxy for connections', action='store_true')
|
||||||
anonGroup.add_argument('--tp', help='using TOR and Privoxy for connections', action='store_true')
|
anonGroup.add_argument('--tp', help='using TOR and Privoxy for connections', action='store_true')
|
||||||
@@ -51,7 +55,7 @@ def main(argv):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if args.threads > settings.MAX_NUMBER_OF_THREADS:
|
if args.threads > settings.MAX_NUMBER_OF_THREADS:
|
||||||
print Fore.RED + "Warning! Threads are set to", args.threads,"(max value is 10)\nThis can cause connection issues and/or DoS\nAborting...." + Fore.RESET
|
output("Warning! Threads are set to", args.threads,"(max value is 10)\nThis can cause connection issues and/or DoS\nAborting....")
|
||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
|
|
||||||
if args.tor:
|
if args.tor:
|
||||||
@@ -94,20 +98,26 @@ def main(argv):
|
|||||||
for extension in args.ext:
|
for extension in args.ext:
|
||||||
settings.EXTENSION_LIST.append(extension)
|
settings.EXTENSION_LIST.append(extension)
|
||||||
|
|
||||||
|
if args.ext_state:
|
||||||
|
settings.EXTENSION_FILE = []
|
||||||
|
for ext_list in args.ext_state:
|
||||||
|
ext_file = ext_list + '_extensions'
|
||||||
|
settings.EXTENSION_FILE.append(ext_file)
|
||||||
|
|
||||||
if args.domain:
|
if args.domain:
|
||||||
for dom in args.domain:
|
for dom in args.domain:
|
||||||
start.check_typo_installation(dom)
|
start.check_typo_installation(dom)
|
||||||
|
|
||||||
elif args.file:
|
elif args.file:
|
||||||
if not isfile(args.file):
|
if not isfile(args.file):
|
||||||
print(Fore.RED + "\nFile not found: " + args.file + "\nAborting..." + Fore.RESET)
|
output("\nFile not found: " + args.file + "\nAborting...")
|
||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
else:
|
else:
|
||||||
with open(args.file, 'r') as f:
|
with open(args.file, 'r') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
start.check_typo_installation(line.strip('\n')[0])
|
start.check_typo_installation(line.strip('\n')[0])
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print Fore.RED + "\nReceived keyboard interrupt.\nQuitting..." + Fore.RESET
|
output("\nReceived keyboard interrupt.\nQuitting...")
|
||||||
exit(-1)
|
exit(-1)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
import traceback
|
import traceback
|
||||||
@@ -123,19 +133,24 @@ def main(argv):
|
|||||||
elif args.tp:
|
elif args.tp:
|
||||||
tp.stop()
|
tp.stop()
|
||||||
|
|
||||||
print '\n'
|
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
print __program__ + ' finished at ' + now.strftime("%Y-%m-%d %H:%M:%S") + '\n'
|
print '\n' + __program__ + ' finished at ' + now.strftime("%Y-%m-%d %H:%M:%S") + '\n'
|
||||||
Style.RESET_ALL
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# print error messages
|
||||||
|
def output(message):
|
||||||
|
if settings.COLORAMA:
|
||||||
|
print Fore.RED + message + Fore.RESET
|
||||||
|
else:
|
||||||
|
print message
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
print(Style.BRIGHT + '\n' + 70*'*')
|
print('\n' + 70*'*')
|
||||||
print('\t' + __program__ )
|
print('\t' + __program__ )
|
||||||
print('\t' + __description__)
|
print('\t' + __description__)
|
||||||
print('\t' + '(c)2014 by ' + __author__)
|
print('\t' + '(c)2014 by ' + __author__)
|
||||||
print('\t' + 'Status:\t' + __status__)
|
print('\t' + 'Status:\t' + __status__)
|
||||||
print('\t' + 'For legal purposes only!')
|
print('\t' + 'For legal purposes only!')
|
||||||
print(70*'*' + '\n')
|
print(70*'*' + '\n')
|
||||||
sys.exit( not main( sys.argv ) )
|
sys.exit( not main( sys.argv ) )
|
||||||
Reference in New Issue
Block a user