# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Copyright (C) 2001 Michael Teo <michaelteo@bigfoot.com>
# smb.py - SMB/CIFS library
#
# This software is provided 'as-is', without any express or implied warranty. 
# In no event will the author be held liable for any damages arising from the 
# use of this software.
#
# Permission is granted to anyone to use this software for any purpose, 
# including commercial applications, and to alter it and redistribute it 
# freely, subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not 
#    claim that you wrote the original software. If you use this software 
#    in a product, an acknowledgment in the product documentation would be
#    appreciated but is not required.
#
# 2. Altered source versions must be plainly marked as such, and must not be 
#    misrepresented as being the original software.
#
# 3. This notice cannot be removed or altered from any source distribution.
#
# Altered source done by Alberto Solino (@agsolino)

# Todo:
# [ ] Try [SMB]transport fragmentation using Transact requests
# [ ] Try other methods of doing write (write_raw, transact2, write, write_and_unlock, write_and_close, write_mpx)
# [-] Try replacements for SMB_COM_NT_CREATE_ANDX  (CREATE, T_TRANSACT_CREATE, OPEN_ANDX works
# [x] Fix forceWriteAndx, which needs to send a RecvRequest, because recv() will not send it
# [x] Fix Recv() when using RecvAndx and the answer comes splet in several packets
# [ ] Try [SMB]transport fragmentation with overlapping segments
# [ ] Try [SMB]transport fragmentation with out of order segments
# [x] Do chained AndX requests
# [ ] Transform the rest of the calls to structure
# [X] Implement TRANS/TRANS2 reassembly for list_path
from __future__ import division
from __future__ import print_function
import os
import socket
from binascii import a2b_hex
import datetime
from struct import pack, unpack
from contextlib import contextmanager
from pyasn1.type.univ import noValue

from impacket import nmb, ntlm, nt_errors, LOG
from impacket.structure import Structure
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech, SPNEGO_NegTokenResp, ASN1_OID, asn1encode, ASN1_AID
from impacket.krb5.gssapi import KRB5_AP_REQ

# For signing
import hashlib

unicode_support = 0
unicode_convert = 1

# Dialect for SMB1
SMB_DIALECT = 'NT LM 0.12'

# Shared Device Type
SHARED_DISK                      = 0x00
SHARED_DISK_HIDDEN               = 0x80000000
SHARED_PRINT_QUEUE               = 0x01
SHARED_DEVICE                    = 0x02
SHARED_IPC                       = 0x03

# Extended attributes mask
ATTR_ARCHIVE                     = 0x020
ATTR_COMPRESSED                  = 0x800
ATTR_NORMAL                      = 0x080
ATTR_HIDDEN                      = 0x002
ATTR_READONLY                    = 0x001
ATTR_TEMPORARY                   = 0x100
ATTR_DIRECTORY                   = 0x010
ATTR_SYSTEM                      = 0x004

# Service Type
SERVICE_DISK                     = 'A:'
SERVICE_PRINTER                  = 'LPT1:'
SERVICE_IPC                      = 'IPC'
SERVICE_COMM                     = 'COMM'
SERVICE_ANY                      = '?????'

# Server Type (Can be used to mask with SMBMachine.get_type() or SMBDomain.get_type())
SV_TYPE_WORKSTATION              = 0x00000001
SV_TYPE_SERVER                   = 0x00000002
SV_TYPE_SQLSERVER                = 0x00000004
SV_TYPE_DOMAIN_CTRL              = 0x00000008
SV_TYPE_DOMAIN_BAKCTRL           = 0x00000010
SV_TYPE_TIME_SOURCE              = 0x00000020
SV_TYPE_AFP                      = 0x00000040
SV_TYPE_NOVELL                   = 0x00000080
SV_TYPE_DOMAIN_MEMBER            = 0x00000100
SV_TYPE_PRINTQ_SERVER            = 0x00000200
SV_TYPE_DIALIN_SERVER            = 0x00000400
SV_TYPE_XENIX_SERVER             = 0x00000800
SV_TYPE_NT                       = 0x00001000
SV_TYPE_WFW                      = 0x00002000
SV_TYPE_SERVER_NT                = 0x00004000
SV_TYPE_POTENTIAL_BROWSER        = 0x00010000
SV_TYPE_BACKUP_BROWSER           = 0x00020000
SV_TYPE_MASTER_BROWSER           = 0x00040000
SV_TYPE_DOMAIN_MASTER            = 0x00080000
SV_TYPE_LOCAL_LIST_ONLY          = 0x40000000
SV_TYPE_DOMAIN_ENUM              = 0x80000000

# Options values for SMB.stor_file and SMB.retr_file
SMB_O_CREAT                      = 0x10   # Create the file if file does not exists. Otherwise, operation fails.
SMB_O_EXCL                       = 0x00   # When used with SMB_O_CREAT, operation fails if file exists. Cannot be used with SMB_O_OPEN.
SMB_O_OPEN                       = 0x01   # Open the file if the file exists
SMB_O_TRUNC                      = 0x02   # Truncate the file if the file exists

# Share Access Mode
SMB_SHARE_COMPAT                 = 0x00
SMB_SHARE_DENY_EXCL              = 0x10
SMB_SHARE_DENY_WRITE             = 0x20
SMB_SHARE_DENY_READEXEC          = 0x30
SMB_SHARE_DENY_NONE              = 0x40
SMB_ACCESS_READ                  = 0x00
SMB_ACCESS_WRITE                 = 0x01
SMB_ACCESS_READWRITE             = 0x02
SMB_ACCESS_EXEC                  = 0x03

TRANS_DISCONNECT_TID             = 1
TRANS_NO_RESPONSE                = 2

STATUS_SUCCESS                   = 0x00000000
STATUS_LOGON_FAILURE             = 0xC000006D
STATUS_LOGON_TYPE_NOT_GRANTED    = 0xC000015B
MAX_TFRAG_SIZE                   = 5840
EVASION_NONE                     = 0
EVASION_LOW                      = 1
EVASION_HIGH                     = 2
EVASION_MAX                      = 3
RPC_X_BAD_STUB_DATA              = 0x6F7

# SMB_FILE_ATTRIBUTES

SMB_FILE_ATTRIBUTE_NORMAL        = 0x0000
SMB_FILE_ATTRIBUTE_READONLY      = 0x0001
SMB_FILE_ATTRIBUTE_HIDDEN        = 0x0002
SMB_FILE_ATTRIBUTE_SYSTEM        = 0x0004
SMB_FILE_ATTRIBUTE_VOLUME        = 0x0008
SMB_FILE_ATTRIBUTE_DIRECTORY     = 0x0010
SMB_FILE_ATTRIBUTE_ARCHIVE       = 0x0020
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

# Session SetupAndX Action flags
SMB_SETUP_GUEST                  = 0x01
SMB_SETUP_USE_LANMAN_KEY         = 0x02

# QUERY_INFORMATION levels
SMB_INFO_ALLOCATION              = 0x0001
SMB_INFO_VOLUME                  = 0x0002
FILE_FS_SIZE_INFORMATION         = 0x0003
SMB_QUERY_FS_VOLUME_INFO         = 0x0102
SMB_QUERY_FS_SIZE_INFO           = 0x0103
SMB_QUERY_FILE_EA_INFO           = 0x0103
SMB_QUERY_FS_DEVICE_INFO         = 0x0104
SMB_QUERY_FS_ATTRIBUTE_INFO      = 0x0105
SMB_QUERY_FILE_BASIC_INFO        = 0x0101
SMB_QUERY_FILE_STANDARD_INFO     = 0x0102
SMB_QUERY_FILE_ALL_INFO          = 0x0107
FILE_FS_FULL_SIZE_INFORMATION    = 0x03EF

# SET_INFORMATION levels
SMB_SET_FILE_DISPOSITION_INFO    = 0x0102
SMB_SET_FILE_BASIC_INFO          = 0x0101
SMB_SET_FILE_END_OF_FILE_INFO    = 0x0104


# File System Attributes
FILE_CASE_SENSITIVE_SEARCH       = 0x00000001
FILE_CASE_PRESERVED_NAMES        = 0x00000002
FILE_UNICODE_ON_DISK             = 0x00000004
FILE_PERSISTENT_ACLS             = 0x00000008
FILE_FILE_COMPRESSION            = 0x00000010
FILE_VOLUME_IS_COMPRESSED        = 0x00008000

# FIND_FIRST2 flags and levels
SMB_FIND_CLOSE_AFTER_REQUEST     = 0x0001
SMB_FIND_CLOSE_AT_EOS            = 0x0002
SMB_FIND_RETURN_RESUME_KEYS      = 0x0004
SMB_FIND_CONTINUE_FROM_LAST      = 0x0008
SMB_FIND_WITH_BACKUP_INTENT      = 0x0010

FILE_DIRECTORY_FILE              = 0x00000001
FILE_DELETE_ON_CLOSE             = 0x00001000
FILE_NON_DIRECTORY_FILE          = 0x00000040

SMB_FIND_INFO_STANDARD           = 0x0001
SMB_FIND_FILE_DIRECTORY_INFO     = 0x0101
SMB_FIND_FILE_FULL_DIRECTORY_INFO= 0x0102
SMB_FIND_FILE_NAMES_INFO         = 0x0103
SMB_FIND_FILE_BOTH_DIRECTORY_INFO= 0x0104
SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO = 0x105
SMB_FIND_FILE_ID_BOTH_DIRECTORY_INFO = 0x106


# DesiredAccess flags
FILE_READ_DATA                   = 0x00000001
FILE_WRITE_DATA                  = 0x00000002
FILE_APPEND_DATA                 = 0x00000004
FILE_EXECUTE                     = 0x00000020
MAXIMUM_ALLOWED                  = 0x02000000
GENERIC_ALL                      = 0x10000000
GENERIC_EXECUTE                  = 0x20000000
GENERIC_WRITE                    = 0x40000000
GENERIC_READ                     = 0x80000000

# ShareAccess flags
FILE_SHARE_NONE                  = 0x00000000
FILE_SHARE_READ                  = 0x00000001
FILE_SHARE_WRITE                 = 0x00000002
FILE_SHARE_DELETE                = 0x00000004

# CreateDisposition flags
FILE_SUPERSEDE                  = 0x00000000
FILE_OPEN                       = 0x00000001
FILE_CREATE                     = 0x00000002
FILE_OPEN_IF                    = 0x00000003
FILE_OVERWRITE                  = 0x00000004
FILE_OVERWRITE_IF               = 0x00000005

def strerror(errclass, errcode):
    if errclass == 0x01:
        return 'OS error', ERRDOS.get(errcode, 'Unknown error')
    elif errclass == 0x02:
        return 'Server error', ERRSRV.get(errcode, 'Unknown error')
    elif errclass == 0x03:
        return 'Hardware error', ERRHRD.get(errcode, 'Unknown error')
    # This is not a standard error class for SMB
    #elif errclass == 0x80:
    #    return 'Browse error', ERRBROWSE.get(errcode, 'Unknown error')
    elif errclass == 0xff:
        return 'Bad command', 'Bad command. Please file bug report'
    else:
        return 'Unknown error', 'Unknown error'

# Raised when an error has occurred during a session
class SessionError(Exception):
    # SMB X/Open error codes for the ERRDOS error class
    ERRsuccess                           = 0
    ERRbadfunc                           = 1
    ERRbadfile                           = 2
    ERRbadpath                           = 3
    ERRnofids                            = 4
    ERRnoaccess                          = 5
    ERRbadfid                            = 6
    ERRbadmcb                            = 7
    ERRnomem                             = 8
    ERRbadmem                            = 9
    ERRbadenv                            = 10
    ERRbadaccess                         = 12
    ERRbaddata                           = 13
    ERRres                               = 14
    ERRbaddrive                          = 15
    ERRremcd                             = 16
    ERRdiffdevice                        = 17
    ERRnofiles                           = 18
    ERRgeneral                           = 31
    ERRbadshare                          = 32
    ERRlock                              = 33
    ERRunsup                             = 50
    ERRnetnamedel                        = 64
    ERRnosuchshare                       = 67
    ERRfilexists                         = 80
    ERRinvalidparam                      = 87
    ERRcannotopen                        = 110
    ERRinsufficientbuffer                = 122
    ERRinvalidname                       = 123
    ERRunknownlevel                      = 124
    ERRnotlocked                         = 158
    ERRrename                            = 183
    ERRbadpipe                           = 230
    ERRpipebusy                          = 231
    ERRpipeclosing                       = 232
    ERRnotconnected                      = 233
    ERRmoredata                          = 234
    ERRnomoreitems                       = 259
    ERRbaddirectory                      = 267
    ERReasnotsupported                   = 282
    ERRlogonfailure                      = 1326
    ERRbuftoosmall                       = 2123
    ERRunknownipc                        = 2142
    ERRnosuchprintjob                    = 2151
    ERRinvgroup                          = 2455

    # here's a special one from observing NT
    ERRnoipc                             = 66

    # These errors seem to be only returned by the NT printer driver system
    ERRdriveralreadyinstalled            = 1795
    ERRunknownprinterport                = 1796
    ERRunknownprinterdriver              = 1797
    ERRunknownprintprocessor             = 1798
    ERRinvalidseparatorfile              = 1799
    ERRinvalidjobpriority                = 1800
    ERRinvalidprintername                = 1801
    ERRprinteralreadyexists              = 1802
    ERRinvalidprintercommand             = 1803
    ERRinvaliddatatype                   = 1804
    ERRinvalidenvironment                = 1805

    ERRunknownprintmonitor               = 3000
    ERRprinterdriverinuse                = 3001
    ERRspoolfilenotfound                 = 3002
    ERRnostartdoc                        = 3003
    ERRnoaddjob                          = 3004
    ERRprintprocessoralreadyinstalled    = 3005
    ERRprintmonitoralreadyinstalled      = 3006
    ERRinvalidprintmonitor               = 3007
    ERRprintmonitorinuse                 = 3008
    ERRprinterhasjobsqueued              = 3009

    # Error codes for the ERRSRV class

    ERRerror                             = 1
    ERRbadpw                             = 2
    ERRbadtype                           = 3
    ERRaccess                            = 4
    ERRinvnid                            = 5
    ERRinvnetname                        = 6
    ERRinvdevice                         = 7
    ERRqfull                             = 49
    ERRqtoobig                           = 50
    ERRinvpfid                           = 52
    ERRsmbcmd                            = 64
    ERRsrverror                          = 65
    ERRfilespecs                         = 67
    ERRbadlink                           = 68
    ERRbadpermits                        = 69
    ERRbadpid                            = 70
    ERRsetattrmode                       = 71
    ERRpaused                            = 81
    ERRmsgoff                            = 82
    ERRnoroom                            = 83
    ERRrmuns                             = 87
    ERRtimeout                           = 88
    ERRnoresource                        = 89
    ERRtoomanyuids                       = 90
    ERRbaduid                            = 91
    ERRuseMPX                            = 250
    ERRuseSTD                            = 251
    ERRcontMPX                           = 252
    ERRbadPW                             = None
    ERRnosupport                         = 0
    ERRunknownsmb                        = 22

    # Error codes for the ERRHRD class

    ERRnowrite                           = 19
    ERRbadunit                           = 20
    ERRnotready                          = 21
    ERRbadcmd                            = 22
    ERRdata                              = 23
    ERRbadreq                            = 24
    ERRseek                              = 25
    ERRbadmedia                          = 26
    ERRbadsector                         = 27
    ERRnopaper                           = 28
    ERRwrite                             = 29
    ERRread                              = 30
    ERRwrongdisk                         = 34
    ERRFCBunavail                        = 35
    ERRsharebufexc                       = 36
    ERRdiskfull                          = 39


    hard_msgs = {
      19: ("ERRnowrite", "Attempt to write on write-protected diskette."),
      20: ("ERRbadunit", "Unknown unit."),
      21: ("ERRnotready", "Drive not ready."),
      22: ("ERRbadcmd", "Unknown command."),
      23: ("ERRdata", "Data error (CRC)."),
      24: ("ERRbadreq", "Bad request structure length."),
      25: ("ERRseek", "Seek error."),
      26: ("ERRbadmedia", "Unknown media type."),
      27: ("ERRbadsector", "Sector not found."),
      28: ("ERRnopaper", "Printer out of paper."),
      29: ("ERRwrite", "Write fault."),
      30: ("ERRread", "Read fault."),
      31: ("ERRgeneral", "General failure."),
      32: ("ERRbadshare", "An open conflicts with an existing open."),
      33: ("ERRlock", "A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process."),
      34: ("ERRwrongdisk", "The wrong disk was found in a drive."),
      35: ("ERRFCBUnavail", "No FCBs are available to process request."),
      36: ("ERRsharebufexc", "A sharing buffer has been exceeded.")
      }

    dos_msgs = {
      ERRbadfunc: ("ERRbadfunc", "Invalid function."),
      ERRbadfile: ("ERRbadfile", "File not found."),
      ERRbadpath: ("ERRbadpath", "Directory invalid."),
      ERRnofids: ("ERRnofids", "No file descriptors available"),
      ERRnoaccess: ("ERRnoaccess", "Access denied."),
      ERRbadfid: ("ERRbadfid", "Invalid file handle."),
      ERRbadmcb: ("ERRbadmcb", "Memory control blocks destroyed."),
      ERRnomem: ("ERRnomem", "Insufficient server memory to perform the requested function."),
      ERRbadmem: ("ERRbadmem", "Invalid memory block address."),
      ERRbadenv: ("ERRbadenv", "Invalid environment."),
      11: ("ERRbadformat", "Invalid format."),
      ERRbadaccess: ("ERRbadaccess", "Invalid open mode."),
      ERRbaddata: ("ERRbaddata", "Invalid data."),
      ERRres: ("ERRres", "reserved."),
      ERRbaddrive: ("ERRbaddrive", "Invalid drive specified."),
      ERRremcd: ("ERRremcd", "A Delete Directory request attempted  to  remove  the  server's  current directory."),
      ERRdiffdevice: ("ERRdiffdevice", "Not same device."),
      ERRnofiles: ("ERRnofiles", "A File Search command can find no more files matching the specified criteria."),
      ERRbadshare: ("ERRbadshare", "The sharing mode specified for an Open conflicts with existing  FIDs  on the file."),
      ERRlock: ("ERRlock", "A Lock request conflicted with an existing lock or specified an  invalid mode,  or an Unlock requested attempted to remove a lock held by another process."),
      ERRunsup: ("ERRunsup",  "The operation is unsupported"),
      ERRnosuchshare: ("ERRnosuchshare",  "You specified an invalid share name"),
      ERRfilexists: ("ERRfilexists", "The file named in a Create Directory, Make  New  File  or  Link  request already exists."),
      ERRinvalidname: ("ERRinvalidname",  "Invalid name"),
      ERRbadpipe: ("ERRbadpipe", "Pipe invalid."),
      ERRpipebusy: ("ERRpipebusy", "All instances of the requested pipe are busy."),
      ERRpipeclosing: ("ERRpipeclosing", "Pipe close in progress."),
      ERRnotconnected: ("ERRnotconnected", "No process on other end of pipe."),
      ERRmoredata: ("ERRmoredata", "There is more data to be returned."),
      ERRinvgroup: ("ERRinvgroup", "Invalid workgroup (try the -W option)"),
      ERRlogonfailure: ("ERRlogonfailure", "Logon failure"),
      ERRdiskfull: ("ERRdiskfull", "Disk full"),
      ERRgeneral: ("ERRgeneral",  "General failure"),
      ERRunknownlevel: ("ERRunknownlevel",  "Unknown info level")
      }

    server_msgs = {
      1: ("ERRerror", "Non-specific error code."),
      2: ("ERRbadpw", "Bad password - name/password pair in a Tree Connect or Session Setup are invalid."),
      3: ("ERRbadtype", "reserved."),
      4: ("ERRaccess", "The requester does not have  the  necessary  access  rights  within  the specified  context for the requested function. The context is defined by the TID or the UID."),
      5: ("ERRinvnid", "The tree ID (TID) specified in a command was invalid."),
      6: ("ERRinvnetname", "Invalid network name in tree connect."),
      7: ("ERRinvdevice", "Invalid device - printer request made to non-printer connection or  non-printer request made to printer connection."),
      49: ("ERRqfull", "Print queue full (files) -- returned by open print file."),
      50: ("ERRqtoobig", "Print queue full -- no space."),
      51: ("ERRqeof", "EOF on print queue dump."),
      52: ("ERRinvpfid", "Invalid print file FID."),
      64: ("ERRsmbcmd", "The server did not recognize the command received."),
      65: ("ERRsrverror","The server encountered an internal error, e.g., system file unavailable."),
      67: ("ERRfilespecs", "The file handle (FID) and pathname parameters contained an invalid  combination of values."),
      68: ("ERRreserved", "reserved."),
      69: ("ERRbadpermits", "The access permissions specified for a file or directory are not a valid combination.  The server cannot set the requested attribute."),
      70: ("ERRreserved", "reserved."),
      71: ("ERRsetattrmode", "The attribute mode in the Set File Attribute request is invalid."),
      81: ("ERRpaused", "Server is paused."),
      82: ("ERRmsgoff", "Not receiving messages."),
      83: ("ERRnoroom", "No room to buffer message."),
      87: ("ERRrmuns", "Too many remote user names."),
      88: ("ERRtimeout", "Operation timed out."),
      89: ("ERRnoresource", "No resources currently available for request."),
      90: ("ERRtoomanyuids", "Too many UIDs active on this session."),
      91: ("ERRbaduid", "The UID is not known as a valid ID on this session."),
      250: ("ERRusempx","Temp unable to support Raw, use MPX mode."),
      251: ("ERRusestd","Temp unable to support Raw, use standard read/write."),
      252: ("ERRcontmpx", "Continue in MPX mode."),
      253: ("ERRreserved", "reserved."),
      254: ("ERRreserved", "reserved."),
  0xFFFF: ("ERRnosupport", "Function not supported.")
  }
    # Error clases

    ERRDOS = 0x1
    error_classes = { 0: ("SUCCESS", {}),
                      ERRDOS: ("ERRDOS", dos_msgs),
                      0x02: ("ERRSRV",server_msgs),
                      0x03: ("ERRHRD",hard_msgs),
                      0x04: ("ERRXOS", {} ),
                      0xE1: ("ERRRMX1", {} ),
                      0xE2: ("ERRRMX2", {} ),
                      0xE3: ("ERRRMX3", {} ),
                      0xFF: ("ERRCMD", {} ) }



    def __init__( self, error_string, error_class, error_code, nt_status = 0, packet=0):
        Exception.__init__(self, error_string)
        self.nt_status = nt_status
        self._args = error_string
        if nt_status:
           self.error_class = 0
           self.error_code  = (error_code << 16) + error_class
        else:
           self.error_class = error_class
           self.error_code = error_code
        self.packet = packet

    def get_error_class( self ):
        return self.error_class

    def get_error_code( self ):
        return self.error_code

    def get_error_packet(self):
        return self.packet

    def __str__( self ):
        error_class = SessionError.error_classes.get( self.error_class, None )
        if not error_class:
            error_code_str = self.error_code
            error_class_str = self.error_class
        else:
            error_class_str = error_class[0]
            error_code = error_class[1].get( self.error_code, None )
            if not error_code:
                error_code_str = self.error_code
            else:
                error_code_str = '%s(%s)' % error_code

        if self.nt_status:
            return 'SMB SessionError: %s(%s)' % nt_errors.ERROR_MESSAGES[self.error_code]
        else:
            # Fall back to the old format
            return 'SMB SessionError: class: %s, code: %s' % (error_class_str, error_code_str)


