~ubuntu-branches/debian/lenny/alpine/lenny

« back to all changes in this revision

Viewing changes to imap/src/osdep/nt/mbxnt.c

  • Committer: Bazaar Package Importer
  • Author(s): Asheesh Laroia
  • Date: 2007-02-17 13:17:42 UTC
  • Revision ID: james.westby@ubuntu.com-20070217131742-99x5c6cpg1pbkdhw
Tags: upstream-0.82+dfsg
ImportĀ upstreamĀ versionĀ 0.82+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* ========================================================================
 
2
 * Copyright 1988-2007 University of Washington
 
3
 *
 
4
 * Licensed under the Apache License, Version 2.0 (the "License");
 
5
 * you may not use this file except in compliance with the License.
 
6
 * You may obtain a copy of the License at
 
7
 *
 
8
 *     http://www.apache.org/licenses/LICENSE-2.0
 
9
 *
 
10
 * 
 
11
 * ========================================================================
 
12
 */
 
13
 
 
14
/*
 
15
 * Program:     MBX mail routines
 
16
 *
 
17
 * Author:      Mark Crispin
 
18
 *              Networks and Distributed Computing
 
19
 *              Computing & Communications
 
20
 *              University of Washington
 
21
 *              Administration Building, AG-44
 
22
 *              Seattle, WA  98195
 
23
 *              Internet: MRC@CAC.Washington.EDU
 
24
 *
 
25
 * Date:        3 October 1995
 
26
 * Last Edited: 5 January 2007
 
27
 */
 
28
 
 
29
 
 
30
/*                              FILE TIME SEMANTICS
 
31
 *
 
32
 * The atime is the last read time of the file.
 
33
 * The mtime is the last flags update time of the file.
 
34
 * The ctime is the last write time of the file.
 
35
 */
 
36
 
 
37
#include <stdio.h>
 
38
#include <ctype.h>
 
39
#include <errno.h>
 
40
extern int errno;               /* just in case */
 
41
#include "mail.h"
 
42
#include "osdep.h"
 
43
#include <fcntl.h>
 
44
#include <time.h>
 
45
#include <sys/stat.h>
 
46
#include <sys/utime.h>
 
47
#include "misc.h"
 
48
#include "dummy.h"
 
49
#include "fdstring.h"
 
50
 
 
51
 
 
52
/* Build parameters */
 
53
 
 
54
#define HDRSIZE 2048
 
55
 
 
56
/* MBX I/O stream local data */
 
57
        
 
58
typedef struct mbx_local {
 
59
  unsigned int flagcheck: 1;    /* if ping should sweep for flags */
 
60
  unsigned int expok: 1;        /* if expunging OK in ping */
 
61
  unsigned int expunged : 1;    /* if one or more expunged messages */
 
62
  int fd;                       /* file descriptor for I/O */
 
63
  int ld;                       /* lock file descriptor */
 
64
  int ffuserflag;               /* first free user flag */
 
65
  off_t filesize;               /* file size parsed */
 
66
  time_t filetime;              /* last file time */
 
67
  time_t lastsnarf;             /* last snarf time */
 
68
  unsigned char *buf;           /* temporary buffer */
 
69
  unsigned long buflen;         /* current size of temporary buffer */
 
70
  char lock[MAILTMPLEN];        /* buffer to write lock name */
 
71
} MBXLOCAL;
 
72
 
 
73
 
 
74
/* Convenient access to local data */
 
75
 
 
76
#define LOCAL ((MBXLOCAL *) stream->local)
 
77
 
 
78
/* Function prototypes */
 
79
 
 
80
DRIVER *mbx_valid (char *name);
 
81
int mbx_isvalid (MAILSTREAM **stream,char *name,char *file,long flags);
 
82
void *mbx_parameters (long function,void *value);
 
83
void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
 
84
void mbx_list (MAILSTREAM *stream,char *ref,char *pat);
 
85
void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat);
 
86
long mbx_create (MAILSTREAM *stream,char *mailbox);
 
87
long mbx_delete (MAILSTREAM *stream,char *mailbox);
 
88
long mbx_rename (MAILSTREAM *stream,char *old,char *newname);
 
89
long mbx_status (MAILSTREAM *stream,char *mbx,long flags);
 
90
MAILSTREAM *mbx_open (MAILSTREAM *stream);
 
91
void mbx_close (MAILSTREAM *stream,long options);
 
92
void mbx_abort (MAILSTREAM *stream);
 
93
void mbx_flags (MAILSTREAM *stream,char *sequence,long flags);
 
94
char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
 
95
                  long flags);
 
96
long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
 
97
void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags);
 
98
void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
 
99
long mbx_ping (MAILSTREAM *stream);
 
100
void mbx_check (MAILSTREAM *stream);
 
101
long mbx_expunge (MAILSTREAM *stream,char *sequence,long options);
 
102
void mbx_snarf (MAILSTREAM *stream);
 
103
long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
 
104
long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
 
105
 
 
106
long mbx_parse (MAILSTREAM *stream);
 
107
MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok);
 
108
unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt);
 
109
void mbx_update_header (MAILSTREAM *stream);
 
110
void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags);
 
111
unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
 
112
                          unsigned long *size,char **hdr);
 
113
unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
 
114
                           long flags);
 
115
long mbx_flaglock (MAILSTREAM *stream);
 
116
 
 
117
/* MBX mail routines */
 
118
 
 
119
 
 
120
/* Driver dispatch used by MAIL */
 
121
 
 
122
DRIVER mbxdriver = {
 
123
  "mbx",                        /* driver name */
 
124
  DR_LOCAL|DR_MAIL|DR_CRLF|DR_LOCKING,
 
125
                                /* driver flags */
 
126
  (DRIVER *) NIL,               /* next driver */
 
127
  mbx_valid,                    /* mailbox is valid for us */
 
128
  mbx_parameters,               /* manipulate parameters */
 
129
  mbx_scan,                     /* scan mailboxes */
 
130
  mbx_list,                     /* list mailboxes */
 
131
  mbx_lsub,                     /* list subscribed mailboxes */
 
132
  NIL,                          /* subscribe to mailbox */
 
133
  NIL,                          /* unsubscribe from mailbox */
 
134
  mbx_create,                   /* create mailbox */
 
135
  mbx_delete,                   /* delete mailbox */
 
136
  mbx_rename,                   /* rename mailbox */
 
137
  mail_status_default,          /* status of mailbox */
 
138
  mbx_open,                     /* open mailbox */
 
139
  mbx_close,                    /* close mailbox */
 
140
  mbx_flags,                    /* fetch message "fast" attributes */
 
141
  mbx_flags,                    /* fetch message flags */
 
142
  NIL,                          /* fetch overview */
 
143
  NIL,                          /* fetch message envelopes */
 
144
  mbx_header,                   /* fetch message header */
 
145
  mbx_text,                     /* fetch message body */
 
146
  NIL,                          /* fetch partial message text */
 
147
  NIL,                          /* unique identifier */
 
148
  NIL,                          /* message number */
 
149
  mbx_flag,                     /* modify flags */
 
150
  mbx_flagmsg,                  /* per-message modify flags */
 
151
  NIL,                          /* search for message based on criteria */
 
152
  NIL,                          /* sort messages */
 
153
  NIL,                          /* thread messages */
 
154
  mbx_ping,                     /* ping mailbox to see if still alive */
 
155
  mbx_check,                    /* check for new messages */
 
156
  mbx_expunge,                  /* expunge deleted messages */
 
157
  mbx_copy,                     /* copy messages to another mailbox */
 
158
  mbx_append,                   /* append string message to mailbox */
 
159
  NIL                           /* garbage collect stream */
 
160
};
 
161
 
 
162
                                /* prototype stream */
 
163
MAILSTREAM mbxproto = {&mbxdriver};
 
164
 
 
165
/* MBX mail validate mailbox
 
166
 * Accepts: mailbox name
 
167
 * Returns: our driver if name is valid, NIL otherwise
 
168
 */
 
169
 
 
170
DRIVER *mbx_valid (char *name)
 
171
{
 
172
  char tmp[MAILTMPLEN];
 
173
  int fd = mbx_isvalid (NIL,name,tmp,NIL);
 
174
  if (fd < 0) return NIL;
 
175
  close (fd);                   /* don't need the fd now */
 
176
  return &mbxdriver;
 
177
}
 
178
 
 
179
 
 
180
/* MBX mail test for valid mailbox
 
181
 * Accepts: returned stream with valid mailbox keywords
 
182
 *          mailbox name
 
183
 *          buffer to write file name
 
184
 *          RW flags or NIL for readonly
 
185
 * Returns: file descriptor if valid, NIL otherwise
 
186
 */
 
187
 
 
188
#define MBXISVALIDNOUID 0x1     /* RW, don't do UID action */
 
189
#define MBXISVALIDUID 0x2       /* RW, do UID action */
 
190
 
 
191
int mbx_isvalid (MAILSTREAM **stream,char *name,char *file,long flags)
 
