#!/bin/sh

# Turn of auto expansion
set -f

po_domain="alterator-ports-access"
alterator_api_version=1

. alterator-sh-functions
. shell-config
. shell-quote
. shell-var

# CONFIG
CONFIG="/etc/alterator-ports-access.conf"


# APPLY CHANGES
apply_changes() {
  echo "[backend-portsctrl] apply_changes." 1>&2
  alterator-ports-access -u -s
}


# LIST YES/NO
list_yes_no() {
  write_enum_item "yes" "`_ 'Yes'`"
  write_enum_item "no" "`_ 'No'`"
}


# LIST CONFIGURED SERIAL PORTS
list_serial() {

  # Create subshell & include config file
  (

  if [ -f "$CONFIG" -a -r "$CONFIG" ]; then
    . "$CONFIG"
  fi

  # Read present serial ports in system
  local ports=( $(udevadm info --path=/devices/platform/serial8250 --export-db --export | sed -n -e '/DEVPATH.*ttyS/ { /serial8250/d; s/^.*\///p }') )

  for port in "${ports[@]}"; do

    printed=

    for enabled in "${SERIAL_ENABLED[@]}"; do
      if [ "$port" = "$enabled" ]; then
        printf "(\"%s\" label_serial_port \"%s\" label_serial_enabled \"%s\")\n" "$port" "$port" "`_ 'Yes'`" >&3
        printed="yes"
        break
      fi
    done

    for enabled in "${SERIAL_DISABLED[@]}"; do
      if [ "$port" = "$enabled" -a -z "$printed" ]; then
        printf "(\"%s\" label_serial_port \"%s\" label_serial_enabled \"%s\")\n" "$port" "$port" "`_ 'No'`" >&3
        printed="yes"
        break
      fi
    done

    # Port not listed in config is enabled
    if [ -z "$printed" ]; then
      printf "(\"%s\" label_serial_port \"%s\" label_serial_enabled \"%s\")\n" "$port" "$port" "`_ 'Yes'`" >&3
    fi

  done

  )

}


# LIST USB WHITE LIST
list_usb_rules() {

  if ! [ -f "$CONFIG" -a -r "$CONFIG" ]; then
    echo "[backend-portsctrl] whitelist empty, no config file ($CONFIG)" 1>&2
    return
  fi

  # Create subshell & include config file
  (

  . "$CONFIG"

  local j=0
  local vendor= prodid= serial= info=

  for i in "${USB_WHITE_LIST[@]}"; do
    vendor="$(echo "$i" | cut -d ';' -f1)"
    prodid="$(echo "$i" | cut -d ';' -f2)"
    serial="$(echo "$i" | cut -d ';' -f3)"
    info="$(echo "$i" | cut -d ';' -f4)"
    printf "(\"%s\" label_usb_rule_number \"%s\" label_usb_vendor \"%s\" label_usb_prodid \"%s\" label_usb_serial \"%s\" label_usb_info \"%s\")\n" "$j" "$j" "$vendor" "$prodid" "$serial" "$info" >&3
    j=$((j+1))
  done

  )

}


