diff options
author | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
---|---|---|
committer | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
commit | 094ca96844842928810f14844413109fc6cdd890 (patch) | |
tree | e60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith/remote.c | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'pith/remote.c')
-rw-r--r-- | pith/remote.c | 2820 |
1 files changed, 2820 insertions, 0 deletions
diff --git a/pith/remote.c b/pith/remote.c new file mode 100644 index 00000000..40a7f055 --- /dev/null +++ b/pith/remote.c @@ -0,0 +1,2820 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: remote.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $"; +#endif + +/* + * ======================================================================== + * Copyright 2006-2008 University of Washington + * Copyright 2013 Eduardo Chappa + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * ======================================================================== + */ + +/*====================================================================== + remote.c + Implements remote IMAP config files (remote config, remote abook). + ====*/ + + +#include "../pith/headers.h" +#include "../pith/remote.h" +#include "../pith/conf.h" +#include "../pith/imap.h" +#include "../pith/msgno.h" +#include "../pith/mailview.h" +#include "../pith/status.h" +#include "../pith/flag.h" +#include "../pith/tempfile.h" +#include "../pith/adrbklib.h" +#include "../pith/detach.h" +#include "../pith/filter.h" +#include "../pith/stream.h" +#include "../pith/options.h" +#include "../pith/busy.h" +#include "../pith/readfile.h" + + +/* + * Internal prototypes + */ +REMDATA_META_S *rd_find_our_metadata(char *, unsigned long *); +int rd_meta_is_broken(FILE *); +int rd_add_hdr_msg(REMDATA_S *, char *); +int rd_store_fake_hdrs(REMDATA_S *, char *, char *, char *); +int rd_upgrade_cookies(REMDATA_S *, long, int); +int rd_check_for_suspect_data(REMDATA_S *); + + +char meta_prefix[] = ".ab"; + + +char *(*pith_opt_rd_metadata_name)(void); + + +char * +read_remote_pinerc(PINERC_S *prc, ParsePinerc which_vars) +{ + int try_cache, no_perm_create_pass = 0; + char *file = NULL; + unsigned flags; + + + dprint((7, "read_remote_pinerc \"%s\"\n", + prc->name ? prc->name : "?")); + + /* + * We don't cache the pinerc, we always copy it. + * + * Don't store the config in a temporary file, just leave it + * in memory while using it. + * It is currently required that NO_PERM_CACHE be set if NO_FILE is set. + */ + flags = (NO_PERM_CACHE | NO_FILE); + +create_the_remote_folder: + + if(no_perm_create_pass){ + if(prc->rd){ + prc->rd->flags &= ~DO_REMTRIM; + rd_close_remdata(&prc->rd); + } + + /* this will cause the remote folder to be created */ + flags = 0; + } + + /* + * We could parse the name here to find what type it is. So far we + * only have type RemImap. + */ + prc->rd = rd_create_remote(RemImap, prc->name, + REMOTE_PINERC_SUBTYPE, + &flags, _("Error: "), + _(" Can't fetch remote configuration.")); + if(!prc->rd) + goto bail_out; + + /* + * On first use we just use a temp file instead of memory (NO_FILE). + * In other words, for our convenience, we don't turn NO_FILE back on + * here. Why is that convenient? Because of the stuff that happened in + * rd_create_remote when flags was set to zero. + */ + if(no_perm_create_pass) + prc->rd->flags |= NO_PERM_CACHE; + + try_cache = rd_read_metadata(prc->rd); + + if(prc->rd->access == MaybeRorW){ + if(prc->rd->read_status == 'R' || + !(which_vars == ParsePers || which_vars == ParsePersPost)){ + prc->rd->access = ReadOnly; + prc->rd->read_status = 'R'; + } + else + prc->rd->access = ReadWrite; + } + + if(prc->rd->access != NoExists){ + + rd_check_remvalid(prc->rd, 1L); + + /* + * If the cached info says it is readonly but + * it looks like it's been fixed now, change it to readwrite. + */ + if((which_vars == ParsePers || which_vars == ParsePersPost) && + prc->rd->read_status == 'R'){ + /* + * We go to this trouble since readonly pinercs + * are likely a mistake. They are usually supposed to be + * readwrite so we open it and check if it's been fixed. + */ + rd_check_readonly_access(prc->rd); + if(prc->rd->read_status == 'W'){ + prc->rd->access = ReadWrite; + prc->rd->flags |= REM_OUTOFDATE; + } + else + prc->rd->access = ReadOnly; + } + + if(prc->rd->flags & REM_OUTOFDATE){ + if(rd_update_local(prc->rd) != 0){ + if(!no_perm_create_pass && prc->rd->flags & NO_PERM_CACHE + && !(prc->rd->flags & USER_SAID_NO)){ + /* + * We don't check for the existence of the remote + * folder when this flag is turned on, so we could + * fail here because the remote folder doesn't exist. + * We try to create it. + */ + no_perm_create_pass++; + goto create_the_remote_folder; + } + + dprint((1, + "read_pinerc_remote: rd_update_local failed\n")); + /* + * Don't give up altogether. We still may be + * able to use a cached copy. + */ + } + else{ + dprint((7, + "%s: copied remote to local (%ld)\n", + prc->rd->rn ? prc->rd->rn : "?", + (long)prc->rd->last_use)); + } + } + + if(prc->rd->access == ReadWrite) + prc->rd->flags |= DO_REMTRIM; + } + + /* If we couldn't get to remote folder, try using the cached copy */ + if(prc->rd->access == NoExists || prc->rd->flags & REM_OUTOFDATE){ + if(try_cache){ + prc->rd->access = ReadOnly; + prc->rd->flags |= USE_OLD_CACHE; + q_status_message(SM_ORDER, 3, 4, + "Can't contact remote config server, using cached copy"); + dprint((2, + "Can't open remote pinerc %s, using local cached copy %s readonly\n", + prc->rd->rn ? prc->rd->rn : "?", + prc->rd->lf ? prc->rd->lf : "?")); + } + else{ + prc->rd->flags &= ~DO_REMTRIM; + goto bail_out; + } + } + + if(prc->rd->flags & NO_FILE) + /* copy text, leave sonofile for later use */ + file = cpystr((char *)so_text(prc->rd->sonofile)); + else + file = read_file(prc->rd->lf, 0); + +bail_out: + if((which_vars == ParsePers || which_vars == ParsePersPost) && + (!file || !prc->rd || prc->rd->access != ReadWrite)){ + prc->readonly = 1; + if(prc == ps_global->prc) + ps_global->readonly_pinerc = 1; + } + + return(file); +} + + +/* + * Check if the remote data folder exists and create an empty folder + * if it doesn't exist. + * + * Args - type -- The type of remote storage. + * remote_name -- + * type_spec -- Type-specific data. + * flags -- + * err_prefix -- Should usually end with a SPACE + * err_suffix -- Should usually begin with a SPACE + * + * Returns a pointer to a REMDATA_S with access set to either + * NoExists or MaybeRorW. On success, "so" will point to a storage object. + */ +REMDATA_S * +rd_create_remote(RemType type, char *remote_name, char *type_spec, + unsigned int *flags, char *err_prefix, char *err_suffix) +{ + REMDATA_S *rd = NULL; + CONTEXT_S *dummy_cntxt = NULL; + + dprint((7, "rd_create_remote \"%s\"\n", + remote_name ? remote_name : "?")); + + rd = rd_new_remdata(type, remote_name, type_spec); + if(flags) + rd->flags = *flags; + + switch(rd->type){ + case RemImap: + if(rd->flags & NO_PERM_CACHE){ + if(rd->rn && (rd->so = so_get(CharStar, NULL, WRITE_ACCESS))){ + if(rd->flags & NO_FILE){ + rd->sonofile = so_get(CharStar, NULL, WRITE_ACCESS); + if(!rd->sonofile) + rd->flags &= ~NO_FILE; + } + + /* + * We're not going to check if it is there in this case, + * in order to save ourselves some round trips and + * connections. We'll just try to select it and then + * recover at that point if it isn't already there. + */ + rd->flags |= REM_OUTOFDATE; + rd->access = MaybeRorW; + } + + } + else{ + /* + * Open_fcc creates the folder if it didn't already exist. + */ + if(rd->rn && (rd->so = open_fcc(rd->rn, &dummy_cntxt, 1, + err_prefix, err_suffix)) != NULL){ + /* + * We know the folder is there but don't know what access + * rights we have until we try to select it, which we don't + * want to do unless we have to. So delay evaluating. + */ + rd->access = MaybeRorW; + } + } + + break; + + default: + q_status_message(SM_ORDER, 3,5, "rd_create_remote: type not supported"); + break; + } + + return(rd); +} + + +REMDATA_S * +rd_new_remdata(RemType type, char *remote_name, char *type_spec) +{ + REMDATA_S *rd = NULL; + + rd = (REMDATA_S *)fs_get(sizeof(*rd)); + memset((void *)rd, 0, sizeof(*rd)); + + rd->type = type; + rd->access = NoExists; + + if(remote_name) + rd->rn = cpystr(remote_name); + + switch(rd->type){ + case RemImap: + if(type_spec) + rd->t.i.special_hdr = cpystr(type_spec); + + break; + + default: + q_status_message(SM_ORDER, 3,5, "rd_new_remdata: type not supported"); + break; + } + + return(rd); +} + + +/* + * Closes the remote stream and frees. + */ +void +rd_free_remdata(REMDATA_S **rd) +{ + if(rd && *rd){ + rd_close_remote(*rd); + + if((*rd)->rn) + fs_give((void **)&(*rd)->rn); + + if((*rd)->lf) + fs_give((void **)&(*rd)->lf); + + if((*rd)->so){ + so_give(&(*rd)->so); + (*rd)->so = NULL; + } + + if((*rd)->sonofile){ + so_give(&(*rd)->sonofile); + (*rd)->sonofile = NULL; + } + + switch((*rd)->type){ + case RemImap: + if((*rd)->t.i.special_hdr) + fs_give((void **)&(*rd)->t.i.special_hdr); + + if((*rd)->t.i.chk_date) + fs_give((void **)&(*rd)->t.i.chk_date); + + break; + + default: + q_status_message(SM_ORDER, 3, 5, + "rd_free_remdata: type not supported"); + break; + } + + fs_give((void **)rd); + } +} + + +/* + * Call this when finished with the remdata. This does the REMTRIM if the + * flag is set, the DEL_FILE if the flag is set. It also closes the stream + * and frees the rd. + */ +void +rd_trim_remdata(REMDATA_S **rd) +{ + if(!(rd && *rd)) + return; + + switch((*rd)->type){ + case RemImap: + /* + * Trim the number of saved copies of remote data history. + * The first message is a fake message that always + * stays there, then come ps_global->remote_abook_history messages + * which are each a revision of the data, then comes the active + * data. + */ + if((*rd)->flags & DO_REMTRIM && + !((*rd)->flags & REM_OUTOFDATE) && + (*rd)->t.i.chk_nmsgs > ps_global->remote_abook_history + 2){ + + /* make sure stream is open */ + rd_ping_stream(*rd); + rd_open_remote(*rd); + + if(!rd_remote_is_readonly(*rd)){ + if((*rd)->t.i.stream && + (*rd)->t.i.stream->nmsgs > + ps_global->remote_abook_history + 2){ + char sequence[20]; + int user_deleted = 0; + + /* + * If user manually deleted some, we'd better not delete + * any more. + */ + if(count_flagged((*rd)->t.i.stream, F_DEL) == 0L){ + + dprint((4, " rd_trim: trimming remote: mark msgs 2-%ld deleted (%s)\n", (*rd)->t.i.stream->nmsgs - 1 - ps_global->remote_abook_history, (*rd)->rn ? (*rd)->rn : "?")); + snprintf(sequence, sizeof(sequence), "2:%ld", + (*rd)->t.i.stream->nmsgs - 1 - ps_global->remote_abook_history); + mail_flag((*rd)->t.i.stream, sequence, + "\\DELETED", ST_SET); + } + else + user_deleted++; + + mail_expunge((*rd)->t.i.stream); + + if(!user_deleted) + rd_update_metadata(*rd, NULL); + /* else + * don't update metafile because user is messing with + * the remote folder manually. We'd better re-read it next + * time. */ + } + } + + ps_global->noshow_error = 0; + } + + break; + + default: + q_status_message(SM_ORDER, 3,5, "rd_trim_remdata: type not supported"); + break; + } +} + + +/* + * All done with this remote data. Trim the folder, close the + * stream, and free. + */ +void +rd_close_remdata(REMDATA_S **rd) +{ + if(!(rd && *rd)) + return; + + rd_trim_remdata(rd); + + if((*rd)->lf && (*rd)->flags & DEL_FILE) + our_unlink((*rd)->lf); + + /* this closes the stream and frees memory */ + rd_free_remdata(rd); +} + + +/* + * Looks in the metadata file for the cache line corresponding to rd and + * fills in data in rd. + * + * Return value -- 1 if it is likely that the filename we're returning + * is the permanent name of the local cache file and it may already have + * a cached copy of the data. This is to tell us if it makes sense to use + * the cached copy when we are unable to contact the remote server. + * 0 otherwise. + */ +int +rd_read_metadata(REMDATA_S *rd) +{ + REMDATA_META_S *rab = NULL; + int try_cache = 0; + + dprint((7, "rd_read_metadata \"%s\"\n", + (rd && rd->rn) ? rd->rn : "?")); + + if(!rd) + return try_cache; + + if(rd->flags & NO_PERM_CACHE) + rab = NULL; + else + rab = rd_find_our_metadata(rd->rn, &rd->flags); + + if(!rab){ + if(!rd->lf){ + rd->flags |= (NO_META_UPDATE | REM_OUTOFDATE); + if(!(rd->flags & NO_FILE)){ + rd->lf = temp_nam(NULL, "a6"); + rd->flags |= DEL_FILE; + } + + /* display error */ + if(!(rd->flags & NO_PERM_CACHE)) + display_message('x'); + + dprint((2, "using temp cache file %s\n", + rd->lf ? rd->lf : "<none>")); + } + } + else if(rab->local_cache_file){ /* A-OK, it was in the file already */ + if(!is_absolute_path(rab->local_cache_file)){ + char dir[MAXPATH+1], path[MAXPATH+1]; + char *lc; + + /* + * This should be the normal case. The file is stored as a + * filename in the pinerc dir, so that it can be + * accessed from the PC or from unix where the pathnames to + * get there will be different. + */ + + if((lc = last_cmpnt(ps_global->pinerc)) != NULL){ + int to_copy; + + to_copy = (lc - ps_global->pinerc > 1) + ? (lc - ps_global->pinerc - 1) : 1; + strncpy(dir, ps_global->pinerc, MIN(to_copy, sizeof(dir)-1)); + dir[MIN(to_copy, sizeof(dir)-1)] = '\0'; + } + else{ + dir[0] = '.'; + dir[1] = '\0'; + } + + build_path(path, dir, rab->local_cache_file, sizeof(path)); + rd->lf = cpystr(path); + } + else{ + rd->lf = rab->local_cache_file; + /* don't free this below, we're using it */ + rab->local_cache_file = NULL; + } + + rd->read_status = rab->read_status; + + switch(rd->type){ + case RemImap: + rd->t.i.chk_date = rab->date; + rab->date = NULL; /* don't free this below, we're using it */ + + dprint((7, + "in read_metadata, setting chk_date from metadata to ->%s<-\n", + rd->t.i.chk_date ? rd->t.i.chk_date : "?")); + rd->t.i.chk_nmsgs = rab->nmsgs; + rd->t.i.uidvalidity = rab->uidvalidity; + rd->t.i.uidnext = rab->uidnext; + rd->t.i.uid = rab->uid; + rd->t.i.chk_nmsgs = rab->nmsgs; + dprint((7, + "setting uid=%lu uidnext=%lu uidval=%lu read_stat=%c nmsgs=%lu\n", + rd->t.i.uid, rd->t.i.uidnext, rd->t.i.uidvalidity, + rd->read_status ? rd->read_status : '0', + rd->t.i.chk_nmsgs)); + break; + + default: + q_status_message(SM_ORDER, 3, 5, + "rd_read_metadata: type not supported"); + break; + } + + if(rd->t.i.chk_nmsgs > 0) + try_cache++; /* cache should be valid if we can't contact server */ + } + /* + * The line for this data wasn't in the metadata file yet. + * Figure out what should go there and put it in. + */ + else{ + /* + * The local_cache_file is where we will store the cached local + * copy of the remote data. + */ + rab->local_cache_file = tempfile_in_same_dir(ps_global->pinerc, + meta_prefix, NULL); + if(rab->local_cache_file){ + rd->lf = rab->local_cache_file; + rd_write_metadata(rd, 0); + rab->local_cache_file = NULL; + } + else + rd->lf = temp_nam(NULL, "a7"); + } + + if(rab){ + if(rab->local_cache_file) + fs_give((void **)&rab->local_cache_file); + if(rab->date) + fs_give((void **)&rab->date); + fs_give((void **)&rab); + } + + return(try_cache); +} + + +/* + * Write out the contents of the metadata file. + * + * Each line should be: folder_name cache_file uidvalidity uidnext uid nmsgs + * read_status date + * + * If delete_it is set, then remove the line instead of updating it. + * + * We have to be careful with the metadata, because it exists in user's + * existing metadata files now, and it is oriented towards only RemImap. + * We would have to change the version number and the format of the lines + * in the file if we add another type. + */ +void +rd_write_metadata(REMDATA_S *rd, int delete_it) +{ + char *tempfile; + FILE *fp_old = NULL, *fp_new = NULL; + char *p = NULL, *pinerc_dir = NULL, *metafile = NULL; + char *rel_filename, *key; + char line[MAILTMPLEN]; + int fd; + + dprint((7, "rd_write_metadata \"%s\"\n", + (rd && rd->rn) ? rd->rn : "?")); + + if(!rd || rd->flags & NO_META_UPDATE) + return; + + if(rd->type != RemImap){ + q_status_message(SM_ORDER, 3, 5, + "rd_write_metadata: type not supported"); + return; + } + + dprint((9, " - rd_write_metadata: rn=%s lf=%s\n", + rd->rn ? rd->rn : "?", rd->lf ? rd->lf : "?")); + + key = rd->rn; + + if(!(pith_opt_rd_metadata_name && (metafile = (*pith_opt_rd_metadata_name)()))) + goto io_err; + + if(!(tempfile = tempfile_in_same_dir(metafile, "a9", &pinerc_dir))) + goto io_err; + + if((fd = our_open(tempfile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) >= 0) + fp_new = fdopen(fd, "w"); + + if(pinerc_dir && rd->lf && strlen(rd->lf) > strlen(pinerc_dir)) + rel_filename = rd->lf + strlen(pinerc_dir) + 1; + else + rel_filename = rd->lf; + + if(pinerc_dir) + fs_give((void **)&pinerc_dir); + + fp_old = our_fopen(metafile, "rb"); + + if(fp_new && fp_old){ + /* + * Write the header line. + */ + if(fprintf(fp_new, "%s %s Pine Metadata\n", + PMAGIC, METAFILE_VERSION_NUM) == EOF) + goto io_err; + + while((p = fgets(line, sizeof(line), fp_old)) != NULL){ + /* + * Skip the header line and any lines that don't begin + * with a "{". + */ + if(line[0] != '{') + continue; + + /* skip the old cache line for this key */ + if(strncmp(line, key, strlen(key)) == 0 && line[strlen(key)] == TAB) + continue; + + /* add this line to new version of file */ + if(fputs(p, fp_new) == EOF) + goto io_err; + } + } + + /* add the cache line for this key */ + /* Warning: this is type RemImap specific right now! */ + if(!delete_it){ + if(!tempfile || + !fp_old || + !fp_new || + p || + fprintf(fp_new, "%s\t%s\t%lu\t%lu\t%lu\t%lu\t%c\t%s\n", + key ? key : "", + rel_filename ? rel_filename : "", + rd->t.i.uidvalidity, rd->t.i.uidnext, rd->t.i.uid, + rd->t.i.chk_nmsgs, + rd->read_status ? rd->read_status : '?', + rd->t.i.chk_date ? rd->t.i.chk_date : "no-match") + == EOF) + goto io_err; + } + + if(fclose(fp_new) == EOF){ + fp_new = NULL; + goto io_err; + } + + if(fclose(fp_old) == EOF){ + fp_old = NULL; + goto io_err; + } + + if(rename_file(tempfile, metafile) < 0) + goto io_err; + + if(tempfile) + fs_give((void **)&tempfile); + + if(metafile) + fs_give((void **)&metafile); + + return; + +io_err: + dprint((2, "io_err in rd_write_metadata(%s), tempfile=%s: %s\n", + metafile ? metafile : "<NULL>", tempfile ? tempfile : "<NULL>", + error_description(errno))); + q_status_message2(SM_ORDER, 3, 5, + "Trouble updating metafile %s, continuing (%s)", + metafile ? metafile : "<NULL>", error_description(errno)); + if(tempfile){ + our_unlink(tempfile); + fs_give((void **)&tempfile); + } + if(metafile) + fs_give((void **)&metafile); + if(fp_old) + (void)fclose(fp_old); + if(fp_new) + (void)fclose(fp_new); +} + + +void +rd_update_metadata(REMDATA_S *rd, char *date) +{ + if(!rd) + return; + + dprint((9, " - rd_update_metadata: rn=%s lf=%s\n", + rd->rn ? rd->rn : "?", rd->lf ? rd->lf : "<none>")); + + switch(rd->type){ + case RemImap: + if(rd->t.i.stream){ + ps_global->noshow_error = 1; + rd_ping_stream(rd); + if(rd->t.i.stream){ + /* + * If nmsgs < 2 then something is wrong. Maybe it is just + * that we haven't been told about the messages we've + * appended ourselves. Try closing and re-opening the stream + * to see those. + */ + if(rd->t.i.stream->nmsgs < 2 || + (rd->t.i.shouldbe_nmsgs && + (rd->t.i.shouldbe_nmsgs != rd->t.i.stream->nmsgs))){ + pine_mail_check(rd->t.i.stream); + if(rd->t.i.stream->nmsgs < 2 || + (rd->t.i.shouldbe_nmsgs && + (rd->t.i.shouldbe_nmsgs != rd->t.i.stream->nmsgs))){ + rd_close_remote(rd); + rd_open_remote(rd); + } + } + + rd->t.i.chk_nmsgs = rd->t.i.stream ? rd->t.i.stream->nmsgs : 0L; + + /* + * If nmsgs < 2 something is wrong. + */ + if(rd->t.i.chk_nmsgs < 2){ + rd->t.i.uidvalidity = 0L; + rd->t.i.uid = 0L; + rd->t.i.uidnext = 0L; + } + else{ + rd->t.i.uidvalidity = rd->t.i.stream->uid_validity; + rd->t.i.uid = mail_uid(rd->t.i.stream, + rd->t.i.stream->nmsgs); + /* + * Uid_last is not always valid. If the last known uid is + * greater than uid_last, go with it instead (uid+1). + * If our guess is wrong (too low), the penalty is not + * harsh. When the uidnexts don't match we open the + * mailbox to check the uid of the actual last message. + * If it was a false hit then we adjust uidnext so it + * will be correct the next time through. + */ + rd->t.i.uidnext = MAX(rd->t.i.stream->uid_last,rd->t.i.uid) + + 1; + } + } + + ps_global->noshow_error = 0; + + /* + * Save the date so that we can check if it changed next time + * we go to write. + */ + if(date){ + if(rd->t.i.chk_date) + fs_give((void **)&rd->t.i.chk_date); + + rd->t.i.chk_date = cpystr(date); + } + + rd_write_metadata(rd, 0); + } + + rd->t.i.shouldbe_nmsgs = 0; + + break; + + default: + q_status_message(SM_ORDER, 3, 5, + "rd_update_metadata: type not supported"); + break; + } +} + + + + +REMDATA_META_S * +rd_find_our_metadata(char *key, long unsigned int *flags) +{ + char *p, *q, *metafile = NULL; + char line[MAILTMPLEN]; + REMDATA_META_S *rab = NULL; + FILE *fp; + + dprint((9, "rd_find_our_metadata \"%s\"\n", key ? key : "?")); + + if(!(key && *key)) + return rab; + + if(!(pith_opt_rd_metadata_name && (metafile = (*pith_opt_rd_metadata_name)()) != NULL)) + return rab; + + /* + * Open the metadata file and get some information out. + */ + fp = our_fopen(metafile, "rb"); + if(fp == NULL){ + q_status_message2(SM_ORDER, 3, 5, + _("can't open metadata file %s, continuing (%s)"), + metafile, error_description(errno)); + dprint((2, + "can't open existing metadata file %s: %s\n", + metafile ? metafile : "?", error_description(errno))); + if(flags) + (*flags) |= NO_META_UPDATE; + + fs_give((void **)&metafile); + + return rab; + } + + /* + * If we make it to this point where we have opened the metadata file + * we return a structure (possibly empty) instead of just a NULL pointer. + */ + rab = (REMDATA_META_S *)fs_get(sizeof(*rab)); + memset(rab, 0, sizeof(*rab)); + + /* + * Check for header line. If it isn't there or is incorrect, + * return with the empty rab. This call also positions the file pointer + * past the header line. + */ + if(rd_meta_is_broken(fp)){ + + dprint((2, + "metadata file is broken, creating new one: %s\n", + metafile ? metafile : "?")); + + /* Make it size zero so we won't copy any bad stuff */ + if(fp_file_size(fp) != 0){ + int fd; + + (void)fclose(fp); + our_unlink(metafile); + + if((fd = our_open(metafile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) < 0){ + q_status_message2(SM_ORDER, 3, 5, + _("can't create metadata file %s, continuing (%s)"), + metafile, error_description(errno)); + dprint((2, + "can't create metadata file %s: %s\n", + metafile ? metafile : "?", + error_description(errno))); + fs_give((void **)&rab); + fs_give((void **)&metafile); + return NULL; + } + + (void)close(fd); + } + else + (void)fclose(fp); + + fs_give((void **)&metafile); + return(rab); + } + + fs_give((void **)&metafile); + + /* Look for our line */ + while((p = fgets(line, sizeof(line), fp)) != NULL) + if(strncmp(line, key, strlen(key)) == 0 && line[strlen(key)] == TAB) + break; + +#define SKIP_TO_TAB(p) do{while(*p && *p != TAB)p++;}while(0) + + /* + * The line should be TAB-separated with fields: + * folder_name cache_file uidvalidity uidnext uid nmsgs read_status date + * This part is highly RemImap-specific right now. + */ + if(p){ /* Found the line, parse it. */ + SKIP_TO_TAB(p); /* skip to TAB following folder_name */ + if(*p == TAB){ + q = ++p; /* q points to cache_file */ + SKIP_TO_TAB(p); /* skip to TAB following cache_file */ + if(*p == TAB){ + *p = '\0'; + rab->local_cache_file = cpystr(q); + q = ++p; /* q points to uidvalidity */ + SKIP_TO_TAB(p); /* skip to TAB following uidvalidity */ + if(*p == TAB){ + *p = '\0'; + rab->uidvalidity = strtoul(q,(char **)NULL,10); + q = ++p; /* q points to uidnext */ + SKIP_TO_TAB(p); /* skip to TAB following uidnext */ + if(*p == TAB){ + *p = '\0'; + rab->uidnext = strtoul(q,(char **)NULL,10); + q = ++p; /* q points to uid */ + SKIP_TO_TAB(p); /* skip to TAB following uid */ + if(*p == TAB){ + *p = '\0'; + rab->uid = strtoul(q,(char **)NULL,10); + q = ++p; /* q points to nmsgs */ + SKIP_TO_TAB(p); /* skip to TAB following nmsgs */ + if(*p == TAB){ + *p = '\0'; + rab->nmsgs = strtoul(q,(char **)NULL,10); + q = ++p; /* q points to read_status */ + SKIP_TO_TAB(p); /* skip to TAB following read_status */ + if(*p == TAB){ + *p = '\0'; + rab->read_status = *q; /* just a char, not whole string */ + q = ++p; /* q points to date */ + while(*p && *p != '\n' && *p != '\r') /* skip to newline */ + p++; + + *p = '\0'; + rab->date = cpystr(q); + } + } + } + } + } + } + } + } + + (void)fclose(fp); + return(rab); +} + + +/* + * Returns: -1 if this doesn't look like a metafile or some error, + * or if it looks like a non-current metafile. + * 0 if it looks like a current metafile. + * + * A side effect is that the file pointer will be pointing to the second + * line if 0 is returned. + */ +int +rd_meta_is_broken(FILE *fp) +{ + char buf[MAILTMPLEN]; + + if(fp == (FILE *)NULL) + return -1; + + if(fp_file_size(fp) < + (long)(SIZEOF_PMAGIC + SIZEOF_SPACE + SIZEOF_VERSION_NUM)) + return -1; + + if(fseek(fp, (long)TO_FIND_HDR_PMAGIC, 0)) + return -1; + + /* check for magic */ + if(fread(buf, sizeof(char), (unsigned)SIZEOF_PMAGIC, fp) != SIZEOF_PMAGIC) + return -1; + + buf[SIZEOF_PMAGIC] = '\0'; + if(strcmp(buf, PMAGIC) != 0) + return -1; + + /* + * If we change to a new version, we may want to add code here to convert + * or to cleanup after the old version. For example, it might just + * remove the old cache files here. + */ + + /* check for matching version number */ + if(fseek(fp, (long)TO_FIND_VERSION_NUM, 0)) + return -1; + + if(fread(buf, sizeof(char), (unsigned)SIZEOF_VERSION_NUM, fp) != + SIZEOF_VERSION_NUM) + return -1; + + buf[SIZEOF_VERSION_NUM] = '\0'; + if(strcmp(buf, METAFILE_VERSION_NUM) != 0) + return -1; + + /* Position file pointer to second line */ + if(fseek(fp, (long)TO_FIND_HDR_PMAGIC, 0)) + return -1; + + if(fgets(buf, sizeof(buf), fp) == NULL) + return -1; + + return 0; +} + + +/* + * Open a data stream to the remote data. + */ +void +rd_open_remote(REMDATA_S *rd) +{ + long openmode = SP_USEPOOL | SP_TEMPUSE; + + dprint((7, "rd_open_remote \"%s\"\n", + (rd && rd->rn) ? rd->rn : "?")); + + if(!rd) + return; + + if(rd_stream_exists(rd)){ + rd->last_use = get_adj_time(); + return; + } + + switch(rd->type){ + case RemImap: + + if(rd->access == ReadOnly) + openmode |= OP_READONLY; + + ps_global->noshow_error = 1; + rd->t.i.stream = context_open(NULL, NULL, rd->rn, openmode, NULL); + ps_global->noshow_error = 0; + + /* Don't try to reopen if there was a problem (auth failure, etc.) */ + if(!rd->t.i.stream) + rd->flags |= USER_SAID_NO; /* Caution: overloading USER_SAID_NO */ + + if(rd->t.i.stream && rd->t.i.stream->halfopen){ + /* this is a failure */ + rd_close_remote(rd); + } + + if(rd->t.i.stream) + rd->last_use = get_adj_time(); + + break; + + default: + q_status_message(SM_ORDER, 3, 5, "rd_open_remote: type not supported"); + break; + } +} + + +/* + * Close a data stream to the remote data. + */ +void +rd_close_remote(REMDATA_S *rd) +{ + if(!rd || !rd_stream_exists(rd)) + return; + + switch(rd->type){ + case RemImap: + ps_global->noshow_error = 1; + pine_mail_close(rd->t.i.stream); + rd->t.i.stream = NULL; + ps_global->noshow_error = 0; + break; + + default: + q_status_message(SM_ORDER, 3, 5, "rd_close_remote: type not supported"); + break; + } +} + + +int +rd_stream_exists(REMDATA_S *rd) +{ + if(!rd) + return(0); + + switch(rd->type){ + case RemImap: + return(rd->t.i.stream ? 1 : 0); + + default: + q_status_message(SM_ORDER, 3,5, "rd_stream_exists: type not supported"); + break; + } + + return(0); +} + + +/* + * Ping the stream and return 1 if it is still alive. + */ +int +rd_ping_stream(REMDATA_S *rd) +{ + int ret = 0; + + dprint((7, "rd_ping_stream \"%s\"\n", + (rd && rd->rn) ? rd->rn : "?")); + + if(!rd) + return(ret); + + if(!rd_stream_exists(rd)){ + switch(rd->type){ + case RemImap: + /* + * If this stream is already actually open, officially open + * it and use it. + */ + if(sp_stream_get(rd->rn, SP_MATCH)){ + long openflags = SP_USEPOOL | SP_TEMPUSE; + + if(rd->access == ReadOnly) + openflags |= OP_READONLY; + + rd->t.i.stream = pine_mail_open(NULL, rd->rn, openflags, NULL); + } + + break; + + default: + break; + } + } + + if(!rd_stream_exists(rd)) + return(ret); + + switch(rd->type){ + case RemImap: + ps_global->noshow_error = 1; + if(pine_mail_ping(rd->t.i.stream)) + ret = 1; + else + rd->t.i.stream = NULL; + + ps_global->noshow_error = 0; + break; + + default: + q_status_message(SM_ORDER, 3, 5, "rd_ping_stream: type not supported"); + break; + } + + return(ret); +} + + +/* + * Change readonly access to readwrite if appropriate. Call this if + * the remote data ought to be readwrite but it is marked readonly in + * the metadata. + */ +void +rd_check_readonly_access(REMDATA_S *rd) +{ + if(rd && rd->read_status == 'R'){ + switch(rd->type){ + case RemImap: + rd_open_remote(rd); + if(rd->t.i.stream && !rd->t.i.stream->rdonly){ + rd->read_status = 'W'; + rd->access = ReadWrite; + } + + break; + + default: + q_status_message(SM_ORDER, 3, 5, + "rd_check_readonly_access: type not supported"); + break; + } + } +} + + +/* + * Initialize remote data. The remote data is initialized + * with no data. On success, the local file will be empty and the remote + * folder (if type RemImap) will contain a header message and an empty + * data message. + * The remote folder already exists before we get here, though it may be empty. + * + * Returns -1 on failure + * 0 on success + */ +int +rd_init_remote(REMDATA_S *rd, int add_only_first_msg) +{ + int err = 0; + char date[200]; + + dprint((7, "rd_init_remote \"%s\"\n", + (rd && rd->rn) ? rd->rn : "?")); + + if(rd && rd->type != RemImap){ + dprint((1, "rd_init_remote: type not supported\n")); + return -1; + } + + /* + * The rest is currently type RemImap-specific. + */ + + if(!(rd->flags & NO_FILE || rd->lf) || + !rd->rn || !rd->so || !rd->t.i.stream || + !rd->t.i.special_hdr){ + dprint((1, + "rd_init_remote: Unexpected error: %s is NULL\n", + !(rd->flags & NO_FILE || rd->lf) ? "localfile" : + !rd->rn ? "remotename" : + !rd->so ? "so" : + !rd->t.i.stream ? "stream" : + !rd->t.i.special_hdr ? "special_hdr" : "?")); + return -1; + } + + /* already init'd */ + if(rd->t.i.stream->nmsgs >= 2 || + (rd->t.i.stream->nmsgs >= 1 && add_only_first_msg)) + return err; + + dprint((7, " - rd_init_remote(%s): %s\n", + rd->t.i.special_hdr ? rd->t.i.special_hdr : "?", + rd->rn ? rd->rn : "?")); + + /* + * We want to protect the user who specifies an actual address book + * file on the remote system as a remote address book. For example, + * they might say address-book={host}.addressbook where .addressbook + * is just a file on host. Remote address books are folders, not + * files. We also want to protect the user who specifies an existing + * mail folder as a remote address book. We do that by requiring the + * first message in the folder to be our header message. + */ + + if(rd->t.i.stream->rdonly){ + q_status_message1(SM_ORDER | SM_DING, 7, 10, + _("Can't initialize folder \"%s\" (write permission)"), rd->rn); + if(rd->t.i.stream->nmsgs > 0) + q_status_message(SM_ORDER | SM_DING, 7, 10, + _("Choose a new, unused folder for the remote data")); + + dprint((1, + "Can't initialize remote folder \"%s\"\n", + rd->rn ? rd->rn : "?")); + dprint((1, + " No write permission for that remote folder.\n")); + dprint((1, + " Choose a new unused folder for the remote data.\n")); + err = -1; + } + + if(!err){ + if(rd->t.i.stream->nmsgs == 0){ + int we_cancel; + + we_cancel = busy_cue(_("Initializing remote data"), NULL, 1); + rfc822_date(date); + /* + * The first message in a remote data folder is a special + * header message. It is there as a way to explain what the + * folder is to users who open it trying to read it. It is also + * used as a consistency check so that we don't use a folder + * that was being used for something else as a remote + * data folder. + */ + err = rd_add_hdr_msg(rd, date); + if(rd->t.i.stream->nmsgs == 0) + rd_ping_stream(rd); + if(rd->t.i.stream && rd->t.i.stream->nmsgs == 0) + pine_mail_check(rd->t.i.stream); + + if(we_cancel) + cancel_busy_cue(-1); + } + else{ + char *eptr = NULL; + + err = rd_chk_for_hdr_msg(&(rd->t.i.stream), rd, &eptr); + if(err){ + q_status_message1(SM_ORDER | SM_DING, 5, 5, + _("\"%s\" has invalid format, can't initialize"), rd->rn); + + dprint((1, + "Can't initialize remote data \"%s\"\n", + rd->rn ? rd->rn : "?")); + + if(eptr){ + q_status_message1(SM_ORDER, 3, 5, "%s", eptr); + dprint((1, "%s\n", eptr ? eptr : "?")); + fs_give((void **)&eptr); + } + } + } + } + + /* + * Add the second (empty) message. + */ + if(!err && !add_only_first_msg){ + char *tempfile = NULL; + int fd = -1; + + if(rd->flags & NO_FILE){ + if(so_truncate(rd->sonofile, 0L) == 0) + err = -1; + } + else{ + if(!(tempfile = tempfile_in_same_dir(rd->lf, "a8", NULL))){ + q_status_message1(SM_ORDER | SM_DING, 3, 5, + _("Error opening temporary file: %s"), + error_description(errno)); + dprint((2, "init_remote: Error opening file: %s\n", + error_description(errno))); + err = -1; + } + + if(!err && + (fd = our_open(tempfile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) < 0){ + q_status_message2(SM_ORDER | SM_DING, 3, 5, + "Error opening temporary file %.200s: %.200s", + tempfile, error_description(errno)); + dprint((2, + "init_remote: Error opening temporary file: %s: %s\n", + tempfile ? tempfile : "?", error_description(errno))); + our_unlink(tempfile); + err = -1; + } + else + (void)close(fd); + + if(!err && rename_file(tempfile, rd->lf) < 0){ + q_status_message2(SM_ORDER | SM_DING, 3, 5, + "Error creating cache file %.200s: %.200s", + rd->lf, error_description(errno)); + our_unlink(tempfile); + err = -1; + } + + if(tempfile) + fs_give((void **)&tempfile); + } + + if(!err){ + err = rd_update_remote(rd, date); + if(err){ + q_status_message1(SM_ORDER | SM_DING, 3, 5, + _("Error copying to remote folder: %s"), + error_description(errno)); + + q_status_message(SM_ORDER | SM_DING, 5, 5, + "Creation of remote data folder failed"); + } + } + } + + if(!err){ + rd_update_metadata(rd, date); + /* turn off out of date flag */ + rd->flags &= ~REM_OUTOFDATE; + } + + return(err ? -1 : 0); +} + + +/* + * IMAP stream should already be open to a remote folder. + * Check the first message in the folder to be sure it is the right + * kind of message, not some message from some other folder. + * + * Returns 0 if ok, < 0 if invalid format. + * + */ +int +rd_chk_for_hdr_msg(MAILSTREAM **streamp, REMDATA_S *rd, char **errmsg) +{ + char *fields[3], *values[3]; + char *h; + int tried_again = 0; + int ret; + MAILSTREAM *st = NULL; + + fields[0] = rd->t.i.special_hdr; + fields[1] = "received"; + fields[2] = NULL; + +try_again: + ret = -1; + + if(!streamp || !*streamp){ + dprint((1, "rd_chk_for_hdr_msg: stream is null\n")); + } + else if((*streamp)->nmsgs == 0){ + ret = -2; + dprint((1, + "rd_chk_for_hdr_msg: stream has nmsgs=0, try a ping\n")); + if(!pine_mail_ping(*streamp)) + *streamp = NULL; + + if(*streamp && (*streamp)->nmsgs == 0){ + dprint((1, + "rd_chk_for_hdr_msg: still looks like nmsgs=0, try a check\n")); + pine_mail_check(*streamp); + } + + if(*streamp && (*streamp)->nmsgs == 0){ + dprint((1, + "rd_chk_for_hdr_msg: still nmsgs=0, try re-opening stream\n")); + + if(rd_stream_exists(rd)) + rd_close_remote(rd); + + rd_open_remote(rd); + if(rd_stream_exists(rd)) + st = rd->t.i.stream; + } + + if(!st) + st = *streamp; + + if(st && st->nmsgs == 0){ + dprint((1, + "rd_chk_for_hdr_msg: can't see header message\n")); + } + } + else + st = *streamp; + + if(st && st->nmsgs != 0 + && (h=pine_fetchheader_lines(st, 1L, NULL, fields))){ + simple_header_parse(h, fields, values); + ret = -3; + if(values[1]) + ret = -4; + else if(values[0]){ + rd->cookie = strtoul(values[0], (char **)NULL, 10); + if(rd->cookie == 0) + ret = -5; + else if(rd->cookie == 1){ + if(rd->flags & COOKIE_ONE_OK || tried_again) + ret = 0; + else + ret = -6; + } + else if(rd->cookie > 1) + ret = 0; + } + + if(values[0]) + fs_give((void **)&values[0]); + + if(values[1]) + fs_give((void **)&values[1]); + + fs_give((void **)&h); + } + + + if(ret && ret != -6 && errmsg){ + *errmsg = (char *)fs_get(500 * sizeof(char)); + (*errmsg)[0] = '\0'; + } + + if(ret == -1){ + /* null stream */ + if(errmsg) + snprintf(*errmsg, 500, _("Can't open remote address book \"%s\""), rd->rn); + } + else if(ret == -2){ + /* no messages in folder */ + if(errmsg) + snprintf(*errmsg, 500, + _("Error: no messages in remote address book \"%s\"!"), + rd->rn); + } + else if(ret == -3){ + /* no cookie */ + if(errmsg) + snprintf(*errmsg, 500, + "First msg in \"%s\" should have \"%s\" header", + rd->rn, rd->t.i.special_hdr); + } + else if(ret == -4){ + /* Received header */ + if(errmsg) + snprintf(*errmsg, 500, + _("Suspicious Received headers in first msg in \"%s\""), + rd->rn); + } + else if(ret == -5){ + + /* cookie is 0 */ + + /* + * This is a failure and should not happen, but we're not going to + * fail on this condition. + */ + dprint((1, "Unexpected value in \"%s\" header of \"%s\"\n", + rd->t.i.special_hdr ? rd->t.i.special_hdr : "?", + rd->rn ? rd->rn : "?")); + ret = 0; + } + else if(ret == -6){ + dprint((1, + "rd_chk_for_hdr_msg: cookie is 1, try to upgrade it\n")); + + if(rd_remote_is_readonly(rd)){ + dprint((1, + "rd_chk_for_hdr_msg: can't upgrade, readonly\n")); + ret = 0; /* stick with 1 */ + } + else{ + /* cookie is 1, upgrade it */ + if(rd_upgrade_cookies(rd, st->nmsgs, 0) == 0){ + /* now check again */ + if(!tried_again){ + tried_again++; + goto try_again; + } + } + + /* + * This is actually a failure but we've decided that this + * failure is ok. + */ + ret = 0; + } + } + + if(errmsg && *errmsg) + dprint((1, "rd_chk_for_hdr_msg: %s\n", *errmsg)); + + return ret; +} + + +/* + * For remote data, this adds the explanatory header + * message to the remote IMAP folder. + * + * Args: rd -- Remote data handle + * date -- The date string to use + * + * Result: 0 success + * -1 failure + */ +int +rd_add_hdr_msg(REMDATA_S *rd, char *date) +{ + int err = 0; + + if(!rd|| rd->type != RemImap || !rd->rn || !rd->so || !rd->t.i.special_hdr){ + dprint((1, + "rd_add_hdr_msg: Unexpected error: %s is NULL\n", + !rd ? "rd" : + !rd->rn ? "remotename" : + !rd->so ? "so" : + !rd->t.i.special_hdr ? "special_hdr" : "?")); + return -1; + } + + err = rd_store_fake_hdrs(rd, "Header Message for Remote Data", + "plain", date); + + /* Write the dummy message */ + if(!strucmp(rd->t.i.special_hdr, REMOTE_ABOOK_SUBTYPE)){ + if(!err && so_puts(rd->so, + "This folder contains a single Alpine addressbook.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "This message is just an explanatory message.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "The last message in the folder is the live addressbook data.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "The rest of the messages contain previous revisions of the addressbook data.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "To restore a previous revision just delete and expunge all of the messages\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "which come after it.\015\012") == 0) + err = -1; + } + else if(!strucmp(rd->t.i.special_hdr, REMOTE_PINERC_SUBTYPE)){ + if(!err && so_puts(rd->so, + "This folder contains a Alpine config file.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "This message is just an explanatory message.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "The last message in the folder is the live config data.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "The rest of the messages contain previous revisions of the data.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "To restore a previous revision just delete and expunge all of the messages\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "which come after it.\015\012") == 0) + err = -1; + } + else{ + if(!err && so_puts(rd->so, + "This folder contains remote Alpine data.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "This message is just an explanatory message.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "The last message in the folder is the live data.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "The rest of the messages contain previous revisions of the data.\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "To restore a previous revision just delete and expunge all of the messages\015\012") == 0) + err = -1; + if(!err && so_puts(rd->so, + "which come after it.\015\012") == 0) + err = -1; + } + + /* Take the message from "so" to the remote folder */ + if(!err){ + MAILSTREAM *st; + + if((st = rd->t.i.stream) != NULL) + rd->t.i.shouldbe_nmsgs = rd->t.i.stream->nmsgs + 1; + else + st = adrbk_handy_stream(rd->rn); + + err = write_fcc(rd->rn, NULL, rd->so, st, "remote data", NULL) ? 0 : -1; + } + + return err; +} + + +/* + * Write some fake header lines into storage object rd->so. + * + * Args: rd + * subject -- subject to put in header + * subtype -- subtype to put in header + * date -- date to put in header + */ +int +rd_store_fake_hdrs(REMDATA_S *rd, char *subject, char *subtype, char *date) +{ + ENVELOPE *fake_env; + BODY *fake_body; + ADDRESS *fake_from; + int err = 0; + char vers[50], *p; + unsigned long r = 0L; + RFC822BUFFER rbuf; + + if(!rd|| rd->type != RemImap || !rd->so || !rd->t.i.special_hdr) + return -1; + + fake_env = (ENVELOPE *)fs_get(sizeof(ENVELOPE)); + memset(fake_env, 0, sizeof(ENVELOPE)); + fake_body = (BODY *)fs_get(sizeof(BODY)); + memset(fake_body, 0, sizeof(BODY)); + fake_from = (ADDRESS *)fs_get(sizeof(ADDRESS)); + memset(fake_from, 0, sizeof(ADDRESS)); + + fake_env->subject = cpystr(subject); + fake_env->date = (unsigned char *) cpystr(date); + fake_from->personal = cpystr("Pine Remote Data"); + fake_from->mailbox = cpystr("nobody"); + fake_from->host = cpystr("nowhere"); + fake_env->from = fake_from; + fake_body->type = REMOTE_DATA_TYPE; + fake_body->subtype = cpystr(subtype); + set_parameter(&fake_body->parameter, "charset", "UTF-8"); + + if(rd->cookie > 0) + r = rd->cookie; + + if(!r){ + int i; + + for(i = 100; i > 0 && r < 1000000; i--) + r = random(); + + if(r < 1000000) + r = 1712836L + getpid(); + + rd->cookie = r; + } + + snprintf(vers, sizeof(vers), "%ld", r); + + p = tmp_20k_buf; + *p = '\0'; + rbuf.f = dummy_soutr; + rbuf.s = NULL; + rbuf.beg = p; + rbuf.cur = p; + rbuf.end = p+SIZEOF_20KBUF-1; + rfc822_output_header_line(&rbuf, rd->t.i.special_hdr, 0L, vers); + rfc822_output_header(&rbuf, fake_env, fake_body, NULL, 0L); + *rbuf.cur = '\0'; + + mail_free_envelope(&fake_env); + mail_free_body(&fake_body); + + /* Write the fake headers */ + if(so_puts(rd->so, tmp_20k_buf) == 0) + err = -1; + + return err; +} + + +/* + * We have discovered that the data in the remote folder is suspicious. + * In some cases it is just because it is from an old version of pine. + * We have decided to update the data so that it won't look suspicious + * next time. + * + * Args -- only_update_last If set, that means to just add a new last message + * by calling rd_update_remote. Don't create a new + * header message and delete the old header message. + * nmsgs Not used if only_update_last is set + */ +int +rd_upgrade_cookies(REMDATA_S *rd, long int nmsgs, int only_update_last) +{ + char date[200]; + int err = 0; + + /* + * We need to copy the data from the last message, add a new header + * message with a random cookie, add the data back in with the + * new cookie, and delete the old messages. + */ + + /* get data */ + rd->flags |= COOKIE_ONE_OK; + + /* + * The local copy may be newer than the remote copy. We don't want to + * blast the local copy in that case. The BELIEVE_CACHE flag tells us + * to not do the blasting. + */ + if(rd->flags & BELIEVE_CACHE) + rd->flags &= ~BELIEVE_CACHE; + else{ + dprint((1, "rd_upgrade_cookies: copy abook data\n")); + err = rd_update_local(rd); + } + + if(!err && !only_update_last){ + rd->cookie = 0; /* causes new cookie to be generated */ + rfc822_date(date); + dprint((1, "rd_upgrade_cookies: add new hdr msg to end\n")); + err = rd_add_hdr_msg(rd, date); + } + + if(!err){ + dprint((1, "rd_upgrade_cookies: copy back data\n")); + err = rd_update_remote(rd, NULL); + } + + rd->flags &= ~COOKIE_ONE_OK; + + if(!err && !only_update_last){ + char sequence[20]; + + /* + * We've created a new header message and added a new copy of the + * data after it. Only problem is that the new copy will have used + * the original header message to get its cookie (== 1) from. We + * could have deleted the original messages before the last step + * to get it right but that would delete all copies of the data + * temporarily. Delete now and then re-update. + */ + rd_ping_stream(rd); + rd_open_remote(rd); + if(rd->t.i.stream && rd->t.i.stream->nmsgs >= nmsgs+2){ + snprintf(sequence, sizeof(sequence), "1:%ld", nmsgs); + mail_flag(rd->t.i.stream, sequence, "\\DELETED", ST_SET); + mail_expunge(rd->t.i.stream); + err = rd_update_remote(rd, NULL); + } + } + + return(err); +} + + +/* + * Copy remote data to local file. If needed, the remote data is initialized + * with no data. On success, the local file contains the remote data (which + * means it will be empty if we initialized). + * + * Returns != 0 on failure + * 0 on success + */ +int +rd_update_local(REMDATA_S *rd) +{ + char *error; + STORE_S *store; + gf_io_t pc; + int i, we_cancel = 0; + BODY *body = NULL; + ENVELOPE *env; + char *tempfile = NULL; + + + if(!rd || !(rd->flags & NO_FILE || rd->lf) || !rd->rn){ + dprint((1, + "rd_update_local: Unexpected error: %s is NULL\n", + !rd ? "rd" : + !(rd->flags & NO_FILE || rd->lf) ? "localfile" : + !rd->rn ? "remotename" : "?")); + + return -1; + } + + dprint((3, " - rd_update_local(%s): %s => %s\n", + rd->type == RemImap ? "Imap" : "?", rd->rn ? rd->rn : "?", + (rd->flags & NO_FILE) ? "<mem>" : (rd->lf ? rd->lf : "?"))); + + switch(rd->type){ + case RemImap: + if(!rd->so || !rd->t.i.special_hdr){ + dprint((1, + "rd_update_local: Unexpected error: %s is NULL\n", + !rd->so ? "so" : + !rd->t.i.special_hdr ? "special_hdr" : "?")); + return -1; + } + + rd_open_remote(rd); + if(!rd_stream_exists(rd)){ + if(rd->flags & NO_PERM_CACHE){ + dprint((1, + "rd_update_local: backtrack, remote folder does not exist yet\n")); + } + else{ + dprint((1, + "rd_update_local: Unexpected error: stream is NULL\n")); + } + + return -1; + } + + if(rd->t.i.stream){ + char ebuf[500]; + char *eptr = NULL; + int chk; + + /* force ReadOnly */ + if(rd->t.i.stream->rdonly){ + rd->read_status = 'R'; + rd->access = ReadOnly; + } + else + rd->read_status = 'W'; + + if(rd->t.i.stream->nmsgs < 2) + return(rd_init_remote(rd, 0)); + else if(rd_chk_for_hdr_msg(&(rd->t.i.stream), rd, &eptr)){ + q_status_message1(SM_ORDER | SM_DING, 5, 5, + _("Can't initialize \"%s\" (invalid format)"), rd->rn); + + if(eptr){ + q_status_message1(SM_ORDER, 3, 5, "%s", eptr); + dprint((1, "%s\n", eptr)); + fs_give((void **)&eptr); + } + + dprint((1, + "Can't initialize remote data \"%s\"\n", + rd->rn ? rd->rn : "?")); + return -1; + } + + we_cancel = busy_cue(_("Copying remote data"), NULL, 1); + + if(rd->flags & NO_FILE){ + store = rd->sonofile; + so_truncate(store, 0L); + } + else{ + if(!(tempfile = tempfile_in_same_dir(rd->lf, "a8", NULL))){ + q_status_message1(SM_ORDER | SM_DING, 3, 5, + _("Error opening temporary file: %s"), + error_description(errno)); + dprint((2, + "rd_update_local: Error opening temporary file: %s\n", + error_description(errno))); + return -1; + } + + /* Copy the data into tempfile */ + if((store = so_get(FileStar, tempfile, WRITE_ACCESS|OWNER_ONLY)) + == NULL){ + q_status_message2(SM_ORDER | SM_DING, 3, 5, + _("Error opening temporary file %s: %s"), + tempfile, error_description(errno)); + dprint((2, + "rd_update_local: Error opening temporary file: %s: %s\n", + tempfile ? tempfile : "?", + error_description(errno))); + our_unlink(tempfile); + fs_give((void **)&tempfile); + return -1; + } + } + + /* + * Copy from the last message in the folder. + */ + if(!pine_mail_fetchstructure(rd->t.i.stream, rd->t.i.stream->nmsgs, + &body)){ + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Can't access remote IMAP data")); + dprint((2, "Can't access remote IMAP data\n")); + if(tempfile){ + our_unlink(tempfile); + fs_give((void **)&tempfile); + } + + if(!(rd->flags & NO_FILE)) + so_give(&store); + + if(we_cancel) + cancel_busy_cue(-1); + + return -1; + } + + if(!body || + body->type != REMOTE_DATA_TYPE || + !body->subtype || + strucmp(body->subtype, rd->t.i.special_hdr)){ + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Remote IMAP folder has wrong contents")); + dprint((2, + "Remote IMAP folder has wrong contents\n")); + if(tempfile){ + our_unlink(tempfile); + fs_give((void **)&tempfile); + } + + if(!(rd->flags & NO_FILE)) + so_give(&store); + + if(we_cancel) + cancel_busy_cue(-1); + + return -1; + } + + if(!(env = pine_mail_fetchenvelope(rd->t.i.stream, + rd->t.i.stream->nmsgs))){ + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Can't access check date in remote data")); + dprint((2, + "Can't access check date in remote data\n")); + if(tempfile){ + our_unlink(tempfile); + fs_give((void **)&tempfile); + } + + if(!(rd->flags & NO_FILE)) + so_give(&store); + + if(we_cancel) + cancel_busy_cue(-1); + + return -1; + } + + if(rd && rd->flags & USER_SAID_YES) + chk = 0; + else + chk = rd_check_for_suspect_data(rd); + + switch(chk){ + case -1: /* suspicious data, user says abort */ + if(tempfile){ + our_unlink(tempfile); + fs_give((void **)&tempfile); + } + + if(!(rd->flags & NO_FILE)) + so_give(&store); + + if(we_cancel) + cancel_busy_cue(-1); + + return -1; + + case 1: /* suspicious data, user says go ahead */ + if(rd_remote_is_readonly(rd)){ + dprint((1, + "rd_update_local: can't upgrade, readonly\n")); + } + else + /* attempt to upgrade cookie in last message */ + (void)rd_upgrade_cookies(rd, 0, 1); + + break; + + case 0: /* all is ok */ + default: + break; + } + + + /* store may have been written to at this point, so we'll clear it out */ + so_truncate(store, 0L); + + gf_set_so_writec(&pc, store); + + error = detach(rd->t.i.stream, rd->t.i.stream->nmsgs, "1", 0L, + NULL, pc, NULL, DT_NODFILTER); + + gf_clear_so_writec(store); + + + if(we_cancel) + cancel_busy_cue(-1); + + if(!(rd->flags & NO_FILE)){ + if(so_give(&store)){ + snprintf(ebuf, sizeof(ebuf), _("Error writing temp file: %s"), + error_description(errno)); + error = ebuf; + } + } + + if(error){ + q_status_message1(SM_ORDER | SM_DING, 3, 4, + _("%s: Error copying remote IMAP data"), error); + dprint((2, "rd_update_local: Error copying: %s\n", + error ? error : "?")); + if(tempfile){ + our_unlink(tempfile); + fs_give((void **)&tempfile); + } + + return -1; + } + + if(tempfile && (i = rename_file(tempfile, rd->lf)) < 0){ +#ifdef _WINDOWS + if(i == -5){ + q_status_message2(SM_ORDER | SM_DING, 3, 4, + _("Error updating local file: %s: %s"), + rd->lf, error_description(errno)); + q_status_message(SM_ORDER, 3, 4, + _("Perhaps another process has the file open?")); + dprint((2, + "Rename_file(%s,%s): %s: returned -5, another process has file open?\n", + tempfile ? tempfile : "?", + rd->lf ? rd->lf : "?", + error_description(errno))); + } + else +#endif /* _WINDOWS */ + { + q_status_message2(SM_ORDER | SM_DING, 3, 5, + _("Error updating cache file %s: %s"), + rd->lf, error_description(errno)); + dprint((2, + "Error updating cache file %s: rename(%s,%s): %s\n", + tempfile ? tempfile : "?", + tempfile ? tempfile : "?", + rd->lf ? rd->lf : "?", + error_description(errno))); + } + + our_unlink(tempfile); + fs_give((void **)&tempfile); + return -1; + } + + dprint((5, + "in rd_update_local, setting chk_date to ->%s<-\n", + env->date ? (char *)env->date : "?")); + rd_update_metadata(rd, (char *) env->date); + + /* turn off out of date flag */ + rd->flags &= ~REM_OUTOFDATE; + + if(tempfile) + fs_give((void **)&tempfile); + + return 0; + } + else{ + q_status_message1(SM_ORDER | SM_DING, 5, 5, + _("Can't open remote IMAP folder \"%s\""), rd->rn); + dprint((1, + "Can't open remote IMAP folder \"%s\"\n", + rd->rn ? rd->rn : "?")); + rd->access = ReadOnly; + return -1; + } + + break; + + default: + dprint((1, "rd_update_local: Unsupported type\n")); + return -1; + } +} + + +/* + * Copy local data to remote folder. + * + * Args lf -- Local file name + * rn -- Remote name + * header_to_check -- Name of indicator header in remote folder + * imapstuff -- Structure holding info about connection + * returndate -- Pointer to the date that was stored in the remote folder + * + * Returns !=0 on failure + * 0 on success + */ +int +rd_update_remote(REMDATA_S *rd, char *returndate) +{ + STORE_S *store; + int err = 0; + long openmode = SP_USEPOOL | SP_TEMPUSE; + MAILSTREAM *st; + char *eptr = NULL; + + if(rd && rd->type != RemImap){ + dprint((1, "rd_update_remote: type not supported\n")); + return -1; + } + + if(!rd || !(rd->flags & NO_FILE || rd->lf) || !rd->rn || + !rd->so || !rd->t.i.special_hdr){ + dprint((1, + "rd_update_remote: Unexpected error: %s is NULL\n", + !rd ? "rd" : + !(rd->flags & NO_FILE || rd->lf) ? "localfile" : + !rd->rn ? "remotename" : + !rd->so ? "so" : + !rd->t.i.special_hdr ? "special_hdr" : "?")); + return -1; + } + + dprint((7, " - rd_update_remote(%s): %s => %s\n", + rd->t.i.special_hdr ? rd->t.i.special_hdr : "?", + rd->lf ? rd->lf : "<mem>", + rd->rn ? rd->rn : "?")); + + if(!(st = rd->t.i.stream)) + st = adrbk_handy_stream(rd->rn); + + if(!st) + st = rd->t.i.stream = context_open(NULL, NULL, rd->rn, openmode, NULL); + + if(!st){ + q_status_message1(SM_ORDER | SM_DING, 5, 5, + _("Can't open \"%s\" for copying"), rd->rn); + dprint((1, + "rd_update_remote: Can't open remote folder \"%s\" for copying\n", + rd->rn ? rd->rn : "?")); + return 1; + } + + rd->last_use = get_adj_time(); + err = rd_chk_for_hdr_msg(&st, rd, &eptr); + if(err){ + q_status_message1(SM_ORDER | SM_DING, 5, 5, + _("\"%s\" has invalid format"), rd->rn); + + if(eptr){ + q_status_message1(SM_ORDER, 3, 5, "%s", eptr); + dprint((1, "%s\n", eptr)); + fs_give((void **)&eptr); + } + + dprint((1, + "rd_update_remote: \"%s\" has invalid format\n", + rd->rn ? rd->rn : "?")); + return 1; + } + + errno = 0; + + /* + * The data that will be going to the remote folder rn is + * written into the following storage object and then copied to + * the remote folder from there. + */ + + if(rd->flags & NO_FILE){ + store = rd->sonofile; + if(store) + so_seek(store, 0L, 0); /* rewind */ + } + else + store = so_get(FileStar, rd->lf, READ_ACCESS); + + if(store != NULL){ + char date[200]; + unsigned char c; + unsigned char last_c = 0; + + /* Reset the storage object, since we may have already used it. */ + if(so_truncate(rd->so, 0L) == 0) + err = 1; + + rfc822_date(date); + dprint((7, + "in rd_update_remote, storing date ->%s<-\n", + date ? date : "?")); + if(!err && rd_store_fake_hdrs(rd, "Pine Remote Data Container", + rd->t.i.special_hdr, date)) + err = 1; + + /* save the date for later comparisons */ + if(!err && returndate) + strncpy(returndate, date, 100); + + /* Write the data */ + while(!err && so_readc(&c, store)){ + /* + * C-client expects CRLF-terminated lines. Convert them + * as we copy into c-client. Read ahead isn't available. + * Leave CRLF as is, convert LF to CRLF, leave CR as is. + */ + if(last_c != '\r' && c == '\n'){ + /* Convert \n to CRFL */ + if(so_writec('\r', rd->so) == 0 || so_writec('\n', rd->so) == 0) + err = 1; + + last_c = 0; + } + else{ + last_c = c; + if(so_writec((int) c, rd->so) == 0) + err = 1; + } + } + + /* + * Take that message from so to the remote folder. + * We append to that folder and always + * use the final message as the active data. + */ + if(!err){ + MAILSTREAM *st; + + if((st = rd->t.i.stream) != NULL) + rd->t.i.shouldbe_nmsgs = rd->t.i.stream->nmsgs + 1; + else + st = adrbk_handy_stream(rd->rn); + + err = write_fcc(rd->rn, NULL, rd->so, st, + "remote data", NULL) ? 0 : 1; + } + + + if(!(rd->flags & NO_FILE)) + so_give(&store); + } + else + err = -1; + + if(err) + dprint((2, "error in rd_update_remote for %s => %s\n", + rd->lf ? rd->lf : "<mem>", rd->rn ? rd->rn : "?")); + + return(err); +} + + +/* + * Check to see if the remote data has changed since we cached it. + * + * Args rd -- REMDATA handle + * do_it_now -- If > 0, check now regardless + * If = 0, check if time since last chk more than default + * If < 0, check if time since last chk more than -do_it_now + */ +void +rd_check_remvalid(REMDATA_S *rd, long int do_it_now) +{ + time_t chk_interval; + long openmode = SP_USEPOOL | SP_TEMPUSE; + MAILSTREAM *stat_stream = NULL; + int we_cancel = 0, got_cmsgs = 0; + unsigned long current_nmsgs; + + dprint((7, "- rd_check_remvalid(%s) -\n", + (rd && rd->rn) ? rd->rn : "")); + + if(rd && rd->type != RemImap){ + dprint((1, "rd_check_remvalid: type not supported\n")); + return; + } + + if(!rd || rd->flags & REM_OUTOFDATE || rd->flags & USE_OLD_CACHE) + return; + + if(!rd->t.i.chk_date){ + dprint((2, + "rd_check_remvalid: remote data %s changed (chk_date)\n", + rd->rn)); + rd->flags |= REM_OUTOFDATE; + return; + } + + if(rd->t.i.chk_nmsgs <= 1){ + dprint((2, + "rd_check_remvalid: remote data %s changed (chk_nmsgs <= 1)\n", + rd->rn ? rd->rn : "?")); + rd->flags |= REM_OUTOFDATE; + return; + } + + if(do_it_now < 0L){ + chk_interval = -1L * do_it_now; + do_it_now = 0L; + } + else + chk_interval = ps_global->remote_abook_validity * 60L; + + /* too soon to check again */ + if(!do_it_now && + (chk_interval == 0L || + get_adj_time() <= rd->last_valid_chk + chk_interval)) + return; + + if(rd->access == ReadOnly) + openmode |= OP_READONLY; + + rd->last_valid_chk = get_adj_time(); + mm_status_result.flags = 0L; + + /* make sure the cache file is still there */ + if(rd->lf && can_access(rd->lf, READ_ACCESS) != 0){ + dprint((2, + "rd_check_remvalid: %s: cache file %s disappeared\n", + rd->rn ? rd->rn : "?", + rd->lf ? rd->lf : "?")); + rd->flags |= REM_OUTOFDATE; + return; + } + + /* + * If the stream is open we should check there instead of using + * a STATUS command. Check to see if it is really still alive with the + * ping. It would be convenient if we could use a status command + * on the open stream but apparently that won't work everywhere. + */ + rd_ping_stream(rd); + +try_looking_in_stream: + + /* + * Get the current number of messages in the folder to + * compare with our saved number of messages. + */ + current_nmsgs = 0; + if(rd->t.i.stream){ + dprint((7, "using open remote data stream\n")); + rd->last_use = get_adj_time(); + current_nmsgs = rd->t.i.stream->nmsgs; + got_cmsgs++; + } + else{ + + /* + * Try to use the imap status command + * to get the information as cheaply as possible. + * If NO_STATUSCMD is set we just bypass all this stuff. + */ + + if(!(rd->flags & NO_STATUSCMD)) + stat_stream = adrbk_handy_stream(rd->rn); + + /* + * If we don't have a stream to use for the status command we + * skip it and open the folder instead. Then, next time we want to + * check we'll probably be able to use that open stream instead of + * opening another one each time for the status command. + */ + if(stat_stream){ + if(!LEVELSTATUS(stat_stream)){ + rd->flags |= NO_STATUSCMD; + dprint((2, + "rd_check_remvalid: remote data %s: server doesn't support status\n", + rd->rn ? rd->rn : "?")); + } + else{ + /* + * This sure seems like a crock. We have to check to + * see if the stream is actually open to the folder + * we want to do the status on because c-client can't + * do a status on an open folder. In this case, we fake + * the status command results ourselves. + * If we're so unlucky as to get back a stream that will + * work for the status command while we also have another + * stream that is rd->rn and we don't pick up on that, + * too bad. + */ + if(same_stream_and_mailbox(rd->rn, stat_stream)){ + dprint((7, + "rd_check_remvalid: faking status\n")); + mm_status_result.flags = SA_MESSAGES | SA_UIDVALIDITY + | SA_UIDNEXT; + mm_status_result.messages = stat_stream->nmsgs; + mm_status_result.uidvalidity = stat_stream->uid_validity; + mm_status_result.uidnext = stat_stream->uid_last+1; + } + else{ + + dprint((7, + "rd_check_remvalid: trying status\n")); + ps_global->noshow_error = 1; + if(!pine_mail_status(stat_stream, rd->rn, + SA_UIDVALIDITY | SA_UIDNEXT | SA_MESSAGES)){ + /* failed, mark it so we won't try again */ + rd->flags |= NO_STATUSCMD; + dprint((2, + "rd_check_remvalid: addrbook %s: status command failed\n", + rd->rn ? rd->rn : "?")); + mm_status_result.flags = 0L; + } + } + + ps_global->noshow_error = 0; + } + } + + /* if we got back a result from the status command, use it */ + if(mm_status_result.flags){ + dprint((7, + "rd_check_remvalid: got status_result 0x%x\n", + mm_status_result.flags)); + if(mm_status_result.flags & SA_MESSAGES){ + current_nmsgs = mm_status_result.messages; + got_cmsgs++; + } + } + } + + /* + * Check current_nmsgs versus what we think it should be. + * If they're different we know things have changed and we can + * return now. If they're the same we don't know. + */ + if(got_cmsgs && current_nmsgs != rd->t.i.chk_nmsgs){ + rd->flags |= REM_OUTOFDATE; + dprint((2, + "rd_check_remvalid: remote data %s changed (current msgs (%ld) != chk_nmsgs (%ld))\n", + rd->rn ? rd->rn : "?", current_nmsgs, rd->t.i.chk_nmsgs)); + return; + } + + /* + * Get the current uidvalidity and uidnext values from the + * folder to compare with our saved values. + */ + if(rd->t.i.stream){ + if(rd->t.i.stream->uid_validity == rd->t.i.uidvalidity){ + /* + * Uid is valid so we just have to check whether or not the + * uid of the last message has changed or not and return. + */ + if(mail_uid(rd->t.i.stream, rd->t.i.stream->nmsgs) != rd->t.i.uid){ + /* uid has changed so we're out of date */ + rd->flags |= REM_OUTOFDATE; + dprint((2, + "rd_check_remvalid: remote data %s changed (uid)\n", + rd->rn ? rd->rn : "?")); + } + else{ + dprint((7,"rd_check_remvalid: uids match\n")); + } + + return; + } + else{ + /* + * If the uidvalidity changed that probably means it can't + * be relied on to be meaningful, so don't use it in the future. + */ + rd->flags |= NO_STATUSCMD; + dprint((7, + "rd_check_remvalid: remote data %s uidvalidity changed, don't use uid\n", + rd->rn ? rd->rn : "?")); + } + } + else{ /* stream not open, use status results */ + if(mm_status_result.flags & SA_UIDVALIDITY && + mm_status_result.flags & SA_UIDNEXT && + mm_status_result.uidvalidity == rd->t.i.uidvalidity){ + + /* + * Uids are valid. + */ + + if(mm_status_result.uidnext == rd->t.i.uidnext){ + /* + * Uidnext valid and unchanged, so the folder is unchanged. + */ + dprint((7, "rd_check_remvalid: uidnexts match\n")); + return; + } + else{ /* uidnext changed, folder _may_ have changed */ + + dprint((7, + "rd_check_remvalid: uidnexts don't match, ours=%lu status=%lu\n", + rd->t.i.uidnext, mm_status_result.uidnext)); + + /* + * Since c-client can't handle a status cmd on the selected + * mailbox, we may have had to guess at the value of uidnext, + * and we don't know for sure that this is a real change. + * We need to open the mailbox and find out for sure. + */ + we_cancel = busy_cue(NULL, NULL, 1); + ps_global->noshow_error = 1; + if((rd->t.i.stream = context_open(NULL, NULL, rd->rn, openmode, + NULL)) != NULL){ + imapuid_t last_msg_uid; + + if(rd->t.i.stream->rdonly) + rd->read_status = 'R'; + + last_msg_uid = mail_uid(rd->t.i.stream, + rd->t.i.stream->nmsgs); + ps_global->noshow_error = 0; + rd->last_use = get_adj_time(); + dprint((7, + "%s: opened to check uid (%ld)\n", + rd->rn ? rd->rn : "?", (long)rd->last_use)); + if(last_msg_uid != rd->t.i.uid){ /* really did change */ + rd->flags |= REM_OUTOFDATE; + dprint((2, + "rd_check_remvalid: remote data %s changed, our uid=%lu real uid=%lu\n", + rd->rn ? rd->rn : "?", + rd->t.i.uid, last_msg_uid)); + } + else{ /* false hit */ + /* + * The uid of the last message is the same as we + * remembered, so the problem is that our guess + * for the nextuid was wrong. It didn't actually + * change. Since we know the correct uidnext now we + * can reset that guess to the correct value for + * next time, avoiding this extra mail_open. + */ + dprint((2, + "rd_check_remvalid: remote data %s false change: adjusting uidnext from %lu to %lu\n", + rd->rn ? rd->rn : "?", + rd->t.i.uidnext, + mm_status_result.uidnext)); + rd->t.i.uidnext = mm_status_result.uidnext; + rd_write_metadata(rd, 0); + } + + if(we_cancel) + cancel_busy_cue(-1); + + return; + } + else{ + ps_global->noshow_error = 0; + dprint((2, + "rd_check_remvalid: couldn't open %s\n", + rd->rn ? rd->rn : "?")); + } + } + } + else{ + /* + * If the status command doesn't return these or + * if the uidvalidity is changing that probably means it can't + * be relied on to be meaningful, so don't use it. + * + * We also come here if we don't have a stat_stream handy to + * look up the status. This happens, for example, if our + * address book is on a different server from the open mail + * folders. In that case, instead of opening a stream, + * doing a status command, and closing the stream, we open + * the stream and use it to check next time, too. + */ + if(stat_stream){ + rd->flags |= NO_STATUSCMD; + dprint((7, + "rd_check_remvalid: remote data %s don't use status\n", + rd->rn ? rd->rn : "?")); + } + + dprint((7, + "opening remote data stream for validity check\n")); + we_cancel = busy_cue(NULL, NULL, 1); + ps_global->noshow_error = 1; + rd->t.i.stream = context_open(NULL, NULL, rd->rn, openmode, + NULL); + ps_global->noshow_error = 0; + if(we_cancel) + cancel_busy_cue(-1); + + we_cancel = 0; + if(rd->t.i.stream) + goto try_looking_in_stream; + else{ + dprint((7, + "rd_check_remvalid: cannot open remote mailbox\n")); + return; + } + } + } + + /* + * If we got here that means that we still don't know if the folder + * changed or not. If the stream is open then it must be the case that + * uidvalidity changed so we can't rely on using uids. If the stream + * isn't open, then either the status command didn't work or the + * uidvalidity changed. In any case, we need to fall back to looking + * inside the folder at the last message and checking whether or not the + * Date of the last message is the one we remembered. + */ + + dprint((7, + "rd_check_remvalid: falling back to Date check\n")); + + /* + * Fall back to looking in the folder at the Date header. + */ + + if(!rd->t.i.stream) + we_cancel = busy_cue(NULL, NULL, 1); + + ps_global->noshow_error = 1; + if(rd->t.i.stream || + (rd->t.i.stream = context_open(NULL,NULL,rd->rn,openmode,NULL))){ + ENVELOPE *env = NULL; + + if(rd->t.i.stream->rdonly) + rd->read_status = 'R'; + + if(rd->t.i.stream->nmsgs != rd->t.i.chk_nmsgs){ + rd->flags |= REM_OUTOFDATE; + dprint((2, + "rd_check_remvalid: remote data %s changed (expected nmsgs %ld, got %ld)\n", + rd->rn ? rd->rn : "?", + rd->t.i.chk_nmsgs, rd->t.i.stream->nmsgs)); + } + else if(rd->t.i.stream->nmsgs > 1){ + env = pine_mail_fetchenvelope(rd->t.i.stream,rd->t.i.stream->nmsgs); + + if(!env || (env->date && strucmp((char *) env->date, rd->t.i.chk_date))){ + rd->flags |= REM_OUTOFDATE; + dprint((2, + "rd_check_remvalid: remote data %s changed (%s)\n", + rd->rn ? rd->rn : "?", env ? "date" : "not enough msgs")); + } + } + + rd->last_use = get_adj_time(); + if(env) + dprint((7, + "%s: got envelope to check date (%ld)\n", + rd->rn ? rd->rn : "?", (long)rd->last_use)); + } + /* else, we give up and go with the cache copy */ + + ps_global->noshow_error = 0; + + if(we_cancel) + cancel_busy_cue(-1); + + dprint((7, "rd_check_remvalid: falling off end\n")); +} + + +/* + * Returns nonzero if remote data is currently readonly. + * + * Returns 0 -- apparently writeable + * 1 -- stream not open + * 2 -- stream open but not writeable + */ +int +rd_remote_is_readonly(REMDATA_S *rd) +{ + if(!rd || rd->access == ReadOnly || !rd_stream_exists(rd)) + return(1); + + switch(rd->type){ + case RemImap: + if(rd->t.i.stream->rdonly) + return(2); + + break; + + default: + q_status_message(SM_ORDER, 3, 5, + "rd_remote_is_readonly: type not supported"); + break; + } + + return(0); +} + + +/* + * Returns 0 if ok + * -1 if not ok and user says No + * 1 if not ok but user says Yes, ok to use it + */ +int +rd_check_for_suspect_data(REMDATA_S *rd) +{ + int ans = -1; + char *fields[3], *values[3], *h; + unsigned long cookie; + + if(!rd || rd->type != RemImap || !rd->so || !rd->rn || !rd->t.i.special_hdr) + return -1; + + fields[0] = rd->t.i.special_hdr; + fields[1] = "received"; + fields[2] = NULL; + cookie = 0L; + if((h=pine_fetchheader_lines(rd->t.i.stream, rd->t.i.stream->nmsgs, + NULL, fields)) != NULL){ + simple_header_parse(h, fields, values); + if(values[1]) /* Received lines present! */ + ans = rd_prompt_about_forged_remote_data(-1, rd, NULL); + else if(values[0]){ + cookie = strtoul(values[0], (char **)NULL, 10); + if(cookie == rd->cookie) /* all's well */ + ans = 0; + else + ans = rd_prompt_about_forged_remote_data(cookie > 1L + ? 100 : cookie, + rd, values[0]); + } + else + ans = rd_prompt_about_forged_remote_data(-2, rd, NULL); + + if(values[0]) + fs_give((void **)&values[0]); + + if(values[1]) + fs_give((void **)&values[1]); + + fs_give((void **)&h); + } + else /* missing magic header */ + ans = rd_prompt_about_forged_remote_data(-2, rd, NULL); + + return ans; +} |