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