#!/bin/sh

. /etc/control.d/functions

CONFIG="${CONFIG:-/etc/security/pam_pkcs11/pam_pkcs11.conf}"
PROFILES_DIR="${PROFILES_DIR:-/etc/security/pam_pkcs11/profiles}"
SUMMARY="${SUMMARY:-PKCS11 profile selector}"

new_summary "$SUMMARY"

read_desc() {
	local profile="$1"; shift
	sed -n -e '/^#/ {
		s/^#[[:space:]]*//;
		p;
		q
	}' -- "$profile"
}

_read_values() {
    local profile="$1"; shift
    sed -n -e '/^[[:space:]]*pam_pkcs11[[:space:]]*{/,$ {
		/^[[:space:]]*pam_pkcs11[[:space:]]*{/ d;
        /^[[:space:]]*[^[:space:]{]*[^{]*[^[:space:]{]\+[[:space:]]*{/,/}/ {
            /^[[:space:]]*[^[:space:]{]*[^{]*[^[:space:]{]\+[[:space:]]*{/ {
                s/^[[:space:]]*\([^[:space:]{]*[^{]*[^[:space:]{]\+\)[[:space:]]*{.*$/    subname = \1;/;
                s/\\/\\\\/g; s/"/\\"/g;
                s/= \(.*\);$/= "\1";/;
                p; d
            }
            /^[[:space:]]*\([^=[:space:]]\+\)[[:space:]]*=[[:space:]]*[^#]\+;/ {
                s/^[[:space:]]*\([^=[:space:]]\+\)[[:space:]]*=[[:space:]]*\(.*[^[:space:]]\)[[:space:]]*;$/    subvalues[subname][\1] = \2;/;
                s/\\/\\\\/g; s/"/\\"/g;
                s/subvalues\[subname\]\[\(.*\)\] = \(.*\);$/subvalues[subname]["\1"] = "\2";/;
                p; d
            }
            /}/ d
        }
        /^[[:space:]]*[^=[:space:]]\+[[:space:]]*=[[:space:]]*[^#]\+;/ {
		    s/^[[:space:]]*\([^=[:space:]]\+\)[[:space:]]*=[[:space:]]*\([^#]\+\);.*$/    values[\1] = \2;/;
            s/\\/\\\\/g; s/"/\\"/g;
            s/values\[\(.*\)\] = \(.*\);$/values["\1"] = "\2";/;
            p; d
        }
        /}/ q
	}' -- "$profile"
}

make_parser_begin() {
	local profile="$1"; shift
	local name="${profile##*/}"
    case "$name" in
        *.*)
            name="$name"
            ;;
        *)
            name="$name.begin.awk"
            ;;
    esac

	cat <<EOF >"$workdir/$name"
BEGIN {
    pam_pkcs11 = 0;
    subconfig = 0;
EOF

    _read_values "$profile" >>"$workdir/$name"
    
	cat <<EOF >>"$workdir/$name"
}
EOF
}

make_parser() {
    local name="${1:-.parser.awk}"
    cat <<EOF >"$workdir/$name"
/^[[:space:]]*#/ {
    if (OUT) { print; }
    next;
}

/^[[:space:]]*[^[:space:]{]*[^{]*[^[:space:]{]+[[:space:]]*{/ {
    if (\$1 == "pam_pkcs11" && \$2 == "{") {
        pam_pkcs11 = 1;
    } else if (pam_pkcs11) {
        subconfig = 1;
        subname = \$1;
        for (i = 2; i < NF && \$i != "{"; i++) {
            subname = subname " " \$i;
        }
    }
    if (OUT) { print; }
    next;
}

/^[[:space:]]*[^=[:space:]]+[[:space:]]*=[[:space:]]*[^#]+;/ {
    value = gensub(/^[[:space:]]*[^=[:space:]]+[[:space:]]*=[[:space:]]*([^#]+)[[:space:]]*;.*\$/, "\\\\1", 1, \$0);
    name = gensub(/^[[:space:]]*([^=[:space:]]+)[[:space:]]*=[[:space:]]*[^#]+[[:space:]]*;.*\$/, "\\\\1", 1, \$0);
    if (subconfig) {
        if (subname in subvalues && name in subvalues[subname]) {
            if (OUT && subvalues[subname][name] != "NONE") {
                print "    " name " = " subvalues[subname][name] ";";
            }
            if (!CMP || value == subvalues[subname][name]) {
                delete subvalues[subname][name];
            }
            if (!CMP) {
                existing[subname] = 1;
            }
        } else {
            if (OUT) { print; }
        }
    } else {
        if (name in values) {
            if (OUT && values[name] != "NONE") {
                print "  " name " = " values[name] ";";
            }
            if (!CMP || value == values[name]) {
                delete values[name];
            }
        } else {
            if (OUT) { print; }
        }
    }
    next;
}

/}/ {
    if (subconfig) {
        subconfig = 0;
        if (subname in subvalues) {
            for (key in subvalues[subname]) {
                if (subvalues[subname][key] != "NONE") {
                    if (OUT) {
                        print "    " key " = " subvalues[subname][key] ";";
                    }
                    if (!CMP) {
                        delete subvalues[subname][key];
                    }
                } else {
                    delete subvalues[subname][key];
                }
            }
            if (!CMP) {
                existing[subname] = 1;
            }
        }
    } else if (pam_pkcs11) {
        pam_pkcs11 = 0;
        for (key in values) {
            if (values[key] != "NONE") {
                if (OUT) {
                    print "  " key " = " values[key] ";";
                }
                if (!CMP) {
                    delete values[key];
                }
            } else {
                delete values[key];
            }
        }
        for (subname in subvalues) {
            if (!(subname in existing)) {
                key_count = 0;
                for (key in subvalues[subname]) {
                    if (subvalues[subname][key] == "NONE") {
                        delete subvalues[subname][key];
                    } else {
                        key_count = key_count + 1;
                    }
                }
                if (key_count > 0) {
                    if (OUT) {
                        print "";
                        print "  " subname " {";
                    }
                    for (key in subvalues[subname]) {
                        if (OUT) {
                            print "    " key " = " subvalues[subname][key] ";";
                        }
                        if (!CMP) {
                            delete subvalues[subname][key];
                        }
                    }
                    if (OUT) {
                        print "  }";
                    }
                }
            }
            if (!CMP) {
                delete subvalues[subname];
            }
        }
    }
    if (OUT) { print; }
    next;
}

{ if (OUT) { print; } }

END {
    value_count = 0;
    for (key in values) { value_count++ };
    subvalue_count = 0;
    for (subname in subvalues) {
        for (key in subvalues[subname]) {
            subvalue_count++;
        }
    }
    diff = 0;
    if (value_count > 0 || subvalue_count > 0) {
        diff = 1;
        print "pam_pkcs11 {";
        for (key in values) {
            print "  " key " = " values[key] ";";
        }
        for (subname in subvalues) {
            subvalue_count = 0;
            for (key in subvalues[subname]) {
                subvalue_count++;
            }
            if (subvalue_count > 0) {
                print "";
                print "  " subname " {";
                for (key in subvalues[subname]) {
                    print "    " key " = " subvalues[subname][key] ";";
                }
                print "  }";
            }
        }
        print "}";
    }
    if (diff) {
        exit 1;
    }
}
EOF
}

read_current() {
	local name=

    make_parser
	for_each_profile make_parser_begin
	
	for f in $workdir/*.begin.awk; do
		name="${f##*/}"; name="${name%.begin.awk}"
		if awk -f "$f" -f "$workdir/.parser.awk" -v 'CMP=1' "$CONFIG" 1>/dev/null 2>&1; then
			echo "$name"
			return 0
		fi
	done
	
	echo 'custom'
}

update() {
	local profile="$1"; shift
	local name="${profile##*/}"
    make_parser
    make_parser_begin "$profile"
	awk -f "$workdir/$name.begin.awk" -f "$workdir/.parser.awk" \
        -v 'OUT=1' -- "$CONFIG" \
        >"$workdir/.config.updated" && \
    mv "$workdir/.config.updated" "$CONFIG"
}

use_profile() {
	local profile="${PROFILES_DIR%/}/$1"; shift
	if [ -e "$profile" ]; then
		update "$profile" && \
		[ -n "${SKIP_MODULE:-}" ] || use_module "$profile"
	else
		echo "ERROR: Profile not found: $1" >&2
		return 1
	fi
}

read_module() {
	local profile="$1"; shift
	sed -n -e '/^[[:space:]]*use_pkcs11_module[[:space:]]*=/ {
		s/^[[:space:]]*use_pkcs11_module[[:space:]]*=[[:space:]]*//;
		s/;.*$//;
		p;
		q
	}' -- "$profile"
}

use_module() {
	local module="$(read_module "$1")"
    if [ -n "$module" ]; then
	    if ! control pam-pkcs11-module "$module"; then
		    echo "Error selecting module: ${module##*/}" >&2
		    return 1
	    fi
    fi
}

for_each_profile() {
	local func="$1"; shift
	if [ -d $PROFILES_DIR ]; then
		for p in ${PROFILES_DIR%/}/*; do
			case "${p##*/}" in
				*~|.*|*.rpmnew|*.rpmold)
					;;
				*)
					"$func" "$p" "$@"
					;;
			esac
		done
	fi
}

register_profile() {
	new_help "${1##*/}" "$(read_desc "$1")"
}

## Main

REQUEST="$*"

workdir="$(mktemp -d --tmpdir "${0##*/}.XXXX")"
trap "rm -rf \"$workdir\"" EXIT

for_each_profile register_profile

case "$REQUEST" in
	help|'help '*)
		control_help "${REQUEST#help}"
		;;
	list)
		control_list
		;;
	summary)
		control_summary
		;;
	status)
		read_current
		;;
	*)
		use_profile "$REQUEST"
		;;
esac
