Архитектура для приложения, повышающего привилегии?
Есть приложение, которое выполняет файловые операции. В основном над юзерскими файлами, но иногда — над системными. Для выполнения над системными требуется повышение прав. Я почитал, как устроен FAR, который делает именно это. Пишут (не знаю, правда или нет), что он показывает диалог о необходимости повышения прав, а затем создаёт новый процесс самого себя от админа. Если юзер нажал «Да» в UAC, старый процесс завершается и происходит клонирование. Для FAR'а это очень удобно, поскольку всё его состояние — по сути, только два адреса: для левой и правой панелей. Моё же приложение имеет сложное состояние, да ещё и с подключаемыми модулями, и мне вариант с клонированием не нравится.
Остаётся вариант с хелперным дочерним процессом, запускаемым из-под админа, который и возьмёт на себя файловые операции.
Архитектура мне видится такой:
1. Файловые операции описаны интерфейсом (условно, IFileOps).
2. Этот интерфейс IFileOps имплементируется неким объектом внутри главного процесса (условно, InProcFileOps) для юзерских файлов. Если юзер не лезет в системные папки, он никогда не видит UAC.
3. Этот же интерфейс IFileOps имплементируется в хелперном процессе (условно, объектом LocalFileOps, где слово Local означает «внешний процесс»).
4. Когда любой метод InProcFileOps получает ERROR_ACCESS_DENIED, главный процесс создаёт хелперный процесс от админа, юзер видит UAC, нажимает «Да», главный процесс заменяет ссылку на InProcFileOps ссылкой на LocalFileOps, дальше операции идут через него, юзер видит UAC один раз. Всё как в FAR.
5. Вызывающая сторона НЕ ЗНАЕТ, кто в данный момент имплементирует IFileOps.
6. LocalFileOps не имплементирует IFileOps с нуля, а создаёт в своём адресном пространстве экземпляр InProcFileOps, затем делегирует ему, обеспечивая правами.
7. Ясен пень, данные при вызове методов IFileOps хелперного процесса из главного процесса нужно как-то маршалить из-за межпроцессной границы.
Вопросы следующие:
1. Не упускаю ли я хорошие альтернативные подходы?
2. Если оставить этот подход, годная ли описанная выше архитектура?
3. И если да, на чём это сделать?
Можно взять COM. Описать IFileOps на IDL. Сделать два кокласса: InProcFileOps в dll и LocalFileOps в .exe. Local-кокласс создавал бы инстанс InProcFileOps и вызывал его методы 1:1. Затем в коде основного процесса написать #import "FileOps.tlb", а весь бойлерплейт по обращению к внешнему процессу, насколько я помню, вроде сам должен после этого сгенерироваться.
Но, товарищи, что-то не уверен я, что в 2026 году стоит писать на COM'е. Может, есть какие-то библиотеки/инструменты, допустим, на пайпах? Главное, чтобы invoke самому не писать. А, ну ещё скорость важна, конечно. Допустим, файлов в папке будет очень много. И для каждого надо что-то узнать и это несколько вызовов разных методов из IFileOps. И всё это, наверно, было бы лучше автоматически упаковывать в один batch… или нет?
0. Юзер запустил ФМ от имени и с правами юзера.
1. Сунулся к файлу, для работы с которым нужны права администратора.
2. ФМ перезапустился под админом.
3. Следующим действием юзер создал файл в своей папочке - поскольку ФМ уже работает под админом, этот файл ему и принадлежит.
4. И дальше начинается обычный для винды треш с правами, порожденный бездарным разграничением работы и администрирования и отсутствием разграничения путей, по которым делается первая и второе.
Решение:
2. Выдать сообщение, что администрировать нужно, осознанно запуская утилиты от админа, и не лохматить бабушку.
Adamos, юзер же не виноват, что в МС придумали треш. Поэтому, если не заставлять юзера платить за ошибки Майкрософт, цепочка должна быть такой: «сунулся к файлу, для работы с которым нужны права администратора» — «нажал кнопку в UAC и получил права». И всё.
ImagineTables, это не в МС придумали треш, это вы готовы укрыть от пользователя те нюансы, над которыми ему реально надо задумываться и не делать через жопу. Админить - значит, админить, осознанно и без костылей.
Допустим, юзер полез в папку другого юзера. Вы предполагаете спросить, нужен ли ему админ, и поднять ему права, чтобы он мог это сделать, а потом не задавать этого вопроса. И вот он стал админом, влез в чужую папку и копирует себе оттуда файл конфига, скажем. Ваш софт ему подстилает админские права, где надо (то есть при любом ограничении доступа). И вот он запускает программу, конфиг к которой скопировал, и тут же получает ошибку, потому что скопированный файл конфига по-прежнему принадлежит другому пользователю и программа его прочитать не может. Но в вашем-то софте он без проблем будет иметь к нему доступ, и без телепатии вообще не поймет, что происходит.
Ситуация высосанная, конечно, но проблемы по примерно такому шаблону слишком лояльный к повышению привилегий софт создает просто-таки неизбежно. Без нормальной организации работы с разделением юзерства и админства эти грабли будут повсеместны.
Я как-то задал тут вопрос, почему перестали работать LL-хоткеи. Получил ответ: «Попробуй увеличить значение HKCU\Control Panel\Desktop\LowLevelHooksTimeout, вдруг поможет» (и это помогло).
Когда я получил этот ответ, то открыл regedit, НЕ думая заранее, нужны права для этого ключа или нет. Это нормально, я хочу, чтобы если бы они были не нужны, то я об этом и не думал, а если нужны — то мне один раз был бы показан UAC.
Но regedit так не умеет. У него в манифесте прописаны права админа, поэтому он при каждом запуске задалбывает меня UAC'ом. Отключить UAC для юзера не вариант.
Если бы regedit был написан как предлагаете вы, я бы его открыл, пронавигировался до HKCU\Control Panel\Desktop\, прокрутился до LowLevelHooksTimeout, нажал на него, ввёл значение, нажал Enter и… получил бы сообщение «администрировать нужно, осознанно запуская утилиты от админа». Т.е. пришлось бы делать это заново.
Если кто-то так пишет софт, я им просто не пользуюсь.
P.S. Я думаю, разницы нет, что брать в пример: работу с реестром или работу с конфигами. Ключи в реестре бывают HKCU/HKLM, а файлы — в юзерской/системной папках. Логика одна и та же. Пример с реестром просто удобнее.
ImagineTables, продукция M$, на 90% состоящая из сохранения совместимости с прошлым веком и технического долга, накопившегося за это время - так себе образец.
HKCU - ветка текущего пользователя, никакого админства тут быть не должно. Ни при запуске, ни при редактировании. Впрочем, в обычном для окошек бардаке "не должно" еще ничего не значит, конечно.
а зачем городить с нуля? то что ты описал в пп 1-6 — это буквально COM Elevation Moniker, готовый Windows-примитив ровно под твой сценарий: CoGetObject с монитором вида Elevation:Administrator!new:{CLSID} сам поднимает elevated-инстанс через UAC, без клонирования процесса и без ручного маршалинга. Не хочешь COM — тот же движок без обвязки: MIDL + ncalrpc, proxy/stub генерятся сами, транспорт локальный. И не мельчи интерфейс, батчи вызовы на список путей — на каталоге с кучей файлов голый RPC на файл сожрёт всю экономию.
COM Elevation Moniker придумали уже после того, как я перелез с COM'а, так что я его упустил. Щас пойду читать, спасибо.
И не мельчи интерфейс, батчи вызовы на список путей — на каталоге с кучей файлов голый RPC на файл сожрёт всю экономию.
Вообще я хотел сделать виртуальный список + асинхронщину. Допустим, юзер выбрал для показа колонки с хешом, свойствами, вычитываемыми приложениями (например, число слов для текстового редактора), да ещё и обычный стафф, типа попытки прочитать иконку.
Соответственно, как я это вижу: юзер скрольнул, сгенерировались плейсхолдеры, в сервер отправился набор асинхронных запросов, по мере получения данных от сервера пошли вызовы колбеков, из них все плейсхолдеры заменились реальными данными.
За MIDL + ncalrpc отдельное спасибо. Его я тоже упустил, но не из-за времени появления, а просто на COM'е пилил в основном dll'ки. Вы прям кладезь ценных знаний.
Да, с виртуальным списком плейсхолдеров и асинхронной подгрузкой это ляжет отлично: сервер пачками отдаёт видимый диапазон путей, колбэки заполняют по готовности. И на MIDL + ncalrpc для такой схемы можно съехать с COM совсем — будет проще и легче.
Вообще, я надеялся, что есть какая-то C++ библиотека, которая делает ООП на пайпах. То есть, кагбе, COM, но не микрософтовский, а свободный и независимый. Но склейка имени интерфейса и метода в имя глобальной функции… Что-то я думаю, проще вызвать при первом запуске `regsvr32` и далее COM Elevation Moniker (инсталлятора быть не должно как явления, это моя принципиальная позиция). Так хоть полиморфизм будет, и много инстансов одного типа без плясок с pThis.
Учти только, что для Elevation Moniker класс должен лежать в HKLM с Elevation\Enabled=1, из HKCU не заведётся. Так что сам regsvr32 при первом запуске тоже потребует один UAC, зато инсталлятор и правда не нужен.
Просто запускать приложение сразу от имени администратора и не заниматься бесполезным усложнением. Не, если скучно и хочется приключений — то вперёд, никаких проблем. Передать состояние между потоками можно через обычный сокет, файл, да даже просто напрямик блок данных передать через чтение/запись в память процесса.
VoidVolker, может, я чего-то не понимаю, но какой комфорт в том, чтобы принуждать юзера проходить UAC при каждом запуске, если он в принципе не хочет работать с файлами за пределами своего профиля?
VoidVolker, галочка ставится один раз, а UAC вылезает при каждом запуске. Чего быть не должно. Можно, конечно, создать таску от админа, а запускать её батником или процессом-стартером от простого юзера, и UAC вылезать не будет, но как тогда будет работать pin to taskbar запущенного окна? Запинится ярлык, который будет запускать основной процесс, и делать он это будет не от админа.
Кроме того, как быть с drag-n-drop'ом? Пишут, что между процессами с разными integrity он невозможен, а от обходного манёвра надо держаться на расстоянии трёхметровой палки:
Он просто выключается так же один раз — и не будет никаких раздражающих сообщений.
Кроме того, как быть с drag-n-drop'ом? Пишут, что между процессами с разными integrity он невозможен, а от обходного манёвра надо держаться на расстоянии трёхметровой палки
Ну, в таком случае тогда да, разделяйте приложение на несколько процессов для работы с файлами на разных уровнях доступа.
Он просто выключается так же один раз — и не будет никаких раздражающих сообщений.
У юзера-то? Ну вот, я и говорю: юзеры будут в восторге. Чтобы комфортно работать с моим приложением, им понадобится отключать UAC.
Кроме того, UAC нельзя отключать. Сам с ним мучаюсь, хотя он мне нафиг не нужен. Нельзя, потому что тогда перестанет работать перетаскивание файлов на кнопки таскбара.