1#!/bin/sh 2#- 3# ---------------------------------------------------------------------------- 4# "THE BEER-WARE LICENSE" (Revision 42): 5# <erdgeist@erdgeist.org> wrote this file. As long as you retain this notice you 6# can do whatever you want with this stuff. If we meet some day, and you think 7# this stuff is worth it, you can buy me a beer in return Poul-Henning Kamp 8# ---------------------------------------------------------------------------- 9# 10# 11 12# define our bail out shortcut 13exerr () { echo -e "Error: $*" >&2 ; exit 1; } 14print_syntax () { echo -e "Syntax: $0 scan [-d device] [-n node]"; exit 1; } 15 16main() { 17unset node device started bdaddresses retry 18 19# Only one command at the moment is scan (+ add) 20[ "$1" = "scan" ] || print_syntax 21shift 22 23# Get command line options 24while getopts :d:n: arg; do 25 case ${arg} in 26 d) device="$OPTARG";; 27 n) node="$OPTARG";; 28 ?) print_syntax;; 29 esac 30done 31shift "$((OPTIND-1))" 32 33# If there's leftover parameters, print usage 34[ "$#" -eq 0 ] || print_syntax 35shift 36 37 38# No use running without super user rights 39if [ $( id -u ) -ne 0 ]; then 40 exerr "$0 must modify files that belong to root. Re-run as root." 41fi 42 43known_nodes=$( /usr/sbin/hccontrol read_node_list 2>/dev/null |\ 44 /usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 ) 45 46# Check if netgraph knows about any HCI nodes 47if ! [ "${known_nodes}" ]; then 48 ng_nodes=$( /usr/sbin/ngctl list 2>/dev/null | \ 49 /usr/bin/grep -o "Name: .* Type: ubt" |/usr/bin/cut -d' ' -f2 ) 50 51 [ "${ng_nodes}" ] || exerr "No Bluetooth host controllers found." 52 53 unset found 54 for n in ${ng_nodes}; do 55 if [ "${n}" = "${node%hci}" ]; then 56 # Found the node but its stack is not set up? Do it now. 57 /usr/sbin/service bluetooth start ${node%hci} || exit 1 58 found="YES" 59 fi 60 done 61 62 # If we have Bluetooth controller nodes without a set up stack, 63 # ask the user if we shall start it up 64 if ! [ "${found}" ]; then 65 printf "No usable Bluetooth host controllers were found.\n" 66 printf "These host controllers exist in the system:\n" 67 printf " %s\n" "${ng_nodes}" 68 prompt="Choose a host controller to set up: [${ng_nodes%% *}]" 69 read -p "${prompt}" node 70 : ${node:="${ng_nodes%% *}"} 71 /usr/sbin/service bluetooth start ${node} || exit 1 72 fi 73 74 # Re-read known nodes 75 known_nodes=$(/usr/sbin/hccontrol read_node_list 2>/dev/null | 76 /usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 ) 77 78 # check if we succeeded in bringing it up 79 [ "${known_nodes}" ] || exerr "Failed to set up Bluetooth stack" 80fi 81 82# if a node was requested on command line, check if it is there 83if [ "${node}" ]; then 84 unset found 85 for n in ${known_nodes}; do 86 [ "${n}" = "${node}" ] && found="YES" 87 [ "${n}" = "${node}hci" ] && node="${node}hci" && found="YES" 88 done 89 [ "${found}" ] || exerr "Node ${node} not found" 90fi 91 92[ "${node}" ] && node="-n ${node}" 93 94while ! [ "${bdaddresses}" ]; do 95 retry=X${retry} 96 printf "Scanning for new Bluetooth devices (Attempt %d of 5) ... " \ 97 ${#retry} 98 bdaddresses=$( /usr/sbin/hccontrol -N ${node} inquiry 2>/dev/null | 99 /usr/bin/grep -o "BD_ADDR: .*" | /usr/bin/cut -d ' ' -f 2 ) 100 101 # Count entries and, if a device was requested on command line, 102 # try to find it 103 unset found count 104 for bdaddress in ${bdaddresses}; do 105 count=X${count} 106 if [ "${bdaddress}" = "${device}" ]; then 107 found=YES 108 bdaddresses="${device}" 109 count=X 110 break 111 fi 112 done 113 114 # If device was requested on command line but is not found, 115 # or no devices found at all, rescan until retry is exhausted 116 if ! [ "${found}" -o "${count}" -a -z "${device}" ]; then 117 printf "failed.\n" 118 if [ "${#retry}" -eq 5 ]; then 119 [ "${device}" ] && exerr "Device ${device} not found" 120 exerr "No new Bluetooth devices found" 121 fi 122 unset bdaddresses 123 sleep 2 124 continue 125 fi 126 127 [ ${#count} -gt 1 ] && plural=s || plural='' 128 printf "done.\nFound %d new bluetooth device%s " ${#count} ${plural} 129 printf "(now scanning for names):\n" 130 131 # Looping again for the faster feedback 132 unset count 133 for bdaddress in ${bdaddresses}; do 134 count=X${count} 135 bdname=$( /usr/bin/bthost -b "${bdaddress}" 2>/dev/null ) 136 friendlyname=$( /usr/sbin/hccontrol Remote_Name_Request \ 137 ${bdaddress} 2> /dev/null | 138 /usr/bin/grep -o "Name: .*" |/usr/bin/cut -d ' ' -f 2- ) 139 140 # sdpcontrol should be able to pull vendor + product id via sdp 141 printf "[%2d] %s\t\"%s\" (%s)\n" ${#count} "${bdaddress}" \ 142 "${friendlyname}" "${bdname}" 143 144 eval bdaddress_${#count}=\${bdaddress} 145 eval bdname_${#count}=\${bdname} 146 eval friendlyname_${#count}=\${friendlyname} 147 done 148 149 # If a device was pre-selected, do not query the user 150 [ "${device}" ] && topair=1 || unset topair 151 152 # Even if only one device was found, user may chose 0 to rescan 153 while ! [ "${topair}" ]; do 154 prompt="Select device to pair with [1" 155 [ ${#count} -gt 1 ] && prompt="${prompt}-${#count}" 156 read -p "${prompt}, or 0 to rescan]: " topair 157 if ! [ "${topair}" -ge 0 -a "${topair}" -le "${#count}" ] \ 158 2>/dev/null ; then 159 printf "Value out of range: %s.\n" {topair} 160 unset topair 161 fi 162 done 163 164 [ "${topair}" -eq "0" ] && unset bdaddresses retry 165done 166 167eval bdaddress=\${bdaddress_${topair}} 168eval bdname=\${bdname_${topair}} 169eval friendlyname=\${friendlyname_${topair}} 170 171# Do we need to add an entry to /etc/bluetooth/hosts? 172if ! [ "${bdname}" ]; then 173 printf "\nAdding device ${bdaddress} to /etc/bluetooth/hosts.\n" 174 175 while ! [ "${bdname}" ]; do 176 read -p "Enter friendly name. [${friendlyname}]: " _r 177 : ${_r:="${friendlyname}"} 178 179 if [ "${_r}" ]; then 180 # Remove white space and non-friendly characters 181 bdname=$( printf "%s" "${_r}" | tr -c '[:alnum:]-,.' _ ) 182 if [ "${_r}" != "${bdname}" ]; then 183 printf "Notice: Using sanitized name" 184 printf "\"%s\" in /etc/bluetooth/hosts.\n" \ 185 "${bdname}" 186 fi 187 fi 188 done 189 190 printf "%s\t%s\n" "${bdaddress}" "${bdname}" >> /etc/bluetooth/hosts 191fi 192 193# If scanning for the name did not succeed, resort to bdname 194: ${friendlyname:="${bdname}"} 195 196# now over to hcsecd 197 198# Since hcsecd does not allow querying for known devices, we need to 199# check for bdaddr entries manually. 200# 201# Also we cannot really modify the PIN in an existing entry. So we 202# need to prompt the user to manually do it and restart this script 203if ! /usr/sbin/service hcsecd enabled; then 204 printf "\nWarning: hcsecd is not enabled.\n" 205 printf "This daemon manages pairing requests.\n" 206 read -p "Enable hcsecd? [yes]: " _r 207 case "${_r}" in 208 no|n|NO|N|No|nO) ;; 209 *) /usr/sbin/service hcsecd enable;; 210 esac 211fi 212 213secd_config=$( /usr/sbin/sysrc -n hcsecd_config ) 214secd_entries=$( /usr/bin/grep -Eo "bdaddr[[:space:]]+(${bdaddress}|${bdname})" \ 215 ${secd_config} | awk '{ print $2; }' ) 216 217if [ "${secd_entries}" ]; then 218 printf "\nWarning: An entry for device %s is already present in %s.\n" \ 219 ${secd_entries} ${secd_config} 220 printf "To modify pairing information, edit this file and run\n" 221 printf " service hcsecd restart\n" 222 read -p "Continue? [yes]: " _r 223 case "${_r}" in no|n|NO|N|No|nO) exit;; esac 224else 225 printf "\nWriting pairing information description block to %s.\n" \ 226 ${secd_config} 227 printf "(To get PIN, put device in pairing mode first.)\n" 228 read -p "Enter PIN [nopin]: " pin 229 [ "${pin}" ] && pin=\""${pin}"\" || pin="nopin" 230 231 # Write out new hcsecd config block 232 printf "\ndevice {\n\tbdaddr\t%s;\n\tname\t\"%s\";\n\tkey\tnokey\;\n\tpin\t%s\;\n}\n" \ 233 "${bdaddress}" "${friendlyname}" "${pin}" >> ${secd_config} 234 235 # ... and make daemon reload config 236 # TODO: hcsecd should provide a reload hook 237 /usr/sbin/service hcsecd onerestart 238 239 # TODO: we should check if hcsecd succeeded pairing and revert to an 240 # old version of hcsecd.conf so we can undo adding the block above and 241 # retry with a new PIN 242 # also, if there's a way to force devices to re-pair, try this 243fi 244 245# now check for specific services to be provided by the device 246# first up: HID 247 248/usr/sbin/sdpcontrol -a "${bdaddress}" search HID | \ 249 /usr/bin/grep -q "^Record Handle: " || exit 0 250 251printf "\nThis device provides human interface device services.\n" 252read -p "Set it up? [yes]: " _r 253case "${_r}" in 254 no|n|NO|N|No|nO) exit 0;; 255 *);; 256esac 257 258# Here we have found an HID and were asked to set it up 259# NOTE: look out for the two exit 0 above if you extend this script 260 261if ! /usr/sbin/service bthidd enabled; then 262 printf "\nWarning: bthidd is not enabled." 263 printf "\nThis daemon manages Bluetooth HID devices.\n" 264 read -p "Enable bthidd? [yes]: " _r 265 case "${_r}" in 266 no|n|NO|N|No|nO) ;; 267 *) /usr/sbin/service bthidd enable;; 268 esac 269fi 270 271# Check if bthidd already knows about this device 272bthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \ 273 /usr/bin/grep "${bdaddress}" ) 274 275if [ "${bthidd_known}" ]; then 276 printf "Notice: Device %s already known to bthidd.\n" "${bdaddress}" 277 return 0 278fi 279 280bthidd_config=$( /usr/sbin/sysrc -n bthidd_config ) 281printf "Writing HID descriptor block to %s ... " "${bthidd_config}" 282/usr/sbin/bthidcontrol -a "${bdaddress}" query >> "${bthidd_config}" 283 284# Re-read config to see if we succeeded adding the device 285bthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \ 286 grep "${bdaddress}" ) 287if ! [ "${bthidd_known}" ]; then 288 printf "failed.\n" 289else 290 printf "success.\nTo re-read its config, bthidd must be restarted.\n" 291 printf "Warning: If a Bluetooth keyboard is being used, the connection" 292 printf "might be lost.\n" 293 printf "It can be manually restarted later with\n" 294 printf " service bthidd restart\n" 295 read -p "Restart bthidd now? [yes]: " _r 296 case "${_r}" in 297 no|n|NO|N|No|nO) ;; 298 *) /usr/sbin/service bthidd onerestart;; 299 esac 300fi 301 302} 303 304# After function definitions, main() can use them 305main "$@" 306exit 0 307 308# TODO 309# * If device is a keyboard, offer a text entry test field and if it does 310# not succeed, leave some clues for debugging (i.e. if the node responds 311# to pings, maybe switch keyboard on/off, etc) 312# * Same if device is a mouse, i.e. hexdump /dev/sysmouse. 313# * If device offers DUN profiles, ask the user if an entry in 314# /etc/ppp/ppp.conf should be created 315# * If OPUSH or SPP is offered, refer to the respective man pages to give 316# some clues how to continue 317