1 /* ========================================================================
2 * Copyright 2008-2012 Mark Crispin
3 * ========================================================================
7 * Program: MIX mail routines
9 * Author(s): Mark Crispin
12 * Last Edited: 15 February 2012
14 * Previous versions of this file were
16 * Copyright 1988-2008 University of Washington
18 * Licensed under the Apache License, Version 2.0 (the "License");
19 * you may not use this file except in compliance with the License.
20 * You may obtain a copy of the License at
22 * http://www.apache.org/licenses/LICENSE-2.0
30 extern int errno
; /* just in case */
43 #define MEGABYTE (1024*1024)
45 #define MIXDATAROLL MEGABYTE /* size at which we roll to a new file */
50 #define MIXNAME ".mix" /* prefix for all MIX file names */
51 #define MIXMETA "meta" /* suffix for metadata */
52 #define MIXINDEX "index" /* suffix for index */
53 #define MIXSTATUS "status" /* suffix for status */
54 #define MIXSORTCACHE "sortcache"/* suffix for sortcache */
55 #define METAMAX (MEGABYTE-1) /* maximum metadata file size (sanity check) */
58 /* MIX file formats */
60 /* sequence format (all but msg files) */
61 #define SEQFMT "S%08lx\015\012"
62 /* metadata file format */
63 #define MTAFMT "V%08lx\015\012L%08lx\015\012N%08lx\015\012"
64 /* index file record format */
65 #define IXRFMT ":%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:%08lx:%08lx:%08lx:%08lx:\015\012"
66 /* status file record format */
67 #define STRFMT ":%08lx:%08lx:%04x:%08lx:\015\012"
68 /* message file header format */
69 #define MSRFMT "%s%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:\015\012"
70 #define MSGTOK ":msg:"
71 #define MSGTSZ (sizeof(MSGTOK)-1)
72 /* sortcache file record format */
73 #define SCRFMT ":%08lx:%08lx:%08lx:%08lx:%08lx:%c%08lx:%08lx:%08lx:\015\012"
75 /* MIX I/O stream local data */
77 typedef struct mix_local
{
78 unsigned long curmsg
; /* current message file number */
79 unsigned long newmsg
; /* current new message file number */
80 time_t lastsnarf
; /* last snarf time */
81 int msgfd
; /* file description of current msg file */
82 int mfd
; /* file descriptor of open metadata */
83 unsigned long metaseq
; /* metadata sequence */
84 char *index
; /* mailbox index name */
85 unsigned long indexseq
; /* index sequence */
86 char *status
; /* mailbox status name */
87 unsigned long statusseq
; /* status sequence */
88 char *sortcache
; /* mailbox sortcache name */
89 unsigned long sortcacheseq
; /* sortcache sequence */
90 unsigned char *buf
; /* temporary buffer */
91 unsigned long buflen
; /* current size of temporary buffer */
92 unsigned int expok
: 1; /* non-zero if expunge reports OK */
93 unsigned int internal
: 1; /* internally opened, do not validate */
97 #define MIXBURP struct mix_burp
100 unsigned long fileno
; /* message file number */
101 char *name
; /* message file name */
102 SEARCHSET
*tail
; /* tail of ranges */
103 SEARCHSET set
; /* set of retained ranges */
104 MIXBURP
*next
; /* next file to burp */
108 /* Convenient access to local data */
110 #define LOCAL ((MIXLOCAL *) stream->local)
112 /* Function prototypes */
114 DRIVER
*mix_valid (char *name
);
115 long mix_isvalid (char *name
,char *meta
);
116 void *mix_parameters (long function
,void *value
);
117 long mix_dirfmttest (char *name
);
118 void mix_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
119 long mix_scan_contents (char *name
,char *contents
,unsigned long csiz
,
121 void mix_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
122 void mix_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
123 long mix_subscribe (MAILSTREAM
*stream
,char *mailbox
);
124 long mix_unsubscribe (MAILSTREAM
*stream
,char *mailbox
);
125 long mix_create (MAILSTREAM
*stream
,char *mailbox
);
126 long mix_delete (MAILSTREAM
*stream
,char *mailbox
);
127 long mix_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
128 int mix_rselect (const struct direct
*name
);
129 MAILSTREAM
*mix_open (MAILSTREAM
*stream
);
130 void mix_close (MAILSTREAM
*stream
,long options
);
131 void mix_abort (MAILSTREAM
*stream
);
132 char *mix_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
134 long mix_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
);
135 void mix_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
);
136 unsigned long *mix_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
137 SORTPGM
*pgm
,long flags
);
138 THREADNODE
*mix_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
139 SEARCHPGM
*spg
,long flags
);
140 long mix_ping (MAILSTREAM
*stream
);
141 void mix_check (MAILSTREAM
*stream
);
142 long mix_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
143 int mix_select (const struct direct
*name
);
144 int mix_msgfsort (const struct direct
**d1
,const struct direct
**d2
);
145 long mix_addset (SEARCHSET
**set
,unsigned long start
,unsigned long size
);
146 long mix_burp (MAILSTREAM
*stream
,MIXBURP
*burp
,unsigned long *reclaimed
);
147 long mix_burp_check (SEARCHSET
*set
,size_t size
,char *file
);
148 long mix_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,
150 long mix_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
151 long mix_append_msg (MAILSTREAM
*stream
,FILE *f
,char *flags
,MESSAGECACHE
*delt
,
152 STRING
*msg
,SEARCHSET
*set
,unsigned long seq
);
154 FILE *mix_parse (MAILSTREAM
*stream
,FILE **idxf
,long iflags
,long sflags
);
155 char *mix_meta_slurp (MAILSTREAM
*stream
,unsigned long *seq
);
156 long mix_meta_update (MAILSTREAM
*stream
);
157 long mix_index_update (MAILSTREAM
*stream
,FILE *idxf
,long flag
);
158 long mix_status_update (MAILSTREAM
*stream
,FILE *statf
,long flag
);
159 FILE *mix_data_open (MAILSTREAM
*stream
,int *fd
,long *size
,
160 unsigned long newsize
);
161 FILE *mix_sortcache_open (MAILSTREAM
*stream
);
162 long mix_sortcache_update (MAILSTREAM
*stream
,FILE **sortcache
);
163 char *mix_read_record (FILE *f
,char *buf
,unsigned long buflen
,char *type
);
164 unsigned long mix_read_sequence (FILE *f
);
165 char *mix_dir (char *dst
,char *name
);
166 char *mix_file (char *dst
,char *dir
,char *name
);
167 char *mix_file_data (char *dst
,char *dir
,unsigned long data
);
168 unsigned long mix_modseq (unsigned long oldseq
);
170 /* MIX mail routines */
173 /* Driver dispatch used by MAIL */
176 "mix", /* driver name */
178 DR_MAIL
|DR_LOCAL
|DR_NOFAST
|DR_CRLF
|DR_LOCKING
|DR_DIRFMT
|DR_MODSEQ
,
179 (DRIVER
*) NIL
, /* next driver */
180 mix_valid
, /* mailbox is valid for us */
181 mix_parameters
, /* manipulate parameters */
182 mix_scan
, /* scan mailboxes */
183 mix_list
, /* find mailboxes */
184 mix_lsub
, /* find subscribed mailboxes */
185 mix_subscribe
, /* subscribe to mailbox */
186 mix_unsubscribe
, /* unsubscribe from mailbox */
187 mix_create
, /* create mailbox */
188 mix_delete
, /* delete mailbox */
189 mix_rename
, /* rename mailbox */
190 mail_status_default
, /* status of mailbox */
191 mix_open
, /* open mailbox */
192 mix_close
, /* close mailbox */
193 NIL
, /* fetch message "fast" attributes */
194 NIL
, /* fetch message flags */
195 NIL
, /* fetch overview */
196 NIL
, /* fetch message envelopes */
197 mix_header
, /* fetch message header only */
198 mix_text
, /* fetch message body only */
199 NIL
, /* fetch partial message test */
200 NIL
, /* unique identifier */
201 NIL
, /* message number */
202 mix_flag
, /* modify flags */
203 NIL
, /* per-message modify flags */
204 NIL
, /* search for message based on criteria */
205 mix_sort
, /* sort messages */
206 mix_thread
, /* thread messages */
207 mix_ping
, /* ping mailbox to see if still alive */
208 mix_check
, /* check for new messages */
209 mix_expunge
, /* expunge deleted messages */
210 mix_copy
, /* copy messages to another mailbox */
211 mix_append
, /* append string message to mailbox */
212 NIL
, /* garbage collect stream */
213 NIL
/* renew stream */
216 /* prototype stream */
217 MAILSTREAM mixproto
= {&mixdriver
};
219 /* MIX mail validate mailbox
220 * Accepts: mailbox name
221 * Returns: our driver if name is valid, NIL otherwise
224 DRIVER
*mix_valid (char *name
)
226 char tmp
[MAILTMPLEN
];
227 return mix_isvalid (name
,tmp
) ? &mixdriver
: NIL
;
231 /* MIX mail test for valid mailbox
232 * Accepts: mailbox name
233 * buffer to return meta name
234 * Returns: T if valid, NIL otherwise, metadata name written in both cases
237 long mix_isvalid (char *name
,char *meta
)
239 char dir
[MAILTMPLEN
];
241 /* validate name as directory */
242 if (!(errno
= ((strlen (name
) > NETMAXMBX
) ? ENAMETOOLONG
: NIL
)) &&
243 *mix_dir (dir
,name
) && mix_file (meta
,dir
,MIXMETA
) &&
244 !stat (dir
,&sbuf
) && ((sbuf
.st_mode
& S_IFMT
) == S_IFDIR
)) {
245 /* name is directory; is it mix? */
246 if (!stat (meta
,&sbuf
) && ((sbuf
.st_mode
& S_IFMT
) == S_IFREG
))
248 else errno
= NIL
; /* directory but not mix */
253 /* MIX manipulate driver parameters
254 * Accepts: function code
255 * function-dependent value
256 * Returns: function-dependent return value
259 void *mix_parameters (long function
,void *value
)
262 switch ((int) function
) {
264 if (value
) ret
= mailboxfile ((char *) value
,"~/INBOX");
267 ret
= (void *) mix_dirfmttest
;
269 case GET_SCANCONTENTS
:
270 ret
= (void *) mix_scan_contents
;
272 case SET_ONETIMEEXPUNGEATPING
:
273 if (value
) ((MIXLOCAL
*) ((MAILSTREAM
*) value
)->local
)->expok
= T
;
274 case GET_ONETIMEEXPUNGEATPING
:
275 if (value
) ret
= (void *)
276 (((MIXLOCAL
*) ((MAILSTREAM
*) value
)->local
)->expok
? VOIDT
: NIL
);
283 /* MIX test for directory format internal node
284 * Accepts: candidate node name
285 * Returns: T if internal name, NIL otherwise
288 long mix_dirfmttest (char *name
)
290 /* belongs to MIX if starts with .mix */
291 return strncmp (name
,MIXNAME
,sizeof (MIXNAME
) - 1) ? NIL
: LONGT
;
294 /* MIX mail scan mailboxes
295 * Accepts: mail stream
301 void mix_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
303 if (stream
) dummy_scan (NIL
,ref
,pat
,contents
);
307 /* MIX scan mailbox for contents
308 * Accepts: mailbox name
311 * file size (ignored)
312 * Returns: NIL if contents not found, T if found
315 long mix_scan_contents (char *name
,char *contents
,unsigned long csiz
,
322 size_t namelen
= strlen (name
);
324 struct direct
**names
= NIL
;
325 if ((nfiles
= scandir (name
,&names
,mix_select
,mix_msgfsort
)) > 0)
326 for (i
= 0; i
< nfiles
; ++i
) {
328 sprintf (s
= (char *) fs_get (namelen
+ strlen (names
[i
]->d_name
) + 2),
329 "%s/%s",name
,names
[i
]->d_name
);
330 if (!stat (s
,&sbuf
) && (csiz
<= sbuf
.st_size
))
331 ret
= dummy_scan_contents (s
,contents
,csiz
,sbuf
.st_size
);
332 fs_give ((void **) &s
);
334 fs_give ((void **) &names
[i
]);
336 /* free directory list */
337 if ((a
= (void *) names
) != NULL
) fs_give ((void **) &a
);
341 /* MIX list mailboxes
342 * Accepts: mail stream
347 void mix_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
349 if (stream
) dummy_list (NIL
,ref
,pat
);
353 /* MIX list subscribed mailboxes
354 * Accepts: mail stream
359 void mix_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
361 if (stream
) dummy_lsub (NIL
,ref
,pat
);
364 /* MIX mail subscribe to mailbox
365 * Accepts: mail stream
366 * mailbox to add to subscription list
367 * Returns: T on success, NIL on failure
370 long mix_subscribe (MAILSTREAM
*stream
,char *mailbox
)
372 return sm_subscribe (mailbox
);
376 /* MIX mail unsubscribe to mailbox
377 * Accepts: mail stream
378 * mailbox to delete from subscription list
379 * Returns: T on success, NIL on failure
382 long mix_unsubscribe (MAILSTREAM
*stream
,char *mailbox
)
384 return sm_unsubscribe (mailbox
);
387 /* MIX mail create mailbox
388 * Accepts: mail stream
389 * mailbox name to create
390 * Returns: T on success, NIL on failure
393 long mix_create (MAILSTREAM
*stream
,char *mailbox
)
398 char *t
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
];
399 char *s
= strrchr (mailbox
,'/');
400 unsigned long now
= time (NIL
);
402 /* always create \NoSelect if trailing / */
403 if (s
&& !s
[1]) return dummy_create (stream
,mailbox
);
405 if (mix_dirfmttest (s
? s
+ 1 : mailbox
))
406 sprintf(tmp
,"Can't create mailbox %.80s: invalid MIX-format name",mailbox
);
407 /* must not already exist */
408 else if ((test
= mail_valid (NIL
,mailbox
,NIL
)) &&
409 strcmp (test
->name
,"dummy"))
410 sprintf (tmp
,"Can't create mailbox %.80s: mailbox already exists",mailbox
);
411 /* create directory and metadata */
412 else if (!dummy_create_path (stream
,
413 mix_file (file
,mix_dir (tmp
,mailbox
),MIXMETA
),
414 get_dir_protection (mailbox
)))
415 sprintf (tmp
,"Can't create mailbox %.80s: %.80s",mailbox
,strerror (errno
));
416 else if (!(f
= fopen (file
,"w")))
417 sprintf (tmp
,"Can't re-open metadata %.80s: %.80s",mailbox
,
419 else { /* success, write initial metadata */
420 fprintf (f
,SEQFMT
,now
);
421 fprintf (f
,MTAFMT
,now
,(unsigned long) 0,now
);
422 for (i
= 0, c
= 'K'; (i
< NUSERFLAGS
) &&
423 (t
= (stream
&& stream
->user_flags
[i
]) ? stream
->user_flags
[i
] :
424 default_user_flag (i
)) && *t
; ++i
) {
425 putc (c
,f
); /* write another keyword */
427 c
= ' '; /* delimiter is now space */
430 set_mbx_protections (mailbox
,file
);
431 /* point to suffix */
432 s
= file
+ strlen (file
) - (sizeof (MIXMETA
) - 1);
433 strcpy (s
,MIXINDEX
); /* create index */
434 if (!dummy_create_path (stream
,file
,get_dir_protection (mailbox
)))
435 sprintf (tmp
,"Can't create mix mailbox index: %.80s",strerror (errno
));
437 set_mbx_protections (mailbox
,file
);
438 strcpy (s
,MIXSTATUS
); /* create status */
439 if (!dummy_create_path (stream
,file
,get_dir_protection (mailbox
)))
440 sprintf (tmp
,"Can't create mix mailbox status: %.80s",
443 set_mbx_protections (mailbox
,file
);
444 sprintf (s
,"%08lx",now
);/* message file */
445 if (!dummy_create_path (stream
,file
,get_dir_protection (mailbox
)))
446 sprintf (tmp
,"Can't create mix mailbox data: %.80s",
449 set_mbx_protections (mailbox
,file
);
450 ret
= LONGT
; /* declare success at this point */
455 if (!ret
) MM_LOG (tmp
,ERROR
); /* some error */
459 /* MIX mail delete mailbox
460 * mailbox name to delete
461 * Returns: T on success, NIL on failure
464 long mix_delete (MAILSTREAM
*stream
,char *mailbox
)
469 char *s
,tmp
[MAILTMPLEN
];
470 if (!mix_isvalid (mailbox
,tmp
))
471 sprintf (tmp
,"Can't delete mailbox %.80s: no such mailbox",mailbox
);
472 else if (((fd
= open (tmp
,O_RDWR
,NIL
)) < 0) || flock (fd
,LOCK_EX
|LOCK_NB
))
473 sprintf (tmp
,"Can't lock mailbox for delete: %.80s",mailbox
);
474 /* delete metadata */
475 else if (unlink (tmp
)) sprintf (tmp
,"Can't delete mailbox %.80s index: %80s",
476 mailbox
,strerror (errno
));
478 close (fd
); /* close descriptor on deleted metadata */
479 /* get directory name */
480 *(s
= strrchr (tmp
,'/')) = '\0';
481 if ((dirp
= opendir (tmp
)) != NULL
) { /* open directory */
482 *s
++ = '/'; /* restore delimiter */
483 /* massacre messages */
484 while ((d
= readdir (dirp
)) != NULL
) if (mix_dirfmttest (d
->d_name
)) {
485 strcpy (s
,d
->d_name
); /* make path */
486 unlink (tmp
); /* sayonara */
488 closedir (dirp
); /* flush directory */
489 *(s
= strrchr (tmp
,'/')) = '\0';
490 if (rmdir (tmp
)) { /* try to remove the directory */
491 sprintf (tmp
,"Can't delete name %.80s: %.80s",
492 mailbox
,strerror (errno
));
496 return T
; /* always success */
498 if (fd
>= 0) close (fd
); /* close any descriptor on metadata */
499 MM_LOG (tmp
,ERROR
); /* something failed */
503 /* MIX mail rename mailbox
504 * Accepts: MIX mail stream
507 * Returns: T on success, NIL on failure
510 long mix_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
512 char c
,*s
,tmp
[MAILTMPLEN
],tmp1
[MAILTMPLEN
];
515 if (!mix_isvalid (old
,tmp
))
516 sprintf (tmp
,"Can't rename mailbox %.80s: no such mailbox",old
);
517 else if (((fd
= open (tmp
,O_RDWR
,NIL
)) < 0) || flock (fd
,LOCK_EX
|LOCK_NB
))
518 sprintf (tmp
,"Can't lock mailbox for rename: %.80s",old
);
519 else if (mix_dirfmttest ((s
= strrchr (newname
,'/')) ? s
+ 1 : newname
))
520 sprintf (tmp
,"Can't rename to mailbox %.80s: invalid MIX-format name",
522 /* new mailbox name must not be valid */
523 else if (mix_isvalid (newname
,tmp
))
524 sprintf (tmp
,"Can't rename to mailbox %.80s: destination already exists",
527 mix_dir (tmp
,old
); /* build old directory name */
528 mix_dir (tmp1
,newname
); /* and new directory name */
529 /* easy if not INBOX */
530 if (compare_cstring (old
,"INBOX")) {
531 /* found superior to destination name? */
532 if ((s
= strrchr (tmp1
,'/')) != NULL
) {
533 c
= *++s
; /* remember first character of inferior */
534 *s
= '\0'; /* tie off to get just superior */
535 /* name doesn't exist, create it */
536 if ((stat (tmp1
,&sbuf
) || ((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
)) &&
537 !dummy_create_path (stream
,tmp1
,get_dir_protection (newname
)))
539 *s
= c
; /* restore full name */
541 if (!rename (tmp
,tmp1
)) {
542 close (fd
); /* close descriptor on metadata */
547 /* RFC 3501 requires this */
548 else if (dummy_create_path (stream
,strcat (tmp1
,"/"),
549 get_dir_protection (newname
))) {
553 struct direct
**names
= NIL
;
554 size_t srcl
= strlen (tmp
);
555 size_t dstl
= strlen (tmp1
);
556 /* rename each mix file to new directory */
557 for (i
= lasterror
= 0,n
= scandir (tmp
,&names
,mix_rselect
,alphasort
);
559 size_t len
= strlen (names
[i
]->d_name
);
560 sprintf (src
= (char *) fs_get (srcl
+ len
+ 2),"%s/%s",
561 tmp
,names
[i
]->d_name
);
562 sprintf (dst
= (char *) fs_get (dstl
+ len
+ 1),"%s%s",
563 tmp1
,names
[i
]->d_name
);
564 if (rename (src
,dst
)) lasterror
= errno
;
565 fs_give ((void **) &src
);
566 fs_give ((void **) &dst
);
567 fs_give ((void **) &names
[i
]);
569 /* free directory list */
570 if ((a
= (void *) names
) != NULL
) fs_give ((void **) &a
);
571 if (lasterror
) errno
= lasterror
;
573 close (fd
); /* close descriptor on metadata */
574 return mix_create (NIL
,"INBOX");
577 sprintf (tmp
,"Can't rename mailbox %.80s to %.80s: %.80s",
578 old
,newname
,strerror (errno
));
580 if (fd
>= 0) close (fd
); /* close any descriptor on metadata */
581 MM_LOG (tmp
,ERROR
); /* something failed */
586 /* MIX test for mix name
587 * Accepts: candidate directory name
588 * Returns: T if mix file name, NIL otherwise
591 int mix_rselect (const struct direct
*name
)
593 return mix_dirfmttest ((char *) name
->d_name
);
597 * Accepts: stream to open
598 * Returns: stream on success, NIL on failure
601 MAILSTREAM
*mix_open (MAILSTREAM
*stream
)
604 /* return prototype for OP_PROTOTYPE call */
605 if (!stream
) return user_flags (&mixproto
);
606 if (stream
->local
) fatal ("mix recycle stream");
607 stream
->local
= memset (fs_get (sizeof (MIXLOCAL
)),0,sizeof (MIXLOCAL
));
608 /* note if an INBOX or not */
609 stream
->inbox
= !compare_cstring (stream
->mailbox
,"INBOX");
610 /* make temporary buffer */
611 LOCAL
->buf
= (char *) fs_get (CHUNKSIZE
);
612 LOCAL
->buflen
= CHUNKSIZE
- 1;
613 /* set stream->mailbox to be directory name */
614 mix_dir (LOCAL
->buf
,stream
->mailbox
);
615 fs_give ((void **) &stream
->mailbox
);
616 stream
->mailbox
= cpystr (LOCAL
->buf
);
617 LOCAL
->msgfd
= -1; /* currently no file open */
618 if (!(((!stream
->rdonly
&& /* open metadata file */
619 ((LOCAL
->mfd
= open (mix_file (LOCAL
->buf
,stream
->mailbox
,MIXMETA
),
620 O_RDWR
,NIL
)) >= 0)) ||
621 ((stream
->rdonly
= T
) &&
622 ((LOCAL
->mfd
= open (mix_file (LOCAL
->buf
,stream
->mailbox
,MIXMETA
),
623 O_RDONLY
,NIL
)) >= 0))) &&
624 !flock (LOCAL
->mfd
,LOCK_SH
))) {
625 MM_LOG ("Error opening mix metadata file",ERROR
);
627 stream
= NIL
; /* open fails */
629 else { /* metadata open, complete open */
630 LOCAL
->index
= cpystr (mix_file (LOCAL
->buf
,stream
->mailbox
,MIXINDEX
));
631 LOCAL
->status
= cpystr (mix_file (LOCAL
->buf
,stream
->mailbox
,MIXSTATUS
));
632 LOCAL
->sortcache
= cpystr (mix_file (LOCAL
->buf
,stream
->mailbox
,
634 stream
->sequence
++; /* bump sequence number */
636 stream
->nmsgs
= stream
->recent
= 0;
637 if ((silent
= stream
->silent
) != 0) LOCAL
->internal
= T
;
639 if (mix_ping (stream
)) { /* do initial ping */
640 /* try burping in case we are exclusive */
641 if (!stream
->rdonly
) mix_expunge (stream
,"",NIL
);
642 if (!(stream
->nmsgs
|| stream
->silent
))
643 MM_LOG ("Mailbox is empty",(long) NIL
);
644 stream
->silent
= silent
; /* now notify upper level */
645 mail_exists (stream
,stream
->nmsgs
);
646 stream
->perm_seen
= stream
->perm_deleted
= stream
->perm_flagged
=
647 stream
->perm_answered
= stream
->perm_draft
= stream
->rdonly
? NIL
: T
;
648 stream
->perm_user_flags
= stream
->rdonly
? NIL
: 0xffffffff;
649 stream
->kwd_create
= /* can we create new user flags? */
650 (stream
->user_flags
[NUSERFLAGS
-1] || stream
->rdonly
) ? NIL
: T
;
652 else { /* got murdelyzed in ping */
657 return stream
; /* return stream to caller */
661 * Accepts: MAIL stream
665 void mix_close (MAILSTREAM
*stream
,long options
)
667 if (LOCAL
) { /* only if a file is open */
668 int silent
= stream
->silent
;
669 stream
->silent
= T
; /* note this stream is dying */
670 /* burp-only or expunge */
671 mix_expunge (stream
,(options
& CL_EXPUNGE
) ? NIL
: "",NIL
);
673 stream
->silent
= silent
; /* reset silent state */
678 /* MIX mail abort stream
679 * Accepts: MAIL stream
682 void mix_abort (MAILSTREAM
*stream
)
684 if (LOCAL
) { /* only if a file is open */
685 /* close current message file if open */
686 if (LOCAL
->msgfd
>= 0) close (LOCAL
->msgfd
);
687 /* close current metadata file if open */
688 if (LOCAL
->mfd
>= 0) close (LOCAL
->mfd
);
689 if (LOCAL
->index
) fs_give ((void **) &LOCAL
->index
);
690 if (LOCAL
->status
) fs_give ((void **) &LOCAL
->status
);
691 if (LOCAL
->sortcache
) fs_give ((void **) &LOCAL
->sortcache
);
692 /* free local scratch buffer */
693 if (LOCAL
->buf
) fs_give ((void **) &LOCAL
->buf
);
694 /* nuke the local data */
695 fs_give ((void **) &stream
->local
);
696 stream
->dtb
= NIL
; /* log out the DTB */
700 /* MIX mail fetch message header
701 * Accepts: MAIL stream
703 * pointer to returned header text length
705 * Returns: message header in RFC822 format
708 char *mix_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
713 char *s
,tmp
[MAILTMPLEN
];
715 if (length
) *length
= 0; /* default return */
716 if (flags
& FT_UID
) return "";/* UID call "impossible" */
717 elt
= mail_elt (stream
,msgno
);/* get elt */
718 /* is message in current message file? */
719 if ((LOCAL
->msgfd
< 0) || (elt
->private.spare
.data
!= LOCAL
->curmsg
)) {
720 if (LOCAL
->msgfd
>= 0) close (LOCAL
->msgfd
);
721 if ((LOCAL
->msgfd
= open (mix_file_data (LOCAL
->buf
,stream
->mailbox
,
722 elt
->private.spare
.data
),
723 O_RDONLY
,NIL
)) < 0) return "";
725 LOCAL
->curmsg
= elt
->private.spare
.data
;
727 lseek (LOCAL
->msgfd
,elt
->private.special
.offset
,L_SET
);
728 /* size of special data and header */
729 j
= elt
->private.msg
.header
.offset
+ elt
->private.msg
.header
.text
.size
;
730 if (j
> LOCAL
->buflen
) { /* is buffer big enough? */
731 /* no, make one that is */
732 fs_give ((void **) &LOCAL
->buf
);
733 LOCAL
->buf
= (char *) fs_get ((LOCAL
->buflen
= j
) + 1);
735 /* Maybe someday validate internaldate too */
736 /* slurp special data + header, validate */
737 if ((read (LOCAL
->msgfd
,LOCAL
->buf
,j
) == j
) &&
738 !strncmp (LOCAL
->buf
,MSGTOK
,MSGTSZ
) &&
739 (elt
->private.uid
== strtoul ((char *) LOCAL
->buf
+ MSGTSZ
,&s
,16)) &&
740 (*s
++ == ':') && (s
= strchr (s
,':')) &&
741 (k
= strtoul (s
+1,&s
,16)) && (*s
++ == ':') &&
742 (s
< (char *) (LOCAL
->buf
+ elt
->private.msg
.header
.offset
))) {
743 /* won, set offset and size of message */
744 i
= elt
->private.msg
.header
.offset
;
745 *length
= elt
->private.msg
.header
.text
.size
;
746 if (k
!= elt
->rfc822_size
) {
747 sprintf (tmp
,"Inconsistency in mix message size, uid=%lx (%lu != %lu)",
748 elt
->private.uid
,elt
->rfc822_size
,k
);
752 else { /* document the problem */
753 LOCAL
->buf
[100] = '\0'; /* tie off buffer at no more than 100 octets */
754 /* or at newline, whichever is first */
755 if ((s
= strpbrk (LOCAL
->buf
,"\015\012")) != NULL
) *s
= '\0';
756 sprintf (tmp
,"Error reading mix message header, uid=%lx, s=%.0lx, h=%s",
757 elt
->private.uid
,elt
->rfc822_size
,LOCAL
->buf
);
759 *length
= i
= j
= 0; /* default to empty */
761 LOCAL
->buf
[j
] = '\0'; /* tie off buffer at the end */
762 return (char *) LOCAL
->buf
+ i
;
765 /* MIX mail fetch message text (body only)
766 * Accepts: MAIL stream
768 * pointer to returned stringstruct
770 * Returns: T on success, NIL on failure
773 long mix_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
778 /* UID call "impossible" */
779 if (flags
& FT_UID
) return NIL
;
780 elt
= mail_elt (stream
,msgno
);
781 /* is message in current message file? */
782 if ((LOCAL
->msgfd
< 0) || (elt
->private.spare
.data
!= LOCAL
->curmsg
)) {
783 if (LOCAL
->msgfd
>= 0) close (LOCAL
->msgfd
);
784 if ((LOCAL
->msgfd
= open (mix_file_data (LOCAL
->buf
,stream
->mailbox
,
785 elt
->private.spare
.data
),
786 O_RDONLY
,NIL
)) < 0) return NIL
;
788 LOCAL
->curmsg
= elt
->private.spare
.data
;
790 /* doing non-peek fetch? */
791 if (!(flags
& FT_PEEK
) && !elt
->seen
) {
792 FILE *idxf
; /* yes, process metadata/index/status */
793 FILE *statf
= mix_parse (stream
,&idxf
,NIL
,LONGT
);
794 elt
->seen
= T
; /* mark as seen */
795 MM_FLAGS (stream
,elt
->msgno
);
796 /* update status file if possible */
797 if (statf
&& !stream
->rdonly
) {
798 elt
->private.mod
= LOCAL
->statusseq
= mix_modseq (LOCAL
->statusseq
);
799 mix_status_update (stream
,statf
,NIL
);
801 if (idxf
) fclose (idxf
); /* release index and status file */
802 if (statf
) fclose (statf
);
804 d
.fd
= LOCAL
->msgfd
; /* set up file descriptor */
805 /* offset of message text */
806 d
.pos
= elt
->private.special
.offset
+ elt
->private.msg
.header
.offset
+
807 elt
->private.msg
.header
.text
.size
;
808 d
.chunk
= LOCAL
->buf
; /* initial buffer chunk */
809 d
.chunksize
= CHUNKSIZE
; /* chunk size */
810 INIT (bs
,fd_string
,&d
,elt
->rfc822_size
- elt
->private.msg
.header
.text
.size
);
814 /* MIX mail modify flags
815 * Accepts: MAIL stream
821 void mix_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
)
824 unsigned long i
,uf
,ffkey
;
828 FILE *statf
= mix_parse (stream
,&idxf
,NIL
,LONGT
);
829 unsigned long seq
= mix_modseq (LOCAL
->statusseq
);
830 /* find first free key */
831 for (ffkey
= 0; (ffkey
< NUSERFLAGS
) && stream
->user_flags
[ffkey
]; ++ffkey
);
832 /* parse sequence and flags */
833 if (((flags
& ST_UID
) ? mail_uid_sequence (stream
,sequence
) :
834 mail_sequence (stream
,sequence
)) &&
835 ((f
= mail_parse_flags (stream
,flag
,&uf
)) || uf
)) {
837 for (i
= 1,nf
= (flags
& ST_SET
) ? T
: NIL
; i
<= stream
->nmsgs
; i
++)
838 if ((elt
= mail_elt (stream
,i
))->sequence
) {
839 struct { /* old flags */
840 unsigned int seen
: 1;
841 unsigned int deleted
: 1;
842 unsigned int flagged
: 1;
843 unsigned int answered
: 1;
844 unsigned int draft
: 1;
845 unsigned long user_flags
;
847 old
.seen
= elt
->seen
; old
.deleted
= elt
->deleted
;
848 old
.flagged
= elt
->flagged
; old
.answered
= elt
->answered
;
849 old
.draft
= elt
->draft
; old
.user_flags
= elt
->user_flags
;
850 if (f
&fSEEN
) elt
->seen
= nf
;
851 if (f
&fDELETED
) elt
->deleted
= nf
;
852 if (f
&fFLAGGED
) elt
->flagged
= nf
;
853 if (f
&fANSWERED
) elt
->answered
= nf
;
854 if (f
&fDRAFT
) elt
->draft
= nf
;
856 if (flags
& ST_SET
) elt
->user_flags
|= uf
;
857 else elt
->user_flags
&= ~uf
;
858 if ((old
.seen
!= elt
->seen
) || (old
.deleted
!= elt
->deleted
) ||
859 (old
.flagged
!= elt
->flagged
) ||
860 (old
.answered
!= elt
->answered
) || (old
.draft
!= elt
->draft
) ||
861 (old
.user_flags
!= elt
->user_flags
)) {
862 if (!stream
->rdonly
) elt
->private.mod
= LOCAL
->statusseq
= seq
;
863 MM_FLAGS (stream
,elt
->msgno
);
866 /* update status file after change */
867 if (statf
&& (seq
== LOCAL
->statusseq
))
868 mix_status_update (stream
,statf
,NIL
);
869 /* update metadata if created a keyword */
870 if ((ffkey
< NUSERFLAGS
) && stream
->user_flags
[ffkey
] &&
871 !mix_meta_update (stream
))
872 MM_LOG ("Error updating mix metadata after keyword creation",ERROR
);
874 if (statf
) fclose (statf
); /* release status file if still open */
875 if (idxf
) fclose (idxf
); /* release index file */
878 /* MIX mail sort messages
879 * Accepts: mail stream
884 * Returns: vector of sorted message sequences or NIL if error
887 unsigned long *mix_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
888 SORTPGM
*pgm
,long flags
)
891 FILE *sortcache
= mix_sortcache_open (stream
);
892 ret
= mail_sort_msgs (stream
,charset
,spg
,pgm
,flags
);
893 mix_sortcache_update (stream
,&sortcache
);
898 /* MIX mail thread messages
899 * Accepts: mail stream
904 * Returns: thread node tree or NIL if error
907 THREADNODE
*mix_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
908 SEARCHPGM
*spg
,long flags
)
911 FILE *sortcache
= mix_sortcache_open (stream
);
912 ret
= mail_thread_msgs (stream
,type
,charset
,spg
,flags
,mail_sort_msgs
);
913 mix_sortcache_update (stream
,&sortcache
);
917 /* MIX mail ping mailbox
918 * Accepts: MAIL stream
919 * Returns: T if stream alive, else NIL
922 static int snarfing
= 0; /* lock against recursive snarfing */
924 long mix_ping (MAILSTREAM
*stream
)
931 unsigned long i
,msglen
;
932 char *message
,date
[MAILTMPLEN
],flags
[MAILTMPLEN
];
933 MAILSTREAM
*sysibx
= NIL
;
935 long snarfok
= LONGT
;
937 if (stream
->inbox
&& !stream
->rdonly
&& !snarfing
&&
938 (time (0) >= (LOCAL
->lastsnarf
+
939 (time_t) mail_parameters (NIL
,GET_SNARFINTERVAL
,NIL
)))) {
940 appenduid_t au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
);
941 copyuid_t cu
= (copyuid_t
) mail_parameters (NIL
,GET_COPYUID
,NIL
);
942 MM_CRITICAL (stream
); /* go critical */
943 snarfing
= T
; /* don't recursively snarf */
944 /* disable APPENDUID/COPYUID callbacks */
945 mail_parameters (NIL
,SET_APPENDUID
,NIL
);
946 mail_parameters (NIL
,SET_COPYUID
,NIL
);
947 /* sizes match and anything in sysinbox? */
948 if (!stat (sysinbox (),&sbuf
) && ((sbuf
.st_mode
& S_IFMT
) == S_IFREG
) &&
949 sbuf
.st_size
&& (sysibx
= mail_open (sysibx
,sysinbox (),OP_SILENT
)) &&
950 !sysibx
->rdonly
&& sysibx
->nmsgs
) {
951 /* for each message in sysibx mailbox */
952 for (i
= 1; snarfok
&& (i
<= sysibx
->nmsgs
); ++i
)
953 if (!(elt
= mail_elt (sysibx
,i
))->deleted
&&
954 (message
= mail_fetch_message (sysibx
,i
,&msglen
,FT_PEEK
)) &&
956 mail_date (date
,elt
); /* make internal date string */
957 /* make flag string */
958 flags
[0] = flags
[1] = '\0';
959 if (elt
->seen
) strcat (flags
," \\Seen");
960 if (elt
->flagged
) strcat (flags
," \\Flagged");
961 if (elt
->answered
) strcat (flags
," \\Answered");
962 if (elt
->draft
) strcat (flags
," \\Draft");
965 INIT (&msg
,mail_string
,message
,msglen
);
966 if ((snarfok
= mail_append_full (stream
,"INBOX",flags
,date
,&msg
)) != 0L) {
968 sprintf (sequence
,"%lu",i
);
969 mail_flag (sysibx
,sequence
,"\\Deleted",ST_SET
);
973 /* now expunge all those messages */
974 if (snarfok
) mail_expunge (sysibx
);
976 sprintf (LOCAL
->buf
,"Can't copy new mail at message: %lu",i
- 1);
977 MM_LOG (LOCAL
->buf
,WARN
);
980 if (sysibx
) mail_close (sysibx
);
981 /* re-enable APPENDUID/COPYUID */
982 mail_parameters (NIL
,SET_APPENDUID
,(void *) au
);
983 mail_parameters (NIL
,SET_COPYUID
,(void *) cu
);
984 snarfing
= NIL
; /* no longer snarfing */
985 MM_NOCRITICAL (stream
); /* release critical */
986 LOCAL
->lastsnarf
= time (0);/* note time of last snarf */
988 /* expunging OK if global flag set */
989 if (mail_parameters (NIL
,GET_EXPUNGEATPING
,NIL
)) LOCAL
->expok
= T
;
990 /* process metadata/index/status */
991 if ((statf
= mix_parse (stream
,&idxf
,LONGT
,
992 (LOCAL
->internal
? NIL
: LONGT
))) != NULL
) {
993 fclose (statf
); /* just close the status file */
994 ret
= LONGT
; /* declare success */
996 if (idxf
) fclose (idxf
); /* release index file */
997 LOCAL
->expok
= NIL
; /* expunge no longer OK */
998 if (!ret
) mix_abort (stream
); /* murdelyze stream if ping fails */
1003 /* MIX mail checkpoint mailbox (burp only)
1004 * Accepts: MAIL stream
1007 void mix_check (MAILSTREAM
*stream
)
1009 if (stream
->rdonly
) /* won't do on readonly files! */
1010 MM_LOG ("Checkpoint ignored on readonly mailbox",NIL
);
1011 /* do burp-only expunge action */
1012 if (mix_expunge (stream
,"",NIL
)) MM_LOG ("Check completed",(long) NIL
);
1015 /* MIX mail expunge mailbox
1016 * Accepts: MAIL stream
1017 * sequence to expunge if non-NIL, empty string for burp only
1019 * Returns: T on success, NIL if failure
1022 long mix_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
1030 unsigned long nexp
= 0;
1031 unsigned long reclaimed
= 0;
1032 int burponly
= (sequence
&& !*sequence
);
1033 LOCAL
->expok
= T
; /* expunge during ping is OK */
1034 if (!(ret
= burponly
|| !sequence
||
1035 ((options
& EX_UID
) ?
1036 mail_uid_sequence (stream
,sequence
) :
1037 mail_sequence (stream
,sequence
))) || stream
->rdonly
);
1038 /* read index and open status exclusive */
1039 else if ((statf
= mix_parse (stream
,&idxf
,LONGT
,
1040 LOCAL
->internal
? NIL
: LONGT
)) != NULL
) {
1041 /* expunge unless just burping */
1042 if (!burponly
) for (i
= 1; i
<= stream
->nmsgs
;) {
1043 elt
= mail_elt (stream
,i
);/* need to expunge this message? */
1044 if (elt
->deleted
&& (sequence
? elt
->sequence
: T
)) {
1045 ++nexp
; /* yes, make it so */
1046 mail_expunged (stream
,i
);
1048 else ++i
; /* otherwise advance to next message */
1051 /* burp if can get exclusive access */
1052 if (!flock (LOCAL
->mfd
,LOCK_EX
|LOCK_NB
)) {
1054 struct direct
**names
= NIL
;
1055 long nfiles
= scandir (stream
->mailbox
,&names
,mix_select
,mix_msgfsort
);
1056 if (nfiles
> 0) { /* if have message files */
1058 /* initialize burp list */
1059 for (i
= 0, burp
= cur
= NIL
; i
< nfiles
; ++i
) {
1060 MIXBURP
*nxt
= (MIXBURP
*) memset (fs_get (sizeof (MIXBURP
)),0,
1062 /* another file found */
1063 if (cur
) cur
= cur
->next
= nxt
;
1064 else cur
= burp
= nxt
;
1065 cur
->name
= cpystr (names
[i
]->d_name
);
1066 cur
->fileno
= strtoul (cur
->name
+ sizeof (MIXNAME
) - 1,NIL
,16);
1067 cur
->tail
= &cur
->set
;
1068 fs_give ((void **) &names
[i
]);
1070 /* now load ranges */
1071 for (i
= 1, cur
= burp
; ret
&& (i
<= stream
->nmsgs
); i
++) {
1072 /* is this message in current set? */
1073 elt
= mail_elt (stream
,i
);
1074 if (cur
&& (elt
->private.spare
.data
!= cur
->fileno
)) {
1075 /* restart if necessary */
1076 if (elt
->private.spare
.data
< cur
->fileno
) cur
= burp
;
1077 /* hunt for appropriate mailbox */
1078 while (cur
&& (elt
->private.spare
.data
> cur
->fileno
))
1080 /* ought to have found it now... */
1081 if (cur
&& (elt
->private.spare
.data
!= cur
->fileno
)) cur
= NIL
;
1083 /* if found, add to set */
1084 if (cur
) ret
= mix_addset (&cur
->tail
,elt
->private.special
.offset
,
1085 elt
->private.msg
.header
.offset
+
1088 sprintf (LOCAL
->buf
,"Can't locate mix message file %.08lx",
1089 elt
->private.spare
.data
);
1090 MM_LOG (LOCAL
->buf
,ERROR
);
1094 if (ret
) /* if no errors, burp all files */
1095 for (cur
= burp
; ret
&& cur
; cur
= cur
->next
) {
1096 /* if non-empty, burp it */
1097 if (cur
->set
.last
) ret
= mix_burp (stream
,cur
,&reclaimed
);
1098 /* empty, delete it unless new msg file */
1099 else if (mix_file_data (LOCAL
->buf
,stream
->mailbox
,cur
->fileno
) &&
1100 ((cur
->fileno
== LOCAL
->newmsg
) ?
1101 truncate (LOCAL
->buf
,0) : unlink (LOCAL
->buf
))) {
1102 sprintf (LOCAL
->buf
,
1103 "Can't delete empty message file %.80s: %.80s",
1104 cur
->name
,strerror (errno
));
1105 MM_LOG (LOCAL
->buf
,WARN
);
1108 while (burp
) { /* flush the burp list */
1110 if (burp
->name
) fs_give ((void **) &burp
->name
);
1111 fs_give ((void **) &burp
);
1115 else MM_LOG ("No mix message files found during expunge",WARN
);
1116 /* free directory list */
1117 if ((a
= (void *) names
) != NULL
) fs_give ((void **) &a
);
1120 /* either way, re-acquire shared lock */
1121 if (flock (LOCAL
->mfd
,LOCK_SH
|LOCK_NB
))
1122 fatal ("Unable to re-acquire metadata shared lock!");
1123 /* Do this step even if ret is NIL (meaning some burp problem)! */
1124 if (nexp
|| reclaimed
) { /* rewrite index and status if changed */
1125 LOCAL
->indexseq
= mix_modseq (LOCAL
->indexseq
);
1126 if ((ret
= mix_index_update (stream
,idxf
,NIL
)) != 0L){
1127 LOCAL
->statusseq
= mix_modseq (LOCAL
->statusseq
);
1128 /* set failure if update fails */
1129 ret
= mix_status_update (stream
,statf
,NIL
);
1133 if (statf
) fclose (statf
); /* close status if still open */
1134 if (idxf
) fclose (idxf
); /* close index if still open */
1135 LOCAL
->expok
= NIL
; /* cancel expok */
1136 if (ret
) { /* only if success */
1138 if (nexp
) sprintf (s
= LOCAL
->buf
,"Expunged %lu messages",nexp
);
1140 sprintf (s
=LOCAL
->buf
,"Reclaimed %lu bytes of expunged space",reclaimed
);
1142 s
= stream
->rdonly
? "Expunge ignored on readonly mailbox" :
1143 "No messages deleted, so no update needed";
1144 if (s
) MM_LOG (s
,(long) NIL
);
1149 /* MIX test for message file name
1150 * Accepts: candidate directory name
1151 * Returns: T if message file name, NIL otherwise
1153 * ".mix" with no suffix was used by experimental versions
1156 int mix_select (const struct direct
*name
)
1159 /* make sure name has prefix */
1160 if (mix_dirfmttest ((char *) name
->d_name
)) {
1161 for (c
= *(s
= ((char *) name
->d_name
) + sizeof (MIXNAME
) - 1); c
&& isxdigit (c
);
1163 if (!c
) return T
; /* all-hex or no suffix */
1165 return NIL
; /* not suffix or non-hex */
1169 /* MIX msg file name comparison
1170 * Accepts: first candidate directory entry
1171 * second candidate directory entry
1172 * Returns: -1 if d1 < d2, 0 if d1 == d2, 1 d1 > d2
1175 int mix_msgfsort (const struct direct
**d1
,const struct direct
**d2
)
1177 char *n1
= (*(struct direct
**) d1
)->d_name
+ sizeof (MIXNAME
) - 1;
1178 char *n2
= (*(struct direct
**) d2
)->d_name
+ sizeof (MIXNAME
) - 1;
1179 return compare_ulong (*n1
? strtoul (n1
,NIL
,16) : 0,
1180 *n2
? strtoul (n2
,NIL
,16) : 0);
1184 /* MIX add a range to a set
1185 * Accepts: pointer to set to add
1188 * Returns: T if success, set updated, NIL otherwise
1191 long mix_addset (SEARCHSET
**set
,unsigned long start
,unsigned long size
)
1193 SEARCHSET
*s
= *set
;
1194 if (start
< s
->last
) { /* sanity check */
1195 char tmp
[MAILTMPLEN
];
1196 sprintf (tmp
,"Backwards-running mix index %lu < %lu",start
,s
->last
);
1200 /* range initially empty? */
1201 if (!s
->last
) s
->first
= start
;
1202 else if (start
> s
->last
) /* no, start new range if can't append */
1203 (*set
= s
= s
->next
= mail_newsearchset ())->first
= start
;
1204 s
->last
= start
+ size
; /* end of current range */
1208 /* MIX burp message file
1209 * Accepts: MAIL stream
1210 * current burp block for this message
1211 * Returns: T if successful, NIL if failed
1214 static char *staterr
= "Error in stat of mix message file %.80s: %.80s";
1215 static char *truncerr
= "Error truncating mix message file %.80s: %.80s";
1217 long mix_burp (MAILSTREAM
*stream
,MIXBURP
*burp
,unsigned long *reclaimed
)
1223 size_t size
,wsize
,wpending
,written
;
1229 /* build file name */
1230 mix_file_data (LOCAL
->buf
,stream
->mailbox
,burp
->fileno
);
1231 /* need to burp at start or multiple ranges? */
1232 if (!burp
->set
.first
&& !burp
->set
.next
) {
1233 /* easy case, single range at start of file */
1234 if (stat (LOCAL
->buf
,&sbuf
)) {
1235 sprintf (LOCAL
->buf
,staterr
,burp
->name
,strerror (errno
));
1236 MM_LOG (LOCAL
->buf
,ERROR
);
1238 /* is this range sane? */
1239 else if (mix_burp_check (&burp
->set
,sbuf
.st_size
,LOCAL
->buf
)) {
1240 /* if matches range then no burp needed! */
1241 if (burp
->set
.last
== sbuf
.st_size
) ret
= LONGT
;
1242 /* just need to remove cruft at end */
1243 else if ((ret
= !truncate (LOCAL
->buf
,burp
->set
.last
)) != 0L)
1244 *reclaimed
+= sbuf
.st_size
- burp
->set
.last
;
1246 sprintf (LOCAL
->buf
,truncerr
,burp
->name
,strerror (errno
));
1247 MM_LOG (LOCAL
->buf
,ERROR
);
1251 /* have to do more work, get the file */
1252 else if (((fd
= open (LOCAL
->buf
,O_RDWR
,NIL
)) < 0) ||
1253 !(f
= fdopen (fd
,"r+b"))) {
1254 sprintf (LOCAL
->buf
,"Error opening mix message file %.80s: %.80s",
1255 burp
->name
,strerror (errno
));
1256 MM_LOG (LOCAL
->buf
,ERROR
);
1257 if (fd
>= 0) close (fd
); /* in case fdopen() failure */
1259 else if (fstat (fd
,&sbuf
)) { /* get file size */
1260 sprintf (LOCAL
->buf
,staterr
,burp
->name
,strerror (errno
));
1261 MM_LOG (LOCAL
->buf
,ERROR
);
1266 else if (mix_burp_check (&burp
->set
,sbuf
.st_size
,LOCAL
->buf
)) {
1267 /* make sure each range starts with token */
1268 for (set
= &burp
->set
; set
; set
= set
->next
)
1269 if (fseek (f
,set
->first
,SEEK_SET
) ||
1270 (fread (LOCAL
->buf
,1,MSGTSZ
,f
) != MSGTSZ
) ||
1271 strncmp (LOCAL
->buf
,MSGTOK
,MSGTSZ
)) {
1272 sprintf (LOCAL
->buf
,"Bad message token in mix message file at %lu",
1274 MM_LOG (LOCAL
->buf
,ERROR
);
1276 return NIL
; /* burp fails for this file */
1278 /* burp out each old message */
1279 for (set
= &burp
->set
, rpos
= wpos
= 0; set
; set
= set
->next
) {
1280 /* move down this range */
1281 for (rpos
= set
->first
, size
= set
->last
- set
->first
;
1282 size
; size
-= wsize
) {
1283 if (rpos
!= wpos
) { /* data to skip at start? */
1284 /* no, slide this buffer down */
1285 wsize
= min (size
,LOCAL
->buflen
);
1286 /* failure is not an option here */
1287 while (fseek (f
,rpos
,SEEK_SET
) ||
1288 (fread (LOCAL
->buf
,1,wsize
,f
) != wsize
)) {
1289 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1290 MM_DISKERROR (stream
,errno
,T
);
1293 while (fseek (f
,wpos
,SEEK_SET
)) {
1294 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1295 MM_DISKERROR (stream
,errno
,T
);
1297 /* and especially not here */
1298 for (s
= LOCAL
->buf
, wpending
= wsize
; wpending
; s
+= written
, wpending
-= written
)
1299 if (!(written
= fwrite (s
,1,wpending
,f
))) {
1300 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1301 MM_DISKERROR (stream
,errno
,T
);
1304 else wsize
= size
; /* nothing to skip, say we wrote it all */
1305 rpos
+= wsize
; wpos
+= wsize
;
1309 while (fflush (f
)) { /* failure also not an option here... */
1310 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1311 MM_DISKERROR (stream
,errno
,T
);
1313 if (ftruncate (fd
,wpos
)) { /* flush cruft at end of file */
1314 sprintf (LOCAL
->buf
,truncerr
,burp
->name
,strerror (errno
));
1315 MM_LOG (LOCAL
->buf
,WARN
);
1317 else *reclaimed
+= rpos
- wpos
;
1318 ret
= !fclose (f
); /* close file */
1319 /* slide down message positions in index */
1320 for (i
= 1,rpos
= 0; i
<= stream
->nmsgs
; ++i
)
1321 if ((elt
= mail_elt (stream
,i
))->private.spare
.data
== burp
->fileno
) {
1322 elt
->private.special
.offset
= rpos
;
1323 rpos
+= elt
->private.msg
.header
.offset
+ elt
->rfc822_size
;
1326 if (rpos
!= wpos
) fatal ("burp size consistency check!");
1332 /* MIX burp sanity check to make sure not burping off end of file
1336 * Returns: T if sane, NIL if insane
1339 long mix_burp_check (SEARCHSET
*set
,size_t size
,char *file
)
1341 do if (set
->last
> size
) { /* sanity check */
1342 char tmp
[MAILTMPLEN
];
1343 sprintf (tmp
,"Unexpected short mix message file %.80s %lu < %lu",
1344 file
,size
,set
->last
);
1346 return NIL
; /* don't burp this file at all */
1347 } while ((set
= set
->next
) != NULL
);
1351 /* MIX mail copy message(s)
1352 * Accepts: MAIL stream
1354 * destination mailbox
1356 * Returns: T if copy successful, else NIL
1359 long mix_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
1363 char tmp
[2*MAILTMPLEN
];
1364 long ret
= mix_isvalid (mailbox
,LOCAL
->buf
);
1365 mailproxycopy_t pc
=
1366 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
1367 MAILSTREAM
*astream
= NIL
;
1371 if (!ret
) switch (errno
) { /* make sure valid mailbox */
1372 case NIL
: /* no error in stat() */
1373 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
1374 sprintf (tmp
,"Not a MIX-format mailbox: %.80s",mailbox
);
1377 default: /* some stat() error */
1378 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before copy",NIL
);
1381 /* get sequence to copy */
1382 else if (!(ret
= ((options
& CP_UID
) ? mail_uid_sequence (stream
,sequence
) :
1383 mail_sequence (stream
,sequence
))));
1384 /* acquire stream to append */
1385 else if ((ret
= ((astream
= mail_open (NIL
,mailbox
,OP_SILENT
)) &&
1387 (((MIXLOCAL
*) astream
->local
)->expok
= T
) &&
1388 (statf
= mix_parse (astream
,&idxf
,LONGT
,NIL
))) ?
1389 LONGT
: NIL
) != 0L) {
1393 unsigned long newsize
,hdrsize
,size
;
1394 MIXLOCAL
*local
= (MIXLOCAL
*) astream
->local
;
1395 unsigned long seq
= mix_modseq (local
->metaseq
);
1396 /* make sure new modseq fits */
1397 if (local
->indexseq
> seq
) seq
= local
->indexseq
+ 1;
1398 if (local
->statusseq
> seq
) seq
= local
->statusseq
+ 1;
1399 /* calculate size of per-message header */
1400 sprintf (local
->buf
,MSRFMT
,MSGTOK
,(unsigned long) 0,0,0,0,0,0,0,'+',0,0,
1402 hdrsize
= strlen (local
->buf
);
1404 MM_CRITICAL (stream
); /* go critical */
1405 astream
->silent
= T
; /* no events here */
1406 /* calculate size that will be added */
1407 for (i
= 1, newsize
= 0; i
<= stream
->nmsgs
; ++i
)
1408 if ((elt
= mail_elt (stream
,i
))->sequence
)
1409 newsize
+= hdrsize
+ elt
->rfc822_size
;
1410 /* open data file */
1411 if ((msgf
= mix_data_open (astream
,&fd
,&size
,newsize
)) != NULL
) {
1413 unsigned long j
,uid
,uidv
;
1414 copyuid_t cu
= (copyuid_t
) mail_parameters (NIL
,GET_COPYUID
,NIL
);
1415 SEARCHSET
*source
= cu
? mail_newsearchset () : NIL
;
1416 SEARCHSET
*dest
= cu
? mail_newsearchset () : NIL
;
1417 for (i
= 1,uid
= uidv
= 0; ret
&& (i
<= stream
->nmsgs
); ++i
)
1418 if (((elt
= mail_elt (stream
,i
))->sequence
) && elt
->rfc822_size
) {
1419 /* is message in current message file? */
1420 if ((LOCAL
->msgfd
< 0) ||
1421 (elt
->private.spare
.data
!= LOCAL
->curmsg
)) {
1422 if (LOCAL
->msgfd
>= 0) close (LOCAL
->msgfd
);
1423 if ((LOCAL
->msgfd
= open (mix_file_data (LOCAL
->buf
,
1425 elt
->private.spare
.data
),
1426 O_RDONLY
,NIL
)) >= 0)
1427 LOCAL
->curmsg
= elt
->private.spare
.data
;
1429 if (LOCAL
->msgfd
< 0) ret
= NIL
;
1430 else { /* got file */
1431 d
.fd
= LOCAL
->msgfd
;/* set up file descriptor */
1432 /* start of message */
1433 d
.pos
= elt
->private.special
.offset
+
1434 elt
->private.msg
.header
.offset
;
1435 d
.chunk
= LOCAL
->buf
;
1436 d
.chunksize
= CHUNKSIZE
;
1437 INIT (&st
,fd_string
,&d
,elt
->rfc822_size
);
1438 /* init flag string */
1439 tmp
[0] = tmp
[1] = '\0';
1440 if ((j
= elt
->user_flags
) != 0L) do
1441 if ((t
= stream
->user_flags
[find_rightmost_bit (&j
)]) && *t
)
1442 strcat (strcat (tmp
," "),t
);
1444 if (elt
->seen
) strcat (tmp
," \\Seen");
1445 if (elt
->deleted
) strcat (tmp
," \\Deleted");
1446 if (elt
->flagged
) strcat (tmp
," \\Flagged");
1447 if (elt
->answered
) strcat (tmp
," \\Answered");
1448 if (elt
->draft
) strcat (tmp
," \\Draft");
1449 tmp
[0] = '('; /* wrap list */
1451 /* if append OK, add to source set */
1452 if ((ret
= mix_append_msg (astream
,msgf
,tmp
,elt
,&st
,dest
,
1454 mail_append_set (source
,mail_uid (stream
,i
));
1458 /* finish write if success */
1459 if (ret
&& (ret
= !fflush (msgf
))) {
1460 fclose (msgf
); /* all good, close the msg file now */
1461 /* write new metadata, index, and status */
1462 local
->metaseq
= local
->indexseq
= local
->statusseq
= seq
;
1463 if ((ret
= (mix_meta_update (astream
) &&
1464 mix_index_update (astream
,idxf
,LONGT
))) != 0L){
1465 /* success, delete if doing a move */
1466 if (options
& CP_MOVE
)
1467 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1468 if ((elt
= mail_elt (stream
,i
))->sequence
) {
1470 if (!stream
->rdonly
) elt
->private.mod
= LOCAL
->statusseq
= seq
;
1471 MM_FLAGS (stream
,elt
->msgno
);
1473 /* done with status file now */
1474 mix_status_update (astream
,statf
,LONGT
);
1475 /* return sets if doing COPYUID */
1476 if (cu
) (*cu
) (stream
,mailbox
,astream
->uid_validity
,source
,dest
);
1477 source
= dest
= NIL
; /* don't free these sets now */
1481 if (errno
) { /* output error message if system call error */
1482 sprintf (tmp
,"Message copy failed: %.80s",strerror (errno
));
1485 ftruncate (fd
,size
); /* revert file */
1486 close (fd
); /* make sure that fclose doesn't corrupt us */
1487 fclose (msgf
); /* free the stdio resources */
1489 /* flush any sets remaining */
1490 mail_free_searchset (&source
);
1491 mail_free_searchset (&dest
);
1493 else { /* message file open failed */
1494 sprintf (tmp
,"Error opening copy message file: %.80s",
1499 MM_NOCRITICAL (stream
);
1501 else MM_LOG ("Can't open copy mailbox",ERROR
);
1502 if (statf
) fclose (statf
); /* close status if still open */
1503 if (idxf
) fclose (idxf
); /* close index if still open */
1504 /* finished with append stream */
1505 if (astream
) mail_close (astream
);
1506 return ret
; /* return state */
1509 /* MIX mail append message from stringstruct
1510 * Accepts: MAIL stream
1511 * destination mailbox
1514 * Returns: T if append successful, else NIL
1517 long mix_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
1520 char *flags
,*date
,tmp
[MAILTMPLEN
];
1521 /* N.B.: can't use LOCAL->buf for tmp */
1522 long ret
= mix_isvalid (mailbox
,tmp
);
1523 /* default stream to prototype */
1524 if (!stream
) stream
= user_flags (&mixproto
);
1525 if (!ret
) switch (errno
) { /* if not valid mailbox */
1526 case ENOENT
: /* no such file? */
1527 if ((ret
= compare_cstring (mailbox
,"INBOX") ?
1528 NIL
: mix_create (NIL
,"INBOX")) != 0L)
1530 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before append",NIL
);
1533 sprintf (tmp
,"Not a MIX-format mailbox: %.80s",mailbox
);
1538 /* get first message */
1539 if (ret
&& MM_APPEND (af
) (stream
,data
,&flags
,&date
,&message
)) {
1540 MAILSTREAM
*astream
;
1544 if ((ret
= ((astream
= mail_open (NIL
,mailbox
,OP_SILENT
)) &&
1546 (((MIXLOCAL
*) astream
->local
)->expok
= T
) &&
1547 (statf
= mix_parse (astream
,&idxf
,LONGT
,NIL
))) ?
1548 LONGT
: NIL
) != 0l) {
1550 unsigned long size
,hdrsize
;
1552 MIXLOCAL
*local
= (MIXLOCAL
*) astream
->local
;
1553 unsigned long seq
= mix_modseq (local
->metaseq
);
1554 /* make sure new modseq fits */
1555 if (local
->indexseq
> seq
) seq
= local
->indexseq
+ 1;
1556 if (local
->statusseq
> seq
) seq
= local
->statusseq
+ 1;
1557 /* calculate size of per-message header */
1558 sprintf (local
->buf
,MSRFMT
,MSGTOK
,(unsigned long) 0,0,0,0,0,0,0,'+',0,0,
1560 hdrsize
= strlen (local
->buf
);
1561 MM_CRITICAL (astream
); /* go critical */
1562 astream
->silent
= T
; /* no events here */
1563 /* open data file */
1564 if ((msgf
= mix_data_open (astream
,&fd
,&size
,hdrsize
+ SIZE (message
))) != NULL
){
1565 appenduid_t au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
);
1566 SEARCHSET
*dst
= au
? mail_newsearchset () : NIL
;
1567 while (ret
&& message
) {/* while good to go and have messages */
1568 errno
= NIL
; /* in case one of these causes failure */
1569 /* guard against zero-length */
1570 if (!(ret
= SIZE (message
)))
1571 MM_LOG ("Append of zero-length message",ERROR
);
1572 else if (date
&& !(ret
= mail_parse_date (&elt
,date
))) {
1573 sprintf (tmp
,"Bad date in append: %.80s",date
);
1577 if (!date
) { /* if date not specified, use now */
1578 internal_date (tmp
);
1579 mail_parse_date (&elt
,tmp
);
1581 ret
= mix_append_msg (astream
,msgf
,flags
,&elt
,message
,dst
,seq
) &&
1582 MM_APPEND (af
) (stream
,data
,&flags
,&date
,&message
);
1586 /* finish write if success */
1587 if (ret
&& (ret
= !fflush (msgf
))) {
1588 fclose (msgf
); /* all good, close the msg file now */
1589 /* write new metadata, index, and status */
1590 local
->metaseq
= local
->indexseq
= local
->statusseq
= seq
;
1591 if ((ret
= (mix_meta_update (astream
) &&
1592 mix_index_update (astream
,idxf
,LONGT
) &&
1593 mix_status_update (astream
,statf
,LONGT
))) && au
) {
1594 (*au
) (mailbox
,astream
->uid_validity
,dst
);
1595 dst
= NIL
; /* don't free this set now */
1598 else { /* failure */
1599 if (errno
) { /* output error message if system call error */
1600 sprintf (tmp
,"Message append failed: %.80s",strerror (errno
));
1603 ftruncate (fd
,size
); /* revert all writes to file*/
1604 close (fd
); /* make sure that fclose doesn't corrupt us */
1605 fclose (msgf
); /* free the stdio resources */
1607 /* flush any set remaining */
1608 mail_free_searchset (&dst
);
1610 else { /* message file open failed */
1611 sprintf (tmp
,"Error opening append message file: %.80s",
1616 MM_NOCRITICAL (astream
); /* release critical */
1618 else MM_LOG ("Can't open append mailbox",ERROR
);
1619 if (statf
) fclose (statf
); /* close status if still open */
1620 if (idxf
) fclose (idxf
); /* close index if still open */
1621 if (astream
) mail_close (astream
);
1626 /* MIX mail append single message
1627 * Accepts: MAIL stream
1628 * flags for new message if non-NIL
1629 * elt with source date if non-NIL
1630 * stringstruct of message text
1631 * searchset to place UID
1633 * Returns: T if success, NIL if failure
1636 long mix_append_msg (MAILSTREAM
*stream
,FILE *f
,char *flags
,MESSAGECACHE
*delt
,
1637 STRING
*msg
,SEARCHSET
*set
,unsigned long seq
)
1641 unsigned long i
,j
,k
,uf
,hoff
;
1644 stream
->kwd_create
= NIL
; /* don't copy unknown keywords */
1645 sf
= mail_parse_flags (stream
,flags
,&uf
);
1646 /* swell the cache */
1647 mail_exists (stream
,++stream
->nmsgs
);
1648 /* assign new UID from metadata */
1649 (elt
= mail_elt (stream
,stream
->nmsgs
))->private.uid
= ++stream
->uid_last
;
1650 elt
->private.mod
= seq
; /* set requested modseq in status */
1651 elt
->rfc822_size
= SIZE (msg
);/* copy message size and date to index */
1652 elt
->year
= delt
->year
; elt
->month
= delt
->month
; elt
->day
= delt
->day
;
1653 elt
->hours
= delt
->hours
; elt
->minutes
= delt
->minutes
;
1654 elt
->seconds
= delt
->seconds
; elt
->zoccident
= delt
->zoccident
;
1655 elt
->zhours
= delt
->zhours
; elt
->zminutes
= delt
->zminutes
;
1657 * Do NOT set elt->valid here! mix_status_update() uses it to determine
1658 * whether a message should be marked as old.
1660 if (sf
&fSEEN
) elt
->seen
= T
; /* copy flags to status */
1661 if (sf
&fDELETED
) elt
->deleted
= T
;
1662 if (sf
&fFLAGGED
) elt
->flagged
= T
;
1663 if (sf
&fANSWERED
) elt
->answered
= T
;
1664 if (sf
&fDRAFT
) elt
->draft
= T
;
1665 elt
->user_flags
|= uf
;
1666 /* message is in new message file */
1667 elt
->private.spare
.data
= LOCAL
->newmsg
;
1669 /* offset to message internal header */
1670 elt
->private.special
.offset
= ftell (f
);
1671 /* build header for message */
1672 fprintf (f
,MSRFMT
,MSGTOK
,elt
->private.uid
,
1673 elt
->year
+ BASEYEAR
,elt
->month
,elt
->day
,
1674 elt
->hours
,elt
->minutes
,elt
->seconds
,
1675 elt
->zoccident
? '-' : '+',elt
->zhours
,elt
->zminutes
,
1677 /* offset to header from internal header */
1678 elt
->private.msg
.header
.offset
= ftell (f
) - elt
->private.special
.offset
;
1679 for (cs
= 0; SIZE (msg
); ) { /* copy message */
1680 if (elt
->private.msg
.header
.text
.size
) {
1681 if (msg
->cursize
) /* blat entire chunk if have it */
1682 for (s
= msg
->curpos
,j
= msg
->cursize
; j
; s
+= k
, j
-= k
)
1683 if (!(k
= fwrite (s
,1,j
,f
))) return NIL
;
1684 SETPOS (msg
,GETPOS (msg
) + msg
->cursize
);
1686 else { /* still searching for delimiter */
1687 c
= 0xff & SNX (msg
); /* get source character */
1688 if (putc (c
,f
) == EOF
) return NIL
;
1689 switch (cs
) { /* decide what to do based on state */
1690 case 0: /* previous char ordinary */
1691 if (c
== '\015') cs
= 1;/* advance if CR */
1693 case 1: /* previous CR, advance if LF */
1694 cs
= (c
== '\012') ? 2 : 0;
1696 case 2: /* previous CRLF, advance if CR */
1697 cs
= (c
== '\015') ? 3 : 0;
1699 case 3: /* previous CRLFCR, done if LF */
1700 if (c
== '\012') elt
->private.msg
.header
.text
.size
=
1701 elt
->rfc822_size
- SIZE (msg
);
1702 cs
= 0; /* reset mechanism */
1707 /* if no delimiter, header is entire msg */
1708 if (!elt
->private.msg
.header
.text
.size
)
1709 elt
->private.msg
.header
.text
.size
= elt
->rfc822_size
;
1710 /* add this message to set */
1711 mail_append_set (set
,elt
->private.uid
);
1712 return LONGT
; /* success */
1715 /* MIX mail read metadata, index, and status
1716 * Accepts: MAIL stream
1717 * returned index file
1718 * index file flags (non-NIL if want to add/remove messages)
1719 * status file flags (non-NIL if want to update elt->valid and old)
1720 * Returns: open status file, or NIL if failure
1722 * Note that this routine can return an open index file even if it fails!
1725 static char *shortmsg
=
1726 "message %lu (UID=%.08lx) truncated by %lu byte(s) (%lu < %lu)";
1728 FILE *mix_parse (MAILSTREAM
*stream
,FILE **idxf
,long iflags
,long sflags
)
1735 short metarepairneeded
= 0;
1736 short indexrepairneeded
= 0;
1737 short silent
= stream
->silent
;
1738 *idxf
= NIL
; /* in case error */
1739 /* readonly means no updates */
1740 if (stream
->rdonly
) iflags
= sflags
= NIL
;
1741 /* open index file */
1742 if ((fd
= open (LOCAL
->index
,iflags
? O_RDWR
: O_RDONLY
,NIL
)) < 0)
1743 MM_LOG ("Error opening mix index file",ERROR
);
1744 /* acquire exclusive access and FILE */
1745 else if (!flock (fd
,iflags
? LOCK_EX
: LOCK_SH
) &&
1746 !(*idxf
= fdopen (fd
,iflags
? "r+b" : "rb"))) {
1747 MM_LOG ("Error obtaining stream on mix index file",ERROR
);
1748 flock (fd
,LOCK_UN
); /* relinquish lock */
1752 /* slurp metadata */
1753 else if ((s
= mix_meta_slurp (stream
,&i
)) != NULL
) {
1754 unsigned long j
= 0; /* non-zero if UIDVALIDITY/UIDLAST changed */
1755 if (i
!= LOCAL
->metaseq
) { /* metadata changed? */
1757 LOCAL
->metaseq
= i
; /* note new metadata sequence */
1758 while (s
&& *s
) { /* parse entire metadata file */
1759 /* locate end of line */
1760 if ((s
= strstr (t
= s
,"\015\012")) != NULL
) {
1761 *s
= '\0'; /* tie off line */
1762 s
+= 2; /* skip past CRLF */
1763 switch (*t
++) { /* parse line */
1764 case 'V': /* UIDVALIDITY */
1765 if (!isxdigit (*t
) || !(i
= strtoul (t
,&t
,16))) {
1766 MM_LOG ("Error in mix metadata file UIDVALIDITY record",ERROR
);
1767 return NIL
; /* give up */
1769 if (i
!= stream
->uid_validity
) j
= stream
->uid_validity
= i
;
1771 case 'L': /* new UIDLAST */
1772 if (!isxdigit (*t
)) {
1773 MM_LOG ("Error in mix metadata file UIDLAST record",ERROR
);
1774 return NIL
; /* give up */
1776 if ((i
= strtoul (t
,&t
,16)) != stream
->uid_last
)
1777 j
= stream
->uid_last
= i
;
1779 case 'N': /* new message file */
1780 if (!isxdigit (*t
)) {
1781 MM_LOG ("Error in mix metadata file new msg record",ERROR
);
1782 return NIL
; /* give up */
1784 if ((i
= strtoul (t
,&t
,16)) != stream
->uid_last
)
1787 case 'K': /* new keyword list */
1788 for (i
= 0; t
&& *t
&& (i
< NUSERFLAGS
); ++i
) {
1789 if ((t
= strchr (k
= t
,' ')) != NULL
) *t
++ = '\0';
1790 /* make sure keyword non-empty */
1791 if (*k
&& (strlen (k
) <= MAXUSERFLAG
)) {
1792 /* in case value changes (shouldn't happen) */
1793 if (stream
->user_flags
[i
] && strcmp (stream
->user_flags
[i
],k
)){
1794 char tmp
[MAILTMPLEN
];
1795 sprintf (tmp
,"flag rename old=%.80s new=%.80s",
1796 stream
->user_flags
[i
],k
);
1798 fs_give ((void **) &stream
->user_flags
[i
]);
1800 if (!stream
->user_flags
[i
]) stream
->user_flags
[i
] = cpystr (k
);
1802 else break; /* empty keyword */
1804 if ((i
< NUSERFLAGS
) && stream
->user_flags
[i
]) {
1805 MM_LOG ("Error in mix metadata file keyword record",ERROR
);
1806 return NIL
; /* give up */
1808 else if (i
== NUSERFLAGS
) stream
->kwd_create
= NIL
;
1812 if (t
&& *t
) { /* junk in line */
1813 MM_LOG ("Error in mix metadata record",ERROR
);
1814 return NIL
; /* give up */
1820 if (!(i
= mix_read_sequence (*idxf
)) || (i
< LOCAL
->indexseq
)) {
1821 MM_LOG ("Error in mix index file sequence record",ERROR
);
1822 return NIL
; /* give up */
1824 /* sequence changed from last time? */
1825 else if (j
|| (i
> LOCAL
->indexseq
)) {
1826 unsigned long prevuid
= 0;
1827 unsigned long uid
,nmsgs
,curfile
,curfilesize
,curpos
;
1828 char *t
,*msg
,tmp
[MAILTMPLEN
];
1829 /* start with no messages */
1830 curfile
= curfilesize
= curpos
= nmsgs
= 0;
1831 /* update sequence iff expunging OK */
1832 if (LOCAL
->expok
) LOCAL
->indexseq
= i
;
1834 while ((s
= mix_read_record (*idxf
,LOCAL
->buf
,LOCAL
->buflen
,"index")) &&
1837 case ':': /* message record */
1838 if (!(isxdigit (*++s
) && (uid
= strtoul (s
,&t
,16)))) msg
= "UID";
1839 else if (!((*t
++ == ':') && isdigit (*t
) && isdigit (t
[1]) &&
1840 isdigit (t
[2]) && isdigit (t
[3]) && isdigit (t
[4]) &&
1841 isdigit (t
[5]) && isdigit (t
[6]) && isdigit (t
[7]) &&
1842 isdigit (t
[8]) && isdigit (t
[9]) && isdigit (t
[10]) &&
1843 isdigit (t
[11]) && isdigit (t
[12]) && isdigit (t
[13]) &&
1844 ((t
[14] == '+') || (t
[14] == '-')) &&
1845 isdigit (t
[15]) && isdigit (t
[16]) && isdigit (t
[17]) &&
1846 isdigit (t
[18]))) msg
= "internaldate";
1847 else if ((*(s
= t
+19) != ':') || !isxdigit (*++s
)) msg
= "size";
1849 unsigned int y
= (((*t
- '0') * 1000) + ((t
[1] - '0') * 100) +
1850 ((t
[2] - '0') * 10) + t
[3] - '0') - BASEYEAR
;
1851 unsigned int m
= ((t
[4] - '0') * 10) + t
[5] - '0';
1852 unsigned int d
= ((t
[6] - '0') * 10) + t
[7] - '0';
1853 unsigned int hh
= ((t
[8] - '0') * 10) + t
[9] - '0';
1854 unsigned int mm
= ((t
[10] - '0') * 10) + t
[11] - '0';
1855 unsigned int ss
= ((t
[12] - '0') * 10) + t
[13] - '0';
1856 unsigned int z
= (t
[14] == '-') ? 1 : 0;
1857 unsigned int zh
= ((t
[15] - '0') * 10) + t
[16] - '0';
1858 unsigned int zm
= ((t
[17] - '0') * 10) + t
[18] - '0';
1859 unsigned long size
= strtoul (s
,&s
,16);
1860 if ((*s
++ == ':') && isxdigit (*s
)) {
1861 unsigned long file
= strtoul (s
,&s
,16);
1862 if ((*s
++ == ':') && isxdigit (*s
)) {
1863 unsigned long pos
= strtoul (s
,&s
,16);
1864 if ((*s
++ == ':') && isxdigit (*s
)) {
1865 unsigned long hpos
= strtoul (s
,&s
,16);
1866 if ((*s
++ == ':') && isxdigit (*s
)) {
1867 unsigned long hsiz
= strtoul (s
,&s
,16);
1868 if (uid
> stream
->uid_last
) {
1869 sprintf (tmp
,"mix index invalid UID (%08lx < %08lx)",
1870 uid
,stream
->uid_last
);
1871 if (stream
->rdonly
) {
1875 strcat (tmp
,", repaired");
1877 stream
->uid_last
= uid
;
1878 metarepairneeded
= T
;
1881 /* ignore expansion values */
1885 sprintf (tmp
,"mix index backwards UID: %lx",uid
);
1890 ++nmsgs
; /* this is another message */
1891 /* within current known range of messages? */
1892 while (nmsgs
<= stream
->nmsgs
) {
1893 /* yes, get corresponding elt */
1894 elt
= mail_elt (stream
,nmsgs
);
1895 /* existing message with matching data? */
1896 if (uid
== elt
->private.uid
) {
1897 /* beware of Dracula's resurrection */
1898 if (elt
->private.ghost
) {
1899 sprintf (tmp
,"mix index data unexpunged UID: %lx",
1904 /* also of static data changing */
1905 if ((size
!= elt
->rfc822_size
) ||
1906 (file
!= elt
->private.spare
.data
) ||
1907 (pos
!= elt
->private.special
.offset
) ||
1908 (hpos
!= elt
->private.msg
.header
.offset
) ||
1909 (hsiz
!= elt
->private.msg
.header
.text
.size
) ||
1910 (y
!= elt
->year
) || (m
!= elt
->month
) ||
1911 (d
!= elt
->day
) || (hh
!= elt
->hours
) ||
1912 (mm
!= elt
->minutes
) || (ss
!= elt
->seconds
) ||
1913 (z
!= elt
->zoccident
) || (zh
!= elt
->zhours
) ||
1914 (zm
!= elt
->zminutes
)) {
1915 sprintf (tmp
,"mix index data mismatch: %lx",uid
);
1921 /* existing msg with lower UID is expunged */
1922 else if (uid
> elt
->private.uid
) {
1923 if (LOCAL
->expok
) mail_expunged (stream
,nmsgs
);
1924 else {/* message expunged, but not yet for us */
1926 elt
->private.ghost
= T
;
1929 else { /* unexpected message record */
1930 sprintf (tmp
,"mix index UID mismatch (%lx < %lx)",
1931 uid
,elt
->private.uid
);
1937 /* time to create a new message? */
1938 if (nmsgs
> stream
->nmsgs
) {
1939 /* defer announcing until later */
1941 mail_exists (stream
,nmsgs
);
1942 stream
->silent
= silent
;
1943 (elt
= mail_elt (stream
,nmsgs
))->recent
= T
;
1944 elt
->private.uid
= uid
; elt
->rfc822_size
= size
;
1945 elt
->private.spare
.data
= file
;
1946 elt
->private.special
.offset
= pos
;
1947 elt
->private.msg
.header
.offset
= hpos
;
1948 elt
->private.msg
.header
.text
.size
= hsiz
;
1949 elt
->year
= y
; elt
->month
= m
; elt
->day
= d
;
1950 elt
->hours
= hh
; elt
->minutes
= mm
;
1951 elt
->seconds
= ss
; elt
->zoccident
= z
;
1952 elt
->zhours
= zh
; elt
->zminutes
= zm
;
1953 /* message in same file? */
1954 if (curfile
== file
) {
1956 MESSAGECACHE
*plt
= mail_elt (stream
,elt
->msgno
-1);
1957 /* uh-oh, calculate delta? */
1959 sprintf (tmp
,shortmsg
,plt
->msgno
,plt
->private.uid
,
1961 /* possible to fix? */
1962 if (!stream
->rdonly
&& LOCAL
->expok
&&
1963 (i
< plt
->rfc822_size
)) {
1964 plt
->rfc822_size
-= i
;
1965 if (plt
->rfc822_size
<
1966 plt
->private.msg
.header
.text
.size
)
1967 plt
->private.msg
.header
.text
.size
=
1969 strcat (tmp
,", repaired");
1970 indexrepairneeded
= T
;
1975 else { /* new file, restart */
1976 if (stat (mix_file_data (LOCAL
->buf
,stream
->mailbox
,
1977 curfile
= file
),&sbuf
)) {
1978 sprintf (tmp
,"Missing mix data file: %.500s",
1984 curfilesize
= sbuf
.st_size
;
1987 /* position of message in file */
1988 curpos
= pos
+ elt
->private.msg
.header
.offset
+
1991 if (curfilesize
< curpos
) {
1992 /* uh-oh, calculate delta? */
1993 i
= curpos
- curfilesize
;
1994 sprintf (tmp
,shortmsg
,elt
->msgno
,elt
->private.uid
,
1995 i
,curfilesize
,curpos
);
1996 /* possible to fix? */
1997 if (!stream
->rdonly
&& LOCAL
->expok
&&
1998 (i
< elt
->rfc822_size
)) {
1999 elt
->rfc822_size
-= i
;
2000 if (elt
->rfc822_size
<
2001 elt
->private.msg
.header
.text
.size
)
2002 elt
->private.msg
.header
.text
.size
=
2004 strcat (tmp
,", repaired");
2005 indexrepairneeded
= T
;
2012 else msg
= "expansion";
2014 else msg
= "header size";
2016 else msg
= "header position";
2018 else msg
= "message position";
2022 sprintf (tmp
,"Error in %s in mix index file: %.500s",msg
,s
);
2026 sprintf (tmp
,"Unknown record in mix index file: %.500s",s
);
2030 if (!s
) return NIL
; /* barfage from mix_read_record() */
2031 /* expunge trailing messages not in index */
2032 if (LOCAL
->expok
) while (nmsgs
< stream
->nmsgs
)
2033 mail_expunged (stream
,stream
->nmsgs
);
2036 /* repair metadata and index if needed */
2037 if ((metarepairneeded
? mix_meta_update (stream
) : T
) &&
2038 (indexrepairneeded
? mix_index_update (stream
,*idxf
,NIL
) : T
)) {
2041 unsigned long uid
,uf
,sf
,mod
;
2044 /* open status file */
2045 if ((fd
= open (LOCAL
->status
,
2046 stream
->rdonly
? O_RDONLY
: O_RDWR
,NIL
)) < 0)
2047 MM_LOG ("Error opening mix status file",ERROR
);
2048 /* acquire exclusive access and FILE */
2049 else if (!flock (fd
,stream
->rdonly
? LOCK_SH
: LOCK_EX
) &&
2050 !(statf
= fdopen (fd
,stream
->rdonly
? "rb" : "r+b"))) {
2051 MM_LOG ("Error obtaining stream on mix status file",ERROR
);
2052 flock (fd
,LOCK_UN
); /* relinquish lock */
2056 else if (!(i
= mix_read_sequence (statf
)) ||
2057 ((i
< LOCAL
->statusseq
) && stream
->nmsgs
&& (i
!= 1))) {
2058 sprintf (LOCAL
->buf
,
2059 "Error in mix status sequence record, i=%lx, seq=%lx",
2060 i
,LOCAL
->statusseq
);
2061 MM_LOG (LOCAL
->buf
,ERROR
);
2063 /* sequence changed from last time? */
2064 else if (i
!= LOCAL
->statusseq
) {
2065 /* update sequence, get first elt */
2066 if (i
> LOCAL
->statusseq
) LOCAL
->statusseq
= i
;
2067 if (stream
->nmsgs
) {
2068 elt
= mail_elt (stream
,i
= 1);
2070 /* read message records */
2071 while ((t
= s
= mix_read_record (statf
,LOCAL
->buf
,LOCAL
->buflen
,
2072 "status")) && *s
&& (*s
++ == ':') &&
2074 uid
= strtoul (s
,&s
,16);
2075 if ((*s
++ == ':') && isxdigit (*s
)) {
2076 uf
= strtoul (s
,&s
,16);
2077 if ((*s
++ == ':') && isxdigit (*s
)) {
2078 sf
= strtoul (s
,&s
,16);
2079 if ((*s
++ == ':') && isxdigit (*s
)) {
2080 mod
= strtoul (s
,&s
,16);
2081 /* ignore expansion values */
2083 /* need to move ahead to next elt? */
2084 while ((uid
> elt
->private.uid
) && (i
< stream
->nmsgs
))
2085 elt
= mail_elt (stream
,++i
);
2086 /* update elt if altered */
2087 if ((uid
== elt
->private.uid
) &&
2088 (!elt
->valid
|| (mod
!= elt
->private.mod
))) {
2089 elt
->user_flags
= uf
;
2090 elt
->private.mod
= mod
;
2091 elt
->seen
= (sf
& fSEEN
) ? T
: NIL
;
2092 elt
->deleted
= (sf
& fDELETED
) ? T
: NIL
;
2093 elt
->flagged
= (sf
& fFLAGGED
) ? T
: NIL
;
2094 elt
->answered
= (sf
& fANSWERED
) ? T
: NIL
;
2095 elt
->draft
= (sf
& fDRAFT
) ? T
: NIL
;
2096 /* announce if altered existing message */
2097 if (elt
->valid
) MM_FLAGS (stream
,elt
->msgno
);
2098 /* first time, is old message? */
2099 else if (sf
& fOLD
) {
2100 /* yes, clear recent and set valid */
2104 /* recent, allowed to update its status? */
2106 /* yes, set valid and check in status */
2108 elt
->private.mod
= mix_modseq (elt
->private.mod
);
2111 /* leave valid unset and recent if sflags not set */
2113 continue; /* everything looks good */
2118 break; /* error somewhere */
2121 if (t
&& *t
) { /* non-null means bogus record */
2122 char msg
[MAILTMPLEN
];
2123 sprintf (msg
,"Error in mix status file message record%s: %.80s",
2124 stream
->rdonly
? "" : ", fixing",t
);
2126 /* update it if not readonly */
2127 if (!stream
->rdonly
) updatep
= T
;
2129 if (updatep
) { /* need to update? */
2130 LOCAL
->statusseq
= mix_modseq (LOCAL
->statusseq
);
2131 mix_status_update (stream
,statf
,LONGT
);
2137 if (statf
) { /* still happy? */
2139 stream
->silent
= silent
; /* now notify upper level */
2140 mail_exists (stream
,stream
->nmsgs
);
2141 for (i
= 1, j
= 0; i
<= stream
->nmsgs
; ++i
)
2142 if (mail_elt (stream
,i
)->recent
) ++j
;
2143 mail_recent (stream
,j
);
2148 /* MIX metadata file routines */
2150 /* MIX read metadata
2151 * Accepts: MAIL stream
2152 * return pointer for modseq
2153 * Returns: pointer to metadata after modseq or NIL if failure
2156 char *mix_meta_slurp (MAILSTREAM
*stream
,unsigned long *seq
)
2161 if (fstat (LOCAL
->mfd
,&sbuf
))
2162 MM_LOG ("Error obtaining size of mix metadata file",ERROR
);
2163 if (sbuf
.st_size
> LOCAL
->buflen
) {
2164 /* should be just a few dozen bytes */
2165 if (sbuf
.st_size
> METAMAX
) fatal ("absurd mix metadata file size");
2166 fs_give ((void **) &LOCAL
->buf
);
2167 LOCAL
->buf
= (char *) fs_get ((LOCAL
->buflen
= sbuf
.st_size
) + 1);
2169 /* read current metadata file */
2170 LOCAL
->buf
[sbuf
.st_size
] = '\0';
2171 if (lseek (LOCAL
->mfd
,0,L_SET
) ||
2172 (read (LOCAL
->mfd
,s
= LOCAL
->buf
,sbuf
.st_size
) != sbuf
.st_size
))
2173 MM_LOG ("Error reading mix metadata file",ERROR
);
2174 else if ((*s
!= 'S') || !isxdigit (s
[1]) ||
2175 ((*seq
= strtoul (s
+1,&s
,16)) < LOCAL
->metaseq
) ||
2176 (*s
++ != '\015') || (*s
++ != '\012'))
2177 MM_LOG ("Error in mix metadata file sequence record",ERROR
);
2182 /* MIX update metadata
2183 * Accepts: MAIL stream
2184 * Returns: T on success, NIL if error
2186 * Index MUST be locked!!
2189 long mix_meta_update (MAILSTREAM
*stream
)
2192 /* do nothing if stream readonly */
2193 if (stream
->rdonly
) ret
= LONGT
;
2195 unsigned char c
,*s
,*ss
,*t
;
2197 /* The worst-case metadata is limited to:
2198 * 4 * (1 + 8 + 2) + (NUSERFLAGS * (MAXUSERFLAG + 1))
2199 * which comes out to 1994 octets. This is much smaller than the normal
2200 * CHUNKSIZE definition of 64K, and CHUNKSIZE is the smallest size of
2203 * If more stuff gets added to the metadata, or if you change the value
2204 * of NUSERFLAGS, MAXUSERFLAG or CHUNKSIZE, be sure to recalculate the
2207 sprintf (LOCAL
->buf
,SEQFMT
,LOCAL
->metaseq
= mix_modseq (LOCAL
->metaseq
));
2208 sprintf (LOCAL
->buf
+ strlen (LOCAL
->buf
),MTAFMT
,
2209 stream
->uid_validity
,stream
->uid_last
,LOCAL
->newmsg
);
2210 for (i
= 0, c
= 'K', s
= ss
= LOCAL
->buf
+ strlen (LOCAL
->buf
);
2211 (i
< NUSERFLAGS
) && (t
= stream
->user_flags
[i
]); ++i
) {
2212 if (!*t
) fatal ("impossible empty keyword");
2213 *s
++ = c
; /* write delimiter */
2214 while (*t
) *s
++ = *t
++; /* write keyword */
2215 c
= ' '; /* delimiter is now space */
2217 if (s
!= ss
) { /* tie off keywords line */
2218 *s
++ = '\015'; *s
++ = '\012';
2220 /* calculate length of metadata */
2221 if ((i
= s
- LOCAL
->buf
) > LOCAL
->buflen
)
2222 fatal ("impossible buffer overflow");
2223 lseek (LOCAL
->mfd
,0,L_SET
); /* rewind file */
2224 /* write new metadata */
2225 ret
= (write (LOCAL
->mfd
,LOCAL
->buf
,i
) == i
) ? LONGT
: NIL
;
2226 ftruncate (LOCAL
->mfd
,i
); /* and tie off at that point */
2231 /* MIX index file routines */
2235 * Accepts: MAIL stream
2237 * expansion check flag
2238 * Returns: T on success, NIL if error
2241 long mix_index_update (MAILSTREAM
*stream
,FILE *idxf
,long flag
)
2245 if (!stream
->rdonly
) { /* do nothing if stream readonly */
2246 if (flag
) { /* need to do expansion check? */
2247 char tmp
[MAILTMPLEN
];
2250 /* calculate file size we need */
2251 for (i
= 1, size
= 0; i
<= stream
->nmsgs
; ++i
)
2252 if (!mail_elt (stream
,i
)->private.ghost
) ++size
;
2253 if (size
) { /* Winston Smith's first dairy entry */
2254 sprintf (tmp
,IXRFMT
,(unsigned long) 0,14,4,4,13,0,0,'+',0,0,
2255 (unsigned long) 0,(unsigned long) 0,(unsigned long) 0,
2256 (unsigned long) 0,(unsigned long) 0);
2257 size
*= strlen (tmp
);
2259 /* calculate file size we need */
2260 sprintf (tmp
,SEQFMT
,LOCAL
->indexseq
);
2261 size
+= strlen (tmp
);
2262 /* get current file size */
2263 if (fstat (fileno (idxf
),&sbuf
)) {
2264 MM_LOG ("Error getting size of mix index file",ERROR
);
2267 /* need to write additional space? */
2268 else if (sbuf
.st_size
< size
) {
2269 void *buf
= fs_get (size
-= sbuf
.st_size
);
2270 memset (buf
,0,size
);
2271 if (fseek (idxf
,0,SEEK_END
) || (fwrite (buf
,1,size
,idxf
) != size
) ||
2273 fseek (idxf
,sbuf
.st_size
,SEEK_SET
);
2274 ftruncate (fileno (idxf
),sbuf
.st_size
);
2275 MM_LOG ("Error extending mix index file",ERROR
);
2278 fs_give ((void **) &buf
);
2282 if (ret
) { /* if still good to go */
2283 rewind (idxf
); /* let's start at the very beginning */
2284 /* write modseq first */
2285 fprintf (idxf
,SEQFMT
,LOCAL
->indexseq
);
2286 /* then write all messages */
2287 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); i
++) {
2288 MESSAGECACHE
*elt
= mail_elt (stream
,i
);
2289 if (!elt
->private.ghost
)/* only write living messages */
2290 fprintf (idxf
,IXRFMT
,elt
->private.uid
,
2291 elt
->year
+ BASEYEAR
,elt
->month
,elt
->day
,
2292 elt
->hours
,elt
->minutes
,elt
->seconds
,
2293 elt
->zoccident
? '-' : '+',elt
->zhours
,elt
->zminutes
,
2294 elt
->rfc822_size
,elt
->private.spare
.data
,
2295 elt
->private.special
.offset
,
2296 elt
->private.msg
.header
.offset
,
2297 elt
->private.msg
.header
.text
.size
);
2298 if (ferror (idxf
)) {
2299 MM_LOG ("Error updating mix index file",ERROR
);
2303 if (fflush (idxf
)) {
2304 MM_LOG ("Error flushing mix index file",ERROR
);
2307 if (ret
) ftruncate (fileno (idxf
),ftell (idxf
));
2313 /* MIX status file routines */
2316 /* MIX update status
2317 * Accepts: MAIL stream
2318 * pointer to open FILE
2319 * expansion check flag
2320 * Returns: T on success, NIL if error
2323 long mix_status_update (MAILSTREAM
*stream
,FILE *statf
,long flag
)
2326 char tmp
[MAILTMPLEN
];
2328 if (!stream
->rdonly
) { /* do nothing if stream readonly */
2329 if (flag
) { /* need to do expansion check? */
2330 char tmp
[MAILTMPLEN
];
2333 /* calculate file size we need */
2334 for (i
= 1, size
= 0; i
<= stream
->nmsgs
; ++i
)
2335 if (!mail_elt (stream
,i
)->private.ghost
) ++size
;
2336 if (size
) { /* number of living messages */
2337 sprintf (tmp
,STRFMT
,(unsigned long) 0,(unsigned long) 0,0,
2339 size
*= strlen (tmp
);
2341 sprintf (tmp
,SEQFMT
,LOCAL
->statusseq
);
2342 size
+= strlen (tmp
);
2343 /* get current file size */
2344 if (fstat (fileno (statf
),&sbuf
)) {
2345 MM_LOG ("Error getting size of mix status file",ERROR
);
2348 /* need to write additional space? */
2349 else if (sbuf
.st_size
< size
) {
2350 void *buf
= fs_get (size
-= sbuf
.st_size
);
2351 memset (buf
,0,size
);
2352 if (fseek (statf
,0,SEEK_END
) || (fwrite (buf
,1,size
,statf
) != size
) ||
2354 fseek (statf
,sbuf
.st_size
,SEEK_SET
);
2355 ftruncate (fileno (statf
),sbuf
.st_size
);
2356 MM_LOG ("Error extending mix status file",ERROR
);
2359 fs_give ((void **) &buf
);
2363 if (ret
) { /* if still good to go */
2364 rewind (statf
); /* let's start at the very beginning */
2365 /* write sequence */
2366 fprintf (statf
,SEQFMT
,LOCAL
->statusseq
);
2367 /* write message status records */
2368 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); ++i
) {
2369 MESSAGECACHE
*elt
= mail_elt (stream
,i
);
2370 /* make sure all messages have a modseq */
2371 if (!elt
->private.mod
) elt
->private.mod
= LOCAL
->statusseq
;
2372 if (!elt
->private.ghost
)/* only write living messages */
2373 fprintf (statf
,STRFMT
,elt
->private.uid
,elt
->user_flags
,
2374 (fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
2375 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
2376 (fDRAFT
* elt
->draft
) + (elt
->valid
? fOLD
: NIL
),
2378 if (ferror (statf
)) {
2379 sprintf (tmp
,"Error updating mix status file: %.80s",
2385 if (ret
&& fflush (statf
)) {
2386 MM_LOG ("Error flushing mix status file",ERROR
);
2389 if (ret
) ftruncate (fileno (statf
),ftell (statf
));
2395 /* MIX data file routines */
2398 /* MIX open data file
2399 * Accepts: MAIL stream
2400 * pointer to returned fd if success
2401 * pointer to returned size if success
2402 * size of new data to be added
2403 * Returns: open FILE, or NIL if failure
2405 * The curend test assumes that the last message of the mailbox is the furthest
2406 * point that the current data file extends, and thus that is all that needs to
2407 * be tested for short file prevention.
2410 FILE *mix_data_open (MAILSTREAM
*stream
,int *fd
,long *size
,
2411 unsigned long newsize
)
2415 MESSAGECACHE
*elt
= stream
->nmsgs
? mail_elt (stream
,stream
->nmsgs
) : NIL
;
2416 unsigned long curend
= (elt
&& (elt
->private.spare
.data
== LOCAL
->newmsg
)) ?
2417 elt
->private.special
.offset
+ elt
->private.msg
.header
.offset
+
2418 elt
->rfc822_size
: 0;
2419 /* allow create if curend 0 */
2420 if ((*fd
= open (mix_file_data (LOCAL
->buf
,stream
->mailbox
,LOCAL
->newmsg
),
2421 O_RDWR
| (curend
? NIL
: O_CREAT
),NIL
)) >= 0) {
2422 fstat (*fd
,&sbuf
); /* get current file size */
2423 /* can we use this file? */
2424 if ((curend
<= sbuf
.st_size
) &&
2425 (!sbuf
.st_size
|| ((sbuf
.st_size
+ newsize
) <= MIXDATAROLL
)))
2426 *size
= sbuf
.st_size
; /* yes, return current size */
2427 else { /* short file or becoming too long */
2428 if (curend
> sbuf
.st_size
) {
2429 char tmp
[MAILTMPLEN
];
2430 sprintf (tmp
,"short mix message file %.08lx (%ld > %ld), rolling",
2431 LOCAL
->newmsg
,curend
,(unsigned long) sbuf
.st_size
);
2432 MM_LOG (tmp
,WARN
); /* shouldn't happen */
2434 close (*fd
); /* roll to a new file */
2436 while ((*fd
= open (mix_file_data
2437 (LOCAL
->buf
,stream
->mailbox
,
2438 LOCAL
->newmsg
= mix_modseq (LOCAL
->newmsg
)),
2439 O_RDWR
| O_CREAT
| O_EXCL
,sbuf
.st_mode
)) < 0) {
2441 case EEXIST
: /* always retry if path exists or interrupt */
2445 default: /* probably EDQUOT */
2447 char tmp
[MAILTMPLEN
];
2448 sprintf (tmp
,"data file %.08lx creation failure: %.80s",
2449 LOCAL
->newmsg
,strerror (errno
));
2450 MM_LOG (tmp
,ERROR
); /* shouldn't happen */
2455 *size
= 0; /* brand new file */
2456 fchmod (*fd
,sbuf
.st_mode
);/* with same mode as previous file */
2459 if (*fd
>= 0) { /* have a data file? */
2460 /* yes, get stdio and set position */
2461 if ((msgf
= fdopen (*fd
,"r+b")) != NULL
)fseek (msgf
,*size
,SEEK_SET
);
2462 else close (*fd
); /* fdopen() failed? */
2464 return msgf
; /* return results */
2467 /* MIX open sortcache
2468 * Accepts: MAIL stream
2469 * Returns: open FILE, or NIL if failure or could only get readonly sortcache
2472 FILE *mix_sortcache_open (MAILSTREAM
*stream
)
2475 unsigned long i
,uid
,sentdate
,fromlen
,tolen
,cclen
,subjlen
,msgidlen
,reflen
;
2476 char *s
,*t
,*msg
,tmp
[MAILTMPLEN
];
2483 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
2484 fstat (LOCAL
->mfd
,&sbuf
);
2485 if (!stream
->nmsgs
); /* do nothing if mailbox empty */
2486 /* open sortcache file */
2487 else if (((fd
= open (LOCAL
->sortcache
,O_RDWR
|O_CREAT
,sbuf
.st_mode
)) < 0) &&
2488 !(rdonly
= ((fd
= open (LOCAL
->sortcache
,O_RDONLY
,NIL
)) >= 0)))
2489 MM_LOG ("Error opening mix sortcache file",WARN
);
2490 /* acquire lock and FILE */
2491 else if (!flock (fd
,rdonly
? LOCK_SH
: LOCK_EX
) &&
2492 !(srtcf
= fdopen (fd
,rdonly
? "rb" : "r+b"))) {
2493 MM_LOG ("Error obtaining stream on mix sortcache file",WARN
);
2494 flock (fd
,LOCK_UN
); /* relinquish lock */
2497 else if (!(i
= mix_read_sequence (srtcf
)) || (i
< LOCAL
->sortcacheseq
))
2498 MM_LOG ("Error in mix sortcache file sequence record",WARN
);
2499 /* sequence changed from last time? */
2500 else if (i
> LOCAL
->sortcacheseq
) {
2501 LOCAL
->sortcacheseq
= i
; /* update sequence */
2502 while ((s
= t
= mix_read_record (srtcf
,LOCAL
->buf
,LOCAL
->buflen
,
2503 "sortcache")) && *s
&&
2504 (msg
= "uid") && (*s
++ == ':') && isxdigit (*s
)) {
2505 uid
= strtoul (s
,&s
,16);
2506 if ((*s
++ == ':') && isxdigit (*s
)) {
2507 sentdate
= strtoul (s
,&s
,16);
2508 if ((*s
++ == ':') && isxdigit (*s
)) {
2509 fromlen
= strtoul (s
,&s
,16);
2510 if ((*s
++ == ':') && isxdigit (*s
)) {
2511 tolen
= strtoul (s
,&s
,16);
2512 if ((*s
++ == ':') && isxdigit (*s
)) {
2513 cclen
= strtoul (s
,&s
,16);
2514 if ((*s
++ == ':') && ((*s
== 'R') || (*s
== ' ')) &&
2516 refwd
= (*s
++ == 'R') ? T
: NIL
;
2517 subjlen
= strtoul (s
,&s
,16);
2518 if ((*s
++ == ':') && isxdigit (*s
)) {
2519 msgidlen
= strtoul (s
,&s
,16);
2520 if ((*s
++ == ':') && isxdigit (*s
)) {
2521 reflen
= strtoul (s
,&s
,16);
2522 /* ignore expansion values */
2525 if ((i
= mail_msgno (stream
,uid
)) != 0L) {
2526 sc
= (SORTCACHE
*) (*mc
) (stream
,i
,CH_SORTCACHE
);
2527 sc
->size
= (elt
= mail_elt (stream
,i
))->rfc822_size
;
2528 sc
->date
= sentdate
;
2529 sc
->arrival
= elt
->day
? mail_longdate (elt
) : 1;
2530 if (refwd
) sc
->refwd
= T
;
2532 if (sc
->from
) fseek (srtcf
,fromlen
+ 2,SEEK_CUR
);
2533 else if ((getc (srtcf
) != 'F') ||
2534 (fread (sc
->from
= (char *) fs_get(fromlen
),
2535 1,fromlen
-1,srtcf
) != (fromlen
-1))||
2536 (sc
->from
[fromlen
-1] = '\0') ||
2537 (getc (srtcf
) != '\015') ||
2538 (getc (srtcf
) != '\012')) {
2544 if (sc
->to
) fseek (srtcf
,tolen
+ 2,SEEK_CUR
);
2545 else if ((getc (srtcf
) != 'T') ||
2546 (fread (sc
->to
= (char *) fs_get (tolen
),
2547 1,tolen
-1,srtcf
) != (tolen
- 1)) ||
2548 (sc
->to
[tolen
-1] = '\0') ||
2549 (getc (srtcf
) != '\015') ||
2550 (getc (srtcf
) != '\012')) {
2556 if (sc
->cc
) fseek (srtcf
,cclen
+ 2,SEEK_CUR
);
2557 else if ((getc (srtcf
) != 'C') ||
2558 (fread (sc
->cc
= (char *) fs_get (cclen
),
2559 1,cclen
-1,srtcf
) != (cclen
- 1)) ||
2560 (sc
->cc
[cclen
-1] = '\0') ||
2561 (getc (srtcf
) != '\015') ||
2562 (getc (srtcf
) != '\012')) {
2568 if (sc
->subject
) fseek (srtcf
,subjlen
+ 2,SEEK_CUR
);
2569 else if ((getc (srtcf
) != 'S') ||
2570 (fread (sc
->subject
=
2571 (char *) fs_get (subjlen
),1,
2572 subjlen
-1,srtcf
) != (subjlen
-1))||
2573 (sc
->subject
[subjlen
-1] = '\0') ||
2574 (getc (srtcf
) != '\015') ||
2575 (getc (srtcf
) != '\012')) {
2576 msg
= "subject data";
2583 fseek (srtcf
,msgidlen
+ 2,SEEK_CUR
);
2584 else if ((getc (srtcf
) != 'M') ||
2585 (fread (sc
->message_id
=
2586 (char *) fs_get (msgidlen
),1,
2587 msgidlen
-1,srtcf
) != (msgidlen
-1))||
2588 (sc
->message_id
[msgidlen
-1] = '\0') ||
2589 (getc (srtcf
) != '\015') ||
2590 (getc (srtcf
) != '\012')) {
2591 msg
= "message-id data";
2596 if (sc
->references
) fseek(srtcf
,reflen
+ 2,SEEK_CUR
);
2597 /* make sure it fits */
2599 if (reflen
>= LOCAL
->buflen
) {
2600 fs_give ((void **) &LOCAL
->buf
);
2601 LOCAL
->buf
= (char *)
2602 fs_get ((LOCAL
->buflen
= reflen
) + 1);
2604 if ((getc (srtcf
) != 'R') ||
2605 (fread (LOCAL
->buf
,1,reflen
-1,srtcf
) !=
2607 (LOCAL
->buf
[reflen
-1] = '\0') ||
2608 (getc (srtcf
) != '\015') ||
2609 (getc (srtcf
) != '\012')) {
2610 msg
= "references data";
2613 for (s
= LOCAL
->buf
,sl
= NIL
,
2614 sc
->references
= mail_newstringlist ();
2615 s
&& *s
; s
+= i
+ 1) {
2616 if ((i
= strtoul (s
,&s
,16)) && (*s
++ == ':') &&
2618 if (sl
) sl
= sl
->next
= mail_newstringlist();
2619 else sl
= sc
->references
;
2621 sl
->text
.data
= cpystr (s
);
2627 (s
!= ((char *) LOCAL
->buf
+ reflen
- 1))) {
2628 msg
= "references length consistency check";
2635 /* UID not found, ignore this message */
2636 else fseek (srtcf
,((fromlen
? fromlen
+ 2 : 0) +
2637 (tolen
? tolen
+ 2 : 0) +
2638 (cclen
? cclen
+ 2 : 0) +
2639 (subjlen
? subjlen
+ 2 : 0) +
2640 (msgidlen
? msgidlen
+ 2 : 0) +
2641 (reflen
? reflen
+ 2 : 0)),
2645 else msg
= "expansion";
2647 else msg
= "references";
2649 else msg
= "message-id";
2651 else msg
= "subject";
2659 else msg
= "sentdate";
2660 break; /* error somewhere */
2662 if (!t
|| *t
) { /* error detected? */
2663 if (t
) { /* non-null means bogus record */
2664 sprintf (tmp
,"Error in %s in mix sortcache record: %.500s",msg
,t
);
2667 fclose (srtcf
); /* either way, must punt */
2671 if (rdonly
&& srtcf
) { /* can't update if readonly */
2672 unlink (LOCAL
->sortcache
); /* try deleting it */
2673 fclose (srtcf
); /* so close it and return as if error */
2676 else if(fd
>= 0) fchmod (fd
,sbuf
.st_mode
);
2680 /* MIX update and close sortcache
2681 * Accepts: MAIL stream
2682 * pointer to open FILE (if FILE is NIL, do nothing)
2683 * Returns: T on success, NIL on error
2686 long mix_sortcache_update (MAILSTREAM
*stream
,FILE **sortcache
)
2688 FILE *f
= *sortcache
;
2690 if (f
) { /* ignore if no file */
2692 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
2693 for (i
= 1; (i
<= stream
->nmsgs
) &&
2694 !((SORTCACHE
*) (*mc
) (stream
,i
,CH_SORTCACHE
))->dirty
; ++i
);
2695 if (i
<= stream
->nmsgs
) { /* only update if some entry is dirty */
2696 rewind (f
); /* let's start at the very beginning */
2697 /* write sequence */
2698 fprintf (f
,SEQFMT
,LOCAL
->sortcacheseq
= mix_modseq(LOCAL
->sortcacheseq
));
2699 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); ++i
) {
2700 MESSAGECACHE
*elt
= mail_elt (stream
,i
);
2701 SORTCACHE
*s
= (SORTCACHE
*) (*mc
) (stream
,i
,CH_SORTCACHE
);
2703 s
->dirty
= NIL
; /* no longer dirty */
2704 if ((sl
= s
->references
) != NULL
) /* count length of references */
2705 for (j
= 1; sl
&& sl
->text
.data
; sl
= sl
->next
)
2706 j
+= 10 + sl
->text
.size
;
2707 else j
= 0; /* no references yet */
2708 fprintf (f
,SCRFMT
,elt
->private.uid
,s
->date
,
2709 s
->from
? strlen (s
->from
) + 1 : 0,
2710 s
->to
? strlen (s
->to
) + 1 : 0,s
->cc
? strlen (s
->cc
) + 1 : 0,
2711 s
->refwd
? 'R' : ' ',s
->subject
? strlen (s
->subject
) + 1: 0,
2712 s
->message_id
? strlen (s
->message_id
) + 1 : 0,j
);
2713 if (s
->from
) fprintf (f
,"F%s\015\012",s
->from
);
2714 if (s
->to
) fprintf (f
,"T%s\015\012",s
->to
);
2715 if (s
->cc
) fprintf (f
,"C%s\015\012",s
->cc
);
2716 if (s
->subject
) fprintf (f
,"S%s\015\012",s
->subject
);
2717 if (s
->message_id
) fprintf (f
,"M%s\015\012",s
->message_id
);
2718 if (j
) { /* any references to write? */
2719 fputc ('R',f
); /* yes, do so */
2720 for (sl
= s
->references
; sl
&& sl
->text
.data
; sl
= sl
->next
)
2721 fprintf (f
,"%08lx:%s:",sl
->text
.size
,sl
->text
.data
);
2722 fputs ("\015\012",f
);
2725 MM_LOG ("Error updating mix sortcache file",WARN
);
2729 if (ret
&& fflush (f
)) {
2730 MM_LOG ("Error flushing mix sortcache file",WARN
);
2733 if (ret
) ftruncate (fileno (f
),ftell (f
));
2736 MM_LOG ("Error closing mix sortcache file",WARN
);
2743 /* MIX generic file routines */
2746 * Accepts: open FILE
2750 * Returns: buffer if success, else NIL (zero-length buffer means EOF)
2753 char *mix_read_record (FILE *f
,char *buf
,unsigned long buflen
,char *type
)
2755 char *s
,tmp
[MAILTMPLEN
];
2756 /* ensure string tied off */
2757 buf
[buflen
-2] = buf
[buflen
-1] = '\0';
2758 while (fgets (buf
,buflen
-1,f
)) {
2759 if ((s
= strchr (buf
,'\012')) != NULL
) {
2760 if ((s
!= buf
) && (s
[-1] == '\015')) --s
;
2761 *s
= '\0'; /* tie off buffer */
2762 if (s
!= buf
) return buf
; /* return if non-empty buffer */
2763 sprintf (tmp
,"Empty mix %s record",type
);
2766 else if (buf
[buflen
-2]) { /* overlong record is bad news */
2767 sprintf (tmp
,"Oversize mix %s record: %.512s",type
,buf
);
2772 sprintf (tmp
,"Truncated mix %s record: %.512s",type
,buf
);
2774 return buf
; /* pass to caller anyway */
2777 buf
[0] = '\0'; /* return empty buffer on EOF */
2781 /* MIX read sequence record
2782 * Accepts: open FILE
2783 * Returns: sequence value, or NIL if failure
2786 unsigned long mix_read_sequence (FILE *f
)
2789 char *s
,tmp
[MAILTMPLEN
];
2790 if (!mix_read_record (f
,tmp
,MAILTMPLEN
-1,"sequence")) return NIL
;
2791 switch (tmp
[0]) { /* examine record */
2792 case '\0': /* end of file */
2793 ret
= 1; /* start a new sequence regime */
2795 case 'S': /* sequence record */
2796 if (isxdigit (tmp
[1])) { /* must be followed by hex value */
2797 ret
= strtoul (tmp
+1,&s
,16);
2798 if (!*s
) break; /* and nothing more */
2800 /* drop into default case */
2801 default: /* anything else is an error */
2802 return NIL
; /* return error */
2807 /* MIX internal routines */
2810 /* MIX mail build directory name
2811 * Accepts: destination string
2813 * Returns: destination or empty string if error
2816 char *mix_dir (char *dst
,char *name
)
2819 /* empty string if mailboxfile fails */
2820 if (!mailboxfile (dst
,name
)) *dst
= '\0';
2821 /* driver-selected INBOX */
2822 else if (!*dst
) mailboxfile (dst
,"~/INBOX");
2823 /* tie off unnecessary trailing / */
2824 else if ((s
= strrchr (dst
,'/')) && !s
[1]) *s
= '\0';
2829 /* MIX mail build file name
2830 * Accepts: destination string
2833 * Returns: destination
2836 char *mix_file (char *dst
,char *dir
,char *name
)
2838 sprintf (dst
,"%.500s/%.80s%.80s",dir
,MIXNAME
,name
);
2843 /* MIX mail build file name from data file number
2844 * Accepts: destination string
2847 * Returns: destination
2850 char *mix_file_data (char *dst
,char *dir
,unsigned long data
)
2852 char tmp
[MAILTMPLEN
];
2853 if (data
) sprintf (tmp
,"%08lx",data
);
2854 else tmp
[0] = '\0'; /* compatibility with experimental version */
2855 return mix_file (dst
,dir
,tmp
);
2858 /* MIX mail get new modseq
2859 * Accepts: old modseq
2860 * Returns: new modseq value
2863 unsigned long mix_modseq (unsigned long oldseq
)
2865 /* normally time now */
2866 unsigned long ret
= (unsigned long) time (NIL
);
2867 /* ensure that modseq doesn't go backwards */
2868 if (ret
<= oldseq
) ret
= oldseq
+ 1;