#!/bin/sh
# 
# This is a shell library containing utility functions the scripts in the 
# desktop-profiles package. This library currently contains the following 
# functions (see the comments with each function for more info):
#    - test_requirement: takes a requirement and (optionally) a username, 
#                        exit code indicates wether the requirement is met
#    - test_profile_requirements: takes a profile's line (from the .listing
#                                 file), and (optionally) a username, exit
#                                 code indicates wether the requirements are
#                                 met
#    - for_each_requirement: first argument is a list of requirements, second
#                            argument is a command to be executed once for 
#                            each requirement (with requirement as argument)
#    - list_listings: returns a space separated list of all .listing files
#                     found in the directories contained in $LISTINGS_DIRS
#    - filter_listings: returns matching profiles from the available listing
#                       files (output influenced by a number of environment
#                       variables, see below for a list)
# 
# See desktop-profiles (7) for more information about using profiles through
# desktop-profiles and the format of the .listing files
#
# (c) 2004 Bart Cornelis <cobaco AT skolelinux no>
###############################################################################

###############################################################################
# test_requirement () - test wether the given requirement is fulfilled for a
#                       given user
#
#     Note: won't work for not-current-user when command-requirments depend on
#           the user's environment settings.
#
# $1 = requirement
# $2 = username (defaults to current user if absent)
#
# Each requirement is of one of the following forms:
#   requirement |  meaning
#   --------------------------
#   <group>     | $USER is a member of <group>
#   !<group>    | $USER must not be member of group
#   !           | always false (i.e. deactivate profile)
#   $(command)  | (shell) command exits succesfully
#
# returns succesfully ($?=0) if requirement is met
# returns 1 otherwise
###############################################################################
test_requirement(){
  # if no requirement (given) -> then it's met
  if (test "$1"x = x); then
    exit;
  fi;

  # initialize needed variables.  Do not give argument to groups when looking up the current
  # user, to avoid expensive lookup with LDAP NSS.
  if (test "$2"x = x) || (test "$USER" = "$2") ; then
    OUR_USER="$USER"
    OUR_GROUPS="`id -Gn`"
  else
    OUR_USER="$2"
    OUR_GROUPS="`id -Gn -- $OUR_USER`"
  fi

  # !... requirement
  if (echo "$1" | grep '^!' > /dev/null) ; then
    GROUP=`echo "$1" | sed 's/^!//'`;
    
    # deactivated profile
    if (test "$GROUP"x = x); then
      exit 1;
    fi;  
    
    # user is not a member of given group
    if (echo $OUR_GROUPS | grep -v $GROUP > /dev/null); then
      exit;# success
    fi;

  # given command must exit succesfully
  elif (echo "$1" | grep '^\$(.*)' > /dev/null); then
    COMMAND="`echo "$1" | sed -e 's/^\$(//' -e 's/)$//'`";
    
    sh -c "$COMMAND" > /dev/null;
    exit $?;    

  # user is a member of given group
  else
    if (echo $OUR_GROUPS | grep $1 > /dev/null); then
      exit;# success
    fi;
  fi;  

  # if we get here the requirement was not met
  exit 1;
}

################################################################
# $1 = list of requirements
# $2 = '$2 $REQUIREMENT' will be executed for each REQUIREMENT
# $3- ... =  extra arguments to pass to each $2 call
################################################################
for_each_requirement(){
  PROFILE_REQUIREMENTS="$1";shift
  COMMAND="$1";shift
  EXTRA_ARGS="$@";
  
  # requirements -> check one by one
  while (test "$PROFILE_REQUIREMENTS"x != x); do
    # attempt to get first (remaining) REQUIREMENT 
    C=1;
    REQUIREMENT=`echo "$PROFILE_REQUIREMENTS" | cut --fields 1 --delimiter " "`;

    # if command requirement
    if (echo "$REQUIREMENT" | grep "^\$(" > /dev/null); then 
      #make sure we have the whole command (with params)
      while (echo "$REQUIREMENT" | grep -v ')$' > /dev/null); do
        C=`expr $C + 1`;
        REQUIREMENT=`echo $PROFILE_REQUIREMENTS | cut --fields -$C --delimiter " "`;
      done;	

      # prepare loop for next iteration 
      C=`expr $C + 1`;
      PROFILE_REQUIREMENTS=`echo $PROFILE_REQUIREMENTS | cut --fields $C- --delimiter " " `;
    else
      # prepare loop for next iteration 
      PROFILE_REQUIREMENTS=`echo $PROFILE_REQUIREMENTS | sed -e "s/^$REQUIREMENT//" -e "s/^ *//"`;
    fi;
      
    "$COMMAND" "$REQUIREMENT" "$EXTRA_ARGS"
  done;
}

###############################################################################
# test_profile_requirements() - test wether the given profile's requirements
#                               are met for a given user.
#
#     Note: won't work for not-current-user when command-requirments depend on
#           the user's environment settings.
#
# $1 = the profile line from the listing file
# $2 = username (defaults to current user if absent)
#
# returns succesfully ($?=0) if requirement is met
# returns 1 otherwise
###############################################################################
test_profile_requirements() {
  PROFILE_REQUIREMENTS="$(echo $@ | cut --fields 5 --delimiter ";")";

  # no requirements -> met
  if (test "$PROFILE_REQUIREMENTS"x = x); then
    exit;      
  fi;
    
  # requirements -> check one by one
  for_each_requirement "$PROFILE_REQUIREMENTS" test_requirement $2;
    
  # all requirements are met (or we wouldn't get here)
  exit;      
}

