1 /* ========================================================================
2 * Copyright 1988-2012 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
23 * Internet: MRC@CAC.Washington.EDU
25 * Date: 3 October 1995
26 * Last Edited: 21 February 2012
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.
40 extern int errno
; /* just in case */
51 /* Build parameters */
56 /* Kludge to make Cygwin happy */
62 /* MBX I/O stream local data */
64 typedef struct mbx_local
{
65 unsigned int flagcheck
: 1; /* if ping should sweep for flags */
66 unsigned int expok
: 1; /* if expunging OK in ping */
67 unsigned int expunged
: 1; /* if one or more expunged messages */
68 int fd
; /* file descriptor for I/O */
69 int ld
; /* lock file descriptor */
70 int ffuserflag
; /* first free user flag */
71 off_t filesize
; /* file size parsed */
72 time_t filetime
; /* last file time */
73 time_t lastsnarf
; /* last snarf time */
74 unsigned long lastpid
; /* PID of last writer */
75 unsigned char *buf
; /* temporary buffer */
76 unsigned long buflen
; /* current size of temporary buffer */
77 char lock
[MAILTMPLEN
]; /* buffer to write lock name */
81 /* Convenient access to local data */
83 #define LOCAL ((MBXLOCAL *) stream->local)
85 /* Function prototypes */
87 DRIVER
*mbx_valid (char *name
);
88 int mbx_isvalid (MAILSTREAM
**stream
,char *name
,char *tmp
,int *ld
,char *lock
,
90 void *mbx_parameters (long function
,void *value
);
91 void mbx_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
92 void mbx_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
93 void mbx_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
94 long mbx_create (MAILSTREAM
*stream
,char *mailbox
);
95 long mbx_delete (MAILSTREAM
*stream
,char *mailbox
);
96 long mbx_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
97 long mbx_status (MAILSTREAM
*stream
,char *mbx
,long flags
);
98 MAILSTREAM
*mbx_open (MAILSTREAM
*stream
);
99 void mbx_close (MAILSTREAM
*stream
,long options
);
100 void mbx_abort (MAILSTREAM
*stream
);
101 void mbx_flags (MAILSTREAM
*stream
,char *sequence
,long flags
);
102 char *mbx_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
104 long mbx_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
);
105 void mbx_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
);
106 void mbx_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
107 long mbx_ping (MAILSTREAM
*stream
);
108 void mbx_check (MAILSTREAM
*stream
);
109 long mbx_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
110 void mbx_snarf (MAILSTREAM
*stream
);
111 long mbx_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
);
112 long mbx_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
114 char *mbx_file (char *dst
,char *name
);
115 long mbx_parse (MAILSTREAM
*stream
);
116 MESSAGECACHE
*mbx_elt (MAILSTREAM
*stream
,unsigned long msgno
,long expok
);
117 unsigned long mbx_read_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
118 void mbx_update_header (MAILSTREAM
*stream
);
119 void mbx_update_status (MAILSTREAM
*stream
,unsigned long msgno
,long flags
);
120 unsigned long mbx_hdrpos (MAILSTREAM
*stream
,unsigned long msgno
,
121 unsigned long *size
,char **hdr
);
122 unsigned long mbx_rewrite (MAILSTREAM
*stream
,unsigned long *reclaimed
,
124 long mbx_flaglock (MAILSTREAM
*stream
);
126 /* MBX mail routines */
129 /* Driver dispatch used by MAIL */
132 "mbx", /* driver name */
133 DR_LOCAL
|DR_MAIL
|DR_CRLF
|DR_LOCKING
,
135 (DRIVER
*) NIL
, /* next driver */
136 mbx_valid
, /* mailbox is valid for us */
137 mbx_parameters
, /* manipulate parameters */
138 mbx_scan
, /* scan mailboxes */
139 mbx_list
, /* list mailboxes */
140 mbx_lsub
, /* list subscribed mailboxes */
141 NIL
, /* subscribe to mailbox */
142 NIL
, /* unsubscribe from mailbox */
143 mbx_create
, /* create mailbox */
144 mbx_delete
, /* delete mailbox */
145 mbx_rename
, /* rename mailbox */
146 mbx_status
, /* status of mailbox */
147 mbx_open
, /* open mailbox */
148 mbx_close
, /* close mailbox */
149 mbx_flags
, /* fetch message "fast" attributes */
150 mbx_flags
, /* fetch message flags */
151 NIL
, /* fetch overview */
152 NIL
, /* fetch message envelopes */
153 mbx_header
, /* fetch message header */
154 mbx_text
, /* fetch message body */
155 NIL
, /* fetch partial message text */
156 NIL
, /* unique identifier */
157 NIL
, /* message number */
158 mbx_flag
, /* modify flags */
159 mbx_flagmsg
, /* per-message modify flags */
160 NIL
, /* search for message based on criteria */
161 NIL
, /* sort messages */
162 NIL
, /* thread messages */
163 mbx_ping
, /* ping mailbox to see if still alive */
164 mbx_check
, /* check for new messages */
165 mbx_expunge
, /* expunge deleted messages */
166 mbx_copy
, /* copy messages to another mailbox */
167 mbx_append
, /* append string message to mailbox */
168 NIL
/* garbage collect stream */
171 /* prototype stream */
172 MAILSTREAM mbxproto
= {&mbxdriver
};
174 /* MBX mail validate mailbox
175 * Accepts: mailbox name
176 * Returns: our driver if name is valid, NIL otherwise
179 DRIVER
*mbx_valid (char *name
)
181 char tmp
[MAILTMPLEN
];
182 int fd
= mbx_isvalid (NIL
,name
,tmp
,NIL
,NIL
,NIL
);
183 if (fd
< 0) return NIL
;
184 close (fd
); /* don't need the fd now */
189 /* MBX mail test for valid mailbox
190 * Accepts: returned stream with valid mailbox keywords
195 * RW flags or NIL for readonly
196 * Returns: file descriptor if valid, NIL otherwise
199 #define MBXISVALIDNOUID 0x1 /* RW, don't do UID action */
200 #define MBXISVALIDUID 0x2 /* RW, do UID action */
202 int mbx_isvalid (MAILSTREAM
**stream
,char *name
,char *tmp
,int *ld
,char *lock
,
210 char c
,*s
,*t
,hdr
[HDRSIZE
];
213 int error
= EINVAL
; /* assume invalid argument */
214 if (ld
) *ld
= -1; /* initially no lock */
215 if ((s
= mbx_file (tmp
,name
)) && !stat (s
,&sbuf
) &&
216 ((fd
= open (tmp
,(flags
? O_RDWR
: O_RDONLY
)|O_BINARY
,NIL
)) >= 0)) {
217 error
= -1; /* bogus format */
218 /* I love cretinous C compilers -- don't you? */
219 if (read (fd
,hdr
,HDRSIZE
) == HDRSIZE
)
220 if ((hdr
[0] == '*') && (hdr
[1] == 'm') && (hdr
[2] == 'b') &&
221 (hdr
[3] == 'x') && (hdr
[4] == '*') && (hdr
[5] == '\015') &&
222 (hdr
[6] == '\012') && isxdigit (hdr
[7]) && isxdigit (hdr
[8]))
223 if (isxdigit (hdr
[9]) && isxdigit (hdr
[10]) && isxdigit (hdr
[11]) &&
224 isxdigit (hdr
[12]) && isxdigit (hdr
[13]) && isxdigit (hdr
[14]) &&
225 isxdigit (c
= hdr
[15]) && isxdigit (hdr
[16]))
226 if (isxdigit (hdr
[17]) && isxdigit (hdr
[18]) &&
227 isxdigit (hdr
[19]) && isxdigit (hdr
[20]) &&
228 isxdigit (hdr
[21]) && isxdigit (hdr
[22]) &&
229 (hdr
[23] == '\015') && (hdr
[24] == '\012')) {
230 ret
= fd
; /* mbx format */
232 if (stream
) { /* lock if making mini-stream */
233 if (flock (fd
,LOCK_SH
) ||
234 (flags
&& ((*ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0))) ret
= -1;
235 /* reread data now that locked */
236 else if (lseek (fd
,0,L_SET
) ||
237 (read (fd
,hdr
,HDRSIZE
) != HDRSIZE
)) ret
= -1;
239 *stream
= (MAILSTREAM
*) memset (fs_get (sizeof (MAILSTREAM
)),
240 0,sizeof (MAILSTREAM
));
241 hdr
[15] = '\0'; /* tie off UIDVALIDITY */
242 (*stream
)->uid_validity
= strtoul (hdr
+7,NIL
,16);
243 hdr
[15] = c
; /* now get UIDLAST */
244 (*stream
)->uid_last
= strtoul (hdr
+15,NIL
,16);
245 /* parse user flags */
246 for (i
= 0, s
= hdr
+ 25;
247 (i
< NUSERFLAGS
) && (t
= strchr (s
,'\015')) && (t
- s
);
249 *t
= '\0'; /* tie off flag */
250 if (strlen (s
) <= MAXUSERFLAG
)
251 (*stream
)->user_flags
[i
] = cpystr (s
);
253 /* make sure have true UIDLAST */
254 if (flags
& MBXISVALIDUID
) {
255 for (upd
= NIL
,pos
= 2048, k
= 0; pos
< sbuf
.st_size
;
257 /* read header for this message */
258 lseek (fd
,pos
,L_SET
);
259 if ((j
= read (fd
,hdr
,64)) >= 0) {
261 if ((s
= strchr (hdr
,'\015')) && (s
[1] == '\012')) {
264 if ((s
= strchr (hdr
,',')) &&
265 (j
= strtol (s
+1,&s
,10)) && (*s
== ';') &&
266 (s
= strchr (s
+1,'-'))) {
267 /* get UID if there is any */
268 i
= strtoul (++s
,&t
,16);
269 if (!*t
&& (t
== (s
+ 8)) &&
270 (i
<= (*stream
)->uid_last
)) {
272 lseek (fd
,pos
+ s
- hdr
,L_SET
);
273 sprintf (hdr
,"%08lx",++(*stream
)->uid_last
);
281 ret
= -1; /* error, give up */
282 *stream
= mail_close (*stream
);
283 pos
= sbuf
.st_size
+ 1;
288 if (upd
) { /* need to update hdr with new UIDLAST? */
290 sprintf (hdr
,"%08lx",(*stream
)->uid_last
);
297 if (ret
!= fd
) close (fd
); /* close the file */
298 else lseek (fd
,0,L_SET
); /* else rewind to start */
299 /* \Marked status? */
300 if (sbuf
.st_ctime
> sbuf
.st_atime
) {
301 tp
[0] = sbuf
.st_atime
; /* preserve atime and mtime */
302 tp
[1] = sbuf
.st_mtime
;
303 utime (tmp
,tp
); /* set the times */
306 /* in case INBOX but not mbx format */
307 else if (((error
= errno
) == ENOENT
) && !compare_cstring (name
,"INBOX"))
309 if ((ret
< 0) && ld
&& (*ld
>= 0)) {
313 errno
= error
; /* return as last error */
314 return ret
; /* return what we should */
317 /* MBX manipulate driver parameters
318 * Accepts: function code
319 * function-dependent value
320 * Returns: function-dependent return value
323 void *mbx_parameters (long function
,void *value
)
326 switch ((int) function
) {
328 if (value
) ret
= mbx_file ((char *) value
,"INBOX");
330 case SET_ONETIMEEXPUNGEATPING
:
331 if (value
) ((MBXLOCAL
*) ((MAILSTREAM
*) value
)->local
)->expok
= T
;
332 case GET_ONETIMEEXPUNGEATPING
:
333 if (value
) ret
= (void *)
334 (((MBXLOCAL
*) ((MAILSTREAM
*) value
)->local
)->expok
? VOIDT
: NIL
);
341 /* MBX mail scan mailboxes
342 * Accepts: mail stream
348 void mbx_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
350 if (stream
) dummy_scan (NIL
,ref
,pat
,contents
);
354 /* MBX mail list mailboxes
355 * Accepts: mail stream
360 void mbx_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
362 if (stream
) dummy_list (NIL
,ref
,pat
);
366 /* MBX mail list subscribed mailboxes
367 * Accepts: mail stream
372 void mbx_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
374 if (stream
) dummy_lsub (NIL
,ref
,pat
);
377 /* MBX mail create mailbox
378 * Accepts: MAIL stream
379 * mailbox name to create
380 * Returns: T on success, NIL on failure
383 long mbx_create (MAILSTREAM
*stream
,char *mailbox
)
385 char *s
,*t
,mbx
[MAILTMPLEN
],tmp
[HDRSIZE
];
388 if (!(s
= mbx_file (mbx
,mailbox
))) {
389 sprintf (mbx
,"Can't create %.80s: invalid name",mailbox
);
392 /* create underlying file */
393 else if (dummy_create_path (stream
,s
,get_dir_protection (mailbox
))) {
394 /* done if made directory */
395 if ((s
= strrchr (s
,'/')) && !s
[1]) return T
;
396 if ((fd
= open (mbx
,O_WRONLY
|O_BINARY
,NIL
)) < 0) {
397 sprintf (tmp
,"Can't reopen mailbox node %.80s: %s",mbx
,strerror (errno
));
399 unlink (mbx
); /* delete the file */
402 memset (tmp
,'\0',HDRSIZE
);/* initialize header */
403 sprintf (s
= tmp
,"*mbx*\015\012%08lx00000000\015\012",
404 (unsigned long) time (0));
405 for (i
= 0; i
< NUSERFLAGS
; ++i
) {
406 t
= (stream
&& stream
->user_flags
[i
]) ? stream
->user_flags
[i
] :
407 ((t
= default_user_flag (i
)) ? t
: "");
408 sprintf (s
+= strlen (s
),"%s\015\012",t
);
410 if (write (fd
,tmp
,HDRSIZE
) != HDRSIZE
) {
411 sprintf (tmp
,"Can't initialize mailbox node %.80s: %s",
412 mbx
,strerror (errno
));
414 unlink (mbx
); /* delete the file */
416 else ret
= T
; /* success */
417 close (fd
); /* close file */
420 /* set proper protections */
421 return ret
? set_mbx_protections (mailbox
,mbx
) : NIL
;
425 /* MBX mail delete mailbox
426 * Accepts: MAIL stream
427 * mailbox name to delete
428 * Returns: T on success, NIL on failure
431 long mbx_delete (MAILSTREAM
*stream
,char *mailbox
)
433 return mbx_rename (stream
,mailbox
,NIL
);
436 /* MBX mail rename mailbox
437 * Accepts: MAIL stream
439 * new mailbox name (or NIL for delete)
440 * Returns: T on success, NIL on failure
443 long mbx_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
446 char c
,*s
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
449 if (!mbx_file (file
,old
) ||
450 (newname
&& (!((s
= mailboxfile (tmp
,newname
)) && *s
) ||
451 ((s
= strrchr (tmp
,'/')) && !s
[1])))) {
452 sprintf (tmp
,newname
?
453 "Can't rename mailbox %.80s to %.80s: invalid name" :
454 "Can't delete mailbox %.80s: invalid name",
459 else if ((fd
= open (file
,O_RDWR
|O_BINARY
,NIL
)) < 0) {
460 sprintf (tmp
,"Can't open mailbox %.80s: %s",old
,strerror (errno
));
464 /* get parse/append permission */
465 if ((ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0) {
466 MM_LOG ("Unable to lock rename mailbox",ERROR
);
469 /* lock out other users */
470 if (flock (fd
,LOCK_EX
|LOCK_NB
)) {
471 close (fd
); /* couldn't lock, give up on it then */
472 sprintf (tmp
,"Mailbox %.80s is in use by another process",old
);
474 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
478 if (newname
) { /* want rename? */
479 /* found superior to destination name? */
480 if (s
= strrchr (tmp
,'/')) {
481 c
= *++s
; /* remember first character of inferior */
482 *s
= '\0'; /* tie off to get just superior */
483 /* superior name doesn't exist, create it */
484 if ((stat (tmp
,&sbuf
) || ((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
)) &&
485 !dummy_create_path (stream
,tmp
,get_dir_protection (newname
)))
487 else *s
= c
; /* restore full name */
489 /* rename the file */
490 if (ret
&& rename (file
,tmp
)) {
491 sprintf (tmp
,"Can't rename mailbox %.80s to %.80s: %s",old
,newname
,
494 ret
= NIL
; /* set failure */
497 else if (unlink (file
)) {
498 sprintf (tmp
,"Can't delete mailbox %.80s: %s",old
,strerror (errno
));
500 ret
= NIL
; /* set failure */
502 flock (fd
,LOCK_UN
); /* release lock on the file */
503 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
504 close (fd
); /* close the file */
505 /* recreate file if renamed INBOX */
506 if (ret
&& !compare_cstring (old
,"INBOX")) mbx_create (NIL
,"INBOX");
507 return ret
; /* return success */
511 * Accepts: mail stream
514 * Returns: T on success, NIL on failure
517 long mbx_status (MAILSTREAM
*stream
,char *mbx
,long flags
)
521 MAILSTREAM
*tstream
= NIL
;
522 MAILSTREAM
*systream
= NIL
;
523 /* make temporary stream (unless this mbx) */
524 if (!stream
&& !(stream
= tstream
=
525 mail_open (NIL
,mbx
,OP_READONLY
|OP_SILENT
)))
527 status
.flags
= flags
; /* return status values */
528 status
.messages
= stream
->nmsgs
;
529 status
.recent
= stream
->recent
;
530 if (flags
& SA_UNSEEN
) /* must search to get unseen messages */
531 for (i
= 1,status
.unseen
= 0; i
<= stream
->nmsgs
; i
++)
532 if (!mail_elt (stream
,i
)->seen
) status
.unseen
++;
533 status
.uidnext
= stream
->uid_last
+ 1;
534 status
.uidvalidity
= stream
->uid_validity
;
535 /* calculate post-snarf results */
536 if (!status
.recent
&& stream
->inbox
&&
537 (systream
= mail_open (NIL
,sysinbox (),OP_READONLY
|OP_SILENT
))) {
538 status
.messages
+= systream
->nmsgs
;
539 status
.recent
+= systream
->recent
;
540 if (flags
& SA_UNSEEN
) /* must search to get unseen messages */
541 for (i
= 1; i
<= systream
->nmsgs
; i
++)
542 if (!mail_elt (systream
,i
)->seen
) status
.unseen
++;
543 /* kludge but probably good enough */
544 status
.uidnext
+= systream
->nmsgs
;
546 MM_STATUS(stream
,mbx
,&status
);/* pass status to main program */
547 if (tstream
) mail_close (tstream
);
548 if (systream
) mail_close (systream
);
549 return T
; /* success */
553 * Accepts: stream to open
554 * Returns: stream on success, NIL on failure
557 MAILSTREAM
*mbx_open (MAILSTREAM
*stream
)
561 char tmp
[MAILTMPLEN
];
562 blocknotify_t bn
= (blocknotify_t
) mail_parameters (NIL
,GET_BLOCKNOTIFY
,NIL
);
563 /* return prototype for OP_PROTOTYPE call */
564 if (!stream
) return user_flags (&mbxproto
);
565 if (stream
->local
) fatal ("mbx recycle stream");
566 /* canonicalize the mailbox name */
567 if (!mbx_file (tmp
,stream
->mailbox
)) {
568 sprintf (tmp
,"Can't open - invalid name: %.80s",stream
->mailbox
);
571 if (stream
->rdonly
||
572 (fd
= open (tmp
,O_RDWR
|O_BINARY
,NIL
)) < 0) {
573 if ((fd
= open (tmp
,O_RDONLY
|O_BINARY
,NIL
)) < 0) {
574 sprintf (tmp
,"Can't open mailbox: %s",strerror (errno
));
578 else if (!stream
->rdonly
) { /* got it, but readonly */
579 MM_LOG ("Can't get write access to mailbox, access is readonly",WARN
);
584 stream
->local
= memset (fs_get (sizeof (MBXLOCAL
)),NIL
,sizeof (MBXLOCAL
));
585 LOCAL
->fd
= fd
; /* bind the file */
586 LOCAL
->ld
= -1; /* no flaglock */
587 LOCAL
->buf
= (char *) fs_get (CHUNKSIZE
);
588 LOCAL
->buflen
= CHUNKSIZE
- 1;
589 /* note if an INBOX or not */
590 stream
->inbox
= !compare_cstring (stream
->mailbox
,"INBOX");
591 fs_give ((void **) &stream
->mailbox
);
592 stream
->mailbox
= cpystr (tmp
);
593 /* get parse/append permission */
594 if ((ld
= lockfd (LOCAL
->fd
,tmp
,LOCK_EX
)) < 0) {
595 MM_LOG ("Unable to lock open mailbox",ERROR
);
598 (*bn
) (BLOCK_FILELOCK
,NIL
);
599 flock (LOCAL
->fd
,LOCK_SH
); /* lock the file */
600 (*bn
) (BLOCK_NONE
,NIL
);
601 unlockfd (ld
,tmp
); /* release shared parse permission */
602 LOCAL
->filesize
= HDRSIZE
; /* initialize parsed file size */
603 /* time not set up yet */
604 LOCAL
->lastsnarf
= LOCAL
->filetime
= 0;
605 LOCAL
->expok
= LOCAL
->flagcheck
= NIL
;
606 stream
->sequence
++; /* bump sequence number */
608 stream
->nmsgs
= stream
->recent
= 0;
609 silent
= stream
->silent
; /* defer events */
611 if (mbx_ping (stream
) && !stream
->nmsgs
)
612 MM_LOG ("Mailbox is empty",(long) NIL
);
613 stream
->silent
= silent
; /* now notify upper level */
614 mail_exists (stream
,stream
->nmsgs
);
615 mail_recent (stream
,stream
->recent
);
616 if (!LOCAL
) return NIL
; /* failure if stream died */
617 stream
->perm_seen
= stream
->perm_deleted
= stream
->perm_flagged
=
618 stream
->perm_answered
= stream
->perm_draft
= stream
->rdonly
? NIL
: T
;
619 stream
->perm_user_flags
= stream
->rdonly
? NIL
: 0xffffffff;
620 stream
->kwd_create
= (stream
->user_flags
[NUSERFLAGS
-1] || stream
->rdonly
) ?
621 NIL
: T
; /* can we create new user flags? */
622 return stream
; /* return stream to caller */
626 * Accepts: MAIL stream
630 void mbx_close (MAILSTREAM
*stream
,long options
)
632 if (stream
&& LOCAL
) { /* only if a file is open */
633 int silent
= stream
->silent
;
634 stream
->silent
= T
; /* note this stream is dying */
635 /* do an expunge if requested */
636 if (options
& CL_EXPUNGE
) mbx_expunge (stream
,NIL
,NIL
);
637 else { /* otherwise do a checkpoint to purge */
638 LOCAL
->expok
= T
; /* possible expunged messages */
641 stream
->silent
= silent
; /* restore previous status */
647 /* MBX mail abort stream
648 * Accepts: MAIL stream
651 void mbx_abort (MAILSTREAM
*stream
)
653 if (stream
&& LOCAL
) { /* only if a file is open */
654 flock (LOCAL
->fd
,LOCK_UN
); /* unlock local file */
655 close (LOCAL
->fd
); /* close the local file */
656 /* free local text buffer */
657 if (LOCAL
->buf
) fs_give ((void **) &LOCAL
->buf
);
658 /* nuke the local data */
659 fs_give ((void **) &stream
->local
);
660 stream
->dtb
= NIL
; /* log out the DTB */
665 /* MBX mail fetch flags
666 * Accepts: MAIL stream
669 * Sniffs at file to see if some other process changed the flags
672 void mbx_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
676 if (mbx_ping (stream
) && /* ping mailbox, get new status for messages */
677 ((flags
& FT_UID
) ? mail_uid_sequence (stream
,sequence
) :
678 mail_sequence (stream
,sequence
)))
679 for (i
= 1; i
<= stream
->nmsgs
; i
++)
680 if ((elt
= mail_elt (stream
,i
))->sequence
&& !elt
->valid
)
681 mbx_elt (stream
,i
,NIL
);
684 /* MBX mail fetch message header
685 * Accepts: MAIL stream
687 * pointer to returned header text length
689 * Returns: message header in RFC822 format
692 char *mbx_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
697 *length
= 0; /* default to empty */
698 if (flags
& FT_UID
) return "";/* UID call "impossible" */
699 /* get header position, possibly header */
700 i
= mbx_hdrpos (stream
,msgno
,length
,&s
);
701 if (!s
) { /* mbx_hdrpos() returned header? */
702 lseek (LOCAL
->fd
,i
,L_SET
); /* no, get to header position */
703 /* is buffer big enough? */
704 if (*length
> LOCAL
->buflen
) {
705 fs_give ((void **) &LOCAL
->buf
);
706 LOCAL
->buf
= (char *) fs_get ((LOCAL
->buflen
= *length
) + 1);
709 read (LOCAL
->fd
,s
= LOCAL
->buf
,*length
);
711 s
[*length
] = '\0'; /* tie off string */
715 /* MBX mail fetch message text (body only)
716 * Accepts: MAIL stream
718 * pointer to returned header text length
720 * Returns: T on success, NIL on failure
723 long mbx_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
728 /* UID call "impossible" */
729 if (flags
& FT_UID
) return NIL
;
730 /* get message status */
731 elt
= mbx_elt (stream
,msgno
,NIL
);
732 /* if message not seen */
733 if (!(flags
& FT_PEEK
) && !elt
->seen
&& mbx_flaglock (stream
)) {
734 elt
->seen
= T
; /* mark message as seen */
735 /* recalculate status */
736 mbx_update_status (stream
,msgno
,NIL
);
737 MM_FLAGS (stream
,msgno
);
739 mbx_flag (stream
,NIL
,NIL
,NIL
);
741 if (!LOCAL
) return NIL
; /* mbx_flaglock() could have aborted */
742 /* find header position */
743 i
= mbx_hdrpos (stream
,msgno
,&j
,NIL
);
744 d
.fd
= LOCAL
->fd
; /* set up file descriptor */
746 d
.chunk
= LOCAL
->buf
; /* initial buffer chunk */
747 d
.chunksize
= CHUNKSIZE
;
748 INIT (bs
,fd_string
,&d
,elt
->rfc822_size
- j
);
749 return LONGT
; /* success */
752 /* MBX mail modify flags
753 * Accepts: MAIL stream
760 void mbx_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
)
764 unsigned long oldpid
= LOCAL
->lastpid
;
765 /* make sure the update takes */
766 if (!stream
->rdonly
&& LOCAL
&& (LOCAL
->fd
>= 0) && (LOCAL
->ld
>= 0)) {
768 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
769 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
770 /* we are the last flag updater */
771 LOCAL
->lastpid
= (unsigned long) getpid ();
772 /* update header if needed */
773 if (((LOCAL
->ffuserflag
< NUSERFLAGS
) &&
774 stream
->user_flags
[LOCAL
->ffuserflag
]) || (oldpid
!= LOCAL
->lastpid
))
775 mbx_update_header (stream
);
776 tp
[0] = time (0); /* make sure read comes after all that */
777 utime (stream
->mailbox
,tp
);
779 if (LOCAL
->ld
>= 0) { /* unlock now */
780 unlockfd (LOCAL
->ld
,LOCAL
->lock
);
786 /* MBX mail per-message modify flags
787 * Accepts: MAIL stream
788 * message cache element
791 void mbx_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
793 if (mbx_flaglock (stream
)) mbx_update_status (stream
,elt
->msgno
,NIL
);
796 /* MBX mail ping mailbox
797 * Accepts: MAIL stream
798 * Returns: T if stream still alive, NIL if not
801 long mbx_ping (MAILSTREAM
*stream
)
806 char lock
[MAILTMPLEN
];
809 if (stream
&& LOCAL
) { /* only if stream already open */
810 int snarf
= stream
->inbox
&& !stream
->rdonly
;
811 ret
= LONGT
; /* assume OK */
812 fstat (LOCAL
->fd
,&sbuf
); /* get current file poop */
813 /* allow expunge if permitted at ping */
814 if (mail_parameters (NIL
,GET_EXPUNGEATPING
,NIL
)) LOCAL
->expok
= T
;
815 /* if external modification */
816 if (LOCAL
->filetime
&& (LOCAL
->filetime
< sbuf
.st_mtime
))
817 LOCAL
->flagcheck
= T
; /* upgrade to flag checking */
818 /* new mail or flagcheck handling needed? */
819 if (((sbuf
.st_size
- LOCAL
->filesize
) || LOCAL
->flagcheck
||
820 !stream
->nmsgs
|| snarf
) &&
821 ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) >= 0)) {
822 /* reparse header if not flagchecking */
823 if (!LOCAL
->flagcheck
) ret
= mbx_parse (stream
);
824 /* sweep mailbox for changed message status */
825 else if (ret
= mbx_parse (stream
)) {
826 unsigned long recent
= 0;
827 LOCAL
->filetime
= sbuf
.st_mtime
;
828 for (i
= 1; i
<= stream
->nmsgs
; )
829 if (elt
= mbx_elt (stream
,i
,LOCAL
->expok
)) {
830 if (elt
->recent
) ++recent
;
833 mail_recent (stream
,recent
);
834 LOCAL
->flagcheck
= NIL
; /* got all the updates */
836 /* always reparse header at least */
837 if (ret
&& snarf
) { /* snarf new messages if still OK */
839 /* parse snarfed messages */
840 ret
= mbx_parse (stream
);
842 unlockfd (ld
,lock
); /* release shared parse/append permission */
844 if (ret
) { /* must still be alive */
845 if (!LOCAL
->expunged
) /* look for holes if none known yet */
846 for (i
= 1, pos
= HDRSIZE
;
847 !LOCAL
->expunged
&& (i
<= stream
->nmsgs
);
848 i
++, pos
+= elt
->private.special
.text
.size
+ elt
->rfc822_size
)
849 if ((elt
= mail_elt (stream
,i
))->private.special
.offset
!= pos
)
850 LOCAL
->expunged
= T
;/* found a hole */
852 if (LOCAL
->expunged
&& !stream
->rdonly
) {
853 if (mbx_rewrite (stream
,&i
,NIL
)) fatal ("expunge on check");
854 if (i
) { /* any space reclaimed? */
855 LOCAL
->expunged
= NIL
;/* no more pending expunge */
856 sprintf (LOCAL
->buf
,"Reclaimed %lu bytes of expunged space",i
);
857 MM_LOG (LOCAL
->buf
,(long) NIL
);
860 LOCAL
->expok
= NIL
; /* no more expok */
863 return ret
; /* return result of the parse */
866 /* MBX mail check mailbox (reparses status too)
867 * Accepts: MAIL stream
870 void mbx_check (MAILSTREAM
*stream
)
872 if (LOCAL
) LOCAL
->expok
= T
; /* mark that a check is desired */
873 if (mbx_ping (stream
)) MM_LOG ("Check completed",(long) NIL
);
877 /* MBX mail expunge mailbox
878 * Accepts: MAIL stream
879 * sequence to expunge if non-NIL
881 * Returns: T if success, NIL if failure
884 long mbx_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
887 unsigned long nexp
,reclaimed
;
888 if (ret
= sequence
? ((options
& EX_UID
) ?
889 mail_uid_sequence (stream
,sequence
) :
890 mail_sequence (stream
,sequence
)) : LONGT
) {
891 if (!mbx_ping (stream
)); /* do nothing if stream dead */
892 else if (stream
->rdonly
) /* won't do on readonly files! */
893 MM_LOG ("Expunge ignored on readonly mailbox",WARN
);
894 /* if expunged any messages */
895 else if (nexp
= mbx_rewrite (stream
,&reclaimed
,sequence
? -1 : 1)) {
896 sprintf (LOCAL
->buf
,"Expunged %lu messages",nexp
);
897 MM_LOG (LOCAL
->buf
,(long) NIL
);
899 else if (reclaimed
) { /* or if any prior expunged space reclaimed */
900 sprintf (LOCAL
->buf
,"Reclaimed %lu bytes of expunged space",reclaimed
);
901 MM_LOG (LOCAL
->buf
,(long) NIL
);
903 else MM_LOG ("No messages deleted, so no update needed",(long) NIL
);
908 /* MBX mail snarf messages from system inbox
909 * Accepts: MAIL stream, already locked
912 void mbx_snarf (MAILSTREAM
*stream
)
915 unsigned long j
,r
,hdrlen
,txtlen
;
917 char *hdr
,*txt
,tmp
[MAILTMPLEN
];
919 MAILSTREAM
*sysibx
= NIL
;
920 /* give up if can't get exclusive permission */
921 if ((time (0) >= (LOCAL
->lastsnarf
+
922 (long) mail_parameters (NIL
,GET_SNARFINTERVAL
,NIL
))) &&
923 strcmp (sysinbox (),stream
->mailbox
)) {
924 MM_CRITICAL (stream
); /* go critical */
925 /* sizes match and anything in sysinbox? */
926 if (!stat (sysinbox (),&sbuf
) && sbuf
.st_size
&&
927 !fstat (LOCAL
->fd
,&sbuf
) && (sbuf
.st_size
== LOCAL
->filesize
) &&
928 (sysibx
= mail_open (sysibx
,sysinbox (),OP_SILENT
)) &&
929 (!sysibx
->rdonly
) && (r
= sysibx
->nmsgs
)) {
930 /* yes, go to end of file in our mailbox */
931 lseek (LOCAL
->fd
,sbuf
.st_size
,L_SET
);
932 /* for each message in sysibx mailbox */
933 while (r
&& (++i
<= sysibx
->nmsgs
)) {
934 /* snarf message from system INBOX */
935 hdr
= cpystr (mail_fetchheader_full (sysibx
,i
,NIL
,&hdrlen
,NIL
));
936 txt
= mail_fetchtext_full (sysibx
,i
,&txtlen
,FT_PEEK
);
937 /* if have a message */
938 if (j
= hdrlen
+ txtlen
) {
939 /* build header line */
940 mail_date (LOCAL
->buf
,elt
= mail_elt (sysibx
,i
));
941 sprintf (LOCAL
->buf
+ strlen (LOCAL
->buf
),
942 ",%lu;00000000%04x-00000000\015\012",j
,(unsigned)
943 ((fSEEN
* elt
->seen
) +
944 (fDELETED
* elt
->deleted
) + (fFLAGGED
* elt
->flagged
) +
945 (fANSWERED
* elt
->answered
) + (fDRAFT
* elt
->draft
)));
947 if ((write (LOCAL
->fd
,LOCAL
->buf
,strlen (LOCAL
->buf
)) < 0) ||
948 (write (LOCAL
->fd
,hdr
,hdrlen
) < 0) ||
949 (write (LOCAL
->fd
,txt
,txtlen
) < 0)) r
= 0;
951 fs_give ((void **) &hdr
);
954 /* make sure all the updates take */
955 if (fsync (LOCAL
->fd
)) r
= 0;
956 if (r
) { /* delete all the messages we copied */
957 if (r
== 1) strcpy (tmp
,"1");
958 else sprintf (tmp
,"1:%lu",r
);
959 mail_setflag (sysibx
,tmp
,"\\Deleted");
960 mail_expunge (sysibx
); /* now expunge all those messages */
963 sprintf (LOCAL
->buf
,"Can't copy new mail: %s",strerror (errno
));
964 MM_LOG (LOCAL
->buf
,WARN
);
965 ftruncate (LOCAL
->fd
,sbuf
.st_size
);
967 fstat (LOCAL
->fd
,&sbuf
); /* yes, get current file size */
968 LOCAL
->filetime
= sbuf
.st_mtime
;
970 if (sysibx
) mail_close (sysibx
);
971 MM_NOCRITICAL (stream
); /* release critical */
972 LOCAL
->lastsnarf
= time (0);/* note time of last snarf */
976 /* MBX mail copy message(s)
977 * Accepts: MAIL stream
979 * destination mailbox
981 * Returns: T if success, NIL if failed
984 long mbx_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
989 unsigned long i
,j
,k
,m
;
992 char *s
,*t
,file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
994 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
995 copyuid_t cu
= (copyuid_t
) mail_parameters (NIL
,GET_COPYUID
,NIL
);
996 SEARCHSET
*source
= cu
? mail_newsearchset () : NIL
;
997 SEARCHSET
*dest
= cu
? mail_newsearchset () : NIL
;
998 MAILSTREAM
*dstream
= NIL
;
999 if (!((options
& CP_UID
) ? mail_uid_sequence (stream
,sequence
) :
1000 mail_sequence (stream
,sequence
))) return NIL
;
1001 /* make sure valid mailbox */
1002 if ((fd
= mbx_isvalid (&dstream
,mailbox
,file
,&ld
,lock
,
1003 cu
? MBXISVALIDUID
: MBXISVALIDNOUID
)) < 0)
1005 case ENOENT
: /* no such file? */
1006 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before copy",NIL
);
1008 case EACCES
: /* file protected */
1009 sprintf (LOCAL
->buf
,"Can't access destination: %.80s",mailbox
);
1010 MM_LOG (LOCAL
->buf
,ERROR
);
1013 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
1014 sprintf (LOCAL
->buf
,"Invalid MBX-format mailbox name: %.80s",mailbox
);
1015 MM_LOG (LOCAL
->buf
,ERROR
);
1018 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
1019 sprintf (LOCAL
->buf
,"Not a MBX-format mailbox: %.80s",mailbox
);
1020 MM_LOG (LOCAL
->buf
,ERROR
);
1023 MM_CRITICAL (stream
); /* go critical */
1024 fstat (fd
,&sbuf
); /* get current file size */
1025 lseek (fd
,sbuf
.st_size
,L_SET
);/* move to end of file */
1027 /* for each requested message */
1028 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); i
++)
1029 if ((elt
= mail_elt (stream
,i
))->sequence
) {
1030 lseek (LOCAL
->fd
,elt
->private.special
.offset
+
1031 elt
->private.special
.text
.size
,L_SET
);
1032 mail_date(LOCAL
->buf
,elt
);/* build target header */
1033 /* get target keyword mask */
1034 for (j
= elt
->user_flags
, k
= 0; j
; )
1035 if (s
= stream
->user_flags
[find_rightmost_bit (&j
)])
1036 for (m
= 0; (m
< NUSERFLAGS
) && (t
= dstream
->user_flags
[m
]); m
++)
1037 if (!compare_cstring (s
,t
) && (k
|= 1 << m
)) break;
1038 sprintf (LOCAL
->buf
+strlen(LOCAL
->buf
),",%lu;%08lx%04x-%08lx\015\012",
1039 elt
->rfc822_size
,k
,(unsigned)
1040 ((fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
1041 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
1042 (fDRAFT
* elt
->draft
)),cu
? ++dstream
->uid_last
: 0);
1043 /* write target header */
1044 if (ret
= (write (fd
,LOCAL
->buf
,strlen (LOCAL
->buf
)) > 0)) {
1045 for (k
= elt
->rfc822_size
; ret
&& (j
= min (k
,LOCAL
->buflen
)); k
-= j
){
1046 read (LOCAL
->fd
,LOCAL
->buf
,j
);
1047 ret
= write (fd
,LOCAL
->buf
,j
) >= 0;
1049 if (cu
) { /* need to pass back new UID? */
1050 mail_append_set (source
,mail_uid (stream
,i
));
1051 mail_append_set (dest
,dstream
->uid_last
);
1056 /* make sure all the updates take */
1057 if (!(ret
&& (ret
= !fsync (fd
)))) {
1058 sprintf (LOCAL
->buf
,"Unable to write message: %s",strerror (errno
));
1059 MM_LOG (LOCAL
->buf
,ERROR
);
1060 ftruncate (fd
,sbuf
.st_size
);
1062 if (cu
&& ret
) { /* return sets if doing COPYUID */
1063 (*cu
) (stream
,mailbox
,dstream
->uid_validity
,source
,dest
);
1064 lseek (fd
,15,L_SET
); /* update UIDLAST */
1065 sprintf (LOCAL
->buf
,"%08lx",dstream
->uid_last
);
1066 write (fd
,LOCAL
->buf
,8);
1068 else { /* flush any sets we may have built */
1069 mail_free_searchset (&source
);
1070 mail_free_searchset (&dest
);
1072 if (ret
) tp
[0] = time (0) - 1;/* set atime to now-1 if successful copy */
1073 /* else preserve \Marked status */
1074 else tp
[0] = (sbuf
.st_ctime
> sbuf
.st_atime
) ? sbuf
.st_atime
: time(0);
1075 tp
[1] = sbuf
.st_mtime
; /* preserve mtime */
1076 utime (file
,tp
); /* set the times */
1077 close (fd
); /* close the file */
1078 MM_NOCRITICAL (stream
); /* release critical */
1079 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
1080 /* delete all requested messages */
1081 if (ret
&& (options
& CP_MOVE
) && mbx_flaglock (stream
)) {
1082 for (i
= 1; i
<= stream
->nmsgs
; i
++) if (mail_elt (stream
,i
)->sequence
) {
1083 /* mark message deleted */
1084 mbx_elt (stream
,i
,NIL
)->deleted
= T
;
1085 /* recalculate status */
1086 mbx_update_status (stream
,i
,NIL
);
1089 mbx_flag (stream
,NIL
,NIL
,NIL
);
1091 if (dstream
!= stream
) mail_close (dstream
);
1095 /* MBX mail append message from stringstruct
1096 * Accepts: MAIL stream
1097 * destination mailbox
1100 * Returns: T if append successful, else NIL
1103 long mbx_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
1107 char *flags
,*date
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
1115 MAILSTREAM
*dstream
= NIL
;
1116 appenduid_t au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
);
1117 SEARCHSET
*dst
= au
? mail_newsearchset () : NIL
;
1118 /* make sure valid mailbox */
1119 if ((fd
= mbx_isvalid (&dstream
,mailbox
,file
,&ld
,lock
,
1120 au
? MBXISVALIDUID
: MBXISVALIDNOUID
)) < 0)
1122 case ENOENT
: /* no such file? */
1123 if (compare_cstring (mailbox
,"INBOX")) {
1124 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before append",NIL
);
1127 /* can create INBOX here */
1128 mbx_create (dstream
= stream
? stream
: user_flags (&mbxproto
),"INBOX");
1129 if ((fd
= mbx_isvalid (&dstream
,mailbox
,file
,&ld
,lock
,
1130 au
? MBXISVALIDUID
: MBXISVALIDNOUID
)) >= 0)
1132 case EACCES
: /* file protected */
1133 sprintf (tmp
,"Can't access destination: %.80s",mailbox
);
1137 sprintf (tmp
,"Invalid MBX-format mailbox name: %.80s",mailbox
);
1141 sprintf (tmp
,"Not a MBX-format mailbox: %.80s",mailbox
);
1146 /* get first message */
1147 if (!MM_APPEND (af
) (dstream
,data
,&flags
,&date
,&message
)) close (fd
);
1148 else if (!(df
= fdopen (fd
,"r+b"))) {
1149 MM_LOG ("Unable to reopen append mailbox",ERROR
);
1153 MM_CRITICAL (dstream
); /* go critical */
1154 fstat (fd
,&sbuf
); /* get current file size */
1155 fseek (df
,sbuf
.st_size
,SEEK_SET
);
1157 for (ret
= LONGT
; ret
&& message
; ) {
1158 if (!SIZE (message
)) { /* guard against zero-length */
1159 MM_LOG ("Append of zero-length message",ERROR
);
1163 f
= mail_parse_flags (dstream
,flags
,&uf
);
1164 if (date
) { /* parse date if given */
1165 if (!mail_parse_date (&elt
,date
)) {
1166 sprintf (tmp
,"Bad date in append: %.80s",date
);
1168 ret
= NIL
; /* mark failure */
1171 mail_date (tmp
,&elt
); /* write preseved date */
1173 else internal_date (tmp
); /* get current date in IMAP format */
1175 if (fprintf (df
,"%s,%lu;%08lx%04lx-%08lx\015\012",tmp
,i
= SIZE (message
),
1176 uf
,(unsigned long) f
,au
? ++dstream
->uid_last
: 0) < 0)
1178 else { /* write message */
1180 if (!message
->cursize
) SETPOS (message
,GETPOS (message
));
1181 for (errno
= 0; !errno
&& i
&&
1182 (j
= fwrite (message
->curpos
,1,message
->cursize
,df
)); i
-= j
) {
1183 SETPOS (message
,GETPOS (message
) + j
);
1185 /* get next message */
1186 if (i
|| !MM_APPEND (af
) (dstream
,data
,&flags
,&date
,&message
))
1188 else if (au
) mail_append_set (dst
,dstream
->uid_last
);
1193 if (!ret
|| (fflush (df
) == EOF
)) {
1195 ftruncate (fd
,sbuf
.st_size
);
1196 close (fd
); /* make sure fclose() doesn't corrupt us */
1198 sprintf (tmp
,"Message append failed: %s",strerror (errno
));
1203 if (au
&& ret
) { /* return sets if doing APPENDUID */
1204 (*au
) (mailbox
,dstream
->uid_validity
,dst
);
1205 fseek (df
,15,SEEK_SET
); /* update UIDLAST */
1206 fprintf (df
,"%08lx",dstream
->uid_last
);
1208 else mail_free_searchset (&dst
);
1209 /* set atime to now-1 if successful copy */
1210 if (ret
) tp
[0] = time (0) - 1;
1211 /* else preserve \Marked status */
1212 else tp
[0] = (sbuf
.st_ctime
> sbuf
.st_atime
) ? sbuf
.st_atime
: time(0);
1213 tp
[1] = sbuf
.st_mtime
; /* preserve mtime */
1214 utime (file
,tp
); /* set the times */
1215 fclose (df
); /* close the file */
1216 MM_NOCRITICAL (dstream
); /* release critical */
1218 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
1219 if (dstream
!= stream
) mail_close (dstream
);
1223 /* Internal routines */
1226 /* MBX mail generate file string
1227 * Accepts: temporary buffer to write into
1228 * mailbox name string
1229 * Returns: local file string or NIL if failure
1232 char *mbx_file (char *dst
,char *name
)
1234 char *s
= mailboxfile (dst
,name
);
1235 return (s
&& !*s
) ? mailboxfile (dst
,"~/INBOX") : s
;
1238 /* MBX mail parse mailbox
1239 * Accepts: MAIL stream
1240 * Returns: T if parse OK
1241 * NIL if failure, stream aborted
1244 long mbx_parse (MAILSTREAM
*stream
)
1247 MESSAGECACHE
*elt
= NIL
;
1248 unsigned char c
,*s
,*t
,*x
;
1249 char tmp
[MAILTMPLEN
];
1250 unsigned long i
,j
,k
,m
;
1251 off_t curpos
= LOCAL
->filesize
;
1252 unsigned long nmsgs
= stream
->nmsgs
;
1253 unsigned long recent
= stream
->recent
;
1254 unsigned long lastuid
= 0;
1257 short silent
= stream
->silent
;
1259 fstat (LOCAL
->fd
,&sbuf
); /* get status */
1260 if (sbuf
.st_size
< curpos
) { /* sanity check */
1261 sprintf (tmp
,"Mailbox shrank from %lu to %lu!",
1262 (unsigned long) curpos
,(unsigned long) sbuf
.st_size
);
1267 lseek (LOCAL
->fd
,0,L_SET
); /* rewind file */
1268 /* read internal header */
1269 read (LOCAL
->fd
,LOCAL
->buf
,HDRSIZE
);
1270 LOCAL
->buf
[HDRSIZE
] = '\0'; /* tie off header */
1271 c
= LOCAL
->buf
[15]; /* save first character of last UID */
1272 LOCAL
->buf
[15] = '\0';
1273 /* parse UID validity */
1274 stream
->uid_validity
= strtoul (LOCAL
->buf
+ 7,NIL
,16);
1275 LOCAL
->buf
[15] = c
; /* restore first character of last UID */
1276 /* parse last UID */
1277 i
= strtoul (LOCAL
->buf
+ 15,NIL
,16);
1278 stream
->uid_last
= stream
->rdonly
? max (i
,stream
->uid_last
) : i
;
1279 /* parse user flags */
1280 for (i
= 0, s
= LOCAL
->buf
+ 25;
1281 (i
< NUSERFLAGS
) && (t
= strchr (s
,'\015')) && (t
- s
);
1283 *t
= '\0'; /* tie off flag */
1284 if (!stream
->user_flags
[i
] && (strlen (s
) <= MAXUSERFLAG
))
1285 stream
->user_flags
[i
] = cpystr (s
);
1287 LOCAL
->ffuserflag
= (int) i
; /* first free user flag */
1289 /* get current last flag updater PID */
1290 i
= (isxdigit (LOCAL
->buf
[HDRSIZE
-10]) && isxdigit (LOCAL
->buf
[HDRSIZE
-9]) &&
1291 isxdigit (LOCAL
->buf
[HDRSIZE
-8]) && isxdigit (LOCAL
->buf
[HDRSIZE
-7]) &&
1292 isxdigit (LOCAL
->buf
[HDRSIZE
-6]) && isxdigit (LOCAL
->buf
[HDRSIZE
-5]) &&
1293 isxdigit (LOCAL
->buf
[HDRSIZE
-4]) && isxdigit (LOCAL
->buf
[HDRSIZE
-3]) &&
1294 (LOCAL
->buf
[HDRSIZE
-2] == '\015') && (LOCAL
->buf
[HDRSIZE
-1] == '\012'))?
1295 strtoul (LOCAL
->buf
+ HDRSIZE
- 8,NIL
,16) : 0;
1296 /* set flagcheck if lastpid changed */
1297 if (LOCAL
->lastpid
&& (LOCAL
->lastpid
!= i
)) LOCAL
->flagcheck
= T
;
1298 LOCAL
->lastpid
= i
; /* set as last PID */
1299 stream
->silent
= T
; /* don't pass up exists events yet */
1300 while (sbuf
.st_size
- curpos
){/* while there is stuff to parse */
1301 /* get to that position in the file */
1302 lseek (LOCAL
->fd
,curpos
,L_SET
);
1303 if ((i
= read (LOCAL
->fd
,LOCAL
->buf
,64)) <= 0) {
1304 sprintf (tmp
,"Unable to read internal header at %lu, size = %lu: %s",
1305 (unsigned long) curpos
,(unsigned long) sbuf
.st_size
,
1306 i
? strerror (errno
) : "no data read");
1311 LOCAL
->buf
[i
] = '\0'; /* tie off buffer just in case */
1312 if (!((s
= strchr (LOCAL
->buf
,'\015')) && (s
[1] == '\012'))) {
1313 sprintf (tmp
,"Unable to find CRLF at %lu in %lu bytes, text: %.80s",
1314 (unsigned long) curpos
,i
,(char *) LOCAL
->buf
);
1319 *s
= '\0'; /* tie off header line */
1320 i
= (s
+ 2) - LOCAL
->buf
; /* note start of text offset */
1321 if (!((s
= strchr (LOCAL
->buf
,',')) && (t
= strchr (s
+1,';')))) {
1322 sprintf (tmp
,"Unable to parse internal header at %lu: %.80s",
1323 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1328 if (!(isxdigit (t
[1]) && isxdigit (t
[2]) && isxdigit (t
[3]) &&
1329 isxdigit (t
[4]) && isxdigit (t
[5]) && isxdigit (t
[6]) &&
1330 isxdigit (t
[7]) && isxdigit (t
[8]) && isxdigit (t
[9]) &&
1331 isxdigit (t
[10]) && isxdigit (t
[11]) && isxdigit (t
[12]))) {
1332 sprintf (tmp
,"Unable to parse message flags at %lu: %.80s",
1333 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1338 if ((t
[13] != '-') || t
[22] ||
1339 !(isxdigit (t
[14]) && isxdigit (t
[15]) && isxdigit (t
[16]) &&
1340 isxdigit (t
[17]) && isxdigit (t
[18]) && isxdigit (t
[19]) &&
1341 isxdigit (t
[20]) && isxdigit (t
[21]))) {
1342 sprintf (tmp
,"Unable to parse message UID at %lu: %.80s",
1343 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1349 *s
++ = '\0'; *t
++ = '\0'; /* break up fields */
1350 /* get message size */
1351 if (!(j
= strtoul (s
,(char **) &x
,10)) && (!(x
&& *x
))) {
1352 sprintf (tmp
,"Unable to parse message size at %lu: %.80s,%.80s;%.80s",
1353 (unsigned long) curpos
,(char *) LOCAL
->buf
,(char *) s
,
1359 /* make sure didn't run off end of file */
1360 if (((off_t
) (curpos
+ i
+ j
)) > sbuf
.st_size
) {
1361 sprintf (tmp
,"Last message (at %lu) runs past end of file (%lu > %lu)",
1362 (unsigned long) curpos
,(unsigned long) (curpos
+ i
+ j
),
1363 (unsigned long) sbuf
.st_size
);
1369 if ((m
= strtoul (t
+13,NIL
,16)) &&
1370 ((m
<= lastuid
) || (m
> stream
->uid_last
))) {
1372 sprintf (tmp
,"Invalid UID %08lx in message %lu, rebuilding UIDs",
1376 /* restart UID validity */
1377 stream
->uid_validity
= time (0);
1379 m
= 0; /* lose this UID */
1380 dirty
= T
; /* mark dirty, set new lastuid */
1381 stream
->uid_last
= lastuid
;
1384 t
[12] = '\0'; /* parse system flags */
1385 if ((k
= strtoul (t
+8,NIL
,16)) & fEXPUNGED
) {
1386 if (m
) lastuid
= m
; /* expunge message, update last UID seen */
1387 else { /* no UID assigned? */
1388 lastuid
= ++stream
->uid_last
;
1392 else { /* not expunged, swell the cache */
1393 added
= T
; /* note that a new message was added */
1394 mail_exists (stream
,++nmsgs
);
1395 /* instantiate an elt for this message */
1396 (elt
= mail_elt (stream
,nmsgs
))->valid
= T
;
1397 /* parse the date */
1398 if (!mail_parse_date (elt
,LOCAL
->buf
)) {
1399 sprintf (tmp
,"Unable to parse message date at %lu: %.80s",
1400 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1405 /* note file offset of header */
1406 elt
->private.special
.offset
= curpos
;
1407 /* and internal header size */
1408 elt
->private.special
.text
.size
= i
;
1409 /* header size not known yet */
1410 elt
->private.msg
.header
.text
.size
= 0;
1411 elt
->rfc822_size
= j
; /* note message size */
1412 /* calculate system flags */
1413 if (k
& fSEEN
) elt
->seen
= T
;
1414 if (k
& fDELETED
) elt
->deleted
= T
;
1415 if (k
& fFLAGGED
) elt
->flagged
= T
;
1416 if (k
& fANSWERED
) elt
->answered
= T
;
1417 if (k
& fDRAFT
) elt
->draft
= T
;
1418 t
[8] = '\0'; /* get user flags value */
1419 elt
->user_flags
= strtoul (t
,NIL
,16);
1420 /* UID already assigned? */
1421 if (!(elt
->private.uid
= m
) || !(k
& fOLD
)) {
1422 elt
->recent
= T
; /* no, mark as recent */
1423 ++recent
; /* count up a new recent message */
1424 dirty
= T
; /* and must rewrite header */
1425 /* assign new UID */
1426 if (!elt
->private.uid
) elt
->private.uid
= ++stream
->uid_last
;
1427 mbx_update_status (stream
,elt
->msgno
,NIL
);
1429 /* update last parsed UID */
1430 lastuid
= elt
->private.uid
;
1432 curpos
+= i
+ j
; /* update position */
1435 if (dirty
&& !stream
->rdonly
){/* update header */
1436 mbx_update_header (stream
);
1437 fsync (LOCAL
->fd
); /* make sure all the UID updates take */
1439 /* update parsed file size and time */
1440 LOCAL
->filesize
= sbuf
.st_size
;
1441 fstat (LOCAL
->fd
,&sbuf
); /* get status again to ensure time is right */
1442 LOCAL
->filetime
= sbuf
.st_mtime
;
1443 if (added
&& !stream
->rdonly
){/* make sure atime updated */
1446 tp
[1] = LOCAL
->filetime
;
1447 utime (stream
->mailbox
,tp
);
1449 stream
->silent
= silent
; /* can pass up events now */
1450 mail_exists (stream
,nmsgs
); /* notify upper level of new mailbox size */
1451 mail_recent (stream
,recent
); /* and of change in recent messages */
1452 return LONGT
; /* return the winnage */
1455 /* MBX get cache element with status updating from file
1456 * Accepts: MAIL stream
1459 * Returns: cache element
1462 MESSAGECACHE
*mbx_elt (MAILSTREAM
*stream
,unsigned long msgno
,long expok
)
1464 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1465 struct { /* old flags */
1466 unsigned int seen
: 1;
1467 unsigned int deleted
: 1;
1468 unsigned int flagged
: 1;
1469 unsigned int answered
: 1;
1470 unsigned int draft
: 1;
1471 unsigned long user_flags
;
1473 old
.seen
= elt
->seen
; old
.deleted
= elt
->deleted
; old
.flagged
= elt
->flagged
;
1474 old
.answered
= elt
->answered
; old
.draft
= elt
->draft
;
1475 old
.user_flags
= elt
->user_flags
;
1477 if (mbx_read_flags (stream
,elt
) && expok
) {
1478 mail_expunged (stream
,elt
->msgno
);
1479 return NIL
; /* return this message was expunged */
1481 if ((old
.seen
!= elt
->seen
) || (old
.deleted
!= elt
->deleted
) ||
1482 (old
.flagged
!= elt
->flagged
) || (old
.answered
!= elt
->answered
) ||
1483 (old
.draft
!= elt
->draft
) || (old
.user_flags
!= elt
->user_flags
))
1484 MM_FLAGS (stream
,msgno
); /* let top level know */
1488 /* MBX read flags from file
1489 * Accepts: MAIL stream
1491 * Returns: non-NIL if message expunged
1494 unsigned long mbx_read_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
1498 fstat (LOCAL
->fd
,&sbuf
); /* get status */
1499 /* make sure file size is good */
1500 if (sbuf
.st_size
< LOCAL
->filesize
) {
1501 sprintf (LOCAL
->buf
,"Mailbox shrank from %lu to %lu in flag read!",
1502 (unsigned long) LOCAL
->filesize
,(unsigned long) sbuf
.st_size
);
1505 /* set the seek pointer */
1506 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1507 elt
->private.special
.text
.size
- 24,L_SET
);
1508 /* read the new flags */
1509 if (read (LOCAL
->fd
,LOCAL
->buf
,14) < 0) {
1510 sprintf (LOCAL
->buf
,"Unable to read new status: %s",strerror (errno
));
1513 if ((LOCAL
->buf
[0] != ';') || (LOCAL
->buf
[13] != '-')) {
1514 LOCAL
->buf
[14] = '\0'; /* tie off buffer for error message */
1515 sprintf (LOCAL
->buf
+50,"Invalid flags for message %lu (%lu %lu): %s",
1516 elt
->msgno
,elt
->private.special
.offset
,
1517 elt
->private.special
.text
.size
,(char *) LOCAL
->buf
);
1518 fatal (LOCAL
->buf
+50);
1520 LOCAL
->buf
[13] = '\0'; /* tie off buffer */
1521 /* calculate system flags */
1522 i
= strtoul (LOCAL
->buf
+9,NIL
,16);
1523 elt
->seen
= i
& fSEEN
? T
: NIL
;
1524 elt
->deleted
= i
& fDELETED
? T
: NIL
;
1525 elt
->flagged
= i
& fFLAGGED
? T
: NIL
;
1526 elt
->answered
= i
& fANSWERED
? T
: NIL
;
1527 elt
->draft
= i
& fDRAFT
? T
: NIL
;
1528 LOCAL
->expunged
|= i
& fEXPUNGED
? T
: NIL
;
1529 LOCAL
->buf
[9] = '\0'; /* tie off flags */
1530 /* get user flags value */
1531 elt
->user_flags
= strtoul (LOCAL
->buf
+1,NIL
,16);
1532 elt
->valid
= T
; /* have valid flags now */
1533 return i
& fEXPUNGED
;
1536 /* MBX update header
1537 * Accepts: MAIL stream
1540 #ifndef CYGKLUDGEOFFSET
1541 #define CYGKLUDGEOFFSET 0
1544 void mbx_update_header (MAILSTREAM
*stream
)
1547 char *s
= LOCAL
->buf
;
1548 memset (s
,'\0',HDRSIZE
); /* initialize header */
1549 sprintf (s
,"*mbx*\015\012%08lx%08lx\015\012",
1550 stream
->uid_validity
,stream
->uid_last
);
1551 for (i
= 0; (i
< NUSERFLAGS
) && stream
->user_flags
[i
]; ++i
)
1552 sprintf (s
+= strlen (s
),"%s\015\012",stream
->user_flags
[i
]);
1553 LOCAL
->ffuserflag
= i
; /* first free user flag */
1554 /* can we create more user flags? */
1555 stream
->kwd_create
= (i
< NUSERFLAGS
) ? T
: NIL
;
1556 /* write reserved lines */
1557 while (i
++ < NUSERFLAGS
) strcat (s
,"\015\012");
1558 sprintf (LOCAL
->buf
+ HDRSIZE
- 10,"%08lx\015\012",LOCAL
->lastpid
);
1559 while (T
) { /* rewind file */
1560 lseek (LOCAL
->fd
,CYGKLUDGEOFFSET
,L_SET
);
1561 /* write new header */
1562 if (write (LOCAL
->fd
,LOCAL
->buf
+ CYGKLUDGEOFFSET
,
1563 HDRSIZE
- CYGKLUDGEOFFSET
) > 0) break;
1564 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1565 MM_DISKERROR (stream
,errno
,T
);
1569 /* MBX update status string
1570 * Accepts: MAIL stream
1575 void mbx_update_status (MAILSTREAM
*stream
,unsigned long msgno
,long flags
)
1578 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1580 if (stream
->rdonly
|| !elt
->valid
) mbx_read_flags (stream
,elt
);
1581 else { /* readwrite */
1582 fstat (LOCAL
->fd
,&sbuf
); /* get status */
1583 /* make sure file size is good */
1584 if (sbuf
.st_size
< LOCAL
->filesize
) {
1585 sprintf (LOCAL
->buf
,"Mailbox shrank from %lu to %lu in flag update!",
1586 (unsigned long) LOCAL
->filesize
,(unsigned long) sbuf
.st_size
);
1589 /* set the seek pointer */
1590 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1591 elt
->private.special
.text
.size
- 24,L_SET
);
1592 /* read the new flags */
1593 if (read (LOCAL
->fd
,LOCAL
->buf
,14) < 0) {
1594 sprintf (LOCAL
->buf
,"Unable to read old status: %s",strerror (errno
));
1597 if ((LOCAL
->buf
[0] != ';') || (LOCAL
->buf
[13] != '-')) {
1598 LOCAL
->buf
[14] = '\0'; /* tie off buffer for error message */
1599 sprintf (LOCAL
->buf
+50,"Invalid flags for message %lu (%lu %lu): %s",
1600 elt
->msgno
,elt
->private.special
.offset
,
1601 elt
->private.special
.text
.size
,(char *) LOCAL
->buf
);
1602 fatal (LOCAL
->buf
+50);
1604 /* print new flag string */
1605 sprintf (LOCAL
->buf
,"%08lx%04x-%08lx",elt
->user_flags
,(unsigned)
1606 (((elt
->deleted
&& flags
) ?
1607 fEXPUNGED
: (strtoul (LOCAL
->buf
+9,NIL
,16)) & fEXPUNGED
) +
1608 (fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
1609 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
1610 (fDRAFT
* elt
->draft
) + fOLD
),elt
->private.uid
);
1611 while (T
) { /* get to that place in the file */
1612 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1613 elt
->private.special
.text
.size
- 23,L_SET
);
1614 /* write new flags and UID */
1615 if (write (LOCAL
->fd
,LOCAL
->buf
,21) > 0) break;
1616 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1617 MM_DISKERROR (stream
,errno
,T
);
1622 /* MBX locate header for a message
1623 * Accepts: MAIL stream
1625 * pointer to returned header size
1626 * pointer to possible returned header
1627 * Returns: position of header in file
1630 #define HDRBUFLEN 16384 /* good enough for most headers */
1631 #define SLOP 4 /* CR LF CR LF */
1633 unsigned long mbx_hdrpos (MAILSTREAM
*stream
,unsigned long msgno
,
1634 unsigned long *size
,char **hdr
)
1636 unsigned long siz
,done
;
1638 unsigned char *s
,*t
,*te
;
1639 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1640 unsigned long ret
= elt
->private.special
.offset
+
1641 elt
->private.special
.text
.size
;
1642 if (hdr
) *hdr
= NIL
; /* assume no header returned */
1643 /* is header size known? */
1644 if (*size
= elt
->private.msg
.header
.text
.size
) return ret
;
1645 /* paranoia check */
1646 if (LOCAL
->buflen
< (HDRBUFLEN
+ SLOP
))
1647 fatal ("LOCAL->buf smaller than HDRBUFLEN");
1648 lseek (LOCAL
->fd
,ret
,L_SET
); /* get to header position */
1649 /* read HDRBUFLEN chunks with 4 byte slop */
1650 for (done
= siz
= 0, s
= LOCAL
->buf
;
1651 (i
= min ((long) (elt
->rfc822_size
- done
),(long) HDRBUFLEN
)) &&
1652 (read (LOCAL
->fd
,s
,i
) == i
);
1653 done
+= i
, siz
+= (t
- LOCAL
->buf
) - SLOP
, s
= LOCAL
->buf
+ SLOP
) {
1654 te
= (t
= s
+ i
) - 12; /* calculate end of fast scan */
1655 /* fast scan for CR */
1656 for (s
= LOCAL
->buf
; s
< te
;)
1657 if (((*s
++ == '\015') || (*s
++ == '\015') || (*s
++ == '\015') ||
1658 (*s
++ == '\015') || (*s
++ == '\015') || (*s
++ == '\015') ||
1659 (*s
++ == '\015') || (*s
++ == '\015') || (*s
++ == '\015') ||
1660 (*s
++ == '\015') || (*s
++ == '\015') || (*s
++ == '\015')) &&
1661 (*s
== '\012') && (*++s
== '\015') && (*++s
== '\012')) {
1662 *size
= elt
->private.msg
.header
.text
.size
= siz
+ (++s
- LOCAL
->buf
);
1663 if (hdr
) *hdr
= LOCAL
->buf
;
1666 for (te
= t
- 3; (s
< te
);) /* final character-at-a-time scan */
1667 if ((*s
++ == '\015') && (*s
== '\012') && (*++s
== '\015') &&
1669 *size
= elt
->private.msg
.header
.text
.size
= siz
+ (++s
- LOCAL
->buf
);
1670 if (hdr
) *hdr
= LOCAL
->buf
;
1673 if (i
<= SLOP
) break; /* end of data */
1674 /* slide over last 4 bytes */
1675 memmove (LOCAL
->buf
,t
- SLOP
,SLOP
);
1676 hdr
= NIL
; /* can't return header this way */
1678 /* not found: header consumes entire message */
1679 elt
->private.msg
.header
.text
.size
= *size
= elt
->rfc822_size
;
1680 if (hdr
) *hdr
= LOCAL
->buf
; /* possibly return header too */
1684 /* MBX mail rewrite mailbox
1685 * Accepts: MAIL stream
1686 * pointer to return reclaimed size
1687 * flags (0 = no expunge, 1 = expunge deleted, -1 = expunge sequence)
1688 * Returns: number of expunged messages
1691 unsigned long mbx_rewrite (MAILSTREAM
*stream
,unsigned long *reclaimed
,
1698 unsigned long i
,j
,k
,m
,delta
;
1699 unsigned long n
= *reclaimed
= 0;
1700 unsigned long recent
= 0;
1701 char lock
[MAILTMPLEN
];
1703 blocknotify_t bn
= (blocknotify_t
) mail_parameters (NIL
,GET_BLOCKNOTIFY
,NIL
);
1704 /* The cretins who designed flock() created a window of vulnerability in
1705 * upgrading locks from shared to exclusive or downgrading from exclusive
1706 * to shared. Rather than maintain the lock at shared status at a minimum,
1707 * flock() actually *releases* the former lock. Obviously they never talked
1708 * to any database guys. Fortunately, we have the parse/append permission
1709 * lock. If we require this lock before going exclusive on the mailbox,
1710 * another process can not sneak in and steal the exclusive mailbox lock on
1711 * us, because it will block on trying to get parse/append permission first.
1713 /* get parse/append permission */
1714 if ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) < 0) {
1715 MM_LOG ("Unable to lock mailbox for rewrite",ERROR
);
1718 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
1719 if (LOCAL
->filetime
&& !LOCAL
->flagcheck
&&
1720 (LOCAL
->filetime
< sbuf
.st_mtime
)) LOCAL
->flagcheck
= T
;
1721 if (!mbx_parse (stream
)) { /* make sure see any newly-arrived messages */
1722 unlockfd (ld
,lock
); /* failed?? */
1725 if (LOCAL
->flagcheck
) { /* sweep flags if need flagcheck */
1726 LOCAL
->filetime
= sbuf
.st_mtime
;
1727 for (i
= 1; i
<= stream
->nmsgs
; ++i
) mbx_elt (stream
,i
,NIL
);
1728 LOCAL
->flagcheck
= NIL
;
1731 /* get exclusive access */
1732 if (!flock (LOCAL
->fd
,LOCK_EX
|LOCK_NB
)) {
1733 MM_CRITICAL (stream
); /* go critical */
1734 for (i
= 1,delta
= 0,pos
= ppos
= HDRSIZE
; i
<= stream
->nmsgs
; ) {
1735 /* note if message not at predicted location */
1736 if (m
= (elt
= mbx_elt (stream
,i
,NIL
))->private.special
.offset
- ppos
) {
1737 ppos
= elt
->private.special
.offset
;
1738 *reclaimed
+= m
; /* note reclaimed message space */
1739 delta
+= m
; /* and as expunge delta */
1741 /* number of bytes to smash or preserve */
1742 ppos
+= (k
= elt
->private.special
.text
.size
+ elt
->rfc822_size
);
1743 /* if need to expunge this message*/
1744 if (flags
&& elt
->deleted
&& ((flags
> 0) || elt
->sequence
)) {
1745 delta
+= k
; /* number of bytes to delete */
1746 mail_expunged(stream
,i
);/* notify upper levels */
1747 n
++; /* count up one more expunged message */
1749 else { /* preserved message */
1750 i
++; /* count this message */
1751 if (elt
->recent
) ++recent
;
1752 if (delta
) { /* moved, note first byte to preserve */
1753 j
= elt
->private.special
.offset
;
1754 do { /* read from source position */
1755 m
= min (k
,LOCAL
->buflen
);
1756 lseek (LOCAL
->fd
,j
,L_SET
);
1757 read (LOCAL
->fd
,LOCAL
->buf
,m
);
1758 pos
= j
- delta
; /* write to destination position */
1760 lseek (LOCAL
->fd
,pos
,L_SET
);
1761 if (write (LOCAL
->fd
,LOCAL
->buf
,m
) > 0) break;
1762 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1763 MM_DISKERROR (stream
,errno
,T
);
1765 pos
+= m
; /* new position */
1766 j
+= m
; /* next chunk, perhaps */
1767 } while (k
-= m
); /* until done */
1768 /* note the new address of this text */
1769 elt
->private.special
.offset
-= delta
;
1771 /* preserved but no deleted messages yet */
1772 else pos
= elt
->private.special
.offset
+ k
;
1775 /* deltaed file size match position? */
1776 if (m
= (LOCAL
->filesize
-= delta
) - pos
) {
1777 *reclaimed
+= m
; /* probably an fEXPUNGED msg */
1778 LOCAL
->filesize
= pos
; /* set correct size */
1780 /* truncate file after last message */
1781 ftruncate (LOCAL
->fd
,LOCAL
->filesize
);
1782 fsync (LOCAL
->fd
); /* force disk update */
1783 MM_NOCRITICAL (stream
); /* release critical */
1784 (*bn
) (BLOCK_FILELOCK
,NIL
);
1785 flock (LOCAL
->fd
,LOCK_SH
); /* allow sharers again */
1786 (*bn
) (BLOCK_NONE
,NIL
);
1789 else { /* can't get exclusive */
1790 (*bn
) (BLOCK_FILELOCK
,NIL
);
1791 flock (LOCAL
->fd
,LOCK_SH
); /* recover previous shared mailbox lock */
1792 (*bn
) (BLOCK_NONE
,NIL
);
1793 /* do hide-expunge when shared */
1794 if (flags
) for (i
= 1; i
<= stream
->nmsgs
; ) {
1795 if (elt
= mbx_elt (stream
,i
,T
)) {
1796 /* make the message invisible */
1797 if (elt
->deleted
&& ((flags
> 0) || elt
->sequence
)) {
1798 mbx_update_status (stream
,elt
->msgno
,LONGT
);
1799 /* notify upper levels */
1800 mail_expunged (stream
,i
);
1801 n
++; /* count up one more expunged message */
1804 i
++; /* preserved message */
1805 if (elt
->recent
) ++recent
;
1808 else n
++; /* count up one more expunged message */
1810 fsync (LOCAL
->fd
); /* force disk update */
1812 fstat (LOCAL
->fd
,&sbuf
); /* get new write time */
1813 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
1814 tp
[0] = time (0); /* reset atime to now */
1815 utime (stream
->mailbox
,tp
);
1816 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
1817 /* notify upper level of new mailbox size */
1818 mail_exists (stream
,stream
->nmsgs
);
1819 mail_recent (stream
,recent
);
1820 return n
; /* return number of expunged messages */
1823 /* MBX mail lock for flag updating
1825 * Returns: T if successful, NIL if failure
1828 long mbx_flaglock (MAILSTREAM
*stream
)
1833 char lock
[MAILTMPLEN
];
1834 /* no-op if readonly or already locked */
1835 if (!stream
->rdonly
&& LOCAL
&& (LOCAL
->fd
>= 0) && (LOCAL
->ld
< 0)) {
1837 if ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) < 0) return NIL
;
1838 if (!LOCAL
->flagcheck
) { /* don't do this if flagcheck already needed */
1839 if (LOCAL
->filetime
) { /* know previous time? */
1840 fstat (LOCAL
->fd
,&sbuf
);/* get current write time */
1841 if (LOCAL
->filetime
< sbuf
.st_mtime
) LOCAL
->flagcheck
= T
;
1842 LOCAL
->filetime
= 0; /* don't do this test for any other messages */
1844 if (!mbx_parse (stream
)) {/* parse mailbox */
1845 unlockfd (ld
,lock
); /* shouldn't happen */
1848 if (LOCAL
->flagcheck
) /* invalidate cache if flagcheck */
1849 for (i
= 1; i
<= stream
->nmsgs
; ++i
) mail_elt (stream
,i
)->valid
= NIL
;
1851 LOCAL
->ld
= ld
; /* copy to stream for subsequent calls */
1852 memcpy (LOCAL
->lock
,lock
,MAILTMPLEN
);