Home | History | Annotate | Line # | Download | only in overlays
homedir.c revision 1.3
      1 /*	$NetBSD: homedir.c,v 1.3 2025/09/05 21:16:32 christos Exp $	*/
      2 
      3 /* homedir.c - create/remove user home directories */
      4 /* $OpenLDAP$ */
      5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      6  *
      7  * Copyright 2009-2024 The OpenLDAP Foundation.
      8  * Portions copyright 2009-2010 Symas Corporation.
      9  * All rights reserved.
     10  *
     11  * Redistribution and use in source and binary forms, with or without
     12  * modification, are permitted only as authorized by the OpenLDAP
     13  * Public License.
     14  *
     15  * A copy of this license is available in the file LICENSE in the
     16  * top-level directory of the distribution or, alternatively, at
     17  * <http://www.OpenLDAP.org/license.html>.
     18  */
     19 /* ACKNOWLEDGEMENTS:
     20  * This work was initially developed by Emily Backes at Symas
     21  * Corp. for inclusion in OpenLDAP Software.
     22  */
     23 
     24 #include <sys/cdefs.h>
     25 __RCSID("$NetBSD: homedir.c,v 1.3 2025/09/05 21:16:32 christos Exp $");
     26 
     27 #include "portable.h"
     28 
     29 #ifdef SLAPD_OVER_HOMEDIR
     30 
     31 #define _FILE_OFFSET_BITS 64
     32 
     33 #include <stdio.h>
     34 #include <fcntl.h>
     35 
     36 #include <ac/string.h>
     37 #include <ac/ctype.h>
     38 #include <ac/errno.h>
     39 #include <sys/stat.h>
     40 #include <ac/unistd.h>
     41 #include <ac/dirent.h>
     42 #include <ac/time.h>
     43 
     44 #include "slap.h"
     45 #include "slap-config.h"
     46 
     47 #define DEFAULT_MIN_UID ( 100 )
     48 #define DEFAULT_SKEL ( LDAP_DIRSEP "etc" LDAP_DIRSEP "skel" )
     49 
     50 typedef struct homedir_regexp {
     51 	char *match;
     52 	char *replace;
     53 	regex_t compiled;
     54 	struct homedir_regexp *next;
     55 } homedir_regexp;
     56 
     57 typedef enum {
     58 	DEL_IGNORE,
     59 	DEL_DELETE,
     60 	DEL_ARCHIVE
     61 } delete_style;
     62 
     63 typedef struct homedir_data {
     64 	char *skeleton_path;
     65 	unsigned min_uid;
     66 	AttributeDescription *home_ad;
     67 	AttributeDescription *uidn_ad;
     68 	AttributeDescription *gidn_ad;
     69 	homedir_regexp *regexps;
     70 	delete_style style;
     71 	char *archive_path;
     72 } homedir_data;
     73 
     74 typedef struct homedir_cb_data {
     75 	slap_overinst *on;
     76 	Entry *entry;
     77 } homedir_cb_data;
     78 
     79 typedef struct name_list {
     80 	char *name;
     81 	struct stat st;
     82 	struct name_list *next;
     83 } name_list;
     84 
     85 typedef struct name_list_list {
     86 	name_list *list;
     87 	struct name_list_list *next;
     88 } name_list_list;
     89 
     90 typedef enum {
     91 	TRAVERSE_CB_CONTINUE,
     92 	TRAVERSE_CB_DONE,
     93 	TRAVERSE_CB_FAIL
     94 } traverse_cb_ret;
     95 
     96 /* private, file info, context */
     97 typedef traverse_cb_ret (*traverse_cb_func)(
     98 		void *,
     99 		const char *,
    100 		const struct stat *,
    101 		void * );
    102 typedef struct traverse_cb {
    103 	traverse_cb_func pre_func;
    104 	traverse_cb_func post_func;
    105 	void *pre_private;
    106 	void *post_private;
    107 } traverse_cb;
    108 
    109 typedef struct copy_private {
    110 	int source_prefix_len;
    111 	const char *dest_prefix;
    112 	int dest_prefix_len;
    113 	uid_t uidn;
    114 	gid_t gidn;
    115 } copy_private;
    116 
    117 typedef struct chown_private {
    118 	uid_t old_uidn;
    119 	uid_t new_uidn;
    120 	gid_t old_gidn;
    121 	gid_t new_gidn;
    122 } chown_private;
    123 
    124 typedef struct ustar_header {
    125 	char name[100];
    126 	char mode[8];
    127 	char uid[8];
    128 	char gid[8];
    129 	char size[12];
    130 	char mtime[12];
    131 	char checksum[8];
    132 	char typeflag[1];
    133 	char linkname[100];
    134 	char magic[6];
    135 	char version[2];
    136 	char uname[32];
    137 	char gname[32];
    138 	char devmajor[8];
    139 	char devminor[8];
    140 	char prefix[155];
    141 	char pad[12];
    142 } ustar_header;
    143 
    144 typedef struct tar_private {
    145 	FILE *file;
    146 	const char *name;
    147 } tar_private;
    148 
    149 /* FIXME: This mutex really needs to be executable-global, but this
    150  * will have to do for now.
    151  */
    152 static ldap_pvt_thread_mutex_t readdir_mutex;
    153 static ConfigDriver homedir_regexp_cfg;
    154 static ConfigDriver homedir_style_cfg;
    155 static slap_overinst homedir;
    156 
    157 static ConfigTable homedircfg[] = {
    158 	{ "homedir-skeleton-path", "pathname", 2, 2, 0,
    159 		ARG_STRING|ARG_OFFSET,
    160 		(void *)offsetof(homedir_data, skeleton_path),
    161 		"( OLcfgCtAt:8.1 "
    162 			"NAME 'olcSkeletonPath' "
    163 			"DESC 'Pathname for home directory skeleton template' "
    164 			"SYNTAX OMsDirectoryString "
    165 			"SINGLE-VALUE )",
    166 		NULL, { .v_string = DEFAULT_SKEL }
    167 	},
    168 
    169 	{ "homedir-min-uidnumber", "uid number", 2, 2, 0,
    170 		ARG_UINT|ARG_OFFSET,
    171 		(void *)offsetof(homedir_data, min_uid),
    172 		"( OLcfgCtAt:8.2 "
    173 			"NAME 'olcMinimumUidNumber' "
    174 			"DESC 'Minimum uidNumber attribute to consider' "
    175 			"SYNTAX OMsInteger "
    176 			"SINGLE-VALUE )",
    177 		NULL, { .v_uint = DEFAULT_MIN_UID }
    178 	},
    179 
    180 	{ "homedir-regexp", "regexp> <path", 3, 3, 0,
    181 		ARG_MAGIC,
    182 		homedir_regexp_cfg,
    183 		"( OLcfgCtAt:8.3 "
    184 			"NAME 'olcHomedirRegexp' "
    185 			"DESC 'Regular expression for matching and transforming paths' "
    186 			"SYNTAX OMsDirectoryString "
    187 			"X-ORDERED 'VALUES' )",
    188 		NULL, NULL
    189 	},
    190 
    191 	{ "homedir-delete-style", "style", 2, 2, 0,
    192 		ARG_MAGIC,
    193 		homedir_style_cfg,
    194 		"( OLcfgCtAt:8.4 "
    195 			"NAME 'olcHomedirDeleteStyle' "
    196 			"DESC 'Action to perform when removing a home directory' "
    197 			"SYNTAX OMsDirectoryString "
    198 			"SINGLE-VALUE )",
    199 		NULL, NULL
    200 	},
    201 
    202 	{ "homedir-archive-path", "pathname", 2, 2, 0,
    203 		ARG_STRING|ARG_OFFSET,
    204 		(void *)offsetof(homedir_data, archive_path),
    205 		"( OLcfgCtAt:8.5 "
    206 			"NAME 'olcHomedirArchivePath' "
    207 			"DESC 'Pathname for home directory archival' "
    208 			"SYNTAX OMsDirectoryString "
    209 			"SINGLE-VALUE )",
    210 		NULL, NULL
    211 	},
    212 
    213 	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
    214 };
    215 
    216 static ConfigOCs homedirocs[] = {
    217 	{ "( OLcfgCtOc:8.1 "
    218 		"NAME 'olcHomedirConfig' "
    219 			"DESC 'Homedir configuration' "
    220 			"SUP olcOverlayConfig "
    221 			"MAY ( olcSkeletonPath $ olcMinimumUidNumber "
    222 				"$ olcHomedirRegexp $ olcHomedirDeleteStyle "
    223 				"$ olcHomedirArchivePath ) )",
    224 		Cft_Overlay, homedircfg },
    225 
    226 	{ NULL, 0, NULL }
    227 };
    228 
    229 static int
    230 homedir_regexp_cfg( ConfigArgs *c )
    231 {
    232 	slap_overinst *on = (slap_overinst *)c->bi;
    233 	homedir_data *data = (homedir_data *)on->on_bi.bi_private;
    234 	int rc = ARG_BAD_CONF;
    235 
    236 	assert( data != NULL );
    237 
    238 	switch ( c->op ) {
    239 		case SLAP_CONFIG_EMIT: {
    240 			int i;
    241 			homedir_regexp *r;
    242 			struct berval bv;
    243 			char buf[4096];
    244 
    245 			bv.bv_val = buf;
    246 			for ( i = 0, r = data->regexps; r != NULL; ++i, r = r->next ) {
    247 				bv.bv_len = snprintf( buf, sizeof(buf), "{%d}%s %s", i,
    248 						r->match, r->replace );
    249 				if ( bv.bv_len >= sizeof(buf) ) {
    250 					Debug( LDAP_DEBUG_ANY, "homedir_regexp_cfg: "
    251 							"emit serialization failed: size %lu\n",
    252 							(unsigned long)bv.bv_len );
    253 					return ARG_BAD_CONF;
    254 				}
    255 				value_add_one( &c->rvalue_vals, &bv );
    256 			}
    257 			rc = 0;
    258 		} break;
    259 
    260 		case LDAP_MOD_DELETE:
    261 			if ( c->valx < 0 ) { /* delete all values */
    262 				homedir_regexp *r, *rnext;
    263 
    264 				for ( r = data->regexps; r != NULL; r = rnext ) {
    265 					rnext = r->next;
    266 					ch_free( r->match );
    267 					ch_free( r->replace );
    268 					regfree( &r->compiled );
    269 					ch_free( r );
    270 				}
    271 				data->regexps = NULL;
    272 				rc = 0;
    273 
    274 			} else { /* delete value by index*/
    275 				homedir_regexp **rp, *r;
    276 				int i;
    277 
    278 				for ( i = 0, rp = &data->regexps; i < c->valx;
    279 						++i, rp = &(*rp)->next )
    280 					;
    281 
    282 				r = *rp;
    283 				*rp = r->next;
    284 				ch_free( r->match );
    285 				ch_free( r->replace );
    286 				regfree( &r->compiled );
    287 				ch_free( r );
    288 
    289 				rc = 0;
    290 			}
    291 			break;
    292 
    293 		case LDAP_MOD_ADD:		/* fallthrough */
    294 		case SLAP_CONFIG_ADD: { /* add values */
    295 			char *match = c->argv[1];
    296 			char *replace = c->argv[2];
    297 			regex_t compiled;
    298 			homedir_regexp **rp, *r;
    299 
    300 			memset( &compiled, 0, sizeof(compiled) );
    301 			rc = regcomp( &compiled, match, REG_EXTENDED );
    302 			if ( rc ) {
    303 				regerror( rc, &compiled, c->cr_msg, sizeof(c->cr_msg) );
    304 				regfree( &compiled );
    305 				return ARG_BAD_CONF;
    306 			}
    307 
    308 			r = ch_calloc( 1, sizeof(homedir_regexp) );
    309 			r->match = strdup( match );
    310 			r->replace = strdup( replace );
    311 			r->compiled = compiled;
    312 
    313 			if ( c->valx == -1 ) { /* append */
    314 				for ( rp = &data->regexps; ( *rp ) != NULL;
    315 						rp = &(*rp)->next )
    316 					;
    317 				*rp = r;
    318 
    319 			} else { /* insert at valx */
    320 				int i;
    321 				for ( i = 0, rp = &data->regexps; i < c->valx;
    322 						rp = &(*rp)->next, ++i )
    323 					;
    324 				r->next = *rp;
    325 				*rp = r;
    326 			}
    327 			rc = 0;
    328 			break;
    329 		}
    330 		default:
    331 			abort();
    332 	}
    333 
    334 	return rc;
    335 }
    336 
    337 static int
    338 homedir_style_cfg( ConfigArgs *c )
    339 {
    340 	slap_overinst *on = (slap_overinst *)c->bi;
    341 	homedir_data *data = (homedir_data *)on->on_bi.bi_private;
    342 	int rc = ARG_BAD_CONF;
    343 	struct berval bv;
    344 
    345 	assert( data != NULL );
    346 
    347 	switch ( c->op ) {
    348 		case SLAP_CONFIG_EMIT:
    349 			bv.bv_val = data->style == DEL_IGNORE ? "IGNORE" :
    350 					data->style == DEL_DELETE	  ? "DELETE" :
    351 													"ARCHIVE";
    352 			bv.bv_len = strlen( bv.bv_val );
    353 			rc = value_add_one( &c->rvalue_vals, &bv );
    354 			if ( rc != 0 ) return ARG_BAD_CONF;
    355 			break;
    356 
    357 		case LDAP_MOD_DELETE:
    358 			data->style = DEL_IGNORE;
    359 			rc = 0;
    360 			break;
    361 
    362 		case LDAP_MOD_ADD:	  /* fallthrough */
    363 		case SLAP_CONFIG_ADD: /* add values */
    364 			if ( strcasecmp( c->argv[1], "IGNORE" ) == 0 )
    365 				data->style = DEL_IGNORE;
    366 			else if ( strcasecmp( c->argv[1], "DELETE" ) == 0 )
    367 				data->style = DEL_DELETE;
    368 			else if ( strcasecmp( c->argv[1], "ARCHIVE" ) == 0 )
    369 				data->style = DEL_ARCHIVE;
    370 			else {
    371 				Debug( LDAP_DEBUG_ANY, "homedir_style_cfg: "
    372 						"unrecognized style keyword\n" );
    373 				return ARG_BAD_CONF;
    374 			}
    375 			rc = 0;
    376 			break;
    377 
    378 		default:
    379 			abort();
    380 	}
    381 
    382 	return rc;
    383 }
    384 
    385 #define HOMEDIR_NULLWRAP(x) ( ( x ) == NULL ? "unknown" : (x) )
    386 static void
    387 report_errno( const char *parent_func, const char *func, const char *filename )
    388 {
    389 	int save_errno = errno;
    390 	char ebuf[1024];
    391 
    392 	Debug( LDAP_DEBUG_ANY, "homedir: "
    393 			"%s: %s: \"%s\": %d (%s)\n",
    394 			HOMEDIR_NULLWRAP(parent_func), HOMEDIR_NULLWRAP(func),
    395 			HOMEDIR_NULLWRAP(filename), save_errno,
    396 			AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
    397 }
    398 
    399 static int
    400 copy_link(
    401 		const char *dest_file,
    402 		const char *source_file,
    403 		const struct stat *st,
    404 		uid_t uidn,
    405 		gid_t gidn,
    406 		void *ctx )
    407 {
    408 	char *buf = NULL;
    409 	int rc;
    410 
    411 	assert( dest_file != NULL );
    412 	assert( source_file != NULL );
    413 	assert( st != NULL );
    414 	assert( (st->st_mode & S_IFMT) == S_IFLNK );
    415 
    416 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    417 			"copy_link: %s to %s\n",
    418 			source_file, dest_file );
    419 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    420 			"copy_link: %s uid %ld gid %ld\n",
    421 			dest_file, (long)uidn, (long)gidn );
    422 
    423 	/* calloc +1 for terminator */
    424 	buf = ber_memcalloc_x( 1, st->st_size + 1, ctx );
    425 	if ( buf == NULL ) {
    426 		Debug( LDAP_DEBUG_ANY, "homedir: "
    427 				"copy_link: alloc failed\n" );
    428 		return 1;
    429 	}
    430 	rc = readlink( source_file, buf, st->st_size );
    431 	if ( rc == -1 ) {
    432 		report_errno( "copy_link", "readlink", source_file );
    433 		goto fail;
    434 	}
    435 	rc = symlink( buf, dest_file );
    436 	if ( rc ) {
    437 		report_errno( "copy_link", "symlink", dest_file );
    438 		goto fail;
    439 	}
    440 	rc = lchown( dest_file, uidn, gidn );
    441 	if ( rc ) {
    442 		report_errno( "copy_link", "lchown", dest_file );
    443 		goto fail;
    444 	}
    445 	goto out;
    446 
    447 fail:
    448 	rc = 1;
    449 
    450 out:
    451 	if ( buf != NULL ) ber_memfree_x( buf, ctx );
    452 	return rc;
    453 }
    454 
    455 static int
    456 copy_blocks(
    457 		FILE *source,
    458 		FILE *dest,
    459 		const char *source_file,
    460 		const char *dest_file )
    461 {
    462 	char buf[4096];
    463 	size_t nread = 0;
    464 	int done = 0;
    465 
    466 	while ( !done ) {
    467 		nread = fread( buf, 1, sizeof(buf), source );
    468 		if ( nread == 0 ) {
    469 			if ( feof( source ) ) {
    470 				done = 1;
    471 			} else if ( ferror( source ) ) {
    472 				if ( source_file != NULL )
    473 					Debug( LDAP_DEBUG_ANY, "homedir: "
    474 							"read error on %s\n",
    475 							source_file );
    476 				goto fail;
    477 			}
    478 		} else {
    479 			size_t nwritten = 0;
    480 			nwritten = fwrite( buf, 1, nread, dest );
    481 			if ( nwritten < nread ) {
    482 				if ( dest_file != NULL )
    483 					Debug( LDAP_DEBUG_ANY, "homedir: "
    484 							"write error on %s\n",
    485 							dest_file );
    486 				goto fail;
    487 			}
    488 		}
    489 	}
    490 	return 0;
    491 fail:
    492 	return 1;
    493 }
    494 
    495 static int
    496 copy_file(
    497 		const char *dest_file,
    498 		const char *source_file,
    499 		uid_t uid,
    500 		gid_t gid,
    501 		int mode )
    502 {
    503 	FILE *source = NULL;
    504 	FILE *dest = NULL;
    505 	int rc;
    506 
    507 	assert( dest_file != NULL );
    508 	assert( source_file != NULL );
    509 
    510 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    511 			"copy_file: %s to %s mode 0%o\n",
    512 			source_file, dest_file, mode );
    513 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    514 			"copy_file: %s uid %ld gid %ld\n",
    515 			dest_file, (long)uid, (long)gid );
    516 
    517 	source = fopen( source_file, "rb" );
    518 	if ( source == NULL ) {
    519 		report_errno( "copy_file", "fopen", source_file );
    520 		goto fail;
    521 	}
    522 	dest = fopen( dest_file, "wb" );
    523 	if ( dest == NULL ) {
    524 		report_errno( "copy_file", "fopen", dest_file );
    525 		goto fail;
    526 	}
    527 
    528 	rc = copy_blocks( source, dest, source_file, dest_file );
    529 	if ( rc != 0 ) goto fail;
    530 
    531 	fclose( source );
    532 	source = NULL;
    533 	rc = fclose( dest );
    534 	dest = NULL;
    535 	if ( rc != 0 ) {
    536 		report_errno( "copy_file", "fclose", dest_file );
    537 		goto fail;
    538 	}
    539 
    540 	/* set owner/permission */
    541 	rc = lchown( dest_file, uid, gid );
    542 	if ( rc != 0 ) {
    543 		report_errno( "copy_file", "lchown", dest_file );
    544 		goto fail;
    545 	}
    546 	rc = chmod( dest_file, mode );
    547 	if ( rc != 0 ) {
    548 		report_errno( "copy_file", "chmod", dest_file );
    549 		goto fail;
    550 	}
    551 
    552 	rc = 0;
    553 	goto out;
    554 fail:
    555 	rc = 1;
    556 out:
    557 	if ( source != NULL ) fclose( source );
    558 	if ( dest != NULL ) fclose( dest );
    559 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    560 			"copy_file: %s to %s exit %d\n",
    561 			source_file, dest_file, rc );
    562 	return rc;
    563 }
    564 
    565 static void
    566 free_name_list( name_list *names, void *ctx )
    567 {
    568 	name_list *next;
    569 
    570 	while ( names != NULL ) {
    571 		next = names->next;
    572 		if ( names->name != NULL ) ber_memfree_x( names->name, ctx );
    573 		ber_memfree_x( names, ctx );
    574 		names = next;
    575 	}
    576 }
    577 
    578 static int
    579 grab_names( const char *dir_path, name_list **names, void *ctx )
    580 {
    581 	int locked = 0;
    582 	DIR *dir = NULL;
    583 	struct dirent *entry = NULL;
    584 	name_list **tail = NULL;
    585 	int dir_path_len = 0;
    586 	int rc = 0;
    587 
    588 	assert( dir_path != NULL );
    589 	assert( names != NULL );
    590 	assert( *names == NULL );
    591 
    592 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    593 			"grab_names: %s\n", dir_path );
    594 
    595 	tail = names;
    596 	dir_path_len = strlen( dir_path );
    597 	ldap_pvt_thread_mutex_lock( &readdir_mutex );
    598 	locked = 1;
    599 
    600 	dir = opendir( dir_path );
    601 	if ( dir == NULL ) {
    602 		report_errno( "grab_names", "opendir", dir_path );
    603 		goto fail;
    604 	}
    605 
    606 	while ( ( entry = readdir( dir ) ) != NULL ) {
    607 		/* no d_namelen in ac/dirent.h */
    608 		int d_namelen = strlen( entry->d_name );
    609 		int full_len;
    610 
    611 		/* Skip . and .. */
    612 		if ( ( d_namelen == 1 && entry->d_name[0] == '.' ) ||
    613 				( d_namelen == 2 && entry->d_name[0] == '.' &&
    614 						entry->d_name[1] == '.' ) ) {
    615 			continue;
    616 		}
    617 
    618 		*tail = ber_memcalloc_x( 1, sizeof(**tail), ctx );
    619 		if ( *tail == NULL ) {
    620 			Debug( LDAP_DEBUG_ANY, "homedir: "
    621 					"grab_names: list alloc failed\n" );
    622 			goto fail;
    623 		}
    624 		(*tail)->next = NULL;
    625 
    626 		/* +1 for dirsep, +1 for term */
    627 		full_len = dir_path_len + 1 + d_namelen + 1;
    628 		(*tail)->name = ber_memalloc_x( full_len, ctx );
    629 		if ( (*tail)->name == NULL ) {
    630 			Debug( LDAP_DEBUG_ANY, "homedir: "
    631 					"grab_names: name alloc failed\n" );
    632 			goto fail;
    633 		}
    634 		snprintf( (*tail)->name, full_len, "%s" LDAP_DIRSEP "%s",
    635 				dir_path, entry->d_name );
    636 		Debug( LDAP_DEBUG_TRACE, "homedir: "
    637 				"grab_names: found \"%s\"\n",
    638 				(*tail)->name );
    639 
    640 		rc = lstat( (*tail)->name, &(*tail)->st );
    641 		if ( rc ) {
    642 			report_errno( "grab_names", "lstat", (*tail)->name );
    643 			goto fail;
    644 		}
    645 
    646 		tail = &(*tail)->next;
    647 	}
    648 	closedir( dir );
    649 	ldap_pvt_thread_mutex_unlock( &readdir_mutex );
    650 	locked = 0;
    651 
    652 	dir = NULL;
    653 	goto success;
    654 
    655 success:
    656 	rc = 0;
    657 	goto out;
    658 fail:
    659 	rc = 1;
    660 	goto out;
    661 out:
    662 	if ( dir != NULL ) closedir( dir );
    663 	if ( locked ) ldap_pvt_thread_mutex_unlock( &readdir_mutex );
    664 	if ( rc != 0 && *names != NULL ) {
    665 		free_name_list( *names, ctx );
    666 		*names = NULL;
    667 	}
    668 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    669 			"grab_names: %s exit %d\n",
    670 			dir_path, rc );
    671 	return rc;
    672 }
    673 
    674 static int
    675 traverse( const char *path, const traverse_cb *cb, void *ctx )
    676 {
    677 	name_list *next_name = NULL;
    678 	name_list_list *dir_stack = NULL;
    679 	name_list_list *next_dir;
    680 	int rc = 0;
    681 
    682 	assert( path != NULL );
    683 	assert( cb != NULL );
    684 	assert( cb->pre_func || cb->post_func );
    685 
    686 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    687 			"traverse: %s\n", path );
    688 
    689 	dir_stack = ber_memcalloc_x( 1, sizeof(*dir_stack), ctx );
    690 	if ( dir_stack == NULL ) goto alloc_fail;
    691 	dir_stack->next = NULL;
    692 	dir_stack->list = ber_memcalloc_x( 1, sizeof(name_list), ctx );
    693 	if ( dir_stack->list == NULL ) goto alloc_fail;
    694 	rc = lstat( path, &dir_stack->list->st );
    695 	if ( rc != 0 ) {
    696 		report_errno( "traverse", "lstat", path );
    697 		goto fail;
    698 	}
    699 	dir_stack->list->next = NULL;
    700 	dir_stack->list->name = ber_strdup_x( path, ctx );
    701 	if ( dir_stack->list->name == NULL ) goto alloc_fail;
    702 
    703 	while ( dir_stack != NULL ) {
    704 		while ( dir_stack->list != NULL ) {
    705 			Debug( LDAP_DEBUG_TRACE, "homedir: "
    706 					"traverse: top of loop with \"%s\"\n",
    707 					dir_stack->list->name );
    708 
    709 			if ( cb->pre_func != NULL ) {
    710 				traverse_cb_ret cb_rc;
    711 				cb_rc = cb->pre_func( cb->pre_private, dir_stack->list->name,
    712 						&dir_stack->list->st, ctx );
    713 
    714 				if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
    715 				if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
    716 			}
    717 			if ( (dir_stack->list->st.st_mode & S_IFMT) == S_IFDIR ) {
    718 				/* push dir onto stack */
    719 				next_dir = dir_stack;
    720 				dir_stack = ber_memalloc_x( sizeof(*dir_stack), ctx );
    721 				if ( dir_stack == NULL ) {
    722 					dir_stack = next_dir;
    723 					goto alloc_fail;
    724 				}
    725 				dir_stack->list = NULL;
    726 				dir_stack->next = next_dir;
    727 				rc = grab_names(
    728 						dir_stack->next->list->name, &dir_stack->list, ctx );
    729 				if ( rc != 0 ) {
    730 					Debug( LDAP_DEBUG_ANY, "homedir: "
    731 							"traverse: grab_names %s failed\n",
    732 							dir_stack->next->list->name );
    733 					goto fail;
    734 				}
    735 			} else {
    736 				/* just a file */
    737 				if ( cb->post_func != NULL ) {
    738 					traverse_cb_ret cb_rc;
    739 					cb_rc = cb->post_func( cb->post_private,
    740 							dir_stack->list->name, &dir_stack->list->st, ctx );
    741 
    742 					if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
    743 					if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
    744 				}
    745 				next_name = dir_stack->list->next;
    746 				ber_memfree_x( dir_stack->list->name, ctx );
    747 				ber_memfree_x( dir_stack->list, ctx );
    748 				dir_stack->list = next_name;
    749 			}
    750 		}
    751 		/* Time to pop a directory off the stack */
    752 		next_dir = dir_stack->next;
    753 		ber_memfree_x( dir_stack, ctx );
    754 		dir_stack = next_dir;
    755 		if ( dir_stack != NULL ) {
    756 			if ( cb->post_func != NULL ) {
    757 				traverse_cb_ret cb_rc;
    758 				cb_rc = cb->post_func( cb->post_private, dir_stack->list->name,
    759 						&dir_stack->list->st, ctx );
    760 
    761 				if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
    762 				if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
    763 			}
    764 			next_name = dir_stack->list->next;
    765 			ber_memfree_x( dir_stack->list->name, ctx );
    766 			ber_memfree_x( dir_stack->list, ctx );
    767 			dir_stack->list = next_name;
    768 		}
    769 	}
    770 
    771 	goto success;
    772 
    773 cb_done:
    774 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    775 			"traverse: cb signaled completion\n" );
    776 success:
    777 	rc = 0;
    778 	goto out;
    779 
    780 cb_fail:
    781 	Debug( LDAP_DEBUG_ANY, "homedir: "
    782 			"traverse: cb signaled failure\n" );
    783 	goto fail;
    784 alloc_fail:
    785 	Debug( LDAP_DEBUG_ANY, "homedir: "
    786 			"traverse: allocation failed\n" );
    787 fail:
    788 	rc = 1;
    789 	goto out;
    790 
    791 out:
    792 	while ( dir_stack != NULL ) {
    793 		free_name_list( dir_stack->list, ctx );
    794 		next_dir = dir_stack->next;
    795 		ber_memfree_x( dir_stack, ctx );
    796 		dir_stack = next_dir;
    797 	}
    798 	return rc;
    799 }
    800 
    801 static traverse_cb_ret
    802 traverse_copy_pre(
    803 		void *private,
    804 		const char *name,
    805 		const struct stat *st,
    806 		void *ctx )
    807 {
    808 	copy_private *cp = private;
    809 	char *dest_name = NULL;
    810 	int source_name_len;
    811 	int dest_name_len;
    812 	int rc;
    813 
    814 	assert( private != NULL );
    815 	assert( name != NULL );
    816 	assert( st != NULL );
    817 
    818 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    819 			"traverse_copy_pre: %s entering\n",
    820 			name );
    821 
    822 	assert( cp->source_prefix_len >= 0 );
    823 	assert( cp->dest_prefix != NULL );
    824 	assert( cp->dest_prefix_len > 1 );
    825 
    826 	source_name_len = strlen( name );
    827 	assert( source_name_len >= cp->source_prefix_len );
    828 	/* +1 for terminator */
    829 	dest_name_len =
    830 			source_name_len + cp->dest_prefix_len - cp->source_prefix_len + 1;
    831 	dest_name = ber_memalloc_x( dest_name_len, ctx );
    832 	if ( dest_name == NULL ) goto alloc_fail;
    833 
    834 	snprintf( dest_name, dest_name_len, "%s%s", cp->dest_prefix,
    835 			name + cp->source_prefix_len );
    836 
    837 	switch ( st->st_mode & S_IFMT ) {
    838 		case S_IFDIR:
    839 			rc = mkdir( dest_name, st->st_mode & 06775 );
    840 			if ( rc ) {
    841 				int save_errno = errno;
    842 				switch ( save_errno ) {
    843 					case EEXIST:
    844 						/* directory already present; nothing to do */
    845 						goto exists;
    846 						break;
    847 					case ENOENT:
    848 						/* FIXME: should mkdir -p here */
    849 						/* fallthrough for now */
    850 					default:
    851 						report_errno( "traverse_copy_pre", "mkdir", dest_name );
    852 						goto fail;
    853 				}
    854 			}
    855 			rc = lchown( dest_name, cp->uidn, cp->gidn );
    856 			if ( rc ) {
    857 				report_errno( "traverse_copy_pre", "lchown", dest_name );
    858 				goto fail;
    859 			}
    860 			rc = chmod( dest_name, st->st_mode & 07777 );
    861 			if ( rc ) {
    862 				report_errno( "traverse_copy_pre", "chmod", dest_name );
    863 				goto fail;
    864 			}
    865 			break;
    866 		case S_IFREG:
    867 			rc = copy_file(
    868 					dest_name, name, cp->uidn, cp->gidn, st->st_mode & 07777 );
    869 			if ( rc ) goto fail;
    870 			break;
    871 		case S_IFIFO:
    872 			rc = mkfifo( dest_name, 0700 );
    873 			if ( rc ) {
    874 				report_errno( "traverse_copy_pre", "mkfifo", dest_name );
    875 				goto fail;
    876 			}
    877 			rc = lchown( dest_name, cp->uidn, cp->gidn );
    878 			if ( rc ) {
    879 				report_errno( "traverse_copy_pre", "lchown", dest_name );
    880 				goto fail;
    881 			}
    882 			rc = chmod( dest_name, st->st_mode & 07777 );
    883 			if ( rc ) {
    884 				report_errno( "traverse_copy_pre", "chmod", dest_name );
    885 				goto fail;
    886 			}
    887 			break;
    888 		case S_IFLNK:
    889 			rc = copy_link( dest_name, name, st, cp->uidn, cp->gidn, ctx );
    890 			if ( rc ) goto fail;
    891 			break;
    892 		default:
    893 			Debug( LDAP_DEBUG_TRACE, "homedir: "
    894 					"traverse_copy_pre: skipping special: %s\n",
    895 					name );
    896 	}
    897 
    898 	goto success;
    899 
    900 alloc_fail:
    901 	Debug( LDAP_DEBUG_ANY, "homedir: "
    902 			"traverse_copy_pre: allocation failed\n" );
    903 fail:
    904 	rc = TRAVERSE_CB_FAIL;
    905 	goto out;
    906 
    907 exists:
    908 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    909 			"traverse_copy_pre: \"%s\" already exists,"
    910 			" skipping the rest\n",
    911 			dest_name );
    912 	rc = TRAVERSE_CB_DONE;
    913 	goto out;
    914 
    915 success:
    916 	rc = TRAVERSE_CB_CONTINUE;
    917 out:
    918 	if ( dest_name != NULL ) ber_memfree_x( dest_name, ctx );
    919 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    920 			"traverse_copy_pre: exit %d\n", rc );
    921 	return rc;
    922 }
    923 
    924 static int
    925 copy_tree(
    926 		const char *dest_path,
    927 		const char *source_path,
    928 		uid_t uidn,
    929 		gid_t gidn,
    930 		void *ctx )
    931 {
    932 	traverse_cb cb;
    933 	copy_private cp;
    934 	int rc;
    935 
    936 	assert( dest_path != NULL );
    937 	assert( source_path != NULL );
    938 
    939 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    940 			"copy_tree: %s to %s entering\n",
    941 			source_path, dest_path );
    942 
    943 	cb.pre_func = traverse_copy_pre;
    944 	cb.post_func = NULL;
    945 	cb.pre_private = &cp;
    946 	cb.post_private = NULL;
    947 
    948 	cp.source_prefix_len = strlen( source_path );
    949 	cp.dest_prefix = dest_path;
    950 	cp.dest_prefix_len = strlen( dest_path );
    951 	cp.uidn = uidn;
    952 	cp.gidn = gidn;
    953 
    954 	if ( cp.source_prefix_len <= cp.dest_prefix_len &&
    955 			strncmp( source_path, dest_path, cp.source_prefix_len ) == 0 &&
    956 			( cp.source_prefix_len == cp.dest_prefix_len ||
    957 					dest_path[cp.source_prefix_len] == LDAP_DIRSEP[0] ) ) {
    958 		Debug( LDAP_DEBUG_ANY, "homedir: "
    959 				"copy_tree: aborting: %s contains %s\n",
    960 				source_path, dest_path );
    961 		return 1;
    962 	}
    963 
    964 	rc = traverse( source_path, &cb, ctx );
    965 
    966 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    967 			"copy_tree: %s exit %d\n", source_path,
    968 			rc );
    969 
    970 	return rc;
    971 }
    972 
    973 static int
    974 homedir_provision(
    975 		const char *dest_path,
    976 		const char *skel_path,
    977 		uid_t uidn,
    978 		gid_t gidn,
    979 		void *ctx )
    980 {
    981 	int rc;
    982 
    983 	assert( dest_path != NULL );
    984 
    985 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    986 			"homedir_provision: %s from skeleton %s\n",
    987 			dest_path, skel_path == NULL ? "(none)" : skel_path );
    988 	Debug( LDAP_DEBUG_TRACE, "homedir: "
    989 			"homedir_provision: %s uidn %ld gidn %ld\n",
    990 			dest_path, (long)uidn, (long)gidn );
    991 
    992 	if ( skel_path == NULL ) {
    993 		rc = mkdir( dest_path, 0700 );
    994 		if ( rc ) {
    995 			int save_errno = errno;
    996 			switch ( save_errno ) {
    997 				case EEXIST:
    998 					/* directory already present; nothing to do */
    999 					/* but down chown either */
   1000 					rc = 0;
   1001 					goto out;
   1002 					break;
   1003 				default:
   1004 					report_errno( "provision_homedir", "mkdir", dest_path );
   1005 					goto fail;
   1006 			}
   1007 		}
   1008 		rc = lchown( dest_path, uidn, gidn );
   1009 		if ( rc ) {
   1010 			report_errno( "provision_homedir", "lchown", dest_path );
   1011 			goto fail;
   1012 		}
   1013 
   1014 	} else {
   1015 		rc = copy_tree( dest_path, skel_path, uidn, gidn, ctx );
   1016 	}
   1017 
   1018 	goto out;
   1019 
   1020 fail:
   1021 	rc = 1;
   1022 	goto out;
   1023 out:
   1024 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1025 			"homedir_provision: %s to %s exit %d\n",
   1026 			skel_path, dest_path, rc );
   1027 	return rc;
   1028 }
   1029 
   1030 /* traverse func for rm -rf */
   1031 static traverse_cb_ret
   1032 traverse_remove_post(
   1033 		void *private,
   1034 		const char *name,
   1035 		const struct stat *st,
   1036 		void *ctx )
   1037 {
   1038 	int rc;
   1039 
   1040 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1041 			"traverse_remove_post: %s entering\n",
   1042 			name );
   1043 
   1044 	if ( (st->st_mode & S_IFMT) == S_IFDIR ) {
   1045 		rc = rmdir( name );
   1046 		if ( rc != 0 ) {
   1047 			report_errno( "traverse_remove_post", "rmdir", name );
   1048 			goto fail;
   1049 		}
   1050 	} else {
   1051 		rc = unlink( name );
   1052 		if ( rc != 0 ) {
   1053 			report_errno( "traverse_remove_post", "unlink", name );
   1054 			goto fail;
   1055 		}
   1056 	}
   1057 
   1058 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1059 			"traverse_remove_post: %s exit continue\n",
   1060 			name );
   1061 	return TRAVERSE_CB_CONTINUE;
   1062 
   1063 fail:
   1064 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1065 			"traverse_remove_post: %s exit failure\n",
   1066 			name );
   1067 	return TRAVERSE_CB_FAIL;
   1068 }
   1069 
   1070 static int
   1071 delete_tree( const char *path, void *ctx )
   1072 {
   1073 	const static traverse_cb cb = { NULL, traverse_remove_post, NULL, NULL };
   1074 	int rc;
   1075 
   1076 	assert( path != NULL );
   1077 
   1078 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1079 			"delete_tree: %s entering\n", path );
   1080 
   1081 	rc = traverse( path, &cb, ctx );
   1082 
   1083 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1084 			"delete_tree: %s exit %d\n", path, rc );
   1085 
   1086 	return rc;
   1087 }
   1088 
   1089 static int
   1090 get_tar_name(
   1091 		const char *path,
   1092 		const char *tar_path,
   1093 		char *tar_name,
   1094 		int name_size )
   1095 {
   1096 	int rc = 0;
   1097 	const char *ch;
   1098 	int fd = -1;
   1099 	int counter = 0;
   1100 	time_t now;
   1101 
   1102 	assert( path != NULL );
   1103 	assert( tar_path != NULL );
   1104 	assert( tar_name != NULL );
   1105 
   1106 	for ( ch = path + strlen( path );
   1107 			*ch != LDAP_DIRSEP[0] && ch > path;
   1108 			--ch )
   1109 		;
   1110 	if ( ch <= path || strlen( ch ) < 2 ) {
   1111 		Debug( LDAP_DEBUG_ANY, "homedir: "
   1112 				"get_tar_name: unable to construct a tar name from input "
   1113 				"path \"%s\"\n",
   1114 				path );
   1115 		goto fail;
   1116 	}
   1117 	++ch; /* skip past sep */
   1118 	time( &now );
   1119 
   1120 	while ( fd < 0 ) {
   1121 		snprintf( tar_name, name_size, "%s" LDAP_DIRSEP "%s-%ld-%d.tar",
   1122 				tar_path, ch, (long)now, counter );
   1123 		fd = open( tar_name, O_WRONLY|O_CREAT|O_EXCL, 0600 );
   1124 		if ( fd < 0 ) {
   1125 			int save_errno = errno;
   1126 			if ( save_errno != EEXIST ) {
   1127 				report_errno( "get_tar_name", "open", tar_name );
   1128 				goto fail;
   1129 			}
   1130 			++counter;
   1131 		}
   1132 	}
   1133 
   1134 	rc = 0;
   1135 	goto out;
   1136 
   1137 fail:
   1138 	rc = 1;
   1139 	*tar_name = '\0';
   1140 out:
   1141 	if ( fd >= 0 ) close( fd );
   1142 	return rc;
   1143 }
   1144 
   1145 /* traverse func for rechown */
   1146 static traverse_cb_ret
   1147 traverse_chown_pre(
   1148 		void *private,
   1149 		const char *name,
   1150 		const struct stat *st,
   1151 		void *ctx )
   1152 {
   1153 	int rc;
   1154 	chown_private *cp = private;
   1155 	uid_t set_uidn = -1;
   1156 	gid_t set_gidn = -1;
   1157 
   1158 	assert( private != NULL );
   1159 	assert( name != NULL );
   1160 	assert( st != NULL );
   1161 
   1162 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1163 			"traverse_chown_pre: %s entering\n",
   1164 			name );
   1165 
   1166 	if ( st->st_uid == cp->old_uidn ) set_uidn = cp->new_uidn;
   1167 	if ( st->st_gid == cp->old_gidn ) set_gidn = cp->new_gidn;
   1168 
   1169 	if ( set_uidn != (uid_t)-1 || set_gidn != (gid_t)-1 ) {
   1170 		rc = lchown( name, set_uidn, set_gidn );
   1171 		if ( rc ) {
   1172 			report_errno( "traverse_chown_pre", "lchown", name );
   1173 			goto fail;
   1174 		}
   1175 	}
   1176 
   1177 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1178 			"traverse_chown_pre: %s exit continue\n",
   1179 			name );
   1180 	return TRAVERSE_CB_CONTINUE;
   1181 
   1182 fail:
   1183 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1184 			"traverse_chown_pre: %s exit failure\n",
   1185 			name );
   1186 	return TRAVERSE_CB_FAIL;
   1187 }
   1188 
   1189 static int
   1190 chown_tree(
   1191 		const char *path,
   1192 		uid_t old_uidn,
   1193 		uid_t new_uidn,
   1194 		gid_t old_gidn,
   1195 		gid_t new_gidn,
   1196 		void *ctx )
   1197 {
   1198 	traverse_cb cb;
   1199 	chown_private cp;
   1200 	int rc;
   1201 
   1202 	assert( path != NULL );
   1203 
   1204 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1205 			"chown_tree: %s entering\n", path );
   1206 
   1207 	cb.pre_func = traverse_chown_pre;
   1208 	cb.post_func = NULL;
   1209 	cb.pre_private = &cp;
   1210 	cb.post_private = NULL;
   1211 
   1212 	cp.old_uidn = old_uidn;
   1213 	cp.new_uidn = new_uidn;
   1214 	cp.old_gidn = old_gidn;
   1215 	cp.new_gidn = new_gidn;
   1216 
   1217 	rc = traverse( path, &cb, ctx );
   1218 
   1219 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1220 			"chown_tree: %s exit %d\n", path, rc );
   1221 
   1222 	return rc;
   1223 }
   1224 
   1225 static int
   1226 homedir_rename( const char *source_path, const char *dest_path )
   1227 {
   1228 	int rc = 0;
   1229 
   1230 	assert( source_path != NULL );
   1231 	assert( dest_path != NULL );
   1232 
   1233 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1234 			"homedir_rename: %s to %s\n",
   1235 			source_path, dest_path );
   1236 	rc = rename( source_path, dest_path );
   1237 	if ( rc != 0 ) {
   1238 		char ebuf[1024];
   1239 		int save_errno = errno;
   1240 
   1241 		Debug( LDAP_DEBUG_ANY, "homedir: "
   1242 				"homedir_rename: rename(\"%s\", \"%s\"): (%s)\n",
   1243 				source_path, dest_path,
   1244 				AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
   1245 	}
   1246 
   1247 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1248 			"homedir_rename: %s to %s exit %d\n",
   1249 			source_path, dest_path, rc );
   1250 	return rc;
   1251 }
   1252 
   1253 /* FIXME: This assumes ASCII; needs fixing for z/OS */
   1254 static int
   1255 tar_set_header( ustar_header *tar, const struct stat *st, const char *name )
   1256 {
   1257 	int name_len;
   1258 	int rc;
   1259 	const char *ch, *end;
   1260 
   1261 	assert( tar != NULL );
   1262 	assert( st != NULL );
   1263 	assert( name != NULL );
   1264 	assert( sizeof(*tar) == 512 );
   1265 	assert( sizeof(tar->name) == 100 );
   1266 	assert( sizeof(tar->prefix) == 155 );
   1267 	assert( sizeof(tar->checksum) == 8 );
   1268 
   1269 	memset( tar, 0, sizeof(*tar) );
   1270 
   1271 	assert( name[0] == LDAP_DIRSEP[0] );
   1272 	name += 1; /* skip leading / */
   1273 
   1274 	name_len = strlen( name );
   1275 
   1276 	/* fits in tar->name? */
   1277 	/* Yes, name and prefix do not need a trailing nul. */
   1278 	if ( name_len <= 100 ) {
   1279 		strncpy( tar->name, name, 100 );
   1280 
   1281 		/* try fit in tar->name + tar->prefix */
   1282 	} else {
   1283 		/* try to find something to stick into tar->name */
   1284 		for ( ch = name + name_len - 100, end = name + name_len;
   1285 				ch < end && *ch != LDAP_DIRSEP[0];
   1286 				++ch )
   1287 			;
   1288 		if ( end - ch > 0 ) /* +1 skip past sep */
   1289 			ch++;
   1290 		else {
   1291 			/* reset; name too long for UStar */
   1292 			Debug( LDAP_DEBUG_ANY, "homedir: "
   1293 					"tar_set_header: name too long: \"%s\"\n",
   1294 					name );
   1295 			ch = name + name_len - 100;
   1296 		}
   1297 		strncpy( tar->name, ch + 1, 100 );
   1298 		{
   1299 			int prefix_len = ( ch - 1 ) - name;
   1300 			if ( prefix_len > 155 ) prefix_len = 155;
   1301 			strncpy( tar->prefix, name, prefix_len );
   1302 		}
   1303 	}
   1304 
   1305 	snprintf( tar->mode, 8, "%06lo ", (long)st->st_mode & 07777 );
   1306 	snprintf( tar->uid, 8, "%06lo ", (long)st->st_uid );
   1307 	snprintf( tar->gid, 8, "%06lo ", (long)st->st_gid );
   1308 	snprintf( tar->mtime, 12, "%010lo ", (long)st->st_mtime );
   1309 	snprintf( tar->size, 12, "%010lo ", (long)0 );
   1310 	switch ( st->st_mode & S_IFMT ) {
   1311 		case S_IFREG:
   1312 			tar->typeflag[0] = '0';
   1313 			snprintf( tar->size, 12, "%010lo ", (long)st->st_size );
   1314 			break;
   1315 		case S_IFLNK:
   1316 			tar->typeflag[0] = '2';
   1317 			rc = readlink( name - 1, tar->linkname, 99 );
   1318 			if ( rc == -1 ) {
   1319 				report_errno( "tar_set_header", "readlink", name );
   1320 				goto fail;
   1321 			}
   1322 			break;
   1323 		case S_IFCHR:
   1324 			tar->typeflag[0] = '3';
   1325 			/* FIXME: this is probably wrong but shouldn't likely be an issue */
   1326 			snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 );
   1327 			snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff );
   1328 			break;
   1329 		case S_IFBLK:
   1330 			tar->typeflag[0] = '4';
   1331 			/* FIXME: this is probably wrong but shouldn't likely be an issue */
   1332 			snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 );
   1333 			snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff );
   1334 			break;
   1335 		case S_IFDIR:
   1336 			tar->typeflag[0] = '5';
   1337 			break;
   1338 		case S_IFIFO:
   1339 			tar->typeflag[0] = '6';
   1340 			break;
   1341 		default:
   1342 			goto fail;
   1343 	}
   1344 	snprintf( tar->magic, 6, "ustar" );
   1345 	tar->version[0] = '0';
   1346 	tar->version[1] = '0';
   1347 
   1348 	{
   1349 		unsigned char *uch = (unsigned char *)tar;
   1350 		unsigned char *uend = uch + 512;
   1351 		unsigned long sum = 0;
   1352 
   1353 		memset( &tar->checksum, ' ', sizeof(tar->checksum) );
   1354 
   1355 		for ( ; uch < uend; ++uch )
   1356 			sum += *uch;
   1357 
   1358 		/* zero-padded, six octal digits, followed by NUL then space (!) */
   1359 		/* Yes, that's terminated exactly reverse of the others. */
   1360 		snprintf( tar->checksum, sizeof(tar->checksum) - 1, "%06lo", sum );
   1361 	}
   1362 
   1363 	return 0;
   1364 fail:
   1365 	return 1;
   1366 }
   1367 
   1368 static traverse_cb_ret
   1369 traverse_tar_pre(
   1370 		void *private,
   1371 		const char *name,
   1372 		const struct stat *st,
   1373 		void *ctx )
   1374 {
   1375 	int rc;
   1376 	traverse_cb_ret cbrc;
   1377 	tar_private *tp = private;
   1378 	ustar_header tar;
   1379 	FILE *source = NULL;
   1380 
   1381 	assert( private != NULL );
   1382 	assert( name != NULL );
   1383 	assert( st != NULL );
   1384 
   1385 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1386 			"traverse_tar_pre: %s entering\n", name );
   1387 
   1388 	switch ( st->st_mode & S_IFMT ) {
   1389 		case S_IFREG:
   1390 			if ( sizeof(st->st_size) > 4 && ( st->st_size >> 33 ) >= 1 ) {
   1391 				Debug( LDAP_DEBUG_TRACE, "homedir: "
   1392 						"traverse_tar_pre: %s is larger than 8GiB POSIX UStar "
   1393 						"file size limit\n",
   1394 						name );
   1395 				goto fail;
   1396 			}
   1397 			/* fallthrough */
   1398 		case S_IFDIR:
   1399 		case S_IFLNK:
   1400 		case S_IFIFO:
   1401 		case S_IFCHR:
   1402 		case S_IFBLK:
   1403 			rc = tar_set_header( &tar, st, name );
   1404 			if ( rc ) goto fail;
   1405 			break;
   1406 		default:
   1407 			Debug( LDAP_DEBUG_TRACE, "homedir: "
   1408 					"traverse_tar_pre: skipping \"%s\" mode %o\n",
   1409 					name, st->st_mode );
   1410 			goto done;
   1411 	}
   1412 
   1413 	rc = fwrite( &tar, 1, 512, tp->file );
   1414 	if ( rc != 512 ) {
   1415 		Debug( LDAP_DEBUG_TRACE, "homedir: "
   1416 				"traverse_tar_pre: write error in tar header\n" );
   1417 		goto fail;
   1418 	}
   1419 
   1420 	if ( (st->st_mode & S_IFMT) == S_IFREG ) {
   1421 		source = fopen( name, "rb" );
   1422 		if ( source == NULL ) {
   1423 			report_errno( "traverse_tar_pre", "fopen", name );
   1424 			goto fail;
   1425 		}
   1426 		rc = copy_blocks( source, tp->file, name, tp->name );
   1427 		if ( rc != 0 ) goto fail;
   1428 		fclose( source );
   1429 		source = NULL;
   1430 	}
   1431 
   1432 	{ /* advance to end of record */
   1433 		off_t pos = ftello( tp->file );
   1434 		if ( pos == -1 ) {
   1435 			report_errno( "traverse_tar_pre", "ftello", tp->name );
   1436 			goto fail;
   1437 		}
   1438 		pos += ( 512 - ( pos % 512 ) ) % 512;
   1439 		rc = fseeko( tp->file, pos, SEEK_SET );
   1440 		if ( rc != 0 ) {
   1441 			report_errno( "traverse_tar_pre", "fseeko", tp->name );
   1442 			goto fail;
   1443 		}
   1444 	}
   1445 
   1446 done:
   1447 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1448 			"traverse_tar_pre: %s exit continue\n",
   1449 			name );
   1450 	cbrc = TRAVERSE_CB_CONTINUE;
   1451 	goto out;
   1452 fail:
   1453 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1454 			"traverse_tar_pre: %s exit failure\n",
   1455 			name );
   1456 	cbrc = TRAVERSE_CB_FAIL;
   1457 
   1458 out:
   1459 	if ( source != NULL ) fclose( source );
   1460 	return cbrc;
   1461 }
   1462 
   1463 static int
   1464 tar_tree( const char *path, const char *tar_name, void *ctx )
   1465 {
   1466 	traverse_cb cb;
   1467 	tar_private tp;
   1468 	int rc;
   1469 
   1470 	assert( path != NULL );
   1471 	assert( tar_name != NULL );
   1472 
   1473 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1474 			"tar_tree: %s into %s entering\n", path,
   1475 			tar_name );
   1476 
   1477 	cb.pre_func = traverse_tar_pre;
   1478 	cb.post_func = NULL;
   1479 	cb.pre_private = &tp;
   1480 	cb.post_private = NULL;
   1481 
   1482 	tp.name = tar_name;
   1483 	tp.file = fopen( tar_name, "wb" );
   1484 	if ( tp.file == NULL ) {
   1485 		report_errno( "tar_tree", "fopen", tar_name );
   1486 		goto fail;
   1487 	}
   1488 
   1489 	rc = traverse( path, &cb, ctx );
   1490 	if ( rc != 0 ) goto fail;
   1491 
   1492 	{
   1493 		off_t pos = ftello( tp.file );
   1494 		if ( pos == -1 ) {
   1495 			report_errno( "tar_tree", "ftello", tp.name );
   1496 			goto fail;
   1497 		}
   1498 		pos += 1024; /* two zero records */
   1499 		pos += ( 10240 - ( pos % 10240 ) ) % 10240;
   1500 		rc = ftruncate( fileno( tp.file ), pos );
   1501 		if ( rc != 0 ) {
   1502 			report_errno( "tar_tree", "ftrunctate", tp.name );
   1503 			goto fail;
   1504 		}
   1505 	}
   1506 
   1507 	rc = fclose( tp.file );
   1508 	tp.file = NULL;
   1509 	if ( rc != 0 ) {
   1510 		report_errno( "tar_tree", "fclose", tp.name );
   1511 		goto fail;
   1512 	}
   1513 	goto out;
   1514 
   1515 fail:
   1516 	rc = 1;
   1517 out:
   1518 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1519 			"tar_tree: %s exit %d\n", path, rc );
   1520 	if ( tp.file != NULL ) fclose( tp.file );
   1521 	return rc;
   1522 }
   1523 
   1524 static int
   1525 homedir_deprovision( const homedir_data *data, const char *path, void *ctx )
   1526 {
   1527 	int rc = 0;
   1528 	char tar_name[1024];
   1529 
   1530 	assert( data != NULL );
   1531 	assert( path != NULL );
   1532 
   1533 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1534 			"homedir_deprovision: %s entering\n",
   1535 			path );
   1536 
   1537 	switch ( data->style ) {
   1538 		case DEL_IGNORE:
   1539 			Debug( LDAP_DEBUG_TRACE, "homedir: "
   1540 					"homedir_deprovision: style is ignore\n" );
   1541 			break;
   1542 		case DEL_ARCHIVE:
   1543 			if ( data->archive_path == NULL ) {
   1544 				Debug( LDAP_DEBUG_ANY, "homedir: "
   1545 						"homedir_deprovision: archive path not set\n" );
   1546 				goto fail;
   1547 			}
   1548 			rc = get_tar_name( path, data->archive_path, tar_name, 1024 );
   1549 			if ( rc != 0 ) goto fail;
   1550 			rc = tar_tree( path, tar_name, ctx );
   1551 			if ( rc != 0 ) {
   1552 				Debug( LDAP_DEBUG_ANY, "homedir: "
   1553 						"homedir_deprovision: archive failed, not deleting\n" );
   1554 				goto fail;
   1555 			}
   1556 			/* fall-through */
   1557 		case DEL_DELETE:
   1558 			rc = delete_tree( path, ctx );
   1559 			break;
   1560 		default:
   1561 			abort();
   1562 	}
   1563 
   1564 	rc = 0;
   1565 	goto out;
   1566 
   1567 fail:
   1568 	rc = 1;
   1569 out:
   1570 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1571 			"homedir_deprovision: %s leaving\n",
   1572 			path );
   1573 
   1574 	return rc;
   1575 }
   1576 
   1577 /* FIXME: This assumes ASCII; needs fixing for z/OS */
   1578 /* FIXME: This should also be in a slapd library function somewhere */
   1579 #define MAX_MATCHES ( 10 )
   1580 static int
   1581 homedir_match(
   1582 		const homedir_regexp *r,
   1583 		const char *homedir,
   1584 		char *result,
   1585 		size_t result_size )
   1586 {
   1587 	int rc;
   1588 	int n;
   1589 	regmatch_t matches[MAX_MATCHES];
   1590 	char *resc, *repc;
   1591 
   1592 	assert( r != NULL );
   1593 	assert( homedir != NULL );
   1594 	assert( result_size > 1 );
   1595 
   1596 	memset( matches, 0, sizeof(matches) );
   1597 	rc = regexec( &r->compiled, homedir, MAX_MATCHES, matches, 0 );
   1598 	if ( rc ) {
   1599 		if ( rc != REG_NOMATCH ) {
   1600 			char msg[256];
   1601 			regerror( rc, &r->compiled, msg, sizeof(msg) );
   1602 			Debug( LDAP_DEBUG_ANY, "homedir_match: "
   1603 					"%s\n", msg );
   1604 		}
   1605 		return rc;
   1606 	}
   1607 
   1608 	for ( resc = result, repc = r->replace;
   1609 			result_size > 1 && *repc != '\0';
   1610 			++repc, ++resc, --result_size ) {
   1611 		switch ( *repc ) {
   1612 			case '$':
   1613 				++repc;
   1614 				n = ( *repc ) - '0';
   1615 				if ( n < 0 || n > ( MAX_MATCHES - 1 ) ||
   1616 						matches[n].rm_so < 0 ) {
   1617 					Debug( LDAP_DEBUG_ANY, "homedir: "
   1618 							"invalid regex term expansion in \"%s\" "
   1619 							"at char %ld, n is %d\n",
   1620 							r->replace, (long)( repc - r->replace ), n );
   1621 					return 1;
   1622 				}
   1623 				{
   1624 					size_t match_len = matches[n].rm_eo - matches[n].rm_so;
   1625 					const char *match_start = homedir + matches[n].rm_so;
   1626 					if ( match_len >= result_size ) goto too_long;
   1627 
   1628 					memcpy( resc, match_start, match_len );
   1629 					result_size -= match_len;
   1630 					resc += match_len - 1;
   1631 				}
   1632 				break;
   1633 
   1634 			case '\\':
   1635 				++repc;
   1636 				/* fallthrough */
   1637 
   1638 			default:
   1639 				*resc = *repc;
   1640 		}
   1641 	}
   1642 	*resc = '\0';
   1643 	if ( *repc != '\0' ) goto too_long;
   1644 
   1645 	return 0;
   1646 
   1647 too_long:
   1648 	Debug( LDAP_DEBUG_ANY, "homedir: "
   1649 			"regex expansion of %s too long\n",
   1650 			r->replace );
   1651 	*result = '\0';
   1652 	return 1;
   1653 }
   1654 
   1655 /* Sift through an entry for interesting values
   1656  * return 0 on success and set vars
   1657  * return 1 if homedir is not present or not valid
   1658  * sets presence if any homedir attributes are noticed
   1659  */
   1660 static int
   1661 harvest_values(
   1662 		const homedir_data *data,
   1663 		const Entry *e,
   1664 		char *home_buf,
   1665 		int home_buf_size,
   1666 		uid_t *uidn,
   1667 		gid_t *gidn,
   1668 		int *presence )
   1669 {
   1670 	Attribute *a;
   1671 	char *homedir = NULL;
   1672 
   1673 	assert( data != NULL );
   1674 	assert( e != NULL );
   1675 	assert( home_buf != NULL );
   1676 	assert( home_buf_size > 1 );
   1677 	assert( uidn != NULL );
   1678 	assert( gidn != NULL );
   1679 	assert( presence != NULL );
   1680 
   1681 	*presence = 0;
   1682 	if ( e == NULL ) return 1;
   1683 	*uidn = 0;
   1684 	*gidn = 0;
   1685 
   1686 	for ( a = e->e_attrs; a->a_next != NULL; a = a->a_next ) {
   1687 		if ( a->a_desc == data->home_ad ) {
   1688 			homedir = a->a_vals[0].bv_val;
   1689 			*presence = 1;
   1690 		} else if ( a->a_desc == data->uidn_ad ) {
   1691 			*uidn = (uid_t)strtol( a->a_vals[0].bv_val, NULL, 10 );
   1692 			*presence = 1;
   1693 		} else if ( a->a_desc == data->gidn_ad ) {
   1694 			*gidn = (gid_t)strtol( a->a_vals[0].bv_val, NULL, 10 );
   1695 			*presence = 1;
   1696 		}
   1697 	}
   1698 	if ( homedir != NULL ) {
   1699 		homedir_regexp *r;
   1700 
   1701 		for ( r = data->regexps; r != NULL; r = r->next ) {
   1702 			int rc = homedir_match( r, homedir, home_buf, home_buf_size );
   1703 			if ( rc == 0 ) return 0;
   1704 		}
   1705 	}
   1706 
   1707 	return 1;
   1708 }
   1709 
   1710 static int
   1711 homedir_mod_cleanup( Operation *op, SlapReply *rs )
   1712 {
   1713 	slap_callback *cb = NULL;
   1714 	slap_callback **cbp = NULL;
   1715 	homedir_cb_data *cb_data = NULL;
   1716 	Entry *e = NULL;
   1717 
   1718 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1719 			"homedir_mod_cleanup: entering\n" );
   1720 
   1721 	for ( cbp = &op->o_callback;
   1722 			*cbp != NULL && (*cbp)->sc_cleanup != homedir_mod_cleanup;
   1723 			cbp = &(*cbp)->sc_next )
   1724 		;
   1725 
   1726 	if ( *cbp == NULL ) goto out;
   1727 	cb = *cbp;
   1728 
   1729 	cb_data = (homedir_cb_data *)cb->sc_private;
   1730 	e = cb_data->entry;
   1731 
   1732 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1733 			"homedir_mod_cleanup: found <%s>\n",
   1734 			e->e_nname.bv_val );
   1735 	entry_free( e );
   1736 	op->o_tmpfree( cb_data, op->o_tmpmemctx );
   1737 	*cbp = cb->sc_next;
   1738 	op->o_tmpfree( cb, op->o_tmpmemctx );
   1739 
   1740 out:
   1741 
   1742 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1743 			"homedir_mod_cleanup: leaving\n" );
   1744 	return SLAP_CB_CONTINUE;
   1745 }
   1746 
   1747 static int
   1748 homedir_mod_response( Operation *op, SlapReply *rs )
   1749 {
   1750 	slap_overinst *on = NULL;
   1751 	homedir_data *data = NULL;
   1752 	slap_callback *cb = NULL;
   1753 	homedir_cb_data *cb_data = NULL;
   1754 	Entry *e = NULL;
   1755 	int rc = SLAP_CB_CONTINUE;
   1756 
   1757 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1758 			"homedir_mod_response: entering\n" );
   1759 
   1760 	if ( rs->sr_err != LDAP_SUCCESS ) {
   1761 		Debug( LDAP_DEBUG_TRACE, "homedir: "
   1762 				"homedir_mod_response: op was not successful\n" );
   1763 		goto out;
   1764 	}
   1765 
   1766 	/* Retrieve stashed entry */
   1767 	for ( cb = op->o_callback;
   1768 			cb != NULL && cb->sc_cleanup != homedir_mod_cleanup;
   1769 			cb = cb->sc_next )
   1770 		;
   1771 	if ( cb == NULL ) goto out;
   1772 	cb_data = (homedir_cb_data *)cb->sc_private;
   1773 	e = cb_data->entry;
   1774 	on = cb_data->on;
   1775 	data = on->on_bi.bi_private;
   1776 	assert( e != NULL );
   1777 	assert( data != NULL );
   1778 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1779 			"homedir_mod_response: found <%s>\n",
   1780 			e->e_nname.bv_val );
   1781 
   1782 	switch ( op->o_tag ) {
   1783 		case LDAP_REQ_DELETE: {
   1784 			char home_buf[1024];
   1785 			uid_t uidn = 0;
   1786 			gid_t gidn = 0;
   1787 			int presence;
   1788 
   1789 			Debug( LDAP_DEBUG_TRACE, "homedir: "
   1790 					"homedir_mod_response: successful delete found\n" );
   1791 			rc = harvest_values( data, e, home_buf, sizeof(home_buf), &uidn,
   1792 					&gidn, &presence );
   1793 			if ( rc == 0 && uidn >= data->min_uid ) {
   1794 				homedir_deprovision( data, home_buf, op->o_tmpmemctx );
   1795 			} else {
   1796 				Debug( LDAP_DEBUG_TRACE, "homedir: "
   1797 						"homedir_mod_response: skipping\n" );
   1798 			}
   1799 			rc = SLAP_CB_CONTINUE;
   1800 			break;
   1801 		}
   1802 
   1803 		case LDAP_REQ_MODIFY:
   1804 		case LDAP_REQ_MODRDN: {
   1805 			Operation nop = *op;
   1806 			Entry *old_entry = e;
   1807 			Entry *new_entry = NULL;
   1808 			Entry *etmp;
   1809 			char old_home[1024];
   1810 			char new_home[1024];
   1811 			uid_t old_uidn, new_uidn;
   1812 			uid_t old_gidn, new_gidn;
   1813 			int old_valid = 0;
   1814 			int new_valid = 0;
   1815 			int old_presence, new_presence;
   1816 
   1817 			Debug( LDAP_DEBUG_TRACE, "homedir: "
   1818 					"homedir_mod_response: successful modify/modrdn found\n" );
   1819 
   1820 			/* retrieve the revised entry */
   1821 			nop.o_bd = on->on_info->oi_origdb;
   1822 			rc = overlay_entry_get_ov(
   1823 					&nop, &op->o_req_ndn, NULL, NULL, 0, &etmp, on );
   1824 			if ( etmp != NULL ) {
   1825 				new_entry = entry_dup( etmp );
   1826 				overlay_entry_release_ov( &nop, etmp, 0, on );
   1827 			}
   1828 			if ( rc || new_entry == NULL ) {
   1829 				Debug( LDAP_DEBUG_ANY, "homedir: "
   1830 						"homedir_mod_response: unable to get revised <%s>\n",
   1831 						op->o_req_ndn.bv_val );
   1832 				if ( new_entry != NULL ) {
   1833 					entry_free( new_entry );
   1834 					new_entry = NULL;
   1835 				}
   1836 			}
   1837 
   1838 			/* analyze old and new */
   1839 			rc = harvest_values( data, old_entry, old_home, 1024, &old_uidn,
   1840 					&old_gidn, &old_presence );
   1841 			if ( rc == 0 && old_uidn >= data->min_uid ) old_valid = 1;
   1842 			if ( new_entry != NULL ) {
   1843 				rc = harvest_values( data, new_entry, new_home, 1024, &new_uidn,
   1844 						&new_gidn, &new_presence );
   1845 				if ( rc == 0 && new_uidn >= data->min_uid ) new_valid = 1;
   1846 				entry_free( new_entry );
   1847 				new_entry = NULL;
   1848 			}
   1849 
   1850 			if ( new_valid && !old_valid ) { /* like an add */
   1851 				if ( old_presence )
   1852 					Debug( LDAP_DEBUG_TRACE, "homedir: "
   1853 							"homedir_mod_response: old entry is now valid\n" );
   1854 				Debug( LDAP_DEBUG_TRACE, "homedir: "
   1855 						"homedir_mod_response: treating like an add\n" );
   1856 				homedir_provision( new_home, data->skeleton_path, new_uidn,
   1857 						new_gidn, op->o_tmpmemctx );
   1858 
   1859 			} else if ( old_valid && !new_valid &&
   1860 					!new_presence ) { /* like a del */
   1861 				Debug( LDAP_DEBUG_TRACE, "homedir: "
   1862 						"homedir_mod_response: treating like a del\n" );
   1863 				homedir_deprovision( data, old_home, op->o_tmpmemctx );
   1864 
   1865 			} else if ( new_valid && old_valid ) { /* change */
   1866 				int did_something = 0;
   1867 
   1868 				if ( strcmp( old_home, new_home ) != 0 ) {
   1869 					Debug( LDAP_DEBUG_TRACE, "homedir: "
   1870 							"homedir_mod_response: treating like a rename\n" );
   1871 					homedir_rename( old_home, new_home );
   1872 					did_something = 1;
   1873 				}
   1874 				if ( old_uidn != new_uidn || old_gidn != new_gidn ) {
   1875 					Debug( LDAP_DEBUG_ANY, "homedir: "
   1876 							"homedir_mod_response: rechowning\n" );
   1877 					chown_tree( new_home, old_uidn, new_uidn, old_gidn,
   1878 							new_gidn, op->o_tmpmemctx );
   1879 					did_something = 1;
   1880 				}
   1881 				if ( !did_something ) {
   1882 					Debug( LDAP_DEBUG_TRACE, "homedir: "
   1883 							"homedir_mod_response: nothing to do\n" );
   1884 				}
   1885 			} else if ( old_presence || new_presence ) {
   1886 				Debug( LDAP_DEBUG_ANY, "homedir: "
   1887 						"homedir_mod_response: <%s> values present "
   1888 						"but invalid; ignoring\n",
   1889 						op->o_req_ndn.bv_val );
   1890 			}
   1891 			rc = SLAP_CB_CONTINUE;
   1892 			break;
   1893 		}
   1894 
   1895 		default:
   1896 			rc = SLAP_CB_CONTINUE;
   1897 	}
   1898 
   1899 out:
   1900 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1901 			"homedir_mod_response: leaving\n" );
   1902 	return rc;
   1903 }
   1904 
   1905 static int
   1906 homedir_op_mod( Operation *op, SlapReply *rs )
   1907 {
   1908 	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
   1909 	slap_callback *cb = NULL;
   1910 	homedir_cb_data *cb_data = NULL;
   1911 	Entry *e = NULL;
   1912 	Entry *se = NULL;
   1913 	Operation nop = *op;
   1914 	int rc;
   1915 
   1916 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1917 			"homedir_op_mod: entering\n" );
   1918 
   1919 	/* retrieve the entry */
   1920 	nop.o_bd = on->on_info->oi_origdb;
   1921 	rc = overlay_entry_get_ov( &nop, &op->o_req_ndn, NULL, NULL, 0, &e, on );
   1922 	if ( e != NULL ) {
   1923 		se = entry_dup( e );
   1924 		overlay_entry_release_ov( &nop, e, 0, on );
   1925 		e = se;
   1926 	}
   1927 	if ( rc || e == NULL ) {
   1928 		Debug( LDAP_DEBUG_ANY, "homedir: "
   1929 				"homedir_op_mod: unable to get <%s>\n",
   1930 				op->o_req_ndn.bv_val );
   1931 		goto out;
   1932 	}
   1933 
   1934 	/* Allocate the callback to hold the entry */
   1935 	cb = op->o_tmpalloc( sizeof(slap_callback), op->o_tmpmemctx );
   1936 	cb_data = op->o_tmpalloc( sizeof(homedir_cb_data), op->o_tmpmemctx );
   1937 	cb->sc_cleanup = homedir_mod_cleanup;
   1938 	cb->sc_response = homedir_mod_response;
   1939 	cb->sc_private = cb_data;
   1940 	cb_data->entry = e;
   1941 	e = NULL;
   1942 	cb_data->on = on;
   1943 	cb->sc_next = op->o_callback;
   1944 	op->o_callback = cb;
   1945 
   1946 out:
   1947 	if ( e != NULL ) entry_free( e );
   1948 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1949 			"homedir_op_mod: leaving\n" );
   1950 	return SLAP_CB_CONTINUE;
   1951 }
   1952 
   1953 static int
   1954 homedir_response( Operation *op, SlapReply *rs )
   1955 {
   1956 	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
   1957 	homedir_data *data = on->on_bi.bi_private;
   1958 
   1959 	Debug( LDAP_DEBUG_TRACE, "homedir: "
   1960 			"homedir_response: entering\n" );
   1961 	if ( rs->sr_err != LDAP_SUCCESS || data == NULL ) return SLAP_CB_CONTINUE;
   1962 
   1963 	switch ( op->o_tag ) {
   1964 		case LDAP_REQ_ADD: { /* Check for new homedir */
   1965 			char home_buf[1024];
   1966 			uid_t uidn = 0;
   1967 			gid_t gidn = 0;
   1968 			int rc, presence;
   1969 
   1970 			rc = harvest_values( data, op->ora_e, home_buf, sizeof(home_buf),
   1971 					&uidn, &gidn, &presence );
   1972 			if ( rc == 0 && uidn >= data->min_uid ) {
   1973 				homedir_provision( home_buf, data->skeleton_path, uidn, gidn,
   1974 						op->o_tmpmemctx );
   1975 			}
   1976 			return SLAP_CB_CONTINUE;
   1977 		}
   1978 
   1979 		default:
   1980 			return SLAP_CB_CONTINUE;
   1981 	}
   1982 
   1983 	return SLAP_CB_CONTINUE;
   1984 }
   1985 
   1986 static int
   1987 homedir_db_init( BackendDB *be, ConfigReply *cr )
   1988 {
   1989 	slap_overinst *on = (slap_overinst *)be->bd_info;
   1990 	homedir_data *data = ch_calloc( 1, sizeof(homedir_data) );
   1991 	const char *text;
   1992 
   1993 	if ( slap_str2ad( "homeDirectory", &data->home_ad, &text ) ||
   1994 			slap_str2ad( "uidNumber", &data->uidn_ad, &text ) ||
   1995 			slap_str2ad( "gidNumber", &data->gidn_ad, &text ) ) {
   1996 		Debug( LDAP_DEBUG_ANY, "homedir: "
   1997 				"nis schema not available\n" );
   1998 		return 1;
   1999 	}
   2000 
   2001 	data->skeleton_path = strdup( DEFAULT_SKEL );
   2002 	data->min_uid = DEFAULT_MIN_UID;
   2003 	data->archive_path = NULL;
   2004 
   2005 	on->on_bi.bi_private = data;
   2006 	return 0;
   2007 }
   2008 
   2009 static int
   2010 homedir_db_destroy( BackendDB *be, ConfigReply *cr )
   2011 {
   2012 	slap_overinst *on = (slap_overinst *)be->bd_info;
   2013 	homedir_data *data = on->on_bi.bi_private;
   2014 	homedir_regexp *r, *rnext;
   2015 
   2016 	if ( data != NULL ) {
   2017 		for ( r = data->regexps; r != NULL; r = rnext ) {
   2018 			rnext = r->next;
   2019 			ch_free( r->match );
   2020 			ch_free( r->replace );
   2021 			regfree( &r->compiled );
   2022 			ch_free( r );
   2023 		}
   2024 		data->regexps = NULL;
   2025 		if ( data->skeleton_path != NULL ) ch_free( data->skeleton_path );
   2026 		if ( data->archive_path != NULL ) ch_free( data->archive_path );
   2027 		ch_free( data );
   2028 	}
   2029 
   2030 	return 0;
   2031 }
   2032 
   2033 int
   2034 homedir_initialize()
   2035 {
   2036 	int rc;
   2037 
   2038 	assert( ' ' == 32 ); /* Lots of ASCII requirements for now */
   2039 
   2040 	memset( &homedir, 0, sizeof(homedir) );
   2041 
   2042 	homedir.on_bi.bi_type = "homedir";
   2043 	homedir.on_bi.bi_db_init = homedir_db_init;
   2044 	homedir.on_bi.bi_db_destroy = homedir_db_destroy;
   2045 	homedir.on_bi.bi_op_delete = homedir_op_mod;
   2046 	homedir.on_bi.bi_op_modify = homedir_op_mod;
   2047 	homedir.on_response = homedir_response;
   2048 
   2049 	homedir.on_bi.bi_cf_ocs = homedirocs;
   2050 	rc = config_register_schema( homedircfg, homedirocs );
   2051 	if ( rc ) return rc;
   2052 
   2053 	ldap_pvt_thread_mutex_init( &readdir_mutex );
   2054 
   2055 	return overlay_register( &homedir );
   2056 }
   2057 
   2058 int
   2059 homedir_terminate()
   2060 {
   2061 	ldap_pvt_thread_mutex_destroy( &readdir_mutex );
   2062 	return 0;
   2063 }
   2064 
   2065 #if SLAPD_OVER_HOMEDIR == SLAPD_MOD_DYNAMIC && defined(PIC)
   2066 int
   2067 init_module( int argc, char *argv[] )
   2068 {
   2069 	return homedir_initialize();
   2070 }
   2071 
   2072 int
   2073 term_module()
   2074 {
   2075 	return homedir_terminate();
   2076 }
   2077 #endif
   2078 
   2079 #endif /* SLAPD_OVER_HOMEDIR */
   2080