* Clean up some function definitions to comply with strict
[alpine.git] / imap / src / osdep / nt / mbxnt.c
blob859d06ea3e0fe4cc9d70d1946445af9566a3f056
1 /* ========================================================================
2 * Copyright 1988-2007 University of Washington
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
8 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
15 * Program: MBX mail routines
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
25 * Date: 3 October 1995
26 * Last Edited: 28 September 2007
30 /* FILE TIME SEMANTICS
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.
37 #include <stdio.h>
38 #include <ctype.h>
39 #include <errno.h>
40 #ifndef errno
41 extern int errno; /* just in case */
42 #endif
43 #include "mail.h"
44 #include "osdep.h"
45 #include <fcntl.h>
46 #include <time.h>
47 #include <sys/stat.h>
48 #include <sys/utime.h>
49 #include "misc.h"
50 #include "dummy.h"
51 #include "fdstring.h"
54 /* Build parameters */
56 #define HDRSIZE 2048
58 /* MBX I/O stream local data */
60 typedef struct mbx_local {
61 unsigned int flagcheck: 1; /* if ping should sweep for flags */
62 unsigned int expok: 1; /* if expunging OK in ping */
63 unsigned int expunged : 1; /* if one or more expunged messages */
64 int fd; /* file descriptor for I/O */
65 int ld; /* lock file descriptor */
66 int ffuserflag; /* first free user flag */
67 off_t filesize; /* file size parsed */
68 time_t filetime; /* last file time */
69 time_t lastsnarf; /* last snarf time */
70 unsigned char *buf; /* temporary buffer */
71 unsigned long buflen; /* current size of temporary buffer */
72 char lock[MAILTMPLEN]; /* buffer to write lock name */
73 } MBXLOCAL;
76 /* Convenient access to local data */
78 #define LOCAL ((MBXLOCAL *) stream->local)
80 /* Function prototypes */
82 DRIVER *mbx_valid (char *name);
83 int mbx_isvalid (MAILSTREAM **stream,char *name,char *file,int *ld,char *lock,
84 long flags);
85 void *mbx_parameters (long function,void *value);
86 void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
87 void mbx_list (MAILSTREAM *stream,char *ref,char *pat);
88 void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat);
89 long mbx_create (MAILSTREAM *stream,char *mailbox);
90 long mbx_delete (MAILSTREAM *stream,char *mailbox);
91 long mbx_rename (MAILSTREAM *stream,char *old,char *newname);
92 long mbx_status (MAILSTREAM *stream,char *mbx,long flags);
93 MAILSTREAM *mbx_open (MAILSTREAM *stream);
94 void mbx_close (MAILSTREAM *stream,long options);
95 void mbx_abort (MAILSTREAM *stream);
96 void mbx_flags (MAILSTREAM *stream,char *sequence,long flags);
97 char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
98 long flags);
99 long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
100 void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
101 void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
102 long mbx_ping (MAILSTREAM *stream);
103 void mbx_check (MAILSTREAM *stream);
104 long mbx_expunge (MAILSTREAM *stream,char *sequence,long options);
105 void mbx_snarf (MAILSTREAM *stream);
106 long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
107 long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
109 long mbx_parse (MAILSTREAM *stream);
110 MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok);
111 unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
112 void mbx_update_header (MAILSTREAM *stream);
113 void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags);
114 unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
115 unsigned long *size,char **hdr);
116 unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
117 long flags);
118 long mbx_flaglock (MAILSTREAM *stream);
120 /* MBX mail routines */
123 /* Driver dispatch used by MAIL */
125 DRIVER mbxdriver = {
126 "mbx", /* driver name */
127 DR_LOCAL|DR_MAIL|DR_CRLF|DR_LOCKING,
128 /* driver flags */
129 (DRIVER *) NIL, /* next driver */
130 mbx_valid, /* mailbox is valid for us */
131 mbx_parameters, /* manipulate parameters */
132 mbx_scan, /* scan mailboxes */
133 mbx_list, /* list mailboxes */
134 mbx_lsub, /* list subscribed mailboxes */
135 NIL, /* subscribe to mailbox */
136 NIL, /* unsubscribe from mailbox */
137 mbx_create, /* create mailbox */
138 mbx_delete, /* delete mailbox */
139 mbx_rename, /* rename mailbox */
140 mail_status_default, /* status of mailbox */
141 mbx_open, /* open mailbox */
142 mbx_close, /* close mailbox */
143 mbx_flags, /* fetch message "fast" attributes */
144 mbx_flags, /* fetch message flags */
145 NIL, /* fetch overview */
146 NIL, /* fetch message envelopes */
147 mbx_header, /* fetch message header */
148 mbx_text, /* fetch message body */
149 NIL, /* fetch partial message text */
150 NIL, /* unique identifier */
151 NIL, /* message number */
152 mbx_flag, /* modify flags */
153 mbx_flagmsg, /* per-message modify flags */
154 NIL, /* search for message based on criteria */
155 NIL, /* sort messages */
156 NIL, /* thread messages */
157 mbx_ping, /* ping mailbox to see if still alive */
158 mbx_check, /* check for new messages */
159 mbx_expunge, /* expunge deleted messages */
160 mbx_copy, /* copy messages to another mailbox */
161 mbx_append, /* append string message to mailbox */
162 NIL, /* garbage collect stream */
163 NIL /* renew stream */
166 /* prototype stream */
167 MAILSTREAM mbxproto = {&mbxdriver};
169 /* MBX mail validate mailbox
170 * Accepts: mailbox name
171 * Returns: our driver if name is valid, NIL otherwise
174 DRIVER *mbx_valid (char *name)
176 char tmp[MAILTMPLEN];
177 int fd = mbx_isvalid (NIL,name,tmp,NIL,NIL,NIL);
178 if (fd < 0) return NIL;
179 close (fd); /* don't need the fd now */
180 return &mbxdriver;
184 /* MBX mail test for valid mailbox
185 * Accepts: returned stream with valid mailbox keywords
186 * mailbox name
187 * buffer to write file name
188 * returned lock fd
189 * returned lock name
190 * RW flags or NIL for readonly
191 * Returns: file descriptor if valid, NIL otherwise
194 #define MBXISVALIDNOUID 0x1 /* RW, don't do UID action */
195 #define MBXISVALIDUID 0x2 /* RW, do UID action */
197 int mbx_isvalid (MAILSTREAM **stream,char *name,char *file,int *ld,char *lock,
198 long flags)
200 int fd,upd;
201 int ret = -1;
202 unsigned long i;
203 long j,k;
204 off_t pos;
205 char c,*s,*t,hdr[HDRSIZE];
206 struct stat sbuf;
207 struct utimbuf times;
208 int error = EINVAL; /* assume invalid argument */
209 if (ld) *ld = -1; /* initially no lock */
210 /* if file, get its status */
211 if ((s = dummy_file (file,name)) && !stat (s,&sbuf) &&
212 ((sbuf.st_mode & S_IFMT) == S_IFREG) &&
213 ((fd = open (file,(flags ? O_RDWR : O_RDONLY)|O_BINARY,NIL)) >= 0)) {
214 error = -1; /* assume bogus format */
215 if (((((j = read (fd,hdr,HDRSIZE)) == HDRSIZE) && (hdr[0] == '*')) ||
216 /* locked, set byte 0 to "*", read rest */
217 ((j < 0) && (lseek (fd,1,L_SET) == 1) &&
218 (read (fd,hdr+1,HDRSIZE-1) == (HDRSIZE-1)) && (hdr[0] = '*'))) &&
219 (hdr[1] == 'm') && (hdr[2] == 'b') && (hdr[3] == 'x') &&
220 (hdr[4] == '*') && (hdr[5] == '\015') && (hdr[6] == '\012') &&
221 isxdigit (hdr[7]) && isxdigit (hdr[8]) && isxdigit (hdr[9]) &&
222 isxdigit (hdr[10]) && isxdigit (hdr[11]) && isxdigit (hdr[12]) &&
223 isxdigit (hdr[13]) && isxdigit (hdr[14]) && isxdigit (c = hdr[15]) &&
224 isxdigit (hdr[16]) && isxdigit (hdr[17]) && isxdigit (hdr[18]) &&
225 isxdigit (hdr[19]) && isxdigit (hdr[20]) && isxdigit (hdr[21]) &&
226 isxdigit (hdr[22]) && (hdr[23] == '\015') && (hdr[24] == '\012')) {
227 ret = fd; /* mbx format */
229 if (stream) { /* lock if making a mini-stream */
230 if (flock (fd,LOCK_SH) ||
231 (flags && ((*ld = lockname (lock,file,LOCK_EX)) < 0))) ret = -1;
232 /* reread data now that locked */
233 else if (lseek (fd,0,L_SET) ||
234 (read (fd,hdr+1,HDRSIZE-1) != (HDRSIZE-1))) ret = -1;
235 else {
236 *stream = (MAILSTREAM *) memset (fs_get (sizeof (MAILSTREAM)),0,
237 sizeof (MAILSTREAM));
238 hdr[15] = '\0'; /* tie off UIDVALIDITY */
239 (*stream)->uid_validity = strtoul (hdr+7,NIL,16);
240 hdr[15] = c; /* now get UIDLAST */
241 (*stream)->uid_last = strtoul (hdr+15,NIL,16);
242 /* parse user flags */
243 for (i = 0, s = hdr + 25;
244 (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
245 i++, s = t + 2) {
246 *t = '\0'; /* tie off flag */
247 if (strlen (s) <= MAXUSERFLAG)
248 (*stream)->user_flags[i] = cpystr (s);
250 /* make sure have true UIDLAST */
251 if (flags & MBXISVALIDUID) {
252 for (upd = NIL,pos = 2048, k = 0; pos < sbuf.st_size;
253 pos += (j + k)) {
254 /* read header for this message */
255 lseek (fd,pos,L_SET);
256 if ((j = read (fd,hdr,64)) >= 0) {
257 hdr[j] = '\0';
258 if ((s = strchr (hdr,'\015')) && (s[1] == '\012')) {
259 *s = '\0';
260 k = s + 2 - hdr;
261 if ((s = strchr (hdr,',')) && (j = strtol (s+1,&s,10)) &&
262 (*s == ';') && (s = strchr (s+1,'-'))) {
263 /* get UID if there is any */
264 i = strtoul (++s,&t,16);
265 if (!*t && (t == (s + 8)) && (i <= (*stream)->uid_last)) {
266 if (!i) {
267 lseek (fd,pos + s - hdr,L_SET);
268 sprintf (hdr,"%08lx",++(*stream)->uid_last);
269 write (fd,hdr,8);
270 upd = T;
272 continue;
276 ret = -1; /* error, give up */
277 *stream = mail_close (*stream);
278 pos = sbuf.st_size + 1;
279 j = k = 0;
283 if (upd) { /* need to update hdr with new UIDLAST? */
284 lseek (fd,15,L_SET);
285 sprintf (hdr,"%08lx",(*stream)->uid_last);
286 write (fd,hdr,8);
292 if (ret != fd) close (fd); /* close the file */
293 else lseek (fd,0,L_SET); /* else rewind to start */
294 /* \Marked status? */
295 if (sbuf.st_ctime > sbuf.st_atime) {
296 /* preserve atime and mtime */
297 times.actime = sbuf.st_atime;
298 times.modtime = sbuf.st_mtime;
299 utime (file,&times); /* set the times */
302 /* in case INBOX but not mbx format */
303 else if (((error = errno) == ENOENT) && !compare_cstring (name,"INBOX"))
304 error = -1;
305 if ((ret < 0) && ld && (*ld >= 0)) {
306 unlockfd (*ld,lock);
307 *ld = -1;
309 errno = error; /* return as last error */
310 return ret; /* return what we should */
313 /* MBX manipulate driver parameters
314 * Accepts: function code
315 * function-dependent value
316 * Returns: function-dependent return value
319 void *mbx_parameters (long function,void *value)
321 void *ret = NIL;
322 switch ((int) function) {
323 case SET_ONETIMEEXPUNGEATPING:
324 if (value) ((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
325 case GET_ONETIMEEXPUNGEATPING:
326 if (value) ret = (void *)
327 (((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
328 break;
330 return ret;
334 /* MBX mail scan mailboxes
335 * Accepts: mail stream
336 * reference
337 * pattern to search
338 * string to scan
341 void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
343 if (stream) dummy_scan (NIL,ref,pat,contents);
347 /* MBX mail list mailboxes
348 * Accepts: mail stream
349 * reference
350 * pattern to search
353 void mbx_list (MAILSTREAM *stream,char *ref,char *pat)
355 if (stream) dummy_list (NIL,ref,pat);
359 /* MBX mail list subscribed mailboxes
360 * Accepts: mail stream
361 * reference
362 * pattern to search
365 void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat)
367 if (stream) dummy_lsub (NIL,ref,pat);
370 /* MBX mail create mailbox
371 * Accepts: MAIL stream
372 * mailbox name to create
373 * Returns: T on success, NIL on failure
376 long mbx_create (MAILSTREAM *stream,char *mailbox)
378 char *s,mbx[MAILTMPLEN],tmp[HDRSIZE];
379 long ret = NIL;
380 int i,fd;
381 if (!(s = dummy_file (mbx,mailbox))) {
382 sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
383 mm_log (mbx,ERROR);
385 /* create underlying file */
386 else if (dummy_create (stream,s)) {
387 /* done if made directory */
388 if ((s = strrchr (s,'\\')) && !s[1]) return T;
389 if ((fd = open (mbx,O_WRONLY|O_BINARY,NIL)) < 0) {
390 sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
391 mm_log (tmp,ERROR);
392 unlink (mbx); /* delete the file */
394 else {
395 memset (tmp,'\0',HDRSIZE);/* initialize header */
396 sprintf (s = tmp,"*mbx*\015\012%08lx00000000\015\012",
397 (unsigned long) time (0));
398 for (i = 0; i < NUSERFLAGS; ++i)
399 sprintf (s += strlen (s),"%s\015\012",
400 (stream && stream->user_flags[i]) ? stream->user_flags[i] :
401 "");
402 if (write (fd,tmp,HDRSIZE) != HDRSIZE) {
403 sprintf (tmp,"Can't initialize mailbox node %.80s: %s",
404 mbx,strerror (errno));
405 mm_log (tmp,ERROR);
406 unlink (mbx); /* delete the file */
408 else ret = T; /* success */
409 close (fd); /* close file */
412 return ret;
416 /* MBX mail delete mailbox
417 * Accepts: MAIL stream
418 * mailbox name to delete
419 * Returns: T on success, NIL on failure
422 long mbx_delete (MAILSTREAM *stream,char *mailbox)
424 return mbx_rename (stream,mailbox,NIL);
427 /* MBX mail rename mailbox
428 * Accepts: MAIL stream
429 * old mailbox name
430 * new mailbox name (or NIL for delete)
431 * Returns: T on success, NIL on failure
434 long mbx_rename (MAILSTREAM *stream,char *old,char *newname)
436 long ret = LONGT;
437 char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
438 int fd,ld;
439 struct stat sbuf;
440 if (!dummy_file (file,old) ||
441 (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
442 ((s = strrchr (tmp,'\\')) && !s[1])))) {
443 sprintf (tmp,newname ?
444 "Can't rename mailbox %.80s to %.80s: invalid name" :
445 "Can't delete mailbox %.80s: invalid name",
446 old,newname);
447 mm_log (tmp,ERROR);
448 return NIL;
450 else if ((fd = open (file,O_RDWR|O_BINARY,NIL)) < 0) {
451 sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
452 mm_log (tmp,ERROR);
453 return NIL;
455 /* get parse/append permission */
456 if ((ld = lockname (lock,file,LOCK_EX)) < 0) {
457 mm_log ("Unable to lock rename mailbox",ERROR);
458 return NIL;
460 /* lock out other users */
461 if (flock (fd,LOCK_EX|LOCK_NB)) {
462 close (fd); /* couldn't lock, give up on it then */
463 sprintf (tmp,"Mailbox %.80s is in use by another process",old);
464 mm_log (tmp,ERROR);
465 unlockfd (ld,lock); /* release exclusive parse/append permission */
466 return NIL;
469 if (newname) { /* want rename? */
470 /* found superior to destination name? */
471 if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
472 ((tmp[1] != ':') || (s != tmp + 2))) {
473 c = s[1]; /* remember character after delimiter */
474 *s = s[1] = '\0'; /* tie off name at delimiter */
475 /* name doesn't exist, create it */
476 if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
477 *s = '\\'; /* restore delimiter */
478 if (!dummy_create (stream,tmp)) ret = NIL;
480 else *s = '\\'; /* restore delimiter */
481 s[1] = c; /* restore character after delimiter */
483 flock (fd,LOCK_UN); /* release lock on the file */
484 close (fd); /* pacify NTFS */
485 /* rename the file */
486 if (ret && rename (file,tmp)) {
487 sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
488 strerror (errno));
489 mm_log (tmp,ERROR);
490 ret = NIL; /* set failure */
493 else {
494 flock (fd,LOCK_UN); /* release lock on the file */
495 close (fd); /* pacify NTFS */
496 if (unlink (file)) {
497 sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
498 mm_log (tmp,ERROR);
499 ret = NIL; /* set failure */
502 unlockfd (ld,lock); /* release exclusive parse/append permission */
503 /* recreate file if renamed INBOX */
504 if (ret && !compare_cstring (old,"INBOX")) mbx_create (NIL,"INBOX");
505 return ret; /* return success */
508 /* MBX mail open
509 * Accepts: stream to open
510 * Returns: stream on success, NIL on failure
513 MAILSTREAM *mbx_open (MAILSTREAM *stream)
515 int fd,ld;
516 short silent;
517 char tmp[MAILTMPLEN];
518 if (!stream) return &mbxproto;/* return prototype for OP_PROTOTYPE call */
519 if (stream->local) fatal ("mbx recycle stream");
520 /* canonicalize the mailbox name */
521 if (!dummy_file (tmp,stream->mailbox)) {
522 sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
523 mm_log (tmp,ERROR);
525 if (stream->rdonly ||
526 (fd = open (tmp,O_RDWR|O_BINARY,NIL)) < 0) {
527 if ((fd = open (tmp,O_RDONLY|O_BINARY,NIL)) < 0) {
528 sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
529 mm_log (tmp,ERROR);
530 return NIL;
532 else if (!stream->rdonly) { /* got it, but readonly */
533 mm_log ("Can't get write access to mailbox, access is readonly",WARN);
534 stream->rdonly = T;
538 stream->local = memset (fs_get (sizeof (MBXLOCAL)),NIL,sizeof (MBXLOCAL));
539 LOCAL->fd = fd; /* bind the file */
540 LOCAL->ld = -1; /* no flaglock */
541 LOCAL->buf = (char *) fs_get (CHUNKSIZE);
542 LOCAL->buflen = CHUNKSIZE - 1;
543 /* note if an INBOX or not */
544 stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
545 fs_give ((void **) &stream->mailbox);
546 stream->mailbox = cpystr (tmp);
547 /* get parse/append permission */
548 if ((ld = lockname (tmp,stream->mailbox,LOCK_EX)) < 0) {
549 mm_log ("Unable to lock open mailbox",ERROR);
550 return NIL;
552 flock (LOCAL->fd,LOCK_SH); /* lock the file */
553 unlockfd (ld,tmp); /* release shared parse permission */
554 LOCAL->filesize = HDRSIZE; /* initialize parsed file size */
555 LOCAL->filetime = 0; /* time not set up yet */
556 LOCAL->expok = LOCAL->flagcheck = NIL;
557 stream->sequence++; /* bump sequence number */
558 /* parse mailbox */
559 stream->nmsgs = stream->recent = 0;
560 silent = stream->silent; /* defer events */
561 stream->silent = T;
562 if (mbx_ping (stream) && !stream->nmsgs)
563 mm_log ("Mailbox is empty",(long) NIL);
564 stream->silent = silent; /* now notify upper level */
565 mail_exists (stream,stream->nmsgs);
566 mail_recent (stream,stream->recent);
567 if (!LOCAL) return NIL; /* failure if stream died */
568 stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
569 stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
570 stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
571 stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ?
572 NIL : T; /* can we create new user flags? */
573 return stream; /* return stream to caller */
576 /* MBX mail close
577 * Accepts: MAIL stream
578 * close options
581 void mbx_close (MAILSTREAM *stream,long options)
583 if (stream && LOCAL) { /* only if a file is open */
584 int silent = stream->silent;
585 stream->silent = T; /* note this stream is dying */
586 /* do an expunge if requested */
587 if (options & CL_EXPUNGE) mbx_expunge (stream,NIL,NIL);
588 else { /* otherwise do a checkpoint to purge */
589 LOCAL->expok = T; /* possible expunged messages */
590 mbx_ping (stream);
592 stream->silent = silent; /* restore previous status */
593 mbx_abort (stream);
598 /* MBX mail abort stream
599 * Accepts: MAIL stream
602 void mbx_abort (MAILSTREAM *stream)
604 if (stream && LOCAL) { /* only if a file is open */
605 flock (LOCAL->fd,LOCK_UN); /* unlock local file */
606 close (LOCAL->fd); /* close the local file */
607 /* free local text buffer */
608 if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
609 /* nuke the local data */
610 fs_give ((void **) &stream->local);
611 stream->dtb = NIL; /* log out the DTB */
616 /* MBX mail fetch flags
617 * Accepts: MAIL stream
618 * sequence
619 * option flags
620 * Sniffs at file to see if some other process changed the flags
623 void mbx_flags (MAILSTREAM *stream,char *sequence,long flags)
625 MESSAGECACHE *elt;
626 unsigned long i;
627 if (mbx_ping (stream) && /* ping mailbox, get new status for messages */
628 ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
629 mail_sequence (stream,sequence)))
630 for (i = 1; i <= stream->nmsgs; i++)
631 if ((elt = mail_elt (stream,i))->sequence && !elt->valid)
632 mbx_elt (stream,i,NIL);
635 /* MBX mail fetch message header
636 * Accepts: MAIL stream
637 * message # to fetch
638 * pointer to returned header text length
639 * option flags
640 * Returns: message header in RFC822 format
643 char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
644 long flags)
646 unsigned long i;
647 char *s;
648 *length = 0; /* default to empty */
649 if (flags & FT_UID) return "";/* UID call "impossible" */
650 /* get header position, possibly header */
651 i = mbx_hdrpos (stream,msgno,length,&s);
652 if (!s) { /* mbx_hdrpos() returned header? */
653 lseek (LOCAL->fd,i,L_SET); /* no, get to header position */
654 /* is buffer big enough? */
655 if (*length > LOCAL->buflen) {
656 fs_give ((void **) &LOCAL->buf);
657 LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
659 /* slurp the data */
660 read (LOCAL->fd,s = LOCAL->buf,*length);
662 s[*length] = '\0'; /* tie off string */
663 return s;
666 /* MBX mail fetch message text (body only)
667 * Accepts: MAIL stream
668 * message # to fetch
669 * pointer to returned header text length
670 * option flags
671 * Returns: T on success, NIL on failure
674 long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
676 FDDATA d;
677 unsigned long i,j;
678 MESSAGECACHE *elt;
679 /* UID call "impossible" */
680 if (flags & FT_UID) return NIL;
681 /* get message status */
682 elt = mbx_elt (stream,msgno,NIL);
683 /* if message not seen */
684 if (!(flags & FT_PEEK) && !elt->seen && mbx_flaglock (stream)) {
685 elt->seen = T; /* mark message as seen */
686 /* recalculate status */
687 mbx_update_status (stream,msgno,NIL);
688 mm_flags (stream,msgno);
689 /* update flags */
690 mbx_flag (stream,NIL,NIL,NIL);
692 if (!LOCAL) return NIL; /* mbx_flaglock() could have aborted */
693 /* find header position */
694 i = mbx_hdrpos (stream,msgno,&j,NIL);
695 d.fd = LOCAL->fd; /* set up file descriptor */
696 d.pos = i + j;
697 d.chunk = LOCAL->buf; /* initial buffer chunk */
698 d.chunksize = CHUNKSIZE;
699 INIT (bs,fd_string,&d,elt->rfc822_size - j);
700 return LONGT; /* success */
703 /* MBX mail modify flags
704 * Accepts: MAIL stream
705 * sequence
706 * flag(s)
707 * option flags
708 * Unlocks flag lock
711 void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
713 struct utimbuf times;
714 struct stat sbuf;
715 /* make sure the update takes */
716 if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld >= 0)) {
717 fsync (LOCAL->fd);
718 fstat (LOCAL->fd,&sbuf); /* get current write time */
719 times.modtime = LOCAL->filetime = sbuf.st_mtime;
720 /* update header */
721 if ((LOCAL->ffuserflag < NUSERFLAGS) &&
722 stream->user_flags[LOCAL->ffuserflag]) mbx_update_header (stream);
723 times.actime = time (0); /* make sure read comes after all that */
724 utime (stream->mailbox,&times);
726 if (LOCAL->ld >= 0) { /* unlock now */
727 unlockfd (LOCAL->ld,LOCAL->lock);
728 LOCAL->ld = -1;
733 /* MBX mail per-message modify flags
734 * Accepts: MAIL stream
735 * message cache element
738 void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
740 if (mbx_flaglock (stream)) mbx_update_status (stream,elt->msgno,NIL);
743 /* MBX mail ping mailbox
744 * Accepts: MAIL stream
745 * Returns: T if stream still alive, NIL if not
748 long mbx_ping (MAILSTREAM *stream)
750 unsigned long i,pos;
751 long ret = NIL;
752 int ld;
753 char lock[MAILTMPLEN];
754 MESSAGECACHE *elt;
755 struct stat sbuf;
756 if (stream && LOCAL) { /* only if stream already open */
757 ret = LONGT; /* assume OK */
758 fstat (LOCAL->fd,&sbuf); /* get current file poop */
759 /* allow expunge if permitted at ping */
760 if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
761 /* if external modification */
762 if (LOCAL->filetime && (LOCAL->filetime < sbuf.st_mtime))
763 LOCAL->flagcheck = T; /* upgrade to flag checking */
764 /* new mail or flagcheck handling needed? */
765 if (((sbuf.st_size - LOCAL->filesize) || LOCAL->flagcheck ||
766 !stream->nmsgs) &&
767 ((ld = lockname (lock,stream->mailbox,LOCK_EX)) >= 0)) {
768 if (!LOCAL->flagcheck) ret = mbx_parse (stream);
769 /* sweep mailbox for changed message status */
770 else if (ret = mbx_parse (stream)) {
771 unsigned long recent = 0;
772 LOCAL->filetime = sbuf.st_mtime;
773 for (i = 1; i <= stream->nmsgs; )
774 if (elt = mbx_elt (stream,i,LOCAL->expok)) {
775 if (elt->recent) ++recent;
776 ++i;
778 mail_recent (stream,recent);
779 LOCAL->flagcheck = NIL; /* got all the updates */
781 unlockfd (ld,lock); /* release shared parse/append permission */
783 if (ret) { /* must still be alive */
784 if (!LOCAL->expunged) /* look for holes if none known yet */
785 for (i = 1, pos = HDRSIZE;
786 !LOCAL->expunged && (i <= stream->nmsgs);
787 i++, pos += elt->private.special.text.size + elt->rfc822_size)
788 if ((elt = mail_elt (stream,i))->private.special.offset != pos)
789 LOCAL->expunged = T;/* found a hole */
790 /* burp any holes */
791 if (LOCAL->expunged && !stream->rdonly) {
792 if (mbx_rewrite (stream,&i,NIL)) fatal ("expunge on check");
793 if (i) { /* any space reclaimed? */
794 LOCAL->expunged = NIL;/* no more pending expunge */
795 sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",i);
796 mm_log (LOCAL->buf,(long) NIL);
799 LOCAL->expok = NIL; /* no more expok */
802 return ret; /* return result of the parse */
805 /* MBX mail check mailbox (reparses status too)
806 * Accepts: MAIL stream
809 void mbx_check (MAILSTREAM *stream)
811 if (LOCAL) LOCAL->expok = T; /* mark that a check is desired */
812 if (mbx_ping (stream)) mm_log ("Check completed",(long) NIL);
816 /* MBX mail expunge mailbox
817 * Accepts: MAIL stream
818 * sequence to expunge if non-NIL
819 * expunge options
820 * Returns: T if success, NIL if failure
823 long mbx_expunge (MAILSTREAM *stream,char *sequence,long options)
825 long ret;
826 unsigned long nexp,reclaimed;
827 if (ret = sequence ? ((options & EX_UID) ?
828 mail_uid_sequence (stream,sequence) :
829 mail_sequence (stream,sequence)) : LONGT) {
830 if (!mbx_ping (stream)); /* do nothing if stream dead */
831 else if (stream->rdonly) /* won't do on readonly files! */
832 mm_log ("Expunge ignored on readonly mailbox",WARN);
833 /* if expunged any messages */
834 else if (nexp = mbx_rewrite (stream,&reclaimed,sequence ? -1 : 1)) {
835 sprintf (LOCAL->buf,"Expunged %lu messages",nexp);
836 mm_log (LOCAL->buf,(long) NIL);
838 else if (reclaimed) { /* or if any prior expunged space reclaimed */
839 sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
840 mm_log (LOCAL->buf,(long) NIL);
842 else mm_log ("No messages deleted, so no update needed",(long) NIL);
844 return ret;
847 /* MBX mail copy message(s)
848 * Accepts: MAIL stream
849 * sequence
850 * destination mailbox
851 * copy options
852 * Returns: T if success, NIL if failed
855 long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
857 struct stat sbuf;
858 struct utimbuf times;
859 MESSAGECACHE *elt;
860 unsigned long i,j,k,m;
861 long ret = LONGT;
862 int fd,ld;
863 char *s,*t,file[MAILTMPLEN],lock[MAILTMPLEN];
864 mailproxycopy_t pc =
865 (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
866 copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
867 SEARCHSET *source = cu ? mail_newsearchset () : NIL;
868 SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
869 MAILSTREAM *dstream = NIL;
870 if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
871 mail_sequence (stream,sequence))) return NIL;
872 /* make sure valid mailbox */
873 if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
874 cu ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
875 switch (errno) {
876 case ENOENT: /* no such file? */
877 mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
878 return NIL;
879 case EACCES: /* file protected */
880 sprintf (LOCAL->buf,"Can't access destination: %.80s",mailbox);
881 MM_LOG (LOCAL->buf,ERROR);
882 return NIL;
883 case EINVAL:
884 if (pc) return (*pc) (stream,sequence,mailbox,options);
885 sprintf (LOCAL->buf,"Invalid MBX-format mailbox name: %.80s",mailbox);
886 mm_log (LOCAL->buf,ERROR);
887 return NIL;
888 default:
889 if (pc) return (*pc) (stream,sequence,mailbox,options);
890 sprintf (LOCAL->buf,"Not a MBX-format mailbox: %.80s",mailbox);
891 mm_log (LOCAL->buf,ERROR);
892 return NIL;
894 /* got file? */
895 if ((fd = open (dummy_file (file,mailbox),O_RDWR|O_CREAT|O_BINARY,
896 S_IREAD|S_IWRITE)) < 0) {
897 sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
898 mm_log (LOCAL->buf,ERROR);
899 return NIL;
901 mm_critical (stream); /* go critical */
902 fstat (fd,&sbuf); /* get current file size */
903 lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
905 /* for each requested message */
906 for (i = 1; ret && (i <= stream->nmsgs); i++)
907 if ((elt = mail_elt (stream,i))->sequence) {
908 lseek (LOCAL->fd,elt->private.special.offset +
909 elt->private.special.text.size,L_SET);
910 mail_date(LOCAL->buf,elt);/* build target header */
911 /* get target keyword mask */
912 for (j = elt->user_flags, k = 0; j; )
913 if (s = stream->user_flags[find_rightmost_bit (&j)])
914 for (m = 0; (m < NUSERFLAGS) && (t = dstream->user_flags[m]); m++)
915 if (!compare_cstring (s,t) && (k |= 1 << m)) break;
916 sprintf (LOCAL->buf+strlen(LOCAL->buf),",%lu;%08lx%04x-%08lx\015\012",
917 elt->rfc822_size,k,(unsigned)
918 ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
919 (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
920 (fDRAFT * elt->draft)),cu ? ++dstream->uid_last : 0);
921 /* write target header */
922 if (ret = (write (fd,LOCAL->buf,strlen (LOCAL->buf)) > 0)) {
923 for (k = elt->rfc822_size; ret && (j = min (k,LOCAL->buflen)); k -= j){
924 read (LOCAL->fd,LOCAL->buf,j);
925 ret = write (fd,LOCAL->buf,j) >= 0;
927 if (cu) { /* need to pass back new UID? */
928 mail_append_set (source,mail_uid (stream,i));
929 mail_append_set (dest,dstream->uid_last);
934 /* make sure all the updates take */
935 if (!(ret && (ret = !fsync (fd)))) {
936 sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
937 mm_log (LOCAL->buf,ERROR);
938 ftruncate (fd,sbuf.st_size);
940 if (cu && ret) { /* return sets if doing COPYUID */
941 (*cu) (stream,mailbox,dstream->uid_validity,source,dest);
942 lseek (fd,15,L_SET); /* update UIDLAST */
943 sprintf (LOCAL->buf,"%08lx",dstream->uid_last);
944 write (fd,LOCAL->buf,8);
946 else { /* flush any sets we may have built */
947 mail_free_searchset (&source);
948 mail_free_searchset (&dest);
950 /* set atime to now-1 if successful copy */
951 if (ret) times.actime = time (0) - 1;
952 /* else preserved \Marked status */
953 else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
954 sbuf.st_atime : time (0);
955 times.modtime = sbuf.st_mtime;/* preserve mtime */
956 utime (file,&times); /* set the times */
957 close (fd); /* close the file */
958 mm_nocritical (stream); /* release critical */
959 unlockfd (ld,lock); /* release exclusive parse/append permission */
960 /* delete all requested messages */
961 if (ret && (options & CP_MOVE) && mbx_flaglock (stream)) {
962 for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
963 /* mark message deleted */
964 mbx_elt (stream,i,NIL)->deleted = T;
965 /* recalculate status */
966 mbx_update_status (stream,i,NIL);
968 /* update flags */
969 mbx_flag (stream,NIL,NIL,NIL);
971 if (dstream != stream) mail_close (dstream);
972 return ret;
975 /* MBX mail append message from stringstruct
976 * Accepts: MAIL stream
977 * destination mailbox
978 * append callback
979 * data for callback
980 * Returns: T if append successful, else NIL
983 long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
985 struct stat sbuf;
986 int fd,ld;
987 char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
988 struct utimbuf times;
989 FILE *df;
990 MESSAGECACHE elt;
991 long f;
992 unsigned long i,uf;
993 STRING *message;
994 long ret = NIL;
995 MAILSTREAM *dstream = NIL;
996 appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
997 SEARCHSET *dst = au ? mail_newsearchset () : NIL;
998 /* make sure valid mailbox */
999 /* make sure valid mailbox */
1000 if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
1001 au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
1002 switch (errno) {
1003 case ENOENT: /* no such file? */
1004 if (compare_cstring (mailbox,"INBOX")) {
1005 mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
1006 return NIL;
1008 /* can create INBOX here */
1009 mbx_create (dstream = stream ? stream : &mbxproto,"INBOX");
1010 if ((fd = mbx_isvalid (&dstream,mailbox,file,&ld,lock,
1011 au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
1012 break;
1013 case EACCES: /* file protected */
1014 sprintf (tmp,"Can't access destination: %.80s",mailbox);
1015 MM_LOG (tmp,ERROR);
1016 return NIL;
1017 case EINVAL:
1018 sprintf (tmp,"Invalid MBX-format mailbox name: %.80s",mailbox);
1019 mm_log (tmp,ERROR);
1020 return NIL;
1021 default:
1022 sprintf (tmp,"Not a MBX-format mailbox: %.80s",mailbox);
1023 mm_log (tmp,ERROR);
1024 return NIL;
1027 /* get first message */
1028 if (!(*af) (dstream,data,&flags,&date,&message)) close (fd);
1029 else if (!(df = fdopen (fd,"r+b"))) {
1030 MM_LOG ("Unable to reopen append mailbox",ERROR);
1031 close (fd);
1033 else {
1034 mm_critical (dstream); /* go critical */
1035 fstat (fd,&sbuf); /* get current file size */
1036 fseek (df,sbuf.st_size,SEEK_SET);
1037 errno = 0;
1038 for (ret = LONGT; ret && message; ) {
1039 if (!SIZE (message)) { /* guard against zero-length */
1040 mm_log ("Append of zero-length message",ERROR);
1041 ret = NIL;
1042 break;
1044 f = mail_parse_flags (dstream,flags,&uf);
1045 if (date) { /* parse date if given */
1046 if (!mail_parse_date (&elt,date)) {
1047 sprintf (tmp,"Bad date in append: %.80s",date);
1048 mm_log (tmp,ERROR);
1049 ret = NIL; /* mark failure */
1050 break;
1052 mail_date (tmp,&elt); /* write preserved date */
1054 else internal_date (tmp); /* get current date in IMAP format */
1055 /* write header */
1056 if (fprintf (df,"%s,%lu;%08lx%04lx-%08lx\015\012",tmp,i = SIZE (message),
1057 uf,(unsigned long) f,au ? ++dstream->uid_last : 0) < 0)
1058 ret = NIL;
1059 else { /* write message */
1060 size_t j;
1061 if (!message->cursize) SETPOS (message,GETPOS (message));
1062 while (i && (j = fwrite (message->curpos,1,message->cursize,df))) {
1063 i -= j;
1064 SETPOS (message,GETPOS (message) + j);
1066 /* get next message */
1067 if (i || !(*af) (dstream,data,&flags,&date,&message)) ret = NIL;
1068 else if (au) mail_append_set (dst,dstream->uid_last);
1072 /* if error... */
1073 if (!ret || (fflush (df) == EOF)) {
1074 /* revert file */
1075 ftruncate (fd,sbuf.st_size);
1076 close (fd); /* make sure fclose() doesn't corrupt us */
1077 if (errno) {
1078 sprintf (tmp,"Message append failed: %s",strerror (errno));
1079 mm_log (tmp,ERROR);
1081 ret = NIL;
1083 if (au && ret) { /* return sets if doing APPENDUID */
1084 (*au) (mailbox,dstream->uid_validity,dst);
1085 fseek (df,15,SEEK_SET); /* update UIDLAST */
1086 fprintf (df,"%08lx",dstream->uid_last);
1088 else mail_free_searchset (&dst);
1089 if (ret) times.actime = time (0) - 1;
1090 /* else preserve \Marked status */
1091 else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
1092 sbuf.st_atime : time (0);
1093 /* preserve mtime */
1094 times.modtime = sbuf.st_mtime;
1095 utime (file,&times); /* set the times */
1096 fclose (df); /* close the file */
1097 mm_nocritical (dstream); /* release critical */
1099 unlockfd (ld,lock); /* release exclusive parse/append permission */
1100 if (dstream != stream) mail_close (dstream);
1101 return ret;
1104 /* Internal routines */
1107 /* MBX mail parse mailbox
1108 * Accepts: MAIL stream
1109 * Returns: T if parse OK
1110 * NIL if failure, stream aborted
1113 long mbx_parse (MAILSTREAM *stream)
1115 struct stat sbuf;
1116 MESSAGECACHE *elt = NIL;
1117 unsigned char c,*s,*t,*x;
1118 char tmp[MAILTMPLEN];
1119 unsigned long i,j,k,m;
1120 off_t curpos = LOCAL->filesize;
1121 unsigned long nmsgs = stream->nmsgs;
1122 unsigned long recent = stream->recent;
1123 unsigned long lastuid = 0;
1124 short dirty = NIL;
1125 short added = NIL;
1126 short silent = stream->silent;
1127 short uidwarn = T;
1128 fstat (LOCAL->fd,&sbuf); /* get status */
1129 if (sbuf.st_size < curpos) { /* sanity check */
1130 sprintf (tmp,"Mailbox shrank from %lu to %lu!",
1131 (unsigned long) curpos,(unsigned long) sbuf.st_size);
1132 mm_log (tmp,ERROR);
1133 mbx_abort (stream);
1134 return NIL;
1136 lseek (LOCAL->fd,0,L_SET); /* rewind file */
1137 /* read internal header */
1138 read (LOCAL->fd,LOCAL->buf,HDRSIZE);
1139 LOCAL->buf[HDRSIZE] = '\0'; /* tie off header */
1140 c = LOCAL->buf[15]; /* save first character of last UID */
1141 LOCAL->buf[15] = '\0';
1142 /* parse UID validity */
1143 stream->uid_validity = strtoul (LOCAL->buf + 7,NIL,16);
1144 LOCAL->buf[15] = c; /* restore first character of last UID */
1145 /* parse last UID */
1146 i = strtoul (LOCAL->buf + 15,NIL,16);
1147 stream->uid_last = stream->rdonly ? max (i,stream->uid_last) : i;
1148 /* parse user flags */
1149 for (i = 0, s = LOCAL->buf + 25;
1150 (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
1151 i++, s = t + 2) {
1152 *t = '\0'; /* tie off flag */
1153 if (!stream->user_flags[i] && (strlen (s) <= MAXUSERFLAG))
1154 stream->user_flags[i] = cpystr (s);
1156 LOCAL->ffuserflag = (int) i; /* first free user flag */
1158 stream->silent = T; /* don't pass up mm_exists() events yet */
1159 while (sbuf.st_size - curpos){/* while there is stuff to parse */
1160 /* get to that position in the file */
1161 lseek (LOCAL->fd,curpos,L_SET);
1162 if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
1163 sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
1164 (unsigned long) curpos,(unsigned long) sbuf.st_size,
1165 i ? strerror (errno) : "no data read");
1166 mm_log (tmp,ERROR);
1167 mbx_abort (stream);
1168 return NIL;
1170 LOCAL->buf[i] = '\0'; /* tie off buffer just in case */
1171 if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
1172 sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %.80s",
1173 (unsigned long) curpos,i,(char *) LOCAL->buf);
1174 mm_log (tmp,ERROR);
1175 mbx_abort (stream);
1176 return NIL;
1178 *s = '\0'; /* tie off header line */
1179 i = (s + 2) - LOCAL->buf; /* note start of text offset */
1180 if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
1181 sprintf (tmp,"Unable to parse internal header at %lu: %.80s",
1182 (unsigned long) curpos,(char *) LOCAL->buf);
1183 mm_log (tmp,ERROR);
1184 mbx_abort (stream);
1185 return NIL;
1187 if (!(isxdigit (t[1]) && isxdigit (t[2]) && isxdigit (t[3]) &&
1188 isxdigit (t[4]) && isxdigit (t[5]) && isxdigit (t[6]) &&
1189 isxdigit (t[7]) && isxdigit (t[8]) && isxdigit (t[9]) &&
1190 isxdigit (t[10]) && isxdigit (t[11]) && isxdigit (t[12]))) {
1191 sprintf (tmp,"Unable to parse message flags at %lu: %.80s",
1192 (unsigned long) curpos,(char *) LOCAL->buf);
1193 mm_log (tmp,ERROR);
1194 mbx_abort (stream);
1195 return NIL;
1197 if ((t[13] != '-') || t[22] ||
1198 !(isxdigit (t[14]) && isxdigit (t[15]) && isxdigit (t[16]) &&
1199 isxdigit (t[17]) && isxdigit (t[18]) && isxdigit (t[19]) &&
1200 isxdigit (t[20]) && isxdigit (t[21]))) {
1201 sprintf (tmp,"Unable to parse message UID at %lu: %.80s",
1202 (unsigned long) curpos,(char *) LOCAL->buf);
1203 mm_log (tmp,ERROR);
1204 mbx_abort (stream);
1205 return NIL;
1208 *s++ = '\0'; *t++ = '\0'; /* break up fields */
1209 /* get message size */
1210 if (!(j = strtoul (s,(char **) &x,10)) && (!(x && *x))) {
1211 sprintf (tmp,"Unable to parse message size at %lu: %.80s,%.80s;%.80s",
1212 (unsigned long) curpos,(char *) LOCAL->buf,(char *) s,
1213 (char *) t);
1214 mm_log (tmp,ERROR);
1215 mbx_abort (stream);
1216 return NIL;
1218 /* make sure didn't run off end of file */
1219 if (((off_t) (curpos + i + j)) > sbuf.st_size) {
1220 sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
1221 (unsigned long) curpos,(unsigned long) (curpos + i + j),
1222 (unsigned long) sbuf.st_size);
1223 mm_log (tmp,ERROR);
1224 mbx_abort (stream);
1225 return NIL;
1227 /* parse UID */
1228 if ((m = strtoul (t+13,NIL,16)) &&
1229 ((m <= lastuid) || (m > stream->uid_last))) {
1230 if (uidwarn) {
1231 sprintf (tmp,"Invalid UID %08lx in message %lu, rebuilding UIDs",
1232 m,nmsgs+1);
1233 mm_log (tmp,WARN);
1234 uidwarn = NIL;
1235 /* restart UID validity */
1236 stream->uid_validity = (unsigned long) time (0);
1238 m = 0; /* lose this UID */
1239 dirty = T; /* mark dirty, set new lastuid */
1240 stream->uid_last = lastuid;
1243 t[12] = '\0'; /* parse system flags */
1244 if ((k = strtoul (t+8,NIL,16)) & fEXPUNGED) {
1245 if (m) lastuid = m; /* expunge message, update last UID seen */
1246 else { /* no UID assigned? */
1247 lastuid = ++stream->uid_last;
1248 dirty = T;
1251 else { /* not expunged, swell the cache */
1252 added = T; /* note that a new message was added */
1253 mail_exists (stream,++nmsgs);
1254 /* instantiate an elt for this message */
1255 (elt = mail_elt (stream,nmsgs))->valid = T;
1256 /* parse the date */
1257 if (!mail_parse_date (elt,LOCAL->buf)) {
1258 sprintf (tmp,"Unable to parse message date at %lu: %.80s",
1259 (unsigned long) curpos,(char *) LOCAL->buf);
1260 mm_log (tmp,ERROR);
1261 mbx_abort (stream);
1262 return NIL;
1264 /* note file offset of header */
1265 elt->private.special.offset = curpos;
1266 /* and internal header size */
1267 elt->private.special.text.size = i;
1268 /* header size not known yet */
1269 elt->private.msg.header.text.size = 0;
1270 elt->rfc822_size = j; /* note message size */
1271 /* calculate system flags */
1272 if (k & fSEEN) elt->seen = T;
1273 if (k & fDELETED) elt->deleted = T;
1274 if (k & fFLAGGED) elt->flagged = T;
1275 if (k & fANSWERED) elt->answered = T;
1276 if (k & fDRAFT) elt->draft = T;
1277 t[8] = '\0'; /* get user flags value */
1278 elt->user_flags = strtoul (t,NIL,16);
1279 /* UID already assigned? */
1280 if (!(elt->private.uid = m) || !(k & fOLD)) {
1281 elt->recent = T; /* no, mark as recent */
1282 ++recent; /* count up a new recent message */
1283 dirty = T; /* and must rewrite header */
1284 /* assign new UID */
1285 if (!elt->private.uid) elt->private.uid = ++stream->uid_last;
1286 mbx_update_status (stream,elt->msgno,NIL);
1288 /* update last parsed UID */
1289 lastuid = elt->private.uid;
1291 curpos += i + j; /* update position */
1294 if (dirty && !stream->rdonly){/* update header */
1295 mbx_update_header (stream);
1296 fsync (LOCAL->fd); /* make sure all the UID updates take */
1298 /* update parsed file size and time */
1299 LOCAL->filesize = sbuf.st_size;
1300 fstat (LOCAL->fd,&sbuf); /* get status again to ensure time is right */
1301 LOCAL->filetime = sbuf.st_mtime;
1302 if (added && !stream->rdonly){/* make sure atime updated */
1303 struct utimbuf times;
1304 times.actime = time (0);
1305 times.modtime = LOCAL->filetime;
1306 utime (stream->mailbox,&times);
1308 stream->silent = silent; /* can pass up events now */
1309 mail_exists (stream,nmsgs); /* notify upper level of new mailbox size */
1310 mail_recent (stream,recent); /* and of change in recent messages */
1311 return LONGT; /* return the winnage */
1314 /* MBX get cache element with status updating from file
1315 * Accepts: MAIL stream
1316 * message number
1317 * expunge OK flag
1318 * Returns: cache element
1321 MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok)
1323 MESSAGECACHE *elt = mail_elt (stream,msgno);
1324 struct { /* old flags */
1325 unsigned int seen : 1;
1326 unsigned int deleted : 1;
1327 unsigned int flagged : 1;
1328 unsigned int answered : 1;
1329 unsigned int draft : 1;
1330 unsigned long user_flags;
1331 } old;
1332 old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
1333 old.answered = elt->answered; old.draft = elt->draft;
1334 old.user_flags = elt->user_flags;
1335 /* get new flags */
1336 if (mbx_read_flags (stream,elt) && expok) {
1337 mail_expunged (stream,elt->msgno);
1338 return NIL; /* return this message was expunged */
1340 if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
1341 (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
1342 (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
1343 mm_flags (stream,msgno); /* let top level know */
1344 return elt;
1347 /* MBX read flags from file
1348 * Accepts: MAIL stream
1349 * cache element
1350 * Returns: non-NIL if message expunged
1353 unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
1355 unsigned long i;
1356 struct stat sbuf;
1357 fstat (LOCAL->fd,&sbuf); /* get status */
1358 /* make sure file size is good */
1359 if (sbuf.st_size < LOCAL->filesize) {
1360 sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag read!",
1361 (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
1362 fatal (LOCAL->buf);
1364 /* set the seek pointer */
1365 lseek (LOCAL->fd,(off_t) elt->private.special.offset +
1366 elt->private.special.text.size - 24,L_SET);
1367 /* read the new flags */
1368 if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
1369 sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
1370 fatal (LOCAL->buf);
1372 if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
1373 LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
1374 sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
1375 elt->msgno,elt->private.special.offset,
1376 elt->private.special.text.size,(char *) LOCAL->buf);
1377 fatal (LOCAL->buf+50);
1379 LOCAL->buf[13] = '\0'; /* tie off buffer */
1380 /* calculate system flags */
1381 i = strtoul (LOCAL->buf+9,NIL,16);
1382 elt->seen = i & fSEEN ? T : NIL;
1383 elt->deleted = i & fDELETED ? T : NIL;
1384 elt->flagged = i & fFLAGGED ? T : NIL;
1385 elt->answered = i & fANSWERED ? T : NIL;
1386 elt->draft = i & fDRAFT ? T : NIL;
1387 LOCAL->expunged |= i & fEXPUNGED ? T : NIL;
1388 LOCAL->buf[9] = '\0'; /* tie off flags */
1389 /* get user flags value */
1390 elt->user_flags = strtoul (LOCAL->buf+1,NIL,16);
1391 elt->valid = T; /* have valid flags now */
1392 return i & fEXPUNGED;
1395 /* MBX update header
1396 * Accepts: MAIL stream
1399 #define NTKLUDGEOFFSET 7
1401 void mbx_update_header (MAILSTREAM *stream)
1403 int i;
1404 char *s = LOCAL->buf;
1405 memset (s,'\0',HDRSIZE); /* initialize header */
1406 sprintf (s,"*mbx*\015\012%08lx%08lx\015\012",
1407 stream->uid_validity,stream->uid_last);
1408 for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i)
1409 sprintf (s += strlen (s),"%s\015\012",stream->user_flags[i]);
1410 LOCAL->ffuserflag = i; /* first free user flag */
1411 /* can we create more user flags? */
1412 stream->kwd_create = (i < NUSERFLAGS) ? T : NIL;
1413 /* write reserved lines */
1414 while (i++ < NUSERFLAGS) strcat (s,"\015\012");
1415 while (T) { /* rewind file */
1416 lseek (LOCAL->fd,NTKLUDGEOFFSET,L_SET);
1417 /* write new header */
1418 if (write (LOCAL->fd,LOCAL->buf + NTKLUDGEOFFSET,
1419 HDRSIZE - NTKLUDGEOFFSET) > 0) break;
1420 mm_notify (stream,strerror (errno),WARN);
1421 mm_diskerror (stream,errno,T);
1425 /* MBX update status string
1426 * Accepts: MAIL stream
1427 * message number
1428 * flags
1431 void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags)
1433 struct stat sbuf;
1434 MESSAGECACHE *elt = mail_elt (stream,msgno);
1435 /* readonly */
1436 if (stream->rdonly || !elt->valid) mbx_read_flags (stream,elt);
1437 else { /* readwrite */
1438 fstat (LOCAL->fd,&sbuf); /* get status */
1439 /* make sure file size is good */
1440 if (sbuf.st_size < LOCAL->filesize) {
1441 sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag update!",
1442 (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
1443 fatal (LOCAL->buf);
1445 /* set the seek pointer */
1446 lseek (LOCAL->fd,(off_t) elt->private.special.offset +
1447 elt->private.special.text.size - 24,L_SET);
1448 /* read the new flags */
1449 if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
1450 sprintf (LOCAL->buf,"Unable to read old status: %s",strerror (errno));
1451 fatal (LOCAL->buf);
1453 if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
1454 LOCAL->buf[14] = '\0'; /* tie off buffer for error message */
1455 sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
1456 elt->msgno,elt->private.special.offset,
1457 elt->private.special.text.size,(char *) LOCAL->buf);
1458 fatal (LOCAL->buf+50);
1460 /* print new flag string */
1461 sprintf (LOCAL->buf,"%08lx%04x-%08lx",elt->user_flags,(unsigned)
1462 (((elt->deleted && flags) ?
1463 fEXPUNGED : (strtoul (LOCAL->buf+9,NIL,16)) & fEXPUNGED) +
1464 (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
1465 (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
1466 (fDRAFT * elt->draft) + fOLD),elt->private.uid);
1467 while (T) { /* get to that place in the file */
1468 lseek (LOCAL->fd,(off_t) elt->private.special.offset +
1469 elt->private.special.text.size - 23,L_SET);
1470 /* write new flags and UID */
1471 if (write (LOCAL->fd,LOCAL->buf,21) > 0) break;
1472 mm_notify (stream,strerror (errno),WARN);
1473 mm_diskerror (stream,errno,T);
1478 /* MBX locate header for a message
1479 * Accepts: MAIL stream
1480 * message number
1481 * pointer to returned header size
1482 * pointer to possible returned header
1483 * Returns: position of header in file
1486 #define HDRBUFLEN 16384 /* good enough for most headers */
1487 #define SLOP 4 /* CR LF CR LF */
1489 unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
1490 unsigned long *size,char **hdr)
1492 unsigned long siz,done;
1493 long i;
1494 unsigned char *s,*t,*te;
1495 MESSAGECACHE *elt = mail_elt (stream,msgno);
1496 unsigned long ret = elt->private.special.offset +
1497 elt->private.special.text.size;
1498 if (hdr) *hdr = NIL; /* assume no header returned */
1499 /* is header size known? */
1500 if (*size = elt->private.msg.header.text.size) return ret;
1501 /* paranoia check */
1502 if (LOCAL->buflen < (HDRBUFLEN + SLOP))
1503 fatal ("LOCAL->buf smaller than HDRBUFLEN");
1504 lseek (LOCAL->fd,ret,L_SET); /* get to header position */
1505 /* read HDRBUFLEN chunks with 4 byte slop */
1506 for (done = siz = 0, s = LOCAL->buf;
1507 (i = min ((long) (elt->rfc822_size - done),(long) HDRBUFLEN)) &&
1508 (read (LOCAL->fd,s,i) == i);
1509 done += i, siz += (t - LOCAL->buf) - SLOP, s = LOCAL->buf + SLOP) {
1510 te = (t = s + i) - 12; /* calculate end of fast scan */
1511 /* fast scan for CR */
1512 for (s = LOCAL->buf; s < te;)
1513 if (((*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
1514 (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
1515 (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
1516 (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015')) &&
1517 (*s == '\012') && (*++s == '\015') && (*++s == '\012')) {
1518 *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
1519 if (hdr) *hdr = LOCAL->buf;
1520 return ret;
1522 for (te = t - 3; (s < te);) /* final character-at-a-time scan */
1523 if ((*s++ == '\015') && (*s == '\012') && (*++s == '\015') &&
1524 (*++s == '\012')) {
1525 *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
1526 if (hdr) *hdr = LOCAL->buf;
1527 return ret;
1529 if (i <= SLOP) break; /* end of data */
1530 /* slide over last 4 bytes */
1531 memmove (LOCAL->buf,t - SLOP,SLOP);
1532 hdr = NIL; /* can't return header this way */
1534 /* not found: header consumes entire message */
1535 elt->private.msg.header.text.size = *size = elt->rfc822_size;
1536 if (hdr) *hdr = LOCAL->buf; /* possibly return header too */
1537 return ret;
1540 /* MBX mail rewrite mailbox
1541 * Accepts: MAIL stream
1542 * pointer to return reclaimed size
1543 * flags (0 = no expunge, 1 = expunge deleted, -1 = expunge sequence)
1544 * Returns: number of expunged messages
1547 unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
1548 long flags)
1550 struct utimbuf times;
1551 struct stat sbuf;
1552 off_t pos,ppos;
1553 int ld;
1554 unsigned long i,j,k,m,delta;
1555 unsigned long n = *reclaimed = 0;
1556 unsigned long recent = 0;
1557 char lock[MAILTMPLEN];
1558 MESSAGECACHE *elt;
1559 /* get parse/append permission */
1560 if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0) {
1561 mm_log ("Unable to lock expunge mailbox",ERROR);
1562 return 0;
1564 fstat (LOCAL->fd,&sbuf); /* get current write time */
1565 if (LOCAL->filetime && !LOCAL->flagcheck &&
1566 (LOCAL->filetime < sbuf.st_mtime)) LOCAL->flagcheck = T;
1567 if (!mbx_parse (stream)) { /* make sure see any newly-arrived messages */
1568 unlockfd (ld,lock); /* failed?? */
1569 return 0;
1571 if (LOCAL->flagcheck) { /* sweep flags if need flagcheck */
1572 LOCAL->filetime = sbuf.st_mtime;
1573 for (i = 1; i <= stream->nmsgs; ++i) mbx_elt (stream,i,NIL);
1574 LOCAL->flagcheck = NIL;
1577 /* get exclusive access */
1578 if (!flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
1579 mm_critical (stream); /* go critical */
1580 for (i = 1,delta = 0,pos = ppos = HDRSIZE; i <= stream->nmsgs; ) {
1581 /* note if message not at predicted location */
1582 if (m = (elt = mbx_elt (stream,i,NIL))->private.special.offset - ppos) {
1583 ppos = elt->private.special.offset;
1584 *reclaimed += m; /* note reclaimed message space */
1585 delta += m; /* and as expunge delta */
1587 /* number of bytes to smash or preserve */
1588 ppos += (k = elt->private.special.text.size + elt->rfc822_size);
1589 /* if need to expunge this message*/
1590 if (flags && elt->deleted && ((flags > 0) || elt->sequence)) {
1591 delta += k; /* number of bytes to delete */
1592 mail_expunged(stream,i);/* notify upper levels */
1593 n++; /* count up one more expunged message */
1595 else { /* preserved message */
1596 i++; /* count this message */
1597 if (elt->recent) ++recent;
1598 if (delta) { /* moved, note first byte to preserve */
1599 j = elt->private.special.offset;
1600 do { /* read from source position */
1601 m = min (k,LOCAL->buflen);
1602 lseek (LOCAL->fd,j,L_SET);
1603 read (LOCAL->fd,LOCAL->buf,m);
1604 pos = j - delta; /* write to destination position */
1605 while (T) {
1606 lseek (LOCAL->fd,pos,L_SET);
1607 if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
1608 mm_notify (stream,strerror (errno),WARN);
1609 mm_diskerror (stream,errno,T);
1611 pos += m; /* new position */
1612 j += m; /* next chunk, perhaps */
1613 } while (k -= m); /* until done */
1614 /* note the new address of this text */
1615 elt->private.special.offset -= delta;
1617 /* preserved but no deleted messages yet */
1618 else pos = elt->private.special.offset + k;
1621 /* deltaed file size match position? */
1622 if (m = (LOCAL->filesize -= delta) - pos) {
1623 *reclaimed += m; /* probably an fEXPUNGED msg */
1624 LOCAL->filesize = pos; /* set correct size */
1626 /* truncate file after last message */
1627 ftruncate (LOCAL->fd,LOCAL->filesize);
1628 fsync (LOCAL->fd); /* force disk update */
1629 mm_nocritical (stream); /* release critical */
1630 flock (LOCAL->fd,LOCK_SH); /* allow sharers again */
1633 else { /* can't get exclusive */
1634 flock (LOCAL->fd,LOCK_SH); /* recover previous shared mailbox lock */
1635 /* do hide-expunge when shared */
1636 if (flags) for (i = 1; i <= stream->nmsgs; ) {
1637 if (elt = mbx_elt (stream,i,T)) {
1638 /* make the message invisible */
1639 if (elt->deleted && ((flags > 0) || elt->sequence)) {
1640 mbx_update_status (stream,elt->msgno,LONGT);
1641 /* notify upper levels */
1642 mail_expunged (stream,i);
1643 n++; /* count up one more expunged message */
1645 else {
1646 i++; /* preserved message */
1647 if (elt->recent) ++recent;
1650 else n++; /* count up one more expunged message */
1652 fsync (LOCAL->fd); /* force disk update */
1654 fstat (LOCAL->fd,&sbuf); /* get new write time */
1655 times.modtime = LOCAL->filetime = sbuf.st_mtime;
1656 times.actime = time (0); /* reset atime to now */
1657 utime (stream->mailbox,&times);
1658 unlockfd (ld,lock); /* release exclusive parse/append permission */
1659 /* notify upper level of new mailbox size */
1660 mail_exists (stream,stream->nmsgs);
1661 mail_recent (stream,recent);
1662 return n; /* return number of expunged messages */
1665 /* MBX mail lock for flag updating
1666 * Accepts: stream
1667 * Returns: T if successful, NIL if failure
1670 long mbx_flaglock (MAILSTREAM *stream)
1672 struct stat sbuf;
1673 unsigned long i;
1674 int ld;
1675 char lock[MAILTMPLEN];
1676 /* no-op if readonly or already locked */
1677 if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld < 0)) {
1678 /* lock now */
1679 if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0) return NIL;
1680 if (!LOCAL->flagcheck) { /* don't do this if flagcheck already needed */
1681 if (LOCAL->filetime) { /* know previous time? */
1682 fstat (LOCAL->fd,&sbuf);/* get current write time */
1683 if (LOCAL->filetime < sbuf.st_mtime) LOCAL->flagcheck = T;
1684 LOCAL->filetime = 0; /* don't do this test for any other messages */
1686 if (!mbx_parse (stream)) {/* parse mailbox */
1687 unlockfd (ld,lock); /* shouldn't happen */
1688 return NIL;
1690 if (LOCAL->flagcheck) /* invalidate cache if flagcheck */
1691 for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->valid = NIL;
1693 LOCAL->ld = ld; /* copy to stream for subsequent calls */
1694 memcpy (LOCAL->lock,lock,MAILTMPLEN);
1696 return LONGT;