summaryrefslogtreecommitdiff
path: root/decoders/mail/mail_pop3.c
diff options
context:
space:
mode:
Diffstat (limited to 'decoders/mail/mail_pop3.c')
-rw-r--r--decoders/mail/mail_pop3.c468
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;
+}