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: MTX 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
26 * Last Edited: 11 October 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 */
52 /* MTX I/O stream local data */
54 typedef struct mtx_local
{
55 unsigned int shouldcheck
: 1; /* if ping should do a check instead */
56 unsigned int mustcheck
: 1; /* if ping must do a check instead */
57 int fd
; /* file descriptor for I/O */
58 off_t filesize
; /* file size parsed */
59 time_t filetime
; /* last file time */
60 time_t lastsnarf
; /* last snarf time */
61 unsigned char *buf
; /* temporary buffer */
62 unsigned long buflen
; /* current size of temporary buffer */
66 /* Convenient access to local data */
68 #define LOCAL ((MTXLOCAL *) stream->local)
71 /* Function prototypes */
73 DRIVER
*mtx_valid (char *name
);
74 int mtx_isvalid (char *name
,char *tmp
);
75 void *mtx_parameters (long function
,void *value
);
76 void mtx_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
77 void mtx_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
78 void mtx_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
79 long mtx_create (MAILSTREAM
*stream
,char *mailbox
);
80 long mtx_delete (MAILSTREAM
*stream
,char *mailbox
);
81 long mtx_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
82 long mtx_status (MAILSTREAM
*stream
,char *mbx
,long flags
);
83 MAILSTREAM
*mtx_open (MAILSTREAM
*stream
);
84 void mtx_close (MAILSTREAM
*stream
,long options
);
85 void mtx_flags (MAILSTREAM
*stream
,char *sequence
,long flags
);
86 char *mtx_header (MAILSTREAM
*stream
,unsigned long msgno
,
87 unsigned long *length
,long flags
);
88 long mtx_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
);
89 void mtx_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
);
90 void mtx_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
91 long mtx_ping (MAILSTREAM
*stream
);
92 void mtx_check (MAILSTREAM
*stream
);
93 void mtx_snarf (MAILSTREAM
*stream
);
94 long mtx_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
95 long mtx_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
);
96 long mtx_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
98 char *mtx_file (char *dst
,char *name
);
99 long mtx_parse (MAILSTREAM
*stream
);
100 MESSAGECACHE
*mtx_elt (MAILSTREAM
*stream
,unsigned long msgno
);
101 void mtx_read_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
102 void mtx_update_status (MAILSTREAM
*stream
,unsigned long msgno
,long syncflag
);
103 unsigned long mtx_hdrpos (MAILSTREAM
*stream
,unsigned long msgno
,
104 unsigned long *size
);
106 /* MTX mail routines */
109 /* Driver dispatch used by MAIL */
112 "mtx", /* driver name */
114 DR_LOCAL
|DR_MAIL
|DR_CRLF
|DR_NOSTICKY
|DR_LOCKING
,
115 (DRIVER
*) NIL
, /* next driver */
116 mtx_valid
, /* mailbox is valid for us */
117 mtx_parameters
, /* manipulate parameters */
118 mtx_scan
, /* scan mailboxes */
119 mtx_list
, /* list mailboxes */
120 mtx_lsub
, /* list subscribed mailboxes */
121 NIL
, /* subscribe to mailbox */
122 NIL
, /* unsubscribe from mailbox */
123 dummy_create
, /* create mailbox */
124 mtx_delete
, /* delete mailbox */
125 mtx_rename
, /* rename mailbox */
126 mtx_status
, /* status of mailbox */
127 mtx_open
, /* open mailbox */
128 mtx_close
, /* close mailbox */
129 mtx_flags
, /* fetch message "fast" attributes */
130 mtx_flags
, /* fetch message flags */
131 NIL
, /* fetch overview */
132 NIL
, /* fetch message envelopes */
133 mtx_header
, /* fetch message header */
134 mtx_text
, /* fetch message body */
135 NIL
, /* fetch partial message text */
136 NIL
, /* unique identifier */
137 NIL
, /* message number */
138 mtx_flag
, /* modify flags */
139 mtx_flagmsg
, /* per-message modify flags */
140 NIL
, /* search for message based on criteria */
141 NIL
, /* sort messages */
142 NIL
, /* thread messages */
143 mtx_ping
, /* ping mailbox to see if still alive */
144 mtx_check
, /* check for new messages */
145 mtx_expunge
, /* expunge deleted messages */
146 mtx_copy
, /* copy messages to another mailbox */
147 mtx_append
, /* append string message to mailbox */
148 NIL
, /* garbage collect stream */
149 NIL
/* renew stream */
152 /* prototype stream */
153 MAILSTREAM mtxproto
= {&mtxdriver
};
155 /* MTX mail validate mailbox
156 * Accepts: mailbox name
157 * Returns: our driver if name is valid, NIL otherwise
160 DRIVER
*mtx_valid (char *name
)
162 char tmp
[MAILTMPLEN
];
163 return mtx_isvalid (name
,tmp
) ? &mtxdriver
: NIL
;
167 /* MTX mail test for valid mailbox
168 * Accepts: mailbox name
169 * Returns: T if valid, NIL otherwise
172 int mtx_isvalid (char *name
,char *tmp
)
176 char *s
,file
[MAILTMPLEN
];
179 errno
= EINVAL
; /* assume invalid argument */
180 /* if file, get its status */
181 if ((s
= mtx_file (file
,name
)) && !stat (s
,&sbuf
)) {
182 if (!sbuf
.st_size
) { /* allow empty file if INBOX */
183 if ((s
= mailboxfile (tmp
,name
)) && !*s
) ret
= T
;
184 else errno
= 0; /* empty file */
186 else if ((fd
= open (file
,O_RDONLY
,NIL
)) >= 0) {
187 memset (tmp
,'\0',MAILTMPLEN
);
188 if ((read (fd
,tmp
,64) >= 0) && (s
= strchr (tmp
,'\015')) &&
189 (s
[1] == '\012')) { /* valid format? */
190 *s
= '\0'; /* tie off header */
191 /* must begin with dd-mmm-yy" */
192 ret
= (((tmp
[2] == '-' && tmp
[6] == '-') ||
193 (tmp
[1] == '-' && tmp
[5] == '-')) &&
194 (s
= strchr (tmp
+18,',')) && strchr (s
+2,';')) ? T
: NIL
;
196 else errno
= -1; /* bogus format */
197 close (fd
); /* close the file */
198 /* \Marked status? */
199 if (sbuf
.st_ctime
> sbuf
.st_atime
) {
200 tp
[0] = sbuf
.st_atime
; /* preserve atime and mtime */
201 tp
[1] = sbuf
.st_mtime
;
202 utime (file
,tp
); /* set the times */
206 /* in case INBOX but not mtx format */
207 else if ((errno
== ENOENT
) && !compare_cstring (name
,"INBOX")) errno
= -1;
208 return ret
; /* return what we should */
211 /* MTX manipulate driver parameters
212 * Accepts: function code
213 * function-dependent value
214 * Returns: function-dependent return value
217 void *mtx_parameters (long function
,void *value
)
220 switch ((int) function
) {
222 if (value
) ret
= mtx_file ((char *) value
,"INBOX");
229 /* MTX mail scan mailboxes
230 * Accepts: mail stream
236 void mtx_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
238 if (stream
) dummy_scan (NIL
,ref
,pat
,contents
);
242 /* MTX mail list mailboxes
243 * Accepts: mail stream
248 void mtx_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
250 if (stream
) dummy_list (NIL
,ref
,pat
);
254 /* MTX mail list subscribed mailboxes
255 * Accepts: mail stream
260 void mtx_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
262 if (stream
) dummy_lsub (NIL
,ref
,pat
);
265 /* MTX mail delete mailbox
266 * Accepts: MAIL stream
267 * mailbox name to delete
268 * Returns: T on success, NIL on failure
271 long mtx_delete (MAILSTREAM
*stream
,char *mailbox
)
273 return mtx_rename (stream
,mailbox
,NIL
);
277 /* MTX mail rename mailbox
278 * Accepts: MAIL stream
280 * new mailbox name (or NIL for delete)
281 * Returns: T on success, NIL on failure
284 long mtx_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
287 char c
,*s
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
290 if (!mtx_file (file
,old
) ||
291 (newname
&& (!((s
= mailboxfile (tmp
,newname
)) && *s
) ||
292 ((s
= strrchr (tmp
,'/')) && !s
[1])))) {
295 "Can't rename mailbox %.80s to %.80s: invalid name", old
,newname
);
297 sprintf (tmp
, "Can't delete mailbox %.80s: invalid name", old
);
301 else if ((fd
= open (file
,O_RDWR
,NIL
)) < 0) {
302 sprintf (tmp
,"Can't open mailbox %.80s: %s",old
,strerror (errno
));
306 /* get exclusive parse/append permission */
307 if ((ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0) {
308 MM_LOG ("Unable to lock rename mailbox",ERROR
);
311 /* lock out other users */
312 if (flock (fd
,LOCK_EX
|LOCK_NB
)) {
313 close (fd
); /* couldn't lock, give up on it then */
314 sprintf (tmp
,"Mailbox %.80s is in use by another process",old
);
316 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
320 if (newname
) { /* want rename? */
321 if ((s
= strrchr (tmp
,'/')) != NULL
) {/* found superior to destination name? */
322 c
= *++s
; /* remember first character of inferior */
323 *s
= '\0'; /* tie off to get just superior */
324 /* name doesn't exist, create it */
325 if ((stat (tmp
,&sbuf
) || ((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
)) &&
326 !dummy_create_path (stream
,tmp
,get_dir_protection (newname
)))
328 else *s
= c
; /* restore full name */
330 /* rename the file */
331 if (ret
&& rename (file
,tmp
)) {
332 sprintf (tmp
,"Can't rename mailbox %.80s to %.80s: %s",old
,newname
,
335 ret
= NIL
; /* set failure */
338 else if (unlink (file
)) {
339 sprintf (tmp
,"Can't delete mailbox %.80s: %s",old
,strerror (errno
));
341 ret
= NIL
; /* set failure */
343 flock (fd
,LOCK_UN
); /* release lock on the file */
344 close (fd
); /* close the file */
345 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
346 /* recreate file if renamed INBOX */
347 if (ret
&& !compare_cstring (old
,"INBOX")) dummy_create (NIL
,"INBOX.MTX");
348 return ret
; /* return success */
352 * Accepts: mail stream
355 * Returns: T on success, NIL on failure
358 long mtx_status (MAILSTREAM
*stream
,char *mbx
,long flags
)
362 MAILSTREAM
*tstream
= NIL
;
363 MAILSTREAM
*systream
= NIL
;
364 /* make temporary stream (unless this mbx) */
365 if (!stream
&& !(stream
= tstream
=
366 mail_open (NIL
,mbx
,OP_READONLY
|OP_SILENT
))) return NIL
;
367 status
.flags
= flags
; /* return status values */
368 status
.messages
= stream
->nmsgs
;
369 status
.recent
= stream
->recent
;
370 if (flags
& SA_UNSEEN
) /* must search to get unseen messages */
371 for (i
= 1,status
.unseen
= 0; i
<= stream
->nmsgs
; i
++)
372 if (!mail_elt (stream
,i
)->seen
) status
.unseen
++;
373 status
.uidnext
= stream
->uid_last
+ 1;
374 status
.uidvalidity
= stream
->uid_validity
;
375 /* calculate post-snarf results */
376 if (!status
.recent
&& stream
->inbox
&&
377 (systream
= mail_open (NIL
,sysinbox (),OP_READONLY
|OP_SILENT
))) {
378 status
.messages
+= systream
->nmsgs
;
379 status
.recent
+= systream
->recent
;
380 if (flags
& SA_UNSEEN
) /* must search to get unseen messages */
381 for (i
= 1; i
<= systream
->nmsgs
; i
++)
382 if (!mail_elt (systream
,i
)->seen
) status
.unseen
++;
383 /* kludge but probably good enough */
384 status
.uidnext
+= systream
->nmsgs
;
386 MM_STATUS(stream
,mbx
,&status
);/* pass status to main program */
387 if (tstream
) mail_close (tstream
);
388 if (systream
) mail_close (systream
);
389 return T
; /* success */
393 * Accepts: stream to open
394 * Returns: stream on success, NIL on failure
397 MAILSTREAM
*mtx_open (MAILSTREAM
*stream
)
400 char tmp
[MAILTMPLEN
];
401 blocknotify_t bn
= (blocknotify_t
) mail_parameters (NIL
,GET_BLOCKNOTIFY
,NIL
);
402 /* return prototype for OP_PROTOTYPE call */
403 if (!stream
) return user_flags (&mtxproto
);
404 if (stream
->local
) fatal ("mtx recycle stream");
405 user_flags (stream
); /* set up user flags */
406 /* canonicalize the mailbox name */
407 if (!mtx_file (tmp
,stream
->mailbox
)) {
408 sprintf (tmp
,"Can't open - invalid name: %.80s",stream
->mailbox
);
411 if (stream
->rdonly
||
412 (fd
= open (tmp
,O_RDWR
,NIL
)) < 0) {
413 if ((fd
= open (tmp
,O_RDONLY
,NIL
)) < 0) {
414 sprintf (tmp
,"Can't open mailbox: %.80s",strerror (errno
));
418 else if (!stream
->rdonly
) { /* got it, but readonly */
419 MM_LOG ("Can't get write access to mailbox, access is readonly",WARN
);
423 stream
->local
= fs_get (sizeof (MTXLOCAL
));
424 LOCAL
->fd
= fd
; /* bind the file */
425 LOCAL
->buf
= (char *) fs_get (CHUNKSIZE
);
426 LOCAL
->buflen
= CHUNKSIZE
- 1;
427 /* note if an INBOX or not */
428 stream
->inbox
= !compare_cstring (stream
->mailbox
,"INBOX");
429 fs_give ((void **) &stream
->mailbox
);
430 stream
->mailbox
= cpystr (tmp
);
431 /* get shared parse permission */
432 if ((ld
= lockfd (fd
,tmp
,LOCK_SH
)) < 0) {
433 MM_LOG ("Unable to lock open mailbox",ERROR
);
436 (*bn
) (BLOCK_FILELOCK
,NIL
);
437 flock (LOCAL
->fd
,LOCK_SH
); /* lock the file */
438 (*bn
) (BLOCK_NONE
,NIL
);
439 unlockfd (ld
,tmp
); /* release shared parse permission */
440 LOCAL
->filesize
= 0; /* initialize parsed file size */
441 /* time not set up yet */
442 LOCAL
->lastsnarf
= LOCAL
->filetime
= 0;
443 LOCAL
->mustcheck
= LOCAL
->shouldcheck
= NIL
;
444 stream
->sequence
++; /* bump sequence number */
446 stream
->nmsgs
= stream
->recent
= 0;
447 if (mtx_ping (stream
) && !stream
->nmsgs
)
448 MM_LOG ("Mailbox is empty",(long) NIL
);
449 if (!LOCAL
) return NIL
; /* failure if stream died */
450 stream
->perm_seen
= stream
->perm_deleted
=
451 stream
->perm_flagged
= stream
->perm_answered
= stream
->perm_draft
=
452 stream
->rdonly
? NIL
: T
;
453 stream
->perm_user_flags
= stream
->rdonly
? NIL
: 0xffffffff;
454 return stream
; /* return stream to caller */
458 * Accepts: MAIL stream
462 void mtx_close (MAILSTREAM
*stream
,long options
)
464 if (stream
&& LOCAL
) { /* only if a file is open */
465 int silent
= stream
->silent
;
466 stream
->silent
= T
; /* note this stream is dying */
467 if (options
& CL_EXPUNGE
) mtx_expunge (stream
,NIL
,NIL
);
468 stream
->silent
= silent
; /* restore previous status */
469 flock (LOCAL
->fd
,LOCK_UN
); /* unlock local file */
470 close (LOCAL
->fd
); /* close the local file */
471 /* free local text buffer */
472 if (LOCAL
->buf
) fs_give ((void **) &LOCAL
->buf
);
473 /* nuke the local data */
474 fs_give ((void **) &stream
->local
);
475 stream
->dtb
= NIL
; /* log out the DTB */
480 /* MTX mail fetch flags
481 * Accepts: MAIL stream
484 * Sniffs at file to see if some other process changed the flags
487 void mtx_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
490 if (mtx_ping (stream
) && /* ping mailbox, get new status for messages */
491 ((flags
& FT_UID
) ? mail_uid_sequence (stream
,sequence
) :
492 mail_sequence (stream
,sequence
)))
493 for (i
= 1; i
<= stream
->nmsgs
; i
++)
494 if (mail_elt (stream
,i
)->sequence
) mtx_elt (stream
,i
);
497 /* MTX mail fetch message header
498 * Accepts: MAIL stream
500 * pointer to returned header text length
502 * Returns: message header in RFC822 format
505 char *mtx_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
508 *length
= 0; /* default to empty */
509 if (flags
& FT_UID
) return "";/* UID call "impossible" */
510 /* get to header position */
511 lseek (LOCAL
->fd
,mtx_hdrpos (stream
,msgno
,length
),L_SET
);
512 /* is buffer big enough? */
513 if (*length
> LOCAL
->buflen
) {
514 fs_give ((void **) &LOCAL
->buf
);
515 LOCAL
->buf
= (char *) fs_get ((LOCAL
->buflen
= *length
) + 1);
517 LOCAL
->buf
[*length
] = '\0'; /* tie off string */
519 read (LOCAL
->fd
,LOCAL
->buf
,*length
);
520 return (char *) LOCAL
->buf
;
523 /* MTX mail fetch message text (body only)
524 * Accepts: MAIL stream
526 * pointer to returned header text length
531 long mtx_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
536 /* UID call "impossible" */
537 if (flags
& FT_UID
) return NIL
;
538 elt
= mtx_elt (stream
,msgno
); /* get message status */
539 /* if message not seen */
540 if (!(flags
& FT_PEEK
) && !elt
->seen
) {
541 elt
->seen
= T
; /* mark message as seen */
542 /* recalculate status */
543 mtx_update_status (stream
,msgno
,NIL
);
544 MM_FLAGS (stream
,msgno
);
546 /* find header position */
547 i
= mtx_hdrpos (stream
,msgno
,&j
);
548 d
.fd
= LOCAL
->fd
; /* set up file descriptor */
550 d
.chunk
= LOCAL
->buf
; /* initial buffer chunk */
551 d
.chunksize
= CHUNKSIZE
;
552 INIT (bs
,fd_string
,&d
,elt
->rfc822_size
- j
);
553 return T
; /* success */
556 /* MTX mail modify flags
557 * Accepts: MAIL stream
563 void mtx_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
)
567 if (!stream
->rdonly
) { /* make sure the update takes */
569 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
570 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
571 tp
[0] = time (0); /* make sure read comes after all that */
572 utime (stream
->mailbox
,tp
);
577 /* MTX mail per-message modify flags
578 * Accepts: MAIL stream
579 * message cache element
582 void mtx_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
585 /* maybe need to do a checkpoint? */
586 if (LOCAL
->filetime
&& !LOCAL
->shouldcheck
) {
587 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
588 if (LOCAL
->filetime
< sbuf
.st_mtime
) LOCAL
->shouldcheck
= T
;
589 LOCAL
->filetime
= 0; /* don't do this test for any other messages */
591 /* recalculate status */
592 mtx_update_status (stream
,elt
->msgno
,NIL
);
595 /* MTX mail ping mailbox
596 * Accepts: MAIL stream
597 * Returns: T if stream still alive, NIL if not
600 long mtx_ping (MAILSTREAM
*stream
)
605 char lock
[MAILTMPLEN
];
607 if (stream
&& LOCAL
) { /* only if stream already open */
608 fstat (LOCAL
->fd
,&sbuf
); /* get current file poop */
609 if (LOCAL
->filetime
&& !(LOCAL
->mustcheck
|| LOCAL
->shouldcheck
) &&
610 (LOCAL
->filetime
< sbuf
.st_mtime
)) LOCAL
->shouldcheck
= T
;
611 /* check for changed message status */
612 if (LOCAL
->mustcheck
|| LOCAL
->shouldcheck
) {
613 LOCAL
->filetime
= sbuf
.st_mtime
;
614 if (LOCAL
->shouldcheck
) /* babble when we do this unilaterally */
615 MM_NOTIFY (stream
,"[CHECK] Checking for flag updates",NIL
);
616 while (i
<= stream
->nmsgs
) mtx_elt (stream
,i
++);
617 LOCAL
->mustcheck
= LOCAL
->shouldcheck
= NIL
;
619 /* get shared parse/append permission */
620 if ((sbuf
.st_size
!= LOCAL
->filesize
) &&
621 ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_SH
)) >= 0)) {
622 /* parse resulting mailbox */
623 r
= (mtx_parse (stream
)) ? T
: NIL
;
624 unlockfd (ld
,lock
); /* release shared parse/append permission */
626 if (LOCAL
) { /* stream must still be alive */
627 /* snarf if this is a read-write inbox */
628 if (stream
->inbox
&& !stream
->rdonly
) {
630 fstat (LOCAL
->fd
,&sbuf
);/* see if file changed now */
631 if ((sbuf
.st_size
!= LOCAL
->filesize
) &&
632 ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_SH
)) >= 0)) {
633 /* parse resulting mailbox */
634 r
= (mtx_parse (stream
)) ? T
: NIL
;
635 unlockfd (ld
,lock
); /* release shared parse/append permission */
640 return r
; /* return result of the parse */
644 /* MTX mail check mailbox (reparses status too)
645 * Accepts: MAIL stream
648 void mtx_check (MAILSTREAM
*stream
)
650 /* mark that a check is desired */
651 if (LOCAL
) LOCAL
->mustcheck
= T
;
652 if (mtx_ping (stream
)) MM_LOG ("Check completed",(long) NIL
);
655 /* MTX mail snarf messages from system inbox
656 * Accepts: MAIL stream
659 void mtx_snarf (MAILSTREAM
*stream
)
662 unsigned long j
,r
,hdrlen
,txtlen
;
664 char *hdr
,*txt
,lock
[MAILTMPLEN
],tmp
[MAILTMPLEN
];
666 MAILSTREAM
*sysibx
= NIL
;
668 /* give up if can't get exclusive permission */
669 if ((time (0) >= (LOCAL
->lastsnarf
+
670 (long) mail_parameters (NIL
,GET_SNARFINTERVAL
,NIL
))) &&
671 strcmp (sysinbox (),stream
->mailbox
) &&
672 ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) >= 0)) {
673 MM_CRITICAL (stream
); /* go critical */
674 /* sizes match and anything in sysinbox? */
675 if (!stat (sysinbox (),&sbuf
) && sbuf
.st_size
&&
676 !fstat (LOCAL
->fd
,&sbuf
) && (sbuf
.st_size
== LOCAL
->filesize
) &&
677 (sysibx
= mail_open (sysibx
,sysinbox (),OP_SILENT
)) &&
678 (!sysibx
->rdonly
) && (r
= sysibx
->nmsgs
)) {
679 /* yes, go to end of file in our mailbox */
680 lseek (LOCAL
->fd
,sbuf
.st_size
,L_SET
);
681 /* for each message in sysibx mailbox */
682 while (r
&& (++i
<= sysibx
->nmsgs
)) {
683 /* snarf message from system INBOX */
684 hdr
= cpystr (mail_fetchheader_full (sysibx
,i
,NIL
,&hdrlen
,NIL
));
685 txt
= mail_fetchtext_full (sysibx
,i
,&txtlen
,FT_PEEK
);
686 /* if have a message */
687 if ((j
= hdrlen
+ txtlen
) != 0L) {
688 /* calculate header line */
689 mail_date (LOCAL
->buf
,elt
= mail_elt (sysibx
,i
));
690 sprintf (LOCAL
->buf
+ strlen (LOCAL
->buf
),
691 ",%lu;0000000000%02o\015\012",j
,(unsigned)
692 ((fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
693 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
694 (fDRAFT
* elt
->draft
)));
696 if ((write (LOCAL
->fd
,LOCAL
->buf
,strlen (LOCAL
->buf
)) < 0) ||
697 (write (LOCAL
->fd
,hdr
,hdrlen
) < 0) ||
698 (write (LOCAL
->fd
,txt
,txtlen
) < 0)) r
= 0;
700 fs_give ((void **) &hdr
);
703 /* make sure all the updates take */
704 if (fsync (LOCAL
->fd
)) r
= 0;
705 if (r
) { /* delete all the messages we copied */
706 if (r
== 1) strcpy (tmp
,"1");
707 else sprintf (tmp
,"1:%lu",r
);
708 mail_flag (sysibx
,tmp
,"\\Deleted",ST_SET
);
709 mail_expunge (sysibx
); /* now expunge all those messages */
712 sprintf (LOCAL
->buf
,"Can't copy new mail: %s",strerror (errno
));
713 MM_LOG (LOCAL
->buf
,WARN
);
714 ftruncate (LOCAL
->fd
,sbuf
.st_size
);
716 fstat (LOCAL
->fd
,&sbuf
); /* yes, get current file size */
717 LOCAL
->filetime
= sbuf
.st_mtime
;
719 if (sysibx
) mail_close (sysibx
);
720 MM_NOCRITICAL (stream
); /* release critical */
721 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
722 LOCAL
->lastsnarf
= time (0);/* note time of last snarf */
726 /* MTX mail expunge mailbox
727 * Accepts: MAIL stream
728 * sequence to expunge if non-NIL
733 long mtx_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
741 unsigned long j
,k
,m
,recent
;
743 unsigned long delta
= 0;
744 char lock
[MAILTMPLEN
];
746 blocknotify_t bn
= (blocknotify_t
) mail_parameters (NIL
,GET_BLOCKNOTIFY
,NIL
);
747 if (!(ret
= (sequence
? ((options
& EX_UID
) ?
748 mail_uid_sequence (stream
,sequence
) :
749 mail_sequence (stream
,sequence
)) : LONGT
) &&
750 mtx_ping (stream
))); /* parse sequence if given, ping stream */
751 else if (stream
->rdonly
) MM_LOG ("Expunge ignored on readonly mailbox",WARN
);
753 if (LOCAL
->filetime
&& !LOCAL
->shouldcheck
) {
754 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
755 if (LOCAL
->filetime
< sbuf
.st_mtime
) LOCAL
->shouldcheck
= T
;
757 /* The cretins who designed flock() created a window of vulnerability in
758 * upgrading locks from shared to exclusive or downgrading from exclusive
759 * to shared. Rather than maintain the lock at shared status at a minimum,
760 * flock() actually *releases* the former lock. Obviously they never talked
761 * to any database guys. Fortunately, we have the parse/append permission
762 * lock. If we require this lock before going exclusive on the mailbox,
763 * another process can not sneak in and steal the exclusive mailbox lock on
764 * us, because it will block on trying to get parse/append permission first.
766 /* get exclusive parse/append permission */
767 if ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) < 0)
768 MM_LOG ("Unable to lock expunge mailbox",ERROR
);
769 /* make sure see any newly-arrived messages */
770 else if (!mtx_parse (stream
));
771 /* get exclusive access */
772 else if (flock (LOCAL
->fd
,LOCK_EX
|LOCK_NB
)) {
773 (*bn
) (BLOCK_FILELOCK
,NIL
);
774 flock (LOCAL
->fd
,LOCK_SH
);/* recover previous lock */
775 (*bn
) (BLOCK_NONE
,NIL
);
776 MM_LOG ("Can't expunge because mailbox is in use by another process",
778 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
782 MM_CRITICAL (stream
); /* go critical */
783 recent
= stream
->recent
; /* get recent now that pinged and locked */
784 /* for each message */
785 while (i
<= stream
->nmsgs
) {
786 /* get cache element */
787 elt
= mtx_elt (stream
,i
);
788 /* number of bytes to smash or preserve */
789 k
= elt
->private.special
.text
.size
+ elt
->rfc822_size
;
790 /* if need to expunge this message */
791 if (elt
->deleted
&& (sequence
? elt
->sequence
: T
)) {
792 /* if recent, note one less recent message */
793 if (elt
->recent
) --recent
;
794 delta
+= k
; /* number of bytes to delete */
795 /* notify upper levels */
796 mail_expunged (stream
,i
);
797 n
++; /* count up one more expunged message */
799 else if (i
++ && delta
) {/* preserved message */
800 /* first byte to preserve */
801 j
= elt
->private.special
.offset
;
802 do { /* read from source position */
803 m
= min (k
,LOCAL
->buflen
);
804 lseek (LOCAL
->fd
,j
,L_SET
);
805 read (LOCAL
->fd
,LOCAL
->buf
,m
);
806 pos
= j
- delta
; /* write to destination position */
807 lseek (LOCAL
->fd
,pos
,L_SET
);
809 lseek (LOCAL
->fd
,pos
,L_SET
);
810 if (write (LOCAL
->fd
,LOCAL
->buf
,m
) > 0) break;
811 MM_NOTIFY (stream
,strerror (errno
),WARN
);
812 MM_DISKERROR (stream
,errno
,T
);
814 pos
+= m
; /* new position */
815 j
+= m
; /* next chunk, perhaps */
816 } while (k
-= m
); /* until done */
817 /* note the new address of this text */
818 elt
->private.special
.offset
-= delta
;
820 /* preserved but no deleted messages */
821 else pos
= elt
->private.special
.offset
+ k
;
823 if (n
) { /* truncate file after last message */
824 if (pos
!= (LOCAL
->filesize
-= delta
)) {
826 "Calculated size mismatch %lu != %lu, delta = %lu",
827 (unsigned long) pos
,(unsigned long) LOCAL
->filesize
,delta
);
828 MM_LOG (LOCAL
->buf
,WARN
);
829 LOCAL
->filesize
= pos
;/* fix it then */
831 ftruncate (LOCAL
->fd
,LOCAL
->filesize
);
832 sprintf (LOCAL
->buf
,"Expunged %lu messages",n
);
833 /* output the news */
834 MM_LOG (LOCAL
->buf
,(long) NIL
);
836 else MM_LOG ("No messages deleted, so no update needed",(long) NIL
);
837 fsync (LOCAL
->fd
); /* force disk update */
838 fstat (LOCAL
->fd
,&sbuf
); /* get new write time */
839 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
840 tp
[0] = time (0); /* reset atime to now */
841 utime (stream
->mailbox
,tp
);
842 MM_NOCRITICAL (stream
); /* release critical */
843 /* notify upper level of new mailbox size */
844 mail_exists (stream
,stream
->nmsgs
);
845 mail_recent (stream
,recent
);
846 (*bn
) (BLOCK_FILELOCK
,NIL
);
847 flock (LOCAL
->fd
,LOCK_SH
);/* allow sharers again */
848 (*bn
) (BLOCK_NONE
,NIL
);
849 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
855 /* MTX mail copy message(s)
856 * Accepts: MAIL stream
858 * destination mailbox
860 * Returns: T if success, NIL if failed
863 long mtx_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
871 char file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
873 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
874 /* make sure valid mailbox */
875 if (!mtx_isvalid (mailbox
,LOCAL
->buf
)) switch (errno
) {
876 case ENOENT
: /* no such file? */
877 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before copy",NIL
);
879 case 0: /* merely empty file? */
881 case EACCES
: /* file protected */
882 sprintf (LOCAL
->buf
,"Can't access destination: %.80s",mailbox
);
883 MM_LOG (LOCAL
->buf
,ERROR
);
886 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
887 sprintf (LOCAL
->buf
,"Invalid MTX-format mailbox name: %.80s",mailbox
);
888 MM_LOG (LOCAL
->buf
,ERROR
);
891 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
892 sprintf (LOCAL
->buf
,"Not a MTX-format mailbox: %.80s",mailbox
);
893 MM_LOG (LOCAL
->buf
,ERROR
);
896 if (!((options
& CP_UID
) ? mail_uid_sequence (stream
,sequence
) :
897 mail_sequence (stream
,sequence
))) return NIL
;
899 if ((fd
= open (mtx_file (file
,mailbox
),O_RDWR
,NIL
)) < 0) {
900 sprintf (LOCAL
->buf
,"Unable to open copy mailbox: %s",strerror (errno
));
901 MM_LOG (LOCAL
->buf
,ERROR
);
904 MM_CRITICAL (stream
); /* go critical */
905 /* get exclusive parse/append permission */
906 if (flock (fd
,LOCK_SH
) || ((ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0)) {
907 MM_LOG ("Unable to lock copy mailbox",ERROR
);
908 MM_NOCRITICAL (stream
);
911 fstat (fd
,&sbuf
); /* get current file size */
912 lseek (fd
,sbuf
.st_size
,L_SET
);/* move to end of file */
914 /* for each requested message */
915 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); i
++)
916 if ((elt
= mail_elt (stream
,i
))->sequence
) {
917 lseek (LOCAL
->fd
,elt
->private.special
.offset
,L_SET
);
918 /* number of bytes to copy */
919 k
= elt
->private.special
.text
.size
+ elt
->rfc822_size
;
920 do { /* read from source position */
921 j
= min (k
,LOCAL
->buflen
);
922 read (LOCAL
->fd
,LOCAL
->buf
,j
);
923 if (write (fd
,LOCAL
->buf
,j
) < 0) ret
= NIL
;
924 } while (ret
&& (k
-= j
));/* until done */
926 /* make sure all the updates take */
927 if (!(ret
&& (ret
= !fsync (fd
)))) {
928 sprintf (LOCAL
->buf
,"Unable to write message: %s",strerror (errno
));
929 MM_LOG (LOCAL
->buf
,ERROR
);
930 ftruncate (fd
,sbuf
.st_size
);
932 if (ret
) tp
[0] = time (0) - 1;/* set atime to now-1 if successful copy */
933 /* else preserve \Marked status */
934 else tp
[0] = (sbuf
.st_ctime
> sbuf
.st_atime
) ? sbuf
.st_atime
: time(0);
935 tp
[1] = sbuf
.st_mtime
; /* preserve mtime */
936 utime (file
,tp
); /* set the times */
937 close (fd
); /* close the file */
938 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
939 MM_NOCRITICAL (stream
); /* release critical */
940 /* delete all requested messages */
941 if (ret
&& (options
& CP_MOVE
)) {
942 for (i
= 1; i
<= stream
->nmsgs
; i
++)
943 if ((elt
= mtx_elt (stream
,i
))->sequence
) {
944 elt
->deleted
= T
; /* mark message deleted */
945 /* recalculate status */
946 mtx_update_status (stream
,i
,NIL
);
948 if (!stream
->rdonly
) { /* make sure the update takes */
950 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
951 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
952 tp
[0] = time (0); /* make sure atime remains greater */
953 utime (stream
->mailbox
,tp
);
956 if (ret
&& mail_parameters (NIL
,GET_COPYUID
,NIL
))
957 MM_LOG ("Can not return meaningful COPYUID with this mailbox format",WARN
);
961 /* MTX mail append message from stringstruct
962 * Accepts: MAIL stream
963 * destination mailbox
966 * Returns: T if append successful, else NIL
969 long mtx_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
973 char *flags
,*date
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
981 /* default stream to prototype */
982 if (!stream
) stream
= user_flags (&mtxproto
);
983 /* make sure valid mailbox */
984 if (!mtx_isvalid (mailbox
,tmp
)) switch (errno
) {
985 case ENOENT
: /* no such file? */
986 if (!compare_cstring (mailbox
,"INBOX")) dummy_create (NIL
,"INBOX.MTX");
988 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before append",NIL
);
992 case 0: /* merely empty file? */
994 case EACCES
: /* file protected */
995 sprintf (tmp
,"Can't access destination: %.80s",mailbox
);
999 sprintf (tmp
,"Invalid MTX-format mailbox name: %.80s",mailbox
);
1003 sprintf (tmp
,"Not a MTX-format mailbox: %.80s",mailbox
);
1007 /* get first message */
1008 if (!MM_APPEND (af
) (stream
,data
,&flags
,&date
,&message
)) return NIL
;
1010 /* open destination mailbox */
1011 if (((fd
= open (mtx_file (file
,mailbox
),O_WRONLY
|O_APPEND
,NIL
)) < 0) ||
1012 !(df
= fdopen (fd
,"ab"))) {
1013 sprintf (tmp
,"Can't open append mailbox: %s",strerror (errno
));
1017 /* get parse/append permission */
1018 if (flock (fd
,LOCK_SH
) || ((ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0)) {
1019 MM_LOG ("Unable to lock append mailbox",ERROR
);
1023 MM_CRITICAL (stream
); /* go critical */
1024 fstat (fd
,&sbuf
); /* get current file size */
1026 do { /* parse flags */
1027 if (!SIZE (message
)) { /* guard against zero-length */
1028 MM_LOG ("Append of zero-length message",ERROR
);
1032 f
= mail_parse_flags (stream
,flags
,&i
);
1033 /* reverse bits (dontcha wish we had CIRC?) */
1034 for (uf
= 0; i
; uf
|= 1 << (29 - find_rightmost_bit (&i
)));
1035 if (date
) { /* parse date if given */
1036 if (!mail_parse_date (&elt
,date
)) {
1037 sprintf (tmp
,"Bad date in append: %.80s",date
);
1039 ret
= NIL
; /* mark failure */
1042 mail_date (tmp
,&elt
); /* write preserved date */
1044 else internal_date (tmp
); /* get current date in IMAP format */
1046 if (fprintf (df
,"%s,%lu;%010lo%02lo\015\012",tmp
,i
= SIZE (message
),uf
,
1047 (unsigned long) f
) < 0) ret
= NIL
;
1048 else { /* write message */
1049 if (i
) do c
= 0xff & SNX (message
);
1050 while ((putc (c
,df
) != EOF
) && --i
);
1051 /* get next message */
1052 if (i
|| !MM_APPEND (af
) (stream
,data
,&flags
,&date
,&message
)) ret
= NIL
;
1054 } while (ret
&& message
);
1056 if (!ret
|| (fflush (df
) == EOF
)) {
1057 ftruncate (fd
,sbuf
.st_size
);/* revert file */
1058 close (fd
); /* make sure fclose() doesn't corrupt us */
1060 sprintf (tmp
,"Message append failed: %s",strerror (errno
));
1065 if (ret
) tp
[0] = time (0) - 1;/* set atime to now-1 if successful copy */
1066 /* else preserve \Marked status */
1067 else tp
[0] = (sbuf
.st_ctime
> sbuf
.st_atime
) ? sbuf
.st_atime
: time(0);
1068 tp
[1] = sbuf
.st_mtime
; /* preserve mtime */
1069 utime (file
,tp
); /* set the times */
1070 fclose (df
); /* close the file */
1071 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
1072 MM_NOCRITICAL (stream
); /* release critical */
1073 if (ret
&& mail_parameters (NIL
,GET_APPENDUID
,NIL
))
1074 MM_LOG ("Can not return meaningful APPENDUID with this mailbox format",
1079 /* Internal routines */
1082 /* MTX mail generate file string
1083 * Accepts: temporary buffer to write into
1084 * mailbox name string
1085 * Returns: local file string or NIL if failure
1088 char *mtx_file (char *dst
,char *name
)
1090 char tmp
[MAILTMPLEN
];
1091 char *s
= mailboxfile (dst
,name
);
1092 /* return our standard inbox */
1093 return (s
&& !*s
) ? mailboxfile (dst
,mtx_isvalid ("~/INBOX",tmp
) ?
1094 "~/INBOX" : "INBOX.MTX") : s
;
1097 /* MTX mail parse mailbox
1098 * Accepts: MAIL stream
1099 * Returns: T if parse OK
1100 * NIL if failure, stream aborted
1103 long mtx_parse (MAILSTREAM
*stream
)
1106 MESSAGECACHE
*elt
= NIL
;
1107 unsigned char c
,*s
,*t
,*x
;
1108 char tmp
[MAILTMPLEN
];
1110 long curpos
= LOCAL
->filesize
;
1111 long nmsgs
= stream
->nmsgs
;
1112 long recent
= stream
->recent
;
1114 short silent
= stream
->silent
;
1115 fstat (LOCAL
->fd
,&sbuf
); /* get status */
1116 if (sbuf
.st_size
< curpos
) { /* sanity check */
1117 sprintf (tmp
,"Mailbox shrank from %lu to %lu!",
1118 (unsigned long) curpos
,(unsigned long) sbuf
.st_size
);
1120 mtx_close (stream
,NIL
);
1123 stream
->silent
= T
; /* don't pass up exists events yet */
1124 while (sbuf
.st_size
- curpos
){/* while there is stuff to parse */
1125 /* get to that position in the file */
1126 lseek (LOCAL
->fd
,curpos
,L_SET
);
1127 if ((i
= read (LOCAL
->fd
,LOCAL
->buf
,64)) <= 0) {
1128 sprintf (tmp
,"Unable to read internal header at %lu, size = %lu: %s",
1129 (unsigned long) curpos
,(unsigned long) sbuf
.st_size
,
1130 i
? strerror (errno
) : "no data read");
1132 mtx_close (stream
,NIL
);
1135 LOCAL
->buf
[i
] = '\0'; /* tie off buffer just in case */
1136 if (!((s
= strchr (LOCAL
->buf
,'\015')) && (s
[1] == '\012'))) {
1137 sprintf (tmp
,"Unable to find CRLF at %lu in %lu bytes, text: %s",
1138 (unsigned long) curpos
,i
,(char *) LOCAL
->buf
);
1140 mtx_close (stream
,NIL
);
1143 *s
= '\0'; /* tie off header line */
1144 i
= (s
+ 2) - LOCAL
->buf
; /* note start of text offset */
1145 if (!((s
= strchr (LOCAL
->buf
,',')) && (t
= strchr (s
+1,';')))) {
1146 sprintf (tmp
,"Unable to parse internal header at %lu: %s",
1147 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1149 mtx_close (stream
,NIL
);
1152 *s
++ = '\0'; *t
++ = '\0'; /* tie off fields */
1154 added
= T
; /* note that a new message was added */
1155 /* swell the cache */
1156 mail_exists (stream
,++nmsgs
);
1157 /* instantiate an elt for this message */
1158 (elt
= mail_elt (stream
,nmsgs
))->valid
= T
;
1159 elt
->private.uid
= ++stream
->uid_last
;
1160 /* note file offset of header */
1161 elt
->private.special
.offset
= curpos
;
1163 elt
->private.special
.text
.size
= 0;
1164 /* header size not known yet */
1165 elt
->private.msg
.header
.text
.size
= 0;
1166 x
= s
; /* parse the header components */
1167 if (mail_parse_date (elt
,LOCAL
->buf
) &&
1168 (elt
->rfc822_size
= strtoul (s
,(char **) &s
,10)) && (!(s
&& *s
)) &&
1169 isdigit (t
[0]) && isdigit (t
[1]) && isdigit (t
[2]) &&
1170 isdigit (t
[3]) && isdigit (t
[4]) && isdigit (t
[5]) &&
1171 isdigit (t
[6]) && isdigit (t
[7]) && isdigit (t
[8]) &&
1172 isdigit (t
[9]) && isdigit (t
[10]) && isdigit (t
[11]) && !t
[12])
1173 elt
->private.special
.text
.size
= i
;
1175 sprintf (tmp
,"Unable to parse internal header elements at %ld: %s,%s;%s",
1176 curpos
,(char *) LOCAL
->buf
,(char *) x
,(char *) t
);
1178 mtx_close (stream
,NIL
);
1181 /* make sure didn't run off end of file */
1182 if ((curpos
+= (elt
->rfc822_size
+ i
)) > sbuf
.st_size
) {
1183 sprintf (tmp
,"Last message (at %lu) runs past end of file (%lu > %lu)",
1184 elt
->private.special
.offset
,(unsigned long) curpos
,
1185 (unsigned long) sbuf
.st_size
);
1187 mtx_close (stream
,NIL
);
1190 c
= t
[10]; /* remember first system flags byte */
1191 t
[10] = '\0'; /* tie off flags */
1192 j
= strtoul (t
,NIL
,8); /* get user flags value */
1193 t
[10] = c
; /* restore first system flags byte */
1194 /* set up all valid user flags (reversed!) */
1195 while (j
) if (((i
= 29 - find_rightmost_bit (&j
)) < NUSERFLAGS
) &&
1196 stream
->user_flags
[i
]) elt
->user_flags
|= 1 << i
;
1197 /* calculate system flags */
1198 if ((j
= ((t
[10]-'0') * 8) + t
[11]-'0') & fSEEN
) elt
->seen
= T
;
1199 if (j
& fDELETED
) elt
->deleted
= T
;
1200 if (j
& fFLAGGED
) elt
->flagged
= T
;
1201 if (j
& fANSWERED
) elt
->answered
= T
;
1202 if (j
& fDRAFT
) elt
->draft
= T
;
1203 if (!(j
& fOLD
)) { /* newly arrived message? */
1205 recent
++; /* count up a new recent message */
1206 /* mark it as old */
1207 mtx_update_status (stream
,nmsgs
,NIL
);
1210 fsync (LOCAL
->fd
); /* make sure all the fOLD flags take */
1211 /* update parsed file size and time */
1212 LOCAL
->filesize
= sbuf
.st_size
;
1213 fstat (LOCAL
->fd
,&sbuf
); /* get status again to ensure time is right */
1214 LOCAL
->filetime
= sbuf
.st_mtime
;
1215 if (added
&& !stream
->rdonly
){/* make sure atime updated */
1218 tp
[1] = LOCAL
->filetime
;
1219 utime (stream
->mailbox
,tp
);
1221 stream
->silent
= silent
; /* can pass up events now */
1222 mail_exists (stream
,nmsgs
); /* notify upper level of new mailbox size */
1223 mail_recent (stream
,recent
); /* and of change in recent messages */
1224 return LONGT
; /* return the winnage */
1227 /* MTX get cache element with status updating from file
1228 * Accepts: MAIL stream
1230 * Returns: cache element
1233 MESSAGECACHE
*mtx_elt (MAILSTREAM
*stream
,unsigned long msgno
)
1235 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1236 struct { /* old flags */
1237 unsigned int seen
: 1;
1238 unsigned int deleted
: 1;
1239 unsigned int flagged
: 1;
1240 unsigned int answered
: 1;
1241 unsigned int draft
: 1;
1242 unsigned long user_flags
;
1244 old
.seen
= elt
->seen
; old
.deleted
= elt
->deleted
; old
.flagged
= elt
->flagged
;
1245 old
.answered
= elt
->answered
; old
.draft
= elt
->draft
;
1246 old
.user_flags
= elt
->user_flags
;
1247 mtx_read_flags (stream
,elt
);
1248 if ((old
.seen
!= elt
->seen
) || (old
.deleted
!= elt
->deleted
) ||
1249 (old
.flagged
!= elt
->flagged
) || (old
.answered
!= elt
->answered
) ||
1250 (old
.draft
!= elt
->draft
) || (old
.user_flags
!= elt
->user_flags
))
1251 MM_FLAGS (stream
,msgno
); /* let top level know */
1255 /* MTX read flags from file
1256 * Accepts: MAIL stream
1257 * Returns: cache element
1260 void mtx_read_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
1263 /* noop if readonly and have valid flags */
1264 if (stream
->rdonly
&& elt
->valid
) return;
1265 /* set the seek pointer */
1266 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1267 elt
->private.special
.text
.size
- 14,L_SET
);
1268 /* read the new flags */
1269 if (read (LOCAL
->fd
,LOCAL
->buf
,12) < 0) {
1270 sprintf (LOCAL
->buf
,"Unable to read new status: %s",strerror (errno
));
1273 /* calculate system flags */
1274 i
= (((LOCAL
->buf
[10]-'0') * 8) + LOCAL
->buf
[11]-'0');
1275 elt
->seen
= i
& fSEEN
? T
: NIL
; elt
->deleted
= i
& fDELETED
? T
: NIL
;
1276 elt
->flagged
= i
& fFLAGGED
? T
: NIL
;
1277 elt
->answered
= i
& fANSWERED
? T
: NIL
; elt
->draft
= i
& fDRAFT
? T
: NIL
;
1278 LOCAL
->buf
[10] = '\0'; /* tie off flags */
1279 j
= strtoul(LOCAL
->buf
,NIL
,8);/* get user flags value */
1280 /* set up all valid user flags (reversed!) */
1281 while (j
) if (((i
= 29 - find_rightmost_bit (&j
)) < NUSERFLAGS
) &&
1282 stream
->user_flags
[i
]) elt
->user_flags
|= 1 << i
;
1283 elt
->valid
= T
; /* have valid flags now */
1286 /* MTX update status string
1287 * Accepts: MAIL stream
1289 * flag saying whether or not to sync
1292 void mtx_update_status (MAILSTREAM
*stream
,unsigned long msgno
,long syncflag
)
1296 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1297 unsigned long j
,k
= 0;
1299 if (stream
->rdonly
|| !elt
->valid
) mtx_read_flags (stream
,elt
);
1300 else { /* readwrite */
1301 j
= elt
->user_flags
; /* get user flags */
1302 /* reverse bits (dontcha wish we had CIRC?) */
1303 while (j
) k
|= 1 << (29 - find_rightmost_bit (&j
));
1304 /* print new flag string */
1305 sprintf (LOCAL
->buf
,"%010lo%02o",k
,(unsigned)
1306 (fOLD
+ (fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
1307 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
1308 (fDRAFT
* elt
->draft
)));
1309 /* get to that place in the file */
1310 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1311 elt
->private.special
.text
.size
- 14,L_SET
);
1312 /* write new flags */
1313 write (LOCAL
->fd
,LOCAL
->buf
,12);
1314 if (syncflag
) { /* sync if requested */
1316 fstat (LOCAL
->fd
,&sbuf
); /* get new write time */
1317 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
1318 tp
[0] = time (0); /* make sure read is later */
1319 utime (stream
->mailbox
,tp
);
1324 /* MTX locate header for a message
1325 * Accepts: MAIL stream
1327 * pointer to returned header size
1328 * Returns: position of header in file
1331 unsigned long mtx_hdrpos (MAILSTREAM
*stream
,unsigned long msgno
,
1332 unsigned long *size
)
1337 char *s
,tmp
[MAILTMPLEN
];
1338 MESSAGECACHE
*elt
= mtx_elt (stream
,msgno
);
1339 unsigned long ret
= elt
->private.special
.offset
+
1340 elt
->private.special
.text
.size
;
1341 /* is header size known? */
1342 if (!(*size
= elt
->private.msg
.header
.text
.size
)) {
1343 lseek (LOCAL
->fd
,ret
,L_SET
);/* get to header position */
1344 /* search message for CRLF CRLF */
1345 for (siz
= 1,s
= tmp
; siz
<= elt
->rfc822_size
; siz
++) {
1346 /* read another buffer as necessary */
1347 if ((--i
<= 0) && /* buffer empty? */
1348 (read (LOCAL
->fd
,s
= tmp
,
1349 i
= min (elt
->rfc822_size
- siz
,(long) MAILTMPLEN
)) < 0))
1350 return ret
; /* I/O error? */
1351 switch (q
) { /* sniff at buffer */
1352 case 0: /* first character */
1353 q
= (*s
++ == '\015') ? 1 : 0;
1355 case 1: /* second character */
1356 q
= (*s
++ == '\012') ? 2 : 0;
1358 case 2: /* third character */
1359 q
= (*s
++ == '\015') ? 3 : 0;
1361 case 3: /* fourth character */
1362 if (*s
++ == '\012') { /* have the sequence? */
1363 /* yes, note for later */
1364 elt
->private.msg
.header
.text
.size
= *size
= siz
;
1367 q
= 0; /* lost... */
1371 /* header consumes entire message */
1372 elt
->private.msg
.header
.text
.size
= *size
= elt
->rfc822_size
;