Mercurial > hgrepos > hgweb.cgi > imapext
comparison src/osdep/amiga/mix.c @ 0:ada5e610ab86
imap-2007e
author | yuuji@gentei.org |
---|---|
date | Mon, 14 Sep 2009 15:17:45 +0900 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:ada5e610ab86 |
---|---|
1 /* ======================================================================== | |
2 * Copyright 1988-2008 University of Washington | |
3 * | |
4 * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 * you may not use this file except in compliance with the License. | |
6 * You may obtain a copy of the License at | |
7 * | |
8 * http://www.apache.org/licenses/LICENSE-2.0 | |
9 * | |
10 * | |
11 * ======================================================================== | |
12 */ | |
13 | |
14 /* | |
15 * Program: MIX mail routines | |
16 * | |
17 * Author(s): Mark Crispin | |
18 * UW Technology | |
19 * University of Washington | |
20 * Seattle, WA 98195 | |
21 * Internet: MRC@Washington.EDU | |
22 * | |
23 * Date: 1 March 2006 | |
24 * Last Edited: 7 May 2008 | |
25 */ | |
26 | |
27 | |
28 #include <stdio.h> | |
29 #include <ctype.h> | |
30 #include <errno.h> | |
31 extern int errno; /* just in case */ | |
32 #include "mail.h" | |
33 #include "osdep.h" | |
34 #include <pwd.h> | |
35 #include <sys/stat.h> | |
36 #include <sys/time.h> | |
37 #include "misc.h" | |
38 #include "dummy.h" | |
39 #include "fdstring.h" | |
40 | |
41 /* MIX definitions */ | |
42 | |
43 #define MEGABYTE (1024*1024) | |
44 | |
45 #define MIXDATAROLL MEGABYTE /* size at which we roll to a new file */ | |
46 | |
47 | |
48 /* MIX files */ | |
49 | |
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) */ | |
56 | |
57 | |
58 /* MIX file formats */ | |
59 | |
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" | |
74 | |
75 /* MIX I/O stream local data */ | |
76 | |
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 */ | |
94 } MIXLOCAL; | |
95 | |
96 | |
97 #define MIXBURP struct mix_burp | |
98 | |
99 MIXBURP { | |
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 */ | |
105 }; | |
106 | |
107 | |
108 /* Convenient access to local data */ | |
109 | |
110 #define LOCAL ((MIXLOCAL *) stream->local) | |
111 | |
112 /* Function prototypes */ | |
113 | |
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, | |
120 unsigned long fsiz); | |
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 (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, | |
133 long flags); | |
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 (struct direct *name); | |
144 int mix_msgfsort (const void *d1,const void *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, | |
149 long options); | |
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); | |
153 | |
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); | |
169 | |
170 /* MIX mail routines */ | |
171 | |
172 | |
173 /* Driver dispatch used by MAIL */ | |
174 | |
175 DRIVER mixdriver = { | |
176 "mix", /* driver name */ | |
177 /* driver flags */ | |
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 }; | |
214 | |
215 /* prototype stream */ | |
216 MAILSTREAM mixproto = {&mixdriver}; | |
217 | |
218 /* MIX mail validate mailbox | |
219 * Accepts: mailbox name | |
220 * Returns: our driver if name is valid, NIL otherwise | |
221 */ | |
222 | |
223 DRIVER *mix_valid (char *name) | |
224 { | |
225 char tmp[MAILTMPLEN]; | |
226 return mix_isvalid (name,tmp) ? &mixdriver : NIL; | |
227 } | |
228 | |
229 | |
230 /* MIX mail test for valid mailbox | |
231 * Accepts: mailbox name | |
232 * buffer to return meta name | |
233 * Returns: T if valid, NIL otherwise, metadata name written in both cases | |
234 */ | |
235 | |
236 long mix_isvalid (char *name,char *meta) | |
237 { | |
238 char dir[MAILTMPLEN]; | |
239 struct stat sbuf; | |
240 /* validate name as directory */ | |
241 if (!(errno = ((strlen (name) > NETMAXMBX) ? ENAMETOOLONG : NIL)) && | |
242 *mix_dir (dir,name) && mix_file (meta,dir,MIXMETA) && | |
243 !stat (dir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) { | |
244 /* name is directory; is it mix? */ | |
245 if (!stat (meta,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG)) | |
246 return LONGT; | |
247 else errno = NIL; /* directory but not mix */ | |
248 } | |
249 return NIL; | |
250 } | |
251 | |
252 /* MIX manipulate driver parameters | |
253 * Accepts: function code | |
254 * function-dependent value | |
255 * Returns: function-dependent return value | |
256 */ | |
257 | |
258 void *mix_parameters (long function,void *value) | |
259 { | |
260 void *ret = NIL; | |
261 switch ((int) function) { | |
262 case GET_INBOXPATH: | |
263 if (value) ret = mailboxfile ((char *) value,"~/INBOX"); | |
264 break; | |
265 case GET_DIRFMTTEST: | |
266 ret = (void *) mix_dirfmttest; | |
267 break; | |
268 case GET_SCANCONTENTS: | |
269 ret = (void *) mix_scan_contents; | |
270 break; | |
271 case SET_ONETIMEEXPUNGEATPING: | |
272 if (value) ((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T; | |
273 case GET_ONETIMEEXPUNGEATPING: | |
274 if (value) ret = (void *) | |
275 (((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL); | |
276 break; | |
277 } | |
278 return ret; | |
279 } | |
280 | |
281 | |
282 /* MIX test for directory format internal node | |
283 * Accepts: candidate node name | |
284 * Returns: T if internal name, NIL otherwise | |
285 */ | |
286 | |
287 long mix_dirfmttest (char *name) | |
288 { | |
289 /* belongs to MIX if starts with .mix */ | |
290 return strncmp (name,MIXNAME,sizeof (MIXNAME) - 1) ? NIL : LONGT; | |
291 } | |
292 | |
293 /* MIX mail scan mailboxes | |
294 * Accepts: mail stream | |
295 * reference | |
296 * pattern to search | |
297 * string to scan | |
298 */ | |
299 | |
300 void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) | |
301 { | |
302 if (stream) dummy_scan (NIL,ref,pat,contents); | |
303 } | |
304 | |
305 | |
306 /* MIX scan mailbox for contents | |
307 * Accepts: mailbox name | |
308 * desired contents | |
309 * contents size | |
310 * file size (ignored) | |
311 * Returns: NIL if contents not found, T if found | |
312 */ | |
313 | |
314 long mix_scan_contents (char *name,char *contents,unsigned long csiz, | |
315 unsigned long fsiz) | |
316 { | |
317 long i,nfiles; | |
318 void *a; | |
319 char *s; | |
320 long ret = NIL; | |
321 size_t namelen = strlen (name); | |
322 struct stat sbuf; | |
323 struct direct **names = NIL; | |
324 if ((nfiles = scandir (name,&names,mix_select,mix_msgfsort)) > 0) | |
325 for (i = 0; i < nfiles; ++i) { | |
326 if (!ret) { | |
327 sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2), | |
328 "%s/%s",name,names[i]->d_name); | |
329 if (!stat (s,&sbuf) && (csiz <= sbuf.st_size)) | |
330 ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size); | |
331 fs_give ((void **) &s); | |
332 } | |
333 fs_give ((void **) &names[i]); | |
334 } | |
335 /* free directory list */ | |
336 if (a = (void *) names) fs_give ((void **) &a); | |
337 return ret; | |
338 } | |
339 | |
340 /* MIX list mailboxes | |
341 * Accepts: mail stream | |
342 * reference | |
343 * pattern to search | |
344 */ | |
345 | |
346 void mix_list (MAILSTREAM *stream,char *ref,char *pat) | |
347 { | |
348 if (stream) dummy_list (NIL,ref,pat); | |
349 } | |
350 | |
351 | |
352 /* MIX list subscribed mailboxes | |
353 * Accepts: mail stream | |
354 * reference | |
355 * pattern to search | |
356 */ | |
357 | |
358 void mix_lsub (MAILSTREAM *stream,char *ref,char *pat) | |
359 { | |
360 if (stream) dummy_lsub (NIL,ref,pat); | |
361 } | |
362 | |
363 /* MIX mail subscribe to mailbox | |
364 * Accepts: mail stream | |
365 * mailbox to add to subscription list | |
366 * Returns: T on success, NIL on failure | |
367 */ | |
368 | |
369 long mix_subscribe (MAILSTREAM *stream,char *mailbox) | |
370 { | |
371 return sm_subscribe (mailbox); | |
372 } | |
373 | |
374 | |
375 /* MIX mail unsubscribe to mailbox | |
376 * Accepts: mail stream | |
377 * mailbox to delete from subscription list | |
378 * Returns: T on success, NIL on failure | |
379 */ | |
380 | |
381 long mix_unsubscribe (MAILSTREAM *stream,char *mailbox) | |
382 { | |
383 return sm_unsubscribe (mailbox); | |
384 } | |
385 | |
386 /* MIX mail create mailbox | |
387 * Accepts: mail stream | |
388 * mailbox name to create | |
389 * Returns: T on success, NIL on failure | |
390 */ | |
391 | |
392 long mix_create (MAILSTREAM *stream,char *mailbox) | |
393 { | |
394 DRIVER *test; | |
395 FILE *f; | |
396 int c,i; | |
397 char *t,tmp[MAILTMPLEN],file[MAILTMPLEN]; | |
398 char *s = strrchr (mailbox,'/'); | |
399 unsigned long now = time (NIL); | |
400 long ret = NIL; | |
401 /* always create \NoSelect if trailing / */ | |
402 if (s && !s[1]) return dummy_create (stream,mailbox); | |
403 /* validate name */ | |
404 if (mix_dirfmttest (s ? s + 1 : mailbox)) | |
405 sprintf(tmp,"Can't create mailbox %.80s: invalid MIX-format name",mailbox); | |
406 /* must not already exist */ | |
407 else if ((test = mail_valid (NIL,mailbox,NIL)) && | |
408 strcmp (test->name,"dummy")) | |
409 sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox); | |
410 /* create directory and metadata */ | |
411 else if (!dummy_create_path (stream, | |
412 mix_file (file,mix_dir (tmp,mailbox),MIXMETA), | |
413 get_dir_protection (mailbox))) | |
414 sprintf (tmp,"Can't create mailbox %.80s: %.80s",mailbox,strerror (errno)); | |
415 else if (!(f = fopen (file,"w"))) | |
416 sprintf (tmp,"Can't re-open metadata %.80s: %.80s",mailbox, | |
417 strerror (errno)); | |
418 else { /* success, write initial metadata */ | |
419 fprintf (f,SEQFMT,now); | |
420 fprintf (f,MTAFMT,now,0,now); | |
421 for (i = 0, c = 'K'; (i < NUSERFLAGS) && | |
422 (t = (stream && stream->user_flags[i]) ? stream->user_flags[i] : | |
423 default_user_flag (i)) && *t; ++i) { | |
424 putc (c,f); /* write another keyword */ | |
425 fputs (t,f); | |
426 c = ' '; /* delimiter is now space */ | |
427 } | |
428 fclose (f); | |
429 set_mbx_protections (mailbox,file); | |
430 /* point to suffix */ | |
431 s = file + strlen (file) - (sizeof (MIXMETA) - 1); | |
432 strcpy (s,MIXINDEX); /* create index */ | |
433 if (!dummy_create_path (stream,file,get_dir_protection (mailbox))) | |
434 sprintf (tmp,"Can't create mix mailbox index: %.80s",strerror (errno)); | |
435 else { | |
436 set_mbx_protections (mailbox,file); | |
437 strcpy (s,MIXSTATUS); /* create status */ | |
438 if (!dummy_create_path (stream,file,get_dir_protection (mailbox))) | |
439 sprintf (tmp,"Can't create mix mailbox status: %.80s", | |
440 strerror (errno)); | |
441 else { | |
442 set_mbx_protections (mailbox,file); | |
443 sprintf (s,"%08lx",now);/* message file */ | |
444 if (!dummy_create_path (stream,file,get_dir_protection (mailbox))) | |
445 sprintf (tmp,"Can't create mix mailbox data: %.80s", | |
446 strerror (errno)); | |
447 else { | |
448 set_mbx_protections (mailbox,file); | |
449 ret = LONGT; /* declare success at this point */ | |
450 } | |
451 } | |
452 } | |
453 } | |
454 if (!ret) MM_LOG (tmp,ERROR); /* some error */ | |
455 return ret; | |
456 } | |
457 | |
458 /* MIX mail delete mailbox | |
459 * mailbox name to delete | |
460 * Returns: T on success, NIL on failure | |
461 */ | |
462 | |
463 long mix_delete (MAILSTREAM *stream,char *mailbox) | |
464 { | |
465 DIR *dirp; | |
466 struct direct *d; | |
467 int fd = -1; | |
468 char *s,tmp[MAILTMPLEN]; | |
469 if (!mix_isvalid (mailbox,tmp)) | |
470 sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox); | |
471 else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB)) | |
472 sprintf (tmp,"Can't lock mailbox for delete: %.80s",mailbox); | |
473 /* delete metadata */ | |
474 else if (unlink (tmp)) sprintf (tmp,"Can't delete mailbox %.80s index: %80s", | |
475 mailbox,strerror (errno)); | |
476 else { | |
477 close (fd); /* close descriptor on deleted metadata */ | |
478 /* get directory name */ | |
479 *(s = strrchr (tmp,'/')) = '\0'; | |
480 if (dirp = opendir (tmp)) { /* open directory */ | |
481 *s++ = '/'; /* restore delimiter */ | |
482 /* massacre messages */ | |
483 while (d = readdir (dirp)) if (mix_dirfmttest (d->d_name)) { | |
484 strcpy (s,d->d_name); /* make path */ | |
485 unlink (tmp); /* sayonara */ | |
486 } | |
487 closedir (dirp); /* flush directory */ | |
488 *(s = strrchr (tmp,'/')) = '\0'; | |
489 if (rmdir (tmp)) { /* try to remove the directory */ | |
490 sprintf (tmp,"Can't delete name %.80s: %.80s", | |
491 mailbox,strerror (errno)); | |
492 MM_LOG (tmp,WARN); | |
493 } | |
494 } | |
495 return T; /* always success */ | |
496 } | |
497 if (fd >= 0) close (fd); /* close any descriptor on metadata */ | |
498 MM_LOG (tmp,ERROR); /* something failed */ | |
499 return NIL; | |
500 } | |
501 | |
502 /* MIX mail rename mailbox | |
503 * Accepts: MIX mail stream | |
504 * old mailbox name | |
505 * new mailbox name | |
506 * Returns: T on success, NIL on failure | |
507 */ | |
508 | |
509 long mix_rename (MAILSTREAM *stream,char *old,char *newname) | |
510 { | |
511 char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN]; | |
512 struct stat sbuf; | |
513 int fd = -1; | |
514 if (!mix_isvalid (old,tmp)) | |
515 sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old); | |
516 else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB)) | |
517 sprintf (tmp,"Can't lock mailbox for rename: %.80s",old); | |
518 else if (mix_dirfmttest ((s = strrchr (newname,'/')) ? s + 1 : newname)) | |
519 sprintf (tmp,"Can't rename to mailbox %.80s: invalid MIX-format name", | |
520 newname); | |
521 /* new mailbox name must not be valid */ | |
522 else if (mix_isvalid (newname,tmp)) | |
523 sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists", | |
524 newname); | |
525 else { | |
526 mix_dir (tmp,old); /* build old directory name */ | |
527 mix_dir (tmp1,newname); /* and new directory name */ | |
528 /* easy if not INBOX */ | |
529 if (compare_cstring (old,"INBOX")) { | |
530 /* found superior to destination name? */ | |
531 if (s = strrchr (tmp1,'/')) { | |
532 c = *++s; /* remember first character of inferior */ | |
533 *s = '\0'; /* tie off to get just superior */ | |
534 /* name doesn't exist, create it */ | |
535 if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && | |
536 !dummy_create_path (stream,tmp1,get_dir_protection (newname))) | |
537 return NIL; | |
538 *s = c; /* restore full name */ | |
539 } | |
540 if (!rename (tmp,tmp1)) { | |
541 close (fd); /* close descriptor on metadata */ | |
542 return LONGT; | |
543 } | |
544 } | |
545 | |
546 /* RFC 3501 requires this */ | |
547 else if (dummy_create_path (stream,strcat (tmp1,"/"), | |
548 get_dir_protection (newname))) { | |
549 void *a; | |
550 int i,n,lasterror; | |
551 char *src,*dst; | |
552 struct direct **names = NIL; | |
553 size_t srcl = strlen (tmp); | |
554 size_t dstl = strlen (tmp1); | |
555 /* rename each mix file to new directory */ | |
556 for (i = lasterror = 0,n = scandir (tmp,&names,mix_rselect,alphasort); | |
557 i < n; ++i) { | |
558 size_t len = strlen (names[i]->d_name); | |
559 sprintf (src = (char *) fs_get (srcl + len + 2),"%s/%s", | |
560 tmp,names[i]->d_name); | |
561 sprintf (dst = (char *) fs_get (dstl + len + 1),"%s%s", | |
562 tmp1,names[i]->d_name); | |
563 if (rename (src,dst)) lasterror = errno; | |
564 fs_give ((void **) &src); | |
565 fs_give ((void **) &dst); | |
566 fs_give ((void **) &names[i]); | |
567 } | |
568 /* free directory list */ | |
569 if (a = (void *) names) fs_give ((void **) &a); | |
570 if (lasterror) errno = lasterror; | |
571 else { | |
572 close (fd); /* close descriptor on metadata */ | |
573 return mix_create (NIL,"INBOX"); | |
574 } | |
575 } | |
576 sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s", | |
577 old,newname,strerror (errno)); | |
578 } | |
579 if (fd >= 0) close (fd); /* close any descriptor on metadata */ | |
580 MM_LOG (tmp,ERROR); /* something failed */ | |
581 return NIL; | |
582 } | |
583 | |
584 | |
585 /* MIX test for mix name | |
586 * Accepts: candidate directory name | |
587 * Returns: T if mix file name, NIL otherwise | |
588 */ | |
589 | |
590 int mix_rselect (struct direct *name) | |
591 { | |
592 return mix_dirfmttest (name->d_name); | |
593 } | |
594 | |
595 /* MIX mail open | |
596 * Accepts: stream to open | |
597 * Returns: stream on success, NIL on failure | |
598 */ | |
599 | |
600 MAILSTREAM *mix_open (MAILSTREAM *stream) | |
601 { | |
602 short silent; | |
603 /* return prototype for OP_PROTOTYPE call */ | |
604 if (!stream) return user_flags (&mixproto); | |
605 if (stream->local) fatal ("mix recycle stream"); | |
606 stream->local = memset (fs_get (sizeof (MIXLOCAL)),0,sizeof (MIXLOCAL)); | |
607 /* note if an INBOX or not */ | |
608 stream->inbox = !compare_cstring (stream->mailbox,"INBOX"); | |
609 /* make temporary buffer */ | |
610 LOCAL->buf = (char *) fs_get (CHUNKSIZE); | |
611 LOCAL->buflen = CHUNKSIZE - 1; | |
612 /* set stream->mailbox to be directory name */ | |
613 mix_dir (LOCAL->buf,stream->mailbox); | |
614 fs_give ((void **) &stream->mailbox); | |
615 stream->mailbox = cpystr (LOCAL->buf); | |
616 LOCAL->msgfd = -1; /* currently no file open */ | |
617 if (!(((!stream->rdonly && /* open metadata file */ | |
618 ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA), | |
619 O_RDWR,NIL)) >= 0)) || | |
620 ((stream->rdonly = T) && | |
621 ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA), | |
622 O_RDONLY,NIL)) >= 0))) && | |
623 !flock (LOCAL->mfd,LOCK_SH))) { | |
624 MM_LOG ("Error opening mix metadata file",ERROR); | |
625 mix_abort (stream); | |
626 stream = NIL; /* open fails */ | |
627 } | |
628 else { /* metadata open, complete open */ | |
629 LOCAL->index = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXINDEX)); | |
630 LOCAL->status = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXSTATUS)); | |
631 LOCAL->sortcache = cpystr (mix_file (LOCAL->buf,stream->mailbox, | |
632 MIXSORTCACHE)); | |
633 stream->sequence++; /* bump sequence number */ | |
634 /* parse mailbox */ | |
635 stream->nmsgs = stream->recent = 0; | |
636 if (silent = stream->silent) LOCAL->internal = T; | |
637 stream->silent = T; | |
638 if (mix_ping (stream)) { /* do initial ping */ | |
639 /* try burping in case we are exclusive */ | |
640 if (!stream->rdonly) mix_expunge (stream,"",NIL); | |
641 if (!(stream->nmsgs || stream->silent)) | |
642 MM_LOG ("Mailbox is empty",(long) NIL); | |
643 stream->silent = silent; /* now notify upper level */ | |
644 mail_exists (stream,stream->nmsgs); | |
645 stream->perm_seen = stream->perm_deleted = stream->perm_flagged = | |
646 stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T; | |
647 stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff; | |
648 stream->kwd_create = /* can we create new user flags? */ | |
649 (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ? NIL : T; | |
650 } | |
651 else { /* got murdelyzed in ping */ | |
652 mix_abort (stream); | |
653 stream = NIL; | |
654 } | |
655 } | |
656 return stream; /* return stream to caller */ | |
657 } | |
658 | |
659 /* MIX mail close | |
660 * Accepts: MAIL stream | |
661 * close options | |
662 */ | |
663 | |
664 void mix_close (MAILSTREAM *stream,long options) | |
665 { | |
666 if (LOCAL) { /* only if a file is open */ | |
667 int silent = stream->silent; | |
668 stream->silent = T; /* note this stream is dying */ | |
669 /* burp-only or expunge */ | |
670 mix_expunge (stream,(options & CL_EXPUNGE) ? NIL : "",NIL); | |
671 mix_abort (stream); | |
672 stream->silent = silent; /* reset silent state */ | |
673 } | |
674 } | |
675 | |
676 | |
677 /* MIX mail abort stream | |
678 * Accepts: MAIL stream | |
679 */ | |
680 | |
681 void mix_abort (MAILSTREAM *stream) | |
682 { | |
683 if (LOCAL) { /* only if a file is open */ | |
684 /* close current message file if open */ | |
685 if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); | |
686 /* close current metadata file if open */ | |
687 if (LOCAL->mfd >= 0) close (LOCAL->mfd); | |
688 if (LOCAL->index) fs_give ((void **) &LOCAL->index); | |
689 if (LOCAL->status) fs_give ((void **) &LOCAL->status); | |
690 if (LOCAL->sortcache) fs_give ((void **) &LOCAL->sortcache); | |
691 /* free local scratch buffer */ | |
692 if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); | |
693 /* nuke the local data */ | |
694 fs_give ((void **) &stream->local); | |
695 stream->dtb = NIL; /* log out the DTB */ | |
696 } | |
697 } | |
698 | |
699 /* MIX mail fetch message header | |
700 * Accepts: MAIL stream | |
701 * message # to fetch | |
702 * pointer to returned header text length | |
703 * option flags | |
704 * Returns: message header in RFC822 format | |
705 */ | |
706 | |
707 char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, | |
708 long flags) | |
709 { | |
710 unsigned long i,j,k; | |
711 int fd; | |
712 char *s,tmp[MAILTMPLEN]; | |
713 MESSAGECACHE *elt; | |
714 if (length) *length = 0; /* default return */ | |
715 if (flags & FT_UID) return "";/* UID call "impossible" */ | |
716 elt = mail_elt (stream,msgno);/* get elt */ | |
717 /* is message in current message file? */ | |
718 if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) { | |
719 if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); | |
720 if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox, | |
721 elt->private.spare.data), | |
722 O_RDONLY,NIL)) < 0) return ""; | |
723 /* got file */ | |
724 LOCAL->curmsg = elt->private.spare.data; | |
725 } | |
726 lseek (LOCAL->msgfd,elt->private.special.offset,L_SET); | |
727 /* size of special data and header */ | |
728 j = elt->private.msg.header.offset + elt->private.msg.header.text.size; | |
729 if (j > LOCAL->buflen) { /* is buffer big enough? */ | |
730 /* no, make one that is */ | |
731 fs_give ((void **) &LOCAL->buf); | |
732 LOCAL->buf = (char *) fs_get ((LOCAL->buflen = j) + 1); | |
733 } | |
734 /* Maybe someday validate internaldate too */ | |
735 /* slurp special data + header, validate */ | |
736 if ((read (LOCAL->msgfd,LOCAL->buf,j) == j) && | |
737 !strncmp (LOCAL->buf,MSGTOK,MSGTSZ) && | |
738 (elt->private.uid == strtoul ((char *) LOCAL->buf + MSGTSZ,&s,16)) && | |
739 (*s++ == ':') && (s = strchr (s,':')) && | |
740 (k = strtoul (s+1,&s,16)) && (*s++ == ':') && | |
741 (s < (char *) (LOCAL->buf + elt->private.msg.header.offset))) { | |
742 /* won, set offset and size of message */ | |
743 i = elt->private.msg.header.offset; | |
744 *length = elt->private.msg.header.text.size; | |
745 if (k != elt->rfc822_size) { | |
746 sprintf (tmp,"Inconsistency in mix message size, uid=%lx (%lu != %lu)", | |
747 elt->private.uid,elt->rfc822_size,k); | |
748 MM_LOG (tmp,WARN); | |
749 } | |
750 } | |
751 else { /* document the problem */ | |
752 LOCAL->buf[100] = '\0'; /* tie off buffer at no more than 100 octets */ | |
753 /* or at newline, whichever is first */ | |
754 if (s = strpbrk (LOCAL->buf,"\015\012")) *s = '\0'; | |
755 sprintf (tmp,"Error reading mix message header, uid=%lx, s=%.0lx, h=%s", | |
756 elt->private.uid,elt->rfc822_size,LOCAL->buf); | |
757 MM_LOG (tmp,ERROR); | |
758 *length = i = j = 0; /* default to empty */ | |
759 } | |
760 LOCAL->buf[j] = '\0'; /* tie off buffer at the end */ | |
761 return (char *) LOCAL->buf + i; | |
762 } | |
763 | |
764 /* MIX mail fetch message text (body only) | |
765 * Accepts: MAIL stream | |
766 * message # to fetch | |
767 * pointer to returned stringstruct | |
768 * option flags | |
769 * Returns: T on success, NIL on failure | |
770 */ | |
771 | |
772 long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) | |
773 { | |
774 unsigned long i; | |
775 FDDATA d; | |
776 MESSAGECACHE *elt; | |
777 /* UID call "impossible" */ | |
778 if (flags & FT_UID) return NIL; | |
779 elt = mail_elt (stream,msgno); | |
780 /* is message in current message file? */ | |
781 if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) { | |
782 if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); | |
783 if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox, | |
784 elt->private.spare.data), | |
785 O_RDONLY,NIL)) < 0) return NIL; | |
786 /* got file */ | |
787 LOCAL->curmsg = elt->private.spare.data; | |
788 } | |
789 /* doing non-peek fetch? */ | |
790 if (!(flags & FT_PEEK) && !elt->seen) { | |
791 FILE *idxf; /* yes, process metadata/index/status */ | |
792 FILE *statf = mix_parse (stream,&idxf,NIL,LONGT); | |
793 elt->seen = T; /* mark as seen */ | |
794 MM_FLAGS (stream,elt->msgno); | |
795 /* update status file if possible */ | |
796 if (statf && !stream->rdonly) { | |
797 elt->private.mod = LOCAL->statusseq = mix_modseq (LOCAL->statusseq); | |
798 mix_status_update (stream,statf,NIL); | |
799 } | |
800 if (idxf) fclose (idxf); /* release index and status file */ | |
801 if (statf) fclose (statf); | |
802 } | |
803 d.fd = LOCAL->msgfd; /* set up file descriptor */ | |
804 /* offset of message text */ | |
805 d.pos = elt->private.special.offset + elt->private.msg.header.offset + | |
806 elt->private.msg.header.text.size; | |
807 d.chunk = LOCAL->buf; /* initial buffer chunk */ | |
808 d.chunksize = CHUNKSIZE; /* chunk size */ | |
809 INIT (bs,fd_string,&d,elt->rfc822_size - elt->private.msg.header.text.size); | |
810 return T; | |
811 } | |
812 | |
813 /* MIX mail modify flags | |
814 * Accepts: MAIL stream | |
815 * sequence | |
816 * flag(s) | |
817 * option flags | |
818 */ | |
819 | |
820 void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) | |
821 { | |
822 MESSAGECACHE *elt; | |
823 unsigned long i,uf,ffkey; | |
824 long f; | |
825 short nf; | |
826 FILE *idxf; | |
827 FILE *statf = mix_parse (stream,&idxf,NIL,LONGT); | |
828 unsigned long seq = mix_modseq (LOCAL->statusseq); | |
829 /* find first free key */ | |
830 for (ffkey = 0; (ffkey < NUSERFLAGS) && stream->user_flags[ffkey]; ++ffkey); | |
831 /* parse sequence and flags */ | |
832 if (((flags & ST_UID) ? mail_uid_sequence (stream,sequence) : | |
833 mail_sequence (stream,sequence)) && | |
834 ((f = mail_parse_flags (stream,flag,&uf)) || uf)) { | |
835 /* alter flags */ | |
836 for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++) | |
837 if ((elt = mail_elt (stream,i))->sequence) { | |
838 struct { /* old flags */ | |
839 unsigned int seen : 1; | |
840 unsigned int deleted : 1; | |
841 unsigned int flagged : 1; | |
842 unsigned int answered : 1; | |
843 unsigned int draft : 1; | |
844 unsigned long user_flags; | |
845 } old; | |
846 old.seen = elt->seen; old.deleted = elt->deleted; | |
847 old.flagged = elt->flagged; old.answered = elt->answered; | |
848 old.draft = elt->draft; old.user_flags = elt->user_flags; | |
849 if (f&fSEEN) elt->seen = nf; | |
850 if (f&fDELETED) elt->deleted = nf; | |
851 if (f&fFLAGGED) elt->flagged = nf; | |
852 if (f&fANSWERED) elt->answered = nf; | |
853 if (f&fDRAFT) elt->draft = nf; | |
854 /* user flags */ | |
855 if (flags & ST_SET) elt->user_flags |= uf; | |
856 else elt->user_flags &= ~uf; | |
857 if ((old.seen != elt->seen) || (old.deleted != elt->deleted) || | |
858 (old.flagged != elt->flagged) || | |
859 (old.answered != elt->answered) || (old.draft != elt->draft) || | |
860 (old.user_flags != elt->user_flags)) { | |
861 if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq; | |
862 MM_FLAGS (stream,elt->msgno); | |
863 } | |
864 } | |
865 /* update status file after change */ | |
866 if (statf && (seq == LOCAL->statusseq)) | |
867 mix_status_update (stream,statf,NIL); | |
868 /* update metadata if created a keyword */ | |
869 if ((ffkey < NUSERFLAGS) && stream->user_flags[ffkey] && | |
870 !mix_meta_update (stream)) | |
871 MM_LOG ("Error updating mix metadata after keyword creation",ERROR); | |
872 } | |
873 if (statf) fclose (statf); /* release status file if still open */ | |
874 if (idxf) fclose (idxf); /* release index file */ | |
875 } | |
876 | |
877 /* MIX mail sort messages | |
878 * Accepts: mail stream | |
879 * character set | |
880 * search program | |
881 * sort program | |
882 * option flags | |
883 * Returns: vector of sorted message sequences or NIL if error | |
884 */ | |
885 | |
886 unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, | |
887 SORTPGM *pgm,long flags) | |
888 { | |
889 unsigned long *ret; | |
890 FILE *sortcache = mix_sortcache_open (stream); | |
891 ret = mail_sort_msgs (stream,charset,spg,pgm,flags); | |
892 mix_sortcache_update (stream,&sortcache); | |
893 return ret; | |
894 } | |
895 | |
896 | |
897 /* MIX mail thread messages | |
898 * Accepts: mail stream | |
899 * thread type | |
900 * character set | |
901 * search program | |
902 * option flags | |
903 * Returns: thread node tree or NIL if error | |
904 */ | |
905 | |
906 THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset, | |
907 SEARCHPGM *spg,long flags) | |
908 { | |
909 THREADNODE *ret; | |
910 FILE *sortcache = mix_sortcache_open (stream); | |
911 ret = mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs); | |
912 mix_sortcache_update (stream,&sortcache); | |
913 return ret; | |
914 } | |
915 | |
916 /* MIX mail ping mailbox | |
917 * Accepts: MAIL stream | |
918 * Returns: T if stream alive, else NIL | |
919 */ | |
920 | |
921 static int snarfing = 0; /* lock against recursive snarfing */ | |
922 | |
923 long mix_ping (MAILSTREAM *stream) | |
924 { | |
925 FILE *idxf,*statf; | |
926 struct stat sbuf; | |
927 STRING msg; | |
928 MESSAGECACHE *elt; | |
929 int mfd,ifd,sfd; | |
930 unsigned long i,msglen; | |
931 char *message,date[MAILTMPLEN],flags[MAILTMPLEN]; | |
932 MAILSTREAM *sysibx = NIL; | |
933 long ret = NIL; | |
934 long snarfok = LONGT; | |
935 /* time to snarf? */ | |
936 if (stream->inbox && !stream->rdonly && !snarfing && | |
937 (time (0) >= (LOCAL->lastsnarf + | |
938 (time_t) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) { | |
939 appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL); | |
940 copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL); | |
941 MM_CRITICAL (stream); /* go critical */ | |
942 snarfing = T; /* don't recursively snarf */ | |
943 /* disable APPENDUID/COPYUID callbacks */ | |
944 mail_parameters (NIL,SET_APPENDUID,NIL); | |
945 mail_parameters (NIL,SET_COPYUID,NIL); | |
946 /* sizes match and anything in sysinbox? */ | |
947 if (!stat (sysinbox (),&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG) && | |
948 sbuf.st_size && (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) && | |
949 !sysibx->rdonly && sysibx->nmsgs) { | |
950 /* for each message in sysibx mailbox */ | |
951 for (i = 1; snarfok && (i <= sysibx->nmsgs); ++i) | |
952 if (!(elt = mail_elt (sysibx,i))->deleted && | |
953 (message = mail_fetch_message (sysibx,i,&msglen,FT_PEEK)) && | |
954 msglen) { | |
955 mail_date (date,elt); /* make internal date string */ | |
956 /* make flag string */ | |
957 flags[0] = flags[1] = '\0'; | |
958 if (elt->seen) strcat (flags," \\Seen"); | |
959 if (elt->flagged) strcat (flags," \\Flagged"); | |
960 if (elt->answered) strcat (flags," \\Answered"); | |
961 if (elt->draft) strcat (flags," \\Draft"); | |
962 flags[0] = '('; | |
963 strcat (flags,")"); | |
964 INIT (&msg,mail_string,message,msglen); | |
965 if (snarfok = mail_append_full (stream,"INBOX",flags,date,&msg)) { | |
966 char sequence[15]; | |
967 sprintf (sequence,"%lu",i); | |
968 mail_flag (sysibx,sequence,"\\Deleted",ST_SET); | |
969 } | |
970 } | |
971 | |
972 /* now expunge all those messages */ | |
973 if (snarfok) mail_expunge (sysibx); | |
974 else { | |
975 sprintf (LOCAL->buf,"Can't copy new mail at message: %lu",i - 1); | |
976 MM_LOG (LOCAL->buf,WARN); | |
977 } | |
978 } | |
979 if (sysibx) mail_close (sysibx); | |
980 /* reenable APPENDUID/COPYUID */ | |
981 mail_parameters (NIL,SET_APPENDUID,(void *) au); | |
982 mail_parameters (NIL,SET_COPYUID,(void *) cu); | |
983 snarfing = NIL; /* no longer snarfing */ | |
984 MM_NOCRITICAL (stream); /* release critical */ | |
985 LOCAL->lastsnarf = time (0);/* note time of last snarf */ | |
986 } | |
987 /* expunging OK if global flag set */ | |
988 if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T; | |
989 /* process metadata/index/status */ | |
990 if (statf = mix_parse (stream,&idxf,LONGT, | |
991 (LOCAL->internal ? NIL : LONGT))) { | |
992 fclose (statf); /* just close the status file */ | |
993 ret = LONGT; /* declare success */ | |
994 } | |
995 if (idxf) fclose (idxf); /* release index file */ | |
996 LOCAL->expok = NIL; /* expunge no longer OK */ | |
997 if (!ret) mix_abort (stream); /* murdelyze stream if ping fails */ | |
998 return ret; | |
999 } | |
1000 | |
1001 | |
1002 /* MIX mail checkpoint mailbox (burp only) | |
1003 * Accepts: MAIL stream | |
1004 */ | |
1005 | |
1006 void mix_check (MAILSTREAM *stream) | |
1007 { | |
1008 if (stream->rdonly) /* won't do on readonly files! */ | |
1009 MM_LOG ("Checkpoint ignored on readonly mailbox",NIL); | |
1010 /* do burp-only expunge action */ | |
1011 if (mix_expunge (stream,"",NIL)) MM_LOG ("Check completed",(long) NIL); | |
1012 } | |
1013 | |
1014 /* MIX mail expunge mailbox | |
1015 * Accepts: MAIL stream | |
1016 * sequence to expunge if non-NIL, empty string for burp only | |
1017 * expunge options | |
1018 * Returns: T on success, NIL if failure | |
1019 */ | |
1020 | |
1021 long mix_expunge (MAILSTREAM *stream,char *sequence,long options) | |
1022 { | |
1023 FILE *idxf = NIL; | |
1024 FILE *statf = NIL; | |
1025 MESSAGECACHE *elt; | |
1026 int ifd,sfd; | |
1027 long ret; | |
1028 unsigned long i; | |
1029 unsigned long nexp = 0; | |
1030 unsigned long reclaimed = 0; | |
1031 int burponly = (sequence && !*sequence); | |
1032 LOCAL->expok = T; /* expunge during ping is OK */ | |
1033 if (!(ret = burponly || !sequence || | |
1034 ((options & EX_UID) ? | |
1035 mail_uid_sequence (stream,sequence) : | |
1036 mail_sequence (stream,sequence))) || stream->rdonly); | |
1037 /* read index and open status exclusive */ | |
1038 else if (statf = mix_parse (stream,&idxf,LONGT, | |
1039 LOCAL->internal ? NIL : LONGT)) { | |
1040 /* expunge unless just burping */ | |
1041 if (!burponly) for (i = 1; i <= stream->nmsgs;) { | |
1042 elt = mail_elt (stream,i);/* need to expunge this message? */ | |
1043 if (sequence ? elt->sequence : elt->deleted) { | |
1044 ++nexp; /* yes, make it so */ | |
1045 mail_expunged (stream,i); | |
1046 } | |
1047 else ++i; /* otherwise advance to next message */ | |
1048 } | |
1049 | |
1050 /* burp if can get exclusive access */ | |
1051 if (!flock (LOCAL->mfd,LOCK_EX|LOCK_NB)) { | |
1052 void *a; | |
1053 struct direct **names = NIL; | |
1054 long nfiles = scandir (stream->mailbox,&names,mix_select,mix_msgfsort); | |
1055 if (nfiles > 0) { /* if have message files */ | |
1056 MIXBURP *burp,*cur; | |
1057 /* initialize burp list */ | |
1058 for (i = 0, burp = cur = NIL; i < nfiles; ++i) { | |
1059 MIXBURP *nxt = (MIXBURP *) memset (fs_get (sizeof (MIXBURP)),0, | |
1060 sizeof (MIXBURP)); | |
1061 /* another file found */ | |
1062 if (cur) cur = cur->next = nxt; | |
1063 else cur = burp = nxt; | |
1064 cur->name = names[i]->d_name; | |
1065 cur->fileno = strtoul (cur->name + sizeof (MIXNAME) - 1,NIL,16); | |
1066 cur->tail = &cur->set; | |
1067 fs_give ((void **) &names[i]); | |
1068 } | |
1069 /* now load ranges */ | |
1070 for (i = 1, cur = burp; ret && (i <= stream->nmsgs); i++) { | |
1071 /* is this message in current set? */ | |
1072 elt = mail_elt (stream,i); | |
1073 if (cur && (elt->private.spare.data != cur->fileno)) { | |
1074 /* restart if necessary */ | |
1075 if (elt->private.spare.data < cur->fileno) cur = burp; | |
1076 /* hunt for appropriate mailbox */ | |
1077 while (cur && (elt->private.spare.data > cur->fileno)) | |
1078 cur = cur->next; | |
1079 /* ought to have found it now... */ | |
1080 if (cur && (elt->private.spare.data != cur->fileno)) cur = NIL; | |
1081 } | |
1082 /* if found, add to set */ | |
1083 if (cur) ret = mix_addset (&cur->tail,elt->private.special.offset, | |
1084 elt->private.msg.header.offset + | |
1085 elt->rfc822_size); | |
1086 else { /* uh-oh */ | |
1087 sprintf (LOCAL->buf,"Can't locate mix message file %.08lx", | |
1088 elt->private.spare.data); | |
1089 MM_LOG (LOCAL->buf,ERROR); | |
1090 ret = NIL; | |
1091 } | |
1092 } | |
1093 if (ret) /* if no errors, burp all files */ | |
1094 for (cur = burp; ret && cur; cur = cur->next) { | |
1095 /* if non-empty, burp it */ | |
1096 if (cur->set.last) ret = mix_burp (stream,cur,&reclaimed); | |
1097 /* empty, delete it unless new msg file */ | |
1098 else if (mix_file_data (LOCAL->buf,stream->mailbox,cur->fileno) && | |
1099 ((cur->fileno == LOCAL->newmsg) ? | |
1100 truncate (LOCAL->buf,0) : unlink (LOCAL->buf))) { | |
1101 sprintf (LOCAL->buf, | |
1102 "Can't delete empty message file %.80s: %.80s", | |
1103 cur->name,strerror (errno)); | |
1104 MM_LOG (LOCAL->buf,WARN); | |
1105 } | |
1106 } | |
1107 } | |
1108 else MM_LOG ("No mix message files found during expunge",WARN); | |
1109 /* free directory list */ | |
1110 if (a = (void *) names) fs_give ((void **) &a); | |
1111 } | |
1112 | |
1113 /* either way, re-acquire shared lock */ | |
1114 if (flock (LOCAL->mfd,LOCK_SH|LOCK_NB)) | |
1115 fatal ("Unable to re-acquire metadata shared lock!"); | |
1116 /* Do this step even if ret is NIL (meaning some burp problem)! */ | |
1117 if (nexp || reclaimed) { /* rewrite index and status if changed */ | |
1118 LOCAL->indexseq = mix_modseq (LOCAL->indexseq); | |
1119 if (mix_index_update (stream,idxf,NIL)) { | |
1120 LOCAL->statusseq = mix_modseq (LOCAL->statusseq); | |
1121 /* set failure if update fails */ | |
1122 ret = mix_status_update (stream,statf,NIL); | |
1123 } | |
1124 } | |
1125 } | |
1126 if (statf) fclose (statf); /* close status if still open */ | |
1127 if (idxf) fclose (idxf); /* close index if still open */ | |
1128 LOCAL->expok = NIL; /* cancel expok */ | |
1129 if (ret) { /* only if success */ | |
1130 char *s = NIL; | |
1131 if (nexp) sprintf (s = LOCAL->buf,"Expunged %lu messages",nexp); | |
1132 else if (reclaimed) | |
1133 sprintf (s=LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed); | |
1134 else if (!burponly) | |
1135 s = stream->rdonly ? "Expunge ignored on readonly mailbox" : | |
1136 "No messages deleted, so no update needed"; | |
1137 if (s) MM_LOG (s,(long) NIL); | |
1138 } | |
1139 return ret; | |
1140 } | |
1141 | |
1142 /* MIX test for message file name | |
1143 * Accepts: candidate directory name | |
1144 * Returns: T if message file name, NIL otherwise | |
1145 * | |
1146 * ".mix" with no suffix was used by experimental versions | |
1147 */ | |
1148 | |
1149 int mix_select (struct direct *name) | |
1150 { | |
1151 char c,*s; | |
1152 /* make sure name has prefix */ | |
1153 if (mix_dirfmttest (name->d_name)) { | |
1154 for (c = *(s = name->d_name + sizeof (MIXNAME) - 1); c && isxdigit (c); | |
1155 c = *s++); | |
1156 if (!c) return T; /* all-hex or no suffix */ | |
1157 } | |
1158 return NIL; /* not suffix or non-hex */ | |
1159 } | |
1160 | |
1161 | |
1162 /* MIX msg file name comparision | |
1163 * Accepts: first candidate directory entry | |
1164 * second candidate directory entry | |
1165 * Returns: -1 if d1 < d2, 0 if d1 == d2, 1 d1 > d2 | |
1166 */ | |
1167 | |
1168 int mix_msgfsort (const void *d1,const void *d2) | |
1169 { | |
1170 char *n1 = (*(struct direct **) d1)->d_name + sizeof (MIXNAME) - 1; | |
1171 char *n2 = (*(struct direct **) d2)->d_name + sizeof (MIXNAME) - 1; | |
1172 return compare_ulong (*n1 ? strtoul (n1,NIL,16) : 0, | |
1173 *n2 ? strtoul (n2,NIL,16) : 0); | |
1174 } | |
1175 | |
1176 | |
1177 /* MIX add a range to a set | |
1178 * Accepts: pointer to set to add | |
1179 * start of set | |
1180 * size of set | |
1181 * Returns: T if success, set updated, NIL otherwise | |
1182 */ | |
1183 | |
1184 long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size) | |
1185 { | |
1186 SEARCHSET *s = *set; | |
1187 if (start < s->last) { /* sanity check */ | |
1188 char tmp[MAILTMPLEN]; | |
1189 sprintf (tmp,"Backwards-running mix index %lu < %lu",start,s->last); | |
1190 MM_LOG (tmp,ERROR); | |
1191 return NIL; | |
1192 } | |
1193 /* range initially empty? */ | |
1194 if (!s->last) s->first = start; | |
1195 else if (start > s->last) /* no, start new range if can't append */ | |
1196 (*set = s = s->next = mail_newsearchset ())->first = start; | |
1197 s->last = start + size; /* end of current range */ | |
1198 return LONGT; | |
1199 } | |
1200 | |
1201 /* MIX burp message file | |
1202 * Accepts: MAIL stream | |
1203 * current burp block for this message | |
1204 * Returns: T if successful, NIL if failed | |
1205 */ | |
1206 | |
1207 static char *staterr = "Error in stat of mix message file %.80s: %.80s"; | |
1208 static char *truncerr = "Error truncating mix message file %.80s: %.80s"; | |
1209 | |
1210 long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed) | |
1211 { | |
1212 MESSAGECACHE *elt; | |
1213 SEARCHSET *set; | |
1214 struct stat sbuf; | |
1215 off_t rpos,wpos; | |
1216 size_t size,wsize,wpending,written; | |
1217 int fd; | |
1218 FILE *f; | |
1219 void *s; | |
1220 unsigned long i; | |
1221 long ret = NIL; | |
1222 /* build file name */ | |
1223 mix_file_data (LOCAL->buf,stream->mailbox,burp->fileno); | |
1224 /* need to burp at start or multiple ranges? */ | |
1225 if (!burp->set.first && !burp->set.next) { | |
1226 /* easy case, single range at start of file */ | |
1227 if (stat (LOCAL->buf,&sbuf)) { | |
1228 sprintf (LOCAL->buf,staterr,burp->name,strerror (errno)); | |
1229 MM_LOG (LOCAL->buf,ERROR); | |
1230 } | |
1231 /* is this range sane? */ | |
1232 else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) { | |
1233 /* if matches range then no burp needed! */ | |
1234 if (burp->set.last == sbuf.st_size) ret = LONGT; | |
1235 /* just need to remove cruft at end */ | |
1236 else if (ret = !truncate (LOCAL->buf,burp->set.last)) | |
1237 *reclaimed += sbuf.st_size - burp->set.last; | |
1238 else { | |
1239 sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno)); | |
1240 MM_LOG (LOCAL->buf,ERROR); | |
1241 } | |
1242 } | |
1243 } | |
1244 /* have to do more work, get the file */ | |
1245 else if (((fd = open (LOCAL->buf,O_RDWR,NIL)) < 0) || | |
1246 !(f = fdopen (fd,"r+b"))) { | |
1247 sprintf (LOCAL->buf,"Error opening mix message file %.80s: %.80s", | |
1248 burp->name,strerror (errno)); | |
1249 MM_LOG (LOCAL->buf,ERROR); | |
1250 if (fd >= 0) close (fd); /* in case fdopen() failure */ | |
1251 } | |
1252 else if (fstat (fd,&sbuf)) { /* get file size */ | |
1253 sprintf (LOCAL->buf,staterr,burp->name,strerror (errno)); | |
1254 MM_LOG (LOCAL->buf,ERROR); | |
1255 fclose (f); | |
1256 } | |
1257 | |
1258 /* only if sane */ | |
1259 else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) { | |
1260 /* make sure each range starts with token */ | |
1261 for (set = &burp->set; set; set = set->next) | |
1262 if (fseek (f,set->first,SEEK_SET) || | |
1263 (fread (LOCAL->buf,1,MSGTSZ,f) != MSGTSZ) || | |
1264 strncmp (LOCAL->buf,MSGTOK,MSGTSZ)) { | |
1265 sprintf (LOCAL->buf,"Bad message token in mix message file at %lu", | |
1266 set->first); | |
1267 MM_LOG (LOCAL->buf,ERROR); | |
1268 fclose (f); | |
1269 return NIL; /* burp fails for this file */ | |
1270 } | |
1271 /* burp out each old message */ | |
1272 for (set = &burp->set, wpos = 0; set; set = set->next) { | |
1273 /* move down this range */ | |
1274 for (rpos = set->first, size = set->last - set->first; | |
1275 size; size -= wsize) { | |
1276 if (rpos != wpos) { /* data to skip at start? */ | |
1277 /* no, slide this buffer down */ | |
1278 wsize = min (size,LOCAL->buflen); | |
1279 /* failure is not an option here */ | |
1280 while (fseek (f,rpos,SEEK_SET) || | |
1281 (fread (LOCAL->buf,1,wsize,f) != wsize)) { | |
1282 MM_NOTIFY (stream,strerror (errno),WARN); | |
1283 MM_DISKERROR (stream,errno,T); | |
1284 } | |
1285 /* nor here */ | |
1286 while (fseek (f,wpos,SEEK_SET)) { | |
1287 MM_NOTIFY (stream,strerror (errno),WARN); | |
1288 MM_DISKERROR (stream,errno,T); | |
1289 } | |
1290 /* and especially not here */ | |
1291 for (s = LOCAL->buf, wpending = wsize; wpending; wpending -= written) | |
1292 if (!(written = fwrite (LOCAL->buf,1,wpending,f))) { | |
1293 MM_NOTIFY (stream,strerror (errno),WARN); | |
1294 MM_DISKERROR (stream,errno,T); | |
1295 } | |
1296 } | |
1297 else wsize = size; /* nothing to skip, say we wrote it all */ | |
1298 rpos += wsize; wpos += wsize; | |
1299 } | |
1300 } | |
1301 | |
1302 while (fflush (f)) { /* failure also not an option here... */ | |
1303 MM_NOTIFY (stream,strerror (errno),WARN); | |
1304 MM_DISKERROR (stream,errno,T); | |
1305 } | |
1306 if (ftruncate (fd,wpos)) { /* flush cruft at end of file */ | |
1307 sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno)); | |
1308 MM_LOG (LOCAL->buf,WARN); | |
1309 } | |
1310 else *reclaimed += rpos - wpos; | |
1311 ret = !fclose (f); /* close file */ | |
1312 /* slide down message positions in index */ | |
1313 for (i = 1,rpos = 0; i <= stream->nmsgs; ++i) | |
1314 if ((elt = mail_elt (stream,i))->private.spare.data == burp->fileno) { | |
1315 elt->private.special.offset = rpos; | |
1316 rpos += elt->private.msg.header.offset + elt->rfc822_size; | |
1317 } | |
1318 /* debugging */ | |
1319 if (rpos != wpos) fatal ("burp size consistency check!"); | |
1320 } | |
1321 return ret; | |
1322 } | |
1323 | |
1324 | |
1325 /* MIX burp sanity check to make sure not burping off end of file | |
1326 * Accepts: burp set | |
1327 * file size | |
1328 * file name | |
1329 * Returns: T if sane, NIL if insane | |
1330 */ | |
1331 | |
1332 long mix_burp_check (SEARCHSET *set,size_t size,char *file) | |
1333 { | |
1334 do if (set->last > size) { /* sanity check */ | |
1335 char tmp[MAILTMPLEN]; | |
1336 sprintf (tmp,"Unexpected short mix message file %.80s %lu < %lu", | |
1337 file,size,set->last); | |
1338 MM_LOG (tmp,ERROR); | |
1339 return NIL; /* don't burp this file at all */ | |
1340 } while (set = set->next); | |
1341 return LONGT; | |
1342 } | |
1343 | |
1344 /* MIX mail copy message(s) | |
1345 * Accepts: MAIL stream | |
1346 * sequence | |
1347 * destination mailbox | |
1348 * copy options | |
1349 * Returns: T if copy successful, else NIL | |
1350 */ | |
1351 | |
1352 long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) | |
1353 { | |
1354 FDDATA d; | |
1355 STRING st; | |
1356 char tmp[2*MAILTMPLEN]; | |
1357 long ret = mix_isvalid (mailbox,LOCAL->buf); | |
1358 mailproxycopy_t pc = | |
1359 (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); | |
1360 MAILSTREAM *astream = NIL; | |
1361 FILE *idxf = NIL; | |
1362 FILE *msgf = NIL; | |
1363 FILE *statf = NIL; | |
1364 if (!ret) switch (errno) { /* make sure valid mailbox */ | |
1365 case NIL: /* no error in stat() */ | |
1366 if (pc) return (*pc) (stream,sequence,mailbox,options); | |
1367 sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox); | |
1368 MM_LOG (tmp,ERROR); | |
1369 break; | |
1370 default: /* some stat() error */ | |
1371 MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL); | |
1372 break; | |
1373 } | |
1374 /* get sequence to copy */ | |
1375 else if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) : | |
1376 mail_sequence (stream,sequence)))); | |
1377 /* acquire stream to append */ | |
1378 else if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) && | |
1379 !astream->rdonly && | |
1380 (((MIXLOCAL *) astream->local)->expok = T) && | |
1381 (statf = mix_parse (astream,&idxf,LONGT,NIL))) ? | |
1382 LONGT : NIL) { | |
1383 int fd; | |
1384 unsigned long i; | |
1385 MESSAGECACHE *elt; | |
1386 unsigned long newsize,hdrsize,size; | |
1387 MIXLOCAL *local = (MIXLOCAL *) astream->local; | |
1388 unsigned long seq = mix_modseq (local->metaseq); | |
1389 /* make sure new modseq fits */ | |
1390 if (local->indexseq > seq) seq = local->indexseq + 1; | |
1391 if (local->statusseq > seq) seq = local->statusseq + 1; | |
1392 /* calculate size of per-message header */ | |
1393 sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0); | |
1394 hdrsize = strlen (local->buf); | |
1395 | |
1396 MM_CRITICAL (stream); /* go critical */ | |
1397 astream->silent = T; /* no events here */ | |
1398 /* calculate size that will be added */ | |
1399 for (i = 1, newsize = 0; i <= stream->nmsgs; ++i) | |
1400 if ((elt = mail_elt (stream,i))->sequence) | |
1401 newsize += hdrsize + elt->rfc822_size; | |
1402 /* open data file */ | |
1403 if (msgf = mix_data_open (astream,&fd,&size,newsize)) { | |
1404 char *t; | |
1405 unsigned long j,uid,uidv; | |
1406 copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL); | |
1407 SEARCHSET *source = cu ? mail_newsearchset () : NIL; | |
1408 SEARCHSET *dest = cu ? mail_newsearchset () : NIL; | |
1409 for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); ++i) | |
1410 if (((elt = mail_elt (stream,i))->sequence) && elt->rfc822_size) { | |
1411 /* is message in current message file? */ | |
1412 if ((LOCAL->msgfd < 0) || | |
1413 (elt->private.spare.data != LOCAL->curmsg)) { | |
1414 if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); | |
1415 if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf, | |
1416 stream->mailbox, | |
1417 elt->private.spare.data), | |
1418 O_RDONLY,NIL)) >= 0) | |
1419 LOCAL->curmsg = elt->private.spare.data; | |
1420 } | |
1421 if (LOCAL->msgfd < 0) ret = NIL; | |
1422 else { /* got file */ | |
1423 d.fd = LOCAL->msgfd;/* set up file descriptor */ | |
1424 /* start of message */ | |
1425 d.pos = elt->private.special.offset + | |
1426 elt->private.msg.header.offset; | |
1427 d.chunk = LOCAL->buf; | |
1428 d.chunksize = CHUNKSIZE; | |
1429 INIT (&st,fd_string,&d,elt->rfc822_size); | |
1430 /* init flag string */ | |
1431 tmp[0] = tmp[1] = '\0'; | |
1432 if (j = elt->user_flags) do | |
1433 if ((t = stream->user_flags[find_rightmost_bit (&j)]) && *t) | |
1434 strcat (strcat (tmp," "),t); | |
1435 while (j); | |
1436 if (elt->seen) strcat (tmp," \\Seen"); | |
1437 if (elt->deleted) strcat (tmp," \\Deleted"); | |
1438 if (elt->flagged) strcat (tmp," \\Flagged"); | |
1439 if (elt->answered) strcat (tmp," \\Answered"); | |
1440 if (elt->draft) strcat (tmp," \\Draft"); | |
1441 tmp[0] = '('; /* wrap list */ | |
1442 strcat (tmp,")"); | |
1443 /* if append OK, add to source set */ | |
1444 if ((ret = mix_append_msg (astream,msgf,tmp,elt,&st,dest, | |
1445 seq)) && source) | |
1446 mail_append_set (source,mail_uid (stream,i)); | |
1447 } | |
1448 } | |
1449 | |
1450 /* finish write if success */ | |
1451 if (ret && (ret = !fflush (msgf))) { | |
1452 fclose (msgf); /* all good, close the msg file now */ | |
1453 /* write new metadata, index, and status */ | |
1454 local->metaseq = local->indexseq = local->statusseq = seq; | |
1455 if (ret = (mix_meta_update (astream) && | |
1456 mix_index_update (astream,idxf,LONGT))) { | |
1457 /* success, delete if doing a move */ | |
1458 if (options & CP_MOVE) | |
1459 for (i = 1; i <= stream->nmsgs; i++) | |
1460 if ((elt = mail_elt (stream,i))->sequence) { | |
1461 elt->deleted = T; | |
1462 if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq; | |
1463 MM_FLAGS (stream,elt->msgno); | |
1464 } | |
1465 /* done with status file now */ | |
1466 mix_status_update (astream,statf,LONGT); | |
1467 /* return sets if doing COPYUID */ | |
1468 if (cu) (*cu) (stream,mailbox,astream->uid_validity,source,dest); | |
1469 source = dest = NIL; /* don't free these sets now */ | |
1470 } | |
1471 } | |
1472 else { /* error */ | |
1473 if (errno) { /* output error message if system call error */ | |
1474 sprintf (tmp,"Message copy failed: %.80s",strerror (errno)); | |
1475 MM_LOG (tmp,ERROR); | |
1476 } | |
1477 ftruncate (fd,size); /* revert file */ | |
1478 close (fd); /* make sure that fclose doesn't corrupt us */ | |
1479 fclose (msgf); /* free the stdio resources */ | |
1480 } | |
1481 /* flush any sets remaining */ | |
1482 mail_free_searchset (&source); | |
1483 mail_free_searchset (&dest); | |
1484 } | |
1485 else { /* message file open failed */ | |
1486 sprintf (tmp,"Error opening copy message file: %.80s", | |
1487 strerror (errno)); | |
1488 MM_LOG (tmp,ERROR); | |
1489 ret = NIL; | |
1490 } | |
1491 MM_NOCRITICAL (stream); | |
1492 } | |
1493 else MM_LOG ("Can't open copy mailbox",ERROR); | |
1494 if (statf) fclose (statf); /* close status if still open */ | |
1495 if (idxf) fclose (idxf); /* close index if still open */ | |
1496 /* finished with append stream */ | |
1497 if (astream) mail_close (astream); | |
1498 return ret; /* return state */ | |
1499 } | |
1500 | |
1501 /* MIX mail append message from stringstruct | |
1502 * Accepts: MAIL stream | |
1503 * destination mailbox | |
1504 * append callback | |
1505 * data for callback | |
1506 * Returns: T if append successful, else NIL | |
1507 */ | |
1508 | |
1509 long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) | |
1510 { | |
1511 STRING *message; | |
1512 char *flags,*date,tmp[MAILTMPLEN]; | |
1513 /* N.B.: can't use LOCAL->buf for tmp */ | |
1514 long ret = mix_isvalid (mailbox,tmp); | |
1515 /* default stream to prototype */ | |
1516 if (!stream) stream = user_flags (&mixproto); | |
1517 if (!ret) switch (errno) { /* if not valid mailbox */ | |
1518 case ENOENT: /* no such file? */ | |
1519 if (ret = compare_cstring (mailbox,"INBOX") ? | |
1520 NIL : mix_create (NIL,"INBOX")) | |
1521 break; | |
1522 MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); | |
1523 break; | |
1524 default: | |
1525 sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox); | |
1526 MM_LOG (tmp,ERROR); | |
1527 break; | |
1528 } | |
1529 | |
1530 /* get first message */ | |
1531 if (ret && MM_APPEND (af) (stream,data,&flags,&date,&message)) { | |
1532 MAILSTREAM *astream; | |
1533 FILE *idxf = NIL; | |
1534 FILE *msgf = NIL; | |
1535 FILE *statf = NIL; | |
1536 if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) && | |
1537 !astream->rdonly && | |
1538 (((MIXLOCAL *) astream->local)->expok = T) && | |
1539 (statf = mix_parse (astream,&idxf,LONGT,NIL))) ? | |
1540 LONGT : NIL) { | |
1541 int fd; | |
1542 unsigned long size,hdrsize; | |
1543 MESSAGECACHE elt; | |
1544 MIXLOCAL *local = (MIXLOCAL *) astream->local; | |
1545 unsigned long seq = mix_modseq (local->metaseq); | |
1546 /* make sure new modseq fits */ | |
1547 if (local->indexseq > seq) seq = local->indexseq + 1; | |
1548 if (local->statusseq > seq) seq = local->statusseq + 1; | |
1549 /* calculate size of per-message header */ | |
1550 sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0); | |
1551 hdrsize = strlen (local->buf); | |
1552 MM_CRITICAL (astream); /* go critical */ | |
1553 astream->silent = T; /* no events here */ | |
1554 /* open data file */ | |
1555 if (msgf = mix_data_open (astream,&fd,&size,hdrsize + SIZE (message))) { | |
1556 appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL); | |
1557 SEARCHSET *dst = au ? mail_newsearchset () : NIL; | |
1558 while (ret && message) {/* while good to go and have messages */ | |
1559 errno = NIL; /* in case one of these causes failure */ | |
1560 /* guard against zero-length */ | |
1561 if (!(ret = SIZE (message))) | |
1562 MM_LOG ("Append of zero-length message",ERROR); | |
1563 else if (date && !(ret = mail_parse_date (&elt,date))) { | |
1564 sprintf (tmp,"Bad date in append: %.80s",date); | |
1565 MM_LOG (tmp,ERROR); | |
1566 } | |
1567 else { | |
1568 if (!date) { /* if date not specified, use now */ | |
1569 internal_date (tmp); | |
1570 mail_parse_date (&elt,tmp); | |
1571 } | |
1572 ret = mix_append_msg (astream,msgf,flags,&elt,message,dst,seq) && | |
1573 MM_APPEND (af) (stream,data,&flags,&date,&message); | |
1574 } | |
1575 } | |
1576 | |
1577 /* finish write if success */ | |
1578 if (ret && (ret = !fflush (msgf))) { | |
1579 fclose (msgf); /* all good, close the msg file now */ | |
1580 /* write new metadata, index, and status */ | |
1581 local->metaseq = local->indexseq = local->statusseq = seq; | |
1582 if ((ret = (mix_meta_update (astream) && | |
1583 mix_index_update (astream,idxf,LONGT) && | |
1584 mix_status_update (astream,statf,LONGT))) && au) { | |
1585 (*au) (mailbox,astream->uid_validity,dst); | |
1586 dst = NIL; /* don't free this set now */ | |
1587 } | |
1588 } | |
1589 else { /* failure */ | |
1590 if (errno) { /* output error message if system call error */ | |
1591 sprintf (tmp,"Message append failed: %.80s",strerror (errno)); | |
1592 MM_LOG (tmp,ERROR); | |
1593 } | |
1594 ftruncate (fd,size); /* revert all writes to file*/ | |
1595 close (fd); /* make sure that fclose doesn't corrupt us */ | |
1596 fclose (msgf); /* free the stdio resources */ | |
1597 } | |
1598 /* flush any set remaining */ | |
1599 mail_free_searchset (&dst); | |
1600 } | |
1601 else { /* message file open failed */ | |
1602 sprintf (tmp,"Error opening append message file: %.80s", | |
1603 strerror (errno)); | |
1604 MM_LOG (tmp,ERROR); | |
1605 ret = NIL; | |
1606 } | |
1607 MM_NOCRITICAL (astream); /* release critical */ | |
1608 } | |
1609 else MM_LOG ("Can't open append mailbox",ERROR); | |
1610 if (statf) fclose (statf); /* close status if still open */ | |
1611 if (idxf) fclose (idxf); /* close index if still open */ | |
1612 if (astream) mail_close (astream); | |
1613 } | |
1614 return ret; | |
1615 } | |
1616 | |
1617 /* MIX mail append single message | |
1618 * Accepts: MAIL stream | |
1619 * flags for new message if non-NIL | |
1620 * elt with source date if non-NIL | |
1621 * stringstruct of message text | |
1622 * searchset to place UID | |
1623 * modseq of message | |
1624 * Returns: T if success, NIL if failure | |
1625 */ | |
1626 | |
1627 long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt, | |
1628 STRING *msg,SEARCHSET *set,unsigned long seq) | |
1629 { | |
1630 MESSAGECACHE *elt; | |
1631 int c,cs; | |
1632 unsigned long i,j,k,uf,hoff; | |
1633 long sf; | |
1634 stream->kwd_create = NIL; /* don't copy unknown keywords */ | |
1635 sf = mail_parse_flags (stream,flags,&uf); | |
1636 /* swell the cache */ | |
1637 mail_exists (stream,++stream->nmsgs); | |
1638 /* assign new UID from metadata */ | |
1639 (elt = mail_elt (stream,stream->nmsgs))->private.uid = ++stream->uid_last; | |
1640 elt->private.mod = seq; /* set requested modseq in status */ | |
1641 elt->rfc822_size = SIZE (msg);/* copy message size and date to index */ | |
1642 elt->year = delt->year; elt->month = delt->month; elt->day = delt->day; | |
1643 elt->hours = delt->hours; elt->minutes = delt->minutes; | |
1644 elt->seconds = delt->seconds; elt->zoccident = delt->zoccident; | |
1645 elt->zhours = delt->zhours; elt->zminutes = delt->zminutes; | |
1646 /* | |
1647 * Do NOT set elt->valid here! mix_status_update() uses it to determine | |
1648 * whether a message should be marked as old. | |
1649 */ | |
1650 if (sf&fSEEN) elt->seen = T; /* copy flags to status */ | |
1651 if (sf&fDELETED) elt->deleted = T; | |
1652 if (sf&fFLAGGED) elt->flagged = T; | |
1653 if (sf&fANSWERED) elt->answered = T; | |
1654 if (sf&fDRAFT) elt->draft = T; | |
1655 elt->user_flags |= uf; | |
1656 /* message is in new message file */ | |
1657 elt->private.spare.data = LOCAL->newmsg; | |
1658 | |
1659 /* offset to message internal header */ | |
1660 elt->private.special.offset = ftell (f); | |
1661 /* build header for message */ | |
1662 fprintf (f,MSRFMT,MSGTOK,elt->private.uid, | |
1663 elt->year + BASEYEAR,elt->month,elt->day, | |
1664 elt->hours,elt->minutes,elt->seconds, | |
1665 elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes, | |
1666 elt->rfc822_size); | |
1667 /* offset to header from internal header */ | |
1668 elt->private.msg.header.offset = ftell (f) - elt->private.special.offset; | |
1669 for (cs = 0; SIZE (msg); ) { /* copy message */ | |
1670 if (elt->private.msg.header.text.size) { | |
1671 if (msg->cursize) /* blat entire chunk if have it */ | |
1672 for (j = msg->cursize; j; j -= k) | |
1673 if (!(k = fwrite (msg->curpos,1,j,f))) return NIL; | |
1674 SETPOS (msg,GETPOS (msg) + msg->cursize); | |
1675 } | |
1676 else { /* still searching for delimiter */ | |
1677 c = 0xff & SNX (msg); /* get source character */ | |
1678 if (putc (c,f) == EOF) return NIL; | |
1679 switch (cs) { /* decide what to do based on state */ | |
1680 case 0: /* previous char ordinary */ | |
1681 if (c == '\015') cs = 1;/* advance if CR */ | |
1682 break; | |
1683 case 1: /* previous CR, advance if LF */ | |
1684 cs = (c == '\012') ? 2 : 0; | |
1685 break; | |
1686 case 2: /* previous CRLF, advance if CR */ | |
1687 cs = (c == '\015') ? 3 : 0; | |
1688 break; | |
1689 case 3: /* previous CRLFCR, done if LF */ | |
1690 if (c == '\012') elt->private.msg.header.text.size = | |
1691 elt->rfc822_size - SIZE (msg); | |
1692 cs = 0; /* reset mechanism */ | |
1693 break; | |
1694 } | |
1695 } | |
1696 } | |
1697 /* if no delimiter, header is entire msg */ | |
1698 if (!elt->private.msg.header.text.size) | |
1699 elt->private.msg.header.text.size = elt->rfc822_size; | |
1700 /* add this message to set */ | |
1701 mail_append_set (set,elt->private.uid); | |
1702 return LONGT; /* success */ | |
1703 } | |
1704 | |
1705 /* MIX mail read metadata, index, and status | |
1706 * Accepts: MAIL stream | |
1707 * returned index file | |
1708 * index file flags (non-NIL if want to add/remove messages) | |
1709 * status file flags (non-NIL if want to update elt->valid and old) | |
1710 * Returns: open status file, or NIL if failure | |
1711 * | |
1712 * Note that this routine can return an open index file even if it fails! | |
1713 */ | |
1714 | |
1715 static char *shortmsg = | |
1716 "message %lu (UID=%.08lx) truncated by %lu byte(s) (%lu < %lu)"; | |
1717 | |
1718 FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags) | |
1719 { | |
1720 int fd; | |
1721 unsigned long i; | |
1722 char *s,*t; | |
1723 struct stat sbuf; | |
1724 FILE *statf = NIL; | |
1725 short metarepairneeded = 0; | |
1726 short indexrepairneeded = 0; | |
1727 short silent = stream->silent; | |
1728 *idxf = NIL; /* in case error */ | |
1729 /* readonly means no updates */ | |
1730 if (stream->rdonly) iflags = sflags = NIL; | |
1731 /* open index file */ | |
1732 if ((fd = open (LOCAL->index,iflags ? O_RDWR : O_RDONLY,NIL)) < 0) | |
1733 MM_LOG ("Error opening mix index file",ERROR); | |
1734 /* acquire exclusive access and FILE */ | |
1735 else if (!flock (fd,iflags ? LOCK_EX : LOCK_SH) && | |
1736 !(*idxf = fdopen (fd,iflags ? "r+b" : "rb"))) { | |
1737 MM_LOG ("Error obtaining stream on mix index file",ERROR); | |
1738 flock (fd,LOCK_UN); /* relinquish lock */ | |
1739 close (fd); | |
1740 } | |
1741 | |
1742 /* slurp metadata */ | |
1743 else if (s = mix_meta_slurp (stream,&i)) { | |
1744 unsigned long j = 0; /* non-zero if UIDVALIDITY/UIDLAST changed */ | |
1745 if (i != LOCAL->metaseq) { /* metadata changed? */ | |
1746 char *t,*k; | |
1747 LOCAL->metaseq = i; /* note new metadata sequence */ | |
1748 while (s && *s) { /* parse entire metadata file */ | |
1749 /* locate end of line */ | |
1750 if (s = strstr (t = s,"\015\012")) { | |
1751 *s = '\0'; /* tie off line */ | |
1752 s += 2; /* skip past CRLF */ | |
1753 switch (*t++) { /* parse line */ | |
1754 case 'V': /* UIDVALIDITY */ | |
1755 if (!isxdigit (*t) || !(i = strtoul (t,&t,16))) { | |
1756 MM_LOG ("Error in mix metadata file UIDVALIDITY record",ERROR); | |
1757 return NIL; /* give up */ | |
1758 } | |
1759 if (i != stream->uid_validity) j = stream->uid_validity = i; | |
1760 break; | |
1761 case 'L': /* new UIDLAST */ | |
1762 if (!isxdigit (*t)) { | |
1763 MM_LOG ("Error in mix metadata file UIDLAST record",ERROR); | |
1764 return NIL; /* give up */ | |
1765 } | |
1766 if ((i = strtoul (t,&t,16)) != stream->uid_last) | |
1767 j = stream->uid_last = i; | |
1768 break; | |
1769 case 'N': /* new message file */ | |
1770 if (!isxdigit (*t)) { | |
1771 MM_LOG ("Error in mix metadata file new msg record",ERROR); | |
1772 return NIL; /* give up */ | |
1773 } | |
1774 if ((i = strtoul (t,&t,16)) != stream->uid_last) | |
1775 LOCAL->newmsg = i; | |
1776 break; | |
1777 case 'K': /* new keyword list */ | |
1778 for (i = 0; t && *t && (i < NUSERFLAGS); ++i) { | |
1779 if (t = strchr (k = t,' ')) *t++ = '\0'; | |
1780 /* make sure keyword non-empty */ | |
1781 if (*k && (strlen (k) <= MAXUSERFLAG)) { | |
1782 /* in case value changes (shouldn't happen) */ | |
1783 if (stream->user_flags[i] && strcmp (stream->user_flags[i],k)){ | |
1784 char tmp[MAILTMPLEN]; | |
1785 sprintf (tmp,"flag rename old=%.80s new=%.80s", | |
1786 stream->user_flags[i],k); | |
1787 MM_LOG (tmp,WARN); | |
1788 fs_give ((void **) &stream->user_flags[i]); | |
1789 } | |
1790 if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (k); | |
1791 } | |
1792 else break; /* empty keyword */ | |
1793 } | |
1794 if ((i < NUSERFLAGS) && stream->user_flags[i]) { | |
1795 MM_LOG ("Error in mix metadata file keyword record",ERROR); | |
1796 return NIL; /* give up */ | |
1797 } | |
1798 else if (i == NUSERFLAGS) stream->kwd_create = NIL; | |
1799 break; | |
1800 } | |
1801 } | |
1802 if (t && *t) { /* junk in line */ | |
1803 MM_LOG ("Error in mix metadata record",ERROR); | |
1804 return NIL; /* give up */ | |
1805 } | |
1806 } | |
1807 } | |
1808 | |
1809 /* get sequence */ | |
1810 if (!(i = mix_read_sequence (*idxf)) || (i < LOCAL->indexseq)) { | |
1811 MM_LOG ("Error in mix index file sequence record",ERROR); | |
1812 return NIL; /* give up */ | |
1813 } | |
1814 /* sequence changed from last time? */ | |
1815 else if (j || (i > LOCAL->indexseq)) { | |
1816 unsigned long uid,nmsgs,curfile,curfilesize,curpos; | |
1817 char *t,*msg,tmp[MAILTMPLEN]; | |
1818 /* start with no messages */ | |
1819 curfile = curfilesize = curpos = nmsgs = 0; | |
1820 /* update sequence iff expunging OK */ | |
1821 if (LOCAL->expok) LOCAL->indexseq = i; | |
1822 /* get first elt */ | |
1823 while ((s = mix_read_record (*idxf,LOCAL->buf,LOCAL->buflen,"index")) && | |
1824 *s) | |
1825 switch (*s) { | |
1826 case ':': /* message record */ | |
1827 if (!(isxdigit (*++s) && (uid = strtoul (s,&t,16)))) msg = "UID"; | |
1828 else if (!((*t++ == ':') && isdigit (*t) && isdigit (t[1]) && | |
1829 isdigit (t[2]) && isdigit (t[3]) && isdigit (t[4]) && | |
1830 isdigit (t[5]) && isdigit (t[6]) && isdigit (t[7]) && | |
1831 isdigit (t[8]) && isdigit (t[9]) && isdigit (t[10]) && | |
1832 isdigit (t[11]) && isdigit (t[12]) && isdigit (t[13]) && | |
1833 ((t[14] == '+') || (t[14] == '-')) && | |
1834 isdigit (t[15]) && isdigit (t[16]) && isdigit (t[17]) && | |
1835 isdigit (t[18]))) msg = "internaldate"; | |
1836 else if ((*(s = t+19) != ':') || !isxdigit (*++s)) msg = "size"; | |
1837 else { | |
1838 unsigned int y = (((*t - '0') * 1000) + ((t[1] - '0') * 100) + | |
1839 ((t[2] - '0') * 10) + t[3] - '0') - BASEYEAR; | |
1840 unsigned int m = ((t[4] - '0') * 10) + t[5] - '0'; | |
1841 unsigned int d = ((t[6] - '0') * 10) + t[7] - '0'; | |
1842 unsigned int hh = ((t[8] - '0') * 10) + t[9] - '0'; | |
1843 unsigned int mm = ((t[10] - '0') * 10) + t[11] - '0'; | |
1844 unsigned int ss = ((t[12] - '0') * 10) + t[13] - '0'; | |
1845 unsigned int z = (t[14] == '-') ? 1 : 0; | |
1846 unsigned int zh = ((t[15] - '0') * 10) + t[16] - '0'; | |
1847 unsigned int zm = ((t[17] - '0') * 10) + t[18] - '0'; | |
1848 unsigned long size = strtoul (s,&s,16); | |
1849 if ((*s++ == ':') && isxdigit (*s)) { | |
1850 unsigned long file = strtoul (s,&s,16); | |
1851 if ((*s++ == ':') && isxdigit (*s)) { | |
1852 unsigned long pos = strtoul (s,&s,16); | |
1853 if ((*s++ == ':') && isxdigit (*s)) { | |
1854 unsigned long hpos = strtoul (s,&s,16); | |
1855 if ((*s++ == ':') && isxdigit (*s)) { | |
1856 unsigned long hsiz = strtoul (s,&s,16); | |
1857 if (uid > stream->uid_last) { | |
1858 sprintf (tmp,"mix index invalid UID (%08lx < %08lx)", | |
1859 uid,stream->uid_last); | |
1860 if (stream->rdonly) { | |
1861 MM_LOG (tmp,ERROR); | |
1862 return NIL; | |
1863 } | |
1864 strcat (tmp,", repaired"); | |
1865 MM_LOG (tmp,WARN); | |
1866 stream->uid_last = uid; | |
1867 metarepairneeded = T; | |
1868 } | |
1869 | |
1870 /* ignore expansion values */ | |
1871 if (*s++ == ':') { | |
1872 MESSAGECACHE *elt; | |
1873 ++nmsgs; /* this is another mesage */ | |
1874 /* within current known range of messages? */ | |
1875 while (nmsgs <= stream->nmsgs) { | |
1876 /* yes, get corresponding elt */ | |
1877 elt = mail_elt (stream,nmsgs); | |
1878 /* existing message with matching data? */ | |
1879 if (uid == elt->private.uid) { | |
1880 /* beware of Dracula's resurrection */ | |
1881 if (elt->private.ghost) { | |
1882 sprintf (tmp,"mix index data unexpunged UID: %lx", | |
1883 uid); | |
1884 MM_LOG (tmp,ERROR); | |
1885 return NIL; | |
1886 } | |
1887 /* also of static data changing */ | |
1888 if ((size != elt->rfc822_size) || | |
1889 (file != elt->private.spare.data) || | |
1890 (pos != elt->private.special.offset) || | |
1891 (hpos != elt->private.msg.header.offset) || | |
1892 (hsiz != elt->private.msg.header.text.size) || | |
1893 (y != elt->year) || (m != elt->month) || | |
1894 (d != elt->day) || (hh != elt->hours) || | |
1895 (mm != elt->minutes) || (ss != elt->seconds) || | |
1896 (z != elt->zoccident) || (zh != elt->zhours) || | |
1897 (zm != elt->zminutes)) { | |
1898 sprintf (tmp,"mix index data mismatch: %lx",uid); | |
1899 MM_LOG (tmp,ERROR); | |
1900 return NIL; | |
1901 } | |
1902 break; | |
1903 } | |
1904 /* existing msg with lower UID is expunged */ | |
1905 else if (uid > elt->private.uid) { | |
1906 if (LOCAL->expok) mail_expunged (stream,nmsgs); | |
1907 else {/* message expunged, but not yet for us */ | |
1908 ++nmsgs; | |
1909 elt->private.ghost = T; | |
1910 } | |
1911 } | |
1912 else { /* unexpected message record */ | |
1913 sprintf (tmp,"mix index UID mismatch (%lx < %lx)", | |
1914 uid,elt->private.uid); | |
1915 MM_LOG (tmp,ERROR); | |
1916 return NIL; | |
1917 } | |
1918 } | |
1919 | |
1920 /* time to create a new message? */ | |
1921 if (nmsgs > stream->nmsgs) { | |
1922 /* defer announcing until later */ | |
1923 stream->silent = T; | |
1924 mail_exists (stream,nmsgs); | |
1925 stream->silent = silent; | |
1926 (elt = mail_elt (stream,nmsgs))->recent = T; | |
1927 elt->private.uid = uid; elt->rfc822_size = size; | |
1928 elt->private.spare.data = file; | |
1929 elt->private.special.offset = pos; | |
1930 elt->private.msg.header.offset = hpos; | |
1931 elt->private.msg.header.text.size = hsiz; | |
1932 elt->year = y; elt->month = m; elt->day = d; | |
1933 elt->hours = hh; elt->minutes = mm; | |
1934 elt->seconds = ss; elt->zoccident = z; | |
1935 elt->zhours = zh; elt->zminutes = zm; | |
1936 /* message in same file? */ | |
1937 if (curfile == file) { | |
1938 if (pos < curpos) { | |
1939 MESSAGECACHE *plt = mail_elt (stream,elt->msgno-1); | |
1940 /* uh-oh, calculate delta? */ | |
1941 i = curpos - pos; | |
1942 sprintf (tmp,shortmsg,plt->msgno,plt->private.uid, | |
1943 i,pos,curpos); | |
1944 /* possible to fix? */ | |
1945 if (!stream->rdonly && LOCAL->expok && | |
1946 (i < plt->rfc822_size)) { | |
1947 plt->rfc822_size -= i; | |
1948 if (plt->rfc822_size < | |
1949 plt->private.msg.header.text.size) | |
1950 plt->private.msg.header.text.size = | |
1951 plt->rfc822_size; | |
1952 strcat (tmp,", repaired"); | |
1953 indexrepairneeded = T; | |
1954 } | |
1955 MM_LOG (tmp,WARN); | |
1956 } | |
1957 } | |
1958 else { /* new file, restart */ | |
1959 if (stat (mix_file_data (LOCAL->buf,stream->mailbox, | |
1960 curfile = file),&sbuf)) { | |
1961 sprintf (tmp,"Missing mix data file: %.500s", | |
1962 LOCAL->buf); | |
1963 MM_LOG (tmp,ERROR); | |
1964 return NIL; | |
1965 } | |
1966 curfile = file; | |
1967 curfilesize = sbuf.st_size; | |
1968 } | |
1969 | |
1970 /* position of message in file */ | |
1971 curpos = pos + elt->private.msg.header.offset + | |
1972 elt->rfc822_size; | |
1973 /* short file? */ | |
1974 if (curfilesize < curpos) { | |
1975 /* uh-oh, calculate delta? */ | |
1976 i = curpos - curfilesize; | |
1977 sprintf (tmp,shortmsg,elt->msgno,elt->private.uid, | |
1978 i,curfilesize,curpos); | |
1979 /* possible to fix? */ | |
1980 if (!stream->rdonly && LOCAL->expok && | |
1981 (i < elt->rfc822_size)) { | |
1982 elt->rfc822_size -= i; | |
1983 if (elt->rfc822_size < | |
1984 elt->private.msg.header.text.size) | |
1985 elt->private.msg.header.text.size = | |
1986 elt->rfc822_size; | |
1987 strcat (tmp,", repaired"); | |
1988 indexrepairneeded = T; | |
1989 } | |
1990 MM_LOG (tmp,WARN); | |
1991 } | |
1992 } | |
1993 break; | |
1994 } | |
1995 else msg = "expansion"; | |
1996 } | |
1997 else msg = "header size"; | |
1998 } | |
1999 else msg = "header position"; | |
2000 } | |
2001 else msg = "message position"; | |
2002 } | |
2003 else msg = "file#"; | |
2004 } | |
2005 sprintf (tmp,"Error in %s in mix index file: %.500s",msg,s); | |
2006 MM_LOG (tmp,ERROR); | |
2007 return NIL; | |
2008 default: | |
2009 sprintf (tmp,"Unknown record in mix index file: %.500s",s); | |
2010 MM_LOG (tmp,ERROR); | |
2011 return NIL; | |
2012 } | |
2013 if (!s) return NIL; /* barfage from mix_read_record() */ | |
2014 /* expunge trailing messages not in index */ | |
2015 if (LOCAL->expok) while (nmsgs < stream->nmsgs) | |
2016 mail_expunged (stream,stream->nmsgs); | |
2017 } | |
2018 | |
2019 /* repair metadata and index if needed */ | |
2020 if ((metarepairneeded ? mix_meta_update (stream) : T) && | |
2021 (indexrepairneeded ? mix_index_update (stream,*idxf,NIL) : T)) { | |
2022 MESSAGECACHE *elt; | |
2023 int fd; | |
2024 unsigned long uid,uf,sf,mod; | |
2025 char *s; | |
2026 int updatep = NIL; | |
2027 /* open status file */ | |
2028 if ((fd = open (LOCAL->status, | |
2029 stream->rdonly ? O_RDONLY : O_RDWR,NIL)) < 0) | |
2030 MM_LOG ("Error opening mix status file",ERROR); | |
2031 /* acquire exclusive access and FILE */ | |
2032 else if (!flock (fd,stream->rdonly ? LOCK_SH : LOCK_EX) && | |
2033 !(statf = fdopen (fd,stream->rdonly ? "rb" : "r+b"))) { | |
2034 MM_LOG ("Error obtaining stream on mix status file",ERROR); | |
2035 flock (fd,LOCK_UN); /* relinquish lock */ | |
2036 close (fd); | |
2037 } | |
2038 /* get sequence */ | |
2039 else if (!(i = mix_read_sequence (statf)) || | |
2040 ((i < LOCAL->statusseq) && stream->nmsgs && (i != 1))) { | |
2041 sprintf (LOCAL->buf, | |
2042 "Error in mix status sequence record, i=%lx, seq=%lx", | |
2043 i,LOCAL->statusseq); | |
2044 MM_LOG (LOCAL->buf,ERROR); | |
2045 } | |
2046 /* sequence changed from last time? */ | |
2047 else if (i != LOCAL->statusseq) { | |
2048 /* update sequence, get first elt */ | |
2049 if (i > LOCAL->statusseq) LOCAL->statusseq = i; | |
2050 if (stream->nmsgs) { | |
2051 elt = mail_elt (stream,i = 1); | |
2052 | |
2053 /* read message records */ | |
2054 while ((t = s = mix_read_record (statf,LOCAL->buf,LOCAL->buflen, | |
2055 "status")) && *s && (*s++ == ':') && | |
2056 isxdigit (*s)) { | |
2057 uid = strtoul (s,&s,16); | |
2058 if ((*s++ == ':') && isxdigit (*s)) { | |
2059 uf = strtoul (s,&s,16); | |
2060 if ((*s++ == ':') && isxdigit (*s)) { | |
2061 sf = strtoul (s,&s,16); | |
2062 if ((*s++ == ':') && isxdigit (*s)) { | |
2063 mod = strtoul (s,&s,16); | |
2064 /* ignore expansion values */ | |
2065 if (*s++ == ':') { | |
2066 /* need to move ahead to next elt? */ | |
2067 while ((uid > elt->private.uid) && (i < stream->nmsgs)) | |
2068 elt = mail_elt (stream,++i); | |
2069 /* update elt if altered */ | |
2070 if ((uid == elt->private.uid) && | |
2071 (!elt->valid || (mod != elt->private.mod))) { | |
2072 elt->user_flags = uf; | |
2073 elt->private.mod = mod; | |
2074 elt->seen = (sf & fSEEN) ? T : NIL; | |
2075 elt->deleted = (sf & fDELETED) ? T : NIL; | |
2076 elt->flagged = (sf & fFLAGGED) ? T : NIL; | |
2077 elt->answered = (sf & fANSWERED) ? T : NIL; | |
2078 elt->draft = (sf & fDRAFT) ? T : NIL; | |
2079 /* announce if altered existing message */ | |
2080 if (elt->valid) MM_FLAGS (stream,elt->msgno); | |
2081 /* first time, is old message? */ | |
2082 else if (sf & fOLD) { | |
2083 /* yes, clear recent and set valid */ | |
2084 elt->recent = NIL; | |
2085 elt->valid = T; | |
2086 } | |
2087 /* recent, allowed to update its status? */ | |
2088 else if (sflags) { | |
2089 /* yes, set valid and check in status */ | |
2090 elt->valid = T; | |
2091 elt->private.mod = mix_modseq (elt->private.mod); | |
2092 updatep = T; | |
2093 } | |
2094 /* leave valid unset and recent if sflags not set */ | |
2095 } | |
2096 continue; /* everything looks good */ | |
2097 } | |
2098 } | |
2099 } | |
2100 } | |
2101 break; /* error somewhere */ | |
2102 } | |
2103 | |
2104 if (t && *t) { /* non-null means bogus record */ | |
2105 char msg[MAILTMPLEN]; | |
2106 sprintf (msg,"Error in mix status file message record%s: %.80s", | |
2107 stream->rdonly ? "" : ", fixing",t); | |
2108 MM_LOG (msg,WARN); | |
2109 /* update it if not readonly */ | |
2110 if (!stream->rdonly) updatep = T; | |
2111 } | |
2112 if (updatep) { /* need to update? */ | |
2113 LOCAL->statusseq = mix_modseq (LOCAL->statusseq); | |
2114 mix_status_update (stream,statf,LONGT); | |
2115 } | |
2116 } | |
2117 } | |
2118 } | |
2119 } | |
2120 if (statf) { /* still happy? */ | |
2121 unsigned long j; | |
2122 stream->silent = silent; /* now notify upper level */ | |
2123 mail_exists (stream,stream->nmsgs); | |
2124 for (i = 1, j = 0; i <= stream->nmsgs; ++i) | |
2125 if (mail_elt (stream,i)->recent) ++j; | |
2126 mail_recent (stream,j); | |
2127 } | |
2128 return statf; | |
2129 } | |
2130 | |
2131 /* MIX metadata file routines */ | |
2132 | |
2133 /* MIX read metadata | |
2134 * Accepts: MAIL stream | |
2135 * return pointer for modseq | |
2136 * Returns: pointer to metadata after modseq or NIL if failure | |
2137 */ | |
2138 | |
2139 char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq) | |
2140 { | |
2141 struct stat sbuf; | |
2142 char *s; | |
2143 char *ret = NIL; | |
2144 if (fstat (LOCAL->mfd,&sbuf)) | |
2145 MM_LOG ("Error obtaining size of mix metatdata file",ERROR); | |
2146 if (sbuf.st_size > LOCAL->buflen) { | |
2147 /* should be just a few dozen bytes */ | |
2148 if (sbuf.st_size > METAMAX) fatal ("absurd mix metadata file size"); | |
2149 fs_give ((void **) &LOCAL->buf); | |
2150 LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size) + 1); | |
2151 } | |
2152 /* read current metadata file */ | |
2153 LOCAL->buf[sbuf.st_size] = '\0'; | |
2154 if (lseek (LOCAL->mfd,0,L_SET) || | |
2155 (read (LOCAL->mfd,s = LOCAL->buf,sbuf.st_size) != sbuf.st_size)) | |
2156 MM_LOG ("Error reading mix metadata file",ERROR); | |
2157 else if ((*s != 'S') || !isxdigit (s[1]) || | |
2158 ((*seq = strtoul (s+1,&s,16)) < LOCAL->metaseq) || | |
2159 (*s++ != '\015') || (*s++ != '\012')) | |
2160 MM_LOG ("Error in mix metadata file sequence record",ERROR); | |
2161 else ret = s; | |
2162 return ret; | |
2163 } | |
2164 | |
2165 /* MIX update metadata | |
2166 * Accepts: MAIL stream | |
2167 * Returns: T on success, NIL if error | |
2168 * | |
2169 * Index MUST be locked!! | |
2170 */ | |
2171 | |
2172 long mix_meta_update (MAILSTREAM *stream) | |
2173 { | |
2174 long ret; | |
2175 /* do nothing if stream readonly */ | |
2176 if (stream->rdonly) ret = LONGT; | |
2177 else { | |
2178 unsigned char c,*s,*ss,*t; | |
2179 unsigned long i; | |
2180 /* The worst-case metadata is limited to: | |
2181 * 4 * (1 + 8 + 2) + (NUSERFLAGS * (MAXUSERFLAG + 1)) | |
2182 * which comes out to 1994 octets. This is much smaller than the normal | |
2183 * CHUNKSIZE definition of 64K, and CHUNKSIZE is the smallest size of | |
2184 * LOCAL->buf. | |
2185 * | |
2186 * If more stuff gets added to the metadata, or if you change the value | |
2187 * of NUSERFLAGS, MAXUSERFLAG or CHUNKSIZE, be sure to recalculate the | |
2188 * above assertation. | |
2189 */ | |
2190 sprintf (LOCAL->buf,SEQFMT,LOCAL->metaseq = mix_modseq (LOCAL->metaseq)); | |
2191 sprintf (LOCAL->buf + strlen (LOCAL->buf),MTAFMT, | |
2192 stream->uid_validity,stream->uid_last,LOCAL->newmsg); | |
2193 for (i = 0, c = 'K', s = ss = LOCAL->buf + strlen (LOCAL->buf); | |
2194 (i < NUSERFLAGS) && (t = stream->user_flags[i]); ++i) { | |
2195 if (!*t) fatal ("impossible empty keyword"); | |
2196 *s++ = c; /* write delimiter */ | |
2197 while (*t) *s++ = *t++; /* write keyword */ | |
2198 c = ' '; /* delimiter is now space */ | |
2199 } | |
2200 if (s != ss) { /* tie off keywords line */ | |
2201 *s++ = '\015'; *s++ = '\012'; | |
2202 } | |
2203 /* calculate length of metadata */ | |
2204 if ((i = s - LOCAL->buf) > LOCAL->buflen) | |
2205 fatal ("impossible buffer overflow"); | |
2206 lseek (LOCAL->mfd,0,L_SET); /* rewind file */ | |
2207 /* write new metadata */ | |
2208 ret = (write (LOCAL->mfd,LOCAL->buf,i) == i) ? LONGT : NIL; | |
2209 ftruncate (LOCAL->mfd,i); /* and tie off at that point */ | |
2210 } | |
2211 return ret; | |
2212 } | |
2213 | |
2214 /* MIX index file routines */ | |
2215 | |
2216 | |
2217 /* MIX update index | |
2218 * Accepts: MAIL stream | |
2219 * open FILE | |
2220 * expansion check flag | |
2221 * Returns: T on success, NIL if error | |
2222 */ | |
2223 | |
2224 long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag) | |
2225 { | |
2226 unsigned long i; | |
2227 long ret = LONGT; | |
2228 if (!stream->rdonly) { /* do nothing if stream readonly */ | |
2229 if (flag) { /* need to do expansion check? */ | |
2230 char tmp[MAILTMPLEN]; | |
2231 size_t size; | |
2232 struct stat sbuf; | |
2233 /* calculate file size we need */ | |
2234 for (i = 1, size = 0; i <= stream->nmsgs; ++i) | |
2235 if (!mail_elt (stream,i)->private.ghost) ++size; | |
2236 if (size) { /* Winston Smith's first dairy entry */ | |
2237 sprintf (tmp,IXRFMT,0,14,4,4,13,0,0,'+',0,0,0,0,0,0,0); | |
2238 size *= strlen (tmp); | |
2239 } | |
2240 /* calculate file size we need */ | |
2241 sprintf (tmp,SEQFMT,LOCAL->indexseq); | |
2242 size += strlen (tmp); | |
2243 /* get current file size */ | |
2244 if (fstat (fileno (idxf),&sbuf)) { | |
2245 MM_LOG ("Error getting size of mix index file",ERROR); | |
2246 ret = NIL; | |
2247 } | |
2248 /* need to write additional space? */ | |
2249 else if (sbuf.st_size < size) { | |
2250 void *buf = fs_get (size -= sbuf.st_size); | |
2251 memset (buf,0,size); | |
2252 if (fseek (idxf,0,SEEK_END) || (fwrite (buf,1,size,idxf) != size) || | |
2253 fflush (idxf)) { | |
2254 fseek (idxf,sbuf.st_size,SEEK_SET); | |
2255 ftruncate (fileno (idxf),sbuf.st_size); | |
2256 MM_LOG ("Error extending mix index file",ERROR); | |
2257 ret = NIL; | |
2258 } | |
2259 fs_give ((void **) &buf); | |
2260 } | |
2261 } | |
2262 | |
2263 if (ret) { /* if still good to go */ | |
2264 rewind (idxf); /* let's start at the very beginning */ | |
2265 /* write modseq first */ | |
2266 fprintf (idxf,SEQFMT,LOCAL->indexseq); | |
2267 /* then write all messages */ | |
2268 for (i = 1; ret && (i <= stream->nmsgs); i++) { | |
2269 MESSAGECACHE *elt = mail_elt (stream,i); | |
2270 if (!elt->private.ghost)/* only write living messages */ | |
2271 fprintf (idxf,IXRFMT,elt->private.uid, | |
2272 elt->year + BASEYEAR,elt->month,elt->day, | |
2273 elt->hours,elt->minutes,elt->seconds, | |
2274 elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes, | |
2275 elt->rfc822_size,elt->private.spare.data, | |
2276 elt->private.special.offset, | |
2277 elt->private.msg.header.offset, | |
2278 elt->private.msg.header.text.size); | |
2279 if (ferror (idxf)) { | |
2280 MM_LOG ("Error updating mix index file",ERROR); | |
2281 ret = NIL; | |
2282 } | |
2283 } | |
2284 if (fflush (idxf)) { | |
2285 MM_LOG ("Error flushing mix index file",ERROR); | |
2286 ret = NIL; | |
2287 } | |
2288 if (ret) ftruncate (fileno (idxf),ftell (idxf)); | |
2289 } | |
2290 } | |
2291 return ret; | |
2292 } | |
2293 | |
2294 /* MIX status file routines */ | |
2295 | |
2296 | |
2297 /* MIX update status | |
2298 * Accepts: MAIL stream | |
2299 * pointer to open FILE | |
2300 * expansion check flag | |
2301 * Returns: T on success, NIL if error | |
2302 */ | |
2303 | |
2304 long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag) | |
2305 { | |
2306 unsigned long i; | |
2307 char tmp[MAILTMPLEN]; | |
2308 long ret = LONGT; | |
2309 if (!stream->rdonly) { /* do nothing if stream readonly */ | |
2310 if (flag) { /* need to do expansion check? */ | |
2311 char tmp[MAILTMPLEN]; | |
2312 size_t size; | |
2313 struct stat sbuf; | |
2314 /* calculate file size we need */ | |
2315 for (i = 1, size = 0; i <= stream->nmsgs; ++i) | |
2316 if (!mail_elt (stream,i)->private.ghost) ++size; | |
2317 if (size) { /* number of living messages */ | |
2318 sprintf (tmp,STRFMT,0,0,0,0); | |
2319 size *= strlen (tmp); | |
2320 } | |
2321 sprintf (tmp,SEQFMT,LOCAL->statusseq); | |
2322 size += strlen (tmp); | |
2323 /* get current file size */ | |
2324 if (fstat (fileno (statf),&sbuf)) { | |
2325 MM_LOG ("Error getting size of mix status file",ERROR); | |
2326 ret = NIL; | |
2327 } | |
2328 /* need to write additional space? */ | |
2329 else if (sbuf.st_size < size) { | |
2330 void *buf = fs_get (size -= sbuf.st_size); | |
2331 memset (buf,0,size); | |
2332 if (fseek (statf,0,SEEK_END) || (fwrite (buf,1,size,statf) != size) || | |
2333 fflush (statf)) { | |
2334 fseek (statf,sbuf.st_size,SEEK_SET); | |
2335 ftruncate (fileno (statf),sbuf.st_size); | |
2336 MM_LOG ("Error extending mix status file",ERROR); | |
2337 ret = NIL; | |
2338 } | |
2339 fs_give ((void **) &buf); | |
2340 } | |
2341 } | |
2342 | |
2343 if (ret) { /* if still good to go */ | |
2344 rewind (statf); /* let's start at the very beginning */ | |
2345 /* write sequence */ | |
2346 fprintf (statf,SEQFMT,LOCAL->statusseq); | |
2347 /* write message status records */ | |
2348 for (i = 1; ret && (i <= stream->nmsgs); ++i) { | |
2349 MESSAGECACHE *elt = mail_elt (stream,i); | |
2350 /* make sure all messages have a modseq */ | |
2351 if (!elt->private.mod) elt->private.mod = LOCAL->statusseq; | |
2352 if (!elt->private.ghost)/* only write living messages */ | |
2353 fprintf (statf,STRFMT,elt->private.uid,elt->user_flags, | |
2354 (fSEEN * elt->seen) + (fDELETED * elt->deleted) + | |
2355 (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + | |
2356 (fDRAFT * elt->draft) + (elt->valid ? fOLD : NIL), | |
2357 elt->private.mod); | |
2358 if (ferror (statf)) { | |
2359 sprintf (tmp,"Error updating mix status file: %.80s", | |
2360 strerror (errno)); | |
2361 MM_LOG (tmp,ERROR); | |
2362 ret = NIL; | |
2363 } | |
2364 } | |
2365 if (ret && fflush (statf)) { | |
2366 MM_LOG ("Error flushing mix status file",ERROR); | |
2367 ret = NIL; | |
2368 } | |
2369 if (ret) ftruncate (fileno (statf),ftell (statf)); | |
2370 } | |
2371 } | |
2372 return ret; | |
2373 } | |
2374 | |
2375 /* MIX data file routines */ | |
2376 | |
2377 | |
2378 /* MIX open data file | |
2379 * Accepts: MAIL stream | |
2380 * pointer to returned fd if success | |
2381 * pointer to returned size if success | |
2382 * size of new data to be added | |
2383 * Returns: open FILE, or NIL if failure | |
2384 * | |
2385 * The curend test assumes that the last message of the mailbox is the furthest | |
2386 * point that the current data file extends, and thus that is all that needs to | |
2387 * be tested for short file prevention. | |
2388 */ | |
2389 | |
2390 FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size, | |
2391 unsigned long newsize) | |
2392 { | |
2393 FILE *msgf = NIL; | |
2394 struct stat sbuf; | |
2395 MESSAGECACHE *elt = stream->nmsgs ? mail_elt (stream,stream->nmsgs) : NIL; | |
2396 unsigned long curend = (elt && (elt->private.spare.data == LOCAL->newmsg)) ? | |
2397 elt->private.special.offset + elt->private.msg.header.offset + | |
2398 elt->rfc822_size : 0; | |
2399 /* allow create if curend 0 */ | |
2400 if ((*fd = open (mix_file_data (LOCAL->buf,stream->mailbox,LOCAL->newmsg), | |
2401 O_RDWR | (curend ? NIL : O_CREAT),NIL)) >= 0) { | |
2402 fstat (*fd,&sbuf); /* get current file size */ | |
2403 /* can we use this file? */ | |
2404 if ((curend <= sbuf.st_size) && | |
2405 (!sbuf.st_size || ((sbuf.st_size + newsize) <= MIXDATAROLL))) | |
2406 *size = sbuf.st_size; /* yes, return current size */ | |
2407 else { /* short file or becoming too long */ | |
2408 if (curend > sbuf.st_size) { | |
2409 char tmp[MAILTMPLEN]; | |
2410 sprintf (tmp,"short mix message file %.08lx (%ld > %ld), rolling", | |
2411 LOCAL->newmsg,curend,sbuf.st_size); | |
2412 MM_LOG (tmp,WARN); /* shouldn't happen */ | |
2413 } | |
2414 close (*fd); /* roll to a new file */ | |
2415 while ((*fd = open (mix_file_data | |
2416 (LOCAL->buf,stream->mailbox, | |
2417 LOCAL->newmsg = mix_modseq (LOCAL->newmsg)), | |
2418 O_RDWR | O_CREAT | O_EXCL,sbuf.st_mode)) < 0); | |
2419 *size = 0; /* brand new file */ | |
2420 fchmod (*fd,sbuf.st_mode);/* with same mode as previous file */ | |
2421 } | |
2422 } | |
2423 if (*fd >= 0) { /* have a data file? */ | |
2424 /* yes, get stdio and set position */ | |
2425 if (msgf = fdopen (*fd,"r+b")) fseek (msgf,*size,SEEK_SET); | |
2426 else close (*fd); /* fdopen() failed? */ | |
2427 } | |
2428 return msgf; /* return results */ | |
2429 } | |
2430 | |
2431 /* MIX open sortcache | |
2432 * Accepts: MAIL stream | |
2433 * Returns: open FILE, or NIL if failure or could only get readonly sortcache | |
2434 */ | |
2435 | |
2436 FILE *mix_sortcache_open (MAILSTREAM *stream) | |
2437 { | |
2438 int fd,refwd; | |
2439 unsigned long i,uid,sentdate,fromlen,tolen,cclen,subjlen,msgidlen,reflen; | |
2440 char *s,*t,*msg,tmp[MAILTMPLEN]; | |
2441 MESSAGECACHE *elt; | |
2442 SORTCACHE *sc; | |
2443 STRINGLIST *sl; | |
2444 struct stat sbuf; | |
2445 int rdonly = NIL; | |
2446 FILE *srtcf = NIL; | |
2447 mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); | |
2448 fstat (LOCAL->mfd,&sbuf); | |
2449 if (!stream->nmsgs); /* do nothing if mailbox empty */ | |
2450 /* open sortcache file */ | |
2451 else if (((fd = open (LOCAL->sortcache,O_RDWR|O_CREAT,sbuf.st_mode)) < 0) && | |
2452 !(rdonly = ((fd = open (LOCAL->sortcache,O_RDONLY,NIL)) >= 0))) | |
2453 MM_LOG ("Error opening mix sortcache file",WARN); | |
2454 /* acquire lock and FILE */ | |
2455 else if (!flock (fd,rdonly ? LOCK_SH : LOCK_EX) && | |
2456 !(srtcf = fdopen (fd,rdonly ? "rb" : "r+b"))) { | |
2457 MM_LOG ("Error obtaining stream on mix sortcache file",WARN); | |
2458 flock (fd,LOCK_UN); /* relinquish lock */ | |
2459 close (fd); | |
2460 } | |
2461 else if (!(i = mix_read_sequence (srtcf)) || (i < LOCAL->sortcacheseq)) | |
2462 MM_LOG ("Error in mix sortcache file sequence record",WARN); | |
2463 /* sequence changed from last time? */ | |
2464 else if (i > LOCAL->sortcacheseq) { | |
2465 LOCAL->sortcacheseq = i; /* update sequence */ | |
2466 while ((s = t = mix_read_record (srtcf,LOCAL->buf,LOCAL->buflen, | |
2467 "sortcache")) && *s && | |
2468 (msg = "uid") && (*s++ == ':') && isxdigit (*s)) { | |
2469 uid = strtoul (s,&s,16); | |
2470 if ((*s++ == ':') && isxdigit (*s)) { | |
2471 sentdate = strtoul (s,&s,16); | |
2472 if ((*s++ == ':') && isxdigit (*s)) { | |
2473 fromlen = strtoul (s,&s,16); | |
2474 if ((*s++ == ':') && isxdigit (*s)) { | |
2475 tolen = strtoul (s,&s,16); | |
2476 if ((*s++ == ':') && isxdigit (*s)) { | |
2477 cclen = strtoul (s,&s,16); | |
2478 if ((*s++ == ':') && ((*s == 'R') || (*s == ' ')) && | |
2479 isxdigit (s[1])) { | |
2480 refwd = (*s++ == 'R') ? T : NIL; | |
2481 subjlen = strtoul (s,&s,16); | |
2482 if ((*s++ == ':') && isxdigit (*s)) { | |
2483 msgidlen = strtoul (s,&s,16); | |
2484 if ((*s++ == ':') && isxdigit (*s)) { | |
2485 reflen = strtoul (s,&s,16); | |
2486 /* ignore expansion values */ | |
2487 if (*s++ == ':') { | |
2488 | |
2489 if (i = mail_msgno (stream,uid)) { | |
2490 sc = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE); | |
2491 sc->size = (elt = mail_elt (stream,i))->rfc822_size; | |
2492 sc->date = sentdate; | |
2493 sc->arrival = elt->day ? mail_longdate (elt) : 1; | |
2494 if (refwd) sc->refwd = T; | |
2495 if (fromlen) { | |
2496 if (sc->from) fseek (srtcf,fromlen + 2,SEEK_CUR); | |
2497 else if ((getc (srtcf) != 'F') || | |
2498 (fread (sc->from = (char *) fs_get(fromlen), | |
2499 1,fromlen-1,srtcf) != (fromlen-1))|| | |
2500 (sc->from[fromlen-1] = '\0') || | |
2501 (getc (srtcf) != '\015') || | |
2502 (getc (srtcf) != '\012')) { | |
2503 msg = "from data"; | |
2504 break; | |
2505 } | |
2506 } | |
2507 if (tolen) { | |
2508 if (sc->to) fseek (srtcf,tolen + 2,SEEK_CUR); | |
2509 else if ((getc (srtcf) != 'T') || | |
2510 (fread (sc->to = (char *) fs_get (tolen), | |
2511 1,tolen-1,srtcf) != (tolen - 1)) || | |
2512 (sc->to[tolen-1] = '\0') || | |
2513 (getc (srtcf) != '\015') || | |
2514 (getc (srtcf) != '\012')) { | |
2515 msg = "to data"; | |
2516 break; | |
2517 } | |
2518 } | |
2519 if (cclen) { | |
2520 if (sc->cc) fseek (srtcf,cclen + 2,SEEK_CUR); | |
2521 else if ((getc (srtcf) != 'C') || | |
2522 (fread (sc->cc = (char *) fs_get (cclen), | |
2523 1,cclen-1,srtcf) != (cclen - 1)) || | |
2524 (sc->cc[cclen-1] = '\0') || | |
2525 (getc (srtcf) != '\015') || | |
2526 (getc (srtcf) != '\012')) { | |
2527 msg = "cc data"; | |
2528 break; | |
2529 } | |
2530 } | |
2531 if (subjlen) { | |
2532 if (sc->subject) fseek (srtcf,subjlen + 2,SEEK_CUR); | |
2533 else if ((getc (srtcf) != 'S') || | |
2534 (fread (sc->subject = | |
2535 (char *) fs_get (subjlen),1, | |
2536 subjlen-1,srtcf) != (subjlen-1))|| | |
2537 (sc->subject[subjlen-1] = '\0') || | |
2538 (getc (srtcf) != '\015') || | |
2539 (getc (srtcf) != '\012')) { | |
2540 msg = "subject data"; | |
2541 break; | |
2542 } | |
2543 } | |
2544 | |
2545 if (msgidlen) { | |
2546 if (sc->message_id) | |
2547 fseek (srtcf,msgidlen + 2,SEEK_CUR); | |
2548 else if ((getc (srtcf) != 'M') || | |
2549 (fread (sc->message_id = | |
2550 (char *) fs_get (msgidlen),1, | |
2551 msgidlen-1,srtcf) != (msgidlen-1))|| | |
2552 (sc->message_id[msgidlen-1] = '\0') || | |
2553 (getc (srtcf) != '\015') || | |
2554 (getc (srtcf) != '\012')) { | |
2555 msg = "message-id data"; | |
2556 break; | |
2557 } | |
2558 } | |
2559 if (reflen) { | |
2560 if (sc->references) fseek(srtcf,reflen + 2,SEEK_CUR); | |
2561 /* make sure it fits */ | |
2562 else { | |
2563 if (reflen >= LOCAL->buflen) { | |
2564 fs_give ((void **) &LOCAL->buf); | |
2565 LOCAL->buf = (char *) | |
2566 fs_get ((LOCAL->buflen = reflen) + 1); | |
2567 } | |
2568 if ((getc (srtcf) != 'R') || | |
2569 (fread (LOCAL->buf,1,reflen-1,srtcf) != | |
2570 (reflen - 1)) || | |
2571 (LOCAL->buf[reflen-1] = '\0') || | |
2572 (getc (srtcf) != '\015') || | |
2573 (getc (srtcf) != '\012')) { | |
2574 msg = "references data"; | |
2575 break; | |
2576 } | |
2577 for (s = LOCAL->buf,sl = NIL, | |
2578 sc->references = mail_newstringlist (); | |
2579 s && *s; s += i + 1) { | |
2580 if ((i = strtoul (s,&s,16)) && (*s++ == ':') && | |
2581 (s[i] == ':')) { | |
2582 if (sl) sl = sl->next = mail_newstringlist(); | |
2583 else sl = sc->references; | |
2584 s[i] = '\0'; | |
2585 sl->text.data = cpystr (s); | |
2586 sl->text.size = i; | |
2587 } | |
2588 else s = NIL; | |
2589 } | |
2590 if (!s || *s || | |
2591 (s != ((char *) LOCAL->buf + reflen - 1))) { | |
2592 msg = "references length consistency check"; | |
2593 break; | |
2594 } | |
2595 } | |
2596 } | |
2597 } | |
2598 | |
2599 /* UID not found, ignore this message */ | |
2600 else fseek (srtcf,((fromlen ? fromlen + 2 : 0) + | |
2601 (tolen ? tolen + 2 : 0) + | |
2602 (cclen ? cclen + 2 : 0) + | |
2603 (subjlen ? subjlen + 2 : 0) + | |
2604 (msgidlen ? msgidlen + 2 : 0) + | |
2605 (reflen ? reflen + 2 : 0)), | |
2606 SEEK_CUR); | |
2607 continue; | |
2608 } | |
2609 else msg = "expansion"; | |
2610 } | |
2611 else msg = "references"; | |
2612 } | |
2613 else msg = "message-id"; | |
2614 } | |
2615 else msg = "subject"; | |
2616 } | |
2617 else msg = "cc"; | |
2618 } | |
2619 else msg = "to"; | |
2620 } | |
2621 else msg = "from"; | |
2622 } | |
2623 else msg = "sentdate"; | |
2624 break; /* error somewhere */ | |
2625 } | |
2626 if (!t || *t) { /* error detected? */ | |
2627 if (t) { /* non-null means bogus record */ | |
2628 sprintf (tmp,"Error in %s in mix sortcache record: %.500s",msg,t); | |
2629 MM_LOG (tmp,WARN); | |
2630 } | |
2631 fclose (srtcf); /* either way, must punt */ | |
2632 srtcf = NIL; | |
2633 } | |
2634 } | |
2635 if (rdonly && srtcf) { /* can't update if readonly */ | |
2636 unlink (LOCAL->sortcache); /* try deleting it */ | |
2637 fclose (srtcf); /* so close it and return as if error */ | |
2638 srtcf = NIL; | |
2639 } | |
2640 else fchmod (fd,sbuf.st_mode); | |
2641 return srtcf; | |
2642 } | |
2643 | |
2644 /* MIX update and close sortcache | |
2645 * Accepts: MAIL stream | |
2646 * pointer to open FILE (if FILE is NIL, do nothing) | |
2647 * Returns: T on success, NIL on error | |
2648 */ | |
2649 | |
2650 long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache) | |
2651 { | |
2652 FILE *f = *sortcache; | |
2653 long ret = LONGT; | |
2654 if (f) { /* ignore if no file */ | |
2655 unsigned long i,j; | |
2656 mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); | |
2657 for (i = 1; (i <= stream->nmsgs) && | |
2658 !((SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE))->dirty; ++i); | |
2659 if (i <= stream->nmsgs) { /* only update if some entry is dirty */ | |
2660 rewind (f); /* let's start at the very beginning */ | |
2661 /* write sequence */ | |
2662 fprintf (f,SEQFMT,LOCAL->sortcacheseq = mix_modseq(LOCAL->sortcacheseq)); | |
2663 for (i = 1; ret && (i <= stream->nmsgs); ++i) { | |
2664 MESSAGECACHE *elt = mail_elt (stream,i); | |
2665 SORTCACHE *s = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE); | |
2666 STRINGLIST *sl; | |
2667 s->dirty = NIL; /* no longer dirty */ | |
2668 if (sl = s->references) /* count length of references */ | |
2669 for (j = 1; sl && sl->text.data; sl = sl->next) | |
2670 j += 10 + sl->text.size; | |
2671 else j = 0; /* no references yet */ | |
2672 fprintf (f,SCRFMT,elt->private.uid,s->date, | |
2673 s->from ? strlen (s->from) + 1 : 0, | |
2674 s->to ? strlen (s->to) + 1 : 0,s->cc ? strlen (s->cc) + 1 : 0, | |
2675 s->refwd ? 'R' : ' ',s->subject ? strlen (s->subject) + 1: 0, | |
2676 s->message_id ? strlen (s->message_id) + 1 : 0,j); | |
2677 if (s->from) fprintf (f,"F%s\015\012",s->from); | |
2678 if (s->to) fprintf (f,"T%s\015\012",s->to); | |
2679 if (s->cc) fprintf (f,"C%s\015\012",s->cc); | |
2680 if (s->subject) fprintf (f,"S%s\015\012",s->subject); | |
2681 if (s->message_id) fprintf (f,"M%s\015\012",s->message_id); | |
2682 if (j) { /* any references to write? */ | |
2683 fputc ('R',f); /* yes, do so */ | |
2684 for (sl = s->references; sl && sl->text.data; sl = sl->next) | |
2685 fprintf (f,"%08lx:%s:",sl->text.size,sl->text.data); | |
2686 fputs ("\015\012",f); | |
2687 } | |
2688 if (ferror (f)) { | |
2689 MM_LOG ("Error updating mix sortcache file",WARN); | |
2690 ret = NIL; | |
2691 } | |
2692 } | |
2693 if (ret && fflush (f)) { | |
2694 MM_LOG ("Error flushing mix sortcache file",WARN); | |
2695 ret = NIL; | |
2696 } | |
2697 if (ret) ftruncate (fileno (f),ftell (f)); | |
2698 } | |
2699 if (fclose (f)) { | |
2700 MM_LOG ("Error closing mix sortcache file",WARN); | |
2701 ret = NIL; | |
2702 } | |
2703 } | |
2704 return ret; | |
2705 } | |
2706 | |
2707 /* MIX generic file routines */ | |
2708 | |
2709 /* MIX read record | |
2710 * Accepts: open FILE | |
2711 * buffer | |
2712 * buffer length | |
2713 * record type | |
2714 * Returns: buffer if success, else NIL (zero-length buffer means EOF) | |
2715 */ | |
2716 | |
2717 char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type) | |
2718 { | |
2719 char *s,tmp[MAILTMPLEN]; | |
2720 /* ensure string tied off */ | |
2721 buf[buflen-2] = buf[buflen-1] = '\0'; | |
2722 while (fgets (buf,buflen-1,f)) { | |
2723 if (s = strchr (buf,'\012')) { | |
2724 if ((s != buf) && (s[-1] == '\015')) --s; | |
2725 *s = '\0'; /* tie off buffer */ | |
2726 if (s != buf) return buf; /* return if non-empty buffer */ | |
2727 sprintf (tmp,"Empty mix %s record",type); | |
2728 MM_LOG (tmp,WARN); | |
2729 } | |
2730 else if (buf[buflen-2]) { /* overlong record is bad news */ | |
2731 sprintf (tmp,"Oversize mix %s record: %.512s",type,buf); | |
2732 MM_LOG (tmp,ERROR); | |
2733 return NIL; | |
2734 } | |
2735 else { | |
2736 sprintf (tmp,"Truncated mix %s record: %.512s",type,buf); | |
2737 MM_LOG (tmp,WARN); | |
2738 return buf; /* pass to caller anyway */ | |
2739 } | |
2740 } | |
2741 buf[0] = '\0'; /* return empty buffer on EOF */ | |
2742 return buf; | |
2743 } | |
2744 | |
2745 /* MIX read sequence record | |
2746 * Accepts: open FILE | |
2747 * Returns: sequence value, or NIL if failure | |
2748 */ | |
2749 | |
2750 unsigned long mix_read_sequence (FILE *f) | |
2751 { | |
2752 unsigned long ret; | |
2753 char *s,tmp[MAILTMPLEN]; | |
2754 if (!mix_read_record (f,tmp,MAILTMPLEN-1,"sequence")) return NIL; | |
2755 switch (tmp[0]) { /* examine record */ | |
2756 case '\0': /* end of file */ | |
2757 ret = 1; /* start a new sequence regime */ | |
2758 break; | |
2759 case 'S': /* sequence record */ | |
2760 if (isxdigit (tmp[1])) { /* must be followed by hex value */ | |
2761 ret = strtoul (tmp+1,&s,16); | |
2762 if (!*s) break; /* and nothing more */ | |
2763 } | |
2764 /* drop into default case */ | |
2765 default: /* anything else is an error */ | |
2766 return NIL; /* return error */ | |
2767 } | |
2768 return ret; | |
2769 } | |
2770 | |
2771 /* MIX internal routines */ | |
2772 | |
2773 | |
2774 /* MIX mail build directory name | |
2775 * Accepts: destination string | |
2776 * source | |
2777 * Returns: destination or empty string if error | |
2778 */ | |
2779 | |
2780 char *mix_dir (char *dst,char *name) | |
2781 { | |
2782 char *s; | |
2783 /* empty string if mailboxfile fails */ | |
2784 if (!mailboxfile (dst,name)) *dst = '\0'; | |
2785 /* driver-selected INBOX */ | |
2786 else if (!*dst) mailboxfile (dst,"~/INBOX"); | |
2787 /* tie off unnecessary trailing / */ | |
2788 else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0'; | |
2789 return dst; | |
2790 } | |
2791 | |
2792 | |
2793 /* MIX mail build file name | |
2794 * Accepts: destination string | |
2795 * directory name | |
2796 * file name | |
2797 * Returns: destination | |
2798 */ | |
2799 | |
2800 char *mix_file (char *dst,char *dir,char *name) | |
2801 { | |
2802 sprintf (dst,"%.500s/%.80s%.80s",dir,MIXNAME,name); | |
2803 return dst; | |
2804 } | |
2805 | |
2806 | |
2807 /* MIX mail build file name from data file number | |
2808 * Accepts: destination string | |
2809 * directory name | |
2810 * data file number | |
2811 * Returns: destination | |
2812 */ | |
2813 | |
2814 char *mix_file_data (char *dst,char *dir,unsigned long data) | |
2815 { | |
2816 char tmp[MAILTMPLEN]; | |
2817 if (data) sprintf (tmp,"%08lx",data); | |
2818 else tmp[0] = '\0'; /* compatibility with experimental version */ | |
2819 return mix_file (dst,dir,tmp); | |
2820 } | |
2821 | |
2822 /* MIX mail get new modseq | |
2823 * Accepts: old modseq | |
2824 * Returns: new modseq value | |
2825 */ | |
2826 | |
2827 unsigned long mix_modseq (unsigned long oldseq) | |
2828 { | |
2829 /* normally time now */ | |
2830 unsigned long ret = (unsigned long) time (NIL); | |
2831 /* ensure that modseq doesn't go backwards */ | |
2832 if (ret <= oldseq) ret = oldseq + 1; | |
2833 return ret; | |
2834 } |