mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2024-11-23 01:56:36 +03:00
Merge branch 'master' of github.com:sqlmapproject/sqlmap
This commit is contained in:
commit
a7cab63796
|
@ -99,6 +99,7 @@ from lib.core.settings import INVALID_UNICODE_CHAR_FORMAT
|
||||||
from lib.core.settings import ISSUES_PAGE
|
from lib.core.settings import ISSUES_PAGE
|
||||||
from lib.core.settings import IS_WIN
|
from lib.core.settings import IS_WIN
|
||||||
from lib.core.settings import LARGE_OUTPUT_THRESHOLD
|
from lib.core.settings import LARGE_OUTPUT_THRESHOLD
|
||||||
|
from lib.core.settings import MIN_ENCODED_LEN_CHECK
|
||||||
from lib.core.settings import MIN_TIME_RESPONSES
|
from lib.core.settings import MIN_TIME_RESPONSES
|
||||||
from lib.core.settings import ML
|
from lib.core.settings import ML
|
||||||
from lib.core.settings import NULL
|
from lib.core.settings import NULL
|
||||||
|
@ -570,7 +571,7 @@ def paramToDict(place, parameters=None):
|
||||||
for encoding in ("hex", "base64"):
|
for encoding in ("hex", "base64"):
|
||||||
try:
|
try:
|
||||||
decoded = value.decode(encoding)
|
decoded = value.decode(encoding)
|
||||||
if all(_ in string.printable for _ in decoded):
|
if len(decoded) > MIN_ENCODED_LEN_CHECK and all(_ in string.printable for _ in decoded):
|
||||||
warnMsg = "provided parameter '%s' " % parameter
|
warnMsg = "provided parameter '%s' " % parameter
|
||||||
warnMsg += "seems to be '%s' encoded" % encoding
|
warnMsg += "seems to be '%s' encoded" % encoding
|
||||||
logger.warn(warnMsg)
|
logger.warn(warnMsg)
|
||||||
|
@ -768,13 +769,6 @@ def dataToOutFile(filename, data):
|
||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
|
|
||||||
def strToHex(value):
|
|
||||||
"""
|
|
||||||
Converts string value to it's hexadecimal representation
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (value if not isinstance(value, unicode) else value.encode(UNICODE_ENCODING)).encode("hex").upper()
|
|
||||||
|
|
||||||
def readInput(message, default=None, checkBatch=True):
|
def readInput(message, default=None, checkBatch=True):
|
||||||
"""
|
"""
|
||||||
Reads input from terminal
|
Reads input from terminal
|
||||||
|
@ -1313,20 +1307,6 @@ def getCharset(charsetType=None):
|
||||||
|
|
||||||
return asciiTbl
|
return asciiTbl
|
||||||
|
|
||||||
def searchEnvPath(filename):
|
|
||||||
retVal = None
|
|
||||||
path = os.environ.get("PATH", "")
|
|
||||||
paths = path.split(";") if IS_WIN else path.split(":")
|
|
||||||
|
|
||||||
for _ in paths:
|
|
||||||
_ = _.replace(";", "")
|
|
||||||
retVal = os.path.exists(os.path.normpath(os.path.join(_, filename)))
|
|
||||||
|
|
||||||
if retVal:
|
|
||||||
break
|
|
||||||
|
|
||||||
return retVal
|
|
||||||
|
|
||||||
def directoryPath(filepath):
|
def directoryPath(filepath):
|
||||||
"""
|
"""
|
||||||
Returns directory path for a given filepath
|
Returns directory path for a given filepath
|
||||||
|
@ -1434,13 +1414,6 @@ def showStaticWords(firstPage, secondPage):
|
||||||
|
|
||||||
logger.info(infoMsg)
|
logger.info(infoMsg)
|
||||||
|
|
||||||
def isWindowsPath(filepath):
|
|
||||||
"""
|
|
||||||
Returns True if given filepath is in Windows format
|
|
||||||
"""
|
|
||||||
|
|
||||||
return re.search("\A[\w]\:\\\\", filepath) is not None
|
|
||||||
|
|
||||||
def isWindowsDriveLetterPath(filepath):
|
def isWindowsDriveLetterPath(filepath):
|
||||||
"""
|
"""
|
||||||
Returns True if given filepath starts with a Windows drive letter
|
Returns True if given filepath starts with a Windows drive letter
|
||||||
|
@ -1470,18 +1443,6 @@ def ntToPosixSlashes(filepath):
|
||||||
|
|
||||||
return filepath.replace('\\', '/')
|
return filepath.replace('\\', '/')
|
||||||
|
|
||||||
def isBase64EncodedString(subject):
|
|
||||||
"""
|
|
||||||
Checks if the provided string is Base64 encoded
|
|
||||||
|
|
||||||
>>> isBase64EncodedString('dGVzdA==')
|
|
||||||
True
|
|
||||||
>>> isBase64EncodedString('123456')
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
|
|
||||||
return re.match(r"\A(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z", subject) is not None
|
|
||||||
|
|
||||||
def isHexEncodedString(subject):
|
def isHexEncodedString(subject):
|
||||||
"""
|
"""
|
||||||
Checks if the provided string is hex encoded
|
Checks if the provided string is hex encoded
|
||||||
|
@ -2485,20 +2446,6 @@ def showHttpErrorCodes():
|
||||||
for code, count in kb.httpErrorCodes.items())
|
for code, count in kb.httpErrorCodes.items())
|
||||||
logger.warn(warnMsg)
|
logger.warn(warnMsg)
|
||||||
|
|
||||||
def getComparePageRatio(firstPage, secondPage, filtered=False):
|
|
||||||
"""
|
|
||||||
Returns comparison ratio between two given pages
|
|
||||||
"""
|
|
||||||
|
|
||||||
if filtered:
|
|
||||||
(firstPage, secondPage) = map(getFilteredPageContent, (firstPage, secondPage))
|
|
||||||
|
|
||||||
seqMatcher = getCurrentThreadData().seqMatcher
|
|
||||||
seqMatcher.set_seq1(firstPage)
|
|
||||||
seqMatcher.set_seq2(secondPage)
|
|
||||||
|
|
||||||
return seqMatcher.quick_ratio()
|
|
||||||
|
|
||||||
def openFile(filename, mode='r'):
|
def openFile(filename, mode='r'):
|
||||||
"""
|
"""
|
||||||
Returns file handle of a given filename
|
Returns file handle of a given filename
|
||||||
|
@ -2752,16 +2699,6 @@ def unsafeSQLIdentificatorNaming(name):
|
||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
|
|
||||||
def isBinaryData(value):
|
|
||||||
"""
|
|
||||||
Tests given value for binary content
|
|
||||||
"""
|
|
||||||
|
|
||||||
retVal = False
|
|
||||||
if isinstance(value, basestring):
|
|
||||||
retVal = reduce(lambda x, y: x or not (y in string.printable or ord(y) > 255), value, False)
|
|
||||||
return retVal
|
|
||||||
|
|
||||||
def isNoneValue(value):
|
def isNoneValue(value):
|
||||||
"""
|
"""
|
||||||
Returns whether the value is unusable (None or '')
|
Returns whether the value is unusable (None or '')
|
||||||
|
|
|
@ -47,25 +47,6 @@ def hexdecode(value):
|
||||||
def hexencode(value):
|
def hexencode(value):
|
||||||
return utf8encode(value).encode("hex")
|
return utf8encode(value).encode("hex")
|
||||||
|
|
||||||
def md5hash(value):
|
|
||||||
if "hashlib" in sys.modules:
|
|
||||||
return hashlib.md5(value).hexdigest()
|
|
||||||
else:
|
|
||||||
return md5.new(value).hexdigest()
|
|
||||||
|
|
||||||
def orddecode(value):
|
|
||||||
packedString = struct.pack("!" + "I" * len(value), *value)
|
|
||||||
return "".join(chr(char) for char in struct.unpack("!" + "I" * (len(packedString) / 4), packedString))
|
|
||||||
|
|
||||||
def ordencode(value):
|
|
||||||
return tuple(ord(char) for char in value)
|
|
||||||
|
|
||||||
def sha1hash(value):
|
|
||||||
if "hashlib" in sys.modules:
|
|
||||||
return hashlib.sha1(value).hexdigest()
|
|
||||||
else:
|
|
||||||
return sha.new(value).hexdigest()
|
|
||||||
|
|
||||||
def unicodeencode(value, encoding=None):
|
def unicodeencode(value, encoding=None):
|
||||||
"""
|
"""
|
||||||
Return 8-bit string representation of the supplied unicode value:
|
Return 8-bit string representation of the supplied unicode value:
|
||||||
|
|
|
@ -131,7 +131,6 @@ from lib.parse.payloads import loadPayloads
|
||||||
from lib.request.basic import checkCharEncoding
|
from lib.request.basic import checkCharEncoding
|
||||||
from lib.request.connect import Connect as Request
|
from lib.request.connect import Connect as Request
|
||||||
from lib.request.dns import DNSServer
|
from lib.request.dns import DNSServer
|
||||||
from lib.request.proxy import ProxyHTTPSHandler
|
|
||||||
from lib.request.basicauthhandler import SmartHTTPBasicAuthHandler
|
from lib.request.basicauthhandler import SmartHTTPBasicAuthHandler
|
||||||
from lib.request.certhandler import HTTPSCertAuthHandler
|
from lib.request.certhandler import HTTPSCertAuthHandler
|
||||||
from lib.request.httpshandler import HTTPSHandler
|
from lib.request.httpshandler import HTTPSHandler
|
||||||
|
@ -970,17 +969,7 @@ def _setHTTPProxy():
|
||||||
proxyString = ""
|
proxyString = ""
|
||||||
|
|
||||||
proxyString += "%s:%d" % (hostname, port)
|
proxyString += "%s:%d" % (hostname, port)
|
||||||
|
proxyHandler = urllib2.ProxyHandler({"http": proxyString, "https": proxyString})
|
||||||
# Workaround for http://bugs.python.org/issue1424152 (urllib/urllib2:
|
|
||||||
# HTTPS over (Squid) Proxy fails) as long as HTTP over SSL requests
|
|
||||||
# can't be tunneled over an HTTP proxy natively by Python (<= 2.5)
|
|
||||||
# urllib2 standard library
|
|
||||||
if PYVERSION >= "2.6":
|
|
||||||
proxyHandler = urllib2.ProxyHandler({"http": proxyString, "https": proxyString})
|
|
||||||
elif conf.scheme == "https":
|
|
||||||
proxyHandler = ProxyHTTPSHandler(proxyString)
|
|
||||||
else:
|
|
||||||
proxyHandler = urllib2.ProxyHandler({"http": proxyString})
|
|
||||||
|
|
||||||
def _setSafeUrl():
|
def _setSafeUrl():
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -506,6 +506,9 @@ MIN_BINARY_DISK_DUMP_SIZE = 100
|
||||||
# Regular expression used for extracting form tags
|
# Regular expression used for extracting form tags
|
||||||
FORM_SEARCH_REGEX = r"(?si)<form(?!.+<form).+?</form>"
|
FORM_SEARCH_REGEX = r"(?si)<form(?!.+<form).+?</form>"
|
||||||
|
|
||||||
|
# Minimum field entry length needed for encoded content (hex, base64,...) check
|
||||||
|
MIN_ENCODED_LEN_CHECK = 5
|
||||||
|
|
||||||
# CSS style used in HTML dump format
|
# CSS style used in HTML dump format
|
||||||
HTML_DUMP_CSS_STYLE = """<style>
|
HTML_DUMP_CSS_STYLE = """<style>
|
||||||
table{
|
table{
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/)
|
|
||||||
See the file 'doc/COPYING' for copying permission
|
|
||||||
"""
|
|
||||||
|
|
||||||
import httplib
|
|
||||||
import socket
|
|
||||||
import ssl
|
|
||||||
import urllib
|
|
||||||
import urllib2
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyHTTPConnection(httplib.HTTPConnection):
|
|
||||||
_ports = {"http": 80, "https": 443}
|
|
||||||
|
|
||||||
def request(self, method, url, body=None, headers={}):
|
|
||||||
# Request is called before connect, so can interpret url and get
|
|
||||||
# real host/port to be used to make CONNECT request to proxy
|
|
||||||
proto, rest = urllib.splittype(url)
|
|
||||||
|
|
||||||
if proto is None:
|
|
||||||
raise ValueError("unknown URL type: %s" % url)
|
|
||||||
|
|
||||||
# Get host
|
|
||||||
host, rest = urllib.splithost(rest)
|
|
||||||
|
|
||||||
# Try to get port
|
|
||||||
host, port = urllib.splitport(host)
|
|
||||||
|
|
||||||
# If port is not defined try to get from proto
|
|
||||||
if port is None:
|
|
||||||
try:
|
|
||||||
port = self._ports[proto]
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError("unknown protocol for: %s" % url)
|
|
||||||
|
|
||||||
self._real_host = host
|
|
||||||
self._real_port = int(port)
|
|
||||||
|
|
||||||
httplib.HTTPConnection.request(self, method, rest, body, headers)
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
httplib.HTTPConnection.connect(self)
|
|
||||||
|
|
||||||
# Send proxy CONNECT request
|
|
||||||
self.send("CONNECT %s:%d HTTP/1.0\r\n\r\n" % (self._real_host, self._real_port))
|
|
||||||
|
|
||||||
# Expect a HTTP/1.0 200 Connection established
|
|
||||||
response = self.response_class(self.sock, strict=self.strict, method=self._method)
|
|
||||||
(version, code, message) = response._read_status()
|
|
||||||
|
|
||||||
# Probably here we can handle auth requests...
|
|
||||||
if code != 200:
|
|
||||||
# Proxy returned and error, abort connection, and raise exception
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
raise socket.error, "Proxy connection failed: %d %s" % (code, message.strip())
|
|
||||||
|
|
||||||
# Eat up header block from proxy
|
|
||||||
while True:
|
|
||||||
# Should not use directly fp probably
|
|
||||||
line = response.fp.readline()
|
|
||||||
|
|
||||||
if line == "\r\n":
|
|
||||||
break
|
|
||||||
|
|
||||||
class ProxyHTTPSConnection(ProxyHTTPConnection):
|
|
||||||
default_port = 443
|
|
||||||
|
|
||||||
def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None, timeout=None):
|
|
||||||
ProxyHTTPConnection.__init__(self, host, port)
|
|
||||||
self.key_file = key_file
|
|
||||||
self.cert_file = cert_file
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
ProxyHTTPConnection.connect(self)
|
|
||||||
|
|
||||||
# Make the sock ssl-aware
|
|
||||||
sslobj = ssl.wrap_socket(self.sock, self.key_file, self.cert_file)
|
|
||||||
self.sock = sslobj
|
|
||||||
|
|
||||||
class ProxyHTTPHandler(urllib2.HTTPHandler):
|
|
||||||
def __init__(self, proxy=None, debuglevel=0):
|
|
||||||
self.proxy = proxy
|
|
||||||
|
|
||||||
urllib2.HTTPHandler.__init__(self, debuglevel)
|
|
||||||
|
|
||||||
def do_open(self, http_class, req):
|
|
||||||
if self.proxy is not None:
|
|
||||||
req.set_proxy(self.proxy, "http")
|
|
||||||
|
|
||||||
return urllib2.HTTPHandler.do_open(self, ProxyHTTPConnection, req)
|
|
||||||
|
|
||||||
class ProxyHTTPSHandler(urllib2.HTTPSHandler):
|
|
||||||
def __init__(self, proxy=None, debuglevel=0):
|
|
||||||
self.proxy = proxy
|
|
||||||
|
|
||||||
urllib2.HTTPSHandler.__init__(self, debuglevel)
|
|
||||||
|
|
||||||
def do_open(self, http_class, req):
|
|
||||||
if self.proxy is not None:
|
|
||||||
req.set_proxy(self.proxy, "https")
|
|
||||||
|
|
||||||
return urllib2.HTTPSHandler.do_open(self, ProxyHTTPSConnection, req)
|
|
|
@ -45,7 +45,7 @@ class Connector(GenericConnector):
|
||||||
except (pyodbc.Error, pyodbc.OperationalError), msg:
|
except (pyodbc.Error, pyodbc.OperationalError), msg:
|
||||||
raise SqlmapConnectionException(msg[1])
|
raise SqlmapConnectionException(msg[1])
|
||||||
|
|
||||||
self.setCursor()
|
self.initCursor()
|
||||||
self.connected()
|
self.connected()
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Connector(GenericConnector):
|
||||||
raise SqlmapConnectionException(msg)
|
raise SqlmapConnectionException(msg)
|
||||||
|
|
||||||
|
|
||||||
self.setCursor()
|
self.initCursor()
|
||||||
self.connected()
|
self.connected()
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
|
|
|
@ -43,7 +43,7 @@ class Connector(GenericConnector):
|
||||||
user=self.user.encode(UNICODE_ENCODING), password=self.password.encode(UNICODE_ENCODING), charset="UTF8") # Reference: http://www.daniweb.com/forums/thread248499.html
|
user=self.user.encode(UNICODE_ENCODING), password=self.password.encode(UNICODE_ENCODING), charset="UTF8") # Reference: http://www.daniweb.com/forums/thread248499.html
|
||||||
except kinterbasdb.OperationalError, msg:
|
except kinterbasdb.OperationalError, msg:
|
||||||
raise SqlmapConnectionException(msg[1])
|
raise SqlmapConnectionException(msg[1])
|
||||||
self.setCursor()
|
self.initCursor()
|
||||||
self.connected()
|
self.connected()
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
|
|
|
@ -44,7 +44,7 @@ class Connector(GenericConnector):
|
||||||
except pymssql.OperationalError, msg:
|
except pymssql.OperationalError, msg:
|
||||||
raise SqlmapConnectionException(msg)
|
raise SqlmapConnectionException(msg)
|
||||||
|
|
||||||
self.setCursor()
|
self.initCursor()
|
||||||
self.connected()
|
self.connected()
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Connector(GenericConnector):
|
||||||
except (pymysql.OperationalError, pymysql.InternalError), msg:
|
except (pymysql.OperationalError, pymysql.InternalError), msg:
|
||||||
raise SqlmapConnectionException(msg[1])
|
raise SqlmapConnectionException(msg[1])
|
||||||
|
|
||||||
self.setCursor()
|
self.initCursor()
|
||||||
self.connected()
|
self.connected()
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Connector(GenericConnector):
|
||||||
except (cx_Oracle.OperationalError, cx_Oracle.DatabaseError), msg:
|
except (cx_Oracle.OperationalError, cx_Oracle.DatabaseError), msg:
|
||||||
raise SqlmapConnectionException(msg)
|
raise SqlmapConnectionException(msg)
|
||||||
|
|
||||||
self.setCursor()
|
self.initCursor()
|
||||||
self.connected()
|
self.connected()
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
|
|
|
@ -41,7 +41,7 @@ class Connector(GenericConnector):
|
||||||
|
|
||||||
self.connector.set_client_encoding('UNICODE')
|
self.connector.set_client_encoding('UNICODE')
|
||||||
|
|
||||||
self.setCursor()
|
self.initCursor()
|
||||||
self.connected()
|
self.connected()
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
|
|
|
@ -63,7 +63,7 @@ class Connector(GenericConnector):
|
||||||
except (self.__sqlite.DatabaseError, self.__sqlite.OperationalError), msg:
|
except (self.__sqlite.DatabaseError, self.__sqlite.OperationalError), msg:
|
||||||
raise SqlmapConnectionException(msg[0])
|
raise SqlmapConnectionException(msg[0])
|
||||||
|
|
||||||
self.setCursor()
|
self.initCursor()
|
||||||
self.connected()
|
self.connected()
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
|
|
|
@ -44,7 +44,7 @@ class Connector(GenericConnector):
|
||||||
except pymssql.OperationalError, msg:
|
except pymssql.OperationalError, msg:
|
||||||
raise SqlmapConnectionException(msg)
|
raise SqlmapConnectionException(msg)
|
||||||
|
|
||||||
self.setCursor()
|
self.initCursor()
|
||||||
self.connected()
|
self.connected()
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
|
|
|
@ -41,12 +41,9 @@ class Connector:
|
||||||
self.connector = None
|
self.connector = None
|
||||||
self.cursor = None
|
self.cursor = None
|
||||||
|
|
||||||
def setCursor(self):
|
def initCursor(self):
|
||||||
self.cursor = self.connector.cursor()
|
self.cursor = self.connector.cursor()
|
||||||
|
|
||||||
def getCursor(self):
|
|
||||||
return self.cursor
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
try:
|
try:
|
||||||
self.cursor.close()
|
self.cursor.close()
|
||||||
|
|
|
@ -21,8 +21,8 @@ from lib.core.common import isTechniqueAvailable
|
||||||
from lib.core.common import parsePasswordHash
|
from lib.core.common import parsePasswordHash
|
||||||
from lib.core.common import randomStr
|
from lib.core.common import randomStr
|
||||||
from lib.core.common import readInput
|
from lib.core.common import readInput
|
||||||
from lib.core.common import strToHex
|
|
||||||
from lib.core.common import unArrayizeValue
|
from lib.core.common import unArrayizeValue
|
||||||
|
from lib.core.convert import hexencode
|
||||||
from lib.core.data import conf
|
from lib.core.data import conf
|
||||||
from lib.core.data import kb
|
from lib.core.data import kb
|
||||||
from lib.core.data import logger
|
from lib.core.data import logger
|
||||||
|
@ -187,7 +187,6 @@ class Users:
|
||||||
|
|
||||||
if retVal:
|
if retVal:
|
||||||
for user, password in filterPairValues(zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr])):
|
for user, password in filterPairValues(zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr])):
|
||||||
# password = "0x%s" % strToHex(password)
|
|
||||||
if user not in kb.data.cachedUsersPasswords:
|
if user not in kb.data.cachedUsersPasswords:
|
||||||
kb.data.cachedUsersPasswords[user] = [password]
|
kb.data.cachedUsersPasswords[user] = [password]
|
||||||
else:
|
else:
|
||||||
|
@ -229,7 +228,7 @@ class Users:
|
||||||
|
|
||||||
if retVal:
|
if retVal:
|
||||||
for user, password in filterPairValues(zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr])):
|
for user, password in filterPairValues(zip(retVal[0]["%s.name" % randStr], retVal[0]["%s.password" % randStr])):
|
||||||
password = "0x%s" % strToHex(password)
|
password = "0x%s" % hexencode(password).upper()
|
||||||
|
|
||||||
if user not in kb.data.cachedUsersPasswords:
|
if user not in kb.data.cachedUsersPasswords:
|
||||||
kb.data.cachedUsersPasswords[user] = [password]
|
kb.data.cachedUsersPasswords[user] = [password]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user