#  runtime : this file is used by the ldapscripts, it sould not be used independently

#  Copyright (C) 2005 Ganal LAPLANCHE - Linagora
#
#  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
#  USA.

### Useful functions ###

# Tests a string
# Input : string to test ($1)
# Output : true or false
is_yes () {
  echo "$1" | grep -qi '^yes$'
}

# Tests a string
# Input : string to test ($1)
# Output : true or false
is_no () {
  echo "$1" | grep -qi '^no$'
}

# Tests a string
# Input : string to test ($1)
# Output : true or false
is_uri () {
  echo "$1" | grep -q '://'
}

# Tests a string
# Input : string to test ($1)
# Output : true or false
is_integer () {
  echo "$1" | grep -qE '^[0-9]+$'
}

# Tests a string (a command name) and tells if it is built-in (true) or external (false)
# Input : string to test ($1)
# Output : true or false
is_builtin () {
  type "$1" 2>/dev/null | grep -qi 'built'
}

# Logs a string to $LOGFILE
# Input : string to log ($1)
# Output : nothing
log_only () {
  if [ -n "$1" ]
  then
    if [ -n "$LOGFILE" ]
    then
      if [ ! -w "$LOGFILE" ]
      then
        touch "$LOGFILE" 2>/dev/null
        if [ $? -ne 0 ]
        then
          echo "Unable to create $LOGFILE, exiting..." && exit 1
        fi
      fi
      echo "$1" >> "$LOGFILE"
    fi
  fi
}

# Echoes and logs a string to $LOGFILE
# Input : string to echo and log ($1)
# Output : nothing
echo_log () {
  [ -n "$1" ] && echo "$1"
  [ -n "$1" ] && log_only "$1"
}

# Echoes/logs $1, exits and returns 0
# Input : string to echo and log ($1)
# Output : 0
end_ok () {
  [ -n "$1" ] && echo_log "$1"
  exit 0
}

# Echoes/logs $1, exits and returns 1
# Input : string to echo and log ($1)
# Output : 1
end_die () {
  [ -n "$1" ] && echo_log "$1"
  exit 1
}

# Allocates and creates a temporary file $_TMPFILE under $TMPDIR
# Output : nothing
mktempf () {
  # Avoid creating two temporary files (must have been released before)
  [ -n "$_TMPFILE" ] && end_die "Error allocating temporary file $_TMPFILE"
  # Name temp file
  _TMPFILE="$TMPDIR/`basename $0`.`date '+%Y%m%d-%H%M%S'`.$$"
  # Catch CTRL-C to remove $_TMPFILE
  trap 'rm -f "$_TMPFILE" 2>/dev/null ; end_die "Interrupted - Removing temporary file $_TMPFILE"' 2
  # Create temp file
  _TMPMASK=`umask`
  umask 0077
  touch "$_TMPFILE" 2>/dev/null || end_die "Error creating temporary file $_TMPFILE"
  umask "$_TMPMASK"
}

# Releases a previously allocated temporary file
# Output : nothing
reltempf () {
  # Clean up the temporary file and restore traps
  rm -f "$_TMPFILE" 2>/dev/null
  # Reset traps
  trap -
  # Clean up name
  _TMPFILE=''
}

### LDAP functions ###

# Performs a search in the LDAP directory
# Input : base ($1), filter ($2), attribute to display ($3)
# Output : entry/entries found (stdout)
_ldapsearch () {
  if [ -n "$BINDPWDFILE" ]
  then
    $LDAPSEARCHBIN -y "$BINDPWDFILE" -D "$BINDDN" -b "${1:-$SUFFIX}" -xH "$SERVER" -s sub -LLL "${2:-(objectclass=*)}" "${3:-*}" 2>>"$LOGFILE" 
  else
    $LDAPSEARCHBIN -w "$BINDPWD" -D "$BINDDN" -b "${1:-$SUFFIX}" -xH "$SERVER" -s sub -LLL "${2:-(objectclass=*)}" "${3:-*}" 2>>"$LOGFILE" 
  fi
}

