#!/bin/bash
# net/l4 - LDAP helper

set -e

source "/home/zwelch/src/mcf/mcsh/install/share/mcsh/mcsh.sh"


######
# Native dependencies

l4_client_packages=(
		libpam-ccreds
		libpam-cracklib
		libpam-ck-connector
		libpam-krb5
		libpam-ldap
		libnss-db
		nslcd
		nss-updatedb
		ldap-utils
		ldap-auth-client
		auth-client-config
		libsasl2-modules-ldap
	)

l4_server_packages=( "${l4_client_packages[@]}" slapd )


######
# Configuration

l4_config_init() {
	config_sys_files_insert "k5.conf" "k5.local"
	config_user_files_insert "k5.conf"
	config_sys_files_append "$script_name.passwd"
	config_user_files_append "$script_name.passwd"

	script_setting_vars \
		ldap_realm ldap_base_dn \
		ldap_host ldap_server \
		ldap_admin ldap_passwd \
		ldap_hosts_ou ldap_users_ou ldap_groups_ou \
		ldap_hosts_dn ldap_users_dn ldap_groups_dn

	script_setting_arrays \
		ldapadd_opts ldapmodify_opts ldapsearch_opts

}

l4_config_check() {
	typeset -gu realm
	if [ -z "$ldap_base_dn" ]; then
		for part in $(IFS='.'; echo $ldap_realm); do
			ldap_base_dn="$ldap_base_dn${ldap_base_dn:+,}dc=$part"
		done
		debug "set \$ldap_base_dn to $ldap_base_dn"
	fi

	ldap_hosts_dn="ou=$ldap_hosts_ou,$ldap_base_dn"
	ldap_users_dn="ou=$ldap_users_ou,$ldap_base_dn"
	ldap_groups_dn="ou=$ldap_groups_ou,$ldap_base_dn"

	ldap_opts=( -xw "$ldap_passwd" -D "$ldap_admin,$ldap_base_dn" )
}


######
# Low-level functions

ldap_uri_for_host() {
	case "$host" in
	$ldap_host|$ldap_server)
		echo "ldapi:///"
		;;
	*)
		echo "ldap://$ldap_host/"
	esac
}

client_gen_config() {
	cat <<EOF
[krb5ldap]
nss_passwd=passwd: files ldap
nss_group=group: files ldap
nss_shadow=shadow: files ldap
nss_netgroup=netgroup: files ldap
pam_auth=auth       sufficient   pam_krb5.so
         auth       required     pam_unix.so nullok_secure use_first_pass
pam_account=account    sufficient   pam_krb5.so
            account    required     pam_unix.so
pam_password=password   sufficient   pam_krb5.so
             password   required     pam_unix.so nullok obscure min=4 max=8 md5
pam_session=session    required     pam_unix.so
            session    required     pam_mkhomedir.so skel=/etc/skel/
            session    optional     pam_krb5.so
            session    optional     pam_foreground.so

[krb5ldap.cached]
nss_passwd=passwd: files ldap [NOTFOUND=return] db
nss_group=group: files ldap [NOTFOUND=return] db
nss_shadow=shadow: files ldap
nss_netgroup=netgroup: files ldap
pam_auth=auth   required       pam_env.so
         auth   sufficient     pam_unix.so likeauth nullok
         auth   [default=ignore success=1 service_err=reset] pam_krb5.so use_first_pass
         auth   [default=die success=done] pam_ccreds.so action=validate use_first_pass
         auth   sufficient pam_ccreds.so action=store use_first_pass
         auth   required        pam_deny.so
pam_account=account    sufficient   pam_krb5.so
            account    required     pam_unix.so
pam_password=password   sufficient   pam_krb5.so
             password   required     pam_unix.so nullok obscure min=4 max=8 md5
pam_session=session    required     pam_unix.so
            session    required     pam_mkhomedir.so skel=/etc/skel/
            session    optional     pam_krb5.so
            session    optional     pam_foreground.so
EOF
}

l4_remote() { cmd_dispatch "$@"; }
l4_remote_usage() {
	cat <<USAGE
<cmd>
Remote Commands:
	install <host>			Installs LDAP packages on remote host
	config <host>			Deploys LDAP support on remote host
USAGE
}

l4_remote_install() {
	has_args 1 "$@"
	local host=$1

	remote_pkg_install $host "${l4_client_packages[@]}"
}

