Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117749566
mailbox.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
77 KB
Referenced Files
None
Subscribers
None
mailbox.c
View Options
/* mailbox.c -- Mailbox manipulation routines
* $Id: mailbox.c,v 1.155 2004/05/22 03:45:51 rjs3 Exp $
* Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any other legal
* details, please contact
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*
*/
#include
<config.h>
#ifdef HAVE_UNISTD_H
#include
<unistd.h>
#endif
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<errno.h>
#include
<syslog.h>
#ifdef HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include
"acl.h"
#include
"assert.h"
#include
"exitcodes.h"
#include
"global.h"
#include
"imap_err.h"
#include
"index.h"
#include
"lock.h"
#include
"mailbox.h"
#include
"map.h"
#include
"mboxlist.h"
#include
"retry.h"
#include
"seen.h"
#include
"util.h"
#include
"xmalloc.h"
static
int
mailbox_doing_reconstruct
=
0
;
#define zeromailbox(m) { memset(&m, 0, sizeof(struct mailbox)); \
(m).header_fd = -1; \
(m).index_fd = -1; \
(m).cache_fd = -1; }
static
int
mailbox_calculate_flagcounts
(
struct
mailbox
*
mailbox
);
static
int
mailbox_upgrade_index
(
struct
mailbox
*
mailbox
);
/*
* Names of the headers we cache in the cyrus.cache file.
*
* Changes to this list probably require bumping the cache version
* number (obviously)
*
* note that header names longer than MAX_CACHED_HEADER_SIZE
* won't be cached regardless
*
* xxx can we get benefits by requireing this list to be sorted?
* (see is_cached_header())
*
*/
const
struct
mailbox_header_cache
mailbox_cache_headers
[]
=
{
/* things we have always cached */
{
"priority"
,
0
},
{
"references"
,
0
},
{
"resent-from"
,
0
},
{
"newsgroups"
,
0
},
{
"followup-to"
,
0
},
/* x headers that we may want to cache anyway */
{
"x-mailer"
,
1
},
{
"x-trace"
,
1
},
/* outlook express seems to want these */
{
"x-ref"
,
2
},
{
"x-priority"
,
2
},
{
"x-msmail-priority"
,
2
},
{
"x-msoesrec"
,
2
},
/* things to never cache */
{
"bcc"
,
BIT32_MAX
},
{
"cc"
,
BIT32_MAX
},
{
"date"
,
BIT32_MAX
},
{
"delivery-date"
,
BIT32_MAX
},
{
"envelope-to"
,
BIT32_MAX
},
{
"from"
,
BIT32_MAX
},
{
"in-reply-to"
,
BIT32_MAX
},
{
"mime-version"
,
BIT32_MAX
},
{
"reply-to"
,
BIT32_MAX
},
{
"received"
,
BIT32_MAX
},
{
"return-path"
,
BIT32_MAX
},
{
"sender"
,
BIT32_MAX
},
{
"subject"
,
BIT32_MAX
},
{
"to"
,
BIT32_MAX
},
/* older versions of PINE (before 4.56) need message-id in the cache too
* though technically it is a waste of space because it is in
* ENVELOPE. We should probably uncomment the following at some
* future point [ken3 notes this may also be useful to have here for
* threading so we can avoid parsing the envelope] */
/* { "message-id", BIT32_MAX }, */
};
const
int
MAILBOX_NUM_CACHE_HEADERS
=
sizeof
(
mailbox_cache_headers
)
/
sizeof
(
struct
mailbox_header_cache
);
/*
* Function to test if a header is in the cache
*
* Assume cache entry version 1, unless other data is found
* in the table.
*/
static
inline
int
is_cached_header
(
const
char
*
hdr
)
{
int
i
;
/* xxx if we can sort the header list we can do better here */
for
(
i
=
0
;
i
<
MAILBOX_NUM_CACHE_HEADERS
;
i
++
)
{
if
(
!
strcmp
(
mailbox_cache_headers
[
i
].
name
,
hdr
))
return
mailbox_cache_headers
[
i
].
min_cache_version
;
}
/* Don't Cache X- headers unless explicitly configured to*/
if
((
hdr
[
0
]
==
'x'
)
&&
(
hdr
[
1
]
==
'-'
))
return
BIT32_MAX
;
/* Everything else we cache in version 1 */
return
1
;
}
/* External API to is_cached_header that prepares the string
*
* Returns minimum version required for lookup to succeed
* or BIT32_MAX if header not cached
*/
int
mailbox_cached_header
(
const
char
*
s
)
{
char
hdr
[
MAX_CACHED_HEADER_SIZE
];
int
i
;
/* Generate lower case copy of string */
/* xxx sometimes the caller has already generated this ..
* maybe we can just require callers to do it? */
for
(
i
=
0
;
*
s
&&
(
i
<
MAX_CACHED_HEADER_SIZE
)
;
i
++
)
hdr
[
i
]
=
tolower
(
*
s
++
);
if
(
*
s
)
return
BIT32_MAX
;
/* Input too long for match */
hdr
[
i
]
=
'\0'
;
return
is_cached_header
(
hdr
);
}
/* Same as mailbox_cached_header, but for use on a header
* as it appears in the message (i.e. :-terminated, not NUL-terminated)
*/
int
mailbox_cached_header_inline
(
const
char
*
text
)
{
char
buf
[
MAX_CACHED_HEADER_SIZE
];
int
i
;
/* Scan for header */
for
(
i
=
0
;
i
<
MAX_CACHED_HEADER_SIZE
;
i
++
)
{
if
(
!
text
[
i
]
||
text
[
i
]
==
'\r'
||
text
[
i
]
==
'\n'
)
break
;
if
(
text
[
i
]
==
':'
)
{
buf
[
i
]
=
'\0'
;
return
is_cached_header
(
buf
);
}
else
{
buf
[
i
]
=
tolower
(
text
[
i
]);
}
}
return
BIT32_MAX
;
}
/* function to be used for notification of mailbox changes/updates */
static
mailbox_notifyproc_t
*
updatenotifier
=
NULL
;
/*
* Set the updatenotifier function
*/
void
mailbox_set_updatenotifier
(
mailbox_notifyproc_t
*
notifyproc
)
{
updatenotifier
=
notifyproc
;
}
/*
* Create connection to acappush (obsolete)
*/
int
mailbox_initialize
(
void
)
{
return
0
;
}
/* create the unique identifier for a mailbox named 'name' with
* uidvalidity 'uidvalidity'. 'uniqueid' should be at least 17 bytes
* long. the unique identifier is just the mailbox name hashed to 32
* bits followed by the uid, both converted to hex.
*/
#define PRIME (2147484043UL)
void
mailbox_make_uniqueid
(
char
*
name
,
unsigned
long
uidvalidity
,
char
*
uniqueid
,
size_t
outlen
)
{
unsigned
long
hash
=
0
;
while
(
*
name
)
{
hash
*=
251
;
hash
+=
*
name
++
;
hash
%=
PRIME
;
}
snprintf
(
uniqueid
,
outlen
,
"%08lx%08lx"
,
hash
,
uidvalidity
);
}
/*
* Calculate relative filename for the message with UID 'uid'
* in 'mailbox'. 'out' must be at least MAILBOX_FNAME_LEN long.
*/
void
mailbox_message_get_fname
(
struct
mailbox
*
mailbox
,
unsigned
long
uid
,
char
*
out
,
size_t
size
)
{
char
buf
[
MAILBOX_FNAME_LEN
];
assert
(
mailbox
->
format
!=
MAILBOX_FORMAT_NETNEWS
);
snprintf
(
buf
,
sizeof
(
buf
),
"%lu."
,
uid
);
assert
(
strlen
(
buf
)
<
size
);
strlcpy
(
out
,
buf
,
size
);
}
/*
* Maps in the content for the message with UID 'uid' in 'mailbox'.
* Returns map in 'basep' and 'lenp'
*/
int
mailbox_map_message
(
struct
mailbox
*
mailbox
,
int
iscurrentdir
,
unsigned
long
uid
,
const
char
**
basep
,
unsigned
long
*
lenp
)
{
int
msgfd
;
char
buf
[
4096
];
char
*
p
=
buf
;
struct
stat
sbuf
;
buf
[
0
]
=
'\0'
;
if
(
!
iscurrentdir
)
{
/* 26 is max # of digits in a long + strlen("/") + strlen(".") */
if
(
strlen
(
mailbox
->
path
)
+
25
>=
sizeof
(
buf
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long while mapping message: %s"
,
mailbox
->
path
);
fatal
(
"path too long for message file"
,
EC_OSFILE
);
}
strlcpy
(
buf
,
mailbox
->
path
,
sizeof
(
buf
));
p
=
buf
+
strlen
(
buf
);
*
p
++
=
'/'
;
}
snprintf
(
p
,
sizeof
(
buf
)
-
strlen
(
buf
),
"%lu."
,
uid
);
msgfd
=
open
(
buf
,
O_RDONLY
,
0666
);
if
(
msgfd
==
-1
)
return
errno
;
if
(
fstat
(
msgfd
,
&
sbuf
)
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: fstat on %s: %m"
,
buf
);
fatal
(
"can't fstat message file"
,
EC_OSFILE
);
}
*
basep
=
0
;
*
lenp
=
0
;
map_refresh
(
msgfd
,
1
,
basep
,
lenp
,
sbuf
.
st_size
,
buf
,
mailbox
->
name
);
close
(
msgfd
);
return
0
;
}
/*
* Releases the buffer obtained from mailbox_map_message()
*/
void
mailbox_unmap_message
(
struct
mailbox
*
mailbox
__attribute__
((
unused
)),
unsigned
long
uid
__attribute__
((
unused
)),
const
char
**
basep
,
unsigned
long
*
lenp
)
{
map_free
(
basep
,
lenp
);
}
/*
* Set the "reconstruct" mode. Causes most errors to be ignored.
*/
void
mailbox_reconstructmode
()
{
mailbox_doing_reconstruct
=
1
;
}
/* stat a mailbox's control files. returns a bitmask that sets
* 0x1 if the header fialed, 0x2 if the index failed, and 0x4 if the cache failed */
int
mailbox_stat
(
const
char
*
mbpath
,
struct
stat
*
header
,
struct
stat
*
index
,
struct
stat
*
cache
)
{
char
fnamebuf
[
MAX_MAILBOX_PATH
];
int
r
=
0
,
ret
=
0
;
assert
(
mbpath
&&
(
header
||
index
));
if
(
header
)
{
snprintf
(
fnamebuf
,
sizeof
(
fnamebuf
),
"%s/cyrus.header"
,
mbpath
);
r
=
stat
(
fnamebuf
,
header
);
if
(
r
)
ret
|=
0x1
;
}
if
(
!
r
&&
index
)
{
snprintf
(
fnamebuf
,
sizeof
(
fnamebuf
),
"%s/cyrus.index"
,
mbpath
);
r
=
stat
(
fnamebuf
,
index
);
if
(
r
)
ret
|=
0x2
;
}
if
(
!
r
&&
cache
)
{
snprintf
(
fnamebuf
,
sizeof
(
fnamebuf
),
"%s/cyrus.cache"
,
mbpath
);
r
=
stat
(
fnamebuf
,
cache
);
if
(
r
)
ret
|=
0x4
;
}
return
ret
;
}
/*
* Open and read the header of the mailbox with name 'name'
* The structure pointed to by 'mailbox' is initialized.
*/
int
mailbox_open_header
(
const
char
*
name
,
struct
auth_state
*
auth_state
,
struct
mailbox
*
mailbox
)
{
char
*
path
,
*
acl
;
int
r
;
r
=
mboxlist_lookup
(
name
,
&
path
,
&
acl
,
NULL
);
if
(
r
)
return
r
;
return
mailbox_open_header_path
(
name
,
path
,
acl
,
auth_state
,
mailbox
,
0
);
}
/*
* Open and read the header of the mailbox with name 'name'
* path 'path', and ACL 'acl'.
* The structure pointed to by 'mailbox' is initialized.
*/
int
mailbox_open_header_path
(
const
char
*
name
,
const
char
*
path
,
const
char
*
acl
,
struct
auth_state
*
auth_state
,
struct
mailbox
*
mailbox
,
int
suppresslog
)
{
char
fnamebuf
[
MAX_MAILBOX_PATH
+
1
];
struct
stat
sbuf
;
int
r
;
zeromailbox
(
*
mailbox
);
strlcpy
(
fnamebuf
,
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_HEADER
,
sizeof
(
fnamebuf
));
mailbox
->
header_fd
=
open
(
fnamebuf
,
O_RDWR
,
0
);
if
(
mailbox
->
header_fd
==
-1
&&
!
mailbox_doing_reconstruct
)
{
if
(
!
suppresslog
)
{
syslog
(
LOG_ERR
,
"IOERROR: opening %s: %m"
,
fnamebuf
);
}
return
IMAP_IOERROR
;
}
if
(
mailbox
->
header_fd
!=
-1
)
{
if
(
fstat
(
mailbox
->
header_fd
,
&
sbuf
)
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: fstating %s: %m"
,
fnamebuf
);
fatal
(
"can't fstat header file"
,
EC_OSFILE
);
}
map_refresh
(
mailbox
->
header_fd
,
1
,
&
mailbox
->
header_base
,
&
mailbox
->
header_len
,
sbuf
.
st_size
,
"header"
,
name
);
mailbox
->
header_ino
=
sbuf
.
st_ino
;
}
mailbox
->
name
=
xstrdup
(
name
);
mailbox
->
path
=
xstrdup
(
path
);
/* Note that the header does have the ACL information, but it is only
* a backup, and the mboxlist data is considered authoritative, so
* we will just use what we were passed */
mailbox
->
acl
=
xstrdup
(
acl
);
mailbox
->
myrights
=
cyrus_acl_myrights
(
auth_state
,
mailbox
->
acl
);
if
(
mailbox
->
header_fd
==
-1
)
return
0
;
r
=
mailbox_read_header
(
mailbox
);
if
(
r
&&
!
mailbox_doing_reconstruct
)
{
mailbox_close
(
mailbox
);
return
r
;
}
return
0
;
}
int
mailbox_open_locked
(
const
char
*
mbname
,
const
char
*
mbpath
,
const
char
*
mbacl
,
struct
auth_state
*
auth_state
,
struct
mailbox
*
mb
,
int
suppresslog
)
{
int
r
;
r
=
mailbox_open_header_path
(
mbname
,
mbpath
,
mbacl
,
auth_state
,
mb
,
suppresslog
);
if
(
r
)
return
r
;
/* now we have to close the mailbox if we fail */
r
=
mailbox_lock_header
(
mb
);
if
(
!
r
)
r
=
mailbox_open_index
(
mb
);
if
(
!
r
)
r
=
mailbox_lock_index
(
mb
);
if
(
r
)
mailbox_close
(
mb
);
return
r
;
}
#define MAXTRIES 60
/*
* Open the index and cache files for 'mailbox'. Also
* read the index header.
*/
int
mailbox_open_index
(
struct
mailbox
*
mailbox
)
{
char
fnamebuf
[
MAX_MAILBOX_PATH
+
1
];
bit32
index_gen
=
0
,
cache_gen
=
0
;
int
tries
=
0
;
if
(
mailbox
->
index_fd
!=
-1
)
{
close
(
mailbox
->
index_fd
);
map_free
(
&
mailbox
->
index_base
,
&
mailbox
->
index_len
);
}
if
(
mailbox
->
cache_fd
!=
-1
)
{
close
(
mailbox
->
cache_fd
);
map_free
(
&
mailbox
->
cache_base
,
&
mailbox
->
cache_len
);
}
do
{
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_INDEX
,
sizeof
(
fnamebuf
));
mailbox
->
index_fd
=
open
(
fnamebuf
,
O_RDWR
,
0
);
if
(
mailbox
->
index_fd
!=
-1
)
{
map_refresh
(
mailbox
->
index_fd
,
0
,
&
mailbox
->
index_base
,
&
mailbox
->
index_len
,
MAP_UNKNOWN_LEN
,
"index"
,
mailbox
->
name
);
}
if
(
mailbox_doing_reconstruct
)
break
;
if
(
mailbox
->
index_fd
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: opening %s: %m"
,
fnamebuf
);
return
IMAP_IOERROR
;
}
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_CACHE
,
sizeof
(
fnamebuf
));
mailbox
->
cache_fd
=
open
(
fnamebuf
,
O_RDWR
,
0
);
if
(
mailbox
->
cache_fd
!=
-1
)
{
struct
stat
sbuf
;
if
(
fstat
(
mailbox
->
cache_fd
,
&
sbuf
)
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: fstating %s: %m"
,
mailbox
->
name
);
fatal
(
"can't fstat cache file"
,
EC_OSFILE
);
}
mailbox
->
cache_size
=
sbuf
.
st_size
;
map_refresh
(
mailbox
->
cache_fd
,
0
,
&
mailbox
->
cache_base
,
&
mailbox
->
cache_len
,
mailbox
->
cache_size
,
"cache"
,
mailbox
->
name
);
}
if
(
mailbox
->
cache_fd
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: opening %s: %m"
,
fnamebuf
);
return
IMAP_IOERROR
;
}
/* Check generation number matches */
if
(
mailbox
->
index_len
<
4
||
mailbox
->
cache_len
<
4
)
{
return
IMAP_MAILBOX_BADFORMAT
;
}
index_gen
=
ntohl
(
*
(
bit32
*
)(
mailbox
->
index_base
+
OFFSET_GENERATION_NO
));
cache_gen
=
ntohl
(
*
(
bit32
*
)(
mailbox
->
cache_base
+
OFFSET_GENERATION_NO
));
if
(
index_gen
!=
cache_gen
)
{
close
(
mailbox
->
index_fd
);
map_free
(
&
mailbox
->
index_base
,
&
mailbox
->
index_len
);
close
(
mailbox
->
cache_fd
);
map_free
(
&
mailbox
->
cache_base
,
&
mailbox
->
cache_len
);
sleep
(
1
);
}
}
while
(
index_gen
!=
cache_gen
&&
tries
++
<
MAXTRIES
);
if
(
index_gen
!=
cache_gen
)
{
return
IMAP_MAILBOX_BADFORMAT
;
}
mailbox
->
generation_no
=
index_gen
;
return
mailbox_read_index_header
(
mailbox
);
}
/*
* Close the mailbox 'mailbox', freeing all associated resources.
*/
void
mailbox_close
(
struct
mailbox
*
mailbox
)
{
int
flag
;
close
(
mailbox
->
header_fd
);
map_free
(
&
mailbox
->
header_base
,
&
mailbox
->
header_len
);
if
(
mailbox
->
index_fd
!=
-1
)
{
close
(
mailbox
->
index_fd
);
map_free
(
&
mailbox
->
index_base
,
&
mailbox
->
index_len
);
}
if
(
mailbox
->
cache_fd
!=
-1
)
{
close
(
mailbox
->
cache_fd
);
map_free
(
&
mailbox
->
cache_base
,
&
mailbox
->
cache_len
);
}
free
(
mailbox
->
name
);
free
(
mailbox
->
path
);
free
(
mailbox
->
acl
);
free
(
mailbox
->
uniqueid
);
if
(
mailbox
->
quota
.
root
)
free
(
mailbox
->
quota
.
root
);
for
(
flag
=
0
;
flag
<
MAX_USER_FLAGS
;
flag
++
)
{
if
(
mailbox
->
flagname
[
flag
])
free
(
mailbox
->
flagname
[
flag
]);
}
zeromailbox
(
*
mailbox
);
}
/*
* Read the header of 'mailbox'
*/
int
mailbox_read_header
(
struct
mailbox
*
mailbox
)
{
int
flag
;
const
char
*
name
,
*
p
,
*
tab
,
*
eol
;
int
oldformat
=
0
;
/* Check magic number */
if
(
mailbox
->
header_len
<
sizeof
(
MAILBOX_HEADER_MAGIC
)
-1
||
strncmp
(
mailbox
->
header_base
,
MAILBOX_HEADER_MAGIC
,
sizeof
(
MAILBOX_HEADER_MAGIC
)
-1
))
{
return
IMAP_MAILBOX_BADFORMAT
;
}
/* Read quota file pathname */
p
=
mailbox
->
header_base
+
sizeof
(
MAILBOX_HEADER_MAGIC
)
-1
;
tab
=
memchr
(
p
,
'\t'
,
mailbox
->
header_len
-
(
p
-
mailbox
->
header_base
));
eol
=
memchr
(
p
,
'\n'
,
mailbox
->
header_len
-
(
p
-
mailbox
->
header_base
));
if
(
!
tab
||
tab
>
eol
||
!
eol
)
{
oldformat
=
1
;
if
(
!
eol
)
return
IMAP_MAILBOX_BADFORMAT
;
else
{
syslog
(
LOG_DEBUG
,
"mailbox '%s' has old cyrus.header"
,
mailbox
->
name
);
}
tab
=
eol
;
}
if
(
mailbox
->
quota
.
root
)
{
free
(
mailbox
->
quota
.
root
);
}
if
(
p
<
tab
)
{
mailbox
->
quota
.
root
=
xstrndup
(
p
,
tab
-
p
);
}
else
{
mailbox
->
quota
.
root
=
NULL
;
}
if
(
!
oldformat
)
{
/* read uniqueid */
p
=
tab
+
1
;
if
(
p
==
eol
)
return
IMAP_MAILBOX_BADFORMAT
;
mailbox
->
uniqueid
=
xstrndup
(
p
,
eol
-
p
);
}
else
{
/* uniqueid needs to be generated when we know the uidvalidity */
mailbox
->
uniqueid
=
NULL
;
}
/* Read names of user flags */
p
=
eol
+
1
;
eol
=
memchr
(
p
,
'\n'
,
mailbox
->
header_len
-
(
p
-
mailbox
->
header_base
));
if
(
!
eol
)
{
return
IMAP_MAILBOX_BADFORMAT
;
}
name
=
p
;
flag
=
0
;
while
(
name
<=
eol
&&
flag
<
MAX_USER_FLAGS
)
{
p
=
memchr
(
name
,
' '
,
eol
-
name
);
if
(
!
p
)
p
=
eol
;
if
(
mailbox
->
flagname
[
flag
])
free
(
mailbox
->
flagname
[
flag
]);
if
(
name
!=
p
)
{
mailbox
->
flagname
[
flag
++
]
=
xstrndup
(
name
,
p
-
name
);
}
else
{
mailbox
->
flagname
[
flag
++
]
=
NULL
;
}
name
=
p
+
1
;
}
while
(
flag
<
MAX_USER_FLAGS
)
{
if
(
mailbox
->
flagname
[
flag
])
free
(
mailbox
->
flagname
[
flag
]);
mailbox
->
flagname
[
flag
++
]
=
NULL
;
}
if
(
!
mailbox
->
uniqueid
)
{
char
buf
[
32
];
/* generate uniqueid */
mailbox_lock_header
(
mailbox
);
mailbox_open_index
(
mailbox
);
mailbox_make_uniqueid
(
mailbox
->
name
,
mailbox
->
uidvalidity
,
buf
,
sizeof
(
buf
));
mailbox
->
uniqueid
=
xstrdup
(
buf
);
mailbox_write_header
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
}
return
0
;
}
/*
* Read the acl out of the header of 'mailbox'
*/
int
mailbox_read_header_acl
(
struct
mailbox
*
mailbox
)
{
const
char
*
p
,
*
eol
;
/* Check magic number */
if
(
mailbox
->
header_len
<
sizeof
(
MAILBOX_HEADER_MAGIC
)
-1
||
strncmp
(
mailbox
->
header_base
,
MAILBOX_HEADER_MAGIC
,
sizeof
(
MAILBOX_HEADER_MAGIC
)
-1
))
{
return
IMAP_MAILBOX_BADFORMAT
;
}
/* Skip quota file pathname */
p
=
mailbox
->
header_base
+
sizeof
(
MAILBOX_HEADER_MAGIC
)
-1
;
eol
=
memchr
(
p
,
'\n'
,
mailbox
->
header_len
-
(
p
-
mailbox
->
header_base
));
if
(
!
eol
)
{
return
IMAP_MAILBOX_BADFORMAT
;
}
/* Skip names of user flags */
p
=
eol
+
1
;
eol
=
memchr
(
p
,
'\n'
,
mailbox
->
header_len
-
(
p
-
mailbox
->
header_base
));
if
(
!
eol
)
{
return
IMAP_MAILBOX_BADFORMAT
;
}
/* Read ACL */
p
=
eol
+
1
;
eol
=
memchr
(
p
,
'\n'
,
mailbox
->
header_len
-
(
p
-
mailbox
->
header_base
));
if
(
!
eol
)
{
return
IMAP_MAILBOX_BADFORMAT
;
}
free
(
mailbox
->
acl
);
mailbox
->
acl
=
xstrndup
(
p
,
eol
-
p
);
return
0
;
}
/*
* Read the the ACL for 'mailbox'.
*/
int
mailbox_read_acl
(
struct
mailbox
*
mailbox
,
struct
auth_state
*
auth_state
)
{
int
r
;
char
*
acl
;
r
=
mboxlist_lookup
(
mailbox
->
name
,
(
char
**
)
0
,
&
acl
,
NULL
);
if
(
r
)
return
r
;
free
(
mailbox
->
acl
);
mailbox
->
acl
=
xstrdup
(
acl
);
mailbox
->
myrights
=
cyrus_acl_myrights
(
auth_state
,
mailbox
->
acl
);
return
0
;
}
/*
* Read the header of the index file for mailbox
*/
int
mailbox_read_index_header
(
struct
mailbox
*
mailbox
)
{
struct
stat
sbuf
;
int
upgrade
=
0
;
int
quota_upgrade_offset
=
0
;
if
(
mailbox
->
index_fd
==
-1
)
return
IMAP_MAILBOX_BADFORMAT
;
fstat
(
mailbox
->
index_fd
,
&
sbuf
);
mailbox
->
index_ino
=
sbuf
.
st_ino
;
mailbox
->
index_mtime
=
sbuf
.
st_mtime
;
mailbox
->
index_size
=
sbuf
.
st_size
;
map_refresh
(
mailbox
->
index_fd
,
0
,
&
mailbox
->
index_base
,
&
mailbox
->
index_len
,
sbuf
.
st_size
,
"index"
,
mailbox
->
name
);
if
(
mailbox
->
index_len
<
OFFSET_POP3_LAST_LOGIN
||
(
mailbox
->
index_len
<
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_START_OFFSET
)))))
{
return
IMAP_MAILBOX_BADFORMAT
;
}
if
(
mailbox_doing_reconstruct
)
{
mailbox
->
generation_no
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_GENERATION_NO
)));
}
mailbox
->
format
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_FORMAT
)));
mailbox
->
minor_version
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_MINOR_VERSION
)));
if
(
mailbox
->
minor_version
<=
5
)
{
/* Upgrade Quota, Add Cache Version Field */
quota_upgrade_offset
=
sizeof
(
bit32
);
}
mailbox
->
start_offset
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_START_OFFSET
)));
mailbox
->
record_size
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_RECORD_SIZE
)));
mailbox
->
exists
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_EXISTS
)));
mailbox
->
last_appenddate
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_LAST_APPENDDATE
)));
mailbox
->
last_uid
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_LAST_UID
)));
mailbox
->
quota_mailbox_used
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_QUOTA_MAILBOX_USED
-
quota_upgrade_offset
)));
if
(
mailbox
->
start_offset
<
OFFSET_POP3_LAST_LOGIN
-
quota_upgrade_offset
+
sizeof
(
bit32
))
{
mailbox
->
pop3_last_login
=
0
;
}
else
{
mailbox
->
pop3_last_login
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_POP3_LAST_LOGIN
-
quota_upgrade_offset
)));
}
if
(
mailbox
->
start_offset
<
OFFSET_UIDVALIDITY
-
quota_upgrade_offset
+
sizeof
(
bit32
))
{
mailbox
->
uidvalidity
=
1
;
}
else
{
mailbox
->
uidvalidity
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
-
quota_upgrade_offset
+
OFFSET_UIDVALIDITY
)));
}
if
(
mailbox
->
start_offset
<
OFFSET_FLAGGED
-
quota_upgrade_offset
+
sizeof
(
bit32
))
{
/* calculate them now */
if
(
mailbox_calculate_flagcounts
(
mailbox
))
return
IMAP_IOERROR
;
upgrade
=
1
;
}
else
{
mailbox
->
deleted
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_DELETED
-
quota_upgrade_offset
)));
mailbox
->
answered
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_ANSWERED
-
quota_upgrade_offset
)));
mailbox
->
flagged
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_FLAGGED
-
quota_upgrade_offset
)));
mailbox
->
dirty
=
0
;
}
if
(
mailbox
->
start_offset
<
OFFSET_POP3_NEW_UIDL
-
quota_upgrade_offset
+
sizeof
(
bit32
))
{
mailbox
->
pop3_new_uidl
=
!
mailbox
->
exists
;
upgrade
=
1
;
}
else
{
mailbox
->
pop3_new_uidl
=
!
mailbox
->
exists
||
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_POP3_NEW_UIDL
-
quota_upgrade_offset
)));
}
if
(
mailbox
->
start_offset
<
OFFSET_LEAKED_CACHE
-
quota_upgrade_offset
+
sizeof
(
bit32
))
{
mailbox
->
leaked_cache_records
=
0
;
upgrade
=
1
;
}
else
{
mailbox
->
leaked_cache_records
=
ntohl
(
*
((
bit32
*
)(
mailbox
->
index_base
+
OFFSET_LEAKED_CACHE
-
quota_upgrade_offset
)));
}
if
(
mailbox
->
record_size
<
INDEX_RECORD_SIZE
)
{
upgrade
=
1
;
}
if
(
upgrade
)
{
if
(
mailbox_upgrade_index
(
mailbox
))
return
IMAP_IOERROR
;
/* things might have been changed out from under us. reread */
return
mailbox_open_index
(
mailbox
);
}
if
(
!
mailbox_doing_reconstruct
&&
(
mailbox
->
minor_version
<
MAILBOX_MINOR_VERSION
))
{
return
IMAP_MAILBOX_BADFORMAT
;
}
return
0
;
}
/*
* Read an index record from a mailbox
*/
int
mailbox_read_index_record
(
mailbox
,
msgno
,
record
)
struct
mailbox
*
mailbox
;
unsigned
msgno
;
struct
index_record
*
record
;
{
unsigned
long
offset
;
unsigned
const
char
*
buf
;
int
n
;
offset
=
mailbox
->
start_offset
+
(
msgno
-1
)
*
mailbox
->
record_size
;
if
(
offset
+
INDEX_RECORD_SIZE
>
mailbox
->
index_len
)
{
syslog
(
LOG_ERR
,
"IOERROR: index record %u for %s past end of file"
,
msgno
,
mailbox
->
name
);
return
IMAP_IOERROR
;
}
buf
=
(
unsigned
char
*
)
mailbox
->
index_base
+
offset
;
record
->
uid
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_UID
)));
record
->
internaldate
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_INTERNALDATE
)));
record
->
sentdate
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_SENTDATE
)));
record
->
size
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_SIZE
)));
record
->
header_size
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_HEADER_SIZE
)));
record
->
content_offset
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_CONTENT_OFFSET
)));
record
->
cache_offset
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_CACHE_OFFSET
)));
record
->
last_updated
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_LAST_UPDATED
)));
record
->
system_flags
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_SYSTEM_FLAGS
)));
for
(
n
=
0
;
n
<
MAX_USER_FLAGS
/
32
;
n
++
)
{
record
->
user_flags
[
n
]
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_USER_FLAGS
+
4
*
n
)));
}
record
->
content_lines
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_CONTENT_LINES
)));
record
->
cache_version
=
htonl
(
*
((
bit32
*
)(
buf
+
OFFSET_CACHE_VERSION
)));
return
0
;
}
/*
* Lock the header for 'mailbox'. Reread header if necessary.
*/
int
mailbox_lock_header
(
mailbox
)
struct
mailbox
*
mailbox
;
{
char
fnamebuf
[
MAX_MAILBOX_PATH
+
1
];
struct
stat
sbuf
;
const
char
*
lockfailaction
;
int
r
;
if
(
mailbox
->
header_lock_count
++
)
return
0
;
assert
(
mailbox
->
index_lock_count
==
0
);
assert
(
mailbox
->
seen_lock_count
==
0
);
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_HEADER
,
sizeof
(
fnamebuf
));
r
=
lock_reopen
(
mailbox
->
header_fd
,
fnamebuf
,
&
sbuf
,
&
lockfailaction
);
if
(
r
)
{
mailbox
->
header_lock_count
--
;
syslog
(
LOG_ERR
,
"IOERROR: %s header for %s: %m"
,
lockfailaction
,
mailbox
->
name
);
return
IMAP_IOERROR
;
}
if
(
sbuf
.
st_ino
!=
mailbox
->
header_ino
)
{
map_free
(
&
mailbox
->
header_base
,
&
mailbox
->
header_len
);
map_refresh
(
mailbox
->
header_fd
,
1
,
&
mailbox
->
header_base
,
&
mailbox
->
header_len
,
sbuf
.
st_size
,
"header"
,
mailbox
->
name
);
mailbox
->
header_ino
=
sbuf
.
st_ino
;
r
=
mailbox_read_header
(
mailbox
);
if
(
r
&&
!
mailbox_doing_reconstruct
)
{
mailbox_unlock_header
(
mailbox
);
return
r
;
}
}
return
0
;
}
/*
* Lock the index file for 'mailbox'. Reread index file header if necessary.
*/
int
mailbox_lock_index
(
mailbox
)
struct
mailbox
*
mailbox
;
{
char
fnamebuf
[
MAX_MAILBOX_PATH
+
1
];
struct
stat
sbuffd
,
sbuffile
;
int
r
;
if
(
mailbox
->
index_lock_count
++
)
return
0
;
assert
(
mailbox
->
seen_lock_count
==
0
);
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_INDEX
,
sizeof
(
fnamebuf
));
/* why is this not a lock_reopen()? -leg
*
* this is not a lock_reopen() because we need to do all of the
* work of mailbox_open_index, not just opening the file --
* presumably we could extend the api of lock_reopen to tell us if
* it had to reopen the file or not, then this could be a bit smarter --
* but we'd still have to deal with the fact that mailbox_open_index
* does its own open() call. -rjs3 */
for
(;;)
{
r
=
lock_blocking
(
mailbox
->
index_fd
);
if
(
r
==
-1
)
{
mailbox
->
index_lock_count
--
;
syslog
(
LOG_ERR
,
"IOERROR: locking index for %s: %m"
,
mailbox
->
name
);
return
IMAP_IOERROR
;
}
fstat
(
mailbox
->
index_fd
,
&
sbuffd
);
r
=
stat
(
fnamebuf
,
&
sbuffile
);
if
(
r
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: stating index for %s: %m"
,
mailbox
->
name
);
mailbox_unlock_index
(
mailbox
);
return
IMAP_IOERROR
;
}
if
(
sbuffd
.
st_ino
==
sbuffile
.
st_ino
)
break
;
if
((
r
=
mailbox_open_index
(
mailbox
)))
{
return
r
;
}
}
r
=
mailbox_read_index_header
(
mailbox
);
if
(
r
&&
!
mailbox_doing_reconstruct
)
{
mailbox_unlock_index
(
mailbox
);
return
r
;
}
return
0
;
}
/*
* Place a POP lock on 'mailbox'.
*/
int
mailbox_lock_pop
(
mailbox
)
struct
mailbox
*
mailbox
;
{
int
r
=
-1
;
if
(
mailbox
->
pop_lock_count
++
)
return
0
;
r
=
lock_nonblocking
(
mailbox
->
cache_fd
);
if
(
r
==
-1
)
{
mailbox
->
pop_lock_count
--
;
if
(
errno
==
EWOULDBLOCK
||
errno
==
EAGAIN
||
errno
==
EACCES
)
{
return
IMAP_MAILBOX_POPLOCKED
;
}
syslog
(
LOG_ERR
,
"IOERROR: locking cache for %s: %m"
,
mailbox
->
name
);
return
IMAP_IOERROR
;
}
return
0
;
}
/*
* Release lock on the header for 'mailbox'
*/
void
mailbox_unlock_header
(
struct
mailbox
*
mailbox
)
{
assert
(
mailbox
->
header_lock_count
!=
0
);
if
(
--
mailbox
->
header_lock_count
==
0
)
{
if
(
lock_unlock
(
mailbox
->
header_fd
))
syslog
(
LOG_ERR
,
"IOERROR: unlocking header of %s: %m"
,
mailbox
->
name
);
}
}
/*
* Release lock on the index file for 'mailbox'
*/
void
mailbox_unlock_index
(
mailbox
)
struct
mailbox
*
mailbox
;
{
assert
(
mailbox
->
index_lock_count
!=
0
);
if
(
--
mailbox
->
index_lock_count
==
0
)
{
if
(
lock_unlock
(
mailbox
->
index_fd
))
syslog
(
LOG_ERR
,
"IOERROR: unlocking index of %s: %m"
,
mailbox
->
name
);
}
}
/*
* Release POP lock for 'mailbox'
*/
void
mailbox_unlock_pop
(
mailbox
)
struct
mailbox
*
mailbox
;
{
assert
(
mailbox
->
pop_lock_count
!=
0
);
if
(
--
mailbox
->
pop_lock_count
==
0
)
{
if
(
lock_unlock
(
mailbox
->
cache_fd
))
syslog
(
LOG_ERR
,
"IOERROR: unlocking POP lock of %s: %m"
,
mailbox
->
name
);
}
}
/*
* Write the header file for 'mailbox'
*/
int
mailbox_write_header
(
struct
mailbox
*
mailbox
)
{
int
flag
;
int
newheader_fd
;
int
r
=
0
;
const
char
*
quota_root
;
char
fnamebuf
[
MAX_MAILBOX_PATH
+
1
];
char
newfnamebuf
[
MAX_MAILBOX_PATH
+
1
];
struct
stat
sbuf
;
struct
iovec
iov
[
10
];
int
niov
;
assert
(
mailbox
->
header_lock_count
!=
0
);
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_HEADER
,
sizeof
(
fnamebuf
));
strlcpy
(
newfnamebuf
,
fnamebuf
,
sizeof
(
newfnamebuf
));
strlcat
(
newfnamebuf
,
".NEW"
,
sizeof
(
newfnamebuf
));
newheader_fd
=
open
(
newfnamebuf
,
O_CREAT
|
O_TRUNC
|
O_RDWR
,
0666
);
if
(
newheader_fd
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: writing %s: %m"
,
newfnamebuf
);
return
IMAP_IOERROR
;
}
/* Write magic header, do NOT write the trailing NUL */
r
=
write
(
newheader_fd
,
MAILBOX_HEADER_MAGIC
,
sizeof
(
MAILBOX_HEADER_MAGIC
)
-
1
);
if
(
r
!=
-1
)
{
niov
=
0
;
quota_root
=
mailbox
->
quota
.
root
?
mailbox
->
quota
.
root
:
""
;
WRITEV_ADDSTR_TO_IOVEC
(
iov
,
niov
,(
char
*
)
quota_root
);
WRITEV_ADD_TO_IOVEC
(
iov
,
niov
,
"
\t
"
,
1
);
WRITEV_ADDSTR_TO_IOVEC
(
iov
,
niov
,
mailbox
->
uniqueid
);
WRITEV_ADD_TO_IOVEC
(
iov
,
niov
,
"
\n
"
,
1
);
r
=
retry_writev
(
newheader_fd
,
iov
,
niov
);
}
if
(
r
!=
-1
)
{
for
(
flag
=
0
;
flag
<
MAX_USER_FLAGS
;
flag
++
)
{
if
(
mailbox
->
flagname
[
flag
])
{
niov
=
0
;
WRITEV_ADDSTR_TO_IOVEC
(
iov
,
niov
,
mailbox
->
flagname
[
flag
]);
WRITEV_ADD_TO_IOVEC
(
iov
,
niov
,
" "
,
1
);
r
=
retry_writev
(
newheader_fd
,
iov
,
niov
);
if
(
r
==
-1
)
break
;
}
}
}
if
(
r
!=
-1
)
{
niov
=
0
;
WRITEV_ADD_TO_IOVEC
(
iov
,
niov
,
"
\n
"
,
1
);
WRITEV_ADDSTR_TO_IOVEC
(
iov
,
niov
,
mailbox
->
acl
);
WRITEV_ADD_TO_IOVEC
(
iov
,
niov
,
"
\n
"
,
1
);
r
=
retry_writev
(
newheader_fd
,
iov
,
niov
);
}
if
(
r
==
-1
||
fsync
(
newheader_fd
)
||
lock_blocking
(
newheader_fd
)
==
-1
||
rename
(
newfnamebuf
,
fnamebuf
)
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: writing %s: %m"
,
newfnamebuf
);
close
(
newheader_fd
);
unlink
(
newfnamebuf
);
return
IMAP_IOERROR
;
}
if
(
mailbox
->
header_fd
!=
-1
)
{
close
(
mailbox
->
header_fd
);
map_free
(
&
mailbox
->
header_base
,
&
mailbox
->
header_len
);
}
mailbox
->
header_fd
=
newheader_fd
;
if
(
fstat
(
mailbox
->
header_fd
,
&
sbuf
)
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: fstating %s: %m"
,
fnamebuf
);
fatal
(
"can't fstat header file"
,
EC_OSFILE
);
}
map_refresh
(
mailbox
->
header_fd
,
1
,
&
mailbox
->
header_base
,
&
mailbox
->
header_len
,
sbuf
.
st_size
,
"header"
,
mailbox
->
name
);
mailbox
->
header_ino
=
sbuf
.
st_ino
;
return
0
;
}
/*
* Write the index header for 'mailbox'
*/
int
mailbox_write_index_header
(
struct
mailbox
*
mailbox
)
{
char
buf
[
INDEX_HEADER_SIZE
];
unsigned
long
header_size
=
sizeof
(
buf
);
int
n
;
assert
(
mailbox
->
index_lock_count
!=
0
);
*
((
bit32
*
)(
buf
+
OFFSET_GENERATION_NO
))
=
htonl
(
mailbox
->
generation_no
);
*
((
bit32
*
)(
buf
+
OFFSET_FORMAT
))
=
htonl
(
mailbox
->
format
);
*
((
bit32
*
)(
buf
+
OFFSET_MINOR_VERSION
))
=
htonl
(
mailbox
->
minor_version
);
*
((
bit32
*
)(
buf
+
OFFSET_START_OFFSET
))
=
htonl
(
mailbox
->
start_offset
);
*
((
bit32
*
)(
buf
+
OFFSET_RECORD_SIZE
))
=
htonl
(
mailbox
->
record_size
);
*
((
bit32
*
)(
buf
+
OFFSET_EXISTS
))
=
htonl
(
mailbox
->
exists
);
*
((
bit32
*
)(
buf
+
OFFSET_LAST_APPENDDATE
))
=
htonl
(
mailbox
->
last_appenddate
);
*
((
bit32
*
)(
buf
+
OFFSET_LAST_UID
))
=
htonl
(
mailbox
->
last_uid
);
*
((
bit32
*
)(
buf
+
OFFSET_QUOTA_RESERVED_FIELD
))
=
htonl
(
0
);
/* RESERVED */
*
((
bit32
*
)(
buf
+
OFFSET_QUOTA_MAILBOX_USED
))
=
htonl
(
mailbox
->
quota_mailbox_used
);
*
((
bit32
*
)(
buf
+
OFFSET_POP3_LAST_LOGIN
))
=
htonl
(
mailbox
->
pop3_last_login
);
*
((
bit32
*
)(
buf
+
OFFSET_UIDVALIDITY
))
=
htonl
(
mailbox
->
uidvalidity
);
*
((
bit32
*
)(
buf
+
OFFSET_DELETED
))
=
htonl
(
mailbox
->
deleted
);
*
((
bit32
*
)(
buf
+
OFFSET_ANSWERED
))
=
htonl
(
mailbox
->
answered
);
*
((
bit32
*
)(
buf
+
OFFSET_FLAGGED
))
=
htonl
(
mailbox
->
flagged
);
*
((
bit32
*
)(
buf
+
OFFSET_POP3_NEW_UIDL
))
=
htonl
(
mailbox
->
pop3_new_uidl
);
*
((
bit32
*
)(
buf
+
OFFSET_LEAKED_CACHE
))
=
htonl
(
mailbox
->
leaked_cache_records
);
*
((
bit32
*
)(
buf
+
OFFSET_SPARE1
))
=
htonl
(
0
);
/* RESERVED */
*
((
bit32
*
)(
buf
+
OFFSET_SPARE2
))
=
htonl
(
0
);
/* RESERVED */
if
(
mailbox
->
start_offset
<
header_size
)
header_size
=
mailbox
->
start_offset
;
lseek
(
mailbox
->
index_fd
,
0
,
SEEK_SET
);
n
=
retry_write
(
mailbox
->
index_fd
,
buf
,
header_size
);
if
((
unsigned
long
)
n
!=
header_size
||
fsync
(
mailbox
->
index_fd
))
{
syslog
(
LOG_ERR
,
"IOERROR: writing index header for %s: %m"
,
mailbox
->
name
);
return
IMAP_IOERROR
;
}
if
(
updatenotifier
)
updatenotifier
(
mailbox
);
return
0
;
}
/*
* Put an index record into a buffer suitable for writing to a file.
*/
void
mailbox_index_record_to_buf
(
struct
index_record
*
record
,
char
*
buf
)
{
int
n
;
*
((
bit32
*
)(
buf
+
OFFSET_UID
))
=
htonl
(
record
->
uid
);
*
((
bit32
*
)(
buf
+
OFFSET_INTERNALDATE
))
=
htonl
(
record
->
internaldate
);
*
((
bit32
*
)(
buf
+
OFFSET_SENTDATE
))
=
htonl
(
record
->
sentdate
);
*
((
bit32
*
)(
buf
+
OFFSET_SIZE
))
=
htonl
(
record
->
size
);
*
((
bit32
*
)(
buf
+
OFFSET_HEADER_SIZE
))
=
htonl
(
record
->
header_size
);
*
((
bit32
*
)(
buf
+
OFFSET_CONTENT_OFFSET
))
=
htonl
(
record
->
content_offset
);
*
((
bit32
*
)(
buf
+
OFFSET_CACHE_OFFSET
))
=
htonl
(
record
->
cache_offset
);
*
((
bit32
*
)(
buf
+
OFFSET_LAST_UPDATED
))
=
htonl
(
record
->
last_updated
);
*
((
bit32
*
)(
buf
+
OFFSET_SYSTEM_FLAGS
))
=
htonl
(
record
->
system_flags
);
for
(
n
=
0
;
n
<
MAX_USER_FLAGS
/
32
;
n
++
)
{
*
((
bit32
*
)(
buf
+
OFFSET_USER_FLAGS
+
4
*
n
))
=
htonl
(
record
->
user_flags
[
n
]);
}
*
((
bit32
*
)(
buf
+
OFFSET_CONTENT_LINES
))
=
htonl
(
record
->
content_lines
);
*
((
bit32
*
)(
buf
+
OFFSET_CACHE_VERSION
))
=
htonl
(
record
->
cache_version
);
}
/*
* Write an index record to a mailbox
* call fsync() on index_fd if 'sync' is true
*/
int
mailbox_write_index_record
(
struct
mailbox
*
mailbox
,
unsigned
msgno
,
struct
index_record
*
record
,
int
sync
)
{
int
n
;
char
buf
[
INDEX_RECORD_SIZE
];
mailbox_index_record_to_buf
(
record
,
buf
);
n
=
lseek
(
mailbox
->
index_fd
,
mailbox
->
start_offset
+
(
msgno
-1
)
*
mailbox
->
record_size
,
SEEK_SET
);
if
(
n
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: seeking index record %u for %s: %m"
,
msgno
,
mailbox
->
name
);
return
IMAP_IOERROR
;
}
n
=
retry_write
(
mailbox
->
index_fd
,
buf
,
INDEX_RECORD_SIZE
);
if
(
n
!=
INDEX_RECORD_SIZE
||
(
sync
&&
fsync
(
mailbox
->
index_fd
)))
{
syslog
(
LOG_ERR
,
"IOERROR: writing index record %u for %s: %m"
,
msgno
,
mailbox
->
name
);
return
IMAP_IOERROR
;
}
return
0
;
}
/*
* Append a new record to the index file
* call fsync() on index_fd if 'sync' is true
*/
int
mailbox_append_index
(
struct
mailbox
*
mailbox
,
struct
index_record
*
record
,
unsigned
start
,
unsigned
num
,
int
sync
)
{
unsigned
i
;
int
len
,
n
;
char
*
buf
,
*
p
;
long
last_offset
;
time_t
now
=
time
(
NULL
);
assert
(
mailbox
->
index_lock_count
!=
0
);
if
(
mailbox
->
record_size
<
INDEX_RECORD_SIZE
)
{
return
IMAP_MAILBOX_BADFORMAT
;
}
len
=
num
*
mailbox
->
record_size
;
buf
=
xmalloc
(
len
);
memset
(
buf
,
0
,
len
);
for
(
i
=
0
;
i
<
num
;
i
++
)
{
/* Sanity check the timestamps so index_fetchreply() won't abort() */
if
(
record
[
i
].
internaldate
<=
0
)
record
[
i
].
internaldate
=
now
;
if
(
record
[
i
].
sentdate
<=
0
)
record
[
i
].
sentdate
=
now
;
if
(
record
[
i
].
last_updated
<=
0
)
record
[
i
].
internaldate
=
now
;
p
=
buf
+
i
*
mailbox
->
record_size
;
mailbox_index_record_to_buf
(
&
record
[
i
],
p
);
}
last_offset
=
mailbox
->
start_offset
+
start
*
mailbox
->
record_size
;
lseek
(
mailbox
->
index_fd
,
last_offset
,
SEEK_SET
);
n
=
retry_write
(
mailbox
->
index_fd
,
buf
,
len
);
free
(
buf
);
if
(
n
!=
len
||
(
sync
&&
fsync
(
mailbox
->
index_fd
)))
{
syslog
(
LOG_ERR
,
"IOERROR: appending index records for %s: %m"
,
mailbox
->
name
);
ftruncate
(
mailbox
->
index_fd
,
last_offset
);
return
IMAP_IOERROR
;
}
return
0
;
}
/*
* Lock the index file for 'mailbox'.
* DON'T Reread index file header if necessary.
*/
static
int
mailbox_lock_index_for_upgrade
(
struct
mailbox
*
mailbox
)
{
char
fnamebuf
[
MAX_MAILBOX_PATH
+
1
];
struct
stat
sbuffd
,
sbuffile
;
int
r
;
if
(
mailbox
->
index_lock_count
++
)
return
0
;
assert
(
mailbox
->
seen_lock_count
==
0
);
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_INDEX
,
sizeof
(
fnamebuf
));
for
(;;)
{
r
=
lock_blocking
(
mailbox
->
index_fd
);
if
(
r
==
-1
)
{
mailbox
->
index_lock_count
--
;
syslog
(
LOG_ERR
,
"IOERROR: locking index for %s: %m"
,
mailbox
->
name
);
return
IMAP_IOERROR
;
}
fstat
(
mailbox
->
index_fd
,
&
sbuffd
);
r
=
stat
(
fnamebuf
,
&
sbuffile
);
if
(
r
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: stating index for %s: %m"
,
mailbox
->
name
);
mailbox_unlock_index
(
mailbox
);
return
IMAP_IOERROR
;
}
if
(
sbuffd
.
st_ino
==
sbuffile
.
st_ino
)
break
;
if
((
r
=
mailbox_open_index
(
mailbox
)))
{
return
r
;
}
}
return
0
;
}
/*
* Upgrade the index header for 'mailbox'
*/
static
int
mailbox_upgrade_index
(
struct
mailbox
*
mailbox
)
{
int
r
;
unsigned
msgno
;
bit32
oldstart_offset
,
oldrecord_size
,
recsize_diff
;
char
buf
[
INDEX_HEADER_SIZE
>
INDEX_RECORD_SIZE
?
INDEX_HEADER_SIZE
:
INDEX_RECORD_SIZE
];
char
fnamebuf
[
MAX_MAILBOX_PATH
+
1
],
fnamebufnew
[
MAX_MAILBOX_PATH
+
1
];
size_t
fnamebuf_len
;
FILE
*
newindex
;
char
*
fnametail
;
char
*
bufp
;
/* Lock files and open new index file */
r
=
mailbox_lock_header
(
mailbox
);
if
(
r
)
return
r
;
r
=
mailbox_lock_index_for_upgrade
(
mailbox
);
if
(
r
)
{
mailbox_unlock_header
(
mailbox
);
return
r
;
}
r
=
mailbox_lock_pop
(
mailbox
);
if
(
r
)
{
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
return
r
;
}
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_INDEX
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
".NEW"
,
sizeof
(
fnamebuf
));
newindex
=
fopen
(
fnamebuf
,
"w+"
);
if
(
!
newindex
)
{
syslog
(
LOG_ERR
,
"IOERROR: creating %s: %m"
,
fnamebuf
);
goto
fail
;
}
/* change version number */
mailbox
->
minor_version
=
MAILBOX_MINOR_VERSION
;
/* save old start_offset; change start_offset */
oldstart_offset
=
mailbox
->
start_offset
;
mailbox
->
start_offset
=
INDEX_HEADER_SIZE
;
/* save old record_size; change record_size */
oldrecord_size
=
mailbox
->
record_size
;
mailbox
->
record_size
=
INDEX_RECORD_SIZE
;
recsize_diff
=
INDEX_RECORD_SIZE
-
oldrecord_size
;
/* Write the new index header */
memset
(
buf
,
0
,
INDEX_HEADER_SIZE
);
*
((
bit32
*
)(
buf
+
OFFSET_GENERATION_NO
))
=
htonl
(
mailbox
->
generation_no
);
*
((
bit32
*
)(
buf
+
OFFSET_FORMAT
))
=
htonl
(
mailbox
->
format
);
*
((
bit32
*
)(
buf
+
OFFSET_MINOR_VERSION
))
=
htonl
(
mailbox
->
minor_version
);
*
((
bit32
*
)(
buf
+
OFFSET_START_OFFSET
))
=
htonl
(
mailbox
->
start_offset
);
*
((
bit32
*
)(
buf
+
OFFSET_RECORD_SIZE
))
=
htonl
(
mailbox
->
record_size
);
*
((
bit32
*
)(
buf
+
OFFSET_EXISTS
))
=
htonl
(
mailbox
->
exists
);
*
((
bit32
*
)(
buf
+
OFFSET_LAST_APPENDDATE
))
=
htonl
(
mailbox
->
last_appenddate
);
*
((
bit32
*
)(
buf
+
OFFSET_LAST_UID
))
=
htonl
(
mailbox
->
last_uid
);
/* OFFSET_QUOTA_RESERVED_FIELD left as zero */
*
((
bit32
*
)(
buf
+
OFFSET_QUOTA_MAILBOX_USED
))
=
htonl
(
mailbox
->
quota_mailbox_used
);
*
((
bit32
*
)(
buf
+
OFFSET_POP3_LAST_LOGIN
))
=
htonl
(
mailbox
->
pop3_last_login
);
*
((
bit32
*
)(
buf
+
OFFSET_UIDVALIDITY
))
=
htonl
(
mailbox
->
uidvalidity
);
*
((
bit32
*
)(
buf
+
OFFSET_DELETED
))
=
htonl
(
mailbox
->
deleted
);
*
((
bit32
*
)(
buf
+
OFFSET_ANSWERED
))
=
htonl
(
mailbox
->
answered
);
*
((
bit32
*
)(
buf
+
OFFSET_FLAGGED
))
=
htonl
(
mailbox
->
flagged
);
*
((
bit32
*
)(
buf
+
OFFSET_POP3_NEW_UIDL
))
=
htonl
(
mailbox
->
pop3_new_uidl
);
*
((
bit32
*
)(
buf
+
OFFSET_LEAKED_CACHE
))
=
htonl
(
mailbox
->
leaked_cache_records
);
fwrite
(
buf
,
1
,
INDEX_HEADER_SIZE
,
newindex
);
/* Write the rest of new index */
memset
(
buf
,
0
,
INDEX_RECORD_SIZE
);
for
(
msgno
=
1
;
msgno
<=
mailbox
->
exists
;
msgno
++
)
{
/* Write the existing (old) part of the index record */
bufp
=
(
char
*
)
(
mailbox
->
index_base
+
oldstart_offset
+
(
msgno
-
1
)
*
oldrecord_size
);
fwrite
(
bufp
,
oldrecord_size
,
1
,
newindex
);
if
(
recsize_diff
)
{
/* We need to upgrade the index record to include new fields. */
/* Currently, this means adding a content_lines placeholder.
* We use BIT32_MAX rather than 0, since a message body can
* be empty. We'll calculate the actual value on demand.
*/
if
(
oldrecord_size
<
OFFSET_CONTENT_LINES
+
sizeof
(
bit32
))
{
*
((
bit32
*
)(
buf
+
OFFSET_CONTENT_LINES
))
=
htonl
(
BIT32_MAX
);
}
/* Set the initial cache version to 0, that is, with the old
* format of the cached headers */
if
(
oldrecord_size
<
OFFSET_CACHE_VERSION
+
sizeof
(
bit32
))
{
*
((
bit32
*
)(
buf
+
OFFSET_CACHE_VERSION
))
=
htonl
(
0
);
}
fwrite
(
buf
+
oldrecord_size
,
recsize_diff
,
1
,
newindex
);
}
}
/* Ensure everything made it to disk */
fflush
(
newindex
);
if
(
ferror
(
newindex
)
||
fsync
(
fileno
(
newindex
)))
{
syslog
(
LOG_ERR
,
"IOERROR: writing index for %s: %m"
,
mailbox
->
name
);
goto
fail
;
}
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
fnamebuf_len
=
strlen
(
fnamebuf
);
fnametail
=
fnamebuf
+
fnamebuf_len
;
strlcpy
(
fnametail
,
FNAME_INDEX
,
sizeof
(
fnamebuf
)
-
fnamebuf_len
);
strlcpy
(
fnamebufnew
,
fnamebuf
,
sizeof
(
fnamebufnew
));
strlcat
(
fnamebufnew
,
".NEW"
,
sizeof
(
fnamebufnew
));
if
(
rename
(
fnamebufnew
,
fnamebuf
))
{
syslog
(
LOG_ERR
,
"IOERROR: renaming index file for %s: %m"
,
mailbox
->
name
);
goto
fail
;
}
mailbox_unlock_pop
(
mailbox
);
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
fclose
(
newindex
);
return
0
;
fail
:
mailbox_unlock_pop
(
mailbox
);
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
return
IMAP_IOERROR
;
}
/*
* Calculate the number of messages in the mailbox with
* answered/deleted/flagged system flags
*/
static
int
mailbox_calculate_flagcounts
(
struct
mailbox
*
mailbox
)
{
int
r
;
unsigned
msgno
;
bit32
numansweredflag
=
0
;
bit32
numdeletedflag
=
0
;
bit32
numflaggedflag
=
0
;
struct
stat
sbuf
;
char
*
bufp
;
/* Lock files */
r
=
mailbox_lock_header
(
mailbox
);
if
(
r
)
return
r
;
r
=
mailbox_lock_index_for_upgrade
(
mailbox
);
if
(
r
)
{
mailbox_unlock_header
(
mailbox
);
return
r
;
}
r
=
mailbox_lock_pop
(
mailbox
);
if
(
r
)
{
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
return
r
;
}
if
(
fstat
(
mailbox
->
cache_fd
,
&
sbuf
)
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: fstating %s: %m"
,
mailbox
->
name
);
fatal
(
"can't fstat cache file"
,
EC_OSFILE
);
}
mailbox
->
cache_size
=
sbuf
.
st_size
;
map_refresh
(
mailbox
->
cache_fd
,
0
,
&
mailbox
->
cache_base
,
&
mailbox
->
cache_len
,
mailbox
->
cache_size
,
"cache"
,
mailbox
->
name
);
/* for each message look at the system flags */
for
(
msgno
=
1
;
msgno
<=
mailbox
->
exists
;
msgno
++
)
{
bit32
sysflags
;
bufp
=
(
char
*
)
(
mailbox
->
index_base
+
mailbox
->
start_offset
+
(
msgno
-
1
)
*
mailbox
->
record_size
);
/* Sanity check */
if
(
*
((
bit32
*
)(
bufp
+
OFFSET_UID
))
==
0
)
{
syslog
(
LOG_ERR
,
"IOERROR: %s zero index record %u/%lu"
,
mailbox
->
name
,
msgno
,
mailbox
->
exists
);
mailbox_unlock_pop
(
mailbox
);
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
return
IMAP_IOERROR
;
}
sysflags
=
ntohl
(
*
((
bit32
*
)(
bufp
+
OFFSET_SYSTEM_FLAGS
)));
if
(
sysflags
&
FLAG_ANSWERED
)
numansweredflag
++
;
if
(
sysflags
&
FLAG_DELETED
)
numdeletedflag
++
;
if
(
sysflags
&
FLAG_FLAGGED
)
numflaggedflag
++
;
}
mailbox
->
answered
=
numansweredflag
;
mailbox
->
deleted
=
numdeletedflag
;
mailbox
->
flagged
=
numflaggedflag
;
mailbox_unlock_pop
(
mailbox
);
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
return
0
;
}
/*
* Perform an expunge operation on 'mailbox'. If 'iscurrentdir' is nonzero,
* the current directory is set to the mailbox directory. If nonzero, the
* function pointed to by 'decideproc' is called (with 'deciderock') to
* determine which messages to expunge. If 'decideproc' is a null pointer,
* then messages with the \Deleted flag are expunged.
*/
int
mailbox_expunge
(
struct
mailbox
*
mailbox
,
int
iscurrentdir
,
mailbox_decideproc_t
*
decideproc
,
void
*
deciderock
)
{
int
r
,
n
;
char
fnamebuf
[
MAX_MAILBOX_PATH
+
1
],
fnamebufnew
[
MAX_MAILBOX_PATH
+
1
];
size_t
fnamebuf_len
;
FILE
*
newindex
=
NULL
,
*
newcache
=
NULL
;
unsigned
long
*
deleted
;
unsigned
numdeleted
=
0
,
quotadeleted
=
0
;
unsigned
numansweredflag
=
0
;
unsigned
numdeletedflag
=
0
;
unsigned
numflaggedflag
=
0
;
unsigned
newexpunged
;
unsigned
newexists
;
unsigned
newdeleted
;
unsigned
newanswered
;
unsigned
newflagged
;
char
*
buf
;
unsigned
msgno
;
struct
stat
sbuf
;
char
*
fnametail
;
struct
txn
*
tid
=
NULL
;
/* Offset into the new cache file for use when updating the index record */
size_t
new_cache_total_size
=
sizeof
(
bit32
);
/* flag => true if we are compressing the cache on this expunge */
int
fixcache
=
0
;
/* Lock files and open new index/cache files */
r
=
mailbox_lock_header
(
mailbox
);
if
(
r
)
return
r
;
r
=
mailbox_lock_index
(
mailbox
);
if
(
r
)
{
mailbox_unlock_header
(
mailbox
);
return
r
;
}
r
=
mailbox_lock_pop
(
mailbox
);
if
(
r
)
{
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
return
r
;
}
#if 0
/* Decide if we are going to prune the cache file. Currently run if
* we have a combined total of (flagged) deleted messages and
* already leaked records that is >= floor(10% of the mailbox size) */
newexists = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_EXISTS)));
newdeleted = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_DELETED)));
newexpunged = ntohl(*((bit32 *)(mailbox->index_base+OFFSET_LEAKED_CACHE)));
if(newdeleted + newexpunged >= newexists / 10) {
newexpunged = 0;
fixcache = 1;
syslog(LOG_DEBUG, "Flushing cache file: %s", mailbox->name);
}
#else
/* Currently we aren't sure we want to actually orphan entries during
* an expunge, so we'll just force cleanup every time */
newexpunged
=
0
;
fixcache
=
1
;
#endif
if
(
fixcache
)
{
if
(
fstat
(
mailbox
->
cache_fd
,
&
sbuf
)
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: fstating %s: %m"
,
fnamebuf
);
/* xxx is fnamebuf initialized??? */
fatal
(
"can't fstat cache file"
,
EC_OSFILE
);
}
mailbox
->
cache_size
=
sbuf
.
st_size
;
map_refresh
(
mailbox
->
cache_fd
,
0
,
&
mailbox
->
cache_base
,
&
mailbox
->
cache_len
,
mailbox
->
cache_size
,
"cache"
,
mailbox
->
name
);
}
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_INDEX
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
".NEW"
,
sizeof
(
fnamebuf
));
newindex
=
fopen
(
fnamebuf
,
"w+"
);
if
(
!
newindex
)
{
syslog
(
LOG_ERR
,
"IOERROR: creating %s: %m"
,
fnamebuf
);
mailbox_unlock_pop
(
mailbox
);
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
return
IMAP_IOERROR
;
}
if
(
fixcache
)
{
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
FNAME_CACHE
,
sizeof
(
fnamebuf
));
strlcat
(
fnamebuf
,
".NEW"
,
sizeof
(
fnamebuf
));
newcache
=
fopen
(
fnamebuf
,
"w+"
);
if
(
!
newcache
)
{
syslog
(
LOG_ERR
,
"IOERROR: creating %s: %m"
,
fnamebuf
);
fclose
(
newindex
);
mailbox_unlock_pop
(
mailbox
);
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
return
IMAP_IOERROR
;
}
}
/* Allocate temporary buffers */
if
(
mailbox
->
exists
>
0
)
{
/* XXX kludge: not all mallocs return a valid pointer to 0 bytes;
some have the good sense to return 0 */
deleted
=
(
unsigned
long
*
)
xmalloc
(
mailbox
->
exists
*
sizeof
(
unsigned
long
));
}
else
{
deleted
=
0
;
}
buf
=
xmalloc
(
mailbox
->
start_offset
>
mailbox
->
record_size
?
mailbox
->
start_offset
:
mailbox
->
record_size
);
/* Copy over headers */
memcpy
(
buf
,
mailbox
->
index_base
,
mailbox
->
start_offset
);
/* Update Generation Number */
if
(
fixcache
)
{
*
((
bit32
*
)
buf
+
OFFSET_GENERATION_NO
)
=
htonl
(
mailbox
->
generation_no
+
1
);
/* Write generation number to cache file */
fwrite
(
buf
,
1
,
sizeof
(
bit32
),
newcache
);
}
/* Write out new index header */
fwrite
(
buf
,
1
,
mailbox
->
start_offset
,
newindex
);
/* Grow the index header if necessary */
for
(
n
=
mailbox
->
start_offset
;
n
<
INDEX_HEADER_SIZE
;
n
++
)
{
if
(
n
==
OFFSET_UIDVALIDITY
+
3
)
{
putc
(
1
,
newindex
);
}
else
{
putc
(
0
,
newindex
);
}
}
/* Copy over records for nondeleted messages */
for
(
msgno
=
1
;
msgno
<=
mailbox
->
exists
;
msgno
++
)
{
memcpy
(
buf
,
mailbox
->
index_base
+
mailbox
->
start_offset
+
(
msgno
-
1
)
*
mailbox
->
record_size
,
mailbox
->
record_size
);
/* Sanity check */
if
(
*
((
bit32
*
)(
buf
+
OFFSET_UID
))
==
0
)
{
syslog
(
LOG_ERR
,
"IOERROR: %s zero index record %u/%lu"
,
mailbox
->
name
,
msgno
,
mailbox
->
exists
);
goto
fail
;
}
if
(
decideproc
?
decideproc
(
mailbox
,
deciderock
,
buf
)
:
(
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_SYSTEM_FLAGS
)))
&
FLAG_DELETED
))
{
bit32
sysflags
;
/* Remember UID and size */
deleted
[
numdeleted
++
]
=
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_UID
)));
quotadeleted
+=
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_SIZE
)));
/* figure out if deleted msg has system flags.
update counts accordingly */
sysflags
=
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_SYSTEM_FLAGS
)));
if
(
sysflags
&
FLAG_ANSWERED
)
numansweredflag
++
;
if
(
sysflags
&
FLAG_DELETED
)
numdeletedflag
++
;
if
(
sysflags
&
FLAG_FLAGGED
)
numflaggedflag
++
;
}
else
if
(
fixcache
)
{
size_t
cache_record_size
;
unsigned
long
cache_offset
;
unsigned
int
cache_ent
;
const
char
*
cacheitem
,
*
cacheitembegin
;
cache_offset
=
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_CACHE_OFFSET
)));
/* Fix up cache file offset */
*
((
bit32
*
)(
buf
+
OFFSET_CACHE_OFFSET
))
=
htonl
(
new_cache_total_size
);
fwrite
(
buf
,
1
,
mailbox
->
record_size
,
newindex
);
/* Compute size of this record */
cacheitembegin
=
cacheitem
=
mailbox
->
cache_base
+
cache_offset
;
for
(
cache_ent
=
0
;
cache_ent
<
NUM_CACHE_FIELDS
;
cache_ent
++
)
{
cacheitem
=
CACHE_ITEM_NEXT
(
cacheitem
);
}
cache_record_size
=
(
cacheitem
-
cacheitembegin
);
new_cache_total_size
+=
cache_record_size
;
/* fwrite will automatically call write() in a sane way */
fwrite
(
cacheitembegin
,
1
,
cache_record_size
,
newcache
);
}
else
{
/* Just copy the index record */
fwrite
(
buf
,
1
,
mailbox
->
record_size
,
newindex
);
}
}
/* Fix up information in index header */
rewind
(
newindex
);
n
=
fread
(
buf
,
1
,
mailbox
->
start_offset
,
newindex
);
if
((
unsigned
long
)
n
!=
mailbox
->
start_offset
)
{
syslog
(
LOG_ERR
,
"IOERROR: reading index header for %s: got %d of %ld"
,
mailbox
->
name
,
n
,
mailbox
->
start_offset
);
goto
fail
;
}
/* Fix up exists */
newexists
=
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_EXISTS
)))
-
numdeleted
;
*
((
bit32
*
)(
buf
+
OFFSET_EXISTS
))
=
htonl
(
newexists
);
/* Fix up expunged count */
if
(
fixcache
)
{
*
((
bit32
*
)(
buf
+
OFFSET_LEAKED_CACHE
))
=
htonl
(
0
);
}
else
{
*
((
bit32
*
)(
buf
+
OFFSET_LEAKED_CACHE
))
=
htonl
(
newexpunged
+
numdeleted
);
}
/* Fix up other counts */
newanswered
=
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_ANSWERED
)))
-
numansweredflag
;
*
((
bit32
*
)(
buf
+
OFFSET_ANSWERED
))
=
htonl
(
newanswered
);
newdeleted
=
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_DELETED
)))
-
numdeletedflag
;
*
((
bit32
*
)(
buf
+
OFFSET_DELETED
))
=
htonl
(
newdeleted
);
newflagged
=
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_FLAGGED
)))
-
numflaggedflag
;
*
((
bit32
*
)(
buf
+
OFFSET_FLAGGED
))
=
htonl
(
newflagged
);
/* Fix up quota_mailbox_used */
*
((
bit32
*
)(
buf
+
OFFSET_QUOTA_MAILBOX_USED
))
=
htonl
(
ntohl
(
*
((
bit32
*
)(
buf
+
OFFSET_QUOTA_MAILBOX_USED
)))
-
quotadeleted
);
/* Fix up start offset if necessary */
if
(
mailbox
->
start_offset
<
INDEX_HEADER_SIZE
)
{
*
((
bit32
*
)(
buf
+
OFFSET_START_OFFSET
))
=
htonl
(
INDEX_HEADER_SIZE
);
}
rewind
(
newindex
);
fwrite
(
buf
,
1
,
mailbox
->
start_offset
,
newindex
);
/* Ensure everything made it to disk */
fflush
(
newindex
);
if
(
fixcache
)
fflush
(
newcache
);
if
(
ferror
(
newindex
)
||
(
fixcache
&&
ferror
(
newcache
))
||
fsync
(
fileno
(
newindex
))
||
(
fixcache
&&
fsync
(
fileno
(
newcache
))))
{
syslog
(
LOG_ERR
,
"IOERROR: writing index/cache for %s: %m"
,
mailbox
->
name
);
goto
fail
;
}
/* Record quota release */
r
=
quota_read
(
&
mailbox
->
quota
,
&
tid
,
1
);
if
(
!
r
)
{
if
(
mailbox
->
quota
.
used
>=
quotadeleted
)
{
mailbox
->
quota
.
used
-=
quotadeleted
;
}
else
{
mailbox
->
quota
.
used
=
0
;
}
r
=
quota_write
(
&
mailbox
->
quota
,
&
tid
);
if
(
!
r
)
quota_commit
(
&
tid
);
else
{
syslog
(
LOG_ERR
,
"LOSTQUOTA: unable to record free of %u bytes in quota %s"
,
quotadeleted
,
mailbox
->
quota
.
root
);
}
}
else
if
(
r
!=
IMAP_QUOTAROOT_NONEXISTENT
)
goto
fail
;
strlcpy
(
fnamebuf
,
mailbox
->
path
,
sizeof
(
fnamebuf
));
fnamebuf_len
=
strlen
(
fnamebuf
);
fnametail
=
fnamebuf
+
fnamebuf_len
;
strlcpy
(
fnametail
,
FNAME_INDEX
,
sizeof
(
fnamebuf
)
-
fnamebuf_len
);
strlcpy
(
fnamebufnew
,
fnamebuf
,
sizeof
(
fnamebufnew
));
strlcat
(
fnamebufnew
,
".NEW"
,
sizeof
(
fnamebufnew
));
if
(
rename
(
fnamebufnew
,
fnamebuf
))
{
syslog
(
LOG_ERR
,
"IOERROR: renaming index file for %s: %m"
,
mailbox
->
name
);
goto
fail
;
}
if
(
fixcache
)
{
strlcpy
(
fnametail
,
FNAME_CACHE
,
sizeof
(
fnamebuf
)
-
fnamebuf_len
);
strlcpy
(
fnamebufnew
,
fnamebuf
,
sizeof
(
fnamebufnew
));
strlcat
(
fnamebufnew
,
".NEW"
,
sizeof
(
fnamebufnew
));
if
(
rename
(
fnamebufnew
,
fnamebuf
))
{
syslog
(
LOG_CRIT
,
"CRITICAL IOERROR: renaming cache file for %s, need to reconstruct: %m"
,
mailbox
->
name
);
/* Fall through and delete message files anyway */
}
}
if
(
numdeleted
)
{
if
(
updatenotifier
)
updatenotifier
(
mailbox
);
}
mailbox_unlock_pop
(
mailbox
);
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
fclose
(
newindex
);
if
(
fixcache
)
fclose
(
newcache
);
/* Delete message files */
*
fnametail
++
=
'/'
;
for
(
msgno
=
0
;
msgno
<
numdeleted
;
msgno
++
)
{
if
(
iscurrentdir
)
{
char
shortfnamebuf
[
MAILBOX_FNAME_LEN
];
mailbox_message_get_fname
(
mailbox
,
deleted
[
msgno
],
shortfnamebuf
,
sizeof
(
shortfnamebuf
));
unlink
(
shortfnamebuf
);
}
else
{
mailbox_message_get_fname
(
mailbox
,
deleted
[
msgno
],
fnametail
,
sizeof
(
fnamebuf
)
-
strlen
(
fnamebuf
));
unlink
(
fnamebuf
);
}
}
free
(
buf
);
if
(
deleted
)
free
(
deleted
);
return
0
;
fail
:
free
(
buf
);
free
(
deleted
);
fclose
(
newindex
);
if
(
fixcache
)
fclose
(
newcache
);
mailbox_unlock_pop
(
mailbox
);
mailbox_unlock_index
(
mailbox
);
mailbox_unlock_header
(
mailbox
);
return
IMAP_IOERROR
;
}
int
mailbox_create
(
const
char
*
name
,
char
*
path
,
const
char
*
acl
,
const
char
*
uniqueid
,
int
format
,
struct
mailbox
*
mailboxp
)
{
int
r
;
char
*
p
;
char
quota_root
[
MAX_MAILBOX_PATH
+
1
];
int
hasquota
;
char
fnamebuf
[
MAX_MAILBOX_PATH
+
1
];
size_t
fname_len
;
struct
mailbox
mailbox
;
int
save_errno
;
int
n
;
const
char
*
lockfailaction
;
struct
stat
sbuf
;
if
(
cyrus_mkdir
(
path
,
0755
)
==
-1
)
return
IMAP_IOERROR
;
if
(
mkdir
(
path
,
0755
)
==
-1
&&
errno
!=
EEXIST
)
{
save_errno
=
errno
;
if
(
stat
(
path
,
&
sbuf
)
==
-1
)
{
errno
=
save_errno
;
syslog
(
LOG_ERR
,
"IOERROR: creating directory %s: %m"
,
path
);
return
IMAP_IOERROR
;
}
}
zeromailbox
(
mailbox
);
hasquota
=
quota_findroot
(
quota_root
,
sizeof
(
quota_root
),
name
);
/* Set up buffer */
strlcpy
(
fnamebuf
,
path
,
sizeof
(
fnamebuf
));
fname_len
=
strlen
(
fnamebuf
);
p
=
fnamebuf
+
fname_len
;
/* Bounds Check
*
* - Do all bounds checking now, before we open anything so that we
* fail as early as possible */
if
(
fname_len
+
strlen
(
FNAME_HEADER
)
>=
sizeof
(
fnamebuf
))
{
syslog
(
LOG_ERR
,
"IOERROR: Mailbox name too long (%s + %s)"
,
fnamebuf
,
FNAME_HEADER
);
return
IMAP_IOERROR
;
}
else
if
(
fname_len
+
strlen
(
FNAME_INDEX
)
>=
sizeof
(
fnamebuf
))
{
syslog
(
LOG_ERR
,
"IOERROR: Mailbox name too long (%s + %s)"
,
fnamebuf
,
FNAME_INDEX
);
return
IMAP_IOERROR
;
}
else
if
(
fname_len
+
strlen
(
FNAME_CACHE
)
>=
sizeof
(
fnamebuf
))
{
syslog
(
LOG_ERR
,
"IOERROR: Mailbox name too long (%s + %s)"
,
fnamebuf
,
FNAME_CACHE
);
return
IMAP_IOERROR
;
}
/* Concatinate */
strcpy
(
p
,
FNAME_HEADER
);
mailbox
.
header_fd
=
open
(
fnamebuf
,
O_RDWR
|
O_TRUNC
|
O_CREAT
,
0666
);
if
(
mailbox
.
header_fd
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: creating %s: %m"
,
fnamebuf
);
return
IMAP_IOERROR
;
}
/* Note that we are locking the mailbox here. Technically, this function
* can be called with a lock on the mailbox list. This would otherwise
* violate the locking semantics, but it is okay since the mailbox list
* changes have not been committed, and the mailbox we create here *can't*
* be opened by anyone else */
r
=
lock_reopen
(
mailbox
.
header_fd
,
fnamebuf
,
NULL
,
&
lockfailaction
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"IOERROR: %s header for new mailbox %s: %m"
,
lockfailaction
,
mailbox
.
name
);
mailbox_close
(
&
mailbox
);
return
IMAP_IOERROR
;
}
mailbox
.
header_lock_count
++
;
mailbox
.
name
=
xstrdup
(
name
);
mailbox
.
path
=
xstrdup
(
path
);
mailbox
.
acl
=
xstrdup
(
acl
);
strcpy
(
p
,
FNAME_INDEX
);
mailbox
.
index_fd
=
open
(
fnamebuf
,
O_RDWR
|
O_TRUNC
|
O_CREAT
,
0666
);
if
(
mailbox
.
index_fd
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: creating %s: %m"
,
fnamebuf
);
mailbox_close
(
&
mailbox
);
return
IMAP_IOERROR
;
}
r
=
lock_reopen
(
mailbox
.
index_fd
,
fnamebuf
,
NULL
,
&
lockfailaction
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"IOERROR: %s index for new mailbox %s: %m"
,
lockfailaction
,
mailbox
.
name
);
mailbox_close
(
&
mailbox
);
return
IMAP_IOERROR
;
}
mailbox
.
index_lock_count
++
;
strcpy
(
p
,
FNAME_CACHE
);
mailbox
.
cache_fd
=
open
(
fnamebuf
,
O_RDWR
|
O_TRUNC
|
O_CREAT
,
0666
);
if
(
mailbox
.
cache_fd
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: creating %s: %m"
,
fnamebuf
);
mailbox_close
(
&
mailbox
);
return
IMAP_IOERROR
;
}
if
(
hasquota
)
mailbox
.
quota
.
root
=
xstrdup
(
quota_root
);
mailbox
.
generation_no
=
0
;
mailbox
.
format
=
format
;
mailbox
.
minor_version
=
MAILBOX_MINOR_VERSION
;
mailbox
.
start_offset
=
INDEX_HEADER_SIZE
;
mailbox
.
record_size
=
INDEX_RECORD_SIZE
;
mailbox
.
exists
=
0
;
mailbox
.
last_appenddate
=
0
;
mailbox
.
last_uid
=
0
;
mailbox
.
quota_mailbox_used
=
0
;
mailbox
.
pop3_last_login
=
0
;
mailbox
.
uidvalidity
=
time
(
0
);
mailbox
.
deleted
=
0
;
mailbox
.
answered
=
0
;
mailbox
.
flagged
=
0
;
mailbox
.
pop3_new_uidl
=
1
;
if
(
!
uniqueid
)
{
size_t
unique_size
=
sizeof
(
char
)
*
32
;
mailbox
.
uniqueid
=
xmalloc
(
unique_size
);
mailbox_make_uniqueid
(
mailbox
.
name
,
mailbox
.
uidvalidity
,
mailbox
.
uniqueid
,
unique_size
);
}
else
{
mailbox
.
uniqueid
=
xstrdup
(
uniqueid
);
}
r
=
mailbox_write_header
(
&
mailbox
);
if
(
!
r
)
r
=
mailbox_write_index_header
(
&
mailbox
);
if
(
!
r
)
{
n
=
retry_write
(
mailbox
.
cache_fd
,
(
char
*
)
&
mailbox
.
generation_no
,
4
);
if
(
n
!=
4
||
fsync
(
mailbox
.
cache_fd
))
{
syslog
(
LOG_ERR
,
"IOERROR: writing initial cache for %s: %m"
,
mailbox
.
name
);
r
=
IMAP_IOERROR
;
}
}
if
(
!
r
)
r
=
seen_create_mailbox
(
&
mailbox
);
if
(
mailboxp
)
{
*
mailboxp
=
mailbox
;
}
else
{
mailbox_close
(
&
mailbox
);
}
return
r
;
}
/*
* Delete and close the mailbox 'mailbox'. Closes 'mailbox' whether
* or not the deletion was successful. Requires a locked mailbox.
*/
int
mailbox_delete
(
struct
mailbox
*
mailbox
,
int
delete_quota_root
)
{
int
r
,
rquota
=
0
;
DIR
*
dirp
;
struct
dirent
*
f
;
char
buf
[
MAX_MAILBOX_PATH
+
1
];
char
*
tail
;
struct
txn
*
tid
=
NULL
;
/* Ensure that we are locked */
if
(
!
mailbox
->
header_lock_count
)
return
IMAP_INTERNAL
;
rquota
=
quota_read
(
&
mailbox
->
quota
,
&
tid
,
1
);
seen_delete_mailbox
(
mailbox
);
if
(
delete_quota_root
&&
!
rquota
)
{
quota_delete
(
&
mailbox
->
quota
,
&
tid
);
free
(
mailbox
->
quota
.
root
);
mailbox
->
quota
.
root
=
NULL
;
}
else
if
(
!
rquota
)
{
/* Free any quota being used by this mailbox */
if
(
mailbox
->
quota
.
used
>=
mailbox
->
quota_mailbox_used
)
{
mailbox
->
quota
.
used
-=
mailbox
->
quota_mailbox_used
;
}
else
{
mailbox
->
quota
.
used
=
0
;
}
r
=
quota_write
(
&
mailbox
->
quota
,
&
tid
);
if
(
r
)
{
syslog
(
LOG_ERR
,
"LOSTQUOTA: unable to record free of %lu bytes in quota %s"
,
mailbox
->
quota_mailbox_used
,
mailbox
->
quota
.
root
);
}
else
quota_commit
(
&
tid
);
}
/* remove all files in directory */
strlcpy
(
buf
,
mailbox
->
path
,
sizeof
(
buf
));
if
(
strlen
(
buf
)
>=
sizeof
(
buf
)
-
2
)
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s)"
,
buf
);
fatal
(
"path too long"
,
EC_OSFILE
);
}
tail
=
buf
+
strlen
(
buf
);
*
tail
++
=
'/'
;
*
tail
=
'\0'
;
dirp
=
opendir
(
mailbox
->
path
);
if
(
dirp
)
{
while
((
f
=
readdir
(
dirp
))
!=
NULL
)
{
if
(
f
->
d_name
[
0
]
==
'.'
&&
(
f
->
d_name
[
1
]
==
'\0'
||
(
f
->
d_name
[
1
]
==
'.'
&&
f
->
d_name
[
2
]
==
'\0'
)))
{
/* readdir() can return "." or "..", and I got a bug report
that SCO might blow the file system to smithereens if we
unlink(".."). Let's not do that. */
continue
;
}
if
(
strlen
(
buf
)
+
strlen
(
f
->
d_name
)
>=
sizeof
(
buf
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
buf
,
f
->
d_name
);
fatal
(
"Path too long"
,
EC_OSFILE
);
}
strcpy
(
tail
,
f
->
d_name
);
unlink
(
buf
);
*
tail
=
'\0'
;
}
closedir
(
dirp
);
}
/* Remove empty directories, going up path */
tail
--
;
do
{
*
tail
=
'\0'
;
}
while
(
rmdir
(
buf
)
==
0
&&
(
tail
=
strrchr
(
buf
,
'/'
)));
mailbox_close
(
mailbox
);
return
0
;
}
/*
* Expunge decision proc used by mailbox_rename() to expunge all messages
* in INBOX
*/
static
int
expungeall
(
struct
mailbox
*
mailbox
__attribute__
((
unused
)),
void
*
rock
__attribute__
((
unused
)),
char
*
indexbuf
__attribute__
((
unused
)))
{
return
1
;
}
/* if 'isinbox' is set, we perform the funky RENAME INBOX INBOX.old
semantics, regardless of whether or not the name of the mailbox is
'user.foo'.*/
/* requires a LOCKED oldmailbox pointer */
int
mailbox_rename_copy
(
struct
mailbox
*
oldmailbox
,
const
char
*
newname
,
char
*
newpath
,
bit32
*
olduidvalidityp
,
bit32
*
newuidvalidityp
,
struct
mailbox
*
newmailbox
)
{
int
r
;
unsigned
int
flag
,
msgno
;
struct
index_record
record
;
char
oldfname
[
MAX_MAILBOX_PATH
+
1
],
newfname
[
MAX_MAILBOX_PATH
+
1
];
size_t
oldfname_len
,
newfname_len
,
fn_len
;
char
*
oldfnametail
,
*
newfnametail
;
struct
txn
*
tid
=
NULL
;
assert
(
oldmailbox
->
header_lock_count
>
0
&&
oldmailbox
->
index_lock_count
>
0
);
/* Create new mailbox */
r
=
mailbox_create
(
newname
,
newpath
,
oldmailbox
->
acl
,
oldmailbox
->
uniqueid
,
oldmailbox
->
format
,
newmailbox
);
if
(
r
)
return
r
;
if
(
strcmp
(
oldmailbox
->
name
,
newname
)
==
0
)
{
/* Just moving mailboxes between partitions */
newmailbox
->
uidvalidity
=
oldmailbox
->
uidvalidity
;
}
if
(
olduidvalidityp
)
*
olduidvalidityp
=
oldmailbox
->
uidvalidity
;
if
(
newuidvalidityp
)
*
newuidvalidityp
=
newmailbox
->
uidvalidity
;
/* Copy flag names */
for
(
flag
=
0
;
flag
<
MAX_USER_FLAGS
;
flag
++
)
{
if
(
oldmailbox
->
flagname
[
flag
])
{
newmailbox
->
flagname
[
flag
]
=
xstrdup
(
oldmailbox
->
flagname
[
flag
]);
}
}
r
=
mailbox_write_header
(
newmailbox
);
if
(
r
)
{
mailbox_close
(
newmailbox
);
return
r
;
}
/* Check quota if necessary */
if
(
newmailbox
->
quota
.
root
)
{
r
=
quota_read
(
&
(
newmailbox
->
quota
),
&
tid
,
1
);
if
(
!
oldmailbox
->
quota
.
root
||
strcmp
(
oldmailbox
->
quota
.
root
,
newmailbox
->
quota
.
root
)
!=
0
)
{
if
(
!
r
&&
newmailbox
->
quota
.
limit
>=
0
&&
newmailbox
->
quota
.
used
+
oldmailbox
->
quota_mailbox_used
>
((
unsigned
)
newmailbox
->
quota
.
limit
*
QUOTA_UNITS
))
{
r
=
IMAP_QUOTA_EXCEEDED
;
}
}
if
(
r
&&
r
!=
IMAP_QUOTAROOT_NONEXISTENT
)
{
mailbox_close
(
newmailbox
);
return
r
;
}
}
strlcpy
(
oldfname
,
oldmailbox
->
path
,
sizeof
(
oldfname
));
oldfname_len
=
strlen
(
oldfname
);
oldfnametail
=
oldfname
+
oldfname_len
;
strlcpy
(
newfname
,
newmailbox
->
path
,
sizeof
(
newfname
));
newfname_len
=
strlen
(
newfname
);
newfnametail
=
newfname
+
newfname_len
;
/* Check to see if we're going to be over-long */
fn_len
=
strlen
(
FNAME_INDEX
);
if
(
oldfname_len
+
fn_len
>
sizeof
(
oldfname
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
oldfname
,
FNAME_INDEX
);
fatal
(
"Path Too Long"
,
EC_OSFILE
);
}
if
(
newfname_len
+
fn_len
>
sizeof
(
newfname
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
newfname
,
FNAME_INDEX
);
fatal
(
"Path Too Long"
,
EC_OSFILE
);
}
fn_len
=
strlen
(
FNAME_CACHE
);
if
(
oldfname_len
+
fn_len
>
sizeof
(
oldfname
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
oldfname
,
FNAME_CACHE
);
fatal
(
"Path Too Long"
,
EC_OSFILE
);
}
if
(
newfname_len
+
fn_len
>
sizeof
(
newfname
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
newfname
,
FNAME_CACHE
);
fatal
(
"Path Too Long"
,
EC_OSFILE
);
}
/* Copy over index/cache files */
strlcpy
(
oldfnametail
,
FNAME_INDEX
,
sizeof
(
oldfname
)
-
oldfname_len
);
strlcpy
(
newfnametail
,
FNAME_INDEX
,
sizeof
(
newfname
)
-
newfname_len
);
unlink
(
newfname
);
/* Make link() possible */
r
=
mailbox_copyfile
(
oldfname
,
newfname
,
0
);
strlcpy
(
oldfnametail
,
FNAME_CACHE
,
sizeof
(
oldfname
)
-
oldfname_len
);
strlcpy
(
newfnametail
,
FNAME_CACHE
,
sizeof
(
newfname
)
-
newfname_len
);
unlink
(
newfname
);
if
(
!
r
)
r
=
mailbox_copyfile
(
oldfname
,
newfname
,
0
);
if
(
r
)
{
mailbox_close
(
newmailbox
);
return
r
;
}
/* Re-open index file and store new uidvalidity */
close
(
newmailbox
->
index_fd
);
newmailbox
->
index_fd
=
dup
(
oldmailbox
->
index_fd
);
(
void
)
mailbox_read_index_header
(
newmailbox
);
newmailbox
->
generation_no
=
oldmailbox
->
generation_no
;
(
void
)
mailbox_write_index_header
(
newmailbox
);
/* Copy over message files */
oldfnametail
++
;
newfnametail
++
;
for
(
msgno
=
1
;
msgno
<=
oldmailbox
->
exists
;
msgno
++
)
{
r
=
mailbox_read_index_record
(
oldmailbox
,
msgno
,
&
record
);
if
(
r
)
break
;
mailbox_message_get_fname
(
oldmailbox
,
record
.
uid
,
oldfnametail
,
sizeof
(
oldfname
)
-
strlen
(
oldfname
));
if
(
strlen
(
newfname
)
+
strlen
(
oldfnametail
)
>=
sizeof
(
newfname
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
newfname
,
oldfnametail
);
fatal
(
"Path too long"
,
EC_OSFILE
);
}
strcpy
(
newfnametail
,
oldfnametail
);
r
=
mailbox_copyfile
(
oldfname
,
newfname
,
0
);
if
(
r
)
break
;
}
if
(
!
r
)
r
=
seen_copy
(
oldmailbox
,
newmailbox
);
/* Record new quota usage */
if
(
!
r
&&
newmailbox
->
quota
.
root
)
{
newmailbox
->
quota
.
used
+=
oldmailbox
->
quota_mailbox_used
;
r
=
quota_write
(
&
(
newmailbox
->
quota
),
&
tid
);
if
(
!
r
)
quota_commit
(
&
tid
);
}
if
(
r
)
{
/* failure and back out */
for
(
msgno
=
1
;
msgno
<=
oldmailbox
->
exists
;
msgno
++
)
{
if
(
mailbox_read_index_record
(
oldmailbox
,
msgno
,
&
record
))
continue
;
mailbox_message_get_fname
(
oldmailbox
,
record
.
uid
,
newfnametail
,
sizeof
(
newfname
)
-
strlen
(
newfname
));
(
void
)
unlink
(
newfname
);
}
}
return
r
;
}
int
mailbox_rename_cleanup
(
struct
mailbox
*
oldmailbox
,
int
isinbox
)
{
int
r
=
0
;
if
(
isinbox
)
{
/* Expunge old mailbox */
r
=
mailbox_expunge
(
oldmailbox
,
0
,
expungeall
,
(
char
*
)
0
);
}
else
{
r
=
mailbox_delete
(
oldmailbox
,
0
);
}
if
(
r
)
{
syslog
(
LOG_CRIT
,
"Rename Failure during mailbox_rename_cleanup (%s), "
\
"potential leaked space (%s)"
,
oldmailbox
->
name
,
error_message
(
r
));
}
return
r
;
}
/*
* Synchronize 'new' mailbox to 'old' mailbox.
*/
int
mailbox_sync
(
const
char
*
oldname
,
const
char
*
oldpath
,
const
char
*
oldacl
,
const
char
*
newname
,
char
*
newpath
,
int
docreate
,
bit32
*
olduidvalidityp
,
bit32
*
newuidvalidityp
,
struct
mailbox
*
mailboxp
)
{
int
r
,
r2
;
struct
mailbox
oldmailbox
,
newmailbox
;
unsigned
int
flag
,
oldmsgno
,
newmsgno
;
struct
index_record
oldrecord
,
newrecord
;
char
oldfname
[
MAX_MAILBOX_PATH
+
1
],
newfname
[
MAX_MAILBOX_PATH
+
1
];
size_t
oldfname_len
,
newfname_len
,
fn_len
;
char
*
oldfnametail
,
*
newfnametail
;
struct
txn
*
tid
=
NULL
;
/* Open old mailbox and lock */
mailbox_open_header_path
(
oldname
,
oldpath
,
oldacl
,
0
,
&
oldmailbox
,
0
);
if
(
oldmailbox
.
format
==
MAILBOX_FORMAT_NETNEWS
)
{
mailbox_close
(
&
oldmailbox
);
return
IMAP_MAILBOX_NOTSUPPORTED
;
}
r
=
mailbox_lock_header
(
&
oldmailbox
);
if
(
!
r
)
r
=
mailbox_open_index
(
&
oldmailbox
);
if
(
!
r
)
r
=
mailbox_lock_index
(
&
oldmailbox
);
if
(
r
)
{
mailbox_close
(
&
oldmailbox
);
return
r
;
}
if
(
docreate
)
{
/* Create new mailbox */
r
=
mailbox_create
(
newname
,
newpath
,
oldmailbox
.
acl
,
oldmailbox
.
uniqueid
,
oldmailbox
.
format
,
&
newmailbox
);
}
else
{
/* Open new mailbox and lock */
r
=
mailbox_open_header_path
(
newname
,
newpath
,
oldacl
,
0
,
&
newmailbox
,
0
);
r
=
mailbox_lock_header
(
&
newmailbox
);
if
(
!
r
)
r
=
mailbox_open_index
(
&
newmailbox
);
if
(
!
r
)
r
=
mailbox_lock_index
(
&
newmailbox
);
if
(
r
)
{
mailbox_close
(
&
newmailbox
);
}
}
if
(
r
)
{
mailbox_close
(
&
oldmailbox
);
return
r
;
}
newmailbox
.
uidvalidity
=
oldmailbox
.
uidvalidity
;
if
(
olduidvalidityp
)
*
olduidvalidityp
=
oldmailbox
.
uidvalidity
;
if
(
newuidvalidityp
)
*
newuidvalidityp
=
newmailbox
.
uidvalidity
;
/* Copy flag names */
for
(
flag
=
0
;
flag
<
MAX_USER_FLAGS
;
flag
++
)
{
if
(
oldmailbox
.
flagname
[
flag
])
{
newmailbox
.
flagname
[
flag
]
=
xstrdup
(
oldmailbox
.
flagname
[
flag
]);
}
}
r
=
mailbox_write_header
(
&
newmailbox
);
if
(
r
)
{
mailbox_close
(
&
newmailbox
);
mailbox_close
(
&
oldmailbox
);
return
r
;
}
/* Check quota if necessary */
if
(
newmailbox
.
quota
.
root
)
{
r
=
quota_read
(
&
newmailbox
.
quota
,
&
tid
,
1
);
if
(
!
oldmailbox
.
quota
.
root
||
strcmp
(
oldmailbox
.
quota
.
root
,
newmailbox
.
quota
.
root
)
!=
0
)
{
if
(
!
r
&&
newmailbox
.
quota
.
limit
>=
0
&&
newmailbox
.
quota
.
used
+
oldmailbox
.
quota_mailbox_used
>
((
unsigned
)
newmailbox
.
quota
.
limit
*
QUOTA_UNITS
))
{
r
=
IMAP_QUOTA_EXCEEDED
;
}
}
if
(
r
&&
r
!=
IMAP_QUOTAROOT_NONEXISTENT
)
{
mailbox_close
(
&
newmailbox
);
mailbox_close
(
&
oldmailbox
);
return
r
;
}
}
strlcpy
(
oldfname
,
oldmailbox
.
path
,
sizeof
(
oldfname
));
strlcat
(
oldfname
,
"/"
,
sizeof
(
oldfname
));
oldfname_len
=
strlen
(
oldfname
);
oldfnametail
=
oldfname
+
oldfname_len
;
strlcpy
(
newfname
,
newmailbox
.
path
,
sizeof
(
newfname
));
strlcat
(
newfname
,
"/"
,
sizeof
(
newfname
));
newfname_len
=
strlen
(
newfname
);
newfnametail
=
newfname
+
newfname_len
;
/*
* Copy over new message files and delete expunged ones.
*
* We use the fact that UIDs are monotonically increasing to our
* advantage; we compare the UIDs from each mailbox in order, and:
*
* - if UID in "slave" mailbox < UID in "master" mailbox,
* then the message has been deleted from "master" since last sync,
* so delete it from "slave" and move on to next "slave" UID
* - if UID in "slave" mailbox == UID in "master" mailbox,
* then message is still current and we already have a copy,
* so move on to next UID in each mailbox
* - if UID in "master" mailbox > last UID in "slave" mailbox,
* then this is a new arrival in "master" since last sync,
* so copy it to "slave" and move on to next "master" UID
*/
newmsgno
=
1
;
for
(
oldmsgno
=
1
;
oldmsgno
<=
oldmailbox
.
exists
;
oldmsgno
++
)
{
r
=
mailbox_read_index_record
(
&
oldmailbox
,
oldmsgno
,
&
oldrecord
);
if
(
r
)
break
;
if
(
newmsgno
<=
newmailbox
.
exists
)
{
do
{
r
=
mailbox_read_index_record
(
&
newmailbox
,
newmsgno
,
&
newrecord
);
if
(
r
)
goto
fail
;
newmsgno
++
;
if
(
newrecord
.
uid
<
oldrecord
.
uid
)
{
/* message expunged since last sync - delete message file */
mailbox_message_get_fname
(
&
newmailbox
,
newrecord
.
uid
,
newfnametail
,
sizeof
(
newfname
)
-
strlen
(
newfname
));
unlink
(
newfname
);
}
}
while
((
newrecord
.
uid
<
oldrecord
.
uid
)
&&
(
newmsgno
<=
newmailbox
.
exists
));
}
/* we check 'exists' instead of last UID in case of empty mailbox */
if
(
newmsgno
>
newmailbox
.
exists
)
{
/* message arrived since last sync - copy message file */
mailbox_message_get_fname
(
&
oldmailbox
,
oldrecord
.
uid
,
oldfnametail
,
sizeof
(
oldfname
)
-
strlen
(
oldfname
));
strcpy
(
newfnametail
,
oldfnametail
);
r
=
mailbox_copyfile
(
oldfname
,
newfname
,
0
);
if
(
r
)
break
;
}
}
if
(
!
r
)
r
=
seen_copy
(
&
oldmailbox
,
&
newmailbox
);
if
(
!
r
)
{
/* Copy over index/cache files */
oldfnametail
--
;
newfnametail
--
;
fn_len
=
strlen
(
FNAME_INDEX
);
if
((
oldfname_len
-
1
)
+
fn_len
>
sizeof
(
oldfname
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
oldfname
,
FNAME_INDEX
);
fatal
(
"Path too long"
,
EC_OSFILE
);
}
if
((
newfname_len
-
1
)
+
fn_len
>
sizeof
(
oldfname
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
newfname
,
FNAME_INDEX
);
fatal
(
"Path too long"
,
EC_OSFILE
);
}
strlcpy
(
oldfnametail
,
FNAME_INDEX
,
sizeof
(
oldfname
)
-
(
oldfname_len
-
1
));
strlcpy
(
newfnametail
,
FNAME_INDEX
,
sizeof
(
newfname
)
-
(
newfname_len
-
1
));
unlink
(
newfname
);
/* Make link() possible */
r
=
mailbox_copyfile
(
oldfname
,
newfname
,
0
);
fn_len
=
strlen
(
FNAME_CACHE
);
if
((
oldfname_len
-
1
)
+
fn_len
>
sizeof
(
oldfname
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
oldfname
,
FNAME_CACHE
);
fatal
(
"Path too long"
,
EC_OSFILE
);
}
if
((
newfname_len
-
1
)
+
fn_len
>
sizeof
(
oldfname
))
{
syslog
(
LOG_ERR
,
"IOERROR: Path too long (%s + %s)"
,
newfname
,
FNAME_CACHE
);
fatal
(
"Path too long"
,
EC_OSFILE
);
}
strlcpy
(
oldfnametail
,
FNAME_CACHE
,
sizeof
(
oldfname
)
-
(
oldfname_len
-
1
));
strlcpy
(
newfnametail
,
FNAME_CACHE
,
sizeof
(
newfname
)
-
(
newfname_len
-
1
));
unlink
(
newfname
);
if
(
!
r
)
r
=
mailbox_copyfile
(
oldfname
,
newfname
,
0
);
if
(
r
)
{
mailbox_close
(
&
newmailbox
);
mailbox_close
(
&
oldmailbox
);
return
r
;
}
/* Re-open index file and store new uidvalidity */
close
(
newmailbox
.
index_fd
);
newmailbox
.
index_fd
=
dup
(
oldmailbox
.
index_fd
);
(
void
)
mailbox_read_index_header
(
&
newmailbox
);
newmailbox
.
generation_no
=
oldmailbox
.
generation_no
;
(
void
)
mailbox_write_index_header
(
&
newmailbox
);
}
/* Record new quota usage */
if
(
!
r
&&
newmailbox
.
quota
.
root
)
{
newmailbox
.
quota
.
used
+=
oldmailbox
.
quota_mailbox_used
;
r
=
quota_write
(
&
newmailbox
.
quota
,
&
tid
);
if
(
!
r
)
quota_commit
(
&
tid
);
tid
=
NULL
;
}
if
(
r
)
goto
fail
;
if
(
r
&&
newmailbox
.
quota
.
root
)
{
r2
=
quota_read
(
&
newmailbox
.
quota
,
&
tid
,
1
);
newmailbox
.
quota
.
used
+=
newmailbox
.
quota_mailbox_used
;
if
(
!
r2
)
{
r2
=
quota_write
(
&
newmailbox
.
quota
,
&
tid
);
if
(
!
r2
)
quota_commit
(
&
tid
);
}
else
if
(
r2
==
IMAP_QUOTAROOT_NONEXISTENT
)
r2
=
0
;
if
(
r2
)
{
syslog
(
LOG_ERR
,
"LOSTQUOTA: unable to record use of %lu bytes in quota %s"
,
newmailbox
.
quota_mailbox_used
,
newmailbox
.
quota
.
root
);
}
}
if
(
r
)
goto
fail
;
mailbox_close
(
&
oldmailbox
);
if
(
mailboxp
)
{
*
mailboxp
=
newmailbox
;
}
else
{
mailbox_close
(
&
newmailbox
);
}
return
0
;
fail
:
#if 0
for (msgno = 1; msgno <= oldmailbox.exists; msgno++) {
if (mailbox_read_index_record(&oldmailbox, msgno, &record)) continue;
mailbox_message_get_fname(&oldmailbox, record.uid, newfnametail,
sizeof(newfname) - strlen(newfname));
(void) unlink(newfname);
}
#endif
mailbox_close
(
&
newmailbox
);
mailbox_close
(
&
oldmailbox
);
return
r
;
}
/*
* Copy (or link) the file 'from' to the file 'to'
*/
int
mailbox_copyfile
(
const
char
*
from
,
const
char
*
to
,
int
nolink
)
{
int
srcfd
,
destfd
;
struct
stat
sbuf
;
const
char
*
src_base
=
0
;
unsigned
long
src_size
=
0
;
int
n
;
if
(
!
nolink
)
{
if
(
link
(
from
,
to
)
==
0
)
return
0
;
if
(
errno
==
EEXIST
)
{
if
(
unlink
(
to
)
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: unlinking to recreate %s: %m"
,
to
);
return
IMAP_IOERROR
;
}
if
(
link
(
from
,
to
)
==
0
)
return
0
;
}
}
destfd
=
open
(
to
,
O_RDWR
|
O_TRUNC
|
O_CREAT
,
0666
);
if
(
destfd
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: creating %s: %m"
,
to
);
return
IMAP_IOERROR
;
}
srcfd
=
open
(
from
,
O_RDONLY
,
0666
);
if
(
srcfd
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: opening %s: %m"
,
from
);
close
(
destfd
);
return
IMAP_IOERROR
;
}
if
(
fstat
(
srcfd
,
&
sbuf
)
==
-1
)
{
syslog
(
LOG_ERR
,
"IOERROR: fstat on %s: %m"
,
from
);
close
(
srcfd
);
close
(
destfd
);
return
IMAP_IOERROR
;
}
map_refresh
(
srcfd
,
1
,
&
src_base
,
&
src_size
,
sbuf
.
st_size
,
from
,
0
);
n
=
retry_write
(
destfd
,
src_base
,
src_size
);
if
(
n
==
-1
||
fsync
(
destfd
))
{
map_free
(
&
src_base
,
&
src_size
);
close
(
srcfd
);
close
(
destfd
);
syslog
(
LOG_ERR
,
"IOERROR: writing %s: %m"
,
to
);
return
IMAP_IOERROR
;
}
map_free
(
&
src_base
,
&
src_size
);
close
(
srcfd
);
close
(
destfd
);
return
0
;
}
void
mailbox_hash_mbox
(
char
*
buf
,
size_t
buf_len
,
const
char
*
root
,
const
char
*
name
)
{
const
char
*
idx
;
char
c
,
*
p
;
snprintf
(
buf
,
buf_len
,
"%s"
,
root
);
buf_len
-=
strlen
(
buf
);
buf
+=
strlen
(
buf
);
if
(
config_virtdomains
&&
(
p
=
strchr
(
name
,
'!'
)))
{
*
p
=
'\0'
;
/* split domain!user */
if
(
config_hashimapspool
)
{
c
=
(
char
)
dir_hash_c
(
name
);
snprintf
(
buf
,
buf_len
,
"%s%c/%s"
,
FNAME_DOMAINDIR
,
c
,
name
);
}
else
{
snprintf
(
buf
,
buf_len
,
"%s%s"
,
FNAME_DOMAINDIR
,
name
);
}
*
p
++
=
'!'
;
/* reassemble domain!user */
name
=
p
;
buf_len
-=
strlen
(
buf
);
buf
+=
strlen
(
buf
);
}
if
(
config_hashimapspool
)
{
idx
=
strchr
(
name
,
'.'
);
if
(
idx
==
NULL
)
{
idx
=
name
;
}
else
{
idx
++
;
}
c
=
(
char
)
dir_hash_c
(
idx
);
snprintf
(
buf
,
buf_len
,
"/%c/%s"
,
c
,
name
);
}
else
{
/* standard mailbox placement */
snprintf
(
buf
,
buf_len
,
"/%s"
,
name
);
}
/* change all '.'s to '/' */
for
(
p
=
buf
;
*
p
;
p
++
)
{
if
(
*
p
==
'.'
)
*
p
=
'/'
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sat, Apr 4, 1:40 AM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18821969
Default Alt Text
mailbox.c (77 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline