xref: /freebsd/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh (revision 24e4dcf4ba5e9dedcf89efd358ea3e1fe5867020)
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