Содержание


Apache 2

Часть 7. Замечания по технике программирования

Comments

Серия контента:

Этот контент является частью # из серии # статей: Apache 2

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Apache 2

Следите за выходом новых статей этой серии.

Сегодня мы поговорим о практике надежного, эффективного, кросс-платформенного программирования. Сюда входит безопасность потоков, распределение ресурсов между процессами, что зависит от соответствующего модуля MPM. Предварительные соглашения - coding conventions - применяемые в Apache, обеспечивают согласованность и облегчают читабельность кода.

1. Coding conventions

Несколько простых правил: в строке должно быть не более 80 символов, включая пробелы. Продолжение на новой строке выравнивается с предыдущей. Перед точкой с запятой не должно быть пробелов. Аргументы функции декларируются в стиле ANSI-C.

Пробелы не ставятся перед и после скобок со списком. Аргументы разделяются запятой и одним пробелом. Отступ блока кода начинается с 4 пробелов. Фигурная скобка, открывающая блок, должна появляться в конце предыдущей линии. Например, оператор Case пишется в следующем стиле:

switch (xyz) {
case X:
    /* code for X */
case Y:
    /* code for Y */
case Z:
    /* code for Z */
}

Указатель декларируется с помощью звездочки, которая присоединяется к имени переменной, а не к типу. Комментарии всегда используют /* ... */.

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

Модули Apache построены на функциях, имеющих тип callback. Для того чтобы предоставить доступ к одним и тем же данным нескольким callback-функциям, используется глобальное пространство. В случае многопоточности это не проходит. Для этого в Apache есть специальный конфигурационный массив - ap_conf_vector_t. Кроме хранения конфигурационных данных, он используется для более глобальных целей.

В Apache есть несколько базовых объектов - процесс, сервер, коннект, запрос. В основном данные привязываются к этим объектам. Комбинация вектора конфигурации с пулами представляет собой фреймворк, в котором данные привязываются к соответствующему объекту. При этом убиваются два зайца:

  1. Конфигурационный массив предоставляет доступ к данным везде, где они востребованы.
  2. Пул привязан к жизненному циклу конкретного объекта, по истечении которого он очищается.

Т. е. данные могут быть ассоциированы с сервером, коннектом или запросом. Доступ к конфигурационному массиву сервера или конфигурационному массиву запроса возможен через объект сервера или запроса:

svr_cfg* my_svr_cfg =
      ap_get_module_config(server->module_config, &my_module);
dir_cfg* my_dir_cfg =
      ap_get_module_config(request->per_dir_config, &my_module);

Для инициализации переменных и их использования в рамках одного запроса, но в разных хуках, есть функции ap_set_module_config и ap_get_module_config. Инициализация:

static int my_early_hook(request_rec* r) {
  req_cfg* my_req;
  ...
  my_req = apr_palloc(r->pool, sizeof(req_cfg));
  ap_set_module_config(r->request_config, &my_module, my_req);
  /* инициализируем my_req  */
}

Использование:

static int my_later_hook(request_rec* r) {
  req_cfg* my_req = ap_get_module_config(r->request_config, &my_module);
  /* читаем my_req */
}

2. Взаимодействие модулей

Модули могут взаимодействовать друг с другом различным образом. В первую очередь это делается с помощью структуры request_rec.

	r->subprocess_env

r->subprocess_env - это таблица типа apr_table, время ее жизни равно времени жизни запроса, и она доступна всем модулям. В ней, в частности, представлено окружение CGI. Любой модуль может прочитать из нее значения. Модули могут определить свои собственные переменные для придания прав доступа другим модулям. Модуль mod_deflate может устанавливать переменный no-gzip и force-gzip, а также nokeepalive. Благодаря этому администратор может динамически конфигурировать систему.

	r->notes

r->notes - таблица типа apr_table_t, имеющая время жизни, равное времени жизни запроса. Сюда модуль может сбросить сообщения, предназначенные другому модулю. Например, она может быть использована для отсылки сообщения пользователю в случае ошибки.

	r->headers_in

