Программирование сервисов в Windows 2000::Журнал СА 1.2002
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Наука и технологии
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Дата-центры  

Дата-центры: есть ли опасность утечки данных?

Российские компании уже несколько лет испытывают дефицит вычислительных мощностей. Рост числа проектов,

 Читать далее...

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

 Читать далее...

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

 Читать далее...

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

 Читать далее...

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

 Читать далее...

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

 Читать далее...

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

 Читать далее...

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

 Читать далее...

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

 Читать далее...

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

 Читать далее...

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

 Читать далее...

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

 Читать далее...

Книжная полка  

Как стать креативным и востребованным

Издательский дом «Питер» предлагает новинки компьютерной литературы, а также книги по бизнесу

 Читать далее...

Книжная полка  

От создания сайтов до разработки и реализации API

В издательстве «БХВ» недавно вышли книги, которые будут интересны системным администраторам, создателям

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 6241
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6947
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 4233
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 3015
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 3813
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 3829
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 6324
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 3174
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 3468
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 7285
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 10652
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 12371
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 14008
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 9132
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 7084
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 5395
Комментарии: 3
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 4624
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 3434
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 3165
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 3406
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

 Читать далее...

02.12.2013г.
Просмотров: 3031
Комментарии: 0
Не думай о минутах свысока

 Читать далее...

Друзья сайта  

 Программирование сервисов в Windows 2000

Архив номеров / 2002 / Выпуск №1 (1) / Программирование сервисов в Windows 2000

Рубрика: Программирование /  Анализ данных

ВСЕВОЛОД СТАХОВ

Программирование сервисов в Windows 2000

Недавно я занимался разработкой утилитки, позволяющей запускать с сервера по сети некоторые программы на определённое время. При этом обмен происходил через UDP-сокеты. Для написания клиентской части необходимы были требования, чтобы она работала под управлением Windows 2000, и обычный непривилегированный пользователь не мог выключить или включить её.

Для этой цели идеально подходят сервисы – служебные программы, выполняющиеся в фоновом режиме и без возможности завершения процесса через диспетчер задач. Для управления сервисами используется административный компонент Windows NT – services.msc (находится в папке «Администрирование» панели управления). Как сервисы организовано множество системных служб Windows, таких как сетевые клиенты и серверы, утилиты управления оборудованием (пресловутый Plug and Play), а также драйверы ядра. Из компонента управления сервисами можно изменять режим работы сервисов. Сервис характеризуется состоянием: запущен, остановлен, приостановлен. Для управления сервисами используются соответствующие кнопки: запустить, остановить, приостановить (доступно лишь для некоторых сервисов), перезапустить. Можно сделать, чтобы некоторые сервисы запускались при запуске компьютера. Для этого существует три режима запуска сервиса: автоматический (auto) – сервис запускается при входе в систему вручную, (manual) – сервис запускается по требованию пользователя или системы, отключено (disabled) – сервис вообще не будет запускаться.

Для изменения режима запуска откройте диалог свойств сервиса и на вкладке «Общие» выберите нужный режим. На данной вкладке также показано имя и описание сервиса (их можно изменить), а также путь к исполняемому файлу сервиса. Кроме того, можно изменять и другие параметры сервиса. Для этого смотрите встроенную справку Windows. При любых манипуляциях с сервисами учтите, что изменение базы данных сервисов доступно только администраторам. Кроме этого, возможна модификация сервисов на членах NT домена через MMC (Microsoft Management Console – mmc.exe) пользователем, имеющим права администратора домена.

Создание сервиса в ОС Windows – задача нетривиальная. Например, создание демона в ОС UNIX намного проще, хотя, я думаю, система сервисов лучше продумана и более централизована. Для создания нового сервиса, прежде всего, его нужно зарегистрировать в базе данных сервисов. Для этого открывается база данных сервисов функцией OpenSCManager для записи-создания, далее добавляется сам сервис функцией CreateService. После этого система может запустить приложение, зарегистрировавшее себя сервисом, но запуск идёт несколько необычным образом: запускается не WinMain, а ServiceMain, которая определяет обработчики событий сервиса (таких как запуск, пауза, остановка) функцией RegisterServiceCtrlHandler, устанавливает текущее состояние сервиса SetServiceStatus и выполняет функцию StartService для каждого зарегистрированного сервиса (их может быть несколько). Немного сложновато, не так ли? Но реально создать сервис ещё сложнее, так как данные функции принимают очень много параметров, например функция CreateProcess принимает аж 13 (!) параметров. Но я всё же попытаюсь вкратце рассказать о каждой функции и приведу конкретный пример сервиса. Итак, начнём с функции открытия базы данных сервисов:

