Описание API взаимодействия с
сервером фильтрации почты
Техническое описание
2004-02-21
Версия 1.5.0

История изменений:
Кто менял      Что менял                                     Когда менял, версия
А. Тутубалин   Начальная редакция                            30.04.2003, 1.0
A. Тутубалин   Исправлена ошибка в описании порядка          18.05.2003, 1.01
               параметров sts_prepare_envelope
A. Тутубалин   Все константы имеют префикс STS_              24.05.2003, 1.02
A. Тутубалин   Расширен интерфейс контейнера для сообщения   21.02.2004, 1.5.0
               message_t. Изменен интерфейс для работы с
               envelope сообщения (старый вызов
               sts_prepare_envelope оставлен только для
               совместимости). Поддержано действие
               HeaderPrepend – добавить строку в начало
               заголовка. Изменено описание glue_actions.
               Описана структура действий над заголовками
Содержание:
1.          НАЗНАЧЕНИЕ ДОКУМЕНТА ............................................................................ 3
2.          ОБЗОР ДОКУМЕНТАЦИИ.................................................................................. 3
3.          ОБЩИЕ СВЕДЕНИЯ ............................................................................................ 3
     3.1.       ИСПОЛЬЗОВАНИЕ БИБЛИОТЕКИ .......................................................................................... 4
       3.1.1.       Заголовочные файлы.......................................................................................................... 4
       3.1.2.       Библиотеки............................................................................................................................ 4
4.          ОСНОВНЫЕ СТРУКТУРЫ ДАННЫХ .............................................................. 4
     4.1.       SPAMTEST_SESSION_T ............................................................................................................... 4
     4.2.       SPAMTEST_STATUS_T ................................................................................................................. 5
       4.2.1.       Константы, описывающие статус письма ..................................................................... 6
       4.2.2.       message_status_t .................................................................................................................... 6
       4.2.3.       Вспомогательные структуры данных .............................................................................. 7
5.          ОСНОВНЫЕ ВЫЗОВЫ API................................................................................. 7
     5.1.       ПРОЦЕСС ОБРАБОТКИ ОТДЕЛЬНОГО СООБЩЕНИЯ ......................................................... 7
     5.2.       КОДЫ ОШИБОК. ........................................................................................................................ 8
     5.3.       STS_STRERROR............................................................................................................................. 8
     5.4.       STS_INIT ....................................................................................................................................... 8
     1.1.       УСТАНОВКА ПАРАМЕТРОВ ENVELOPE СООБЩЕНИЯ........................................................ 9
     5.6.       STS_SEND_BODY....................................................................................................................... 10
     5.7.       STS_BODY_END ........................................................................................................................11
     5.8.       STS_CLOSE.................................................................................................................................. 11
     5.9.       ЖУРНАЛИРОВАНИЕ (ВЕДЕНИЕ ЛОГ-ФАЙЛОВ)................................................................. 11
6.          ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ.................................................................12
     6.1.       ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ ОБРАБОТКИ ДАННЫХ ОТ ФИЛЬТРА ................12
       6.1.1.       make_changed_headers.......................................................................................................12
       6.1.2.       glue_actions...........................................................................................................................12
       6.1.3.       dump_message_status .........................................................................................................13
     6.2.       «КОНТЕЙНЕРЫ» ДЛЯ РАБОТЫ С СООБЩЕНИЯМИ ...........................................................13
       6.2.1.       Обработка ошибок ............................................................................................................14
       6.2.2.       Списки реципиентов (recipient_list_t) ..........................................................................14
       6.2.3.       Контейнер для текстов (message_text_t) ......................................................................15
     6.2.4.   Контейнер для заголовков (headers_list_t) ..................................................................16
     6.2.5.   Контейнер для сообщения (message_t)........................................................................17
     6.2.6.   Канонизация E-mail адресов...........................................................................................18
7.     АЛЛОЦИРОВАНИЕ И ОСВОБОЖДЕНИЕ ПАМЯТИ .................................18
8.     ОБРАБОТКА ТАЙМАУТОВ В ФУНКЦИЯХ STS_*.........................................19



1. НАЗНАЧЕНИЕ ДОКУМЕНТА
В данном документе описывается API «библиотеки сопряжения», предназначенной для
написания произвольных клиентов к серверу фильтрации почты (антиспам-фильтру).


2. ОБЗОР ДОКУМЕНТАЦИИ
Документация на Spamtest-API включает в себя следущие документы
Spamtest-API-Intro – обзор архитектуры и способов работы фильтра
Spamtest-API-Reference – (данный документ) детальное описание API и структур данных.
Spamtest-API-Sample – пример использования API
Spamtest-MasterServer – описание process-server, управляющего запуском, остановом и
           т.п. процессов фильтрации
Spamtest-Protocol – описание протокола взаимодействия фильтр-клиент


