Недавно один из клиентов спросил меня, возможно ли для заданного пользователя узнать имена всех файлов, созданных им в структуре каталогов, содержащей более 20 000 файлов. Поначалу вопрос показался мне абсурдным, поскольку единственный способ получить эти данные состоит в выяснении владельца для каждого файла отдельно и последующем формировании соответствующего списка. Однако потом я вспомнил, что несколько лет назад пользовался утилитой subinacl.exe, входящей в комплект Resource Kit, для смены владельцев файлов. Тогда же я обнаружил, что, помимо прочих многочисленных возможностей этой программы, она позволяет просматривать свойства файла, в том числе идентификатор SID пользователя, который является владельцем данного файла. После того как стало ясно, что с помощью утилиты subinacl.exe необходимая информация может быть получена, я разработал сценарий с именем ownedby.cmd, показанный в Листинге 1, который может использоваться для организации аудита владения файлами.

Утилита Subinacl.exe

В принципе, существуют разные версии Subinacl – первая из них была выпущена Microsoft в составе пакета Microsoft Windows NT Server 4.0 Resource Kit, а вторая, обновленная ее версия, вышла вместе с пакетом Microsoft Windows 2000 Resource Kit. Эти две версии программы немного отличаются друг от друга, поскольку в версию для Windows 2000 были включены некоторые дополнительные функции в соответствии с новыми возможностями этой операционной системы. Однако для реализации тех задач, которые решаются сценарием ownedby.cmd, можно использовать любую из версий Subinacl – обе они будут работать одинаково. Чтобы познакомиться с полным перечнем возможностей данной утилиты, нужно набрать в командной строке:

subinacl.exe /help /full

Если требуется получить детальную информацию о списке контроля доступа (ACL) заданного файла, необходимо ввести следующую команду:

subinacl.exe /file filename

где filename – имя интересующего вас файла. Если вам не требуется вся информация, выводимая этой командой, добавьте ключ /noverbose – в этом случае данные об ACL файла будут представлены в виде сводки. На Рисунке 1 показан пример данных, выводимых утилитой subinacl при использовании этих ключей. Здесь видно, что одна из строк содержит данные /owner = S-1-5-32-544, указывающие на то, что владелец данного файла имеет SID S-1-5-32-544.

Теперь у вас, вероятно, возник вопрос, а что, собственно, дает нам знание SID владельца файла? В конце концов, найдется не так много администраторов, которые помнят наизусть идентификаторы SID всех своих пользователей. Подход, реализованный в сценарии ownedby.cmd, состоит в том, чтобы сначала установить имя пользователя, определить его SID, а затем найти все файлы, связанные с этим SID. Поэтому следующий шаг в решении нашей задачи состоит в том, чтобы найти утилиту, которая может преобразовывать имена пользователей в соответствующие идентификаторы SID. К счастью, такая программа, которая называется Getsid, существует, и она включена компанией Microsoft в состав пакетов Resource Kit для Windows 2000 и Windows NT 4.0.

Утилита Getsid.exe

Утилита getsid.exe выполняет сравнение пользовательских SID для двух учетных записей. Синтаксис запуска выглядит следующим образом:

getsid server1 account1 server2 account2

Здесь параметры server1 и server2 определяют имена серверов, к которым мы обращаемся для получения информации о SID, а account1 и account2 соответствуют учетным записям пользователей, которые необходимо сравнить. Но поскольку в сценарии Ownedby.cmd утилита Getsid используется для получения данных об одном конкретном пользователе, в данном случае значения параметров server1 и server2 будут одинаковыми, так же, как и значения параметров account1 и account2. Например, для того чтобы получить значение SID пользователя JDoe с контроллера домена (DC), имеющего имя mydc, нужно ввести следующую команду:

getsid mydc JDoe mydc JDoe

Как работает сценарий

Итак, базовый инструментарий для создания сценария есть. С помощью subinacl мы получаем SID владельца файла, а Getsid позволит нам узнать SID по заданному имени. Имея в своем распоряжении эти утилиты, можно выполнять сопоставление имени пользователя с именем владельца файла. Задача ownedby.cmd состоит в том, чтобы для конкретной структуры каталогов и заданного имени пользователя вывести пути и имена всех файлов, владельцем которых является данный пользователь. В сценарии имеется один жестко запрограммированный параметр (имя контроллера домена) и три параметра периода выполнения: имя пользователя (userid), каталог верхнего уровня той структуры, в которой будет производиться поиск (root_directory) и имя выходного файла (outputfile). Полный синтаксис запуска сценария Ownedby выглядит следующим образом:

ownedby.cmd userid root_directory outputfile

Например, если ввести команду

ownedby.cmd JDoe G:users results.txt

то в результате будет сформирован файл results.txt, содержащий список всех файлов в каталоге G:users (включая содержимое подкаталогов), владельцем которых является пользователь JDoe.

