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: 15 June 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>
53 /* MTX I/O stream local data */
55 typedef struct mtx_local
{
56 unsigned int shouldcheck
: 1; /* if ping should do a check instead */
57 unsigned int mustcheck
: 1; /* if ping must do a check instead */
58 int fd
; /* file descriptor for I/O */
59 off_t filesize
; /* file size parsed */
60 time_t filetime
; /* last file time */
61 time_t lastsnarf
; /* last snarf time */
62 unsigned char *buf
; /* temporary buffer */
63 unsigned long buflen
; /* current size of temporary buffer */
67 /* Convenient access to local data */
69 #define LOCAL ((MTXLOCAL *) stream->local)
72 /* Function prototypes */
74 DRIVER
*mtx_valid (char *name
);
75 int mtx_isvalid (char *name
,char *file
);
76 void *mtx_parameters (long function
,void *value
);
77 void mtx_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
78 void mtx_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
79 void mtx_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
80 long mtx_create (MAILSTREAM
*stream
,char *mailbox
);
81 long mtx_delete (MAILSTREAM
*stream
,char *mailbox
);
82 long mtx_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
83 long mtx_status (MAILSTREAM
*stream
,char *mbx
,long flags
);
84 MAILSTREAM
*mtx_open (MAILSTREAM
*stream
);
85 void mtx_close (MAILSTREAM
*stream
,long options
);
86 void mtx_flags (MAILSTREAM
*stream
,char *sequence
,long flags
);
87 char *mtx_header (MAILSTREAM
*stream
,unsigned long msgno
,
88 unsigned long *length
,long flags
);
89 long mtx_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
);
90 void mtx_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
);
91 void mtx_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
92 long mtx_ping (MAILSTREAM
*stream
);
93 void mtx_check (MAILSTREAM
*stream
);
94 void mtx_snarf (MAILSTREAM
*stream
);
95 long mtx_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
96 long mtx_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
);
97 long mtx_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
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
);
107 /* MTX mail routines */
110 /* Driver dispatch used by MAIL */
113 "mtx", /* driver name */
115 DR_LOCAL
|DR_MAIL
|DR_CRLF
|DR_NOSTICKY
,
116 (DRIVER
*) NIL
, /* next driver */
117 mtx_valid
, /* mailbox is valid for us */
118 mtx_parameters
, /* manipulate parameters */
119 mtx_scan
, /* scan mailboxes */
120 mtx_list
, /* list mailboxes */
121 mtx_lsub
, /* list subscribed mailboxes */
122 NIL
, /* subscribe to mailbox */
123 NIL
, /* unsubscribe from mailbox */
124 mtx_create
, /* create mailbox */
125 mtx_delete
, /* delete mailbox */
126 mtx_rename
, /* rename mailbox */
127 mail_status_default
, /* status of mailbox */
128 mtx_open
, /* open mailbox */
129 mtx_close
, /* close mailbox */
130 mtx_flags
, /* fetch message "fast" attributes */
131 mtx_flags
, /* fetch message flags */
132 NIL
, /* fetch overview */
133 NIL
, /* fetch message envelopes */
134 mtx_header
, /* fetch message header */
135 mtx_text
, /* fetch message body */
136 NIL
, /* fetch partial message text */
137 NIL
, /* unique identifier */
138 NIL
, /* message number */
139 mtx_flag
, /* modify flags */
140 mtx_flagmsg
, /* per-message modify flags */
141 NIL
, /* search for message based on criteria */
142 NIL
, /* sort messages */
143 NIL
, /* thread messages */
144 mtx_ping
, /* ping mailbox to see if still alive */
145 mtx_check
, /* check for new messages */
146 mtx_expunge
, /* expunge deleted messages */
147 mtx_copy
, /* copy messages to another mailbox */
148 mtx_append
, /* append string message to mailbox */
149 NIL
/* garbage collect 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 * buffer to return file name
170 * Returns: T if valid, NIL otherwise
173 int mtx_isvalid (char *name
,char *file
)
177 char *s
,tmp
[MAILTMPLEN
];
179 struct utimbuf times
;
180 errno
= EINVAL
; /* assume invalid argument */
181 /* if file, get its status */
182 if ((s
= dummy_file (file
,name
)) && !stat (s
,&sbuf
) &&
183 ((sbuf
.st_mode
& S_IFMT
) == S_IFREG
)) {
184 if (!sbuf
.st_size
)errno
= 0;/* empty file */
185 else if ((fd
= open (file
,O_BINARY
|O_RDONLY
,NIL
)) >= 0) {
186 memset (tmp
,'\0',MAILTMPLEN
);
187 if ((read (fd
,tmp
,64) >= 0) && (s
= strchr (tmp
,'\015')) &&
188 (s
[1] == '\012')) { /* valid format? */
189 *s
= '\0'; /* tie off header */
190 /* must begin with dd-mmm-yy" */
191 ret
= (((tmp
[2] == '-' && tmp
[6] == '-') ||
192 (tmp
[1] == '-' && tmp
[5] == '-')) &&
193 (s
= strchr (tmp
+18,',')) && strchr (s
+2,';')) ? T
: NIL
;
195 else errno
= -1; /* bogus format */
196 close (fd
); /* close the file */
197 /* \Marked status? */
198 if (sbuf
.st_ctime
> sbuf
.st_atime
) {
199 /* preserve atime and mtime */
200 times
.actime
= sbuf
.st_atime
;
201 times
.modtime
= sbuf
.st_mtime
;
202 utime (file
,×
); /* 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 */
212 /* MTX manipulate driver parameters
213 * Accepts: function code
214 * function-dependent value
215 * Returns: function-dependent return value
218 void *mtx_parameters (long function
,void *value
)
223 /* MTX mail scan mailboxes
224 * Accepts: mail stream
230 void mtx_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
232 if (stream
) dummy_scan (NIL
,ref
,pat
,contents
);
236 /* MTX mail list mailboxes
237 * Accepts: mail stream
242 void mtx_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
244 if (stream
) dummy_list (NIL
,ref
,pat
);
248 /* MTX mail list subscribed mailboxes
249 * Accepts: mail stream
254 void mtx_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
256 if (stream
) dummy_lsub (NIL
,ref
,pat
);
259 /* MTX mail create mailbox
260 * Accepts: MAIL stream
261 * mailbox name to create
262 * Returns: T on success, NIL on failure
265 long mtx_create (MAILSTREAM
*stream
,char *mailbox
)
267 char *s
,mbx
[MAILTMPLEN
];
268 if (s
= dummy_file (mbx
,mailbox
)) return dummy_create (stream
,s
);
269 sprintf (mbx
,"Can't create %.80s: invalid name",mailbox
);
275 /* MTX mail delete mailbox
276 * Accepts: MAIL stream
277 * mailbox name to delete
278 * Returns: T on success, NIL on failure
281 long mtx_delete (MAILSTREAM
*stream
,char *mailbox
)
283 return mtx_rename (stream
,mailbox
,NIL
);
286 /* MTX mail rename mailbox
287 * Accepts: MAIL stream
289 * new mailbox name (or NIL for delete)
290 * Returns: T on success, NIL on failure
293 long mtx_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
296 char c
,*s
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
299 if (!dummy_file (file
,old
) ||
300 (newname
&& (!((s
= mailboxfile (tmp
,newname
)) && *s
) ||
301 ((s
= strrchr (tmp
,'\\')) && !s
[1])))) {
302 sprintf (tmp
,newname
?
303 "Can't rename mailbox %.80s to %.80s: invalid name" :
304 "Can't delete mailbox %.80s: invalid name",
309 if ((fd
= open (file
,O_BINARY
|O_RDWR
,NIL
)) < 0) {
310 sprintf (tmp
,"Can't open mailbox %.80s: %s",old
,strerror (errno
));
314 /* get exclusive parse/append permission */
315 if ((ld
= lockname (lock
,file
,LOCK_EX
)) < 0) {
316 mm_log ("Unable to lock rename mailbox",ERROR
);
319 /* lock out other users */
320 if (flock (fd
,LOCK_EX
|LOCK_NB
)) {
321 close (fd
); /* couldn't lock, give up on it then */
322 sprintf (tmp
,"Mailbox %.80s is in use by another process",old
);
324 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
328 if (newname
) { /* want rename? */
329 /* found superior to destination name? */
330 if ((s
= strrchr (tmp
,'\\')) && (s
!= tmp
) &&
331 ((tmp
[1] != ':') || (s
!= tmp
+ 2))) {
332 c
= s
[1]; /* remember character after delimiter */
333 *s
= s
[1] = '\0'; /* tie off name at delimiter */
334 /* name doesn't exist, create it */
335 if (stat (tmp
,&sbuf
) || ((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
)) {
336 *s
= '\\'; /* restore delimiter */
337 if (!dummy_create (stream
,tmp
)) ret
= NIL
;
339 else *s
= '\\'; /* restore delimiter */
340 s
[1] = c
; /* restore character after delimiter */
342 flock (fd
,LOCK_UN
); /* release lock on the file */
343 close (fd
); /* pacify NTFS */
344 /* rename the file */
345 if (ret
&& rename (file
,tmp
)) {
346 sprintf (tmp
,"Can't rename mailbox %.80s to %.80s: %s",old
,newname
,
349 ret
= NIL
; /* set failure */
353 flock (fd
,LOCK_UN
); /* release lock on the file */
354 close (fd
); /* pacify NTFS */
356 sprintf (tmp
,"Can't delete mailbox %.80s: %.80s",old
,strerror (errno
));
358 ret
= NIL
; /* set failure */
361 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
362 return ret
; /* return success */
366 * Accepts: stream to open
367 * Returns: stream on success, NIL on failure
370 MAILSTREAM
*mtx_open (MAILSTREAM
*stream
)
373 char tmp
[MAILTMPLEN
];
374 /* return prototype for OP_PROTOTYPE call */
375 if (!stream
) return &mtxproto
;
376 if (stream
->local
) fatal ("mtx recycle stream");
377 /* canonicalize the mailbox name */
378 if (!dummy_file (tmp
,stream
->mailbox
)) {
379 sprintf (tmp
,"Can't open - invalid name: %.80s",stream
->mailbox
);
382 if (stream
->rdonly
||
383 (fd
= open (tmp
,O_BINARY
|O_RDWR
,NIL
)) < 0) {
384 if ((fd
= open (tmp
,O_BINARY
|O_RDONLY
,NIL
)) < 0) {
385 sprintf (tmp
,"Can't open mailbox: %.80s",strerror (errno
));
389 else if (!stream
->rdonly
) { /* got it, but readonly */
390 mm_log ("Can't get write access to mailbox, access is readonly",WARN
);
394 stream
->local
= fs_get (sizeof (MTXLOCAL
));
395 LOCAL
->fd
= fd
; /* bind the file */
396 LOCAL
->buf
= (char *) fs_get (CHUNKSIZE
);
397 LOCAL
->buflen
= CHUNKSIZE
- 1;
398 /* note if an INBOX or not */
399 stream
->inbox
= !compare_cstring (stream
->mailbox
,"INBOX");
400 fs_give ((void **) &stream
->mailbox
);
401 stream
->mailbox
= cpystr (tmp
);
402 /* get shared parse permission */
403 if ((ld
= lockname (tmp
,stream
->mailbox
,LOCK_SH
)) < 0) {
404 mm_log ("Unable to lock open mailbox",ERROR
);
407 flock (LOCAL
->fd
,LOCK_SH
); /* lock the file */
408 unlockfd (ld
,tmp
); /* release shared parse permission */
409 LOCAL
->filesize
= 0; /* initialize parsed file size */
410 LOCAL
->filetime
= 0; /* time not set up yet */
411 LOCAL
->mustcheck
= LOCAL
->shouldcheck
= NIL
;
412 stream
->sequence
++; /* bump sequence number */
413 stream
->uid_validity
= (unsigned long) time (0);
415 stream
->nmsgs
= stream
->recent
= 0;
416 if (mtx_ping (stream
) && !stream
->nmsgs
)
417 mm_log ("Mailbox is empty",(long) NIL
);
418 if (!LOCAL
) return NIL
; /* failure if stream died */
419 stream
->perm_seen
= stream
->perm_deleted
=
420 stream
->perm_flagged
= stream
->perm_answered
= stream
->perm_draft
=
421 stream
->rdonly
? NIL
: T
;
422 stream
->perm_user_flags
= stream
->rdonly
? NIL
: 0xffffffff;
423 return stream
; /* return stream to caller */
427 * Accepts: MAIL stream
431 void mtx_close (MAILSTREAM
*stream
,long options
)
433 if (stream
&& LOCAL
) { /* only if a file is open */
434 int silent
= stream
->silent
;
435 stream
->silent
= T
; /* note this stream is dying */
436 if (options
& CL_EXPUNGE
) mtx_expunge (stream
,NIL
,NIL
);
437 stream
->silent
= silent
; /* restore previous status */
438 flock (LOCAL
->fd
,LOCK_UN
); /* unlock local file */
439 close (LOCAL
->fd
); /* close the local file */
440 /* free local text buffer */
441 if (LOCAL
->buf
) fs_give ((void **) &LOCAL
->buf
);
442 /* nuke the local data */
443 fs_give ((void **) &stream
->local
);
444 stream
->dtb
= NIL
; /* log out the DTB */
449 /* MTX mail fetch flags
450 * Accepts: MAIL stream
453 * Sniffs at file to see if some other process changed the flags
456 void mtx_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
459 if (mtx_ping (stream
) && /* ping mailbox, get new status for messages */
460 ((flags
& FT_UID
) ? mail_uid_sequence (stream
,sequence
) :
461 mail_sequence (stream
,sequence
)))
462 for (i
= 1; i
<= stream
->nmsgs
; i
++)
463 if (mail_elt (stream
,i
)->sequence
) mtx_elt (stream
,i
);
466 /* MTX mail fetch message header
467 * Accepts: MAIL stream
469 * pointer to returned header text length
471 * Returns: message header in RFC822 format
474 char *mtx_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
477 *length
= 0; /* default to empty */
478 if (flags
& FT_UID
) return "";/* UID call "impossible" */
479 /* get to header position */
480 lseek (LOCAL
->fd
,mtx_hdrpos (stream
,msgno
,length
),L_SET
);
481 /* is buffer big enough? */
482 if (*length
> LOCAL
->buflen
) {
483 fs_give ((void **) &LOCAL
->buf
);
484 LOCAL
->buf
= (char *) fs_get ((LOCAL
->buflen
= *length
) + 1);
486 LOCAL
->buf
[*length
] = '\0'; /* tie off string */
488 read (LOCAL
->fd
,LOCAL
->buf
,*length
);
492 /* MTX mail fetch message text (body only)
493 * Accepts: MAIL stream
495 * pointer to returned header text length
500 long mtx_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
505 /* UID call "impossible" */
506 if (flags
& FT_UID
) return NIL
;
507 elt
= mtx_elt (stream
,msgno
); /* get message status */
508 /* if message not seen */
509 if (!(flags
& FT_PEEK
) && !elt
->seen
) {
510 elt
->seen
= T
; /* mark message as seen */
511 /* recalculate status */
512 mtx_update_status (stream
,msgno
,NIL
);
513 mm_flags (stream
,msgno
);
515 /* find header position */
516 i
= mtx_hdrpos (stream
,msgno
,&j
);
517 d
.fd
= LOCAL
->fd
; /* set up file descriptor */
519 d
.chunk
= LOCAL
->buf
; /* initial buffer chunk */
520 d
.chunksize
= CHUNKSIZE
;
521 INIT (bs
,fd_string
,&d
,elt
->rfc822_size
- j
);
522 return T
; /* success */
525 /* MTX mail modify flags
526 * Accepts: MAIL stream
532 void mtx_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
)
534 struct utimbuf times
;
536 if (!stream
->rdonly
) { /* make sure the update takes */
538 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
539 times
.modtime
= LOCAL
->filetime
= sbuf
.st_mtime
;
540 times
.actime
= time (0); /* make sure read comes after all that */
541 utime (stream
->mailbox
,×
);
546 /* MTX mail per-message modify flags
547 * Accepts: MAIL stream
548 * message cache element
551 void mtx_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
554 /* maybe need to do a checkpoint? */
555 if (LOCAL
->filetime
&& !LOCAL
->shouldcheck
) {
556 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
557 if (LOCAL
->filetime
< sbuf
.st_mtime
) LOCAL
->shouldcheck
= T
;
558 LOCAL
->filetime
= 0; /* don't do this test for any other messages */
560 /* recalculate status */
561 mtx_update_status (stream
,elt
->msgno
,NIL
);
564 /* MTX mail ping mailbox
565 * Accepts: MAIL stream
566 * Returns: T if stream still alive, NIL if not
569 long mtx_ping (MAILSTREAM
*stream
)
574 char lock
[MAILTMPLEN
];
576 if (stream
&& LOCAL
) { /* only if stream already open */
577 fstat (LOCAL
->fd
,&sbuf
); /* get current file poop */
578 if (LOCAL
->filetime
&& !(LOCAL
->mustcheck
|| LOCAL
->shouldcheck
) &&
579 (LOCAL
->filetime
< sbuf
.st_mtime
)) LOCAL
->shouldcheck
= T
;
580 /* check for changed message status */
581 if (LOCAL
->mustcheck
|| LOCAL
->shouldcheck
) {
582 LOCAL
->filetime
= sbuf
.st_mtime
;
583 if (LOCAL
->shouldcheck
) /* babble when we do this unilaterally */
584 mm_notify (stream
,"[CHECK] Checking for flag updates",NIL
);
585 while (i
<= stream
->nmsgs
) mtx_elt (stream
,i
++);
586 LOCAL
->mustcheck
= LOCAL
->shouldcheck
= NIL
;
588 /* get shared parse/append permission */
589 if ((sbuf
.st_size
!= LOCAL
->filesize
) &&
590 ((ld
= lockname (lock
,stream
->mailbox
,LOCK_SH
)) >= 0)) {
591 /* parse resulting mailbox */
592 r
= (mtx_parse (stream
)) ? T
: NIL
;
593 unlockfd (ld
,lock
); /* release shared parse/append permission */
596 return r
; /* return result of the parse */
600 /* MTX mail check mailbox (reparses status too)
601 * Accepts: MAIL stream
604 void mtx_check (MAILSTREAM
*stream
)
606 /* mark that a check is desired */
607 if (LOCAL
) LOCAL
->mustcheck
= T
;
608 if (mtx_ping (stream
)) mm_log ("Check completed",(long) NIL
);
611 /* MTX mail expunge mailbox
612 * sequence to expunge if non-NIL
617 long mtx_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
620 struct utimbuf times
;
625 unsigned long j
,k
,m
,recent
;
627 unsigned long delta
= 0;
628 char lock
[MAILTMPLEN
];
630 if (!(ret
= (sequence
? ((options
& EX_UID
) ?
631 mail_uid_sequence (stream
,sequence
) :
632 mail_sequence (stream
,sequence
)) : LONGT
) &&
633 mtx_ping (stream
))); /* parse sequence if given, ping stream */
634 else if (stream
->rdonly
) mm_log ("Expunge ignored on readonly mailbox",WARN
);
636 if (LOCAL
->filetime
&& !LOCAL
->shouldcheck
) {
637 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
638 if (LOCAL
->filetime
< sbuf
.st_mtime
) LOCAL
->shouldcheck
= T
;
640 /* get exclusive parse/append permission */
641 if ((ld
= lockname (lock
,stream
->mailbox
,LOCK_EX
)) < 0)
642 mm_log ("Unable to lock expunge mailbox",ERROR
);
643 /* make sure see any newly-arrived messages */
644 else if (!mtx_parse (stream
));
645 /* get exclusive access */
646 else if (flock (LOCAL
->fd
,LOCK_EX
|LOCK_NB
)) {
647 flock (LOCAL
->fd
,LOCK_SH
);/* recover previous lock */
648 mm_log ("Can't expunge because mailbox is in use by another process",
650 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
654 mm_critical (stream
); /* go critical */
655 recent
= stream
->recent
; /* get recent now that pinged and locked */
656 /* for each message */
657 while (i
<= stream
->nmsgs
) {
658 /* get cache element */
659 elt
= mtx_elt (stream
,i
);
660 /* number of bytes to smash or preserve */
661 k
= elt
->private.special
.text
.size
+ elt
->rfc822_size
;
662 /* if need to expunge this message */
663 if (elt
->deleted
&& (sequence
? elt
->sequence
: T
)) {
664 /* if recent, note one less recent message */
665 if (elt
->recent
) --recent
;
666 delta
+= k
; /* number of bytes to delete */
667 /* notify upper levels */
668 mail_expunged (stream
,i
);
669 n
++; /* count up one more expunged message */
671 else if (i
++ && delta
) {/* preserved message */
672 /* first byte to preserve */
673 j
= elt
->private.special
.offset
;
674 do { /* read from source position */
675 m
= min (k
,LOCAL
->buflen
);
676 lseek (LOCAL
->fd
,j
,L_SET
);
677 read (LOCAL
->fd
,LOCAL
->buf
,m
);
678 pos
= j
- delta
; /* write to destination position */
680 lseek (LOCAL
->fd
,pos
,L_SET
);
681 if (write (LOCAL
->fd
,LOCAL
->buf
,m
) > 0) break;
682 mm_notify (stream
,strerror (errno
),WARN
);
683 mm_diskerror (stream
,errno
,T
);
685 pos
+= m
; /* new position */
686 j
+= m
; /* next chunk, perhaps */
687 } while (k
-= m
); /* until done */
688 /* note the new address of this text */
689 elt
->private.special
.offset
-= delta
;
691 /* preserved but no deleted messages */
692 else pos
= elt
->private.special
.offset
+ k
;
694 if (n
) { /* truncate file after last message */
695 if (pos
!= (LOCAL
->filesize
-= delta
)) {
697 "Calculated size mismatch %lu != %lu, delta = %lu",
698 (unsigned long) pos
,(unsigned long) LOCAL
->filesize
,delta
);
699 mm_log (LOCAL
->buf
,WARN
);
700 LOCAL
->filesize
= pos
;/* fix it then */
702 ftruncate (LOCAL
->fd
,LOCAL
->filesize
);
703 sprintf (LOCAL
->buf
,"Expunged %lu messages",n
);
704 /* output the news */
705 mm_log (LOCAL
->buf
,(long) NIL
);
707 else mm_log ("No messages deleted, so no update needed",(long) NIL
);
708 fsync (LOCAL
->fd
); /* force disk update */
709 fstat (LOCAL
->fd
,&sbuf
); /* get new write time */
710 times
.modtime
= LOCAL
->filetime
= sbuf
.st_mtime
;
711 times
.actime
= time (0); /* reset atime to now */
712 utime (stream
->mailbox
,×
);
713 mm_nocritical (stream
); /* release critical */
714 /* notify upper level of new mailbox size */
715 mail_exists (stream
,stream
->nmsgs
);
716 mail_recent (stream
,recent
);
717 flock (LOCAL
->fd
,LOCK_SH
);/* allow sharers again */
718 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
724 /* MTX mail copy message(s)
725 * Accepts: MAIL stream
727 * destination mailbox
729 * Returns: T if success, NIL if failed
732 long mtx_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
735 struct utimbuf times
;
740 char file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
742 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
743 /* make sure valid mailbox */
744 if (!mtx_isvalid (mailbox
,file
)) switch (errno
) {
745 case ENOENT
: /* no such file? */
746 mm_notify (stream
,"[TRYCREATE] Must create mailbox before copy",NIL
);
748 case 0: /* merely empty file? */
750 case EACCES
: /* file protected */
751 sprintf (LOCAL
->buf
,"Can't access destination: %.80s",mailbox
);
752 MM_LOG (LOCAL
->buf
,ERROR
);
755 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
756 sprintf (LOCAL
->buf
,"Invalid MTX-format mailbox name: %.80s",mailbox
);
757 mm_log (LOCAL
->buf
,ERROR
);
760 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
761 sprintf (LOCAL
->buf
,"Not a MTX-format mailbox: %.80s",mailbox
);
762 mm_log (LOCAL
->buf
,ERROR
);
765 if (!((options
& CP_UID
) ? mail_uid_sequence (stream
,sequence
) :
766 mail_sequence (stream
,sequence
))) return NIL
;
768 if ((fd
= open (file
,O_BINARY
|O_RDWR
|O_CREAT
,S_IREAD
|S_IWRITE
)) < 0) {
769 sprintf (LOCAL
->buf
,"Unable to open copy mailbox: %.80s",strerror (errno
));
770 mm_log (LOCAL
->buf
,ERROR
);
773 mm_critical (stream
); /* go critical */
774 /* get exclusive parse/append permission */
775 if (flock (fd
,LOCK_SH
) || ((ld
= lockname (lock
,file
,LOCK_EX
)) < 0)) {
776 mm_log ("Unable to lock copy mailbox",ERROR
);
777 mm_nocritical (stream
);
780 fstat (fd
,&sbuf
); /* get current file size */
781 lseek (fd
,sbuf
.st_size
,L_SET
);/* move to end of file */
783 /* for each requested message */
784 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); i
++)
785 if ((elt
= mail_elt (stream
,i
))->sequence
) {
786 lseek (LOCAL
->fd
,elt
->private.special
.offset
,L_SET
);
787 /* number of bytes to copy */
788 k
= elt
->private.special
.text
.size
+ elt
->rfc822_size
;
789 do { /* read from source position */
790 j
= min (k
,LOCAL
->buflen
);
791 read (LOCAL
->fd
,LOCAL
->buf
,j
);
792 if (write (fd
,LOCAL
->buf
,j
) < 0) ret
= NIL
;
793 } while (ret
&& (k
-= j
));/* until done */
795 /* make sure all the updates take */
796 if (!(ret
&& (ret
= !fsync (fd
)))) {
797 sprintf (LOCAL
->buf
,"Unable to write message: %s",strerror (errno
));
798 mm_log (LOCAL
->buf
,ERROR
);
799 ftruncate (fd
,sbuf
.st_size
);
801 /* set atime to now-1 if successful copy */
802 if (ret
) times
.actime
= time (0) - 1;
803 /* else preserved \Marked status */
804 else times
.actime
= (sbuf
.st_ctime
> sbuf
.st_atime
) ?
805 sbuf
.st_atime
: time (0);
806 times
.modtime
= sbuf
.st_mtime
;/* preserve mtime */
807 utime (file
,×
); /* set the times */
808 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
809 close (fd
); /* close the file */
810 mm_nocritical (stream
); /* release critical */
811 /* delete all requested messages */
812 if (ret
&& (options
& CP_MOVE
)) {
813 for (i
= 1; i
<= stream
->nmsgs
; i
++)
814 if ((elt
= mtx_elt (stream
,i
))->sequence
) {
815 elt
->deleted
= T
; /* mark message deleted */
816 /* recalculate status */
817 mtx_update_status (stream
,i
,NIL
);
819 if (!stream
->rdonly
) { /* make sure the update takes */
821 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
822 times
.modtime
= LOCAL
->filetime
= sbuf
.st_mtime
;
823 times
.actime
= time (0); /* make sure atime remains greater */
824 utime (stream
->mailbox
,×
);
827 if (ret
&& mail_parameters (NIL
,GET_COPYUID
,NIL
))
828 mm_log ("Can not return meaningful COPYUID with this mailbox format",WARN
);
832 /* MTX mail append message from stringstruct
833 * Accepts: MAIL stream
834 * destination mailbox
837 * Returns: T if append successful, else NIL
840 long mtx_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
844 char *flags
,*date
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
845 struct utimbuf times
;
852 /* default stream to prototype */
853 if (!stream
) stream
= &mtxproto
;
854 /* make sure valid mailbox */
855 if (!mtx_isvalid (mailbox
,file
)) switch (errno
) {
856 case ENOENT
: /* no such file? */
857 if (!compare_cstring (mailbox
,"INBOX")) mtx_create (NIL
,"INBOX");
859 mm_notify (stream
,"[TRYCREATE] Must create mailbox before append",NIL
);
863 case 0: /* merely empty file? */
865 case EACCES
: /* file protected */
866 sprintf (tmp
,"Can't access destination: %.80s",mailbox
);
870 sprintf (tmp
,"Invalid MTX-format mailbox name: %.80s",mailbox
);
874 sprintf (tmp
,"Not a MTX-format mailbox: %.80s",mailbox
);
878 /* get first message */
879 if (!(*af
) (stream
,data
,&flags
,&date
,&message
)) return NIL
;
881 /* open destination mailbox */
882 if (((fd
= open (file
,O_BINARY
|O_WRONLY
|O_APPEND
|O_CREAT
,S_IREAD
|S_IWRITE
))
883 < 0) || !(df
= fdopen (fd
,"ab"))) {
884 sprintf (tmp
,"Can't open append mailbox: %s",strerror (errno
));
888 /* get parse/append permission */
889 if (flock (fd
,LOCK_SH
) || ((ld
= lockname (lock
,file
,LOCK_EX
)) < 0)) {
890 mm_log ("Unable to lock append mailbox",ERROR
);
894 mm_critical (stream
); /* go critical */
895 fstat (fd
,&sbuf
); /* get current file size */
897 do { /* parse flags */
898 if (!SIZE (message
)) { /* guard against zero-length */
899 mm_log ("Append of zero-length message",ERROR
);
903 f
= mail_parse_flags (stream
,flags
,&i
);
904 /* reverse bits (dontcha wish we had CIRC?) */
905 for (uf
= 0; i
; uf
|= 1 << (29 - find_rightmost_bit (&i
)));
906 if (date
) { /* parse date if given */
907 if (!mail_parse_date (&elt
,date
)) {
908 sprintf (tmp
,"Bad date in append: %.80s",date
);
910 ret
= NIL
; /* mark failure */
913 mail_date (tmp
,&elt
); /* write preserved date */
915 else internal_date (tmp
); /* get current date in IMAP format */
917 if (fprintf (df
,"%s,%lu;%010lo%02lo\015\012",tmp
,i
= SIZE (message
),uf
,
918 (unsigned long) f
) < 0) ret
= NIL
;
919 else { /* write message */
920 if (i
) do c
= 0xff & SNX (message
);
921 while ((putc (c
,df
) != EOF
) && --i
);
922 /* get next message */
923 if (i
|| !(*af
) (stream
,data
,&flags
,&date
,&message
)) ret
= NIL
;
925 } while (ret
&& message
);
927 if (!ret
|| (fflush (df
) == EOF
)) {
928 ftruncate (fd
,sbuf
.st_size
);/* revert file */
929 close (fd
); /* make sure fclose() doesn't corrupt us */
931 sprintf (tmp
,"Message append failed: %s",strerror (errno
));
936 if (ret
) times
.actime
= time (0) - 1;
937 /* else preserved \Marked status */
938 else times
.actime
= (sbuf
.st_ctime
> sbuf
.st_atime
) ?
939 sbuf
.st_atime
: time (0);
940 times
.modtime
= sbuf
.st_mtime
;/* preserve mtime */
941 utime (file
,×
); /* set the times */
942 fclose (df
); /* close the file */
943 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
944 mm_nocritical (stream
); /* release critical */
945 if (ret
&& mail_parameters (NIL
,GET_APPENDUID
,NIL
))
946 mm_log ("Can not return meaningful APPENDUID with this mailbox format",
951 /* Internal routines */
954 /* MTX mail parse mailbox
955 * Accepts: MAIL stream
956 * Returns: T if parse OK
957 * NIL if failure, stream aborted
960 long mtx_parse (MAILSTREAM
*stream
)
963 MESSAGECACHE
*elt
= NIL
;
964 unsigned char c
,*s
,*t
,*x
;
965 char tmp
[MAILTMPLEN
];
967 long curpos
= LOCAL
->filesize
;
968 long nmsgs
= stream
->nmsgs
;
969 long recent
= stream
->recent
;
971 short silent
= stream
->silent
;
972 fstat (LOCAL
->fd
,&sbuf
); /* get status */
973 if (sbuf
.st_size
< curpos
) { /* sanity check */
974 sprintf (tmp
,"Mailbox shrank from %ld to %ld!",curpos
,sbuf
.st_size
);
976 mtx_close (stream
,NIL
);
979 stream
->silent
= T
; /* don't pass up mm_exists() events yet */
980 while (sbuf
.st_size
- curpos
){/* while there is stuff to parse */
981 /* get to that position in the file */
982 lseek (LOCAL
->fd
,curpos
,L_SET
);
983 if ((i
= read (LOCAL
->fd
,LOCAL
->buf
,64)) <= 0) {
984 sprintf (tmp
,"Unable to read internal header at %lu, size = %lu: %s",
985 (unsigned long) curpos
,(unsigned long) sbuf
.st_size
,
986 i
? strerror (errno
) : "no data read");
988 mtx_close (stream
,NIL
);
991 LOCAL
->buf
[i
] = '\0'; /* tie off buffer just in case */
992 if (!((s
= strchr (LOCAL
->buf
,'\015')) && (s
[1] == '\012'))) {
993 sprintf (tmp
,"Unable to find CRLF at %lu in %lu bytes, text: %s",
994 (unsigned long) curpos
,i
,(char *) LOCAL
->buf
);
996 mtx_close (stream
,NIL
);
999 *s
= '\0'; /* tie off header line */
1000 i
= (s
+ 2) - LOCAL
->buf
; /* note start of text offset */
1001 if (!((s
= strchr (LOCAL
->buf
,',')) && (t
= strchr (s
+1,';')))) {
1002 sprintf (tmp
,"Unable to parse internal header at %lu: %s",
1003 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1005 mtx_close (stream
,NIL
);
1008 *s
++ = '\0'; *t
++ = '\0'; /* tie off fields */
1010 added
= T
; /* note that a new message was added */
1011 /* swell the cache */
1012 mail_exists (stream
,++nmsgs
);
1013 /* instantiate an elt for this message */
1014 (elt
= mail_elt (stream
,nmsgs
))->valid
= T
;
1015 elt
->private.uid
= ++stream
->uid_last
;
1016 /* note file offset of header */
1017 elt
->private.special
.offset
= curpos
;
1019 elt
->private.special
.text
.size
= 0;
1020 /* header size not known yet */
1021 elt
->private.msg
.header
.text
.size
= 0;
1022 x
= s
; /* parse the header components */
1023 if (mail_parse_date (elt
,LOCAL
->buf
) &&
1024 (elt
->rfc822_size
= strtoul (s
,(char **) &s
,10)) && (!(s
&& *s
)) &&
1025 isdigit (t
[0]) && isdigit (t
[1]) && isdigit (t
[2]) &&
1026 isdigit (t
[3]) && isdigit (t
[4]) && isdigit (t
[5]) &&
1027 isdigit (t
[6]) && isdigit (t
[7]) && isdigit (t
[8]) &&
1028 isdigit (t
[9]) && isdigit (t
[10]) && isdigit (t
[11]) && !t
[12])
1029 elt
->private.special
.text
.size
= i
;
1031 sprintf (tmp
,"Unable to parse internal header elements at %ld: %s,%s;%s",
1032 curpos
,(char *) LOCAL
->buf
,(char *) x
,(char *) t
);
1034 mtx_close (stream
,NIL
);
1037 /* make sure didn't run off end of file */
1038 if ((curpos
+= (elt
->rfc822_size
+ i
)) > sbuf
.st_size
) {
1039 sprintf (tmp
,"Last message (at %lu) runs past end of file (%lu > %lu)",
1040 elt
->private.special
.offset
,(unsigned long) curpos
,
1041 (unsigned long) sbuf
.st_size
);
1043 mtx_close (stream
,NIL
);
1046 c
= t
[10]; /* remember first system flags byte */
1047 t
[10] = '\0'; /* tie off flags */
1048 j
= strtoul (t
,NIL
,8); /* get user flags value */
1049 t
[10] = c
; /* restore first system flags byte */
1050 /* set up all valid user flags (reversed!) */
1051 while (j
) if (((i
= 29 - find_rightmost_bit (&j
)) < NUSERFLAGS
) &&
1052 stream
->user_flags
[i
]) elt
->user_flags
|= 1 << i
;
1053 /* calculate system flags */
1054 if ((j
= ((t
[10]-'0') * 8) + t
[11]-'0') & fSEEN
) elt
->seen
= T
;
1055 if (j
& fDELETED
) elt
->deleted
= T
;
1056 if (j
& fFLAGGED
) elt
->flagged
= T
;
1057 if (j
& fANSWERED
) elt
->answered
= T
;
1058 if (j
& fDRAFT
) elt
->draft
= T
;
1059 if (!(j
& fOLD
)) { /* newly arrived message? */
1061 recent
++; /* count up a new recent message */
1062 /* mark it as old */
1063 mtx_update_status (stream
,nmsgs
,NIL
);
1066 fsync (LOCAL
->fd
); /* make sure all the fOLD flags take */
1067 /* update parsed file size and time */
1068 LOCAL
->filesize
= sbuf
.st_size
;
1069 fstat (LOCAL
->fd
,&sbuf
); /* get status again to ensure time is right */
1070 LOCAL
->filetime
= sbuf
.st_mtime
;
1071 if (added
&& !stream
->rdonly
){/* make sure atime updated */
1072 struct utimbuf times
;
1073 times
.actime
= time (0);
1074 times
.modtime
= LOCAL
->filetime
;
1075 utime (stream
->mailbox
,×
);
1077 stream
->silent
= silent
; /* can pass up events now */
1078 mail_exists (stream
,nmsgs
); /* notify upper level of new mailbox size */
1079 mail_recent (stream
,recent
); /* and of change in recent messages */
1080 return LONGT
; /* return the winnage */
1083 /* MTX get cache element with status updating from file
1084 * Accepts: MAIL stream
1086 * Returns: cache element
1089 MESSAGECACHE
*mtx_elt (MAILSTREAM
*stream
,unsigned long msgno
)
1091 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1092 struct { /* old flags */
1093 unsigned int seen
: 1;
1094 unsigned int deleted
: 1;
1095 unsigned int flagged
: 1;
1096 unsigned int answered
: 1;
1097 unsigned int draft
: 1;
1098 unsigned long user_flags
;
1100 old
.seen
= elt
->seen
; old
.deleted
= elt
->deleted
; old
.flagged
= elt
->flagged
;
1101 old
.answered
= elt
->answered
; old
.draft
= elt
->draft
;
1102 old
.user_flags
= elt
->user_flags
;
1103 mtx_read_flags (stream
,elt
);
1104 if ((old
.seen
!= elt
->seen
) || (old
.deleted
!= elt
->deleted
) ||
1105 (old
.flagged
!= elt
->flagged
) || (old
.answered
!= elt
->answered
) ||
1106 (old
.draft
!= elt
->draft
) || (old
.user_flags
!= elt
->user_flags
))
1107 mm_flags (stream
,msgno
); /* let top level know */
1111 /* MTX read flags from file
1112 * Accepts: MAIL stream
1113 * Returns: cache element
1116 void mtx_read_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
1119 /* noop if readonly and have valid flags */
1120 if (stream
->rdonly
&& elt
->valid
) return;
1121 /* set the seek pointer */
1122 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1123 elt
->private.special
.text
.size
- 14,L_SET
);
1124 /* read the new flags */
1125 if (read (LOCAL
->fd
,LOCAL
->buf
,12) < 0) {
1126 sprintf (LOCAL
->buf
,"Unable to read new status: %s",strerror (errno
));
1129 /* calculate system flags */
1130 i
= (((LOCAL
->buf
[10]-'0') * 8) + LOCAL
->buf
[11]-'0');
1131 elt
->seen
= i
& fSEEN
? T
: NIL
; elt
->deleted
= i
& fDELETED
? T
: NIL
;
1132 elt
->flagged
= i
& fFLAGGED
? T
: NIL
;
1133 elt
->answered
= i
& fANSWERED
? T
: NIL
; elt
->draft
= i
& fDRAFT
? T
: NIL
;
1134 LOCAL
->buf
[10] = '\0'; /* tie off flags */
1135 j
= strtoul(LOCAL
->buf
,NIL
,8);/* get user flags value */
1136 /* set up all valid user flags (reversed!) */
1137 while (j
) if (((i
= 29 - find_rightmost_bit (&j
)) < NUSERFLAGS
) &&
1138 stream
->user_flags
[i
]) elt
->user_flags
|= 1 << i
;
1139 elt
->valid
= T
; /* have valid flags now */
1142 /* MTX update status string
1143 * Accepts: MAIL stream
1145 * flag saying whether or not to sync
1148 void mtx_update_status (MAILSTREAM
*stream
,unsigned long msgno
,long syncflag
)
1150 struct utimbuf times
;
1152 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1153 unsigned long j
,k
= 0;
1155 if (stream
->rdonly
|| !elt
->valid
) mtx_read_flags (stream
,elt
);
1156 else { /* readwrite */
1157 j
= elt
->user_flags
; /* get user flags */
1158 /* reverse bits (dontcha wish we had CIRC?) */
1159 while (j
) k
|= 1 << (29 - find_rightmost_bit (&j
));
1160 /* print new flag string */
1161 sprintf (LOCAL
->buf
,"%010lo%02o",k
,(unsigned)
1162 (fOLD
+ (fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
1163 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
1164 (fDRAFT
* elt
->draft
)));
1165 while (T
) { /* get to that place in the file */
1166 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1167 elt
->private.special
.text
.size
- 14,L_SET
);
1168 /* write new flags */
1169 if (write (LOCAL
->fd
,LOCAL
->buf
,12) > 0) break;
1170 mm_notify (stream
,strerror (errno
),WARN
);
1171 mm_diskerror (stream
,errno
,T
);
1173 if (syncflag
) { /* sync if requested */
1175 fstat (LOCAL
->fd
,&sbuf
); /* get new write time */
1176 times
.modtime
= LOCAL
->filetime
= sbuf
.st_mtime
;
1177 times
.actime
= time (0); /* make sure read is later */
1178 utime (stream
->mailbox
,×
);
1183 /* MTX locate header for a message
1184 * Accepts: MAIL stream
1186 * pointer to returned header size
1187 * Returns: position of header in file
1190 unsigned long mtx_hdrpos (MAILSTREAM
*stream
,unsigned long msgno
,
1191 unsigned long *size
)
1196 char *s
,tmp
[MAILTMPLEN
];
1197 MESSAGECACHE
*elt
= mtx_elt (stream
,msgno
);
1198 unsigned long ret
= elt
->private.special
.offset
+
1199 elt
->private.special
.text
.size
;
1200 /* is header size known? */
1201 if (!(*size
= elt
->private.msg
.header
.text
.size
)) {
1202 lseek (LOCAL
->fd
,ret
,L_SET
);/* get to header position */
1203 /* search message for CRLF CRLF */
1204 for (siz
= 1,s
= tmp
; siz
<= elt
->rfc822_size
; siz
++) {
1205 /* read another buffer as necessary */
1206 if ((--i
<= 0) && /* buffer empty? */
1207 (read (LOCAL
->fd
,s
= tmp
,
1208 i
= min (elt
->rfc822_size
- siz
,(long) MAILTMPLEN
)) < 0))
1209 return ret
; /* I/O error? */
1210 switch (q
) { /* sniff at buffer */
1211 case 0: /* first character */
1212 q
= (*s
++ == '\015') ? 1 : 0;
1214 case 1: /* second character */
1215 q
= (*s
++ == '\012') ? 2 : 0;
1217 case 2: /* third character */
1218 q
= (*s
++ == '\015') ? 3 : 0;
1220 case 3: /* fourth character */
1221 if (*s
++ == '\012') { /* have the sequence? */
1222 /* yes, note for later */
1223 elt
->private.msg
.header
.text
.size
= *size
= siz
;
1226 q
= 0; /* lost... */
1230 /* header consumes entire message */
1231 elt
->private.msg
.header
.text
.size
= *size
= elt
->rfc822_size
;