summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAurelien Aptel <aaptel@suse.com>2019-10-14 19:07:38 +0200
committerPavel Shilovsky <pshilov@microsoft.com>2020-09-03 10:35:18 -0700
commit422f0e98161326de824db8ee8148559f801b7b71 (patch)
tree3ee9842b5e3a501bcbcfb5c09945f2ca2c2df503
parentded4e81796a18cde73329e838357f084aa05720f (diff)
downloadcifs-utils-422f0e98161326de824db8ee8148559f801b7b71.tar.gz
cifs-utils-422f0e98161326de824db8ee8148559f801b7b71.tar.bz2
cifs-utils-422f0e98161326de824db8ee8148559f801b7b71.zip
smbinfo: rewrite in python
Signed-off-by: Aurelien Aptel <aaptel@suse.com>
-rw-r--r--Makefile.am3
-rwxr-xr-xsmbinfo779
-rw-r--r--smbinfo.c1296
3 files changed, 780 insertions, 1298 deletions
diff --git a/Makefile.am b/Makefile.am
index fe9cd34..8e376be 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -81,8 +81,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@suse.com>
+# Copyright (C) 2019 Ronnie Sahlberg <lsahlberg@redhat.com>
+#
+# 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@redhat.com) 2018
- * Copyright (C) Aurelien Aptel (aaptel@suse.com) 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");
- bre