# Adds an entry to the LDAP directory
# Input : LDIF - entry to add (stdin)
# Output : nothing
_ldapadd () {
  if [ -n "$BINDPWDFILE" ]
  then
    $LDAPADDBIN -y "$BINDPWDFILE" -D "$BINDDN" -xH "$SERVER" 2>>"$LOGFILE" 1>/dev/null
  else
    $LDAPADDBIN -w "$BINDPWD" -D "$BINDDN" -xH "$SERVER" 2>>"$LOGFILE" 1>/dev/null
  fi
}

# Modifies an entry in the LDAP directory
# Input : LDIF - modification information (stdin)
# Output : nothing
_ldapmodify () {
  if [ -n "$BINDPWDFILE" ]
  then
    $LDAPMODIFYBIN -y "$BINDPWDFILE" -D "$BINDDN" -xH "$SERVER" 2>>"$LOGFILE" 1>/dev/null
  else
    $LDAPMODIFYBIN -w "$BINDPWD" -D "$BINDDN" -xH "$SERVER" 2>>"$LOGFILE" 1>/dev/null
  fi
}

# Renames an entry in the LDAP directory
# Input : old dn ($1), new rdn ($2)
# Output : nothing
_ldaprename () {
  if [ -z "$1" ] || [ -z "$2" ]
  then
    end_die "_ldaprename : missing argument(s)"
  else
    if [ -n "$BINDPWDFILE" ]
    then
      $LDAPMODRDNBIN -y "$BINDPWDFILE" -D "$BINDDN" -xH "$SERVER" -r "$1" "$2" 2>>"$LOGFILE" 1>/dev/null
    else
      $LDAPMODRDNBIN -w "$BINDPWD" -D "$BINDDN" -xH "$SERVER" -r "$1" "$2" 2>>"$LOGFILE" 1>/dev/null
    fi
  fi
}

# Deletes an entry in the LDAP directory
# Input : dn to delete ($1)
# Output : nothing
_ldapdelete () {
  [ -z "$1" ] && end_die "_ldapdelete : missing argument"
  if [ -n "$BINDPWDFILE" ]
  then
    $LDAPDELETEBIN -y "$BINDPWDFILE" -D "$BINDDN" -xH "$SERVER" -r "$1" 2>>"$LOGFILE" 1>/dev/null
  else
    $LDAPDELETEBIN -w "$BINDPWD" -D "$BINDDN" -xH "$SERVER" -r "$1" 2>>"$LOGFILE" 1>/dev/null
  fi
}

# Extracts LDIF information from $0 (the current script itself)
# selecting lines beginning with $1 occurrences of '#'
# Input : depth ($1)
# Output : extracted LDIF data (stdout)
_extractldif () {
  if [ -n "$1" ] && is_integer "$1"
  then
    _EXTRACTDEPTH="$1"
  else
    echo_log "Warning : invalid depth supplied to _extractldif(), using default (2)..."
    _EXTRACTDEPTH='2'
  fi
  grep -E "^#{$_EXTRACTDEPTH}[^#]*$" "$0" | sed -e 's|^#*||' 2>>"$LOGFILE"
}

# Filters LDIF information
# Input : Data to filter (stdin)
# Output : Filtered data (stdout)
_filterldif () {
  # Allocate and create temp file
  mktempf

  # Generate filter file
  echo "s|<group>|$_GROUP|g" > "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<user>|$_USER|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<uid>|$_UID|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<gid>|$_GID|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<suffix>|$SUFFIX|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<_suffix>|$_SUFFIX|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<usuffix>|$USUFFIX|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<_usuffix>|$_USUFFIX|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<msuffix>|$MSUFFIX|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<_msuffix>|$_MSUFFIX|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<gsuffix>|$GSUFFIX|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<_gsuffix>|$_GSUFFIX|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<home>|$_HOMEDIR|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<shell>|$USHELL|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<password>|$_PASSWORD|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<gecos>|$_GECOS|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
  echo "s|<entry>|$_ENTRY|g" >> "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"

  # Use it
  sed -f "$_TMPFILE" 2>>"$LOGFILE"

  # Release temp file
  reltempf
}

### Nsswitch functions

