#!/bin/sh

# This file is covered by the GNU General Public License.
# Copyright (C) 2012 Andrew V. Stepanov <stanv@altlinux.org>.
# Copyright (C) 2023 Paul Wolneykien <manowar@altlinux.org>.

# Set serial ports access settigs

# This file is part of alterator-ports-access package


# Require libshell
. shell-error
. shell-quote
. shell-config
. shell-var
. alterator-ports-access-lib.sh

# Config file
readonly UDEV_RULEDIR="/etc/udev/rules.d"
readonly SERIAL_UDEV_RULES="$UDEV_RULEDIR/99-alterator-ports-access-00-serial.rules"
readonly USB_UDEV_RULES="$UDEV_RULEDIR/99-alterator-ports-access-01-usb-auth.rules"
readonly USB_DEVNODE_UDEV_RULES="$UDEV_RULEDIR/99-alterator-ports-access-02-usb-dev.rules"

cleanup_serial_rules() {
	find "$UDEV_RULEDIR" -name '*alterator-ports-access*-serial.rules' -delete
}

cleanup_usb_rules() {
	find "$UDEV_RULEDIR" -name '*alterator-ports-access.rules' -o \
		                 -name '*alterator-ports-access*-usb*.rules' \
		 -delete
}

serial_rules_header() {
    cat << 'EOF'
# This file is covered by the GNU General Public License.
# Generated by alterator-ports-access. Do not edit manually.
EOF
}

print_serial_run() {
    echo -n ", RUN+=\"/usr/bin/setserial /dev/%k uart"
    case "$1" in
        enable|on)
            echo -n ' 16550A'
            ;;
        *)
            echo -n ' none'
            ;;
    esac
    echo "\""
}

udev_trigger_serial() {
    udevadm control -R
    udevadm trigger --subsystem-match="tty"
}

enable_serial() {
    /usr/bin/setserial "/dev/$1" uart 16550A
}

# Don't control serial ports
# KERNEL=="tty[A-Z]*[0-9]|ttymxc[0-9]*|pppox[0-9]*|ircomm[0-9]*|noz[0-9]*|rfcomm[0-9]*", GROUP="uucp"
serial_control_off() {
	cleanup_serial_rules
    serial_rules_header > "$SERIAL_UDEV_RULES"
    udev_trigger_serial
    portsctrl_iterate_serial enable_serial
}

_print_access() {
    [ -z "$1" ] || echo -n ", OWNER=\"$(quote_shell "$1")\""
    [ -z "$2" ] || echo -n ", GROUP=\"$(quote_shell "$2")\""
    [ -z "$3" ] || echo -n ", MODE=\"$(quote_shell "$3")\""
}
print_access() {
    portsctrl_parse_access _print_access "$1"
}

# Apply rules for serial ports

print_serial() {
    local port="$1"
    local enabled="$2"
    local access="$3"

    local onoff=
    if shell_var_is_yes "$enabled"; then
        onoff='on'
    else
        onoff='off'
    fi

    cat <<EOF

SUBSYSTEM=="tty", KERNEL=="$port"$(print_access "$access")$(print_serial_run "$onoff")
EOF
}

serial_control_on() {
	cleanup_serial_rules
    serial_rules_header > "$SERIAL_UDEV_RULES"
    portsctrl_iterate_serial print_serial >> "$SERIAL_UDEV_RULES"
    udev_trigger_serial
}

udev_trigger_usb() {
    # Apply new udev rules
    udevadm control -R
    verbose "Apply authorized_default"
    udevadm trigger --subsystem-match="usb" --attr-match="authorized_default"
    verbose "Recursively apply authorized"
	udevadm trigger -v --subsystem-match="usb" --attr-match="authorized" |
		while read devpath; do
			udevadm trigger "$devpath"
		done
}

usb_rules_header() {
  cat << 'EOF'
# This file is covered by the GNU General Public License.
# Generated by alterator-ports-access. Do not edit manually.
# For more information see kernel/Documentation/usb/authorization.txt.

ACTION!="add|change", GOTO="alterator_ports_access_end"
SUBSYSTEM!="usb", GOTO="alterator_ports_access_end"
EOF
}

usb_rules_footer() {
  cat << 'EOF'

LABEL="alterator_ports_access_end"
EOF
}

# Turn off USB control
usb_control_off() {
	cleanup_usb_rules
    usb_rules_header > "$USB_UDEV_RULES"
    cat << 'EOF' >> "$USB_UDEV_RULES"

ATTR{authorized_default}=="?*", ATTR{authorized_default}="1"
ATTR{authorized}=="?*", ATTR{authorized}="1"
EOF

    usb_rules_footer >> "$USB_UDEV_RULES"
    udev_trigger_usb
}

