Compare commits

..

22 commits

Author SHA1 Message Date
79e19d3d15
add a very limited INCLUDE_ONLY option 2023-12-04 15:23:04 -08:00
e0690b07eb
add docker-based build 2023-12-04 15:22:21 -08:00
6faf91a806
add an ignore filter based on substring of the file 2023-11-09 12:26:33 -08:00
Oswald Buddenhagen
bb5e98e9ec bump version 2021-12-03 11:56:16 +01:00
Oswald Buddenhagen
f2b1e80033 modernize configure.ac 2021-12-03 11:56:16 +01:00
Oswald Buddenhagen
e686f88318 don't complain about concurrent flagging as deleted
the result of propagating a deletion is flagging as deleted, so shut up
if the only remote change is exactly that.
2021-12-03 11:56:16 +01:00
Oswald Buddenhagen
51673214ab fix read beyond end of input in copy_msg_convert()
the input isn't necessarily null-terminated (it currently is for imap,
but not for maildir), so if the message ended somewhere within the
header field name, we'd read beyond its end, which theoretically could
cause a crash. no other adverse effects could result, as we'd stop
processing such a broken message right afterwards.

amends 70bad661.
2021-12-03 11:46:33 +01:00
Oswald Buddenhagen
127003ee37 reject unreasonably long mailbox names from IMAP LIST
this wasn't really a security problem, as the name mapping we actually
do does not change the string length, and the iteration was already
safe after the literal length fix, but it's still better to catch weird
input.
2021-12-01 10:07:40 +01:00
Oswald Buddenhagen
92921b1d3b reject messages that grow too large due to conversion
that shouldn't really be a problem, as we have 2GB of headroom, and most
growth would happen when sending an all-newlines message from maildir to
imap (due to CR additions), which is mostly non-critical. but better
safe than sorry.
2021-12-01 10:07:40 +01:00
Oswald Buddenhagen
bc15e571b6 report conversion errors directly in copy_msg_convert()
that makes it easier to report various conditions without introducing
separate error codes.
2021-12-01 10:07:40 +01:00
Oswald Buddenhagen
ba13362a52 deal with oversized messages in maildirs
don't try to read messages > 2G, as that will only lead to trouble down
the line.

this wouldn't have worked on linux anyway (we read in one chunk, and
that is limited to (2^31 - 2^12) on all architectures), but on
platforms were big reads work, this was a security problem if one
synchronized other users' maildirs.

as a minor fix on the side, we now also clip the reported message size,
so MaxSize works for excessively big messages.
2021-12-01 10:07:40 +01:00
Oswald Buddenhagen
463272eab8 CVE-2021-3657: reject excessively large IMAP literals
we didn't limit the 32-bit size of literals so far, which, given that we
use int-sized lengths & offsets, permitted all kinds of buffer
overflows. malicious/compromised servers may have been able to exploit
this. actual email senders would be constrained by size limits for
delivered mails, and to cause more than a crash they'd have to predict
the exact size of the final message.

we now limit to 2GB, which, given that we use unsigned ints since
e2d3b4d55 (v1.4.0), gives the handlers downstream plenty of headroom.

an alternative would have been using 64-bit offsets, but this seems like
major overkill, even if IMAP4rev2 recently mandated it (we talk only
IMAP4rev1, so we can ignore it).
2021-12-01 10:07:24 +01:00
Oswald Buddenhagen
87065c12b4 CVE-2021-44143: don't overflow heap on messages without headers
when a broken/compromised/malicious server gives us a message that
starts with an empty line, we'd enter the path for inserting a pristine
placeholder subject, for which we unfortunately didn't actually allocate
space (unless MaxSize is in use and the message exceeds it).

