imapext-2007

diff src/osdep/unix/mix.c @ 0:ada5e610ab86

imap-2007e
author yuuji@gentei.org
date Mon, 14 Sep 2009 15:17:45 +0900
parents
children
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/osdep/unix/mix.c	Mon Sep 14 15:17:45 2009 +0900
     1.3 @@ -0,0 +1,2834 @@
     1.4 +/* ========================================================================
     1.5 + * Copyright 1988-2008 University of Washington
     1.6 + *
     1.7 + * Licensed under the Apache License, Version 2.0 (the "License");
     1.8 + * you may not use this file except in compliance with the License.
     1.9 + * You may obtain a copy of the License at
    1.10 + *
    1.11 + *     http://www.apache.org/licenses/LICENSE-2.0
    1.12 + *
    1.13 + * 
    1.14 + * ========================================================================
    1.15 + */
    1.16 +
    1.17 +/*
    1.18 + * Program:	MIX mail routines
    1.19 + *
    1.20 + * Author(s):	Mark Crispin
    1.21 + *		UW Technology
    1.22 + *		University of Washington
    1.23 + *		Seattle, WA  98195
    1.24 + *		Internet: MRC@Washington.EDU
    1.25 + *
    1.26 + * Date:	1 March 2006
    1.27 + * Last Edited:	7 May 2008
    1.28 + */
    1.29 +
    1.30 +
    1.31 +#include <stdio.h>
    1.32 +#include <ctype.h>
    1.33 +#include <errno.h>
    1.34 +extern int errno;		/* just in case */
    1.35 +#include "mail.h"
    1.36 +#include "osdep.h"
    1.37 +#include <pwd.h>
    1.38 +#include <sys/stat.h>
    1.39 +#include <sys/time.h>
    1.40 +#include "misc.h"
    1.41 +#include "dummy.h"
    1.42 +#include "fdstring.h"
    1.43 +
    1.44 +/* MIX definitions */
    1.45 +
    1.46 +#define MEGABYTE (1024*1024)
    1.47 +
    1.48 +#define MIXDATAROLL MEGABYTE	/* size at which we roll to a new file */
    1.49 +
    1.50 +
    1.51 +/* MIX files */
    1.52 +
    1.53 +#define MIXNAME ".mix"		/* prefix for all MIX file names */
    1.54 +#define MIXMETA "meta"		/* suffix for metadata */
    1.55 +#define MIXINDEX "index"	/* suffix for index */
    1.56 +#define MIXSTATUS "status"	/* suffix for status */
    1.57 +#define MIXSORTCACHE "sortcache"/* suffix for sortcache */
    1.58 +#define METAMAX (MEGABYTE-1)	/* maximum metadata file size (sanity check) */
    1.59 +
    1.60 +
    1.61 +/* MIX file formats */
    1.62 +
    1.63 +				/* sequence format (all but msg files) */
    1.64 +#define SEQFMT "S%08lx\015\012"
    1.65 +				/* metadata file format */
    1.66 +#define MTAFMT "V%08lx\015\012L%08lx\015\012N%08lx\015\012"
    1.67 +				/* index file record format */
    1.68 +#define IXRFMT ":%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:%08lx:%08lx:%08lx:%08lx:\015\012"
    1.69 +				/* status file record format */
    1.70 +#define STRFMT ":%08lx:%08lx:%04x:%08lx:\015\012"
    1.71 +				/* message file header format */
    1.72 +#define MSRFMT "%s%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:\015\012"
    1.73 +#define MSGTOK ":msg:"
    1.74 +#define MSGTSZ (sizeof(MSGTOK)-1)
    1.75 +				/* sortcache file record format */
    1.76 +#define SCRFMT ":%08lx:%08lx:%08lx:%08lx:%08lx:%c%08lx:%08lx:%08lx:\015\012"
    1.77 +
    1.78 +/* MIX I/O stream local data */
    1.79 +	
    1.80 +typedef struct mix_local {
    1.81 +  unsigned long curmsg;		/* current message file number */
    1.82 +  unsigned long newmsg;		/* current new message file number */
    1.83 +  time_t lastsnarf;		/* last snarf time */
    1.84 +  int msgfd;			/* file description of current msg file */
    1.85 +  int mfd;			/* file descriptor of open metadata */
    1.86 +  unsigned long metaseq;	/* metadata sequence */
    1.87 +  char *index;			/* mailbox index name */
    1.88 +  unsigned long indexseq;	/* index sequence */
    1.89 +  char *status;			/* mailbox status name */
    1.90 +  unsigned long statusseq;	/* status sequence */
    1.91 +  char *sortcache;		/* mailbox sortcache name */
    1.92 +  unsigned long sortcacheseq;	/* sortcache sequence */
    1.93 +  unsigned char *buf;		/* temporary buffer */
    1.94 +  unsigned long buflen;		/* current size of temporary buffer */
    1.95 +  unsigned int expok : 1;	/* non-zero if expunge reports OK */
    1.96 +  unsigned int internal : 1;	/* internally opened, do not validate */
    1.97 +} MIXLOCAL;
    1.98 +
    1.99 +
   1.100 +#define MIXBURP struct mix_burp
   1.101 +
   1.102 +MIXBURP {
   1.103 +  unsigned long fileno;		/* message file number */
   1.104 +  char *name;			/* message file name */
   1.105 +  SEARCHSET *tail;		/* tail of ranges */
   1.106 +  SEARCHSET set;		/* set of retained ranges */
   1.107 +  MIXBURP *next;		/* next file to burp */
   1.108 +};
   1.109 +
   1.110 +
   1.111 +/* Convenient access to local data */
   1.112 +
   1.113 +#define LOCAL ((MIXLOCAL *) stream->local)
   1.114 +
   1.115 +/* Function prototypes */
   1.116 +
   1.117 +DRIVER *mix_valid (char *name);
   1.118 +long mix_isvalid (char *name,char *meta);
   1.119 +void *mix_parameters (long function,void *value);
   1.120 +long mix_dirfmttest (char *name);
   1.121 +void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
   1.122 +long mix_scan_contents (char *name,char *contents,unsigned long csiz,
   1.123 +			unsigned long fsiz);
   1.124 +void mix_list (MAILSTREAM *stream,char *ref,char *pat);
   1.125 +void mix_lsub (MAILSTREAM *stream,char *ref,char *pat);
   1.126 +long mix_subscribe (MAILSTREAM *stream,char *mailbox);
   1.127 +long mix_unsubscribe (MAILSTREAM *stream,char *mailbox);
   1.128 +long mix_create (MAILSTREAM *stream,char *mailbox);
   1.129 +long mix_delete (MAILSTREAM *stream,char *mailbox);
   1.130 +long mix_rename (MAILSTREAM *stream,char *old,char *newname);
   1.131 +int mix_rselect (struct direct *name);
   1.132 +MAILSTREAM *mix_open (MAILSTREAM *stream);
   1.133 +void mix_close (MAILSTREAM *stream,long options);
   1.134 +void mix_abort (MAILSTREAM *stream);
   1.135 +char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
   1.136 +		  long flags);
   1.137 +long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
   1.138 +void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
   1.139 +unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
   1.140 +			 SORTPGM *pgm,long flags);
   1.141 +THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset,
   1.142 +			SEARCHPGM *spg,long flags);
   1.143 +long mix_ping (MAILSTREAM *stream);
   1.144 +void mix_check (MAILSTREAM *stream);
   1.145 +long mix_expunge (MAILSTREAM *stream,char *sequence,long options);
   1.146 +int mix_select (struct direct *name);
   1.147 +int mix_msgfsort (const void *d1,const void *d2);
   1.148 +long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size);
   1.149 +long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed);
   1.150 +long mix_burp_check (SEARCHSET *set,size_t size,char *file);
   1.151 +long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
   1.152 +	       long options);
   1.153 +long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
   1.154 +long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt,
   1.155 +		     STRING *msg,SEARCHSET *set,unsigned long seq);
   1.156 +
   1.157 +FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags);
   1.158 +char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq);
   1.159 +long mix_meta_update (MAILSTREAM *stream);
   1.160 +long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag);
   1.161 +long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag);
   1.162 +FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size,
   1.163 +		     unsigned long newsize);
   1.164 +FILE *mix_sortcache_open (MAILSTREAM *stream);
   1.165 +long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache);
   1.166 +char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type);
   1.167 +unsigned long mix_read_sequence (FILE *f);
   1.168 +char *mix_dir (char *dst,char *name);
   1.169 +char *mix_file (char *dst,char *dir,char *name);
   1.170 +char *mix_file_data (char *dst,char *dir,unsigned long data);
   1.171 +unsigned long mix_modseq (unsigned long oldseq);
   1.172 +
   1.173 +/* MIX mail routines */
   1.174 +
   1.175 +
   1.176 +/* Driver dispatch used by MAIL */
   1.177 +
   1.178 +DRIVER mixdriver = {
   1.179 +  "mix",			/* driver name */
   1.180 +				/* driver flags */
   1.181 +  DR_MAIL|DR_LOCAL|DR_NOFAST|DR_CRLF|DR_LOCKING|DR_DIRFMT|DR_MODSEQ,
   1.182 +  (DRIVER *) NIL,		/* next driver */
   1.183 +  mix_valid,			/* mailbox is valid for us */
   1.184 +  mix_parameters,		/* manipulate parameters */
   1.185 +  mix_scan,			/* scan mailboxes */
   1.186 +  mix_list,			/* find mailboxes */
   1.187 +  mix_lsub,			/* find subscribed mailboxes */
   1.188 +  mix_subscribe,		/* subscribe to mailbox */
   1.189 +  mix_unsubscribe,		/* unsubscribe from mailbox */
   1.190 +  mix_create,			/* create mailbox */
   1.191 +  mix_delete,			/* delete mailbox */
   1.192 +  mix_rename,			/* rename mailbox */
   1.193 +  mail_status_default,		/* status of mailbox */
   1.194 +  mix_open,			/* open mailbox */
   1.195 +  mix_close,			/* close mailbox */
   1.196 +  NIL,				/* fetch message "fast" attributes */
   1.197 +  NIL,				/* fetch message flags */
   1.198 +  NIL,				/* fetch overview */
   1.199 +  NIL,				/* fetch message envelopes */
   1.200 +  mix_header,			/* fetch message header only */
   1.201 +  mix_text,			/* fetch message body only */
   1.202 +  NIL,				/* fetch partial message test */
   1.203 +  NIL,				/* unique identifier */
   1.204 +  NIL,				/* message number */
   1.205 +  mix_flag,			/* modify flags */
   1.206 +  NIL,				/* per-message modify flags */
   1.207 +  NIL,				/* search for message based on criteria */
   1.208 +  mix_sort,			/* sort messages */
   1.209 +  mix_thread,			/* thread messages */
   1.210 +  mix_ping,			/* ping mailbox to see if still alive */
   1.211 +  mix_check,			/* check for new messages */
   1.212 +  mix_expunge,			/* expunge deleted messages */
   1.213 +  mix_copy,			/* copy messages to another mailbox */
   1.214 +  mix_append,			/* append string message to mailbox */
   1.215 +  NIL				/* garbage collect stream */
   1.216 +};
   1.217 +
   1.218 +				/* prototype stream */
   1.219 +MAILSTREAM mixproto = {&mixdriver};
   1.220 +
   1.221 +/* MIX mail validate mailbox
   1.222 + * Accepts: mailbox name
   1.223 + * Returns: our driver if name is valid, NIL otherwise
   1.224 + */
   1.225 +
   1.226 +DRIVER *mix_valid (char *name)
   1.227 +{
   1.228 +  char tmp[MAILTMPLEN];
   1.229 +  return mix_isvalid (name,tmp) ? &mixdriver : NIL;
   1.230 +}
   1.231 +
   1.232 +
   1.233 +/* MIX mail test for valid mailbox
   1.234 + * Accepts: mailbox name
   1.235 + *	    buffer to return meta name
   1.236 + * Returns: T if valid, NIL otherwise, metadata name written in both cases
   1.237 + */
   1.238 +
   1.239 +long mix_isvalid (char *name,char *meta)
   1.240 +{
   1.241 +  char dir[MAILTMPLEN];
   1.242 +  struct stat sbuf;
   1.243 +				/* validate name as directory */
   1.244 +  if (!(errno = ((strlen (name) > NETMAXMBX) ? ENAMETOOLONG : NIL)) &&
   1.245 +      *mix_dir (dir,name) && mix_file (meta,dir,MIXMETA) &&
   1.246 +      !stat (dir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
   1.247 +				/* name is directory; is it mix? */
   1.248 +    if (!stat (meta,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG))
   1.249 +      return LONGT;
   1.250 +    else errno = NIL;		/* directory but not mix */
   1.251 +  }
   1.252 +  return NIL;
   1.253 +}
   1.254 +
   1.255 +/* MIX manipulate driver parameters
   1.256 + * Accepts: function code
   1.257 + *	    function-dependent value
   1.258 + * Returns: function-dependent return value
   1.259 + */
   1.260 +
   1.261 +void *mix_parameters (long function,void *value)
   1.262 +{
   1.263 +  void *ret = NIL;
   1.264 +  switch ((int) function) {
   1.265 +  case GET_INBOXPATH:
   1.266 +    if (value) ret = mailboxfile ((char *) value,"~/INBOX");
   1.267 +    break;
   1.268 +  case GET_DIRFMTTEST:
   1.269 +    ret = (void *) mix_dirfmttest;
   1.270 +    break;
   1.271 +  case GET_SCANCONTENTS:
   1.272 +    ret = (void *) mix_scan_contents;
   1.273 +    break;
   1.274 +  case SET_ONETIMEEXPUNGEATPING:
   1.275 +    if (value) ((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
   1.276 +  case GET_ONETIMEEXPUNGEATPING:
   1.277 +    if (value) ret = (void *)
   1.278 +      (((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
   1.279 +    break;
   1.280 +  }
   1.281 +  return ret;
   1.282 +}
   1.283 +
   1.284 +
   1.285 +/* MIX test for directory format internal node
   1.286 + * Accepts: candidate node name
   1.287 + * Returns: T if internal name, NIL otherwise
   1.288 + */
   1.289 +
   1.290 +long mix_dirfmttest (char *name)
   1.291 +{
   1.292 +				/* belongs to MIX if starts with .mix */
   1.293 +  return strncmp (name,MIXNAME,sizeof (MIXNAME) - 1) ? NIL : LONGT;
   1.294 +}
   1.295 +
   1.296 +/* MIX mail scan mailboxes
   1.297 + * Accepts: mail stream
   1.298 + *	    reference
   1.299 + *	    pattern to search
   1.300 + *	    string to scan
   1.301 + */
   1.302 +
   1.303 +void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
   1.304 +{
   1.305 +  if (stream) dummy_scan (NIL,ref,pat,contents);
   1.306 +}
   1.307 +
   1.308 +
   1.309 +/* MIX scan mailbox for contents
   1.310 + * Accepts: mailbox name
   1.311 + *	    desired contents
   1.312 + *	    contents size
   1.313 + *	    file size (ignored)
   1.314 + * Returns: NIL if contents not found, T if found
   1.315 + */
   1.316 +
   1.317 +long mix_scan_contents (char *name,char *contents,unsigned long csiz,
   1.318 +			unsigned long fsiz)
   1.319 +{
   1.320 +  long i,nfiles;
   1.321 +  void *a;
   1.322 +  char *s;
   1.323 +  long ret = NIL;
   1.324 +  size_t namelen = strlen (name);
   1.325 +  struct stat sbuf;
   1.326 +  struct direct **names = NIL;
   1.327 +  if ((nfiles = scandir (name,&names,mix_select,mix_msgfsort)) > 0)
   1.328 +    for (i = 0; i < nfiles; ++i) {
   1.329 +      if (!ret) {
   1.330 +	sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2),
   1.331 +		 "%s/%s",name,names[i]->d_name);
   1.332 +	if (!stat (s,&sbuf) && (csiz <= sbuf.st_size))
   1.333 +	  ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size);
   1.334 +	fs_give ((void **) &s);
   1.335 +      }
   1.336 +      fs_give ((void **) &names[i]);
   1.337 +    }
   1.338 +				/* free directory list */
   1.339 +  if (a = (void *) names) fs_give ((void **) &a);
   1.340 +  return ret;
   1.341 +}
   1.342 +
   1.343 +/* MIX list mailboxes
   1.344 + * Accepts: mail stream
   1.345 + *	    reference
   1.346 + *	    pattern to search
   1.347 + */
   1.348 +
   1.349 +void mix_list (MAILSTREAM *stream,char *ref,char *pat)
   1.350 +{
   1.351 +  if (stream) dummy_list (NIL,ref,pat);
   1.352 +}
   1.353 +
   1.354 +
   1.355 +/* MIX list subscribed mailboxes
   1.356 + * Accepts: mail stream
   1.357 + *	    reference
   1.358 + *	    pattern to search
   1.359 + */
   1.360 +
   1.361 +void mix_lsub (MAILSTREAM *stream,char *ref,char *pat)
   1.362 +{
   1.363 +  if (stream) dummy_lsub (NIL,ref,pat);
   1.364 +}
   1.365 +
   1.366 +/* MIX mail subscribe to mailbox
   1.367 + * Accepts: mail stream
   1.368 + *	    mailbox to add to subscription list
   1.369 + * Returns: T on success, NIL on failure
   1.370 + */
   1.371 +
   1.372 +long mix_subscribe (MAILSTREAM *stream,char *mailbox)
   1.373 +{
   1.374 +  return sm_subscribe (mailbox);
   1.375 +}
   1.376 +
   1.377 +
   1.378 +/* MIX mail unsubscribe to mailbox
   1.379 + * Accepts: mail stream
   1.380 + *	    mailbox to delete from subscription list
   1.381 + * Returns: T on success, NIL on failure
   1.382 + */
   1.383 +
   1.384 +long mix_unsubscribe (MAILSTREAM *stream,char *mailbox)
   1.385 +{
   1.386 +  return sm_unsubscribe (mailbox);
   1.387 +}
   1.388 +
   1.389 +/* MIX mail create mailbox
   1.390 + * Accepts: mail stream
   1.391 + *	    mailbox name to create
   1.392 + * Returns: T on success, NIL on failure
   1.393 + */
   1.394 +
   1.395 +long mix_create (MAILSTREAM *stream,char *mailbox)
   1.396 +{
   1.397 +  DRIVER *test;
   1.398 +  FILE *f;
   1.399 +  int c,i;
   1.400 +  char *t,tmp[MAILTMPLEN],file[MAILTMPLEN];
   1.401 +  char *s = strrchr (mailbox,'/');
   1.402 +  unsigned long now = time (NIL);
   1.403 +  long ret = NIL;
   1.404 +				/* always create \NoSelect if trailing /  */
   1.405 +  if (s && !s[1]) return dummy_create (stream,mailbox);
   1.406 +				/* validate name */
   1.407 +  if (mix_dirfmttest (s ? s + 1 : mailbox))
   1.408 +    sprintf(tmp,"Can't create mailbox %.80s: invalid MIX-format name",mailbox);
   1.409 +				/* must not already exist */
   1.410 +  else if ((test = mail_valid (NIL,mailbox,NIL)) &&
   1.411 +	   strcmp (test->name,"dummy"))
   1.412 +    sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
   1.413 +				/* create directory and metadata */
   1.414 +  else if (!dummy_create_path (stream,
   1.415 +			       mix_file (file,mix_dir (tmp,mailbox),MIXMETA),
   1.416 +			       get_dir_protection (mailbox)))
   1.417 +    sprintf (tmp,"Can't create mailbox %.80s: %.80s",mailbox,strerror (errno));
   1.418 +  else if (!(f = fopen (file,"w")))
   1.419 +    sprintf (tmp,"Can't re-open metadata %.80s: %.80s",mailbox,
   1.420 +	     strerror (errno));
   1.421 +  else {			/* success, write initial metadata */
   1.422 +    fprintf (f,SEQFMT,now);
   1.423 +    fprintf (f,MTAFMT,now,0,now);
   1.424 +    for (i = 0, c = 'K'; (i < NUSERFLAGS) &&
   1.425 +	   (t = (stream && stream->user_flags[i]) ? stream->user_flags[i] :
   1.426 +	    default_user_flag (i)) && *t; ++i) {
   1.427 +      putc (c,f);		/* write another keyword */
   1.428 +      fputs (t,f);
   1.429 +      c = ' ';			/* delimiter is now space */
   1.430 +    }
   1.431 +    fclose (f);
   1.432 +    set_mbx_protections (mailbox,file);
   1.433 +				/* point to suffix */
   1.434 +    s = file + strlen (file) - (sizeof (MIXMETA) - 1);
   1.435 +    strcpy (s,MIXINDEX);	/* create index */
   1.436 +    if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
   1.437 +      sprintf (tmp,"Can't create mix mailbox index: %.80s",strerror (errno));
   1.438 +    else {
   1.439 +      set_mbx_protections (mailbox,file);
   1.440 +      strcpy (s,MIXSTATUS);	/* create status */
   1.441 +      if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
   1.442 +	sprintf (tmp,"Can't create mix mailbox status: %.80s",
   1.443 +		 strerror (errno));
   1.444 +      else {
   1.445 +	set_mbx_protections (mailbox,file);
   1.446 +	sprintf (s,"%08lx",now);/* message file */
   1.447 +	if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
   1.448 +	  sprintf (tmp,"Can't create mix mailbox data: %.80s",
   1.449 +		   strerror (errno));
   1.450 +	else {
   1.451 +	  set_mbx_protections (mailbox,file);
   1.452 +	  ret = LONGT;	/* declare success at this point */
   1.453 +	}
   1.454 +      }
   1.455 +    }
   1.456 +  }
   1.457 +  if (!ret) MM_LOG (tmp,ERROR);	/* some error */
   1.458 +  return ret;
   1.459 +}
   1.460 +
   1.461 +/* MIX mail delete mailbox
   1.462 + *	    mailbox name to delete
   1.463 + * Returns: T on success, NIL on failure
   1.464 + */
   1.465 +
   1.466 +long mix_delete (MAILSTREAM *stream,char *mailbox)
   1.467 +{
   1.468 +  DIR *dirp;
   1.469 +  struct direct *d;
   1.470 +  int fd = -1;
   1.471 +  char *s,tmp[MAILTMPLEN];
   1.472 +  if (!mix_isvalid (mailbox,tmp))
   1.473 +    sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
   1.474 +  else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB))
   1.475 +    sprintf (tmp,"Can't lock mailbox for delete: %.80s",mailbox);
   1.476 +				/* delete metadata */
   1.477 +  else if (unlink (tmp)) sprintf (tmp,"Can't delete mailbox %.80s index: %80s",
   1.478 +				  mailbox,strerror (errno));
   1.479 +  else {
   1.480 +    close (fd);			/* close descriptor on deleted metadata */
   1.481 +				/* get directory name */
   1.482 +    *(s = strrchr (tmp,'/')) = '\0';
   1.483 +    if (dirp = opendir (tmp)) {	/* open directory */
   1.484 +      *s++ = '/';		/* restore delimiter */
   1.485 +				/* massacre messages */
   1.486 +      while (d = readdir (dirp)) if (mix_dirfmttest (d->d_name)) {
   1.487 +	strcpy (s,d->d_name);	/* make path */
   1.488 +	unlink (tmp);		/* sayonara */
   1.489 +      }
   1.490 +      closedir (dirp);		/* flush directory */
   1.491 +      *(s = strrchr (tmp,'/')) = '\0';
   1.492 +      if (rmdir (tmp)) {	/* try to remove the directory */
   1.493 +	sprintf (tmp,"Can't delete name %.80s: %.80s",
   1.494 +		 mailbox,strerror (errno));
   1.495 +	MM_LOG (tmp,WARN);
   1.496 +      }
   1.497 +    }
   1.498 +    return T;			/* always success */
   1.499 +  }
   1.500 +  if (fd >= 0) close (fd);	/* close any descriptor on metadata */
   1.501 +  MM_LOG (tmp,ERROR);		/* something failed */
   1.502 +  return NIL;
   1.503 +}
   1.504 +
   1.505 +/* MIX mail rename mailbox
   1.506 + * Accepts: MIX mail stream
   1.507 + *	    old mailbox name
   1.508 + *	    new mailbox name
   1.509 + * Returns: T on success, NIL on failure
   1.510 + */
   1.511 +
   1.512 +long mix_rename (MAILSTREAM *stream,char *old,char *newname)
   1.513 +{
   1.514 +  char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
   1.515 +  struct stat sbuf;
   1.516 +  int fd = -1;
   1.517 +  if (!mix_isvalid (old,tmp))
   1.518 +    sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
   1.519 +  else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB))
   1.520 +    sprintf (tmp,"Can't lock mailbox for rename: %.80s",old);
   1.521 +  else if (mix_dirfmttest ((s = strrchr (newname,'/')) ? s + 1 : newname))
   1.522 +    sprintf (tmp,"Can't rename to mailbox %.80s: invalid MIX-format name",
   1.523 +	     newname);
   1.524 +				/* new mailbox name must not be valid */
   1.525 +  else if (mix_isvalid (newname,tmp))
   1.526 +    sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
   1.527 +	     newname);
   1.528 +  else {
   1.529 +    mix_dir (tmp,old);		/* build old directory name */
   1.530 +    mix_dir (tmp1,newname);	/* and new directory name */
   1.531 +				/* easy if not INBOX */
   1.532 +    if (compare_cstring (old,"INBOX")) {
   1.533 +				/* found superior to destination name? */
   1.534 +      if (s = strrchr (tmp1,'/')) {
   1.535 +	c = *++s;		/* remember first character of inferior */
   1.536 +	*s = '\0';		/* tie off to get just superior */
   1.537 +				/* name doesn't exist, create it */
   1.538 +	if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
   1.539 +	    !dummy_create_path (stream,tmp1,get_dir_protection (newname)))
   1.540 +	  return NIL;
   1.541 +	*s = c;			/* restore full name */
   1.542 +      }
   1.543 +      if (!rename (tmp,tmp1)) {
   1.544 +	close (fd);		/* close descriptor on metadata */
   1.545 +	return LONGT;
   1.546 +      }
   1.547 +    }
   1.548 +
   1.549 +				/* RFC 3501 requires this */
   1.550 +    else if (dummy_create_path (stream,strcat (tmp1,"/"),
   1.551 +				get_dir_protection (newname))) {
   1.552 +      void *a;
   1.553 +      int i,n,lasterror;
   1.554 +      char *src,*dst;
   1.555 +      struct direct **names = NIL;
   1.556 +      size_t srcl = strlen (tmp);
   1.557 +      size_t dstl = strlen (tmp1);
   1.558 +				/* rename each mix file to new directory */
   1.559 +      for (i = lasterror = 0,n = scandir (tmp,&names,mix_rselect,alphasort);
   1.560 +	   i < n; ++i) {
   1.561 +	size_t len = strlen (names[i]->d_name);
   1.562 +	sprintf (src = (char *) fs_get (srcl + len + 2),"%s/%s",
   1.563 +		 tmp,names[i]->d_name);
   1.564 +	sprintf (dst = (char *) fs_get (dstl + len + 1),"%s%s",
   1.565 +		 tmp1,names[i]->d_name);
   1.566 +	if (rename (src,dst)) lasterror = errno;
   1.567 +	fs_give ((void **) &src);
   1.568 +	fs_give ((void **) &dst);
   1.569 +	fs_give ((void **) &names[i]);
   1.570 +      }
   1.571 +				/* free directory list */
   1.572 +      if (a = (void *) names) fs_give ((void **) &a);
   1.573 +      if (lasterror) errno = lasterror;
   1.574 +      else {
   1.575 +	close (fd);		/* close descriptor on metadata */
   1.576 +	return mix_create (NIL,"INBOX");
   1.577 +      }
   1.578 +    }
   1.579 +    sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",
   1.580 +	     old,newname,strerror (errno));
   1.581 +  }
   1.582 +  if (fd >= 0) close (fd);	/* close any descriptor on metadata */
   1.583 +  MM_LOG (tmp,ERROR);		/* something failed */
   1.584 +  return NIL;
   1.585 +}
   1.586 +
   1.587 +
   1.588 +/* MIX test for mix name
   1.589 + * Accepts: candidate directory name
   1.590 + * Returns: T if mix file name, NIL otherwise
   1.591 + */
   1.592 +
   1.593 +int mix_rselect (struct direct *name)
   1.594 +{
   1.595 +  return mix_dirfmttest (name->d_name);
   1.596 +}
   1.597 +
   1.598 +/* MIX mail open
   1.599 + * Accepts: stream to open
   1.600 + * Returns: stream on success, NIL on failure
   1.601 + */
   1.602 +
   1.603 +MAILSTREAM *mix_open (MAILSTREAM *stream)
   1.604 +{
   1.605 +  short silent;
   1.606 +				/* return prototype for OP_PROTOTYPE call */
   1.607 +  if (!stream) return user_flags (&mixproto);
   1.608 +  if (stream->local) fatal ("mix recycle stream");
   1.609 +  stream->local = memset (fs_get (sizeof (MIXLOCAL)),0,sizeof (MIXLOCAL));
   1.610 +				/* note if an INBOX or not */
   1.611 +  stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
   1.612 +				/* make temporary buffer */
   1.613 +  LOCAL->buf = (char *) fs_get (CHUNKSIZE);
   1.614 +  LOCAL->buflen = CHUNKSIZE - 1;
   1.615 +				/* set stream->mailbox to be directory name */
   1.616 +  mix_dir (LOCAL->buf,stream->mailbox);
   1.617 +  fs_give ((void **) &stream->mailbox);
   1.618 +  stream->mailbox = cpystr (LOCAL->buf);
   1.619 +  LOCAL->msgfd = -1;		/* currently no file open */
   1.620 +  if (!(((!stream->rdonly &&	/* open metadata file */
   1.621 +	  ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA),
   1.622 +			       O_RDWR,NIL)) >= 0)) ||
   1.623 +	 ((stream->rdonly = T) &&
   1.624 +	  ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA),
   1.625 +			       O_RDONLY,NIL)) >= 0))) &&
   1.626 +	!flock (LOCAL->mfd,LOCK_SH))) {
   1.627 +    MM_LOG ("Error opening mix metadata file",ERROR);
   1.628 +    mix_abort (stream);
   1.629 +    stream = NIL;		/* open fails */
   1.630 +  }
   1.631 +  else {			/* metadata open, complete open */
   1.632 +    LOCAL->index = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXINDEX));
   1.633 +    LOCAL->status = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXSTATUS));
   1.634 +    LOCAL->sortcache = cpystr (mix_file (LOCAL->buf,stream->mailbox,
   1.635 +					 MIXSORTCACHE));
   1.636 +    stream->sequence++;		/* bump sequence number */
   1.637 +				/* parse mailbox */
   1.638 +    stream->nmsgs = stream->recent = 0;
   1.639 +    if (silent = stream->silent) LOCAL->internal = T;
   1.640 +    stream->silent = T;
   1.641 +    if (mix_ping (stream)) {	/* do initial ping */
   1.642 +				/* try burping in case we are exclusive */
   1.643 +      if (!stream->rdonly) mix_expunge (stream,"",NIL);
   1.644 +      if (!(stream->nmsgs || stream->silent))
   1.645 +	MM_LOG ("Mailbox is empty",(long) NIL);
   1.646 +      stream->silent = silent;	/* now notify upper level */
   1.647 +      mail_exists (stream,stream->nmsgs);
   1.648 +      stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
   1.649 +	stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
   1.650 +      stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
   1.651 +      stream->kwd_create =	/* can we create new user flags? */
   1.652 +	(stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ? NIL : T;
   1.653 +    }
   1.654 +    else {			/* got murdelyzed in ping */
   1.655 +      mix_abort (stream);
   1.656 +      stream = NIL;
   1.657 +    }
   1.658 +  }
   1.659 +  return stream;		/* return stream to caller */
   1.660 +}
   1.661 +
   1.662 +/* MIX mail close
   1.663 + * Accepts: MAIL stream
   1.664 + *	    close options
   1.665 + */
   1.666 +
   1.667 +void mix_close (MAILSTREAM *stream,long options)
   1.668 +{
   1.669 +  if (LOCAL) {			/* only if a file is open */
   1.670 +    int silent = stream->silent;
   1.671 +    stream->silent = T;		/* note this stream is dying */
   1.672 +				/* burp-only or expunge */
   1.673 +    mix_expunge (stream,(options & CL_EXPUNGE) ? NIL : "",NIL);
   1.674 +    mix_abort (stream);
   1.675 +    stream->silent = silent;	/* reset silent state */
   1.676 +  }
   1.677 +}
   1.678 +
   1.679 +
   1.680 +/* MIX mail abort stream
   1.681 + * Accepts: MAIL stream
   1.682 + */
   1.683 +
   1.684 +void mix_abort (MAILSTREAM *stream)
   1.685 +{
   1.686 +  if (LOCAL) {			/* only if a file is open */
   1.687 +				/* close current message file if open */
   1.688 +    if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
   1.689 +				/* close current metadata file if open */
   1.690 +    if (LOCAL->mfd >= 0) close (LOCAL->mfd);
   1.691 +    if (LOCAL->index) fs_give ((void **) &LOCAL->index);
   1.692 +    if (LOCAL->status) fs_give ((void **) &LOCAL->status);
   1.693 +    if (LOCAL->sortcache) fs_give ((void **) &LOCAL->sortcache);
   1.694 +				/* free local scratch buffer */
   1.695 +    if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
   1.696 +				/* nuke the local data */
   1.697 +    fs_give ((void **) &stream->local);
   1.698 +    stream->dtb = NIL;		/* log out the DTB */
   1.699 +  }
   1.700 +}
   1.701 +
   1.702 +/* MIX mail fetch message header
   1.703 + * Accepts: MAIL stream
   1.704 + *	    message # to fetch
   1.705 + *	    pointer to returned header text length
   1.706 + *	    option flags
   1.707 + * Returns: message header in RFC822 format
   1.708 + */
   1.709 +
   1.710 +char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
   1.711 +		  long flags)
   1.712 +{
   1.713 +  unsigned long i,j,k;
   1.714 +  int fd;
   1.715 +  char *s,tmp[MAILTMPLEN];
   1.716 +  MESSAGECACHE *elt;
   1.717 +  if (length) *length = 0;	/* default return */
   1.718 +  if (flags & FT_UID) return "";/* UID call "impossible" */
   1.719 +  elt = mail_elt (stream,msgno);/* get elt */
   1.720 +				/* is message in current message file? */
   1.721 +  if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) {
   1.722 +    if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
   1.723 +    if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox,
   1.724 +					     elt->private.spare.data),
   1.725 +					     O_RDONLY,NIL)) < 0) return "";
   1.726 +				/* got file */
   1.727 +    LOCAL->curmsg = elt->private.spare.data;
   1.728 +  }    
   1.729 +  lseek (LOCAL->msgfd,elt->private.special.offset,L_SET);
   1.730 +				/* size of special data and header */
   1.731 +  j = elt->private.msg.header.offset + elt->private.msg.header.text.size;
   1.732 +  if (j > LOCAL->buflen) {	/* is buffer big enough? */
   1.733 +				/* no, make one that is */
   1.734 +    fs_give ((void **) &LOCAL->buf);
   1.735 +    LOCAL->buf = (char *) fs_get ((LOCAL->buflen = j) + 1);
   1.736 +  }
   1.737 +  /* Maybe someday validate internaldate too */
   1.738 +				/* slurp special data + header, validate */
   1.739 +  if ((read (LOCAL->msgfd,LOCAL->buf,j) == j) &&
   1.740 +      !strncmp (LOCAL->buf,MSGTOK,MSGTSZ) &&
   1.741 +      (elt->private.uid == strtoul ((char *) LOCAL->buf + MSGTSZ,&s,16)) &&
   1.742 +      (*s++ == ':') && (s = strchr (s,':')) &&
   1.743 +      (k = strtoul (s+1,&s,16)) && (*s++ == ':') &&
   1.744 +      (s < (char *) (LOCAL->buf + elt->private.msg.header.offset))) {
   1.745 +				/* won, set offset and size of message */
   1.746 +    i = elt->private.msg.header.offset;
   1.747 +    *length = elt->private.msg.header.text.size;
   1.748 +    if (k != elt->rfc822_size) {
   1.749 +      sprintf (tmp,"Inconsistency in mix message size, uid=%lx (%lu != %lu)",
   1.750 +	       elt->private.uid,elt->rfc822_size,k);
   1.751 +      MM_LOG (tmp,WARN);
   1.752 +    }
   1.753 +  }
   1.754 +  else {			/* document the problem */
   1.755 +    LOCAL->buf[100] = '\0';	/* tie off buffer at no more than 100 octets */
   1.756 +				/* or at newline, whichever is first */
   1.757 +    if (s = strpbrk (LOCAL->buf,"\015\012")) *s = '\0';
   1.758 +    sprintf (tmp,"Error reading mix message header, uid=%lx, s=%.0lx, h=%s",
   1.759 +	     elt->private.uid,elt->rfc822_size,LOCAL->buf);
   1.760 +    MM_LOG (tmp,ERROR);
   1.761 +    *length = i = j = 0;	/* default to empty */
   1.762 +  }
   1.763 +  LOCAL->buf[j] = '\0';		/* tie off buffer at the end */
   1.764 +  return (char *) LOCAL->buf + i;
   1.765 +}
   1.766 +
   1.767 +/* MIX mail fetch message text (body only)
   1.768 + * Accepts: MAIL stream
   1.769 + *	    message # to fetch
   1.770 + *	    pointer to returned stringstruct
   1.771 + *	    option flags
   1.772 + * Returns: T on success, NIL on failure
   1.773 + */
   1.774 +
   1.775 +long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
   1.776 +{
   1.777 +  unsigned long i;
   1.778 +  FDDATA d;
   1.779 +  MESSAGECACHE *elt;
   1.780 +				/* UID call "impossible" */
   1.781 +  if (flags & FT_UID) return NIL;
   1.782 +  elt = mail_elt (stream,msgno);
   1.783 +				/* is message in current message file? */
   1.784 +  if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) {
   1.785 +    if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
   1.786 +    if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox,
   1.787 +					     elt->private.spare.data),
   1.788 +					     O_RDONLY,NIL)) < 0) return NIL;
   1.789 +				/* got file */
   1.790 +    LOCAL->curmsg = elt->private.spare.data;
   1.791 +  }    
   1.792 +				/* doing non-peek fetch? */
   1.793 +  if (!(flags & FT_PEEK) && !elt->seen) {
   1.794 +    FILE *idxf;			/* yes, process metadata/index/status */
   1.795 +    FILE *statf = mix_parse (stream,&idxf,NIL,LONGT);
   1.796 +    elt->seen = T;		/* mark as seen */
   1.797 +    MM_FLAGS (stream,elt->msgno);
   1.798 +				/* update status file if possible */
   1.799 +    if (statf && !stream->rdonly) {
   1.800 +      elt->private.mod = LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
   1.801 +      mix_status_update (stream,statf,NIL);
   1.802 +    }
   1.803 +    if (idxf) fclose (idxf);	/* release index and status file */
   1.804 +    if (statf) fclose (statf);
   1.805 +  } 
   1.806 +  d.fd = LOCAL->msgfd;		/* set up file descriptor */
   1.807 +				/* offset of message text */
   1.808 +  d.pos = elt->private.special.offset + elt->private.msg.header.offset +
   1.809 +    elt->private.msg.header.text.size;
   1.810 +  d.chunk = LOCAL->buf;		/* initial buffer chunk */
   1.811 +  d.chunksize = CHUNKSIZE;	/* chunk size */
   1.812 +  INIT (bs,fd_string,&d,elt->rfc822_size - elt->private.msg.header.text.size);
   1.813 +  return T;
   1.814 +}
   1.815 +
   1.816 +/* MIX mail modify flags
   1.817 + * Accepts: MAIL stream
   1.818 + *	    sequence
   1.819 + *	    flag(s)
   1.820 + *	    option flags
   1.821 + */
   1.822 +
   1.823 +void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
   1.824 +{
   1.825 +  MESSAGECACHE *elt;
   1.826 +  unsigned long i,uf,ffkey;
   1.827 +  long f;
   1.828 +  short nf;
   1.829 +  FILE *idxf;
   1.830 +  FILE *statf = mix_parse (stream,&idxf,NIL,LONGT);
   1.831 +  unsigned long seq = mix_modseq (LOCAL->statusseq);
   1.832 +				/* find first free key */
   1.833 +  for (ffkey = 0; (ffkey < NUSERFLAGS) && stream->user_flags[ffkey]; ++ffkey);
   1.834 +				/* parse sequence and flags */
   1.835 +  if (((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
   1.836 +       mail_sequence (stream,sequence)) &&
   1.837 +      ((f = mail_parse_flags (stream,flag,&uf)) || uf)) {
   1.838 +				/* alter flags */
   1.839 +    for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++)
   1.840 +      if ((elt = mail_elt (stream,i))->sequence) {
   1.841 +	struct {		/* old flags */
   1.842 +	  unsigned int seen : 1;
   1.843 +	  unsigned int deleted : 1;
   1.844 +	  unsigned int flagged : 1;
   1.845 +	  unsigned int answered : 1;
   1.846 +	  unsigned int draft : 1;
   1.847 +	  unsigned long user_flags;
   1.848 +	} old;
   1.849 +	old.seen = elt->seen; old.deleted = elt->deleted;
   1.850 +	old.flagged = elt->flagged; old.answered = elt->answered;
   1.851 +	old.draft = elt->draft; old.user_flags = elt->user_flags;
   1.852 +	if (f&fSEEN) elt->seen = nf;
   1.853 +	if (f&fDELETED) elt->deleted = nf;
   1.854 +	if (f&fFLAGGED) elt->flagged = nf;
   1.855 +	if (f&fANSWERED) elt->answered = nf;
   1.856 +	if (f&fDRAFT) elt->draft = nf;
   1.857 +				/* user flags */
   1.858 +	if (flags & ST_SET) elt->user_flags |= uf;
   1.859 +	else elt->user_flags &= ~uf;
   1.860 +	if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
   1.861 +	    (old.flagged != elt->flagged) ||
   1.862 +	    (old.answered != elt->answered) || (old.draft != elt->draft) ||
   1.863 +	    (old.user_flags != elt->user_flags)) {
   1.864 +	  if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq;
   1.865 +	  MM_FLAGS (stream,elt->msgno);
   1.866 +	}
   1.867 +      }
   1.868 +				/* update status file after change */
   1.869 +    if (statf && (seq == LOCAL->statusseq))
   1.870 +      mix_status_update (stream,statf,NIL);
   1.871 +				/* update metadata if created a keyword */
   1.872 +    if ((ffkey < NUSERFLAGS) && stream->user_flags[ffkey] &&
   1.873 +	!mix_meta_update (stream))
   1.874 +      MM_LOG ("Error updating mix metadata after keyword creation",ERROR);
   1.875 +  }
   1.876 +  if (statf) fclose (statf);	/* release status file if still open */
   1.877 +  if (idxf) fclose (idxf);	/* release index file */
   1.878 +}
   1.879 +
   1.880 +/* MIX mail sort messages
   1.881 + * Accepts: mail stream
   1.882 + *	    character set
   1.883 + *	    search program
   1.884 + *	    sort program
   1.885 + *	    option flags
   1.886 + * Returns: vector of sorted message sequences or NIL if error
   1.887 + */
   1.888 +
   1.889 +unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
   1.890 +			 SORTPGM *pgm,long flags)
   1.891 +{
   1.892 +  unsigned long *ret;
   1.893 +  FILE *sortcache = mix_sortcache_open (stream);
   1.894 +  ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
   1.895 +  mix_sortcache_update (stream,&sortcache);
   1.896 +  return ret;
   1.897 +}
   1.898 +
   1.899 +
   1.900 +/* MIX mail thread messages
   1.901 + * Accepts: mail stream
   1.902 + *	    thread type
   1.903 + *	    character set
   1.904 + *	    search program
   1.905 + *	    option flags
   1.906 + * Returns: thread node tree or NIL if error
   1.907 + */
   1.908 +
   1.909 +THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset,
   1.910 +			SEARCHPGM *spg,long flags)
   1.911 +{
   1.912 +  THREADNODE *ret;
   1.913 +  FILE *sortcache = mix_sortcache_open (stream);
   1.914 +  ret = mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs);
   1.915 +  mix_sortcache_update (stream,&sortcache);
   1.916 +  return ret;
   1.917 +}
   1.918 +
   1.919 +/* MIX mail ping mailbox
   1.920 + * Accepts: MAIL stream
   1.921 + * Returns: T if stream alive, else NIL
   1.922 + */
   1.923 +
   1.924 +static int snarfing = 0;	/* lock against recursive snarfing */
   1.925 +
   1.926 +long mix_ping (MAILSTREAM *stream)
   1.927 +{
   1.928 +  FILE *idxf,*statf;
   1.929 +  struct stat sbuf;
   1.930 +  STRING msg;
   1.931 +  MESSAGECACHE *elt;
   1.932 +  int mfd,ifd,sfd;
   1.933 +  unsigned long i,msglen;
   1.934 +  char *message,date[MAILTMPLEN],flags[MAILTMPLEN];
   1.935 +  MAILSTREAM *sysibx = NIL;
   1.936 +  long ret = NIL;
   1.937 +  long snarfok = LONGT;
   1.938 +				/* time to snarf? */
   1.939 +  if (stream->inbox && !stream->rdonly && !snarfing &&
   1.940 +      (time (0) >= (LOCAL->lastsnarf +
   1.941 +		    (time_t) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) {
   1.942 +    appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
   1.943 +    copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
   1.944 +    MM_CRITICAL (stream);	/* go critical */
   1.945 +    snarfing = T;		/* don't recursively snarf */
   1.946 +				/* disable APPENDUID/COPYUID callbacks */
   1.947 +    mail_parameters (NIL,SET_APPENDUID,NIL);
   1.948 +    mail_parameters (NIL,SET_COPYUID,NIL);
   1.949 +				/* sizes match and anything in sysinbox? */
   1.950 +    if (!stat (sysinbox (),&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG) &&
   1.951 +	sbuf.st_size &&	(sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
   1.952 +	!sysibx->rdonly && sysibx->nmsgs) {
   1.953 +				/* for each message in sysibx mailbox */
   1.954 +      for (i = 1; snarfok && (i <= sysibx->nmsgs); ++i)
   1.955 +	if (!(elt = mail_elt (sysibx,i))->deleted &&
   1.956 +	    (message = mail_fetch_message (sysibx,i,&msglen,FT_PEEK)) &&
   1.957 +	    msglen) {
   1.958 +	  mail_date (date,elt);	/* make internal date string */
   1.959 +				/* make flag string */
   1.960 +	  flags[0] = flags[1] = '\0';
   1.961 +	  if (elt->seen) strcat (flags," \\Seen");
   1.962 +	  if (elt->flagged) strcat (flags," \\Flagged");
   1.963 +	  if (elt->answered) strcat (flags," \\Answered");
   1.964 +	  if (elt->draft) strcat (flags," \\Draft");
   1.965 +	  flags[0] = '(';
   1.966 +	  strcat (flags,")");
   1.967 +	  INIT (&msg,mail_string,message,msglen);
   1.968 +	  if (snarfok = mail_append_full (stream,"INBOX",flags,date,&msg)) {
   1.969 +	    char sequence[15];
   1.970 +	    sprintf (sequence,"%lu",i);
   1.971 +	    mail_flag (sysibx,sequence,"\\Deleted",ST_SET);
   1.972 +	  }
   1.973 +	}
   1.974 +
   1.975 +				/* now expunge all those messages */
   1.976 +      if (snarfok) mail_expunge (sysibx);
   1.977 +      else {
   1.978 +	sprintf (LOCAL->buf,"Can't copy new mail at message: %lu",i - 1);
   1.979 +	MM_LOG (LOCAL->buf,WARN);
   1.980 +      }
   1.981 +    }
   1.982 +    if (sysibx) mail_close (sysibx);
   1.983 +				/* reenable APPENDUID/COPYUID */
   1.984 +    mail_parameters (NIL,SET_APPENDUID,(void *) au);
   1.985 +    mail_parameters (NIL,SET_COPYUID,(void *) cu);
   1.986 +    snarfing = NIL;		/* no longer snarfing */
   1.987 +    MM_NOCRITICAL (stream);	/* release critical */
   1.988 +    LOCAL->lastsnarf = time (0);/* note time of last snarf */
   1.989 +  }
   1.990 +				/* expunging OK if global flag set */
   1.991 +  if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
   1.992 +				/* process metadata/index/status */
   1.993 +  if (statf = mix_parse (stream,&idxf,LONGT,
   1.994 +			 (LOCAL->internal ? NIL : LONGT))) {
   1.995 +    fclose (statf);		/* just close the status file */
   1.996 +    ret = LONGT;		/* declare success */
   1.997 +  }
   1.998 +  if (idxf) fclose (idxf);	/* release index file */
   1.999 +  LOCAL->expok = NIL;		/* expunge no longer OK */
  1.1000 +  if (!ret) mix_abort (stream);	/* murdelyze stream if ping fails */
  1.1001 +  return ret;
  1.1002 +}
  1.1003 +
  1.1004 +
  1.1005 +/* MIX mail checkpoint mailbox (burp only)
  1.1006 + * Accepts: MAIL stream
  1.1007 + */
  1.1008 +
  1.1009 +void mix_check (MAILSTREAM *stream)
  1.1010 +{
  1.1011 +  if (stream->rdonly)		/* won't do on readonly files! */
  1.1012 +    MM_LOG ("Checkpoint ignored on readonly mailbox",NIL);
  1.1013 +				/* do burp-only expunge action */
  1.1014 +  if (mix_expunge (stream,"",NIL)) MM_LOG ("Check completed",(long) NIL);
  1.1015 +}
  1.1016 +
  1.1017 +/* MIX mail expunge mailbox
  1.1018 + * Accepts: MAIL stream
  1.1019 + *	    sequence to expunge if non-NIL, empty string for burp only
  1.1020 + *	    expunge options
  1.1021 + * Returns: T on success, NIL if failure
  1.1022 + */
  1.1023 +
  1.1024 +long mix_expunge (MAILSTREAM *stream,char *sequence,long options)
  1.1025 +{
  1.1026 +  FILE *idxf = NIL;
  1.1027 +  FILE *statf = NIL;
  1.1028 +  MESSAGECACHE *elt;
  1.1029 +  int ifd,sfd;
  1.1030 +  long ret;
  1.1031 +  unsigned long i;
  1.1032 +  unsigned long nexp = 0;
  1.1033 +  unsigned long reclaimed = 0;
  1.1034 +  int burponly = (sequence && !*sequence);
  1.1035 +  LOCAL->expok = T;		/* expunge during ping is OK */
  1.1036 +  if (!(ret = burponly || !sequence ||
  1.1037 +	((options & EX_UID) ?
  1.1038 +	 mail_uid_sequence (stream,sequence) :
  1.1039 +	 mail_sequence (stream,sequence))) || stream->rdonly);
  1.1040 +				/* read index and open status exclusive */
  1.1041 +  else if (statf = mix_parse (stream,&idxf,LONGT,
  1.1042 +			      LOCAL->internal ? NIL : LONGT)) {
  1.1043 +				/* expunge unless just burping */
  1.1044 +    if (!burponly) for (i = 1; i <= stream->nmsgs;) {
  1.1045 +      elt = mail_elt (stream,i);/* need to expunge this message? */
  1.1046 +      if (sequence ? elt->sequence : elt->deleted) {
  1.1047 +	++nexp;			/* yes, make it so */
  1.1048 +	mail_expunged (stream,i);
  1.1049 +      }
  1.1050 +      else ++i;		       /* otherwise advance to next message */
  1.1051 +    }
  1.1052 +
  1.1053 +				/* burp if can get exclusive access */
  1.1054 +    if (!flock (LOCAL->mfd,LOCK_EX|LOCK_NB)) {
  1.1055 +      void *a;
  1.1056 +      struct direct **names = NIL;
  1.1057 +      long nfiles = scandir (stream->mailbox,&names,mix_select,mix_msgfsort);
  1.1058 +      if (nfiles > 0) {		/* if have message files */
  1.1059 +	MIXBURP *burp,*cur;
  1.1060 +				/* initialize burp list */
  1.1061 +	for (i = 0, burp = cur = NIL; i < nfiles; ++i) {
  1.1062 +	  MIXBURP *nxt = (MIXBURP *) memset (fs_get (sizeof (MIXBURP)),0,
  1.1063 +					     sizeof (MIXBURP));
  1.1064 +				/* another file found */
  1.1065 +	  if (cur) cur = cur->next = nxt;
  1.1066 +	  else cur = burp = nxt;
  1.1067 +	  cur->name = names[i]->d_name;
  1.1068 +	  cur->fileno = strtoul (cur->name + sizeof (MIXNAME) - 1,NIL,16);
  1.1069 +	  cur->tail = &cur->set;
  1.1070 +	  fs_give ((void **) &names[i]);
  1.1071 +	}
  1.1072 +				/* now load ranges */
  1.1073 +	for (i = 1, cur = burp; ret && (i <= stream->nmsgs); i++) {
  1.1074 +				/* is this message in current set? */
  1.1075 +	  elt = mail_elt (stream,i);
  1.1076 +	  if (cur && (elt->private.spare.data != cur->fileno)) {
  1.1077 +				/* restart if necessary */
  1.1078 +	    if (elt->private.spare.data < cur->fileno) cur = burp;
  1.1079 +				/* hunt for appropriate mailbox */
  1.1080 +	    while (cur && (elt->private.spare.data > cur->fileno))
  1.1081 +	      cur = cur->next;
  1.1082 +				/* ought to have found it now... */
  1.1083 +	    if (cur && (elt->private.spare.data != cur->fileno)) cur = NIL;
  1.1084 +	  }
  1.1085 +				/* if found, add to set */
  1.1086 +	  if (cur) ret = mix_addset (&cur->tail,elt->private.special.offset,
  1.1087 +				     elt->private.msg.header.offset +
  1.1088 +				     elt->rfc822_size);
  1.1089 +	  else {		/* uh-oh */
  1.1090 +	    sprintf (LOCAL->buf,"Can't locate mix message file %.08lx",
  1.1091 +		     elt->private.spare.data);
  1.1092 +	    MM_LOG (LOCAL->buf,ERROR);
  1.1093 +	    ret = NIL;
  1.1094 +	  }
  1.1095 +	}
  1.1096 +	if (ret) 		/* if no errors, burp all files */
  1.1097 +	  for (cur = burp; ret && cur; cur = cur->next) {
  1.1098 +				/* if non-empty, burp it */
  1.1099 +	    if (cur->set.last) ret = mix_burp (stream,cur,&reclaimed);
  1.1100 +				/* empty, delete it unless new msg file */
  1.1101 +	    else if (mix_file_data (LOCAL->buf,stream->mailbox,cur->fileno) &&
  1.1102 +		     ((cur->fileno == LOCAL->newmsg) ?
  1.1103 +		      truncate (LOCAL->buf,0) : unlink (LOCAL->buf))) {
  1.1104 +	      sprintf (LOCAL->buf,
  1.1105 +		       "Can't delete empty message file %.80s: %.80s",
  1.1106 +		       cur->name,strerror (errno));
  1.1107 +	      MM_LOG (LOCAL->buf,WARN);
  1.1108 +	    }
  1.1109 +	  }
  1.1110 +      }
  1.1111 +      else MM_LOG ("No mix message files found during expunge",WARN);
  1.1112 +				/* free directory list */
  1.1113 +      if (a = (void *) names) fs_give ((void **) &a);
  1.1114 +    }
  1.1115 +
  1.1116 +				/* either way, re-acquire shared lock */
  1.1117 +    if (flock (LOCAL->mfd,LOCK_SH|LOCK_NB))
  1.1118 +      fatal ("Unable to re-acquire metadata shared lock!");
  1.1119 +    /* Do this step even if ret is NIL (meaning some burp problem)! */
  1.1120 +    if (nexp || reclaimed) {	/* rewrite index and status if changed */
  1.1121 +      LOCAL->indexseq = mix_modseq (LOCAL->indexseq);
  1.1122 +      if (mix_index_update (stream,idxf,NIL)) {
  1.1123 +	LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
  1.1124 +				/* set failure if update fails */
  1.1125 +	ret = mix_status_update (stream,statf,NIL);
  1.1126 +      }
  1.1127 +    }
  1.1128 +  }
  1.1129 +  if (statf) fclose (statf);	/* close status if still open */
  1.1130 +  if (idxf) fclose (idxf);	/* close index if still open */
  1.1131 +  LOCAL->expok = NIL;		/* cancel expok */
  1.1132 +  if (ret) {			/* only if success */
  1.1133 +    char *s = NIL;
  1.1134 +    if (nexp) sprintf (s = LOCAL->buf,"Expunged %lu messages",nexp);
  1.1135 +    else if (reclaimed)
  1.1136 +      sprintf (s=LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
  1.1137 +    else if (!burponly)
  1.1138 +      s = stream->rdonly ? "Expunge ignored on readonly mailbox" :
  1.1139 +	   "No messages deleted, so no update needed";
  1.1140 +    if (s) MM_LOG (s,(long) NIL);
  1.1141 +  }
  1.1142 +  return ret;
  1.1143 +}
  1.1144 +
  1.1145 +/* MIX test for message file name
  1.1146 + * Accepts: candidate directory name
  1.1147 + * Returns: T if message file name, NIL otherwise
  1.1148 + *
  1.1149 + * ".mix" with no suffix was used by experimental versions
  1.1150 + */
  1.1151 +
  1.1152 +int mix_select (struct direct *name)
  1.1153 +{
  1.1154 +  char c,*s;
  1.1155 +				/* make sure name has prefix */
  1.1156 +  if (mix_dirfmttest (name->d_name)) {
  1.1157 +    for (c = *(s = name->d_name + sizeof (MIXNAME) - 1); c && isxdigit (c);
  1.1158 +	 c = *s++);
  1.1159 +    if (!c) return T;		/* all-hex or no suffix */
  1.1160 +  }
  1.1161 +  return NIL;			/* not suffix or non-hex */
  1.1162 +}
  1.1163 +
  1.1164 +
  1.1165 +/* MIX msg file name comparision
  1.1166 + * Accepts: first candidate directory entry
  1.1167 + *	    second candidate directory entry
  1.1168 + * Returns: -1 if d1 < d2, 0 if d1 == d2, 1 d1 > d2
  1.1169 + */
  1.1170 +
  1.1171 +int mix_msgfsort (const void *d1,const void *d2)
  1.1172 +{
  1.1173 +  char *n1 = (*(struct direct **) d1)->d_name + sizeof (MIXNAME) - 1;
  1.1174 +  char *n2 = (*(struct direct **) d2)->d_name + sizeof (MIXNAME) - 1;
  1.1175 +  return compare_ulong (*n1 ? strtoul (n1,NIL,16) : 0,
  1.1176 +			*n2 ? strtoul (n2,NIL,16) : 0);
  1.1177 +}
  1.1178 +
  1.1179 +
  1.1180 +/* MIX add a range to a set
  1.1181 + * Accepts: pointer to set to add
  1.1182 + *	    start of set
  1.1183 + *	    size of set
  1.1184 + * Returns: T if success, set updated, NIL otherwise
  1.1185 + */
  1.1186 +
  1.1187 +long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size)
  1.1188 +{
  1.1189 +  SEARCHSET *s = *set;
  1.1190 +  if (start < s->last) {	/* sanity check */
  1.1191 +    char tmp[MAILTMPLEN];
  1.1192 +    sprintf (tmp,"Backwards-running mix index %lu < %lu",start,s->last);
  1.1193 +    MM_LOG (tmp,ERROR);
  1.1194 +    return NIL;
  1.1195 +  }
  1.1196 +				/* range initially empty? */
  1.1197 +  if (!s->last) s->first = start;
  1.1198 +  else if (start > s->last)	/* no, start new range if can't append */
  1.1199 +    (*set = s = s->next = mail_newsearchset ())->first = start;
  1.1200 +  s->last = start + size;	/* end of current range */
  1.1201 +  return LONGT;
  1.1202 +}
  1.1203 +
  1.1204 +/* MIX burp message file
  1.1205 + * Accepts: MAIL stream
  1.1206 + *	    current burp block for this message
  1.1207 + * Returns: T if successful, NIL if failed
  1.1208 + */
  1.1209 +
  1.1210 +static char *staterr = "Error in stat of mix message file %.80s: %.80s";
  1.1211 +static char *truncerr = "Error truncating mix message file %.80s: %.80s";
  1.1212 +
  1.1213 +long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed)
  1.1214 +{
  1.1215 +  MESSAGECACHE *elt;
  1.1216 +  SEARCHSET *set;
  1.1217 +  struct stat sbuf;
  1.1218 +  off_t rpos,wpos;
  1.1219 +  size_t size,wsize,wpending,written;
  1.1220 +  int fd;
  1.1221 +  FILE *f;
  1.1222 +  void *s;
  1.1223 +  unsigned long i;
  1.1224 +  long ret = NIL;
  1.1225 +				/* build file name */
  1.1226 +  mix_file_data (LOCAL->buf,stream->mailbox,burp->fileno);
  1.1227 +				/* need to burp at start or multiple ranges? */
  1.1228 +  if (!burp->set.first && !burp->set.next) {
  1.1229 +				/* easy case, single range at start of file */
  1.1230 +    if (stat (LOCAL->buf,&sbuf)) {
  1.1231 +      sprintf (LOCAL->buf,staterr,burp->name,strerror (errno));
  1.1232 +      MM_LOG (LOCAL->buf,ERROR);
  1.1233 +    }
  1.1234 +				/* is this range sane? */
  1.1235 +    else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) {
  1.1236 +				/* if matches range then no burp needed! */
  1.1237 +      if (burp->set.last == sbuf.st_size) ret = LONGT;
  1.1238 +				/* just need to remove cruft at end */
  1.1239 +      else if (ret = !truncate (LOCAL->buf,burp->set.last))
  1.1240 +	*reclaimed += sbuf.st_size - burp->set.last;
  1.1241 +      else {
  1.1242 +	sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno));
  1.1243 +	MM_LOG (LOCAL->buf,ERROR);
  1.1244 +      }
  1.1245 +    }
  1.1246 +  }
  1.1247 +				/* have to do more work, get the file */
  1.1248 +  else if (((fd = open (LOCAL->buf,O_RDWR,NIL)) < 0) ||
  1.1249 +	   !(f = fdopen (fd,"r+b"))) {
  1.1250 +    sprintf (LOCAL->buf,"Error opening mix message file %.80s: %.80s",
  1.1251 +	     burp->name,strerror (errno));
  1.1252 +    MM_LOG (LOCAL->buf,ERROR);
  1.1253 +    if (fd >= 0) close (fd);	/* in case fdopen() failure */
  1.1254 +  }
  1.1255 +  else if (fstat (fd,&sbuf)) {	/* get file size */
  1.1256 +    sprintf (LOCAL->buf,staterr,burp->name,strerror (errno));
  1.1257 +    MM_LOG (LOCAL->buf,ERROR);
  1.1258 +    fclose (f);
  1.1259 +  }
  1.1260 +
  1.1261 +				/* only if sane */
  1.1262 +  else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) {
  1.1263 +				/* make sure each range starts with token */
  1.1264 +    for (set = &burp->set; set; set = set->next)
  1.1265 +      if (fseek (f,set->first,SEEK_SET) ||
  1.1266 +	  (fread (LOCAL->buf,1,MSGTSZ,f) != MSGTSZ) ||
  1.1267 +	  strncmp (LOCAL->buf,MSGTOK,MSGTSZ)) {
  1.1268 +	sprintf (LOCAL->buf,"Bad message token in mix message file at %lu",
  1.1269 +		 set->first);
  1.1270 +	MM_LOG (LOCAL->buf,ERROR);
  1.1271 +	fclose (f);
  1.1272 +	return NIL;		/* burp fails for this file */
  1.1273 +      }
  1.1274 +				/* burp out each old message */
  1.1275 +    for (set = &burp->set, wpos = 0; set; set = set->next) {
  1.1276 +				/* move down this range */
  1.1277 +      for (rpos = set->first, size = set->last - set->first;
  1.1278 +	   size; size -= wsize) {
  1.1279 +	if (rpos != wpos) {	/* data to skip at start? */
  1.1280 +				/* no, slide this buffer down */
  1.1281 +	  wsize = min (size,LOCAL->buflen);
  1.1282 +				/* failure is not an option here */
  1.1283 +	  while (fseek (f,rpos,SEEK_SET) ||
  1.1284 +		 (fread (LOCAL->buf,1,wsize,f) != wsize)) {
  1.1285 +	    MM_NOTIFY (stream,strerror (errno),WARN);
  1.1286 +	    MM_DISKERROR (stream,errno,T);
  1.1287 +	  }
  1.1288 +				/* nor here */
  1.1289 +	  while (fseek (f,wpos,SEEK_SET)) {
  1.1290 +	    MM_NOTIFY (stream,strerror (errno),WARN);
  1.1291 +	    MM_DISKERROR (stream,errno,T);
  1.1292 +	  }
  1.1293 +				/* and especially not here */
  1.1294 +	  for (s = LOCAL->buf, wpending = wsize; wpending; wpending -= written)
  1.1295 +	    if (!(written = fwrite (LOCAL->buf,1,wpending,f))) {
  1.1296 +	      MM_NOTIFY (stream,strerror (errno),WARN);
  1.1297 +	      MM_DISKERROR (stream,errno,T);
  1.1298 +	    }
  1.1299 +	}
  1.1300 +	else wsize = size;	/* nothing to skip, say we wrote it all */
  1.1301 +	rpos += wsize; wpos += wsize;
  1.1302 +      }
  1.1303 +    }
  1.1304 +
  1.1305 +    while (fflush (f)) {	/* failure also not an option here... */
  1.1306 +      MM_NOTIFY (stream,strerror (errno),WARN);
  1.1307 +      MM_DISKERROR (stream,errno,T);
  1.1308 +    }
  1.1309 +    if (ftruncate (fd,wpos)) {	/* flush cruft at end of file */
  1.1310 +      sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno));
  1.1311 +      MM_LOG (LOCAL->buf,WARN);
  1.1312 +    }
  1.1313 +    else *reclaimed += rpos - wpos;
  1.1314 +    ret = !fclose (f);		/* close file */
  1.1315 +				/* slide down message positions in index */
  1.1316 +    for (i = 1,rpos = 0; i <= stream->nmsgs; ++i)
  1.1317 +      if ((elt = mail_elt (stream,i))->private.spare.data == burp->fileno) {
  1.1318 +	elt->private.special.offset = rpos;
  1.1319 +	rpos += elt->private.msg.header.offset + elt->rfc822_size;
  1.1320 +      }
  1.1321 +				/* debugging */
  1.1322 +    if (rpos != wpos) fatal ("burp size consistency check!");
  1.1323 +  }
  1.1324 +  return ret;
  1.1325 +}
  1.1326 +
  1.1327 +
  1.1328 +/* MIX burp sanity check to make sure not burping off end of file
  1.1329 + * Accepts: burp set
  1.1330 + *	    file size
  1.1331 + *	    file name
  1.1332 + * Returns: T if sane, NIL if insane
  1.1333 + */
  1.1334 +
  1.1335 +long mix_burp_check (SEARCHSET *set,size_t size,char *file)
  1.1336 +{
  1.1337 +  do if (set->last > size) {	/* sanity check */
  1.1338 +    char tmp[MAILTMPLEN];
  1.1339 +    sprintf (tmp,"Unexpected short mix message file %.80s %lu < %lu",
  1.1340 +	     file,size,set->last);
  1.1341 +    MM_LOG (tmp,ERROR);
  1.1342 +    return NIL;			/* don't burp this file at all */
  1.1343 +  } while (set = set->next);
  1.1344 +  return LONGT;
  1.1345 +}
  1.1346 +
  1.1347 +/* MIX mail copy message(s)
  1.1348 + * Accepts: MAIL stream
  1.1349 + *	    sequence
  1.1350 + *	    destination mailbox
  1.1351 + *	    copy options
  1.1352 + * Returns: T if copy successful, else NIL
  1.1353 + */
  1.1354 +
  1.1355 +long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
  1.1356 +{
  1.1357 +  FDDATA d;
  1.1358 +  STRING st;
  1.1359 +  char tmp[2*MAILTMPLEN];
  1.1360 +  long ret = mix_isvalid (mailbox,LOCAL->buf);
  1.1361 +  mailproxycopy_t pc =
  1.1362 +    (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
  1.1363 +  MAILSTREAM *astream = NIL;
  1.1364 +  FILE *idxf = NIL;
  1.1365 +  FILE *msgf = NIL;
  1.1366 +  FILE *statf = NIL;
  1.1367 +  if (!ret) switch (errno) {	/* make sure valid mailbox */
  1.1368 +  case NIL:			/* no error in stat() */
  1.1369 +    if (pc) return (*pc) (stream,sequence,mailbox,options);
  1.1370 +    sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox);
  1.1371 +    MM_LOG (tmp,ERROR);
  1.1372 +    break;
  1.1373 +  default:			/* some stat() error */
  1.1374 +    MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
  1.1375 +    break;
  1.1376 +  }
  1.1377 +				/* get sequence to copy */
  1.1378 +  else if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
  1.1379 +		    mail_sequence (stream,sequence))));
  1.1380 +				/* acquire stream to append */
  1.1381 +  else if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) &&
  1.1382 +		  !astream->rdonly &&
  1.1383 +		  (((MIXLOCAL *) astream->local)->expok = T) &&
  1.1384 +		  (statf = mix_parse (astream,&idxf,LONGT,NIL))) ?
  1.1385 +	   LONGT : NIL) {
  1.1386 +    int fd;
  1.1387 +    unsigned long i;
  1.1388 +    MESSAGECACHE *elt;
  1.1389 +    unsigned long newsize,hdrsize,size;
  1.1390 +    MIXLOCAL *local = (MIXLOCAL *) astream->local;
  1.1391 +    unsigned long seq = mix_modseq (local->metaseq);
  1.1392 +				/* make sure new modseq fits */
  1.1393 +    if (local->indexseq > seq) seq = local->indexseq + 1;
  1.1394 +    if (local->statusseq > seq) seq = local->statusseq + 1;
  1.1395 +				/* calculate size of per-message header */
  1.1396 +    sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0);
  1.1397 +    hdrsize = strlen (local->buf);
  1.1398 +
  1.1399 +    MM_CRITICAL (stream);	/* go critical */
  1.1400 +    astream->silent = T;	/* no events here */
  1.1401 +				/* calculate size that will be added */
  1.1402 +    for (i = 1, newsize = 0; i <= stream->nmsgs; ++i)
  1.1403 +      if ((elt = mail_elt (stream,i))->sequence)
  1.1404 +	newsize += hdrsize + elt->rfc822_size;
  1.1405 +				/* open data file */
  1.1406 +    if (msgf = mix_data_open (astream,&fd,&size,newsize)) {
  1.1407 +      char *t;
  1.1408 +      unsigned long j,uid,uidv;
  1.1409 +      copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
  1.1410 +      SEARCHSET *source = cu ? mail_newsearchset () : NIL;
  1.1411 +      SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
  1.1412 +      for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); ++i) 
  1.1413 +	if (((elt = mail_elt (stream,i))->sequence) && elt->rfc822_size) {
  1.1414 +				/* is message in current message file? */
  1.1415 +	  if ((LOCAL->msgfd < 0) ||
  1.1416 +	      (elt->private.spare.data != LOCAL->curmsg)) {
  1.1417 +	    if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
  1.1418 +	    if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,
  1.1419 +						     stream->mailbox,
  1.1420 +						     elt->private.spare.data),
  1.1421 +				      O_RDONLY,NIL)) >= 0)
  1.1422 +	      LOCAL->curmsg = elt->private.spare.data;
  1.1423 +	  }
  1.1424 +	  if (LOCAL->msgfd < 0) ret = NIL;
  1.1425 +	  else {		/* got file */
  1.1426 +	    d.fd = LOCAL->msgfd;/* set up file descriptor */
  1.1427 +				/* start of message */
  1.1428 +	    d.pos = elt->private.special.offset +
  1.1429 +	      elt->private.msg.header.offset;
  1.1430 +	    d.chunk = LOCAL->buf;
  1.1431 +	    d.chunksize = CHUNKSIZE;
  1.1432 +	    INIT (&st,fd_string,&d,elt->rfc822_size);
  1.1433 +				/* init flag string */
  1.1434 +	    tmp[0] = tmp[1] = '\0';
  1.1435 +	    if (j = elt->user_flags) do
  1.1436 +	      if ((t = stream->user_flags[find_rightmost_bit (&j)]) && *t)
  1.1437 +		strcat (strcat (tmp," "),t);
  1.1438 +	    while (j);
  1.1439 +	    if (elt->seen) strcat (tmp," \\Seen");
  1.1440 +	    if (elt->deleted) strcat (tmp," \\Deleted");
  1.1441 +	    if (elt->flagged) strcat (tmp," \\Flagged");
  1.1442 +	    if (elt->answered) strcat (tmp," \\Answered");
  1.1443 +	    if (elt->draft) strcat (tmp," \\Draft");
  1.1444 +	    tmp[0] = '(';	/* wrap list */
  1.1445 +	    strcat (tmp,")");
  1.1446 +				/* if append OK, add to source set */
  1.1447 +	    if ((ret = mix_append_msg (astream,msgf,tmp,elt,&st,dest,
  1.1448 +				       seq)) &&	source)
  1.1449 +	      mail_append_set (source,mail_uid (stream,i));
  1.1450 +	  }
  1.1451 +	}
  1.1452 +
  1.1453 +				/* finish write if success */
  1.1454 +      if (ret && (ret = !fflush (msgf))) {
  1.1455 +	fclose (msgf);		/* all good, close the msg file now */
  1.1456 +				/* write new metadata, index, and status */
  1.1457 +	local->metaseq = local->indexseq = local->statusseq = seq;
  1.1458 +	if (ret = (mix_meta_update (astream) &&
  1.1459 +		   mix_index_update (astream,idxf,LONGT))) {
  1.1460 +				/* success, delete if doing a move */
  1.1461 +	  if (options & CP_MOVE)
  1.1462 +	    for (i = 1; i <= stream->nmsgs; i++)
  1.1463 +	      if ((elt = mail_elt (stream,i))->sequence) {
  1.1464 +		elt->deleted = T;
  1.1465 +		if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq;
  1.1466 +		MM_FLAGS (stream,elt->msgno);
  1.1467 +	      }
  1.1468 +				/* done with status file now */
  1.1469 +	  mix_status_update (astream,statf,LONGT);
  1.1470 +				/* return sets if doing COPYUID */
  1.1471 +	  if (cu) (*cu) (stream,mailbox,astream->uid_validity,source,dest);
  1.1472 +	  source = dest = NIL;	/* don't free these sets now */
  1.1473 +	}
  1.1474 +      }
  1.1475 +      else {			/* error */
  1.1476 +	if (errno) {		/* output error message if system call error */
  1.1477 +	  sprintf (tmp,"Message copy failed: %.80s",strerror (errno));
  1.1478 +	  MM_LOG (tmp,ERROR);
  1.1479 +	}
  1.1480 +	ftruncate (fd,size);	/* revert file */
  1.1481 +	close (fd);		/* make sure that fclose doesn't corrupt us */
  1.1482 +	fclose (msgf);		/* free the stdio resources */
  1.1483 +      }
  1.1484 +				/* flush any sets remaining */
  1.1485 +      mail_free_searchset (&source);
  1.1486 +      mail_free_searchset (&dest);
  1.1487 +    }
  1.1488 +    else {			/* message file open failed */
  1.1489 +      sprintf (tmp,"Error opening copy message file: %.80s",
  1.1490 +	       strerror (errno));
  1.1491 +      MM_LOG (tmp,ERROR);
  1.1492 +      ret = NIL;
  1.1493 +    }
  1.1494 +    MM_NOCRITICAL (stream);
  1.1495 +  }
  1.1496 +  else MM_LOG ("Can't open copy mailbox",ERROR);
  1.1497 +  if (statf) fclose (statf);	/* close status if still open */
  1.1498 +  if (idxf) fclose (idxf);	/* close index if still open */
  1.1499 +				/* finished with append stream */
  1.1500 +  if (astream) mail_close (astream);
  1.1501 +  return ret;			/* return state */
  1.1502 +}
  1.1503 +
  1.1504 +/* MIX mail append message from stringstruct
  1.1505 + * Accepts: MAIL stream
  1.1506 + *	    destination mailbox
  1.1507 + *	    append callback
  1.1508 + *	    data for callback
  1.1509 + * Returns: T if append successful, else NIL
  1.1510 + */
  1.1511 +
  1.1512 +long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
  1.1513 +{
  1.1514 +  STRING *message;
  1.1515 +  char *flags,*date,tmp[MAILTMPLEN];
  1.1516 +				/* N.B.: can't use LOCAL->buf for tmp */
  1.1517 +  long ret = mix_isvalid (mailbox,tmp);
  1.1518 +				/* default stream to prototype */
  1.1519 +  if (!stream) stream = user_flags (&mixproto);
  1.1520 +  if (!ret) switch (errno) {	/* if not valid mailbox */
  1.1521 +  case ENOENT:			/* no such file? */
  1.1522 +    if (ret = compare_cstring (mailbox,"INBOX") ?
  1.1523 +	NIL : mix_create (NIL,"INBOX"))
  1.1524 +      break;
  1.1525 +    MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
  1.1526 +    break;
  1.1527 +  default:
  1.1528 +    sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox);
  1.1529 +    MM_LOG (tmp,ERROR);
  1.1530 +    break;
  1.1531 +  }
  1.1532 +
  1.1533 +				/* get first message */
  1.1534 +  if (ret && MM_APPEND (af) (stream,data,&flags,&date,&message)) {
  1.1535 +    MAILSTREAM *astream;
  1.1536 +    FILE *idxf = NIL;
  1.1537 +    FILE *msgf = NIL;
  1.1538 +    FILE *statf = NIL;
  1.1539 +    if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) &&
  1.1540 +	       !astream->rdonly &&
  1.1541 +	       (((MIXLOCAL *) astream->local)->expok = T) &&
  1.1542 +	       (statf = mix_parse (astream,&idxf,LONGT,NIL))) ?
  1.1543 +	LONGT : NIL) {
  1.1544 +      int fd;
  1.1545 +      unsigned long size,hdrsize;
  1.1546 +      MESSAGECACHE elt;
  1.1547 +      MIXLOCAL *local = (MIXLOCAL *) astream->local;
  1.1548 +      unsigned long seq = mix_modseq (local->metaseq);
  1.1549 +				/* make sure new modseq fits */
  1.1550 +      if (local->indexseq > seq) seq = local->indexseq + 1;
  1.1551 +      if (local->statusseq > seq) seq = local->statusseq + 1;
  1.1552 +				/* calculate size of per-message header */
  1.1553 +      sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0);
  1.1554 +      hdrsize = strlen (local->buf);
  1.1555 +      MM_CRITICAL (astream);	/* go critical */
  1.1556 +      astream->silent = T;	/* no events here */
  1.1557 +				/* open data file */
  1.1558 +      if (msgf = mix_data_open (astream,&fd,&size,hdrsize + SIZE (message))) {
  1.1559 +	appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
  1.1560 +	SEARCHSET *dst = au ? mail_newsearchset () : NIL;
  1.1561 +	while (ret && message) {/* while good to go and have messages */
  1.1562 +	  errno = NIL;		/* in case one of these causes failure */
  1.1563 +				/* guard against zero-length */
  1.1564 +	  if (!(ret = SIZE (message)))
  1.1565 +	    MM_LOG ("Append of zero-length message",ERROR);
  1.1566 +	  else if (date && !(ret = mail_parse_date (&elt,date))) {
  1.1567 +	    sprintf (tmp,"Bad date in append: %.80s",date);
  1.1568 +	    MM_LOG (tmp,ERROR);
  1.1569 +	  }
  1.1570 +	  else {
  1.1571 +	    if (!date) {	/* if date not specified, use now */
  1.1572 +	      internal_date (tmp);
  1.1573 +	      mail_parse_date (&elt,tmp);
  1.1574 +	    }
  1.1575 +	    ret = mix_append_msg (astream,msgf,flags,&elt,message,dst,seq) &&
  1.1576 +	      MM_APPEND (af) (stream,data,&flags,&date,&message);
  1.1577 +	  }
  1.1578 +	}
  1.1579 +
  1.1580 +				/* finish write if success */
  1.1581 +	if (ret && (ret = !fflush (msgf))) {
  1.1582 +	  fclose (msgf);	/* all good, close the msg file now */
  1.1583 +				/* write new metadata, index, and status */
  1.1584 +	  local->metaseq = local->indexseq = local->statusseq = seq;
  1.1585 +	  if ((ret = (mix_meta_update (astream) &&
  1.1586 +		      mix_index_update (astream,idxf,LONGT) &&
  1.1587 +		      mix_status_update (astream,statf,LONGT))) && au) {
  1.1588 +	      (*au) (mailbox,astream->uid_validity,dst);
  1.1589 +	      dst = NIL;	/* don't free this set now */
  1.1590 +	  }
  1.1591 +	}
  1.1592 +	else {			/* failure */
  1.1593 +	  if (errno) {		/* output error message if system call error */
  1.1594 +	    sprintf (tmp,"Message append failed: %.80s",strerror (errno));
  1.1595 +	    MM_LOG (tmp,ERROR);
  1.1596 +	  }
  1.1597 +	  ftruncate (fd,size);	/* revert all writes to file*/
  1.1598 +	  close (fd);		/* make sure that fclose doesn't corrupt us */
  1.1599 +	  fclose (msgf);	/* free the stdio resources */
  1.1600 +	}
  1.1601 +				/* flush any set remaining */
  1.1602 +	mail_free_searchset (&dst);
  1.1603 +      }
  1.1604 +      else {			/* message file open failed */
  1.1605 +	sprintf (tmp,"Error opening append message file: %.80s",
  1.1606 +		 strerror (errno));
  1.1607 +	MM_LOG (tmp,ERROR);
  1.1608 +	ret = NIL;
  1.1609 +      }
  1.1610 +      MM_NOCRITICAL (astream);	/* release critical */
  1.1611 +    }
  1.1612 +    else MM_LOG ("Can't open append mailbox",ERROR);
  1.1613 +    if (statf) fclose (statf);	/* close status if still open */
  1.1614 +    if (idxf) fclose (idxf);	/* close index if still open */
  1.1615 +    if (astream) mail_close (astream);
  1.1616 +  }
  1.1617 +  return ret;
  1.1618 +}
  1.1619 +
  1.1620 +/* MIX mail append single message
  1.1621 + * Accepts: MAIL stream
  1.1622 + *	    flags for new message if non-NIL
  1.1623 + *	    elt with source date if non-NIL
  1.1624 + *	    stringstruct of message text
  1.1625 + *	    searchset to place UID
  1.1626 + *	    modseq of message
  1.1627 + * Returns: T if success, NIL if failure
  1.1628 + */
  1.1629 +
  1.1630 +long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt,
  1.1631 +		     STRING *msg,SEARCHSET *set,unsigned long seq)
  1.1632 +{
  1.1633 +  MESSAGECACHE *elt;
  1.1634 +  int c,cs;
  1.1635 +  unsigned long i,j,k,uf,hoff;
  1.1636 +  long sf;
  1.1637 +  stream->kwd_create = NIL;	/* don't copy unknown keywords */
  1.1638 +  sf = mail_parse_flags (stream,flags,&uf);
  1.1639 +				/* swell the cache */
  1.1640 +  mail_exists (stream,++stream->nmsgs);
  1.1641 +				/* assign new UID from metadata */
  1.1642 +  (elt = mail_elt (stream,stream->nmsgs))->private.uid = ++stream->uid_last;
  1.1643 +  elt->private.mod = seq;	/* set requested modseq in status */
  1.1644 +  elt->rfc822_size = SIZE (msg);/* copy message size and date to index */
  1.1645 +  elt->year = delt->year; elt->month = delt->month; elt->day = delt->day;
  1.1646 +  elt->hours = delt->hours; elt->minutes = delt->minutes;
  1.1647 +  elt->seconds = delt->seconds; elt->zoccident = delt->zoccident;
  1.1648 +  elt->zhours = delt->zhours; elt->zminutes = delt->zminutes;
  1.1649 +  /*
  1.1650 +   * Do NOT set elt->valid here!  mix_status_update() uses it to determine
  1.1651 +   * whether a message should be marked as old.
  1.1652 +   */
  1.1653 +  if (sf&fSEEN) elt->seen = T;	/* copy flags to status */
  1.1654 +  if (sf&fDELETED) elt->deleted = T;
  1.1655 +  if (sf&fFLAGGED) elt->flagged = T;
  1.1656 +  if (sf&fANSWERED) elt->answered = T;
  1.1657 +  if (sf&fDRAFT) elt->draft = T;
  1.1658 +  elt->user_flags |= uf;
  1.1659 +				/* message is in new message file */
  1.1660 +  elt->private.spare.data = LOCAL->newmsg;
  1.1661 +
  1.1662 +				/* offset to message internal header */
  1.1663 +  elt->private.special.offset = ftell (f);
  1.1664 +				/* build header for message */
  1.1665 +  fprintf (f,MSRFMT,MSGTOK,elt->private.uid,
  1.1666 +	   elt->year + BASEYEAR,elt->month,elt->day,
  1.1667 +	   elt->hours,elt->minutes,elt->seconds,
  1.1668 +	   elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes,
  1.1669 +	   elt->rfc822_size);
  1.1670 +				/* offset to header from  internal header */
  1.1671 +  elt->private.msg.header.offset = ftell (f) - elt->private.special.offset;
  1.1672 +  for (cs = 0; SIZE (msg); ) {	/* copy message */
  1.1673 +    if (elt->private.msg.header.text.size) {
  1.1674 +      if (msg->cursize)		/* blat entire chunk if have it */
  1.1675 +	for (j = msg->cursize; j; j -= k)
  1.1676 +	  if (!(k = fwrite (msg->curpos,1,j,f))) return NIL;
  1.1677 +      SETPOS (msg,GETPOS (msg) + msg->cursize);
  1.1678 +    }
  1.1679 +    else {			/* still searching for delimiter */
  1.1680 +      c = 0xff & SNX (msg);	/* get source character */
  1.1681 +      if (putc (c,f) == EOF) return NIL;
  1.1682 +      switch (cs) {		/* decide what to do based on state */
  1.1683 +      case 0:			/* previous char ordinary */
  1.1684 +	if (c == '\015') cs = 1;/* advance if CR */
  1.1685 +	break;
  1.1686 +      case 1:			/* previous CR, advance if LF */
  1.1687 +	cs = (c == '\012') ? 2 : 0;
  1.1688 +	break;
  1.1689 +      case 2:			/* previous CRLF, advance if CR */
  1.1690 +	cs = (c == '\015') ? 3 : 0;
  1.1691 +	break;
  1.1692 +      case 3:			/* previous CRLFCR, done if LF */
  1.1693 +	if (c == '\012') elt->private.msg.header.text.size =
  1.1694 +			   elt->rfc822_size - SIZE (msg);
  1.1695 +	cs = 0;			/* reset mechanism */
  1.1696 +	break;
  1.1697 +      }
  1.1698 +    }
  1.1699 +  }
  1.1700 +				/* if no delimiter, header is entire msg */
  1.1701 +  if (!elt->private.msg.header.text.size)
  1.1702 +    elt->private.msg.header.text.size = elt->rfc822_size;
  1.1703 +				/* add this message to set */
  1.1704 +  mail_append_set (set,elt->private.uid);
  1.1705 +  return LONGT;			/* success */
  1.1706 +}
  1.1707 +
  1.1708 +/* MIX mail read metadata, index, and status
  1.1709 + * Accepts: MAIL stream
  1.1710 + *	    returned index file
  1.1711 + *	    index file flags (non-NIL if want to add/remove messages)
  1.1712 + *	    status file flags (non-NIL if want to update elt->valid and old)
  1.1713 + * Returns: open status file, or NIL if failure
  1.1714 + *
  1.1715 + * Note that this routine can return an open index file even if it fails!
  1.1716 + */
  1.1717 +
  1.1718 +static char *shortmsg =
  1.1719 +  "message %lu (UID=%.08lx) truncated by %lu byte(s) (%lu < %lu)";
  1.1720 +
  1.1721 +FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags)
  1.1722 +{
  1.1723 +  int fd;
  1.1724 +  unsigned long i;
  1.1725 +  char *s,*t;
  1.1726 +  struct stat sbuf;
  1.1727 +  FILE *statf = NIL;
  1.1728 +  short metarepairneeded = 0;
  1.1729 +  short indexrepairneeded = 0;
  1.1730 +  short silent = stream->silent;
  1.1731 +  *idxf = NIL;			/* in case error */
  1.1732 +				/* readonly means no updates */
  1.1733 +  if (stream->rdonly) iflags = sflags = NIL;
  1.1734 +				/* open index file */
  1.1735 +  if ((fd = open (LOCAL->index,iflags ? O_RDWR : O_RDONLY,NIL)) < 0)
  1.1736 +    MM_LOG ("Error opening mix index file",ERROR);
  1.1737 +				/* acquire exclusive access and FILE */
  1.1738 +  else if (!flock (fd,iflags ? LOCK_EX : LOCK_SH) &&
  1.1739 +	   !(*idxf = fdopen (fd,iflags ? "r+b" : "rb"))) {
  1.1740 +    MM_LOG ("Error obtaining stream on mix index file",ERROR);
  1.1741 +    flock (fd,LOCK_UN);		/* relinquish lock */
  1.1742 +    close (fd);
  1.1743 +  }
  1.1744 +
  1.1745 +				/* slurp metadata */
  1.1746 +  else if (s = mix_meta_slurp (stream,&i)) {
  1.1747 +    unsigned long j = 0;	/* non-zero if UIDVALIDITY/UIDLAST changed */
  1.1748 +    if (i != LOCAL->metaseq) {	/* metadata changed? */
  1.1749 +      char *t,*k;
  1.1750 +      LOCAL->metaseq = i;	/* note new metadata sequence */
  1.1751 +      while (s && *s) {		/* parse entire metadata file */
  1.1752 +				/* locate end of line */
  1.1753 +	if (s = strstr (t = s,"\015\012")) {
  1.1754 +	  *s = '\0';		/* tie off line */
  1.1755 +	  s += 2;		/* skip past CRLF */
  1.1756 +	  switch (*t++) {	/* parse line */
  1.1757 +	  case 'V':		/* UIDVALIDITY */
  1.1758 +	    if (!isxdigit (*t) || !(i = strtoul (t,&t,16))) {
  1.1759 +	      MM_LOG ("Error in mix metadata file UIDVALIDITY record",ERROR);
  1.1760 +	      return NIL;	/* give up */
  1.1761 +	    }
  1.1762 +	    if (i != stream->uid_validity) j = stream->uid_validity = i;
  1.1763 +	    break;
  1.1764 +	  case 'L':		/* new UIDLAST */
  1.1765 +	    if (!isxdigit (*t)) {
  1.1766 +	      MM_LOG ("Error in mix metadata file UIDLAST record",ERROR);
  1.1767 +	      return NIL;	/* give up */
  1.1768 +	    }
  1.1769 +	    if ((i = strtoul (t,&t,16)) != stream->uid_last)
  1.1770 +	      j = stream->uid_last = i;
  1.1771 +	    break;
  1.1772 +	  case 'N':		/* new message file */
  1.1773 +	    if (!isxdigit (*t)) {
  1.1774 +	      MM_LOG ("Error in mix metadata file new msg record",ERROR);
  1.1775 +	      return NIL;	/* give up */
  1.1776 +	    }
  1.1777 +	    if ((i = strtoul (t,&t,16)) != stream->uid_last)
  1.1778 +	      LOCAL->newmsg = i;
  1.1779 +	    break;
  1.1780 +	  case 'K':		/* new keyword list */
  1.1781 +	    for (i = 0; t && *t && (i < NUSERFLAGS); ++i) {
  1.1782 +	      if (t = strchr (k = t,' ')) *t++ = '\0';
  1.1783 +				/* make sure keyword non-empty */
  1.1784 +	      if (*k && (strlen (k) <= MAXUSERFLAG)) {
  1.1785 +				/* in case value changes (shouldn't happen) */
  1.1786 +		if (stream->user_flags[i] && strcmp (stream->user_flags[i],k)){
  1.1787 +		  char tmp[MAILTMPLEN];
  1.1788 +		  sprintf (tmp,"flag rename old=%.80s new=%.80s",
  1.1789 +			   stream->user_flags[i],k);
  1.1790 +		  MM_LOG (tmp,WARN);
  1.1791 +		  fs_give ((void **) &stream->user_flags[i]);
  1.1792 +		}
  1.1793 +		if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (k);
  1.1794 +	      }
  1.1795 +	      else break;	/* empty keyword */
  1.1796 +	    }
  1.1797 +	    if ((i < NUSERFLAGS) && stream->user_flags[i]) {
  1.1798 +	      MM_LOG ("Error in mix metadata file keyword record",ERROR);
  1.1799 +	      return NIL;	/* give up */
  1.1800 +	    }
  1.1801 +	    else if (i == NUSERFLAGS) stream->kwd_create = NIL;
  1.1802 +	    break;
  1.1803 +	  }
  1.1804 +	}
  1.1805 +	if (t && *t) {		/* junk in line */
  1.1806 +	  MM_LOG ("Error in mix metadata record",ERROR);
  1.1807 +	  return NIL;		/* give up */
  1.1808 +	}
  1.1809 +      }
  1.1810 +    }
  1.1811 +
  1.1812 +				/* get sequence */
  1.1813 +    if (!(i = mix_read_sequence (*idxf)) || (i < LOCAL->indexseq)) {
  1.1814 +      MM_LOG ("Error in mix index file sequence record",ERROR);
  1.1815 +      return NIL;		/* give up */
  1.1816 +    }
  1.1817 +				/* sequence changed from last time? */
  1.1818 +    else if (j || (i > LOCAL->indexseq)) {
  1.1819 +      unsigned long uid,nmsgs,curfile,curfilesize,curpos;
  1.1820 +      char *t,*msg,tmp[MAILTMPLEN];
  1.1821 +				/* start with no messages */
  1.1822 +      curfile = curfilesize = curpos = nmsgs = 0;
  1.1823 +				/* update sequence iff expunging OK */
  1.1824 +      if (LOCAL->expok) LOCAL->indexseq = i;
  1.1825 +				/* get first elt */
  1.1826 +      while ((s = mix_read_record (*idxf,LOCAL->buf,LOCAL->buflen,"index")) &&
  1.1827 +	     *s)
  1.1828 +	switch (*s) {
  1.1829 +	case ':':		/* message record */
  1.1830 +	  if (!(isxdigit (*++s) && (uid = strtoul (s,&t,16)))) msg = "UID";
  1.1831 +	  else if (!((*t++ == ':') && isdigit (*t) && isdigit (t[1]) &&
  1.1832 +		     isdigit (t[2]) && isdigit (t[3]) && isdigit (t[4]) &&
  1.1833 +		     isdigit (t[5]) && isdigit (t[6]) && isdigit (t[7]) &&
  1.1834 +		     isdigit (t[8]) && isdigit (t[9]) && isdigit (t[10]) &&
  1.1835 +		     isdigit (t[11]) && isdigit (t[12]) && isdigit (t[13]) &&
  1.1836 +		     ((t[14] == '+') || (t[14] == '-')) && 
  1.1837 +		     isdigit (t[15]) && isdigit (t[16]) && isdigit (t[17]) &&
  1.1838 +		     isdigit (t[18]))) msg = "internaldate";
  1.1839 +	  else if ((*(s = t+19) != ':') || !isxdigit (*++s)) msg = "size";
  1.1840 +	  else {
  1.1841 +	    unsigned int y = (((*t - '0') * 1000) + ((t[1] - '0') * 100) +
  1.1842 +			      ((t[2] - '0') * 10) + t[3] - '0') - BASEYEAR;
  1.1843 +	    unsigned int m = ((t[4] - '0') * 10) + t[5] - '0';
  1.1844 +	    unsigned int d = ((t[6] - '0') * 10) + t[7] - '0';
  1.1845 +	    unsigned int hh = ((t[8] - '0') * 10) + t[9] - '0';
  1.1846 +	    unsigned int mm = ((t[10] - '0') * 10) + t[11] - '0';
  1.1847 +	    unsigned int ss = ((t[12] - '0') * 10) + t[13] - '0';
  1.1848 +	    unsigned int z = (t[14] == '-') ? 1 : 0;
  1.1849 +	    unsigned int zh = ((t[15] - '0') * 10) + t[16] - '0';
  1.1850 +	    unsigned int zm = ((t[17] - '0') * 10) + t[18] - '0';
  1.1851 +	    unsigned long size = strtoul (s,&s,16);
  1.1852 +	    if ((*s++ == ':') && isxdigit (*s)) {
  1.1853 +	      unsigned long file = strtoul (s,&s,16);
  1.1854 +	      if ((*s++ == ':') && isxdigit (*s)) {
  1.1855 +		unsigned long pos = strtoul (s,&s,16);
  1.1856 +		if ((*s++ == ':') && isxdigit (*s)) {
  1.1857 +		  unsigned long hpos = strtoul (s,&s,16);
  1.1858 +		  if ((*s++ == ':') && isxdigit (*s)) {
  1.1859 +		    unsigned long hsiz = strtoul (s,&s,16);
  1.1860 +		    if (uid > stream->uid_last) {
  1.1861 +		      sprintf (tmp,"mix index invalid UID (%08lx < %08lx)",
  1.1862 +			       uid,stream->uid_last);
  1.1863 +		      if (stream->rdonly) {
  1.1864 +			MM_LOG (tmp,ERROR);
  1.1865 +			return NIL;
  1.1866 +		      }
  1.1867 +		      strcat (tmp,", repaired");
  1.1868 +		      MM_LOG (tmp,WARN);
  1.1869 +		      stream->uid_last = uid;
  1.1870 +		      metarepairneeded = T;
  1.1871 +		    }
  1.1872 +
  1.1873 +				/* ignore expansion values */
  1.1874 +		    if (*s++ == ':') {
  1.1875 +		      MESSAGECACHE *elt;
  1.1876 +		      ++nmsgs;	/* this is another mesage */
  1.1877 +				/* within current known range of messages? */
  1.1878 +		      while (nmsgs <= stream->nmsgs) {
  1.1879 +				/* yes, get corresponding elt */
  1.1880 +			elt = mail_elt (stream,nmsgs);
  1.1881 +				/* existing message with matching data? */
  1.1882 +			if (uid == elt->private.uid) {
  1.1883 +				/* beware of Dracula's resurrection */
  1.1884 +			  if (elt->private.ghost) {
  1.1885 +			    sprintf (tmp,"mix index data unexpunged UID: %lx",
  1.1886 +				     uid);
  1.1887 +			    MM_LOG (tmp,ERROR);
  1.1888 +			    return NIL;
  1.1889 +			  }
  1.1890 +				/* also of static data changing */
  1.1891 +			  if ((size != elt->rfc822_size) ||
  1.1892 +			      (file != elt->private.spare.data) ||
  1.1893 +			      (pos != elt->private.special.offset) ||
  1.1894 +			      (hpos != elt->private.msg.header.offset) ||
  1.1895 +			      (hsiz != elt->private.msg.header.text.size) ||
  1.1896 +			      (y != elt->year) || (m != elt->month) ||
  1.1897 +			      (d != elt->day) || (hh != elt->hours) ||
  1.1898 +			      (mm != elt->minutes) || (ss != elt->seconds) ||
  1.1899 +			      (z != elt->zoccident) || (zh != elt->zhours) ||
  1.1900 +			      (zm != elt->zminutes)) {
  1.1901 +			    sprintf (tmp,"mix index data mismatch: %lx",uid);
  1.1902 +			    MM_LOG (tmp,ERROR);
  1.1903 +			    return NIL;
  1.1904 +			  }
  1.1905 +			  break;
  1.1906 +			}
  1.1907 +				/* existing msg with lower UID is expunged */
  1.1908 +			else if (uid > elt->private.uid) {
  1.1909 +			  if (LOCAL->expok) mail_expunged (stream,nmsgs);
  1.1910 +			  else {/* message expunged, but not yet for us */
  1.1911 +			    ++nmsgs;
  1.1912 +			    elt->private.ghost = T;
  1.1913 +			  }
  1.1914 +			}
  1.1915 +			else {	/* unexpected message record */
  1.1916 +			  sprintf (tmp,"mix index UID mismatch (%lx < %lx)",
  1.1917 +				   uid,elt->private.uid);
  1.1918 +			  MM_LOG (tmp,ERROR);
  1.1919 +			  return NIL;
  1.1920 +			}
  1.1921 +		      }
  1.1922 +
  1.1923 +				/* time to create a new message? */
  1.1924 +		      if (nmsgs > stream->nmsgs) {
  1.1925 +				/* defer announcing until later */
  1.1926 +			stream->silent = T;
  1.1927 +			mail_exists (stream,nmsgs);
  1.1928 +			stream->silent = silent;
  1.1929 +			(elt = mail_elt (stream,nmsgs))->recent = T;
  1.1930 +			elt->private.uid = uid; elt->rfc822_size = size;
  1.1931 +			elt->private.spare.data = file;
  1.1932 +			elt->private.special.offset = pos;
  1.1933 +			elt->private.msg.header.offset = hpos;
  1.1934 +			elt->private.msg.header.text.size = hsiz;
  1.1935 +			elt->year = y; elt->month = m; elt->day = d;
  1.1936 +			elt->hours = hh; elt->minutes = mm;
  1.1937 +			elt->seconds = ss; elt->zoccident = z;
  1.1938 +			elt->zhours = zh; elt->zminutes = zm;
  1.1939 +				/* message in same file? */
  1.1940 +			if (curfile == file) {
  1.1941 +			  if (pos < curpos) {
  1.1942 +			    MESSAGECACHE *plt = mail_elt (stream,elt->msgno-1);
  1.1943 +				/* uh-oh, calculate delta? */
  1.1944 +			    i = curpos - pos;
  1.1945 +			    sprintf (tmp,shortmsg,plt->msgno,plt->private.uid,
  1.1946 +				     i,pos,curpos);
  1.1947 +				/* possible to fix? */
  1.1948 +			    if (!stream->rdonly && LOCAL->expok &&
  1.1949 +				(i < plt->rfc822_size)) {
  1.1950 +			      plt->rfc822_size -= i;
  1.1951 +			      if (plt->rfc822_size <
  1.1952 +				  plt->private.msg.header.text.size)
  1.1953 +				plt->private.msg.header.text.size =
  1.1954 +				  plt->rfc822_size;
  1.1955 +			      strcat (tmp,", repaired");
  1.1956 +			      indexrepairneeded = T;
  1.1957 +			    }
  1.1958 +			    MM_LOG (tmp,WARN);
  1.1959 +			  }
  1.1960 +			}
  1.1961 +			else {	/* new file, restart */
  1.1962 +			  if (stat (mix_file_data (LOCAL->buf,stream->mailbox,
  1.1963 +						   curfile = file),&sbuf)) {
  1.1964 +			    sprintf (tmp,"Missing mix data file: %.500s",
  1.1965 +				     LOCAL->buf);
  1.1966 +			    MM_LOG (tmp,ERROR);
  1.1967 +			    return NIL;
  1.1968 +			  }
  1.1969 +			  curfile = file;
  1.1970 +			  curfilesize = sbuf.st_size;
  1.1971 +			}
  1.1972 +
  1.1973 +				/* position of message in file */
  1.1974 +			curpos = pos + elt->private.msg.header.offset +
  1.1975 +			  elt->rfc822_size;
  1.1976 +				/* short file? */
  1.1977 +			if (curfilesize < curpos) {
  1.1978 +				/* uh-oh, calculate delta? */
  1.1979 +			    i = curpos - curfilesize;
  1.1980 +			    sprintf (tmp,shortmsg,elt->msgno,elt->private.uid,
  1.1981 +				     i,curfilesize,curpos);
  1.1982 +				/* possible to fix? */
  1.1983 +			    if (!stream->rdonly && LOCAL->expok &&
  1.1984 +				(i < elt->rfc822_size)) {
  1.1985 +			      elt->rfc822_size -= i;
  1.1986 +			      if (elt->rfc822_size <
  1.1987 +				  elt->private.msg.header.text.size)
  1.1988 +				elt->private.msg.header.text.size =
  1.1989 +				  elt->rfc822_size;
  1.1990 +			      strcat (tmp,", repaired");
  1.1991 +			      indexrepairneeded = T;
  1.1992 +			    }
  1.1993 +			    MM_LOG (tmp,WARN);
  1.1994 +			}
  1.1995 +		      }
  1.1996 +		      break;
  1.1997 +		    }
  1.1998 +		    else msg = "expansion";
  1.1999 +		  }
  1.2000 +		  else msg = "header size";
  1.2001 +		}
  1.2002 +		else msg = "header position";
  1.2003 +	      }
  1.2004 +	      else msg = "message position";
  1.2005 +	    }
  1.2006 +	    else msg = "file#";
  1.2007 +	  }
  1.2008 +	  sprintf (tmp,"Error in %s in mix index file: %.500s",msg,s);
  1.2009 +	  MM_LOG (tmp,ERROR);
  1.2010 +	  return NIL;
  1.2011 +	default:
  1.2012 +	  sprintf (tmp,"Unknown record in mix index file: %.500s",s);
  1.2013 +	  MM_LOG (tmp,ERROR);
  1.2014 +	  return NIL;
  1.2015 +	}
  1.2016 +      if (!s) return NIL;	/* barfage from mix_read_record() */
  1.2017 +				/* expunge trailing messages not in index */
  1.2018 +      if (LOCAL->expok) while (nmsgs < stream->nmsgs)
  1.2019 +	mail_expunged (stream,stream->nmsgs);
  1.2020 +    }
  1.2021 +
  1.2022 +				/* repair metadata and index if needed */
  1.2023 +    if ((metarepairneeded ? mix_meta_update (stream) : T) &&
  1.2024 +	(indexrepairneeded ? mix_index_update (stream,*idxf,NIL) : T)) {
  1.2025 +      MESSAGECACHE *elt;
  1.2026 +      int fd;
  1.2027 +      unsigned long uid,uf,sf,mod;
  1.2028 +      char *s;
  1.2029 +      int updatep = NIL;
  1.2030 +				/* open status file */
  1.2031 +      if ((fd = open (LOCAL->status,
  1.2032 +		      stream->rdonly ? O_RDONLY : O_RDWR,NIL)) < 0)
  1.2033 +	MM_LOG ("Error opening mix status file",ERROR);
  1.2034 +				/* acquire exclusive access and FILE */
  1.2035 +      else if (!flock (fd,stream->rdonly ? LOCK_SH : LOCK_EX) &&
  1.2036 +	       !(statf = fdopen (fd,stream->rdonly ? "rb" : "r+b"))) {
  1.2037 +	MM_LOG ("Error obtaining stream on mix status file",ERROR);
  1.2038 +	flock (fd,LOCK_UN);	/* relinquish lock */
  1.2039 +	close (fd);
  1.2040 +      }
  1.2041 +				/* get sequence */
  1.2042 +      else if (!(i = mix_read_sequence (statf)) ||
  1.2043 +	       ((i < LOCAL->statusseq) && stream->nmsgs && (i != 1))) {
  1.2044 +	sprintf (LOCAL->buf,
  1.2045 +		 "Error in mix status sequence record, i=%lx, seq=%lx",
  1.2046 +		 i,LOCAL->statusseq);
  1.2047 +	MM_LOG (LOCAL->buf,ERROR);
  1.2048 +      }
  1.2049 +				/* sequence changed from last time? */
  1.2050 +      else if (i != LOCAL->statusseq) {
  1.2051 +				/* update sequence, get first elt */
  1.2052 +	if (i > LOCAL->statusseq) LOCAL->statusseq = i;
  1.2053 +	if (stream->nmsgs) {
  1.2054 +	  elt = mail_elt (stream,i = 1);
  1.2055 +
  1.2056 +				/* read message records */
  1.2057 +	  while ((t = s = mix_read_record (statf,LOCAL->buf,LOCAL->buflen,
  1.2058 +					   "status")) && *s && (*s++ == ':') &&
  1.2059 +		 isxdigit (*s)) {
  1.2060 +	    uid = strtoul (s,&s,16);
  1.2061 +	    if ((*s++ == ':') && isxdigit (*s)) {
  1.2062 +	      uf = strtoul (s,&s,16);
  1.2063 +	      if ((*s++ == ':') && isxdigit (*s)) {
  1.2064 +		sf = strtoul (s,&s,16);
  1.2065 +		if ((*s++ == ':') && isxdigit (*s)) {
  1.2066 +		  mod = strtoul (s,&s,16);
  1.2067 +				/* ignore expansion values */
  1.2068 +		  if (*s++ == ':') {
  1.2069 +				/* need to move ahead to next elt? */
  1.2070 +		    while ((uid > elt->private.uid) && (i < stream->nmsgs))
  1.2071 +		      elt = mail_elt (stream,++i);
  1.2072 +				/* update elt if altered */
  1.2073 +		    if ((uid == elt->private.uid) &&
  1.2074 +			(!elt->valid || (mod != elt->private.mod))) {
  1.2075 +		      elt->user_flags = uf;
  1.2076 +		      elt->private.mod = mod;
  1.2077 +		      elt->seen = (sf & fSEEN) ? T : NIL;
  1.2078 +		      elt->deleted = (sf & fDELETED) ? T : NIL;
  1.2079 +		      elt->flagged = (sf & fFLAGGED) ? T : NIL;
  1.2080 +		      elt->answered = (sf & fANSWERED) ? T : NIL;
  1.2081 +		      elt->draft = (sf & fDRAFT) ? T : NIL;
  1.2082 +				/* announce if altered existing message */
  1.2083 +		      if (elt->valid) MM_FLAGS (stream,elt->msgno);
  1.2084 +				/* first time, is old message? */
  1.2085 +		      else if (sf & fOLD) {
  1.2086 +				/* yes, clear recent and set valid */
  1.2087 +			elt->recent = NIL;
  1.2088 +			elt->valid = T;
  1.2089 +		      }
  1.2090 +				/* recent, allowed to update its status? */
  1.2091 +		      else if (sflags) {
  1.2092 +				/* yes, set valid and check in status */
  1.2093 +			elt->valid = T;
  1.2094 +			elt->private.mod = mix_modseq (elt->private.mod);
  1.2095 +			updatep = T;
  1.2096 +		      }
  1.2097 +		      /* leave valid unset and recent if sflags not set */
  1.2098 +		    }
  1.2099 +		    continue;	/* everything looks good */
  1.2100 +		  }
  1.2101 +		}
  1.2102 +	      }
  1.2103 +	    }
  1.2104 +	    break;		/* error somewhere */
  1.2105 +	  }
  1.2106 +
  1.2107 +	  if (t && *t) {	/* non-null means bogus record */
  1.2108 +	    char msg[MAILTMPLEN];
  1.2109 +	    sprintf (msg,"Error in mix status file message record%s: %.80s",
  1.2110 +		     stream->rdonly ? "" : ", fixing",t);
  1.2111 +	    MM_LOG (msg,WARN);
  1.2112 +				/* update it if not readonly */
  1.2113 +	    if (!stream->rdonly) updatep = T;
  1.2114 +	  }
  1.2115 +	  if (updatep) {		/* need to update? */
  1.2116 +	    LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
  1.2117 +	    mix_status_update (stream,statf,LONGT);
  1.2118 +	  }
  1.2119 +	}
  1.2120 +      }
  1.2121 +    }
  1.2122 +  }
  1.2123 +  if (statf) {			/* still happy? */
  1.2124 +    unsigned long j;
  1.2125 +    stream->silent = silent;	/* now notify upper level */
  1.2126 +    mail_exists (stream,stream->nmsgs);
  1.2127 +    for (i = 1, j = 0; i <= stream->nmsgs; ++i)
  1.2128 +      if (mail_elt (stream,i)->recent) ++j;
  1.2129 +    mail_recent (stream,j);
  1.2130 +  }
  1.2131 +  return statf;
  1.2132 +}
  1.2133 +
  1.2134 +/* MIX metadata file routines */
  1.2135 +
  1.2136 +/* MIX read metadata
  1.2137 + * Accepts: MAIL stream
  1.2138 + *	    return pointer for modseq
  1.2139 + * Returns: pointer to metadata after modseq or NIL if failure
  1.2140 + */
  1.2141 +
  1.2142 +char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq)
  1.2143 +{
  1.2144 +  struct stat sbuf;
  1.2145 +  char *s;
  1.2146 +  char *ret = NIL;
  1.2147 +  if (fstat (LOCAL->mfd,&sbuf))
  1.2148 +    MM_LOG ("Error obtaining size of mix metatdata file",ERROR);
  1.2149 +  if (sbuf.st_size > LOCAL->buflen) {
  1.2150 +				/* should be just a few dozen bytes */
  1.2151 +    if (sbuf.st_size > METAMAX) fatal ("absurd mix metadata file size");
  1.2152 +    fs_give ((void **) &LOCAL->buf);
  1.2153 +    LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size) + 1);
  1.2154 +  }
  1.2155 +				/* read current metadata file */
  1.2156 +  LOCAL->buf[sbuf.st_size] = '\0';
  1.2157 +  if (lseek (LOCAL->mfd,0,L_SET) ||
  1.2158 +      (read (LOCAL->mfd,s = LOCAL->buf,sbuf.st_size) != sbuf.st_size))
  1.2159 +    MM_LOG ("Error reading mix metadata file",ERROR);
  1.2160 +  else if ((*s != 'S') || !isxdigit (s[1]) ||
  1.2161 +	   ((*seq = strtoul (s+1,&s,16)) < LOCAL->metaseq) ||
  1.2162 +	   (*s++ != '\015') || (*s++ != '\012'))
  1.2163 +    MM_LOG ("Error in mix metadata file sequence record",ERROR);
  1.2164 +  else ret = s;
  1.2165 +  return ret;
  1.2166 +}
  1.2167 +
  1.2168 +/* MIX update metadata
  1.2169 + * Accepts: MAIL stream
  1.2170 + * Returns: T on success, NIL if error
  1.2171 + *
  1.2172 + * Index MUST be locked!!
  1.2173 + */
  1.2174 +
  1.2175 +long mix_meta_update (MAILSTREAM *stream)
  1.2176 +{
  1.2177 +  long ret;
  1.2178 +				/* do nothing if stream readonly */
  1.2179 +  if (stream->rdonly) ret = LONGT;
  1.2180 +  else {
  1.2181 +    unsigned char c,*s,*ss,*t;
  1.2182 +    unsigned long i;
  1.2183 +    /* The worst-case metadata is limited to:
  1.2184 +     *    4 * (1 + 8 + 2) + (NUSERFLAGS * (MAXUSERFLAG + 1))
  1.2185 +     * which comes out to 1994 octets.  This is much smaller than the normal
  1.2186 +     * CHUNKSIZE definition of 64K, and CHUNKSIZE is the smallest size of
  1.2187 +     * LOCAL->buf.
  1.2188 +     *
  1.2189 +     * If more stuff gets added to the metadata, or if you change the value
  1.2190 +     * of NUSERFLAGS, MAXUSERFLAG or CHUNKSIZE, be sure to recalculate the
  1.2191 +     * above assertation.
  1.2192 +     */
  1.2193 +    sprintf (LOCAL->buf,SEQFMT,LOCAL->metaseq = mix_modseq (LOCAL->metaseq));
  1.2194 +    sprintf (LOCAL->buf + strlen (LOCAL->buf),MTAFMT,
  1.2195 +	     stream->uid_validity,stream->uid_last,LOCAL->newmsg);
  1.2196 +    for (i = 0, c = 'K', s = ss = LOCAL->buf + strlen (LOCAL->buf);
  1.2197 +	 (i < NUSERFLAGS) && (t = stream->user_flags[i]); ++i) {
  1.2198 +      if (!*t) fatal ("impossible empty keyword");
  1.2199 +      *s++ = c;			/* write delimiter */
  1.2200 +      while (*t) *s++ = *t++;	/* write keyword */
  1.2201 +      c = ' ';			/* delimiter is now space */
  1.2202 +    }
  1.2203 +    if (s != ss) {		/* tie off keywords line */
  1.2204 +      *s++ = '\015'; *s++ = '\012';
  1.2205 +    }
  1.2206 +				/* calculate length of metadata */
  1.2207 +    if ((i = s - LOCAL->buf) > LOCAL->buflen)
  1.2208 +      fatal ("impossible buffer overflow");
  1.2209 +    lseek (LOCAL->mfd,0,L_SET);	/* rewind file */
  1.2210 +				/* write new metadata */
  1.2211 +    ret = (write (LOCAL->mfd,LOCAL->buf,i) == i) ? LONGT : NIL;
  1.2212 +    ftruncate (LOCAL->mfd,i);	/* and tie off at that point */
  1.2213 +  }
  1.2214 +  return ret;
  1.2215 +}
  1.2216 +
  1.2217 +/* MIX index file routines */
  1.2218 +
  1.2219 +
  1.2220 +/* MIX update index
  1.2221 + * Accepts: MAIL stream
  1.2222 + *	    open FILE
  1.2223 + *	    expansion check flag
  1.2224 + * Returns: T on success, NIL if error
  1.2225 + */
  1.2226 +
  1.2227 +long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag)
  1.2228 +{
  1.2229 +  unsigned long i;
  1.2230 +  long ret = LONGT;
  1.2231 +  if (!stream->rdonly) {	/* do nothing if stream readonly */
  1.2232 +    if (flag) {			/* need to do expansion check? */
  1.2233 +      char tmp[MAILTMPLEN];
  1.2234 +      size_t size;
  1.2235 +      struct stat sbuf;
  1.2236 +				/* calculate file size we need */
  1.2237 +      for (i = 1, size = 0; i <= stream->nmsgs; ++i)
  1.2238 +	if (!mail_elt (stream,i)->private.ghost) ++size;
  1.2239 +      if (size) {		/* Winston Smith's first dairy entry */
  1.2240 +	sprintf (tmp,IXRFMT,0,14,4,4,13,0,0,'+',0,0,0,0,0,0,0);
  1.2241 +	size *= strlen (tmp);
  1.2242 +      }
  1.2243 +				/* calculate file size we need */
  1.2244 +      sprintf (tmp,SEQFMT,LOCAL->indexseq);
  1.2245 +      size += strlen (tmp);
  1.2246 +				/* get current file size */
  1.2247 +      if (fstat (fileno (idxf),&sbuf)) {
  1.2248 +	MM_LOG ("Error getting size of mix index file",ERROR);
  1.2249 +	ret = NIL;
  1.2250 +      }
  1.2251 +				/* need to write additional space? */
  1.2252 +      else if (sbuf.st_size < size) {
  1.2253 +	void *buf = fs_get (size -= sbuf.st_size);
  1.2254 +	memset (buf,0,size);
  1.2255 +	if (fseek (idxf,0,SEEK_END) || (fwrite (buf,1,size,idxf) != size) ||
  1.2256 +	    fflush (idxf)) {
  1.2257 +	  fseek (idxf,sbuf.st_size,SEEK_SET);
  1.2258 +	  ftruncate (fileno (idxf),sbuf.st_size);
  1.2259 +	  MM_LOG ("Error extending mix index file",ERROR);
  1.2260 +	  ret = NIL;
  1.2261 +	}
  1.2262 +	fs_give ((void **) &buf);
  1.2263 +      }
  1.2264 +    }
  1.2265 +
  1.2266 +    if (ret) {			/* if still good to go */
  1.2267 +      rewind (idxf);		/* let's start at the very beginning */
  1.2268 +				/* write modseq first */
  1.2269 +      fprintf (idxf,SEQFMT,LOCAL->indexseq);
  1.2270 +				/* then write all messages */
  1.2271 +      for (i = 1; ret && (i <= stream->nmsgs); i++) {
  1.2272 +	MESSAGECACHE *elt = mail_elt (stream,i);
  1.2273 +	if (!elt->private.ghost)/* only write living messages */
  1.2274 +	  fprintf (idxf,IXRFMT,elt->private.uid,
  1.2275 +		   elt->year + BASEYEAR,elt->month,elt->day,
  1.2276 +		   elt->hours,elt->minutes,elt->seconds,
  1.2277 +		   elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes,
  1.2278 +		   elt->rfc822_size,elt->private.spare.data,
  1.2279 +		   elt->private.special.offset,
  1.2280 +		   elt->private.msg.header.offset,
  1.2281 +		   elt->private.msg.header.text.size);
  1.2282 +	if (ferror (idxf)) {
  1.2283 +	  MM_LOG ("Error updating mix index file",ERROR);
  1.2284 +	  ret = NIL;
  1.2285 +	}
  1.2286 +      }
  1.2287 +      if (fflush (idxf)) {
  1.2288 +	MM_LOG ("Error flushing mix index file",ERROR);
  1.2289 +	ret = NIL;
  1.2290 +      }
  1.2291 +      if (ret) ftruncate (fileno (idxf),ftell (idxf));
  1.2292 +    }
  1.2293 +  }
  1.2294 +  return ret;
  1.2295 +}
  1.2296 +
  1.2297 +/* MIX status file routines */
  1.2298 +
  1.2299 +
  1.2300 +/* MIX update status
  1.2301 + * Accepts: MAIL stream
  1.2302 + *	    pointer to open FILE
  1.2303 + *	    expansion check flag
  1.2304 + * Returns: T on success, NIL if error
  1.2305 + */
  1.2306 +
  1.2307 +long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag)
  1.2308 +{
  1.2309 +  unsigned long i;
  1.2310 +  char tmp[MAILTMPLEN];
  1.2311 +  long ret = LONGT;
  1.2312 +  if (!stream->rdonly) {	/* do nothing if stream readonly */
  1.2313 +    if (flag) {			/* need to do expansion check? */
  1.2314 +      char tmp[MAILTMPLEN];
  1.2315 +      size_t size;
  1.2316 +      struct stat sbuf;
  1.2317 +				/* calculate file size we need */
  1.2318 +      for (i = 1, size = 0; i <= stream->nmsgs; ++i)
  1.2319 +	if (!mail_elt (stream,i)->private.ghost) ++size;
  1.2320 +      if (size) {		/* number of living messages */
  1.2321 +	sprintf (tmp,STRFMT,0,0,0,0);
  1.2322 +	size *= strlen (tmp);
  1.2323 +      }
  1.2324 +      sprintf (tmp,SEQFMT,LOCAL->statusseq);
  1.2325 +      size += strlen (tmp);
  1.2326 +				/* get current file size */
  1.2327 +      if (fstat (fileno (statf),&sbuf)) {
  1.2328 +	MM_LOG ("Error getting size of mix status file",ERROR);
  1.2329 +	ret = NIL;
  1.2330 +      }
  1.2331 +				/* need to write additional space? */
  1.2332 +      else if (sbuf.st_size < size) {
  1.2333 +	void *buf = fs_get (size -= sbuf.st_size);
  1.2334 +	memset (buf,0,size);
  1.2335 +	if (fseek (statf,0,SEEK_END) || (fwrite (buf,1,size,statf) != size) ||
  1.2336 +	    fflush (statf)) {
  1.2337 +	  fseek (statf,sbuf.st_size,SEEK_SET);
  1.2338 +	  ftruncate (fileno (statf),sbuf.st_size);
  1.2339 +	  MM_LOG ("Error extending mix status file",ERROR);
  1.2340 +	  ret = NIL;
  1.2341 +	}
  1.2342 +	fs_give ((void **) &buf);
  1.2343 +      }
  1.2344 +    }
  1.2345 +
  1.2346 +    if (ret) {			/* if still good to go */
  1.2347 +      rewind (statf);		/* let's start at the very beginning */
  1.2348 +				/* write sequence */
  1.2349 +      fprintf (statf,SEQFMT,LOCAL->statusseq);
  1.2350 +				/* write message status records */
  1.2351 +      for (i = 1; ret && (i <= stream->nmsgs); ++i) {
  1.2352 +	MESSAGECACHE *elt = mail_elt (stream,i);
  1.2353 +				/* make sure all messages have a modseq */
  1.2354 +	if (!elt->private.mod) elt->private.mod = LOCAL->statusseq;
  1.2355 +	if (!elt->private.ghost)/* only write living messages */
  1.2356 +	  fprintf (statf,STRFMT,elt->private.uid,elt->user_flags,
  1.2357 +		   (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
  1.2358 +		   (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
  1.2359 +		   (fDRAFT * elt->draft) + (elt->valid ? fOLD : NIL),
  1.2360 +		   elt->private.mod);
  1.2361 +	if (ferror (statf)) {
  1.2362 +	  sprintf (tmp,"Error updating mix status file: %.80s",
  1.2363 +		   strerror (errno));
  1.2364 +	  MM_LOG (tmp,ERROR);
  1.2365 +	  ret = NIL;
  1.2366 +	}
  1.2367 +      }
  1.2368 +      if (ret && fflush (statf)) {
  1.2369 +	MM_LOG ("Error flushing mix status file",ERROR);
  1.2370 +	ret = NIL;
  1.2371 +      }
  1.2372 +      if (ret) ftruncate (fileno (statf),ftell (statf));
  1.2373 +    }
  1.2374 +  }
  1.2375 +  return ret;
  1.2376 +}
  1.2377 +
  1.2378 +/* MIX data file routines */
  1.2379 +
  1.2380 +
  1.2381 +/* MIX open data file
  1.2382 + * Accepts: MAIL stream
  1.2383 + *	    pointer to returned fd if success
  1.2384 + *	    pointer to returned size if success
  1.2385 + *	    size of new data to be added
  1.2386 + * Returns: open FILE, or NIL if failure
  1.2387 + *
  1.2388 + * The curend test assumes that the last message of the mailbox is the furthest
  1.2389 + * point that the current data file extends, and thus that is all that needs to
  1.2390 + * be tested for short file prevention.
  1.2391 + */
  1.2392 +
  1.2393 +FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size,
  1.2394 +		     unsigned long newsize)
  1.2395 +{
  1.2396 +  FILE *msgf = NIL;
  1.2397 +  struct stat sbuf;
  1.2398 +  MESSAGECACHE *elt = stream->nmsgs ? mail_elt (stream,stream->nmsgs) : NIL;
  1.2399 +  unsigned long curend = (elt && (elt->private.spare.data == LOCAL->newmsg)) ?
  1.2400 +    elt->private.special.offset + elt->private.msg.header.offset +
  1.2401 +    elt->rfc822_size : 0;
  1.2402 +				/* allow create if curend 0 */
  1.2403 +  if ((*fd = open (mix_file_data (LOCAL->buf,stream->mailbox,LOCAL->newmsg),
  1.2404 +		   O_RDWR | (curend ? NIL : O_CREAT),NIL)) >= 0) {
  1.2405 +    fstat (*fd,&sbuf);		/* get current file size */
  1.2406 +				/* can we use this file? */
  1.2407 +    if ((curend <= sbuf.st_size) &&
  1.2408 +	(!sbuf.st_size || ((sbuf.st_size + newsize) <= MIXDATAROLL)))
  1.2409 +      *size = sbuf.st_size;	/* yes, return current size */
  1.2410 +    else {			/* short file or becoming too long */
  1.2411 +      if (curend > sbuf.st_size) {
  1.2412 +	char tmp[MAILTMPLEN];
  1.2413 +	sprintf (tmp,"short mix message file %.08lx (%ld > %ld), rolling",
  1.2414 +		 LOCAL->newmsg,curend,sbuf.st_size);
  1.2415 +	MM_LOG (tmp,WARN);	/* shouldn't happen */
  1.2416 +      }
  1.2417 +      close (*fd);		/* roll to a new file */
  1.2418 +      while ((*fd = open (mix_file_data
  1.2419 +			  (LOCAL->buf,stream->mailbox,
  1.2420 +			   LOCAL->newmsg = mix_modseq (LOCAL->newmsg)),
  1.2421 +			  O_RDWR | O_CREAT | O_EXCL,sbuf.st_mode)) < 0);
  1.2422 +      *size = 0;		/* brand new file */
  1.2423 +      fchmod (*fd,sbuf.st_mode);/* with same mode as previous file */
  1.2424 +    }
  1.2425 +  }
  1.2426 +  if (*fd >= 0) {		/* have a data file? */
  1.2427 +				/* yes, get stdio and set position */
  1.2428 +    if (msgf = fdopen (*fd,"r+b")) fseek (msgf,*size,SEEK_SET);
  1.2429 +    else close (*fd);		/* fdopen() failed? */
  1.2430 +  }
  1.2431 +  return msgf;			/* return results */
  1.2432 +}
  1.2433 +
  1.2434 +/* MIX open sortcache
  1.2435 + * Accepts: MAIL stream
  1.2436 + * Returns: open FILE, or NIL if failure or could only get readonly sortcache
  1.2437 + */
  1.2438 +
  1.2439 +FILE *mix_sortcache_open (MAILSTREAM *stream)
  1.2440 +{
  1.2441 +  int fd,refwd;
  1.2442 +  unsigned long i,uid,sentdate,fromlen,tolen,cclen,subjlen,msgidlen,reflen;
  1.2443 +  char *s,*t,*msg,tmp[MAILTMPLEN];
  1.2444 +  MESSAGECACHE *elt;
  1.2445 +  SORTCACHE *sc;
  1.2446 +  STRINGLIST *sl;
  1.2447 +  struct stat sbuf;
  1.2448 +  int rdonly = NIL;
  1.2449 +  FILE *srtcf = NIL;
  1.2450 +  mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1.2451 +  fstat (LOCAL->mfd,&sbuf);
  1.2452 +  if (!stream->nmsgs);		/* do nothing if mailbox empty */
  1.2453 +				/* open sortcache file */
  1.2454 +  else if (((fd = open (LOCAL->sortcache,O_RDWR|O_CREAT,sbuf.st_mode)) < 0) &&
  1.2455 +	   !(rdonly = ((fd = open (LOCAL->sortcache,O_RDONLY,NIL)) >= 0)))
  1.2456 +    MM_LOG ("Error opening mix sortcache file",WARN);
  1.2457 +				/* acquire lock and FILE */
  1.2458 +  else if (!flock (fd,rdonly ? LOCK_SH : LOCK_EX) &&
  1.2459 +	   !(srtcf = fdopen (fd,rdonly ? "rb" : "r+b"))) {
  1.2460 +    MM_LOG ("Error obtaining stream on mix sortcache file",WARN);
  1.2461 +    flock (fd,LOCK_UN);		/* relinquish lock */
  1.2462 +    close (fd);
  1.2463 +  }
  1.2464 +  else if (!(i = mix_read_sequence (srtcf)) || (i < LOCAL->sortcacheseq))
  1.2465 +    MM_LOG ("Error in mix sortcache file sequence record",WARN);
  1.2466 +				/* sequence changed from last time? */
  1.2467 +  else if (i > LOCAL->sortcacheseq) {
  1.2468 +    LOCAL->sortcacheseq = i;	/* update sequence */
  1.2469 +    while ((s = t = mix_read_record (srtcf,LOCAL->buf,LOCAL->buflen,
  1.2470 +				     "sortcache")) && *s &&
  1.2471 +	   (msg = "uid") && (*s++ == ':') && isxdigit (*s)) {
  1.2472 +      uid = strtoul (s,&s,16);
  1.2473 +      if ((*s++ == ':') && isxdigit (*s)) {
  1.2474 +	sentdate = strtoul (s,&s,16);
  1.2475 +	if ((*s++ == ':') && isxdigit (*s)) {
  1.2476 +	  fromlen = strtoul (s,&s,16);
  1.2477 +	  if ((*s++ == ':') && isxdigit (*s)) {
  1.2478 +	    tolen = strtoul (s,&s,16);
  1.2479 +	    if ((*s++ == ':') && isxdigit (*s)) {
  1.2480 +	      cclen = strtoul (s,&s,16);
  1.2481 +	      if ((*s++ == ':') && ((*s == 'R') || (*s == ' ')) &&
  1.2482 +		  isxdigit (s[1])) {
  1.2483 +		refwd = (*s++ == 'R') ? T : NIL;
  1.2484 +		subjlen = strtoul (s,&s,16);
  1.2485 +		if ((*s++ == ':') && isxdigit (*s)) {
  1.2486 +		  msgidlen = strtoul (s,&s,16);
  1.2487 +		  if ((*s++ == ':') && isxdigit (*s)) {
  1.2488 +		    reflen = strtoul (s,&s,16);
  1.2489 +				/* ignore expansion values */
  1.2490 +		    if (*s++ == ':') {
  1.2491 +
  1.2492 +		      if (i = mail_msgno (stream,uid)) {
  1.2493 +			sc = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE);
  1.2494 +			sc->size = (elt = mail_elt (stream,i))->rfc822_size;
  1.2495 +			sc->date = sentdate;
  1.2496 +			sc->arrival = elt->day ? mail_longdate (elt) : 1;
  1.2497 +			if (refwd) sc->refwd = T;
  1.2498 +			if (fromlen) {
  1.2499 +			  if (sc->from) fseek (srtcf,fromlen + 2,SEEK_CUR);
  1.2500 +			  else if ((getc (srtcf) != 'F') ||
  1.2501 +				   (fread (sc->from = (char *) fs_get(fromlen),
  1.2502 +					   1,fromlen-1,srtcf) != (fromlen-1))||
  1.2503 +				   (sc->from[fromlen-1] = '\0') ||
  1.2504 +				   (getc (srtcf) != '\015') ||
  1.2505 +				   (getc (srtcf) != '\012')) {
  1.2506 +			    msg = "from data";
  1.2507 +			    break;
  1.2508 +			  }
  1.2509 +			}
  1.2510 +			if (tolen) {
  1.2511 +			  if (sc->to) fseek (srtcf,tolen + 2,SEEK_CUR);
  1.2512 +			  else if ((getc (srtcf) != 'T') ||
  1.2513 +				   (fread (sc->to = (char *) fs_get (tolen),
  1.2514 +					   1,tolen-1,srtcf) != (tolen - 1)) ||
  1.2515 +				   (sc->to[tolen-1] = '\0') ||
  1.2516 +				   (getc (srtcf) != '\015') ||
  1.2517 +				   (getc (srtcf) != '\012')) {
  1.2518 +			    msg = "to data";
  1.2519 +			    break;
  1.2520 +			  }
  1.2521 +			}
  1.2522 +			if (cclen) {
  1.2523 +			  if (sc->cc) fseek (srtcf,cclen + 2,SEEK_CUR);
  1.2524 +			  else if ((getc (srtcf) != 'C') ||
  1.2525 +				   (fread (sc->cc = (char *) fs_get (cclen),
  1.2526 +					   1,cclen-1,srtcf) != (cclen - 1)) ||
  1.2527 +				   (sc->cc[cclen-1] = '\0') ||
  1.2528 +				   (getc (srtcf) != '\015') ||
  1.2529 +				   (getc (srtcf) != '\012')) {
  1.2530 +			    msg = "cc data";
  1.2531 +			    break;
  1.2532 +			  }
  1.2533 +			}
  1.2534 +			if (subjlen) {
  1.2535 +			  if (sc->subject) fseek (srtcf,subjlen + 2,SEEK_CUR);
  1.2536 +			  else if ((getc (srtcf) != 'S') ||
  1.2537 +				     (fread (sc->subject =
  1.2538 +					     (char *) fs_get (subjlen),1,
  1.2539 +					     subjlen-1,srtcf) != (subjlen-1))||
  1.2540 +				   (sc->subject[subjlen-1] = '\0') ||
  1.2541 +				   (getc (srtcf) != '\015') ||
  1.2542 +				   (getc (srtcf) != '\012')) {
  1.2543 +			    msg = "subject data";
  1.2544 +			    break;
  1.2545 +			  }
  1.2546 +			}
  1.2547 +
  1.2548 +			if (msgidlen) {
  1.2549 +			  if (sc->message_id)
  1.2550 +			    fseek (srtcf,msgidlen + 2,SEEK_CUR);
  1.2551 +			  else if ((getc (srtcf) != 'M') ||
  1.2552 +				   (fread (sc->message_id = 
  1.2553 +					   (char *) fs_get (msgidlen),1,
  1.2554 +					   msgidlen-1,srtcf) != (msgidlen-1))||
  1.2555 +				   (sc->message_id[msgidlen-1] = '\0') ||
  1.2556 +				   (getc (srtcf) != '\015') ||
  1.2557 +				   (getc (srtcf) != '\012')) {
  1.2558 +			    msg = "message-id data";
  1.2559 +			    break;
  1.2560 +			  }
  1.2561 +			}
  1.2562 +			if (reflen) {
  1.2563 +			  if (sc->references) fseek(srtcf,reflen + 2,SEEK_CUR);
  1.2564 +				/* make sure it fits */
  1.2565 +			  else {
  1.2566 +			    if (reflen >= LOCAL->buflen) {
  1.2567 +			      fs_give ((void **) &LOCAL->buf);
  1.2568 +			      LOCAL->buf = (char *)
  1.2569 +				fs_get ((LOCAL->buflen = reflen) + 1);
  1.2570 +			      }
  1.2571 +			    if ((getc (srtcf) != 'R') ||
  1.2572 +				(fread (LOCAL->buf,1,reflen-1,srtcf) !=
  1.2573 +				 (reflen - 1)) ||
  1.2574 +				(LOCAL->buf[reflen-1] = '\0') ||
  1.2575 +				(getc (srtcf) != '\015') ||
  1.2576 +				(getc (srtcf) != '\012')) {
  1.2577 +			      msg = "references data";
  1.2578 +			      break;
  1.2579 +			    }
  1.2580 +			    for (s = LOCAL->buf,sl = NIL,
  1.2581 +				   sc->references = mail_newstringlist ();
  1.2582 +				 s && *s; s += i + 1) {
  1.2583 +			      if ((i = strtoul (s,&s,16)) && (*s++ == ':') &&
  1.2584 +				  (s[i] == ':')) {
  1.2585 +				if (sl) sl = sl->next = mail_newstringlist();
  1.2586 +				else sl = sc->references;
  1.2587 +				s[i] = '\0';
  1.2588 +				sl->text.data = cpystr (s);
  1.2589 +				sl->text.size = i;
  1.2590 +			      }
  1.2591 +			      else s = NIL;
  1.2592 +			    }
  1.2593 +			    if (!s || *s ||
  1.2594 +				(s != ((char *) LOCAL->buf + reflen - 1))) {
  1.2595 +			      msg = "references length consistency check";
  1.2596 +			      break;
  1.2597 +			    }
  1.2598 +			  }
  1.2599 +			}
  1.2600 +		      }
  1.2601 +
  1.2602 +				/* UID not found, ignore this message */
  1.2603 +		      else fseek (srtcf,((fromlen ? fromlen + 2 : 0) +
  1.2604 +					 (tolen ? tolen + 2 : 0) +
  1.2605 +					 (cclen ? cclen + 2 : 0) +
  1.2606 +					 (subjlen ? subjlen + 2 : 0) +
  1.2607 +					 (msgidlen ? msgidlen + 2 : 0) +
  1.2608 +					 (reflen ? reflen + 2 : 0)),
  1.2609 +				  SEEK_CUR);
  1.2610 +		      continue;
  1.2611 +		    }
  1.2612 +		    else msg = "expansion";
  1.2613 +		  }
  1.2614 +		  else msg = "references";
  1.2615 +		}
  1.2616 +		else msg = "message-id";
  1.2617 +	      }
  1.2618 +	      else msg = "subject";
  1.2619 +	    }
  1.2620 +	    else msg = "cc";
  1.2621 +	  }
  1.2622 +	  else msg = "to";
  1.2623 +	}
  1.2624 +	else msg = "from";
  1.2625 +      }
  1.2626 +      else msg = "sentdate";
  1.2627 +      break;			/* error somewhere */
  1.2628 +    }
  1.2629 +    if (!t || *t) {		/* error detected? */
  1.2630 +      if (t) {			/* non-null means bogus record */
  1.2631 +	sprintf (tmp,"Error in %s in mix sortcache record: %.500s",msg,t);
  1.2632 +	MM_LOG (tmp,WARN);
  1.2633 +      }
  1.2634 +      fclose (srtcf);		/* either way, must punt */
  1.2635 +      srtcf = NIL;
  1.2636 +    }
  1.2637 +  }
  1.2638 +  if (rdonly && srtcf) {	/* can't update if readonly */
  1.2639 +    unlink (LOCAL->sortcache);	/* try deleting it */
  1.2640 +    fclose (srtcf);		/* so close it and return as if error */
  1.2641 +    srtcf = NIL;
  1.2642 +  }
  1.2643 +  else fchmod (fd,sbuf.st_mode);
  1.2644 +  return srtcf;
  1.2645 +}
  1.2646 +
  1.2647 +/* MIX update and close sortcache
  1.2648 + * Accepts: MAIL stream
  1.2649 + *	    pointer to open FILE (if FILE is NIL, do nothing)
  1.2650 + * Returns: T on success, NIL on error
  1.2651 + */
  1.2652 +
  1.2653 +long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache)
  1.2654 +{
  1.2655 +  FILE *f = *sortcache;
  1.2656 +  long ret = LONGT;
  1.2657 +  if (f) {			/* ignore if no file */
  1.2658 +    unsigned long i,j;
  1.2659 +    mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1.2660 +    for (i = 1; (i <= stream->nmsgs) &&
  1.2661 +	   !((SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE))->dirty; ++i);
  1.2662 +    if (i <= stream->nmsgs) {	/* only update if some entry is dirty */
  1.2663 +      rewind (f);		/* let's start at the very beginning */
  1.2664 +				/* write sequence */
  1.2665 +      fprintf (f,SEQFMT,LOCAL->sortcacheseq = mix_modseq(LOCAL->sortcacheseq));
  1.2666 +      for (i = 1; ret && (i <= stream->nmsgs); ++i) {
  1.2667 +	MESSAGECACHE *elt = mail_elt (stream,i);
  1.2668 +	SORTCACHE *s = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE);
  1.2669 +	STRINGLIST *sl;
  1.2670 +	s->dirty = NIL;		/* no longer dirty */
  1.2671 +	if (sl = s->references)	/* count length of references */
  1.2672 +	  for (j = 1; sl && sl->text.data; sl = sl->next)
  1.2673 +	    j += 10 + sl->text.size;
  1.2674 +	else j = 0;		/* no references yet */
  1.2675 +	fprintf (f,SCRFMT,elt->private.uid,s->date,
  1.2676 +		 s->from ? strlen (s->from) + 1 : 0,
  1.2677 +		 s->to ? strlen (s->to) + 1 : 0,s->cc ? strlen (s->cc) + 1 : 0,
  1.2678 +		 s->refwd ? 'R' : ' ',s->subject ? strlen (s->subject) + 1: 0,
  1.2679 +		 s->message_id ? strlen (s->message_id) + 1 : 0,j);
  1.2680 +	if (s->from) fprintf (f,"F%s\015\012",s->from);
  1.2681 +	if (s->to) fprintf (f,"T%s\015\012",s->to);
  1.2682 +	if (s->cc) fprintf (f,"C%s\015\012",s->cc);
  1.2683 +	if (s->subject) fprintf (f,"S%s\015\012",s->subject);
  1.2684 +	if (s->message_id) fprintf (f,"M%s\015\012",s->message_id);
  1.2685 +	if (j) {		/* any references to write? */
  1.2686 +	  fputc ('R',f);	/* yes, do so */
  1.2687 +	  for (sl = s->references; sl && sl->text.data; sl = sl->next)
  1.2688 +	    fprintf (f,"%08lx:%s:",sl->text.size,sl->text.data);
  1.2689 +	  fputs ("\015\012",f);
  1.2690 +	}
  1.2691 +	if (ferror (f)) {
  1.2692 +	  MM_LOG ("Error updating mix sortcache file",WARN);
  1.2693 +	  ret = NIL;
  1.2694 +	}
  1.2695 +      }
  1.2696 +      if (ret && fflush (f)) {
  1.2697 +	MM_LOG ("Error flushing mix sortcache file",WARN);
  1.2698 +	ret = NIL;
  1.2699 +      }
  1.2700 +      if (ret) ftruncate (fileno (f),ftell (f));
  1.2701 +    }
  1.2702 +    if (fclose (f)) {
  1.2703 +      MM_LOG ("Error closing mix sortcache file",WARN);
  1.2704 +      ret = NIL;
  1.2705 +    }
  1.2706 +  }
  1.2707 +  return ret;
  1.2708 +}
  1.2709 +
  1.2710 +/* MIX generic file routines */
  1.2711 +
  1.2712 +/* MIX read record
  1.2713 + * Accepts: open FILE
  1.2714 + *	    buffer
  1.2715 + *	    buffer length
  1.2716 + *	    record type
  1.2717 + * Returns: buffer if success, else NIL (zero-length buffer means EOF)
  1.2718 + */
  1.2719 +
  1.2720 +char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type)
  1.2721 +{
  1.2722 +  char *s,tmp[MAILTMPLEN];
  1.2723 +				/* ensure string tied off */
  1.2724 +  buf[buflen-2] = buf[buflen-1] = '\0';
  1.2725 +  while (fgets (buf,buflen-1,f)) {
  1.2726 +    if (s = strchr (buf,'\012')) {
  1.2727 +      if ((s != buf) && (s[-1] == '\015')) --s;
  1.2728 +      *s = '\0';		/* tie off buffer */
  1.2729 +      if (s != buf) return buf;	/* return if non-empty buffer */
  1.2730 +      sprintf (tmp,"Empty mix %s record",type);
  1.2731 +      MM_LOG (tmp,WARN);
  1.2732 +    }
  1.2733 +    else if (buf[buflen-2]) {	/* overlong record is bad news */
  1.2734 +      sprintf (tmp,"Oversize mix %s record: %.512s",type,buf);
  1.2735 +      MM_LOG (tmp,ERROR);
  1.2736 +      return NIL;
  1.2737 +    }
  1.2738 +    else {
  1.2739 +      sprintf (tmp,"Truncated mix %s record: %.512s",type,buf);
  1.2740 +      MM_LOG (tmp,WARN);
  1.2741 +      return buf;		/* pass to caller anyway */
  1.2742 +    }
  1.2743 +  }
  1.2744 +  buf[0] = '\0';		/* return empty buffer on EOF */
  1.2745 +  return buf;
  1.2746 +}
  1.2747 +
  1.2748 +/* MIX read sequence record
  1.2749 + * Accepts: open FILE
  1.2750 + * Returns: sequence value, or NIL if failure
  1.2751 + */
  1.2752 +
  1.2753 +unsigned long mix_read_sequence (FILE *f)
  1.2754 +{
  1.2755 +  unsigned long ret;
  1.2756 +  char *s,tmp[MAILTMPLEN];
  1.2757 +  if (!mix_read_record (f,tmp,MAILTMPLEN-1,"sequence")) return NIL;
  1.2758 +  switch (tmp[0]) {		/* examine record */
  1.2759 +  case '\0':			/* end of file */
  1.2760 +    ret = 1;			/* start a new sequence regime */
  1.2761 +    break;
  1.2762 +  case 'S':			/* sequence record */
  1.2763 +    if (isxdigit (tmp[1])) {	/* must be followed by hex value */
  1.2764 +      ret = strtoul (tmp+1,&s,16);
  1.2765 +      if (!*s) break;		/* and nothing more */
  1.2766 +    }
  1.2767 +				/* drop into default case */
  1.2768 +  default:			/* anything else is an error */
  1.2769 +    return NIL;			/* return error */
  1.2770 +  }
  1.2771 +  return ret;
  1.2772 +}
  1.2773 +
  1.2774 +/* MIX internal routines */
  1.2775 +
  1.2776 +
  1.2777 +/* MIX mail build directory name
  1.2778 + * Accepts: destination string
  1.2779 + *          source
  1.2780 + * Returns: destination or empty string if error
  1.2781 + */
  1.2782 +
  1.2783 +char *mix_dir (char *dst,char *name)
  1.2784 +{
  1.2785 +  char *s;
  1.2786 +				/* empty string if mailboxfile fails */
  1.2787 +  if (!mailboxfile (dst,name)) *dst = '\0';
  1.2788 +				/* driver-selected INBOX  */
  1.2789 +  else if (!*dst) mailboxfile (dst,"~/INBOX");
  1.2790 +				/* tie off unnecessary trailing / */
  1.2791 +  else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0';
  1.2792 +  return dst;
  1.2793 +}
  1.2794 +
  1.2795 +
  1.2796 +/* MIX mail build file name
  1.2797 + * Accepts: destination string
  1.2798 + *	    directory name
  1.2799 + *	    file name
  1.2800 + * Returns: destination
  1.2801 + */
  1.2802 +
  1.2803 +char *mix_file (char *dst,char *dir,char *name)
  1.2804 +{
  1.2805 +  sprintf (dst,"%.500s/%.80s%.80s",dir,MIXNAME,name);
  1.2806 +  return dst;
  1.2807 +}
  1.2808 +
  1.2809 +
  1.2810 +/* MIX mail build file name from data file number
  1.2811 + * Accepts: destination string
  1.2812 + *	    directory name
  1.2813 + *	    data file number
  1.2814 + * Returns: destination
  1.2815 + */
  1.2816 +
  1.2817 +char *mix_file_data (char *dst,char *dir,unsigned long data)
  1.2818 +{
  1.2819 +  char tmp[MAILTMPLEN];
  1.2820 +  if (data) sprintf (tmp,"%08lx",data);
  1.2821 +  else tmp[0] = '\0';		/* compatibility with experimental version */
  1.2822 +  return mix_file (dst,dir,tmp);
  1.2823 +}
  1.2824 +
  1.2825 +/* MIX mail get new modseq
  1.2826 + * Accepts: old modseq
  1.2827 + * Returns: new modseq value
  1.2828 + */
  1.2829 +
  1.2830 +unsigned long mix_modseq (unsigned long oldseq)
  1.2831 +{
  1.2832 +				/* normally time now */
  1.2833 +  unsigned long ret = (unsigned long) time (NIL);
  1.2834 +				/* ensure that modseq doesn't go backwards */
  1.2835 +  if (ret <= oldseq) ret = oldseq + 1;
  1.2836 +  return ret;
  1.2837 +}

UW-IMAP'd extensions by yuuji