diff options
Diffstat (limited to 'decoders/mail/mail_pop3.c')
| -rw-r--r-- | decoders/mail/mail_pop3.c | 468 |
1 files changed, 468 insertions, 0 deletions
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 <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <assert.h> + +#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; +} |
