#!/bin/sh -f

alterator_api_version=1
po_domain="alterator-ldap-groups"

# delimeters
rdelim='[[:space:]]\+'
wdelim=' '


cache_dir="/var/cache/alterator/ldap-groups"
default_groups="cdwriter cdrom audio proc radio camera floppy xgrp scanner uucp users"

. alterator-sh-functions
. alterator-openldap-functions
. alterator-usersource-functions
. shell-quote

#turn off auto expansion
set -f

### cache

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

### member
member_list()
{
    local group="$1"; shift
    local member_in_file="$cache_dir/member-in-$group"
    local member_out_file="$cache_dir/member-out-$group"

    case "$mode" in
		local)
			grep "^$group:" /etc/group | cut -f4 -d':' | \
				tr ',' '\n' | sort >"$member_in_file"
			getent passwd | cut -f1 -d ':'
			;;
		ldap|krb5)
			ldap-getent group "$group" memberUid | sort | \
				sed -e 's/,[[:blank:]]*/\n/g' >"$member_in_file"
			ldap-getent passwd '*' uid
			;;
		ad)
			assert_netcmdplus || return 1
			$netcmdplus group listmembers "$group" >"$member_in_file"
			$netcmdplus user list
			;;
    esac | sort | \
		while read user; do
			fgrep -wqs "$user" "$member_in_file" || echo "$user"
		done >"$member_out_file"
}

member_list_in()
{
  local group="$1";shift
  local member_in_file="$cache_dir/member-in-$group"
  local member_out_file="$cache_dir/member-out-$group"

  [ -f "$member_in_file" -a -f "$member_out_file" ] || member_list "$group"

  cat "$member_in_file"
}

member_list_out()
{
  local group="$1";shift
  local member_in_file="$cache_dir/member-in-$group"
  local member_out_file="$cache_dir/member-out-$group"

  [ -f "$member_in_file" -a -f "$member_out_file" ] || member_list "$group"

  cat "$member_out_file"
}

member_add()
{
  local group="$1";shift
  local user="$1";shift
  local member_in_file="$cache_dir/member-in-$group"
  local member_out_file="$cache_dir/member-out-$group"

  [ -f "$member_in_file" -a -f "$member_out_file" ] || member_list "$group"
  echo "$user" | tr ';' '\n' | \
  while read usr; do
    file_list_add "$member_in_file" "$usr"
    file_list_del "$member_out_file" "$usr"
  done
}

member_del()
{
  local group="$1";shift
  local user="$1";shift
  local member_in_file="$cache_dir/member-in-$group"
  local member_out_file="$cache_dir/member-out-$group"

  [ -f "$member_in_file" -a -f "$member_out_file" ] || member_list "$group"
  echo "$user" | tr ';' '\n' |
  while read usr; do
    file_list_add "$member_out_file" "$usr"
    file_list_del "$member_in_file" "$usr"
  done
}

member_reset()
{
  local group="$1";shift
  local member_in_file="$cache_dir/member-in-$group"
  local member_out_file="$cache_dir/member-out-$group"

  rm -f -- "$member_in_file" "$member_out_file"
}

member_commit()
{
  local group="$1";shift
  local member_in_file="$cache_dir/member-in-$group"
  local member_out_file="$cache_dir/member-out-$group"

  [ -f "$member_in_file" -a -f "$member_out_file" ] || member_list "$group"
  
  case "$mode" in
	  local)
		  userlist="$(cat $member_in_file | sed -e '/^$/d' | tr ';\n' ',' | sed -e 's/,$//')"
		  [ -n $userlist ] && gpasswd -M "$userlist" "$group" || \
				  gpasswd -M "" "$group"
		  ;;
	  ldap|krb5)
		  if [ -s "$member_in_file" ]; then
			  sed 's/.*/memberUid:&/' "$member_in_file" | \
				  ldap-groupmod replace "$group" >/dev/null
		  else
			  printf 'memberUid:\n' | ldap-groupmod replace "$group" >/dev/null
		  fi
		  ;;
	  ad)
		  local current="$cache_dir/.current"
		  assert_netcmdplus || return 1
		  $netcmdplus group listmembers "$group" >"$current"
		  cat "$member_in_file" | \
			  while read user; do
				  if ! fgrep -wqs "$user" "$current"; then
					  $netcmdplus group addmembers "$group" "$user"
				  fi
			  done
		  cat "$current" | \
			  while read user; do
				  if ! fgrep -wqs "$user" "$member_in_file"; then
					  $netcmdplus group removemembers "$group" "$user"
				  fi
			  done
		  rm -f "$current"
		  ;;
  esac
  
  member_reset "$group"
}

