imapext-2007
diff src/osdep/unix/mix.c @ 0:ada5e610ab86
imap-2007e
author | yuuji@gentei.org |
---|---|
date | Mon, 14 Sep 2009 15:17:45 +0900 |
parents | |
children |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/osdep/unix/mix.c Mon Sep 14 15:17:45 2009 +0900 1.3 @@ -0,0 +1,2834 @@ 1.4 +/* ======================================================================== 1.5 + * Copyright 1988-2008 University of Washington 1.6 + * 1.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.8 + * you may not use this file except in compliance with the License. 1.9 + * You may obtain a copy of the License at 1.10 + * 1.11 + * http://www.apache.org/licenses/LICENSE-2.0 1.12 + * 1.13 + * 1.14 + * ======================================================================== 1.15 + */ 1.16 + 1.17 +/* 1.18 + * Program: MIX mail routines 1.19 + * 1.20 + * Author(s): Mark Crispin 1.21 + * UW Technology 1.22 + * University of Washington 1.23 + * Seattle, WA 98195 1.24 + * Internet: MRC@Washington.EDU 1.25 + * 1.26 + * Date: 1 March 2006 1.27 + * Last Edited: 7 May 2008 1.28 + */ 1.29 + 1.30 + 1.31 +#include <stdio.h> 1.32 +#include <ctype.h> 1.33 +#include <errno.h> 1.34 +extern int errno; /* just in case */ 1.35 +#include "mail.h" 1.36 +#include "osdep.h" 1.37 +#include <pwd.h> 1.38 +#include <sys/stat.h> 1.39 +#include <sys/time.h> 1.40 +#include "misc.h" 1.41 +#include "dummy.h" 1.42 +#include "fdstring.h" 1.43 + 1.44 +/* MIX definitions */ 1.45 + 1.46 +#define MEGABYTE (1024*1024) 1.47 + 1.48 +#define MIXDATAROLL MEGABYTE /* size at which we roll to a new file */ 1.49 + 1.50 + 1.51 +/* MIX files */ 1.52 + 1.53 +#define MIXNAME ".mix" /* prefix for all MIX file names */ 1.54 +#define MIXMETA "meta" /* suffix for metadata */ 1.55 +#define MIXINDEX "index" /* suffix for index */ 1.56 +#define MIXSTATUS "status" /* suffix for status */ 1.57 +#define MIXSORTCACHE "sortcache"/* suffix for sortcache */ 1.58 +#define METAMAX (MEGABYTE-1) /* maximum metadata file size (sanity check) */ 1.59 + 1.60 + 1.61 +/* MIX file formats */ 1.62 + 1.63 + /* sequence format (all but msg files) */ 1.64 +#define SEQFMT "S%08lx\015\012" 1.65 + /* metadata file format */ 1.66 +#define MTAFMT "V%08lx\015\012L%08lx\015\012N%08lx\015\012" 1.67 + /* index file record format */ 1.68 +#define IXRFMT ":%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:%08lx:%08lx:%08lx:%08lx:\015\012" 1.69 + /* status file record format */ 1.70 +#define STRFMT ":%08lx:%08lx:%04x:%08lx:\015\012" 1.71 + /* message file header format */ 1.72 +#define MSRFMT "%s%08lx:%04d%02d%02d%02d%02d%02d%c%02d%02d:%08lx:\015\012" 1.73 +#define MSGTOK ":msg:" 1.74 +#define MSGTSZ (sizeof(MSGTOK)-1) 1.75 + /* sortcache file record format */ 1.76 +#define SCRFMT ":%08lx:%08lx:%08lx:%08lx:%08lx:%c%08lx:%08lx:%08lx:\015\012" 1.77 + 1.78 +/* MIX I/O stream local data */ 1.79 + 1.80 +typedef struct mix_local { 1.81 + unsigned long curmsg; /* current message file number */ 1.82 + unsigned long newmsg; /* current new message file number */ 1.83 + time_t lastsnarf; /* last snarf time */ 1.84 + int msgfd; /* file description of current msg file */ 1.85 + int mfd; /* file descriptor of open metadata */ 1.86 + unsigned long metaseq; /* metadata sequence */ 1.87 + char *index; /* mailbox index name */ 1.88 + unsigned long indexseq; /* index sequence */ 1.89 + char *status; /* mailbox status name */ 1.90 + unsigned long statusseq; /* status sequence */ 1.91 + char *sortcache; /* mailbox sortcache name */ 1.92 + unsigned long sortcacheseq; /* sortcache sequence */ 1.93 + unsigned char *buf; /* temporary buffer */ 1.94 + unsigned long buflen; /* current size of temporary buffer */ 1.95 + unsigned int expok : 1; /* non-zero if expunge reports OK */ 1.96 + unsigned int internal : 1; /* internally opened, do not validate */ 1.97 +} MIXLOCAL; 1.98 + 1.99 + 1.100 +#define MIXBURP struct mix_burp 1.101 + 1.102 +MIXBURP { 1.103 + unsigned long fileno; /* message file number */ 1.104 + char *name; /* message file name */ 1.105 + SEARCHSET *tail; /* tail of ranges */ 1.106 + SEARCHSET set; /* set of retained ranges */ 1.107 + MIXBURP *next; /* next file to burp */ 1.108 +}; 1.109 + 1.110 + 1.111 +/* Convenient access to local data */ 1.112 + 1.113 +#define LOCAL ((MIXLOCAL *) stream->local) 1.114 + 1.115 +/* Function prototypes */ 1.116 + 1.117 +DRIVER *mix_valid (char *name); 1.118 +long mix_isvalid (char *name,char *meta); 1.119 +void *mix_parameters (long function,void *value); 1.120 +long mix_dirfmttest (char *name); 1.121 +void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents); 1.122 +long mix_scan_contents (char *name,char *contents,unsigned long csiz, 1.123 + unsigned long fsiz); 1.124 +void mix_list (MAILSTREAM *stream,char *ref,char *pat); 1.125 +void mix_lsub (MAILSTREAM *stream,char *ref,char *pat); 1.126 +long mix_subscribe (MAILSTREAM *stream,char *mailbox); 1.127 +long mix_unsubscribe (MAILSTREAM *stream,char *mailbox); 1.128 +long mix_create (MAILSTREAM *stream,char *mailbox); 1.129 +long mix_delete (MAILSTREAM *stream,char *mailbox); 1.130 +long mix_rename (MAILSTREAM *stream,char *old,char *newname); 1.131 +int mix_rselect (struct direct *name); 1.132 +MAILSTREAM *mix_open (MAILSTREAM *stream); 1.133 +void mix_close (MAILSTREAM *stream,long options); 1.134 +void mix_abort (MAILSTREAM *stream); 1.135 +char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, 1.136 + long flags); 1.137 +long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); 1.138 +void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags); 1.139 +unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, 1.140 + SORTPGM *pgm,long flags); 1.141 +THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset, 1.142 + SEARCHPGM *spg,long flags); 1.143 +long mix_ping (MAILSTREAM *stream); 1.144 +void mix_check (MAILSTREAM *stream); 1.145 +long mix_expunge (MAILSTREAM *stream,char *sequence,long options); 1.146 +int mix_select (struct direct *name); 1.147 +int mix_msgfsort (const void *d1,const void *d2); 1.148 +long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size); 1.149 +long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed); 1.150 +long mix_burp_check (SEARCHSET *set,size_t size,char *file); 1.151 +long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox, 1.152 + long options); 1.153 +long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data); 1.154 +long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt, 1.155 + STRING *msg,SEARCHSET *set,unsigned long seq); 1.156 + 1.157 +FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags); 1.158 +char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq); 1.159 +long mix_meta_update (MAILSTREAM *stream); 1.160 +long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag); 1.161 +long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag); 1.162 +FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size, 1.163 + unsigned long newsize); 1.164 +FILE *mix_sortcache_open (MAILSTREAM *stream); 1.165 +long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache); 1.166 +char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type); 1.167 +unsigned long mix_read_sequence (FILE *f); 1.168 +char *mix_dir (char *dst,char *name); 1.169 +char *mix_file (char *dst,char *dir,char *name); 1.170 +char *mix_file_data (char *dst,char *dir,unsigned long data); 1.171 +unsigned long mix_modseq (unsigned long oldseq); 1.172 + 1.173 +/* MIX mail routines */ 1.174 + 1.175 + 1.176 +/* Driver dispatch used by MAIL */ 1.177 + 1.178 +DRIVER mixdriver = { 1.179 + "mix", /* driver name */ 1.180 + /* driver flags */ 1.181 + DR_MAIL|DR_LOCAL|DR_NOFAST|DR_CRLF|DR_LOCKING|DR_DIRFMT|DR_MODSEQ, 1.182 + (DRIVER *) NIL, /* next driver */ 1.183 + mix_valid, /* mailbox is valid for us */ 1.184 + mix_parameters, /* manipulate parameters */ 1.185 + mix_scan, /* scan mailboxes */ 1.186 + mix_list, /* find mailboxes */ 1.187 + mix_lsub, /* find subscribed mailboxes */ 1.188 + mix_subscribe, /* subscribe to mailbox */ 1.189 + mix_unsubscribe, /* unsubscribe from mailbox */ 1.190 + mix_create, /* create mailbox */ 1.191 + mix_delete, /* delete mailbox */ 1.192 + mix_rename, /* rename mailbox */ 1.193 + mail_status_default, /* status of mailbox */ 1.194 + mix_open, /* open mailbox */ 1.195 + mix_close, /* close mailbox */ 1.196 + NIL, /* fetch message "fast" attributes */ 1.197 + NIL, /* fetch message flags */ 1.198 + NIL, /* fetch overview */ 1.199 + NIL, /* fetch message envelopes */ 1.200 + mix_header, /* fetch message header only */ 1.201 + mix_text, /* fetch message body only */ 1.202 + NIL, /* fetch partial message test */ 1.203 + NIL, /* unique identifier */ 1.204 + NIL, /* message number */ 1.205 + mix_flag, /* modify flags */ 1.206 + NIL, /* per-message modify flags */ 1.207 + NIL, /* search for message based on criteria */ 1.208 + mix_sort, /* sort messages */ 1.209 + mix_thread, /* thread messages */ 1.210 + mix_ping, /* ping mailbox to see if still alive */ 1.211 + mix_check, /* check for new messages */ 1.212 + mix_expunge, /* expunge deleted messages */ 1.213 + mix_copy, /* copy messages to another mailbox */ 1.214 + mix_append, /* append string message to mailbox */ 1.215 + NIL /* garbage collect stream */ 1.216 +}; 1.217 + 1.218 + /* prototype stream */ 1.219 +MAILSTREAM mixproto = {&mixdriver}; 1.220 + 1.221 +/* MIX mail validate mailbox 1.222 + * Accepts: mailbox name 1.223 + * Returns: our driver if name is valid, NIL otherwise 1.224 + */ 1.225 + 1.226 +DRIVER *mix_valid (char *name) 1.227 +{ 1.228 + char tmp[MAILTMPLEN]; 1.229 + return mix_isvalid (name,tmp) ? &mixdriver : NIL; 1.230 +} 1.231 + 1.232 + 1.233 +/* MIX mail test for valid mailbox 1.234 + * Accepts: mailbox name 1.235 + * buffer to return meta name 1.236 + * Returns: T if valid, NIL otherwise, metadata name written in both cases 1.237 + */ 1.238 + 1.239 +long mix_isvalid (char *name,char *meta) 1.240 +{ 1.241 + char dir[MAILTMPLEN]; 1.242 + struct stat sbuf; 1.243 + /* validate name as directory */ 1.244 + if (!(errno = ((strlen (name) > NETMAXMBX) ? ENAMETOOLONG : NIL)) && 1.245 + *mix_dir (dir,name) && mix_file (meta,dir,MIXMETA) && 1.246 + !stat (dir,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) { 1.247 + /* name is directory; is it mix? */ 1.248 + if (!stat (meta,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG)) 1.249 + return LONGT; 1.250 + else errno = NIL; /* directory but not mix */ 1.251 + } 1.252 + return NIL; 1.253 +} 1.254 + 1.255 +/* MIX manipulate driver parameters 1.256 + * Accepts: function code 1.257 + * function-dependent value 1.258 + * Returns: function-dependent return value 1.259 + */ 1.260 + 1.261 +void *mix_parameters (long function,void *value) 1.262 +{ 1.263 + void *ret = NIL; 1.264 + switch ((int) function) { 1.265 + case GET_INBOXPATH: 1.266 + if (value) ret = mailboxfile ((char *) value,"~/INBOX"); 1.267 + break; 1.268 + case GET_DIRFMTTEST: 1.269 + ret = (void *) mix_dirfmttest; 1.270 + break; 1.271 + case GET_SCANCONTENTS: 1.272 + ret = (void *) mix_scan_contents; 1.273 + break; 1.274 + case SET_ONETIMEEXPUNGEATPING: 1.275 + if (value) ((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T; 1.276 + case GET_ONETIMEEXPUNGEATPING: 1.277 + if (value) ret = (void *) 1.278 + (((MIXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL); 1.279 + break; 1.280 + } 1.281 + return ret; 1.282 +} 1.283 + 1.284 + 1.285 +/* MIX test for directory format internal node 1.286 + * Accepts: candidate node name 1.287 + * Returns: T if internal name, NIL otherwise 1.288 + */ 1.289 + 1.290 +long mix_dirfmttest (char *name) 1.291 +{ 1.292 + /* belongs to MIX if starts with .mix */ 1.293 + return strncmp (name,MIXNAME,sizeof (MIXNAME) - 1) ? NIL : LONGT; 1.294 +} 1.295 + 1.296 +/* MIX mail scan mailboxes 1.297 + * Accepts: mail stream 1.298 + * reference 1.299 + * pattern to search 1.300 + * string to scan 1.301 + */ 1.302 + 1.303 +void mix_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents) 1.304 +{ 1.305 + if (stream) dummy_scan (NIL,ref,pat,contents); 1.306 +} 1.307 + 1.308 + 1.309 +/* MIX scan mailbox for contents 1.310 + * Accepts: mailbox name 1.311 + * desired contents 1.312 + * contents size 1.313 + * file size (ignored) 1.314 + * Returns: NIL if contents not found, T if found 1.315 + */ 1.316 + 1.317 +long mix_scan_contents (char *name,char *contents,unsigned long csiz, 1.318 + unsigned long fsiz) 1.319 +{ 1.320 + long i,nfiles; 1.321 + void *a; 1.322 + char *s; 1.323 + long ret = NIL; 1.324 + size_t namelen = strlen (name); 1.325 + struct stat sbuf; 1.326 + struct direct **names = NIL; 1.327 + if ((nfiles = scandir (name,&names,mix_select,mix_msgfsort)) > 0) 1.328 + for (i = 0; i < nfiles; ++i) { 1.329 + if (!ret) { 1.330 + sprintf (s = (char *) fs_get (namelen + strlen (names[i]->d_name) + 2), 1.331 + "%s/%s",name,names[i]->d_name); 1.332 + if (!stat (s,&sbuf) && (csiz <= sbuf.st_size)) 1.333 + ret = dummy_scan_contents (s,contents,csiz,sbuf.st_size); 1.334 + fs_give ((void **) &s); 1.335 + } 1.336 + fs_give ((void **) &names[i]); 1.337 + } 1.338 + /* free directory list */ 1.339 + if (a = (void *) names) fs_give ((void **) &a); 1.340 + return ret; 1.341 +} 1.342 + 1.343 +/* MIX list mailboxes 1.344 + * Accepts: mail stream 1.345 + * reference 1.346 + * pattern to search 1.347 + */ 1.348 + 1.349 +void mix_list (MAILSTREAM *stream,char *ref,char *pat) 1.350 +{ 1.351 + if (stream) dummy_list (NIL,ref,pat); 1.352 +} 1.353 + 1.354 + 1.355 +/* MIX list subscribed mailboxes 1.356 + * Accepts: mail stream 1.357 + * reference 1.358 + * pattern to search 1.359 + */ 1.360 + 1.361 +void mix_lsub (MAILSTREAM *stream,char *ref,char *pat) 1.362 +{ 1.363 + if (stream) dummy_lsub (NIL,ref,pat); 1.364 +} 1.365 + 1.366 +/* MIX mail subscribe to mailbox 1.367 + * Accepts: mail stream 1.368 + * mailbox to add to subscription list 1.369 + * Returns: T on success, NIL on failure 1.370 + */ 1.371 + 1.372 +long mix_subscribe (MAILSTREAM *stream,char *mailbox) 1.373 +{ 1.374 + return sm_subscribe (mailbox); 1.375 +} 1.376 + 1.377 + 1.378 +/* MIX mail unsubscribe to mailbox 1.379 + * Accepts: mail stream 1.380 + * mailbox to delete from subscription list 1.381 + * Returns: T on success, NIL on failure 1.382 + */ 1.383 + 1.384 +long mix_unsubscribe (MAILSTREAM *stream,char *mailbox) 1.385 +{ 1.386 + return sm_unsubscribe (mailbox); 1.387 +} 1.388 + 1.389 +/* MIX mail create mailbox 1.390 + * Accepts: mail stream 1.391 + * mailbox name to create 1.392 + * Returns: T on success, NIL on failure 1.393 + */ 1.394 + 1.395 +long mix_create (MAILSTREAM *stream,char *mailbox) 1.396 +{ 1.397 + DRIVER *test; 1.398 + FILE *f; 1.399 + int c,i; 1.400 + char *t,tmp[MAILTMPLEN],file[MAILTMPLEN]; 1.401 + char *s = strrchr (mailbox,'/'); 1.402 + unsigned long now = time (NIL); 1.403 + long ret = NIL; 1.404 + /* always create \NoSelect if trailing / */ 1.405 + if (s && !s[1]) return dummy_create (stream,mailbox); 1.406 + /* validate name */ 1.407 + if (mix_dirfmttest (s ? s + 1 : mailbox)) 1.408 + sprintf(tmp,"Can't create mailbox %.80s: invalid MIX-format name",mailbox); 1.409 + /* must not already exist */ 1.410 + else if ((test = mail_valid (NIL,mailbox,NIL)) && 1.411 + strcmp (test->name,"dummy")) 1.412 + sprintf (tmp,"Can't create mailbox %.80s: mailbox already exists",mailbox); 1.413 + /* create directory and metadata */ 1.414 + else if (!dummy_create_path (stream, 1.415 + mix_file (file,mix_dir (tmp,mailbox),MIXMETA), 1.416 + get_dir_protection (mailbox))) 1.417 + sprintf (tmp,"Can't create mailbox %.80s: %.80s",mailbox,strerror (errno)); 1.418 + else if (!(f = fopen (file,"w"))) 1.419 + sprintf (tmp,"Can't re-open metadata %.80s: %.80s",mailbox, 1.420 + strerror (errno)); 1.421 + else { /* success, write initial metadata */ 1.422 + fprintf (f,SEQFMT,now); 1.423 + fprintf (f,MTAFMT,now,0,now); 1.424 + for (i = 0, c = 'K'; (i < NUSERFLAGS) && 1.425 + (t = (stream && stream->user_flags[i]) ? stream->user_flags[i] : 1.426 + default_user_flag (i)) && *t; ++i) { 1.427 + putc (c,f); /* write another keyword */ 1.428 + fputs (t,f); 1.429 + c = ' '; /* delimiter is now space */ 1.430 + } 1.431 + fclose (f); 1.432 + set_mbx_protections (mailbox,file); 1.433 + /* point to suffix */ 1.434 + s = file + strlen (file) - (sizeof (MIXMETA) - 1); 1.435 + strcpy (s,MIXINDEX); /* create index */ 1.436 + if (!dummy_create_path (stream,file,get_dir_protection (mailbox))) 1.437 + sprintf (tmp,"Can't create mix mailbox index: %.80s",strerror (errno)); 1.438 + else { 1.439 + set_mbx_protections (mailbox,file); 1.440 + strcpy (s,MIXSTATUS); /* create status */ 1.441 + if (!dummy_create_path (stream,file,get_dir_protection (mailbox))) 1.442 + sprintf (tmp,"Can't create mix mailbox status: %.80s", 1.443 + strerror (errno)); 1.444 + else { 1.445 + set_mbx_protections (mailbox,file); 1.446 + sprintf (s,"%08lx",now);/* message file */ 1.447 + if (!dummy_create_path (stream,file,get_dir_protection (mailbox))) 1.448 + sprintf (tmp,"Can't create mix mailbox data: %.80s", 1.449 + strerror (errno)); 1.450 + else { 1.451 + set_mbx_protections (mailbox,file); 1.452 + ret = LONGT; /* declare success at this point */ 1.453 + } 1.454 + } 1.455 + } 1.456 + } 1.457 + if (!ret) MM_LOG (tmp,ERROR); /* some error */ 1.458 + return ret; 1.459 +} 1.460 + 1.461 +/* MIX mail delete mailbox 1.462 + * mailbox name to delete 1.463 + * Returns: T on success, NIL on failure 1.464 + */ 1.465 + 1.466 +long mix_delete (MAILSTREAM *stream,char *mailbox) 1.467 +{ 1.468 + DIR *dirp; 1.469 + struct direct *d; 1.470 + int fd = -1; 1.471 + char *s,tmp[MAILTMPLEN]; 1.472 + if (!mix_isvalid (mailbox,tmp)) 1.473 + sprintf (tmp,"Can't delete mailbox %.80s: no such mailbox",mailbox); 1.474 + else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB)) 1.475 + sprintf (tmp,"Can't lock mailbox for delete: %.80s",mailbox); 1.476 + /* delete metadata */ 1.477 + else if (unlink (tmp)) sprintf (tmp,"Can't delete mailbox %.80s index: %80s", 1.478 + mailbox,strerror (errno)); 1.479 + else { 1.480 + close (fd); /* close descriptor on deleted metadata */ 1.481 + /* get directory name */ 1.482 + *(s = strrchr (tmp,'/')) = '\0'; 1.483 + if (dirp = opendir (tmp)) { /* open directory */ 1.484 + *s++ = '/'; /* restore delimiter */ 1.485 + /* massacre messages */ 1.486 + while (d = readdir (dirp)) if (mix_dirfmttest (d->d_name)) { 1.487 + strcpy (s,d->d_name); /* make path */ 1.488 + unlink (tmp); /* sayonara */ 1.489 + } 1.490 + closedir (dirp); /* flush directory */ 1.491 + *(s = strrchr (tmp,'/')) = '\0'; 1.492 + if (rmdir (tmp)) { /* try to remove the directory */ 1.493 + sprintf (tmp,"Can't delete name %.80s: %.80s", 1.494 + mailbox,strerror (errno)); 1.495 + MM_LOG (tmp,WARN); 1.496 + } 1.497 + } 1.498 + return T; /* always success */ 1.499 + } 1.500 + if (fd >= 0) close (fd); /* close any descriptor on metadata */ 1.501 + MM_LOG (tmp,ERROR); /* something failed */ 1.502 + return NIL; 1.503 +} 1.504 + 1.505 +/* MIX mail rename mailbox 1.506 + * Accepts: MIX mail stream 1.507 + * old mailbox name 1.508 + * new mailbox name 1.509 + * Returns: T on success, NIL on failure 1.510 + */ 1.511 + 1.512 +long mix_rename (MAILSTREAM *stream,char *old,char *newname) 1.513 +{ 1.514 + char c,*s,tmp[MAILTMPLEN],tmp1[MAILTMPLEN]; 1.515 + struct stat sbuf; 1.516 + int fd = -1; 1.517 + if (!mix_isvalid (old,tmp)) 1.518 + sprintf (tmp,"Can't rename mailbox %.80s: no such mailbox",old); 1.519 + else if (((fd = open (tmp,O_RDWR,NIL)) < 0) || flock (fd,LOCK_EX|LOCK_NB)) 1.520 + sprintf (tmp,"Can't lock mailbox for rename: %.80s",old); 1.521 + else if (mix_dirfmttest ((s = strrchr (newname,'/')) ? s + 1 : newname)) 1.522 + sprintf (tmp,"Can't rename to mailbox %.80s: invalid MIX-format name", 1.523 + newname); 1.524 + /* new mailbox name must not be valid */ 1.525 + else if (mix_isvalid (newname,tmp)) 1.526 + sprintf (tmp,"Can't rename to mailbox %.80s: destination already exists", 1.527 + newname); 1.528 + else { 1.529 + mix_dir (tmp,old); /* build old directory name */ 1.530 + mix_dir (tmp1,newname); /* and new directory name */ 1.531 + /* easy if not INBOX */ 1.532 + if (compare_cstring (old,"INBOX")) { 1.533 + /* found superior to destination name? */ 1.534 + if (s = strrchr (tmp1,'/')) { 1.535 + c = *++s; /* remember first character of inferior */ 1.536 + *s = '\0'; /* tie off to get just superior */ 1.537 + /* name doesn't exist, create it */ 1.538 + if ((stat (tmp1,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && 1.539 + !dummy_create_path (stream,tmp1,get_dir_protection (newname))) 1.540 + return NIL; 1.541 + *s = c; /* restore full name */ 1.542 + } 1.543 + if (!rename (tmp,tmp1)) { 1.544 + close (fd); /* close descriptor on metadata */ 1.545 + return LONGT; 1.546 + } 1.547 + } 1.548 + 1.549 + /* RFC 3501 requires this */ 1.550 + else if (dummy_create_path (stream,strcat (tmp1,"/"), 1.551 + get_dir_protection (newname))) { 1.552 + void *a; 1.553 + int i,n,lasterror; 1.554 + char *src,*dst; 1.555 + struct direct **names = NIL; 1.556 + size_t srcl = strlen (tmp); 1.557 + size_t dstl = strlen (tmp1); 1.558 + /* rename each mix file to new directory */ 1.559 + for (i = lasterror = 0,n = scandir (tmp,&names,mix_rselect,alphasort); 1.560 + i < n; ++i) { 1.561 + size_t len = strlen (names[i]->d_name); 1.562 + sprintf (src = (char *) fs_get (srcl + len + 2),"%s/%s", 1.563 + tmp,names[i]->d_name); 1.564 + sprintf (dst = (char *) fs_get (dstl + len + 1),"%s%s", 1.565 + tmp1,names[i]->d_name); 1.566 + if (rename (src,dst)) lasterror = errno; 1.567 + fs_give ((void **) &src); 1.568 + fs_give ((void **) &dst); 1.569 + fs_give ((void **) &names[i]); 1.570 + } 1.571 + /* free directory list */ 1.572 + if (a = (void *) names) fs_give ((void **) &a); 1.573 + if (lasterror) errno = lasterror; 1.574 + else { 1.575 + close (fd); /* close descriptor on metadata */ 1.576 + return mix_create (NIL,"INBOX"); 1.577 + } 1.578 + } 1.579 + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s", 1.580 + old,newname,strerror (errno)); 1.581 + } 1.582 + if (fd >= 0) close (fd); /* close any descriptor on metadata */ 1.583 + MM_LOG (tmp,ERROR); /* something failed */ 1.584 + return NIL; 1.585 +} 1.586 + 1.587 + 1.588 +/* MIX test for mix name 1.589 + * Accepts: candidate directory name 1.590 + * Returns: T if mix file name, NIL otherwise 1.591 + */ 1.592 + 1.593 +int mix_rselect (struct direct *name) 1.594 +{ 1.595 + return mix_dirfmttest (name->d_name); 1.596 +} 1.597 + 1.598 +/* MIX mail open 1.599 + * Accepts: stream to open 1.600 + * Returns: stream on success, NIL on failure 1.601 + */ 1.602 + 1.603 +MAILSTREAM *mix_open (MAILSTREAM *stream) 1.604 +{ 1.605 + short silent; 1.606 + /* return prototype for OP_PROTOTYPE call */ 1.607 + if (!stream) return user_flags (&mixproto); 1.608 + if (stream->local) fatal ("mix recycle stream"); 1.609 + stream->local = memset (fs_get (sizeof (MIXLOCAL)),0,sizeof (MIXLOCAL)); 1.610 + /* note if an INBOX or not */ 1.611 + stream->inbox = !compare_cstring (stream->mailbox,"INBOX"); 1.612 + /* make temporary buffer */ 1.613 + LOCAL->buf = (char *) fs_get (CHUNKSIZE); 1.614 + LOCAL->buflen = CHUNKSIZE - 1; 1.615 + /* set stream->mailbox to be directory name */ 1.616 + mix_dir (LOCAL->buf,stream->mailbox); 1.617 + fs_give ((void **) &stream->mailbox); 1.618 + stream->mailbox = cpystr (LOCAL->buf); 1.619 + LOCAL->msgfd = -1; /* currently no file open */ 1.620 + if (!(((!stream->rdonly && /* open metadata file */ 1.621 + ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA), 1.622 + O_RDWR,NIL)) >= 0)) || 1.623 + ((stream->rdonly = T) && 1.624 + ((LOCAL->mfd = open (mix_file (LOCAL->buf,stream->mailbox,MIXMETA), 1.625 + O_RDONLY,NIL)) >= 0))) && 1.626 + !flock (LOCAL->mfd,LOCK_SH))) { 1.627 + MM_LOG ("Error opening mix metadata file",ERROR); 1.628 + mix_abort (stream); 1.629 + stream = NIL; /* open fails */ 1.630 + } 1.631 + else { /* metadata open, complete open */ 1.632 + LOCAL->index = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXINDEX)); 1.633 + LOCAL->status = cpystr (mix_file (LOCAL->buf,stream->mailbox,MIXSTATUS)); 1.634 + LOCAL->sortcache = cpystr (mix_file (LOCAL->buf,stream->mailbox, 1.635 + MIXSORTCACHE)); 1.636 + stream->sequence++; /* bump sequence number */ 1.637 + /* parse mailbox */ 1.638 + stream->nmsgs = stream->recent = 0; 1.639 + if (silent = stream->silent) LOCAL->internal = T; 1.640 + stream->silent = T; 1.641 + if (mix_ping (stream)) { /* do initial ping */ 1.642 + /* try burping in case we are exclusive */ 1.643 + if (!stream->rdonly) mix_expunge (stream,"",NIL); 1.644 + if (!(stream->nmsgs || stream->silent)) 1.645 + MM_LOG ("Mailbox is empty",(long) NIL); 1.646 + stream->silent = silent; /* now notify upper level */ 1.647 + mail_exists (stream,stream->nmsgs); 1.648 + stream->perm_seen = stream->perm_deleted = stream->perm_flagged = 1.649 + stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T; 1.650 + stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff; 1.651 + stream->kwd_create = /* can we create new user flags? */ 1.652 + (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ? NIL : T; 1.653 + } 1.654 + else { /* got murdelyzed in ping */ 1.655 + mix_abort (stream); 1.656 + stream = NIL; 1.657 + } 1.658 + } 1.659 + return stream; /* return stream to caller */ 1.660 +} 1.661 + 1.662 +/* MIX mail close 1.663 + * Accepts: MAIL stream 1.664 + * close options 1.665 + */ 1.666 + 1.667 +void mix_close (MAILSTREAM *stream,long options) 1.668 +{ 1.669 + if (LOCAL) { /* only if a file is open */ 1.670 + int silent = stream->silent; 1.671 + stream->silent = T; /* note this stream is dying */ 1.672 + /* burp-only or expunge */ 1.673 + mix_expunge (stream,(options & CL_EXPUNGE) ? NIL : "",NIL); 1.674 + mix_abort (stream); 1.675 + stream->silent = silent; /* reset silent state */ 1.676 + } 1.677 +} 1.678 + 1.679 + 1.680 +/* MIX mail abort stream 1.681 + * Accepts: MAIL stream 1.682 + */ 1.683 + 1.684 +void mix_abort (MAILSTREAM *stream) 1.685 +{ 1.686 + if (LOCAL) { /* only if a file is open */ 1.687 + /* close current message file if open */ 1.688 + if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); 1.689 + /* close current metadata file if open */ 1.690 + if (LOCAL->mfd >= 0) close (LOCAL->mfd); 1.691 + if (LOCAL->index) fs_give ((void **) &LOCAL->index); 1.692 + if (LOCAL->status) fs_give ((void **) &LOCAL->status); 1.693 + if (LOCAL->sortcache) fs_give ((void **) &LOCAL->sortcache); 1.694 + /* free local scratch buffer */ 1.695 + if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); 1.696 + /* nuke the local data */ 1.697 + fs_give ((void **) &stream->local); 1.698 + stream->dtb = NIL; /* log out the DTB */ 1.699 + } 1.700 +} 1.701 + 1.702 +/* MIX mail fetch message header 1.703 + * Accepts: MAIL stream 1.704 + * message # to fetch 1.705 + * pointer to returned header text length 1.706 + * option flags 1.707 + * Returns: message header in RFC822 format 1.708 + */ 1.709 + 1.710 +char *mix_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length, 1.711 + long flags) 1.712 +{ 1.713 + unsigned long i,j,k; 1.714 + int fd; 1.715 + char *s,tmp[MAILTMPLEN]; 1.716 + MESSAGECACHE *elt; 1.717 + if (length) *length = 0; /* default return */ 1.718 + if (flags & FT_UID) return "";/* UID call "impossible" */ 1.719 + elt = mail_elt (stream,msgno);/* get elt */ 1.720 + /* is message in current message file? */ 1.721 + if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) { 1.722 + if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); 1.723 + if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox, 1.724 + elt->private.spare.data), 1.725 + O_RDONLY,NIL)) < 0) return ""; 1.726 + /* got file */ 1.727 + LOCAL->curmsg = elt->private.spare.data; 1.728 + } 1.729 + lseek (LOCAL->msgfd,elt->private.special.offset,L_SET); 1.730 + /* size of special data and header */ 1.731 + j = elt->private.msg.header.offset + elt->private.msg.header.text.size; 1.732 + if (j > LOCAL->buflen) { /* is buffer big enough? */ 1.733 + /* no, make one that is */ 1.734 + fs_give ((void **) &LOCAL->buf); 1.735 + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = j) + 1); 1.736 + } 1.737 + /* Maybe someday validate internaldate too */ 1.738 + /* slurp special data + header, validate */ 1.739 + if ((read (LOCAL->msgfd,LOCAL->buf,j) == j) && 1.740 + !strncmp (LOCAL->buf,MSGTOK,MSGTSZ) && 1.741 + (elt->private.uid == strtoul ((char *) LOCAL->buf + MSGTSZ,&s,16)) && 1.742 + (*s++ == ':') && (s = strchr (s,':')) && 1.743 + (k = strtoul (s+1,&s,16)) && (*s++ == ':') && 1.744 + (s < (char *) (LOCAL->buf + elt->private.msg.header.offset))) { 1.745 + /* won, set offset and size of message */ 1.746 + i = elt->private.msg.header.offset; 1.747 + *length = elt->private.msg.header.text.size; 1.748 + if (k != elt->rfc822_size) { 1.749 + sprintf (tmp,"Inconsistency in mix message size, uid=%lx (%lu != %lu)", 1.750 + elt->private.uid,elt->rfc822_size,k); 1.751 + MM_LOG (tmp,WARN); 1.752 + } 1.753 + } 1.754 + else { /* document the problem */ 1.755 + LOCAL->buf[100] = '\0'; /* tie off buffer at no more than 100 octets */ 1.756 + /* or at newline, whichever is first */ 1.757 + if (s = strpbrk (LOCAL->buf,"\015\012")) *s = '\0'; 1.758 + sprintf (tmp,"Error reading mix message header, uid=%lx, s=%.0lx, h=%s", 1.759 + elt->private.uid,elt->rfc822_size,LOCAL->buf); 1.760 + MM_LOG (tmp,ERROR); 1.761 + *length = i = j = 0; /* default to empty */ 1.762 + } 1.763 + LOCAL->buf[j] = '\0'; /* tie off buffer at the end */ 1.764 + return (char *) LOCAL->buf + i; 1.765 +} 1.766 + 1.767 +/* MIX mail fetch message text (body only) 1.768 + * Accepts: MAIL stream 1.769 + * message # to fetch 1.770 + * pointer to returned stringstruct 1.771 + * option flags 1.772 + * Returns: T on success, NIL on failure 1.773 + */ 1.774 + 1.775 +long mix_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags) 1.776 +{ 1.777 + unsigned long i; 1.778 + FDDATA d; 1.779 + MESSAGECACHE *elt; 1.780 + /* UID call "impossible" */ 1.781 + if (flags & FT_UID) return NIL; 1.782 + elt = mail_elt (stream,msgno); 1.783 + /* is message in current message file? */ 1.784 + if ((LOCAL->msgfd < 0) || (elt->private.spare.data != LOCAL->curmsg)) { 1.785 + if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); 1.786 + if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf,stream->mailbox, 1.787 + elt->private.spare.data), 1.788 + O_RDONLY,NIL)) < 0) return NIL; 1.789 + /* got file */ 1.790 + LOCAL->curmsg = elt->private.spare.data; 1.791 + } 1.792 + /* doing non-peek fetch? */ 1.793 + if (!(flags & FT_PEEK) && !elt->seen) { 1.794 + FILE *idxf; /* yes, process metadata/index/status */ 1.795 + FILE *statf = mix_parse (stream,&idxf,NIL,LONGT); 1.796 + elt->seen = T; /* mark as seen */ 1.797 + MM_FLAGS (stream,elt->msgno); 1.798 + /* update status file if possible */ 1.799 + if (statf && !stream->rdonly) { 1.800 + elt->private.mod = LOCAL->statusseq = mix_modseq (LOCAL->statusseq); 1.801 + mix_status_update (stream,statf,NIL); 1.802 + } 1.803 + if (idxf) fclose (idxf); /* release index and status file */ 1.804 + if (statf) fclose (statf); 1.805 + } 1.806 + d.fd = LOCAL->msgfd; /* set up file descriptor */ 1.807 + /* offset of message text */ 1.808 + d.pos = elt->private.special.offset + elt->private.msg.header.offset + 1.809 + elt->private.msg.header.text.size; 1.810 + d.chunk = LOCAL->buf; /* initial buffer chunk */ 1.811 + d.chunksize = CHUNKSIZE; /* chunk size */ 1.812 + INIT (bs,fd_string,&d,elt->rfc822_size - elt->private.msg.header.text.size); 1.813 + return T; 1.814 +} 1.815 + 1.816 +/* MIX mail modify flags 1.817 + * Accepts: MAIL stream 1.818 + * sequence 1.819 + * flag(s) 1.820 + * option flags 1.821 + */ 1.822 + 1.823 +void mix_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags) 1.824 +{ 1.825 + MESSAGECACHE *elt; 1.826 + unsigned long i,uf,ffkey; 1.827 + long f; 1.828 + short nf; 1.829 + FILE *idxf; 1.830 + FILE *statf = mix_parse (stream,&idxf,NIL,LONGT); 1.831 + unsigned long seq = mix_modseq (LOCAL->statusseq); 1.832 + /* find first free key */ 1.833 + for (ffkey = 0; (ffkey < NUSERFLAGS) && stream->user_flags[ffkey]; ++ffkey); 1.834 + /* parse sequence and flags */ 1.835 + if (((flags & ST_UID) ? mail_uid_sequence (stream,sequence) : 1.836 + mail_sequence (stream,sequence)) && 1.837 + ((f = mail_parse_flags (stream,flag,&uf)) || uf)) { 1.838 + /* alter flags */ 1.839 + for (i = 1,nf = (flags & ST_SET) ? T : NIL; i <= stream->nmsgs; i++) 1.840 + if ((elt = mail_elt (stream,i))->sequence) { 1.841 + struct { /* old flags */ 1.842 + unsigned int seen : 1; 1.843 + unsigned int deleted : 1; 1.844 + unsigned int flagged : 1; 1.845 + unsigned int answered : 1; 1.846 + unsigned int draft : 1; 1.847 + unsigned long user_flags; 1.848 + } old; 1.849 + old.seen = elt->seen; old.deleted = elt->deleted; 1.850 + old.flagged = elt->flagged; old.answered = elt->answered; 1.851 + old.draft = elt->draft; old.user_flags = elt->user_flags; 1.852 + if (f&fSEEN) elt->seen = nf; 1.853 + if (f&fDELETED) elt->deleted = nf; 1.854 + if (f&fFLAGGED) elt->flagged = nf; 1.855 + if (f&fANSWERED) elt->answered = nf; 1.856 + if (f&fDRAFT) elt->draft = nf; 1.857 + /* user flags */ 1.858 + if (flags & ST_SET) elt->user_flags |= uf; 1.859 + else elt->user_flags &= ~uf; 1.860 + if ((old.seen != elt->seen) || (old.deleted != elt->deleted) || 1.861 + (old.flagged != elt->flagged) || 1.862 + (old.answered != elt->answered) || (old.draft != elt->draft) || 1.863 + (old.user_flags != elt->user_flags)) { 1.864 + if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq; 1.865 + MM_FLAGS (stream,elt->msgno); 1.866 + } 1.867 + } 1.868 + /* update status file after change */ 1.869 + if (statf && (seq == LOCAL->statusseq)) 1.870 + mix_status_update (stream,statf,NIL); 1.871 + /* update metadata if created a keyword */ 1.872 + if ((ffkey < NUSERFLAGS) && stream->user_flags[ffkey] && 1.873 + !mix_meta_update (stream)) 1.874 + MM_LOG ("Error updating mix metadata after keyword creation",ERROR); 1.875 + } 1.876 + if (statf) fclose (statf); /* release status file if still open */ 1.877 + if (idxf) fclose (idxf); /* release index file */ 1.878 +} 1.879 + 1.880 +/* MIX mail sort messages 1.881 + * Accepts: mail stream 1.882 + * character set 1.883 + * search program 1.884 + * sort program 1.885 + * option flags 1.886 + * Returns: vector of sorted message sequences or NIL if error 1.887 + */ 1.888 + 1.889 +unsigned long *mix_sort (MAILSTREAM *stream,char *charset,SEARCHPGM *spg, 1.890 + SORTPGM *pgm,long flags) 1.891 +{ 1.892 + unsigned long *ret; 1.893 + FILE *sortcache = mix_sortcache_open (stream); 1.894 + ret = mail_sort_msgs (stream,charset,spg,pgm,flags); 1.895 + mix_sortcache_update (stream,&sortcache); 1.896 + return ret; 1.897 +} 1.898 + 1.899 + 1.900 +/* MIX mail thread messages 1.901 + * Accepts: mail stream 1.902 + * thread type 1.903 + * character set 1.904 + * search program 1.905 + * option flags 1.906 + * Returns: thread node tree or NIL if error 1.907 + */ 1.908 + 1.909 +THREADNODE *mix_thread (MAILSTREAM *stream,char *type,char *charset, 1.910 + SEARCHPGM *spg,long flags) 1.911 +{ 1.912 + THREADNODE *ret; 1.913 + FILE *sortcache = mix_sortcache_open (stream); 1.914 + ret = mail_thread_msgs (stream,type,charset,spg,flags,mail_sort_msgs); 1.915 + mix_sortcache_update (stream,&sortcache); 1.916 + return ret; 1.917 +} 1.918 + 1.919 +/* MIX mail ping mailbox 1.920 + * Accepts: MAIL stream 1.921 + * Returns: T if stream alive, else NIL 1.922 + */ 1.923 + 1.924 +static int snarfing = 0; /* lock against recursive snarfing */ 1.925 + 1.926 +long mix_ping (MAILSTREAM *stream) 1.927 +{ 1.928 + FILE *idxf,*statf; 1.929 + struct stat sbuf; 1.930 + STRING msg; 1.931 + MESSAGECACHE *elt; 1.932 + int mfd,ifd,sfd; 1.933 + unsigned long i,msglen; 1.934 + char *message,date[MAILTMPLEN],flags[MAILTMPLEN]; 1.935 + MAILSTREAM *sysibx = NIL; 1.936 + long ret = NIL; 1.937 + long snarfok = LONGT; 1.938 + /* time to snarf? */ 1.939 + if (stream->inbox && !stream->rdonly && !snarfing && 1.940 + (time (0) >= (LOCAL->lastsnarf + 1.941 + (time_t) mail_parameters (NIL,GET_SNARFINTERVAL,NIL)))) { 1.942 + appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL); 1.943 + copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL); 1.944 + MM_CRITICAL (stream); /* go critical */ 1.945 + snarfing = T; /* don't recursively snarf */ 1.946 + /* disable APPENDUID/COPYUID callbacks */ 1.947 + mail_parameters (NIL,SET_APPENDUID,NIL); 1.948 + mail_parameters (NIL,SET_COPYUID,NIL); 1.949 + /* sizes match and anything in sysinbox? */ 1.950 + if (!stat (sysinbox (),&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFREG) && 1.951 + sbuf.st_size && (sysibx = mail_open (sysibx,sysinbox (),OP_SILENT)) && 1.952 + !sysibx->rdonly && sysibx->nmsgs) { 1.953 + /* for each message in sysibx mailbox */ 1.954 + for (i = 1; snarfok && (i <= sysibx->nmsgs); ++i) 1.955 + if (!(elt = mail_elt (sysibx,i))->deleted && 1.956 + (message = mail_fetch_message (sysibx,i,&msglen,FT_PEEK)) && 1.957 + msglen) { 1.958 + mail_date (date,elt); /* make internal date string */ 1.959 + /* make flag string */ 1.960 + flags[0] = flags[1] = '\0'; 1.961 + if (elt->seen) strcat (flags," \\Seen"); 1.962 + if (elt->flagged) strcat (flags," \\Flagged"); 1.963 + if (elt->answered) strcat (flags," \\Answered"); 1.964 + if (elt->draft) strcat (flags," \\Draft"); 1.965 + flags[0] = '('; 1.966 + strcat (flags,")"); 1.967 + INIT (&msg,mail_string,message,msglen); 1.968 + if (snarfok = mail_append_full (stream,"INBOX",flags,date,&msg)) { 1.969 + char sequence[15]; 1.970 + sprintf (sequence,"%lu",i); 1.971 + mail_flag (sysibx,sequence,"\\Deleted",ST_SET); 1.972 + } 1.973 + } 1.974 + 1.975 + /* now expunge all those messages */ 1.976 + if (snarfok) mail_expunge (sysibx); 1.977 + else { 1.978 + sprintf (LOCAL->buf,"Can't copy new mail at message: %lu",i - 1); 1.979 + MM_LOG (LOCAL->buf,WARN); 1.980 + } 1.981 + } 1.982 + if (sysibx) mail_close (sysibx); 1.983 + /* reenable APPENDUID/COPYUID */ 1.984 + mail_parameters (NIL,SET_APPENDUID,(void *) au); 1.985 + mail_parameters (NIL,SET_COPYUID,(void *) cu); 1.986 + snarfing = NIL; /* no longer snarfing */ 1.987 + MM_NOCRITICAL (stream); /* release critical */ 1.988 + LOCAL->lastsnarf = time (0);/* note time of last snarf */ 1.989 + } 1.990 + /* expunging OK if global flag set */ 1.991 + if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T; 1.992 + /* process metadata/index/status */ 1.993 + if (statf = mix_parse (stream,&idxf,LONGT, 1.994 + (LOCAL->internal ? NIL : LONGT))) { 1.995 + fclose (statf); /* just close the status file */ 1.996 + ret = LONGT; /* declare success */ 1.997 + } 1.998 + if (idxf) fclose (idxf); /* release index file */ 1.999 + LOCAL->expok = NIL; /* expunge no longer OK */ 1.1000 + if (!ret) mix_abort (stream); /* murdelyze stream if ping fails */ 1.1001 + return ret; 1.1002 +} 1.1003 + 1.1004 + 1.1005 +/* MIX mail checkpoint mailbox (burp only) 1.1006 + * Accepts: MAIL stream 1.1007 + */ 1.1008 + 1.1009 +void mix_check (MAILSTREAM *stream) 1.1010 +{ 1.1011 + if (stream->rdonly) /* won't do on readonly files! */ 1.1012 + MM_LOG ("Checkpoint ignored on readonly mailbox",NIL); 1.1013 + /* do burp-only expunge action */ 1.1014 + if (mix_expunge (stream,"",NIL)) MM_LOG ("Check completed",(long) NIL); 1.1015 +} 1.1016 + 1.1017 +/* MIX mail expunge mailbox 1.1018 + * Accepts: MAIL stream 1.1019 + * sequence to expunge if non-NIL, empty string for burp only 1.1020 + * expunge options 1.1021 + * Returns: T on success, NIL if failure 1.1022 + */ 1.1023 + 1.1024 +long mix_expunge (MAILSTREAM *stream,char *sequence,long options) 1.1025 +{ 1.1026 + FILE *idxf = NIL; 1.1027 + FILE *statf = NIL; 1.1028 + MESSAGECACHE *elt; 1.1029 + int ifd,sfd; 1.1030 + long ret; 1.1031 + unsigned long i; 1.1032 + unsigned long nexp = 0; 1.1033 + unsigned long reclaimed = 0; 1.1034 + int burponly = (sequence && !*sequence); 1.1035 + LOCAL->expok = T; /* expunge during ping is OK */ 1.1036 + if (!(ret = burponly || !sequence || 1.1037 + ((options & EX_UID) ? 1.1038 + mail_uid_sequence (stream,sequence) : 1.1039 + mail_sequence (stream,sequence))) || stream->rdonly); 1.1040 + /* read index and open status exclusive */ 1.1041 + else if (statf = mix_parse (stream,&idxf,LONGT, 1.1042 + LOCAL->internal ? NIL : LONGT)) { 1.1043 + /* expunge unless just burping */ 1.1044 + if (!burponly) for (i = 1; i <= stream->nmsgs;) { 1.1045 + elt = mail_elt (stream,i);/* need to expunge this message? */ 1.1046 + if (sequence ? elt->sequence : elt->deleted) { 1.1047 + ++nexp; /* yes, make it so */ 1.1048 + mail_expunged (stream,i); 1.1049 + } 1.1050 + else ++i; /* otherwise advance to next message */ 1.1051 + } 1.1052 + 1.1053 + /* burp if can get exclusive access */ 1.1054 + if (!flock (LOCAL->mfd,LOCK_EX|LOCK_NB)) { 1.1055 + void *a; 1.1056 + struct direct **names = NIL; 1.1057 + long nfiles = scandir (stream->mailbox,&names,mix_select,mix_msgfsort); 1.1058 + if (nfiles > 0) { /* if have message files */ 1.1059 + MIXBURP *burp,*cur; 1.1060 + /* initialize burp list */ 1.1061 + for (i = 0, burp = cur = NIL; i < nfiles; ++i) { 1.1062 + MIXBURP *nxt = (MIXBURP *) memset (fs_get (sizeof (MIXBURP)),0, 1.1063 + sizeof (MIXBURP)); 1.1064 + /* another file found */ 1.1065 + if (cur) cur = cur->next = nxt; 1.1066 + else cur = burp = nxt; 1.1067 + cur->name = names[i]->d_name; 1.1068 + cur->fileno = strtoul (cur->name + sizeof (MIXNAME) - 1,NIL,16); 1.1069 + cur->tail = &cur->set; 1.1070 + fs_give ((void **) &names[i]); 1.1071 + } 1.1072 + /* now load ranges */ 1.1073 + for (i = 1, cur = burp; ret && (i <= stream->nmsgs); i++) { 1.1074 + /* is this message in current set? */ 1.1075 + elt = mail_elt (stream,i); 1.1076 + if (cur && (elt->private.spare.data != cur->fileno)) { 1.1077 + /* restart if necessary */ 1.1078 + if (elt->private.spare.data < cur->fileno) cur = burp; 1.1079 + /* hunt for appropriate mailbox */ 1.1080 + while (cur && (elt->private.spare.data > cur->fileno)) 1.1081 + cur = cur->next; 1.1082 + /* ought to have found it now... */ 1.1083 + if (cur && (elt->private.spare.data != cur->fileno)) cur = NIL; 1.1084 + } 1.1085 + /* if found, add to set */ 1.1086 + if (cur) ret = mix_addset (&cur->tail,elt->private.special.offset, 1.1087 + elt->private.msg.header.offset + 1.1088 + elt->rfc822_size); 1.1089 + else { /* uh-oh */ 1.1090 + sprintf (LOCAL->buf,"Can't locate mix message file %.08lx", 1.1091 + elt->private.spare.data); 1.1092 + MM_LOG (LOCAL->buf,ERROR); 1.1093 + ret = NIL; 1.1094 + } 1.1095 + } 1.1096 + if (ret) /* if no errors, burp all files */ 1.1097 + for (cur = burp; ret && cur; cur = cur->next) { 1.1098 + /* if non-empty, burp it */ 1.1099 + if (cur->set.last) ret = mix_burp (stream,cur,&reclaimed); 1.1100 + /* empty, delete it unless new msg file */ 1.1101 + else if (mix_file_data (LOCAL->buf,stream->mailbox,cur->fileno) && 1.1102 + ((cur->fileno == LOCAL->newmsg) ? 1.1103 + truncate (LOCAL->buf,0) : unlink (LOCAL->buf))) { 1.1104 + sprintf (LOCAL->buf, 1.1105 + "Can't delete empty message file %.80s: %.80s", 1.1106 + cur->name,strerror (errno)); 1.1107 + MM_LOG (LOCAL->buf,WARN); 1.1108 + } 1.1109 + } 1.1110 + } 1.1111 + else MM_LOG ("No mix message files found during expunge",WARN); 1.1112 + /* free directory list */ 1.1113 + if (a = (void *) names) fs_give ((void **) &a); 1.1114 + } 1.1115 + 1.1116 + /* either way, re-acquire shared lock */ 1.1117 + if (flock (LOCAL->mfd,LOCK_SH|LOCK_NB)) 1.1118 + fatal ("Unable to re-acquire metadata shared lock!"); 1.1119 + /* Do this step even if ret is NIL (meaning some burp problem)! */ 1.1120 + if (nexp || reclaimed) { /* rewrite index and status if changed */ 1.1121 + LOCAL->indexseq = mix_modseq (LOCAL->indexseq); 1.1122 + if (mix_index_update (stream,idxf,NIL)) { 1.1123 + LOCAL->statusseq = mix_modseq (LOCAL->statusseq); 1.1124 + /* set failure if update fails */ 1.1125 + ret = mix_status_update (stream,statf,NIL); 1.1126 + } 1.1127 + } 1.1128 + } 1.1129 + if (statf) fclose (statf); /* close status if still open */ 1.1130 + if (idxf) fclose (idxf); /* close index if still open */ 1.1131 + LOCAL->expok = NIL; /* cancel expok */ 1.1132 + if (ret) { /* only if success */ 1.1133 + char *s = NIL; 1.1134 + if (nexp) sprintf (s = LOCAL->buf,"Expunged %lu messages",nexp); 1.1135 + else if (reclaimed) 1.1136 + sprintf (s=LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed); 1.1137 + else if (!burponly) 1.1138 + s = stream->rdonly ? "Expunge ignored on readonly mailbox" : 1.1139 + "No messages deleted, so no update needed"; 1.1140 + if (s) MM_LOG (s,(long) NIL); 1.1141 + } 1.1142 + return ret; 1.1143 +} 1.1144 + 1.1145 +/* MIX test for message file name 1.1146 + * Accepts: candidate directory name 1.1147 + * Returns: T if message file name, NIL otherwise 1.1148 + * 1.1149 + * ".mix" with no suffix was used by experimental versions 1.1150 + */ 1.1151 + 1.1152 +int mix_select (struct direct *name) 1.1153 +{ 1.1154 + char c,*s; 1.1155 + /* make sure name has prefix */ 1.1156 + if (mix_dirfmttest (name->d_name)) { 1.1157 + for (c = *(s = name->d_name + sizeof (MIXNAME) - 1); c && isxdigit (c); 1.1158 + c = *s++); 1.1159 + if (!c) return T; /* all-hex or no suffix */ 1.1160 + } 1.1161 + return NIL; /* not suffix or non-hex */ 1.1162 +} 1.1163 + 1.1164 + 1.1165 +/* MIX msg file name comparision 1.1166 + * Accepts: first candidate directory entry 1.1167 + * second candidate directory entry 1.1168 + * Returns: -1 if d1 < d2, 0 if d1 == d2, 1 d1 > d2 1.1169 + */ 1.1170 + 1.1171 +int mix_msgfsort (const void *d1,const void *d2) 1.1172 +{ 1.1173 + char *n1 = (*(struct direct **) d1)->d_name + sizeof (MIXNAME) - 1; 1.1174 + char *n2 = (*(struct direct **) d2)->d_name + sizeof (MIXNAME) - 1; 1.1175 + return compare_ulong (*n1 ? strtoul (n1,NIL,16) : 0, 1.1176 + *n2 ? strtoul (n2,NIL,16) : 0); 1.1177 +} 1.1178 + 1.1179 + 1.1180 +/* MIX add a range to a set 1.1181 + * Accepts: pointer to set to add 1.1182 + * start of set 1.1183 + * size of set 1.1184 + * Returns: T if success, set updated, NIL otherwise 1.1185 + */ 1.1186 + 1.1187 +long mix_addset (SEARCHSET **set,unsigned long start,unsigned long size) 1.1188 +{ 1.1189 + SEARCHSET *s = *set; 1.1190 + if (start < s->last) { /* sanity check */ 1.1191 + char tmp[MAILTMPLEN]; 1.1192 + sprintf (tmp,"Backwards-running mix index %lu < %lu",start,s->last); 1.1193 + MM_LOG (tmp,ERROR); 1.1194 + return NIL; 1.1195 + } 1.1196 + /* range initially empty? */ 1.1197 + if (!s->last) s->first = start; 1.1198 + else if (start > s->last) /* no, start new range if can't append */ 1.1199 + (*set = s = s->next = mail_newsearchset ())->first = start; 1.1200 + s->last = start + size; /* end of current range */ 1.1201 + return LONGT; 1.1202 +} 1.1203 + 1.1204 +/* MIX burp message file 1.1205 + * Accepts: MAIL stream 1.1206 + * current burp block for this message 1.1207 + * Returns: T if successful, NIL if failed 1.1208 + */ 1.1209 + 1.1210 +static char *staterr = "Error in stat of mix message file %.80s: %.80s"; 1.1211 +static char *truncerr = "Error truncating mix message file %.80s: %.80s"; 1.1212 + 1.1213 +long mix_burp (MAILSTREAM *stream,MIXBURP *burp,unsigned long *reclaimed) 1.1214 +{ 1.1215 + MESSAGECACHE *elt; 1.1216 + SEARCHSET *set; 1.1217 + struct stat sbuf; 1.1218 + off_t rpos,wpos; 1.1219 + size_t size,wsize,wpending,written; 1.1220 + int fd; 1.1221 + FILE *f; 1.1222 + void *s; 1.1223 + unsigned long i; 1.1224 + long ret = NIL; 1.1225 + /* build file name */ 1.1226 + mix_file_data (LOCAL->buf,stream->mailbox,burp->fileno); 1.1227 + /* need to burp at start or multiple ranges? */ 1.1228 + if (!burp->set.first && !burp->set.next) { 1.1229 + /* easy case, single range at start of file */ 1.1230 + if (stat (LOCAL->buf,&sbuf)) { 1.1231 + sprintf (LOCAL->buf,staterr,burp->name,strerror (errno)); 1.1232 + MM_LOG (LOCAL->buf,ERROR); 1.1233 + } 1.1234 + /* is this range sane? */ 1.1235 + else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) { 1.1236 + /* if matches range then no burp needed! */ 1.1237 + if (burp->set.last == sbuf.st_size) ret = LONGT; 1.1238 + /* just need to remove cruft at end */ 1.1239 + else if (ret = !truncate (LOCAL->buf,burp->set.last)) 1.1240 + *reclaimed += sbuf.st_size - burp->set.last; 1.1241 + else { 1.1242 + sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno)); 1.1243 + MM_LOG (LOCAL->buf,ERROR); 1.1244 + } 1.1245 + } 1.1246 + } 1.1247 + /* have to do more work, get the file */ 1.1248 + else if (((fd = open (LOCAL->buf,O_RDWR,NIL)) < 0) || 1.1249 + !(f = fdopen (fd,"r+b"))) { 1.1250 + sprintf (LOCAL->buf,"Error opening mix message file %.80s: %.80s", 1.1251 + burp->name,strerror (errno)); 1.1252 + MM_LOG (LOCAL->buf,ERROR); 1.1253 + if (fd >= 0) close (fd); /* in case fdopen() failure */ 1.1254 + } 1.1255 + else if (fstat (fd,&sbuf)) { /* get file size */ 1.1256 + sprintf (LOCAL->buf,staterr,burp->name,strerror (errno)); 1.1257 + MM_LOG (LOCAL->buf,ERROR); 1.1258 + fclose (f); 1.1259 + } 1.1260 + 1.1261 + /* only if sane */ 1.1262 + else if (mix_burp_check (&burp->set,sbuf.st_size,LOCAL->buf)) { 1.1263 + /* make sure each range starts with token */ 1.1264 + for (set = &burp->set; set; set = set->next) 1.1265 + if (fseek (f,set->first,SEEK_SET) || 1.1266 + (fread (LOCAL->buf,1,MSGTSZ,f) != MSGTSZ) || 1.1267 + strncmp (LOCAL->buf,MSGTOK,MSGTSZ)) { 1.1268 + sprintf (LOCAL->buf,"Bad message token in mix message file at %lu", 1.1269 + set->first); 1.1270 + MM_LOG (LOCAL->buf,ERROR); 1.1271 + fclose (f); 1.1272 + return NIL; /* burp fails for this file */ 1.1273 + } 1.1274 + /* burp out each old message */ 1.1275 + for (set = &burp->set, wpos = 0; set; set = set->next) { 1.1276 + /* move down this range */ 1.1277 + for (rpos = set->first, size = set->last - set->first; 1.1278 + size; size -= wsize) { 1.1279 + if (rpos != wpos) { /* data to skip at start? */ 1.1280 + /* no, slide this buffer down */ 1.1281 + wsize = min (size,LOCAL->buflen); 1.1282 + /* failure is not an option here */ 1.1283 + while (fseek (f,rpos,SEEK_SET) || 1.1284 + (fread (LOCAL->buf,1,wsize,f) != wsize)) { 1.1285 + MM_NOTIFY (stream,strerror (errno),WARN); 1.1286 + MM_DISKERROR (stream,errno,T); 1.1287 + } 1.1288 + /* nor here */ 1.1289 + while (fseek (f,wpos,SEEK_SET)) { 1.1290 + MM_NOTIFY (stream,strerror (errno),WARN); 1.1291 + MM_DISKERROR (stream,errno,T); 1.1292 + } 1.1293 + /* and especially not here */ 1.1294 + for (s = LOCAL->buf, wpending = wsize; wpending; wpending -= written) 1.1295 + if (!(written = fwrite (LOCAL->buf,1,wpending,f))) { 1.1296 + MM_NOTIFY (stream,strerror (errno),WARN); 1.1297 + MM_DISKERROR (stream,errno,T); 1.1298 + } 1.1299 + } 1.1300 + else wsize = size; /* nothing to skip, say we wrote it all */ 1.1301 + rpos += wsize; wpos += wsize; 1.1302 + } 1.1303 + } 1.1304 + 1.1305 + while (fflush (f)) { /* failure also not an option here... */ 1.1306 + MM_NOTIFY (stream,strerror (errno),WARN); 1.1307 + MM_DISKERROR (stream,errno,T); 1.1308 + } 1.1309 + if (ftruncate (fd,wpos)) { /* flush cruft at end of file */ 1.1310 + sprintf (LOCAL->buf,truncerr,burp->name,strerror (errno)); 1.1311 + MM_LOG (LOCAL->buf,WARN); 1.1312 + } 1.1313 + else *reclaimed += rpos - wpos; 1.1314 + ret = !fclose (f); /* close file */ 1.1315 + /* slide down message positions in index */ 1.1316 + for (i = 1,rpos = 0; i <= stream->nmsgs; ++i) 1.1317 + if ((elt = mail_elt (stream,i))->private.spare.data == burp->fileno) { 1.1318 + elt->private.special.offset = rpos; 1.1319 + rpos += elt->private.msg.header.offset + elt->rfc822_size; 1.1320 + } 1.1321 + /* debugging */ 1.1322 + if (rpos != wpos) fatal ("burp size consistency check!"); 1.1323 + } 1.1324 + return ret; 1.1325 +} 1.1326 + 1.1327 + 1.1328 +/* MIX burp sanity check to make sure not burping off end of file 1.1329 + * Accepts: burp set 1.1330 + * file size 1.1331 + * file name 1.1332 + * Returns: T if sane, NIL if insane 1.1333 + */ 1.1334 + 1.1335 +long mix_burp_check (SEARCHSET *set,size_t size,char *file) 1.1336 +{ 1.1337 + do if (set->last > size) { /* sanity check */ 1.1338 + char tmp[MAILTMPLEN]; 1.1339 + sprintf (tmp,"Unexpected short mix message file %.80s %lu < %lu", 1.1340 + file,size,set->last); 1.1341 + MM_LOG (tmp,ERROR); 1.1342 + return NIL; /* don't burp this file at all */ 1.1343 + } while (set = set->next); 1.1344 + return LONGT; 1.1345 +} 1.1346 + 1.1347 +/* MIX mail copy message(s) 1.1348 + * Accepts: MAIL stream 1.1349 + * sequence 1.1350 + * destination mailbox 1.1351 + * copy options 1.1352 + * Returns: T if copy successful, else NIL 1.1353 + */ 1.1354 + 1.1355 +long mix_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) 1.1356 +{ 1.1357 + FDDATA d; 1.1358 + STRING st; 1.1359 + char tmp[2*MAILTMPLEN]; 1.1360 + long ret = mix_isvalid (mailbox,LOCAL->buf); 1.1361 + mailproxycopy_t pc = 1.1362 + (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL); 1.1363 + MAILSTREAM *astream = NIL; 1.1364 + FILE *idxf = NIL; 1.1365 + FILE *msgf = NIL; 1.1366 + FILE *statf = NIL; 1.1367 + if (!ret) switch (errno) { /* make sure valid mailbox */ 1.1368 + case NIL: /* no error in stat() */ 1.1369 + if (pc) return (*pc) (stream,sequence,mailbox,options); 1.1370 + sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox); 1.1371 + MM_LOG (tmp,ERROR); 1.1372 + break; 1.1373 + default: /* some stat() error */ 1.1374 + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before copy",NIL); 1.1375 + break; 1.1376 + } 1.1377 + /* get sequence to copy */ 1.1378 + else if (!(ret = ((options & CP_UID) ? mail_uid_sequence (stream,sequence) : 1.1379 + mail_sequence (stream,sequence)))); 1.1380 + /* acquire stream to append */ 1.1381 + else if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) && 1.1382 + !astream->rdonly && 1.1383 + (((MIXLOCAL *) astream->local)->expok = T) && 1.1384 + (statf = mix_parse (astream,&idxf,LONGT,NIL))) ? 1.1385 + LONGT : NIL) { 1.1386 + int fd; 1.1387 + unsigned long i; 1.1388 + MESSAGECACHE *elt; 1.1389 + unsigned long newsize,hdrsize,size; 1.1390 + MIXLOCAL *local = (MIXLOCAL *) astream->local; 1.1391 + unsigned long seq = mix_modseq (local->metaseq); 1.1392 + /* make sure new modseq fits */ 1.1393 + if (local->indexseq > seq) seq = local->indexseq + 1; 1.1394 + if (local->statusseq > seq) seq = local->statusseq + 1; 1.1395 + /* calculate size of per-message header */ 1.1396 + sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0); 1.1397 + hdrsize = strlen (local->buf); 1.1398 + 1.1399 + MM_CRITICAL (stream); /* go critical */ 1.1400 + astream->silent = T; /* no events here */ 1.1401 + /* calculate size that will be added */ 1.1402 + for (i = 1, newsize = 0; i <= stream->nmsgs; ++i) 1.1403 + if ((elt = mail_elt (stream,i))->sequence) 1.1404 + newsize += hdrsize + elt->rfc822_size; 1.1405 + /* open data file */ 1.1406 + if (msgf = mix_data_open (astream,&fd,&size,newsize)) { 1.1407 + char *t; 1.1408 + unsigned long j,uid,uidv; 1.1409 + copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL); 1.1410 + SEARCHSET *source = cu ? mail_newsearchset () : NIL; 1.1411 + SEARCHSET *dest = cu ? mail_newsearchset () : NIL; 1.1412 + for (i = 1,uid = uidv = 0; ret && (i <= stream->nmsgs); ++i) 1.1413 + if (((elt = mail_elt (stream,i))->sequence) && elt->rfc822_size) { 1.1414 + /* is message in current message file? */ 1.1415 + if ((LOCAL->msgfd < 0) || 1.1416 + (elt->private.spare.data != LOCAL->curmsg)) { 1.1417 + if (LOCAL->msgfd >= 0) close (LOCAL->msgfd); 1.1418 + if ((LOCAL->msgfd = open (mix_file_data (LOCAL->buf, 1.1419 + stream->mailbox, 1.1420 + elt->private.spare.data), 1.1421 + O_RDONLY,NIL)) >= 0) 1.1422 + LOCAL->curmsg = elt->private.spare.data; 1.1423 + } 1.1424 + if (LOCAL->msgfd < 0) ret = NIL; 1.1425 + else { /* got file */ 1.1426 + d.fd = LOCAL->msgfd;/* set up file descriptor */ 1.1427 + /* start of message */ 1.1428 + d.pos = elt->private.special.offset + 1.1429 + elt->private.msg.header.offset; 1.1430 + d.chunk = LOCAL->buf; 1.1431 + d.chunksize = CHUNKSIZE; 1.1432 + INIT (&st,fd_string,&d,elt->rfc822_size); 1.1433 + /* init flag string */ 1.1434 + tmp[0] = tmp[1] = '\0'; 1.1435 + if (j = elt->user_flags) do 1.1436 + if ((t = stream->user_flags[find_rightmost_bit (&j)]) && *t) 1.1437 + strcat (strcat (tmp," "),t); 1.1438 + while (j); 1.1439 + if (elt->seen) strcat (tmp," \\Seen"); 1.1440 + if (elt->deleted) strcat (tmp," \\Deleted"); 1.1441 + if (elt->flagged) strcat (tmp," \\Flagged"); 1.1442 + if (elt->answered) strcat (tmp," \\Answered"); 1.1443 + if (elt->draft) strcat (tmp," \\Draft"); 1.1444 + tmp[0] = '('; /* wrap list */ 1.1445 + strcat (tmp,")"); 1.1446 + /* if append OK, add to source set */ 1.1447 + if ((ret = mix_append_msg (astream,msgf,tmp,elt,&st,dest, 1.1448 + seq)) && source) 1.1449 + mail_append_set (source,mail_uid (stream,i)); 1.1450 + } 1.1451 + } 1.1452 + 1.1453 + /* finish write if success */ 1.1454 + if (ret && (ret = !fflush (msgf))) { 1.1455 + fclose (msgf); /* all good, close the msg file now */ 1.1456 + /* write new metadata, index, and status */ 1.1457 + local->metaseq = local->indexseq = local->statusseq = seq; 1.1458 + if (ret = (mix_meta_update (astream) && 1.1459 + mix_index_update (astream,idxf,LONGT))) { 1.1460 + /* success, delete if doing a move */ 1.1461 + if (options & CP_MOVE) 1.1462 + for (i = 1; i <= stream->nmsgs; i++) 1.1463 + if ((elt = mail_elt (stream,i))->sequence) { 1.1464 + elt->deleted = T; 1.1465 + if (!stream->rdonly) elt->private.mod = LOCAL->statusseq = seq; 1.1466 + MM_FLAGS (stream,elt->msgno); 1.1467 + } 1.1468 + /* done with status file now */ 1.1469 + mix_status_update (astream,statf,LONGT); 1.1470 + /* return sets if doing COPYUID */ 1.1471 + if (cu) (*cu) (stream,mailbox,astream->uid_validity,source,dest); 1.1472 + source = dest = NIL; /* don't free these sets now */ 1.1473 + } 1.1474 + } 1.1475 + else { /* error */ 1.1476 + if (errno) { /* output error message if system call error */ 1.1477 + sprintf (tmp,"Message copy failed: %.80s",strerror (errno)); 1.1478 + MM_LOG (tmp,ERROR); 1.1479 + } 1.1480 + ftruncate (fd,size); /* revert file */ 1.1481 + close (fd); /* make sure that fclose doesn't corrupt us */ 1.1482 + fclose (msgf); /* free the stdio resources */ 1.1483 + } 1.1484 + /* flush any sets remaining */ 1.1485 + mail_free_searchset (&source); 1.1486 + mail_free_searchset (&dest); 1.1487 + } 1.1488 + else { /* message file open failed */ 1.1489 + sprintf (tmp,"Error opening copy message file: %.80s", 1.1490 + strerror (errno)); 1.1491 + MM_LOG (tmp,ERROR); 1.1492 + ret = NIL; 1.1493 + } 1.1494 + MM_NOCRITICAL (stream); 1.1495 + } 1.1496 + else MM_LOG ("Can't open copy mailbox",ERROR); 1.1497 + if (statf) fclose (statf); /* close status if still open */ 1.1498 + if (idxf) fclose (idxf); /* close index if still open */ 1.1499 + /* finished with append stream */ 1.1500 + if (astream) mail_close (astream); 1.1501 + return ret; /* return state */ 1.1502 +} 1.1503 + 1.1504 +/* MIX mail append message from stringstruct 1.1505 + * Accepts: MAIL stream 1.1506 + * destination mailbox 1.1507 + * append callback 1.1508 + * data for callback 1.1509 + * Returns: T if append successful, else NIL 1.1510 + */ 1.1511 + 1.1512 +long mix_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) 1.1513 +{ 1.1514 + STRING *message; 1.1515 + char *flags,*date,tmp[MAILTMPLEN]; 1.1516 + /* N.B.: can't use LOCAL->buf for tmp */ 1.1517 + long ret = mix_isvalid (mailbox,tmp); 1.1518 + /* default stream to prototype */ 1.1519 + if (!stream) stream = user_flags (&mixproto); 1.1520 + if (!ret) switch (errno) { /* if not valid mailbox */ 1.1521 + case ENOENT: /* no such file? */ 1.1522 + if (ret = compare_cstring (mailbox,"INBOX") ? 1.1523 + NIL : mix_create (NIL,"INBOX")) 1.1524 + break; 1.1525 + MM_NOTIFY (stream,"[TRYCREATE] Must create mailbox before append",NIL); 1.1526 + break; 1.1527 + default: 1.1528 + sprintf (tmp,"Not a MIX-format mailbox: %.80s",mailbox); 1.1529 + MM_LOG (tmp,ERROR); 1.1530 + break; 1.1531 + } 1.1532 + 1.1533 + /* get first message */ 1.1534 + if (ret && MM_APPEND (af) (stream,data,&flags,&date,&message)) { 1.1535 + MAILSTREAM *astream; 1.1536 + FILE *idxf = NIL; 1.1537 + FILE *msgf = NIL; 1.1538 + FILE *statf = NIL; 1.1539 + if (ret = ((astream = mail_open (NIL,mailbox,OP_SILENT)) && 1.1540 + !astream->rdonly && 1.1541 + (((MIXLOCAL *) astream->local)->expok = T) && 1.1542 + (statf = mix_parse (astream,&idxf,LONGT,NIL))) ? 1.1543 + LONGT : NIL) { 1.1544 + int fd; 1.1545 + unsigned long size,hdrsize; 1.1546 + MESSAGECACHE elt; 1.1547 + MIXLOCAL *local = (MIXLOCAL *) astream->local; 1.1548 + unsigned long seq = mix_modseq (local->metaseq); 1.1549 + /* make sure new modseq fits */ 1.1550 + if (local->indexseq > seq) seq = local->indexseq + 1; 1.1551 + if (local->statusseq > seq) seq = local->statusseq + 1; 1.1552 + /* calculate size of per-message header */ 1.1553 + sprintf (local->buf,MSRFMT,MSGTOK,0,0,0,0,0,0,0,'+',0,0,0); 1.1554 + hdrsize = strlen (local->buf); 1.1555 + MM_CRITICAL (astream); /* go critical */ 1.1556 + astream->silent = T; /* no events here */ 1.1557 + /* open data file */ 1.1558 + if (msgf = mix_data_open (astream,&fd,&size,hdrsize + SIZE (message))) { 1.1559 + appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL); 1.1560 + SEARCHSET *dst = au ? mail_newsearchset () : NIL; 1.1561 + while (ret && message) {/* while good to go and have messages */ 1.1562 + errno = NIL; /* in case one of these causes failure */ 1.1563 + /* guard against zero-length */ 1.1564 + if (!(ret = SIZE (message))) 1.1565 + MM_LOG ("Append of zero-length message",ERROR); 1.1566 + else if (date && !(ret = mail_parse_date (&elt,date))) { 1.1567 + sprintf (tmp,"Bad date in append: %.80s",date); 1.1568 + MM_LOG (tmp,ERROR); 1.1569 + } 1.1570 + else { 1.1571 + if (!date) { /* if date not specified, use now */ 1.1572 + internal_date (tmp); 1.1573 + mail_parse_date (&elt,tmp); 1.1574 + } 1.1575 + ret = mix_append_msg (astream,msgf,flags,&elt,message,dst,seq) && 1.1576 + MM_APPEND (af) (stream,data,&flags,&date,&message); 1.1577 + } 1.1578 + } 1.1579 + 1.1580 + /* finish write if success */ 1.1581 + if (ret && (ret = !fflush (msgf))) { 1.1582 + fclose (msgf); /* all good, close the msg file now */ 1.1583 + /* write new metadata, index, and status */ 1.1584 + local->metaseq = local->indexseq = local->statusseq = seq; 1.1585 + if ((ret = (mix_meta_update (astream) && 1.1586 + mix_index_update (astream,idxf,LONGT) && 1.1587 + mix_status_update (astream,statf,LONGT))) && au) { 1.1588 + (*au) (mailbox,astream->uid_validity,dst); 1.1589 + dst = NIL; /* don't free this set now */ 1.1590 + } 1.1591 + } 1.1592 + else { /* failure */ 1.1593 + if (errno) { /* output error message if system call error */ 1.1594 + sprintf (tmp,"Message append failed: %.80s",strerror (errno)); 1.1595 + MM_LOG (tmp,ERROR); 1.1596 + } 1.1597 + ftruncate (fd,size); /* revert all writes to file*/ 1.1598 + close (fd); /* make sure that fclose doesn't corrupt us */ 1.1599 + fclose (msgf); /* free the stdio resources */ 1.1600 + } 1.1601 + /* flush any set remaining */ 1.1602 + mail_free_searchset (&dst); 1.1603 + } 1.1604 + else { /* message file open failed */ 1.1605 + sprintf (tmp,"Error opening append message file: %.80s", 1.1606 + strerror (errno)); 1.1607 + MM_LOG (tmp,ERROR); 1.1608 + ret = NIL; 1.1609 + } 1.1610 + MM_NOCRITICAL (astream); /* release critical */ 1.1611 + } 1.1612 + else MM_LOG ("Can't open append mailbox",ERROR); 1.1613 + if (statf) fclose (statf); /* close status if still open */ 1.1614 + if (idxf) fclose (idxf); /* close index if still open */ 1.1615 + if (astream) mail_close (astream); 1.1616 + } 1.1617 + return ret; 1.1618 +} 1.1619 + 1.1620 +/* MIX mail append single message 1.1621 + * Accepts: MAIL stream 1.1622 + * flags for new message if non-NIL 1.1623 + * elt with source date if non-NIL 1.1624 + * stringstruct of message text 1.1625 + * searchset to place UID 1.1626 + * modseq of message 1.1627 + * Returns: T if success, NIL if failure 1.1628 + */ 1.1629 + 1.1630 +long mix_append_msg (MAILSTREAM *stream,FILE *f,char *flags,MESSAGECACHE *delt, 1.1631 + STRING *msg,SEARCHSET *set,unsigned long seq) 1.1632 +{ 1.1633 + MESSAGECACHE *elt; 1.1634 + int c,cs; 1.1635 + unsigned long i,j,k,uf,hoff; 1.1636 + long sf; 1.1637 + stream->kwd_create = NIL; /* don't copy unknown keywords */ 1.1638 + sf = mail_parse_flags (stream,flags,&uf); 1.1639 + /* swell the cache */ 1.1640 + mail_exists (stream,++stream->nmsgs); 1.1641 + /* assign new UID from metadata */ 1.1642 + (elt = mail_elt (stream,stream->nmsgs))->private.uid = ++stream->uid_last; 1.1643 + elt->private.mod = seq; /* set requested modseq in status */ 1.1644 + elt->rfc822_size = SIZE (msg);/* copy message size and date to index */ 1.1645 + elt->year = delt->year; elt->month = delt->month; elt->day = delt->day; 1.1646 + elt->hours = delt->hours; elt->minutes = delt->minutes; 1.1647 + elt->seconds = delt->seconds; elt->zoccident = delt->zoccident; 1.1648 + elt->zhours = delt->zhours; elt->zminutes = delt->zminutes; 1.1649 + /* 1.1650 + * Do NOT set elt->valid here! mix_status_update() uses it to determine 1.1651 + * whether a message should be marked as old. 1.1652 + */ 1.1653 + if (sf&fSEEN) elt->seen = T; /* copy flags to status */ 1.1654 + if (sf&fDELETED) elt->deleted = T; 1.1655 + if (sf&fFLAGGED) elt->flagged = T; 1.1656 + if (sf&fANSWERED) elt->answered = T; 1.1657 + if (sf&fDRAFT) elt->draft = T; 1.1658 + elt->user_flags |= uf; 1.1659 + /* message is in new message file */ 1.1660 + elt->private.spare.data = LOCAL->newmsg; 1.1661 + 1.1662 + /* offset to message internal header */ 1.1663 + elt->private.special.offset = ftell (f); 1.1664 + /* build header for message */ 1.1665 + fprintf (f,MSRFMT,MSGTOK,elt->private.uid, 1.1666 + elt->year + BASEYEAR,elt->month,elt->day, 1.1667 + elt->hours,elt->minutes,elt->seconds, 1.1668 + elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes, 1.1669 + elt->rfc822_size); 1.1670 + /* offset to header from internal header */ 1.1671 + elt->private.msg.header.offset = ftell (f) - elt->private.special.offset; 1.1672 + for (cs = 0; SIZE (msg); ) { /* copy message */ 1.1673 + if (elt->private.msg.header.text.size) { 1.1674 + if (msg->cursize) /* blat entire chunk if have it */ 1.1675 + for (j = msg->cursize; j; j -= k) 1.1676 + if (!(k = fwrite (msg->curpos,1,j,f))) return NIL; 1.1677 + SETPOS (msg,GETPOS (msg) + msg->cursize); 1.1678 + } 1.1679 + else { /* still searching for delimiter */ 1.1680 + c = 0xff & SNX (msg); /* get source character */ 1.1681 + if (putc (c,f) == EOF) return NIL; 1.1682 + switch (cs) { /* decide what to do based on state */ 1.1683 + case 0: /* previous char ordinary */ 1.1684 + if (c == '\015') cs = 1;/* advance if CR */ 1.1685 + break; 1.1686 + case 1: /* previous CR, advance if LF */ 1.1687 + cs = (c == '\012') ? 2 : 0; 1.1688 + break; 1.1689 + case 2: /* previous CRLF, advance if CR */ 1.1690 + cs = (c == '\015') ? 3 : 0; 1.1691 + break; 1.1692 + case 3: /* previous CRLFCR, done if LF */ 1.1693 + if (c == '\012') elt->private.msg.header.text.size = 1.1694 + elt->rfc822_size - SIZE (msg); 1.1695 + cs = 0; /* reset mechanism */ 1.1696 + break; 1.1697 + } 1.1698 + } 1.1699 + } 1.1700 + /* if no delimiter, header is entire msg */ 1.1701 + if (!elt->private.msg.header.text.size) 1.1702 + elt->private.msg.header.text.size = elt->rfc822_size; 1.1703 + /* add this message to set */ 1.1704 + mail_append_set (set,elt->private.uid); 1.1705 + return LONGT; /* success */ 1.1706 +} 1.1707 + 1.1708 +/* MIX mail read metadata, index, and status 1.1709 + * Accepts: MAIL stream 1.1710 + * returned index file 1.1711 + * index file flags (non-NIL if want to add/remove messages) 1.1712 + * status file flags (non-NIL if want to update elt->valid and old) 1.1713 + * Returns: open status file, or NIL if failure 1.1714 + * 1.1715 + * Note that this routine can return an open index file even if it fails! 1.1716 + */ 1.1717 + 1.1718 +static char *shortmsg = 1.1719 + "message %lu (UID=%.08lx) truncated by %lu byte(s) (%lu < %lu)"; 1.1720 + 1.1721 +FILE *mix_parse (MAILSTREAM *stream,FILE **idxf,long iflags,long sflags) 1.1722 +{ 1.1723 + int fd; 1.1724 + unsigned long i; 1.1725 + char *s,*t; 1.1726 + struct stat sbuf; 1.1727 + FILE *statf = NIL; 1.1728 + short metarepairneeded = 0; 1.1729 + short indexrepairneeded = 0; 1.1730 + short silent = stream->silent; 1.1731 + *idxf = NIL; /* in case error */ 1.1732 + /* readonly means no updates */ 1.1733 + if (stream->rdonly) iflags = sflags = NIL; 1.1734 + /* open index file */ 1.1735 + if ((fd = open (LOCAL->index,iflags ? O_RDWR : O_RDONLY,NIL)) < 0) 1.1736 + MM_LOG ("Error opening mix index file",ERROR); 1.1737 + /* acquire exclusive access and FILE */ 1.1738 + else if (!flock (fd,iflags ? LOCK_EX : LOCK_SH) && 1.1739 + !(*idxf = fdopen (fd,iflags ? "r+b" : "rb"))) { 1.1740 + MM_LOG ("Error obtaining stream on mix index file",ERROR); 1.1741 + flock (fd,LOCK_UN); /* relinquish lock */ 1.1742 + close (fd); 1.1743 + } 1.1744 + 1.1745 + /* slurp metadata */ 1.1746 + else if (s = mix_meta_slurp (stream,&i)) { 1.1747 + unsigned long j = 0; /* non-zero if UIDVALIDITY/UIDLAST changed */ 1.1748 + if (i != LOCAL->metaseq) { /* metadata changed? */ 1.1749 + char *t,*k; 1.1750 + LOCAL->metaseq = i; /* note new metadata sequence */ 1.1751 + while (s && *s) { /* parse entire metadata file */ 1.1752 + /* locate end of line */ 1.1753 + if (s = strstr (t = s,"\015\012")) { 1.1754 + *s = '\0'; /* tie off line */ 1.1755 + s += 2; /* skip past CRLF */ 1.1756 + switch (*t++) { /* parse line */ 1.1757 + case 'V': /* UIDVALIDITY */ 1.1758 + if (!isxdigit (*t) || !(i = strtoul (t,&t,16))) { 1.1759 + MM_LOG ("Error in mix metadata file UIDVALIDITY record",ERROR); 1.1760 + return NIL; /* give up */ 1.1761 + } 1.1762 + if (i != stream->uid_validity) j = stream->uid_validity = i; 1.1763 + break; 1.1764 + case 'L': /* new UIDLAST */ 1.1765 + if (!isxdigit (*t)) { 1.1766 + MM_LOG ("Error in mix metadata file UIDLAST record",ERROR); 1.1767 + return NIL; /* give up */ 1.1768 + } 1.1769 + if ((i = strtoul (t,&t,16)) != stream->uid_last) 1.1770 + j = stream->uid_last = i; 1.1771 + break; 1.1772 + case 'N': /* new message file */ 1.1773 + if (!isxdigit (*t)) { 1.1774 + MM_LOG ("Error in mix metadata file new msg record",ERROR); 1.1775 + return NIL; /* give up */ 1.1776 + } 1.1777 + if ((i = strtoul (t,&t,16)) != stream->uid_last) 1.1778 + LOCAL->newmsg = i; 1.1779 + break; 1.1780 + case 'K': /* new keyword list */ 1.1781 + for (i = 0; t && *t && (i < NUSERFLAGS); ++i) { 1.1782 + if (t = strchr (k = t,' ')) *t++ = '\0'; 1.1783 + /* make sure keyword non-empty */ 1.1784 + if (*k && (strlen (k) <= MAXUSERFLAG)) { 1.1785 + /* in case value changes (shouldn't happen) */ 1.1786 + if (stream->user_flags[i] && strcmp (stream->user_flags[i],k)){ 1.1787 + char tmp[MAILTMPLEN]; 1.1788 + sprintf (tmp,"flag rename old=%.80s new=%.80s", 1.1789 + stream->user_flags[i],k); 1.1790 + MM_LOG (tmp,WARN); 1.1791 + fs_give ((void **) &stream->user_flags[i]); 1.1792 + } 1.1793 + if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (k); 1.1794 + } 1.1795 + else break; /* empty keyword */ 1.1796 + } 1.1797 + if ((i < NUSERFLAGS) && stream->user_flags[i]) { 1.1798 + MM_LOG ("Error in mix metadata file keyword record",ERROR); 1.1799 + return NIL; /* give up */ 1.1800 + } 1.1801 + else if (i == NUSERFLAGS) stream->kwd_create = NIL; 1.1802 + break; 1.1803 + } 1.1804 + } 1.1805 + if (t && *t) { /* junk in line */ 1.1806 + MM_LOG ("Error in mix metadata record",ERROR); 1.1807 + return NIL; /* give up */ 1.1808 + } 1.1809 + } 1.1810 + } 1.1811 + 1.1812 + /* get sequence */ 1.1813 + if (!(i = mix_read_sequence (*idxf)) || (i < LOCAL->indexseq)) { 1.1814 + MM_LOG ("Error in mix index file sequence record",ERROR); 1.1815 + return NIL; /* give up */ 1.1816 + } 1.1817 + /* sequence changed from last time? */ 1.1818 + else if (j || (i > LOCAL->indexseq)) { 1.1819 + unsigned long uid,nmsgs,curfile,curfilesize,curpos; 1.1820 + char *t,*msg,tmp[MAILTMPLEN]; 1.1821 + /* start with no messages */ 1.1822 + curfile = curfilesize = curpos = nmsgs = 0; 1.1823 + /* update sequence iff expunging OK */ 1.1824 + if (LOCAL->expok) LOCAL->indexseq = i; 1.1825 + /* get first elt */ 1.1826 + while ((s = mix_read_record (*idxf,LOCAL->buf,LOCAL->buflen,"index")) && 1.1827 + *s) 1.1828 + switch (*s) { 1.1829 + case ':': /* message record */ 1.1830 + if (!(isxdigit (*++s) && (uid = strtoul (s,&t,16)))) msg = "UID"; 1.1831 + else if (!((*t++ == ':') && isdigit (*t) && isdigit (t[1]) && 1.1832 + isdigit (t[2]) && isdigit (t[3]) && isdigit (t[4]) && 1.1833 + isdigit (t[5]) && isdigit (t[6]) && isdigit (t[7]) && 1.1834 + isdigit (t[8]) && isdigit (t[9]) && isdigit (t[10]) && 1.1835 + isdigit (t[11]) && isdigit (t[12]) && isdigit (t[13]) && 1.1836 + ((t[14] == '+') || (t[14] == '-')) && 1.1837 + isdigit (t[15]) && isdigit (t[16]) && isdigit (t[17]) && 1.1838 + isdigit (t[18]))) msg = "internaldate"; 1.1839 + else if ((*(s = t+19) != ':') || !isxdigit (*++s)) msg = "size"; 1.1840 + else { 1.1841 + unsigned int y = (((*t - '0') * 1000) + ((t[1] - '0') * 100) + 1.1842 + ((t[2] - '0') * 10) + t[3] - '0') - BASEYEAR; 1.1843 + unsigned int m = ((t[4] - '0') * 10) + t[5] - '0'; 1.1844 + unsigned int d = ((t[6] - '0') * 10) + t[7] - '0'; 1.1845 + unsigned int hh = ((t[8] - '0') * 10) + t[9] - '0'; 1.1846 + unsigned int mm = ((t[10] - '0') * 10) + t[11] - '0'; 1.1847 + unsigned int ss = ((t[12] - '0') * 10) + t[13] - '0'; 1.1848 + unsigned int z = (t[14] == '-') ? 1 : 0; 1.1849 + unsigned int zh = ((t[15] - '0') * 10) + t[16] - '0'; 1.1850 + unsigned int zm = ((t[17] - '0') * 10) + t[18] - '0'; 1.1851 + unsigned long size = strtoul (s,&s,16); 1.1852 + if ((*s++ == ':') && isxdigit (*s)) { 1.1853 + unsigned long file = strtoul (s,&s,16); 1.1854 + if ((*s++ == ':') && isxdigit (*s)) { 1.1855 + unsigned long pos = strtoul (s,&s,16); 1.1856 + if ((*s++ == ':') && isxdigit (*s)) { 1.1857 + unsigned long hpos = strtoul (s,&s,16); 1.1858 + if ((*s++ == ':') && isxdigit (*s)) { 1.1859 + unsigned long hsiz = strtoul (s,&s,16); 1.1860 + if (uid > stream->uid_last) { 1.1861 + sprintf (tmp,"mix index invalid UID (%08lx < %08lx)", 1.1862 + uid,stream->uid_last); 1.1863 + if (stream->rdonly) { 1.1864 + MM_LOG (tmp,ERROR); 1.1865 + return NIL; 1.1866 + } 1.1867 + strcat (tmp,", repaired"); 1.1868 + MM_LOG (tmp,WARN); 1.1869 + stream->uid_last = uid; 1.1870 + metarepairneeded = T; 1.1871 + } 1.1872 + 1.1873 + /* ignore expansion values */ 1.1874 + if (*s++ == ':') { 1.1875 + MESSAGECACHE *elt; 1.1876 + ++nmsgs; /* this is another mesage */ 1.1877 + /* within current known range of messages? */ 1.1878 + while (nmsgs <= stream->nmsgs) { 1.1879 + /* yes, get corresponding elt */ 1.1880 + elt = mail_elt (stream,nmsgs); 1.1881 + /* existing message with matching data? */ 1.1882 + if (uid == elt->private.uid) { 1.1883 + /* beware of Dracula's resurrection */ 1.1884 + if (elt->private.ghost) { 1.1885 + sprintf (tmp,"mix index data unexpunged UID: %lx", 1.1886 + uid); 1.1887 + MM_LOG (tmp,ERROR); 1.1888 + return NIL; 1.1889 + } 1.1890 + /* also of static data changing */ 1.1891 + if ((size != elt->rfc822_size) || 1.1892 + (file != elt->private.spare.data) || 1.1893 + (pos != elt->private.special.offset) || 1.1894 + (hpos != elt->private.msg.header.offset) || 1.1895 + (hsiz != elt->private.msg.header.text.size) || 1.1896 + (y != elt->year) || (m != elt->month) || 1.1897 + (d != elt->day) || (hh != elt->hours) || 1.1898 + (mm != elt->minutes) || (ss != elt->seconds) || 1.1899 + (z != elt->zoccident) || (zh != elt->zhours) || 1.1900 + (zm != elt->zminutes)) { 1.1901 + sprintf (tmp,"mix index data mismatch: %lx",uid); 1.1902 + MM_LOG (tmp,ERROR); 1.1903 + return NIL; 1.1904 + } 1.1905 + break; 1.1906 + } 1.1907 + /* existing msg with lower UID is expunged */ 1.1908 + else if (uid > elt->private.uid) { 1.1909 + if (LOCAL->expok) mail_expunged (stream,nmsgs); 1.1910 + else {/* message expunged, but not yet for us */ 1.1911 + ++nmsgs; 1.1912 + elt->private.ghost = T; 1.1913 + } 1.1914 + } 1.1915 + else { /* unexpected message record */ 1.1916 + sprintf (tmp,"mix index UID mismatch (%lx < %lx)", 1.1917 + uid,elt->private.uid); 1.1918 + MM_LOG (tmp,ERROR); 1.1919 + return NIL; 1.1920 + } 1.1921 + } 1.1922 + 1.1923 + /* time to create a new message? */ 1.1924 + if (nmsgs > stream->nmsgs) { 1.1925 + /* defer announcing until later */ 1.1926 + stream->silent = T; 1.1927 + mail_exists (stream,nmsgs); 1.1928 + stream->silent = silent; 1.1929 + (elt = mail_elt (stream,nmsgs))->recent = T; 1.1930 + elt->private.uid = uid; elt->rfc822_size = size; 1.1931 + elt->private.spare.data = file; 1.1932 + elt->private.special.offset = pos; 1.1933 + elt->private.msg.header.offset = hpos; 1.1934 + elt->private.msg.header.text.size = hsiz; 1.1935 + elt->year = y; elt->month = m; elt->day = d; 1.1936 + elt->hours = hh; elt->minutes = mm; 1.1937 + elt->seconds = ss; elt->zoccident = z; 1.1938 + elt->zhours = zh; elt->zminutes = zm; 1.1939 + /* message in same file? */ 1.1940 + if (curfile == file) { 1.1941 + if (pos < curpos) { 1.1942 + MESSAGECACHE *plt = mail_elt (stream,elt->msgno-1); 1.1943 + /* uh-oh, calculate delta? */ 1.1944 + i = curpos - pos; 1.1945 + sprintf (tmp,shortmsg,plt->msgno,plt->private.uid, 1.1946 + i,pos,curpos); 1.1947 + /* possible to fix? */ 1.1948 + if (!stream->rdonly && LOCAL->expok && 1.1949 + (i < plt->rfc822_size)) { 1.1950 + plt->rfc822_size -= i; 1.1951 + if (plt->rfc822_size < 1.1952 + plt->private.msg.header.text.size) 1.1953 + plt->private.msg.header.text.size = 1.1954 + plt->rfc822_size; 1.1955 + strcat (tmp,", repaired"); 1.1956 + indexrepairneeded = T; 1.1957 + } 1.1958 + MM_LOG (tmp,WARN); 1.1959 + } 1.1960 + } 1.1961 + else { /* new file, restart */ 1.1962 + if (stat (mix_file_data (LOCAL->buf,stream->mailbox, 1.1963 + curfile = file),&sbuf)) { 1.1964 + sprintf (tmp,"Missing mix data file: %.500s", 1.1965 + LOCAL->buf); 1.1966 + MM_LOG (tmp,ERROR); 1.1967 + return NIL; 1.1968 + } 1.1969 + curfile = file; 1.1970 + curfilesize = sbuf.st_size; 1.1971 + } 1.1972 + 1.1973 + /* position of message in file */ 1.1974 + curpos = pos + elt->private.msg.header.offset + 1.1975 + elt->rfc822_size; 1.1976 + /* short file? */ 1.1977 + if (curfilesize < curpos) { 1.1978 + /* uh-oh, calculate delta? */ 1.1979 + i = curpos - curfilesize; 1.1980 + sprintf (tmp,shortmsg,elt->msgno,elt->private.uid, 1.1981 + i,curfilesize,curpos); 1.1982 + /* possible to fix? */ 1.1983 + if (!stream->rdonly && LOCAL->expok && 1.1984 + (i < elt->rfc822_size)) { 1.1985 + elt->rfc822_size -= i; 1.1986 + if (elt->rfc822_size < 1.1987 + elt->private.msg.header.text.size) 1.1988 + elt->private.msg.header.text.size = 1.1989 + elt->rfc822_size; 1.1990 + strcat (tmp,", repaired"); 1.1991 + indexrepairneeded = T; 1.1992 + } 1.1993 + MM_LOG (tmp,WARN); 1.1994 + } 1.1995 + } 1.1996 + break; 1.1997 + } 1.1998 + else msg = "expansion"; 1.1999 + } 1.2000 + else msg = "header size"; 1.2001 + } 1.2002 + else msg = "header position"; 1.2003 + } 1.2004 + else msg = "message position"; 1.2005 + } 1.2006 + else msg = "file#"; 1.2007 + } 1.2008 + sprintf (tmp,"Error in %s in mix index file: %.500s",msg,s); 1.2009 + MM_LOG (tmp,ERROR); 1.2010 + return NIL; 1.2011 + default: 1.2012 + sprintf (tmp,"Unknown record in mix index file: %.500s",s); 1.2013 + MM_LOG (tmp,ERROR); 1.2014 + return NIL; 1.2015 + } 1.2016 + if (!s) return NIL; /* barfage from mix_read_record() */ 1.2017 + /* expunge trailing messages not in index */ 1.2018 + if (LOCAL->expok) while (nmsgs < stream->nmsgs) 1.2019 + mail_expunged (stream,stream->nmsgs); 1.2020 + } 1.2021 + 1.2022 + /* repair metadata and index if needed */ 1.2023 + if ((metarepairneeded ? mix_meta_update (stream) : T) && 1.2024 + (indexrepairneeded ? mix_index_update (stream,*idxf,NIL) : T)) { 1.2025 + MESSAGECACHE *elt; 1.2026 + int fd; 1.2027 + unsigned long uid,uf,sf,mod; 1.2028 + char *s; 1.2029 + int updatep = NIL; 1.2030 + /* open status file */ 1.2031 + if ((fd = open (LOCAL->status, 1.2032 + stream->rdonly ? O_RDONLY : O_RDWR,NIL)) < 0) 1.2033 + MM_LOG ("Error opening mix status file",ERROR); 1.2034 + /* acquire exclusive access and FILE */ 1.2035 + else if (!flock (fd,stream->rdonly ? LOCK_SH : LOCK_EX) && 1.2036 + !(statf = fdopen (fd,stream->rdonly ? "rb" : "r+b"))) { 1.2037 + MM_LOG ("Error obtaining stream on mix status file",ERROR); 1.2038 + flock (fd,LOCK_UN); /* relinquish lock */ 1.2039 + close (fd); 1.2040 + } 1.2041 + /* get sequence */ 1.2042 + else if (!(i = mix_read_sequence (statf)) || 1.2043 + ((i < LOCAL->statusseq) && stream->nmsgs && (i != 1))) { 1.2044 + sprintf (LOCAL->buf, 1.2045 + "Error in mix status sequence record, i=%lx, seq=%lx", 1.2046 + i,LOCAL->statusseq); 1.2047 + MM_LOG (LOCAL->buf,ERROR); 1.2048 + } 1.2049 + /* sequence changed from last time? */ 1.2050 + else if (i != LOCAL->statusseq) { 1.2051 + /* update sequence, get first elt */ 1.2052 + if (i > LOCAL->statusseq) LOCAL->statusseq = i; 1.2053 + if (stream->nmsgs) { 1.2054 + elt = mail_elt (stream,i = 1); 1.2055 + 1.2056 + /* read message records */ 1.2057 + while ((t = s = mix_read_record (statf,LOCAL->buf,LOCAL->buflen, 1.2058 + "status")) && *s && (*s++ == ':') && 1.2059 + isxdigit (*s)) { 1.2060 + uid = strtoul (s,&s,16); 1.2061 + if ((*s++ == ':') && isxdigit (*s)) { 1.2062 + uf = strtoul (s,&s,16); 1.2063 + if ((*s++ == ':') && isxdigit (*s)) { 1.2064 + sf = strtoul (s,&s,16); 1.2065 + if ((*s++ == ':') && isxdigit (*s)) { 1.2066 + mod = strtoul (s,&s,16); 1.2067 + /* ignore expansion values */ 1.2068 + if (*s++ == ':') { 1.2069 + /* need to move ahead to next elt? */ 1.2070 + while ((uid > elt->private.uid) && (i < stream->nmsgs)) 1.2071 + elt = mail_elt (stream,++i); 1.2072 + /* update elt if altered */ 1.2073 + if ((uid == elt->private.uid) && 1.2074 + (!elt->valid || (mod != elt->private.mod))) { 1.2075 + elt->user_flags = uf; 1.2076 + elt->private.mod = mod; 1.2077 + elt->seen = (sf & fSEEN) ? T : NIL; 1.2078 + elt->deleted = (sf & fDELETED) ? T : NIL; 1.2079 + elt->flagged = (sf & fFLAGGED) ? T : NIL; 1.2080 + elt->answered = (sf & fANSWERED) ? T : NIL; 1.2081 + elt->draft = (sf & fDRAFT) ? T : NIL; 1.2082 + /* announce if altered existing message */ 1.2083 + if (elt->valid) MM_FLAGS (stream,elt->msgno); 1.2084 + /* first time, is old message? */ 1.2085 + else if (sf & fOLD) { 1.2086 + /* yes, clear recent and set valid */ 1.2087 + elt->recent = NIL; 1.2088 + elt->valid = T; 1.2089 + } 1.2090 + /* recent, allowed to update its status? */ 1.2091 + else if (sflags) { 1.2092 + /* yes, set valid and check in status */ 1.2093 + elt->valid = T; 1.2094 + elt->private.mod = mix_modseq (elt->private.mod); 1.2095 + updatep = T; 1.2096 + } 1.2097 + /* leave valid unset and recent if sflags not set */ 1.2098 + } 1.2099 + continue; /* everything looks good */ 1.2100 + } 1.2101 + } 1.2102 + } 1.2103 + } 1.2104 + break; /* error somewhere */ 1.2105 + } 1.2106 + 1.2107 + if (t && *t) { /* non-null means bogus record */ 1.2108 + char msg[MAILTMPLEN]; 1.2109 + sprintf (msg,"Error in mix status file message record%s: %.80s", 1.2110 + stream->rdonly ? "" : ", fixing",t); 1.2111 + MM_LOG (msg,WARN); 1.2112 + /* update it if not readonly */ 1.2113 + if (!stream->rdonly) updatep = T; 1.2114 + } 1.2115 + if (updatep) { /* need to update? */ 1.2116 + LOCAL->statusseq = mix_modseq (LOCAL->statusseq); 1.2117 + mix_status_update (stream,statf,LONGT); 1.2118 + } 1.2119 + } 1.2120 + } 1.2121 + } 1.2122 + } 1.2123 + if (statf) { /* still happy? */ 1.2124 + unsigned long j; 1.2125 + stream->silent = silent; /* now notify upper level */ 1.2126 + mail_exists (stream,stream->nmsgs); 1.2127 + for (i = 1, j = 0; i <= stream->nmsgs; ++i) 1.2128 + if (mail_elt (stream,i)->recent) ++j; 1.2129 + mail_recent (stream,j); 1.2130 + } 1.2131 + return statf; 1.2132 +} 1.2133 + 1.2134 +/* MIX metadata file routines */ 1.2135 + 1.2136 +/* MIX read metadata 1.2137 + * Accepts: MAIL stream 1.2138 + * return pointer for modseq 1.2139 + * Returns: pointer to metadata after modseq or NIL if failure 1.2140 + */ 1.2141 + 1.2142 +char *mix_meta_slurp (MAILSTREAM *stream,unsigned long *seq) 1.2143 +{ 1.2144 + struct stat sbuf; 1.2145 + char *s; 1.2146 + char *ret = NIL; 1.2147 + if (fstat (LOCAL->mfd,&sbuf)) 1.2148 + MM_LOG ("Error obtaining size of mix metatdata file",ERROR); 1.2149 + if (sbuf.st_size > LOCAL->buflen) { 1.2150 + /* should be just a few dozen bytes */ 1.2151 + if (sbuf.st_size > METAMAX) fatal ("absurd mix metadata file size"); 1.2152 + fs_give ((void **) &LOCAL->buf); 1.2153 + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = sbuf.st_size) + 1); 1.2154 + } 1.2155 + /* read current metadata file */ 1.2156 + LOCAL->buf[sbuf.st_size] = '\0'; 1.2157 + if (lseek (LOCAL->mfd,0,L_SET) || 1.2158 + (read (LOCAL->mfd,s = LOCAL->buf,sbuf.st_size) != sbuf.st_size)) 1.2159 + MM_LOG ("Error reading mix metadata file",ERROR); 1.2160 + else if ((*s != 'S') || !isxdigit (s[1]) || 1.2161 + ((*seq = strtoul (s+1,&s,16)) < LOCAL->metaseq) || 1.2162 + (*s++ != '\015') || (*s++ != '\012')) 1.2163 + MM_LOG ("Error in mix metadata file sequence record",ERROR); 1.2164 + else ret = s; 1.2165 + return ret; 1.2166 +} 1.2167 + 1.2168 +/* MIX update metadata 1.2169 + * Accepts: MAIL stream 1.2170 + * Returns: T on success, NIL if error 1.2171 + * 1.2172 + * Index MUST be locked!! 1.2173 + */ 1.2174 + 1.2175 +long mix_meta_update (MAILSTREAM *stream) 1.2176 +{ 1.2177 + long ret; 1.2178 + /* do nothing if stream readonly */ 1.2179 + if (stream->rdonly) ret = LONGT; 1.2180 + else { 1.2181 + unsigned char c,*s,*ss,*t; 1.2182 + unsigned long i; 1.2183 + /* The worst-case metadata is limited to: 1.2184 + * 4 * (1 + 8 + 2) + (NUSERFLAGS * (MAXUSERFLAG + 1)) 1.2185 + * which comes out to 1994 octets. This is much smaller than the normal 1.2186 + * CHUNKSIZE definition of 64K, and CHUNKSIZE is the smallest size of 1.2187 + * LOCAL->buf. 1.2188 + * 1.2189 + * If more stuff gets added to the metadata, or if you change the value 1.2190 + * of NUSERFLAGS, MAXUSERFLAG or CHUNKSIZE, be sure to recalculate the 1.2191 + * above assertation. 1.2192 + */ 1.2193 + sprintf (LOCAL->buf,SEQFMT,LOCAL->metaseq = mix_modseq (LOCAL->metaseq)); 1.2194 + sprintf (LOCAL->buf + strlen (LOCAL->buf),MTAFMT, 1.2195 + stream->uid_validity,stream->uid_last,LOCAL->newmsg); 1.2196 + for (i = 0, c = 'K', s = ss = LOCAL->buf + strlen (LOCAL->buf); 1.2197 + (i < NUSERFLAGS) && (t = stream->user_flags[i]); ++i) { 1.2198 + if (!*t) fatal ("impossible empty keyword"); 1.2199 + *s++ = c; /* write delimiter */ 1.2200 + while (*t) *s++ = *t++; /* write keyword */ 1.2201 + c = ' '; /* delimiter is now space */ 1.2202 + } 1.2203 + if (s != ss) { /* tie off keywords line */ 1.2204 + *s++ = '\015'; *s++ = '\012'; 1.2205 + } 1.2206 + /* calculate length of metadata */ 1.2207 + if ((i = s - LOCAL->buf) > LOCAL->buflen) 1.2208 + fatal ("impossible buffer overflow"); 1.2209 + lseek (LOCAL->mfd,0,L_SET); /* rewind file */ 1.2210 + /* write new metadata */ 1.2211 + ret = (write (LOCAL->mfd,LOCAL->buf,i) == i) ? LONGT : NIL; 1.2212 + ftruncate (LOCAL->mfd,i); /* and tie off at that point */ 1.2213 + } 1.2214 + return ret; 1.2215 +} 1.2216 + 1.2217 +/* MIX index file routines */ 1.2218 + 1.2219 + 1.2220 +/* MIX update index 1.2221 + * Accepts: MAIL stream 1.2222 + * open FILE 1.2223 + * expansion check flag 1.2224 + * Returns: T on success, NIL if error 1.2225 + */ 1.2226 + 1.2227 +long mix_index_update (MAILSTREAM *stream,FILE *idxf,long flag) 1.2228 +{ 1.2229 + unsigned long i; 1.2230 + long ret = LONGT; 1.2231 + if (!stream->rdonly) { /* do nothing if stream readonly */ 1.2232 + if (flag) { /* need to do expansion check? */ 1.2233 + char tmp[MAILTMPLEN]; 1.2234 + size_t size; 1.2235 + struct stat sbuf; 1.2236 + /* calculate file size we need */ 1.2237 + for (i = 1, size = 0; i <= stream->nmsgs; ++i) 1.2238 + if (!mail_elt (stream,i)->private.ghost) ++size; 1.2239 + if (size) { /* Winston Smith's first dairy entry */ 1.2240 + sprintf (tmp,IXRFMT,0,14,4,4,13,0,0,'+',0,0,0,0,0,0,0); 1.2241 + size *= strlen (tmp); 1.2242 + } 1.2243 + /* calculate file size we need */ 1.2244 + sprintf (tmp,SEQFMT,LOCAL->indexseq); 1.2245 + size += strlen (tmp); 1.2246 + /* get current file size */ 1.2247 + if (fstat (fileno (idxf),&sbuf)) { 1.2248 + MM_LOG ("Error getting size of mix index file",ERROR); 1.2249 + ret = NIL; 1.2250 + } 1.2251 + /* need to write additional space? */ 1.2252 + else if (sbuf.st_size < size) { 1.2253 + void *buf = fs_get (size -= sbuf.st_size); 1.2254 + memset (buf,0,size); 1.2255 + if (fseek (idxf,0,SEEK_END) || (fwrite (buf,1,size,idxf) != size) || 1.2256 + fflush (idxf)) { 1.2257 + fseek (idxf,sbuf.st_size,SEEK_SET); 1.2258 + ftruncate (fileno (idxf),sbuf.st_size); 1.2259 + MM_LOG ("Error extending mix index file",ERROR); 1.2260 + ret = NIL; 1.2261 + } 1.2262 + fs_give ((void **) &buf); 1.2263 + } 1.2264 + } 1.2265 + 1.2266 + if (ret) { /* if still good to go */ 1.2267 + rewind (idxf); /* let's start at the very beginning */ 1.2268 + /* write modseq first */ 1.2269 + fprintf (idxf,SEQFMT,LOCAL->indexseq); 1.2270 + /* then write all messages */ 1.2271 + for (i = 1; ret && (i <= stream->nmsgs); i++) { 1.2272 + MESSAGECACHE *elt = mail_elt (stream,i); 1.2273 + if (!elt->private.ghost)/* only write living messages */ 1.2274 + fprintf (idxf,IXRFMT,elt->private.uid, 1.2275 + elt->year + BASEYEAR,elt->month,elt->day, 1.2276 + elt->hours,elt->minutes,elt->seconds, 1.2277 + elt->zoccident ? '-' : '+',elt->zhours,elt->zminutes, 1.2278 + elt->rfc822_size,elt->private.spare.data, 1.2279 + elt->private.special.offset, 1.2280 + elt->private.msg.header.offset, 1.2281 + elt->private.msg.header.text.size); 1.2282 + if (ferror (idxf)) { 1.2283 + MM_LOG ("Error updating mix index file",ERROR); 1.2284 + ret = NIL; 1.2285 + } 1.2286 + } 1.2287 + if (fflush (idxf)) { 1.2288 + MM_LOG ("Error flushing mix index file",ERROR); 1.2289 + ret = NIL; 1.2290 + } 1.2291 + if (ret) ftruncate (fileno (idxf),ftell (idxf)); 1.2292 + } 1.2293 + } 1.2294 + return ret; 1.2295 +} 1.2296 + 1.2297 +/* MIX status file routines */ 1.2298 + 1.2299 + 1.2300 +/* MIX update status 1.2301 + * Accepts: MAIL stream 1.2302 + * pointer to open FILE 1.2303 + * expansion check flag 1.2304 + * Returns: T on success, NIL if error 1.2305 + */ 1.2306 + 1.2307 +long mix_status_update (MAILSTREAM *stream,FILE *statf,long flag) 1.2308 +{ 1.2309 + unsigned long i; 1.2310 + char tmp[MAILTMPLEN]; 1.2311 + long ret = LONGT; 1.2312 + if (!stream->rdonly) { /* do nothing if stream readonly */ 1.2313 + if (flag) { /* need to do expansion check? */ 1.2314 + char tmp[MAILTMPLEN]; 1.2315 + size_t size; 1.2316 + struct stat sbuf; 1.2317 + /* calculate file size we need */ 1.2318 + for (i = 1, size = 0; i <= stream->nmsgs; ++i) 1.2319 + if (!mail_elt (stream,i)->private.ghost) ++size; 1.2320 + if (size) { /* number of living messages */ 1.2321 + sprintf (tmp,STRFMT,0,0,0,0); 1.2322 + size *= strlen (tmp); 1.2323 + } 1.2324 + sprintf (tmp,SEQFMT,LOCAL->statusseq); 1.2325 + size += strlen (tmp); 1.2326 + /* get current file size */ 1.2327 + if (fstat (fileno (statf),&sbuf)) { 1.2328 + MM_LOG ("Error getting size of mix status file",ERROR); 1.2329 + ret = NIL; 1.2330 + } 1.2331 + /* need to write additional space? */ 1.2332 + else if (sbuf.st_size < size) { 1.2333 + void *buf = fs_get (size -= sbuf.st_size); 1.2334 + memset (buf,0,size); 1.2335 + if (fseek (statf,0,SEEK_END) || (fwrite (buf,1,size,statf) != size) || 1.2336 + fflush (statf)) { 1.2337 + fseek (statf,sbuf.st_size,SEEK_SET); 1.2338 + ftruncate (fileno (statf),sbuf.st_size); 1.2339 + MM_LOG ("Error extending mix status file",ERROR); 1.2340 + ret = NIL; 1.2341 + } 1.2342 + fs_give ((void **) &buf); 1.2343 + } 1.2344 + } 1.2345 + 1.2346 + if (ret) { /* if still good to go */ 1.2347 + rewind (statf); /* let's start at the very beginning */ 1.2348 + /* write sequence */ 1.2349 + fprintf (statf,SEQFMT,LOCAL->statusseq); 1.2350 + /* write message status records */ 1.2351 + for (i = 1; ret && (i <= stream->nmsgs); ++i) { 1.2352 + MESSAGECACHE *elt = mail_elt (stream,i); 1.2353 + /* make sure all messages have a modseq */ 1.2354 + if (!elt->private.mod) elt->private.mod = LOCAL->statusseq; 1.2355 + if (!elt->private.ghost)/* only write living messages */ 1.2356 + fprintf (statf,STRFMT,elt->private.uid,elt->user_flags, 1.2357 + (fSEEN * elt->seen) + (fDELETED * elt->deleted) + 1.2358 + (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) + 1.2359 + (fDRAFT * elt->draft) + (elt->valid ? fOLD : NIL), 1.2360 + elt->private.mod); 1.2361 + if (ferror (statf)) { 1.2362 + sprintf (tmp,"Error updating mix status file: %.80s", 1.2363 + strerror (errno)); 1.2364 + MM_LOG (tmp,ERROR); 1.2365 + ret = NIL; 1.2366 + } 1.2367 + } 1.2368 + if (ret && fflush (statf)) { 1.2369 + MM_LOG ("Error flushing mix status file",ERROR); 1.2370 + ret = NIL; 1.2371 + } 1.2372 + if (ret) ftruncate (fileno (statf),ftell (statf)); 1.2373 + } 1.2374 + } 1.2375 + return ret; 1.2376 +} 1.2377 + 1.2378 +/* MIX data file routines */ 1.2379 + 1.2380 + 1.2381 +/* MIX open data file 1.2382 + * Accepts: MAIL stream 1.2383 + * pointer to returned fd if success 1.2384 + * pointer to returned size if success 1.2385 + * size of new data to be added 1.2386 + * Returns: open FILE, or NIL if failure 1.2387 + * 1.2388 + * The curend test assumes that the last message of the mailbox is the furthest 1.2389 + * point that the current data file extends, and thus that is all that needs to 1.2390 + * be tested for short file prevention. 1.2391 + */ 1.2392 + 1.2393 +FILE *mix_data_open (MAILSTREAM *stream,int *fd,long *size, 1.2394 + unsigned long newsize) 1.2395 +{ 1.2396 + FILE *msgf = NIL; 1.2397 + struct stat sbuf; 1.2398 + MESSAGECACHE *elt = stream->nmsgs ? mail_elt (stream,stream->nmsgs) : NIL; 1.2399 + unsigned long curend = (elt && (elt->private.spare.data == LOCAL->newmsg)) ? 1.2400 + elt->private.special.offset + elt->private.msg.header.offset + 1.2401 + elt->rfc822_size : 0; 1.2402 + /* allow create if curend 0 */ 1.2403 + if ((*fd = open (mix_file_data (LOCAL->buf,stream->mailbox,LOCAL->newmsg), 1.2404 + O_RDWR | (curend ? NIL : O_CREAT),NIL)) >= 0) { 1.2405 + fstat (*fd,&sbuf); /* get current file size */ 1.2406 + /* can we use this file? */ 1.2407 + if ((curend <= sbuf.st_size) && 1.2408 + (!sbuf.st_size || ((sbuf.st_size + newsize) <= MIXDATAROLL))) 1.2409 + *size = sbuf.st_size; /* yes, return current size */ 1.2410 + else { /* short file or becoming too long */ 1.2411 + if (curend > sbuf.st_size) { 1.2412 + char tmp[MAILTMPLEN]; 1.2413 + sprintf (tmp,"short mix message file %.08lx (%ld > %ld), rolling", 1.2414 + LOCAL->newmsg,curend,sbuf.st_size); 1.2415 + MM_LOG (tmp,WARN); /* shouldn't happen */ 1.2416 + } 1.2417 + close (*fd); /* roll to a new file */ 1.2418 + while ((*fd = open (mix_file_data 1.2419 + (LOCAL->buf,stream->mailbox, 1.2420 + LOCAL->newmsg = mix_modseq (LOCAL->newmsg)), 1.2421 + O_RDWR | O_CREAT | O_EXCL,sbuf.st_mode)) < 0); 1.2422 + *size = 0; /* brand new file */ 1.2423 + fchmod (*fd,sbuf.st_mode);/* with same mode as previous file */ 1.2424 + } 1.2425 + } 1.2426 + if (*fd >= 0) { /* have a data file? */ 1.2427 + /* yes, get stdio and set position */ 1.2428 + if (msgf = fdopen (*fd,"r+b")) fseek (msgf,*size,SEEK_SET); 1.2429 + else close (*fd); /* fdopen() failed? */ 1.2430 + } 1.2431 + return msgf; /* return results */ 1.2432 +} 1.2433 + 1.2434 +/* MIX open sortcache 1.2435 + * Accepts: MAIL stream 1.2436 + * Returns: open FILE, or NIL if failure or could only get readonly sortcache 1.2437 + */ 1.2438 + 1.2439 +FILE *mix_sortcache_open (MAILSTREAM *stream) 1.2440 +{ 1.2441 + int fd,refwd; 1.2442 + unsigned long i,uid,sentdate,fromlen,tolen,cclen,subjlen,msgidlen,reflen; 1.2443 + char *s,*t,*msg,tmp[MAILTMPLEN]; 1.2444 + MESSAGECACHE *elt; 1.2445 + SORTCACHE *sc; 1.2446 + STRINGLIST *sl; 1.2447 + struct stat sbuf; 1.2448 + int rdonly = NIL; 1.2449 + FILE *srtcf = NIL; 1.2450 + mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); 1.2451 + fstat (LOCAL->mfd,&sbuf); 1.2452 + if (!stream->nmsgs); /* do nothing if mailbox empty */ 1.2453 + /* open sortcache file */ 1.2454 + else if (((fd = open (LOCAL->sortcache,O_RDWR|O_CREAT,sbuf.st_mode)) < 0) && 1.2455 + !(rdonly = ((fd = open (LOCAL->sortcache,O_RDONLY,NIL)) >= 0))) 1.2456 + MM_LOG ("Error opening mix sortcache file",WARN); 1.2457 + /* acquire lock and FILE */ 1.2458 + else if (!flock (fd,rdonly ? LOCK_SH : LOCK_EX) && 1.2459 + !(srtcf = fdopen (fd,rdonly ? "rb" : "r+b"))) { 1.2460 + MM_LOG ("Error obtaining stream on mix sortcache file",WARN); 1.2461 + flock (fd,LOCK_UN); /* relinquish lock */ 1.2462 + close (fd); 1.2463 + } 1.2464 + else if (!(i = mix_read_sequence (srtcf)) || (i < LOCAL->sortcacheseq)) 1.2465 + MM_LOG ("Error in mix sortcache file sequence record",WARN); 1.2466 + /* sequence changed from last time? */ 1.2467 + else if (i > LOCAL->sortcacheseq) { 1.2468 + LOCAL->sortcacheseq = i; /* update sequence */ 1.2469 + while ((s = t = mix_read_record (srtcf,LOCAL->buf,LOCAL->buflen, 1.2470 + "sortcache")) && *s && 1.2471 + (msg = "uid") && (*s++ == ':') && isxdigit (*s)) { 1.2472 + uid = strtoul (s,&s,16); 1.2473 + if ((*s++ == ':') && isxdigit (*s)) { 1.2474 + sentdate = strtoul (s,&s,16); 1.2475 + if ((*s++ == ':') && isxdigit (*s)) { 1.2476 + fromlen = strtoul (s,&s,16); 1.2477 + if ((*s++ == ':') && isxdigit (*s)) { 1.2478 + tolen = strtoul (s,&s,16); 1.2479 + if ((*s++ == ':') && isxdigit (*s)) { 1.2480 + cclen = strtoul (s,&s,16); 1.2481 + if ((*s++ == ':') && ((*s == 'R') || (*s == ' ')) && 1.2482 + isxdigit (s[1])) { 1.2483 + refwd = (*s++ == 'R') ? T : NIL; 1.2484 + subjlen = strtoul (s,&s,16); 1.2485 + if ((*s++ == ':') && isxdigit (*s)) { 1.2486 + msgidlen = strtoul (s,&s,16); 1.2487 + if ((*s++ == ':') && isxdigit (*s)) { 1.2488 + reflen = strtoul (s,&s,16); 1.2489 + /* ignore expansion values */ 1.2490 + if (*s++ == ':') { 1.2491 + 1.2492 + if (i = mail_msgno (stream,uid)) { 1.2493 + sc = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE); 1.2494 + sc->size = (elt = mail_elt (stream,i))->rfc822_size; 1.2495 + sc->date = sentdate; 1.2496 + sc->arrival = elt->day ? mail_longdate (elt) : 1; 1.2497 + if (refwd) sc->refwd = T; 1.2498 + if (fromlen) { 1.2499 + if (sc->from) fseek (srtcf,fromlen + 2,SEEK_CUR); 1.2500 + else if ((getc (srtcf) != 'F') || 1.2501 + (fread (sc->from = (char *) fs_get(fromlen), 1.2502 + 1,fromlen-1,srtcf) != (fromlen-1))|| 1.2503 + (sc->from[fromlen-1] = '\0') || 1.2504 + (getc (srtcf) != '\015') || 1.2505 + (getc (srtcf) != '\012')) { 1.2506 + msg = "from data"; 1.2507 + break; 1.2508 + } 1.2509 + } 1.2510 + if (tolen) { 1.2511 + if (sc->to) fseek (srtcf,tolen + 2,SEEK_CUR); 1.2512 + else if ((getc (srtcf) != 'T') || 1.2513 + (fread (sc->to = (char *) fs_get (tolen), 1.2514 + 1,tolen-1,srtcf) != (tolen - 1)) || 1.2515 + (sc->to[tolen-1] = '\0') || 1.2516 + (getc (srtcf) != '\015') || 1.2517 + (getc (srtcf) != '\012')) { 1.2518 + msg = "to data"; 1.2519 + break; 1.2520 + } 1.2521 + } 1.2522 + if (cclen) { 1.2523 + if (sc->cc) fseek (srtcf,cclen + 2,SEEK_CUR); 1.2524 + else if ((getc (srtcf) != 'C') || 1.2525 + (fread (sc->cc = (char *) fs_get (cclen), 1.2526 + 1,cclen-1,srtcf) != (cclen - 1)) || 1.2527 + (sc->cc[cclen-1] = '\0') || 1.2528 + (getc (srtcf) != '\015') || 1.2529 + (getc (srtcf) != '\012')) { 1.2530 + msg = "cc data"; 1.2531 + break; 1.2532 + } 1.2533 + } 1.2534 + if (subjlen) { 1.2535 + if (sc->subject) fseek (srtcf,subjlen + 2,SEEK_CUR); 1.2536 + else if ((getc (srtcf) != 'S') || 1.2537 + (fread (sc->subject = 1.2538 + (char *) fs_get (subjlen),1, 1.2539 + subjlen-1,srtcf) != (subjlen-1))|| 1.2540 + (sc->subject[subjlen-1] = '\0') || 1.2541 + (getc (srtcf) != '\015') || 1.2542 + (getc (srtcf) != '\012')) { 1.2543 + msg = "subject data"; 1.2544 + break; 1.2545 + } 1.2546 + } 1.2547 + 1.2548 + if (msgidlen) { 1.2549 + if (sc->message_id) 1.2550 + fseek (srtcf,msgidlen + 2,SEEK_CUR); 1.2551 + else if ((getc (srtcf) != 'M') || 1.2552 + (fread (sc->message_id = 1.2553 + (char *) fs_get (msgidlen),1, 1.2554 + msgidlen-1,srtcf) != (msgidlen-1))|| 1.2555 + (sc->message_id[msgidlen-1] = '\0') || 1.2556 + (getc (srtcf) != '\015') || 1.2557 + (getc (srtcf) != '\012')) { 1.2558 + msg = "message-id data"; 1.2559 + break; 1.2560 + } 1.2561 + } 1.2562 + if (reflen) { 1.2563 + if (sc->references) fseek(srtcf,reflen + 2,SEEK_CUR); 1.2564 + /* make sure it fits */ 1.2565 + else { 1.2566 + if (reflen >= LOCAL->buflen) { 1.2567 + fs_give ((void **) &LOCAL->buf); 1.2568 + LOCAL->buf = (char *) 1.2569 + fs_get ((LOCAL->buflen = reflen) + 1); 1.2570 + } 1.2571 + if ((getc (srtcf) != 'R') || 1.2572 + (fread (LOCAL->buf,1,reflen-1,srtcf) != 1.2573 + (reflen - 1)) || 1.2574 + (LOCAL->buf[reflen-1] = '\0') || 1.2575 + (getc (srtcf) != '\015') || 1.2576 + (getc (srtcf) != '\012')) { 1.2577 + msg = "references data"; 1.2578 + break; 1.2579 + } 1.2580 + for (s = LOCAL->buf,sl = NIL, 1.2581 + sc->references = mail_newstringlist (); 1.2582 + s && *s; s += i + 1) { 1.2583 + if ((i = strtoul (s,&s,16)) && (*s++ == ':') && 1.2584 + (s[i] == ':')) { 1.2585 + if (sl) sl = sl->next = mail_newstringlist(); 1.2586 + else sl = sc->references; 1.2587 + s[i] = '\0'; 1.2588 + sl->text.data = cpystr (s); 1.2589 + sl->text.size = i; 1.2590 + } 1.2591 + else s = NIL; 1.2592 + } 1.2593 + if (!s || *s || 1.2594 + (s != ((char *) LOCAL->buf + reflen - 1))) { 1.2595 + msg = "references length consistency check"; 1.2596 + break; 1.2597 + } 1.2598 + } 1.2599 + } 1.2600 + } 1.2601 + 1.2602 + /* UID not found, ignore this message */ 1.2603 + else fseek (srtcf,((fromlen ? fromlen + 2 : 0) + 1.2604 + (tolen ? tolen + 2 : 0) + 1.2605 + (cclen ? cclen + 2 : 0) + 1.2606 + (subjlen ? subjlen + 2 : 0) + 1.2607 + (msgidlen ? msgidlen + 2 : 0) + 1.2608 + (reflen ? reflen + 2 : 0)), 1.2609 + SEEK_CUR); 1.2610 + continue; 1.2611 + } 1.2612 + else msg = "expansion"; 1.2613 + } 1.2614 + else msg = "references"; 1.2615 + } 1.2616 + else msg = "message-id"; 1.2617 + } 1.2618 + else msg = "subject"; 1.2619 + } 1.2620 + else msg = "cc"; 1.2621 + } 1.2622 + else msg = "to"; 1.2623 + } 1.2624 + else msg = "from"; 1.2625 + } 1.2626 + else msg = "sentdate"; 1.2627 + break; /* error somewhere */ 1.2628 + } 1.2629 + if (!t || *t) { /* error detected? */ 1.2630 + if (t) { /* non-null means bogus record */ 1.2631 + sprintf (tmp,"Error in %s in mix sortcache record: %.500s",msg,t); 1.2632 + MM_LOG (tmp,WARN); 1.2633 + } 1.2634 + fclose (srtcf); /* either way, must punt */ 1.2635 + srtcf = NIL; 1.2636 + } 1.2637 + } 1.2638 + if (rdonly && srtcf) { /* can't update if readonly */ 1.2639 + unlink (LOCAL->sortcache); /* try deleting it */ 1.2640 + fclose (srtcf); /* so close it and return as if error */ 1.2641 + srtcf = NIL; 1.2642 + } 1.2643 + else fchmod (fd,sbuf.st_mode); 1.2644 + return srtcf; 1.2645 +} 1.2646 + 1.2647 +/* MIX update and close sortcache 1.2648 + * Accepts: MAIL stream 1.2649 + * pointer to open FILE (if FILE is NIL, do nothing) 1.2650 + * Returns: T on success, NIL on error 1.2651 + */ 1.2652 + 1.2653 +long mix_sortcache_update (MAILSTREAM *stream,FILE **sortcache) 1.2654 +{ 1.2655 + FILE *f = *sortcache; 1.2656 + long ret = LONGT; 1.2657 + if (f) { /* ignore if no file */ 1.2658 + unsigned long i,j; 1.2659 + mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); 1.2660 + for (i = 1; (i <= stream->nmsgs) && 1.2661 + !((SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE))->dirty; ++i); 1.2662 + if (i <= stream->nmsgs) { /* only update if some entry is dirty */ 1.2663 + rewind (f); /* let's start at the very beginning */ 1.2664 + /* write sequence */ 1.2665 + fprintf (f,SEQFMT,LOCAL->sortcacheseq = mix_modseq(LOCAL->sortcacheseq)); 1.2666 + for (i = 1; ret && (i <= stream->nmsgs); ++i) { 1.2667 + MESSAGECACHE *elt = mail_elt (stream,i); 1.2668 + SORTCACHE *s = (SORTCACHE *) (*mc) (stream,i,CH_SORTCACHE); 1.2669 + STRINGLIST *sl; 1.2670 + s->dirty = NIL; /* no longer dirty */ 1.2671 + if (sl = s->references) /* count length of references */ 1.2672 + for (j = 1; sl && sl->text.data; sl = sl->next) 1.2673 + j += 10 + sl->text.size; 1.2674 + else j = 0; /* no references yet */ 1.2675 + fprintf (f,SCRFMT,elt->private.uid,s->date, 1.2676 + s->from ? strlen (s->from) + 1 : 0, 1.2677 + s->to ? strlen (s->to) + 1 : 0,s->cc ? strlen (s->cc) + 1 : 0, 1.2678 + s->refwd ? 'R' : ' ',s->subject ? strlen (s->subject) + 1: 0, 1.2679 + s->message_id ? strlen (s->message_id) + 1 : 0,j); 1.2680 + if (s->from) fprintf (f,"F%s\015\012",s->from); 1.2681 + if (s->to) fprintf (f,"T%s\015\012",s->to); 1.2682 + if (s->cc) fprintf (f,"C%s\015\012",s->cc); 1.2683 + if (s->subject) fprintf (f,"S%s\015\012",s->subject); 1.2684 + if (s->message_id) fprintf (f,"M%s\015\012",s->message_id); 1.2685 + if (j) { /* any references to write? */ 1.2686 + fputc ('R',f); /* yes, do so */ 1.2687 + for (sl = s->references; sl && sl->text.data; sl = sl->next) 1.2688 + fprintf (f,"%08lx:%s:",sl->text.size,sl->text.data); 1.2689 + fputs ("\015\012",f); 1.2690 + } 1.2691 + if (ferror (f)) { 1.2692 + MM_LOG ("Error updating mix sortcache file",WARN); 1.2693 + ret = NIL; 1.2694 + } 1.2695 + } 1.2696 + if (ret && fflush (f)) { 1.2697 + MM_LOG ("Error flushing mix sortcache file",WARN); 1.2698 + ret = NIL; 1.2699 + } 1.2700 + if (ret) ftruncate (fileno (f),ftell (f)); 1.2701 + } 1.2702 + if (fclose (f)) { 1.2703 + MM_LOG ("Error closing mix sortcache file",WARN); 1.2704 + ret = NIL; 1.2705 + } 1.2706 + } 1.2707 + return ret; 1.2708 +} 1.2709 + 1.2710 +/* MIX generic file routines */ 1.2711 + 1.2712 +/* MIX read record 1.2713 + * Accepts: open FILE 1.2714 + * buffer 1.2715 + * buffer length 1.2716 + * record type 1.2717 + * Returns: buffer if success, else NIL (zero-length buffer means EOF) 1.2718 + */ 1.2719 + 1.2720 +char *mix_read_record (FILE *f,char *buf,unsigned long buflen,char *type) 1.2721 +{ 1.2722 + char *s,tmp[MAILTMPLEN]; 1.2723 + /* ensure string tied off */ 1.2724 + buf[buflen-2] = buf[buflen-1] = '\0'; 1.2725 + while (fgets (buf,buflen-1,f)) { 1.2726 + if (s = strchr (buf,'\012')) { 1.2727 + if ((s != buf) && (s[-1] == '\015')) --s; 1.2728 + *s = '\0'; /* tie off buffer */ 1.2729 + if (s != buf) return buf; /* return if non-empty buffer */ 1.2730 + sprintf (tmp,"Empty mix %s record",type); 1.2731 + MM_LOG (tmp,WARN); 1.2732 + } 1.2733 + else if (buf[buflen-2]) { /* overlong record is bad news */ 1.2734 + sprintf (tmp,"Oversize mix %s record: %.512s",type,buf); 1.2735 + MM_LOG (tmp,ERROR); 1.2736 + return NIL; 1.2737 + } 1.2738 + else { 1.2739 + sprintf (tmp,"Truncated mix %s record: %.512s",type,buf); 1.2740 + MM_LOG (tmp,WARN); 1.2741 + return buf; /* pass to caller anyway */ 1.2742 + } 1.2743 + } 1.2744 + buf[0] = '\0'; /* return empty buffer on EOF */ 1.2745 + return buf; 1.2746 +} 1.2747 + 1.2748 +/* MIX read sequence record 1.2749 + * Accepts: open FILE 1.2750 + * Returns: sequence value, or NIL if failure 1.2751 + */ 1.2752 + 1.2753 +unsigned long mix_read_sequence (FILE *f) 1.2754 +{ 1.2755 + unsigned long ret; 1.2756 + char *s,tmp[MAILTMPLEN]; 1.2757 + if (!mix_read_record (f,tmp,MAILTMPLEN-1,"sequence")) return NIL; 1.2758 + switch (tmp[0]) { /* examine record */ 1.2759 + case '\0': /* end of file */ 1.2760 + ret = 1; /* start a new sequence regime */ 1.2761 + break; 1.2762 + case 'S': /* sequence record */ 1.2763 + if (isxdigit (tmp[1])) { /* must be followed by hex value */ 1.2764 + ret = strtoul (tmp+1,&s,16); 1.2765 + if (!*s) break; /* and nothing more */ 1.2766 + } 1.2767 + /* drop into default case */ 1.2768 + default: /* anything else is an error */ 1.2769 + return NIL; /* return error */ 1.2770 + } 1.2771 + return ret; 1.2772 +} 1.2773 + 1.2774 +/* MIX internal routines */ 1.2775 + 1.2776 + 1.2777 +/* MIX mail build directory name 1.2778 + * Accepts: destination string 1.2779 + * source 1.2780 + * Returns: destination or empty string if error 1.2781 + */ 1.2782 + 1.2783 +char *mix_dir (char *dst,char *name) 1.2784 +{ 1.2785 + char *s; 1.2786 + /* empty string if mailboxfile fails */ 1.2787 + if (!mailboxfile (dst,name)) *dst = '\0'; 1.2788 + /* driver-selected INBOX */ 1.2789 + else if (!*dst) mailboxfile (dst,"~/INBOX"); 1.2790 + /* tie off unnecessary trailing / */ 1.2791 + else if ((s = strrchr (dst,'/')) && !s[1]) *s = '\0'; 1.2792 + return dst; 1.2793 +} 1.2794 + 1.2795 + 1.2796 +/* MIX mail build file name 1.2797 + * Accepts: destination string 1.2798 + * directory name 1.2799 + * file name 1.2800 + * Returns: destination 1.2801 + */ 1.2802 + 1.2803 +char *mix_file (char *dst,char *dir,char *name) 1.2804 +{ 1.2805 + sprintf (dst,"%.500s/%.80s%.80s",dir,MIXNAME,name); 1.2806 + return dst; 1.2807 +} 1.2808 + 1.2809 + 1.2810 +/* MIX mail build file name from data file number 1.2811 + * Accepts: destination string 1.2812 + * directory name 1.2813 + * data file number 1.2814 + * Returns: destination 1.2815 + */ 1.2816 + 1.2817 +char *mix_file_data (char *dst,char *dir,unsigned long data) 1.2818 +{ 1.2819 + char tmp[MAILTMPLEN]; 1.2820 + if (data) sprintf (tmp,"%08lx",data); 1.2821 + else tmp[0] = '\0'; /* compatibility with experimental version */ 1.2822 + return mix_file (dst,dir,tmp); 1.2823 +} 1.2824 + 1.2825 +/* MIX mail get new modseq 1.2826 + * Accepts: old modseq 1.2827 + * Returns: new modseq value 1.2828 + */ 1.2829 + 1.2830 +unsigned long mix_modseq (unsigned long oldseq) 1.2831 +{ 1.2832 + /* normally time now */ 1.2833 + unsigned long ret = (unsigned long) time (NIL); 1.2834 + /* ensure that modseq doesn't go backwards */ 1.2835 + if (ret <= oldseq) ret = oldseq + 1; 1.2836 + return ret; 1.2837 +}