10fc0faf8SLars Engels#!/bin/sh 20fc0faf8SLars Engels#- 3*e1bd7279SLars Engels# ---------------------------------------------------------------------------- 4*e1bd7279SLars Engels# "THE BEER-WARE LICENSE" (Revision 42): 5*e1bd7279SLars Engels# <erdgeist@erdgeist.org> wrote this file. As long as you retain this notice you 6*e1bd7279SLars Engels# can do whatever you want with this stuff. If we meet some day, and you think 7*e1bd7279SLars Engels# this stuff is worth it, you can buy me a beer in return Poul-Henning Kamp 8*e1bd7279SLars Engels# ---------------------------------------------------------------------------- 90fc0faf8SLars Engels# 100fc0faf8SLars Engels# 110fc0faf8SLars Engels 120fc0faf8SLars Engels# define our bail out shortcut 130fc0faf8SLars Engelsexerr () { echo -e "Error: $*" >&2 ; exit 1; } 140fc0faf8SLars Engelsprint_syntax () { echo -e "Syntax: $0 scan [-d device] [-n node]"; exit 1; } 150fc0faf8SLars Engels 160fc0faf8SLars Engelsmain() { 170fc0faf8SLars Engelsunset node device started bdaddresses retry 180fc0faf8SLars Engels 190fc0faf8SLars Engels# Only one command at the moment is scan (+ add) 200fc0faf8SLars Engels[ "$#" -eq 1 -a "$1" = "scan" ] || print_syntax 210fc0faf8SLars Engelsshift 220fc0faf8SLars Engels 230fc0faf8SLars Engels# Get command line options 240fc0faf8SLars Engelswhile getopts :d:n: arg; do 250fc0faf8SLars Engels case ${arg} in 260fc0faf8SLars Engels d) device="$OPTARG";; 270fc0faf8SLars Engels n) node="$OPTARG";; 280fc0faf8SLars Engels ?) print_syntax;; 290fc0faf8SLars Engels esac 300fc0faf8SLars Engelsdone 310fc0faf8SLars Engels 320fc0faf8SLars Engels# No use running without super user rights 33*e1bd7279SLars Engelsif [ $( id -u ) -ne 0 ]; then 34*e1bd7279SLars Engels exerr "$0 must modify files that belong to root. Re-run as root." 35*e1bd7279SLars Engelsfi 360fc0faf8SLars Engels 370fc0faf8SLars Engelsknown_nodes=$( /usr/sbin/hccontrol read_node_list 2>/dev/null |\ 380fc0faf8SLars Engels /usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 ) 390fc0faf8SLars Engels 400fc0faf8SLars Engels# Check if netgraph knows about any HCI nodes 410fc0faf8SLars Engelsif ! [ "${known_nodes}" ]; then 420fc0faf8SLars Engels ng_nodes=$( /usr/sbin/ngctl list 2>/dev/null | \ 430fc0faf8SLars Engels /usr/bin/grep -o "Name: .* Type: ubt" |/usr/bin/cut -d' ' -f2 ) 440fc0faf8SLars Engels 450fc0faf8SLars Engels [ "${ng_nodes}" ] || exerr "No Bluetooth host controllers found." 460fc0faf8SLars Engels 470fc0faf8SLars Engels unset found 480fc0faf8SLars Engels for n in ${ng_nodes}; do 490fc0faf8SLars Engels if [ "${n}" = "${node%hci}" ]; then 50*e1bd7279SLars Engels # Found the node but its stack is not set up? Do it now. 510fc0faf8SLars Engels /usr/sbin/service bluetooth start ${node%hci} || exit 1 520fc0faf8SLars Engels found="YES" 530fc0faf8SLars Engels fi 540fc0faf8SLars Engels done 550fc0faf8SLars Engels 560fc0faf8SLars Engels # If we have Bluetooth controller nodes without a set up stack, 570fc0faf8SLars Engels # ask the user if we shall start it up 580fc0faf8SLars Engels if ! [ "${found}" ]; then 590fc0faf8SLars Engels printf "No usable Bluetooth host controllers were found.\n" 60*e1bd7279SLars Engels printf "These host controllers exist in the system:\n" 61*e1bd7279SLars Engels printf " %s\n" "${ng_nodes}" 62*e1bd7279SLars Engels prompt="Choose a host controller to set up: [${ng_nodes%% *}]" 63*e1bd7279SLars Engels read -p "${prompt}" node 640fc0faf8SLars Engels : ${node:="${ng_nodes%% *}"} 650fc0faf8SLars Engels /usr/sbin/service bluetooth start ${node} || exit 1 660fc0faf8SLars Engels fi 670fc0faf8SLars Engels 680fc0faf8SLars Engels # Re-read known nodes 69*e1bd7279SLars Engels known_nodes=$(/usr/sbin/hccontrol read_node_list 2>/dev/null | 700fc0faf8SLars Engels /usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 ) 71*e1bd7279SLars Engels 720fc0faf8SLars Engels # check if we succeeded in bringing it up 730fc0faf8SLars Engels [ "${known_nodes}" ] || exerr "Failed to set up Bluetooth stack" 740fc0faf8SLars Engelsfi 750fc0faf8SLars Engels 760fc0faf8SLars Engels# if a node was requested on command line, check if it is there 770fc0faf8SLars Engelsif [ "${node}" ]; then 780fc0faf8SLars Engels unset found 790fc0faf8SLars Engels for n in ${known_nodes}; do 800fc0faf8SLars Engels [ "${n}" = "${node}" ] && found="YES" 810fc0faf8SLars Engels [ "${n}" = "${node}hci" ] && node="${node}hci" && found="YES" 820fc0faf8SLars Engels done 830fc0faf8SLars Engels [ "${found}" ] || exerr "Node ${node} not found" 840fc0faf8SLars Engelsfi 850fc0faf8SLars Engels 860fc0faf8SLars Engels[ "${node}" ] && node="-n ${node}" 870fc0faf8SLars Engels 880fc0faf8SLars Engelswhile ! [ "${bdaddresses}" ]; do 890fc0faf8SLars Engels retry=X${retry} 90*e1bd7279SLars Engels printf "Scanning for new Bluetooth devices (Attempt %d of 5) ... " \ 91*e1bd7279SLars Engels ${#retry} 92*e1bd7279SLars Engels bdaddresses=$( /usr/sbin/hccontrol -N ${node} inquiry 2>/dev/null | 930fc0faf8SLars Engels /usr/bin/grep -o "BD_ADDR: .*" | /usr/bin/cut -d ' ' -f 2 ) 940fc0faf8SLars Engels 950fc0faf8SLars Engels # Count entries and, if a device was requested on command line, 960fc0faf8SLars Engels # try to find it 970fc0faf8SLars Engels unset found count 980fc0faf8SLars Engels for bdaddress in ${bdaddresses}; do 990fc0faf8SLars Engels count=X${count} 1000fc0faf8SLars Engels if [ "${bdaddress}" = "${device}" ]; then 1010fc0faf8SLars Engels found=YES 1020fc0faf8SLars Engels bdaddresses="${device}" 1030fc0faf8SLars Engels count=X 1040fc0faf8SLars Engels break 1050fc0faf8SLars Engels fi 1060fc0faf8SLars Engels done 1070fc0faf8SLars Engels 1080fc0faf8SLars Engels # If device was requested on command line but is not found, 1090fc0faf8SLars Engels # or no devices found at all, rescan until retry is exhausted 1100fc0faf8SLars Engels if ! [ "${found}" -o "${count}" -a -z "${device}" ]; then 1110fc0faf8SLars Engels printf "failed.\n" 1120fc0faf8SLars Engels if [ "${#retry}" -eq 5 ]; then 1130fc0faf8SLars Engels [ "${device}" ] && exerr "Device ${device} not found" 1140fc0faf8SLars Engels exerr "No new Bluetooth devices found" 1150fc0faf8SLars Engels fi 1160fc0faf8SLars Engels unset bdaddresses 1170fc0faf8SLars Engels sleep 2 1180fc0faf8SLars Engels continue 1190fc0faf8SLars Engels fi 1200fc0faf8SLars Engels 1210fc0faf8SLars Engels [ ${#count} -gt 1 ] && plural=s || plural='' 122*e1bd7279SLars Engels printf "done.\nFound %d new bluetooth device%s " ${#count} ${plural} 123*e1bd7279SLars Engels printf "(now scanning for names):\n" 1240fc0faf8SLars Engels 1250fc0faf8SLars Engels # Looping again for the faster feedback 1260fc0faf8SLars Engels unset count 1270fc0faf8SLars Engels for bdaddress in ${bdaddresses}; do 1280fc0faf8SLars Engels count=X${count} 1290fc0faf8SLars Engels bdname=$( /usr/bin/bthost -b "${bdaddress}" 2>/dev/null ) 130*e1bd7279SLars Engels friendlyname=$( /usr/sbin/hccontrol Remote_Name_Request \ 131*e1bd7279SLars Engels ${bdaddress} 2> /dev/null | 1320fc0faf8SLars Engels /usr/bin/grep -o "Name: .*" |/usr/bin/cut -d ' ' -f 2- ) 1330fc0faf8SLars Engels 134*e1bd7279SLars Engels # sdpcontrol should be able to pull vendor + product id via sdp 135*e1bd7279SLars Engels printf "[%2d] %s\t\"%s\" (%s)\n" ${#count} "${bdaddress}" \ 136*e1bd7279SLars Engels "${friendlyname}" "${bdname}" 1370fc0faf8SLars Engels 1380fc0faf8SLars Engels eval bdaddress_${#count}=\${bdaddress} 1390fc0faf8SLars Engels eval bdname_${#count}=\${bdname} 1400fc0faf8SLars Engels eval friendlyname_${#count}=\${friendlyname} 1410fc0faf8SLars Engels done 1420fc0faf8SLars Engels 1430fc0faf8SLars Engels # If a device was pre-selected, do not query the user 1440fc0faf8SLars Engels [ "${device}" ] && topair=1 || unset topair 1450fc0faf8SLars Engels 1460fc0faf8SLars Engels # Even if only one device was found, user may chose 0 to rescan 1470fc0faf8SLars Engels while ! [ "${topair}" ]; do 148*e1bd7279SLars Engels prompt="Select device to pair with [1" 149*e1bd7279SLars Engels [ ${#count} -gt 1 ] && prompt="${prompt}-${#count}" 150*e1bd7279SLars Engels read -p "${prompt}, or 0 to rescan]: " topair 151*e1bd7279SLars Engels if ! [ "${topair}" -ge 0 -a "${topair}" -le "${#count}" ] \ 152*e1bd7279SLars Engels 2>/dev/null ; then 1530fc0faf8SLars Engels printf "Value out of range: %s.\n" {topair} 1540fc0faf8SLars Engels unset topair 1550fc0faf8SLars Engels fi 1560fc0faf8SLars Engels done 1570fc0faf8SLars Engels 1580fc0faf8SLars Engels [ "${topair}" -eq "0" ] && unset bdaddresses retry 1590fc0faf8SLars Engelsdone 1600fc0faf8SLars Engels 1610fc0faf8SLars Engelseval bdaddress=\${bdaddress_${topair}} 1620fc0faf8SLars Engelseval bdname=\${bdname_${topair}} 1630fc0faf8SLars Engelseval friendlyname=\${friendlyname_${topair}} 1640fc0faf8SLars Engels 1650fc0faf8SLars Engels# Do we need to add an entry to /etc/bluetooth/hosts? 1660fc0faf8SLars Engelsif ! [ "${bdname}" ]; then 1670fc0faf8SLars Engels printf "\nAdding device ${bdaddress} to /etc/bluetooth/hosts.\n" 1680fc0faf8SLars Engels 1690fc0faf8SLars Engels while ! [ "${bdname}" ]; do 170*e1bd7279SLars Engels read -p "Enter friendly name. [${friendlyname}]: " _r 171*e1bd7279SLars Engels : ${_r:="${friendlyname}"} 1720fc0faf8SLars Engels 173*e1bd7279SLars Engels if [ "${_r}" ]; then 1740fc0faf8SLars Engels # Remove white space and non-friendly characters 175*e1bd7279SLars Engels bdname=$( printf "%s" "${_r}" | tr -c '[:alnum:]-,.' _ ) 176*e1bd7279SLars Engels if [ "${_r}" != "${bdname}" ]; then 177*e1bd7279SLars Engels printf "Notice: Using sanitized name" 178*e1bd7279SLars Engels printf "\"%s\" in /etc/bluetooth/hosts.\n" \ 179*e1bd7279SLars Engels "${bdname}" 180*e1bd7279SLars Engels fi 1810fc0faf8SLars Engels fi 1820fc0faf8SLars Engels done 1830fc0faf8SLars Engels 1840fc0faf8SLars Engels printf "%s\t%s\n" "${bdaddress}" "${bdname}" >> /etc/bluetooth/hosts 1850fc0faf8SLars Engelsfi 1860fc0faf8SLars Engels 1870fc0faf8SLars Engels# If scanning for the name did not succeed, resort to bdname 1880fc0faf8SLars Engels: ${friendlyname:="${bdname}"} 1890fc0faf8SLars Engels 1900fc0faf8SLars Engels# now over to hcsecd 1910fc0faf8SLars Engels 1920fc0faf8SLars Engels# Since hcsecd does not allow querying for known devices, we need to 1930fc0faf8SLars Engels# check for bdaddr entries manually. 1940fc0faf8SLars Engels# 1950fc0faf8SLars Engels# Also we cannot really modify the PIN in an existing entry. So we 1960fc0faf8SLars Engels# need to prompt the user to manually do it and restart this script 1970fc0faf8SLars Engelsif ! /usr/sbin/service hcsecd enabled; then 198*e1bd7279SLars Engels printf "\nWarning: hcsecd is not enabled.\n" 199*e1bd7279SLars Engels printf "This daemon manages pairing requests.\n" 200*e1bd7279SLars Engels read -p "Enable hcsecd? [yes]: " _r 201*e1bd7279SLars Engels case "${_r}" in 202*e1bd7279SLars Engels no|n|NO|N|No|nO) ;; 203*e1bd7279SLars Engels *) /usr/sbin/service hcsecd enable;; 204*e1bd7279SLars Engels esac 2050fc0faf8SLars Engelsfi 206*e1bd7279SLars Engels 2070fc0faf8SLars Engelssecd_config=$( /usr/sbin/sysrc -n hcsecd_config ) 208*e1bd7279SLars Engelssecd_entries=$( /usr/bin/grep -Eo "bdaddr[[:space:]]+(${bdaddress}|${bdname})" \ 209*e1bd7279SLars Engels ${secd_config} | awk '{ print $2; }' ) 2100fc0faf8SLars Engels 2110fc0faf8SLars Engelsif [ "${secd_entries}" ]; then 212*e1bd7279SLars Engels printf "\nWarning: An entry for device %s is already present in %s.\n" \ 213*e1bd7279SLars Engels ${secd_entries} ${secd_config} 214*e1bd7279SLars Engels printf "To modify pairing information, edit this file and run\n" 215*e1bd7279SLars Engels printf " service hcsecd restart\n" 216*e1bd7279SLars Engels read -p "Continue? [yes]: " _r 217*e1bd7279SLars Engels case "${_r}" in no|n|NO|N|No|nO) exit;; esac 2180fc0faf8SLars Engelselse 219*e1bd7279SLars Engels printf "\nWriting pairing information description block to %s.\n" \ 220*e1bd7279SLars Engels ${secd_config} 2210fc0faf8SLars Engels printf "(To get PIN, put device in pairing mode first.)\n" 2220fc0faf8SLars Engels read -p "Enter PIN [nopin]: " pin 2230fc0faf8SLars Engels [ "${pin}" ] && pin=\""${pin}"\" || pin="nopin" 2240fc0faf8SLars Engels 2250fc0faf8SLars Engels # Write out new hcsecd config block 2260fc0faf8SLars Engels printf "\ndevice {\n\tbdaddr\t%s;\n\tname\t\"%s\";\n\tkey\tnokey\;\n\tpin\t%s\;\n}\n" \ 2270fc0faf8SLars Engels "${bdaddress}" "${friendlyname}" "${pin}" >> ${secd_config} 2280fc0faf8SLars Engels 229*e1bd7279SLars Engels # ... and make daemon reload config 230*e1bd7279SLars Engels # TODO: hcsecd should provide a reload hook 231*e1bd7279SLars Engels /usr/sbin/service hcsecd onerestart 2320fc0faf8SLars Engels 233*e1bd7279SLars Engels # TODO: we should check if hcsecd succeeded pairing and revert to an 234*e1bd7279SLars Engels # old version of hcsecd.conf so we can undo adding the block above and 235*e1bd7279SLars Engels # retry with a new PIN 2360fc0faf8SLars Engels # also, if there's a way to force devices to re-pair, try this 2370fc0faf8SLars Engelsfi 2380fc0faf8SLars Engels 2390fc0faf8SLars Engels# now check for specific services to be provided by the device 2400fc0faf8SLars Engels# first up: HID 2410fc0faf8SLars Engels 242*e1bd7279SLars Engels/usr/sbin/sdpcontrol -a "${bdaddress}" search HID | \ 243*e1bd7279SLars Engels /usr/bin/grep -q "^Record Handle: " || exit 0 2440fc0faf8SLars Engels 2450fc0faf8SLars Engelsprintf "\nThis device provides human interface device services.\n" 246*e1bd7279SLars Engelsread -p "Set it up? [yes]: " _r 247*e1bd7279SLars Engelscase "${_r}" in 248*e1bd7279SLars Engels no|n|NO|N|No|nO) exit 0;; 249*e1bd7279SLars Engels *);; 250*e1bd7279SLars Engelsesac 251*e1bd7279SLars Engels 252*e1bd7279SLars Engels# Here we have found an HID and were asked to set it up 253*e1bd7279SLars Engels# NOTE: look out for the two exit 0 above if you extend this script 254*e1bd7279SLars Engels 2550fc0faf8SLars Engelsif ! /usr/sbin/service bthidd enabled; then 2560fc0faf8SLars Engels printf "\nWarning: bthidd is not enabled." 2570fc0faf8SLars Engels printf "\nThis daemon manages Bluetooth HID devices.\n" 258*e1bd7279SLars Engels read -p "Enable bthidd? [yes]: " _r 259*e1bd7279SLars Engels case "${_r}" in 260*e1bd7279SLars Engels no|n|NO|N|No|nO) ;; 261*e1bd7279SLars Engels *) /usr/sbin/service bthidd enable;; 262*e1bd7279SLars Engels esac 2630fc0faf8SLars Engelsfi 2640fc0faf8SLars Engels 2650fc0faf8SLars Engels# Check if bthidd already knows about this device 2660fc0faf8SLars Engelsbthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \ 2670fc0faf8SLars Engels /usr/bin/grep "${bdaddress}" ) 268*e1bd7279SLars Engels 2690fc0faf8SLars Engelsif [ "${bthidd_known}" ]; then 2700fc0faf8SLars Engels printf "Notice: Device %s already known to bthidd.\n" "${bdaddress}" 271*e1bd7279SLars Engels return 0 272*e1bd7279SLars Engelsfi 273*e1bd7279SLars Engels 2740fc0faf8SLars Engelsbthidd_config=$( /usr/sbin/sysrc -n bthidd_config ) 2750fc0faf8SLars Engelsprintf "Writing HID descriptor block to %s ... " "${bthidd_config}" 2760fc0faf8SLars Engels/usr/sbin/bthidcontrol -a "${bdaddress}" query >> "${bthidd_config}" 2770fc0faf8SLars Engels 2780fc0faf8SLars Engels# Re-read config to see if we succeeded adding the device 2790fc0faf8SLars Engelsbthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \ 2800fc0faf8SLars Engels grep "${bdaddress}" ) 2810fc0faf8SLars Engelsif ! [ "${bthidd_known}" ]; then 2820fc0faf8SLars Engels printf "failed.\n" 2830fc0faf8SLars Engelselse 2840fc0faf8SLars Engels printf "success.\nTo re-read its config, bthidd must be restarted.\n" 285*e1bd7279SLars Engels printf "Warning: If a Bluetooth keyboard is being used, the connection" 286*e1bd7279SLars Engels printf "might be lost.\n" 287*e1bd7279SLars Engels printf "It can be manually restarted later with\n" 288*e1bd7279SLars Engels printf " service bthidd restart\n" 289*e1bd7279SLars Engels read -p "Restart bthidd now? [yes]: " _r 290*e1bd7279SLars Engels case "${_r}" in 291*e1bd7279SLars Engels no|n|NO|N|No|nO) ;; 292*e1bd7279SLars Engels *) /usr/sbin/service bthidd onerestart;; 2930fc0faf8SLars Engels esac 2940fc0faf8SLars Engelsfi 2950fc0faf8SLars Engels 2960fc0faf8SLars Engels} 2970fc0faf8SLars Engels 2980fc0faf8SLars Engels# After function definitions, main() can use them 2990fc0faf8SLars Engelsmain "$@" 300*e1bd7279SLars Engelsexit 0 3010fc0faf8SLars Engels 3020fc0faf8SLars Engels# TODO 3030fc0faf8SLars Engels# * If device is a keyboard, offer a text entry test field and if it does 3040fc0faf8SLars Engels# not succeed, leave some clues for debugging (i.e. if the node responds 3050fc0faf8SLars Engels# to pings, maybe switch keyboard on/off, etc) 3060fc0faf8SLars Engels# * Same if device is a mouse, i.e. hexdump /dev/sysmouse. 3070fc0faf8SLars Engels# * If device offers DUN profiles, ask the user if an entry in 3080fc0faf8SLars Engels# /etc/ppp/ppp.conf should be created 3090fc0faf8SLars Engels# * If OPUSH or SPP is offered, refer to the respective man pages to give 3100fc0faf8SLars Engels# some clues how to continue 311