/* reconnect and verify idnexes if connection was lost */ int pop_reconnect (CONTEXT *ctx) { int ret; POP_DATA *pop_data = (POP_DATA *)ctx->data; progress_t progressbar; if (pop_data->status == POP_CONNECTED) return 0; if (pop_data->status == POP_BYE) return -1; FOREVER { mutt_socket_close (pop_data->conn); ret = pop_open_connection (pop_data); if (ret == 0) { int i; mutt_progress_init (&progressbar, _("Verifying message indexes..."), M_PROGRESS_SIZE, NetInc, 0); for (i = 0; i < ctx->msgcount; i++) ctx->hdrs[i]->refno = -1; ret = pop_fetch_data (pop_data, "UIDL\r\n", &progressbar, check_uidl, ctx); if (ret == -2) { mutt_error ("%s", pop_data->err_msg); mutt_sleep (2); } } if (ret == 0) return 0; pop_logout (ctx); if (ret < -1) return -1; if (query_quadoption (OPT_POPRECONNECT, _("Connection lost. Reconnect to POP server?")) != M_YES) return -1; } }
/* * Get capabilities * 0 - successful, * -1 - conection lost, * -2 - execution error. */ static int pop_capabilities (POP_DATA *pop_data, int mode) { char buf[LONG_STRING]; /* don't check capabilities on reconnect */ if (pop_data->capabilities) return 0; /* init capabilities */ if (mode == 0) { pop_data->cmd_capa = 0; pop_data->cmd_stls = 0; pop_data->cmd_user = 0; pop_data->cmd_uidl = 0; pop_data->cmd_top = 0; pop_data->resp_codes = 0; pop_data->expire = 1; pop_data->login_delay = 0; FREE (&pop_data->auth_list); } /* Execute CAPA command */ if (mode == 0 || pop_data->cmd_capa) { strfcpy (buf, "CAPA\r\n", sizeof (buf)); switch (pop_fetch_data (pop_data, buf, NULL, fetch_capa, pop_data)) { case 0: { pop_data->cmd_capa = 1; break; } case -1: return -1; } } /* CAPA not supported, use defaults */ if (mode == 0 && !pop_data->cmd_capa) { pop_data->cmd_user = 2; pop_data->cmd_uidl = 2; pop_data->cmd_top = 2; strfcpy (buf, "AUTH\r\n", sizeof (buf)); if (pop_fetch_data (pop_data, buf, NULL, fetch_auth, pop_data) == -1) return -1; } /* Check capabilities */ if (mode == 2) { char *msg = NULL; if (!pop_data->expire) msg = _("Unable to leave messages on server."); if (!pop_data->cmd_top) msg = _("Command TOP is not supported by server."); if (!pop_data->cmd_uidl) msg = _("Command UIDL is not supported by server."); if (msg && pop_data->cmd_capa) { mutt_error (msg); return -2; } pop_data->capabilities = 1; } return 0; }
/* Fetch messages and save them in $spoolfile */ void pop_fetch_mail(void) { char buffer[LONG_STRING]; char msgbuf[SHORT_STRING]; char *url, *p; int i, delanswer, last = 0, msgs, bytes, rset = 0, ret; CONNECTION *conn; CONTEXT ctx; MESSAGE *msg = NULL; ACCOUNT acct; POP_DATA *pop_data; if (!PopHost) { mutt_error _("POP host is not defined."); return; } url = p = safe_calloc(strlen(PopHost) + 7, sizeof(char)); if (url_check_scheme(PopHost) == U_UNKNOWN) { strcpy(url, "pop://"); /* __STRCPY_CHECKED__ */ p = strchr(url, '\0'); } strcpy(p, PopHost); /* __STRCPY_CHECKED__ */ ret = pop_parse_path(url, &acct); safe_free(&url); if (ret) { mutt_error(_("%s is an invalid POP path"), PopHost); return; } conn = mutt_conn_find(NULL, &acct); if (!conn) return; pop_data = safe_calloc(1, sizeof(POP_DATA)); pop_data->conn = conn; if (pop_open_connection(pop_data) < 0) { mutt_socket_free(pop_data->conn); safe_free(&pop_data); return; } conn->data = pop_data; mutt_message _("Checking for new messages..."); /* find out how many messages are in the mailbox. */ strfcpy(buffer, "STAT\r\n", sizeof(buffer)); ret = pop_query(pop_data, buffer, sizeof(buffer)); if (ret == -1) goto fail; if (ret == -2) { mutt_error("%s", pop_data->err_msg); goto finish; } sscanf(buffer, "+OK %d %d", &msgs, &bytes); /* only get unread messages */ if ((msgs > 0) && option(OPTPOPLAST)) { strfcpy(buffer, "LAST\r\n", sizeof(buffer)); ret = pop_query(pop_data, buffer, sizeof(buffer)); if (ret == -1) goto fail; if (ret == 0) sscanf(buffer, "+OK %d", &last); } if (msgs <= last) { mutt_message _("No new mail in POP mailbox."); goto finish; } if (mx_open_mailbox(NONULL(Spoolfile), M_APPEND, &ctx) == NULL) goto finish; delanswer = query_quadoption(OPT_POPDELETE, _( "Delete messages from server?")); snprintf(msgbuf, sizeof(msgbuf), _( "Reading new messages (%d bytes)..."), bytes); mutt_message("%s", msgbuf); for (i = last + 1; i <= msgs; i++) { if ((msg = mx_open_new_message(&ctx, NULL, M_ADD_FROM)) == NULL) ret = -3; else { snprintf(buffer, sizeof(buffer), "RETR %d\r\n", i); ret = pop_fetch_data(pop_data, buffer, NULL, fetch_message, msg->fp); if (ret == -3) rset = 1; if ((ret == 0) && (mx_commit_message(msg, &ctx) != 0)) { rset = 1; ret = -3; } mx_close_message(&msg); } if ((ret == 0) && (delanswer == M_YES)) { /* delete the message on the server */ snprintf(buffer, sizeof(buffer), "DELE %d\r\n", i); ret = pop_query(pop_data, buffer, sizeof(buffer)); } if (ret == -1) { mx_close_mailbox(&ctx, NULL); goto fail; } if (ret == -2) { mutt_error("%s", pop_data->err_msg); break; } if (ret == -3) { mutt_error _("Error while writing mailbox!"); break; } mutt_message(_( "%s [%d of %d messages read]"), msgbuf, i - last, msgs - last); } mx_close_mailbox(&ctx, NULL); if (rset) { /* make sure no messages get deleted */ strfcpy(buffer, "RSET\r\n", sizeof(buffer)); if (pop_query(pop_data, buffer, sizeof(buffer)) == -1) goto fail; } finish: /* exit gracefully */ strfcpy(buffer, "QUIT\r\n", sizeof(buffer)); if (pop_query(pop_data, buffer, sizeof(buffer)) == -1) goto fail; mutt_socket_close(conn); safe_free(&pop_data); return; fail: mutt_error _("Server closed connection!"); mutt_socket_close(conn); safe_free(&pop_data); }
/* * Read header * returns: * 0 on success * -1 - connection lost, * -2 - invalid command or execution error, * -3 - error writing to tempfile */ static int pop_read_header(POP_DATA *pop_data, HEADER *h) { FILE *f; int ret, index; long length; char buf[LONG_STRING]; char tempfile[_POSIX_PATH_MAX]; mutt_mktemp(tempfile, sizeof(tempfile)); if (!(f = safe_fopen(tempfile, "w+"))) { mutt_perror(tempfile); return -3; } snprintf(buf, sizeof(buf), "LIST %d\r\n", h->refno); ret = pop_query(pop_data, buf, sizeof(buf)); if (ret == 0) { sscanf(buf, "+OK %d %ld", &index, &length); snprintf(buf, sizeof(buf), "TOP %d 0\r\n", h->refno); ret = pop_fetch_data(pop_data, buf, NULL, fetch_message, f); if (pop_data->cmd_top == 2) { if (ret == 0) { pop_data->cmd_top = 1; dprint(1, "pop_read_header: set TOP capability\n"); } if (ret == -2) { pop_data->cmd_top = 0; dprint(1, "pop_read_header: unset TOP capability\n"); snprintf(pop_data->err_msg, sizeof(pop_data->err_msg), _("Command TOP is not supported by server.")); } } } switch (ret) { case 0: { rewind(f); h->env = mutt_read_rfc822_header(f, h, 0, 0); h->content->length = length - h->content->offset + 1; rewind(f); while (!feof(f)) { h->content->length--; fgets(buf, sizeof(buf), f); } break; } case -2: { mutt_error("%s", pop_data->err_msg); break; } case -3: { mutt_error _("Can't write header to temporary file!"); break; } } safe_fclose(&f); unlink(tempfile); return ret; }
/* fetch message from POP server */ int pop_fetch_message(MESSAGE *msg, CONTEXT *ctx, int msgno) { int ret; void *uidl; char buf[LONG_STRING]; char path[_POSIX_PATH_MAX]; progress_t progressbar; POP_DATA *pop_data = (POP_DATA *)ctx->data; POP_CACHE *cache; HEADER *h = ctx->hdrs[msgno]; unsigned short bcache = 1; /* see if we already have the message in body cache */ if ((msg->fp = mutt_bcache_get(pop_data->bcache, h->data))) return 0; /* * see if we already have the message in our cache in * case $message_cachedir is unset */ cache = &pop_data->cache[h->index % POP_CACHE_LEN]; if (cache->path) { if (cache->index == h->index) { /* yes, so just return a pointer to the message */ msg->fp = fopen(cache->path, "r"); if (msg->fp) return 0; mutt_perror(cache->path); mutt_sleep(2); return -1; } else { /* clear the previous entry */ unlink(cache->path); safe_free(&cache->path); } } FOREVER { if (pop_reconnect(ctx) < 0) return -1; /* verify that massage index is correct */ if (h->refno < 0) { mutt_error _( "The message index is incorrect. Try reopening the mailbox."); mutt_sleep(2); return -1; } mutt_progress_init(&progressbar, _( "Fetching message..."), M_PROGRESS_SIZE, NetInc, h->content->length + h->content->offset - 1); /* see if we can put in body cache; use our cache as fallback */ if (!(msg->fp = mutt_bcache_put(pop_data->bcache, h->data, 1))) { /* no */ bcache = 0; mutt_mktemp(path, sizeof(path)); if (!(msg->fp = safe_fopen(path, "w+"))) { mutt_perror(path); mutt_sleep(2); return -1; } } snprintf(buf, sizeof(buf), "RETR %d\r\n", h->refno); ret = pop_fetch_data(pop_data, buf, &progressbar, fetch_message, msg->fp); if (ret == 0) break; safe_fclose(&msg->fp); /* if RETR failed (e.g. connection closed), be sure to remove either * the file in bcache or from POP's own cache since the next iteration * of the loop will re-attempt to put() the message */ if (!bcache) unlink(path); if (ret == -2) { mutt_error("%s", pop_data->err_msg); mutt_sleep(2); return -1; } if (ret == -3) { mutt_error _("Can't write message to temporary file!"); mutt_sleep(2); return -1; } } /* Update the header information. Previously, we only downloaded a * portion of the headers, those required for the main display. */ if (bcache) mutt_bcache_commit(pop_data->bcache, h->data); else { cache->index = h->index; cache->path = safe_strdup(path); } rewind(msg->fp); uidl = h->data; /* we replace envelop, key in subj_hash has to be updated as well */ if (ctx->subj_hash && h->env->real_subj) hash_delete(ctx->subj_hash, h->env->real_subj, h, NULL); mutt_free_envelope(&h->env); h->env = mutt_read_rfc822_header(msg->fp, h, 0, 0); if (ctx->subj_hash && h->env->real_subj) hash_insert(ctx->subj_hash, h->env->real_subj, h, 1); h->data = uidl; h->lines = 0; fgets(buf, sizeof(buf), msg->fp); while (!feof(msg->fp)) { ctx->hdrs[msgno]->lines++; fgets(buf, sizeof(buf), msg->fp); } h->content->length = ftello(msg->fp) - h->content->offset; /* This needs to be done in case this is a multipart message */ if (!WithCrypto) h->security = crypt_query(h->content); mutt_clear_error(); rewind(msg->fp); return 0; }
/* * Read headers * returns: * 0 on success * -1 - connection lost, * -2 - invalid command or execution error, * -3 - error writing to tempfile */ static int pop_fetch_headers(CONTEXT *ctx) { int i, ret, old_count, new_count, deleted; unsigned short hcached = 0, bcached; POP_DATA *pop_data = (POP_DATA *)ctx->data; progress_t progress; time(&pop_data->check_time); pop_data->clear_cache = 0; for (i = 0; i < ctx->msgcount; i++) ctx->hdrs[i]->refno = -1; old_count = ctx->msgcount; ret = pop_fetch_data(pop_data, "UIDL\r\n", NULL, fetch_uidl, ctx); new_count = ctx->msgcount; ctx->msgcount = old_count; if (pop_data->cmd_uidl == 2) { if (ret == 0) { pop_data->cmd_uidl = 1; dprint(1, "pop_fetch_headers: set UIDL capability\n"); } if ((ret == -2) && (pop_data->cmd_uidl == 2)) { pop_data->cmd_uidl = 0; dprint(1, "pop_fetch_headers: unset UIDL capability\n"); snprintf(pop_data->err_msg, sizeof(pop_data->err_msg), _("Command UIDL is not supported by server.")); } } if (!ctx->quiet) mutt_progress_init(&progress, _("Fetching message headers..."), M_PROGRESS_MSG, ReadInc, new_count - old_count); if (ret == 0) { for (i = 0, deleted = 0; i < old_count; i++) { if (ctx->hdrs[i]->refno == -1) { ctx->hdrs[i]->deleted = 1; deleted++; } } if (deleted > 0) { mutt_error(_( "%d messages have been lost. Try reopening the mailbox."), deleted); mutt_sleep(2); } for (i = old_count; i < new_count; i++) { if (!ctx->quiet) mutt_progress_update(&progress, i + 1 - old_count, -1); if ((ret = pop_read_header(pop_data, ctx->hdrs[i])) < 0) break; /* * faked support for flags works like this: * - if 'hcached' is 1, we have the message in our hcache: * - if we also have a body: read * - if we don't have a body: old * (if $mark_old is set which is maybe wrong as * $mark_old should be considered for syncing the * folder and not when opening it XXX) * - if 'hcached' is 0, we don't have the message in our hcache: * - if we also have a body: read * - if we don't have a body: new */ bcached = mutt_bcache_exists(pop_data->bcache, ctx->hdrs[i]->data) == 0; ctx->hdrs[i]->old = 0; ctx->hdrs[i]->read = 0; if (hcached) { if (bcached) ctx->hdrs[i]->read = 1; else if (option(OPTMARKOLD)) ctx->hdrs[i]->old = 1; } else { if (bcached) ctx->hdrs[i]->read = 1; } ctx->msgcount++; } if (i > old_count) mx_update_context(ctx, i - old_count); } if (ret < 0) { for (i = ctx->msgcount; i < new_count; i++) mutt_free_header(&ctx->hdrs[i]); return ret; } /* after putting the result into our structures, * clean up cache, i.e. wipe messages deleted outside * the availability of our cache */ if (option(OPTMESSAGECACHECLEAN)) mutt_bcache_list(pop_data->bcache, msg_cache_check, (void *)ctx); mutt_clear_error(); return new_count - old_count; }
/* * Read headers * returns: * 0 on success * -1 - conection lost, * -2 - invalid command or execution error, * -3 - error writing to tempfile */ static int pop_fetch_headers (CONTEXT *ctx) { int i, ret, old_count, new_count, deleted; unsigned short hcached = 0, bcached; POP_DATA *pop_data = (POP_DATA *)ctx->data; progress_t progress; #ifdef USE_HCACHE header_cache_t *hc = NULL; void *data; hc = pop_hcache_open (pop_data, ctx->path); #endif time (&pop_data->check_time); pop_data->clear_cache = 0; for (i = 0; i < ctx->msgcount; i++) ctx->hdrs[i]->refno = -1; old_count = ctx->msgcount; ret = pop_fetch_data (pop_data, "UIDL\r\n", NULL, fetch_uidl, ctx); new_count = ctx->msgcount; ctx->msgcount = old_count; if (pop_data->cmd_uidl == 2) { if (ret == 0) { pop_data->cmd_uidl = 1; dprint (1, (debugfile, "pop_fetch_headers: set UIDL capability\n")); } if (ret == -2 && pop_data->cmd_uidl == 2) { pop_data->cmd_uidl = 0; dprint (1, (debugfile, "pop_fetch_headers: unset UIDL capability\n")); snprintf (pop_data->err_msg, sizeof (pop_data->err_msg), _("Command UIDL is not supported by server.")); } } if (!ctx->quiet) mutt_progress_init (&progress, _("Fetching message headers..."), M_PROGRESS_MSG, ReadInc, new_count - old_count); if (ret == 0) { for (i = 0, deleted = 0; i < old_count; i++) { if (ctx->hdrs[i]->refno == -1) { ctx->hdrs[i]->deleted = 1; deleted++; } } if (deleted > 0) { mutt_error (_("%d messages have been lost. Try reopening the mailbox."), deleted); mutt_sleep (2); } for (i = old_count; i < new_count; i++) { if (!ctx->quiet) mutt_progress_update (&progress, i + 1 - old_count, -1); #if USE_HCACHE if ((data = mutt_hcache_fetch (hc, ctx->hdrs[i]->data, strlen))) { char *uidl = safe_strdup (ctx->hdrs[i]->data); int refno = ctx->hdrs[i]->refno; int index = ctx->hdrs[i]->index; /* * - POP dynamically numbers headers and relies on h->refno * to map messages; so restore header and overwrite restored * refno with current refno, same for index * - h->data needs to a separate pointer as it's driver-specific * data freed separately elsewhere * (the old h->data should point inside a malloc'd block from * hcache so there shouldn't be a memleak here) */ HEADER *h = mutt_hcache_restore ((unsigned char *) data, NULL); mutt_free_header (&ctx->hdrs[i]); ctx->hdrs[i] = h; ctx->hdrs[i]->refno = refno; ctx->hdrs[i]->index = index; ctx->hdrs[i]->data = uidl; ret = 0; hcached = 1; } else #endif if ((ret = pop_read_header (pop_data, ctx->hdrs[i])) < 0) break; #if USE_HCACHE else { mutt_hcache_store (hc, ctx->hdrs[i]->data, ctx->hdrs[i], 0, strlen, M_GENERATE_UIDVALIDITY); } FREE(&data); #endif /* * faked support for flags works like this: * - if 'hcached' is 1, we have the message in our hcache: * - if we also have a body: read * - if we don't have a body: old * (if $mark_old is set which is maybe wrong as * $mark_old should be considered for syncing the * folder and not when opening it XXX) * - if 'hcached' is 0, we don't have the message in our hcache: * - if we also have a body: read * - if we don't have a body: new */ bcached = mutt_bcache_exists (pop_data->bcache, ctx->hdrs[i]->data) == 0; ctx->hdrs[i]->old = 0; ctx->hdrs[i]->read = 0; if (hcached) { if (bcached) ctx->hdrs[i]->read = 1; else if (option (OPTMARKOLD)) ctx->hdrs[i]->old = 1; } else { if (bcached) ctx->hdrs[i]->read = 1; } ctx->msgcount++; } if (i > old_count) mx_update_context (ctx, i - old_count); } #if USE_HCACHE mutt_hcache_close (hc); #endif if (ret < 0) { for (i = ctx->msgcount; i < new_count; i++) mutt_free_header (&ctx->hdrs[i]); return ret; } /* after putting the result into our structures, * clean up cache, i.e. wipe messages deleted outside * the availability of our cache */ if (option (OPTMESSAGECACHECLEAN)) mutt_bcache_list (pop_data->bcache, msg_cache_check, (void*)ctx); mutt_clear_error (); return (new_count - old_count); }