# Converts to gid any group passed in as name/gid
# Input : the name or gid to convert ($1)
# Output : the result of the conversion ($_GID)
_grouptogid () {
  [ -z "$1" ] && end_die "_grouptogid : missing argument"
  _GID=`$GETENTGRCMD "$1" 2>/dev/null | head -n 1 | cut -d ":" -f 3`
  if [ -z "$_GID" ]
  then
    _GID=`echo "$1" | grep '^[0-9]\+$'` # Check if group is a gid
    [ -z "$_GID" ] && end_die "Cannot resolve group $1 to gid : groupname not found"
    echo_log "Warning : gid $2 not resolved, using it anyway..."
  fi
}

# Converts to name any group passed in as name/gid
# Input : the name or gid to convert ($1)
# Output : the result of the conversion ($_GID)
_gidtogroup () {
  [ -z "$1" ] && end_die "_gidtogroup : missing argument"
  _GID=`$GETENTGRCMD "$1" 2>/dev/null | head -n 1 | cut -d ":" -f 1`
  if [ -z "$_GID" ]
  then
    _GID="$1"
    echo_log "Warning : group $1 not resolved, using it anyway..."
  fi
}

# Converts to uid any user passed in as name/uid
# Input : the name or uid to convert ($1)
# Output : the result of the conversion ($_UID)
_usertouid () {
  [ -z "$1" ] && end_die "_usertouid : missing argument"
  _UID=`$GETENTPWCMD "$1" 2>/dev/null | head -n 1 | cut -d ":" -f 3`
  if [ -z "$_UID" ]
  then
    _UID=`echo "$1" | grep '^[0-9]\+$'` # Check if user is a UID
    [ -z "$_UID" ] && end_die "Cannot resolve user $1 to uid : username not found"
    echo_log "Warning : uid $1 not resolved, using it anyway..."
  fi
}

# Converts to name any user passed in as name/uid
# Input : the name or uid to convert ($1)
# Output : the result of the conversion ($_UID)
_uidtouser () {
  [ -z "$1" ] && end_die "_uidtouser : missing argument"
  _UID=`$GETENTPWCMD "$1" 2>/dev/null | head -n 1 | cut -d ":" -f 1`
  if [ -z "$_UID" ]
  then
    _UID="$1"
    echo_log "Warning : user $1 not resolved, using it anyway..."
  fi
}

### LDAP advanced functions

# Finds the last group id used in LDAP
# Input : nothing
# Output : the last gid used + 1 (so the first useable gid) ($_GID)
_findlastgroup () {
  _GID=`_ldapsearch "$GSUFFIX,$SUFFIX" '(objectClass=posixGroup)' gidNumber | grep "gidNumber: " | sed -e "s|gidNumber: ||" | uniq | sort -n | tail -n 1`
  if [ -z "$_GID" ] || [ ! "$_GID" -gt "$GIDSTART" ]
  then
    _GID="$GIDSTART"
  fi
  _GID=`expr "$_GID" + 1`
}

# Finds the last machine id used in LDAP
# Input : nothing
# Output : the last machine id used + 1 (so the first useable machine id) ($_UID)
_findlastmachine () {
  _UID=`_ldapsearch "$SUFFIX" '(objectClass=posixAccount)' uidNumber | grep "uidNumber: " | sed -e "s|uidNumber: ||" | uniq | sort -n | tail -n 1`
  if [ -z "$_UID" ] || [ ! "$_UID" -gt "$MIDSTART" ]
  then
    _UID="$MIDSTART"
  fi
  _UID=`expr "$_UID" + 1`
}

# Finds the last user id used in LDAP
# Input : nothing
# Output : the last user id used + 1 (so the first useable user id) ($_UID)
_findlastuser () {
  _UID=`_ldapsearch "$SUFFIX" '(objectClass=posixAccount)' uidNumber | grep "uidNumber: " | sed -e "s|uidNumber: ||" | uniq | sort -n | tail -n 1`
  if [ -z "$_UID" ] || [ ! "$_UID" -gt "$UIDSTART" ]
  then
    _UID="$UIDSTART"
  fi
  _UID=`expr "$_UID" + 1`
}

# Finds a particular entry in the LDAP directory
# Input : base ($1), filter ($2)
# Output : the dn of the first matching entry found ($_ENTRY)
_findentry () {
  _ENTRY=`_ldapsearch "$1" "$2" dn | grep "dn: " | head -n 1 | sed -e "s|dn: ||"`
}

### Other functions ###

