#!/bin/bash
# net/l4 - LDAP helper
set -e
source "/home/zwelch/src/mcf/mcsh-release/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
script_setting_vars ldap_sort_order ldap_sort_field
script_setting_arrays ldap_sort_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" )
ldap_sort_order=${ldap_sort_order:-id}
case "$ldap_sort_order" in
name) ldap_sort_field=1 ;;
id) ldap_sort_field=2; ldap_sort_opts=( -n ) ;;
gid) ldap_sort_field=3; ldap_sort_opts=( -n ) ;;
*) error "$ldap_sort_order: unknown sort order" ;;
esac
}
######
# Low-level functions
ldap_sort() { sort -k $ldap_sort_field "${ldap_sort_opts[@]}"; }
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 "$@"
apt_install_remote "$1" "${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" |sort; }
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
} | ldap_sort
}
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" |sort; }
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 "$@" |ldap_sort
}
_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 "$@"
Generated on Tue Jul 4 17:00:07 PDT 2017 by mcsh d14 v0.21.0.