################################################################################
# outputs a space separated list of all .listing files found in the directories
# contained in $LISTINGS_DIRS
################################################################################
list_listings () {
  # Make sure the variable we need are initialized
  LISTINGS_DIRS=${LISTINGS_DIRS:-'/etc/desktop-profiles'}

  for DIR in $LISTINGS_DIRS; do
    echo -n $(ls -1 $DIR/*.listing);
    echo -n " ";
  done;
  
  echo;
}

###############################################################################
# filter_listings() - filters the profiles in the .listing files 
#                     (criteria and output-format are set through a number of
#                      environment variables, as listed below)
#
# The following environment variables _may_ be used: 
# - NAME_FILTER, LOCATION_FILTER, REQUIREMENT_FILTER, 
#   KIND_FILTER, and DESCRIPTION_FILTER: contain the regexp filter to be used
#   on the corresponding field of the profile-line
# - PRECEDENCE_FILTER contains the second half of an expression to be passed to 
#   the test program (e.g. '-gt 0', '-ge 0', or '-lt 50')
# - OUR_USER: requirements need to be met for this user
# - FORMAT: don't just echo the profile-line from the .listing file, but use
#   the specified format (may use the variables NAME, LOCATION, PRECEDENCE, 
#   REQUIREMENT, KIND, DESCRIPTION, FILE variables. First 6 refer to the 
#   the respective fields for that profile, FILE refers to the file the profile
#   is listed in.
#	NOTE: characters interpreted specially by the shell (such as ';') need
#             to be escaped.
# - SORT_KEY: sort on field (NOTE: numeric)
# - SORT_ARGS: extra arguments to be given to the sort command (e.g. when
#   sorting on the precedence field (3) you probably want to set this to
#   '--general-numeric-sort --reverse')
# - LISTINGS_DIRS: the directory containing the .listing files to include 
#   (defaults to '/etc/desktop-profiles', probably shouldn't be changed ever)
#
# In absence of any set variables it will just output all available profiles
# sorted by name.
#
# The list-desktop-profile script from the desktop-profiles package offers an
# example of how to use this function.
###############################################################################
filter_listings () {
  # Make sure the variable we need are initialized

  SORT_KEY=${SORT_KEY:-1}
  SORT_ARGS=${SORT_ARGS:-''}
  
  NAME_FILTER=${NAME_FILTER:-''}
  LOCATION_FILTER=${LOCATION_FILTER:-''}
  PRECEDENCE_FILTER=${PRECEDENCE_FILTER:-''}
  REQUIREMENT_FILTER=${REQUIREMENT_FILTER:-''}
  KIND_FILTER=${KIND_FILTER:-''}
  DESCRIPTION_FILTER=${DESCRIPTION_FILTER:-''}

  OUR_USER=${OUR_USER:-''}

  FORMAT=${FORMAT:-'$NAME\;$KIND\;$LOCATION\;$PRECEDENCE\;$REQUIREMENTS\;$DESCRIPTION'};
  
  LISTINGS_LIST=$(list_listings)

  # do the filtering
  cat $LISTINGS_LIST |  grep -v -e "^[[:space:]]*#" -e "^[[:space:]]*$" | sort $SORT_ARGS --key="$SORT_KEY" --field-separator=';' | \
  while read PROFILE; do
    # split fields
    export NAME="`echo $PROFILE | cut --delimiter ';' --fields 1`";
    export KIND="`echo $PROFILE | cut --delimiter ';' --fields 2`";
    export LOCATION="`echo $PROFILE | cut --delimiter ';' --fields 3`";
    export PRECEDENCE="`echo $PROFILE | cut --delimiter ';' --fields 4`";
    export REQUIREMENTS="`echo $PROFILE | cut --delimiter ';' --fields 5`";
    export DESCRIPTION="`echo $PROFILE | cut --delimiter ';' --fields 6`";

    export FILE=`grep -l "^$NAME;" $LISTINGS_LIST`;

    if (test "$PRECEDENCE"x = x); then 
      #unset = lower then anything, so set to insanely low value
      NORM_PRECEDENCE='-999999999999999999';
    else 
      NORM_PRECEDENCE=$PRECEDENCE;
    fi;
    
    # if filters don't match -> go to next profile
    if ( (test "${NAME_FILTER:-''}" != '') && (echo "$NAME" | grep -v "$NAME_FILTER" > /dev/null) ) ||
       ( (test "${LOCATION_FILTER:-''}" != '') && (echo "$LOCATION" | grep -v "$LOCATION_FILTER" > /dev/null) ) ||
       ( (test "${PRECEDENCE_FILTER:-''}" != '') && !(test "$NORM_PRECEDENCE" $PRECEDENCE_FILTER) ) ||
       ( (test "${REQUIREMENT_FILTER:-''}" != '') && (echo "$REQUIREMENTS" | grep -v "$REQUIREMENT_FILTER" > /dev/null) ) ||
       ( (test "${KIND_FILTER:-''}" != '') && (echo "$KIND" | grep -v "$KIND_FILTER" > /dev/null) ) ||
       ( (test "${DESCRIPTION_FILTER:-''}" != '') && (echo "$DESCRIPTION" | grep -v "$DESCRIPTION_FILTER" > /dev/null) ); then
      continue;
    fi;  

    # if we have a username to match for, and requirements are not met
    if (test "$OUR_USER" != '') && 
       !(test_profile_requirements "$PROFILE" "$OUR_USER"); then
       # -> go to next profile
       continue;
    fi;  

    # if we get here output the profile's information in the requested format
    echo $(sh -c "echo $FORMAT");
  done;
}  
