#!/bin/bash
# Documentation is included at the end of this file.
# Original Author: Gregory Allan, copyright 2005, all rights
# reserved. This script is licensed under the GPL General
# Public License. If you improve it, I ask that you please send
# me a copy at gregory@lawfulpath.com. I know you don't have to, 
# but I will appreciate it.

# Set user fields
inc=5			# set same as cron probe (leave alone)
timeMax=150		# default time limit
absolMax=840		# absolute Max 14hrs
targetUser=user		# username to be restricted
counterLog="/home/share/$targetUser-counter.log"
statusLog="/home/share/$targetUser-status.log"
activeGroups="$targetUser,games,restrict"
expiredGroups="$targetUser,games"
targetProcess[1]="gaim"
targetProcess[2]="galeon"
targetProcess[3]="konqueror"
targetProcess[4]="kopete"
targetProcess[5]="mozilla"
targetProcess[6]="ymessenger"
usermodPath="/usr/sbin/usermod"

# note: I discovered that KDE can be made to run konqueror without
# it showing up in the process list. If, for instance, your user
# runs kmail, and clicks on a link, konqueror will open. The user
# can then open any other site. As a solution, you can restrict
# kmail, but there are probably other ways to initiate konqueror
# where it won't show up. If your user gets too sophisticated you'll
# probably have to restrict bash.

# ***** Functions *******

getCmd ()
{ # Check for command line parameter
  # returns $act, where 0 request off, 1 requests on, p requests probe, 
  #         3 requests counter reset only; 4 requests status, 
  #         h requests help
  # returns $reset, where 1 demands a reset, but 0 does not

  case "$arg1" in		# Parse $arg1
	0 ) act=0 ;;
	1 ) act=1 ;;
	p ) act=p ;;
	r ) act=3 ;;
	s ) act=4 ;;
	* ) act=h ;;
  esac

  reset=0			# Zero $reset
  if [ $act = "3" ]		# Set reset if requested in $arg1
	then
	  reset=1
  fi
	
  			
  empty="x"			# Concatenate $arg2 with x to guarantee value
  look="$arg2$empty"
  if [ $look = "rx" ]		# Check if $arg2 requests reset
	then
	  reset=1
  fi

   # These statements debug this function
   # echo "getCmd: \$arg1 = $arg1"
   # echo "getCmd: \$act = $act"
   # echo "getCmd: \$arg2 = $arg2"
   # echo "getCmd: \$reset = $reset"
   # echo "getCmd: \$look = $look"
   # exit
}
	
printHelp ()
{ # Display command line options
  echo ""
  echo "*** $0 Help:"
  echo "This script controls restricted services for $targetUser."
  echo "If it is run without any command line options, or with any"
  echo "options other than those shown here, this help screen will"
  echo "be displayed. The second parameter is optional."
  echo "Syntax: $0 option1 [option2] [option3]"
  echo "Option 1:"
  echo "   0  turn service off"
  echo "   1  turn service on"
  echo "   p  probe for service use; increment if true"
  echo "   r  reset counter"
  echo "   s  print status (is service turned on or off)"
  echo "Option 2:"
  echo "   r  reset counter"
  echo "Option 3:"
  echo "   x, where x=(minutes); sets counter to x if included"
  echo ""
  echo "Note that if option p is selected, no other options will be"
  echo "considered."
  echo ""

  exit 0
}

serviceOn ()
{ # Turn on user services
  # Works by adding the restricted group to user's groups.
  $usermodPath -G $activeGroups $targetUser
  echo "1" > $statusLog
}

serviceOff ()
{ # Turn off user services. Only acts if service is currently on.
  # Works by removing restricted group from user's groups
  # User must be logged off for restrictions to take effect.
  # First kill line stops user's running programs nicely.
  # Second kill line logs user off.
  probeServiceStatus
  if [ $statusService = 1 ]
	then
	  $usermodPath -G $expiredGroups $targetUser
	  kill -15 `ps -u $targetUser`
	  sleep 30
	  kill  -9 `ps -u $targetUser | grep bash`
	  echo "0" > $statusLog
  fi
}