_print_auth_rule() {
	local rule="$1"
	[ -z "$rule" ] || \
		echo "ATTR{authorized}==\"?*\"$rule, ATTR{authorized}=\"1\", GOTO=\"alterator_ports_access_end\""
}

print_usb_rule() {
    local num="$1"
    local vendorid="$2"
    local prodid="$3"
    local serial="$4"
	local class="$5"
	local subclass="$6"
	local proto="$7"
    local info="$8"
    local access="$9"

	if [ -n "$vendorid$prodid$serial" ]; then
		# Allow the device entirely
		echo
		echo "# Allow whole device with $vendorid:$prodid:$serial"
		local rule=
		[ -z "$vendorid" ] || \
			rule="$rule, ATTRS{idVendor}==\"$(quote_shell "$vendorid")\""
		[ -z "$prodid" ] || \
			rule="$rule, ATTRS{idProduct}==\"$(quote_shell "$prodid")\""
		[ -z "$serial" ] || \
			rule="$rule, ATTRS{serial}==\"$(quote_shell "$serial")\""
		[ -z "$class" ] || \
			rule="$rule, ATTRS{bDeviceClass}==\"$(quote_shell "$class")\""
		[ -z "$subclass" ] || \
			rule="$rule, ATTRS{bDeviceSubClass}==\"$(quote_shell "$subclass")\""
		[ -z "$proto" ] || \
			rule="$rule, ATTRS{bDeviceProtocol}==\"$(quote_shell "$proto")\""
		[ -z "$access" ] || \
			rule="$rule$(print_access "$access")"
		_print_auth_rule "$rule"
	elif [ -n "$class$subclass$proto" ]; then
		# Allow whole device or only some of its interfaces
		# based on the specified class parameters
		echo
		echo "# Allow whole device of :$class$subclass$proto: class"
		local rule=
		[ -z "$class" ] || \
			rule="$rule, ATTRS{bDeviceClass}==\"$(quote_shell "$class")\""
		[ -z "$subclass" ] || \
			rule="$rule, ATTRS{bDeviceSubClass}==\"$(quote_shell "$subclass")\""
		[ -z "$proto" ] || \
			rule="$rule, ATTRS{bDeviceProtocol}==\"$(quote_shell "$proto")\""
		[ -z "$access" ] || \
			rule="$rule$(print_access "$access")"
		_print_auth_rule "$rule"

		echo "# Allow top-level device having :$class$subclass$proto: class interface"
		local rule=
		rule="$rule, ENV{ID_USB_INTERFACES}==\"*:$(quote_shell "${class:-??}")$(quote_shell "${subclass:-??}")$(quote_shell "${proto:-??}"):*\""
		[ -z "$access" ] || \
			rule="$rule$(print_access "$access")"
		_print_auth_rule "$rule"

		echo "# Allow interface of $class$subclass$proto: class"
		local rule=
		[ -z "$class" ] || \
			rule="$rule, ATTR{bInterfaceClass}==\"$(quote_shell "$class")\""
		[ -z "$subclass" ] || \
			rule="$rule, ATTR{bInterfaceSubClass}==\"$(quote_shell "$subclass")\""
		[ -z "$proto" ] || \
			rule="$rule, ATTR{bInterfaceProtocol}==\"$(quote_shell "$proto")\""
		[ -z "$access" ] || \
			rule="$rule$(print_access "$access")"
		_print_auth_rule "$rule"
	fi
}

usb_devnode_rules_header() {
    serial_rules_header "$@"
}

print_usb_devnode_rule() {
    local num="$1"
    local vendorid="$2"
    local prodid="$3"
    local serial="$4"
	local class="$5"
	local subclass="$6"
	local proto="$7"
    local info="$8"
    local access="$9"

    # Skip devices with the default access mode:
    [ -n "$access" ] || return 0

	# Normalize class parameters (closes: 48360):
	[ "$class" != "00" ] || class=
	[ "$subclass" != "00" ] || subclass=
	[ "$proto" != "00" ] || proto=

	# Skip empty rules:
    [ -n "$vendorid$prodid$serial$class$subclass$proto" ] || return 0

	local rule="ENV{ID_BUS}==\"usb\""

    [ -z "$vendorid" ] || \
		rule="$rule, ENV{ID_VENDOR_ID}==\"$(quote_shell "$vendorid")\""
    [ -z "$prodid" ] || \
		rule="$rule, ENV{ID_MODEL_ID}==\"$(quote_shell "$prodid")\""
    [ -z "$serial" ] || \
        rule="$rule, ENV{ID_SERIAL_SHORT}==\"$(quote_shell "$serial")\""
	[ -z "$class$subclass$proto" ] || \
		rule="$rule, ENV{ID_USB_INTERFACES}==\"*:$(quote_shell "${class:-??}")$(quote_shell "${subclass:-??}")$(quote_shell "${proto:-??}"):*\""

    echo "$rule$(print_access "$access")"
}