# LIST PRESENT USB DEVICES
list_prsnt_devices() {

  # Force scan, temporary disable USB control
  if shell_var_is_yes "$in_force"; then
    echo "[backend-portsctrl] temporary disable USB control" 1>&2
    alterator-ports-access -e
  fi

  for i in $(find /sys -name authorized); do
    local dir="$(dirname "$i")"
    local devclass="$(cat "$dir/bDeviceClass")"

    # Ignore USB hubs
    [ "$devclass" = "09" ] && continue
    [ -z "$devclass" ] && continue

    local idProduct="$(cat "$dir/idProduct")"
    local idVendor="$(cat "$dir/idVendor")"
    local serial="$(cat "$dir/serial" 2>/dev/null)"

    [ -z "$devclass" ] && continue
    [ -z "$idVendor" ] && continue


    if [ -n "$serial" ]; then
      serial_msg="$serial"
    else
      serial_msg="`_ 'None'`"
    fi

    local db="/usr/share/misc/usb.ids"

    local manufacturer= product=

    if [ -e "$db" ]; then
      manufacturer="$(sed -n -e "/^$idVendor/ s/....[[:space:]]\+//p" "$db")"
      product="$(sed -n -e "/^$idVendor/,/^[^[:space:]]/{ s/^[[:space:]]\+$idProduct[[:space:]]\+//p }" "$db")"
    fi

    [ -z "$manufacturer" ] && manufacturer="$(cat "$dir/manufacturer" 2>/dev/null)"
    [ -z "$product" ] && product="$(cat "$dir/product" 2>/dev/null)"

    [ -z "${manufacturer##*unauthorized*}" ] && continue
    [ -z "${product##*unauthorized*}" ] && continue

    printf "(\"%s\" label_prsnt_usb_vendor \"%s\" label_prsnt_usb_product \"%s\" label_prsnt_usb_idvendor \"%s\" label_prsnt_usb_idproduct \"%s\" label_prsnt_usb_serial \"%s\")\n" \
      "$idVendor:$idProduct:$serial:$manufacturer $product" "$manufacturer" "$product" "$idVendor" "$idProduct" "$serial_msg" >&3

  done

  # Renew USB control
  if shell_var_is_yes "$in_force"; then
    echo "[backend-portsctrl] renew USB control" 1>&2
    alterator-ports-access -u
  fi

}


# READ SERIAL PORT STATUS
print_serial () {

  if [ -z "$in_port" ]; then
    write_error "Serial port name doesn't specified."
    return 1
  fi

  # Check config file exists
  if ! [ -f "$CONFIG" -a -r "$CONFIG" ]; then
    write_string_param "label_serial_selected" "$in_port"
    write_string_param "list_serial_enabled" "`_ 'Yes'`"
    return
  fi

  # Create subshell & include config file
  (

  . "$CONFIG"

  write_string_param "label_serial_selected" "$in_port"

  for i in "${SERIAL_DISABLED[@]}"; do
    if [ "$in_port" = "$i" ]; then
      write_string_param "list_serial_enabled" "`_ 'No'`"
      return
    fi
  done

  write_string_param "list_serial_enabled" "`_ 'Yes'`"

  )
}


# READ CONTROL STATUS FOR USB & SERIAL
print_status () {

  # Check config file exists
  if ! [ -f "$CONFIG" -a -r "$CONFIG" ]; then
    write_bool_param "serial_ctrl_on" 0
    write_bool_param "usb_ctrl_on" 0
    return
  fi

  # Create subshell & include config file
  (

  . "$CONFIG"

  if shell_var_is_yes "$SERIAL_CONTROL"; then
    write_bool_param "serial_ctrl_on" 1
  else
    write_bool_param "serial_ctrl_on" 0
  fi

  if shell_var_is_yes "$USB_CONTROL"; then
    write_bool_param "usb_ctrl_on" 1
  else
    write_bool_param "usb_ctrl_on" 0
  fi

  if shell_var_is_yes "$USB_ALLOW_HID"; then
    write_bool_param "usb_hid" 1
  else
    write_bool_param "usb_hid" 0
  fi

  )
}

# CHECK CONFIG EVAILABLE
check_config () {
  if ! [ -f "$CONFIG" -a -r "$CONFIG" ]; then
    # Create config
    if ! touch "$CONFIG"; then
      write_error "Can't create config file: ($CONFIG)"
      return 1
    else
      echo "[backend-portsctrl] new config file created ($CONFIG)" 1>&2
    fi
  fi
}

# TURN ON/OFF CONTROL
control_ports() {

  # Check correct invocation
  if [ -z "$in_proto" -o -z "$in_enabled" ]; then
    write_error "Bad call."
    return 1
  fi

  local status=

  if [ "$in_enabled" = "#t" ]; then
    status="yes"
  else
    status="no"
  fi

  # Check config file exists
  check_config

  # Update config
  local proto="$(echo "$in_proto" | tr '[:lower:]' '[:upper:]')"
  shell_config_set "$CONFIG" "${proto}_CONTROL" "\"$(quote_shell "$status")\""

  # Apply changes
  apply_changes

}


# NEW SERIAL PORT PARAMETERS
write_serial_options() {

  # Check correct invocation
  if [ -z "$in_port" -o -z "$in_enabled" ]; then
    write_error "Bad call."
    return 1
  fi

  # Check config file exists
  check_config

  # Create subshell & include config file
  (
  . "$CONFIG"

  # Remove any occurrence
  shell_config_set "$CONFIG" 'SERIAL_DISABLED' "($(for i in "${SERIAL_DISABLED[@]}"; do [ "$in_port" = "$i" ] && continue; echo -n "\"$(quote_shell "$i")\" "; done))"
  shell_config_set "$CONFIG" 'SERIAL_ENABLED' "($(for i in "${SERIAL_ENABLED[@]}"; do [ "$in_port" = "$i" ] && continue; echo -n "\"$(quote_shell "$i")\" "; done))"

  # Re-read config
  . "$CONFIG"

  # Add port to some list
  if shell_var_is_yes "$in_enabled"; then
    shell_config_set "$CONFIG" 'SERIAL_ENABLED' "($(for i in "${SERIAL_ENABLED[@]}" "$in_port"; do echo -n "\"$(quote_shell "$i")\" "; done))"
    echo "enabled access to serial port(s) $in_port" | logger -p user.warning
  elif shell_var_is_no "$in_enabled"; then
    shell_config_set "$CONFIG" 'SERIAL_DISABLED' "($(for i in "${SERIAL_DISABLED[@]}" "$in_port"; do echo -n "\"$(quote_shell "$i")\" "; done))"
    echo "disabled access to serial port(s) $in_port" | logger -p user.warning
  fi

  )

  # Apply changes
  apply_changes

}