probeServiceStatus ()
{ # Check to see if service is currently turned on or off
  # returns $statusService, where 1 is on, or 0 is off
  touch $statusLog		# make sure log file exists
  read test < $statusLog	# get contents of log
  check="x"
  if [ "$test$check" = "1x" ]	# log is not empty AND contains 1
	then
	  statusService=1	# service is currently on
	else
	  statusService=0	# otherwise it's currently off
  fi
}
 
printStatus ()
{ # Display current status for user: 0 or 1
  probeServiceStatus
  echo ""
  echo "$0: Restricted service status for $targetUser: $statusService"
  echo ""
}

resetCounter ()
{ # Reset the counter to $timeMax
  check="x"
  if [ $arg3$check = "x" ]
	then
	  counter=$timeMax
	elif [ $arg3 -gt 5 -a $arg3 -lt $absolMax ]
	  then
		counter=$arg3
	  else
		counter=$timeMax
  fi
  echo $counter > $counterLog		# Write counter to Log
  chmod 644 $counterLog			# Make counter world readable
}

getCounter ()
{ # Get the current counter value
  touch $counterLog			# Make sure counterLog exists
  read counter < $counterLog		# Get current counter value
  check="x"				# Guarantee counter has a value
  if [ "$counter$check" = "x" ]
    then 
	counter=0
  fi
}

decrementCounter ()
{ # Counter begins at $timeMax and decrements to zero
  getCounter				# Get the current counter value
  let "counter = $counter - $inc"	# Decrement counter by value $inc
  echo "$counter" > "$counterLog"	# Write new counter value to log
  chmod 644 $counterLog			# Make counter world readable
}	

probeLoginStatus ()
{ # Test to see if targetUser is currently logged in.
  # returns $statusLogin, where 1=true, or 0=false
  # Not currently used in this script.
  test=`ps -u $targetUser | grep bash`
  check="x"
  if [ "$test$check" = "x" ]
     then 
	statusLogin=0
     else 
	statusLogin=1
  fi
}

testExpired ()
{ # Has service-use time expired?
  # Returns $expire, where 1=true and 0=false
  getCounter				# Get current counter value
  if [ $counter -le 0 ]
    then
	expire=1
    else
	expire=0
  fi
}

probeServiceUse ()
{ # Test to see if targetProcesses are being used.
  # returns $statusService, where 1=true or 0=false
  # any targetProcess found true will return true for all
  statusServiceUse=0			# rule out false positives
  elementCount=${#targetProcess[@]}	# get number of processes to test for
  check="x"

  for loop in `seq 1 $elementCount`;
	do
	  test=`ps -u $targetUser | grep ${targetProcess[$loop]}`
	  if [ "$test$check" != "x" ]
	     then 
		statusServiceUse=1
	  fi;
  done
}

probeMain ()
{ # Decides whether counter needs decrementing, and
  # whether or not to switch off restricted services.
  # This function does not return to main body.

  # Only proceed if services are currently turned on
  # Then check if restricted services are in use
  probeServiceStatus
  probeServiceUse
  if [ $statusService = 0 ]; then
	exit 0
  elif [ $statusServiceUse = 1 ]; then
	decrementCounter
	testExpired
	if [ $expire = 1 ]; then
	  serviceOff
	  exit 0
	else
	  exit 0
	fi
  fi
}

# / End Functions

# ***** Program flow begins here *******

umask 022	# Make log files world readable
arg1=$1		# First put command line arguments
arg2=$2		# into useable variables.
arg3=$3

getCmd		# Translate command line arguments

if [ $reset = 1 ]; then
  resetCounter
fi

case $act in	# Parse command line arguments
  p ) probeMain ;;
  h ) printHelp ;;
  1 ) serviceOn ;;
  0 ) serviceOff ;;
  4 ) printStatus ;;
esac

exit 0