# Raised when an supported feature is present/required in the protocol but is not
# currently supported by pysmb
class UnsupportedFeature(Exception):
    pass

# Contains information about a SMB shared device/service
class SharedDevice:
    def __init__(self, name, share_type, comment):
        self.__name = name
        self.__type = share_type
        self.__comment = comment

    def get_name(self):
        return self.__name

    def get_type(self):
        return self.__type

    def get_comment(self):
        return self.__comment

    def __repr__(self):
        return '<SharedDevice instance: name=' + self.__name + ', type=' + str(self.__type) + ', comment="' + self.__comment + '">'


# Contains information about the shared file/directory
class SharedFile:
    def __init__(self, ctime, atime, mtime, filesize, allocsize, attribs, shortname, longname):
        self.__ctime = ctime
        self.__atime = atime
        self.__mtime = mtime
        self.__filesize = filesize
        self.__allocsize = allocsize
        self.__attribs = attribs
        try:
            if isinstance(shortname,bytes):
                self.__shortname = shortname[:shortname.index(b'\0')]
            else:
                self.__shortname = shortname[:shortname.index('\0')]
        except (ValueError, TypeError):
            self.__shortname = shortname
        try:
            if isinstance(shortname,bytes):
                self.__longname = longname[:longname.index(b'\0')]
            else:
                self.__longname = longname[:longname.index('\0')]
        except (ValueError, TypeError):
            self.__longname = longname

    def get_ctime(self):
        return self.__ctime

    def get_ctime_epoch(self):
        return self.__convert_smbtime(self.__ctime)

    def get_mtime(self):
        return self.__mtime

    def get_mtime_epoch(self):
        return self.__convert_smbtime(self.__mtime)

    def get_atime(self):
        return self.__atime

    def get_atime_epoch(self):
        return self.__convert_smbtime(self.__atime)

    def get_filesize(self):
        return self.__filesize

    def get_allocsize(self):
        return self.__allocsize

    def get_attributes(self):
        return self.__attribs

    def is_archive(self):
        return self.__attribs & ATTR_ARCHIVE

    def is_compressed(self):
        return self.__attribs & ATTR_COMPRESSED

    def is_normal(self):
        return self.__attribs & ATTR_NORMAL

    def is_hidden(self):
        return self.__attribs & ATTR_HIDDEN

    def is_readonly(self):
        return self.__attribs & ATTR_READONLY

    def is_temporary(self):
        return self.__attribs & ATTR_TEMPORARY

    def is_directory(self):
        return self.__attribs & ATTR_DIRECTORY

    def is_system(self):
        return self.__attribs & ATTR_SYSTEM

    def get_shortname(self):
        return self.__shortname

    def get_longname(self):
        return self.__longname

    def __repr__(self):
        return '<SharedFile instance: shortname="' + self.__shortname + '", longname="' + self.__longname + '", filesize=' + str(self.__filesize) + '>'

    @staticmethod
    def __convert_smbtime(t):
        x = t >> 32
        y = t & 0xffffffff
        geo_cal_offset = 11644473600.0  # = 369.0 * 365.25 * 24 * 60 * 60 - (3.0 * 24 * 60 * 60 + 6.0 * 60 * 60)
        return (x * 4.0 * (1 << 30) + (y & 0xfff00000)) * 1.0e-7 - geo_cal_offset


# Contain information about a SMB machine
class SMBMachine:
    def __init__(self, nbname, nbt_type, comment):
        self.__nbname = nbname
        self.__type = nbt_type
        self.__comment = comment

    def __repr__(self):
        return '<SMBMachine instance: nbname="' + self.__nbname + '", type=' + hex(self.__type) + ', comment="' + self.__comment + '">'

class SMBDomain:
    def __init__(self, nbgroup, domain_type, master_browser):
        self.__nbgroup = nbgroup
        self.__type = domain_type
        self.__master_browser = master_browser

    def __repr__(self):
        return '<SMBDomain instance: nbgroup="' + self.__nbgroup + '", type=' + hex(self.__type) + ', master browser="' + self.__master_browser + '">'

# Represents a SMB Packet
class NewSMBPacket(Structure):
    structure = (
        ('Signature', '"\xffSMB'),
        ('Command','B=0'),
        ('ErrorClass','B=0'),
        ('_reserved','B=0'),
        ('ErrorCode','<H=0'),
        ('Flags1','B=0'),
        ('Flags2','<H=0'),
        ('PIDHigh','<H=0'),
        ('SecurityFeatures','8s=""'),
        ('Reserved','<H=0'),
        ('Tid','<H=0xffff'),
        ('Pid','<H=0'),
        ('Uid','<H=0'),
        ('Mid','<H=0'),
        ('Data','*:'),
    )

    def __init__(self, **kargs):
        Structure.__init__(self, **kargs)

        if ('Flags2' in self.fields) is False:
             self['Flags2'] = 0
        if ('Flags1' in self.fields) is False:
             self['Flags1'] = 0

        if 'data' not in kargs:
            self['Data'] = []

    def addCommand(self, command):
        if len(self['Data']) == 0:
            self['Command'] = command.command
        else:
            self['Data'][-1]['Parameters']['AndXCommand'] = command.command
            self['Data'][-1]['Parameters']['AndXOffset'] = len(self)
        self['Data'].append(command)

    def isMoreData(self):
        return (self['Command'] in [SMB.SMB_COM_TRANSACTION, SMB.SMB_COM_READ_ANDX, SMB.SMB_COM_READ_RAW] and
                self['ErrorClass'] == 1 and self['ErrorCode'] == SessionError.ERRmoredata)

    def isMoreProcessingRequired(self):
        return self['ErrorClass'] == 0x16 and self['ErrorCode'] == 0xc000

    def isValidAnswer(self, cmd):
        # this was inside a loop reading more from the net (with recv_packet(None))
        if self['Command'] == cmd:
            if (self['ErrorClass'] == 0x00 and self['ErrorCode']  == 0x00):
                    return 1
            elif self.isMoreData():
                return 1
            elif self.isMoreProcessingRequired():
                return 1
            raise SessionError("SMB Library Error", self['ErrorClass'] + (self['_reserved'] << 8), self['ErrorCode'], self['Flags2'] & SMB.FLAGS2_NT_STATUS, self)
        else:
            raise UnsupportedFeature("Unexpected answer from server: Got %d, Expected %d" % (self['Command'], cmd))


class SMBCommand(Structure):
    structure = (
        ('WordCount', 'B=len(Parameters)//2'),
        ('_ParametersLength','_-Parameters','WordCount*2'),
        ('Parameters',':'),             # default set by constructor
        ('ByteCount','<H-Data'),
        ('Data',':'),                   # default set by constructor
    )

    def __init__(self, commandOrData = None, data = None, **kargs):
        if type(commandOrData) == type(0):
            self.command = commandOrData
        else:
            data = data or commandOrData

        Structure.__init__(self, data = data, **kargs)

        if data is None:
            self['Parameters'] = ''
            self['Data']       = ''

class AsciiOrUnicodeStructure(Structure):
    UnicodeStructure = ()
    AsciiStructure   = ()
    def __init__(self, flags = 0, **kargs):
        if flags & SMB.FLAGS2_UNICODE:
            self.structure = self.UnicodeStructure
        else:
            self.structure = self.AsciiStructure
        Structure.__init__(self, **kargs)

class SMBCommand_Parameters(Structure):
    pass

class SMBAndXCommand_Parameters(Structure):
    commonHdr = (
        ('AndXCommand','B=0xff'),
        ('_reserved','B=0'),
        ('AndXOffset','<H=0'),
    )
    structure = (       # default structure, overridden by subclasses
        ('Data',':=""'),
    )

############# TRANSACTIONS RELATED
# TRANS2_QUERY_FS_INFORMATION
# QUERY_FS Information Levels
# SMB_QUERY_FS_ATTRIBUTE_INFO
class SMBQueryFsAttributeInfo(Structure):
    structure = (
        ('FileSystemAttributes','<L'),
        ('MaxFilenNameLengthInBytes','<L'),
        ('LengthOfFileSystemName','<L-FileSystemName'),
        ('FileSystemName',':'),
    )

class SMBQueryFsInfoVolume(AsciiOrUnicodeStructure):
    commonHdr = (
        ('ulVolSerialNbr','<L=0xABCDEFAA'),
        ('cCharCount','<B-VolumeLabel'),
    )
    AsciiStructure = (
        ('VolumeLabel','z'),
    )
    UnicodeStructure = (
        ('VolumeLabel','u'),
    )

# FILE_FS_SIZE_INFORMATION
class FileFsSizeInformation(Structure):
    structure = (
        ('TotalAllocationUnits','<q=148529400'),
        ('AvailableAllocationUnits','<q=14851044'),
        ('SectorsPerAllocationUnit','<L=2'),
        ('BytesPerSector','<L=512'),
    )

# SMB_QUERY_FS_SIZE_INFO
class SMBQueryFsSizeInfo(Structure):
    structure = (
        ('TotalAllocationUnits','<q=148529400'),
        ('TotalFreeAllocationUnits','<q=14851044'),
        ('SectorsPerAllocationUnit','<L=2'),
        ('BytesPerSector','<L=512'),
    )
# FILE_FS_FULL_SIZE_INFORMATION
class SMBFileFsFullSizeInformation(Structure):
    structure = (
        ('TotalAllocationUnits','<q=148529400'),
        ('CallerAvailableAllocationUnits','<q=148529400'),
        ('ActualAvailableAllocationUnits','<q=148529400'),
        ('SectorsPerAllocationUnit','<L=15'),
        ('BytesPerSector','<L=512')
    )
# SMB_QUERY_FS_VOLUME_INFO
class SMBQueryFsVolumeInfo(Structure):
    structure = (
        ('VolumeCreationTime','<q'),
        ('SerialNumber','<L=0xABCDEFAA'),
        ('VolumeLabelSize','<L=len(VolumeLabel)'),
        ('Reserved','<H=0x10'),
        ('VolumeLabel',':')
    )
# SMB_FIND_FILE_BOTH_DIRECTORY_INFO level
class SMBFindFileBothDirectoryInfo(AsciiOrUnicodeStructure):
    commonHdr = (
        ('NextEntryOffset','<L=0'),
        ('FileIndex','<L=0'),
        ('CreationTime','<q'),
        ('LastAccessTime','<q'),
        ('LastWriteTime','<q'),
        ('LastChangeTime','<q'),
        ('EndOfFile','<q=0'),
        ('AllocationSize','<q=0'),
        ('ExtFileAttributes','<L=0'),
    )
    AsciiStructure = (
        ('FileNameLength','<L-FileName','len(FileName)'),
        ('EaSize','<L=0'),
        ('ShortNameLength','<B=0'),
        ('Reserved','<B=0'),
        ('ShortName','24s'),
        ('FileName',':'),
    )
    UnicodeStructure = (
        ('FileNameLength','<L-FileName','len(FileName)*2'),
        ('EaSize','<L=0'),
        ('ShortNameLength','<B=0'),
        ('Reserved','<B=0'),
        ('ShortName','24s'),
        ('FileName',':'),
    )

# SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO level
class SMBFindFileIdFullDirectoryInfo(AsciiOrUnicodeStructure):
    commonHdr = (
        ('NextEntryOffset','<L=0'),
        ('FileIndex','<L=0'),
        ('CreationTime','<q'),
        ('LastAccessTime','<q'),
        ('LastWriteTime','<q'),
        ('LastChangeTime','<q'),
        ('EndOfFile','<q=0'),
        ('AllocationSize','<q=0'),
        ('ExtFileAttributes','<L=0'),
    )
    AsciiStructure = (
        ('FileNameLength','<L-FileName','len(FileName)'),
        ('EaSize','<L=0'),
        ('FileID','<q=0'),
        ('FileName',':'),
    )
    UnicodeStructure = (
        ('FileNameLength','<L-FileName','len(FileName)*2'),
        ('EaSize','<L=0'),
        ('FileID','<q=0'),
        ('FileName',':'),
    )

# SMB_FIND_FILE_ID_BOTH_DIRECTORY_INFO level
class SMBFindFileIdBothDirectoryInfo(AsciiOrUnicodeStructure):
    commonHdr = (
        ('NextEntryOffset','<L=0'),
        ('FileIndex','<L=0'),
        ('CreationTime','<q'),
        ('LastAccessTime','<q'),
        ('LastWriteTime','<q'),
        ('LastChangeTime','<q'),
        ('EndOfFile','<q=0'),
        ('AllocationSize','<q=0'),
        ('ExtFileAttributes','<L=0'),
    )
    AsciiStructure = (
        ('FileNameLength','<L-FileName','len(FileName)'),
        ('EaSize','<L=0'),
        ('ShortNameLength','<B=0'),
        ('Reserved','<B=0'),
        ('ShortName','24s'),
        ('Reserved','<H=0'),
        ('FileID','<q=0'),
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('FileNameLength','<L-FileName','len(FileName)*2'),
        ('EaSize','<L=0'),
        ('ShortNameLength','<B=0'),
        ('Reserved','<B=0'),
        ('ShortName','24s'),
        ('Reserved','<H=0'),
        ('FileID','<q=0'),
        ('FileName',':'),
    )

# SMB_FIND_FILE_DIRECTORY_INFO level
class SMBFindFileDirectoryInfo(AsciiOrUnicodeStructure):
    commonHdr = (
        ('NextEntryOffset','<L=0'),
        ('FileIndex','<L=0'),
        ('CreationTime','<q'),
        ('LastAccessTime','<q'),
        ('LastWriteTime','<q'),
        ('LastChangeTime','<q'),
        ('EndOfFile','<q=0'),
        ('AllocationSize','<q=1'),
        ('ExtFileAttributes','<L=0'),
    )
    AsciiStructure = (
        ('FileNameLength','<L-FileName','len(FileName)'),
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('FileNameLength','<L-FileName','len(FileName)*2'),
        ('FileName',':'),
    )

# SMB_FIND_FILE_NAMES_INFO level
class SMBFindFileNamesInfo(AsciiOrUnicodeStructure):
    commonHdr = (
        ('NextEntryOffset','<L=0'),
        ('FileIndex','<L=0'),
    )
    AsciiStructure = (
        ('FileNameLength','<L-FileName','len(FileName)'),
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('FileNameLength','<L-FileName','len(FileName)*2'),
        ('FileName',':'),
    )

# SMB_FIND_FILE_FULL_DIRECTORY_INFO level
class SMBFindFileFullDirectoryInfo(AsciiOrUnicodeStructure):
    commonHdr = (
        ('NextEntryOffset','<L=0'),
        ('FileIndex','<L=0'),
        ('CreationTime','<q'),
        ('LastAccessTime','<q'),
        ('LastWriteTime','<q'),
        ('LastChangeTime','<q'),
        ('EndOfFile','<q=0'),
        ('AllocationSize','<q=1'),
        ('ExtFileAttributes','<L=0'),
    )
    AsciiStructure = (
        ('FileNameLength','<L-FileName','len(FileName)'),
        ('EaSize','<L'),
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('FileNameLength','<L-FileName','len(FileName)*2'),
        ('EaSize','<L'),
        ('FileName',':'),
    )

# SMB_FIND_INFO_STANDARD level
class SMBFindInfoStandard(AsciiOrUnicodeStructure):
    commonHdr = (
        ('ResumeKey','<L=0xff'),
        ('CreationDate','<H=0'),
        ('CreationTime','<H=0'),
        ('LastAccessDate','<H=0'),
        ('LastAccessTime','<H=0'),
        ('LastWriteDate','<H=0'),
        ('LastWriteTime','<H=0'),
        ('EaSize','<L'),
        ('AllocationSize','<L=1'),
        ('ExtFileAttributes','<H=0'),
    )
    AsciiStructure = (
        ('FileNameLength','<B-FileName','len(FileName)'),
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('FileNameLength','<B-FileName','len(FileName)*2'),
        ('FileName',':'),
    )

# SET_FILE_INFORMATION structures
# SMB_SET_FILE_DISPOSITION_INFO
class SMBSetFileDispositionInfo(Structure):
    structure = (
        ('DeletePending','<B'),
    )

# SMB_SET_FILE_BASIC_INFO
class SMBSetFileBasicInfo(Structure):
    structure = (
        ('CreationTime','<q'),
        ('LastAccessTime','<q'),
        ('LastWriteTime','<q'),
        ('ChangeTime','<q'),
        ('ExtFileAttributes','<H'),
        ('Reserved','<L'),
    )

# FILE_STREAM_INFORMATION
class SMBFileStreamInformation(Structure):
    commonHdr = (
        ('NextEntryOffset','<L=0'),
        ('StreamNameLength','<L=0'),
        ('StreamSize','<q=0'),
        ('StreamAllocationSize','<q=0'),
        ('StreamName',':=""'),
    )

# FILE_NETWORK_OPEN_INFORMATION
class SMBFileNetworkOpenInfo(Structure):
    structure = (
        ('CreationTime','<q=0'),
        ('LastAccessTime','<q=0'),
        ('LastWriteTime','<q=0'),
        ('ChangeTime','<q=0'),
        ('AllocationSize','<q=0'),
        ('EndOfFile','<q=0'),
        ('FileAttributes','<L=0'),
        ('Reserved','<L=0'),
    )

# SMB_SET_FILE_END_OF_FILE_INFO
class SMBSetFileEndOfFileInfo(Structure):
    structure = (
        ('EndOfFile','<q'),
    )

# TRANS2_FIND_NEXT2
class SMBFindNext2_Parameters(AsciiOrUnicodeStructure):
     commonHdr = (
         ('SID','<H'),
         ('SearchCount','<H'),
         ('InformationLevel','<H'),
         ('ResumeKey','<L'),
         ('Flags','<H'),
     )
     AsciiStructure = (
         ('FileName','z'),
     )
     UnicodeStructure = (
         ('FileName','u'),
     )

class SMBFindNext2Response_Parameters(Structure):
     structure = (
         ('SearchCount','<H'),
         ('EndOfSearch','<H=1'),
         ('EaErrorOffset','<H=0'),
         ('LastNameOffset','<H=0'),
     )

class SMBFindNext2_Data(Structure):
     structure = (
         ('GetExtendedAttributesListLength','_-GetExtendedAttributesList', 'self["GetExtendedAttributesListLength"]'),
         ('GetExtendedAttributesList',':'),
     )


# TRANS2_FIND_FIRST2 
class SMBFindFirst2Response_Parameters(Structure):
     structure = (
         ('SID','<H'),
         ('SearchCount','<H'),
         ('EndOfSearch','<H=1'),
         ('EaErrorOffset','<H=0'),
         ('LastNameOffset','<H=0'),
     )

class SMBFindFirst2_Parameters(AsciiOrUnicodeStructure):
     commonHdr = (
         ('SearchAttributes','<H'),
         ('SearchCount','<H'),
         ('Flags','<H'),
         ('InformationLevel','<H'),
         ('SearchStorageType','<L'),
     )
     AsciiStructure = (
         ('FileName','z'),
     )
     UnicodeStructure = (
         ('FileName','u'),
     )

class SMBFindFirst2_Data(Structure):
     structure = (
         ('GetExtendedAttributesListLength','_-GetExtendedAttributesList', 'self["GetExtendedAttributesListLength"]'),
         ('GetExtendedAttributesList',':'),
     )

# TRANS2_SET_PATH_INFORMATION
class SMBSetPathInformation_Parameters(AsciiOrUnicodeStructure):
    commonHdr = (
        ('InformationLevel','<H'),
        ('Reserved','<L'),
    )
    AsciiStructure = (
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('FileName','u'),
    )

class SMBSetPathInformationResponse_Parameters(Structure):
    structure = (
        ('EaErrorOffset','<H=0'),
    )

# TRANS2_SET_FILE_INFORMATION
class SMBSetFileInformation_Parameters(Structure):
    structure = (
        ('FID','<H'),
        ('InformationLevel','<H'),
        ('Reserved','<H'),
    )

class SMBSetFileInformationResponse_Parameters(Structure):
    structure = (
        ('EaErrorOffset','<H=0'),
    )

# TRANS2_QUERY_FILE_INFORMATION
class SMBQueryFileInformation_Parameters(Structure):
    structure = (
        ('FID','<H'),
        ('InformationLevel','<H'),
    )

class SMBQueryFileInformationResponse_Parameters(Structure):
    structure = (
        ('EaErrorOffset','<H=0'),
    )

class SMBQueryFileInformation_Data(Structure):
    structure = (
        ('GetExtendedAttributeList',':'),
    )

# TRANS2_QUERY_PATH_INFORMATION
class SMBQueryPathInformationResponse_Parameters(Structure):
    structure = (
        ('EaErrorOffset','<H=0'),
    )

class SMBQueryPathInformation_Parameters(AsciiOrUnicodeStructure):
    commonHdr = (
        ('InformationLevel','<H'),
        ('Reserved','<L=0'),
    )
    AsciiStructure = (
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('FileName','u'),
    )

class SMBQueryPathInformation_Data(Structure):
    structure = (
        ('GetExtendedAttributeList',':'),
    )


# SMB_QUERY_FILE_EA_INFO
class SMBQueryFileEaInfo(Structure):
    structure = (
        ('EaSize','<L=0'),
    )

# SMB_QUERY_FILE_BASIC_INFO
class SMBQueryFileBasicInfo(Structure):
    structure = (
        ('CreationTime','<q'),
        ('LastAccessTime','<q'),
        ('LastWriteTime','<q'),
        ('LastChangeTime','<q'),
        ('ExtFileAttributes','<L'),
        #('Reserved','<L=0'),
    )

# SMB_QUERY_FILE_STANDARD_INFO
class SMBQueryFileStandardInfo(Structure):
    structure = (
        ('AllocationSize','<q'),
        ('EndOfFile','<q'),
        ('NumberOfLinks','<L=0'),
        ('DeletePending','<B=0'),
        ('Directory','<B'),
    )

# SMB_QUERY_FILE_ALL_INFO
class SMBQueryFileAllInfo(Structure):
    structure = (
        ('CreationTime','<q'),
        ('LastAccessTime','<q'),
        ('LastWriteTime','<q'),
        ('LastChangeTime','<q'),
        ('ExtFileAttributes','<L'),
        ('Reserved','<L=0'),
        ('AllocationSize','<q'),
        ('EndOfFile','<q'),
        ('NumberOfLinks','<L=0'),
        ('DeletePending','<B=0'),
        ('Directory','<B'),
        ('Reserved','<H=0'),
        ('EaSize','<L=0'),
        ('FileNameLength','<L-FileName','len(FileName)'),
        ('FileName',':'),
    )

# \PIPE\LANMAN NetShareEnum
class SMBNetShareEnum(Structure):
    structure = (
        ('RAPOpcode','<H=0'),
        ('ParamDesc','z'),
        ('DataDesc','z'),
        ('InfoLevel','<H'),
        ('ReceiveBufferSize','<H'),
    )

class SMBNetShareEnumResponse(Structure):
    structure = (
        ('Status','<H=0'),
        ('Convert','<H=0'),
        ('EntriesReturned','<H'),
        ('EntriesAvailable','<H'),
    )

class NetShareInfo1(Structure):
    structure = (
        ('NetworkName','13s'),
        ('Pad','<B=0'),
        ('Type','<H=0'),
        ('RemarkOffsetLow','<H=0'),
        ('RemarkOffsetHigh','<H=0'),
    )

# \PIPE\LANMAN NetServerGetInfo
class SMBNetServerGetInfoResponse(Structure):
    structure = (
        ('Status','<H=0'),
        ('Convert','<H=0'),
        ('TotalBytesAvailable','<H'),
    )

class SMBNetServerInfo1(Structure):
    # Level 1 Response
    structure = (
        ('ServerName','16s'),
        ('MajorVersion','B=5'),
        ('MinorVersion','B=0'),
        ('ServerType','<L=3'),
        ('ServerCommentLow','<H=0'),
        ('ServerCommentHigh','<H=0'),
    )

# \PIPE\LANMAN NetShareGetInfo
class SMBNetShareGetInfo(Structure):
    structure = (
        ('RAPOpcode','<H=0'),
        ('ParamDesc','z'),
        ('DataDesc','z'),
        ('ShareName','z'),
        ('InfoLevel','<H'),
        ('ReceiveBufferSize','<H'),
    )

class SMBNetShareGetInfoResponse(Structure):
    structure = (
        ('Status','<H=0'),
        ('Convert','<H=0'),
        ('TotalBytesAvailable','<H'),
    )

############# Security Features
class SecurityFeatures(Structure):
    structure = (
        ('Key','<L=0'),
        ('CID','<H=0'),
        ('SequenceNumber','<H=0'),
    )

############# SMB_COM_QUERY_INFORMATION2 (0x23)
class SMBQueryInformation2_Parameters(Structure):
    structure = (
        ('Fid','<H'),
    )

class SMBQueryInformation2Response_Parameters(Structure):
    structure = (
        ('CreateDate','<H'),
        ('CreationTime','<H'),
        ('LastAccessDate','<H'),
        ('LastAccessTime','<H'),
        ('LastWriteDate','<H'),
        ('LastWriteTime','<H'),
        ('FileDataSize','<L'),
        ('FileAllocationSize','<L'),
        ('FileAttributes','<L'),
    )



############# SMB_COM_SESSION_SETUP_ANDX (0x73)
class SMBSessionSetupAndX_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('MaxBuffer','<H'),
        ('MaxMpxCount','<H'),
        ('VCNumber','<H'),
        ('SessionKey','<L'),
        ('AnsiPwdLength','<H'),
        ('UnicodePwdLength','<H'),
        ('_reserved','<L=0'),
        ('Capabilities','<L'),
    )

class SMBSessionSetupAndX_Extended_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('MaxBufferSize','<H'),
        ('MaxMpxCount','<H'),
        ('VcNumber','<H'),
        ('SessionKey','<L'),
        ('SecurityBlobLength','<H'),
        ('Reserved','<L=0'),
        ('Capabilities','<L'),
    )

class SMBSessionSetupAndX_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('AnsiPwdLength','_-AnsiPwd','self["AnsiPwdLength"]'),
        ('UnicodePwdLength','_-UnicodePwd','self["UnicodePwdLength"]'),
        ('AnsiPwd',':=""'),
        ('UnicodePwd',':=""'),
        ('Account','z=""'),
        ('PrimaryDomain','z=""'),
        ('NativeOS','z=""'),
        ('NativeLanMan','z=""'),
    )

    UnicodeStructure = (
        ('AnsiPwdLength','_-AnsiPwd','self["AnsiPwdLength"]'),
        ('UnicodePwdLength','_-UnicodePwd','self["UnicodePwdLength"]'),
        ('AnsiPwd',':=""'),
        ('UnicodePwd',':=""'),
        ('Account','u=""'),
        ('PrimaryDomain','u=""'),
        ('NativeOS','u=""'),
        ('NativeLanMan','u=""'),
    )

class SMBSessionSetupAndX_Extended_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('SecurityBlobLength','_-SecurityBlob','self["SecurityBlobLength"]'),
        ('SecurityBlob',':'),
        ('NativeOS','z=""'),
        ('NativeLanMan','z=""'),
    )

    UnicodeStructure = (
        ('SecurityBlobLength','_-SecurityBlob','self["SecurityBlobLength"]'),
        ('SecurityBlob',':'),
        ('NativeOS','u=""'),
        ('NativeLanMan','u=""'),
    )

class SMBSessionSetupAndXResponse_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('Action','<H'),
    )

class SMBSessionSetupAndX_Extended_Response_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('Action','<H=0'),
        ('SecurityBlobLength','<H'),
    )

class SMBSessionSetupAndXResponse_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('NativeOS','z=""'),
        ('NativeLanMan','z=""'),
        ('PrimaryDomain','z=""'),
    )

    UnicodeStructure = (
        ('NativeOS','u=""'),
        ('NativeLanMan','u=""'),
        ('PrimaryDomain','u=""'),
    )

class SMBSessionSetupAndX_Extended_Response_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('SecurityBlobLength','_-SecurityBlob','self["SecurityBlobLength"]'),
        ('SecurityBlob',':'),
        ('NativeOS','z=""'),
        ('NativeLanMan','z=""'),
    )

    UnicodeStructure = (
        ('SecurityBlobLength','_-SecurityBlob','self["SecurityBlobLength"]'),
        ('SecurityBlob',':'),
        ('PadLen','_-Pad','1 if (len(self["SecurityBlob"]) % 2 == 0) else 0'),
        ('Pad',':=""'),
        ('NativeOS','u=""'),
        ('NativeLanMan','u=""'),
    )
    def getData(self):
        if self.structure == self.UnicodeStructure:
            if len(str(self['SecurityBlob'])) % 2 == 0:
                self['Pad'] = '\x00'
        return AsciiOrUnicodeStructure.getData(self)

############# SMB_COM_TREE_CONNECT (0x70)
class SMBTreeConnect_Parameters(SMBCommand_Parameters):
    structure = (
    )

class SMBTreeConnect_Data(SMBCommand_Parameters):
    structure = (
        ('PathFormat','"\x04'),
        ('Path','z'),
        ('PasswordFormat','"\x04'),
        ('Password','z'),
        ('ServiceFormat','"\x04'),
        ('Service','z'),
    )

############# SMB_COM_TREE_CONNECT_ANDX (0x75)
class SMBTreeConnectAndX_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('Flags','<H=0'),
        ('PasswordLength','<H'),
    )

class SMBTreeConnectAndXResponse_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('OptionalSupport','<H=0'),
    )

class SMBTreeConnectAndXExtendedResponse_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('OptionalSupport','<H=1'),
        ('MaximalShareAccessRights','<L=0x1fffff'),
        ('GuestMaximalShareAccessRights','<L=0x1fffff'),
    )

class SMBTreeConnectAndX_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('_PasswordLength','_-Password','self["_PasswordLength"]'),
        ('Password',':'),
        ('Path','z'),
        ('Service','z'),
    )

    UnicodeStructure = (
        ('_PasswordLength','_-Password','self["_PasswordLength"] if self["_PasswordLength"] > 0 else 1'),
        ('Password',':'),
        ('Path','u'),
        ('Service','z'),
    )

class SMBTreeConnectAndXResponse_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('Service','z'),
        ('PadLen','_-Pad','self["PadLen"]'),
        ('Pad',':=""'),
        ('NativeFileSystem','z'),
    )
    UnicodeStructure = (
        ('Service','z'),
        ('PadLen','_-Pad','self["PadLen"]'),
        ('Pad',':=""'),
        ('NativeFileSystem','u'),
    )

############# SMB_COM_NT_CREATE_ANDX (0xA2)
class SMBNtCreateAndX_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('_reserved', 'B=0'),
        ('FileNameLength','<H'),     # NameLength
        ('CreateFlags','<L'),        # Flags
        ('RootFid','<L=0'),          # RootDirectoryFID
        ('AccessMask','<L'),         # DesiredAccess
        ('AllocationSizeLo','<L=0'), # AllocationSize
        ('AllocationSizeHi','<L=0'),
        ('FileAttributes','<L=0'),   # ExtFileAttributes
        ('ShareAccess','<L=3'),      #
        ('Disposition','<L=1'),      # CreateDisposition
        ('CreateOptions','<L'),      # CreateOptions
        ('Impersonation','<L=2'),
        ('SecurityFlags','B=3'),
    )

class SMBNtCreateAndXResponse_Parameters(SMBAndXCommand_Parameters):
    # XXX Is there a memory leak in the response for NTCreate (where the Data section would be) in Win 2000, Win XP, and Win 2003?
    structure = (
        ('OplockLevel', 'B=0'),
        ('Fid','<H'),
        ('CreateAction','<L'),
        ('CreateTime','<q=0'),
        ('LastAccessTime','<q=0'),
        ('LastWriteTime','<q=0'),
        ('LastChangeTime','<q=0'),
        ('FileAttributes','<L=0x80'),
        ('AllocationSize','<q=0'),
        ('EndOfFile','<q=0'),
        ('FileType','<H=0'),
        ('IPCState','<H=0'),
        ('IsDirectory','B'),
    )

class SMBNtCreateAndXExtendedResponse_Parameters(SMBAndXCommand_Parameters):
    # [MS-SMB] Extended response description
    structure = (
        ('OplockLevel', 'B=0'),
        ('Fid','<H'),
        ('CreateAction','<L'),
        ('CreateTime','<q=0'),
        ('LastAccessTime','<q=0'),
        ('LastWriteTime','<q=0'),
        ('LastChangeTime','<q=0'),
        ('FileAttributes','<L=0x80'),
        ('AllocationSize','<q=0'),
        ('EndOfFile','<q=0'),
        ('FileType','<H=0'),
        ('IPCState','<H=0'),
        ('IsDirectory','B'),
        ('VolumeGUID','16s'),
        ('FileIdLow','<L=0'),
        ('FileIdHigh','<L=0'),
        ('MaximalAccessRights','<L=0x12019b'),
        ('GuestMaximalAccessRights','<L=0x120089'),
    )

class SMBNtCreateAndX_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('Pad','B'),
        ('FileName','u'),
    )

############# SMB_COM_OPEN_ANDX (0xD2)
class SMBOpenAndX_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('Flags','<H=0'),
        ('DesiredAccess','<H=0'),
        ('SearchAttributes','<H=0'),
        ('FileAttributes','<H=0'),
        ('CreationTime','<L=0'),
        ('OpenMode','<H=1'),        # SMB_O_OPEN = 1
        ('AllocationSize','<L=0'),
        ('Reserved','8s=""'),
    )

class SMBOpenAndX_Data(SMBNtCreateAndX_Data):
    pass

class SMBOpenAndXResponse_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('Fid','<H=0'),
        ('FileAttributes','<H=0'),
        ('LastWriten','<L=0'),
        ('FileSize','<L=0'),
        ('GrantedAccess','<H=0'),
        ('FileType','<H=0'),
        ('IPCState','<H=0'),
        ('Action','<H=0'),
        ('ServerFid','<L=0'),
        ('_reserved','<H=0'),
    )

############# SMB_COM_WRITE (0x0B)
class SMBWrite_Parameters(SMBCommand_Parameters):
    structure = (
        ('Fid','<H'),
        ('Count','<H'),
        ('Offset','<L'),
        ('Remaining','<H'),
    )

class SMBWriteResponse_Parameters(SMBCommand_Parameters):
    structure = (
        ('Count','<H'),
    )

class SMBWrite_Data(Structure):
    structure = (
        ('BufferFormat','<B=1'),
        ('DataLength','<H-Data'),
        ('Data',':'),
    )


############# SMB_COM_WRITE_ANDX (0x2F)
class SMBWriteAndX_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('Fid','<H=0'),
        ('Offset','<L=0'),
        ('_reserved','<L=0xff'),
        ('WriteMode','<H=8'),
        ('Remaining','<H=0'),
        ('DataLength_Hi','<H=0'),
        ('DataLength','<H=0'),
        ('DataOffset','<H=0'),
        ('HighOffset','<L=0'),
    )

class SMBWriteAndX_Data_Short(Structure):
     structure = (
         ('_PadLen','_-Pad','self["DataOffset"] - 59'),
         ('Pad',':'),
         #('Pad','<B=0'),
         ('DataLength','_-Data','self["DataLength"]'),
         ('Data',':'),
     )

class SMBWriteAndX_Data(Structure):
     structure = (
         ('_PadLen','_-Pad','self["DataOffset"] - 63'),
         ('Pad',':'),
         #('Pad','<B=0'),
         ('DataLength','_-Data','self["DataLength"]'),
         ('Data',':'),
     )


class SMBWriteAndX_Parameters_Short(SMBAndXCommand_Parameters):
    structure = (
        ('Fid','<H'),
        ('Offset','<L'),
        ('_reserved','<L=0xff'),
        ('WriteMode','<H=8'),
        ('Remaining','<H'),
        ('DataLength_Hi','<H=0'),
        ('DataLength','<H'),
        ('DataOffset','<H=0'),
    )

class SMBWriteAndXResponse_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('Count','<H'),
        ('Available','<H'),
        ('Reserved','<L=0'),
    )

############# SMB_COM_WRITE_RAW (0x1D)
class SMBWriteRaw_Parameters(SMBCommand_Parameters):
    structure = (
        ('Fid','<H'),
        ('Count','<H'),
        ('_reserved','<H=0'),
        ('Offset','<L'),
        ('Timeout','<L=0'),
        ('WriteMode','<H=0'),
        ('_reserved2','<L=0'),
        ('DataLength','<H'),
        ('DataOffset','<H=0'),
    )

############# SMB_COM_READ (0x0A)
class SMBRead_Parameters(SMBCommand_Parameters):
    structure = (
        ('Fid','<H'),
        ('Count','<H'),
        ('Offset','<L'),
        ('Remaining','<H=Count'),
    )

class SMBReadResponse_Parameters(Structure):
    structure = (
        ('Count','<H=0'),
        ('_reserved','8s=""'),
    )

class SMBReadResponse_Data(Structure):
    structure = (
        ('BufferFormat','<B=0x1'),
        ('DataLength','<H-Data'),
        ('Data',':'),
    )

