Signed-off-by: Aurelien Aptel <aaptel@xxxxxxxx> --- Makefile.am | 3 +- smbinfo | 779 +++++++++++++++++++++++++++++++++++ smbinfo.c | 1296 ----------------------------------------------------------- 3 files changed, 780 insertions(+), 1298 deletions(-) create mode 100755 smbinfo delete mode 100644 smbinfo.c diff --git a/Makefile.am b/Makefile.am index 8291b99..33a420c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -80,8 +80,7 @@ setcifsacl.rst: setcifsacl.rst.in endif if CONFIG_SMBINFO -bin_PROGRAMS += smbinfo -smbinfo_SOURCES = smbinfo.c +bin_SCRIPTS = smbinfo rst_man_pages += smbinfo.1 endif diff --git a/smbinfo b/smbinfo new file mode 100755 index 0000000..1be82c7 --- /dev/null +++ b/smbinfo @@ -0,0 +1,779 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# smbinfo is a cmdline tool to query SMB-specific file and fs +# information on a Linux SMB mount (cifs.ko). +# +# Copyright (C) 2019 Aurelien Aptel <aaptel@xxxxxxxx> +# Copyright (C) 2019 Ronnie Sahlberg <lsahlberg@xxxxxxxxxx> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import re +import argparse +import fcntl +import struct +import stat +import datetime + +VERBOSE = False + +# ioctl ctl codes +CIFS_QUERY_INFO = 0xc018cf07 +CIFS_ENUMERATE_SNAPSHOTS = 0x800ccf06 +CIFS_DUMP_KEY = 0xc03acf08 + +# large enough input buffer length +INPUT_BUFFER_LENGTH = 16384 + +# cifs query flags +PASSTHRU_QUERY_INFO = 0x00000000 +PASSTHRU_FSCTL = 0x00000001 + +DIR_ACCESS_FLAGS = [ + (0x00000001, "LIST_DIRECTORY"), + (0x00000002, "ADD_FILE"), + (0x00000004, "ADD_SUBDIRECTORY"), + (0x00000008, "READ_EA"), + (0x00000010, "WRITE_EA"), + (0x00000020, "TRAVERSE"), + (0x00000040, "DELETE_CHILD"), + (0x00000080, "READ_ATTRIBUTES"), + (0x00000100, "WRITE_ATTRIBUTES"), + (0x00010000, "DELETE"), + (0x00020000, "READ_CONTROL"), + (0x00040000, "WRITE_DAC"), + (0x00080000, "WRITE_OWNER"), + (0x00100000, "SYNCHRONIZER"), + (0x01000000, "ACCESS_SYSTEM_SECURITY"), + (0x02000000, "MAXIMUM_ALLOWED"), + (0x10000000, "GENERIC_ALL"), + (0x20000000, "GENERIC_EXECUTE"), + (0x40000000, "GENERIC_WRITE"), + (0x80000000, "GENERIC_READ"), +] + +FILE_ACCESS_FLAGS = [ + (0x00000001, "READ_DATA"), + (0x00000002, "WRITE_DATA"), + (0x00000004, "APPEND_DATA"), + (0x00000008, "READ_EA"), + (0x00000010, "WRITE_EA"), + (0x00000020, "EXECUTE"), + (0x00000040, "DELETE_CHILD"), + (0x00000080, "READ_ATTRIBUTES"), + (0x00000100, "WRITE_ATTRIBUTES"), + (0x00010000, "DELETE"), + (0x00020000, "READ_CONTROL"), + (0x00040000, "WRITE_DAC"), + (0x00080000, "WRITE_OWNER"), + (0x00100000, "SYNCHRONIZER"), + (0x01000000, "ACCESS_SYSTEM_SECURITY"), + (0x02000000, "MAXIMUM_ALLOWED"), + (0x10000000, "GENERIC_ALL"), + (0x20000000, "GENERIC_EXECUTE"), + (0x40000000, "GENERIC_WRITE"), + (0x80000000, "GENERIC_READ"), +] + +FILE_ATTR_FLAGS = [ + (0x00000001, "READ_ONLY"), + (0x00000002, "HIDDEN"), + (0x00000004, "SYSTEM"), + (0x00000010, "DIRECTORY"), + (0x00000020, "ARCHIVE"), + (0x00000080, "NORMAL"), + (0x00000100, "TEMPORARY"), + (0x00000200, "SPARSE_FILE"), + (0x00000400, "REPARSE_POINT"), + (0x00000800, "COMPRESSED"), + (0x00001000, "OFFLINE"), + (0x00002000, "NOT_CONTENT_INDEXED"), + (0x00004000, "ENCRYPTED"), + (0x00008000, "INTEGRITY_STREAM"), + (0x00020000, "NO_SCRUB_DATA"), +] + +FILE_MODE_FLAGS = [ + (0x00000002, "WRITE_THROUGH"), + (0x00000004, "SEQUENTIAL_ONLY"), + (0x00000008, "NO_INTERMEDIATE_BUFFERING"), + (0x00000010, "SYNCHRONOUS_IO_ALERT"), + (0x00000020, "SYNCHRONOUS_IO_NONALERT"), + (0x00001000, "DELETE_ON_CLOSE"), +] + +ALIGN_TYPES = [ + (0, "BYTE_ALIGNMENT"), + (1, "WORD_ALIGNMENT"), + (3, "LONG_ALIGNMENT"), + (7, "QUAD_ALIGNMENT"), + (15, "OCTA_ALIGNMENT"), + (31, "32_bit_ALIGNMENT"), + (63, "64_bit_ALIGNMENT"), + (127, "128_bit_ALIGNMENT"), + (255, "254_bit_ALIGNMENT"), + (511, "512_bit_ALIGNMENT"), +] + +COMPRESSION_TYPES = [ + (0x0000, "NONE"), + (0x0002, "LZNT1"), +] + +CONTROL_FLAGS = [ + (0x8000, "SR"), + (0x4000, "RM"), + (0x2000, "PS"), + (0x1000, "PD"), + (0x0800, "SI"), + (0x0400, "DI"), + (0x0200, "SC"), + (0x0100, "DC"), + (0x0080, "DT"), + (0x0040, "SS"), + (0x0020, "SD"), + (0x0010, "SP"), + (0x0008, "DD"), + (0x0004, "DP"), + (0x0002, "GD"), + (0x0001, "OD"), +] + +ACE_TYPES = [ + (0x00, "ALLOWED"), + (0x01, "DENIED"), + (0x02, "AUDIT"), + (0x03, "ALARM"), + (0x04, "ALLOWED_COMPOUND"), + (0x05, "ALLOWED_OBJECT"), + (0x06, "DENIED_OBJECT"), + (0x07, "AUDIT_OBJECT"), + (0x08, "ALARM_OBJECT"), + (0x09, "ALLOWED_CALLBACK"), + (0x0a, "DENIED_CALLBACK"), + (0x0b, "ALLOWED_CALLBACK_OBJECT"), + (0x0c, "DENIED_CALLBACK_OBJECT"), + (0x0d, "AUDIT_CALLBACK"), + (0x0e, "ALARM_CALLBACK"), + (0x0f, "AUDIT_CALLBACK_OBJECT"), + (0x10, "ALARM_CALLBACK_OBJECT"), + (0x11, "MANDATORY_LABEL"), + (0x12, "RESOURCE_ATTRIBUTE"), + (0x13, "SCOPED_POLICY_ID"), +] + +ACE_FLAGS = [ + (0x80, "FAILED_ACCESS"), + (0x40, "SUCCESSFUL_ACCESS"), + (0x10, "INHERITED"), + (0x08, "INHERIT_ONLY"), + (0x04, "NO_PROPAGATE_INHERIT"), + (0x02, "CONTAINER_INHERIT"), + (0x01, "OBJECT_INHERIT"), +] + +CIPHER_TYPES = [ + (0x00, "SMB3.0 CCM encryption"), + (0x01, "CCM encryption"), + (0x02, "GCM encryption"), +] + +def main(): + # + # Global options and arguments + # + + ap = argparse.ArgumentParser(description="Display SMB-specific file information using cifs IOCTL") + ap.add_argument("-V", "--verbose", action="store_true", help="verbose output") + subp = ap.add_subparsers(help="sub-commands help") + subp.required = True + subp.dest = 'subcommand' + + # + # To add a new sub-command xxx, add a subparser xxx complete with + # help, options and/or arguments and implement cmd_xxx() + # + + sap = subp.add_parser("fileaccessinfo", help="Prints FileAccessInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_fileaccessinfo) + + sap = subp.add_parser("filealigninfo", help="Prints FileAlignInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_filealigninfo) + + sap = subp.add_parser("fileallinfo", help="Prints FileAllInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_fileallinfo) + + sap = subp.add_parser("filebasicinfo", help="Prints FileBasicInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_filebasicinfo) + + sap = subp.add_parser("fileeainfo", help="Prints FileEAInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_fileeainfo) + + sap = subp.add_parser("filefsfullsizeinfo", help="Prints FileFsFullSizeInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_filefsfullsizeinfo) + + sap = subp.add_parser("fileinternalinfo", help="Prints FileInternalInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_fileinternalinfo) + + sap = subp.add_parser("filemodeinfo", help="Prints FileModeInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_filemodeinfo) + + sap = subp.add_parser("filepositioninfo", help="Prints FilePositionInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_filepositioninfo) + + sap = subp.add_parser("filestandardinfo", help="Prints FileStandardInfo for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_filestandardinfo) + + sap = subp.add_parser("fsctl-getobjid", help="Prints the objectid of the file and GUID of the underlying volume.") + sap.add_argument("file") + sap.set_defaults(func=cmd_fsctl_getobjid) + + sap = subp.add_parser("getcompression", help="Prints the compression setting for the file") + sap.add_argument("file") + sap.set_defaults(func=cmd_getcompression) + + sap = subp.add_parser("setcompression", help="Sets the compression level for the file") + sap.add_argument("type", choices=['no','default','lznt1']) + sap.add_argument("file") + sap.set_defaults(func=cmd_setcompression) + + sap = subp.add_parser("list-snapshots", help="List the previous versions of the volume that backs this file") + sap.add_argument("file") + sap.set_defaults(func=cmd_list_snapshots) + + sap = subp.add_parser("quota", help="Prints the quota for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_quota) + + sap = subp.add_parser("secdesc", help="Prints the security descriptor for a cifs file") + sap.add_argument("file") + sap.set_defaults(func=cmd_secdesc) + + sap = subp.add_parser("keys", help="Prints the decryption information needed to view encrypted network traces") + sap.add_argument("file") + sap.set_defaults(func=cmd_keys) + + # parse arguments + args = ap.parse_args() + + # act on any global options + if args.verbose: + global VERBOSE + VERBOSE = True + + # call subcommand function + args.func(args) + +class QueryInfoStruct: + def __init__(self, + info_type=0, file_info_class=0, additional_information=0, + flags=0, input_buffer_length=0, output_buffer_length=0): + self.info_type = info_type + self.file_info_class = file_info_class + self.additional_information = additional_information + self.flags = flags + self.input_buffer_length = input_buffer_length + self.output_buffer_length = output_buffer_length + buf_size = max(self.input_buffer_length, self.output_buffer_length) + self.input_buffer = bytearray(buf_size) + + def pack_input(self, fmt, offset, *vals): + struct.pack_into(fmt, self.input_buffer, offset, *vals) + + def ioctl(self, fd, out_fmt=None): + buf = bytearray() + buf.extend(struct.pack("IIIIII", + self.info_type, + self.file_info_class, + self.additional_information, + self.flags, + self.input_buffer_length, + self.output_buffer_length)) + in_len = len(buf) + buf.extend(self.input_buffer) + fcntl.ioctl(fd, CIFS_QUERY_INFO, buf, True) + if out_fmt: + return struct.unpack_from(out_fmt, buf, in_len) + else: + return buf[in_len:] + +def flags_to_str(flags, bitlist, verbose=None): + if verbose is None: + verbose = VERBOSE + + if not verbose: + return "0x%08x"%flags + + out = [] + for bit, name in bitlist: + if flags & bit: + out.append(name) + + return "0x%08x (%s)"%(flags, ",".join(out)) + +def type_to_str(typ, typelist, verbose=None): + if verbose is None: + verbose = VERBOSE + + if not verbose: + return "0x%08x"%typ + + s = "Unknown" + for val, name in typelist: + if typ == val: + s = name + + return "0x%08x (%s)"%(typ, s) + +def cmd_fileaccessinfo(args): + qi = QueryInfoStruct(info_type=0x1, file_info_class=8, input_buffer_length=4) + try: + fd = os.open(args.file, os.O_RDONLY) + info = os.fstat(fd) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print_fileaccessinfo(buf, info) + +def print_fileaccessinfo(buf, info): + flags = struct.unpack_from('<I', buf, 0)[0] + if stat.S_ISDIR(info.st_mode): + print("Directory access flags:", flags_to_str(flags, DIR_ACCESS_FLAGS)) + else: + print("File/Printer access flags:", flags_to_str(flags, FILE_ACCESS_FLAGS)) + +def cmd_filealigninfo(args): + qi = QueryInfoStruct(info_type=0x1, file_info_class=17, input_buffer_length=4) + try: + fd = os.open(args.file, os.O_RDONLY) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print_filealigninfo(buf) + +def print_filealigninfo(buf): + mask = struct.unpack_from('<I', buf, 0)[0] + print("File alignment: %s"%type_to_str(mask, ALIGN_TYPES)) + +def cmd_fileallinfo(args): + qi = QueryInfoStruct(info_type=0x1, file_info_class=18, input_buffer_length=INPUT_BUFFER_LENGTH) + try: + fd = os.open(args.file, os.O_RDONLY) + info = os.fstat(fd) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print_filebasicinfo(buf) + print_filestandardinfo(buf[40:]) + print_fileinternalinfo(buf[64:]) + print_fileeainfo(buf[72:]) + print_fileaccessinfo(buf[76:], info) + print_filepositioninfo(buf[80:]) + print_filemodeinfo(buf[88:]) + print_filealigninfo(buf[92:]) + +def win_to_datetime(smb2_time): + usec = (smb2_time / 10) % 1000000 + sec = (smb2_time - 116444736000000000) // 10000000 + return datetime.datetime.fromtimestamp(sec + usec/10000000) + +def cmd_filebasicinfo(args): + qi = QueryInfoStruct(info_type=0x1, file_info_class=4, input_buffer_length=40) + try: + fd = os.open(args.file, os.O_RDONLY) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print_filebasicinfo(buf) + +def print_filebasicinfo(buf): + ctime, atime, wtime, mtime, attrs = struct.unpack_from('<QQQQI', buf, 0) + print("Creation Time: %s"%win_to_datetime(ctime)) + print("Last Access Time: %s"%win_to_datetime(atime)) + print("Last Write Time: %s"%win_to_datetime(wtime)) + print("Last Change Time: %s"%win_to_datetime(mtime)) + print("File Attributes: %s"%flags_to_str(attrs, FILE_ATTR_FLAGS)) + +def cmd_fileeainfo(args): + qi = QueryInfoStruct(info_type=0x1, file_info_class=7, input_buffer_length=4) + try: + fd = os.open(args.file, os.O_RDONLY) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print_fileeainfo(buf) + +def print_fileeainfo(buf): + size = struct.unpack_from('<I', buf, 0)[0] + print("EA Size: %d"%size) + +def cmd_filefsfullsizeinfo(args): + qi = QueryInfoStruct(info_type=0x2, file_info_class=7, input_buffer_length=32) + try: + fd = os.open(args.file, os.O_RDONLY) + total, caller_avail, actual_avail, sec_per_unit, byte_per_sec = qi.ioctl(fd, CIFS_QUERY_INFO, '<QQQII') + except Exception as e: + print("syscall failed: %s"%e) + return False + + print("Total Allocation Units: %d"%total) + print("Caller Available Allocation Units: %d"%caller_avail) + print("Actual Available Allocation Units: %d"%actual_avail) + print("Sectors Per Allocation Unit: %d"%sec_per_unit) + print("Bytes Per Sector: %d"%byte_per_sec) + +def cmd_fileinternalinfo(args): + qi = QueryInfoStruct(info_type=0x1, file_info_class=6, input_buffer_length=8) + try: + fd = os.open(args.file, os.O_RDONLY) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print_fileinternalinfo(buf) + +def print_fileinternalinfo(buf): + index = struct.unpack_from('<Q', buf, 0)[0] + print("Index Number: %d"%index) + + +def cmd_filemodeinfo(args): + qi = QueryInfoStruct(info_type=0x1, file_info_class=16, input_buffer_length=4) + try: + fd = os.open(args.file, os.O_RDONLY) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print_filemodeinfo(buf) + +def print_filemodeinfo(buf): + mode = struct.unpack_from('<I', buf, 0)[0] + print("Mode: %s"%flags_to_str(mode, FILE_MODE_FLAGS)) + +def cmd_filepositioninfo(args): + qi = QueryInfoStruct(info_type=0x1, file_info_class=14, input_buffer_length=8) + try: + fd = os.open(args.file, os.O_RDONLY) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print_filepositioninfo(buf) + +def print_filepositioninfo(buf): + offset = struct.unpack_from('<Q', buf, 0)[0] + print("Current Byte Offset: %d"%offset) + +def cmd_filestandardinfo(args): + qi = QueryInfoStruct(info_type=0x1, file_info_class=5, input_buffer_length=24) + try: + fd = os.open(args.file, os.O_RDONLY) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print_filestandardinfo(buf) + +def print_filestandardinfo(buf): + nalloc, eof, nlink, del_pending, del_dir = struct.unpack_from('<QQIBB', buf, 0) + print("Allocation Size: %d"%nalloc) + print("End Of File: %d"%eof) + print("Number of Links: %d"%nlink) + print("Delete Pending: %d"%del_pending) + print("Delete Directory: %d"%del_dir) + +def guid_to_str(buf): + return "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x"%struct.unpack_from('<ISSBBBBBBBB', buf, 0) + +def cmd_fsctl_getobjid(args): + qi = QueryInfoStruct(info_type=0x9009c, file_info_class=5, flags=PASSTHRU_FSCTL, input_buffer_length=64) + try: + fd = os.open(args.file, os.O_RDONLY) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print("Object-ID: %s"%guid_to_str(buf)) + print("Birth-Volume-ID: %s"%guid_to_str(buf[16:])) + print("Birth-Object-ID: %s"%guid_to_str(buf[32:])) + print("Domain-ID: %s"%guid_to_str(buf[48:])) + +def cmd_getcompression(args): + qi = QueryInfoStruct(info_type=0x9003c, flags=PASSTHRU_FSCTL, input_buffer_length=2) + try: + fd = os.open(args.file, os.O_RDONLY) + ctype = qi.ioctl(fd, CIFS_QUERY_INFO, '<H')[0] + except Exception as e: + print("syscall failed: %s"%e) + return False + + ctype_name = "UNKNOWN" + for val, name in COMPRESSION_TYPES: + if ctype == val: + ctype_name = name + break + print("Compression: %d (%s)"%(ctype, ctype_name)) + +def cmd_setcompression(args): + qi = QueryInfoStruct(info_type=0x9c040, flags=PASSTHRU_FSCTL, output_buffer_length=2) + type_map = {'no': 0, 'default': 1, 'lznt1': 2} + qi.pack_input('<H', 0, type_map[args.type]) + try: + fd = os.open(args.file, os.O_RDONLY) + qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + +class SnapshotArrayStruct: + def __init__(self, + nb_snapshots=0, + nb_snapshots_returned=0, + snapshot_array_size=12): + self.nb_snapshots = nb_snapshots + self.nb_snapshots_returned = nb_snapshots_returned + self.snapshot_array_size = snapshot_array_size + self.snapshot_array = [] + + def ioctl(self, fd, op): + buf = bytearray() + buf.extend(struct.pack("III", + self.nb_snapshots, + self.nb_snapshots_returned, + self.snapshot_array_size)) + + buf.extend(bytearray(16 + self.snapshot_array_size)) + fcntl.ioctl(fd, op, buf, True) + + out = SnapshotArrayStruct() + out.nb_snapshots, out.nb_snapshots_returned, out.snapshot_array_size = struct.unpack_from('III', buf, 0) + data = buf[12:] + + for gmt in re.findall(rb'''@([^\x00]+)''', data): + d = datetime.strptime("@GMT-%Y.%m.%d-%H.%M.%S") + out.snapshot_array.append(d) + + return out + +def datetime_to_smb(dt): + ntfs_time_offset = (369*365 + 89) * 24 * 3600 * 10000000 + return datetime.datetime.timestamp(dt) * 10000000 + ntfs_time_offset + +def cmd_list_snapshots(args): + sa1req = SnapshotArrayStruct() + sa1res = None + sa2req = None + sa2res = None + + try: + fd = os.open(args.file, os.O_RDONLY) + sa1res = sa1req.ioctl(fd, CIFS_ENUMERATE_SNAPSHOTS) + except Exception as e: + print("syscall failed: %s"%e) + return False + + if sa1res.nb_snapshots == 0: + return + + sa2req = SnapshotArrayStruct(nb_snapshots=sa1res.nb_snapshots, snapshot_array_size=sa1res.snapshot_array_size) + try: + fd = os.open(args.file, os.O_RDONLY) + sa2res = sa2req.ioctl(fd, CIFS_ENUMERATE_SNAPSHOTS) + except Exception as e: + print("syscall failed: %s"%e) + return False + + + print("Number of snapshots: %d Number of snapshots returned: %d"%(sa2res.nb_snapshots, sa2res.nb_snapshots_returned)) + print("Snapshot list in GMT (Coordinated UTC Time) and SMB format (100 nanosecond units needed for snapshot mounts):") + for i, d in enumerate(sa2res.snapshot_array): + print("%d) GMT: %s\n SMB3: %d"%(i, d, datetime_to_smb(d))) + +class SID: + def __init__(self, buf, off=0): + rev, sublen = struct.unpack_from('BB', buf, off+0) + off += 2 + auth = 0 + subauth = [] + for i in range(6): + auth = (auth << 8)|buf[off] + off += 1 + for i in range(sublen): + subauth.append(struct.unpack_from('<I', buf, off)) + off += 4 + + self.rev = rev + self.auth = auth + self.subauth = subauth + + def __str__(self): + auth = ("0x%x" if self.auth >= 2**32 else "%d")%self.auth + return "S-%d-%s-%s"%(self.rev, auth, '-'.join(["%d"%x for x in self.subauth])) + +class ACE: + def __init__(self, buf, off=0, is_dir=False): + self.typ, self.flags, self.size = struct.unpack_from('<BBH', buf, off) + self.is_dir = is_dir + if self.typ not in [0,1,2]: + self.buf = buf[4:] + else: + self.mask = struct.unpack_from('<I', buf, off+4)[0] + self.sid = SID(buf, off+8) + + def __str__(self): + s = [] + s.append("Type: %s" % type_to_str(self.typ, ACE_TYPES)) + s.append("Flags: %s" % flags_to_str(self.flags, ACE_FLAGS)) + if self.typ not in [0,1,2]: + s.append("<%s>"%(" ".join(["%02x"%x for x in self.buf]))) + else: + s.append("Mask: %s"%flags_to_str(self.mask, (DIR_ACCESS_FLAGS if self.is_dir else FILE_ACCESS_FLAGS))) + s.append("SID: %s"%self.sid) + return ", ".join(s) + +def cmd_quota(args): + qi = QueryInfoStruct(info_type=0x04, input_buffer_length=INPUT_BUFFER_LENGTH) + qi.pack_input('BBI', 0, + 0, # return single + 1, # restart scan + 0, # sid list length + ) + qi.output_buffer_length = 16 + buf = None + + try: + fd = os.open(args.file, os.O_RDONLY) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + off = 0 + while off < len(buf): + next_off = struct.unpack_from('<I', buf, off+ 0)[0] + sid_len = struct.unpack_from('<I', buf, off+ 4)[0] + atime = struct.unpack_from('<Q', buf, off+ 8)[0] + qused = struct.unpack_from('<Q', buf, off+16)[0] + qthresh = struct.unpack_from('<Q', buf, off+24)[0] + qlimit = struct.unpack_from('<Q', buf, off+32)[0] + sid = SID(buf, off+40) + + print("SID Length: %d"%sid_len) + print("Change Time: %s"%win_to_datetime(atime)) + print("Quota Used: %d"%qused) + print("Quota Threshold:", ("NO THRESHOLD" if qthresh == 0xffffffffffffffff else "%d"%qthresh)) + print("Quota Limit:", ("NO LIMIT" if qlimit == 0xffffffffffffffff else "%d"%qlimit)) + print("SID: %s"%sid) + + if next_off == 0: + break + off += next_off + +def cmd_secdesc(args): + qi = QueryInfoStruct(info_type=0x03, + additional_information=0x7, # owner, group, dacl + input_buffer_length=INPUT_BUFFER_LENGTH) + buf = None + info = None + + try: + fd = os.open(args.file, os.O_RDONLY) + info = os.fstat(fd) + buf = qi.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + is_dir = stat.S_ISDIR(info.st_mode) + rev, ctrl, off_owner, off_group, off_dacl = struct.unpack_from('<BxHIIxxxxI', buf, 0) + + print("Revision: %d"%rev) + print("Control: %s"%flags_to_str(ctrl, CONTROL_FLAGS)) + if off_owner: + print("Owner: %s"%SID(buf, off_owner)) + if off_group: + print("Group: %s"%SID(buf, off_group)) + if off_dacl: + print("DACL:") + rev, count = struct.unpack_from('<BxxxH', buf, off_dacl) + off_dacl += 8 + for i in range(count): + ace = ACE(buf, off_dacl, is_dir=is_dir) + print(ace) + off_dacl += ace.size + + +class KeyDebugInfoStruct: + def __init__(self): + self.suid = bytearray() + self.cipher = 0 + self.auth_key = bytearray() + self.enc_key = bytearray() + self.dec_key = bytearray() + + def ioctl(self, fd): + buf = bytearray() + buf.extend(struct.pack("= 8s H 16s 16s 16s", self.suid, self.cipher, + self.auth_key, self.enc_key, self.dec_key)) + fcntl.ioctl(fd, CIFS_DUMP_KEY, buf, True) + (self.suid, self.cipher, self.auth_key, + self.enc_key, self.dec_key) = struct.unpack_from('= 8s H 16s 16s 16s', buf, 0) + +def bytes_to_hex(buf): + return " ".join(["%02x"%x for x in buf]) + +def cmd_keys(args): + kd = KeyDebugInfoStruct() + try: + fd = os.open(args.file, os.O_RDONLY) + kd.ioctl(fd) + except Exception as e: + print("syscall failed: %s"%e) + return False + + print("Session Id: %s"%bytes_to_hex(kd.suid)) + print("Cipher: %s"%type_to_str(kd.cipher, CIPHER_TYPES, verbose=True)) + print("Session Key: %s"%bytes_to_hex(kd.auth_key)) + print("Encryption key: %s"%bytes_to_hex(kd.enc_key)) + print("Decryption key: %s"%bytes_to_hex(kd.dec_key)) + +if __name__ == '__main__': + main() diff --git a/smbinfo.c b/smbinfo.c deleted file mode 100644 index 636f1bd..0000000 --- a/smbinfo.c +++ /dev/null @@ -1,1296 +0,0 @@ -/* - * smbinfo - * - * Copyright (C) Ronnie Sahlberg (lsahlberg@xxxxxxxxxx) 2018 - * Copyright (C) Aurelien Aptel (aaptel@xxxxxxxx) 2018 - * - * Display SMB-specific file information using cifs IOCTL - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif /* HAVE_CONFIG_H */ - -#include <endian.h> -#include <errno.h> -#include <getopt.h> -#include <sys/ioctl.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <string.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <time.h> -#include <unistd.h> -#include <inttypes.h> - -#define CIFS_IOCTL_MAGIC 0xCF - -/* query_info flags */ -#define PASSTHRU_QUERY_INFO 0x00000000 -#define PASSTHRU_FSCTL 0x00000001 - -struct smb_query_info { - uint32_t info_type; - uint32_t file_info_class; - uint32_t additional_information; - uint32_t flags; - uint32_t input_buffer_length; - uint32_t output_buffer_length; - /* char buffer[]; */ -} __packed; - -#define SMB3_SIGN_KEY_SIZE 16 -struct smb3_key_debug_info { - uint64_t Suid; - uint16_t cipher_type; - uint8_t auth_key[16]; /* SMB2_NTLMV2_SESSKEY_SIZE */ - uint8_t smb3encryptionkey[SMB3_SIGN_KEY_SIZE]; - uint8_t smb3decryptionkey[SMB3_SIGN_KEY_SIZE]; -} __attribute__((packed)); - -#define CIFS_QUERY_INFO _IOWR(CIFS_IOCTL_MAGIC, 7, struct smb_query_info) -#define CIFS_DUMP_KEY _IOWR(CIFS_IOCTL_MAGIC, 8, struct smb3_key_debug_info) -#define INPUT_BUFFER_LENGTH 16384 - -int verbose; - -static void -usage(char *name) -{ - fprintf(stderr, "Usage: %s [-V] <command> <file>\n" - "-V for verbose output\n" - "-h display this help text\n" - "-v print smbinfo version\n" - "Commands are\n" - " fileaccessinfo:\n" - " Prints FileAccessInfo for a cifs file.\n" - " filealigninfo:\n" - " Prints FileAlignInfo for a cifs file.\n" - " fileallinfo:\n" - " Prints FileAllInfo for a cifs file.\n" - " filebasicinfo:\n" - " Prints FileBasicInfo for a cifs file.\n" - " fileeainfo:\n" - " Prints FileEAInfo for a cifs file.\n" - " filefsfullsizeinfo:\n" - " Prints FileFsFullSizeInfo for a cifs share.\n" - " fileinternalinfo:\n" - " Prints FileInternalInfo for a cifs file.\n" - " filemodeinfo:\n" - " Prints FileModeInfo for a cifs file.\n" - " filepositioninfo:\n" - " Prints FilePositionInfo for a cifs file.\n" - " filestandardinfo:\n" - " Prints FileStandardInfo for a cifs file.\n" - " fsctl-getobjid:\n" - " Prints the objectid of the file and GUID of the underlying volume.\n" - " getcompression:\n" - " Prints the compression setting for the file.\n" - " setcompression <no|default|lznt1>:\n" - " Sets the compression level for the file.\n" - " list-snapshots:\n" - " List the previous versions of the volume that backs this file.\n" - " quota:\n" - " Prints the quota for a cifs file.\n" - " secdesc:\n" - " Prints the security descriptor for a cifs file.\n" - " keys:\n" - " Prints the decryption information needed to view encrypted network traces.\n", - name); - exit(1); -} - -static void -short_usage(char *name) -{ - fprintf(stderr, "Usage: %s [-v] [-V] <command> <file>\n" - "Try 'smbinfo -h' for more information.\n", name); - exit(1); -} - -static void -win_to_timeval(uint64_t smb2_time, struct timeval *tv) -{ - tv->tv_usec = (smb2_time / 10) % 1000000; - tv->tv_sec = (smb2_time - 116444736000000000) / 10000000; -} - -struct bit_string { - unsigned int bit; - char *string; -}; - -struct bit_string directory_access_mask[] = { - { 0x00000001, "LIST_DIRECTORY" }, - { 0x00000002, "ADD_FILE" }, - { 0x00000004, "ADD_SUBDIRECTORY" }, - { 0x00000008, "READ_EA" }, - { 0x00000010, "WRITE_EA" }, - { 0x00000020, "TRAVERSE" }, - { 0x00000040, "DELETE_CHILD" }, - { 0x00000080, "READ_ATTRIBUTES" }, - { 0x00000100, "WRITE_ATTRIBUTES" }, - { 0x00010000, "DELETE" }, - { 0x00020000, "READ_CONTROL" }, - { 0x00040000, "WRITE_DAC" }, - { 0x00080000, "WRITE_OWNER" }, - { 0x00100000, "SYNCHRONIZER" }, - { 0x01000000, "ACCESS_SYSTEM_SECURITY" }, - { 0x02000000, "MAXIMUM_ALLOWED" }, - { 0x10000000, "GENERIC_ALL" }, - { 0x20000000, "GENERIC_EXECUTE" }, - { 0x40000000, "GENERIC_WRITE" }, - { 0x80000000, "GENERIC_READ" }, - { 0, NULL } -}; - -struct bit_string file_access_mask[] = { - { 0x00000001, "READ_DATA" }, - { 0x00000002, "WRITE_DATA" }, - { 0x00000004, "APPEND_DATA" }, - { 0x00000008, "READ_EA" }, - { 0x00000010, "WRITE_EA" }, - { 0x00000020, "EXECUTE" }, - { 0x00000040, "DELETE_CHILD" }, - { 0x00000080, "READ_ATTRIBUTES" }, - { 0x00000100, "WRITE_ATTRIBUTES" }, - { 0x00010000, "DELETE" }, - { 0x00020000, "READ_CONTROL" }, - { 0x00040000, "WRITE_DAC" }, - { 0x00080000, "WRITE_OWNER" }, - { 0x00100000, "SYNCHRONIZER" }, - { 0x01000000, "ACCESS_SYSTEM_SECURITY" }, - { 0x02000000, "MAXIMUM_ALLOWED" }, - { 0x10000000, "GENERIC_ALL" }, - { 0x20000000, "GENERIC_EXECUTE" }, - { 0x40000000, "GENERIC_WRITE" }, - { 0x80000000, "GENERIC_READ" }, - { 0, NULL } -}; - -static void -print_bits(uint32_t mask, struct bit_string *bs) -{ - int first = 1; - - if (!verbose) - return; - - while (bs->string) { - if (mask & bs->bit) { - printf("%s%s", first?"":",", bs->string); - first = 0; - } - bs++; - } - if (!first) - printf(" "); -} - -static void -print_guid(uint8_t *sd) -{ - uint32_t u32; - uint16_t u16; - int i; - - memcpy(&u32, &sd[0], 4); - printf("%08x-", le32toh(u32)); - - memcpy(&u16, &sd[4], 2); - printf("%04x-", le16toh(u16)); - - memcpy(&u16, &sd[6], 2); - printf("%04x-", le16toh(u16)); - - printf("%02x%02x-", sd[8], sd[9]); - for (i = 0; i < 6; i++) - printf("%02x", sd[10 + i]); -} - -static void -print_objidbuf(uint8_t *sd) -{ - printf("Object-ID: "); - print_guid(&sd[0]); - printf("\n"); - - printf("Birth-Volume-ID: "); - print_guid(&sd[16]); - printf("\n"); - - printf("Birth-Object-ID: "); - print_guid(&sd[32]); - printf("\n"); - - printf("Domain-ID: "); - print_guid(&sd[48]); - printf("\n"); -} - -static void -fsctlgetobjid(int f) -{ - struct smb_query_info *qi; - struct stat st; - - fstat(f, &st); - - qi = malloc(sizeof(struct smb_query_info) + 64); - memset(qi, 0, sizeof(qi) + 64); - qi->info_type = 0x9009c; - qi->file_info_class = 0; - qi->additional_information = 0; - qi->input_buffer_length = 64; - qi->flags = PASSTHRU_FSCTL; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - print_objidbuf((uint8_t *)(&qi[1])); - - free(qi); -} - -static void -print_getcompression(uint8_t *sd) -{ - uint16_t u16; - - memcpy(&u16, &sd[0], 2); - u16 = le16toh(u16); - - printf("Compression: "); - switch (u16) { - case 0: - printf("(0) NONE\n"); - break; - case 2: - printf("(2) LZNT1\n"); - break; - default: - printf("(%d) UNKNOWN\n", u16); - break; - } -} - -static void -getcompression(int f) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 2); - memset(qi, 0, sizeof(qi) + 2); - qi->info_type = 0x9003c; - qi->file_info_class = 0; - qi->additional_information = 0; - qi->input_buffer_length = 2; - qi->flags = PASSTHRU_FSCTL; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - print_getcompression((uint8_t *)(&qi[1])); - - free(qi); -} - -static void -setcompression(int f, uint16_t level) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 2); - memset(qi, 0, sizeof(qi) + 2); - qi->info_type = 0x9c040; - qi->file_info_class = 0; - qi->additional_information = 0; - qi->output_buffer_length = 2; - qi->flags = PASSTHRU_FSCTL; - - level = htole16(level); - memcpy(&qi[1], &level, 2); - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - free(qi); -} - -static void -print_fileaccessinfo(uint8_t *sd, int type) -{ - uint32_t access_flags; - - memcpy(&access_flags, &sd[0], 4); - access_flags = le32toh(access_flags); - - if (type == S_IFDIR) { - printf("Directory access flags 0x%08x: ", access_flags); - print_bits(access_flags, directory_access_mask); - } else { - printf("File/Printer access flags 0x%08x: ", access_flags); - print_bits(access_flags, file_access_mask); - } - printf("\n"); -} - -static void -fileaccessinfo(int f) -{ - struct smb_query_info *qi; - struct stat st; - - fstat(f, &st); - - qi = malloc(sizeof(struct smb_query_info) + 4); - memset(qi, 0, sizeof(qi) + 4); - qi->info_type = 0x01; - qi->file_info_class = 8; - qi->additional_information = 0; - qi->input_buffer_length = 4; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_fileaccessinfo((uint8_t *)(&qi[1]), st.st_mode & S_IFMT); - free(qi); -} - -static void -print_filealigninfo(uint8_t *sd) -{ - uint32_t mask; - - memcpy(&mask, &sd[0], 4); - mask = le32toh(mask); - - printf("File alignment: "); - if (mask == 0) - printf("BYTE_ALIGNMENT"); - else if (mask == 1) - printf("WORD_ALIGNMENT"); - else if (mask == 3) - printf("LONG_ALIGNMENT"); - else if (mask == 7) - printf("QUAD_ALIGNMENT"); - else if (mask == 15) - printf("OCTA_ALIGNMENT"); - else if (mask == 31) - printf("32_bit_ALIGNMENT"); - else if (mask == 63) - printf("64_bit_ALIGNMENT"); - else if (mask == 127) - printf("128_bit_ALIGNMENT"); - else if (mask == 255) - printf("254_bit_ALIGNMENT"); - else if (mask == 511) - printf("512_bit_ALIGNMENT"); - - printf("\n"); -} - -static void -filealigninfo(int f) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 4); - memset(qi, 0, sizeof(qi) + 4); - qi->info_type = 0x01; - qi->file_info_class = 17; - qi->additional_information = 0; - qi->input_buffer_length = 4; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_filealigninfo((uint8_t *)(&qi[1])); - free(qi); -} - -struct bit_string file_attributes_mask[] = { - { 0x00000001, "READ_ONLY" }, - { 0x00000002, "HIDDEN" }, - { 0x00000004, "SYSTEM" }, - { 0x00000010, "DIRECTORY" }, - { 0x00000020, "ARCHIVE" }, - { 0x00000080, "NORMAL" }, - { 0x00000100, "TEMPORARY" }, - { 0x00000200, "SPARSE_FILE" }, - { 0x00000400, "REPARSE_POINT" }, - { 0x00000800, "COMPRESSED" }, - { 0x00001000, "OFFLINE" }, - { 0x00002000, "NOT_CONTENT_INDEXED" }, - { 0x00004000, "ENCRYPTED" }, - { 0x00008000, "INTEGRITY_STREAM" }, - { 0x00020000, "NO_SCRUB_DATA" }, - { 0, NULL } -}; - -static void -print_filebasicinfo(uint8_t *sd) -{ - struct timeval tv; - uint64_t u64; - uint32_t u32; - - memcpy(&u64, &sd[0], 8); - win_to_timeval(le64toh(u64), &tv); - printf("Creation Time %s", ctime(&tv.tv_sec)); - - memcpy(&u64, &sd[8], 8); - win_to_timeval(le64toh(u64), &tv); - printf("Last Access Time %s", ctime(&tv.tv_sec)); - - memcpy(&u64, &sd[16], 8); - win_to_timeval(le64toh(u64), &tv); - printf("Last Write Time %s", ctime(&tv.tv_sec)); - - memcpy(&u64, &sd[24], 8); - win_to_timeval(le64toh(u64), &tv); - printf("Last Change Time %s", ctime(&tv.tv_sec)); - - memcpy(&u32, &sd[32], 4); - u32 = le32toh(u32); - printf("File Attributes 0x%08x: ", u32); - print_bits(u32, file_attributes_mask); - printf("\n"); -} - -static void -filebasicinfo(int f) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 40); - memset(qi, 0, sizeof(qi) + 40); - qi->info_type = 0x01; - qi->file_info_class = 4; - qi->additional_information = 0; - qi->input_buffer_length = 40; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_filebasicinfo((uint8_t *)(&qi[1])); - free(qi); -} - -static void -print_filestandardinfo(uint8_t *sd) -{ - uint64_t u64; - uint32_t u32; - - memcpy(&u64, &sd[0], 8); - printf("Allocation Size %" PRIu64 "\n", le64toh(u64)); - - memcpy(&u64, &sd[8], 8); - printf("End Of File %" PRIu64 "\n", le64toh(u64)); - - memcpy(&u32, &sd[16], 4); - printf("Number Of Links %" PRIu32 "\n", le32toh(u32)); - - printf("Delete Pending %d\n", sd[20]); - printf("Delete Directory %d\n", sd[21]); -} - -static void -filestandardinfo(int f) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 24); - memset(qi, 0, sizeof(qi) + 24); - qi->info_type = 0x01; - qi->file_info_class = 5; - qi->additional_information = 0; - qi->input_buffer_length = 24; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_filestandardinfo((uint8_t *)(&qi[1])); - free(qi); -} - -static void -print_fileinternalinfo(uint8_t *sd) -{ - uint64_t u64; - - memcpy(&u64, &sd[0], 8); - printf("Index Number %" PRIu64 "\n", le64toh(u64)); -} - -static void -fileinternalinfo(int f) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 8); - memset(qi, 0, sizeof(qi) + 8); - qi->info_type = 0x01; - qi->file_info_class = 6; - qi->additional_information = 0; - qi->input_buffer_length = 8; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_fileinternalinfo((uint8_t *)(&qi[1])); - free(qi); -} - -struct bit_string file_mode_mask[] = { - { 0x00000002, "WRITE_THROUGH" }, - { 0x00000004, "SEQUENTIAL_ONLY" }, - { 0x00000008, "NO_INTERMEDIATE_BUFFERING" }, - { 0x00000010, "SYNCHRONOUS_IO_ALERT" }, - { 0x00000020, "SYNCHRONOUS_IO_NONALERT" }, - { 0x00001000, "DELETE_ON_CLOSE" }, - { 0, NULL } -}; - -static void -print_filemodeinfo(uint8_t *sd) -{ - uint32_t u32; - - memcpy(&u32, &sd[32], 4); - u32 = le32toh(u32); - printf("Mode 0x%08x: ", u32); - print_bits(u32, file_mode_mask); - printf("\n"); -} - -static void -filemodeinfo(int f) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 4); - memset(qi, 0, sizeof(qi) + 4); - qi->info_type = 0x01; - qi->file_info_class = 16; - qi->additional_information = 0; - qi->input_buffer_length = 4; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_filemodeinfo((uint8_t *)(&qi[1])); - free(qi); -} - -static void -print_filepositioninfo(uint8_t *sd) -{ - uint64_t u64; - - memcpy(&u64, &sd[0], 8); - printf("Current Byte Offset %" PRIu64 "\n", le64toh(u64)); -} - -static void -filepositioninfo(int f) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 8); - memset(qi, 0, sizeof(qi) + 8); - qi->info_type = 0x01; - qi->file_info_class = 14; - qi->additional_information = 0; - qi->input_buffer_length = 8; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_filepositioninfo((uint8_t *)(&qi[1])); - free(qi); -} - -static void -print_fileeainfo(uint8_t *sd) -{ - uint32_t u32; - - memcpy(&u32, &sd[0], 4); - printf("Ea Size %" PRIu32 "\n", le32toh(u32)); -} - -static void -fileeainfo(int f) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 4); - memset(qi, 0, sizeof(qi) + 4); - qi->info_type = 0x01; - qi->file_info_class = 7; - qi->additional_information = 0; - qi->input_buffer_length = 4; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_fileeainfo((uint8_t *)(&qi[1])); - free(qi); -} - -static void -print_filefullsizeinfo(uint8_t *sd) -{ - uint32_t u32; - uint64_t u64; - - memcpy(&u64, &sd[0], 8); - printf("Total Allocation Units: %" PRIu64 "\n", le64toh(u64)); - - memcpy(&u64, &sd[8], 8); - printf("Caller Available Allocation Units: %" PRIu64 "\n", - le64toh(u64)); - - memcpy(&u64, &sd[16], 8); - printf("Actual Available Allocation Units: %" PRIu64 "\n", - le64toh(u64)); - - memcpy(&u32, &sd[24], 4); - printf("Sectors Per Allocation Unit: %" PRIu32 "\n", le32toh(u32)); - - memcpy(&u32, &sd[28], 4); - printf("Bytes Per Sector: %" PRIu32 "\n", le32toh(u32)); -} - -static void -filefsfullsizeinfo(int f) -{ - struct smb_query_info *qi; - - qi = malloc(sizeof(struct smb_query_info) + 32); - memset(qi, 0, sizeof(qi) + 32); - qi->info_type = 0x02; - qi->file_info_class = 7; - qi->additional_information = 0; - qi->input_buffer_length = 32; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_filefullsizeinfo((uint8_t *)(&qi[1])); - free(qi); -} - -static void -fileallinfo(int f) -{ - struct smb_query_info *qi; - struct stat st; - - fstat(f, &st); - - qi = malloc(sizeof(struct smb_query_info) + INPUT_BUFFER_LENGTH); - memset(qi, 0, sizeof(qi) + INPUT_BUFFER_LENGTH); - qi->info_type = 0x01; - qi->file_info_class = 18; - qi->additional_information = 0; - qi->input_buffer_length = INPUT_BUFFER_LENGTH; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_filebasicinfo((uint8_t *)(&qi[1])); - print_filestandardinfo((uint8_t *)(&qi[1]) + 40); - print_fileinternalinfo((uint8_t *)(&qi[1]) + 64); - print_fileeainfo((uint8_t *)(&qi[1]) + 72); - print_fileaccessinfo((uint8_t *)(&qi[1]) + 76, st.st_mode & S_IFMT); - print_filepositioninfo((uint8_t *)(&qi[1]) + 80); - print_filemodeinfo((uint8_t *)(&qi[1]) + 88); - print_filealigninfo((uint8_t *)(&qi[1]) + 92); - // SMB2 servers like Win16 does not seem to return name info - free(qi); -} - -static void -print_sid(unsigned char *sd) -{ - int i; - uint32_t subauth; - uint64_t idauth; - - if (sd[0] != 1) { - fprintf(stderr, "Unknown SID revision\n"); - return; - } - - idauth = 0; - for (i = 0; i < 6; i++) - idauth = (idauth << 8) | sd[2 + i]; - - printf("S-1-%" PRIu64, idauth); - for (i = 0; i < sd[1]; i++) { - memcpy(&subauth, &sd[8 + 4 * i], 4); - subauth = le32toh(subauth); - printf("-%u", subauth); - } -} - -static void -print_ace_type(uint8_t t) -{ - switch(t) { - case 0x00: printf("ALLOWED"); break; - case 0x01: printf("DENIED"); break; - case 0x02: printf("AUDIT"); break; - case 0x03: printf("ALARM"); break; - case 0x04: printf("ALLOWED_COMPOUND"); break; - case 0x05: printf("ALLOWED_OBJECT"); break; - case 0x06: printf("DENIED_OBJECT"); break; - case 0x07: printf("AUDIT_OBJECT"); break; - case 0x08: printf("ALARM_OBJECT"); break; - case 0x09: printf("ALLOWED_CALLBACK"); break; - case 0x0a: printf("DENIED_CALLBACK"); break; - case 0x0b: printf("ALLOWED_CALLBACK_OBJECT"); break; - case 0x0c: printf("DENIED_CALLBACK_OBJECT"); break; - case 0x0d: printf("AUDIT_CALLBACK"); break; - case 0x0e: printf("ALARM_CALLBACK"); break; - case 0x0f: printf("AUDIT_CALLBACK_OBJECT"); break; - case 0x10: printf("ALARM_CALLBACK_OBJECT"); break; - case 0x11: printf("MANDATORY_LABEL"); break; - case 0x12: printf("RESOURCE_ATTRIBUTE"); break; - case 0x13: printf("SCOPED_POLICY_ID"); break; - default: printf("<UNKNOWN>"); - } - printf(" "); -} - -struct bit_string ace_flags_mask[] = { - { 0x80, "FAILED_ACCESS" }, - { 0x40, "SUCCESSFUL_ACCESS" }, - { 0x10, "INHERITED" }, - { 0x08, "INHERIT_ONLY" }, - { 0x04, "NO_PROPAGATE_INHERIT" }, - { 0x02, "CONTAINER_INHERIT" }, - { 0x01, "OBJECT_INHERIT" }, - { 0, NULL } -}; - -static void -print_mask_sid_ace(unsigned char *sd, int type) -{ - uint32_t u32; - - memcpy(&u32, &sd[0], 4); - printf("Mask:0x%08x ", le32toh(u32)); - if (type == S_IFDIR) - print_bits(le32toh(u32), directory_access_mask); - else - print_bits(le32toh(u32), file_access_mask); - printf("SID:"); - print_sid(&sd[4]); - printf("\n"); -} - -static int -print_ace(unsigned char *sd, int type) -{ - uint16_t size; - int i; - - printf("Type:0x%02x ", sd[0]); - if (verbose) { - print_ace_type(sd[0]); - } - - printf("Flags:0x%02x ", sd[1]); - print_bits(sd[1], ace_flags_mask); - - memcpy(&size, &sd[2], 2); - size = le16toh(size); - - switch (sd[0]) { - case 0x00: - case 0x01: - case 0x02: - print_mask_sid_ace(&sd[4], type); - break; - default: - for (i = 0; i < size; i++) - printf("%02x", sd[4 + i]); - } - - printf("\n"); - return size; -} - -static void -print_acl(unsigned char *sd, int type) -{ - int i, off; - uint16_t count; - - if ((sd[0] != 2) && (sd[0] != 4)) { - fprintf(stderr, "Unknown ACL revision\n"); - return; - } - - memcpy(&count, &sd[4], 2); - count = le16toh(count); - off = 8; - for (i = 0; i < count; i++) - off += print_ace(&sd[off], type); -} - -struct bit_string control_bits_mask[] = { - { 0x8000, "SR" }, - { 0x4000, "RM" }, - { 0x2000, "PS" }, - { 0x1000, "PD" }, - { 0x0800, "SI" }, - { 0x0400, "DI" }, - { 0x0200, "SC" }, - { 0x0100, "DC" }, - { 0x0080, "DT" }, - { 0x0040, "SS" }, - { 0x0020, "SD" }, - { 0x0010, "SP" }, - { 0x0008, "DD" }, - { 0x0004, "DP" }, - { 0x0002, "GD" }, - { 0x0001, "OD" }, - { 0, NULL } -}; - -static void -print_control(uint16_t c) -{ - printf("Control: 0x%04x ", c); - print_bits(c, control_bits_mask); - printf("\n"); -} - -static void -print_sd(uint8_t *sd, int type) -{ - int offset_owner, offset_group, offset_dacl; - uint16_t u16; - - printf("Revision:%d\n", sd[0]); - if (sd[0] != 1) { - fprintf(stderr, "Unknown SD revision\n"); - exit(1); - } - - memcpy(&u16, &sd[2], 2); - print_control(le16toh(u16)); - - memcpy(&offset_owner, &sd[4], 4); - offset_owner = le32toh(offset_owner); - memcpy(&offset_group, &sd[8], 4); - offset_group = le32toh(offset_group); - memcpy(&offset_dacl, &sd[16], 4); - offset_dacl = le32toh(offset_dacl); - - if (offset_owner) { - printf("Owner: "); - print_sid(&sd[offset_owner]); - printf("\n"); - } - if (offset_group) { - printf("Group: "); - print_sid(&sd[offset_group]); - printf("\n"); - } - if (offset_dacl) { - printf("DACL:\n"); - print_acl(&sd[offset_dacl], type); - } -} - -static void -secdesc(int f) -{ - struct smb_query_info *qi; - struct stat st; - - fstat(f, &st); - - qi = malloc(sizeof(struct smb_query_info) + INPUT_BUFFER_LENGTH); - memset(qi, 0, sizeof(qi) + INPUT_BUFFER_LENGTH); - qi->info_type = 0x03; - qi->file_info_class = 0; - qi->additional_information = 0x00000007; /* Owner, Group, Dacl */ - qi->input_buffer_length = INPUT_BUFFER_LENGTH; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_sd((uint8_t *)(&qi[1]), st.st_mode & S_IFMT); - free(qi); -} - -static void -print_quota(unsigned char *sd) -{ - uint32_t u32, neo; - uint64_t u64; - struct timeval tv; - int i, off = 0; - -one_more: - memcpy(&u32, &sd[off], 4); - neo = le32toh(u32); - - memcpy(&u32, &sd[off + 4], 4); - u32 = le32toh(u32); - printf("SID Length %d\n", u32); - - memcpy(&u64, &sd[off + 8], 8); - win_to_timeval(le64toh(u64), &tv); - printf("Change Time %s", ctime(&tv.tv_sec)); - - memcpy(&u64, &sd[off + 16], 8); - u64 = le32toh(u64); - printf("Quota Used %" PRIu64 "\n", u64); - - memcpy(&u64, &sd[off + 24], 8); - u64 = le64toh(u64); - if (u64 == 0xffffffffffffffff) - printf("Quota Threshold NO THRESHOLD\n"); - else - printf("Quota Threshold %" PRIu64 "\n", u64); - - memcpy(&u64, &sd[off + 32], 8); - u64 = le64toh(u64); - if (u64 == 0xffffffffffffffff) - printf("Quota Limit NO LIMIT\n"); - else - printf("Quota Limit %" PRIu64 "\n", u64); - - printf("SID: S-1"); - u64 = 0; - for (i = 0; i < 6; i++) - u64 = (u64 << 8) | sd[off + 42 + i]; - printf("-%" PRIu64, u64); - - for (i = 0; i < sd[off + 41]; i++) { - memcpy(&u32, &sd[off + 48 + 4 * i], 4); - u32 = le32toh(u32); - printf("-%u", u32); - } - printf("\n\n"); - off += neo; - - if (neo != 0) - goto one_more; -} - -static void -quota(int f) -{ - struct smb_query_info *qi; - char *buf; - int i; - - qi = malloc(sizeof(struct smb_query_info) + INPUT_BUFFER_LENGTH); - memset(qi, 0, sizeof(struct smb_query_info) + INPUT_BUFFER_LENGTH); - qi->info_type = 0x04; - qi->file_info_class = 0; - qi->additional_information = 0; /* Owner, Group, Dacl */ - qi->input_buffer_length = INPUT_BUFFER_LENGTH; - - buf = (char *)&qi[1]; - buf[0] = 0; /* return single */ - buf[1] = 1; /* restart scan */ - - /* sid list length */ - i = 0; - memcpy(&buf[4], &i, 4); - - qi->output_buffer_length = 16; - - if (ioctl(f, CIFS_QUERY_INFO, qi) < 0) { - fprintf(stderr, "ioctl failed with %s\n", strerror(errno)); - exit(1); - } - - print_quota((unsigned char *)(&qi[1])); - free(qi); -} - - -struct smb_snapshot_array { - int32_t number_of_snapshots; - int32_t number_of_snapshots_returned; - int32_t snapshot_array_size; - char snapshot_data[0]; -}; - - -#define GMT_NAME_LEN 24 /* length of a @GMT- name */ -#define GMT_FORMAT "@GMT-%Y.%m.%d-%H.%M.%S" - -#define NTFS_TIME_OFFSET ((unsigned long long)(369*365 + 89) * 24 * 3600 * 10000000) - -static void print_snapshots(struct smb_snapshot_array *psnap) -{ - int current_snapshot_entry = 0; - char gmt_token[GMT_NAME_LEN + 1] = {0}; - int i; - int j = 0; - struct tm tm; - unsigned long long dce_time; - - printf("Number of snapshots: %d Number of snapshots returned: %d\n", - psnap->number_of_snapshots, - psnap->number_of_snapshots_returned); - printf("Snapshot list in GMT (Coordinated UTC Time) and SMB format (100 nanosecond units needed for snapshot mounts):"); - for (i = 0; i < psnap->snapshot_array_size; i++) { - if (psnap->snapshot_data[i] == '@') { - j = 0; - current_snapshot_entry++; - printf("\n%d) GMT:", current_snapshot_entry); - } - if (psnap->snapshot_data[i] != 0) { - gmt_token[j] = psnap->snapshot_data[i]; - j++; - } - if (j == GMT_NAME_LEN) { - printf("%s", gmt_token); - j = 0; - strptime(gmt_token, GMT_FORMAT, &tm); - dce_time = timegm(&tm) * 10000000 + NTFS_TIME_OFFSET; - printf("\n SMB3:%llu", dce_time); - } - } - printf("\n"); -} - -static void -dump_keys(int f) -{ - struct smb3_key_debug_info keys_info; - uint8_t *psess_id; - - if (ioctl(f, CIFS_DUMP_KEY, &keys_info) < 0) { - fprintf(stderr, "Querying keys information failed with %s\n", strerror(errno)); - exit(1); - } - - if (keys_info.cipher_type == 1) - printf("CCM encryption"); - else if (keys_info.cipher_type == 2) - printf("GCM encryption"); - else if (keys_info.cipher_type == 0) - printf("SMB3.0 CCM encryption"); - else - printf("unknown encryption type"); - - printf("\nSession Id: "); - psess_id = (uint8_t *)&keys_info.Suid; - for (int i = 0; i < 8; i++) - printf(" %02x", psess_id[i]); - - printf("\nSession Key: "); - for (int i = 0; i < 16; i++) - printf(" %02x", keys_info.auth_key[i]); - printf("\nServer Encryption Key: "); - for (int i = 0; i < SMB3_SIGN_KEY_SIZE; i++) - printf(" %02x", keys_info.smb3encryptionkey[i]); - printf("\nServer Decryption Key: "); - for (int i = 0; i < SMB3_SIGN_KEY_SIZE; i++) - printf(" %02x", keys_info.smb3decryptionkey[i]); - printf("\n"); -} - -#define CIFS_ENUMERATE_SNAPSHOTS _IOR(CIFS_IOCTL_MAGIC, 6, struct smb_snapshot_array) - -#define MIN_SNAPSHOT_ARRAY_SIZE 16 /* See MS-SMB2 section 3.3.5.15.1 */ - -static void -list_snapshots(int f) -{ - - struct smb_snapshot_array snap_inf; - struct smb_snapshot_array *buf; - - /* - * When first field in structure we pass in here is zero, cifs.ko can - * recognize that this is the first query and that it must set the SMB3 - * FSCTL response buffer size (in the request) to exactly 16 bytes - * (which is required by some servers to process the initial query) - */ - snap_inf.number_of_snapshots = 0; - snap_inf.number_of_snapshots_returned = 0; - snap_inf.snapshot_array_size = sizeof(struct smb_snapshot_array); - - /* Query the number of snapshots so we know how much to allocate */ - if (ioctl(f, CIFS_ENUMERATE_SNAPSHOTS, &snap_inf) < 0) { - fprintf(stderr, "Querying snapshots failed with %s\n", strerror(errno)); - exit(1); - } - - if (snap_inf.number_of_snapshots == 0) - return; - - /* Now that we know the size, query the list from the server */ - - buf = malloc(snap_inf.snapshot_array_size + MIN_SNAPSHOT_ARRAY_SIZE); - - if (buf == NULL) { - printf("Failed, out of memory.\n"); - exit(1); - } - /* - * first parm is non-zero which allows cifs.ko to recognize that this is - * the second query (it has to set response buf size larger) - */ - buf->number_of_snapshots = snap_inf.number_of_snapshots; - - buf->snapshot_array_size = snap_inf.snapshot_array_size; - - if (ioctl(f, CIFS_ENUMERATE_SNAPSHOTS, buf) < 0) { - fprintf(stderr, "Querying snapshots failed with %s\n", strerror(errno)); - exit(1); - } - - print_snapshots(buf); - free(buf); -} - -static int -parse_compression(const char *arg) -{ - if (!strcmp(arg, "no")) - return 0; - else if (!strcmp(arg, "default")) - return 1; - else if (!strcmp(arg, "lznt1")) - return 2; - - fprintf(stderr, "compression must be no|default|lznt1\n"); - exit(10); -} - -int main(int argc, char *argv[]) -{ - int c; - int f; - int compression = 1; - - if (argc < 2) { - short_usage(argv[0]); - } - - while ((c = getopt_long(argc, argv, "c:vVh", NULL, NULL)) != -1) { - switch (c) { - case 'c': - compression = parse_compression(optarg); - break; - case 'v': - printf("smbinfo version %s\n", VERSION); - return 0; - case 'V': - verbose = 1; - break; - case 'h': - usage(argv[0]); - break; - default: - short_usage(argv[0]); - } - } - - if (optind >= argc -1) - short_usage(argv[0]); - - if ((f = open(argv[optind + 1 ], O_RDONLY)) < 0) { - fprintf(stderr, "Failed to open %s\n", argv[optind + 1]); - exit(1); - } - - if (!strcmp(argv[optind], "fileaccessinfo")) - fileaccessinfo(f); - else if (!strcmp(argv[optind], "filealigninfo")) - filealigninfo(f); - else if (!strcmp(argv[optind], "fileallinfo")) - fileallinfo(f); - else if (!strcmp(argv[optind], "filebasicinfo")) - filebasicinfo(f); - else if (!strcmp(argv[optind], "fileeainfo")) - fileeainfo(f); - else if (!strcmp(argv[optind], "filefsfullsizeinfo")) - filefsfullsizeinfo(f); - else if (!strcmp(argv[optind], "fileinternalinfo")) - fileinternalinfo(f); - else if (!strcmp(argv[optind], "filemodeinfo")) - filemodeinfo(f); - else if (!strcmp(argv[optind], "filepositioninfo")) - filepositioninfo(f); - else if (!strcmp(argv[optind], "filestandardinfo")) - filestandardinfo(f); - else if (!strcmp(argv[optind], "fsctl-getobjid")) - fsctlgetobjid(f); - else if (!strcmp(argv[optind], "getcompression")) - getcompression(f); - else if (!strcmp(argv[optind], "setcompression")) - setcompression(f, compression); - else if (!strcmp(argv[optind], "list-snapshots")) - list_snapshots(f); - else if (!strcmp(argv[optind], "quota")) - quota(f); - else if (!strcmp(argv[optind], "secdesc")) - secdesc(f); - else if (!strcmp(argv[optind], "keys")) - dump_keys(f); - else { - fprintf(stderr, "Unknown command %s\n", argv[optind]); - exit(1); - } - - close(f); - return 0; -} -- 2.16.4