l4_remote_config() {
	has_args 1 "$@"
	local host=$1

	local src
	src=$(cmd_tempfile)
	client_gen_config >$src

	local auth_config_file
	auth_config_file=$etcdir/auth-client-config/profile.d/krb-auth-config
	remote_deploy_file $host "$src" "$auth_config_file"

	run rm -f $src
	remote_sudo $host chown root:root $auth_config_file
	remote_sudo $host chmod go-rwx $auth_config_file
	remote_sudo $host auth-client-config -a -p krb5ldap
}

setup_schema() {
	cat <<EOF
dn: $ldap_hosts_dn
objectClass: organizationalUnit
ou: $ldap_hosts_ou

dn: $ldap_users_dn
objectClass: organizationalUnit
ou: $ldap_users_ou

dn: $ldap_groups_dn
objectClass: organizationalUnit
ou: $ldap_groups_ou
EOF
}

setup_sasl() {
	cat <<EOF
olcSaslHost: $kdc_host.$domain
olcSaslRealm: $ldap_realm
olcSaslSecProps: noplain,noactive,noanonymous,minssf=56
olcAuthzRegexp: {0}"uid=([^/]*),cn=$domain,cn=GSSAPI,cn=auth" "uid=\$1,$ldap_users_dn"
olcAuthzRegexp: {1}"uid=host/([^/]*).$domain,cn=$domain,cn=gssapi,cn=auth" "cn=\$1,$ldap_hosts_dn"
olcAuthzRegexp: {2}"uid=ldap/admin,cn=$domain,cn=gssapi,cn=auth" "cn=admin,cn=config"
EOF
}


run_ldap() {
	local cmd="ldap$1"
	shift 1
	if $logging; then
		tee -a ldap.log | run $cmd "$@"
	else
		run $cmd "$@"
	fi
}

run_ldapadd() {
	local opts=( "${ldap_opts[@]}" "${ldapadd_opts[@]}" )
	run_ldap add "${opts[@]}" "$@"
}
run_ldapmodify() {
	local opts=( "${ldap_opts[@]}" "${ldapmodify_opts[@]}" )
	run_ldap modify "${opts[@]}" "$@"
}
run_ldapsearch() {
	local opts=( "${ldap_opts[@]}" "${ldapsearch_opts[@]}" )
	run_ldap search "${opts[@]}" -b $ldap_base_dn "$@"
}


host_dn() { has_args 1 "$@"; echo "dn: cn=$1,$ldap_hosts_dn"; }
user_dn() { has_args 1 "$@"; echo "dn: uid=$1,$ldap_users_dn"; }
group_dn() { has_args 1 "$@"; echo "dn: cn=$1,$ldap_groups_dn"; }

ldif_extract() { grep "^$1:" | cut -d' ' -f2-; }

entity_exists() {
	local etype=$1
	shift
	has_args 1 "$@"
	local name=$1 id
	id=$(l4_${etype}_id "$name")
	test "$id"
}

entity_extract() {
	has_args 3 "$@"
	local etype=$1
	local name=$2
	local field=$3
	l4_${etype}_show "$name" | ldif_extract "$field"
}

entity_ids() {
	local etype=$1
	local field=$2
	shift 2
	if [ -z "$*" ]; then
		with_all_${etype}s l4_${etype}_ids "$@"
		return
	fi
	for g in "$@"; do
		l4_${etype}_extract "$g" "${field}"
	done
}
entity_next_id() {
	local -a ids
	ids=($(l4_${1}_ids))
	local max=0
	for i in ${ids[@]}; do
		if [ $i -gt $max ]; then
			max=$i
		fi
	done
	echo $((max + 1))
}
entity_delete() {
	local etype=$1
	shift 1
	min_args 1 "$@"
	for i in "$@"; do
		(
			${etype}_dn "$i"
			echo "changetype: delete"
		) | run_ldapmodify
	done
}

entity_dn_usage() {
	cat<<USAGE
{add|edit|delete} <rdn> [...]
Commands:
	add <rdn> <newrdn>+		Add an RDN
	edit <rdn> <newrdn>		Change an RDN
	delete <rdn>			Remove an RDN
USAGE
}
entity_dn_modify() {
	local entitytype=$1
	local attr=$2
	local action=$3
	shift 3
	min_args 1 "$@"
	local old=$1
	shift 1
	(
		"${entitytype}_dn" "$old"
		echo "changetype: moddn"
		case "$action" in
		add)
			min_args 1 "$@"
			for i in "$@"; do
				echo "newrdn: $attr=$i"
			done
			echo "deleteoldrdn: 0"
			;;
		modify)
			min_args 1 "$@"
			for i in "$@"; do
				echo "newrdn: $attr=$i"
			done
			echo "deleteoldrdn: 1"
			;;
		delete)
			has_args 0 "$@"
			echo "newrdn: $attr=$old"
			echo "deleteoldrdn: 1"
			;;
		esac
	) | run_ldapmodify
}

