#!/bin/sh

alterator_api_version=1
. alterator-sh-functions

# unset this to disable debug
DEBUG=1

# directory with desktop-files
DESKTOPDIR="/etc/alterator/groups"

# cached group information
cache_dir="/var/cache/alterator/groups"

# get min and max gids from /etc/login.defs
get_minmax_gid(){
  awk '/^GID_MIN/{gid_min=$2};
       /^GID_MAX/{gid_max=$2};
       /^UID_MIN/{uid_min=$2};
       /^UID_MAX/{uid_max=$2};
       END{print gid_min, gid_max, uid_min, uid_max}'\
    < /etc/login.defs
}
LOGIN_DEFS_DATA=$(get_minmax_gid)
GID_MIN=$(echo $LOGIN_DEFS_DATA | cut -f1 -d' ')
GID_MAX=$(echo $LOGIN_DEFS_DATA | cut -f2 -d' ')
UID_MIN=$(echo $LOGIN_DEFS_DATA | cut -f3 -d' ')
UID_MAX=$(echo $LOGIN_DEFS_DATA | cut -f4 -d' ')

##############################################################################

gid_of(){
  [ -n "$1" ] || return
  echo $(getent group $1 | cut -f3 -d:)
}

uid_of(){
  [ -n "$1" ] || return
  echo $(getent passwd $1 | cut -f3 -d:)
}

# is group with gid $1 a system group
# (gid < GID_MIN  or  gid > GID_MAX)
test_gid_system(){
  [ -n "$1" ] || return
  echo $(( ($1<$GID_MIN) || ($1>$GID_MAX)))
}
test_group_system(){
  [ -n $1 ] || return
  test_gid_system $(gid_of $1)
}

# is group with gid $1 a primary group?
test_gid_primary(){
  [ -n "$1" ] || return
  getent passwd | cut -f4 -d: | grep $1 > /dev/null
  echo $?
}
test_group_primary(){
  [ -n $1 ] || return
  test_gid_primary $(gid_of $1)
}

# same tests for users
test_uid_system(){
  [ -n "$1" ] || return
  echo $(( ($1<$UID_MIN) || ($1>$UID_MAX)))
}
test_user_system(){
  [ -n $1 ] || return
  test_uid_system $(uid_of $1)
}

reset_cache(){
  rm -rf -- "$cache_dir"
  mkdir -p -- "$cache_dir"
}


##############################################################################

show_system="#t"
show_user=""

# list members
list_members(){
    local members_file="$cache_dir/member-in-$in_group"
    write_debug "list members for group $in_group\n"
    local IFS=:

    if [ ! -e "$members_file" ] ; then
        # Generate members list
        getent group "$in_group" | (
            read name password gid users
	     echo $users | sed "s/,/\n/g" | sort > "$members_file"
        )
    fi
    cat "$members_file" | grep -v '^$'
}

# list non-member users
list_non_members(){
    local members_file="$cache_dir/member-in-$in_group"
    write_debug "list non-member users for group $in_group\n"

    [ -n "$UID_MIN" ] || UID_MIN=500

    # List all ordinary users except members
    getent passwd |
    awk -F: -v "uid_min=$UID_MIN" '($3>=uid_min || $1=="root") && $7!="/dev/null"{print $1}'|
	sort |
	comm -23 - "$members_file"

    return 0
}

# list system groups (according to $show_* settings)
on_list(){
  write_debug "list groups\n"
  local IFS=:
  [ "$show_system" = "#t" ] && write_debug "show system groups\n"
  [ "$show_user"   = "#t" ] && write_debug "show user groups\n"
  getent group | sort |
  while read name password gid users; do
    [ "${name#_}" = "$name" ] &&\
    [ "$show_system" == "#t" -a "$(test_gid_system $gid)" = 1 ] ||\
    [ "$show_user" == "#t" -a "$(test_gid_system $gid)" = 0 ] &&\
    write_enum_item "$name"
  done
  return 0
}

# add member to cached group
add_member(){
    local members_file="$cache_dir/member-in-$in_group"
    local temp_file="$cache_dir/.temp"
    cat   "$members_file" > "$temp_file"
    echo  "$in_user" >> "$temp_file"
    sort  "$temp_file" > "$members_file"
    rm -f "$temp_file"
}

# remove member from cached group
remove_member(){
    local members_file="$cache_dir/member-in-$in_group"
    subst "/^$in_user$/d" "$members_file"
}

