Основы apache (httpd)

Общий механизм HTTP авторизации

RFC 7235 определяет средства HTTP авторизации, которые может использовать сервер для запроса (en-US) у клиента аутентификационной информации. Сценарий запрос-ответ подразумевает, что вначале сервер отвечает клиенту со статусом (Unauthorized) и предоставляет информацию о порядке авторизации через заголовок WWW-Authenticate (en-US), содержащий хотя бы один метод авторизации. Клиент, который хочет авторизоваться, может сделать это, включив в следующий запрос заголовок с требуемыми данными. Обычно, клиент отображает запрос пароля пользователю, и после получения ответа отправляет запрос с пользовательскими данными в заголовке .

В случае базовой авторизации как на иллюстрации выше, обмен должен вестись через HTTPS (TLS) соединение, чтобы обеспечить защищённость.

Этот же механизм запроса и ответа может быть использован для прокси-авторизации. В таком случае ответ посылает промежуточный прокси-сервер, который требует авторизации. Поскольку обе формы авторизации могут использоваться одновременно, для них используются разные заголовки и коды статуса ответа. В случае с прокси, статус-код запроса (Proxy Authentication Required) и заголовок Proxy-Authenticate (en-US), который содержит хотя бы один запрос, относящийся к прокси-авторизации, а для передачи авторизационных данных прокси-серверу используется заголовок Proxy-Authorization (en-US).

Если (прокси) сервер получает корректные учётные данные, но они не подходят для доступа к данному ресурсу, сервер должен отправить ответ со статус кодом  . В отличии от статус кода   или  , аутентификация для этого пользователя не возможна.

Аутентификация с помощью изображений, загружаемых из разных источников, была до недавнего времени потенциальной дырой в безопасности. Начиная с Firefox 59, изображения, загружаемые из разных источников в текущий документ, больше не запускают диалог HTTP-аутентификации, предотвращая тем самым кражу пользовательских данных (если нарушители смогли встроить это изображение в страницу).

Браузеры используют кодировку  для имени пользователя и пароля. Firefox использовал , но она была заменена  с целью уравнения с другими браузерами, а также чтобы избежать потенциальных проблем (таких как баг 1419658).

WWW-Authenticate (en-US) и Proxy-Authenticate (en-US) заголовки ответа которые определяют методы, что следует использовать для получения доступа к ресурсу. Они должны указывать, какую схему аутентификации использовать, чтобы клиент, желающий авторизоваться, знал, какие данные предоставить. Синтаксис для этих заголовков следующий:

WWW-Authenticate: <type> realm=<realm>
Proxy-Authenticate: <type> realm=<realm>

Here, is the authentication scheme («Basic» is the most common scheme and ). The realm is used to describe the protected area or to indicate the scope of protection. This could be a message like «Access to the staging site» or similar, so that the user knows to which space they are trying to get access to.

The and Proxy-Authorization (en-US) request headers contain the credentials to authenticate a user agent with a (proxy) server. Here, the type is needed again followed by the credentials, which can be encoded or encrypted depending on which authentication scheme is used.

Authorization: <type> <credentials>
Proxy-Authorization: <type> <credentials>

The general HTTP authentication framework is used by several authentication schemes. Schemes can differ in security strength and in their availability in client or server software.

The most common authentication scheme is the «Basic» authentication scheme which is introduced in more details below. IANA maintains a list of authentication schemes, but there are other schemes offered by host services, such as Amazon AWS. Common authentication schemes include:

  • Basic (see RFC 7617, base64-encoded credentials. See below for more information.),
  • Bearer (see RFC 6750, bearer tokens to access OAuth 2.0-protected resources),
  • Digest (see RFC 7616, only md5 hashing is supported in Firefox, see баг 472823 for SHA encryption support),
  • HOBA (see RFC 7486 (draft), HTTP Origin-Bound Authentication, digital-signature-based),
  • Mutual (see draft-ietf-httpauth-mutual),
  • AWS4-HMAC-SHA256 (see AWS docs).

Настройка PHP аутентификации GSSAPI[править]