r->headers_in - здесь хранятся заголовки запроса, доступно всем модулям. Модули могут изменять эти заголовки, например, сюда может быть записана информация, прочитанная из куков клиента.

	r->headers_out

r->headers_out - здесь хранятся заголовки ответа клиенту. Эта информация, хранимая в формате apr_table_t, конвертируется в простой текст в фильтрах вывода.

	r->err_headers_out

r->err_headers_out - заголовки ответа в случае, когда вместо ответа возвращается ошибка.

3. Временные файлы

Стандартный механизм создания временного файла выглядит следующим образом:

FILE *create_tmpfile_BAD(apr_pool_t *pool)
{
    FILE *ret ;
    char *template = apr_pstrdup(pool, "/tmp/my-module.XXXXXX");
    int fd = mkstemp(template);
    if (fd == -1) {
        apr_log_perror(....);
        return NULL;
    }
    ret = fdopen(fd, "rw");
    if (ret == NULL) {
        apr_log_perror(....);
        close(fd);
        return NULL;
    }
    apr_pool_cleanup_register(pool, ret, (void*)fclose,
                     apr_pool_cleanup_null);
    return ret;
}

Этот пример не совсем корректен по следующим причинам:

  1. Каталог /tmp/ корректен лишь в unix-системах.
  2. fdopen ссылается на POSIX.
  3. Тип FILE* на разных платформах ведет себя по-разному.

Вот APR-версия, гарантированно работающая на всех поддерживаемых платформах:

apr_file_t* create_tmpfile_GOOD(apr_pool_t *pool)
{
    apr_file_t *ret = NULL;
    const char *tempdir;
    char *template;
    apr_status_t rv;
    rv = apr_temp_dir_get(&tempdir, pool);
    if (rv != APR_SUCCESS) {
        ap_log_perror(APLOG_MARK, APLOG_ERR, rv, pool, "No temp dir!");
      return NULL;
  }
  rv = apr_filepath_merge(&template, tempdir, "my-module.XXXXXX",
                   APR_FILEPATH_NATIVE, pool);
  if (rv != APR_SUCCESS) {
      ap_log_perror(APLOG_MARK, APLOG_ERR, rv, pool,
              "File path error!");
      return NULL;
  }
  rv = apr_file_mktemp(&ret, template, 0, pool);
  if (rv != APR_SUCCESS) {
      ap_log_perror(APLOG_MARK, APLOG_ERR, rv, pool,
              "Failed to open tempfile!");
      return NULL;
  }
  return ret;
}

4. MPM

Apache поддерживает как многопроцессность, так и многопоточность. Одной из основных проблем является координация между ними. Формы взаимодействия ограничены самой платформой Apache. В первую очередь при реализации становятся актуальными два момента:

  1. Глобальные блокировки.
  2. Глобальная память.

APR содержит реализацию этих необходимых требований. В APR есть 2 типа мьютексов:

  1. apr_proc_mutex - мьютекс процессов.
  2. apr_global_mutex - глобальный мьютекс.

У глобального мьютекса более сложная инициализация. Он должен быть создан в родительском процессе в post_config фазе, причем каждый потомок должен быть приаттачен к родителю в child_init фазе:

static int my_post_config(apr_pool_t *pool, apr_pool_t *plog,
                apr_pool_t *ptemp, server_rec *s)
{
    /* поддерживаются несколько типов блокировок - см. apr_global_mutex.h
     * APR_LOCK_DEFAULT устанавливает тип блокировки по умолчанию
     */
    apr_status_t rc;
    my_svr_cfg *cfg =
        ap_get_module_config(s->module_config, &my_module);
    rc = apr_global_mutex_create(&cfg->mutex, cfg->mutex_name,
                        APR_LOCK_DEFAULT, pool);
    if (rc != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, rc, s,
                  "Parent could not create mutex %s", cfg->mutex_name);
        return rc;
    }
