#!/bin/ksh -p
#
# ident "@(#)utpamcfg.sh	1.7 05/07/18 SMI"
#
# Copyright 2003, 2005 Sun Microsystems, Inc.  All rights reserved.
#
#
# utpamcfg - This script [un]configures the PAM configuration
#		file (normally /etc/pam.conf).
#
#    usage:
#
#	utpamcfg -c client [-m module [-M modargs]] [-t template] [-T tag] [-n true|false]
#
#		-c client
#		    Configure PAM stack for specified PAM client.
#		-m module
#		    Add optional PAM module specified to the top
#		    of the PAM auth stack for this client's entry.
#		-M modargs
#		    Arguments to provide to PAM module specified by
#		    the -m switch.
#		-t template
#		    Use the PAM client specified as the template
#		    client PAM stack (default is to use "other").
#		-T tag
#		    Use comment tag specified (default is to use
#		    "SunRay Server Software" (note no spacing
#		    between "Sun" and "Ray").
#		-n true|false
#		     true: use new pam_unix stack (S9 and later)
#		    false: use S8 pam_unix stack
#
#	utpamcfg -a client [-m module [-M modargs]] [-t template] [-T tag] [-n true|false] [-f flag]
#
#               -a client
#                  Add module for client to the top of the stack
#               -f flag
#                   The flag to be used for the pam line, eg: requisite, 
#                   sufficient etc
#		-c client
#		    Configure PAM stack for specified PAM client.
#		-m module
#		    Add optional PAM module specified to the top
#		    of the PAM auth stack for this client's entry.
#		-M modargs
#		    Arguments to provide to PAM module specified by
#		    the -m switch.
#		-t template
#		    Use the PAM client specified as the template
#		    client PAM stack (default is to use "other").
#		-T tag
#		    Use comment tag specified (default is to use
#		    "SunRay Server Software" (note no spacing
#		    between "Sun" and "Ray").
#		-n true|false
#		     true: use new pam_unix stack (S9 and later)
#		    false: use S8 pam_unix stack

#	utpamcfg -u client [-T tag]
#
#		-u client
#		    Unconfigure PAM stack for specified PAM client.
#		-T tag
#		    Remove comment tag specified (default same
#		    as above).
#
#	utpamcfg -l client [-T tag]
#
#		-l client
#		    List PAM stack for specified PAM client.
#		-T tag
#		    Display comment tag specified (default same
#		    as above).
#
#    undocumented switches:
#
#	utpamcfg [-d] [-P conffile]
#
#		-d
#		    Enables debugging messages.
#		-P conffile
#		    Specifies an alternative PAM configuration file.
#