Для работы аутентификации PHP в LDAP необходима директива KrbSaveCredentials On, при её включении будет создаваться нужная нам переменная $_SERVER.
Также необходимо проверить наличие в DNS PTR-записей нашего web-сервера:

# host 192.168.135.195
195.135.168.192.in-addr.arpa domain name pointer apserver.domg.testg.

192.168.135.195 — ip-адрес нашего web-сервера.
Установим необходимые пакеты:

# apt-get install pecl-krb5 php5-ldap

Внимание! GSSAPI не работает по протоколу ldaps://, поэтому указывайте ldap://

Для проверки работоспособности LDAP запросов с помощью PHP и Kerberos, создадим в нашей корневой папке файл index.php, следующего содержания:

<?php
    var_dump($_SERVER'PHP_AUTH_USER']);
    var_dump($_SERVER'KRB5CCNAME']);
    putenv("KRB5CCNAME={$_SERVER'KRB5CCNAME'}");
    $ldapconn = ldap_connect("ldap://dcd.domg.testg:389") or die('error connect с $ldaphost') ;
    ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldapconn, LDAP_OPT_REFERRALS, );
    $result = ldap_sasl_bind($ldapconn,'','','GSSAPI','DOMG.TESTG') or die('Failed to GSSAPI bind sasl bind.<br />');
    $result = ldap_search($ldapconn, 'DC=DOMG,DC=TESTG', '(objectClass=*)', array('mail'));
    $result_entries = ldap_get_entries($ldapconn, $result);
    var_dump($result_entries);
?>
string(7) "u01domg" string(50) "FILE:/var/run/httpd2/krbcache/krb5cc_apache_NHBQeV"