entity_attr_usage() {
	local cn=$1
	cat <<USAGE
{add|edit|delete} <$cn> <attr> [<value> ...]
Commands:
	add <$cn> <attr> <value>+	Add an attribute value
	edit <$cn> <attr> <value>+	Change an attribute value
	delete <$cn> <attr>		Remove an attribute value
USAGE
}

entity_attr_modify() {
	local entitytype=$1
	local changetype=$2
	local action=$3
	shift 3
	min_args 3 "$@"
	local cn=$1
	local field=$2
	shift 2
	(
		"${entitytype}_dn" "$cn"
		echo "changetype: $changetype"
		if [ "$action" ]; then
			echo "$action: $field"
		fi
		for value in "$@"; do
			echo "$field: $value"
		done
	) | run_ldapmodify
}


with_all_entities() {
	local etype=$1
	local func=$2
	local -a items
	items=($(l4_${etype}_list))
	$func "${items[@]}"
}

_for_each_entity() {
	local e=$1
	if ! entity_exists $etype "$e"; then
		warn "$e: $etype not found"
		return
	fi
	$efunc "$e"
}
for_each_entity() {
	min_args 2 "$@"
	local etype=$1
	local efunc=$2
	shift 2
	for_each _for_each_entity "$@"
}

with_all_hosts() { with_all_entities host "$@"; }
for_each_host() { for_each_entity host "$@"; }

with_all_users() { with_all_entities user "$@"; }
for_each_user() { for_each_entity user "$@"; }

with_all_groups() { with_all_entities group "$@"; }
for_each_group() { for_each_entity group "$@"; }


######
# Host CLI (incomplete)

l4_host() { cmd_dispatch "$@"; }
l4_host_exists() { entity_exists host "$@"; }
l4_host_extract() { entity_extract host "$@"; }
l4_host_id() { entity_extract host "$1" hid?; }
l4_host_ids() { entity_ids host hid? "$@"; }
l4_host_next_id() { entity_next_id host; }
l4_host_delete() { entity_delete host "$@"; }

l4_host_dn() { cmd_dispatch "$@"; }
l4_host_dn_usage() { entity_dn_usage; }
l4_host_dn_add() { entity_dn_modify host cn add "$@"; }
l4_host_dn_edit() { entity_dn_modify host cn modify "$@"; }
l4_host_dn_delete() { entity_dn_modify host cn delete "$@"; }

l4_host_attr() { cmd_dispatch "$@"; }
l4_host_attr_usage() { entity_attr_usage hid?; }
l4_host_attr_add() { entity_attr_modify host modify add "$@"; }
l4_host_attr_edit() { entity_attr_modify host modify replace "$@"; }
l4_host_attr_delete() { entity_attr_modify host modify delete "$@"; }


######
# Group CLI

l4_group() { cmd_dispatch "$@"; }
l4_group_usage() {
	cat <<EOF
<cmd> [...]
Group Commands:
	info [<name>+]			Prints human readable list
	list				Prints list of group names
	ids				Prints list of group ids
	add <name> <gid> <desc>		Adds a new group
	delete <name>+			Deletes one or more groups

Group Query Commands:
	next_id				Prints next available group ID
	exists <name>			Returns true if group exists
	show <name> [<attrs>+]		Displays group record
	id <name>			Displays group id

Group Command Groups:
	dn ....				Group of DN commands
	attr ....			Group of attribute commands
	user ....			Group of member user commands
EOF
}

l4_group_exists() { entity_exists group "$@"; }
l4_group_extract() { entity_extract group "$@"; }
l4_group_id() { entity_extract group "$1" gidNumber; }
l4_group_ids() { entity_ids group gidNumber "$@"; }
l4_group_next_id() { entity_next_id group; }
l4_group_delete() { entity_delete group "$@"; }