192
{
 
193
  int fd,upd;
 
194
  int ret = -1;
 
195
  unsigned long i;
 
196
  long j,k;
 
197
  off_t pos;
 
198
  char c,*s,*t,hdr[HDRSIZE];
 
199
  struct stat sbuf;
 
200
  struct utimbuf times;
 
201
  errno = EINVAL;               /* assume invalid argument */
 
202
                                /* if file, get its status */
 
203
  if ((s = dummy_file (file,name)) && !stat (s,&sbuf) &&
 
204
      ((sbuf.st_mode & S_IFMT) == S_IFREG) &&
 
205
      ((fd = open (file,(flags ? O_RDWR : O_RDONLY)|O_BINARY,NIL)) >= 0)) {
 
206
    errno = -1;                 /* assume bogus format */
 
207
    if (((((j = read (fd,hdr,HDRSIZE)) == HDRSIZE) && (hdr[0] == '*')) ||
 
208
                                /* locked, set byte 0 to "*", read rest */
 
209
         ((j < 0) && (lseek (fd,1,L_SET) == 1) &&
 
210
          (read (fd,hdr+1,HDRSIZE-1) == (HDRSIZE-1)) && (hdr[0] = '*'))) &&
 
211
        (hdr[1] == 'm') && (hdr[2] == 'b') && (hdr[3] == 'x') &&
 
212
        (hdr[4] == '*') && (hdr[5] == '\015') && (hdr[6] == '\012') &&
 
213
        isxdigit (hdr[7]) && isxdigit (hdr[8]) && isxdigit (hdr[9]) &&
 
214
        isxdigit (hdr[10]) && isxdigit (hdr[11]) && isxdigit (hdr[12]) &&
 
215
        isxdigit (hdr[13]) && isxdigit (hdr[14]) && isxdigit (c = hdr[15]) &&
 
216
        isxdigit (hdr[16]) && isxdigit (hdr[17]) && isxdigit (hdr[18]) &&
 
217
        isxdigit (hdr[19]) && isxdigit (hdr[20]) && isxdigit (hdr[21]) &&
 
218
        isxdigit (hdr[22]) && (hdr[23] == '\015') && (hdr[24] == '\012')) {
 
219
      ret = fd;
 
220
 
 
221
      if (stream) {             /* stream specified? */
 
222
        *stream = (MAILSTREAM *) memset (fs_get (sizeof (MAILSTREAM)),0,
 
223
                                         sizeof (MAILSTREAM));
 
224
        hdr[15] = '\0';         /* tie off UIDVALIDITY */
 
225
        (*stream)->uid_validity = strtoul (hdr+7,NIL,16);
 
226
        hdr[15] = c;            /* now get UIDLAST */
 
227
        (*stream)->uid_last = strtoul (hdr+15,NIL,16);
 
228
                                /* parse user flags */
 
229
        for (i = 0, s = hdr + 25;
 
230
             (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
 
231
             i++, s = t + 2) {
 
232
          *t = '\0';            /* tie off flag */
 
233
          if (strlen (s) <= MAXUSERFLAG)
 
234
            (*stream)->user_flags[i] = cpystr (s);
 
235
        }
 
236
                                /* make sure have true UIDLAST */
 
237
        if (flags & MBXISVALIDUID) {
 
238
          for (upd = NIL,pos = 2048, k = 0; pos < sbuf.st_size;
 
239
               pos += (j + k)) {
 
240
                                /* read header for this message */
 
241
            lseek (fd,pos,L_SET);
 
242
            if ((j = read (fd,hdr,64)) >= 0) {
 
243
              hdr[j] = '\0';
 
244
              if ((s = strchr (hdr,'\015')) && (s[1] == '\012')) {
 
245
                *s = '\0';
 
246
                k = s + 2 - hdr;
 
247
                if ((s = strchr (hdr,',')) && (j = strtol (s+1,&s,10)) &&
 
248
                    (*s == ';') && (s = strchr (s+1,'-'))) {
 
249
                                /* get UID if there is any */
 
250
                  i = strtoul (++s,&t,16);
 
251
                  if (!*t && (t == (s + 8)) && (i <= (*stream)->uid_last)) {
 
252
                    if (!i) {
 
253
                      lseek (fd,pos + s - hdr,L_SET);
 
254
                      sprintf (hdr,"%08lx",++(*stream)->uid_last);
 
255
                      write (fd,hdr,8);
 
256
                      upd = T;
 
257
                    }
 
258
                    continue;
 
259
                  }
 
260
                }
 
261
              }
 
262
              ret = -1;         /* error, give up */
 
263
              *stream = mail_close (*stream);
 
264
              pos = sbuf.st_size + 1;
 
265
              j = k = 0;
 
266
            }
 
267
          }
 
268
          if (upd) {        /* need to update hdr with new UIDLAST? */
 
269
            lseek (fd,15,L_SET);
 
270
            sprintf (hdr,"%08lx",(*stream)->uid_last);
 
271
            write (fd,hdr,8);
 
272
          }
 
273
        }
 
274
      }
 
275
    }
 
276
    if (ret != fd) close (fd);  /* close the file */
 
277
    else lseek (fd,0,L_SET);    /* else rewind to start */
 
278
                                /* \Marked status? */
 
279
    if (sbuf.st_ctime > sbuf.st_atime) {
 
280
                                /* preserve atime and mtime */
 
281
      times.actime = sbuf.st_atime;
 
282
      times.modtime = sbuf.st_mtime;
 
283
      utime (file,&times);      /* set the times */
 
284
    }
 
285
  }
 
286
                                /* in case INBOX but not mbx format */
 
287
  else if ((errno == ENOENT) && !compare_cstring (name,"INBOX")) errno = -1;
 
288
  return ret;                   /* return what we should */
 
289
}
 
290
 
 
291
/* MBX manipulate driver parameters
 
292
 * Accepts: function code
 
293
 *          function-dependent value
 
294
 * Returns: function-dependent return value
 
295
 */
 
296
 
 
297
void *mbx_parameters (long function,void *value)
 
298
{
 
299
  void *ret = NIL;
 
300
  switch ((int) function) {
 
301
  case SET_ONETIMEEXPUNGEATPING:
 
302
    if (value) ((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok = T;
 
303
  case GET_ONETIMEEXPUNGEATPING:
 
304
    if (value) ret = (void *)
 
305
      (((MBXLOCAL *) ((MAILSTREAM *) value)->local)->expok ? VOIDT : NIL);
 
306
    break;
 
307
  }
 
308
  return ret;
 
309
}
 
310
 
 
311
 
 
312
/* MBX mail scan mailboxes
 
313
 * Accepts: mail stream
 
314
 *          reference
 
315
 *          pattern to search
 
316
 *          string to scan
 
317
 */
 
318
 
 
319
void mbx_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
 
320
{
 
321
  if (stream) dummy_scan (NIL,ref,pat,contents);
 
322
}
 
323
 
 
324
 
 
325
/* MBX mail list mailboxes
 
326
 * Accepts: mail stream
 
327
 *          reference
 
328
 *          pattern to search
 
329
 */
 
330
 
 
331
void mbx_list (MAILSTREAM *stream,char *ref,char *pat)
 
332
{
 
333
  if (stream) dummy_list (NIL,ref,pat);
 
334
}
 
335
 
 
336
 
 
337
/* MBX mail list subscribed mailboxes
 
338
 * Accepts: mail stream
 
339
 *          reference
 
340
 *          pattern to search
 
341
 */
 
342
 
 
343
void mbx_lsub (MAILSTREAM *stream,char *ref,char *pat)
 
344
{
 
345
  if (stream) dummy_lsub (NIL,ref,pat);
 
346
}
 
347
 
 
348
/* MBX mail create mailbox
 
349
 * Accepts: MAIL stream
 
350
 *          mailbox name to create
 
351
 * Returns: T on success, NIL on failure
 
352
 */
 
353
 
 
354
long mbx_create (MAILSTREAM *stream,char *mailbox)
 
355
{
 
356
  char *s,mbx[MAILTMPLEN],tmp[HDRSIZE];
 
357
  long ret = NIL;
 
358
  int i,fd;
 
359
  if (!(s = dummy_file (mbx,mailbox))) {
 
360
    sprintf (mbx,"Can't create %.80s: invalid name",mailbox);
 
361
    mm_log (mbx,ERROR);
 
362
  }
 
363
                                /* create underlying file */
 
364
  else if (dummy_create (stream,s)) {
 
365
                                /* done if made directory */
 
366
    if ((s = strrchr (s,'\\')) && !s[1]) return T;
 
367
    if ((fd = open (mbx,O_WRONLY|O_BINARY,NIL)) < 0) {
 
368
      sprintf (tmp,"Can't reopen mailbox node %.80s: %s",mbx,strerror (errno));
 
369
      mm_log (tmp,ERROR);
 
370
      unlink (mbx);             /* delete the file */
 
371
    }
 
372
    else {
 
373
      memset (tmp,'\0',HDRSIZE);/* initialize header */
 
374
      sprintf (s = tmp,"*mbx*\015\012%08lx00000000\015\012",
 
375
               (unsigned long) time (0));
 
376
      for (i = 0; i < NUSERFLAGS; ++i)
 
377
        sprintf (s += strlen (s),"%s\015\012",
 
378
                 (stream && stream->user_flags[i]) ? stream->user_flags[i] :
 
379
                 "");
 
380
      if (write (fd,tmp,HDRSIZE) != HDRSIZE) {
 
381
        sprintf (tmp,"Can't initialize mailbox node %.80s: %s",
 
382
                 mbx,strerror (errno));
 
383
        mm_log (tmp,ERROR);
 
384
        unlink (mbx);           /* delete the file */
 
385
      }
 
386
      else ret = T;             /* success */
 
387
      close (fd);               /* close file */
 
388
    }
 
389
  }
 
390
  return ret;
 
391
}
 
392
 
 
393
 
 
394
/* MBX mail delete mailbox
 
395
 * Accepts: MAIL stream
 
396
 *          mailbox name to delete
 
397
 * Returns: T on success, NIL on failure
 
398
 */
 
399
 
 
400
long mbx_delete (MAILSTREAM *stream,char *mailbox)
 
401
{
 
402
  return mbx_rename (stream,mailbox,NIL);
 
403
}
 
404
 
 
405
/* MBX mail rename mailbox
 
406
 * Accepts: MAIL stream
 
407
 *          old mailbox name
 
408
 *          new mailbox name (or NIL for delete)
 
409
 * Returns: T on success, NIL on failure
 
410
 */
 
411
 
 
412
long mbx_rename (MAILSTREAM *stream,char *old,char *newname)
 
413
{
 
414
  long ret = LONGT;
 
415
  char c,*s,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
 
416
  int fd,ld;
 
417
  struct stat sbuf;
 
418
  if (!dummy_file (file,old) ||
 
419
      (newname && (!((s = mailboxfile (tmp,newname)) && *s) ||
 
420
                   ((s = strrchr (tmp,'\\')) && !s[1])))) {
 
421
    sprintf (tmp,newname ?
 
422
             "Can't rename mailbox %.80s to %.80s: invalid name" :
 
423
             "Can't delete mailbox %.80s: invalid name",
 
424
             old,newname);
 
425
    mm_log (tmp,ERROR);
 
426
    return NIL;
 
427
  }
 
428
  else if ((fd = open (file,O_RDWR|O_BINARY,NIL)) < 0) {
 
429
    sprintf (tmp,"Can't open mailbox %.80s: %s",old,strerror (errno));
 
430
    mm_log (tmp,ERROR);
 
431
    return NIL;
 
432
  }
 
433
                                /* get parse/append permission */
 
434
  if ((ld = lockname (lock,file,LOCK_EX)) < 0) {
 
435
    mm_log ("Unable to lock rename mailbox",ERROR);
 
436
    return NIL;
 
437
  }
 
438
                                /* lock out other users */
 
439
  if (flock (fd,LOCK_EX|LOCK_NB)) {
 
440
    close (fd);                 /* couldn't lock, give up on it then */
 
441
    sprintf (tmp,"Mailbox %.80s is in use by another process",old);
 
442
    mm_log (tmp,ERROR);
 
443
    unlockfd (ld,lock);         /* release exclusive parse/append permission */
 
444
    return NIL;
 
445
  }
 
446
 
 
447
  if (newname) {                /* want rename? */
 
448
                                /* found superior to destination name? */
 
449
    if ((s = strrchr (tmp,'\\')) && (s != tmp) &&
 
450
        ((tmp[1] != ':') || (s != tmp + 2))) {
 
451
      c = s[1];                 /* remember character after delimiter */
 
452
      *s = s[1] = '\0';         /* tie off name at delimiter */
 
453
                                /* name doesn't exist, create it */
 
454
      if (stat (tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
 
455
        *s = '\\';              /* restore delimiter */
 
456
        if (!dummy_create (stream,tmp)) ret = NIL;
 
457
      }
 
458
      else *s = '\\';           /* restore delimiter */
 
459
      s[1] = c;                 /* restore character after delimiter */
 
460
    }
 
461
    flock (fd,LOCK_UN);         /* release lock on the file */
 
462
    close (fd);                 /* pacify NTFS */
 
463
                                /* rename the file */
 
464
    if (ret && rename (file,tmp)) {
 
465
      sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %s",old,newname,
 
466
               strerror (errno));
 
467
      mm_log (tmp,ERROR);
 
468
      ret = NIL;                /* set failure */
 
469
    }
 
470
  }
 
471
  else {
 
472
    flock (fd,LOCK_UN);         /* release lock on the file */
 
473
    close (fd);                 /* pacify NTFS */
 
474
    if (unlink (file)) {
 
475
      sprintf (tmp,"Can't delete mailbox %.80s: %s",old,strerror (errno));
 
476
      mm_log (tmp,ERROR);
 
477
      ret = NIL;                /* set failure */
 
478
    }
 
479
  }
 
480
  unlockfd (ld,lock);           /* release exclusive parse/append permission */
 
481
                                /* recreate file if renamed INBOX */
 
482
  if (ret && !compare_cstring (old,"INBOX")) mbx_create (NIL,"INBOX");
 
483
  return ret;                   /* return success */
 
484
}
 
485
 
 
486
/* MBX mail open
 
487
 * Accepts: stream to open
 
488
 * Returns: stream on success, NIL on failure
 
489
 */
 
490
 
 
491
MAILSTREAM *mbx_open (MAILSTREAM *stream)
 
492
{
 
493
  int fd,ld;
 
494
  short silent;
 
495
  char tmp[MAILTMPLEN];
 
496
  if (!stream) return &mbxproto;/* return prototype for OP_PROTOTYPE call */
 
497
  if (stream->local) fatal ("mbx recycle stream");
 
498
                                /* canonicalize the mailbox name */
 
499
  if (!dummy_file (tmp,stream->mailbox)) {
 
500
    sprintf (tmp,"Can't open - invalid name: %.80s",stream->mailbox);
 
501
    mm_log (tmp,ERROR);
 
502
  }
 
503
  if (stream->rdonly ||
 
504
      (fd = open (tmp,O_RDWR|O_BINARY,NIL)) < 0) {
 
505
    if ((fd = open (tmp,O_RDONLY|O_BINARY,NIL)) < 0) {
 
506
      sprintf (tmp,"Can't open mailbox: %s",strerror (errno));
 
507
      mm_log (tmp,ERROR);
 
508
      return NIL;
 
509
    }
 
510
    else if (!stream->rdonly) { /* got it, but readonly */
 
511
      mm_log ("Can't get write access to mailbox, access is readonly",WARN);
 
512
      stream->rdonly = T;
 
513
    }
 
514
  }
 
515
 
 
516
  stream->local = memset (fs_get (sizeof (MBXLOCAL)),NIL,sizeof (MBXLOCAL));
 
517
  LOCAL->fd = fd;               /* bind the file */
 
518
  LOCAL->ld = -1;               /* no flaglock */
 
519
  LOCAL->buf = (char *) fs_get (CHUNKSIZE);
 
520
  LOCAL->buflen = CHUNKSIZE - 1;
 
521
                                /* note if an INBOX or not */
 
522
  stream->inbox = !compare_cstring (stream->mailbox,"INBOX");
 
523
  fs_give ((void **) &stream->mailbox);
 
524
  stream->mailbox = cpystr (tmp);
 
525
                                /* get parse/append permission */
 
526
  if ((ld = lockname (tmp,stream->mailbox,LOCK_EX)) < 0) {
 
527
    mm_log ("Unable to lock open mailbox",ERROR);
 
528
    return NIL;
 
529
  }
 
530
  flock (LOCAL->fd,LOCK_SH);    /* lock the file */
 
531
  unlockfd (ld,tmp);            /* release shared parse permission */
 
532
  LOCAL->filesize = HDRSIZE;    /* initialize parsed file size */
 
533
  LOCAL->filetime = 0;          /* time not set up yet */
 
534
  LOCAL->expok = LOCAL->flagcheck = NIL;
 
535
  stream->sequence++;           /* bump sequence number */
 
536
                                /* parse mailbox */
 
537
  stream->nmsgs = stream->recent = 0;
 
538
  silent = stream->silent;      /* defer events */
 
539
  stream->silent = T;
 
540
  if (mbx_ping (stream) && !stream->nmsgs)
 
541
    mm_log ("Mailbox is empty",(long) NIL);
 
542
  stream->silent = silent;      /* now notify upper level */
 
543
  mail_exists (stream,stream->nmsgs);
 
544
  mail_recent (stream,stream->recent);
 
545
  if (!LOCAL) return NIL;       /* failure if stream died */
 
546
  stream->perm_seen = stream->perm_deleted = stream->perm_flagged =
 
547
    stream->perm_answered = stream->perm_draft = stream->rdonly ? NIL : T;
 
548
  stream->perm_user_flags = stream->rdonly ? NIL : 0xffffffff;
 
549
  stream->kwd_create = (stream->user_flags[NUSERFLAGS-1] || stream->rdonly) ?
 
550
    NIL : T;                    /* can we create new user flags? */
 
551
  return stream;                /* return stream to caller */
 
552
}
 
553
 
 
554
/* MBX mail close
 
555
 * Accepts: MAIL stream
 
556
 *          close options
 
557
 */
 
558
 
 
559
void mbx_close (MAILSTREAM *stream,long options)
 
560
{
 
561
  if (stream && LOCAL) {        /* only if a file is open */
 
562
    int silent = stream->silent;
 
563
    stream->silent = T;         /* note this stream is dying */
 
564
                                /* do an expunge if requested */
 
565
    if (options & CL_EXPUNGE) mbx_expunge (stream,NIL,NIL);
 
566
    else {                      /* otherwise do a checkpoint to purge */
 
567
      LOCAL->expok = T;         /*  possible expunged messages */
 
568
      mbx_ping (stream);
 
569
    }
 
570
    stream->silent = silent;    /* restore previous status */
 
571
    mbx_abort (stream);
 
572
  }
 
573
}
 
574
 
 
575
 
 
576
/* MBX mail abort stream
 
577
 * Accepts: MAIL stream
 
578
 */
 
579
 
 
580
void mbx_abort (MAILSTREAM *stream)
 
581
{
 
582
  if (stream && LOCAL) {        /* only if a file is open */
 
583
    flock (LOCAL->fd,LOCK_UN);  /* unlock local file */
 
584
    close (LOCAL->fd);          /* close the local file */
 
585
                                /* free local text buffer */
 
586
    if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
 
587
                                /* nuke the local data */
 
588
    fs_give ((void **) &stream->local);
 
589
    stream->dtb = NIL;          /* log out the DTB */
 
590
  }
 
591
}
 
592
 
 
593
 
 
594
/* MBX mail fetch flags
 
595
 * Accepts: MAIL stream
 
596
 *          sequence
 
597
 *          option flags
 
598
 * Sniffs at file to see if some other process changed the flags
 
599
 */
 
600
 
 
601
void mbx_flags (MAILSTREAM *stream,char *sequence,long flags)
 
602
{
 
603
  MESSAGECACHE *elt;
 
604
  unsigned long i;
 
605
  if (mbx_ping (stream) &&      /* ping mailbox, get new status for messages */
 
606
      ((flags & FT_UID) ? mail_uid_sequence (stream,sequence) :
 
607
       mail_sequence (stream,sequence)))
 
608
    for (i = 1; i <= stream->nmsgs; i++) 
 
609
      if ((elt = mail_elt (stream,i))->sequence && !elt->valid)
 
610
        mbx_elt (stream,i,NIL);
 
611
}
 
612
 
 
613
/* MBX mail fetch message header
 
614
 * Accepts: MAIL stream
 
615
 *          message # to fetch
 
616
 *          pointer to returned header text length
 
617
 *          option flags
 
618
 * Returns: message header in RFC822 format
 
619
 */
 
620
 
 
621
char *mbx_header (MAILSTREAM *stream,unsigned long msgno,unsigned long *length,
 
622
                  long flags)
 
623
{
 
624
  unsigned long i;
 
625
  char *s;
 
626
  *length = 0;                  /* default to empty */
 
627
  if (flags & FT_UID) return "";/* UID call "impossible" */
 
628
                                /* get header position, possibly header */
 
629
  i = mbx_hdrpos (stream,msgno,length,&s);
 
630
  if (!s) {                     /* mbx_hdrpos() returned header? */
 
631
    lseek (LOCAL->fd,i,L_SET);  /* no, get to header position */
 
632
                                /* is buffer big enough? */
 
633
    if (*length > LOCAL->buflen) {
 
634
      fs_give ((void **) &LOCAL->buf);
 
635
      LOCAL->buf = (char *) fs_get ((LOCAL->buflen = *length) + 1);
 
636
    }
 
637
                                /* slurp the data */
 
638
    read (LOCAL->fd,s = LOCAL->buf,*length);
 
639
  }
 
640
  s[*length] = '\0';            /* tie off string */
 
641
  return s;
 
642
}
 
643
 
 
644
/* MBX mail fetch message text (body only)
 
645
 * Accepts: MAIL stream
 
646
 *          message # to fetch
 
647
 *          pointer to returned header text length
 
648
 *          option flags
 
649
 * Returns: T, always
 
650
 */
 
651
 
 
652
long mbx_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
 
653
{
 
654
  FDDATA d;
 
655
  unsigned long i,j;
 
656
  MESSAGECACHE *elt;
 
657
                                /* UID call "impossible" */
 
658
  if (flags & FT_UID) return NIL;
 
659
                                /* get message status */
 
660
  elt = mbx_elt (stream,msgno,NIL);
 
661
                                /* if message not seen */
 
662
  if (!(flags & FT_PEEK) && !elt->seen && mbx_flaglock (stream)) {
 
663
    elt->seen = T;              /* mark message as seen */
 
664
                                /* recalculate status */
 
665
    mbx_update_status (stream,msgno,NIL);
 
666
    mm_flags (stream,msgno);
 
667
                                /* update flags */
 
668
    mbx_flag (stream,NIL,NIL,NIL);
 
669
  }
 
670
  if (LOCAL) {                  /* find header position */
 
671
    i = mbx_hdrpos (stream,msgno,&j,NIL);
 
672
    d.fd = LOCAL->fd;           /* set up file descriptor */
 
673
    d.pos = i + j;
 
674
    d.chunk = LOCAL->buf;       /* initial buffer chunk */
 
675
    d.chunksize = CHUNKSIZE;
 
676
    INIT (bs,fd_string,&d,elt->rfc822_size - j);
 
677
  }
 
678
  else i = 0;                   /* mbx_flaglock() could have aborted */
 
679
  return T;                     /* success */
 
680
}
 
681
 
 
682
/* MBX mail modify flags
 
683
 * Accepts: MAIL stream
 
684
 *          sequence
 
685
 *          flag(s)
 
686
 *          option flags
 
687
 */
 
688
 
 
689
void mbx_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
 
690
{
 
691
  struct utimbuf times;
 
692
  struct stat sbuf;
 
693
                                /* make sure the update takes */
 
694
  if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld >= 0)) {
 
695
    fsync (LOCAL->fd);
 
696
    fstat (LOCAL->fd,&sbuf);    /* get current write time */
 
697
    times.modtime = LOCAL->filetime = sbuf.st_mtime;
 
698
                                /* update header */
 
699
    if ((LOCAL->ffuserflag < NUSERFLAGS) &&
 
700
        stream->user_flags[LOCAL->ffuserflag]) mbx_update_header (stream);
 
701
    times.actime = time (0);    /* make sure read comes after all that */
 
702
    utime (stream->mailbox,&times);
 
703
    unlockfd (LOCAL->ld,LOCAL->lock);
 
704
    LOCAL->ld = -1;
 
705
  }
 
706
}
 
707
 
 
708
 
 
709
/* MBX mail per-message modify flags
 
710
 * Accepts: MAIL stream
 
711
 *          message cache element
 
712
 */
 
713
 
 
714
void mbx_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
 
715
{
 
716
  if (mbx_flaglock (stream)) mbx_update_status (stream,elt->msgno,NIL);
 
717
}
 
718
 
 
719
/* MBX mail ping mailbox
 
720
 * Accepts: MAIL stream
 
721
 * Returns: T if stream still alive, NIL if not
 
722
 */
 
723
 
 
724
long mbx_ping (MAILSTREAM *stream)
 
725
{
 
726
  unsigned long i,pos;
 
727
  long ret = NIL;
 
728
  int ld;
 
729
  char lock[MAILTMPLEN];
 
730
  MESSAGECACHE *elt;
 
731
  struct stat sbuf;
 
732
  if (stream && LOCAL) {        /* only if stream already open */
 
733
    ret = LONGT;                /* assume OK */
 
734
    fstat (LOCAL->fd,&sbuf);    /* get current file poop */
 
735
                                /* allow expunge if permitted at ping */
 
736
    if (mail_parameters (NIL,GET_EXPUNGEATPING,NIL)) LOCAL->expok = T;
 
737
                                /* if external modification */
 
738
    if (LOCAL->filetime && (LOCAL->filetime < sbuf.st_mtime))
 
739
      LOCAL->flagcheck = T;     /* upgrade to flag checking */
 
740
                                /* new mail or flagcheck handling needed? */
 
741
    if (((i = (sbuf.st_size - LOCAL->filesize)) || LOCAL->flagcheck ||
 
742
         !stream->nmsgs) &&
 
743
        ((ld = lockname (lock,stream->mailbox,LOCK_EX)) >= 0)) {
 
744
      if (LOCAL->flagcheck) {   /* sweep mailbox for changed message status */
 
745
        if (ret = mbx_parse (stream)) {
 
746
          LOCAL->filetime = sbuf.st_mtime;
 
747
          for (i = 1; i <= stream->nmsgs; )
 
748
            if (mbx_elt (stream,i,LOCAL->expok)) ++i;
 
749
          LOCAL->flagcheck =NIL;/* got all the updates */
 
750
        }
 
751
      }
 
752
      else if (i) ret = mbx_parse (stream);
 
753
      unlockfd (ld,lock);       /* release shared parse/append permission */
 
754
    }
 
755
    if (ret) {                  /* must still be alive */
 
756
      if (!LOCAL->expunged)     /* look for holes if none known yet */
 
757
        for (i = 1, pos = HDRSIZE;
 
758
             !LOCAL->expunged && (i <= stream->nmsgs);
 
759
             i++, pos += elt->private.special.text.size + elt->rfc822_size)
 
760
          if ((elt = mail_elt (stream,i))->private.special.offset != pos)
 
761
            LOCAL->expunged = T;/* found a hole */
 
762
                                /* burp any holes */
 
763
      if (LOCAL->expunged && !stream->rdonly) {
 
764
        if (mbx_rewrite (stream,&i,NIL)) fatal ("expunge on check");
 
765
        if (i) {                /* any space reclaimed? */
 
766
          LOCAL->expunged = NIL;/* no more pending expunge */
 
767
          sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",i);
 
768
          mm_log (LOCAL->buf,(long) NIL);
 
769
        }
 
770
      }
 
771
      LOCAL->expok = NIL;       /* no more expok */
 
772
    }
 
773
  }
 
774
  return ret;                   /* return result of the parse */
 
775
}
 
776
 
 
777
/* MBX mail check mailbox (reparses status too)
 
778
 * Accepts: MAIL stream
 
779
 */
 
780
 
 
781
void mbx_check (MAILSTREAM *stream)
 
782
{
 
783
  if (LOCAL) LOCAL->expok = T;  /* mark that a check is desired */
 
784
  if (mbx_ping (stream)) mm_log ("Check completed",(long) NIL);
 
785
}
 
786
 
 
787
 
 
788
/* MBX mail expunge mailbox
 
789
 * Accepts: MAIL stream
 
790
 *          sequence to expunge if non-NIL
 
791
 *          expunge options
 
792
 * Returns: T if success, NIL if failure
 
793
 */
 
794
 
 
795
long mbx_expunge (MAILSTREAM *stream,char *sequence,long options)
 
796
{
 
797
  long ret;
 
798
  unsigned long nexp,reclaimed;
 
799
  if (ret = sequence ? ((options & EX_UID) ?
 
800
                        mail_uid_sequence (stream,sequence) :
 
801
                        mail_sequence (stream,sequence)) : LONGT) {
 
802
    if (!mbx_ping (stream));    /* do nothing if stream dead */
 
803
    else if (stream->rdonly)    /* won't do on readonly files! */
 
804
      mm_log ("Expunge ignored on readonly mailbox",WARN);
 
805
                                /* if expunged any messages */
 
806
    else if (nexp = mbx_rewrite (stream,&reclaimed,sequence ? -1 : 1)) {
 
807
      sprintf (LOCAL->buf,"Expunged %lu messages",nexp);
 
808
      mm_log (LOCAL->buf,(long) NIL);
 
809
    }
 
810
    else if (reclaimed) {        /* or if any prior expunged space reclaimed */
 
811
      sprintf (LOCAL->buf,"Reclaimed %lu bytes of expunged space",reclaimed);
 
812
      mm_log (LOCAL->buf,(long) NIL);
 
813
    }
 
814
    else mm_log ("No messages deleted, so no update needed",(long) NIL);
 
815
  }
 
816
  return ret;
 
817
}
 
818
 
 
819
/* MBX mail copy message(s)
 
820
 * Accepts: MAIL stream
 
821
 *          sequence
 
822
 *          destination mailbox
 
823
 *          copy options
 
824
 * Returns: T if success, NIL if failed
 
825
 */
 
826
 
 
827
long mbx_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
 
828
{
 
829
  struct stat sbuf;
 
830
  struct utimbuf times;
 
831
  MESSAGECACHE *elt;
 
832
  unsigned long i,j,k,m;
 
833
  long ret = LONGT;
 
834
  int fd,ld;
 
835
  char *s,*t,file[MAILTMPLEN],lock[MAILTMPLEN];
 
836
  mailproxycopy_t pc =
 
837
    (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
 
838
  copyuid_t cu = (copyuid_t) mail_parameters (NIL,GET_COPYUID,NIL);
 
839
  SEARCHSET *source = cu ? mail_newsearchset () : NIL;
 
840
  SEARCHSET *dest = cu ? mail_newsearchset () : NIL;
 
841
  MAILSTREAM *dstream = NIL;
 
842
  if (!((options & CP_UID) ? mail_uid_sequence (stream,sequence) :
 
843
        mail_sequence (stream,sequence))) return NIL;
 
844
                                /* make sure valid mailbox */
 
845
  if ((fd = mbx_isvalid (&dstream,mailbox,file,
 
846
                         cu ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
 
847
    switch (errno) {
 
848
    case ENOENT:                /* no such file? */
 
849
      mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
 
850
      return NIL;
 
851
    case EINVAL:
 
852
      if (pc) return (*pc) (stream,sequence,mailbox,options);
 
853
      sprintf (LOCAL->buf,"Invalid MBX-format mailbox name: %.80s",mailbox);
 
854
      mm_log (LOCAL->buf,ERROR);
 
855
      return NIL;
 
856
    default:
 
857
      if (pc) return (*pc) (stream,sequence,mailbox,options);
 
858
      sprintf (LOCAL->buf,"Not a MBX-format mailbox: %.80s",mailbox);
 
859
      mm_log (LOCAL->buf,ERROR);
 
860
      return NIL;
 
861
    }
 
862
                                /* got file? */  
 
863
  if ((fd = open (dummy_file (file,mailbox),O_RDWR|O_CREAT|O_BINARY,
 
864
                  S_IREAD|S_IWRITE)) < 0) {
 
865
    sprintf (LOCAL->buf,"Unable to open copy mailbox: %s",strerror (errno));
 
866
    mm_log (LOCAL->buf,ERROR);
 
867
    return NIL;
 
868
  }
 
869
  mm_critical (stream);         /* go critical */
 
870
                                /* get parse/append permission */
 
871
  if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
 
872
    mm_log ("Unable to lock copy mailbox",ERROR);
 
873
    mm_nocritical (stream);
 
874
    return NIL;
 
875
  }
 
876
  fstat (fd,&sbuf);             /* get current file size */
 
877
  lseek (fd,sbuf.st_size,L_SET);/* move to end of file */
 
878
 
 
879
                                /* for each requested message */
 
880
  for (i = 1; ret && (i <= stream->nmsgs); i++) 
 
881
    if ((elt = mail_elt (stream,i))->sequence) {
 
882
      lseek (LOCAL->fd,elt->private.special.offset +
 
883
             elt->private.special.text.size,L_SET);
 
884
      mail_date(LOCAL->buf,elt);/* build target header */
 
885
                                /* get target keyword mask */
 
886
      for (j = elt->user_flags, k = 0; j; )
 
887
        if (s = stream->user_flags[find_rightmost_bit (&j)])
 
888
          for (m = 0; (m < NUSERFLAGS) && (t = dstream->user_flags[m]); m++)
 
889
            if (!compare_cstring (s,t) && (k |= 1 << m)) break;
 
890
      sprintf (LOCAL->buf+strlen(LOCAL->buf),",%lu;%08lx%04x-%08lx\015\012",
 
891
               elt->rfc822_size,k,(unsigned)
 
892
               ((fSEEN * elt->seen) + (fDELETED * elt->deleted) +
 
893
                (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
 
894
                (fDRAFT * elt->draft)),cu ? ++dstream->uid_last : 0);
 
895
                                /* write target header */
 
896
      if (ret = (write (fd,LOCAL->buf,strlen (LOCAL->buf)) > 0)) {
 
897
        for (k = elt->rfc822_size; ret && (j = min (k,LOCAL->buflen)); k -= j){
 
898
          read (LOCAL->fd,LOCAL->buf,j);
 
899
          ret = write (fd,LOCAL->buf,j) >= 0;
 
900
        }
 
901
        if (cu) {               /* need to pass back new UID? */
 
902
          mail_append_set (source,mail_uid (stream,i));
 
903
          mail_append_set (dest,dstream->uid_last);
 
904
        }
 
905
      }
 
906
    }
 
907
 
 
908
                                /* make sure all the updates take */
 
909
  if (!(ret && (ret = !fsync (fd)))) {
 
910
    sprintf (LOCAL->buf,"Unable to write message: %s",strerror (errno));
 
911
    mm_log (LOCAL->buf,ERROR);
 
912
    ftruncate (fd,sbuf.st_size);
 
913
  }
 
914
  if (cu && ret) {              /* return sets if doing COPYUID */
 
915
    (*cu) (stream,mailbox,dstream->uid_validity,source,dest);
 
916
    lseek (fd,15,L_SET);        /* update UIDLAST */
 
917
    sprintf (LOCAL->buf,"%08lx",dstream->uid_last);
 
918
    write (fd,LOCAL->buf,8);
 
919
  }
 
920
  else {                        /* flush any sets we may have built */
 
921
    mail_free_searchset (&source);
 
922
    mail_free_searchset (&dest);
 
923
  }
 
924
                                /* set atime to now-1 if successful copy */
 
925
  if (ret) times.actime = time (0) - 1;
 
926
                                /* else preserved \Marked status */
 
927
  else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
 
928
         sbuf.st_atime : time (0);
 
929
  times.modtime = sbuf.st_mtime;/* preserve mtime */
 
930
  utime (file,&times);          /* set the times */
 
931
  unlockfd (ld,lock);           /* release exclusive parse/append permission */
 
932
  close (fd);                   /* close the file */
 
933
  mm_nocritical (stream);       /* release critical */
 
934
                                /* delete all requested messages */
 
935
  if (ret && (options & CP_MOVE) && mbx_flaglock (stream)) {
 
936
    for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
 
937
                                /* mark message deleted */
 
938
      mbx_elt (stream,i,NIL)->deleted = T;
 
939
                                /* recalculate status */
 
940
      mbx_update_status (stream,i,NIL);
 
941
    }
 
942
                                /* update flags */
 
943
    mbx_flag (stream,NIL,NIL,NIL);
 
944
  }
 
945
  if (dstream != stream) mail_close (dstream);
 
946
  return ret;
 
947
}
 
948
 
 
949
/* MBX mail append message from stringstruct
 
950
 * Accepts: MAIL stream
 
951
 *          destination mailbox
 
952
 *          append callback
 
953
 *          data for callback
 
954
 * Returns: T if append successful, else NIL
 
955
 */
 
956
 
 
957
long mbx_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
 
958
{
 
959
  struct stat sbuf;
 
960
  int fd,ld;
 
961
  char *flags,*date,tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
 
962
  struct utimbuf times;
 
963
  FILE *df;
 
964
  MESSAGECACHE elt;
 
965
  long f;
 
966
  unsigned long i,uf;
 
967
  STRING *message;
 
968
  long ret = NIL;
 
969
  MAILSTREAM *dstream = NIL;
 
970
  appenduid_t au = (appenduid_t) mail_parameters (NIL,GET_APPENDUID,NIL);
 
971
  SEARCHSET *dst = au ? mail_newsearchset () : NIL;
 
972
                                /* make sure valid mailbox */
 
973
                                /* make sure valid mailbox */
 
974
  if ((fd = mbx_isvalid (&dstream,mailbox,file,
 
975
                         au ? MBXISVALIDUID : MBXISVALIDNOUID)) < 0)
 
976
    switch (errno) {
 
977
    case ENOENT:                /* no such file? */
 
978
      if (compare_cstring (mailbox,"INBOX")) {
 
979
        mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
 
980
        return NIL;
 
981
      }
 
982
                                /* can create INBOX here */
 
983
      mbx_create (dstream = stream ? stream : &mbxproto,"INBOX");
 
984
      break;
 
985
    case EINVAL:
 
986
      sprintf (tmp,"Invalid MBX-format mailbox name: %.80s",mailbox);
 
987
      mm_log (tmp,ERROR);
 
988
      return NIL;
 
989
    default:
 
990
      sprintf (tmp,"Not a MBX-format mailbox: %.80s",mailbox);
 
991
      mm_log (tmp,ERROR);
 
992
      return NIL;
 
993
    }
 
994
 
 
995
                                /* get first message */
 
996
  if (!(*af) (dstream,data,&flags,&date,&message)) close (fd);
 
997
                                /* get parse/append permission */
 
998
  else if (flock (fd,LOCK_SH) || ((ld = lockname (lock,file,LOCK_EX)) < 0)) {
 
999
    mm_log ("Unable to lock append mailbox",ERROR);
 
1000
    close (fd);
 
1001
  }
 
1002
  else if (!(df = fdopen (fd,"r+b"))) {
 
1003
    MM_LOG ("Unable to reopen append mailbox",ERROR);
 
1004
    unlockfd (ld,lock);
 
1005
    close (fd);
 
1006
  }
 
1007
  else {
 
1008
    mm_critical (dstream);      /* go critical */
 
1009
    fstat (fd,&sbuf);           /* get current file size */
 
1010
    fseek (df,sbuf.st_size,SEEK_SET);
 
1011
    errno = 0;
 
1012
    for (ret = LONGT; ret && message; ) {
 
1013
      if (!SIZE (message)) {    /* guard against zero-length */
 
1014
        mm_log ("Append of zero-length message",ERROR);
 
1015
        ret = NIL;
 
1016
        break;
 
1017
      }
 
1018
      f = mail_parse_flags (dstream,flags,&uf);
 
1019
      if (date) {               /* parse date if given */
 
1020
        if (!mail_parse_date (&elt,date)) {
 
1021
          sprintf (tmp,"Bad date in append: %.80s",date);
 
1022
          mm_log (tmp,ERROR);
 
1023
          ret = NIL;            /* mark failure */
 
1024
          break;
 
1025
        }
 
1026
        mail_date (tmp,&elt);   /* write preseved date */
 
1027
      }
 
1028
      else internal_date (tmp); /* get current date in IMAP format */
 
1029
                                /* write header */
 
1030
      if (fprintf (df,"%s,%lu;%08lx%04lx-%08lx\015\012",tmp,i = SIZE (message),
 
1031
                   uf,(unsigned long) f,++dstream->uid_last) < 0) ret = NIL;
 
1032
      else {                    /* write message */
 
1033
        size_t j;
 
1034
        if (!message->cursize) SETPOS (message,GETPOS (message));
 
1035
        while (i && (j = fwrite (message->curpos,1,message->cursize,df))) {
 
1036
          i -= j;
 
1037
          SETPOS (message,GETPOS (message) + j);
 
1038
        }
 
1039
                                /* get next message */
 
1040
        if (i || !(*af) (dstream,data,&flags,&date,&message)) ret = NIL;
 
1041
        else if (au) mail_append_set (dst,dstream->uid_last);
 
1042
      }
 
1043
    }
 
1044
 
 
1045
                                /* if error... */
 
1046
    if (!ret || (fflush (df) == EOF)) {
 
1047
                                /* revert file */
 
1048
      ftruncate (fd,sbuf.st_size);
 
1049
      close (fd);               /* make sure fclose() doesn't corrupt us */
 
1050
      if (errno) {
 
1051
        sprintf (tmp,"Message append failed: %s",strerror (errno));
 
1052
        mm_log (tmp,ERROR);
 
1053
      }
 
1054
      ret = NIL;
 
1055
    }
 
1056
                                /* return sets if doing APPENDUID */
 
1057
    if (au && ret) (*au) (mailbox,dstream->uid_validity,dst);
 
1058
    else mail_free_searchset (&dst);
 
1059
    fseek (df,15,SEEK_SET);     /* update UIDLAST */
 
1060
    fprintf (df,"%08lx",dstream->uid_last);
 
1061
    if (ret) times.actime = time (0) - 1;
 
1062
                                /* else preserve \Marked status */
 
1063
    else times.actime = (sbuf.st_ctime > sbuf.st_atime) ?
 
1064
           sbuf.st_atime : time (0);
 
1065
                                /* preserve mtime */
 
1066
    times.modtime = sbuf.st_mtime;
 
1067
    utime (file,&times);        /* set the times */
 
1068
    fclose (df);                /* close the file */
 
1069
    unlockfd (ld,lock);         /* release exclusive parse/append permission */
 
1070
    mm_nocritical (dstream);    /* release critical */
 
1071
  }
 
1072
  if (dstream != stream) mail_close (dstream);
 
1073
  return ret;
 
1074
}
 
1075
 
 
1076
/* Internal routines */
 
1077
 
 
1078
 
 
1079
/* MBX mail parse mailbox
 
1080
 * Accepts: MAIL stream
 
1081
 * Returns: T if parse OK
 
1082
 *          NIL if failure, stream aborted
 
1083
 */
 
1084
 
 
1085
long mbx_parse (MAILSTREAM *stream)
 
1086
{
 
1087
  struct stat sbuf;
 
1088
  MESSAGECACHE *elt = NIL;
 
1089
  unsigned char c,*s,*t,*x;
 
1090
  char tmp[MAILTMPLEN];
 
1091
  unsigned long i,j,k,m;
 
1092
  off_t curpos = LOCAL->filesize;
 
1093
  unsigned long nmsgs = stream->nmsgs;
 
1094
  unsigned long recent = stream->recent;
 
1095
  unsigned long lastuid = 0;
 
1096
  short dirty = NIL;
 
1097
  short added = NIL;
 
1098
  short silent = stream->silent;
 
1099
  short uidwarn = T;
 
1100
  fstat (LOCAL->fd,&sbuf);      /* get status */
 
1101
  if (sbuf.st_size < curpos) {  /* sanity check */
 
1102
    sprintf (tmp,"Mailbox shrank from %lu to %lu!",
 
1103
             (unsigned long) curpos,(unsigned long) sbuf.st_size);
 
1104
    mm_log (tmp,ERROR);
 
1105
    mbx_abort (stream);
 
1106
    return NIL;
 
1107
  }
 
1108
  lseek (LOCAL->fd,0,L_SET);    /* rewind file */
 
1109
                                /* read internal header */
 
1110
  read (LOCAL->fd,LOCAL->buf,HDRSIZE);
 
1111
  LOCAL->buf[HDRSIZE] = '\0';   /* tie off header */
 
1112
  c = LOCAL->buf[15];           /* save first character of last UID */
 
1113
  LOCAL->buf[15] = '\0';
 
1114
                                /* parse UID validity */
 
1115
  stream->uid_validity = strtoul (LOCAL->buf + 7,NIL,16);
 
1116
  LOCAL->buf[15] = c;           /* restore first character of last UID */
 
1117
                                /* parse last UID */
 
1118
  i = strtoul (LOCAL->buf + 15,NIL,16);
 
1119
  stream->uid_last = stream->rdonly ? max (i,stream->uid_last) : i;
 
1120
                                /* parse user flags */
 
1121
  for (i = 0, s = LOCAL->buf + 25;
 
1122
       (i < NUSERFLAGS) && (t = strchr (s,'\015')) && (t - s);
 
1123
       i++, s = t + 2) {
 
1124
    *t = '\0';                  /* tie off flag */
 
1125
    if (!stream->user_flags[i] && (strlen (s) <= MAXUSERFLAG))
 
1126
      stream->user_flags[i] = cpystr (s);
 
1127
  }
 
1128
  LOCAL->ffuserflag = (int) i;  /* first free user flag */
 
1129
 
 
1130
  stream->silent = T;           /* don't pass up mm_exists() events yet */
 
1131
  while (sbuf.st_size - curpos){/* while there is stuff to parse */
 
1132
                                /* get to that position in the file */
 
1133
    lseek (LOCAL->fd,curpos,L_SET);
 
1134
    if ((i = read (LOCAL->fd,LOCAL->buf,64)) <= 0) {
 
1135
      sprintf (tmp,"Unable to read internal header at %lu, size = %lu: %s",
 
1136
               (unsigned long) curpos,(unsigned long) sbuf.st_size,
 
1137
               i ? strerror (errno) : "no data read");
 
1138
      mm_log (tmp,ERROR);
 
1139
      mbx_abort (stream);
 
1140
      return NIL;
 
1141
    }
 
1142
    LOCAL->buf[i] = '\0';       /* tie off buffer just in case */
 
1143
    if (!((s = strchr (LOCAL->buf,'\015')) && (s[1] == '\012'))) {
 
1144
      sprintf (tmp,"Unable to find CRLF at %lu in %lu bytes, text: %.80s",
 
1145
               (unsigned long) curpos,i,(char *) LOCAL->buf);
 
1146
      mm_log (tmp,ERROR);
 
1147
      mbx_abort (stream);
 
1148
      return NIL;
 
1149
    }
 
1150
    *s = '\0';                  /* tie off header line */
 
1151
    i = (s + 2) - LOCAL->buf;   /* note start of text offset */
 
1152
    if (!((s = strchr (LOCAL->buf,',')) && (t = strchr (s+1,';')))) {
 
1153
      sprintf (tmp,"Unable to parse internal header at %lu: %.80s",
 
1154
               (unsigned long) curpos,(char *) LOCAL->buf);
 
1155
      mm_log (tmp,ERROR);
 
1156
      mbx_abort (stream);
 
1157
      return NIL;
 
1158
    }
 
1159
    if (!(isxdigit (t[1]) && isxdigit (t[2]) && isxdigit (t[3]) &&
 
1160
          isxdigit (t[4]) && isxdigit (t[5]) && isxdigit (t[6]) &&
 
1161
          isxdigit (t[7]) && isxdigit (t[8]) && isxdigit (t[9]) &&
 
1162
          isxdigit (t[10]) && isxdigit (t[11]) && isxdigit (t[12]))) {
 
1163
      sprintf (tmp,"Unable to parse message flags at %lu: %.80s",
 
1164
               (unsigned long) curpos,(char *) LOCAL->buf);
 
1165
      mm_log (tmp,ERROR);
 
1166
      mbx_abort (stream);
 
1167
      return NIL;
 
1168
    }
 
1169
    if ((t[13] != '-') || t[22] ||
 
1170
        !(isxdigit (t[14]) && isxdigit (t[15]) && isxdigit (t[16]) &&
 
1171
          isxdigit (t[17]) && isxdigit (t[18]) && isxdigit (t[19]) &&
 
1172
          isxdigit (t[20]) && isxdigit (t[21]))) {
 
1173
      sprintf (tmp,"Unable to parse message UID at %lu: %.80s",
 
1174
               (unsigned long) curpos,(char *) LOCAL->buf);
 
1175
      mm_log (tmp,ERROR);
 
1176
      mbx_abort (stream);
 
1177
      return NIL;
 
1178
    }
 
1179
 
 
1180
    *s++ = '\0'; *t++ = '\0';   /* break up fields */
 
1181
                                /* get message size */
 
1182
    if (!(j = strtoul (s,(char **) &x,10)) && (!(x && *x))) {
 
1183
      sprintf (tmp,"Unable to parse message size at %lu: %.80s,%.80s;%.80s",
 
1184
               (unsigned long) curpos,(char *) LOCAL->buf,(char *) s,
 
1185
               (char *) t);
 
1186
      mm_log (tmp,ERROR);
 
1187
      mbx_abort (stream);
 
1188
      return NIL;
 
1189
    }
 
1190
                                /* make sure didn't run off end of file */
 
1191
    if (((off_t) (curpos + i + j)) > sbuf.st_size) {
 
1192
      sprintf (tmp,"Last message (at %lu) runs past end of file (%lu > %lu)",
 
1193
               (unsigned long) curpos,(unsigned long) (curpos + i + j),
 
1194
               (unsigned long) sbuf.st_size);
 
1195
      mm_log (tmp,ERROR);
 
1196
      mbx_abort (stream);
 
1197
      return NIL;
 
1198
    }
 
1199
                                /* parse UID */
 
1200
    if ((m = strtoul (t+13,NIL,16)) &&
 
1201
        ((m <= lastuid) || (m > stream->uid_last))) {
 
1202
      if (uidwarn) {
 
1203
        sprintf (tmp,"Invalid UID %08lx in message %lu, rebuilding UIDs",
 
1204
                 m,nmsgs+1);
 
1205
        mm_log (tmp,WARN);
 
1206
        uidwarn = NIL;
 
1207
                                /* restart UID validity */
 
1208
        stream->uid_validity = (unsigned long) time (0);
 
1209
      }
 
1210
      m = 0;                    /* lose this UID */
 
1211
      dirty = T;                /* mark dirty, set new lastuid */
 
1212
      stream->uid_last = lastuid;
 
1213
    }
 
1214
 
 
1215
    t[12] = '\0';               /* parse system flags */
 
1216
    if ((k = strtoul (t+8,NIL,16)) & fEXPUNGED) {
 
1217
      if (m) lastuid = m;       /* expunge message, update last UID seen */
 
1218
      else {                    /* no UID assigned? */
 
1219
        lastuid = ++stream->uid_last;
 
1220
        dirty = T;
 
1221
      }
 
1222
    }
 
1223
    else {                      /* not expunged, swell the cache */
 
1224
      added = T;                /* note that a new message was added */
 
1225
      mail_exists (stream,++nmsgs);
 
1226
                                /* instantiate an elt for this message */
 
1227
      (elt = mail_elt (stream,nmsgs))->valid = T;
 
1228
                                /* parse the date */
 
1229
      if (!mail_parse_date (elt,LOCAL->buf)) {
 
1230
        sprintf (tmp,"Unable to parse message date at %lu: %.80s",
 
1231
                 (unsigned long) curpos,(char *) LOCAL->buf);
 
1232
        mm_log (tmp,ERROR);
 
1233
        mbx_abort (stream);
 
1234
        return NIL;
 
1235
      }
 
1236
                                /* note file offset of header */
 
1237
      elt->private.special.offset = curpos;
 
1238
                                /* and internal header size */
 
1239
      elt->private.special.text.size = i;
 
1240
                                /* header size not known yet */
 
1241
      elt->private.msg.header.text.size = 0;
 
1242
      elt->rfc822_size = j;     /* note message size */
 
1243
                                /* calculate system flags */
 
1244
      if (k & fSEEN) elt->seen = T;
 
1245
      if (k & fDELETED) elt->deleted = T;
 
1246
      if (k & fFLAGGED) elt->flagged = T;
 
1247
      if (k & fANSWERED) elt->answered = T;
 
1248
      if (k & fDRAFT) elt->draft = T;
 
1249
      t[8] = '\0';              /* get user flags value */
 
1250
      elt->user_flags = strtoul (t,NIL,16);
 
1251
                                /* UID already assigned? */
 
1252
      if (!(elt->private.uid = m) || !(k & fOLD)) {
 
1253
        elt->recent = T;        /* no, mark as recent */
 
1254
        ++recent;               /* count up a new recent message */
 
1255
        dirty = T;              /* and must rewrite header */
 
1256
                                /* assign new UID */
 
1257
        if (!elt->private.uid) elt->private.uid = ++stream->uid_last;
 
1258
        mbx_update_status (stream,elt->msgno,NIL);
 
1259
      }
 
1260
                                /* update last parsed UID */
 
1261
      lastuid = elt->private.uid;
 
1262
    }
 
1263
    curpos += i + j;            /* update position */
 
1264
  }
 
1265
 
 
1266
  if (dirty && !stream->rdonly){/* update header */
 
1267
    mbx_update_header (stream);
 
1268
    fsync (LOCAL->fd);          /* make sure all the UID updates take */
 
1269
  }
 
1270
                                /* update parsed file size and time */
 
1271
  LOCAL->filesize = sbuf.st_size;
 
1272
  fstat (LOCAL->fd,&sbuf);      /* get status again to ensure time is right */
 
1273
  LOCAL->filetime = sbuf.st_mtime;
 
1274
  if (added && !stream->rdonly){/* make sure atime updated */
 
1275
    struct utimbuf times;
 
1276
    times.actime = time (0);
 
1277
    times.modtime = LOCAL->filetime;
 
1278
    utime (stream->mailbox,&times);
 
1279
  }
 
1280
  stream->silent = silent;      /* can pass up events now */
 
1281
  mail_exists (stream,nmsgs);   /* notify upper level of new mailbox size */
 
1282
  mail_recent (stream,recent);  /* and of change in recent messages */
 
1283
  return LONGT;                 /* return the winnage */
 
1284
}
 
1285
 
 
1286
/* MBX get cache element with status updating from file
 
1287
 * Accepts: MAIL stream
 
1288
 *          message number
 
1289
 *          expunge OK flag
 
1290
 * Returns: cache element
 
1291
 */
 
1292
 
 
1293
MESSAGECACHE *mbx_elt (MAILSTREAM *stream,unsigned long msgno,long expok)
 
1294
{
 
1295
  MESSAGECACHE *elt = mail_elt (stream,msgno);
 
1296
  struct {                      /* old flags */
 
1297
    unsigned int seen : 1;
 
1298
    unsigned int deleted : 1;
 
1299
    unsigned int flagged : 1;
 
1300
    unsigned int answered : 1;
 
1301
    unsigned int draft : 1;
 
1302
    unsigned long user_flags;
 
1303
  } old;
 
1304
  old.seen = elt->seen; old.deleted = elt->deleted; old.flagged = elt->flagged;
 
1305
  old.answered = elt->answered; old.draft = elt->draft;
 
1306
  old.user_flags = elt->user_flags;
 
1307
                                /* get new flags */
 
1308
  if (mbx_read_flags (stream,elt) && expok) {
 
1309
    mail_expunged (stream,elt->msgno);
 
1310
    return NIL;                 /* return this message was expunged */
 
1311
  }
 
1312
  if ((old.seen != elt->seen) || (old.deleted != elt->deleted) ||
 
1313
      (old.flagged != elt->flagged) || (old.answered != elt->answered) ||
 
1314
      (old.draft != elt->draft) || (old.user_flags != elt->user_flags))
 
1315
    mm_flags (stream,msgno);    /* let top level know */
 
1316
  return elt;
 
1317
}
 
1318
 
 
1319
/* MBX read flags from file
 
1320
 * Accepts: MAIL stream
 
1321
 *          cache element
 
1322
 * Returns: non-NIL if message expunged
 
1323
 */
 
1324
 
 
1325
unsigned long mbx_read_flags (MAILSTREAM *stream,MESSAGECACHE *elt)
 
1326
{
 
1327
  unsigned long i;
 
1328
  struct stat sbuf;
 
1329
  fstat (LOCAL->fd,&sbuf);      /* get status */
 
1330
                                /* make sure file size is good */
 
1331
  if (sbuf.st_size < LOCAL->filesize) {
 
1332
    sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag read!",
 
1333
             (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
 
1334
    fatal (LOCAL->buf);
 
1335
  }
 
1336
                                /* set the seek pointer */
 
1337
  lseek (LOCAL->fd,(off_t) elt->private.special.offset +
 
1338
         elt->private.special.text.size - 24,L_SET);
 
1339
                                /* read the new flags */
 
1340
  if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
 
1341
    sprintf (LOCAL->buf,"Unable to read new status: %s",strerror (errno));
 
1342
    fatal (LOCAL->buf);
 
1343
  }
 
1344
  if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
 
1345
    LOCAL->buf[14] = '\0';      /* tie off buffer for error message */
 
1346
    sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
 
1347
             elt->msgno,elt->private.special.offset,
 
1348
             elt->private.special.text.size,(char *) LOCAL->buf);
 
1349
    fatal (LOCAL->buf+50);
 
1350
  }
 
1351
  LOCAL->buf[13] = '\0';        /* tie off buffer */
 
1352
                                /* calculate system flags */
 
1353
  i = strtoul (LOCAL->buf+9,NIL,16);
 
1354
  elt->seen = i & fSEEN ? T : NIL;
 
1355
  elt->deleted = i & fDELETED ? T : NIL;
 
1356
  elt->flagged = i & fFLAGGED ? T : NIL;
 
1357
  elt->answered = i & fANSWERED ? T : NIL;
 
1358
  elt->draft = i & fDRAFT ? T : NIL;
 
1359
  LOCAL->expunged |= i & fEXPUNGED ? T : NIL;
 
1360
  LOCAL->buf[9] = '\0';         /* tie off flags */
 
1361
                                /* get user flags value */
 
1362
  elt->user_flags = strtoul (LOCAL->buf+1,NIL,16);
 
1363
  elt->valid = T;               /* have valid flags now */
 
1364
  return i & fEXPUNGED;
 
1365
}
 
1366
 
 
1367
/* MBX update header
 
1368
 * Accepts: MAIL stream
 
1369
 */
 
1370
 
 
1371
#define NTKLUDGEOFFSET 7
 
1372
 
 
1373
void mbx_update_header (MAILSTREAM *stream)
 
1374
{
 
1375
  int i;
 
1376
  char *s = LOCAL->buf;
 
1377
  memset (s,'\0',HDRSIZE);      /* initialize header */
 
1378
  sprintf (s,"*mbx*\015\012%08lx%08lx\015\012",
 
1379
           stream->uid_validity,stream->uid_last);
 
1380
  for (i = 0; (i < NUSERFLAGS) && stream->user_flags[i]; ++i)
 
1381
    sprintf (s += strlen (s),"%s\015\012",stream->user_flags[i]);
 
1382
  LOCAL->ffuserflag = i;        /* first free user flag */
 
1383
                                /* can we create more user flags? */
 
1384
  stream->kwd_create = (i < NUSERFLAGS) ? T : NIL;
 
1385
                                /* write reserved lines */
 
1386
  while (i++ < NUSERFLAGS) strcat (s,"\015\012");
 
1387
  while (T) {                   /* rewind file */
 
1388
    lseek (LOCAL->fd,NTKLUDGEOFFSET,L_SET);
 
1389
                                /* write new header */
 
1390
    if (write (LOCAL->fd,LOCAL->buf + NTKLUDGEOFFSET,
 
1391
               HDRSIZE - NTKLUDGEOFFSET) > 0) break;
 
1392
    mm_notify (stream,strerror (errno),WARN);
 
1393
    mm_diskerror (stream,errno,T);
 
1394
  }
 
1395
}
 
1396
 
 
1397
/* MBX update status string
 
1398
 * Accepts: MAIL stream
 
1399
 *          message number
 
1400
 *          flags
 
1401
 */
 
1402
 
 
1403
void mbx_update_status (MAILSTREAM *stream,unsigned long msgno,long flags)
 
1404
{
 
1405
  struct stat sbuf;
 
1406
  MESSAGECACHE *elt = mail_elt (stream,msgno);
 
1407
                                /* readonly */
 
1408
  if (stream->rdonly || !elt->valid) mbx_read_flags (stream,elt);
 
1409
  else {                        /* readwrite */
 
1410
    fstat (LOCAL->fd,&sbuf);    /* get status */
 
1411
                                /* make sure file size is good */
 
1412
    if (sbuf.st_size < LOCAL->filesize) {
 
1413
      sprintf (LOCAL->buf,"Mailbox shrank from %lu to %lu in flag update!",
 
1414
               (unsigned long) LOCAL->filesize,(unsigned long) sbuf.st_size);
 
1415
      fatal (LOCAL->buf);
 
1416
    }
 
1417
                                /* set the seek pointer */
 
1418
    lseek (LOCAL->fd,(off_t) elt->private.special.offset +
 
1419
           elt->private.special.text.size - 24,L_SET);
 
1420
                                /* read the new flags */
 
1421
    if (read (LOCAL->fd,LOCAL->buf,14) < 0) {
 
1422
      sprintf (LOCAL->buf,"Unable to read old status: %s",strerror (errno));
 
1423
      fatal (LOCAL->buf);
 
1424
    }
 
1425
    if ((LOCAL->buf[0] != ';') || (LOCAL->buf[13] != '-')) {
 
1426
      LOCAL->buf[14] = '\0';    /* tie off buffer for error message */
 
1427
      sprintf (LOCAL->buf+50,"Invalid flags for message %lu (%lu %lu): %s",
 
1428
               elt->msgno,elt->private.special.offset,
 
1429
               elt->private.special.text.size,(char *) LOCAL->buf);
 
1430
      fatal (LOCAL->buf+50);
 
1431
    }
 
1432
                                /* print new flag string */
 
1433
    sprintf (LOCAL->buf,"%08lx%04x-%08lx",elt->user_flags,(unsigned)
 
1434
             (((elt->deleted && flags) ?
 
1435
               fEXPUNGED : (strtoul (LOCAL->buf+9,NIL,16)) & fEXPUNGED) +
 
1436
              (fSEEN * elt->seen) + (fDELETED * elt->deleted) +
 
1437
              (fFLAGGED * elt->flagged) + (fANSWERED * elt->answered) +
 
1438
              (fDRAFT * elt->draft) + fOLD),elt->private.uid);
 
1439
    while (T) {                 /* get to that place in the file */
 
1440
      lseek (LOCAL->fd,(off_t) elt->private.special.offset +
 
1441
             elt->private.special.text.size - 23,L_SET);
 
1442
                                /* write new flags and UID */
 
1443
      if (write (LOCAL->fd,LOCAL->buf,21) > 0) break;
 
1444
      mm_notify (stream,strerror (errno),WARN);
 
1445
      mm_diskerror (stream,errno,T);
 
1446
    }
 
1447
  }
 
1448
}
 
1449
 
 
1450
/* MBX locate header for a message
 
1451
 * Accepts: MAIL stream
 
1452
 *          message number
 
1453
 *          pointer to returned header size
 
1454
 *          pointer to possible returned header
 
1455
 * Returns: position of header in file
 
1456
 */
 
1457
 
 
1458
#define HDRBUFLEN 16384         /* good enough for most headers */
 
1459
#define SLOP 4                  /* CR LF CR LF */
 
1460
 
 
1461
unsigned long mbx_hdrpos (MAILSTREAM *stream,unsigned long msgno,
 
1462
                          unsigned long *size,char **hdr)
 
1463
{
 
1464
  unsigned long siz,done;
 
1465
  long i;
 
1466
  unsigned char *s,*t,*te;
 
1467
  MESSAGECACHE *elt = mail_elt (stream,msgno);
 
1468
  unsigned long ret = elt->private.special.offset +
 
1469
    elt->private.special.text.size;
 
1470
  if (hdr) *hdr = NIL;          /* assume no header returned */
 
1471
                                /* is header size known? */ 
 
1472
  if (*size = elt->private.msg.header.text.size) return ret;
 
1473
                                /* paranoia check */
 
1474
  if (LOCAL->buflen < (HDRBUFLEN + SLOP))
 
1475
    fatal ("LOCAL->buf smaller than HDRBUFLEN");
 
1476
  lseek (LOCAL->fd,ret,L_SET);  /* get to header position */
 
1477
                                /* read HDRBUFLEN chunks with 4 byte slop */
 
1478
  for (done = siz = 0, s = LOCAL->buf;
 
1479
       (i = min ((long) (elt->rfc822_size - done),(long) HDRBUFLEN)) &&
 
1480
       (read (LOCAL->fd,s,i) == i);
 
1481
       done += i, siz += (t - LOCAL->buf) - SLOP, s = LOCAL->buf + SLOP) {
 
1482
    te = (t = s + i) - 12;      /* calculate end of fast scan */
 
1483
                                /* fast scan for CR */
 
1484
    for (s = LOCAL->buf; s < te;)
 
1485
      if (((*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
 
1486
           (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
 
1487
           (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015') ||
 
1488
           (*s++ == '\015') || (*s++ == '\015') || (*s++ == '\015')) &&
 
1489
          (*s == '\012') && (*++s == '\015') && (*++s == '\012')) {
 
1490
        *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
 
1491
        if (hdr) *hdr = LOCAL->buf;
 
1492
        return ret;
 
1493
      }
 
1494
    for (te = t - 3; (s < te);) /* final character-at-a-time scan */
 
1495
      if ((*s++ == '\015') && (*s == '\012') && (*++s == '\015') &&
 
1496
          (*++s == '\012')) {
 
1497
        *size = elt->private.msg.header.text.size = siz + (++s - LOCAL->buf);
 
1498
        if (hdr) *hdr = LOCAL->buf;
 
1499
        return ret;
 
1500
      }
 
1501
    if (i <= SLOP) break;       /* end of data */
 
1502
                                /* slide over last 4 bytes */
 
1503
    memmove (LOCAL->buf,t - SLOP,SLOP);
 
1504
    hdr = NIL;                  /* can't return header this way */
 
1505
  }
 
1506
                                /* not found: header consumes entire message */
 
1507
  elt->private.msg.header.text.size = *size = elt->rfc822_size;
 
1508
  if (hdr) *hdr = LOCAL->buf;   /* possibly return header too */
 
1509
  return ret;
 
1510
}
 
1511
 
 
1512
/* MBX mail rewrite mailbox
 
1513
 * Accepts: MAIL stream
 
1514
 *          pointer to return reclaimed size
 
1515
 *          flags (0 = no expunge, 1 = expunge deleted, -1 = expunge sequence)
 
1516
 * Returns: number of expunged messages
 
1517
 */
 
1518
 
 
1519
unsigned long mbx_rewrite (MAILSTREAM *stream,unsigned long *reclaimed,
 
1520
                           long flags)
 
1521
{
 
1522
  struct utimbuf times;
 
1523
  struct stat sbuf;
 
1524
  off_t pos,ppos;
 
1525
  int ld;
 
1526
  unsigned long i,j,k,m,delta;
 
1527
  unsigned long n = *reclaimed = 0;
 
1528
  unsigned long recent = 0;
 
1529
  char lock[MAILTMPLEN];
 
1530
  MESSAGECACHE *elt;
 
1531
                                /* get parse/append permission */
 
1532
  if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0) {
 
1533
    mm_log ("Unable to lock expunge mailbox",ERROR);
 
1534
    return 0;
 
1535
  }
 
1536
  fstat (LOCAL->fd,&sbuf);      /* get current write time */
 
1537
  if (LOCAL->filetime && !LOCAL->flagcheck &&
 
1538
      (LOCAL->filetime < sbuf.st_mtime)) LOCAL->flagcheck = T;
 
1539
  if (!mbx_parse (stream)) {    /* make sure see any newly-arrived messages */
 
1540
    unlockfd (ld,lock);         /* failed?? */
 
1541
    return 0;
 
1542
  }
 
1543
  if (LOCAL->flagcheck) {       /* sweep flags if need flagcheck */
 
1544
    LOCAL->filetime = sbuf.st_mtime;
 
1545
    for (i = 1; i <= stream->nmsgs; ++i) mbx_elt (stream,i,NIL);
 
1546
    LOCAL->flagcheck = NIL;
 
1547
  }
 
1548
 
 
1549
                                /* get exclusive access */
 
1550
  if (!flock (LOCAL->fd,LOCK_EX|LOCK_NB)) {
 
1551
    mm_critical (stream);       /* go critical */
 
1552
    for (i = 1,delta = 0,pos = ppos = HDRSIZE; i <= stream->nmsgs; ) {
 
1553
                                /* note if message not at predicted location */
 
1554
      if (m = (elt = mbx_elt (stream,i,NIL))->private.special.offset - ppos) {
 
1555
        ppos = elt->private.special.offset;
 
1556
        *reclaimed += m;        /* note reclaimed message space */
 
1557
        delta += m;             /* and as expunge delta  */
 
1558
      }
 
1559
                                /* number of bytes to smash or preserve */
 
1560
      ppos += (k = elt->private.special.text.size + elt->rfc822_size);
 
1561
                                /* if need to expunge this message*/
 
1562
      if (flags && elt->deleted && ((flags > 0) || elt->sequence)) {
 
1563
        delta += k;             /* number of bytes to delete */
 
1564
        mail_expunged(stream,i);/* notify upper levels */
 
1565
        n++;                    /* count up one more expunged message */
 
1566
      }
 
1567
      else {                    /* preserved message */
 
1568
        i++;                    /* count this message */
 
1569
        if (elt->recent) ++recent;
 
1570
        if (delta) {            /* moved, note first byte to preserve */
 
1571
          j = elt->private.special.offset;
 
1572
          do {                  /* read from source position */
 
1573
            m = min (k,LOCAL->buflen);
 
1574
            lseek (LOCAL->fd,j,L_SET);
 
1575
            read (LOCAL->fd,LOCAL->buf,m);
 
1576
            pos = j - delta;    /* write to destination position */
 
1577
            while (T) {
 
1578
              lseek (LOCAL->fd,pos,L_SET);
 
1579
              if (write (LOCAL->fd,LOCAL->buf,m) > 0) break;
 
1580
              mm_notify (stream,strerror (errno),WARN);
 
1581
              mm_diskerror (stream,errno,T);
 
1582
            }
 
1583
            pos += m;           /* new position */
 
1584
            j += m;             /* next chunk, perhaps */
 
1585
          } while (k -= m);     /* until done */
 
1586
                                /* note the new address of this text */
 
1587
          elt->private.special.offset -= delta;
 
1588
        }
 
1589
                                /* preserved but no deleted messages yet */
 
1590
        else pos = elt->private.special.offset + k;
 
1591
      }
 
1592
    }
 
1593
                                /* deltaed file size match position? */
 
1594
    if (m = (LOCAL->filesize -= delta) - pos) {
 
1595
      *reclaimed += m;          /* probably an fEXPUNGED msg */
 
1596
      LOCAL->filesize = pos;    /* set correct size */
 
1597
    }
 
1598
                                /* truncate file after last message */
 
1599
    ftruncate (LOCAL->fd,LOCAL->filesize);
 
1600
    fsync (LOCAL->fd);          /* force disk update */
 
1601
    mm_nocritical (stream);     /* release critical */
 
1602
    flock (LOCAL->fd,LOCK_SH);  /* allow sharers again */
 
1603
    unlockfd (ld,lock);         /* release exclusive parse/append permission */
 
1604
  }
 
1605
 
 
1606
  else {                        /* can't get exclusive */
 
1607
    flock (LOCAL->fd,LOCK_SH);  /* recover previous shared mailbox lock */
 
1608
    unlockfd (ld,lock);         /* release exclusive parse/append permission */
 
1609
                                /* do hide-expunge when shared */
 
1610
    if (flags) for (i = 1; i <= stream->nmsgs; ) {
 
1611
      if (elt = mbx_elt (stream,i,T)) {
 
1612
                                /* make the message invisible */
 
1613
        if (elt->deleted && ((flags > 0) || elt->sequence)) {
 
1614
          mbx_update_status (stream,elt->msgno,LONGT);
 
1615
                                /* notify upper levels */
 
1616
          mail_expunged (stream,i);
 
1617
          n++;                  /* count up one more expunged message */
 
1618
        }
 
1619
        else {
 
1620
          i++;                  /* preserved message */
 
1621
          if (elt->recent) ++recent;
 
1622
        }
 
1623
      }
 
1624
      else n++;                 /* count up one more expunged message */
 
1625
    }
 
1626
    fsync (LOCAL->fd);          /* force disk update */
 
1627
  }
 
1628
  fstat (LOCAL->fd,&sbuf);      /* get new write time */
 
1629
  times.modtime = LOCAL->filetime = sbuf.st_mtime;
 
1630
  times.actime = time (0);      /* reset atime to now */
 
1631
  utime (stream->mailbox,&times);
 
1632
                                /* notify upper level of new mailbox size */
 
1633
  mail_exists (stream,stream->nmsgs);
 
1634
  mail_recent (stream,recent);
 
1635
  return n;                     /* return number of expunged messages */
 
1636
}
 
1637
 
 
1638
/* MBX mail lock for flag updating
 
1639
 * Accepts: stream
 
1640
 * Returns: T if successful, NIL if failure
 
1641
 */
 
1642
 
 
1643
long mbx_flaglock (MAILSTREAM *stream)
 
1644
{
 
1645
  struct stat sbuf;
 
1646
  unsigned long i;
 
1647
  int ld;
 
1648
  char lock[MAILTMPLEN];
 
1649
                                /* no-op if readonly or already locked */
 
1650
  if (!stream->rdonly && LOCAL && (LOCAL->fd >= 0) && (LOCAL->ld < 0)) {
 
1651
                                /* lock now */
 
1652
    if ((ld = lockname (lock,stream->mailbox,LOCK_EX)) < 0) return NIL;
 
1653
    if (!LOCAL->flagcheck) {    /* don't do this if flagcheck already needed */
 
1654
      if (LOCAL->filetime) {    /* know previous time? */
 
1655
        fstat (LOCAL->fd,&sbuf);/* get current write time */
 
1656
        if (LOCAL->filetime < sbuf.st_mtime) LOCAL->flagcheck = T;
 
1657
        LOCAL->filetime = 0;    /* don't do this test for any other messages */
 
1658
      }
 
1659
      if (!mbx_parse (stream)) {/* parse mailbox */
 
1660
        unlockfd (ld,lock);     /* shouldn't happen */
 
1661
        return NIL;
 
1662
      }
 
1663
      if (LOCAL->flagcheck)     /* invalidate cache if flagcheck */
 
1664
        for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->valid = NIL;
 
1665
    }
 
1666
    LOCAL->ld = ld;             /* copy to stream for subseuent calls */
 
1667
    memcpy (LOCAL->lock,lock,MAILTMPLEN);
 
1668
  }
 
1669
  return LONGT;
 
1670
}