imapext-2007

diff src/osdep/amiga/mmdf.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/amiga/mmdf.c	Mon Sep 14 15:17:45 2009 +0900
     1.3 @@ -0,0 +1,2549 @@
     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:	MMDF mail routines
    1.19 + *
    1.20 + * Author:	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:	20 December 1989
    1.27 + * Last Edited:	27 March 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 <signal.h>
    1.36 +#include "mail.h"
    1.37 +#include "osdep.h"
    1.38 +#include <time.h>
    1.39 +#include <sys/stat.h>
    1.40 +#include "pseudo.h"
    1.41 +#include "fdstring.h"
    1.42 +#include "misc.h"
    1.43 +#include "dummy.h"
    1.44 +
    1.45 +/* Supposedly, this page has everything the MMDF driver needs to know about
    1.46 + * the MMDF delimiter.  By changing these macros, the MMDF driver should
    1.47 + * change with it.  Note that if you change the length of MMDFHDRTXT you
    1.48 + * also need to change the ISMMDF and RETIFMMDFWRD macros to reflect the new
    1.49 + * size.
    1.50 + */
    1.51 +
    1.52 +
    1.53 +/* Useful MMDF constants */
    1.54 +
    1.55 +#define MMDFCHR '\01'		/* MMDF character */
    1.56 +#define MMDFCHRS 0x01010101	/* MMDF header character spread in a word */
    1.57 +				/* MMDF header text */
    1.58 +#define MMDFHDRTXT "\01\01\01\01\n"
    1.59 +				/* length of MMDF header text */
    1.60 +#define MMDFHDRLEN (sizeof (MMDFHDRTXT) - 1)
    1.61 +
    1.62 +
    1.63 +/* Validate MMDF header
    1.64 + * Accepts: pointer to candidate string to validate as an MMDF header
    1.65 + * Returns: T if valid; else NIL
    1.66 + */
    1.67 +
    1.68 +#define ISMMDF(s)							\
    1.69 +  ((*(s) == MMDFCHR) && ((s)[1] == MMDFCHR) && ((s)[2] == MMDFCHR) &&	\
    1.70 +   ((s)[3] == MMDFCHR) && ((s)[4] == '\n'))
    1.71 +
    1.72 +
    1.73 +/* Return if a 32-bit word has the start of an MMDF header
    1.74 + * Accepts: pointer to word of four bytes to validate as an MMDF header
    1.75 + * Returns: pointer to MMDF header, else proceeds
    1.76 + */
    1.77 +
    1.78 +#define RETIFMMDFWRD(s) {						\
    1.79 +  if (s[3] == MMDFCHR) {						\
    1.80 +    if ((s[4] == MMDFCHR) && (s[5] == MMDFCHR) && (s[6] == MMDFCHR) &&	\
    1.81 +	(s[7] == '\n')) return s + 3;					\
    1.82 +    else if (s[2] == MMDFCHR) {						\
    1.83 +      if ((s[4] == MMDFCHR) && (s[5] == MMDFCHR) && (s[6] == '\n'))	\
    1.84 +	return s + 2;							\
    1.85 +      else if (s[1] == MMDFCHR) {					\
    1.86 +	if ((s[4] == MMDFCHR) && (s[5] == '\n')) return s + 1;		\
    1.87 +	else if ((*s == MMDFCHR) && (s[4] == '\n')) return s;		\
    1.88 +      }									\
    1.89 +    }									\
    1.90 +  }									\
    1.91 +}
    1.92 +
    1.93 +/* Validate line
    1.94 + * Accepts: pointer to candidate string to validate as a From header
    1.95 + *	    return pointer to end of date/time field
    1.96 + *	    return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
    1.97 + *	    return pointer to offset from t of time zone (if non-zero)
    1.98 + * Returns: t,ti,zn set if valid From string, else ti is NIL
    1.99 + */
   1.100 +
   1.101 +#define VALID(s,x,ti,zn) {						\
   1.102 +  ti = 0;								\
   1.103 +  if ((*s == 'F') && (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') &&	\
   1.104 +      (s[4] == ' ')) {							\
   1.105 +    for (x = s + 5; *x && *x != '\n'; x++);				\
   1.106 +    if (*x) {								\
   1.107 +      if (x - s >= 41) {						\
   1.108 +	for (zn = -1; x[zn] != ' '; zn--);				\
   1.109 +	if ((x[zn-1] == 'm') && (x[zn-2] == 'o') && (x[zn-3] == 'r') &&	\
   1.110 +	    (x[zn-4] == 'f') && (x[zn-5] == ' ') && (x[zn-6] == 'e') &&	\
   1.111 +	    (x[zn-7] == 't') && (x[zn-8] == 'o') && (x[zn-9] == 'm') &&	\
   1.112 +	    (x[zn-10] == 'e') && (x[zn-11] == 'r') && (x[zn-12] == ' '))\
   1.113 +	  x += zn - 12;							\
   1.114 +      }									\
   1.115 +      if (x - s >= 27) {						\
   1.116 +	if (x[-5] == ' ') {						\
   1.117 +	  if (x[-8] == ':') zn = 0,ti = -5;				\
   1.118 +	  else if (x[-9] == ' ') ti = zn = -9;				\
   1.119 +	  else if ((x[-11] == ' ') && ((x[-10]=='+') || (x[-10]=='-')))	\
   1.120 +	    ti = zn = -11;						\
   1.121 +	}								\
   1.122 +	else if (x[-4] == ' ') {					\
   1.123 +	  if (x[-9] == ' ') zn = -4,ti = -9;				\
   1.124 +	}								\
   1.125 +	else if (x[-6] == ' ') {					\
   1.126 +	  if ((x[-11] == ' ') && ((x[-5] == '+') || (x[-5] == '-')))	\
   1.127 +	    zn = -6,ti = -11;						\
   1.128 +	}								\
   1.129 +	if (ti && !((x[ti - 3] == ':') &&				\
   1.130 +		    (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') &&	\
   1.131 +		    (x[ti - 3] == ' ') && (x[ti - 7] == ' ') &&		\
   1.132 +		    (x[ti - 11] == ' '))) ti = 0;			\
   1.133 +      }									\
   1.134 +    }									\
   1.135 +  }									\
   1.136 +}
   1.137 +
   1.138 +/* You are not expected to understand this macro, but read the next page if
   1.139 + * you are not faint of heart.
   1.140 + *
   1.141 + * Known formats to the VALID macro are:
   1.142 + *		From user Wed Dec  2 05:53 1992
   1.143 + * BSD		From user Wed Dec  2 05:53:22 1992
   1.144 + * SysV		From user Wed Dec  2 05:53 PST 1992
   1.145 + * rn		From user Wed Dec  2 05:53:22 PST 1992
   1.146 + *		From user Wed Dec  2 05:53 -0700 1992
   1.147 + * emacs	From user Wed Dec  2 05:53:22 -0700 1992
   1.148 + *		From user Wed Dec  2 05:53 1992 PST
   1.149 + *		From user Wed Dec  2 05:53:22 1992 PST
   1.150 + *		From user Wed Dec  2 05:53 1992 -0700
   1.151 + * Solaris	From user Wed Dec  2 05:53:22 1992 -0700
   1.152 + *
   1.153 + * Plus all of the above with `` remote from xxx'' after it. Thank you very
   1.154 + * much, smail and Solaris, for making my life considerably more complicated.
   1.155 + */
   1.156 +
   1.157 +/*
   1.158 + * What?  You want to understand the VALID macro anyway?  Alright, since you
   1.159 + * insist.  Actually, it isn't really all that difficult, provided that you
   1.160 + * take it step by step.
   1.161 + *
   1.162 + * Line 1	Initializes the return ti value to failure (0);
   1.163 + * Lines 2-3	Validates that the 1st-5th characters are ``From ''.
   1.164 + * Lines 4-5	Validates that there is an end of line and points x at it.
   1.165 + * Lines 6-13	First checks to see if the line is at least 41 characters long.
   1.166 + *		If so, it scans backwards to find the rightmost space.  From
   1.167 + *		that point, it scans backwards to see if the string matches
   1.168 + *		`` remote from''.  If so, it sets x to point to the space at
   1.169 + *		the start of the string.
   1.170 + * Line 14	Makes sure that there are at least 27 characters in the line.
   1.171 + * Lines 15-20	Checks if the date/time ends with the year (there is a space
   1.172 + *		five characters back).  If there is a colon three characters
   1.173 + *		further back, there is no timezone field, so zn is set to 0
   1.174 + *		and ti is set in front of the year.  Otherwise, there must
   1.175 + *		either to be a space four characters back for a three-letter
   1.176 + *		timezone, or a space six characters back followed by a + or -
   1.177 + *		for a numeric timezone; in either case, zn and ti become the
   1.178 + *		offset of the space immediately before it.
   1.179 + * Lines 21-23	Are the failure case for line 14.  If there is a space four
   1.180 + *		characters back, it is a three-letter timezone; there must be a
   1.181 + *		space for the year nine characters back.  zn is the zone
   1.182 + *		offset; ti is the offset of the space.
   1.183 + * Lines 24-27	Are the failure case for line 20.  If there is a space six
   1.184 + *		characters back, it is a numeric timezone; there must be a
   1.185 + *		space eleven characters back and a + or - five characters back.
   1.186 + *		zn is the zone offset; ti is the offset of the space.
   1.187 + * Line 28-31	If ti is valid, make sure that the string before ti is of the
   1.188 + *		form www mmm dd hh:mm or www mmm dd hh:mm:ss, otherwise
   1.189 + *		invalidate ti.  There must be a colon three characters back
   1.190 + *		and a space six or nine	characters back (depending upon
   1.191 + *		whether or not the character six characters back is a colon).
   1.192 + *		There must be a space three characters further back (in front
   1.193 + *		of the day), one seven characters back (in front of the month),
   1.194 + *		and one eleven characters back (in front of the day of week).
   1.195 + *		ti is set to be the offset of the space before the time.
   1.196 + *
   1.197 + * Why a macro?  It gets invoked a *lot* in a tight loop.  On some of the
   1.198 + * newer pipelined machines it is faster being open-coded than it would be if
   1.199 + * subroutines are called.
   1.200 + *
   1.201 + * Why does it scan backwards from the end of the line, instead of doing the
   1.202 + * much easier forward scan?  There is no deterministic way to parse the
   1.203 + * ``user'' field, because it may contain unquoted spaces!  Yes, I tested it to
   1.204 + * see if unquoted spaces were possible.  They are, and I've encountered enough
   1.205 + * evil mail to be totally unwilling to trust that ``it will never happen''.
   1.206 + */
   1.207 +
   1.208 +/* Build parameters */
   1.209 +
   1.210 +#define KODRETRY 15		/* kiss-of-death retry in seconds */
   1.211 +#define LOCKTIMEOUT 5		/* lock timeout in minutes */
   1.212 +
   1.213 +
   1.214 +/* MMDF I/O stream local data */
   1.215 +
   1.216 +typedef struct mmdf_local {
   1.217 +  unsigned int dirty : 1;	/* disk copy needs updating */
   1.218 +  unsigned int ddirty : 1;	/* double-dirty, ping becomes checkpoint */
   1.219 +  unsigned int pseudo : 1;	/* uses a pseudo message */
   1.220 +  unsigned int appending : 1;	/* don't mark new messages as old */
   1.221 +  int fd;			/* mailbox file descriptor */
   1.222 +  int ld;			/* lock file descriptor */
   1.223 +  char *lname;			/* lock file name */
   1.224 +  off_t filesize;		/* file size parsed */
   1.225 +  time_t filetime;		/* last file time */
   1.226 +  unsigned char *buf;		/* temporary buffer */
   1.227 +  unsigned long buflen;		/* current size of temporary buffer */
   1.228 +  unsigned long uid;		/* current text uid */
   1.229 +  SIZEDTEXT text;		/* current text */
   1.230 +  unsigned long textlen;	/* current text length */
   1.231 +  char *line;			/* returned line */
   1.232 +  char *linebuf;		/* line readin buffer */
   1.233 +  unsigned long linebuflen;	/* current line readin buffer length */
   1.234 +} MMDFLOCAL;
   1.235 +
   1.236 +
   1.237 +/* Convenient access to local data */
   1.238 +
   1.239 +#define LOCAL ((MMDFLOCAL *) stream->local)
   1.240 +
   1.241 +
   1.242 +/* MMDF protected file structure */
   1.243 +
   1.244 +typedef struct mmdf_file {
   1.245 +  MAILSTREAM *stream;		/* current stream */
   1.246 +  off_t curpos;			/* current file position */
   1.247 +  off_t protect;		/* protected position */
   1.248 +  off_t filepos;		/* current last written file position */
   1.249 +  char *buf;			/* overflow buffer */
   1.250 +  size_t buflen;		/* current overflow buffer length */
   1.251 +  char *bufpos;			/* current buffer position */
   1.252 +} MMDFFILE;
   1.253 +
   1.254 +/* Function prototypes */
   1.255 +
   1.256 +DRIVER *mmdf_valid (char *name);
   1.257 +long mmdf_isvalid (char *name,char *tmp);
   1.258 +long mmdf_isvalid_fd (int fd,char *tmp);
   1.259 +void *mmdf_parameters (long function,void *value);
   1.260 +void mmdf_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
   1.261 +void mmdf_list (MAILSTREAM *stream,char *ref,char *pat);
   1.262 +void mmdf_lsub (MAILSTREAM *stream,char *ref,char *pat);
   1.263 +long mmdf_create (MAILSTREAM *stream,char *mailbox);
   1.264 +long mmdf_delete (MAILSTREAM *stream,char *mailbox);
   1.265 +long mmdf_rename (MAILSTREAM *stream,char *old,char *newname);
   1.266 +MAILSTREAM *mmdf_open (MAILSTREAM *stream);
   1.267 +void mmdf_close (MAILSTREAM *stream,long options);
   1.268 +char *mmdf_header (MAILSTREAM *stream,unsigned long msgno,
   1.269 +		   unsigned long *length,long flags);
   1.270 +long mmdf_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
   1.271 +char *mmdf_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
   1.272 +		      unsigned long *length,long flags);
   1.273 +void mmdf_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
   1.274 +long mmdf_ping (MAILSTREAM *stream);
   1.275 +void mmdf_check (MAILSTREAM *stream);
   1.276 +long mmdf_expunge (MAILSTREAM *stream,char *sequence,long options);
   1.277 +long mmdf_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
   1.278 +long mmdf_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
   1.279 +int mmdf_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
   1.280 +		     STRING *msg);
   1.281 +int mmdf_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set);
   1.282 +
   1.283 +void mmdf_abort (MAILSTREAM *stream);
   1.284 +char *mmdf_file (char *dst,char *name);
   1.285 +int mmdf_lock (char *file,int flags,int mode,DOTLOCK *lock,int op);
   1.286 +void mmdf_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock);
   1.287 +int mmdf_parse (MAILSTREAM *stream,DOTLOCK *lock,int op);
   1.288 +char *mmdf_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size);
   1.289 +unsigned long mmdf_pseudo (MAILSTREAM *stream,char *hdr);
   1.290 +unsigned long mmdf_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
   1.291 +			    unsigned long uid,long flag);
   1.292 +long mmdf_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
   1.293 +		   long flags);
   1.294 +long mmdf_extend (MAILSTREAM *stream,unsigned long size);
   1.295 +void mmdf_write (MMDFFILE *f,char *s,unsigned long i);
   1.296 +void mmdf_phys_write (MMDFFILE *f,char *buf,size_t size);
   1.297 +
   1.298 +/* MMDF mail routines */
   1.299 +
   1.300 +
   1.301 +/* Driver dispatch used by MAIL */
   1.302 +
   1.303 +DRIVER mmdfdriver = {
   1.304 +  "mmdf",			/* driver name */
   1.305 +				/* driver flags */
   1.306 +  DR_LOCAL|DR_MAIL|DR_LOCKING|DR_NONEWMAILRONLY|DR_XPOINT,
   1.307 +  (DRIVER *) NIL,		/* next driver */
   1.308 +  mmdf_valid,			/* mailbox is valid for us */
   1.309 +  mmdf_parameters,		/* manipulate parameters */
   1.310 +  mmdf_scan,			/* scan mailboxes */
   1.311 +  mmdf_list,			/* list mailboxes */
   1.312 +  mmdf_lsub,			/* list subscribed mailboxes */
   1.313 +  NIL,				/* subscribe to mailbox */
   1.314 +  NIL,				/* unsubscribe from mailbox */
   1.315 +  mmdf_create,			/* create mailbox */
   1.316 +  mmdf_delete,			/* delete mailbox */
   1.317 +  mmdf_rename,			/* rename mailbox */
   1.318 +  mail_status_default,		/* status of mailbox */
   1.319 +  mmdf_open,			/* open mailbox */
   1.320 +  mmdf_close,			/* close mailbox */
   1.321 +  NIL,				/* fetch message "fast" attributes */
   1.322 +  NIL,				/* fetch message flags */
   1.323 +  NIL,				/* fetch overview */
   1.324 +  NIL,				/* fetch message envelopes */
   1.325 +  mmdf_header,			/* fetch message header */
   1.326 +  mmdf_text,			/* fetch message text */
   1.327 +  NIL,				/* fetch partial message text */
   1.328 +  NIL,				/* unique identifier */
   1.329 +  NIL,				/* message number */
   1.330 +  NIL,				/* modify flags */
   1.331 +  mmdf_flagmsg,			/* per-message modify flags */
   1.332 +  NIL,				/* search for message based on criteria */
   1.333 +  NIL,				/* sort messages */
   1.334 +  NIL,				/* thread messages */
   1.335 +  mmdf_ping,			/* ping mailbox to see if still alive */
   1.336 +  mmdf_check,			/* check for new messages */
   1.337 +  mmdf_expunge,			/* expunge deleted messages */
   1.338 +  mmdf_copy,			/* copy messages to another mailbox */
   1.339 +  mmdf_append,			/* append string message to mailbox */
   1.340 +  NIL				/* garbage collect stream */
   1.341 +};
   1.342 +
   1.343 +				/* prototype stream */
   1.344 +MAILSTREAM mmdfproto = {&mmdfdriver};
   1.345 +
   1.346 +char *mmdfhdr = MMDFHDRTXT;	/* MMDF header */
   1.347 +
   1.348 +/* MMDF mail validate mailbox
   1.349 + * Accepts: mailbox name
   1.350 + * Returns: our driver if name is valid, NIL otherwise
   1.351 + */
   1.352 +
   1.353 +DRIVER *mmdf_valid (char *name)
   1.354 +{
   1.355 +  char tmp[MAILTMPLEN];
   1.356 +  return mmdf_isvalid (name,tmp) ? &mmdfdriver : NIL;
   1.357 +}
   1.358 +
   1.359 +
   1.360 +/* MMDF mail test for valid mailbox name
   1.361 + * Accepts: mailbox name
   1.362 + *	    scratch buffer
   1.363 + * Returns: T if valid, NIL otherwise
   1.364 + */
   1.365 +
   1.366 +long mmdf_isvalid (char *name,char *tmp)
   1.367 +{
   1.368 +  int fd;
   1.369 +  int ret = NIL;
   1.370 +  char *t,file[MAILTMPLEN];
   1.371 +  struct stat sbuf;
   1.372 +  time_t tp[2];
   1.373 +  errno = EINVAL;		/* assume invalid argument */
   1.374 +				/* must be non-empty file */
   1.375 +  if ((t = dummy_file (file,name)) && !stat (t,&sbuf)) {
   1.376 +    if (!sbuf.st_size)errno = 0;/* empty file */
   1.377 +    else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
   1.378 +				/* error -1 for invalid format */
   1.379 +      if (!(ret = mmdf_isvalid_fd (fd,tmp))) errno = -1;
   1.380 +      close (fd);		/* close the file */
   1.381 +				/* \Marked status? */
   1.382 +      if ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) {
   1.383 +	tp[0] = sbuf.st_atime;	/* preserve atime and mtime */
   1.384 +	tp[1] = sbuf.st_mtime;
   1.385 +	utime (file,tp);	/* set the times */
   1.386 +      }
   1.387 +    }
   1.388 +  }
   1.389 +  return ret;			/* return what we should */
   1.390 +}
   1.391 +
   1.392 +/* MMDF mail test for valid mailbox
   1.393 + * Accepts: file descriptor
   1.394 + *	    scratch buffer
   1.395 + * Returns: T if valid, NIL otherwise
   1.396 + */
   1.397 +
   1.398 +long mmdf_isvalid_fd (int fd,char *tmp)
   1.399 +{
   1.400 +  int ret = NIL;
   1.401 +  memset (tmp,'\0',MAILTMPLEN);
   1.402 +  if (read (fd,tmp,MAILTMPLEN-1) >= 0) ret = ISMMDF (tmp) ? T : NIL;
   1.403 +  return ret;			/* return what we should */
   1.404 +}
   1.405 +
   1.406 +
   1.407 +/* MMDF manipulate driver parameters
   1.408 + * Accepts: function code
   1.409 + *	    function-dependent value
   1.410 + * Returns: function-dependent return value
   1.411 + */
   1.412 +
   1.413 +void *mmdf_parameters (long function,void *value)
   1.414 +{
   1.415 +  void *ret = NIL;
   1.416 +  switch ((int) function) {
   1.417 +  case GET_INBOXPATH:
   1.418 +    if (value) ret = dummy_file ((char *) value,"INBOX");
   1.419 +    break;
   1.420 +  }
   1.421 +  return ret;
   1.422 +}
   1.423 +
   1.424 +/* MMDF mail scan mailboxes
   1.425 + * Accepts: mail stream
   1.426 + *	    reference
   1.427 + *	    pattern to search
   1.428 + *	    string to scan
   1.429 + */
   1.430 +
   1.431 +void mmdf_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
   1.432 +{
   1.433 +  if (stream) dummy_scan (NIL,ref,pat,contents);
   1.434 +}
   1.435 +
   1.436 +
   1.437 +/* MMDF mail list mailboxes
   1.438 + * Accepts: mail stream
   1.439 + *	    reference
   1.440 + *	    pattern to search
   1.441 + */
   1.442 +
   1.443 +void mmdf_list (MAILSTREAM *stream,char *ref,char *pat)
   1.444 +{
   1.445 +  if (stream) dummy_list (NIL,ref,pat);
   1.446 +}
   1.447 +
   1.448 +
   1.449 +/* MMDF mail list subscribed mailboxes
   1.450 + * Accepts: mail stream
   1.451 + *	    reference
   1.452 + *	    pattern to search
   1.453 + */
   1.454 +
   1.455 +void mmdf_lsub (MAILSTREAM *stream,char *ref,char *pat)
   1.456 +{
   1.457 +  if (stream) dummy_lsub (NIL,ref,pat);
   1.458 +}
   1.459 +
   1.460 +/* MMDF mail create mailbox
   1.461 + * Accepts: MAIL stream
   1.462 + *	    mailbox name to create
   1.463 + * Returns: T on success, NIL on failure
   1.464 + */
   1.465 +
   1.466 +long mmdf_create (MAILSTREAM *stream,char *mailbox)
   1.467 +{
   1.468 +  char *s,mbx[MAILTMPLEN],tmp[MAILTMPLEN];
   1.469 +  long ret = NIL;
   1.470 +  int i,fd;
   1.471 +  time_t ti = time (0);
   1.472 +  if (!(s = dummy_file (mbx,mailbox))) {
   1.473 +    sprintf (tmp,"Can't create %.80s: invalid name",mailbox);
   1.474 +    MM_LOG (tmp,ERROR);
   1.475 +  }
   1.476 +				/* create underlying file */
   1.477 +  else if (dummy_create_path (stream,s,get_dir_protection (mailbox))) {
   1.478 +				/* done if dir-only or whiner */
   1.479 +    if (((s = strrchr (s,'/')) && !s[1]) ||
   1.480 +	mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) ret = T;
   1.481 +    else if ((fd = open (mbx,O_WRONLY,
   1.482 +		    (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL))) < 0) {
   1.483 +      sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
   1.484 +      MM_LOG (tmp,ERROR);
   1.485 +      unlink (mbx);		/* delete the file */
   1.486 +    }
   1.487 +    else {			/* initialize header */
   1.488 +      memset (tmp,'\0',MAILTMPLEN);
   1.489 +      sprintf (tmp,"%sFrom %s %sDate: ",mmdfhdr,pseudo_from,ctime (&ti));
   1.490 +      rfc822_date (s = tmp + strlen (tmp));
   1.491 +      sprintf (s += strlen (s),	/* write the pseudo-header */
   1.492 +	       "\nFrom: %s <%s@%s>\nSubject: %s\nX-IMAP: %010lu 0000000000",
   1.493 +	       pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
   1.494 +	       (unsigned long) ti);
   1.495 +      for (i = 0; i < NUSERFLAGS; ++i) if (default_user_flag (i))
   1.496 +	sprintf (s += strlen (s)," %s",default_user_flag (i));
   1.497 +      sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr);
   1.498 +      if (write (fd,tmp,strlen (tmp)) > 0) ret = T;
   1.499 +      else {
   1.500 +	sprintf (tmp,"Can't initialize mailbox node %.80s: %s",mbx,
   1.501 +		 strerror (errno));
   1.502 +	MM_LOG (tmp,ERROR);
   1.503 +	unlink (mbx);		/* delete the file */
   1.504 +      }
   1.505 +      close (fd);		/* close file */
   1.506 +    }
   1.507 +  }
   1.508 +				/* set proper protections */
   1.509 +  return ret ? set_mbx_protections (mailbox,mbx) : NIL;
   1.510 +}
   1.511 +
   1.512 +/* MMDF mail delete mailbox
   1.513 + * Accepts: MAIL stream
   1.514 + *	    mailbox name to delete
   1.515 + * Returns: T on success, NIL on failure
   1.516 + */
   1.517 +
   1.518 +long mmdf_delete (MAILSTREAM *stream,char *mailbox)
   1.519 +{
   1.520 +  return mmdf_rename (stream,mailbox,NIL);
   1.521 +}
   1.522 +
   1.523 +
   1.524 +/* MMDF mail rename mailbox
   1.525 + * Accepts: MAIL stream
   1.526 + *	    old mailbox name
   1.527 + *	    new mailbox name (or NIL for delete)
   1.528 + * Returns: T on success, NIL on failure
   1.529 + */
   1.530 +
   1.531 +long mmdf_rename (MAILSTREAM *stream,char *old,char *newname)
   1.532 +{
   1.533 +  long ret = NIL;
   1.534 +  char c,*s = NIL;
   1.535 +  char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
   1.536 +  DOTLOCK lockx;
   1.537 +  int fd,ld;
   1.538 +  long i;
   1.539 +  struct stat sbuf;
   1.540 +  MM_CRITICAL (stream);	/* get the c-client lock */
   1.541 +  if (!dummy_file (file,old) ||
   1.542 +      (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
   1.543 +		   ((s = strrchr (tmp,'/')) && !s[1]))))
   1.544 +    sprintf (tmp,newname ?
   1.545 +	     "Can't rename mailbox %.80s to %.80s: invalid name" :
   1.546 +	     "Can't delete mailbox %.80s: invalid name",
   1.547 +	     old,newname);
   1.548 +				/* lock out other c-clients */
   1.549 +  else if ((ld = lockname (lock,file,LOCK_EX|LOCK_NB,&i)) < 0)
   1.550 +    sprintf (tmp,"Mailbox %.80s is in use by another process",old);
   1.551 +
   1.552 +  else {
   1.553 +    if ((fd = mmdf_lock (file,O_RDWR,
   1.554 +			 (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
   1.555 +			 &lockx,LOCK_EX)) < 0)
   1.556 +       sprintf (tmp,"Can't lock mailbox %.80s: %s",old,strerror (errno));
   1.557 +    else {
   1.558 +      if (newname) {		/* want rename? */
   1.559 +				/* found superior to destination name? */
   1.560 +	if (s = strrchr (s,'/')) {
   1.561 +	  c = *++s;		/* remember first character of inferior */
   1.562 +	  *s = '\0';		/* tie off to get just superior */
   1.563 +				/* name doesn't exist, create it */
   1.564 +	  if ((stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
   1.565 +	      !dummy_create_path (stream,tmp,get_dir_protection (newname))) {
   1.566 +	    mmdf_unlock (fd,NIL,&lockx);
   1.567 +	    mmdf_unlock (ld,NIL,NIL);
   1.568 +	    unlink (lock);
   1.569 +	    MM_NOCRITICAL (stream);
   1.570 +	    return ret;		/* return success or failure */
   1.571 +	  }
   1.572 +	  *s = c;		/* restore full name */
   1.573 +	}
   1.574 +	if (rename (file,tmp))
   1.575 +	  sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
   1.576 +		   strerror (errno));
   1.577 +	else ret = T;		/* set success */
   1.578 +      }
   1.579 +      else if (unlink (file))
   1.580 +	sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
   1.581 +      else ret = T;		/* set success */
   1.582 +      mmdf_unlock (fd,NIL,&lockx);
   1.583 +    }
   1.584 +    mmdf_unlock (ld,NIL,NIL);	/* flush the lock */
   1.585 +    unlink (lock);
   1.586 +  }
   1.587 +  MM_NOCRITICAL (stream);	/* no longer critical */
   1.588 +  if (!ret) MM_LOG (tmp,ERROR);	/* log error */
   1.589 +  return ret;			/* return success or failure */
   1.590 +}
   1.591 +
   1.592 +/* MMDF mail open
   1.593 + * Accepts: Stream to open
   1.594 + * Returns: Stream on success, NIL on failure
   1.595 + */
   1.596 +
   1.597 +MAILSTREAM *mmdf_open (MAILSTREAM *stream)
   1.598 +{
   1.599 +  long i;
   1.600 +  int fd;
   1.601 +  char tmp[MAILTMPLEN];
   1.602 +  DOTLOCK lock;
   1.603 +  long retry;
   1.604 +				/* return prototype for OP_PROTOTYPE call */
   1.605 +  if (!stream) return user_flags (&mmdfproto);
   1.606 +  retry = stream->silent ? 1 : KODRETRY;
   1.607 +  if (stream->local) fatal ("mmdf recycle stream");
   1.608 +  stream->local = memset (fs_get (sizeof (MMDFLOCAL)),0,sizeof (MMDFLOCAL));
   1.609 +				/* note if an INBOX or not */
   1.610 +  stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
   1.611 +				/* canonicalize the stream mailbox name */
   1.612 +  if (!dummy_file (tmp,stream->mailbox)) {
   1.613 +    sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
   1.614 +    MM_LOG (tmp,ERROR);
   1.615 +    return NIL;
   1.616 +  }
   1.617 +				/* flush old name */
   1.618 +  fs_give ((void **) &stream->mailbox);
   1.619 +				/* save canonical name */
   1.620 +  stream->mailbox = cpystr (tmp);
   1.621 +  LOCAL->fd = LOCAL->ld = -1;	/* no file or state locking yet */
   1.622 +  LOCAL->buf = (char *) fs_get (CHUNKSIZE);
   1.623 +  LOCAL->buflen = CHUNKSIZE - 1;
   1.624 +  LOCAL->text.data = (unsigned char *) fs_get (CHUNKSIZE);
   1.625 +  LOCAL->text.size = CHUNKSIZE - 1;
   1.626 +  LOCAL->linebuf = (char *) fs_get (CHUNKSIZE);
   1.627 +  LOCAL->linebuflen = CHUNKSIZE - 1;
   1.628 +  stream->sequence++;		/* bump sequence number */
   1.629 +
   1.630 +				/* make lock for read/write access */
   1.631 +  if (!stream->rdonly) while (retry) {
   1.632 +				/* try to lock file */
   1.633 +    if ((fd = lockname (tmp,stream->mailbox,LOCK_EX|LOCK_NB,&i)) < 0) {
   1.634 +				/* suppressing kiss-of-death? */
   1.635 +      if (stream->nokod) retry = 0;
   1.636 +				/* no, first time through? */
   1.637 +      else if (retry-- == KODRETRY) {
   1.638 +				/* learned other guy's PID and can signal? */
   1.639 +	if (i && !kill ((int) i,SIGUSR2)) {
   1.640 +	  sprintf (tmp,"Trying to get mailbox lock from process %ld",i);
   1.641 +	  MM_LOG (tmp,WARN);
   1.642 +	}
   1.643 +	else retry = 0;		/* give up */
   1.644 +      }
   1.645 +      if (!stream->silent) {	/* nothing if silent stream */
   1.646 +	if (retry) sleep (1);	/* wait a second before trying again */
   1.647 +	else MM_LOG ("Mailbox is open by another process, access is readonly",
   1.648 +		     WARN);
   1.649 +      }
   1.650 +    }
   1.651 +    else {			/* got the lock, nobody else can alter state */
   1.652 +      LOCAL->ld = fd;		/* note lock's fd and name */
   1.653 +      LOCAL->lname = cpystr (tmp);
   1.654 +				/* make sure mode OK (don't use fchmod()) */
   1.655 +      chmod (LOCAL->lname,(long) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
   1.656 +      if (stream->silent) i = 0;/* silent streams won't accept KOD */
   1.657 +      else {			/* note our PID in the lock */
   1.658 +	sprintf (tmp,"%d",getpid ());
   1.659 +	write (fd,tmp,(i = strlen (tmp))+1);
   1.660 +      }
   1.661 +      ftruncate (fd,i);		/* make sure tied off */
   1.662 +      fsync (fd);		/* make sure it's available */
   1.663 +      retry = 0;		/* no more need to try */
   1.664 +    }
   1.665 +  }
   1.666 +
   1.667 +				/* parse mailbox */
   1.668 +  stream->nmsgs = stream->recent = 0;
   1.669 +				/* will we be able to get write access? */
   1.670 +  if ((LOCAL->ld >= 0) && access (stream->mailbox,W_OK) && (errno == EACCES)) {
   1.671 +    MM_LOG ("Can't get write access to mailbox, access is readonly",WARN);
   1.672 +    flock (LOCAL->ld,LOCK_UN);	/* release the lock */
   1.673 +    close (LOCAL->ld);		/* close the lock file */
   1.674 +    LOCAL->ld = -1;		/* no more lock fd */
   1.675 +    unlink (LOCAL->lname);	/* delete it */
   1.676 +  }
   1.677 +				/* reset UID validity */
   1.678 +  stream->uid_validity = stream->uid_last = 0;
   1.679 +  if (stream->silent && !stream->rdonly && (LOCAL->ld < 0))
   1.680 +    mmdf_abort (stream);	/* abort if can't get RW silent stream */
   1.681 +				/* parse mailbox */
   1.682 +  else if (mmdf_parse (stream,&lock,LOCK_SH)) {
   1.683 +    mmdf_unlock (LOCAL->fd,stream,&lock);
   1.684 +    mail_unlock (stream);
   1.685 +    MM_NOCRITICAL (stream);	/* done with critical */
   1.686 +  }
   1.687 +  if (!LOCAL) return NIL;	/* failure if stream died */
   1.688 +				/* make sure upper level knows readonly */
   1.689 +  stream->rdonly = (LOCAL->ld < 0);
   1.690 +				/* notify about empty mailbox */
   1.691 +  if (!(stream->nmsgs || stream->silent)) MM_LOG ("Mailbox is empty",NIL);
   1.692 +  if (!stream->rdonly) {	/* flags stick if readwrite */
   1.693 +    stream->perm_seen = stream->perm_deleted =
   1.694 +      stream->perm_flagged = stream->perm_answered = stream->perm_draft = T;
   1.695 +    if (!stream->uid_nosticky) {/* users with lives get permanent keywords */
   1.696 +      stream->perm_user_flags = 0xffffffff;
   1.697 +				/* and maybe can create them too! */
   1.698 +      stream->kwd_create = stream->user_flags[NUSERFLAGS-1] ? NIL : T;
   1.699 +    }
   1.700 +  }
   1.701 +  return stream;		/* return stream alive to caller */
   1.702 +}
   1.703 +
   1.704 +
   1.705 +/* MMDF mail close
   1.706 + * Accepts: MAIL stream
   1.707 + *	    close options
   1.708 + */
   1.709 +
   1.710 +void mmdf_close (MAILSTREAM *stream,long options)
   1.711 +{
   1.712 +  int silent = stream->silent;
   1.713 +  stream->silent = T;		/* go silent */
   1.714 +				/* expunge if requested */
   1.715 +  if (options & CL_EXPUNGE) mmdf_expunge (stream,NIL,NIL);
   1.716 +				/* else dump final checkpoint */
   1.717 +  else if (LOCAL->dirty) mmdf_check (stream);
   1.718 +  stream->silent = silent;	/* restore old silence state */
   1.719 +  mmdf_abort (stream);		/* now punt the file and local data */
   1.720 +}
   1.721 +
   1.722 +/* MMDF mail fetch message header
   1.723 + * Accepts: MAIL stream
   1.724 + *	    message # to fetch
   1.725 + *	    pointer to returned header text length
   1.726 + *	    option flags
   1.727 + * Returns: message header in RFC822 format
   1.728 + */
   1.729 +
   1.730 +				/* lines to filter from header */
   1.731 +static STRINGLIST *mmdf_hlines = NIL;
   1.732 +
   1.733 +char *mmdf_header (MAILSTREAM *stream,unsigned long msgno,
   1.734 +		   unsigned long *length,long flags)
   1.735 +{
   1.736 +  MESSAGECACHE *elt;
   1.737 +  unsigned char *s,*t,*tl;
   1.738 +  *length = 0;			/* default to empty */
   1.739 +  if (flags & FT_UID) return "";/* UID call "impossible" */
   1.740 +  elt = mail_elt (stream,msgno);/* get cache */
   1.741 +  if (!mmdf_hlines) {		/* once only code */
   1.742 +    STRINGLIST *lines = mmdf_hlines = mail_newstringlist ();
   1.743 +    lines->text.size = strlen ((char *) (lines->text.data =
   1.744 +					 (unsigned char *) "Status"));
   1.745 +    lines = lines->next = mail_newstringlist ();
   1.746 +    lines->text.size = strlen ((char *) (lines->text.data =
   1.747 +					 (unsigned char *) "X-Status"));
   1.748 +    lines = lines->next = mail_newstringlist ();
   1.749 +    lines->text.size = strlen ((char *) (lines->text.data =
   1.750 +					 (unsigned char *) "X-Keywords"));
   1.751 +    lines = lines->next = mail_newstringlist ();
   1.752 +    lines->text.size = strlen ((char *) (lines->text.data =
   1.753 +					 (unsigned char *) "X-UID"));
   1.754 +    lines = lines->next = mail_newstringlist ();
   1.755 +    lines->text.size = strlen ((char *) (lines->text.data =
   1.756 +					 (unsigned char *) "X-IMAP"));
   1.757 +    lines = lines->next = mail_newstringlist ();
   1.758 +    lines->text.size = strlen ((char *) (lines->text.data =
   1.759 +					 (unsigned char *) "X-IMAPbase"));
   1.760 +  }
   1.761 +				/* go to header position */
   1.762 +  lseek (LOCAL->fd,elt->private.special.offset +
   1.763 +	 elt->private.msg.header.offset,L_SET);
   1.764 +
   1.765 +  if (flags & FT_INTERNAL) {	/* initial data OK? */
   1.766 +    if (elt->private.msg.header.text.size > LOCAL->buflen) {
   1.767 +      fs_give ((void **) &LOCAL->buf);
   1.768 +      LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
   1.769 +				     elt->private.msg.header.text.size) + 1);
   1.770 +    }
   1.771 +				/* read message */
   1.772 +    read (LOCAL->fd,LOCAL->buf,elt->private.msg.header.text.size);
   1.773 +				/* got text, tie off string */
   1.774 +    LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0';
   1.775 +				/* squeeze out CRs (in case from PC) */
   1.776 +    for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
   1.777 +      if (*t != '\r') *s++ = *t;
   1.778 +    *s = '\0';
   1.779 +    *length = s - LOCAL->buf;	/* adjust length */
   1.780 +  }
   1.781 +  else {			/* need to make a CRLF version */
   1.782 +    read (LOCAL->fd,s = (char *) fs_get (elt->private.msg.header.text.size+1),
   1.783 +	  elt->private.msg.header.text.size);
   1.784 +				/* tie off string, and convert to CRLF */
   1.785 +    s[elt->private.msg.header.text.size] = '\0';
   1.786 +    *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s,
   1.787 +			  elt->private.msg.header.text.size);
   1.788 +    fs_give ((void **) &s);	/* free readin buffer */
   1.789 +				/* squeeze out spurious CRs */
   1.790 +    for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
   1.791 +      if ((*t != '\r') || (t[1] == '\n')) *s++ = *t;
   1.792 +    *s = '\0';
   1.793 +    *length = s - LOCAL->buf;	/* adjust length */
   1.794 +  }
   1.795 +  *length = mail_filter (LOCAL->buf,*length,mmdf_hlines,FT_NOT);
   1.796 +  return (char *) LOCAL->buf;	/* return processed copy */
   1.797 +}
   1.798 +
   1.799 +/* MMDF mail fetch message text
   1.800 + * Accepts: MAIL stream
   1.801 + *	    message # to fetch
   1.802 + *	    pointer to returned stringstruct
   1.803 + *	    option flags
   1.804 + * Returns: T on success, NIL if failure
   1.805 + */
   1.806 +
   1.807 +long mmdf_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
   1.808 +{
   1.809 +  char *s;
   1.810 +  unsigned long i;
   1.811 +  MESSAGECACHE *elt;
   1.812 +				/* UID call "impossible" */
   1.813 +  if (flags & FT_UID) return NIL;
   1.814 +  elt = mail_elt (stream,msgno);/* get cache element */
   1.815 +				/* if message not seen */
   1.816 +  if (!(flags & FT_PEEK) && !elt->seen) {
   1.817 +				/* mark message seen and dirty */
   1.818 +    elt->seen = elt->private.dirty = LOCAL->dirty = T;
   1.819 +    MM_FLAGS (stream,msgno);
   1.820 +  }
   1.821 +  s = mmdf_text_work (stream,elt,&i,flags);
   1.822 +  INIT (bs,mail_string,s,i);	/* set up stringstruct */
   1.823 +  return T;			/* success */
   1.824 +}
   1.825 +
   1.826 +/* MMDF mail fetch message text worker routine
   1.827 + * Accepts: MAIL stream
   1.828 + *	    message cache element
   1.829 + *	    pointer to returned header text length
   1.830 + *	    option flags
   1.831 + */
   1.832 +
   1.833 +char *mmdf_text_work (MAILSTREAM *stream,MESSAGECACHE *elt,
   1.834 +		      unsigned long *length,long flags)
   1.835 +{
   1.836 +  FDDATA d;
   1.837 +  STRING bs;
   1.838 +  unsigned char c,*s,*t,*tl,tmp[CHUNKSIZE];
   1.839 +				/* go to text position */
   1.840 +  lseek (LOCAL->fd,elt->private.special.offset +
   1.841 +	 elt->private.msg.text.offset,L_SET);
   1.842 +  if (flags & FT_INTERNAL) {	/* initial data OK? */
   1.843 +    if (elt->private.msg.text.text.size > LOCAL->buflen) {
   1.844 +      fs_give ((void **) &LOCAL->buf);
   1.845 +      LOCAL->buf = (char *) fs_get ((LOCAL->buflen =
   1.846 +				     elt->private.msg.text.text.size) + 1);
   1.847 +    }
   1.848 +				/* read message */
   1.849 +    read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size);
   1.850 +				/* got text, tie off string */
   1.851 +    LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0';
   1.852 +				/* squeeze out CRs (in case from PC) */
   1.853 +    for (s = t = LOCAL->buf,tl = LOCAL->buf + *length; t < tl; t++)
   1.854 +      if (*t != '\r') *s++ = *t;
   1.855 +    *s = '\0';
   1.856 +    *length = s - LOCAL->buf;	/* adjust length */
   1.857 +    return (char *) LOCAL->buf;
   1.858 +  }
   1.859 +
   1.860 +				/* have it cached already? */
   1.861 +  if (elt->private.uid != LOCAL->uid) {
   1.862 +				/* not cached, cache it now */
   1.863 +    LOCAL->uid = elt->private.uid;
   1.864 +				/* is buffer big enough? */
   1.865 +    if (elt->rfc822_size > LOCAL->text.size) {
   1.866 +      /* excessively conservative, but the right thing is too hard to do */
   1.867 +      fs_give ((void **) &LOCAL->text.data);
   1.868 +      LOCAL->text.data = (unsigned char *)
   1.869 +	fs_get ((LOCAL->text.size = elt->rfc822_size) + 1);
   1.870 +    }
   1.871 +    d.fd = LOCAL->fd;		/* yes, set up file descriptor */
   1.872 +    d.pos = elt->private.special.offset + elt->private.msg.text.offset;
   1.873 +    d.chunk = tmp;		/* initial buffer chunk */
   1.874 +    d.chunksize = CHUNKSIZE;	/* file chunk size */
   1.875 +    INIT (&bs,fd_string,&d,elt->private.msg.text.text.size);
   1.876 +    for (s = (char *) LOCAL->text.data; SIZE (&bs);) switch (c = SNX (&bs)) {
   1.877 +    case '\r':			/* carriage return seen */
   1.878 +      break;
   1.879 +    case '\n':
   1.880 +      *s++ = '\r';		/* insert a CR */
   1.881 +    default:
   1.882 +      *s++ = c;			/* copy characters */
   1.883 +    }
   1.884 +    *s = '\0';			/* tie off buffer */
   1.885 +				/* calculate length of cached data */
   1.886 +    LOCAL->textlen = s - LOCAL->text.data;
   1.887 +  }
   1.888 +  *length = LOCAL->textlen;	/* return from cache */
   1.889 +  return (char *) LOCAL->text.data;
   1.890 +}
   1.891 +
   1.892 +/* MMDF per-message modify flag
   1.893 + * Accepts: MAIL stream
   1.894 + *	    message cache element
   1.895 + */
   1.896 +
   1.897 +void mmdf_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
   1.898 +{
   1.899 +				/* only after finishing */
   1.900 +  if (elt->valid) elt->private.dirty = LOCAL->dirty = T;
   1.901 +}
   1.902 +
   1.903 +
   1.904 +/* MMDF mail ping mailbox
   1.905 + * Accepts: MAIL stream
   1.906 + * Returns: T if stream alive, else NIL
   1.907 + */
   1.908 +
   1.909 +long mmdf_ping (MAILSTREAM *stream)
   1.910 +{
   1.911 +  DOTLOCK lock;
   1.912 +  struct stat sbuf;
   1.913 +  long reparse;
   1.914 +				/* big no-op if not readwrite */
   1.915 +  if (LOCAL && (LOCAL->ld >= 0) && !stream->lock) {
   1.916 +    if (stream->rdonly) {	/* does he want to give up readwrite? */
   1.917 +				/* checkpoint if we changed something */
   1.918 +      if (LOCAL->dirty) mmdf_check (stream);
   1.919 +      flock (LOCAL->ld,LOCK_UN);/* release readwrite lock */
   1.920 +      close (LOCAL->ld);	/* close the readwrite lock file */
   1.921 +      LOCAL->ld = -1;		/* no more readwrite lock fd */
   1.922 +      unlink (LOCAL->lname);	/* delete the readwrite lock file */
   1.923 +    }
   1.924 +    else {			/* see if need to reparse */
   1.925 +      if (!(reparse = (long) mail_parameters (NIL,GET_NETFSSTATBUG,NIL))) {
   1.926 +				/* get current mailbox size */
   1.927 +	if (LOCAL->fd >= 0) fstat (LOCAL->fd,&sbuf);
   1.928 +	else if (stat (stream->mailbox,&sbuf)) {
   1.929 +	  sprintf (LOCAL->buf,"Mailbox stat failed, aborted: %s",
   1.930 +		   strerror (errno));
   1.931 +	  MM_LOG (LOCAL->buf,ERROR);
   1.932 +	  mmdf_abort (stream);
   1.933 +	  return NIL;
   1.934 +	}
   1.935 +	reparse = (sbuf.st_size != LOCAL->filesize);
   1.936 +      }
   1.937 +				/* parse if mailbox changed */
   1.938 +      if ((LOCAL->ddirty || reparse) && mmdf_parse (stream,&lock,LOCK_EX)) {
   1.939 +				/* force checkpoint if double-dirty */
   1.940 +	if (LOCAL->ddirty) mmdf_rewrite (stream,NIL,&lock,NIL);
   1.941 +				/* unlock mailbox */
   1.942 +	else mmdf_unlock (LOCAL->fd,stream,&lock);
   1.943 +	mail_unlock (stream);	/* and stream */
   1.944 +				/* done with critical */
   1.945 +	MM_NOCRITICAL (stream);
   1.946 +      }
   1.947 +    }
   1.948 +  }
   1.949 +  return LOCAL ? LONGT : NIL;	/* return if still alive */
   1.950 +}
   1.951 +
   1.952 +/* MMDF mail check mailbox
   1.953 + * Accepts: MAIL stream
   1.954 + */
   1.955 +
   1.956 +void mmdf_check (MAILSTREAM *stream)
   1.957 +{
   1.958 +  DOTLOCK lock;
   1.959 +				/* parse and lock mailbox */
   1.960 +  if (LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
   1.961 +      mmdf_parse (stream,&lock,LOCK_EX)) {
   1.962 +				/* any unsaved changes? */
   1.963 +    if (LOCAL->dirty && mmdf_rewrite (stream,NIL,&lock,NIL)) {
   1.964 +      if (!stream->silent) MM_LOG ("Checkpoint completed",NIL);
   1.965 +    }
   1.966 +				/* no checkpoint needed, just unlock */
   1.967 +    else mmdf_unlock (LOCAL->fd,stream,&lock);
   1.968 +    mail_unlock (stream);	/* unlock the stream */
   1.969 +    MM_NOCRITICAL (stream);	/* done with critical */
   1.970 +  }
   1.971 +}
   1.972 +
   1.973 +
   1.974 +/* MMDF mail expunge mailbox
   1.975 + * Accepts: MAIL stream
   1.976 + *	    sequence to expunge if non-NIL
   1.977 + *	    expunge options
   1.978 + * Returns: T, always
   1.979 + */
   1.980 +
   1.981 +long mmdf_expunge (MAILSTREAM *stream,char *sequence,long options)
   1.982 +{
   1.983 +  long ret;
   1.984 +  unsigned long i;
   1.985 +  DOTLOCK lock;
   1.986 +  char *msg = NIL;
   1.987 +  if (ret = (sequence ? ((options & EX_UID) ?
   1.988 +			 mail_uid_sequence (stream,sequence) :
   1.989 +			 mail_sequence (stream,sequence)) : LONGT) &&
   1.990 +      LOCAL && (LOCAL->ld >= 0) && !stream->lock &&
   1.991 +      mmdf_parse (stream,&lock,LOCK_EX)) {
   1.992 +				/* check expunged messages if not dirty */
   1.993 +    for (i = 1; !LOCAL->dirty && (i <= stream->nmsgs); i++) {
   1.994 +      MESSAGECACHE *elt = mail_elt (stream,i);
   1.995 +      if (mail_elt (stream,i)->deleted) LOCAL->dirty = T;
   1.996 +    }
   1.997 +    if (!LOCAL->dirty) {	/* not dirty and no expunged messages */
   1.998 +      mmdf_unlock (LOCAL->fd,stream,&lock);
   1.999 +      msg = "No messages deleted, so no update needed";
  1.1000 +    }
  1.1001 +    else if (mmdf_rewrite (stream,&i,&lock,sequence ? LONGT : NIL)) {
  1.1002 +      if (i) sprintf (msg = LOCAL->buf,"Expunged %lu messages",i);
  1.1003 +      else msg = "Mailbox checkpointed, but no messages expunged";
  1.1004 +    }
  1.1005 +				/* rewrite failed */
  1.1006 +    else mmdf_unlock (LOCAL->fd,stream,&lock);
  1.1007 +    mail_unlock (stream);	/* unlock the stream */
  1.1008 +    MM_NOCRITICAL (stream);	/* done with critical */
  1.1009 +    if (msg && !stream->silent) MM_LOG (msg,NIL);
  1.1010 +  }
  1.1011 +  else if (!stream->silent)
  1.1012 +    MM_LOG ("Expunge ignored on readonly mailbox",WARN);
  1.1013 +  return ret;
  1.1014 +}
  1.1015 +
  1.1016 +/* MMDF mail copy message(s)
  1.1017 + * Accepts: MAIL stream
  1.1018 + *	    sequence
  1.1019 + *	    destination mailbox
  1.1020 + *	    copy options
  1.1021 + * Returns: T if copy successful, else NIL
  1.1022 + */
  1.1023 +
  1.1024 +long mmdf_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
  1.1025 +{
  1.1026 +  struct stat sbuf;
  1.1027 +  int fd;
  1.1028 +  char *s,file[MAILTMPLEN];
  1.1029 +  DOTLOCK lock;
  1.1030 +  time_t tp[2];
  1.1031 +  unsigned long i,j;
  1.1032 +  MESSAGECACHE *elt;
  1.1033 +  long ret = T;
  1.1034 +  mailproxycopy_t pc =
  1.1035 +    (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
  1.1036 +  copyuid_t cu = (copyuid_t) (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ?
  1.1037 +			      NIL : mail_parameters (NIL,GET_COPYUID,NIL));
  1.1038 +  SEARCHSET *source = cu ? mail_newsearchset () : NIL;
  1.1039 +  SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
  1.1040 +  MAILSTREAM *tstream = NIL;
  1.1041 +  if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
  1.1042 +	mail_sequence (stream,sequence))) return NIL;
  1.1043 +				/* make sure destination is valid */
  1.1044 +  if (!(mmdf_valid (mailbox) || !errno))
  1.1045 +    switch (errno) {
  1.1046 +    case ENOENT:		/* no such file? */
  1.1047 +      if (compare_cstring (mailbox,"INBOX")) {
  1.1048 +	MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
  1.1049 +	return NIL;
  1.1050 +      }
  1.1051 +      if (pc) return (*pc) (stream,sequence,mailbox,options);
  1.1052 +      mmdf_create (NIL,"INBOX");/* create empty INBOX */
  1.1053 +    case EACCES:		/* file protected */
  1.1054 +      sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
  1.1055 +      MM_LOG (LOCAL->buf,ERROR);
  1.1056 +      return NIL;
  1.1057 +    case EINVAL:
  1.1058 +      if (pc) return (*pc) (stream,sequence,mailbox,options);
  1.1059 +      sprintf (LOCAL->buf,"Invalid MMDF-format mailbox name: %.80s",mailbox);
  1.1060 +      MM_LOG (LOCAL->buf,ERROR);
  1.1061 +      return NIL;
  1.1062 +    default:
  1.1063 +      if (pc) return (*pc) (stream,sequence,mailbox,options);
  1.1064 +      sprintf (LOCAL->buf,"Not a MMDF-format mailbox: %.80s",mailbox);
  1.1065 +      MM_LOG (LOCAL->buf,ERROR);
  1.1066 +      return NIL;
  1.1067 +    }
  1.1068 +
  1.1069 +				/* try to open rewrite for UIDPLUS */
  1.1070 +  if ((tstream = mail_open_work (&mmdfdriver,NIL,mailbox,
  1.1071 +				 OP_SILENT|OP_NOKOD)) && tstream->rdonly)
  1.1072 +    tstream = mail_close (tstream);
  1.1073 +  if (cu && !tstream) {		/* wanted a COPYUID? */
  1.1074 +    sprintf (LOCAL->buf,"Unable to write-open mailbox for COPYUID: %.80s",
  1.1075 +	     mailbox);
  1.1076 +    MM_LOG (LOCAL->buf,WARN);
  1.1077 +    cu = NIL;			/* don't try to do COPYUID */
  1.1078 +  }
  1.1079 +  LOCAL->buf[0] = '\0';
  1.1080 +  MM_CRITICAL (stream);		/* go critical */
  1.1081 +  if ((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
  1.1082 +		       (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
  1.1083 +		       &lock,LOCK_EX)) < 0) {
  1.1084 +    MM_NOCRITICAL (stream);	/* done with critical */
  1.1085 +    sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  1.1086 +    MM_LOG (LOCAL->buf,ERROR);	/* log the error */
  1.1087 +    return NIL;			/* failed */
  1.1088 +  }
  1.1089 +  fstat (fd,&sbuf);		/* get current file size */
  1.1090 +				/* write all requested messages to mailbox */
  1.1091 +  for (i = 1; ret && (i <= stream->nmsgs); i++)
  1.1092 +    if ((elt = mail_elt (stream,i))->sequence) {
  1.1093 +      lseek (LOCAL->fd,elt->private.special.offset,L_SET);
  1.1094 +      read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
  1.1095 +      if (write (fd,LOCAL->buf,elt->private.special.text.size) < 0) ret = NIL;
  1.1096 +      else {			/* internal header succeeded */
  1.1097 +	s = mmdf_header (stream,i,&j,FT_INTERNAL);
  1.1098 +				/* header size, sans trailing newline */
  1.1099 +	if (j && (s[j - 2] == '\n')) j--;
  1.1100 +	if (write (fd,s,j) < 0) ret = NIL;
  1.1101 +	else {			/* message header succeeded */
  1.1102 +	  j = tstream ?		/* write UIDPLUS data if have readwrite */
  1.1103 +	    mmdf_xstatus (stream,LOCAL->buf,elt,++(tstream->uid_last),LONGT) :
  1.1104 +	    mmdf_xstatus (stream,LOCAL->buf,elt,NIL,NIL);
  1.1105 +	  if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
  1.1106 +	  else {		/* message status succeeded */
  1.1107 +	    s = mmdf_text_work (stream,elt,&j,FT_INTERNAL);
  1.1108 +	    if ((write (fd,s,j) < 0) || (write (fd,mmdfhdr,MMDFHDRLEN) < 0))
  1.1109 +	      ret = NIL;
  1.1110 +	    else if (cu) {	/* need to pass back new UID? */
  1.1111 +	      mail_append_set (source,mail_uid (stream,i));
  1.1112 +	      mail_append_set (dest,tstream->uid_last);
  1.1113 +	    }
  1.1114 +	  }
  1.1115 +	}
  1.1116 +      }
  1.1117 +    }
  1.1118 +
  1.1119 +  if (!ret || fsync (fd)) {	/* force out the update */
  1.1120 +    sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
  1.1121 +    ftruncate (fd,sbuf.st_size);
  1.1122 +    ret = NIL;
  1.1123 +  }
  1.1124 +				/* force UIDVALIDITY assignment now */
  1.1125 +  if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
  1.1126 +				/* return sets if doing COPYUID */
  1.1127 +  if (cu && ret) (*cu) (stream,mailbox,tstream->uid_validity,source,dest);
  1.1128 +  else {			/* flush any sets we may have built */
  1.1129 +    mail_free_searchset (&source);
  1.1130 +    mail_free_searchset (&dest);
  1.1131 +  }
  1.1132 +  tp[1] = time (0);		/* set mtime to now */
  1.1133 +  if (ret) tp[0] = tp[1] - 1;	/* set atime to now-1 if successful copy */
  1.1134 +  else tp[0] =			/* else preserve \Marked status */
  1.1135 +	 ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
  1.1136 +	 sbuf.st_atime : tp[1];
  1.1137 +  utime (file,tp);		/* set the times */
  1.1138 +  mmdf_unlock (fd,NIL,&lock);	/* unlock and close mailbox */
  1.1139 +  if (tstream) {		/* update last UID if we can */
  1.1140 +    MMDFLOCAL *local = (MMDFLOCAL *) tstream->local;
  1.1141 +    local->dirty = T;		/* do a rewrite */
  1.1142 +    local->appending = T;	/* but not at the cost of marking as old */
  1.1143 +    tstream = mail_close (tstream);
  1.1144 +  }
  1.1145 +				/* log the error */
  1.1146 +  if (!ret) MM_LOG (LOCAL->buf,ERROR);
  1.1147 +				/* delete if requested message */
  1.1148 +  else if (options & CP_MOVE) for (i = 1; i <= stream->nmsgs; i++)
  1.1149 +    if ((elt = mail_elt (stream,i))->sequence)
  1.1150 +      elt->deleted = elt->private.dirty = LOCAL->dirty = T;
  1.1151 +  MM_NOCRITICAL (stream);	/* release critical */
  1.1152 +  return ret;
  1.1153 +}
  1.1154 +
  1.1155 +/* MMDF mail append message from stringstruct
  1.1156 + * Accepts: MAIL stream
  1.1157 + *	    destination mailbox
  1.1158 + *	    append callback
  1.1159 + *	    data for callback
  1.1160 + * Returns: T if append successful, else NIL
  1.1161 + */
  1.1162 +
  1.1163 +#define BUFLEN 8*MAILTMPLEN
  1.1164 +
  1.1165 +long mmdf_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
  1.1166 +{
  1.1167 +  struct stat sbuf;
  1.1168 +  int fd;
  1.1169 +  unsigned long i;
  1.1170 +  char *flags,*date,buf[BUFLEN],tmp[MAILTMPLEN],file[MAILTMPLEN];
  1.1171 +  time_t tp[2];
  1.1172 +  FILE *sf,*df;
  1.1173 +  MESSAGECACHE elt;
  1.1174 +  DOTLOCK lock;
  1.1175 +  STRING *message;
  1.1176 +  unsigned long uidlocation = 0;
  1.1177 +  appenduid_t au = (appenduid_t)
  1.1178 +    (mail_parameters (NIL,GET_USERHASNOLIFE,NIL) ? NIL :
  1.1179 +     mail_parameters (NIL,GET_APPENDUID,NIL));
  1.1180 +  SEARCHSET *dst = au ? mail_newsearchset () : NIL;
  1.1181 +  long ret = LONGT;
  1.1182 +  MAILSTREAM *tstream = NIL;
  1.1183 +				/* default stream to prototype */
  1.1184 +  if (!stream) {		/* stream specified? */
  1.1185 +    stream = &mmdfproto;	/* no, default stream to prototype */
  1.1186 +    for (i = 0; i < NUSERFLAGS && stream->user_flags[i]; ++i)
  1.1187 +      fs_give ((void **) &stream->user_flags[i]);
  1.1188 +  }
  1.1189 +  if (!mmdf_valid (mailbox)) switch (errno) {
  1.1190 +  case ENOENT:			/* no such file? */
  1.1191 +    if (compare_cstring (mailbox,"INBOX")) {
  1.1192 +      MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
  1.1193 +      return NIL;
  1.1194 +    }
  1.1195 +    mmdf_create (NIL,"INBOX");	/* create empty INBOX */
  1.1196 +  case 0:			/* merely empty file? */
  1.1197 +    tstream = stream;
  1.1198 +    break;
  1.1199 +  case EACCES:			/* file protected */
  1.1200 +    sprintf (tmp,"Can't access destination: %.80s",mailbox);
  1.1201 +    MM_LOG (tmp,ERROR);
  1.1202 +    return NIL;
  1.1203 +  case EINVAL:
  1.1204 +    sprintf (tmp,"Invalid MMDF-format mailbox name: %.80s",mailbox);
  1.1205 +    MM_LOG (tmp,ERROR);
  1.1206 +    return NIL;
  1.1207 +  default:
  1.1208 +    sprintf (tmp,"Not a MMDF-format mailbox: %.80s",mailbox);
  1.1209 +    MM_LOG (tmp,ERROR);
  1.1210 +    return NIL;
  1.1211 +  }
  1.1212 +				/* get sniffing stream for keywords */
  1.1213 +  else if (!(tstream = mail_open (NIL,mailbox,
  1.1214 +				  OP_READONLY|OP_SILENT|OP_NOKOD|OP_SNIFF))) {
  1.1215 +    sprintf (tmp,"Unable to examine mailbox for APPEND: %.80s",mailbox);
  1.1216 +    MM_LOG (tmp,ERROR);
  1.1217 +    return NIL;
  1.1218 +  }
  1.1219 +
  1.1220 +				/* get first message */
  1.1221 +  if (!MM_APPEND (af) (tstream,data,&flags,&date,&message)) return NIL;
  1.1222 +  if (!(sf = tmpfile ())) {	/* must have scratch file */
  1.1223 +    sprintf (tmp,".%lx.%lx",(unsigned long) time (0),(unsigned long)getpid ());
  1.1224 +    if (!stat (tmp,&sbuf) || !(sf = fopen (tmp,"wb+"))) {
  1.1225 +      sprintf (tmp,"Unable to create scratch file: %.80s",strerror (errno));
  1.1226 +      MM_LOG (tmp,ERROR);
  1.1227 +      return NIL;
  1.1228 +    }
  1.1229 +    unlink (tmp);
  1.1230 +  }
  1.1231 +  do {				/* parse date */
  1.1232 +    if (!date) rfc822_date (date = tmp);
  1.1233 +    if (!mail_parse_date (&elt,date)) {
  1.1234 +      sprintf (tmp,"Bad date in append: %.80s",date);
  1.1235 +      MM_LOG (tmp,ERROR);
  1.1236 +    }
  1.1237 +    else {			/* user wants to suppress time zones? */
  1.1238 +      if (mail_parameters (NIL,GET_NOTIMEZONES,NIL)) {
  1.1239 +	time_t when = mail_longdate (&elt);
  1.1240 +	date = ctime (&when);	/* use traditional date */
  1.1241 +      }
  1.1242 +				/* use POSIX-style date */
  1.1243 +      else date = mail_cdate (tmp,&elt);
  1.1244 +      if (!SIZE (message)) MM_LOG ("Append of zero-length message",ERROR);
  1.1245 +      else if (!mmdf_collect_msg (tstream,sf,flags,date,message)) {
  1.1246 +	sprintf (tmp,"Error writing scratch file: %.80s",strerror (errno));
  1.1247 +	MM_LOG (tmp,ERROR);
  1.1248 +      }
  1.1249 +				/* get next message */
  1.1250 +      else if (MM_APPEND (af) (tstream,data,&flags,&date,&message)) continue;
  1.1251 +    }
  1.1252 +    fclose (sf);		/* punt scratch file */
  1.1253 +    return NIL;			/* give up */
  1.1254 +  } while (message);		/* until no more messages */
  1.1255 +  if (fflush (sf)) {
  1.1256 +    sprintf (tmp,"Error finishing scratch file: %.80s",strerror (errno));
  1.1257 +    MM_LOG (tmp,ERROR);
  1.1258 +    fclose (sf);		/* punt scratch file */
  1.1259 +    return NIL;			/* give up */
  1.1260 +  }
  1.1261 +  i = ftell (sf);		/* size of scratch file */
  1.1262 +  if (tstream != stream) tstream = mail_close (tstream);
  1.1263 +
  1.1264 +  MM_CRITICAL (stream);		/* go critical */
  1.1265 +				/* try to open readwrite for UIDPLUS */
  1.1266 +  if ((tstream = mail_open_work (&mmdfdriver,NIL,mailbox,
  1.1267 +				 OP_SILENT|OP_NOKOD)) && tstream->rdonly)
  1.1268 +    tstream = mail_close (tstream);
  1.1269 +  if (au && !tstream) {		/* wanted an APPENDUID? */
  1.1270 +    sprintf (tmp,"Unable to re-open mailbox for APPENDUID: %.80s",mailbox);
  1.1271 +    MM_LOG (tmp,WARN);
  1.1272 +    au = NIL;
  1.1273 +  }
  1.1274 +  if (((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND,
  1.1275 +			(long) mail_parameters (NIL,GET_MBXPROTECTION,NIL),
  1.1276 +			&lock,LOCK_EX)) < 0) ||
  1.1277 +      !(df = fdopen (fd,"ab"))) {
  1.1278 +    MM_NOCRITICAL (stream);	/* done with critical */
  1.1279 +    sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
  1.1280 +    MM_LOG (tmp,ERROR);
  1.1281 +    return NIL;
  1.1282 +  }
  1.1283 +  fstat (fd,&sbuf);		/* get current file size */
  1.1284 +  rewind (sf);
  1.1285 +  tp[1] = time (0);		/* set mtime to now */
  1.1286 +				/* write all messages */
  1.1287 +  if (!mmdf_append_msgs (tstream,sf,df,au ? dst : NIL) ||
  1.1288 +      (fflush (df) == EOF) || fsync (fd)) {
  1.1289 +    sprintf (buf,"Message append failed: %s",strerror (errno));
  1.1290 +    MM_LOG (buf,ERROR);
  1.1291 +    ftruncate (fd,sbuf.st_size);
  1.1292 +    tp[0] =			/* preserve \Marked status */
  1.1293 +      ((sbuf.st_ctime > sbuf.st_atime) || (sbuf.st_mtime > sbuf.st_atime)) ?
  1.1294 +      sbuf.st_atime : tp[1];
  1.1295 +    ret = NIL;			/* return error */
  1.1296 +  }
  1.1297 +  else tp[0] = tp[1] - 1;	/* set atime to now-1 if successful copy */
  1.1298 +  utime (file,tp);		/* set the times */
  1.1299 +  fclose (sf);			/* done with scratch file */
  1.1300 +				/* force UIDVALIDITY assignment now */
  1.1301 +  if (tstream && !tstream->uid_validity) tstream->uid_validity = time (0);
  1.1302 +				/* return sets if doing APPENDUID */
  1.1303 +  if (au && ret) (*au) (mailbox,tstream->uid_validity,dst);
  1.1304 +  else mail_free_searchset (&dst);
  1.1305 +  mmdf_unlock (fd,NIL,&lock);	/* unlock and close mailbox */
  1.1306 +  fclose (df);
  1.1307 +  if (tstream) {		/* update last UID if we can */
  1.1308 +    MMDFLOCAL *local = (MMDFLOCAL *) tstream->local;
  1.1309 +    local->dirty = T;		/* do a rewrite */
  1.1310 +    local->appending = T;	/* but not at the cost of marking as old */
  1.1311 +    tstream = mail_close (tstream);
  1.1312 +  }
  1.1313 +  MM_NOCRITICAL (stream);	/* release critical */
  1.1314 +  return ret;
  1.1315 +}
  1.1316 +
  1.1317 +/* Collect and write single message to append scratch file
  1.1318 + * Accepts: MAIL stream
  1.1319 + *	    scratch file
  1.1320 + *	    flags
  1.1321 + *	    date
  1.1322 + *	    message stringstruct
  1.1323 + * Returns: NIL if write error, else T
  1.1324 + */
  1.1325 +
  1.1326 +int mmdf_collect_msg (MAILSTREAM *stream,FILE *sf,char *flags,char *date,
  1.1327 +  		     STRING *msg)
  1.1328 +{
  1.1329 +  unsigned char *s,*t;
  1.1330 +  unsigned long uf;
  1.1331 +  long f = mail_parse_flags (stream,flags,&uf);
  1.1332 +				/* write metadata, note date ends with NL */
  1.1333 +  if (fprintf (sf,"%ld %lu %s",f,SIZE (msg) + 1,date) < 0) return NIL;
  1.1334 +  while (uf)			/* write user flags */    
  1.1335 +    if ((s = stream->user_flags[find_rightmost_bit (&uf)]) &&
  1.1336 +	(fprintf (sf," %s",s) < 0)) return NIL;
  1.1337 +  if (putc ('\n',sf) == EOF) return NIL;
  1.1338 +  while (SIZE (msg)) {		/* copy text to scratch file */
  1.1339 +    for (s = (unsigned char *) msg->curpos, t = s + msg->cursize; s < t; ++s)
  1.1340 +      if (!*s) *s = 0x80;	/* disallow NUL */
  1.1341 +				/* write buffered text */
  1.1342 +    if (fwrite (msg->curpos,1,msg->cursize,sf) == msg->cursize)
  1.1343 +      SETPOS (msg,GETPOS (msg) + msg->cursize);
  1.1344 +    else return NIL;		/* failed */
  1.1345 +  }
  1.1346 +				/* write trailing newline and return */
  1.1347 +  return (putc ('\n',sf) == EOF) ? NIL : T;
  1.1348 +}
  1.1349 +
  1.1350 +/* Append messages from scratch file to mailbox
  1.1351 + * Accepts: MAIL stream
  1.1352 + *	    source file
  1.1353 + *	    destination file
  1.1354 + *	    uidset to update if non-NIL
  1.1355 + * Returns: T if success, NIL if failure
  1.1356 + */
  1.1357 +
  1.1358 +int mmdf_append_msgs (MAILSTREAM *stream,FILE *sf,FILE *df,SEARCHSET *set)
  1.1359 +{
  1.1360 +  int c;
  1.1361 +  long f;
  1.1362 +  unsigned long i,j;
  1.1363 +  char *x,tmp[MAILTMPLEN];
  1.1364 +  int hdrp = T;
  1.1365 +				/* get message metadata line */
  1.1366 +  while (fgets (tmp,MAILTMPLEN,sf)) {
  1.1367 +    if (!(isdigit (tmp[0]) && strchr (tmp,'\n'))) return NIL;
  1.1368 +    f = strtol (tmp,&x,10);	/* get flags */
  1.1369 +    if (!((*x++ == ' ') && isdigit (*x))) return NIL;
  1.1370 +    i = strtoul (x,&x,10);	/* get message size */
  1.1371 +    if ((*x++ != ' ') ||	/* build initial header */
  1.1372 +	(fprintf (df,"%sFrom %s@%s %sStatus: ",mmdfhdr,myusername(),
  1.1373 +		  mylocalhost(),x) < 0) ||
  1.1374 +	(f&fSEEN && (putc ('R',df) == EOF)) ||
  1.1375 +	(fputs ("\nX-Status: ",df) == EOF) ||
  1.1376 +	(f&fDELETED && (putc ('D',df) == EOF)) ||
  1.1377 +	(f&fFLAGGED && (putc ('F',df) == EOF)) ||
  1.1378 +	(f&fANSWERED && (putc ('A',df) == EOF)) ||
  1.1379 +	(f&fDRAFT && (putc ('T',df) == EOF)) ||
  1.1380 +	(fputs ("\nX-Keywords:",df) == EOF)) return NIL;
  1.1381 +				/* copy keywords */
  1.1382 +    while ((c = getc (sf)) != '\n') switch (c) {
  1.1383 +    case EOF:
  1.1384 +      return NIL;
  1.1385 +    default:
  1.1386 +      if (putc (c,df) == EOF) return NIL;
  1.1387 +    }
  1.1388 +    if ((putc ('\n',df) == EOF) ||
  1.1389 +	(set && (fprintf (df,"X-UID: %lu\n",++(stream->uid_last)) < 0)))
  1.1390 +      return NIL;
  1.1391 +
  1.1392 +    for (c = '\n'; i && fgets (tmp,MAILTMPLEN,sf); c = tmp[j-1]) {
  1.1393 +				/* get read line length */
  1.1394 +      if (i < (j = strlen (tmp))) fatal ("mmdf_append_msgs overrun");
  1.1395 +      i -= j;			/* number of bytes left */
  1.1396 +				/* squish out ^A and CRs (note copies NUL) */
  1.1397 +      for (x = tmp; x = strpbrk (x,"\01\r"); --j) memmove (x,x+1,j-(x-tmp));
  1.1398 +      if (!j) continue;		/* do nothing if line emptied */
  1.1399 +				/* start of line? */
  1.1400 +      if ((c == '\n')) switch (tmp[0]) {
  1.1401 +      case 'S': case 's':	/* possible "Status:" */
  1.1402 +	if (hdrp && (j > 6) && ((tmp[1] == 't') || (tmp[1] == 'T')) &&
  1.1403 +	    ((tmp[2] == 'a') || (tmp[2] == 'A')) &&
  1.1404 +	    ((tmp[3] == 't') || (tmp[3] == 'T')) &&
  1.1405 +	    ((tmp[4] == 'u') || (tmp[4] == 'U')) &&
  1.1406 +	    ((tmp[5] == 's') || (tmp[5] == 'S')) && (tmp[6] == ':') &&
  1.1407 +	    (fputs ("X-Original-",df) == EOF)) return NIL;
  1.1408 +	break;
  1.1409 +      case 'X': case 'x':	/* possible X-??? header */
  1.1410 +	if (hdrp && (tmp[1] == '-') &&
  1.1411 +				/* possible X-UID: */
  1.1412 +	    (((j > 5) && ((tmp[2] == 'U') || (tmp[2] == 'u')) &&
  1.1413 +	      ((tmp[3] == 'I') || (tmp[3] == 'i')) &&
  1.1414 +	      ((tmp[4] == 'D') || (tmp[4] == 'd')) && (tmp[5] == ':')) ||
  1.1415 +				/* possible X-IMAP: */
  1.1416 +	     ((j > 6) && ((tmp[2] == 'I') || (tmp[2] == 'i')) &&
  1.1417 +	      ((tmp[3] == 'M') || (tmp[3] == 'm')) &&
  1.1418 +	      ((tmp[4] == 'A') || (tmp[4] == 'a')) &&
  1.1419 +	      ((tmp[5] == 'P') || (tmp[5] == 'p')) &&
  1.1420 +	      ((tmp[6] == ':') ||
  1.1421 +				/* or X-IMAPbase: */
  1.1422 +	       ((j > 10) && ((tmp[6] == 'b') || (tmp[6] == 'B')) &&
  1.1423 +		((tmp[7] == 'a') || (tmp[7] == 'A')) &&
  1.1424 +		((tmp[8] == 's') || (tmp[8] == 'S')) &&
  1.1425 +		((tmp[9] == 'e') || (tmp[9] == 'E')) && (tmp[10] == ':')))) ||
  1.1426 +				/* possible X-Status: */
  1.1427 +	     ((j > 8) && ((tmp[2] == 'S') || (tmp[2] == 's')) &&
  1.1428 +	      ((tmp[3] == 't') || (tmp[3] == 'T')) &&
  1.1429 +	      ((tmp[4] == 'a') || (tmp[4] == 'A')) &&
  1.1430 +	      ((tmp[5] == 't') || (tmp[5] == 'T')) &&
  1.1431 +	      ((tmp[6] == 'u') || (tmp[6] == 'U')) &&
  1.1432 +	      ((tmp[7] == 's') || (tmp[7] == 'S')) && (tmp[8] == ':')) ||
  1.1433 +				/* possible X-Keywords: */
  1.1434 +	     ((j > 10) && ((tmp[2] == 'K') || (tmp[2] == 'k')) &&
  1.1435 +	      ((tmp[3] == 'e') || (tmp[3] == 'E')) &&
  1.1436 +	      ((tmp[4] == 'y') || (tmp[4] == 'Y')) &&
  1.1437 +	      ((tmp[5] == 'w') || (tmp[5] == 'W')) &&
  1.1438 +	      ((tmp[6] == 'o') || (tmp[6] == 'O')) &&
  1.1439 +	      ((tmp[7] == 'r') || (tmp[7] == 'R')) &&
  1.1440 +	      ((tmp[8] == 'd') || (tmp[8] == 'D')) &&
  1.1441 +	      ((tmp[9] == 's') || (tmp[9] == 'S')) && (tmp[10] == ':'))) &&
  1.1442 +	    (fputs ("X-Original-",df) == EOF)) return NIL;
  1.1443 +	break;
  1.1444 +      case '\n':		/* blank line */
  1.1445 +	hdrp = NIL;
  1.1446 +	break;
  1.1447 +      default:			/* nothing to do */
  1.1448 +	break;
  1.1449 +      }
  1.1450 +				/* just write the line */
  1.1451 +      if (fwrite (tmp,1,j,df) != j) return NIL;
  1.1452 +    }
  1.1453 +				/* make sure read entire msg & wrote trailer */
  1.1454 +    if (i || (fputs (mmdfhdr,df) == EOF)) return NIL;
  1.1455 +				/* update set */
  1.1456 +    if (stream) mail_append_set (set,stream->uid_last);
  1.1457 +  }
  1.1458 +  return T;
  1.1459 +}
  1.1460 +
  1.1461 +/* Internal routines */
  1.1462 +
  1.1463 +
  1.1464 +/* MMDF mail abort stream
  1.1465 + * Accepts: MAIL stream
  1.1466 + */
  1.1467 +
  1.1468 +void mmdf_abort (MAILSTREAM *stream)
  1.1469 +{
  1.1470 +  if (LOCAL) {			/* only if a file is open */
  1.1471 +    if (LOCAL->fd >= 0) close (LOCAL->fd);
  1.1472 +    if (LOCAL->ld >= 0) {	/* have a mailbox lock? */
  1.1473 +      flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
  1.1474 +      close (LOCAL->ld);	/* close the lock file */
  1.1475 +      unlink (LOCAL->lname);	/* and delete it */
  1.1476 +    }
  1.1477 +    if (LOCAL->lname) fs_give ((void **) &LOCAL->lname);
  1.1478 +				/* free local text buffers */
  1.1479 +    if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  1.1480 +    if (LOCAL->text.data) fs_give ((void **) &LOCAL->text.data);
  1.1481 +    if (LOCAL->linebuf) fs_give ((void **) &LOCAL->linebuf);
  1.1482 +    if (LOCAL->line) fs_give ((void **) &LOCAL->line);
  1.1483 +				/* nuke the local data */
  1.1484 +    fs_give ((void **) &stream->local);
  1.1485 +    stream->dtb = NIL;		/* log out the DTB */
  1.1486 +  }
  1.1487 +}
  1.1488 +
  1.1489 +/* MMDF open and lock mailbox
  1.1490 + * Accepts: file name to open/lock
  1.1491 + *	    file open mode
  1.1492 + *	    destination buffer for lock file name
  1.1493 + *	    type of locking operation (LOCK_SH or LOCK_EX)
  1.1494 + */
  1.1495 +
  1.1496 +int mmdf_lock (char *file,int flags,int mode,DOTLOCK *lock,int op)
  1.1497 +{
  1.1498 +  int fd;
  1.1499 +  blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
  1.1500 +  (*bn) (BLOCK_FILELOCK,NIL);
  1.1501 +				/* try locking the easy way */
  1.1502 +  if (dotlock_lock (file,lock,-1)) {
  1.1503 +				/* got dotlock file, easy open */
  1.1504 +    if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
  1.1505 +    else dotlock_unlock (lock);	/* open failed, free the dotlock */
  1.1506 +  }
  1.1507 +				/* no dot lock file, open file now */
  1.1508 +  else if ((fd = open (file,flags,mode)) >= 0) {
  1.1509 +				/* try paranoid way to make a dot lock file */
  1.1510 +    if (dotlock_lock (file,lock,fd)) {
  1.1511 +      close (fd);		/* get fresh fd in case of timing race */
  1.1512 +      if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
  1.1513 +				/* open failed, free the dotlock */
  1.1514 +      else dotlock_unlock (lock);
  1.1515 +    }
  1.1516 +    else flock (fd,op);		/* paranoid way failed, just flock() it */
  1.1517 +  }
  1.1518 +  (*bn) (BLOCK_NONE,NIL);
  1.1519 +  return fd;
  1.1520 +}
  1.1521 +
  1.1522 +/* MMDF unlock and close mailbox
  1.1523 + * Accepts: file descriptor
  1.1524 + *	    (optional) mailbox stream to check atime/mtime
  1.1525 + *	    (optional) lock file name
  1.1526 + */
  1.1527 +
  1.1528 +void mmdf_unlock (int fd,MAILSTREAM *stream,DOTLOCK *lock)
  1.1529 +{
  1.1530 +  if (stream) {			/* need to muck with times? */
  1.1531 +    struct stat sbuf;
  1.1532 +    time_t tp[2];
  1.1533 +    time_t now = time (0);
  1.1534 +    fstat (fd,&sbuf);		/* get file times */
  1.1535 +    if (LOCAL->ld >= 0) {	/* yes, readwrite session? */
  1.1536 +      tp[0] = now;		/* set atime to now */
  1.1537 +				/* set mtime to (now - 1) if necessary */
  1.1538 +      tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
  1.1539 +    }
  1.1540 +    else if (stream->recent) {	/* readonly with recent messages */
  1.1541 +      if ((sbuf.st_atime >= sbuf.st_mtime) ||
  1.1542 +	  (sbuf.st_atime >= sbuf.st_ctime))
  1.1543 +				/* keep past mtime, whack back atime */
  1.1544 +	tp[0] = (tp[1] = (sbuf.st_mtime < now) ? sbuf.st_mtime : now) - 1;
  1.1545 +      else now = 0;		/* no time change needed */
  1.1546 +    }
  1.1547 +				/* readonly with no recent messages */
  1.1548 +    else if ((sbuf.st_atime < sbuf.st_mtime) ||
  1.1549 +	     (sbuf.st_atime < sbuf.st_ctime)) {
  1.1550 +      tp[0] = now;		/* set atime to now */
  1.1551 +				/* set mtime to (now - 1) if necessary */
  1.1552 +      tp[1] = (now > sbuf.st_mtime) ? sbuf.st_mtime : now - 1;
  1.1553 +    }
  1.1554 +    else now = 0;		/* no time change needed */
  1.1555 +				/* set the times, note change */
  1.1556 +    if (now && !utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
  1.1557 +  }
  1.1558 +  flock (fd,LOCK_UN);		/* release flock'ers */
  1.1559 +  if (!stream) close (fd);	/* close the file if no stream */
  1.1560 +  dotlock_unlock (lock);	/* flush the lock file if any */
  1.1561 +}
  1.1562 +
  1.1563 +/* MMDF mail parse and lock mailbox
  1.1564 + * Accepts: MAIL stream
  1.1565 + *	    space to write lock file name
  1.1566 + *	    type of locking operation
  1.1567 + * Returns: T if parse OK, critical & mailbox is locked shared; NIL if failure
  1.1568 + */
  1.1569 +
  1.1570 +int mmdf_parse (MAILSTREAM *stream,DOTLOCK *lock,int op)
  1.1571 +{
  1.1572 +  int ti,zn,m;
  1.1573 +  unsigned long i,j,k;
  1.1574 +  unsigned char c,*s,*t,*u,tmp[MAILTMPLEN],date[30];
  1.1575 +  int retain = T;
  1.1576 +  unsigned long nmsgs = stream->nmsgs;
  1.1577 +  unsigned long prevuid = nmsgs ? mail_elt (stream,nmsgs)->private.uid : 0;
  1.1578 +  unsigned long recent = stream->recent;
  1.1579 +  unsigned long oldnmsgs = stream->nmsgs;
  1.1580 +  short silent = stream->silent;
  1.1581 +  short pseudoseen = NIL;
  1.1582 +  struct stat sbuf;
  1.1583 +  STRING bs;
  1.1584 +  FDDATA d;
  1.1585 +  MESSAGECACHE *elt;
  1.1586 +  mail_lock (stream);		/* guard against recursion or pingers */
  1.1587 +				/* toss out previous descriptor */
  1.1588 +  if (LOCAL->fd >= 0) close (LOCAL->fd);
  1.1589 +  MM_CRITICAL (stream);		/* open and lock mailbox (shared OK) */
  1.1590 +  if ((LOCAL->fd = mmdf_lock (stream->mailbox,(LOCAL->ld >= 0) ?
  1.1591 +			      O_RDWR : O_RDONLY,
  1.1592 +			      (long)mail_parameters(NIL,GET_MBXPROTECTION,NIL),
  1.1593 +			      lock,op)) < 0) {
  1.1594 +    sprintf (tmp,"Mailbox open failed, aborted: %s",strerror (errno));
  1.1595 +    MM_LOG (tmp,ERROR);
  1.1596 +    mmdf_abort (stream);
  1.1597 +    mail_unlock (stream);
  1.1598 +    MM_NOCRITICAL (stream);	/* done with critical */
  1.1599 +    return NIL;
  1.1600 +  }
  1.1601 +  fstat (LOCAL->fd,&sbuf);	/* get status */
  1.1602 +				/* validate change in size */
  1.1603 +  if (sbuf.st_size < LOCAL->filesize) {
  1.1604 +    sprintf (tmp,"Mailbox shrank from %lu to %lu bytes, aborted",
  1.1605 +	     (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
  1.1606 +    MM_LOG (tmp,ERROR);		/* this is pretty bad */
  1.1607 +    mmdf_unlock (LOCAL->fd,stream,lock);
  1.1608 +    mmdf_abort (stream);
  1.1609 +    mail_unlock (stream);
  1.1610 +    MM_NOCRITICAL (stream);	/* done with critical */
  1.1611 +    return NIL;
  1.1612 +  }
  1.1613 +
  1.1614 +				/* new data? */
  1.1615 +  else if (i = sbuf.st_size - LOCAL->filesize) {
  1.1616 +    d.fd = LOCAL->fd;		/* yes, set up file descriptor */
  1.1617 +    d.pos = LOCAL->filesize;	/* get to that position in the file */
  1.1618 +    d.chunk = LOCAL->buf;	/* initial buffer chunk */
  1.1619 +    d.chunksize = CHUNKSIZE;	/* file chunk size */
  1.1620 +    INIT (&bs,fd_string,&d,i);	/* initialize stringstruct */
  1.1621 +				/* skip leading whitespace for broken MTAs */
  1.1622 +    while (((c = CHR (&bs)) == '\n') || (c == '\r') ||
  1.1623 +	   (c == ' ') || (c == '\t')) SNX (&bs);
  1.1624 +    if (SIZE (&bs)) {		/* read new data */
  1.1625 +				/* remember internal header position */
  1.1626 +      j = LOCAL->filesize + GETPOS (&bs);
  1.1627 +      s = mmdf_mbxline (stream,&bs,&i);
  1.1628 +      stream->silent = T;	/* quell main program new message events */
  1.1629 +      do {			/* read MMDF header */
  1.1630 +	if (!(i && ISMMDF (s))){/* see if valid MMDF header */
  1.1631 +	  sprintf (tmp,"Unexpected changes to mailbox (try restarting): %.20s",
  1.1632 +		   (char *) s);
  1.1633 +				/* see if we can back up to a line */
  1.1634 +	  if (i && (j > MMDFHDRLEN)) {
  1.1635 +	    SETPOS (&bs,j -= MMDFHDRLEN);
  1.1636 +				/* read previous line */
  1.1637 +	    s = mmdf_mbxline (stream,&bs,&i);
  1.1638 +				/* kill the error if it looks good */
  1.1639 +	    if (i && ISMMDF (s)) tmp[0] = '\0';
  1.1640 +	  }
  1.1641 +	  if (tmp[0]) {
  1.1642 +	    MM_LOG (tmp,ERROR);
  1.1643 +	    mmdf_unlock (LOCAL->fd,stream,lock);
  1.1644 +	    mmdf_abort (stream);
  1.1645 +	    mail_unlock (stream);
  1.1646 +	    MM_NOCRITICAL (stream);
  1.1647 +	    return NIL;
  1.1648 +	  }
  1.1649 +	}
  1.1650 +				/* instantiate first new message */
  1.1651 +	mail_exists (stream,++nmsgs);
  1.1652 +	(elt = mail_elt (stream,nmsgs))->valid = T;
  1.1653 +	recent++;		/* assume recent by default */
  1.1654 +	elt->recent = T;
  1.1655 +				/* note position/size of internal header */
  1.1656 +	elt->private.special.offset = j;
  1.1657 +	elt->private.special.text.size = i;
  1.1658 +
  1.1659 +	s = mmdf_mbxline (stream,&bs,&i);
  1.1660 +	ti = 0;			/* assume not a valid date */
  1.1661 +	zn = 0,t = NIL;
  1.1662 +	if (i) VALID (s,t,ti,zn);
  1.1663 +	if (ti) {		/* generate plausible IMAPish date string */
  1.1664 +				/* this is also part of header */
  1.1665 +	  elt->private.special.text.size += i;
  1.1666 +	  date[2] = date[6] = date[20] = '-'; date[11] = ' ';
  1.1667 +	  date[14] = date[17] = ':';
  1.1668 +				/* dd */
  1.1669 +	  date[0] = t[ti - 2]; date[1] = t[ti - 1];
  1.1670 +				/* mmm */
  1.1671 +	  date[3] = t[ti - 6]; date[4] = t[ti - 5]; date[5] = t[ti - 4];
  1.1672 +				/* hh */
  1.1673 +	  date[12] = t[ti + 1]; date[13] = t[ti + 2];
  1.1674 +				/* mm */
  1.1675 +	  date[15] = t[ti + 4]; date[16] = t[ti + 5];
  1.1676 +	  if (t[ti += 6]==':'){	/* ss */
  1.1677 +	    date[18] = t[++ti]; date[19] = t[++ti];
  1.1678 +	    ti++;		/* move to space */
  1.1679 +	  }
  1.1680 +	  else date[18] = date[19] = '0';
  1.1681 +				/* yy -- advance over timezone if necessary */
  1.1682 +	  if (zn == ti) ti += (((t[zn+1] == '+') || (t[zn+1] == '-')) ? 6 : 4);
  1.1683 +	  date[7] = t[ti + 1]; date[8] = t[ti + 2];
  1.1684 +	  date[9] = t[ti + 3]; date[10] = t[ti + 4];
  1.1685 +				/* zzz */
  1.1686 +	  t = zn ? (t + zn + 1) : (unsigned char *) "LCL";
  1.1687 +	  date[21] = *t++; date[22] = *t++; date[23] = *t++;
  1.1688 +	  if ((date[21] != '+') && (date[21] != '-')) date[24] = '\0';
  1.1689 +	  else {		/* numeric time zone */
  1.1690 +	    date[24] = *t++; date[25] = *t++;
  1.1691 +	    date[26] = '\0'; date[20] = ' ';
  1.1692 +	  }
  1.1693 +				/* set internal date */
  1.1694 +	  if (!mail_parse_date (elt,date)) {
  1.1695 +	    sprintf (tmp,"Unable to parse internal date: %s",(char *) date);
  1.1696 +	    MM_LOG (tmp,WARN);
  1.1697 +	  }
  1.1698 +	}
  1.1699 +	else {			/* make date from file date */
  1.1700 +	  struct tm *tm = gmtime (&sbuf.st_mtime);
  1.1701 +	  elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  1.1702 +	  elt->year = tm->tm_year + 1900 - BASEYEAR;
  1.1703 +	  elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  1.1704 +	  elt->seconds = tm->tm_sec;
  1.1705 +	  elt->zhours = 0; elt->zminutes = 0;
  1.1706 +	  t = NIL;		/* suppress line read */
  1.1707 +	}
  1.1708 +				/* header starts here */
  1.1709 +	elt->private.msg.header.offset = elt->private.special.text.size;
  1.1710 +
  1.1711 +	do {			/* look for message body */
  1.1712 +	  j = GETPOS (&bs);	/* note position before line */
  1.1713 +	  if (t) s = t = mmdf_mbxline (stream,&bs,&i);
  1.1714 +	  else t = s;		/* this line read was suppressed */
  1.1715 +	  if (ISMMDF (s)) {	/* found terminator in header? */
  1.1716 +	    SETPOS (&bs,j);	/* oops, back up before line */
  1.1717 +				/* must insert a newline */
  1.1718 +	    elt->private.spare.data++;
  1.1719 +	    break;		/* punt */
  1.1720 +	  }
  1.1721 +				/* this line is part of header */
  1.1722 +	  elt->private.msg.header.text.size += i;
  1.1723 +	  if (i) switch (*s) {	/* check header lines */
  1.1724 +	  case 'X':		/* possible X-???: line */
  1.1725 +	    if (s[1] == '-') {	/* must be immediately followed by hyphen */
  1.1726 +				/* X-Status: becomes Status: in S case */
  1.1727 +	      if (s[2] == 'S' && s[3] == 't' && s[4] == 'a' && s[5] == 't' &&
  1.1728 +		  s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
  1.1729 +				/* possible X-Keywords */
  1.1730 +	      else if (s[2] == 'K' && s[3] == 'e' && s[4] == 'y' &&
  1.1731 +		       s[5] == 'w' && s[6] == 'o' && s[7] == 'r' &&
  1.1732 +		       s[8] == 'd' && s[9] == 's' && s[10] == ':') {
  1.1733 +		SIZEDTEXT uf;
  1.1734 +		retain = NIL;	/* don't retain continuation */
  1.1735 +		s += 11;	/* flush leading whitespace */
  1.1736 +		while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n'))){
  1.1737 +		  while (*s == ' ') s++;
  1.1738 +				/* find end of keyword */
  1.1739 +		  if (!(u = strpbrk (s," \n\r"))) u = s + strlen (s);
  1.1740 +				/* got a keyword? */
  1.1741 +		  if ((k = (u - s)) && (k <= MAXUSERFLAG)) {
  1.1742 +		    uf.data = (unsigned char *) s;
  1.1743 +		    uf.size = k;
  1.1744 +		    for (j = 0; (j < NUSERFLAGS) && stream->user_flags[j]; ++j)
  1.1745 +		      if (!compare_csizedtext (stream->user_flags[j],&uf)) {
  1.1746 +			elt->user_flags |= ((long) 1) << j;
  1.1747 +			break;
  1.1748 +		      }
  1.1749 +		  }
  1.1750 +		  s = u;	/* advance to next keyword */
  1.1751 +		}
  1.1752 +		break;
  1.1753 +	      }
  1.1754 +
  1.1755 +				/* possible X-IMAP */
  1.1756 +	      else if ((s[2] == 'I') && (s[3] == 'M') && (s[4] == 'A') &&
  1.1757 +		       (s[5] == 'P') && ((m = (s[6] == ':')) ||
  1.1758 +					 ((s[6] == 'b') && (s[7] == 'a') &&
  1.1759 +					  (s[8] == 's') && (s[9] == 'e') &&
  1.1760 +					  (s[10] == ':')))) {
  1.1761 +		retain = NIL;	/* don't retain continuation */
  1.1762 +		if ((nmsgs == 1) && !stream->uid_validity) {
  1.1763 +				/* advance to data */
  1.1764 +		  s += m ? 7 : 11;
  1.1765 +				/* flush whitespace */
  1.1766 +		  while (*s == ' ') s++;
  1.1767 +		  j = 0;	/* slurp UID validity */
  1.1768 +				/* found a digit? */
  1.1769 +		  while (isdigit (*s)) {
  1.1770 +		    j *= 10;	/* yes, add it in */
  1.1771 +		    j += *s++ - '0';
  1.1772 +		  }
  1.1773 +				/* flush whitespace */
  1.1774 +		  while (*s == ' ') s++;
  1.1775 +				/* must have valid UID validity and UID last */
  1.1776 +		  if (j && isdigit (*s)) {
  1.1777 +				/* pseudo-header seen if X-IMAP */
  1.1778 +		    if (m) pseudoseen = LOCAL->pseudo = T;
  1.1779 +				/* save UID validity */
  1.1780 +		    stream->uid_validity = j;
  1.1781 +		    j = 0;	/* slurp UID last */
  1.1782 +		    while (isdigit (*s)) {
  1.1783 +		      j *= 10;	/* yes, add it in */
  1.1784 +		      j += *s++ - '0';
  1.1785 +		    }
  1.1786 +				/* save UID last */
  1.1787 +		    stream->uid_last = j;
  1.1788 +				/* process keywords */
  1.1789 +		    for (j = 0; (*s != '\n') && ((*s != '\r')||(s[1] != '\n'));
  1.1790 +			 s = u,j++) {
  1.1791 +				/* flush leading whitespace */
  1.1792 +		      while (*s == ' ') s++;
  1.1793 +		      u = strpbrk (s," \n\r");
  1.1794 +				/* got a keyword? */
  1.1795 +		      if ((j < NUSERFLAGS) && (k = (u - s)) &&
  1.1796 +			  (k <= MAXUSERFLAG)) {
  1.1797 +			if (stream->user_flags[j])
  1.1798 +			  fs_give ((void **) &stream->user_flags[j]);
  1.1799 +			stream->user_flags[j] = (char *) fs_get (k + 1);
  1.1800 +			strncpy (stream->user_flags[j],s,k);
  1.1801 +			stream->user_flags[j][k] = '\0';
  1.1802 +		      }
  1.1803 +		    }
  1.1804 +		  }
  1.1805 +		}
  1.1806 +		break;
  1.1807 +	      }
  1.1808 +
  1.1809 +				/* possible X-UID */
  1.1810 +	      else if (s[2] == 'U' && s[3] == 'I' && s[4] == 'D' &&
  1.1811 +		       s[5] == ':') {
  1.1812 +		retain = NIL;	/* don't retain continuation */
  1.1813 +				/* only believe if have a UID validity */
  1.1814 +		if (stream->uid_validity && ((nmsgs > 1) || !pseudoseen)) {
  1.1815 +		  s += 6;	/* advance to UID value */
  1.1816 +				/* flush whitespace */
  1.1817 +		  while (*s == ' ') s++;
  1.1818 +		  j = 0;
  1.1819 +				/* found a digit? */
  1.1820 +		  while (isdigit (*s)) {
  1.1821 +		    j *= 10;	/* yes, add it in */
  1.1822 +		    j += *s++ - '0';
  1.1823 +		  }
  1.1824 +				/* flush remainder of line */
  1.1825 +		  while (*s != '\n') s++;
  1.1826 +				/* make sure not duplicated */
  1.1827 +		  if (elt->private.uid)
  1.1828 +		    sprintf (tmp,"Message %lu UID %lu already has UID %lu",
  1.1829 +			     pseudoseen ? elt->msgno - 1 : elt->msgno,
  1.1830 +			     j,elt->private.uid);
  1.1831 +				/* make sure UID doesn't go backwards */
  1.1832 +		  else if (j <= prevuid)
  1.1833 +		    sprintf (tmp,"Message %lu UID %lu less than %lu",
  1.1834 +			     pseudoseen ? elt->msgno - 1 : elt->msgno,
  1.1835 +			     j,prevuid + 1);
  1.1836 +#if 0	/* this is currently broken by UIDPLUS */
  1.1837 +				/* or skip by mailbox's recorded last */
  1.1838 +		  else if (j > stream->uid_last)
  1.1839 +		    sprintf (tmp,"Message %lu UID %lu greater than last %lu",
  1.1840 +			     pseudoseen ? elt->msgno - 1 : elt->msgno,
  1.1841 +			     j,stream->uid_last);
  1.1842 +#endif
  1.1843 +		  else {	/* normal UID case */
  1.1844 +		    prevuid = elt->private.uid = j;
  1.1845 +#if 1	/* temporary kludge for UIDPLUS */
  1.1846 +		    if (prevuid > stream->uid_last) {
  1.1847 +		      stream->uid_last = prevuid;
  1.1848 +		      LOCAL->ddirty = LOCAL->dirty = T;
  1.1849 +		    }		    
  1.1850 +#endif
  1.1851 +		    break;	/* exit this cruft */
  1.1852 +		  }
  1.1853 +		  MM_LOG (tmp,WARN);
  1.1854 +				/* invalidate UID validity */
  1.1855 +		  stream->uid_validity = 0;
  1.1856 +		  elt->private.uid = 0;
  1.1857 +		}
  1.1858 +		break;
  1.1859 +	      }
  1.1860 +	    }
  1.1861 +				/* otherwise fall into S case */
  1.1862 +
  1.1863 +	  case 'S':		/* possible Status: line */
  1.1864 +	    if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
  1.1865 +		s[4] == 'u' && s[5] == 's' && s[6] == ':') {
  1.1866 +	      retain = NIL;	/* don't retain continuation */
  1.1867 +	      s += 6;		/* advance to status flags */
  1.1868 +	      do switch (*s++) {/* parse flags */
  1.1869 +	      case 'R':		/* message read */
  1.1870 +		elt->seen = T;
  1.1871 +		break;
  1.1872 +	      case 'O':		/* message old */
  1.1873 +		if (elt->recent) {
  1.1874 +		  elt->recent = NIL;
  1.1875 +		  recent--;	/* it really wasn't recent */
  1.1876 +		}
  1.1877 +		break;
  1.1878 +	      case 'D':		/* message deleted */
  1.1879 +		elt->deleted = T;
  1.1880 +		break;
  1.1881 +	      case 'F':		/* message flagged */
  1.1882 +		elt->flagged = T;
  1.1883 +		break;
  1.1884 +	      case 'A':		/* message answered */
  1.1885 +		elt->answered = T;
  1.1886 +		break;
  1.1887 +	      case 'T':		/* message is a draft */
  1.1888 +		elt->draft = T;
  1.1889 +		break;
  1.1890 +	      default:		/* some other crap */
  1.1891 +		break;
  1.1892 +	      } while (*s && (*s != '\n') && ((*s != '\r') || (s[1] != '\n')));
  1.1893 +	      break;		/* all done */
  1.1894 +	    }
  1.1895 +				/* otherwise fall into default case */
  1.1896 +
  1.1897 +	  default:		/* ordinary header line */
  1.1898 +	    if ((*s == 'S') || (*s == 's') ||
  1.1899 +		(((*s == 'X') || (*s == 'x')) && (s[1] == '-'))) {
  1.1900 +	      unsigned char *e,*v;
  1.1901 +				/* must match what mail_filter() does */
  1.1902 +	      for (u = s,v = tmp,e = u + min (i,MAILTMPLEN - 1);
  1.1903 +		   (u < e) && ((c = (*u ? *u : (*u = ' '))) != ':') &&
  1.1904 +		   ((c > ' ') || ((c != ' ') && (c != '\t') &&
  1.1905 +				  (c != '\r') && (c != '\n')));
  1.1906 +		   *v++ = *u++);
  1.1907 +	      *v = '\0';	/* tie off */
  1.1908 +				/* matches internal header? */
  1.1909 +	      if (!compare_cstring (tmp,"STATUS") ||
  1.1910 +		  !compare_cstring (tmp,"X-STATUS") ||
  1.1911 +		  !compare_cstring (tmp,"X-KEYWORDS") ||
  1.1912 +		  !compare_cstring (tmp,"X-UID") ||
  1.1913 +		  !compare_cstring (tmp,"X-IMAP") ||
  1.1914 +		  !compare_cstring (tmp,"X-IMAPBASE")) {
  1.1915 +		char err[MAILTMPLEN];
  1.1916 +		sprintf (err,"Discarding bogus %s header in message %lu",
  1.1917 +			 (char *) tmp,elt->msgno);
  1.1918 +		MM_LOG (err,WARN);
  1.1919 +		retain = NIL;	/* don't retain continuation */
  1.1920 +		break;		/* different case or something */
  1.1921 +	      }
  1.1922 +	    }
  1.1923 +				/* retain or non-continuation? */
  1.1924 +	    if (retain || ((*s != ' ') && (*s != '\t'))) {
  1.1925 +	      retain = T;	/* retaining continuation now */
  1.1926 +				/* line length in LF format newline */
  1.1927 +	      for (j = k = 0; j < i; ++j) if (s[j] != '\r') ++k;
  1.1928 +				/* "internal" header size */
  1.1929 +	      elt->private.spare.data += k;
  1.1930 +				/* message size */
  1.1931 +	      elt->rfc822_size += k + 1;
  1.1932 +	    }
  1.1933 +	    else {
  1.1934 +	      char err[MAILTMPLEN];
  1.1935 +	      sprintf (err,"Discarding bogus continuation in msg %lu: %.80s",
  1.1936 +		      elt->msgno,(char *) s);
  1.1937 +	      if (u = strpbrk (err,"\r\n")) *u = '\0';
  1.1938 +	      MM_LOG (err,WARN);
  1.1939 +	      break;		/* different case or something */
  1.1940 +	    }
  1.1941 +	    break;
  1.1942 +	  }
  1.1943 +	} while (i && (*t != '\n') && ((*t != '\r') || (t[1] != '\n')));
  1.1944 +				/* "internal" header sans trailing newline */
  1.1945 +	if (i) elt->private.spare.data--;
  1.1946 +				/* assign a UID if none found */
  1.1947 +	if (((nmsgs > 1) || !pseudoseen) && !elt->private.uid) {
  1.1948 +	  prevuid = elt->private.uid = ++stream->uid_last;
  1.1949 +	  elt->private.dirty = T;
  1.1950 +	}
  1.1951 +	else elt->private.dirty = elt->recent;
  1.1952 +
  1.1953 +				/* note size of header, location of text */
  1.1954 +	elt->private.msg.header.text.size =
  1.1955 +	  (elt->private.msg.text.offset =
  1.1956 +	   (LOCAL->filesize + GETPOS (&bs)) - elt->private.special.offset) -
  1.1957 +	     elt->private.special.text.size;
  1.1958 +				/* note current position */
  1.1959 +	j = LOCAL->filesize + GETPOS (&bs);
  1.1960 +	if (i) do {		/* look for next message */
  1.1961 +	  s = mmdf_mbxline (stream,&bs,&i);
  1.1962 +	  if (i) {		/* got new data? */
  1.1963 +	    if (ISMMDF (s)) break;
  1.1964 +	    else {		/* not a header line, add it to message */
  1.1965 +	      elt->rfc822_size += i;
  1.1966 +	      for (j = 0; j < i; ++j) switch (s[j]) {
  1.1967 +	      case '\r':	/* squeeze out CRs */
  1.1968 +		elt->rfc822_size -= 1;
  1.1969 +		break;
  1.1970 +	      case '\n':	/* LF becomes CRLF */
  1.1971 +		elt->rfc822_size += 1;
  1.1972 +		break;
  1.1973 +	      default:
  1.1974 +		break;
  1.1975 +	      }
  1.1976 +				/* update current position */
  1.1977 +	      j = LOCAL->filesize + GETPOS (&bs);
  1.1978 +	    }
  1.1979 +	  }
  1.1980 +	} while (i);		/* until found a header */
  1.1981 +	elt->private.msg.text.text.size = j -
  1.1982 +	  (elt->private.special.offset + elt->private.msg.text.offset);
  1.1983 +	if (i) {		/* get next header line */
  1.1984 +				/* remember first internal header position */
  1.1985 +	  j = LOCAL->filesize + GETPOS (&bs);
  1.1986 +	  s = mmdf_mbxline (stream,&bs,&i);
  1.1987 +	}
  1.1988 +				/* until end of buffer */
  1.1989 +      } while (!stream->sniff && i);
  1.1990 +      if (pseudoseen) {		/* flush pseudo-message if present */
  1.1991 +				/* decrement recent count */
  1.1992 +	if (mail_elt (stream,1)->recent) recent--;
  1.1993 +				/* and the exists count */
  1.1994 +	mail_exists (stream,nmsgs--);
  1.1995 +	mail_expunged(stream,1);/* fake an expunge of that message */
  1.1996 +      }
  1.1997 +				/* need to start a new UID validity? */
  1.1998 +      if (!stream->uid_validity) {
  1.1999 +	stream->uid_validity = time (0);
  1.2000 +				/* in case a whiner with no life */
  1.2001 +	if (mail_parameters (NIL,GET_USERHASNOLIFE,NIL))
  1.2002 +	  stream->uid_nosticky = T;
  1.2003 +	else if (nmsgs) {	/* don't bother if empty file */
  1.2004 +				/* make dirty to restart UID epoch */
  1.2005 +	  LOCAL->ddirty = LOCAL->dirty = T;
  1.2006 +				/* need to rewrite msg 1 if not pseudo */
  1.2007 +	  if (!LOCAL->pseudo) mail_elt (stream,1)->private.dirty = T;
  1.2008 +	  MM_LOG ("Assigning new unique identifiers to all messages",NIL);
  1.2009 +	}
  1.2010 +      }
  1.2011 +      stream->nmsgs = oldnmsgs;	/* whack it back down */
  1.2012 +      stream->silent = silent;	/* restore old silent setting */
  1.2013 +				/* notify upper level of new mailbox sizes */
  1.2014 +      mail_exists (stream,nmsgs);
  1.2015 +      mail_recent (stream,recent);
  1.2016 +				/* mark dirty so O flags are set */
  1.2017 +      if (recent) LOCAL->dirty = T;
  1.2018 +    }
  1.2019 +  }
  1.2020 +				/* no change, don't babble if never got time */
  1.2021 +  else if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
  1.2022 +    MM_LOG ("New mailbox modification time but apparently no changes",WARN);
  1.2023 +				/* update parsed file size and time */
  1.2024 +  LOCAL->filesize = sbuf.st_size;
  1.2025 +  LOCAL->filetime = sbuf.st_mtime;
  1.2026 +  return T;			/* return the winnage */
  1.2027 +}
  1.2028 +
  1.2029 +/* MMDF read line from mailbox
  1.2030 + * Accepts: mail stream
  1.2031 + *	    stringstruct
  1.2032 + *	    pointer to line size
  1.2033 + * Returns: pointer to input line
  1.2034 + */
  1.2035 +
  1.2036 +char *mmdf_mbxline (MAILSTREAM *stream,STRING *bs,unsigned long *size)
  1.2037 +{
  1.2038 +  unsigned long i,j,k,m;
  1.2039 +  char *s,*t,*te;
  1.2040 +  char *ret = "";
  1.2041 +				/* flush old buffer */
  1.2042 +  if (LOCAL->line) fs_give ((void **) &LOCAL->line);
  1.2043 +				/* if buffer needs refreshing */
  1.2044 +  if (!bs->cursize) SETPOS (bs,GETPOS (bs));
  1.2045 +  if (SIZE (bs)) {		/* find newline */
  1.2046 +				/* end of fast scan */
  1.2047 +    te = (t = (s = bs->curpos) + bs->cursize) - 12;
  1.2048 +    while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
  1.2049 +		       (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
  1.2050 +		       (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
  1.2051 +		       (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
  1.2052 +      --s;			/* back up */
  1.2053 +      break;			/* exit loop */
  1.2054 +    }
  1.2055 +				/* final character-at-a-time scan */
  1.2056 +    while ((s < t) && (*s != '\n')) ++s;
  1.2057 +				/* difficult case if line spans buffer */
  1.2058 +    if ((i = s - bs->curpos) == bs->cursize) {
  1.2059 +				/* have space in line buffer? */
  1.2060 +      if (i > LOCAL->linebuflen) {
  1.2061 +	fs_give ((void **) &LOCAL->linebuf);
  1.2062 +	LOCAL->linebuf = (char *) fs_get (LOCAL->linebuflen = i);
  1.2063 +      }
  1.2064 +				/* remember what we have so far */
  1.2065 +      memcpy (LOCAL->linebuf,bs->curpos,i);
  1.2066 +				/* load next buffer */
  1.2067 +      SETPOS (bs,k = GETPOS (bs) + i);
  1.2068 +				/* end of fast scan */
  1.2069 +      te = (t = (s = bs->curpos) + bs->cursize) - 12;
  1.2070 +				/* fast scan in overlap buffer */
  1.2071 +      while (s < te) if ((*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
  1.2072 +			 (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
  1.2073 +			 (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n') ||
  1.2074 +			 (*s++ == '\n') || (*s++ == '\n') || (*s++ == '\n')) {
  1.2075 +	--s;			/* back up */
  1.2076 +	break;			/* exit loop */
  1.2077 +      }
  1.2078 +
  1.2079 +				/* final character-at-a-time scan */
  1.2080 +      while ((s < t) && (*s != '\n')) ++s;
  1.2081 +				/* huge line? */
  1.2082 +      if ((j = s - bs->curpos) == bs->cursize) {
  1.2083 +	SETPOS (bs,GETPOS (bs) + j);
  1.2084 +				/* look for end of line (s-l-o-w!!) */
  1.2085 +	for (m = SIZE (bs); m && (SNX (bs) != '\n'); --m,++j);
  1.2086 +	SETPOS (bs,k);		/* go back to where it started */
  1.2087 +      }
  1.2088 +				/* got size of data, make buffer for return */
  1.2089 +      ret = LOCAL->line = (char *) fs_get (i + j + 2);
  1.2090 +				/* copy first chunk */
  1.2091 +      memcpy (ret,LOCAL->linebuf,i);
  1.2092 +      while (j) {		/* copy remainder */
  1.2093 +	if (!bs->cursize) SETPOS (bs,GETPOS (bs));
  1.2094 +	memcpy (ret + i,bs->curpos,k = min (j,bs->cursize));
  1.2095 +	i += k;			/* account for this much read in */
  1.2096 +	j -= k;
  1.2097 +	bs->curpos += k;	/* increment new position */
  1.2098 +	bs->cursize -= k;	/* eat that many bytes */
  1.2099 +      }
  1.2100 +				/* read newline at end */
  1.2101 +      if (SIZE (bs)) ret[i++] = SNX (bs);
  1.2102 +      ret[i] = '\0';		/* makes debugging easier */
  1.2103 +    }
  1.2104 +    else {			/* this is easy */
  1.2105 +      ret = bs->curpos;		/* string it at this position */
  1.2106 +      bs->curpos += ++i;	/* increment new position */
  1.2107 +      bs->cursize -= i;		/* eat that many bytes */
  1.2108 +    }
  1.2109 +    *size = i;			/* return that to user */
  1.2110 +  }
  1.2111 +  else *size = 0;		/* end of data, return empty */
  1.2112 +				/* embedded MMDF header at end of line? */
  1.2113 +  if ((*size > sizeof (MMDFHDRTXT)) &&
  1.2114 +      (s = ret + *size - (i = sizeof (MMDFHDRTXT) - 1)) && ISMMDF (s)) {
  1.2115 +    SETPOS (bs,GETPOS (bs) - i);/* back up to start of MMDF header */
  1.2116 +    *size -= i;			/* reduce length of line */
  1.2117 +    ret[*size - 1] = '\n';	/* force newline at end */
  1.2118 +  }
  1.2119 +  return ret;
  1.2120 +}
  1.2121 +
  1.2122 +/* MMDF make pseudo-header
  1.2123 + * Accepts: MAIL stream
  1.2124 + *	    buffer to write pseudo-header
  1.2125 + * Returns: length of pseudo-header
  1.2126 + */
  1.2127 +
  1.2128 +unsigned long mmdf_pseudo (MAILSTREAM *stream,char *hdr)
  1.2129 +{
  1.2130 +  int i;
  1.2131 +  char *s,tmp[MAILTMPLEN];
  1.2132 +  time_t now = time (0);
  1.2133 +  rfc822_fixed_date (tmp);
  1.2134 +  sprintf (hdr,"%sFrom %s %.24s\nDate: %s\nFrom: %s <%s@%.80s>\nSubject: %s\nMessage-ID: <%lu@%.80s>\nX-IMAP: %010lu %010lu",
  1.2135 +	   mmdfhdr,pseudo_from,ctime (&now),
  1.2136 +	   tmp,pseudo_name,pseudo_from,mylocalhost (),pseudo_subject,
  1.2137 +	   (unsigned long) now,mylocalhost (),stream->uid_validity,
  1.2138 +	   stream->uid_last);
  1.2139 +  for (s = hdr + strlen (hdr),i = 0; i < NUSERFLAGS; ++i)
  1.2140 +    if (stream->user_flags[i])
  1.2141 +      sprintf (s += strlen (s)," %s",stream->user_flags[i]);
  1.2142 +  sprintf (s += strlen (s),"\nStatus: RO\n\n%s\n%s",pseudo_msg,mmdfhdr);
  1.2143 +  return strlen (hdr);
  1.2144 +}
  1.2145 +
  1.2146 +/* MMDF make status string
  1.2147 + * Accepts: MAIL stream
  1.2148 + *	    destination string to write
  1.2149 + *	    message cache entry
  1.2150 + *	    UID to write if non-zero (else use elt->private.uid)
  1.2151 + *	    non-zero flag to write UID (.LT. 0 to write UID base info too)
  1.2152 + * Returns: length of string
  1.2153 + */
  1.2154 +
  1.2155 +unsigned long mmdf_xstatus (MAILSTREAM *stream,char *status,MESSAGECACHE *elt,
  1.2156 +			    unsigned long uid,long flag)
  1.2157 +{
  1.2158 +  char *t,stack[64];
  1.2159 +  char *s = status;
  1.2160 +  unsigned long n;
  1.2161 +  int pad = 50;
  1.2162 +  int sticky = uid ? T : !stream->uid_nosticky;
  1.2163 +  /* This used to use sprintf(), but thanks to certain cretinous C libraries
  1.2164 +     with horribly slow implementations of sprintf() I had to change it to this
  1.2165 +     mess.  At least it should be fast. */
  1.2166 +  if ((flag < 0) && sticky) {	/* need to write X-IMAPbase: header? */
  1.2167 +    *s++ = 'X'; *s++ = '-'; *s++ = 'I'; *s++ = 'M'; *s++ = 'A'; *s++ = 'P';
  1.2168 +    *s++ = 'b'; *s++ = 'a'; *s++ = 's'; *s++ = 'e'; *s++ = ':'; *s++ = ' ';
  1.2169 +    t = stack;
  1.2170 +    n = stream->uid_validity;	/* push UID validity digits on the stack */
  1.2171 +    do *t++ = (char) (n % 10) + '0';
  1.2172 +    while (n /= 10);
  1.2173 +				/* pop UID validity digits from stack */
  1.2174 +    while (t > stack) *s++ = *--t;
  1.2175 +   *s++ = ' ';
  1.2176 +    n = stream->uid_last;	/* push UID last digits on the stack */
  1.2177 +    do *t++ = (char) (n % 10) + '0';
  1.2178 +    while (n /= 10);
  1.2179 +				/* pop UID last digits from stack */
  1.2180 +    while (t > stack) *s++ = *--t;
  1.2181 +    for (n = 0; n < NUSERFLAGS; ++n) if (t = stream->user_flags[n])
  1.2182 +      for (*s++ = ' '; *t; *s++ = *t++);
  1.2183 +    *s++ = '\n';
  1.2184 +    pad += 30;			/* increased padding if have IMAPbase */
  1.2185 +  }
  1.2186 +  *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't'; *s++ = 'u'; *s++ = 's';
  1.2187 +  *s++ = ':'; *s++ = ' ';
  1.2188 +  if (elt->seen) *s++ = 'R';
  1.2189 +				/* only write O if have a UID */
  1.2190 +  if (flag && (!elt->recent || !LOCAL->appending)) *s++ = 'O';
  1.2191 +  *s++ = '\n';
  1.2192 +  *s++ = 'X'; *s++ = '-'; *s++ = 'S'; *s++ = 't'; *s++ = 'a'; *s++ = 't';
  1.2193 +  *s++ = 'u'; *s++ = 's'; *s++ = ':'; *s++ = ' ';
  1.2194 +  if (elt->deleted) *s++ = 'D';
  1.2195 +  if (elt->flagged) *s++ = 'F';
  1.2196 +  if (elt->answered) *s++ = 'A';
  1.2197 +  if (elt->draft) *s++ = 'T';
  1.2198 +    *s++ = '\n';
  1.2199 +
  1.2200 +  if (sticky) {			/* only do this if UIDs sticky */
  1.2201 +    *s++ = 'X'; *s++ = '-'; *s++ = 'K'; *s++ = 'e'; *s++ = 'y'; *s++ = 'w';
  1.2202 +    *s++ = 'o'; *s++ = 'r'; *s++ = 'd'; *s++ = 's'; *s++ = ':';
  1.2203 +    if (n = elt->user_flags) do {
  1.2204 +      *s++ = ' ';
  1.2205 +      for (t = stream->user_flags[find_rightmost_bit (&n)]; *t; *s++ = *t++);
  1.2206 +    } while (n);
  1.2207 +    n = s - status;		/* get size of stuff so far */
  1.2208 +				/* pad X-Keywords to make size constant */
  1.2209 +    if (n < pad) for (n = pad - n; n > 0; --n) *s++ = ' ';
  1.2210 +    *s++ = '\n';
  1.2211 +    if (flag) {			/* want to include UID? */
  1.2212 +      t = stack;
  1.2213 +				/* push UID digits on the stack */
  1.2214 +      n = uid ? uid : elt->private.uid;
  1.2215 +      do *t++ = (char) (n % 10) + '0';
  1.2216 +      while (n /= 10);
  1.2217 +      *s++ = 'X'; *s++ = '-'; *s++ = 'U'; *s++ = 'I'; *s++ = 'D'; *s++ = ':';
  1.2218 +      *s++ = ' ';
  1.2219 +				/* pop UID from stack */
  1.2220 +      while (t > stack) *s++ = *--t;
  1.2221 +      *s++ = '\n';
  1.2222 +    }
  1.2223 +  }
  1.2224 +  *s++ = '\n'; *s = '\0';	/* end of extended message status */
  1.2225 +  return s - status;		/* return size of resulting string */
  1.2226 +}
  1.2227 +
  1.2228 +/* Rewrite mailbox file
  1.2229 + * Accepts: MAIL stream, must be critical and locked
  1.2230 + *	    return pointer to number of expunged messages if want expunge
  1.2231 + *	    lock file name
  1.2232 + *	    expunge sequence, not deleted flag
  1.2233 + * Returns: T if success and mailbox unlocked, NIL if failure
  1.2234 + */
  1.2235 +
  1.2236 +#define OVERFLOWBUFLEN 8192	/* initial overflow buffer length */
  1.2237 +
  1.2238 +long mmdf_rewrite (MAILSTREAM *stream,unsigned long *nexp,DOTLOCK *lock,
  1.2239 +		   long flags)
  1.2240 +{
  1.2241 +  MESSAGECACHE *elt;
  1.2242 +  MMDFFILE f;
  1.2243 +  char *s;
  1.2244 +  time_t tp[2];
  1.2245 +  long ret,flag;
  1.2246 +  unsigned long i,j;
  1.2247 +  unsigned long recent = stream->recent;
  1.2248 +  unsigned long size = LOCAL->pseudo ? mmdf_pseudo (stream,LOCAL->buf) : 0;
  1.2249 +  if (nexp) *nexp = 0;		/* initially nothing expunged */
  1.2250 +				/* calculate size of mailbox after rewrite */
  1.2251 +  for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs; i++) {
  1.2252 +    elt = mail_elt (stream,i);	/* get cache */
  1.2253 +    if (!(nexp && elt->deleted && (flags ? elt->sequence : T))) {
  1.2254 +				/* add RFC822 size of this message */
  1.2255 +      size += elt->private.special.text.size + elt->private.spare.data +
  1.2256 +	mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag) +
  1.2257 +	  elt->private.msg.text.text.size + MMDFHDRLEN;
  1.2258 +      flag = 1;			/* only count X-IMAPbase once */
  1.2259 +    }
  1.2260 +  }
  1.2261 +				/* no messages, has a life, and no pseudo */
  1.2262 +  if (!size && !mail_parameters (NIL,GET_USERHASNOLIFE,NIL)) {
  1.2263 +    LOCAL->pseudo = T;		/* so make a pseudo-message now */
  1.2264 +    size = mmdf_pseudo (stream,LOCAL->buf);
  1.2265 +  }
  1.2266 +				/* extend the file as necessary */
  1.2267 +  if (ret = mmdf_extend (stream,size)) {
  1.2268 +    /* Set up buffered I/O file structure
  1.2269 +     * curpos	current position being written through buffering
  1.2270 +     * filepos	current position being written physically to the disk
  1.2271 +     * bufpos	current position being written in the buffer
  1.2272 +     * protect	current maximum position that can be written to the disk
  1.2273 +     *		before buffering is forced
  1.2274 +     * The code tries to buffer so that that disk is written in multiples of
  1.2275 +     * OVERBLOWBUFLEN bytes.
  1.2276 +     */
  1.2277 +    f.stream = stream;		/* note mail stream */
  1.2278 +    f.curpos = f.filepos = 0;	/* start of file */
  1.2279 +    f.protect = stream->nmsgs ?	/* initial protection pointer */
  1.2280 +    mail_elt (stream,1)->private.special.offset : 8192;
  1.2281 +    f.bufpos = f.buf = (char *) fs_get (f.buflen = OVERFLOWBUFLEN);
  1.2282 +
  1.2283 +    if (LOCAL->pseudo)		/* update pseudo-header */
  1.2284 +      mmdf_write (&f,LOCAL->buf,mmdf_pseudo (stream,LOCAL->buf));
  1.2285 +				/* loop through all messages */
  1.2286 +    for (i = 1,flag = LOCAL->pseudo ? 1 : -1; i <= stream->nmsgs;) {
  1.2287 +      elt = mail_elt (stream,i);/* get cache */
  1.2288 +				/* expunge this message? */
  1.2289 +      if (nexp && elt->deleted && (flags ? elt->sequence : T)) {
  1.2290 +				/* one less recent message */
  1.2291 +	if (elt->recent) --recent;
  1.2292 +	mail_expunged(stream,i);/* notify upper levels */
  1.2293 +	++*nexp;		/* count up one more expunged message */
  1.2294 +      }
  1.2295 +      else {			/* preserve this message */
  1.2296 +	i++;			/* advance to next message */
  1.2297 +	if ((flag < 0) ||	/* need to rewrite message? */
  1.2298 +	    elt->private.dirty || (f.curpos != elt->private.special.offset) ||
  1.2299 +	    (elt->private.msg.header.text.size !=
  1.2300 +	     (elt->private.spare.data +
  1.2301 +	      mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag)))) {
  1.2302 +	  unsigned long newoffset = f.curpos;
  1.2303 +				/* yes, seek to internal header */
  1.2304 +	  lseek (LOCAL->fd,elt->private.special.offset,L_SET);
  1.2305 +	  read (LOCAL->fd,LOCAL->buf,elt->private.special.text.size);
  1.2306 +				/* see if need to squeeze out a CR */
  1.2307 +	  if (LOCAL->buf[elt->private.special.text.size - 2] == '\r') {
  1.2308 +	    LOCAL->buf[--elt->private.special.text.size - 1] = '\n';
  1.2309 +	    --size;		/* squeezed out a CR from PC */
  1.2310 +	  }
  1.2311 +				/* protection pointer moves to RFC822 header */
  1.2312 +	  f.protect = elt->private.special.offset +
  1.2313 +	    elt->private.msg.header.offset;
  1.2314 +				/* write internal header */
  1.2315 +	  mmdf_write (&f,LOCAL->buf,elt->private.special.text.size);
  1.2316 +				/* get RFC822 header */
  1.2317 +	  s = mmdf_header (stream,elt->msgno,&j,FT_INTERNAL);
  1.2318 +				/* in case this got decremented */
  1.2319 +	  elt->private.msg.header.offset = elt->private.special.text.size;
  1.2320 +				/* header size, sans trailing newline */
  1.2321 +	  if ((j < 2) || (s[j - 2] == '\n')) j--;
  1.2322 +				/* this can happen if CRs were squeezed */
  1.2323 +	  if (j < elt->private.spare.data) {
  1.2324 +				/* so fix up counts */
  1.2325 +	    size -= elt->private.spare.data - j;
  1.2326 +	    elt->private.spare.data = j;
  1.2327 +	  }
  1.2328 +	  else if (j != elt->private.spare.data)
  1.2329 +	    fatal ("header size inconsistent");
  1.2330 +				/* protection pointer moves to RFC822 text */
  1.2331 +	  f.protect = elt->private.special.offset +
  1.2332 +	    elt->private.msg.text.offset;
  1.2333 +	  mmdf_write (&f,s,j);	/* write RFC822 header */
  1.2334 +				/* write status and UID */
  1.2335 +	  mmdf_write (&f,LOCAL->buf,
  1.2336 +		      j = mmdf_xstatus (stream,LOCAL->buf,elt,NIL,flag));
  1.2337 +	  flag = 1;		/* only write X-IMAPbase once */
  1.2338 +				/* new file header size */
  1.2339 +	  elt->private.msg.header.text.size = elt->private.spare.data + j;
  1.2340 +
  1.2341 +				/* did text move? */
  1.2342 +	  if (f.curpos != f.protect) {
  1.2343 +				/* get message text */
  1.2344 +	    s = mmdf_text_work (stream,elt,&j,FT_INTERNAL);
  1.2345 +				/* this can happen if CRs were squeezed */
  1.2346 +	    if (j < elt->private.msg.text.text.size) {
  1.2347 +				/* so fix up counts */
  1.2348 +	      size -= elt->private.msg.text.text.size - j;
  1.2349 +	      elt->private.msg.text.text.size = j;
  1.2350 +	    }
  1.2351 +				/* can't happen it says here */
  1.2352 +	    else if (j > elt->private.msg.text.text.size)
  1.2353 +	      fatal ("text size inconsistent");
  1.2354 +				/* new text offset, status/UID may change it */
  1.2355 +	    elt->private.msg.text.offset = f.curpos - newoffset;
  1.2356 +				/* protection pointer moves to next message */
  1.2357 +	    f.protect = (i <= stream->nmsgs) ?
  1.2358 +	      mail_elt (stream,i)->private.special.offset :
  1.2359 +		(f.curpos + j + MMDFHDRLEN);
  1.2360 +	    mmdf_write (&f,s,j);/* write text */
  1.2361 +				/* write trailing newline */
  1.2362 +	    mmdf_write (&f,mmdfhdr,MMDFHDRLEN);
  1.2363 +	  }
  1.2364 +	  else {		/* tie off header and status */
  1.2365 +	    mmdf_write (&f,NIL,NIL);
  1.2366 +	    f.curpos = f.protect =/* restart everything at end of message */
  1.2367 +	      f.filepos += elt->private.msg.text.text.size + MMDFHDRLEN;
  1.2368 +	  }
  1.2369 +				/* new internal header offset */
  1.2370 +	  elt->private.special.offset = newoffset;
  1.2371 +	  elt->private.dirty =NIL;/* message is now clean */
  1.2372 +	}
  1.2373 +	else {			/* no need to rewrite this message */
  1.2374 +				/* tie off previous message if needed */
  1.2375 +	  mmdf_write (&f,NIL,NIL);
  1.2376 +	  f.curpos = f.protect =/* restart everything at end of message */
  1.2377 +	    f.filepos += elt->private.special.text.size +
  1.2378 +	      elt->private.msg.header.text.size +
  1.2379 +		elt->private.msg.text.text.size + MMDFHDRLEN;
  1.2380 +	}
  1.2381 +      }
  1.2382 +    }
  1.2383 +
  1.2384 +    mmdf_write (&f,NIL,NIL);	/* tie off final message */
  1.2385 +    if (size != f.filepos) fatal ("file size inconsistent");
  1.2386 +    fs_give ((void **) &f.buf);	/* free buffer */
  1.2387 +				/* make sure tied off */
  1.2388 +    ftruncate (LOCAL->fd,LOCAL->filesize = size);
  1.2389 +    fsync (LOCAL->fd);		/* make sure the updates take */
  1.2390 +    if (size && (flag < 0)) fatal ("lost UID base information");
  1.2391 +				/* no longer dirty */
  1.2392 +    LOCAL->ddirty = LOCAL->dirty = NIL;
  1.2393 +  				/* notify upper level of new mailbox sizes */
  1.2394 +    mail_exists (stream,stream->nmsgs);
  1.2395 +    mail_recent (stream,recent);
  1.2396 +				/* set atime to now, mtime a second earlier */
  1.2397 +    tp[1] = (tp[0] = time (0)) - 1;
  1.2398 +				/* set the times, note change */
  1.2399 +    if (!utime (stream->mailbox,tp)) LOCAL->filetime = tp[1];
  1.2400 +    close (LOCAL->fd);		/* close and reopen file */
  1.2401 +    if ((LOCAL->fd = open (stream->mailbox,O_RDWR,
  1.2402 +			   (long) mail_parameters (NIL,GET_MBXPROTECTION,NIL)))
  1.2403 +	< 0) {
  1.2404 +      sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno));
  1.2405 +      MM_LOG (LOCAL->buf,ERROR);
  1.2406 +      mmdf_abort (stream);
  1.2407 +    }
  1.2408 +    dotlock_unlock (lock);	/* flush the lock file */
  1.2409 +  }
  1.2410 +  return ret;			/* return state from algorithm */
  1.2411 +}
  1.2412 +
  1.2413 +/* Extend MMDF mailbox file
  1.2414 + * Accepts: MAIL stream
  1.2415 + *	    new desired size
  1.2416 + * Return: T if success, else NIL
  1.2417 + */
  1.2418 +
  1.2419 +long mmdf_extend (MAILSTREAM *stream,unsigned long size)
  1.2420 +{
  1.2421 +  unsigned long i = (size > LOCAL->filesize) ? size - LOCAL->filesize : 0;
  1.2422 +  if (i) {			/* does the mailbox need to grow? */
  1.2423 +    if (i > LOCAL->buflen) {	/* make sure have enough space */
  1.2424 +				/* this user won the lottery all right */
  1.2425 +      fs_give ((void **) &LOCAL->buf);
  1.2426 +      LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
  1.2427 +    }
  1.2428 +    memset (LOCAL->buf,'\0',i);	/* get a block of nulls */
  1.2429 +    while (T) {			/* until write successful or punt */
  1.2430 +      lseek (LOCAL->fd,LOCAL->filesize,L_SET);
  1.2431 +      if ((write (LOCAL->fd,LOCAL->buf,i) >= 0) && !fsync (LOCAL->fd)) break;
  1.2432 +      else {
  1.2433 +	long e = errno;		/* note error before doing ftruncate */
  1.2434 +	ftruncate (LOCAL->fd,LOCAL->filesize);
  1.2435 +	if (MM_DISKERROR (stream,e,NIL)) {
  1.2436 +	  fsync (LOCAL->fd);	/* user chose to punt */
  1.2437 +	  sprintf (LOCAL->buf,"Unable to extend mailbox: %s",strerror (e));
  1.2438 +	  if (!stream->silent) MM_LOG (LOCAL->buf,ERROR);
  1.2439 +	  return NIL;
  1.2440 +	}
  1.2441 +      }
  1.2442 +    }
  1.2443 +  }
  1.2444 +  return LONGT;
  1.2445 +}
  1.2446 +
  1.2447 +/* Write data to buffered file
  1.2448 + * Accepts: buffered file pointer
  1.2449 + *	    file data or NIL to indicate "flush buffer"
  1.2450 + *	    date size (ignored for "flush buffer")
  1.2451 + * Does not return until success
  1.2452 + */
  1.2453 +
  1.2454 +void mmdf_write (MMDFFILE *f,char *buf,unsigned long size)
  1.2455 +{
  1.2456 +  unsigned long i,j,k;
  1.2457 +  if (buf) {			/* doing buffered write? */
  1.2458 +    i = f->bufpos - f->buf;	/* yes, get size of current buffer data */
  1.2459 +				/* yes, have space in current buffer chunk? */
  1.2460 +    if (j = i ? ((f->buflen - i) % OVERFLOWBUFLEN) : f->buflen) {
  1.2461 +				/* yes, fill up buffer as much as we can */
  1.2462 +      memcpy (f->bufpos,buf,k = min (j,size));
  1.2463 +      f->bufpos += k;		/* new buffer position */
  1.2464 +      f->curpos += k;		/* new current position */
  1.2465 +      if (j -= k) return;	/* all done if still have buffer free space */
  1.2466 +      buf += k;			/* full, get new unwritten data pointer */
  1.2467 +      size -= k;		/* new data size */
  1.2468 +      i += k;			/* new buffer data size */
  1.2469 +    }
  1.2470 +    /* This chunk of the buffer is full.  See if can make some space by
  1.2471 +     * writing to the disk, if there's enough unprotected space to do so.
  1.2472 +     * Try to fill out any unaligned chunk, along with any subsequent full
  1.2473 +     * chunks that will fit in unprotected space.
  1.2474 +     */
  1.2475 +				/* any unprotected space we can write to? */
  1.2476 +    if (j = min (i,f->protect - f->filepos)) {
  1.2477 +				/* yes, filepos not at chunk boundary? */
  1.2478 +      if ((k = f->filepos % OVERFLOWBUFLEN) && ((k = OVERFLOWBUFLEN - k) < j))
  1.2479 +	j -= k;			/* yes, and can write out partial chunk */
  1.2480 +      else k = 0;		/* no partial chunk to write */
  1.2481 +				/* if at least a chunk free, write that too */
  1.2482 +      if (j > OVERFLOWBUFLEN) k += j - (j % OVERFLOWBUFLEN);
  1.2483 +      if (k) {			/* write data if there is anything we can */
  1.2484 +	mmdf_phys_write (f,f->buf,k);
  1.2485 +				/* slide buffer */
  1.2486 +	if (i -= k) memmove (f->buf,f->buf + k,i);
  1.2487 +	f->bufpos = f->buf + i;	/* new end of buffer */
  1.2488 +      }
  1.2489 +    }
  1.2490 +
  1.2491 +    /* Have flushed the buffer as best as possible.  All done if no more
  1.2492 +     * data to write.  Otherwise, if the buffer is empty AND if the unwritten
  1.2493 +     * data is larger than a chunk AND the unprotected space is also larger
  1.2494 +     * than a chunk, then write as many chunks as we can directly from the
  1.2495 +     * data.  Buffer the rest, expanding the buffer as needed.
  1.2496 +     */
  1.2497 +    if (size) {			/* have more data that we need to buffer? */
  1.2498 +				/* can write any of it to disk instead? */
  1.2499 +      if ((f->bufpos == f->buf) && 
  1.2500 +	  ((j = min (f->protect - f->filepos,size)) > OVERFLOWBUFLEN)) {
  1.2501 +				/* write as much as we can right now */
  1.2502 +	mmdf_phys_write (f,buf,j -= (j % OVERFLOWBUFLEN));
  1.2503 +	buf += j;		/* new data pointer */
  1.2504 +	size -= j;		/* new data size */
  1.2505 +	f->curpos += j;		/* advance current pointer */
  1.2506 +      }
  1.2507 +      if (size) {		/* still have data that we need to buffer? */
  1.2508 +				/* yes, need to expand the buffer? */
  1.2509 +	if ((i = ((f->bufpos + size) - f->buf)) > f->buflen) {
  1.2510 +				/* note current position in buffer */
  1.2511 +	  j = f->bufpos - f->buf;
  1.2512 +	  i += OVERFLOWBUFLEN;	/* yes, grow another chunk */
  1.2513 +	  fs_resize ((void **) &f->buf,f->buflen = i - (i % OVERFLOWBUFLEN));
  1.2514 +				/* in case buffer relocated */
  1.2515 +	  f->bufpos = f->buf + j;
  1.2516 +	}
  1.2517 +				/* buffer remaining data */
  1.2518 +	memcpy (f->bufpos,buf,size);
  1.2519 +	f->bufpos += size;	/* new end of buffer */
  1.2520 +	f->curpos += size;	/* advance current pointer */
  1.2521 +      }
  1.2522 +    }
  1.2523 +  }
  1.2524 +  else {			/* flush buffer to disk */
  1.2525 +    mmdf_phys_write (f,f->buf,i = f->bufpos - f->buf);
  1.2526 +    f->bufpos = f->buf;		/* reset buffer */
  1.2527 +				/* update positions */
  1.2528 +    f->curpos = f->protect = f->filepos;
  1.2529 +  }
  1.2530 +}
  1.2531 +
  1.2532 +/* Physical disk write
  1.2533 + * Accepts: buffered file pointer
  1.2534 + *	    buffer address
  1.2535 + *	    buffer size
  1.2536 + * Does not return until success
  1.2537 + */
  1.2538 +
  1.2539 +void mmdf_phys_write (MMDFFILE *f,char *buf,size_t size)
  1.2540 +{
  1.2541 +  MAILSTREAM *stream = f->stream;
  1.2542 +				/* write data at desired position */
  1.2543 +  while (size && ((lseek (LOCAL->fd,f->filepos,L_SET) < 0) ||
  1.2544 +		  (write (LOCAL->fd,buf,size) < 0))) {
  1.2545 +    int e;
  1.2546 +    char tmp[MAILTMPLEN];
  1.2547 +    sprintf (tmp,"Unable to write to mailbox: %s",strerror (e = errno));
  1.2548 +    MM_LOG (tmp,ERROR);
  1.2549 +    MM_DISKERROR (NIL,e,T);	/* serious problem, must retry */
  1.2550 +  }
  1.2551 +  f->filepos += size;		/* update file position */
  1.2552 +}

UW-IMAP'd extensions by yuuji