# read group $in_name
on_read(){
  write_debug "read data for group $in_name\n"
  local IFS=:
  getent group "$in_name" | (
    read name password gid users
    if [ -f "$DESKTOPDIR/$name.desktop" ]; then
      write_string_param label "$(alterator-dump-desktop -v lang="$in_language" -v out="Name" "$DESKTOPDIR/$name.desktop")"
      write_string_param comment "$(alterator-dump-desktop -v lang="$in_language" -v out="Comment" "$DESKTOPDIR/$name.desktop")"
    fi
    write_string_param name   "$name"
    write_string_param gid    "$gid"
    write_string_param label  "$label"
    write_bool_param   protected "$(test_group_system "$name" || test_group_primary "$name")"
  )
  return 0
}

# create group
on_new(){
  write_debug "adding group $in_name\n"

  if [ -z "$in_name" ]; then
    write_error "`_ "Can't add group with empty name"`" #'
    return 1
  fi

  local ans=$(groupadd "$in_name" 2>&1)
  if [ -n "$ans" ]; then
    write_error "`_ "Can't add group"`: ${ans##*:}" #'
    return 1
  fi
  return 0
}

# delete group
on_delete(){
  write_debug "deleting group $in_name\n"
  if [ -z "$in_name" ]; then
    write_error "`_ "Can't delete group with empty name"`" #'
    return 1
  fi
  local ans=$(groupdel "$in_name" 2>&1)
  if [ -n "$ans" ]; then
    write_error "`_ "Can't delete group"`: ${ans##*:}" #'
    return 1
  fi
  return 0
}

# modify users in group
on_write(){
  local members_file="$cache_dir/member-in-$in_group"
  # get old user list
  local IFS=:

  # users on input can be separated by newline, spaces, commas
  local new_users=$(list_members | tr "\n" "," | sed 's/,$//')
  write_debug "change users in group $in_group: $users -> $new_users\n"

  if [ -z "$in_group" ]; then
    write_error "`_ "Can't modify group with empty name"`" #'
    return 1
  fi

  getent group "$in_group" | (
    read name password gid users;

    local IFS=','

    # remove old users
    for ou in $users; do
      local eq=
      for nu in $new_users; do
        [ "$nu" == "$ou" ] && eq=1
      done
      [ -z "$eq" ] && user_del_from_gr "$ou" "$in_group"
    done

    # add new users
    for nu in $new_users; do
      local eq=
      for ou in $users; do
        [ "$nu" == "$ou" ] && eq=1
      done
      [ -z "$eq" ] && user_add_to_gr "$nu" "$in_group"
    done
  )
}

user_add_to_gr(){
  write_debug "add user $1 to $2\n"
  local mygroups=$2
  local IFS=' '
  local ans=
  for g in $(id -nG $1); do mygroups="$mygroups,$g"; done
  ans=$(usermod -G $mygroups $1 2>&1) ||\
    write_error "`_ "Can't add user"` $1 `_ "to group"` $2: ${ans##*:}" #'
}

user_del_from_gr(){
  write_debug "remove user $1 from $2\n"
  local mygroups=
  local IFS=' '
  local ans=
  for g in $(id -nG $1); do
    [ "$g" != "$2" ] && mygroups="${mygroups:+$mygroups,}$g"; 
  done
  ans=$(usermod -G $mygroups $1 2>&1) ||\
    write_error "`_ "Can't remove user"` $1 `_ "from group"` $2: ${ans##*:}"  #'
}

on_set_skippings(){
  write_debug "show_system=$in_show_system show_user=$in_show_user\n"
  show_system=$in_show_system;
  show_user=$in_show_user;
}

##############################################################################
##### Message loop
##############################################################################

reset_cache

on_message() {
  case "$in_action" in
    list)
	case "$in__objects" in
	    members)      [ -n "$in_group" ] || return
		          list_members | write_enum ;;
	    non_members)  [ -n "$in_group" ] || return
		          list_non_members | write_enum ;;
	    *)            on_list ;;
	esac
	;;
    read)            on_read   ;;
    write)
        case "$in__objects" in
            add_member)   [ -n "$in_group" -a -n "$in_user" ] || return
                          add_member ;;
            remove_member) [ -n "$in_group" -a -n "$in_user" ] || return
                          remove_member ;;
            *)            on_write ;;
        esac
        ;;
    new)             on_new    ;;
    delete)          on_delete ;;
    set_skippings)   on_set_skippings ;;
  esac
}

message_loop
