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.
41 extern int errno
; /* just in case */
53 /* Build parameters */
58 /* Kludge to make Cygwin happy */
64 /* MBX I/O stream local data */
66 typedef struct mbx_local
{
67 unsigned int flagcheck
: 1; /* if ping should sweep for flags */
68 unsigned int expok
: 1; /* if expunging OK in ping */
69 unsigned int expunged
: 1; /* if one or more expunged messages */
70 int fd
; /* file descriptor for I/O */
71 int ld
; /* lock file descriptor */
72 int ffuserflag
; /* first free user flag */
73 off_t filesize
; /* file size parsed */
74 time_t filetime
; /* last file time */
75 time_t lastsnarf
; /* last snarf time */
76 unsigned long lastpid
; /* PID of last writer */
77 unsigned char *buf
; /* temporary buffer */
78 unsigned long buflen
; /* current size of temporary buffer */
79 char lock
[MAILTMPLEN
]; /* buffer to write lock name */
83 /* Convenient access to local data */
85 #define LOCAL ((MBXLOCAL *) stream->local)
87 /* Function prototypes */
89 DRIVER
*mbx_valid (char *name
);
90 int mbx_isvalid (MAILSTREAM
**stream
,char *name
,char *tmp
,int *ld
,char *lock
,
92 void *mbx_parameters (long function
,void *value
);
93 void mbx_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
94 void mbx_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
95 void mbx_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
96 long mbx_create (MAILSTREAM
*stream
,char *mailbox
);
97 long mbx_delete (MAILSTREAM
*stream
,char *mailbox
);
98 long mbx_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
99 long mbx_status (MAILSTREAM
*stream
,char *mbx
,long flags
);
100 MAILSTREAM
*mbx_open (MAILSTREAM
*stream
);
101 void mbx_close (MAILSTREAM
*stream
,long options
);
102 void mbx_abort (MAILSTREAM
*stream
);
103 void mbx_flags (MAILSTREAM
*stream
,char *sequence
,long flags
);
104 char *mbx_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
106 long mbx_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
);
107 void mbx_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
);
108 void mbx_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
109 long mbx_ping (MAILSTREAM
*stream
);
110 void mbx_check (MAILSTREAM
*stream
);
111 long mbx_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
112 void mbx_snarf (MAILSTREAM
*stream
);
113 long mbx_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
);
114 long mbx_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
116 char *mbx_file (char *dst
,char *name
);
117 long mbx_parse (MAILSTREAM
*stream
);
118 MESSAGECACHE
*mbx_elt (MAILSTREAM
*stream
,unsigned long msgno
,long expok
);
119 unsigned long mbx_read_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
120 void mbx_update_header (MAILSTREAM
*stream
);
121 void mbx_update_status (MAILSTREAM
*stream
,unsigned long msgno
,long flags
);
122 unsigned long mbx_hdrpos (MAILSTREAM
*stream
,unsigned long msgno
,
123 unsigned long *size
,char **hdr
);
124 unsigned long mbx_rewrite (MAILSTREAM
*stream
,unsigned long *reclaimed
,
126 long mbx_flaglock (MAILSTREAM
*stream
);
128 /* MBX mail routines */
131 /* Driver dispatch used by MAIL */
134 "mbx", /* driver name */
135 DR_LOCAL
|DR_MAIL
|DR_CRLF
|DR_LOCKING
,
137 (DRIVER
*) NIL
, /* next driver */
138 mbx_valid
, /* mailbox is valid for us */
139 mbx_parameters
, /* manipulate parameters */
140 mbx_scan
, /* scan mailboxes */
141 mbx_list
, /* list mailboxes */
142 mbx_lsub
, /* list subscribed mailboxes */
143 NIL
, /* subscribe to mailbox */
144 NIL
, /* unsubscribe from mailbox */
145 mbx_create
, /* create mailbox */
146 mbx_delete
, /* delete mailbox */
147 mbx_rename
, /* rename mailbox */
148 mbx_status
, /* status of mailbox */
149 mbx_open
, /* open mailbox */
150 mbx_close
, /* close mailbox */
151 mbx_flags
, /* fetch message "fast" attributes */
152 mbx_flags
, /* fetch message flags */
153 NIL
, /* fetch overview */
154 NIL
, /* fetch message envelopes */
155 mbx_header
, /* fetch message header */
156 mbx_text
, /* fetch message body */
157 NIL
, /* fetch partial message text */
158 NIL
, /* unique identifier */
159 NIL
, /* message number */
160 mbx_flag
, /* modify flags */
161 mbx_flagmsg
, /* per-message modify flags */
162 NIL
, /* search for message based on criteria */
163 NIL
, /* sort messages */
164 NIL
, /* thread messages */
165 mbx_ping
, /* ping mailbox to see if still alive */
166 mbx_check
, /* check for new messages */
167 mbx_expunge
, /* expunge deleted messages */
168 mbx_copy
, /* copy messages to another mailbox */
169 mbx_append
, /* append string message to mailbox */
170 NIL
, /* garbage collect stream */
171 NIL
/* renew stream */
174 /* prototype stream */
175 MAILSTREAM mbxproto
= {&mbxdriver
};
177 /* MBX mail validate mailbox
178 * Accepts: mailbox name
179 * Returns: our driver if name is valid, NIL otherwise
182 DRIVER
*mbx_valid (char *name
)
184 char tmp
[MAILTMPLEN
];
185 int fd
= mbx_isvalid (NIL
,name
,tmp
,NIL
,NIL
,NIL
);
186 if (fd
< 0) return NIL
;
187 close (fd
); /* don't need the fd now */
192 /* MBX mail test for valid mailbox
193 * Accepts: returned stream with valid mailbox keywords
198 * RW flags or NIL for readonly
199 * Returns: file descriptor if valid, NIL otherwise
202 #define MBXISVALIDNOUID 0x1 /* RW, don't do UID action */
203 #define MBXISVALIDUID 0x2 /* RW, do UID action */
205 int mbx_isvalid (MAILSTREAM
**stream
,char *name
,char *tmp
,int *ld
,char *lock
,
213 char c
,*s
,*t
,hdr
[HDRSIZE
];
216 int error
= EINVAL
; /* assume invalid argument */
217 if (ld
) *ld
= -1; /* initially no lock */
218 if ((s
= mbx_file (tmp
,name
)) && !stat (s
,&sbuf
) &&
219 ((fd
= open (tmp
,(flags
? O_RDWR
: O_RDONLY
)|O_BINARY
,NIL
)) >= 0)) {
220 error
= -1; /* bogus format */
221 /* I love cretinous C compilers -- don't you? */
222 if (read (fd
,hdr
,HDRSIZE
) == HDRSIZE
)
223 if ((hdr
[0] == '*') && (hdr
[1] == 'm') && (hdr
[2] == 'b') &&
224 (hdr
[3] == 'x') && (hdr
[4] == '*') && (hdr
[5] == '\015') &&
225 (hdr
[6] == '\012') && isxdigit (hdr
[7]) && isxdigit (hdr
[8]))
226 if (isxdigit (hdr
[9]) && isxdigit (hdr
[10]) && isxdigit (hdr
[11]) &&
227 isxdigit (hdr
[12]) && isxdigit (hdr
[13]) && isxdigit (hdr
[14]) &&
228 isxdigit (c
= hdr
[15]) && isxdigit (hdr
[16]))
229 if (isxdigit (hdr
[17]) && isxdigit (hdr
[18]) &&
230 isxdigit (hdr
[19]) && isxdigit (hdr
[20]) &&
231 isxdigit (hdr
[21]) && isxdigit (hdr
[22]) &&
232 (hdr
[23] == '\015') && (hdr
[24] == '\012')) {
233 ret
= fd
; /* mbx format */
235 if (stream
) { /* lock if making mini-stream */
236 if (flock (fd
,LOCK_SH
) ||
237 (flags
&& ((*ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0))) ret
= -1;
238 /* reread data now that locked */
239 else if (lseek (fd
,0,L_SET
) ||
240 (read (fd
,hdr
,HDRSIZE
) != HDRSIZE
)) ret
= -1;
242 *stream
= (MAILSTREAM
*) memset (fs_get (sizeof (MAILSTREAM
)),
243 0,sizeof (MAILSTREAM
));
244 hdr
[15] = '\0'; /* tie off UIDVALIDITY */
245 (*stream
)->uid_validity
= strtoul (hdr
+7,NIL
,16);
246 hdr
[15] = c
; /* now get UIDLAST */
247 (*stream
)->uid_last
= strtoul (hdr
+15,NIL
,16);
248 /* parse user flags */
249 for (i
= 0, s
= hdr
+ 25;
250 (i
< NUSERFLAGS
) && (t
= strchr (s
,'\015')) && (t
- s
);
252 *t
= '\0'; /* tie off flag */
253 if (strlen (s
) <= MAXUSERFLAG
)
254 (*stream
)->user_flags
[i
] = cpystr (s
);
256 /* make sure have true UIDLAST */
257 if (flags
& MBXISVALIDUID
) {
258 for (upd
= NIL
,pos
= 2048, k
= 0; pos
< sbuf
.st_size
;
260 /* read header for this message */
261 lseek (fd
,pos
,L_SET
);
262 if ((j
= read (fd
,hdr
,64)) >= 0) {
264 if ((s
= strchr (hdr
,'\015')) && (s
[1] == '\012')) {
267 if ((s
= strchr (hdr
,',')) &&
268 (j
= strtol (s
+1,&s
,10)) && (*s
== ';') &&
269 (s
= strchr (s
+1,'-'))) {
270 /* get UID if there is any */
271 i
= strtoul (++s
,&t
,16);
272 if (!*t
&& (t
== (s
+ 8)) &&
273 (i
<= (*stream
)->uid_last
)) {
275 lseek (fd
,pos
+ s
- hdr
,L_SET
);
276 sprintf (hdr
,"%08lx",++(*stream
)->uid_last
);
284 ret
= -1; /* error, give up */
285 *stream
= mail_close (*stream
);
286 pos
= sbuf
.st_size
+ 1;
291 if (upd
) { /* need to update hdr with new UIDLAST? */
293 sprintf (hdr
,"%08lx",(*stream
)->uid_last
);
300 if (ret
!= fd
) close (fd
); /* close the file */
301 else lseek (fd
,0,L_SET
); /* else rewind to start */
302 /* \Marked status? */
303 if (sbuf
.st_ctime
> sbuf
.st_atime
) {
304 tp
[0] = sbuf
.st_atime
; /* preserve atime and mtime */
305 tp
[1] = sbuf
.st_mtime
;
306 utime (tmp
,tp
); /* set the times */
309 /* in case INBOX but not mbx format */
310 else if (((error
= errno
) == ENOENT
) && !compare_cstring (name
,"INBOX"))
312 if ((ret
< 0) && ld
&& (*ld
>= 0)) {
316 errno
= error
; /* return as last error */
317 return ret
; /* return what we should */
320 /* MBX manipulate driver parameters
321 * Accepts: function code
322 * function-dependent value
323 * Returns: function-dependent return value
326 void *mbx_parameters (long function
,void *value
)
329 switch ((int) function
) {
331 if (value
) ret
= mbx_file ((char *) value
,"INBOX");
333 case SET_ONETIMEEXPUNGEATPING
:
334 if (value
) ((MBXLOCAL
*) ((MAILSTREAM
*) value
)->local
)->expok
= T
;
335 case GET_ONETIMEEXPUNGEATPING
:
336 if (value
) ret
= (void *)
337 (((MBXLOCAL
*) ((MAILSTREAM
*) value
)->local
)->expok
? VOIDT
: NIL
);
344 /* MBX mail scan mailboxes
345 * Accepts: mail stream
351 void mbx_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
353 if (stream
) dummy_scan (NIL
,ref
,pat
,contents
);
357 /* MBX mail list mailboxes
358 * Accepts: mail stream
363 void mbx_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
365 if (stream
) dummy_list (NIL
,ref
,pat
);
369 /* MBX mail list subscribed mailboxes
370 * Accepts: mail stream
375 void mbx_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
377 if (stream
) dummy_lsub (NIL
,ref
,pat
);
380 /* MBX mail create mailbox
381 * Accepts: MAIL stream
382 * mailbox name to create
383 * Returns: T on success, NIL on failure
386 long mbx_create (MAILSTREAM
*stream
,char *mailbox
)
388 char *s
,*t
,mbx
[MAILTMPLEN
],tmp
[HDRSIZE
];
391 if (!(s
= mbx_file (mbx
,mailbox
))) {
392 sprintf (mbx
,"Can't create %.80s: invalid name",mailbox
);
395 /* create underlying file */
396 else if (dummy_create_path (stream
,s
,get_dir_protection (mailbox
))) {
397 /* done if made directory */
398 if ((s
= strrchr (s
,'/')) && !s
[1]) return T
;
399 if ((fd
= open (mbx
,O_WRONLY
|O_BINARY
,NIL
)) < 0) {
400 sprintf (tmp
,"Can't reopen mailbox node %.80s: %s",mbx
,strerror (errno
));
402 unlink (mbx
); /* delete the file */
405 memset (tmp
,'\0',HDRSIZE
);/* initialize header */
406 sprintf (s
= tmp
,"*mbx*\015\012%08lx00000000\015\012",
407 (unsigned long) time (0));
408 for (i
= 0; i
< NUSERFLAGS
; ++i
) {
409 t
= (stream
&& stream
->user_flags
[i
]) ? stream
->user_flags
[i
] :
410 ((t
= default_user_flag (i
)) ? t
: "");
411 sprintf (s
+= strlen (s
),"%s\015\012",t
);
413 if (write (fd
,tmp
,HDRSIZE
) != HDRSIZE
) {
414 sprintf (tmp
,"Can't initialize mailbox node %.80s: %s",
415 mbx
,strerror (errno
));
417 unlink (mbx
); /* delete the file */
419 else ret
= T
; /* success */
420 close (fd
); /* close file */
423 /* set proper protections */
424 return ret
? set_mbx_protections (mailbox
,mbx
) : NIL
;
428 /* MBX mail delete mailbox
429 * Accepts: MAIL stream
430 * mailbox name to delete
431 * Returns: T on success, NIL on failure
434 long mbx_delete (MAILSTREAM
*stream
,char *mailbox
)
436 return mbx_rename (stream
,mailbox
,NIL
);
439 /* MBX mail rename mailbox
440 * Accepts: MAIL stream
442 * new mailbox name (or NIL for delete)
443 * Returns: T on success, NIL on failure
446 long mbx_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
449 char c
,*s
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
452 if (!mbx_file (file
,old
) ||
453 (newname
&& (!((s
= mailboxfile (tmp
,newname
)) && *s
) ||
454 ((s
= strrchr (tmp
,'/')) && !s
[1])))) {
455 sprintf (tmp
,newname
?
456 "Can't rename mailbox %.80s to %.80s: invalid name" :
457 "Can't delete mailbox %.80s: invalid name",
462 else if ((fd
= open (file
,O_RDWR
|O_BINARY
,NIL
)) < 0) {
463 sprintf (tmp
,"Can't open mailbox %.80s: %s",old
,strerror (errno
));
467 /* get parse/append permission */
468 if ((ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0) {
469 MM_LOG ("Unable to lock rename mailbox",ERROR
);
472 /* lock out other users */
473 if (flock (fd
,LOCK_EX
|LOCK_NB
)) {
474 close (fd
); /* couldn't lock, give up on it then */
475 sprintf (tmp
,"Mailbox %.80s is in use by another process",old
);
477 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
481 if (newname
) { /* want rename? */
482 /* found superior to destination name? */
483 if ((s
= strrchr (tmp
,'/')) != NULL
) {
484 c
= *++s
; /* remember first character of inferior */
485 *s
= '\0'; /* tie off to get just superior */
486 /* superior name doesn't exist, create it */
487 if ((stat (tmp
,&sbuf
) || ((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
)) &&
488 !dummy_create_path (stream
,tmp
,get_dir_protection (newname
)))
490 else *s
= c
; /* restore full name */
492 /* rename the file */
493 if (ret
&& rename (file
,tmp
)) {
494 sprintf (tmp
,"Can't rename mailbox %.80s to %.80s: %s",old
,newname
,
497 ret
= NIL
; /* set failure */
500 else if (unlink (file
)) {
501 sprintf (tmp
,"Can't delete mailbox %.80s: %s",old
,strerror (errno
));
503 ret
= NIL
; /* set failure */
505 flock (fd
,LOCK_UN
); /* release lock on the file */
506 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
507 close (fd
); /* close the file */
508 /* recreate file if renamed INBOX */
509 if (ret
&& !compare_cstring (old
,"INBOX")) mbx_create (NIL
,"INBOX");
510 return ret
; /* return success */
514 * Accepts: mail stream
517 * Returns: T on success, NIL on failure
520 long mbx_status (MAILSTREAM
*stream
,char *mbx
,long flags
)
524 MAILSTREAM
*tstream
= NIL
;
525 MAILSTREAM
*systream
= NIL
;
526 /* make temporary stream (unless this mbx) */
527 if (!stream
&& !(stream
= tstream
=
528 mail_open (NIL
,mbx
,OP_READONLY
|OP_SILENT
)))
530 status
.flags
= flags
; /* return status values */
531 status
.messages
= stream
->nmsgs
;
532 status
.recent
= stream
->recent
;
533 if (flags
& SA_UNSEEN
) /* must search to get unseen messages */
534 for (i
= 1,status
.unseen
= 0; i
<= stream
->nmsgs
; i
++)
535 if (!mail_elt (stream
,i
)->seen
) status
.unseen
++;
536 status
.uidnext
= stream
->uid_last
+ 1;
537 status
.uidvalidity
= stream
->uid_validity
;
538 /* calculate post-snarf results */
539 if (!status
.recent
&& stream
->inbox
&&
540 (systream
= mail_open (NIL
,sysinbox (),OP_READONLY
|OP_SILENT
))) {
541 status
.messages
+= systream
->nmsgs
;
542 status
.recent
+= systream
->recent
;
543 if (flags
& SA_UNSEEN
) /* must search to get unseen messages */
544 for (i
= 1; i
<= systream
->nmsgs
; i
++)
545 if (!mail_elt (systream
,i
)->seen
) status
.unseen
++;
546 /* kludge but probably good enough */
547 status
.uidnext
+= systream
->nmsgs
;
549 MM_STATUS(stream
,mbx
,&status
);/* pass status to main program */
550 if (tstream
) mail_close (tstream
);
551 if (systream
) mail_close (systream
);
552 return T
; /* success */
556 * Accepts: stream to open
557 * Returns: stream on success, NIL on failure
560 MAILSTREAM
*mbx_open (MAILSTREAM
*stream
)
564 char tmp
[MAILTMPLEN
];
565 blocknotify_t bn
= (blocknotify_t
) mail_parameters (NIL
,GET_BLOCKNOTIFY
,NIL
);
566 /* return prototype for OP_PROTOTYPE call */
567 if (!stream
) return user_flags (&mbxproto
);
568 if (stream
->local
) fatal ("mbx recycle stream");
569 /* canonicalize the mailbox name */
570 if (!mbx_file (tmp
,stream
->mailbox
)) {
571 sprintf (tmp
,"Can't open - invalid name: %.80s",stream
->mailbox
);
574 if (stream
->rdonly
||
575 (fd
= open (tmp
,O_RDWR
|O_BINARY
,NIL
)) < 0) {
576 if ((fd
= open (tmp
,O_RDONLY
|O_BINARY
,NIL
)) < 0) {
577 sprintf (tmp
,"Can't open mailbox: %s",strerror (errno
));
581 else if (!stream
->rdonly
) { /* got it, but readonly */
582 MM_LOG ("Can't get write access to mailbox, access is readonly",WARN
);
587 stream
->local
= memset (fs_get (sizeof (MBXLOCAL
)),NIL
,sizeof (MBXLOCAL
));
588 LOCAL
->fd
= fd
; /* bind the file */
589 LOCAL
->ld
= -1; /* no flaglock */
590 LOCAL
->buf
= (char *) fs_get (CHUNKSIZE
);
591 LOCAL
->buflen
= CHUNKSIZE
- 1;
592 /* note if an INBOX or not */
593 stream
->inbox
= !compare_cstring (stream
->mailbox
,"INBOX");
594 fs_give ((void **) &stream
->mailbox
);
595 stream
->mailbox
= cpystr (tmp
);
596 /* get parse/append permission */
597 if ((ld
= lockfd (LOCAL
->fd
,tmp
,LOCK_EX
)) < 0) {
598 MM_LOG ("Unable to lock open mailbox",ERROR
);
601 (*bn
) (BLOCK_FILELOCK
,NIL
);
602 flock (LOCAL
->fd
,LOCK_SH
); /* lock the file */
603 (*bn
) (BLOCK_NONE
,NIL
);
604 unlockfd (ld
,tmp
); /* release shared parse permission */
605 LOCAL
->filesize
= HDRSIZE
; /* initialize parsed file size */
606 /* time not set up yet */
607 LOCAL
->lastsnarf
= LOCAL
->filetime
= 0;
608 LOCAL
->expok
= LOCAL
->flagcheck
= NIL
;
609 stream
->sequence
++; /* bump sequence number */
611 stream
->nmsgs
= stream
->recent
= 0;
612 silent
= stream
->silent
; /* defer events */
614 if (mbx_ping (stream
) && !stream
->nmsgs
)
615 MM_LOG ("Mailbox is empty",(long) NIL
);
616 stream
->silent
= silent
; /* now notify upper level */
617 mail_exists (stream
,stream
->nmsgs
);
618 mail_recent (stream
,stream
->recent
);
619 if (!LOCAL
) return NIL
; /* failure if stream died */
620 stream
->perm_seen
= stream
->perm_deleted
= stream
->perm_flagged
=
621 stream
->perm_answered
= stream
->perm_draft
= stream
->rdonly
? NIL
: T
;
622 stream
->perm_user_flags
= stream
->rdonly
? NIL
: 0xffffffff;
623 stream
->kwd_create
= (stream
->user_flags
[NUSERFLAGS
-1] || stream
->rdonly
) ?
624 NIL
: T
; /* can we create new user flags? */
625 return stream
; /* return stream to caller */
629 * Accepts: MAIL stream
633 void mbx_close (MAILSTREAM
*stream
,long options
)
635 if (stream
&& LOCAL
) { /* only if a file is open */
636 int silent
= stream
->silent
;
637 stream
->silent
= T
; /* note this stream is dying */
638 /* do an expunge if requested */
639 if (options
& CL_EXPUNGE
) mbx_expunge (stream
,NIL
,NIL
);
640 else { /* otherwise do a checkpoint to purge */
641 LOCAL
->expok
= T
; /* possible expunged messages */
644 stream
->silent
= silent
; /* restore previous status */
650 /* MBX mail abort stream
651 * Accepts: MAIL stream
654 void mbx_abort (MAILSTREAM
*stream
)
656 if (stream
&& LOCAL
) { /* only if a file is open */
657 flock (LOCAL
->fd
,LOCK_UN
); /* unlock local file */
658 close (LOCAL
->fd
); /* close the local file */
659 /* free local text buffer */
660 if (LOCAL
->buf
) fs_give ((void **) &LOCAL
->buf
);
661 /* nuke the local data */
662 fs_give ((void **) &stream
->local
);
663 stream
->dtb
= NIL
; /* log out the DTB */
668 /* MBX mail fetch flags
669 * Accepts: MAIL stream
672 * Sniffs at file to see if some other process changed the flags
675 void mbx_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
679 if (mbx_ping (stream
) && /* ping mailbox, get new status for messages */
680 ((flags
& FT_UID
) ? mail_uid_sequence (stream
,sequence
) :
681 mail_sequence (stream
,sequence
)))
682 for (i
= 1; i
<= stream
->nmsgs
; i
++)
683 if ((elt
= mail_elt (stream
,i
))->sequence
&& !elt
->valid
)
684 mbx_elt (stream
,i
,NIL
);
687 /* MBX mail fetch message header
688 * Accepts: MAIL stream
690 * pointer to returned header text length
692 * Returns: message header in RFC822 format
695 char *mbx_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
700 *length
= 0; /* default to empty */
701 if (flags
& FT_UID
) return "";/* UID call "impossible" */
702 /* get header position, possibly header */
703 i
= mbx_hdrpos (stream
,msgno
,length
,&s
);
704 if (!s
) { /* mbx_hdrpos() returned header? */
705 lseek (LOCAL
->fd
,i
,L_SET
); /* no, get to header position */
706 /* is buffer big enough? */
707 if (*length
> LOCAL
->buflen
) {
708 fs_give ((void **) &LOCAL
->buf
);
709 LOCAL
->buf
= (char *) fs_get ((LOCAL
->buflen
= *length
) + 1);
712 read (LOCAL
->fd
,s
= LOCAL
->buf
,*length
);
714 s
[*length
] = '\0'; /* tie off string */
718 /* MBX mail fetch message text (body only)
719 * Accepts: MAIL stream
721 * pointer to returned header text length
723 * Returns: T on success, NIL on failure
726 long mbx_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
731 /* UID call "impossible" */
732 if (flags
& FT_UID
) return NIL
;
733 /* get message status */
734 elt
= mbx_elt (stream
,msgno
,NIL
);
735 /* if message not seen */
736 if (!(flags
& FT_PEEK
) && !elt
->seen
&& mbx_flaglock (stream
)) {
737 elt
->seen
= T
; /* mark message as seen */
738 /* recalculate status */
739 mbx_update_status (stream
,msgno
,NIL
);
740 MM_FLAGS (stream
,msgno
);
742 mbx_flag (stream
,NIL
,NIL
,NIL
);
744 if (!LOCAL
) return NIL
; /* mbx_flaglock() could have aborted */
745 /* find header position */
746 i
= mbx_hdrpos (stream
,msgno
,&j
,NIL
);
747 d
.fd
= LOCAL
->fd
; /* set up file descriptor */
749 d
.chunk
= LOCAL
->buf
; /* initial buffer chunk */
750 d
.chunksize
= CHUNKSIZE
;
751 INIT (bs
,fd_string
,&d
,elt
->rfc822_size
- j
);
752 return LONGT
; /* success */
755 /* MBX mail modify flags
756 * Accepts: MAIL stream
763 void mbx_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
)
767 unsigned long oldpid
= LOCAL
->lastpid
;
768 /* make sure the update takes */
769 if (!stream
->rdonly
&& LOCAL
&& (LOCAL
->fd
>= 0) && (LOCAL
->ld
>= 0)) {
771 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
772 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
773 /* we are the last flag updater */
774 LOCAL
->lastpid
= (unsigned long) getpid ();
775 /* update header if needed */
776 if (((LOCAL
->ffuserflag
< NUSERFLAGS
) &&
777 stream
->user_flags
[LOCAL
->ffuserflag
]) || (oldpid
!= LOCAL
->lastpid
))
778 mbx_update_header (stream
);
779 tp
[0] = time (0); /* make sure read comes after all that */
780 utime (stream
->mailbox
,tp
);
782 if (LOCAL
->ld
>= 0) { /* unlock now */
783 unlockfd (LOCAL
->ld
,LOCAL
->lock
);
789 /* MBX mail per-message modify flags
790 * Accepts: MAIL stream
791 * message cache element
794 void mbx_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
796 if (mbx_flaglock (stream
)) mbx_update_status (stream
,elt
->msgno
,NIL
);
799 /* MBX mail ping mailbox
800 * Accepts: MAIL stream
801 * Returns: T if stream still alive, NIL if not
804 long mbx_ping (MAILSTREAM
*stream
)
809 char lock
[MAILTMPLEN
];
812 if (stream
&& LOCAL
) { /* only if stream already open */
813 int snarf
= stream
->inbox
&& !stream
->rdonly
;
814 ret
= LONGT
; /* assume OK */
815 fstat (LOCAL
->fd
,&sbuf
); /* get current file poop */
816 /* allow expunge if permitted at ping */
817 if (mail_parameters (NIL
,GET_EXPUNGEATPING
,NIL
)) LOCAL
->expok
= T
;
818 /* if external modification */
819 if (LOCAL
->filetime
&& (LOCAL
->filetime
< sbuf
.st_mtime
))
820 LOCAL
->flagcheck
= T
; /* upgrade to flag checking */
821 /* new mail or flagcheck handling needed? */
822 if (((sbuf
.st_size
- LOCAL
->filesize
) || LOCAL
->flagcheck
||
823 !stream
->nmsgs
|| snarf
) &&
824 ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) >= 0)) {
825 /* reparse header if not flagchecking */
826 if (!LOCAL
->flagcheck
) ret
= mbx_parse (stream
);
827 /* sweep mailbox for changed message status */
828 else if ((ret
= mbx_parse (stream
)) != 0L) {
829 unsigned long recent
= 0;
830 LOCAL
->filetime
= sbuf
.st_mtime
;
831 for (i
= 1; i
<= stream
->nmsgs
; )
832 if ((elt
= mbx_elt (stream
,i
,LOCAL
->expok
)) != NULL
) {
833 if (elt
->recent
) ++recent
;
836 mail_recent (stream
,recent
);
837 LOCAL
->flagcheck
= NIL
; /* got all the updates */
839 /* always reparse header at least */
840 if (ret
&& snarf
) { /* snarf new messages if still OK */
842 /* parse snarfed messages */
843 ret
= mbx_parse (stream
);
845 unlockfd (ld
,lock
); /* release shared parse/append permission */
847 if (ret
) { /* must still be alive */
848 if (!LOCAL
->expunged
) /* look for holes if none known yet */
849 for (i
= 1, pos
= HDRSIZE
;
850 !LOCAL
->expunged
&& (i
<= stream
->nmsgs
);
851 i
++, pos
+= elt
->private.special
.text
.size
+ elt
->rfc822_size
)
852 if ((elt
= mail_elt (stream
,i
))->private.special
.offset
!= pos
)
853 LOCAL
->expunged
= T
;/* found a hole */
855 if (LOCAL
->expunged
&& !stream
->rdonly
) {
856 if (mbx_rewrite (stream
,&i
,NIL
)) fatal ("expunge on check");
857 if (i
) { /* any space reclaimed? */
858 LOCAL
->expunged
= NIL
;/* no more pending expunge */
859 sprintf (LOCAL
->buf
,"Reclaimed %lu bytes of expunged space",i
);
860 MM_LOG (LOCAL
->buf
,(long) NIL
);
863 LOCAL
->expok
= NIL
; /* no more expok */
866 return ret
; /* return result of the parse */
869 /* MBX mail check mailbox (reparses status too)
870 * Accepts: MAIL stream
873 void mbx_check (MAILSTREAM
*stream
)
875 if (LOCAL
) LOCAL
->expok
= T
; /* mark that a check is desired */
876 if (mbx_ping (stream
)) MM_LOG ("Check completed",(long) NIL
);
880 /* MBX mail expunge mailbox
881 * Accepts: MAIL stream
882 * sequence to expunge if non-NIL
884 * Returns: T if success, NIL if failure
887 long mbx_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
890 unsigned long nexp
,reclaimed
;
891 if ((ret
= sequence
? ((options
& EX_UID
) ?
892 mail_uid_sequence (stream
,sequence
) :
893 mail_sequence (stream
,sequence
)) : LONGT
) != 0L) {
894 if (!mbx_ping (stream
)); /* do nothing if stream dead */
895 else if (stream
->rdonly
) /* won't do on readonly files! */
896 MM_LOG ("Expunge ignored on readonly mailbox",WARN
);
897 /* if expunged any messages */
898 else if ((nexp
= mbx_rewrite (stream
,&reclaimed
,sequence
? -1 : 1)) != 0L){
899 sprintf (LOCAL
->buf
,"Expunged %lu messages",nexp
);
900 MM_LOG (LOCAL
->buf
,(long) NIL
);
902 else if (reclaimed
) { /* or if any prior expunged space reclaimed */
903 sprintf (LOCAL
->buf
,"Reclaimed %lu bytes of expunged space",reclaimed
);
904 MM_LOG (LOCAL
->buf
,(long) NIL
);
906 else MM_LOG ("No messages deleted, so no update needed",(long) NIL
);
911 /* MBX mail snarf messages from system inbox
912 * Accepts: MAIL stream, already locked
915 void mbx_snarf (MAILSTREAM
*stream
)
918 unsigned long j
,r
,hdrlen
,txtlen
;
920 char *hdr
,*txt
,tmp
[MAILTMPLEN
];
922 MAILSTREAM
*sysibx
= NIL
;
923 /* give up if can't get exclusive permission */
924 if ((time (0) >= (LOCAL
->lastsnarf
+
925 (long) mail_parameters (NIL
,GET_SNARFINTERVAL
,NIL
))) &&
926 strcmp (sysinbox (),stream
->mailbox
)) {
927 MM_CRITICAL (stream
); /* go critical */
928 /* sizes match and anything in sysinbox? */
929 if (!stat (sysinbox (),&sbuf
) && sbuf
.st_size
&&
930 !fstat (LOCAL
->fd
,&sbuf
) && (sbuf
.st_size
== LOCAL
->filesize
) &&
931 (sysibx
= mail_open (sysibx
,sysinbox (),OP_SILENT
)) &&
932 (!sysibx
->rdonly
) && (r
= sysibx
->nmsgs
)) {
933 /* yes, go to end of file in our mailbox */
934 lseek (LOCAL
->fd
,sbuf
.st_size
,L_SET
);
935 /* for each message in sysibx mailbox */
936 while (r
&& (++i
<= sysibx
->nmsgs
)) {
937 /* snarf message from system INBOX */
938 hdr
= cpystr (mail_fetchheader_full (sysibx
,i
,NIL
,&hdrlen
,NIL
));
939 txt
= mail_fetchtext_full (sysibx
,i
,&txtlen
,FT_PEEK
);
940 /* if have a message */
941 if ((j
= hdrlen
+ txtlen
) != 0L){
942 /* build header line */
943 mail_date (LOCAL
->buf
,elt
= mail_elt (sysibx
,i
));
944 sprintf (LOCAL
->buf
+ strlen (LOCAL
->buf
),
945 ",%lu;00000000%04x-00000000\015\012",j
,(unsigned)
946 ((fSEEN
* elt
->seen
) +
947 (fDELETED
* elt
->deleted
) + (fFLAGGED
* elt
->flagged
) +
948 (fANSWERED
* elt
->answered
) + (fDRAFT
* elt
->draft
)));
950 if ((write (LOCAL
->fd
,LOCAL
->buf
,strlen (LOCAL
->buf
)) < 0) ||
951 (write (LOCAL
->fd
,hdr
,hdrlen
) < 0) ||
952 (write (LOCAL
->fd
,txt
,txtlen
) < 0)) r
= 0;
954 fs_give ((void **) &hdr
);
957 /* make sure all the updates take */
958 if (fsync (LOCAL
->fd
)) r
= 0;
959 if (r
) { /* delete all the messages we copied */
960 if (r
== 1) strcpy (tmp
,"1");
961 else sprintf (tmp
,"1:%lu",r
);
962 mail_setflag (sysibx
,tmp
,"\\Deleted");
963 mail_expunge (sysibx
); /* now expunge all those messages */
966 sprintf (LOCAL
->buf
,"Can't copy new mail: %s",strerror (errno
));
967 MM_LOG (LOCAL
->buf
,WARN
);
968 ftruncate (LOCAL
->fd
,sbuf
.st_size
);
970 fstat (LOCAL
->fd
,&sbuf
); /* yes, get current file size */
971 LOCAL
->filetime
= sbuf
.st_mtime
;
973 if (sysibx
) mail_close (sysibx
);
974 MM_NOCRITICAL (stream
); /* release critical */
975 LOCAL
->lastsnarf
= time (0);/* note time of last snarf */
979 /* MBX mail copy message(s)
980 * Accepts: MAIL stream
982 * destination mailbox
984 * Returns: T if success, NIL if failed
987 long mbx_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
992 unsigned long i
,j
,k
,m
;
995 char *s
,*t
,file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
997 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
998 copyuid_t cu
= (copyuid_t
) mail_parameters (NIL
,GET_COPYUID
,NIL
);
999 SEARCHSET
*source
= cu
? mail_newsearchset () : NIL
;
1000 SEARCHSET
*dest
= cu
? mail_newsearchset () : NIL
;
1001 MAILSTREAM
*dstream
= NIL
;
1002 if (!((options
& CP_UID
) ? mail_uid_sequence (stream
,sequence
) :
1003 mail_sequence (stream
,sequence
))) return NIL
;
1004 /* make sure valid mailbox */
1005 if ((fd
= mbx_isvalid (&dstream
,mailbox
,file
,&ld
,lock
,
1006 cu
? MBXISVALIDUID
: MBXISVALIDNOUID
)) < 0)
1008 case ENOENT
: /* no such file? */
1009 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before copy",NIL
);
1011 case EACCES
: /* file protected */
1012 sprintf (LOCAL
->buf
,"Can't access destination: %.80s",mailbox
);
1013 MM_LOG (LOCAL
->buf
,ERROR
);
1016 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
1017 sprintf (LOCAL
->buf
,"Invalid MBX-format mailbox name: %.80s",mailbox
);
1018 MM_LOG (LOCAL
->buf
,ERROR
);
1021 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
1022 sprintf (LOCAL
->buf
,"Not a MBX-format mailbox: %.80s",mailbox
);
1023 MM_LOG (LOCAL
->buf
,ERROR
);
1026 MM_CRITICAL (stream
); /* go critical */
1027 fstat (fd
,&sbuf
); /* get current file size */
1028 lseek (fd
,sbuf
.st_size
,L_SET
);/* move to end of file */
1030 /* for each requested message */
1031 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); i
++)
1032 if ((elt
= mail_elt (stream
,i
))->sequence
) {
1033 lseek (LOCAL
->fd
,elt
->private.special
.offset
+
1034 elt
->private.special
.text
.size
,L_SET
);
1035 mail_date(LOCAL
->buf
,elt
);/* build target header */
1036 /* get target keyword mask */
1037 for (j
= elt
->user_flags
, k
= 0; j
; )
1038 if ((s
= stream
->user_flags
[find_rightmost_bit (&j
)]) != NULL
)
1039 for (m
= 0; (m
< NUSERFLAGS
) && (t
= dstream
->user_flags
[m
]); m
++)
1040 if (!compare_cstring (s
,t
) && (k
|= 1 << m
)) break;
1041 sprintf (LOCAL
->buf
+strlen(LOCAL
->buf
),",%lu;%08lx%04x-%08lx\015\012",
1042 elt
->rfc822_size
,k
,(unsigned)
1043 ((fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
1044 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
1045 (fDRAFT
* elt
->draft
)),cu
? ++dstream
->uid_last
: 0);
1046 /* write target header */
1047 if ((ret
= (write (fd
,LOCAL
->buf
,strlen (LOCAL
->buf
)) > 0)) != 0L) {
1048 for (k
= elt
->rfc822_size
; ret
&& (j
= min (k
,LOCAL
->buflen
)); k
-= j
){
1049 read (LOCAL
->fd
,LOCAL
->buf
,j
);
1050 ret
= write (fd
,LOCAL
->buf
,j
) >= 0;
1052 if (cu
) { /* need to pass back new UID? */
1053 mail_append_set (source
,mail_uid (stream
,i
));
1054 mail_append_set (dest
,dstream
->uid_last
);
1059 /* make sure all the updates take */
1060 if (!(ret
&& (ret
= !fsync (fd
)))) {
1061 sprintf (LOCAL
->buf
,"Unable to write message: %s",strerror (errno
));
1062 MM_LOG (LOCAL
->buf
,ERROR
);
1063 ftruncate (fd
,sbuf
.st_size
);
1065 if (cu
&& ret
) { /* return sets if doing COPYUID */
1066 (*cu
) (stream
,mailbox
,dstream
->uid_validity
,source
,dest
);
1067 lseek (fd
,15,L_SET
); /* update UIDLAST */
1068 sprintf (LOCAL
->buf
,"%08lx",dstream
->uid_last
);
1069 write (fd
,LOCAL
->buf
,8);
1071 else { /* flush any sets we may have built */
1072 mail_free_searchset (&source
);
1073 mail_free_searchset (&dest
);
1075 if (ret
) tp
[0] = time (0) - 1;/* set atime to now-1 if successful copy */
1076 /* else preserve \Marked status */
1077 else tp
[0] = (sbuf
.st_ctime
> sbuf
.st_atime
) ? sbuf
.st_atime
: time(0);
1078 tp
[1] = sbuf
.st_mtime
; /* preserve mtime */
1079 utime (file
,tp
); /* set the times */
1080 close (fd
); /* close the file */
1081 MM_NOCRITICAL (stream
); /* release critical */
1082 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
1083 /* delete all requested messages */
1084 if (ret
&& (options
& CP_MOVE
) && mbx_flaglock (stream
)) {
1085 for (i
= 1; i
<= stream
->nmsgs
; i
++) if (mail_elt (stream
,i
)->sequence
) {
1086 /* mark message deleted */
1087 mbx_elt (stream
,i
,NIL
)->deleted
= T
;
1088 /* recalculate status */
1089 mbx_update_status (stream
,i
,NIL
);
1092 mbx_flag (stream
,NIL
,NIL
,NIL
);
1094 if (dstream
!= stream
) mail_close (dstream
);
1098 /* MBX mail append message from stringstruct
1099 * Accepts: MAIL stream
1100 * destination mailbox
1103 * Returns: T if append successful, else NIL
1106 long mbx_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
1110 char *flags
,*date
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
1118 MAILSTREAM
*dstream
= NIL
;
1119 appenduid_t au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
);
1120 SEARCHSET
*dst
= au
? mail_newsearchset () : NIL
;
1121 /* make sure valid mailbox */
1122 if ((fd
= mbx_isvalid (&dstream
,mailbox
,file
,&ld
,lock
,
1123 au
? MBXISVALIDUID
: MBXISVALIDNOUID
)) < 0)
1125 case ENOENT
: /* no such file? */
1126 if (compare_cstring (mailbox
,"INBOX")) {
1127 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before append",NIL
);
1130 /* can create INBOX here */
1131 mbx_create (dstream
= stream
? stream
: user_flags (&mbxproto
),"INBOX");
1132 if ((fd
= mbx_isvalid (&dstream
,mailbox
,file
,&ld
,lock
,
1133 au
? MBXISVALIDUID
: MBXISVALIDNOUID
)) >= 0)
1135 case EACCES
: /* file protected */
1136 sprintf (tmp
,"Can't access destination: %.80s",mailbox
);
1140 sprintf (tmp
,"Invalid MBX-format mailbox name: %.80s",mailbox
);
1144 sprintf (tmp
,"Not a MBX-format mailbox: %.80s",mailbox
);
1149 /* get first message */
1150 if (!MM_APPEND (af
) (dstream
,data
,&flags
,&date
,&message
)) close (fd
);
1151 else if (!(df
= fdopen (fd
,"r+b"))) {
1152 MM_LOG ("Unable to reopen append mailbox",ERROR
);
1156 MM_CRITICAL (dstream
); /* go critical */
1157 fstat (fd
,&sbuf
); /* get current file size */
1158 fseek (df
,sbuf
.st_size
,SEEK_SET
);
1160 for (ret
= LONGT
; ret
&& message
; ) {
1161 if (!SIZE (message
)) { /* guard against zero-length */
1162 MM_LOG ("Append of zero-length message",ERROR
);
1166 f
= mail_parse_flags (dstream
,flags
,&uf
);
1167 if (date
) { /* parse date if given */
1168 if (!mail_parse_date (&elt
,date
)) {
1169 sprintf (tmp
,"Bad date in append: %.80s",date
);
1171 ret
= NIL
; /* mark failure */
1174 mail_date (tmp
,&elt
); /* write preserved date */
1176 else internal_date (tmp
); /* get current date in IMAP format */
1178 if (fprintf (df
,"%s,%lu;%08lx%04lx-%08lx\015\012",tmp
,i
= SIZE (message
),
1179 uf
,(unsigned long) f
,au
? ++dstream
->uid_last
: 0) < 0)
1181 else { /* write message */
1183 if (!message
->cursize
) SETPOS (message
,GETPOS (message
));
1184 for (errno
= 0; !errno
&& i
&&
1185 (j
= fwrite (message
->curpos
,1,message
->cursize
,df
)); i
-= j
) {
1186 SETPOS (message
,GETPOS (message
) + j
);
1188 /* get next message */
1189 if (i
|| !MM_APPEND (af
) (dstream
,data
,&flags
,&date
,&message
))
1191 else if (au
) mail_append_set (dst
,dstream
->uid_last
);
1196 if (!ret
|| (fflush (df
) == EOF
)) {
1198 ftruncate (fd
,sbuf
.st_size
);
1199 close (fd
); /* make sure fclose() doesn't corrupt us */
1201 sprintf (tmp
,"Message append failed: %s",strerror (errno
));
1206 if (au
&& ret
) { /* return sets if doing APPENDUID */
1207 (*au
) (mailbox
,dstream
->uid_validity
,dst
);
1208 fseek (df
,15,SEEK_SET
); /* update UIDLAST */
1209 fprintf (df
,"%08lx",dstream
->uid_last
);
1211 else mail_free_searchset (&dst
);
1212 /* set atime to now-1 if successful copy */
1213 if (ret
) tp
[0] = time (0) - 1;
1214 /* else preserve \Marked status */
1215 else tp
[0] = (sbuf
.st_ctime
> sbuf
.st_atime
) ? sbuf
.st_atime
: time(0);
1216 tp
[1] = sbuf
.st_mtime
; /* preserve mtime */
1217 utime (file
,tp
); /* set the times */
1218 fclose (df
); /* close the file */
1219 MM_NOCRITICAL (dstream
); /* release critical */
1221 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
1222 if (dstream
!= stream
) mail_close (dstream
);
1226 /* Internal routines */
1229 /* MBX mail generate file string
1230 * Accepts: temporary buffer to write into
1231 * mailbox name string
1232 * Returns: local file string or NIL if failure
1235 char *mbx_file (char *dst
,char *name
)
1237 char *s
= mailboxfile (dst
,name
);
1238 return (s
&& !*s
) ? mailboxfile (dst
,"~/INBOX") : s
;
1241 /* MBX mail parse mailbox
1242 * Accepts: MAIL stream
1243 * Returns: T if parse OK
1244 * NIL if failure, stream aborted
1247 long mbx_parse (MAILSTREAM
*stream
)
1250 MESSAGECACHE
*elt
= NIL
;
1251 unsigned char c
,*s
,*t
,*x
;
1252 char tmp
[MAILTMPLEN
];
1253 unsigned long i
,j
,k
,m
;
1254 off_t curpos
= LOCAL
->filesize
;
1255 unsigned long nmsgs
= stream
->nmsgs
;
1256 unsigned long recent
= stream
->recent
;
1257 unsigned long lastuid
= 0;
1260 short silent
= stream
->silent
;
1262 fstat (LOCAL
->fd
,&sbuf
); /* get status */
1263 if (sbuf
.st_size
< curpos
) { /* sanity check */
1264 sprintf (tmp
,"Mailbox shrank from %lu to %lu!",
1265 (unsigned long) curpos
,(unsigned long) sbuf
.st_size
);
1270 lseek (LOCAL
->fd
,0,L_SET
); /* rewind file */
1271 /* read internal header */
1272 read (LOCAL
->fd
,LOCAL
->buf
,HDRSIZE
);
1273 LOCAL
->buf
[HDRSIZE
] = '\0'; /* tie off header */
1274 c
= LOCAL
->buf
[15]; /* save first character of last UID */
1275 LOCAL
->buf
[15] = '\0';
1276 /* parse UID validity */
1277 stream
->uid_validity
= strtoul (LOCAL
->buf
+ 7,NIL
,16);
1278 LOCAL
->buf
[15] = c
; /* restore first character of last UID */
1279 /* parse last UID */
1280 i
= strtoul (LOCAL
->buf
+ 15,NIL
,16);
1281 stream
->uid_last
= stream
->rdonly
? max (i
,stream
->uid_last
) : i
;
1282 /* parse user flags */
1283 for (i
= 0, s
= LOCAL
->buf
+ 25;
1284 (i
< NUSERFLAGS
) && (t
= strchr (s
,'\015')) && (t
- s
);
1286 *t
= '\0'; /* tie off flag */
1287 if (!stream
->user_flags
[i
] && (strlen (s
) <= MAXUSERFLAG
))
1288 stream
->user_flags
[i
] = cpystr (s
);
1290 LOCAL
->ffuserflag
= (int) i
; /* first free user flag */
1292 /* get current last flag updater PID */
1293 i
= (isxdigit (LOCAL
->buf
[HDRSIZE
-10]) && isxdigit (LOCAL
->buf
[HDRSIZE
-9]) &&
1294 isxdigit (LOCAL
->buf
[HDRSIZE
-8]) && isxdigit (LOCAL
->buf
[HDRSIZE
-7]) &&
1295 isxdigit (LOCAL
->buf
[HDRSIZE
-6]) && isxdigit (LOCAL
->buf
[HDRSIZE
-5]) &&
1296 isxdigit (LOCAL
->buf
[HDRSIZE
-4]) && isxdigit (LOCAL
->buf
[HDRSIZE
-3]) &&
1297 (LOCAL
->buf
[HDRSIZE
-2] == '\015') && (LOCAL
->buf
[HDRSIZE
-1] == '\012'))?
1298 strtoul (LOCAL
->buf
+ HDRSIZE
- 8,NIL
,16) : 0;
1299 /* set flagcheck if lastpid changed */
1300 if (LOCAL
->lastpid
&& (LOCAL
->lastpid
!= i
)) LOCAL
->flagcheck
= T
;
1301 LOCAL
->lastpid
= i
; /* set as last PID */
1302 stream
->silent
= T
; /* don't pass up exists events yet */
1303 while (sbuf
.st_size
- curpos
){/* while there is stuff to parse */
1304 /* get to that position in the file */
1305 lseek (LOCAL
->fd
,curpos
,L_SET
);
1306 if ((i
= read (LOCAL
->fd
,LOCAL
->buf
,64)) <= 0) {
1307 sprintf (tmp
,"Unable to read internal header at %lu, size = %lu: %s",
1308 (unsigned long) curpos
,(unsigned long) sbuf
.st_size
,
1309 i
? strerror (errno
) : "no data read");
1314 LOCAL
->buf
[i
] = '\0'; /* tie off buffer just in case */
1315 if (!((s
= strchr (LOCAL
->buf
,'\015')) && (s
[1] == '\012'))) {
1316 sprintf (tmp
,"Unable to find CRLF at %lu in %lu bytes, text: %.80s",
1317 (unsigned long) curpos
,i
,(char *) LOCAL
->buf
);
1322 *s
= '\0'; /* tie off header line */
1323 i
= (s
+ 2) - LOCAL
->buf
; /* note start of text offset */
1324 if (!((s
= strchr (LOCAL
->buf
,',')) && (t
= strchr (s
+1,';')))) {
1325 sprintf (tmp
,"Unable to parse internal header at %lu: %.80s",
1326 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1331 if (!(isxdigit (t
[1]) && isxdigit (t
[2]) && isxdigit (t
[3]) &&
1332 isxdigit (t
[4]) && isxdigit (t
[5]) && isxdigit (t
[6]) &&
1333 isxdigit (t
[7]) && isxdigit (t
[8]) && isxdigit (t
[9]) &&
1334 isxdigit (t
[10]) && isxdigit (t
[11]) && isxdigit (t
[12]))) {
1335 sprintf (tmp
,"Unable to parse message flags at %lu: %.80s",
1336 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1341 if ((t
[13] != '-') || t
[22] ||
1342 !(isxdigit (t
[14]) && isxdigit (t
[15]) && isxdigit (t
[16]) &&
1343 isxdigit (t
[17]) && isxdigit (t
[18]) && isxdigit (t
[19]) &&
1344 isxdigit (t
[20]) && isxdigit (t
[21]))) {
1345 sprintf (tmp
,"Unable to parse message UID at %lu: %.80s",
1346 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1352 *s
++ = '\0'; *t
++ = '\0'; /* break up fields */
1353 /* get message size */
1354 if (!(j
= strtoul (s
,(char **) &x
,10)) && (!(x
&& *x
))) {
1355 sprintf (tmp
,"Unable to parse message size at %lu: %.80s,%.80s;%.80s",
1356 (unsigned long) curpos
,(char *) LOCAL
->buf
,(char *) s
,
1362 /* make sure didn't run off end of file */
1363 if (((off_t
) (curpos
+ i
+ j
)) > sbuf
.st_size
) {
1364 sprintf (tmp
,"Last message (at %lu) runs past end of file (%lu > %lu)",
1365 (unsigned long) curpos
,(unsigned long) (curpos
+ i
+ j
),
1366 (unsigned long) sbuf
.st_size
);
1372 if ((m
= strtoul (t
+13,NIL
,16)) &&
1373 ((m
<= lastuid
) || (m
> stream
->uid_last
))) {
1375 sprintf (tmp
,"Invalid UID %08lx in message %lu, rebuilding UIDs",
1379 /* restart UID validity */
1380 stream
->uid_validity
= time (0);
1382 m
= 0; /* lose this UID */
1383 dirty
= T
; /* mark dirty, set new lastuid */
1384 stream
->uid_last
= lastuid
;
1387 t
[12] = '\0'; /* parse system flags */
1388 if ((k
= strtoul (t
+8,NIL
,16)) & fEXPUNGED
) {
1389 if (m
) lastuid
= m
; /* expunge message, update last UID seen */
1390 else { /* no UID assigned? */
1391 lastuid
= ++stream
->uid_last
;
1395 else { /* not expunged, swell the cache */
1396 added
= T
; /* note that a new message was added */
1397 mail_exists (stream
,++nmsgs
);
1398 /* instantiate an elt for this message */
1399 (elt
= mail_elt (stream
,nmsgs
))->valid
= T
;
1400 /* parse the date */
1401 if (!mail_parse_date (elt
,LOCAL
->buf
)) {
1402 sprintf (tmp
,"Unable to parse message date at %lu: %.80s",
1403 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1408 /* note file offset of header */
1409 elt
->private.special
.offset
= curpos
;
1410 /* and internal header size */
1411 elt
->private.special
.text
.size
= i
;
1412 /* header size not known yet */
1413 elt
->private.msg
.header
.text
.size
= 0;
1414 elt
->rfc822_size
= j
; /* note message size */
1415 /* calculate system flags */
1416 if (k
& fSEEN
) elt
->seen
= T
;
1417 if (k
& fDELETED
) elt
->deleted
= T
;
1418 if (k
& fFLAGGED
) elt
->flagged
= T
;
1419 if (k
& fANSWERED
) elt
->answered
= T
;
1420 if (k
& fDRAFT
) elt
->draft
= T
;
1421 t
[8] = '\0'; /* get user flags value */
1422 elt
->user_flags
= strtoul (t
,NIL
,16);
1423 /* UID already assigned? */
1424 if (!(elt
->private.uid
= m
) || !(k
& fOLD
)) {
1425 elt
->recent
= T
; /* no, mark as recent */
1426 ++recent
; /* count up a new recent message */
1427 dirty
= T
; /* and must rewrite header */
1428 /* assign new UID */
1429 if (!elt
->private.uid
) elt
->private.uid
= ++stream
->uid_last
;
1430 mbx_update_status (stream
,elt
->msgno
,NIL
);
1432 /* update last parsed UID */
1433 lastuid
= elt
->private.uid
;
1435 curpos
+= i
+ j
; /* update position */
1438 if (dirty
&& !stream
->rdonly
){/* update header */
1439 mbx_update_header (stream
);
1440 fsync (LOCAL
->fd
); /* make sure all the UID updates take */
1442 /* update parsed file size and time */
1443 LOCAL
->filesize
= sbuf
.st_size
;
1444 fstat (LOCAL
->fd
,&sbuf
); /* get status again to ensure time is right */
1445 LOCAL
->filetime
= sbuf
.st_mtime
;
1446 if (added
&& !stream
->rdonly
){/* make sure atime updated */
1449 tp
[1] = LOCAL
->filetime
;
1450 utime (stream
->mailbox
,tp
);
1452 stream
->silent
= silent
; /* can pass up events now */
1453 mail_exists (stream
,nmsgs
); /* notify upper level of new mailbox size */
1454 mail_recent (stream
,recent
); /* and of change in recent messages */
1455 return LONGT
; /* return the winnage */
1458 /* MBX get cache element with status updating from file
1459 * Accepts: MAIL stream
1462 * Returns: cache element
1465 MESSAGECACHE
*mbx_elt (MAILSTREAM
*stream
,unsigned long msgno
,long expok
)
1467 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1468 struct { /* old flags */
1469 unsigned int seen
: 1;
1470 unsigned int deleted
: 1;
1471 unsigned int flagged
: 1;
1472 unsigned int answered
: 1;
1473 unsigned int draft
: 1;
1474 unsigned long user_flags
;
1476 old
.seen
= elt
->seen
; old
.deleted
= elt
->deleted
; old
.flagged
= elt
->flagged
;
1477 old
.answered
= elt
->answered
; old
.draft
= elt
->draft
;
1478 old
.user_flags
= elt
->user_flags
;
1480 if (mbx_read_flags (stream
,elt
) && expok
) {
1481 mail_expunged (stream
,elt
->msgno
);
1482 return NIL
; /* return this message was expunged */
1484 if ((old
.seen
!= elt
->seen
) || (old
.deleted
!= elt
->deleted
) ||
1485 (old
.flagged
!= elt
->flagged
) || (old
.answered
!= elt
->answered
) ||
1486 (old
.draft
!= elt
->draft
) || (old
.user_flags
!= elt
->user_flags
))
1487 MM_FLAGS (stream
,msgno
); /* let top level know */
1491 /* MBX read flags from file
1492 * Accepts: MAIL stream
1494 * Returns: non-NIL if message expunged
1497 unsigned long mbx_read_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
1501 fstat (LOCAL
->fd
,&sbuf
); /* get status */
1502 /* make sure file size is good */
1503 if (sbuf
.st_size
< LOCAL
->filesize
) {
1504 sprintf (LOCAL
->buf
,"Mailbox shrank from %lu to %lu in flag read!",
1505 (unsigned long) LOCAL
->filesize
,(unsigned long) sbuf
.st_size
);
1508 /* set the seek pointer */
1509 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1510 elt
->private.special
.text
.size
- 24,L_SET
);
1511 /* read the new flags */
1512 if (read (LOCAL
->fd
,LOCAL
->buf
,14) < 0) {
1513 sprintf (LOCAL
->buf
,"Unable to read new status: %s",strerror (errno
));
1516 if ((LOCAL
->buf
[0] != ';') || (LOCAL
->buf
[13] != '-')) {
1518 LOCAL
->buf
[14] = '\0'; /* tie off buffer for error message */
1519 strncpy(tmp
, LOCAL
->buf
, 14);
1520 sprintf (LOCAL
->buf
+50,"Invalid flags for message %lu (%lu %lu): %s",
1521 elt
->msgno
,elt
->private.special
.offset
,
1522 elt
->private.special
.text
.size
,(char *) tmp
);
1523 fatal (LOCAL
->buf
+50);
1525 LOCAL
->buf
[13] = '\0'; /* tie off buffer */
1526 /* calculate system flags */
1527 i
= strtoul (LOCAL
->buf
+9,NIL
,16);
1528 elt
->seen
= i
& fSEEN
? T
: NIL
;
1529 elt
->deleted
= i
& fDELETED
? T
: NIL
;
1530 elt
->flagged
= i
& fFLAGGED
? T
: NIL
;
1531 elt
->answered
= i
& fANSWERED
? T
: NIL
;
1532 elt
->draft
= i
& fDRAFT
? T
: NIL
;
1533 LOCAL
->expunged
|= i
& fEXPUNGED
? T
: NIL
;
1534 LOCAL
->buf
[9] = '\0'; /* tie off flags */
1535 /* get user flags value */
1536 elt
->user_flags
= strtoul (LOCAL
->buf
+1,NIL
,16);
1537 elt
->valid
= T
; /* have valid flags now */
1538 return i
& fEXPUNGED
;
1541 /* MBX update header
1542 * Accepts: MAIL stream
1545 #ifndef CYGKLUDGEOFFSET
1546 #define CYGKLUDGEOFFSET 0
1549 void mbx_update_header (MAILSTREAM
*stream
)
1552 char *s
= LOCAL
->buf
;
1553 memset (s
,'\0',HDRSIZE
); /* initialize header */
1554 sprintf (s
,"*mbx*\015\012%08lx%08lx\015\012",
1555 stream
->uid_validity
,stream
->uid_last
);
1556 for (i
= 0; (i
< NUSERFLAGS
) && stream
->user_flags
[i
]; ++i
)
1557 sprintf (s
+= strlen (s
),"%s\015\012",stream
->user_flags
[i
]);
1558 LOCAL
->ffuserflag
= i
; /* first free user flag */
1559 /* can we create more user flags? */
1560 stream
->kwd_create
= (i
< NUSERFLAGS
) ? T
: NIL
;
1561 /* write reserved lines */
1562 while (i
++ < NUSERFLAGS
) strcat (s
,"\015\012");
1563 sprintf (LOCAL
->buf
+ HDRSIZE
- 10,"%08lx\015\012",LOCAL
->lastpid
);
1564 while (T
) { /* rewind file */
1565 lseek (LOCAL
->fd
,CYGKLUDGEOFFSET
,L_SET
);
1566 /* write new header */
1567 if (write (LOCAL
->fd
,LOCAL
->buf
+ CYGKLUDGEOFFSET
,
1568 HDRSIZE
- CYGKLUDGEOFFSET
) > 0) break;
1569 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1570 MM_DISKERROR (stream
,errno
,T
);
1574 /* MBX update status string
1575 * Accepts: MAIL stream
1580 void mbx_update_status (MAILSTREAM
*stream
,unsigned long msgno
,long flags
)
1583 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1585 if (stream
->rdonly
|| !elt
->valid
) mbx_read_flags (stream
,elt
);
1586 else { /* readwrite */
1587 fstat (LOCAL
->fd
,&sbuf
); /* get status */
1588 /* make sure file size is good */
1589 if (sbuf
.st_size
< LOCAL
->filesize
) {
1590 sprintf (LOCAL
->buf
,"Mailbox shrank from %lu to %lu in flag update!",
1591 (unsigned long) LOCAL
->filesize
,(unsigned long) sbuf
.st_size
);
1594 /* set the seek pointer */
1595 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1596 elt
->private.special
.text
.size
- 24,L_SET
);
1597 /* read the new flags */
1598 if (read (LOCAL
->fd
,LOCAL
->buf
,14) < 0) {
1599 sprintf (LOCAL
->buf
,"Unable to read old status: %s",strerror (errno
));
1602 if ((LOCAL
->buf
[0] != ';') || (LOCAL
->buf
[13] != '-')) {
1604 LOCAL
->buf
[14] = '\0'; /* tie off buffer for error message */
1605 strncpy(tmp
, LOCAL
->buf
, 14);
1606 LOCAL
->buf
[14] = '\0'; /* tie off buffer for error message */
1607 sprintf (LOCAL
->buf
+50,"Invalid flags for message %lu (%lu %lu): %s",
1608 elt
->msgno
,elt
->private.special
.offset
,
1609 elt
->private.special
.text
.size
,(char *) tmp
);
1610 fatal (LOCAL
->buf
+50);
1612 /* print new flag string */
1613 sprintf (LOCAL
->buf
,"%08lx%04x-%08lx",elt
->user_flags
,(unsigned)
1614 (((elt
->deleted
&& flags
) ?
1615 fEXPUNGED
: (strtoul (LOCAL
->buf
+9,NIL
,16)) & fEXPUNGED
) +
1616 (fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
1617 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
1618 (fDRAFT
* elt
->draft
) + fOLD
),elt
->private.uid
);
1619 while (T
) { /* get to that place in the file */
1620 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1621 elt
->private.special
.text
.size
- 23,L_SET
);
1622 /* write new flags and UID */
1623 if (write (LOCAL
->fd
,LOCAL
->buf
,21) > 0) break;
1624 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1625 MM_DISKERROR (stream
,errno
,T
);
1630 /* MBX locate header for a message
1631 * Accepts: MAIL stream
1633 * pointer to returned header size
1634 * pointer to possible returned header
1635 * Returns: position of header in file
1638 #define HDRBUFLEN 16384 /* good enough for most headers */
1639 #define SLOP 4 /* CR LF CR LF */
1641 unsigned long mbx_hdrpos (MAILSTREAM
*stream
,unsigned long msgno
,
1642 unsigned long *size
,char **hdr
)
1644 unsigned long siz
,done
;
1646 unsigned char *s
,*t
,*te
;
1647 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1648 unsigned long ret
= elt
->private.special
.offset
+
1649 elt
->private.special
.text
.size
;
1650 if (hdr
) *hdr
= NIL
; /* assume no header returned */
1651 /* is header size known? */
1652 if ((*size
= elt
->private.msg
.header
.text
.size
) != 0L) return ret
;
1653 /* paranoia check */
1654 if (LOCAL
->buflen
< (HDRBUFLEN
+ SLOP
))
1655 fatal ("LOCAL->buf smaller than HDRBUFLEN");
1656 lseek (LOCAL
->fd
,ret
,L_SET
); /* get to header position */
1657 /* read HDRBUFLEN chunks with 4 byte slop */
1658 for (done
= siz
= 0, s
= LOCAL
->buf
;
1659 (i
= min ((long) (elt
->rfc822_size
- done
),(long) HDRBUFLEN
)) &&
1660 (read (LOCAL
->fd
,s
,i
) == i
);
1661 done
+= i
, siz
+= (t
- LOCAL
->buf
) - SLOP
, s
= LOCAL
->buf
+ SLOP
) {
1662 te
= (t
= s
+ i
) - 12; /* calculate end of fast scan */
1663 /* fast scan for CR */
1664 for (s
= LOCAL
->buf
; s
< te
;)
1665 if (((*s
++ == '\015') || (*s
++ == '\015') || (*s
++ == '\015') ||
1666 (*s
++ == '\015') || (*s
++ == '\015') || (*s
++ == '\015') ||
1667 (*s
++ == '\015') || (*s
++ == '\015') || (*s
++ == '\015') ||
1668 (*s
++ == '\015') || (*s
++ == '\015') || (*s
++ == '\015')) &&
1669 (*s
== '\012') && (*++s
== '\015') && (*++s
== '\012')) {
1670 *size
= elt
->private.msg
.header
.text
.size
= siz
+ (++s
- LOCAL
->buf
);
1671 if (hdr
) *hdr
= LOCAL
->buf
;
1674 for (te
= t
- 3; (s
< te
);) /* final character-at-a-time scan */
1675 if ((*s
++ == '\015') && (*s
== '\012') && (*++s
== '\015') &&
1677 *size
= elt
->private.msg
.header
.text
.size
= siz
+ (++s
- LOCAL
->buf
);
1678 if (hdr
) *hdr
= LOCAL
->buf
;
1681 if (i
<= SLOP
) break; /* end of data */
1682 /* slide over last 4 bytes */
1683 memmove (LOCAL
->buf
,t
- SLOP
,SLOP
);
1684 hdr
= NIL
; /* can't return header this way */
1686 /* not found: header consumes entire message */
1687 elt
->private.msg
.header
.text
.size
= *size
= elt
->rfc822_size
;
1688 if (hdr
) *hdr
= LOCAL
->buf
; /* possibly return header too */
1692 /* MBX mail rewrite mailbox
1693 * Accepts: MAIL stream
1694 * pointer to return reclaimed size
1695 * flags (0 = no expunge, 1 = expunge deleted, -1 = expunge sequence)
1696 * Returns: number of expunged messages
1699 unsigned long mbx_rewrite (MAILSTREAM
*stream
,unsigned long *reclaimed
,
1706 unsigned long i
,j
,k
,m
,delta
;
1707 unsigned long n
= *reclaimed
= 0;
1708 unsigned long recent
= 0;
1709 char lock
[MAILTMPLEN
];
1711 blocknotify_t bn
= (blocknotify_t
) mail_parameters (NIL
,GET_BLOCKNOTIFY
,NIL
);
1712 /* The cretins who designed flock() created a window of vulnerability in
1713 * upgrading locks from shared to exclusive or downgrading from exclusive
1714 * to shared. Rather than maintain the lock at shared status at a minimum,
1715 * flock() actually *releases* the former lock. Obviously they never talked
1716 * to any database guys. Fortunately, we have the parse/append permission
1717 * lock. If we require this lock before going exclusive on the mailbox,
1718 * another process can not sneak in and steal the exclusive mailbox lock on
1719 * us, because it will block on trying to get parse/append permission first.
1721 /* get parse/append permission */
1722 if ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) < 0) {
1723 MM_LOG ("Unable to lock mailbox for rewrite",ERROR
);
1726 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
1727 if (LOCAL
->filetime
&& !LOCAL
->flagcheck
&&
1728 (LOCAL
->filetime
< sbuf
.st_mtime
)) LOCAL
->flagcheck
= T
;
1729 if (!mbx_parse (stream
)) { /* make sure see any newly-arrived messages */
1730 unlockfd (ld
,lock
); /* failed?? */
1733 if (LOCAL
->flagcheck
) { /* sweep flags if need flagcheck */
1734 LOCAL
->filetime
= sbuf
.st_mtime
;
1735 for (i
= 1; i
<= stream
->nmsgs
; ++i
) mbx_elt (stream
,i
,NIL
);
1736 LOCAL
->flagcheck
= NIL
;
1739 /* get exclusive access */
1740 if (!flock (LOCAL
->fd
,LOCK_EX
|LOCK_NB
)) {
1741 MM_CRITICAL (stream
); /* go critical */
1742 for (i
= 1,delta
= 0,pos
= ppos
= HDRSIZE
; i
<= stream
->nmsgs
; ) {
1743 /* note if message not at predicted location */
1744 if ((m
= (elt
= mbx_elt (stream
,i
,NIL
))->private.special
.offset
- ppos
) != 0L) {
1745 ppos
= elt
->private.special
.offset
;
1746 *reclaimed
+= m
; /* note reclaimed message space */
1747 delta
+= m
; /* and as expunge delta */
1749 /* number of bytes to smash or preserve */
1750 ppos
+= (k
= elt
->private.special
.text
.size
+ elt
->rfc822_size
);
1751 /* if need to expunge this message*/
1752 if (flags
&& elt
->deleted
&& ((flags
> 0) || elt
->sequence
)) {
1753 delta
+= k
; /* number of bytes to delete */
1754 mail_expunged(stream
,i
);/* notify upper levels */
1755 n
++; /* count up one more expunged message */
1757 else { /* preserved message */
1758 i
++; /* count this message */
1759 if (elt
->recent
) ++recent
;
1760 if (delta
) { /* moved, note first byte to preserve */
1761 j
= elt
->private.special
.offset
;
1762 do { /* read from source position */
1763 m
= min (k
,LOCAL
->buflen
);
1764 lseek (LOCAL
->fd
,j
,L_SET
);
1765 read (LOCAL
->fd
,LOCAL
->buf
,m
);
1766 pos
= j
- delta
; /* write to destination position */
1768 lseek (LOCAL
->fd
,pos
,L_SET
);
1769 if (write (LOCAL
->fd
,LOCAL
->buf
,m
) > 0) break;
1770 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1771 MM_DISKERROR (stream
,errno
,T
);
1773 pos
+= m
; /* new position */
1774 j
+= m
; /* next chunk, perhaps */
1775 } while (k
-= m
); /* until done */
1776 /* note the new address of this text */
1777 elt
->private.special
.offset
-= delta
;
1779 /* preserved but no deleted messages yet */
1780 else pos
= elt
->private.special
.offset
+ k
;
1783 /* deltaed file size match position? */
1784 if ((m
= (LOCAL
->filesize
-= delta
) - pos
) != 0L) {
1785 *reclaimed
+= m
; /* probably an fEXPUNGED msg */
1786 LOCAL
->filesize
= pos
; /* set correct size */
1788 /* truncate file after last message */
1789 ftruncate (LOCAL
->fd
,LOCAL
->filesize
);
1790 fsync (LOCAL
->fd
); /* force disk update */
1791 MM_NOCRITICAL (stream
); /* release critical */
1792 (*bn
) (BLOCK_FILELOCK
,NIL
);
1793 flock (LOCAL
->fd
,LOCK_SH
); /* allow sharers again */
1794 (*bn
) (BLOCK_NONE
,NIL
);
1797 else { /* can't get exclusive */
1798 (*bn
) (BLOCK_FILELOCK
,NIL
);
1799 flock (LOCAL
->fd
,LOCK_SH
); /* recover previous shared mailbox lock */
1800 (*bn
) (BLOCK_NONE
,NIL
);
1801 /* do hide-expunge when shared */
1802 if (flags
) for (i
= 1; i
<= stream
->nmsgs
; ) {
1803 if ((elt
= mbx_elt (stream
,i
,T
)) != NULL
) {
1804 /* make the message invisible */
1805 if (elt
->deleted
&& ((flags
> 0) || elt
->sequence
)) {
1806 mbx_update_status (stream
,elt
->msgno
,LONGT
);
1807 /* notify upper levels */
1808 mail_expunged (stream
,i
);
1809 n
++; /* count up one more expunged message */
1812 i
++; /* preserved message */
1813 if (elt
->recent
) ++recent
;
1816 else n
++; /* count up one more expunged message */
1818 fsync (LOCAL
->fd
); /* force disk update */
1820 fstat (LOCAL
->fd
,&sbuf
); /* get new write time */
1821 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
1822 tp
[0] = time (0); /* reset atime to now */
1823 utime (stream
->mailbox
,tp
);
1824 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
1825 /* notify upper level of new mailbox size */
1826 mail_exists (stream
,stream
->nmsgs
);
1827 mail_recent (stream
,recent
);
1828 return n
; /* return number of expunged messages */
1831 /* MBX mail lock for flag updating
1833 * Returns: T if successful, NIL if failure
1836 long mbx_flaglock (MAILSTREAM
*stream
)
1841 char lock
[MAILTMPLEN
];
1842 /* no-op if readonly or already locked */
1843 if (!stream
->rdonly
&& LOCAL
&& (LOCAL
->fd
>= 0) && (LOCAL
->ld
< 0)) {
1845 if ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) < 0) return NIL
;
1846 if (!LOCAL
->flagcheck
) { /* don't do this if flagcheck already needed */
1847 if (LOCAL
->filetime
) { /* know previous time? */
1848 fstat (LOCAL
->fd
,&sbuf
);/* get current write time */
1849 if (LOCAL
->filetime
< sbuf
.st_mtime
) LOCAL
->flagcheck
= T
;
1850 LOCAL
->filetime
= 0; /* don't do this test for any other messages */
1852 if (!mbx_parse (stream
)) {/* parse mailbox */
1853 unlockfd (ld
,lock
); /* shouldn't happen */
1856 if (LOCAL
->flagcheck
) /* invalidate cache if flagcheck */
1857 for (i
= 1; i
<= stream
->nmsgs
; ++i
) mail_elt (stream
,i
)->valid
= NIL
;
1859 LOCAL
->ld
= ld
; /* copy to stream for subsequent calls */
1860 memcpy (LOCAL
->lock
,lock
,MAILTMPLEN
);