### e-mail

email_list()
{
  local group="$1"; shift
  local email_file="$cache_dir/email-$group"
  case "$mode" in
	  local)
	  ;;
	  ldap|krb5)
		  if [ -f "$email_file" ]; then
			  cat "$email_file"
		  else
			  ldap-getent group "$group" 'mail'
		  fi
		  ;;
	  ad)
		  assert_netcmdplus || return 1
		  $netcmdplus user getpassword --filter "(&(cn=$group)(objectClass=group))" --attributes=mail | \
			  sed -n -e '/^mail:/ { s/^[^:]\+:[[:space:]]*//; p }'
		  ;;
  esac | \
	  sed -e 's/,[[:blank:]]*/\n/g' | tee "$email_file"
}

email_add()
{
  local group="$1"; shift
  local email="$1"; shift
  local email_file="$cache_dir/email-$group"

  [ -f "$email_file" ] || email_list >/dev/null
  file_list_add "$email_file" "$email"
}

email_del()
{
  local group="$1"; shift
  local email="$1"; shift
  local email_file="$cache_dir/email-$group"

  [ -f "$email_file" ] || email_list >/dev/null
  file_list_del "$email_file" "$email"
}

email_reset()
{
  local group="$1"; shift
  local email_file="$cache_dir/email-$group"

  rm -f -- "$email_file"
}

email_commit()
{
  local group="$1"; shift
  local email_file="$cache_dir/email-$group"

  [ -f "$email_file" ] || return 0

  if [ -s "$email_file" ]; then
	  case "$mode" in
		  ldap|krb5)
			  sed 's/.*/mail:&/' "$email_file" | \
				  ldap-groupmod replace "$group" >/dev/null
			  ;;
		  ad)
			  assert_netcmdplus || return 1
			  $netcmdplus group update "$group" --mail-address="$(_read_email_list)"
			  ;;
	  esac
  else
	  case "$mode" in
		  ldap|krb5)
	  		  printf 'mail:\n' | ldap-groupmod replace
			  ;;
		  ad)
			  $netcmdplus group update "$group" --mail-address=""
			  ;;
	  esac
  fi
  
  email_reset "$group"
}

### group
local_groups()
{
	getent group | cut -f1 -d ':' | sort -u | \
	while read name; do
        write_enum_item "$name" "$name"
    done
}

ldap_groups()
{
    ldap-getent group '*' cn | grep -v '\$$' | sort | \
        while read name; do
        	write_enum_item "$name" "$name"
        done
}

ad_groups()
{
	assert_netcmdplus || return 1
	$netcmdplus group list | sort | \
        while read name; do
        	write_enum_item "$name" "$name"
        done
}

list_groups()
{
    case "$mode" in
		local)  
			local_groups 
			;;
		ldap|krb5)
			ldap_groups
			;;
		ad)
			ad_groups
			;;
    esac
}