# Turn on USB control
usb_control_on() {
	cleanup_usb_rules
    usb_rules_header > "$USB_UDEV_RULES"
    cat << 'EOF' >> "$USB_UDEV_RULES"

# Always authorize USB hubs
ATTR{authorized}=="?*", ATTR{bDeviceClass}=="09", ATTR{authorized}="1", GOTO="alterator_ports_access_end"
ATTR{authorized}=="?*", ATTR{bInterfaceClass}=="09", ATTR{authorized}="1", GOTO="alterator_ports_access_end"
EOF

    portsctrl_iterate_usb print_usb_rule >> "$USB_UDEV_RULES"

    usb_devnode_rules_header > "$USB_DEVNODE_UDEV_RULES"
    portsctrl_iterate_usb print_usb_devnode_rule >> "$USB_DEVNODE_UDEV_RULES"

    # Include config file
    (
        if portsctrl_config_exists; then
            . "$CONFIG"
        fi

        # HID
        if shell_var_is_yes "$USB_ALLOW_HID"; then
            cat << 'EOF' >> "$USB_UDEV_RULES"

# Allow HID
ATTR{authorized}=="?*", ATTR{bDeviceClass}=="03", ATTR{authorized}="1", GOTO="alterator_ports_access_end"
ATTR{authorized}=="?*", ENV{ID_USB_INTERFACES}=="*:03*", ATTR{authorized}="1", GOTO="alterator_ports_access_end"
ATTR{authorized}=="?*", ATTR{bInterfaceClass}=="03", ATTR{authorized}="1", GOTO="alterator_ports_access_end"
EOF
        fi
    )

  cat << 'EOF' >> "$USB_UDEV_RULES"

# Block all other USB devices
ATTR{authorized}=="?*", ATTR{authorized}="0", RUN="/bin/sh -c 'echo UNAUTHORIZED_DEVICE_INSERTED | systemd-cat -t devaccess -p crit'"
EOF

  usb_rules_footer >> "$USB_UDEV_RULES"

  udev_trigger_usb
}


# ENTRY POINT


# http://wiki.bash-hackers.org/howto/getopts_tutorial
while getopts ":seuhv" opt; do
  case $opt in
    u)
      verbose "Update USB ports configuration"
      USB="yes"
      ;;
    s)
      verbose "Update serial ports configuration"
      SERIAL="yes"
      ;;
    e)
      verbose "Disable USB control, enable all USB devices"
      USB_CONTROL_OFF="yes"
	  USB=yes
      ;;
    h)
      message "Use: $PROG [-s] [-u] [-e]"
      message "-s   update serial ports status"
      message "-e   disable USB control without touch config, enable all USB devices"
      message "-u   update USB ports status"
      message "Config file: $CONFIG"
      exit
      ;;
    v)
      verbose=1
      ;;
    \?)
      fatal "Invalid option: -$OPTARG"
      ;;
  esac
done

# Test correct invocation
if [ -z "$USB" -a -z "$SERIAL" -a -z "$USB_CTRL_OFF" ]; then
  message "Use: $PROG -h for help."
  exit
fi

# Config file exist
if ! [ -f "$CONFIG" -a -r "$CONFIG" ]; then
  fatal "Can't find configuration file $CONFIG"
fi

# Include config file
if portsctrl_config_exists; then
    . "$CONFIG"
fi

if shell_var_is_yes "$USB_CONTROL_OFF"; then
	USB_CONTROL="no"
fi

# Configure USB ports
if shell_var_is_yes "$USB"; then
  verbose "Configure USB ports"

  if shell_var_is_yes "$USB_CONTROL"; then
    verbose "USB control is on"
    usb_control_on
  else
    verbose "USB control is off"
    usb_control_off
  fi

fi

# Configure serial ports
if shell_var_is_yes "$SERIAL"; then
  verbose "Configure serial ports"

  if shell_var_is_yes "$SERIAL_CONTROL"; then
    verbose "Serial control is on"
    serial_control_on
  else
    verbose "Serial control is off"
    serial_control_off
  fi
fi

# vim: autoindent tabstop=2 shiftwidth=2 expandtab softtabstop=2 filetype=sh