# Generates a password using the $PASSWORDGEN variable
# Input : the username related to the generation ($1)
# Output : the generated password ($_PASSWORD)
_genpassword () {
  PASSWORDGEN=`echo "$PASSWORDGEN" | sed -e "s|%u|$1|g"`
  _PASSWORD=`eval $PASSWORDGEN`
}

# Changes a password for a particular DN
# Input : new clear-text password ($1), user DN ($2)
# Output : nothing
_changepassword () {
  if [ -z "$1" ] || [ -z "$2" ]
  then
    end_die "_changepassword : missing argument(s)"
  else
    if is_yes "$RECORDPASSWORDS"
    then
      echo "$2 : $1" >> "$PASSWORDFILE"
    fi
    if [ -n "$BINDPWDFILE" ]
    then
      ## Change password in a secure way
      # Allocate and create temp file
      mktempf
      # Generate password file
      echo "$1" > "$_TMPFILE" || end_die "Error writing to temporary file $_TMPFILE"
      # Change password
      $LDAPPASSWDBIN -y "$BINDPWDFILE" -D "$BINDDN" -xH "$SERVER" -T "$_TMPFILE" "$2" 2>>"$LOGFILE" 1>/dev/null
      # Release temp file
      reltempf
    else
      ## Change password in the unsecure, old-fashioned way
      $LDAPPASSWDBIN -w "$BINDPWD" -D "$BINDDN" -xH "$SERVER" -s "$1" "$2" 2>>"$LOGFILE" 1>/dev/null
    fi
  fi
}

### Source configuration file

_CONFIGFILE="/etc/ldapscripts/ldapscripts.conf"
. "$_CONFIGFILE" || end_die "Unable to source configuration file ($_CONFIGFILE), exiting..."

### Checks and defaults ###

# Check if ldap client tools are correctly configured
if [ ! -x "$LDAPADDBIN" ] || [ ! -x "$LDAPDELETEBIN" ] || [ ! -x "$LDAPSEARCHBIN" ] || [ ! -x "$LDAPMODIFYBIN" ] || [ ! -x "$LDAPPASSWDBIN" ] || [ ! -x "$LDAPMODRDNBIN" ]
then
  end_die "You must have OpenLDAP client commands installed before running these scripts"
fi

# Check for bindpwd file
if [ ! -f "$BINDPWDFILE" ] || [ ! -r "$BINDPWDFILE" ]
then
  if [ -n "$BINDPWD" ]
  then
    echo_log "Warning : using command-line passwords, ldapscripts may not be safe"
  else
    end_die "Unable to read password file $BINDPWDFILE, exiting..."
  fi
fi

# Does the shell has built-in echo command ?
# If not, print a warning message
if is_builtin "echo" && is_builtin "["
then
  :
else
  echo_log "Warning : 'echo' or '[' (test) is not built-in, ldapscripts may not be safe"
fi

# Check if a full URI has been given
if is_uri "$SERVER"
then
  :
else
  SERVER="ldap://$SERVER"
fi

# Check homes, shell and logfile
UHOMES=${UHOMES:-"/dev/null"}
USHELL=${USHELL:-"/bin/false"}
LOGFILE=${LOGFILE:-"/var/log/ldapscripts.log"}
TMPDIR=${TMPDIR:-"/tmp"}

# Check password file if password recording set
if is_yes "$RECORDPASSWORDS"
then
  PASSWORDFILE=${PASSWORDFILE:-"/var/log/ldapscripts_passwd.log"}
  if [ ! -w "$PASSWORDFILE" ]
  then
    touch "$PASSWORDFILE" 2>/dev/null || end_die "Unable to create password log file $PASSWORDFILE, exiting..."
  fi
fi

# Guess what kind of getent command to use
if [ -z "$GETENTPWCMD" ] || [ -z "$GETENTGRCMD" ]
then
  case "`uname`" in
    Linux*)
      GETENTPWCMD="getent passwd"
      GETENTGRCMD="getent group"
      ;;
    FreeBSD*)
      GETENTPWCMD="pw usershow"
      GETENTGRCMD="pw groupshow"
      ;;
    *)
      GETENTPWCMD="getent passwd"
      GETENTGRCMD="getent group"
      ;;
  esac
fi

# Record command call into logfile
_NOW=`date "+%D - %R"`
log_only ">> $_NOW : Command : $0 $*"