3. ОБЩИЕ СВЕДЕНИЯ
API библиотеки сопряжения (далее в тексте – API) предназначено для коммуникации
между сервером фильтрации почты Spamtest/Kaspersky Antispam (далее в тексте –
фильтр) и клиентским приложением в которое нужно встроить возможность
фильтрации почты на спам (далее в тексте – клиент или пользователь).
Библиотека предназначена для использования с компилятором GNU CC на платформах
FreeBSD и Linux. Версии под другие операционные системы могут быть предоставлены
по запросу.
Библиотека написана на языке C, другие языки программирования «внутри» не
используются.
Библиотека требует стандартной libc и базовых функций для работы с сетью
(gethostbyname, getservbyname, socket, connect, fcntl, read, write), в ряде операционных
систем часть перечисленных функций может быть в libnsl
Библиотека является thread safe, работа с multithread тестировалась под FreeBSD 4.8
pthreads, Linux kernel 2.4 (posix threads), Solaris 2.6-8.0.
   3.1.      Использование библиотеки
      3.1.1.        Заголовочные файлы
Для использования библиотеки в приложении нужно включить в исходный текст
заголовочные файлы spamtest.h (базовые функции, разделы 4 и 5 ) и msgstore.h
(функции для работы с контейнерами, см. раздел 6). Эти заголовочные файлы находятся
в каталоге /usr/local/ap-mailfilter/include

      3.1.2.        Библиотеки
Для сборки single-thread версии используется библиотека libspamtest.a
Для сборки multi-thread версии используется библиотека libspamtest_r.a
Обе библиотеки находятся в каталоге /usr/local/ap-mailfilter/lib
При сборке multi-thread приложения под FreeBSD компилятору следует указывать ключ –
pthread, под другими OS - -D_REENTRANT и ключи для сборки multi-threaded
приложения.


4. ОСНОВНЫЕ СТРУКТУРЫ ДАННЫХ
   4.1.      spamtest_session_t
Структура spamtest_session_t описывает все данные сессии взаимодействия с фильтром.
Она должна быть проинициализирована до начала сессии, часть данных в ней
предназначен для чтения пользователем.

typedef struct __spamtest_session_t {
    /* == THESE PARAMETERS ARE SET BY USER === */
    char *access_address;
    char *mail_domain;
    size_t bufsize;
    logger_proc logp;
    long rw_timeout;
    long connect_timeout;

    /* == THESE PARAMETERS SHOULD BE USED BY USER === */
    spamtest_status_t status;
    int recomended_size;
    int should_store_message;
    char *message_id;
    /* internal filter data, do not touch */
…. Внутренние данные ... не описаны
} spamtest_session_t;
Интерес для пользователя в этой структуре представляют только описаные выше поля.
Назначение их таково:
Поля, устанавливаемые пользователем до начала сессии:
access_address – «адрес» по которому нужно соединяться с процессом фильтрации в
   формате unix:/path/to/socket или tcp:host:port. Умолчания нет, данное поле должно
   инициализироваться всегда.
mail_domain – имя почтового домена для использования в случае, когда домен у
  отправителя/получателя сообщения не указан. Умолчания нет, если данное поле не
  заполнено (NULL), то нормализация адресов не производится.
bufsize – размеры буферов ввода-вывода. Умолчание – 8192 байта.
logp – указатель на процедуру ведения логов (журналов), процедура по-умолчанию
   описана ниже.
rw_timeout – таймаут на операции ввода-вывода в миллисекундах. Таймаут описывает
   общее время сессии, однако это поведение можно изменить (см. описание основных
   функций). Умолчание – 50000 мс.
connect_timeout - таймаут на соединение с фильтром в миллисекундах. Умолчание –
   1000 мс.
Поля, предназначенные для чтения пользователем.
status – поле типа spamtest_status_t, описано ниже.
recomended_size – рекомендуемый фильтром размер посылаемых данных до того, как
   спрашивать статус письма (см. ниже sts_send_body/sts_body_end).
should_store_message – нужно ли сохранять сообщение на клиенте локально (Actions-
   mode) или можно не сохранять (SMTP-mode).
message_id – идентификатор сообщения, присвоенный фильтром.

   4.2.      spamtest_status_t
Структура spamtest_status_t описывает состояние сессии а также (если это применимо к
конкретной сессии) – описание действий над сообщениями, которые вернул фильтр.
typedef struct
{
    session_status_t      status;
    char                  *reason;
    int                   errorcode;
    int                   count;
    message_status_t      *ms_array;
/* внутренние данные не   описаны */
}spamtest_status_t;
status – состояние сессии, одна из констант, описанная в пункте 3.3.1
reason – если письмо отвергнуто (status==STS_ERROR/ STS_REJECT), то по какой
   причине.
