1 /* ========================================================================
2  * Copyright 1988-2007 University of Washington
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *
11  * ========================================================================
12  */
13 
14 /*
15  * Program:	MTX mail routines
16  *
17  * Author:	Mark Crispin
18  *		Networks and Distributed Computing
19  *		Computing & Communications
20  *		University of Washington
21  *		Administration Building, AG-44
22  *		Seattle, WA  98195
23  *		Internet: MRC@CAC.Washington.EDU
24  *
25  * Date:	22 May 1990
26  * Last Edited:	15 June 2007
27  */
28 
29 
30 /*				FILE TIME SEMANTICS
31  *
32  * The atime is the last read time of the file.
33  * The mtime is the last flags update time of the file.
34  * The ctime is the last write time of the file.
35  */
36 
37 #include <stdio.h>
38 #include <ctype.h>
39 #include <errno.h>
40 extern int errno;		/* just in case */
41 #include "mail.h"
42 #include "osdep.h"
43 #include <fcntl.h>
44 #include <time.h>
45 #include <sys/stat.h>
46 #include <sys/utime.h>
47 #include "misc.h"
48 #include "dummy.h"
49 #include "fdstring.h"
50 
51 /* MTX I/O stream local data */
52 
53 typedef struct mtx_local {
54   unsigned int shouldcheck: 1;	/* if ping should do a check instead */
55   unsigned int mustcheck: 1;	/* if ping must do a check instead */
56   int fd;			/* file descriptor for I/O */
57   off_t filesize;		/* file size parsed */
58   time_t filetime;		/* last file time */
59   time_t lastsnarf;		/* last snarf time */
60   unsigned char *buf;		/* temporary buffer */
61   unsigned long buflen;		/* current size of temporary buffer */
62 } MTXLOCAL;
63 
64 
65 /* Convenient access to local data */
66 
67 #define LOCAL ((MTXLOCAL *) stream->local)
68 
69 
70 /* Function prototypes */
71 
72 DRIVER *mtx_valid (char *name);
73 int mtx_isvalid (char *name,char *file);
74 void *mtx_parameters (long function,void *value);
75 void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
76 void mtx_list (MAILSTREAM *stream,char *ref,char *pat);
77 void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat);
78 long mtx_create (MAILSTREAM *stream,char *mailbox);
79 long mtx_delete (MAILSTREAM *stream,char *mailbox);
80 long mtx_rename (MAILSTREAM *stream,char *old,char *newname);
81 long mtx_status (MAILSTREAM *stream,char *mbx,long flags);
82 MAILSTREAM *mtx_open (MAILSTREAM *stream);
83 void mtx_close (MAILSTREAM *stream,long options);
84 void mtx_flags (MAILSTREAM *stream,char *sequence,long flags);
85 char *mtx_header (MAILSTREAM *stream,unsigned long msgno,
86 		  unsigned long *length,long flags);
87 long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
88 void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
89 void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
90 long mtx_ping (MAILSTREAM *stream);
91 void mtx_check (MAILSTREAM *stream);
92 void mtx_snarf (MAILSTREAM *stream);
93 long mtx_expunge (MAILSTREAM *stream,char *sequence,long options);
94 long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
95 long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
96 
97 long mtx_parse (MAILSTREAM *stream);
98 MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno);
99 void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
100 void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag);
101 unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
102 			  unsigned long *size);
103 
104 
105 /* MTX mail routines */
106 
107 
108 /* Driver dispatch used by MAIL */
109 
110 DRIVER mtxdriver = {
111   "mtx",			/* driver name */
112 				/* driver flags */
113   DR_LOCAL|DR_MAIL|DR_CRLF|DR_NOSTICKY,
114   (DRIVER *) NIL,		/* next driver */
115   mtx_valid,			/* mailbox is valid for us */
116   mtx_parameters,		/* manipulate parameters */
117   mtx_scan,			/* scan mailboxes */
118   mtx_list,			/* list mailboxes */
119   mtx_lsub,			/* list subscribed mailboxes */
120   NIL,				/* subscribe to mailbox */
121   NIL,				/* unsubscribe from mailbox */
122   mtx_create,			/* create mailbox */
123   mtx_delete,			/* delete mailbox */
124   mtx_rename,			/* rename mailbox */
125   mail_status_default,		/* status of mailbox */
126   mtx_open,			/* open mailbox */
127   mtx_close,			/* close mailbox */
128   mtx_flags,			/* fetch message "fast" attributes */
129   mtx_flags,			/* fetch message flags */
130   NIL,				/* fetch overview */
131   NIL,				/* fetch message envelopes */
132   mtx_header,			/* fetch message header */
133   mtx_text,			/* fetch message body */
134   NIL,				/* fetch partial message text */
135   NIL,				/* unique identifier */
136   NIL,				/* message number */
137   mtx_flag,			/* modify flags */
138   mtx_flagmsg,			/* per-message modify flags */
139   NIL,				/* search for message based on criteria */
140   NIL,				/* sort messages */
141   NIL,				/* thread messages */
142   mtx_ping,			/* ping mailbox to see if still alive */
143   mtx_check,			/* check for new messages */
144   mtx_expunge,			/* expunge deleted messages */
145   mtx_copy,			/* copy messages to another mailbox */
146   mtx_append,			/* append string message to mailbox */
147   NIL				/* garbage collect stream */
148 };
149 
150 				/* prototype stream */
151 MAILSTREAM mtxproto = {&mtxdriver};
152 
153 /* MTX mail validate mailbox
154  * Accepts: mailbox name
155  * Returns: our driver if name is valid, NIL otherwise
156  */
157 
mtx_valid(char * name)158 DRIVER *mtx_valid (char *name)
159 {
160   char tmp[MAILTMPLEN];
161   return mtx_isvalid (name,tmp) ? &mtxdriver : NIL;
162 }
163 
164 
165 /* MTX mail test for valid mailbox
166  * Accepts: mailbox name
167  *	    buffer to return file name
168  * Returns: T if valid, NIL otherwise
169  */
170 
mtx_isvalid(char * name,char * file)171 int mtx_isvalid (char *name,char *file)
172 {
173   int fd;
174   int ret = NIL;
175   char *s,tmp[MAILTMPLEN];
176   struct stat sbuf;
177   struct utimbuf times;
178   errno = EINVAL;		/* assume invalid argument */
179 				/* if file, get its status */
180   if ((s = dummy_file (file,name)) && !stat (s,&sbuf) &&
181       ((sbuf.st_mode & S_IFMT) == S_IFREG)) {
182     if (!sbuf.st_size)errno = 0;/* empty file */
183     else if ((fd = open (file,O_BINARY|O_RDONLY,NIL)) >= 0) {
184       memset (tmp,'\0',MAILTMPLEN);
185       if ((read (fd,tmp,64) >= 0) && (s = strchr (tmp,'\015')) &&
186 	  (s[1] == '\012')) {	/* valid format? */
187 	*s = '\0';		/* tie off header */
188 				/* must begin with dd-mmm-yy" */
189 	ret = (((tmp[2] == '-' && tmp[6] == '-') ||
190 		(tmp[1] == '-' && tmp[5] == '-')) &&
191 	       (s = strchr (tmp+18,',')) && strchr (s+2,';')) ? T : NIL;
192       }
193       else errno = -1;		/* bogus format */
194       close (fd);		/* close the file */
195 				/* \Marked status? */
196       if (sbuf.st_ctime > sbuf.st_atime) {
197 				/* preserve atime and mtime */
198 	times.actime = sbuf.st_atime;
199 	times.modtime = sbuf.st_mtime;
200 	utime (file,&times);	/* set the times */
201       }
202     }
203   }
204 				/* in case INBOX but not mtx format */
205   else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
206   return ret;			/* return what we should */
207 }
208 
209 
210 /* MTX manipulate driver parameters
211  * Accepts: function code
212  *	    function-dependent value
213  * Returns: function-dependent return value
214  */
215 
mtx_parameters(long function,void * value)216 void *mtx_parameters (long function,void *value)
217 {
218   return NIL;
219 }
220 
221 /* MTX mail scan mailboxes
222  * Accepts: mail stream
223  *	    reference
224  *	    pattern to search
225  *	    string to scan
226  */
227 
mtx_scan(MAILSTREAM * stream,char * ref,char * pat,char * contents)228 void mtx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
229 {
230   if (stream) dummy_scan (NIL,ref,pat,contents);
231 }
232 
233 
234 /* MTX mail list mailboxes
235  * Accepts: mail stream
236  *	    reference
237  *	    pattern to search
238  */
239 
mtx_list(MAILSTREAM * stream,char * ref,char * pat)240 void mtx_list (MAILSTREAM *stream,char *ref,char *pat)
241 {
242   if (stream) dummy_list (NIL,ref,pat);
243 }
244 
245 
246 /* MTX mail list subscribed mailboxes
247  * Accepts: mail stream
248  *	    reference
249  *	    pattern to search
250  */
251 
mtx_lsub(MAILSTREAM * stream,char * ref,char * pat)252 void mtx_lsub (MAILSTREAM *stream,char *ref,char *pat)
253 {
254   if (stream) dummy_lsub (NIL,ref,pat);
255 }
256 
257 /* MTX mail create mailbox
258  * Accepts: MAIL stream
259  *	    mailbox name to create
260  * Returns: T on success, NIL on failure
261  */
262 
mtx_create(MAILSTREAM * stream,char * mailbox)263 long mtx_create (MAILSTREAM *stream,char *mailbox)
264 {
265   char *s,mbx[MAILTMPLEN];
266   if (s = dummy_file (mbx,mailbox)) return dummy_create (stream,s);
267   sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
268   mm_log (mbx,ERROR);
269   return NIL;
270 }
271 
272 
273 /* MTX mail delete mailbox
274  * Accepts: MAIL stream
275  *	    mailbox name to delete
276  * Returns: T on success, NIL on failure
277  */
278 
mtx_delete(MAILSTREAM * stream,char * mailbox)279 long mtx_delete (MAILSTREAM *stream,char *mailbox)
280 {
281   return mtx_rename (stream,mailbox,NIL);
282 }
283 
284 /* MTX mail rename mailbox
285  * Accepts: MAIL stream
286  *	    old mailbox name
287  *	    new mailbox name (or NIL for delete)
288  * Returns: T on success, NIL on failure
289  */
290 
mtx_rename(MAILSTREAM * stream,char * old,char * newname)291 long mtx_rename (MAILSTREAM *stream,char *old,char *newname)
292 {
293   long ret = LONGT;
294   char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
295   int fd,ld;
296   struct stat sbuf;
297   if (!dummy_file (file,old) ||
298       (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
299 		   ((s = strrchr (tmp,'\\')) && !s[1])))) {
300     sprintf (tmp,newname ?
301 	     "Can't rename mailbox %.80s to %.80s: invalid name" :
302 	     "Can't delete mailbox %.80s: invalid name",
303 	     old,newname);
304     mm_log (tmp,ERROR);
305     return NIL;
306   }
307   if ((fd = open (file,O_BINARY|O_RDWR,NIL)) < 0) {
308     sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
309     mm_log (tmp,ERROR);
310     return NIL;
311   }
312 				/* get exclusive parse/append permission */
313   if ((ld = lockname (lock,file,LOCK_EX)) < 0) {
314     mm_log ("Unable to lock rename mailbox",ERROR);
315     return NIL;
316   }
317 				/* lock out other users */
318   if (flock (fd,LOCK_EX|LOCK_NB)) {
319     close (fd);			/* couldn't lock, give up on it then */
320     sprintf (tmp,"Mailbox %.80s is in use by another process",old);
321     mm_log (tmp,ERROR);
322     unlockfd (ld,lock);		/* release exclusive parse/append permission */
323     return NIL;
324   }
325 
326   if (newname) {		/* want rename? */
327 				/* found superior to destination name? */
328     if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
329 	((tmp[1] != ':') || (s != tmp + 2))) {
330       c = s[1];			/* remember character after delimiter */
331       *s = s[1] = '\0';		/* tie off name at delimiter */
332 				/* name doesn't exist, create it */
333       if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
334 	*s = '\\';		/* restore delimiter */
335 	if (!dummy_create (stream,tmp)) ret = NIL;
336       }
337       else *s = '\\';		/* restore delimiter */
338       s[1] = c;			/* restore character after delimiter */
339     }
340     flock (fd,LOCK_UN);		/* release lock on the file */
341     close (fd);			/* pacify NTFS */
342 				/* rename the file */
343     if (ret && rename (file,tmp)) {
344       sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
345 	       strerror (errno));
346       mm_log (tmp,ERROR);
347       ret = NIL;		/* set failure */
348     }
349   }
350   else {
351     flock (fd,LOCK_UN);		/* release lock on the file */
352     close (fd);			/* pacify NTFS */
353     if (unlink (file)) {
354       sprintf (tmp,"Can't delete mailbox %.80s: %.80s",old,strerror (errno));
355       mm_log (tmp,ERROR);
356       ret = NIL;		/* set failure */
357     }
358   }
359   unlockfd (ld,lock);		/* release exclusive parse/append permission */
360   return ret;			/* return success */
361 }
362 
363 /* MTX mail open
364  * Accepts: stream to open
365  * Returns: stream on success, NIL on failure
366  */
367 
mtx_open(MAILSTREAM * stream)368 MAILSTREAM *mtx_open (MAILSTREAM *stream)
369 {
370   int fd,ld;
371   char tmp[MAILTMPLEN];
372 				/* return prototype for OP_PROTOTYPE call */
373   if (!stream) return &mtxproto;
374   if (stream->local) fatal ("mtx recycle stream");
375 				/* canonicalize the mailbox name */
376   if (!dummy_file (tmp,stream->mailbox)) {
377     sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
378     mm_log (tmp,ERROR);
379   }
380   if (stream->rdonly ||
381       (fd = open (tmp,O_BINARY|O_RDWR,NIL)) < 0) {
382     if ((fd = open (tmp,O_BINARY|O_RDONLY,NIL)) < 0) {
383       sprintf (tmp,"Can't open mailbox: %.80s",strerror (errno));
384       mm_log (tmp,ERROR);
385       return NIL;
386     }
387     else if (!stream->rdonly) {	/* got it, but readonly */
388       mm_log ("Can't get write access to mailbox, access is readonly",WARN);
389       stream->rdonly = T;
390     }
391   }
392   stream->local = fs_get (sizeof (MTXLOCAL));
393   LOCAL->fd = fd;		/* bind the file */
394   LOCAL->buf = (char *) fs_get (CHUNKSIZE);
395   LOCAL->buflen = CHUNKSIZE - 1;
396 				/* note if an INBOX or not */
397   stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
398   fs_give ((void **) &stream->mailbox);
399   stream->mailbox = cpystr (tmp);
400 				/* get shared parse permission */
401   if ((ld = lockname (tmp,stream->mailbox,LOCK_SH)) < 0) {
402     mm_log ("Unable to lock open mailbox",ERROR);
403     return NIL;
404   }
405   flock (LOCAL->fd,LOCK_SH);	/* lock the file */
406   unlockfd (ld,tmp);		/* release shared parse permission */
407   LOCAL->filesize = 0;		/* initialize parsed file size */
408   LOCAL->filetime = 0;		/* time not set up yet */
409   LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
410   stream->sequence++;		/* bump sequence number */
411   stream->uid_validity = (unsigned long) time (0);
412 				/* parse mailbox */
413   stream->nmsgs = stream->recent = 0;
414   if (mtx_ping (stream) && !stream->nmsgs)
415     mm_log ("Mailbox is empty",(long) NIL);
416   if (!LOCAL) return NIL;	/* failure if stream died */
417   stream->perm_seen = stream->perm_deleted =
418     stream->perm_flagged = stream->perm_answered = stream->perm_draft =
419       stream->rdonly ? NIL : T;
420   stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
421   return stream;		/* return stream to caller */
422 }
423 
424 /* MTX mail close
425  * Accepts: MAIL stream
426  *	    close options
427  */
428 
mtx_close(MAILSTREAM * stream,long options)429 void mtx_close (MAILSTREAM *stream,long options)
430 {
431   if (stream && LOCAL) {	/* only if a file is open */
432     int silent = stream->silent;
433     stream->silent = T;		/* note this stream is dying */
434     if (options & CL_EXPUNGE) mtx_expunge (stream,NIL,NIL);
435     stream->silent = silent;	/* restore previous status */
436     flock (LOCAL->fd,LOCK_UN);	/* unlock local file */
437     close (LOCAL->fd);		/* close the local file */
438 				/* free local text buffer */
439     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
440 				/* nuke the local data */
441     fs_give ((void **) &stream->local);
442     stream->dtb = NIL;		/* log out the DTB */
443   }
444 }
445 
446 
447 /* MTX mail fetch flags
448  * Accepts: MAIL stream
449  *	    sequence
450  *	    option flags
451  * Sniffs at file to see if some other process changed the flags
452  */
453 
mtx_flags(MAILSTREAM * stream,char * sequence,long flags)454 void mtx_flags (MAILSTREAM *stream,char *sequence,long flags)
455 {
456   unsigned long i;
457   if (mtx_ping (stream) && 	/* ping mailbox, get new status for messages */
458       ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
459        mail_sequence (stream,sequence)))
460     for (i = 1; i <= stream->nmsgs; i++)
461       if (mail_elt (stream,i)->sequence) mtx_elt (stream,i);
462 }
463 
464 /* MTX mail fetch message header
465  * Accepts: MAIL stream
466  *	    message # to fetch
467  *	    pointer to returned header text length
468  *	    option flags
469  * Returns: message header in RFC822 format
470  */
471 
mtx_header(MAILSTREAM * stream,unsigned long msgno,unsigned long * length,long flags)472 char *mtx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
473 		  long flags)
474 {
475   *length = 0;			/* default to empty */
476   if (flags & FT_UID) return "";/* UID call "impossible" */
477 				/* get to header position */
478   lseek (LOCAL->fd,mtx_hdrpos (stream,msgno,length),L_SET);
479 				/* is buffer big enough? */
480   if (*length > LOCAL->buflen) {
481     fs_give ((void **) &LOCAL->buf);
482     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
483   }
484   LOCAL->buf[*length] = '\0';	/* tie off string */
485 				/* slurp the data */
486   read (LOCAL->fd,LOCAL->buf,*length);
487   return LOCAL->buf;
488 }
489 
490 /* MTX mail fetch message text (body only)
491  * Accepts: MAIL stream
492  *	    message # to fetch
493  *	    pointer to returned header text length
494  *	    option flags
495  * Returns: T, always
496  */
497 
mtx_text(MAILSTREAM * stream,unsigned long msgno,STRING * bs,long flags)498 long mtx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
499 {
500   FDDATA d;
501   unsigned long i,j;
502   MESSAGECACHE *elt;
503 				/* UID call "impossible" */
504   if (flags & FT_UID) return NIL;
505   elt = mtx_elt (stream,msgno);	/* get message status */
506 				/* if message not seen */
507   if (!(flags & FT_PEEK) && !elt->seen) {
508     elt->seen = T;		/* mark message as seen */
509 				/* recalculate status */
510     mtx_update_status (stream,msgno,NIL);
511     mm_flags (stream,msgno);
512   }
513 				/* find header position */
514   i = mtx_hdrpos (stream,msgno,&j);
515   d.fd = LOCAL->fd;		/* set up file descriptor */
516   d.pos = i + j;
517   d.chunk = LOCAL->buf;	/* initial buffer chunk */
518   d.chunksize = CHUNKSIZE;
519   INIT (bs,fd_string,&d,elt->rfc822_size - j);
520   return T;			/* success */
521 }
522 
523 /* MTX mail modify flags
524  * Accepts: MAIL stream
525  *	    sequence
526  *	    flag(s)
527  *	    option flags
528  */
529 
mtx_flag(MAILSTREAM * stream,char * sequence,char * flag,long flags)530 void mtx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
531 {
532   struct utimbuf times;
533   struct stat sbuf;
534   if (!stream->rdonly) {	/* make sure the update takes */
535     fsync (LOCAL->fd);
536     fstat (LOCAL->fd,&sbuf);	/* get current write time */
537     times.modtime = LOCAL->filetime = sbuf.st_mtime;
538     times.actime = time (0);	/* make sure read comes after all that */
539     utime (stream->mailbox,&times);
540   }
541 }
542 
543 
544 /* MTX mail per-message modify flags
545  * Accepts: MAIL stream
546  *	    message cache element
547  */
548 
mtx_flagmsg(MAILSTREAM * stream,MESSAGECACHE * elt)549 void mtx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
550 {
551   struct stat sbuf;
552 				/* maybe need to do a checkpoint? */
553   if (LOCAL->filetime && !LOCAL->shouldcheck) {
554     fstat (LOCAL->fd,&sbuf);	/* get current write time */
555     if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
556     LOCAL->filetime = 0;	/* don't do this test for any other messages */
557   }
558 				/* recalculate status */
559   mtx_update_status (stream,elt->msgno,NIL);
560 }
561 
562 /* MTX mail ping mailbox
563  * Accepts: MAIL stream
564  * Returns: T if stream still alive, NIL if not
565  */
566 
mtx_ping(MAILSTREAM * stream)567 long mtx_ping (MAILSTREAM *stream)
568 {
569   unsigned long i = 1;
570   long r = T;
571   int ld;
572   char lock[MAILTMPLEN];
573   struct stat sbuf;
574   if (stream && LOCAL) {	/* only if stream already open */
575     fstat (LOCAL->fd,&sbuf);	/* get current file poop */
576     if (LOCAL->filetime && !(LOCAL->mustcheck || LOCAL->shouldcheck) &&
577 	(LOCAL->filetime < sbuf.st_mtime)) LOCAL->shouldcheck = T;
578 				/* check for changed message status */
579     if (LOCAL->mustcheck || LOCAL->shouldcheck) {
580       LOCAL->filetime = sbuf.st_mtime;
581       if (LOCAL->shouldcheck)	/* babble when we do this unilaterally */
582 	mm_notify (stream,"[CHECK] Checking for flag updates",NIL);
583       while (i <= stream->nmsgs) mtx_elt (stream,i++);
584       LOCAL->mustcheck = LOCAL->shouldcheck = NIL;
585     }
586 				/* get shared parse/append permission */
587     if ((sbuf.st_size != LOCAL->filesize) &&
588 	((ld = lockname (lock,stream->mailbox,LOCK_SH)) >= 0)) {
589 				/* parse resulting mailbox */
590       r = (mtx_parse (stream)) ? T : NIL;
591       unlockfd (ld,lock);	/* release shared parse/append permission */
592     }
593   }
594   return r;			/* return result of the parse */
595 }
596 
597 
598 /* MTX mail check mailbox (reparses status too)
599  * Accepts: MAIL stream
600  */
601 
mtx_check(MAILSTREAM * stream)602 void mtx_check (MAILSTREAM *stream)
603 {
604 				/* mark that a check is desired */
605   if (LOCAL) LOCAL->mustcheck = T;
606   if (mtx_ping (stream)) mm_log ("Check completed",(long) NIL);
607 }
608 
609 /* MTX mail expunge mailbox
610  *	    sequence to expunge if non-NIL
611  *	    expunge options
612  * Returns: T, always
613  */
614 
mtx_expunge(MAILSTREAM * stream,char * sequence,long options)615 long mtx_expunge (MAILSTREAM *stream,char *sequence,long options)
616 {
617   long ret;
618   struct utimbuf times;
619   struct stat sbuf;
620   off_t pos = 0;
621   int ld;
622   unsigned long i = 1;
623   unsigned long j,k,m,recent;
624   unsigned long n = 0;
625   unsigned long delta = 0;
626   char lock[MAILTMPLEN];
627   MESSAGECACHE *elt;
628   if (!(ret = (sequence ? ((options & EX_UID) ?
629 			   mail_uid_sequence (stream,sequence) :
630 			   mail_sequence (stream,sequence)) : LONGT) &&
631 	mtx_ping (stream)));	/* parse sequence if given, ping stream */
632   else if (stream->rdonly) mm_log ("Expunge ignored on readonly mailbox",WARN);
633   else {
634     if (LOCAL->filetime && !LOCAL->shouldcheck) {
635       fstat (LOCAL->fd,&sbuf);	/* get current write time */
636       if (LOCAL->filetime < sbuf.st_mtime) LOCAL->shouldcheck = T;
637     }
638 				/* get exclusive parse/append permission */
639     if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0)
640       mm_log ("Unable to lock expunge mailbox",ERROR);
641 				/* make sure see any newly-arrived messages */
642     else if (!mtx_parse (stream));
643 				/* get exclusive access */
644     else if (flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
645       flock (LOCAL->fd,LOCK_SH);/* recover previous lock */
646       mm_log ("Can't expunge because mailbox is in use by another process",
647 	      ERROR);
648       unlockfd (ld,lock);	/* release exclusive parse/append permission */
649     }
650 
651     else {
652       mm_critical (stream);	/* go critical */
653       recent = stream->recent;	/* get recent now that pinged and locked */
654 				/* for each message */
655       while (i <= stream->nmsgs) {
656 				/* get cache element */
657 	elt = mtx_elt (stream,i);
658 				/* number of bytes to smash or preserve */
659 	k = elt->private.special.text.size + elt->rfc822_size;
660 				/* if need to expunge this message */
661 	if (elt->deleted && (sequence ? elt->sequence : T)) {
662 				/* if recent, note one less recent message */
663 	  if (elt->recent) --recent;
664 	  delta += k;		/* number of bytes to delete */
665 				/* notify upper levels */
666 	  mail_expunged (stream,i);
667 	  n++;			/* count up one more expunged message */
668 	}
669 	else if (i++ && delta) {/* preserved message */
670 				/* first byte to preserve */
671 	  j = elt->private.special.offset;
672 	  do {			/* read from source position */
673 	    m = min (k,LOCAL->buflen);
674 	    lseek (LOCAL->fd,j,L_SET);
675 	    read (LOCAL->fd,LOCAL->buf,m);
676 	    pos = j - delta;	/* write to destination position */
677 	    while (T) {
678 	      lseek (LOCAL->fd,pos,L_SET);
679 	      if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
680 	      mm_notify (stream,strerror (errno),WARN);
681 	      mm_diskerror (stream,errno,T);
682 	    }
683 	    pos += m;		/* new position */
684 	    j += m;		/* next chunk, perhaps */
685 	  } while (k -= m);	/* until done */
686 				/* note the new address of this text */
687 	  elt->private.special.offset -= delta;
688 	}
689 				/* preserved but no deleted messages */
690 	else pos = elt->private.special.offset + k;
691       }
692       if (n) {			/* truncate file after last message */
693 	if (pos != (LOCAL->filesize -= delta)) {
694 	  sprintf (LOCAL->buf,
695 		   "Calculated size mismatch %lu != %lu, delta = %lu",
696 		   (unsigned long) pos,(unsigned long) LOCAL->filesize,delta);
697 	  mm_log (LOCAL->buf,WARN);
698 	  LOCAL->filesize = pos;/* fix it then */
699 	}
700 	ftruncate (LOCAL->fd,LOCAL->filesize);
701 	sprintf (LOCAL->buf,"Expunged %lu messages",n);
702 				/* output the news */
703 	mm_log (LOCAL->buf,(long) NIL);
704       }
705       else mm_log ("No messages deleted, so no update needed",(long) NIL);
706       fsync (LOCAL->fd);	/* force disk update */
707       fstat (LOCAL->fd,&sbuf);	/* get new write time */
708       times.modtime = LOCAL->filetime = sbuf.st_mtime;
709       times.actime = time (0);	/* reset atime to now */
710       utime (stream->mailbox,&times);
711       mm_nocritical (stream);	/* release critical */
712 				/* notify upper level of new mailbox size */
713       mail_exists (stream,stream->nmsgs);
714       mail_recent (stream,recent);
715       flock (LOCAL->fd,LOCK_SH);/* allow sharers again */
716       unlockfd (ld,lock);	/* release exclusive parse/append permission */
717     }
718   }
719   return ret;
720 }
721 
722 /* MTX mail copy message(s)
723  * Accepts: MAIL stream
724  *	    sequence
725  *	    destination mailbox
726  *	    copy options
727  * Returns: T if success, NIL if failed
728  */
729 
mtx_copy(MAILSTREAM * stream,char * sequence,char * mailbox,long options)730 long mtx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
731 {
732   struct stat sbuf;
733   struct utimbuf times;
734   MESSAGECACHE *elt;
735   unsigned long i,j,k;
736   long ret = LONGT;
737   int fd,ld;
738   char file[MAILTMPLEN],lock[MAILTMPLEN];
739   mailproxycopy_t pc =
740     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
741 				/* make sure valid mailbox */
742   if (!mtx_isvalid (mailbox,file)) switch (errno) {
743   case ENOENT:			/* no such file? */
744     mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
745     return NIL;
746   case 0:			/* merely empty file? */
747     break;
748   case EACCES:			/* file protected */
749     sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
750     MM_LOG (LOCAL->buf,ERROR);
751     return NIL;
752   case EINVAL:
753     if (pc) return (*pc) (stream,sequence,mailbox,options);
754     sprintf (LOCAL->buf,"Invalid MTX-format mailbox name: %.80s",mailbox);
755     mm_log (LOCAL->buf,ERROR);
756     return NIL;
757   default:
758     if (pc) return (*pc) (stream,sequence,mailbox,options);
759     sprintf (LOCAL->buf,"Not a MTX-format mailbox: %.80s",mailbox);
760     mm_log (LOCAL->buf,ERROR);
761     return NIL;
762   }
763   if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
764 	mail_sequence (stream,sequence))) return NIL;
765 				/* got file? */
766   if ((fd = open (file,O_BINARY|O_RDWR|O_CREAT,S_IREAD|S_IWRITE)) < 0) {
767     sprintf (LOCAL->buf,"Unable to open copy mailbox: %.80s",strerror (errno));
768     mm_log (LOCAL->buf,ERROR);
769     return NIL;
770   }
771   mm_critical (stream);		/* go critical */
772 				/* get exclusive parse/append permission */
773   if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
774     mm_log ("Unable to lock copy mailbox",ERROR);
775     mm_nocritical (stream);
776     return NIL;
777   }
778   fstat (fd,&sbuf);		/* get current file size */
779   lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
780 
781 				/* for each requested message */
782   for (i = 1; ret && (i <= stream->nmsgs); i++)
783     if ((elt = mail_elt (stream,i))->sequence) {
784       lseek (LOCAL->fd,elt->private.special.offset,L_SET);
785 				/* number of bytes to copy */
786       k = elt->private.special.text.size + elt->rfc822_size;
787       do {			/* read from source position */
788 	j = min (k,LOCAL->buflen);
789 	read (LOCAL->fd,LOCAL->buf,j);
790 	if (write (fd,LOCAL->buf,j) < 0) ret = NIL;
791       } while (ret && (k -= j));/* until done */
792     }
793 				/* make sure all the updates take */
794   if (!(ret && (ret = !fsync (fd)))) {
795     sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
796     mm_log (LOCAL->buf,ERROR);
797     ftruncate (fd,sbuf.st_size);
798   }
799 				/* set atime to now-1 if successful copy */
800   if (ret) times.actime = time (0) - 1;
801 				/* else preserved \Marked status */
802   else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
803 	 sbuf.st_atime : time (0);
804   times.modtime = sbuf.st_mtime;/* preserve mtime */
805   utime (file,&times);		/* set the times */
806   unlockfd (ld,lock);		/* release exclusive parse/append permission */
807   close (fd);			/* close the file */
808   mm_nocritical (stream);	/* release critical */
809 				/* delete all requested messages */
810   if (ret && (options & CP_MOVE)) {
811     for (i = 1; i <= stream->nmsgs; i++)
812       if ((elt = mtx_elt (stream,i))->sequence) {
813 	elt->deleted = T;	/* mark message deleted */
814 				/* recalculate status */
815 	mtx_update_status (stream,i,NIL);
816       }
817     if (!stream->rdonly) {	/* make sure the update takes */
818       fsync (LOCAL->fd);
819       fstat (LOCAL->fd,&sbuf);	/* get current write time */
820       times.modtime = LOCAL->filetime = sbuf.st_mtime;
821       times.actime = time (0);	/* make sure atime remains greater */
822       utime (stream->mailbox,&times);
823     }
824   }
825   if (ret && mail_parameters (NIL,GET_COPYUID,NIL))
826     mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN);
827   return ret;
828 }
829 
830 /* MTX mail append message from stringstruct
831  * Accepts: MAIL stream
832  *	    destination mailbox
833  *	    append callback
834  *	    data for callback
835  * Returns: T if append successful, else NIL
836  */
837 
mtx_append(MAILSTREAM * stream,char * mailbox,append_t af,void * data)838 long mtx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
839 {
840   struct stat sbuf;
841   int fd,ld,c;
842   char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
843   struct utimbuf times;
844   FILE *df;
845   MESSAGECACHE elt;
846   long f;
847   unsigned long i,uf;
848   STRING *message;
849   long ret = LONGT;
850 				/* default stream to prototype */
851   if (!stream) stream = &mtxproto;
852 				/* make sure valid mailbox */
853   if (!mtx_isvalid (mailbox,file)) switch (errno) {
854   case ENOENT:			/* no such file? */
855     if (!compare_cstring (mailbox,"INBOX")) mtx_create (NIL,"INBOX");
856     else {
857       mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
858       return NIL;
859     }
860 				/* falls through */
861   case 0:			/* merely empty file? */
862     break;
863   case EACCES:			/* file protected */
864     sprintf (tmp,"Can't access destination: %.80s",mailbox);
865     MM_LOG (tmp,ERROR);
866     return NIL;
867   case EINVAL:
868     sprintf (tmp,"Invalid MTX-format mailbox name: %.80s",mailbox);
869     mm_log (tmp,ERROR);
870     return NIL;
871   default:
872     sprintf (tmp,"Not a MTX-format mailbox: %.80s",mailbox);
873     mm_log (tmp,ERROR);
874     return NIL;
875   }
876 				/* get first message */
877   if (!(*af) (stream,data,&flags,&date,&message)) return NIL;
878 
879 				/* open destination mailbox */
880   if (((fd = open (file,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IREAD|S_IWRITE))
881        < 0) || !(df = fdopen (fd,"ab"))) {
882     sprintf (tmp,"Can't open append mailbox: %s",strerror (errno));
883     mm_log (tmp,ERROR);
884     return NIL;
885   }
886 				/* get parse/append permission */
887   if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
888     mm_log ("Unable to lock append mailbox",ERROR);
889     close (fd);
890     return NIL;
891   }
892   mm_critical (stream);		/* go critical */
893   fstat (fd,&sbuf);		/* get current file size */
894   errno = 0;
895   do {				/* parse flags */
896     if (!SIZE (message)) {	/* guard against zero-length */
897       mm_log ("Append of zero-length message",ERROR);
898       ret = NIL;
899       break;
900     }
901     f = mail_parse_flags (stream,flags,&i);
902 				/* reverse bits (dontcha wish we had CIRC?) */
903     for (uf = 0; i; uf |= 1 << (29 - find_rightmost_bit (&i)));
904     if (date) {			/* parse date if given */
905       if (!mail_parse_date (&elt,date)) {
906 	sprintf (tmp,"Bad date in append: %.80s",date);
907 	mm_log (tmp,ERROR);
908 	ret = NIL;		/* mark failure */
909 	break;
910       }
911       mail_date (tmp,&elt);	/* write preserved date */
912     }
913     else internal_date (tmp);	/* get current date in IMAP format */
914 				/* write header */
915     if (fprintf (df,"%s,%lu;%010lo%02lo\015\012",tmp,i = SIZE (message),uf,
916 		 (unsigned long) f) < 0) ret = NIL;
917     else {			/* write message */
918       if (i) do c = 0xff & SNX (message);
919       while ((putc (c,df) != EOF) && --i);
920 				/* get next message */
921       if (i || !(*af) (stream,data,&flags,&date,&message)) ret = NIL;
922     }
923   } while (ret && message);
924 				/* if error... */
925   if (!ret || (fflush (df) == EOF)) {
926     ftruncate (fd,sbuf.st_size);/* revert file */
927     close (fd);			/* make sure fclose() doesn't corrupt us */
928     if (errno) {
929       sprintf (tmp,"Message append failed: %s",strerror (errno));
930       mm_log (tmp,ERROR);
931     }
932     ret = NIL;
933   }
934   if (ret) times.actime = time (0) - 1;
935 				/* else preserved \Marked status */
936   else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
937 	 sbuf.st_atime : time (0);
938   times.modtime = sbuf.st_mtime;/* preserve mtime */
939   utime (file,&times);		/* set the times */
940   fclose (df);			/* close the file */
941   unlockfd (ld,lock);		/* release exclusive parse/append permission */
942   mm_nocritical (stream);	/* release critical */
943   if (ret && mail_parameters (NIL,GET_APPENDUID,NIL))
944     mm_log ("Can not return meaningful APPENDUID with this mailbox format",
945 	    WARN);
946   return ret;
947 }
948 
949 /* Internal routines */
950 
951 
952 /* MTX mail parse mailbox
953  * Accepts: MAIL stream
954  * Returns: T if parse OK
955  *	    NIL if failure, stream aborted
956  */
957 
mtx_parse(MAILSTREAM * stream)958 long mtx_parse (MAILSTREAM *stream)
959 {
960   struct stat sbuf;
961   MESSAGECACHE *elt = NIL;
962   unsigned char c,*s,*t,*x;
963   char tmp[MAILTMPLEN];
964   unsigned long i,j;
965   long curpos = LOCAL->filesize;
966   long nmsgs = stream->nmsgs;
967   long recent = stream->recent;
968   short added = NIL;
969   short silent = stream->silent;
970   fstat (LOCAL->fd,&sbuf);	/* get status */
971   if (sbuf.st_size < curpos) {	/* sanity check */
972     sprintf (tmp,"Mailbox shrank from %ld to %ld!",curpos,sbuf.st_size);
973     mm_log (tmp,ERROR);
974     mtx_close (stream,NIL);
975     return NIL;
976   }
977   stream->silent = T;		/* don't pass up mm_exists() events yet */
978   while (sbuf.st_size - curpos){/* while there is stuff to parse */
979 				/* get to that position in the file */
980     lseek (LOCAL->fd,curpos,L_SET);
981     if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
982       sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
983 	       (unsigned long) curpos,(unsigned long) sbuf.st_size,
984 	       i ? strerror (errno) : "no data read");
985       mm_log (tmp,ERROR);
986       mtx_close (stream,NIL);
987       return NIL;
988     }
989     LOCAL->buf[i] = '\0';	/* tie off buffer just in case */
990     if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
991       sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %s",
992 	       (unsigned long) curpos,i,(char *) LOCAL->buf);
993       mm_log (tmp,ERROR);
994       mtx_close (stream,NIL);
995       return NIL;
996     }
997     *s = '\0';			/* tie off header line */
998     i = (s + 2) - LOCAL->buf;	/* note start of text offset */
999     if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
1000       sprintf (tmp,"Unable to parse internal header at %lu: %s",
1001 	       (unsigned long) curpos,(char *) LOCAL->buf);
1002       mm_log (tmp,ERROR);
1003       mtx_close (stream,NIL);
1004       return NIL;
1005     }
1006     *s++ = '\0'; *t++ = '\0';	/* tie off fields */
1007 
1008     added = T;			/* note that a new message was added */
1009 				/* swell the cache */
1010     mail_exists (stream,++nmsgs);
1011 				/* instantiate an elt for this message */
1012     (elt = mail_elt (stream,nmsgs))->valid = T;
1013     elt->private.uid = ++stream->uid_last;
1014 				/* note file offset of header */
1015     elt->private.special.offset = curpos;
1016 				/* in case error */
1017     elt->private.special.text.size = 0;
1018 				/* header size not known yet */
1019     elt->private.msg.header.text.size = 0;
1020     x = s;			/* parse the header components */
1021     if (mail_parse_date (elt,LOCAL->buf) &&
1022 	(elt->rfc822_size = strtoul (s,(char **) &s,10)) && (!(s && *s)) &&
1023 	isdigit (t[0]) && isdigit (t[1]) && isdigit (t[2]) &&
1024 	isdigit (t[3]) && isdigit (t[4]) && isdigit (t[5]) &&
1025 	isdigit (t[6]) && isdigit (t[7]) && isdigit (t[8]) &&
1026 	isdigit (t[9]) && isdigit (t[10]) && isdigit (t[11]) && !t[12])
1027       elt->private.special.text.size = i;
1028     else {			/* oops */
1029       sprintf (tmp,"Unable to parse internal header elements at %ld: %s,%s;%s",
1030 	       curpos,(char *) LOCAL->buf,(char *) x,(char *) t);
1031       mm_log (tmp,ERROR);
1032       mtx_close (stream,NIL);
1033       return NIL;
1034     }
1035 				/* make sure didn't run off end of file */
1036     if ((curpos += (elt->rfc822_size + i)) > sbuf.st_size) {
1037       sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
1038 	       elt->private.special.offset,(unsigned long) curpos,
1039 	       (unsigned long) sbuf.st_size);
1040       mm_log (tmp,ERROR);
1041       mtx_close (stream,NIL);
1042       return NIL;
1043     }
1044     c = t[10];			/* remember first system flags byte */
1045     t[10] = '\0';		/* tie off flags */
1046     j = strtoul (t,NIL,8);	/* get user flags value */
1047     t[10] = c;			/* restore first system flags byte */
1048 				/* set up all valid user flags (reversed!) */
1049     while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
1050 		  stream->user_flags[i]) elt->user_flags |= 1 << i;
1051 				/* calculate system flags */
1052     if ((j = ((t[10]-'0') * 8) + t[11]-'0') & fSEEN) elt->seen = T;
1053     if (j & fDELETED) elt->deleted = T;
1054     if (j & fFLAGGED) elt->flagged = T;
1055     if (j & fANSWERED) elt->answered = T;
1056     if (j & fDRAFT) elt->draft = T;
1057     if (!(j & fOLD)) {		/* newly arrived message? */
1058       elt->recent = T;
1059       recent++;			/* count up a new recent message */
1060 				/* mark it as old */
1061       mtx_update_status (stream,nmsgs,NIL);
1062     }
1063   }
1064   fsync (LOCAL->fd);		/* make sure all the fOLD flags take */
1065 				/* update parsed file size and time */
1066   LOCAL->filesize = sbuf.st_size;
1067   fstat (LOCAL->fd,&sbuf);	/* get status again to ensure time is right */
1068   LOCAL->filetime = sbuf.st_mtime;
1069   if (added && !stream->rdonly){/* make sure atime updated */
1070     struct utimbuf times;
1071     times.actime = time (0);
1072     times.modtime = LOCAL->filetime;
1073     utime (stream->mailbox,&times);
1074   }
1075   stream->silent = silent;	/* can pass up events now */
1076   mail_exists (stream,nmsgs);	/* notify upper level of new mailbox size */
1077   mail_recent (stream,recent);	/* and of change in recent messages */
1078   return LONGT;			/* return the winnage */
1079 }
1080 
1081 /* MTX get cache element with status updating from file
1082  * Accepts: MAIL stream
1083  *	    message number
1084  * Returns: cache element
1085  */
1086 
mtx_elt(MAILSTREAM * stream,unsigned long msgno)1087 MESSAGECACHE *mtx_elt (MAILSTREAM *stream,unsigned long msgno)
1088 {
1089   MESSAGECACHE *elt = mail_elt (stream,msgno);
1090   struct {			/* old flags */
1091     unsigned int seen : 1;
1092     unsigned int deleted : 1;
1093     unsigned int flagged : 1;
1094     unsigned int answered : 1;
1095     unsigned int draft : 1;
1096     unsigned long user_flags;
1097   } old;
1098   old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
1099   old.answered = elt->answered; old.draft = elt->draft;
1100   old.user_flags = elt->user_flags;
1101   mtx_read_flags (stream,elt);
1102   if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
1103       (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
1104       (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
1105     mm_flags (stream,msgno);	/* let top level know */
1106   return elt;
1107 }
1108 
1109 /* MTX read flags from file
1110  * Accepts: MAIL stream
1111  * Returns: cache element
1112  */
1113 
mtx_read_flags(MAILSTREAM * stream,MESSAGECACHE * elt)1114 void mtx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
1115 {
1116   unsigned long i,j;
1117 				/* noop if readonly and have valid flags */
1118   if (stream->rdonly && elt->valid) return;
1119 				/* set the seek pointer */
1120   lseek (LOCAL->fd,(off_t) elt->private.special.offset +
1121 	 elt->private.special.text.size - 14,L_SET);
1122 				/* read the new flags */
1123   if (read (LOCAL->fd,LOCAL->buf,12) < 0) {
1124     sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
1125     fatal (LOCAL->buf);
1126   }
1127 				/* calculate system flags */
1128   i = (((LOCAL->buf[10]-'0') * 8) + LOCAL->buf[11]-'0');
1129   elt->seen = i & fSEEN ? T : NIL; elt->deleted = i & fDELETED ? T : NIL;
1130   elt->flagged = i & fFLAGGED ? T : NIL;
1131   elt->answered = i & fANSWERED ? T : NIL; elt->draft = i & fDRAFT ? T : NIL;
1132   LOCAL->buf[10] = '\0';	/* tie off flags */
1133   j = strtoul(LOCAL->buf,NIL,8);/* get user flags value */
1134 				/* set up all valid user flags (reversed!) */
1135   while (j) if (((i = 29 - find_rightmost_bit (&j)) < NUSERFLAGS) &&
1136 		stream->user_flags[i]) elt->user_flags |= 1 << i;
1137   elt->valid = T;		/* have valid flags now */
1138 }
1139 
1140 /* MTX update status string
1141  * Accepts: MAIL stream
1142  *	    message number
1143  *	    flag saying whether or not to sync
1144  */
1145 
mtx_update_status(MAILSTREAM * stream,unsigned long msgno,long syncflag)1146 void mtx_update_status (MAILSTREAM *stream,unsigned long msgno,long syncflag)
1147 {
1148   struct utimbuf times;
1149   struct stat sbuf;
1150   MESSAGECACHE *elt = mail_elt (stream,msgno);
1151   unsigned long j,k = 0;
1152 				/* readonly */
1153   if (stream->rdonly || !elt->valid) mtx_read_flags (stream,elt);
1154   else {			/* readwrite */
1155     j = elt->user_flags;	/* get user flags */
1156 				/* reverse bits (dontcha wish we had CIRC?) */
1157     while (j) k |= 1 << (29 - find_rightmost_bit (&j));
1158 				/* print new flag string */
1159     sprintf (LOCAL->buf,"%010lo%02o",k,(unsigned)
1160 	     (fOLD + (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
1161 	      (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
1162 	      (fDRAFT * elt->draft)));
1163     while (T) {			/* get to that place in the file */
1164       lseek (LOCAL->fd,(off_t) elt->private.special.offset +
1165 	     elt->private.special.text.size - 14,L_SET);
1166 				/* write new flags */
1167       if (write (LOCAL->fd,LOCAL->buf,12) > 0) break;
1168       mm_notify (stream,strerror (errno),WARN);
1169       mm_diskerror (stream,errno,T);
1170     }
1171     if (syncflag) {		/* sync if requested */
1172       fsync (LOCAL->fd);
1173       fstat (LOCAL->fd,&sbuf);	/* get new write time */
1174       times.modtime = LOCAL->filetime = sbuf.st_mtime;
1175       times.actime = time (0);	/* make sure read is later */
1176       utime (stream->mailbox,&times);
1177     }
1178   }
1179 }
1180 
1181 /* MTX locate header for a message
1182  * Accepts: MAIL stream
1183  *	    message number
1184  *	    pointer to returned header size
1185  * Returns: position of header in file
1186  */
1187 
mtx_hdrpos(MAILSTREAM * stream,unsigned long msgno,unsigned long * size)1188 unsigned long mtx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
1189 			  unsigned long *size)
1190 {
1191   unsigned long siz;
1192   long i = 0;
1193   int q = 0;
1194   char *s,tmp[MAILTMPLEN];
1195   MESSAGECACHE *elt = mtx_elt (stream,msgno);
1196   unsigned long ret = elt->private.special.offset +
1197     elt->private.special.text.size;
1198 				/* is header size known? */
1199   if (!(*size = elt->private.msg.header.text.size)) {
1200     lseek (LOCAL->fd,ret,L_SET);/* get to header position */
1201 				/* search message for CRLF CRLF */
1202     for (siz = 1,s = tmp; siz <= elt->rfc822_size; siz++) {
1203 				/* read another buffer as necessary */
1204       if ((--i <= 0) &&		/* buffer empty? */
1205 	  (read (LOCAL->fd,s = tmp,
1206 		 i = min (elt->rfc822_size - siz,(long) MAILTMPLEN)) < 0))
1207 	return ret;		/* I/O error? */
1208       switch (q) {		/* sniff at buffer */
1209       case 0:			/* first character */
1210 	q = (*s++ == '\015') ? 1 : 0;
1211 	break;
1212       case 1:			/* second character */
1213 	q = (*s++ == '\012') ? 2 : 0;
1214 	break;
1215       case 2:			/* third character */
1216 	q = (*s++ == '\015') ? 3 : 0;
1217 	break;
1218       case 3:			/* fourth character */
1219 	if (*s++ == '\012') {	/* have the sequence? */
1220 				/* yes, note for later */
1221 	  elt->private.msg.header.text.size = *size = siz;
1222 	  return ret;
1223 	}
1224 	q = 0;			/* lost... */
1225 	break;
1226       }
1227     }
1228 				/* header consumes entire message */
1229     elt->private.msg.header.text.size = *size = elt->rfc822_size;
1230   }
1231   return ret;
1232 }
1233