l4_group_dn() { cmd_dispatch "$@"; }
l4_group_dn_usage() { entity_dn_usage; }
l4_group_dn_add() { entity_dn_modify group cn add "$@"; }
l4_group_dn_edit() { entity_dn_modify group cn modify "$@" ; }
l4_group_dn_delete() { entity_dn_modify group cn delete "$@" ; }

l4_group_attr() { cmd_dispatch "$@"; }
l4_group_attr_usage() { entity_attr_usage cn; }
l4_group_attr_add() { entity_attr_modify group modify add "$@"; }
l4_group_attr_edit() { entity_attr_modify group modify replace "$@"; }
l4_group_attr_delete() { entity_attr_modify group modify delete "$@"; }

l4_group_list() {
	l4_extract "(objectClass=posixGroup)" "cn"
}

l4_group_show() {
	min_args 1 "$@"
	local cn=$1
	shift
	l4_search "(&(cn=$cn)(objectClass=posixGroup))" "$@"
}

l4_group_info() {
	if [ -z "$*" ]; then
		with_all_groups l4_group_info
		return
	fi
	group_info Login   GID   Description
	group_info ------- ----- -------------
	for g in "$@"; do
		local d
		d=$(l4_group_extract "$g" description)
		i=$(l4_group_extract "$g" gidNumber)
		group_info "$g" "$i" "$d" "${u[*]}"
	done
}
group_info() {
	printf "%-14s %-5s\t%-32s\t%s\n" "$@"
}

l4_group_users() {
	if [ -z "$*" ]; then
		with_all_groups l4_group_users
		return
	fi
	for g in "$@"; do
		local -a u
		u=($(l4_group_user_list "$g"))
		echo "$g: ${u[@]}"
	done
}

l4_group_add() {
	has_args 3 "$@"
	local gid name
	name=$1
	gid=$2
	desc=$3
	run_ldapadd <<EOF
dn: cn=$name,$ldap_groups_dn
objectClass: posixGroup
cn: $name
gidNumber: $gid
description: $desc
EOF
}


l4_group_user() { cmd_dispatch "$@"; }
l4_group_user_usage() {
	cat <<EOF
<cmd> [...]
Group User Commands:
	list <name>
	add <name> <uid>+
	delete <name> <uid>+
	modify <action> <name> <uid>+
EOF
}
l4_group_user_modify() {
	min_args 3 "$@"
	local action cn first
	action=$1
	cn=$2
	shift 2
	(
		group_dn "$cn"
		echo "changetype: modify"
		echo "$action: memberUid"
		for uid in "$@"; do
			echo "memberUid: $uid"
		done
	) | run_ldapmodify
}
l4_group_user_add() { l4_group_user_modify add "$@"; }
l4_group_user_delete() { l4_group_user_modify delete "$@"; }

l4_group_user_list() {
	has_args 1 "$@"
	l4_group_extract "$1" memberUid
}


######
# User CLI

l4_user() { cmd_dispatch "$@"; }
l4_user_usage() {
	cat <<EOF
<cmd> [...]
User Commands:
	info [<name>+]			Prints human readable list
	list				Prints list of user names
	ids				Prints list of user ids
	names				Prints list of user names
	email [<name>+]			Prints list of user emails

	add <name> <uid> <gid> <first> <last> [<email>] [<passwd>]
					Adds a new user
	delete <name>+			Deletes one or more users
	passwd <name>			Changes the user password

User Query Commands:
	next_id				Prints next available group ID
	exists <name>			Returns true if group exists
	show <name> [<attrs>+]		Displays group record
	id <name>			Displays group id

User Command Groups:
	dn ....				Group of DN commands
	attr ....			Group of attribute commands
EOF
}

l4_user_exists() { entity_exists user "$@"; }
l4_user_extract() { entity_extract user "$@"; }
l4_user_id() { entity_extract user "$1" uidNumber; }
l4_user_ids() { entity_ids user uidNumber "$@"; }
l4_user_next_id() { entity_next_id user; }
l4_user_delete() { entity_delete user "$@"; }

l4_user_dn() { cmd_dispatch "$@"; }
l4_user_dn_usage() { entity_dn_usage; }
l4_user_dn_add() { entity_dn_modify user uid  add "$@"; }
l4_user_dn_edit() { entity_dn_modify user uid modify "$@" ; }
l4_user_dn_delete() { entity_dn_modify user uid delete "$@" ; }

l4_user_attr() { cmd_dispatch "$@"; }
l4_user_attr_usage() { entity_attr_usage uid; }
l4_user_attr_add() { entity_attr_modify user modify add "$@"; }
l4_user_attr_edit() { entity_attr_modify user modify replace "$@"; }
l4_user_attr_delete() { entity_attr_modify user modify delete "$@"; }


l4_user_list() { l4_extract "(objectClass=posixAccount)" "uid"; }

l4_user_show() {
	min_args 1 "$@"
	local uid=$1
	shift 1
	l4_search "(&(uid=$uid)(objectClass=posixAccount))" "$@"
}

l4_user_info() {
	if [ -z "$*" ]; then
		with_all_users l4_user_info
		return
	fi

	user_info Login  UID  GID  Name
	user_info ------ ---- ---- -----
	for_each_user _l4_user_info "$@"
}
_l4_user_info() {
	local u=$1
	local f g
	f=$(l4_user_extract "$u" uidNumber)
	g=$(l4_user_extract "$u" gidNumber)
	local -a n
	n=($(l4_user_extract "$u" displayName))
	user_info "$u" "$f" "$g" "${n[*]}"
}
user_info() { printf "%-14s\t%5s %4s\t%s\n" "$@"; }


l4_user_names() {
	if [ -z "$*" ]; then
		with_all_users l4_user_names
		return
	fi
	user_names Login Given Surname 'Full Name' 'Display'
	user_names ----- ----- ------- --------- -----
	for_each_user _l4_user_names "$@"
}
_l4_user_names() {
	local u=$1
	local gn sn cn dn
	gn=$(l4_user_extract "$u" givenName)
	sn=$(l4_user_extract "$u" sn)
	cn=$(l4_user_extract "$u" cn)
	dn=$(l4_user_extract "$u" displayName)
	user_names "$u" "$gn" "$sn" "$cn" "$dn"
}
user_names() { printf "%-14s\t%-10s %-10s %-16s\t%-16s\n" "$@"; }



_l4_user_email() {
	local u=$1
	n=$(l4_user_extract "$u" displayName)
	e=$(l4_user_extract "$u" mail)
	echo "$n <$e>"
}
l4_user_email() {
	if [ -z "$*" ]; then
		with_all_users l4_user_email
		return
	fi
	for_each_user _l4_user_email "$@"
}


l4_user_add() {
	min_args 5 "$@"
	local login=$1
	local uid=$2
	local gid=$3
	local gn=$4
	local sn=$5
	local mail=$6
	local passwd=$7

	if [ -z "$uid" -o $uid -eq 0 ]; then
		uid=$(l4_user_next_id)
	fi
	if [ -z "$gid" -o $gid -eq 0 ]; then
		gid=$(l4_group_next_id)
	fi

	local home
	if [ "$mail" ]; then
		home="/bin/false"
	else
		mail="$login@$domain"
		home="/home/$login"
	fi
	[ "$passwd" ] || passwd=$(apg -n 1 -c "$login$uid")
	local fn="$gn $sn"
	if true; then
		user_dn $login
		cat <<EOF
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: $login
sn: $sn
givenName: $gn
cn: $fn
displayName: $fn
uidNumber: $uid
gidNumber: $gid
userPassword: $passwd
gecos: $fn
loginShell: /bin/false
homeDirectory: $home
mail: $mail
EOF
	fi | run_ldapadd

	l4_user_show "$login"
}


######
# Low-level commands

l4_extract() {
	local search=$1
	local field=$2
	l4_search "$search" "$field" | ldif_extract $field
}


l4_search() {
	run_ldapsearch "$@"
}

l4_add() {
	run_ldapadd "$@"
}


######
# Check

run_check() { echo "=== $@ ==="; "$@"; }
l4_check() {
	if $intense; then
		run_check l4_user_info
		run_check l4_user_names
		run_check l4_user_email
		run_check l4_group_info
	else
		app_help
	fi
}


######
# Main

l4_desc() { echo "LDAP management helper"; }

l4_usage() {
	cat <<USAGE
<cmd> ...
General Commands:
	add ...				- run ldapadd
	search <query> [<field>+]	- Run a search
	extract <query> <field>		- Extract field values

Command Groups:
	user ...			User management
	group ...			Group management
	host ...			Host management
	remote ...			Remote host management
USAGE
}

l4_help() {
	cat<<HELP
The $script_name tool automates an LDAP server and directory.
HELP
}

app_run "$@"

View the Script Reference Index


Generated on Tue Apr 25 21:19:43 PDT 2017 by mcsh i7 v0.18.0.