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