switched to impacket for mssql_login

pull/4/merge
lanjelot 11 years ago
parent 5da0649d2e
commit 2a1b9c5b27

@ -12,10 +12,10 @@
# details (http://www.gnu.org/licenses/gpl.txt). # details (http://www.gnu.org/licenses/gpl.txt).
__author__ = 'Sebastien Macke' __author__ = 'Sebastien Macke'
__twitter__ = '@lanjelot'
__email__ = 'patator@hsc.fr' __email__ = 'patator@hsc.fr'
__url__ = 'http://www.hsc.fr/ressources/outils/patator/' __url__ = 'http://www.hsc.fr/ressources/outils/patator/'
__git__ = 'http://code.google.com/p/patator/' __git__ = 'http://code.google.com/p/patator/'
__twitter__ = 'http://twitter.com/lanjelot'
__version__ = '0.5-beta' __version__ = '0.5-beta'
__license__ = 'GPLv2' __license__ = 'GPLv2'
__banner__ = 'Patator v%s (%s)' % (__version__, __git__) __banner__ = 'Patator v%s (%s)' % (__version__, __git__)
@ -121,7 +121,7 @@ pycurl | HTTP | http://pycurl.sourceforge.net/
-------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------
openldap | LDAP | http://www.openldap.org/ | 2.4.24 | openldap | LDAP | http://www.openldap.org/ | 2.4.24 |
-------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------
impacket | SMB | http://code.google.com/p/impacket/ | svn#525 | impacket | SMB | http://code.google.com/p/impacket/ | svn#765 |
-------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------
cx_Oracle | Oracle | http://cx-oracle.sourceforge.net/ | 5.1.1 | cx_Oracle | Oracle | http://cx-oracle.sourceforge.net/ | 5.1.1 |
-------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------
@ -265,7 +265,7 @@ ftp_login host=NET0 user=anonymous password=test@example.com 0=10.0.0.0/24
}}} }}}
{{{ SSH {{{ SSH
* Brute-force authentication. Do not report wrong passwords. * Brute-force authentication with password same as login (aka single mode). Do not report wrong passwords.
--------- ---------
ssh_login host=10.0.0.1 user=FILE0 password=FILE0 0=logins.txt -x ignore:mesg='Authentication failed.' ssh_login host=10.0.0.1 user=FILE0 password=FILE0 0=logins.txt -x ignore:mesg='Authentication failed.'
@ -574,6 +574,7 @@ TODO
---- ----
* new option -e ns like in Medusa (not likely to be implemented due to design) * new option -e ns like in Medusa (not likely to be implemented due to design)
* replace dnspython|paramiko|IPy with a better module (scapy|libssh2|... ?) * replace dnspython|paramiko|IPy with a better module (scapy|libssh2|... ?)
* use impacket/enum_lookupsids to automatically get the sid
''' '''
# }}} # }}}
@ -608,13 +609,14 @@ logger.addHandler(handler)
# imports {{{ # imports {{{
import re import re
import os import os
from sys import stdin, exc_info, exit, version_info from sys import stdin, exc_info, exit, version_info, maxint
from time import localtime, strftime, sleep, time from time import localtime, strftime, sleep, time
from functools import reduce from functools import reduce
from threading import Thread, active_count from threading import Thread, active_count
from select import select from select import select
from itertools import product, chain, islice from itertools import islice
from string import ascii_lowercase import string
import random
from binascii import hexlify from binascii import hexlify
from base64 import b64encode from base64 import b64encode
from datetime import timedelta, datetime from datetime import timedelta, datetime
@ -1131,7 +1133,6 @@ Please read the README inside for more examples and usage information.
for fname in v.split(','): for fname in v.split(','):
if fname == '-': # stdin if fname == '-': # stdin
from sys import maxint
size += maxint size += maxint
files.append(stdin) files.append(stdin)
@ -1185,7 +1186,7 @@ Please read the README inside for more examples and usage information.
off = self.resume[idx] off = self.resume[idx]
if count < off * len(self.resume): if count < off * len(self.resume):
logger.debug('Skipping %d %s, resume[%d]: %s' % (count, ':'.join(prod), idx, self.resume[idx])) #logger.debug('Skipping %d %s, resume[%d]: %s' % (count, ':'.join(prod), idx, self.resume[idx]))
count += 1 count += 1
continue continue
@ -1600,8 +1601,8 @@ class FTP_login(TCP_Cache):
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [21]'), ('port', 'target port [21]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('tls', 'use TLS [0|1]'), ('tls', 'use TLS [0|1]'),
@ -1664,8 +1665,8 @@ class SSH_login(TCP_Cache):
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [22]'), ('port', 'target port [22]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('auth_type', 'auth type to use [password|keyboard-interactive]'), ('auth_type', 'auth type to use [password|keyboard-interactive]'),
@ -1722,8 +1723,8 @@ class Telnet_login(TCP_Cache):
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [23]'), ('port', 'target port [23]'),
('inputs', 'list of values to input'), ('inputs', 'list of values to input'),
('prompt_re', 'regular expression to match prompts [\w+]'), ('prompt_re', 'regular expression to match prompts [\w+]'),
('timeout', 'seconds to wait for a response and for prompt_re to match received data [20]'), ('timeout', 'seconds to wait for a response and for prompt_re to match received data [20]'),
@ -1778,8 +1779,8 @@ class SMTP_Base(TCP_Cache):
available_options = TCP_Cache.available_options available_options = TCP_Cache.available_options
available_options += ( available_options += (
('timeout', 'seconds to wait for a response [10]'), ('timeout', 'seconds to wait for a response [10]'),
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [25]'), ('port', 'target port [25]'),
('ssl', 'use SSL [0|1]'), ('ssl', 'use SSL [0|1]'),
('helo', 'helo or ehlo command to send after connect [skip]'), ('helo', 'helo or ehlo command to send after connect [skip]'),
('starttls', 'send STARTTLS [0|1]'), ('starttls', 'send STARTTLS [0|1]'),
@ -1924,8 +1925,8 @@ class Finger_lookup:
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [79]'), ('port', 'target port [79]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('timeout', 'seconds to wait for a response [5]'), ('timeout', 'seconds to wait for a response [5]'),
) )
@ -1979,8 +1980,8 @@ class LDAP_login:
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [389]'), ('port', 'target port [389]'),
('binddn', 'usernames to test'), ('binddn', 'usernames to test'),
('bindpw', 'passwords to test'), ('bindpw', 'passwords to test'),
('basedn', 'base DN for search'), ('basedn', 'base DN for search'),
@ -2026,12 +2027,12 @@ class SMB_login(TCP_Cache):
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [139]'), ('port', 'target port [139]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('password_hash', "LM/NT hashes to test, at least one hash must be provided ('lm:nt' or ':nt' or 'lm:')"), ('password_hash', "LM/NT hashes to test, at least one hash must be provided ('lm:nt' or ':nt' or 'lm:')"),
('domain', 'domains to test'), ('domain', 'domain to test'),
) )
available_options += TCP_Cache.available_options available_options += TCP_Cache.available_options
@ -2074,16 +2075,19 @@ class SMB_login(TCP_Cache):
fp, _ = self.bind(host, port) fp, _ = self.bind(host, port)
try: try:
if user is not None: if user is None:
if password is not None: fp.login('', '') # to get computer name
fp.login(user, password, domain) fp.login_standard('', '') # to get workgroup or domain (Primary Domain)
else:
else: if password is None:
lmhash, nthash = password_hash.split(':') lmhash, nthash = password_hash.split(':')
fp.login(user, '', domain, lmhash, nthash) fp.login(user, '', domain, lmhash, nthash)
else:
fp.login(user, password, domain)
logger.debug('No error') logger.debug('No error')
code, mesg = '0', fp.get_server_name() code, mesg = '0', '%s\\%s (%s)' % (fp.get_server_domain(), fp.get_server_name(), fp.get_server_os())
self.reset() self.reset()
@ -2125,8 +2129,8 @@ class SMB_lookupsid(TCP_Cache):
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [139]'), ('port', 'target port [139]'),
('sid', 'SID to test'), ('sid', 'SID to test'),
('rid', 'RID to test'), ('rid', 'RID to test'),
('user', 'username to use if auth required'), ('user', 'username to use if auth required'),
@ -2192,8 +2196,8 @@ class POP_login(TCP_Cache):
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [110]'), ('port', 'target port [110]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('ssl', 'use SSL [0|1]'), ('ssl', 'use SSL [0|1]'),
@ -2244,8 +2248,8 @@ class POP_passd:
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [106]'), ('port', 'target port [106]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('timeout', 'seconds to wait for a response [10]'), ('timeout', 'seconds to wait for a response [10]'),
@ -2293,8 +2297,8 @@ class IMAP_login:
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [143]'), ('port', 'target port [143]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('ssl', 'use SSL [0|1]'), ('ssl', 'use SSL [0|1]'),
@ -2378,8 +2382,8 @@ class VMauthd_login(TCP_Cache):
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [902]'), ('port', 'target port [902]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('ssl', 'use SSL [1|0]'), ('ssl', 'use SSL [1|0]'),
@ -2437,8 +2441,8 @@ class MySQL_login:
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [3306]'), ('port', 'target port [3306]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('timeout', 'seconds to wait for a response [10]'), ('timeout', 'seconds to wait for a response [10]'),
@ -2467,8 +2471,8 @@ class MySQL_query(TCP_Cache):
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'port to use [3306]'), ('port', 'target port [3306]'),
('user', 'username to use'), ('user', 'username to use'),
('password', 'password to use'), ('password', 'password to use'),
('query', 'SQL query to execute'), ('query', 'SQL query to execute'),
@ -2498,74 +2502,11 @@ class MySQL_query(TCP_Cache):
# MSSQL {{{ # MSSQL {{{
# I did not use pymssql because neither version 1.x nor 2.0.0b1_dev were multithreads safe (they all segfault) # I did not use pymssql because neither version 1.x nor 2.0.0b1_dev were multithreads safe (they all segfault)
class MSSQL: try:
# ripped from medusa mssql.c from impacket import tds
hdr = '\x02\x00\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ from impacket.tds import TDS_ERROR_TOKEN, TDS_LOGINACK_TOKEN
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' except ImportError:
warnings.append('impacket')
pt2 = '\x30\x30\x30\x30\x30\x30\x61\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x20\x18\x81\xb8\x2c\x08\x03\x01\x06\x0a\x09\x01\x01\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x73\x71\x75\x65\x6c\x64\x61\x20\x31\x2e\x30\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
pt3 = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x04\x02\x00\x00\x4d\x53\x44\x42\x4c\x49\x42\x00\x00\x00\x07\x06\x00\x00' \
'\x00\x00\x0d\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00'
langp = '\x02\x01\x00\x47\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x30\x30\x00\x00' \
'\x00\x03\x00\x00\x00'
def connect(self, host, port, timeout):
self.fp = socket.create_connection((host, port), timeout)
def login(self, user, password):
MAX_LEN = 30
user_len = len(user)
password_len = len(password)
data = self.hdr + user[:MAX_LEN] + '\x00' * (MAX_LEN - user_len) + chr(user_len) + \
password[:MAX_LEN] + '\x00' * (MAX_LEN - password_len) + chr(password_len) + self.pt2 + chr(password_len) + \
password[:MAX_LEN] + '\x00' * (MAX_LEN - password_len) + self.pt3
self.fp.sendall(data)
self.fp.sendall(self.langp)
resp = self.fp.recv(1024)
code, size = self.parse(resp)
return code, size
def parse(self, resp):
i = 8
while True:
resp = resp[i:]
code, size = unpack('<cH', resp[:3])
#logger.debug('code: %s / size: %d' % (code.encode('hex'), size))
if code == '\xfd': # Done
break
if code in ('\xaa', '\xab') : # Error or Info message
num, state, severity, msg_len = unpack('IBBB', resp[3:10])
msg = resp[11:11+msg_len]
return num, msg
i = size + 3
raise Exception('Failed to parse response')
class MSSQL_login: class MSSQL_login:
'''Brute-force MSSQL''' '''Brute-force MSSQL'''
@ -2574,22 +2515,44 @@ class MSSQL_login:
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [1433]'), ('port', 'target port [1433]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('timeout', 'seconds to wait for a response [10]'), ('windows_auth', 'use Windows auth [0|1]'),
('domain', 'domain to test []'),
('password_hash', "LM/NT hashes to test ('lm:nt' or ':nt')"),
#('timeout', 'seconds to wait for a response [10]'),
) )
available_actions = () available_actions = ()
Response = Response_Base Response = Response_Base
def execute(self, host, port='1433', user='', password='', timeout='10'): def execute(self, host, port='1433', user='', password='', windows_auth='0', domain='', password_hash=None): #, timeout='10'):
m = MSSQL()
m.connect(host, int(port), int(timeout)) fp = tds.MSSQL(host, int(port))
code, mesg = m.login(user, password) fp.connect()
return self.Response(code, mesg)
if windows_auth == '0':
r = fp.login(None, user, password, None, None, False)
else:
r = fp.login(None, user, password, domain, password_hash, True)
if not r:
key = fp.replies[TDS_ERROR_TOKEN][0]
code = key['Number']
mesg = key['MsgText'].decode('utf-16le')
else:
key = fp.replies[TDS_LOGINACK_TOKEN][0]
code = '0'
mesg = '%s (%d%d %d%d)' % (key['ProgName'].decode('utf-16le'), key['MajorVer'], key['MinorVer'], key['BuildNumHi'], key['BuildNumLow'])
fp.disconnect()
return self.Response(code, mesg)
# }}} # }}}
# Oracle {{{ # Oracle {{{
@ -2645,8 +2608,8 @@ class Pgsql_login:
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [5432]'), ('port', 'target port [5432]'),
('user', 'usernames to test'), ('user', 'usernames to test'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('database', 'databases to test [postgres]'), ('database', 'databases to test [postgres]'),
@ -2745,8 +2708,8 @@ class HTTP_fuzz(TCP_Cache):
available_options = ( available_options = (
('url', 'main url to target (scheme://host[:port]/path?query)'), ('url', 'main url to target (scheme://host[:port]/path?query)'),
#('host', 'hostnames or subnets to target'), #('host', 'target host'),
#('port', 'ports to target'), #('port', 'target port'),
#('scheme', 'scheme [http | https]'), #('scheme', 'scheme [http | https]'),
#('path', 'web path [/]'), #('path', 'web path [/]'),
#('query', 'query string'), #('query', 'query string'),
@ -3010,8 +2973,8 @@ class VNC_login:
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [5900]'), ('port', 'target port [5900]'),
('password', 'passwords to test'), ('password', 'passwords to test'),
('timeout', 'seconds to wait for a response [10]'), ('timeout', 'seconds to wait for a response [10]'),
) )
@ -3061,12 +3024,13 @@ def dns_query(server, timeout, protocol, qname, qtype, qclass):
return response return response
def generate_tld(): def generate_tld():
from itertools import product
gtld = [ gtld = [
'aero', 'arpa', 'asia', 'biz', 'cat', 'com', 'coop', 'edu', 'aero', 'arpa', 'asia', 'biz', 'cat', 'com', 'coop', 'edu',
'gov', 'info', 'int', 'jobs', 'mil', 'mobi', 'museum', 'name', 'gov', 'info', 'int', 'jobs', 'mil', 'mobi', 'museum', 'name',
'net', 'org', 'pro', 'tel', 'travel'] 'net', 'org', 'pro', 'tel', 'travel']
cctld = [''.join(i) for i in product(*[ascii_lowercase]*2)] cctld = [''.join(i) for i in product(*[string.ascii_lowercase]*2)]
tld = gtld + cctld tld = gtld + cctld
return tld, len(tld) return tld, len(tld)
@ -3359,8 +3323,8 @@ class SNMP_login:
) )
available_options = ( available_options = (
('host', 'hostnames or subnets to target'), ('host', 'target host'),
('port', 'ports to target [161]'), ('port', 'target port [161]'),
('version', 'SNMP version to use [2|3|1]'), ('version', 'SNMP version to use [2|3|1]'),
#('security_name', 'SNMP v1/v2 username, for most purposes it can be any arbitrary string [test-agent]'), #('security_name', 'SNMP v1/v2 username, for most purposes it can be any arbitrary string [test-agent]'),
('community', 'SNMPv1/2c community names to test [public]'), ('community', 'SNMPv1/2c community names to test [public]'),
@ -3512,7 +3476,7 @@ dependencies = {
'paramiko': [('ssh_login',), 'http://www.lag.net/paramiko/', '1.7.7.1'], 'paramiko': [('ssh_login',), 'http://www.lag.net/paramiko/', '1.7.7.1'],
'pycurl': [('http_fuzz',), 'http://pycurl.sourceforge.net/', '7.19.0'], 'pycurl': [('http_fuzz',), 'http://pycurl.sourceforge.net/', '7.19.0'],
'openldap': [('ldap_login',), 'http://www.openldap.org/', '2.4.24'], 'openldap': [('ldap_login',), 'http://www.openldap.org/', '2.4.24'],
'impacket': [('smb_login','smb_lookupsid'), 'http://oss.coresecurity.com/projects/impacket.html', 'svn#414'], 'impacket': [('smb_login','smb_lookupsid','mssql_login'), 'http://oss.coresecurity.com/projects/impacket.html', 'svn#765'],
'cx_Oracle': [('oracle_login',), 'http://cx-oracle.sourceforge.net/', '5.1.1'], 'cx_Oracle': [('oracle_login',), 'http://cx-oracle.sourceforge.net/', '5.1.1'],
'mysql-python': [('mysql_login',), 'http://sourceforge.net/projects/mysql-python/', '1.2.3'], 'mysql-python': [('mysql_login',), 'http://sourceforge.net/projects/mysql-python/', '1.2.3'],
'psycopg': [('pgsql_login',), 'http://initd.org/psycopg/', '2.4.5'], 'psycopg': [('pgsql_login',), 'http://initd.org/psycopg/', '2.4.5'],
@ -3555,7 +3519,7 @@ Available modules:
# dependencies # dependencies
abort = False abort = False
for w in warnings: for w in set(warnings):
mods, url, ver = dependencies[w] mods, url, ver = dependencies[w]
if name in mods: if name in mods:
print('ERROR: %s %s (%s) is required to run %s.' % (w, ver, url, name)) print('ERROR: %s %s (%s) is required to run %s.' % (w, ver, url, name))

Loading…
Cancel
Save