# REMOVE ENTRY FROM WHITE LIST
usb_rm_rule() {

  # Check correct invocation
  if [ -z "$in_rule_num" ]; then
    write_error "Bad call."
    return 1
  fi

  # Check config file exists
  check_config

  # Create subshell & include config file
  (
  . "$CONFIG"

  USB_WHITE_LIST[$in_rule_num]=
  shell_config_set "$CONFIG" 'USB_WHITE_LIST' "($(for i in "${USB_WHITE_LIST[@]}"; do test -z "$i" && continue; echo -n "\"$(quote_shell "$i")\" "; done))"
  )

  # Apply changes
  apply_changes

}


# ADD NEW ENTRY TO WHITE LIST
usb_add_new_rule() {

  # Check correct invocation
  if [ -z "$in_vendor" -a -z "$in_productid" -a -z "$in_serial" ]; then
    write_error "Bad call."
    return 1
  fi

  # Check config file exists
  check_config

  # Create subshell & include config file
  (
  . "$CONFIG"

  # Test for existing rule
  for i in "${USB_WHITE_LIST[@]}"; do
    vendor="$(echo "$i" | cut -d ';' -f1)"
    prodid="$(echo "$i" | cut -d ';' -f2)"
    serial="$(echo "$i" | cut -d ';' -f3)"
    info="$(echo "$i" | cut -d ';' -f4)"

    if [ "$vendor" = "$in_vendor" -a "$prodid" = "$in_productid" -a "$serial" = "$in_serial" -a "$info" = "$in_info" ]; then
      return
    fi
  done

  shell_config_set "$CONFIG" 'USB_WHITE_LIST' "($(for i in "${USB_WHITE_LIST[@]}" "$in_vendor;$in_productid;$in_serial;$in_info"; do echo -n "\"$(quote_shell "$i")\" "; done))"
  )

  # Apply changes
  apply_changes

}


