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
29 extern int errno
; /* just in case */
41 #define MEGABYTE (1024*1024)
43 #define MIXDATAROLL MEGABYTE /* size at which we roll to a new file */
48 #define MIXNAME ".mix" /* prefix for all MIX file names */
49 #define MIXMETA "meta" /* suffix for metadata */
50 #define MIXINDEX "index" /* suffix for index */
51 #define MIXSTATUS "status" /* suffix for status */
52 #define MIXSORTCACHE "sortcache"/* suffix for sortcache */
53 #define METAMAX (MEGABYTE-1) /* maximum metadata file size (sanity check) */
56 /* MIX file formats */
58 /* sequence format (all but msg files) */
59 #define SEQFMT "S%08lx\015\012"
60 /* metadata file format */
61 #define MTAFMT "V%08lx\015\012L%08lx\015\012N%08lx\015\012"
62 /* index file record format */
63 #define IXRFMT ":%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:%08lx:%08lx:%08lx:%08lx:\015\012"
64 /* status file record format */
65 #define STRFMT ":%08lx:%08lx:%04x:%08lx:\015\012"
66 /* message file header format */
67 #define MSRFMT "%s%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:\015\012"
68 #define MSGTOK ":msg:"
69 #define MSGTSZ (sizeof(MSGTOK)-1)
70 /* sortcache file record format */
71 #define SCRFMT ":%08lx:%08lx:%08lx:%08lx:%08lx:%c%08lx:%08lx:%08lx:\015\012"
73 /* MIX I/O stream local data */
75 typedef struct mix_local
{
76 unsigned long curmsg
; /* current message file number */
77 unsigned long newmsg
; /* current new message file number */
78 time_t lastsnarf
; /* last snarf time */
79 int msgfd
; /* file description of current msg file */
80 int mfd
; /* file descriptor of open metadata */
81 unsigned long metaseq
; /* metadata sequence */
82 char *index
; /* mailbox index name */
83 unsigned long indexseq
; /* index sequence */
84 char *status
; /* mailbox status name */
85 unsigned long statusseq
; /* status sequence */
86 char *sortcache
; /* mailbox sortcache name */
87 unsigned long sortcacheseq
; /* sortcache sequence */
88 unsigned char *buf
; /* temporary buffer */
89 unsigned long buflen
; /* current size of temporary buffer */
90 unsigned int expok
: 1; /* non-zero if expunge reports OK */
91 unsigned int internal
: 1; /* internally opened, do not validate */
95 #define MIXBURP struct mix_burp
98 unsigned long fileno
; /* message file number */
99 char *name
; /* message file name */
100 SEARCHSET
*tail
; /* tail of ranges */
101 SEARCHSET set
; /* set of retained ranges */
102 MIXBURP
*next
; /* next file to burp */
106 /* Convenient access to local data */
108 #define LOCAL ((MIXLOCAL *) stream->local)
110 /* Function prototypes */
112 DRIVER
*mix_valid (char *name
);
113 long mix_isvalid (char *name
,char *meta
);
114 void *mix_parameters (long function
,void *value
);
115 long mix_dirfmttest (char *name
);
116 void mix_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
);
117 long mix_scan_contents (char *name
,char *contents
,unsigned long csiz
,
119 void mix_list (MAILSTREAM
*stream
,char *ref
,char *pat
);
120 void mix_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
);
121 long mix_subscribe (MAILSTREAM
*stream
,char *mailbox
);
122 long mix_unsubscribe (MAILSTREAM
*stream
,char *mailbox
);
123 long mix_create (MAILSTREAM
*stream
,char *mailbox
);
124 long mix_delete (MAILSTREAM
*stream
,char *mailbox
);
125 long mix_rename (MAILSTREAM
*stream
,char *old
,char *newname
);
126 int mix_rselect (struct direct
*name
);
127 MAILSTREAM
*mix_open (MAILSTREAM
*stream
);
128 void mix_close (MAILSTREAM
*stream
,long options
);
129 void mix_abort (MAILSTREAM
*stream
);
130 char *mix_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
132 long mix_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
);
133 void mix_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
);
134 unsigned long *mix_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
135 SORTPGM
*pgm
,long flags
);
136 THREADNODE
*mix_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
137 SEARCHPGM
*spg
,long flags
);
138 long mix_ping (MAILSTREAM
*stream
);
139 void mix_check (MAILSTREAM
*stream
);
140 long mix_expunge (MAILSTREAM
*stream
,char *sequence
,long options
);
141 int mix_select (struct direct
*name
);
142 int mix_msgfsort (const void *d1
,const void *d2
);
143 long mix_addset (SEARCHSET
**set
,unsigned long start
,unsigned long size
);
144 long mix_burp (MAILSTREAM
*stream
,MIXBURP
*burp
,unsigned long *reclaimed
);
145 long mix_burp_check (SEARCHSET
*set
,size_t size
,char *file
);
146 long mix_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,
148 long mix_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
);
149 long mix_append_msg (MAILSTREAM
*stream
,FILE *f
,char *flags
,MESSAGECACHE
*delt
,
150 STRING
*msg
,SEARCHSET
*set
,unsigned long seq
);
152 FILE *mix_parse (MAILSTREAM
*stream
,FILE **idxf
,long iflags
,long sflags
);
153 char *mix_meta_slurp (MAILSTREAM
*stream
,unsigned long *seq
);
154 long mix_meta_update (MAILSTREAM
*stream
);
155 long mix_index_update (MAILSTREAM
*stream
,FILE *idxf
,long flag
);
156 long mix_status_update (MAILSTREAM
*stream
,FILE *statf
,long flag
);
157 FILE *mix_data_open (MAILSTREAM
*stream
,int *fd
,long *size
,
158 unsigned long newsize
);
159 FILE *mix_sortcache_open (MAILSTREAM
*stream
);
160 long mix_sortcache_update (MAILSTREAM
*stream
,FILE **sortcache
);
161 char *mix_read_record (FILE *f
,char *buf
,unsigned long buflen
,char *type
);
162 unsigned long mix_read_sequence (FILE *f
);
163 char *mix_dir (char *dst
,char *name
);
164 char *mix_file (char *dst
,char *dir
,char *name
);
165 char *mix_file_data (char *dst
,char *dir
,unsigned long data
);
166 unsigned long mix_modseq (unsigned long oldseq
);
168 /* MIX mail routines */
171 /* Driver dispatch used by MAIL */
174 "mix", /* driver name */
176 DR_MAIL
|DR_LOCAL
|DR_NOFAST
|DR_CRLF
|DR_LOCKING
|DR_DIRFMT
|DR_MODSEQ
,
177 (DRIVER
*) NIL
, /* next driver */
178 mix_valid
, /* mailbox is valid for us */
179 mix_parameters
, /* manipulate parameters */
180 mix_scan
, /* scan mailboxes */
181 mix_list
, /* find mailboxes */
182 mix_lsub
, /* find subscribed mailboxes */
183 mix_subscribe
, /* subscribe to mailbox */
184 mix_unsubscribe
, /* unsubscribe from mailbox */
185 mix_create
, /* create mailbox */
186 mix_delete
, /* delete mailbox */
187 mix_rename
, /* rename mailbox */
188 mail_status_default
, /* status of mailbox */
189 mix_open
, /* open mailbox */
190 mix_close
, /* close mailbox */
191 NIL
, /* fetch message "fast" attributes */
192 NIL
, /* fetch message flags */
193 NIL
, /* fetch overview */
194 NIL
, /* fetch message envelopes */
195 mix_header
, /* fetch message header only */
196 mix_text
, /* fetch message body only */
197 NIL
, /* fetch partial message test */
198 NIL
, /* unique identifier */
199 NIL
, /* message number */
200 mix_flag
, /* modify flags */
201 NIL
, /* per-message modify flags */
202 NIL
, /* search for message based on criteria */
203 mix_sort
, /* sort messages */
204 mix_thread
, /* thread messages */
205 mix_ping
, /* ping mailbox to see if still alive */
206 mix_check
, /* check for new messages */
207 mix_expunge
, /* expunge deleted messages */
208 mix_copy
, /* copy messages to another mailbox */
209 mix_append
, /* append string message to mailbox */
210 NIL
/* garbage collect stream */
213 /* prototype stream */
214 MAILSTREAM mixproto
= {&mixdriver
};
216 /* MIX mail validate mailbox
217 * Accepts: mailbox name
218 * Returns: our driver if name is valid, NIL otherwise
221 DRIVER
*mix_valid (char *name
)
223 char tmp
[MAILTMPLEN
];
224 return mix_isvalid (name
,tmp
) ? &mixdriver
: NIL
;
228 /* MIX mail test for valid mailbox
229 * Accepts: mailbox name
230 * buffer to return meta name
231 * Returns: T if valid, NIL otherwise, metadata name written in both cases
234 long mix_isvalid (char *name
,char *meta
)
236 char dir
[MAILTMPLEN
];
238 /* validate name as directory */
239 if (!(errno
= ((strlen (name
) > NETMAXMBX
) ? ENAMETOOLONG
: NIL
)) &&
240 *mix_dir (dir
,name
) && mix_file (meta
,dir
,MIXMETA
) &&
241 !stat (dir
,&sbuf
) && ((sbuf
.st_mode
& S_IFMT
) == S_IFDIR
)) {
242 /* name is directory; is it mix? */
243 if (!stat (meta
,&sbuf
) && ((sbuf
.st_mode
& S_IFMT
) == S_IFREG
))
245 else errno
= NIL
; /* directory but not mix */
250 /* MIX manipulate driver parameters
251 * Accepts: function code
252 * function-dependent value
253 * Returns: function-dependent return value
256 void *mix_parameters (long function
,void *value
)
259 switch ((int) function
) {
261 if (value
) ret
= mailboxfile ((char *) value
,"~/INBOX");
264 ret
= (void *) mix_dirfmttest
;
266 case GET_SCANCONTENTS
:
267 ret
= (void *) mix_scan_contents
;
269 case SET_ONETIMEEXPUNGEATPING
:
270 if (value
) ((MIXLOCAL
*) ((MAILSTREAM
*) value
)->local
)->expok
= T
;
271 case GET_ONETIMEEXPUNGEATPING
:
272 if (value
) ret
= (void *)
273 (((MIXLOCAL
*) ((MAILSTREAM
*) value
)->local
)->expok
? VOIDT
: NIL
);
280 /* MIX test for directory format internal node
281 * Accepts: candidate node name
282 * Returns: T if internal name, NIL otherwise
285 long mix_dirfmttest (char *name
)
287 /* belongs to MIX if starts with .mix */
288 return strncmp (name
,MIXNAME
,sizeof (MIXNAME
) - 1) ? NIL
: LONGT
;
291 /* MIX mail scan mailboxes
292 * Accepts: mail stream
298 void mix_scan (MAILSTREAM
*stream
,char *ref
,char *pat
,char *contents
)
300 if (stream
) dummy_scan (NIL
,ref
,pat
,contents
);
304 /* MIX scan mailbox for contents
305 * Accepts: mailbox name
308 * file size (ignored)
309 * Returns: NIL if contents not found, T if found
312 long mix_scan_contents (char *name
,char *contents
,unsigned long csiz
,
319 size_t namelen
= strlen (name
);
321 struct direct
**names
= NIL
;
322 if ((nfiles
= scandir (name
,&names
,mix_select
,mix_msgfsort
)) > 0)
323 for (i
= 0; i
< nfiles
; ++i
) {
325 sprintf (s
= (char *) fs_get (namelen
+ strlen (names
[i
]->d_name
) + 2),
326 "%s/%s",name
,names
[i
]->d_name
);
327 if (!stat (s
,&sbuf
) && (csiz
<= sbuf
.st_size
))
328 ret
= dummy_scan_contents (s
,contents
,csiz
,sbuf
.st_size
);
329 fs_give ((void **) &s
);
331 fs_give ((void **) &names
[i
]);
333 /* free directory list */
334 if (a
= (void *) names
) fs_give ((void **) &a
);
338 /* MIX list mailboxes
339 * Accepts: mail stream
344 void mix_list (MAILSTREAM
*stream
,char *ref
,char *pat
)
346 if (stream
) dummy_list (NIL
,ref
,pat
);
350 /* MIX list subscribed mailboxes
351 * Accepts: mail stream
356 void mix_lsub (MAILSTREAM
*stream
,char *ref
,char *pat
)
358 if (stream
) dummy_lsub (NIL
,ref
,pat
);
361 /* MIX mail subscribe to mailbox
362 * Accepts: mail stream
363 * mailbox to add to subscription list
364 * Returns: T on success, NIL on failure
367 long mix_subscribe (MAILSTREAM
*stream
,char *mailbox
)
369 return sm_subscribe (mailbox
);
373 /* MIX mail unsubscribe to mailbox
374 * Accepts: mail stream
375 * mailbox to delete from subscription list
376 * Returns: T on success, NIL on failure
379 long mix_unsubscribe (MAILSTREAM
*stream
,char *mailbox
)
381 return sm_unsubscribe (mailbox
);
384 /* MIX mail create mailbox
385 * Accepts: mail stream
386 * mailbox name to create
387 * Returns: T on success, NIL on failure
390 long mix_create (MAILSTREAM
*stream
,char *mailbox
)
395 char *t
,tmp
[MAILTMPLEN
],file
[MAILTMPLEN
];
396 char *s
= strrchr (mailbox
,'/');
397 unsigned long now
= time (NIL
);
399 /* always create \NoSelect if trailing / */
400 if (s
&& !s
[1]) return dummy_create (stream
,mailbox
);
402 if (mix_dirfmttest (s
? s
+ 1 : mailbox
))
403 sprintf(tmp
,"Can't create mailbox %.80s: invalid MIX-format name",mailbox
);
404 /* must not already exist */
405 else if ((test
= mail_valid (NIL
,mailbox
,NIL
)) &&
406 strcmp (test
->name
,"dummy"))
407 sprintf (tmp
,"Can't create mailbox %.80s: mailbox already exists",mailbox
);
408 /* create directory and metadata */
409 else if (!dummy_create_path (stream
,
410 mix_file (file
,mix_dir (tmp
,mailbox
),MIXMETA
),
411 get_dir_protection (mailbox
)))
412 sprintf (tmp
,"Can't create mailbox %.80s: %.80s",mailbox
,strerror (errno
));
413 else if (!(f
= fopen (file
,"w")))
414 sprintf (tmp
,"Can't re-open metadata %.80s: %.80s",mailbox
,
416 else { /* success, write initial metadata */
417 fprintf (f
,SEQFMT
,now
);
418 fprintf (f
,MTAFMT
,now
,(unsigned long) 0,now
);
419 for (i
= 0, c
= 'K'; (i
< NUSERFLAGS
) &&
420 (t
= (stream
&& stream
->user_flags
[i
]) ? stream
->user_flags
[i
] :
421 default_user_flag (i
)) && *t
; ++i
) {
422 putc (c
,f
); /* write another keyword */
424 c
= ' '; /* delimiter is now space */
427 set_mbx_protections (mailbox
,file
);
428 /* point to suffix */
429 s
= file
+ strlen (file
) - (sizeof (MIXMETA
) - 1);
430 strcpy (s
,MIXINDEX
); /* create index */
431 if (!dummy_create_path (stream
,file
,get_dir_protection (mailbox
)))
432 sprintf (tmp
,"Can't create mix mailbox index: %.80s",strerror (errno
));
434 set_mbx_protections (mailbox
,file
);
435 strcpy (s
,MIXSTATUS
); /* create status */
436 if (!dummy_create_path (stream
,file
,get_dir_protection (mailbox
)))
437 sprintf (tmp
,"Can't create mix mailbox status: %.80s",
440 set_mbx_protections (mailbox
,file
);
441 sprintf (s
,"%08lx",now
);/* message file */
442 if (!dummy_create_path (stream
,file
,get_dir_protection (mailbox
)))
443 sprintf (tmp
,"Can't create mix mailbox data: %.80s",
446 set_mbx_protections (mailbox
,file
);
447 ret
= LONGT
; /* declare success at this point */
452 if (!ret
) MM_LOG (tmp
,ERROR
); /* some error */
456 /* MIX mail delete mailbox
457 * mailbox name to delete
458 * Returns: T on success, NIL on failure
461 long mix_delete (MAILSTREAM
*stream
,char *mailbox
)
466 char *s
,tmp
[MAILTMPLEN
];
467 if (!mix_isvalid (mailbox
,tmp
))
468 sprintf (tmp
,"Can't delete mailbox %.80s: no such mailbox",mailbox
);
469 else if (((fd
= open (tmp
,O_RDWR
,NIL
)) < 0) || flock (fd
,LOCK_EX
|LOCK_NB
))
470 sprintf (tmp
,"Can't lock mailbox for delete: %.80s",mailbox
);
471 /* delete metadata */
472 else if (unlink (tmp
)) sprintf (tmp
,"Can't delete mailbox %.80s index: %80s",
473 mailbox
,strerror (errno
));
475 close (fd
); /* close descriptor on deleted metadata */
476 /* get directory name */
477 *(s
= strrchr (tmp
,'/')) = '\0';
478 if (dirp
= opendir (tmp
)) { /* open directory */
479 *s
++ = '/'; /* restore delimiter */
480 /* massacre messages */
481 while (d
= readdir (dirp
)) if (mix_dirfmttest (d
->d_name
)) {
482 strcpy (s
,d
->d_name
); /* make path */
483 unlink (tmp
); /* sayonara */
485 closedir (dirp
); /* flush directory */
486 *(s
= strrchr (tmp
,'/')) = '\0';
487 if (rmdir (tmp
)) { /* try to remove the directory */
488 sprintf (tmp
,"Can't delete name %.80s: %.80s",
489 mailbox
,strerror (errno
));
493 return T
; /* always success */
495 if (fd
>= 0) close (fd
); /* close any descriptor on metadata */
496 MM_LOG (tmp
,ERROR
); /* something failed */
500 /* MIX mail rename mailbox
501 * Accepts: MIX mail stream
504 * Returns: T on success, NIL on failure
507 long mix_rename (MAILSTREAM
*stream
,char *old
,char *newname
)
509 char c
,*s
,tmp
[MAILTMPLEN
],tmp1
[MAILTMPLEN
];
512 if (!mix_isvalid (old
,tmp
))
513 sprintf (tmp
,"Can't rename mailbox %.80s: no such mailbox",old
);
514 else if (((fd
= open (tmp
,O_RDWR
,NIL
)) < 0) || flock (fd
,LOCK_EX
|LOCK_NB
))
515 sprintf (tmp
,"Can't lock mailbox for rename: %.80s",old
);
516 else if (mix_dirfmttest ((s
= strrchr (newname
,'/')) ? s
+ 1 : newname
))
517 sprintf (tmp
,"Can't rename to mailbox %.80s: invalid MIX-format name",
519 /* new mailbox name must not be valid */
520 else if (mix_isvalid (newname
,tmp
))
521 sprintf (tmp
,"Can't rename to mailbox %.80s: destination already exists",
524 mix_dir (tmp
,old
); /* build old directory name */
525 mix_dir (tmp1
,newname
); /* and new directory name */
526 /* easy if not INBOX */
527 if (compare_cstring (old
,"INBOX")) {
528 /* found superior to destination name? */
529 if (s
= strrchr (tmp1
,'/')) {
530 c
= *++s
; /* remember first character of inferior */
531 *s
= '\0'; /* tie off to get just superior */
532 /* name doesn't exist, create it */
533 if ((stat (tmp1
,&sbuf
) || ((sbuf
.st_mode
& S_IFMT
) != S_IFDIR
)) &&
534 !dummy_create_path (stream
,tmp1
,get_dir_protection (newname
)))
536 *s
= c
; /* restore full name */
538 if (!rename (tmp
,tmp1
)) {
539 close (fd
); /* close descriptor on metadata */
544 /* RFC 3501 requires this */
545 else if (dummy_create_path (stream
,strcat (tmp1
,"/"),
546 get_dir_protection (newname
))) {
550 struct direct
**names
= NIL
;
551 size_t srcl
= strlen (tmp
);
552 size_t dstl
= strlen (tmp1
);
553 /* rename each mix file to new directory */
554 for (i
= lasterror
= 0,n
= scandir (tmp
,&names
,mix_rselect
,alphasort
);
556 size_t len
= strlen (names
[i
]->d_name
);
557 sprintf (src
= (char *) fs_get (srcl
+ len
+ 2),"%s/%s",
558 tmp
,names
[i
]->d_name
);
559 sprintf (dst
= (char *) fs_get (dstl
+ len
+ 1),"%s%s",
560 tmp1
,names
[i
]->d_name
);
561 if (rename (src
,dst
)) lasterror
= errno
;
562 fs_give ((void **) &src
);
563 fs_give ((void **) &dst
);
564 fs_give ((void **) &names
[i
]);
566 /* free directory list */
567 if (a
= (void *) names
) fs_give ((void **) &a
);
568 if (lasterror
) errno
= lasterror
;
570 close (fd
); /* close descriptor on metadata */
571 return mix_create (NIL
,"INBOX");
574 sprintf (tmp
,"Can't rename mailbox %.80s to %.80s: %.80s",
575 old
,newname
,strerror (errno
));
577 if (fd
>= 0) close (fd
); /* close any descriptor on metadata */
578 MM_LOG (tmp
,ERROR
); /* something failed */
583 /* MIX test for mix name
584 * Accepts: candidate directory name
585 * Returns: T if mix file name, NIL otherwise
588 int mix_rselect (struct direct
*name
)
590 return mix_dirfmttest (name
->d_name
);
594 * Accepts: stream to open
595 * Returns: stream on success, NIL on failure
598 MAILSTREAM
*mix_open (MAILSTREAM
*stream
)
601 /* return prototype for OP_PROTOTYPE call */
602 if (!stream
) return user_flags (&mixproto
);
603 if (stream
->local
) fatal ("mix recycle stream");
604 stream
->local
= memset (fs_get (sizeof (MIXLOCAL
)),0,sizeof (MIXLOCAL
));
605 /* note if an INBOX or not */
606 stream
->inbox
= !compare_cstring (stream
->mailbox
,"INBOX");
607 /* make temporary buffer */
608 LOCAL
->buf
= (char *) fs_get (CHUNKSIZE
);
609 LOCAL
->buflen
= CHUNKSIZE
- 1;
610 /* set stream->mailbox to be directory name */
611 mix_dir (LOCAL
->buf
,stream
->mailbox
);
612 fs_give ((void **) &stream
->mailbox
);
613 stream
->mailbox
= cpystr (LOCAL
->buf
);
614 LOCAL
->msgfd
= -1; /* currently no file open */
615 if (!(((!stream
->rdonly
&& /* open metadata file */
616 ((LOCAL
->mfd
= open (mix_file (LOCAL
->buf
,stream
->mailbox
,MIXMETA
),
617 O_RDWR
,NIL
)) >= 0)) ||
618 ((stream
->rdonly
= T
) &&
619 ((LOCAL
->mfd
= open (mix_file (LOCAL
->buf
,stream
->mailbox
,MIXMETA
),
620 O_RDONLY
,NIL
)) >= 0))) &&
621 !flock (LOCAL
->mfd
,LOCK_SH
))) {
622 MM_LOG ("Error opening mix metadata file",ERROR
);
624 stream
= NIL
; /* open fails */
626 else { /* metadata open, complete open */
627 LOCAL
->index
= cpystr (mix_file (LOCAL
->buf
,stream
->mailbox
,MIXINDEX
));
628 LOCAL
->status
= cpystr (mix_file (LOCAL
->buf
,stream
->mailbox
,MIXSTATUS
));
629 LOCAL
->sortcache
= cpystr (mix_file (LOCAL
->buf
,stream
->mailbox
,
631 stream
->sequence
++; /* bump sequence number */
633 stream
->nmsgs
= stream
->recent
= 0;
634 if (silent
= stream
->silent
) LOCAL
->internal
= T
;
636 if (mix_ping (stream
)) { /* do initial ping */
637 /* try burping in case we are exclusive */
638 if (!stream
->rdonly
) mix_expunge (stream
,"",NIL
);
639 if (!(stream
->nmsgs
|| stream
->silent
))
640 MM_LOG ("Mailbox is empty",(long) NIL
);
641 stream
->silent
= silent
; /* now notify upper level */
642 mail_exists (stream
,stream
->nmsgs
);
643 stream
->perm_seen
= stream
->perm_deleted
= stream
->perm_flagged
=
644 stream
->perm_answered
= stream
->perm_draft
= stream
->rdonly
? NIL
: T
;
645 stream
->perm_user_flags
= stream
->rdonly
? NIL
: 0xffffffff;
646 stream
->kwd_create
= /* can we create new user flags? */
647 (stream
->user_flags
[NUSERFLAGS
-1] || stream
->rdonly
) ? NIL
: T
;
649 else { /* got murdelyzed in ping */
654 return stream
; /* return stream to caller */
658 * Accepts: MAIL stream
662 void mix_close (MAILSTREAM
*stream
,long options
)
664 if (LOCAL
) { /* only if a file is open */
665 int silent
= stream
->silent
;
666 stream
->silent
= T
; /* note this stream is dying */
667 /* burp-only or expunge */
668 mix_expunge (stream
,(options
& CL_EXPUNGE
) ? NIL
: "",NIL
);
670 stream
->silent
= silent
; /* reset silent state */
675 /* MIX mail abort stream
676 * Accepts: MAIL stream
679 void mix_abort (MAILSTREAM
*stream
)
681 if (LOCAL
) { /* only if a file is open */
682 /* close current message file if open */
683 if (LOCAL
->msgfd
>= 0) close (LOCAL
->msgfd
);
684 /* close current metadata file if open */
685 if (LOCAL
->mfd
>= 0) close (LOCAL
->mfd
);
686 if (LOCAL
->index
) fs_give ((void **) &LOCAL
->index
);
687 if (LOCAL
->status
) fs_give ((void **) &LOCAL
->status
);
688 if (LOCAL
->sortcache
) fs_give ((void **) &LOCAL
->sortcache
);
689 /* free local scratch buffer */
690 if (LOCAL
->buf
) fs_give ((void **) &LOCAL
->buf
);
691 /* nuke the local data */
692 fs_give ((void **) &stream
->local
);
693 stream
->dtb
= NIL
; /* log out the DTB */
697 /* MIX mail fetch message header
698 * Accepts: MAIL stream
700 * pointer to returned header text length
702 * Returns: message header in RFC822 format
705 char *mix_header (MAILSTREAM
*stream
,unsigned long msgno
,unsigned long *length
,
710 char *s
,tmp
[MAILTMPLEN
];
712 if (length
) *length
= 0; /* default return */
713 if (flags
& FT_UID
) return "";/* UID call "impossible" */
714 elt
= mail_elt (stream
,msgno
);/* get elt */
715 /* is message in current message file? */
716 if ((LOCAL
->msgfd
< 0) || (elt
->private.spare
.data
!= LOCAL
->curmsg
)) {
717 if (LOCAL
->msgfd
>= 0) close (LOCAL
->msgfd
);
718 if ((LOCAL
->msgfd
= open (mix_file_data (LOCAL
->buf
,stream
->mailbox
,
719 elt
->private.spare
.data
),
720 O_RDONLY
,NIL
)) < 0) return "";
722 LOCAL
->curmsg
= elt
->private.spare
.data
;
724 lseek (LOCAL
->msgfd
,elt
->private.special
.offset
,L_SET
);
725 /* size of special data and header */
726 j
= elt
->private.msg
.header
.offset
+ elt
->private.msg
.header
.text
.size
;
727 if (j
> LOCAL
->buflen
) { /* is buffer big enough? */
728 /* no, make one that is */
729 fs_give ((void **) &LOCAL
->buf
);
730 LOCAL
->buf
= (char *) fs_get ((LOCAL
->buflen
= j
) + 1);
732 /* Maybe someday validate internaldate too */
733 /* slurp special data + header, validate */
734 if ((read (LOCAL
->msgfd
,LOCAL
->buf
,j
) == j
) &&
735 !strncmp (LOCAL
->buf
,MSGTOK
,MSGTSZ
) &&
736 (elt
->private.uid
== strtoul ((char *) LOCAL
->buf
+ MSGTSZ
,&s
,16)) &&
737 (*s
++ == ':') && (s
= strchr (s
,':')) &&
738 (k
= strtoul (s
+1,&s
,16)) && (*s
++ == ':') &&
739 (s
< (char *) (LOCAL
->buf
+ elt
->private.msg
.header
.offset
))) {
740 /* won, set offset and size of message */
741 i
= elt
->private.msg
.header
.offset
;
742 *length
= elt
->private.msg
.header
.text
.size
;
743 if (k
!= elt
->rfc822_size
) {
744 sprintf (tmp
,"Inconsistency in mix message size, uid=%lx (%lu != %lu)",
745 elt
->private.uid
,elt
->rfc822_size
,k
);
749 else { /* document the problem */
750 LOCAL
->buf
[100] = '\0'; /* tie off buffer at no more than 100 octets */
751 /* or at newline, whichever is first */
752 if (s
= strpbrk (LOCAL
->buf
,"\015\012")) *s
= '\0';
753 sprintf (tmp
,"Error reading mix message header, uid=%lx, s=%.0lx, h=%s",
754 elt
->private.uid
,elt
->rfc822_size
,LOCAL
->buf
);
756 *length
= i
= j
= 0; /* default to empty */
758 LOCAL
->buf
[j
] = '\0'; /* tie off buffer at the end */
759 return (char *) LOCAL
->buf
+ i
;
762 /* MIX mail fetch message text (body only)
763 * Accepts: MAIL stream
765 * pointer to returned stringstruct
767 * Returns: T on success, NIL on failure
770 long mix_text (MAILSTREAM
*stream
,unsigned long msgno
,STRING
*bs
,long flags
)
775 /* UID call "impossible" */
776 if (flags
& FT_UID
) return NIL
;
777 elt
= mail_elt (stream
,msgno
);
778 /* is message in current message file? */
779 if ((LOCAL
->msgfd
< 0) || (elt
->private.spare
.data
!= LOCAL
->curmsg
)) {
780 if (LOCAL
->msgfd
>= 0) close (LOCAL
->msgfd
);
781 if ((LOCAL
->msgfd
= open (mix_file_data (LOCAL
->buf
,stream
->mailbox
,
782 elt
->private.spare
.data
),
783 O_RDONLY
,NIL
)) < 0) return NIL
;
785 LOCAL
->curmsg
= elt
->private.spare
.data
;
787 /* doing non-peek fetch? */
788 if (!(flags
& FT_PEEK
) && !elt
->seen
) {
789 FILE *idxf
; /* yes, process metadata/index/status */
790 FILE *statf
= mix_parse (stream
,&idxf
,NIL
,LONGT
);
791 elt
->seen
= T
; /* mark as seen */
792 MM_FLAGS (stream
,elt
->msgno
);
793 /* update status file if possible */
794 if (statf
&& !stream
->rdonly
) {
795 elt
->private.mod
= LOCAL
->statusseq
= mix_modseq (LOCAL
->statusseq
);
796 mix_status_update (stream
,statf
,NIL
);
798 if (idxf
) fclose (idxf
); /* release index and status file */
799 if (statf
) fclose (statf
);
801 d
.fd
= LOCAL
->msgfd
; /* set up file descriptor */
802 /* offset of message text */
803 d
.pos
= elt
->private.special
.offset
+ elt
->private.msg
.header
.offset
+
804 elt
->private.msg
.header
.text
.size
;
805 d
.chunk
= LOCAL
->buf
; /* initial buffer chunk */
806 d
.chunksize
= CHUNKSIZE
; /* chunk size */
807 INIT (bs
,fd_string
,&d
,elt
->rfc822_size
- elt
->private.msg
.header
.text
.size
);
811 /* MIX mail modify flags
812 * Accepts: MAIL stream
818 void mix_flag (MAILSTREAM
*stream
,char *sequence
,char *flag
,long flags
)
821 unsigned long i
,uf
,ffkey
;
825 FILE *statf
= mix_parse (stream
,&idxf
,NIL
,LONGT
);
826 unsigned long seq
= mix_modseq (LOCAL
->statusseq
);
827 /* find first free key */
828 for (ffkey
= 0; (ffkey
< NUSERFLAGS
) && stream
->user_flags
[ffkey
]; ++ffkey
);
829 /* parse sequence and flags */
830 if (((flags
& ST_UID
) ? mail_uid_sequence (stream
,sequence
) :
831 mail_sequence (stream
,sequence
)) &&
832 ((f
= mail_parse_flags (stream
,flag
,&uf
)) || uf
)) {
834 for (i
= 1,nf
= (flags
& ST_SET
) ? T
: NIL
; i
<= stream
->nmsgs
; i
++)
835 if ((elt
= mail_elt (stream
,i
))->sequence
) {
836 struct { /* old flags */
837 unsigned int seen
: 1;
838 unsigned int deleted
: 1;
839 unsigned int flagged
: 1;
840 unsigned int answered
: 1;
841 unsigned int draft
: 1;
842 unsigned long user_flags
;
844 old
.seen
= elt
->seen
; old
.deleted
= elt
->deleted
;
845 old
.flagged
= elt
->flagged
; old
.answered
= elt
->answered
;
846 old
.draft
= elt
->draft
; old
.user_flags
= elt
->user_flags
;
847 if (f
&fSEEN
) elt
->seen
= nf
;
848 if (f
&fDELETED
) elt
->deleted
= nf
;
849 if (f
&fFLAGGED
) elt
->flagged
= nf
;
850 if (f
&fANSWERED
) elt
->answered
= nf
;
851 if (f
&fDRAFT
) elt
->draft
= nf
;
853 if (flags
& ST_SET
) elt
->user_flags
|= uf
;
854 else elt
->user_flags
&= ~uf
;
855 if ((old
.seen
!= elt
->seen
) || (old
.deleted
!= elt
->deleted
) ||
856 (old
.flagged
!= elt
->flagged
) ||
857 (old
.answered
!= elt
->answered
) || (old
.draft
!= elt
->draft
) ||
858 (old
.user_flags
!= elt
->user_flags
)) {
859 if (!stream
->rdonly
) elt
->private.mod
= LOCAL
->statusseq
= seq
;
860 MM_FLAGS (stream
,elt
->msgno
);
863 /* update status file after change */
864 if (statf
&& (seq
== LOCAL
->statusseq
))
865 mix_status_update (stream
,statf
,NIL
);
866 /* update metadata if created a keyword */
867 if ((ffkey
< NUSERFLAGS
) && stream
->user_flags
[ffkey
] &&
868 !mix_meta_update (stream
))
869 MM_LOG ("Error updating mix metadata after keyword creation",ERROR
);
871 if (statf
) fclose (statf
); /* release status file if still open */
872 if (idxf
) fclose (idxf
); /* release index file */
875 /* MIX mail sort messages
876 * Accepts: mail stream
881 * Returns: vector of sorted message sequences or NIL if error
884 unsigned long *mix_sort (MAILSTREAM
*stream
,char *charset
,SEARCHPGM
*spg
,
885 SORTPGM
*pgm
,long flags
)
888 FILE *sortcache
= mix_sortcache_open (stream
);
889 ret
= mail_sort_msgs (stream
,charset
,spg
,pgm
,flags
);
890 mix_sortcache_update (stream
,&sortcache
);
895 /* MIX mail thread messages
896 * Accepts: mail stream
901 * Returns: thread node tree or NIL if error
904 THREADNODE
*mix_thread (MAILSTREAM
*stream
,char *type
,char *charset
,
905 SEARCHPGM
*spg
,long flags
)
908 FILE *sortcache
= mix_sortcache_open (stream
);
909 ret
= mail_thread_msgs (stream
,type
,charset
,spg
,flags
,mail_sort_msgs
);
910 mix_sortcache_update (stream
,&sortcache
);
914 /* MIX mail ping mailbox
915 * Accepts: MAIL stream
916 * Returns: T if stream alive, else NIL
919 static int snarfing
= 0; /* lock against recursive snarfing */
921 long mix_ping (MAILSTREAM
*stream
)
928 unsigned long i
,msglen
;
929 char *message
,date
[MAILTMPLEN
],flags
[MAILTMPLEN
];
930 MAILSTREAM
*sysibx
= NIL
;
932 long snarfok
= LONGT
;
934 if (stream
->inbox
&& !stream
->rdonly
&& !snarfing
&&
935 (time (0) >= (LOCAL
->lastsnarf
+
936 (time_t) mail_parameters (NIL
,GET_SNARFINTERVAL
,NIL
)))) {
937 appenduid_t au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
);
938 copyuid_t cu
= (copyuid_t
) mail_parameters (NIL
,GET_COPYUID
,NIL
);
939 MM_CRITICAL (stream
); /* go critical */
940 snarfing
= T
; /* don't recursively snarf */
941 /* disable APPENDUID/COPYUID callbacks */
942 mail_parameters (NIL
,SET_APPENDUID
,NIL
);
943 mail_parameters (NIL
,SET_COPYUID
,NIL
);
944 /* sizes match and anything in sysinbox? */
945 if (!stat (sysinbox (),&sbuf
) && ((sbuf
.st_mode
& S_IFMT
) == S_IFREG
) &&
946 sbuf
.st_size
&& (sysibx
= mail_open (sysibx
,sysinbox (),OP_SILENT
)) &&
947 !sysibx
->rdonly
&& sysibx
->nmsgs
) {
948 /* for each message in sysibx mailbox */
949 for (i
= 1; snarfok
&& (i
<= sysibx
->nmsgs
); ++i
)
950 if (!(elt
= mail_elt (sysibx
,i
))->deleted
&&
951 (message
= mail_fetch_message (sysibx
,i
,&msglen
,FT_PEEK
)) &&
953 mail_date (date
,elt
); /* make internal date string */
954 /* make flag string */
955 flags
[0] = flags
[1] = '\0';
956 if (elt
->seen
) strcat (flags
," \\Seen");
957 if (elt
->flagged
) strcat (flags
," \\Flagged");
958 if (elt
->answered
) strcat (flags
," \\Answered");
959 if (elt
->draft
) strcat (flags
," \\Draft");
962 INIT (&msg
,mail_string
,message
,msglen
);
963 if (snarfok
= mail_append_full (stream
,"INBOX",flags
,date
,&msg
)) {
965 sprintf (sequence
,"%lu",i
);
966 mail_flag (sysibx
,sequence
,"\\Deleted",ST_SET
);
970 /* now expunge all those messages */
971 if (snarfok
) mail_expunge (sysibx
);
973 sprintf (LOCAL
->buf
,"Can't copy new mail at message: %lu",i
- 1);
974 MM_LOG (LOCAL
->buf
,WARN
);
977 if (sysibx
) mail_close (sysibx
);
978 /* reenable APPENDUID/COPYUID */
979 mail_parameters (NIL
,SET_APPENDUID
,(void *) au
);
980 mail_parameters (NIL
,SET_COPYUID
,(void *) cu
);
981 snarfing
= NIL
; /* no longer snarfing */
982 MM_NOCRITICAL (stream
); /* release critical */
983 LOCAL
->lastsnarf
= time (0);/* note time of last snarf */
985 /* expunging OK if global flag set */
986 if (mail_parameters (NIL
,GET_EXPUNGEATPING
,NIL
)) LOCAL
->expok
= T
;
987 /* process metadata/index/status */
988 if (statf
= mix_parse (stream
,&idxf
,LONGT
,
989 (LOCAL
->internal
? NIL
: LONGT
))) {
990 fclose (statf
); /* just close the status file */
991 ret
= LONGT
; /* declare success */
993 if (idxf
) fclose (idxf
); /* release index file */
994 LOCAL
->expok
= NIL
; /* expunge no longer OK */
995 if (!ret
) mix_abort (stream
); /* murdelyze stream if ping fails */
1000 /* MIX mail checkpoint mailbox (burp only)
1001 * Accepts: MAIL stream
1004 void mix_check (MAILSTREAM
*stream
)
1006 if (stream
->rdonly
) /* won't do on readonly files! */
1007 MM_LOG ("Checkpoint ignored on readonly mailbox",NIL
);
1008 /* do burp-only expunge action */
1009 if (mix_expunge (stream
,"",NIL
)) MM_LOG ("Check completed",(long) NIL
);
1012 /* MIX mail expunge mailbox
1013 * Accepts: MAIL stream
1014 * sequence to expunge if non-NIL, empty string for burp only
1016 * Returns: T on success, NIL if failure
1019 long mix_expunge (MAILSTREAM
*stream
,char *sequence
,long options
)
1027 unsigned long nexp
= 0;
1028 unsigned long reclaimed
= 0;
1029 int burponly
= (sequence
&& !*sequence
);
1030 LOCAL
->expok
= T
; /* expunge during ping is OK */
1031 if (!(ret
= burponly
|| !sequence
||
1032 ((options
& EX_UID
) ?
1033 mail_uid_sequence (stream
,sequence
) :
1034 mail_sequence (stream
,sequence
))) || stream
->rdonly
);
1035 /* read index and open status exclusive */
1036 else if (statf
= mix_parse (stream
,&idxf
,LONGT
,
1037 LOCAL
->internal
? NIL
: LONGT
)) {
1038 /* expunge unless just burping */
1039 if (!burponly
) for (i
= 1; i
<= stream
->nmsgs
;) {
1040 elt
= mail_elt (stream
,i
);/* need to expunge this message? */
1041 if (elt
->deleted
&& (sequence
? elt
->sequence
: T
)) {
1042 ++nexp
; /* yes, make it so */
1043 mail_expunged (stream
,i
);
1045 else ++i
; /* otherwise advance to next message */
1048 /* burp if can get exclusive access */
1049 if (!flock (LOCAL
->mfd
,LOCK_EX
|LOCK_NB
)) {
1051 struct direct
**names
= NIL
;
1052 long nfiles
= scandir (stream
->mailbox
,&names
,mix_select
,mix_msgfsort
);
1053 if (nfiles
> 0) { /* if have message files */
1055 /* initialize burp list */
1056 for (i
= 0, burp
= cur
= NIL
; i
< nfiles
; ++i
) {
1057 MIXBURP
*nxt
= (MIXBURP
*) memset (fs_get (sizeof (MIXBURP
)),0,
1059 /* another file found */
1060 if (cur
) cur
= cur
->next
= nxt
;
1061 else cur
= burp
= nxt
;
1062 cur
->name
= cpystr (names
[i
]->d_name
);
1063 cur
->fileno
= strtoul (cur
->name
+ sizeof (MIXNAME
) - 1,NIL
,16);
1064 cur
->tail
= &cur
->set
;
1065 fs_give ((void **) &names
[i
]);
1067 /* now load ranges */
1068 for (i
= 1, cur
= burp
; ret
&& (i
<= stream
->nmsgs
); i
++) {
1069 /* is this message in current set? */
1070 elt
= mail_elt (stream
,i
);
1071 if (cur
&& (elt
->private.spare
.data
!= cur
->fileno
)) {
1072 /* restart if necessary */
1073 if (elt
->private.spare
.data
< cur
->fileno
) cur
= burp
;
1074 /* hunt for appropriate mailbox */
1075 while (cur
&& (elt
->private.spare
.data
> cur
->fileno
))
1077 /* ought to have found it now... */
1078 if (cur
&& (elt
->private.spare
.data
!= cur
->fileno
)) cur
= NIL
;
1080 /* if found, add to set */
1081 if (cur
) ret
= mix_addset (&cur
->tail
,elt
->private.special
.offset
,
1082 elt
->private.msg
.header
.offset
+
1085 sprintf (LOCAL
->buf
,"Can't locate mix message file %.08lx",
1086 elt
->private.spare
.data
);
1087 MM_LOG (LOCAL
->buf
,ERROR
);
1091 if (ret
) /* if no errors, burp all files */
1092 for (cur
= burp
; ret
&& cur
; cur
= cur
->next
) {
1093 /* if non-empty, burp it */
1094 if (cur
->set
.last
) ret
= mix_burp (stream
,cur
,&reclaimed
);
1095 /* empty, delete it unless new msg file */
1096 else if (mix_file_data (LOCAL
->buf
,stream
->mailbox
,cur
->fileno
) &&
1097 ((cur
->fileno
== LOCAL
->newmsg
) ?
1098 truncate (LOCAL
->buf
,0) : unlink (LOCAL
->buf
))) {
1099 sprintf (LOCAL
->buf
,
1100 "Can't delete empty message file %.80s: %.80s",
1101 cur
->name
,strerror (errno
));
1102 MM_LOG (LOCAL
->buf
,WARN
);
1105 while (burp
) { /* flush the burp list */
1107 if (burp
->name
) fs_give ((void **) &burp
->name
);
1108 fs_give ((void **) &burp
);
1112 else MM_LOG ("No mix message files found during expunge",WARN
);
1113 /* free directory list */
1114 if (a
= (void *) names
) fs_give ((void **) &a
);
1117 /* either way, re-acquire shared lock */
1118 if (flock (LOCAL
->mfd
,LOCK_SH
|LOCK_NB
))
1119 fatal ("Unable to re-acquire metadata shared lock!");
1120 /* Do this step even if ret is NIL (meaning some burp problem)! */
1121 if (nexp
|| reclaimed
) { /* rewrite index and status if changed */
1122 LOCAL
->indexseq
= mix_modseq (LOCAL
->indexseq
);
1123 if (ret
= mix_index_update (stream
,idxf
,NIL
)) {
1124 LOCAL
->statusseq
= mix_modseq (LOCAL
->statusseq
);
1125 /* set failure if update fails */
1126 ret
= mix_status_update (stream
,statf
,NIL
);
1130 if (statf
) fclose (statf
); /* close status if still open */
1131 if (idxf
) fclose (idxf
); /* close index if still open */
1132 LOCAL
->expok
= NIL
; /* cancel expok */
1133 if (ret
) { /* only if success */
1135 if (nexp
) sprintf (s
= LOCAL
->buf
,"Expunged %lu messages",nexp
);
1137 sprintf (s
=LOCAL
->buf
,"Reclaimed %lu bytes of expunged space",reclaimed
);
1139 s
= stream
->rdonly
? "Expunge ignored on readonly mailbox" :
1140 "No messages deleted, so no update needed";
1141 if (s
) MM_LOG (s
,(long) NIL
);
1146 /* MIX test for message file name
1147 * Accepts: candidate directory name
1148 * Returns: T if message file name, NIL otherwise
1150 * ".mix" with no suffix was used by experimental versions
1153 int mix_select (struct direct
*name
)
1156 /* make sure name has prefix */
1157 if (mix_dirfmttest (name
->d_name
)) {
1158 for (c
= *(s
= name
->d_name
+ sizeof (MIXNAME
) - 1); c
&& isxdigit (c
);
1160 if (!c
) return T
; /* all-hex or no suffix */
1162 return NIL
; /* not suffix or non-hex */
1166 /* MIX msg file name comparision
1167 * Accepts: first candidate directory entry
1168 * second candidate directory entry
1169 * Returns: -1 if d1 < d2, 0 if d1 == d2, 1 d1 > d2
1172 int mix_msgfsort (const void *d1
,const void *d2
)
1174 char *n1
= (*(struct direct
**) d1
)->d_name
+ sizeof (MIXNAME
) - 1;
1175 char *n2
= (*(struct direct
**) d2
)->d_name
+ sizeof (MIXNAME
) - 1;
1176 return compare_ulong (*n1
? strtoul (n1
,NIL
,16) : 0,
1177 *n2
? strtoul (n2
,NIL
,16) : 0);
1181 /* MIX add a range to a set
1182 * Accepts: pointer to set to add
1185 * Returns: T if success, set updated, NIL otherwise
1188 long mix_addset (SEARCHSET
**set
,unsigned long start
,unsigned long size
)
1190 SEARCHSET
*s
= *set
;
1191 if (start
< s
->last
) { /* sanity check */
1192 char tmp
[MAILTMPLEN
];
1193 sprintf (tmp
,"Backwards-running mix index %lu < %lu",start
,s
->last
);
1197 /* range initially empty? */
1198 if (!s
->last
) s
->first
= start
;
1199 else if (start
> s
->last
) /* no, start new range if can't append */
1200 (*set
= s
= s
->next
= mail_newsearchset ())->first
= start
;
1201 s
->last
= start
+ size
; /* end of current range */
1205 /* MIX burp message file
1206 * Accepts: MAIL stream
1207 * current burp block for this message
1208 * Returns: T if successful, NIL if failed
1211 static char *staterr
= "Error in stat of mix message file %.80s: %.80s";
1212 static char *truncerr
= "Error truncating mix message file %.80s: %.80s";
1214 long mix_burp (MAILSTREAM
*stream
,MIXBURP
*burp
,unsigned long *reclaimed
)
1220 size_t size
,wsize
,wpending
,written
;
1226 /* build file name */
1227 mix_file_data (LOCAL
->buf
,stream
->mailbox
,burp
->fileno
);
1228 /* need to burp at start or multiple ranges? */
1229 if (!burp
->set
.first
&& !burp
->set
.next
) {
1230 /* easy case, single range at start of file */
1231 if (stat (LOCAL
->buf
,&sbuf
)) {
1232 sprintf (LOCAL
->buf
,staterr
,burp
->name
,strerror (errno
));
1233 MM_LOG (LOCAL
->buf
,ERROR
);
1235 /* is this range sane? */
1236 else if (mix_burp_check (&burp
->set
,sbuf
.st_size
,LOCAL
->buf
)) {
1237 /* if matches range then no burp needed! */
1238 if (burp
->set
.last
== sbuf
.st_size
) ret
= LONGT
;
1239 /* just need to remove cruft at end */
1240 else if (ret
= !truncate (LOCAL
->buf
,burp
->set
.last
))
1241 *reclaimed
+= sbuf
.st_size
- burp
->set
.last
;
1243 sprintf (LOCAL
->buf
,truncerr
,burp
->name
,strerror (errno
));
1244 MM_LOG (LOCAL
->buf
,ERROR
);
1248 /* have to do more work, get the file */
1249 else if (((fd
= open (LOCAL
->buf
,O_RDWR
,NIL
)) < 0) ||
1250 !(f
= fdopen (fd
,"r+b"))) {
1251 sprintf (LOCAL
->buf
,"Error opening mix message file %.80s: %.80s",
1252 burp
->name
,strerror (errno
));
1253 MM_LOG (LOCAL
->buf
,ERROR
);
1254 if (fd
>= 0) close (fd
); /* in case fdopen() failure */
1256 else if (fstat (fd
,&sbuf
)) { /* get file size */
1257 sprintf (LOCAL
->buf
,staterr
,burp
->name
,strerror (errno
));
1258 MM_LOG (LOCAL
->buf
,ERROR
);
1263 else if (mix_burp_check (&burp
->set
,sbuf
.st_size
,LOCAL
->buf
)) {
1264 /* make sure each range starts with token */
1265 for (set
= &burp
->set
; set
; set
= set
->next
)
1266 if (fseek (f
,set
->first
,SEEK_SET
) ||
1267 (fread (LOCAL
->buf
,1,MSGTSZ
,f
) != MSGTSZ
) ||
1268 strncmp (LOCAL
->buf
,MSGTOK
,MSGTSZ
)) {
1269 sprintf (LOCAL
->buf
,"Bad message token in mix message file at %lu",
1271 MM_LOG (LOCAL
->buf
,ERROR
);
1273 return NIL
; /* burp fails for this file */
1275 /* burp out each old message */
1276 for (set
= &burp
->set
, rpos
= wpos
= 0; set
; set
= set
->next
) {
1277 /* move down this range */
1278 for (rpos
= set
->first
, size
= set
->last
- set
->first
;
1279 size
; size
-= wsize
) {
1280 if (rpos
!= wpos
) { /* data to skip at start? */
1281 /* no, slide this buffer down */
1282 wsize
= min (size
,LOCAL
->buflen
);
1283 /* failure is not an option here */
1284 while (fseek (f
,rpos
,SEEK_SET
) ||
1285 (fread (LOCAL
->buf
,1,wsize
,f
) != wsize
)) {
1286 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1287 MM_DISKERROR (stream
,errno
,T
);
1290 while (fseek (f
,wpos
,SEEK_SET
)) {
1291 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1292 MM_DISKERROR (stream
,errno
,T
);
1294 /* and especially not here */
1295 for (s
= LOCAL
->buf
, wpending
= wsize
; wpending
; s
+= written
, wpending
-= written
)
1296 if (!(written
= fwrite (s
,1,wpending
,f
))) {
1297 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1298 MM_DISKERROR (stream
,errno
,T
);
1301 else wsize
= size
; /* nothing to skip, say we wrote it all */
1302 rpos
+= wsize
; wpos
+= wsize
;
1306 while (fflush (f
)) { /* failure also not an option here... */
1307 MM_NOTIFY (stream
,strerror (errno
),WARN
);
1308 MM_DISKERROR (stream
,errno
,T
);
1310 if (ftruncate (fd
,wpos
)) { /* flush cruft at end of file */
1311 sprintf (LOCAL
->buf
,truncerr
,burp
->name
,strerror (errno
));
1312 MM_LOG (LOCAL
->buf
,WARN
);
1314 else *reclaimed
+= rpos
- wpos
;
1315 ret
= !fclose (f
); /* close file */
1316 /* slide down message positions in index */
1317 for (i
= 1,rpos
= 0; i
<= stream
->nmsgs
; ++i
)
1318 if ((elt
= mail_elt (stream
,i
))->private.spare
.data
== burp
->fileno
) {
1319 elt
->private.special
.offset
= rpos
;
1320 rpos
+= elt
->private.msg
.header
.offset
+ elt
->rfc822_size
;
1323 if (rpos
!= wpos
) fatal ("burp size consistency check!");
1329 /* MIX burp sanity check to make sure not burping off end of file
1333 * Returns: T if sane, NIL if insane
1336 long mix_burp_check (SEARCHSET
*set
,size_t size
,char *file
)
1338 do if (set
->last
> size
) { /* sanity check */
1339 char tmp
[MAILTMPLEN
];
1340 sprintf (tmp
,"Unexpected short mix message file %.80s %lu < %lu",
1341 file
,size
,set
->last
);
1343 return NIL
; /* don't burp this file at all */
1344 } while (set
= set
->next
);
1348 /* MIX mail copy message(s)
1349 * Accepts: MAIL stream
1351 * destination mailbox
1353 * Returns: T if copy successful, else NIL
1356 long mix_copy (MAILSTREAM
*stream
,char *sequence
,char *mailbox
,long options
)
1360 char tmp
[2*MAILTMPLEN
];
1361 long ret
= mix_isvalid (mailbox
,LOCAL
->buf
);
1362 mailproxycopy_t pc
=
1363 (mailproxycopy_t
) mail_parameters (stream
,GET_MAILPROXYCOPY
,NIL
);
1364 MAILSTREAM
*astream
= NIL
;
1368 if (!ret
) switch (errno
) { /* make sure valid mailbox */
1369 case NIL
: /* no error in stat() */
1370 if (pc
) return (*pc
) (stream
,sequence
,mailbox
,options
);
1371 sprintf (tmp
,"Not a MIX-format mailbox: %.80s",mailbox
);
1374 default: /* some stat() error */
1375 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before copy",NIL
);
1378 /* get sequence to copy */
1379 else if (!(ret
= ((options
& CP_UID
) ? mail_uid_sequence (stream
,sequence
) :
1380 mail_sequence (stream
,sequence
))));
1381 /* acquire stream to append */
1382 else if (ret
= ((astream
= mail_open (NIL
,mailbox
,OP_SILENT
)) &&
1384 (((MIXLOCAL
*) astream
->local
)->expok
= T
) &&
1385 (statf
= mix_parse (astream
,&idxf
,LONGT
,NIL
))) ?
1390 unsigned long newsize
,hdrsize
,size
;
1391 MIXLOCAL
*local
= (MIXLOCAL
*) astream
->local
;
1392 unsigned long seq
= mix_modseq (local
->metaseq
);
1393 /* make sure new modseq fits */
1394 if (local
->indexseq
> seq
) seq
= local
->indexseq
+ 1;
1395 if (local
->statusseq
> seq
) seq
= local
->statusseq
+ 1;
1396 /* calculate size of per-message header */
1397 sprintf (local
->buf
,MSRFMT
,MSGTOK
,(unsigned long) 0,0,0,0,0,0,0,'+',0,0,
1399 hdrsize
= strlen (local
->buf
);
1401 MM_CRITICAL (stream
); /* go critical */
1402 astream
->silent
= T
; /* no events here */
1403 /* calculate size that will be added */
1404 for (i
= 1, newsize
= 0; i
<= stream
->nmsgs
; ++i
)
1405 if ((elt
= mail_elt (stream
,i
))->sequence
)
1406 newsize
+= hdrsize
+ elt
->rfc822_size
;
1407 /* open data file */
1408 if (msgf
= mix_data_open (astream
,&fd
,&size
,newsize
)) {
1410 unsigned long j
,uid
,uidv
;
1411 copyuid_t cu
= (copyuid_t
) mail_parameters (NIL
,GET_COPYUID
,NIL
);
1412 SEARCHSET
*source
= cu
? mail_newsearchset () : NIL
;
1413 SEARCHSET
*dest
= cu
? mail_newsearchset () : NIL
;
1414 for (i
= 1,uid
= uidv
= 0; ret
&& (i
<= stream
->nmsgs
); ++i
)
1415 if (((elt
= mail_elt (stream
,i
))->sequence
) && elt
->rfc822_size
) {
1416 /* is message in current message file? */
1417 if ((LOCAL
->msgfd
< 0) ||
1418 (elt
->private.spare
.data
!= LOCAL
->curmsg
)) {
1419 if (LOCAL
->msgfd
>= 0) close (LOCAL
->msgfd
);
1420 if ((LOCAL
->msgfd
= open (mix_file_data (LOCAL
->buf
,
1422 elt
->private.spare
.data
),
1423 O_RDONLY
,NIL
)) >= 0)
1424 LOCAL
->curmsg
= elt
->private.spare
.data
;
1426 if (LOCAL
->msgfd
< 0) ret
= NIL
;
1427 else { /* got file */
1428 d
.fd
= LOCAL
->msgfd
;/* set up file descriptor */
1429 /* start of message */
1430 d
.pos
= elt
->private.special
.offset
+
1431 elt
->private.msg
.header
.offset
;
1432 d
.chunk
= LOCAL
->buf
;
1433 d
.chunksize
= CHUNKSIZE
;
1434 INIT (&st
,fd_string
,&d
,elt
->rfc822_size
);
1435 /* init flag string */
1436 tmp
[0] = tmp
[1] = '\0';
1437 if (j
= elt
->user_flags
) do
1438 if ((t
= stream
->user_flags
[find_rightmost_bit (&j
)]) && *t
)
1439 strcat (strcat (tmp
," "),t
);
1441 if (elt
->seen
) strcat (tmp
," \\Seen");
1442 if (elt
->deleted
) strcat (tmp
," \\Deleted");
1443 if (elt
->flagged
) strcat (tmp
," \\Flagged");
1444 if (elt
->answered
) strcat (tmp
," \\Answered");
1445 if (elt
->draft
) strcat (tmp
," \\Draft");
1446 tmp
[0] = '('; /* wrap list */
1448 /* if append OK, add to source set */
1449 if ((ret
= mix_append_msg (astream
,msgf
,tmp
,elt
,&st
,dest
,
1451 mail_append_set (source
,mail_uid (stream
,i
));
1455 /* finish write if success */
1456 if (ret
&& (ret
= !fflush (msgf
))) {
1457 fclose (msgf
); /* all good, close the msg file now */
1458 /* write new metadata, index, and status */
1459 local
->metaseq
= local
->indexseq
= local
->statusseq
= seq
;
1460 if (ret
= (mix_meta_update (astream
) &&
1461 mix_index_update (astream
,idxf
,LONGT
))) {
1462 /* success, delete if doing a move */
1463 if (options
& CP_MOVE
)
1464 for (i
= 1; i
<= stream
->nmsgs
; i
++)
1465 if ((elt
= mail_elt (stream
,i
))->sequence
) {
1467 if (!stream
->rdonly
) elt
->private.mod
= LOCAL
->statusseq
= seq
;
1468 MM_FLAGS (stream
,elt
->msgno
);
1470 /* done with status file now */
1471 mix_status_update (astream
,statf
,LONGT
);
1472 /* return sets if doing COPYUID */
1473 if (cu
) (*cu
) (stream
,mailbox
,astream
->uid_validity
,source
,dest
);
1474 source
= dest
= NIL
; /* don't free these sets now */
1478 if (errno
) { /* output error message if system call error */
1479 sprintf (tmp
,"Message copy failed: %.80s",strerror (errno
));
1482 ftruncate (fd
,size
); /* revert file */
1483 close (fd
); /* make sure that fclose doesn't corrupt us */
1484 fclose (msgf
); /* free the stdio resources */
1486 /* flush any sets remaining */
1487 mail_free_searchset (&source
);
1488 mail_free_searchset (&dest
);
1490 else { /* message file open failed */
1491 sprintf (tmp
,"Error opening copy message file: %.80s",
1496 MM_NOCRITICAL (stream
);
1498 else MM_LOG ("Can't open copy mailbox",ERROR
);
1499 if (statf
) fclose (statf
); /* close status if still open */
1500 if (idxf
) fclose (idxf
); /* close index if still open */
1501 /* finished with append stream */
1502 if (astream
) mail_close (astream
);
1503 return ret
; /* return state */
1506 /* MIX mail append message from stringstruct
1507 * Accepts: MAIL stream
1508 * destination mailbox
1511 * Returns: T if append successful, else NIL
1514 long mix_append (MAILSTREAM
*stream
,char *mailbox
,append_t af
,void *data
)
1517 char *flags
,*date
,tmp
[MAILTMPLEN
];
1518 /* N.B.: can't use LOCAL->buf for tmp */
1519 long ret
= mix_isvalid (mailbox
,tmp
);
1520 /* default stream to prototype */
1521 if (!stream
) stream
= user_flags (&mixproto
);
1522 if (!ret
) switch (errno
) { /* if not valid mailbox */
1523 case ENOENT
: /* no such file? */
1524 if (ret
= compare_cstring (mailbox
,"INBOX") ?
1525 NIL
: mix_create (NIL
,"INBOX"))
1527 MM_NOTIFY (stream
,"[TRYCREATE] Must create mailbox before append",NIL
);
1530 sprintf (tmp
,"Not a MIX-format mailbox: %.80s",mailbox
);
1535 /* get first message */
1536 if (ret
&& MM_APPEND (af
) (stream
,data
,&flags
,&date
,&message
)) {
1537 MAILSTREAM
*astream
;
1541 if (ret
= ((astream
= mail_open (NIL
,mailbox
,OP_SILENT
)) &&
1543 (((MIXLOCAL
*) astream
->local
)->expok
= T
) &&
1544 (statf
= mix_parse (astream
,&idxf
,LONGT
,NIL
))) ?
1547 unsigned long size
,hdrsize
;
1549 MIXLOCAL
*local
= (MIXLOCAL
*) astream
->local
;
1550 unsigned long seq
= mix_modseq (local
->metaseq
);
1551 /* make sure new modseq fits */
1552 if (local
->indexseq
> seq
) seq
= local
->indexseq
+ 1;
1553 if (local
->statusseq
> seq
) seq
= local
->statusseq
+ 1;
1554 /* calculate size of per-message header */
1555 sprintf (local
->buf
,MSRFMT
,MSGTOK
,(unsigned long) 0,0,0,0,0,0,0,'+',0,0,
1557 hdrsize
= strlen (local
->buf
);
1558 MM_CRITICAL (astream
); /* go critical */
1559 astream
->silent
= T
; /* no events here */
1560 /* open data file */
1561 if (msgf
= mix_data_open (astream
,&fd
,&size
,hdrsize
+ SIZE (message
))) {
1562 appenduid_t au
= (appenduid_t
) mail_parameters (NIL
,GET_APPENDUID
,NIL
);
1563 SEARCHSET
*dst
= au
? mail_newsearchset () : NIL
;
1564 while (ret
&& message
) {/* while good to go and have messages */
1565 errno
= NIL
; /* in case one of these causes failure */
1566 /* guard against zero-length */
1567 if (!(ret
= SIZE (message
)))
1568 MM_LOG ("Append of zero-length message",ERROR
);
1569 else if (date
&& !(ret
= mail_parse_date (&elt
,date
))) {
1570 sprintf (tmp
,"Bad date in append: %.80s",date
);
1574 if (!date
) { /* if date not specified, use now */
1575 internal_date (tmp
);
1576 mail_parse_date (&elt
,tmp
);
1578 ret
= mix_append_msg (astream
,msgf
,flags
,&elt
,message
,dst
,seq
) &&
1579 MM_APPEND (af
) (stream
,data
,&flags
,&date
,&message
);
1583 /* finish write if success */
1584 if (ret
&& (ret
= !fflush (msgf
))) {
1585 fclose (msgf
); /* all good, close the msg file now */
1586 /* write new metadata, index, and status */
1587 local
->metaseq
= local
->indexseq
= local
->statusseq
= seq
;
1588 if ((ret
= (mix_meta_update (astream
) &&
1589 mix_index_update (astream
,idxf
,LONGT
) &&
1590 mix_status_update (astream
,statf
,LONGT
))) && au
) {
1591 (*au
) (mailbox
,astream
->uid_validity
,dst
);
1592 dst
= NIL
; /* don't free this set now */
1595 else { /* failure */
1596 if (errno
) { /* output error message if system call error */
1597 sprintf (tmp
,"Message append failed: %.80s",strerror (errno
));
1600 ftruncate (fd
,size
); /* revert all writes to file*/
1601 close (fd
); /* make sure that fclose doesn't corrupt us */
1602 fclose (msgf
); /* free the stdio resources */
1604 /* flush any set remaining */
1605 mail_free_searchset (&dst
);
1607 else { /* message file open failed */
1608 sprintf (tmp
,"Error opening append message file: %.80s",
1613 MM_NOCRITICAL (astream
); /* release critical */
1615 else MM_LOG ("Can't open append mailbox",ERROR
);
1616 if (statf
) fclose (statf
); /* close status if still open */
1617 if (idxf
) fclose (idxf
); /* close index if still open */
1618 if (astream
) mail_close (astream
);
1623 /* MIX mail append single message
1624 * Accepts: MAIL stream
1625 * flags for new message if non-NIL
1626 * elt with source date if non-NIL
1627 * stringstruct of message text
1628 * searchset to place UID
1630 * Returns: T if success, NIL if failure
1633 long mix_append_msg (MAILSTREAM
*stream
,FILE *f
,char *flags
,MESSAGECACHE
*delt
,
1634 STRING
*msg
,SEARCHSET
*set
,unsigned long seq
)
1638 unsigned long i
,j
,k
,uf
,hoff
;
1641 stream
->kwd_create
= NIL
; /* don't copy unknown keywords */
1642 sf
= mail_parse_flags (stream
,flags
,&uf
);
1643 /* swell the cache */
1644 mail_exists (stream
,++stream
->nmsgs
);
1645 /* assign new UID from metadata */
1646 (elt
= mail_elt (stream
,stream
->nmsgs
))->private.uid
= ++stream
->uid_last
;
1647 elt
->private.mod
= seq
; /* set requested modseq in status */
1648 elt
->rfc822_size
= SIZE (msg
);/* copy message size and date to index */
1649 elt
->year
= delt
->year
; elt
->month
= delt
->month
; elt
->day
= delt
->day
;
1650 elt
->hours
= delt
->hours
; elt
->minutes
= delt
->minutes
;
1651 elt
->seconds
= delt
->seconds
; elt
->zoccident
= delt
->zoccident
;
1652 elt
->zhours
= delt
->zhours
; elt
->zminutes
= delt
->zminutes
;
1654 * Do NOT set elt->valid here! mix_status_update() uses it to determine
1655 * whether a message should be marked as old.
1657 if (sf
&fSEEN
) elt
->seen
= T
; /* copy flags to status */
1658 if (sf
&fDELETED
) elt
->deleted
= T
;
1659 if (sf
&fFLAGGED
) elt
->flagged
= T
;
1660 if (sf
&fANSWERED
) elt
->answered
= T
;
1661 if (sf
&fDRAFT
) elt
->draft
= T
;
1662 elt
->user_flags
|= uf
;
1663 /* message is in new message file */
1664 elt
->private.spare
.data
= LOCAL
->newmsg
;
1666 /* offset to message internal header */
1667 elt
->private.special
.offset
= ftell (f
);
1668 /* build header for message */
1669 fprintf (f
,MSRFMT
,MSGTOK
,elt
->private.uid
,
1670 elt
->year
+ BASEYEAR
,elt
->month
,elt
->day
,
1671 elt
->hours
,elt
->minutes
,elt
->seconds
,
1672 elt
->zoccident
? '-' : '+',elt
->zhours
,elt
->zminutes
,
1674 /* offset to header from internal header */
1675 elt
->private.msg
.header
.offset
= ftell (f
) - elt
->private.special
.offset
;
1676 for (cs
= 0; SIZE (msg
); ) { /* copy message */
1677 if (elt
->private.msg
.header
.text
.size
) {
1678 if (msg
->cursize
) /* blat entire chunk if have it */
1679 for (s
= msg
->curpos
,j
= msg
->cursize
; j
; s
+= k
, j
-= k
)
1680 if (!(k
= fwrite (s
,1,j
,f
))) return NIL
;
1681 SETPOS (msg
,GETPOS (msg
) + msg
->cursize
);
1683 else { /* still searching for delimiter */
1684 c
= 0xff & SNX (msg
); /* get source character */
1685 if (putc (c
,f
) == EOF
) return NIL
;
1686 switch (cs
) { /* decide what to do based on state */
1687 case 0: /* previous char ordinary */
1688 if (c
== '\015') cs
= 1;/* advance if CR */
1690 case 1: /* previous CR, advance if LF */
1691 cs
= (c
== '\012') ? 2 : 0;
1693 case 2: /* previous CRLF, advance if CR */
1694 cs
= (c
== '\015') ? 3 : 0;
1696 case 3: /* previous CRLFCR, done if LF */
1697 if (c
== '\012') elt
->private.msg
.header
.text
.size
=
1698 elt
->rfc822_size
- SIZE (msg
);
1699 cs
= 0; /* reset mechanism */
1704 /* if no delimiter, header is entire msg */
1705 if (!elt
->private.msg
.header
.text
.size
)
1706 elt
->private.msg
.header
.text
.size
= elt
->rfc822_size
;
1707 /* add this message to set */
1708 mail_append_set (set
,elt
->private.uid
);
1709 return LONGT
; /* success */
1712 /* MIX mail read metadata, index, and status
1713 * Accepts: MAIL stream
1714 * returned index file
1715 * index file flags (non-NIL if want to add/remove messages)
1716 * status file flags (non-NIL if want to update elt->valid and old)
1717 * Returns: open status file, or NIL if failure
1719 * Note that this routine can return an open index file even if it fails!
1722 static char *shortmsg
=
1723 "message %lu (UID=%.08lx) truncated by %lu byte(s) (%lu < %lu)";
1725 FILE *mix_parse (MAILSTREAM
*stream
,FILE **idxf
,long iflags
,long sflags
)
1732 short metarepairneeded
= 0;
1733 short indexrepairneeded
= 0;
1734 short silent
= stream
->silent
;
1735 *idxf
= NIL
; /* in case error */
1736 /* readonly means no updates */
1737 if (stream
->rdonly
) iflags
= sflags
= NIL
;
1738 /* open index file */
1739 if ((fd
= open (LOCAL
->index
,iflags
? O_RDWR
: O_RDONLY
,NIL
)) < 0)
1740 MM_LOG ("Error opening mix index file",ERROR
);
1741 /* acquire exclusive access and FILE */
1742 else if (!flock (fd
,iflags
? LOCK_EX
: LOCK_SH
) &&
1743 !(*idxf
= fdopen (fd
,iflags
? "r+b" : "rb"))) {
1744 MM_LOG ("Error obtaining stream on mix index file",ERROR
);
1745 flock (fd
,LOCK_UN
); /* relinquish lock */
1749 /* slurp metadata */
1750 else if (s
= mix_meta_slurp (stream
,&i
)) {
1751 unsigned long j
= 0; /* non-zero if UIDVALIDITY/UIDLAST changed */
1752 if (i
!= LOCAL
->metaseq
) { /* metadata changed? */
1754 LOCAL
->metaseq
= i
; /* note new metadata sequence */
1755 while (s
&& *s
) { /* parse entire metadata file */
1756 /* locate end of line */
1757 if (s
= strstr (t
= s
,"\015\012")) {
1758 *s
= '\0'; /* tie off line */
1759 s
+= 2; /* skip past CRLF */
1760 switch (*t
++) { /* parse line */
1761 case 'V': /* UIDVALIDITY */
1762 if (!isxdigit (*t
) || !(i
= strtoul (t
,&t
,16))) {
1763 MM_LOG ("Error in mix metadata file UIDVALIDITY record",ERROR
);
1764 return NIL
; /* give up */
1766 if (i
!= stream
->uid_validity
) j
= stream
->uid_validity
= i
;
1768 case 'L': /* new UIDLAST */
1769 if (!isxdigit (*t
)) {
1770 MM_LOG ("Error in mix metadata file UIDLAST record",ERROR
);
1771 return NIL
; /* give up */
1773 if ((i
= strtoul (t
,&t
,16)) != stream
->uid_last
)
1774 j
= stream
->uid_last
= i
;
1776 case 'N': /* new message file */
1777 if (!isxdigit (*t
)) {
1778 MM_LOG ("Error in mix metadata file new msg record",ERROR
);
1779 return NIL
; /* give up */
1781 if ((i
= strtoul (t
,&t
,16)) != stream
->uid_last
)
1784 case 'K': /* new keyword list */
1785 for (i
= 0; t
&& *t
&& (i
< NUSERFLAGS
); ++i
) {
1786 if (t
= strchr (k
= t
,' ')) *t
++ = '\0';
1787 /* make sure keyword non-empty */
1788 if (*k
&& (strlen (k
) <= MAXUSERFLAG
)) {
1789 /* in case value changes (shouldn't happen) */
1790 if (stream
->user_flags
[i
] && strcmp (stream
->user_flags
[i
],k
)){
1791 char tmp
[MAILTMPLEN
];
1792 sprintf (tmp
,"flag rename old=%.80s new=%.80s",
1793 stream
->user_flags
[i
],k
);
1795 fs_give ((void **) &stream
->user_flags
[i
]);
1797 if (!stream
->user_flags
[i
]) stream
->user_flags
[i
] = cpystr (k
);
1799 else break; /* empty keyword */
1801 if ((i
< NUSERFLAGS
) && stream
->user_flags
[i
]) {
1802 MM_LOG ("Error in mix metadata file keyword record",ERROR
);
1803 return NIL
; /* give up */
1805 else if (i
== NUSERFLAGS
) stream
->kwd_create
= NIL
;
1809 if (t
&& *t
) { /* junk in line */
1810 MM_LOG ("Error in mix metadata record",ERROR
);
1811 return NIL
; /* give up */
1817 if (!(i
= mix_read_sequence (*idxf
)) || (i
< LOCAL
->indexseq
)) {
1818 MM_LOG ("Error in mix index file sequence record",ERROR
);
1819 return NIL
; /* give up */
1821 /* sequence changed from last time? */
1822 else if (j
|| (i
> LOCAL
->indexseq
)) {
1823 unsigned long prevuid
= 0;
1824 unsigned long uid
,nmsgs
,curfile
,curfilesize
,curpos
;
1825 char *t
,*msg
,tmp
[MAILTMPLEN
];
1826 /* start with no messages */
1827 curfile
= curfilesize
= curpos
= nmsgs
= 0;
1828 /* update sequence iff expunging OK */
1829 if (LOCAL
->expok
) LOCAL
->indexseq
= i
;
1831 while ((s
= mix_read_record (*idxf
,LOCAL
->buf
,LOCAL
->buflen
,"index")) &&
1834 case ':': /* message record */
1835 if (!(isxdigit (*++s
) && (uid
= strtoul (s
,&t
,16)))) msg
= "UID";
1836 else if (!((*t
++ == ':') && isdigit (*t
) && isdigit (t
[1]) &&
1837 isdigit (t
[2]) && isdigit (t
[3]) && isdigit (t
[4]) &&
1838 isdigit (t
[5]) && isdigit (t
[6]) && isdigit (t
[7]) &&
1839 isdigit (t
[8]) && isdigit (t
[9]) && isdigit (t
[10]) &&
1840 isdigit (t
[11]) && isdigit (t
[12]) && isdigit (t
[13]) &&
1841 ((t
[14] == '+') || (t
[14] == '-')) &&
1842 isdigit (t
[15]) && isdigit (t
[16]) && isdigit (t
[17]) &&
1843 isdigit (t
[18]))) msg
= "internaldate";
1844 else if ((*(s
= t
+19) != ':') || !isxdigit (*++s
)) msg
= "size";
1846 unsigned int y
= (((*t
- '0') * 1000) + ((t
[1] - '0') * 100) +
1847 ((t
[2] - '0') * 10) + t
[3] - '0') - BASEYEAR
;
1848 unsigned int m
= ((t
[4] - '0') * 10) + t
[5] - '0';
1849 unsigned int d
= ((t
[6] - '0') * 10) + t
[7] - '0';
1850 unsigned int hh
= ((t
[8] - '0') * 10) + t
[9] - '0';
1851 unsigned int mm
= ((t
[10] - '0') * 10) + t
[11] - '0';
1852 unsigned int ss
= ((t
[12] - '0') * 10) + t
[13] - '0';
1853 unsigned int z
= (t
[14] == '-') ? 1 : 0;
1854 unsigned int zh
= ((t
[15] - '0') * 10) + t
[16] - '0';
1855 unsigned int zm
= ((t
[17] - '0') * 10) + t
[18] - '0';
1856 unsigned long size
= strtoul (s
,&s
,16);
1857 if ((*s
++ == ':') && isxdigit (*s
)) {
1858 unsigned long file
= strtoul (s
,&s
,16);
1859 if ((*s
++ == ':') && isxdigit (*s
)) {
1860 unsigned long pos
= strtoul (s
,&s
,16);
1861 if ((*s
++ == ':') && isxdigit (*s
)) {
1862 unsigned long hpos
= strtoul (s
,&s
,16);
1863 if ((*s
++ == ':') && isxdigit (*s
)) {
1864 unsigned long hsiz
= strtoul (s
,&s
,16);
1865 if (uid
> stream
->uid_last
) {
1866 sprintf (tmp
,"mix index invalid UID (%08lx < %08lx)",
1867 uid
,stream
->uid_last
);
1868 if (stream
->rdonly
) {
1872 strcat (tmp
,", repaired");
1874 stream
->uid_last
= uid
;
1875 metarepairneeded
= T
;
1878 /* ignore expansion values */
1882 sprintf (tmp
,"mix index backwards UID: %lx",uid
);
1887 ++nmsgs
; /* this is another mesage */
1888 /* within current known range of messages? */
1889 while (nmsgs
<= stream
->nmsgs
) {
1890 /* yes, get corresponding elt */
1891 elt
= mail_elt (stream
,nmsgs
);
1892 /* existing message with matching data? */
1893 if (uid
== elt
->private.uid
) {
1894 /* beware of Dracula's resurrection */
1895 if (elt
->private.ghost
) {
1896 sprintf (tmp
,"mix index data unexpunged UID: %lx",
1901 /* also of static data changing */
1902 if ((size
!= elt
->rfc822_size
) ||
1903 (file
!= elt
->private.spare
.data
) ||
1904 (pos
!= elt
->private.special
.offset
) ||
1905 (hpos
!= elt
->private.msg
.header
.offset
) ||
1906 (hsiz
!= elt
->private.msg
.header
.text
.size
) ||
1907 (y
!= elt
->year
) || (m
!= elt
->month
) ||
1908 (d
!= elt
->day
) || (hh
!= elt
->hours
) ||
1909 (mm
!= elt
->minutes
) || (ss
!= elt
->seconds
) ||
1910 (z
!= elt
->zoccident
) || (zh
!= elt
->zhours
) ||
1911 (zm
!= elt
->zminutes
)) {
1912 sprintf (tmp
,"mix index data mismatch: %lx",uid
);
1918 /* existing msg with lower UID is expunged */
1919 else if (uid
> elt
->private.uid
) {
1920 if (LOCAL
->expok
) mail_expunged (stream
,nmsgs
);
1921 else {/* message expunged, but not yet for us */
1923 elt
->private.ghost
= T
;
1926 else { /* unexpected message record */
1927 sprintf (tmp
,"mix index UID mismatch (%lx < %lx)",
1928 uid
,elt
->private.uid
);
1934 /* time to create a new message? */
1935 if (nmsgs
> stream
->nmsgs
) {
1936 /* defer announcing until later */
1938 mail_exists (stream
,nmsgs
);
1939 stream
->silent
= silent
;
1940 (elt
= mail_elt (stream
,nmsgs
))->recent
= T
;
1941 elt
->private.uid
= uid
; elt
->rfc822_size
= size
;
1942 elt
->private.spare
.data
= file
;
1943 elt
->private.special
.offset
= pos
;
1944 elt
->private.msg
.header
.offset
= hpos
;
1945 elt
->private.msg
.header
.text
.size
= hsiz
;
1946 elt
->year
= y
; elt
->month
= m
; elt
->day
= d
;
1947 elt
->hours
= hh
; elt
->minutes
= mm
;
1948 elt
->seconds
= ss
; elt
->zoccident
= z
;
1949 elt
->zhours
= zh
; elt
->zminutes
= zm
;
1950 /* message in same file? */
1951 if (curfile
== file
) {
1953 MESSAGECACHE
*plt
= mail_elt (stream
,elt
->msgno
-1);
1954 /* uh-oh, calculate delta? */
1956 sprintf (tmp
,shortmsg
,plt
->msgno
,plt
->private.uid
,
1958 /* possible to fix? */
1959 if (!stream
->rdonly
&& LOCAL
->expok
&&
1960 (i
< plt
->rfc822_size
)) {
1961 plt
->rfc822_size
-= i
;
1962 if (plt
->rfc822_size
<
1963 plt
->private.msg
.header
.text
.size
)
1964 plt
->private.msg
.header
.text
.size
=
1966 strcat (tmp
,", repaired");
1967 indexrepairneeded
= T
;
1972 else { /* new file, restart */
1973 if (stat (mix_file_data (LOCAL
->buf
,stream
->mailbox
,
1974 curfile
= file
),&sbuf
)) {
1975 sprintf (tmp
,"Missing mix data file: %.500s",
1981 curfilesize
= sbuf
.st_size
;
1984 /* position of message in file */
1985 curpos
= pos
+ elt
->private.msg
.header
.offset
+
1988 if (curfilesize
< curpos
) {
1989 /* uh-oh, calculate delta? */
1990 i
= curpos
- curfilesize
;
1991 sprintf (tmp
,shortmsg
,elt
->msgno
,elt
->private.uid
,
1992 i
,curfilesize
,curpos
);
1993 /* possible to fix? */
1994 if (!stream
->rdonly
&& LOCAL
->expok
&&
1995 (i
< elt
->rfc822_size
)) {
1996 elt
->rfc822_size
-= i
;
1997 if (elt
->rfc822_size
<
1998 elt
->private.msg
.header
.text
.size
)
1999 elt
->private.msg
.header
.text
.size
=
2001 strcat (tmp
,", repaired");
2002 indexrepairneeded
= T
;
2009 else msg
= "expansion";
2011 else msg
= "header size";
2013 else msg
= "header position";
2015 else msg
= "message position";
2019 sprintf (tmp
,"Error in %s in mix index file: %.500s",msg
,s
);
2023 sprintf (tmp
,"Unknown record in mix index file: %.500s",s
);
2027 if (!s
) return NIL
; /* barfage from mix_read_record() */
2028 /* expunge trailing messages not in index */
2029 if (LOCAL
->expok
) while (nmsgs
< stream
->nmsgs
)
2030 mail_expunged (stream
,stream
->nmsgs
);
2033 /* repair metadata and index if needed */
2034 if ((metarepairneeded
? mix_meta_update (stream
) : T
) &&
2035 (indexrepairneeded
? mix_index_update (stream
,*idxf
,NIL
) : T
)) {
2038 unsigned long uid
,uf
,sf
,mod
;
2041 /* open status file */
2042 if ((fd
= open (LOCAL
->status
,
2043 stream
->rdonly
? O_RDONLY
: O_RDWR
,NIL
)) < 0)
2044 MM_LOG ("Error opening mix status file",ERROR
);
2045 /* acquire exclusive access and FILE */
2046 else if (!flock (fd
,stream
->rdonly
? LOCK_SH
: LOCK_EX
) &&
2047 !(statf
= fdopen (fd
,stream
->rdonly
? "rb" : "r+b"))) {
2048 MM_LOG ("Error obtaining stream on mix status file",ERROR
);
2049 flock (fd
,LOCK_UN
); /* relinquish lock */
2053 else if (!(i
= mix_read_sequence (statf
)) ||
2054 ((i
< LOCAL
->statusseq
) && stream
->nmsgs
&& (i
!= 1))) {
2055 sprintf (LOCAL
->buf
,
2056 "Error in mix status sequence record, i=%lx, seq=%lx",
2057 i
,LOCAL
->statusseq
);
2058 MM_LOG (LOCAL
->buf
,ERROR
);
2060 /* sequence changed from last time? */
2061 else if (i
!= LOCAL
->statusseq
) {
2062 /* update sequence, get first elt */
2063 if (i
> LOCAL
->statusseq
) LOCAL
->statusseq
= i
;
2064 if (stream
->nmsgs
) {
2065 elt
= mail_elt (stream
,i
= 1);
2067 /* read message records */
2068 while ((t
= s
= mix_read_record (statf
,LOCAL
->buf
,LOCAL
->buflen
,
2069 "status")) && *s
&& (*s
++ == ':') &&
2071 uid
= strtoul (s
,&s
,16);
2072 if ((*s
++ == ':') && isxdigit (*s
)) {
2073 uf
= strtoul (s
,&s
,16);
2074 if ((*s
++ == ':') && isxdigit (*s
)) {
2075 sf
= strtoul (s
,&s
,16);
2076 if ((*s
++ == ':') && isxdigit (*s
)) {
2077 mod
= strtoul (s
,&s
,16);
2078 /* ignore expansion values */
2080 /* need to move ahead to next elt? */
2081 while ((uid
> elt
->private.uid
) && (i
< stream
->nmsgs
))
2082 elt
= mail_elt (stream
,++i
);
2083 /* update elt if altered */
2084 if ((uid
== elt
->private.uid
) &&
2085 (!elt
->valid
|| (mod
!= elt
->private.mod
))) {
2086 elt
->user_flags
= uf
;
2087 elt
->private.mod
= mod
;
2088 elt
->seen
= (sf
& fSEEN
) ? T
: NIL
;
2089 elt
->deleted
= (sf
& fDELETED
) ? T
: NIL
;
2090 elt
->flagged
= (sf
& fFLAGGED
) ? T
: NIL
;
2091 elt
->answered
= (sf
& fANSWERED
) ? T
: NIL
;
2092 elt
->draft
= (sf
& fDRAFT
) ? T
: NIL
;
2093 /* announce if altered existing message */
2094 if (elt
->valid
) MM_FLAGS (stream
,elt
->msgno
);
2095 /* first time, is old message? */
2096 else if (sf
& fOLD
) {
2097 /* yes, clear recent and set valid */
2101 /* recent, allowed to update its status? */
2103 /* yes, set valid and check in status */
2105 elt
->private.mod
= mix_modseq (elt
->private.mod
);
2108 /* leave valid unset and recent if sflags not set */
2110 continue; /* everything looks good */
2115 break; /* error somewhere */
2118 if (t
&& *t
) { /* non-null means bogus record */
2119 char msg
[MAILTMPLEN
];
2120 sprintf (msg
,"Error in mix status file message record%s: %.80s",
2121 stream
->rdonly
? "" : ", fixing",t
);
2123 /* update it if not readonly */
2124 if (!stream
->rdonly
) updatep
= T
;
2126 if (updatep
) { /* need to update? */
2127 LOCAL
->statusseq
= mix_modseq (LOCAL
->statusseq
);
2128 mix_status_update (stream
,statf
,LONGT
);
2134 if (statf
) { /* still happy? */
2136 stream
->silent
= silent
; /* now notify upper level */
2137 mail_exists (stream
,stream
->nmsgs
);
2138 for (i
= 1, j
= 0; i
<= stream
->nmsgs
; ++i
)
2139 if (mail_elt (stream
,i
)->recent
) ++j
;
2140 mail_recent (stream
,j
);
2145 /* MIX metadata file routines */
2147 /* MIX read metadata
2148 * Accepts: MAIL stream
2149 * return pointer for modseq
2150 * Returns: pointer to metadata after modseq or NIL if failure
2153 char *mix_meta_slurp (MAILSTREAM
*stream
,unsigned long *seq
)
2158 if (fstat (LOCAL
->mfd
,&sbuf
))
2159 MM_LOG ("Error obtaining size of mix metatdata file",ERROR
);
2160 if (sbuf
.st_size
> LOCAL
->buflen
) {
2161 /* should be just a few dozen bytes */
2162 if (sbuf
.st_size
> METAMAX
) fatal ("absurd mix metadata file size");
2163 fs_give ((void **) &LOCAL
->buf
);
2164 LOCAL
->buf
= (char *) fs_get ((LOCAL
->buflen
= sbuf
.st_size
) + 1);
2166 /* read current metadata file */
2167 LOCAL
->buf
[sbuf
.st_size
] = '\0';
2168 if (lseek (LOCAL
->mfd
,0,L_SET
) ||
2169 (read (LOCAL
->mfd
,s
= LOCAL
->buf
,sbuf
.st_size
) != sbuf
.st_size
))
2170 MM_LOG ("Error reading mix metadata file",ERROR
);
2171 else if ((*s
!= 'S') || !isxdigit (s
[1]) ||
2172 ((*seq
= strtoul (s
+1,&s
,16)) < LOCAL
->metaseq
) ||
2173 (*s
++ != '\015') || (*s
++ != '\012'))
2174 MM_LOG ("Error in mix metadata file sequence record",ERROR
);
2179 /* MIX update metadata
2180 * Accepts: MAIL stream
2181 * Returns: T on success, NIL if error
2183 * Index MUST be locked!!
2186 long mix_meta_update (MAILSTREAM
*stream
)
2189 /* do nothing if stream readonly */
2190 if (stream
->rdonly
) ret
= LONGT
;
2192 unsigned char c
,*s
,*ss
,*t
;
2194 /* The worst-case metadata is limited to:
2195 * 4 * (1 + 8 + 2) + (NUSERFLAGS * (MAXUSERFLAG + 1))
2196 * which comes out to 1994 octets. This is much smaller than the normal
2197 * CHUNKSIZE definition of 64K, and CHUNKSIZE is the smallest size of
2200 * If more stuff gets added to the metadata, or if you change the value
2201 * of NUSERFLAGS, MAXUSERFLAG or CHUNKSIZE, be sure to recalculate the
2202 * above assertation.
2204 sprintf (LOCAL
->buf
,SEQFMT
,LOCAL
->metaseq
= mix_modseq (LOCAL
->metaseq
));
2205 sprintf (LOCAL
->buf
+ strlen (LOCAL
->buf
),MTAFMT
,
2206 stream
->uid_validity
,stream
->uid_last
,LOCAL
->newmsg
);
2207 for (i
= 0, c
= 'K', s
= ss
= LOCAL
->buf
+ strlen (LOCAL
->buf
);
2208 (i
< NUSERFLAGS
) && (t
= stream
->user_flags
[i
]); ++i
) {
2209 if (!*t
) fatal ("impossible empty keyword");
2210 *s
++ = c
; /* write delimiter */
2211 while (*t
) *s
++ = *t
++; /* write keyword */
2212 c
= ' '; /* delimiter is now space */
2214 if (s
!= ss
) { /* tie off keywords line */
2215 *s
++ = '\015'; *s
++ = '\012';
2217 /* calculate length of metadata */
2218 if ((i
= s
- LOCAL
->buf
) > LOCAL
->buflen
)
2219 fatal ("impossible buffer overflow");
2220 lseek (LOCAL
->mfd
,0,L_SET
); /* rewind file */
2221 /* write new metadata */
2222 ret
= (write (LOCAL
->mfd
,LOCAL
->buf
,i
) == i
) ? LONGT
: NIL
;
2223 ftruncate (LOCAL
->mfd
,i
); /* and tie off at that point */
2228 /* MIX index file routines */
2232 * Accepts: MAIL stream
2234 * expansion check flag
2235 * Returns: T on success, NIL if error
2238 long mix_index_update (MAILSTREAM
*stream
,FILE *idxf
,long flag
)
2242 if (!stream
->rdonly
) { /* do nothing if stream readonly */
2243 if (flag
) { /* need to do expansion check? */
2244 char tmp
[MAILTMPLEN
];
2247 /* calculate file size we need */
2248 for (i
= 1, size
= 0; i
<= stream
->nmsgs
; ++i
)
2249 if (!mail_elt (stream
,i
)->private.ghost
) ++size
;
2250 if (size
) { /* Winston Smith's first dairy entry */
2251 sprintf (tmp
,IXRFMT
,(unsigned long) 0,14,4,4,13,0,0,'+',0,0,
2252 (unsigned long) 0,(unsigned long) 0,(unsigned long) 0,
2253 (unsigned long) 0,(unsigned long) 0);
2254 size
*= strlen (tmp
);
2256 /* calculate file size we need */
2257 sprintf (tmp
,SEQFMT
,LOCAL
->indexseq
);
2258 size
+= strlen (tmp
);
2259 /* get current file size */
2260 if (fstat (fileno (idxf
),&sbuf
)) {
2261 MM_LOG ("Error getting size of mix index file",ERROR
);
2264 /* need to write additional space? */
2265 else if (sbuf
.st_size
< size
) {
2266 void *buf
= fs_get (size
-= sbuf
.st_size
);
2267 memset (buf
,0,size
);
2268 if (fseek (idxf
,0,SEEK_END
) || (fwrite (buf
,1,size
,idxf
) != size
) ||
2270 fseek (idxf
,sbuf
.st_size
,SEEK_SET
);
2271 ftruncate (fileno (idxf
),sbuf
.st_size
);
2272 MM_LOG ("Error extending mix index file",ERROR
);
2275 fs_give ((void **) &buf
);
2279 if (ret
) { /* if still good to go */
2280 rewind (idxf
); /* let's start at the very beginning */
2281 /* write modseq first */
2282 fprintf (idxf
,SEQFMT
,LOCAL
->indexseq
);
2283 /* then write all messages */
2284 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); i
++) {
2285 MESSAGECACHE
*elt
= mail_elt (stream
,i
);
2286 if (!elt
->private.ghost
)/* only write living messages */
2287 fprintf (idxf
,IXRFMT
,elt
->private.uid
,
2288 elt
->year
+ BASEYEAR
,elt
->month
,elt
->day
,
2289 elt
->hours
,elt
->minutes
,elt
->seconds
,
2290 elt
->zoccident
? '-' : '+',elt
->zhours
,elt
->zminutes
,
2291 elt
->rfc822_size
,elt
->private.spare
.data
,
2292 elt
->private.special
.offset
,
2293 elt
->private.msg
.header
.offset
,
2294 elt
->private.msg
.header
.text
.size
);
2295 if (ferror (idxf
)) {
2296 MM_LOG ("Error updating mix index file",ERROR
);
2300 if (fflush (idxf
)) {
2301 MM_LOG ("Error flushing mix index file",ERROR
);
2304 if (ret
) ftruncate (fileno (idxf
),ftell (idxf
));
2310 /* MIX status file routines */
2313 /* MIX update status
2314 * Accepts: MAIL stream
2315 * pointer to open FILE
2316 * expansion check flag
2317 * Returns: T on success, NIL if error
2320 long mix_status_update (MAILSTREAM
*stream
,FILE *statf
,long flag
)
2323 char tmp
[MAILTMPLEN
];
2325 if (!stream
->rdonly
) { /* do nothing if stream readonly */
2326 if (flag
) { /* need to do expansion check? */
2327 char tmp
[MAILTMPLEN
];
2330 /* calculate file size we need */
2331 for (i
= 1, size
= 0; i
<= stream
->nmsgs
; ++i
)
2332 if (!mail_elt (stream
,i
)->private.ghost
) ++size
;
2333 if (size
) { /* number of living messages */
2334 sprintf (tmp
,STRFMT
,(unsigned long) 0,(unsigned long) 0,0,
2336 size
*= strlen (tmp
);
2338 sprintf (tmp
,SEQFMT
,LOCAL
->statusseq
);
2339 size
+= strlen (tmp
);
2340 /* get current file size */
2341 if (fstat (fileno (statf
),&sbuf
)) {
2342 MM_LOG ("Error getting size of mix status file",ERROR
);
2345 /* need to write additional space? */
2346 else if (sbuf
.st_size
< size
) {
2347 void *buf
= fs_get (size
-= sbuf
.st_size
);
2348 memset (buf
,0,size
);
2349 if (fseek (statf
,0,SEEK_END
) || (fwrite (buf
,1,size
,statf
) != size
) ||
2351 fseek (statf
,sbuf
.st_size
,SEEK_SET
);
2352 ftruncate (fileno (statf
),sbuf
.st_size
);
2353 MM_LOG ("Error extending mix status file",ERROR
);
2356 fs_give ((void **) &buf
);
2360 if (ret
) { /* if still good to go */
2361 rewind (statf
); /* let's start at the very beginning */
2362 /* write sequence */
2363 fprintf (statf
,SEQFMT
,LOCAL
->statusseq
);
2364 /* write message status records */
2365 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); ++i
) {
2366 MESSAGECACHE
*elt
= mail_elt (stream
,i
);
2367 /* make sure all messages have a modseq */
2368 if (!elt
->private.mod
) elt
->private.mod
= LOCAL
->statusseq
;
2369 if (!elt
->private.ghost
)/* only write living messages */
2370 fprintf (statf
,STRFMT
,elt
->private.uid
,elt
->user_flags
,
2371 (fSEEN
* elt
->seen
) + (fDELETED
* elt
->deleted
) +
2372 (fFLAGGED
* elt
->flagged
) + (fANSWERED
* elt
->answered
) +
2373 (fDRAFT
* elt
->draft
) + (elt
->valid
? fOLD
: NIL
),
2375 if (ferror (statf
)) {
2376 sprintf (tmp
,"Error updating mix status file: %.80s",
2382 if (ret
&& fflush (statf
)) {
2383 MM_LOG ("Error flushing mix status file",ERROR
);
2386 if (ret
) ftruncate (fileno (statf
),ftell (statf
));
2392 /* MIX data file routines */
2395 /* MIX open data file
2396 * Accepts: MAIL stream
2397 * pointer to returned fd if success
2398 * pointer to returned size if success
2399 * size of new data to be added
2400 * Returns: open FILE, or NIL if failure
2402 * The curend test assumes that the last message of the mailbox is the furthest
2403 * point that the current data file extends, and thus that is all that needs to
2404 * be tested for short file prevention.
2407 FILE *mix_data_open (MAILSTREAM
*stream
,int *fd
,long *size
,
2408 unsigned long newsize
)
2412 MESSAGECACHE
*elt
= stream
->nmsgs
? mail_elt (stream
,stream
->nmsgs
) : NIL
;
2413 unsigned long curend
= (elt
&& (elt
->private.spare
.data
== LOCAL
->newmsg
)) ?
2414 elt
->private.special
.offset
+ elt
->private.msg
.header
.offset
+
2415 elt
->rfc822_size
: 0;
2416 /* allow create if curend 0 */
2417 if ((*fd
= open (mix_file_data (LOCAL
->buf
,stream
->mailbox
,LOCAL
->newmsg
),
2418 O_RDWR
| (curend
? NIL
: O_CREAT
),NIL
)) >= 0) {
2419 fstat (*fd
,&sbuf
); /* get current file size */
2420 /* can we use this file? */
2421 if ((curend
<= sbuf
.st_size
) &&
2422 (!sbuf
.st_size
|| ((sbuf
.st_size
+ newsize
) <= MIXDATAROLL
)))
2423 *size
= sbuf
.st_size
; /* yes, return current size */
2424 else { /* short file or becoming too long */
2425 if (curend
> sbuf
.st_size
) {
2426 char tmp
[MAILTMPLEN
];
2427 sprintf (tmp
,"short mix message file %.08lx (%ld > %ld), rolling",
2428 LOCAL
->newmsg
,curend
,(unsigned long) sbuf
.st_size
);
2429 MM_LOG (tmp
,WARN
); /* shouldn't happen */
2431 close (*fd
); /* roll to a new file */
2433 while ((*fd
= open (mix_file_data
2434 (LOCAL
->buf
,stream
->mailbox
,
2435 LOCAL
->newmsg
= mix_modseq (LOCAL
->newmsg
)),
2436 O_RDWR
| O_CREAT
| O_EXCL
,sbuf
.st_mode
)) < 0) {
2438 case EEXIST
: /* always retry if path exists or interrupt */
2442 default: /* probably EDQUOT */
2444 char tmp
[MAILTMPLEN
];
2445 sprintf (tmp
,"data file %.08lx creation failure: %.80s",
2446 LOCAL
->newmsg
,strerror (errno
));
2447 MM_LOG (tmp
,ERROR
); /* shouldn't happen */
2452 *size
= 0; /* brand new file */
2453 fchmod (*fd
,sbuf
.st_mode
);/* with same mode as previous file */
2456 if (*fd
>= 0) { /* have a data file? */
2457 /* yes, get stdio and set position */
2458 if (msgf
= fdopen (*fd
,"r+b")) fseek (msgf
,*size
,SEEK_SET
);
2459 else close (*fd
); /* fdopen() failed? */
2461 return msgf
; /* return results */
2464 /* MIX open sortcache
2465 * Accepts: MAIL stream
2466 * Returns: open FILE, or NIL if failure or could only get readonly sortcache
2469 FILE *mix_sortcache_open (MAILSTREAM
*stream
)
2472 unsigned long i
,uid
,sentdate
,fromlen
,tolen
,cclen
,subjlen
,msgidlen
,reflen
;
2473 char *s
,*t
,*msg
,tmp
[MAILTMPLEN
];
2480 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
2481 fstat (LOCAL
->mfd
,&sbuf
);
2482 if (!stream
->nmsgs
); /* do nothing if mailbox empty */
2483 /* open sortcache file */
2484 else if (((fd
= open (LOCAL
->sortcache
,O_RDWR
|O_CREAT
,sbuf
.st_mode
)) < 0) &&
2485 !(rdonly
= ((fd
= open (LOCAL
->sortcache
,O_RDONLY
,NIL
)) >= 0)))
2486 MM_LOG ("Error opening mix sortcache file",WARN
);
2487 /* acquire lock and FILE */
2488 else if (!flock (fd
,rdonly
? LOCK_SH
: LOCK_EX
) &&
2489 !(srtcf
= fdopen (fd
,rdonly
? "rb" : "r+b"))) {
2490 MM_LOG ("Error obtaining stream on mix sortcache file",WARN
);
2491 flock (fd
,LOCK_UN
); /* relinquish lock */
2494 else if (!(i
= mix_read_sequence (srtcf
)) || (i
< LOCAL
->sortcacheseq
))
2495 MM_LOG ("Error in mix sortcache file sequence record",WARN
);
2496 /* sequence changed from last time? */
2497 else if (i
> LOCAL
->sortcacheseq
) {
2498 LOCAL
->sortcacheseq
= i
; /* update sequence */
2499 while ((s
= t
= mix_read_record (srtcf
,LOCAL
->buf
,LOCAL
->buflen
,
2500 "sortcache")) && *s
&&
2501 (msg
= "uid") && (*s
++ == ':') && isxdigit (*s
)) {
2502 uid
= strtoul (s
,&s
,16);
2503 if ((*s
++ == ':') && isxdigit (*s
)) {
2504 sentdate
= strtoul (s
,&s
,16);
2505 if ((*s
++ == ':') && isxdigit (*s
)) {
2506 fromlen
= strtoul (s
,&s
,16);
2507 if ((*s
++ == ':') && isxdigit (*s
)) {
2508 tolen
= strtoul (s
,&s
,16);
2509 if ((*s
++ == ':') && isxdigit (*s
)) {
2510 cclen
= strtoul (s
,&s
,16);
2511 if ((*s
++ == ':') && ((*s
== 'R') || (*s
== ' ')) &&
2513 refwd
= (*s
++ == 'R') ? T
: NIL
;
2514 subjlen
= strtoul (s
,&s
,16);
2515 if ((*s
++ == ':') && isxdigit (*s
)) {
2516 msgidlen
= strtoul (s
,&s
,16);
2517 if ((*s
++ == ':') && isxdigit (*s
)) {
2518 reflen
= strtoul (s
,&s
,16);
2519 /* ignore expansion values */
2522 if (i
= mail_msgno (stream
,uid
)) {
2523 sc
= (SORTCACHE
*) (*mc
) (stream
,i
,CH_SORTCACHE
);
2524 sc
->size
= (elt
= mail_elt (stream
,i
))->rfc822_size
;
2525 sc
->date
= sentdate
;
2526 sc
->arrival
= elt
->day
? mail_longdate (elt
) : 1;
2527 if (refwd
) sc
->refwd
= T
;
2529 if (sc
->from
) fseek (srtcf
,fromlen
+ 2,SEEK_CUR
);
2530 else if ((getc (srtcf
) != 'F') ||
2531 (fread (sc
->from
= (char *) fs_get(fromlen
),
2532 1,fromlen
-1,srtcf
) != (fromlen
-1))||
2533 (sc
->from
[fromlen
-1] = '\0') ||
2534 (getc (srtcf
) != '\015') ||
2535 (getc (srtcf
) != '\012')) {
2541 if (sc
->to
) fseek (srtcf
,tolen
+ 2,SEEK_CUR
);
2542 else if ((getc (srtcf
) != 'T') ||
2543 (fread (sc
->to
= (char *) fs_get (tolen
),
2544 1,tolen
-1,srtcf
) != (tolen
- 1)) ||
2545 (sc
->to
[tolen
-1] = '\0') ||
2546 (getc (srtcf
) != '\015') ||
2547 (getc (srtcf
) != '\012')) {
2553 if (sc
->cc
) fseek (srtcf
,cclen
+ 2,SEEK_CUR
);
2554 else if ((getc (srtcf
) != 'C') ||
2555 (fread (sc
->cc
= (char *) fs_get (cclen
),
2556 1,cclen
-1,srtcf
) != (cclen
- 1)) ||
2557 (sc
->cc
[cclen
-1] = '\0') ||
2558 (getc (srtcf
) != '\015') ||
2559 (getc (srtcf
) != '\012')) {
2565 if (sc
->subject
) fseek (srtcf
,subjlen
+ 2,SEEK_CUR
);
2566 else if ((getc (srtcf
) != 'S') ||
2567 (fread (sc
->subject
=
2568 (char *) fs_get (subjlen
),1,
2569 subjlen
-1,srtcf
) != (subjlen
-1))||
2570 (sc
->subject
[subjlen
-1] = '\0') ||
2571 (getc (srtcf
) != '\015') ||
2572 (getc (srtcf
) != '\012')) {
2573 msg
= "subject data";
2580 fseek (srtcf
,msgidlen
+ 2,SEEK_CUR
);
2581 else if ((getc (srtcf
) != 'M') ||
2582 (fread (sc
->message_id
=
2583 (char *) fs_get (msgidlen
),1,
2584 msgidlen
-1,srtcf
) != (msgidlen
-1))||
2585 (sc
->message_id
[msgidlen
-1] = '\0') ||
2586 (getc (srtcf
) != '\015') ||
2587 (getc (srtcf
) != '\012')) {
2588 msg
= "message-id data";
2593 if (sc
->references
) fseek(srtcf
,reflen
+ 2,SEEK_CUR
);
2594 /* make sure it fits */
2596 if (reflen
>= LOCAL
->buflen
) {
2597 fs_give ((void **) &LOCAL
->buf
);
2598 LOCAL
->buf
= (char *)
2599 fs_get ((LOCAL
->buflen
= reflen
) + 1);
2601 if ((getc (srtcf
) != 'R') ||
2602 (fread (LOCAL
->buf
,1,reflen
-1,srtcf
) !=
2604 (LOCAL
->buf
[reflen
-1] = '\0') ||
2605 (getc (srtcf
) != '\015') ||
2606 (getc (srtcf
) != '\012')) {
2607 msg
= "references data";
2610 for (s
= LOCAL
->buf
,sl
= NIL
,
2611 sc
->references
= mail_newstringlist ();
2612 s
&& *s
; s
+= i
+ 1) {
2613 if ((i
= strtoul (s
,&s
,16)) && (*s
++ == ':') &&
2615 if (sl
) sl
= sl
->next
= mail_newstringlist();
2616 else sl
= sc
->references
;
2618 sl
->text
.data
= cpystr (s
);
2624 (s
!= ((char *) LOCAL
->buf
+ reflen
- 1))) {
2625 msg
= "references length consistency check";
2632 /* UID not found, ignore this message */
2633 else fseek (srtcf
,((fromlen
? fromlen
+ 2 : 0) +
2634 (tolen
? tolen
+ 2 : 0) +
2635 (cclen
? cclen
+ 2 : 0) +
2636 (subjlen
? subjlen
+ 2 : 0) +
2637 (msgidlen
? msgidlen
+ 2 : 0) +
2638 (reflen
? reflen
+ 2 : 0)),
2642 else msg
= "expansion";
2644 else msg
= "references";
2646 else msg
= "message-id";
2648 else msg
= "subject";
2656 else msg
= "sentdate";
2657 break; /* error somewhere */
2659 if (!t
|| *t
) { /* error detected? */
2660 if (t
) { /* non-null means bogus record */
2661 sprintf (tmp
,"Error in %s in mix sortcache record: %.500s",msg
,t
);
2664 fclose (srtcf
); /* either way, must punt */
2668 if (rdonly
&& srtcf
) { /* can't update if readonly */
2669 unlink (LOCAL
->sortcache
); /* try deleting it */
2670 fclose (srtcf
); /* so close it and return as if error */
2673 else fchmod (fd
,sbuf
.st_mode
);
2677 /* MIX update and close sortcache
2678 * Accepts: MAIL stream
2679 * pointer to open FILE (if FILE is NIL, do nothing)
2680 * Returns: T on success, NIL on error
2683 long mix_sortcache_update (MAILSTREAM
*stream
,FILE **sortcache
)
2685 FILE *f
= *sortcache
;
2687 if (f
) { /* ignore if no file */
2689 mailcache_t mc
= (mailcache_t
) mail_parameters (NIL
,GET_CACHE
,NIL
);
2690 for (i
= 1; (i
<= stream
->nmsgs
) &&
2691 !((SORTCACHE
*) (*mc
) (stream
,i
,CH_SORTCACHE
))->dirty
; ++i
);
2692 if (i
<= stream
->nmsgs
) { /* only update if some entry is dirty */
2693 rewind (f
); /* let's start at the very beginning */
2694 /* write sequence */
2695 fprintf (f
,SEQFMT
,LOCAL
->sortcacheseq
= mix_modseq(LOCAL
->sortcacheseq
));
2696 for (i
= 1; ret
&& (i
<= stream
->nmsgs
); ++i
) {
2697 MESSAGECACHE
*elt
= mail_elt (stream
,i
);
2698 SORTCACHE
*s
= (SORTCACHE
*) (*mc
) (stream
,i
,CH_SORTCACHE
);
2700 s
->dirty
= NIL
; /* no longer dirty */
2701 if (sl
= s
->references
) /* count length of references */
2702 for (j
= 1; sl
&& sl
->text
.data
; sl
= sl
->next
)
2703 j
+= 10 + sl
->text
.size
;
2704 else j
= 0; /* no references yet */
2705 fprintf (f
,SCRFMT
,elt
->private.uid
,s
->date
,
2706 s
->from
? strlen (s
->from
) + 1 : 0,
2707 s
->to
? strlen (s
->to
) + 1 : 0,s
->cc
? strlen (s
->cc
) + 1 : 0,
2708 s
->refwd
? 'R' : ' ',s
->subject
? strlen (s
->subject
) + 1: 0,
2709 s
->message_id
? strlen (s
->message_id
) + 1 : 0,j
);
2710 if (s
->from
) fprintf (f
,"F%s\015\012",s
->from
);
2711 if (s
->to
) fprintf (f
,"T%s\015\012",s
->to
);
2712 if (s
->cc
) fprintf (f
,"C%s\015\012",s
->cc
);
2713 if (s
->subject
) fprintf (f
,"S%s\015\012",s
->subject
);
2714 if (s
->message_id
) fprintf (f
,"M%s\015\012",s
->message_id
);
2715 if (j
) { /* any references to write? */
2716 fputc ('R',f
); /* yes, do so */
2717 for (sl
= s
->references
; sl
&& sl
->text
.data
; sl
= sl
->next
)
2718 fprintf (f
,"%08lx:%s:",sl
->text
.size
,sl
->text
.data
);
2719 fputs ("\015\012",f
);
2722 MM_LOG ("Error updating mix sortcache file",WARN
);
2726 if (ret
&& fflush (f
)) {
2727 MM_LOG ("Error flushing mix sortcache file",WARN
);
2730 if (ret
) ftruncate (fileno (f
),ftell (f
));
2733 MM_LOG ("Error closing mix sortcache file",WARN
);
2740 /* MIX generic file routines */
2743 * Accepts: open FILE
2747 * Returns: buffer if success, else NIL (zero-length buffer means EOF)
2750 char *mix_read_record (FILE *f
,char *buf
,unsigned long buflen
,char *type
)
2752 char *s
,tmp
[MAILTMPLEN
];
2753 /* ensure string tied off */
2754 buf
[buflen
-2] = buf
[buflen
-1] = '\0';
2755 while (fgets (buf
,buflen
-1,f
)) {
2756 if (s
= strchr (buf
,'\012')) {
2757 if ((s
!= buf
) && (s
[-1] == '\015')) --s
;
2758 *s
= '\0'; /* tie off buffer */
2759 if (s
!= buf
) return buf
; /* return if non-empty buffer */
2760 sprintf (tmp
,"Empty mix %s record",type
);
2763 else if (buf
[buflen
-2]) { /* overlong record is bad news */
2764 sprintf (tmp
,"Oversize mix %s record: %.512s",type
,buf
);
2769 sprintf (tmp
,"Truncated mix %s record: %.512s",type
,buf
);
2771 return buf
; /* pass to caller anyway */
2774 buf
[0] = '\0'; /* return empty buffer on EOF */
2778 /* MIX read sequence record
2779 * Accepts: open FILE
2780 * Returns: sequence value, or NIL if failure
2783 unsigned long mix_read_sequence (FILE *f
)
2786 char *s
,tmp
[MAILTMPLEN
];
2787 if (!mix_read_record (f
,tmp
,MAILTMPLEN
-1,"sequence")) return NIL
;
2788 switch (tmp
[0]) { /* examine record */
2789 case '\0': /* end of file */
2790 ret
= 1; /* start a new sequence regime */
2792 case 'S': /* sequence record */
2793 if (isxdigit (tmp
[1])) { /* must be followed by hex value */
2794 ret
= strtoul (tmp
+1,&s
,16);
2795 if (!*s
) break; /* and nothing more */
2797 /* drop into default case */
2798 default: /* anything else is an error */
2799 return NIL
; /* return error */
2804 /* MIX internal routines */
2807 /* MIX mail build directory name
2808 * Accepts: destination string
2810 * Returns: destination or empty string if error
2813 char *mix_dir (char *dst
,char *name
)
2816 /* empty string if mailboxfile fails */
2817 if (!mailboxfile (dst
,name
)) *dst
= '\0';
2818 /* driver-selected INBOX */
2819 else if (!*dst
) mailboxfile (dst
,"~/INBOX");
2820 /* tie off unnecessary trailing / */
2821 else if ((s
= strrchr (dst
,'/')) && !s
[1]) *s
= '\0';
2826 /* MIX mail build file name
2827 * Accepts: destination string
2830 * Returns: destination
2833 char *mix_file (char *dst
,char *dir
,char *name
)
2835 sprintf (dst
,"%.500s/%.80s%.80s",dir
,MIXNAME
,name
);
2840 /* MIX mail build file name from data file number
2841 * Accepts: destination string
2844 * Returns: destination
2847 char *mix_file_data (char *dst
,char *dir
,unsigned long data
)
2849 char tmp
[MAILTMPLEN
];
2850 if (data
) sprintf (tmp
,"%08lx",data
);
2851 else tmp
[0] = '\0'; /* compatibility with experimental version */
2852 return mix_file (dst
,dir
,tmp
);
2855 /* MIX mail get new modseq
2856 * Accepts: old modseq
2857 * Returns: new modseq value
2860 unsigned long mix_modseq (unsigned long oldseq
)
2862 /* normally time now */
2863 unsigned long ret
= (unsigned long) time (NIL
);
2864 /* ensure that modseq doesn't go backwards */
2865 if (ret
<= oldseq
) ret
= oldseq
+ 1;