#!/bin/sh
##
## Samba: smbpasswd(8) wrapper to abstract error messages
## Copyright (c) 2007 SATOH Fumiyasu @ OSS Technology Co., Japan
##                    <http://www.osstech.co.jp/>
##
## Date: 2007-07-26, since 2007-05-08
##

set -u

## Functions
## ======================================================================

if [ x"`echo -n`" = x"-n" ]; then
  ## SysV?
  echo_n_opt=''
  echo_n_trailer='\c'
else
  ## BSD, Linux and so on
  echo_n_opt='-n'
  echo_n_trailer=''
fi

if type nawk >/dev/null 2>&1; then
  awk_cmd="nawk"
else
  awk_cmd="awk"
fi

## Echo without newline
echo_n()
{
  echo $echo_n_opt ${1:+"$@"$echo_n_trailer}
}

rand()
{
  "$awk_cmd" 'BEGIN{
    srand();
    srand(srand() + '$$' + '`/bin/sh -c 'echo $$'`');
    print int(rand() * '"${1:-2}"') + ('"${2:-0}"');
    exit;
  }'
}

## Options
## ======================================================================

smb_conf="${SMB_CONF:-}"
smb_server="127.0.0.1"
resolve_order=""
debug_level=""
smbpasswd_cmd="${SMB_SMBPASSWD_CMD:-smbpasswd}"
net_cmd="${SMB_NET_CMD:-net}"
passwd_cmd="${SMB_PASSWD_CMD:-/usr/bin/passwd}"
flag_check_id="yes"
flag_fallback_passwd=""

cmd_name="$0"
cmd_usage="Usage: $0 [OPTIONS] [USER]

Options [default value]:
 -c SMB_CONF
    The Samba configuration file, i.e smb.conf
 -r SMB_SERVER
    Remote SMB server where the user exists on [$smb_server]
 -R NAME_RESOLVE_ORDER
    Name  resolve order
 -D DEBUG_LEVEL
    Debug level
 -s SMBPASSWD
    Path for smbpasswd command [search from \$PATH]
 -L
    Do not check if user has valid account on this host
 -f
    Fallback to passwd(1) if user is in /etc/passwd
 -0 CMD_NAME
    Override command name in output messages [$cmd_name]

Arguments [default value]:
 USER
    Username to change its password [login username]
"

while getopts c:r:R:D:s:Lf0:h opt; do
  case "$opt" in
  c)
    smb_conf="$OPTARG"
    ;;
  r)
    smb_server="$OPTARG"
    ;;
  R)
    resolve_order="$OPTARG"
    ;;
  D)
    debug_level="$OPTARG"
    ;;
  s)
    smbpasswd_cmd="$OPTARG"
    ;;
  L)
    flag_check_id=""
    ;;
  f)
    flag_fallback_passwd="yes"
    ;;
  0)
    cmd_name="$OPTARG"
    ;;
  h)
    echo "$cmd_usage"
    exit 0
    ;;
  *)
    exit 1
    ;;
  esac
done
shift `/usr/bin/expr $OPTIND - 1`

if [ -n "${1:-}" ]; then
  smb_user="$1"; shift
  if [ -z "$flag_check_id" ] || /usr/bin/id -- "$smb_user" >/dev/null 2>&1; then
    : OK
  else
    echo "$cmd_name: Unknown user: $smb_user" 1>&2
    exit 1
  fi
else
  smb_user="`/usr/bin/id |/bin/sed 's/).*//;s/.*(//'`"
fi

if [ -n "$flag_fallback_passwd" ] && /bin/grep "^$smb_user:" /etc/passwd >/dev/null; then
## FIXME: passwd(1) on some platforms has no -r option (e.g. RHEL4)
exec "$passwd_cmd" -r files "$smb_user"
exit 1
fi

if type "$smbpasswd_cmd" >/dev/null 2>&1; then
  : OK
else
  echo "$cmd_name: Command not found: $smbpasswd_cmd" 1>&2
  exit 1
fi

if [ x"$smb_server" = x"-" ]; then
  if type "$net_cmd" >/dev/null 2>&1; then
    : OK
  else
    echo "$cmd_name: Command not found: $net_cmd" 1>&2
    exit 1
  fi

  dcs="`"$net_cmd" lookup dc`"
  if [ -z "$dcs" ]; then
    echo "$cmd_name: Domain controller not found" 1>&2
    exit 1
  fi
  dc_num="`echo "$dcs" |wc -l`"
  if [ "$dc_num" -eq 1 ]; then
    smb_server="$dcs"
  else
    dc_select="`rand "$dc_num" 1`"
    smb_server="`echo "$dcs" |head -n "$dc_select" |tail -n 1`"
  fi
fi

## ======================================================================

echo "$cmd_name: Changing password for $smb_user"
trap '/bin/stty echo; echo; exit 1' 0 HUP INT TERM PIPE
/bin/stty -echo

echo_n "Enter current password: "
read old
echo
echo_n "Enter new password: "
read new
echo
echo_n "Re-enter new password: "
read new2
echo

/bin/stty echo
trap 0 HUP INT TERM PIPE

if [ x"$new" != x"$new2" ]; then
  echo "$cmd_name: New passwords don't match" 1>&2
  exit 1
fi

## Run smbpasswd
## ----------------------------------------------------------------------

out="`
  { echo "$old"; echo "$new"; echo "$new"; } \
  |LC_ALL=C "$smbpasswd_cmd" \
    -s \
    -r "$smb_server" \
    -U "$smb_user" \
    ${smb_conf:+"-c$smb_conf"} \
    ${resolve_order:+"-R$resolve_order"} \
    ${debug_level:+"-D$debug_level"} \
    2>&1
`"
status="$?"

## NOTE: Do not use $status to check if smbpasswd succeeded or not
##       because smbpasswd returns a false-positive status code on
##       some situation. -- fumiyas, 2007-07-26
##       See also: https://bugzilla.samba.org/show_bug.cgi?id=4711

## Result
## ----------------------------------------------------------------------

result="`
  echo "$out" \
  |/bin/sed \
    -e 's/.*: \(NT_STATUS_[A-Z_]*\)$/\1/' \
    -e 's/.* Error was *: *//' \
    -e 's/^Error connecting to .* (\(.*\))$/\1/' \
    -e 's/\.$//' \
    -e 'q'
`"

## Failure
## ----------------------------------------------------------------------

case "$result" in
"Password changed for user "*)
  echo "$cmd_name: Password changed for $smb_user"
  exit 0
  ;;
NT_STATUS_LOGON_FAILURE)
  reason="Authentication failure"
  ;;
*)
  reason="$result"
  ;;
esac

echo "$cmd_name: Error: $reason" 1>&2
echo "$cmd_name: Password unchanged" 1>&2

exit 1

