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