# DESIGN NOTE: This script now handles /etc/pam.conf format
# configurations as well as /etc/pam.d/ directory-based
# configurations.  The format of the files in an /etc/pam.d/
# configuration is idential to the /etc/pam.conf format, except that
# the first field on each line in /etc/pam.conf is the "service name"
# (referred to in this script generally as the "client"), whereas in
# /etc/pam.d the service name is the name of the file itself, and that
# field is not included in the contents.  Systems (like Linux) that
# allow /etc/pam.d/ configurations also allow /etc/pam.conf
# configurations, and the rule is that if /etc/pam.d exists, then
# /etc/pam.conf is ignored.
#
# However, on Solaris /etc/pam.d handling is disabled, because some
# layered products create a spurious /etc/pam.d directory


    mod="`basename $0`"
    pam_conf_sys="/etc/pam.conf"
    pam_dir="/etc/pam.d"
    if [ -d $pam_dir -a "`uname -s`" != SunOS ]; then
	isdir=true
    else
	isdir=false
    fi
    template_client=""
    default_template_client="other"
    tag="SunRay Server Software"
    flag="sufficient"

    config="none"
    PAM_module=""
    PAM_module_args=""
    debugme=false;
    tmpfile="/tmp/$mod.$$"
    savefile="/tmp/$mod.save.$$"
    newpam=false
    
    # set umask so that our file creation is done with the correct perms
    umask 022

    # Setup trap handler to cleanup
    trap "cleanup; exit" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 

    #
    # Display command line usage and optional descriptive text.
    #
    function usage {

	msg="$1"

	echo "usage: $mod -c client [-m module [-M modargs] [-t template] [-T tag] [-n true|false]"
	echo "usage: $mod -u client [-T tag]"
	echo "usage: $mod -l client"
	echo "usage: $mod -a -c client [-m module [-M modargs] [-T tag] [-f flag]"


	if [ "$msg" != "" ] ; then
	    echo "usage: $mod $msg"
	fi

    }

    #
    # Clean up any leftovers in preperation to exit.
    #
    function cleanup {

	ss="$status"

	debug "cleanup" "Swish swish swish. Wipe wipe wipe."

	rm -f $tmpfile
	rm -f $savefile

	debug "cleanup" "Garbage collected and expunged."

	status="$ss"
    }

    #
    # Display messages if debug is enabled
    #
    #	calling:
    #	    debug facility message
    #
    function debug {

	fac="$1"
	msg="$2"

	sep=": "

	if [ "$fac" == "" ] ; then
	    sep=""
	fi

	if [ $debugme == true ] ; then
	    echo "$fac$sep$msg"
	fi

    }

    #
    # Check if filename refers to a file and if it is writable
    # and readable.
    #
    #	calling:
    #	    checkfile filename mode
    #
    #	Notes: If mode == "-w" no write accessability check will
    #		be done on the specified filename.
    #
    function checkfile {

	fn="$1"
	mode="$2"

	status=0

	debug "checkfile" "filename=[$fn] mode=[$mode]"

	if [ "$fn" == "" ] ; then
	    echo "$mod: no filename specified"
	    status=100
	    return
	fi

	if [ "$mode" = "-w" ] ; then
	    wrcheck=false
	    wrmsg=""
	else
	    wrcheck=true
	    wrmsg="writable|"
	fi

	if test ! -a "$fn" ; then
	    if $isdir ; then
	    	# in the pam.d case, it's possible that the conffile does
		# not exist.  This is for the case where we are creating a
		# new stack from scratch.
	    	debug "checkfile" "$fn is OK (no file in pam.d directory)"
	    else
	    	echo "$mod: $fn does not exist"
	    fi
	    status=101
	elif test ! -f "$fn" ; then
	    echo "$mod: $fn is not a regular file"
	    status=102
	elif test $wrcheck == true -a ! -w "$fn" ; then
	    echo "$mod: $fn is not writable"
	    status=103
	elif test ! -r "$fn" ; then
	    echo "$mod: $fn is not readable"
	    status=104
	else
	    debug "checkfile" "$fn is OK (exists|regular|${wrmsg}readable)"
	fi

	return
    }

    #
    # List PAM stack for client.
    #
    #	calling:
    #	    list config_file client tag
    #
    # Return non-zero status if stack does not exist
    #
    function list {

	conffile="$1"
	client="$2"
	tag="$3"

	debug "list" "conffile=[$conffile] client=[$client] tag=[$tag]"

	checkfile "$conffile" "-w"
	if [ $status != 0 ] ; then
	    return 1
	fi

	if $isdir ; then
	    cat $conffile
	    status=$?
	else
	    egrep "$tag" $conffile | egrep "[ 	]*$client "

	    OUT=$(awk "
	        {
	    	if (\$1 == \"$client\")
		    printf( \$0 \"\n\" );
		}" $conffile 2>/dev/null
	    )
	    print "$OUT"
	    if [ -z "$OUT" ]; then
		status=1
	    fi
	fi
	return $status
    }

    #
    # Unconfig (remove) the client from the file.
    #
    #	calling:
    #	    unconfig config_file client tag
    #
    function unconfig {

	conffile="$1"
	client="$2"
	tag="$3"

	debug "unconfig" "conffile=[$conffile] client=[$client] tag=[$tag]"

	if $isdir && [ ! -f $conffile ] ; then
	    # Already unconfigured
	    return
	fi

	checkfile "$conffile"
	if [ $status != 0 ] ; then
	    return
	fi

	# unconfig the whole client stack
	if $isdir ; then
	    rm -f $pam_conf
	    return
	else
	    awk "{
	            if (!(\$1 == \"$client\"))
		        printf( \$0 \"\n\" );
	        }" $conffile >$tmpfile 2>/dev/null
	fi

	if [ "$?" = 0 ] ; then
	    egrep -v "$tag" $tmpfile >$conffile
	    rm $tmpfile
	    debug "unconfig" "$conffile updated"
	else
	    rm $tmpfile
	    usage "Error updating $conffile"
	    status=5
	fi

	return
    }

    #
    # Config (add) the client to the file.
    #
    #	calling:
    #	    config config_file client module modargs tag template_client
    #
    function config {

	conffile="$1"
	client="$2"
	module="$3"
	modargs="$4"
	tag="$5"
	template="$6"
	prefix="$7"

	# XXX Note that we must remove any existing "module" line if it
	# XXX is in the template stack so that we can replace it with
	# XXX the (possibily identical) module + modargs line specified.

	debug "config" "conffile=[$conffile] client=[$client]"
	debug "config" "module=[$module] modargs=[$modargs]"
	debug "config" "tag=[$tag] template=[$template] prefix=[$prefix]"

	# first unconfig any old entries
	unconfig "$conffile" "$client" "$tag"
	if [ $status != 0 ] ; then
	    return
	fi

	if [ $isdir == false ]; then
	    # check if the file is still accessable just in case
	    # unconfig did something unsociable to it
	    checkfile "$conffile"
	    if [ $status != 0 ] ; then
		return
	    fi
	fi

	#
	# Create the PAM stack for this client.
	# create_stack returns the stack in $client_stack
	#
	create_stack "$conffile" "$prefix" "$template"
	if [ $status != 0 ] ; then
	    echo "$mod: unable to create stack for $client"
	    return
	fi

	# add in optional module
	if [ "$module" != "" ] ; then
	    add_module "$client" "$prefix" "$module" "$modargs" "$client_stack"
	fi

	add_tag "$client" "$tag" "$client_stack"

	debug
	debug "" "------------------------------"
	debug "config" "PAM stack for $client:"
	debug "" "$client_stack"
	debug "" "------------------------------"
	debug

	# actually update the PAM configuration file
	echo "$client_stack" >>$conffile

	return
    }

    #
    # Create a PAM stack for the client - base it on the template
    # PAM client if that was specified, otherwise just use the entries
    # for the "$default_template_client" client. If that doesn't exist,
    # return some reasonable default stack that should work.
    #
    # Notes: Depends on $newpam to be set to determine which type
    #	of default stack to return.
    #
    #	calling:
    #	    create_stack config_file prefix template_client
    #
    #	returns:
    #	    client stack in $client_stack variable
    #
    function create_stack {

	conffile="$1"
	prefix="$2"
	template="$3"

	status=0

	if $isdir ; then
	  procfiles="$conffile $pam_dir/$default_template_client"
	else
	  procfiles=$conffile
	fi

	for cfile in $procfiles
	  do
	  if $isdir ; then
	      debug "create_stack" "cfile = [$cfile]"
	      if [ -f $cfile ]; then
		  client_stack="`cat $cfile`"
		  debug "create_stack" "found stack in [$cfile] for [$client]"
		  return
	      fi
	  else
	      for cname in $template $default_template_client
		do
		debug "create_stack" "cname = [$cname] cfile = [$cfile]"

		awk "{
		    if (\$1 == \"$cname\") {
			    printf(\"${prefix}\");
			    for (i = 2; i <= NF; i++)
			    printf(\$i \" \");
			    printf(\"\n\");
			}
		}" $cfile 2>/dev/null >$tmpfile

		if [ "`wc -l $tmpfile | awk '{print $1}'`" -gt 0 ] ; then
		    client_stack="`cat $tmpfile`"
		    debug "create_stack" "found stack in [$cfile] for [$cname]"
		    return
		fi
	      done
	    fi
	done

	# default template stack - couldn't find template or "other"
	# to copy.
	# XXX This pre-existing logic is pretty bad, actually - we
	# shouldn't be forcing any particular stack since the overall
	# stack pattern is not stable from Solaris release to release,
	# let alone Linux.  What's "new" for S9 will be old for S9+
	# ...  The good news is that for Linux this case never arises,
	# there always exists an "other" stack (probably true for
	# Solaris these days also).
	if [ $newpam == true ] ; then
	    client_stack="${prefix}auth requisite pam_authtok_get.so.1\n"
	    client_stack="${client_stack}${prefix}auth required  pam_dhkeys.so.1\n"
	    client_stack="${client_stack}${prefix}auth required  pam_unix_auth.so.1"
	else
	    client_stack="${prefix}auth required /usr/lib/security/\$ISA/pam_unix.so.1"
	fi

	return
    }

    #
    # Add in the optional module.
    #
    #	calling:
    #	    add_module client prefix module modargs client_stack
    #
    #	returns:
    #	    updated client stack in $client_stack variable
    #
    function add_module {

	client="$1"
	prefix="$2"
	module="$3"
	modargs="$4"
	cs="$5"

	debug "add_module" "client=[$client] prefix=[$prefix] module=[$module] modargs=[$modargs]"

	client_stack="${prefix}auth sufficient $module $modargs\n$cs"

	return
    }

    #
    # Add in the optional tag as a comment.
    #
    #	calling:
    #	    add_tag client tag client_stack
    #
    #	returns:
    #	    updated client stack in $client_stack variable
    #
    function add_tag {

	client="$1"
	tag="$2"
	cs="$3"

	debug "add_tag" "client=[$client] tag=[$tag]"

	client_stack="# added to $client by $tag\n$cs"

	return
    }

    #
    # Save the current configuration for the given client from
    # the configuration file
    #
    #	calling:
    #	    saveconfig configfile client
    #
    #
    function saveconfig {

	conffile="$1"
	client="$2"

	debug "saveconfig" "conffile=[$conffile] client=[$client]"

	checkfile "$conffile"
	if [ $status != 0 ] ; then
	    return
	fi
	if $isdir ; then
	    cp $conffile $savefile
	else
	    awk "{
		if ((\$1 == \"$client\"))
		    printf( \$0 \"\n\" );
		}" $conffile >$savefile 2>/dev/null
	fi
	return
    }

    #
    # Add in a module to the top of the stack for the given 
    # client. Can be enhanced to say at what position, currently
    # always adds to the top of the stack
    #
    function add_this_module {

	conffile="$1"
	client="$2"
	PAM_module="$3"
	PAM_module_args="$4"
	tag="$5"
	flag="$6"
	prefix="$7"

	debug "add_this_module" "call saveconfig"

	# save the current configuration for the client
	saveconfig "$conffile" "$client"

	# unconfigure the client from the conf file
	unconfig "$conffile" "$client" "$tag"

	debug "add_this_module" "sending: $tag"
	add_tag "$client" "$tag" ""

	debug "add_this_module" "sending: $client auth $flag $PAM_module $PAM_module_args"
	client_stack="${client_stack}${prefix}auth $flag $PAM_module $PAM_module_args"
	# echo the new module to the conffile
	if $isdir ; then
	    if [ -f $savefile ]; then
	    	# keep the pam version at the top of the file
	    	pam_ver="$(grep '^#%PAM' $savefile)"
	    	echo "$pam_ver" >> $conffile
	    fi
	else
	    # set a version that won't match the filter below
	    pam_ver="HighlyUnlikelyString!%!"
	fi
	echo $client_stack >> $conffile
	
	# append old configurations to the file
	# strip out any old tags and the pam version (emitted at top for pam.d/*)
	if [ -f $savefile ]; then
	    egrep -v "$tag|$pam_ver" $savefile >> $conffile
	fi

	rm -f $savefile

	return
    }

    #
    # Main code starts here
    #
    while getopts c:m:M:u:t:T:dP:n:l:a:f: arg 2>/dev/null
      do

	case $arg in
	    a) PAM_client="$OPTARG" ; config=add ;;
	    c) PAM_client="$OPTARG" ; config=true ;;
	    m) PAM_module="$OPTARG" ;;
	    M) PAM_module_args="$OPTARG" ;;
	    u) PAM_client="$OPTARG" ; config=false ;;
	    t) template_client="$OPTARG" ;;
	    T) tag="$OPTARG" ;;
	    d) debugme=true ;;
	    P) pam_conf_new="$OPTARG" ;;
	    n) newpam="$OPTARG" ;;
	    l) PAM_client="$OPTARG" ; config=list ;;
	    f) flag="$OPTARG" ;;
	   \?) usage ; exit 1 ;;

	esac

      done

    case $newpam in
	t*|T*) newpam=true ;;
	f*|F*) newpam=false ;;
	    *) usage "Invalid option to -n" ; exit 2 ;;
    esac

    if [ -n "$pam_conf_new" ]; then
	pam_conf=$pam_conf_new
	conf_prefix="$PAM_client "
    elif $isdir ; then
	pam_conf=${pam_dir}/${PAM_client}
	conf_prefix=""
    else
	pam_conf=$pam_conf_sys
	conf_prefix="$PAM_client "
    fi

    # Deal with alternate root installs
    if [ -n "$PKG_INSTALL_ROOT" ]; then
	pam_conf=${PKG_INSTALL_ROOT}/${pam_conf}
    fi

    debug
    debug "" "Current Working Parameters:"
    debug "" "    config: [$config]"
    debug "" "    PAM conf file: [$pam_conf]"
    debug "" "    PAM client: [$PAM_client]"
    debug "" "    PAM module: [$PAM_module]"
    debug "" "    PAM module args: [$PAM_module_args]"
    debug "" "    Template client: [$template_client]"
    debug "" "    Default template client: [$default_template_client]"
    debug "" "    Configuration prefix: [$conf_prefix]"
    debug "" "    New PAM type: [$newpam]"
    debug

    status=0

    case $config in
	    true) config "$pam_conf" "$PAM_client" "$PAM_module" "$PAM_module_args" "$tag" "$template_client" "$conf_prefix" ;;
	    false) unconfig "$pam_conf" "$PAM_client" "$tag" ;;
	    list) list "$pam_conf" "$PAM_client" "$tag" ;;
	    add) add_this_module "$pam_conf" "$PAM_client" "$PAM_module" "$PAM_module_args" "$tag" "$flag" "$conf_prefix" ;;

	    none) usage ; exit 1 ;;
    esac

    # cleanup any schmutz left over
    cleanup

    exit $status
