Commit ab1a0237 authored by Quanah Gibson-Mount's avatar Quanah Gibson-Mount
Browse files

ITS#5408,ITS#5319,ITS#5329

parent 210b7225
......@@ -31,24 +31,37 @@
#include "lutil.h"
#include "config.h"
typedef struct enumCookie {
Operation *op;
SlapReply *rs;
Entry **entries;
ID elen;
ID eind;
} enumCookie;
struct ldif_tool {
Entry **entries; /* collected by bi_tool_entry_first() */
ID elen; /* length of entries[] array */
ID ecount; /* number of entries */
ID ecurrent; /* bi_tool_entry_next() position */
# define ENTRY_BUFF_INCREMENT 500 /* initial entries[] length */
};
/* Per-database data */
struct ldif_info {
struct berval li_base_path;
enumCookie li_tool_cookie;
ID li_tool_current;
ldap_pvt_thread_rdwr_t li_rdwr;
struct berval li_base_path; /* database directory */
struct ldif_tool li_tool; /* for slap tools */
/*
* Read-only LDAP requests readlock li_rdwr for filesystem input.
* Update requests first lock li_modop_mutex for filesystem I/O,
* and then writelock li_rdwr as well for filesystem output.
* This allows update requests to do callbacks that acquire
* read locks, e.g. access controls that inspect entries.
* (An alternative would be recursive read/write locks.)
*/
ldap_pvt_thread_mutex_t li_modop_mutex; /* serialize update requests */
ldap_pvt_thread_rdwr_t li_rdwr; /* no other I/O when writing */
};
#ifdef _WIN32
#define mkdir(a,b) mkdir(a)
#define move_file(from, to) (!MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING))
#else
#define move_file(from, to) rename(from, to)
#endif
#define move_dir(from, to) rename(from, to)
#define LDIF ".ldif"
......@@ -58,11 +71,10 @@ struct ldif_info {
* Unsafe/translated characters in the filesystem.
*
* LDIF_UNSAFE_CHAR(c) returns true if the character c is not to be used
* in relative filenames, except it should accept '\\' even if unsafe and
* need not reject '{' and '}'. The value should be a constant expression.
* in relative filenames, except it should accept '\\', '{' and '}' even
* if unsafe. The value should be a constant expression.
*
* If '\\' is unsafe, #define LDIF_ESCAPE_CHAR as a safe character.
*
* If '{' and '}' are unsafe, #define IX_FSL/IX_FSR as safe characters.
* (Not digits, '-' or '+'. IX_FSL == IX_FSR is allowed.)
*
......@@ -87,7 +99,7 @@ struct ldif_info {
#else /* _WIN32 */
/* Windows version - Microsoft's list of unsafe characters, except '\\' */
#define LDIF_ESCAPE_CHAR '^'
#define LDIF_ESCAPE_CHAR '^' /* Not '\\' (unsafe on Windows) */
#define LDIF_UNSAFE_CHAR(c) \
((c) == '/' || (c) == ':' || \
(c) == '<' || (c) == '>' || (c) == '"' || \
......@@ -132,8 +144,19 @@ struct ldif_info {
(!(LDIF_UNSAFE_CHAR(x) || (x) == '\\' || (x) == IX_DNL || (x) == IX_DNR) \
&& (c) == (x))
/* Collect other "safe char" tests here, until someone needs a fix. */
enum {
eq_unsafe = LDIF_UNSAFE_CHAR('='),
safe_filenames = STRLENOF("" LDAP_DIRSEP "") == 1 && !(
LDIF_UNSAFE_CHAR('-') || /* for "{-1}frontend" in bconfig.c */
LDIF_UNSAFE_CHAR(LDIF_ESCAPE_CHAR) ||
LDIF_UNSAFE_CHAR(IX_FSL) || LDIF_UNSAFE_CHAR(IX_FSR))
};
/* Sanity check: Try to force a compilation error if !safe_filenames */
typedef struct {
int assert_safe_filenames : safe_filenames ? 2 : -2;
} assert_safe_filenames[safe_filenames ? 2 : -2];
#define ENTRY_BUFF_INCREMENT 500
static ConfigTable ldifcfg[] = {
{ "directory", "dir", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
......@@ -156,6 +179,10 @@ static ConfigOCs ldifocs[] = {
};
/*
* Handle file/directory names.
*/
/* Set *res = LDIF filename path for the normalized DN */
static void
dn2path( BackendDB *be, struct berval *dn, struct berval *res )
......@@ -215,48 +242,157 @@ dn2path( BackendDB *be, struct berval *dn, struct berval *res )
assert( res->bv_len <= len );
}
static char * slurp_file(int fd) {
int read_chars_total = 0;
int read_chars = 0;
int entry_size;
char * entry;
char * entry_pos;
/*
* *dest = dupbv(<dir + LDAP_DIRSEP>), plus room for <more>-sized filename.
* Return pointer past the dirname.
*/
static char *
fullpath_alloc( struct berval *dest, const struct berval *dir, ber_len_t more )
{
char *s = SLAP_MALLOC( dir->bv_len + more + 2 );
dest->bv_val = s;
if ( s == NULL ) {
dest->bv_len = 0;
Debug( LDAP_DEBUG_ANY, "back-ldif: out of memory\n", 0, 0, 0 );
} else {
s = lutil_strcopy( dest->bv_val, dir->bv_val );
*s++ = LDAP_DIRSEP[0];
*s = '\0';
dest->bv_len = s - dest->bv_val;
}
return s;
}
/*
* Append filename to fullpath_alloc() dirname or replace previous filename.
* dir_end = fullpath_alloc() return value.
*/
#define FILL_PATH(fpath, dir_end, filename) \
((fpath)->bv_len = lutil_strcopy(dir_end, filename) - (fpath)->bv_val)
/* .ldif entry filename length <-> subtree dirname length. */
#define ldif2dir_len(bv) ((bv).bv_len -= STRLENOF(LDIF))
#define dir2ldif_len(bv) ((bv).bv_len += STRLENOF(LDIF))
/* .ldif entry filename <-> subtree dirname, both with dirname length. */
#define ldif2dir_name(bv) ((bv).bv_val[(bv).bv_len] = '\0')
#define dir2ldif_name(bv) ((bv).bv_val[(bv).bv_len] = LDIF_FILETYPE_SEP)
/* Get the parent directory path, plus the LDIF suffix overwritten by a \0. */
static int
get_parent_path( struct berval *dnpath, struct berval *res )
{
ber_len_t i = dnpath->bv_len;
while ( i > 0 && dnpath->bv_val[ --i ] != LDAP_DIRSEP[0] ) ;
if ( res == NULL ) {
res = dnpath;
} else {
res->bv_val = SLAP_MALLOC( i + 1 + STRLENOF(LDIF) );
if ( res->bv_val == NULL )
return LDAP_OTHER;
AC_MEMCPY( res->bv_val, dnpath->bv_val, i );
}
res->bv_len = i;
strcpy( res->bv_val + i, LDIF );
res->bv_val[i] = '\0';
return LDAP_SUCCESS;
}
/* Make temporary filename pattern for mkstemp() based on dnpath. */
static char *
ldif_tempname( const struct berval *dnpath )
{
static const char suffix[] = ".XXXXXX";
ber_len_t len = dnpath->bv_len - STRLENOF( LDIF );
char *name = SLAP_MALLOC( len + sizeof( suffix ) );
if ( name != NULL ) {
AC_MEMCPY( name, dnpath->bv_val, len );
strcpy( name + len, suffix );
}
return name;
}
/*
* Read a file, or stat() it if datap == NULL. Allocate and fill *datap.
* Return LDAP_SUCCESS, LDAP_NO_SUCH_OBJECT (no such file), or another error.
*/
static int
ldif_read_file( const char *path, char **datap )
{
int rc, fd, len;
int res = -1; /* 0:success, <0:error, >0:file too big/growing. */
struct stat st;
char *data = NULL, *ptr;
fstat(fd, &st);
entry_size = st.st_size;
entry = ch_malloc( entry_size+1 );
entry_pos = entry;
while(1) {
read_chars = read(fd, (void *) entry_pos, entry_size - read_chars_total);
if(read_chars == -1) {
SLAP_FREE(entry);
return NULL;
}
if(read_chars == 0) {
entry[read_chars_total] = '\0';
break;
if ( datap == NULL ) {
res = stat( path, &st );
goto done;
}
fd = open( path, O_RDONLY );
if ( fd >= 0 ) {
if ( fstat( fd, &st ) == 0 ) {
if ( st.st_size > INT_MAX - 2 ) {
res = 1;
} else {
len = st.st_size + 1; /* +1 detects file size > st.st_size */
*datap = data = ptr = SLAP_MALLOC( len + 1 );
if ( ptr != NULL ) {
while ( len && (res = read( fd, ptr, len )) ) {
if ( res > 0 ) {
len -= res;
ptr += res;
} else if ( errno != EINTR ) {
break;
}
}
*ptr = '\0';
}
}
}
else {
read_chars_total += read_chars;
entry_pos += read_chars;
if ( close( fd ) < 0 )
res = -1;
}
done:
if ( res == 0 ) {
Debug( LDAP_DEBUG_TRACE, "ldif_read_file: %s: \"%s\"\n",
datap ? "read entry file" : "entry file exists", path, 0 );
rc = LDAP_SUCCESS;
} else {
if ( res < 0 && errno == ENOENT ) {
Debug( LDAP_DEBUG_TRACE, "ldif_read_file: "
"no entry file \"%s\"\n", path, 0, 0 );
rc = LDAP_NO_SUCH_OBJECT;
} else {
const char *msg = res < 0 ? STRERROR( errno ) : "bad stat() size";
Debug( LDAP_DEBUG_ANY, "ldif_read_file: %s for \"%s\"\n",
msg, path, 0 );
rc = LDAP_OTHER;
}
if ( data != NULL )
SLAP_FREE( data );
}
return entry;
return rc;
}
/*
* return nonnegative for success or -1 for error
* do not return numbers less than -1
*/
static int spew_file(int fd, char * spew, int len) {
static int
spew_file( int fd, const char *spew, int len, int *save_errno )
{
int writeres = 0;
while(len > 0) {
writeres = write(fd, spew, len);
if(writeres == -1) {
return -1;
*save_errno = errno;
if (*save_errno != EINTR)
break;
}
else {
spew += writeres;
......@@ -266,412 +402,603 @@ static int spew_file(int fd, char * spew, int len) {
return writeres;
}
/* Write an entry LDIF file. Create parentdir first if non-NULL. */
static int
spew_entry( Entry * e, struct berval * path, int dolock, int *save_errnop )
ldif_write_entry(
Operation *op,
Entry *e,
const struct berval *path,
const char *parentdir,
const char **text )
{
int rs, save_errno = 0;
int openres;
int res, spew_res;
int entry_length;
char * entry_as_string;
char *tmpfname = NULL;
tmpfname = ch_malloc( path->bv_len + STRLENOF( "XXXXXX" ) + 1 );
AC_MEMCPY( tmpfname, path->bv_val, path->bv_len );
AC_MEMCPY( &tmpfname[ path->bv_len ], "XXXXXX", STRLENOF( "XXXXXX" ) + 1 );
openres = mkstemp( tmpfname );
if ( openres == -1 ) {
int rc = LDAP_OTHER, res, save_errno = 0;
int fd, entry_length;
char *entry_as_string, *tmpfname;
if ( op->o_abandon )
return SLAPD_ABANDON;
if ( parentdir != NULL && mkdir( parentdir, 0750 ) < 0 ) {
save_errno = errno;
rs = LDAP_UNWILLING_TO_PERFORM;
Debug( LDAP_DEBUG_ANY, "could not create tmpfile \"%s\": %s\n",
tmpfname, STRERROR( save_errno ), 0 );
Debug( LDAP_DEBUG_ANY, "ldif_write_entry: %s \"%s\": %s\n",
"cannot create parent directory",
parentdir, STRERROR( save_errno ) );
*text = "internal error (cannot create parent directory)";
return rc;
}
tmpfname = ldif_tempname( path );
fd = tmpfname == NULL ? -1 : mkstemp( tmpfname );
if ( fd < 0 ) {
save_errno = errno;
Debug( LDAP_DEBUG_ANY, "ldif_write_entry: %s for \"%s\": %s\n",
"cannot create file", e->e_dn, STRERROR( save_errno ) );
*text = "internal error (cannot create file)";
} else {
ber_len_t dn_len = e->e_name.bv_len;
struct berval rdn;
int tmp;
/* Only save the RDN onto disk */
dnRdn( &e->e_name, &rdn );
if ( rdn.bv_len != e->e_name.bv_len ) {
if ( rdn.bv_len != dn_len ) {
e->e_name.bv_val[rdn.bv_len] = '\0';
tmp = e->e_name.bv_len;
e->e_name.bv_len = rdn.bv_len;
rdn.bv_len = tmp;
}
spew_res = -2;
if ( dolock ) {
ldap_pvt_thread_mutex_lock(&entry2str_mutex);
}
entry_as_string = entry2str(e, &entry_length);
if ( entry_as_string != NULL ) {
spew_res = spew_file( openres,
entry_as_string, entry_length );
if ( spew_res == -1 ) {
save_errno = errno;
}
}
if ( dolock ) {
ldap_pvt_thread_mutex_unlock(&entry2str_mutex);
}
res = -2;
ldap_pvt_thread_mutex_lock( &entry2str_mutex );
entry_as_string = entry2str( e, &entry_length );
if ( entry_as_string != NULL )
res = spew_file( fd, entry_as_string, entry_length, &save_errno );
ldap_pvt_thread_mutex_unlock( &entry2str_mutex );
/* Restore full DN */
if ( rdn.bv_len != e->e_name.bv_len ) {
e->e_name.bv_val[e->e_name.bv_len] = ',';
e->e_name.bv_len = rdn.bv_len;
if ( rdn.bv_len != dn_len ) {
e->e_name.bv_val[rdn.bv_len] = ',';
e->e_name.bv_len = dn_len;
}
res = close( openres );
rs = LDAP_UNWILLING_TO_PERFORM;
if ( spew_res > -2 ) {
if ( res == -1 || spew_res == -1 ) {
if ( save_errno == 0 ) {
save_errno = errno;
}
Debug( LDAP_DEBUG_ANY, "write error to tmpfile \"%s\": %s\n",
tmpfname, STRERROR( save_errno ), 0 );
if ( close( fd ) < 0 && res >= 0 ) {
res = -1;
save_errno = errno;
}
if ( res >= 0 ) {
if ( move_file( tmpfname, path->bv_val ) == 0 ) {
Debug( LDAP_DEBUG_TRACE, "ldif_write_entry: "
"wrote entry \"%s\"\n", e->e_name.bv_val, 0, 0 );
rc = LDAP_SUCCESS;
} else {
#ifdef _WIN32
/* returns 0 on failure, nonzero on success */
res = MoveFileEx( tmpfname, path->bv_val,
MOVEFILE_REPLACE_EXISTING ) == 0;
#else
res = rename( tmpfname, path->bv_val );
#endif
if ( res == 0 ) {
rs = LDAP_SUCCESS;
} else {
save_errno = errno;
switch ( save_errno ) {
case ENOENT:
rs = LDAP_NO_SUCH_OBJECT;
break;
default:
break;
}
}
save_errno = errno;
Debug( LDAP_DEBUG_ANY, "ldif_write_entry: "
"could not put entry file for \"%s\" in place: %s\n",
e->e_name.bv_val, STRERROR( save_errno ), 0 );
*text = "internal error (could not put entry file in place)";
}
} else if ( res == -1 ) {
Debug( LDAP_DEBUG_ANY, "ldif_write_entry: %s \"%s\": %s\n",
"write error to", tmpfname, STRERROR( save_errno ) );
*text = "internal error (write error to entry file)";
}
if ( rs != LDAP_SUCCESS ) {
if ( rc != LDAP_SUCCESS ) {
unlink( tmpfname );
}
}
ch_free( tmpfname );
if ( rs != LDAP_SUCCESS && save_errnop != NULL ) {
*save_errnop = save_errno;
}
return rs;
if ( tmpfname )
SLAP_FREE( tmpfname );
return rc;
}
static Entry * get_entry_for_fd(int fd,
/*
* Read the entry at path, or if entryp==NULL just see if it exists.
* pdn and pndn are the parent's DN and normalized DN, or both NULL.
* Return an LDAP result code.
*/
static int
ldif_read_entry(
Operation *op,
const char *path,
struct berval *pdn,
struct berval *pndn)
struct berval *pndn,
Entry **entryp,
const char **text )
{
char * entry = (char *) slurp_file(fd);
Entry * ldentry = NULL;
/* error reading file */
if(entry == NULL) {
goto return_value;
}
int rc;
Entry *entry;
char *entry_as_string;
struct berval rdn;
ldentry = str2entry(entry);
if ( ldentry ) {
struct berval rdn;
rdn = ldentry->e_name;
build_new_dn( &ldentry->e_name, pdn, &rdn, NULL );
ch_free( rdn.bv_val );
rdn = ldentry->e_nname;
build_new_dn( &ldentry->e_nname, pndn, &rdn, NULL );
ch_free( rdn.bv_val );
}
/* TODO: Does slapd prevent Abandon of Bind as per rfc4511?
* If so we need not check for LDAP_REQ_BIND here.
*/
if ( op->o_abandon && op->o_tag != LDAP_REQ_BIND )
return SLAPD_ABANDON;
return_value:
if(fd != -1) {
if(close(fd) != 0) {
/* log error */
rc = ldif_read_file( path, entryp ? &entry_as_string : NULL );
switch ( rc ) {
case LDAP_SUCCESS:
if ( entryp == NULL )
break;
*entryp = entry = str2entry( entry_as_string );
SLAP_FREE( entry_as_string );
if ( entry == NULL ) {
rc = LDAP_OTHER;
if ( text != NULL )
*text = "internal error (cannot parse some entry file)";
break;
}
if ( pdn == NULL || BER_BVISEMPTY( pdn ) )
break;
/* Append parent DN to DN from LDIF file */
rdn = entry->e_name;
build_new_dn( &entry->e_name, pdn, &rdn, NULL );
SLAP_FREE( rdn.bv_val );
rdn = entry->e_nname;
build_new_dn( &entry->e_nname, pndn, &rdn, NULL );
SLAP_FREE( rdn.bv_val );
break;
case LDAP_OTHER:
if ( text != NULL )
*text = entryp
? "internal error (cannot read some entry file)"
: "internal error (cannot stat some entry file)";
break;
}
if(entry != NULL)
SLAP_FREE(entry);
return ldentry;
return rc;
}
/*
* Read the operation's entry, or if entryp==NULL just see if it exists.
* Return an LDAP result code. May set *text to a message on failure.
* If pathp is non-NULL, set it to the entry filename on success.
*/
static int
get_entry(
Operation *op,
Entry **entryp,
struct berval *pathp )
struct berval *pathp,
const char **text )
{
int rc;
struct berval path, pdn, pndn;
int fd;
dnParent(&op->o_req_dn, &pdn);
dnParent(&op->o_req_ndn, &pndn);
dnParent( &op->o_req_dn, &pdn );
dnParent( &op->o_req_ndn, &pndn );
dn2path( op->o_bd, &op->o_req_ndn, &path );
fd = open(path.bv_val, O_RDONLY);
/* error opening file (mebbe should log error) */
if ( fd == -1 && ( errno != ENOENT || op->o_tag != LDAP_REQ_ADD ) ) {
Debug( LDAP_DEBUG_ANY, "failed to open file \"%s\": %s\n",
path.bv_val, STRERROR(errno), 0 );
}
*entryp = fd < 0 ? NULL : get_entry_for_fd( fd, &pdn, &pndn );
rc = *entryp ? LDAP_SUCCESS : LDAP_NO_SUCH_OBJECT;
rc = ldif_read_entry( op, path.bv_val, &pdn, &pndn, entryp, text );
if ( rc == LDAP_SUCCESS && pathp != NULL ) {
*pathp = path;
} else {
SLAP_FREE(path.bv_val);
SLAP_FREE( path.bv_val );
}
return rc;
}
static void fullpath(struct berval *base, struct berval *name, struct berval *res) {
char *ptr;
res->bv_len = name->bv_len + base->bv_len + 1;
res->bv_val = ch_malloc( res->bv_len + 1 );
strcpy(res->bv_val, base->bv_val);
ptr = res->bv_val + base->bv_len;
*ptr++ = LDAP_DIRSEP[0];
strcpy(ptr, name->bv_val);
}
/*
* RDN-named directory entry, with special handling of "attr={num}val" RDNs.
* For sorting, filename "attr=val.ldif" is truncated to "attr="val\0ldif",
* and filename "attr={num}val.ldif" to "attr={\0um}val.ldif".
* Does not sort escaped chars correctly, would need to un-escape them.
*/
typedef struct bvlist {
struct bvlist *next;
struct berval bv;
struct berval num;
int inum;
int off;
char *trunc; /* filename was truncated here */