SC_HANDLE OpenSCManager (const char *MachineName, const char* DatabaseName, DWORD Desired Access);

Функция возвращает дескриптор базы данных сервисов с именем DatabaseName, если данный параметр равен NULL, то используется база данных по умолчанию (для этой же цели можно использовать константу SERVICES_ACTIVE_DATABASE) на компьютере MachineName или на локальном компьютере, если данный параметр NULL. Параметр DesiredAccess определяет режим доступа к файлу. Обычно используются константы GENERIC_READ, для чтения GENERIC_WRITE для создания новых сервисов или изменения параметров старых и GENERIC_EXECUTE разрешение на выполнение сервисов. При ошибке возвращается NULL, иначе – дескриптор базы данных.

Далее происходит регистрация сервиса функцией CreateService, которая возвращает дескриптор сервиса для использования в данном процессе:

SC_HANDLE CreateService(

  SC_HANDLE hSCManager,

  LPCTSTR lpServiceName,

  LPCTSTR lpDisplayName,

  DWORD dwDesiredAccess,

  DWORD dwServiceType,  

  DWORD dwStartType,    

  DWORD dwErrorControl, 

  LPCTSTR lpBinaryPathName, 

  LPCTSTR lpLoadOrderGroup, 

  LPDWORD lpdwTagId,    

  LPCTSTR lpDependencies, 

  LPCTSTR lpServiceStartName,

  LPCTSTR lpPassword

);

 Итак, поподробнее о параметрах, так как я не думаю, что их названия говорят сами за себя.

  • SC_HANDLE hSCManager – дескриптор базы данных сервисов, полученный функцией OpenSCManager.
  • LPCTSTR lpServiceName – строка (до 256 символов), реальное имя сервиса в базе данных.
  • LPCTSTR lpDisplayName – данный параметр – имя сервиса, которое показывается в инструменте управления сервисами.
  • DWORD dwDesiredAccess – флаг доступа к сервису. Может принимать любые значения доступа. Обычно используются константы GENERIC_READ, GENERIC_WRITE, GENERIC_EXECUTE, а также значение SERVICE_ALL_ACCESS для предоставления полного доступа к сервису.
  • DWORD dwServiceType – флаг типа сервиса, то есть то, как он будет выполняться системой. Возможные значения:
    • SERVICE_WIN32_OWN_PROCESS – сервис существует в виде отдельного процесса;
    • SERVICE_WIN32_SHARE_PROCESS – сервис разделяет процесс под названием services с другими сервисами;
    • SERVICE_WIN32_KERNEL_DRIVER – сервис является драйвером ядра (то есть сам является частью ядра);
    • SERVICE_WIN32_FILE_SYSTEM_DRIVER – сервис представляет собой драйвер файловой системы. Практически все сервисы, не являющиеся системными, создаются как собственные процессы SERVICE_WIN32_OWN_PROCESS. В этом случае, если необходимо, чтобы сервис взаимодействовал с рабочим столом, можно указать через битовое «или» флаг SERVICE_INTERACTIVE_PROCESS.
  • DWORD dwStartType – флаг способа запуска сервиса, может принимать следующие значения:
    • SERVICE_BOOT_START – служба запускается загрузчиком, NT то есть при загрузке ядра (допустимо только при создании драйвера ядра);
    • SERVICE_SYSTEM_START – сервис запускается после загрузки ядра при инициализации драйверов (это также допустимо только для драйверов);
    • SERVICE_AUTO_START – служба запускается при входе в систему;
    • SERVICE_DEMAND_START – сервис запускается другим приложением (в том числе инструментом управления службами);
    • SERVICE_DISABLED – сервис не может быть запущен. Для стандартных служб обычно применяются флаги SERVICE_AUTO_START и SERVICE_DEMAND_START для, соответственно, автоматической или ручной загрузки.
  • DWORD dwErrorControl – флаг поведения при ошибке. Для большинства сервисов (кроме критичных и системных драйверов) разумно писать SERVICE_ERROR_IGNORE для игнорирования ошибки или SERVICE_ERROR_NORMAL для вывода сообщения о невозможности запуска (также добавляется запись в системный журнал). Для драйверов при невозможности их загрузки обычно происходит откат системы и (или) перезагрузка.
  • LPCTSTR lpBinaryPathName – полный путь к исполняемому файлу сервиса. Учтите, что если путь содержит пробелы или является очень длинным, то писать его нужно в кавычках внутри строки, то есть «d:my foldermy_super service.exe».
  • LPCTSTR lpLoadOrderGroup – имя группы сервиса, обычное значение – NULL, для указания, что сервис не принадлежит никакой группе.
  • LPDWORD lpdwTagId – этот параметр содержит тег группы, которые применяются драйверами для идентификации внутри группы, обычное значение – NULL.
  • LPCTSTR lpDependencies – массив строк-зависимостей сервиса. Если службы, от которых зависит данный сервис не запустились, то не произойдёт и запуска данного сервиса. Данный параметр помогает выстроить иерархическую структуру служб.
  • LPCTSTR lpServiceStartName – имя пользователя для запуска сервиса. Имя вводится в формате Имя_доменаимя_пользователя (или .имя_пользователя для локального домена). Если данный параметр NULL, то используется системный профиль LocalSystem, что подходит для большинства сервисов.
  • LPCTSTR lpPassword – параметр, определяющий пароль для выбранного профиля. Если параметр равен NULL, то считается, что пароль – пустая строка. Для профиля локальной системы LocalSystem пароль всегда должен быть NULL.

Итак, если вы дочитали, до этого момента, то всё остальное должно показаться вам детскими играми.

Кстати, сообщу информацию, которая многим покажется полезной: после регистрации сервиса его параметры записываются в системный реестр Windows в улей HKEY_LOCAL_MACHINESystemCurrentControlSetServices имя_сервиса. Для людей, любящих копаться в настройках ОС сообщаю: большинство системных драйверов являются сервисами и их настройки можно поменять в реестре. Например, очень интересным является ключ Tcpip – можно менять очень много «скрытых» настроек сети (включая настройки сетевых карт!). Для поклонников безопасной сети есть ещё один ключик: Lanmanserver – позволяет прикрыть некоторые дыры безопасности, например NetBios NULL session. Но вернёмся к реальности – к созданию сервисов. Чтобы еще больше отпугнуть читателя, приведу пример создания функции CreateService:

CreateService (hsSManager,lpServiceName, lpDisplayName, dwDesiredAccess, dwServiceType, dwStartControl,

    lpBinaryPathName, lpLoadOrderGroup, lpdwTagId, lpDependencies, lpServiceStartName, lpPassword);

После создания сервиса считайте, что ваше дело завершеносмело пишите CloseServiceHandle (SC_HANDLE sh) и выходите из программы. Тут я сделаю небольшое лирическое отступление. Как известно, в ОС Windows после загрузки программы в память вызывается специализированная функция main для консольных приложений и WinMain для приложений GUI Windows.

Программа, работающая как служба, ведёт себя несколько по-другому: вызов сервиса происходит функцией StartService(SC_HANDLE sh, DWORD param_count, char **parameters), после чего он загружается в память (если ещё не был загружен) и вызывается функция ServiceMain(DWORD param_count, char **parameters), которая также ведёт себя очень хитро, но об этом чуть позднее.

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

#include <windows.h>

