Mercurial > masqmail
view src/pop3_in.c @ 162:71dcdc2020bc
guess-hostname: ordered guesses by quality
author | meillo@marmaro.de |
---|---|
date | Thu, 08 Jul 2010 12:19:11 +0200 (2010-07-08) |
parents | 2685e59f6f43 |
children | dc89737b27aa |
line wrap: on
line source
/* 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 <sys/wait.h> #include <sys/stat.h> #include "masqmail.h" #include "pop3_in.h" #include "readsock.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; /* FIXME: Paolo's code has the return stmt inside the if block right above it. What is correct? */ } 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); } } else { /* info->size > max_size */ 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