# The purpose of this script is to enable restriction of services
# based on the amount of time the services have been used. 
#
# For instance, you may have a teenager who spends all her time
# chatting with friends on IM (the reason I created this), and you 
# want to limit her time per day, so she has a chance to get something
# productive done. Maybe you'd like to limit her to, for example, two 
# hours of chat per day.
#
# This script is very flexible. It can easily be configured to
# work with any process(es) you choose, and can be configured 
# for any amount of minutes. It relies on cron, usermod, ps, and a few 
# internal bash commands which should work across most any unix-type 
# system. If you have problems, make sure your path to usermod is
# the same as used in the script.
#
# Here's how it works:
#
# Access to services is controlled with group permissions. Create a
# group for restricted services. I called mine "restrict". Whichever
# services you are restricting, assign them that group ownership, and
# chmod them 750. Be sure to specify each service in the array
# $targetProcess[]. Add or subtract $targetProcess[] lines as needed.
#
# There are a number of user fields in the beginning which must
# be assigned values.  This script is designed to be used with only 
# one user. If you want to restrict several users, create a new directory
# for each, and place a copy of this script in each directory. I use
# /usr/local/bin/$targetUser, but you can use whatever you want.
#
# The difference between $activeGroups and $expiredGroups is that
# the former contains the restricted group, while the latter does not.
# 
# Set timeMax to the number of minutes allowed before the service is cut 
# off.  Set $inc to 5, unless you have a good reason to do otherwise.
# $inc must match the interval you probe with cron.
#
# Reference timeLock in root's crontab, set to execute once every $inc
# minutes. A counter will be decremented from $timeMax down to zero,
# at which point the restricted group will be removed from the user's
# group list, the user's running processes will be gracefully shut down,
# and the user will be logged off the system. This is necessary for the
# revised group list to take effect. The user may log back in again and
# have use of non-restricted services.
#
# Here's an explanation of my crontab entries:
#
# 00 8 * * * /usr/local/bin/restrict/nicole/timeLock 1 r
# (Every day at 8am turn on services and reset the counter)
#
# */5 8-21 * * * /usr/local/bin/restrict/nicole/timeLock p
# (Every day between 8am-10pm, probe every 5 minutes; decrement counter
# only if restricted services are being used, and restrict services once
# the time has expired.)
#
# 00 22 * * * /usr/local/bin/restrict/nicole/timeLock 0
# (Every night at 10pm, restrict services whether time has expired or not.)
#
# Note that if time has already expired before 10pm, user will not be
# logged off at 10pm. This is easily changed by commenting out the 
# conditional test in $serviceOff. You can easily modify this to lock out
# the user entirely during off hours, by making bash a restricted service.
#
# Provision is made for a third option, to be used with the r (reset)
# option, to custom set timeMax. This way you can easily modify crontab
# to give the user more time on weekends, without changing your config for
# the rest of the week, or give the user more time by entering a simple
# command in the console. Example: timeLock 1 r 60 (grants one hour)
#
# The $counterLog has permissions 644, so the user can check her time
# remaining. Save the following script to a seperate file and put in her path:
#
#!/bin/bash
# This script works with timeLock to print the time remaining
# before services are restricted.
# targetUser="nicole"
# counterLog="/var/log/$targetUser-counter.log"
# check="x"
#
# read tRemain < $counterLog
# if [ $tRemain$check = "x" ]; then
#        message="You are out of time."
# elif [ $tRemain -le 0 ]; then
#        message="You are out of time."
# else
#        message=$tRemain
# fi
#
# echo ""
# echo "timeLock Status Report"
# echo -n "Time remaining: "
# echo "$message"
# echo ""
#
# exit 0#
# --end script
#
# I am a complete novice at writing scripts, and I'm sure it
# shows. But this script is useful to me, and hopefully will
# be useful to others. In particular, the "kill" lines in
# function serviceOff generate a number of harmless errors, but
# it gets the job done. I'm sure there's a cleaner way to do this.
# I welcome suggestions and improvements.

EOF
