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