В ходе выполнения сценарий должен определить SID заданного пользователя и сохранить значение в переменной sid для его дальнейшего использования (Листинг 1, фрагмент с меткой А). Сначала выполняется обнуление переменной sid, для того чтобы очистить ее от полученных ранее данных. Затем определяется SID пользователя, для чего запускается утилита Getsid в цикле For. Выходные данные фильтруются таким образом, чтобы выводились только строки, содержащие значение "S-", которое является префиксом SID. Значение SID извлекается из седьмой строки ("tokens=7") выходных данных Getsid, а затем сохраняется в переменной sid. Значение %DC% представляет собой жестко запрограммированное имя контроллера домена, которое определяется в сценарии выше. В переменной %USERID% содержится идентификатор ID пользователя, который был указан в качестве параметра командной строки при запуске сценария.

Далее, как видно из фрагмента кода, обозначенного меткой B, с помощью команды Dir, выполняемой в цикле For, получаем список файлов, параметры которых далее сравниваются со значением ID пользователя (%USERID%). Здесь имеется несколько ключей, которые предписывают не включать в выходные данные информацию о заголовках и краткие сводки для всех подкаталогов (ключ /s) и выводить данные в "простом" (bare) формате (ключ /b). Кроме того, поскольку нас интересуют только файлы, а не каталоги, с помощью ключа /a-d указывается, что нужно выводить данные только о тех объектах, которые имеют атрибут "не каталог" (not directory). В переменной ROOTDIR содержится название каталога, в котором будет производиться поиск. Для рассмотренного выше примера переменная ROOTDIR должна содержать значение G:users.

Далее для каждого имени файла, возвращаемого командой Dir, выполняется раздел кода сценария, обозначенный меткой :checkowner. В данной секции сначала запускается утилита Subinacl с соответствующим именем файла, а информация о владельце сохраняется в переменной owner. Далее сценарий проверяет, является ли заданный пользователь владельцем этого файла, для этого строка с данными о владельце, полученными от Subinacl, выводится в виде эха и перенаправляется в команду Find, которая сравнивает SID заданного пользователя с SID владельца файла. При этом, если переменная ERRORLEVEL равна 0, совпадение считается найденным, и переменной результата поиска присваивается значение 1, как показано во фрагменте кода, обозначенном меткой D. В сценарии предусмотрен сброс переменной результата поиска перед присвоением ей какого-либо значения, поэтому наличие установленного флага совпадения можно обнаружить путем проверки, определил ли сценарий значение переменной поиска. Если эта переменная определена, следовательно, совпадение выявлено, и сценарий заносит имя соответствующего файла в файл результатов поиска (см. фрагмент кода с меткой E).

Если вы посмотрите на текст сценария, то увидите, что здесь также выполняется сопоставление переменной owner, определяющей владельца файла, с параметром USERID, что показано во фрагменте кода с меткой C. Необходимость этого обусловлена тем, что когда вы запускаете сценарий на системах с Windows NT 4.0 Server, программа Subinacl будет возвращать в явном виде имя пользователя, а не его SID. Поэтому при наличии в сценарии данной процедуры его можно использовать как для систем Windows Server 2003 и Windows 2000, так и для более старых версий ОС семейства Windows NT 4.0.

Практическое использование сценария

Несмотря на то, что сценарий ownedby.cmd содержит не слишком объемный код, а текст его является достаточно очевидным, у него есть ряд особенностей. Ниже приводится несколько замечаний, которые необходимо учесть, прежде чем начинать использовать сценарий в том виде, в котором он был написан:

  1. Убедитесь, что исполняемые файлы subinacl.exe и getsid.exe находятся в том же каталоге, что и сам сценарий, либо в каталоге, путь к которому описан переменной среды PATH.
  2. В разделе сценария, обозначенном меткой Name of DC, замените имя mydc на актуальное имя одного из ваших контроллеров домена, чтобы команда Getsid имела информацию о том, куда обращаться с запросами о получении сведений о SID.
  3. Если запустить сценарий на системах Windows XP и Windows 2000, то при каждом запросе, выполняемом утилитой Subinacl, будет выводиться следующий заголовок:
    Default Sam Server will be 0
    Default Sam Server will be 0
    Появление этих строк связано с тем, что Subinacl не может перенаправлять этот текст, поэтому и ownedby.cmd не может скрыть его от просмотра, сделав вывод более "чистым".
  4. Сценарий ownedby.cmd не в состоянии определить, какими файлами владеет пользователь с учетной записью Administrator (Администратор). Когда файл создается от имени этой учетной записи, система Windows автоматически назначает ему в качестве владельца группу Administrators (Администраторы). Следовательно, если вы не активируете на данном сервере аудит, то не сможете определить, кто именно из администраторов создал этот файл.

