%PDF- %PDF-
Direktori : /usr/lib/python2.7/site-packages/salt/utils/ |
Current File : //usr/lib/python2.7/site-packages/salt/utils/smb.py |
# -*- coding: utf-8 -*- ''' Utility functions for SMB connections :depends: impacket ''' from __future__ import absolute_import, print_function, unicode_literals import socket import uuid # Import python libs import salt.utils.files import salt.utils.stringutils import salt.utils.versions import logging log = logging.getLogger(__name__) from salt.exceptions import MissingSmb try: import impacket.smbconnection from impacket.smbconnection import SessionError as smbSessionError from impacket.smb3 import SessionError as smb3SessionError HAS_IMPACKET = True except ImportError: HAS_IMPACKET = False try: from smbprotocol.connection import Connection from smbprotocol.session import Session from smbprotocol.tree import TreeConnect from smbprotocol.open import ( Open, ImpersonationLevel, FilePipePrinterAccessMask, FileAttributes, CreateDisposition, CreateOptions, ShareAccess, DirectoryAccessMask, FileInformationClass ) from smbprotocol.create_contexts import ( CreateContextName, SMB2CreateContextRequest, SMB2CreateQueryMaximalAccessRequest ) from smbprotocol.security_descriptor import ( AccessAllowedAce, AccessMask, AclPacket, SDControl, SIDPacket, SMB2CreateSDBuffer ) logging.getLogger('smbprotocol').setLevel(logging.WARNING) HAS_SMBPROTOCOL = True except ImportError: HAS_SMBPROTOCOL = False class SMBProto(object): def __init__(self, server, username, password, port=445): connection_id = uuid.uuid4() addr = socket.gethostbyname(server) self.server = server connection = Connection(connection_id, addr, port, require_signing=True) self.session = Session(connection, username, password, require_encryption=False) def connect(self): self.connection.connect() self.session.connect() def close(self): self.session.connection.disconnect(True) @property def connection(self): return self.session.connection def tree_connect(self, share): if share.endswith('$'): share = r'\\{}\{}'.format(self.server, share) tree = TreeConnect(self.session, share) tree.connect() return tree @staticmethod def normalize_filename(file): return file.lstrip('\\') @classmethod def open_file(cls, tree, file): file = cls.normalize_filename(file) # ensure file is created, get maximal access, and set everybody read access max_req = SMB2CreateContextRequest() max_req['buffer_name'] = CreateContextName.SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST max_req['buffer_data'] = SMB2CreateQueryMaximalAccessRequest() # create security buffer that sets the ACL for everyone to have read access everyone_sid = SIDPacket() everyone_sid.from_string("S-1-1-0") ace = AccessAllowedAce() ace['mask'] = AccessMask.GENERIC_ALL ace['sid'] = everyone_sid acl = AclPacket() acl['aces'] = [ace] sec_desc = SMB2CreateSDBuffer() sec_desc['control'].set_flag(SDControl.SELF_RELATIVE) sec_desc.set_dacl(acl) sd_buffer = SMB2CreateContextRequest() sd_buffer['buffer_name'] = CreateContextName.SMB2_CREATE_SD_BUFFER sd_buffer['buffer_data'] = sec_desc create_contexts = [ max_req, sd_buffer ] file_open = Open(tree, file) open_info = file_open.create( ImpersonationLevel.Impersonation, FilePipePrinterAccessMask.GENERIC_READ | FilePipePrinterAccessMask.GENERIC_WRITE, FileAttributes.FILE_ATTRIBUTE_NORMAL, ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE, CreateDisposition.FILE_OVERWRITE_IF, CreateOptions.FILE_NON_DIRECTORY_FILE, ) return file_open @staticmethod def open_directory(tree, name, create=False): # ensure directory is created dir_open = Open(tree, name) if create: dir_open.create( ImpersonationLevel.Impersonation, DirectoryAccessMask.GENERIC_READ | DirectoryAccessMask.GENERIC_WRITE, FileAttributes.FILE_ATTRIBUTE_DIRECTORY, ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE, CreateDisposition.FILE_OPEN_IF, CreateOptions.FILE_DIRECTORY_FILE ) return dir_open class StrHandle(object): ''' Fakes a file handle, so that raw strings may be uploaded instead of having to write files first. Used by put_str() ''' def __init__(self, content): ''' Init ''' self.content = content self.finished = False def string(self, writesize=None): ''' Looks like a file handle ''' if not self.finished: self.finished = True return self.content return '' def _get_conn_impacket(host=None, username=None, password=None, client_name=None, port=445): conn = impacket.smbconnection.SMBConnection( remoteName=host, remoteHost=host, myName=client_name, ) conn.login(user=username, password=password) return conn def _get_conn_smbprotocol(host='', username='', password='', client_name='', port=445): conn = SMBProto(host, username, password, port) conn.connect() return conn def get_conn(host='', username=None, password=None, port=445): ''' Get an SMB connection ''' if HAS_IMPACKET and not HAS_SMBPROTOCOL: salt.utils.versions.warn_until( 'Sodium', 'Support of impacket has been depricated and will be ' 'removed in Sodium. Please install smbprotocol instead.' ) if HAS_SMBPROTOCOL: log.info('Get connection smbprotocol') return _get_conn_smbprotocol(host, username, password, port=port) elif HAS_IMPACKET: log.info('Get connection impacket') return _get_conn_impacket(host, username, password, port=port) return False def _mkdirs_impacket(path, share='C$', conn=None, host=None, username=None, password=None): ''' Recursively create a directory structure on an SMB share Paths should be passed in with forward-slash delimiters, and should not start with a forward-slash. ''' if conn is None: conn = get_conn(host, username, password) if conn is False: return False comps = path.split('/') pos = 1 for comp in comps: cwd = '\\'.join(comps[0:pos]) try: conn.listPath(share, cwd) except (smbSessionError, smb3SessionError): log.exception('Encountered error running conn.listPath') conn.createDirectory(share, cwd) pos += 1 def _mkdirs_smbprotocol(path, share='C$', conn=None, host=None, username=None, password=None): if conn is None: conn = get_conn(host, username, password) if conn is False: return False tree = conn.tree_connect(share) comps = path.split('/') pos = 1 for comp in comps: cwd = '\\'.join(comps[0:pos]) dir_open = conn.open_directory(tree, cwd, create=True) compound_messages = [ dir_open.query_directory("*", FileInformationClass.FILE_NAMES_INFORMATION, send=False), dir_open.close(False, send=False) ] requests = conn.session.connection.send_compound([x[0] for x in compound_messages], conn.session.session_id, tree.tree_connect_id) for i, request in enumerate(requests): response = compound_messages[i][1](request) pos += 1 def mkdirs(path, share='C$', conn=None, host=None, username=None, password=None): if HAS_SMBPROTOCOL: return _mkdirs_smbprotocol(path, share, conn=conn, host=host, username=username, password=password) elif HAS_IMPACKET: return _mkdirs_impacket(path, share, conn=conn, host=host, username=username, password=password) raise MissingSmb("SMB library required (impacket or smbprotocol)") def _put_str_impacket(content, path, share='C$', conn=None, host=None, username=None, password=None): if conn is None: conn = get_conn(host, username, password) if conn is False: return False fh_ = StrHandle(content) conn.putFile(share, path, fh_.string) def _put_str_smbprotocol( content, path, share='C$', conn=None, host=None, username=None, password=None): if conn is None: conn = get_conn(host, username, password) if conn is False: return False tree = conn.tree_connect(share) try: file_open = conn.open_file(tree, path) file_open.write(salt.utils.stringutils.to_bytes(content), 0) finally: file_open.close() def put_str(content, path, share='C$', conn=None, host=None, username=None, password=None): ''' Wrapper around impacket.smbconnection.putFile() that allows a string to be uploaded, without first writing it as a local file ''' if HAS_SMBPROTOCOL: return _put_str_smbprotocol( content, path, share, conn=conn, host=host, username=username, password=password ) elif HAS_IMPACKET: return _put_str_impacket( content, path, share, conn=conn, host=host, username=username, password=password ) raise MissingSmb("SMB library required (impacket or smbprotocol)") def _put_file_impacket(local_path, path, share='C$', conn=None, host=None, username=None, password=None): ''' Wrapper around impacket.smbconnection.putFile() that allows a file to be uploaded Example usage: import salt.utils.smb smb_conn = salt.utils.smb.get_conn('10.0.0.45', 'vagrant', 'vagrant') salt.utils.smb.put_file('/root/test.pdf', 'temp\\myfiles\\test1.pdf', conn=smb_conn) ''' if conn is None: conn = get_conn(host, username, password) if conn is False: return False if hasattr(local_path, 'read'): conn.putFile(share, path, local_path) return with salt.utils.files.fopen(local_path, 'rb') as fh_: conn.putFile(share, path, fh_.read) def _put_file_smbprotocol( local_path, path, share='C$', conn=None, host=None, username=None, password=None, chunk_size=1024 * 1024): if conn is None: conn = get_conn(host, username, password) if conn is False: return False tree = conn.tree_connect(share) file_open = conn.open_file(tree, path) with salt.utils.files.fopen(local_path, 'rb') as fh_: try: position = 0 while True: chunk = fh_.read(chunk_size) if not chunk: break file_open.write(chunk, position) position += len(chunk) finally: file_open.close(False) def put_file(local_path, path, share='C$', conn=None, host=None, username=None, password=None): ''' Wrapper around impacket.smbconnection.putFile() that allows a file to be uploaded Example usage: import salt.utils.smb smb_conn = salt.utils.smb.get_conn('10.0.0.45', 'vagrant', 'vagrant') salt.utils.smb.put_file('/root/test.pdf', 'temp\\myfiles\\test1.pdf', conn=smb_conn) ''' if HAS_SMBPROTOCOL: return _put_file_smbprotocol( local_path, path, share, conn=conn, host=host, username=username, password=password ) elif HAS_IMPACKET: return _put_file_impacket( local_path, path, share, conn=conn, host=host, username=username, password=password ) raise MissingSmb("SMB library required (impacket or smbprotocol)") def _delete_file_impacket(path, share='C$', conn=None, host=None, username=None, password=None): if conn is None: conn = get_conn(host, username, password) if conn is False: return False conn.deleteFile(share, path) def _delete_file_smbprotocol(path, share='C$', conn=None, host=None, username=None, password=None): if conn is None: conn = get_conn(host, username, password) if conn is False: return False tree = conn.tree_connect(share) file_open = Open(tree, path) delete_msgs = [ file_open.create( ImpersonationLevel.Impersonation, FilePipePrinterAccessMask.GENERIC_READ | FilePipePrinterAccessMask.DELETE, FileAttributes.FILE_ATTRIBUTE_NORMAL, ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_DELETE_ON_CLOSE, send=False ), file_open.close(False, send=False) ] requests = conn.connection.send_compound([x[0] for x in delete_msgs], conn.session.session_id, tree.tree_connect_id, related=True) responses = [] for i, request in enumerate(requests): # A SMBResponseException will be raised if something went wrong response = delete_msgs[i][1](request) responses.append(response) def delete_file(path, share='C$', conn=None, host=None, username=None, password=None): if HAS_SMBPROTOCOL: return _delete_file_smbprotocol(path, share, conn=conn, host=host, username=username, password=password) elif HAS_IMPACKET: return _delete_file_impacket(path, share, conn=conn, host=host, username=username, password=password) raise MissingSmb("SMB library required (impacket or smbprotocol)") def _delete_directory_impacket(path, share='C$', conn=None, host=None, username=None, password=None): if conn is None: conn = get_conn(host, username, password) if conn is False: return False conn.deleteDirectory(share, path) def _delete_directory_smbprotocol(path, share='C$', conn=None, host=None, username=None, password=None): if conn is None: conn = get_conn(host, username, password) if conn is False: return False log.debug("_delete_directory_smbprotocol - share: %s, path: %s", share, path) tree = conn.tree_connect(share) dir_open = Open(tree, path) delete_msgs = [ dir_open.create( ImpersonationLevel.Impersonation, DirectoryAccessMask.DELETE, FileAttributes.FILE_ATTRIBUTE_DIRECTORY, 0, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE | CreateOptions.FILE_DELETE_ON_CLOSE, send=False ), dir_open.close(False, send=False) ] delete_reqs = conn.connection.send_compound([x[0] for x in delete_msgs], sid=conn.session.session_id, tid=tree.tree_connect_id, related=True) for i, request in enumerate(delete_reqs): # A SMBResponseException will be raised if something went wrong response = delete_msgs[i][1](request) def delete_directory(path, share='C$', conn=None, host=None, username=None, password=None): if HAS_SMBPROTOCOL: return _delete_directory_smbprotocol( path, share, conn=conn, host=host, username=username, password=password ) elif HAS_IMPACKET: return _delete_directory_impacket( path, share, conn=conn, host=host, username=username, password=password ) raise MissingSmb("SMB library required (impacket or smbprotocol)")