Mercurial > masqmail
diff src/pop3_in.c @ 0:08114f7dcc23 0.2.21
this is masqmail-0.2.21 from oliver kurth
author | meillo@marmaro.de |
---|---|
date | Fri, 26 Sep 2008 17:05:23 +0200 |
parents | |
children | 26e34ae9a3e3 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pop3_in.c Fri Sep 26 17:05:23 2008 +0200 @@ -0,0 +1,820 @@ +/* pop3_in.c, Copyright (C) 2000 by Oliver Kurth, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* see RFC 1725 */ + +#include "masqmail.h" +#include "pop3_in.h" +#include "readsock.h" + +#include <sys/wait.h> +#include <sys/stat.h> + +#ifdef USE_LIB_CRYPTO +#include <openssl/md5.h> +#else +#include "md5/global.h" +#include "md5/md5.h" +#endif + +#ifdef ENABLE_POP3 + +/* experimental feature */ +#define DO_WRITE_UIDL_EARLY 1 + +static +gchar *MD5String (char *string) +{ + MD5_CTX context; + unsigned char digest[16]; + char str_digest[33]; + int i; + +#ifdef USE_LIB_CRYPTO + MD5(string, strlen(string), digest); +#else + MD5Init(&context); + MD5Update(&context, string, strlen(string)); + MD5Final(digest, &context); +#endif + for (i = 0; i < 16; i++) + sprintf(str_digest+2*i, "%02x", digest[i]); + + return g_strdup(str_digest); +} + +static +pop3_base *create_pop3base(gint sock, guint flags) +{ + gint dup_sock; + + pop3_base *popb = (pop3_base *)g_malloc(sizeof(pop3_base)); + if(popb){ + memset(popb, 0, sizeof(pop3_base)); + + popb->error = pop3_ok; + + popb->buffer = (gchar *)g_malloc(POP3_BUF_LEN); + + dup_sock = dup(sock); + popb->out = fdopen(sock, "w"); + popb->in = fdopen(dup_sock, "r"); + + popb->flags = flags; + } + return popb; +} + +static +void pop3_printf(FILE *out, gchar *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + DEBUG(4){ + gchar buf[256]; + va_list args_copy; + + va_copy(args_copy, args); + vsnprintf(buf, 255, fmt, args_copy); + va_end(args_copy); + + debugf(">>>%s", buf); + } + + vfprintf(out, fmt, args); fflush(out); + + va_end(args); +} + +static +gboolean find_uid(pop3_base *popb, gchar *str) +{ + GList *node, *node_next; + + for(node = popb->list_uid_old; node; node=node_next){ + gchar *uid = (gchar *)(node->data); + node_next = node->next; + if(strcmp(uid, str) == 0){ +#if 1 + popb->list_uid_old = g_list_remove_link(popb->list_uid_old, node); + g_list_free_1(node); + g_free(uid); +#endif + return TRUE; + } + } + return FALSE; +} + +static +gboolean write_uidl(pop3_base *popb, gchar *user) +{ + gboolean ok = FALSE; + GList *node; + gchar *filename = g_strdup_printf("%s/popuidl/%s@%s", + conf.spool_dir, + user, popb->remote_host); + gchar *tmpname = g_strdup_printf("%s.tmp", filename); + FILE *fptr = fopen(tmpname, "wt"); + + if(fptr){ + foreach(popb->drop_list, node){ + msg_info *info = (msg_info *)(node->data); + if(info->is_fetched || info->is_in_uidl) + fprintf(fptr, "%s\n", info->uid); + } + fclose(fptr); + ok = (rename(tmpname, filename) != -1); + } + + g_free(tmpname); + g_free(filename); + return ok; +} + +static +gboolean read_uidl_fname(pop3_base *popb, gchar *filename) +{ + gboolean ok = FALSE; + FILE *fptr = fopen(filename, "rt"); + gchar buf[256]; + + if(fptr){ + popb->list_uid_old = NULL; + while(fgets(buf, 255, fptr)){ + if(buf[strlen(buf)-1] == '\n'){ + g_strchomp(buf); + popb->list_uid_old = + g_list_append(popb->list_uid_old, g_strdup(buf)); + }else{ + logwrite(LOG_ALERT, "broken uid: %s\n", buf); + break; + } + } + fclose(fptr); + ok = TRUE; + }else + logwrite(LOG_ALERT, "opening of %s failed: %s", filename, strerror(errno)); + return ok; +} + +static +gboolean read_uidl(pop3_base *popb, gchar *user) +{ + gboolean ok = FALSE; + struct stat statbuf; + gchar *filename = g_strdup_printf("%s/popuidl/%s@%s", + conf.spool_dir, + user, popb->remote_host); + + if(stat(filename, &statbuf) == 0){ + ok = read_uidl_fname(popb, filename); + if(ok){ + GList *drop_node; + foreach(popb->drop_list, drop_node){ + msg_info *info = (msg_info *)(drop_node->data); + if(find_uid(popb, info->uid)){ + DEBUG(5) debugf("msg with uid '%s' already known\n", info->uid); + info->is_in_uidl = TRUE; + popb->uidl_known_cnt++; + }else + DEBUG(5) debugf("msg with uid '%s' not known\n", info->uid); + } + } + }else{ + logwrite(LOG_DEBUG, "no uidl file '%s' found\n", filename); + ok = TRUE; + } + + g_free(filename); + return ok; /* return code is irrelevant, do not check... */ +} + +static +gboolean read_response(pop3_base *popb, int timeout) +{ + gint len; + + len = read_sockline(popb->in, popb->buffer, POP3_BUF_LEN, timeout, READSOCKL_CHUG); + + if(len == -3){ + popb->error = pop3_timeout; + return FALSE; + } + else if(len == -2){ + popb->error = pop3_syntax; + return FALSE; + } + else if(len == -1){ + popb->error = pop3_eof; + return FALSE; + } + + return TRUE; +} + +static +gboolean check_response(pop3_base *popb) +{ + char c = popb->buffer[0]; + + if(c == '+'){ + popb->error = pop3_ok; + return TRUE; + }else if(c == '-') + popb->error = pop3_fail; + else + popb->error = pop3_syntax; + return FALSE; +} + +static +gboolean strtoi(gchar *p, gchar **pend, gint *val) +{ + gchar buf[12]; + gint i = 0; + + while(*p && isspace(*p)) p++; + if(*p){ + while((i < 11) && isdigit(*p)) + buf[i++] = *(p++); + buf[i] = 0; + *val = atoi(buf); + *pend = p; + return TRUE; + } + return FALSE; +} + +static +gboolean check_response_int_int(pop3_base *popb, gint *arg0, gint *arg1) +{ + if(check_response(popb)){ + gchar *p = &(popb->buffer[3]); + gchar *pe; + + if(strtoi(p, &pe, arg0)){ + DEBUG(5) debugf("arg0 = %d\n", *arg0); + p = pe; + if(strtoi(p, &pe, arg1)) + DEBUG(5) debugf("arg1 = %d\n", *arg1); + return TRUE; + } + popb->error = pop3_syntax; + } + return FALSE; +} + +static +gboolean get_drop_listing(pop3_base *popb) +{ + gchar buf[64]; + + DEBUG(5) debugf("get_drop_listing() entered\n"); + + while(1){ + gint len = read_sockline(popb->in, buf, 64, POP3_CMD_TIMEOUT, READSOCKL_CHUG); + if(len > 0){ + if(buf[0] == '.') + return TRUE; + else{ + gint number, msg_size; + gchar *p = buf, *pe; + if(strtoi(p, &pe, &number)){ + p = pe; + if(strtoi(p, &pe, &msg_size)){ + msg_info *info = g_malloc(sizeof(msg_info)); + info->number = number; + info->size = msg_size; + + DEBUG(5) debugf("get_drop_listing(), number = %d, msg_size = %d\n", number, msg_size); + + info->uid = NULL; + info->is_fetched = FALSE; + info->is_in_uidl = FALSE; + popb->drop_list = g_list_append(popb->drop_list, info); + }else{ + popb->error = pop3_syntax; + break; + } + }else{ + popb->error = pop3_syntax; + break; + } + } + }else{ + popb->error = (len == -1) ? pop3_eof : pop3_timeout; + return FALSE; + } + } + return FALSE; +} + +static +gboolean get_uid_listing(pop3_base *popb) +{ + gchar buf[64]; + + while(1){ + gint len = read_sockline(popb->in, buf, 64, POP3_CMD_TIMEOUT, READSOCKL_CHUG); + if(len > 0){ + if(buf[0] == '.') + return TRUE; + else{ + gint number; + gchar *p = buf, *pe; + if(strtoi(p, &pe, &number)){ + msg_info *info = NULL; + GList *drop_node; + + p = pe; + while(*p && isspace(*p)) p++; + + foreach(popb->drop_list, drop_node){ + msg_info *curr_info = (msg_info *)(drop_node->data); + if(curr_info->number == number){ + info = curr_info; + break; + } + } + if(info){ + info->uid = g_strdup(p); + g_strchomp(info->uid); + } + + }else{ + popb->error = pop3_syntax; + break; + } + } + } + } + return FALSE; +} + +static +gboolean check_init_response(pop3_base *popb) +{ + if(check_response(popb)){ + gchar buf[256]; + gchar *p = popb->buffer; + gint i = 0; + if(*p){ + while(*p && (*p != '<')) p++; + while(*p && (*p != '>') && (i < 254)) + buf[i++] = *(p++); + buf[i++] = '>'; + buf[i] = 0; + + popb->timestamp = g_strdup(buf); + + return TRUE; + } + } + return FALSE; +} + +void pop3_in_close(pop3_base *popb) +{ + GList *node; + + fclose(popb->in); + fclose(popb->out); + + close(popb->sock); + + foreach(popb->list_uid_old, node){ + gchar *uid = (gchar *)(node->data); + g_free(uid); + } + g_list_free(popb->list_uid_old); + + foreach(popb->drop_list, node){ + msg_info *info = (msg_info *)(node->data); + if(info->uid) g_free(info->uid); + g_free(info); + } + g_list_free(popb->drop_list); + + if(popb->buffer) g_free(popb->buffer); + if(popb->timestamp) g_free(popb->timestamp); +} + +pop3_base *pop3_in_open(gchar *host, gint port, GList *resolve_list, guint flags) +{ + pop3_base *popb; + gint sock; + mxip_addr *addr; + + DEBUG(5) debugf("pop3_in_open entered, host = %s\n", host); + + if((addr = connect_resolvelist(&sock, host, port, resolve_list))){ + /* create structure to hold status data: */ + popb = create_pop3base(sock, flags); + popb->remote_host = addr->name; + + DEBUG(5){ + struct sockaddr_in name; + int len; + getsockname(sock, (struct sockaddr *)(&name), &len); + debugf("socket: name.sin_addr = %s\n", inet_ntoa(name.sin_addr)); + } + return popb; + } + return NULL; +} + +pop3_base *pop3_in_open_child(gchar *cmd, guint flags) +{ + pop3_base *popb; + gint sock; + + DEBUG(5) debugf("pop3_in_open_child entered, cmd = %s\n", cmd); + + sock = child(cmd); + + if(sock > 0){ + + popb = create_pop3base(sock, flags); + popb->remote_host = NULL; + + return popb; + } + logwrite(LOG_ALERT, "child failed (sock = %d): %s\n", sock, strerror(errno)); + + return NULL; +} + +gboolean pop3_in_init(pop3_base *popb) +{ + gboolean ok; + + if((ok = read_response(popb, POP3_INITIAL_TIMEOUT))){ + ok = check_init_response(popb); + } + if(!ok) + /* pop3_in_log_failure(popb, NULL);*/ + logwrite(LOG_ALERT, "pop3 failed\n"); + return ok; +} + +gboolean pop3_in_login(pop3_base *popb, gchar *user, gchar *pass) +{ + if(popb->flags & POP3_FLAG_APOP){ + + gchar *string = g_strdup_printf("%s%s", popb->timestamp, pass); + gchar *digest = MD5String(string); + pop3_printf(popb->out, "APOP %s %s\r\n", user, digest); + g_free(string); + g_free(digest); + if(read_response(popb, POP3_CMD_TIMEOUT)){ + if(check_response(popb)) + return TRUE; + else + popb->error = pop3_login_failure; + } + + }else{ + + pop3_printf(popb->out, "USER %s\r\n", user); + if(read_response(popb, POP3_CMD_TIMEOUT)){ + if(check_response(popb)){ + pop3_printf(popb->out, "PASS %s\r\n", pass); + if(read_response(popb, POP3_CMD_TIMEOUT)){ + if(check_response(popb)) + return TRUE; + else + popb->error = pop3_login_failure; + } + }else{ + popb->error = pop3_login_failure; + } + } + } + return FALSE; +} + +gboolean pop3_in_stat(pop3_base *popb) +{ + pop3_printf(popb->out, "STAT\r\n"); + if(read_response(popb, POP3_CMD_TIMEOUT)){ + gint msg_cnt, mbox_size; + if(check_response_int_int(popb, &msg_cnt, &mbox_size)){ + popb->msg_cnt = msg_cnt; + popb->mbox_size = mbox_size; + + return TRUE; + } + } + return FALSE; +} + +gboolean pop3_in_list(pop3_base *popb) +{ + pop3_printf(popb->out, "LIST\r\n"); + if(read_response(popb, POP3_CMD_TIMEOUT)){ + if(get_drop_listing(popb)){ + return TRUE; + } + } + return FALSE; +} + +gboolean pop3_in_dele(pop3_base *popb, gint number) +{ + pop3_printf(popb->out, "DELE %d\r\n", number); + if(read_response(popb, POP3_CMD_TIMEOUT)){ + return TRUE; + } + return FALSE; +} + +message *pop3_in_retr(pop3_base *popb, gint number, address *rcpt) +{ + accept_error err; + + pop3_printf(popb->out, "RETR %d\r\n", number); + if(read_response(popb, POP3_CMD_TIMEOUT)){ + message *msg = create_message(); + msg->received_host = popb->remote_host; + msg->received_prot = (popb->flags & POP3_FLAG_APOP) ? PROT_APOP : PROT_POP3; + msg->transfer_id = (popb->next_id)++; + msg->rcpt_list = g_list_append(NULL, copy_address(rcpt)); + + if((err = accept_message(popb->in, msg, + ACC_MAIL_FROM_HEAD|(conf.do_save_envelope_to ? ACC_SAVE_ENVELOPE_TO : 0))) + == AERR_OK) + return msg; + + destroy_message(msg); + } + return NULL; +} + +gboolean pop3_in_uidl(pop3_base *popb) +{ + pop3_printf(popb->out, "UIDL\r\n"); + if(read_response(popb, POP3_CMD_TIMEOUT)){ + if(get_uid_listing(popb)){ + return TRUE; + } + } + return FALSE; +} + +gboolean pop3_in_quit(pop3_base *popb) +{ + pop3_printf(popb->out, "QUIT\r\n"); + + DEBUG(4) debugf("QUIT\n"); + + signal(SIGALRM, SIG_DFL); + + return TRUE; +} + +/* Send a DELE command for each message in (the old) uid listing. + This is to prevent mail from to be kept on server, if a previous + transaction was interupted. */ +gboolean pop3_in_uidl_dele(pop3_base *popb) +{ + GList *drop_node; + + foreach(popb->drop_list, drop_node){ + msg_info *info = (msg_info *)(drop_node->data); + /* if(find_uid(popb, info->uid)){*/ + if(info->is_in_uidl){ + if(!pop3_in_dele(popb, info->number)) + return FALSE; + /* TODO: it probably makes sense to also + delete this uid from the listing */ + } + } + return TRUE; +} + +gboolean pop3_get(pop3_base *popb, + gchar *user, gchar *pass, address *rcpt, address *return_path, + gint max_count, gint max_size, gboolean max_size_delete) +{ + gboolean ok = FALSE; + gint num_children = 0; + + DEBUG(5) debugf("rcpt = %s@%s\n", rcpt->local_part, rcpt->domain); + + signal(SIGCHLD, SIG_DFL); + + if(pop3_in_init(popb)){ + if(pop3_in_login(popb, user, pass)){ + if(pop3_in_stat(popb)){ + if(popb->msg_cnt > 0){ + + logwrite(LOG_NOTICE|LOG_VERBOSE, "%d message(s) for user %s at %s\n", + popb->msg_cnt, user, popb->remote_host); + + if(pop3_in_list(popb)){ + gboolean do_get = !(popb->flags & POP3_FLAG_UIDL); + if(!do_get) do_get = pop3_in_uidl(popb); + if(do_get){ + gint count = 0; + GList *drop_node; + + if(popb->flags & POP3_FLAG_UIDL){ + read_uidl(popb, user); + logwrite(LOG_VERBOSE|LOG_NOTICE, "%d message(s) already in uidl.\n", + popb->uidl_known_cnt); + } + if((popb->flags & POP3_FLAG_UIDL) && (popb->flags & POP3_FLAG_UIDL_DELE)) + pop3_in_uidl_dele(popb); + + foreach(popb->drop_list, drop_node){ + + msg_info *info = (msg_info *)(drop_node->data); + gboolean do_get_this = !(popb->flags & POP3_FLAG_UIDL); + /* if(!do_get_this) do_get_this = !find_uid(popb, info->uid);*/ + if(!do_get_this) do_get_this = !(info->is_in_uidl); + if(do_get_this){ + + if((info->size < max_size) || (max_size == 0)){ + message *msg; + + logwrite(LOG_VERBOSE|LOG_NOTICE, "receiving message %d\n", info->number); + msg = pop3_in_retr(popb, info->number, rcpt); + + if(msg){ + if(return_path) + msg->return_path = copy_address(return_path); + if(spool_write(msg, TRUE)){ + pid_t pid; + logwrite(LOG_NOTICE, "%s <= %s host=%s with %s\n", + msg->uid, + addr_string(msg->return_path), + popb->remote_host, + (popb->flags & POP3_FLAG_APOP) ? + prot_names[PROT_APOP] : prot_names[PROT_POP3] + ); + info->is_fetched = TRUE; + count++; +#if DO_WRITE_UIDL_EARLY + if(popb->flags & POP3_FLAG_UIDL) write_uidl(popb, user); +#endif + if(!conf.do_queue){ + + /* wait for child processes. If there are too many, + we wait blocking, before we fork another one */ + while(num_children > 0){ + int status, options = WNOHANG; + pid_t pid; + + if(num_children >= POP3_MAX_CHILDREN){ + logwrite(LOG_NOTICE, "too many children - waiting\n"); + options = 0; + } + if((pid = waitpid(0, &status, options)) > 0){ + num_children--; + if(WEXITSTATUS(status) != EXIT_SUCCESS) + logwrite(LOG_WARNING, + "delivery process with pid %d returned %d\n", + pid, WEXITSTATUS(status)); + if(WIFSIGNALED(status)) + logwrite(LOG_WARNING, + "delivery process with pid %d got signal: %d\n", + pid, WTERMSIG(status)); + }else if(pid < 0){ + logwrite(LOG_WARNING, "wait got error: %s\n", strerror(errno)); + } + } + + if((pid = fork()) == 0){ + deliver(msg); + _exit(EXIT_SUCCESS); + }else if(pid < 0){ + logwrite(LOG_ALERT|LOG_VERBOSE, + "could not fork for delivery, id = %s: %s\n", + msg->uid, strerror(errno)); + }else + num_children++; + }else{ + DEBUG(1) debugf("queuing forced by configuration or option.\n"); + } + if(popb->flags & POP3_FLAG_DELETE) + pop3_in_dele(popb, info->number); + + destroy_message(msg); + }/* if(spool_write(msg, TRUE)) */ + }else{ + logwrite(LOG_ALERT, + "retrieving of message %d failed: %d\n", + info->number, popb->error); + } + }/* if((info->size > max_size) ... */ + else{ + logwrite(LOG_NOTICE|LOG_VERBOSE, "size of message #%d (%d) > max_size (%d)\n", + info->number, info->size, max_size); + if(max_size_delete) + if(popb->flags & POP3_FLAG_DELETE) + pop3_in_dele(popb, info->number); + } + }/* if(do_get_this) ... */ + else{ + if(popb->flags & POP3_FLAG_UIDL){ + info->is_fetched = TRUE; /* obsolete? */ + logwrite(LOG_VERBOSE, "message %d already known\n", + info->number); + DEBUG(1) debugf("message %d (uid = %s) not fetched\n", + info->number, info->uid); +#if 0 +#if DO_WRITE_UIDL_EARLY + write_uidl(popb, user); /* obsolete? */ +#endif +#endif + } + } + if((max_count != 0) && (count >= max_count)) + break; + }/* foreach() */ +#if DO_WRITE_UIDL_EARLY +#else + if(popb->flags & POP3_FLAG_UIDL) write_uidl(popb, user); +#endif + }/* if(pop3_in_uidl(popb) ... */ + }/* if(pop3_in_list(popb)) */ + }/* if(popb->msg_cnt > 0) */ + else{ + logwrite(LOG_NOTICE|LOG_VERBOSE, + "no messages for user %s at %s\n", user, popb->remote_host); + } + ok = TRUE; + } + pop3_in_quit(popb); + }else{ + logwrite(LOG_ALERT|LOG_VERBOSE, + "pop3 login failed for user %s, host = %s\n", user, popb->remote_host); + } + } + if(!ok){ + logwrite(LOG_ALERT|LOG_VERBOSE, "pop3 failed, error = %d\n", popb->error); + } + + while(num_children > 0){ + int status; + pid_t pid; + if((pid = wait(&status)) > 0){ + num_children--; + if(WEXITSTATUS(status) != EXIT_SUCCESS) + logwrite(LOG_WARNING, + "delivery process with pid %d returned %d\n", + pid, WEXITSTATUS(status)); + if(WIFSIGNALED(status)) + logwrite(LOG_WARNING, + "delivery process with pid %d got signal: %d\n", + pid, WTERMSIG(status)); + }else{ + logwrite(LOG_WARNING, "wait got error: %s\n", strerror(errno)); + } + } + + return ok; +} + +/* function just to log into a pop server, + for pop_before_smtp (or is it smtp_after_pop?) +*/ + +gboolean pop3_login(gchar *host, gint port, GList *resolve_list, + gchar *user, gchar *pass, guint flags) +{ + gboolean ok = FALSE; + pop3_base *popb; + + signal(SIGCHLD, SIG_IGN); + + if((popb = pop3_in_open(host, port, resolve_list, flags))){ + if(pop3_in_init(popb)){ + if(pop3_in_login(popb, user, pass)) + ok = TRUE; + else + logwrite(LOG_ALERT|LOG_VERBOSE, + "pop3 login failed for user %s, host = %s\n", user, host); + } + pop3_in_close(popb); + } + return ok; +} + +#endif