e1bd727904
shell scripts in base, removes the bogus "copyleft", adds the BeerWare license header and uses rc.subr(8) new 'enable' keyword for adding entries in rc.conf(5). Submitted by: erdgeist <erdgeist@erdgeist.org> Approved by: bapt MFC after: 2 weeks
312 lines
9.7 KiB
Bash
Executable File
312 lines
9.7 KiB
Bash
Executable File
#!/bin/sh
|
|
#-
|
|
# ----------------------------------------------------------------------------
|
|
# "THE BEER-WARE LICENSE" (Revision 42):
|
|
# <erdgeist@erdgeist.org> wrote this file. As long as you retain this notice you
|
|
# can do whatever you want with this stuff. If we meet some day, and you think
|
|
# this stuff is worth it, you can buy me a beer in return Poul-Henning Kamp
|
|
# ----------------------------------------------------------------------------
|
|
#
|
|
# $FreeBSD$
|
|
#
|
|
|
|
# define our bail out shortcut
|
|
exerr () { echo -e "Error: $*" >&2 ; exit 1; }
|
|
print_syntax () { echo -e "Syntax: $0 scan [-d device] [-n node]"; exit 1; }
|
|
|
|
main() {
|
|
unset node device started bdaddresses retry
|
|
|
|
# Only one command at the moment is scan (+ add)
|
|
[ "$#" -eq 1 -a "$1" = "scan" ] || print_syntax
|
|
shift
|
|
|
|
# Get command line options
|
|
while getopts :d:n: arg; do
|
|
case ${arg} in
|
|
d) device="$OPTARG";;
|
|
n) node="$OPTARG";;
|
|
?) print_syntax;;
|
|
esac
|
|
done
|
|
|
|
# No use running without super user rights
|
|
if [ $( id -u ) -ne 0 ]; then
|
|
exerr "$0 must modify files that belong to root. Re-run as root."
|
|
fi
|
|
|
|
known_nodes=$( /usr/sbin/hccontrol read_node_list 2>/dev/null |\
|
|
/usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 )
|
|
|
|
# Check if netgraph knows about any HCI nodes
|
|
if ! [ "${known_nodes}" ]; then
|
|
ng_nodes=$( /usr/sbin/ngctl list 2>/dev/null | \
|
|
/usr/bin/grep -o "Name: .* Type: ubt" |/usr/bin/cut -d' ' -f2 )
|
|
|
|
[ "${ng_nodes}" ] || exerr "No Bluetooth host controllers found."
|
|
|
|
unset found
|
|
for n in ${ng_nodes}; do
|
|
if [ "${n}" = "${node%hci}" ]; then
|
|
# Found the node but its stack is not set up? Do it now.
|
|
/usr/sbin/service bluetooth start ${node%hci} || exit 1
|
|
found="YES"
|
|
fi
|
|
done
|
|
|
|
# If we have Bluetooth controller nodes without a set up stack,
|
|
# ask the user if we shall start it up
|
|
if ! [ "${found}" ]; then
|
|
printf "No usable Bluetooth host controllers were found.\n"
|
|
printf "These host controllers exist in the system:\n"
|
|
printf " %s\n" "${ng_nodes}"
|
|
prompt="Choose a host controller to set up: [${ng_nodes%% *}]"
|
|
read -p "${prompt}" node
|
|
: ${node:="${ng_nodes%% *}"}
|
|
/usr/sbin/service bluetooth start ${node} || exit 1
|
|
fi
|
|
|
|
# Re-read known nodes
|
|
known_nodes=$(/usr/sbin/hccontrol read_node_list 2>/dev/null |
|
|
/usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 )
|
|
|
|
# check if we succeeded in bringing it up
|
|
[ "${known_nodes}" ] || exerr "Failed to set up Bluetooth stack"
|
|
fi
|
|
|
|
# if a node was requested on command line, check if it is there
|
|
if [ "${node}" ]; then
|
|
unset found
|
|
for n in ${known_nodes}; do
|
|
[ "${n}" = "${node}" ] && found="YES"
|
|
[ "${n}" = "${node}hci" ] && node="${node}hci" && found="YES"
|
|
done
|
|
[ "${found}" ] || exerr "Node ${node} not found"
|
|
fi
|
|
|
|
[ "${node}" ] && node="-n ${node}"
|
|
|
|
while ! [ "${bdaddresses}" ]; do
|
|
retry=X${retry}
|
|
printf "Scanning for new Bluetooth devices (Attempt %d of 5) ... " \
|
|
${#retry}
|
|
bdaddresses=$( /usr/sbin/hccontrol -N ${node} inquiry 2>/dev/null |
|
|
/usr/bin/grep -o "BD_ADDR: .*" | /usr/bin/cut -d ' ' -f 2 )
|
|
|
|
# Count entries and, if a device was requested on command line,
|
|
# try to find it
|
|
unset found count
|
|
for bdaddress in ${bdaddresses}; do
|
|
count=X${count}
|
|
if [ "${bdaddress}" = "${device}" ]; then
|
|
found=YES
|
|
bdaddresses="${device}"
|
|
count=X
|
|
break
|
|
fi
|
|
done
|
|
|
|
# If device was requested on command line but is not found,
|
|
# or no devices found at all, rescan until retry is exhausted
|
|
if ! [ "${found}" -o "${count}" -a -z "${device}" ]; then
|
|
printf "failed.\n"
|
|
if [ "${#retry}" -eq 5 ]; then
|
|
[ "${device}" ] && exerr "Device ${device} not found"
|
|
exerr "No new Bluetooth devices found"
|
|
fi
|
|
unset bdaddresses
|
|
sleep 2
|
|
continue
|
|
fi
|
|
|
|
[ ${#count} -gt 1 ] && plural=s || plural=''
|
|
printf "done.\nFound %d new bluetooth device%s " ${#count} ${plural}
|
|
printf "(now scanning for names):\n"
|
|
|
|
# Looping again for the faster feedback
|
|
unset count
|
|
for bdaddress in ${bdaddresses}; do
|
|
count=X${count}
|
|
bdname=$( /usr/bin/bthost -b "${bdaddress}" 2>/dev/null )
|
|
friendlyname=$( /usr/sbin/hccontrol Remote_Name_Request \
|
|
${bdaddress} 2> /dev/null |
|
|
/usr/bin/grep -o "Name: .*" |/usr/bin/cut -d ' ' -f 2- )
|
|
|
|
# sdpcontrol should be able to pull vendor + product id via sdp
|
|
printf "[%2d] %s\t\"%s\" (%s)\n" ${#count} "${bdaddress}" \
|
|
"${friendlyname}" "${bdname}"
|
|
|
|
eval bdaddress_${#count}=\${bdaddress}
|
|
eval bdname_${#count}=\${bdname}
|
|
eval friendlyname_${#count}=\${friendlyname}
|
|
done
|
|
|
|
# If a device was pre-selected, do not query the user
|
|
[ "${device}" ] && topair=1 || unset topair
|
|
|
|
# Even if only one device was found, user may chose 0 to rescan
|
|
while ! [ "${topair}" ]; do
|
|
prompt="Select device to pair with [1"
|
|
[ ${#count} -gt 1 ] && prompt="${prompt}-${#count}"
|
|
read -p "${prompt}, or 0 to rescan]: " topair
|
|
if ! [ "${topair}" -ge 0 -a "${topair}" -le "${#count}" ] \
|
|
2>/dev/null ; then
|
|
printf "Value out of range: %s.\n" {topair}
|
|
unset topair
|
|
fi
|
|
done
|
|
|
|
[ "${topair}" -eq "0" ] && unset bdaddresses retry
|
|
done
|
|
|
|
eval bdaddress=\${bdaddress_${topair}}
|
|
eval bdname=\${bdname_${topair}}
|
|
eval friendlyname=\${friendlyname_${topair}}
|
|
|
|
# Do we need to add an entry to /etc/bluetooth/hosts?
|
|
if ! [ "${bdname}" ]; then
|
|
printf "\nAdding device ${bdaddress} to /etc/bluetooth/hosts.\n"
|
|
|
|
while ! [ "${bdname}" ]; do
|
|
read -p "Enter friendly name. [${friendlyname}]: " _r
|
|
: ${_r:="${friendlyname}"}
|
|
|
|
if [ "${_r}" ]; then
|
|
# Remove white space and non-friendly characters
|
|
bdname=$( printf "%s" "${_r}" | tr -c '[:alnum:]-,.' _ )
|
|
if [ "${_r}" != "${bdname}" ]; then
|
|
printf "Notice: Using sanitized name"
|
|
printf "\"%s\" in /etc/bluetooth/hosts.\n" \
|
|
"${bdname}"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
printf "%s\t%s\n" "${bdaddress}" "${bdname}" >> /etc/bluetooth/hosts
|
|
fi
|
|
|
|
# If scanning for the name did not succeed, resort to bdname
|
|
: ${friendlyname:="${bdname}"}
|
|
|
|
# now over to hcsecd
|
|
|
|
# Since hcsecd does not allow querying for known devices, we need to
|
|
# check for bdaddr entries manually.
|
|
#
|
|
# Also we cannot really modify the PIN in an existing entry. So we
|
|
# need to prompt the user to manually do it and restart this script
|
|
if ! /usr/sbin/service hcsecd enabled; then
|
|
printf "\nWarning: hcsecd is not enabled.\n"
|
|
printf "This daemon manages pairing requests.\n"
|
|
read -p "Enable hcsecd? [yes]: " _r
|
|
case "${_r}" in
|
|
no|n|NO|N|No|nO) ;;
|
|
*) /usr/sbin/service hcsecd enable;;
|
|
esac
|
|
fi
|
|
|
|
secd_config=$( /usr/sbin/sysrc -n hcsecd_config )
|
|
secd_entries=$( /usr/bin/grep -Eo "bdaddr[[:space:]]+(${bdaddress}|${bdname})" \
|
|
${secd_config} | awk '{ print $2; }' )
|
|
|
|
if [ "${secd_entries}" ]; then
|
|
printf "\nWarning: An entry for device %s is already present in %s.\n" \
|
|
${secd_entries} ${secd_config}
|
|
printf "To modify pairing information, edit this file and run\n"
|
|
printf " service hcsecd restart\n"
|
|
read -p "Continue? [yes]: " _r
|
|
case "${_r}" in no|n|NO|N|No|nO) exit;; esac
|
|
else
|
|
printf "\nWriting pairing information description block to %s.\n" \
|
|
${secd_config}
|
|
printf "(To get PIN, put device in pairing mode first.)\n"
|
|
read -p "Enter PIN [nopin]: " pin
|
|
[ "${pin}" ] && pin=\""${pin}"\" || pin="nopin"
|
|
|
|
# Write out new hcsecd config block
|
|
printf "\ndevice {\n\tbdaddr\t%s;\n\tname\t\"%s\";\n\tkey\tnokey\;\n\tpin\t%s\;\n}\n" \
|
|
"${bdaddress}" "${friendlyname}" "${pin}" >> ${secd_config}
|
|
|
|
# ... and make daemon reload config
|
|
# TODO: hcsecd should provide a reload hook
|
|
/usr/sbin/service hcsecd onerestart
|
|
|
|
# TODO: we should check if hcsecd succeeded pairing and revert to an
|
|
# old version of hcsecd.conf so we can undo adding the block above and
|
|
# retry with a new PIN
|
|
# also, if there's a way to force devices to re-pair, try this
|
|
fi
|
|
|
|
# now check for specific services to be provided by the device
|
|
# first up: HID
|
|
|
|
/usr/sbin/sdpcontrol -a "${bdaddress}" search HID | \
|
|
/usr/bin/grep -q "^Record Handle: " || exit 0
|
|
|
|
printf "\nThis device provides human interface device services.\n"
|
|
read -p "Set it up? [yes]: " _r
|
|
case "${_r}" in
|
|
no|n|NO|N|No|nO) exit 0;;
|
|
*);;
|
|
esac
|
|
|
|
# Here we have found an HID and were asked to set it up
|
|
# NOTE: look out for the two exit 0 above if you extend this script
|
|
|
|
if ! /usr/sbin/service bthidd enabled; then
|
|
printf "\nWarning: bthidd is not enabled."
|
|
printf "\nThis daemon manages Bluetooth HID devices.\n"
|
|
read -p "Enable bthidd? [yes]: " _r
|
|
case "${_r}" in
|
|
no|n|NO|N|No|nO) ;;
|
|
*) /usr/sbin/service bthidd enable;;
|
|
esac
|
|
fi
|
|
|
|
# Check if bthidd already knows about this device
|
|
bthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \
|
|
/usr/bin/grep "${bdaddress}" )
|
|
|
|
if [ "${bthidd_known}" ]; then
|
|
printf "Notice: Device %s already known to bthidd.\n" "${bdaddress}"
|
|
return 0
|
|
fi
|
|
|
|
bthidd_config=$( /usr/sbin/sysrc -n bthidd_config )
|
|
printf "Writing HID descriptor block to %s ... " "${bthidd_config}"
|
|
/usr/sbin/bthidcontrol -a "${bdaddress}" query >> "${bthidd_config}"
|
|
|
|
# Re-read config to see if we succeeded adding the device
|
|
bthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \
|
|
grep "${bdaddress}" )
|
|
if ! [ "${bthidd_known}" ]; then
|
|
printf "failed.\n"
|
|
else
|
|
printf "success.\nTo re-read its config, bthidd must be restarted.\n"
|
|
printf "Warning: If a Bluetooth keyboard is being used, the connection"
|
|
printf "might be lost.\n"
|
|
printf "It can be manually restarted later with\n"
|
|
printf " service bthidd restart\n"
|
|
read -p "Restart bthidd now? [yes]: " _r
|
|
case "${_r}" in
|
|
no|n|NO|N|No|nO) ;;
|
|
*) /usr/sbin/service bthidd onerestart;;
|
|
esac
|
|
fi
|
|
|
|
}
|
|
|
|
# After function definitions, main() can use them
|
|
main "$@"
|
|
exit 0
|
|
|
|
# TODO
|
|
# * If device is a keyboard, offer a text entry test field and if it does
|
|
# not succeed, leave some clues for debugging (i.e. if the node responds
|
|
# to pings, maybe switch keyboard on/off, etc)
|
|
# * Same if device is a mouse, i.e. hexdump /dev/sysmouse.
|
|
# * If device offers DUN profiles, ask the user if an entry in
|
|
# /etc/ppp/ppp.conf should be created
|
|
# * If OPUSH or SPP is offered, refer to the respective man pages to give
|
|
# some clues how to continue
|