Есть у меня в домене супер-группа "разрешен доступ в интернет". Почему супер? Потому что в неё включаются преимущественно другие группы, по одной на каждое подразделение, в которые могут быть включены другие группы и т.д. Возникла задача: узнать, сколько всего пользователей прямо или косвенно входят в эту группу.
Синтаксис виндовой dsget и её родственных утилит вызывает у меня стойкое отвращение обилием лишних параметров и общей запутанностью. Но раз уж прокси-сервер у меня работает под убунтой, то почему бы не задействовать мощный язык bash-a и возможности Samba?
Что у нас есть из инструментов? В первую и главную очередь - сильномогучая команда net. Во вторую - очень полезная команда wbinfo. Надо скомпоновать результаты их работы, чтобы на выходе получить список пользователей группы и всех входящих в неё подгрупп.
Далее по тексту подразумевается, что всё выполняется на компьютере с установленной Samba, введённом в домен. Разделитель домена и имени объекта - обратный слэш "\".
Все команды выполняются от имени супер-пользователя.
# getent group имя_группы
Ой! А почему для доменной группы оно выдало только ее название и GID? Где члены группы? Увы, getent не очень хорошо работает с доменными группами, а значит бесполезна для нас. Попробуем альтернативу.
# net group members list имя_группы
тоже что-то не то - и авторизацию подавай, и еще чего-то не хватает... В общем, для наших целей лучше всего подходит команда в таком формате:
# net rpc group members имя_группы -P -S FQDN_или_IP_контроллера_домена
RPC - это протокол, по которому будем общаться с DC.
-P (заглавная!) - будем притворяться компьютером, то есть, использовать для авторизации на контроллере учетные данные компа, с которого работаем. Соответственно, полномочий меньше (но для наших целей достаточно), а имя и пароль вводить не требуется.
- S - иногда самба не может нормально понять, с каким DC работать, поэтому лучше явно указать ближайший. Контроллер может быть задан своим полным именем, например dc2016.contoso.com или IP-адресом. (я не проверял, можно ли использовать IPv6 за неимением оного протокола в моей сети).
# net rpc group members inet053 -P -S dc.contoso.comCONTOSO\users053
Опа! users053 - это группа, в которую включены все работники службы "053", например, чтобы их скопом включать в другие группы. Это хорошо, что я вижу, что она включена полностью в группу управления доступом inet053, но легче от этого не становится, я ж не могу помнить всех работников этой службы.
# net rpc group members users053 -P -S dc.contoso.com
CONTOSO\сергей-а
CONTOSO\сергей-ф
CONTOSO\евгений-к
CONTOSO\ира-г
CONTOSO\оля-р
CONTOSO\remotes
Уже легче. Но видно, что в группу users053 входит еще группа remotes, то есть глубина вложенности не определена и заранее неизвестна. Теперь надо заходить в remotes, смотреть ее список пользователей и т.д. В общем, классическая задача рекурсивного обхода дерева. Абсолютное большинство языков программирования общего назначения легко справятся, а что насчет bash-a?
Ответ выглядит простым: в баше есть функции и есть рекурсия. Но об этом потом.
Очевидный алгоритм:
1. получить список членов группы
2. если есть обычные пользователи, то сразу вывести их список
3. для каждой вложенной группы повторить, начиная с шага 1.
1. получить список членов группы
2. если есть обычные пользователи, то сразу вывести их список
3. для каждой вложенной группы повторить, начиная с шага 1.
Проблема за малым: в выводе net group members совершенно невозможно отличить автоматически группы от пользователей и других объектов. И вот тут обратимся за помощью к wbinfo:
# wbinfo -n имя_объекта
вернет нам SID этого объекта с краткой расшифровкой:
S-1-5-21-5*******6-4********3-3********3-2***8 SID_USER (1)
# wbinfo -n users053
S-1-5-21-5*******6-4********3-3********3-1***6 SID_DOM_GROUP (2)
Мне лень расшифровывать SID-ы самому, поэтому поверю в способности wbinfo.
(псевдокод)if right(wbinfo-result) = "SID_USER (1)"
then ObjectType="U"
else if right(wbinfo-result) = "SID_DOM_GROUP (2)"
then ObjectType="G"
else ObjectType="-"
endif
endif
То есть, надо пройтись по списку членов группы и проставить каждому "расовую принадлежность". В зависимости от типа объекта, либо снова получать список его членов, либо просто вывести его, либо проигнорировать, если это не группа и не пользователь.
На этом самбовская часть работы закончена, осталось лишь обернуть ее в программный код, который будет управлять процессом. Вероятно, потребуется создать функцию получения списка членов группы, которая при необходимости будет вызывать саму себя для раскрытия подгрупп в текущей группе.
Итак, готовый скрипт. Для красоты пользователи и вложенные группы выводятся со сдвигом вправо в зависимости от глубины вложенности. Но можно этого не делать. Весь вывод выполняется одной командой, настроить формат которой можете по своему усмотрению.
#!/bin/bash
# Глобальные переменные
cur_level=0
users_total=0
cur_group=1
cur_user=0
# Имя контроллера домена, используется в функции get_memb
DC_name=dc2016.contoso.com
# Аналог ДОСовской команды PAUSE
# Использовался при отладке этого скрипта, сейчас не нужен, но оставил на всякий случай
pause() {
read -sn1 -p "Press any key to continue..."
}
# Получаем список членов группы, имя которой передано в параметре $1
# использование:
# переменная=$(get_memb ИМЯ_ГРУППЫ)
# Не забываем задать правильное имя контроллера в переменной DC_name до первого вызова этой функции
get_memb() {
local loc_1
# Убираем, если есть, префикс домена с его бэкслешами
# (замените слово domain в выражении sed-a на имя своего домена
loc_1=$(echo "$1"|sed -e 's/^[dD][oO][mM][aA][iI][nN]\\*//')
# результаты сортируем просто для удобства
net rpc group members "$loc_1" -P -S $DC_name|sort -f
}
# Возвращает тип объекта: "U" - пользователь, "G" - группа, "-" - х.з. что
# использование:
# переменная=$(ana_type ИМЯ_ОБЪЕКТА)
# где ИМЯ_ОБЪЕКТА в стандартной доменной нотации вида домен\объект, как его возвращает get_memb()
ana_type() {
local otype e
# Результат обращения к wbinfo сохраним в переменной, чтобы не дёргать ее дважды
otype=$(wbinfo -n "$1")
# Если есть более кошерный способ искать подстроку в переменной, рад буду узнать
echo $otype|grep -q "SID_USER (1)"
# На всякий случай сохраняем код возврата из grep в переменную. Может работать и без такого сохранения.
e=$?
if [ $e -eq 0 ]
then
echo U
else
echo $otype|grep -q "SID_DOM_GROUP (2)"
e=$?
if [ $e -eq 0 ]
then
echo G
else
echo -
fi
fi
}
# Печатает строку из $1 повторений символа или строки $2 (почему-то не работает с пробелами)
# Используется для отступа, показывающего глубину вложенности. Не обязательно. Совсем.
repchar() {
local a
if [ "0" -ne "$1" ]; then for a in $(seq 1 $1); do echo -n $2; done; fi
}
# Основной цикл
# $1 - имя группы для обработки (с префиксом домена или без оного)
# переменные явно объявлены локально, чтобы избежать проблем при рекурсии
parse_group() {
local a b itype members nmembers
# Получение списка членов группы - процесс небыстрый, поэтому его результаты сохраним в переменной: они нужны как минимум дважды
members=$(get_memb "$1")
nmembers=$(echo $members|wc -w)
# В группе не найдено участников? Идём мимо нее
if [ "0" -eq "$nmembers" ]; then return; fi # Есть участники? Увеличим счетчик глубины вложенности
cur_level=$[$cur_level+1]
for a in $members; do
b=$[$b+1]
itype=$(ana_type "$a")
echo $(repchar $cur_level "_") $cur_level\:$cur_group\:$b\:$itype\:$a
# Это пользователь? Увеличим счетчик и идем дальше
if [ "$itype" == "U" ]; then users_total=$[$users_total+1]; fi
# Это группа? Вызываем себя рекурсивно для её разбора
if [ "$itype" == "G" ]; then parse_group $a; fi
done
cur_group=$[$cur_group+1]
# Обработали, может подымемся на предыдущий уровень вложенности?
cur_level=$[$cur_level-1]
}
# два варианта запуска, например.
# учтите, что разворачивание группы "Пользователи домена" может быть очень долгим.
parse_group "domain users"
parse_group "$1"
echo Users found:$users_total
# Глобальные переменные
cur_level=0
users_total=0
cur_group=1
cur_user=0
# Имя контроллера домена, используется в функции get_memb
DC_name=dc2016.contoso.com
# Аналог ДОСовской команды PAUSE
# Использовался при отладке этого скрипта, сейчас не нужен, но оставил на всякий случай
pause() {
read -sn1 -p "Press any key to continue..."
}
# Получаем список членов группы, имя которой передано в параметре $1
# использование:
# переменная=$(get_memb ИМЯ_ГРУППЫ)
# Не забываем задать правильное имя контроллера в переменной DC_name до первого вызова этой функции
get_memb() {
local loc_1
# Убираем, если есть, префикс домена с его бэкслешами
# (замените слово domain в выражении sed-a на имя своего домена
loc_1=$(echo "$1"|sed -e 's/^[dD][oO][mM][aA][iI][nN]\\*//')
# результаты сортируем просто для удобства
net rpc group members "$loc_1" -P -S $DC_name|sort -f
}
# Возвращает тип объекта: "U" - пользователь, "G" - группа, "-" - х.з. что
# использование:
# переменная=$(ana_type ИМЯ_ОБЪЕКТА)
# где ИМЯ_ОБЪЕКТА в стандартной доменной нотации вида домен\объект, как его возвращает get_memb()
ana_type() {
local otype e
# Результат обращения к wbinfo сохраним в переменной, чтобы не дёргать ее дважды
otype=$(wbinfo -n "$1")
# Если есть более кошерный способ искать подстроку в переменной, рад буду узнать
echo $otype|grep -q "SID_USER (1)"
# На всякий случай сохраняем код возврата из grep в переменную. Может работать и без такого сохранения.
e=$?
if [ $e -eq 0 ]
then
echo U
else
echo $otype|grep -q "SID_DOM_GROUP (2)"
e=$?
if [ $e -eq 0 ]
then
echo G
else
echo -
fi
fi
}
# Печатает строку из $1 повторений символа или строки $2 (почему-то не работает с пробелами)
# Используется для отступа, показывающего глубину вложенности. Не обязательно. Совсем.
repchar() {
local a
if [ "0" -ne "$1" ]; then for a in $(seq 1 $1); do echo -n $2; done; fi
}
# Основной цикл
# $1 - имя группы для обработки (с префиксом домена или без оного)
# переменные явно объявлены локально, чтобы избежать проблем при рекурсии
parse_group() {
local a b itype members nmembers
# Получение списка членов группы - процесс небыстрый, поэтому его результаты сохраним в переменной: они нужны как минимум дважды
members=$(get_memb "$1")
nmembers=$(echo $members|wc -w)
# В группе не найдено участников? Идём мимо нее
if [ "0" -eq "$nmembers" ]; then return; fi # Есть участники? Увеличим счетчик глубины вложенности
cur_level=$[$cur_level+1]
for a in $members; do
b=$[$b+1]
itype=$(ana_type "$a")
echo $(repchar $cur_level "_") $cur_level\:$cur_group\:$b\:$itype\:$a
# Это пользователь? Увеличим счетчик и идем дальше
if [ "$itype" == "U" ]; then users_total=$[$users_total+1]; fi
# Это группа? Вызываем себя рекурсивно для её разбора
if [ "$itype" == "G" ]; then parse_group $a; fi
done
cur_group=$[$cur_group+1]
# Обработали, может подымемся на предыдущий уровень вложенности?
cur_level=$[$cur_level-1]
}
# два варианта запуска, например.
# учтите, что разворачивание группы "Пользователи домена" может быть очень долгим.
parse_group "domain users"
parse_group "$1"
echo Users found:$users_total
Комментариев нет:
Отправить комментарий
Пожалуйста, воздержитесь от грубостей и персональных нападок.
Я не против матерщины, но она должна быть уместной и использоваться для выражения эмоций, а не в качестве основного средства выражения мыслей.