Сценарий разрабатывался и тестировался на системах Windows 2000 SP4 и SP3, а также на системах Windows NT 4.0 SP6a, однако он должен работать и на других системах. Хотя может показаться, что данный сценарий имеет довольно ограниченное применение, тем не менее, по разным причинам мне уже приходилось составлять более десятка таких отчетов – обычно для того чтобы выяснить, кто является владельцем файлов, когда сами пользователи не могли точно ответить на этот вопрос. При наличии сценария ownedby.cmd данная работа может быть выполнена без особых усилий с вашей стороны.


Листинг 1. Ownedby.cmd
@ECHO OFF
:: -----------------------------------------------------------------
:: Имя файла: ownedby.cmd
::
:: Автор: Steve Seguis
::
:: Назначение: Получение списка всех файлов в указанном каталоге
::		 и всех его подкаталогах,
::		 к которым заданный пользователь имеет доступ
:: Выходные данные : Данные выводятся в файл с заданным именем.
::
:: Синтаксис : ownedby.cmd   
::
:: Пример: ownedby.cmd JDoe "G:users" results.txt
::        Данная команда выводит в файл results.txt
::        сведения обо всех файлах в каталоге G:users и его подкаталогах,
::        владельцем которых является пользователь Jdoe.
::        Если описание пути к корневому каталогу содержит пробелы,
::        то в этом случае его необходимо заключать в двойные кавычки.
::
:: Требования: Утилиты Subinacl.exe и getsid.exe
::             из пакетов Resource Kit
::             систем Windows 2000 или Windows NT Server 4.0
::             должны быть размещены в том же самом каталоге,
::             в котором находится файл сценария,
::             либо быть в каталоге, путь к которому описан
::             в переменной среды PATH.
:: ------------------------------------------------------------------

setlocal
:: --- Проверка, указал ли пользователь все три параметра. ---
If "%3"=="" Goto syntax

:: --- Имя контроллера домена ---
Set DC=mydc

:: --- Сохраняем параметры. ---
Set USERID=%1
Set ROOTDIR=%2
Set ROOTDIR=%ROOTDIR:"=%
Set OUTPUTFILE=%3

If not exist "%ROOTDIR%" ECHO "%ROOTDIR%" не существует & Goto syntax

If "%DC%"=="mydc" ECHO Измените параметр запуска DC и запустите сценарий снова & Goto 
:EOF

' BEGIN CALLOUT A
:: --- Получение данных о SID пользователя. ---
Set sid=
For /f "tokens=7" %%i in ('getsid \%DC% %USERID% \%DC% %USERID% ^| find "S-"') Do set 
sid=%%i
' END CALLOUT A

:: --- Вывод заголовка в файл результатов. ---
ECHO Listing all files in %rootdir% owned by %userid%... > %OUTPUTFILE%
ECHO -------------------------------------------------------------- >> %OUTPUTFILE%
ECHO. >> %OUTPUTFILE%

:: ---Для каждого файла каталога %ROOTDIR% и всех его подкаталогов
::    определяем, является ли его владельцем пользователь %USERID%. ---
' BEGIN CALLOUT B
For /f "tokens=*" %%i in ('dir /s /b /a-d "%ROOTDIR%"') Do call :checkowner "%%i"
endlocal
Goto :EOF
' END CALLOUT B

:checkowner
Set FILENAME=%1

:: --- Используем программу Subinacl для вывода сведений о владельце файла
::     и ищем совпадение между значением USERID и SID пользователя. ---
Set owner=
For /F "tokens=*" %%j in ('SUBINACL /noverbose /file %FILENAME% ^| find "/owner"') Do set 
owner=%%j
If not defined owner Goto :EOF

' BEGIN CALLOUT C
:: --- Если обнаружено, что заданный пользователь 
::     является владельцем файла,
::     фиксируем это в выходном файле. ---
Set found=
ECHO %owner% | find /I "%USERID%" > NUL
If %ERRORLEVEL% EQU 0 Set found=1
' END CALLOUT C

' BEGIN CALLOUT D
If not defined sid Goto :nosid
ECHO %owner% | find /I "%sid%" > NUL
If %ERRORLEVEL% EQU 0 Set found=1
:nosid
' END CALLOUT D

' BEGIN CALLOUT E
If defined found ECHO %FILENAME% >> %OUTPUTFILE%
' END CALLOUT E
Goto :EOF

:syntax
ECHO.
ECHO Синтаксис: ownedby.cmd ^ ^ ^
ECHO.
ECHO Назначение: Получение списка всех файлов в каталоге 
ECHO             и всех его подкаталогах, владельцем которых является 
ECHO             пользователь .
ECHO             Результаты выводятся в файл .

ECHO          Если в описании пути root_directory имеются пробелы,
ECHO          то в этом случае 
ECHO          его необходимо целиком заключать в двойные кавычки.
ECHO