int main(int argc, char **argv){

    SC_HANDLE sh;

    SC_HANDLE svdb;           // Дескриптор базы сервисов

    SERVICE_TABLE_ENTRY   DispatchTable[] =

    {

        { "MyService", MyServiceStart      },

        { NULL,              NULL          }

    };

    if(argc < 2){             // Запуск без аргументов

           // Запуск сервиса

 

           if (!StartServiceCtrlDispatcher( DispatchTable))

           {

           write_to_log("Can`t execute service");

           }

    }

       if(argc == 2){           // Передан один параметр

           if(strcmp(argv[1], “-i”))

                 install_service(); // Установка сервиса если аргумент -i

           if(strcmp(argv[1], “-u”))

                 uninstall_service(); // Удаление сервиса если аргумент -u

       }

       else{

           write_to_log(“Bad usage”);

           return -1;

       }

       return 0;

}

В принципе, всё не так уж и сложно, хотя я кое-чего не рассказал. Во-первых, функция OpenService служит для загрузки сервиса в память. Функция принимает три параметра: дескриптор базы сервисов, имя сервиса и тип доступа – тут всё понятно. Для удаления сервиса используется функция DeleteService(SC_HANDLE sh), которая возвращает ноль в случае ошибки. При использовании данной функции учтите, что при открытии сервиса функцией OpenService, вы должны указать в правах доступа единственный флаг DELETE для удаления его из базы. Для запуска самого сервиса используется функция StartServiceCtrlDispatcher(LPSERVICE_TABLE_ENTRY lpServiceStartTable). Функция принимает единственный аргумент – массив строк, содержащий две строки – имя сервиса и имя функции-обработчика, заканчивается список двумя пустыми строками. После этого начинается обработка функции сервиса ServiceMain.

Итак, начало самого интересного – написание функции ServiceMain. Данная функция служит для запуска, инициализации и изменения статуса сервиса. Поведение данной функции строго регламентировано: во-первых, она должна заполнить поля структуры SERVICE_STATUS значениями параметров данного сервиса; во-вторых – зарегистрировать обработчик событий сервиса функцией RegisterServiceCtrlHandler; и, в-третьих – выполнить действия по инициализации сервиса и установить состояние сервиса функцией SetServiceStatus. Итак, обо всём по порядку. Думаю, отдельного описания заслуживает структура SERVICE_STATUS:

struct SERVICE_STATUS {

DWORD dwServiceType; – это поле означает то же, что и в функции CreateService, т.е. тип приложения сервиса (отдельный, драйвер ядра,

драйвер ФС)

DWORD dwCurrentState; – а вот это специфическое поле – содержит текущее состояние сервиса, именно его должна устанавливать ServiceMain.

Допустимые значения:

SERVICE_STOPPED – сервис остановлен;

SERVICE_START_PENDING – сервис запускается;

SERVICE_STOP_PENDING – сервис останавливается;

SERVICE_RUNNING – сервис уже запущен;

SERVICE_CONTINUE_PENDING – сервис продолжает работу;

SERVICE_PAUSE_PENDING – сервис переходит в режим паузы;

SERVICE_PAUSED – сервис находится в режиме паузы.

DWORD dwControlsAccepted; – битовая маска, содержащая допустимые состояния сервиса (через побитное «или»).

Допустимые константы:                                                 

SERVICE_ACCEPT_STOP – сервис может быть остановлен;

SERVICE_ACCEPT_PAUSE_CONTINUE – сервис может быть поставлен и снят с паузы;

SERVICE_ACCEPT_SHUTDOWN – сервис будет оповещён при выходе из системы.

DWORD dwWin32ExitCode; – этот параметр сообщает системе значение, которое возвращает сервис при ошибке, подробнее ошибка определяется

следующим параметром.

DWORD dwServiceSpecificExitCode; – конкретная ошибка, произошедшая в сервисе

DWORD dwCheckPoint; – а это положение прогресс-бара при запуске-остановке сервиса, используется для визуализации процесса запуска

сервиса.

DWORD dwWaitHint; – время в миллисекундах, которое ждёт вызывающая программа до изменения либо текущего статуса, либо dwCheckPoint.

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

};

