isync/src/mdconvert.c
Oswald Buddenhagen d34baeb886 fix hypothetical buffer overflows
if something managed to make the maildir .uidvalidity files big enough
(possible only by appending garbage or scrambling them alltogether), we
would overflow the read buffer by one when appending the terminating
null.
this is not expected to have any real-world impact.

found by coverity.
2014-04-12 13:03:46 +02:00

285 lines
7.4 KiB
C

/*
* mdconvert - Maildir UID scheme converter
* Copyright (C) 2004 Oswald Buddenhagen <ossi@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <autodefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <dirent.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <db.h>
#define EXE "mdconvert"
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
# define ATTR_NORETURN __attribute__((noreturn))
# define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var)))
#else
# define ATTR_NORETURN
# define ATTR_PRINTFLIKE(fmt,var)
#endif
static void ATTR_NORETURN
oob( void )
{
fputs( "Fatal: buffer too small. Please report a bug.\n", stderr );
abort();
}
static void ATTR_PRINTFLIKE(1, 2)
sys_error( const char *msg, ... )
{
va_list va;
char buf[1024];
va_start( va, msg );
if ((unsigned)vsnprintf( buf, sizeof(buf), msg, va ) >= sizeof(buf))
oob();
va_end( va );
perror( buf );
}
static int ATTR_PRINTFLIKE(3, 4)
nfsnprintf( char *buf, int blen, const char *fmt, ... )
{
int ret;
va_list va;
va_start( va, fmt );
if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
oob();
va_end( va );
return ret;
}
static const char *subdirs[] = { "cur", "new" };
static struct flock lck;
static DBT key, value;
static int
convert( const char *box, int altmap )
{
DB *db;
DIR *d;
struct dirent *e;
const char *u, *ru;
char *p, *s, *dpath, *spath, *dbpath;
int i, n, ret, sfd, dfd, bl, ml, uv[2], uid;
struct stat st;
char buf[_POSIX_PATH_MAX], buf2[_POSIX_PATH_MAX];
char umpath[_POSIX_PATH_MAX], uvpath[_POSIX_PATH_MAX], tdpath[_POSIX_PATH_MAX];
if (stat( box, &st ) || !S_ISDIR(st.st_mode)) {
fprintf( stderr, "'%s' is no Maildir mailbox.\n", box );
return 1;
}
nfsnprintf( umpath, sizeof(umpath), "%s/.isyncuidmap.db", box );
nfsnprintf( uvpath, sizeof(uvpath), "%s/.uidvalidity", box );
if (altmap)
dpath = umpath, spath = uvpath, dbpath = tdpath;
else
spath = umpath, dpath = uvpath, dbpath = umpath;
nfsnprintf( tdpath, sizeof(tdpath), "%s.tmp", dpath );
if ((sfd = open( spath, O_RDWR )) < 0) {
if (errno != ENOENT)
sys_error( "Cannot open %s", spath );
return 1;
}
if (fcntl( sfd, F_SETLKW, &lck )) {
sys_error( "Cannot lock %s", spath );
goto sbork;
}
if ((dfd = open( tdpath, O_RDWR|O_CREAT, 0600 )) < 0) {
sys_error( "Cannot create %s", tdpath );
goto sbork;
}
if (db_create( &db, 0, 0 )) {
fputs( "Error: db_create() failed\n", stderr );
goto tbork;
}
if ((ret = (db->open)( db, 0, dbpath, 0, DB_HASH, altmap ? DB_CREATE|DB_TRUNCATE : 0, 0 ))) {
db->err( db, ret, "Error: db->open(%s)", dbpath );
dbork:
db->close( db, 0 );
tbork:
unlink( tdpath );
close( dfd );
sbork:
close( sfd );
return 1;
}
key.data = (void *)"UIDVALIDITY";
key.size = 11;
if (altmap) {
if ((n = read( sfd, buf, sizeof(buf) - 1 )) <= 0 ||
(buf[n] = 0, sscanf( buf, "%d\n%d", &uv[0], &uv[1] ) != 2))
{
fprintf( stderr, "Error: cannot read UIDVALIDITY of '%s'.\n", box );
goto dbork;
}
value.data = uv;
value.size = sizeof(uv);
if ((ret = db->put( db, 0, &key, &value, 0 ))) {
db->err( db, ret, "Error: cannot write UIDVALIDITY for '%s'", box );
goto dbork;
}
} else {
if ((ret = db->get( db, 0, &key, &value, 0 ))) {
db->err( db, ret, "Error: cannot read UIDVALIDITY of '%s'", box );
goto dbork;
}
n = sprintf( buf, "%d\n%d\n", ((int *)value.data)[0], ((int *)value.data)[1] );
if (write( dfd, buf, n ) != n) {
fprintf( stderr, "Error: cannot write UIDVALIDITY for '%s'.\n", box );
goto dbork;
}
}
again:
for (i = 0; i < 2; i++) {
bl = nfsnprintf( buf, sizeof(buf), "%s/%s/", box, subdirs[i] );
if (!(d = opendir( buf ))) {
sys_error( "Cannot list %s", buf );
goto dbork;
}
while ((e = readdir( d ))) {
if (*e->d_name == '.')
continue;
nfsnprintf( buf + bl, sizeof(buf) - bl, "%s", e->d_name );
memcpy( buf2, buf, bl );
p = strstr( e->d_name, ",U=" );
if (p)
for (u = p, ru = p + 3; isdigit( (unsigned char)*ru ); ru++);
else
u = ru = strchr( e->d_name, ':' );
if (u)
ml = u - e->d_name;
else
ru = "", ml = sizeof(buf);
if (altmap) {
if (!p)
continue;
key.data = e->d_name;
key.size = (size_t)(strchr( e->d_name, ',' ) - e->d_name);
uid = atoi( p + 3 );
value.data = &uid;
value.size = sizeof(uid);
if ((ret = db->put( db, 0, &key, &value, 0 ))) {
db->err( db, ret, "Error: cannot write UID for '%s'", box );
goto ebork;
}
nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s%s", ml, e->d_name, ru );
} else {
s = strpbrk( e->d_name, ",:" );
key.data = e->d_name;
key.size = s ? (size_t)(s - e->d_name) : strlen( e->d_name );
if ((ret = db->get( db, 0, &key, &value, 0 ))) {
if (ret != DB_NOTFOUND) {
db->err( db, ret, "Error: cannot read UID for '%s'", box );
goto ebork;
}
continue;
}
uid = *(int *)value.data;
nfsnprintf( buf2 + bl, sizeof(buf2) - bl, "%.*s,U=%d%s", ml, e->d_name, uid, ru );
}
if (rename( buf, buf2 )) {
if (errno == ENOENT) {
closedir( d );
goto again;
}
sys_error( "Cannot rename %s to %s", buf, buf2 );
ebork:
closedir( d );
goto dbork;
}
}
closedir( d );
}
db->close( db, 0 );
close( dfd );
if (rename( tdpath, dpath )) {
sys_error( "Cannot rename %s to %s", tdpath, dpath );
close( sfd );
return 1;
}
if (unlink( spath ))
sys_error( "Cannot remove %s", spath );
close( sfd );
return 0;
}
int
main( int argc, char **argv )
{
int oint, ret, altmap = 0;
for (oint = 1; oint < argc; oint++) {
if (!strcmp( argv[oint], "-h" ) || !strcmp( argv[oint], "--help" )) {
puts(
"Usage: " EXE " [-a] mailbox...\n"
" -a, --alt convert to alternative (DB based) UID scheme\n"
" -n, --native convert to native (file name based) UID scheme (default)\n"
" -h, --help show this help message\n"
" -v, --version display version"
);
return 0;
} else if (!strcmp( argv[oint], "-v" ) || !strcmp( argv[oint], "--version" )) {
puts( EXE " " VERSION " - Maildir UID scheme converter" );
return 0;
} else if (!strcmp( argv[oint], "-a" ) || !strcmp( argv[oint], "--alt" )) {
altmap = 1;
} else if (!strcmp( argv[oint], "-n" ) || !strcmp( argv[oint], "--native" )) {
altmap = 0;
} else if (!strcmp( argv[oint], "--" )) {
oint++;
break;
} else if (argv[oint][0] == '-') {
fprintf( stderr, "Unrecognized option '%s'. Try " EXE " -h\n", argv[oint] );
return 1;
} else
break;
}
if (oint == argc) {
fprintf( stderr, "Mailbox specification missing. Try " EXE " -h\n" );
return 1;
}
#if SEEK_SET != 0
lck.l_whence = SEEK_SET;
#endif
#if F_WRLCK != 0
lck.l_type = F_WRLCK;
#endif
ret = 0;
for (; oint < argc; oint++)
ret |= convert( argv[oint], altmap );
return ret;
}