Commit 8382d3c3 authored by Ondřej Kuzník's avatar Ondřej Kuzník Committed by Quanah Gibson-Mount
Browse files

ITS#9470 Add homedir overlay

parent 61e9b6d3
......@@ -349,6 +349,7 @@ Overlays="accesslog \
deref \
dyngroup \
dynlist \
homedir \
memberof \
ppolicy \
proxycache \
......@@ -388,6 +389,8 @@ OL_ARG_ENABLE(dyngroup, [AS_HELP_STRING([--enable-dyngroup], [Dynamic Group over
no, [no yes mod], ol_enable_overlays)
OL_ARG_ENABLE(dynlist, [AS_HELP_STRING([--enable-dynlist], [Dynamic List overlay])],
no, [no yes mod], ol_enable_overlays)
OL_ARG_ENABLE(homedir, [AS_HELP_STRING([--enable-homedir], [Home Directory Management overlay])],
no, [no yes mod], ol_enable_overlays)
OL_ARG_ENABLE(memberof, [AS_HELP_STRING([--enable-memberof], [Reverse Group Membership overlay])],
no, [no yes mod], ol_enable_overlays)
OL_ARG_ENABLE(ppolicy, [AS_HELP_STRING([--enable-ppolicy], [Password Policy overlay])],
......@@ -587,6 +590,7 @@ BUILD_DEREF=no
BUILD_DYNGROUP=no
BUILD_DYNLIST=no
BUILD_LASTMOD=no
BUILD_HOMEDIR=no
BUILD_MEMBEROF=no
BUILD_PPOLICY=no
BUILD_PROXYCACHE=no
......@@ -2838,6 +2842,18 @@ if test "$ol_enable_dynlist" != no ; then
AC_DEFINE_UNQUOTED(SLAPD_OVER_DYNLIST,$MFLAG,[define for Dynamic List overlay])
fi
if test "$ol_enable_homedir" != no ; then
BUILD_HOMEDIR=$ol_enable_homedir
if test "$ol_enable_homedir" = mod ; then
MFLAG=SLAPD_MOD_DYNAMIC
SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS homedir.la"
else
MFLAG=SLAPD_MOD_STATIC
SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS homedir.o"
fi
AC_DEFINE_UNQUOTED(SLAPD_OVER_HOMEDIR,$MFLAG,[define for Home Directory Management overlay])
fi
if test "$ol_enable_memberof" != no ; then
BUILD_MEMBEROF=$ol_enable_memberof
if test "$ol_enable_memberof" = mod ; then
......@@ -3110,6 +3126,7 @@ dnl overlays
AC_SUBST(BUILD_DYNGROUP)
AC_SUBST(BUILD_DYNLIST)
AC_SUBST(BUILD_LASTMOD)
AC_SUBST(BUILD_HOMEDIR)
AC_SUBST(BUILD_MEMBEROF)
AC_SUBST(BUILD_PPOLICY)
AC_SUBST(BUILD_PROXYCACHE)
......
.TH SLAPO-HOMEDIR 5 "RELEASEDATE" "OpenLDAP LDVERSION"
.\" Copyright 1998-2021 The OpenLDAP Foundation, All Rights Reserved.
.\" Copying restrictions apply. See the COPYRIGHT file.
.\" $OpenLDAP$
.SH NAME
slapo\-homedir \- Home directory provisioning overlay
.SH SYNOPSIS
ETCDIR/slapd.conf
.SH DESCRIPTION
The
.B homedir
overlay causes
.BR slapd (8)
to notice changes involving RFC-2307bis style user-objects and make
appropriate changes to the local filesystem. This can be performed
on both master and replica systems, so it is possible to perform
remote home directory provisioning.
.SH CONFIGURATION
Both slapd.conf and back-config style configuration is supported.
.TP
.B overlay homedir
This directive adds the homedir overlay to the current database,
or to the frontend, if used before any database instantiation; see
.BR slapd.conf (5)
for details.
.TP
.B homedir\-skeleton\-path <pathname>
.TP
.B olcSkeletonPath: pathname
These options set the path to the skeleton account directory.
(Generally, /etc/skel) Files in this directory will be copied into
newly created home directories. Copying is recursive and handles
symlinks and fifos, but will skip most specials.
.TP
.B homedir\-min\-uidnumber <user id number>
.TP
.B olcMinimumUidNumber: number
These options configure the minimum userid to use in any home
directory attempt. This is a basic safety measure to prevent
accidently using system accounts. See REPLICATION for more flexible
options for selecting accounts.
.TP
.B homedir\-regexp <regexp> <path>
.TP
.B olcHomedirRegexp: regexp path
These options configure a set of regular expressions to use for
matching and optionally remapping incoming
.B homeDirectory
attribute values to pathnames on the local filesystem. $number
expansion is supported to access values captured in parentheses.
For example, to accept any directory starting with \/home and use it
verbatim on the local filesystem:
.B homedir-regexp ^(/home/[\-_/a\-z0\-9]+)$ $1
To match the same set of directories, but create them instead under
\/export\/home, as is popular on Solaris NFS servers:
.B homedir-regexp ^(/home/[\-_/a\-z0\-9]+)$ /export$1
.TP
.B homedir\-delete\-style style
.TP
.B olcHomedirDeleteStyle: style
These options configure how deletes of posixAccount entries or their
attributes are handled; valid styles are
.B IGNORE,
which does nothing, and
.B DELETE,
which immediately performs a recursive delete on the home directory,
and
.B ARCHIVE,
which archives the home directory contents in a TAR file for later
examination. The default is IGNORE. Use with caution. ARCHIVE
requires homedir-archive-path to be set, or it functions similar to
IGNORE.
.TP
.B homedir\-archive\-path <pathname>
.TP
.B olcArchivePath: pathname
These options specify the destination path for TAR files created by
the ARCHIVE delete style.
.SH REPLICATION
The homedir overlay can operate on either master or replica systems
with no changes. See
.BR slapd.conf (5)
or
.BR slapd\-config (5)
for more information on configure syncrepl.
Partial replication (e.g. with filters) is especially useful for
providing different provisioning options to different sets of users.
.SH BUGS
DELETE, MOD, and MODRDN operations that remove the unix attributes
when delete style is set to DELETE will recursively delete the (regex
modified) home directory from the disk. Please be careful when
deleting or changing values.
MOD and MODRDN will correctly respond to homeDirectory changes and
perform a non-destructive rename() operation on the filesystem, but
this does not correctly retry with a recursive copy when moving
between filesystems.
The recursive copy/delete/chown/tar functions are not aware of ACLs,
extended attributes, forks, sparse files, or hard links. Block and
character device archival is non-portable, but should not be an issue
in home directories, hopefully.
Copying and archiving may not support files larger than 2GiB on some
architectures. Bare POSIX UStar archives cannot support internal
files larger than 8GiB. The current tar generator does not attempt to
resolve uid/gid into symbolic names.
No attempt is made to try to mkdir() the parent directories needed for
a given home directory or archive path.
.SH FILES
.TP
ETCDIR/slapd.conf
default slapd configuration file
.TP
/etc/skel (or similar)
source of new homedir files.
.SH SEE ALSO
.BR slapd.conf (5),
.BR slapd\-config (5),
.BR slapd (8),
RFC-2307, RFC-2307bis.
.SH ACKNOWLEDGEMENTS
.P
This module was written in 2009 by Emily Backes for Symas Corporation.
......@@ -22,6 +22,7 @@ SRCS = overlays.c \
deref.c \
dyngroup.c \
dynlist.c \
homedir.c \
memberof.c \
pcache.c \
collect.c \
......@@ -88,6 +89,9 @@ dyngroup.la : dyngroup.lo
dynlist.la : dynlist.lo
$(LTLINK_MOD) -module -o $@ dynlist.lo version.lo $(LINK_LIBS)
homedir.la : homedir.lo
$(LTLINK_MOD) -module -o $@ homedir.lo version.lo $(LINK_LIBS)
memberof.la : memberof.lo
$(LTLINK_MOD) -module -o $@ memberof.lo version.lo $(LINK_LIBS)
......
/* homedir.c - create/remove user home directories */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright 2009-2010 The OpenLDAP Foundation.
* Portions copyright 2009-2010 Symas Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* ACKNOWLEDGEMENTS:
* This work was initially developed by Emily Backes at Symas
* Corp. for inclusion in OpenLDAP Software.
*/
#include "portable.h"
#ifdef SLAPD_OVER_HOMEDIR
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <fcntl.h>
#include <ac/string.h>
#include <ac/ctype.h>
#include <ac/errno.h>
#include <sys/stat.h>
#include <ac/unistd.h>
#include <ac/dirent.h>
#include <ac/time.h>
#include "slap.h"
#include "slap-config.h"
#define DEFAULT_MIN_UID ( 100 )
#define DEFAULT_SKEL ( LDAP_DIRSEP "etc" LDAP_DIRSEP "skel" )
typedef struct homedir_regexp {
char *match;
char *replace;
regex_t compiled;
struct homedir_regexp *next;
} homedir_regexp;
typedef enum {
DEL_IGNORE,
DEL_DELETE,
DEL_ARCHIVE
} delete_style;
typedef struct homedir_data {
char *skeleton_path;
unsigned min_uid;
AttributeDescription *home_ad;
AttributeDescription *uidn_ad;
AttributeDescription *gidn_ad;
homedir_regexp *regexps;
delete_style style;
char *archive_path;
} homedir_data;
typedef struct homedir_cb_data {
slap_overinst *on;
Entry *entry;
} homedir_cb_data;
typedef struct name_list {
char *name;
struct stat st;
struct name_list *next;
} name_list;
typedef struct name_list_list {
name_list *list;
struct name_list_list *next;
} name_list_list;
typedef enum {
TRAVERSE_CB_CONTINUE,
TRAVERSE_CB_DONE,
TRAVERSE_CB_FAIL
} traverse_cb_ret;
/* private, file info, context */
typedef traverse_cb_ret (*traverse_cb_func)(
void *,
const char *,
const struct stat *,
void * );
typedef struct traverse_cb {
traverse_cb_func pre_func;
traverse_cb_func post_func;
void *pre_private;
void *post_private;
} traverse_cb;
typedef struct copy_private {
int source_prefix_len;
const char *dest_prefix;
int dest_prefix_len;
uid_t uidn;
gid_t gidn;
} copy_private;
typedef struct chown_private {
uid_t old_uidn;
uid_t new_uidn;
gid_t old_gidn;
gid_t new_gidn;
} chown_private;
typedef struct ustar_header {
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char checksum[8];
char typeflag[1];
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char pad[12];
} ustar_header;
typedef struct tar_private {
FILE *file;
const char *name;
} tar_private;
/* FIXME: This mutex really needs to be executable-global, but this
* will have to do for now.
*/
static ldap_pvt_thread_mutex_t readdir_mutex;
static ConfigDriver homedir_regexp_cfg;
static ConfigDriver homedir_style_cfg;
static slap_overinst homedir;
static ConfigTable homedircfg[] = {
{ "homedir-skeleton-path", "pathname", 2, 2, 0,
ARG_STRING|ARG_OFFSET,
(void *)offsetof(homedir_data, skeleton_path),
"( OLcfgCtAt:8.1 "
"NAME 'olcSkeletonPath' "
"DESC 'Pathname for home directory skeleton template' "
"SYNTAX OMsDirectoryString "
"SINGLE-VALUE )",
NULL, { .v_string = DEFAULT_SKEL }
},
{ "homedir-min-uidnumber", "uid number", 2, 2, 0,
ARG_UINT|ARG_OFFSET,
(void *)offsetof(homedir_data, min_uid),
"( OLcfgCtAt:8.2 "
"NAME 'olcMinimumUidNumber' "
"DESC 'Minimum uidNumber attribute to consider' "
"SYNTAX OMsInteger "
"SINGLE-VALUE )",
NULL, { .v_uint = DEFAULT_MIN_UID }
},
{ "homedir-regexp", "regexp> <path", 3, 3, 0,
ARG_MAGIC,
homedir_regexp_cfg,
"( OLcfgCtAt:8.3 "
"NAME 'olcHomedirRegexp' "
"DESC 'Regular expression for matching and transforming paths' "
"SYNTAX OMsDirectoryString "
"X-ORDERED 'VALUES' )",
NULL, NULL
},
{ "homedir-delete-style", "style", 2, 2, 0,
ARG_MAGIC,
homedir_style_cfg,
"( OLcfgCtAt:8.4 "
"NAME 'olcHomedirDeleteStyle' "
"DESC 'Action to perform when removing a home directory' "
"SYNTAX OMsDirectoryString "
"SINGLE-VALUE )",
NULL, NULL
},
{ "homedir-archive-path", "pathname", 2, 2, 0,
ARG_STRING|ARG_OFFSET,
(void *)offsetof(homedir_data, archive_path),
"( OLcfgCtAt:8.5 "
"NAME 'olcHomedirArchivePath' "
"DESC 'Pathname for home directory archival' "
"SYNTAX OMsDirectoryString "
"SINGLE-VALUE )",
NULL, NULL
},
{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};
static ConfigOCs homedirocs[] = {
{ "( OLcfgCtOc:8.1 "
"NAME 'olcHomedirConfig' "
"DESC 'Homedir configuration' "
"SUP olcOverlayConfig "
"MAY ( olcSkeletonPath $ olcMinimumUidNumber "
"$ olcHomedirRegexp $ olcHomedirDeleteStyle "
"$ olcHomedirArchivePath ) )",
Cft_Overlay, homedircfg },
{ NULL, 0, NULL }
};
static int
homedir_regexp_cfg( ConfigArgs *c )
{
slap_overinst *on = (slap_overinst *)c->bi;
homedir_data *data = (homedir_data *)on->on_bi.bi_private;
int rc = ARG_BAD_CONF;
assert( data != NULL );
switch ( c->op ) {
case SLAP_CONFIG_EMIT: {
int i;
homedir_regexp *r;
struct berval bv;
char buf[4096];
bv.bv_val = buf;
for ( i = 0, r = data->regexps; r != NULL; ++i, r = r->next ) {
bv.bv_len = snprintf( buf, sizeof(buf), "{%d}%s %s", i,
r->match, r->replace );
if ( bv.bv_len >= sizeof(buf) ) {
Debug( LDAP_DEBUG_ANY, "homedir_regexp_cfg: "
"emit serialization failed: size %lu\n",
(unsigned long)bv.bv_len );
return ARG_BAD_CONF;
}
value_add_one( &c->rvalue_vals, &bv );
}
rc = 0;
} break;
case LDAP_MOD_DELETE:
if ( c->valx < 0 ) { /* delete all values */
homedir_regexp *r, *rnext;
for ( r = data->regexps; r != NULL; r = rnext ) {
rnext = r->next;
ch_free( r->match );
ch_free( r->replace );
regfree( &r->compiled );
ch_free( r );
}
data->regexps = NULL;
rc = 0;
} else { /* delete value by index*/
homedir_regexp **rp, *r;
int i;
for ( i = 0, rp = &data->regexps; i < c->valx;
++i, rp = &(*rp)->next )
;
r = *rp;
*rp = r->next;
ch_free( r->match );
ch_free( r->replace );
regfree( &r->compiled );
ch_free( r );
rc = 0;
}
break;
case LDAP_MOD_ADD: /* fallthrough */
case SLAP_CONFIG_ADD: { /* add values */
char *match = c->argv[1];
char *replace = c->argv[2];
regex_t compiled;
homedir_regexp **rp, *r;
memset( &compiled, 0, sizeof(compiled) );
rc = regcomp( &compiled, match, REG_EXTENDED );
if ( rc ) {
regerror( rc, &compiled, c->cr_msg, sizeof(c->cr_msg) );
regfree( &compiled );
return ARG_BAD_CONF;
}
r = ch_calloc( 1, sizeof(homedir_regexp) );
r->match = strdup( match );
r->replace = strdup( replace );
r->compiled = compiled;
if ( c->valx == -1 ) { /* append */
for ( rp = &data->regexps; ( *rp ) != NULL;
rp = &(*rp)->next )
;
*rp = r;
} else { /* insert at valx */
int i;
for ( i = 0, rp = &data->regexps; i < c->valx;
rp = &(*rp)->next, ++i )
;
r->next = *rp;
*rp = r;
}
rc = 0;
break;
}
default:
abort();
}
return rc;
}
static int
homedir_style_cfg( ConfigArgs *c )
{
slap_overinst *on = (slap_overinst *)c->bi;
homedir_data *data = (homedir_data *)on->on_bi.bi_private;
int rc = ARG_BAD_CONF;
struct berval bv;
assert( data != NULL );
switch ( c->op ) {
case SLAP_CONFIG_EMIT:
bv.bv_val = data->style == DEL_IGNORE ? "IGNORE" :
data->style == DEL_DELETE ? "DELETE" :
"ARCHIVE";
bv.bv_len = strlen( bv.bv_val );
rc = value_add_one( &c->rvalue_vals, &bv );
if ( rc != 0 ) return ARG_BAD_CONF;
break;
case LDAP_MOD_DELETE:
data->style = DEL_IGNORE;
rc = 0;
break;
case LDAP_MOD_ADD: /* fallthrough */
case SLAP_CONFIG_ADD: /* add values */
if ( strcasecmp( c->argv[1], "IGNORE" ) == 0 )
data->style = DEL_IGNORE;
else if ( strcasecmp( c->argv[1], "DELETE" ) == 0 )
data->style = DEL_DELETE;
else if ( strcasecmp( c->argv[1], "ARCHIVE" ) == 0 )
data->style = DEL_ARCHIVE;
else {
Debug( LDAP_DEBUG_ANY, "homedir_style_cfg: "
"unrecognized style keyword\n" );
return ARG_BAD_CONF;
}
rc = 0;
break;
default:
abort();
}
return rc;
}
#define HOMEDIR_NULLWRAP(x) ( ( x ) == NULL ? "unknown" : (x) )
static void
report_errno( const char *parent_func, const char *func, const char *filename )
{
int save_errno = errno;
char ebuf[1024];
Debug( LDAP_DEBUG_ANY, "homedir: "
"%s: %s: \"%s\": %d (%s)\n",
HOMEDIR_NULLWRAP(parent_func), HOMEDIR_NULLWRAP(func),
HOMEDIR_NULLWRAP(filename), save_errno,
AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
}
static int
copy_link(
const char *dest_file,
const char *source_file,
const struct stat *st,
uid_t uidn,
gid_t gidn,
void *ctx )
{
char *buf = NULL;
int rc;
assert( dest_file != NULL );
assert( source_file != NULL );
assert( st != NULL );
assert( (st->st_mode & S_IFMT) == S_IFLNK );
Debug( LDAP_DEBUG_TRACE, "homedir: "
"copy_link: %s to %s\n",
source_file, dest_file );
Debug( LDAP_DEBUG_TRACE, "homedir: "
"copy_link: %s uid %ld gid %ld\n",
dest_file, (long)uidn, (long)gidn );
/* calloc +1 for terminator */
buf = ber_memcalloc_x( 1, st->st_size + 1, ctx );
if ( buf == NULL ) {
Debug( LDAP_DEBUG_ANY, "homedir: "
"copy_link: alloc failed\n" );
return 1;
}
rc = readlink( source_file, buf, st->st_size );
if ( rc == -1 ) {
report_errno( "copy_link", "readlink", source_file );