array(224) {
  =>
  int(223)
  =>
  array(2) {
    =>
    int(0)
    =>
    string(16) "DC=domg,DC=testg"
  }
...

3: Настройка mod_evasive

Теперь нужно открыть конфигурационный файл модуля mod_evasive и настроить некоторые параметры.

Примечание: Данное руководство охватывает только базовые параметры настройки модуля. Конфигурационный файл содержит детальное описание каждого параметра; в случае необходимости, пожалуйста, обратитесь к нему за подробностями.

Откройте файл в текстовом редакторе:

К примеру, чтобы модуль отправлял извещения на адрес [email protected], нужно добавить в файл (или раскомментировать) строку:

Примечание: для отправки извещений модуль mod_evasive использует /bin/mail. Для этого вам нужно установить и настроить почтовый сервер.

Следующий параметр – DOSWhitelist. Эта директива задаёт список заведомо безопасных IP-адресов. Такие адреса никогда не будут заблокированы. Цель белого списка заключается в защите программного обеспечения, сценариев, локальных поисковиков и других автоматизированных средств системы от блокировки в случае запроса больших объемов данных.

Чтобы добавить адрес в белый список, внесите в файл следующую строку:

Примечание: Замените условный IP своим IP-адресом.

Чтобы добавить в белый список несколько IP-адресов, просто добавьте новую директиву DOSWhitelist для следующего адреса:

Далее рекомендуется настроить параметры DOSPageCount и DOSSiteCount. Установите более щадящие значения, в противном случае модуль будет безосновательно блокировать клиентов.

DOSPageCount ограничивает количество запросов IP-адреса к одной странице в течение определённого интервала времени (как правило, в течение 1 секунды). Если IP-адрес превысил лимит, он будет заблокирован. Значение по умолчанию – 2 – очень строгое, и многие клиенты могут случайно попасть в бан. Измените его хотя бы на 20:

DOSSiteCount ограничивает общее количество запросов IP-адреса (т.е. ко всем страницам сайта) в течение определённого интервала (по умолчанию – за 1 секунду). Увеличьте значение данного параметра до 100:

Параметр DOSBlockingPeriod задаёт временной интервал (в секундах), в течение которого клиент будет заблокирован на данном сайте. Отправляя запросы в течение этого периода, клиент будет получать ошибку 403, а интервал будет восстанавливаться (по умолчанию – 10 секунд).

Чтобы увеличить продолжительность блокировки, можно изменить значение данного параметра:

Параметр DOSLogDir ссылается на временный каталог mod_evasive; по умолчанию для блокировки используется каталог /tmp, который может стать причиной некоторых проблем безопасности, если система открыта для пользователей оболочки. Если в вашей сети есть непривилегированные пользователи оболочки, нужно создать отдельный каталог, доступный для записи только пользователю, управляющему Apache (обычно он называется apache), а затем установить этот параметр в файле mod_evasive.conf.

К примеру, можно настроить mod_evasive для использования нестандартного каталога /var/log/mod_evasive. Создайте каталог:

Передайте права на него пользователю apache:

А затем отредактируйте конфигурацию mod_evasive и укажите новый каталог:

Параметр DOSSystemCommand задаёт команду, которую нужно запустить в случае блокировки IP-адреса. При помощи этого параметра можно интегрировать в mod_evasive брандмауэр или сценарий оболочки и заблокировать вредоносный IP при помощи брандмауэра.

Поддержка сессии на Perl и PHP

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

#perl$ENV{REMOTE_USER}#php$_SERVER

Вот такие дела

Большая получилась статья, как я и обещал. Но зато в ней рассмотрены основные принципы работы апачевой base-автроизации, а так же собраны решения некоторых проблем, которые могут возникнуть при работе с ней. Скажу честно, провел не один час, в поисках ответов на некоторые вопросы, решенные в этой статье. Надеюсь, она поможет сэкономить время другим программистам.

Минусы и недостатки

Когда посетителей на сервере много, Апач работает медленно. А всё потому, что в 1995 году высокой нагрузкой считалось, условно, 1000 посетителей в минуту, а сейчас — миллион. И когда обращений к сайту становится слишком много (а Апач обрабатывает каждое соединение по очереди) — сервер не справляется и тормозит.

Второй недостаток — уязвимость подключаемых модулей. Сам Апач проверен на надёжность и безопасность много раз, а вот в модулях могут быть проблемы. Если подключить модуль, в котором есть дыры в безопасности, то через них можно получить доступ и к серверу, и к файлам, которые на нём хранятся.

Настройка Apache

Уже прошло то время, когда конфигурация Apache хранилась в одном файле. Но оно и правильно, когда все распределено по своим директориям, в конфигурационных файлах легче ориентироваться.

Все настройки содержатся в папке /etc/apache/:

  • Файл /etc/apache2/apache2.conf отвечает за основные настройки
  • /etc/apache2/conf-available/* — дополнительные настройки веб-сервера
  • /etc/apache2/mods-available/* — настройки модулей
  • /etc/apache2/sites-available/* — настойки виртуальных хостов
  • /etc/apache2/ports.conf — порты, на которых работает apache
  • /etc/apache2/envvars

Как вы заметили есть две папки для conf, mods и site. Это available и enabled. При включении модуля или хоста создается символическая ссылка из папки available (доступно) в папку enable (включено). Поэтому настройки лучше выполнять именно в папках available. Вообще говоря, можно было бы обойтись без этих папок, взять все и по старинке свалить в один файл, и все бы работало, но сейчас так никто не делает.

Сначала давайте рассмотрим главный файл конфигурации:

Timeout — указывает как долго сервер будет пытаться продолжить прерванную передачу или прием данных. 160 секунд будет вполне достаточно.

KeepAlive On — очень полезный параметр, позволяет передавать несколько файлов, за одно соединение, например, не только саму html страницу, но и картинки и css файлы.

MaxKeepAliveRequests 100 — максимальное количество запросов за одно соединение, чем больше, тем лучше.

KeepAliveTimeout 5 — таймаут соединения, обычно для загрузки страницы достаточно 5-10 секунд, так что больше ставить не нужно, но и рвать соединение раньше чем загрузились все данные тоже не нужно.

User, Group — пользователь и группа, от имени которых будет работать программа.

HostnameLookups — записывать в логи вместо ip адресов доменные имена, лучше отключить, чтобы ускорить работу.

LogLevel — уровень логирования ошибок. По умолчанию используется warn, но чтобы логи заполнялись медленнее достаточно включить error

Include — все директивы include отвечают за подключение рассмотренных выше конфигурационных файлов.

Директивы Directory отвечают за настройку прав доступа к той или иной директории в файловой системе. Синтаксис здесь такой:

Здесь доступны такие основные опции:

AllowOverride — указывает нужно ли читать .htaccess файлы из этой директории, это такие же файлы настроек и таким же синтаксисом. All — разрешать все, None — не читать эти файлы.

DocumentRoot — устанавливает из какой папки нужно брать документы для отображенияа пользователю

Options — указывает какие особенности веб-сервера нужно разрешить в этой папке. Например, All — разрешить все, FollowSymLinks — переходить по символическим ссылкам, Indexes — отображать содержимое каталога если нет файла индекса.

Require — устанавливает, какие пользователи имеют доступ к этому каталогу. Require all denied — всем запретить, Require all granted — всем разрешить. можно использовать вместо all директиву user или group чтобы явно указать пользователя.

Order — позволяет управлять доступом к директории. Принимает два значения Allow,Deny — разрешить для всех, кроме указанных или Deny,Allow — запретить для всех, кроме указанных. Теперь мы можем запретить доступ к директории для всех: Deny from all, а затем разрешить только для приложения от losst.ru: Allow from losst.ru.

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

У нас остался файл /etc/apache2/ports.conf:

В нем только одна директива, Listen, которая указывает программе на каком порту нужно работать.

Последний файл /etc/apache2/envvars, его вы вряд ли будете использовать, в нем указанны переменные, которые можно использовать в других конфигурационных файлах.

Дальше поговорим немного о htacess. Совсем немного.

Что такое HTTP Basic и Digest аутентификация

HTTP Basic и Digest аутентификации предназначены для контроля доступа на уровне веб-сервера. Если при попытке открыть веб-страницу или войти в настройки роутера вы видите такое окно:

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

Нам более привычна аутентификация с помощью веб-форм, когда чтобы попасть в раздел с ограниченным доступом мы вводим логин и пароль в форму на веб-странице, например:

Эти два вида аутентификаций довольно разные. Алгоритм работы аутентификации через веб форму входа примерно следующий:

  • пользователь вводит данные на веб-страницу и они передаются, обычно, методом POST на веб-сервер
  • веб-сервер передаёт принятые данные веб-приложению
  • веб-приложение сверяет присланные учётные данные с хранящимися в базе данных
  • если имя пользователь и пароль верны, то пользователю присылается маркер (токен) любого вида, который позволяет отличить пользователя, и даётся указание веб-браузеру сохранить его в кукиз
  • веб-брауезр сохраняет этот токен в cookie
  • сайт также запоминает токен и пользователя, которому он назначен
  • при каждом последующем запросе веб-браузер отправляет, среди прочих HTTP заголовков, и токен из cookie
  • теперь веб-сайт сверяет не логин и пароль, а токен из кукиз — если совпал, значит это авторизованный пользователь и ему можно показать ограниченный для доступа контент

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

Как можно догадаться, правильная и безопасная реализация описанного метода требует определённых усилий (проверка пароля, который обычно хранится в виде хеша, необходима база данных с паролями и токенами, нужно написать код веб-формы и код на стороне сервера, принимающий данные, получение и отправка кукиз и так далее).

HTTP Basic и Digest аутентификации очень сильно отличаются. Во-первых, они очень простые для реализации — достаточно создать на сервере файл с хешами пользователей и подключить его к веб-серверу.

Во-вторых, проверка проходит на уровне веб-сервера, не нужно заботиться о реализации аутентификации в веб-приложении, на веб-сайте.

Но и минусов у HTTP Basic и Digest аутентификации слишком много:

  • при Basic пароль передаётся в открытом виде, а при Digest хоть и передаётся хеш, но он очень слаб к брут-форсу, а также уязвимый к атаке понижения до Basic
  • хеши паролей, которые хранятся на сервере, используют очень слабый для брут-форса алгоритм — легко взломать
  • протокол очень стандартизирован — все данные передаются в HTTP заголовках, от этого очень легко написать анализатор передаваемых данных. Как результат, очень многие программы для анализа сетевого трафика имеют встроенную функцию извлекать пароль в открытом виде из переданных данных (Wireshark, Ettercap, Bettercap и прочие). Что касается аутентификации с помощью веб-форм, то они почти всегда разные и это крайне затрудняет написание универсального парсера, извлекающего пароль из переданных данных
  • нет возможности настроить защиту от брут-форса, например, запрет авторизации при нескольких неудачных попытках. Веб-сайты могут реализовать такие защиты, а также, например, случайный маркер в форме, который должен быть передан обратно, иначе данные не будут приняты, ограничение на количество неудачных попыток и прочее.

Кстати, при HTTP Basic и Digest аутентификации не используются кукиз, поэтому чтобы не приходилось вводить пароль каждый раз при доступе к серверу, веб-браузер пользователя запоминает введённые логин и пароль и отправляет их при каждом последующем запросе — в результате создаётся ощущения, что веб-сервер нас запомнил.

Проверяем запустился ли веб сервер

Для этого открываем любой броузер и указываем адрес страницы http://localhost

Мы должны увидеть страничку с надписью It Works !

Выясним IP адрес нашего компьютера в локальной сети. Для этого в нижнем правом углу (рядом с часами) находим иконку локальной сети, кликаем на ней правой кнопкой и открываем «Центр управления сетями и общим доступом»

Выбираем нашу сеть

И нажимаем кнопку «Сведения»

В моем случае адрес компьютера в локальной сети 192.168.0.189

Теперь возвращаемся в броузер и проверяем доступность страницы It Works по IP адресу http://192.168.0.189 (в вашем случае цифры будут отличаться)

Если снова увидели знакомую страницу It Works — все хорошо,

Настройка виртуальных хостов Apache

Было бы не совсем удобно, если на одной физической машине можно было размещать только один сайт. Apache может поддерживать сотни сайтов на одном компьютере и выдавать для каждого из них правильное содержимое. Для этого используются виртуальные хосты. Сервер определяет к какому домену приходит запрос и отдает нужное содержимое из папки этого домена.

Настройки хостов Apache расположены в папке /etc/apache2/sites-available/. Для создания нового хоста достаточно создать файл с любым именем (лучше кончено с именем хоста) и заполнить его нужными данными. Обернуть все эти параметры нужно в директиву VirtualHost. Кроме рассмотренных параметров здесь будут использоваться такие:

  • ServerName — основное имя домена
  • ServerAlias — дополнительное имя, по которому будет доступен сайт
  • ServerAdmin — электронная почта администратора
  • DocumentRoot — папка с документами для этого домена

Например:

Виртуальные хосты, как и модули нужно активировать. Для этого есть специальные утилиты. Чтобы активировать наберите:

Здесь test.site — имя файла виртуального хоста. Для отключения тоже есть команда:

Настройка виртуальных хостов Apache завершена и на публичном сервере это все бы уже работало, но если вам нужна настройка Apache на домашней машине, то вы ваш новый сайт не откроется в браузере. Браузер не знает такого сайта. И откуда ему знать? DNS службы не могут ничего сообщить об этом доменном имени. Но в системе Linux мы можем сами указать ip адреса для доменных имен в файле /etc/hosts. Поэтому добавляем в конец файла такие строки:

Вот, ну теперь будет работать, открывайте браузер, проверяйте.

HTTP-аутентификация

По поводу HTTP-аутентификации все тоже очень просто, но рассмотрим подробнее. Мы хотим ограничить доступ к странице, где осуществляется вход на сайт. Не будем брать конкретную систему управления, возьмем абстрактно, что у нас в корне сайта есть файл , а также папка , которые нам нужно защитить. Для этого потребуется внести правки в файл. Если этого файла нет в корне сайта, или в папке «admin», то его нужно будет создать, но в большинстве случаев он присутствует, и содержит в себе уникальные настройки для сайта, начиная от кодировки и заканчивая правилами перенаправлений и ЧПУ.

Выше представлен код, который реализует HTTP-аутентификацию. Находится он в той директории, которую мы хотим защитить и прописан в файле .

Разберем его по строкам:

  1. В кавычках находится сообщение, которое будет указывать пользователю необходимость авторизоваться для доступа к данной директории;
  2. Тип аутентификации. Всегда ставьте Basic, он без проблем поддерживается всеми браузерами и везде работает корректно;
  3. Путь к файлу, в котором находятся данные о пользователях и паролях, мы создадим его следующим этапом;
  4. Строка, которая указывает, что доступ к данным получит только valid-user, т. е. только тот, кто авторизуется.

Тот же самый код, обрамленный в условие, которое касается только файла . Все очень просто и понятно.

Приступим к созданию файла . Он создается утилитой, которая входит в ПО Apache. Можно скачать и отдельно, работает она из командной строки, без GUI.

Подробнее о команде:

  • – ключ create создает новый файл;
  • – шифрует пароли по алгоритму MD5;
  • – имя файла с паролями;
  • – пользователь, пароль которого будет добавлен.

Результатом выполнения этой команды в консольной строке вам предложат ввести пароль и подтвердить его. В случае совпадения введенных данных появится новый файл, который необходимо будет расположить по адресу, указанному в (в нашем примере это )

Обратите внимание, путь должен быть полным, узнать его можно в

Исходное положение

  • Свежеустановленная Ubuntu Server 16.04. Имя сервера ubuntuadmember, запись в DNS присутствует.
  • Установленные пакеты Apache и Perl (включая модуль Perl для Apache)
  • В Apache настроено исполнение perl-скриптов
  • Все перечисленные ниже команды выполняются от учетки root, чтобы не плодить постоянных sudo

test.pl

#!/usr/bin/perl
 use strict;
 use warnings;

print qq(Content-type: text/plain\n\n);

print "REMOTE_USER -> $ENV{REMOTE_USER}\n\n";

foreach (keys %ENV){
 print "$_ -> $ENV{$_}\n";
 }

Если все работает правильно, получится приблизительно такая картинка:

Обращаю внимание, что переменная REMOTE_USER пустая (в общем списке ее вообще нет), т.е. для сервера подключение не авторизовано, анонимно

Будем устранять.

Чем различаются HTTP Basic и Digest аутентификации

HTTP Basic и Digest аутентификации предназначены для контроля доступа на уровне веб-сервера. Если при попытке открыть веб-страницу или войти в настройки роутера вы видите такое окно:

Для конечного пользователя Basic и Digest выглядят одинаково. С технической точки зрения они различаются тем, что при Basic аутентификации пароль передаётся в открытом виде, а при Digest пароль зашифрован. Но официальная документация Apache рекомендует не полагаться на шифрование Digest, поскольку оно слишком слабое, а вместо этого использовать Basic аутентификацию в паре с HTTPS, чтобы невозможно было перехватить логин и пароль.

Альтернативное хранение пароля

Поскольку хранение паролей в текстовых файлах имеет вышеуказанные проблемы,вы можете захотеть хранить свои пароли где-нибудь еще,например,в базе данных.

и — это два модуля, которые делают это возможным. Вместо того чтобы выбирать , вместо этого вы можете выбрать или в качестве формата хранения.

Например,чтобы выбрать файл dbm,а не текстовый файл:

<Directory "/www/docs/private">
    AuthName "Private"
    AuthType Basic
    AuthBasicProvider dbm
    AuthDBMUserFile "/www/passwords/passwd.dbm"
    Require valid-user
</Directory>

Другие варианты доступны. Обратитесь к документации для получения более подробной информации.

Настроим Apache на авторизацию

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

Отредактируем файл /etc/apache2/sites-enabled/000-default.conf и добавим в него перед </VirtualHost> следующий блок:

<Location />
 AuthType Kerberos
 AuthName "Kerberos Login"
 KrbMethodK5Passwd off
 Krb5Keytab /etc/krb5.keytab
 KrbServiceName HTTP/[email protected]
 Require valid-user
</Location>

Выделено красным:

  • /etc/krb5.keytab — дефолтный путь к файл с ключами. Если хотите поменять, не забудьте переместить и файл
  • HTTP/[email protected] — должен в точности совпадать с названием компьютера и доменом

После внесения изменений нужно рестартовать Apache

service apache2 restart
Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Люкс-хост
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: