#!/bin/sh

po_domain="alterator-pkcs11"
alterator_api_version=1

. alterator-sh-functions
. shell-signal
. shell-quote

cleanup_function()
{
	[ -z "$tmpdir" ] ||
		rm -rf -- "$tmpdir"
}

tmpdir="$(mktemp -dt "${0##*/}.XXXXXXXX")"
set_cleanup_handler cleanup_function

mount_dir="/mnt/${0##*/}"
cert_dir="/etc/security/pam_pkcs11/cacerts"

### physical device setup

__read_var()
{
	local line="$1";shift
	local name="$1";shift
	local v="${line#* $name=\"}"
	echo "${v%%\"*}"
}

__read_priority()
{
	local dev_name="$1";shift
	if [ -d "/sys/class/block/$dev_name/md" ];then
		echo "2"
	elif [ -d "/sys/devices/virtual/block/$dev_name" ];then
		echo "1"
	else
		echo "0"
	fi
}

__list_partitions()
{
	local line= fs_uuid= fs_type=
	local dev_major_minor= dev_name= dev_priority=
	blkid -c /dev/null|
		while read line; do
			dev_name="${line%%:*}"
			dev_name="$(udevadm info --query name --name "$dev_name")"
			dev_major_minor="$(cat /sys/class/block/$dev_name/dev)"

			fs_uuid="$(__read_var "$line" UUID)"
			fs_type="$(__read_var "$line" TYPE)"

			case "$fs_type" in
				ext2|ext3|ext4|xfs|ntfs|vfat) ;;
				*) continue ;;
			esac

			dev_priority="$(__read_priority "$dev_name")"

			printf '%s\t%s\t%s\t%s\t%s\n' \
				"$dev_major_minor" \
				"$fs_uuid" "$dev_name" "$dev_priority" \
				"$fs_type"
		done|
			sort -k1,1
}

__read_major_minor()
{
	local data="$(stat -L -c '%t:%T' "$1")"
	local major="${data%:*}"
	local minor="${data#*:}"

	printf '%s:%s' "$((0x$major))" "$((0x$minor))"
}

__list_mpoints()
{
	local device= mpoint= line= uuid= tags=
	while read device mpoint line; do
		[ -b "$device" ] || continue

		printf '%s\t%s\n' "$(__read_major_minor "$device")" "$mpoint"
	done </proc/mounts|
		sort -k1,1
}

__device_list()
{
	local tempfile="$(mktemp -t scan-partitions.XXXX)"

	__list_mpoints >"$tempfile"
	__list_partitions|
		join -a1 -j1 -  "$tempfile"|
			awk '
{
	id=$2;
	dv=$3;
	pr=$4;
	fs=$5;
	mp=$6;

	if (mp) pr+=4;
	if (pr >= pr_list[id]) { pr_list[id]=pr; mp_list[id]=mp; fs_list[id]=fs; dv_list[id]=dv } 
}
END { for (i in dv_list) { printf "%s\t%s\t%s\t%s\n",i,dv_list[i],fs_list[i],mp_list[i]; } }
'
	rm -f -- "$tempfile"
}

cert_cn()
{
	local file="$1"
	local subj cn

	subj="$(openssl x509 -in "$file" -noout -subject)"
	[ -n "$subj" -a -z "${subj##*/CN=*}" ] || return
	cn="${subj##*/CN=}"
	cn="${cn%%/*}"

	printf '%s' "$cn"
}

__fetch_certs()
{
	local dev="$1" dir="$2"
	local file cn

	find -L "$dir" -maxdepth 1 -mindepth 1 -type f -name '*.pem' -printf '%P\n' |
		while read file; do
			[ -f "$dir/$file" ] || continue
			openssl verify -CAfile "$dir/$file" "$dir/$file" >&2 || continue
			cn="$(cert_cn "$dir/$file"| tr -cs 'A-Za-z0-9_-' _)"
			[ -n "$cn" ] || continue

			cat "$dir/$file" > "$tmpdir/$dev-$file"
		done
}

update_cert_cache()
{
	local uuid dev fs mp need_umount

	find "$tmpdir" -type f -delete
	__device_list|
		while read uuid dev fs mp; do
			need_umount=

			if [ -z "$mp" ]; then
				mp="$mount_dir"
				mkdir -p -- "$mp"
				[ "$fs" != "ntfs" ] || fs="ntfs-3g"

				mount -t "$fs" -r "/dev/$dev" "$mp" >&2 || continue
				need_umount=1
			fi

			__fetch_certs "$dev" "$mp"

			[ -z "$need_umount" ] ||
				umount "$mp"
		done
}

update_cert_cache >/dev/null
c_rehash "$cert_dir" >/dev/null

on_message() {
	local cn subj tmp_passwd username dir file line

	set | grep '^in_'
	case "$in_action" in
		list)
			dir=

			case "$in__objects" in
				avail_certs)
					dir="$tmpdir"
					;;
				certs)
					dir="$cert_dir"
					;;
			esac

			[ -n "$dir" ] || return

			find -L "$dir" -maxdepth 1 -mindepth 1 -type f -name '*.pem' -printf '%P\n' |
				while read file; do
					cn="$(cert_cn "$dir/$file")"
					subj="$(openssl x509 -in "$dir/$file" -noout -subject 2>/dev/null | cut -d= -f 2- | sed 's,\(^[[:blank:]]\+\|[[:blank:]]\+$\),,g')"
					tmp_passwd="$(mktemp -t)"
					getent passwd > "$tmp_passwd"
					username=
					while read line; do
						case "$line" in
							*:*:*:*:"$cn":*:*)
								username="${line%%:*}"
								break
								;;
						esac
					done <"$tmp_passwd"
					rm -f -- "$tmp_passwd"

					write_table_item \
						name "$file" \
						subject "$subj" \
						user "$username"
				done
			;;
		read)
			;;
		write)
			case "$in__objects" in
				avail_certs)
					if [ -n "$in_refresh" ]; then
						update_cert_cache
					elif [ -n "$in_add" -a -n "$in_cert" ]; then
						cn="$(cert_cn "$tmpdir/$in_cert"| tr -cs 'A-Za-z0-9_-' _)"
						cat "$tmpdir/$in_cert" > "$cert_dir/$cn.pem"
						c_rehash "$cert_dir"
					fi
					;;
				certs)
					if [ -n "$in_delete" -a -n "$in_cert" ]; then
						rm -f "$cert_dir/$in_cert"
						c_rehash "$cert_dir"
					fi
					;;
			esac
			;;
	esac
}

message_loop
# vim: set ts=4:
