1# -*- coding: utf-8 -*-
2# Copyright: (c) 2019, Jordan Borean (@jborean93) <jborean93@gmail.com>
3# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
4
5import logging
6
7from collections import (
8    OrderedDict,
9)
10
11from smbprotocol import (
12    Dialects,
13    MAX_PAYLOAD_SIZE,
14)
15
16from smbprotocol.create_contexts import (
17    SMB2CreateContextRequest,
18)
19
20from smbprotocol.exceptions import (
21    FileClosed,
22    SMBException,
23    SMBUnsupportedFeature,
24)
25
26from smbprotocol.file_info import (
27    FileBothDirectoryInformation,
28    FileDirectoryInformation,
29    FileFullDirectoryInformation,
30    FileFullEaInformation,
31    FileIdBothDirectoryInformation,
32    FileIdFullDirectoryInformation,
33    FileNamesInformation,
34    FileStreamInformation,
35    InfoType,
36
37    # While this shouldn't ever be removed, we need to keep this imported so we stay backwards compat. These were
38    # originally defined here.
39    FileAttributes,
40    FileInformationClass,
41)
42
43from smbprotocol.header import (
44    Commands,
45)
46
47from smbprotocol.structure import (
48    BytesField,
49    DateTimeField,
50    EnumField,
51    FlagField,
52    IntField,
53    ListField,
54    Structure,
55    StructureField,
56)
57
58log = logging.getLogger(__name__)
59
60
61class RequestedOplockLevel(object):
62    """
63    [MS-SMB2] v53.0 2017-09-15
64
65    2.2.31 SMB2 CREATE Request RequestedOplockLevel
66    The requested oplock level used when creating/accessing a file.
67    """
68    SMB2_OPLOCK_LEVEL_NONE = 0x00
69    SMB2_OPLOCK_LEVEL_II = 0x01
70    SMB2_OPLOCK_LEVEL_EXCLUSIVE = 0x08
71    SMB2_OPLOCK_LEVEL_BATCH = 0x09
72    SMB2_OPLOCK_LEVEL_LEASE = 0xFF
73
74
75class ImpersonationLevel(object):
76    """
77    [MS-SMB2] v53.0 2017-09-15
78
79    2.2.31 SMB2 CREATE Request ImpersonationLevel
80    The impersonation level requested by the application in a create request.
81    """
82    Anonymous = 0x0
83    Identification = 0x1
84    Impersonation = 0x2
85    Delegate = 0x3
86
87
88class ShareAccess(object):
89    """
90    [MS-SMB2] v53.0 2017-09-15
91
92    2.2.31 SMB2 CREATE Request ShareAccess
93    The sharing mode for the open
94    """
95    FILE_SHARE_READ = 0x1
96    FILE_SHARE_WRITE = 0x2
97    FILE_SHARE_DELETE = 0x4
98
99
100class CreateDisposition(object):
101    """
102    [MS-SMB2] v53.0 2017-09-15
103
104    2.2.31 SMB2 CREATE Request CreateDisposition
105    Defines the action the server must take if the file that is specific
106    already exists.
107    """
108    FILE_SUPERSEDE = 0x0
109    FILE_OPEN = 0x1
110    FILE_CREATE = 0x2
111    FILE_OPEN_IF = 0x3
112    FILE_OVERWRITE = 0x4
113    FILE_OVERWRITE_IF = 0x5
114
115
116class CreateOptions(object):
117    """
118    [MS-SMB2] v53.0 2017-09-15
119
120    2.2.31 SMB2 CREATE Request CreateOptions
121    Specifies the options to be applied when creating or opening the file
122    """
123    FILE_DIRECTORY_FILE = 0x00000001
124    FILE_WRITE_THROUGH = 0x00000002
125    FILE_SEQUENTIAL_ONLY = 0x00000004
126    FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008
127    FILE_SYNCHRONOUS_IO_ALERT = 0x00000010
128    FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020
129    FILE_NON_DIRECTORY_FILE = 0x00000040
130    FILE_COMPLETE_IF_OPLOCKED = 0x00000100
131    FILE_NO_EA_KNOWLEDGE = 0x00000200
132    FILE_RANDOM_ACCESS = 0x00000800
133    FILE_DELETE_ON_CLOSE = 0x00001000
134    FILE_OPEN_BY_FILE_ID = 0x00002000
135    FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000
136    FILE_NO_COMPRESSION = 0x00008000
137    FILE_OPEN_REMOTE_INSTANCE = 0x00000400
138    FILE_OPEN_REQUIRING_OPLOCK = 0x00010000
139    FILE_DISALLOW_EXCLUSIVE = 0x00020000
140    FILE_RESERVE_OPFILTER = 0x00100000
141    FILE_OPEN_REPARSE_POINT = 0x00200000
142    FILE_OPEN_NO_RECALL = 0x00400000
143    FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000
144
145
146class FilePipePrinterAccessMask(object):
147    """
148    [MS-SMB2] v53.0 2017-09-15
149
150    2.2.13.1.1 File_Pipe_Printer_Access_Mask
151    Access Mask flag values to be used when accessing a file, pipe, or printer
152    """
153    FILE_READ_DATA = 0x00000001
154    FILE_WRITE_DATA = 0x00000002
155    FILE_APPEND_DATA = 0x00000004
156    FILE_READ_EA = 0x00000008
157    FILE_WRITE_EA = 0x00000010
158    FILE_DELETE_CHILD = 0x00000040
159    FILE_EXECUTE = 0x00000020
160    FILE_READ_ATTRIBUTES = 0x00000080
161    FILE_WRITE_ATTRIBUTES = 0x00000100
162    DELETE = 0x00010000
163    READ_CONTROL = 0x00020000
164    WRITE_DAC = 0x00040000
165    WRITE_OWNER = 0x00080000
166    SYNCHRONIZE = 0x00100000
167    ACCESS_SYSTEM_SECURITY = 0x01000000
168    MAXIMUM_ALLOWED = 0x02000000
169    GENERIC_ALL = 0x10000000
170    GENERIC_EXECUTE = 0x20000000
171    GENERIC_WRITE = 0x40000000
172    GENERIC_READ = 0x80000000
173
174
175class DirectoryAccessMask(object):
176    """
177    [MS-SMB2] v53.0 2017-09-15
178
179    2.2.13.1.2 Directory_Access_Mask
180    Access Mask flag values to be used when accessing a directory
181    """
182    FILE_LIST_DIRECTORY = 0x00000001
183    FILE_ADD_FILE = 0x00000002
184    FILE_ADD_SUBDIRECTORY = 0x00000004
185    FILE_READ_EA = 0x00000008
186    FILE_WRITE_EA = 0x00000010
187    FILE_TRAVERSE = 0x00000020
188    FILE_DELETE_CHILD = 0x00000040
189    FILE_READ_ATTRIBUTES = 0x00000080
190    FILE_WRITE_ATTRIBUTES = 0x00000100
191    DELETE = 0x00010000
192    READ_CONTROL = 0x00020000
193    WRITE_DAC = 0x00040000
194    WRITE_OWNER = 0x00080000
195    SYNCHRONIZE = 0x00100000
196    ACCESS_SYSTEM_SECURITY = 0x01000000
197    MAXIMUM_ALLOWED = 0x02000000
198    GENERIC_ALL = 0x10000000
199    GENERIC_EXECUTE = 0x20000000
200    GENERIC_WRITE = 0x40000000
201    GENERIC_READ = 0x80000000
202
203
204class FileFlags(object):
205    """
206    [MS-SMB2] v53.0 2017-09-15
207
208    2.2.14 SMB2 CREATE Response Flags
209    Flag that details info about the file that was opened.
210    """
211    SMB2_CREATE_FLAG_REPARSEPOINT = 0x1
212
213
214class CreateAction(object):
215    """
216    [MS-SMB2] v53.0 2017-09-15
217
218    2.2.14 SMB2 CREATE Response Flags
219    The action taken in establishing the open.
220    """
221    FILE_SUPERSEDED = 0x0
222    FILE_OPENED = 0x1
223    FILE_CREATED = 0x2
224    FILE_OVERWRITTEN = 0x3
225
226
227class CloseFlags(object):
228    """
229    [MS-SMB2] v53.0 2017-09-15
230
231    2.2.15 SMB2 CLOSE Request Flags
232    Flags to indicate how to process the operation
233    """
234    SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB = 0x01
235
236
237class ReadFlags(object):
238    """
239    [MS-SMB2] v53.0 2017-09-15
240
241    2.2.19 SMB2 READ Request Flags
242    Read flags for SMB 3.0.2 and newer dialects
243    """
244    SMB2_READFLAG_READ_UNBUFFERED = 0x01
245
246
247class ReadWriteChannel(object):
248    """
249    [MS-SMB2] v53.0 2017-09-15
250
251    2.2.19/21 SMB2 READ/Write Request Channel
252    Channel information for an SMB2 READ Request message
253    """
254    SMB2_CHANNEL_NONE = 0x0
255    SMB2_CHANNEL_RDMA_V1 = 0x1
256    SMB2_CHANNEL_RDMA_V1_INVALIDATE = 0x2
257
258
259class WriteFlags(object):
260    """
261    [MS-SMB2] v53.0 2017-09-15
262
263    2.2.21 SMB2 WRITE Request Flags
264    Flags to indicate how to process the operation
265    """
266    SMB2_WRITEFLAG_WRITE_THROUGH = 0x00000001
267    SMB2_WRITEFLAG_WRITE_UNBUFFERED = 0x00000002
268
269
270class QueryDirectoryFlags(object):
271    """
272    [MS-SMB2] v53.0 2017-09-15
273
274    2.2.33 SMB2 QUERY_DIRECTORY Request Flags
275    Indicates how the query directory operation MUST be processed.
276    """
277    SMB2_RESTART_SCANS = 0x01
278    SMB2_RETURN_SINGLE_ENTRY = 0x02
279    SMB2_INDEX_SPECIFIED = 0x04
280    SMB2_REOPEN = 0x10
281
282
283class QueryInfoFlags(object):
284    """
285    [MS-SMB2] 2.2.37 SMB2 QUERY_INFO Request - Flags
286    https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/d623b2f7-a5cd-4639-8cc9-71fa7d9f9ba9
287
288    Flags to set on a QUERY_INFO request.
289    """
290    SL_RESTART_SCAN = 0x00000001
291    SL_RETURN_SINGLE_ENTRY = 0x00000002
292    SL_INDEX_SPECIFIED = 0x00000004
293
294
295class InfoAdditionalInformation(object):
296    """
297    [MS-SMB2] 2.2.39 SMB2 SET_INFO Request - AdditionalInformation
298    https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ee9614c4-be54-4a3c-98f1-769a7032a0e4
299
300    If security information is being set, this value must contains one or more of the following flags.
301    """
302    OWNER_SECURTIY_INFORMATION = 0x00000001
303    GROUP_SECURITY_INFORMATION = 0x00000002
304    DACL_SECURITY_INFORMATION = 0x00000004
305    SACL_SECURITY_INFORMATION = 0x00000008
306    LABEL_SECURITY_INFORMATION = 0x00000010
307    ATTRIBUTE_SECURITY_INFORMATION = 0x00000020
308    SCOPE_SECURITY_INFORMATION = 0x00000040
309    BACKUP_SECURITY_INFORMATION = 0x00010000
310
311
312class SMB2CreateRequest(Structure):
313    """
314    [MS-SMB2] v53.0 2017-09-15
315
316    2.2.13 SMB2 CREATE Request
317    The SMB2 Create Request packet is sent by a client to request either
318    creation of or access to a file.
319    """
320    COMMAND = Commands.SMB2_CREATE
321
322    def __init__(self):
323        # pep 80 char issues force me to define this here
324        create_con_req = SMB2CreateContextRequest
325        self.fields = OrderedDict([
326            ('structure_size', IntField(
327                size=2,
328                default=57,
329            )),
330            ('security_flags', IntField(size=1)),
331            ('requested_oplock_level', EnumField(
332                size=1,
333                enum_type=RequestedOplockLevel
334            )),
335            ('impersonation_level', EnumField(
336                size=4,
337                enum_type=ImpersonationLevel
338            )),
339            ('smb_create_flags', IntField(size=8)),
340            ('reserved', IntField(size=8)),
341            ('desired_access', IntField(size=4)),
342            ('file_attributes', IntField(size=4)),
343            ('share_access', FlagField(
344                size=4,
345                flag_type=ShareAccess
346            )),
347            ('create_disposition', EnumField(
348                size=4,
349                enum_type=CreateDisposition
350            )),
351            ('create_options', FlagField(
352                size=4,
353                flag_type=CreateOptions
354            )),
355            ('name_offset', IntField(
356                size=2,
357                default=120  # (header size 64) + (structure size 56)
358            )),
359            ('name_length', IntField(
360                size=2,
361                default=lambda s: self._name_length(s)
362            )),
363            ('create_contexts_offset', IntField(
364                size=4,
365                default=lambda s: self._create_contexts_offset(s)
366            )),
367            ('create_contexts_length', IntField(
368                size=4,
369                default=lambda s: len(s['buffer_contexts'])
370            )),
371            # Technically these are all under buffer but we split it to make
372            # things easier
373            ('buffer_path', BytesField(
374                size=lambda s: self._buffer_path_size(s),
375            )),
376            ('padding', BytesField(
377                size=lambda s: self._padding_size(s),
378                default=lambda s: b"\x00" * self._padding_size(s)
379            )),
380            ('buffer_contexts', ListField(
381                size=lambda s: s['create_contexts_length'].get_value(),
382                list_type=StructureField(
383                    structure_type=create_con_req
384                ),
385                unpack_func=lambda s, d: self._buffer_context_list(s, d)
386            ))
387        ])
388        super(SMB2CreateRequest, self).__init__()
389
390    def _name_length(self, structure):
391        buffer_path = structure['buffer_path'].get_value()
392        return len(buffer_path) if buffer_path != b"\x00\x00" else 0
393
394    def _create_contexts_offset(self, structure):
395        if len(structure['buffer_contexts']) == 0:
396            return 0
397        else:
398            return structure['name_offset'].get_value() + \
399                len(structure['padding']) + len(structure['buffer_path'])
400
401    def _buffer_path_size(self, structure):
402        name_length = structure['name_length'].get_value()
403        return name_length if name_length != 0 else 2
404
405    def _padding_size(self, structure):
406        # no padding is needed if there are no contexts
407        if structure['create_contexts_length'].get_value() == 0:
408            return 0
409
410        mod = structure['name_length'].get_value() % 8
411        return 0 if mod == 0 else 8 - mod
412
413    def _buffer_context_list(self, structure, data):
414        context_list = []
415        last_context = data == b""
416        while not last_context:
417            create_context = SMB2CreateContextRequest()
418            data = create_context.unpack(data)
419            context_list.append(create_context)
420            last_context = create_context['next'].get_value() == 0
421
422        return context_list
423
424
425class SMB2CreateResponse(Structure):
426    """
427    [MS-SMB2] v53.0 2017-09-15
428
429    2.2.14 SMB2 CREATE Response
430    The SMB2 Create Response packet is sent by the server to an SMB2 CREATE
431    Request.
432    """
433    COMMAND = Commands.SMB2_CREATE
434
435    def __init__(self):
436        create_con_req = SMB2CreateContextRequest
437        self.fields = OrderedDict([
438            ('structure_size', IntField(
439                size=2,
440                default=89
441            )),
442            ('oplock_level', EnumField(
443                size=1,
444                enum_type=RequestedOplockLevel
445            )),
446            ('flag', FlagField(
447                size=1,
448                flag_type=FileFlags
449            )),
450            ('create_action', EnumField(
451                size=4,
452                enum_type=CreateAction
453            )),
454            ('creation_time', DateTimeField(size=8)),
455            ('last_access_time', DateTimeField(size=8)),
456            ('last_write_time', DateTimeField(size=8)),
457            ('change_time', DateTimeField(size=8)),
458            ('allocation_size', IntField(size=8)),
459            ('end_of_file', IntField(size=8)),
460            ('file_attributes', FlagField(
461                size=4,
462                flag_type=FileAttributes,
463                flag_strict=False,
464            )),
465            ('reserved2', IntField(size=4)),
466            ('file_id', BytesField(size=16)),
467            ('create_contexts_offset', IntField(
468                size=4,
469                default=lambda s: self._create_contexts_offset(s)
470            )),
471            ('create_contexts_length', IntField(
472                size=4,
473                default=lambda s: len(s['buffer'])
474            )),
475            ('buffer', ListField(
476                size=lambda s: s['create_contexts_length'].get_value(),
477                list_type=StructureField(
478                    structure_type=create_con_req
479                ),
480                unpack_func=lambda s, d: self._buffer_context_list(s, d)
481            ))
482        ])
483        super(SMB2CreateResponse, self).__init__()
484
485    def _create_contexts_offset(self, structure):
486        if len(structure['buffer']) == 0:
487            return 0
488        else:
489            return 152
490
491    def _buffer_context_list(self, structure, data):
492        context_list = []
493        last_context = data == b""
494        while not last_context:
495            create_context = SMB2CreateContextRequest()
496            data = create_context.unpack(data)
497            context_list.append(create_context)
498            # Manually make sure the final padding is present
499            create_context['padding2'] = b"\x00" * create_context._padding2_size(create_context)
500            last_context = create_context['next'].get_value() == 0
501
502        return context_list
503
504
505class SMB2CloseRequest(Structure):
506    """
507    [MS-SMB2] v53.0 2017-09-15
508
509    2.2.15 SMB2 CLOSE Request
510    Used by the client to close an instance of a file
511    """
512    COMMAND = Commands.SMB2_CLOSE
513
514    def __init__(self):
515        self.fields = OrderedDict([
516            ('structure_size', IntField(
517                size=2,
518                default=24
519            )),
520            ('flags', FlagField(
521                size=2,
522                flag_type=CloseFlags
523            )),
524            ('reserved', IntField(size=4)),
525            ('file_id', BytesField(size=16)),
526        ])
527        super(SMB2CloseRequest, self).__init__()
528
529
530class SMB2CloseResponse(Structure):
531    """
532    [MS-SMB2] v53.0 2017-09-15
533
534    2.2.16 SMB2 CLOSE Response
535    The response of a SMB2 CLOSE Request
536    """
537    COMMAND = Commands.SMB2_CLOSE
538
539    def __init__(self):
540        self.fields = OrderedDict([
541            ('structure_size', IntField(
542                size=2,
543                default=60
544            )),
545            ('flags', FlagField(
546                size=2,
547                flag_type=CloseFlags
548            )),
549            ('reserved', IntField(size=4)),
550            ('creation_time', DateTimeField()),
551            ('last_access_time', DateTimeField()),
552            ('last_write_time', DateTimeField()),
553            ('change_time', DateTimeField()),
554            ('allocation_size', IntField(size=8)),
555            ('end_of_file', IntField(size=8)),
556            ('file_attributes', FlagField(
557                size=4,
558                flag_type=FileAttributes,
559                flag_strict=False,
560            ))
561        ])
562        super(SMB2CloseResponse, self).__init__()
563
564
565class SMB2FlushRequest(Structure):
566    """
567    [MS-SMB2] v53.0 2017-09-15
568
569    2.2.17 SMB2 FLUSH Request
570    Flush all cached file information for a specified open of a file to the
571    persistent store that backs the file.
572    """
573    COMMAND = Commands.SMB2_FLUSH
574
575    def __init__(self):
576        self.fields = OrderedDict([
577            ('structure_size', IntField(
578                size=2,
579                default=24
580            )),
581            ('reserved1', IntField(size=2)),
582            ('reserved2', IntField(size=4)),
583            ('file_id', BytesField(size=16)),
584        ])
585        super(SMB2FlushRequest, self).__init__()
586
587
588class SMB2FlushResponse(Structure):
589    """
590    [MS-SMB2] v53.0 2017-09-15
591
592    2.2.18 SMB2 FLUSH Response
593    SMB2 FLUSH Response packet sent by the server.
594    """
595    COMMAND = Commands.SMB2_FLUSH
596
597    def __init__(self):
598        self.fields = OrderedDict([
599            ('structure_size', IntField(
600                size=2,
601                default=4
602            )),
603            ('reserved', IntField(size=2))
604        ])
605        super(SMB2FlushResponse, self).__init__()
606
607
608class SMB2ReadRequest(Structure):
609    """
610    [MS-SMB2] v53.0 2017-09-15
611
612    2.2.19 SMB2 READ Request
613    The request is used to run a read operation on the file specified.
614    """
615    COMMAND = Commands.SMB2_READ
616
617    def __init__(self):
618        self.fields = OrderedDict([
619            ('structure_size', IntField(
620                size=2,
621                default=49
622            )),
623            ('padding', IntField(size=1)),
624            ('flags', FlagField(
625                size=1,
626                flag_type=ReadFlags
627            )),
628            ('length', IntField(
629                size=4
630            )),
631            ('offset', IntField(
632                size=8
633            )),
634            ('file_id', BytesField(size=16)),
635            ('minimum_count', IntField(
636                size=4
637            )),
638            ('channel', FlagField(
639                size=4,
640                flag_type=ReadWriteChannel
641            )),
642            ('remaining_bytes', IntField(size=4)),
643            ('read_channel_info_offset', IntField(
644                size=2,
645                default=lambda s: self._get_read_channel_info_offset(s)
646            )),
647            ('read_channel_info_length', IntField(
648                size=2,
649                default=lambda s: self._get_read_channel_info_length(s)
650            )),
651            ('buffer', BytesField(
652                size=lambda s: self._get_buffer_length(s),
653                default=b"\x00"
654            ))
655        ])
656        super(SMB2ReadRequest, self).__init__()
657
658    def _get_read_channel_info_offset(self, structure):
659        if structure['channel'].get_value() == 0:
660            return 0
661        else:
662            return 64 + structure['structure_size'].get_value() - 1
663
664    def _get_read_channel_info_length(self, structure):
665        if structure['channel'].get_value() == 0:
666            return 0
667        else:
668            return len(structure['buffer'].get_value())
669
670    def _get_buffer_length(self, structure):
671        # buffer should contain 1 byte of \x00 and not be empty
672        if structure['channel'].get_value() == 0:
673            return 1
674        else:
675            return structure['read_channel_info_length'].get_value()
676
677
678class SMB2ReadResponse(Structure):
679    """
680    [MS-SMB2] v53.0 2017-09-15
681
682    2.2.20 SMB2 READ Response
683    Response to an SMB2 READ Request.
684    """
685    COMMAND = Commands.SMB2_READ
686
687    def __init__(self):
688        self.fields = OrderedDict([
689            ('structure_size', IntField(
690                size=2,
691                default=17
692            )),
693            ('data_offset', IntField(size=1)),
694            ('reserved', IntField(size=1)),
695            ('data_length', IntField(
696                size=4,
697                default=lambda s: len(s['buffer'])
698            )),
699            ('data_remaining', IntField(size=4)),
700            ('reserved2', IntField(size=4)),
701            ('buffer', BytesField(
702                size=lambda s: s['data_length'].get_value()
703            ))
704        ])
705        super(SMB2ReadResponse, self).__init__()
706
707
708class SMB2WriteRequest(Structure):
709    """
710    [MS-SMB2] v53.0 2017-09-15
711
712    2.2.21 SMB2 WRITE Request
713    A write packet to sent to an open file or named pipe on the server
714    """
715    COMMAND = Commands.SMB2_WRITE
716
717    def __init__(self):
718        self.fields = OrderedDict([
719            ('structure_size', IntField(
720                size=2,
721                default=49
722            )),
723            ('data_offset', IntField(  # offset to the buffer field
724                size=2,
725                default=0x70  # seems to be hardcoded to this value
726            )),
727            ('length', IntField(
728                size=4,
729                default=lambda s: len(s['buffer'])
730            )),
731            ('offset', IntField(size=8)),  # the offset in the file of the data
732            ('file_id', BytesField(size=16)),
733            ('channel', FlagField(
734                size=4,
735                flag_type=ReadWriteChannel
736            )),
737            ('remaining_bytes', IntField(size=4)),
738            ('write_channel_info_offset', IntField(
739                size=2,
740                default=lambda s: self._get_write_channel_info_offset(s)
741            )),
742            ('write_channel_info_length', IntField(
743                size=2,
744                default=lambda s: len(s['buffer_channel_info'])
745            )),
746            ('flags', FlagField(
747                size=4,
748                flag_type=WriteFlags
749            )),
750            ('buffer', BytesField(
751                size=lambda s: s['length'].get_value()
752            )),
753            ('buffer_channel_info', BytesField(
754                size=lambda s: s['write_channel_info_length'].get_value()
755            ))
756        ])
757        super(SMB2WriteRequest, self).__init__()
758
759    def _get_write_channel_info_offset(self, structure):
760        if len(structure['buffer_channel_info']) == 0:
761            return 0
762        else:
763            header_size = 64
764            packet_size = structure['structure_size'].get_value() - 1
765            buffer_size = len(structure['buffer'])
766            return header_size + packet_size + buffer_size
767
768
769class SMB2WriteResponse(Structure):
770    """
771    [MS-SMB2] v53.0 2017-09-15
772
773    2.2.22 SMB2 WRITE Response
774    The response to the SMB2 WRITE Request sent by the server
775    """
776    COMMAND = Commands.SMB2_WRITE
777
778    def __init__(self):
779        self.fields = OrderedDict([
780            ('structure_size', IntField(
781                size=2,
782                default=17
783            )),
784            ('reserved', IntField(size=2)),
785            ('count', IntField(size=4)),
786            ('remaining', IntField(size=4)),
787            ('write_channel_info_offset', IntField(size=2)),
788            ('write_channel_info_length', IntField(size=2))
789        ])
790        super(SMB2WriteResponse, self).__init__()
791
792
793class SMB2QueryDirectoryRequest(Structure):
794    """
795    [MS-SMB2] v53.0 2017-09-15
796
797    2.2.33 QUERY_DIRECTORY Request
798    Used by the client to obtain a directory enumeration on a directory open.
799    """
800    COMMAND = Commands.SMB2_QUERY_DIRECTORY
801
802    def __init__(self):
803        self.fields = OrderedDict([
804            ('structure_size', IntField(
805                size=2,
806                default=33
807            )),
808            ('file_information_class', EnumField(
809                size=1,
810                enum_type=FileInformationClass
811            )),
812            ('flags', FlagField(
813                size=1,
814                flag_type=QueryDirectoryFlags
815            )),
816            ('file_index', IntField(size=4)),
817            ('file_id', BytesField(size=16)),
818            ('file_name_offset', IntField(
819                size=2,
820                default=lambda s: 0 if len(s['buffer']) == 0 else 96
821            )),
822            ('file_name_length', IntField(
823                size=2,
824                default=lambda s: len(s['buffer'])
825            )),
826            ('output_buffer_length', IntField(size=4)),
827            # UTF-16-LE encoded search pattern
828            ('buffer', BytesField(
829                size=lambda s: s['file_name_length'].get_value()
830            ))
831        ])
832        super(SMB2QueryDirectoryRequest, self).__init__()
833
834    @staticmethod
835    def unpack_response(file_information_class, buffer):
836        """
837        Pass in the buffer value from the response object to unpack it and
838        return a list of query response structures for the request.
839
840        :param file_information_class: The info class that represents the
841            buffer.
842        :param buffer: The raw bytes value of the SMB2QueryDirectoryResponse
843            buffer field.
844        :return: List of query_info.* structures based on the
845            FileInformationClass used in the initial query request.
846        """
847        resp_structure = {
848            FileInformationClass.FILE_DIRECTORY_INFORMATION: FileDirectoryInformation,
849            FileInformationClass.FILE_NAMES_INFORMATION: FileNamesInformation,
850            FileInformationClass.FILE_BOTH_DIRECTORY_INFORMATION: FileBothDirectoryInformation,
851            FileInformationClass.FILE_ID_BOTH_DIRECTORY_INFORMATION: FileIdBothDirectoryInformation,
852            FileInformationClass.FILE_FULL_DIRECTORY_INFORMATION: FileFullDirectoryInformation,
853            FileInformationClass.FILE_ID_FULL_DIRECTORY_INFORMATION: FileIdFullDirectoryInformation,
854        }[file_information_class]
855        query_results = []
856
857        current_offset = 0
858        is_next = True
859        while is_next:
860            result = resp_structure()
861            result.unpack(buffer[current_offset:])
862            query_results.append(result)
863            current_offset += result['next_entry_offset'].get_value()
864            is_next = result['next_entry_offset'].get_value() != 0
865
866        return query_results
867
868
869class SMB2QueryDirectoryResponse(Structure):
870    """
871    [MS-SMB2] v53.0 2017-09-15
872
873    2.2.34 SMB2 QUERY_DIRECTORY Response
874    Response to an SMB2 QUERY_DIRECTORY Request.
875    """
876
877    COMMAND = Commands.SMB2_QUERY_DIRECTORY
878
879    def __init__(self):
880        self.fields = OrderedDict([
881            ('structure_size', IntField(
882                size=2,
883                default=9
884            )),
885            ('output_buffer_offset', IntField(
886                size=2,
887                default=72
888            )),
889            ('output_buffer_length', IntField(
890                size=4,
891                default=lambda s: len(s['buffer'])
892            )),
893            # this structure varies based on the requested information class
894            ('buffer', BytesField(
895                size=lambda s: s['output_buffer_length'].get_value()
896            ))
897        ])
898        super(SMB2QueryDirectoryResponse, self).__init__()
899
900
901class SMB2QueryInfoRequest(Structure):
902    """
903    [MS-SMB2] 2.2.37 SMB2 QUERY_INFO Request
904    https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/d623b2f7-a5cd-4639-8cc9-71fa7d9f9ba9
905
906    Sent by a client to request information on a file, named pipe, or underlying volume.
907    """
908    COMMAND = Commands.SMB2_QUERY_INFO
909
910    def __init__(self):
911        self.fields = OrderedDict([
912            ('structure_size', IntField(
913                size=2,
914                default=41,
915            )),
916            ('info_type', EnumField(
917                size=1,
918                enum_type=InfoType,
919            )),
920            ('file_info_class', EnumField(
921                size=1,
922                enum_type=FileInformationClass,
923                default=FileInformationClass.FILE_NONE
924            )),
925            ('output_buffer_length', IntField(
926                size=4,
927                default=lambda s: 0,
928            )),
929            ('input_buffer_offset', IntField(
930                size=2,
931                default=lambda s: 0 if s['input_buffer_length'].get_value() == 0 else 104,
932            )),
933            ('reserved', IntField(size=2)),
934            ('input_buffer_length', IntField(
935                size=4,
936                default=lambda s: len(s['buffer']),
937            )),
938            ('additional_information', IntField(size=4)),
939            ('flags', FlagField(
940                size=4,
941                flag_type=QueryInfoFlags,
942            )),
943            ('file_id', BytesField(size=16)),
944            ('buffer', BytesField(
945                size=lambda s: s['input_buffer_length'].get_value(),
946            )),
947        ])
948        super(SMB2QueryInfoRequest, self).__init__()
949
950
951class SMB2QueryInfoResponse(Structure):
952    """
953    [MS-SMB2] 2.2.38 SMB2 QUERY_INFO Response
954    https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/3b1b3598-a898-44ca-bfac-2dcae065247f
955
956    Sent by the server in response to an SMB2QueryInfoRequest.
957    """
958    COMMAND = Commands.SMB2_QUERY_INFO
959
960    def __init__(self):
961        self.fields = OrderedDict([
962            ('structure_size', IntField(
963                size=2,
964                default=9,
965            )),
966            ('output_buffer_offset', IntField(
967                size=2,
968                default=72,
969            )),
970            ('output_buffer_length', IntField(
971                size=4,
972                default=lambda s: len(s['buffer']),
973            )),
974            ('buffer', BytesField(
975                size=lambda s: s['output_buffer_length'].get_value(),
976            )),
977        ])
978        super(SMB2QueryInfoResponse, self).__init__()
979
980    def parse_buffer(self, file_info_type):
981        buffer = self['buffer'].get_value()
982
983        def unpack_list(buffer, byte_boundary):
984            info_list = []
985            while buffer:
986                entry = file_info_type()
987                buffer = entry.unpack(buffer)
988
989                padded_size = len(entry) % byte_boundary
990                buffer_offset = (byte_boundary - padded_size) if padded_size else 0
991                buffer = buffer[buffer_offset:]
992
993                info_list.append(entry)
994
995            return info_list
996
997        file_obj = file_info_type()
998        if isinstance(file_obj, FileFullEaInformation):
999            return unpack_list(buffer, 4)
1000        elif isinstance(file_obj, FileStreamInformation):
1001            return unpack_list(buffer, 8)
1002        else:
1003            file_obj.unpack(buffer)
1004            return file_obj
1005
1006
1007class SMB2SetInfoRequest(Structure):
1008    """
1009    [MS-SMB2] 2.2.39 SMB2 SET_INFO Request
1010    https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ee9614c4-be54-4a3c-98f1-769a7032a0e4
1011
1012    Sent by a client to set information on a file or underlying object store.
1013    """
1014    COMMAND = Commands.SMB2_SET_INFO
1015
1016    def __init__(self):
1017        self.fields = OrderedDict([
1018            ('structure_size', IntField(
1019                size=2,
1020                default=33,
1021            )),
1022            ('info_type', EnumField(
1023                size=1,
1024                enum_type=InfoType,
1025            )),
1026            ('file_info_class', EnumField(
1027                size=1,
1028                enum_type=FileInformationClass,
1029                default=FileInformationClass.FILE_NONE
1030            )),
1031            ('buffer_length', IntField(
1032                size=4,
1033                default=lambda s: len(s['buffer']),
1034            )),
1035            ('buffer_offset', IntField(
1036                size=2,
1037                default=96
1038            )),
1039            ('reserved', IntField(size=2)),
1040            ('additional_information', FlagField(
1041                size=4,
1042                flag_type=InfoAdditionalInformation,
1043            )),
1044            ('file_id', BytesField(size=16)),
1045            ('buffer', BytesField(
1046                size=lambda s: s['buffer_length'].get_value()
1047            ))
1048        ])
1049        super(SMB2SetInfoRequest, self).__init__()
1050
1051
1052class SMB2SetInfoResponse(Structure):
1053    """
1054    [MS-SMB2] 2.2.40 SMB2 SET_INFO Response
1055    https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/c4318eb4-bdab-49b7-9352-abd7005c7f19
1056
1057    Sent by the server in response to an SMB2SetInfoRequest to notify the client that its request has been successfully
1058    processed.
1059    """
1060    COMMAND = Commands.SMB2_SET_INFO
1061
1062    def __init__(self):
1063        self.fields = OrderedDict([
1064            ('structure_size', IntField(
1065                size=2,
1066                default=2
1067            )),
1068        ])
1069        super(SMB2SetInfoResponse, self).__init__()
1070
1071
1072class Open(object):
1073
1074    def __init__(self, tree, name):
1075        """
1076        [MS-SMB2] v53.0 2017-09-15
1077
1078        3.2.1.6 Per Application Open of a File
1079        Attributes per each open of a file. A file can be a File, Pipe,
1080        Directory, or Printer
1081
1082        :param tree: The Tree (share) the file is located in.
1083        :param name: The name of the file, excluding the share path.
1084        """
1085        # properties available based on the file itself
1086        self._connected = False
1087        self.create_action = None
1088        self.creation_time = None
1089        self.last_access_time = None
1090        self.last_write_time = None
1091        self.change_time = None
1092        self.allocation_size = None
1093        self.end_of_file = None
1094        self.file_attributes = None
1095
1096        # properties used privately
1097        # set to { 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF } to allow message
1098        # compounding with the open as the first message, once opened this
1099        # will be overwritten by the open response
1100        self.file_id = b"\xff" * 16
1101        self.tree_connect = tree
1102        self.connection = tree.session.connection
1103        self.oplock_level = None
1104        self.durable = None
1105        self.file_name = name
1106        self.resilient_handle = None
1107        self.last_disconnect_time = None
1108        self.resilient_timeout = None
1109
1110        # an array of entries used to maintain information about outstanding
1111        # lock and unlock operations performed on resilient Opens. Contains
1112        #     sequence_number - 4-bit integer modulo 16
1113        #     free - boolean value where False is no outstanding requests
1114        self.operation_buckets = []
1115
1116        # SMB 3.x+
1117        self.durable_timeout = None
1118
1119        # Table of outstanding requests, lookup by Request.cancel_id,
1120        # message_id
1121        self.outstanding_requests = {}
1122
1123        self.create_guid = None
1124        self.is_persistent = None
1125        self.desired_access = None
1126        self.share_mode = None
1127        self.create_options = None
1128        self.file_attributes = None
1129        self.create_disposition = None
1130
1131    @property
1132    def connected(self):
1133        return self._connected
1134
1135    def create(self, impersonation_level, desired_access, file_attributes,
1136               share_access, create_disposition, create_options,
1137               create_contexts=None,
1138               oplock_level=RequestedOplockLevel.SMB2_OPLOCK_LEVEL_NONE,
1139               send=True):
1140        """
1141        This will open the file based on the input parameters supplied. Any
1142        file open should also be called with Open.close() when it is finished.
1143
1144        More details on how each option affects the open process can be found
1145        here https://msdn.microsoft.com/en-us/library/cc246502.aspx.
1146
1147        Supports out of band send function, call this function with send=False
1148        to return a tuple of (SMB2CreateRequest, receive_func) instead of
1149        sending the the request and waiting for the response. The receive_func
1150        can be used to get the response from the server by passing in the
1151        Request that was used to sent it out of band.
1152
1153        :param impersonation_level: (ImpersonationLevel) The type of
1154            impersonation level that is issuing the create request.
1155        :param desired_access: The level of access that is required of the
1156            open. FilePipePrinterAccessMask or DirectoryAccessMask should be
1157            used depending on the type of file being opened.
1158        :param file_attributes: (FileAttributes) attributes to set on the file
1159            being opened, this usually is for opens that creates a file.
1160        :param share_access: (ShareAccess) Specifies the sharing mode for the
1161            open.
1162        :param create_disposition: (CreateDisposition) Defines the action the
1163            server MUST take if the file already exists.
1164        :param create_options: (CreateOptions) Specifies the options to be
1165            applied when creating or opening the file.
1166        :param create_contexts: (List<SMB2CreateContextRequest>) List of
1167            context request values to be applied to the create.
1168
1169        Create Contexts are used to encode additional flags and attributes when
1170        opening files. More details on create context request values can be
1171        found here https://msdn.microsoft.com/en-us/library/cc246504.aspx.
1172
1173        :param oplock_level: The requested oplock level of the request.
1174        :param send: Whether to send the request in the same call or return the
1175            message to the caller and the unpack function
1176
1177        :return: List of context response values or None if there are no
1178            context response values. If the context response value is not known
1179            to smbprotocol then the list value would be raw bytes otherwise
1180            it is a Structure defined in create_contexts.py
1181        """
1182        create = SMB2CreateRequest()
1183        create['requested_oplock_level'] = oplock_level
1184        create['impersonation_level'] = impersonation_level
1185        create['desired_access'] = desired_access
1186        create['file_attributes'] = file_attributes
1187        create['share_access'] = share_access
1188        create['create_disposition'] = create_disposition
1189        create['create_options'] = create_options
1190        if self.file_name == "":
1191            create['buffer_path'] = b"\x00\x00"
1192        else:
1193            create['buffer_path'] = self.file_name.encode('utf-16-le')
1194        if create_contexts:
1195            create['buffer_contexts'] = SMB2CreateContextRequest.pack_multiple(create_contexts)
1196
1197        if self.connection.dialect >= Dialects.SMB_3_0_0:
1198            self.desired_access = desired_access
1199            self.share_mode = share_access
1200            self.create_options = create_options
1201            self.file_attributes = file_attributes
1202            self.create_disposition = create_disposition
1203
1204        if not send:
1205            return create, self._create_response
1206
1207        log.info("Session: %s, Tree Connect: %s - sending SMB2 Create Request "
1208                 "for file %s" % (self.tree_connect.session.username,
1209                                  self.tree_connect.share_name,
1210                                  self.file_name))
1211
1212        log.debug(create)
1213        request = self.connection.send(create,
1214                                       self.tree_connect.session.session_id,
1215                                       self.tree_connect.tree_connect_id)
1216        return self._create_response(request)
1217
1218    def _create_response(self, request):
1219        log.info("Session: %s, Tree Connect: %s - receiving SMB2 Create "
1220                 "Response" % (self.tree_connect.session.username,
1221                               self.tree_connect.share_name))
1222        response = self.connection.receive(request)
1223        create_response = SMB2CreateResponse()
1224        create_response.unpack(response['data'].get_value())
1225
1226        # Manually set the length so the debug log won't fail due to some servers returning a padded value which is not
1227        # reflected in the padding2 of the context response.
1228        create_response['create_contexts_length'] = len(create_response['buffer'])
1229
1230        self._connected = True
1231        log.debug(create_response)
1232
1233        self.file_id = create_response['file_id'].get_value()
1234        self.tree_connect.session.open_table[self.file_id] = self
1235        self.oplock_level = create_response['oplock_level'].get_value()
1236        self.durable = False
1237        self.resilient_handle = False
1238        self.last_disconnect_time = 0
1239
1240        self.create_action = create_response['create_action'].get_value()
1241        self.creation_time = create_response['creation_time'].get_value()
1242        self.last_access_time = create_response['last_access_time'].get_value()
1243        self.last_write_time = create_response['last_write_time'].get_value()
1244        self.change_time = create_response['change_time'].get_value()
1245        self.allocation_size = create_response['allocation_size'].get_value()
1246        self.end_of_file = create_response['end_of_file'].get_value()
1247        self.file_attributes = create_response['file_attributes'].get_value()
1248
1249        create_contexts_response = None
1250        if create_response['create_contexts_length'].get_value() > 0:
1251            create_contexts_response = []
1252            for context in create_response['buffer'].get_value():
1253                create_contexts_response.append(context.get_context_data())
1254
1255        return create_contexts_response
1256
1257    def read(self, offset, length, min_length=0, unbuffered=False, wait=True,
1258             send=True):
1259        """
1260        Reads from an opened file or pipe
1261
1262        Supports out of band send function, call this function with send=False
1263        to return a tuple of (SMB2ReadRequest, receive_func) instead of
1264        sending the the request and waiting for the response. The receive_func
1265        can be used to get the response from the server by passing in the
1266        Request that was used to sent it out of band.
1267
1268        :param offset: The offset to start the read of the file.
1269        :param length: The number of bytes to read from the offset.
1270        :param min_length: The minimum number of bytes to be read for a
1271            successful operation.
1272        :param unbuffered: Whether to the server should cache the read data at
1273            intermediate layers, only value for SMB 3.0.2 or newer
1274        :param wait: If send=True, whether to wait for a response if
1275            STATUS_PENDING was received from the server or fail.
1276        :param send: Whether to send the request in the same call or return the
1277            message to the caller and the unpack function
1278        :return: A byte string of the bytes read
1279        """
1280        if length > self.connection.max_read_size:
1281            raise SMBException("The requested read length %d is greater than "
1282                               "the maximum negotiated read size %d"
1283                               % (length, self.connection.max_read_size))
1284
1285        read = SMB2ReadRequest()
1286        read['length'] = length
1287        read['offset'] = offset
1288        read['minimum_count'] = min_length
1289        read['file_id'] = self.file_id
1290        read['padding'] = b"\x50"
1291
1292        if unbuffered:
1293            if self.connection.dialect < Dialects.SMB_3_0_2:
1294                raise SMBUnsupportedFeature(self.connection.dialect,
1295                                            Dialects.SMB_3_0_2,
1296                                            "SMB2_READFLAG_READ_UNBUFFERED",
1297                                            True)
1298            read['flags'].set_flag(ReadFlags.SMB2_READFLAG_READ_UNBUFFERED)
1299
1300        if not send:
1301            return read, self._read_response
1302
1303        log.info("Session: %s, Tree Connect ID: %s - sending SMB2 Read "
1304                 "Request for file %s" % (self.tree_connect.session.username,
1305                                          self.tree_connect.share_name,
1306                                          self.file_name))
1307        log.debug(read)
1308        request = self.connection.send(read,
1309                                       self.tree_connect.session.session_id,
1310                                       self.tree_connect.tree_connect_id)
1311        return self._read_response(request, wait)
1312
1313    def _read_response(self, request, wait=True):
1314        log.info("Session: %s, Tree Connect ID: %s - receiving SMB2 Read "
1315                 "Response" % (self.tree_connect.session.username,
1316                               self.tree_connect.share_name))
1317        response = self.connection.receive(request, wait=wait)
1318        read_response = SMB2ReadResponse()
1319        read_response.unpack(response['data'].get_value())
1320        log.debug(read_response)
1321
1322        return read_response['buffer'].get_value()
1323
1324    def write(self, data, offset=0, write_through=False, unbuffered=False,
1325              wait=True, send=True):
1326        """
1327        Writes data to an opened file.
1328
1329        Supports out of band send function, call this function with send=False
1330        to return a tuple of (SMBWriteRequest, receive_func) instead of
1331        sending the the request and waiting for the response. The receive_func
1332        can be used to get the response from the server by passing in the
1333        Request that was used to sent it out of band.
1334
1335        :param data: The bytes data to write.
1336        :param offset: The offset in the file to write the bytes at
1337        :param write_through: Whether written data is persisted to the
1338            underlying storage, not valid for SMB 2.0.2.
1339        :param unbuffered: Whether to the server should cache the write data at
1340            intermediate layers, only value for SMB 3.0.2 or newer
1341        :param wait: If send=True, whether to wait for a response if
1342            STATUS_PENDING was received from the server or fail.
1343        :param send: Whether to send the request in the same call or return the
1344            message to the caller and the unpack function
1345        :return: The number of bytes written
1346        """
1347        data_len = len(data)
1348        if data_len > self.connection.max_write_size:
1349            raise SMBException("The requested write length %d is greater than "
1350                               "the maximum negotiated write size %d"
1351                               % (data_len, self.connection.max_write_size))
1352
1353        write = SMB2WriteRequest()
1354        write['length'] = len(data)
1355        write['offset'] = offset
1356        write['file_id'] = self.file_id
1357        write['buffer'] = data
1358
1359        if write_through:
1360            if self.connection.dialect < Dialects.SMB_2_1_0:
1361                raise SMBUnsupportedFeature(self.connection.dialect,
1362                                            Dialects.SMB_2_1_0,
1363                                            "SMB2_WRITEFLAG_WRITE_THROUGH",
1364                                            True)
1365            write['flags'].set_flag(WriteFlags.SMB2_WRITEFLAG_WRITE_THROUGH)
1366
1367        if unbuffered:
1368            if self.connection.dialect < Dialects.SMB_3_0_2:
1369                raise SMBUnsupportedFeature(self.connection.dialect,
1370                                            Dialects.SMB_3_0_2,
1371                                            "SMB2_WRITEFLAG_WRITE_UNBUFFERED",
1372                                            True)
1373            write['flags'].set_flag(WriteFlags.SMB2_WRITEFLAG_WRITE_UNBUFFERED)
1374
1375        if not send:
1376            return write, self._write_response
1377
1378        log.info("Session: %s, Tree Connect: %s - sending SMB2 Write Request "
1379                 "for file %s" % (self.tree_connect.session.username,
1380                                  self.tree_connect.share_name,
1381                                  self.file_name))
1382        log.debug(write)
1383        request = self.connection.send(write,
1384                                       self.tree_connect.session.session_id,
1385                                       self.tree_connect.tree_connect_id)
1386        return self._write_response(request, wait)
1387
1388    def _write_response(self, request, wait=True):
1389        log.info("Session: %s, Tree Connect: %s - receiving SMB2 Write "
1390                 "Response" % (self.tree_connect.session.username,
1391                               self.tree_connect.share_name))
1392        response = self.connection.receive(request, wait=wait)
1393        write_response = SMB2WriteResponse()
1394        write_response.unpack(response['data'].get_value())
1395        log.debug(write_response)
1396
1397        return write_response['count'].get_value()
1398
1399    def flush(self, send=True):
1400        """
1401        A command sent by the client to request that a server flush all cached
1402        file information for the opened file.
1403
1404        Supports out of band send function, call this function with send=False
1405        to return a tuple of (SMB2FlushRequest, receive_func) instead of
1406        sending the the request and waiting for the response. The receive_func
1407        can be used to get the response from the server by passing in the
1408        Request that was used to sent it out of band.
1409
1410        :param send: Whether to send the request in the same call or return the
1411            message to the caller and the unpack function
1412        :return: The SMB2FlushResponse received from the server
1413        """
1414        flush = SMB2FlushRequest()
1415        flush['file_id'] = self.file_id
1416
1417        if not send:
1418            return flush, self._flush_response
1419
1420        log.info("Session: %s, Tree Connect: %s - sending SMB2 Flush Request "
1421                 "for file %s" % (self.tree_connect.session.username,
1422                                  self.tree_connect.share_name,
1423                                  self.file_name))
1424        log.debug(flush)
1425        request = self.connection.send(flush,
1426                                       self.tree_connect.session.session_id,
1427                                       self.tree_connect.tree_connect_id)
1428        return self._flush_response(request)
1429
1430    def _flush_response(self, request):
1431        log.info("Session: %s, Tree Connect: %s - receiving SMB2 Flush "
1432                 "Response" % (self.tree_connect.session.username,
1433                               self.tree_connect.share_name))
1434        response = self.connection.receive(request)
1435        flush_response = SMB2FlushResponse()
1436        flush_response.unpack(response['data'].get_value())
1437        log.debug(flush_response)
1438        return flush_response
1439
1440    def query_directory(self, pattern, file_information_class, flags=None,
1441                        file_index=0, max_output=MAX_PAYLOAD_SIZE, send=True):
1442        """
1443        Run a Query/Find on an opened directory based on the params passed in.
1444
1445        Supports out of band send function, call this function with send=False
1446        to return a tuple of (SMB2QueryDirectoryRequest, receive_func) instead
1447        of sending the the request and waiting for the response. The
1448        receive_func can be used to get the response from the server by passing
1449        in the Request that was used to sent it out of band.
1450
1451        :param pattern: The string pattern to use for the query, this pattern
1452            format is based on the SMB server but * is usually a wildcard
1453        :param file_information_class: FileInformationClass that defines the
1454            format of the result that is returned
1455        :param flags: QueryDirectoryFlags that control how the operation must
1456            be processed.
1457        :param file_index: If the flags SMB2_INDEX_SPECIFIED, this is the index
1458            the query should resume on, otherwise should be 0
1459        :param max_output: The maximum output size, defaulted to the max credit
1460            size but can be increased to reduced round trip operations.
1461        :param send: Whether to send the request in the same call or return the
1462            message to the caller and the unpack function
1463        :return: A list of structures defined in query_info.py, the list entry
1464            structure is based on the value of file_information_class in the
1465            request message
1466        """
1467        query = SMB2QueryDirectoryRequest()
1468        query['file_information_class'] = file_information_class
1469        query['flags'] = flags
1470        query['file_index'] = file_index
1471        query['file_id'] = self.file_id
1472        query['output_buffer_length'] = max_output
1473        query['buffer'] = pattern.encode('utf-16-le')
1474
1475        if not send:
1476            return query, self._query_directory_response
1477
1478        log.info("Session: %s, Tree Connect: %s - sending SMB2 Query "
1479                 "Directory Request for directory %s"
1480                 % (self.tree_connect.session.username,
1481                    self.tree_connect.share_name, self.file_name))
1482        log.debug(query)
1483        request = self.connection.send(query,
1484                                       self.tree_connect.session.session_id,
1485                                       self.tree_connect.tree_connect_id)
1486        return self._query_directory_response(request)
1487
1488    def _query_directory_response(self, request):
1489        log.info("Session: %s, Tree Connect: %s - receiving SMB2 Query "
1490                 "Response" % (self.tree_connect.session.username,
1491                               self.tree_connect.share_name))
1492        response = self.connection.receive(request)
1493        query_response = SMB2QueryDirectoryResponse()
1494        query_response.unpack(response['data'].get_value())
1495        log.debug(query_response)
1496
1497        query_request = SMB2QueryDirectoryRequest()
1498        query_request.unpack(request.message['data'].get_value())
1499        file_cl = query_request['file_information_class'].get_value()
1500        data = query_response['buffer'].get_value()
1501        results = SMB2QueryDirectoryRequest.unpack_response(file_cl, data)
1502        return results
1503
1504    def close(self, get_attributes=False, send=True):
1505        """
1506        Closes an opened file.
1507
1508        Supports out of band send function, call this function with send=False
1509        to return a tuple of (SMB2CloseRequest, receive_func) instead of
1510        sending the the request and waiting for the response. The receive_func
1511        can be used to get the response from the server by passing in the
1512        Request that was used to sent it out of band.
1513
1514        :param get_attributes: (Bool) whether to get the latest attributes on
1515            the close and set them on the Open object
1516        :param send: Whether to send the request in the same call or return the
1517            message to the caller and the unpack function
1518        :return: SMB2CloseResponse message received from the server
1519        """
1520        # it is already closed and this isn't for an out of band request
1521        if not self._connected and send:
1522            return
1523
1524        close = SMB2CloseRequest()
1525
1526        close['file_id'] = self.file_id
1527        if get_attributes:
1528            close['flags'] = CloseFlags.SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB
1529
1530        if not send:
1531            return close, self._close_response
1532
1533        log.info("Session: %s, Tree Connect: %s - sending SMB2 Close Request "
1534                 "for file %s" % (self.tree_connect.session.username,
1535                                  self.tree_connect.share_name,
1536                                  self.file_name))
1537        log.debug(close)
1538        request = self.connection.send(close,
1539                                       self.tree_connect.session.session_id,
1540                                       self.tree_connect.tree_connect_id)
1541        return self._close_response(request)
1542
1543    def _close_response(self, request):
1544        log.info("Session: %s, Tree Connect: %s - receiving SMB2 Close "
1545                 "Response" % (self.tree_connect.session.username,
1546                               self.tree_connect.share_name))
1547        try:
1548            response = self.connection.receive(request)
1549        except FileClosed:
1550            self._connected = False
1551            self.tree_connect.session.open_table.pop(self.file_id, None)
1552            return
1553
1554        c_resp = SMB2CloseResponse()
1555        c_resp.unpack(response['data'].get_value())
1556        log.debug(c_resp)
1557        self._connected = False
1558        del self.tree_connect.session.open_table[self.file_id]
1559
1560        # update the attributes if requested
1561        close_request = SMB2CloseRequest()
1562        close_request.unpack(request.message['data'].get_value())
1563        if close_request['flags'].has_flag(
1564                CloseFlags.SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB):
1565            self.creation_time = c_resp['creation_time'].get_value()
1566            self.last_access_time = c_resp['last_access_time'].get_value()
1567            self.last_write_time = c_resp['last_write_time'].get_value()
1568            self.change_time = c_resp['change_time'].get_value()
1569            self.allocation_size = c_resp['allocation_size'].get_value()
1570            self.end_of_file = c_resp['end_of_file'].get_value()
1571            self.file_attributes = c_resp['file_attributes'].get_value()
1572        return c_resp
1573