############# SMB_COM_READ_RAW (0x1A)
class SMBReadRaw_Parameters(SMBCommand_Parameters):
    structure = (
        ('Fid','<H'),
        ('Offset','<L'),
        ('MaxCount','<H'),
        ('MinCount','<H=MaxCount'),
        ('Timeout','<L=0'),
        ('_reserved','<H=0'),
    )

############# SMB_COM_NT_TRANSACT  (0xA0)
class SMBNTTransaction_Parameters(SMBCommand_Parameters):
    structure = (
        ('MaxSetupCount','<B=0'),
        ('Reserved1','<H=0'),
        ('TotalParameterCount','<L'),
        ('TotalDataCount','<L'),
        ('MaxParameterCount','<L=1024'),
        ('MaxDataCount','<L=65504'),
        ('ParameterCount','<L'),
        ('ParameterOffset','<L'),
        ('DataCount','<L'),
        ('DataOffset','<L'),
        ('SetupCount','<B=len(Setup)//2'),
        ('Function','<H=0'),
        ('SetupLength','_-Setup','SetupCount*2'),
        ('Setup',':'),
    )

class SMBNTTransactionResponse_Parameters(SMBCommand_Parameters):
    structure = (
        ('Reserved1','3s=""'),
        ('TotalParameterCount','<L'),
        ('TotalDataCount','<L'),
        ('ParameterCount','<L'),
        ('ParameterOffset','<L'),
        ('ParameterDisplacement','<L=0'),
        ('DataCount','<L'),
        ('DataOffset','<L'),
        ('DataDisplacement','<L=0'),
        ('SetupCount','<B=0'),
        ('SetupLength','_-Setup','SetupCount*2'),
        ('Setup',':'),
    )

class SMBNTTransaction_Data(Structure):
    structure = (
        ('Pad1Length','_-Pad1','self["Pad1Length"]'),
        ('Pad1',':'),
        ('NT_Trans_ParametersLength','_-NT_Trans_Parameters','self["NT_Trans_ParametersLength"]'),
        ('NT_Trans_Parameters',':'),
        ('Pad2Length','_-Pad2','self["Pad2Length"]'),
        ('Pad2',':'),
        ('NT_Trans_DataLength','_-NT_Trans_Data','self["NT_Trans_DataLength"]'),
        ('NT_Trans_Data',':'),
    )

class SMBNTTransactionResponse_Data(Structure):
    structure = (
        ('Pad1Length','_-Pad1','self["Pad1Length"]'),
        ('Pad1',':'),
        ('Trans_ParametersLength','_-Trans_Parameters','self["Trans_ParametersLength"]'),
        ('Trans_Parameters',':'),
        ('Pad2Length','_-Pad2','self["Pad2Length"]'),
        ('Pad2',':'),
        ('Trans_DataLength','_-Trans_Data','self["Trans_DataLength"]'),
        ('Trans_Data',':'),
    )


############# SMB_COM_TRANSACTION2_SECONDARY (0x33)
class SMBTransaction2Secondary_Parameters(SMBCommand_Parameters):
    structure = (
        ('TotalParameterCount','<H'),
        ('TotalDataCount','<H'),
        ('ParameterCount','<H'),
        ('ParameterOffset','<H'),
        ('ParameterDisplacement','<H'),
        ('DataCount','<H'),
        ('DataOffset','<H'),
        ('DataDisplacement','<H=0'),
        ('FID','<H'),
    )

class SMBTransaction2Secondary_Data(Structure):
    structure = (
        ('Pad1Length','_-Pad1','self["Pad1Length"]'),
        ('Pad1',':'),
        ('Trans_ParametersLength','_-Trans_Parameters','self["Trans_ParametersLength"]'),
        ('Trans_Parameters',':'),
        ('Pad2Length','_-Pad2','self["Pad2Length"]'),
        ('Pad2',':'),
        ('Trans_DataLength','_-Trans_Data','self["Trans_DataLength"]'),
        ('Trans_Data',':'),
    )


############# SMB_COM_TRANSACTION2 (0x32)

class SMBTransaction2_Parameters(SMBCommand_Parameters):
    structure = (
        ('TotalParameterCount','<H'),
        ('TotalDataCount','<H'),
        ('MaxParameterCount','<H=1024'),
        ('MaxDataCount','<H=65504'),
        ('MaxSetupCount','<B=0'),
        ('Reserved1','<B=0'),
        ('Flags','<H=0'),
        ('Timeout','<L=0'),
        ('Reserved2','<H=0'),
        ('ParameterCount','<H'),
        ('ParameterOffset','<H'),
        ('DataCount','<H'),
        ('DataOffset','<H'),
        ('SetupCount','<B=len(Setup)//2'),
        ('Reserved3','<B=0'),
        ('SetupLength','_-Setup','SetupCount*2'),
        ('Setup',':'),
    )

class SMBTransaction2Response_Parameters(SMBCommand_Parameters):
    structure = (
        ('TotalParameterCount','<H'),
        ('TotalDataCount','<H'),
        ('Reserved1','<H=0'),
        ('ParameterCount','<H'),
        ('ParameterOffset','<H'),
        ('ParameterDisplacement','<H=0'),
        ('DataCount','<H'),
        ('DataOffset','<H'),
        ('DataDisplacement','<H=0'),
        ('SetupCount','<B=0'),
        ('Reserved2','<B=0'),
        ('SetupLength','_-Setup','SetupCount*2'),
        ('Setup',':'),
    )

class SMBTransaction2_Data(Structure):
    structure = (
#        ('NameLength','_-Name','1'),
#        ('Name',':'),
        ('Pad1Length','_-Pad1','self["Pad1Length"]'),
        ('Pad1',':'),
        ('Trans_ParametersLength','_-Trans_Parameters','self["Trans_ParametersLength"]'),
        ('Trans_Parameters',':'),
        ('Pad2Length','_-Pad2','self["Pad2Length"]'),
        ('Pad2',':'),
        ('Trans_DataLength','_-Trans_Data','self["Trans_DataLength"]'),
        ('Trans_Data',':'),
    )

class SMBTransaction2Response_Data(Structure):
    structure = (
        ('Pad1Length','_-Pad1','self["Pad1Length"]'),
        ('Pad1',':'),
        ('Trans_ParametersLength','_-Trans_Parameters','self["Trans_ParametersLength"]'),
        ('Trans_Parameters',':'),
        ('Pad2Length','_-Pad2','self["Pad2Length"]'),
        ('Pad2',':'),
        ('Trans_DataLength','_-Trans_Data','self["Trans_DataLength"]'),
        ('Trans_Data',':'),
    )

############# SMB_COM_QUERY_INFORMATION (0x08)

class SMBQueryInformation_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('BufferFormat','B=4'),
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('BufferFormat','B=4'),
        ('FileName','u'),
    )


class SMBQueryInformationResponse_Parameters(Structure):
    structure = (
        ('FileAttributes','<H'),
        ('LastWriteTime','<L'),
        ('FileSize','<L'),
        ('Reserved','"0123456789'),
    )

############# SMB_COM_TRANSACTION (0x25)
class SMBTransaction_Parameters(SMBCommand_Parameters):
    structure = (
        ('TotalParameterCount','<H'),
        ('TotalDataCount','<H'),
        ('MaxParameterCount','<H=1024'),
        ('MaxDataCount','<H=65504'),
        ('MaxSetupCount','<B=0'),
        ('Reserved1','<B=0'),
        ('Flags','<H=0'),
        ('Timeout','<L=0'),
        ('Reserved2','<H=0'),
        ('ParameterCount','<H'),
        ('ParameterOffset','<H'),
        ('DataCount','<H'),
        ('DataOffset','<H'),
        ('SetupCount','<B=len(Setup)//2'),
        ('Reserved3','<B=0'),
        ('SetupLength','_-Setup','SetupCount*2'),
        ('Setup',':'),
    )

class SMBTransactionResponse_Parameters(SMBCommand_Parameters):
    structure = (
        ('TotalParameterCount','<H'),
        ('TotalDataCount','<H'),
        ('Reserved1','<H=0'),
        ('ParameterCount','<H'),
        ('ParameterOffset','<H'),
        ('ParameterDisplacement','<H=0'),
        ('DataCount','<H'),
        ('DataOffset','<H'),
        ('DataDisplacement','<H=0'),
        ('SetupCount','<B'),
        ('Reserved2','<B=0'),
        ('SetupLength','_-Setup','SetupCount*2'),
        ('Setup',':'),
    )

# TODO: We should merge these both. But this will require fixing
# the instances where this structure is used on the client side
class SMBTransaction_SData(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('Name','z'),
        ('Trans_ParametersLength','_-Trans_Parameters'),
        ('Trans_Parameters',':'),
        ('Trans_DataLength','_-Trans_Data'),
        ('Trans_Data',':'),
    )
    UnicodeStructure = (
        ('Pad','B'),
        ('Name','u'),
        ('Trans_ParametersLength','_-Trans_Parameters'),
        ('Trans_Parameters',':'),
        ('Trans_DataLength','_-Trans_Data'),
        ('Trans_Data',':'),
    )

class SMBTransaction_Data(Structure):
    structure = (
        ('NameLength','_-Name'),
        ('Name',':'),
        ('Trans_ParametersLength','_-Trans_Parameters'),
        ('Trans_Parameters',':'),
        ('Trans_DataLength','_-Trans_Data'),
        ('Trans_Data',':'),
    )

class SMBTransactionResponse_Data(Structure):
    structure = (
        ('Trans_ParametersLength','_-Trans_Parameters'),
        ('Trans_Parameters',':'),
        ('Trans_DataLength','_-Trans_Data'),
        ('Trans_Data',':'),
    )

############# SMB_COM_READ_ANDX (0x2E)
class SMBReadAndX_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('Fid','<H'),
        ('Offset','<L'),
        ('MaxCount','<H'),
        ('MinCount','<H=MaxCount'),
        ('_reserved','<L=0x0'),
        ('Remaining','<H=MaxCount'),
        ('HighOffset','<L=0'),
    )

class SMBReadAndX_Parameters2(SMBAndXCommand_Parameters):
    structure = (
        ('Fid','<H'),
        ('Offset','<L'),
        ('MaxCount','<H'),
        ('MinCount','<H=MaxCount'),
        ('_reserved','<L=0xffffffff'),
        ('Remaining','<H=MaxCount'),
    )

class SMBReadAndXResponse_Parameters(SMBAndXCommand_Parameters):
    structure = (
        ('Remaining','<H=0'),
        ('DataMode','<H=0'),
        ('_reserved','<H=0'),
        ('DataCount','<H'),
        ('DataOffset','<H'),
        ('DataCount_Hi','<L'),
        ('_reserved2','6s=""'),
    )

############# SMB_COM_ECHO (0x2B)
class SMBEcho_Data(Structure):
    structure = (
        ('Data',':'),
    )

class SMBEcho_Parameters(Structure):
    structure = (
        ('EchoCount','<H'),
    )

class SMBEchoResponse_Data(Structure):
    structure = (
        ('Data',':'),
    )

class SMBEchoResponse_Parameters(Structure):
    structure = (
        ('SequenceNumber','<H=1'),
    )

############# SMB_COM_QUERY_INFORMATION_DISK (0x80)
class SMBQueryInformationDiskResponse_Parameters(Structure):
    structure = (
        ('TotalUnits','<H'),
        ('BlocksPerUnit','<H'),
        ('BlockSize','<H'),
        ('FreeUnits','<H'),
        ('Reserved','<H=0'),
    )


############# SMB_COM_LOGOFF_ANDX (0x74)
class SMBLogOffAndX(SMBAndXCommand_Parameters):
    strucure = ()

############# SMB_COM_CLOSE (0x04)
class SMBClose_Parameters(SMBCommand_Parameters):
   structure = (
        ('FID','<H'),
        ('Time','<L=0'),
   )

############# SMB_COM_FLUSH (0x05)
class SMBFlush_Parameters(SMBCommand_Parameters):
   structure = (
        ('FID','<H'),
   )

############# SMB_COM_CREATE_DIRECTORY (0x00)
class SMBCreateDirectory_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('BufferFormat','<B=4'),
        ('DirectoryName','z'),
    )
    UnicodeStructure = (
        ('BufferFormat','<B=4'),
        ('DirectoryName','u'),
    )

############# SMB_COM_DELETE (0x06)
class SMBDelete_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('BufferFormat','<B=4'),
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('BufferFormat','<B=4'),
        ('FileName','u'),
    )

class SMBDelete_Parameters(Structure):
    structure = (
        ('SearchAttributes','<H'),
    )

############# SMB_COM_DELETE_DIRECTORY (0x01)
class SMBDeleteDirectory_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('BufferFormat','<B=4'),
        ('DirectoryName','z'),
    )
    UnicodeStructure = (
        ('BufferFormat','<B=4'),
        ('DirectoryName','u'),
    )

############# SMB_COM_CHECK_DIRECTORY (0x10)
class SMBCheckDirectory_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('BufferFormat','<B=4'),
        ('DirectoryName','z'),
    )
    UnicodeStructure = (
        ('BufferFormat','<B=4'),
        ('DirectoryName','u'),
    )

############# SMB_COM_RENAME (0x07)
class SMBRename_Parameters(SMBCommand_Parameters):
    structure = (
        ('SearchAttributes','<H'),
    )

class SMBRename_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('BufferFormat1','<B=4'),
        ('OldFileName','z'),
        ('BufferFormat2','<B=4'),
        ('NewFileName','z'),
    )
    UnicodeStructure = (
        ('BufferFormat1','<B=4'),
        ('OldFileName','u'),
        ('BufferFormat2','<B=4'),
        ('Pad','B=0'),
        ('NewFileName','u'),
    )


############# SMB_COM_OPEN (0x02)
class SMBOpen_Parameters(SMBCommand_Parameters):
    structure = (
        ('DesiredAccess','<H=0'),
        ('SearchAttributes','<H=0'),
    )

class SMBOpen_Data(AsciiOrUnicodeStructure):
    AsciiStructure = (
        ('FileNameFormat','"\x04'),
        ('FileName','z'),
    )
    UnicodeStructure = (
        ('FileNameFormat','"\x04'),
        ('FileName','u'),
    )

class SMBOpenResponse_Parameters(SMBCommand_Parameters):
    structure = (
        ('Fid','<H=0'),
        ('FileAttributes','<H=0'),
        ('LastWriten','<L=0'),
        ('FileSize','<L=0'),
        ('GrantedAccess','<H=0'),
    )

############# EXTENDED SECURITY CLASSES
class SMBExtended_Security_Parameters(Structure):
    structure = (
        ('DialectIndex','<H'),
        ('SecurityMode','<B'),
        ('MaxMpxCount','<H'),
        ('MaxNumberVcs','<H'),
        ('MaxBufferSize','<L'),
        ('MaxRawSize','<L'),
        ('SessionKey','<L'),
        ('Capabilities','<L'),
        ('LowDateTime','<L'),
        ('HighDateTime','<L'),
        ('ServerTimeZone','<H'),
        ('ChallengeLength','<B'),
    )

class SMBExtended_Security_Data(Structure):
    structure = (
        ('ServerGUID','16s'),
        ('SecurityBlob',':'),
    )

class SMBNTLMDialect_Parameters(Structure):
    structure = (
        ('DialectIndex','<H'),
        ('SecurityMode','<B'),
        ('MaxMpxCount','<H'),
        ('MaxNumberVcs','<H'),
        ('MaxBufferSize','<L'),
        ('MaxRawSize','<L'),
        ('SessionKey','<L'),
        ('Capabilities','<L'),
        ('LowDateTime','<L'),
        ('HighDateTime','<L'),
        ('ServerTimeZone','<H'),
        ('ChallengeLength','<B'),
    )

class SMBNTLMDialect_Data(Structure):
    structure = (
        ('ChallengeLength','_-Challenge','self["ChallengeLength"]'),
        ('Challenge',':'),
        ('Payload',':'),
# For some reason on an old Linux this field is not present, we have to check this out. There must be a flag stating this.
        ('DomainName','_'),
        ('ServerName','_'),
    )
    def __init__(self,data = None, alignment = 0):
         Structure.__init__(self,data,alignment)
         #self['ChallengeLength']=8

    def fromString(self,data):
        Structure.fromString(self,data)
        self['DomainName'] = ''
        self['ServerName'] = ''

class SMB:
    # SMB Command Codes
    SMB_COM_CREATE_DIRECTORY                = 0x00
    SMB_COM_DELETE_DIRECTORY                = 0x01
    SMB_COM_OPEN                            = 0x02
    SMB_COM_CREATE                          = 0x03
    SMB_COM_CLOSE                           = 0x04
    SMB_COM_FLUSH                           = 0x05
    SMB_COM_DELETE                          = 0x06
    SMB_COM_RENAME                          = 0x07
    SMB_COM_QUERY_INFORMATION               = 0x08
    SMB_COM_SET_INFORMATION                 = 0x09
    SMB_COM_READ                            = 0x0A
    SMB_COM_WRITE                           = 0x0B
    SMB_COM_LOCK_BYTE_RANGE                 = 0x0C
    SMB_COM_UNLOCK_BYTE_RANGE               = 0x0D
    SMB_COM_CREATE_TEMPORARY                = 0x0E
    SMB_COM_CREATE_NEW                      = 0x0F
    SMB_COM_CHECK_DIRECTORY                 = 0x10
    SMB_COM_PROCESS_EXIT                    = 0x11
    SMB_COM_SEEK                            = 0x12
    SMB_COM_LOCK_AND_READ                   = 0x13
    SMB_COM_WRITE_AND_UNLOCK                = 0x14
    SMB_COM_READ_RAW                        = 0x1A
    SMB_COM_READ_MPX                        = 0x1B
    SMB_COM_READ_MPX_SECONDARY              = 0x1C
    SMB_COM_WRITE_RAW                       = 0x1D
    SMB_COM_WRITE_MPX                       = 0x1E
    SMB_COM_WRITE_MPX_SECONDARY             = 0x1F
    SMB_COM_WRITE_COMPLETE                  = 0x20
    SMB_COM_QUERY_SERVER                    = 0x21
    SMB_COM_SET_INFORMATION2                = 0x22
    SMB_COM_QUERY_INFORMATION2              = 0x23
    SMB_COM_LOCKING_ANDX                    = 0x24
    SMB_COM_TRANSACTION                     = 0x25
    SMB_COM_TRANSACTION_SECONDARY           = 0x26
    SMB_COM_IOCTL                           = 0x27
    SMB_COM_IOCTL_SECONDARY                 = 0x28
    SMB_COM_COPY                            = 0x29
    SMB_COM_MOVE                            = 0x2A
    SMB_COM_ECHO                            = 0x2B
    SMB_COM_WRITE_AND_CLOSE                 = 0x2C
    SMB_COM_OPEN_ANDX                       = 0x2D
    SMB_COM_READ_ANDX                       = 0x2E
    SMB_COM_WRITE_ANDX                      = 0x2F
    SMB_COM_NEW_FILE_SIZE                   = 0x30
    SMB_COM_CLOSE_AND_TREE_DISC             = 0x31
    SMB_COM_TRANSACTION2                    = 0x32
    SMB_COM_TRANSACTION2_SECONDARY          = 0x33
    SMB_COM_FIND_CLOSE2                     = 0x34
    SMB_COM_FIND_NOTIFY_CLOSE               = 0x35
    # Used by Xenix/Unix 0x60 - 0x6E 
    SMB_COM_TREE_CONNECT                    = 0x70
    SMB_COM_TREE_DISCONNECT                 = 0x71
    SMB_COM_NEGOTIATE                       = 0x72
    SMB_COM_SESSION_SETUP_ANDX              = 0x73
    SMB_COM_LOGOFF_ANDX                     = 0x74
    SMB_COM_TREE_CONNECT_ANDX               = 0x75
    SMB_COM_QUERY_INFORMATION_DISK          = 0x80
    SMB_COM_SEARCH                          = 0x81
    SMB_COM_FIND                            = 0x82
    SMB_COM_FIND_UNIQUE                     = 0x83
    SMB_COM_FIND_CLOSE                      = 0x84
    SMB_COM_NT_TRANSACT                     = 0xA0
    SMB_COM_NT_TRANSACT_SECONDARY           = 0xA1
    SMB_COM_NT_CREATE_ANDX                  = 0xA2
    SMB_COM_NT_CANCEL                       = 0xA4
    SMB_COM_NT_RENAME                       = 0xA5
    SMB_COM_OPEN_PRINT_FILE                 = 0xC0
    SMB_COM_WRITE_PRINT_FILE                = 0xC1
    SMB_COM_CLOSE_PRINT_FILE                = 0xC2
    SMB_COM_GET_PRINT_QUEUE                 = 0xC3
    SMB_COM_READ_BULK                       = 0xD8
    SMB_COM_WRITE_BULK                      = 0xD9
    SMB_COM_WRITE_BULK_DATA                 = 0xDA

    # TRANSACT codes
    TRANS_TRANSACT_NMPIPE                   = 0x26

    # TRANSACT2 codes
    TRANS2_FIND_FIRST2                      = 0x0001
    TRANS2_FIND_NEXT2                       = 0x0002
    TRANS2_QUERY_FS_INFORMATION             = 0x0003
    TRANS2_QUERY_PATH_INFORMATION           = 0x0005
    TRANS2_QUERY_FILE_INFORMATION           = 0x0007
    TRANS2_SET_FILE_INFORMATION             = 0x0008
    TRANS2_SET_PATH_INFORMATION             = 0x0006

    # Security Share Mode (Used internally by SMB class)
    SECURITY_SHARE_MASK                     = 0x01
    SECURITY_SHARE_SHARE                    = 0x00
    SECURITY_SHARE_USER                     = 0x01
    SECURITY_SIGNATURES_ENABLED             = 0X04
    SECURITY_SIGNATURES_REQUIRED            = 0X08

    # Security Auth Mode (Used internally by SMB class)
    SECURITY_AUTH_MASK                      = 0x02
    SECURITY_AUTH_ENCRYPTED                 = 0x02
    SECURITY_AUTH_PLAINTEXT                 = 0x00

    # Raw Mode Mask (Used internally by SMB class. Good for dialect up to and including LANMAN2.1)
    RAW_READ_MASK                           = 0x01
    RAW_WRITE_MASK                          = 0x02

    # Capabilities Mask (Used internally by SMB class. Good for dialect NT LM 0.12)
    CAP_RAW_MODE                            = 0x00000001
    CAP_MPX_MODE                            = 0x0002
    CAP_UNICODE                             = 0x0004
    CAP_LARGE_FILES                         = 0x0008
    CAP_EXTENDED_SECURITY                   = 0x80000000
    CAP_USE_NT_ERRORS                       = 0x40
    CAP_NT_SMBS                             = 0x10
    CAP_LARGE_READX                         = 0x00004000
    CAP_LARGE_WRITEX                        = 0x00008000
    CAP_RPC_REMOTE_APIS                     = 0x20

    # Flags1 Mask
    FLAGS1_LOCK_AND_READ_OK                 = 0x01
    FLAGS1_PATHCASELESS                     = 0x08
    FLAGS1_CANONICALIZED_PATHS              = 0x10
    FLAGS1_REPLY                            = 0x80

    # Flags2 Mask
    FLAGS2_LONG_NAMES                       = 0x0001
    FLAGS2_EAS                              = 0x0002
    FLAGS2_SMB_SECURITY_SIGNATURE           = 0x0004
    FLAGS2_IS_LONG_NAME                     = 0x0040
    FLAGS2_DFS                              = 0x1000
    FLAGS2_PAGING_IO                        = 0x2000
    FLAGS2_NT_STATUS                        = 0x4000
    FLAGS2_UNICODE                          = 0x8000
    FLAGS2_COMPRESSED                       = 0x0008
    FLAGS2_SMB_SECURITY_SIGNATURE_REQUIRED  = 0x0010
    FLAGS2_EXTENDED_SECURITY                = 0x0800

    # Dialect's Security Mode flags
    NEGOTIATE_USER_SECURITY                 = 0x01
    NEGOTIATE_ENCRYPT_PASSWORDS             = 0x02
    NEGOTIATE_SECURITY_SIGNATURE_ENABLE     = 0x04
    NEGOTIATE_SECURITY_SIGNATURE_REQUIRED   = 0x08

    # Tree Connect AndX Response optionalSuppor flags
    SMB_SUPPORT_SEARCH_BITS                 = 0x01
    SMB_SHARE_IS_IN_DFS                     = 0x02

    def __init__(self, remote_name, remote_host, my_name=None, host_type=nmb.TYPE_SERVER, sess_port=445, timeout=None,
                 UDP=0, session=None, negPacket=None):
        # The uid attribute will be set when the client calls the login() method
        self._uid = 0
        self.__server_name = ''
        self.__client_name = ''
        self.__server_os = ''
        self.__server_os_major = None
        self.__server_os_minor = None
        self.__server_os_build = None
        self.__server_lanman = ''
        self.__server_domain = ''
        self.__server_dns_domain_name = ''
        self.__remote_name = remote_name.upper()
        self.__remote_host = remote_host
        self.__isNTLMv2 = True
        self._dialects_parameters = None
        self._dialects_data = None
        self._doKerberos = False

        # Credentials
        self.__userName = b''
        self.__password = b''
        self.__domain   = b''
        self.__lmhash   = b''
        self.__nthash   = b''
        self.__aesKey   = b''
        self.__kdc      = b''
        self.__TGT      = None
        self.__TGS      = None

        # Negotiate Protocol Result, used everywhere
        # Could be extended or not, flags should be checked before 
        self._dialect_data = 0
        self._dialect_parameters = 0
        self._action = 0
        self._sess = None
        self.encrypt_passwords = True
        self.tid = 0
        self.fid = 0

        # Signing stuff
        self._SignSequenceNumber = 0
        self._SigningSessionKey = b''
        self._SigningChallengeResponse = b''
        self._SignatureEnabled = False
        self._SignatureVerificationEnabled = False
        self._SignatureRequired = False

        # Base flags (default flags, can be overridden using set_flags())
        self.__flags1 = SMB.FLAGS1_PATHCASELESS | SMB.FLAGS1_CANONICALIZED_PATHS
        self.__flags2 = SMB.FLAGS2_EXTENDED_SECURITY | SMB.FLAGS2_NT_STATUS | SMB.FLAGS2_LONG_NAMES

        if timeout is None:
            self.__timeout = 60
        else:
            self.__timeout = timeout

        # If port 445 and the name sent is *SMBSERVER we're setting the name to the IP. 
        # This is to help some old applications still believing 
        # *SMSBSERVER will work against modern OSes. If port is NETBIOS_SESSION_PORT the user better 
        # know about *SMBSERVER's limitations
        if sess_port == 445 and remote_name == '*SMBSERVER':
           self.__remote_name = remote_host

        # This is on purpose. I'm still not convinced to do a socket.gethostname() if not specified
        if my_name is None:
            self.__client_name = b''
        else:
            self.__client_name = my_name

        if session is None:
            if not my_name:
                # If destination port is 139 yes, there's some client disclosure
                my_name = socket.gethostname()
                i = my_name.find('.')
                if i > -1:
                    my_name = my_name[:i]

            if UDP:
                self._sess = nmb.NetBIOSUDPSession(my_name, remote_name, remote_host, host_type, sess_port, self.__timeout)
            else:
                self._sess = nmb.NetBIOSTCPSession(my_name, remote_name, remote_host, host_type, sess_port, self.__timeout)

                # Initialize session values (_dialect_data and _dialect_parameters)
                self.neg_session()

                # Call login() without any authentication information to 
                # setup a session if the remote server
                # is in share mode.
                if (self._dialects_parameters['SecurityMode'] & SMB.SECURITY_SHARE_MASK) == SMB.SECURITY_SHARE_SHARE:
                    self.login('', '')
        else:
            self._sess = session
            self.neg_session(negPacket = negPacket)
            # Call login() without any authentication information to 
            # setup a session if the remote server
            # is in share mode.
            if (self._dialects_parameters['SecurityMode'] & SMB.SECURITY_SHARE_MASK) == SMB.SECURITY_SHARE_SHARE:
                self.login('', '')

    @staticmethod
    def ntlm_supported():
        return False

    def getKerberos(self):
        return self._doKerberos

    def get_remote_name(self):
        return self.__remote_name

    def set_remote_name(self, name):
        self.__remote_name = name
        return True

    def get_remote_host(self):
        return self.__remote_host

    def get_flags(self):
        return self.__flags1, self.__flags2

    def set_flags(self, flags1=None, flags2=None):
        if flags1 is not None:
           self.__flags1 = flags1
        if flags2 is not None:
           self.__flags2 = flags2

    def set_timeout(self, timeout):
        prev_timeout = self.__timeout
        self.__timeout = timeout
        return prev_timeout

    def get_timeout(self):
        return self.__timeout

    @contextmanager
    def use_timeout(self, timeout):
        prev_timeout = self.set_timeout(timeout)
        try:
            yield
        finally:
            self.set_timeout(prev_timeout)

    def get_session(self):
        return self._sess

    def get_tid(self):
        return self.tid

    def get_fid(self):
        return self.fid

    def isGuestSession(self):
        return self._action & SMB_SETUP_GUEST

    def doesSupportNTLMv2(self):
        return self.__isNTLMv2

    def close_session(self):
        if self._sess:
            self._sess.close()
            self._sess = None

    def recvSMB(self):
        r = self._sess.recv_packet(self.__timeout)
        return NewSMBPacket(data = r.get_trailer())

    @staticmethod
    def __decode_trans(params, data):
        totparamcnt, totdatacnt, _, paramcnt, paramoffset, paramds, datacnt, dataoffset, datads, setupcnt = unpack('<HHHHHHHHHB', params[:19])
        if paramcnt + paramds < totparamcnt or datacnt + datads < totdatacnt:
            has_more = 1
        else:
            has_more = 0
        paramoffset = paramoffset - 55 - setupcnt * 2
        dataoffset = dataoffset - 55 - setupcnt * 2
        return has_more, params[20:20 + setupcnt * 2], data[paramoffset:paramoffset + paramcnt], data[dataoffset:dataoffset + datacnt]

    # TODO: Move this to NewSMBPacket, it belongs there
    def signSMB(self, packet, signingSessionKey, signingChallengeResponse):
        # This logic MUST be applied for messages sent in response to any of the higher-layer actions and in
        # compliance with the message sequencing rules.
        #  * The client or server that sends the message MUST provide the 32-bit sequence number for this
        #    message, as specified in sections 3.2.4.1 and 3.3.4.1.
        #  * The SMB_FLAGS2_SMB_SECURITY_SIGNATURE flag in the header MUST be set.
        #  * To generate the signature, a 32-bit sequence number is copied into the 
        #    least significant 32 bits of the SecuritySignature field and the remaining 
        #    4 bytes are set to 0x00.
        #  * The MD5 algorithm, as specified in [RFC1321], MUST be used to generate a hash of the SMB
        #    message from the start of the SMB Header, which is defined as follows.
        #    CALL MD5Init( md5context )
        #    CALL MD5Update( md5context, Connection.SigningSessionKey )
        #    CALL MD5Update( md5context, Connection.SigningChallengeResponse )
        #    CALL MD5Update( md5context, SMB message )
        #    CALL MD5Final( digest, md5context )
        #    SET signature TO the first 8 bytes of the digest
        # The resulting 8-byte signature MUST be copied into the SecuritySignature field of the SMB Header,
        # after which the message can be transmitted.

        #print "seq(%d) signingSessionKey %r, signingChallengeResponse %r" % (self._SignSequenceNumber, signingSessionKey, signingChallengeResponse)
        packet['SecurityFeatures'] = pack('<q',self._SignSequenceNumber)
        # Sign with the sequence
        m = hashlib.md5()
        m.update( signingSessionKey )
        m.update( signingChallengeResponse )
        m.update( packet.getData() )
        # Replace sequence with acual hash
        packet['SecurityFeatures'] = m.digest()[:8]
        if self._SignatureVerificationEnabled:
           self._SignSequenceNumber +=1
        else:
           self._SignSequenceNumber +=2

    def checkSignSMB(self, packet, signingSessionKey, signingChallengeResponse):
        # Let's check
        signature = packet['SecurityFeatures']
        #print "Signature received: %r " % signature
        self.signSMB(packet, signingSessionKey, signingChallengeResponse)
        #print "Signature calculated: %r" % packet['SecurityFeatures']
        if self._SignatureVerificationEnabled is not True:
           self._SignSequenceNumber -= 1
        return packet['SecurityFeatures'] == signature

    def sendSMB(self,smb):
        smb['Uid'] = self._uid
        #At least on AIX, PIDs can exceed 16 bits, so we mask them out
        smb['Pid'] = (os.getpid() & 0xFFFF)
        # set flags
        smb['Flags1'] |= self.__flags1
        smb['Flags2'] |= self.__flags2
        if self._SignatureEnabled:
            smb['Flags2'] |= SMB.FLAGS2_SMB_SECURITY_SIGNATURE
            self.signSMB(smb, self._SigningSessionKey, self._SigningChallengeResponse)

        self._sess.send_packet(smb.getData())

    @staticmethod
    def isValidAnswer(s, cmd):
        while 1:
            if s.rawData():
                if s.get_command() == cmd:
                    if s.get_error_class() == 0x00 and s.get_error_code() == 0x00:
                        return 1
                    else:
                        raise SessionError( "SMB Library Error", s.get_error_class()+ (s.get_reserved() << 8), s.get_error_code() , s.get_flags2() & SMB.FLAGS2_NT_STATUS)
                else:
                    break
        return 0

    def neg_session(self, extended_security = True, negPacket = None):
        def parsePacket(smb):
            # If server speaks Unicode, let's set that flag from now on
            if smb['Flags2'] & SMB.FLAGS2_UNICODE:
                self.__flags2 |= SMB.FLAGS2_UNICODE

            if smb.isValidAnswer(SMB.SMB_COM_NEGOTIATE):
                sessionResponse = SMBCommand(smb['Data'][0])
                self._dialects_parameters = SMBNTLMDialect_Parameters(sessionResponse['Parameters'])
                self._dialects_data = SMBNTLMDialect_Data()
                self._dialects_data['ChallengeLength'] = self._dialects_parameters['ChallengeLength']
                self._dialects_data.fromString(sessionResponse['Data'])
                if self._dialects_parameters['Capabilities'] & SMB.CAP_EXTENDED_SECURITY:
                    # Whether we choose it or it is enforced by the server, we go for extended security
                    self._dialects_parameters = SMBExtended_Security_Parameters(sessionResponse['Parameters'])
                    self._dialects_data = SMBExtended_Security_Data(sessionResponse['Data'])
                    # Let's setup some variable for later use
                    if self._dialects_parameters['SecurityMode'] & SMB.SECURITY_SIGNATURES_REQUIRED:
                         self._SignatureRequired = True

                    # Interestingly, the security Blob might be missing sometimes.
                    #spnego = SPNEGO_NegTokenInit(self._dialects_data['SecurityBlob'])
                    #for i in spnego['MechTypes']:
                    #      print "Mech Found: %s" % MechTypes[i]
                    return 1

                # If not, let's try the old way
                else:
                    if self._dialects_data['ServerName'] is not None:
                        self.__server_name = self._dialects_data['ServerName']

                    if self._dialects_parameters['DialectIndex'] == 0xffff:
                        raise UnsupportedFeature("Remote server does not know NT LM 0.12")
                    return 1
            else:
                return 0

        if negPacket is None:
            smb = NewSMBPacket()
            negSession = SMBCommand(SMB.SMB_COM_NEGOTIATE)
            flags2 = self.get_flags()[1]
            if extended_security is True:
                self.set_flags(flags2=flags2|SMB.FLAGS2_EXTENDED_SECURITY)
            else:
                self.set_flags(flags2=flags2 & (~SMB.FLAGS2_EXTENDED_SECURITY))

            negSession['Data'] = b'\x02NT LM 0.12\x00'
            smb.addCommand(negSession)
            self.sendSMB(smb)

            while 1:
                smb = self.recvSMB()
                return parsePacket(smb)
        else:

            return parsePacket( NewSMBPacket( data = negPacket))

    def tree_connect(self, path, password = '', service = SERVICE_ANY):
        LOG.warning("[MS-CIFS] This is an original Core Protocol command.This command has been deprecated.Client Implementations SHOULD use SMB_COM_TREE_CONNECT_ANDX")

        # return 0x800
        if password:
            # Password is only encrypted if the server passed us an "encryption" during protocol dialect
            if self._dialects_parameters['ChallengeLength'] > 0:
                # this code is untested
                password = self.get_ntlmv1_response(ntlm.compute_lmhash(password))

        if not unicode_support:
            if unicode_convert:
                path = str(path)
            else:
                raise Exception('SMB: Can\t conver path from unicode!')

        smb = NewSMBPacket()
        treeConnect = SMBCommand(SMB.SMB_COM_TREE_CONNECT)
        treeConnect['Parameters'] = SMBTreeConnect_Parameters()
        treeConnect['Data']       = SMBTreeConnect_Data()
        treeConnect['Data']['Path'] = path.upper()
        treeConnect['Data']['Password'] = password
        treeConnect['Data']['Service'] = service
        smb.addCommand(treeConnect)
        self.sendSMB(smb)

        while 1:
            smb = self.recvSMB()
            if smb.isValidAnswer(SMB.SMB_COM_TREE_CONNECT):
                # XXX Here we are ignoring the rest of the response
                return smb['Tid']
            return smb['Tid']

    def get_uid(self):
        return self._uid

    def set_uid(self, uid):
        self._uid = uid

    def tree_connect_andx(self, path, password = None, service = SERVICE_ANY, smb_packet=None):
        if password:
            # Password is only encrypted if the server passed us an "encryption" during protocol dialect
            if self._dialects_parameters['ChallengeLength'] > 0:
                # this code is untested
                password = self.get_ntlmv1_response(ntlm.compute_lmhash(password))
        else:
            password = '\x00'

        if not unicode_support:
            if unicode_convert:
                path = str(path)
            else:
                raise Exception('SMB: Can\t convert path from unicode!')

        if smb_packet is None:
            smb = NewSMBPacket()
        else:
            smb = smb_packet

        # Just in case this came with the full path ,let's just leave 
        # the sharename, we'll take care of the rest

        share = path.split('\\')[-1]
        try:
            _, _, _, _, sockaddr = socket.getaddrinfo(self.get_remote_host(), 80, 0, 0, socket.IPPROTO_TCP)[0]
            remote_host = sockaddr[0]
        except Exception:
            remote_host =  self.get_remote_host()

        path = '\\\\' + remote_host + '\\' +share
        path = path.upper().encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else path

        treeConnect = SMBCommand(SMB.SMB_COM_TREE_CONNECT_ANDX)
        treeConnect['Parameters'] = SMBTreeConnectAndX_Parameters()
        treeConnect['Data']       = SMBTreeConnectAndX_Data(flags=self.__flags2)
        treeConnect['Parameters']['PasswordLength'] = len(password)
        treeConnect['Data']['Password'] = password
        treeConnect['Data']['Path'] = path
        treeConnect['Data']['Service'] = service

        if self.__flags2 & SMB.FLAGS2_UNICODE:
            treeConnect['Data']['Pad'] = 0x0

        smb.addCommand(treeConnect)

        # filename = "\PIPE\epmapper"

        # ntCreate = SMBCommand(SMB.SMB_COM_NT_CREATE_ANDX)
        # ntCreate['Parameters'] = SMBNtCreateAndX_Parameters()
        # ntCreate['Data']       = SMBNtCreateAndX_Data()
        # ntCreate['Parameters']['FileNameLength'] = len(filename)
        # ntCreate['Parameters']['CreateFlags'] = 0
        # ntCreate['Parameters']['AccessMask'] = 0x3
        # ntCreate['Parameters']['CreateOptions'] = 0x0
        # ntCreate['Data']['FileName'] = filename

        # smb.addCommand(ntCreate)
        self.sendSMB(smb)

        while 1:
            smb = self.recvSMB()
            if smb.isValidAnswer(SMB.SMB_COM_TREE_CONNECT_ANDX):
                # XXX Here we are ignoring the rest of the response
                self.tid = smb['Tid']
                return self.tid
            self.tid = smb['Tid']
            return self.tid

    # backwars compatibility
    connect_tree = tree_connect_andx

    @staticmethod
    def getDialect():
        return SMB_DIALECT

    def get_server_name(self):
        #return self._dialects_data['ServerName']
        return self.__server_name

    def get_client_name(self):
        return self.__client_name

    def get_session_key(self):
        return self._SigningSessionKey

    def set_session_key(self, key):
        self._SignatureEnabled = True
        self._SignSequenceNumber = 2
        self._SigningSessionKey = key

    def get_encryption_key(self):
        if 'Challenge' in self._dialects_data.fields:
            return self._dialects_data['Challenge']
        else:
            return None

    def get_server_time(self):
        timestamp = self._dialects_parameters['HighDateTime']
        timestamp <<= 32
        timestamp |= self._dialects_parameters['LowDateTime']
        timestamp -= 116444736000000000
        timestamp //= 10000000
        d = datetime.datetime.utcfromtimestamp(timestamp)
        return d.strftime("%a, %d %b %Y %H:%M:%S GMT")

    def disconnect_tree(self, tid):
        smb = NewSMBPacket()
        smb['Tid']  = tid

        smb.addCommand(SMBCommand(SMB.SMB_COM_TREE_DISCONNECT))

        self.sendSMB(smb)
        self.recvSMB()

    def open(self, tid, filename, open_mode, desired_access):
        filename = filename.replace('/', '\\')
        filename = filename.encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else filename

        smb = NewSMBPacket()
        smb['Tid']    = tid

        openFile = SMBCommand(SMB.SMB_COM_OPEN)
        openFile['Parameters'] = SMBOpen_Parameters()
        openFile['Parameters']['DesiredAccess']    = desired_access
        openFile['Parameters']['OpenMode']         = open_mode
        openFile['Parameters']['SearchAttributes'] = ATTR_READONLY | ATTR_HIDDEN | ATTR_ARCHIVE
        openFile['Data']       = SMBOpen_Data(flags=self.__flags2)
        openFile['Data']['FileName'] = filename

        smb.addCommand(openFile)

        self.sendSMB(smb)

        smb = self.recvSMB()
        if smb.isValidAnswer(SMB.SMB_COM_OPEN):
            # XXX Here we are ignoring the rest of the response
            openFileResponse   = SMBCommand(smb['Data'][0])
            openFileParameters = SMBOpenResponse_Parameters(openFileResponse['Parameters'])

            return (
                openFileParameters['Fid'],
                openFileParameters['FileAttributes'],
                openFileParameters['LastWriten'],
                openFileParameters['FileSize'],
                openFileParameters['GrantedAccess'],
            )

    def open_andx(self, tid, filename, open_mode, desired_access):
        filename = filename.replace('/', '\\')
        filename = filename.encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else filename

        smb = NewSMBPacket()
        smb['Tid']    = tid

        openFile = SMBCommand(SMB.SMB_COM_OPEN_ANDX)
        openFile['Parameters'] = SMBOpenAndX_Parameters()
        openFile['Parameters']['DesiredAccess']    = desired_access
        openFile['Parameters']['OpenMode']         = open_mode
        openFile['Parameters']['SearchAttributes'] = ATTR_READONLY | ATTR_HIDDEN | ATTR_ARCHIVE
        openFile['Data']       = SMBOpenAndX_Data(flags=self.__flags2)
        openFile['Data']['FileName'] = filename

        if self.__flags2 & SMB.FLAGS2_UNICODE:
            openFile['Data']['Pad'] = 0x0

        smb.addCommand(openFile)

        self.sendSMB(smb)

        smb = self.recvSMB()
        if smb.isValidAnswer(SMB.SMB_COM_OPEN_ANDX):
            # XXX Here we are ignoring the rest of the response
            openFileResponse   = SMBCommand(smb['Data'][0])
            openFileParameters = SMBOpenAndXResponse_Parameters(openFileResponse['Parameters'])

            return (
                openFileParameters['Fid'],
                openFileParameters['FileAttributes'],
                openFileParameters['LastWriten'],
                openFileParameters['FileSize'],
                openFileParameters['GrantedAccess'],
                openFileParameters['FileType'],
                openFileParameters['IPCState'],
                openFileParameters['Action'],
                openFileParameters['ServerFid'],
            )

    def close(self, tid, fid):
        smb = NewSMBPacket()
        smb['Tid']    = tid

        closeFile = SMBCommand(SMB.SMB_COM_CLOSE)
        closeFile['Parameters'] = SMBClose_Parameters()
        closeFile['Parameters']['FID']    = fid
        smb.addCommand(closeFile)

        self.sendSMB(smb)
        smb = self.recvSMB()
        if smb.isValidAnswer(SMB.SMB_COM_CLOSE):
           return 1
        return 0

    def send_trans(self, tid, setup, name, param, data, noAnswer = 0):
        smb = NewSMBPacket()
        smb['Tid']    = tid

        transCommand = SMBCommand(SMB.SMB_COM_TRANSACTION)
        transCommand['Parameters'] = SMBTransaction_Parameters()
        transCommand['Data'] = SMBTransaction_Data()

        transCommand['Parameters']['Setup'] = setup
        transCommand['Parameters']['TotalParameterCount'] = len(param)
        transCommand['Parameters']['TotalDataCount'] = len(data)

        transCommand['Parameters']['ParameterCount'] = len(param)
        transCommand['Parameters']['ParameterOffset'] = 32+3+28+len(setup)+len(name)

        transCommand['Parameters']['DataCount'] = len(data)
        transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param)

        transCommand['Data']['Name'] = name
        transCommand['Data']['Trans_Parameters'] = param
        transCommand['Data']['Trans_Data'] = data

        if noAnswer:
           transCommand['Parameters']['Flags'] = TRANS_NO_RESPONSE

        smb.addCommand(transCommand)

        self.sendSMB(smb)

    def send_trans2(self, tid, setup, name, param, data):
        smb = NewSMBPacket()
        smb['Tid']    = tid

        command = pack('<H', setup)

        transCommand = SMBCommand(SMB.SMB_COM_TRANSACTION2)
        transCommand['Parameters'] = SMBTransaction2_Parameters()
        transCommand['Parameters']['MaxDataCount'] = self._dialects_parameters['MaxBufferSize']
        transCommand['Data'] = SMBTransaction2_Data()

        transCommand['Parameters']['Setup'] = command
        transCommand['Parameters']['TotalParameterCount'] = len(param)
        transCommand['Parameters']['TotalDataCount'] = len(data)

        if len(param) > 0:
            padLen = (4 - (32+2+28 + len(command)) % 4 ) % 4
            padBytes = '\xFF' * padLen
            transCommand['Data']['Pad1'] = padBytes
        else:
            transCommand['Data']['Pad1'] = ''
            padLen = 0

        transCommand['Parameters']['ParameterCount'] = len(param)
        transCommand['Parameters']['ParameterOffset'] = 32+2+28+len(command)+len(name) + padLen

        if len(data) > 0:
            pad2Len = (4 - (32+2+28 + len(command) + padLen + len(param)) % 4) % 4
            transCommand['Data']['Pad2'] = '\xFF' * pad2Len
        else:
            transCommand['Data']['Pad2'] = ''
            pad2Len = 0

        transCommand['Parameters']['DataCount'] = len(data)
        transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param) + pad2Len

        transCommand['Data']['Name'] = name
        transCommand['Data']['Trans_Parameters'] = param
        transCommand['Data']['Trans_Data'] = data
        smb.addCommand(transCommand)

        self.sendSMB(smb)

    def query_file_info(self, tid, fid, fileInfoClass = SMB_QUERY_FILE_STANDARD_INFO):
        self.send_trans2(tid, SMB.TRANS2_QUERY_FILE_INFORMATION, '\x00', pack('<HH', fid, fileInfoClass), '')

        resp = self.recvSMB()
        if resp.isValidAnswer(SMB.SMB_COM_TRANSACTION2):
            trans2Response = SMBCommand(resp['Data'][0])
            trans2Parameters = SMBTransaction2Response_Parameters(trans2Response['Parameters'])
            # Remove Potential Prefix Padding
            return trans2Response['Data'][-trans2Parameters['TotalDataCount']:]

    def __nonraw_retr_file(self, tid, fid, offset, datasize, callback):
        if (self._dialects_parameters['Capabilities'] & SMB.CAP_LARGE_READX) and self._SignatureEnabled is False:
            max_buf_size = 65000
        else:
            max_buf_size = self._dialects_parameters['MaxBufferSize'] & ~0x3ff  # Read in multiple KB blocks

        read_offset = offset
        while read_offset < datasize:
            data = self.read_andx(tid, fid, read_offset, max_buf_size)

            callback(data)
            read_offset += len(data)

    def __nonraw_stor_file(self, tid, fid, offset, datasize, callback):
        if (self._dialects_parameters['Capabilities'] & SMB.CAP_LARGE_WRITEX) and self._SignatureEnabled is False:
            max_buf_size = 65000
        else:
            max_buf_size = self._dialects_parameters['MaxBufferSize'] & ~0x3ff  # Write in multiple KB blocks

        write_offset = offset
        while 1:
            data = callback(max_buf_size)
            if not data:
                break

            smb = self.write_andx(tid,fid,data, write_offset)
            writeResponse   = SMBCommand(smb['Data'][0])
            writeResponseParameters = SMBWriteAndXResponse_Parameters(writeResponse['Parameters'])
            write_offset += writeResponseParameters['Count']

    def get_server_domain(self):
        return self.__server_domain

    def get_server_dns_domain_name(self):
        return self.__server_dns_domain_name

    def get_server_os(self):
        return self.__server_os

    def get_server_os_major(self):
        return self.__server_os_major

    def get_server_os_minor(self):
        return self.__server_os_minor

    def get_server_os_build(self):
        return self.__server_os_build

    def set_server_os(self, os):
        self.__server_os = os

    def get_server_lanman(self):
        return self.__server_lanman

    def is_login_required(self):
        # Login is required if share mode is user. 
        # Otherwise only public services or services in share mode
        # are allowed.
        return (self._dialects_parameters['SecurityMode'] & SMB.SECURITY_SHARE_MASK) == SMB.SECURITY_SHARE_USER

    def is_signing_required(self):
        return self._SignatureRequired

    def get_ntlmv1_response(self, key):
        challenge = self._dialects_data['Challenge']
        return ntlm.get_ntlmv1_response(key, challenge)

    def kerberos_login(self, user, password, domain = '', lmhash = '', nthash = '', aesKey = '', kdcHost = '', TGT=None, TGS=None):
        # Importing down here so pyasn1 is not required if kerberos is not used.
        from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set
        from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS
        from impacket.krb5 import constants
        from impacket.krb5.types import Principal, KerberosTime, Ticket
        from pyasn1.codec.der import decoder, encoder
        import datetime

        # login feature does not support unicode
        # disable it if enabled
        flags2 = self.__flags2
        if flags2 & SMB.FLAGS2_UNICODE:
            self.__flags2 = flags2 & (flags2 ^ SMB.FLAGS2_UNICODE)

        # If TGT or TGS are specified, they are in the form of:
        # TGS['KDC_REP'] = the response from the server
        # TGS['cipher'] = the cipher used
        # TGS['sessionKey'] = the sessionKey
        # If we have hashes, normalize them
        if lmhash != '' or nthash != '':
            if len(lmhash) % 2:
                lmhash = '0%s' % lmhash
            if len(nthash) % 2:
                nthash = '0%s' % nthash
            try: # just in case they were converted already
                lmhash = a2b_hex(lmhash)
                nthash = a2b_hex(nthash)
            except:
                pass

        self.__userName = user
        self.__password = password
        self.__domain   = domain
        self.__lmhash   = lmhash
        self.__nthash   = nthash
        self.__aesKey   = aesKey
        self.__kdc      = kdcHost
        self.__TGT      = TGT
        self.__TGS      = TGS
        self._doKerberos= True

        # First of all, we need to get a TGT for the user
        userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
        if TGT is None:
            if TGS is None:
                tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost)
        else:
            tgt = TGT['KDC_REP']
            cipher = TGT['cipher']
            sessionKey = TGT['sessionKey']

        # Now that we have the TGT, we should ask for a TGS for cifs

        if TGS is None:
            serverName = Principal('cifs/%s' % self.__remote_name, type=constants.PrincipalNameType.NT_SRV_INST.value)
            tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey)
        else:
            tgs = TGS['KDC_REP']
            cipher = TGS['cipher']
            sessionKey = TGS['sessionKey']

        smb = NewSMBPacket()

        # Are we required to sign SMB? If so we do it, if not we skip it
        if self._SignatureRequired:
           smb['Flags2'] |= SMB.FLAGS2_SMB_SECURITY_SIGNATURE


        sessionSetup = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX)
        sessionSetup['Parameters'] = SMBSessionSetupAndX_Extended_Parameters()
        sessionSetup['Data']       = SMBSessionSetupAndX_Extended_Data()

        sessionSetup['Parameters']['MaxBufferSize']        = 61440
        sessionSetup['Parameters']['MaxMpxCount']          = 2
        sessionSetup['Parameters']['VcNumber']             = 1
        sessionSetup['Parameters']['SessionKey']           = 0
        sessionSetup['Parameters']['Capabilities']         = SMB.CAP_EXTENDED_SECURITY | SMB.CAP_USE_NT_ERRORS | SMB.CAP_UNICODE | SMB.CAP_LARGE_READX | SMB.CAP_LARGE_WRITEX


        # Let's build a NegTokenInit with the NTLMSSP
        # TODO: In the future we should be able to choose different providers

        blob = SPNEGO_NegTokenInit()

        # Kerberos v5 mech
        blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]

        # Let's extract the ticket from the TGS
        tgs = decoder.decode(tgs, asn1Spec = TGS_REP())[0]
        ticket = Ticket()
        ticket.from_asn1(tgs['ticket'])

        # Now let's build the AP_REQ
        apReq = AP_REQ()
        apReq['pvno'] = 5
        apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)

        opts = list()
        apReq['ap-options'] = constants.encodeFlags(opts)
        seq_set(apReq,'ticket', ticket.to_asn1)

        authenticator = Authenticator()
        authenticator['authenticator-vno'] = 5
        authenticator['crealm'] = domain
        seq_set(authenticator, 'cname', userName.components_to_asn1)
        now = datetime.datetime.utcnow()

        authenticator['cusec'] = now.microsecond
        authenticator['ctime'] = KerberosTime.to_asn1(now)

        encodedAuthenticator = encoder.encode(authenticator)

        # Key Usage 11
        # AP-REQ Authenticator (includes application authenticator
        # subkey), encrypted with the application session key
        # (Section 5.5.1)
        encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None)

        apReq['authenticator'] = noValue
        apReq['authenticator']['etype'] = cipher.enctype
        apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator

        blob['MechToken'] = pack('B', ASN1_AID) + asn1encode(pack('B', ASN1_OID) + asn1encode(
            TypesMech['KRB5 - Kerberos 5']) + KRB5_AP_REQ + encoder.encode(apReq))

        sessionSetup['Parameters']['SecurityBlobLength']  = len(blob)
        sessionSetup['Parameters'].getData()
        sessionSetup['Data']['SecurityBlob']       = blob.getData()

        # Fake Data here, don't want to get us fingerprinted
        sessionSetup['Data']['NativeOS']      = 'Unix'
        sessionSetup['Data']['NativeLanMan']  = 'Samba'

        smb.addCommand(sessionSetup)
        self.sendSMB(smb)

        smb = self.recvSMB()
        if smb.isValidAnswer(SMB.SMB_COM_SESSION_SETUP_ANDX):
            # We will need to use this uid field for all future requests/responses
            self._uid = smb['Uid']

            # Now we have to extract the blob to continue the auth process
            sessionResponse   = SMBCommand(smb['Data'][0])
            sessionParameters = SMBSessionSetupAndX_Extended_Response_Parameters(sessionResponse['Parameters'])
            sessionData       = SMBSessionSetupAndX_Extended_Response_Data(flags = smb['Flags2'])
            sessionData['SecurityBlobLength'] = sessionParameters['SecurityBlobLength']
            sessionData.fromString(sessionResponse['Data'])

            self._action = sessionParameters['Action']
            # If smb sign required, let's enable it for the rest of the connection
            if self._dialects_parameters['SecurityMode'] & SMB.SECURITY_SIGNATURES_REQUIRED:
               self._SigningSessionKey = sessionKey.contents
               self._SignSequenceNumber = 2
               self._SignatureEnabled = True

            # restore unicode flag if needed
            if flags2 & SMB.FLAGS2_UNICODE:
                self.__flags2 |= SMB.FLAGS2_UNICODE

            return 1
        else:
            raise Exception('Error: Could not login successfully')

    def login_extended(self, user, password, domain = '', lmhash = '', nthash = '', use_ntlmv2 = True ):

        # login feature does not support unicode
        # disable it if enabled
        flags2 = self.__flags2
        if flags2 & SMB.FLAGS2_UNICODE:
            self.__flags2 = flags2 & (flags2 ^ SMB.FLAGS2_UNICODE)

        # Once everything's working we should join login methods into a single one
        smb = NewSMBPacket()
        # Are we required to sign SMB? If so we do it, if not we skip it
        if self._SignatureRequired:
           smb['Flags2'] |= SMB.FLAGS2_SMB_SECURITY_SIGNATURE

        sessionSetup = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX)
        sessionSetup['Parameters'] = SMBSessionSetupAndX_Extended_Parameters()
        sessionSetup['Data']       = SMBSessionSetupAndX_Extended_Data()

        sessionSetup['Parameters']['MaxBufferSize']        = 61440
        sessionSetup['Parameters']['MaxMpxCount']          = 2
        sessionSetup['Parameters']['VcNumber']             = 1
        sessionSetup['Parameters']['SessionKey']           = 0
        sessionSetup['Parameters']['Capabilities']         = SMB.CAP_EXTENDED_SECURITY | SMB.CAP_USE_NT_ERRORS | SMB.CAP_UNICODE | SMB.CAP_LARGE_READX | SMB.CAP_LARGE_WRITEX


        # Let's build a NegTokenInit with the NTLMSSP
        # TODO: In the future we should be able to choose different providers

        blob = SPNEGO_NegTokenInit()

        # NTLMSSP
        blob['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']]
        auth = ntlm.getNTLMSSPType1(self.get_client_name(),domain,self._SignatureRequired, use_ntlmv2 = use_ntlmv2)
        blob['MechToken'] = auth.getData()

        sessionSetup['Parameters']['SecurityBlobLength']  = len(blob)
        sessionSetup['Parameters'].getData()
        sessionSetup['Data']['SecurityBlob']       = blob.getData()

        # Fake Data here, don't want to get us fingerprinted
        sessionSetup['Data']['NativeOS']      = 'Unix'
        sessionSetup['Data']['NativeLanMan']  = 'Samba'

        smb.addCommand(sessionSetup)
        self.sendSMB(smb)

        smb = self.recvSMB()
        if smb.isValidAnswer(SMB.SMB_COM_SESSION_SETUP_ANDX):
            # We will need to use this uid field for all future requests/responses
            self._uid = smb['Uid']

            # Now we have to extract the blob to continue the auth process
            sessionResponse   = SMBCommand(smb['Data'][0])
            sessionParameters = SMBSessionSetupAndX_Extended_Response_Parameters(sessionResponse['Parameters'])
            sessionData       = SMBSessionSetupAndX_Extended_Response_Data(flags = smb['Flags2'])
            sessionData['SecurityBlobLength'] = sessionParameters['SecurityBlobLength']
            sessionData.fromString(sessionResponse['Data'])
            respToken = SPNEGO_NegTokenResp(sessionData['SecurityBlob'])

            # Let's parse some data and keep it to ourselves in case it is asked
            ntlmChallenge = ntlm.NTLMAuthChallenge(respToken['ResponseToken'])
            if ntlmChallenge['TargetInfoFields_len'] > 0:
                av_pairs = ntlm.AV_PAIRS(ntlmChallenge['TargetInfoFields'][:ntlmChallenge['TargetInfoFields_len']])
                if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None:
                   try:
                       self.__server_name = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode('utf-16le')
                   except UnicodeDecodeError:
                       # For some reason, we couldn't decode Unicode here.. silently discard the operation
                       pass
                if av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME] is not None:
                   try:
                       if self.__server_name != av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le'):
                           self.__server_domain = av_pairs[ntlm.NTLMSSP_AV_DOMAINNAME][1].decode('utf-16le')
                   except UnicodeDecodeError:
                       # For some reason, we couldn't decode Unicode here.. silently discard the operation
                       pass
                if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] is not None:
                   try:
                       self.__server_dns_domain_name = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode('utf-16le')
                   except UnicodeDecodeError:
                       # For some reason, we couldn't decode Unicode here.. silently discard the operation
                       pass

            # Parse Version to know the target Operating system name. Not provided elsewhere anymore
            if 'Version' in ntlmChallenge.fields:
                version = ntlmChallenge['Version']

                if len(version) >= 4:
                   self.__server_os_major, self.__server_os_minor, self.__server_os_build = unpack('<BBH',version[:4])

            type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, respToken['ResponseToken'], user, password, domain, lmhash, nthash, use_ntlmv2 = use_ntlmv2)

            if exportedSessionKey is not None:
                self._SigningSessionKey = exportedSessionKey

            smb = NewSMBPacket()

            # Are we required to sign SMB? If so we do it, if not we skip it
            if self._SignatureRequired:
               smb['Flags2'] |= SMB.FLAGS2_SMB_SECURITY_SIGNATURE

            respToken2 = SPNEGO_NegTokenResp()
            respToken2['ResponseToken'] = type3.getData()

            # Reusing the previous structure
            sessionSetup['Parameters']['SecurityBlobLength'] = len(respToken2)
            sessionSetup['Data']['SecurityBlob'] = respToken2.getData()

            # Storing some info for later use
            self.__server_os     = sessionData['NativeOS']
            self.__server_lanman = sessionData['NativeLanMan']

            smb.addCommand(sessionSetup)
            self.sendSMB(smb)

            smb = self.recvSMB()
            self._uid = 0
            if smb.isValidAnswer(SMB.SMB_COM_SESSION_SETUP_ANDX):
                self._uid = smb['Uid']
                sessionResponse   = SMBCommand(smb['Data'][0])
                sessionParameters = SMBSessionSetupAndXResponse_Parameters(sessionResponse['Parameters'])

                self._action = sessionParameters['Action']
                # If smb sign required, let's enable it for the rest of the connection
                if self._dialects_parameters['SecurityMode'] & SMB.SECURITY_SIGNATURES_REQUIRED:
                   self._SignSequenceNumber = 2
                   self._SignatureEnabled = True

                # restore unicode flag if needed
                if flags2 & SMB.FLAGS2_UNICODE:
                    self.__flags2 |= SMB.FLAGS2_UNICODE

                return 1
        else:
            raise Exception('Error: Could not login successfully')

    def getCredentials(self):
        return (
            self.__userName,
            self.__password,
            self.__domain,
            self.__lmhash,
            self.__nthash,
            self.__aesKey,
            self.__TGT,
            self.__TGS)

    def getIOCapabilities(self):
        res = dict()
        if (self._dialects_parameters['Capabilities'] & SMB.CAP_LARGE_READX) and self._SignatureEnabled is False:
            max_size = 65000
        else:
            max_size = self._dialects_parameters['MaxBufferSize'] # Read in multiple KB blocks
        res['MaxReadSize'] = max_size
        res['MaxWriteSize'] = max_size
        return res

    def login(self, user, password, domain = '', lmhash = '', nthash = '', ntlm_fallback = True):

        # If we have hashes, normalize them
        if lmhash != '' or nthash != '':
            if len(lmhash) % 2:
                lmhash = '0%s' % lmhash
            if len(nthash) % 2:
                nthash = '0%s' % nthash
            try: # just in case they were converted already
                lmhash = a2b_hex(lmhash)
                nthash = a2b_hex(nthash)
            except:
                pass

        self.__userName = user
        self.__password = password
        self.__domain   = domain
        self.__lmhash   = lmhash
        self.__nthash   = nthash
        self.__aesKey   = ''
        self.__TGT      = None
        self.__TGS      = None

        if self._dialects_parameters['Capabilities'] & SMB.CAP_EXTENDED_SECURITY:
            try:
                self.login_extended(user, password, domain, lmhash, nthash, use_ntlmv2 = True)
            except:
                # If the target OS is Windows 5.0 or Samba, let's try using NTLMv1
                if ntlm_fallback and ((self.get_server_lanman().find('Windows 2000') != -1) or (self.get_server_lanman().find('Samba') != -1)):
                    self.login_extended(user, password, domain, lmhash, nthash, use_ntlmv2 = False)
                    self.__isNTLMv2 = False
                else:
                    raise
        elif ntlm_fallback:
            self.login_standard(user, password, domain, lmhash, nthash)
            self.__isNTLMv2 = False
        else:
            raise SessionError('Cannot authenticate against target, enable ntlm_fallback')

    def login_standard(self, user, password, domain = '', lmhash = '', nthash = ''):

        # login feature does not support unicode
        # disable it if enabled
        flags2 = self.__flags2
        if flags2 & SMB.FLAGS2_UNICODE:
            self.__flags2 = flags2 & (flags2 ^ SMB.FLAGS2_UNICODE)

        # Only supports NTLMv1
        # Password is only encrypted if the server passed us an "encryption key" during protocol dialect negotiation
        if self._dialects_parameters['ChallengeLength'] > 0:
            if lmhash != '' or nthash != '':
               pwd_ansi = self.get_ntlmv1_response(lmhash)
               pwd_unicode = self.get_ntlmv1_response(nthash)
            elif password:
               lmhash = ntlm.compute_lmhash(password)
               nthash = ntlm.compute_nthash(password)
               pwd_ansi = self.get_ntlmv1_response(lmhash)
               pwd_unicode = self.get_ntlmv1_response(nthash)
            else: # NULL SESSION
               pwd_ansi = ''
               pwd_unicode = ''
        else:
            pwd_ansi = password
            pwd_unicode = ''

        smb = NewSMBPacket()

        sessionSetup = SMBCommand(SMB.SMB_COM_SESSION_SETUP_ANDX)
        sessionSetup['Parameters'] = SMBSessionSetupAndX_Parameters()
        sessionSetup['Data']       = SMBSessionSetupAndX_Data()

        sessionSetup['Parameters']['MaxBuffer']        = 61440
        sessionSetup['Parameters']['MaxMpxCount']      = 2
        sessionSetup['Parameters']['VCNumber']         = os.getpid() & 0xFFFF # Value has to be expressed in 2 bytes
        sessionSetup['Parameters']['SessionKey']       = self._dialects_parameters['SessionKey']
        sessionSetup['Parameters']['AnsiPwdLength']    = len(pwd_ansi)
        sessionSetup['Parameters']['UnicodePwdLength'] = len(pwd_unicode)
        sessionSetup['Parameters']['Capabilities']     = SMB.CAP_RAW_MODE | SMB.CAP_USE_NT_ERRORS | SMB.CAP_LARGE_READX | SMB.CAP_LARGE_WRITEX

        sessionSetup['Data']['AnsiPwd']       = pwd_ansi
        sessionSetup['Data']['UnicodePwd']    = pwd_unicode
        sessionSetup['Data']['Account']       = str(user)
        sessionSetup['Data']['PrimaryDomain'] = str(domain)
        sessionSetup['Data']['NativeOS']      = str(os.name)
        sessionSetup['Data']['NativeLanMan']  = 'pysmb'
        smb.addCommand(sessionSetup)

        self.sendSMB(smb)

        smb = self.recvSMB()
        if smb.isValidAnswer(SMB.SMB_COM_SESSION_SETUP_ANDX):
            # We will need to use this uid field for all future requests/responses
            self._uid = smb['Uid']
            sessionResponse   = SMBCommand(smb['Data'][0])
            sessionParameters = SMBSessionSetupAndXResponse_Parameters(sessionResponse['Parameters'])
            sessionData       = SMBSessionSetupAndXResponse_Data(flags = smb['Flags2'], data = sessionResponse['Data'])

            self._action = sessionParameters['Action']

            # Still gotta figure out how to do this with no EXTENDED_SECURITY
            if sessionParameters['Action'] & SMB_SETUP_USE_LANMAN_KEY == 0:
                 self._SigningChallengeResponse = sessionSetup['Data']['UnicodePwd']
                 self._SigningSessionKey = nthash
            else:
                 self._SigningChallengeResponse = sessionSetup['Data']['AnsiPwd']
                 self._SigningSessionKey = lmhash

            #self._SignSequenceNumber = 1
            #self.checkSignSMB(smb, self._SigningSessionKey ,self._SigningChallengeResponse)
            #self._SignatureEnabled = True
            self.__server_os     = sessionData['NativeOS']
            self.__server_lanman = sessionData['NativeLanMan']
            self.__server_domain = sessionData['PrimaryDomain']

            # restore unicode flag if needed
            if flags2 & SMB.FLAGS2_UNICODE:
                self.__flags2 |= SMB.FLAGS2_UNICODE

            return 1
        else:
            raise Exception('Error: Could not login successfully')

    def waitNamedPipe(self, tid, pipe, timeout = 5, noAnswer = 0):
        smb = NewSMBPacket()
        smb['Tid']    = tid

        transCommand = SMBCommand(SMB.SMB_COM_TRANSACTION)
        transCommand['Parameters'] = SMBTransaction_Parameters()
        transCommand['Data'] = SMBTransaction_Data()

        setup = '\x53\x00\x00\x00'
        name = '\\PIPE%s\x00' % pipe
        transCommand['Parameters']['Setup'] = setup
        transCommand['Parameters']['TotalParameterCount'] = 0
        transCommand['Parameters']['TotalDataCount'] = 0
        transCommand['Parameters']['MaxParameterCount'] = 0
        transCommand['Parameters']['MaxDataCount'] = 0
        transCommand['Parameters']['Timeout'] = timeout * 1000

        transCommand['Parameters']['ParameterCount'] = 0
        transCommand['Parameters']['ParameterOffset'] = 32+3+28+len(setup)+len(name)

        transCommand['Parameters']['DataCount'] = 0
        transCommand['Parameters']['DataOffset'] = 0

        transCommand['Data']['Name'] = name
        transCommand['Data']['Trans_Parameters'] = ''
        transCommand['Data']['Trans_Data'] = ''

        if noAnswer:
           transCommand['Parameters']['Flags'] = TRANS_NO_RESPONSE

        smb.addCommand(transCommand)
        self.sendSMB(smb)

        smb = self.recvSMB()
        if smb.isValidAnswer(SMB.SMB_COM_TRANSACTION):
           return 1
        return 0

    def read(self, tid, fid, offset=0, max_size = None, wait_answer=1):
        if not max_size:
            max_size = self._dialects_parameters['MaxBufferSize'] # Read in multiple KB blocks

        # max_size is not working, because although it would, the server returns an error (More data avail)

        smb = NewSMBPacket()
        smb['Tid']    = tid

        read = SMBCommand(SMB.SMB_COM_READ)
        read['Parameters'] = SMBRead_Parameters()
        read['Parameters']['Fid'] = fid
        read['Parameters']['Offset'] = offset
        read['Parameters']['Count'] = max_size
        smb.addCommand(read)

        if wait_answer:
            while 1:
                self.sendSMB(smb)
                ans = self.recvSMB()

                if ans.isValidAnswer(SMB.SMB_COM_READ):
                    readResponse   = SMBCommand(ans['Data'][0])
                    readData       = SMBReadResponse_Data(readResponse['Data'])

                    return readData['Data']

        return None

    def read_andx(self, tid, fid, offset=0, max_size = None, wait_answer=1, smb_packet=None):
        if not max_size:
            if (self._dialects_parameters['Capabilities'] & SMB.CAP_LARGE_READX) and self._SignatureEnabled is False:
                max_size = 65000
            else:
                max_size = self._dialects_parameters['MaxBufferSize'] # Read in multiple KB blocks

        # max_size is not working, because although it would, the server returns an error (More data avail)

        if smb_packet is None:
            smb = NewSMBPacket()
            smb['Tid']    = tid

            readAndX = SMBCommand(SMB.SMB_COM_READ_ANDX)
            readAndX['Parameters'] = SMBReadAndX_Parameters()
            readAndX['Parameters']['Fid'] = fid
            readAndX['Parameters']['Offset'] = offset
            readAndX['Parameters']['MaxCount'] = max_size
            smb.addCommand(readAndX)
        else:
            smb = smb_packet

        if wait_answer:
            answer = b''
            while 1:
                self.sendSMB(smb)
                ans = self.recvSMB()

                if ans.isValidAnswer(SMB.SMB_COM_READ_ANDX):
                    # XXX Here we are only using a few fields from the response
                    readAndXResponse   = SMBCommand(ans['Data'][0])
                    readAndXParameters = SMBReadAndXResponse_Parameters(readAndXResponse['Parameters'])

                    offset = readAndXParameters['DataOffset']
                    count = readAndXParameters['DataCount']+0x10000*readAndXParameters['DataCount_Hi']
                    answer += ans.getData()[offset:offset+count]
                    if not ans.isMoreData():
                        return answer
                    max_size = min(max_size, readAndXParameters['Remaining'])
                    readAndX['Parameters']['Offset'] += count                      # XXX Offset is not important (apparently)
        else:
            self.sendSMB(smb)
            ans = self.recvSMB()

            try:
                if ans.isValidAnswer(SMB.SMB_COM_READ_ANDX):
                    return ans
                else:
                    return None
            except:
                return ans

        return None

    def read_raw(self, tid, fid, offset=0, max_size = None, wait_answer=1):
        if not max_size:
            max_size = self._dialects_parameters['MaxBufferSize'] # Read in multiple KB blocks

        # max_size is not working, because although it would, the server returns an error (More data avail)
        smb = NewSMBPacket()
        smb['Tid']    = tid

        readRaw = SMBCommand(SMB.SMB_COM_READ_RAW)
        readRaw['Parameters'] = SMBReadRaw_Parameters()
        readRaw['Parameters']['Fid'] = fid
        readRaw['Parameters']['Offset'] = offset
        readRaw['Parameters']['MaxCount'] = max_size
        smb.addCommand(readRaw)

        self.sendSMB(smb)
        if wait_answer:
            data = self._sess.recv_packet(self.__timeout).get_trailer()
            if not data:
                # If there is no data it means there was an error
                data = self.read_andx(tid, fid, offset, max_size)
            return data

        return None

    def write(self,tid,fid,data, offset = 0, wait_answer=1):
        smb = NewSMBPacket()
        smb['Tid']    = tid

        write = SMBCommand(SMB.SMB_COM_WRITE)
        write['Parameters'] = SMBWrite_Parameters()
        write['Data'] = SMBWrite_Data()
        write['Parameters']['Fid'] = fid
        write['Parameters']['Count'] = len(data)
        write['Parameters']['Offset'] = offset
        write['Parameters']['Remaining'] = len(data)
        write['Data']['Data'] = data
        smb.addCommand(write)

        self.sendSMB(smb)

        if wait_answer:
            smb = self.recvSMB()
            if smb.isValidAnswer(SMB.SMB_COM_WRITE):
                return smb
        return None

    def write_andx(self,tid,fid,data, offset = 0, wait_answer=1, write_pipe_mode = False, smb_packet=None):
        if smb_packet is None:
            smb = NewSMBPacket()
            smb['Tid']    = tid

            writeAndX = SMBCommand(SMB.SMB_COM_WRITE_ANDX)
            smb.addCommand(writeAndX)

            writeAndX['Parameters'] = SMBWriteAndX_Parameters()
            writeAndX['Parameters']['Fid'] = fid
            writeAndX['Parameters']['Offset'] = offset
            writeAndX['Parameters']['WriteMode'] = 8
            writeAndX['Parameters']['Remaining'] = len(data)
            writeAndX['Parameters']['DataLength'] = len(data)
            writeAndX['Parameters']['DataOffset'] = len(smb)    # this length already includes the parameter
            writeAndX['Data'] = data

            if write_pipe_mode is True:
                # First of all we gotta know what the MaxBuffSize is
                maxBuffSize = self._dialects_parameters['MaxBufferSize']
                if len(data) > maxBuffSize:
                    chunks_size = maxBuffSize - 60
                    writeAndX['Parameters']['WriteMode'] = 0x0c
                    sendData = b'\xff\xff' + data
                    totalLen = len(sendData)
                    writeAndX['Parameters']['DataLength'] = chunks_size
                    writeAndX['Parameters']['Remaining'] = totalLen-2
                    writeAndX['Data'] = sendData[:chunks_size]

                    self.sendSMB(smb)
                    if wait_answer:
                        smbResp = self.recvSMB()
                        smbResp.isValidAnswer(SMB.SMB_COM_WRITE_ANDX)

                    alreadySent = chunks_size
                    sendData = sendData[chunks_size:]

                    while alreadySent < totalLen:
                        writeAndX['Parameters']['WriteMode'] = 0x04
                        writeAndX['Parameters']['DataLength'] = len(sendData[:chunks_size])
                        writeAndX['Data'] = sendData[:chunks_size]
                        self.sendSMB(smb)
                        if wait_answer:
                            smbResp = self.recvSMB()
                            smbResp.isValidAnswer(SMB.SMB_COM_WRITE_ANDX)
                        alreadySent += writeAndX['Parameters']['DataLength']
                        sendData = sendData[chunks_size:]

                    return smbResp

        else:
            smb = smb_packet

        self.sendSMB(smb)

        if wait_answer:
            smb = self.recvSMB()
            if smb.isValidAnswer(SMB.SMB_COM_WRITE_ANDX):
                return smb
        return None

    def write_raw(self,tid,fid,data, offset = 0, wait_answer=1):
        LOG.warning("[MS-CIFS] This command was introduced in the CorePlus dialect, but is often listed as part of the LAN Manager 1.0 dialect.This command has been deprecated.Clients SHOULD use SMB_COM_WRITE_ANDX")
        smb = NewSMBPacket()
        smb['Tid']    = tid

        writeRaw = SMBCommand(SMB.SMB_COM_WRITE_RAW)
        writeRaw['Parameters'] = SMBWriteRaw_Parameters()
        writeRaw['Parameters']['Fid'] = fid
        writeRaw['Parameters']['Offset'] = offset
        writeRaw['Parameters']['Count'] = len(data)
        writeRaw['Parameters']['DataLength'] = 0
        writeRaw['Parameters']['DataOffset'] = 0
        smb.addCommand(writeRaw)

        self.sendSMB(smb)
        self._sess.send_packet(data)

        if wait_answer:
            smb = self.recvSMB()
            if smb.isValidAnswer(SMB.SMB_COM_WRITE_RAW):
                return smb
        return None

    def TransactNamedPipe(self, tid, fid, data = '', noAnswer = 0, waitAnswer = 1, offset = 0):
        self.send_trans(tid,pack('<HH', 0x26, fid),'\\PIPE\\\x00','',data, noAnswer = noAnswer)

        if noAnswer or not waitAnswer:
            return
        smb = self.recvSMB()
        if smb.isValidAnswer(SMB.SMB_COM_TRANSACTION):
           transResponse = SMBCommand(smb['Data'][0])
           transParameters = SMBTransactionResponse_Parameters(transResponse['Parameters'])
           return transResponse['Data'][-transParameters['TotalDataCount']:] # Remove Potential Prefix Padding
        return None

    def TransactNamedPipeRecv(self):
        s = self.recvSMB()
        if s.isValidAnswer(SMB.SMB_COM_TRANSACTION):
           transResponse = SMBCommand(s['Data'][0])
           transParameters = SMBTransactionResponse_Parameters(transResponse['Parameters'])
           return transResponse['Data'][-transParameters['TotalDataCount']:] # Remove Potential Prefix Padding
        return None

    def nt_create_andx(self,tid,filename, smb_packet=None, cmd = None, shareAccessMode = FILE_SHARE_READ | FILE_SHARE_WRITE, disposition = FILE_OPEN, accessMask = 0x2019f):
        filename = filename.replace('/', '\\')
        filename = filename.encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else filename

        if smb_packet is None:
            smb = NewSMBPacket()
            smb['Tid']    = tid
        else:
            smb = smb_packet

        if cmd is None:
            ntCreate = SMBCommand(SMB.SMB_COM_NT_CREATE_ANDX)
            ntCreate['Parameters'] = SMBNtCreateAndX_Parameters()
            ntCreate['Data']       = SMBNtCreateAndX_Data(flags=self.__flags2)
            ntCreate['Parameters']['FileNameLength'] = len(filename)
            ntCreate['Parameters']['CreateFlags'] = 0x16
            ntCreate['Parameters']['AccessMask'] = accessMask
            ntCreate['Parameters']['CreateOptions'] = 0x40
            ntCreate['Parameters']['ShareAccess'] = shareAccessMode
            ntCreate['Parameters']['Disposition'] = disposition
            ntCreate['Data']['FileName'] = filename

            if self.__flags2 & SMB.FLAGS2_UNICODE:
                ntCreate['Data']['Pad'] = 0x0
        else:
            ntCreate = cmd

        smb.addCommand(ntCreate)

        self.sendSMB(smb)

        while 1:
            smb = self.recvSMB()
            if smb.isValidAnswer(SMB.SMB_COM_NT_CREATE_ANDX):
                # XXX Here we are ignoring the rest of the response
                ntCreateResponse   = SMBCommand(smb['Data'][0])
                ntCreateParameters = SMBNtCreateAndXResponse_Parameters(ntCreateResponse['Parameters'])

                self.fid = ntCreateParameters['Fid']
                return ntCreateParameters['Fid']

    def logoff(self):
        smb = NewSMBPacket()

        logOff = SMBCommand(SMB.SMB_COM_LOGOFF_ANDX)
        logOff['Parameters'] = SMBLogOffAndX()
        smb.addCommand(logOff)

        self.sendSMB(smb)
        self.recvSMB()
        # Let's clear some fields so you can login again under the same session
        self._uid = 0

    def list_path(self, service, path = '*', password = None):
        path = path.replace('/', '\\')
        path = path.encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else path

        tid = self.tree_connect_andx('\\\\' + self.__remote_name + '\\' + service, password)
        try:
            findFirstParameter = SMBFindFirst2_Parameters(self.__flags2)
            findFirstParameter['SearchAttributes'] = SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_HIDDEN | \
                                                     SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_READONLY | \
                                                     SMB_FILE_ATTRIBUTE_ARCHIVE
            findFirstParameter['SearchCount'] = 512
            findFirstParameter['Flags'] = SMB_FIND_RETURN_RESUME_KEYS | SMB_FIND_CLOSE_AT_EOS
            findFirstParameter['InformationLevel'] = SMB_FIND_FILE_BOTH_DIRECTORY_INFO
            findFirstParameter['SearchStorageType'] = 0
            if self.__flags2 & SMB.FLAGS2_UNICODE:
                findFirstParameter['FileName'] = path + b'\x00\x00'
            else:
                findFirstParameter['FileName'] = path + '\x00'
            self.send_trans2(tid, SMB.TRANS2_FIND_FIRST2, '\x00', findFirstParameter, '')
            files = [ ]

            totalDataCount = 1
            findData = b''
            findFirst2ParameterBlock = b''
            while len(findData) < totalDataCount:
                resp = self.recvSMB()

                if resp.isValidAnswer(SMB.SMB_COM_TRANSACTION2):
                    trans2Response = SMBCommand(resp['Data'][0])
                    trans2Parameters = SMBTransaction2Response_Parameters(trans2Response['Parameters'])
                    totalDataCount = trans2Parameters['TotalDataCount']
                    findFirst2ParameterBlock += trans2Response['Data'][trans2Parameters['ParameterOffset']-55:][:trans2Parameters['ParameterCount']]
                    findData += trans2Response['Data'][trans2Parameters['DataOffset']-55:]

            findParameterBlock = SMBFindFirst2Response_Parameters(findFirst2ParameterBlock)
            # Save the SID for resume operations
            sid = findParameterBlock['SID']

            while True:
                record = SMBFindFileBothDirectoryInfo(data = findData)

                shortname = record['ShortName'].decode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else \
                                                                        record['ShortName'].decode('latin-1')
                filename = record['FileName'].decode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else \
                                                                        record['FileName'].decode('latin-1')

                fileRecord = SharedFile(record['CreationTime'], record['LastAccessTime'], record['LastChangeTime'],
                                  record['EndOfFile'], record['AllocationSize'], record['ExtFileAttributes'],
                                  shortname, filename)
                files.append(fileRecord)
                if record['NextEntryOffset'] > 0 and len(findData[record['NextEntryOffset']:]) > 0:
                    findData = findData[record['NextEntryOffset']:]
                else:
                    # More data to search?
                    if findParameterBlock['EndOfSearch'] == 0:
                        resume_filename = record['FileName']
                        findNextParameter = SMBFindNext2_Parameters()
                        findNextParameter['SID'] = sid
                        findNextParameter['SearchCount'] = 1024
                        findNextParameter['InformationLevel'] = SMB_FIND_FILE_BOTH_DIRECTORY_INFO
                        findNextParameter['ResumeKey'] = 0
                        findNextParameter['Flags'] = SMB_FIND_RETURN_RESUME_KEYS | SMB_FIND_CLOSE_AT_EOS
                        if self.__flags2 & SMB.FLAGS2_UNICODE:
                            findNextParameter['FileName'] = resume_filename + b'\x00\x00'
                        else:
                            findNextParameter['FileName'] = resume_filename + '\x00'
                        self.send_trans2(tid, SMB.TRANS2_FIND_NEXT2, '\x00', findNextParameter, '')
                        findData = b''
                        findNext2ParameterBlock = b''
                        totalDataCount = 1
                        while len(findData) < totalDataCount:
                            resp = self.recvSMB()

                            if resp.isValidAnswer(SMB.SMB_COM_TRANSACTION2):
                                trans2Response = SMBCommand(resp['Data'][0])
                                trans2Parameters = SMBTransaction2Response_Parameters(trans2Response['Parameters'])
                                totalDataCount = trans2Parameters['TotalDataCount']
                                findNext2ParameterBlock += trans2Response['Data'][trans2Parameters['ParameterOffset']-55:][:trans2Parameters['ParameterCount']]
                                findData += trans2Response['Data'][trans2Parameters['DataOffset']-55:]
                                findParameterBlock = SMBFindNext2Response_Parameters(findNext2ParameterBlock)
                    else:
                       break
        finally:
            self.disconnect_tree(tid)

        return files

    def retr_file(self, service, filename, callback, mode = FILE_OPEN, offset = 0, password = None, shareAccessMode = SMB_ACCESS_READ):
        filename = filename.replace('/', '\\')

        fid = -1
        tid = self.tree_connect_andx('\\\\' + self.__remote_name + '\\' + service, password)
        try:
            fid = self.nt_create_andx(tid, filename, shareAccessMode = shareAccessMode, accessMask = 0x20089)

            res = self.query_file_info(tid, fid)
            datasize = SMBQueryFileStandardInfo(res)['EndOfFile']

            self.__nonraw_retr_file(tid, fid, offset, datasize, callback)
        finally:
            if fid >= 0:
                self.close(tid, fid)
            self.disconnect_tree(tid)

    def stor_file(self, service, filename, callback, mode = FILE_OVERWRITE_IF, offset = 0, password = None, shareAccessMode = SMB_ACCESS_WRITE):
        filename = filename.replace('/', '\\')

        fid = -1
        tid = self.tree_connect_andx('\\\\' + self.__remote_name + '\\' + service, password)
        try:
            fid = self.nt_create_andx(tid, filename, shareAccessMode = shareAccessMode, disposition = mode )

            self.__nonraw_stor_file(tid, fid, offset, 0, callback)
        finally:
            if fid >= 0:
                self.close(tid, fid)
            self.disconnect_tree(tid)

    def stor_file_nonraw(self, service, filename, callback, mode = FILE_OVERWRITE_IF, offset = 0, password = None, shareAccessMode = SMB_ACCESS_WRITE ):
        filename = filename.replace('/', '\\')

        fid = -1
        tid = self.tree_connect_andx('\\\\' + self.__remote_name + '\\' + service, password)
        try:
            fid = self.nt_create_andx(tid, filename, shareAccessMode = shareAccessMode, disposition = mode)
            self.__nonraw_stor_file(tid, fid, offset, 0, callback)
        finally:
            if fid >= 0:
                self.close(tid, fid)
            self.disconnect_tree(tid)

    def check_dir(self, service, path, password = None):
        path = path.replace('/', '\\')
        tid = self.tree_connect_andx('\\\\' + self.__remote_name + '\\' + service, password)
        try:
            smb = NewSMBPacket()
            smb['Tid'] = tid
            smb['Mid'] = 0

            cmd = SMBCommand(SMB.SMB_COM_CHECK_DIRECTORY)
            cmd['Parameters'] = ''
            cmd['Data'] = SMBCheckDirectory_Data(flags = self.__flags2)
            cmd['Data']['DirectoryName'] = path.encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else path
            smb.addCommand(cmd)

            self.sendSMB(smb)

            while 1:
                s = self.recvSMB()
                if s.isValidAnswer(SMB.SMB_COM_CHECK_DIRECTORY):
                    return
        finally:
            self.disconnect_tree(tid)

    def remove(self, service, path, password = None):
        path = path.replace('/', '\\')
        # Perform a list to ensure the path exists
        self.list_path(service, path, password)

        tid = self.tree_connect_andx('\\\\' + self.__remote_name + '\\' + service, password)
        try:
            smb = NewSMBPacket()
            smb['Tid'] = tid
            smb['Mid'] = 0

            cmd = SMBCommand(SMB.SMB_COM_DELETE)
            cmd['Parameters'] = SMBDelete_Parameters()
            cmd['Parameters']['SearchAttributes'] = ATTR_HIDDEN | ATTR_SYSTEM | ATTR_ARCHIVE
            cmd['Data'] = SMBDelete_Data(flags = self.__flags2)
            cmd['Data']['FileName'] = (path + '\x00').encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else (path + '\x00')
            smb.addCommand(cmd)

            self.sendSMB(smb)

            while 1:
                s = self.recvSMB()
                if s.isValidAnswer(SMB.SMB_COM_DELETE):
                    return
        finally:
            self.disconnect_tree(tid)

    def rmdir(self, service, path, password = None):
        path = path.replace('/', '\\')
        # Check that the directory exists
        self.check_dir(service, path, password)

        tid = self.tree_connect_andx('\\\\' + self.__remote_name + '\\' + service, password)
        try:
            path = path.encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else path

            smb = NewSMBPacket()
            smb['Tid'] = tid
            createDir = SMBCommand(SMB.SMB_COM_DELETE_DIRECTORY)
            createDir['Data'] = SMBDeleteDirectory_Data(flags=self.__flags2)
            createDir['Data']['DirectoryName'] = path
            smb.addCommand(createDir)

            self.sendSMB(smb)

            while 1:
                s = self.recvSMB()
                if s.isValidAnswer(SMB.SMB_COM_DELETE_DIRECTORY):
                    return
        finally:
            self.disconnect_tree(tid)

    def mkdir(self, service, path, password = None):
        path = path.replace('/', '\\')
        tid = self.tree_connect_andx('\\\\' + self.__remote_name + '\\' + service, password)
        try:
            path = path.encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else path

            smb = NewSMBPacket()
            smb['Tid'] = tid
            smb['Mid'] = 0

            createDir = SMBCommand(SMB.SMB_COM_CREATE_DIRECTORY)
            createDir['Data'] = SMBCreateDirectory_Data(flags=self.__flags2)
            createDir['Data']['DirectoryName'] = path
            smb.addCommand(createDir)

            self.sendSMB(smb)

            smb = self.recvSMB()
            if smb.isValidAnswer(SMB.SMB_COM_CREATE_DIRECTORY):
                return 1
            return 0
        finally:
            self.disconnect_tree(tid)

    def rename(self, service, old_path, new_path, password = None):
        old_path = old_path.replace('/', '\\')
        new_path = new_path.replace('/', '\\')
        tid = self.tree_connect_andx('\\\\' + self.__remote_name + '\\' + service, password)
        try:
            smb = NewSMBPacket()
            smb['Tid'] = tid
            smb['Mid'] = 0

            renameCmd = SMBCommand(SMB.SMB_COM_RENAME)
            renameCmd['Parameters'] = SMBRename_Parameters()
            renameCmd['Parameters']['SearchAttributes'] = ATTR_SYSTEM | ATTR_HIDDEN | ATTR_DIRECTORY
            renameCmd['Data'] = SMBRename_Data(flags = self.__flags2)
            renameCmd['Data']['OldFileName'] = old_path.encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else old_path
            renameCmd['Data']['NewFileName'] = new_path.encode('utf-16le') if self.__flags2 & SMB.FLAGS2_UNICODE else new_path
            smb.addCommand(renameCmd)

            self.sendSMB(smb)

            smb = self.recvSMB()
            if smb.isValidAnswer(SMB.SMB_COM_RENAME):
               return 1
            return 0
        finally:
            self.disconnect_tree(tid)

    def writeFile(self, treeId, fileId, data, offset = 0):
        if (self._dialects_parameters['Capabilities'] & SMB.CAP_LARGE_WRITEX) and self._SignatureEnabled is False:
            max_buf_size = 65000
        else:
            max_buf_size = self._dialects_parameters['MaxBufferSize'] & ~0x3ff  # Write in multiple KB blocks

        write_offset = offset
        while 1:
            if len(data) == 0:
                break
            writeData = data[:max_buf_size]
            data = data[max_buf_size:]

            smb = self.write_andx(treeId,fileId,writeData, write_offset)
            writeResponse   = SMBCommand(smb['Data'][0])
            writeResponseParameters = SMBWriteAndXResponse_Parameters(writeResponse['Parameters'])
            write_offset += writeResponseParameters['Count']

    def get_socket(self):
        return self._sess.get_socket()

    def send_nt_trans(self, tid, subcommand, max_param_count, setup='', param='', data=''):
        """
        [MS-CIFS]: 2.2.4.62.1 SMB_COM_NT_TRANSACT request.
        :param tid:
        :param subcommand: The transaction subcommand code
        :param max_param_count:  This field MUST be set as specified in the subsections of Transaction subcommands.
        :param setup: Transaction context to the server, depends on transaction subcommand.
        :param param: Subcommand parameter bytes if any, depends on transaction subcommand.
        :param data: Subcommand data bytes if any, depends on transaction subcommand.
        :return: Buffer relative to requested subcommand.
        """
        smb_packet = NewSMBPacket()
        smb_packet['Tid'] = tid
        #    setup depends on NT_TRANSACT subcommands so it may be 0.
        setup_bytes = pack('<H', setup) if setup != '' else ''

        transCommand = SMBCommand(SMB.SMB_COM_NT_TRANSACT)
        transCommand['Parameters'] = SMBNTTransaction_Parameters()
        transCommand['Parameters']['MaxDataCount'] = self._dialects_parameters['MaxBufferSize']
        transCommand['Parameters']['Setup'] = setup_bytes
        transCommand['Parameters']['Function'] = subcommand
        transCommand['Parameters']['TotalParameterCount'] = len(param)
        transCommand['Parameters']['TotalDataCount'] = len(data)
        transCommand['Parameters']['MaxParameterCount'] = max_param_count
        transCommand['Parameters']['MaxSetupCount'] = 0

        transCommand['Data'] = SMBNTTransaction_Data()

        # SMB header size + SMB_COM_NT_TRANSACT parameters size + length of setup bytes.
        offset = 32 + 3 + 38 + len(setup_bytes)
        transCommand['Data']['Pad1'] = ''
        if offset % 4 != 0:
            transCommand['Data']['Pad1'] = '\0' * (4 - offset % 4)
            offset += (4 - offset % 4)  # pad1 length

        if len(param) > 0:
            transCommand['Parameters']['ParameterOffset'] = offset
        else:
            transCommand['Parameters']['ParameterOffset'] = 0

        offset += len(param)
        transCommand['Data']['Pad2'] = ''
        if offset % 4 != 0:
            transCommand['Data']['Pad2'] = '\0' * (4 - offset % 4)
            offset += (4 - offset % 4)

        if len(data) > 0:
            transCommand['Parameters']['DataOffset'] = offset
        else:
            transCommand['Parameters']['DataOffset'] = 0

        transCommand['Parameters']['DataCount'] = len(data)
        transCommand['Parameters']['ParameterCount'] = len(param)
        transCommand['Data']['NT_Trans_Parameters'] = param
        transCommand['Data']['NT_Trans_Data'] = data
        smb_packet.addCommand(transCommand)

        self.sendSMB(smb_packet)

    def query_sec_info(self, tid, fid, additional_information=7):
        """
        [MS-CIFS]: 2.2.7.6.1
        NT_TRANSACT_QUERY_SECURITY_DESC 0x0006
        :param tid: valid tree id.
        :param fid: valid file handle.
        :param additional_information: SecurityInfoFields. default = owner + group + dacl ie. 7
        :return: security descriptor buffer
        """
        self.send_nt_trans(tid, subcommand=0x0006, max_param_count=4,
                           param=pack('<HHL', fid, 0x0000, additional_information))
        resp = self.recvSMB()
        if resp.isValidAnswer(SMB.SMB_COM_NT_TRANSACT):
            nt_trans_response = SMBCommand(resp['Data'][0])
            nt_trans_parameters = SMBNTTransactionResponse_Parameters(nt_trans_response['Parameters'])
            # Remove Potential Prefix Padding
            return nt_trans_response['Data'][-nt_trans_parameters['TotalDataCount']:]

    def echo(self, text = '', count = 1):

        smb = NewSMBPacket()
        comEcho = SMBCommand(SMB.SMB_COM_ECHO)
        comEcho['Parameters'] = SMBEcho_Parameters()
        comEcho['Data']       = SMBEcho_Data()
        comEcho['Parameters']['EchoCount'] = count
        comEcho['Data']['Data'] = text
        smb.addCommand(comEcho)

        self.sendSMB(smb)

        for i in range(count):
            resp = self.recvSMB()
            resp.isValidAnswer(SMB.SMB_COM_ECHO)
        return True

