#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: remote.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
#endif

/*
 * ========================================================================
 * Copyright 2013-2020 Eduardo Chappa
 * Copyright 2006-2008 University of Washington
 *
 * 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;
}