#ifdef AP_NEED_SET_MUTEX_PERMS
    rc = unixd_set_global_mutex_perms(cfg->mutex);
    if (rc != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, rc, cfg,
                     "Parent could not set permissions on global mutex:"
                     " check User and Group directives");
        return rc;
    }
#endif
    apr_pool_cleanup_register(pool, cfg->mutex,
           (void*)apr_global_mutex_destroy, apr_pool_cleanup_null);
    return OK ;
}
static void my_child_init(apr_pool_t *pool, server_rec *s)
{
    my_svr_cfg *cfg
        = ap_get_module_config(s->module_config, &my_module);
    apr_global_mutex_child_init(&cfg->mutex, cfg->mutex_name, pool);
}
static void my_hooks(apr_pool_t *pool)
{
    ap_hook_child_init(my_child_init, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_config(my_post_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_handler(my_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

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

static int my_handler(request_rec *r)
{
    apr_status_t rv;
    my_svr_cfg *cfg;
    cfg = ap_get_module_config(r->server->module_config, &my_module);
    /* захватываем мьютекс, блокируя конфигурацию модуля */
    rv = apr_global_mutex_lock(cfg->mutex);
    if (rv != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                "my_module: failed to acquire mutex!");
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    /* регистрируя cleanup и делая блокировку, мы ничем не рискуем */
    apr_pool_cleanup_register(r->pool, cfg->mutex,
                    (void*)apr_global_mutex_unlock,
                    apr_pool_cleanup_null);
 
  /* отпускаем блокировку */
  rv = apr_global_mutex_unlock(cfg->mutex);
  if ( rv != APR_SUCCESS ) {
      ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
              "my_module: failed to release mutex!");
  }
  apr_pool_cleanup_kill(r->pool, cfg->mutex, apr_global_mutex_unlock);
  return OK;
}

Глобальная память может быть использования для кеширования ресурса, который многократно используется. Модуль apr_shm подходит для хранения глобальных данных фиксированного размера, таких как переменные или структуры, но не указатели - для этого подходит другой модуль — apr_rmm. В следующем примере модуль mod_ldap использует комбинацию глобального кэша и динамического выделения:

apr_status_t util_ldap_cache_init(apr_pool_t *pool, util_ldap_state_t *st)
{
#if APR_HAS_SHARED_MEMORY
    apr_status_t result;
    apr_size_t size;
    if (st->cache_file) {
        /* удаляем shm сегмент с тем же именем*/
        apr_shm_remove(st->cache_file, st->pool);
    }
    size = APR_ALIGN_DEFAULT(st->cache_bytes);
    result = apr_shm_create(&st->cache_shm, size,
                st->cache_file, st->pool);
    if (result != APR_SUCCESS) {
        return result;
    }
    /* Определяем размер shm сегмента */
    size = apr_shm_size_get(st->cache_shm);
    /* создаем rmm "handler" для получения указателя на shared memory area */
    result = apr_rmm_init(&st->cache_rmm, NULL,
                          apr_shm_baseaddr_get(st->cache_shm), size,
                          st->pool);
    if (result != APR_SUCCESS) {
        return result;
    }
#endif
    /* OMITTED FOR BREVITY */
    /* регистрируем cleanup для пула с вызовом apr_rmm_destroy
     * и apr_shm_destroy, когда apache exits.
     */
    return APR_SUCCESS;
}

Используя функцию apr_rmm, получаем указатель на глобальную память.

5. Secure

Каждый URL переводится на локальный путь, при этом имя файла может проходить проверку на соответствие определенным шаблонам, например [\w-_]{1-16}\.\w{3}. Подобные шаблоны могут широко варьироваться в зависимости от особенностей конкретной операционной системы. Аналогичные принципы работают тогда, когда модули Apache используют данные из заголовков клиентского запроса, из тела запроса или других источников. В случае ошибки необходимо генерировать соответствующий ErrorDocument.

На системном уровне нужно выполнять следующие требования: проверять статус всех системных вызовов и APR вызовов. В случае обнаружения ошибок останавливать обработку запроса и возвращать HTTP_INTERNAL_SERVER_ERROR. Нужно контролировать переполнение буфера. Прежде чем ваш модуль начнет писать данные в выделенный буфер, всегда проверяйте его размер.

Отказ в обслуживании - denial of service (DoS) - происходит тогда, когда сервер становится неуправляемым. Это может произойти, например, тогда, когда происходит массовая атака и сайт не в состоянии обслужить огромное число запросов, апогеем такой атаки является случай, когда атаки происходят с различных машин.

Защита от доса может быть реализована на администраторском уровне. Для этого есть ряд модулей:

  1. mod_evasive - ограничивает трафик по клиентскому IP.
  2. mod_cband - управление трафиком и числом коннектов.
  3. mod_load_average - отказывает в обслуживании запроса, если сервер слишком занят. В этом случае возвращается 503-я ошибка - server busy.
  4. mod_robots - фильтрует роботов и спам-ботов.

При написании собственного модуля хорошим тоном считаются следующие моменты:

  1. Используйте apr_reslist - это даст возможность администратору ограничить количество одновременных пользователей.
  2. Клиенту на сетевом уровне нужно выставлять таймаут.
  3. Если модуль поддерживает большие транзакции, например, потоковое видео, необходимо постоянно следить за освобождением памяти, используя локальные пулы.

На уровне операционной системы нужно соблюдать ряд требований. Apache не должен иметь рутовых прав, в частности, на файлы и каталоги. Выполнение CGI скриптов нужно оборачивать с помощью suexec. Должен стоять файрвол. Доступ к файловой системе должен быть описан в httpd.conf. Вызовы с префиксом apr_filepath фильтруют доступ к неавторизованным файлам и каталогам.

Одной из форм эксплойта является запись файла в каталог /tmp с последующим его выполнением. Причем это выполняется даже не на уровне сервера, а на уровне приложения (php, например). В этом случае на уровне администратора нужно выполнить следующие правила:

  1. На уровне прав пользователей/каталогов ограничивать пользователей Apache писать в определенные каталоги.
  2. Для того чтобы нельзя было запустить программу, каталог должен быть примонтирован с параметром noexec.

6. Другие языки

Модуль Apache может быть написан не только на С, но и на других языках:

  1. Компилируемые языки: C++ , Fortran, Modula.
  2. Скриптовые языки: Perl, PHP, Python, Ruby, Tcl. Наиболее полное соответствие API в этой группе языков у Perl.

Собрать и загрузить модуль можно по следующей схеме:

  1. Делается экспортная обвязка С-совместимого кода.
  2. При компиляции привязываются линки на APR.
  3. Линкуется как динамический объект.
  4. Скомпилированный модуль помещается в каталог, где находятся модули Apache.
  5. Загрузка прописывается в httpd.conf.

Пример компиляции:

# c++ -g -O2 -Wall -fPIC -I/usr/local/apache/include mod_foo.cpp
# c++ -shared -o mod_foo.so mod_foo.o
# cp mod_foo.so /usr/local/apache/modules/

Макросы, используемые в вашем модуле, должны иметь расширенную версию. Например, пусть имеется макрос:

AP_INIT_TAKE2("ValidatorDefault",
    (const char*(*)())ValidatorDefault, NULL, OR_ALL,
        "Default parser; default allowed parsers" )

Он должен быть расширен до формы:

{ "ValidatorDefault",
    (const char*(*)(cmd_parms*, void*))ValidatorDefault,
    __null, (1|2|4|8|16), TAKE2,
    "Default parser; default allowed parsers" }

Заключение

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

Источник: Nick Kew, The Apache Modules Book.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=623794
ArticleTitle=Apache 2: Часть 7. Замечания по технике программирования
publish-date=02082011