Описание 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 секунд минимум) При этой схеме проблемы коммуникации с фильтром на ранних стадиях передачи письма будут детектированы через значительно меньшее время.