1 /* ========================================================================
2  * Copyright 2008-2012 Mark Crispin
3  * ========================================================================
4  */
5 
6 /*
7  * Program:	MIX mail routines
8  *
9  * Author(s):	Mark Crispin
10  *
11  * Date:	1 March 2006
12  * Last Edited:	15 February 2012
13  *
14  * Previous versions of this file were
15  *
16  * Copyright 1988-2008 University of Washington
17  *
18  * Licensed under the Apache License, Version 2.0 (the "License");
19  * you may not use this file except in compliance with the License.
20  * You may obtain a copy of the License at
21  *
22  *     http://www.apache.org/licenses/LICENSE-2.0
23  */
24 
25 
26 #include <stdio.h>
27 #include <ctype.h>
28 #include <errno.h>
29 extern int errno;		/* just in case */
30 #include "mail.h"
31 #include "osdep.h"
32 #include <pwd.h>
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include "misc.h"
36 #include "dummy.h"
37 #include "fdstring.h"
38 
39 /* MIX definitions */
40 
41 #define MEGABYTE (1024*1024)
42 
43 #define MIXDATAROLL MEGABYTE	/* size at which we roll to a new file */
44 
45 
46 /* MIX files */
47 
48 #define MIXNAME ".mix"		/* prefix for all MIX file names */
49 #define MIXMETA "meta"		/* suffix for metadata */
50 #define MIXINDEX "index"	/* suffix for index */
51 #define MIXSTATUS "status"	/* suffix for status */
52 #define MIXSORTCACHE "sortcache"/* suffix for sortcache */
53 #define METAMAX (MEGABYTE-1)	/* maximum metadata file size (sanity check) */
54 
55 
56 /* MIX file formats */
57 
58 				/* sequence format (all but msg files) */
59 #define SEQFMT "S%08lx\015\012"
60 				/* metadata file format */
61 #define MTAFMT "V%08lx\015\012L%08lx\015\012N%08lx\015\012"
62 				/* index file record format */
63 #define IXRFMT ":%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:%08lx:%08lx:%08lx:%08lx:\015\012"
64 				/* status file record format */
65 #define STRFMT ":%08lx:%08lx:%04x:%08lx:\015\012"
66 				/* message file header format */
67 #define MSRFMT "%s%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:\015\012"
68 #define MSGTOK ":msg:"
69 #define MSGTSZ (sizeof(MSGTOK)-1)
70 				/* sortcache file record format */
71 #define SCRFMT ":%08lx:%08lx:%08lx:%08lx:%08lx:%c%08lx:%08lx:%08lx:\015\012"
72 
73 /* MIX I/O stream local data */
74 
75 typedef struct mix_local {
76   unsigned long curmsg;		/* current message file number */
77   unsigned long newmsg;		/* current new message file number */
78   time_t lastsnarf;		/* last snarf time */
79   int msgfd;			/* file description of current msg file */
80   int mfd;			/* file descriptor of open metadata */
81   unsigned long metaseq;	/* metadata sequence */
82   char *index;			/* mailbox index name */
83   unsigned long indexseq;	/* index sequence */
84   char *status;			/* mailbox status name */
85   unsigned long statusseq;	/* status sequence */
86   char *sortcache;		/* mailbox sortcache name */
87   unsigned long sortcacheseq;	/* sortcache sequence */
88   unsigned char *buf;		/* temporary buffer */
89   unsigned long buflen;		/* current size of temporary buffer */
90   unsigned int expok : 1;	/* non-zero if expunge reports OK */
91   unsigned int internal : 1;	/* internally opened, do not validate */
92 } MIXLOCAL;
93 
94 
95 #define MIXBURP struct mix_burp
96 
97 MIXBURP {
98   unsigned long fileno;		/* message file number */
99   char *name;			/* message file name */
100   SEARCHSET *tail;		/* tail of ranges */
101   SEARCHSET set;		/* set of retained ranges */
102   MIXBURP *next;		/* next file to burp */
103 };
104 
105 
106 /* Convenient access to local data */
107 
108 #define LOCAL ((MIXLOCAL *) stream->local)
109 
110 /* Function prototypes */
111 
112 DRIVER *mix_valid (char *name);
113 long mix_isvalid (char *name,char *meta);
114 void *mix_parameters (long function,void *value);
115 long mix_dirfmttest (char *name);
116 void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
117 long mix_scan_contents (char *name,char *contents,unsigned long csiz,
118 			unsigned long fsiz);
119 void mix_list (MAILSTREAM *stream,char *ref,char *pat);
120 void mix_lsub (MAILSTREAM *stream,char *ref,char *pat);
121 long mix_subscribe (MAILSTREAM *stream,char *mailbox);
122 long mix_unsubscribe (MAILSTREAM *stream,char *mailbox);
123 long mix_create (MAILSTREAM *stream,char *mailbox);
124 long mix_delete (MAILSTREAM *stream,char *mailbox);
125 long mix_rename (MAILSTREAM *stream,char *old,char *newname);
126 int mix_rselect (struct direct *name);
127 MAILSTREAM *mix_open (MAILSTREAM *stream);
128 void mix_close (MAILSTREAM *stream,long options);
129 void mix_abort (MAILSTREAM *stream);
130 char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
131 		  long flags);
132 long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
133 void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
134 unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
135 			 SORTPGM *pgm,long flags);
136 THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset,
137 			SEARCHPGM *spg,long flags);
138 long mix_ping (MAILSTREAM *stream);
139 void mix_check (MAILSTREAM *stream);
140 long mix_expunge (MAILSTREAM *stream,char *sequence,long options);
141 int mix_select (struct direct *name);
142 int mix_msgfsort (const void *d1,const void *d2);
143 long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size);
144 long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed);
145 long mix_burp_check (SEARCHSET *set,size_t size,char *file);
146 long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,
147 	       long options);
148 long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
149 long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt,
150 		     STRING *msg,SEARCHSET *set,unsigned long seq);
151 
152 FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags);
153 char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq);
154 long mix_meta_update (MAILSTREAM *stream);
155 long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag);
156 long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag);
157 FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size,
158 		     unsigned long newsize);
159 FILE *mix_sortcache_open (MAILSTREAM *stream);
160 long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache);
161 char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type);
162 unsigned long mix_read_sequence (FILE *f);
163 char *mix_dir (char *dst,char *name);
164 char *mix_file (char *dst,char *dir,char *name);
165 char *mix_file_data (char *dst,char *dir,unsigned long data);
166 unsigned long mix_modseq (unsigned long oldseq);
167 
168 /* MIX mail routines */
169 
170 
171 /* Driver dispatch used by MAIL */
172 
173 DRIVER mixdriver = {
174   "mix",			/* driver name */
175 				/* driver flags */
176   DR_MAIL|DR_LOCAL|DR_NOFAST|DR_CRLF|DR_LOCKING|DR_DIRFMT|DR_MODSEQ,
177   (DRIVER *) NIL,		/* next driver */
178   mix_valid,			/* mailbox is valid for us */
179   mix_parameters,		/* manipulate parameters */
180   mix_scan,			/* scan mailboxes */
181   mix_list,			/* find mailboxes */
182   mix_lsub,			/* find subscribed mailboxes */
183   mix_subscribe,		/* subscribe to mailbox */
184   mix_unsubscribe,		/* unsubscribe from mailbox */
185   mix_create,			/* create mailbox */
186   mix_delete,			/* delete mailbox */
187   mix_rename,			/* rename mailbox */
188   mail_status_default,		/* status of mailbox */
189   mix_open,			/* open mailbox */
190   mix_close,			/* close mailbox */
191   NIL,				/* fetch message "fast" attributes */
192   NIL,				/* fetch message flags */
193   NIL,				/* fetch overview */
194   NIL,				/* fetch message envelopes */
195   mix_header,			/* fetch message header only */
196   mix_text,			/* fetch message body only */
197   NIL,				/* fetch partial message test */
198   NIL,				/* unique identifier */
199   NIL,				/* message number */
200   mix_flag,			/* modify flags */
201   NIL,				/* per-message modify flags */
202   NIL,				/* search for message based on criteria */
203   mix_sort,			/* sort messages */
204   mix_thread,			/* thread messages */
205   mix_ping,			/* ping mailbox to see if still alive */
206   mix_check,			/* check for new messages */
207   mix_expunge,			/* expunge deleted messages */
208   mix_copy,			/* copy messages to another mailbox */
209   mix_append,			/* append string message to mailbox */
210   NIL,				/* garbage collect stream */
211   NIL				/* renew stream */
212 };
213 
214 				/* prototype stream */
215 MAILSTREAM mixproto = {&mixdriver};
216 
217 /* MIX mail validate mailbox
218  * Accepts: mailbox name
219  * Returns: our driver if name is valid, NIL otherwise
220  */
221 
mix_valid(char * name)222 DRIVER *mix_valid (char *name)
223 {
224   char tmp[MAILTMPLEN];
225   return mix_isvalid (name,tmp) ? &mixdriver : NIL;
226 }
227 
228 
229 /* MIX mail test for valid mailbox
230  * Accepts: mailbox name
231  *	    buffer to return meta name
232  * Returns: T if valid, NIL otherwise, metadata name written in both cases
233  */
234 
mix_isvalid(char * name,char * meta)235 long mix_isvalid (char *name,char *meta)
236 {
237   char dir[MAILTMPLEN];
238   struct stat sbuf;
239 				/* validate name as directory */
240   if (!(errno = ((strlen (name) > NETMAXMBX) ? ENAMETOOLONG : NIL)) &&
241       *mix_dir (dir,name) && mix_file (meta,dir,MIXMETA) &&
242       !stat (dir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
243 				/* name is directory; is it mix? */
244     if (!stat (meta,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG))
245       return LONGT;
246     else errno = NIL;		/* directory but not mix */
247   }
248   return NIL;
249 }
250 
251 /* MIX manipulate driver parameters
252  * Accepts: function code
253  *	    function-dependent value
254  * Returns: function-dependent return value
255  */
256 
mix_parameters(long function,void * value)257 void *mix_parameters (long function,void *value)
258 {
259   void *ret = NIL;
260   switch ((int) function) {
261   case GET_INBOXPATH:
262     if (value) ret = mailboxfile ((char *) value,"~/INBOX");
263     break;
264   case GET_DIRFMTTEST:
265     ret = (void *) mix_dirfmttest;
266     break;
267   case GET_SCANCONTENTS:
268     ret = (void *) mix_scan_contents;
269     break;
270   case SET_ONETIMEEXPUNGEATPING:
271     if (value) ((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
272   case GET_ONETIMEEXPUNGEATPING:
273     if (value) ret = (void *)
274       (((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
275     break;
276   }
277   return ret;
278 }
279 
280 
281 /* MIX test for directory format internal node
282  * Accepts: candidate node name
283  * Returns: T if internal name, NIL otherwise
284  */
285 
mix_dirfmttest(char * name)286 long mix_dirfmttest (char *name)
287 {
288 				/* belongs to MIX if starts with .mix */
289   return strncmp (name,MIXNAME,sizeof (MIXNAME) - 1) ? NIL : LONGT;
290 }
291 
292 /* MIX mail scan mailboxes
293  * Accepts: mail stream
294  *	    reference
295  *	    pattern to search
296  *	    string to scan
297  */
298 
mix_scan(MAILSTREAM * stream,char * ref,char * pat,char * contents)299 void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
300 {
301   if (stream) dummy_scan (NIL,ref,pat,contents);
302 }
303 
304 
305 /* MIX scan mailbox for contents
306  * Accepts: mailbox name
307  *	    desired contents
308  *	    contents size
309  *	    file size (ignored)
310  * Returns: NIL if contents not found, T if found
311  */
312 
mix_scan_contents(char * name,char * contents,unsigned long csiz,unsigned long fsiz)313 long mix_scan_contents (char *name,char *contents,unsigned long csiz,
314 			unsigned long fsiz)
315 {
316   long i,nfiles;
317   void *a;
318   char *s;
319   long ret = NIL;
320   size_t namelen = strlen (name);
321   struct stat sbuf;
322   struct direct **names = NIL;
323   if ((nfiles = scandir (name,&names,mix_select,mix_msgfsort)) > 0)
324     for (i = 0; i < nfiles; ++i) {
325       if (!ret) {
326 	sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2),
327 		 "%s/%s",name,names[i]->d_name);
328 	if (!stat (s,&sbuf) && (csiz <= sbuf.st_size))
329 	  ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size);
330 	fs_give ((void **) &s);
331       }
332       fs_give ((void **) &names[i]);
333     }
334 				/* free directory list */
335   if ((a = (void *) names) != NULL) fs_give ((void **) &a);
336   return ret;
337 }
338 
339 /* MIX list mailboxes
340  * Accepts: mail stream
341  *	    reference
342  *	    pattern to search
343  */
344 
mix_list(MAILSTREAM * stream,char * ref,char * pat)345 void mix_list (MAILSTREAM *stream,char *ref,char *pat)
346 {
347   if (stream) dummy_list (NIL,ref,pat);
348 }
349 
350 
351 /* MIX list subscribed mailboxes
352  * Accepts: mail stream
353  *	    reference
354  *	    pattern to search
355  */
356 
mix_lsub(MAILSTREAM * stream,char * ref,char * pat)357 void mix_lsub (MAILSTREAM *stream,char *ref,char *pat)
358 {
359   if (stream) dummy_lsub (NIL,ref,pat);
360 }
361 
362 /* MIX mail subscribe to mailbox
363  * Accepts: mail stream
364  *	    mailbox to add to subscription list
365  * Returns: T on success, NIL on failure
366  */
367 
mix_subscribe(MAILSTREAM * stream,char * mailbox)368 long mix_subscribe (MAILSTREAM *stream,char *mailbox)
369 {
370   return sm_subscribe (mailbox);
371 }
372 
373 
374 /* MIX mail unsubscribe to mailbox
375  * Accepts: mail stream
376  *	    mailbox to delete from subscription list
377  * Returns: T on success, NIL on failure
378  */
379 
mix_unsubscribe(MAILSTREAM * stream,char * mailbox)380 long mix_unsubscribe (MAILSTREAM *stream,char *mailbox)
381 {
382   return sm_unsubscribe (mailbox);
383 }
384 
385 /* MIX mail create mailbox
386  * Accepts: mail stream
387  *	    mailbox name to create
388  * Returns: T on success, NIL on failure
389  */
390 
mix_create(MAILSTREAM * stream,char * mailbox)391 long mix_create (MAILSTREAM *stream,char *mailbox)
392 {
393   DRIVER *test;
394   FILE *f;
395   int c,i;
396   char *t,tmp[MAILTMPLEN],file[MAILTMPLEN];
397   char *s = strrchr (mailbox,'/');
398   unsigned long now = time (NIL);
399   long ret = NIL;
400 				/* always create \NoSelect if trailing /  */
401   if (s && !s[1]) return dummy_create (stream,mailbox);
402 				/* validate name */
403   if (mix_dirfmttest (s ? s + 1 : mailbox))
404     sprintf(tmp,"Can't create mailbox %.80s: invalid MIX-format name",mailbox);
405 				/* must not already exist */
406   else if ((test = mail_valid (NIL,mailbox,NIL)) &&
407 	   strcmp (test->name,"dummy"))
408     sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox);
409 				/* create directory and metadata */
410   else if (!dummy_create_path (stream,
411 			       mix_file (file,mix_dir (tmp,mailbox),MIXMETA),
412 			       get_dir_protection (mailbox)))
413     sprintf (tmp,"Can't create mailbox %.80s: %.80s",mailbox,strerror (errno));
414   else if (!(f = fopen (file,"w")))
415     sprintf (tmp,"Can't re-open metadata %.80s: %.80s",mailbox,
416 	     strerror (errno));
417   else {			/* success, write initial metadata */
418     fprintf (f,SEQFMT,now);
419     fprintf (f,MTAFMT,now,(unsigned long) 0,now);
420     for (i = 0, c = 'K'; (i < NUSERFLAGS) &&
421 	   (t = (stream && stream->user_flags[i]) ? stream->user_flags[i] :
422 	    default_user_flag (i)) && *t; ++i) {
423       putc (c,f);		/* write another keyword */
424       fputs (t,f);
425       c = ' ';			/* delimiter is now space */
426     }
427     fclose (f);
428     set_mbx_protections (mailbox,file);
429 				/* point to suffix */
430     s = file + strlen (file) - (sizeof (MIXMETA) - 1);
431     strcpy (s,MIXINDEX);	/* create index */
432     if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
433       sprintf (tmp,"Can't create mix mailbox index: %.80s",strerror (errno));
434     else {
435       set_mbx_protections (mailbox,file);
436       strcpy (s,MIXSTATUS);	/* create status */
437       if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
438 	sprintf (tmp,"Can't create mix mailbox status: %.80s",
439 		 strerror (errno));
440       else {
441 	set_mbx_protections (mailbox,file);
442 	sprintf (s,"%08lx",now);/* message file */
443 	if (!dummy_create_path (stream,file,get_dir_protection (mailbox)))
444 	  sprintf (tmp,"Can't create mix mailbox data: %.80s",
445 		   strerror (errno));
446 	else {
447 	  set_mbx_protections (mailbox,file);
448 	  ret = LONGT;	/* declare success at this point */
449 	}
450       }
451     }
452   }
453   if (!ret) MM_LOG (tmp,ERROR);	/* some error */
454   return ret;
455 }
456 
457 /* MIX mail delete mailbox
458  *	    mailbox name to delete
459  * Returns: T on success, NIL on failure
460  */
461 
mix_delete(MAILSTREAM * stream,char * mailbox)462 long mix_delete (MAILSTREAM *stream,char *mailbox)
463 {
464   DIR *dirp;
465   struct direct *d;
466   int fd = -1;
467   char *s,tmp[MAILTMPLEN];
468   if (!mix_isvalid (mailbox,tmp))
469     sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox);
470   else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB))
471     sprintf (tmp,"Can't lock mailbox for delete: %.80s",mailbox);
472 				/* delete metadata */
473   else if (unlink (tmp)) sprintf (tmp,"Can't delete mailbox %.80s index: %80s",
474 				  mailbox,strerror (errno));
475   else {
476     close (fd);			/* close descriptor on deleted metadata */
477 				/* get directory name */
478     *(s = strrchr (tmp,'/')) = '\0';
479     if ((dirp = opendir (tmp)) != NULL) {	/* open directory */
480       *s++ = '/';		/* restore delimiter */
481 				/* massacre messages */
482       while ((d = readdir (dirp)) != NULL) if (mix_dirfmttest (d->d_name)) {
483 	strcpy (s,d->d_name);	/* make path */
484 	unlink (tmp);		/* sayonara */
485       }
486       closedir (dirp);		/* flush directory */
487       *(s = strrchr (tmp,'/')) = '\0';
488       if (rmdir (tmp)) {	/* try to remove the directory */
489 	sprintf (tmp,"Can't delete name %.80s: %.80s",
490 		 mailbox,strerror (errno));
491 	MM_LOG (tmp,WARN);
492       }
493     }
494     return T;			/* always success */
495   }
496   if (fd >= 0) close (fd);	/* close any descriptor on metadata */
497   MM_LOG (tmp,ERROR);		/* something failed */
498   return NIL;
499 }
500 
501 /* MIX mail rename mailbox
502  * Accepts: MIX mail stream
503  *	    old mailbox name
504  *	    new mailbox name
505  * Returns: T on success, NIL on failure
506  */
507 
mix_rename(MAILSTREAM * stream,char * old,char * newname)508 long mix_rename (MAILSTREAM *stream,char *old,char *newname)
509 {
510   char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN];
511   struct stat sbuf;
512   int fd = -1;
513   if (!mix_isvalid (old,tmp))
514     sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old);
515   else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB))
516     sprintf (tmp,"Can't lock mailbox for rename: %.80s",old);
517   else if (mix_dirfmttest ((s = strrchr (newname,'/')) ? s + 1 : newname))
518     sprintf (tmp,"Can't rename to mailbox %.80s: invalid MIX-format name",
519 	     newname);
520 				/* new mailbox name must not be valid */
521   else if (mix_isvalid (newname,tmp))
522     sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists",
523 	     newname);
524   else {
525     mix_dir (tmp,old);		/* build old directory name */
526     mix_dir (tmp1,newname);	/* and new directory name */
527 				/* easy if not INBOX */
528     if (compare_cstring (old,"INBOX")) {
529 				/* found superior to destination name? */
530       if ((s = strrchr (tmp1,'/')) != NULL) {
531 	c = *++s;		/* remember first character of inferior */
532 	*s = '\0';		/* tie off to get just superior */
533 				/* name doesn't exist, create it */
534 	if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) &&
535 	    !dummy_create_path (stream,tmp1,get_dir_protection (newname)))
536 	  return NIL;
537 	*s = c;			/* restore full name */
538       }
539       if (!rename (tmp,tmp1)) {
540 	close (fd);		/* close descriptor on metadata */
541 	return LONGT;
542       }
543     }
544 
545 				/* RFC 3501 requires this */
546     else if (dummy_create_path (stream,strcat (tmp1,"/"),
547 				get_dir_protection (newname))) {
548       void *a;
549       int i,n,lasterror;
550       char *src,*dst;
551       struct direct **names = NIL;
552       size_t srcl = strlen (tmp);
553       size_t dstl = strlen (tmp1);
554 				/* rename each mix file to new directory */
555       for (i = lasterror = 0,n = scandir (tmp,&names,mix_rselect,alphasort);
556 	   i < n; ++i) {
557 	size_t len = strlen (names[i]->d_name);
558 	sprintf (src = (char *) fs_get (srcl + len + 2),"%s/%s",
559 		 tmp,names[i]->d_name);
560 	sprintf (dst = (char *) fs_get (dstl + len + 1),"%s%s",
561 		 tmp1,names[i]->d_name);
562 	if (rename (src,dst)) lasterror = errno;
563 	fs_give ((void **) &src);
564 	fs_give ((void **) &dst);
565 	fs_give ((void **) &names[i]);
566       }
567 				/* free directory list */
568       if ((a = (void *) names) != NULL) fs_give ((void **) &a);
569       if (lasterror) errno = lasterror;
570       else {
571 	close (fd);		/* close descriptor on metadata */
572 	return mix_create (NIL,"INBOX");
573       }
574     }
575     sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",
576 	     old,newname,strerror (errno));
577   }
578   if (fd >= 0) close (fd);	/* close any descriptor on metadata */
579   MM_LOG (tmp,ERROR);		/* something failed */
580   return NIL;
581 }
582 
583 
584 /* MIX test for mix name
585  * Accepts: candidate directory name
586  * Returns: T if mix file name, NIL otherwise
587  */
588 
mix_rselect(struct direct * name)589 int mix_rselect (struct direct *name)
590 {
591   return mix_dirfmttest (name->d_name);
592 }
593 
594 /* MIX mail open
595  * Accepts: stream to open
596  * Returns: stream on success, NIL on failure
597  */
598 
mix_open(MAILSTREAM * stream)599 MAILSTREAM *mix_open (MAILSTREAM *stream)
600 {
601   short silent;
602 				/* return prototype for OP_PROTOTYPE call */
603   if (!stream) return user_flags (&mixproto);
604   if (stream->local) fatal ("mix recycle stream");
605   stream->local = memset (fs_get (sizeof (MIXLOCAL)),0,sizeof (MIXLOCAL));
606 				/* note if an INBOX or not */
607   stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
608 				/* make temporary buffer */
609   LOCAL->buf = (char *) fs_get (CHUNKSIZE);
610   LOCAL->buflen = CHUNKSIZE - 1;
611 				/* set stream->mailbox to be directory name */
612   mix_dir (LOCAL->buf,stream->mailbox);
613   fs_give ((void **) &stream->mailbox);
614   stream->mailbox = cpystr (LOCAL->buf);
615   LOCAL->msgfd = -1;		/* currently no file open */
616   if (!(((!stream->rdonly &&	/* open metadata file */
617 	  ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA),
618 			       O_RDWR,NIL)) >= 0)) ||
619 	 ((stream->rdonly = T) &&
620 	  ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA),
621 			       O_RDONLY,NIL)) >= 0))) &&
622 	!flock (LOCAL->mfd,LOCK_SH))) {
623     MM_LOG ("Error opening mix metadata file",ERROR);
624     mix_abort (stream);
625     stream = NIL;		/* open fails */
626   }
627   else {			/* metadata open, complete open */
628     LOCAL->index = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXINDEX));
629     LOCAL->status = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXSTATUS));
630     LOCAL->sortcache = cpystr (mix_file (LOCAL->buf,stream->mailbox,
631 					 MIXSORTCACHE));
632     stream->sequence++;		/* bump sequence number */
633 				/* parse mailbox */
634     stream->nmsgs = stream->recent = 0;
635     if ((silent = stream->silent) != 0) LOCAL->internal = T;
636     stream->silent = T;
637     if (mix_ping (stream)) {	/* do initial ping */
638 				/* try burping in case we are exclusive */
639       if (!stream->rdonly) mix_expunge (stream,"",NIL);
640       if (!(stream->nmsgs || stream->silent))
641 	MM_LOG ("Mailbox is empty",(long) NIL);
642       stream->silent = silent;	/* now notify upper level */
643       mail_exists (stream,stream->nmsgs);
644       stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
645 	stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
646       stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
647       stream->kwd_create =	/* can we create new user flags? */
648 	(stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ? NIL : T;
649     }
650     else {			/* got murdelyzed in ping */
651       mix_abort (stream);
652       stream = NIL;
653     }
654   }
655   return stream;		/* return stream to caller */
656 }
657 
658 /* MIX mail close
659  * Accepts: MAIL stream
660  *	    close options
661  */
662 
mix_close(MAILSTREAM * stream,long options)663 void mix_close (MAILSTREAM *stream,long options)
664 {
665   if (LOCAL) {			/* only if a file is open */
666     int silent = stream->silent;
667     stream->silent = T;		/* note this stream is dying */
668 				/* burp-only or expunge */
669     mix_expunge (stream,(options & CL_EXPUNGE) ? NIL : "",NIL);
670     mix_abort (stream);
671     stream->silent = silent;	/* reset silent state */
672   }
673 }
674 
675 
676 /* MIX mail abort stream
677  * Accepts: MAIL stream
678  */
679 
mix_abort(MAILSTREAM * stream)680 void mix_abort (MAILSTREAM *stream)
681 {
682   if (LOCAL) {			/* only if a file is open */
683 				/* close current message file if open */
684     if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
685 				/* close current metadata file if open */
686     if (LOCAL->mfd >= 0) close (LOCAL->mfd);
687     if (LOCAL->index) fs_give ((void **) &LOCAL->index);
688     if (LOCAL->status) fs_give ((void **) &LOCAL->status);
689     if (LOCAL->sortcache) fs_give ((void **) &LOCAL->sortcache);
690 				/* free local scratch buffer */
691     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
692 				/* nuke the local data */
693     fs_give ((void **) &stream->local);
694     stream->dtb = NIL;		/* log out the DTB */
695   }
696 }
697 
698 /* MIX mail fetch message header
699  * Accepts: MAIL stream
700  *	    message # to fetch
701  *	    pointer to returned header text length
702  *	    option flags
703  * Returns: message header in RFC822 format
704  */
705 
mix_header(MAILSTREAM * stream,unsigned long msgno,unsigned long * length,long flags)706 char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
707 		  long flags)
708 {
709   unsigned long i,j,k;
710   int fd;
711   char *s,tmp[MAILTMPLEN];
712   MESSAGECACHE *elt;
713   if (length) *length = 0;	/* default return */
714   if (flags & FT_UID) return "";/* UID call "impossible" */
715   elt = mail_elt (stream,msgno);/* get elt */
716 				/* is message in current message file? */
717   if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) {
718     if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
719     if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox,
720 					     elt->private.spare.data),
721 					     O_RDONLY,NIL)) < 0) return "";
722 				/* got file */
723     LOCAL->curmsg = elt->private.spare.data;
724   }
725   lseek (LOCAL->msgfd,elt->private.special.offset,L_SET);
726 				/* size of special data and header */
727   j = elt->private.msg.header.offset + elt->private.msg.header.text.size;
728   if (j > LOCAL->buflen) {	/* is buffer big enough? */
729 				/* no, make one that is */
730     fs_give ((void **) &LOCAL->buf);
731     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = j) + 1);
732   }
733   /* Maybe someday validate internaldate too */
734 				/* slurp special data + header, validate */
735   if ((read (LOCAL->msgfd,LOCAL->buf,j) == j) &&
736       !strncmp (LOCAL->buf,MSGTOK,MSGTSZ) &&
737       (elt->private.uid == strtoul ((char *) LOCAL->buf + MSGTSZ,&s,16)) &&
738       (*s++ == ':') && (s = strchr (s,':')) &&
739       (k = strtoul (s+1,&s,16)) && (*s++ == ':') &&
740       (s < (char *) (LOCAL->buf + elt->private.msg.header.offset))) {
741 				/* won, set offset and size of message */
742     i = elt->private.msg.header.offset;
743     *length = elt->private.msg.header.text.size;
744     if (k != elt->rfc822_size) {
745       sprintf (tmp,"Inconsistency in mix message size, uid=%lx (%lu != %lu)",
746 	       elt->private.uid,elt->rfc822_size,k);
747       MM_LOG (tmp,WARN);
748     }
749   }
750   else {			/* document the problem */
751     LOCAL->buf[100] = '\0';	/* tie off buffer at no more than 100 octets */
752 				/* or at newline, whichever is first */
753     if ((s = strpbrk (LOCAL->buf,"\015\012")) != NULL) *s = '\0';
754     sprintf (tmp,"Error reading mix message header, uid=%lx, s=%.0lx, h=%s",
755 	     elt->private.uid,elt->rfc822_size,LOCAL->buf);
756     MM_LOG (tmp,ERROR);
757     *length = i = j = 0;	/* default to empty */
758   }
759   LOCAL->buf[j] = '\0';		/* tie off buffer at the end */
760   return (char *) LOCAL->buf + i;
761 }
762 
763 /* MIX mail fetch message text (body only)
764  * Accepts: MAIL stream
765  *	    message # to fetch
766  *	    pointer to returned stringstruct
767  *	    option flags
768  * Returns: T on success, NIL on failure
769  */
770 
mix_text(MAILSTREAM * stream,unsigned long msgno,STRING * bs,long flags)771 long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
772 {
773   unsigned long i;
774   FDDATA d;
775   MESSAGECACHE *elt;
776 				/* UID call "impossible" */
777   if (flags & FT_UID) return NIL;
778   elt = mail_elt (stream,msgno);
779 				/* is message in current message file? */
780   if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) {
781     if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
782     if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox,
783 					     elt->private.spare.data),
784 					     O_RDONLY,NIL)) < 0) return NIL;
785 				/* got file */
786     LOCAL->curmsg = elt->private.spare.data;
787   }
788 				/* doing non-peek fetch? */
789   if (!(flags & FT_PEEK) && !elt->seen) {
790     FILE *idxf;			/* yes, process metadata/index/status */
791     FILE *statf = mix_parse (stream,&idxf,NIL,LONGT);
792     elt->seen = T;		/* mark as seen */
793     MM_FLAGS (stream,elt->msgno);
794 				/* update status file if possible */
795     if (statf && !stream->rdonly) {
796       elt->private.mod = LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
797       mix_status_update (stream,statf,NIL);
798     }
799     if (idxf) fclose (idxf);	/* release index and status file */
800     if (statf) fclose (statf);
801   }
802   d.fd = LOCAL->msgfd;		/* set up file descriptor */
803 				/* offset of message text */
804   d.pos = elt->private.special.offset + elt->private.msg.header.offset +
805     elt->private.msg.header.text.size;
806   d.chunk = LOCAL->buf;		/* initial buffer chunk */
807   d.chunksize = CHUNKSIZE;	/* chunk size */
808   INIT (bs,fd_string,&d,elt->rfc822_size - elt->private.msg.header.text.size);
809   return T;
810 }
811 
812 /* MIX mail modify flags
813  * Accepts: MAIL stream
814  *	    sequence
815  *	    flag(s)
816  *	    option flags
817  */
818 
mix_flag(MAILSTREAM * stream,char * sequence,char * flag,long flags)819 void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
820 {
821   MESSAGECACHE *elt;
822   unsigned long i,uf,ffkey;
823   long f;
824   short nf;
825   FILE *idxf;
826   FILE *statf = mix_parse (stream,&idxf,NIL,LONGT);
827   unsigned long seq = mix_modseq (LOCAL->statusseq);
828 				/* find first free key */
829   for (ffkey = 0; (ffkey < NUSERFLAGS) && stream->user_flags[ffkey]; ++ffkey);
830 				/* parse sequence and flags */
831   if (((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
832        mail_sequence (stream,sequence)) &&
833       ((f = mail_parse_flags (stream,flag,&uf)) || uf)) {
834 				/* alter flags */
835     for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++)
836       if ((elt = mail_elt (stream,i))->sequence) {
837 	struct {		/* old flags */
838 	  unsigned int seen : 1;
839 	  unsigned int deleted : 1;
840 	  unsigned int flagged : 1;
841 	  unsigned int answered : 1;
842 	  unsigned int draft : 1;
843 	  unsigned long user_flags;
844 	} old;
845 	old.seen = elt->seen; old.deleted = elt->deleted;
846 	old.flagged = elt->flagged; old.answered = elt->answered;
847 	old.draft = elt->draft; old.user_flags = elt->user_flags;
848 	if (f&fSEEN) elt->seen = nf;
849 	if (f&fDELETED) elt->deleted = nf;
850 	if (f&fFLAGGED) elt->flagged = nf;
851 	if (f&fANSWERED) elt->answered = nf;
852 	if (f&fDRAFT) elt->draft = nf;
853 				/* user flags */
854 	if (flags & ST_SET) elt->user_flags |= uf;
855 	else elt->user_flags &= ~uf;
856 	if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
857 	    (old.flagged != elt->flagged) ||
858 	    (old.answered != elt->answered) || (old.draft != elt->draft) ||
859 	    (old.user_flags != elt->user_flags)) {
860 	  if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq;
861 	  MM_FLAGS (stream,elt->msgno);
862 	}
863       }
864 				/* update status file after change */
865     if (statf && (seq == LOCAL->statusseq))
866       mix_status_update (stream,statf,NIL);
867 				/* update metadata if created a keyword */
868     if ((ffkey < NUSERFLAGS) && stream->user_flags[ffkey] &&
869 	!mix_meta_update (stream))
870       MM_LOG ("Error updating mix metadata after keyword creation",ERROR);
871   }
872   if (statf) fclose (statf);	/* release status file if still open */
873   if (idxf) fclose (idxf);	/* release index file */
874 }
875 
876 /* MIX mail sort messages
877  * Accepts: mail stream
878  *	    character set
879  *	    search program
880  *	    sort program
881  *	    option flags
882  * Returns: vector of sorted message sequences or NIL if error
883  */
884 
mix_sort(MAILSTREAM * stream,char * charset,SEARCHPGM * spg,SORTPGM * pgm,long flags)885 unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg,
886 			 SORTPGM *pgm,long flags)
887 {
888   unsigned long *ret;
889   FILE *sortcache = mix_sortcache_open (stream);
890   ret = mail_sort_msgs (stream,charset,spg,pgm,flags);
891   mix_sortcache_update (stream,&sortcache);
892   return ret;
893 }
894 
895 
896 /* MIX mail thread messages
897  * Accepts: mail stream
898  *	    thread type
899  *	    character set
900  *	    search program
901  *	    option flags
902  * Returns: thread node tree or NIL if error
903  */
904 
mix_thread(MAILSTREAM * stream,char * type,char * charset,SEARCHPGM * spg,long flags)905 THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset,
906 			SEARCHPGM *spg,long flags)
907 {
908   THREADNODE *ret;
909   FILE *sortcache = mix_sortcache_open (stream);
910   ret = mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs);
911   mix_sortcache_update (stream,&sortcache);
912   return ret;
913 }
914 
915 /* MIX mail ping mailbox
916  * Accepts: MAIL stream
917  * Returns: T if stream alive, else NIL
918  */
919 
920 static int snarfing = 0;	/* lock against recursive snarfing */
921 
mix_ping(MAILSTREAM * stream)922 long mix_ping (MAILSTREAM *stream)
923 {
924   FILE *idxf,*statf;
925   struct stat sbuf;
926   STRING msg;
927   MESSAGECACHE *elt;
928   int mfd,ifd,sfd;
929   unsigned long i,msglen;
930   char *message,date[MAILTMPLEN],flags[MAILTMPLEN];
931   MAILSTREAM *sysibx = NIL;
932   long ret = NIL;
933   long snarfok = LONGT;
934 				/* time to snarf? */
935   if (stream->inbox && !stream->rdonly && !snarfing &&
936       (time (0) >= (LOCAL->lastsnarf +
937 		    (time_t) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) {
938     appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
939     copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
940     MM_CRITICAL (stream);	/* go critical */
941     snarfing = T;		/* don't recursively snarf */
942 				/* disable APPENDUID/COPYUID callbacks */
943     mail_parameters (NIL,SET_APPENDUID,NIL);
944     mail_parameters (NIL,SET_COPYUID,NIL);
945 				/* sizes match and anything in sysinbox? */
946     if (!stat (sysinbox (),&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG) &&
947 	sbuf.st_size &&	(sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) &&
948 	!sysibx->rdonly && sysibx->nmsgs) {
949 				/* for each message in sysibx mailbox */
950       for (i = 1; snarfok && (i <= sysibx->nmsgs); ++i)
951 	if (!(elt = mail_elt (sysibx,i))->deleted &&
952 	    (message = mail_fetch_message (sysibx,i,&msglen,FT_PEEK)) &&
953 	    msglen) {
954 	  mail_date (date,elt);	/* make internal date string */
955 				/* make flag string */
956 	  flags[0] = flags[1] = '\0';
957 	  if (elt->seen) strcat (flags," \\Seen");
958 	  if (elt->flagged) strcat (flags," \\Flagged");
959 	  if (elt->answered) strcat (flags," \\Answered");
960 	  if (elt->draft) strcat (flags," \\Draft");
961 	  flags[0] = '(';
962 	  strcat (flags,")");
963 	  INIT (&msg,mail_string,message,msglen);
964 	  if ((snarfok = mail_append_full (stream,"INBOX",flags,date,&msg)) != 0L) {
965 	    char sequence[15];
966 	    sprintf (sequence,"%lu",i);
967 	    mail_flag (sysibx,sequence,"\\Deleted",ST_SET);
968 	  }
969 	}
970 
971 				/* now expunge all those messages */
972       if (snarfok) mail_expunge (sysibx);
973       else {
974 	sprintf (LOCAL->buf,"Can't copy new mail at message: %lu",i - 1);
975 	MM_LOG (LOCAL->buf,WARN);
976       }
977     }
978     if (sysibx) mail_close (sysibx);
979 				/* re-enable APPENDUID/COPYUID */
980     mail_parameters (NIL,SET_APPENDUID,(void *) au);
981     mail_parameters (NIL,SET_COPYUID,(void *) cu);
982     snarfing = NIL;		/* no longer snarfing */
983     MM_NOCRITICAL (stream);	/* release critical */
984     LOCAL->lastsnarf = time (0);/* note time of last snarf */
985   }
986 				/* expunging OK if global flag set */
987   if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
988 				/* process metadata/index/status */
989   if ((statf = mix_parse (stream,&idxf,LONGT,
990 			 (LOCAL->internal ? NIL : LONGT))) != NULL) {
991     fclose (statf);		/* just close the status file */
992     ret = LONGT;		/* declare success */
993   }
994   if (idxf) fclose (idxf);	/* release index file */
995   LOCAL->expok = NIL;		/* expunge no longer OK */
996   if (!ret) mix_abort (stream);	/* murdelyze stream if ping fails */
997   return ret;
998 }
999 
1000 
1001 /* MIX mail checkpoint mailbox (burp only)
1002  * Accepts: MAIL stream
1003  */
1004 
mix_check(MAILSTREAM * stream)1005 void mix_check (MAILSTREAM *stream)
1006 {
1007   if (stream->rdonly)		/* won't do on readonly files! */
1008     MM_LOG ("Checkpoint ignored on readonly mailbox",NIL);
1009 				/* do burp-only expunge action */
1010   if (mix_expunge (stream,"",NIL)) MM_LOG ("Check completed",(long) NIL);
1011 }
1012 
1013 /* MIX mail expunge mailbox
1014  * Accepts: MAIL stream
1015  *	    sequence to expunge if non-NIL, empty string for burp only
1016  *	    expunge options
1017  * Returns: T on success, NIL if failure
1018  */
1019 
mix_expunge(MAILSTREAM * stream,char * sequence,long options)1020 long mix_expunge (MAILSTREAM *stream,char *sequence,long options)
1021 {
1022   FILE *idxf = NIL;
1023   FILE *statf = NIL;
1024   MESSAGECACHE *elt;
1025   int ifd,sfd;
1026   long ret;
1027   unsigned long i;
1028   unsigned long nexp = 0;
1029   unsigned long reclaimed = 0;
1030   int burponly = (sequence && !*sequence);
1031   LOCAL->expok = T;		/* expunge during ping is OK */
1032   if (!(ret = burponly || !sequence ||
1033 	((options & EX_UID) ?
1034 	 mail_uid_sequence (stream,sequence) :
1035 	 mail_sequence (stream,sequence))) || stream->rdonly);
1036 				/* read index and open status exclusive */
1037   else if ((statf = mix_parse (stream,&idxf,LONGT,
1038 			      LOCAL->internal ? NIL : LONGT)) != NULL) {
1039 				/* expunge unless just burping */
1040     if (!burponly) for (i = 1; i <= stream->nmsgs;) {
1041       elt = mail_elt (stream,i);/* need to expunge this message? */
1042       if (elt->deleted && (sequence ? elt->sequence : T)) {
1043 	++nexp;			/* yes, make it so */
1044 	mail_expunged (stream,i);
1045       }
1046       else ++i;		       /* otherwise advance to next message */
1047     }
1048 
1049 				/* burp if can get exclusive access */
1050     if (!flock (LOCAL->mfd,LOCK_EX|LOCK_NB)) {
1051       void *a;
1052       struct direct **names = NIL;
1053       long nfiles = scandir (stream->mailbox,&names,mix_select,mix_msgfsort);
1054       if (nfiles > 0) {		/* if have message files */
1055 	MIXBURP *burp,*cur;
1056 				/* initialize burp list */
1057 	for (i = 0, burp = cur = NIL; i < nfiles; ++i) {
1058 	  MIXBURP *nxt = (MIXBURP *) memset (fs_get (sizeof (MIXBURP)),0,
1059 					     sizeof (MIXBURP));
1060 				/* another file found */
1061 	  if (cur) cur = cur->next = nxt;
1062 	  else cur = burp = nxt;
1063 	  cur->name = cpystr (names[i]->d_name);
1064 	  cur->fileno = strtoul (cur->name + sizeof (MIXNAME) - 1,NIL,16);
1065 	  cur->tail = &cur->set;
1066 	  fs_give ((void **) &names[i]);
1067 	}
1068 				/* now load ranges */
1069 	for (i = 1, cur = burp; ret && (i <= stream->nmsgs); i++) {
1070 				/* is this message in current set? */
1071 	  elt = mail_elt (stream,i);
1072 	  if (cur && (elt->private.spare.data != cur->fileno)) {
1073 				/* restart if necessary */
1074 	    if (elt->private.spare.data < cur->fileno) cur = burp;
1075 				/* hunt for appropriate mailbox */
1076 	    while (cur && (elt->private.spare.data > cur->fileno))
1077 	      cur = cur->next;
1078 				/* ought to have found it now... */
1079 	    if (cur && (elt->private.spare.data != cur->fileno)) cur = NIL;
1080 	  }
1081 				/* if found, add to set */
1082 	  if (cur) ret = mix_addset (&cur->tail,elt->private.special.offset,
1083 				     elt->private.msg.header.offset +
1084 				     elt->rfc822_size);
1085 	  else {		/* uh-oh */
1086 	    sprintf (LOCAL->buf,"Can't locate mix message file %.08lx",
1087 		     elt->private.spare.data);
1088 	    MM_LOG (LOCAL->buf,ERROR);
1089 	    ret = NIL;
1090 	  }
1091 	}
1092 	if (ret) 		/* if no errors, burp all files */
1093 	  for (cur = burp; ret && cur; cur = cur->next) {
1094 				/* if non-empty, burp it */
1095 	    if (cur->set.last) ret = mix_burp (stream,cur,&reclaimed);
1096 				/* empty, delete it unless new msg file */
1097 	    else if (mix_file_data (LOCAL->buf,stream->mailbox,cur->fileno) &&
1098 		     ((cur->fileno == LOCAL->newmsg) ?
1099 		      truncate (LOCAL->buf,0) : unlink (LOCAL->buf))) {
1100 	      sprintf (LOCAL->buf,
1101 		       "Can't delete empty message file %.80s: %.80s",
1102 		       cur->name,strerror (errno));
1103 	      MM_LOG (LOCAL->buf,WARN);
1104 	    }
1105 	  }
1106 	while (burp) {		/* flush the burp list */
1107 	  cur = burp->next;
1108 	  if (burp->name) fs_give ((void **) &burp->name);
1109 	  fs_give ((void **) &burp);
1110 	  burp = cur;
1111 	}
1112       }
1113       else MM_LOG ("No mix message files found during expunge",WARN);
1114 				/* free directory list */
1115       if ((a = (void *) names) != NULL) fs_give ((void **) &a);
1116     }
1117 
1118 				/* either way, re-acquire shared lock */
1119     if (flock (LOCAL->mfd,LOCK_SH|LOCK_NB))
1120       fatal ("Unable to re-acquire metadata shared lock!");
1121     /* Do this step even if ret is NIL (meaning some burp problem)! */
1122     if (nexp || reclaimed) {	/* rewrite index and status if changed */
1123       LOCAL->indexseq = mix_modseq (LOCAL->indexseq);
1124       if ((ret = mix_index_update (stream,idxf,NIL)) != 0L){
1125 	LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
1126 				/* set failure if update fails */
1127 	ret = mix_status_update (stream,statf,NIL);
1128       }
1129     }
1130   }
1131   if (statf) fclose (statf);	/* close status if still open */
1132   if (idxf) fclose (idxf);	/* close index if still open */
1133   LOCAL->expok = NIL;		/* cancel expok */
1134   if (ret) {			/* only if success */
1135     char *s = NIL;
1136     if (nexp) sprintf (s = LOCAL->buf,"Expunged %lu messages",nexp);
1137     else if (reclaimed)
1138       sprintf (s=LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
1139     else if (!burponly)
1140       s = stream->rdonly ? "Expunge ignored on readonly mailbox" :
1141 	   "No messages deleted, so no update needed";
1142     if (s) MM_LOG (s,(long) NIL);
1143   }
1144   return ret;
1145 }
1146 
1147 /* MIX test for message file name
1148  * Accepts: candidate directory name
1149  * Returns: T if message file name, NIL otherwise
1150  *
1151  * ".mix" with no suffix was used by experimental versions
1152  */
1153 
mix_select(struct direct * name)1154 int mix_select (struct direct *name)
1155 {
1156   char c,*s;
1157 				/* make sure name has prefix */
1158   if (mix_dirfmttest (name->d_name)) {
1159     for (c = *(s = name->d_name + sizeof (MIXNAME) - 1); c && isxdigit (c);
1160 	 c = *s++);
1161     if (!c) return T;		/* all-hex or no suffix */
1162   }
1163   return NIL;			/* not suffix or non-hex */
1164 }
1165 
1166 
1167 /* MIX msg file name comparison
1168  * Accepts: first candidate directory entry
1169  *	    second candidate directory entry
1170  * Returns: -1 if d1 < d2, 0 if d1 == d2, 1 d1 > d2
1171  */
1172 
mix_msgfsort(const void * d1,const void * d2)1173 int mix_msgfsort (const void *d1,const void *d2)
1174 {
1175   char *n1 = (*(struct direct **) d1)->d_name + sizeof (MIXNAME) - 1;
1176   char *n2 = (*(struct direct **) d2)->d_name + sizeof (MIXNAME) - 1;
1177   return compare_ulong (*n1 ? strtoul (n1,NIL,16) : 0,
1178 			*n2 ? strtoul (n2,NIL,16) : 0);
1179 }
1180 
1181 
1182 /* MIX add a range to a set
1183  * Accepts: pointer to set to add
1184  *	    start of set
1185  *	    size of set
1186  * Returns: T if success, set updated, NIL otherwise
1187  */
1188 
mix_addset(SEARCHSET ** set,unsigned long start,unsigned long size)1189 long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size)
1190 {
1191   SEARCHSET *s = *set;
1192   if (start < s->last) {	/* sanity check */
1193     char tmp[MAILTMPLEN];
1194     sprintf (tmp,"Backwards-running mix index %lu < %lu",start,s->last);
1195     MM_LOG (tmp,ERROR);
1196     return NIL;
1197   }
1198 				/* range initially empty? */
1199   if (!s->last) s->first = start;
1200   else if (start > s->last)	/* no, start new range if can't append */
1201     (*set = s = s->next = mail_newsearchset ())->first = start;
1202   s->last = start + size;	/* end of current range */
1203   return LONGT;
1204 }
1205 
1206 /* MIX burp message file
1207  * Accepts: MAIL stream
1208  *	    current burp block for this message
1209  * Returns: T if successful, NIL if failed
1210  */
1211 
1212 static char *staterr = "Error in stat of mix message file %.80s: %.80s";
1213 static char *truncerr = "Error truncating mix message file %.80s: %.80s";
1214 
mix_burp(MAILSTREAM * stream,MIXBURP * burp,unsigned long * reclaimed)1215 long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed)
1216 {
1217   MESSAGECACHE *elt;
1218   SEARCHSET *set;
1219   struct stat sbuf;
1220   off_t rpos,wpos;
1221   size_t size,wsize,wpending,written;
1222   int fd;
1223   FILE *f;
1224   void *s;
1225   unsigned long i;
1226   long ret = NIL;
1227 				/* build file name */
1228   mix_file_data (LOCAL->buf,stream->mailbox,burp->fileno);
1229 				/* need to burp at start or multiple ranges? */
1230   if (!burp->set.first && !burp->set.next) {
1231 				/* easy case, single range at start of file */
1232     if (stat (LOCAL->buf,&sbuf)) {
1233       sprintf (LOCAL->buf,staterr,burp->name,strerror (errno));
1234       MM_LOG (LOCAL->buf,ERROR);
1235     }
1236 				/* is this range sane? */
1237     else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) {
1238 				/* if matches range then no burp needed! */
1239       if (burp->set.last == sbuf.st_size) ret = LONGT;
1240 				/* just need to remove cruft at end */
1241       else if ((ret = !truncate (LOCAL->buf,burp->set.last)) != 0L)
1242 	*reclaimed += sbuf.st_size - burp->set.last;
1243       else {
1244 	sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno));
1245 	MM_LOG (LOCAL->buf,ERROR);
1246       }
1247     }
1248   }
1249 				/* have to do more work, get the file */
1250   else if (((fd = open (LOCAL->buf,O_RDWR,NIL)) < 0) ||
1251 	   !(f = fdopen (fd,"r+b"))) {
1252     sprintf (LOCAL->buf,"Error opening mix message file %.80s: %.80s",
1253 	     burp->name,strerror (errno));
1254     MM_LOG (LOCAL->buf,ERROR);
1255     if (fd >= 0) close (fd);	/* in case fdopen() failure */
1256   }
1257   else if (fstat (fd,&sbuf)) {	/* get file size */
1258     sprintf (LOCAL->buf,staterr,burp->name,strerror (errno));
1259     MM_LOG (LOCAL->buf,ERROR);
1260     fclose (f);
1261   }
1262 
1263 				/* only if sane */
1264   else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) {
1265 				/* make sure each range starts with token */
1266     for (set = &burp->set; set; set = set->next)
1267       if (fseek (f,set->first,SEEK_SET) ||
1268 	  (fread (LOCAL->buf,1,MSGTSZ,f) != MSGTSZ) ||
1269 	  strncmp (LOCAL->buf,MSGTOK,MSGTSZ)) {
1270 	sprintf (LOCAL->buf,"Bad message token in mix message file at %lu",
1271 		 set->first);
1272 	MM_LOG (LOCAL->buf,ERROR);
1273 	fclose (f);
1274 	return NIL;		/* burp fails for this file */
1275       }
1276 				/* burp out each old message */
1277     for (set = &burp->set, rpos = wpos = 0; set; set = set->next) {
1278 				/* move down this range */
1279       for (rpos = set->first, size = set->last - set->first;
1280 	   size; size -= wsize) {
1281 	if (rpos != wpos) {	/* data to skip at start? */
1282 				/* no, slide this buffer down */
1283 	  wsize = min (size,LOCAL->buflen);
1284 				/* failure is not an option here */
1285 	  while (fseek (f,rpos,SEEK_SET) ||
1286 		 (fread (LOCAL->buf,1,wsize,f) != wsize)) {
1287 	    MM_NOTIFY (stream,strerror (errno),WARN);
1288 	    MM_DISKERROR (stream,errno,T);
1289 	  }
1290 				/* nor here */
1291 	  while (fseek (f,wpos,SEEK_SET)) {
1292 	    MM_NOTIFY (stream,strerror (errno),WARN);
1293 	    MM_DISKERROR (stream,errno,T);
1294 	  }
1295 				/* and especially not here */
1296 	  for (s = LOCAL->buf, wpending = wsize; wpending; s += written, wpending -= written)
1297 	    if (!(written = fwrite (s,1,wpending,f))) {
1298 	      MM_NOTIFY (stream,strerror (errno),WARN);
1299 	      MM_DISKERROR (stream,errno,T);
1300 	    }
1301 	}
1302 	else wsize = size;	/* nothing to skip, say we wrote it all */
1303 	rpos += wsize; wpos += wsize;
1304       }
1305     }
1306 
1307     while (fflush (f)) {	/* failure also not an option here... */
1308       MM_NOTIFY (stream,strerror (errno),WARN);
1309       MM_DISKERROR (stream,errno,T);
1310     }
1311     if (ftruncate (fd,wpos)) {	/* flush cruft at end of file */
1312       sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno));
1313       MM_LOG (LOCAL->buf,WARN);
1314     }
1315     else *reclaimed += rpos - wpos;
1316     ret = !fclose (f);		/* close file */
1317 				/* slide down message positions in index */
1318     for (i = 1,rpos = 0; i <= stream->nmsgs; ++i)
1319       if ((elt = mail_elt (stream,i))->private.spare.data == burp->fileno) {
1320 	elt->private.special.offset = rpos;
1321 	rpos += elt->private.msg.header.offset + elt->rfc822_size;
1322       }
1323 				/* debugging */
1324     if (rpos != wpos) fatal ("burp size consistency check!");
1325   }
1326   return ret;
1327 }
1328 
1329 
1330 /* MIX burp sanity check to make sure not burping off end of file
1331  * Accepts: burp set
1332  *	    file size
1333  *	    file name
1334  * Returns: T if sane, NIL if insane
1335  */
1336 
mix_burp_check(SEARCHSET * set,size_t size,char * file)1337 long mix_burp_check (SEARCHSET *set,size_t size,char *file)
1338 {
1339   do if (set->last > size) {	/* sanity check */
1340     char tmp[MAILTMPLEN];
1341     sprintf (tmp,"Unexpected short mix message file %.80s %lu < %lu",
1342 	     file,size,set->last);
1343     MM_LOG (tmp,ERROR);
1344     return NIL;			/* don't burp this file at all */
1345   } while ((set = set->next) != NULL);
1346   return LONGT;
1347 }
1348 
1349 /* MIX mail copy message(s)
1350  * Accepts: MAIL stream
1351  *	    sequence
1352  *	    destination mailbox
1353  *	    copy options
1354  * Returns: T if copy successful, else NIL
1355  */
1356 
mix_copy(MAILSTREAM * stream,char * sequence,char * mailbox,long options)1357 long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
1358 {
1359   FDDATA d;
1360   STRING st;
1361   char tmp[2*MAILTMPLEN];
1362   long ret = mix_isvalid (mailbox,LOCAL->buf);
1363   mailproxycopy_t pc =
1364     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
1365   MAILSTREAM *astream = NIL;
1366   FILE *idxf = NIL;
1367   FILE *msgf = NIL;
1368   FILE *statf = NIL;
1369   if (!ret) switch (errno) {	/* make sure valid mailbox */
1370   case NIL:			/* no error in stat() */
1371     if (pc) return (*pc) (stream,sequence,mailbox,options);
1372     sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox);
1373     MM_LOG (tmp,ERROR);
1374     break;
1375   default:			/* some stat() error */
1376     MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
1377     break;
1378   }
1379 				/* get sequence to copy */
1380   else if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
1381 		    mail_sequence (stream,sequence))));
1382 				/* acquire stream to append */
1383   else if ((ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) &&
1384 		  !astream->rdonly &&
1385 		  (((MIXLOCAL *) astream->local)->expok = T) &&
1386 		  (statf = mix_parse (astream,&idxf,LONGT,NIL))) ?
1387 	   LONGT : NIL) != 0L) {
1388     int fd;
1389     unsigned long i;
1390     MESSAGECACHE *elt;
1391     unsigned long newsize,hdrsize,size;
1392     MIXLOCAL *local = (MIXLOCAL *) astream->local;
1393     unsigned long seq = mix_modseq (local->metaseq);
1394 				/* make sure new modseq fits */
1395     if (local->indexseq > seq) seq = local->indexseq + 1;
1396     if (local->statusseq > seq) seq = local->statusseq + 1;
1397 				/* calculate size of per-message header */
1398     sprintf (local->buf,MSRFMT,MSGTOK,(unsigned long) 0,0,0,0,0,0,0,'+',0,0,
1399 	     (unsigned long) 0);
1400     hdrsize = strlen (local->buf);
1401 
1402     MM_CRITICAL (stream);	/* go critical */
1403     astream->silent = T;	/* no events here */
1404 				/* calculate size that will be added */
1405     for (i = 1, newsize = 0; i <= stream->nmsgs; ++i)
1406       if ((elt = mail_elt (stream,i))->sequence)
1407 	newsize += hdrsize + elt->rfc822_size;
1408 				/* open data file */
1409     if ((msgf = mix_data_open (astream,&fd,&size,newsize)) != NULL) {
1410       char *t;
1411       unsigned long j,uid,uidv;
1412       copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
1413       SEARCHSET *source = cu ? mail_newsearchset () : NIL;
1414       SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
1415       for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); ++i)
1416 	if (((elt = mail_elt (stream,i))->sequence) && elt->rfc822_size) {
1417 				/* is message in current message file? */
1418 	  if ((LOCAL->msgfd < 0) ||
1419 	      (elt->private.spare.data != LOCAL->curmsg)) {
1420 	    if (LOCAL->msgfd >= 0) close (LOCAL->msgfd);
1421 	    if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,
1422 						     stream->mailbox,
1423 						     elt->private.spare.data),
1424 				      O_RDONLY,NIL)) >= 0)
1425 	      LOCAL->curmsg = elt->private.spare.data;
1426 	  }
1427 	  if (LOCAL->msgfd < 0) ret = NIL;
1428 	  else {		/* got file */
1429 	    d.fd = LOCAL->msgfd;/* set up file descriptor */
1430 				/* start of message */
1431 	    d.pos = elt->private.special.offset +
1432 	      elt->private.msg.header.offset;
1433 	    d.chunk = LOCAL->buf;
1434 	    d.chunksize = CHUNKSIZE;
1435 	    INIT (&st,fd_string,&d,elt->rfc822_size);
1436 				/* init flag string */
1437 	    tmp[0] = tmp[1] = '\0';
1438 	    if ((j = elt->user_flags) != 0L) do
1439 	      if ((t = stream->user_flags[find_rightmost_bit (&j)]) && *t)
1440 		strcat (strcat (tmp," "),t);
1441 	    while (j);
1442 	    if (elt->seen) strcat (tmp," \\Seen");
1443 	    if (elt->deleted) strcat (tmp," \\Deleted");
1444 	    if (elt->flagged) strcat (tmp," \\Flagged");
1445 	    if (elt->answered) strcat (tmp," \\Answered");
1446 	    if (elt->draft) strcat (tmp," \\Draft");
1447 	    tmp[0] = '(';	/* wrap list */
1448 	    strcat (tmp,")");
1449 				/* if append OK, add to source set */
1450 	    if ((ret = mix_append_msg (astream,msgf,tmp,elt,&st,dest,
1451 				       seq)) &&	source)
1452 	      mail_append_set (source,mail_uid (stream,i));
1453 	  }
1454 	}
1455 
1456 				/* finish write if success */
1457       if (ret && (ret = !fflush (msgf))) {
1458 	fclose (msgf);		/* all good, close the msg file now */
1459 				/* write new metadata, index, and status */
1460 	local->metaseq = local->indexseq = local->statusseq = seq;
1461 	if ((ret = (mix_meta_update (astream) &&
1462 		   mix_index_update (astream,idxf,LONGT))) != 0L){
1463 				/* success, delete if doing a move */
1464 	  if (options & CP_MOVE)
1465 	    for (i = 1; i <= stream->nmsgs; i++)
1466 	      if ((elt = mail_elt (stream,i))->sequence) {
1467 		elt->deleted = T;
1468 		if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq;
1469 		MM_FLAGS (stream,elt->msgno);
1470 	      }
1471 				/* done with status file now */
1472 	  mix_status_update (astream,statf,LONGT);
1473 				/* return sets if doing COPYUID */
1474 	  if (cu) (*cu) (stream,mailbox,astream->uid_validity,source,dest);
1475 	  source = dest = NIL;	/* don't free these sets now */
1476 	}
1477       }
1478       else {			/* error */
1479 	if (errno) {		/* output error message if system call error */
1480 	  sprintf (tmp,"Message copy failed: %.80s",strerror (errno));
1481 	  MM_LOG (tmp,ERROR);
1482 	}
1483 	ftruncate (fd,size);	/* revert file */
1484 	close (fd);		/* make sure that fclose doesn't corrupt us */
1485 	fclose (msgf);		/* free the stdio resources */
1486       }
1487 				/* flush any sets remaining */
1488       mail_free_searchset (&source);
1489       mail_free_searchset (&dest);
1490     }
1491     else {			/* message file open failed */
1492       sprintf (tmp,"Error opening copy message file: %.80s",
1493 	       strerror (errno));
1494       MM_LOG (tmp,ERROR);
1495       ret = NIL;
1496     }
1497     MM_NOCRITICAL (stream);
1498   }
1499   else MM_LOG ("Can't open copy mailbox",ERROR);
1500   if (statf) fclose (statf);	/* close status if still open */
1501   if (idxf) fclose (idxf);	/* close index if still open */
1502 				/* finished with append stream */
1503   if (astream) mail_close (astream);
1504   return ret;			/* return state */
1505 }
1506 
1507 /* MIX mail append message from stringstruct
1508  * Accepts: MAIL stream
1509  *	    destination mailbox
1510  *	    append callback
1511  *	    data for callback
1512  * Returns: T if append successful, else NIL
1513  */
1514 
mix_append(MAILSTREAM * stream,char * mailbox,append_t af,void * data)1515 long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
1516 {
1517   STRING *message;
1518   char *flags,*date,tmp[MAILTMPLEN];
1519 				/* N.B.: can't use LOCAL->buf for tmp */
1520   long ret = mix_isvalid (mailbox,tmp);
1521 				/* default stream to prototype */
1522   if (!stream) stream = user_flags (&mixproto);
1523   if (!ret) switch (errno) {	/* if not valid mailbox */
1524   case ENOENT:			/* no such file? */
1525     if ((ret = compare_cstring (mailbox,"INBOX") ?
1526 	NIL : mix_create (NIL,"INBOX")) != 0L)
1527       break;
1528     MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL);
1529     break;
1530   default:
1531     sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox);
1532     MM_LOG (tmp,ERROR);
1533     break;
1534   }
1535 
1536 				/* get first message */
1537   if (ret && MM_APPEND (af) (stream,data,&flags,&date,&message)) {
1538     MAILSTREAM *astream;
1539     FILE *idxf = NIL;
1540     FILE *msgf = NIL;
1541     FILE *statf = NIL;
1542     if ((ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) &&
1543 	       !astream->rdonly &&
1544 	       (((MIXLOCAL *) astream->local)->expok = T) &&
1545 	       (statf = mix_parse (astream,&idxf,LONGT,NIL))) ?
1546 	LONGT : NIL) != 0l) {
1547       int fd;
1548       unsigned long size,hdrsize;
1549       MESSAGECACHE elt;
1550       MIXLOCAL *local = (MIXLOCAL *) astream->local;
1551       unsigned long seq = mix_modseq (local->metaseq);
1552 				/* make sure new modseq fits */
1553       if (local->indexseq > seq) seq = local->indexseq + 1;
1554       if (local->statusseq > seq) seq = local->statusseq + 1;
1555 				/* calculate size of per-message header */
1556       sprintf (local->buf,MSRFMT,MSGTOK,(unsigned long) 0,0,0,0,0,0,0,'+',0,0,
1557 	       (unsigned long) 0);
1558       hdrsize = strlen (local->buf);
1559       MM_CRITICAL (astream);	/* go critical */
1560       astream->silent = T;	/* no events here */
1561 				/* open data file */
1562       if ((msgf = mix_data_open (astream,&fd,&size,hdrsize + SIZE (message))) != NULL){
1563 	appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
1564 	SEARCHSET *dst = au ? mail_newsearchset () : NIL;
1565 	while (ret && message) {/* while good to go and have messages */
1566 	  errno = NIL;		/* in case one of these causes failure */
1567 				/* guard against zero-length */
1568 	  if (!(ret = SIZE (message)))
1569 	    MM_LOG ("Append of zero-length message",ERROR);
1570 	  else if (date && !(ret = mail_parse_date (&elt,date))) {
1571 	    sprintf (tmp,"Bad date in append: %.80s",date);
1572 	    MM_LOG (tmp,ERROR);
1573 	  }
1574 	  else {
1575 	    if (!date) {	/* if date not specified, use now */
1576 	      internal_date (tmp);
1577 	      mail_parse_date (&elt,tmp);
1578 	    }
1579 	    ret = mix_append_msg (astream,msgf,flags,&elt,message,dst,seq) &&
1580 	      MM_APPEND (af) (stream,data,&flags,&date,&message);
1581 	  }
1582 	}
1583 
1584 				/* finish write if success */
1585 	if (ret && (ret = !fflush (msgf))) {
1586 	  fclose (msgf);	/* all good, close the msg file now */
1587 				/* write new metadata, index, and status */
1588 	  local->metaseq = local->indexseq = local->statusseq = seq;
1589 	  if ((ret = (mix_meta_update (astream) &&
1590 		      mix_index_update (astream,idxf,LONGT) &&
1591 		      mix_status_update (astream,statf,LONGT))) && au) {
1592 	      (*au) (mailbox,astream->uid_validity,dst);
1593 	      dst = NIL;	/* don't free this set now */
1594 	  }
1595 	}
1596 	else {			/* failure */
1597 	  if (errno) {		/* output error message if system call error */
1598 	    sprintf (tmp,"Message append failed: %.80s",strerror (errno));
1599 	    MM_LOG (tmp,ERROR);
1600 	  }
1601 	  ftruncate (fd,size);	/* revert all writes to file*/
1602 	  close (fd);		/* make sure that fclose doesn't corrupt us */
1603 	  fclose (msgf);	/* free the stdio resources */
1604 	}
1605 				/* flush any set remaining */
1606 	mail_free_searchset (&dst);
1607       }
1608       else {			/* message file open failed */
1609 	sprintf (tmp,"Error opening append message file: %.80s",
1610 		 strerror (errno));
1611 	MM_LOG (tmp,ERROR);
1612 	ret = NIL;
1613       }
1614       MM_NOCRITICAL (astream);	/* release critical */
1615     }
1616     else MM_LOG ("Can't open append mailbox",ERROR);
1617     if (statf) fclose (statf);	/* close status if still open */
1618     if (idxf) fclose (idxf);	/* close index if still open */
1619     if (astream) mail_close (astream);
1620   }
1621   return ret;
1622 }
1623 
1624 /* MIX mail append single message
1625  * Accepts: MAIL stream
1626  *	    flags for new message if non-NIL
1627  *	    elt with source date if non-NIL
1628  *	    stringstruct of message text
1629  *	    searchset to place UID
1630  *	    modseq of message
1631  * Returns: T if success, NIL if failure
1632  */
1633 
mix_append_msg(MAILSTREAM * stream,FILE * f,char * flags,MESSAGECACHE * delt,STRING * msg,SEARCHSET * set,unsigned long seq)1634 long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt,
1635 		     STRING *msg,SEARCHSET *set,unsigned long seq)
1636 {
1637   MESSAGECACHE *elt;
1638   int c,cs;
1639   unsigned long i,j,k,uf,hoff;
1640   long sf;
1641   void *s;
1642   stream->kwd_create = NIL;	/* don't copy unknown keywords */
1643   sf = mail_parse_flags (stream,flags,&uf);
1644 				/* swell the cache */
1645   mail_exists (stream,++stream->nmsgs);
1646 				/* assign new UID from metadata */
1647   (elt = mail_elt (stream,stream->nmsgs))->private.uid = ++stream->uid_last;
1648   elt->private.mod = seq;	/* set requested modseq in status */
1649   elt->rfc822_size = SIZE (msg);/* copy message size and date to index */
1650   elt->year = delt->year; elt->month = delt->month; elt->day = delt->day;
1651   elt->hours = delt->hours; elt->minutes = delt->minutes;
1652   elt->seconds = delt->seconds; elt->zoccident = delt->zoccident;
1653   elt->zhours = delt->zhours; elt->zminutes = delt->zminutes;
1654   /*
1655    * Do NOT set elt->valid here!  mix_status_update() uses it to determine
1656    * whether a message should be marked as old.
1657    */
1658   if (sf&fSEEN) elt->seen = T;	/* copy flags to status */
1659   if (sf&fDELETED) elt->deleted = T;
1660   if (sf&fFLAGGED) elt->flagged = T;
1661   if (sf&fANSWERED) elt->answered = T;
1662   if (sf&fDRAFT) elt->draft = T;
1663   elt->user_flags |= uf;
1664 				/* message is in new message file */
1665   elt->private.spare.data = LOCAL->newmsg;
1666 
1667 				/* offset to message internal header */
1668   elt->private.special.offset = ftell (f);
1669 				/* build header for message */
1670   fprintf (f,MSRFMT,MSGTOK,elt->private.uid,
1671 	   elt->year + BASEYEAR,elt->month,elt->day,
1672 	   elt->hours,elt->minutes,elt->seconds,
1673 	   elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes,
1674 	   elt->rfc822_size);
1675 				/* offset to header from  internal header */
1676   elt->private.msg.header.offset = ftell (f) - elt->private.special.offset;
1677   for (cs = 0; SIZE (msg); ) {	/* copy message */
1678     if (elt->private.msg.header.text.size) {
1679       if (msg->cursize)		/* blat entire chunk if have it */
1680 	for (s = msg->curpos,j = msg->cursize; j; s += k, j -= k)
1681 	  if (!(k = fwrite (s,1,j,f))) return NIL;
1682       SETPOS (msg,GETPOS (msg) + msg->cursize);
1683     }
1684     else {			/* still searching for delimiter */
1685       c = 0xff & SNX (msg);	/* get source character */
1686       if (putc (c,f) == EOF) return NIL;
1687       switch (cs) {		/* decide what to do based on state */
1688       case 0:			/* previous char ordinary */
1689 	if (c == '\015') cs = 1;/* advance if CR */
1690 	break;
1691       case 1:			/* previous CR, advance if LF */
1692 	cs = (c == '\012') ? 2 : 0;
1693 	break;
1694       case 2:			/* previous CRLF, advance if CR */
1695 	cs = (c == '\015') ? 3 : 0;
1696 	break;
1697       case 3:			/* previous CRLFCR, done if LF */
1698 	if (c == '\012') elt->private.msg.header.text.size =
1699 			   elt->rfc822_size - SIZE (msg);
1700 	cs = 0;			/* reset mechanism */
1701 	break;
1702       }
1703     }
1704   }
1705 				/* if no delimiter, header is entire msg */
1706   if (!elt->private.msg.header.text.size)
1707     elt->private.msg.header.text.size = elt->rfc822_size;
1708 				/* add this message to set */
1709   mail_append_set (set,elt->private.uid);
1710   return LONGT;			/* success */
1711 }
1712 
1713 /* MIX mail read metadata, index, and status
1714  * Accepts: MAIL stream
1715  *	    returned index file
1716  *	    index file flags (non-NIL if want to add/remove messages)
1717  *	    status file flags (non-NIL if want to update elt->valid and old)
1718  * Returns: open status file, or NIL if failure
1719  *
1720  * Note that this routine can return an open index file even if it fails!
1721  */
1722 
1723 static char *shortmsg =
1724   "message %lu (UID=%.08lx) truncated by %lu byte(s) (%lu < %lu)";
1725 
mix_parse(MAILSTREAM * stream,FILE ** idxf,long iflags,long sflags)1726 FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags)
1727 {
1728   int fd;
1729   unsigned long i;
1730   char *s,*t;
1731   struct stat sbuf;
1732   FILE *statf = NIL;
1733   short metarepairneeded = 0;
1734   short indexrepairneeded = 0;
1735   short silent = stream->silent;
1736   *idxf = NIL;			/* in case error */
1737 				/* readonly means no updates */
1738   if (stream->rdonly) iflags = sflags = NIL;
1739 				/* open index file */
1740   if ((fd = open (LOCAL->index,iflags ? O_RDWR : O_RDONLY,NIL)) < 0)
1741     MM_LOG ("Error opening mix index file",ERROR);
1742 				/* acquire exclusive access and FILE */
1743   else if (!flock (fd,iflags ? LOCK_EX : LOCK_SH) &&
1744 	   !(*idxf = fdopen (fd,iflags ? "r+b" : "rb"))) {
1745     MM_LOG ("Error obtaining stream on mix index file",ERROR);
1746     flock (fd,LOCK_UN);		/* relinquish lock */
1747     close (fd);
1748   }
1749 
1750 				/* slurp metadata */
1751   else if ((s = mix_meta_slurp (stream,&i)) != NULL) {
1752     unsigned long j = 0;	/* non-zero if UIDVALIDITY/UIDLAST changed */
1753     if (i != LOCAL->metaseq) {	/* metadata changed? */
1754       char *t,*k;
1755       LOCAL->metaseq = i;	/* note new metadata sequence */
1756       while (s && *s) {		/* parse entire metadata file */
1757 				/* locate end of line */
1758 	if ((s = strstr (t = s,"\015\012")) != NULL) {
1759 	  *s = '\0';		/* tie off line */
1760 	  s += 2;		/* skip past CRLF */
1761 	  switch (*t++) {	/* parse line */
1762 	  case 'V':		/* UIDVALIDITY */
1763 	    if (!isxdigit (*t) || !(i = strtoul (t,&t,16))) {
1764 	      MM_LOG ("Error in mix metadata file UIDVALIDITY record",ERROR);
1765 	      return NIL;	/* give up */
1766 	    }
1767 	    if (i != stream->uid_validity) j = stream->uid_validity = i;
1768 	    break;
1769 	  case 'L':		/* new UIDLAST */
1770 	    if (!isxdigit (*t)) {
1771 	      MM_LOG ("Error in mix metadata file UIDLAST record",ERROR);
1772 	      return NIL;	/* give up */
1773 	    }
1774 	    if ((i = strtoul (t,&t,16)) != stream->uid_last)
1775 	      j = stream->uid_last = i;
1776 	    break;
1777 	  case 'N':		/* new message file */
1778 	    if (!isxdigit (*t)) {
1779 	      MM_LOG ("Error in mix metadata file new msg record",ERROR);
1780 	      return NIL;	/* give up */
1781 	    }
1782 	    if ((i = strtoul (t,&t,16)) != stream->uid_last)
1783 	      LOCAL->newmsg = i;
1784 	    break;
1785 	  case 'K':		/* new keyword list */
1786 	    for (i = 0; t && *t && (i < NUSERFLAGS); ++i) {
1787 	      if ((t = strchr (k = t,' ')) != NULL) *t++ = '\0';
1788 				/* make sure keyword non-empty */
1789 	      if (*k && (strlen (k) <= MAXUSERFLAG)) {
1790 				/* in case value changes (shouldn't happen) */
1791 		if (stream->user_flags[i] && strcmp (stream->user_flags[i],k)){
1792 		  char tmp[MAILTMPLEN];
1793 		  sprintf (tmp,"flag rename old=%.80s new=%.80s",
1794 			   stream->user_flags[i],k);
1795 		  MM_LOG (tmp,WARN);
1796 		  fs_give ((void **) &stream->user_flags[i]);
1797 		}
1798 		if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (k);
1799 	      }
1800 	      else break;	/* empty keyword */
1801 	    }
1802 	    if ((i < NUSERFLAGS) && stream->user_flags[i]) {
1803 	      MM_LOG ("Error in mix metadata file keyword record",ERROR);
1804 	      return NIL;	/* give up */
1805 	    }
1806 	    else if (i == NUSERFLAGS) stream->kwd_create = NIL;
1807 	    break;
1808 	  }
1809 	}
1810 	if (t && *t) {		/* junk in line */
1811 	  MM_LOG ("Error in mix metadata record",ERROR);
1812 	  return NIL;		/* give up */
1813 	}
1814       }
1815     }
1816 
1817 				/* get sequence */
1818     if (!(i = mix_read_sequence (*idxf)) || (i < LOCAL->indexseq)) {
1819       MM_LOG ("Error in mix index file sequence record",ERROR);
1820       return NIL;		/* give up */
1821     }
1822 				/* sequence changed from last time? */
1823     else if (j || (i > LOCAL->indexseq)) {
1824       unsigned long prevuid = 0;
1825       unsigned long uid,nmsgs,curfile,curfilesize,curpos;
1826       char *t,*msg,tmp[MAILTMPLEN];
1827 				/* start with no messages */
1828       curfile = curfilesize = curpos = nmsgs = 0;
1829 				/* update sequence iff expunging OK */
1830       if (LOCAL->expok) LOCAL->indexseq = i;
1831 				/* get first elt */
1832       while ((s = mix_read_record (*idxf,LOCAL->buf,LOCAL->buflen,"index")) &&
1833 	     *s)
1834 	switch (*s) {
1835 	case ':':		/* message record */
1836 	  if (!(isxdigit (*++s) && (uid = strtoul (s,&t,16)))) msg = "UID";
1837 	  else if (!((*t++ == ':') && isdigit (*t) && isdigit (t[1]) &&
1838 		     isdigit (t[2]) && isdigit (t[3]) && isdigit (t[4]) &&
1839 		     isdigit (t[5]) && isdigit (t[6]) && isdigit (t[7]) &&
1840 		     isdigit (t[8]) && isdigit (t[9]) && isdigit (t[10]) &&
1841 		     isdigit (t[11]) && isdigit (t[12]) && isdigit (t[13]) &&
1842 		     ((t[14] == '+') || (t[14] == '-')) &&
1843 		     isdigit (t[15]) && isdigit (t[16]) && isdigit (t[17]) &&
1844 		     isdigit (t[18]))) msg = "internaldate";
1845 	  else if ((*(s = t+19) != ':') || !isxdigit (*++s)) msg = "size";
1846 	  else {
1847 	    unsigned int y = (((*t - '0') * 1000) + ((t[1] - '0') * 100) +
1848 			      ((t[2] - '0') * 10) + t[3] - '0') - BASEYEAR;
1849 	    unsigned int m = ((t[4] - '0') * 10) + t[5] - '0';
1850 	    unsigned int d = ((t[6] - '0') * 10) + t[7] - '0';
1851 	    unsigned int hh = ((t[8] - '0') * 10) + t[9] - '0';
1852 	    unsigned int mm = ((t[10] - '0') * 10) + t[11] - '0';
1853 	    unsigned int ss = ((t[12] - '0') * 10) + t[13] - '0';
1854 	    unsigned int z = (t[14] == '-') ? 1 : 0;
1855 	    unsigned int zh = ((t[15] - '0') * 10) + t[16] - '0';
1856 	    unsigned int zm = ((t[17] - '0') * 10) + t[18] - '0';
1857 	    unsigned long size = strtoul (s,&s,16);
1858 	    if ((*s++ == ':') && isxdigit (*s)) {
1859 	      unsigned long file = strtoul (s,&s,16);
1860 	      if ((*s++ == ':') && isxdigit (*s)) {
1861 		unsigned long pos = strtoul (s,&s,16);
1862 		if ((*s++ == ':') && isxdigit (*s)) {
1863 		  unsigned long hpos = strtoul (s,&s,16);
1864 		  if ((*s++ == ':') && isxdigit (*s)) {
1865 		    unsigned long hsiz = strtoul (s,&s,16);
1866 		    if (uid > stream->uid_last) {
1867 		      sprintf (tmp,"mix index invalid UID (%08lx < %08lx)",
1868 			       uid,stream->uid_last);
1869 		      if (stream->rdonly) {
1870 			MM_LOG (tmp,ERROR);
1871 			return NIL;
1872 		      }
1873 		      strcat (tmp,", repaired");
1874 		      MM_LOG (tmp,WARN);
1875 		      stream->uid_last = uid;
1876 		      metarepairneeded = T;
1877 		    }
1878 
1879 				/* ignore expansion values */
1880 		    if (*s++ == ':') {
1881 		      MESSAGECACHE *elt;
1882 		      if(prevuid > uid) {
1883 			sprintf (tmp,"mix index backwards UID: %lx",uid);
1884 			MM_LOG (tmp,ERROR);
1885 			return NIL;
1886 		      }
1887 		      prevuid = uid;
1888 		      ++nmsgs;	/* this is another message */
1889 				/* within current known range of messages? */
1890 		      while (nmsgs <= stream->nmsgs) {
1891 				/* yes, get corresponding elt */
1892 			elt = mail_elt (stream,nmsgs);
1893 				/* existing message with matching data? */
1894 			if (uid == elt->private.uid) {
1895 				/* beware of Dracula's resurrection */
1896 			  if (elt->private.ghost) {
1897 			    sprintf (tmp,"mix index data unexpunged UID: %lx",
1898 				     uid);
1899 			    MM_LOG (tmp,ERROR);
1900 			    return NIL;
1901 			  }
1902 				/* also of static data changing */
1903 			  if ((size != elt->rfc822_size) ||
1904 			      (file != elt->private.spare.data) ||
1905 			      (pos != elt->private.special.offset) ||
1906 			      (hpos != elt->private.msg.header.offset) ||
1907 			      (hsiz != elt->private.msg.header.text.size) ||
1908 			      (y != elt->year) || (m != elt->month) ||
1909 			      (d != elt->day) || (hh != elt->hours) ||
1910 			      (mm != elt->minutes) || (ss != elt->seconds) ||
1911 			      (z != elt->zoccident) || (zh != elt->zhours) ||
1912 			      (zm != elt->zminutes)) {
1913 			    sprintf (tmp,"mix index data mismatch: %lx",uid);
1914 			    MM_LOG (tmp,ERROR);
1915 			    return NIL;
1916 			  }
1917 			  break;
1918 			}
1919 				/* existing msg with lower UID is expunged */
1920 			else if (uid > elt->private.uid) {
1921 			  if (LOCAL->expok) mail_expunged (stream,nmsgs);
1922 			  else {/* message expunged, but not yet for us */
1923 			    ++nmsgs;
1924 			    elt->private.ghost = T;
1925 			  }
1926 			}
1927 			else {	/* unexpected message record */
1928 			  sprintf (tmp,"mix index UID mismatch (%lx < %lx)",
1929 				   uid,elt->private.uid);
1930 			  MM_LOG (tmp,ERROR);
1931 			  return NIL;
1932 			}
1933 		      }
1934 
1935 				/* time to create a new message? */
1936 		      if (nmsgs > stream->nmsgs) {
1937 				/* defer announcing until later */
1938 			stream->silent = T;
1939 			mail_exists (stream,nmsgs);
1940 			stream->silent = silent;
1941 			(elt = mail_elt (stream,nmsgs))->recent = T;
1942 			elt->private.uid = uid; elt->rfc822_size = size;
1943 			elt->private.spare.data = file;
1944 			elt->private.special.offset = pos;
1945 			elt->private.msg.header.offset = hpos;
1946 			elt->private.msg.header.text.size = hsiz;
1947 			elt->year = y; elt->month = m; elt->day = d;
1948 			elt->hours = hh; elt->minutes = mm;
1949 			elt->seconds = ss; elt->zoccident = z;
1950 			elt->zhours = zh; elt->zminutes = zm;
1951 				/* message in same file? */
1952 			if (curfile == file) {
1953 			  if (pos < curpos) {
1954 			    MESSAGECACHE *plt = mail_elt (stream,elt->msgno-1);
1955 				/* uh-oh, calculate delta? */
1956 			    i = curpos - pos;
1957 			    sprintf (tmp,shortmsg,plt->msgno,plt->private.uid,
1958 				     i,pos,curpos);
1959 				/* possible to fix? */
1960 			    if (!stream->rdonly && LOCAL->expok &&
1961 				(i < plt->rfc822_size)) {
1962 			      plt->rfc822_size -= i;
1963 			      if (plt->rfc822_size <
1964 				  plt->private.msg.header.text.size)
1965 				plt->private.msg.header.text.size =
1966 				  plt->rfc822_size;
1967 			      strcat (tmp,", repaired");
1968 			      indexrepairneeded = T;
1969 			    }
1970 			    MM_LOG (tmp,WARN);
1971 			  }
1972 			}
1973 			else {	/* new file, restart */
1974 			  if (stat (mix_file_data (LOCAL->buf,stream->mailbox,
1975 						   curfile = file),&sbuf)) {
1976 			    sprintf (tmp,"Missing mix data file: %.500s",
1977 				     LOCAL->buf);
1978 			    MM_LOG (tmp,ERROR);
1979 			    return NIL;
1980 			  }
1981 			  curfile = file;
1982 			  curfilesize = sbuf.st_size;
1983 			}
1984 
1985 				/* position of message in file */
1986 			curpos = pos + elt->private.msg.header.offset +
1987 			  elt->rfc822_size;
1988 				/* short file? */
1989 			if (curfilesize < curpos) {
1990 				/* uh-oh, calculate delta? */
1991 			    i = curpos - curfilesize;
1992 			    sprintf (tmp,shortmsg,elt->msgno,elt->private.uid,
1993 				     i,curfilesize,curpos);
1994 				/* possible to fix? */
1995 			    if (!stream->rdonly && LOCAL->expok &&
1996 				(i < elt->rfc822_size)) {
1997 			      elt->rfc822_size -= i;
1998 			      if (elt->rfc822_size <
1999 				  elt->private.msg.header.text.size)
2000 				elt->private.msg.header.text.size =
2001 				  elt->rfc822_size;
2002 			      strcat (tmp,", repaired");
2003 			      indexrepairneeded = T;
2004 			    }
2005 			    MM_LOG (tmp,WARN);
2006 			}
2007 		      }
2008 		      break;
2009 		    }
2010 		    else msg = "expansion";
2011 		  }
2012 		  else msg = "header size";
2013 		}
2014 		else msg = "header position";
2015 	      }
2016 	      else msg = "message position";
2017 	    }
2018 	    else msg = "file#";
2019 	  }
2020 	  sprintf (tmp,"Error in %s in mix index file: %.500s",msg,s);
2021 	  MM_LOG (tmp,ERROR);
2022 	  return NIL;
2023 	default:
2024 	  sprintf (tmp,"Unknown record in mix index file: %.500s",s);
2025 	  MM_LOG (tmp,ERROR);
2026 	  return NIL;
2027 	}
2028       if (!s) return NIL;	/* barfage from mix_read_record() */
2029 				/* expunge trailing messages not in index */
2030       if (LOCAL->expok) while (nmsgs < stream->nmsgs)
2031 	mail_expunged (stream,stream->nmsgs);
2032     }
2033 
2034 				/* repair metadata and index if needed */
2035     if ((metarepairneeded ? mix_meta_update (stream) : T) &&
2036 	(indexrepairneeded ? mix_index_update (stream,*idxf,NIL) : T)) {
2037       MESSAGECACHE *elt;
2038       int fd;
2039       unsigned long uid,uf,sf,mod;
2040       char *s;
2041       int updatep = NIL;
2042 				/* open status file */
2043       if ((fd = open (LOCAL->status,
2044 		      stream->rdonly ? O_RDONLY : O_RDWR,NIL)) < 0)
2045 	MM_LOG ("Error opening mix status file",ERROR);
2046 				/* acquire exclusive access and FILE */
2047       else if (!flock (fd,stream->rdonly ? LOCK_SH : LOCK_EX) &&
2048 	       !(statf = fdopen (fd,stream->rdonly ? "rb" : "r+b"))) {
2049 	MM_LOG ("Error obtaining stream on mix status file",ERROR);
2050 	flock (fd,LOCK_UN);	/* relinquish lock */
2051 	close (fd);
2052       }
2053 				/* get sequence */
2054       else if (!(i = mix_read_sequence (statf)) ||
2055 	       ((i < LOCAL->statusseq) && stream->nmsgs && (i != 1))) {
2056 	sprintf (LOCAL->buf,
2057 		 "Error in mix status sequence record, i=%lx, seq=%lx",
2058 		 i,LOCAL->statusseq);
2059 	MM_LOG (LOCAL->buf,ERROR);
2060       }
2061 				/* sequence changed from last time? */
2062       else if (i != LOCAL->statusseq) {
2063 				/* update sequence, get first elt */
2064 	if (i > LOCAL->statusseq) LOCAL->statusseq = i;
2065 	if (stream->nmsgs) {
2066 	  elt = mail_elt (stream,i = 1);
2067 
2068 				/* read message records */
2069 	  while ((t = s = mix_read_record (statf,LOCAL->buf,LOCAL->buflen,
2070 					   "status")) && *s && (*s++ == ':') &&
2071 		 isxdigit (*s)) {
2072 	    uid = strtoul (s,&s,16);
2073 	    if ((*s++ == ':') && isxdigit (*s)) {
2074 	      uf = strtoul (s,&s,16);
2075 	      if ((*s++ == ':') && isxdigit (*s)) {
2076 		sf = strtoul (s,&s,16);
2077 		if ((*s++ == ':') && isxdigit (*s)) {
2078 		  mod = strtoul (s,&s,16);
2079 				/* ignore expansion values */
2080 		  if (*s++ == ':') {
2081 				/* need to move ahead to next elt? */
2082 		    while ((uid > elt->private.uid) && (i < stream->nmsgs))
2083 		      elt = mail_elt (stream,++i);
2084 				/* update elt if altered */
2085 		    if ((uid == elt->private.uid) &&
2086 			(!elt->valid || (mod != elt->private.mod))) {
2087 		      elt->user_flags = uf;
2088 		      elt->private.mod = mod;
2089 		      elt->seen = (sf & fSEEN) ? T : NIL;
2090 		      elt->deleted = (sf & fDELETED) ? T : NIL;
2091 		      elt->flagged = (sf & fFLAGGED) ? T : NIL;
2092 		      elt->answered = (sf & fANSWERED) ? T : NIL;
2093 		      elt->draft = (sf & fDRAFT) ? T : NIL;
2094 				/* announce if altered existing message */
2095 		      if (elt->valid) MM_FLAGS (stream,elt->msgno);
2096 				/* first time, is old message? */
2097 		      else if (sf & fOLD) {
2098 				/* yes, clear recent and set valid */
2099 			elt->recent = NIL;
2100 			elt->valid = T;
2101 		      }
2102 				/* recent, allowed to update its status? */
2103 		      else if (sflags) {
2104 				/* yes, set valid and check in status */
2105 			elt->valid = T;
2106 			elt->private.mod = mix_modseq (elt->private.mod);
2107 			updatep = T;
2108 		      }
2109 		      /* leave valid unset and recent if sflags not set */
2110 		    }
2111 		    continue;	/* everything looks good */
2112 		  }
2113 		}
2114 	      }
2115 	    }
2116 	    break;		/* error somewhere */
2117 	  }
2118 
2119 	  if (t && *t) {	/* non-null means bogus record */
2120 	    char msg[MAILTMPLEN];
2121 	    sprintf (msg,"Error in mix status file message record%s: %.80s",
2122 		     stream->rdonly ? "" : ", fixing",t);
2123 	    MM_LOG (msg,WARN);
2124 				/* update it if not readonly */
2125 	    if (!stream->rdonly) updatep = T;
2126 	  }
2127 	  if (updatep) {		/* need to update? */
2128 	    LOCAL->statusseq = mix_modseq (LOCAL->statusseq);
2129 	    mix_status_update (stream,statf,LONGT);
2130 	  }
2131 	}
2132       }
2133     }
2134   }
2135   if (statf) {			/* still happy? */
2136     unsigned long j;
2137     stream->silent = silent;	/* now notify upper level */
2138     mail_exists (stream,stream->nmsgs);
2139     for (i = 1, j = 0; i <= stream->nmsgs; ++i)
2140       if (mail_elt (stream,i)->recent) ++j;
2141     mail_recent (stream,j);
2142   }
2143   return statf;
2144 }
2145 
2146 /* MIX metadata file routines */
2147 
2148 /* MIX read metadata
2149  * Accepts: MAIL stream
2150  *	    return pointer for modseq
2151  * Returns: pointer to metadata after modseq or NIL if failure
2152  */
2153 
mix_meta_slurp(MAILSTREAM * stream,unsigned long * seq)2154 char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq)
2155 {
2156   struct stat sbuf;
2157   char *s;
2158   char *ret = NIL;
2159   if (fstat (LOCAL->mfd,&sbuf))
2160     MM_LOG ("Error obtaining size of mix metadata file",ERROR);
2161   if (sbuf.st_size > LOCAL->buflen) {
2162 				/* should be just a few dozen bytes */
2163     if (sbuf.st_size > METAMAX) fatal ("absurd mix metadata file size");
2164     fs_give ((void **) &LOCAL->buf);
2165     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size) + 1);
2166   }
2167 				/* read current metadata file */
2168   LOCAL->buf[sbuf.st_size] = '\0';
2169   if (lseek (LOCAL->mfd,0,L_SET) ||
2170       (read (LOCAL->mfd,s = LOCAL->buf,sbuf.st_size) != sbuf.st_size))
2171     MM_LOG ("Error reading mix metadata file",ERROR);
2172   else if ((*s != 'S') || !isxdigit (s[1]) ||
2173 	   ((*seq = strtoul (s+1,&s,16)) < LOCAL->metaseq) ||
2174 	   (*s++ != '\015') || (*s++ != '\012'))
2175     MM_LOG ("Error in mix metadata file sequence record",ERROR);
2176   else ret = s;
2177   return ret;
2178 }
2179 
2180 /* MIX update metadata
2181  * Accepts: MAIL stream
2182  * Returns: T on success, NIL if error
2183  *
2184  * Index MUST be locked!!
2185  */
2186 
mix_meta_update(MAILSTREAM * stream)2187 long mix_meta_update (MAILSTREAM *stream)
2188 {
2189   long ret;
2190 				/* do nothing if stream readonly */
2191   if (stream->rdonly) ret = LONGT;
2192   else {
2193     unsigned char c,*s,*ss,*t;
2194     unsigned long i;
2195     /* The worst-case metadata is limited to:
2196      *    4 * (1 + 8 + 2) + (NUSERFLAGS * (MAXUSERFLAG + 1))
2197      * which comes out to 1994 octets.  This is much smaller than the normal
2198      * CHUNKSIZE definition of 64K, and CHUNKSIZE is the smallest size of
2199      * LOCAL->buf.
2200      *
2201      * If more stuff gets added to the metadata, or if you change the value
2202      * of NUSERFLAGS, MAXUSERFLAG or CHUNKSIZE, be sure to recalculate the
2203      * above assertion.
2204      */
2205     sprintf (LOCAL->buf,SEQFMT,LOCAL->metaseq = mix_modseq (LOCAL->metaseq));
2206     sprintf (LOCAL->buf + strlen (LOCAL->buf),MTAFMT,
2207 	     stream->uid_validity,stream->uid_last,LOCAL->newmsg);
2208     for (i = 0, c = 'K', s = ss = LOCAL->buf + strlen (LOCAL->buf);
2209 	 (i < NUSERFLAGS) && (t = stream->user_flags[i]); ++i) {
2210       if (!*t) fatal ("impossible empty keyword");
2211       *s++ = c;			/* write delimiter */
2212       while (*t) *s++ = *t++;	/* write keyword */
2213       c = ' ';			/* delimiter is now space */
2214     }
2215     if (s != ss) {		/* tie off keywords line */
2216       *s++ = '\015'; *s++ = '\012';
2217     }
2218 				/* calculate length of metadata */
2219     if ((i = s - LOCAL->buf) > LOCAL->buflen)
2220       fatal ("impossible buffer overflow");
2221     lseek (LOCAL->mfd,0,L_SET);	/* rewind file */
2222 				/* write new metadata */
2223     ret = (write (LOCAL->mfd,LOCAL->buf,i) == i) ? LONGT : NIL;
2224     ftruncate (LOCAL->mfd,i);	/* and tie off at that point */
2225   }
2226   return ret;
2227 }
2228 
2229 /* MIX index file routines */
2230 
2231 
2232 /* MIX update index
2233  * Accepts: MAIL stream
2234  *	    open FILE
2235  *	    expansion check flag
2236  * Returns: T on success, NIL if error
2237  */
2238 
mix_index_update(MAILSTREAM * stream,FILE * idxf,long flag)2239 long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag)
2240 {
2241   unsigned long i;
2242   long ret = LONGT;
2243   if (!stream->rdonly) {	/* do nothing if stream readonly */
2244     if (flag) {			/* need to do expansion check? */
2245       char tmp[MAILTMPLEN];
2246       size_t size;
2247       struct stat sbuf;
2248 				/* calculate file size we need */
2249       for (i = 1, size = 0; i <= stream->nmsgs; ++i)
2250 	if (!mail_elt (stream,i)->private.ghost) ++size;
2251       if (size) {		/* Winston Smith's first dairy entry */
2252 	sprintf (tmp,IXRFMT,(unsigned long) 0,14,4,4,13,0,0,'+',0,0,
2253 		 (unsigned long) 0,(unsigned long) 0,(unsigned long) 0,
2254 		 (unsigned long) 0,(unsigned long) 0);
2255 	size *= strlen (tmp);
2256       }
2257 				/* calculate file size we need */
2258       sprintf (tmp,SEQFMT,LOCAL->indexseq);
2259       size += strlen (tmp);
2260 				/* get current file size */
2261       if (fstat (fileno (idxf),&sbuf)) {
2262 	MM_LOG ("Error getting size of mix index file",ERROR);
2263 	ret = NIL;
2264       }
2265 				/* need to write additional space? */
2266       else if (sbuf.st_size < size) {
2267 	void *buf = fs_get (size -= sbuf.st_size);
2268 	memset (buf,0,size);
2269 	if (fseek (idxf,0,SEEK_END) || (fwrite (buf,1,size,idxf) != size) ||
2270 	    fflush (idxf)) {
2271 	  fseek (idxf,sbuf.st_size,SEEK_SET);
2272 	  ftruncate (fileno (idxf),sbuf.st_size);
2273 	  MM_LOG ("Error extending mix index file",ERROR);
2274 	  ret = NIL;
2275 	}
2276 	fs_give ((void **) &buf);
2277       }
2278     }
2279 
2280     if (ret) {			/* if still good to go */
2281       rewind (idxf);		/* let's start at the very beginning */
2282 				/* write modseq first */
2283       fprintf (idxf,SEQFMT,LOCAL->indexseq);
2284 				/* then write all messages */
2285       for (i = 1; ret && (i <= stream->nmsgs); i++) {
2286 	MESSAGECACHE *elt = mail_elt (stream,i);
2287 	if (!elt->private.ghost)/* only write living messages */
2288 	  fprintf (idxf,IXRFMT,elt->private.uid,
2289 		   elt->year + BASEYEAR,elt->month,elt->day,
2290 		   elt->hours,elt->minutes,elt->seconds,
2291 		   elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes,
2292 		   elt->rfc822_size,elt->private.spare.data,
2293 		   elt->private.special.offset,
2294 		   elt->private.msg.header.offset,
2295 		   elt->private.msg.header.text.size);
2296 	if (ferror (idxf)) {
2297 	  MM_LOG ("Error updating mix index file",ERROR);
2298 	  ret = NIL;
2299 	}
2300       }
2301       if (fflush (idxf)) {
2302 	MM_LOG ("Error flushing mix index file",ERROR);
2303 	ret = NIL;
2304       }
2305       if (ret) ftruncate (fileno (idxf),ftell (idxf));
2306     }
2307   }
2308   return ret;
2309 }
2310 
2311 /* MIX status file routines */
2312 
2313 
2314 /* MIX update status
2315  * Accepts: MAIL stream
2316  *	    pointer to open FILE
2317  *	    expansion check flag
2318  * Returns: T on success, NIL if error
2319  */
2320 
mix_status_update(MAILSTREAM * stream,FILE * statf,long flag)2321 long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag)
2322 {
2323   unsigned long i;
2324   char tmp[MAILTMPLEN];
2325   long ret = LONGT;
2326   if (!stream->rdonly) {	/* do nothing if stream readonly */
2327     if (flag) {			/* need to do expansion check? */
2328       char tmp[MAILTMPLEN];
2329       size_t size;
2330       struct stat sbuf;
2331 				/* calculate file size we need */
2332       for (i = 1, size = 0; i <= stream->nmsgs; ++i)
2333 	if (!mail_elt (stream,i)->private.ghost) ++size;
2334       if (size) {		/* number of living messages */
2335 	sprintf (tmp,STRFMT,(unsigned long) 0,(unsigned long) 0,0,
2336 		 (unsigned long) 0);
2337 	size *= strlen (tmp);
2338       }
2339       sprintf (tmp,SEQFMT,LOCAL->statusseq);
2340       size += strlen (tmp);
2341 				/* get current file size */
2342       if (fstat (fileno (statf),&sbuf)) {
2343 	MM_LOG ("Error getting size of mix status file",ERROR);
2344 	ret = NIL;
2345       }
2346 				/* need to write additional space? */
2347       else if (sbuf.st_size < size) {
2348 	void *buf = fs_get (size -= sbuf.st_size);
2349 	memset (buf,0,size);
2350 	if (fseek (statf,0,SEEK_END) || (fwrite (buf,1,size,statf) != size) ||
2351 	    fflush (statf)) {
2352 	  fseek (statf,sbuf.st_size,SEEK_SET);
2353 	  ftruncate (fileno (statf),sbuf.st_size);
2354 	  MM_LOG ("Error extending mix status file",ERROR);
2355 	  ret = NIL;
2356 	}
2357 	fs_give ((void **) &buf);
2358       }
2359     }
2360 
2361     if (ret) {			/* if still good to go */
2362       rewind (statf);		/* let's start at the very beginning */
2363 				/* write sequence */
2364       fprintf (statf,SEQFMT,LOCAL->statusseq);
2365 				/* write message status records */
2366       for (i = 1; ret && (i <= stream->nmsgs); ++i) {
2367 	MESSAGECACHE *elt = mail_elt (stream,i);
2368 				/* make sure all messages have a modseq */
2369 	if (!elt->private.mod) elt->private.mod = LOCAL->statusseq;
2370 	if (!elt->private.ghost)/* only write living messages */
2371 	  fprintf (statf,STRFMT,elt->private.uid,elt->user_flags,
2372 		   (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
2373 		   (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
2374 		   (fDRAFT * elt->draft) + (elt->valid ? fOLD : NIL),
2375 		   elt->private.mod);
2376 	if (ferror (statf)) {
2377 	  sprintf (tmp,"Error updating mix status file: %.80s",
2378 		   strerror (errno));
2379 	  MM_LOG (tmp,ERROR);
2380 	  ret = NIL;
2381 	}
2382       }
2383       if (ret && fflush (statf)) {
2384 	MM_LOG ("Error flushing mix status file",ERROR);
2385 	ret = NIL;
2386       }
2387       if (ret) ftruncate (fileno (statf),ftell (statf));
2388     }
2389   }
2390   return ret;
2391 }
2392 
2393 /* MIX data file routines */
2394 
2395 
2396 /* MIX open data file
2397  * Accepts: MAIL stream
2398  *	    pointer to returned fd if success
2399  *	    pointer to returned size if success
2400  *	    size of new data to be added
2401  * Returns: open FILE, or NIL if failure
2402  *
2403  * The curend test assumes that the last message of the mailbox is the furthest
2404  * point that the current data file extends, and thus that is all that needs to
2405  * be tested for short file prevention.
2406  */
2407 
mix_data_open(MAILSTREAM * stream,int * fd,long * size,unsigned long newsize)2408 FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size,
2409 		     unsigned long newsize)
2410 {
2411   FILE *msgf = NIL;
2412   struct stat sbuf;
2413   MESSAGECACHE *elt = stream->nmsgs ? mail_elt (stream,stream->nmsgs) : NIL;
2414   unsigned long curend = (elt && (elt->private.spare.data == LOCAL->newmsg)) ?
2415     elt->private.special.offset + elt->private.msg.header.offset +
2416     elt->rfc822_size : 0;
2417 				/* allow create if curend 0 */
2418   if ((*fd = open (mix_file_data (LOCAL->buf,stream->mailbox,LOCAL->newmsg),
2419 		   O_RDWR | (curend ? NIL : O_CREAT),NIL)) >= 0) {
2420     fstat (*fd,&sbuf);		/* get current file size */
2421 				/* can we use this file? */
2422     if ((curend <= sbuf.st_size) &&
2423 	(!sbuf.st_size || ((sbuf.st_size + newsize) <= MIXDATAROLL)))
2424       *size = sbuf.st_size;	/* yes, return current size */
2425     else {			/* short file or becoming too long */
2426       if (curend > sbuf.st_size) {
2427 	char tmp[MAILTMPLEN];
2428 	sprintf (tmp,"short mix message file %.08lx (%ld > %ld), rolling",
2429 		 LOCAL->newmsg,curend,(unsigned long) sbuf.st_size);
2430 	MM_LOG (tmp,WARN);	/* shouldn't happen */
2431       }
2432       close (*fd);		/* roll to a new file */
2433       errno = NIL;
2434       while ((*fd = open (mix_file_data
2435 			  (LOCAL->buf,stream->mailbox,
2436 			   LOCAL->newmsg = mix_modseq (LOCAL->newmsg)),
2437 			  O_RDWR | O_CREAT | O_EXCL,sbuf.st_mode)) < 0) {
2438 	switch(errno) {
2439 	case EEXIST:		/* always retry if path exists or interrupt */
2440 	case EINTR:
2441 	  errno = NIL;
2442 	  break;
2443 	default:		/* probably EDQUOT */
2444 	  {
2445 	    char tmp[MAILTMPLEN];
2446 	    sprintf (tmp,"data file %.08lx creation failure: %.80s",
2447 		     LOCAL->newmsg,strerror (errno));
2448 	    MM_LOG (tmp,ERROR);	/* shouldn't happen */
2449 	    return NIL;
2450 	  }
2451 	}
2452       }
2453       *size = 0;		/* brand new file */
2454       fchmod (*fd,sbuf.st_mode);/* with same mode as previous file */
2455     }
2456   }
2457   if (*fd >= 0) {		/* have a data file? */
2458 				/* yes, get stdio and set position */
2459     if ((msgf = fdopen (*fd,"r+b")) != NULL)fseek (msgf,*size,SEEK_SET);
2460     else close (*fd);		/* fdopen() failed? */
2461   }
2462   return msgf;			/* return results */
2463 }
2464 
2465 /* MIX open sortcache
2466  * Accepts: MAIL stream
2467  * Returns: open FILE, or NIL if failure or could only get readonly sortcache
2468  */
2469 
mix_sortcache_open(MAILSTREAM * stream)2470 FILE *mix_sortcache_open (MAILSTREAM *stream)
2471 {
2472   int fd,refwd;
2473   unsigned long i,uid,sentdate,fromlen,tolen,cclen,subjlen,msgidlen,reflen;
2474   char *s,*t,*msg,tmp[MAILTMPLEN];
2475   MESSAGECACHE *elt;
2476   SORTCACHE *sc;
2477   STRINGLIST *sl;
2478   struct stat sbuf;
2479   int rdonly = NIL;
2480   FILE *srtcf = NIL;
2481   mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
2482   fstat (LOCAL->mfd,&sbuf);
2483   if (!stream->nmsgs);		/* do nothing if mailbox empty */
2484 				/* open sortcache file */
2485   else if (((fd = open (LOCAL->sortcache,O_RDWR|O_CREAT,sbuf.st_mode)) < 0) &&
2486 	   !(rdonly = ((fd = open (LOCAL->sortcache,O_RDONLY,NIL)) >= 0)))
2487     MM_LOG ("Error opening mix sortcache file",WARN);
2488 				/* acquire lock and FILE */
2489   else if (!flock (fd,rdonly ? LOCK_SH : LOCK_EX) &&
2490 	   !(srtcf = fdopen (fd,rdonly ? "rb" : "r+b"))) {
2491     MM_LOG ("Error obtaining stream on mix sortcache file",WARN);
2492     flock (fd,LOCK_UN);		/* relinquish lock */
2493     close (fd);
2494   }
2495   else if (!(i = mix_read_sequence (srtcf)) || (i < LOCAL->sortcacheseq))
2496     MM_LOG ("Error in mix sortcache file sequence record",WARN);
2497 				/* sequence changed from last time? */
2498   else if (i > LOCAL->sortcacheseq) {
2499     LOCAL->sortcacheseq = i;	/* update sequence */
2500     while ((s = t = mix_read_record (srtcf,LOCAL->buf,LOCAL->buflen,
2501 				     "sortcache")) && *s &&
2502 	   (msg = "uid") && (*s++ == ':') && isxdigit (*s)) {
2503       uid = strtoul (s,&s,16);
2504       if ((*s++ == ':') && isxdigit (*s)) {
2505 	sentdate = strtoul (s,&s,16);
2506 	if ((*s++ == ':') && isxdigit (*s)) {
2507 	  fromlen = strtoul (s,&s,16);
2508 	  if ((*s++ == ':') && isxdigit (*s)) {
2509 	    tolen = strtoul (s,&s,16);
2510 	    if ((*s++ == ':') && isxdigit (*s)) {
2511 	      cclen = strtoul (s,&s,16);
2512 	      if ((*s++ == ':') && ((*s == 'R') || (*s == ' ')) &&
2513 		  isxdigit (s[1])) {
2514 		refwd = (*s++ == 'R') ? T : NIL;
2515 		subjlen = strtoul (s,&s,16);
2516 		if ((*s++ == ':') && isxdigit (*s)) {
2517 		  msgidlen = strtoul (s,&s,16);
2518 		  if ((*s++ == ':') && isxdigit (*s)) {
2519 		    reflen = strtoul (s,&s,16);
2520 				/* ignore expansion values */
2521 		    if (*s++ == ':') {
2522 
2523 		      if ((i = mail_msgno (stream,uid)) != 0L) {
2524 			sc = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE);
2525 			sc->size = (elt = mail_elt (stream,i))->rfc822_size;
2526 			sc->date = sentdate;
2527 			sc->arrival = elt->day ? mail_longdate (elt) : 1;
2528 			if (refwd) sc->refwd = T;
2529 			if (fromlen) {
2530 			  if (sc->from) fseek (srtcf,fromlen + 2,SEEK_CUR);
2531 			  else if ((getc (srtcf) != 'F') ||
2532 				   (fread (sc->from = (char *) fs_get(fromlen),
2533 					   1,fromlen-1,srtcf) != (fromlen-1))||
2534 				   (sc->from[fromlen-1] = '\0') ||
2535 				   (getc (srtcf) != '\015') ||
2536 				   (getc (srtcf) != '\012')) {
2537 			    msg = "from data";
2538 			    break;
2539 			  }
2540 			}
2541 			if (tolen) {
2542 			  if (sc->to) fseek (srtcf,tolen + 2,SEEK_CUR);
2543 			  else if ((getc (srtcf) != 'T') ||
2544 				   (fread (sc->to = (char *) fs_get (tolen),
2545 					   1,tolen-1,srtcf) != (tolen - 1)) ||
2546 				   (sc->to[tolen-1] = '\0') ||
2547 				   (getc (srtcf) != '\015') ||
2548 				   (getc (srtcf) != '\012')) {
2549 			    msg = "to data";
2550 			    break;
2551 			  }
2552 			}
2553 			if (cclen) {
2554 			  if (sc->cc) fseek (srtcf,cclen + 2,SEEK_CUR);
2555 			  else if ((getc (srtcf) != 'C') ||
2556 				   (fread (sc->cc = (char *) fs_get (cclen),
2557 					   1,cclen-1,srtcf) != (cclen - 1)) ||
2558 				   (sc->cc[cclen-1] = '\0') ||
2559 				   (getc (srtcf) != '\015') ||
2560 				   (getc (srtcf) != '\012')) {
2561 			    msg = "cc data";
2562 			    break;
2563 			  }
2564 			}
2565 			if (subjlen) {
2566 			  if (sc->subject) fseek (srtcf,subjlen + 2,SEEK_CUR);
2567 			  else if ((getc (srtcf) != 'S') ||
2568 				     (fread (sc->subject =
2569 					     (char *) fs_get (subjlen),1,
2570 					     subjlen-1,srtcf) != (subjlen-1))||
2571 				   (sc->subject[subjlen-1] = '\0') ||
2572 				   (getc (srtcf) != '\015') ||
2573 				   (getc (srtcf) != '\012')) {
2574 			    msg = "subject data";
2575 			    break;
2576 			  }
2577 			}
2578 
2579 			if (msgidlen) {
2580 			  if (sc->message_id)
2581 			    fseek (srtcf,msgidlen + 2,SEEK_CUR);
2582 			  else if ((getc (srtcf) != 'M') ||
2583 				   (fread (sc->message_id =
2584 					   (char *) fs_get (msgidlen),1,
2585 					   msgidlen-1,srtcf) != (msgidlen-1))||
2586 				   (sc->message_id[msgidlen-1] = '\0') ||
2587 				   (getc (srtcf) != '\015') ||
2588 				   (getc (srtcf) != '\012')) {
2589 			    msg = "message-id data";
2590 			    break;
2591 			  }
2592 			}
2593 			if (reflen) {
2594 			  if (sc->references) fseek(srtcf,reflen + 2,SEEK_CUR);
2595 				/* make sure it fits */
2596 			  else {
2597 			    if (reflen >= LOCAL->buflen) {
2598 			      fs_give ((void **) &LOCAL->buf);
2599 			      LOCAL->buf = (char *)
2600 				fs_get ((LOCAL->buflen = reflen) + 1);
2601 			      }
2602 			    if ((getc (srtcf) != 'R') ||
2603 				(fread (LOCAL->buf,1,reflen-1,srtcf) !=
2604 				 (reflen - 1)) ||
2605 				(LOCAL->buf[reflen-1] = '\0') ||
2606 				(getc (srtcf) != '\015') ||
2607 				(getc (srtcf) != '\012')) {
2608 			      msg = "references data";
2609 			      break;
2610 			    }
2611 			    for (s = LOCAL->buf,sl = NIL,
2612 				   sc->references = mail_newstringlist ();
2613 				 s && *s; s += i + 1) {
2614 			      if ((i = strtoul (s,&s,16)) && (*s++ == ':') &&
2615 				  (s[i] == ':')) {
2616 				if (sl) sl = sl->next = mail_newstringlist();
2617 				else sl = sc->references;
2618 				s[i] = '\0';
2619 				sl->text.data = cpystr (s);
2620 				sl->text.size = i;
2621 			      }
2622 			      else s = NIL;
2623 			    }
2624 			    if (!s || *s ||
2625 				(s != ((char *) LOCAL->buf + reflen - 1))) {
2626 			      msg = "references length consistency check";
2627 			      break;
2628 			    }
2629 			  }
2630 			}
2631 		      }
2632 
2633 				/* UID not found, ignore this message */
2634 		      else fseek (srtcf,((fromlen ? fromlen + 2 : 0) +
2635 					 (tolen ? tolen + 2 : 0) +
2636 					 (cclen ? cclen + 2 : 0) +
2637 					 (subjlen ? subjlen + 2 : 0) +
2638 					 (msgidlen ? msgidlen + 2 : 0) +
2639 					 (reflen ? reflen + 2 : 0)),
2640 				  SEEK_CUR);
2641 		      continue;
2642 		    }
2643 		    else msg = "expansion";
2644 		  }
2645 		  else msg = "references";
2646 		}
2647 		else msg = "message-id";
2648 	      }
2649 	      else msg = "subject";
2650 	    }
2651 	    else msg = "cc";
2652 	  }
2653 	  else msg = "to";
2654 	}
2655 	else msg = "from";
2656       }
2657       else msg = "sentdate";
2658       break;			/* error somewhere */
2659     }
2660     if (!t || *t) {		/* error detected? */
2661       if (t) {			/* non-null means bogus record */
2662 	sprintf (tmp,"Error in %s in mix sortcache record: %.500s",msg,t);
2663 	MM_LOG (tmp,WARN);
2664       }
2665       fclose (srtcf);		/* either way, must punt */
2666       srtcf = NIL;
2667     }
2668   }
2669   if (rdonly && srtcf) {	/* can't update if readonly */
2670     unlink (LOCAL->sortcache);	/* try deleting it */
2671     fclose (srtcf);		/* so close it and return as if error */
2672     srtcf = NIL;
2673   }
2674   else fchmod (fd,sbuf.st_mode);
2675   return srtcf;
2676 }
2677 
2678 /* MIX update and close sortcache
2679  * Accepts: MAIL stream
2680  *	    pointer to open FILE (if FILE is NIL, do nothing)
2681  * Returns: T on success, NIL on error
2682  */
2683 
mix_sortcache_update(MAILSTREAM * stream,FILE ** sortcache)2684 long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache)
2685 {
2686   FILE *f = *sortcache;
2687   long ret = LONGT;
2688   if (f) {			/* ignore if no file */
2689     unsigned long i,j;
2690     mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
2691     for (i = 1; (i <= stream->nmsgs) &&
2692 	   !((SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE))->dirty; ++i);
2693     if (i <= stream->nmsgs) {	/* only update if some entry is dirty */
2694       rewind (f);		/* let's start at the very beginning */
2695 				/* write sequence */
2696       fprintf (f,SEQFMT,LOCAL->sortcacheseq = mix_modseq(LOCAL->sortcacheseq));
2697       for (i = 1; ret && (i <= stream->nmsgs); ++i) {
2698 	MESSAGECACHE *elt = mail_elt (stream,i);
2699 	SORTCACHE *s = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE);
2700 	STRINGLIST *sl;
2701 	s->dirty = NIL;		/* no longer dirty */
2702 	if ((sl = s->references) != NULL)	/* count length of references */
2703 	  for (j = 1; sl && sl->text.data; sl = sl->next)
2704 	    j += 10 + sl->text.size;
2705 	else j = 0;		/* no references yet */
2706 	fprintf (f,SCRFMT,elt->private.uid,s->date,
2707 		 s->from ? strlen (s->from) + 1 : 0,
2708 		 s->to ? strlen (s->to) + 1 : 0,s->cc ? strlen (s->cc) + 1 : 0,
2709 		 s->refwd ? 'R' : ' ',s->subject ? strlen (s->subject) + 1: 0,
2710 		 s->message_id ? strlen (s->message_id) + 1 : 0,j);
2711 	if (s->from) fprintf (f,"F%s\015\012",s->from);
2712 	if (s->to) fprintf (f,"T%s\015\012",s->to);
2713 	if (s->cc) fprintf (f,"C%s\015\012",s->cc);
2714 	if (s->subject) fprintf (f,"S%s\015\012",s->subject);
2715 	if (s->message_id) fprintf (f,"M%s\015\012",s->message_id);
2716 	if (j) {		/* any references to write? */
2717 	  fputc ('R',f);	/* yes, do so */
2718 	  for (sl = s->references; sl && sl->text.data; sl = sl->next)
2719 	    fprintf (f,"%08lx:%s:",sl->text.size,sl->text.data);
2720 	  fputs ("\015\012",f);
2721 	}
2722 	if (ferror (f)) {
2723 	  MM_LOG ("Error updating mix sortcache file",WARN);
2724 	  ret = NIL;
2725 	}
2726       }
2727       if (ret && fflush (f)) {
2728 	MM_LOG ("Error flushing mix sortcache file",WARN);
2729 	ret = NIL;
2730       }
2731       if (ret) ftruncate (fileno (f),ftell (f));
2732     }
2733     if (fclose (f)) {
2734       MM_LOG ("Error closing mix sortcache file",WARN);
2735       ret = NIL;
2736     }
2737   }
2738   return ret;
2739 }
2740 
2741 /* MIX generic file routines */
2742 
2743 /* MIX read record
2744  * Accepts: open FILE
2745  *	    buffer
2746  *	    buffer length
2747  *	    record type
2748  * Returns: buffer if success, else NIL (zero-length buffer means EOF)
2749  */
2750 
mix_read_record(FILE * f,char * buf,unsigned long buflen,char * type)2751 char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type)
2752 {
2753   char *s,tmp[MAILTMPLEN];
2754 				/* ensure string tied off */
2755   buf[buflen-2] = buf[buflen-1] = '\0';
2756   while (fgets (buf,buflen-1,f)) {
2757     if ((s = strchr (buf,'\012')) != NULL) {
2758       if ((s != buf) && (s[-1] == '\015')) --s;
2759       *s = '\0';		/* tie off buffer */
2760       if (s != buf) return buf;	/* return if non-empty buffer */
2761       sprintf (tmp,"Empty mix %s record",type);
2762       MM_LOG (tmp,WARN);
2763     }
2764     else if (buf[buflen-2]) {	/* overlong record is bad news */
2765       sprintf (tmp,"Oversize mix %s record: %.512s",type,buf);
2766       MM_LOG (tmp,ERROR);
2767       return NIL;
2768     }
2769     else {
2770       sprintf (tmp,"Truncated mix %s record: %.512s",type,buf);
2771       MM_LOG (tmp,WARN);
2772       return buf;		/* pass to caller anyway */
2773     }
2774   }
2775   buf[0] = '\0';		/* return empty buffer on EOF */
2776   return buf;
2777 }
2778 
2779 /* MIX read sequence record
2780  * Accepts: open FILE
2781  * Returns: sequence value, or NIL if failure
2782  */
2783 
mix_read_sequence(FILE * f)2784 unsigned long mix_read_sequence (FILE *f)
2785 {
2786   unsigned long ret;
2787   char *s,tmp[MAILTMPLEN];
2788   if (!mix_read_record (f,tmp,MAILTMPLEN-1,"sequence")) return NIL;
2789   switch (tmp[0]) {		/* examine record */
2790   case '\0':			/* end of file */
2791     ret = 1;			/* start a new sequence regime */
2792     break;
2793   case 'S':			/* sequence record */
2794     if (isxdigit (tmp[1])) {	/* must be followed by hex value */
2795       ret = strtoul (tmp+1,&s,16);
2796       if (!*s) break;		/* and nothing more */
2797     }
2798 				/* drop into default case */
2799   default:			/* anything else is an error */
2800     return NIL;			/* return error */
2801   }
2802   return ret;
2803 }
2804 
2805 /* MIX internal routines */
2806 
2807 
2808 /* MIX mail build directory name
2809  * Accepts: destination string
2810  *          source
2811  * Returns: destination or empty string if error
2812  */
2813 
mix_dir(char * dst,char * name)2814 char *mix_dir (char *dst,char *name)
2815 {
2816   char *s;
2817 				/* empty string if mailboxfile fails */
2818   if (!mailboxfile (dst,name)) *dst = '\0';
2819 				/* driver-selected INBOX  */
2820   else if (!*dst) mailboxfile (dst,"~/INBOX");
2821 				/* tie off unnecessary trailing / */
2822   else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0';
2823   return dst;
2824 }
2825 
2826 
2827 /* MIX mail build file name
2828  * Accepts: destination string
2829  *	    directory name
2830  *	    file name
2831  * Returns: destination
2832  */
2833 
mix_file(char * dst,char * dir,char * name)2834 char *mix_file (char *dst,char *dir,char *name)
2835 {
2836   sprintf (dst,"%.500s/%.80s%.80s",dir,MIXNAME,name);
2837   return dst;
2838 }
2839 
2840 
2841 /* MIX mail build file name from data file number
2842  * Accepts: destination string
2843  *	    directory name
2844  *	    data file number
2845  * Returns: destination
2846  */
2847 
mix_file_data(char * dst,char * dir,unsigned long data)2848 char *mix_file_data (char *dst,char *dir,unsigned long data)
2849 {
2850   char tmp[MAILTMPLEN];
2851   if (data) sprintf (tmp,"%08lx",data);
2852   else tmp[0] = '\0';		/* compatibility with experimental version */
2853   return mix_file (dst,dir,tmp);
2854 }
2855 
2856 /* MIX mail get new modseq
2857  * Accepts: old modseq
2858  * Returns: new modseq value
2859  */
2860 
mix_modseq(unsigned long oldseq)2861 unsigned long mix_modseq (unsigned long oldseq)
2862 {
2863 				/* normally time now */
2864   unsigned long ret = (unsigned long) time (NIL);
2865 				/* ensure that modseq doesn't go backwards */
2866   if (ret <= oldseq) ret = oldseq + 1;
2867   return ret;
2868 }
2869