# Control USB HID devices
usb_hid() {

  # Check config file exists
  check_config

  # Update config
  if [ "$in_allow_hid" = "#t" ] ; then
    allow="yes"
  else
    allow="no"
  fi

  shell_config_set "$CONFIG" "USB_ALLOW_HID" "\"$allow\""

  # Apply changes
  apply_changes

}


# Add present USB device to whitelist
usb_add_prsnt() {

  # Check correct invocation
  if [ -z "$in_vendor_product" ]; then
    write_error "Bad call."
    return 1
  fi

  vendor="$(echo "$in_vendor_product" | cut -d ':' -f1)"
  product="$(echo "$in_vendor_product" | cut -d ':' -f2)"
  serial="$(echo "$in_vendor_product" | cut -d ':' -f3)"
  info="$(echo "$in_vendor_product" | cut -d ':' -f4)"

  [ -z "$vendor" ] && return
  [ -z "$product" ] && return

  in_vendor="$vendor" in_productid="$product" in_info="$info" in_serial="$serial" usb_add_new_rule

}


# MAIN LOOP
on_message() {
  case "$in_action" in

    read)

      case "$in__objects" in
        serial_port)
          # READ SERIAL PORT STATUS
          echo "[backend-portsctrl] read serial port settings" 1>&2
          print_serial
          ;;

        status)
          # READ CONTROL STATUS FOR USB & SERIAL
          echo "[backend-portsctrl] read status" 1>&2
          print_status
          ;;

        *)
          # UNDEFINED READ REQUEST
          echo "[backend-portsctrl] undefined read request: $in__objects" 1>&2

          ;;
      esac
      ;;

    write)

      case "$in__objects" in
        ctrl)
          # TURN ON/OFF CONTROL
          echo "[backend-portsctrl] write ports ctrl" 1>&2
          control_ports

          ;;

        serial)
          # NEW SERIAL PORT PARAMETERS
          echo "[backend-portsctrl] write serial port options" 1>&2
          write_serial_options

          ;;

        usb_rm)
          # REMOVE ENTRY FROM WHITE LIST
          echo "[backend-portsctrl] remove rule from whitelist" 1>&2
          usb_rm_rule

          ;;

        usb_add)
          # ADD NEW ENTRY TO WHITE LIST
          echo "[backend-portsctrl] add rule to whitelist" 1>&2
          usb_add_new_rule

          ;;

        usb_add_prsnt)
          # ADD PRESENT USB DEVICE TO WHITE LIST
          echo "[backend-portsctrl] add present USB device to whitelist" 1>&2
          usb_add_prsnt

          ;;

        usb_hid)
          # CONTROL USB HID
          echo "[backend-portsctrl] control USB HID" 1>&2
          usb_hid

          ;;

        *)
          # UNDEFINED WRITE REQUEST
          echo "[backend-portsctrl] undefined write request: $in__objects" 1>&2

          ;;

      esac
      ;;

    list)

      case "$in__objects" in

        list_serial)
          # LIST SERIAL PORTS
          echo "[backend-portsctrl] list serial ports" 1>&2
          list_serial

          ;;

        list_usb_rules)
          # LIST USB WHITE LIST
          echo "[backend-portsctrl] list whitelist" 1>&2
          list_usb_rules

          ;;

        list_serial_enabled)
          # BOOL LIST: ON/OFF SERIAL PORT
          echo "[backend-portsctrl] yes/no list for serial port" 1>&2
          list_yes_no

          ;;

        list_prsnt_devices)
          # LIST PRESENT USB DEVICES
          echo "[backend-portsctrl] list present devices" 1>&2
          list_prsnt_devices

          ;;


        *)
          # UNDEFINED LIST REQUEST
          echo "[backend-portsctrl] undefined list request: $in__objects" 1>&2

          ;;

      esac
      ;;

    *)
      # UNDEFINED ACTION
      :
      ;;
  esac
}

message_loop

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