errorcode – для статуса STS_SENDERROR – код ошибки.
count – для статуса STS_ACCEPT – число порожденных фильтром сообщений.
ms_array – массив порожденных фильтром статусов отдельных писем, общим числом
  count.
      4.2.1.        Константы, описывающие статус письма
Статус ответа фильтра (session_status_t, поле status):
   o STS_SENDERROR - произошла ошибка сессии.
   o STS_CONTINUE – фильтр не определил еще статус сообщения, необходимо
     продолжать посылать данные.
   o STS_ACCEPT – фильтр определил статус письма – оно должно быть принято. В
     поле count находится счетчик количества действий над письмами (не менее
     одного), в поле ms_array – описание действий. Посылающему релею нужно отдать
     SMTP-статус 220
   o STS_FILTER_ERROR – произошла ошибка фильтра (например, в SMTP-режиме
     не удалось открыть соединение на принимающий relay). Посылающему релею
     нужно отдать статус 45x, дабы он перепослал сообщение позже.
   o STS_REJECT – фильтр отверг сообщение, посылающему релею нужно отдать
     статус 55x с сообщением из поля reason.
   o STS_BLACKHOLE – принятое письмо надлежит уничтожить (не пересылать
     дальше), Посылающему релею нужно отдать SMTP-статус 220
   o STS_SMTP_ACCEPT – письмо отослано фильтром дальше в SMTP-режиме.
     Посылающему релею нужно отдать SMTP-статус 220

      4.2.2.        message_status_t
Структура message_status_t содержит следущие поля:
typedef struct message_status_t
{
    spamtest_reply_t    action;           /* STS_ACTION_CHANGE/BOUNCE */
    char                *mailfrom;
    recipient_list_t    *rcpts;           /* RCPT TO list for message */
    int                 action_count;     /* count for header actions */
    hdraction_t         *act_array; /* array of header actions */
    message_text_t      *bouncemsg; /* message body for BOUNCE */
} message_status_t;
action – тип ответа фильтра:
    o STS_ACTION_CHANGE – фильтр предлагает изменить заголовки/список
      получателей пиьсьма, описание действий будет в массиве act_array, счетчик
      действий – в action_count
    o STS_ACTION_BOUNCE – фильтр предоставляет целое письмо для посылки (с
      заголовками и текстом), текст письма будет в поле bouncemsg
mailfrom – отправитель для сгенерированного сообщения.
rcpts – список получателей для для сгенерированного сообщения.
action_count – количество действий над заголовками
act_array – массив действий над заголовками.
bouncemsg – body (headers+text) сгенерированного фильтром нового письма
Массив act_array – это массив структур, общим количеством action_count, структуры
  имеют следущее описание:
typedef struct hdraction_t
{
     spamtest_action_t    type;                /* STS_HEADER_ADD/DEL/NEW/CHG/PREPEND */
     char                 *header;             /* header name */
     char                 *value;              /* header value */
}
type – одна констант типа spamtest_action_t:
    o STS_ACTION_NONE – пустое действие
    o STS_ACTION_DEL – удалить все заголовки с данным именем
    o STS_ACTION_CHG – изменить заголовок (эквивалентна DEL+ADD)
    o STS_ACTION_ADD – добавить строку в конец первого заголовка
    o STS_ACTION_NEW – добавить новый заголовок
    o STS_ACTION_PREPEND – добавить строку в начало первого заголовка
header – имя заголовка над которым нужно произвести действие (без ‘:’ на конце)
value – NULL для STS_ACTION_DEL, добавляемая строка для CHG/ADD/NEW/
    PREPEND

       4.2.3.       Вспомогательные структуры данных
Описание вспомогательных структур данных (типы-контейнеры для списка реципиентов,
для текста сообщений, для наборов заголовков) пользователю библиотеки не нужно, все
необходимые действия можно произвести с помощью готовых библиотечных функций.
При необходимости, информацию о структурах данных можно почерпнуть из
заголовочных файлов. Вспомогательные функции для работы с этими данными описаны
в разделе 6.


5. ОСНОВНЫЕ ВЫЗОВЫ API
    5.1.     Процесс обработки отдельного сообщения
Процесс обработки отдельного сообщения включает в себя такие стадии:
    1. Инициализации полей структуры spamtest_session_t (необходимо, как минимум,
       заполнить поле access_address).
    2. Вызов функции sts_init() – выполнится соединение с фильтром, библиотека
       определит режим работы.
    3. одним или несколькими вызовами sts_set_/sts_add устанавливаются параметры
       envelope сообщения (адрес посылающего relay, адреса отправителя и получателей,
       дополнительные данные если они существуют)
   4. Одним или многими вызовами sts_send_body в фильтр передается body
      сообщения (заголовки и текст), фильтр возвращает статус сообщения, возможно
      что статус письма удастся определить до передачи его целиком
   5. Если на шаге 4 статус письма не был получен, то вызовом sts_body_end() фильтр
      уведомляется об окончании сообщения, результатом вызова будет статус письма.
   6. Вызовом sts_close() закрывается соединение и деаллоцируется память.

   5.2.      Коды ошибок.
