#!/bin/sh

# This file is covered by the GNU General Public License.
# Copyright (C) 2012 Andrew V. Stepanov <stanv@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 USB_UDEV_RULES="$UDEV_RULEDIR/40-alterator-ports-access.rules"
readonly SERIAL_UDEV_RULES="$UDEV_RULEDIR/40-alterator-ports-access-serial.rules"
readonly USB_DEVNODE_UDEV_RULES="$UDEV_RULEDIR/99-alterator-ports-access-usbdevs.rules"

serial_rules_header() {
    cat << 'EOF'
# This file is covered by the GNU General Public License.
# Copyright (C) 2022 Paul Wolneykien <manowar@altlinux.org>.
# Part of alterator-ports-access.
# Do not edit manually.
EOF
}

print_serial_run() {
    echo -n ", RUN+=\"/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() {
    /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() {
    serial_rules_header > "$SERIAL_UDEV_RULES"
    udev_trigger_serial
    portsctrl_iterate_serial enable_serial
}

_print_access() {
    [ -z "$1" ] || echo -n ", OWNER=\"$1\""
    [ -z "$2" ] || echo -n ", GROUP=\"$2\""
    [ -z "$3" ] || echo -n ", MODE=\"$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() {
    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 "Apply authorized"
    udevadm trigger --subsystem-match="usb" --attr-match="authorized"
}

usb_rules_header() {
  cat << 'EOF'
# This file is covered by the GNU General Public License.
# Copyright (C) 2012 Andrew V. Stepanov <stanv@altlinux.org>.
# Copyright (C) 2022 Paul Wolneykien <manowar@altlinux.org>.
# Part of 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() {
    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_usb_rule() {
    local num="$1"
    local vendorid="$2"
    local prodid="$3"
    local serial="$4"
    local info="$5"
    local access="$6"

    local rule="ATTR{authorized}==\"?*\""
    local add=

    if [ -n "$vendorid" ]; then
      vendorid="$(quote_shell "$vendorid")"
      rule="$rule, ATTR{idVendor}==\"$vendorid\""
      add='yes'
    fi

    if [ -n "$prodid" ]; then
      prodid="$(quote_shell "$prodid")"
      rule="$rule, ATTR{idProduct}==\"$prodid\""
      add='yes'
    fi

    if [ -n "$serial" ]; then
        serial="$(quote_shell "$serial")"
        rule="$rule, ATTR{serial}==\"$serial\""
        add='yes'
    fi

    if [ -n "$access" ]; then
        rule="$rule$(print_access "$access")"
        add='yes'
    fi

    rule="$rule, ATTR{authorized}=\"1\", GOTO=\"alterator_ports_access_end\""

    if shell_var_is_yes "$add"; then
      echo "$rule"
      echo "$rule" | sed 's/ATTR{id/ATTRS{id/g;s/ATTR{serial}/ATTRS{serial}/g'
    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 info="$5"
    local access="$6"

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

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

    if [ -n "$vendorid" ]; then
      vendorid="$(quote_shell "$vendorid")"
      rule="$rule, ENV{ID_VENDOR_ID}==\"$vendorid\""
      add='yes'
    fi

    if [ -n "$prodid" ]; then
      prodid="$(quote_shell "$prodid")"
      rule="$rule, ENV{ID_MODEL_ID}==\"$prodid\""
      add='yes'
    fi

    if [ -n "$serial" ]; then
        serial="$(quote_shell "$serial")"
        rule="$rule, ENV{ID_SERIAL_SHORT}==\"$serial\""
        add='yes'
    fi

    rule="$rule$(print_access "$access")"

    if shell_var_is_yes "$add"; then
      echo "$rule"
    fi
}

# Turn on USB control
usb_control_on() {
    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"

# Un block device. Necessary step. Suppose device is already deactivated, so we need to look up his properties.
ATTR{authorized}=="?*", ATTR{authorized}="1"

# Allow HID
ATTR{authorized}=="?*", PROGRAM="alterator-ports-access", 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_CTRL_OFF="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

# 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
    USB_CTRL_OFF=yes
  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

if shell_var_is_yes "$USB_CTRL_OFF"; then

  for i in $(find /sys -name authorized); do
    [ -n "${e##*usb*}" ] && continue
    echo 1 > "$i"
  done
  for dev in $(ls /sys/bus/usb/devices); do
	  echo $dev > /sys/bus/usb/drivers_probe
  done

fi

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