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: Tenex 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.
38 * Most of the text sizes are in internal (LF-only) form, except for the
39 * msg.text size. Beware.
45 extern int errno
; /* just in case */
52 /* TENEX I/O stream local data */
54 typedef struct tenex_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
; /* local snarf time */
61 unsigned char *buf
; /* temporary buffer */
62 unsigned long buflen
; /* current size of temporary buffer */
63 unsigned long uid
; /* current text uid */
64 SIZEDTEXT text
; /* current text */
68 /* Convenient access to local data */
70 #define LOCAL ((TENEXLOCAL *) stream->local)
73 /* Function prototypes */
75 DRIVER
*tenex_valid (char *name
);
76 int tenex_isvalid (char *name
,char *tmp
);
77 void *tenex_parameters (long function
,void *value
);
78 void tenex_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
79 void tenex_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
80 void tenex_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
81 long tenex_create (MAILSTREAM
*stream
,char *mailbox
);
82 long tenex_delete (MAILSTREAM
*stream
,char *mailbox
);
83 long tenex_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
84 long tenex_status (MAILSTREAM
*stream
,char *mbx
,long flags
);
85 MAILSTREAM
*tenex_open (MAILSTREAM
*stream
);
86 void tenex_close (MAILSTREAM
*stream
,long options
);
87 void tenex_fast (MAILSTREAM
*stream
,char *sequence
,long flags
);
88 void tenex_flags (MAILSTREAM
*stream
,char *sequence
,long flags
);
89 char *tenex_header (MAILSTREAM
*stream
,unsigned long msgno
,
90 unsigned long *length
,long flags
);
91 long tenex_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
);
92 void tenex_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
);
93 void tenex_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
94 long tenex_ping (MAILSTREAM
*stream
);
95 void tenex_check (MAILSTREAM
*stream
);
96 void tenex_snarf (MAILSTREAM
*stream
);
97 long tenex_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
98 long tenex_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
);
99 long tenex_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
101 unsigned long tenex_size (MAILSTREAM
*stream
,unsigned long m
);
102 char *tenex_file (char *dst
,char *name
);
103 long tenex_parse (MAILSTREAM
*stream
);
104 MESSAGECACHE
*tenex_elt (MAILSTREAM
*stream
,unsigned long msgno
);
105 void tenex_read_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
);
106 void tenex_update_status (MAILSTREAM
*stream
,unsigned long msgno
,
108 unsigned long tenex_hdrpos (MAILSTREAM
*stream
,unsigned long msgno
,
109 unsigned long *size
);
111 /* Tenex mail routines */
114 /* Driver dispatch used by MAIL */
116 DRIVER tenexdriver
= {
117 "tenex", /* driver name */
118 DR_LOCAL
|DR_MAIL
|DR_NOSTICKY
|DR_LOCKING
,
120 (DRIVER
*) NIL
, /* next driver */
121 tenex_valid
, /* mailbox is valid for us */
122 tenex_parameters
, /* manipulate parameters */
123 tenex_scan
, /* scan mailboxes */
124 tenex_list
, /* list mailboxes */
125 tenex_lsub
, /* list subscribed mailboxes */
126 NIL
, /* subscribe to mailbox */
127 NIL
, /* unsubscribe from mailbox */
128 dummy_create
, /* create mailbox */
129 tenex_delete
, /* delete mailbox */
130 tenex_rename
, /* rename mailbox */
131 tenex_status
, /* status of mailbox */
132 tenex_open
, /* open mailbox */
133 tenex_close
, /* close mailbox */
134 tenex_fast
, /* fetch message "fast" attributes */
135 tenex_flags
, /* fetch message flags */
136 NIL
, /* fetch overview */
137 NIL
, /* fetch message envelopes */
138 tenex_header
, /* fetch message header */
139 tenex_text
, /* fetch message body */
140 NIL
, /* fetch partial message text */
141 NIL
, /* unique identifier */
142 NIL
, /* message number */
143 tenex_flag
, /* modify flags */
144 tenex_flagmsg
, /* per-message modify flags */
145 NIL
, /* search for message based on criteria */
146 NIL
, /* sort messages */
147 NIL
, /* thread messages */
148 tenex_ping
, /* ping mailbox to see if still alive */
149 tenex_check
, /* check for new messages */
150 tenex_expunge
, /* expunge deleted messages */
151 tenex_copy
, /* copy messages to another mailbox */
152 tenex_append
, /* append string message to mailbox */
153 NIL
/* garbage collect stream */
156 /* prototype stream */
157 MAILSTREAM tenexproto
= {&tenexdriver
};
159 /* Tenex mail validate mailbox
160 * Accepts: mailbox name
161 * Returns: our driver if name is valid, NIL otherwise
164 DRIVER
*tenex_valid (char *name
)
166 char tmp
[MAILTMPLEN
];
167 return tenex_isvalid (name
,tmp
) ? &tenexdriver
: NIL
;
171 /* Tenex mail test for valid mailbox
172 * Accepts: mailbox name
173 * Returns: T if valid, NIL otherwise
176 int tenex_isvalid (char *name
,char *tmp
)
180 char *s
,file
[MAILTMPLEN
];
183 errno
= EINVAL
; /* assume invalid argument */
184 /* if file, get its status */
185 if ((s
= tenex_file (file
,name
)) && !stat (s
,&sbuf
)) {
186 if (!sbuf
.st_size
) { /* allow empty file if INBOX */
187 if ((s
= mailboxfile (tmp
,name
)) && !*s
) ret
= T
;
188 else errno
= 0; /* empty file */
190 else if ((fd
= open (file
,O_RDONLY
,NIL
)) >= 0) {
191 memset (tmp
,'\0',MAILTMPLEN
);
192 if ((read (fd
,tmp
,64) >= 0) && (s
= strchr (tmp
,'\012')) &&
193 (s
[-1] != '\015')) { /* valid format? */
194 *s
= '\0'; /* tie off header */
195 /* must begin with dd-mmm-yy" */
196 ret
= (((tmp
[2] == '-' && tmp
[6] == '-') ||
197 (tmp
[1] == '-' && tmp
[5] == '-')) &&
198 (s
= strchr (tmp
+18,',')) && strchr (s
+2,';')) ? T
: NIL
;
200 else errno
= -1; /* bogus format */
201 close (fd
); /* close the file */
202 /* \Marked status? */
203 if (sbuf
.st_ctime
> sbuf
.st_atime
) {
204 tp
[0] = sbuf
.st_atime
; /* preserve atime and mtime */
205 tp
[1] = sbuf
.st_mtime
;
206 utime (file
,tp
); /* set the times */
210 /* in case INBOX but not tenex format */
211 else if ((errno
== ENOENT
) && !compare_cstring (name
,"INBOX")) errno
= -1;
212 return ret
; /* return what we should */
215 /* Tenex manipulate driver parameters
216 * Accepts: function code
217 * function-dependent value
218 * Returns: function-dependent return value
221 void *tenex_parameters (long function
,void *value
)
224 switch ((int) function
) {
226 if (value
) ret
= tenex_file ((char *) value
,"INBOX");
233 /* Tenex mail scan mailboxes
234 * Accepts: mail stream
240 void tenex_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
242 if (stream
) dummy_scan (NIL
,ref
,pat
,contents
);
246 /* Tenex mail list mailboxes
247 * Accepts: mail stream
252 void tenex_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
254 if (stream
) dummy_list (NIL
,ref
,pat
);
258 /* Tenex mail list subscribed mailboxes
259 * Accepts: mail stream
264 void tenex_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
266 if (stream
) dummy_lsub (NIL
,ref
,pat
);
269 /* Tenex mail delete mailbox
270 * Accepts: MAIL stream
271 * mailbox name to delete
272 * Returns: T on success, NIL on failure
275 long tenex_delete (MAILSTREAM
*stream
,char *mailbox
)
277 return tenex_rename (stream
,mailbox
,NIL
);
281 /* Tenex mail rename mailbox
282 * Accepts: MAIL stream
284 * new mailbox name (or NIL for delete)
285 * Returns: T on success, NIL on failure
288 long tenex_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
291 char c
,*s
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
294 if (!tenex_file (file
,old
) ||
295 (newname
&& (!((s
= mailboxfile (tmp
,newname
)) && *s
) ||
296 ((s
= strrchr (tmp
,'/')) && !s
[1])))) {
299 "Can't rename mailbox %.80s to %.80s: invalid name",
303 "Can't delete mailbox %.80s: invalid name", old
);
307 else if ((fd
= open (file
,O_RDWR
,NIL
)) < 0) {
308 sprintf (tmp
,"Can't open mailbox %.80s: %s",old
,strerror (errno
));
312 /* get exclusive parse/append permission */
313 if ((ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0) {
314 MM_LOG ("Unable to lock rename mailbox",ERROR
);
317 /* lock out other users */
318 if (flock (fd
,LOCK_EX
|LOCK_NB
)) {
319 close (fd
); /* couldn't lock, give up on it then */
320 sprintf (tmp
,"Mailbox %.80s is in use by another process",old
);
322 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
326 if (newname
) { /* want rename? */
327 if ((s
= strrchr (tmp
,'/')) != NULL
) {/* found superior to destination name? */
328 c
= *++s
; /* remember first character of inferior */
329 *s
= '\0'; /* tie off to get just superior */
330 /* name doesn't exist, create it */
331 if ((stat (tmp
,&sbuf
) || ((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
)) &&
332 !dummy_create_path (stream
,tmp
,get_dir_protection (newname
)))
334 else *s
= c
; /* restore full name */
336 /* rename the file */
337 if (ret
&& rename (file
,tmp
)) {
338 sprintf (tmp
,"Can't rename mailbox %.80s to %.80s: %s",old
,newname
,
341 ret
= NIL
; /* set failure */
344 else if (unlink (file
)) {
345 sprintf (tmp
,"Can't delete mailbox %.80s: %s",old
,strerror (errno
));
347 ret
= NIL
; /* set failure */
349 flock (fd
,LOCK_UN
); /* release lock on the file */
350 close (fd
); /* close the file */
351 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
352 /* recreate file if renamed INBOX */
353 if (ret
&& !compare_cstring (old
,"INBOX")) dummy_create (NIL
,"mail.txt");
354 return ret
; /* return success */
358 * Accepts: mail stream
361 * Returns: T on success, NIL on failure
364 long tenex_status (MAILSTREAM
*stream
,char *mbx
,long flags
)
368 MAILSTREAM
*tstream
= NIL
;
369 MAILSTREAM
*systream
= NIL
;
370 /* make temporary stream (unless this mbx) */
371 if (!stream
&& !(stream
= tstream
=
372 mail_open (NIL
,mbx
,OP_READONLY
|OP_SILENT
))) return NIL
;
373 status
.flags
= flags
; /* return status values */
374 status
.messages
= stream
->nmsgs
;
375 status
.recent
= stream
->recent
;
376 if (flags
& SA_UNSEEN
) /* must search to get unseen messages */
377 for (i
= 1,status
.unseen
= 0; i
<= stream
->nmsgs
; i
++)
378 if (!mail_elt (stream
,i
)->seen
) status
.unseen
++;
379 status
.uidnext
= stream
->uid_last
+ 1;
380 status
.uidvalidity
= stream
->uid_validity
;
381 /* calculate post-snarf results */
382 if (!status
.recent
&& stream
->inbox
&&
383 (systream
= mail_open (NIL
,sysinbox (),OP_READONLY
|OP_SILENT
))) {
384 status
.messages
+= systream
->nmsgs
;
385 status
.recent
+= systream
->recent
;
386 if (flags
& SA_UNSEEN
) /* must search to get unseen messages */
387 for (i
= 1; i
<= systream
->nmsgs
; i
++)
388 if (!mail_elt (systream
,i
)->seen
) status
.unseen
++;
389 /* kludge but probably good enough */
390 status
.uidnext
+= systream
->nmsgs
;
392 MM_STATUS(stream
,mbx
,&status
);/* pass status to main program */
393 if (tstream
) mail_close (tstream
);
394 if (systream
) mail_close (systream
);
395 return T
; /* success */
399 * Accepts: stream to open
400 * Returns: stream on success, NIL on failure
403 MAILSTREAM
*tenex_open (MAILSTREAM
*stream
)
406 char tmp
[MAILTMPLEN
];
407 blocknotify_t bn
= (blocknotify_t
) mail_parameters (NIL
,GET_BLOCKNOTIFY
,NIL
);
408 /* return prototype for OP_PROTOTYPE call */
409 if (!stream
) return user_flags (&tenexproto
);
410 if (stream
->local
) fatal ("tenex recycle stream");
411 user_flags (stream
); /* set up user flags */
412 /* canonicalize the mailbox name */
413 if (!tenex_file (tmp
,stream
->mailbox
)) {
414 sprintf (tmp
,"Can't open - invalid name: %.80s",stream
->mailbox
);
417 if (stream
->rdonly
||
418 (fd
= open (tmp
,O_RDWR
,NIL
)) < 0) {
419 if ((fd
= open (tmp
,O_RDONLY
,NIL
)) < 0) {
420 sprintf (tmp
,"Can't open mailbox: %s",strerror (errno
));
424 else if (!stream
->rdonly
) { /* got it, but readonly */
425 MM_LOG ("Can't get write access to mailbox, access is readonly",WARN
);
429 stream
->local
= fs_get (sizeof (TENEXLOCAL
));
430 LOCAL
->buf
= (char *) fs_get (CHUNKSIZE
);
431 LOCAL
->buflen
= CHUNKSIZE
- 1;
432 LOCAL
->text
.data
= (unsigned char *) fs_get (CHUNKSIZE
);
433 LOCAL
->text
.size
= CHUNKSIZE
- 1;
435 /* note if an INBOX or not */
436 stream
->inbox
= !compare_cstring (stream
->mailbox
,"INBOX");
437 LOCAL
->fd
= fd
; /* bind the file */
439 fs_give ((void **) &stream
->mailbox
);
440 /* save canonical name */
441 stream
->mailbox
= cpystr (tmp
);
442 /* get shared parse permission */
443 if ((ld
= lockfd (fd
,tmp
,LOCK_SH
)) < 0) {
444 MM_LOG ("Unable to lock open mailbox",ERROR
);
447 (*bn
) (BLOCK_FILELOCK
,NIL
);
448 flock (LOCAL
->fd
,LOCK_SH
); /* lock the file */
449 (*bn
) (BLOCK_NONE
,NIL
);
450 unlockfd (ld
,tmp
); /* release shared parse permission */
451 LOCAL
->filesize
= 0; /* initialize parsed file size */
452 /* time not set up yet */
453 LOCAL
->lastsnarf
= LOCAL
->filetime
= 0;
454 LOCAL
->mustcheck
= LOCAL
->shouldcheck
= NIL
;
455 stream
->sequence
++; /* bump sequence number */
457 stream
->nmsgs
= stream
->recent
= 0;
458 if (tenex_ping (stream
) && !stream
->nmsgs
)
459 MM_LOG ("Mailbox is empty",(long) NIL
);
460 if (!LOCAL
) return NIL
; /* failure if stream died */
461 stream
->perm_seen
= stream
->perm_deleted
=
462 stream
->perm_flagged
= stream
->perm_answered
= stream
->perm_draft
=
463 stream
->rdonly
? NIL
: T
;
464 stream
->perm_user_flags
= stream
->rdonly
? NIL
: 0xffffffff;
465 return stream
; /* return stream to caller */
469 * Accepts: MAIL stream
473 void tenex_close (MAILSTREAM
*stream
,long options
)
475 if (stream
&& LOCAL
) { /* only if a file is open */
476 int silent
= stream
->silent
;
477 stream
->silent
= T
; /* note this stream is dying */
478 if (options
& CL_EXPUNGE
) tenex_expunge (stream
,NIL
,NIL
);
479 stream
->silent
= silent
; /* restore previous status */
480 flock (LOCAL
->fd
,LOCK_UN
); /* unlock local file */
481 close (LOCAL
->fd
); /* close the local file */
482 /* free local text buffer */
483 if (LOCAL
->buf
) fs_give ((void **) &LOCAL
->buf
);
484 if (LOCAL
->text
.data
) fs_give ((void **) &LOCAL
->text
.data
);
485 /* nuke the local data */
486 fs_give ((void **) &stream
->local
);
487 stream
->dtb
= NIL
; /* log out the DTB */
491 /* Tenex mail fetch fast data
492 * Accepts: MAIL stream
497 void tenex_fast (MAILSTREAM
*stream
,char *sequence
,long flags
)
502 if (stream
&& LOCAL
&&
503 ((flags
& FT_UID
) ? mail_uid_sequence (stream
,sequence
) :
504 mail_sequence (stream
,sequence
)))
505 for (i
= 1; i
<= stream
->nmsgs
; i
++)
506 if ((elt
= mail_elt (stream
,i
))->sequence
) {
507 if (!elt
->rfc822_size
) { /* have header size yet? */
508 lseek (LOCAL
->fd
,elt
->private.special
.offset
+
509 elt
->private.special
.text
.size
,L_SET
);
510 /* resize bigbuf if necessary */
511 if (LOCAL
->buflen
< elt
->private.msg
.full
.text
.size
) {
512 fs_give ((void **) &LOCAL
->buf
);
513 LOCAL
->buflen
= elt
->private.msg
.full
.text
.size
;
514 LOCAL
->buf
= (char *) fs_get (LOCAL
->buflen
+ 1);
517 LOCAL
->buf
[elt
->private.msg
.full
.text
.size
] = '\0';
518 /* read in the message */
519 read (LOCAL
->fd
,LOCAL
->buf
,elt
->private.msg
.full
.text
.size
);
520 INIT (&bs
,mail_string
,(void *) LOCAL
->buf
,
521 elt
->private.msg
.full
.text
.size
);
522 /* calculate its CRLF size */
523 elt
->rfc822_size
= strcrlflen (&bs
);
525 tenex_elt (stream
,i
); /* get current flags from file */
530 /* Tenex mail fetch flags
531 * Accepts: MAIL stream
534 * Sniffs at file to get flags
537 void tenex_flags (MAILSTREAM
*stream
,char *sequence
,long flags
)
540 if (stream
&& LOCAL
&&
541 ((flags
& FT_UID
) ? mail_uid_sequence (stream
,sequence
) :
542 mail_sequence (stream
,sequence
)))
543 for (i
= 1; i
<= stream
->nmsgs
; i
++)
544 if (mail_elt (stream
,i
)->sequence
) tenex_elt (stream
,i
);
547 /* TENEX mail fetch message header
548 * Accepts: MAIL stream
550 * pointer to returned header text length
552 * Returns: message header in RFC822 format
555 char *tenex_header (MAILSTREAM
*stream
,unsigned long msgno
,
556 unsigned long *length
,long flags
)
560 *length
= 0; /* default to empty */
561 if (flags
& FT_UID
) return "";/* UID call "impossible" */
562 /* get to header position */
563 lseek (LOCAL
->fd
,tenex_hdrpos (stream
,msgno
,&i
),L_SET
);
564 if (flags
& FT_INTERNAL
) {
565 if (i
> LOCAL
->buflen
) { /* resize if not enough space */
566 fs_give ((void **) &LOCAL
->buf
);
567 LOCAL
->buf
= (char *) fs_get (LOCAL
->buflen
= i
+ 1);
570 read (LOCAL
->fd
,LOCAL
->buf
,*length
= i
);
573 s
= (char *) fs_get (i
+ 1);/* get readin buffer */
574 s
[i
] = '\0'; /* tie off string */
575 read (LOCAL
->fd
,s
,i
); /* slurp the data */
576 /* make CRLF copy of string */
577 *length
= strcrlfcpy (&LOCAL
->buf
,&LOCAL
->buflen
,s
,i
);
578 fs_give ((void **) &s
); /* free readin buffer */
580 return (char *) LOCAL
->buf
;
583 /* TENEX mail fetch message text (body only)
584 * Accepts: MAIL stream
586 * pointer to returned stringstruct
591 long tenex_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
596 /* UID call "impossible" */
597 if (flags
& FT_UID
) return NIL
;
598 /* get message status */
599 elt
= tenex_elt (stream
,msgno
);
600 /* if message not seen */
601 if (!(flags
& FT_PEEK
) && !elt
->seen
) {
602 elt
->seen
= T
; /* mark message as seen */
603 /* recalculate status */
604 tenex_update_status (stream
,msgno
,T
);
605 MM_FLAGS (stream
,msgno
);
607 if (flags
& FT_INTERNAL
) { /* if internal representation wanted */
608 /* find header position */
609 i
= tenex_hdrpos (stream
,msgno
,&j
);
610 if (i
> LOCAL
->buflen
) { /* resize if not enough space */
611 fs_give ((void **) &LOCAL
->buf
);
612 LOCAL
->buf
= (char *) fs_get (LOCAL
->buflen
= i
+ 1);
614 /* go to text position */
615 lseek (LOCAL
->fd
,i
+ j
,L_SET
);
617 read (LOCAL
->fd
,LOCAL
->buf
,i
);
618 /* set up stringstruct for internal */
619 INIT (bs
,mail_string
,LOCAL
->buf
,i
);
621 else { /* normal form, previous text cached? */
622 if (elt
->private.uid
== LOCAL
->uid
)
623 i
= elt
->private.msg
.text
.text
.size
;
624 else { /* not cached, cache it now */
625 LOCAL
->uid
= elt
->private.uid
;
626 /* find header position */
627 i
= tenex_hdrpos (stream
,msgno
,&j
);
628 /* go to text position */
629 lseek (LOCAL
->fd
,i
+ j
,L_SET
);
630 s
= (char *) fs_get ((i
= tenex_size (stream
,msgno
) - j
) + 1);
631 s
[i
] = '\0'; /* tie off string */
632 read (LOCAL
->fd
,s
,i
); /* slurp the data */
633 /* make CRLF copy of string */
634 i
= elt
->private.msg
.text
.text
.size
=
635 strcrlfcpy (&LOCAL
->text
.data
,&LOCAL
->text
.size
,s
,i
);
636 fs_give ((void **) &s
); /* free readin buffer */
638 /* set up stringstruct */
639 INIT (bs
,mail_string
,LOCAL
->text
.data
,i
);
641 return T
; /* success */
644 /* Tenex mail modify flags
645 * Accepts: MAIL stream
651 void tenex_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
)
655 if (!stream
->rdonly
) { /* make sure the update takes */
657 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
658 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
659 tp
[0] = time (0); /* make sure read comes after all that */
660 utime (stream
->mailbox
,tp
);
665 /* Tenex mail per-message modify flags
666 * Accepts: MAIL stream
667 * message cache element
670 void tenex_flagmsg (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
673 /* maybe need to do a checkpoint? */
674 if (LOCAL
->filetime
&& !LOCAL
->shouldcheck
) {
675 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
676 if (LOCAL
->filetime
< sbuf
.st_mtime
) LOCAL
->shouldcheck
= T
;
677 LOCAL
->filetime
= 0; /* don't do this test for any other messages */
679 /* recalculate status */
680 tenex_update_status (stream
,elt
->msgno
,NIL
);
683 /* Tenex mail ping mailbox
684 * Accepts: MAIL stream
685 * Returns: T if stream still alive, NIL if not
688 long tenex_ping (MAILSTREAM
*stream
)
693 char lock
[MAILTMPLEN
];
695 if (stream
&& LOCAL
) { /* only if stream already open */
696 fstat (LOCAL
->fd
,&sbuf
); /* get current file poop */
697 if (LOCAL
->filetime
&& !(LOCAL
->mustcheck
|| LOCAL
->shouldcheck
) &&
698 (LOCAL
->filetime
< sbuf
.st_mtime
)) LOCAL
->shouldcheck
= T
;
699 /* check for changed message status */
700 if (LOCAL
->mustcheck
|| LOCAL
->shouldcheck
) {
701 LOCAL
->filetime
= sbuf
.st_mtime
;
702 if (LOCAL
->shouldcheck
) /* babble when we do this unilaterally */
703 MM_NOTIFY (stream
,"[CHECK] Checking for flag updates",NIL
);
704 while (i
<= stream
->nmsgs
) tenex_elt (stream
,i
++);
705 LOCAL
->mustcheck
= LOCAL
->shouldcheck
= NIL
;
707 /* get shared parse/append permission */
708 if ((sbuf
.st_size
!= LOCAL
->filesize
) &&
709 ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_SH
)) >= 0)) {
710 /* parse resulting mailbox */
711 r
= (tenex_parse (stream
)) ? T
: NIL
;
712 unlockfd (ld
,lock
); /* release shared parse/append permission */
714 if (LOCAL
) { /* stream must still be alive */
715 /* snarf if this is a read-write inbox */
716 if (stream
->inbox
&& !stream
->rdonly
) {
717 tenex_snarf (stream
);
718 fstat (LOCAL
->fd
,&sbuf
);/* see if file changed now */
719 if ((sbuf
.st_size
!= LOCAL
->filesize
) &&
720 ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_SH
)) >= 0)) {
721 /* parse resulting mailbox */
722 r
= (tenex_parse (stream
)) ? T
: NIL
;
723 unlockfd (ld
,lock
); /* release shared parse/append permission */
728 return r
; /* return result of the parse */
732 /* Tenex mail check mailbox (reparses status too)
733 * Accepts: MAIL stream
736 void tenex_check (MAILSTREAM
*stream
)
738 /* mark that a check is desired */
739 if (LOCAL
) LOCAL
->mustcheck
= T
;
740 if (tenex_ping (stream
)) MM_LOG ("Check completed",(long) NIL
);
743 /* Tenex mail snarf messages from system inbox
744 * Accepts: MAIL stream
747 void tenex_snarf (MAILSTREAM
*stream
)
750 unsigned long j
,r
,hdrlen
,txtlen
;
752 char *hdr
,*txt
,lock
[MAILTMPLEN
],tmp
[MAILTMPLEN
];
754 MAILSTREAM
*sysibx
= NIL
;
756 /* give up if can't get exclusive permission */
757 if ((time (0) >= (LOCAL
->lastsnarf
+
758 (long) mail_parameters (NIL
,GET_SNARFINTERVAL
,NIL
))) &&
759 strcmp (sysinbox (),stream
->mailbox
) &&
760 ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) >= 0)) {
761 MM_CRITICAL (stream
); /* go critical */
762 /* sizes match and anything in sysinbox? */
763 if (!stat (sysinbox (),&sbuf
) && sbuf
.st_size
&&
764 !fstat (LOCAL
->fd
,&sbuf
) && (sbuf
.st_size
== LOCAL
->filesize
) &&
765 (sysibx
= mail_open (sysibx
,sysinbox (),OP_SILENT
)) &&
766 (!sysibx
->rdonly
) && (r
= sysibx
->nmsgs
)) {
767 /* yes, go to end of file in our mailbox */
768 lseek (LOCAL
->fd
,sbuf
.st_size
,L_SET
);
769 /* for each message in sysibx mailbox */
770 while (r
&& (++i
<= sysibx
->nmsgs
)) {
771 /* snarf message from system INBOX */
772 hdr
= cpystr (mail_fetchheader_full(sysibx
,i
,NIL
,&hdrlen
,FT_INTERNAL
));
773 txt
= mail_fetchtext_full (sysibx
,i
,&txtlen
,FT_INTERNAL
|FT_PEEK
);
774 /* if have a message */
775 if ((j
= hdrlen
+ txtlen
) != 0L) {
776 /* calculate header line */
777 mail_date (LOCAL
->buf
,elt
= mail_elt (sysibx
,i
));
778 sprintf (LOCAL
->buf
+ strlen (LOCAL
->buf
),
779 ",%lu;0000000000%02o\n",j
,(unsigned)
780 ((fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
781 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
782 (fDRAFT
* elt
->draft
)));
784 if ((write (LOCAL
->fd
,LOCAL
->buf
,strlen (LOCAL
->buf
)) < 0) ||
785 (write (LOCAL
->fd
,hdr
,hdrlen
) < 0) ||
786 (write (LOCAL
->fd
,txt
,txtlen
) < 0)) r
= 0;
788 fs_give ((void **) &hdr
);
791 /* make sure all the updates take */
792 if (fsync (LOCAL
->fd
)) r
= 0;
793 if (r
) { /* delete all the messages we copied */
794 if (r
== 1) strcpy (tmp
,"1");
795 else sprintf (tmp
,"1:%lu",r
);
796 mail_flag (sysibx
,tmp
,"\\Deleted",ST_SET
);
797 mail_expunge (sysibx
); /* now expunge all those messages */
800 sprintf (LOCAL
->buf
,"Can't copy new mail: %s",strerror (errno
));
801 MM_LOG (LOCAL
->buf
,WARN
);
802 ftruncate (LOCAL
->fd
,sbuf
.st_size
);
804 fstat (LOCAL
->fd
,&sbuf
); /* yes, get current file size */
805 LOCAL
->filetime
= sbuf
.st_mtime
;
807 if (sysibx
) mail_close (sysibx
);
808 MM_NOCRITICAL (stream
); /* release critical */
809 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
810 LOCAL
->lastsnarf
= time (0);/* note time of last snarf */
814 /* Tenex mail expunge mailbox
815 * Accepts: MAIL stream
816 * sequence to expunge if non-NIL
821 long tenex_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
829 unsigned long j
,k
,m
,recent
;
831 unsigned long delta
= 0;
832 char lock
[MAILTMPLEN
];
834 blocknotify_t bn
= (blocknotify_t
) mail_parameters (NIL
,GET_BLOCKNOTIFY
,NIL
);
835 if (!(ret
= (sequence
? ((options
& EX_UID
) ?
836 mail_uid_sequence (stream
,sequence
) :
837 mail_sequence (stream
,sequence
)) : LONGT
) &&
838 tenex_ping (stream
))); /* parse sequence if given, ping stream */
839 else if (stream
->rdonly
) MM_LOG ("Expunge ignored on readonly mailbox",WARN
);
841 if (LOCAL
->filetime
&& !LOCAL
->shouldcheck
) {
842 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
843 if (LOCAL
->filetime
< sbuf
.st_mtime
) LOCAL
->shouldcheck
= T
;
845 /* The cretins who designed flock() created a window of vulnerability in
846 * upgrading locks from shared to exclusive or downgrading from exclusive
847 * to shared. Rather than maintain the lock at shared status at a minimum,
848 * flock() actually *releases* the former lock. Obviously they never talked
849 * to any database guys. Fortunately, we have the parse/append permission
850 * lock. If we require this lock before going exclusive on the mailbox,
851 * another process can not sneak in and steal the exclusive mailbox lock on
852 * us, because it will block on trying to get parse/append permission first.
854 /* get exclusive parse/append permission */
855 if ((ld
= lockfd (LOCAL
->fd
,lock
,LOCK_EX
)) < 0)
856 MM_LOG ("Unable to lock expunge mailbox",ERROR
);
857 /* make sure see any newly-arrived messages */
858 else if (!tenex_parse (stream
));
859 /* get exclusive access */
860 else if (flock (LOCAL
->fd
,LOCK_EX
|LOCK_NB
)) {
861 (*bn
) (BLOCK_FILELOCK
,NIL
);
862 flock (LOCAL
->fd
,LOCK_SH
);/* recover previous lock */
863 (*bn
) (BLOCK_NONE
,NIL
);
864 MM_LOG ("Can't expunge because mailbox is in use by another process",
866 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
870 MM_CRITICAL (stream
); /* go critical */
871 recent
= stream
->recent
; /* get recent now that pinged and locked */
872 /* for each message */
873 while (i
<= stream
->nmsgs
) {
874 /* get cache element */
875 elt
= tenex_elt (stream
,i
);
876 /* number of bytes to smash or preserve */
877 k
= elt
->private.special
.text
.size
+ tenex_size (stream
,i
);
878 /* if need to expunge this message */
879 if (elt
->deleted
&& (sequence
? elt
->sequence
: T
)) {
880 /* if recent, note one less recent message */
881 if (elt
->recent
) --recent
;
882 delta
+= k
; /* number of bytes to delete */
883 /* notify upper levels */
884 mail_expunged (stream
,i
);
885 n
++; /* count up one more expunged message */
887 else if (i
++ && delta
) {/* preserved message */
888 /* first byte to preserve */
889 j
= elt
->private.special
.offset
;
890 do { /* read from source position */
891 m
= min (k
,LOCAL
->buflen
);
892 lseek (LOCAL
->fd
,j
,L_SET
);
893 read (LOCAL
->fd
,LOCAL
->buf
,m
);
894 pos
= j
- delta
; /* write to destination position */
895 lseek (LOCAL
->fd
,pos
,L_SET
);
897 lseek (LOCAL
->fd
,pos
,L_SET
);
898 if (write (LOCAL
->fd
,LOCAL
->buf
,m
) > 0) break;
899 MM_NOTIFY (stream
,strerror (errno
),WARN
);
900 MM_DISKERROR (stream
,errno
,T
);
902 pos
+= m
; /* new position */
903 j
+= m
; /* next chunk, perhaps */
904 } while (k
-= m
); /* until done */
905 /* note the new address of this text */
906 elt
->private.special
.offset
-= delta
;
908 /* preserved but no deleted messages */
909 else pos
= elt
->private.special
.offset
+ k
;
912 if (n
) { /* truncate file after last message */
913 if (pos
!= (LOCAL
->filesize
-= delta
)) {
915 "Calculated size mismatch %lu != %lu, delta = %lu",
916 (unsigned long) pos
,(unsigned long) LOCAL
->filesize
,delta
);
917 MM_LOG (LOCAL
->buf
,WARN
);
918 LOCAL
->filesize
= pos
;/* fix it then */
920 ftruncate (LOCAL
->fd
,LOCAL
->filesize
);
921 sprintf (LOCAL
->buf
,"Expunged %lu messages",n
);
922 /* output the news */
923 MM_LOG (LOCAL
->buf
,(long) NIL
);
925 else MM_LOG ("No messages deleted, so no update needed",(long) NIL
);
926 fsync (LOCAL
->fd
); /* force disk update */
927 fstat (LOCAL
->fd
,&sbuf
); /* get new write time */
928 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
929 tp
[0] = time (0); /* reset atime to now */
930 utime (stream
->mailbox
,tp
);
931 MM_NOCRITICAL (stream
); /* release critical */
932 /* notify upper level of new mailbox size */
933 mail_exists (stream
,stream
->nmsgs
);
934 mail_recent (stream
,recent
);
935 (*bn
) (BLOCK_FILELOCK
,NIL
);
936 flock (LOCAL
->fd
,LOCK_SH
);/* allow sharers again */
937 (*bn
) (BLOCK_NONE
,NIL
);
938 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
944 /* Tenex mail copy message(s)
945 * Accepts: MAIL stream
947 * destination mailbox
949 * Returns: T if success, NIL if failed
952 long tenex_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
960 char file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
962 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
963 /* make sure valid mailbox */
964 if (!tenex_isvalid (mailbox
,LOCAL
->buf
)) switch (errno
) {
965 case ENOENT
: /* no such file? */
966 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before copy",NIL
);
968 case 0: /* merely empty file? */
970 case EACCES
: /* file protected */
971 sprintf (LOCAL
->buf
,"Can't access destination: %.80s",mailbox
);
972 MM_LOG (LOCAL
->buf
,ERROR
);
975 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
976 sprintf (LOCAL
->buf
,"Invalid Tenex-format mailbox name: %.80s",mailbox
);
977 MM_LOG (LOCAL
->buf
,ERROR
);
980 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
981 sprintf (LOCAL
->buf
,"Not a Tenex-format mailbox: %.80s",mailbox
);
982 MM_LOG (LOCAL
->buf
,ERROR
);
985 if (!((options
& CP_UID
) ? mail_uid_sequence (stream
,sequence
) :
986 mail_sequence (stream
,sequence
))) return NIL
;
988 if ((fd
= open (tenex_file(file
,mailbox
),O_RDWR
,NIL
)) < 0) {
989 sprintf (LOCAL
->buf
,"Unable to open copy mailbox: %s",strerror (errno
));
990 MM_LOG (LOCAL
->buf
,ERROR
);
993 MM_CRITICAL (stream
); /* go critical */
994 /* get exclusive parse/append permission */
995 if (flock (fd
,LOCK_SH
) || ((ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0)) {
996 MM_LOG ("Unable to lock copy mailbox",ERROR
);
997 MM_NOCRITICAL (stream
);
1000 fstat (fd
,&sbuf
); /* get current file size */
1001 lseek (fd
,sbuf
.st_size
,L_SET
);/* move to end of file */
1003 /* for each requested message */
1004 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); i
++)
1005 if ((elt
= mail_elt (stream
,i
))->sequence
) {
1006 lseek (LOCAL
->fd
,elt
->private.special
.offset
,L_SET
);
1007 /* number of bytes to copy */
1008 k
= elt
->private.special
.text
.size
+ tenex_size (stream
,i
);
1009 do { /* read from source position */
1010 j
= min (k
,LOCAL
->buflen
);
1011 read (LOCAL
->fd
,LOCAL
->buf
,j
);
1012 if (write (fd
,LOCAL
->buf
,j
) < 0) ret
= NIL
;
1013 } while (ret
&& (k
-= j
));/* until done */
1015 /* make sure all the updates take */
1016 if (!(ret
&& (ret
= !fsync (fd
)))) {
1017 sprintf (LOCAL
->buf
,"Unable to write message: %s",strerror (errno
));
1018 MM_LOG (LOCAL
->buf
,ERROR
);
1019 ftruncate (fd
,sbuf
.st_size
);
1021 if (ret
) tp
[0] = time (0) - 1;/* set atime to now-1 if successful copy */
1022 /* else preserve \Marked status */
1023 else tp
[0] = (sbuf
.st_ctime
> sbuf
.st_atime
) ? sbuf
.st_atime
: time(0);
1024 tp
[1] = sbuf
.st_mtime
; /* preserve mtime */
1025 utime (file
,tp
); /* set the times */
1026 close (fd
); /* close the file */
1027 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
1028 MM_NOCRITICAL (stream
); /* release critical */
1029 /* delete all requested messages */
1030 if (ret
&& (options
& CP_MOVE
)) {
1031 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1032 if ((elt
= tenex_elt (stream
,i
))->sequence
) {
1033 elt
->deleted
= T
; /* mark message deleted */
1034 /* recalculate status */
1035 tenex_update_status (stream
,i
,NIL
);
1037 if (!stream
->rdonly
) { /* make sure the update takes */
1039 fstat (LOCAL
->fd
,&sbuf
); /* get current write time */
1040 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
1041 tp
[0] = time (0); /* make sure atime remains greater */
1042 utime (stream
->mailbox
,tp
);
1045 if (ret
&& mail_parameters (NIL
,GET_COPYUID
,NIL
))
1046 MM_LOG ("Can not return meaningful COPYUID with this mailbox format",WARN
);
1050 /* Tenex mail append message from stringstruct
1051 * Accepts: MAIL stream
1052 * destination mailbox
1055 * Returns: T if append successful, else NIL
1058 long tenex_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
1062 char *flags
,*date
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
],lock
[MAILTMPLEN
];
1067 unsigned long i
,j
,uf
,size
;
1070 /* default stream to prototype */
1071 if (!stream
) stream
= user_flags (&tenexproto
);
1072 /* make sure valid mailbox */
1073 if (!tenex_isvalid (mailbox
,tmp
)) switch (errno
) {
1074 case ENOENT
: /* no such file? */
1075 if (!compare_cstring (mailbox
,"INBOX")) dummy_create (NIL
,"mail.txt");
1077 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before append",NIL
);
1081 case 0: /* merely empty file? */
1083 case EACCES
: /* file protected */
1084 sprintf (tmp
,"Can't access destination: %.80s",mailbox
);
1088 sprintf (tmp
,"Invalid TENEX-format mailbox name: %.80s",mailbox
);
1092 sprintf (tmp
,"Not a TENEX-format mailbox: %.80s",mailbox
);
1096 /* get first message */
1097 if (!MM_APPEND (af
) (stream
,data
,&flags
,&date
,&message
)) return NIL
;
1099 /* open destination mailbox */
1100 if (((fd
= open (tenex_file (file
,mailbox
),O_WRONLY
|O_APPEND
,NIL
)) < 0) ||
1101 !(df
= fdopen (fd
,"ab"))) {
1102 sprintf (tmp
,"Can't open append mailbox: %s",strerror (errno
));
1106 /* get parse/append permission */
1107 if (flock (fd
,LOCK_SH
) || ((ld
= lockfd (fd
,lock
,LOCK_EX
)) < 0)) {
1108 MM_LOG ("Unable to lock append mailbox",ERROR
);
1112 MM_CRITICAL (stream
); /* go critical */
1113 fstat (fd
,&sbuf
); /* get current file size */
1115 do { /* parse flags */
1116 if (!SIZE (message
)) { /* guard against zero-length */
1117 MM_LOG ("Append of zero-length message",ERROR
);
1121 f
= mail_parse_flags (stream
,flags
,&i
);
1122 /* reverse bits (dontcha wish we had CIRC?) */
1123 for (uf
= 0; i
; uf
|= 1 << (29 - find_rightmost_bit (&i
)));
1124 if (date
) { /* parse date if given */
1125 if (!mail_parse_date (&elt
,date
)) {
1126 sprintf (tmp
,"Bad date in append: %.80s",date
);
1128 ret
= NIL
; /* mark failure */
1131 mail_date (tmp
,&elt
); /* write preseved date */
1133 else internal_date (tmp
); /* get current date in IMAP format */
1134 i
= GETPOS (message
); /* remember current position */
1135 for (j
= SIZE (message
), size
= 0; j
; --j
)
1136 if (SNX (message
) != '\015') ++size
;
1137 SETPOS (message
,i
); /* restore position */
1139 if (fprintf (df
,"%s,%lu;%010lo%02lo\n",tmp
,size
,uf
,(unsigned long) f
) < 0)
1141 else { /* write message */
1142 while (size
) if ((c
= 0xff & SNX (message
)) != '\015') {
1143 if (putc (c
,df
) != EOF
) --size
;
1146 /* get next message */
1147 if (size
|| !MM_APPEND (af
) (stream
,data
,&flags
,&date
,&message
))
1150 } while (ret
&& message
);
1152 if (!ret
|| (fflush (df
) == EOF
)) {
1153 ftruncate (fd
,sbuf
.st_size
);/* revert file */
1154 close (fd
); /* make sure fclose() doesn't corrupt us */
1156 sprintf (tmp
,"Message append failed: %s",strerror (errno
));
1161 if (ret
) tp
[0] = time (0) - 1;/* set atime to now-1 if successful copy */
1162 /* else preserve \Marked status */
1163 else tp
[0] = (sbuf
.st_ctime
> sbuf
.st_atime
) ? sbuf
.st_atime
: time(0);
1164 tp
[1] = sbuf
.st_mtime
; /* preserve mtime */
1165 utime (file
,tp
); /* set the times */
1166 fclose (df
); /* close the file */
1167 unlockfd (ld
,lock
); /* release exclusive parse/append permission */
1168 MM_NOCRITICAL (stream
); /* release critical */
1169 if (ret
&& mail_parameters (NIL
,GET_APPENDUID
,NIL
))
1170 MM_LOG ("Can not return meaningful APPENDUID with this mailbox format",
1175 /* Internal routines */
1178 /* Tenex mail return internal message size in bytes
1179 * Accepts: MAIL stream
1181 * Returns: internal size of message
1184 unsigned long tenex_size (MAILSTREAM
*stream
,unsigned long m
)
1186 MESSAGECACHE
*elt
= mail_elt (stream
,m
);
1187 return ((m
< stream
->nmsgs
) ? mail_elt (stream
,m
+1)->private.special
.offset
:
1189 (elt
->private.special
.offset
+ elt
->private.special
.text
.size
);
1193 /* Tenex mail generate file string
1194 * Accepts: temporary buffer to write into
1195 * mailbox name string
1196 * Returns: local file string or NIL if failure
1199 char *tenex_file (char *dst
,char *name
)
1201 char tmp
[MAILTMPLEN
];
1202 char *s
= mailboxfile (dst
,name
);
1203 /* return our standard inbox */
1204 return (s
&& !*s
) ? mailboxfile (dst
,tenex_isvalid ("~/INBOX",tmp
) ?
1205 "~/INBOX" : "mail.txt") : s
;
1208 /* Tenex mail parse mailbox
1209 * Accepts: MAIL stream
1210 * Returns: T if parse OK
1211 * NIL if failure, stream aborted
1214 long tenex_parse (MAILSTREAM
*stream
)
1217 MESSAGECACHE
*elt
= NIL
;
1218 unsigned char c
,*s
,*t
,*x
;
1219 char tmp
[MAILTMPLEN
];
1221 long curpos
= LOCAL
->filesize
;
1222 long nmsgs
= stream
->nmsgs
;
1223 long recent
= stream
->recent
;
1225 short silent
= stream
->silent
;
1226 fstat (LOCAL
->fd
,&sbuf
); /* get status */
1227 if (sbuf
.st_size
< curpos
) { /* sanity check */
1228 sprintf (tmp
,"Mailbox shrank from %lu to %lu!",
1229 (unsigned long) curpos
,(unsigned long) sbuf
.st_size
);
1231 tenex_close (stream
,NIL
);
1234 stream
->silent
= T
; /* don't pass up exists events yet */
1235 while (sbuf
.st_size
- curpos
){/* while there is stuff to parse */
1236 /* get to that position in the file */
1237 lseek (LOCAL
->fd
,curpos
,L_SET
);
1238 if ((i
= read (LOCAL
->fd
,LOCAL
->buf
,64)) <= 0) {
1239 sprintf (tmp
,"Unable to read internal header at %lu, size = %lu: %s",
1240 (unsigned long) curpos
,(unsigned long) sbuf
.st_size
,
1241 i
? strerror (errno
) : "no data read");
1243 tenex_close (stream
,NIL
);
1246 LOCAL
->buf
[i
] = '\0'; /* tie off buffer just in case */
1247 if (!(s
= strchr (LOCAL
->buf
,'\012'))) {
1248 sprintf (tmp
,"Unable to find newline at %lu in %lu bytes, text: %s",
1249 (unsigned long) curpos
,i
,(char *) LOCAL
->buf
);
1251 tenex_close (stream
,NIL
);
1254 *s
= '\0'; /* tie off header line */
1255 i
= (s
+ 1) - LOCAL
->buf
; /* note start of text offset */
1256 if (!((s
= strchr (LOCAL
->buf
,',')) && (t
= strchr (s
+1,';')))) {
1257 sprintf (tmp
,"Unable to parse internal header at %lu: %s",
1258 (unsigned long) curpos
,(char *) LOCAL
->buf
);
1260 tenex_close (stream
,NIL
);
1263 *s
++ = '\0'; *t
++ = '\0'; /* tie off fields */
1265 added
= T
; /* note that a new message was added */
1266 /* swell the cache */
1267 mail_exists (stream
,++nmsgs
);
1268 /* instantiate an elt for this message */
1269 (elt
= mail_elt (stream
,nmsgs
))->valid
= T
;
1270 elt
->private.uid
= ++stream
->uid_last
;
1271 /* note file offset of header */
1272 elt
->private.special
.offset
= curpos
;
1274 elt
->private.special
.text
.size
= 0;
1275 /* header size not known yet */
1276 elt
->private.msg
.header
.text
.size
= 0;
1277 x
= s
; /* parse the header components */
1278 if (mail_parse_date (elt
,LOCAL
->buf
) &&
1279 (elt
->private.msg
.full
.text
.size
= strtoul (s
,(char **) &s
,10)) &&
1280 (!(s
&& *s
)) && isdigit (t
[0]) && isdigit (t
[1]) && isdigit (t
[2]) &&
1281 isdigit (t
[3]) && isdigit (t
[4]) && isdigit (t
[5]) &&
1282 isdigit (t
[6]) && isdigit (t
[7]) && isdigit (t
[8]) &&
1283 isdigit (t
[9]) && isdigit (t
[10]) && isdigit (t
[11]) && !t
[12])
1284 elt
->private.special
.text
.size
= i
;
1286 sprintf (tmp
,"Unable to parse internal header elements at %ld: %s,%s;%s",
1287 curpos
,(char *) LOCAL
->buf
,(char *) x
,(char *) t
);
1289 tenex_close (stream
,NIL
);
1292 /* make sure didn't run off end of file */
1293 if ((curpos
+= (elt
->private.msg
.full
.text
.size
+ i
)) > sbuf
.st_size
) {
1294 sprintf (tmp
,"Last message (at %lu) runs past end of file (%lu > %lu)",
1295 elt
->private.special
.offset
,(unsigned long) curpos
,
1296 (unsigned long) sbuf
.st_size
);
1298 tenex_close (stream
,NIL
);
1301 c
= t
[10]; /* remember first system flags byte */
1302 t
[10] = '\0'; /* tie off flags */
1303 j
= strtoul (t
,NIL
,8); /* get user flags value */
1304 t
[10] = c
; /* restore first system flags byte */
1305 /* set up all valid user flags (reversed!) */
1306 while (j
) if (((i
= 29 - find_rightmost_bit (&j
)) < NUSERFLAGS
) &&
1307 stream
->user_flags
[i
]) elt
->user_flags
|= 1 << i
;
1308 /* calculate system flags */
1309 if ((j
= ((t
[10]-'0') * 8) + t
[11]-'0') & fSEEN
) elt
->seen
= T
;
1310 if (j
& fDELETED
) elt
->deleted
= T
;
1311 if (j
& fFLAGGED
) elt
->flagged
= T
;
1312 if (j
& fANSWERED
) elt
->answered
= T
;
1313 if (j
& fDRAFT
) elt
->draft
= T
;
1314 if (!(j
& fOLD
)) { /* newly arrived message? */
1316 recent
++; /* count up a new recent message */
1317 /* mark it as old */
1318 tenex_update_status (stream
,nmsgs
,NIL
);
1321 fsync (LOCAL
->fd
); /* make sure all the fOLD flags take */
1322 /* update parsed file size and time */
1323 LOCAL
->filesize
= sbuf
.st_size
;
1324 fstat (LOCAL
->fd
,&sbuf
); /* get status again to ensure time is right */
1325 LOCAL
->filetime
= sbuf
.st_mtime
;
1326 if (added
&& !stream
->rdonly
){/* make sure atime updated */
1329 tp
[1] = LOCAL
->filetime
;
1330 utime (stream
->mailbox
,tp
);
1332 stream
->silent
= silent
; /* can pass up events now */
1333 mail_exists (stream
,nmsgs
); /* notify upper level of new mailbox size */
1334 mail_recent (stream
,recent
); /* and of change in recent messages */
1335 return LONGT
; /* return the winnage */
1338 /* Tenex get cache element with status updating from file
1339 * Accepts: MAIL stream
1341 * Returns: cache element
1344 MESSAGECACHE
*tenex_elt (MAILSTREAM
*stream
,unsigned long msgno
)
1346 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1347 struct { /* old flags */
1348 unsigned int seen
: 1;
1349 unsigned int deleted
: 1;
1350 unsigned int flagged
: 1;
1351 unsigned int answered
: 1;
1352 unsigned int draft
: 1;
1353 unsigned long user_flags
;
1355 old
.seen
= elt
->seen
; old
.deleted
= elt
->deleted
; old
.flagged
= elt
->flagged
;
1356 old
.answered
= elt
->answered
; old
.draft
= elt
->draft
;
1357 old
.user_flags
= elt
->user_flags
;
1358 tenex_read_flags (stream
,elt
);
1359 if ((old
.seen
!= elt
->seen
) || (old
.deleted
!= elt
->deleted
) ||
1360 (old
.flagged
!= elt
->flagged
) || (old
.answered
!= elt
->answered
) ||
1361 (old
.draft
!= elt
->draft
) || (old
.user_flags
!= elt
->user_flags
))
1362 MM_FLAGS (stream
,msgno
); /* let top level know */
1366 /* Tenex read flags from file
1367 * Accepts: MAIL stream
1368 * Returns: cache element
1371 void tenex_read_flags (MAILSTREAM
*stream
,MESSAGECACHE
*elt
)
1374 /* noop if readonly and have valid flags */
1375 if (stream
->rdonly
&& elt
->valid
) return;
1376 /* set the seek pointer */
1377 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1378 elt
->private.special
.text
.size
- 13,L_SET
);
1379 /* read the new flags */
1380 if (read (LOCAL
->fd
,LOCAL
->buf
,12) < 0) {
1381 sprintf (LOCAL
->buf
,"Unable to read new status: %s",strerror (errno
));
1384 /* calculate system flags */
1385 i
= (((LOCAL
->buf
[10]-'0') * 8) + LOCAL
->buf
[11]-'0');
1386 elt
->seen
= i
& fSEEN
? T
: NIL
; elt
->deleted
= i
& fDELETED
? T
: NIL
;
1387 elt
->flagged
= i
& fFLAGGED
? T
: NIL
;
1388 elt
->answered
= i
& fANSWERED
? T
: NIL
; elt
->draft
= i
& fDRAFT
? T
: NIL
;
1389 LOCAL
->buf
[10] = '\0'; /* tie off flags */
1390 j
= strtoul(LOCAL
->buf
,NIL
,8);/* get user flags value */
1391 /* set up all valid user flags (reversed!) */
1392 while (j
) if (((i
= 29 - find_rightmost_bit (&j
)) < NUSERFLAGS
) &&
1393 stream
->user_flags
[i
]) elt
->user_flags
|= 1 << i
;
1394 elt
->valid
= T
; /* have valid flags now */
1397 /* Tenex update status string
1398 * Accepts: MAIL stream
1400 * flag saying whether or not to sync
1403 void tenex_update_status (MAILSTREAM
*stream
,unsigned long msgno
,long syncflag
)
1407 MESSAGECACHE
*elt
= mail_elt (stream
,msgno
);
1408 unsigned long j
,k
= 0;
1410 if (stream
->rdonly
|| !elt
->valid
) tenex_read_flags (stream
,elt
);
1411 else { /* readwrite */
1412 j
= elt
->user_flags
; /* get user flags */
1413 /* reverse bits (dontcha wish we had CIRC?) */
1414 while (j
) k
|= 1 << (29 - find_rightmost_bit (&j
));
1415 /* print new flag string */
1416 sprintf (LOCAL
->buf
,"%010lo%02o",k
,(unsigned)
1417 (fOLD
+ (fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
1418 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
1419 (fDRAFT
* elt
->draft
)));
1420 /* get to that place in the file */
1421 lseek (LOCAL
->fd
,(off_t
) elt
->private.special
.offset
+
1422 elt
->private.special
.text
.size
- 13,L_SET
);
1423 /* write new flags */
1424 write (LOCAL
->fd
,LOCAL
->buf
,12);
1425 if (syncflag
) { /* sync if requested */
1427 fstat (LOCAL
->fd
,&sbuf
); /* get new write time */
1428 tp
[1] = LOCAL
->filetime
= sbuf
.st_mtime
;
1429 tp
[0] = time (0); /* make sure read is later */
1430 utime (stream
->mailbox
,tp
);
1435 /* Tenex locate header for a message
1436 * Accepts: MAIL stream
1438 * pointer to returned header size
1439 * Returns: position of header in file
1442 unsigned long tenex_hdrpos (MAILSTREAM
*stream
,unsigned long msgno
,
1443 unsigned long *size
)
1449 MESSAGECACHE
*elt
= tenex_elt (stream
,msgno
);
1450 unsigned long ret
= elt
->private.special
.offset
+
1451 elt
->private.special
.text
.size
;
1452 unsigned long msiz
= tenex_size (stream
,msgno
);
1453 /* is header size known? */
1454 if (!(*size
= elt
->private.msg
.header
.text
.size
)) {
1455 lseek (LOCAL
->fd
,ret
,L_SET
);/* get to header position */
1456 /* search message for LF LF */
1457 for (siz
= 0; siz
< msiz
; siz
++) {
1458 if (--i
<= 0) /* read another buffer as necessary */
1459 read (LOCAL
->fd
,s
= LOCAL
->buf
,i
= min (msiz
-siz
,(long) MAILTMPLEN
));
1460 /* two newline sequence? */
1461 if ((c
== '\012') && (*s
== '\012')) {
1462 /* yes, note for later */
1463 elt
->private.msg
.header
.text
.size
= (*size
= siz
+ 1);
1465 return ret
; /* return to caller */
1467 else c
= *s
++; /* next character */
1469 /* header consumes entire message */
1470 elt
->private.msg
.header
.text
.size
= *size
= msiz
;