#!/bin/ksh
#
# ident "@(#)utpamcfg.sh	1.3 03/09/11 SMI"
#
# Copyright 2003 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.
#
    mod="`basename $0`"
    pam_conf="/etc/pam.conf"
    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
	    echo "$mod: $fn does not exist"
	    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
    #
    function list {

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

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

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

	egrep "$tag" $conffile | egrep "$client "

	awk "{
	    if (\$1 == \"$client\")
		printf( \$0 \"\n\" );
	    }" $conffile 2>/dev/null
    }

    #
    # 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]"

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

	awk "{
	    if (!(\$1 == \"$client\"))
		printf( \$0 \"\n\" );
	    }" $conffile >$tmpfile 2>/dev/null

	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"

	# 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]"

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

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

	#
	# Create the PAM stack for this client.
	# create_stack returns the stack in $client_stack
	#
	create_stack "$conffile" "$client" "$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" "$module" "$modargs" "$client_stack"
	fi

	# add in optional tag
	if [ "$tag" != "" ] ; then
	    add_tag "$client" "$tag" "$client_stack"
	fi

	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 client template_client
    #
    #	returns:
    #	    client stack in $client_stack variable
    #
    function create_stack {

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

	status=0

	for cname in $template $default_template_client
	  do
	    debug "create_stack" "cname = [$cname]"

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

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

	  done

	# default template stack
	if [ $newpam == true ] ; then
	    client_stack="$client auth requisite pam_authtok_get.so.1\n"
	    client_stack="${client_stack}$client auth required  pam_dhkeys.so.1\n"
	    client_stack="${client_stack}$client auth required  pam_unix_auth.so.1"
	else
	    client_stack="$client auth required /usr/lib/security/\$ISA/pam_unix.so.1"
	fi

	return
    }

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

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

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

	client_stack="$client 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
	awk "{
	    if ((\$1 == \"$client\"))
		printf( \$0 \"\n\" );
	    }" $conffile >$savefile 2>/dev/null

	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"

	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"

	# echo the new module to the conffile
	debug "add_this_module" "sending: $tag"
	debug "add_this_module" "sending: $client auth $flag $PAM_module $PAM_module_args"

	add_tag "$client" "$tag" ""
	client_stack="${client_stack}$client auth $flag $PAM_module $PAM_module_args"
	
	echo $client_stack >> $conffile
	
	# append old configurations to the file
	cat $savefile >> $conffile
	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="$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

    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 "" "    New PAM type: [$newpam]"
    debug

    case $config in
	    true) config "$pam_conf" "$PAM_client" "$PAM_module" "$PAM_module_args" "$tag" "$template_client" ;;
	    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" ;;

	    none) usage ; exit 1 ;;
    esac

    # cleanup any schmutz left over
    cleanup

    exit $status