group_new()
{
    local r=; ret=1
	
    case "$mode" in
		local)
			r="$(groupadd "$1" 2>&1)"
			ret=$?
			;;
		ldap|krb5)
			r="$(ldap-groupadd "$1" 2>&1)"
			ret=$?
			;;
		ad)
			if ! assert_netcmdplus; then
				r="samba-tool-plus command not found"
			else
				r="$($netcmdplus group add "$1" 2>&1)"
				ret=$?
			fi
			;;
		*)
			r="Invalid mode $mode"
			;;
    esac

    if [ $ret -ne 0 ]; then
		write_error "$r"
		return 1
    fi

    return 0
}

group_delete()
{
    local r=; ret=1
    case "$mode" in
		local)
			r="$(groupdel "$1" 2>&1)"
			ret=$?
			;;
		ldap|krb5)
			r="$(ldap-groupdel "$1" 2>&1)"
			ret=$?
			;;
		ad)
			if ! assert_netcmdplus; then
				r="samba-tool-plus command not found"
			else
				r="$($netcmdplus group delete "$1" 2>&1)"
				ret=$?
			fi
			;;
		*)
			r="Invalid mode $mode"
			;;
    esac
	
    if [ $ret -ne 0 ]; then
		write_error "$r"
		return 1
    fi
	
    return 0
}

# Mapping to UNIX and Samba groups

# Get mapping info
mapping_get()
{
    local group="$1"

    # Set default values
    local has_unix_map="#f"
    local unix_gid=
    local has_samba_map="#f"
    local samba_name="$(echo "$group" | sed 's/^./\u&/')"

	case "$mode" in
		ldap|krb5)
			unix_gid="$(ldap-getent group "$group" gidNumber)"
			;;
		ad)
			assert_netcmdplus || return 1
			unix_gid="$($netcmdplus user getpassword --filter "(&(cn=$group)(objectClass=group))" --attributes=gidNumber | sed -n -e '/^gidNumber:/ { s/^[^:]\+:[[:space:]]*//; p }')"
			;;
	esac
	
    # System group mapping
    if [ "${unix_gid:--1}" -ge 0 -a "${unix_gid:--1}" -lt 500 ]; then
        has_unix_map="#t"
    else
        unix_gid=-1
    fi

	case "$mode" in
		ad)
			;;
		*)
			# Samba group mapping
			local r="$(net groupmap list | sed 's/^\(.*\) (S[0-9-]*) -> /\1\t/' | grep -P "\t${1}$" | cut -f1)"
			if [ -n "$r" ]; then
				has_samba_map="#t"
				samba_name="$r"
			fi
			;;
	esac

    # Write values
    write_string_param has_unix_map "$has_unix_map"
    write_string_param unix_gid "$unix_gid"
    write_string_param has_samba_map "$has_samba_map"
    write_string_param samba_name "$samba_name"
}

# Set mapping
mapping_set()
{
    [ -z "$in_group" ] && return 1

	if [ "$in_has_unix_map" = "#t" -a \
		 "$in_unix_gid" -ge 0 -a "$in_unix_gid" -lt 500 ]
	then
		# Map to the Unix group
		case "$mode" in
			ldap|krb5)			
				ldap-groupmod -g "$in_unix_gid" "$in_group"
				;;
			ad)
				assert_netcmdplus || return 1
				$netcmdplus group update "$in_group" --gid-number="$in_unix_gid"
				;;
		esac
	else
		# Unmap from the Unix group
		case "$mode" in
			ldap|krb5)
				# TODO: Fix ldap-groupmod to support this.
				ldap-groupmod -g "-1" "$in_group"
				;;
			ad)
				assert_netcmdplus || return 1
				$netcmdplus group update "$in_group" --gid-number=""
				;;
		esac
    fi

	case "$mode" in
		ldap|krb5)
			# Map to Samba group (only in LDAP mode)
			if [ "$in_has_samba_map" = "#t" ]; then
				ldap-groupmod -s "$in_samba_name" "$in_group" ||:
			else
				if [ -n "$(net groupmap list | grep " -> ${in_group}\$")" ]; then
					ldap-groupmod -u "$in_group" ||:
				fi
			fi
			;;
	esac
}

