folder reorganization
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
@@ -0,0 +1,390 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from nmb.NetBIOS import NetBIOS
|
||||
from platformcode import logger
|
||||
from smb.SMBConnection import SMBConnection
|
||||
|
||||
GitHub = 'https://github.com/miketeo/pysmb' #buscar aquí de vez en cuando la última versiónde SMB-pysmb, y actualizar en Alfa
|
||||
vesion_actual_pysmb = '1.1.25' #actualizada el 25/11/2018
|
||||
|
||||
remote = None
|
||||
|
||||
|
||||
def parse_url(url):
|
||||
# logger.info("Url: %s" % url)
|
||||
url = url.strip()
|
||||
patron = "^smb://(?:([^;\n]+);)?(?:([^:@\n]+)[:|@])?(?:([^@\n]+)@)?([^/]+)/([^/\n]+)([/]?.*?)$"
|
||||
domain, user, password, server_name, share_name, path = re.compile(patron, re.DOTALL).match(url).groups()
|
||||
|
||||
server_name, server_ip = get_server_name_ip(server_name)
|
||||
|
||||
if not user: user = 'guest'
|
||||
if not password: password = ""
|
||||
if not domain: domain = ""
|
||||
if path.endswith("/"): path = path[:-1]
|
||||
if not path: path = "/"
|
||||
|
||||
# logger.info("Dominio: '%s' |Usuario: '%s' | Password: '%s' | Servidor: '%s' | IP: '%s' | Share Name: '%s' | Path: '%s'" % (domain, user, password, server_name, server_ip, share_name, path))
|
||||
return server_name, server_ip, share_name, unicode(path, "utf8"), user, password, domain
|
||||
|
||||
|
||||
def get_server_name_ip(server):
|
||||
if re.compile("^\d+.\d+.\d+.\d+$").findall(server) or re.compile("^([^\.]+\.(?:[^\.]+\.)?(?:\w+)?)$").findall(server):
|
||||
server_ip = server
|
||||
server_name = None
|
||||
else:
|
||||
server_ip = None
|
||||
server_name = server.upper()
|
||||
|
||||
if not server_ip: server_ip = NetBIOS().queryName(server_name)[0]
|
||||
if not server_name: server_name = NetBIOS().queryIPForName(server_ip)[0]
|
||||
|
||||
return server_name, server_ip
|
||||
|
||||
|
||||
def connect(url):
|
||||
# logger.info("Url: %s" % url)
|
||||
global remote
|
||||
server_name, server_ip, share_name, path, user, password, domain = parse_url(url)
|
||||
|
||||
#Da problemas asumir que la sesión está abierta. Si se abrió pero ha caducado, dará error. Mejor conectar siempre
|
||||
"""
|
||||
if not remote or not remote.sock or not server_name == remote.remote_name:
|
||||
remote = SMBConnection(user, password, domain, server_name)
|
||||
remote.connect(server_ip, 139)
|
||||
"""
|
||||
remote = SMBConnection(user, password, domain, server_name)
|
||||
remote.connect(ip=server_ip, timeout=20)
|
||||
|
||||
return remote, share_name, path
|
||||
|
||||
|
||||
def listdir(url):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
try:
|
||||
files = [f.filename for f in remote.listPath(share_name, path) if not f.filename in [".", ".."]]
|
||||
return files
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
|
||||
def walk(url, topdown=True, onerror=None):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
|
||||
try:
|
||||
names = remote.listPath(share_name, path)
|
||||
except Exception, _err:
|
||||
if onerror is not None:
|
||||
onerror(_err)
|
||||
return
|
||||
|
||||
dirs, nondirs = [], []
|
||||
for name in names:
|
||||
if name.filename in [".", ".."]:
|
||||
continue
|
||||
if name.isDirectory:
|
||||
dirs.append(name.filename)
|
||||
else:
|
||||
nondirs.append(name.filename)
|
||||
if topdown:
|
||||
yield unicode(url, "utf8"), dirs, nondirs
|
||||
|
||||
for name in dirs:
|
||||
new_path = "/".join(url.split("/") + [name.encode("utf8")])
|
||||
for x in walk(new_path, topdown, onerror):
|
||||
yield x
|
||||
if not topdown:
|
||||
yield unicode(url, "utf8"), dirs, nondirs
|
||||
|
||||
|
||||
def get_attributes(url):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
try:
|
||||
return remote.getAttributes(share_name, path)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
|
||||
def mkdir(url):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
try:
|
||||
remote.createDirectory(share_name, path)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
|
||||
def smb_open(url, mode):
|
||||
logger.info("Url: %s" % url)
|
||||
return SMBFile(url, mode)
|
||||
|
||||
|
||||
def isfile(url):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
try:
|
||||
files = [f.filename for f in remote.listPath(share_name, os.path.dirname(path)) if not f.isDirectory]
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
return os.path.basename(path) in files
|
||||
|
||||
|
||||
def isdir(url):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
try:
|
||||
folders = [f.filename for f in remote.listPath(share_name, os.path.dirname(path)) if f.isDirectory]
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
return os.path.basename(path) in folders or path == "/"
|
||||
|
||||
|
||||
def exists(url):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
try:
|
||||
files = [f.filename for f in remote.listPath(share_name, os.path.dirname(path))]
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
return os.path.basename(path) in files or path == "/"
|
||||
|
||||
|
||||
def remove(url):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
try:
|
||||
remote.deleteFiles(share_name, path)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
|
||||
def rmdir(url):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
try:
|
||||
remote.deleteDirectory(share_name, path)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
|
||||
def rename(url, new_name):
|
||||
logger.info("Url: %s" % url)
|
||||
remote, share_name, path = connect(url)
|
||||
_, _, _, new_name, _, _, _ = parse_url(new_name)
|
||||
try:
|
||||
remote.rename(share_name, path, new_name)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
|
||||
class SMBFile(object):
|
||||
def __init__(self, url, mode="r"):
|
||||
import random
|
||||
try:
|
||||
import xbmc
|
||||
except:
|
||||
xbmc = None
|
||||
self.url = url
|
||||
self.remote, self.share, self.path = path = connect(url)
|
||||
self.mode = mode
|
||||
self.binary = False
|
||||
self.canread = False
|
||||
self.canwrite = False
|
||||
self.closed = True
|
||||
self.size = 0
|
||||
self.pos = 0
|
||||
if xbmc:
|
||||
self.tmp_path = os.path.join(xbmc.translatePath("special://temp/"), "%08x" % (random.getrandbits(32)))
|
||||
else:
|
||||
self.tmp_path = os.path.join(os.getenv("TEMP") or os.getenv("TMP") or os.getenv("TMPDIR"),
|
||||
"%08x" % (random.getrandbits(32)))
|
||||
self.tmp_file = None
|
||||
|
||||
self.__get_mode__()
|
||||
|
||||
def __del__(self):
|
||||
if self.tmp_file:
|
||||
self.tmp_file.close()
|
||||
|
||||
if os.path.isfile(self.tmp_path):
|
||||
os.remove(self.tmp_path)
|
||||
|
||||
def tmpfile(self):
|
||||
if self.tmp_file:
|
||||
self.tmp_file.close()
|
||||
self.tmp_file = open(self.tmp_path, "w+b")
|
||||
return self.tmp_file
|
||||
|
||||
def __get_mode__(self):
|
||||
if "r+" in self.mode:
|
||||
try:
|
||||
attr = self.remote.getAttributes(self.share, self.path)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
self.size = attr.file_size
|
||||
self.canread = True
|
||||
self.canwrite = True
|
||||
self.closed = False
|
||||
|
||||
elif "r" in self.mode:
|
||||
try:
|
||||
attr = self.remote.getAttributes(self.share, self.path)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
self.size = attr.file_size
|
||||
self.canread = True
|
||||
self.closed = False
|
||||
|
||||
elif "w+" in self.mode:
|
||||
try:
|
||||
self.remote.storeFileFromOffset(self.share, self.path, self.tmpfile(), 0, truncate=True)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
self.canread = True
|
||||
self.canwrite = True
|
||||
self.closed = False
|
||||
|
||||
elif "w" in self.mode:
|
||||
try:
|
||||
self.remote.storeFileFromOffset(self.share, self.path, self.tmpfile(), 0, truncate=True)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
self.canwrite = True
|
||||
self.closed = False
|
||||
|
||||
elif "a+" in self.mode:
|
||||
try:
|
||||
self.remote.storeFileFromOffset(self.share, self.path, self.tmpfile(), 0)
|
||||
attr = self.remote.getAttributes(self.share, self.path)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
self.size = attr.file_size
|
||||
self.pos = self.size
|
||||
self.canwrite = True
|
||||
self.canread = True
|
||||
self.closed = False
|
||||
|
||||
elif "a" in self.mode:
|
||||
try:
|
||||
self.remote.storeFileFromOffset(self.share, self.path, self.tmpfile(), 0)
|
||||
attr = self.remote.getAttributes(self.share, self.path)
|
||||
except Exception, e:
|
||||
raise type(e)(e.message, "")
|
||||
|
||||
self.size = attr.file_size
|
||||
self.pos = self.size
|
||||
self.canwrite = True
|
||||
self.closed = False
|
||||
|
||||
if "b" in self.mode:
|
||||
self.binary = True
|
||||
|
||||
def seek(self, offset, whence=0):
|
||||
if whence == 0:
|
||||
self.pos = offset
|
||||
if whence == 1:
|
||||
self.pos += offset
|
||||
if whence == 2:
|
||||
self.pos = self.size + offset
|
||||
|
||||
if self.pos < 0: self.pos = 0
|
||||
|
||||
def tell(self):
|
||||
return self.pos
|
||||
|
||||
def write(self, data):
|
||||
if not self.canwrite:
|
||||
raise IOError("File not open for writing")
|
||||
f = self.tmpfile()
|
||||
f.write(data)
|
||||
f.seek(0)
|
||||
self.remote.storeFileFromOffset(self.share, self.path, f, self.pos)
|
||||
self.pos += len(data)
|
||||
if self.pos > self.size:
|
||||
self.size = self.pos
|
||||
|
||||
def read(self, size=-1L):
|
||||
if not self.canread:
|
||||
raise IOError("File not open for reading")
|
||||
f = self.tmpfile()
|
||||
self.remote.retrieveFileFromOffset(self.share, self.path, f, self.pos, size)
|
||||
f.seek(0)
|
||||
data = f.read()
|
||||
self.seek(len(data), 1)
|
||||
return data
|
||||
|
||||
def truncate(self, size=None):
|
||||
if not self.canwrite:
|
||||
raise IOError("File not open for writing")
|
||||
data = self.read(size)
|
||||
f = self.tmpfile()
|
||||
self.pos = 0
|
||||
f.write(data)
|
||||
f.seek(0)
|
||||
self.remote.storeFileFromOffset(self.share, self.path, f, self.pos, truncate=True)
|
||||
|
||||
def close(self):
|
||||
self.remote.close()
|
||||
self.closed = True
|
||||
self.canwrite = False
|
||||
self.canread = False
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def writelines(self, sequence):
|
||||
for line in sequence:
|
||||
self.write(line)
|
||||
|
||||
def readlines(self, sizehint=0):
|
||||
if not self.canread:
|
||||
raise IOError("File not open for reading")
|
||||
f = self.tmpfile()
|
||||
self.remote.retrieveFileFromOffset(self.share, self.path, f, self.pos)
|
||||
f.seek(0)
|
||||
data = f.readlines(sizehint)
|
||||
self.pos += len(data)
|
||||
|
||||
if not self.binary:
|
||||
data = [l.replace("\r", "") for l in data]
|
||||
return data
|
||||
|
||||
def readline(self, size=-1):
|
||||
if not self.canread:
|
||||
raise IOError("File not open for reading")
|
||||
f = self.tmpfile()
|
||||
self.remote.retrieveFileFromOffset(self.share, self.path, f, self.pos, size)
|
||||
f.seek(0)
|
||||
data = f.readline(size)
|
||||
self.pos += len(data)
|
||||
|
||||
if not self.binary:
|
||||
data = data.replace("\r", "")
|
||||
return data
|
||||
|
||||
def __iter__(self):
|
||||
return self.readlines().__iter__()
|
||||
|
||||
def xreadlines(self):
|
||||
return self.__iter__()
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
return "<open SMBFile '%s', mode '%s' at %s>" % (self.url, self.mode, hex(id(self)))
|
||||
|
||||
@property
|
||||
def __class__(self):
|
||||
return "<type 'file'>"
|
||||
@@ -0,0 +1,144 @@
|
||||
|
||||
import os, logging, random, socket, time, select
|
||||
from base import NBNS, NotConnectedError
|
||||
from nmb_constants import TYPE_CLIENT, TYPE_SERVER, TYPE_WORKSTATION
|
||||
|
||||
class NetBIOS(NBNS):
|
||||
|
||||
log = logging.getLogger('NMB.NetBIOS')
|
||||
|
||||
def __init__(self, broadcast = True, listen_port = 0):
|
||||
"""
|
||||
Instantiate a NetBIOS instance, and creates a IPv4 UDP socket to listen/send NBNS packets.
|
||||
|
||||
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
|
||||
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
|
||||
"""
|
||||
self.broadcast = broadcast
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
if self.broadcast:
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
if listen_port:
|
||||
self.sock.bind(( '', listen_port ))
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the underlying and free resources.
|
||||
|
||||
The NetBIOS instance should not be used to perform any operations after this method returns.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
|
||||
def write(self, data, ip, port):
|
||||
assert self.sock, 'Socket is already closed'
|
||||
self.sock.sendto(data, ( ip, port ))
|
||||
|
||||
def queryName(self, name, ip = '', port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
|
||||
:return: A list of IP addresses in dotted notation (aaa.bbb.ccc.ddd). On timeout, returns None.
|
||||
"""
|
||||
assert self.sock, 'Socket is already closed'
|
||||
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
data = self.prepareNameQuery(trn_id, name)
|
||||
if self.broadcast and not ip:
|
||||
ip = '<broadcast>'
|
||||
elif not ip:
|
||||
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
|
||||
|
||||
self.write(data, ip, port)
|
||||
|
||||
return self._pollForNetBIOSPacket(trn_id, timeout)
|
||||
|
||||
def queryIPForName(self, ip, port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
|
||||
|
||||
The implementation of this function is contributed by Jason Anderson.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
|
||||
:return: A list of string containing the names of the machine at *ip*. On timeout, returns None.
|
||||
"""
|
||||
assert self.sock, 'Socket is already closed'
|
||||
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
data = self.prepareNetNameQuery(trn_id, False)
|
||||
self.write(data, ip, port)
|
||||
ret = self._pollForQueryPacket(trn_id, timeout)
|
||||
if ret:
|
||||
return map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret))
|
||||
else:
|
||||
return None
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _pollForNetBIOSPacket(self, wait_trn_id, timeout):
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
try:
|
||||
_timeout = end_time - time.time()
|
||||
if _timeout <= 0:
|
||||
return None
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
|
||||
if not ready:
|
||||
return None
|
||||
|
||||
data, _ = self.sock.recvfrom(0xFFFF)
|
||||
if len(data) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
trn_id, ret = self.decodePacket(data)
|
||||
|
||||
if trn_id == wait_trn_id:
|
||||
return ret
|
||||
except select.error, ex:
|
||||
if type(ex) is types.TupleType:
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
|
||||
#
|
||||
# Contributed by Jason Anderson
|
||||
#
|
||||
def _pollForQueryPacket(self, wait_trn_id, timeout):
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
try:
|
||||
_timeout = end_time - time.time()
|
||||
if _timeout <= 0:
|
||||
return None
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
|
||||
if not ready:
|
||||
return None
|
||||
|
||||
data, _ = self.sock.recvfrom(0xFFFF)
|
||||
if len(data) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
trn_id, ret = self.decodeIPQueryPacket(data)
|
||||
|
||||
if trn_id == wait_trn_id:
|
||||
return ret
|
||||
except select.error, ex:
|
||||
if type(ex) is types.TupleType:
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
@@ -0,0 +1,136 @@
|
||||
|
||||
import os, logging, random, socket, time
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet.protocol import DatagramProtocol
|
||||
from nmb_constants import TYPE_SERVER
|
||||
from base import NBNS
|
||||
|
||||
IP_QUERY, NAME_QUERY = range(2)
|
||||
|
||||
class NetBIOSTimeout(Exception):
|
||||
"""Raised in NBNSProtocol via Deferred.errback method when queryName method has timeout waiting for reply"""
|
||||
pass
|
||||
|
||||
class NBNSProtocol(DatagramProtocol, NBNS):
|
||||
|
||||
log = logging.getLogger('NMB.NBNSProtocol')
|
||||
|
||||
def __init__(self, broadcast = True, listen_port = 0):
|
||||
"""
|
||||
Instantiate a NBNSProtocol instance.
|
||||
|
||||
This automatically calls reactor.listenUDP method to start listening for incoming packets, so you **must not** call the listenUDP method again.
|
||||
|
||||
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
|
||||
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
|
||||
"""
|
||||
self.broadcast = broadcast
|
||||
self.pending_trns = { } # TRN ID -> ( expiry_time, name, Deferred instance )
|
||||
self.transport = reactor.listenUDP(listen_port, self)
|
||||
if self.broadcast:
|
||||
self.transport.getHandle().setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
reactor.callLater(1, self.cleanupPendingTrns)
|
||||
|
||||
def datagramReceived(self, data, from_info):
|
||||
host, port = from_info
|
||||
trn_id, ret = self.decodePacket(data)
|
||||
|
||||
# pending transaction exists for trn_id - handle it and remove from queue
|
||||
if trn_id in self.pending_trns:
|
||||
_, ip, d = self.pending_trns.pop(trn_id)
|
||||
if ip is NAME_QUERY:
|
||||
# decode as query packet
|
||||
trn_id, ret = self.decodeIPQueryPacket(data)
|
||||
d.callback(ret)
|
||||
|
||||
def write(self, data, ip, port):
|
||||
# We don't use the transport.write method directly as it keeps raising DeprecationWarning for ip='<broadcast>'
|
||||
self.transport.getHandle().sendto(data, ( ip, port ))
|
||||
|
||||
def queryName(self, name, ip = '', port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the returned Deferred instance will be called with a NetBIOSTimeout exception.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of IP addresses in dotted notation (aaa.bbb.ccc.ddd).
|
||||
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
|
||||
"""
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
while True:
|
||||
if not self.pending_trns.has_key(trn_id):
|
||||
break
|
||||
else:
|
||||
trn_id = (trn_id + 1) & 0xFFFF
|
||||
|
||||
data = self.prepareNameQuery(trn_id, name)
|
||||
if self.broadcast and not ip:
|
||||
ip = '<broadcast>'
|
||||
elif not ip:
|
||||
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
|
||||
|
||||
self.write(data, ip, port)
|
||||
|
||||
d = defer.Deferred()
|
||||
self.pending_trns[trn_id] = ( time.time()+timeout, name, d )
|
||||
return d
|
||||
|
||||
def queryIPForName(self, ip, port = 137, timeout = 30):
|
||||
"""
|
||||
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
|
||||
|
||||
The implementation of this function is contributed by Jason Anderson.
|
||||
|
||||
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
|
||||
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
|
||||
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
|
||||
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of names of the machine at *ip*.
|
||||
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
|
||||
"""
|
||||
trn_id = random.randint(1, 0xFFFF)
|
||||
while True:
|
||||
if not self.pending_trns.has_key(trn_id):
|
||||
break
|
||||
else:
|
||||
trn_id = (trn_id + 1) & 0xFFFF
|
||||
|
||||
data = self.prepareNetNameQuery(trn_id)
|
||||
self.write(data, ip, port)
|
||||
|
||||
d = defer.Deferred()
|
||||
d2 = defer.Deferred()
|
||||
d2.addErrback(d.errback)
|
||||
|
||||
def stripCode(ret):
|
||||
if ret is not None: # got valid response. Somehow the callback is also called when there is an error.
|
||||
d.callback(map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret)))
|
||||
|
||||
d2.addCallback(stripCode)
|
||||
self.pending_trns[trn_id] = ( time.time()+timeout, NAME_QUERY, d2 )
|
||||
return d
|
||||
|
||||
def stopProtocol(self):
|
||||
DatagramProtocol.stopProtocol(self)
|
||||
|
||||
def cleanupPendingTrns(self):
|
||||
now = time.time()
|
||||
|
||||
# reply should have been received in the past
|
||||
expired = filter(lambda (trn_id, (expiry_time, name, d)): expiry_time < now, self.pending_trns.iteritems())
|
||||
|
||||
# remove expired items from dict + call errback
|
||||
def expire_item(item):
|
||||
trn_id, (expiry_time, name, d) = item
|
||||
|
||||
del self.pending_trns[trn_id]
|
||||
try:
|
||||
d.errback(NetBIOSTimeout(name))
|
||||
except: pass
|
||||
|
||||
map(expire_item, expired)
|
||||
|
||||
if self.transport:
|
||||
reactor.callLater(1, self.cleanupPendingTrns)
|
||||
@@ -0,0 +1,181 @@
|
||||
|
||||
import struct, logging, random
|
||||
from nmb_constants import *
|
||||
from nmb_structs import *
|
||||
from utils import encode_name
|
||||
|
||||
class NMBSession:
|
||||
|
||||
log = logging.getLogger('NMB.NMBSession')
|
||||
|
||||
def __init__(self, my_name, remote_name, host_type = TYPE_SERVER, is_direct_tcp = False):
|
||||
self.my_name = my_name.upper()
|
||||
self.remote_name = remote_name.upper()
|
||||
self.host_type = host_type
|
||||
self.data_buf = ''
|
||||
|
||||
if is_direct_tcp:
|
||||
self.data_nmb = DirectTCPSessionMessage()
|
||||
self.sendNMBPacket = self._sendNMBPacket_DirectTCP
|
||||
else:
|
||||
self.data_nmb = NMBSessionMessage()
|
||||
self.sendNMBPacket = self._sendNMBPacket_NetBIOS
|
||||
|
||||
#
|
||||
# Overridden Methods
|
||||
#
|
||||
|
||||
def write(self, data):
|
||||
raise NotImplementedError
|
||||
|
||||
def onNMBSessionMessage(self, flags, data):
|
||||
pass
|
||||
|
||||
def onNMBSessionOK(self):
|
||||
pass
|
||||
|
||||
def onNMBSessionFailed(self):
|
||||
pass
|
||||
|
||||
#
|
||||
# Public Methods
|
||||
#
|
||||
|
||||
def feedData(self, data):
|
||||
self.data_buf = self.data_buf + data
|
||||
|
||||
offset = 0
|
||||
while True:
|
||||
length = self.data_nmb.decode(self.data_buf, offset)
|
||||
if length == 0:
|
||||
break
|
||||
elif length > 0:
|
||||
offset += length
|
||||
self._processNMBSessionPacket(self.data_nmb)
|
||||
else:
|
||||
raise NMBError
|
||||
|
||||
if offset > 0:
|
||||
self.data_buf = self.data_buf[offset:]
|
||||
|
||||
def sendNMBMessage(self, data):
|
||||
self.sendNMBPacket(SESSION_MESSAGE, data)
|
||||
|
||||
def requestNMBSession(self):
|
||||
my_name_encoded = encode_name(self.my_name, TYPE_WORKSTATION)
|
||||
remote_name_encoded = encode_name(self.remote_name, self.host_type)
|
||||
self.sendNMBPacket(SESSION_REQUEST, remote_name_encoded + my_name_encoded)
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _processNMBSessionPacket(self, packet):
|
||||
if packet.type == SESSION_MESSAGE:
|
||||
self.onNMBSessionMessage(packet.flags, packet.data)
|
||||
elif packet.type == POSITIVE_SESSION_RESPONSE:
|
||||
self.onNMBSessionOK()
|
||||
elif packet.type == NEGATIVE_SESSION_RESPONSE:
|
||||
self.onNMBSessionFailed()
|
||||
elif packet.type == SESSION_KEEPALIVE:
|
||||
# Discard keepalive packets - [RFC1002]: 5.2.2.1
|
||||
pass
|
||||
else:
|
||||
self.log.warning('Unrecognized NMB session type: 0x%02x', packet.type)
|
||||
|
||||
def _sendNMBPacket_NetBIOS(self, packet_type, data):
|
||||
length = len(data)
|
||||
assert length <= 0x01FFFF
|
||||
flags = 0
|
||||
if length > 0xFFFF:
|
||||
flags |= 0x01
|
||||
length &= 0xFFFF
|
||||
self.write(struct.pack('>BBH', packet_type, flags, length) + data)
|
||||
|
||||
def _sendNMBPacket_DirectTCP(self, packet_type, data):
|
||||
length = len(data)
|
||||
assert length <= 0x00FFFFFF
|
||||
self.write(struct.pack('>I', length) + data)
|
||||
|
||||
|
||||
class NBNS:
|
||||
|
||||
log = logging.getLogger('NMB.NBNS')
|
||||
|
||||
HEADER_STRUCT_FORMAT = '>HHHHHH'
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
def write(self, data, ip, port):
|
||||
raise NotImplementedError
|
||||
|
||||
def decodePacket(self, data):
|
||||
if len(data) < self.HEADER_STRUCT_SIZE:
|
||||
raise Exception
|
||||
|
||||
trn_id, code, question_count, answer_count, authority_count, additional_count = struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
|
||||
|
||||
is_response = bool((code >> 15) & 0x01)
|
||||
opcode = (code >> 11) & 0x0F
|
||||
flags = (code >> 4) & 0x7F
|
||||
rcode = code & 0x0F
|
||||
|
||||
if opcode == 0x0000 and is_response:
|
||||
name_len = ord(data[self.HEADER_STRUCT_SIZE])
|
||||
offset = self.HEADER_STRUCT_SIZE+2+name_len+8 # constant 2 for the padding bytes before/after the Name and constant 8 for the Type, Class and TTL fields in the Answer section after the Name
|
||||
record_count = (struct.unpack('>H', data[offset:offset+2])[0]) / 6
|
||||
|
||||
offset += 4 # Constant 4 for the Data Length and Flags field
|
||||
ret = [ ]
|
||||
for i in range(0, record_count):
|
||||
ret.append('%d.%d.%d.%d' % struct.unpack('4B', (data[offset:offset + 4])))
|
||||
offset += 6
|
||||
return trn_id, ret
|
||||
else:
|
||||
return trn_id, None
|
||||
|
||||
|
||||
def prepareNameQuery(self, trn_id, name, is_broadcast = True):
|
||||
header = struct.pack(self.HEADER_STRUCT_FORMAT,
|
||||
trn_id, (is_broadcast and 0x0110) or 0x0100, 1, 0, 0, 0)
|
||||
payload = encode_name(name, 0x20) + '\x00\x20\x00\x01'
|
||||
|
||||
return header + payload
|
||||
|
||||
#
|
||||
# Contributed by Jason Anderson
|
||||
#
|
||||
def decodeIPQueryPacket(self, data):
|
||||
if len(data) < self.HEADER_STRUCT_SIZE:
|
||||
raise Exception
|
||||
|
||||
trn_id, code, question_count, answer_count, authority_count, additional_count = struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
|
||||
|
||||
is_response = bool((code >> 15) & 0x01)
|
||||
opcode = (code >> 11) & 0x0F
|
||||
flags = (code >> 4) & 0x7F
|
||||
rcode = code & 0x0F
|
||||
numnames = struct.unpack('B', data[self.HEADER_STRUCT_SIZE + 44])[0]
|
||||
|
||||
if numnames > 0:
|
||||
ret = [ ]
|
||||
offset = self.HEADER_STRUCT_SIZE + 45
|
||||
|
||||
for i in range(0, numnames):
|
||||
mynme = data[offset:offset + 15]
|
||||
mynme = mynme.strip()
|
||||
ret.append(( mynme, ord(data[offset+15]) ))
|
||||
offset += 18
|
||||
|
||||
return trn_id, ret
|
||||
else:
|
||||
return trn_id, None
|
||||
|
||||
#
|
||||
# Contributed by Jason Anderson
|
||||
#
|
||||
def prepareNetNameQuery(self, trn_id, is_broadcast = True):
|
||||
header = struct.pack(self.HEADER_STRUCT_FORMAT,
|
||||
trn_id, (is_broadcast and 0x0010) or 0x0000, 1, 0, 0, 0)
|
||||
payload = encode_name('*', 0) + '\x00\x21\x00\x01'
|
||||
|
||||
return header + payload
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
# Default port for NetBIOS name service
|
||||
NETBIOS_NS_PORT = 137
|
||||
|
||||
# Default port for NetBIOS session service
|
||||
NETBIOS_SESSION_PORT = 139
|
||||
|
||||
# Owner Node Type Constants
|
||||
NODE_B = 0x00
|
||||
NODE_P = 0x01
|
||||
NODE_M = 0x10
|
||||
NODE_RESERVED = 0x11
|
||||
|
||||
# Name Type Constants
|
||||
TYPE_UNKNOWN = 0x01
|
||||
TYPE_WORKSTATION = 0x00
|
||||
TYPE_CLIENT = 0x03
|
||||
TYPE_SERVER = 0x20
|
||||
TYPE_DOMAIN_MASTER = 0x1B
|
||||
TYPE_MASTER_BROWSER = 0x1D
|
||||
TYPE_BROWSER = 0x1E
|
||||
|
||||
TYPE_NAMES = { TYPE_UNKNOWN: 'Unknown',
|
||||
TYPE_WORKSTATION: 'Workstation',
|
||||
TYPE_CLIENT: 'Client',
|
||||
TYPE_SERVER: 'Server',
|
||||
TYPE_MASTER_BROWSER: 'Master Browser',
|
||||
TYPE_BROWSER: 'Browser Server',
|
||||
TYPE_DOMAIN_MASTER: 'Domain Master'
|
||||
}
|
||||
|
||||
# Values for Session Packet Type field in Session Packets
|
||||
SESSION_MESSAGE = 0x00
|
||||
SESSION_REQUEST = 0x81
|
||||
POSITIVE_SESSION_RESPONSE = 0x82
|
||||
NEGATIVE_SESSION_RESPONSE = 0x83
|
||||
REGTARGET_SESSION_RESPONSE = 0x84
|
||||
SESSION_KEEPALIVE = 0x85
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
import struct
|
||||
|
||||
class NMBError(Exception): pass
|
||||
|
||||
|
||||
class NotConnectedError(NMBError):
|
||||
"""
|
||||
Raisd when the underlying NMB connection has been disconnected or not connected yet
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NMBSessionMessage:
|
||||
|
||||
HEADER_STRUCT_FORMAT = '>BBH'
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.type = 0
|
||||
self.flags = 0
|
||||
self.data = ''
|
||||
|
||||
def decode(self, data, offset):
|
||||
data_len = len(data)
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE:
|
||||
# Not enough data for decoding
|
||||
return 0
|
||||
|
||||
self.reset()
|
||||
self.type, self.flags, length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])
|
||||
|
||||
if self.flags & 0x01:
|
||||
length |= 0x010000
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
|
||||
return 0
|
||||
|
||||
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
|
||||
return self.HEADER_STRUCT_SIZE + length
|
||||
|
||||
|
||||
class DirectTCPSessionMessage(NMBSessionMessage):
|
||||
|
||||
HEADER_STRUCT_FORMAT = '>I'
|
||||
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
||||
|
||||
def decode(self, data, offset):
|
||||
data_len = len(data)
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE:
|
||||
# Not enough data for decoding
|
||||
return 0
|
||||
|
||||
self.reset()
|
||||
length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])[0]
|
||||
|
||||
if length >> 24 != 0:
|
||||
raise NMBError("Invalid protocol header for Direct TCP session message")
|
||||
|
||||
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
|
||||
return 0
|
||||
|
||||
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
|
||||
return self.HEADER_STRUCT_SIZE + length
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
import string, re
|
||||
|
||||
|
||||
def encode_name(name, type, scope = None):
|
||||
"""
|
||||
Perform first and second level encoding of name as specified in RFC 1001 (Section 4)
|
||||
"""
|
||||
if name == '*':
|
||||
name = name + '\0' * 15
|
||||
elif len(name) > 15:
|
||||
name = name[:15] + chr(type)
|
||||
else:
|
||||
name = string.ljust(name, 15) + chr(type)
|
||||
|
||||
def _do_first_level_encoding(m):
|
||||
s = ord(m.group(0))
|
||||
return string.uppercase[s >> 4] + string.uppercase[s & 0x0f]
|
||||
|
||||
encoded_name = chr(len(name) * 2) + re.sub('.', _do_first_level_encoding, name)
|
||||
if scope:
|
||||
encoded_scope = ''
|
||||
for s in string.split(scope, '.'):
|
||||
encoded_scope = encoded_scope + chr(len(s)) + s
|
||||
return encoded_name + encoded_scope + '\0'
|
||||
else:
|
||||
return encoded_name + '\0'
|
||||
|
||||
|
||||
def decode_name(name):
|
||||
name_length = ord(name[0])
|
||||
assert name_length == 32
|
||||
|
||||
def _do_first_level_decoding(m):
|
||||
s = m.group(0)
|
||||
return chr(((ord(s[0]) - ord('A')) << 4) | (ord(s[1]) - ord('A')))
|
||||
|
||||
decoded_name = re.sub('..', _do_first_level_decoding, name[1:33])
|
||||
if name[33] == '\0':
|
||||
return 34, decoded_name, ''
|
||||
else:
|
||||
decoded_domain = ''
|
||||
offset = 34
|
||||
while 1:
|
||||
domain_length = ord(name[offset])
|
||||
if domain_length == 0:
|
||||
break
|
||||
decoded_domain = '.' + name[offset:offset + domain_length]
|
||||
offset = offset + domain_length
|
||||
return offset + 1, decoded_name, decoded_domain
|
||||
@@ -0,0 +1,8 @@
|
||||
import sys
|
||||
|
||||
# http://www.python.org/dev/peps/pep-0396/
|
||||
__version__ = '0.1.9'
|
||||
|
||||
if sys.version_info[:2] < (2, 4):
|
||||
raise RuntimeError('PyASN1 requires Python 2.4 or later')
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# This file is necessary to make this directory a package.
|
||||
@@ -0,0 +1 @@
|
||||
# This file is necessary to make this directory a package.
|
||||
@@ -0,0 +1,842 @@
|
||||
# BER decoder
|
||||
from pyasn1 import debug, error
|
||||
from pyasn1.codec.ber import eoo
|
||||
from pyasn1.compat.octets import oct2int, isOctetsType
|
||||
from pyasn1.type import tag, univ, char, useful, tagmap
|
||||
|
||||
|
||||
class AbstractDecoder:
|
||||
protoComponent = None
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
raise error.PyAsn1Error('Decoder not implemented for %s' % (tagSet,))
|
||||
|
||||
def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
raise error.PyAsn1Error('Indefinite length mode decoder not implemented for %s' % (tagSet,))
|
||||
|
||||
class AbstractSimpleDecoder(AbstractDecoder):
|
||||
tagFormats = (tag.tagFormatSimple,)
|
||||
def _createComponent(self, asn1Spec, tagSet, value=None):
|
||||
if tagSet[0][1] not in self.tagFormats:
|
||||
raise error.PyAsn1Error('Invalid tag format %s for %s' % (tagSet[0], self.protoComponent.prettyPrintType()))
|
||||
if asn1Spec is None:
|
||||
return self.protoComponent.clone(value, tagSet)
|
||||
elif value is None:
|
||||
return asn1Spec
|
||||
else:
|
||||
return asn1Spec.clone(value)
|
||||
|
||||
class AbstractConstructedDecoder(AbstractDecoder):
|
||||
tagFormats = (tag.tagFormatConstructed,)
|
||||
def _createComponent(self, asn1Spec, tagSet, value=None):
|
||||
if tagSet[0][1] not in self.tagFormats:
|
||||
raise error.PyAsn1Error('Invalid tag format %s for %s' % (tagSet[0], self.protoComponent.prettyPrintType()))
|
||||
if asn1Spec is None:
|
||||
return self.protoComponent.clone(tagSet)
|
||||
else:
|
||||
return asn1Spec.clone()
|
||||
|
||||
class ExplicitTagDecoder(AbstractSimpleDecoder):
|
||||
protoComponent = univ.Any('')
|
||||
tagFormats = (tag.tagFormatConstructed,)
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
if substrateFun:
|
||||
return substrateFun(
|
||||
self._createComponent(asn1Spec, tagSet, ''),
|
||||
substrate, length
|
||||
)
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
value, _ = decodeFun(head, asn1Spec, tagSet, length)
|
||||
return value, tail
|
||||
|
||||
def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
if substrateFun:
|
||||
return substrateFun(
|
||||
self._createComponent(asn1Spec, tagSet, ''),
|
||||
substrate, length
|
||||
)
|
||||
value, substrate = decodeFun(substrate, asn1Spec, tagSet, length)
|
||||
terminator, substrate = decodeFun(substrate, allowEoo=True)
|
||||
if eoo.endOfOctets.isSameTypeWith(terminator) and \
|
||||
terminator == eoo.endOfOctets:
|
||||
return value, substrate
|
||||
else:
|
||||
raise error.PyAsn1Error('Missing end-of-octets terminator')
|
||||
|
||||
explicitTagDecoder = ExplicitTagDecoder()
|
||||
|
||||
class IntegerDecoder(AbstractSimpleDecoder):
|
||||
protoComponent = univ.Integer(0)
|
||||
precomputedValues = {
|
||||
'\x00': 0,
|
||||
'\x01': 1,
|
||||
'\x02': 2,
|
||||
'\x03': 3,
|
||||
'\x04': 4,
|
||||
'\x05': 5,
|
||||
'\x06': 6,
|
||||
'\x07': 7,
|
||||
'\x08': 8,
|
||||
'\x09': 9,
|
||||
'\xff': -1,
|
||||
'\xfe': -2,
|
||||
'\xfd': -3,
|
||||
'\xfc': -4,
|
||||
'\xfb': -5
|
||||
}
|
||||
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length,
|
||||
state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
if not head:
|
||||
return self._createComponent(asn1Spec, tagSet, 0), tail
|
||||
if head in self.precomputedValues:
|
||||
value = self.precomputedValues[head]
|
||||
else:
|
||||
firstOctet = oct2int(head[0])
|
||||
if firstOctet & 0x80:
|
||||
value = -1
|
||||
else:
|
||||
value = 0
|
||||
for octet in head:
|
||||
value = value << 8 | oct2int(octet)
|
||||
return self._createComponent(asn1Spec, tagSet, value), tail
|
||||
|
||||
class BooleanDecoder(IntegerDecoder):
|
||||
protoComponent = univ.Boolean(0)
|
||||
def _createComponent(self, asn1Spec, tagSet, value=None):
|
||||
return IntegerDecoder._createComponent(self, asn1Spec, tagSet, value and 1 or 0)
|
||||
|
||||
class BitStringDecoder(AbstractSimpleDecoder):
|
||||
protoComponent = univ.BitString(())
|
||||
tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed)
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length,
|
||||
state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
if tagSet[0][1] == tag.tagFormatSimple: # XXX what tag to check?
|
||||
if not head:
|
||||
raise error.PyAsn1Error('Empty substrate')
|
||||
trailingBits = oct2int(head[0])
|
||||
if trailingBits > 7:
|
||||
raise error.PyAsn1Error(
|
||||
'Trailing bits overflow %s' % trailingBits
|
||||
)
|
||||
head = head[1:]
|
||||
lsb = p = 0; l = len(head)-1; b = []
|
||||
while p <= l:
|
||||
if p == l:
|
||||
lsb = trailingBits
|
||||
j = 7
|
||||
o = oct2int(head[p])
|
||||
while j >= lsb:
|
||||
b.append((o>>j)&0x01)
|
||||
j = j - 1
|
||||
p = p + 1
|
||||
return self._createComponent(asn1Spec, tagSet, b), tail
|
||||
r = self._createComponent(asn1Spec, tagSet, ())
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
while head:
|
||||
component, head = decodeFun(head, self.protoComponent)
|
||||
r = r + component
|
||||
return r, tail
|
||||
|
||||
def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
r = self._createComponent(asn1Spec, tagSet, '')
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
while substrate:
|
||||
component, substrate = decodeFun(substrate, self.protoComponent,
|
||||
allowEoo=True)
|
||||
if eoo.endOfOctets.isSameTypeWith(component) and \
|
||||
component == eoo.endOfOctets:
|
||||
break
|
||||
r = r + component
|
||||
else:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'No EOO seen before substrate ends'
|
||||
)
|
||||
return r, substrate
|
||||
|
||||
class OctetStringDecoder(AbstractSimpleDecoder):
|
||||
protoComponent = univ.OctetString('')
|
||||
tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed)
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length,
|
||||
state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
if tagSet[0][1] == tag.tagFormatSimple: # XXX what tag to check?
|
||||
return self._createComponent(asn1Spec, tagSet, head), tail
|
||||
r = self._createComponent(asn1Spec, tagSet, '')
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
while head:
|
||||
component, head = decodeFun(head, self.protoComponent)
|
||||
r = r + component
|
||||
return r, tail
|
||||
|
||||
def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
r = self._createComponent(asn1Spec, tagSet, '')
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
while substrate:
|
||||
component, substrate = decodeFun(substrate, self.protoComponent,
|
||||
allowEoo=True)
|
||||
if eoo.endOfOctets.isSameTypeWith(component) and \
|
||||
component == eoo.endOfOctets:
|
||||
break
|
||||
r = r + component
|
||||
else:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'No EOO seen before substrate ends'
|
||||
)
|
||||
return r, substrate
|
||||
|
||||
class NullDecoder(AbstractSimpleDecoder):
|
||||
protoComponent = univ.Null('')
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
r = self._createComponent(asn1Spec, tagSet)
|
||||
if head:
|
||||
raise error.PyAsn1Error('Unexpected %d-octet substrate for Null' % length)
|
||||
return r, tail
|
||||
|
||||
class ObjectIdentifierDecoder(AbstractSimpleDecoder):
|
||||
protoComponent = univ.ObjectIdentifier(())
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length,
|
||||
state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
if not head:
|
||||
raise error.PyAsn1Error('Empty substrate')
|
||||
|
||||
oid = ()
|
||||
index = 0
|
||||
substrateLen = len(head)
|
||||
while index < substrateLen:
|
||||
subId = oct2int(head[index])
|
||||
index += 1
|
||||
if subId < 128:
|
||||
oid = oid + (subId,)
|
||||
elif subId > 128:
|
||||
# Construct subid from a number of octets
|
||||
nextSubId = subId
|
||||
subId = 0
|
||||
while nextSubId >= 128:
|
||||
subId = (subId << 7) + (nextSubId & 0x7F)
|
||||
if index >= substrateLen:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'Short substrate for sub-OID past %s' % (oid,)
|
||||
)
|
||||
nextSubId = oct2int(head[index])
|
||||
index += 1
|
||||
oid = oid + ((subId << 7) + nextSubId,)
|
||||
elif subId == 128:
|
||||
# ASN.1 spec forbids leading zeros (0x80) in OID
|
||||
# encoding, tolerating it opens a vulnerability. See
|
||||
# http://www.cosic.esat.kuleuven.be/publications/article-1432.pdf
|
||||
# page 7
|
||||
raise error.PyAsn1Error('Invalid octet 0x80 in OID encoding')
|
||||
|
||||
# Decode two leading arcs
|
||||
if 0 <= oid[0] <= 39:
|
||||
oid = (0,) + oid
|
||||
elif 40 <= oid[0] <= 79:
|
||||
oid = (1, oid[0]-40) + oid[1:]
|
||||
elif oid[0] >= 80:
|
||||
oid = (2, oid[0]-80) + oid[1:]
|
||||
else:
|
||||
raise error.PyAsn1Error('Malformed first OID octet: %s' % head[0])
|
||||
|
||||
return self._createComponent(asn1Spec, tagSet, oid), tail
|
||||
|
||||
class RealDecoder(AbstractSimpleDecoder):
|
||||
protoComponent = univ.Real()
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
if not head:
|
||||
return self._createComponent(asn1Spec, tagSet, 0.0), tail
|
||||
fo = oct2int(head[0]); head = head[1:]
|
||||
if fo & 0x80: # binary encoding
|
||||
if not head:
|
||||
raise error.PyAsn1Error("Incomplete floating-point value")
|
||||
n = (fo & 0x03) + 1
|
||||
if n == 4:
|
||||
n = oct2int(head[0])
|
||||
head = head[1:]
|
||||
eo, head = head[:n], head[n:]
|
||||
if not eo or not head:
|
||||
raise error.PyAsn1Error('Real exponent screwed')
|
||||
e = oct2int(eo[0]) & 0x80 and -1 or 0
|
||||
while eo: # exponent
|
||||
e <<= 8
|
||||
e |= oct2int(eo[0])
|
||||
eo = eo[1:]
|
||||
b = fo >> 4 & 0x03 # base bits
|
||||
if b > 2:
|
||||
raise error.PyAsn1Error('Illegal Real base')
|
||||
if b == 1: # encbase = 8
|
||||
e *= 3
|
||||
elif b == 2: # encbase = 16
|
||||
e *= 4
|
||||
p = 0
|
||||
while head: # value
|
||||
p <<= 8
|
||||
p |= oct2int(head[0])
|
||||
head = head[1:]
|
||||
if fo & 0x40: # sign bit
|
||||
p = -p
|
||||
sf = fo >> 2 & 0x03 # scale bits
|
||||
p *= 2**sf
|
||||
value = (p, 2, e)
|
||||
elif fo & 0x40: # infinite value
|
||||
value = fo & 0x01 and '-inf' or 'inf'
|
||||
elif fo & 0xc0 == 0: # character encoding
|
||||
if not head:
|
||||
raise error.PyAsn1Error("Incomplete floating-point value")
|
||||
try:
|
||||
if fo & 0x3 == 0x1: # NR1
|
||||
value = (int(head), 10, 0)
|
||||
elif fo & 0x3 == 0x2: # NR2
|
||||
value = float(head)
|
||||
elif fo & 0x3 == 0x3: # NR3
|
||||
value = float(head)
|
||||
else:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'Unknown NR (tag %s)' % fo
|
||||
)
|
||||
except ValueError:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'Bad character Real syntax'
|
||||
)
|
||||
else:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'Unknown encoding (tag %s)' % fo
|
||||
)
|
||||
return self._createComponent(asn1Spec, tagSet, value), tail
|
||||
|
||||
class SequenceDecoder(AbstractConstructedDecoder):
|
||||
protoComponent = univ.Sequence()
|
||||
def _getComponentTagMap(self, r, idx):
|
||||
try:
|
||||
return r.getComponentTagMapNearPosition(idx)
|
||||
except error.PyAsn1Error:
|
||||
return
|
||||
|
||||
def _getComponentPositionByType(self, r, t, idx):
|
||||
return r.getComponentPositionNearType(t, idx)
|
||||
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
r = self._createComponent(asn1Spec, tagSet)
|
||||
idx = 0
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
while head:
|
||||
asn1Spec = self._getComponentTagMap(r, idx)
|
||||
component, head = decodeFun(head, asn1Spec)
|
||||
idx = self._getComponentPositionByType(
|
||||
r, component.getEffectiveTagSet(), idx
|
||||
)
|
||||
r.setComponentByPosition(idx, component, asn1Spec is None)
|
||||
idx = idx + 1
|
||||
r.setDefaultComponents()
|
||||
r.verifySizeSpec()
|
||||
return r, tail
|
||||
|
||||
def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
r = self._createComponent(asn1Spec, tagSet)
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
idx = 0
|
||||
while substrate:
|
||||
asn1Spec = self._getComponentTagMap(r, idx)
|
||||
component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True)
|
||||
if eoo.endOfOctets.isSameTypeWith(component) and \
|
||||
component == eoo.endOfOctets:
|
||||
break
|
||||
idx = self._getComponentPositionByType(
|
||||
r, component.getEffectiveTagSet(), idx
|
||||
)
|
||||
r.setComponentByPosition(idx, component, asn1Spec is None)
|
||||
idx = idx + 1
|
||||
else:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'No EOO seen before substrate ends'
|
||||
)
|
||||
r.setDefaultComponents()
|
||||
r.verifySizeSpec()
|
||||
return r, substrate
|
||||
|
||||
class SequenceOfDecoder(AbstractConstructedDecoder):
|
||||
protoComponent = univ.SequenceOf()
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
r = self._createComponent(asn1Spec, tagSet)
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
asn1Spec = r.getComponentType()
|
||||
idx = 0
|
||||
while head:
|
||||
component, head = decodeFun(head, asn1Spec)
|
||||
r.setComponentByPosition(idx, component, asn1Spec is None)
|
||||
idx = idx + 1
|
||||
r.verifySizeSpec()
|
||||
return r, tail
|
||||
|
||||
def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
r = self._createComponent(asn1Spec, tagSet)
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
asn1Spec = r.getComponentType()
|
||||
idx = 0
|
||||
while substrate:
|
||||
component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True)
|
||||
if eoo.endOfOctets.isSameTypeWith(component) and \
|
||||
component == eoo.endOfOctets:
|
||||
break
|
||||
r.setComponentByPosition(idx, component, asn1Spec is None)
|
||||
idx = idx + 1
|
||||
else:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'No EOO seen before substrate ends'
|
||||
)
|
||||
r.verifySizeSpec()
|
||||
return r, substrate
|
||||
|
||||
class SetDecoder(SequenceDecoder):
|
||||
protoComponent = univ.Set()
|
||||
def _getComponentTagMap(self, r, idx):
|
||||
return r.getComponentTagMap()
|
||||
|
||||
def _getComponentPositionByType(self, r, t, idx):
|
||||
nextIdx = r.getComponentPositionByType(t)
|
||||
if nextIdx is None:
|
||||
return idx
|
||||
else:
|
||||
return nextIdx
|
||||
|
||||
class SetOfDecoder(SequenceOfDecoder):
|
||||
protoComponent = univ.SetOf()
|
||||
|
||||
class ChoiceDecoder(AbstractConstructedDecoder):
|
||||
protoComponent = univ.Choice()
|
||||
tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed)
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
r = self._createComponent(asn1Spec, tagSet)
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
if r.getTagSet() == tagSet: # explicitly tagged Choice
|
||||
component, head = decodeFun(
|
||||
head, r.getComponentTagMap()
|
||||
)
|
||||
else:
|
||||
component, head = decodeFun(
|
||||
head, r.getComponentTagMap(), tagSet, length, state
|
||||
)
|
||||
if isinstance(component, univ.Choice):
|
||||
effectiveTagSet = component.getEffectiveTagSet()
|
||||
else:
|
||||
effectiveTagSet = component.getTagSet()
|
||||
r.setComponentByType(effectiveTagSet, component, 0, asn1Spec is None)
|
||||
return r, tail
|
||||
|
||||
def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
r = self._createComponent(asn1Spec, tagSet)
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
if r.getTagSet() == tagSet: # explicitly tagged Choice
|
||||
component, substrate = decodeFun(substrate, r.getComponentTagMap())
|
||||
# eat up EOO marker
|
||||
eooMarker, substrate = decodeFun(substrate, allowEoo=True)
|
||||
if not eoo.endOfOctets.isSameTypeWith(eooMarker) or \
|
||||
eooMarker != eoo.endOfOctets:
|
||||
raise error.PyAsn1Error('No EOO seen before substrate ends')
|
||||
else:
|
||||
component, substrate= decodeFun(
|
||||
substrate, r.getComponentTagMap(), tagSet, length, state
|
||||
)
|
||||
if isinstance(component, univ.Choice):
|
||||
effectiveTagSet = component.getEffectiveTagSet()
|
||||
else:
|
||||
effectiveTagSet = component.getTagSet()
|
||||
r.setComponentByType(effectiveTagSet, component, 0, asn1Spec is None)
|
||||
return r, substrate
|
||||
|
||||
class AnyDecoder(AbstractSimpleDecoder):
|
||||
protoComponent = univ.Any()
|
||||
tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed)
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
if asn1Spec is None or \
|
||||
asn1Spec is not None and tagSet != asn1Spec.getTagSet():
|
||||
# untagged Any container, recover inner header substrate
|
||||
length = length + len(fullSubstrate) - len(substrate)
|
||||
substrate = fullSubstrate
|
||||
if substrateFun:
|
||||
return substrateFun(self._createComponent(asn1Spec, tagSet),
|
||||
substrate, length)
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
return self._createComponent(asn1Spec, tagSet, value=head), tail
|
||||
|
||||
def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet,
|
||||
length, state, decodeFun, substrateFun):
|
||||
if asn1Spec is not None and tagSet == asn1Spec.getTagSet():
|
||||
# tagged Any type -- consume header substrate
|
||||
header = ''
|
||||
else:
|
||||
# untagged Any, recover header substrate
|
||||
header = fullSubstrate[:-len(substrate)]
|
||||
|
||||
r = self._createComponent(asn1Spec, tagSet, header)
|
||||
|
||||
# Any components do not inherit initial tag
|
||||
asn1Spec = self.protoComponent
|
||||
|
||||
if substrateFun:
|
||||
return substrateFun(r, substrate, length)
|
||||
while substrate:
|
||||
component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True)
|
||||
if eoo.endOfOctets.isSameTypeWith(component) and \
|
||||
component == eoo.endOfOctets:
|
||||
break
|
||||
r = r + component
|
||||
else:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'No EOO seen before substrate ends'
|
||||
)
|
||||
return r, substrate
|
||||
|
||||
# character string types
|
||||
class UTF8StringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.UTF8String()
|
||||
class NumericStringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.NumericString()
|
||||
class PrintableStringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.PrintableString()
|
||||
class TeletexStringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.TeletexString()
|
||||
class VideotexStringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.VideotexString()
|
||||
class IA5StringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.IA5String()
|
||||
class GraphicStringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.GraphicString()
|
||||
class VisibleStringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.VisibleString()
|
||||
class GeneralStringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.GeneralString()
|
||||
class UniversalStringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.UniversalString()
|
||||
class BMPStringDecoder(OctetStringDecoder):
|
||||
protoComponent = char.BMPString()
|
||||
|
||||
# "useful" types
|
||||
class ObjectDescriptorDecoder(OctetStringDecoder):
|
||||
protoComponent = useful.ObjectDescriptor()
|
||||
class GeneralizedTimeDecoder(OctetStringDecoder):
|
||||
protoComponent = useful.GeneralizedTime()
|
||||
class UTCTimeDecoder(OctetStringDecoder):
|
||||
protoComponent = useful.UTCTime()
|
||||
|
||||
tagMap = {
|
||||
univ.Integer.tagSet: IntegerDecoder(),
|
||||
univ.Boolean.tagSet: BooleanDecoder(),
|
||||
univ.BitString.tagSet: BitStringDecoder(),
|
||||
univ.OctetString.tagSet: OctetStringDecoder(),
|
||||
univ.Null.tagSet: NullDecoder(),
|
||||
univ.ObjectIdentifier.tagSet: ObjectIdentifierDecoder(),
|
||||
univ.Enumerated.tagSet: IntegerDecoder(),
|
||||
univ.Real.tagSet: RealDecoder(),
|
||||
univ.Sequence.tagSet: SequenceDecoder(), # conflicts with SequenceOf
|
||||
univ.Set.tagSet: SetDecoder(), # conflicts with SetOf
|
||||
univ.Choice.tagSet: ChoiceDecoder(), # conflicts with Any
|
||||
# character string types
|
||||
char.UTF8String.tagSet: UTF8StringDecoder(),
|
||||
char.NumericString.tagSet: NumericStringDecoder(),
|
||||
char.PrintableString.tagSet: PrintableStringDecoder(),
|
||||
char.TeletexString.tagSet: TeletexStringDecoder(),
|
||||
char.VideotexString.tagSet: VideotexStringDecoder(),
|
||||
char.IA5String.tagSet: IA5StringDecoder(),
|
||||
char.GraphicString.tagSet: GraphicStringDecoder(),
|
||||
char.VisibleString.tagSet: VisibleStringDecoder(),
|
||||
char.GeneralString.tagSet: GeneralStringDecoder(),
|
||||
char.UniversalString.tagSet: UniversalStringDecoder(),
|
||||
char.BMPString.tagSet: BMPStringDecoder(),
|
||||
# useful types
|
||||
useful.ObjectDescriptor.tagSet: ObjectDescriptorDecoder(),
|
||||
useful.GeneralizedTime.tagSet: GeneralizedTimeDecoder(),
|
||||
useful.UTCTime.tagSet: UTCTimeDecoder()
|
||||
}
|
||||
|
||||
# Type-to-codec map for ambiguous ASN.1 types
|
||||
typeMap = {
|
||||
univ.Set.typeId: SetDecoder(),
|
||||
univ.SetOf.typeId: SetOfDecoder(),
|
||||
univ.Sequence.typeId: SequenceDecoder(),
|
||||
univ.SequenceOf.typeId: SequenceOfDecoder(),
|
||||
univ.Choice.typeId: ChoiceDecoder(),
|
||||
univ.Any.typeId: AnyDecoder()
|
||||
}
|
||||
|
||||
( stDecodeTag, stDecodeLength, stGetValueDecoder, stGetValueDecoderByAsn1Spec,
|
||||
stGetValueDecoderByTag, stTryAsExplicitTag, stDecodeValue,
|
||||
stDumpRawValue, stErrorCondition, stStop ) = [x for x in range(10)]
|
||||
|
||||
class Decoder:
|
||||
defaultErrorState = stErrorCondition
|
||||
# defaultErrorState = stDumpRawValue
|
||||
defaultRawDecoder = AnyDecoder()
|
||||
supportIndefLength = True
|
||||
def __init__(self, tagMap, typeMap={}):
|
||||
self.__tagMap = tagMap
|
||||
self.__typeMap = typeMap
|
||||
# Tag & TagSet objects caches
|
||||
self.__tagCache = {}
|
||||
self.__tagSetCache = {}
|
||||
|
||||
def __call__(self, substrate, asn1Spec=None, tagSet=None,
|
||||
length=None, state=stDecodeTag, recursiveFlag=1,
|
||||
substrateFun=None, allowEoo=False):
|
||||
if debug.logger & debug.flagDecoder:
|
||||
debug.logger('decoder called at scope %s with state %d, working with up to %d octets of substrate: %s' % (debug.scope, state, len(substrate), debug.hexdump(substrate)))
|
||||
fullSubstrate = substrate
|
||||
while state != stStop:
|
||||
if state == stDecodeTag:
|
||||
if not substrate:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'Short octet stream on tag decoding'
|
||||
)
|
||||
if not isOctetsType(substrate) and \
|
||||
not isinstance(substrate, univ.OctetString):
|
||||
raise error.PyAsn1Error('Bad octet stream type')
|
||||
# Decode tag
|
||||
firstOctet = substrate[0]
|
||||
substrate = substrate[1:]
|
||||
if firstOctet in self.__tagCache:
|
||||
lastTag = self.__tagCache[firstOctet]
|
||||
else:
|
||||
t = oct2int(firstOctet)
|
||||
# Look for end-of-octets sentinel
|
||||
if t == 0:
|
||||
if substrate and oct2int(substrate[0]) == 0:
|
||||
if allowEoo and self.supportIndefLength:
|
||||
debug.logger and debug.logger & debug.flagDecoder and debug.logger('end-of-octets sentinel found')
|
||||
value, substrate = eoo.endOfOctets, substrate[1:]
|
||||
state = stStop
|
||||
continue
|
||||
else:
|
||||
raise error.PyAsn1Error('Unexpected end-of-contents sentinel')
|
||||
else:
|
||||
raise error.PyAsn1Error('Zero tag encountered')
|
||||
tagClass = t&0xC0
|
||||
tagFormat = t&0x20
|
||||
tagId = t&0x1F
|
||||
if tagId == 0x1F:
|
||||
tagId = 0
|
||||
while 1:
|
||||
if not substrate:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'Short octet stream on long tag decoding'
|
||||
)
|
||||
t = oct2int(substrate[0])
|
||||
tagId = tagId << 7 | (t&0x7F)
|
||||
substrate = substrate[1:]
|
||||
if not t&0x80:
|
||||
break
|
||||
lastTag = tag.Tag(
|
||||
tagClass=tagClass, tagFormat=tagFormat, tagId=tagId
|
||||
)
|
||||
if tagId < 31:
|
||||
# cache short tags
|
||||
self.__tagCache[firstOctet] = lastTag
|
||||
if tagSet is None:
|
||||
if firstOctet in self.__tagSetCache:
|
||||
tagSet = self.__tagSetCache[firstOctet]
|
||||
else:
|
||||
# base tag not recovered
|
||||
tagSet = tag.TagSet((), lastTag)
|
||||
if firstOctet in self.__tagCache:
|
||||
self.__tagSetCache[firstOctet] = tagSet
|
||||
else:
|
||||
tagSet = lastTag + tagSet
|
||||
state = stDecodeLength
|
||||
debug.logger and debug.logger & debug.flagDecoder and debug.logger('tag decoded into %s, decoding length' % tagSet)
|
||||
if state == stDecodeLength:
|
||||
# Decode length
|
||||
if not substrate:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'Short octet stream on length decoding'
|
||||
)
|
||||
firstOctet = oct2int(substrate[0])
|
||||
if firstOctet == 128:
|
||||
size = 1
|
||||
length = -1
|
||||
elif firstOctet < 128:
|
||||
length, size = firstOctet, 1
|
||||
else:
|
||||
size = firstOctet & 0x7F
|
||||
# encoded in size bytes
|
||||
length = 0
|
||||
lengthString = substrate[1:size+1]
|
||||
# missing check on maximum size, which shouldn't be a
|
||||
# problem, we can handle more than is possible
|
||||
if len(lengthString) != size:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'%s<%s at %s' %
|
||||
(size, len(lengthString), tagSet)
|
||||
)
|
||||
for char in lengthString:
|
||||
length = (length << 8) | oct2int(char)
|
||||
size = size + 1
|
||||
substrate = substrate[size:]
|
||||
if length != -1 and len(substrate) < length:
|
||||
raise error.SubstrateUnderrunError(
|
||||
'%d-octet short' % (length - len(substrate))
|
||||
)
|
||||
if length == -1 and not self.supportIndefLength:
|
||||
error.PyAsn1Error('Indefinite length encoding not supported by this codec')
|
||||
state = stGetValueDecoder
|
||||
debug.logger and debug.logger & debug.flagDecoder and debug.logger('value length decoded into %d, payload substrate is: %s' % (length, debug.hexdump(length == -1 and substrate or substrate[:length])))
|
||||
if state == stGetValueDecoder:
|
||||
if asn1Spec is None:
|
||||
state = stGetValueDecoderByTag
|
||||
else:
|
||||
state = stGetValueDecoderByAsn1Spec
|
||||
#
|
||||
# There're two ways of creating subtypes in ASN.1 what influences
|
||||
# decoder operation. These methods are:
|
||||
# 1) Either base types used in or no IMPLICIT tagging has been
|
||||
# applied on subtyping.
|
||||
# 2) Subtype syntax drops base type information (by means of
|
||||
# IMPLICIT tagging.
|
||||
# The first case allows for complete tag recovery from substrate
|
||||
# while the second one requires original ASN.1 type spec for
|
||||
# decoding.
|
||||
#
|
||||
# In either case a set of tags (tagSet) is coming from substrate
|
||||
# in an incremental, tag-by-tag fashion (this is the case of
|
||||
# EXPLICIT tag which is most basic). Outermost tag comes first
|
||||
# from the wire.
|
||||
#
|
||||
if state == stGetValueDecoderByTag:
|
||||
if tagSet in self.__tagMap:
|
||||
concreteDecoder = self.__tagMap[tagSet]
|
||||
else:
|
||||
concreteDecoder = None
|
||||
if concreteDecoder:
|
||||
state = stDecodeValue
|
||||
else:
|
||||
_k = tagSet[:1]
|
||||
if _k in self.__tagMap:
|
||||
concreteDecoder = self.__tagMap[_k]
|
||||
else:
|
||||
concreteDecoder = None
|
||||
if concreteDecoder:
|
||||
state = stDecodeValue
|
||||
else:
|
||||
state = stTryAsExplicitTag
|
||||
if debug.logger and debug.logger & debug.flagDecoder:
|
||||
debug.logger('codec %s chosen by a built-in type, decoding %s' % (concreteDecoder and concreteDecoder.__class__.__name__ or "<none>", state == stDecodeValue and 'value' or 'as explicit tag'))
|
||||
debug.scope.push(concreteDecoder is None and '?' or concreteDecoder.protoComponent.__class__.__name__)
|
||||
if state == stGetValueDecoderByAsn1Spec:
|
||||
if isinstance(asn1Spec, (dict, tagmap.TagMap)):
|
||||
if tagSet in asn1Spec:
|
||||
__chosenSpec = asn1Spec[tagSet]
|
||||
else:
|
||||
__chosenSpec = None
|
||||
if debug.logger and debug.logger & debug.flagDecoder:
|
||||
debug.logger('candidate ASN.1 spec is a map of:')
|
||||
for t, v in asn1Spec.getPosMap().items():
|
||||
debug.logger(' %s -> %s' % (t, v.__class__.__name__))
|
||||
if asn1Spec.getNegMap():
|
||||
debug.logger('but neither of: ')
|
||||
for t, v in asn1Spec.getNegMap().items():
|
||||
debug.logger(' %s -> %s' % (t, v.__class__.__name__))
|
||||
debug.logger('new candidate ASN.1 spec is %s, chosen by %s' % (__chosenSpec is None and '<none>' or __chosenSpec.prettyPrintType(), tagSet))
|
||||
else:
|
||||
__chosenSpec = asn1Spec
|
||||
debug.logger and debug.logger & debug.flagDecoder and debug.logger('candidate ASN.1 spec is %s' % asn1Spec.__class__.__name__)
|
||||
if __chosenSpec is not None and (
|
||||
tagSet == __chosenSpec.getTagSet() or \
|
||||
tagSet in __chosenSpec.getTagMap()
|
||||
):
|
||||
# use base type for codec lookup to recover untagged types
|
||||
baseTagSet = __chosenSpec.baseTagSet
|
||||
if __chosenSpec.typeId is not None and \
|
||||
__chosenSpec.typeId in self.__typeMap:
|
||||
# ambiguous type
|
||||
concreteDecoder = self.__typeMap[__chosenSpec.typeId]
|
||||
debug.logger and debug.logger & debug.flagDecoder and debug.logger('value decoder chosen for an ambiguous type by type ID %s' % (__chosenSpec.typeId,))
|
||||
elif baseTagSet in self.__tagMap:
|
||||
# base type or tagged subtype
|
||||
concreteDecoder = self.__tagMap[baseTagSet]
|
||||
debug.logger and debug.logger & debug.flagDecoder and debug.logger('value decoder chosen by base %s' % (baseTagSet,))
|
||||
else:
|
||||
concreteDecoder = None
|
||||
if concreteDecoder:
|
||||
asn1Spec = __chosenSpec
|
||||
state = stDecodeValue
|
||||
else:
|
||||
state = stTryAsExplicitTag
|
||||
else:
|
||||
concreteDecoder = None
|
||||
state = stTryAsExplicitTag
|
||||
if debug.logger and debug.logger & debug.flagDecoder:
|
||||
debug.logger('codec %s chosen by ASN.1 spec, decoding %s' % (state == stDecodeValue and concreteDecoder.__class__.__name__ or "<none>", state == stDecodeValue and 'value' or 'as explicit tag'))
|
||||
debug.scope.push(__chosenSpec is None and '?' or __chosenSpec.__class__.__name__)
|
||||
if state == stTryAsExplicitTag:
|
||||
if tagSet and \
|
||||
tagSet[0][1] == tag.tagFormatConstructed and \
|
||||
tagSet[0][0] != tag.tagClassUniversal:
|
||||
# Assume explicit tagging
|
||||
concreteDecoder = explicitTagDecoder
|
||||
state = stDecodeValue
|
||||
else:
|
||||
concreteDecoder = None
|
||||
state = self.defaultErrorState
|
||||
debug.logger and debug.logger & debug.flagDecoder and debug.logger('codec %s chosen, decoding %s' % (concreteDecoder and concreteDecoder.__class__.__name__ or "<none>", state == stDecodeValue and 'value' or 'as failure'))
|
||||
if state == stDumpRawValue:
|
||||
concreteDecoder = self.defaultRawDecoder
|
||||
debug.logger and debug.logger & debug.flagDecoder and debug.logger('codec %s chosen, decoding value' % concreteDecoder.__class__.__name__)
|
||||
state = stDecodeValue
|
||||
if state == stDecodeValue:
|
||||
if recursiveFlag == 0 and not substrateFun: # legacy
|
||||
substrateFun = lambda a,b,c: (a,b[:c])
|
||||
if length == -1: # indef length
|
||||
value, substrate = concreteDecoder.indefLenValueDecoder(
|
||||
fullSubstrate, substrate, asn1Spec, tagSet, length,
|
||||
stGetValueDecoder, self, substrateFun
|
||||
)
|
||||
else:
|
||||
value, substrate = concreteDecoder.valueDecoder(
|
||||
fullSubstrate, substrate, asn1Spec, tagSet, length,
|
||||
stGetValueDecoder, self, substrateFun
|
||||
)
|
||||
state = stStop
|
||||
debug.logger and debug.logger & debug.flagDecoder and debug.logger('codec %s yields type %s, value:\n%s\n...remaining substrate is: %s' % (concreteDecoder.__class__.__name__, value.__class__.__name__, value.prettyPrint(), substrate and debug.hexdump(substrate) or '<none>'))
|
||||
if state == stErrorCondition:
|
||||
raise error.PyAsn1Error(
|
||||
'%s not in asn1Spec: %s' % (tagSet, asn1Spec)
|
||||
)
|
||||
if debug.logger and debug.logger & debug.flagDecoder:
|
||||
debug.scope.pop()
|
||||
debug.logger('decoder left scope %s, call completed' % debug.scope)
|
||||
return value, substrate
|
||||
|
||||
decode = Decoder(tagMap, typeMap)
|
||||
|
||||
# XXX
|
||||
# non-recursive decoding; return position rather than substrate
|
||||
@@ -0,0 +1,434 @@
|
||||
# BER encoder
|
||||
from pyasn1 import debug, error
|
||||
from pyasn1.codec.ber import eoo
|
||||
from pyasn1.compat.octets import int2oct, oct2int, ints2octs, null, str2octs
|
||||
from pyasn1.type import base, tag, univ, char, useful
|
||||
|
||||
|
||||
class Error(Exception): pass
|
||||
|
||||
class AbstractItemEncoder:
|
||||
supportIndefLenMode = 1
|
||||
def encodeTag(self, t, isConstructed):
|
||||
tagClass, tagFormat, tagId = t.asTuple() # this is a hotspot
|
||||
v = tagClass | tagFormat
|
||||
if isConstructed:
|
||||
v = v|tag.tagFormatConstructed
|
||||
if tagId < 31:
|
||||
return int2oct(v|tagId)
|
||||
else:
|
||||
s = int2oct(tagId&0x7f)
|
||||
tagId = tagId >> 7
|
||||
while tagId:
|
||||
s = int2oct(0x80|(tagId&0x7f)) + s
|
||||
tagId = tagId >> 7
|
||||
return int2oct(v|0x1F) + s
|
||||
|
||||
def encodeLength(self, length, defMode):
|
||||
if not defMode and self.supportIndefLenMode:
|
||||
return int2oct(0x80)
|
||||
if length < 0x80:
|
||||
return int2oct(length)
|
||||
else:
|
||||
substrate = null
|
||||
while length:
|
||||
substrate = int2oct(length&0xff) + substrate
|
||||
length = length >> 8
|
||||
substrateLen = len(substrate)
|
||||
if substrateLen > 126:
|
||||
raise Error('Length octets overflow (%d)' % substrateLen)
|
||||
return int2oct(0x80 | substrateLen) + substrate
|
||||
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
raise Error('Not implemented')
|
||||
|
||||
def _encodeEndOfOctets(self, encodeFun, defMode):
|
||||
if defMode or not self.supportIndefLenMode:
|
||||
return null
|
||||
else:
|
||||
return encodeFun(eoo.endOfOctets, defMode)
|
||||
|
||||
def encode(self, encodeFun, value, defMode, maxChunkSize):
|
||||
substrate, isConstructed = self.encodeValue(
|
||||
encodeFun, value, defMode, maxChunkSize
|
||||
)
|
||||
tagSet = value.getTagSet()
|
||||
if tagSet:
|
||||
if not isConstructed: # primitive form implies definite mode
|
||||
defMode = 1
|
||||
return self.encodeTag(
|
||||
tagSet[-1], isConstructed
|
||||
) + self.encodeLength(
|
||||
len(substrate), defMode
|
||||
) + substrate + self._encodeEndOfOctets(encodeFun, defMode)
|
||||
else:
|
||||
return substrate # untagged value
|
||||
|
||||
class EndOfOctetsEncoder(AbstractItemEncoder):
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
return null, 0
|
||||
|
||||
class ExplicitlyTaggedItemEncoder(AbstractItemEncoder):
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
if isinstance(value, base.AbstractConstructedAsn1Item):
|
||||
value = value.clone(tagSet=value.getTagSet()[:-1],
|
||||
cloneValueFlag=1)
|
||||
else:
|
||||
value = value.clone(tagSet=value.getTagSet()[:-1])
|
||||
return encodeFun(value, defMode, maxChunkSize), 1
|
||||
|
||||
explicitlyTaggedItemEncoder = ExplicitlyTaggedItemEncoder()
|
||||
|
||||
class BooleanEncoder(AbstractItemEncoder):
|
||||
supportIndefLenMode = 0
|
||||
_true = ints2octs((1,))
|
||||
_false = ints2octs((0,))
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
return value and self._true or self._false, 0
|
||||
|
||||
class IntegerEncoder(AbstractItemEncoder):
|
||||
supportIndefLenMode = 0
|
||||
supportCompactZero = False
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
if value == 0: # shortcut for zero value
|
||||
if self.supportCompactZero:
|
||||
# this seems to be a correct way for encoding zeros
|
||||
return null, 0
|
||||
else:
|
||||
# this seems to be a widespread way for encoding zeros
|
||||
return ints2octs((0,)), 0
|
||||
octets = []
|
||||
value = int(value) # to save on ops on asn1 type
|
||||
while 1:
|
||||
octets.insert(0, value & 0xff)
|
||||
if value == 0 or value == -1:
|
||||
break
|
||||
value = value >> 8
|
||||
if value == 0 and octets[0] & 0x80:
|
||||
octets.insert(0, 0)
|
||||
while len(octets) > 1 and \
|
||||
(octets[0] == 0 and octets[1] & 0x80 == 0 or \
|
||||
octets[0] == 0xff and octets[1] & 0x80 != 0):
|
||||
del octets[0]
|
||||
return ints2octs(octets), 0
|
||||
|
||||
class BitStringEncoder(AbstractItemEncoder):
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
if not maxChunkSize or len(value) <= maxChunkSize*8:
|
||||
out_len = (len(value) + 7) // 8
|
||||
out_list = out_len * [0]
|
||||
j = 7
|
||||
i = -1
|
||||
for val in value:
|
||||
j += 1
|
||||
if j == 8:
|
||||
i += 1
|
||||
j = 0
|
||||
out_list[i] = out_list[i] | val << (7-j)
|
||||
return int2oct(7-j) + ints2octs(out_list), 0
|
||||
else:
|
||||
pos = 0; substrate = null
|
||||
while 1:
|
||||
# count in octets
|
||||
v = value.clone(value[pos*8:pos*8+maxChunkSize*8])
|
||||
if not v:
|
||||
break
|
||||
substrate = substrate + encodeFun(v, defMode, maxChunkSize)
|
||||
pos = pos + maxChunkSize
|
||||
return substrate, 1
|
||||
|
||||
class OctetStringEncoder(AbstractItemEncoder):
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
if not maxChunkSize or len(value) <= maxChunkSize:
|
||||
return value.asOctets(), 0
|
||||
else:
|
||||
pos = 0; substrate = null
|
||||
while 1:
|
||||
v = value.clone(value[pos:pos+maxChunkSize])
|
||||
if not v:
|
||||
break
|
||||
substrate = substrate + encodeFun(v, defMode, maxChunkSize)
|
||||
pos = pos + maxChunkSize
|
||||
return substrate, 1
|
||||
|
||||
class NullEncoder(AbstractItemEncoder):
|
||||
supportIndefLenMode = 0
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
return null, 0
|
||||
|
||||
class ObjectIdentifierEncoder(AbstractItemEncoder):
|
||||
supportIndefLenMode = 0
|
||||
precomputedValues = {
|
||||
(1, 3, 6, 1, 2): (43, 6, 1, 2),
|
||||
(1, 3, 6, 1, 4): (43, 6, 1, 4)
|
||||
}
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
oid = value.asTuple()
|
||||
if oid[:5] in self.precomputedValues:
|
||||
octets = self.precomputedValues[oid[:5]]
|
||||
oid = oid[5:]
|
||||
else:
|
||||
if len(oid) < 2:
|
||||
raise error.PyAsn1Error('Short OID %s' % (value,))
|
||||
|
||||
octets = ()
|
||||
|
||||
# Build the first twos
|
||||
if oid[0] == 0 and 0 <= oid[1] <= 39:
|
||||
oid = (oid[1],) + oid[2:]
|
||||
elif oid[0] == 1 and 0 <= oid[1] <= 39:
|
||||
oid = (oid[1] + 40,) + oid[2:]
|
||||
elif oid[0] == 2:
|
||||
oid = (oid[1] + 80,) + oid[2:]
|
||||
else:
|
||||
raise error.PyAsn1Error(
|
||||
'Impossible initial arcs %s at %s' % (oid[:2], value)
|
||||
)
|
||||
|
||||
# Cycle through subIds
|
||||
for subId in oid:
|
||||
if subId > -1 and subId < 128:
|
||||
# Optimize for the common case
|
||||
octets = octets + (subId & 0x7f,)
|
||||
elif subId < 0:
|
||||
raise error.PyAsn1Error(
|
||||
'Negative OID arc %s at %s' % (subId, value)
|
||||
)
|
||||
else:
|
||||
# Pack large Sub-Object IDs
|
||||
res = (subId & 0x7f,)
|
||||
subId = subId >> 7
|
||||
while subId > 0:
|
||||
res = (0x80 | (subId & 0x7f),) + res
|
||||
subId = subId >> 7
|
||||
# Add packed Sub-Object ID to resulted Object ID
|
||||
octets += res
|
||||
|
||||
return ints2octs(octets), 0
|
||||
|
||||
class RealEncoder(AbstractItemEncoder):
|
||||
supportIndefLenMode = 0
|
||||
binEncBase = 2 # set to None to choose encoding base automatically
|
||||
def _dropFloatingPoint(self, m, encbase, e):
|
||||
ms, es = 1, 1
|
||||
if m < 0:
|
||||
ms = -1 # mantissa sign
|
||||
if e < 0:
|
||||
es = -1 # exponenta sign
|
||||
m *= ms
|
||||
if encbase == 8:
|
||||
m = m*2**(abs(e) % 3 * es)
|
||||
e = abs(e) // 3 * es
|
||||
elif encbase == 16:
|
||||
m = m*2**(abs(e) % 4 * es)
|
||||
e = abs(e) // 4 * es
|
||||
|
||||
while 1:
|
||||
if int(m) != m:
|
||||
m *= encbase
|
||||
e -= 1
|
||||
continue
|
||||
break
|
||||
return ms, int(m), encbase, e
|
||||
|
||||
def _chooseEncBase(self, value):
|
||||
m, b, e = value
|
||||
base = [2, 8, 16]
|
||||
if value.binEncBase in base:
|
||||
return self._dropFloatingPoint(m, value.binEncBase, e)
|
||||
elif self.binEncBase in base:
|
||||
return self._dropFloatingPoint(m, self.binEncBase, e)
|
||||
# auto choosing base 2/8/16
|
||||
mantissa = [m, m, m]
|
||||
exponenta = [e, e, e]
|
||||
encbase = 2
|
||||
e = float('inf')
|
||||
for i in range(3):
|
||||
sign, mantissa[i], base[i], exponenta[i] = \
|
||||
self._dropFloatingPoint(mantissa[i], base[i], exponenta[i])
|
||||
if abs(exponenta[i]) < abs(e) or \
|
||||
(abs(exponenta[i]) == abs(e) and mantissa[i] < m):
|
||||
e = exponenta[i]
|
||||
m = int(mantissa[i])
|
||||
encbase = base[i]
|
||||
return sign, m, encbase, e
|
||||
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
if value.isPlusInfinity():
|
||||
return int2oct(0x40), 0
|
||||
if value.isMinusInfinity():
|
||||
return int2oct(0x41), 0
|
||||
m, b, e = value
|
||||
if not m:
|
||||
return null, 0
|
||||
if b == 10:
|
||||
return str2octs('\x03%dE%s%d' % (m, e == 0 and '+' or '', e)), 0
|
||||
elif b == 2:
|
||||
fo = 0x80 # binary encoding
|
||||
ms, m, encbase, e = self._chooseEncBase(value)
|
||||
if ms < 0: # mantissa sign
|
||||
fo = fo | 0x40 # sign bit
|
||||
# exponenta & mantissa normalization
|
||||
if encbase == 2:
|
||||
while m & 0x1 == 0:
|
||||
m >>= 1
|
||||
e += 1
|
||||
elif encbase == 8:
|
||||
while m & 0x7 == 0:
|
||||
m >>= 3
|
||||
e += 1
|
||||
fo |= 0x10
|
||||
else: # encbase = 16
|
||||
while m & 0xf == 0:
|
||||
m >>= 4
|
||||
e += 1
|
||||
fo |= 0x20
|
||||
sf = 0 # scale factor
|
||||
while m & 0x1 == 0:
|
||||
m >>= 1
|
||||
sf += 1
|
||||
if sf > 3:
|
||||
raise error.PyAsn1Error('Scale factor overflow') # bug if raised
|
||||
fo |= sf << 2
|
||||
eo = null
|
||||
if e == 0 or e == -1:
|
||||
eo = int2oct(e&0xff)
|
||||
else:
|
||||
while e not in (0, -1):
|
||||
eo = int2oct(e&0xff) + eo
|
||||
e >>= 8
|
||||
if e == 0 and eo and oct2int(eo[0]) & 0x80:
|
||||
eo = int2oct(0) + eo
|
||||
if e == -1 and eo and not (oct2int(eo[0]) & 0x80):
|
||||
eo = int2oct(0xff) + eo
|
||||
n = len(eo)
|
||||
if n > 0xff:
|
||||
raise error.PyAsn1Error('Real exponent overflow')
|
||||
if n == 1:
|
||||
pass
|
||||
elif n == 2:
|
||||
fo |= 1
|
||||
elif n == 3:
|
||||
fo |= 2
|
||||
else:
|
||||
fo |= 3
|
||||
eo = int2oct(n&0xff) + eo
|
||||
po = null
|
||||
while m:
|
||||
po = int2oct(m&0xff) + po
|
||||
m >>= 8
|
||||
substrate = int2oct(fo) + eo + po
|
||||
return substrate, 0
|
||||
else:
|
||||
raise error.PyAsn1Error('Prohibited Real base %s' % b)
|
||||
|
||||
class SequenceEncoder(AbstractItemEncoder):
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
value.setDefaultComponents()
|
||||
value.verifySizeSpec()
|
||||
substrate = null; idx = len(value)
|
||||
while idx > 0:
|
||||
idx = idx - 1
|
||||
if value[idx] is None: # Optional component
|
||||
continue
|
||||
component = value.getDefaultComponentByPosition(idx)
|
||||
if component is not None and component == value[idx]:
|
||||
continue
|
||||
substrate = encodeFun(
|
||||
value[idx], defMode, maxChunkSize
|
||||
) + substrate
|
||||
return substrate, 1
|
||||
|
||||
class SequenceOfEncoder(AbstractItemEncoder):
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
value.verifySizeSpec()
|
||||
substrate = null; idx = len(value)
|
||||
while idx > 0:
|
||||
idx = idx - 1
|
||||
substrate = encodeFun(
|
||||
value[idx], defMode, maxChunkSize
|
||||
) + substrate
|
||||
return substrate, 1
|
||||
|
||||
class ChoiceEncoder(AbstractItemEncoder):
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
return encodeFun(value.getComponent(), defMode, maxChunkSize), 1
|
||||
|
||||
class AnyEncoder(OctetStringEncoder):
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
return value.asOctets(), defMode == 0
|
||||
|
||||
tagMap = {
|
||||
eoo.endOfOctets.tagSet: EndOfOctetsEncoder(),
|
||||
univ.Boolean.tagSet: BooleanEncoder(),
|
||||
univ.Integer.tagSet: IntegerEncoder(),
|
||||
univ.BitString.tagSet: BitStringEncoder(),
|
||||
univ.OctetString.tagSet: OctetStringEncoder(),
|
||||
univ.Null.tagSet: NullEncoder(),
|
||||
univ.ObjectIdentifier.tagSet: ObjectIdentifierEncoder(),
|
||||
univ.Enumerated.tagSet: IntegerEncoder(),
|
||||
univ.Real.tagSet: RealEncoder(),
|
||||
# Sequence & Set have same tags as SequenceOf & SetOf
|
||||
univ.SequenceOf.tagSet: SequenceOfEncoder(),
|
||||
univ.SetOf.tagSet: SequenceOfEncoder(),
|
||||
univ.Choice.tagSet: ChoiceEncoder(),
|
||||
# character string types
|
||||
char.UTF8String.tagSet: OctetStringEncoder(),
|
||||
char.NumericString.tagSet: OctetStringEncoder(),
|
||||
char.PrintableString.tagSet: OctetStringEncoder(),
|
||||
char.TeletexString.tagSet: OctetStringEncoder(),
|
||||
char.VideotexString.tagSet: OctetStringEncoder(),
|
||||
char.IA5String.tagSet: OctetStringEncoder(),
|
||||
char.GraphicString.tagSet: OctetStringEncoder(),
|
||||
char.VisibleString.tagSet: OctetStringEncoder(),
|
||||
char.GeneralString.tagSet: OctetStringEncoder(),
|
||||
char.UniversalString.tagSet: OctetStringEncoder(),
|
||||
char.BMPString.tagSet: OctetStringEncoder(),
|
||||
# useful types
|
||||
useful.ObjectDescriptor.tagSet: OctetStringEncoder(),
|
||||
useful.GeneralizedTime.tagSet: OctetStringEncoder(),
|
||||
useful.UTCTime.tagSet: OctetStringEncoder()
|
||||
}
|
||||
|
||||
# Type-to-codec map for ambiguous ASN.1 types
|
||||
typeMap = {
|
||||
univ.Set.typeId: SequenceEncoder(),
|
||||
univ.SetOf.typeId: SequenceOfEncoder(),
|
||||
univ.Sequence.typeId: SequenceEncoder(),
|
||||
univ.SequenceOf.typeId: SequenceOfEncoder(),
|
||||
univ.Choice.typeId: ChoiceEncoder(),
|
||||
univ.Any.typeId: AnyEncoder()
|
||||
}
|
||||
|
||||
class Encoder:
|
||||
supportIndefLength = True
|
||||
def __init__(self, tagMap, typeMap={}):
|
||||
self.__tagMap = tagMap
|
||||
self.__typeMap = typeMap
|
||||
|
||||
def __call__(self, value, defMode=True, maxChunkSize=0):
|
||||
if not defMode and not self.supportIndefLength:
|
||||
raise error.PyAsn1Error('Indefinite length encoding not supported by this codec')
|
||||
debug.logger & debug.flagEncoder and debug.logger('encoder called in %sdef mode, chunk size %s for type %s, value:\n%s' % (not defMode and 'in' or '', maxChunkSize, value.prettyPrintType(), value.prettyPrint()))
|
||||
tagSet = value.getTagSet()
|
||||
if len(tagSet) > 1:
|
||||
concreteEncoder = explicitlyTaggedItemEncoder
|
||||
else:
|
||||
if value.typeId is not None and value.typeId in self.__typeMap:
|
||||
concreteEncoder = self.__typeMap[value.typeId]
|
||||
elif tagSet in self.__tagMap:
|
||||
concreteEncoder = self.__tagMap[tagSet]
|
||||
else:
|
||||
tagSet = value.baseTagSet
|
||||
if tagSet in self.__tagMap:
|
||||
concreteEncoder = self.__tagMap[tagSet]
|
||||
else:
|
||||
raise Error('No encoder for %s' % (value,))
|
||||
debug.logger & debug.flagEncoder and debug.logger('using value codec %s chosen by %s' % (concreteEncoder.__class__.__name__, tagSet))
|
||||
substrate = concreteEncoder.encode(
|
||||
self, value, defMode, maxChunkSize
|
||||
)
|
||||
debug.logger & debug.flagEncoder and debug.logger('built %s octets of substrate: %s\nencoder completed' % (len(substrate), debug.hexdump(substrate)))
|
||||
return substrate
|
||||
|
||||
encode = Encoder(tagMap, typeMap)
|
||||
@@ -0,0 +1,8 @@
|
||||
from pyasn1.type import base, tag
|
||||
|
||||
class EndOfOctets(base.AbstractSimpleAsn1Item):
|
||||
defaultValue = 0
|
||||
tagSet = tag.initTagSet(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x00)
|
||||
)
|
||||
endOfOctets = EndOfOctets()
|
||||
@@ -0,0 +1 @@
|
||||
# This file is necessary to make this directory a package.
|
||||
@@ -0,0 +1,36 @@
|
||||
# CER decoder
|
||||
from pyasn1 import error
|
||||
from pyasn1.codec.ber import decoder
|
||||
from pyasn1.compat.octets import oct2int
|
||||
from pyasn1.type import univ
|
||||
|
||||
|
||||
class BooleanDecoder(decoder.AbstractSimpleDecoder):
|
||||
protoComponent = univ.Boolean(0)
|
||||
def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length,
|
||||
state, decodeFun, substrateFun):
|
||||
head, tail = substrate[:length], substrate[length:]
|
||||
if not head or length != 1:
|
||||
raise error.PyAsn1Error('Not single-octet Boolean payload')
|
||||
byte = oct2int(head[0])
|
||||
# CER/DER specifies encoding of TRUE as 0xFF and FALSE as 0x0, while
|
||||
# BER allows any non-zero value as TRUE; cf. sections 8.2.2. and 11.1
|
||||
# in http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
|
||||
if byte == 0xff:
|
||||
value = 1
|
||||
elif byte == 0x00:
|
||||
value = 0
|
||||
else:
|
||||
raise error.PyAsn1Error('Unexpected Boolean payload: %s' % byte)
|
||||
return self._createComponent(asn1Spec, tagSet, value), tail
|
||||
|
||||
tagMap = decoder.tagMap.copy()
|
||||
tagMap.update({
|
||||
univ.Boolean.tagSet: BooleanDecoder()
|
||||
})
|
||||
|
||||
typeMap = decoder.typeMap
|
||||
|
||||
class Decoder(decoder.Decoder): pass
|
||||
|
||||
decode = Decoder(tagMap, decoder.typeMap)
|
||||
@@ -0,0 +1,131 @@
|
||||
# CER encoder
|
||||
from pyasn1 import error
|
||||
from pyasn1.codec.ber import encoder
|
||||
from pyasn1.compat.octets import int2oct, str2octs, null
|
||||
from pyasn1.type import univ
|
||||
from pyasn1.type import useful
|
||||
|
||||
|
||||
class BooleanEncoder(encoder.IntegerEncoder):
|
||||
def encodeValue(self, encodeFun, client, defMode, maxChunkSize):
|
||||
if client == 0:
|
||||
substrate = int2oct(0)
|
||||
else:
|
||||
substrate = int2oct(255)
|
||||
return substrate, 0
|
||||
|
||||
class BitStringEncoder(encoder.BitStringEncoder):
|
||||
def encodeValue(self, encodeFun, client, defMode, maxChunkSize):
|
||||
return encoder.BitStringEncoder.encodeValue(
|
||||
self, encodeFun, client, defMode, 1000
|
||||
)
|
||||
|
||||
class OctetStringEncoder(encoder.OctetStringEncoder):
|
||||
def encodeValue(self, encodeFun, client, defMode, maxChunkSize):
|
||||
return encoder.OctetStringEncoder.encodeValue(
|
||||
self, encodeFun, client, defMode, 1000
|
||||
)
|
||||
|
||||
class RealEncoder(encoder.RealEncoder):
|
||||
def _chooseEncBase(self, value):
|
||||
m, b, e = value
|
||||
return self._dropFloatingPoint(m, b, e)
|
||||
|
||||
# specialized GeneralStringEncoder here
|
||||
|
||||
class GeneralizedTimeEncoder(OctetStringEncoder):
|
||||
zchar = str2octs('Z')
|
||||
pluschar = str2octs('+')
|
||||
minuschar = str2octs('-')
|
||||
zero = str2octs('0')
|
||||
def encodeValue(self, encodeFun, client, defMode, maxChunkSize):
|
||||
octets = client.asOctets()
|
||||
# This breaks too many existing data items
|
||||
# if '.' not in octets:
|
||||
# raise error.PyAsn1Error('Format must include fraction of second: %r' % octets)
|
||||
if len(octets) < 15:
|
||||
raise error.PyAsn1Error('Bad UTC time length: %r' % octets)
|
||||
if self.pluschar in octets or self.minuschar in octets:
|
||||
raise error.PyAsn1Error('Must be UTC time: %r' % octets)
|
||||
if octets[-1] != self.zchar[0]:
|
||||
raise error.PyAsn1Error('Missing timezone specifier: %r' % octets)
|
||||
return encoder.OctetStringEncoder.encodeValue(
|
||||
self, encodeFun, client, defMode, 1000
|
||||
)
|
||||
|
||||
class UTCTimeEncoder(encoder.OctetStringEncoder):
|
||||
zchar = str2octs('Z')
|
||||
pluschar = str2octs('+')
|
||||
minuschar = str2octs('-')
|
||||
def encodeValue(self, encodeFun, client, defMode, maxChunkSize):
|
||||
octets = client.asOctets()
|
||||
if self.pluschar in octets or self.minuschar in octets:
|
||||
raise error.PyAsn1Error('Must be UTC time: %r' % octets)
|
||||
if octets and octets[-1] != self.zchar[0]:
|
||||
client = client.clone(octets + self.zchar)
|
||||
if len(client) != 13:
|
||||
raise error.PyAsn1Error('Bad UTC time length: %r' % client)
|
||||
return encoder.OctetStringEncoder.encodeValue(
|
||||
self, encodeFun, client, defMode, 1000
|
||||
)
|
||||
|
||||
class SetOfEncoder(encoder.SequenceOfEncoder):
|
||||
def encodeValue(self, encodeFun, client, defMode, maxChunkSize):
|
||||
if isinstance(client, univ.SequenceAndSetBase):
|
||||
client.setDefaultComponents()
|
||||
client.verifySizeSpec()
|
||||
substrate = null; idx = len(client)
|
||||
# This is certainly a hack but how else do I distinguish SetOf
|
||||
# from Set if they have the same tags&constraints?
|
||||
if isinstance(client, univ.SequenceAndSetBase):
|
||||
# Set
|
||||
comps = []
|
||||
while idx > 0:
|
||||
idx = idx - 1
|
||||
if client[idx] is None: # Optional component
|
||||
continue
|
||||
if client.getDefaultComponentByPosition(idx) == client[idx]:
|
||||
continue
|
||||
comps.append(client[idx])
|
||||
comps.sort(key=lambda x: isinstance(x, univ.Choice) and \
|
||||
x.getMinTagSet() or x.getTagSet())
|
||||
for c in comps:
|
||||
substrate += encodeFun(c, defMode, maxChunkSize)
|
||||
else:
|
||||
# SetOf
|
||||
compSubs = []
|
||||
while idx > 0:
|
||||
idx = idx - 1
|
||||
compSubs.append(
|
||||
encodeFun(client[idx], defMode, maxChunkSize)
|
||||
)
|
||||
compSubs.sort() # perhaps padding's not needed
|
||||
substrate = null
|
||||
for compSub in compSubs:
|
||||
substrate += compSub
|
||||
return substrate, 1
|
||||
|
||||
tagMap = encoder.tagMap.copy()
|
||||
tagMap.update({
|
||||
univ.Boolean.tagSet: BooleanEncoder(),
|
||||
univ.BitString.tagSet: BitStringEncoder(),
|
||||
univ.OctetString.tagSet: OctetStringEncoder(),
|
||||
univ.Real.tagSet: RealEncoder(),
|
||||
useful.GeneralizedTime.tagSet: GeneralizedTimeEncoder(),
|
||||
useful.UTCTime.tagSet: UTCTimeEncoder(),
|
||||
univ.SetOf().tagSet: SetOfEncoder() # conflcts with Set
|
||||
})
|
||||
|
||||
typeMap = encoder.typeMap.copy()
|
||||
typeMap.update({
|
||||
univ.Set.typeId: SetOfEncoder(),
|
||||
univ.SetOf.typeId: SetOfEncoder()
|
||||
})
|
||||
|
||||
class Encoder(encoder.Encoder):
|
||||
def __call__(self, client, defMode=False, maxChunkSize=0):
|
||||
return encoder.Encoder.__call__(self, client, defMode, maxChunkSize)
|
||||
|
||||
encode = Encoder(tagMap, typeMap)
|
||||
|
||||
# EncoderFactory queries class instance and builds a map of tags -> encoders
|
||||
@@ -0,0 +1 @@
|
||||
# This file is necessary to make this directory a package.
|
||||
@@ -0,0 +1,9 @@
|
||||
# DER decoder
|
||||
from pyasn1.codec.cer import decoder
|
||||
|
||||
tagMap = decoder.tagMap
|
||||
typeMap = decoder.typeMap
|
||||
class Decoder(decoder.Decoder):
|
||||
supportIndefLength = False
|
||||
|
||||
decode = Decoder(tagMap, typeMap)
|
||||
@@ -0,0 +1,33 @@
|
||||
# DER encoder
|
||||
from pyasn1 import error
|
||||
from pyasn1.codec.cer import encoder
|
||||
from pyasn1.type import univ
|
||||
|
||||
|
||||
class SetOfEncoder(encoder.SetOfEncoder):
|
||||
def _cmpSetComponents(self, c1, c2):
|
||||
tagSet1 = isinstance(c1, univ.Choice) and \
|
||||
c1.getEffectiveTagSet() or c1.getTagSet()
|
||||
tagSet2 = isinstance(c2, univ.Choice) and \
|
||||
c2.getEffectiveTagSet() or c2.getTagSet()
|
||||
return cmp(tagSet1, tagSet2)
|
||||
|
||||
tagMap = encoder.tagMap.copy()
|
||||
tagMap.update({
|
||||
# Overload CER encoders with BER ones (a bit hackerish XXX)
|
||||
univ.BitString.tagSet: encoder.encoder.BitStringEncoder(),
|
||||
univ.OctetString.tagSet: encoder.encoder.OctetStringEncoder(),
|
||||
# Set & SetOf have same tags
|
||||
univ.SetOf().tagSet: SetOfEncoder()
|
||||
})
|
||||
|
||||
typeMap = encoder.typeMap
|
||||
|
||||
class Encoder(encoder.Encoder):
|
||||
supportIndefLength = False
|
||||
def __call__(self, client, defMode=True, maxChunkSize=0):
|
||||
if not defMode:
|
||||
raise error.PyAsn1Error('DER forbids indefinite length mode')
|
||||
return encoder.Encoder.__call__(self, client, defMode, maxChunkSize)
|
||||
|
||||
encode = Encoder(tagMap, typeMap)
|
||||
@@ -0,0 +1 @@
|
||||
# This file is necessary to make this directory a package.
|
||||
@@ -0,0 +1,10 @@
|
||||
from sys import version_info
|
||||
|
||||
if version_info[0:2] < (2, 6):
|
||||
def bin(x):
|
||||
if x <= 1:
|
||||
return '0b'+str(x)
|
||||
else:
|
||||
return bin(x>>1) + str(x&1)
|
||||
else:
|
||||
bin = bin
|
||||
@@ -0,0 +1,22 @@
|
||||
from sys import version_info
|
||||
|
||||
if version_info[0] <= 2:
|
||||
int2oct = chr
|
||||
ints2octs = lambda s: ''.join([ int2oct(x) for x in s ])
|
||||
null = ''
|
||||
oct2int = ord
|
||||
octs2ints = lambda s: [ oct2int(x) for x in s ]
|
||||
str2octs = lambda x: x
|
||||
octs2str = lambda x: x
|
||||
isOctetsType = lambda s: isinstance(s, str)
|
||||
isStringType = lambda s: isinstance(s, (str, unicode))
|
||||
else:
|
||||
ints2octs = bytes
|
||||
int2oct = lambda x: ints2octs((x,))
|
||||
null = ints2octs()
|
||||
oct2int = lambda x: x
|
||||
octs2ints = lambda s: [ x for x in s ]
|
||||
str2octs = lambda x: x.encode()
|
||||
octs2str = lambda x: x.decode()
|
||||
isOctetsType = lambda s: isinstance(s, bytes)
|
||||
isStringType = lambda s: isinstance(s, str)
|
||||
@@ -0,0 +1,110 @@
|
||||
import logging
|
||||
|
||||
from pyasn1 import __version__
|
||||
from pyasn1 import error
|
||||
from pyasn1.compat.octets import octs2ints
|
||||
|
||||
flagNone = 0x0000
|
||||
flagEncoder = 0x0001
|
||||
flagDecoder = 0x0002
|
||||
flagAll = 0xffff
|
||||
|
||||
flagMap = {
|
||||
'encoder': flagEncoder,
|
||||
'decoder': flagDecoder,
|
||||
'all': flagAll
|
||||
}
|
||||
|
||||
class Printer:
|
||||
def __init__(self, logger=None, handler=None, formatter=None):
|
||||
if logger is None:
|
||||
logger = logging.getLogger('pyasn1')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
if handler is None:
|
||||
handler = logging.StreamHandler()
|
||||
if formatter is None:
|
||||
formatter = logging.Formatter('%(asctime)s %(name)s: %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
handler.setLevel(logging.DEBUG)
|
||||
logger.addHandler(handler)
|
||||
self.__logger = logger
|
||||
|
||||
def __call__(self, msg): self.__logger.debug(msg)
|
||||
def __str__(self): return '<python built-in logging>'
|
||||
|
||||
if hasattr(logging, 'NullHandler'):
|
||||
NullHandler = logging.NullHandler
|
||||
else:
|
||||
# Python 2.6 and older
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
class Debug:
|
||||
defaultPrinter = None
|
||||
def __init__(self, *flags, **options):
|
||||
self._flags = flagNone
|
||||
if options.get('printer') is not None:
|
||||
self._printer = options.get('printer')
|
||||
elif self.defaultPrinter is not None:
|
||||
self._printer = self.defaultPrinter
|
||||
if 'loggerName' in options:
|
||||
# route our logs to parent logger
|
||||
self._printer = Printer(
|
||||
logger=logging.getLogger(options['loggerName']),
|
||||
handler=NullHandler()
|
||||
)
|
||||
else:
|
||||
self._printer = Printer()
|
||||
self('running pyasn1 version %s' % __version__)
|
||||
for f in flags:
|
||||
inverse = f and f[0] in ('!', '~')
|
||||
if inverse:
|
||||
f = f[1:]
|
||||
try:
|
||||
if inverse:
|
||||
self._flags &= ~flagMap[f]
|
||||
else:
|
||||
self._flags |= flagMap[f]
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error('bad debug flag %s' % f)
|
||||
|
||||
self('debug category \'%s\' %s' % (f, inverse and 'disabled' or 'enabled'))
|
||||
|
||||
def __str__(self):
|
||||
return 'logger %s, flags %x' % (self._printer, self._flags)
|
||||
|
||||
def __call__(self, msg):
|
||||
self._printer(msg)
|
||||
|
||||
def __and__(self, flag):
|
||||
return self._flags & flag
|
||||
|
||||
def __rand__(self, flag):
|
||||
return flag & self._flags
|
||||
|
||||
logger = 0
|
||||
|
||||
def setLogger(l):
|
||||
global logger
|
||||
logger = l
|
||||
|
||||
def hexdump(octets):
|
||||
return ' '.join(
|
||||
[ '%s%.2X' % (n%16 == 0 and ('\n%.5d: ' % n) or '', x)
|
||||
for n,x in zip(range(len(octets)), octs2ints(octets)) ]
|
||||
)
|
||||
|
||||
class Scope:
|
||||
def __init__(self):
|
||||
self._list = []
|
||||
|
||||
def __str__(self): return '.'.join(self._list)
|
||||
|
||||
def push(self, token):
|
||||
self._list.append(token)
|
||||
|
||||
def pop(self):
|
||||
return self._list.pop()
|
||||
|
||||
scope = Scope()
|
||||
@@ -0,0 +1,3 @@
|
||||
class PyAsn1Error(Exception): pass
|
||||
class ValueConstraintError(PyAsn1Error): pass
|
||||
class SubstrateUnderrunError(PyAsn1Error): pass
|
||||
@@ -0,0 +1 @@
|
||||
# This file is necessary to make this directory a package.
|
||||
@@ -0,0 +1,280 @@
|
||||
# Base classes for ASN.1 types
|
||||
import sys
|
||||
|
||||
from pyasn1 import error
|
||||
from pyasn1.type import constraint, tagmap, tag
|
||||
|
||||
|
||||
class Asn1Item: pass
|
||||
|
||||
class Asn1ItemBase(Asn1Item):
|
||||
# Set of tags for this ASN.1 type
|
||||
tagSet = tag.TagSet()
|
||||
|
||||
# A list of constraint.Constraint instances for checking values
|
||||
subtypeSpec = constraint.ConstraintsIntersection()
|
||||
|
||||
# Used for ambiguous ASN.1 types identification
|
||||
typeId = None
|
||||
|
||||
def __init__(self, tagSet=None, subtypeSpec=None):
|
||||
if tagSet is None:
|
||||
self._tagSet = self.tagSet
|
||||
else:
|
||||
self._tagSet = tagSet
|
||||
if subtypeSpec is None:
|
||||
self._subtypeSpec = self.subtypeSpec
|
||||
else:
|
||||
self._subtypeSpec = subtypeSpec
|
||||
|
||||
def _verifySubtypeSpec(self, value, idx=None):
|
||||
try:
|
||||
self._subtypeSpec(value, idx)
|
||||
except error.PyAsn1Error:
|
||||
c, i, t = sys.exc_info()
|
||||
raise c('%s at %s' % (i, self.__class__.__name__))
|
||||
|
||||
def getSubtypeSpec(self): return self._subtypeSpec
|
||||
|
||||
def getTagSet(self): return self._tagSet
|
||||
def getEffectiveTagSet(self): return self._tagSet # used by untagged types
|
||||
def getTagMap(self): return tagmap.TagMap({self._tagSet: self})
|
||||
|
||||
def isSameTypeWith(self, other, matchTags=True, matchConstraints=True):
|
||||
return self is other or \
|
||||
(not matchTags or \
|
||||
self._tagSet == other.getTagSet()) and \
|
||||
(not matchConstraints or \
|
||||
self._subtypeSpec==other.getSubtypeSpec())
|
||||
|
||||
def isSuperTypeOf(self, other, matchTags=True, matchConstraints=True):
|
||||
"""Returns true if argument is a ASN1 subtype of ourselves"""
|
||||
return (not matchTags or \
|
||||
self._tagSet.isSuperTagSetOf(other.getTagSet())) and \
|
||||
(not matchConstraints or \
|
||||
(self._subtypeSpec.isSuperTypeOf(other.getSubtypeSpec())))
|
||||
|
||||
class NoValue:
|
||||
def __getattr__(self, attr):
|
||||
raise error.PyAsn1Error('No value for %s()' % attr)
|
||||
def __getitem__(self, i):
|
||||
raise error.PyAsn1Error('No value')
|
||||
def __repr__(self): return '%s()' % self.__class__.__name__
|
||||
|
||||
noValue = NoValue()
|
||||
|
||||
# Base class for "simple" ASN.1 objects. These are immutable.
|
||||
class AbstractSimpleAsn1Item(Asn1ItemBase):
|
||||
defaultValue = noValue
|
||||
def __init__(self, value=None, tagSet=None, subtypeSpec=None):
|
||||
Asn1ItemBase.__init__(self, tagSet, subtypeSpec)
|
||||
if value is None or value is noValue:
|
||||
value = self.defaultValue
|
||||
if value is None or value is noValue:
|
||||
self.__hashedValue = value = noValue
|
||||
else:
|
||||
value = self.prettyIn(value)
|
||||
self._verifySubtypeSpec(value)
|
||||
self.__hashedValue = hash(value)
|
||||
self._value = value
|
||||
self._len = None
|
||||
|
||||
def __repr__(self):
|
||||
r = []
|
||||
if self._value is not self.defaultValue:
|
||||
r.append(self.prettyOut(self._value))
|
||||
if self._tagSet is not self.tagSet:
|
||||
r.append('tagSet=%r' % (self._tagSet,))
|
||||
if self._subtypeSpec is not self.subtypeSpec:
|
||||
r.append('subtypeSpec=%r' % (self._subtypeSpec,))
|
||||
return '%s(%s)' % (self.__class__.__name__, ', '.join(r))
|
||||
|
||||
def __str__(self): return str(self._value)
|
||||
def __eq__(self, other):
|
||||
return self is other and True or self._value == other
|
||||
def __ne__(self, other): return self._value != other
|
||||
def __lt__(self, other): return self._value < other
|
||||
def __le__(self, other): return self._value <= other
|
||||
def __gt__(self, other): return self._value > other
|
||||
def __ge__(self, other): return self._value >= other
|
||||
if sys.version_info[0] <= 2:
|
||||
def __nonzero__(self): return bool(self._value)
|
||||
else:
|
||||
def __bool__(self): return bool(self._value)
|
||||
def __hash__(self):
|
||||
return self.__hashedValue is noValue and hash(noValue) or self.__hashedValue
|
||||
|
||||
def hasValue(self):
|
||||
return not isinstance(self._value, NoValue)
|
||||
|
||||
def clone(self, value=None, tagSet=None, subtypeSpec=None):
|
||||
if value is None and tagSet is None and subtypeSpec is None:
|
||||
return self
|
||||
if value is None:
|
||||
value = self._value
|
||||
if tagSet is None:
|
||||
tagSet = self._tagSet
|
||||
if subtypeSpec is None:
|
||||
subtypeSpec = self._subtypeSpec
|
||||
return self.__class__(value, tagSet, subtypeSpec)
|
||||
|
||||
def subtype(self, value=None, implicitTag=None, explicitTag=None,
|
||||
subtypeSpec=None):
|
||||
if value is None:
|
||||
value = self._value
|
||||
if implicitTag is not None:
|
||||
tagSet = self._tagSet.tagImplicitly(implicitTag)
|
||||
elif explicitTag is not None:
|
||||
tagSet = self._tagSet.tagExplicitly(explicitTag)
|
||||
else:
|
||||
tagSet = self._tagSet
|
||||
if subtypeSpec is None:
|
||||
subtypeSpec = self._subtypeSpec
|
||||
else:
|
||||
subtypeSpec = subtypeSpec + self._subtypeSpec
|
||||
return self.__class__(value, tagSet, subtypeSpec)
|
||||
|
||||
def prettyIn(self, value): return value
|
||||
def prettyOut(self, value): return str(value)
|
||||
|
||||
def prettyPrint(self, scope=0):
|
||||
if self.hasValue():
|
||||
return self.prettyOut(self._value)
|
||||
else:
|
||||
return '<no value>'
|
||||
|
||||
# XXX Compatibility stub
|
||||
def prettyPrinter(self, scope=0): return self.prettyPrint(scope)
|
||||
|
||||
def prettyPrintType(self, scope=0):
|
||||
return '%s -> %s' % (self.getTagSet(), self.__class__.__name__)
|
||||
|
||||
#
|
||||
# Constructed types:
|
||||
# * There are five of them: Sequence, SequenceOf/SetOf, Set and Choice
|
||||
# * ASN1 types and values are represened by Python class instances
|
||||
# * Value initialization is made for defaulted components only
|
||||
# * Primary method of component addressing is by-position. Data model for base
|
||||
# type is Python sequence. Additional type-specific addressing methods
|
||||
# may be implemented for particular types.
|
||||
# * SequenceOf and SetOf types do not implement any additional methods
|
||||
# * Sequence, Set and Choice types also implement by-identifier addressing
|
||||
# * Sequence, Set and Choice types also implement by-asn1-type (tag) addressing
|
||||
# * Sequence and Set types may include optional and defaulted
|
||||
# components
|
||||
# * Constructed types hold a reference to component types used for value
|
||||
# verification and ordering.
|
||||
# * Component type is a scalar type for SequenceOf/SetOf types and a list
|
||||
# of types for Sequence/Set/Choice.
|
||||
#
|
||||
|
||||
class AbstractConstructedAsn1Item(Asn1ItemBase):
|
||||
componentType = None
|
||||
sizeSpec = constraint.ConstraintsIntersection()
|
||||
def __init__(self, componentType=None, tagSet=None,
|
||||
subtypeSpec=None, sizeSpec=None):
|
||||
Asn1ItemBase.__init__(self, tagSet, subtypeSpec)
|
||||
if componentType is None:
|
||||
self._componentType = self.componentType
|
||||
else:
|
||||
self._componentType = componentType
|
||||
if sizeSpec is None:
|
||||
self._sizeSpec = self.sizeSpec
|
||||
else:
|
||||
self._sizeSpec = sizeSpec
|
||||
self._componentValues = []
|
||||
self._componentValuesSet = 0
|
||||
|
||||
def __repr__(self):
|
||||
r = []
|
||||
if self._componentType is not self.componentType:
|
||||
r.append('componentType=%r' % (self._componentType,))
|
||||
if self._tagSet is not self.tagSet:
|
||||
r.append('tagSet=%r' % (self._tagSet,))
|
||||
if self._subtypeSpec is not self.subtypeSpec:
|
||||
r.append('subtypeSpec=%r' % (self._subtypeSpec,))
|
||||
r = '%s(%s)' % (self.__class__.__name__, ', '.join(r))
|
||||
if self._componentValues:
|
||||
r += '.setComponents(%s)' % ', '.join([repr(x) for x in self._componentValues])
|
||||
return r
|
||||
|
||||
def __eq__(self, other):
|
||||
return self is other and True or self._componentValues == other
|
||||
def __ne__(self, other): return self._componentValues != other
|
||||
def __lt__(self, other): return self._componentValues < other
|
||||
def __le__(self, other): return self._componentValues <= other
|
||||
def __gt__(self, other): return self._componentValues > other
|
||||
def __ge__(self, other): return self._componentValues >= other
|
||||
if sys.version_info[0] <= 2:
|
||||
def __nonzero__(self): return bool(self._componentValues)
|
||||
else:
|
||||
def __bool__(self): return bool(self._componentValues)
|
||||
|
||||
def getComponentTagMap(self):
|
||||
raise error.PyAsn1Error('Method not implemented')
|
||||
|
||||
def _cloneComponentValues(self, myClone, cloneValueFlag): pass
|
||||
|
||||
def clone(self, tagSet=None, subtypeSpec=None, sizeSpec=None,
|
||||
cloneValueFlag=None):
|
||||
if tagSet is None:
|
||||
tagSet = self._tagSet
|
||||
if subtypeSpec is None:
|
||||
subtypeSpec = self._subtypeSpec
|
||||
if sizeSpec is None:
|
||||
sizeSpec = self._sizeSpec
|
||||
r = self.__class__(self._componentType, tagSet, subtypeSpec, sizeSpec)
|
||||
if cloneValueFlag:
|
||||
self._cloneComponentValues(r, cloneValueFlag)
|
||||
return r
|
||||
|
||||
def subtype(self, implicitTag=None, explicitTag=None, subtypeSpec=None,
|
||||
sizeSpec=None, cloneValueFlag=None):
|
||||
if implicitTag is not None:
|
||||
tagSet = self._tagSet.tagImplicitly(implicitTag)
|
||||
elif explicitTag is not None:
|
||||
tagSet = self._tagSet.tagExplicitly(explicitTag)
|
||||
else:
|
||||
tagSet = self._tagSet
|
||||
if subtypeSpec is None:
|
||||
subtypeSpec = self._subtypeSpec
|
||||
else:
|
||||
subtypeSpec = subtypeSpec + self._subtypeSpec
|
||||
if sizeSpec is None:
|
||||
sizeSpec = self._sizeSpec
|
||||
else:
|
||||
sizeSpec = sizeSpec + self._sizeSpec
|
||||
r = self.__class__(self._componentType, tagSet, subtypeSpec, sizeSpec)
|
||||
if cloneValueFlag:
|
||||
self._cloneComponentValues(r, cloneValueFlag)
|
||||
return r
|
||||
|
||||
def _verifyComponent(self, idx, value): pass
|
||||
|
||||
def verifySizeSpec(self): self._sizeSpec(self)
|
||||
|
||||
def getComponentByPosition(self, idx):
|
||||
raise error.PyAsn1Error('Method not implemented')
|
||||
def setComponentByPosition(self, idx, value, verifyConstraints=True):
|
||||
raise error.PyAsn1Error('Method not implemented')
|
||||
|
||||
def setComponents(self, *args, **kwargs):
|
||||
for idx in range(len(args)):
|
||||
self[idx] = args[idx]
|
||||
for k in kwargs:
|
||||
self[k] = kwargs[k]
|
||||
return self
|
||||
|
||||
def getComponentType(self): return self._componentType
|
||||
|
||||
def setDefaultComponents(self): pass
|
||||
|
||||
def __getitem__(self, idx): return self.getComponentByPosition(idx)
|
||||
def __setitem__(self, idx, value): self.setComponentByPosition(idx, value)
|
||||
|
||||
def __len__(self): return len(self._componentValues)
|
||||
|
||||
def clear(self):
|
||||
self._componentValues = []
|
||||
self._componentValuesSet = 0
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# ASN.1 "character string" types
|
||||
from pyasn1.type import univ, tag
|
||||
|
||||
class NumericString(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 18)
|
||||
)
|
||||
|
||||
class PrintableString(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 19)
|
||||
)
|
||||
|
||||
class TeletexString(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 20)
|
||||
)
|
||||
|
||||
class T61String(TeletexString): pass
|
||||
|
||||
class VideotexString(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 21)
|
||||
)
|
||||
|
||||
class IA5String(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 22)
|
||||
)
|
||||
|
||||
class GraphicString(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 25)
|
||||
)
|
||||
|
||||
class VisibleString(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 26)
|
||||
)
|
||||
|
||||
class ISO646String(VisibleString): pass
|
||||
|
||||
class GeneralString(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 27)
|
||||
)
|
||||
|
||||
class UniversalString(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 28)
|
||||
)
|
||||
encoding = "utf-32-be"
|
||||
|
||||
class BMPString(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 30)
|
||||
)
|
||||
encoding = "utf-16-be"
|
||||
|
||||
class UTF8String(univ.OctetString):
|
||||
tagSet = univ.OctetString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12)
|
||||
)
|
||||
encoding = "utf-8"
|
||||
@@ -0,0 +1,202 @@
|
||||
#
|
||||
# ASN.1 subtype constraints classes.
|
||||
#
|
||||
# Constraints are relatively rare, but every ASN1 object
|
||||
# is doing checks all the time for whether they have any
|
||||
# constraints and whether they are applicable to the object.
|
||||
#
|
||||
# What we're going to do is define objects/functions that
|
||||
# can be called unconditionally if they are present, and that
|
||||
# are simply not present if there are no constraints.
|
||||
#
|
||||
# Original concept and code by Mike C. Fletcher.
|
||||
#
|
||||
import sys
|
||||
|
||||
from pyasn1.type import error
|
||||
|
||||
|
||||
class AbstractConstraint:
|
||||
"""Abstract base-class for constraint objects
|
||||
|
||||
Constraints should be stored in a simple sequence in the
|
||||
namespace of their client Asn1Item sub-classes.
|
||||
"""
|
||||
def __init__(self, *values):
|
||||
self._valueMap = {}
|
||||
self._setValues(values)
|
||||
self.__hashedValues = None
|
||||
def __call__(self, value, idx=None):
|
||||
try:
|
||||
self._testValue(value, idx)
|
||||
except error.ValueConstraintError:
|
||||
raise error.ValueConstraintError(
|
||||
'%s failed at: \"%s\"' % (self, sys.exc_info()[1])
|
||||
)
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (
|
||||
self.__class__.__name__,
|
||||
', '.join([repr(x) for x in self._values])
|
||||
)
|
||||
def __eq__(self, other):
|
||||
return self is other and True or self._values == other
|
||||
def __ne__(self, other): return self._values != other
|
||||
def __lt__(self, other): return self._values < other
|
||||
def __le__(self, other): return self._values <= other
|
||||
def __gt__(self, other): return self._values > other
|
||||
def __ge__(self, other): return self._values >= other
|
||||
if sys.version_info[0] <= 2:
|
||||
def __nonzero__(self): return bool(self._values)
|
||||
else:
|
||||
def __bool__(self): return bool(self._values)
|
||||
|
||||
def __hash__(self):
|
||||
if self.__hashedValues is None:
|
||||
self.__hashedValues = hash((self.__class__.__name__, self._values))
|
||||
return self.__hashedValues
|
||||
|
||||
def _setValues(self, values): self._values = values
|
||||
def _testValue(self, value, idx):
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
# Constraints derivation logic
|
||||
def getValueMap(self): return self._valueMap
|
||||
def isSuperTypeOf(self, otherConstraint):
|
||||
return self in otherConstraint.getValueMap() or \
|
||||
otherConstraint is self or otherConstraint == self
|
||||
def isSubTypeOf(self, otherConstraint):
|
||||
return otherConstraint in self._valueMap or \
|
||||
otherConstraint is self or otherConstraint == self
|
||||
|
||||
class SingleValueConstraint(AbstractConstraint):
|
||||
"""Value must be part of defined values constraint"""
|
||||
def _testValue(self, value, idx):
|
||||
# XXX index vals for performance?
|
||||
if value not in self._values:
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
class ContainedSubtypeConstraint(AbstractConstraint):
|
||||
"""Value must satisfy all of defined set of constraints"""
|
||||
def _testValue(self, value, idx):
|
||||
for c in self._values:
|
||||
c(value, idx)
|
||||
|
||||
class ValueRangeConstraint(AbstractConstraint):
|
||||
"""Value must be within start and stop values (inclusive)"""
|
||||
def _testValue(self, value, idx):
|
||||
if value < self.start or value > self.stop:
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
def _setValues(self, values):
|
||||
if len(values) != 2:
|
||||
raise error.PyAsn1Error(
|
||||
'%s: bad constraint values' % (self.__class__.__name__,)
|
||||
)
|
||||
self.start, self.stop = values
|
||||
if self.start > self.stop:
|
||||
raise error.PyAsn1Error(
|
||||
'%s: screwed constraint values (start > stop): %s > %s' % (
|
||||
self.__class__.__name__,
|
||||
self.start, self.stop
|
||||
)
|
||||
)
|
||||
AbstractConstraint._setValues(self, values)
|
||||
|
||||
class ValueSizeConstraint(ValueRangeConstraint):
|
||||
"""len(value) must be within start and stop values (inclusive)"""
|
||||
def _testValue(self, value, idx):
|
||||
l = len(value)
|
||||
if l < self.start or l > self.stop:
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
class PermittedAlphabetConstraint(SingleValueConstraint):
|
||||
def _setValues(self, values):
|
||||
self._values = ()
|
||||
for v in values:
|
||||
self._values = self._values + tuple(v)
|
||||
|
||||
def _testValue(self, value, idx):
|
||||
for v in value:
|
||||
if v not in self._values:
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
# This is a bit kludgy, meaning two op modes within a single constraing
|
||||
class InnerTypeConstraint(AbstractConstraint):
|
||||
"""Value must satisfy type and presense constraints"""
|
||||
def _testValue(self, value, idx):
|
||||
if self.__singleTypeConstraint:
|
||||
self.__singleTypeConstraint(value)
|
||||
elif self.__multipleTypeConstraint:
|
||||
if idx not in self.__multipleTypeConstraint:
|
||||
raise error.ValueConstraintError(value)
|
||||
constraint, status = self.__multipleTypeConstraint[idx]
|
||||
if status == 'ABSENT': # XXX presense is not checked!
|
||||
raise error.ValueConstraintError(value)
|
||||
constraint(value)
|
||||
|
||||
def _setValues(self, values):
|
||||
self.__multipleTypeConstraint = {}
|
||||
self.__singleTypeConstraint = None
|
||||
for v in values:
|
||||
if isinstance(v, tuple):
|
||||
self.__multipleTypeConstraint[v[0]] = v[1], v[2]
|
||||
else:
|
||||
self.__singleTypeConstraint = v
|
||||
AbstractConstraint._setValues(self, values)
|
||||
|
||||
# Boolean ops on constraints
|
||||
|
||||
class ConstraintsExclusion(AbstractConstraint):
|
||||
"""Value must not fit the single constraint"""
|
||||
def _testValue(self, value, idx):
|
||||
try:
|
||||
self._values[0](value, idx)
|
||||
except error.ValueConstraintError:
|
||||
return
|
||||
else:
|
||||
raise error.ValueConstraintError(value)
|
||||
|
||||
def _setValues(self, values):
|
||||
if len(values) != 1:
|
||||
raise error.PyAsn1Error('Single constraint expected')
|
||||
AbstractConstraint._setValues(self, values)
|
||||
|
||||
class AbstractConstraintSet(AbstractConstraint):
|
||||
"""Value must not satisfy the single constraint"""
|
||||
def __getitem__(self, idx): return self._values[idx]
|
||||
|
||||
def __add__(self, value): return self.__class__(self, value)
|
||||
def __radd__(self, value): return self.__class__(self, value)
|
||||
|
||||
def __len__(self): return len(self._values)
|
||||
|
||||
# Constraints inclusion in sets
|
||||
|
||||
def _setValues(self, values):
|
||||
self._values = values
|
||||
for v in values:
|
||||
self._valueMap[v] = 1
|
||||
self._valueMap.update(v.getValueMap())
|
||||
|
||||
class ConstraintsIntersection(AbstractConstraintSet):
|
||||
"""Value must satisfy all constraints"""
|
||||
def _testValue(self, value, idx):
|
||||
for v in self._values:
|
||||
v(value, idx)
|
||||
|
||||
class ConstraintsUnion(AbstractConstraintSet):
|
||||
"""Value must satisfy at least one constraint"""
|
||||
def _testValue(self, value, idx):
|
||||
for v in self._values:
|
||||
try:
|
||||
v(value, idx)
|
||||
except error.ValueConstraintError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
raise error.ValueConstraintError(
|
||||
'all of %s failed for \"%s\"' % (self._values, value)
|
||||
)
|
||||
|
||||
# XXX
|
||||
# add tests for type check
|
||||
@@ -0,0 +1,3 @@
|
||||
from pyasn1.error import PyAsn1Error
|
||||
|
||||
class ValueConstraintError(PyAsn1Error): pass
|
||||
@@ -0,0 +1,151 @@
|
||||
# NamedType specification for constructed types
|
||||
import sys
|
||||
|
||||
from pyasn1 import error
|
||||
from pyasn1.type import tagmap
|
||||
|
||||
|
||||
class NamedType:
|
||||
isOptional = 0
|
||||
isDefaulted = 0
|
||||
def __init__(self, name, t):
|
||||
self.__name = name; self.__type = t
|
||||
def __repr__(self): return '%s(%r, %r)' % (
|
||||
self.__class__.__name__, self.__name, self.__type
|
||||
)
|
||||
def __eq__(self, other): return tuple(self) == tuple(other)
|
||||
def __ne__(self, other): return tuple(self) != tuple(other)
|
||||
def __lt__(self, other): return tuple(self) < tuple(other)
|
||||
def __le__(self, other): return tuple(self) <= tuple(other)
|
||||
def __gt__(self, other): return tuple(self) > tuple(other)
|
||||
def __ge__(self, other): return tuple(self) >= tuple(other)
|
||||
def __hash__(self): return hash(tuple(self))
|
||||
|
||||
def getType(self): return self.__type
|
||||
def getName(self): return self.__name
|
||||
def __getitem__(self, idx):
|
||||
if idx == 0: return self.__name
|
||||
if idx == 1: return self.__type
|
||||
raise IndexError()
|
||||
|
||||
class OptionalNamedType(NamedType):
|
||||
isOptional = 1
|
||||
class DefaultedNamedType(NamedType):
|
||||
isDefaulted = 1
|
||||
|
||||
class NamedTypes:
|
||||
def __init__(self, *namedTypes):
|
||||
self.__namedTypes = namedTypes
|
||||
self.__namedTypesLen = len(self.__namedTypes)
|
||||
self.__minTagSet = None
|
||||
self.__tagToPosIdx = {}; self.__nameToPosIdx = {}
|
||||
self.__tagMap = { False: None, True: None }
|
||||
self.__ambigiousTypes = {}
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (
|
||||
self.__class__.__name__,
|
||||
', '.join([ repr(x) for x in self.__namedTypes ])
|
||||
)
|
||||
def __eq__(self, other): return tuple(self) == tuple(other)
|
||||
def __ne__(self, other): return tuple(self) != tuple(other)
|
||||
def __lt__(self, other): return tuple(self) < tuple(other)
|
||||
def __le__(self, other): return tuple(self) <= tuple(other)
|
||||
def __gt__(self, other): return tuple(self) > tuple(other)
|
||||
def __ge__(self, other): return tuple(self) >= tuple(other)
|
||||
def __hash__(self): return hash(tuple(self))
|
||||
|
||||
def __getitem__(self, idx): return self.__namedTypes[idx]
|
||||
|
||||
if sys.version_info[0] <= 2:
|
||||
def __nonzero__(self): return bool(self.__namedTypesLen)
|
||||
else:
|
||||
def __bool__(self): return bool(self.__namedTypesLen)
|
||||
def __len__(self): return self.__namedTypesLen
|
||||
|
||||
def clone(self): return self.__class__(*self.__namedTypes)
|
||||
|
||||
def getTypeByPosition(self, idx):
|
||||
if idx < 0 or idx >= self.__namedTypesLen:
|
||||
raise error.PyAsn1Error('Type position out of range')
|
||||
else:
|
||||
return self.__namedTypes[idx].getType()
|
||||
|
||||
def getPositionByType(self, tagSet):
|
||||
if not self.__tagToPosIdx:
|
||||
idx = self.__namedTypesLen
|
||||
while idx > 0:
|
||||
idx = idx - 1
|
||||
tagMap = self.__namedTypes[idx].getType().getTagMap()
|
||||
for t in tagMap.getPosMap():
|
||||
if t in self.__tagToPosIdx:
|
||||
raise error.PyAsn1Error('Duplicate type %s' % (t,))
|
||||
self.__tagToPosIdx[t] = idx
|
||||
try:
|
||||
return self.__tagToPosIdx[tagSet]
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error('Type %s not found' % (tagSet,))
|
||||
|
||||
def getNameByPosition(self, idx):
|
||||
try:
|
||||
return self.__namedTypes[idx].getName()
|
||||
except IndexError:
|
||||
raise error.PyAsn1Error('Type position out of range')
|
||||
def getPositionByName(self, name):
|
||||
if not self.__nameToPosIdx:
|
||||
idx = self.__namedTypesLen
|
||||
while idx > 0:
|
||||
idx = idx - 1
|
||||
n = self.__namedTypes[idx].getName()
|
||||
if n in self.__nameToPosIdx:
|
||||
raise error.PyAsn1Error('Duplicate name %s' % (n,))
|
||||
self.__nameToPosIdx[n] = idx
|
||||
try:
|
||||
return self.__nameToPosIdx[name]
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error('Name %s not found' % (name,))
|
||||
|
||||
def __buildAmbigiousTagMap(self):
|
||||
ambigiousTypes = ()
|
||||
idx = self.__namedTypesLen
|
||||
while idx > 0:
|
||||
idx = idx - 1
|
||||
t = self.__namedTypes[idx]
|
||||
if t.isOptional or t.isDefaulted:
|
||||
ambigiousTypes = (t, ) + ambigiousTypes
|
||||
else:
|
||||
ambigiousTypes = (t, )
|
||||
self.__ambigiousTypes[idx] = NamedTypes(*ambigiousTypes)
|
||||
|
||||
def getTagMapNearPosition(self, idx):
|
||||
if not self.__ambigiousTypes: self.__buildAmbigiousTagMap()
|
||||
try:
|
||||
return self.__ambigiousTypes[idx].getTagMap()
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error('Type position out of range')
|
||||
|
||||
def getPositionNearType(self, tagSet, idx):
|
||||
if not self.__ambigiousTypes: self.__buildAmbigiousTagMap()
|
||||
try:
|
||||
return idx+self.__ambigiousTypes[idx].getPositionByType(tagSet)
|
||||
except KeyError:
|
||||
raise error.PyAsn1Error('Type position out of range')
|
||||
|
||||
def genMinTagSet(self):
|
||||
if self.__minTagSet is None:
|
||||
for t in self.__namedTypes:
|
||||
__type = t.getType()
|
||||
tagSet = getattr(__type,'getMinTagSet',__type.getTagSet)()
|
||||
if self.__minTagSet is None or tagSet < self.__minTagSet:
|
||||
self.__minTagSet = tagSet
|
||||
return self.__minTagSet
|
||||
|
||||
def getTagMap(self, uniq=False):
|
||||
if self.__tagMap[uniq] is None:
|
||||
tagMap = tagmap.TagMap()
|
||||
for nt in self.__namedTypes:
|
||||
tagMap = tagMap.clone(
|
||||
nt.getType(), nt.getType().getTagMap(), uniq
|
||||
)
|
||||
self.__tagMap[uniq] = tagMap
|
||||
return self.__tagMap[uniq]
|
||||
@@ -0,0 +1,58 @@
|
||||
# ASN.1 named integers
|
||||
from pyasn1 import error
|
||||
|
||||
__all__ = [ 'NamedValues' ]
|
||||
|
||||
class NamedValues:
|
||||
def __init__(self, *namedValues):
|
||||
self.nameToValIdx = {}; self.valToNameIdx = {}
|
||||
self.namedValues = ()
|
||||
automaticVal = 1
|
||||
for namedValue in namedValues:
|
||||
if isinstance(namedValue, tuple):
|
||||
name, val = namedValue
|
||||
else:
|
||||
name = namedValue
|
||||
val = automaticVal
|
||||
if name in self.nameToValIdx:
|
||||
raise error.PyAsn1Error('Duplicate name %s' % (name,))
|
||||
self.nameToValIdx[name] = val
|
||||
if val in self.valToNameIdx:
|
||||
raise error.PyAsn1Error('Duplicate value %s=%s' % (name, val))
|
||||
self.valToNameIdx[val] = name
|
||||
self.namedValues = self.namedValues + ((name, val),)
|
||||
automaticVal = automaticVal + 1
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, ', '.join([repr(x) for x in self.namedValues]))
|
||||
|
||||
def __str__(self): return str(self.namedValues)
|
||||
|
||||
def __eq__(self, other): return tuple(self) == tuple(other)
|
||||
def __ne__(self, other): return tuple(self) != tuple(other)
|
||||
def __lt__(self, other): return tuple(self) < tuple(other)
|
||||
def __le__(self, other): return tuple(self) <= tuple(other)
|
||||
def __gt__(self, other): return tuple(self) > tuple(other)
|
||||
def __ge__(self, other): return tuple(self) >= tuple(other)
|
||||
def __hash__(self): return hash(tuple(self))
|
||||
|
||||
def getName(self, value):
|
||||
if value in self.valToNameIdx:
|
||||
return self.valToNameIdx[value]
|
||||
|
||||
def getValue(self, name):
|
||||
if name in self.nameToValIdx:
|
||||
return self.nameToValIdx[name]
|
||||
|
||||
def __getitem__(self, i): return self.namedValues[i]
|
||||
def __len__(self): return len(self.namedValues)
|
||||
|
||||
def __add__(self, namedValues):
|
||||
return self.__class__(*self.namedValues + namedValues)
|
||||
def __radd__(self, namedValues):
|
||||
return self.__class__(*namedValues + tuple(self))
|
||||
|
||||
def clone(self, *namedValues):
|
||||
return self.__class__(*tuple(self) + namedValues)
|
||||
|
||||
# XXX clone/subtype?
|
||||
@@ -0,0 +1,129 @@
|
||||
# ASN.1 types tags
|
||||
from operator import getitem
|
||||
|
||||
from pyasn1 import error
|
||||
|
||||
tagClassUniversal = 0x00
|
||||
tagClassApplication = 0x40
|
||||
tagClassContext = 0x80
|
||||
tagClassPrivate = 0xC0
|
||||
|
||||
tagFormatSimple = 0x00
|
||||
tagFormatConstructed = 0x20
|
||||
|
||||
tagCategoryImplicit = 0x01
|
||||
tagCategoryExplicit = 0x02
|
||||
tagCategoryUntagged = 0x04
|
||||
|
||||
class Tag:
|
||||
def __init__(self, tagClass, tagFormat, tagId):
|
||||
if tagId < 0:
|
||||
raise error.PyAsn1Error(
|
||||
'Negative tag ID (%s) not allowed' % (tagId,)
|
||||
)
|
||||
self.__tag = (tagClass, tagFormat, tagId)
|
||||
self.uniq = (tagClass, tagId)
|
||||
self.__hashedUniqTag = hash(self.uniq)
|
||||
|
||||
def __str__(self):
|
||||
return '[%s:%s:%s]' % self.__tag
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(tagClass=%s, tagFormat=%s, tagId=%s)' % (
|
||||
(self.__class__.__name__,) + self.__tag
|
||||
)
|
||||
# These is really a hotspot -- expose public "uniq" attribute to save on
|
||||
# function calls
|
||||
def __eq__(self, other): return self.uniq == other.uniq
|
||||
def __ne__(self, other): return self.uniq != other.uniq
|
||||
def __lt__(self, other): return self.uniq < other.uniq
|
||||
def __le__(self, other): return self.uniq <= other.uniq
|
||||
def __gt__(self, other): return self.uniq > other.uniq
|
||||
def __ge__(self, other): return self.uniq >= other.uniq
|
||||
def __hash__(self): return self.__hashedUniqTag
|
||||
def __getitem__(self, idx): return self.__tag[idx]
|
||||
def __and__(self, otherTag):
|
||||
(tagClass, tagFormat, tagId) = otherTag
|
||||
return self.__class__(
|
||||
self.__tag&tagClass, self.__tag&tagFormat, self.__tag&tagId
|
||||
)
|
||||
def __or__(self, otherTag):
|
||||
(tagClass, tagFormat, tagId) = otherTag
|
||||
return self.__class__(
|
||||
self.__tag[0]|tagClass,
|
||||
self.__tag[1]|tagFormat,
|
||||
self.__tag[2]|tagId
|
||||
)
|
||||
def asTuple(self): return self.__tag # __getitem__() is slow
|
||||
|
||||
class TagSet:
|
||||
def __init__(self, baseTag=(), *superTags):
|
||||
self.__baseTag = baseTag
|
||||
self.__superTags = superTags
|
||||
self.__hashedSuperTags = hash(superTags)
|
||||
_uniq = ()
|
||||
for t in superTags:
|
||||
_uniq = _uniq + t.uniq
|
||||
self.uniq = _uniq
|
||||
self.__lenOfSuperTags = len(superTags)
|
||||
|
||||
def __str__(self):
|
||||
return self.__superTags and '+'.join([str(x) for x in self.__superTags]) or '[untagged]'
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (
|
||||
self.__class__.__name__,
|
||||
'(), ' + ', '.join([repr(x) for x in self.__superTags])
|
||||
)
|
||||
|
||||
def __add__(self, superTag):
|
||||
return self.__class__(
|
||||
self.__baseTag, *self.__superTags + (superTag,)
|
||||
)
|
||||
def __radd__(self, superTag):
|
||||
return self.__class__(
|
||||
self.__baseTag, *(superTag,) + self.__superTags
|
||||
)
|
||||
|
||||
def tagExplicitly(self, superTag):
|
||||
tagClass, tagFormat, tagId = superTag
|
||||
if tagClass == tagClassUniversal:
|
||||
raise error.PyAsn1Error(
|
||||
'Can\'t tag with UNIVERSAL-class tag'
|
||||
)
|
||||
if tagFormat != tagFormatConstructed:
|
||||
superTag = Tag(tagClass, tagFormatConstructed, tagId)
|
||||
return self + superTag
|
||||
|
||||
def tagImplicitly(self, superTag):
|
||||
tagClass, tagFormat, tagId = superTag
|
||||
if self.__superTags:
|
||||
superTag = Tag(tagClass, self.__superTags[-1][1], tagId)
|
||||
return self[:-1] + superTag
|
||||
|
||||
def getBaseTag(self): return self.__baseTag
|
||||
def __getitem__(self, idx):
|
||||
if isinstance(idx, slice):
|
||||
return self.__class__(
|
||||
self.__baseTag, *getitem(self.__superTags, idx)
|
||||
)
|
||||
return self.__superTags[idx]
|
||||
def __eq__(self, other): return self.uniq == other.uniq
|
||||
def __ne__(self, other): return self.uniq != other.uniq
|
||||
def __lt__(self, other): return self.uniq < other.uniq
|
||||
def __le__(self, other): return self.uniq <= other.uniq
|
||||
def __gt__(self, other): return self.uniq > other.uniq
|
||||
def __ge__(self, other): return self.uniq >= other.uniq
|
||||
def __hash__(self): return self.__hashedSuperTags
|
||||
def __len__(self): return self.__lenOfSuperTags
|
||||
def isSuperTagSetOf(self, tagSet):
|
||||
if len(tagSet) < self.__lenOfSuperTags:
|
||||
return
|
||||
idx = self.__lenOfSuperTags - 1
|
||||
while idx >= 0:
|
||||
if self.__superTags[idx] != tagSet[idx]:
|
||||
return
|
||||
idx = idx - 1
|
||||
return 1
|
||||
|
||||
def initTagSet(tag): return TagSet(tag, tag)
|
||||
@@ -0,0 +1,66 @@
|
||||
from pyasn1 import error
|
||||
|
||||
class TagMap:
|
||||
def __init__(self, posMap={}, negMap={}, defType=None):
|
||||
self.__posMap = posMap.copy()
|
||||
self.__negMap = negMap.copy()
|
||||
self.__defType = defType
|
||||
|
||||
def __contains__(self, tagSet):
|
||||
return tagSet in self.__posMap or \
|
||||
self.__defType is not None and tagSet not in self.__negMap
|
||||
|
||||
def __getitem__(self, tagSet):
|
||||
if tagSet in self.__posMap:
|
||||
return self.__posMap[tagSet]
|
||||
elif tagSet in self.__negMap:
|
||||
raise error.PyAsn1Error('Key in negative map')
|
||||
elif self.__defType is not None:
|
||||
return self.__defType
|
||||
else:
|
||||
raise KeyError()
|
||||
|
||||
def __repr__(self):
|
||||
s = self.__class__.__name__ + '('
|
||||
if self.__posMap:
|
||||
s = s + 'posMap=%r, ' % (self.__posMap,)
|
||||
if self.__negMap:
|
||||
s = s + 'negMap=%r, ' % (self.__negMap,)
|
||||
if self.__defType is not None:
|
||||
s = s + 'defType=%r' % (self.__defType,)
|
||||
return s + ')'
|
||||
|
||||
def __str__(self):
|
||||
s = self.__class__.__name__ + ':\n'
|
||||
if self.__posMap:
|
||||
s = s + 'posMap:\n%s, ' % ',\n '.join([ x.prettyPrintType() for x in self.__posMap.values()])
|
||||
if self.__negMap:
|
||||
s = s + 'negMap:\n%s, ' % ',\n '.join([ x.prettyPrintType() for x in self.__negMap.values()])
|
||||
if self.__defType is not None:
|
||||
s = s + 'defType:\n%s, ' % self.__defType.prettyPrintType()
|
||||
return s
|
||||
|
||||
def clone(self, parentType, tagMap, uniq=False):
|
||||
if self.__defType is not None and tagMap.getDef() is not None:
|
||||
raise error.PyAsn1Error('Duplicate default value at %s' % (self,))
|
||||
if tagMap.getDef() is not None:
|
||||
defType = tagMap.getDef()
|
||||
else:
|
||||
defType = self.__defType
|
||||
|
||||
posMap = self.__posMap.copy()
|
||||
for k in tagMap.getPosMap():
|
||||
if uniq and k in posMap:
|
||||
raise error.PyAsn1Error('Duplicate positive key %s' % (k,))
|
||||
posMap[k] = parentType
|
||||
|
||||
negMap = self.__negMap.copy()
|
||||
negMap.update(tagMap.getNegMap())
|
||||
|
||||
return self.__class__(
|
||||
posMap, negMap, defType,
|
||||
)
|
||||
|
||||
def getPosMap(self): return self.__posMap.copy()
|
||||
def getNegMap(self): return self.__negMap.copy()
|
||||
def getDef(self): return self.__defType
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
# ASN.1 "useful" types
|
||||
from pyasn1.type import char, tag
|
||||
|
||||
class ObjectDescriptor(char.GraphicString):
|
||||
tagSet = char.GraphicString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 7)
|
||||
)
|
||||
|
||||
class GeneralizedTime(char.VisibleString):
|
||||
tagSet = char.VisibleString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 24)
|
||||
)
|
||||
|
||||
class UTCTime(char.VisibleString):
|
||||
tagSet = char.VisibleString.tagSet.tagImplicitly(
|
||||
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 23)
|
||||
)
|
||||
@@ -0,0 +1,630 @@
|
||||
|
||||
import os, logging, select, socket, struct, errno
|
||||
from smb_constants import *
|
||||
from smb_structs import *
|
||||
from base import SMB, NotConnectedError, NotReadyError, SMBTimeout
|
||||
|
||||
|
||||
class SMBConnection(SMB):
|
||||
|
||||
log = logging.getLogger('SMB.SMBConnection')
|
||||
|
||||
#: SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
SIGN_NEVER = 0
|
||||
#: SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
SIGN_WHEN_SUPPORTED = 1
|
||||
#: SMB messages will only be signed when remote server requires signing.
|
||||
SIGN_WHEN_REQUIRED = 2
|
||||
|
||||
def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
|
||||
"""
|
||||
Create a new SMBConnection instance.
|
||||
|
||||
*username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server.
|
||||
File operations can only be proceeded after the connection has been authenticated successfully.
|
||||
|
||||
Note that you need to call *connect* method to actually establish the SMB connection to the remote server and perform authentication.
|
||||
|
||||
The default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139.
|
||||
Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445.
|
||||
|
||||
:param string my_name: The local NetBIOS machine name that will identify where this connection is originating from.
|
||||
You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+``
|
||||
:param string remote_name: The NetBIOS machine name of the remote server.
|
||||
On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties".
|
||||
This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected.
|
||||
:param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string.
|
||||
:param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication.
|
||||
The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured.
|
||||
Hence, we can only "guess" or try both algorithms.
|
||||
On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2.
|
||||
:param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*.
|
||||
If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing.
|
||||
If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
:param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication.
|
||||
The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).
|
||||
"""
|
||||
SMB.__init__(self, username, password, my_name, remote_name, domain, use_ntlm_v2, sign_options, is_direct_tcp)
|
||||
self.sock = None
|
||||
self.auth_result = None
|
||||
self.is_busy = False
|
||||
self.is_direct_tcp = is_direct_tcp
|
||||
|
||||
#
|
||||
# SMB (and its superclass) Methods
|
||||
#
|
||||
|
||||
def onAuthOK(self):
|
||||
self.auth_result = True
|
||||
|
||||
def onAuthFailed(self):
|
||||
self.auth_result = False
|
||||
|
||||
def write(self, data):
|
||||
assert self.sock
|
||||
data_len = len(data)
|
||||
total_sent = 0
|
||||
while total_sent < data_len:
|
||||
sent = self.sock.send(data[total_sent:])
|
||||
if sent == 0:
|
||||
raise NotConnectedError('Server disconnected')
|
||||
total_sent = total_sent + sent
|
||||
|
||||
#
|
||||
# Misc Properties
|
||||
#
|
||||
|
||||
@property
|
||||
def isUsingSMB2(self):
|
||||
"""A convenient property to return True if the underlying SMB connection is using SMB2 protocol."""
|
||||
return self.is_using_smb2
|
||||
|
||||
|
||||
#
|
||||
# Public Methods
|
||||
#
|
||||
|
||||
def connect(self, ip, port = 139, sock_family = socket.AF_INET, timeout = 60):
|
||||
"""
|
||||
Establish the SMB connection to the remote SMB/CIFS server.
|
||||
|
||||
You must call this method before attempting any of the file operations with the remote server.
|
||||
This method will block until the SMB connection has attempted at least one authentication.
|
||||
|
||||
:return: A boolean value indicating the result of the authentication atttempt: True if authentication is successful; False, if otherwise.
|
||||
"""
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
|
||||
self.auth_result = None
|
||||
self.sock = socket.socket(sock_family)
|
||||
self.sock.settimeout(timeout)
|
||||
self.sock.connect(( ip, port ))
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
if not self.is_direct_tcp:
|
||||
self.requestNMBSession()
|
||||
else:
|
||||
self.onNMBSessionOK()
|
||||
while self.auth_result is None:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return self.auth_result
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Terminate the SMB connection (if it has been started) and release any sources held by the underlying socket.
|
||||
"""
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
|
||||
def listShares(self, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of shared resources on remote server.
|
||||
|
||||
:return: A list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances describing the shared resource
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(entries):
|
||||
self.is_busy = False
|
||||
results.extend(entries)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._listShares(cb, eb, timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results
|
||||
|
||||
def listPath(self, service_name, path,
|
||||
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL,
|
||||
pattern = '*', timeout = 30):
|
||||
"""
|
||||
Retrieve a directory listing of files/folders at *path*
|
||||
|
||||
For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
|
||||
It ignores other attributes like compression, indexed, sparse, temporary and encryption.
|
||||
|
||||
Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN),
|
||||
system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files
|
||||
and directories (SMB_FILE_ATTRIBUTE_DIRECTORY).
|
||||
If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant.
|
||||
SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
||||
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
||||
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
||||
:return: A list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(entries):
|
||||
self.is_busy = False
|
||||
results.extend(entries)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._listPath(service_name, path, cb, eb, search = search, pattern = pattern, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results
|
||||
|
||||
def listSnapshots(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of available snapshots (shadow copies) for *path*.
|
||||
|
||||
Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots
|
||||
:return: A list of python *datetime.DateTime* instances in GMT/UTC time zone
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(entries):
|
||||
self.is_busy = False
|
||||
results.extend(entries)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._listSnapshots(service_name, path, cb, eb, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results
|
||||
|
||||
def getAttributes(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve information about the file at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:return: A :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file.
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(info):
|
||||
self.is_busy = False
|
||||
results.append(info)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._getAttributes(service_name, path, cb, eb, timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
def getSecurity(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve the security descriptor of the file at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:return: A :class:`smb.security_descriptors.SecurityDescriptor` instance containing the security information of the file.
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(info):
|
||||
self.is_busy = False
|
||||
results.append(info)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._getSecurity(service_name, path, cb, eb, timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
Use *retrieveFileFromOffset()* method if you wish to specify the offset to read from the remote *path* and/or the number of bytes to write to the *file_obj*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service.
|
||||
:return: A 2-element tuple of ( file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
return self.retrieveFileFromOffset(service_name, path, file_obj, 0L, -1L, timeout)
|
||||
|
||||
def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0L, max_length = -1L, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* up to *max_length* number of bytes.
|
||||
:param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value.
|
||||
:param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF.
|
||||
If zero, the method returns immediately after the file is opened successfully for reading.
|
||||
:return: A 2-element tuple of ( file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
results.append(r[1:])
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._retrieveFileFromOffset(service_name, path, file_obj, cb, eb, offset, max_length, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
def storeFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Store the contents of the *file_obj* at *path* on the *service_name*.
|
||||
If the file already exists on the remote server, it will be truncated and overwritten.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten.
|
||||
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF.
|
||||
:return: Number of bytes uploaded
|
||||
"""
|
||||
return self.storeFileFromOffset(service_name, path, file_obj, 0L, True, timeout)
|
||||
|
||||
def storeFileFromOffset(self, service_name, path, file_obj, offset = 0L, truncate = False, timeout = 30):
|
||||
"""
|
||||
Store the contents of the *file_obj* at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created.
|
||||
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF.
|
||||
:param offset: Long integer value which specifies the offset in the remote server to start writing. First byte of the file is 0.
|
||||
:param truncate: Boolean value. If True and the file exists on the remote server, it will be truncated first before writing. Default is False.
|
||||
:return: the file position where the next byte will be written.
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
results.append(r[1])
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._storeFileFromOffset(service_name, path, file_obj, cb, eb, offset, truncate = truncate, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
def deleteFiles(self, service_name, path_file_pattern, timeout = 30):
|
||||
"""
|
||||
Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
||||
Wildcards may be used in th filename component of the path.
|
||||
If your path/filename contains non-English characters, you must pass in an unicode string.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._deleteFiles(service_name, path_file_pattern, cb, eb, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def resetFileAttributes(self, service_name, path_file_pattern, timeout = 30):
|
||||
"""
|
||||
Reset file attributes of one or more regular files or folders.
|
||||
It supports the use of wildcards in file names, allowing for unlocking of multiple files/folders in a single request.
|
||||
This function is very helpful when deleting files/folders that are read-only.
|
||||
Note: this function is currently only implemented for SMB2! Technically, it sets the FILE_ATTRIBUTE_NORMAL flag, therefore clearing all other flags. (See https://msdn.microsoft.com/en-us/library/cc232110.aspx for further information)
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
||||
Wildcards may be used in the filename component of the path.
|
||||
If your path/filename contains non-English characters, you must pass in an unicode string.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._resetFileAttributes(service_name, path_file_pattern, cb, eb, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def createDirectory(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Creates a new directory *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the new folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._createDirectory(service_name, path, cb, eb, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def deleteDirectory(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Delete the empty folder at *path* on *service_name*
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._deleteDirectory(service_name, path, cb, eb, timeout = timeout)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def rename(self, service_name, old_path, new_path, timeout = 30):
|
||||
"""
|
||||
Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders
|
||||
|
||||
*old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:return: None
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._rename(service_name, old_path, new_path, cb, eb)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
def echo(self, data, timeout = 10):
|
||||
"""
|
||||
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
||||
|
||||
:param bytes data: Data to send to the remote server. Must be a bytes object.
|
||||
:return: The *data* parameter
|
||||
"""
|
||||
if not self.sock:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
results = [ ]
|
||||
|
||||
def cb(r):
|
||||
self.is_busy = False
|
||||
results.append(r)
|
||||
|
||||
def eb(failure):
|
||||
self.is_busy = False
|
||||
raise failure
|
||||
|
||||
self.is_busy = True
|
||||
try:
|
||||
self._echo(data, cb, eb)
|
||||
while self.is_busy:
|
||||
self._pollForNetBIOSPacket(timeout)
|
||||
finally:
|
||||
self.is_busy = False
|
||||
|
||||
return results[0]
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _pollForNetBIOSPacket(self, timeout):
|
||||
expiry_time = time.time() + timeout
|
||||
read_len = 4
|
||||
data = ''
|
||||
|
||||
while read_len > 0:
|
||||
try:
|
||||
if expiry_time < time.time():
|
||||
raise SMBTimeout
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], timeout)
|
||||
if not ready:
|
||||
raise SMBTimeout
|
||||
|
||||
d = self.sock.recv(read_len)
|
||||
if len(d) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
data = data + d
|
||||
read_len -= len(d)
|
||||
except select.error, ex:
|
||||
if isinstance(ex, types.TupleType):
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
|
||||
type_, flags, length = struct.unpack('>BBH', data)
|
||||
if type_ == 0x0:
|
||||
# This is a Direct TCP packet
|
||||
# The length is specified in the header from byte 8. (0-indexed)
|
||||
# we read a structure assuming NBT, so to get the real length
|
||||
# combine the length and flag fields together
|
||||
length = length + (flags << 16)
|
||||
else:
|
||||
# This is a NetBIOS over TCP (NBT) packet
|
||||
# The length is specified in the header from byte 16. (0-indexed)
|
||||
if flags & 0x01:
|
||||
length = length | 0x10000
|
||||
|
||||
read_len = length
|
||||
while read_len > 0:
|
||||
try:
|
||||
if expiry_time < time.time():
|
||||
raise SMBTimeout
|
||||
|
||||
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], timeout)
|
||||
if not ready:
|
||||
raise SMBTimeout
|
||||
|
||||
d = self.sock.recv(read_len)
|
||||
if len(d) == 0:
|
||||
raise NotConnectedError
|
||||
|
||||
data = data + d
|
||||
read_len -= len(d)
|
||||
except select.error, ex:
|
||||
if isinstance(ex, types.TupleType):
|
||||
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
|
||||
raise ex
|
||||
else:
|
||||
raise ex
|
||||
|
||||
self.feedData(data)
|
||||
@@ -0,0 +1,97 @@
|
||||
|
||||
import os, sys, socket, urllib2, mimetypes, mimetools, tempfile
|
||||
from urllib import (unwrap, unquote, splittype, splithost, quote,
|
||||
addinfourl, splitport, splittag,
|
||||
splitattr, ftpwrapper, splituser, splitpasswd, splitvalue)
|
||||
from nmb.NetBIOS import NetBIOS
|
||||
from smb.SMBConnection import SMBConnection
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
USE_NTLM = True
|
||||
MACHINE_NAME = None
|
||||
|
||||
class SMBHandler(urllib2.BaseHandler):
|
||||
|
||||
def smb_open(self, req):
|
||||
global USE_NTLM, MACHINE_NAME
|
||||
|
||||
host = req.get_host()
|
||||
if not host:
|
||||
raise urllib2.URLError('SMB error: no host given')
|
||||
host, port = splitport(host)
|
||||
if port is None:
|
||||
port = 139
|
||||
else:
|
||||
port = int(port)
|
||||
|
||||
# username/password handling
|
||||
user, host = splituser(host)
|
||||
if user:
|
||||
user, passwd = splitpasswd(user)
|
||||
else:
|
||||
passwd = None
|
||||
host = unquote(host)
|
||||
user = user or ''
|
||||
|
||||
domain = ''
|
||||
if ';' in user:
|
||||
domain, user = user.split(';', 1)
|
||||
|
||||
passwd = passwd or ''
|
||||
myname = MACHINE_NAME or self.generateClientMachineName()
|
||||
|
||||
n = NetBIOS()
|
||||
names = n.queryIPForName(host)
|
||||
if names:
|
||||
server_name = names[0]
|
||||
else:
|
||||
raise urllib2.URLError('SMB error: Hostname does not reply back with its machine name')
|
||||
|
||||
path, attrs = splitattr(req.get_selector())
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
dirs = path.split('/')
|
||||
dirs = map(unquote, dirs)
|
||||
service, path = dirs[0], '/'.join(dirs[1:])
|
||||
|
||||
try:
|
||||
conn = SMBConnection(user, passwd, myname, server_name, domain=domain, use_ntlm_v2 = USE_NTLM)
|
||||
conn.connect(host, port)
|
||||
|
||||
if req.has_data():
|
||||
data_fp = req.get_data()
|
||||
filelen = conn.storeFile(service, path, data_fp)
|
||||
|
||||
headers = "Content-length: 0\n"
|
||||
fp = StringIO("")
|
||||
else:
|
||||
fp = self.createTempFile()
|
||||
file_attrs, retrlen = conn.retrieveFile(service, path, fp)
|
||||
fp.seek(0)
|
||||
|
||||
headers = ""
|
||||
mtype = mimetypes.guess_type(req.get_full_url())[0]
|
||||
if mtype:
|
||||
headers += "Content-type: %s\n" % mtype
|
||||
if retrlen is not None and retrlen >= 0:
|
||||
headers += "Content-length: %d\n" % retrlen
|
||||
|
||||
sf = StringIO(headers)
|
||||
headers = mimetools.Message(sf)
|
||||
|
||||
return addinfourl(fp, headers, req.get_full_url())
|
||||
except Exception, ex:
|
||||
raise urllib2.URLError, ('smb error: %s' % ex), sys.exc_info()[2]
|
||||
|
||||
def createTempFile(self):
|
||||
return tempfile.TemporaryFile()
|
||||
|
||||
def generateClientMachineName(self):
|
||||
hostname = socket.gethostname()
|
||||
if hostname:
|
||||
return hostname.split('.')[0]
|
||||
return 'SMB%d' % os.getpid()
|
||||
@@ -0,0 +1,409 @@
|
||||
|
||||
import os, logging, time
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet.protocol import ClientFactory, Protocol
|
||||
from smb_constants import *
|
||||
from smb_structs import *
|
||||
from base import SMB, NotConnectedError, NotReadyError, SMBTimeout
|
||||
|
||||
|
||||
__all__ = [ 'SMBProtocolFactory', 'NotConnectedError', 'NotReadyError' ]
|
||||
|
||||
|
||||
class SMBProtocol(Protocol, SMB):
|
||||
|
||||
log = logging.getLogger('SMB.SMBProtocol')
|
||||
|
||||
#
|
||||
# Protocol Methods
|
||||
#
|
||||
|
||||
def connectionMade(self):
|
||||
self.factory.instance = self
|
||||
if not self.is_direct_tcp:
|
||||
self.requestNMBSession()
|
||||
else:
|
||||
self.onNMBSessionOK()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
if self.factory.instance == self:
|
||||
self.instance = None
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.feedData(data)
|
||||
|
||||
#
|
||||
# SMB (and its superclass) Methods
|
||||
#
|
||||
|
||||
def write(self, data):
|
||||
self.transport.write(data)
|
||||
|
||||
def onAuthOK(self):
|
||||
if self.factory.instance == self:
|
||||
self.factory.onAuthOK()
|
||||
reactor.callLater(1, self._cleanupPendingRequests)
|
||||
|
||||
def onAuthFailed(self):
|
||||
if self.factory.instance == self:
|
||||
self.factory.onAuthFailed()
|
||||
|
||||
def onNMBSessionFailed(self):
|
||||
self.log.error('Cannot establish NetBIOS session. You might have provided a wrong remote_name')
|
||||
|
||||
#
|
||||
# Protected Methods
|
||||
#
|
||||
|
||||
def _cleanupPendingRequests(self):
|
||||
if self.factory.instance == self:
|
||||
now = time.time()
|
||||
to_remove = []
|
||||
for mid, r in self.pending_requests.iteritems():
|
||||
if r.expiry_time < now:
|
||||
try:
|
||||
r.errback(SMBTimeout())
|
||||
except Exception: pass
|
||||
to_remove.append(mid)
|
||||
|
||||
for mid in to_remove:
|
||||
del self.pending_requests[mid]
|
||||
|
||||
reactor.callLater(1, self._cleanupPendingRequests)
|
||||
|
||||
|
||||
class SMBProtocolFactory(ClientFactory):
|
||||
|
||||
protocol = SMBProtocol
|
||||
log = logging.getLogger('SMB.SMBFactory')
|
||||
|
||||
#: SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
SIGN_NEVER = 0
|
||||
#: SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
SIGN_WHEN_SUPPORTED = 1
|
||||
#: SMB messages will only be signed when remote server requires signing.
|
||||
SIGN_WHEN_REQUIRED = 2
|
||||
|
||||
def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
|
||||
"""
|
||||
Create a new SMBProtocolFactory instance. You will pass this instance to *reactor.connectTCP()* which will then instantiate the TCP connection to the remote SMB/CIFS server.
|
||||
Note that the default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139.
|
||||
Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445.
|
||||
|
||||
*username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server.
|
||||
File operations can only be proceeded after the connection has been authenticated successfully.
|
||||
|
||||
:param string my_name: The local NetBIOS machine name that will identify where this connection is originating from.
|
||||
You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+``.
|
||||
:param string remote_name: The NetBIOS machine name of the remote server.
|
||||
On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties".
|
||||
This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected.
|
||||
:param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string.
|
||||
:param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication.
|
||||
The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured.
|
||||
Hence, we can only "guess" or try both algorithms.
|
||||
On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2.
|
||||
:param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*.
|
||||
If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing.
|
||||
If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing.
|
||||
If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
|
||||
:param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication.
|
||||
The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).
|
||||
"""
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.my_name = my_name
|
||||
self.remote_name = remote_name
|
||||
self.domain = domain
|
||||
self.use_ntlm_v2 = use_ntlm_v2
|
||||
self.sign_options = sign_options
|
||||
self.is_direct_tcp = is_direct_tcp
|
||||
self.instance = None #: The single SMBProtocol instance for each SMBProtocolFactory instance. Usually, you should not need to touch this attribute directly.
|
||||
|
||||
#
|
||||
# Public Property
|
||||
#
|
||||
|
||||
@property
|
||||
def isReady(self):
|
||||
"""A convenient property to return True if the underlying SMB connection has connected to remote server, has successfully authenticated itself and is ready for file operations."""
|
||||
return bool(self.instance and self.instance.has_authenticated)
|
||||
|
||||
@property
|
||||
def isUsingSMB2(self):
|
||||
"""A convenient property to return True if the underlying SMB connection is using SMB2 protocol."""
|
||||
return self.instance and self.instance.is_using_smb2
|
||||
|
||||
#
|
||||
# Public Methods for Callbacks
|
||||
#
|
||||
|
||||
def onAuthOK(self):
|
||||
"""
|
||||
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
|
||||
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
|
||||
File operations can proceed when this method has been called.
|
||||
"""
|
||||
pass
|
||||
|
||||
def onAuthFailed(self):
|
||||
"""
|
||||
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
|
||||
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
|
||||
|
||||
If you want to retry authenticating from this method,
|
||||
1. Disconnect the underlying SMB connection (call ``self.instance.transport.loseConnection()``)
|
||||
2. Create a new SMBProtocolFactory subclass instance with different user credientials or different NTLM algorithm flag.
|
||||
3. Call ``reactor.connectTCP`` with the new instance to re-establish the SMB connection
|
||||
"""
|
||||
pass
|
||||
|
||||
#
|
||||
# Public Methods
|
||||
#
|
||||
|
||||
def listShares(self, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of shared resources on remote server.
|
||||
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listShares(d.callback, d.errback, timeout)
|
||||
return d
|
||||
|
||||
def listPath(self, service_name, path,
|
||||
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL,
|
||||
pattern = '*', timeout = 30):
|
||||
"""
|
||||
Retrieve a directory listing of files/folders at *path*
|
||||
|
||||
For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
|
||||
It ignores other attributes like compression, indexed, sparse, temporary and encryption.
|
||||
|
||||
Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN),
|
||||
system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files
|
||||
and directories (SMB_FILE_ATTRIBUTE_DIRECTORY).
|
||||
If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant.
|
||||
SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
|
||||
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
|
||||
:param string/unicode pattern: the filter to apply to the results before returning to the client.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listPath(service_name, path, d.callback, d.errback, search = search, pattern = pattern, timeout = timeout)
|
||||
return d
|
||||
|
||||
def listSnapshots(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve a list of available snapshots (a.k.a. shadow copies) for *path*.
|
||||
|
||||
Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of python *datetime.DateTime*
|
||||
instances in GMT/UTC time zone
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._listSnapshots(service_name, path, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def getAttributes(self, service_name, path, timeout = 30):
|
||||
"""
|
||||
Retrieve information about the file at *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._getAttributes(service_name, path, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
Use *retrieveFileFromOffset()* method if you need to specify the offset to read from the remote *path* and/or the maximum number of bytes to write to the *file_obj*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
return self.retrieveFileFromOffset(service_name, path, file_obj, 0L, -1L, timeout)
|
||||
|
||||
def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0L, max_length = -1L, timeout = 30):
|
||||
"""
|
||||
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service.
|
||||
:param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value.
|
||||
:param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF.
|
||||
If zero, the *Deferred* callback is invoked immediately after the file is opened successfully for reading.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
|
||||
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._retrieveFileFromOffset(service_name, path, file_obj, d.callback, d.errback, offset, max_length, timeout = timeout)
|
||||
return d
|
||||
|
||||
def storeFile(self, service_name, path, file_obj, timeout = 30):
|
||||
"""
|
||||
Store the contents of the *file_obj* at *path* on the *service_name*.
|
||||
|
||||
The meaning of the *timeout* parameter will be different from other file operation methods. As the uploaded file usually exceeeds the maximum size
|
||||
of each SMB/CIFS data message, it will be packetized into a series of messages (usually about 60kBytes).
|
||||
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and acknowledged
|
||||
by the remote SMB/CIFS server.
|
||||
|
||||
:param string/unicode service_name: the name of the shared folder for the *path*
|
||||
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten.
|
||||
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
|
||||
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *file_obj*, number of bytes uploaded ).
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._storeFile(service_name, path, file_obj, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def deleteFiles(self, service_name, path_file_pattern, timeout = 30):
|
||||
"""
|
||||
Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
|
||||
Wildcards may be used in th filename component of the path.
|
||||
If your path/filename contains non-English characters, you must pass in an unicode string.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path_file_pattern* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._deleteFiles(service_name, path_file_pattern, d.callback, d.errback, timeout = timeout)
|
||||
return d
|
||||
|
||||
def createDirectory(self, service_name, path):
|
||||
"""
|
||||
Creates a new directory *path* on the *service_name*.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the new folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._createDirectory(service_name, path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def deleteDirectory(self, service_name, path):
|
||||
"""
|
||||
Delete the empty folder at *path* on *service_name*
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._deleteDirectory(service_name, path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def rename(self, service_name, old_path, new_path):
|
||||
"""
|
||||
Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders
|
||||
|
||||
*old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder.
|
||||
If the path contains non-English characters, an unicode string must be used to pass in the path.
|
||||
|
||||
:param string/unicode service_name: Contains the name of the shared folder.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *old_path*, *new_path* ).
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._rename(service_name, old_path, new_path, d.callback, d.errback)
|
||||
return d
|
||||
|
||||
def echo(self, data, timeout = 10):
|
||||
"""
|
||||
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
|
||||
|
||||
:param bytes data: Data to send to the remote server. Must be a bytes object.
|
||||
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
|
||||
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter.
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
d = defer.Deferred()
|
||||
self.instance._echo(data, d.callback, d.errback, timeout)
|
||||
return d
|
||||
|
||||
def closeConnection(self):
|
||||
"""
|
||||
Disconnect from the remote SMB/CIFS server. The TCP connection will be closed at the earliest opportunity after this method returns.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
if not self.instance:
|
||||
raise NotConnectedError('Not connected to server')
|
||||
|
||||
self.instance.transport.loseConnection()
|
||||
|
||||
#
|
||||
# ClientFactory methods
|
||||
# (Do not touch these unless you know what you are doing)
|
||||
#
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
p = self.protocol(self.username, self.password, self.my_name, self.remote_name, self.domain, self.use_ntlm_v2, self.sign_options, self.is_direct_tcp)
|
||||
p.factory = self
|
||||
return p
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,248 @@
|
||||
|
||||
import types, hmac, binascii, struct, random
|
||||
from utils.pyDes import des
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
hashlib.new('md4')
|
||||
|
||||
def MD4(): return hashlib.new('md4')
|
||||
except ( ImportError, ValueError ):
|
||||
from utils.md4 import MD4
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
def MD5(s): return hashlib.md5(s)
|
||||
except ImportError:
|
||||
import md5
|
||||
def MD5(s): return md5.new(s)
|
||||
|
||||
################
|
||||
# NTLMv2 Methods
|
||||
################
|
||||
|
||||
# The following constants are defined in accordance to [MS-NLMP]: 2.2.2.5
|
||||
|
||||
NTLM_NegotiateUnicode = 0x00000001
|
||||
NTLM_NegotiateOEM = 0x00000002
|
||||
NTLM_RequestTarget = 0x00000004
|
||||
NTLM_Unknown9 = 0x00000008
|
||||
NTLM_NegotiateSign = 0x00000010
|
||||
NTLM_NegotiateSeal = 0x00000020
|
||||
NTLM_NegotiateDatagram = 0x00000040
|
||||
NTLM_NegotiateLanManagerKey = 0x00000080
|
||||
NTLM_Unknown8 = 0x00000100
|
||||
NTLM_NegotiateNTLM = 0x00000200
|
||||
NTLM_NegotiateNTOnly = 0x00000400
|
||||
NTLM_Anonymous = 0x00000800
|
||||
NTLM_NegotiateOemDomainSupplied = 0x00001000
|
||||
NTLM_NegotiateOemWorkstationSupplied = 0x00002000
|
||||
NTLM_Unknown6 = 0x00004000
|
||||
NTLM_NegotiateAlwaysSign = 0x00008000
|
||||
NTLM_TargetTypeDomain = 0x00010000
|
||||
NTLM_TargetTypeServer = 0x00020000
|
||||
NTLM_TargetTypeShare = 0x00040000
|
||||
NTLM_NegotiateExtendedSecurity = 0x00080000
|
||||
NTLM_NegotiateIdentify = 0x00100000
|
||||
NTLM_Unknown5 = 0x00200000
|
||||
NTLM_RequestNonNTSessionKey = 0x00400000
|
||||
NTLM_NegotiateTargetInfo = 0x00800000
|
||||
NTLM_Unknown4 = 0x01000000
|
||||
NTLM_NegotiateVersion = 0x02000000
|
||||
NTLM_Unknown3 = 0x04000000
|
||||
NTLM_Unknown2 = 0x08000000
|
||||
NTLM_Unknown1 = 0x10000000
|
||||
NTLM_Negotiate128 = 0x20000000
|
||||
NTLM_NegotiateKeyExchange = 0x40000000
|
||||
NTLM_Negotiate56 = 0x80000000
|
||||
|
||||
NTLM_FLAGS = NTLM_NegotiateUnicode | \
|
||||
NTLM_RequestTarget | \
|
||||
NTLM_NegotiateNTLM | \
|
||||
NTLM_NegotiateAlwaysSign | \
|
||||
NTLM_NegotiateExtendedSecurity | \
|
||||
NTLM_NegotiateTargetInfo | \
|
||||
NTLM_NegotiateVersion | \
|
||||
NTLM_Negotiate128 | \
|
||||
NTLM_NegotiateKeyExchange | \
|
||||
NTLM_Negotiate56
|
||||
|
||||
def generateNegotiateMessage():
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.1
|
||||
"""
|
||||
s = struct.pack('<8sII8s8s8s',
|
||||
'NTLMSSP\0', 0x01, NTLM_FLAGS,
|
||||
'\0' * 8, # Domain
|
||||
'\0' * 8, # Workstation
|
||||
'\x06\x00\x72\x17\x00\x00\x00\x0F') # Version [MS-NLMP]: 2.2.2.10
|
||||
return s
|
||||
|
||||
|
||||
def generateAuthenticateMessage(challenge_flags, nt_response, lm_response, session_key, user, domain = 'WORKGROUP', workstation = 'LOCALHOST'):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.3
|
||||
"""
|
||||
FORMAT = '<8sIHHIHHIHHIHHIHHIHHII'
|
||||
FORMAT_SIZE = struct.calcsize(FORMAT)
|
||||
|
||||
lm_response_length = len(lm_response)
|
||||
lm_response_offset = FORMAT_SIZE
|
||||
nt_response_length = len(nt_response)
|
||||
nt_response_offset = lm_response_offset + lm_response_length
|
||||
domain_unicode = domain.encode('UTF-16LE')
|
||||
domain_length = len(domain_unicode)
|
||||
domain_offset = nt_response_offset + nt_response_length
|
||||
|
||||
padding = ''
|
||||
if domain_offset % 2 != 0:
|
||||
padding = '\0'
|
||||
domain_offset += 1
|
||||
|
||||
user_unicode = user.encode('UTF-16LE')
|
||||
user_length = len(user_unicode)
|
||||
user_offset = domain_offset + domain_length
|
||||
workstation_unicode = workstation.encode('UTF-16LE')
|
||||
workstation_length = len(workstation_unicode)
|
||||
workstation_offset = user_offset + user_length
|
||||
session_key_length = len(session_key)
|
||||
session_key_offset = workstation_offset + workstation_length
|
||||
|
||||
auth_flags = challenge_flags
|
||||
auth_flags &= ~NTLM_NegotiateVersion
|
||||
|
||||
s = struct.pack(FORMAT,
|
||||
'NTLMSSP\0', 0x03,
|
||||
lm_response_length, lm_response_length, lm_response_offset,
|
||||
nt_response_length, nt_response_length, nt_response_offset,
|
||||
domain_length, domain_length, domain_offset,
|
||||
user_length, user_length, user_offset,
|
||||
workstation_length, workstation_length, workstation_offset,
|
||||
session_key_length, session_key_length, session_key_offset,
|
||||
auth_flags)
|
||||
|
||||
return s + lm_response + nt_response + padding + domain_unicode + user_unicode + workstation_unicode + session_key
|
||||
|
||||
|
||||
def decodeChallengeMessage(ntlm_data):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- [MS-NLMP]: 2.2.1.2
|
||||
- [MS-NLMP]: 2.2.2.1 (AV_PAIR)
|
||||
"""
|
||||
FORMAT = '<8sIHHII8s8sHHI'
|
||||
FORMAT_SIZE = struct.calcsize(FORMAT)
|
||||
|
||||
signature, message_type, \
|
||||
targetname_len, targetname_maxlen, targetname_offset, \
|
||||
flags, challenge, _, \
|
||||
targetinfo_len, targetinfo_maxlen, targetinfo_offset, \
|
||||
= struct.unpack(FORMAT, ntlm_data[:FORMAT_SIZE])
|
||||
|
||||
assert signature == 'NTLMSSP\0'
|
||||
assert message_type == 0x02
|
||||
|
||||
return challenge, flags, ntlm_data[targetinfo_offset:targetinfo_offset+targetinfo_len]
|
||||
|
||||
|
||||
def generateChallengeResponseV2(password, user, server_challenge, server_info, domain = '', client_challenge = None):
|
||||
client_timestamp = '\0' * 8
|
||||
|
||||
if not client_challenge:
|
||||
client_challenge = ''
|
||||
for i in range(0, 8):
|
||||
client_challenge += chr(random.getrandbits(8))
|
||||
assert len(client_challenge) == 8
|
||||
|
||||
d = MD4()
|
||||
d.update(password.encode('UTF-16LE'))
|
||||
ntlm_hash = d.digest() # The NT password hash
|
||||
response_key = hmac.new(ntlm_hash, (user.upper() + domain).encode('UTF-16LE')).digest() # The NTLMv2 password hash. In [MS-NLMP], this is the result of NTOWFv2 and LMOWFv2 functions
|
||||
temp = '\x01\x01' + '\0'*6 + client_timestamp + client_challenge + '\0'*4 + server_info
|
||||
ntproofstr = hmac.new(response_key, server_challenge + temp).digest()
|
||||
|
||||
nt_challenge_response = ntproofstr + temp
|
||||
lm_challenge_response = hmac.new(response_key, server_challenge + client_challenge).digest() + client_challenge
|
||||
session_key = hmac.new(response_key, ntproofstr).digest()
|
||||
|
||||
return nt_challenge_response, lm_challenge_response, session_key
|
||||
|
||||
|
||||
################
|
||||
# NTLMv1 Methods
|
||||
################
|
||||
|
||||
def expandDesKey(key):
|
||||
"""Expand the key from a 7-byte password key into a 8-byte DES key"""
|
||||
s = chr(((ord(key[0]) >> 1) & 0x7f) << 1)
|
||||
s = s + chr(((ord(key[0]) & 0x01) << 6 | ((ord(key[1]) >> 2) & 0x3f)) << 1)
|
||||
s = s + chr(((ord(key[1]) & 0x03) << 5 | ((ord(key[2]) >> 3) & 0x1f)) << 1)
|
||||
s = s + chr(((ord(key[2]) & 0x07) << 4 | ((ord(key[3]) >> 4) & 0x0f)) << 1)
|
||||
s = s + chr(((ord(key[3]) & 0x0f) << 3 | ((ord(key[4]) >> 5) & 0x07)) << 1)
|
||||
s = s + chr(((ord(key[4]) & 0x1f) << 2 | ((ord(key[5]) >> 6) & 0x03)) << 1)
|
||||
s = s + chr(((ord(key[5]) & 0x3f) << 1 | ((ord(key[6]) >> 7) & 0x01)) << 1)
|
||||
s = s + chr((ord(key[6]) & 0x7f) << 1)
|
||||
return s
|
||||
|
||||
|
||||
def DESL(K, D):
|
||||
"""
|
||||
References:
|
||||
===========
|
||||
- http://ubiqx.org/cifs/SMB.html (2.8.3.4)
|
||||
- [MS-NLMP]: Section 6
|
||||
"""
|
||||
d1 = des(expandDesKey(K[0:7]))
|
||||
d2 = des(expandDesKey(K[7:14]))
|
||||
d3 = des(expandDesKey(K[14:16] + '\0' * 5))
|
||||
return d1.encrypt(D) + d2.encrypt(D) + d3.encrypt(D)
|
||||
|
||||
|
||||
def generateChallengeResponseV1(password, server_challenge, has_extended_security = False, client_challenge = None):
|
||||
"""
|
||||
Generate a NTLMv1 response
|
||||
|
||||
@param password: User password string
|
||||
@param server_challange: A 8-byte challenge string sent from the server
|
||||
@param has_extended_security: A boolean value indicating whether NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is enabled in the NTLM negFlag
|
||||
@param client_challenge: A 8-byte string representing client challenge. If None, it will be generated randomly if needed by the response generation
|
||||
@return: a tuple of ( NT challenge response string, LM challenge response string )
|
||||
|
||||
References:
|
||||
===========
|
||||
- http://ubiqx.org/cifs/SMB.html (2.8.3.3 and 2.8.3.4)
|
||||
- [MS-NLMP]: 3.3.1
|
||||
"""
|
||||
_password = (password.upper() + '\0' * 14)[:14]
|
||||
d1 = des(expandDesKey(_password[:7]))
|
||||
d2 = des(expandDesKey(_password[7:]))
|
||||
lm_response_key = d1.encrypt("KGS!@#$%") + d2.encrypt("KGS!@#$%") # LM password hash. In [MS-NLMP], this is the result of LMOWFv1 function
|
||||
|
||||
d = MD4()
|
||||
d.update(password.encode('UTF-16LE'))
|
||||
nt_response_key = d.digest() # In [MS-NLMP], this is the result of NTOWFv1 function
|
||||
|
||||
if has_extended_security:
|
||||
if not client_challenge:
|
||||
client_challenge = ''
|
||||
for i in range(0, 8):
|
||||
client_challenge += chr(random.getrandbits(8))
|
||||
|
||||
assert len(client_challenge) == 8
|
||||
|
||||
lm_challenge_response = client_challenge + '\0'*16
|
||||
nt_challenge_response = DESL(nt_response_key, MD5(server_challenge + client_challenge).digest()[0:8])
|
||||
else:
|
||||
nt_challenge_response = DESL(nt_response_key, server_challenge) # The result after DESL is the NT response
|
||||
lm_challenge_response = DESL(lm_response_key, server_challenge) # The result after DESL is the LM response
|
||||
|
||||
d = MD4()
|
||||
d.update(nt_response_key)
|
||||
session_key = d.digest()
|
||||
|
||||
return nt_challenge_response, lm_challenge_response, session_key
|
||||
@@ -0,0 +1,367 @@
|
||||
"""
|
||||
This module implements security descriptors, and the partial structures
|
||||
used in them, as specified in [MS-DTYP].
|
||||
"""
|
||||
|
||||
import struct
|
||||
|
||||
|
||||
# Security descriptor control flags
|
||||
# [MS-DTYP]: 2.4.6
|
||||
SECURITY_DESCRIPTOR_OWNER_DEFAULTED = 0x0001
|
||||
SECURITY_DESCRIPTOR_GROUP_DEFAULTED = 0x0002
|
||||
SECURITY_DESCRIPTOR_DACL_PRESENT = 0x0004
|
||||
SECURITY_DESCRIPTOR_DACL_DEFAULTED = 0x0008
|
||||
SECURITY_DESCRIPTOR_SACL_PRESENT = 0x0010
|
||||
SECURITY_DESCRIPTOR_SACL_DEFAULTED = 0x0020
|
||||
SECURITY_DESCRIPTOR_SERVER_SECURITY = 0x0040
|
||||
SECURITY_DESCRIPTOR_DACL_TRUSTED = 0x0080
|
||||
SECURITY_DESCRIPTOR_DACL_COMPUTED_INHERITANCE_REQUIRED = 0x0100
|
||||
SECURITY_DESCRIPTOR_SACL_COMPUTED_INHERITANCE_REQUIRED = 0x0200
|
||||
SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED = 0x0400
|
||||
SECURITY_DESCRIPTOR_SACL_AUTO_INHERITED = 0x0800
|
||||
SECURITY_DESCRIPTOR_DACL_PROTECTED = 0x1000
|
||||
SECURITY_DESCRIPTOR_SACL_PROTECTED = 0x2000
|
||||
SECURITY_DESCRIPTOR_RM_CONTROL_VALID = 0x4000
|
||||
SECURITY_DESCRIPTOR_SELF_RELATIVE = 0x8000
|
||||
|
||||
# ACE types
|
||||
# [MS-DTYP]: 2.4.4.1
|
||||
ACE_TYPE_ACCESS_ALLOWED = 0x00
|
||||
ACE_TYPE_ACCESS_DENIED = 0x01
|
||||
ACE_TYPE_SYSTEM_AUDIT = 0x02
|
||||
ACE_TYPE_SYSTEM_ALARM = 0x03
|
||||
ACE_TYPE_ACCESS_ALLOWED_COMPOUND = 0x04
|
||||
ACE_TYPE_ACCESS_ALLOWED_OBJECT = 0x05
|
||||
ACE_TYPE_ACCESS_DENIED_OBJECT = 0x06
|
||||
ACE_TYPE_SYSTEM_AUDIT_OBJECT = 0x07
|
||||
ACE_TYPE_SYSTEM_ALARM_OBJECT = 0x08
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK = 0x09
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK = 0x0A
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0x0C
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK = 0x0D
|
||||
ACE_TYPE_SYSTEM_ALARM_CALLBACK = 0x0E
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F
|
||||
ACE_TYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10
|
||||
ACE_TYPE_SYSTEM_MANDATORY_LABEL = 0x11
|
||||
ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE = 0x12
|
||||
ACE_TYPE_SYSTEM_SCOPED_POLICY_ID = 0x13
|
||||
|
||||
# ACE flags
|
||||
# [MS-DTYP]: 2.4.4.1
|
||||
ACE_FLAG_OBJECT_INHERIT = 0x01
|
||||
ACE_FLAG_CONTAINER_INHERIT = 0x02
|
||||
ACE_FLAG_NO_PROPAGATE_INHERIT = 0x04
|
||||
ACE_FLAG_INHERIT_ONLY = 0x08
|
||||
ACE_FLAG_INHERITED = 0x10
|
||||
ACE_FLAG_SUCCESSFUL_ACCESS = 0x40
|
||||
ACE_FLAG_FAILED_ACCESS = 0x80
|
||||
|
||||
# Pre-defined well-known SIDs
|
||||
# [MS-DTYP]: 2.4.2.4
|
||||
SID_NULL = "S-1-0-0"
|
||||
SID_EVERYONE = "S-1-1-0"
|
||||
SID_LOCAL = "S-1-2-0"
|
||||
SID_CONSOLE_LOGON = "S-1-2-1"
|
||||
SID_CREATOR_OWNER = "S-1-3-0"
|
||||
SID_CREATOR_GROUP = "S-1-3-1"
|
||||
SID_OWNER_SERVER = "S-1-3-2"
|
||||
SID_GROUP_SERVER = "S-1-3-3"
|
||||
SID_OWNER_RIGHTS = "S-1-3-4"
|
||||
SID_NT_AUTHORITY = "S-1-5"
|
||||
SID_DIALUP = "S-1-5-1"
|
||||
SID_NETWORK = "S-1-5-2"
|
||||
SID_BATCH = "S-1-5-3"
|
||||
SID_INTERACTIVE = "S-1-5-4"
|
||||
SID_SERVICE = "S-1-5-6"
|
||||
SID_ANONYMOUS = "S-1-5-7"
|
||||
SID_PROXY = "S-1-5-8"
|
||||
SID_ENTERPRISE_DOMAIN_CONTROLLERS = "S-1-5-9"
|
||||
SID_PRINCIPAL_SELF = "S-1-5-10"
|
||||
SID_AUTHENTICATED_USERS = "S-1-5-11"
|
||||
SID_RESTRICTED_CODE = "S-1-5-12"
|
||||
SID_TERMINAL_SERVER_USER = "S-1-5-13"
|
||||
SID_REMOTE_INTERACTIVE_LOGON = "S-1-5-14"
|
||||
SID_THIS_ORGANIZATION = "S-1-5-15"
|
||||
SID_IUSR = "S-1-5-17"
|
||||
SID_LOCAL_SYSTEM = "S-1-5-18"
|
||||
SID_LOCAL_SERVICE = "S-1-5-19"
|
||||
SID_NETWORK_SERVICE = "S-1-5-20"
|
||||
SID_COMPOUNDED_AUTHENTICATION = "S-1-5-21-0-0-0-496"
|
||||
SID_CLAIMS_VALID = "S-1-5-21-0-0-0-497"
|
||||
SID_BUILTIN_ADMINISTRATORS = "S-1-5-32-544"
|
||||
SID_BUILTIN_USERS = "S-1-5-32-545"
|
||||
SID_BUILTIN_GUESTS = "S-1-5-32-546"
|
||||
SID_POWER_USERS = "S-1-5-32-547"
|
||||
SID_ACCOUNT_OPERATORS = "S-1-5-32-548"
|
||||
SID_SERVER_OPERATORS = "S-1-5-32-549"
|
||||
SID_PRINTER_OPERATORS = "S-1-5-32-550"
|
||||
SID_BACKUP_OPERATORS = "S-1-5-32-551"
|
||||
SID_REPLICATOR = "S-1-5-32-552"
|
||||
SID_ALIAS_PREW2KCOMPACC = "S-1-5-32-554"
|
||||
SID_REMOTE_DESKTOP = "S-1-5-32-555"
|
||||
SID_NETWORK_CONFIGURATION_OPS = "S-1-5-32-556"
|
||||
SID_INCOMING_FOREST_TRUST_BUILDERS = "S-1-5-32-557"
|
||||
SID_PERFMON_USERS = "S-1-5-32-558"
|
||||
SID_PERFLOG_USERS = "S-1-5-32-559"
|
||||
SID_WINDOWS_AUTHORIZATION_ACCESS_GROUP = "S-1-5-32-560"
|
||||
SID_TERMINAL_SERVER_LICENSE_SERVERS = "S-1-5-32-561"
|
||||
SID_DISTRIBUTED_COM_USERS = "S-1-5-32-562"
|
||||
SID_IIS_IUSRS = "S-1-5-32-568"
|
||||
SID_CRYPTOGRAPHIC_OPERATORS = "S-1-5-32-569"
|
||||
SID_EVENT_LOG_READERS = "S-1-5-32-573"
|
||||
SID_CERTIFICATE_SERVICE_DCOM_ACCESS = "S-1-5-32-574"
|
||||
SID_RDS_REMOTE_ACCESS_SERVERS = "S-1-5-32-575"
|
||||
SID_RDS_ENDPOINT_SERVERS = "S-1-5-32-576"
|
||||
SID_RDS_MANAGEMENT_SERVERS = "S-1-5-32-577"
|
||||
SID_HYPER_V_ADMINS = "S-1-5-32-578"
|
||||
SID_ACCESS_CONTROL_ASSISTANCE_OPS = "S-1-5-32-579"
|
||||
SID_REMOTE_MANAGEMENT_USERS = "S-1-5-32-580"
|
||||
SID_WRITE_RESTRICTED_CODE = "S-1-5-33"
|
||||
SID_NTLM_AUTHENTICATION = "S-1-5-64-10"
|
||||
SID_SCHANNEL_AUTHENTICATION = "S-1-5-64-14"
|
||||
SID_DIGEST_AUTHENTICATION = "S-1-5-64-21"
|
||||
SID_THIS_ORGANIZATION_CERTIFICATE = "S-1-5-65-1"
|
||||
SID_NT_SERVICE = "S-1-5-80"
|
||||
SID_USER_MODE_DRIVERS = "S-1-5-84-0-0-0-0-0"
|
||||
SID_LOCAL_ACCOUNT = "S-1-5-113"
|
||||
SID_LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP = "S-1-5-114"
|
||||
SID_OTHER_ORGANIZATION = "S-1-5-1000"
|
||||
SID_ALL_APP_PACKAGES = "S-1-15-2-1"
|
||||
SID_ML_UNTRUSTED = "S-1-16-0"
|
||||
SID_ML_LOW = "S-1-16-4096"
|
||||
SID_ML_MEDIUM = "S-1-16-8192"
|
||||
SID_ML_MEDIUM_PLUS = "S-1-16-8448"
|
||||
SID_ML_HIGH = "S-1-16-12288"
|
||||
SID_ML_SYSTEM = "S-1-16-16384"
|
||||
SID_ML_PROTECTED_PROCESS = "S-1-16-20480"
|
||||
SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY = "S-1-18-1"
|
||||
SID_SERVICE_ASSERTED_IDENTITY = "S-1-18-2"
|
||||
SID_FRESH_PUBLIC_KEY_IDENTITY = "S-1-18-3"
|
||||
SID_KEY_TRUST_IDENTITY = "S-1-18-4"
|
||||
SID_KEY_PROPERTY_MFA = "S-1-18-5"
|
||||
SID_KEY_PROPERTY_ATTESTATION = "S-1-18-6"
|
||||
|
||||
|
||||
class SID(object):
|
||||
"""
|
||||
A Windows security identifier. Represents a single principal, such a
|
||||
user or a group, as a sequence of numbers consisting of the revision,
|
||||
identifier authority, and a variable-length list of subauthorities.
|
||||
|
||||
See [MS-DTYP]: 2.4.2
|
||||
"""
|
||||
def __init__(self, revision, identifier_authority, subauthorities):
|
||||
#: Revision, should always be 1.
|
||||
self.revision = revision
|
||||
#: An integer representing the identifier authority.
|
||||
self.identifier_authority = identifier_authority
|
||||
#: A list of integers representing all subauthorities.
|
||||
self.subauthorities = subauthorities
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
String representation, as specified in [MS-DTYP]: 2.4.2.1
|
||||
"""
|
||||
if self.identifier_authority >= 2**32:
|
||||
id_auth = '%#x' % (self.identifier_authority,)
|
||||
else:
|
||||
id_auth = self.identifier_authority
|
||||
auths = [self.revision, id_auth] + self.subauthorities
|
||||
return 'S-' + '-'.join(str(subauth) for subauth in auths)
|
||||
|
||||
def __repr__(self):
|
||||
return 'SID(%r)' % (str(self),)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data, return_tail=False):
|
||||
revision, subauth_count = struct.unpack('<BB', data[:2])
|
||||
identifier_authority = struct.unpack('>Q', '\x00\x00' + data[2:8])[0]
|
||||
subauth_data = data[8:]
|
||||
subauthorities = [struct.unpack('<L', subauth_data[4 * i : 4 * (i+1)])[0]
|
||||
for i in range(subauth_count)]
|
||||
sid = cls(revision, identifier_authority, subauthorities)
|
||||
if return_tail:
|
||||
return sid, subauth_data[4 * subauth_count :]
|
||||
return sid
|
||||
|
||||
|
||||
class ACE(object):
|
||||
"""
|
||||
Represents a single access control entry.
|
||||
|
||||
See [MS-DTYP]: 2.4.4
|
||||
"""
|
||||
HEADER_FORMAT = '<BBH'
|
||||
|
||||
def __init__(self, type_, flags, mask, sid, additional_data):
|
||||
#: An integer representing the type of the ACE. One of the
|
||||
#: ``ACE_TYPE_*`` constants. Corresponds to the ``AceType`` field
|
||||
#: from [MS-DTYP] 2.4.4.1.
|
||||
self.type = type_
|
||||
#: An integer bitmask with ACE flags, corresponds to the
|
||||
#: ``AceFlags`` field.
|
||||
self.flags = flags
|
||||
#: An integer representing the ``ACCESS_MASK`` as specified in
|
||||
#: [MS-DTYP] 2.4.3.
|
||||
self.mask = mask
|
||||
#: The :class:`SID` of a trustee.
|
||||
self.sid = sid
|
||||
#: A dictionary of additional fields present in the ACE, depending
|
||||
#: on the type. The following fields can be present:
|
||||
#:
|
||||
#: * ``flags``
|
||||
#: * ``object_type``
|
||||
#: * ``inherited_object_type``
|
||||
#: * ``application_data``
|
||||
#: * ``attribute_data``
|
||||
self.additional_data = additional_data
|
||||
|
||||
def __repr__(self):
|
||||
return "ACE(type=%#04x, flags=%#04x, mask=%#010x, sid=%s)" % (
|
||||
self.type, self.flags, self.mask, self.sid,
|
||||
)
|
||||
|
||||
@property
|
||||
def isInheritOnly(self):
|
||||
"""Convenience property which indicates if this ACE is inherit
|
||||
only, meaning that it doesn't apply to the object itself."""
|
||||
return bool(self.flags & ACE_FLAG_INHERIT_ONLY)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
header_size = struct.calcsize(cls.HEADER_FORMAT)
|
||||
header = data[:header_size]
|
||||
type_, flags, size = struct.unpack(cls.HEADER_FORMAT, header)
|
||||
|
||||
assert len(data) >= size
|
||||
|
||||
body = data[header_size:size]
|
||||
additional_data = {}
|
||||
|
||||
# In all ACE types, the mask immediately follows the header.
|
||||
mask = struct.unpack('<I', body[:4])[0]
|
||||
body = body[4:]
|
||||
|
||||
# All OBJECT-type ACEs contain additional flags, and two GUIDs as
|
||||
# the following fields.
|
||||
if type_ in (ACE_TYPE_ACCESS_ALLOWED_OBJECT,
|
||||
ACE_TYPE_ACCESS_DENIED_OBJECT,
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
|
||||
additional_data['flags'] = struct.unpack('<I', body[:4])[0]
|
||||
additional_data['object_type'] = body[4:20]
|
||||
additional_data['inherited_object_type'] = body[20:36]
|
||||
body = body[36:]
|
||||
|
||||
# Then the SID in all types.
|
||||
sid, body = SID.from_bytes(body, return_tail=True)
|
||||
|
||||
# CALLBACK-type ACEs (and for some obscure reason,
|
||||
# SYSTEM_AUDIT_OBJECT) have a final tail of application data.
|
||||
if type_ in (ACE_TYPE_ACCESS_ALLOWED_CALLBACK,
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK,
|
||||
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK,
|
||||
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
|
||||
additional_data['application_data'] = body
|
||||
|
||||
# SYSTEM_RESOURCE_ATTRIBUTE ACEs have a tail of attribute data.
|
||||
if type_ == ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE:
|
||||
additional_data['attribute_data'] = body
|
||||
|
||||
return cls(type_, flags, mask, sid, additional_data)
|
||||
|
||||
|
||||
class ACL(object):
|
||||
"""
|
||||
Access control list, encapsulating a sequence of access control
|
||||
entries.
|
||||
|
||||
See [MS-DTYP]: 2.4.5
|
||||
"""
|
||||
HEADER_FORMAT = '<BBHHH'
|
||||
|
||||
def __init__(self, revision, aces):
|
||||
#: Integer value of the revision.
|
||||
self.revision = revision
|
||||
#: List of :class:`ACE` instances.
|
||||
self.aces = aces
|
||||
|
||||
def __repr__(self):
|
||||
return "ACL(%r)" % (self.aces,)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
revision = None
|
||||
aces = []
|
||||
|
||||
header_size = struct.calcsize(cls.HEADER_FORMAT)
|
||||
header, remaining = data[:header_size], data[header_size:]
|
||||
revision, sbz1, size, count, sbz2 = struct.unpack(cls.HEADER_FORMAT, header)
|
||||
|
||||
assert len(data) >= size
|
||||
|
||||
for i in range(count):
|
||||
ace_size = struct.unpack('<H', remaining[2:4])[0]
|
||||
ace_data, remaining = remaining[:ace_size], remaining[ace_size:]
|
||||
aces.append(ACE.from_bytes(ace_data))
|
||||
|
||||
return cls(revision, aces)
|
||||
|
||||
|
||||
class SecurityDescriptor(object):
|
||||
"""
|
||||
Represents a security descriptor.
|
||||
|
||||
See [MS-DTYP]: 2.4.6
|
||||
"""
|
||||
|
||||
HEADER_FORMAT = '<BBHIIII'
|
||||
|
||||
def __init__(self, flags, owner, group, dacl, sacl):
|
||||
#: Integer bitmask of control flags. Corresponds to the
|
||||
#: ``Control`` field in [MS-DTYP] 2.4.6.
|
||||
self.flags = flags
|
||||
#: Instance of :class:`SID` representing the owner user.
|
||||
self.owner = owner
|
||||
#: Instance of :class:`SID` representing the owner group.
|
||||
self.group = group
|
||||
#: Instance of :class:`ACL` representing the discretionary access
|
||||
#: control list, which specifies access restrictions of an object.
|
||||
self.dacl = dacl
|
||||
#: Instance of :class:`ACL` representing the system access control
|
||||
#: list, which specifies audit logging of an object.
|
||||
self.sacl = sacl
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
owner = None
|
||||
group = None
|
||||
dacl = None
|
||||
sacl = None
|
||||
|
||||
header = data[:struct.calcsize(cls.HEADER_FORMAT)]
|
||||
(revision, sbz1, flags, owner_offset, group_offset, sacl_offset,
|
||||
dacl_offset) = struct.unpack(cls.HEADER_FORMAT, header)
|
||||
|
||||
assert revision == 1
|
||||
assert flags & SECURITY_DESCRIPTOR_SELF_RELATIVE
|
||||
for offset in (owner_offset, group_offset, sacl_offset, dacl_offset):
|
||||
assert 0 <= offset < len(data)
|
||||
|
||||
if owner_offset:
|
||||
owner = SID.from_bytes(data[owner_offset:])
|
||||
if group_offset:
|
||||
group = SID.from_bytes(data[group_offset:])
|
||||
if dacl_offset:
|
||||
dacl = ACL.from_bytes(data[dacl_offset:])
|
||||
if sacl_offset:
|
||||
sacl = ACL.from_bytes(data[sacl_offset:])
|
||||
|
||||
return cls(flags, owner, group, dacl, sacl)
|
||||
@@ -0,0 +1,136 @@
|
||||
|
||||
from pyasn1.type import tag, univ, namedtype, namedval, constraint
|
||||
from pyasn1.codec.der import encoder, decoder
|
||||
|
||||
__all__ = [ 'generateNegotiateSecurityBlob', 'generateAuthSecurityBlob', 'decodeChallengeSecurityBlob', 'decodeAuthResponseSecurityBlob' ]
|
||||
|
||||
|
||||
class UnsupportedSecurityProvider(Exception): pass
|
||||
class BadSecurityBlobError(Exception): pass
|
||||
|
||||
|
||||
def generateNegotiateSecurityBlob(ntlm_data):
|
||||
mech_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
|
||||
mech_types = MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
|
||||
mech_types.setComponentByPosition(0, univ.ObjectIdentifier('1.3.6.1.4.1.311.2.2.10'))
|
||||
|
||||
n = NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
|
||||
n.setComponentByName('mechTypes', mech_types)
|
||||
n.setComponentByName('mechToken', mech_token)
|
||||
|
||||
nt = NegotiationToken()
|
||||
nt.setComponentByName('negTokenInit', n)
|
||||
|
||||
ct = ContextToken()
|
||||
ct.setComponentByName('thisMech', univ.ObjectIdentifier('1.3.6.1.5.5.2'))
|
||||
ct.setComponentByName('innerContextToken', nt)
|
||||
|
||||
return encoder.encode(ct)
|
||||
|
||||
|
||||
def generateAuthSecurityBlob(ntlm_data):
|
||||
response_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
|
||||
|
||||
n = NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
|
||||
n.setComponentByName('responseToken', response_token)
|
||||
|
||||
nt = NegotiationToken()
|
||||
nt.setComponentByName('negTokenTarg', n)
|
||||
|
||||
return encoder.encode(nt)
|
||||
|
||||
|
||||
def decodeChallengeSecurityBlob(data):
|
||||
try:
|
||||
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
|
||||
nt = d.getComponentByName('negTokenTarg')
|
||||
|
||||
token = nt.getComponentByName('responseToken')
|
||||
if not token:
|
||||
raise BadSecurityBlobError('NTLMSSP_CHALLENGE security blob does not contain responseToken field')
|
||||
|
||||
provider_oid = nt.getComponentByName('supportedMech')
|
||||
if provider_oid and str(provider_oid) != '1.3.6.1.4.1.311.2.2.10': # This OID is defined in [MS-NLMP]: 1.9
|
||||
raise UnsupportedSecurityProvider('Security provider "%s" is not supported by pysmb' % str(provider_oid))
|
||||
|
||||
result = nt.getComponentByName('negResult')
|
||||
return int(result), str(token)
|
||||
except Exception, ex:
|
||||
raise BadSecurityBlobError(str(ex))
|
||||
|
||||
|
||||
def decodeAuthResponseSecurityBlob(data):
|
||||
try:
|
||||
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
|
||||
nt = d.getComponentByName('negTokenTarg')
|
||||
|
||||
result = nt.getComponentByName('negResult')
|
||||
return int(result)
|
||||
except Exception, ex:
|
||||
raise BadSecurityBlobError(str(ex))
|
||||
|
||||
|
||||
#
|
||||
# GSS-API ASN.1 (RFC2478 section 3.2.1)
|
||||
#
|
||||
|
||||
RESULT_ACCEPT_COMPLETED = 0
|
||||
RESULT_ACCEPT_INCOMPLETE = 1
|
||||
RESULT_REJECT = 2
|
||||
|
||||
class NegResultEnumerated(univ.Enumerated):
|
||||
namedValues = namedval.NamedValues(
|
||||
( 'accept_completed', 0 ),
|
||||
( 'accept_incomplete', 1 ),
|
||||
( 'reject', 2 )
|
||||
)
|
||||
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0, 1, 2)
|
||||
|
||||
|
||||
class MechTypeList(univ.SequenceOf):
|
||||
componentType = univ.ObjectIdentifier()
|
||||
|
||||
|
||||
class ContextFlags(univ.BitString):
|
||||
namedValues = namedval.NamedValues(
|
||||
( 'delegFlag', 0 ),
|
||||
( 'mutualFlag', 1 ),
|
||||
( 'replayFlag', 2 ),
|
||||
( 'sequenceFlag', 3 ),
|
||||
( 'anonFlag', 4 ),
|
||||
( 'confFlag', 5 ),
|
||||
( 'integFlag', 6 )
|
||||
)
|
||||
|
||||
|
||||
class NegTokenInit(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('mechTypes', MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.OptionalNamedType('reqFlags', ContextFlags().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||
namedtype.OptionalNamedType('mechToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||
)
|
||||
|
||||
|
||||
class NegTokenTarg(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.OptionalNamedType('negResult', NegResultEnumerated().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.OptionalNamedType('supportedMech', univ.ObjectIdentifier().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
|
||||
namedtype.OptionalNamedType('responseToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
|
||||
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
|
||||
)
|
||||
|
||||
|
||||
class NegotiationToken(univ.Choice):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('negTokenInit', NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
|
||||
namedtype.NamedType('negTokenTarg', NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
|
||||
)
|
||||
|
||||
|
||||
class ContextToken(univ.Sequence):
|
||||
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('thisMech', univ.ObjectIdentifier()),
|
||||
namedtype.NamedType('innerContextToken', NegotiationToken())
|
||||
)
|
||||
@@ -0,0 +1,115 @@
|
||||
|
||||
# Bitmask for Flags field in SMB2 message header
|
||||
SMB2_FLAGS_SERVER_TO_REDIR = 0x01
|
||||
SMB2_FLAGS_ASYNC_COMMAND = 0x02
|
||||
SMB2_FLAGS_RELATED_OPERATIONS = 0x04
|
||||
SMB2_FLAGS_SIGNED = 0x08
|
||||
SMB2_FLAGS_DFS_OPERATIONS = 0x10000000
|
||||
|
||||
# Values for Command field in SMB2 message header
|
||||
SMB2_COM_NEGOTIATE = 0x0000
|
||||
SMB2_COM_SESSION_SETUP = 0x0001
|
||||
SMB2_COM_LOGOFF = 0x0002
|
||||
SMB2_COM_TREE_CONNECT = 0x0003
|
||||
SMB2_COM_TREE_DISCONNECT = 0x0004
|
||||
SMB2_COM_CREATE = 0x0005
|
||||
SMB2_COM_CLOSE = 0x0006
|
||||
SMB2_COM_FLUSH = 0x0007
|
||||
SMB2_COM_READ = 0x0008
|
||||
SMB2_COM_WRITE = 0x0009
|
||||
SMB2_COM_LOCK = 0x000A
|
||||
SMB2_COM_IOCTL = 0x000B
|
||||
SMB2_COM_CANCEL = 0x000C
|
||||
SMB2_COM_ECHO = 0x000D
|
||||
SMB2_COM_QUERY_DIRECTORY = 0x000E
|
||||
SMB2_COM_CHANGE_NOTIFY = 0x000F
|
||||
SMB2_COM_QUERY_INFO = 0x0010
|
||||
SMB2_COM_SET_INFO = 0x0011
|
||||
SMB2_COM_OPLOCK_BREAK = 0x0012
|
||||
|
||||
SMB2_COMMAND_NAMES = {
|
||||
0x0000: 'SMB2_COM_NEGOTIATE',
|
||||
0x0001: 'SMB2_COM_SESSION_SETUP',
|
||||
0x0002: 'SMB2_COM_LOGOFF',
|
||||
0x0003: 'SMB2_COM_TREE_CONNECT',
|
||||
0x0004: 'SMB2_COM_TREE_DISCONNECT',
|
||||
0x0005: 'SMB2_COM_CREATE',
|
||||
0x0006: 'SMB2_COM_CLOSE',
|
||||
0x0007: 'SMB2_COM_FLUSH',
|
||||
0x0008: 'SMB2_COM_READ',
|
||||
0x0009: 'SMB2_COM_WRITE',
|
||||
0x000A: 'SMB2_COM_LOCK',
|
||||
0x000B: 'SMB2_COM_IOCTL',
|
||||
0x000C: 'SMB2_COM_CANCEL',
|
||||
0x000D: 'SMB2_COM_ECHO',
|
||||
0x000E: 'SMB2_COM_QUERY_DIRECTORY',
|
||||
0x000F: 'SMB2_COM_CHANGE_NOTIFY',
|
||||
0x0010: 'SMB2_COM_QUERY_INFO',
|
||||
0x0011: 'SMB2_COM_SET_INFO',
|
||||
0x0012: 'SMB2_COM_OPLOCK_BREAK',
|
||||
}
|
||||
|
||||
# Values for dialect_revision field in SMB2NegotiateResponse class
|
||||
SMB2_DIALECT_2 = 0x0202 # 2.0.2 - First SMB2 version
|
||||
SMB2_DIALECT_21 = 0x0210 # 2.1 - Windows 7
|
||||
SMB2_DIALET_30 = 0x0300 # 3.0 - Windows 8
|
||||
SMB2_DIALECT_302 = 0x0302 # 3.0.2 - Windows 8.1
|
||||
SMB2_DIALECT_311 = 0x0311 # 3.1.1 - Windows 10
|
||||
SMB2_DIALECT_2ALL = 0x02FF # Wildcard (for negotiation only)
|
||||
|
||||
# Bit mask for SecurityMode field in SMB2NegotiateResponse class
|
||||
SMB2_NEGOTIATE_SIGNING_ENABLED = 0x0001
|
||||
SMB2_NEGOTIATE_SIGNING_REQUIRED = 0x0002
|
||||
|
||||
# Values for ShareType field in SMB2TreeConnectResponse class
|
||||
SMB2_SHARE_TYPE_DISK = 0x01
|
||||
SMB2_SHARE_TYPE_PIPE = 0x02
|
||||
SMB2_SHARE_TYPE_PRINTER = 0x03
|
||||
|
||||
# Bitmask for Capabilities in SMB2TreeConnectResponse class
|
||||
SMB2_SHARE_CAP_DFS = 0x0008
|
||||
|
||||
|
||||
# SMB 2.1 / 3 Capabilities flags
|
||||
SMB2_GLOBAL_CAP_DFS = 0x01
|
||||
SMB2_GLOBAL_CAP_LEASING = 0x02
|
||||
SMB2_GLOBAL_CAP_LARGE_MTU = 0x04
|
||||
SMB2_GLOBAL_CAP_MULTI_CHANNEL = 0x08
|
||||
SMB2_GLOBAL_CAP_PERSISTENT_HANDLES = 0x10
|
||||
SMB2_GLOBAL_CAP_DIRECTORY_LEASING = 0x20
|
||||
SMB2_GLOBAL_CAP_ENCRYPTION = 0x40
|
||||
|
||||
|
||||
# Values for OpLockLevel field in SMB2CreateRequest class
|
||||
SMB2_OPLOCK_LEVEL_NONE = 0x00
|
||||
SMB2_OPLOCK_LEVEL_II = 0x01
|
||||
SMB2_OPLOCK_LEVEL_EXCLUSIVE = 0x08
|
||||
SMB2_OPLOCK_LEVEL_BATCH = 0x09
|
||||
SMB2_OPLOCK_LEVEL_LEASE = 0xFF
|
||||
|
||||
# Values for FileAttributes field in SMB2CreateRequest class
|
||||
# The values are defined in [MS-FSCC] 2.6
|
||||
SMB2_FILE_ATTRIBUTE_ARCHIVE = 0x0020
|
||||
SMB2_FILE_ATTRIBUTE_COMPRESSED = 0x0800
|
||||
SMB2_FILE_ATTRIBUTE_DIRECTORY = 0x0010
|
||||
SMB2_FILE_ATTRIBUTE_ENCRYPTED = 0x4000
|
||||
SMB2_FILE_ATTRIBUTE_HIDDEN = 0x0002
|
||||
SMB2_FILE_ATTRIBUTE_NORMAL = 0x0080
|
||||
SMB2_FILE_ATTRIBUTE_NOTINDEXED = 0x2000
|
||||
SMB2_FILE_ATTRIBUTE_OFFLINE = 0x1000
|
||||
SMB2_FILE_ATTRIBUTE_READONLY = 0x0001
|
||||
SMB2_FILE_ATTRIBUTE_SPARSE = 0x0200
|
||||
SMB2_FILE_ATTRIBUTE_SYSTEM = 0x0004
|
||||
SMB2_FILE_ATTRIBUTE_TEMPORARY = 0x0100
|
||||
|
||||
# Values for CreateAction field in SMB2CreateResponse class
|
||||
SMB2_FILE_SUPERCEDED = 0x00
|
||||
SMB2_FILE_OPENED = 0x01
|
||||
SMB2_FILE_CREATED = 0x02
|
||||
SMB2_FILE_OVERWRITTEN = 0x03
|
||||
|
||||
# Values for InfoType field in SMB2QueryInfoRequest class
|
||||
SMB2_INFO_FILE = 0x01
|
||||
SMB2_INFO_FILESYSTEM = 0x02
|
||||
SMB2_INFO_SECURITY = 0x03
|
||||
SMB2_INFO_QUOTA = 0x04
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,257 @@
|
||||
|
||||
# Values for Command field in SMB message header
|
||||
SMB_COM_CREATE_DIRECTORY = 0x00
|
||||
SMB_COM_DELETE_DIRECTORY = 0x01
|
||||
SMB_COM_CLOSE = 0x04
|
||||
SMB_COM_DELETE = 0x06
|
||||
SMB_COM_RENAME = 0x07
|
||||
SMB_COM_TRANSACTION = 0x25
|
||||
SMB_COM_ECHO = 0x2B
|
||||
SMB_COM_OPEN_ANDX = 0x2D
|
||||
SMB_COM_READ_ANDX = 0x2E
|
||||
SMB_COM_WRITE_ANDX = 0x2F
|
||||
SMB_COM_TRANSACTION2 = 0x32
|
||||
SMB_COM_NEGOTIATE = 0x72
|
||||
SMB_COM_SESSION_SETUP_ANDX = 0x73
|
||||
SMB_COM_TREE_CONNECT_ANDX = 0x75
|
||||
SMB_COM_NT_TRANSACT = 0xA0
|
||||
SMB_COM_NT_CREATE_ANDX = 0xA2
|
||||
|
||||
SMB_COMMAND_NAMES = {
|
||||
0x00: 'SMB_COM_CREATE_DIRECTORY',
|
||||
0x01: 'SMB_COM_DELETE_DIRECTORY',
|
||||
0x04: 'SMB_COM_CLOSE',
|
||||
0x06: 'SMB_COM_DELETE',
|
||||
0x25: 'SMB_COM_TRANSACTION',
|
||||
0x2B: 'SMB_COM_ECHO',
|
||||
0x2D: 'SMB_COM_OPEN_ANDX',
|
||||
0x2E: 'SMB_COM_READ_ANDX',
|
||||
0x2F: 'SMB_COM_WRITE_ANDX',
|
||||
0x32: 'SMB_COM_TRANSACTION2',
|
||||
0x72: 'SMB_COM_NEGOTIATE',
|
||||
0x73: 'SMB_COM_SESSION_SETUP_ANDX',
|
||||
0x75: 'SMB_COM_TREE_CONNECT_ANDX',
|
||||
0xA0: 'SMB_COM_NT_TRANSACT',
|
||||
0xA2: 'SMB_COM_NT_CREATE_ANDX',
|
||||
}
|
||||
|
||||
# Bitmask for Flags field in SMB message header
|
||||
SMB_FLAGS_LOCK_AND_READ_OK = 0x01 # LANMAN1.0
|
||||
SMB_FLAGS_BUF_AVAIL = 0x02 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_CASE_INSENSITIVE = 0x08 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_CANONICALIZED_PATHS = 0x10 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_OPLOCK = 0x20 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_OPBATCH = 0x40 # LANMAN1.0, Obsolete
|
||||
SMB_FLAGS_REPLY = 0x80 # LANMAN1.0
|
||||
|
||||
# Bitmask for Flags2 field in SMB message header
|
||||
SMB_FLAGS2_LONG_NAMES = 0x0001 # LANMAN2.0
|
||||
SMB_FLAGS2_EAS = 0x0002 # LANMAN1.2
|
||||
SMB_FLAGS2_SMB_SECURITY_SIGNATURE = 0x0004 # NT LANMAN
|
||||
SMB_FLAGS2_IS_LONG_NAME = 0x0040 # NT LANMAN
|
||||
SMB_FLAGS2_DFS = 0x1000 # NT LANMAN
|
||||
SMB_FLAGS2_REPARSE_PATH = 0x0400 #
|
||||
SMB_FLAGS2_EXTENDED_SECURITY = 0x0800 #
|
||||
SMB_FLAGS2_PAGING_IO = 0x2000 # NT LANMAN
|
||||
SMB_FLAGS2_NT_STATUS = 0x4000 # NT LANMAN
|
||||
SMB_FLAGS2_UNICODE = 0x8000 # NT LANMAN
|
||||
|
||||
# Bitmask for Capabilities field in SMB_COM_SESSION_SETUP_ANDX response
|
||||
# [MS-SMB]: 2.2.4.5.2.1 (Capabilities field)
|
||||
CAP_RAW_MODE = 0x01
|
||||
CAP_MPX_MODE = 0x02
|
||||
CAP_UNICODE = 0x04
|
||||
CAP_LARGE_FILES = 0x08
|
||||
CAP_NT_SMBS = 0x10
|
||||
CAP_RPC_REMOTE_APIS = 0x20
|
||||
CAP_STATUS32 = 0x40
|
||||
CAP_LEVEL_II_OPLOCKS = 0x80
|
||||
CAP_LOCK_AND_READ = 0x0100
|
||||
CAP_NT_FIND = 0x0200
|
||||
CAP_DFS = 0x1000
|
||||
CAP_INFOLEVEL_PASSTHRU = 0x2000
|
||||
CAP_LARGE_READX = 0x4000
|
||||
CAP_LARGE_WRITEX = 0x8000
|
||||
CAP_LWIO = 0x010000
|
||||
CAP_UNIX = 0x800000
|
||||
CAP_COMPRESSED = 0x02000000
|
||||
CAP_DYNAMIC_REAUTH = 0x20000000
|
||||
CAP_PERSISTENT_HANDLES = 0x40000000
|
||||
CAP_EXTENDED_SECURITY = 0x80000000
|
||||
|
||||
# Value for Action field in SMB_COM_SESSION_SETUP_ANDX response
|
||||
SMB_SETUP_GUEST = 0x0001
|
||||
SMB_SETUP_USE_LANMAN_KEY = 0X0002
|
||||
|
||||
# Bitmask for SecurityMode field in SMB_COM_NEGOTIATE response
|
||||
NEGOTIATE_USER_SECURITY = 0x01
|
||||
NEGOTIATE_ENCRYPT_PASSWORDS = 0x02
|
||||
NEGOTIATE_SECURITY_SIGNATURES_ENABLE = 0x04
|
||||
NEGOTIATE_SECURITY_SIGNATURES_REQUIRE = 0x08
|
||||
|
||||
# Available constants for Service field in SMB_COM_TREE_CONNECT_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.55.1 (Service field)
|
||||
SERVICE_PRINTER = 'LPT1:'
|
||||
SERVICE_NAMED_PIPE = 'IPC'
|
||||
SERVICE_COMM = 'COMM'
|
||||
SERVICE_ANY = '?????'
|
||||
|
||||
# Bitmask for Flags field in SMB_COM_NT_CREATE_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB]: 2.2.4.9.1
|
||||
NT_CREATE_REQUEST_OPLOCK = 0x02
|
||||
NT_CREATE_REQUEST_OPBATCH = 0x04
|
||||
NT_CREATE_OPEN_TARGET_DIR = 0x08
|
||||
NT_CREATE_REQUEST_EXTENDED_RESPONSE = 0x10 # Defined in [MS-SMB]: 2.2.4.9.1
|
||||
|
||||
# Bitmask for DesiredAccess field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# Also used for MaximalAccess field in SMB2TreeConnectResponse class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13.1.1
|
||||
FILE_READ_DATA = 0x01
|
||||
FILE_WRITE_DATA = 0X02
|
||||
FILE_APPEND_DATA = 0x04
|
||||
FILE_READ_EA = 0x08
|
||||
FILE_WRITE_EA = 0x10
|
||||
FILE_EXECUTE = 0x20
|
||||
FILE_DELETE_CHILD = 0x40
|
||||
FILE_READ_ATTRIBUTES = 0x80
|
||||
FILE_WRITE_ATTRIBUTES = 0x0100
|
||||
DELETE = 0x010000
|
||||
READ_CONTROL = 0x020000
|
||||
WRITE_DAC = 0x040000
|
||||
WRITE_OWNER = 0x080000
|
||||
SYNCHRONIZE = 0x100000
|
||||
ACCESS_SYSTEM_SECURITY = 0x01000000
|
||||
MAXIMUM_ALLOWED = 0x02000000
|
||||
GENERIC_ALL = 0x10000000
|
||||
GENERIC_EXECUTE = 0x20000000
|
||||
GENERIC_WRITE = 0x40000000
|
||||
GENERIC_READ = 0x80000000L
|
||||
|
||||
# SMB_EXT_FILE_ATTR bitmask ([MS-CIFS]: 2.2.1.2.3)
|
||||
# Includes extensions defined in [MS-SMB] 2.2.1.2.1
|
||||
# Bitmask for FileAttributes field in SMB_COM_NT_CREATE_ANDX request ([MS-CIFS]: 2.2.4.64.1)
|
||||
# Also used for FileAttributes field in SMB2CreateRequest class ([MS-SMB2]: 2.2.13)
|
||||
ATTR_READONLY = 0x01
|
||||
ATTR_HIDDEN = 0x02
|
||||
ATTR_SYSTEM = 0x04
|
||||
ATTR_DIRECTORY = 0x10
|
||||
ATTR_ARCHIVE = 0x20
|
||||
ATTR_NORMAL = 0x80
|
||||
ATTR_TEMPORARY = 0x0100
|
||||
ATTR_SPARSE = 0x0200
|
||||
ATTR_REPARSE_POINT = 0x0400
|
||||
ATTR_COMPRESSED = 0x0800
|
||||
ATTR_OFFLINE = 0x1000
|
||||
ATTR_NOT_CONTENT_INDEXED = 0x2000
|
||||
ATTR_ENCRYPTED = 0x4000
|
||||
POSIX_SEMANTICS = 0x01000000
|
||||
BACKUP_SEMANTICS = 0x02000000
|
||||
DELETE_ON_CLOSE = 0x04000000
|
||||
SEQUENTIAL_SCAN = 0x08000000
|
||||
RANDOM_ACCESS = 0x10000000
|
||||
NO_BUFFERING = 0x20000000
|
||||
WRITE_THROUGH = 0x80000000
|
||||
|
||||
# Bitmask for ShareAccess field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_SHARE_NONE = 0x00
|
||||
FILE_SHARE_READ = 0x01
|
||||
FILE_SHARE_WRITE = 0x02
|
||||
FILE_SHARE_DELETE = 0x04
|
||||
|
||||
# Values for CreateDisposition field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_SUPERSEDE = 0x00
|
||||
FILE_OPEN = 0x01
|
||||
FILE_CREATE = 0x02
|
||||
FILE_OPEN_IF = 0x03
|
||||
FILE_OVERWRITE = 0x04
|
||||
FILE_OVERWRITE_IF = 0x05
|
||||
|
||||
# Bitmask for CreateOptions field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
FILE_DIRECTORY_FILE = 0x01
|
||||
FILE_WRITE_THROUGH = 0x02
|
||||
FILE_SEQUENTIAL_ONLY = 0x04
|
||||
FILE_NO_INTERMEDIATE_BUFFERING = 0x08
|
||||
FILE_SYNCHRONOUS_IO_ALERT = 0x10
|
||||
FILE_SYNCHRONOUS_IO_NONALERT = 0x20
|
||||
FILE_NON_DIRECTORY_FILE = 0x40
|
||||
FILE_CREATE_TREE_CONNECTION = 0x80
|
||||
FILE_COMPLETE_IF_OPLOCKED = 0x0100
|
||||
FILE_NO_EA_KNOWLEDGE = 0x0200
|
||||
FILE_OPEN_FOR_RECOVERY = 0x0400
|
||||
FILE_RANDOM_ACCESS = 0x0800
|
||||
FILE_DELETE_ON_CLOSE = 0x1000
|
||||
FILE_OPEN_BY_FILE_ID = 0x2000
|
||||
FILE_OPEN_FOR_BACKUP_INTENT = 0x4000
|
||||
FILE_NO_COMPRESSION = 0x8000
|
||||
FILE_RESERVE_OPFILTER = 0x100000
|
||||
FILE_OPEN_NO_RECALL = 0x400000
|
||||
FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x800000
|
||||
|
||||
# Values for ImpersonationLevel field in SMB_COM_NT_CREATE_ANDX request
|
||||
# and SMB2CreateRequest class
|
||||
# For interpretations about these values, refer to [MS-WSO] and [MSDN-IMPERS]
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
# [MS-SMB]: 2.2.4.9.1
|
||||
# [MS-SMB2]: 2.2.13
|
||||
SEC_ANONYMOUS = 0x00
|
||||
SEC_IDENTIFY = 0x01
|
||||
SEC_IMPERSONATE = 0x02
|
||||
SEC_DELEGATION = 0x03 # Defined in [MS-SMB]: 2.2.4.9.1
|
||||
|
||||
# Values for SecurityFlags field in SMB_COM_NT_CREATE_ANDX request
|
||||
# [MS-CIFS]: 2.2.4.64.1
|
||||
SMB_SECURITY_CONTEXT_TRACKING = 0x01
|
||||
SMB_SECURITY_EFFECTIVE_ONLY = 0x02
|
||||
|
||||
# Bitmask for Flags field in SMB_COM_TRANSACTION2 request
|
||||
# [MS-CIFS]: 2.2.4.46.1
|
||||
DISCONNECT_TID = 0x01
|
||||
NO_RESPONSE = 0x02
|
||||
|
||||
# Bitmask for basic file attributes
|
||||
# [MS-CIFS]: 2.2.1.2.4
|
||||
SMB_FILE_ATTRIBUTE_NORMAL = 0x00
|
||||
SMB_FILE_ATTRIBUTE_READONLY = 0x01
|
||||
SMB_FILE_ATTRIBUTE_HIDDEN = 0x02
|
||||
SMB_FILE_ATTRIBUTE_SYSTEM = 0x04
|
||||
SMB_FILE_ATTRIBUTE_VOLUME = 0x08 # Unsupported for listPath() operations
|
||||
SMB_FILE_ATTRIBUTE_DIRECTORY = 0x10
|
||||
SMB_FILE_ATTRIBUTE_ARCHIVE = 0x20
|
||||
# SMB_FILE_ATTRIBUTE_INCL_NORMAL is a special placeholder to include normal files for
|
||||
# with other search attributes for listPath() operations. It is not defined in the MS-CIFS specs.
|
||||
SMB_FILE_ATTRIBUTE_INCL_NORMAL = 0x10000
|
||||
# Do not use the following values for listPath() operations as they are not supported for SMB2
|
||||
SMB_SEARCH_ATTRIBUTE_READONLY = 0x0100
|
||||
SMB_SEARCH_ATTRIBUTE_HIDDEN = 0x0200
|
||||
SMB_SEARCH_ATTRIBUTE_SYSTEM = 0x0400
|
||||
SMB_SEARCH_ATTRIBUTE_DIRECTORY = 0x1000
|
||||
SMB_SEARCH_ATTRIBUTE_ARCHIVE = 0x2000
|
||||
|
||||
# Bitmask for OptionalSupport field in SMB_COM_TREE_CONNECT_ANDX response
|
||||
SMB_TREE_CONNECTX_SUPPORT_SEARCH = 0x0001
|
||||
SMB_TREE_CONNECTX_SUPPORT_DFS = 0x0002
|
||||
|
||||
# Bitmask for security information fields, specified as
|
||||
# AdditionalInformation in SMB2
|
||||
# [MS-SMB]: 2.2.7.4
|
||||
# [MS-SMB2]: 2.2.37
|
||||
OWNER_SECURITY_INFORMATION = 0x00000001
|
||||
GROUP_SECURITY_INFORMATION = 0x00000002
|
||||
DACL_SECURITY_INFORMATION = 0x00000004
|
||||
SACL_SECURITY_INFORMATION = 0x00000008
|
||||
LABEL_SECURITY_INFORMATION = 0x00000010
|
||||
ATTRIBUTE_SECURITY_INFORMATION = 0x00000020
|
||||
SCOPE_SECURITY_INFORMATION = 0x00000040
|
||||
BACKUP_SECURITY_INFORMATION = 0x00010000
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
|
||||
md4.py and U32.py
|
||||
Both modules downloaded from http://www.oocities.org/rozmanov/python/md4.html.
|
||||
Licensed under LGPL
|
||||
|
||||
pyDes.py 2.0.0
|
||||
Downloaded from http://twhiteman.netfirms.com/des.html
|
||||
Licensed under public domain
|
||||
|
||||
sha256.py
|
||||
Downloaded from http://xbmc-addons.googlecode.com/svn-history/r1686/trunk/scripts/OpenSubtitles_OSD/resources/lib/sha256.py
|
||||
Licensed under MIT
|
||||
@@ -0,0 +1,148 @@
|
||||
# U32.py implements 32-bit unsigned int class for Python
|
||||
# Version 1.0
|
||||
# Copyright (C) 2001-2002 Dmitry Rozmanov
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# e-mail: dima@xenon.spb.ru
|
||||
#
|
||||
#====================================================================
|
||||
|
||||
C = 0x1000000000L
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def norm(n):
|
||||
return n & 0xFFFFFFFFL
|
||||
|
||||
#====================================================================
|
||||
class U32:
|
||||
v = 0L
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __init__(self, value = 0):
|
||||
self.v = C + norm(abs(long(value)))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def set(self, value = 0):
|
||||
self.v = C + norm(abs(long(value)))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __repr__(self):
|
||||
return hex(norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __long__(self): return long(norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __int__(self): return int(norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __chr__(self): return chr(norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __add__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v + b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __sub__(self, b):
|
||||
r = U32()
|
||||
if self.v < b.v:
|
||||
r.v = C + norm(0x100000000L - (b.v - self.v))
|
||||
else: r.v = C + norm(self.v - b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __mul__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v * b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __div__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) / norm(b.v))
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __mod__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) % norm(b.v))
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __neg__(self): return U32(self.v)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __pos__(self): return U32(self.v)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __abs__(self): return U32(self.v)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __invert__(self):
|
||||
r = U32()
|
||||
r.v = C + norm(~self.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __lshift__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v << b)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __rshift__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) >> b)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __and__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v & b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __or__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v | b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __xor__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v ^ b.v)
|
||||
return r
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __not__(self):
|
||||
return U32(not norm(self.v))
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def truth(self):
|
||||
return norm(self.v)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __cmp__(self, b):
|
||||
if norm(self.v) > norm(b.v): return 1
|
||||
elif norm(self.v) < norm(b.v): return -1
|
||||
else: return 0
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
def __nonzero__(self):
|
||||
return norm(self.v)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
def convertFILETIMEtoEpoch(t):
|
||||
return (t - 116444736000000000L) / 10000000.0;
|
||||
@@ -0,0 +1,254 @@
|
||||
# md4.py implements md4 hash class for Python
|
||||
# Version 1.0
|
||||
# Copyright (C) 2001-2002 Dmitry Rozmanov
|
||||
#
|
||||
# based on md4.c from "the Python Cryptography Toolkit, version 1.0.0
|
||||
# Copyright (C) 1995, A.M. Kuchling"
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# e-mail: dima@xenon.spb.ru
|
||||
#
|
||||
#====================================================================
|
||||
|
||||
# MD4 validation data
|
||||
|
||||
md4_test= [
|
||||
('', 0x31d6cfe0d16ae931b73c59d7e0c089c0L),
|
||||
("a", 0xbde52cb31de33e46245e05fbdbd6fb24L),
|
||||
("abc", 0xa448017aaf21d8525fc10ae87aa6729dL),
|
||||
("message digest", 0xd9130a8164549fe818874806e1c7014bL),
|
||||
("abcdefghijklmnopqrstuvwxyz", 0xd79e1c308aa5bbcdeea8ed63df412da9L),
|
||||
("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
||||
0x043f8582f241db351ce627e153e7f0e4L),
|
||||
("12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
0xe33b4ddc9c38f2199c3e7b164fcc0536L),
|
||||
]
|
||||
|
||||
#====================================================================
|
||||
from U32 import U32
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
class MD4:
|
||||
A = None
|
||||
B = None
|
||||
C = None
|
||||
D = None
|
||||
count, len1, len2 = None, None, None
|
||||
buf = []
|
||||
|
||||
#-----------------------------------------------------
|
||||
def __init__(self):
|
||||
|
||||
|
||||
self.A = U32(0x67452301L)
|
||||
self.B = U32(0xefcdab89L)
|
||||
self.C = U32(0x98badcfeL)
|
||||
self.D = U32(0x10325476L)
|
||||
self.count, self.len1, self.len2 = U32(0L), U32(0L), U32(0L)
|
||||
self.buf = [0x00] * 64
|
||||
|
||||
#-----------------------------------------------------
|
||||
def __repr__(self):
|
||||
r = 'A = %s, \nB = %s, \nC = %s, \nD = %s.\n' % (self.A.__repr__(), self.B.__repr__(), self.C.__repr__(), self.D.__repr__())
|
||||
r = r + 'count = %s, \nlen1 = %s, \nlen2 = %s.\n' % (self.count.__repr__(), self.len1.__repr__(), self.len2.__repr__())
|
||||
for i in range(4):
|
||||
for j in range(16):
|
||||
r = r + '%4s ' % hex(self.buf[i+j])
|
||||
r = r + '\n'
|
||||
|
||||
return r
|
||||
#-----------------------------------------------------
|
||||
def make_copy(self):
|
||||
|
||||
dest = new()
|
||||
|
||||
dest.len1 = self.len1
|
||||
dest.len2 = self.len2
|
||||
dest.A = self.A
|
||||
dest.B = self.B
|
||||
dest.C = self.C
|
||||
dest.D = self.D
|
||||
dest.count = self.count
|
||||
for i in range(self.count):
|
||||
dest.buf[i] = self.buf[i]
|
||||
|
||||
return dest
|
||||
|
||||
#-----------------------------------------------------
|
||||
def update(self, str):
|
||||
|
||||
buf = []
|
||||
for i in str: buf.append(ord(i))
|
||||
ilen = U32(len(buf))
|
||||
|
||||
# check if the first length is out of range
|
||||
# as the length is measured in bits then multiplay it by 8
|
||||
if (long(self.len1 + (ilen << 3)) < long(self.len1)):
|
||||
self.len2 = self.len2 + U32(1)
|
||||
|
||||
self.len1 = self.len1 + (ilen << 3)
|
||||
self.len2 = self.len2 + (ilen >> 29)
|
||||
|
||||
L = U32(0)
|
||||
bufpos = 0
|
||||
while (long(ilen) > 0):
|
||||
if (64 - long(self.count)) < long(ilen): L = U32(64 - long(self.count))
|
||||
else: L = ilen
|
||||
for i in range(int(L)): self.buf[i + int(self.count)] = buf[i + bufpos]
|
||||
self.count = self.count + L
|
||||
ilen = ilen - L
|
||||
bufpos = bufpos + int(L)
|
||||
|
||||
if (long(self.count) == 64L):
|
||||
self.count = U32(0L)
|
||||
X = []
|
||||
i = 0
|
||||
for j in range(16):
|
||||
X.append(U32(self.buf[i]) + (U32(self.buf[i+1]) << 8) + \
|
||||
(U32(self.buf[i+2]) << 16) + (U32(self.buf[i+3]) << 24))
|
||||
i = i + 4
|
||||
|
||||
A = self.A
|
||||
B = self.B
|
||||
C = self.C
|
||||
D = self.D
|
||||
|
||||
A = f1(A,B,C,D, 0, 3, X)
|
||||
D = f1(D,A,B,C, 1, 7, X)
|
||||
C = f1(C,D,A,B, 2,11, X)
|
||||
B = f1(B,C,D,A, 3,19, X)
|
||||
A = f1(A,B,C,D, 4, 3, X)
|
||||
D = f1(D,A,B,C, 5, 7, X)
|
||||
C = f1(C,D,A,B, 6,11, X)
|
||||
B = f1(B,C,D,A, 7,19, X)
|
||||
A = f1(A,B,C,D, 8, 3, X)
|
||||
D = f1(D,A,B,C, 9, 7, X)
|
||||
C = f1(C,D,A,B,10,11, X)
|
||||
B = f1(B,C,D,A,11,19, X)
|
||||
A = f1(A,B,C,D,12, 3, X)
|
||||
D = f1(D,A,B,C,13, 7, X)
|
||||
C = f1(C,D,A,B,14,11, X)
|
||||
B = f1(B,C,D,A,15,19, X)
|
||||
|
||||
A = f2(A,B,C,D, 0, 3, X)
|
||||
D = f2(D,A,B,C, 4, 5, X)
|
||||
C = f2(C,D,A,B, 8, 9, X)
|
||||
B = f2(B,C,D,A,12,13, X)
|
||||
A = f2(A,B,C,D, 1, 3, X)
|
||||
D = f2(D,A,B,C, 5, 5, X)
|
||||
C = f2(C,D,A,B, 9, 9, X)
|
||||
B = f2(B,C,D,A,13,13, X)
|
||||
A = f2(A,B,C,D, 2, 3, X)
|
||||
D = f2(D,A,B,C, 6, 5, X)
|
||||
C = f2(C,D,A,B,10, 9, X)
|
||||
B = f2(B,C,D,A,14,13, X)
|
||||
A = f2(A,B,C,D, 3, 3, X)
|
||||
D = f2(D,A,B,C, 7, 5, X)
|
||||
C = f2(C,D,A,B,11, 9, X)
|
||||
B = f2(B,C,D,A,15,13, X)
|
||||
|
||||
A = f3(A,B,C,D, 0, 3, X)
|
||||
D = f3(D,A,B,C, 8, 9, X)
|
||||
C = f3(C,D,A,B, 4,11, X)
|
||||
B = f3(B,C,D,A,12,15, X)
|
||||
A = f3(A,B,C,D, 2, 3, X)
|
||||
D = f3(D,A,B,C,10, 9, X)
|
||||
C = f3(C,D,A,B, 6,11, X)
|
||||
B = f3(B,C,D,A,14,15, X)
|
||||
A = f3(A,B,C,D, 1, 3, X)
|
||||
D = f3(D,A,B,C, 9, 9, X)
|
||||
C = f3(C,D,A,B, 5,11, X)
|
||||
B = f3(B,C,D,A,13,15, X)
|
||||
A = f3(A,B,C,D, 3, 3, X)
|
||||
D = f3(D,A,B,C,11, 9, X)
|
||||
C = f3(C,D,A,B, 7,11, X)
|
||||
B = f3(B,C,D,A,15,15, X)
|
||||
|
||||
self.A = self.A + A
|
||||
self.B = self.B + B
|
||||
self.C = self.C + C
|
||||
self.D = self.D + D
|
||||
|
||||
#-----------------------------------------------------
|
||||
def digest(self):
|
||||
|
||||
res = [0x00] * 16
|
||||
s = [0x00] * 8
|
||||
padding = [0x00] * 64
|
||||
padding[0] = 0x80
|
||||
padlen, oldlen1, oldlen2 = U32(0), U32(0), U32(0)
|
||||
|
||||
temp = self.make_copy()
|
||||
|
||||
oldlen1 = temp.len1
|
||||
oldlen2 = temp.len2
|
||||
if (56 <= long(self.count)): padlen = U32(56 - long(self.count) + 64)
|
||||
else: padlen = U32(56 - long(self.count))
|
||||
|
||||
temp.update(int_array2str(padding[:int(padlen)]))
|
||||
|
||||
s[0]= (oldlen1) & U32(0xFF)
|
||||
s[1]=((oldlen1) >> 8) & U32(0xFF)
|
||||
s[2]=((oldlen1) >> 16) & U32(0xFF)
|
||||
s[3]=((oldlen1) >> 24) & U32(0xFF)
|
||||
s[4]= (oldlen2) & U32(0xFF)
|
||||
s[5]=((oldlen2) >> 8) & U32(0xFF)
|
||||
s[6]=((oldlen2) >> 16) & U32(0xFF)
|
||||
s[7]=((oldlen2) >> 24) & U32(0xFF)
|
||||
temp.update(int_array2str(s))
|
||||
|
||||
res[ 0]= temp.A & U32(0xFF)
|
||||
res[ 1]=(temp.A >> 8) & U32(0xFF)
|
||||
res[ 2]=(temp.A >> 16) & U32(0xFF)
|
||||
res[ 3]=(temp.A >> 24) & U32(0xFF)
|
||||
res[ 4]= temp.B & U32(0xFF)
|
||||
res[ 5]=(temp.B >> 8) & U32(0xFF)
|
||||
res[ 6]=(temp.B >> 16) & U32(0xFF)
|
||||
res[ 7]=(temp.B >> 24) & U32(0xFF)
|
||||
res[ 8]= temp.C & U32(0xFF)
|
||||
res[ 9]=(temp.C >> 8) & U32(0xFF)
|
||||
res[10]=(temp.C >> 16) & U32(0xFF)
|
||||
res[11]=(temp.C >> 24) & U32(0xFF)
|
||||
res[12]= temp.D & U32(0xFF)
|
||||
res[13]=(temp.D >> 8) & U32(0xFF)
|
||||
res[14]=(temp.D >> 16) & U32(0xFF)
|
||||
res[15]=(temp.D >> 24) & U32(0xFF)
|
||||
|
||||
return int_array2str(res)
|
||||
|
||||
#====================================================================
|
||||
# helpers
|
||||
def F(x, y, z): return (((x) & (y)) | ((~x) & (z)))
|
||||
def G(x, y, z): return (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
|
||||
def H(x, y, z): return ((x) ^ (y) ^ (z))
|
||||
|
||||
def ROL(x, n): return (((x) << n) | ((x) >> (32-n)))
|
||||
|
||||
def f1(a, b, c, d, k, s, X): return ROL(a + F(b, c, d) + X[k], s)
|
||||
def f2(a, b, c, d, k, s, X): return ROL(a + G(b, c, d) + X[k] + U32(0x5a827999L), s)
|
||||
def f3(a, b, c, d, k, s, X): return ROL(a + H(b, c, d) + X[k] + U32(0x6ed9eba1L), s)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# helper function
|
||||
def int_array2str(array):
|
||||
str = ''
|
||||
for i in array:
|
||||
str = str + chr(i)
|
||||
return str
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# To be able to use md4.new() instead of md4.MD4()
|
||||
new = MD4
|
||||
@@ -0,0 +1,852 @@
|
||||
#############################################################################
|
||||
# Documentation #
|
||||
#############################################################################
|
||||
|
||||
# Author: Todd Whiteman
|
||||
# Date: 16th March, 2009
|
||||
# Verion: 2.0.0
|
||||
# License: Public Domain - free to do as you wish
|
||||
# Homepage: http://twhiteman.netfirms.com/des.html
|
||||
#
|
||||
# This is a pure python implementation of the DES encryption algorithm.
|
||||
# It's pure python to avoid portability issues, since most DES
|
||||
# implementations are programmed in C (for performance reasons).
|
||||
#
|
||||
# Triple DES class is also implemented, utilising the DES base. Triple DES
|
||||
# is either DES-EDE3 with a 24 byte key, or DES-EDE2 with a 16 byte key.
|
||||
#
|
||||
# See the README.txt that should come with this python module for the
|
||||
# implementation methods used.
|
||||
#
|
||||
# Thanks to:
|
||||
# * David Broadwell for ideas, comments and suggestions.
|
||||
# * Mario Wolff for pointing out and debugging some triple des CBC errors.
|
||||
# * Santiago Palladino for providing the PKCS5 padding technique.
|
||||
# * Shaya for correcting the PAD_PKCS5 triple des CBC errors.
|
||||
#
|
||||
"""A pure python implementation of the DES and TRIPLE DES encryption algorithms.
|
||||
|
||||
Class initialization
|
||||
--------------------
|
||||
pyDes.des(key, [mode], [IV], [pad], [padmode])
|
||||
pyDes.triple_des(key, [mode], [IV], [pad], [padmode])
|
||||
|
||||
key -> Bytes containing the encryption key. 8 bytes for DES, 16 or 24 bytes
|
||||
for Triple DES
|
||||
mode -> Optional argument for encryption type, can be either
|
||||
pyDes.ECB (Electronic Code Book) or pyDes.CBC (Cypher Block Chaining)
|
||||
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
|
||||
Length must be 8 bytes.
|
||||
pad -> Optional argument, set the pad character (PAD_NORMAL) to use during
|
||||
all encrypt/decrpt operations done with this instance.
|
||||
padmode -> Optional argument, set the padding mode (PAD_NORMAL or PAD_PKCS5)
|
||||
to use during all encrypt/decrpt operations done with this instance.
|
||||
|
||||
I recommend to use PAD_PKCS5 padding, as then you never need to worry about any
|
||||
padding issues, as the padding can be removed unambiguously upon decrypting
|
||||
data that was encrypted using PAD_PKCS5 padmode.
|
||||
|
||||
Common methods
|
||||
--------------
|
||||
encrypt(data, [pad], [padmode])
|
||||
decrypt(data, [pad], [padmode])
|
||||
|
||||
data -> Bytes to be encrypted/decrypted
|
||||
pad -> Optional argument. Only when using padmode of PAD_NORMAL. For
|
||||
encryption, adds this characters to the end of the data block when
|
||||
data is not a multiple of 8 bytes. For decryption, will remove the
|
||||
trailing characters that match this pad character from the last 8
|
||||
bytes of the unencrypted data block.
|
||||
padmode -> Optional argument, set the padding mode, must be one of PAD_NORMAL
|
||||
or PAD_PKCS5). Defaults to PAD_NORMAL.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
from pyDes import *
|
||||
|
||||
data = "Please encrypt my data"
|
||||
k = des("DESCRYPT", CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
|
||||
# For Python3, you'll need to use bytes, i.e.:
|
||||
# data = b"Please encrypt my data"
|
||||
# k = des(b"DESCRYPT", CBC, b"\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
|
||||
d = k.encrypt(data)
|
||||
print "Encrypted: %r" % d
|
||||
print "Decrypted: %r" % k.decrypt(d)
|
||||
assert k.decrypt(d, padmode=PAD_PKCS5) == data
|
||||
|
||||
|
||||
See the module source (pyDes.py) for more examples of use.
|
||||
You can also run the pyDes.py file without and arguments to see a simple test.
|
||||
|
||||
Note: This code was not written for high-end systems needing a fast
|
||||
implementation, but rather a handy portable solution with small usage.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
# _pythonMajorVersion is used to handle Python2 and Python3 differences.
|
||||
_pythonMajorVersion = sys.version_info[0]
|
||||
|
||||
# Modes of crypting / cyphering
|
||||
ECB = 0
|
||||
CBC = 1
|
||||
|
||||
# Modes of padding
|
||||
PAD_NORMAL = 1
|
||||
PAD_PKCS5 = 2
|
||||
|
||||
# PAD_PKCS5: is a method that will unambiguously remove all padding
|
||||
# characters after decryption, when originally encrypted with
|
||||
# this padding mode.
|
||||
# For a good description of the PKCS5 padding technique, see:
|
||||
# http://www.faqs.org/rfcs/rfc1423.html
|
||||
|
||||
# The base class shared by des and triple des.
|
||||
class _baseDes(object):
|
||||
def __init__(self, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
|
||||
if IV:
|
||||
IV = self._guardAgainstUnicode(IV)
|
||||
if pad:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
self.block_size = 8
|
||||
# Sanity checking of arguments.
|
||||
if pad and padmode == PAD_PKCS5:
|
||||
raise ValueError("Cannot use a pad character with PAD_PKCS5")
|
||||
if IV and len(IV) != self.block_size:
|
||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
||||
|
||||
# Set the passed in variables
|
||||
self._mode = mode
|
||||
self._iv = IV
|
||||
self._padding = pad
|
||||
self._padmode = padmode
|
||||
|
||||
def getKey(self):
|
||||
"""getKey() -> bytes"""
|
||||
return self.__key
|
||||
|
||||
def setKey(self, key):
|
||||
"""Will set the crypting key for this object."""
|
||||
key = self._guardAgainstUnicode(key)
|
||||
self.__key = key
|
||||
|
||||
def getMode(self):
|
||||
"""getMode() -> pyDes.ECB or pyDes.CBC"""
|
||||
return self._mode
|
||||
|
||||
def setMode(self, mode):
|
||||
"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
|
||||
self._mode = mode
|
||||
|
||||
def getPadding(self):
|
||||
"""getPadding() -> bytes of length 1. Padding character."""
|
||||
return self._padding
|
||||
|
||||
def setPadding(self, pad):
|
||||
"""setPadding() -> bytes of length 1. Padding character."""
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
self._padding = pad
|
||||
|
||||
def getPadMode(self):
|
||||
"""getPadMode() -> pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
|
||||
return self._padmode
|
||||
|
||||
def setPadMode(self, mode):
|
||||
"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
|
||||
self._padmode = mode
|
||||
|
||||
def getIV(self):
|
||||
"""getIV() -> bytes"""
|
||||
return self._iv
|
||||
|
||||
def setIV(self, IV):
|
||||
"""Will set the Initial Value, used in conjunction with CBC mode"""
|
||||
if not IV or len(IV) != self.block_size:
|
||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
||||
IV = self._guardAgainstUnicode(IV)
|
||||
self._iv = IV
|
||||
|
||||
def _padData(self, data, pad, padmode):
|
||||
# Pad data depending on the mode
|
||||
if padmode is None:
|
||||
# Get the default padding mode.
|
||||
padmode = self.getPadMode()
|
||||
if pad and padmode == PAD_PKCS5:
|
||||
raise ValueError("Cannot use a pad character with PAD_PKCS5")
|
||||
|
||||
if padmode == PAD_NORMAL:
|
||||
if len(data) % self.block_size == 0:
|
||||
# No padding required.
|
||||
return data
|
||||
|
||||
if not pad:
|
||||
# Get the default padding.
|
||||
pad = self.getPadding()
|
||||
if not pad:
|
||||
raise ValueError("Data must be a multiple of " + str(self.block_size) + " bytes in length. Use padmode=PAD_PKCS5 or set the pad character.")
|
||||
data += (self.block_size - (len(data) % self.block_size)) * pad
|
||||
|
||||
elif padmode == PAD_PKCS5:
|
||||
pad_len = 8 - (len(data) % self.block_size)
|
||||
if _pythonMajorVersion < 3:
|
||||
data += pad_len * chr(pad_len)
|
||||
else:
|
||||
data += bytes([pad_len] * pad_len)
|
||||
|
||||
return data
|
||||
|
||||
def _unpadData(self, data, pad, padmode):
|
||||
# Unpad data depending on the mode.
|
||||
if not data:
|
||||
return data
|
||||
if pad and padmode == PAD_PKCS5:
|
||||
raise ValueError("Cannot use a pad character with PAD_PKCS5")
|
||||
if padmode is None:
|
||||
# Get the default padding mode.
|
||||
padmode = self.getPadMode()
|
||||
|
||||
if padmode == PAD_NORMAL:
|
||||
if not pad:
|
||||
# Get the default padding.
|
||||
pad = self.getPadding()
|
||||
if pad:
|
||||
data = data[:-self.block_size] + \
|
||||
data[-self.block_size:].rstrip(pad)
|
||||
|
||||
elif padmode == PAD_PKCS5:
|
||||
if _pythonMajorVersion < 3:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
data = data[:-pad_len]
|
||||
|
||||
return data
|
||||
|
||||
def _guardAgainstUnicode(self, data):
|
||||
# Only accept byte strings or ascii unicode values, otherwise
|
||||
# there is no way to correctly decode the data into bytes.
|
||||
if _pythonMajorVersion < 3:
|
||||
if isinstance(data, unicode):
|
||||
raise ValueError("pyDes can only work with bytes, not Unicode strings.")
|
||||
else:
|
||||
if isinstance(data, str):
|
||||
# Only accept ascii unicode values.
|
||||
try:
|
||||
return data.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
raise ValueError("pyDes can only work with encoded strings, not Unicode.")
|
||||
return data
|
||||
|
||||
#############################################################################
|
||||
# DES #
|
||||
#############################################################################
|
||||
class des(_baseDes):
|
||||
"""DES encryption/decrytpion class
|
||||
|
||||
Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
|
||||
|
||||
pyDes.des(key,[mode], [IV])
|
||||
|
||||
key -> Bytes containing the encryption key, must be exactly 8 bytes
|
||||
mode -> Optional argument for encryption type, can be either pyDes.ECB
|
||||
(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
|
||||
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
|
||||
Must be 8 bytes in length.
|
||||
pad -> Optional argument, set the pad character (PAD_NORMAL) to use
|
||||
during all encrypt/decrpt operations done with this instance.
|
||||
padmode -> Optional argument, set the padding mode (PAD_NORMAL or
|
||||
PAD_PKCS5) to use during all encrypt/decrpt operations done
|
||||
with this instance.
|
||||
"""
|
||||
|
||||
|
||||
# Permutation and translation tables for DES
|
||||
__pc1 = [56, 48, 40, 32, 24, 16, 8,
|
||||
0, 57, 49, 41, 33, 25, 17,
|
||||
9, 1, 58, 50, 42, 34, 26,
|
||||
18, 10, 2, 59, 51, 43, 35,
|
||||
62, 54, 46, 38, 30, 22, 14,
|
||||
6, 61, 53, 45, 37, 29, 21,
|
||||
13, 5, 60, 52, 44, 36, 28,
|
||||
20, 12, 4, 27, 19, 11, 3
|
||||
]
|
||||
|
||||
# number left rotations of pc1
|
||||
__left_rotations = [
|
||||
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
|
||||
]
|
||||
|
||||
# permuted choice key (table 2)
|
||||
__pc2 = [
|
||||
13, 16, 10, 23, 0, 4,
|
||||
2, 27, 14, 5, 20, 9,
|
||||
22, 18, 11, 3, 25, 7,
|
||||
15, 6, 26, 19, 12, 1,
|
||||
40, 51, 30, 36, 46, 54,
|
||||
29, 39, 50, 44, 32, 47,
|
||||
43, 48, 38, 55, 33, 52,
|
||||
45, 41, 49, 35, 28, 31
|
||||
]
|
||||
|
||||
# initial permutation IP
|
||||
__ip = [57, 49, 41, 33, 25, 17, 9, 1,
|
||||
59, 51, 43, 35, 27, 19, 11, 3,
|
||||
61, 53, 45, 37, 29, 21, 13, 5,
|
||||
63, 55, 47, 39, 31, 23, 15, 7,
|
||||
56, 48, 40, 32, 24, 16, 8, 0,
|
||||
58, 50, 42, 34, 26, 18, 10, 2,
|
||||
60, 52, 44, 36, 28, 20, 12, 4,
|
||||
62, 54, 46, 38, 30, 22, 14, 6
|
||||
]
|
||||
|
||||
# Expansion table for turning 32 bit blocks into 48 bits
|
||||
__expansion_table = [
|
||||
31, 0, 1, 2, 3, 4,
|
||||
3, 4, 5, 6, 7, 8,
|
||||
7, 8, 9, 10, 11, 12,
|
||||
11, 12, 13, 14, 15, 16,
|
||||
15, 16, 17, 18, 19, 20,
|
||||
19, 20, 21, 22, 23, 24,
|
||||
23, 24, 25, 26, 27, 28,
|
||||
27, 28, 29, 30, 31, 0
|
||||
]
|
||||
|
||||
# The (in)famous S-boxes
|
||||
__sbox = [
|
||||
# S1
|
||||
[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
||||
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
||||
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
||||
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
||||
|
||||
# S2
|
||||
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
||||
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
||||
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
||||
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
||||
|
||||
# S3
|
||||
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
||||
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
||||
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
||||
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
||||
|
||||
# S4
|
||||
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
||||
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
||||
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
||||
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
||||
|
||||
# S5
|
||||
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
||||
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
||||
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
||||
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
||||
|
||||
# S6
|
||||
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
||||
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
||||
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
||||
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
||||
|
||||
# S7
|
||||
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
||||
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
||||
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
||||
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
||||
|
||||
# S8
|
||||
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
||||
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
||||
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
||||
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
|
||||
]
|
||||
|
||||
|
||||
# 32-bit permutation function P used on the output of the S-boxes
|
||||
__p = [
|
||||
15, 6, 19, 20, 28, 11,
|
||||
27, 16, 0, 14, 22, 25,
|
||||
4, 17, 30, 9, 1, 7,
|
||||
23,13, 31, 26, 2, 8,
|
||||
18, 12, 29, 5, 21, 10,
|
||||
3, 24
|
||||
]
|
||||
|
||||
# final permutation IP^-1
|
||||
__fp = [
|
||||
39, 7, 47, 15, 55, 23, 63, 31,
|
||||
38, 6, 46, 14, 54, 22, 62, 30,
|
||||
37, 5, 45, 13, 53, 21, 61, 29,
|
||||
36, 4, 44, 12, 52, 20, 60, 28,
|
||||
35, 3, 43, 11, 51, 19, 59, 27,
|
||||
34, 2, 42, 10, 50, 18, 58, 26,
|
||||
33, 1, 41, 9, 49, 17, 57, 25,
|
||||
32, 0, 40, 8, 48, 16, 56, 24
|
||||
]
|
||||
|
||||
# Type of crypting being done
|
||||
ENCRYPT = 0x00
|
||||
DECRYPT = 0x01
|
||||
|
||||
# Initialisation
|
||||
def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
|
||||
# Sanity checking of arguments.
|
||||
if len(key) != 8:
|
||||
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
|
||||
_baseDes.__init__(self, mode, IV, pad, padmode)
|
||||
self.key_size = 8
|
||||
|
||||
self.L = []
|
||||
self.R = []
|
||||
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
|
||||
self.final = []
|
||||
|
||||
self.setKey(key)
|
||||
|
||||
def setKey(self, key):
|
||||
"""Will set the crypting key for this object. Must be 8 bytes."""
|
||||
_baseDes.setKey(self, key)
|
||||
self.__create_sub_keys()
|
||||
|
||||
def __String_to_BitList(self, data):
|
||||
"""Turn the string data, into a list of bits (1, 0)'s"""
|
||||
if _pythonMajorVersion < 3:
|
||||
# Turn the strings into integers. Python 3 uses a bytes
|
||||
# class, which already has this behaviour.
|
||||
data = [ord(c) for c in data]
|
||||
l = len(data) * 8
|
||||
result = [0] * l
|
||||
pos = 0
|
||||
for ch in data:
|
||||
i = 7
|
||||
while i >= 0:
|
||||
if ch & (1 << i) != 0:
|
||||
result[pos] = 1
|
||||
else:
|
||||
result[pos] = 0
|
||||
pos += 1
|
||||
i -= 1
|
||||
|
||||
return result
|
||||
|
||||
def __BitList_to_String(self, data):
|
||||
"""Turn the list of bits -> data, into a string"""
|
||||
result = []
|
||||
pos = 0
|
||||
c = 0
|
||||
while pos < len(data):
|
||||
c += data[pos] << (7 - (pos % 8))
|
||||
if (pos % 8) == 7:
|
||||
result.append(c)
|
||||
c = 0
|
||||
pos += 1
|
||||
|
||||
if _pythonMajorVersion < 3:
|
||||
return ''.join([ chr(c) for c in result ])
|
||||
else:
|
||||
return bytes(result)
|
||||
|
||||
def __permutate(self, table, block):
|
||||
"""Permutate this block with the specified table"""
|
||||
return list(map(lambda x: block[x], table))
|
||||
|
||||
# Transform the secret key, so that it is ready for data processing
|
||||
# Create the 16 subkeys, K[1] - K[16]
|
||||
def __create_sub_keys(self):
|
||||
"""Create the 16 subkeys K[1] to K[16] from the given key"""
|
||||
key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))
|
||||
i = 0
|
||||
# Split into Left and Right sections
|
||||
self.L = key[:28]
|
||||
self.R = key[28:]
|
||||
while i < 16:
|
||||
j = 0
|
||||
# Perform circular left shifts
|
||||
while j < des.__left_rotations[i]:
|
||||
self.L.append(self.L[0])
|
||||
del self.L[0]
|
||||
|
||||
self.R.append(self.R[0])
|
||||
del self.R[0]
|
||||
|
||||
j += 1
|
||||
|
||||
# Create one of the 16 subkeys through pc2 permutation
|
||||
self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R)
|
||||
|
||||
i += 1
|
||||
|
||||
# Main part of the encryption algorithm, the number cruncher :)
|
||||
def __des_crypt(self, block, crypt_type):
|
||||
"""Crypt the block of data through DES bit-manipulation"""
|
||||
block = self.__permutate(des.__ip, block)
|
||||
self.L = block[:32]
|
||||
self.R = block[32:]
|
||||
|
||||
# Encryption starts from Kn[1] through to Kn[16]
|
||||
if crypt_type == des.ENCRYPT:
|
||||
iteration = 0
|
||||
iteration_adjustment = 1
|
||||
# Decryption starts from Kn[16] down to Kn[1]
|
||||
else:
|
||||
iteration = 15
|
||||
iteration_adjustment = -1
|
||||
|
||||
i = 0
|
||||
while i < 16:
|
||||
# Make a copy of R[i-1], this will later become L[i]
|
||||
tempR = self.R[:]
|
||||
|
||||
# Permutate R[i - 1] to start creating R[i]
|
||||
self.R = self.__permutate(des.__expansion_table, self.R)
|
||||
|
||||
# Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here
|
||||
self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
|
||||
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
|
||||
# Optimization: Replaced below commented code with above
|
||||
#j = 0
|
||||
#B = []
|
||||
#while j < len(self.R):
|
||||
# self.R[j] = self.R[j] ^ self.Kn[iteration][j]
|
||||
# j += 1
|
||||
# if j % 6 == 0:
|
||||
# B.append(self.R[j-6:j])
|
||||
|
||||
# Permutate B[1] to B[8] using the S-Boxes
|
||||
j = 0
|
||||
Bn = [0] * 32
|
||||
pos = 0
|
||||
while j < 8:
|
||||
# Work out the offsets
|
||||
m = (B[j][0] << 1) + B[j][5]
|
||||
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
|
||||
|
||||
# Find the permutation value
|
||||
v = des.__sbox[j][(m << 4) + n]
|
||||
|
||||
# Turn value into bits, add it to result: Bn
|
||||
Bn[pos] = (v & 8) >> 3
|
||||
Bn[pos + 1] = (v & 4) >> 2
|
||||
Bn[pos + 2] = (v & 2) >> 1
|
||||
Bn[pos + 3] = v & 1
|
||||
|
||||
pos += 4
|
||||
j += 1
|
||||
|
||||
# Permutate the concatination of B[1] to B[8] (Bn)
|
||||
self.R = self.__permutate(des.__p, Bn)
|
||||
|
||||
# Xor with L[i - 1]
|
||||
self.R = list(map(lambda x, y: x ^ y, self.R, self.L))
|
||||
# Optimization: This now replaces the below commented code
|
||||
#j = 0
|
||||
#while j < len(self.R):
|
||||
# self.R[j] = self.R[j] ^ self.L[j]
|
||||
# j += 1
|
||||
|
||||
# L[i] becomes R[i - 1]
|
||||
self.L = tempR
|
||||
|
||||
i += 1
|
||||
iteration += iteration_adjustment
|
||||
|
||||
# Final permutation of R[16]L[16]
|
||||
self.final = self.__permutate(des.__fp, self.R + self.L)
|
||||
return self.final
|
||||
|
||||
|
||||
# Data to be encrypted/decrypted
|
||||
def crypt(self, data, crypt_type):
|
||||
"""Crypt the data in blocks, running it through des_crypt()"""
|
||||
|
||||
# Error check the data
|
||||
if not data:
|
||||
return ''
|
||||
if len(data) % self.block_size != 0:
|
||||
if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks
|
||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
|
||||
if not self.getPadding():
|
||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
|
||||
else:
|
||||
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
|
||||
# print "Len of data: %f" % (len(data) / self.block_size)
|
||||
|
||||
if self.getMode() == CBC:
|
||||
if self.getIV():
|
||||
iv = self.__String_to_BitList(self.getIV())
|
||||
else:
|
||||
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
|
||||
|
||||
# Split the data into blocks, crypting each one seperately
|
||||
i = 0
|
||||
dict = {}
|
||||
result = []
|
||||
#cached = 0
|
||||
#lines = 0
|
||||
while i < len(data):
|
||||
# Test code for caching encryption results
|
||||
#lines += 1
|
||||
#if dict.has_key(data[i:i+8]):
|
||||
#print "Cached result for: %s" % data[i:i+8]
|
||||
# cached += 1
|
||||
# result.append(dict[data[i:i+8]])
|
||||
# i += 8
|
||||
# continue
|
||||
|
||||
block = self.__String_to_BitList(data[i:i+8])
|
||||
|
||||
# Xor with IV if using CBC mode
|
||||
if self.getMode() == CBC:
|
||||
if crypt_type == des.ENCRYPT:
|
||||
block = list(map(lambda x, y: x ^ y, block, iv))
|
||||
#j = 0
|
||||
#while j < len(block):
|
||||
# block[j] = block[j] ^ iv[j]
|
||||
# j += 1
|
||||
|
||||
processed_block = self.__des_crypt(block, crypt_type)
|
||||
|
||||
if crypt_type == des.DECRYPT:
|
||||
processed_block = list(map(lambda x, y: x ^ y, processed_block, iv))
|
||||
#j = 0
|
||||
#while j < len(processed_block):
|
||||
# processed_block[j] = processed_block[j] ^ iv[j]
|
||||
# j += 1
|
||||
iv = block
|
||||
else:
|
||||
iv = processed_block
|
||||
else:
|
||||
processed_block = self.__des_crypt(block, crypt_type)
|
||||
|
||||
|
||||
# Add the resulting crypted block to our list
|
||||
#d = self.__BitList_to_String(processed_block)
|
||||
#result.append(d)
|
||||
result.append(self.__BitList_to_String(processed_block))
|
||||
#dict[data[i:i+8]] = d
|
||||
i += 8
|
||||
|
||||
# print "Lines: %d, cached: %d" % (lines, cached)
|
||||
|
||||
# Return the full crypted string
|
||||
if _pythonMajorVersion < 3:
|
||||
return ''.join(result)
|
||||
else:
|
||||
return bytes.fromhex('').join(result)
|
||||
|
||||
def encrypt(self, data, pad=None, padmode=None):
|
||||
"""encrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : Bytes to be encrypted
|
||||
pad : Optional argument for encryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be encrypted
|
||||
with the already specified key. Data does not have to be a
|
||||
multiple of 8 bytes if the padding character is supplied, or
|
||||
the padmode is set to PAD_PKCS5, as bytes will then added to
|
||||
ensure the be padded data is a multiple of 8 bytes.
|
||||
"""
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
data = self._padData(data, pad, padmode)
|
||||
return self.crypt(data, des.ENCRYPT)
|
||||
|
||||
def decrypt(self, data, pad=None, padmode=None):
|
||||
"""decrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : Bytes to be encrypted
|
||||
pad : Optional argument for decryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be decrypted
|
||||
with the already specified key. In PAD_NORMAL mode, if the
|
||||
optional padding character is supplied, then the un-encrypted
|
||||
data will have the padding characters removed from the end of
|
||||
the bytes. This pad removal only occurs on the last 8 bytes of
|
||||
the data (last data block). In PAD_PKCS5 mode, the special
|
||||
padding end markers will be removed from the data after decrypting.
|
||||
"""
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
data = self.crypt(data, des.DECRYPT)
|
||||
return self._unpadData(data, pad, padmode)
|
||||
|
||||
|
||||
|
||||
#############################################################################
|
||||
# Triple DES #
|
||||
#############################################################################
|
||||
class triple_des(_baseDes):
|
||||
"""Triple DES encryption/decrytpion class
|
||||
|
||||
This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or
|
||||
the DES-EDE2 (when a 16 byte key is supplied) encryption methods.
|
||||
Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
|
||||
|
||||
pyDes.des(key, [mode], [IV])
|
||||
|
||||
key -> Bytes containing the encryption key, must be either 16 or
|
||||
24 bytes long
|
||||
mode -> Optional argument for encryption type, can be either pyDes.ECB
|
||||
(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
|
||||
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
|
||||
Must be 8 bytes in length.
|
||||
pad -> Optional argument, set the pad character (PAD_NORMAL) to use
|
||||
during all encrypt/decrpt operations done with this instance.
|
||||
padmode -> Optional argument, set the padding mode (PAD_NORMAL or
|
||||
PAD_PKCS5) to use during all encrypt/decrpt operations done
|
||||
with this instance.
|
||||
"""
|
||||
def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
|
||||
_baseDes.__init__(self, mode, IV, pad, padmode)
|
||||
self.setKey(key)
|
||||
|
||||
def setKey(self, key):
|
||||
"""Will set the crypting key for this object. Either 16 or 24 bytes long."""
|
||||
self.key_size = 24 # Use DES-EDE3 mode
|
||||
if len(key) != self.key_size:
|
||||
if len(key) == 16: # Use DES-EDE2 mode
|
||||
self.key_size = 16
|
||||
else:
|
||||
raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long")
|
||||
if self.getMode() == CBC:
|
||||
if not self.getIV():
|
||||
# Use the first 8 bytes of the key
|
||||
self._iv = key[:self.block_size]
|
||||
if len(self.getIV()) != self.block_size:
|
||||
raise ValueError("Invalid IV, must be 8 bytes in length")
|
||||
self.__key1 = des(key[:8], self._mode, self._iv,
|
||||
self._padding, self._padmode)
|
||||
self.__key2 = des(key[8:16], self._mode, self._iv,
|
||||
self._padding, self._padmode)
|
||||
if self.key_size == 16:
|
||||
self.__key3 = self.__key1
|
||||
else:
|
||||
self.__key3 = des(key[16:], self._mode, self._iv,
|
||||
self._padding, self._padmode)
|
||||
_baseDes.setKey(self, key)
|
||||
|
||||
# Override setter methods to work on all 3 keys.
|
||||
|
||||
def setMode(self, mode):
|
||||
"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
|
||||
_baseDes.setMode(self, mode)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setMode(mode)
|
||||
|
||||
def setPadding(self, pad):
|
||||
"""setPadding() -> bytes of length 1. Padding character."""
|
||||
_baseDes.setPadding(self, pad)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setPadding(pad)
|
||||
|
||||
def setPadMode(self, mode):
|
||||
"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
|
||||
_baseDes.setPadMode(self, mode)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setPadMode(mode)
|
||||
|
||||
def setIV(self, IV):
|
||||
"""Will set the Initial Value, used in conjunction with CBC mode"""
|
||||
_baseDes.setIV(self, IV)
|
||||
for key in (self.__key1, self.__key2, self.__key3):
|
||||
key.setIV(IV)
|
||||
|
||||
def encrypt(self, data, pad=None, padmode=None):
|
||||
"""encrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : bytes to be encrypted
|
||||
pad : Optional argument for encryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be encrypted
|
||||
with the already specified key. Data does not have to be a
|
||||
multiple of 8 bytes if the padding character is supplied, or
|
||||
the padmode is set to PAD_PKCS5, as bytes will then added to
|
||||
ensure the be padded data is a multiple of 8 bytes.
|
||||
"""
|
||||
ENCRYPT = des.ENCRYPT
|
||||
DECRYPT = des.DECRYPT
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
# Pad the data accordingly.
|
||||
data = self._padData(data, pad, padmode)
|
||||
if self.getMode() == CBC:
|
||||
self.__key1.setIV(self.getIV())
|
||||
self.__key2.setIV(self.getIV())
|
||||
self.__key3.setIV(self.getIV())
|
||||
i = 0
|
||||
result = []
|
||||
while i < len(data):
|
||||
block = self.__key1.crypt(data[i:i+8], ENCRYPT)
|
||||
block = self.__key2.crypt(block, DECRYPT)
|
||||
block = self.__key3.crypt(block, ENCRYPT)
|
||||
self.__key1.setIV(block)
|
||||
self.__key2.setIV(block)
|
||||
self.__key3.setIV(block)
|
||||
result.append(block)
|
||||
i += 8
|
||||
if _pythonMajorVersion < 3:
|
||||
return ''.join(result)
|
||||
else:
|
||||
return bytes.fromhex('').join(result)
|
||||
else:
|
||||
data = self.__key1.crypt(data, ENCRYPT)
|
||||
data = self.__key2.crypt(data, DECRYPT)
|
||||
return self.__key3.crypt(data, ENCRYPT)
|
||||
|
||||
def decrypt(self, data, pad=None, padmode=None):
|
||||
"""decrypt(data, [pad], [padmode]) -> bytes
|
||||
|
||||
data : bytes to be encrypted
|
||||
pad : Optional argument for decryption padding. Must only be one byte
|
||||
padmode : Optional argument for overriding the padding mode.
|
||||
|
||||
The data must be a multiple of 8 bytes and will be decrypted
|
||||
with the already specified key. In PAD_NORMAL mode, if the
|
||||
optional padding character is supplied, then the un-encrypted
|
||||
data will have the padding characters removed from the end of
|
||||
the bytes. This pad removal only occurs on the last 8 bytes of
|
||||
the data (last data block). In PAD_PKCS5 mode, the special
|
||||
padding end markers will be removed from the data after
|
||||
decrypting, no pad character is required for PAD_PKCS5.
|
||||
"""
|
||||
ENCRYPT = des.ENCRYPT
|
||||
DECRYPT = des.DECRYPT
|
||||
data = self._guardAgainstUnicode(data)
|
||||
if pad is not None:
|
||||
pad = self._guardAgainstUnicode(pad)
|
||||
if self.getMode() == CBC:
|
||||
self.__key1.setIV(self.getIV())
|
||||
self.__key2.setIV(self.getIV())
|
||||
self.__key3.setIV(self.getIV())
|
||||
i = 0
|
||||
result = []
|
||||
while i < len(data):
|
||||
iv = data[i:i+8]
|
||||
block = self.__key3.crypt(iv, DECRYPT)
|
||||
block = self.__key2.crypt(block, ENCRYPT)
|
||||
block = self.__key1.crypt(block, DECRYPT)
|
||||
self.__key1.setIV(iv)
|
||||
self.__key2.setIV(iv)
|
||||
self.__key3.setIV(iv)
|
||||
result.append(block)
|
||||
i += 8
|
||||
if _pythonMajorVersion < 3:
|
||||
data = ''.join(result)
|
||||
else:
|
||||
data = bytes.fromhex('').join(result)
|
||||
else:
|
||||
data = self.__key3.crypt(data, DECRYPT)
|
||||
data = self.__key2.crypt(data, ENCRYPT)
|
||||
data = self.__key1.crypt(data, DECRYPT)
|
||||
return self._unpadData(data, pad, padmode)
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/python
|
||||
__author__ = 'Thomas Dixon'
|
||||
__license__ = 'MIT'
|
||||
|
||||
import copy, struct, sys
|
||||
|
||||
digest_size = 32
|
||||
blocksize = 1
|
||||
|
||||
def new(m=None):
|
||||
return sha256(m)
|
||||
|
||||
class sha256(object):
|
||||
_k = (0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2)
|
||||
_h = (0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
|
||||
_output_size = 8
|
||||
|
||||
blocksize = 1
|
||||
block_size = 64
|
||||
digest_size = 32
|
||||
|
||||
def __init__(self, m=None):
|
||||
self._buffer = ''
|
||||
self._counter = 0
|
||||
|
||||
if m is not None:
|
||||
if type(m) is not str:
|
||||
raise TypeError, '%s() argument 1 must be string, not %s' % (self.__class__.__name__, type(m).__name__)
|
||||
self.update(m)
|
||||
|
||||
def _rotr(self, x, y):
|
||||
return ((x >> y) | (x << (32-y))) & 0xFFFFFFFF
|
||||
|
||||
def _sha256_process(self, c):
|
||||
w = [0]*64
|
||||
w[0:15] = struct.unpack('!16L', c)
|
||||
|
||||
for i in range(16, 64):
|
||||
s0 = self._rotr(w[i-15], 7) ^ self._rotr(w[i-15], 18) ^ (w[i-15] >> 3)
|
||||
s1 = self._rotr(w[i-2], 17) ^ self._rotr(w[i-2], 19) ^ (w[i-2] >> 10)
|
||||
w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFF
|
||||
|
||||
a,b,c,d,e,f,g,h = self._h
|
||||
|
||||
for i in range(64):
|
||||
s0 = self._rotr(a, 2) ^ self._rotr(a, 13) ^ self._rotr(a, 22)
|
||||
maj = (a & b) ^ (a & c) ^ (b & c)
|
||||
t2 = s0 + maj
|
||||
s1 = self._rotr(e, 6) ^ self._rotr(e, 11) ^ self._rotr(e, 25)
|
||||
ch = (e & f) ^ ((~e) & g)
|
||||
t1 = h + s1 + ch + self._k[i] + w[i]
|
||||
|
||||
h = g
|
||||
g = f
|
||||
f = e
|
||||
e = (d + t1) & 0xFFFFFFFF
|
||||
d = c
|
||||
c = b
|
||||
b = a
|
||||
a = (t1 + t2) & 0xFFFFFFFF
|
||||
|
||||
self._h = [(x+y) & 0xFFFFFFFF for x,y in zip(self._h, [a,b,c,d,e,f,g,h])]
|
||||
|
||||
def update(self, m):
|
||||
if not m:
|
||||
return
|
||||
if type(m) is not str:
|
||||
raise TypeError, '%s() argument 1 must be string, not %s' % (sys._getframe().f_code.co_name, type(m).__name__)
|
||||
|
||||
self._buffer += m
|
||||
self._counter += len(m)
|
||||
|
||||
while len(self._buffer) >= 64:
|
||||
self._sha256_process(self._buffer[:64])
|
||||
self._buffer = self._buffer[64:]
|
||||
|
||||
def digest(self):
|
||||
mdi = self._counter & 0x3F
|
||||
length = struct.pack('!Q', self._counter<<3)
|
||||
|
||||
if mdi < 56:
|
||||
padlen = 55-mdi
|
||||
else:
|
||||
padlen = 119-mdi
|
||||
|
||||
r = self.copy()
|
||||
r.update('\x80'+('\x00'*padlen)+length)
|
||||
return ''.join([struct.pack('!L', i) for i in r._h[:self._output_size]])
|
||||
|
||||
def hexdigest(self):
|
||||
return self.digest().encode('hex')
|
||||
|
||||
def copy(self):
|
||||
return copy.deepcopy(self)
|
||||
Reference in New Issue
Block a user