Итак, обычно вначале устанавливается значение статуса SERVICE_START_PENDING, затем устанавливается обработчик сообщений, инициализация сервиса и только после этого статус сервиса устанавливается в SERVICE_RUNNING. При данных действиях не забудьте корректно установить dwWaitHint, иначе система сочтёт, что ваш сервис не успел запуститься, и замочит его без сожаления. Для регистрации обработчика событий используется функция SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(LPCTSTR service_name, LPHANDLER_FUNCTION handler_function) – первый параметр – имя сервиса, второй – указатель на функцию-обработчик, данная функция возвращает дескриптор состояния сервиса для функции SetServiceStatus или NULL в случае ошибки. Для установки текущего состояния сервиса используется функция SetServiceStatus (SERVICE_STATUS_HANDLE ssh, LPSERVICE_STATUS status) – первый параметр – дескриптор состояния, а второй – указатель на структуру статуса. После этого приведу простенький пример главной функции:

void MyServiceStart (DWORD argc, LPTSTR *argv)

{

    DWORD status;

    DWORD specificError;

    SERVICE_STATUS MyServiceStatus;     //Эта та самая структура статуса

    // А вот мы её заполняем

    MyServiceStatus.dwServiceType        = SERVICE_WIN32_OWN_PROCESS;

    MyServiceStatus.dwCurrentState       = SERVICE_START_PENDING;

    MyServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP;

    MyServiceStatus.dwWin32ExitCode      = 0;

    MyServiceStatus.dwServiceSpecificExitCode = 0;

    MyServiceStatus.dwCheckPoint         = 0;

    MyServiceStatus.dwWaitHint           = 5000;

    //Регистрируем обработчик событий

    MyServiceStatusHandle = RegisterServiceCtrlHandler("MyService", MyServiceCtrlHandler);

    if (MyServiceStatusHandle == (SERVICE_STATUS_HANDLE)0)

    {

           write_to_log("Can not register handler function");

        return;

    }

    //А теперь выполняем некоторые инициализационные действия.

    status = MyServiceInitialization(argc,argv, &specificError);

    // Проверяем, как прошла инициализация

    if (status != NO_ERROR)

    { 

           //Ошибка - останавливаем сервис и выходим

        MyServiceStatus.dwCurrentState       = SERVICE_STOPPED;

        MyServiceStatus.dwCheckPoint         = 0;

        MyServiceStatus.dwWaitHint           = 5000;

        MyServiceStatus.dwWin32ExitCode      = status;

        MyServiceStatus.dwServiceSpecificExitCode = specificError;

        SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus);

        return;

    }

    // Всё прошло успешно :) идём дальше.

    MyServiceStatus.dwCurrentState       = SERVICE_RUNNING;

    MyServiceStatus.dwCheckPoint         = 0;

    MyServiceStatus.dwWaitHint           = 0;

    if (!SetServiceStatus (MyServiceStatusHandle, &MyServiceStatus))

    {

        status = GetLastError();

        write_to_log("Can not set service status 8-(");

    }

    //Сервис начал работать.

    write_to_log("Yeah, this works!",0);

    return;

}

Прекрасная функция, не так ли? Ну и для последнего штриха расскажу ещё про обработчик сообщений. Если Вы когда-нибудь писали GUI программу, то, наверное, знаете, что обработчики событий – это длинная функция, которая получает номер события и выполняет в соответствии с этим некие действия. Так, в сервисах наблюдается нечто подобное. Формат функции должен быть такой:

void WINAPI Handler(DWORD fdwControl);

параметр fdwControl содержит текущее сообщение от системы.

Возможные значения этого параметра:

  • SERVICE_CONTROL_STOP cервис должен остановиться.
  • StartServiceCtrlDispatcher (LPSERVICE_TABLE_ENTRY lpServiceStartTable) SERVICE_CONTROL_PAUSEcервис должен перейти в режим паузы.
  • SERVICE_CONTROL_CONTINUE cервис должен выйти из режима паузы.
  • SERVICE_CONTROL_INTERROGATEcервис должен сообщить о себе информацию.
  • SERVICE_CONTROL_SHUTDOWN система собирается выключаться.

Ну вот, в принципе, и всё. Для дальнейшей информации Вы можете обратиться к MSDN. Но для большинства сервисов этой информации оказывается достаточно. Итак, дерзайте!


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru