From 185a61e8aa06e0f9f4e561947af43c86940babf1 Mon Sep 17 00:00:00 2001 From: zhuzhenjun Date: Mon, 18 Nov 2024 10:19:53 +0000 Subject: mail decoder refactoring --- decoders/mail/CMakeLists.txt | 14 + decoders/mail/mail.c | 514 +++++ decoders/mail/mail_email.c | 1692 +++++++++++++++++ decoders/mail/mail_email.h | 342 ++++ decoders/mail/mail_imap.c | 1966 ++++++++++++++++++++ decoders/mail/mail_imap.h | 123 ++ decoders/mail/mail_internal.h | 104 ++ decoders/mail/mail_mime.c | 707 +++++++ decoders/mail/mail_mime.h | 118 ++ decoders/mail/mail_pop3.c | 468 +++++ decoders/mail/mail_pop3.h | 99 + decoders/mail/mail_smtp.c | 503 +++++ decoders/mail/mail_smtp.h | 90 + decoders/mail/mail_util.c | 1453 +++++++++++++++ decoders/mail/mail_util.h | 77 + decoders/mail/version.map | 13 + include/stellar/mail.h | 96 + test/decoders/mail/CMakeLists.txt | 53 + test/decoders/mail/mail_test_main.cpp | 80 + test/decoders/mail/mail_test_module.cpp | 367 ++++ ...1-imap-mail-account-in-command-with-base64.pcap | Bin 0 -> 17486 bytes test/decoders/mail/pcap/02-imap-starttls.pcap | Bin 0 -> 555955 bytes test/decoders/mail/pcap/03-smtp-s2c-mail.pcap | Bin 0 -> 1618 bytes .../pcap/04-smtp-attachment-charset-unknown.pcap | Bin 0 -> 4013 bytes test/decoders/mail/pcap/05-smtp-curl-mails.pcap | Bin 0 -> 7456 bytes test/decoders/mail/pcap/06-smtp-starttls.pcap | Bin 0 -> 4220 bytes test/decoders/mail/pcap/07-smtp-auth-xoauth2.pcap | Bin 0 -> 4866 bytes .../mail/pcap/08-smtp-mime-header-rfc2047.pcap | Bin 0 -> 3063 bytes .../decoders/mail/pcap/09-pop3-c2s-55325-1100.pcap | Bin 0 -> 2767 bytes .../decoders/mail/pcap/10-pop3-s2c-1100-55325.pcap | Bin 0 -> 18034 bytes test/decoders/mail/pcap/11-pop3-c2s.pcap | Bin 0 -> 2767 bytes test/decoders/mail/pcap/12-pop3-s2c.pcap | Bin 0 -> 18034 bytes test/decoders/mail/pcap/13-pop3-mail.pcap | Bin 0 -> 14121 bytes test/decoders/mail/pcap/14-pop3-starttls.pcap | Bin 0 -> 9400 bytes test/decoders/mail/pcap/15-pop3-capa.pcap | Bin 0 -> 1656785 bytes test/decoders/mail/pcap/16-ftp-c2s-8569-8021.pcap | Bin 0 -> 1565 bytes test/decoders/mail/pcap/17-ftp-c2s-8569-8080.pcap | Bin 0 -> 1565 bytes test/decoders/mail/pcap/18-http-delete.pcap | Bin 0 -> 1642 bytes ...1-imap-mail-account-in-command-with-base64.json | 8 + 39 files changed, 8887 insertions(+) create mode 100644 decoders/mail/CMakeLists.txt create mode 100644 decoders/mail/mail.c create mode 100644 decoders/mail/mail_email.c create mode 100644 decoders/mail/mail_email.h create mode 100644 decoders/mail/mail_imap.c create mode 100644 decoders/mail/mail_imap.h create mode 100644 decoders/mail/mail_internal.h create mode 100644 decoders/mail/mail_mime.c create mode 100644 decoders/mail/mail_mime.h create mode 100644 decoders/mail/mail_pop3.c create mode 100644 decoders/mail/mail_pop3.h create mode 100644 decoders/mail/mail_smtp.c create mode 100644 decoders/mail/mail_smtp.h create mode 100644 decoders/mail/mail_util.c create mode 100644 decoders/mail/mail_util.h create mode 100644 decoders/mail/version.map create mode 100644 include/stellar/mail.h create mode 100644 test/decoders/mail/CMakeLists.txt create mode 100644 test/decoders/mail/mail_test_main.cpp create mode 100644 test/decoders/mail/mail_test_module.cpp create mode 100644 test/decoders/mail/pcap/01-imap-mail-account-in-command-with-base64.pcap create mode 100644 test/decoders/mail/pcap/02-imap-starttls.pcap create mode 100644 test/decoders/mail/pcap/03-smtp-s2c-mail.pcap create mode 100644 test/decoders/mail/pcap/04-smtp-attachment-charset-unknown.pcap create mode 100644 test/decoders/mail/pcap/05-smtp-curl-mails.pcap create mode 100644 test/decoders/mail/pcap/06-smtp-starttls.pcap create mode 100644 test/decoders/mail/pcap/07-smtp-auth-xoauth2.pcap create mode 100644 test/decoders/mail/pcap/08-smtp-mime-header-rfc2047.pcap create mode 100644 test/decoders/mail/pcap/09-pop3-c2s-55325-1100.pcap create mode 100644 test/decoders/mail/pcap/10-pop3-s2c-1100-55325.pcap create mode 100644 test/decoders/mail/pcap/11-pop3-c2s.pcap create mode 100644 test/decoders/mail/pcap/12-pop3-s2c.pcap create mode 100644 test/decoders/mail/pcap/13-pop3-mail.pcap create mode 100644 test/decoders/mail/pcap/14-pop3-starttls.pcap create mode 100644 test/decoders/mail/pcap/15-pop3-capa.pcap create mode 100644 test/decoders/mail/pcap/16-ftp-c2s-8569-8021.pcap create mode 100644 test/decoders/mail/pcap/17-ftp-c2s-8569-8080.pcap create mode 100644 test/decoders/mail/pcap/18-http-delete.pcap create mode 100644 test/decoders/mail/result/01-imap-mail-account-in-command-with-base64.json diff --git a/decoders/mail/CMakeLists.txt b/decoders/mail/CMakeLists.txt new file mode 100644 index 0000000..4e1a1f9 --- /dev/null +++ b/decoders/mail/CMakeLists.txt @@ -0,0 +1,14 @@ +set(DECODER_NAME mail_decoder) + +file(GLOB DECODER_SRC "${CMAKE_CURRENT_SOURCE_DIR}/${DECODER_NAME}*.c") + +add_library( + ${DECODER_NAME} + ${DECODER_SRC} +) + +set_target_properties( + ${DECODER_NAME} PROPERTIES + LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/version.map" +) + diff --git a/decoders/mail/mail.c b/decoders/mail/mail.c new file mode 100644 index 0000000..7516548 --- /dev/null +++ b/decoders/mail/mail.c @@ -0,0 +1,514 @@ +#include +#include +#include +#include +#include + +#include "stellar/packet.h" +#include "stellar/utils.h" +#include "stellar/mq.h" +#include "stellar/session.h" +#include "stellar/mail.h" + +#include "mail_internal.h" +#include "mail_parser.h" + +#define PORT_SMTP 25 +#define PORT_POP3 110 +#define PORT_IMAP 143 + +static int is_current_packet_c2s(struct session *sess) +{ + const struct layer *first_pkt_layer, *curr_pkt_layer; + + const struct packet *first_pkt, *curr_pkt; + + first_pkt = session_get_first_packet(sess, FLOW_TYPE_C2S); + curr_pkt = session_get_current_packet(sess); + + int i = 0; + int layer_count = packet_get_layer_count(first_pkt); + + for (i = 0; i < 2 /* l3/l4 */; i++) { + first_pkt_layer = packet_get_layer_by_idx(first_pkt, layer_count - 1 -i); + curr_pkt_layer = packet_get_layer_by_idx(curr_pkt, layer_count - 1 -i); + + if (first_pkt_layer->proto != curr_pkt_layer->proto) { + return 0; + } + switch (first_pkt_layer->proto) { + case LAYER_PROTO_TCP: + if (0 != memcmp(first_pkt_layer->hdr.tcp, curr_pkt_layer->hdr.tcp, sizeof(struct tcphdr))) { + return 0; + } + break; + case LAYER_PROTO_UDP: + if (0 != memcmp(first_pkt_layer->hdr.udp, curr_pkt_layer->hdr.udp, sizeof(struct udphdr))) { + return 0; + } + break; + case LAYER_PROTO_IPV4: + if (0 != memcmp(first_pkt_layer->hdr.ip4, curr_pkt_layer->hdr.ip4, sizeof(struct ip))) { + return 0; + } + break; + case LAYER_PROTO_IPV6: + if (0 != memcmp(first_pkt_layer->hdr.ip6, curr_pkt_layer->hdr.ip6, sizeof(struct ip6_hdr))) { + return 0; + } + break; + default: + return 0; + } + + } + + return 1; +} + +static int mail_subscribe_command(struct mail_env *mail_env, mail_command_callback_func *cb, void *arg) +{ + int topic; + struct mq_schema *schema; + + schema = module_manager_get_mq_schema(mail_env->mod_mgr_ref); + if (schema == NULL) { + return -1; + } + + topic = mq_schema_get_topic_id(schema, MAIL_COMMAND_TOPIC_NAME); + if (topic < 0) { + return -1; + } + + return mq_schema_subscribe(schema, topic, (on_msg_cb_func *)(void *)cb, arg); +} + +static int mail_subscribe_header(struct mail_env *mail_env, mail_header_callback_func *cb, void *arg) +{ + int topic; + struct mq_schema *schema; + + schema = module_manager_get_mq_schema(mail_env->mod_mgr_ref); + if (schema == NULL) { + return -1; + } + + topic = mq_schema_get_topic_id(schema, MAIL_HEADER_TOPIC_NAME); + if (topic < 0) { + return -1; + } + + return mq_schema_subscribe(schema, topic, (on_msg_cb_func *)(void *)cb, arg); +} + +static int mail_subscribe_body(struct mail_env *mail_env, mail_body_callback_func *cb, void *arg) +{ + int topic; + struct mq_schema *schema; + + schema = module_manager_get_mq_schema(mail_env->mod_mgr_ref); + if (schema == NULL) { + return -1; + } + + topic = mq_schema_get_topic_id(schema, MAIL_BODY_TOPIC_NAME); + if (topic < 0) { + return -1; + } + + return mq_schema_subscribe(schema, topic, (on_msg_cb_func *)(void *)cb, arg); +} + +static int mail_subscribe_attachment(struct mail_env *mail_env, mail_attachment_callback_func *cb, void *arg) +{ + int topic; + struct mq_schema *schema; + + schema = module_manager_get_mq_schema(mail_env->mod_mgr_ref); + if (schema == NULL) { + return -1; + } + + topic = mq_schema_get_topic_id(schema, MAIL_ATTACHMENT_TOPIC_NAME); + if (topic < 0) { + return -1; + } + + return mq_schema_subscribe(schema, topic, (on_msg_cb_func *)(void *)cb, arg); +} + +static int mail_subscribe_eml(struct mail_env *mail_env, mail_eml_callback_func *cb, void *arg) +{ + int topic; + struct mq_schema *schema; + + schema = module_manager_get_mq_schema(mail_env->mod_mgr_ref); + if (schema == NULL) { + return -1; + } + + topic = mq_schema_get_topic_id(schema, MAIL_EML_TOPIC_NAME); + if (topic < 0) { + return -1; + } + + return mq_schema_subscribe(schema, topic, (on_msg_cb_func *)(void *)cb, arg); +} + +static void mail_dispatch(int topic, void *msg, on_msg_cb_func *msg_cb, void *arg, void *dispatch_arg) +{ + struct mail_message *mail_msg; + struct mail_env *mail_env; + + mail_msg = (struct mail_message *)msg; + mail_env = (struct mail_env *)dispatch_arg; + + if (topic == mail_env->command_topic_id) { + mail_command_callback_func *command_cb = (mail_command_callback_func *)(void *)msg_cb; + command_cb(mail_msg->sess_ref, mail_msg->mail_protocol, mail_msg->mail_seq, + mail_msg->command, mail_msg->command_param, mail_msg->command_param_len, + mail_msg->command_line, mail_msg->command_line_len, + arg); + return; + } + + if (topic == mail_env->header_topic_id) { + mail_header_callback_func *header_cb = (mail_header_callback_func *)(void *)msg_cb; + header_cb(mail_msg->sess_ref, mail_msg->mail_protocol, mail_msg->mail_seq, &mail_msg->header, arg); + return; + } + + if (topic == mail_env->body_topic_id) { + mail_body_callback_func *body_cb = (mail_body_callback_func *)(void *)msg_cb; + body_cb(mail_msg->sess_ref, mail_msg->mail_protocol, mail_msg->mail_seq, + mail_msg->body, mail_msg->body_len, + mail_msg->body_offset, mail_msg->is_body_finished, + arg); + return; + } + + if (topic == mail_env->attachment_topic_id) { + mail_attachment_callback_func *attachment_cb = (mail_attachment_callback_func *)(void *)msg_cb; + attachment_cb(mail_msg->sess_ref, mail_msg->mail_protocol, mail_msg->mail_seq, + mail_msg->attachment_name, mail_msg->attachment_name_len, + mail_msg->attachment, mail_msg->attachment_len, + mail_msg->attachment_offset, mail_msg->is_attachment_finished, + arg); + return; + } + + if (topic == mail_env->eml_topic_id) { + mail_eml_callback_func *eml_cb = (mail_eml_callback_func *)(void *)msg_cb; + eml_cb(mail_msg->sess_ref, mail_msg->mail_protocol, + mail_msg->eml, mail_msg->eml_len, + mail_msg->eml_offset, mail_msg->is_eml_finished, + arg); + return; + } +} + +int mail_subscribe(struct mail_env *mail_env, + mail_command_callback_func command_cb, + mail_header_callback_func *header_cb, + mail_body_callback_func *body_cb, + mail_attachment_callback_func *attachment_cb, + mail_eml_callback_func *eml_cb, + void *arg) +{ + int ret; + + ret = mail_subscribe_command(mail_env, command_cb, arg); + if (ret < 0) { + return ret; + } + + ret = mail_subscribe_header(mail_env, header_cb, arg); + if (ret < 0) { + return ret; + } + + ret = mail_subscribe_body(mail_env, body_cb, arg); + if (ret < 0) { + return ret; + } + + ret = mail_subscribe_attachment(mail_env, attachment_cb, arg); + if (ret < 0) { + return ret; + } + + ret = mail_subscribe_eml(mail_env, eml_cb, arg); + if (ret < 0) { + return ret; + } + + return 0; +} + +static void mail_session_ctx_free(struct mail_session_ctx *session_ctx) +{ + switch (session_ctx->protocol) { + case MAIL_PROTOCOL_SMTP: + if (session_ctx->smtp_parser) { + smtp_parser_free(session_ctx->smtp_parser); + } + break; + case MAIL_PROTOCOL_POP3: + break; + case MAIL_PROTOCOL_IMAP: + break; + default: + break; + } + free(session_ctx); +} + +static struct mail_session_ctx *mail_session_ctx_new(void) +{ + struct mail_session_ctx *session_ctx; + session_ctx = (struct mail_session_ctx *)calloc(1, sizeof(struct mail_session_ctx)); + return session_ctx; +} + +static int mail_info_process(struct mail_env *mail_env, struct mail_session_ctx *session_ctx, const char *payload, size_t payload_len, int is_c2s) +{ + int ret; + + ret = smtp_parser_get_command(session_ctx->smtp_parser); + if (ret == 0) { + mail_publish_command(mail_env, sess, mail_protocol, mail_seq, cmd, cmd_param, cmd_param_len, cmd_line, cmd_line_len); + } + + struct mail_info *mail; + mail = smtp_parser_get_mailinfo(session_ctx->smtp_parser); + if (mail) { + mail_info_get_header(); + mail_info_get_body(); + mail_info_get_attachment(); + } +} + +static int mail_imap_process(struct mail_env *mail_env, struct mail_session_ctx *session_ctx, const char *payload, size_t payload_len, int is_c2s) +{ + int ret; + + ret = smtp_parser_process(session_ctx->smtp_parser, payload, payload_len , is_c2s); + if (ret != 0) { + return; + } + + ret = smtp_parser_get_command(session_ctx->smtp_parser); + if (ret == 0) { + mail_publish_command(mail_env, sess, mail_protocol, mail_seq, cmd, cmd_param, cmd_param_len, cmd_line, cmd_line_len); + } + + struct mail_info *mail; + mail = smtp_parser_get_mailinfo(session_ctx->smtp_parser); + if (mail) { + mail_info_get_header(); + mail_info_get_body(); + mail_info_get_attachment(); + } +} + +static int mail_pop3_process(struct mail_env *mail_env, struct mail_session_ctx *session_ctx, const char *payload, size_t payload_len, int is_c2s) +{ + int ret; + + ret = smtp_parser_process(session_ctx->smtp_parser, payload, payload_len , is_c2s); + if (ret != 0) { + return; + } + + ret = smtp_parser_get_command(session_ctx->smtp_parser); + if (ret == 0) { + mail_publish_command(mail_env, sess, mail_protocol, mail_seq, cmd, cmd_param, cmd_param_len, cmd_line, cmd_line_len); + } + + struct mail_info *mail; + mail = smtp_parser_get_mailinfo(session_ctx->smtp_parser); + if (mail) { + mail_info_get_header(); + mail_info_get_body(); + mail_info_get_attachment(); + } +} + +static int mail_smtp_process(struct mail_env *mail_env, struct mail_session_ctx *session_ctx, const char *payload, size_t payload_len, int is_c2s) +{ + int ret; + + if (session_ctx->smtp_parser == NULL) { + session_ctx->smtp_parser = smtp_parser_new(mail_env); + } + + ret = smtp_parser_process(session_ctx->smtp_parser, payload, payload_len , is_c2s); + if (ret != 0) { + return -1; + } + + return 0; +} + +static void mail_on_tcp_payload(struct session *sess, enum session_state state, const char *payload, uint32_t payload_len, void *arg) +{ + (void)state; + int ret; + int is_packet_c2s; + struct mail_session_ctx *session_ctx; + struct mail_env *mail_env = (struct mail_env *)arg; + + if (payload == NULL || payload_len == 0) { + return; + } + + session_ctx = (struct mail_session_ctx *)session_get_exdata(sess, mail_env->exdata_id); + if (session_ctx == NULL) { + session_ctx = mail_session_ctx_new(); + session_ctx->mail_env_ref = mail_env; + session_ctx->sess_ref = sess; + session_ctx->protocol = MAIL_PROTOCOL_MAX; + session_ctx->is_mail_protocol = 1; + session_set_exdata(sess, mail_env->exdata_id, session_ctx); + + if (smtp_identify(payload, payload_len, is_packet_c2s)) { + session_ctx->protocol = MAIL_PROTOCOL_SMTP; + } + //if (pop3_identify((char *)payload, payload_len, is_packet_c2s)) { + // session_ctx->protocol = MAIL_PROTOCOL_POP3; + //} + + //if (imap_identify((char *)payload, payload_len, is_packet_c2s)) { + // session_ctx->protocol = MAIL_PROTOCOL_IMAP; + //} + } + + is_packet_c2s = is_current_packet_c2s(sess); + + switch (session_ctx->protocol) { + case MAIL_PROTOCOL_SMTP: + mail_smtp_process(mail_env, session_ctx, payload, payload_len, is_packet_c2s); + break; + case MAIL_PROTOCOL_POP3: + //ret = pop3_parser_process(session_ctx->smtp, payload, payload_len , is_packet_c2s); + //if (ret != 0) { + // return; + //} + break; + case MAIL_PROTOCOL_IMAP: + //ret = imap_parser_process(session_ctx->smtp, payload, payload_len , is_packet_c2s); + //if (ret != 0) { + // return; + //} + break; + default: + break; + } +} + +void mail_on_exdata_free(int idx, void *ex_ptr, void *arg) +{ + (void)(idx); + (void)(arg); + struct mail_session_ctx *session_ctx = (struct mail_session_ctx *)ex_ptr; + + if (session_ctx) { + mail_session_ctx_free(session_ctx); + } +} + +static void mail_on_message_free(void *msg, void *arg) +{ + (void)(arg); + struct mail_message *mail_msg; + + mail_msg = (struct mail_message *)msg; + if (mail_msg) { + free(mail_msg); + } +} + +void mail_exit(struct module_manager *mod_mgr, struct module *mod) +{ + (void)(mod_mgr); + struct mail_env *mail_env; + struct mq_schema *schema; + + if (mod) { + mail_env = (struct mail_env *)module_get_ctx(mod); + if (mail_env) { + schema = module_manager_get_mq_schema(mod_mgr); + mq_schema_destroy_topic(schema, mail_env->command_topic_id); + mq_schema_destroy_topic(schema, mail_env->header_topic_id); + mq_schema_destroy_topic(schema, mail_env->body_topic_id); + mq_schema_destroy_topic(schema, mail_env->attachment_topic_id); + mq_schema_destroy_topic(schema, mail_env->eml_topic_id); + free(mail_env); + } + + module_free(mod); + } +} + +struct module* mail_init(struct module_manager *mod_mgr) +{ + int ret; + struct module *mod; + struct mq_schema *schema; + struct session_manager *sess_mgr; + struct mail_env *mail_env; + + mail_env = (struct mail_env *)calloc(1, sizeof(struct mail_env)); + mail_env->mod_mgr_ref = mod_mgr; + mod = module_new(MAIL_MODULE_NAME, mail_env); + sess_mgr = module_to_session_manager(module_manager_get_module(mod_mgr, SESSION_MANAGER_MODULE_NAME)); + schema = module_manager_get_mq_schema(mod_mgr); + + if (sess_mgr == NULL || schema == NULL) { + goto exit; + } + + ret = session_manager_subscribe_tcp_stream(sess_mgr, mail_on_tcp_payload, mail_env); + if (ret < 0) { + goto exit; + } + + mail_env->exdata_id = session_manager_new_session_exdata_index(sess_mgr, MAIL_EXDATA_NAME, mail_on_exdata_free, NULL); + if (mail_env->exdata_id < 0) { + goto exit; + } + + mail_env->command_topic_id = mq_schema_create_topic(schema, MAIL_COMMAND_TOPIC_NAME, mail_dispatch, mail_env, mail_on_message_free, NULL); + if (mail_env->command_topic_id < 0) { + goto exit; + } + mail_env->header_topic_id = mq_schema_create_topic(schema, MAIL_HEADER_TOPIC_NAME, mail_dispatch, mail_env, mail_on_message_free, NULL); + if (mail_env->header_topic_id < 0) { + goto exit; + } + mail_env->body_topic_id = mq_schema_create_topic(schema, MAIL_BODY_TOPIC_NAME, mail_dispatch, mail_env, mail_on_message_free, NULL); + if (mail_env->body_topic_id < 0) { + goto exit; + } + mail_env->attachment_topic_id = mq_schema_create_topic(schema, MAIL_ATTACHMENT_TOPIC_NAME, mail_dispatch, mail_env, mail_on_message_free, NULL); + if (mail_env->attachment_topic_id < 0) { + goto exit; + } + mail_env->eml_topic_id = mq_schema_create_topic(schema, MAIL_EML_TOPIC_NAME, mail_dispatch, mail_env, mail_on_message_free, NULL); + if (mail_env->eml_topic_id < 0) { + goto exit; + } + + return mod; +exit: + mail_exit(mod_mgr, mod); + return NULL; +} + +struct mail_env *module_to_mail_env(struct module *mod) +{ + assert(mod); + assert(strcmp(module_get_name(mod), MAIL_MODULE_NAME) == 0); + return module_get_ctx(mod); +} diff --git a/decoders/mail/mail_email.c b/decoders/mail/mail_email.c new file mode 100644 index 0000000..55a3e48 --- /dev/null +++ b/decoders/mail/mail_email.c @@ -0,0 +1,1692 @@ +#include +#include +#include +#include +#include +#include + +#include "mail_email.h" + +/********************************************************************* +函数名称:buf_elem_check_size +功能简介: +输入参数: +输出参数:无 +返回值:-1 出错 0 有足够空间;1-重新分配空间 +*********************************************************************/ +int buf_cache_check_size(stBufCache *pdstBuf, int wantedlen) +{ + int ret=0, needlen, block_num=0; + + if(pdstBuf->max_len > wantedlen + pdstBuf->len) + return 0; + + needlen = wantedlen + pdstBuf->len - pdstBuf->max_len; + block_num = needlen/REALLOC_BLOCK_SIZE; + if(needlen%REALLOC_BLOCK_SIZE) + block_num += 1; + + pdstBuf->max_len += block_num*REALLOC_BLOCK_SIZE; + + if(pdstBuf->max_len > MAX_MALLOC_SIZE) + { + pdstBuf->max_len -= block_num*REALLOC_BLOCK_SIZE; + return -1; + } + + if(pdstBuf->buf == NULL) + { + pdstBuf->buf = (char *)malloc(pdstBuf->max_len); + } + else + { + pdstBuf->buf = (char *)realloc(pdstBuf->buf, pdstBuf->max_len); + ret = 1; + } + + return ret; +} + +/********************************************************************* +函数名称:buf_elem_check_size +功能简介: +输入参数: +输出参数:无 +返回值:-1 出错 0 正常 +*********************************************************************/ +int buf_elem_check_size(stMailElem *pdstBuf, int wantedlen) +{ + int needlen, block_num=0; + + if(pdstBuf->max_len > wantedlen + pdstBuf->buflen) + return 0; + + needlen = wantedlen + pdstBuf->buflen + 1 - pdstBuf->max_len; // +1 for '\0' + block_num = needlen/BUF_ELEM_BLOCK; + if(needlen%BUF_ELEM_BLOCK) + block_num += 1; + + pdstBuf->max_len += block_num*BUF_ELEM_BLOCK; + + if(pdstBuf->buf == NULL) + { + pdstBuf->buf = (char *)malloc(pdstBuf->max_len); + } + else + { + pdstBuf->buf = (char *)realloc(pdstBuf->buf, pdstBuf->max_len); + } + + return 0; +} + +/********************************************************************* +函数名称:process_originaldata +功能简介:将data拷贝到originaldata的buffer中 +输入参数:mailinfo:邮件关键信息 + data:本次要拷贝的原始数据 + datalen:本次要拷贝的原始数据的长度 +输出参数: +返回值:-1 出错 0 正常 +*********************************************************************/ +int save_original_data(stBufCache *pbuf, const char *data, int datalen) +{ + if(buf_cache_check_size(pbuf, datalen) < 0) + { + return -1; + } + + memcpy((char *)pbuf->buf + pbuf->len, data, datalen); + pbuf->len += datalen; + + return 0; +} + +/********************************************************************* +函数名称:mail_clear_mailinfo +功能简介:清空邮件处理数据结构(tdMailInfo: session_info->app_info),释放缓冲区 +参数:mailpme: +返回值: 无 +*********************************************************************/ +static void mail_clear_mailinfo(tdMailInfo *plug_mailinfo) +{ + if(plug_mailinfo->pMailInfo->username->buf) + free(plug_mailinfo->pMailInfo->username->buf); + if(plug_mailinfo->pMailInfo->password->buf) + free(plug_mailinfo->pMailInfo->password->buf); + if(plug_mailinfo->pMailInfo->sendaddr->buf) + free(plug_mailinfo->pMailInfo->sendaddr->buf); + if(plug_mailinfo->pMailInfo->recvaddr->buf) + free(plug_mailinfo->pMailInfo->recvaddr->buf); + if(plug_mailinfo->pMailInfo->subject->buf) + free(plug_mailinfo->pMailInfo->subject->buf); + if(plug_mailinfo->pMailInfo->date->buf) + free(plug_mailinfo->pMailInfo->date->buf); + + if(plug_mailinfo->elem != NULL) + free(plug_mailinfo->elem); +} + + +/********************************************************************* +函数名称:clear_mailpme +功能简介:清空邮件处理数据结构,释放缓冲区 +参数:mailpme: +返回值: 无 +*********************************************************************/ +void mail_clear_mailpme(stMailPme * mailpme) +{ + if(mailpme->is_called) + { + mailpme->plug_mailinfo.buf = (char *)mailpme->pOriginalData.buf; + mailpme->plug_mailinfo.buflen = mailpme->pOriginalData.len; + } + mail_clear_mailinfo(&mailpme->plug_mailinfo); + clear_mime_info(mailpme->mimeinfo); + + if(mailpme->pDataLineBuf.buf) + free(mailpme->pDataLineBuf.buf); + if(mailpme->pOriginalData.buf) + free(mailpme->pOriginalData.buf); + if(mailpme->pMimeDecodeBuf.buf) + free(mailpme->pMimeDecodeBuf.buf); + if(mailpme->pBizRegion.buf) + free(mailpme->pBizRegion.buf); + free(mailpme); + +} + +/********************************************************************* +函数名称:init_mailinfo +功能简介:初始化mailinfo结构体; +参数:mailinfo:stMailInfoOld 结构体; +返回值:succ:0 error:<0; +*********************************************************************/ +static int mail_init_mailinfo(stMailPme *mailpme) +{ + tdMailInfo *pmailinfo = &mailpme->plug_mailinfo; + + memset(pmailinfo, 0, sizeof(tdMailInfo)); + + if(mailpme->iProtoType == MAIL_SERVICE_SMTP) + pmailinfo->protocol = SMTP_PROTOCOL; + else if(mailpme->iProtoType == MAIL_SERVICE_POP3) + pmailinfo->protocol = POP3_PROTOCOL; + else + pmailinfo->protocol = IMAP_PROTOCOL; + + pmailinfo->pMailInfo = &mailpme->pMailinfo; + pmailinfo->pMailInfo->username = &mailpme->keyinfo.username; + pmailinfo->pMailInfo->password = &mailpme->keyinfo.password; + pmailinfo->pMailInfo->sendaddr= &mailpme->keyinfo.sendaddr; + pmailinfo->pMailInfo->recvaddr = &mailpme->keyinfo.recvaddr; + pmailinfo->pMailInfo->subject = &mailpme->keyinfo.subject; + pmailinfo->pMailInfo->date = &mailpme->keyinfo.date; + + pmailinfo->elem = (stFieldElem *)malloc(sizeof(stFieldElem)*MAIL_ELEM_NUM); + mailpme->max_elem_num = MAIL_ELEM_NUM; + + return 0; +} + +/********************************************************************* +函数名称:init_mailpme +功能简介:初始化邮件处理数据结构 +输入参数:mailpme: +输出参数:无 +返回值: -1:出错, 0:正常 +*********************************************************************/ +char mail_init_mailpme(stMailPme** mailpme_pointer,char ServiceType) +{ + stMailPme* mailpme = NULL; + + mailpme = (stMailPme *)malloc(sizeof(stMailPme)); + memset(mailpme, 0, sizeof(stMailPme)); //MUST DO IT! + mailpme->iProtoType = ServiceType; + + mail_init_mailinfo(mailpme); + + init_mime_info(&mailpme->mimeinfo); + + //other info + mailpme->EmlAnalyseState=MAIL_STAT_INIT; + mailpme->MailInfoState=SMTP_NO_USER; + mailpme->pre_dir = -1; + mailpme->pending_state = 1; + + (*mailpme_pointer)=mailpme; + + return 0; +} + +/********************************************************************* +函数名称:init_mailinfo +功能简介:初始化mailinfo结构体; +参数:mailinfo:stMailInfoOld 结构体; +返回值:succ:0 error:<0; +*********************************************************************/ +static void mail_reset_mailinfo(tdMailInfo *pmailinfo) +{ + /*if(pmailinfo->pMailInfo->username->buf != NULL) //同一个链接中最初有用户名密码,后续的会话可以继续获得它 + { + pmailinfo->pMailInfo->username->buflen = 0; + pmailinfo->pMailInfo->username->buf[0] = '\0'; + } + if(pmailinfo->pMailInfo->password->buf != NULL) + { + pmailinfo->pMailInfo->password->buflen = 0; + pmailinfo->pMailInfo->password->buf[0] = '\0'; + }*/ + if(pmailinfo->pMailInfo->sendaddr->buf != NULL) + { + pmailinfo->pMailInfo->sendaddr->buflen = 0; + pmailinfo->pMailInfo->sendaddr->buf[0] = '\0'; + } + if(pmailinfo->pMailInfo->recvaddr->buf != NULL) + { + pmailinfo->pMailInfo->recvaddr->buflen = 0; + pmailinfo->pMailInfo->recvaddr->buf[0] = '\0'; + } + if(pmailinfo->pMailInfo->subject->buf != NULL) + { + pmailinfo->pMailInfo->subject->buflen = 0; + pmailinfo->pMailInfo->subject->buf[0] = '\0'; + } + if(pmailinfo->pMailInfo->date->buf != NULL) + { + pmailinfo->pMailInfo->date->buflen = 0; + pmailinfo->pMailInfo->date->buf[0] = '\0'; + } + + pmailinfo->trans_enc = MAIL_TRANS_ENC_UNKNOWN; + pmailinfo->session_seq++; + pmailinfo->buflen = 0; + pmailinfo->buf = NULL; + pmailinfo->current_charset = NULL; + + if(pmailinfo->elem_num > 0) + { + pmailinfo->elem_num = 0; + } +} + + +/********************************************************************* +函数名称:mail_reset_mailpme_noclose +功能简介:为新会话重置邮件处理数据结构;不调用业务层SESSION_CLOSE +输入参数:mailpme: +输出参数:无 +返回值: 无 +*********************************************************************/ +char mail_reset_mailpme_noclose(stMailPme * mailpme) +{ + mailpme->pDataLineBuf.len = 0; + mailpme->pOriginalData.len = 0; + mailpme->pMimeDecodeBuf.len = 0; + mailpme->pBizRegion.len = 0; + + mail_reset_mailinfo(&mailpme->plug_mailinfo); + reset_mime_info(mailpme->mimeinfo, 0); + + mailpme->subject_charset[0] = '\0'; + mailpme->EmlAnalyseState = MAIL_STAT_INIT; + mailpme->MailInfoState = SMTP_NO_USER; + //mailpme->s_total_loss = 0; + + mailpme->maybe_end = 0; //IMAP协议,上一次MAYBE_END,下一个是FETCH或结束命令,需要清空该状态 + mailpme->is_called = 0; + mailpme->pending_state = 1; + return 0; +} + +void mail_close_business(stMailPme * mailpme) +{ + if(mailpme->is_called) + { + mailpme->is_called = 0; + } +} + +/********************************************************************* +函数名称:reset_mailpme +功能简介:为新会话重置邮件处理数据结构;先调用业务层SESSION_CLOSE +输入参数:mailpme: +输出参数:无 +返回值: 无 +*********************************************************************/ +char mail_reset_mailpme(stMailPme * mailpme) +{ + return mail_reset_mailpme_noclose(mailpme); +} + +/********************************************************************* +函数名称:process_payload_r +功能简介:处理tcpdetail结构体中data数据,根据pDataLineBuf及payload来获取一完整行; + 如果从payload中取得\r后,\r之后还有\n\t或\n空格,则更新payload,调用者 + 接着进行循环,重复上述操作。 +参数:mailpme: +返回值: + MAIL_INPUT_END: 输入结束了 + MAIL_FOLD_LINE_END: 找到完整行了 + MAIL_FOLD_LINE_START: 需要进一步循环 + MAIL_GETLINE_ERR:出错 +*********************************************************************/ +static int process_payload_r(char **p_payload,int *p_payload_len,char **line_start,int *plinelen,stMailPme * mailpme) +{ + int len; + char *poss_n = NULL; + char *payload = *p_payload; + int payload_len = *p_payload_len; + + poss_n = (char *)memchr(payload, '\n', payload_len); + if(poss_n != NULL && poss_n >= payload + payload_len) + { + poss_n = NULL; + } + + if(poss_n == NULL || poss_n + 1 >= payload + payload_len) //不足以断定是折叠行,继续缓存 + { + while(mailpme->pDataLineBuf.len >0 && (*((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len-1)=='\r' || *((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len-1)=='\n')) + mailpme->pDataLineBuf.len--; + + if(buf_cache_check_size(&mailpme->pDataLineBuf, payload_len)<0) + { + return MAIL_GETLINE_ERR; + } + memcpy((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len, payload, payload_len); + mailpme->pDataLineBuf.len += payload_len; + return MAIL_INPUT_END; + } + else if(*(poss_n+1) != '\t' && *(poss_n+1) != ' ') //a fold line + { + len = poss_n - payload + 1; //包含换行符长度 + mailpme->iDataPointer += len; + + if(mailpme->pDataLineBuf.len > 0) + { + while(mailpme->pDataLineBuf.len >0 && (*((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len-1)=='\r' || *((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len-1)=='\n')) + mailpme->pDataLineBuf.len--; + + if(buf_cache_check_size(&mailpme->pDataLineBuf, len)<0) + { + return MAIL_GETLINE_ERR; + } + + memcpy((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len, payload, len); + mailpme->pDataLineBuf.len += len; + + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + } + else + { + *line_start = payload; + *plinelen = len; + } + return MAIL_FOLD_LINE_END; + } + else //未找到换行符或是折叠行,缓存之 + { + len = poss_n - payload + 1; + + while(mailpme->pDataLineBuf.len>0 && (*((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len-1)=='\r' || *((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len-1)=='\n')) + mailpme->pDataLineBuf.len--; + + if(buf_cache_check_size(&mailpme->pDataLineBuf, len)<0) + { + return MAIL_GETLINE_ERR; + } + + memcpy((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len, payload, len); + mailpme->pDataLineBuf.len += len; + payload += len; + payload_len -= len; + mailpme->iDataPointer += len; + + *p_payload = payload; + *p_payload_len = payload_len; + return MAIL_FOLD_LINE_START; + } +} + +/********************************************************************* +函数名称:GetMailFoldLine +功能简介:从之前缓冲区和当前待处理数据中找到完整行, + 如果能够找到完整行,则提取完整行内容,并且去除特殊符号 + 如果不能找到完整行,则当前数据拷贝到缓冲区中待下次处理 +输入参数:payload:当前待处理数据 + payload_len:当前待处理数据的长度 + mailpme:上下文信息,其中pDataLineBuf为缓冲区 + thread_seq:线程号,主要为了dictator系列参数使用 +输出参数:line_start:目的地址指针 + plinelen :目标长度 +返回值:是否处理完毕 +*********************************************************************/ +static int mail_get_fold_line(char *payload,int payload_len,char **line_start,int *plinelen, stMailPme *mailpme) +{ + char *posd_n = NULL; + int ret, flag=0; + + if(payload_len == 0) + return MAIL_INPUT_END; + + while(payload_len > 0) + { + if(mailpme->pDataLineBuf.len > 0) + { + posd_n = (char *)memrchr(mailpme->pDataLineBuf.buf, '\n', mailpme->pDataLineBuf.len); + if(posd_n != NULL && posd_n >= (char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len) + { + posd_n = NULL; + } + } + else + posd_n = NULL; + + if(posd_n != NULL) + { + if(posd_n+1 < (char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len && *(posd_n+1)=='\t') + { + mailpme->pDataLineBuf.len -= 1;//\t + ret = process_payload_r(&payload, &payload_len, line_start, plinelen, mailpme); + if(ret == MAIL_FOLD_LINE_START) + continue; + else + return ret; + } + else if(*payload != '\t' && *payload != ' ') + { + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + return MAIL_FOLD_LINE_END; + } + else + { + while(payload_len>0 && (*payload == '\t' || *payload == ' ')) + { + payload++; + payload_len--; + mailpme->iDataPointer++; + flag = 1; + } + if(payload_len == 0) + { + if(flag) + { + if(buf_cache_check_size(&mailpme->pDataLineBuf, 1)<0) + return MAIL_GETLINE_ERR; + *((char*)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len) = '\t'; + mailpme->pDataLineBuf.len += 1; + } + return MAIL_INPUT_END; + } + flag = 0; + + ret = process_payload_r(&payload, &payload_len, line_start, plinelen, mailpme); + if(ret == MAIL_FOLD_LINE_START) + continue; + else + return ret; + } + } + else + { + if(mailpme->pDataLineBuf.len == 0) + { + if(*payload=='\n') //一个\n空行 + { + mailpme->iDataPointer += 1; + *line_start = payload; + *plinelen = 1; + return MAIL_FOLD_LINE_END; + } + else if(payload_len>=2 && *payload=='\r' && *(payload+1) == '\n') //一个\r\n空行 + { + mailpme->iDataPointer += 2; + *line_start = payload; + *plinelen = 2; + return MAIL_FOLD_LINE_END; + } + } + + ret = process_payload_r(&payload, &payload_len, line_start, plinelen, mailpme); + if(ret == MAIL_FOLD_LINE_START) + continue; + else + return ret; + } + } + + return MAIL_INPUT_END; +} + + +/********************************************************************* +函数名称:GetEmlLineCore +功能简介:非折叠行时,从给定的缓冲区内,提取一行数据,并返回; +输入参数:wantedlen:需要的空间大小;rn:输出是否带换行符 +输出参数:line_start:目的地址指针 + plinelen :目标长度 +返回值:-1 出错 0 正常 +*********************************************************************/ +int mail_get_one_tcpline(char *payload,int payload_len,char **line_start,int *plinelen,stMailPme * mailpme, int rn) +{ + char *pos_n = NULL; + int datalen; + + pos_n = (char *)memchr(payload, '\n', payload_len); + if(pos_n == NULL) //直接缓存,不管大小?这一行还没传输完; + { + if(buf_cache_check_size(&mailpme->pDataLineBuf, payload_len)<0) + { + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + return MAIL_LINE_END; + } + + memcpy((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len, payload, payload_len); + mailpme->pDataLineBuf.len += payload_len; + mailpme->iDataPointer += payload_len; + return MAIL_INPUT_END; + } + else //找到了一个完整行 + { + if(rn) + { + datalen = pos_n - payload + 1; + mailpme->iDataPointer += datalen; + } + else + { + datalen = pos_n - payload; + mailpme->iDataPointer += datalen + 1; //include \n + } + + //上一个包缓冲了部分数据,则首先进行拼接 + if(mailpme->pDataLineBuf.len > 0) + { + if(buf_cache_check_size(&mailpme->pDataLineBuf, datalen)<0) + { + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + mailpme->iDataPointer -= rn?datalen:(datalen+1); + return MAIL_LINE_END; + } + memcpy((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len, payload, datalen); + mailpme->pDataLineBuf.len += datalen; + + if(rn==0) + { + pos_n = (char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len - 1; + if(*pos_n == '\r') //100% properly + mailpme->pDataLineBuf.len -= 1; + } + + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + return MAIL_LINE_END; + } + + if(rn==0 && datalen > 0) + { + if(*(payload+datalen-1) == '\r') + datalen = datalen - 1; + } + *line_start = payload; + *plinelen = datalen; + return MAIL_LINE_END; + } +} + +/********************************************************************* +函数名称:mail_get_complete_region +功能简介:非折叠行时,从给定的缓冲区内,提取最大限度的同类型数据, + 直到遇到特殊字符或包结束;考虑到了IMAP协议')'之前没有换行的情况 +输入参数:wantedlen:需要的空间大小; +输出参数:line_start:目的地址指针 + plinelen :目标长度 +返回值:获取行状态 +*********************************************************************/ +static int mail_get_complete_region(char *payload,int payload_len,char **line_start,int *plinelen,stMailPme * mailpme) +{ + char *pos_n = NULL; + char *srcend = payload + payload_len, *pc=payload; + int datalen; + + //找截止符: \r\n#, where # is '\r' or '-' + while(pc < srcend && (pos_n = (char *)memchr(pc, '\n', srcend-pc)) != NULL) + { + mailpme->iDataPointer += pos_n - pc + 1; + pc = pos_n + 1; + datalen = pc-payload; + + if(mailpme->pDataLineBuf.len>0)//开头是特殊字符,或者上一次残留数据无换行 + { + if(buf_cache_check_size(&mailpme->pDataLineBuf, datalen)<0) + { + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + mailpme->iDataPointer -= datalen; + return MAIL_LINE_END; + } + memcpy((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len, payload, datalen); + mailpme->pDataLineBuf.len += datalen; + + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + return MAIL_LINE_END; + } + //本次BODY和下个BOUNDARY之间可能没有空行,所以要比较'-'; + //结束符"."和前面的数据如果在一个包里,需要从BODY中分离出;POP3常见; + //符号')'在IMAP中作为结束符 + else if(pc < srcend && (*pc=='-' || (*pc=='.' && mailpme->plug_mailinfo.protocol!=IMAP_PROTOCOL) || (*pc==')' && mailpme->plug_mailinfo.protocol==IMAP_PROTOCOL))) //--BB\r\n)\r\n + { + *line_start = payload; + *plinelen = datalen; + return MAIL_LINE_END; + } + else if((payload_len>=2 && *payload=='-' && *(payload+1)=='-') || ((pc-payload<=3) && (*payload=='.' || *payload==')'))) + { + *line_start = payload; + *plinelen = datalen; + return MAIL_LINE_END; + } + else if(pc-payload>=3 && *(pc-3)==')' && mailpme->plug_mailinfo.protocol==IMAP_PROTOCOL) //')'之前没有换行;与上一个if语句顺序不能变,因为')'之前可能有换行 + { + mailpme->iDataPointer -= 3; //把')'留在缓冲区等待下次处理 + pc = pc-3; //包含')',后面会当成'\n'减掉这个长度;征文POP头部也有 + *line_start = payload; + *plinelen = pc-payload; + return MAIL_LINE_END; + } + } + + datalen = pc - payload; + if(datalen > 0) //找到了\n + { + mailpme->iDataPointer += srcend - pc; + *line_start = payload; + *plinelen = payload_len; + return MAIL_PACKET_END; + } + else + { + //很长的一行直接返回,有的正文没换行 + //存储的原因是:有时候处理到包尾部,剩余长度较小,协议比较关键字时可能会误命中 + if(buf_cache_check_size(&mailpme->pDataLineBuf, payload_len)<0) + { + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + return MAIL_LINE_END; + } + memcpy((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len, payload, payload_len); + mailpme->pDataLineBuf.len += payload_len; + mailpme->iDataPointer += payload_len; + + *line_start = NULL; + *plinelen = 0; + return MAIL_INPUT_END; + } + + return MAIL_INPUT_END; +} + +/********************************************************************* +函数名称:mail_get_tcpdata +功能简介:从邮件会话的tcp数据流中,获取一个数据行,其中完成了对邮件折叠行 +的拼接处理,去除了数据流中的回车换行。非完整行和非折叠行数据保存在mailpme->sDataline中 +输入参数:a_tcp:邮件数据流 + mailpme:邮件结构体 + need_folding:1需要处理折叠行 =0不需要处理折叠行 + line_start: +输出参数:line_start:分离出来的数据的指针 +返回值: 标志邮件行处理的进展情况,是行结束,还是输入结束等等 +*********************************************************************/ +int mail_get_tcpdata(const char *payload, size_t payload_len, stMailPme* mailpme, int need_folding, char ** output_buf,int *output_len) +{ + char* curpointer = (char*)(payload) + mailpme->iDataPointer; + + if((unsigned int)(mailpme->iDataPointer) >= (unsigned int)(payload_len)) + return MAIL_INPUT_END; + + if(need_folding == 1) + { + return mail_get_fold_line(curpointer, payload_len-mailpme->iDataPointer, output_buf, output_len, mailpme); + } + else if(mailpme->EmlAnalyseState != MAIL_STAT_BODY || mailpme->obtain_line) + { + if(mailpme->obtain_line) + mailpme->obtain_line = 0; + return mail_get_one_tcpline(curpointer, payload_len-mailpme->iDataPointer, output_buf, output_len, mailpme, 1); + } + else + { + return mail_get_complete_region(curpointer, payload_len-mailpme->iDataPointer, output_buf, output_len, mailpme); + } +} + +/********************************************************************* +函数名称:get_a_tcp_line +功能简介:从邮件会话的tcp数据流中,获取一个数据行,其中完成了对邮件折叠行 +的拼接处理,去除了数据流中的回车换行。非完整行和非折叠行数据保存在mailpme->sDataline中 +输入参数:a_tcp:邮件数据流 + mailpme:邮件结构体 + need_folding:1需要处理折叠行 =0不需要处理折叠行 + line_start: +输出参数:line_start:分离出来的数据的指针 +返回值: 标志邮件行处理的进展情况,是行结束,还是输入结束等等 +*********************************************************************/ +int mail_get_atcpline_c2s(const char *payload, size_t payload_len, stMailPme* mailpme, char ** output_buf,int *output_len) +{ + char* curpointer = (char*)(payload) + mailpme->iDataPointer; + + if((unsigned int)(mailpme->iDataPointer) >= (unsigned int)(payload_len)) + return MAIL_INPUT_END; + + return mail_get_one_tcpline(curpointer, payload_len-mailpme->iDataPointer, output_buf, output_len, mailpme, 0); +} + +/********************************************************************* +函数名称:mail_get_only_mailaddr +功能简介:从输入串srcstr中提取邮件地址信息 +输入参数: + srcstr:输入数据串 + payload_len: 输入串长度 +输出参数:addrbegin:邮件地址开始位置 + addrlen:邮件地址长度 +返回值:<0:非法邮件地址;>0: 正常 +*********************************************************************/ +static int mail_get_only_mailaddr(char *srcstr,int payload_len,char **addrbegin,int *addrlen) +{ + char *mailbegin=NULL, *mailend=NULL, *mailflag=NULL; + char *srcend; + + if(payload_len<=0 || srcstr==NULL) + return -1; + srcend = srcstr + payload_len; + + //add by lqy 20070529 + //避免出现传入数据很长,但是都没有<,要对数据进行全部扫描, + //认为邮件地址信息不应该很长,只扫描前128个就可以判断出有没有< + if(payload_len>128) + mailflag=(char *)memchr((void *)srcstr,'<',128); + else + mailflag=(char *)memchr((void *)srcstr,'<',payload_len); + + if(mailflag!=NULL) + { + mailbegin = mailflag+1; + while(mailbegin', srcend-mailbegin+1); + if(mailend == NULL) + { + return -2; + } + if((mailend-mailbegin)>0 && memchr((void *)mailbegin, '@', mailend-mailbegin)!=NULL) + { + *addrbegin=mailbegin; + *addrlen=(int)(mailend-mailbegin); + return (mailend-srcstr); + } + else + { + //char p[128]={0}; + //memcpy(p, mailflag, (mailend-mailflag+1)>=128?127:(mailend-mailflag+1)); + return -3; + } + } + else + { + mailflag=srcstr; + while(mailflagsrcstr && *mailbegin!=' ' && *mailbegin!='\t' && *mailbegin!=',' && *mailbegin!=';') + { + mailbegin--; + } + mailbegin++; + + mailend=mailflag+1; + while(mailend0) + { + ret=mail_get_only_mailaddr(srcstr, datalen, &addrbuf, &addrlen); + if(ret<0) + break; + + if(buf_elem_check_size(pdstBuf, addrlen+2) < 0) + return -6; + + //添加<标志,添加邮件地址,添加>标志 + pdstBuf->buf[pdstBuf->buflen] = '<'; + pdstBuf->buflen += 1; + memcpy(pdstBuf->buf + pdstBuf->buflen, addrbuf, addrlen); + pdstBuf->buflen += addrlen; + pdstBuf->buf[pdstBuf->buflen] = '>'; + pdstBuf->buflen += 1; + + srcstr += ret; + datalen -= ret; + } + + if(pdstBuf->buf == NULL) + return -7; + + pdstBuf->buf[pdstBuf->buflen] = '\0'; + + return 0; +} + +/********************************************************************* +函数名称:process_smtp_cmd_user_pass +功能简介:处理邮件头data命令, +输入参数: + mailpme:邮件处理结构体 +输出参数:无 +返回值: -1:error +*********************************************************************/ +int mail_save_mail_elem(char *payload, int datalen, stMailElem *pelem) +{ + if(buf_elem_check_size(pelem, datalen)) + return -1; + + memcpy(pelem->buf+pelem->buflen, payload, datalen); + pelem->buflen += datalen; + pelem->buf[pelem->buflen] = '\0'; + + return 0; +} + +void drop_set_value(stMailPme* mailpme) +{ + mailpme->EmlAnalyseState = MAIL_STAT_BODY; //消除获取折叠行的可能 + mailpme->mimeinfo->text_begin = 1; +} + + +int mail_get_line(struct evbuffer *line_cache, char *payload, int payload_len, char **line_start, char **line_end) +{ + char *pos_n = NULL; + int datalen; + + pos_n = (char *)memchr(payload, '\n', payload_len); + if(pos_n == NULL) //直接缓存,不管大小?这一行还没传输完; + { + + if(buf_cache_check_size(line_cache, payload_len)<0) + { + *line_start = (char *)line_cache.buf; + *plinelen = mailpme->pDataLineBuf.len; + return MAIL_LINE_END; + } + + memcpy((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len, payload, payload_len); + mailpme->pDataLineBuf.len += payload_len; + mailpme->iDataPointer += payload_len; + return MAIL_INPUT_END; + } + else //找到了一个完整行 + { + if(rn) + { + datalen = pos_n - payload + 1; + mailpme->iDataPointer += datalen; + } + else + { + datalen = pos_n - payload; + mailpme->iDataPointer += datalen + 1; //include \n + } + + //上一个包缓冲了部分数据,则首先进行拼接 + if(mailpme->pDataLineBuf.len > 0) + { + if(buf_cache_check_size(&mailpme->pDataLineBuf, datalen)<0) + { + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + mailpme->iDataPointer -= rn?datalen:(datalen+1); + return MAIL_LINE_END; + } + memcpy((char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len, payload, datalen); + mailpme->pDataLineBuf.len += datalen; + + if(rn==0) + { + pos_n = (char *)mailpme->pDataLineBuf.buf + mailpme->pDataLineBuf.len - 1; + if(*pos_n == '\r') //100% properly + mailpme->pDataLineBuf.len -= 1; + } + + *line_start = (char *)mailpme->pDataLineBuf.buf; + *plinelen = mailpme->pDataLineBuf.len; + return MAIL_LINE_END; + } + + if(rn==0 && datalen > 0) + { + if(*(payload+datalen-1) == '\r') + datalen = datalen - 1; + } + *line_start = payload; + *plinelen = datalen; + return MAIL_LINE_END; + } +} + + +int mail_analy_emlheader(const char *a_mail_line,int datalen,int EmlAnalyseState); +int process_mail_data(int EML_opst,char *maildata_line,int datalen,stMailPme * mailpme); +#include +#include +#include +#include + +#include "mail_common.h" +#include "mail_mime.h" +#include "mail_analyze.h" + +static int save_region_data(stMailPme *mailpme, unsigned long long prot_flag, void *buf, int buflen, char *charset, int copy) +{ + if(mailpme->plug_mailinfo.elem_num >= mailpme->max_elem_num) + { + mailpme->max_elem_num += MAIL_ELEM_NUM; + mailpme->plug_mailinfo.elem = (stFieldElem *)realloc(mailpme->plug_mailinfo.elem, sizeof(stFieldElem)*mailpme->max_elem_num); + if(mailpme->plug_mailinfo.elem == NULL) + return -1; + } + + if(copy) + { + int ret; + + ret = buf_cache_check_size(&mailpme->pBizRegion, buflen); + if(ret < 0) + return -1; + else if(ret == 1) //重新分配了缓存,需要更新一下指针 + { + int i, offset=0; + + for(i=0;iplug_mailinfo.elem_num;i++) + { + mailpme->plug_mailinfo.elem[i].buf = (char *)mailpme->pBizRegion.buf + offset; + offset += mailpme->plug_mailinfo.elem[i].buflen; + } + } + memcpy((char*)mailpme->pBizRegion.buf + mailpme->pBizRegion.len, buf, buflen); + mailpme->plug_mailinfo.elem[mailpme->plug_mailinfo.elem_num].buf = (char *)mailpme->pBizRegion.buf + mailpme->pBizRegion.len; + mailpme->pBizRegion.len += buflen;//只在回调业务后清空 + } + else + { + mailpme->plug_mailinfo.elem[mailpme->plug_mailinfo.elem_num].buf = buf; + } + + mailpme->plug_mailinfo.elem[mailpme->plug_mailinfo.elem_num].buflen = buflen; + mailpme->plug_mailinfo.elem[mailpme->plug_mailinfo.elem_num].prot_flag = prot_flag; + mailpme->plug_mailinfo.elem[mailpme->plug_mailinfo.elem_num].current_charset = charset; + mailpme->plug_mailinfo.elem_num++; + + return 0; +} + +static int process_date_eml(const char *maildata, int datalen, stMailPme *mailpme) +{ + int ret; + int decoded_len; + char *decoded; + char decode_buf[MAX_ORDER_LINE_SIZE+1]; + char charset_str_buf[MAIL_MAX_CHARSET_LEN]; + stMailElem *pelem = mailpme->plug_mailinfo.pMailInfo->date; + + if(datalen >= MAX_ORDER_LINE_SIZE) { + return 0; + } + if(datalen <=5) { + return -1; + } + + maildata += 5; + datalen -= 5; + + decoded_len = sizeof(decode_buf); + ret = mime_header_decode(maildata, datalen, decode_buf, &decoded_len, charset_str_buf, sizeof(charset_str_buf)); + if(ret < 0) { + decoded = (char *)maildata; + decoded_len = datalen; + } else { + decoded = decode_buf; + } + + if(buf_elem_check_size(pelem, decoded_len) < 0) { + return -1; + } + memcpy(pelem->buf, decoded, decoded_len); + pelem->buflen = decoded_len; + pelem->buf[pelem->buflen] = '\0'; + return 0; +} + +static int process_mailfrom_eml(char *maildata,int datalen,stMailPme *mailpme) +{ + int ret; + int decoded_len; + char *decoded; + char decode_buf[MAX_ORDER_LINE_SIZE+1]; + char charset_str_buf[MAIL_MAX_CHARSET_LEN]; + + //from: + if(datalen<=5) { + return -1; + } + + maildata += 5; + datalen -= 5; + + if(mailpme->plug_mailinfo.pMailInfo->sendaddr->buflen<=0) + { + decoded_len = sizeof(decode_buf); + ret = mime_header_decode(maildata, datalen, decode_buf, &decoded_len, charset_str_buf, sizeof(charset_str_buf)); + if(ret < 0) { + decoded = (char *)maildata; + decoded_len = datalen; + } else { + decoded = decode_buf; + } + + ret = mail_get_mailaddr(mailpme->plug_mailinfo.pMailInfo->sendaddr, decoded, decoded_len); + if(ret<0) { + return -1; + } + } + + return 0; +} + +static int process_mailto_eml(char *maildata,int datalen,stMailPme * mailpme) +{ + int ret; + int decoded_len; + char *decoded; + char decode_buf[MAX_ORDER_LINE_SIZE+1]; + char charset_str_buf[MAIL_MAX_CHARSET_LEN]; + + //bcc: + if(datalen<=4) { + return -1; + } + + if(strncmp_one_word_mesa("bcc:", "BCC:", 4, maildata, datalen)) { + maildata += 4;//错过长度"bcc: " + datalen -= 4; + } else { + maildata += 3;//错过长度"cc: "或者"to: " + datalen -= 3; + } + + decoded_len = sizeof(decode_buf); + ret = mime_header_decode(maildata, datalen, decode_buf, &decoded_len, charset_str_buf, sizeof(charset_str_buf)); + if(ret < 0) { + decoded = (char *)maildata; + decoded_len = datalen; + } else { + decoded = decode_buf; + } + + ret = mail_get_mailaddr(mailpme->plug_mailinfo.pMailInfo->recvaddr, decoded, decoded_len); + if(ret < 0) { + return -1; + } + + return 0; +} + +static int process_subject_eml(char *maildata,int datalen,stMailPme * mailpme) +{ + if(datalen>=MAX_ORDER_LINE_SIZE) + return -1; + + maildata += 8;datalen -= 8; //subject: + while(datalen>0 && (*maildata==' ' || *maildata=='\t')) + { + maildata++; datalen--; + } + if(datalen<=0) + { + return -1; + } + + int ret=0; + char out[MAX_ORDER_LINE_SIZE+1], *pout=out; + int outl = MAX_ORDER_LINE_SIZE+1; + stMailElem *pelem = mailpme->plug_mailinfo.pMailInfo->subject; + + ret = mime_header_decode(maildata, datalen, pout, &outl, mailpme->subject_charset, MAIL_MAX_CHARSET_LEN); + if(ret<0) + { + pout = maildata; + outl = datalen; + } + + if(buf_elem_check_size(pelem, outl) < 0) + { + return -1; + } + memcpy(pelem->buf, pout, outl); + pelem->buflen = outl; + pelem->buf[pelem->buflen] = '\0'; + return 0; +} + +int mail_analy_emlheader(const char *a_mail_line,int datalen,int EmlAnalyseState) +{ + if(EmlAnalyseState == MAIL_STAT_BODY) + return EML_DATA_BODY_IN; + + const char *pos_colon; + + /*MAIL_INIT和MAILHEADER 状态*/ + pos_colon = (const char *)memchr(a_mail_line, ':', datalen>26?26:datalen); + if(pos_colon != NULL) + { + datalen = pos_colon-a_mail_line; + + switch(datalen) + { + case 2: + if(strcmp_one_word_mesa_equal_len("to", "TO", a_mail_line, 2)) + return EML_DATA_TO; + if(strcmp_one_word_mesa_equal_len("cc", "CC", a_mail_line, 2)) + return EML_DATA_CC; + break; + + case 3: + if(strcmp_one_word_mesa_equal_len("bcc", "BCC", a_mail_line, 3)) + return EML_DATA_BCC; + break; + + case 4: + if(strcmp_one_word_mesa_equal_len("date", "DATE", a_mail_line, 4)) + return EML_DATA_DATE; + if(strcmp_one_word_mesa_equal_len("from", "FROM", a_mail_line, 4)) + return EML_DATA_FROM; + break; + + case 7: + if(strcmp_one_word_mesa_equal_len("subject", "SUBJECT", a_mail_line, 7)) + return EML_DATA_SUBJECT; + break; + + case 8: + if(strcmp_one_word_mesa_equal_len("received", "RECEIVED", a_mail_line, 8)) + return EML_DATA_RECEIVED; + if(strcmp_one_word_mesa_equal_len("reply-to", "REPLY-TO", a_mail_line, 8)) + return EML_DATA_REPLY_TO; + break; + + case 11: + if(strcmp_one_word_mesa_equal_len("return-path", "RETURN-PATH", a_mail_line, 11)) + return EML_DATA_RETURN_PATH; + break; + + case 12: + if(strcmp_one_word_mesa_equal_len("delivered-to", "DELIVERED-TO", a_mail_line, 12)) + return EML_DATA_DELIVERED_TO; + if(MAIL_STAT_HEADER == EmlAnalyseState) + { + if(strcmp_one_word_mesa_equal_len("content-type", "CONTENT-TYPE", a_mail_line, 12)) + return EML_DATA_CONTENTTYPE; + return EML_DATA_HEAD_IN; + } + break; + + case 25: + if(MAIL_STAT_HEADER == EmlAnalyseState) + { + if(strcmp_one_word_mesa_equal_len("content-transfer-encoding", "CONTENT-TRANSFER-ENCODING", a_mail_line, 25)) + return EML_DATA_CONTENTTRANSFERENCODING; + return EML_DATA_HEAD_IN; + } + break; + + default: + if(MAIL_STAT_HEADER != EmlAnalyseState && strncmp_one_word_mesa("x-", "X-", 2, a_mail_line, datalen)) + return EML_DATA_HEAD_IN; + break; + } + + //以下几个状态只有头部状态才能返回 //adjust by lqy 20100204 + if(MAIL_STAT_HEADER == EmlAnalyseState) + { + return EML_DATA_HEAD_IN; + } + } + else if(MAIL_STAT_HEADER == EmlAnalyseState) + { + if((datalen==2 && !strncmp(a_mail_line, "\r\n", 2))||(datalen==1 && *a_mail_line=='\n')) + return EML_DATA_HEAD_BODY_BORDER; + + return EML_DATA_HEAD_IN; + } + + return EML_UNKNOWN; +} + +static int process_data_bodyin_cmd(char *a_mail_line, int datalen,stMailPme * mailpme) +{ + int ret; + MimeParse_t * mimeinfo = mailpme->mimeinfo; + + //沿用TODO + if((mailpme->MailInfoState == MAIL_GET_FILECOUNT)||(mailpme->MailInfoState == MAIL_GET_FILENAME)) + { + mailpme->MailInfoState = MAIL_GET_FILECOUNT; //邮件附件内容 + } + else if(mailpme->MailInfoState != MAIL_GET_FILECOUNT_END) + { + mailpme->MailInfoState = MAIL_GET_COUNT; //邮件正文内容 + } + + mimeinfo->src = a_mail_line; //src是一个指向mime解码前数据的指针 + mimeinfo->srcLen = datalen; + + if(buf_cache_check_size(&mailpme->pMimeDecodeBuf, mimeinfo->srcLen+1) < 0) + { + return -1; + } + mimeinfo->dst = (char *)mailpme->pMimeDecodeBuf.buf; + mimeinfo->dstSize = mailpme->pMimeDecodeBuf.max_len; + mailpme->pMimeDecodeBuf.len = 0; + + ret = mime_parse_feed(mimeinfo, 1); + if(ret<0) + { + return -1; + } + + if((ret == MIME_LINE_CONT_TYPE || ret == MIME_LINE_CONT_DISP) && mimeinfo->filename != NULL) + { + mailpme->MailInfoState = MAIL_GET_FILENAME; + } + else if(ret == MIME_LINE_CONT_OTHR) //如果在FILENAME之后来,会冲掉MAIL_GET_FILENAME状态,导致附件内容无法解析 + { + mailpme->mimeinfo->actLen = 0; + //mailpme->MailInfoState = MAIL_GET_OTHER; + } + else if(ret == MIME_LINE_CONT_BODY) + { + if(mailpme->MailInfoState == MAIL_GET_FILECOUNT || mailpme->MailInfoState == MAIL_GET_FILENAME || \ + mailpme->MailInfoState == MAIL_GET_FILECOUNT_END) + { + mailpme->MailInfoState = MAIL_GET_FILECOUNT; //邮件附件内容 + } + else + { + mailpme->MailInfoState = MAIL_GET_COUNT; //邮件正文内容 + } + } + else if(ret == MIME_LINE_BOUNARY || ret == MIME_LINE_BOUNARY_END) + { + if(mailpme->MailInfoState == MAIL_GET_FILECOUNT) + { + mailpme->MailInfoState = MAIL_GET_FILECOUNT_END; + } + } + + return 0; +} + +int process_mail_data(int EML_opst, char *maildata_line,int datalen,stMailPme * mailpme) +{ + int processRet, tmp_len, norn_len=datalen; + long long prot_flag=0; + void *buf=NULL; + int buflen=0, region_offest; + char *charset = NULL; + + switch(EML_opst) + { + case EML_DATA_DATE: + if(mailpme->EmlAnalyseState != MAIL_STAT_HEADER) + { + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; + } + mailpme->MailInfoState = MAIL_GET_DATE; + + while(norn_len > 0 && (*(maildata_line + norn_len -1)=='\n' || *(maildata_line + norn_len -1)=='\r')) + norn_len -= 1; + + processRet=process_date_eml(maildata_line, norn_len, mailpme); //-2:去掉\r\n + if(processRet<0) + { + break; + } + + prot_flag = MAIL_DATE; + buf = mailpme->plug_mailinfo.pMailInfo->date->buf; + buflen = mailpme->plug_mailinfo.pMailInfo->date->buflen; + break; + + case EML_DATA_FROM: + if(mailpme->EmlAnalyseState != MAIL_STAT_HEADER) + { + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; + } + mailpme->MailInfoState = MAIL_GET_FROM; + + while(norn_len > 0 && (*(maildata_line + norn_len -1)=='\n' || *(maildata_line + norn_len -1)=='\r')) + norn_len -= 1; + + mailpme->plug_mailinfo.pMailInfo->sendaddr->buflen = 0; + processRet = process_mailfrom_eml(maildata_line, norn_len, mailpme); + if(processRet<0) + { + break; + } + + prot_flag = MAIL_FROM; + buf = mailpme->plug_mailinfo.pMailInfo->sendaddr->buf; + buflen = mailpme->plug_mailinfo.pMailInfo->sendaddr->buflen; + break; + + case EML_DATA_TO: + if(mailpme->EmlAnalyseState != MAIL_STAT_HEADER) + { + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; + } + mailpme->MailInfoState = MAIL_GET_TO; + + while(norn_len > 0 && (*(maildata_line + norn_len -1)=='\n' || *(maildata_line + norn_len -1)=='\r')) + norn_len -= 1; + + tmp_len = mailpme->plug_mailinfo.pMailInfo->recvaddr->buflen; + + processRet = process_mailto_eml(maildata_line, norn_len, mailpme); + if(processRet<0) + { + break; + } + + prot_flag = MAIL_TO; + buf = mailpme->plug_mailinfo.pMailInfo->recvaddr->buf + tmp_len; + buflen = mailpme->plug_mailinfo.pMailInfo->recvaddr->buflen - tmp_len; + break; + + case EML_DATA_CC: + if(mailpme->EmlAnalyseState != MAIL_STAT_HEADER) + { + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; + } + mailpme->MailInfoState = MAIL_GET_TO; //TODO + + while(norn_len > 0 && (*(maildata_line + norn_len -1)=='\n' || *(maildata_line + norn_len -1)=='\r')) + norn_len -= 1; + + tmp_len = mailpme->plug_mailinfo.pMailInfo->recvaddr->buflen; + + processRet = process_mailto_eml(maildata_line, norn_len, mailpme); + if(processRet<0) + { + break; + } + + prot_flag = MAIL_CC; + buf = mailpme->plug_mailinfo.pMailInfo->recvaddr->buf + tmp_len; + buflen = mailpme->plug_mailinfo.pMailInfo->recvaddr->buflen - tmp_len; + break; + + case EML_DATA_BCC: + if(mailpme->EmlAnalyseState != MAIL_STAT_HEADER) + { + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; + } + mailpme->MailInfoState = MAIL_GET_TO; //TODO + + while(norn_len > 0 && (*(maildata_line + norn_len -1)=='\n' || *(maildata_line + norn_len -1)=='\r')) + norn_len -= 1; + + tmp_len = mailpme->plug_mailinfo.pMailInfo->recvaddr->buflen; + + processRet = process_mailto_eml(maildata_line, norn_len, mailpme); + if(processRet<0) + { + break; + } + + prot_flag = MAIL_BCC; + buf = mailpme->plug_mailinfo.pMailInfo->recvaddr->buf + tmp_len; + buflen = mailpme->plug_mailinfo.pMailInfo->recvaddr->buflen - tmp_len; + break; + + case EML_DATA_REPLY_TO: + if(mailpme->EmlAnalyseState != MAIL_STAT_HEADER) + { + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; + } + mailpme->MailInfoState = MAIL_GET_TO; + + while(norn_len > 0 && (*(maildata_line + norn_len -1)=='\n' || *(maildata_line + norn_len -1)=='\r')) + norn_len -= 1; + + tmp_len = mailpme->plug_mailinfo.pMailInfo->recvaddr->buflen; + processRet = mail_get_mailaddr(mailpme->plug_mailinfo.pMailInfo->recvaddr, maildata_line+9, norn_len-9); + if(processRet<0) + { + break; + } + + prot_flag = MAIL_REPLY_TO; + buf = mailpme->plug_mailinfo.pMailInfo->recvaddr->buf + tmp_len; + buflen = mailpme->plug_mailinfo.pMailInfo->recvaddr->buflen - tmp_len; + mailpme->plug_mailinfo.pMailInfo->recvaddr->buflen = tmp_len; //临时借用缓冲区,之后恢复 + break; + case EML_DATA_SUBJECT: + if(mailpme->EmlAnalyseState != MAIL_STAT_HEADER) + { + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; + } + mailpme->MailInfoState = MAIL_GET_SUB; //TODO + + while(norn_len > 0 && (*(maildata_line + norn_len -1)=='\n' || *(maildata_line + norn_len -1)=='\r')) + norn_len -= 1; + + processRet = process_subject_eml(maildata_line, norn_len, mailpme); + if(processRet<0) + { + break; + } + + prot_flag = MAIL_SUBJECT; + buf = mailpme->plug_mailinfo.pMailInfo->subject->buf; + buflen = mailpme->plug_mailinfo.pMailInfo->subject->buflen; + charset = mailpme->subject_charset; + break; + + case EML_DATA_CONTENTTYPE: + case EML_DATA_CONTENTTRANSFERENCODING: + case EML_DATA_BODY_IN: + processRet = process_data_bodyin_cmd(maildata_line, datalen, mailpme); + if(processRet < 0) + { + return -1;; + } + + if(mailpme->MailInfoState == MAIL_GET_FILENAME) + { + charset = mailpme->mimeinfo->filename_charset; + prot_flag = MAIL_ATTACH_NAME; + + buflen = mailpme->mimeinfo->filenameLen; + if(buflen == 0) + { + buf = (void *)NULL_FILENAME; + buflen = NULL_FILENAME_LEN; + } + else + { + buf = mailpme->mimeinfo->filename; + } + } + //Content-type得到附件名后,还有其他Ccontent-xxx字段,此时正文没开始 + else if(mailpme->MailInfoState == MAIL_GET_FILECOUNT && mailpme->mimeinfo->text_begin==1) + { + charset = mailpme->mimeinfo->charset; + prot_flag = MAIL_ATTACH_CONTENT; + buf = mailpme->mimeinfo->dst; + buflen = mailpme->mimeinfo->actLen; + } + else if(mailpme->MailInfoState == MAIL_GET_COUNT) + { + charset = mailpme->mimeinfo->charset; + prot_flag = MAIL_CONTENT; + buf = mailpme->mimeinfo->dst; + buflen = mailpme->mimeinfo->actLen; + } + break; + + case EML_DATA_HEAD_BODY_BORDER: //进入消息体处理状态,邮件头部完毕 + mailpme->EmlAnalyseState = MAIL_STAT_BODY; + //对于没有使用MIME的邮件,进入正文后应该设置正文开始标记text_begin + //否则按照现在的处理流程,会按照折叠行来获取正文数据 + body_update_text_begin(mailpme->mimeinfo); + break; + + case EML_DATA_RETURN_PATH: + case EML_DATA_RECEIVED: + case EML_DATA_DELIVERED_TO: + case EML_DATA_HEAD_IN: + if(mailpme->EmlAnalyseState != MAIL_STAT_HEADER) + { + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; + } + + while(norn_len > 0 && (*(maildata_line + norn_len -1)=='\n' || *(maildata_line + norn_len -1)=='\r')) + norn_len -= 1; + + prot_flag = MAIL_OTHER; + region_offest = 0; + char *p = (char *)memchr(maildata_line, ':', norn_len); + int region_len = (p==NULL)?0:(p-maildata_line); + region_offest = region_len+1; + while(region_offestplug_mailinfo.cur_offset = mailpme->pOriginalData.len; + processRet = save_original_data(&mailpme->pOriginalData, maildata_line, datalen); + if(processRet < 0) + { + return -1; + } + + if(buflen > 0) + { + if(save_region_data(mailpme, prot_flag, buf, buflen, charset, 0)) + { + return -1; + } + } + + return 0; +} + + + + + + +#include +#include + +#include "mail_global.h" +#include "mail_parser.h" + +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-function" + +enum MAIL_STATE { + MAIL_STATE_INIT, + MAIL_STATE_HEADER_START, + MAIL_STATE_HEADER_END, + MAIL_STATE_BODY_START, + MAIL_STATE_BODY_END, + MAIL_STATE_EXIT, +}; + +struct mail_parser { + enum MAIL_STATE state; + + enum MAIL_PROTOCOL protocol; + size_t mail_seq; + stMailPme *mailpme; +}; + +enum MAIL_STATE mail_parser_get_mail_state(struct mail_parser *parser) +{ + return parser->state; +} + +enum MAIL_PROTOCOL mail_parser_get_protocol(struct mail_parser *parser) +{ + return parser->protocol; +} + +size_t mail_parser_get_seq(struct mail_parser *parser) +{ + return parser->mail_seq; +} + +struct mail_header *mail_parser_get_header(struct mail_parser *parser) +{ + return NULL; +} + +int mail_parser_get_body(struct mail_parser *parser, char **body, size_t *body_len, size_t *body_offset, int *is_body_finished) +{ + return 0; +} + +int mail_parser_get_attachment(struct mail_parser *parser, char **attachment_name, size_t *attachment_name_len, char **attachment, size_t *attachment_len, size_t *attachment_offset, int *is_attachment_finished) +{ + return 0; +} + +int mail_parser_get_eml(struct mail_parser *parser, char **eml, size_t *eml_len, size_t *eml_offset, int *is_eml_finished) +{ + return 0; +} + +int mail_parser_process(struct mail_parser *parser,const char *payload, size_t payload_len, int is_c2s) +{ + return 0; +} + +void mail_parser_free(struct mail_parser *parser) +{ + if (parser) { + if (parser->mailpme) { + mail_reset_mailpme_noclose(parser->mailpme); + } + free(parser); + } +} + +struct mail_parser *mail_parser_new(enum MAIL_PROTOCOL protocol) +{ + struct mail_parser *parser = NULL; + + parser = (struct mail_parser *)calloc(1, sizeof(struct mail_parser)); + mail_init_mailpme(&parser->mailpme); + + return parser; +} diff --git a/decoders/mail/mail_email.h b/decoders/mail/mail_email.h new file mode 100644 index 0000000..77ca3c7 --- /dev/null +++ b/decoders/mail/mail_email.h @@ -0,0 +1,342 @@ +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include "mail_internal.h" +#include "mail_mime.h" + +typedef enum +{ + MAIL_USERNAME_MASK=0, + MAIL_PASSWARD_MASK, + MAIL_FROM_MASK, + MAIL_TO_MASK, + MAIL_CC_MASK, + MAIL_BCC_MASK, + MAIL_DATE_MASK, + MAIL_SUBJECT_MASK, + MAIL_CONTENT_MASK, + MAIL_ATTACH_NAME_MASK, + MAIL_ATTACH_CONTENT_MASK, + MAIL_OTHER_MASK, + MAIL_FROM_CMD_MASK, + MAIL_TO_CMD_MASK, + MAIL_EHLO_CMD_MASK, + MAIL_REPLY_TO_MASK, + + /*ADD*/ + MAIL_REGION_NUM, + MAIL_STARTTLS_CMD_MASK, +}mail_interested_region_mask; + +//邮件各字段flag对应值;程序中定义的宏在字段前加"MAIL_",配置文件中注册的字段没有前面的"MAIL_" +//配置文件中支持"ALL" +//配置文件注册字段大小写敏感 +#define MAIL_USERNAME ((long long)1<app_info + stKeyInfo pMailinfo; //plug_mailinfo.pMailinfo + new_stKeyInfo keyinfo; //plug_mailinfo.pMailinfo的成员 + stFieldElem elem; + + MimeParse_t *mimeinfo; // mime解码结构 + char subject_charset[MAIL_MAX_CHARSET_LEN]; //主题字符集编码格式 + + int iDataPointer; // 当前读取的数据偏移,用于按行读取中 + int max_elem_num; + + int EmlAnalyseState;// 标志邮件处理状态 + int MailInfoState; //zcw add, from stMailInfoOld + + int iProtoType; //邮件会话结构的协议类型 + + char pending_state; //1-为新的会话刚准备好上下文,0-新会话已到来 + char is_called; //当前会话回调过业务层 + char pre_dir; //上一个流的方向,用于双方向缓存数据的时候使用,对于SMTP只考虑单方向的忽略此变量 + + //NEXT ONLY IMAP4 + char is_continue; + char maybe_end; //')'不能确定结束与否的时候设置标记 + short obtain_line; //点名接下来获取一行,级别低于折叠行 + int pre_opsate; //')'不能确定结束与否的时候保留上一次的行类型 + long pre_uid; //IMAP协议按照字节数获取邮件内容的情况 + long uid; + + void* app_pme; +}stMailPme; + +int buf_elem_check_size(stMailElem *pdstBuf, int wantedlen); +int buf_cache_check_size(stBufCache *pbuf, int wantedlen); + +int save_original_data(stBufCache *pbuf, const char *data, int datalen); + +char mail_init_mailpme(stMailPme** mailpme_pointer,char ServiceType, int is_c2s); +void mail_clear_mailpme(stMailPme * mailpme); +char mail_reset_mailpme(stMailPme * mailpme); +char mail_reset_mailpme_noclose(stMailPme * mailpme); +void mail_close_business(stMailPme * mailpme); + +int mail_get_atcpline_c2s(const char *payload, size_t payload_len, stMailPme* mailpme, char ** output_buf,int *output_len); +int mail_get_tcpdata(const char *payload, size_t payload_len, stMailPme* smtpinfo, int need_folding, char ** output_buf,int *output_len); +int mail_get_mailaddr(stMailElem *pdstBuf,char *srcstr,int datalen); +int mail_save_mail_elem(char *srcdata, int datalen, stMailElem *pelem); + + + + + + +#include "stellar/mail.h" + +struct email_header { + struct iovec *from; + struct iovec *to; + struct iovec *cc; + struct iovec *bcc; + struct iovec *reply_to; + struct iovec *date; + struct iovec *subject; + + struct iovec *header_fields; + size_t n_header_fields; +}; + +struct email_body { + const char *body; + size_t body_len; + size_t body_offset; +}; + +struct email_attachment { + const char *attachment_name; + size_t attachment_name_len; + const char *attachment; + size_t attachment_len; + size_t attachment_offset; +}; + +struct email_eml { + const char *eml; + size_t eml_len; + size_t eml_offset; +}; + +enum email_state { + MAIL_HEADER, + MAIL_, + MAIL_CONTENT, + MAIL_ATTACHMENT, +}; + +struct email_parser { + enum email_state state; + struct email_header header; + struct email_body body; + struct email_attachment attachment; + struct email_eml eml; +}; + +struct mail_parser; + +enum MAIL_PROTOCOL email_parser_get_protocol(struct email_parser *parser); +size_t email_parser_get_seq(struct email_parser *parser); +int email_parser_get_command(struct email_parser *parser, enum MAIL_COMMAND *cmd, char **cmd_param, size_t *cmd_param_len, char **cmd_line, size_t *cmd_line_len); +struct email_header *mail_parser_get_header(struct email_parser *parser); +int email_parser_get_body(struct email_parser *parser, char **body, size_t *body_len, size_t *body_offset, int *is_body_finished); +int email_parser_get_attachment(struct email_parser *parser, char **attachment_name, size_t *attachment_name_len, char **attachment, size_t *attachment_len, size_t *attachment_offset, int *is_attachment_finished); +int email_parser_get_eml(struct email_parser *parser, char **eml, size_t *eml_len, size_t *eml_offset, int *is_eml_finished); + +int email_parser_process(struct email_parser *parser, const char *payload, size_t payload_len, int is_c2s); + +struct mail_parser *email_parser_init(); +void email_parser_exit(struct email_parser *parser); + +#ifdef __cplusplus +} +#endif diff --git a/decoders/mail/mail_imap.c b/decoders/mail/mail_imap.c new file mode 100644 index 0000000..dcc6ecf --- /dev/null +++ b/decoders/mail/mail_imap.c @@ -0,0 +1,1966 @@ +#include +#include +#include +#include +#include +#include + +#include "mail_internal.h" +#include "mail_util.h" +#include "mail_email.h" + +extern stMailLocalInfo g_mail_localinfo; + +static void imap_free_node(void *data) +{ + mime_item_t *pitem = (mime_item_t *)data; + + if(pitem!=NULL) + { + if(pitem->mime_header.buf != NULL) + dictator_free(pitem->thread_id, pitem->mime_header.buf); + if(pitem->attach_filename != NULL) + dictator_free(pitem->thread_id, pitem->attach_filename); + if(pitem->boundary != NULL) + dictator_free(pitem->thread_id, pitem->boundary); + dictator_free(pitem->thread_id, pitem); + } +} + +void imap_free_htable_cb(void *data) +{ + imap_free_node(data); + + if(g_mail_localinfo.stat_trig) + { + FS_operate(g_mail_localinfo.fsstat_handle, g_mail_localinfo.stat_id[STAT_ID_HTABLE_NUM], 0, FS_OP_ADD, -1); + } +} + +/********************************************************************* +函数名称:get_value_parenthesis +功能简介:从参数组合列表中取得相应变量的值 +输入参数:name1/name2可能的变量 +输出参数:out:变量的值的字符串起始指针;outl:字符串长度 +返回值:1-取得了变量值;0-未找到 +*********************************************************************/ +static int get_value_parenthesis(const char *ps, const char *p_end, const char *name1, int len1, const char *name2, int len2, const char **out, int *outl) +{ + const char *pe; + + if((ps = mesa_memncasemem(ps, p_end-ps, name1, len1)) != NULL) + { + ps += len1; + } + else if((ps = mesa_memncasemem(ps, p_end-ps, name2, len2)) != NULL) + { + ps += len2; + } + else + { + return 0; + } + + while(psmime_header, "\r\nContent-Type:multipart/", 25, pitem->thread_id); +#else + save_original_data(&pitem->mime_header, "Content-Type:multipart/", 23, pitem->thread_id); +#endif + save_original_data(&pitem->mime_header, ps, p-ps, pitem->thread_id); + p++; + break; + + case MULTI_PARENTHESIS: + if(*p != '(') //没有参数组合列表,后续的不处理了,也可能后续没有扩展字段 + { + p = p_end; + break; + } + ps = p+1; + if((p=look_for_parenthesis_end(ps, p_end))==NULL || *p!=')') + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log,RLOG_LV_FATAL, MAILIMAP4_MODULE, "look_for_parenthesis_end error, input not complete()."); + break; + } + if(get_value_parenthesis(ps, p, "\"boundary\" ", 11, "boundary ", 9, &ps, &len)) + { + save_original_data(&pitem->mime_header, ";boundary=", 10, pitem->thread_id); + save_original_data(&pitem->mime_header, ps, len, pitem->thread_id); + + if(*ps == '"') + { + ps++; + len-=2; + } + pitem->boundary = (char *)dictator_malloc(pitem->thread_id, len+1); + memcpy(pitem->boundary, ps, len); + pitem->boundary[len] = '\0'; + pitem->bound_len = len; + } + p++; + break; + + case MULTI_DISPOSITION: + case MULTI_LANGUAGE: + case MULTI_LOCATION: + break; + default: return -1; + } + } + + save_original_data(&pitem->mime_header, "\r\n\r\n", 4, pitem->thread_id); //MIME头部和邮件正文之间需要有空行 + return 0; +} + +/********************************************************************* +函数名称:parse_not_multipart_line +功能简介:解析一行非multipart,将响应的结果写入mime_item_t结构体 +输入参数: +输出参数: +返回值:0-正常;-1-失败 +*********************************************************************/ +static int parse_not_multipart_line(mime_item_t *pitem, const char *line, int linelen, int is_extend) +{ + int i, len, type=CONT_TYPE_OTHER; + const char *p=line, *p_end=line+linelen, *ps; + + for(i=NOTMUL_TYPE; i<=NOTMUL_DISPOSITION&&(pmime_header, "\r\nContent-Type:", 15, pitem->thread_id); +#else + save_original_data(&pitem->mime_header, "Content-Type:", 13, pitem->thread_id); +#endif + save_original_data(&pitem->mime_header, ps, p-ps, pitem->thread_id); + save_original_data(&pitem->mime_header, "/", 1, pitem->thread_id); + if(is_extend) + { + if(strcmp_one_word_mesa("text", "TEXT", 4, ps, p-ps)) + type = CONT_TYPE_TEXT; + else if(strcmp_one_word_mesa("message", "MESSAGE", 7, ps, p-ps)) + type = CONT_TYPE_MESSAGE; + } + p++; + break; + + case NOTMUL_SUBTYPE: + if((ps = look_for_string_end(p, p_end, &p)) == NULL) + { + return -1; + } + save_original_data(&pitem->mime_header, ps, p-ps, pitem->thread_id); + if(type==CONT_TYPE_MESSAGE && strcmp_one_word_mesa("rfc822", "RFC822", 6, ps, p-ps)) + { + type = CONT_TYPE_RFC822; + } + p++; + break; + + case NOTMUL_PARENTHESIS: + if(*p != '(') //没有参数组合列表,不处理;跳到下一个字段 + { + p = (const char*)memchr(p, ' ', p_end-p); + if(p==NULL) + p=p_end; + break; + } + ps = p+1; + if((p=look_for_parenthesis_end(ps, p_end))==NULL || *p!=')') + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log,RLOG_LV_FATAL, MAILIMAP4_MODULE, "look_for_parenthesis_end error, input not complete()."); + i=NOTMUL_NUM; + break; + } + + if(get_value_parenthesis(ps, p, "\"charset\" ", 10, "charset ", 8, &ps, &len)) + { + save_original_data(&pitem->mime_header, ";charset=", 9, pitem->thread_id); + save_original_data(&pitem->mime_header, ps, len, pitem->thread_id); + + if(*ps == '"') + { + ps++; + len-=2; + } + if(len >= MAIL_MAX_CHARSET_LEN) + { + len = MAIL_MAX_CHARSET_LEN - 1; + } + memcpy(pitem->charset, ps, len); + pitem->charset[len] = '\0'; + } + if(get_value_parenthesis(ps, p, "\"name\" ", 7, "name ", 5, &ps, &len)) //查找附件名 + { + save_original_data(&pitem->mime_header, ";name=", 6, pitem->thread_id); + save_original_data(&pitem->mime_header, ps, len, pitem->thread_id); + + if(*ps == '"') + { + ps++; + len-=2; + } + pitem->attach_filename = (char *)dictator_malloc(pitem->thread_id, len+1); + mime_header_decode(ps, len, pitem->attach_filename, &len, pitem->filename_charset, MAIL_MAX_CHARSET_LEN, g_mail_localinfo.runtime_log); + pitem->attach_filename[len] = '\0'; + pitem->filename_len = len; + } + p++; + break; + + case NOTMUL_ID: + case NOTMUL_DESCR: + if((ps = look_for_string_end(p, p_end, &p)) == NULL) //跳过这个字段 + { + return -1; + } + p++; + break; + + case NOTMUL_ENCODE: + if((ps = look_for_string_end(p, p_end, &p)) == NULL) + { + return -1; + } + if(strcmp_one_word_mesa("base64", "BASE64", 6, ps, p-ps)) + { + save_original_data(&pitem->mime_header, "\r\nContent-Transfer-Encoding:base64", 34, pitem->thread_id); + pitem->trans_enc = MIME_TRANSENC_BASE64; + } + else if(strcmp_one_word_mesa("quoted-printable", "QUOTED-PRINTABLE", 16, ps, p-ps)) + { + save_original_data(&pitem->mime_header, "\r\nContent-Transfer-Encoding:quoted-printable", 44, pitem->thread_id); + pitem->trans_enc = MIME_TRANSENC_QP; + } + else if(!strcmp_one_word_mesa("nil", "NIL", 3, ps, p-ps)) + { + save_original_data(&pitem->mime_header, "\r\nContent-Transfer-Encoding:", 28, pitem->thread_id); + save_original_data(&pitem->mime_header, ps, p-ps, pitem->thread_id); + } + p++; + break; + + case NOTMUL_SIZE://有可能是最后一个字段,取结束位置的时候要注意与上述区别 + if(is_extend==0)// || pitem->attach_filename != NULL) //非扩展类型后面的不处理 + { + i=NOTMUL_NUM; //结束循环 + break; + } + else if((p = (const char *)memchr(p, ' ', p_end-p))==NULL) //跳过这个字段 + { + return -1; + } + p++; + break; + + case NOTMUL_EVENLOP: + case NOTMUL_BODYSTRUCT: + if(type == CONT_TYPE_RFC822) //类型是MESSAGE/RFC822独有这2个字段;跳过这个字段; + { + if(*p == '(') + { + if((p=look_for_parenthesis_end(p+1, p_end))==NULL || *p!=')') + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log,RLOG_LV_FATAL, MAILIMAP4_MODULE, "message/rfc822 input error, not complete()."); + i=NOTMUL_NUM; + break; + } + } + else + { + p = (const char *)memchr(p, ' ', p_end-p); + if(p==NULL) + { + i=NOTMUL_NUM; + break; + } + } + p++; + break; + } + else //类型不是MESSAGE/RFC822,没有这2个字段 + { + i += 2; + } + //fall through + case NOTMUL_LINES: + if(type==CONT_TYPE_TEXT || type==CONT_TYPE_RFC822) //跳过这个字段 + { + p = (const char *)memchr(p, ' ', p_end-p); + if(p==NULL) + return -1; + break; + } + else //主类型不是TEXT,且类型不是MESSAGE/RFC822,没有该字段 + { + i++; + } + //fall through + case NOTMUL_MD5: + if((ps = look_for_string_end(p, p_end, &p)) == NULL) //跳过这个字段 + { + return -1; + } + p++; + break; + + case NOTMUL_DISPOSITION: + if(*p != '(') + { + i=NOTMUL_NUM; + break; + } + p++; + if((ps = look_for_string_end(p, p_end, &p)) == NULL) + { + i=NOTMUL_NUM; + break; + } + save_original_data(&pitem->mime_header, "\r\nContent-Disposition:", 22, pitem->thread_id); + save_original_data(&pitem->mime_header, ps, p-ps, pitem->thread_id); //例如得到类型ATTATCHMENT + while(pmime_header, ";filename=", 10, pitem->thread_id); + save_original_data(&pitem->mime_header, ps, len, pitem->thread_id); + + if(*ps == '"') + { + ps++; + len-=2; + } + if(pitem->attach_filename==NULL) + { + pitem->attach_filename = (char *)dictator_malloc(pitem->thread_id, len+1); + mime_header_decode(ps, len, pitem->attach_filename, &len, pitem->filename_charset, MAIL_MAX_CHARSET_LEN, g_mail_localinfo.runtime_log); + pitem->attach_filename[len] = '\0'; + pitem->filename_len = len; + } + } + break; + case NOTMUL_LANGUAGE: + case NOTMUL_LOCATION: + break; + default:return -1; + } + } + + save_original_data(&pitem->mime_header, "\r\n\r\n", 4, pitem->thread_id); + return 0; +} + +/********************************************************************* +函数名称:process_bodystructure_block +功能简介:解析一个块,加入哈希表 +输入参数: +输出参数: +返回值:0-正常;-1-失败;-2-用户名为空,无法拼接key值 +*********************************************************************/ +static int process_bodystructure_block(stMailPme* mailpme, const char *path, const char *block, int blocklen, long uid, int is_multi, int is_extend) +{ + stMailElem *pelem = mailpme->plug_mailinfo.pMailInfo->username; //TODO: S2C响应有时候有 + mime_item_t *pitem; + char key[256]; + long cb_ret; + + if(pelem->buflen==0) + return -2; + + snprintf(key, 256, "%s-%ld-%s", pelem->buf, uid, path); + + pitem = (mime_item_t *)MESA_htable_search(g_mail_localinfo.htable_imap[mailpme->thread_num], (unsigned char *)key, strlen(key)); + if(pitem == NULL) //应该不是很频繁出现BODYSTRUCTURE,暂时不放在CALL_BACK函数里了。 + { + pitem = (mime_item_t *)dictator_malloc(mailpme->thread_num, sizeof(mime_item_t)); + memset(pitem, 0, sizeof(mime_item_t)); + pitem->thread_id = mailpme->thread_num; + pitem->is_multi = is_multi; +#ifdef DBG_FULL + save_original_data(&pitem->mime_header, block, blocklen, pitem->thread_id); +#endif + if(is_multi==0) + { + block += 1; //去掉括号 + blocklen -= 2; + if(parse_not_multipart_line(pitem, block, blocklen, is_extend)!=0) //解析一个非multipart行 + { + char buf[512]; + memcpy(buf, block, blocklen>511?511:blocklen); + buf[blocklen>511?511:blocklen] = '\0'; + imap_free_node(pitem); + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log, RLOG_LV_FATAL, MAILIMAP4_MODULE, "parse_not_multipart_line failed, is_extend:%d, %d-%s", is_extend,blocklen, buf); + return -1; + } + } + else if(is_extend) + { + if(parse_multipart_line(pitem, block, blocklen)!=0)//解析一个multipart行 + { + char buf[512]; + memcpy(buf, block, blocklen>511?511:blocklen); + buf[blocklen>511?511:blocklen] = '\0'; + imap_free_node(pitem); + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log, RLOG_LV_FATAL, MAILIMAP4_MODULE, "parse_multipart_line failed, %d-%s", blocklen, buf); + return -1; + } + } + else //MULTIPART块且无扩展字段,不处理 + { + imap_free_node(pitem); + return 0; + } + + if((cb_ret = MESA_htable_add(g_mail_localinfo.htable_imap[mailpme->thread_num], (unsigned char *)key, strlen(key), pitem)) < 0) + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log, RLOG_LV_DEBUG, MAILIMAP4_MODULE, "in func process_bodystructure_block, MESA_htable_add failed: %d.", cb_ret); + imap_free_node(pitem); + return 0; + } + else if(g_mail_localinfo.stat_trig) + { + FS_operate(g_mail_localinfo.fsstat_handle, g_mail_localinfo.stat_id[STAT_ID_HTABLE_NUM], 0, FS_OP_ADD, 1); + } + } + + return 0; +} + +/********************************************************************* +函数名称:parse_bodystructure +功能简介:处理服务器body/bodystructure响应,提取MIME块及其对应的块号,加入哈希表 +输入参数: +输出参数: +返回值:正常返回处理的字节数;<0-失败 +*********************************************************************/ +static int parse_bodystructure(stMailPme* mailpme, const char *bodystruct, int bodylen, long uid, int is_extend) +{ + int single=0, depth=0;//depth:总深度 + long len; + const char *pc=bodystruct, *p_end=bodystruct+bodylen, *pstart=NULL; + int state=STAT_BLOCK_IDLE, pre_state=STAT_BLOCK_IDLE; + char path[128]="0", *ptmp; + MESA_lqueue_head lq_head; + + lq_head = MESA_lqueue_create(0, 1024); + if(lq_head==NULL) + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log,RLOG_LV_FATAL, MAILIMAP4_MODULE, "MESA_lqueue_create error."); + return -1; + } + + while(pc < p_end) + { + if(*pc == ' ') + { + pc++; + continue; + } + + switch(state) + { + case STAT_BLOCK_IDLE: + if(*pc != '(') + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log,RLOG_LV_FATAL, MAILIMAP4_MODULE, "input error1."); + MESA_lqueue_destroy(lq_head, NULL, NULL); + return -1; + } + pre_state = state; + state = STAT_BLOCK_START; + pstart=pc; //对于单一块需要 + pc++; + depth++; //每遇到一个'(',深度加1,下同; + while(pcplug_mailinfo.pMailInfo->username->buflen==0) + return -1; + snprintf(key, 256, "%s-%ld-%s", mailpme->plug_mailinfo.pMailInfo->username->buf, uid, block_seq); + + pitem = (mime_item_t *)MESA_htable_search(g_mail_localinfo.htable_imap[mailpme->thread_num], (unsigned char *)key, strlen(key)); + if(pitem != NULL) + { + if(pitem->is_multi) + { + if(pitem->boundary) + add_boundary(pitem->boundary, pitem->bound_len, &mailpme->mimeinfo->b_header, mailpme->thread_num); + save_original_data(&mailpme->pOriginalData, (const char *)pitem->mime_header.buf, pitem->mime_header.len, mailpme->thread_num); + } + else + { + save_original_data(&mailpme->pOriginalData, (const char *)pitem->mime_header.buf, pitem->mime_header.len, mailpme->thread_num); + mailpme->mimeinfo->TransferEnc = pitem->trans_enc; + if((len = strlen(pitem->charset))>0) + { + memcpy(mailpme->mimeinfo->charset, pitem->charset, len); + mailpme->mimeinfo->charset[len] = '\0'; + } + if(pitem->filename_len>0 && (g_mail_localinfo.protocol_flag & MAIL_ATTACH_NAME)) + { + mailpme->MailInfoState = MAIL_GET_FILENAME; + len = call_biz_per_line(mailpme, MAIL_ATTACH_NAME, pitem->attach_filename, pitem->filename_len, pitem->filename_charset, a_tcp, raw_pkt); + if(len & PROT_STATE_DROPME) + { + drop_set_value(mailpme); + return IMAP_CMD_DROP; + } + } + } + + *((char *)pitem->mime_header.buf + pitem->mime_header.len) = '\0'; //TODO + return 0; + } + else + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log,RLOG_LV_INFO, MAILIMAP4_MODULE, "MESA_htable_search_cb %s, no result found.", key); + } + + return -1; +} + +/********************************************************************* +函数名称:imap4_reset_mailpme +功能简介:如果来了新块则进行复位 +输入参数:uid新来的块的uid号 +输出参数: +返回值: +*********************************************************************/ +static int imap4_reset_mailpme(stMailPme* mailpme, long uid, struct streaminfo *a_tcp, const void *raw_pkt) +{ + mailpme->pre_uid = mailpme->uid; + mailpme->uid = uid; + + if(mailpme->pending_state==0 && mailpme->uid != mailpme->pre_uid) + { + if(mailpme->is_drop) + mail_reset_mailpme_noclose(mailpme, a_tcp, raw_pkt); + else + mail_reset_mailpme(mailpme, a_tcp, raw_pkt); + } + mailpme->pending_state = 0; + + return 0; +} + +/********************************************************************* +函数名称:prepare_parse_bodystructure +功能简介:准备进行BODYSTRUCTURE解析,先判断是否完整,如果不完整,缓存 + 原始数据,等待下一个包进行拼接后再重复上述操作。 +输入参数: + start:mail_get_tcpdata函数输出的数据指针output_line + linelen: mail_get_tcpdata函数输出的数据长度outputlen + src: BODYSTRUCTURE结构开始指针 + srclen: src长度 +输出参数: +返回值: 0-正常;其他-数据不完整,等待下次 +*********************************************************************/ +static int prepare_parse_bodystructure(stMailPme* mailpme, const char *start, int linelen, const char *src, int srclen, long uid, int is_extend) +{ + const char *ptmp, *srcend=src+srclen; + int ret; + + if(!strncmp(srcend-3, "}\r\n", 3) || !strncmp(srcend-2, "}\n", 2)) //不完整,后续可能还有;如果没有也就是解析失败而已 + { + ptmp = (const char *)memrchr(src, '{', srclen-3); + if(ptmp != NULL) + { + linelen = ptmp - start; + if(start != mailpme->pDataLineBuf.buf) + { + if(buf_cache_check_size(&mailpme->pDataLineBuf, linelen, mailpme->thread_num)<0) + return IMAP_CMD_ANS; + memcpy(mailpme->pDataLineBuf.buf, start, linelen); + } + mailpme->pDataLineBuf.len = linelen; + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; //后续有可能是折叠行,且后续也是邮件头部(如果有的话) + return IMAP_CMD_FETCH_ANS; + } + } + + ret = parse_bodystructure(mailpme, src, srclen, uid, is_extend); //解析失败也正常返回,丢弃本次BODYSTRUCTURE + + if(ret>0 && (srcend -(src+ret) < 5)) //the second 5 is lengh of "body[" + return IMAP_CMD_FETCH_ANS; + + return 0; +} + +/********************************************************************* +函数名称:imap_is_wanted_line +功能简介:根据输入的数据的特征,输入数据长度和会话处理状态,判断并返回 + imap应答信息类型编号 +输入参数: + apply_count:数据包的大小 + a_imap_line :输入邮件数据,不包含"\r\n" + mailpme:邮件数据结构 +输出参数:如果检测到邮件内容开始,则更新mailpme->EmlAnalyseState +返回值: 对应的命令的编码 +*********************************************************************/ +static int imap_is_wanted_line(const char *a_imap_line,int linelen, struct tcpdetail *ptcpdetail, stMailPme* mailpme, struct streaminfo *a_tcp, const void *raw_pkt) +{ + const char *p=a_imap_line, *p_end=a_imap_line+linelen, *pp=NULL; + int tmplen, retCMD=0; + + if(linelen>0 && (p=(char *)memchr(a_imap_line, ' ', (linelen>RESPONSE_FETCH_MAX_LEN)?RESPONSE_FETCH_MAX_LEN:linelen))) + p++; + if(p!=NULL && (pp=(char *)memchr(p,' ',p_end-p))) //提取fetch response中的关键词,如"fetch (body[header" + pp++; + + //以下是对邮件开始的判断 + if(pp!=NULL && (pp+6)= (MAX_ORDER_LINE_SIZE-1))?(MAX_ORDER_LINE_SIZE-1):(p_end-pp); + memcpy(templine, pp, tmplen); + templine[tmplen]='\0'; + + ptmp2=mesa_memncasemem(templine, tmplen, "UID ", 4); + if(ptmp2==NULL || sscanf(ptmp2+3, "%*[ ]%ld%c", &uid, &ch)!=2 || ch!=' ') //TODO: 不是字符串 + { + uid = 0; + } + else if((ptmp1=mesa_memncasemem(pp, p_end-pp, "BODYSTRUCTURE (", 15))!=NULL && mailpme->plug_mailinfo.pMailInfo->username->buflen>0) //含有BASE64编码字符,所以不可以变成小写 + { + //要保留"BODYSTRUCTURE ("中的'(',因为有单一块的情况 + if((tmp = prepare_parse_bodystructure(mailpme, a_imap_line, linelen, ptmp1+14, p_end-ptmp1-14, uid, 1)) != 0) + return tmp; + } + else if((ptmp1=mesa_memncasemem(pp, p_end-pp, "BODY (", 6))!=NULL && mailpme->plug_mailinfo.pMailInfo->username->buflen>0) + { + if((tmp = prepare_parse_bodystructure(mailpme, a_imap_line, linelen, ptmp1+5, p_end-ptmp1-5, uid, 0)) != 0) + return tmp; + } + + //将输入变成小写 + for(tmp=0;tmpis_continue = 0; + imap4_reset_mailpme(mailpme, uid, a_tcp, raw_pkt); + mailpme->EmlAnalyseState=MAIL_STAT_HEADER; +#ifdef DBG_FULL + return EML_DATA_HEAD_IN; +#else + return IMAP_CMD_FETCH_ANS; +#endif + } + //对完整邮件开始的判定,一个空的块声明指向整个邮件,包括头部。 + else if((ptmp1!= NULL && *(ptmp1+5)==']' && *(ptmp1+6)==' ') || (ptmp2!=NULL && *(ptmp2+10)==']' && *(ptmp2+11)==' ') || strstr(templine, "rfc822 {")) + { + mailpme->is_continue = 0; + imap4_reset_mailpme(mailpme, uid, a_tcp, raw_pkt); + mailpme->EmlAnalyseState=MAIL_STAT_HEADER; +#ifdef DBG_FULL + return EML_DATA_HEAD_IN; +#else + return IMAP_CMD_FETCH_ANS; +#endif + } + //对邮件一个片段进行判定,如C2S:BODY.PEEK[]<262144.262144>; S2C:body[]<262144> + else if((ptmp1!= NULL && sscanf(ptmp1+5, "]<%[0-9]%c", block_seq, &ch)==2 && ch=='>') || (ptmp2!=NULL && sscanf(ptmp2+10, "]<%[0-9]%c", block_seq, &ch)==2 && ch=='>')) + { + mailpme->is_continue = 1; + imap4_reset_mailpme(mailpme, uid, a_tcp, raw_pkt); + + if(!strcmp(block_seq, "0")) + { + mailpme->EmlAnalyseState=MAIL_STAT_HEADER; +#ifdef DBG_FULL + return EML_DATA_HEAD_IN; +#else + return IMAP_CMD_FETCH_ANS; +#endif + } + else + { + return IMAP_CMD_DATA_CONTINUE; + } + } + //对邮件体body开始的判定,是邮件体的所有部分,不包括邮件头 + else if((ptmp1!=NULL && !strncmp(ptmp1+5, "text", 4)) || (ptmp2!=NULL && !strncmp(ptmp2+10, "text", 4)) || strstr(templine, "rfc822.text")) + { + mailpme->is_continue = 0; + imap4_reset_mailpme(mailpme, uid, a_tcp, raw_pkt); + + tmp = get_block_mime_header(mailpme, uid, "0", a_tcp, raw_pkt); + if(tmp<0) + { + return IMAP_CMD_FETCH_ANS; + } + else if(tmp==IMAP_CMD_DROP) + { + return IMAP_CMD_DROP; + } + return IMAP_CMD_DATA_BORDER; + } + //邮件的一个块的判定,如body[1.2];block_seq存储其块号 + else if((ptmp1!=NULL && sscanf(ptmp1+5, "%[0-9.]%c", block_seq, &ch)==2 && ch==']') || (ptmp2!=NULL && sscanf(ptmp2+10, "%[0-9.]%c", block_seq, &ch)==2 && ch==']')) + { + mailpme->is_continue = 0; + imap4_reset_mailpme(mailpme, uid, a_tcp, raw_pkt); + + tmp = get_block_mime_header(mailpme, uid, block_seq, a_tcp, raw_pkt); + if(tmp<0) + { + return IMAP_CMD_FETCH_ANS; + } + else if(tmp==IMAP_CMD_DROP) + { + return IMAP_CMD_DROP; + } + return IMAP_CMD_DATA_BORDER; + } + } + + if(mailpme->maybe_end == 1) + { + mailpme->maybe_end = 0; + if(p && ((strncmp_one_word_mesa("ok fetch ", "OK FETCH ", 9, p, p_end-p))||(strncmp_one_word_mesa("ok uid fetch ", "OK UID FETCH ", 13, p, p_end-p)))) + { + return IMAP_CMD_DATA_END; + } + else + { + return IMAP_CMD_NOT_END; + } + } + else if((linelen==3 && !strncmp(a_imap_line, ")\r\n", 3)) || (linelen==2 && !strncmp(a_imap_line, ")\n", 2))) + { + mailpme->maybe_end = 1; + mailpme->obtain_line = 1; //有可能若干个头部一起传,')'之后紧跟FETCH;后续正文中的空行被MIME略去了 + return IMAP_CMD_MAYBE_END; + } + + if(((!strncmp(a_imap_line, "* ", 2) || !strncmp(a_imap_line, "+ ", 2)) || + (p && linelenEmlAnalyseState==MAIL_STAT_INIT || ptcpdetail->datalen < IMAP_OK_CMD_LEN) + return IMAP_CMD_ANS; + else + retCMD = 1; + } + + if((mailpme->EmlAnalyseState!=MAIL_STAT_INIT) && + p && ((strncmp_one_word_mesa("ok fetch completed", "OK FETCH COMPLETED", 18, p, p_end-p))||(strncmp_one_word_mesa("ok uid fetch completed", "OK UID FETCH COMPLETED", 22, p, p_end-p)))) + { + return IMAP_CMD_DATA_END; + } + else if(retCMD==1) + { + return IMAP_CMD_ANS; + } + + //应该在判断IMAP_CMD_DATA_END之后; + //否则会话不正常结束,维持的是DROP的时候的EmlAnalyseState状态,下一个会话要求获取行 + if(mailpme->is_drop) + return IMAP_CMD_DROP; + + retCMD=mail_analy_emlheader(a_imap_line,linelen,mailpme->EmlAnalyseState); + if(retCMD!=EML_UNKNOWN) + { + return retCMD; + } + + return IMAP_CMD_UNKNOWN; +} + +/********************************************************************* +函数名称:imap_process_s2c +功能简介:处理邮件IMAP会话S2C流上的一个数据包,每个包调用1次该函数 +输入参数: + tcp_stream:tcp会话流 + mailpme:邮件处理结构体 +输出参数:无 +返回值:业务插件的返回值 +*********************************************************************/ +static int imap_process_s2c(stMailPme* mailpme, struct streaminfo *a_stream, const void *raw_pkt) +{ + int rec=PROT_STATE_GIVEME, needfolding=0; + int line_status = MAIL_FOLD_LINE_START; + char *output_line = NULL; + int outputlen=0; + struct tcpdetail *ptcpdetail = a_stream->ptcpdetail; + + memset(&mailpme->session_info, 0, sizeof(stSessionInfo)); + mailpme->session_info.plugid = g_mail_localinfo.plugid; + mailpme->session_info.session_state = SESSION_STATE_DATA; + mailpme->session_info.app_info = &mailpme->plug_mailinfo; + + while(1) + { + if(mailpme->EmlAnalyseState == MAIL_STAT_HEADER || (mailpme->EmlAnalyseState == MAIL_STAT_BODY && mailpme->mimeinfo->text_begin==0)) + needfolding=1; + else + needfolding=0; + + line_status = mail_get_tcpdata(ptcpdetail, mailpme, needfolding, &output_line, &outputlen); + if(line_status == MAIL_INPUT_END) + break; + else if(line_status == MAIL_GETLINE_ERR) + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log,RLOG_LV_FATAL, MAILIMAP4_MODULE, "imap get a line error!!! drop now!\n"); + return PROT_STATE_DROPME; + } + mailpme->pDataLineBuf.len = 0; + + line_status = imap_is_wanted_line(output_line, outputlen, ptcpdetail, mailpme, a_stream, raw_pkt); + + switch(line_status) + { + case IMAP_CMD_DROP: + case IMAP_CMD_FETCH_ANS: + case IMAP_CMD_UNKNOWN: + case IMAP_CMD_MAYBE_END: + case IMAP_CMD_DATA_CONTINUE: //按字节获取邮件,忽略FETCH响应头部 + break; + + case IMAP_CMD_DATA_BORDER: //本次TCP数据不是原始邮件,不进行缓存 + mailpme->EmlAnalyseState = MAIL_STAT_BODY; + body_update_text_begin(mailpme->mimeinfo); + break; + + case IMAP_CMD_ANS: //结束标记和命令丢失,可以借助响应来终止会话 + case EML_DATA_END: + case IMAP_CMD_DATA_END: + //是CONTINUE块就不用复位,下一次沿用本次的状态;下一个包只能获取第1行; + //如果下一次是新的块,则在判断是新块的地方进行复位。 + if(mailpme->is_continue) + mailpme->obtain_line = 1; + //如果不是CONTINUE块,要为下一个会话准备SESSION_PENDING,同时及时尽早结束本块; + else if(mailpme->pending_state==0) + { + if(mailpme->is_drop) + mail_reset_mailpme_noclose(mailpme, a_stream, raw_pkt); + else + mail_reset_mailpme(mailpme, a_stream, raw_pkt); + + //邮件请求块完毕,紧接着下一个邮件头,响应也有可能是折叠行(BODYSTRUCTURE) + mailpme->EmlAnalyseState = MAIL_STAT_HEADER; + } + break; + + case IMAP_CMD_NOT_END: + if(mailpme->is_drop) + break; + line_status = mailpme->pre_opsate; + rec = process_mail_data(line_status, (char *)")\r\n", 3, mailpme, a_stream, raw_pkt); + if(rec & PROT_STATE_DROPME) + { + drop_set_value(mailpme); + break; + } + //fall through + default: + if(mailpme->is_drop) + break; + mailpme->pre_opsate = line_status; + rec = process_mail_data(line_status, output_line, outputlen, mailpme, a_stream, raw_pkt); + if(rec & PROT_STATE_DROPME) + { + drop_set_value(mailpme); + } + break; + } + } + mailpme->iDataPointer = 0; + + //call biz per packet + if(!g_mail_localinfo.callback_per_line && mailpme->plug_mailinfo.elem_num>0) + { + rec = call_biz_per_packet(mailpme, a_stream, raw_pkt); + if(rec & PROT_STATE_DROPME) + { + drop_set_value(mailpme); + } + } + + return (rec&PROT_STATE_DROPPKT)?PROT_STATE_DROPPKT:PROT_STATE_GIVEME; +} + + +/********************************************************************* +函数名称:pop3_process_c2s +功能简介:处理邮件IMAP会话C2S流上的一个数据包,每个包调用1次该函数 +输入参数: + tcp_stream:tcp会话流 + mailpme:邮件处理结构体 +输出参数:无 +返回值:业务插件的返回值 +*********************************************************************/ +static int imap_process_c2s(stMailPme* mailpme, struct streaminfo *a_stream, const void *raw_pkt) +{ + //只处理用户名密码,只有最初状态有 + if(mailpme->EmlAnalyseState!=MAIL_STAT_INIT || mailpme->is_drop) + return PROT_STATE_GIVEME; + + int rec=PROT_STATE_GIVEME; + int line_status = MAIL_FOLD_LINE_START; + char *output_line = NULL; + int outputlen=0, matchlen; + struct tcpdetail *ptcpdetail = a_stream->ptcpdetail; + char *p, *p_end; + + //换了方向,上个流缓存的数据清掉.TODO + if(mailpme->pre_dir != a_stream->curdir) + { + mailpme->pre_dir = a_stream->curdir; + mailpme->pDataLineBuf.len = 0; + } + + while(1) + { + line_status = mail_get_atcpline_c2s(ptcpdetail, mailpme, &output_line, &outputlen); + if(line_status == MAIL_INPUT_END) + break; + + if(line_status == MAIL_GETLINE_ERR) + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log,RLOG_LV_FATAL, MAILIMAP4_MODULE, "imap get a line error!!! drop now!\n"); + return PROT_STATE_DROPME; + } + mailpme->pDataLineBuf.len = 0; + p_end = output_line+outputlen; + + if(outputlen>0 && (p=(char *)memchr(output_line, ' ', outputlen))) + { + char *pp1, *pp2; + p++; + if((matchlen=strncmp_two_word_mesa("authenticate", "AUTHENTICATE", 12, "xoauth2", "XOAUTH2", 7, p, p_end-p))!=0) + { + mailpme->iDataPointer = 0; + return oauth2_username_biz(mailpme, p+matchlen, p_end-p-matchlen, a_stream, raw_pkt); + } + if(strncmp_one_word_mesa("starttls", "STARTTLS", 8, p, p_end-p)!=0) + { + if(g_mail_localinfo.protocol_flag&MAIL_STARTTLS_CMD) + { + rec = call_biz_per_line(mailpme, MAIL_STARTTLS_CMD, p, 8, NULL, a_stream, raw_pkt);// 8 = strlen("STARTTLS") + if(rec & PROT_STATE_DROPME) + { + drop_set_value(mailpme); + } + return PROT_STATE_DROPME; + } + } + if(strncmp_one_word_mesa("login", "LOGIN", 5, p, p_end-p)==0) + { + mailpme->iDataPointer = 0; + return PROT_STATE_GIVEME; + } + p += 6; //length of "login " + while(*p==' ') + p++; + if(p>=p_end) + { + mailpme->iDataPointer = 0; + return PROT_STATE_GIVEME; + } + if(*p == '"') + pp1 = (char *)memchr(p+1, '"', p_end - p - 1); + else + pp1 = (char *)memchr(p, ' ', p_end - p); + if(pp1 == NULL) // ""这样的用户名 + { + mailpme->iDataPointer = 0; + return PROT_STATE_GIVEME; + } + pp2 = pp1+1; + + if(*p == '"') + p += 1; + if(*(pp1-1)=='"') + pp1 -= 1; + + while(*pp2 == ' ') + pp2++; + if(*pp2=='"') + pp2 += 1; + if(*(p_end-1)=='"') + p_end -= 1; + + if(pp1<=p || pp2>=p_end) + { + mailpme->iDataPointer = 0; + return PROT_STATE_GIVEME; + } + + mailpme->plug_mailinfo.pMailInfo->password->buflen = 0; + mailpme->plug_mailinfo.pMailInfo->username->buflen = 0; + mailpme->session_info.session_state = SESSION_STATE_DATA; + mail_save_mail_elem(p, pp1-p, mailpme->plug_mailinfo.pMailInfo->username, mailpme->thread_num); + if(g_mail_localinfo.protocol_flag&MAIL_USERNAME) + { + rec = call_biz_per_line(mailpme, MAIL_USERNAME, p, pp1-p, NULL, a_stream, raw_pkt); + if(rec & PROT_STATE_DROPME) + { + drop_set_value(mailpme); + break; + } + } + + mail_save_mail_elem(pp2, p_end-pp2, mailpme->plug_mailinfo.pMailInfo->password, mailpme->thread_num); + if(g_mail_localinfo.protocol_flag&MAIL_PASSWARD) + { + rec = call_biz_per_line(mailpme, MAIL_PASSWARD, pp2, p_end-pp2, NULL, a_stream, raw_pkt); + if(rec & PROT_STATE_DROPME) + { + drop_set_value(mailpme); + break; + } + } + } + + if(rec&PROT_STATE_DROPPKT) + { + break; + } + } + mailpme->iDataPointer = 0; + + return (rec&PROT_STATE_DROPPKT)?PROT_STATE_DROPPKT:PROT_STATE_GIVEME; +} + +/**************************************************************************** +函数名:line_is_imap_cmd() +功能:判断C->S方向,客户端命令是否是IMAP4的命令 +输入: +输出: +返回值: 0-不是;1-是; +*****************************************************************************/ +static int line_is_imap_cmd(char *data, int datalen) +{ + if(strncmp_one_word_mesa("capability", "CAPABILITY", 10, data, datalen)) + return 1; + if(strncmp_one_word_mesa("authenticate ", "AUTHENTICATE ", 13, data, datalen)) + return 1; + if(strncmp_one_word_mesa("login ", "LOGIN ", 6, data, datalen)) + return 1; + if(strncmp_one_word_mesa("noop", "NOOP", 4, data, datalen)) + return 1; + if(strncmp_one_word_mesa("select ", "SELECT ", 7, data, datalen)) + return 1; + if(strncmp_one_word_mesa("status ", "STATUS ", 7, data, datalen)) + return 1; + if(strncmp_one_word_mesa("uid ", "UID ", 4, data, datalen)) + return 1; + if(strncmp_one_word_mesa("fetch ", "FETCH ", 6, data, datalen)) + return 1; + if(strncmp_one_word_mesa("store ", "STORE ", 6, data, datalen)) + return 1; + if(strncmp_one_word_mesa("append ", "APPEND ", 7, data, datalen)) + return 1; + if(strncmp_one_word_mesa("starttls ", "STARTTLS ", 9, data, datalen)) + return 1; + + return 0; +} + +/**************************************************************************** +函数名:imap4_entry_fun() +功能:IMAP入口函数 +输入:stream.h +输出:stream.h +返回值: APP_STATE_GIVEME/APP_STATE_DROPME/APP_STATE_DROPPKT +*****************************************************************************/ +char imap4_entry_fun(struct streaminfo *a_tcp, void **pme, int thread_seq,const void *raw_pkt) +{ + int rec = PROT_STATE_GIVEME; + stMailPme* mailpme = (stMailPme*)(*(pme)); + struct tcpdetail *ptcpdetail = a_tcp->ptcpdetail; + + switch(a_tcp->opstate) + { + case OP_STATE_PENDING: + if(IMAP_PROTOCOL == mail_identify_imap(a_tcp, (char *)ptcpdetail->pdata, ptcpdetail->datalen, thread_seq)) + { + rec = mail_init_mailpme(&mailpme,MAIL_SERVICE_IMAP4,a_tcp,raw_pkt); + if(rec < 0) + { + MESA_HANDLE_RUNTIME_LOG(g_mail_localinfo.runtime_log,RLOG_LV_FATAL, MAILIMAP4_MODULE, "mail_init_mailpme() error!"); + return APP_STATE_DROPME; + } + *pme = (void*)mailpme; + project_req_add_uint(a_tcp, g_mail_localinfo.proto_identify_id, IMAP_PROTOCOL); + } + else + { + return APP_STATE_DROPME; + } + //no break here. + + case OP_STATE_DATA: + if(a_tcp->curdir == DIR_S2C) + { + //if(ptcpdetail->lostlen>0) + //{ + // mailpme->s_total_loss += ptcpdetail->lostlen; + //} + + rec = imap_process_s2c(mailpme, a_tcp, raw_pkt); + if(rec & PROT_STATE_DROPME) + { + mail_clear_mailpme(mailpme, a_tcp, raw_pkt); + return APP_STATE_DROPME; + } + } + else if(a_tcp->curdir == DIR_C2S) + { + rec = imap_process_c2s(mailpme, a_tcp, raw_pkt); + if(rec & PROT_STATE_DROPME) + { + mail_clear_mailpme(mailpme, a_tcp, raw_pkt); + return APP_STATE_DROPME; + } + } + assert(mailpme->thread_num==a_tcp->threadnum); + break; + + case OP_STATE_CLOSE: + if(g_mail_localinfo.stat_trig) + { + FS_operate(g_mail_localinfo.fsstat_handle, g_mail_localinfo.stat_id[STAT_ID_MAIL_PKTS], 0, FS_OP_ADD, a_tcp->ptcpdetail->serverpktnum+a_tcp->ptcpdetail->clientpktnum); + FS_operate(g_mail_localinfo.fsstat_handle, g_mail_localinfo.stat_id[STAT_ID_MAIL_BYTES], 0, FS_OP_ADD, a_tcp->ptcpdetail->serverbytes+a_tcp->ptcpdetail->clientbytes); + } + mail_add_proto_tag(g_mail_localinfo.proto_tag_id, a_tcp, "MAIL", strlen("MAIL")); + mail_clear_mailpme(mailpme,a_tcp,raw_pkt); + return APP_STATE_DROPME; + + default: + return APP_STATE_DROPME; + } + + if(rec & PROT_STATE_DROPME) + return APP_STATE_DROPME; + else if(rec & PROT_STATE_DROPPKT) + return APP_STATE_DROPPKT; + else + return APP_STATE_GIVEME; +} + + +int imap_process(stMailPme* mailpme, const char *payload, size_t payload, int is_c2s) +{ + +} + +int imap_identify(const char *payload, size_t payload_len, int is_c2s) +{ + char *p = NULL; + int left_len = 0; + + if (payload == NULL || payload_len < 2) { + return 0; + } + + p = (char *)memchr(payload, ' ', payload_len); + if (p == NULL) { + return 0; + } + + left_len = payload_len - (p - payload); + if (left_len > 1) { + p++; + left_len--; + } + + if (is_c2s) { + if (0 == strncmp(payload, "* ", strlen("* ")) || + 0 == strncmp(payload, "+ ", strlen("+ ")) || + 0 == strncmp(p, "OK", strlen("OK")) || + 0 == strncmp(p, "NO", strlen("NO")) || + (left_len >= 3 && 0 == strncmp(p, "BAD", strlen("BAD"))) || + (left_len >=6 && 0 == strncmp(p, "EXISTS", strlen("EXISTS")))) { + return 1; + } + } else { + if (line_is_imap_cmd(p, left_len)) { + return 1; + } + } + return 0; +} + +const char* g_imap_command[] = { + "unknown", + "ehlo", + "helo", + "auth", + "starttls", + "mail", + "send", + "soml", + "saml", + "rcpt", + "data", + "vrfy", + "expn", + "noop", + "rset", + "quit", +}; + +static enum IMAP_COMMAND imap_str2command(const char *payload, size_t payload_len) +{ + int i; + if (payload == NULL || payload_len == 0) { + return IMAP_COMMAND_UNKNOWN; + } + for(i = 1; i < IMAP_COMMAND_MAX; i++) { + if(0 == SAFE_STRNCASECMP(payload, payload_len, g_imap_command[i], strlen(g_imap_command[i]))) { + break; + } + } + if (i == IMAP_COMMAND_MAX) { + return IMAP_COMMAND_UNKNOWN; + } + return (enum IMAP_COMMAND)i; +} + +static int is_imap_cmd(const char *data, int datalen) +{ + if (IMAP_COMMAND_UNKNOWN == imap_str2command(data, datalen)) { + return 0; + } + return 1; +} + +static int is_imap_res(const char *data, size_t datalen) +{ + if (datalen >= strlen(IMAP_STR_220) && 0 == strncmp(data, IMAP_STR_220, strlen(IMAP_STR_220)) { + return 1; + } + return 0; +} + +int imap_identify(const char *payload, size_t payload_len, int is_c2s) +{ + if (payload == NULL || payload_len < 4) { + return 0; + } + + if (is_c2s) { + if (is_imap_cmd(payload, payload_len)) { + return 1; + } + } else { + if (is_imap_res(payload, payload_len)) { + return 1; + } + } + return 0; +} + +static int imap_parse_authentication_xoauth2(struct imap_authentication *auth, const char *line_start, size_t line_len) +{ + int decoded; + char decode_buf[512]; + char *username; + size_t username_len; + + decoded = Base64_DecodeBlock((unsigned char *)line_start, line_len, (unsigned char *)decode_buf, sizeof(decode_buf)); + if (decoded < 0) { + return -1; + } + + if (0 != mail_xoauth2_get_username((const char *)decode_buf, decoded, &username, &username_len)) { + return -1; + } + + if (username_len >= sizeof(auth->username)) { + return -1; + } + + memcpy(auth->username, username, username_len); + auth->username_len = username_len; + + auth->state = IMAP_AUTH_DONE; + return 0; +} + +static int imap_parse_authentication_plain(struct imap_authentication *auth, const char *line_start, size_t line_len) { + int decoded; + char decode_buf[512]; + char *current_pos; + char *username_start = NULL; + char *password_start = NULL; + size_t remaining_len = decoded; + + decoded = Base64_DecodeBlock((unsigned char *)line_start, line_len, (unsigned char *)decode_buf, sizeof(decode_buf)); + if (decoded < 0) { + return -1; + } + + // \0\0\0 + current_pos = decode_buf; + + while (remaining_len > 0 && *current_pos == '\0') { + current_pos++; + remaining_len--; + } + + username_start = current_pos; + while (remaining_len > 0 && *current_pos != '\0') { + current_pos++; + remaining_len--; + } + + if (remaining_len > 0 && *current_pos == '\0') { + current_pos++; + remaining_len--; + } + + password_start = current_pos; + + size_t username_len = username_start ? (size_t)(current_pos - username_start - 1) : 0; + size_t password_len = password_start ? (size_t)(decode_buf + decoded - password_start - 1) : 0; + + if (username_len >= IMAP_USERNAME_LEN_MAX || password_len >= IMAP_PASSWORD_LEN_MAX) { + return -1; + } + + if (username_len > 0) { + memcpy(auth->username, username_start, username_len); + auth->username_len = username_len; + } else { + auth->username_len = 0; + } + + if (password_len > 0) { + memcpy(auth->password, password_start, password_len); + auth->password_len = password_len; + } else { + auth->password_len = 0; + } + + auth->state = IMAP_AUTH_DONE; + return 0; +} + +static int imap_parse_authentication_login(struct imap_authentication *auth, const char *line_start, size_t line_len) +{ + int decoded; + char decode_buf[512]; + + decoded = Base64_DecodeBlock((unsigned char *)line_start, line_len, (unsigned char *)decode_buf, sizeof(decode_buf)); + if (decoded < 0) { + return -1; + } + + if (auth->state == IMAP_AUTH_WAITING_USERNAME) { + auth->state = IMAP_AUTH_WAITING_PASSWORD; + + if (decoded > sizeof(auth->username)) { + return -1; + } + memcpy(auth->username, decode_buf, decoded); + return 0; + } + + if (auth->state == IMAP_AUTH_WAITING_PASSWORD) { + auth->state = IMAP_AUTH_DONE; + + if (decoded > IMAP_PASSWORD_LEN_MAX) { + return -1; + } + memcpy(auth->password, decode_buf, decoded); + return 0; + } + + return 0; +} + +static enum IMAP_AUTH_STATE imap_parse_authentication(struct imap_authentication *auth, enum IMAP_AUTH_TYPE type, const char *line_start, size_t line_len) +{ + int ret = 0; + + if (auth->state == IMAP_AUTH_DONE) { + return IMAP_AUTH_DONE; + } + + switch (type) { + case IMAP_AUTH_TYPE_LOGIN: + ret = imap_parse_authentication_login(auth, line_start, line_len); + case IMAP_AUTH_TYPE_PLAIN: + ret = imap_parse_authentication_plain(auth, line_start, line_len); + case IMAP_AUTH_TYPE_XOAUTH2: + ret =imap_parse_authentication_xoauth2(auth, line_start, line_len); + default: + break; + } + + if (ret != 0) { + return IMAP_AUTH_DONE; + } + + return auth->state;; +} + +int imap_parse_command_auth(struct imap_authentication *auth, struct imap_command *imap_cmd) +{ + if (0 == SAFE_STRNCASECMP(imap_cmd.arg, imap_cmd.arg_len, "login", strlen("login"))) { + auth->type = IMAP_AUTH_TYPE_LOGIN; + auth->state = IMAP_AUTH_WAITING_USERNAME; + } + if (0 == SAFE_STRNCASECMP(imap_cmd.arg, imap_cmd.arg_len, "plain", strlen("plain"))) { + auth->type = IMAP_AUTH_TYPE_PLAIN; + auth->state = IMAP_AUTH_WAITING_USERNAME; + } + if (0 == SAFE_STRNCASECMP(imap_cmd.arg, imap_cmd.arg_len, "xoauth2", strlen("xoauth2"))) { + auth->type = IMAP_AUTH_TYPE_XOAUTH2; + auth->state = IMAP_AUTH_WAITING_USERNAME; + } + return 0; +} + +int imap_parse_command(struct imap_command *imap_cmd, const char *line_start, size_t line_len) +{ + int ret; + struct iovec parts[IMAP_COMMAND_LINE_PART_NUM]; + + memset(imap_cmd, 0 , sizeof(struct imap_command)); + + ret = strnsplit(parts, sizeof(parts)/sizeof(parts[0]), ' ', line_start, line_len); + if (ret <= 0 || ret > IMAP_COMMAND_LINE_PART_NUM) { + return -1; + } + + imap_cmd->cmd = imap_str2command(line_start, line_len); + imap_cmd->cmd_line = line_start; + imap_cmd->cmd_line_len = line_len; + + if (ret == IMAP_COMMAND_LINE_PART_NUM) { + imap_cmd->arg = parts[IMAP_COMMAND_LINE_PART_ARG].iov_base; + imap_cmd->arg_len = parts[IMAP_COMMAND_LINE_PART_ARG].iov_len; + } + + return 0; +} + +static int imap_parser_publish_command(struct imap_parser *parser, struct session *sess, size_t mail_seq, struct imap_command *imap_cmd) +{ + int ret; + struct mq_runtime *runtime; + struct mail_message *msg; + + runtime = module_manager_get_mq_runtime(parser->mail_env_ref->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = MAIL_PROTOCOL_IMAP; + msg->command->cmd = MAIL_CMD_OTHER; + msg->command->arg = imap_cmd->arg; + msg->command->arg_len = imap_cmd->arg_len; + msg->command->cmd_line = imap_cmd->cmd_line; + msg->command->cmd_line_len = imap_cmd->cmd_line_len; + + switch (imap_cmd->cmd) { + case IMAP_COMMAND_MAIL: + struct iovec mail_from; + ret = mail_extract_email_address(imap_cmd.arg, imap_cmd.arg_len, mail_from); + if (ret != 0) { + break; + } + msg->command.cmd = MAIL_CMD_MAIL_FROM; + msg->command.arg = mail_from.iov_base; + msg->command.arg_len = mail_from.iov_len; + break; + case IMAP_COMMAND_RCPT: + case IMAP_COMMAND_SEND: + case IMAP_COMMAND_SAML: + case IMAP_COMMAND_SOML: + struct iovec rcpt_to; + ret = mail_extract_email_address(imap_cmd.arg, imap_cmd.arg_len, mail_from); + if (ret != 0) { + break; + } + msg->command.cmd = MAIL_CMD_RCPT_TO; + msg->command.arg = mail_from.iov_base; + msg->command.arg_len = mail_from.iov_len; + break; + case IMAP_COMMAND_STARTTLS: + msg->command.cmd = MAIL_CMD_STARTTLS; + msg->command.arg = NULL; + msg->command.arg_len = 0; + break; + case IMAP_COMMAND_EHLO: + case IMAP_COMMAND_HELO: + msg->command.cmd = MAIL_CMD_EHLO; + break; + default: + break; + } + + mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); +} + +static int imap_parser_publish_username(struct imap_parser *parser, struct session *sess, size_t mail_seq, const char *username, size_t username_len) +{ + int ret; + struct mq_runtime *runtime; + struct mail_message *msg; + + if (username == NULL || username_len == 0) { + return -1; + } + + runtime = module_manager_get_mq_runtime(parser->mail_env_ref->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = MAIL_PROTOCOL_IMAP; + msg->command->cmd = MAIL_CMD_USERNAME; + msg->command->arg = username; + msg->command->arg_len = username_len; + msg->command->cmd_line = username; + msg->command->cmd_line_len = username_len; + + mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); + + return 0; +} + +static int imap_parser_publish_password(struct imap_parser *parser, struct session *sess, size_t mail_seq, const char *password, size_t password_len) +{ + int ret; + struct mq_runtime *runtime; + struct mail_message *msg; + + if (password == NULL || password_len == 0) { + return -1; + } + + runtime = module_manager_get_mq_runtime(parser->mail_env_ref->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = MAIL_PROTOCOL_IMAP; + msg->command->cmd = MAIL_CMD_PASSWORD; + msg->command->arg = password; + msg->command->arg_len = password_len; + msg->command->cmd_line = password; + msg->command->cmd_line_len = password_len; + + mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); + + return 0; +} + +int imap_parser_process_c2s(struct imap_parser *imap, struct session *sess, const char *payload, size_t payload_len, int is_c2s) +{ + int ret; + struct imap_command *imap_cmd; + enum IMAP_AUTH_STATE auth_state; + + if (imap->state == IMAP_STATE_EXIT) { + return 0; + } + + // mail + if (imap->state == IMAP_STATE_WAITING_MAIL_DATA) { + ret = mail_parser_process(imap->mail_parser, payload, payload_len); + if (ret != 0) { + imap->state = IMAP_STATE_EXIT; + return -1; + } + return 0; + } + + line_buffer_clear(imap->line_buffer); // clear completed line + line_buffer_write(imap->line_buffer, payload, payload_len); + while (1) { + const char *line_start; + size_t line_len; + + // line + ret = line_buffer_readln(imap->line_buffer, &line_start, &line_len); + if (ret != 0) { + imap->state = IMAP_STATE_EXIT; + return -1; + } + + if (line_start == NULL || line_len == 0) { + break; + } + + switch (imap->state) { + // auth + case IMAP_STATE_WAITING_AUTHENTICATION: + auth_state = imap_parse_authentication(&imap->authentication, line_start, line_len); + if (auth_state == IMAP_AUTH_DONE) { + imap_parser_publish_username(imap, sess, mail_parser_get_seq(imap->mail_parser), imap->authentication.username, imap->authentication.username_len); + imap_parser_publish_password(imap, sess, mail_parser_get_seq(imap->mail_parser), imap->authentication.password, imap->authentication.password_len); + imap->state = IMAP_STATE_WAITING_COMMAND; + } + break; + // command + case IMAP_STATE_WAITING_COMMAND: + struct imap_command imap_cmd; + ret = imap_parse_command(&imap_cmd, line_start, line_len); + if (ret != 0) { + imap->state = IMAP_STATE_EXIT; + return -1; + } + + switch (imap_cmd.cmd) { + case IMAP_COMMAND_AUTH: + imap_parse_command_auth(&imap->authentication, &imap_cmd); + imap->state = IMAP_STATE_WAITING_AUTHENTICATION; + break; + case IMAP_COMMAND_DATA: + imap->state = IMAP_STATE_WAITING_MAIL_DATA; + break; + case IMAP_COMMAND_RSET: + case IMAP_COMMAND_QUIT: + case IMAP_COMMAND_STARTTLS: + imap->state = IMAP_STATE_EXIT; + break; + default: + break; + } + + imap_parser_publish_command(imap, sess, mail_parser_get_seq(imap->mail_parser), imap_cmd); + break; + default: + break; + } + } + + return 0; +} + +int imap_parser_process_s2c(struct imap_parser *imap, struct session *sess, const char *payload, size_t payload_len, int is_c2s) +{ + return 0; +} + +int imap_parser_process(struct imap_parser *parser, struct session *sess, const char *payload, size_t payload_len, int is_c2s) +{ + if (is_c2s) { + return imap_parser_process_c2s(parser, sess, payload, payload_len); + } else { + return imap_parser_process_s2c(parser, sess, payload, payload_len); + } +} + +void imap_parser_free(struct imap_parser *parser) +{ + if (parser) { + if (parser->line_buffer) { + stream_buffer_deinit(parser->line_buffer); + } + if (parser->mail_parser) { + mail_parser_free(parser->mail_parser); + } + free(parser); + } +} + +struct imap_parser * imap_parser_new(struct mail_env *mail_env) +{ + struct imap_parser *parser = (struct imap_parser *)calloc(1, sizeof(struct imap_parser)); + parser->mail_env_ref = mail_env; + parser->mail_parser = mail_parser_new(mail_env); + stream_buffer_init(&parser->line_buffer, IMAP_LINE_BUFFER_MAX); + return parser; +} + diff --git a/decoders/mail/mail_imap.h b/decoders/mail/mail_imap.h new file mode 100644 index 0000000..b90aa06 --- /dev/null +++ b/decoders/mail/mail_imap.h @@ -0,0 +1,123 @@ +#pragma once + +#include "mail_global.h" + +#define MAILIMAP4_MODULE "[MAIL_IMAP4]" + +#define RESPONSE_FETCH_MAX_LEN 32 //取得FETCH响应第2个空格最大长度 + +#define CONT_TYPE_TEXT 1 +#define CONT_TYPE_MESSAGE 2 +#define CONT_TYPE_RFC822 3 +#define CONT_TYPE_OTHER 4 + +//解析BODYSTRUCTURE一行时的过程状态 +typedef enum +{ + STAT_BLOCK_IDLE=0, + STAT_BLOCK_START, + STAT_BLOCK_PROC, + STAT_BLOCK_END, +}STAT_BODYSTRUCT_t; + +//multipart块所有的字段顺序 +typedef enum +{ + MULTI_SUBTYPE=0, + MULTI_PARENTHESIS, //参数组合列表;从该字段往后都是扩展字段 + MULTI_DISPOSITION, + MULTI_LANGUAGE, + MULTI_LOCATION, + MULTI_NUM, +}MULTI_FIELD_t; + +//非multipart块所有的字段顺序 +typedef enum +{ + NOTMUL_TYPE=0, + NOTMUL_SUBTYPE, + NOTMUL_PARENTHESIS, + NOTMUL_ID, + NOTMUL_DESCR, /*5*/ + NOTMUL_ENCODE, + NOTMUL_SIZE, + NOTMUL_EVENLOP, //MESSAGE/RFC822独有,从该字段往后都是扩展字段 + NOTMUL_BODYSTRUCT, //MESSAGE/RFC822独有 + NOTMUL_LINES, //主类型NOTMUL_TYPE为"TEXT"或类型为MESSAGE/RFC822才有该字段; + NOTMUL_MD5, + NOTMUL_DISPOSITION, /*10*/ + NOTMUL_LANGUAGE, + NOTMUL_LOCATION, + NOTMUL_NUM, +}NOTMUL_FIELD_t; + +#define IMAP_OK_CMD_LEN 30 //IMAP协议FETCH响应结束的长度,如"B00964 OK Fetch completed" +#define IMAP_LABEL_OK_MAX_LEN 100 + +//邮件命令编号从20开始 +typedef enum IMAP_CMD_STATE +{ + IMAP_CMD_ANS=20, + IMAP_CMD_FETCH_ANS, //不需要进行复位的响应 + IMAP_CMD_DATA_END, + IMAP_CMD_MAYBE_END, //遇到")\r\n"的状态 + IMAP_CMD_NOT_END, //判断上一次')'结束标记IMAP_CMD_MAYBE_END不是真正的结束 + IMAP_CMD_DATA_CONTINUE, //按字节获取邮件的后续块 + IMAP_CMD_DATA_BORDER, //来的是一个块的开始,是BODY内容 + IMAP_CMD_DROP, //处理一个标号块时发现有附件名,回调业务层之后业务层给DROP + IMAP_CMD_UNKNOWN, +}IMAP_CMD_STATE_t; + +//为body/bodystructure解析设置的哈希节点 +typedef struct __mime_item +{ + stBufCache mime_header; //用于存储构造MIME头部 + char charset[MAIL_MAX_CHARSET_LEN]; + char filename_charset[MAIL_MAX_CHARSET_LEN]; + char *attach_filename; + char *boundary; + int filename_len; + int bound_len; + int trans_enc; //传输编码 + int is_multi; //是否是MULTIPART块 + int thread_id; +}mime_item_t; + +enum IMAP_COMMAND { + IMAP_COMMAND_UNKNOWN, + IMAP_COMMAND_CAPABILITY , + IMAP_COMMAND_NOOP , + IMAP_COMMAND_LOGOUT , + IMAP_COMMAND_Client , + IMAP_COMMAND_STARTTLS , + IMAP_COMMAND_AUTHENTICATE , + IMAP_COMMAND_LOGIN , + IMAP_COMMAND_Client , + IMAP_COMMAND_SELECT , + IMAP_COMMAND_EXAMINE , + IMAP_COMMAND_CREATE , + IMAP_COMMAND_DELETE , + IMAP_COMMAND_RENAME , + IMAP_COMMAND_SUBSCRIBE , + IMAP_COMMAND_UNSUBSCRIBE , + IMAP_COMMAND_LIST , + IMAP_COMMAND_LSUB , + IMAP_COMMAND_STATUS , + IMAP_COMMAND_APPEND , + IMAP_COMMAND_Client , + IMAP_COMMAND_CHECK , + IMAP_COMMAND_CLOSE , + IMAP_COMMAND_EXPUNGE , + IMAP_COMMAND_SEARCH , + IMAP_COMMAND_FETCH , + IMAP_COMMAND_STORE , + IMAP_COMMAND_COPY , + IMAP_COMMAND_UID, + IMAP_COMMAND_MAX, +}; + +int imap_identify(const char *payload, size_t payload_len, int is_c2s); +int imap_parser_process(struct pop3_parser *parser, struct session *sess, const char *payload, size_t payload_len, int is_c2s); + + + diff --git a/decoders/mail/mail_internal.h b/decoders/mail/mail_internal.h new file mode 100644 index 0000000..c200172 --- /dev/null +++ b/decoders/mail/mail_internal.h @@ -0,0 +1,104 @@ +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +#define MAIL_EXDATA_NAME "MAIL_EXDATA" +#define MAIL_COMMAND_TOPIC_NAME "MAIL_COMMAND" +#define MAIL_HEADER_TOPIC_NAME "MAIL_HEADER" +#define MAIL_BODY_TOPIC_NAME "MAIL_BODY" +#define MAIL_ATTACHMENT_TOPIC_NAME "MAIL_ATTACHMENT" +#define MAIL_EML_TOPIC_NAME "MAIL_EML" + +enum MAIL_MESSAGE_TYPE { + MAIL_MESSAGE_TYPE_COMMAND, + MAIL_MESSAGE_TYPE_HEADER, + MAIL_MESSAGE_TYPE_BODY, + MAIL_MESSAGE_TYPE_ATTACHMENT, + MAIL_MESSAGE_TYPE_EML, + MAIL_MESSAGE_TYPE_MAX, +}; + +struct mail_authentication { + const char *username; + size_t username_len; + const char *password; + size_t password_len; +}; + +struct mail_command { + enum MAIL_COMMAND cmd; + const char *arg; + size_t arg_len; + const char *cmd_line; + size_t cmd_line_len; +}; + +struct mail_body { + char *body; + size_t body_len; + size_t body_offset; + int is_body_finished; +}; + +struct mail_attachment { + char *attachment_name; + size_t attachment_name_len; + char *attachment; + size_t attachment_len; + size_t attachment_offset; + int is_attachment_finished; +}; + +struct mail_eml { + char *eml; + size_t eml_len; + size_t eml_offset; + int is_eml_finished; +}; + +struct mail_message { + enum MAIL_MESSAGE_TYPE type; + enum MAIL_PROTOCOL mail_protocol; + size_t mail_seq; + + struct mail_command command; + struct mail_header header; + struct mail_body body; + struct mail_attachment attachment; + struct mail_eml eml; + + struct session *sess_ref; +}; + +struct mail_session_ctx { + enum MAIL_PROTOCOL protocol; + + struct smtp_parser *smtp_parser; + //struct imap_parser *imap; + //struct pop3_parser *pop3; + + int is_mail_protocol; + struct mail_env *mail_env_ref; + struct session *sess_ref; +}; + +struct mail_env { + int command_topic_id; + int header_topic_id; + int body_topic_id; + int attachment_topic_id; + int eml_topic_id; + int exdata_id; + + struct module_manager *mod_mgr_ref; +}; + +#ifdef __cplusplus +} +#endif + diff --git a/decoders/mail/mail_mime.c b/decoders/mail/mail_mime.c new file mode 100644 index 0000000..d38b8cb --- /dev/null +++ b/decoders/mail/mail_mime.c @@ -0,0 +1,707 @@ +#include +#include +#include +#include + +#include "mail_mime.h" + +static void urldecode2(char *dst, const char *src) +{ + char a, b; + while (*src) { + if ((*src == '%') && + ((a = src[1]) && (b = src[2])) && + (isxdigit(a) && isxdigit(b))) { + if (a >= 'a') + a -= 'a'-'A'; + if (a >= 'A') + a -= ('A' - 10); + else + a -= '0'; + if (b >= 'a') + b -= 'a'-'A'; + if (b >= 'A') + b -= ('A' - 10); + else + b -= '0'; + *dst++ = 16*a+b; + src+=3; + } else if (*src == '+') { + *dst++ = ' '; + src++; + } else { + *dst++ = *src++; + } + } + *dst++ = '\0'; +} + +static int check_mem_size(int wantedlen, char **pmem, int *len) +{ + if(wantedlen < *len) + return 0; + + if(*pmem == NULL) + *pmem = (char *)malloc(wantedlen); + else + *pmem = (char *)realloc(*pmem, wantedlen); + *len = wantedlen; + + return 0; +} + +/********************************************************************* +函数名称:subject_decode +功能简介:对主题和附件名进行解码 + 形如:=?gbk?Q?=B2=E2=CA=D4=D2=BB=CF=C2?=,支持一行多段这样的输入; + 解码结果在out中,如果失败,out存储原始数据 +输入参数:in: 带解码数据;inl:in的长度 + outl:out的大小 +输出参数:out: 解码后缓冲区; outl: 解码后长度 + charset:编码类型,如gbk +返回值:0 +*********************************************************************/ +#ifdef __cplusplus + extern "C" + { +#endif +int decode_mime_header(const char *in, int inl, char *out, int *outl, char* charset, int max_charset_len) +{ + return mime_header_decode(in, inl, out, outl, charset, max_charset_len); +} +#ifdef __cplusplus +} +#endif + + +int mime_header_decode(const char *in, int inl, char *out, int *outl, char* charset, int max_charset_len) +{ + const char *in_end=in+inl, *begin, *chset_end, *encode_end, *end; + int outsize = *outl, total_len=0; + int ret, charset_len; + + //找到编码开始位置 + begin = (char *)mail_memmem(in, inl, "=?", 2); + if(begin == NULL) + goto out_cp; + + //第一个问号是字符集编码结束位置 + chset_end = (char *)memchr(begin+2, '?', in_end-begin-2); + if(chset_end == NULL) + goto out_cp; + + //第2个问号是传输编码结束位置 + encode_end = (char *)memchr(chset_end+1, '?', in_end-chset_end-1); + if(encode_end == NULL) + goto out_cp; + + //本段编码结束位置 + end = (char *)mail_memmem(encode_end+1, in_end-encode_end-1, "?=", 2); + if(end == NULL) + goto out_cp; + + charset_len = (chset_end-begin-2)>=max_charset_len?(max_charset_len-1):(chset_end-begin-2); + memcpy(charset, begin+2, charset_len); + charset[charset_len] = '\0'; + + if(*(encode_end-1) == 'B' || *(encode_end-1) == 'b') + { + if(begin - in > 0) + { + memcpy(out, in, begin-in); + total_len = begin-in; + } + + ret = Base64_DecodeBlock((const unsigned char *)(encode_end+1), end-encode_end-1, (unsigned char *)out+total_len, outsize-total_len); + if(ret < 0) + { + char buf[128]={0}; + int datalen = (end-encode_end-1)>=128?127:(end-encode_end-1); + memcpy(buf, encode_end+1, datalen); + buf[datalen] = '\0'; + goto out_cp; + } + total_len += ret; + } + else if(*(encode_end-1) == 'Q' || *(encode_end-1) == 'q') + { + if(begin - in > 0) + { + memcpy(out, in, begin-in); + total_len = begin-in; + } + + ret = QP_DecodeBlock((const unsigned char *)(encode_end+1), end-encode_end-1, (unsigned char *)out+total_len); + if(ret < 0) + { + char buf[128]={0}; + int datalen = (end-encode_end-1)>=128?127:(end-encode_end-1); + memcpy(buf, encode_end+1, datalen); + buf[datalen] = '\0'; + goto out_cp; + } + total_len += ret; + } + else + goto out_cp; + + if(in_end - end - 2 > 0) + { + outsize -= total_len; + mime_header_decode(end+2, in_end-end-2, out+total_len, &outsize, charset, max_charset_len); + total_len += outsize; + } + + *outl = total_len; + + return 0; + +out_cp: + memcpy(out, in, inl); + *outl = inl; + return 0; +} + +/********************************************************************* +函数名称:add_boundary(char *line, boundary_list **Head) +功能简介:将找到的界限加入界限链表 +输入参数:src:当前行 + n:行长度 + Head:指向包含boundary_list域的结构体指针的指针 + +输出参数: +返回值:-1 分配内存失败,正常处理返回0 +*********************************************************************/ +int add_boundary(const char *src, int n, boundary_list **Head) +{ + boundary_list *p; + int len; + + if(src == NULL || n <= 0) + return -1; + + p = (boundary_list *)malloc(sizeof(boundary_list)); + if(*Head==NULL) + { + p->main_bound = BOUNDARY_TYPE_MAIN; + } + else + { + p->main_bound = BOUNDARY_TYPE_OTHER; + } + len = (n>=BOUNDARY_SIZE)?(BOUNDARY_SIZE-1):n; + memcpy(p->str, src, len); + p->str_len = len; + + p->next = *Head; + *Head = p; + + return 0; +} + +/********************************************************************* +函数名称:search_boundary +功能简介:查找某行是否有界限。 +输入参数:src:要查找的行 + head:指向界限链表头的指针 +输出参数: +返回值:找到界限,返回1;未找到,返回0 +*********************************************************************/ +static int search_boundary(char *src, int srclen, boundary_list *head) +{ + boundary_list *p; + + for(p=head; p!=NULL; p=p->next) + { + if(srclen != p->str_len) + continue; + + if (strncmp(src, p->str, srclen) == 0) + return p->main_bound; + } + return 0; +} + +/********************************************************************* +函数名称:boundary_free +功能简介:释放界线链表 +输入参数:head:指向界限链表头的指针 +输出参数:无 +返回值: 无 +*********************************************************************/ +static void free_boundary( boundary_list *head) +{ + boundary_list *p = head, *q; + + while(p != NULL) + { + q = p; + p = p->next; + + free(q); + } +} + +static int get_value_th(char *temp, int len,char **ppval,int *val_len) +{ + char *temp2=NULL; + char *tp=NULL; + char *tpend=NULL; + + tp=temp; + tpend=tp+len; + while((tp[0]==' ')&&(tpTransferEnc = MIME_TRANSENC_UNKNOWN; + + pmimeinfo->handle.length=0; //handle_init(); + pmimeinfo->line_type = MIME_LINE_NULL; + + *mimeinfo = pmimeinfo; + + return 0; +} + +/********************************************************************* +函数名称:clear_mimeinfo +功能简介:清空数据缓冲区数据 +输入参数:mimeinfo: +输出参数:无 +返回值: 无 +*********************************************************************/ +void clear_mime_info(MimeParse_t *mimeinfo) +{ + free_boundary(mimeinfo->b_header); + + if(mimeinfo->filename!=NULL) + free(mimeinfo->filename); + + free(mimeinfo); +} + +/********************************************************************* +函数名称:reset_mimeinfo +功能简介:清空MIME数据结构体,如果是邮件的部分内容结束,则不清空边界链表, + 否则清空边界链表 +输入参数:mimeinfo: + PartEnd:1是邮件部分内容结束,0 整体邮件处理结束 +输出参数:无 +返回值: 无 +*********************************************************************/ +void reset_mime_info(MimeParse_t * mimeinfo,int PartEnd) +{ + if(PartEnd==0)//一个区块完成 + { + free_boundary(mimeinfo->b_header); + mimeinfo->b_header = NULL; + mimeinfo->bound_comes = 0; + } + + mimeinfo->src = NULL; + mimeinfo->dst = NULL; + mimeinfo->srcLen = 0; + mimeinfo->dstSize = 0; + mimeinfo->actLen = 0; + mimeinfo->text_begin = 0; + mimeinfo->filenameLen = 0; + if(mimeinfo->filename!=NULL) + { + free(mimeinfo->filename); + mimeinfo->filename = NULL; + } + + mimeinfo->filename_charset[0] = 0; + mimeinfo->charset[0] = 0; + + mimeinfo->TransferEnc = MIME_TRANSENC_UNKNOWN; + mimeinfo->line_type = MIME_LINE_NULL; + + mimeinfo->handle.length=0; +} + +/********************************************************************* +函数名称:detect_charenc +功能简介:把字符串的传输编码方式转换成相应的整数定义 +输入参数:charset : 传输编码方式字符串(in) +输出参数:无 +返回值: 对应的编码方式代码或者未判别标志 +*********************************************************************/ +static int detect_transenc(char * transenc, int len) +{ + while(*transenc == ' ') + { + transenc++; + len--; + } + + if(strncmp_one_word_mesa("base64", "BASE64", 6, transenc, len)) + return MIME_TRANSENC_BASE64; + if(strncmp_one_word_mesa("quoted-printable", "QUOTED-PRINTABLE", 16, transenc, len)) + return MIME_TRANSENC_QP; + + return MIME_TRANSENC_UNKNOWN; +} + +/********************************************************************* +函数名称:line_is_mime_header +功能简介: +输入参数:pInfo->src是完整行,没有支持开头是空格的情况 +输出参数:无 +返回值: <0: error; >0: header or null-line; 0: CONTENT +*********************************************************************/ +static int mime_line_identify(MimeParse_t *pInfo) +{ + if(pInfo->srcLen==0 || (pInfo->srcLen==2 && pInfo->src[0]=='\r' && pInfo->src[1]=='\n')||(pInfo->srcLen==1 && pInfo->src[0]=='\n')) //一个空行 + { + return MIME_LINE_NULL; + } + + if(pInfo->srcLen>2 && strncmp(pInfo->src, "--", 2) == 0) + { + int len = pInfo->srcLen; + + while(len>2 && (*(pInfo->src+len-1)=='\r' || *(pInfo->src+len-1)=='\n')) + len--; + + if(!search_boundary(pInfo->src + 2, len-2, pInfo->b_header)) + { + int type; + if(len >=4 && pInfo->src[len-1] == '-' && pInfo->src[len-2] == '-' && \ + (type=search_boundary(pInfo->src + 2, len-4, pInfo->b_header))>0) + { + if(type==BOUNDARY_TYPE_MAIN) + { + pInfo->bound_comes = 0; + pInfo->line_type = MIME_LINE_COMMENT; + } + return MIME_LINE_BOUNARY_END; + } + //TODO: 正文中明文有---的情况,如发送失败的回传邮件 + if(pInfo->bound_comes==0 && pInfo->b_header != NULL) + return MIME_LINE_COMMENT; + else + return MIME_LINE_CONT_BODY; + } + return MIME_LINE_BOUNARY; + } + + if(pInfo->line_type == MIME_LINE_CONT_BODY || pInfo->line_type == MIME_LINE_COMMENT) + return pInfo->line_type; + + if(strncmp_one_word_mesa("content-type:", "CONTENT-TYPE:", PARTHEAD_CONTTYPE_LEN, pInfo->src, pInfo->srcLen)) + return MIME_LINE_CONT_TYPE; + if(strncmp_one_word_mesa("content-transfer-encoding:", "CONTENT-TRANSFER-ENCODING:", PARTHEAD_TRANSENC_LEN, pInfo->src, pInfo->srcLen)) + return MIME_LINE_CONT_ENC; + if(strncmp_one_word_mesa("content-disposition:", "CONTENT-DISPOSITION:", PARTHEAD_CONTENTDISPOSITION_LEN, pInfo->src, pInfo->srcLen)) + return MIME_LINE_CONT_DISP; +// if(check_str_begin(pInfo->src, pInfo->srcLen, "Content")) +// return MIME_LINE_CONT_OTHR; + + if(pInfo->bound_comes==0 && pInfo->b_header != NULL) + return MIME_LINE_COMMENT; + if(pInfo->boundary_pending) + return MIME_LINE_CONT_OTHR; + //TODO:如果开头是空格 + + return MIME_LINE_CONT_BODY; +} + +/********************************************************************* +函数名称:mime_parse_feed +功能简介:进行MIME解码,输入内容为MIME原始编码数据,调用者 + 根据返回值进行相应的操作。 +注意事项:如果不进行解码或不是编码字符,目的缓冲区指向源缓冲区, + 因此在未使用完目的缓冲区前,源缓冲区不可改变内容或指针; +输入参数:mimeinfo: + decode:是否进行传输编码解码,1-是;0-否 +输出参数:无 +返回值:MIME行类型 +*********************************************************************/ +int mime_parse_feed(MimeParse_t *pInfo, int decode) +{ + int ret; + char *pout = NULL, *pc; + int outlen=0, pclen; + int len; + + if(pInfo->dst==NULL) + return -10; + + pInfo->actLen = 0; + + pInfo->line_type = mime_line_identify(pInfo); + switch(pInfo->line_type) + { + case MIME_LINE_ERR: + return -2; + + case MIME_LINE_NULL: + // MIME头和体之间必然有空格;但是体和下一个boundary之间就不一定有空格 + pInfo->text_begin = 1; + pInfo->boundary_pending = 0; + + if(pInfo->bound_comes==0 && pInfo->b_header != NULL) + pInfo->line_type = MIME_LINE_COMMENT; + else + pInfo->line_type = MIME_LINE_CONT_BODY; //TODO: 正文中出现"\r\n---anystr",前面的空行被丢弃了 + break; + + case MIME_LINE_BOUNARY: + reset_mime_info(pInfo, 1); //set text_begin=0 + pInfo->boundary_pending = 1; + + if(pInfo->bound_comes == 0) + pInfo->bound_comes = 1; + break; + + case MIME_LINE_BOUNARY_END: + pInfo->text_begin = 0; //一般后续要么结束,要么是新的BOUNARY;为了IMAP结尾的')' + break; + + case MIME_LINE_CONT_TYPE: + pc = pInfo->src + PARTHEAD_CONTTYPE_LEN; + pclen = pInfo->srcLen - PARTHEAD_CONTTYPE_LEN; + + if(check_name_th(pc, pclen, PARTHEAD_CHARSET, &pout, &outlen)==0) + { + len = (outlen>=MAIL_MAX_CHARSET_LEN)?(MAIL_MAX_CHARSET_LEN-1):outlen; + memcpy(pInfo->charset, pout, len); + pInfo->charset[len] = '\0'; + } + + if(pInfo->filename != NULL) + { + free(pInfo->filename); + pInfo->filename = NULL; + pInfo->filenameLen = 0; + } + else if(check_name_th(pc, pclen, PARTHEAD_NAME, &pout, &outlen)==0) + { + //tmplen为0的情况,pInfo->filename!=NULL && pInfo->filenameLen==0, 调用者自行处理 + if(check_mem_size(outlen+1, &pInfo->filename, &pInfo->filenameLen)) + return -3; + + mime_header_decode(pout, outlen, pInfo->filename, &pInfo->filenameLen, pInfo->filename_charset, MAIL_MAX_CHARSET_LEN); + pInfo->filename[pInfo->filenameLen] = '\0'; //ENDPART reset时会清理,释放 + } + + if(check_name_th(pc, pclen, PARTHEAD_BOUNDARY, &pout, &outlen)==0) + { + if(search_boundary(pout, outlen, pInfo->b_header)==0) + { + if(add_boundary(pout, outlen, &pInfo->b_header)) + return -4; + } + } + break; + + case MIME_LINE_CONT_DISP: + if (pInfo->filename != NULL) // && strlen(pInfo->filename)>0) + { + free(pInfo->filename); + pInfo->filename = NULL; //避免重复调用业务层 + pInfo->filenameLen = 0; + break; + } + + pc = pInfo->src + PARTHEAD_CONTENTDISPOSITION_LEN; + pclen = pInfo->srcLen - PARTHEAD_CONTENTDISPOSITION_LEN; + + if (check_name_th(pc, pclen, PARTHEAD_FILENAME, &pout, &outlen) == 0) + { + //tmplen为0的情况,pInfo->filename!=NULL && pInfo->filenameLen==0, 调用者自行处理 + if (check_mem_size(outlen + 1, &pInfo->filename, &pInfo->filenameLen)) + return -5; + + mime_header_decode(pout, outlen, pInfo->filename, &pInfo->filenameLen, pInfo->filename_charset, MAIL_MAX_CHARSET_LEN); + pInfo->filename[pInfo->filenameLen] = '\0'; //ENDPART reset时会清理,释放 + } + else if (check_name_th(pc, pclen, PARTHEAD_FILENAME_EXT, &pout, &outlen) == 0) + { + // support RFC 6266, ext value, like filename*=utf-8''%e2%82%ac%20rates + const char *p_first_quote = NULL, *p_second_quote = NULL, *p_actual_value = NULL; + int actual_value_len = 0; + // define in RFC 2231.section 4, filename ext value format [charset'language'actual value] + for (int i = 0; i < outlen; i++) + { + if (pout[i] == '\'') + { + if (p_first_quote == NULL) + { + p_first_quote = (pout + i); + } + else + { + p_second_quote = (pout + i); + break; + } + } + } + // must include two single quote + if (p_first_quote == NULL || p_second_quote == NULL) + { + p_actual_value = pout; + actual_value_len = outlen; + } + else + // copy charset if possible + { + p_actual_value = (p_second_quote + 1); + actual_value_len = outlen - (p_actual_value - pout); + if (p_first_quote != pout && (p_first_quote - pout) < MAIL_MAX_CHARSET_LEN) + { + memcpy(pInfo->filename_charset, pout, (p_first_quote - pout)); + } + } + if (check_mem_size(actual_value_len + 1, &pInfo->filename, &pInfo->filenameLen)) + return -3; + memset(pInfo->filename, 0, actual_value_len + 1); + memcpy(pInfo->filename, p_actual_value, actual_value_len); + char *decode_buff = (char *)malloc(actual_value_len + 1); + urldecode2(decode_buff, pInfo->filename); + memset(pInfo->filename, 0, actual_value_len + 1); + memcpy(pInfo->filename, decode_buff, strlen(decode_buff)); + pInfo->filenameLen = strlen(decode_buff); + free(decode_buff); + } + else + { + while (pclen > 0 && (*pc == ' ' || *pc == '\t')) + { + pc++; + pclen--; + } + + if (pclen > 0 && strncmp_one_word_mesa("attachment", "ATTACHMENT", 10, pc, pclen)) + { + pInfo->filename = (char *)malloc(1); + pInfo->filenameLen = 0; + pInfo->filename[0] = '\0'; + } + } + break; + + case MIME_LINE_CONT_ENC: + pInfo->TransferEnc = detect_transenc(pInfo->src + PARTHEAD_TRANSENC_LEN, pInfo->srcLen-PARTHEAD_TRANSENC_LEN); + break; + + case MIME_LINE_CONT_OTHR: + pInfo->dst = pInfo->src; + pInfo->actLen = pInfo->srcLen; + break; + + case MIME_LINE_COMMENT: //注释行 + break; + + case MIME_LINE_CONT_BODY: + //正文或附件内容 + if((pInfo->TransferEnc==MIME_TRANSENC_BASE64 || pInfo->TransferEnc==MIME_TRANSENC_QP) && decode!=0) + { + if(pInfo->TransferEnc==MIME_TRANSENC_BASE64) + { + ret = Base64_DecodeFeed_r_n(&pInfo->handle, (unsigned char *)pInfo->src, pInfo->srcLen, (unsigned char *)pInfo->dst, pInfo->dstSize); + if(ret < 0) + { + char buf[128]={0}; + memcpy(buf, pInfo->src, pInfo->srcLen>=128?127:pInfo->srcLen); + return ret; + } + } + else + { + ret = QP_DecodeFeed(&pInfo->handle, (unsigned char *)pInfo->src, pInfo->srcLen, (unsigned char *)pInfo->dst); + if(ret < 0) + { + return ret; + } + } + + pInfo->actLen = ret; + } + else + { + pInfo->dst = pInfo->src; + pInfo->actLen = pInfo->srcLen; + //pInfo->text_begin=1; + } + break; + + default: + break; + } + + return pInfo->line_type; +} + +void body_update_text_begin(MimeParse_t *pInfo) +{ + if(pInfo->text_begin==0 && pInfo->b_header==NULL) + pInfo->text_begin = 1; +} + diff --git a/decoders/mail/mail_mime.h b/decoders/mail/mail_mime.h new file mode 100644 index 0000000..2dca78c --- /dev/null +++ b/decoders/mail/mail_mime.h @@ -0,0 +1,118 @@ +#pragma once + +#include "mail_email.h" + +#ifndef MAIL_MAX_CHARSET_LEN +#define MAIL_MAX_CHARSET_LEN 32 +#endif + +#define MIME_MODULE "[MIME_PARSE]" + +//定义MIME行获取状态 +enum +{ + MIME_LINE_ERR=-1, + MIME_LINE_NULL, + MIME_LINE_BOUNARY, + MIME_LINE_BOUNARY_END, + MIME_LINE_CONT_TYPE, + MIME_LINE_CONT_ENC, + MIME_LINE_CONT_DISP, + MIME_LINE_CONT_OTHR, + MIME_LINE_CONT_BODY, + MIME_LINE_COMMENT, +}; + +#define EML_LIEN_SIZE 128 + +// 定义转换编码的类型 +#define MIME_TRANSENC_UNKNOWN 0 +#define MIME_TRANSENC_BASE64 1 +#define MIME_TRANSENC_QP 2 + +// 定义字符编码类型 +#define MIME_CHARENC_UNKNOWN 0 +#define MIME_CHARENC_UTF7 1 +#define MIME_CHARENC_UTF8 2 + +// 定义字符集类型 +#define MIME_CHARSET_UNKNOWN 0 +#define MIME_CHARSET_ASCII 1 +#define MIME_CHARSET_ISO88591 2 +#define MIME_CHARSET_UNICODE 3 +#define MIME_CHARSET_GB2312 4 +#define MIME_CHARSET_BIG5 5 +#define MIME_CHARSET_UTF7 6 +#define MIME_CHARSET_UTF8 7 +#define MIME_CHARSET_GBK 8 +#define MIME_CHARSET_HZ 9 + +// multipart's header information +//#define PARTHEAD_CONTTYPE "Content-Type:" +//#define PARTHEAD_TRANSENC "Content-Transfer-Encoding:" +//#define PARTHEAD_CONTENTDISPOSITION "Content-Disposition:" +#define PARTHEAD_CHARSET "charset=" +#define PARTHEAD_NAME "name=" +#define PARTHEAD_FILENAME "filename=" +#define PARTHEAD_FILENAME_EXT "filename*=" +#define PARTHEAD_BOUNDARY "boundary=" + +#define PARTHEAD_CONTTYPE_LEN 13 +#define PARTHEAD_TRANSENC_LEN 26 +#define PARTHEAD_CONTENTDISPOSITION_LEN 20 + +#define MAIL_STRING_ALLOC_SIZE 16 + +#define BOUNDARY_SIZE 80 +#define BOUNDARY_TYPE_MAIN 1 +#define BOUNDARY_TYPE_OTHER 2 + +typedef struct _boundary_list +{ + struct _boundary_list *next; + char str[BOUNDARY_SIZE]; + int str_len; + int main_bound; +} boundary_list; + + +typedef struct __MimeParse +{ + char *src; //源缓冲区,作为指针使用,不可为其分配内存,因为它会改变所指 + char *dst; //目的缓冲区,作为指针使用,不可为其分配内存,因为它会改变所指 + + int srcLen; + int dstSize; //目的缓冲区空闲大小TODO + int actLen; //实际占用的长度。 + + int text_begin; //0:后续需要处理折叠行; 1:后续不是折叠行 + int line_type; //MIME行类型 + + int TransferEnc; // 传输编码类型 + decode_handle_t handle; + short bound_comes; //是否来过BOUNDARY,局部结束不要清零,用来区分注释行 + short boundary_pending; + + // 附件文件名,以'\0'结尾;如果filename!=NULL&&filenameLen==0,说明没有附件名 + //或没解析出来,但这确实是附件名,后续内容应当作附件内容。 + int filenameLen; //filename的长度 + char* filename; + + char filename_charset[MAIL_MAX_CHARSET_LEN]; //附件名字符编码,以'\0'结尾 + char charset[MAIL_MAX_CHARSET_LEN]; //正文或附件内容字符编码,以'\0'结尾 + + boundary_list * b_header; /*存放边界的链表的表头指针*/ +} MimeParse_t; + +int init_mime_info(MimeParse_t **mimeinfo); + +//PartEnd: 邮件复位时输入0 +void reset_mime_info(MimeParse_t * mimeinfo,int PartEnd); +void clear_mime_info(MimeParse_t * mimeinfo); +//decode:是否进行传输编码解码;1-是;0-否; +int mime_parse_feed(MimeParse_t *pInfo, int decode); +void body_update_text_begin(MimeParse_t *pInfo); + +int mime_header_decode(const char *in, int inl, char *out, int *outl, char* charset, int max_charset_len); + +int add_boundary(const char *src, int n, boundary_list **Head); diff --git a/decoders/mail/mail_pop3.c b/decoders/mail/mail_pop3.c new file mode 100644 index 0000000..55e29ff --- /dev/null +++ b/decoders/mail/mail_pop3.c @@ -0,0 +1,468 @@ +#include +#include +#include +#include +#include + +#include "mail_internal.h" +#include "mail_util.h" +#include "mail_email.h" +#include "mail_pop3.h" + +#include "base64.h" + +const char* g_pop3_command[] = { + "unknown", + "top", + "uidl", + "capa", + "auth", + "user", + "pass", + "stls", + "apop", + "stat", + "list", + "retr", + "dele", + "noop", + "rset", + "quit", + "pipeling", + "resp-code", + "auth-resp-code", + "sasl plain login", +}; + +static enum POP3_COMMAND pop3_str2command(const char *payload, size_t payload_len) +{ + int i; + if (payload == NULL || payload_len == 0) { + return POP3_COMMAND_UNKNOWN; + } + for(i = 1; i < POP3_COMMAND_MAX; i++) { + if(0 == SAFE_STRNCASECMP(payload, payload_len, g_pop3_command[i], strlen(g_pop3_command[i]))) { + break; + } + } + if (i == POP3_COMMAND_MAX) { + return POP3_COMMAND_UNKNOWN; + } + return (enum POP3_COMMAND)i; +} + +static int is_pop3_cmd(const char *data, int datalen) +{ + if (POP3_COMMAND_UNKNOWN == pop3_str2command(data, datalen)) { + return 0; + } + return 1; +} + +static int is_pop3_res(const char *data, size_t datalen) +{ + if (datalen >= strlen(POP3_STR_OK) && 0 == strncmp(data, POP3_STR_OK, 3)) { + return 1; + } + if (datalen >= strlen(POP3_STR_ERR) && 0 == strncmp(data, POP3_STR_ERR, 3)) { + return 1; + } + return 0; +} + +int pop3_identify(const char *payload, size_t payload_len, int is_c2s) +{ + if (payload == NULL || payload_len < 4) { + return 0; + } + + if (is_c2s) { + if (is_pop3_cmd(payload, payload_len)) { + const char *line_end = memchr(payload, '\r', payload_len); + size_t line_len = line_end - payload; + if (line_end && line_len > strlen("http/1.") && 0 == strncasecmp(payload, "http/1.", line_len)) { + return 0; + } + return 1; + } + } else { + if (is_pop3_res(payload, payload_len)) { + return 1; + } + } + return 0; +} + +static int pop3_parse_authentication_xoauth2(struct pop3_authentication *auth, const char *line_start, size_t line_len) +{ + int decoded; + char decode_buf[512]; + char *username; + size_t username_len; + + decoded = Base64_DecodeBlock((unsigned char *)line_start, line_len, (unsigned char *)decode_buf, sizeof(decode_buf)); + if (decoded < 0) { + return -1; + } + + if (0 != mail_xoauth2_get_username((const char *)decode_buf, decoded, &username, &username_len)) { + return -1; + } + + if (username_len >= sizeof(auth->username)) { + return -1; + } + + memcpy(auth->username, username, username_len); + auth->username_len = username_len; + + auth->state = POP3_AUTH_DONE; + return 0; +} + +static enum POP3_AUTH_STATE pop3_parse_authentication(struct pop3_authentication *auth, enum POP3_AUTH_TYPE type, const char *line_start, size_t line_len) +{ + int ret = 0; + + if (auth->state == POP3_AUTH_DONE) { + return POP3_AUTH_DONE; + } + + switch (type) { + case POP3_AUTH_TYPE_XOAUTH2: + ret = pop3_parse_authentication_xoauth2(auth, line_start, line_len); + default: + break; + } + + if (ret != 0) { + return POP3_AUTH_DONE; + } + + return auth->state; +} + +int pop3_parse_command_user(struct pop3_authentication *auth, struct pop3_command *pop3_cmd) +{ + if (auth->state == POP3_AUTH_DONE) { + return -1; + } + + if (pop3_cmd->arg && pop3_cmd->arg_len > 0) { + return -1; + } + + memcpy(auth->username, pop3_cmd->arg, pop3_cmd->arg_len); + auth->username_len = pop3_cmd->arg_len; + auth->type = POP3_AUTH_TYPE_USERPASS; + auth->state = POP3_AUTH_WAITING_PASSWORD; + + return 0; +} + +int pop3_parse_command_pass(struct pop3_authentication *auth, struct pop3_command *pop3_cmd) +{ + if (auth->state == POP3_AUTH_DONE) { + return -1; + } + + if (pop3_cmd->arg && pop3_cmd->arg_len > 0) { + return -1; + } + + memcpy(auth->password, pop3_cmd->arg, pop3_cmd->arg_len); + auth->password_len = pop3_cmd->arg_len; + auth->type = POP3_AUTH_TYPE_USERPASS; + auth->state = POP3_AUTH_DONE; + + return 0; +} + +int pop3_parse_command_auth(struct pop3_authentication *auth, struct pop3_command *pop3_cmd) +{ + if (0 == SAFE_STRNCASECMP(pop3_cmd.arg, pop3_cmd.arg_len, "xoauth2", strlen("xoauth2"))) { + auth->type = POP3_AUTH_TYPE_XOAUTH2; + auth->state = POP3_AUTH_WAITING_USERNAME; + } + return 0; +} + +int pop3_parse_command(struct pop3_command *pop3_cmd, const char *line_start, size_t line_len) +{ + int ret; + struct iovec parts[POP3_COMMAND_LINE_PART_NUM]; + + memset(pop3_cmd, 0 , sizeof(struct pop3_command)); + + ret = strnsplit(parts, sizeof(parts)/sizeof(parts[0]), ' ', line_start, line_len); + if (ret <= 0 || ret > POP3_COMMAND_LINE_PART_NUM) { + return -1; + } + + pop3_cmd->cmd = pop3_str2command(line_start, line_len); + pop3_cmd->cmd_line = line_start; + pop3_cmd->cmd_line_len = line_len; + + if (ret == POP3_COMMAND_LINE_PART_NUM) { + pop3_cmd->arg = parts[POP3_COMMAND_LINE_PART_ARG].iov_base; + pop3_cmd->arg_len = parts[POP3_COMMAND_LINE_PART_ARG].iov_len; + } + + return 0; +} + +static int pop3_parser_publish_command(struct pop3_parser *parser, struct session *sess, size_t mail_seq, struct pop3_command *pop3_cmd) +{ + int ret; + struct mq_runtime *runtime; + struct mail_message *msg; + + runtime = module_manager_get_mq_runtime(parser->mail_env_ref->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = MAIL_PROTOCOL_POP3; + msg->command->cmd = MAIL_CMD_OTHER; + msg->command->arg = pop3_cmd->arg; + msg->command->arg_len = pop3_cmd->arg_len; + msg->command->cmd_line = pop3_cmd->cmd_line; + msg->command->cmd_line_len = pop3_cmd->cmd_line_len; + + switch (pop3_cmd->cmd) { + case POP3_COMMAND_STLS: + msg->command.cmd = MAIL_CMD_STARTTLS; + msg->command.arg = NULL; + msg->command.arg_len = 0; + break; + default: + break; + } + + mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); +} + +static int pop3_parser_publish_username(struct pop3_parser *parser, struct session *sess, size_t mail_seq, const char *username, size_t username_len) +{ + int ret; + struct mq_runtime *runtime; + struct mail_message *msg; + + if (username == NULL || username_len == 0) { + return -1; + } + + runtime = module_manager_get_mq_runtime(parser->mail_env_ref->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = MAIL_PROTOCOL_POP3; + msg->command->cmd = MAIL_CMD_USERNAME; + msg->command->arg = username; + msg->command->arg_len = username_len; + msg->command->cmd_line = username; + msg->command->cmd_line_len = username_len; + + mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); + + return 0; +} + +static int pop3_parser_publish_password(struct pop3_parser *parser, struct session *sess, size_t mail_seq, const char *password, size_t password_len) +{ + int ret; + struct mq_runtime *runtime; + struct mail_message *msg; + + if (password == NULL || password_len == 0) { + return -1; + } + + runtime = module_manager_get_mq_runtime(parser->mail_env_ref->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = MAIL_PROTOCOL_POP3; + msg->command->cmd = MAIL_CMD_PASSWORD; + msg->command->arg = password; + msg->command->arg_len = password_len; + msg->command->cmd_line = password; + msg->command->cmd_line_len = password_len; + + mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); + + return 0; +} + +int pop3_parser_process_c2s(struct pop3_parser *pop3, struct session *sess, const char *payload, size_t payload_len, int is_c2s) +{ + int ret; + struct pop3_command *pop3_cmd; + enum POP3_AUTH_STATE auth_state; + + if (pop3->state == POP3_STATE_EXIT) { + return 0; + } + + line_buffer_clear(pop3->line_buffer_c2s); // clear completed line + line_buffer_write(pop3->line_buffer_c2s, payload, payload_len); + while (1) { + const char *line_start; + size_t line_len; + + // line + ret = line_buffer_readln(pop3->line_buffer_c2s, &line_start, &line_len); + if (ret != 0) { + pop3->state = POP3_STATE_EXIT; + return -1; + } + + if (line_start == NULL || line_len == 0) { + break; + } + + switch (pop3->state) { + // auth + case POP3_STATE_WAITING_AUTHENTICATION: + auth_state = pop3_parse_authentication(&pop3->authentication, line_start, line_len); + if (auth_state == POP3_AUTH_DONE) { + pop3_parser_publish_username(pop3, sess, mail_parser_get_seq(pop3->mail_parser), pop3->authentication.username, pop3->authentication.username_len); + pop3_parser_publish_password(pop3, sess, mail_parser_get_seq(pop3->mail_parser), pop3->authentication.password, pop3->authentication.password_len); + pop3->state = POP3_STATE_WAITING_COMMAND; + } + break; + // command + case POP3_STATE_WAITING_COMMAND: + struct pop3_command pop3_cmd; + ret = pop3_parse_command(&pop3_cmd, line_start, line_len); + if (ret != 0) { + pop3->state = POP3_STATE_EXIT; + return -1; + } + + switch (pop3_cmd.cmd) { + case POP3_COMMAND_USER: + pop3_parse_command_user(&pop3->authentication, pop3_cmd); + break; + case POP3_COMMAND_PASS: + pop3_parse_command_pass(&pop3->authentication, pop3_cmd); + break; + case POP3_COMMAND_AUTH: + pop3_parse_command_auth(&pop3->authentication, &pop3_cmd); + pop3->state = POP3_STATE_WAITING_AUTHENTICATION; + break; + case POP3_COMMAND_RETR: + pop3->state = POP3_STATE_WAITING_MAIL_DATA; + break; + case POP3_COMMAND_RSET: + case POP3_COMMAND_QUIT: + case POP3_COMMAND_STLS: + pop3->state = POP3_STATE_EXIT; + break; + default: + break; + } + + pop3_parser_publish_command(pop3, sess, mail_parser_get_seq(pop3->mail_parser), pop3_cmd); + break; + default: + break; + } + } + + return 0; +} + +int pop3_parser_process_s2c(struct pop3_parser *pop3, struct session *sess, const char *payload, size_t payload_len, int is_c2s) +{ + int ret; + size_t line_len; + const char *line_start; + const char *line_end; + struct iovec parts[POP3_RETR_RESPONSE_LINE_PART_NUM]; + + if (pop3->state == POP3_STATE_EXIT) { + return 0; + } + + if (pop3->state == POP3_STATE_WAITING_MAIL_DATA) { + ret = mail_parser_process(pop3->mail_parser, payload, payload_len); + if (ret != 0) { + pop3->state = POP3_STATE_EXIT; + return -1; + } + return 0; + } else { + // get line + line_end = (const char *)memmem(payload, payload_len, POP3_LINE_END, strlen(POP3_LINE_END)); + if (line_end == NULL) { + return 0; + } + + line_start = payload; + line_len = line_end - payload + strlen(POP3_LINE_END); + + // is command retr response line ? + ret = strnsplit(parts, sizeof(parts)/sizeof(parts[0]), ' ', line_start, line_len); + if (ret == POP3_RETR_RESPONSE_LINE_PART_NUM && + 0 == SAFE_STRNCASECMP("+OK", strlen("+OK"), parts[POP3_RETR_RESPONSE_LINE_PART_OK].iov_base, parts[POP3_RETR_RESPONSE_LINE_PART_OK].iov_len) && + 0 == SAFE_STRNCASECMP("octets", strlen("+octets"), parts[POP3_RETR_RESPONSE_LINE_PART_OCTETS].iov_base, parts[POP3_RETR_RESPONSE_LINE_PART_OCTETS].iov_len)) { + + pop3->state = POP3_STATE_WAITING_MAIL_DATA; + ret = mail_parser_process(pop3->mail_parser, payload + line_len, payload_len - line_len); + if (ret != 0) { + pop3->state = POP3_STATE_EXIT; + return -1; + } + return 0; + } + } + + return 0; +} + +int pop3_parser_process(struct pop3_parser *parser, struct session *sess, const char *payload, size_t payload_len, int is_c2s) +{ + if (is_c2s) { + return pop3_parser_process_c2s(parser, sess, payload, payload_len); + } else { + return pop3_parser_process_s2c(parser, sess, payload, payload_len); + } +} + +void pop3_parser_free(struct pop3_parser *parser) +{ + if (parser) { + if (parser->line_buffer_c2s) { + stream_buffer_deinit(parser->line_buffer_c2s); + } + if (parser->line_buffer_s2c) { + stream_buffer_deinit(parser->line_buffer_c2s); + } + if (parser->mail_parser) { + mail_parser_free(parser->mail_parser); + } + free(parser); + } +} + +struct pop3_parser * pop3_parser_new(struct mail_env *mail_env) +{ + struct pop3_parser *parser = (struct pop3_parser *)calloc(1, sizeof(struct pop3_parser)); + parser->mail_env_ref = mail_env; + parser->mail_parser = mail_parser_new(mail_env); + stream_buffer_init(&parser->line_buffer_c2s, POP3_LINE_BUFFER_MAX); + stream_buffer_init(&parser->line_buffer_s2c, POP3_LINE_BUFFER_MAX); + return parser; +} diff --git a/decoders/mail/mail_pop3.h b/decoders/mail/mail_pop3.h new file mode 100644 index 0000000..b0c1d0d --- /dev/null +++ b/decoders/mail/mail_pop3.h @@ -0,0 +1,99 @@ +#pragma once + +#include "mail_internal.h" +#include "mail_util.h" +#include "mail_email.h" + +#define POP3_STR_OK "+OK" +#define POP3_STR_ERR "-ERR" +#define POP3_LINE_END "\r\n" + +#define POP3_COMMAND_LINE_PART_CMD 0 +#define POP3_COMMAND_LINE_PART_ARG 1 +#define POP3_COMMAND_LINE_PART_NUM 2 + +#define POP3_RETR_RESPONSE_LINE_PART_OK 0 +#define POP3_RETR_RESPONSE_LINE_PART_SIZE 1 +#define POP3_RETR_RESPONSE_LINE_PART_OCTETS 2 +#define POP3_RETR_RESPONSE_LINE_PART_NUM 3 + +#define POP3_LINE_BUFFER_MAX 4096 +#define POP3_USERNAME_LEN_MAX 128; +#define POP3_PASSWORD_LEN_MAX 128; + +enum POP3_COMMAND { + POP3_COMMAND_UNKNOWN, + POP3_COMMAND_TOP, + POP3_COMMAND_UIDL, + POP3_COMMAND_CAPA, + POP3_COMMAND_AUTH, + POP3_COMMAND_USER, + POP3_COMMAND_PASS, + POP3_COMMAND_STLS, + POP3_COMMAND_APOP, + POP3_COMMAND_STAT, + POP3_COMMAND_LIST, + POP3_COMMAND_RETR, + POP3_COMMAND_DELE, + POP3_COMMAND_NOOP, + POP3_COMMAND_RSET, + POP3_COMMAND_QUIT, + POP3_COMMAND_PIPELINING, + POP3_COMMAND_RESP_CODES, + POP3_COMMAND_AUTH_RESP_CODE, + POP3_COMMAND_SASL_PLAIN_LOGIN, + POP3_COMMAND_MAX, +}; + +enum POP3_STATE { + POP3_STATE_WAITING_COMMAND, + POP3_STATE_WAITING_AUTHENTICATION, + POP3_STATE_WAITING_MAIL_DATA, + POP3_STATE_EXIT, +}; + +struct pop3_command { + enum POP3_COMMAND cmd; + char *arg; + size_t arg_len; + char *cmd_line; + size_t cmd_line_len; +}; + +enum POP3_AUTH_TYPE { + POP3_AUTH_TYPE_UNKNOWN, + POP3_AUTH_TYPE_USERPASS, + POP3_AUTH_TYPE_XOAUTH2, + POP3_AUTH_TYPE_MAX, +}; + +enum POP3_AUTH_STATE { + POP3_AUTH_INIT, + POP3_AUTH_WAITING_USERNAME, + POP3_AUTH_WAITING_PASSWORD, + POP3_AUTH_DONE, +}; + +struct pop3_authentication { + enum POP3_AUTH_TYPE type; + enum POP3_AUTH_STATE state; + char username[POP3_USERNAME_LEN_MAX]; + size_t username_len; + char password[POP3_PASSWORD_LEN_MAX]; + size_t password_len; +}; + +struct pop3_parser { + enum POP3_STATE state; + + struct stream_buffer line_buffer_c2s; + struct stream_buffer line_buffer_s2c; + struct pop3_authentication authentication; + + struct mail_parser *mail_parser; + struct mail_env *mail_env_ref; +}; + +int pop3_identify(const char *payload, size_t payload_len, int is_c2s); +int pop3_parser_process(struct pop3_parser *parser, struct session *sess, const char *payload, size_t payload_len, int is_c2s); + diff --git a/decoders/mail/mail_smtp.c b/decoders/mail/mail_smtp.c new file mode 100644 index 0000000..a05f0d6 --- /dev/null +++ b/decoders/mail/mail_smtp.c @@ -0,0 +1,503 @@ +#include +#include +#include +#include + +#include "mail_smtp.h" + +const char* g_smtp_command[] = { + "unknown", + "ehlo", + "helo", + "auth", + "starttls", + "mail", + "send", + "soml", + "saml", + "rcpt", + "data", + "vrfy", + "expn", + "noop", + "rset", + "quit", +}; + +static enum SMTP_COMMAND smtp_str2command(const char *payload, size_t payload_len) +{ + int i; + if (payload == NULL || payload_len == 0) { + return SMTP_COMMAND_UNKNOWN; + } + for(i = 1; i < SMTP_COMMAND_MAX; i++) { + if(0 == SAFE_STRNCASECMP(payload, payload_len, g_smtp_command[i], strlen(g_smtp_command[i]))) { + break; + } + } + if (i == SMTP_COMMAND_MAX) { + return SMTP_COMMAND_UNKNOWN; + } + return (enum SMTP_COMMAND)i; +} + +static int is_smtp_cmd(const char *data, int datalen) +{ + if (SMTP_COMMAND_UNKNOWN == smtp_str2command(data, datalen)) { + return 0; + } + return 1; +} + +static int is_smtp_res(const char *data, size_t datalen) +{ + if (datalen >= strlen(SMTP_STR_220) && 0 == strncmp(data, SMTP_STR_220, strlen(SMTP_STR_220)) { + return 1; + } + return 0; +} + +int smtp_identify(const char *payload, size_t payload_len, int is_c2s) +{ + if (payload == NULL || payload_len < 4) { + return 0; + } + + if (is_c2s) { + if (is_smtp_cmd(payload, payload_len)) { + return 1; + } + } else { + if (is_smtp_res(payload, payload_len)) { + return 1; + } + } + return 0; +} + +static int smtp_parse_authentication_xoauth2(struct smtp_authentication *auth, const char *line_start, size_t line_len) +{ + int decoded; + char decode_buf[512]; + char *username; + size_t username_len; + + decoded = Base64_DecodeBlock((unsigned char *)line_start, line_len, (unsigned char *)decode_buf, sizeof(decode_buf)); + if (decoded < 0) { + return -1; + } + + if (0 != mail_xoauth2_get_username((const char *)decode_buf, decoded, &username, &username_len)) { + return -1; + } + + if (username_len >= sizeof(auth->username)) { + return -1; + } + + memcpy(auth->username, username, username_len); + auth->username_len = username_len; + + auth->state = SMTP_AUTH_DONE; + return 0; +} + +static int smtp_parse_authentication_plain(struct smtp_authentication *auth, const char *line_start, size_t line_len) { + int decoded; + char decode_buf[512]; + char *current_pos; + char *username_start = NULL; + char *password_start = NULL; + size_t remaining_len = decoded; + + decoded = Base64_DecodeBlock((unsigned char *)line_start, line_len, (unsigned char *)decode_buf, sizeof(decode_buf)); + if (decoded < 0) { + return -1; + } + + // \0\0\0 + current_pos = decode_buf; + + while (remaining_len > 0 && *current_pos == '\0') { + current_pos++; + remaining_len--; + } + + username_start = current_pos; + while (remaining_len > 0 && *current_pos != '\0') { + current_pos++; + remaining_len--; + } + + if (remaining_len > 0 && *current_pos == '\0') { + current_pos++; + remaining_len--; + } + + password_start = current_pos; + + size_t username_len = username_start ? (size_t)(current_pos - username_start - 1) : 0; + size_t password_len = password_start ? (size_t)(decode_buf + decoded - password_start - 1) : 0; + + if (username_len >= SMTP_USERNAME_LEN_MAX || password_len >= SMTP_PASSWORD_LEN_MAX) { + return -1; + } + + if (username_len > 0) { + memcpy(auth->username, username_start, username_len); + auth->username_len = username_len; + } else { + auth->username_len = 0; + } + + if (password_len > 0) { + memcpy(auth->password, password_start, password_len); + auth->password_len = password_len; + } else { + auth->password_len = 0; + } + + auth->state = SMTP_AUTH_DONE; + return 0; +} + +static int smtp_parse_authentication_login(struct smtp_authentication *auth, const char *line_start, size_t line_len) +{ + int decoded; + char decode_buf[512]; + + decoded = Base64_DecodeBlock((unsigned char *)line_start, line_len, (unsigned char *)decode_buf, sizeof(decode_buf)); + if (decoded < 0) { + return -1; + } + + if (auth->state == SMTP_AUTH_WAITING_USERNAME) { + auth->state = SMTP_AUTH_WAITING_PASSWORD; + + if (decoded > sizeof(auth->username)) { + return -1; + } + memcpy(auth->username, decode_buf, decoded); + return 0; + } + + if (auth->state == SMTP_AUTH_WAITING_PASSWORD) { + auth->state = SMTP_AUTH_DONE; + + if (decoded > SMTP_PASSWORD_LEN_MAX) { + return -1; + } + memcpy(auth->password, decode_buf, decoded); + return 0; + } + + return 0; +} + +static enum SMTP_AUTH_STATE smtp_parse_authentication(struct smtp_authentication *auth, enum SMTP_AUTH_TYPE type, const char *line_start, size_t line_len) +{ + int ret = 0; + + if (auth->state == SMTP_AUTH_DONE) { + return SMTP_AUTH_DONE; + } + + switch (type) { + case SMTP_AUTH_TYPE_LOGIN: + ret = smtp_parse_authentication_login(auth, line_start, line_len); + case SMTP_AUTH_TYPE_PLAIN: + ret = smtp_parse_authentication_plain(auth, line_start, line_len); + case SMTP_AUTH_TYPE_XOAUTH2: + ret =smtp_parse_authentication_xoauth2(auth, line_start, line_len); + default: + break; + } + + if (ret != 0) { + return SMTP_AUTH_DONE; + } + + return auth->state;; +} + +int smtp_parse_command_auth(struct smtp_authentication *auth, struct smtp_command *smtp_cmd) +{ + if (0 == SAFE_STRNCASECMP(smtp_cmd.arg, smtp_cmd.arg_len, "login", strlen("login"))) { + auth->type = SMTP_AUTH_TYPE_LOGIN; + auth->state = SMTP_AUTH_WAITING_USERNAME; + } + if (0 == SAFE_STRNCASECMP(smtp_cmd.arg, smtp_cmd.arg_len, "plain", strlen("plain"))) { + auth->type = SMTP_AUTH_TYPE_PLAIN; + auth->state = SMTP_AUTH_WAITING_USERNAME; + } + if (0 == SAFE_STRNCASECMP(smtp_cmd.arg, smtp_cmd.arg_len, "xoauth2", strlen("xoauth2"))) { + auth->type = SMTP_AUTH_TYPE_XOAUTH2; + auth->state = SMTP_AUTH_WAITING_USERNAME; + } + return 0; +} + +int smtp_parse_command(struct smtp_command *smtp_cmd, const char *line_start, size_t line_len) +{ + int ret; + struct iovec parts[SMTP_COMMAND_LINE_PART_NUM]; + + memset(smtp_cmd, 0 , sizeof(struct smtp_command)); + + ret = strnsplit(parts, sizeof(parts)/sizeof(parts[0]), ' ', line_start, line_len); + if (ret <= 0 || ret > SMTP_COMMAND_LINE_PART_NUM) { + return -1; + } + + smtp_cmd->cmd = smtp_str2command(line_start, line_len); + smtp_cmd->cmd_line = line_start; + smtp_cmd->cmd_line_len = line_len; + + if (ret == SMTP_COMMAND_LINE_PART_NUM) { + smtp_cmd->arg = parts[SMTP_COMMAND_LINE_PART_ARG].iov_base; + smtp_cmd->arg_len = parts[SMTP_COMMAND_LINE_PART_ARG].iov_len; + } + + return 0; +} + +static int smtp_parser_publish_command(struct smtp_parser *parser, struct session *sess, size_t mail_seq, struct smtp_command *smtp_cmd) +{ + int ret; + struct mq_runtime *runtime; + struct mail_message *msg; + + runtime = module_manager_get_mq_runtime(parser->mail_env_ref->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = MAIL_PROTOCOL_SMTP; + msg->command->cmd = MAIL_CMD_OTHER; + msg->command->arg = smtp_cmd->arg; + msg->command->arg_len = smtp_cmd->arg_len; + msg->command->cmd_line = smtp_cmd->cmd_line; + msg->command->cmd_line_len = smtp_cmd->cmd_line_len; + + switch (smtp_cmd->cmd) { + case SMTP_COMMAND_MAIL: + struct iovec mail_from; + ret = mail_extract_email_address(smtp_cmd.arg, smtp_cmd.arg_len, mail_from); + if (ret != 0) { + break; + } + msg->command.cmd = MAIL_CMD_MAIL_FROM; + msg->command.arg = mail_from.iov_base; + msg->command.arg_len = mail_from.iov_len; + break; + case SMTP_COMMAND_RCPT: + case SMTP_COMMAND_SEND: + case SMTP_COMMAND_SAML: + case SMTP_COMMAND_SOML: + struct iovec rcpt_to; + ret = mail_extract_email_address(smtp_cmd.arg, smtp_cmd.arg_len, mail_from); + if (ret != 0) { + break; + } + msg->command.cmd = MAIL_CMD_RCPT_TO; + msg->command.arg = mail_from.iov_base; + msg->command.arg_len = mail_from.iov_len; + break; + case SMTP_COMMAND_STARTTLS: + msg->command.cmd = MAIL_CMD_STARTTLS; + msg->command.arg = NULL; + msg->command.arg_len = 0; + break; + case SMTP_COMMAND_EHLO: + case SMTP_COMMAND_HELO: + msg->command.cmd = MAIL_CMD_EHLO; + break; + default: + break; + } + + mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); +} + +static int smtp_parser_publish_username(struct smtp_parser *parser, struct session *sess, size_t mail_seq, const char *username, size_t username_len) +{ + int ret; + struct mq_runtime *runtime; + struct mail_message *msg; + + if (username == NULL || username_len == 0) { + return -1; + } + + runtime = module_manager_get_mq_runtime(parser->mail_env_ref->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = MAIL_PROTOCOL_SMTP; + msg->command->cmd = MAIL_CMD_USERNAME; + msg->command->arg = username; + msg->command->arg_len = username_len; + msg->command->cmd_line = username; + msg->command->cmd_line_len = username_len; + + mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); + + return 0; +} + +static int smtp_parser_publish_password(struct smtp_parser *parser, struct session *sess, size_t mail_seq, const char *password, size_t password_len) +{ + int ret; + struct mq_runtime *runtime; + struct mail_message *msg; + + if (password == NULL || password_len == 0) { + return -1; + } + + runtime = module_manager_get_mq_runtime(parser->mail_env_ref->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = MAIL_PROTOCOL_SMTP; + msg->command->cmd = MAIL_CMD_PASSWORD; + msg->command->arg = password; + msg->command->arg_len = password_len; + msg->command->cmd_line = password; + msg->command->cmd_line_len = password_len; + + mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); + + return 0; +} + +int smtp_parser_process_c2s(struct smtp_parser *smtp, struct session *sess, const char *payload, size_t payload_len, int is_c2s) +{ + int ret; + struct smtp_command *smtp_cmd; + enum SMTP_AUTH_STATE auth_state; + + if (smtp->state == SMTP_STATE_EXIT) { + return 0; + } + + // mail + if (smtp->state == SMTP_STATE_WAITING_MAIL_DATA) { + ret = mail_parser_process(smtp->mail_parser, payload, payload_len); + if (ret != 0) { + smtp->state = SMTP_STATE_EXIT; + return -1; + } + return 0; + } + + line_buffer_clear(smtp->line_buffer); // clear completed line + line_buffer_write(smtp->line_buffer, payload, payload_len); + while (1) { + const char *line_start; + size_t line_len; + + // line + ret = line_buffer_readln(smtp->line_buffer, &line_start, &line_len); + if (ret != 0) { + smtp->state = SMTP_STATE_EXIT; + return -1; + } + + if (line_start == NULL || line_len == 0) { + break; + } + + switch (smtp->state) { + // auth + case SMTP_STATE_WAITING_AUTHENTICATION: + auth_state = smtp_parse_authentication(&smtp->authentication, line_start, line_len); + if (auth_state == SMTP_AUTH_DONE) { + smtp_parser_publish_username(smtp, sess, mail_parser_get_seq(smtp->mail_parser), smtp->authentication.username, smtp->authentication.username_len); + smtp_parser_publish_password(smtp, sess, mail_parser_get_seq(smtp->mail_parser), smtp->authentication.password, smtp->authentication.password_len); + smtp->state = SMTP_STATE_WAITING_COMMAND; + } + break; + // command + case SMTP_STATE_WAITING_COMMAND: + struct smtp_command smtp_cmd; + ret = smtp_parse_command(&smtp_cmd, line_start, line_len); + if (ret != 0) { + smtp->state = SMTP_STATE_EXIT; + return -1; + } + + switch (smtp_cmd.cmd) { + case SMTP_COMMAND_AUTH: + smtp_parse_command_auth(&smtp->authentication, &smtp_cmd); + smtp->state = SMTP_STATE_WAITING_AUTHENTICATION; + break; + case SMTP_COMMAND_DATA: + smtp->state = SMTP_STATE_WAITING_MAIL_DATA; + break; + case SMTP_COMMAND_RSET: + case SMTP_COMMAND_QUIT: + case SMTP_COMMAND_STARTTLS: + smtp->state = SMTP_STATE_EXIT; + break; + default: + break; + } + + smtp_parser_publish_command(smtp, sess, mail_parser_get_seq(smtp->mail_parser), smtp_cmd); + break; + default: + break; + } + } + + return 0; +} + +int smtp_parser_process_s2c(struct smtp_parser *smtp, struct session *sess, const char *payload, size_t payload_len, int is_c2s) +{ + return 0; +} + +int smtp_parser_process(struct smtp_parser *parser, struct session *sess, const char *payload, size_t payload_len, int is_c2s) +{ + if (is_c2s) { + return smtp_parser_process_c2s(parser, sess, payload, payload_len); + } else { + return smtp_parser_process_s2c(parser, sess, payload, payload_len); + } +} + +void smtp_parser_free(struct smtp_parser *parser) +{ + if (parser) { + if (parser->line_buffer) { + stream_buffer_deinit(parser->line_buffer); + } + if (parser->mail_parser) { + mail_parser_free(parser->mail_parser); + } + free(parser); + } +} + +struct smtp_parser * smtp_parser_new(struct mail_env *mail_env) +{ + struct smtp_parser *parser = (struct smtp_parser *)calloc(1, sizeof(struct smtp_parser)); + parser->mail_env_ref = mail_env; + parser->mail_parser = mail_parser_new(mail_env); + stream_buffer_init(&parser->line_buffer, SMTP_LINE_BUFFER_MAX); + return parser; +} + diff --git a/decoders/mail/mail_smtp.h b/decoders/mail/mail_smtp.h new file mode 100644 index 0000000..5d74b48 --- /dev/null +++ b/decoders/mail/mail_smtp.h @@ -0,0 +1,90 @@ +#pragma once + +#include "mail_internal.h" +#include "mail_util.h" +#include "mail_email.h" + +#define SMTP_STR_220 "220" +#define SMTP_STR_HELO "HELO " +#define SMTP_STR_EHLO "EHLO " +#define SMTP_LINE_END "\r\n" + +#define SMTP_COMMAND_LINE_PART_CMD 0 +#define SMTP_COMMAND_LINE_PART_ARG 1 +#define SMTP_COMMAND_LINE_PART_NUM 2 + +#define SMTP_LINE_BUFFER_MAX 4096 +#define SMTP_USERNAME_LEN_MAX 128; +#define SMTP_PASSWORD_LEN_MAX 128; + +enum SMTP_COMMAND { + SMTP_COMMAND_UNKNOWN, + SMTP_COMMAND_EHLO, + SMTP_COMMAND_HELO, + SMTP_COMMAND_AUTH, + SMTP_COMMAND_STARTTLS, + SMTP_COMMAND_MAIL, + SMTP_COMMAND_SEND, + SMTP_COMMAND_SOML, + SMTP_COMMAND_SAML, + SMTP_COMMAND_RCPT, + SMTP_COMMAND_DATA, + SMTP_COMMAND_VRFY, + SMTP_COMMAND_EXPN, + SMTP_COMMAND_NOOP, + SMTP_COMMAND_RSET, + SMTP_COMMAND_QUIT, + SMTP_COMMAND_MAX, +}; + +enum SMTP_STATE { + SMTP_STATE_WAITING_COMMAND, + SMTP_STATE_WAITING_AUTHENTICATION, + SMTP_STATE_WAITING_MAIL_DATA, + SMTP_STATE_EXIT, +}; + +struct smtp_command { + enum SMTP_COMMAND cmd; + char *arg; + size_t arg_len; + char *cmd_line; + size_t cmd_line_len; +}; + +enum SMTP_AUTH_TYPE { + SMTP_AUTH_TYPE_UNKNOWN, + SMTP_AUTH_TYPE_LOGIN, + SMTP_AUTH_TYPE_PLAIN, + SMTP_AUTH_TYPE_XOAUTH2, + SMTP_AUTH_TYPE_MAX, +}; + +enum SMTP_AUTH_STATE { + SMTP_AUTH_INIT, + SMTP_AUTH_WAITING_USERNAME, + SMTP_AUTH_WAITING_PASSWORD, + SMTP_AUTH_DONE, +}; + +struct smtp_authentication { + enum SMTP_AUTH_TYPE type; + enum SMTP_AUTH_STATE state; + char username[SMTP_USERNAME_LEN_MAX]; + size_t username_len; + char password[SMTP_PASSWORD_LEN_MAX]; + size_t password_len; +}; + +struct smtp_parser { + enum SMTP_STATE state; + + struct stream_buffer line_buffer; + struct smtp_authentication authentication; + + struct mail_parser *mail_parser; + struct mail_env *mail_env_ref; +}; + +int smtp_identify(const char *payload, size_t payload_len, int is_c2s); +int smtp_parser_process(struct smtp_parser *parser, struct session *sess, const char *payload, size_t payload_len, int is_c2s); diff --git a/decoders/mail/mail_util.c b/decoders/mail/mail_util.c new file mode 100644 index 0000000..aa1a776 --- /dev/null +++ b/decoders/mail/mail_util.c @@ -0,0 +1,1453 @@ +#include +#include +#include +#include +#include +#include + +#include "mail_util.h" + +void line_buffer_clear(struct stream_buffer *line_buffer) +{ + stream_buffer_slide(line_buffer); +} + +void line_buffer_write(struct stream_buffer *line_buffer, const char *payload, size_t payload_len) +{ + stream_buffer_append(line_buffer, payload, payload_len); +} + +int line_buffer_readln(struct stream_buffer *line_buffer, const char **line_start, size_t *line_len) +{ + size_t buf_len; + const char *buf; + const char *line_end; + + *line_start = NULL; + *line_len = 0; + + if (stream_buffer_is_full(line_buffer)) { + stream_buffer_reset(line_buffer); + return -1; + } + + stream_buffer_get_data(line_buffer, &buf, &buf_len, 0); + line_end = (const char *)memmem(buf, buf_len, SMTP_LINE_END, strlen(SMTP_LINE_END)); + if (line_end == NULL) { + return 0; + } + + *line_start = buf; + *line_len = line_end - buf + strlen(SMTP_LINE_END); + + stream_buffer_shift(line_buffer, *line_len); + + return 1; +} + +static inline void stream_buffer_deinit(struct stream_buffer *sb) +{ + sb->buf_size = 0; + sb->buf_len = 0; + sb->buf_off = 0; + if (sb->buf) { + free(sb->buf); + } +} + +static inline void stream_buffer_init(struct stream_buffer *sb, size_t size) +{ + sb->buf_size = size; + sb->buf_len = 0; + sb->buf_off = 0; + sb->buf = NULL; +} + +static inline int stream_buffer_is_full(struct stream_buffer *sb) +{ + if (sb->buf && sb->buf_off + sb->buf_len == sb->buf_size) { + return 1; + } + return 0; +} + +static inline int stream_buffer_is_empty(struct stream_buffer *sb) +{ + if (sb->buf == NULL || sb->buf_off + sb->buf_len == 0) { + return 1; + } + return 0; +} + +static inline void stream_buffer_reset(struct stream_buffer *sb) +{ + if (sb->buf && sb->buf_off + sb->buf_len > 0) { + sb->buf_len = 0; + sb->buf_off = 0; + } +} + +static inline void stream_buffer_append(struct stream_buffer *sb, const char *data, size_t data_len) +{ + if (sb->buf == NULL && sb->buf_size > 0) { + sb->buf = (char *)malloc(sb->buf_size); + sb->buf_len = 0; + sb->buf_off = 0; + } + if (sb->buf_off + sb->buf_len + data_len > sb->buf_size) { + data_len = sb->buf_size - sb->buf_off - sb->buf_len; + } + memcpy(sb->buf + sb->buf_off + sb->buf_len, data, data_len); +} + +static inline void stream_buffer_shift(struct stream_buffer *sb, size_t offset) +{ + if (sb->buf) { + if (sb->buf_len > offset) { + sb->buf_len -= offset; + sb->buf_off += offset; + } else { + sb->buf_off += sb->buf_len; + sb->buf_len = 0; + } + } +} + +static inline void stream_buffer_slide(struct stream_buffer *sb) +{ + if (sb->buf && sb->buf_off > 0) { + if (sb->buf_len > 0) { + memmove(sb->buf, sb->buf + sb->buf_off, sb->buf_len); + sb->buf_off = 0; + } else { + stream_buffer_reset(sb); + } + } +} + +static inline void stream_buffer_get_data(struct stream_buffer *sb, const char **data, size_t *data_len, size_t offset) +{ + *data = NULL; + *data_len = 0; + if (sb->buf && sb->buf_len > offset) { + *data = sb->buf + sb->buf_off + offset; + *data_len = sb->buf_len - offset; + } +} + +static char* strntrim_sp(char *str, size_t *len) { + char *end; + + while (*str == ' ' && *len > 0) { + str++; + (*len)--; + } + + if (*len == 0) { + return str; + } + + end = str + *len - 1; + while (end > str && *end == ' ') { + end--; + (*len)--; + } + + return str; +} + +int strnsplit(struct iovec *out, size_t n_out, char delimiter, const char *data, size_t data_len) +{ + size_t i; + size_t offset; + size_t part_len; + const char *data_end; + const char *part_start; + const char *part_end; + + if (out == NULL || n_out == 0 || data == NULL || data_len == 0) { + return -1; + } + + offset = 0; + data_end = data + data_len; + + for (i = 0; i < n_out - 1; i++) { + part_start = data + offset; + part_end = memchr(part_start, delimiter, data_end - part_start); + + if (part_end != NULL) { + // Found a delimiter + part_len = part_end - part_start; + out[i].iov_base = strntrim_sp((char*)part_start, &part_len); + out[i].iov_len = part_len; + offset += part_len + 1; + } else { + // Last part, no delimiter found + part_len = data_end - part_start; + out[i].iov_base = strntrim_sp((char*)part_start, &part_len); + out[i].iov_len = part_len; + offset += part_len; + return i + 1; + } + + if (offset >= data_len) { + return i + 1; + } + } + + // last part + part_start = data + offset; + part_len = data_end - part_start; + out[i].iov_base = strntrim_sp((char*)part_start, &part_len); + out[i].iov_len = part_len; + + return n_out; +} + +int mail_xoauth2_get_username(const char *buf, size_t len, char **username, size_t *username_len) { + if (!buf || len == 0 || !username || !username_len) { + return -1; + } + + const char *user_prefix = "user="; + size_t prefix_len = strlen(user_prefix); + + const char *username_start = NULL; + for (size_t i = 0; i <= len - prefix_len; i++) { + if (strncasecmp(buf + i, user_prefix, prefix_len) == 0) { + username_start = buf + i + prefix_len; + break; + } + } + + if (!username_start) { + return -1; + } + + const char *username_end = username_start; + while (username_end < buf + len && *username_end != ';') { + username_end++; + } + + *username_len = username_end - username_start; + *username = (char *)username_start; + + return 0; +} + +int mail_extract_email_address(const char *buffer, size_t length, struct iovec *email_address) +{ + if (email_address == NULL) { + return -1; + } + + email_address->iov_base = NULL; + email_address->iov_len = 0; + + const char *start = (const char *)memchr(buffer, '<', length); + if (!start) { + return -2; + } + start++; + + const char *end = (const char *)memchr(start, '>', buffer + length - start); + if (!end) { + return -3; + } + + const char *at_sign = (const char *)memchr(start, '@', end - start); + if (!at_sign) { + return -4; + } + + email_address->iov_base = (void *)start; + email_address->iov_len = end - start; + + return 0; +} + +int mail_publish_command(struct mail_env *mail_env, struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + enum MAIL_COMMAND command, + char *command_param, size_t command_param_len, + char *command_line, size_t command_line_len) +{ + struct mq_runtime *runtime; + struct mail_message *msg; + + runtime = module_manager_get_mq_runtime(mail_env->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = mail_protocol; + msg->command = command; + msg->command_param = command_param; + msg->command_param_len = command_param_len; + msg->command_line = command_line; + msg->command_line_len = command_line_len; + + return mq_runtime_publish_message(runtime, mail_env->command_topic_id, msg); +} + +int mail_publish_header(struct mail_env *mail_env, struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + struct mail_header *header) +{ + struct mq_runtime *runtime; + struct mail_message *msg; + + runtime = module_manager_get_mq_runtime(mail_env->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = mail_protocol; + msg->header = *header; + + return mq_runtime_publish_message(runtime, mail_env->header_topic_id, msg); +} + +int mail_publish_body(struct mail_env *mail_env, struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + char *body, size_t body_len, + size_t body_offset, int is_finished) +{ + struct mq_runtime *runtime; + struct mail_message *msg; + + runtime = module_manager_get_mq_runtime(mail_env->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = mail_protocol; + msg->body = body; + msg->body_len = body_len; + msg->body_offset = body_offset; + msg->is_body_finished = is_finished; + + return mq_runtime_publish_message(runtime, mail_env->body_topic_id, msg); +} + +int mail_publish_attachment(struct mail_env *mail_env, struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + char *attachment_name, size_t attachment_name_len, + char *attachment, size_t attachment_len, + size_t attachment_offset, int is_finished) +{ + struct mq_runtime *runtime; + struct mail_message *msg; + + runtime = module_manager_get_mq_runtime(mail_env->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_seq = mail_seq; + msg->mail_protocol = mail_protocol; + msg->attachment_name = attachment_name; + msg->attachment_name_len = attachment_name_len; + msg->attachment = attachment; + msg->attachment_len = attachment_len; + msg->attachment_offset = attachment_offset; + msg->is_attachment_finished = is_finished; + + return mq_runtime_publish_message(runtime, mail_env->attachment_topic_id, msg); +} + +int mail_publish_eml(struct mail_env *mail_env, struct session *sess, + enum MAIL_PROTOCOL mail_protocol, + char *eml, size_t eml_len, + size_t eml_offset, int is_eml_finished) +{ + struct mq_runtime *runtime; + struct mail_message *msg; + + runtime = module_manager_get_mq_runtime(mail_env->mod_mgr_ref); + if (runtime == NULL) { + return -1; + } + + msg = (struct mail_message *)calloc(1, sizeof(struct mail_message)); + msg->sess_ref = sess; + msg->mail_protocol = mail_protocol; + msg->eml = eml; + msg->eml_len = eml_len; + msg->eml_offset = eml_offset; + msg->is_eml_finished = is_eml_finished; + + return mq_runtime_publish_message(runtime, mail_env->eml_topic_id, msg); +} + +int mail_message_process(struct mail_env *mail_env, struct mail_session_ctx *session_ctx) +{ + size_t mail_seq; + enum MAIL_PROTOCOL mail_protocol; + + mail_seq = mail_parser_get_seq(session_ctx->smtp); + mail_protocol = mail_parser_get_protocol(session_ctx->parser); + + enum MAIL_COMMAND cmd; + char *cmd_param; + size_t cmd_param_len; + char *cmd_line; + size_t cmd_line_len; + + ret = mail_parser_get_command(session_ctx->parser, &cmd, &cmd_param, &cmd_param_len, &cmd_line, &cmd_line_len); + if (ret == 0) { + mail_publish_command(mail_env, sess, mail_protocol, mail_seq, cmd, cmd_param, cmd_param_len, cmd_line, cmd_line_len); + } + + struct mail_header *header; + + header = mail_parser_get_header(session_ctx->parser); + if (header) { + mail_publish_header(mail_env, sess, mail_protocol, mail_seq, header); + } + + char *body; + size_t body_len; + size_t body_offset; + int is_body_finished; + + ret = mail_parser_get_body(session_ctx->parser, &body, &body_len, &body_offset, &is_body_finished); + if (ret == 0) { + mail_publish_body(mail_env, sess, mail_protocol, mail_seq, + body, body_len, + body_offset, is_body_finished); + } + + char *attachment_name; + size_t attachment_name_len; + char *attachment; + size_t attachment_len; + size_t attachment_offset; + int is_attachment_finished; + + ret = mail_parser_get_attachment(session_ctx->parser, &attachment_name, &attachment_name_len, &attachment, &attachment_len, &attachment_offset, &is_attachment_finished); + if (ret == 0) { + mail_publish_attachment(mail_env, sess, mail_protocol, mail_seq, + attachment_name, attachment_name_len, + attachment, attachment_len, + attachment_offset, is_attachment_finished); + } + + char *eml; + size_t eml_len; + size_t eml_offset; + int is_eml_finished; + + ret = mail_parser_get_eml(session_ctx->parser, &eml, &eml_len, &eml_offset, &is_eml_finished); + if (ret == 0) { + mail_publish_eml(mail_env, sess, mail_protocol, + eml, eml_len, + eml_offset, is_eml_finished); + } + +} + + + + +/*- + * 64 char lines + * pad input with 0 + * left over chars are set to = + * 1 byte => xx== + * 2 bytes => xxx= + * 3 bytes => xxxx + */ +static const char data_bin2ascii[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define conv_bin2ascii(a) (data_bin2ascii[(a)&0x3f]) + +/*- + * 0xF0 is a EOLN + * 0xF1 is ignore but next needs to be 0xF0 (for \r\n processing). + * 0xF2 is EOF + * 0xE0 is ignore at start of line. + * 0xFF is error + */ +#define B64_EOLN 0xF0 +#define B64_CR 0xF1 +#define B64_EOF 0xF2 +#define B64_WS 0xE0 +#define B64_ERROR 0xFF +#define B64_NOT_BASE64(a) (((a)|0x13) == 0xF3) +#define B64_BASE64(a) !B64_NOT_BASE64(a) + +static const unsigned char data_ascii2bin[128] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE0, 0xF0, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xF2, 0xFF, 0x3F, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, + 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +//切记不可以如此调用: conv_ascii2bin(x++); 因为会++两次 +#define conv_ascii2bin(aa) (((aa) & 0x80)?(0xFF):data_ascii2bin[(aa)]) + +/*static unsigned char conv_ascii2bin(unsigned char a) +{ + if (a & 0x80) + return B64_ERROR; + return data_ascii2bin[a]; + //return (((a) & 0x80)?B64_ERROR:data_ascii2bin[(a)]); +} +*/ + +/********************************************************************* +函数名称:Base64_EncodeBlock +功能简介:对一完整BASE64编码块进行编码 +输入参数:in:待编码的字符; + inl:in的长度 +输出参数:out:编码后存储的缓冲区; +返回值:编码后的长度 +*********************************************************************/ +int Base64_EncodeBlock(const unsigned char *in, int inl, unsigned char *out) +{ + int i, ret = 0; + unsigned long l; + + for (i = inl; i > 0; i -= 3) { + if (i >= 3) { + l = (((unsigned long)in[0]) << 16L) | + (((unsigned long)in[1]) << 8L) | in[2]; + *(out++) = conv_bin2ascii(l >> 18L); + *(out++) = conv_bin2ascii(l >> 12L); + *(out++) = conv_bin2ascii(l >> 6L); + *(out++) = conv_bin2ascii(l); + } else { + l = ((unsigned long)in[0]) << 16L; + if (i == 2) + l |= ((unsigned long)in[1] << 8L); + + *(out++) = conv_bin2ascii(l >> 18L); + *(out++) = conv_bin2ascii(l >> 12L); + *(out++) = (i == 1) ? '=' : conv_bin2ascii(l >> 6L); + *(out++) = '='; + } + ret += 4; + in += 3; + } + + *out = '\0'; + return (ret); +} + +/********************************************************************* +函数名称:Base64_DecodeBlock +功能简介:对一完整BASE64编码块进行解析,自动忽略首尾非BASE64编码字符 +输入参数:in:待解码的字符; + inl:in的长度 +输出参数:out:解码后存储的缓冲区; + 必须保证有足够的空间,一般达到@inl大小即可; +返回值:<0:失败;>=0:解码后的长度 +*********************************************************************/ +int Base64_DecodeBlock(const unsigned char *in, int inl, unsigned char *out, int outsize) +{ + int i, ret = 0; + unsigned char a, b, c, d; + unsigned long l; + + /* ignore not-base64-encoded charactor. */ + while ((conv_ascii2bin(*in) == B64_WS) && (inl > 0)) + { + in++; + inl--; + } + while ((inl > 3) && (B64_NOT_BASE64(conv_ascii2bin(in[inl - 1])))) + inl--; + + if (inl % 4 != 0) + return -1; + + if(outsize < (inl*3)/4) + return -2; + + for (i = 0; i < inl; i += 4) + { + a = conv_ascii2bin(*(in)); + b = conv_ascii2bin(*(in+1)); + c = conv_ascii2bin(*(in+2)); + d = conv_ascii2bin(*(in+3)); + if ((a & 0x80) || (b & 0x80) || (c & 0x80) || (d & 0x80)) + return (-1); + l = ((((unsigned long)a) << 18L) | (((unsigned long)b) << 12L) | + (((unsigned long)c) << 6L) | (((unsigned long)d))); + *(out++) = (unsigned char)(l >> 16L) & 0xff; + *(out++) = (unsigned char)(l >> 8L) & 0xff; + *(out++) = (unsigned char)(l) & 0xff; + ret += 3; + in+=4; + } + + for(i = inl; i > 0; i -= 4) + { + if(*(in-3) == '=') + { + in -= 4; + ret -= 3; + continue; + } + + while(*(--in) == '=') + ret -= 1; + + break; + } + + return ret; +} + +void Base64_DecodeInit(decode_handle_t *handle) +{ + handle->length = 0; +} + +/********************************************************************* +函数名称:Base64_DecodeFeed +功能简介:对部分BASE64编码块进行解析,自动忽略首尾非BASE64编码字符 +输入参数:in:待解码的字符; + inl:in的长度,可能不是4的倍数,对于小于4的部分,会转存一下, + 下次解码时将其拼接到输入数据的头部;转存的最大长度为3, + 因为如果达到了4,就可以将该部分解码出来; +输出参数:out:解码后存储的缓冲区; +返回值:-1:失败;0:需要更多的数据;>0:解码后的长度 +*********************************************************************/ +int Base64_DecodeFeed(decode_handle_t *handle, const unsigned char *in, int inl, unsigned char *out, int outsize) +{ + int i, j=0, dst_len = 0; + const unsigned char *pc=in, *pend=in+inl; + unsigned char de[4]; + unsigned long l; + + /* ignore not-base64-encoded charactor. */ + while(pcpc && B64_NOT_BASE64(conv_ascii2bin(*(pend-1)))) + pend--; + + if(handle->length>0) + { + while(pclength<4) + { + handle->enc_data[handle->length++] = *pc; + pc++; + } + if(handle->length < 4) + { + return 0; + } + else + { + for(i=0; i<4; i++) + { + de[j++] = conv_ascii2bin(handle->enc_data[i]); + } + } + } + + do + { + while(j < 4 && pc outsize) + return -2; + + if ((de[0] & 0x80) || (de[1] & 0x80) || (de[2] & 0x80) || (de[3] & 0x80)) + return (-1); + l = ((((unsigned long)de[0]) << 18L) | (((unsigned long)de[1]) << 12L) | + (((unsigned long)de[2]) << 6L) | (((unsigned long)de[3]))); + *(out++) = (unsigned char)(l >> 16L) & 0xff; + *(out++) = (unsigned char)(l >> 8L) & 0xff; + *(out++) = (unsigned char)(l) & 0xff; + + dst_len += 3; + }while(pc < pend); + + pc--; + //有残留,进行缓存,不要存换行符 + if(j>0) + { + handle->length = j; + while(j>0 && pc>=in) + { + handle->enc_data[j-1] = *pc; + pc--; + j--; + } + return dst_len; + } + //无解码残留 + else if(pc>=in && *pc == '=') + { + dst_len -= 1; + pc--; + if((pc>=in && *pc == '=') ||(handle->length>0 && handle->enc_data[handle->length-1]=='=')) + dst_len -= 1; + } + handle->length = 0; + + return dst_len; +} + +/********************************************************************* +函数名称:Base64_DecodeFeed_r_n +功能简介:与Base64_DecodeFeed不同之处在于: + 输入数据任何位置有可能出现'\r'或'\n',或一起出现; +输入参数:同Base64_DecodeFeed +输出参数:同Base64_DecodeFeed +返回值:同Base64_DecodeFeed +*********************************************************************/ +int Base64_DecodeFeed_r_n(decode_handle_t *handle, const unsigned char *in, int inl, unsigned char *out, int outsize) +{ + int i, j=0, dst_len = 0; + const unsigned char *pc=in, *pend=in+inl; + unsigned char de[4]; + unsigned long l; + + /* ignore not-base64-encoded charactor. */ + while(pcpc && B64_NOT_BASE64(conv_ascii2bin(*(pend-1)))) + pend--; + + //上一次有残留,先补齐4个字符到handle(为了结尾处判断残留是否是换行符) + if(handle->length>0) + { + while(pclength<4) + { + if(*pc=='\r' || *pc=='\n' || *pc==' ' || *pc=='\t') + { + pc++; + continue; + } + handle->enc_data[handle->length++] = *pc; + pc++; + } + if(handle->length < 4) + { + return 0; + } + else + { + for(i=0; i<4; i++) + { + de[j++] = conv_ascii2bin(handle->enc_data[i]); + } + } + } + + do + { + while(j < 4) + { + while(pc= pend) + break; + + de[j++] = conv_ascii2bin(*pc); + pc++; + } + if(j < 4) + break; + j = 0; + + if(dst_len + 3 > outsize) + return -2; + + if ((de[0] & 0x80) || (de[1] & 0x80) || (de[2] & 0x80) || (de[3] & 0x80)) + return (-1); + l = ((((unsigned long)de[0]) << 18L) | (((unsigned long)de[1]) << 12L) | + (((unsigned long)de[2]) << 6L) | (((unsigned long)de[3]))); + *(out++) = (unsigned char)(l >> 16L) & 0xff; + *(out++) = (unsigned char)(l >> 8L) & 0xff; + *(out++) = (unsigned char)(l) & 0xff; + + dst_len += 3; + }while(pc < pend); + + pc--; + //有残留,进行缓存,不要存换行符 + if(j>0) + { + handle->length = j; + while(j>0) + { + while(pc>=in && (*pc == '\r' || *pc == '\n' || *pc==' ' || *pc=='\t')) + { + pc--; + continue; + } + handle->enc_data[j-1] = *pc; + pc--; + j--; + } + return dst_len; + } + //无解码残留,如果尾部是=,修正实际的解码后长度 + else if(pc>=in && *pc == '=') + { + dst_len -= 1; + pc--; + if((pc>=in && *pc == '=') ||(handle->length>0 && handle->enc_data[handle->length-1]=='=')) + dst_len -= 1; + } + handle->length = 0; + + return dst_len; +} + +static unsigned char decode_hex( char *streamptr ) +{ + unsigned char result = 0; + char blivit; + + blivit = *streamptr++; + if ( blivit >= 'a' ) + blivit -= ' '; + if ( blivit > '9' ) + blivit -= 0x07; + + result = blivit & 0x0F; + result <<= 4; + + blivit = *streamptr++; + if ( blivit >= 'a' ) + blivit -= ' '; + if ( blivit > '9' ) + blivit -= 0x07; + + result |= blivit & 0x0F; + + return result; +} + +/********************************************************************* +函数名称:QP_DecodeBlock +功能简介:对一完整QP编码块进行解析,会忽略'\r''\n'字符 +输入参数:in:待解码的字符; + inl:in的长度 +输出参数:out:解码后存储的缓冲区, + 必须保证有足够的空间,一般达到@inl大小即可; +返回值:解码后的长度 +*********************************************************************/ +//"PPPPPP=X",如果X是一16进制数,是否保留"=X"?目前是保留。 +int QP_DecodeBlock(const unsigned char *in, int inl, unsigned char *out) +{ + int index=0, index2=0; //分别是buf、enc_data里内容长度 + const unsigned char *temp=in, *inputend=in+inl; + unsigned char value; + int state=0; + char buf[2]; + int dst_len=0; + + if(inl == 0) + return 0; + + while(temp '9')) && ((*temp < 'A') || (*temp > 'F')) && ((*temp < 'a') || (*temp > 'f'))) + { + out[dst_len++] = '='; + out[dst_len] = '\0'; + + if(index!=0) //遇到了=XY串,其中X是16进制数,Y不是 + { + out[dst_len++] = buf[0]; + out[dst_len] = '\0'; + } + if(*temp== '=') + { + state = 1; + } + else + { + out[dst_len++] = *temp; + out[dst_len] = '\0'; + state=0; + index2=0; + } + index=0; + } + //是可解码的字符,但是不够 + else if(index<1) + { + buf[index++]=*temp; + } + //进行解码 + else + { + buf[index++]=*temp; + value=decode_hex(buf); + out[dst_len++] = value; + out[dst_len]='\0'; + index=0; + state=0; + index2=0; + } + temp=temp+1; + break; + + case 2: + state = 0; + index = 0; + index2 = 0; + + if(*temp == '=') + { + index2++; + state=1; + } + else if(*temp!='\n' && *temp!='\r') + { + out[dst_len++] = *temp; + out[dst_len]='\0'; + } + temp++; + break; + + default: break; + } + } + + //保留最后不足以进行解码的数据 + if(index2!=0) + { + out[dst_len++] = '='; + out[dst_len]='\0'; + + if(index!=0) + { + out[dst_len++] = buf[0]; + out[dst_len]='\0'; + } + } + + return dst_len; +} + +void QP_DecodeInit(decode_handle_t *handle) +{ + handle->length = 0; +} + +/********************************************************************* +函数名称:QP_DecodeFeed +功能简介:对部分QP编码块进行解析 +输入参数:in:待解码的字符; + inl:in的长度,可能不足以解码出来,对于不足以解码的部分, + 会转存一下,下次解码时优先解决该部分,转存的最大长度为2; + 如果为1,必然是'=';如果为2,必然是"=X";X是一个16进制数; +输出参数:out:解码后存储的缓冲区; +返回值:-1:失败;0:需要更多的数据;>0:解码后的长度 +*********************************************************************/ +int QP_DecodeFeed(decode_handle_t *handle,const unsigned char *in, int inl, unsigned char *out) +{ + int index=0, index2=0; //分别是buf、enc_data里内容长度 + const unsigned char *temp=in, *inputend=in+inl; + unsigned char value; + int state=0; + char buf[2]; + int dst_len=0; + + if(inl == 0) + return 0; + + if(handle->length > 0) + { + if(handle->length == 1) + { + state = 1; + index2 = 1; + } + else if(handle->length == 2) + { + if(handle->enc_data[1] == '\n') + { + state = 2; + index2 = 2; + } + else + { + state = 1; + index2 = 1; + index = 1; + buf[0] = handle->enc_data[1]; + } + } + + handle->length = 0; + } + + while(temp '9')) && ((*temp < 'A') || (*temp > 'F')) && ((*temp < 'a') || (*temp > 'f'))) + { + out[dst_len++] = '='; + out[dst_len] = '\0'; + + if(index!=0) //遇到了=XY串,其中X是16进制数,Y不是 + { + out[dst_len++] = buf[0]; + out[dst_len] = '\0'; + } + if(*temp== '=') + { + state = 1; + } + else + { + out[dst_len++] = *temp; + out[dst_len] = '\0'; + state=0; + index2=0; + } + index=0; + } + //是可解码的字符,但是不够 + else if(index<1) + { + buf[index++]=*temp; + } + //进行解码 + else + { + buf[index++]=*temp; + value=decode_hex(buf); + out[dst_len++] = value; + out[dst_len]='\0'; + index=0; + state=0; + index2=0; + } + temp=temp+1; + break; + + case 2: + state = 0; + index = 0; + index2 = 0; + + if(*temp == '=') + { + index2++; + state=1; + } + else if(*temp!='\n' && *temp!='\r') + { + out[dst_len++] = *temp; + out[dst_len]='\0'; + } + temp++; + break; + + default: break; + } + } + + if(index2!=0) + { + handle->enc_data[0] = '='; + handle->length = 1; + + if(index2 == 2) + handle->enc_data[handle->length++] = '\n'; + else if(index!=0) + { + handle->enc_data[1] = buf[0]; + handle->length = 2; + } + } + + return dst_len; +} + + +int strncasecmp_one_word_mesa(const char *str1, size_t len1, const char *str2, size_t len2) +{ + if (len2 != len1 || len1==0) + return 0; + + do { + if(*str1 - 'a' > 0) //str1小写 + { + if (*str1 == *str2 || toupper(*str1) == *str2) + { + ++str1; + ++str2; + continue; + } + } + else + { + if (*str1 == *str2 || tolower(*str1) == *str2) + { + ++str1; + ++str2; + continue; + } + } + return 0; + } while (--len1); + + return 1; +} + + +int strncmp_one_word_mesa(const char *s1_lowercase, const char *s1_uppercase, size_t len1, const char *s2, size_t len2) +{ + if (len1 > len2) + return 0; + + const unsigned char *s1, *s12; + + if (islower(s2[len1-1])) + { + s1 = (const unsigned char *)s1_lowercase; + s12= (const unsigned char *)s1_uppercase; + } + else + { + s1 = (const unsigned char *)s1_uppercase; + s12= (const unsigned char *)s1_lowercase; + } + + while(len1--) { + if (*s1 == *s2 || *s12 == *s2) + { + ++s1; + ++s12; + ++s2; + continue; + } + return 0; + } + + return 1; +} + + +/* +* mesa_memicmp2 +* FUNCTION +* compare s2 with s1, ignorance of case +* INPUT +* @s1_lowercase MUST be lowercase +* @s1_uppercase MUST be uppercase +* @len1 is the length of s1 +* @s2 is the string you want to be compared +* @len2 is the length of s2 +* OUTPUT: +* 0:s1==s2 +* -1:s1!=s2 +*/ +int strcmp_one_word_mesa(const char *s1_lowercase, const char *s1_uppercase, size_t len1, const char *s2, size_t len2) +{ + unsigned char *s1,*s12; + + if (len1 != len2) + return 0; + + if (s2[len2-1]-'a'>=0) + { + s1 = (unsigned char *)s1_lowercase; + s12= (unsigned char *)s1_uppercase; + } + else + { + s1 = (unsigned char *)s1_uppercase; + s12= (unsigned char *)s1_lowercase; + } + + do { + if (*s1 == *s2 || *s12 == *s2) + { + ++s1; + ++s12; + ++s2; + continue; + } + return 0; + } while (--len1); + + return 1; +} + +//相对于strcmp_one_word_mesa仅仅去掉了长度相等的比较 +int strcmp_one_word_mesa_equal_len(const char *s1_lowercase, const char *s1_uppercase, const char *s2, size_t len) +{ + unsigned char *s1,*s12; + + if (s2[len-1]-'a'>=0) + { + s1 = (unsigned char *)s1_lowercase; + s12= (unsigned char *)s1_uppercase; + } + else + { + s1 = (unsigned char *)s1_uppercase; + s12= (unsigned char *)s1_lowercase; + } + + do { + if (*s1 == *s2 || *s12 == *s2) + { + ++s1; + ++s12; + ++s2; + continue; + } + return 0; + } while (--len); + + return 1; +} + +int strncmp_two_word_mesa(const char *s1_lower, const char *s1_upper, size_t len1, + const char *s2_lower, const char *s2_upper, size_t len2, const char *data, int dlen) +{ + int matchlen=0; + + if(!strncmp_one_word_mesa(s1_lower, s1_upper, len1, data, dlen)) + { + return 0; + } + data += len1; dlen-=len1; matchlen+=len1; + while(dlen>0 && *data==' ') + { + data++;dlen--;matchlen++; + } + if(dlen == 0) return 0; + + if(!strncmp_one_word_mesa(s2_lower, s2_upper, len2, data, dlen)) + { + return 0; + } + data += len2; dlen-=len2; matchlen+=len2; + while(dlen>0 && *data==' ') + { + data++;dlen--;matchlen++; + } + return matchlen; +} + +/********************************************************************* +函数名称:mail_strcmp_two_word +功能简介:判断一个由2个单词组成的字符串是不是目标SMTP命令字符串,含':', + 忽略目标字符串2个单词及':'之间的空格,如"mail from :",忽略大小写 +输入参数:srcdata: +输出参数:无 +返回值: 1:是 + 0:不是 +*********************************************************************/ +int strcmp_two_word(char *srcdata,int datalen, const char *first, int flen, const char *second, int slen) +{ + char * tok; + + if(datalen < flen+slen+1) + return 0; + + if(strncasecmp(srcdata, first, flen) != 0) + { + return 0; + } + + tok = srcdata + flen; + while(*tok==' ') + tok++; + + if(strncasecmp(tok, second, slen) != 0) + return 0; + + tok += slen; + while(*tok==' ') + tok++; + if( *tok != ':' ) + return 0; + + return 1; +} + + +//查找开始位置 +const void *mail_memmem(const void *start, unsigned int s_len, const void *find, unsigned int f_len) +{ + const char *p, *q; + unsigned int len; + + p = (const char *)start, q = (const char *)find; + len = 0; + while((p - (const char *)start + f_len) <= s_len) + { + while(*p++ == *q++) + { + len++; + if(len == f_len) + return(p - f_len); + }; + q = (const char *)find; + len = 0; + }; + + return NULL; +} + +/* +* mesa_memncasemem3 +* FUNCTION +* find needle in haystack, ignorance of case +* INPUT +* @needle MUST be lowercase +* @needlelen is the length of needle +* OUTPUT +* NULL if not found +*/ +char *mesa_memncasemem(const char *haystack, size_t haystacklen,const char *needle, size_t needlelen) +{ + unsigned char *psub,*psrc,*p,*pendsrc,*pendsubstr; + int diff=-32; //A=a-32; + + if ((haystack==NULL) || needle==NULL) + return NULL; + if (haystacklen= (s2_len) ? strncasecmp(s1, s2, s2_len) : -1) + +struct stream_buffer { + char *buf; + size_t buf_off; + size_t buf_len; + size_t buf_size; +}; + +// clear already readed complete lines +void line_buffer_clear(struct stream_buffer *line_buffer); +void line_buffer_write(struct stream_buffer *line_buffer, const char *payload, size_t payload_len); +int line_buffer_readln(struct stream_buffer *line_buffer, const char **line_start, size_t *line_len); + +int mail_extract_email_address(const char *buffer, size_t length, struct iovec *email_address); + + + +#define MAIL_DIM(a) (sizeof (a) / sizeof ((a)[0])) + +//用户一般不用关心该结构体成员,除非想关注解码后是否有数据残留; +//但是在进行新的解码之前需要调用QP_DecodeInit重新进行初始化; +typedef struct __decode_handle { + int length; //解码残留的数据长度 + unsigned char enc_data[4]; +} decode_handle_t; + +void QP_DecodeInit(decode_handle_t *handle); + +/****************************QP_DecodeBlock***************************************** +函数名称:QP_DecodeFeed/QP_DecodeBlock +输入参数:in:待解码的字符,会忽略'\r''\n'字符 + inl:f的长度 +输出参数:out:解码后存储的缓冲区; + 必须保证有足够的空间,否则引起内存访问错误,一般达到@inl大小即可; +返回值:<0:失败;>=0:解码后的长度 +*********************************************************************/ +//输入数据为任意长度,如果不足以解码出来,会转存到下次; +int QP_DecodeFeed(decode_handle_t *handle,const unsigned char *in, int inl, unsigned char *out); + +//对一个完整块进行解码 +int QP_DecodeBlock(const unsigned char *in, int inl, unsigned char *out); + +int Base64_EncodeBlock(const unsigned char *in, int inl, unsigned char *out); + +void Base64_DecodeInit(decode_handle_t *handle); + +/********************************************************************* +函数名称:Base64_DecodeBlock/Base64_DecodeFeed/Base64_DecodeFeed_r_n +输入参数:in:待解码的字符; + inl:in的长度 +输出参数:out:解码后存储的缓冲区; + 必须保证有足够的空间,否则引起内存访问错误,一般达到@inl大小即可; +返回值:<0:失败;>=0:解码后的长度 +*********************************************************************/ +//输入数据为任意长度,如果不足以解码出来,会转存到下次; +int Base64_DecodeFeed(decode_handle_t *handle, const unsigned char *in, int inl, unsigned char *out, int outsize); + +//与Base64_DecodeFeed不同之处在于: 输入数据任何位置有可能出现'\r'或'\n',或一起出现; +int Base64_DecodeFeed_r_n(decode_handle_t *handle, const unsigned char *in, int inl, unsigned char *out, int outsize); + +//对一个完整块进行解码 +int Base64_DecodeBlock(const unsigned char *in, int inl, unsigned char *out, int outsize); + +int strncasecmp_one_word_mesa(const char *str1, size_t len1, const char *str2, size_t len2); +int strcmp_one_word_mesa(const char *s1_lowercase, const char *s1_uppercase, size_t len1, const char *s2, size_t len2); +int strncmp_one_word_mesa(const char *s1_lowercase, const char *s1_uppercase, size_t len1, const char *s2, size_t len2); +int strcmp_one_word_mesa_equal_len(const char *s1_lowercase, const char *s1_uppercase, const char *s2, size_t len); + +int strncmp_two_word_mesa(const char *s1_lower, const char *s1_upper, size_t len1, + const char *s2_lower, const char *s2_upper, size_t len2, const char *data, int dlen); +int strcmp_two_word(char *srcdata,int datalen, const char *first, int flen, const char *second, int slen); + +const void *mail_memmem(const void *start, unsigned int s_len, const void *find, unsigned int f_len); +char *mesa_memncasemem(const char *haystack, size_t haystacklen,const char *needle, size_t needlelen); diff --git a/decoders/mail/version.map b/decoders/mail/version.map new file mode 100644 index 0000000..ec98aad --- /dev/null +++ b/decoders/mail/version.map @@ -0,0 +1,13 @@ +VERS_2.4{ +global: +extern "C" { + mail_init; + mail_exit; + mail_subscribe; + module_to_mail_decoder; + GIT_VERSION_*; +}; + +local: *; +}; + diff --git a/include/stellar/mail.h b/include/stellar/mail.h new file mode 100644 index 0000000..a7e8a27 --- /dev/null +++ b/include/stellar/mail.h @@ -0,0 +1,96 @@ +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#include "stellar/session.h" +#include "stellar/module.h" + +#define MAIL_MODULE_NAME "MAIL_MODULE" + +enum MAIL_COMMAND { + MAIL_CMD_USERNAME, + MAIL_CMD_PASSWORD, + MAIL_CMD_STARTTLS, + MAIL_CMD_MAIL_FROM, + MAIL_CMD_RCPT_TO, + MAIL_CMD_EHLO, + MAIL_CMD_OTHER, + MAIL_CMD_MAX, +}; + +enum MAIL_PROTOCOL { + MAIL_PROTOCOL_SMTP, + MAIL_PROTOCOL_POP3, + MAIL_PROTOCOL_IMAP, + MAIL_PROTOCOL_MAX, +}; + +struct mail_header_field { + char *field_name; + size_t field_name_len; + char *field_value; + size_t field_value_len; +}; + +struct mail_header { + struct mail_header_field *from; + struct mail_header_field *to; + struct mail_header_field *cc; + struct mail_header_field *bcc; + struct mail_header_field *reply_to; + struct mail_header_field *date; + struct mail_header_field *subject; + + struct mail_header_field *header_fields; + size_t n_header_fields; +}; + +typedef void mail_command_callback_func(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + enum MAIL_COMMAND command, + char *cmd_arg, size_t cmd_arg_len, + char *cmd_line, size_t cmd_line_len, + void *arg); + +typedef void mail_header_callback_func(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + struct mail_header *header, + void *arg); + +typedef void mail_body_callback_func(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + char *body, size_t body_len, size_t offset, int is_finish, + void *arg); + +typedef void mail_attachment_callback_func(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + char *name, size_t name_len, + char *attachment, size_t attachment_len, size_t offset, int is_finish, + void *arg); + +typedef void mail_eml_callback_func(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, + char *eml, size_t eml_len, size_t offset, int is_finish, + void *arg); + + +struct mail_decoder; +struct mail_decoder *module_to_mail_decoder(struct module *module); +int mail_subscribe(struct mail_decoder *decoder, + mail_command_callback_func *command_cb, + mail_header_callback_func *header_cb, + mail_body_callback_func *data_cb, + mail_attachment_callback_func *attachment_cb, + mail_eml_callback_func *eml_cb, + void *arg); + +#ifdef __cplusplus +} +#endif + diff --git a/test/decoders/mail/CMakeLists.txt b/test/decoders/mail/CMakeLists.txt new file mode 100644 index 0000000..378f401 --- /dev/null +++ b/test/decoders/mail/CMakeLists.txt @@ -0,0 +1,53 @@ +set(TEST_NAME mail_test) +set(TEST_MAIN ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}) + +file(GLOB TEST_SRC "${TEST_NAME}*.cpp") + +add_executable( + ${TEST_NAME} + ${TEST_SRC} +) + +target_include_directories( + ${TEST_NAME} PRIVATE + ${CMAKE_SOURCE_DIR}/deps/ + ${CMAKE_SOURCE_DIR}/decoders/ +) + +target_link_libraries( + ${TEST_NAME} + mail + stellar_lib + cjson-static + dl "-rdynamic" + gtest + gmock +) + +add_test( NAME ${TEST_NAME}.SETUP + COMMAND sh -c " + mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/result/ && + mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/log/ && + mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/conf/ && + cat ${CMAKE_SOURCE_DIR}/conf/stellar.toml > ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml && + cat ${CMAKE_CURRENT_SOURCE_DIR}/conf/spec.toml >> ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml && + sed -i 's/mode = \"pcapfile\"/mode = \"pcaplist\"/g' ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml && + sed -i 's/pcap_path = \"\\\/tmp\\\/test.pcap\"/pcap_path = \"pcaplist.txt\"/g' ${CMAKE_CURRENT_BINARY_DIR}/conf/stellar.toml && + find ${CMAKE_CURRENT_SOURCE_DIR}/result/ -type f | xargs -i cp {} ${CMAKE_CURRENT_BINARY_DIR}/result/ && + find ${CMAKE_CURRENT_SOURCE_DIR}/pcap/ -type f > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all + " +) + +add_test( + NAME ${TEST_NAME}.imap + COMMAND sh -c " + cat ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.all | grep '01-imap-mail-account-in-command-with-base64' > ${CMAKE_CURRENT_BINARY_DIR}/pcaplist.txt && + ${TEST_MAIN} -m -c ./conf/stellar.toml -f ${CMAKE_CURRENT_BINARY_DIR}/result/01-imap-mail-account-in-command-with-base64.json + " +) + +set_tests_properties( + ${TEST_NAME}.imap + PROPERTIES + FIXTURES_REQUIRED ${TEST_NAME}.SETUP +) diff --git a/test/decoders/mail/mail_test_main.cpp b/test/decoders/mail/mail_test_main.cpp new file mode 100644 index 0000000..8cbaa1b --- /dev/null +++ b/test/decoders/mail/mail_test_main.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include "stellar/stellar.h" + +#include + +const char *usage = "Usage: %s [-m] [-u test_filter] [-c config_file] [-f result_file]\n"; + +int main(int argc, char **argv) { + int opt; + int run_unittest = 0; + int run_stellar = 0; + const char *test_filter = NULL; + const char *result_filename = NULL; + const char *conf_filename = NULL; + + if (argc == 1) { + fprintf(stderr, usage, argv[0]); + exit(-1); + } + + while ((opt = getopt(argc, argv, "hmu:c:f:")) != -1) { + switch (opt) { + case 'u': + run_unittest = 1; + test_filter = optarg; + break; + case 'm': + run_stellar = 1; + break; + case 'f': + result_filename = optarg; + break; + case 'c': + conf_filename = optarg; + break; + case 'h': + default: + fprintf(stderr, usage, argv[0]); + exit(-1); + } + } + + ::testing::InitGoogleTest(&argc, argv); + + if (run_unittest) { + testing::GTEST_FLAG(filter) = test_filter ? test_filter : ""; + int test_ret = RUN_ALL_TESTS(); + if (test_ret != 0) { + fprintf(stderr, "Tests failed with return code %d\n", test_ret); + return test_ret; + } + } + + if (run_stellar) { + if (result_filename == NULL) { + result_filename = "./mail_result.json"; + } + // setenv for mail test module + setenv("MAIL_TEST_RESULT_EXPECT", result_filename, 1); + + if (conf_filename == NULL) { + conf_filename = "./conf/stellar.toml"; + } + + struct stellar *st = stellar_new(conf_filename); + if (st == NULL) { + fprintf(stderr, "Failed to create stellar instance.\n"); + return -1; + } + + stellar_run(st); + stellar_free(st); + } + + return ::testing::Test::HasFailure() ? -1 : 0; +} + diff --git a/test/decoders/mail/mail_test_module.cpp b/test/decoders/mail/mail_test_module.cpp new file mode 100644 index 0000000..827d273 --- /dev/null +++ b/test/decoders/mail/mail_test_module.cpp @@ -0,0 +1,367 @@ +#include +#include +#include + +#include "stellar/stellar.h" +#include "stellar/module.h" +#include "stellar/session.h" +#include "stellar/utils.h" +#include "stellar/mail.h" + +#include +#include +#include "cjson/cJSON.h" + +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-function" + +#define MAIL_TEST_MODULE_NAME "MAIL_TEST_MODULE" +#define MAIL_TEST_RESULT_EXPECT_ENV "MAIL_TEST_RESULT_EXPECT" +#define MAIL_TEST_EXDATA_NAME "MAIL_TEST_EXDATA" + +#define MAIL_TEST_TRANSACTION_SEQ_MAX 64 + +#define timeval_delta_ms(start, end) (((end).tv_sec-(start).tv_sec)*1000 + ((end).tv_usec-(start).tv_usec)/1000) +#define timeval_delta_us(start, end) (((end).tv_sec-(start).tv_sec)*1000*1000 + ((end).tv_usec-(start).tv_usec)) +#define timeval_to_ms(t) ((t).tv_sec*1000+(t).tv_usec/1000) + +struct mail_test_sockaddr_in { + int family; + union { + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } sockaddr; +}; + +struct mail_test_exdata { + int callback_times; + struct mail_test_module_ctx *mod_ctx_ref; +}; + +struct mail_test_result { + cJSON *test_json; + cJSON *expect_json; + int count; +}; + +struct mail_test_module_ctx { + int exdata_id; + struct mail_test_result *result; + + // depends a single session pcap + int callback_times; + struct module_manager *mod_mgr_ref; +}; + +const char *mail_protocol_name[]={"SMTP", "POP3", "IMAP"}; + +static void mail_test_result_commit(struct mail_test_result *result, cJSON *json) +{ + cJSON_AddNumberToObject(json, "test_result", ++result->count); + cJSON_AddItemToArray(result->test_json, json); +} + +static void mail_test_result_compare(struct mail_test_result *result) +{ + EXPECT_TRUE(result->expect_json != NULL); + EXPECT_TRUE(result->test_json != NULL); + + int i, json_compare; + int test_result_count, expect_result_count; + char *test_str, *expect_str; + cJSON *tmp_test, *tmp_expect; + + //expect_str = cJSON_Print(result->expect_json); + //test_str = cJSON_Print(result->test_json); + //printf("LOAD Raw:\n%s\n", expect_str); + //printf("TEST Raw:\n%s\n", test_str); + + test_result_count = cJSON_GetArraySize(result->test_json); + expect_result_count = cJSON_GetArraySize(result->expect_json); + + EXPECT_EQ(test_result_count, expect_result_count); + + for (i = 0; i < MIN(test_result_count, expect_result_count); i++) { + tmp_test = cJSON_GetArrayItem(result->test_json, i); + tmp_expect = cJSON_GetArrayItem(result->expect_json, i); + expect_str = cJSON_Print(tmp_expect); + test_str = cJSON_Print(tmp_test); + + json_compare = cJSON_Compare(tmp_expect, tmp_test, 0); + if (json_compare != 1) { + printf("LOAD Diff:\n%s\n", expect_str); + printf("TEST Diff:\n%s\n", test_str); + } + + free(expect_str); + free(test_str); + + EXPECT_EQ(json_compare, 1); + break; + } +} + +static void mail_test_result_exit(struct mail_test_result *result) +{ + if (result->expect_json) { + cJSON_Delete(result->expect_json); + } + if (result->test_json) { + cJSON_Delete(result->test_json); + } + free(result); +} + +static struct mail_test_result * mail_test_result_init(const char *filename) +{ + long filesize; + char *buffer; + FILE *file; + struct mail_test_result *result; + + result = (struct mail_test_result *)calloc(1, sizeof(struct mail_test_result)); + + file = fopen(filename, "rb"); + if (file) { + fseek(file, 0, SEEK_END); + filesize = ftell(file); + rewind(file); + buffer = (char *)calloc(filesize + 1, 1); + fread(buffer, 1, filesize, file); + + result->expect_json = cJSON_Parse(buffer); + + free(buffer); + fclose(file); + } + + result->test_json = cJSON_CreateArray(); + + printf("mail test result expect: %s\n", filename); + return result; +} + +static void mail_test_store_packet_dst(struct mail_test_sockaddr_in *sockaddr, const struct packet *pkt) { + const struct layer *layer; + + if (sockaddr == NULL) { + return; + } + + memset(sockaddr, 0, sizeof(struct mail_test_sockaddr_in)); + + layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 2); + if (layer->proto == LAYER_PROTO_IPV4) { + sockaddr->family = AF_INET; + sockaddr->sockaddr.ipv4.sin_family = AF_INET; + sockaddr->sockaddr.ipv4.sin_addr = layer->hdr.ip4->ip_dst; + + layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 1); + if (layer->proto == LAYER_PROTO_TCP) { + sockaddr->sockaddr.ipv4.sin_port = htons(layer->hdr.tcp->dest); + } + if (layer->proto == LAYER_PROTO_UDP) { + sockaddr->sockaddr.ipv4.sin_port = htons(layer->hdr.udp->dest); + } + } + if (layer->proto == LAYER_PROTO_IPV6) { + sockaddr->family = AF_INET6; + sockaddr->sockaddr.ipv6.sin6_family = AF_INET6; + memcpy(&sockaddr->sockaddr.ipv6.sin6_addr, &layer->hdr.ip6->ip6_dst, sizeof(struct in6_addr)); + + layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 2); + if (layer->proto == LAYER_PROTO_TCP) { + sockaddr->sockaddr.ipv6.sin6_port = htons(layer->hdr.tcp->dest); + } + if (layer->proto == LAYER_PROTO_UDP) { + sockaddr->sockaddr.ipv6.sin6_port = htons(layer->hdr.udp->dest); + } + } +} + +static void mail_test_store_packet_src(struct mail_test_sockaddr_in *sockaddr, const struct packet *pkt) { + const struct layer *layer; + + if (sockaddr == NULL) { + return; + } + + memset(sockaddr, 0, sizeof(struct mail_test_sockaddr_in)); + + layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 2); + if (layer->proto == LAYER_PROTO_IPV4) { + sockaddr->family = AF_INET; + sockaddr->sockaddr.ipv4.sin_family = AF_INET; + sockaddr->sockaddr.ipv4.sin_addr = layer->hdr.ip4->ip_src; + + layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 1); + if (layer->proto == LAYER_PROTO_TCP) { + sockaddr->sockaddr.ipv4.sin_port = htons(layer->hdr.tcp->source); + } + if (layer->proto == LAYER_PROTO_UDP) { + sockaddr->sockaddr.ipv4.sin_port = htons(layer->hdr.udp->source); + } + } + if (layer->proto == LAYER_PROTO_IPV6) { + sockaddr->family = AF_INET6; + sockaddr->sockaddr.ipv6.sin6_family = AF_INET6; + memcpy(&sockaddr->sockaddr.ipv6.sin6_addr, &layer->hdr.ip6->ip6_src, sizeof(struct in6_addr)); + + layer = packet_get_layer_by_idx(pkt, packet_get_layer_count(pkt) - 2); + if (layer->proto == LAYER_PROTO_TCP) { + sockaddr->sockaddr.ipv6.sin6_port = htons(layer->hdr.tcp->source); + } + if (layer->proto == LAYER_PROTO_UDP) { + sockaddr->sockaddr.ipv6.sin6_port = htons(layer->hdr.udp->source); + } + } +} + +static int mail_test_compare_packet_src(struct mail_test_sockaddr_in *sockaddr, const struct packet *pkt) +{ + struct mail_test_sockaddr_in pkt_src; + mail_test_store_packet_src(&pkt_src, pkt); + return 0 == memcmp(sockaddr, &pkt_src, sizeof(pkt_src)); +} + +void mail_test_exdata_free(struct mail_test_exdata *exdata) +{ + if (exdata) { + free(exdata); + } +} + +struct mail_test_exdata * mail_test_exdata_new(void) +{ + struct mail_test_exdata *exdata; + exdata = (struct mail_test_exdata *)calloc(1, sizeof(struct mail_test_exdata)); + return exdata; +} + +void mail_test_command_callback(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + enum MAIL_COMMAND command, + char *cmd_param, size_t cmd_param_len, + char *cmd_line, size_t cmd_line_len, + void *arg) +{ + printf("mail command callback\n"); + printf("%.*s\n", cmd_line_len, cmd_line); +} + +void mail_test_header_callback(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + struct mail_header *header, + void *arg) +{ + printf("mail header callback\n"); +} + +void mail_test_body_callback(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + char *body, size_t body_len, size_t offset, int is_finish, + void *arg) +{ + printf("mail body callback\n"); +} + +void mail_test_attachment_callback(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, size_t mail_seq, + char *name, size_t name_len, + char *attachment, size_t attachment_len, size_t offset, int is_finish, + void *arg) +{ + printf("mail attachment callback\n"); +} + +void mail_test_eml_callback(struct session *sess, + enum MAIL_PROTOCOL mail_protocol, + char *eml, size_t eml_len, size_t offset, int is_finish, + void *arg) +{ + struct mail_test_exdata *exdata; + struct mail_test_module_ctx *mod_ctx = (struct mail_test_module_ctx *)arg; + + exdata = (struct mail_test_exdata *)session_get_exdata(sess, mod_ctx->exdata_id); + if (exdata == NULL) { + exdata = (struct mail_test_exdata *)calloc(1, sizeof(struct mail_test_exdata)); + session_set_exdata(sess, mod_ctx->exdata_id, exdata); + } + + printf("mail eml callback: %s\n", mail_protocol_name[mail_protocol]); + +} + +void mail_test_on_exdata_free(int idx, void *ex_ptr, void *arg) +{ + (void)(idx); + (void)(arg); + struct mail_test_exdata *exdata = (struct mail_test_exdata *)ex_ptr; + + if (exdata) { + mail_test_exdata_free(exdata); + } +} + +extern "C" void mail_test_exit(struct module_manager *mod_mgr, struct module *mod) +{ + (void)(mod_mgr); + struct mail_test_module_ctx *mod_ctx; + + if (mod) { + mod_ctx = (struct mail_test_module_ctx *)module_get_ctx(mod); + if (mod_ctx) { + mail_test_result_compare(mod_ctx->result); + mail_test_result_exit(mod_ctx->result); + free(mod_ctx); + } + module_free(mod); + } +} + +extern "C" struct module *mail_test_init(struct module_manager *mod_mgr) +{ + int ret; + struct module *mod; + struct mail_test_module_ctx *mod_ctx; + struct session_manager *sess_mgr; + struct mail_decoder *decoder; + + mod_ctx = (struct mail_test_module_ctx *)calloc(1, sizeof(struct mail_test_module_ctx)); + mod_ctx->mod_mgr_ref = mod_mgr; + mod = module_new(MAIL_TEST_MODULE_NAME, mod_ctx); + sess_mgr = module_to_session_manager(module_manager_get_module(mod_mgr, SESSION_MANAGER_MODULE_NAME)); + + if (mod_mgr == NULL || sess_mgr == NULL) { + goto exit; + } + + mod_ctx->exdata_id = session_manager_new_session_exdata_index(sess_mgr, MAIL_TEST_EXDATA_NAME, mail_test_on_exdata_free, mod_ctx); + if (mod_ctx->exdata_id < 0) { + goto exit; + } + + mod_ctx->result = mail_test_result_init(getenv(MAIL_TEST_RESULT_EXPECT_ENV)); + if (mod_ctx->result == NULL) { + goto exit; + } + + decoder = module_to_mail_decoder(module_manager_get_module(mod_mgr, MAIL_MODULE_NAME)); + ret = mail_subscribe(decoder, + mail_test_command_callback, + mail_test_header_callback, + mail_test_body_callback, + mail_test_attachment_callback, + mail_test_eml_callback, + mod_ctx); + if (ret < 0) { + goto exit; + } + + return mod; +exit: + printf("mail_test module init failed!\n"); + mail_test_exit(mod_mgr, mod); + return NULL; +} + diff --git a/test/decoders/mail/pcap/01-imap-mail-account-in-command-with-base64.pcap b/test/decoders/mail/pcap/01-imap-mail-account-in-command-with-base64.pcap new file mode 100644 index 0000000..d4344c7 Binary files /dev/null and b/test/decoders/mail/pcap/01-imap-mail-account-in-command-with-base64.pcap differ diff --git a/test/decoders/mail/pcap/02-imap-starttls.pcap b/test/decoders/mail/pcap/02-imap-starttls.pcap new file mode 100644 index 0000000..2fd3bf8 Binary files /dev/null and b/test/decoders/mail/pcap/02-imap-starttls.pcap differ diff --git a/test/decoders/mail/pcap/03-smtp-s2c-mail.pcap b/test/decoders/mail/pcap/03-smtp-s2c-mail.pcap new file mode 100644 index 0000000..3549c0d Binary files /dev/null and b/test/decoders/mail/pcap/03-smtp-s2c-mail.pcap differ diff --git a/test/decoders/mail/pcap/04-smtp-attachment-charset-unknown.pcap b/test/decoders/mail/pcap/04-smtp-attachment-charset-unknown.pcap new file mode 100644 index 0000000..1cd8cfc Binary files /dev/null and b/test/decoders/mail/pcap/04-smtp-attachment-charset-unknown.pcap differ diff --git a/test/decoders/mail/pcap/05-smtp-curl-mails.pcap b/test/decoders/mail/pcap/05-smtp-curl-mails.pcap new file mode 100644 index 0000000..b452fd7 Binary files /dev/null and b/test/decoders/mail/pcap/05-smtp-curl-mails.pcap differ diff --git a/test/decoders/mail/pcap/06-smtp-starttls.pcap b/test/decoders/mail/pcap/06-smtp-starttls.pcap new file mode 100644 index 0000000..c26ec62 Binary files /dev/null and b/test/decoders/mail/pcap/06-smtp-starttls.pcap differ diff --git a/test/decoders/mail/pcap/07-smtp-auth-xoauth2.pcap b/test/decoders/mail/pcap/07-smtp-auth-xoauth2.pcap new file mode 100644 index 0000000..b6e3bf8 Binary files /dev/null and b/test/decoders/mail/pcap/07-smtp-auth-xoauth2.pcap differ diff --git a/test/decoders/mail/pcap/08-smtp-mime-header-rfc2047.pcap b/test/decoders/mail/pcap/08-smtp-mime-header-rfc2047.pcap new file mode 100644 index 0000000..c7754e0 Binary files /dev/null and b/test/decoders/mail/pcap/08-smtp-mime-header-rfc2047.pcap differ diff --git a/test/decoders/mail/pcap/09-pop3-c2s-55325-1100.pcap b/test/decoders/mail/pcap/09-pop3-c2s-55325-1100.pcap new file mode 100644 index 0000000..3b54a68 Binary files /dev/null and b/test/decoders/mail/pcap/09-pop3-c2s-55325-1100.pcap differ diff --git a/test/decoders/mail/pcap/10-pop3-s2c-1100-55325.pcap b/test/decoders/mail/pcap/10-pop3-s2c-1100-55325.pcap new file mode 100644 index 0000000..f018536 Binary files /dev/null and b/test/decoders/mail/pcap/10-pop3-s2c-1100-55325.pcap differ diff --git a/test/decoders/mail/pcap/11-pop3-c2s.pcap b/test/decoders/mail/pcap/11-pop3-c2s.pcap new file mode 100644 index 0000000..dc1c679 Binary files /dev/null and b/test/decoders/mail/pcap/11-pop3-c2s.pcap differ diff --git a/test/decoders/mail/pcap/12-pop3-s2c.pcap b/test/decoders/mail/pcap/12-pop3-s2c.pcap new file mode 100644 index 0000000..810d885 Binary files /dev/null and b/test/decoders/mail/pcap/12-pop3-s2c.pcap differ diff --git a/test/decoders/mail/pcap/13-pop3-mail.pcap b/test/decoders/mail/pcap/13-pop3-mail.pcap new file mode 100644 index 0000000..6663699 Binary files /dev/null and b/test/decoders/mail/pcap/13-pop3-mail.pcap differ diff --git a/test/decoders/mail/pcap/14-pop3-starttls.pcap b/test/decoders/mail/pcap/14-pop3-starttls.pcap new file mode 100644 index 0000000..faae843 Binary files /dev/null and b/test/decoders/mail/pcap/14-pop3-starttls.pcap differ diff --git a/test/decoders/mail/pcap/15-pop3-capa.pcap b/test/decoders/mail/pcap/15-pop3-capa.pcap new file mode 100644 index 0000000..a2eabc5 Binary files /dev/null and b/test/decoders/mail/pcap/15-pop3-capa.pcap differ diff --git a/test/decoders/mail/pcap/16-ftp-c2s-8569-8021.pcap b/test/decoders/mail/pcap/16-ftp-c2s-8569-8021.pcap new file mode 100644 index 0000000..f6cbb48 Binary files /dev/null and b/test/decoders/mail/pcap/16-ftp-c2s-8569-8021.pcap differ diff --git a/test/decoders/mail/pcap/17-ftp-c2s-8569-8080.pcap b/test/decoders/mail/pcap/17-ftp-c2s-8569-8080.pcap new file mode 100644 index 0000000..75dc205 Binary files /dev/null and b/test/decoders/mail/pcap/17-ftp-c2s-8569-8080.pcap differ diff --git a/test/decoders/mail/pcap/18-http-delete.pcap b/test/decoders/mail/pcap/18-http-delete.pcap new file mode 100644 index 0000000..08f04bd Binary files /dev/null and b/test/decoders/mail/pcap/18-http-delete.pcap differ diff --git a/test/decoders/mail/result/01-imap-mail-account-in-command-with-base64.json b/test/decoders/mail/result/01-imap-mail-account-in-command-with-base64.json new file mode 100644 index 0000000..4eea170 --- /dev/null +++ b/test/decoders/mail/result/01-imap-mail-account-in-command-with-base64.json @@ -0,0 +1,8 @@ +[ + { + "MAIL_STARTTLS_CMD": "1", + "Mail_proto": "IMAP", + "Tuple4": "192.168.32.123.54643>159.226.251.13.143", + "name": "MAIL_RESULT_1" + } +] -- cgit v1.2.3