Определены следущие виды ошибок:
STS_ERR_NO_ERR – нет ошибки.
STS_ERR_OUT_OF_MEMORY – невозможно аллоцировать память.
STS_ERR_UNABLE_TO_CONNECT – невозможно соединиться с фильтром (connection
refused и т.п.).
STS_ERR_CONNECT_TIMEOUT – при соединении с фильтром наступил таймаут.
STS_ERR_SND_RCV_TIMEOUT – при вводе-выводе данных наступил таймаут.
STS_ERR_INCORRECT_PROTOCOL – ошибка в передаче данных – неверный
протокол, неожиданные данные и т.п.
STS_ERR_UNABLE_TO_RESOLVE – невозможно определить адрес по hostname (для
адресов доступа вида tcp:host:port)
STS_ERR_READ_ERR – ошибка при чтении.
STS_ERR_ILLEGAL_CONFIG – пользователем задана неверная конфигурация.
STS_ERR_WRITE_ERR – ошибка при записи.
STS_ERR_INVALID_PARAMS – в функцию sts_* переданы неверные параметры.
STS_ERR_NOT_INITED – функция sts_* вызвана до sts_init()

   5.3.      sts_strerror
char* sts_strerror(int errorcode)
Функция предназначена для расшифровки кодов ошибок , она возвращает словесное
описание соотв. кода.

   5.4.      sts_init
int sts_init(spamtest_session_t * session)
Функция производит инициализацию сессии – аллоцирование памяти, соединение с
фильтром, определение режима работы. До вызова этой функции должны быть
проинициализированы параметры сессии. Пример использования:
    spamtest_session_t s;
    bzero(&s,sizeof(s));
    s.access_address = “tcp:myserver:1234”;
     s.mail_domain = “pupkin.com”;
     s.rw_timeout= 10000;
     s.connect_timeout= 3000;
     if(STS_ERR_NO_ERR != sts_init(&s)) my_error_handler(“sts_init”);


   5.5.       Установка параметров envelope сообщения
int sts_set_sender(spamtest_session_t *s, const char *mail_from);
int sts_add_recipient(spamtest_session_t *s, const char *rcpt);
int sts_add_attribute(spamtest_session_t *s, const char *attrname, const char *attrval);
int sts_set_relay(spamtest_session_t *s, const char *relay_IP);
Совместимость со старыми версиями:
int sts_prepare_envelope(spamtest_session_t *s,
                const char* mail_from,
                const char * rcpt,
                const char *relay_IP,
                int *stat);
Функции инициализируют envelope сообщения:
   o sts_set_sender – устанавливает E-mail адрес отправителя сообщения в значение,
     указанное параметром mail_from. Функция может быть вызвана многократно, в
     этом случае она переустановит значение отправителя.
   o sts_add_recipient – добавляет в список получателей письма значение rcpt. Функция
     может быть вызвана многократно, при этом список отправителей будет дополнен.
   o sts_add_attribute – добавляет значение attrname=attrval к атрибутам письма.
     Поддерживаются следующие атрибуты:
          o client_address – IP-адрес посылающего релея в символьном виде
            (aaa.bbb.cc.ddd)
          o client_name – DNS-имя посылающей стороны (если оно было определено
            MTA), либо “unknown” если IP посылающего релея в DNS отсутствует.
          o helo_name – строка, переданная посылающим релеем в HELO SMTP-
            сессии.
   o sts_set_relay – эквивалентно вызову sts_add_attribute(..,”client_address”,relay_IP) с
     дополнительной проверкой параметра relay_IP на валидность его как
     представления IP-адреса.
   o sts_prepare_envelope – вызов, оставленный для совместимости с ранними версиями
     API. Эквивалентен вызову sts_set_sender+sts_add_recipient+sts_set_relay.
Возвращаемые значения:
При отсутствии ошибки все вызовы возвращают 0.
Все функции могут вернуть STS_ERR_OUT_OF_MEMORY при нехватке памяти.
sts_set_relay может вернуть STS_ERR_ILLEGAL_ADDRESS если ей передано
некорректное значение адреса
sts_prepare_envelope возвращает 0, либо STS_ERR_OUT_OF_MEMORY. Если ей был
передан неверный адрес в параметре relay_IP и ненулевой параметр stat, то в *stat
ставится информационный бит STS_ERR_ILLEGAL_RELAY

   5.6.      sts_send_body
spamtest_status_t sts_send_body(
             spamtest_session_t *session,
             char *data,
             unsigned int datalen,
             unsigned int flags)
Функция выполняет посылку части body письма (заголовков и текста) на фильтр.
Параметры:
   o session – указатель на структуру spamtest_session_t
   o data – указатель на посылаемые данные (см. ниже)
   o datalen – длина посылаемых данных в байтах
   o flags – битовая маска, являющаяся комбинацией из значений:
          o STS_REINIT_TIMEOUT - реинициализировать таймаут (начать отсчет
            заново) – может применяться в случае, когда таймаут хочется менять на
            ходу.
          o STS_FLUSH_DATA – явно запросить статус письма у фильтра, даже если
            послано меньше данных чем им запрошено
          o STS_FINAL_CHUNK – индицирует, что посылается последняя порция
            данных, в этом случае внутри sts_send_body() будет вызвана sts_body_end()
Замечания:
Данные можно передавать кусками любой длины, хоть по одному байту, но при этом
следует учитывать что:
   o При посылке неполных строк эффективность протокола передачи падает
   o В случае, если в заголовках письма содержится строка ‘From …‘ (разделитель в
     Unix-mailbox), то она будет правильно обработана фильтром только если послана
     за один вызов sts_send_body
Другими словами, оптимально передавать данные в эту функцию отдельными строками
или по несколько целых строк за один раз. С другой стороны, если можно гарантировать
передачу заголовков письма за один вызов (сразу большим куском), то вышеописанные
соображения уже не столь существенны и вполне разумным будет следующий код:
while((read_len = read(input, databuf, 128*1024))>0)
      {
             status=sts_send_body(session,databuf,read_len);
             /* обработка статуса возврата
     }
Возвращаемые значения:
   o NULL при неверном параметре session
   o Указатель на структуру spamtest_status_t во всех прочих случаях

   5.7.      sts_body_end
spamtest_status_t *sts_body_end(
              spamtest_session_t *session,
              unsigned int flags)
Функция уведомляет фильтр об окончании передачи текста сообщения и получает статус
сообщения.
Параметры:
   o session – указатель на структуру spamtest_session_t
   o flags – битовая маска, являющаяся комбинацией из значений:
          o STS_REINIT_TIMEOUT - реинициализировать таймаут (начать отсчет
            заново) – может применяться в случае, когда таймаут хочется менять на
            ходу.
Возвращаемые значения:
   o NULL при неверном параметре session
   o Указатель на структуру spamtest_status_t во всех прочих случаях

   5.8.      sts_close
void sts_close(spamtest_session_t *session)
Функция закрывает соединение и освобождает память.
Параметры:
   o session – указатель на структуру spamtest_session_t
Возвращаемые значения: нет.

   5.9.      Журналирование (ведение лог-файлов)
Внутри библиотеки производятся вызовы функции журналирования. Эта функция может
быть предоставлена пользователем (поле spamtest_session_t logp), либо же используется
функция по умолчанию.
Функция журналирования имеет прототип вида:
typedef int (* logger_proc)(spamtest_session_t *session,int loglevel,const char *
fmt,...);
Параметры:
   o session – указатель на структуру spamtest_session_t
   o loglevel – уровень сообщения (от LOG_ERR до LOG_DEBUG+2)
   o fmt – формат вывода (аналогично printf()/syslog())
Функция по-умолчанию имеет следующий код:
int sts_default_logger_log_level=LOG_ERR;
static int default_logger(spamtest_session_t *s, int loglevel, char *fmt, ...)
{
    char logbuf[512];
    char logbuf2[128];
    va_list ap;
    if(loglevel>sts_default_logger_log_level) return 0;
    va_start(ap, fmt);
    vsnprintf(logbuf,512, fmt, ap);
    va_end(ap);
    snprintf(logbuf2,128,"%s:",s->message_id?s->message_id:"No MSGID");
    syslog(loglevel>LOG_DEBUG?LOG_DEBUG:loglevel,"%s %s",logbuf2,logbuf);
    return 0;
}
Значение переменной sts_default_logger_log_level может меняться пользователем.



6. ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
   6.1.   Вспомогательные функции для обработки данных
      от фильтра
      6.1.1.       make_changed_headers
headers_list_t* make_changed_headers
      (message_status_t * sts,
       message_t *message);
Функция делает измененные заголовки для сообщения message на основании
полученного от фильтра статуса message_status_t *sts
Типы данных headers_list_t, message_t и средства для работы с ними рассмотрены ниже в
пункте 6.2

      6.1.2.       glue_actions
spamtest_status_t* glue_actions(spamtest_status_t *s);
Функция предназначена для объединения статусов писем, полученных от фильтра в
ситуации когда:
   1. От фильтра получены множественные статусы (например, разные пользователи
      заказали разные действия для спама, а письмо пришло на много получателей)
   2. Клиентское приложение не поддерживает размножения писем
Функция glue_actions возвращает новый статус сообщения у которого:
          o Поле count равно 0 или 1
          o Выполнено склеивание действий над заголовками
Склеивание выполняется следующим способом:
   1. Если от фильтра получены только bounce messages (одно или несколько), то
      делается новое сообщение у которого:
          a. Отправитель и bodу берутся от первого из bounces
          b. Список получателей формируется из получателей всех bounces без дублей.
   2. Если от фильтра получены Actions и bounces, то:
          a. Все bounce messages игнорируются
          b. Создается одно Action-message у которого:
                 i. Отправитель и bodу берутся от первого из писем.
                ii. Список получателей формируется из получателей всех Actions без
                    дублей.
               iii. Действия над заголовками «складываются», т.е. все HEADER_CHG
                    рассматриваются как DEL+ADD, после чего выполняются сначала
                    все DELETE, которые присутствуют во всех наборах действий, затем
                    все действия из первого набора действий, затем все действия из 2-го и
                    последующих наборов действий, которые не являются
                    дублирующими (уже выполнеными).

      6.1.3.       dump_message_status
void dump_message_status(spamtest_status_t *smsg);
Функция предназначена для отладки. Она печатает статус сообщения в читаемой форме
на стандартный выход (stdout)

   6.2.      «Контейнеры» для работы с сообщениями
Контейнеры предназначены для удобной работы с частями сообщения (заголовками,
атрибутами, body) или с сообщением «в целом». Контейнеры являются частью ответа
полученного из фильтра, их следует использовать с помощью методов контейнера. Те
же контейнеры могут использоваться для работы с письмом (сохранение копии письма
или его части) внутри вызвавшего приложения, однако большинству контейнеров, за
исключением message_t, нужен менеджер памяти (msg_heap_t), работа с которым в
данном документе не описана.

      6.2.1.        Обработка ошибок
Все перечисленные ниже функции возвращают при ошибке либо NULL (для функций,
возвращающих указатель), либо, для функций возвращающих целое, - отрицательное
значение – код ошибки. Положительные и нулевые значения для возвращающих целое
чисел означают успешное завершение.
Единственный код ошибки который может возникнуть – STS_ERR_OUT_OF_MEMORY

      6.2.2.        Списки реципиентов (recipient_list_t)
Для работы со списком реципиентов предназначен тип данных recipient_list_t, для
работы с ним предназначены следущие функции (инициализация требует знаний о
memory manager, предполагается что список реципиентов попадает к пользователю
библиотеки уже инициализированным):
   o void rcptlist_clean(recipient_list_t*); - очистка списка
   o int rcptlist_add(recipient_list_t*,const char*); - добавление элемента
   o int rcptlist_exists(recipient_list_t*,const char*); - проверка существования
     элемента
   o int rcptlist_addn(recipient_list_t *rl, const char *rcpt,size_t len); - добавление
     элемента с известной длиной строки
   o int rcptlist_adduniq(recipient_list_t *rl, const char *rcpt); - добавление
     уникального элемента (если он уже не существует в списке)
   o int rcptlist_count(recipient_list_t*); - возвращает число элементов в списке
   o char* rcptlist_getn(recipient_list_t*,int i); - возвращает I-й элемент
   o int rcptlist_iterate(recipient_list_t*, writer_func_t func); - для каждого элемента
     списка запускается func() в которую передаются два параметра – указатель на
     начало строкового представления элемента и длина.
Дополнительные описания:
typedef void (* writer_func_t)(const char*, size_t len);
Пример использования, печать списков реципиентов письма, полученных в ответе от
фильтра:

recipient_list_t *list = spamtest_status->ms_array[0].rcpts;
int i;
for (i=0; i< rcptlist_count(list); i++)
      printf(“RCPT TO: %s\n”,rcptlist_getn(list,i));
Эквивалентный код через iterator выглядит так:
void writer (const char *data, size_t len)
{
       printf(“RCPT TO: %s\n”,data);
}

rcptlist_iterate(spamtest_status->ms_array[0].rcpts, writer);
Для создания собственного списка реципиентов используется вызов
recipient_list_t *rcptlist_init(msg_heap_t *heap);


       6.2.3.       Контейнер для текстов (message_text_t)
Контейнер message_text_t предназначен для хранения произвольных текстов, включая
тексты сообщений. В этом сообщении не производится разделения на заголовки и текст
письма, поэтому отдельная работа с заголовками невозможна.
Хранение производится эффективным по памяти способом, реаллокация памяти не
используется (только до-аллокация), как следствие письмо хранится кусками (chunk)
произвольной длины. Эти куски – не NULL terminated, соответственно работать с
ними строковыми функциями (вычисляющими длину) нельзя.
Для работы с этим типом данных предназначены следущие функции:
    o long msgtext_totalsize(message_text_t *msgt); - возвращает общую длину (сумму
      по всем chunks) сообщения
    o int msgtext_count_chunks(message_text_t *msgt); - возвращает количество
      чанков.
    o int msgtext_chunk_len(message_text_t *msgt, size_t chunk); - возвращает длину
      куска с номером chunk.
    o char* msgtext_get_chunk(message_text_t *msgt, size_t chunk); - возвращает
      указатель на кусок с номером chunk
    o void msgtext_callback(message_text_t *msgt, writer_func_t func); - вызывает
      функцию func на каждый chunk с параметрами (указатель на данные, длина)
    o int msgtext_puttext(message_text_t *msgt, const char *data, size_t len); -
      помещает в контейнер текст data длиной len
Пример использования, печать сохраненного сообщения:
«Вручную»:
message_text_t *msg = spamtest_status->ms_array[0].bouncemsg;
int i;
for (i=0; i< msgtext_count_chunks(msg); i++)
      write(1,msgtext_get_chunk(msg,i),msgtext_chunk_len(msg,i));
Через итератор:
void writer (const char *data, size_t len)
{
      write(1,data,len)
}

msgtext_callback(spamtest_status->ms_array[0].bouncemsg, writer);
Для создания собственного контейнера для тела письма может быть использован один из
двух «конструкторов»:
message_text_t *msgtext_init(msg_heap_t *heap);
message_text_t *msgtext_init_with_tempfile(msg_heap_t *heap,
                                           const char *dir,size_t max_rss);
Первый из них создает контейнер «в памяти», второй – создает «умный» контейнер,
который при превышении текстом письма размера max_rss создаст временный файл без
имени в каталоге dir. Доступ к файлу будет осуществляться через mmap().
Если параметры dir либо max_rss – нулевые, то второй конструктор полностью
аналогичен первому.
При создании контейнера вторым способом (с временным файлом), его необходимо
явно закрывать вызовом
void msgtext_close(message_text_t *msgt);
этот вызов выполнит munmap() и закроет временный файл.

      6.2.4.       Контейнер для заголовков (headers_list_t)
Тип данных headers_list_t предназначен для работы с заголовками – добавления,
удаления и т.п.
Вызовы и типы данных:
   o typedef void (* header_iterator_t)(char* hname, char *hval); - функция-итератор,
     вызывается с двумя NULL-terminated параметрами – имя заголовка и его значение.
     Значение включает в себя завершающий символ ‘\n’
   o int headers_iterate(headers_list_t *hdrs, header_iterator_t callback); - вызов
     итератора callback для списка hdrs
   o headers_list_t * headers_clone(message_t *msg); - делает копию заголовков для
     сообщения msg (тип данных message_t рассмотрен ниже)
   o int headers_del_hdr(headers_list_t *,const char *header); - уничтожает из списка
     (помечает как удаленные) все заголовки с именем header
   o int headers_add_hdr(headers_list_t *,const char *header, const char *value); -
     добавляет заголовок header со значением value. Если заголовок уже существует, то
     новое value добавляется к самому нижнему значению через continuation symbols
   o int headers_new_hdr(headers_list_t *,const char *header,const char *value); -
     добавляет заголовок header со значением value. Если заголовок уже существует, то
     добавляется новое поле Header: value
   o int headers_chg_hdr(headers_list_t *hdr,const char *header,const char *value); -
     эквивалентно комбинации headers_del_hdr + headers_add_hdr
Пример использования, получить копию заголовков от сообщения message_t (см. ниже),
добавить свои заголовок и вывести на печать:
void hdr_writer(char *header, char *value)
{
      printf(“%s %s”,header,value);
}
message_t *msg;
headers_list_t *my_headers = headers_clone(msg);
headers_new_hdr(my_headers,”X-Header”, “Approved by electronics”);
headers_iterate(my_headers,hdr_writer);

      6.2.5.       Контейнер для сообщения (message_t)
Контейнер для сообщения message_t является хранилищем всех необходимых данных
сообщения, имеющим все необходимые поля. Контейнер предназначен для хранения
сообщений, размером не более максимального значения size_t (4Gb на большинстве
систем).
Для использования этого контейнера предназначены следущие функции:
Создание:
   o message_t *message_init(void);
   o message_t *message_init_with_tempfile(const char*tempdir, size_t maxrss);
Создание контейнера первым способом даст «контейнер в памяти» - сообщение будет
храниться в памяти процесса. При использовании второго способа, если maxrss больше 0
и tempdir не равно NULL, то по достижении телом сообщения размера maxrss в каталоге
tempdir будет создан временный файл без имени (mkstemp + unlink) в котором и будет
сохраняться тело сообщения. Доступ к телу сообщения будет осуществляться через
mmap().
Уничтожение:
   o void message_free(message_t *msg);
Установка/получение envelope from:
   o int message_set_from(message_t *msg, const char *from);
   o char *get_message_from(message_t *msg);
Установка/получение IP-адреса релея (передается в виде строки «1.2.3.4»):
   o int message_set_ip(message_t *msg, const char *ip);
   o char *get_message_ip(message_t *msg);
Работа со списком реципиентов, использует внутреннее поле recipient_list_t, функции
полностью аналогичны rcptlist_*
   o void message_clean_rcpt(message_t *msg);
   o int message_add_rcpt(message_t *msg, const char *rcpt);
   o int get_message_nrcpt(message_t*);
   o char *get_message_rcpt(message_t *msg, int i);
   o int message_rcpts_iterate(message_t *msg, writer_func_t callback);
Работа с заголовками, использует внутреннее поле типа headers_list_t, функции
полностью аналогичны набору headers_*
   o headers_list_t * headers_clone(message_t *msg); - делает копию заголовков для
     сообщения msg
   o int message_headers_iterate(message_t *msg, header_iterator_t callback);
Заголовки разбираются автоматически при передаче в контейнер данных, для работы с
ними необходимо cоздавать их копию функцией headers_clone

Передача куска текста письма для разбора сохранения в контейнере:
   o int message_put_body(message_t *msg, char *data,size_t len);


Работа с телом сообщения, функции аналогичны контейнеру message_text_t:
   o long get_message_bodysize(message_t *msgt);
   o int get_message_bodychunks_count(message_t *msgt);
   o int get_message_bodychunk_len(message_t *msgt,size_t chunk);
   o char* get_message_bodychunk_ptr(message_t *msgt,size_t chunk);

Пример использования этого типа данных рассмотрен в отдельном документе (API-
Sample)

      6.2.6.       Канонизация E-mail адресов
Для работы с E-mail адресами предназначены следующие 4 функции:
   o int rfc822_unquote_one(char *dest,unsigned int size, char *from, char
     *defdomain);
   o int rfc822_quote_one(char *dest,unsigned int size, char *from, char *defdomain);
   o recipient_list_t *rfc822_unquote_list(recipient_list_t *from,char *defdomain);
   o recipient_list_t *rfc822_quote_list(recipient_list_t *from,char *defdomain);
Функции rfc822_unquote удаляют из адреса комментарии, лишние скобки и кавычки,
переводят ‘quoted’ вид в ‘unquoted’ (‘“user “@domain’ в ‘user @domain’)
Функции rfc822_quote делают обратную операцию (переводят адреса в quoted вид).
Функции *_one работают с одним адресом, помещая результат в буфер dest длиной size.
Функции *_list работают с контейнером recipient_list_t, генерируют новый контейнер с
результатами операций
Параметр defdomain во всех 4-х случаях используется как домен по-умолчанию для
адресов вида “user”


7. АЛЛОЦИРОВАНИЕ И ОСВОБОЖДЕНИЕ ПАМЯТИ
Для аллоцирования памяти используется свой менеджер памяти, ведущий список всех
аллоцированных сегментов. Параметры передаваемые пользователем (адреса, домены и
т.п.) в нужных случаях копируются.
Освобождение памяти производится в функции sts_close (вся память аллоцированная
функциями sts_* и функциями поддержки (make_changed_headers и glue_actions) и в
функции message_free() (вся память аллоцированная при создании структуры message_t и
работе с ней)
Функции rfc822_* аллоцируют память для работы и освобождают ее перед возвратом
управления.


8. ОБРАБОТКА ТАЙМАУТОВ В ФУНКЦИЯХ STS_*
Правильное установление таймаутов в сложных случаях – ключ к написанию
приложений, устойчивых к проблемам фильтра (возникающих, например при работе с
RBL и DNS-timeouts). Общие соображения на этот счет таковы:
   1. Процесс приема сообщений фильтром – быстрый, поэтому в процессе передачи
      текста в фильтр большой таймаут не нужен, нескольких секунд (или менее) вполне
      достаточно.
   2. Процесс обработки сообщения фильтром может занять несколько десятков
      секунд (при работе с большим количеством медленных DNS ), обработка одного
      длинного сообщения методами контентной фильтрации может занимать до 3-7
      секунд.
   Исходя из вышесказанного, можно предложить две схемы работы с таймаутами
      1. «Жесткая схема». Таймаут ставится один раз, до вызова sts_init. Таймаут должен
         быть достаточно большим, по меньшей мере 20-30 секунд.
      2. «Гибкая схема». Таймаут устанавливается коротким (единицы секунд), при этом
         каждый вызов sts_send_body производится с флагом STS_REINIT_TIMEOUT.
         После посылки spamtest_session_t->recomended_size байт сообщения (этот
         лимит выставляется фильтром достаточно большим) таймаут
         переустанавливатеся на достаточно большое число (15-25 секунд минимум)
         При этой схеме проблемы коммуникации с фильтром на ранних стадиях
         передачи письма будут детектированы через значительно меньшее время.