ERRDOS = { 1: 'Invalid function',
           2: 'File not found',
           3: 'Invalid directory',
           4: 'Too many open files',
           5: 'Access denied',
           6: 'Invalid file handle. Please file a bug report.',
           7: 'Memory control blocks destroyed',
           8: 'Out of memory',
           9: 'Invalid memory block address',
           10: 'Invalid environment',
           11: 'Invalid format',
           12: 'Invalid open mode',
           13: 'Invalid data',
           15: 'Invalid drive',
           16: 'Attempt to remove server\'s current directory',
           17: 'Not the same device',
           18: 'No files found',
           32: 'Sharing mode conflicts detected',
           33: 'Lock request conflicts detected',
           80: 'File already exists'
           }

ERRSRV = { 1: 'Non-specific error',
           2: 'Bad password',
           4: 'Access denied',
           5: 'Invalid tid. Please file a bug report.',
           6: 'Invalid network name',
           7: 'Invalid device',
           49: 'Print queue full',
           50: 'Print queue full',
           51: 'EOF on print queue dump',
           52: 'Invalid print file handle',
           64: 'Command not recognized. Please file a bug report.',
           65: 'Internal server error',
           67: 'Invalid path',
           69: 'Invalid access permissions',
           71: 'Invalid attribute mode',
           81: 'Server is paused',
           82: 'Not receiving messages',
           83: 'No room to buffer messages',
           87: 'Too many remote user names',
           88: 'Operation timeout',
           89: 'Out of resources',
           91: 'Invalid user handle. Please file a bug report.',
           250: 'Temporarily unable to support raw mode for transfer',
           251: 'Temporarily unable to support raw mode for transfer',
           252: 'Continue in MPX mode',
           65535: 'Unsupported function'
           }

ERRHRD = { 19: 'Media is write-protected',
           20: 'Unknown unit',
           21: 'Drive not ready',
           22: 'Unknown command',
           23: 'CRC error',
           24: 'Bad request',
           25: 'Seek error',
           26: 'Unknown media type',
           27: 'Sector not found',
           28: 'Printer out of paper',
           29: 'Write fault',
           30: 'Read fault',
           31: 'General failure',
           32: 'Open conflicts with an existing open',
           33: 'Invalid lock request',
           34: 'Wrong disk in drive',
           35: 'FCBs not available',
           36: 'Sharing buffer exceeded'
           }