note that this cannot be triggered by merely receiving a crafted mail
with no headers (yes, it's actually possible to send such a thing), as
the delivery of mails adds plenty of headers.

amends 70bad661.
2021-11-25 16:14:32 +01:00
Oswald Buddenhagen
6e5dc6c2f2 bump version 2021-07-29 13:14:24 +02:00
Oswald Buddenhagen
7979782676 limit maildir nesting depth
this is a cheap way to catch symlink loops. 10 seems like a reasonable
limit, as it's unlikely that anyone would be able to actually work with
such a deeply nested mailbox tree.

fixes debian bug #990117.
2021-07-29 13:14:18 +02:00
Oswald Buddenhagen
a846ab054d enable embedding arbitrarily long strings into IMAP commands
the AUTHENTICATE command may get insanely long for GSSAPI when SASL-IR
is available. instead of growing the buffers each time someone hits the
limit (as done in f7cec306), remove the limitation altogether.

imap_vprintf() still contains a fixed-size buffer which could overflow
when really long strings (e.g., mailbox names) need to be quoted. this
seems very unlikely, so we'll deal with it if someone actually hits it.

REFMAIL: 87sg1qxdye.fsf@cern.ch
2021-06-11 18:24:00 +02:00
Oswald Buddenhagen
da65672f08 bump version 2021-06-03 11:07:35 +02:00
Oswald Buddenhagen
444601a1e0 Merge branch '1.3' into 1.4
Conflicts:
	configure.ac
	src/drv_imap.c
2021-06-03 11:04:56 +02:00
Oswald Buddenhagen
ed3bfdac4a bump version 2021-06-03 11:02:40 +02:00
Oswald Buddenhagen
589d2ed428 CVE-2021-3578: fix handling of unexpected APPENDUID response code
if the code was sent in response to anything but a STORE, we'd overwrite
a data pointer in one of our imap_cmd subclasses, an allocator data
structure, or the start of the next allocation, with an int that was
completely under the server's control. it's plausible that this could be
exploited for remote code execution.

to avoid this, we could ensure that the object is of the right type
prior to casting, by using a new flag in the parameter block. but it's
easier to just dispose of the out_uid field altogether and reuse the uid
field that is present in the parameter block anyway, but was used only
for FETCH commands so far.

this problem was found by Lukas Braun <koomi@moshbit.net> using a
fuzzer.
2021-06-03 11:02:23 +02:00
Oswald Buddenhagen
a86e6f8c7c don't crash on malformed CAPABILITY responses
amends 95a83c822.

this problem was found by Lukas Braun <koomi@moshbit.net> using a
fuzzer.
2021-06-02 15:51:23 +02:00
Oswald Buddenhagen
d8feb67dae tolerate INBOX mis-casing in Path
while it's technically reasonable to expect the user to match the
server's casing of INBOX if they set Path, this might come as a
surprise to those who know that the IMAP INBOX is case-insensitive.
so tolerate any casing instead. as a minor side effect, we'd now even be
able to deal with a server using different casing in NAMESPACE and LIST.
2021-03-19 18:21:34 +01:00
7 changed files with 251 additions and 96 deletions

41
Dockerfile Normal file
View file

@ -0,0 +1,41 @@
#FROM debian:bookworm-20231030-slim
FROM debian:bullseye-20220801
# need to add
# removed
# libsasl2-modules \
# ca-certificates \
# version pinning is being handled in our from line
# hadolint ignore=DL3008
RUN true && \
apt-get update && \
groupadd --gid 1000 user && \
useradd -m --home-dir /home/user --shell /bin/sh --uid 1000 --gid 1000 user && \
apt-get install -y --no-install-recommends \
libsasl2-2 \
libsasl2-dev \
perl \
libdatetime-format-dateparse-perl \
autoconf \
automake \
zlib1g-dev \
libdb-dev \
libsasl2-dev \
libssl-dev \
gcc \
make \
git \
&& \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
true
# Built with
# apt install build-essential dh-autoreconf git
# apt install libssl-dev
# apt install zlib1g-dev
# apt install libsasl2-dev
WORKDIR /home/user
USER user

3
build Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
# Note we assume this is run with podman. Take -u 0:0 out if using docker
docker run -u 0:0 -it --rm -v $(pwd):/make -w /make isync-build make

View file

@ -1,4 +1,4 @@
AC_INIT([isync], [1.4.1])
AC_INIT([isync], [1.4.4])
AC_CONFIG_HEADERS([autodefs.h])
AC_CANONICAL_TARGET
@ -62,8 +62,8 @@ if test "x$ob_cv_perl_ver" = "xno"; then
fi
AC_CACHE_CHECK([whether strftime supports %z], ob_cv_strftime_z,
[AC_TRY_RUN(
[#include <time.h>
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <time.h>
#include <string.h>
int main(void)
@ -73,7 +73,7 @@ int main(void)
strftime(buf, sizeof(buf), "%z", localtime(&t));
return !(buf[0] == '+' || buf[0] == '-');
}
], [ob_cv_strftime_z=yes], [ob_cv_strftime_z=no], [ob_cv_strftime_z="yes (assumed)"])])
]])], [ob_cv_strftime_z=yes], [ob_cv_strftime_z=no], [ob_cv_strftime_z="yes (assumed)"])])
if test "x$ob_cv_strftime_z" = x"no"; then
AC_MSG_ERROR([libc lacks necessary feature])
fi
@ -96,7 +96,7 @@ fi
have_ssl_paths=
AC_ARG_WITH(ssl,
AC_HELP_STRING([--with-ssl[=PATH]], [where to look for SSL [detect]]),
AS_HELP_STRING([--with-ssl[=PATH]], [where to look for SSL [detect]]),
[ob_cv_with_ssl=$withval])
if test "x$ob_cv_with_ssl" != xno; then
case $ob_cv_with_ssl in
@ -193,12 +193,13 @@ AC_CACHE_CHECK([for Berkeley DB >= 4.1], ac_cv_berkdb4,
[ac_cv_berkdb4=no
sav_LIBS=$LIBS
LIBS="$LIBS -ldb"
AC_TRY_LINK([#include <db.h>],
[DB *db;
db_create(&db, 0, 0);
db->truncate(db, 0, 0, 0);
db->open(db, 0, "foo", "foo", DB_HASH, DB_CREATE, 0)],
[ac_cv_berkdb4=yes])
AC_LINK_IFELSE([AC_LANG_PROGRAM(
[#include <db.h>],
[DB *db;
db_create(&db, 0, 0);
db->truncate(db, 0, 0, 0);
db->open(db, 0, "foo", "foo", DB_HASH, DB_CREATE, 0);
])], [ac_cv_berkdb4=yes], [])
LIBS=$sav_LIBS
])
if test "x$ac_cv_berkdb4" = xyes; then

View file

@ -35,7 +35,6 @@ typedef struct driver driver_t;
struct store_conf *next; \
char *name; \
driver_t *driver; \
const char *path; /* should this be here? its interpretation is driver-specific */ \
const char *flat_delim; \
const char *map_inbox; \
const char *trash; \

View file

@ -74,6 +74,7 @@ typedef union imap_store_conf {
struct {
STORE_CONF
imap_server_conf_t *server;
char *path; // Note: this may be modified after the delimiter is determined.
char delimiter;
char use_namespace;
char use_lsub;
@ -114,8 +115,8 @@ union imap_store {
struct {
STORE(union imap_store)
const char *label; // foreign
const char *prefix;
const char *name;
char *prefix;
uint ref_count;
uint opts;
enum { SST_BAD, SST_HALF, SST_GOOD } state;
@ -216,7 +217,6 @@ typedef union {
IMAP_CMD
void (*callback)( int sts, uint uid, void *aux );
void *callback_aux;
uint out_uid;
};
} imap_cmd_out_uid_t;
@ -335,42 +335,45 @@ done_imap_cmd( imap_store_t *ctx, imap_cmd_t *cmd, int response )
static void
send_imap_cmd( imap_store_t *ctx, imap_cmd_t *cmd )
{
int litplus, iovcnt = 1;
int bufl;
const char *buffmt;
conn_iovec_t iov[3];
char buf[4096];
int litplus, iovcnt = 3;
uint tbufl, lbufl;
conn_iovec_t iov[5];
char tagbuf[16];
char lenbuf[16];
cmd->tag = ++ctx->nexttag;
tbufl = nfsnprintf( tagbuf, sizeof(tagbuf), "%d ", cmd->tag );
if (!cmd->param.data) {
buffmt = "%d %s\r\n";
memcpy( lenbuf, "\r\n", 3 );
lbufl = 2;
litplus = 0;
} else if ((cmd->param.to_trash && ctx->trashnc == TrashUnknown) || !CAP(LITERALPLUS) || cmd->param.data_len >= 100*1024) {
buffmt = "%d %s{%d}\r\n";
lbufl = nfsnprintf( lenbuf, sizeof(lenbuf), "{%u}\r\n", cmd->param.data_len );
litplus = 0;
} else {
buffmt = "%d %s{%d+}\r\n";
lbufl = nfsnprintf( lenbuf, sizeof(lenbuf), "{%u+}\r\n", cmd->param.data_len );
litplus = 1;
}
DIAG_PUSH
DIAG_DISABLE("-Wformat-nonliteral")
bufl = nfsnprintf( buf, sizeof(buf), buffmt,
cmd->tag, cmd->cmd, cmd->param.data_len );
DIAG_POP
if (DFlags & DEBUG_NET) {
if (ctx->num_in_progress)
printf( "(%d in progress) ", ctx->num_in_progress );
if (starts_with( cmd->cmd, -1, "LOGIN", 5 ))
printf( "%s>>> %d LOGIN <user> <pass>\n", ctx->label, cmd->tag );
printf( "%s>>> %sLOGIN <user> <pass>\r\n", ctx->label, tagbuf );
else if (starts_with( cmd->cmd, -1, "AUTHENTICATE PLAIN", 18 ))
printf( "%s>>> %d AUTHENTICATE PLAIN <authdata>\n", ctx->label, cmd->tag );
printf( "%s>>> %sAUTHENTICATE PLAIN <authdata>\r\n", ctx->label, tagbuf );
else
printf( "%s>>> %s", ctx->label, buf );
printf( "%s>>> %s%s%s", ctx->label, tagbuf, cmd->cmd, lenbuf );
fflush( stdout );
}
iov[0].buf = buf;
iov[0].len = (uint)bufl;
iov[0].buf = tagbuf;
iov[0].len = tbufl;
iov[0].takeOwn = KeepOwn;
iov[1].buf = cmd->cmd;
iov[1].len = strlen( cmd->cmd );
iov[1].takeOwn = KeepOwn;
iov[2].buf = lenbuf;
iov[2].len = lbufl;
iov[2].takeOwn = KeepOwn;
if (litplus) {
if (DFlags & DEBUG_NET_ALL) {
printf( "%s>>>>>>>>>\n", ctx->label );
@ -378,15 +381,15 @@ DIAG_POP
printf( "%s>>>>>>>>>\n", ctx->label );
fflush( stdout );
}
iov[1].buf = cmd->param.data;
iov[1].len = cmd->param.data_len;
iov[1].takeOwn = GiveOwn;
iov[3].buf = cmd->param.data;
iov[3].len = cmd->param.data_len;
iov[3].takeOwn = GiveOwn;
cmd->param.data = NULL;
ctx->buffer_mem -= cmd->param.data_len;
iov[2].buf = "\r\n";
iov[2].len = 2;
iov[2].takeOwn = KeepOwn;
iovcnt = 3;
iov[4].buf = "\r\n";
iov[4].len = 2;
iov[4].takeOwn = KeepOwn;
iovcnt = 5;
}
socket_write( &ctx->conn, iov, iovcnt );
if (cmd->param.to_trash && ctx->trashnc == TrashUnknown)
@ -512,7 +515,20 @@ imap_vprintf( const char *fmt, va_list ap )
const char *s;
char *d, *ed;
char c;
char buf[4096];
#define MAX_SEGS 16
#define add_seg(s, l) \
do { \
if (nsegs == MAX_SEGS) \
oob(); \
segs[nsegs] = s; \
segls[nsegs++] = l; \
totlen += l; \
} while (0)
int nsegs = 0;
uint totlen = 0;
const char *segs[MAX_SEGS];
uint segls[MAX_SEGS];
char buf[1000];
d = buf;
ed = d + sizeof(buf);
@ -521,12 +537,10 @@ imap_vprintf( const char *fmt, va_list ap )
c = *fmt;
if (!c || c == '%') {
uint l = fmt - s;
if (d + l > ed)
oob();
memcpy( d, s, l );
d += l;
if (l)
add_seg( s, l );
if (!c)
return nfstrndup( buf, (size_t)(d - buf) );
break;
uint maxlen = UINT_MAX;
c = *++fmt;
if (c == '\\') {
@ -535,6 +549,7 @@ imap_vprintf( const char *fmt, va_list ap )
fputs( "Fatal: unsupported escaped format specifier. Please report a bug.\n", stderr );
abort();
}
char *bd = d;
s = va_arg( ap, const char * );
while ((c = *s++)) {
if (d + 2 > ed)
@ -543,6 +558,9 @@ imap_vprintf( const char *fmt, va_list ap )
*d++ = '\\';
*d++ = c;
}
l = d - bd;
if (l)
add_seg( bd, l );
} else { /* \\ cannot be combined with anything else. */
if (c == '.') {
c = *++fmt;
@ -556,18 +574,21 @@ imap_vprintf( const char *fmt, va_list ap )
if (c == 'c') {
if (d + 1 > ed)
oob();
add_seg( d, 1 );
*d++ = (char)va_arg( ap , int );
} else if (c == 's') {
s = va_arg( ap, const char * );
l = strnlen( s, maxlen );
if (d + l > ed)
oob();
memcpy( d, s, l );
d += l;
if (l)
add_seg( s, l );
} else if (c == 'd') {
d += nfsnprintf( d, ed - d, "%d", va_arg( ap , int ) );
l = nfsnprintf( d, ed - d, "%d", va_arg( ap, int ) );
add_seg( d, l );
d += l;
} else if (c == 'u') {
d += nfsnprintf( d, ed - d, "%u", va_arg( ap , uint ) );
l = nfsnprintf( d, ed - d, "%u", va_arg( ap, uint ) );
add_seg( d, l );
d += l;
} else {
fputs( "Fatal: unsupported format specifier. Please report a bug.\n", stderr );
abort();
@ -578,6 +599,13 @@ imap_vprintf( const char *fmt, va_list ap )
fmt++;
}
}
char *out = d = nfmalloc( totlen + 1 );
for (int i = 0; i < nsegs; i++) {
memcpy( d, segs[i], segls[i] );
d += segls[i];
}
*d = 0;
return out;
}
static void
@ -849,6 +877,11 @@ parse_imap_list( imap_store_t *ctx, char **sp, parse_list_state_t *sts )
bytes = (int)(cur->len = strtoul( s + 1, &s, 10 ));
if (*s != '}' || *++s)
goto bail;
if ((uint)bytes >= INT_MAX) {
error( "IMAP error: excessively large literal from %s "
"- THIS MIGHT BE AN ATTEMPT TO HACK YOU!\n", ctx->conn.name );
goto bail;
}
s = cur->val = nfmalloc( cur->len + 1 );
s[cur->len] = 0;
@ -1272,7 +1305,7 @@ parse_response_code( imap_store_t *ctx, imap_cmd_t *cmd, char *s )
return RESP_CANCEL;
}
} else if (!strcmp( "CAPABILITY", arg )) {
if (!(p = strchr( s, ']' ))) {
if (!s || !(p = strchr( s, ']' ))) {
error( "IMAP error: malformed CAPABILITY status\n" );
return RESP_CANCEL;
}
@ -1288,11 +1321,22 @@ parse_response_code( imap_store_t *ctx, imap_cmd_t *cmd, char *s )
}
for (; isspace( (uchar)*s ); s++);
error( "*** IMAP ALERT *** %s\n", s );
} else if (cmd && !strcmp( "APPENDUID", arg )) {
} else if (!strcmp( "APPENDUID", arg )) {
// The checks ensure that:
// - cmd => this is the final tagged response of a command, at which
// point cmd was already removed from ctx->in_progress, so param.uid
// is available for reuse.
// - !param.uid => the command isn't actually a FETCH. This doesn't
// really matter, as the field is safe to overwrite given the
// previous condition; it just has no effect for non-APPENDs.
if (!cmd || cmd->param.uid) {
error( "IMAP error: unexpected APPENDUID status\n" );
return RESP_CANCEL;
}
if (!(arg = next_arg( &s )) ||
(ctx->uidvalidity = strtoul( arg, &earg, 10 ), *earg) ||
!(arg = next_arg( &s )) ||
(((imap_cmd_out_uid_t *)cmd)->out_uid = strtoul( arg, &earg, 10 ), *earg != ']'))
(cmd->param.uid = strtoul( arg, &earg, 10 ), *earg != ']'))
{
error( "IMAP error: malformed APPENDUID status\n" );
return RESP_CANCEL;
@ -1374,6 +1418,13 @@ is_INBOX( imap_store_t *ctx, const char *arg, int argl )
return 1;
}
static void
normalize_INBOX( imap_store_t *ctx, char *arg, int argl )
{
if (is_inbox( ctx, arg, argl ))
memcpy( arg, "INBOX", 5 );
}
static int
parse_list_rsp_p2( imap_store_t *ctx, list_t *list, char *cmd ATTR_UNUSED )
{
@ -1388,33 +1439,31 @@ parse_list_rsp_p2( imap_store_t *ctx, list_t *list, char *cmd ATTR_UNUSED )
}
arg = list->val;
argl = (int)list->len;
if (argl > 1000) {
warn( "IMAP warning: ignoring unreasonably long mailbox name '%.100s[...]'\n", arg );
return LIST_OK;
}
// The server might be weird and have a non-uppercase INBOX. It
// may legitimately do so, but we need the canonical spelling.
normalize_INBOX( ctx, arg, argl );
if ((l = strlen( ctx->prefix ))) {
if (!starts_with( arg, argl, ctx->prefix, l )) {
if (is_inbox( ctx, arg, argl )) {
// INBOX and its subfolders bypass the namespace.
goto inbox;
if (!is_INBOX( ctx, arg, argl ))
return LIST_OK;
// INBOX and its subfolders bypass the namespace.
} else {
arg += l;
argl -= l;
// A folder named "INBOX" would be indistinguishable from the
// actual INBOX after prefix stripping, so drop it. This applies
// only to the fully uppercased spelling, as our canonical box
// names are case-sensitive (unlike IMAP's INBOX).
if (is_INBOX( ctx, arg, argl )) {
if (!arg[5]) // No need to complain about subfolders as well.
warn( "IMAP warning: ignoring INBOX in %s\n", ctx->prefix );
return LIST_OK;
}
return LIST_OK;
}
arg += l;
argl -= l;
// A folder named "INBOX" would be indistinguishable from the
// actual INBOX after prefix stripping, so drop it. This applies
// only to the fully uppercased spelling, as our canonical box
// names are case-sensitive (unlike IMAP's INBOX).
if (is_INBOX( ctx, arg, argl )) {
if (!arg[5]) // No need to complain about subfolders as well.
warn( "IMAP warning: ignoring INBOX in %s\n", ctx->prefix );
return LIST_OK;
}
} else if (is_inbox( ctx, arg, argl )) {
inbox:
// The server might be weird and have a non-uppercase INBOX. It
// may legitimately do so, but we need the canonical spelling.
// Note that we do that only after prefix matching, under the
// assumption that the NAMESPACE (or Path) matches the
// capitalization of LIST.
memcpy( arg, "INBOX", 5 );
}
if (argl >= 5 && !memcmp( arg + argl - 5, ".lock", 5 )) /* workaround broken servers */
return LIST_OK;
@ -2539,6 +2588,8 @@ imap_open_store_finalize( imap_store_t *ctx )
ctx->state = SST_GOOD;
if (!ctx->prefix)
ctx->prefix = "";
else
normalize_INBOX( ctx, ctx->prefix, -1 );
ctx->trashnc = TrashUnknown;
ctx->callbacks.imap_open( DRV_OK, ctx->callback_aux );
}
@ -3163,7 +3214,6 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int to_trash,
ctx->buffer_mem += data->len;
cmd->param.data_len = data->len;
cmd->param.data = data->data;
cmd->out_uid = 0;
if (to_trash) {
cmd->param.create = 1;
@ -3199,7 +3249,7 @@ imap_store_msg_p2( imap_store_t *ctx ATTR_UNUSED, imap_cmd_t *cmd, int response
imap_cmd_out_uid_t *cmdp = (imap_cmd_out_uid_t *)cmd;
transform_msg_response( &response );
cmdp->callback( response, cmdp->out_uid, cmdp->callback_aux );
cmdp->callback( response, cmdp->param.uid, cmdp->callback_aux );
}
/******************* imap_find_new_msgs *******************/

View file

@ -54,6 +54,7 @@ typedef union maildir_store_conf {
store_conf_t gen;
struct {
STORE_CONF
char *path;
char *inbox;
#ifdef USE_DB
int alt_map;
@ -394,7 +395,7 @@ static int maildir_list_inbox( maildir_store_t *ctx, int flags, const char *base
static int maildir_list_path( maildir_store_t *ctx, int flags, const char *inbox );
static int
maildir_list_recurse( maildir_store_t *ctx, int isBox, int flags,
maildir_list_recurse( maildir_store_t *ctx, int isBox, int flags, int depth,
const char *inbox, uint inboxLen, const char *basePath, uint basePathLen,
char *path, int pathLen, char *name, int nameLen )
{
@ -416,6 +417,12 @@ maildir_list_recurse( maildir_store_t *ctx, int isBox, int flags,
closedir( dir );
return -1;
}
if (++depth > 10) {
// We do the other checks first to avoid confusing error messages for files.
error( "Maildir error: path %s is too deeply nested. Symlink loop?\n", path );
closedir( dir );
return -1;
}
while ((de = readdir( dir ))) {
const char *ent = de->d_name;
if (ent[0] == '.' && (!ent[1] || (ent[1] == '.' && !ent[2])))
@ -463,7 +470,7 @@ maildir_list_recurse( maildir_store_t *ctx, int isBox, int flags,
add_string_list( &ctx->boxes, name );
path[pl] = 0;
name[nl++] = '/';
if (maildir_list_recurse( ctx, isBox + 1, flags, inbox, inboxLen, basePath, basePathLen, path, pl, name, nl ) < 0) {
if (maildir_list_recurse( ctx, isBox + 1, flags, depth, inbox, inboxLen, basePath, basePathLen, path, pl, name, nl ) < 0) {
closedir( dir );
return -1;
}
@ -484,7 +491,7 @@ maildir_list_inbox( maildir_store_t *ctx, int flags, const char *basePath )
add_string_list( &ctx->boxes, "INBOX" );
return maildir_list_recurse(
ctx, 1, flags, NULL, 0, basePath, basePath ? strlen( basePath ) - 1 : 0,
ctx, 1, flags, 0, NULL, 0, basePath, basePath ? strlen( basePath ) - 1 : 0,
path, nfsnprintf( path, _POSIX_PATH_MAX, "%s/", ctx->conf->inbox ),
name, nfsnprintf( name, _POSIX_PATH_MAX, "INBOX/" ) );
}
@ -501,7 +508,7 @@ maildir_list_path( maildir_store_t *ctx, int flags, const char *inbox )
if (maildir_ensure_path( ctx->conf ) < 0)
return -1;
return maildir_list_recurse(
ctx, 0, flags, inbox, inbox ? strlen( inbox ) : 0, NULL, 0,
ctx, 0, flags, 0, inbox, inbox ? strlen( inbox ) : 0, NULL, 0,
path, nfsnprintf( path, _POSIX_PATH_MAX, "%s", ctx->conf->path ),
name, 0 );
}
@ -913,6 +920,28 @@ maildir_compare( const void *l, const void *r )
return strcmp( lm->base, rm->base );
}
int is_gmail(const char* name) {
// NOTE: This is not an exact science. For instance, there is a good
// shot that mail sent from containers will end up matching this pattern
char token[16]; // If host name is longer than 12, then we already have our answer
// we expect files in the form of: "1700172103.R12128272304961211247.hostname,U=27:2,S"
// we are looking for "hostname"
// All gmail mails have a hostname in the form of "ff6d55f971cc".
// All are 12 characters long
// All are hexadecimal
if (sscanf(name, "%*[^.].%*[^.].%15[^,]", token) == 1) {
token[14] = '\0';
size_t len = strlen(token);
if (len != 12) return 0;
if (strspn(token, "0123456789abcdef") == len) return 1;
}
// We are here because a) the input format is not as expected,
// or b) the hostname is not gmail
return 0;
}
static int
maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
{
@ -984,9 +1013,25 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
#endif /* USE_DB */
return DRV_BOX_BAD;
}
const char *filter = getenv("MBSYNC_MAILDIR_IGNORE");
const char *MAGIC_INCLUDE = "^[^\\.]+\\.[^\\.]+\\.[0-9a-f]{12}\\,.*";
char *include = getenv("MBSYNC_MAILDIR_INCLUDE_ONLY");
if (include && strncmp(MAGIC_INCLUDE, include, strlen(MAGIC_INCLUDE)) != 0) {
include = NULL;
error("MBSYNC_MAILDIR_INCLUDE_ONLY can only be '%s'\n", MAGIC_INCLUDE);
}
if (include && filter) {
include = NULL;
error("MBSYNC_MAILDIR_IGNORE cannot be used with MBSYNC_MAILDIR_INCLUDE_ONLY. INCLUDE_ONLY will be ignored\n");
}
while ((e = readdir( d ))) {
if (*e->d_name == '.')
continue;
if (filter && strstr(e->d_name, filter))
continue;
if (include && !is_gmail(e->d_name))
continue;
ctx->total_msgs++;
ctx->recent_msgs += i;
#ifdef USE_DB
@ -1161,7 +1206,8 @@ maildir_scan( maildir_store_t *ctx, msg_t_array_alloc_t *msglist )
}
goto retry;
}
entry->size = (uint)st.st_size;
// The clipped value is good enough for MaxSize comparisons.
entry->size = st.st_size > UINT_MAX ? UINT_MAX : (uint)st.st_size;
}
if (want_tuid || want_msgid) {
if (!(f = fopen( buf, "r" ))) {
@ -1556,12 +1602,17 @@ maildir_fetch_msg( store_t *gctx, message_t *gmsg, msg_data_t *data, int minimal
}
}
fstat( fd, &st );
if (st.st_size > INT_MAX) {
error( "Maildir error: %s is too big", buf );
goto mbad;
}
data->len = st.st_size;
if (data->date == -1)
data->date = st.st_mtime;
data->data = nfmalloc( data->len );
if (read( fd, data->data, data->len ) != data->len) {
sys_error( "Maildir error: cannot read %s", buf );
mbad:
close( fd );
cb( DRV_MSG_BAD, aux );
return;

View file

@ -406,11 +406,11 @@ copy_msg_bytes( char **out_ptr, const char *in_buf, uint *in_idx, uint in_len, i
}
static int
copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars, int t )
{
char *in_buf = vars->data.data;
uint in_len = vars->data.len;
uint idx = 0, sbreak = 0, ebreak = 0, break2 = 0;
uint idx = 0, sbreak = 0, ebreak = 0, break2 = UINT_MAX;
uint lines = 0, hdr_crs = 0, bdy_crs = 0, app_cr = 0, extra = 0;
uint add_subj = 0;
@ -428,9 +428,10 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
if (!vars->minimal)
goto oke;
} else {
if (!break2 && vars->minimal && !strncasecmp( in_buf + start, "Subject:", 8 )) {
if (break2 == UINT_MAX && vars->minimal &&
starts_with_upper( in_buf + start, (int)(in_len - start), "SUBJECT:", 8 )) {
break2 = start + 8;
if (in_buf[break2] == ' ')
if (break2 < in_len && in_buf[break2] == ' ')
break2++;
}
lines++;
@ -441,7 +442,7 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
sbreak = ebreak = start;
if (vars->minimal) {
in_len = idx;
if (!break2) {
if (break2 == UINT_MAX) {
break2 = start;
add_subj = 1;
}
@ -451,7 +452,8 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
goto nloop;
}
}
/* invalid message */
warn( "Warning: message %u from %s has incomplete header; skipping.\n",
vars->msg->uid, str_fn[1-t] );
free( in_buf );
return 0;
oke:
@ -493,10 +495,16 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
}
vars->data.len = in_len + extra;
if (vars->data.len > INT_MAX) {
warn( "Warning: message %u from %s is too big after conversion; skipping.\n",
vars->msg->uid, str_fn[1-t] );
free( in_buf );
return 0;
}
char *out_buf = vars->data.data = nfmalloc( vars->data.len );
idx = 0;
if (vars->srec) {
if (break2 && break2 < sbreak) {
if (break2 < sbreak) {
copy_msg_bytes( &out_buf, in_buf, &idx, break2, in_cr, out_cr );
memcpy( out_buf, dummy_pfx, strlen(dummy_pfx) );
out_buf += strlen(dummy_pfx);
@ -512,7 +520,7 @@ copy_msg_convert( int in_cr, int out_cr, copy_vars_t *vars )
*out_buf++ = '\n';
idx = ebreak;
if (break2 >= sbreak) {
if (break2 != UINT_MAX && break2 >= sbreak) {
copy_msg_bytes( &out_buf, in_buf, &idx, break2, in_cr, out_cr );
if (!add_subj) {
memcpy( out_buf, dummy_pfx, strlen(dummy_pfx) );
@ -556,9 +564,7 @@ msg_fetched( int sts, void *aux )
scr = (svars->drv[1-t]->get_caps( svars->ctx[1-t] ) / DRV_CRLF) & 1;
tcr = (svars->drv[t]->get_caps( svars->ctx[t] ) / DRV_CRLF) & 1;
if (vars->srec || scr != tcr) {
if (!copy_msg_convert( scr, tcr, vars )) {
warn( "Warning: message %u from %s has incomplete header.\n",
vars->msg->uid, str_fn[1-t] );
if (!copy_msg_convert( scr, tcr, vars, t )) {
vars->cb( SYNC_NOGOOD, 0, vars );
return;
}
@ -1690,7 +1696,11 @@ box_loaded( int sts, message_t *msgs, int total_msgs, int recent_msgs, void *aux
JLOG( "> %u %u 0", (srec->uid[F], srec->uid[N]), "near side expired, orphaning far side" );
srec->uid[N] = 0;
} else {
if (srec->msg[t] && (srec->msg[t]->status & M_FLAGS) && srec->msg[t]->flags != srec->flags)
if (srec->msg[t] && (srec->msg[t]->status & M_FLAGS) &&
// Ignore deleted flag, as that's what we'll change ourselves ...
(((srec->msg[t]->flags & ~F_DELETED) != (srec->flags & ~F_DELETED)) ||
// ... except for undeletion, as that's the opposite.
(!(srec->msg[t]->flags & F_DELETED) && (srec->flags & F_DELETED))))
notice( "Notice: conflicting changes in (%u,%u)\n", srec->uid[F], srec->uid[N] );
if (svars->chan->ops[t] & OP_DELETE) {
debug( " %sing delete\n", str_hl[t] );