#!/bin/bash # ------- # File: ldap-user-sync # Description: llxcfg-backend script to keep LDAP in sync with # 'the rest of the world' # Author: Luis Garcia Gisbert # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA # -------- set -e # load vars PATH="/usr/sbin:/usr/bin:/sbin:/bin" PATH_TO_BATCH="/var/log/lliurex-lwatlogs/prebackend" PATH_TO_BATCH_UPDATE="/var/log/lliurex-lwatlogs/prebackend_update" PATH_LOG_STUDENTS="/var/log/lliurex-lwatlogs/lwat_alumnos" PATH_LOG_TEACHERS="/var/log/lliurex-lwatlogs/lwat_profesores" PATH_LOG_OTHER="/var/log/lliurex-lwatlogs/lwat_otros" # ADDED Groups for ACL's AD_GROUP="admin" PR_GROUP="teachers" WORKING_FILE=$(mktemp /tmp/working_batch.XXXXXXXXX) # functions die(){ echo -e "$1" >&2 RC=$2 [ "$RC" ] || RC=1 exit $RC } # # LOG # do_log(){ # Make Log of Parameter echo "$@" >> /root/usersync.log } usage() { CMD_NAME="$(basename "$0")" die "Usage: $CMD_NAME passwd USER_DN PASSWD\n\ $CMD_NAME {addgroup|delgroup} USER_DN GROUP1 [... GROUPN]\n\ $CMD_NAME {add} USER_DN PASSWD INITIAL_GROUP [GROUP1 ... GROUPN]\n $CMD_NAME {del} USER_DN HOME_DIRECTORY INITIAL_GROUP [GROUP1 ... GROUPN]\n" } write_ldif(){ cat << EOF dn: $USER_DN changetype: modify replace: $PASS_ATTRIBUTE ${PASS_ATTRIBUTE}: foo - EOF } verify_credentials(){ local rc # create temporary ldiff file TMP_FILE="$(tempfile)" rc=0 write_ldif > $TMP_FILE echo "$USER_PASSWD" |ldapmodify -n -D "$USER_DN" -W -H "$LDAP_URI" -x -f "$TMP_FILE" &>/dev/null || rc=$? rm "$TMP_FILE" return $rc } dn_component(){ echo "[[:blank:]]*${1}[[:blank:]]*=[[:blank:]]*\([[:alnum:]]\+\)[[:blank:]]*" } test_userdn(){ if echo ${USER_DN} |grep -q "^$(dn_component uid)," ; then return 0 fi return 1 } # # KRB5 ADD PRINCIPAL # add_krb5_princ(){ # Add Principal to Krb5 if it not present return 0 } # # TEST LDAP USER # test_ldap_user(){ # Is the $USER_NAME in ldap local rc rc=0 getent passwd "$USER_NAME" || getent passwd |grep -q "^$USER_NAME:" || rc=$? return $rc } # # TEST USER # test_user(){ # Test if the user is in LDAP and Kerberos. # For this mission use his friends : # - test_ldap_user # - test_krb5_user [ -n ${USER_NAME} -a -n ${USER_TYPE} ] || return 1 # verificar que es un usuario valido del sistema y de red (userfuns) ... # Importa el Tipo de Usuario ???? # Is a System User of NetUser and under the Realm? test_ldap_user || return 1 return 0 } # # TEST HOME # test_home(){ # Test if HomeDirectory is stored in LDAP get_home || return 1 [ -d "$USER_HOMEDIR" ] || return 1 return 0 } # # IS LINKABLE # is_linkable(){ # This Functions test if the Group Directory Exists in /net/groups and # if not exists create it. local GROUP GROUP="$1" [ -d "${NETGROUP_DIR}/${1}" ] || mkdir -p "${NETGROUP_DIR}/${1}" return 0 } # # SET PASSWD # set_passwd() { # Set passwd in Kerberos # this password is sended by echo to not create a new Process # (a naughty user can be "listening" on ps ) if [ $USER_GROUP = "teachers" ] ; then PATH_FILE_WORK=$PATH_LOG_TEACHERS else if [ $USER_GROUP = "students" ] ; then PATH_FILE_WORK=$PATH_LOG_STUDENTS else PATH_FILE_WORK=$PATH_LOG_OTHER fi fi verify_credentials || return $? echo -e "${USER_PASSWD}\n${USER_PASSWD}" | llxcfg-krb5 passwd ${USER_NAME} &>/dev/null if [ $USER_GROUP != "teachers" ] ; then sed -i -e "s%\(###${USER_NAME}###\).*\(###\)%\1${USER_PASSWD}\2%g" $PATH_FILE_WORK fi return 0 } create_home(){ mkdir -p "$USER_HOMEDIR" -m "770" # copy skel rsync -rlptgoD /etc/skel/UserFiles "${USER_HOMEDIR}" chown -R "$USER_NAME":"$USER_INITIAL_GROUP" "$USER_HOMEDIR" find ${USER_HOMEDIR} -type f -exec chmod 660 {} \; || true find ${USER_HOMEDIR} -type d -exec chmod 770 {} \; || true return 0 } # sets default & admin acls set_admin_acl(){ local userhome userhome="$1" [ ! -d "$userhome" ] && return 0 setfacl -k ${userhome} setfacl -d -m g:${AD_GROUP}:rwx ${userhome} setfacl -m g:${AD_GROUP}:rwx ${userhome} return 0 } set_prof_acl(){ local userhome userhome="$1" [ ! -d "$userhome" ] && return 0 setfacl -d -m g:${PR_GROUP}:rwx ${userhome} setfacl -m g:${PR_GROUP}:rwx ${userhome} return 0 } # # Remove Home Directory # remove_home(){ # This Functions Remove homeDirectory from /net/home # preserve homedir ? rm -rf "$USER_HOMEDIR" } # # Delete From Group # del_group(){ # This function maintain the Consistence of Universe # deleting /net/groups/Group/SimbolicLink and if it # was the last directory ...erase the Parent Directory # /net/groups/Group local GROUP_DIR GROUP_DIR=${1} if [ -L "${NETGROUP_DIR}/${GROUP_DIR}/${USER_NAME}" ] ; then rm -rf "${NETGROUP_DIR}/${GROUP_DIR}/${USER_NAME}" fi return 0 } create_netdir(){ mkdir -p "$1" chmod $NETHOME_MODE "$1" chown "$NETHOME_UID":"$NETHOME_GID" "$1" # netgroup acls ... return 0 } add_group(){ local USER_GROUP NET_DIR USER_GROUP="$1" NET_DIR="${NETHOME_DIR}/${1}" if is_linkable ${USER_GROUP} ; then #[ -d "${NET_DIR}" ] || create_netdir "${NET_DIR}" ln -s "$USER_HOMEDIR" "${NETGROUP_DIR}/${USER_GROUP}" 2>/dev/null || true fi return 0 } enhance_home(){ GRP_USER="$(id -ng $USER_NAME)" BN_HOMEDIR="$(basename $USER_HOMEDIR)" USER_HOMEDIR="$NETHOME_DIR/$GRP_USER/$BN_HOMEDIR" } get_home(){ rc=0 USER_HOMEDIR="$(${LDAPSEARCH_URI}"(uid=$USER_NAME)" $HOME_ATTRIBUTE |sed -ne "/^[[:blank:]]*${HOME_ATTRIBUTE}:/s%^[[:blank:]]*${HOME_ATTRIBUTE}:[[:blank:]]*%%p" 2>/dev/null)" || rc=$? enhance_home return $rc } is_eraseable(){ # Function test if The User can be deleted # Prerrequisites are: # - LDAP is Alive and Working # - User is NOT in LDAP set -o pipefail rc=0 RES=$($LDAPSEARCH_URI "(uid=$USER_NAME)" 2>&1 | wc -l ) || rc=$? set +o pipefail if [ "$rc" = "0" ] && [ "$RES" = "0" ]; then return 0 else return 1 fi } head_hunter(){ # Find and cut the selected lines START_LINE=$1 END_LINE=$2 FILE_TMP=$(mktemp /tmp/batch_ldap.XXXXXXXX) #echo Thanks To René sed -n $1,$2p "$PATH_TO_BATCH" > "$FILE_TMP" mv "$FILE_TMP" "$WORKING_FILE" return 0 } batch_processor(){ # Batch mode (file is STDIN) # THE MOTHER OF THE LAMB # shift # If no more arguments all file will be processed # # 0 Arguments = ALL FILE # # If 2 parameter given process line between $PARAMETER_1 and $PARAMETER_2 # # [ "$(find "$PATH_TO_BATCH" -printf "%u %g %m")" = "www-data www-data 600" ] || die "Batch file not present...exiting" ARGI=$(($ARGI + 1)) FIRST_PARAMETER=${ARGV[$ARGI]} ARGI=$(($ARGI + 1)) SECOND_PARAMETER=${ARGV[$ARGI]} ARGI=$(($ARGI + 1)) if [ -n "$FIRST_PARAMETER" -a -n "$SECOND_PARAMETER" ] ; then if [ $FIRST_PARAMETER -gt $SECOND_PARAMETER ] ; then head_hunter $SECOND_PARAMETER $FIRST_PARAMETER else head_hunter $FIRST_PARAMETER $SECOND_PARAMETER fi else cp $PATH_TO_BATCH $WORKING_FILE fi [ -d /net/home/students/ ] || mkdir -p /net/home/students/ [ -d /net/home/teachers/ ] || mkdir -p /net/home/teachers/ # First do something checks [ "$(find /net/home/students/ -printf "%m")" = 755 ] || chmod 755 /net/home/students/ [ "$(find /net/home/teachers/ -printf "%m")" = 755 ] || chmod 755 /net/home/teachers/ # Work with the WORKING FILE FILEUSERS=$(mktemp /tmp/users_working_batch.XXXXXXXXX) FILEGROUPS=$(mktemp /tmp/groups_working_batch.XXXXXXXXX) cat $WORKING_FILE | while read USER_NAME USER_PASSWD USER_HOMEDIR USER_INITIAL_GROUP USER_UNINITIAL_GROUPS; do enhance_home echo -e "${USER_PASSWD}\n${USER_PASSWD}" | llxcfg-krb5 addprinc ${USER_NAME} &>/dev/null create_home echo "$USER_HOMEDIR" >> $FILEUSERS for MYGROUP in $USER_UNINITIAL_GROUPS ; do add_group $MYGROUP echo "${NETGROUP_DIR}/${MYGROUP}" >> $FILEGROUPS done done # Removed for security rm -f $PATH_TO_BATCH rm -f $WORKING_FILE exit 0 } # # Process file to change links of users from group to other group. # File : /var/log/lliurex-lwatlog/prebackend_update # batch_processor_update(){ # Batch mode (file is STDIN) # THE MOTHER OF THE LAMB # shift # If no more arguments all file will be processed # # 0 Arguments = ALL FILE # # If 2 parameter given process line between $PARAMETER_1 and $PARAMETER_2 # # [ "$(find "$PATH_TO_BATCH_UPDATE" -printf "%u %g %m")" = "www-data www-data 600" ] || die "Batch file not present...exiting" ARGI=$(($ARGI + 1)) FIRST_PARAMETER=${ARGV[$ARGI]} ARGI=$(($ARGI + 1)) SECOND_PARAMETER=${ARGV[$ARGI]} ARGI=$(($ARGI + 1)) if [ -n "$FIRST_PARAMETER" -a -n "$SECOND_PARAMETER" ] ; then if [ $FIRST_PARAMETER -gt $SECOND_PARAMETER ] ; then head_hunter $SECOND_PARAMETER $FIRST_PARAMETER else head_hunter $FIRST_PARAMETER $SECOND_PARAMETER fi else cp $PATH_TO_BATCH_UPDATE $WORKING_FILE fi [ -d /net/groups/ ] || mkdir -p /net/groups/ # First do something checks [ "$(find /net/groups -printf "%m")" = 755 ] || chmod 755 /net/groups/ # Work with the WORKING FILE cat $WORKING_FILE | while read line; do # # Foreach USER_INITIAL_GROUP or USER_UNINITIAL_GROUPS its groups has been separated by : # USER_NAME;USER_HOMEDIR;USER_UNINITIAL_GROUPS:USER_UNINITIAL_GROUPS;USER_OLD_GROUPS:USER_OLD_GROUPS # USER_NAME=`echo $line | cut -d ";" -f1` USER_HOMEDIR=`echo $line | cut -d ";" -f2` enhance_home USER_OLD_GROUPS=`echo $line | cut -d ";" -f3 | tr ":" " "` USER_UNINITIAL_GROUPS=`echo $line | cut -d ";" -f4 | tr ":" " "` for DEL_GROUP in $USER_OLD_GROUPS ; do del_group $DEL_GROUP done for MYGROUP in $USER_UNINITIAL_GROUPS ; do add_group $MYGROUP done done # Removed for security rm -f $PATH_TO_BATCH_UPDATE rm -f $WORKING_FILE exit 0 } can_delete_home(){ # This Fuction test if home is present # and can be deleted # In This Function USER_HOMEDIR is a parameter USER_HOMEDIR=${1} enhance_home [ -d ${USER_HOMEDIR} ] || return 1 return 0 } ######## # MAIN # ######## # declare ARGV array declare -a ARGV # Bash arrays are 0-based, but we like ARGC to be equal to the number of non-option arguments. # To solve this issue, we use a "C-like" ARGV vector by setting ARGV[0] with the name of script # ('basename' of script for arbitrary cosmetic reasons), so real arguments begin with index 1 # the array will be ARGC+1 elements, and each element ARGV[n] is equivalent to $n argument # ARGC=1 ARGV[0]="$(basename "$0")" # Now, fill ARGV with \0 teminated stdin args eval "$(tr "\000" "\n"|sed -e 's%"%\\\"%g;s%^%"%;s%$%"%'|while read -r a; do echo "ARGV[$ARGC]=${a};"; ARGC=$(($ARGC+1)); done)" # explanation:' # tr command splits stdin into several lines (one argument per line) (WARNING: arguments CAN NOT contain \n !!!) # sed escapes any previous occurrence of " in arguments and confines each argument into " (to avoid blanks trimming in read) # 'read -r' allows backslashes to be considered as part of the line (and not escape characters) # the output is very similar to 'showvars' llxcfg script (but with array elements), so 'eval' can do the required "black magic" # fix ARGC value (subshell paradigm ...) ARGC=$((${#ARGV[@]} - 1)) ARGI=1 # ARGI variable will be used as index to access ARGV elements. ARGI is initialized to 1, # so ARGV[$ARGI] is equivalent to $1. We can simulate 'shift' by increasing ARGI, because # accesing ARGV[$ARGI] (after 'ARGI++') is equivalent to access $1 positional parameter after a shift command # List of Vars VAR_LIST="LDAP_BASE_DN LDAP_URI KRB5_REALM LDAP_MASTER_URI LDAP_SRV_MODE" SHOWVARS="/usr/sbin/llxcfg-showvars" eval $(${SHOWVARS} ${VAR_LIST}) if [ "$LDAP_SRV_MODE" = "SLAVE" -a "$LDAP_MASTER_URI" ] ; then LDAP_URI="$LDAP_MASTER_URI" fi #LDAPSEARCH PARAMETRIZED LDAPSEARCH_URI="ldapsearch -H "$LDAP_URI" -x -LLL " # # Rest of Values # PASS_ATTRIBUTE="userPassword" HOME_ATTRIBUTE="homeDirectory" HOMEDIR_MODE="700" HOME_MODE="600" NETHOME_MODE="755" NETHOME_UID="root" NETHOME_GID="root" NETHOME_DIR="/net/home" NETGROUP_DIR="/net/groups" USER_HOMEDIR="" HOME_IS_LOCAL="" # # Sanity Checks # #First store the "Action" Value USER_ACTION="${ARGV[$ARGI]}" [ "$USER_ACTION" ] || die "Not Action present" # If action "addgroup" is present, then we test # if the Groups is under: "Managed". And then continue # making the Groups simbolic links. If not under "Managed" # the action is: die early (critical time operation). [ "$USER_ACTION" != "addgroup" -o -z "${ARGV[3]%%cn=*,ou=Managed,*}" ] || die "Not Handled by Backend Group $GROUP_DIRECTORY" # If action is "clean-logs" the backend call llxcfg-ldap # to clean ldap-logs. This function remains the last and # the "pre-last" log usefull, because if this operation not maked # /var/lib/ldap will be full of useless logs. [ "$USER_ACTION" != "clean-logs" ] || llxcfg-ldap clean-logs [ "$USER_ACTION" != "batch" ] || batch_processor [ "$USER_ACTION" != "batch-update" ] || batch_processor_update [ "$USER_ACTION" != "lliurex-ldap-clean-logs" ] || lliurex-ldap-clean-logs & # shift ARGI=$(($ARGI + 1)) # User DN is now the firsparameter in $@ USER_DN="${ARGV[$ARGI]}" # shift ARGI=$(($ARGI + 1)) # Test if UserDN is under LDAP test_userdn || die "userDn not valid" # Extract some usefull values: # # USER_NAME # USER_TYPE USER_NAME="$(echo "$USER_DN" | sed -ne "s%^$(dn_component uid).*%\1%p")" USER_TYPE="$(echo "$USER_DN" | sed -ne "s%^$(dn_component uid),$(dn_component ou).*%\2%p")" # More parameters? [ "${ARGV[$ARGI]}" ] || die "Need some parameters more" rc=0 case "$USER_ACTION" in passwd) # params: (plaintext) user_passwo test_user || die "The User is not valid" USER_PASSWD=${ARGV[3]} #shift USER_GROUP=${ARGV[4]} set_passwd || rc=$? ;; addgroup|delgroup) test_home || die "Home Directory is not Valid Home" GROUP_DIRECTORY="$(echo ${ARGV[$ARGI]} | sed -ne "s%^$(dn_component cn).*%\1%p")" [ "$GROUP_DIRECTORY" ] || die "Group Directory is no present" # If Action is "addgroup" if [ "$USER_ACTION" = "addgroup" ] ; then add_group "$GROUP_DIRECTORY" || rc=$? export NETGROUP_DIR GROUP_DIRECTORY USER_NAME llxcfg-ldapbackend-scripts addgroup # If not (is delgroup) else del_group "$GROUP_DIRECTORY" || rc=$? clean-empty-directories "$NETGROUP_DIR" export NETGROUP_DIR GROUP_DIRECTORY USER_NAME llxcfg-ldapbackend-scripts delgroup "$NETGROUP_DIR" fi ;; add) # Add User Function # params: passwd, group list test_ldap_user || die "The User is not valid" # Add User to Kerberos USER_PASSWD="${ARGV[$ARGI]}" llxcfg-krb5 addprinc ${USER_NAME} ${USER_PASSWD} || true get_home || die "Home is Not Valid" # shift ARGI=$(($ARGI + 1)) USER_INITIAL_GROUP="${ARGV[$ARGI]}" [ -n "$USER_HOMEDIR" -a -n "$USER_INITIAL_GROUP" ] || usage if [ $rc -eq 0 ] ; then create_home || rc=$? fi export USER_HOMEDIR llxcfg-ldapbackend-scripts add ;; del) # delete user # Parameters: # USER_HOMEDIR GROUP [GROUP1...GROUPN] # unset_passwd in krb ? is_eraseable || die "Is not eraseable" can_delete_home ${ARGV[$ARGI]} || die "The Home is not present" # shift ARGI=$(($ARGI + 1)) remove_home find "$NETGROUP_DIR" -mindepth 1 -maxdepth 1 | clean-broken-links - clean-empty-directories "$NETGROUP_DIR" llxcfg-krb5 --force delprinc "$USER_NAME@$KRB5_REALM" 2>/dev/null if [ $USER_TYPE = Students ]; then sed -i "/###$USER_NAME###.*###/d" $PATH_LOG_STUDENTS else if [ $USER_TYPE = Teachers ] ;then sed -i "/###$USER_NAME###.*###/d" $PATH_LOG_TEACHERS else sed -i "/###$USER_NAME###.*###/d" $PATH_LOG_OTHER fi fi export USER_HOMEDIR llxcfg-ldapbackend-scripts del ;; *) usage ;; esac # # Error # if [ $rc -ne 0 ] ; then die "Error" $rc fi rm $WORKING_FILE exit 0