# Get system groups
list_system_groups() {
    # Add first item
    write_enum_item "-1" "`_ "Select system group:"`"

    # Append system groups
    cat /etc/group | cut -f1,3 -d':' --output-delimiter="$(echo -en '\t')" | sort \
        | while read gname gid; do \
            if [ "$gid" -lt 500 ]; then \
                write_enum_item "$gid" "$gname"    
            fi
        done

}

set_dn_conf
reset_cache

on_message()
{
	case "$in_action" in
	type)
	    write_type_item	group		ldap-group-name
	    write_type_item	member_in	ldap-group-name
	    write_type_item	member_out	ldap-group-name
	    write_type_item	new_group	ldap-group-name
	    write_type_item	new_email	e-mail
		write_type_item	netcmdplus_available boolean
	    ;;
	list)
	    case "$in__objects" in
            mode)
            	check_mode
                ;;
            bases)
				list_bases "$in_mode" "$in_rem_host"
	            ;;
			rem_bases)
				[ -z "$in_rem_host" ] || list_bases "$in_mode" "$in_rem_host"
				;;
            system_groups)
                list_system_groups
                ;;
			/)
				list_groups
				;;
            *)
				write_error "Unexpected object: $in__objects"
                ;;
        esac
	    ;;
	delete)
	    [ -n "$in_group" ] || return
	    group_delete "$in_group"
	    ;;
	new)
	    case "$in__objects" in
			source)
				set_new_source
				;;
    	    /)
				[ -n "$in_new_group" ] || return
				group_new "$in_new_group" || return
				;;
			*)
				write_error "Unexpected object: $in__objects"
				;;
	    esac
	    ;;
    read)
        case "$in__objects" in
            mapping)
                [ -n "$in_group" ] || return
                mapping_get "$in_group"
                ;;
			*)
				write_error "Unexpected object: $in__objects"
				;;
        esac
        ;;
    write)
        case "$in__objects" in
            mapping)
	            [ -n "$in_group" ] || return
        	    mapping_set "$in_group"
                ;;
            *)
				write_error "Unexpected object: $in__objects"
                ;;
        esac
        ;;
	member_list_in)
	    [ -n "$in_group" ] || return
	    member_list_in "$in_group" | write_enum_nolabel
	    ;;
	member_list_out)
	    [ -n "$in_group" ] || return
	    member_list_out "$in_group" | write_enum_nolabel
	    ;;
	member_add)
	    [ -n "$in_group" -a -n "$in_member_out" ] || return
	    member_add "$in_group" "$in_member_out"
	    ;;
	member_del)
	    [ -n "$in_group" -a -n "$in_member_in" ] || return
	    member_del "$in_group" "$in_member_in"
	    ;;
	member_commit)
	    [ -n "$in_group" ] || return
	    member_commit "$in_group"
	    ;;
	member_reset)
	    [ -n "$in_group" ] || return
	    member_reset "$in_group"
	    ;;
	email_list)
	    [ -n "$in_group" ] || return
	    email_list "$in_group" | write_enum_nolabel
	    ;;
	email_add)
	    [ -n "$in_group" -a -n "$in_new_email" ] || return
	    email_add "$in_group" "$in_new_email"
	    ;;
	email_del)
	    [ -n "$in_group" -a -n "$in_email" ] || return
	    email_del "$in_group" "$in_email"
	    ;;
	email_commit)
	    [ -n "$in_group" ] || return
	    email_commit "$in_group"
	    ;;
	email_reset)
	    [ -n "$in_group" ] || return
	    email_reset "$in_group"
	    ;;
	set_bind)
	    [ -n "$in_mode" -a -n "$in_base" -a -n "$in_host"  -a -n "$in_rootdn" -a -n "$in_rootpw" ] && set_new_bind
	    ;;
	*)
		write_error "Unexpected action: $in_action"
		;;
	esac
}

message_loop
