xref: /freebsd/usr.sbin/bluetooth/bluetooth-config/bluetooth-config.sh (revision d0b2dbfa0ecf2bbc9709efc5e20baf8e4b44bbbf)
10fc0faf8SLars Engels#!/bin/sh
20fc0faf8SLars Engels#-
3*e1bd7279SLars Engels# ----------------------------------------------------------------------------
4*e1bd7279SLars Engels# "THE BEER-WARE LICENSE" (Revision 42):
5*e1bd7279SLars Engels# <erdgeist@erdgeist.org> wrote this file. As long as you retain this notice you
6*e1bd7279SLars Engels# can do whatever you want with this stuff. If we meet some day, and you think
7*e1bd7279SLars Engels# this stuff is worth it, you can buy me a beer in return Poul-Henning Kamp
8*e1bd7279SLars Engels# ----------------------------------------------------------------------------
90fc0faf8SLars Engels#
100fc0faf8SLars Engels#
110fc0faf8SLars Engels
120fc0faf8SLars Engels# define our bail out shortcut
130fc0faf8SLars Engelsexerr () { echo -e "Error: $*" >&2 ; exit 1; }
140fc0faf8SLars Engelsprint_syntax () { echo -e "Syntax: $0 scan [-d device] [-n node]"; exit 1; }
150fc0faf8SLars Engels
160fc0faf8SLars Engelsmain() {
170fc0faf8SLars Engelsunset node device started bdaddresses retry
180fc0faf8SLars Engels
190fc0faf8SLars Engels# Only one command at the moment is scan (+ add)
200fc0faf8SLars Engels[ "$#" -eq 1 -a "$1" = "scan" ] || print_syntax
210fc0faf8SLars Engelsshift
220fc0faf8SLars Engels
230fc0faf8SLars Engels# Get command line options
240fc0faf8SLars Engelswhile getopts :d:n: arg; do
250fc0faf8SLars Engels	case ${arg} in
260fc0faf8SLars Engels		d) device="$OPTARG";;
270fc0faf8SLars Engels		n) node="$OPTARG";;
280fc0faf8SLars Engels		?) print_syntax;;
290fc0faf8SLars Engels	esac
300fc0faf8SLars Engelsdone
310fc0faf8SLars Engels
320fc0faf8SLars Engels# No use running without super user rights
33*e1bd7279SLars Engelsif [ $( id -u ) -ne 0 ]; then
34*e1bd7279SLars Engels	exerr "$0 must modify files that belong to root.  Re-run as root."
35*e1bd7279SLars Engelsfi
360fc0faf8SLars Engels
370fc0faf8SLars Engelsknown_nodes=$( /usr/sbin/hccontrol read_node_list 2>/dev/null |\
380fc0faf8SLars Engels	/usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 )
390fc0faf8SLars Engels
400fc0faf8SLars Engels# Check if netgraph knows about any HCI nodes
410fc0faf8SLars Engelsif ! [ "${known_nodes}" ]; then
420fc0faf8SLars Engels	ng_nodes=$( /usr/sbin/ngctl list 2>/dev/null | \
430fc0faf8SLars Engels		/usr/bin/grep -o "Name: .* Type: ubt" |/usr/bin/cut -d' ' -f2 )
440fc0faf8SLars Engels
450fc0faf8SLars Engels	[ "${ng_nodes}" ] || exerr "No Bluetooth host controllers found."
460fc0faf8SLars Engels
470fc0faf8SLars Engels	unset found
480fc0faf8SLars Engels	for n in ${ng_nodes}; do
490fc0faf8SLars Engels		if [ "${n}" = "${node%hci}" ]; then
50*e1bd7279SLars Engels			# Found the node but its stack is not set up? Do it now.
510fc0faf8SLars Engels			/usr/sbin/service bluetooth start ${node%hci} || exit 1
520fc0faf8SLars Engels			found="YES"
530fc0faf8SLars Engels		fi
540fc0faf8SLars Engels	done
550fc0faf8SLars Engels
560fc0faf8SLars Engels	# If we have Bluetooth controller nodes without a set up stack,
570fc0faf8SLars Engels	# ask the user if we shall start it up
580fc0faf8SLars Engels	if ! [ "${found}" ]; then
590fc0faf8SLars Engels		printf "No usable Bluetooth host controllers were found.\n"
60*e1bd7279SLars Engels		printf "These host controllers exist in the system:\n"
61*e1bd7279SLars Engels		printf "  %s\n" "${ng_nodes}"
62*e1bd7279SLars Engels		prompt="Choose a host controller to set up: [${ng_nodes%% *}]"
63*e1bd7279SLars Engels		read -p "${prompt}" node
640fc0faf8SLars Engels		: ${node:="${ng_nodes%% *}"}
650fc0faf8SLars Engels		/usr/sbin/service bluetooth start ${node} || exit 1
660fc0faf8SLars Engels	fi
670fc0faf8SLars Engels
680fc0faf8SLars Engels	# Re-read known nodes
69*e1bd7279SLars Engels	known_nodes=$(/usr/sbin/hccontrol read_node_list 2>/dev/null |
700fc0faf8SLars Engels		/usr/bin/tail -n +2 | /usr/bin/cut -d ' ' -f 1 )
71*e1bd7279SLars Engels
720fc0faf8SLars Engels	# check if we succeeded in bringing it up
730fc0faf8SLars Engels	[ "${known_nodes}" ] || exerr "Failed to set up Bluetooth stack"
740fc0faf8SLars Engelsfi
750fc0faf8SLars Engels
760fc0faf8SLars Engels# if a node was requested on command line, check if it is there
770fc0faf8SLars Engelsif [ "${node}" ]; then
780fc0faf8SLars Engels	unset found
790fc0faf8SLars Engels	for n in ${known_nodes}; do
800fc0faf8SLars Engels		[ "${n}" = "${node}" ] && found="YES"
810fc0faf8SLars Engels		[ "${n}" = "${node}hci" ] && node="${node}hci" && found="YES"
820fc0faf8SLars Engels	done
830fc0faf8SLars Engels	[ "${found}" ] || exerr "Node ${node} not found"
840fc0faf8SLars Engelsfi
850fc0faf8SLars Engels
860fc0faf8SLars Engels[ "${node}" ] && node="-n ${node}"
870fc0faf8SLars Engels
880fc0faf8SLars Engelswhile ! [ "${bdaddresses}" ]; do
890fc0faf8SLars Engels	retry=X${retry}
90*e1bd7279SLars Engels	printf "Scanning for new Bluetooth devices (Attempt %d of 5) ... " \
91*e1bd7279SLars Engels		${#retry}
92*e1bd7279SLars Engels	bdaddresses=$( /usr/sbin/hccontrol -N ${node} inquiry 2>/dev/null |
930fc0faf8SLars Engels		/usr/bin/grep -o "BD_ADDR: .*" | /usr/bin/cut -d ' ' -f 2 )
940fc0faf8SLars Engels
950fc0faf8SLars Engels	# Count entries and, if a device was requested on command line,
960fc0faf8SLars Engels	# try to find it
970fc0faf8SLars Engels	unset found count
980fc0faf8SLars Engels	for bdaddress in ${bdaddresses}; do
990fc0faf8SLars Engels		count=X${count}
1000fc0faf8SLars Engels		if [ "${bdaddress}" = "${device}" ]; then
1010fc0faf8SLars Engels			found=YES
1020fc0faf8SLars Engels			bdaddresses="${device}"
1030fc0faf8SLars Engels			count=X
1040fc0faf8SLars Engels			break
1050fc0faf8SLars Engels		fi
1060fc0faf8SLars Engels	done
1070fc0faf8SLars Engels
1080fc0faf8SLars Engels	# If device was requested on command line but is not found,
1090fc0faf8SLars Engels	# or no devices found at all, rescan until retry is exhausted
1100fc0faf8SLars Engels	if ! [ "${found}" -o "${count}" -a -z "${device}" ]; then
1110fc0faf8SLars Engels		printf "failed.\n"
1120fc0faf8SLars Engels		if [ "${#retry}" -eq 5 ]; then
1130fc0faf8SLars Engels			[ "${device}" ] && exerr "Device ${device} not found"
1140fc0faf8SLars Engels			exerr "No new Bluetooth devices found"
1150fc0faf8SLars Engels		fi
1160fc0faf8SLars Engels		unset bdaddresses
1170fc0faf8SLars Engels		sleep 2
1180fc0faf8SLars Engels		continue
1190fc0faf8SLars Engels	fi
1200fc0faf8SLars Engels
1210fc0faf8SLars Engels	[ ${#count} -gt 1 ] && plural=s || plural=''
122*e1bd7279SLars Engels	printf "done.\nFound %d new bluetooth device%s " ${#count} ${plural}
123*e1bd7279SLars Engels	printf "(now scanning for names):\n"
1240fc0faf8SLars Engels
1250fc0faf8SLars Engels	# Looping again for the faster feedback
1260fc0faf8SLars Engels	unset count
1270fc0faf8SLars Engels	for bdaddress in ${bdaddresses}; do
1280fc0faf8SLars Engels		count=X${count}
1290fc0faf8SLars Engels		bdname=$( /usr/bin/bthost -b "${bdaddress}" 2>/dev/null )
130*e1bd7279SLars Engels		friendlyname=$( /usr/sbin/hccontrol Remote_Name_Request \
131*e1bd7279SLars Engels			${bdaddress} 2> /dev/null |
1320fc0faf8SLars Engels			/usr/bin/grep -o "Name: .*" |/usr/bin/cut -d ' ' -f 2- )
1330fc0faf8SLars Engels
134*e1bd7279SLars Engels		# sdpcontrol should be able to pull vendor + product id via sdp
135*e1bd7279SLars Engels		printf "[%2d] %s\t\"%s\" (%s)\n" ${#count} "${bdaddress}" \
136*e1bd7279SLars Engels			"${friendlyname}" "${bdname}"
1370fc0faf8SLars Engels
1380fc0faf8SLars Engels		eval bdaddress_${#count}=\${bdaddress}
1390fc0faf8SLars Engels		eval bdname_${#count}=\${bdname}
1400fc0faf8SLars Engels		eval friendlyname_${#count}=\${friendlyname}
1410fc0faf8SLars Engels	done
1420fc0faf8SLars Engels
1430fc0faf8SLars Engels	# If a device was pre-selected, do not query the user
1440fc0faf8SLars Engels	[ "${device}" ] && topair=1 || unset topair
1450fc0faf8SLars Engels
1460fc0faf8SLars Engels	# Even if only one device was found, user may chose 0 to rescan
1470fc0faf8SLars Engels	while ! [ "${topair}" ]; do
148*e1bd7279SLars Engels		prompt="Select device to pair with [1"
149*e1bd7279SLars Engels		[ ${#count} -gt 1 ] && prompt="${prompt}-${#count}"
150*e1bd7279SLars Engels		read -p "${prompt}, or 0 to rescan]: " topair
151*e1bd7279SLars Engels		if ! [ "${topair}" -ge 0 -a "${topair}" -le "${#count}" ] \
152*e1bd7279SLars Engels			2>/dev/null ; then
1530fc0faf8SLars Engels			printf "Value out of range: %s.\n" {topair}
1540fc0faf8SLars Engels			unset topair
1550fc0faf8SLars Engels		fi
1560fc0faf8SLars Engels	done
1570fc0faf8SLars Engels
1580fc0faf8SLars Engels	[ "${topair}" -eq "0" ] && unset bdaddresses retry
1590fc0faf8SLars Engelsdone
1600fc0faf8SLars Engels
1610fc0faf8SLars Engelseval bdaddress=\${bdaddress_${topair}}
1620fc0faf8SLars Engelseval bdname=\${bdname_${topair}}
1630fc0faf8SLars Engelseval friendlyname=\${friendlyname_${topair}}
1640fc0faf8SLars Engels
1650fc0faf8SLars Engels# Do we need to add an entry to /etc/bluetooth/hosts?
1660fc0faf8SLars Engelsif ! [ "${bdname}" ]; then
1670fc0faf8SLars Engels	printf "\nAdding device ${bdaddress} to /etc/bluetooth/hosts.\n"
1680fc0faf8SLars Engels
1690fc0faf8SLars Engels	while ! [ "${bdname}" ]; do
170*e1bd7279SLars Engels		read -p "Enter friendly name. [${friendlyname}]: " _r
171*e1bd7279SLars Engels		: ${_r:="${friendlyname}"}
1720fc0faf8SLars Engels
173*e1bd7279SLars Engels		if [ "${_r}" ]; then
1740fc0faf8SLars Engels			# Remove white space and non-friendly characters
175*e1bd7279SLars Engels			bdname=$( printf "%s" "${_r}" | tr -c '[:alnum:]-,.' _ )
176*e1bd7279SLars Engels			if [ "${_r}" != "${bdname}" ]; then
177*e1bd7279SLars Engels				printf "Notice: Using sanitized name"
178*e1bd7279SLars Engels				printf "\"%s\" in /etc/bluetooth/hosts.\n" \
179*e1bd7279SLars Engels					"${bdname}"
180*e1bd7279SLars Engels			fi
1810fc0faf8SLars Engels		fi
1820fc0faf8SLars Engels	done
1830fc0faf8SLars Engels
1840fc0faf8SLars Engels	printf "%s\t%s\n" "${bdaddress}" "${bdname}" >> /etc/bluetooth/hosts
1850fc0faf8SLars Engelsfi
1860fc0faf8SLars Engels
1870fc0faf8SLars Engels# If scanning for the name did not succeed, resort to bdname
1880fc0faf8SLars Engels: ${friendlyname:="${bdname}"}
1890fc0faf8SLars Engels
1900fc0faf8SLars Engels# now over to hcsecd
1910fc0faf8SLars Engels
1920fc0faf8SLars Engels# Since hcsecd does not allow querying for known devices, we need to
1930fc0faf8SLars Engels# check for bdaddr entries manually.
1940fc0faf8SLars Engels#
1950fc0faf8SLars Engels# Also we cannot really modify the PIN in an existing entry. So we
1960fc0faf8SLars Engels# need to prompt the user to manually do it and restart this script
1970fc0faf8SLars Engelsif ! /usr/sbin/service hcsecd enabled; then
198*e1bd7279SLars Engels	printf "\nWarning: hcsecd is not enabled.\n"
199*e1bd7279SLars Engels	printf "This daemon manages pairing requests.\n"
200*e1bd7279SLars Engels	read -p "Enable hcsecd? [yes]: " _r
201*e1bd7279SLars Engels	case "${_r}" in
202*e1bd7279SLars Engels		no|n|NO|N|No|nO) ;;
203*e1bd7279SLars Engels		*) /usr/sbin/service hcsecd enable;;
204*e1bd7279SLars Engels	esac
2050fc0faf8SLars Engelsfi
206*e1bd7279SLars Engels
2070fc0faf8SLars Engelssecd_config=$( /usr/sbin/sysrc -n hcsecd_config )
208*e1bd7279SLars Engelssecd_entries=$( /usr/bin/grep -Eo "bdaddr[[:space:]]+(${bdaddress}|${bdname})" \
209*e1bd7279SLars Engels	${secd_config} | awk '{ print $2; }' )
2100fc0faf8SLars Engels
2110fc0faf8SLars Engelsif [ "${secd_entries}" ]; then
212*e1bd7279SLars Engels	printf "\nWarning: An entry for device %s is already present in %s.\n" \
213*e1bd7279SLars Engels		${secd_entries} ${secd_config}
214*e1bd7279SLars Engels	printf "To modify pairing information, edit this file and run\n"
215*e1bd7279SLars Engels	printf "  service hcsecd restart\n"
216*e1bd7279SLars Engels	read -p "Continue? [yes]: " _r
217*e1bd7279SLars Engels	case "${_r}" in no|n|NO|N|No|nO) exit;; esac
2180fc0faf8SLars Engelselse
219*e1bd7279SLars Engels	printf "\nWriting pairing information description block to %s.\n" \
220*e1bd7279SLars Engels		${secd_config}
2210fc0faf8SLars Engels	printf "(To get PIN, put device in pairing mode first.)\n"
2220fc0faf8SLars Engels	read -p "Enter PIN [nopin]: " pin
2230fc0faf8SLars Engels	[ "${pin}" ] && pin=\""${pin}"\" || pin="nopin"
2240fc0faf8SLars Engels
2250fc0faf8SLars Engels	# Write out new hcsecd config block
2260fc0faf8SLars Engels	printf "\ndevice {\n\tbdaddr\t%s;\n\tname\t\"%s\";\n\tkey\tnokey\;\n\tpin\t%s\;\n}\n" \
2270fc0faf8SLars Engels		"${bdaddress}" "${friendlyname}" "${pin}" >> ${secd_config}
2280fc0faf8SLars Engels
229*e1bd7279SLars Engels	# ... and make daemon reload config
230*e1bd7279SLars Engels	# TODO: hcsecd should provide a reload hook
231*e1bd7279SLars Engels	/usr/sbin/service hcsecd onerestart
2320fc0faf8SLars Engels
233*e1bd7279SLars Engels	# TODO: we should check if hcsecd succeeded pairing and revert to an
234*e1bd7279SLars Engels	# old version of hcsecd.conf so we can undo adding the block above and
235*e1bd7279SLars Engels	# retry with a new PIN
2360fc0faf8SLars Engels	# also, if there's a way to force devices to re-pair, try this
2370fc0faf8SLars Engelsfi
2380fc0faf8SLars Engels
2390fc0faf8SLars Engels# now check for specific services to be provided by the device
2400fc0faf8SLars Engels# first up: HID
2410fc0faf8SLars Engels
242*e1bd7279SLars Engels/usr/sbin/sdpcontrol -a "${bdaddress}" search HID | \
243*e1bd7279SLars Engels	/usr/bin/grep -q "^Record Handle: " || exit 0
2440fc0faf8SLars Engels
2450fc0faf8SLars Engelsprintf "\nThis device provides human interface device services.\n"
246*e1bd7279SLars Engelsread -p "Set it up? [yes]: " _r
247*e1bd7279SLars Engelscase "${_r}" in
248*e1bd7279SLars Engels	no|n|NO|N|No|nO) exit 0;;
249*e1bd7279SLars Engels	*);;
250*e1bd7279SLars Engelsesac
251*e1bd7279SLars Engels
252*e1bd7279SLars Engels# Here we have found an HID and were asked to set it up
253*e1bd7279SLars Engels# NOTE: look out for the two exit 0 above if you extend this script
254*e1bd7279SLars Engels
2550fc0faf8SLars Engelsif ! /usr/sbin/service bthidd enabled; then
2560fc0faf8SLars Engels	printf "\nWarning: bthidd is not enabled."
2570fc0faf8SLars Engels	printf "\nThis daemon manages Bluetooth HID devices.\n"
258*e1bd7279SLars Engels	read -p "Enable bthidd? [yes]: " _r
259*e1bd7279SLars Engels	case "${_r}" in
260*e1bd7279SLars Engels		no|n|NO|N|No|nO) ;;
261*e1bd7279SLars Engels		 *) /usr/sbin/service bthidd enable;;
262*e1bd7279SLars Engels	esac
2630fc0faf8SLars Engelsfi
2640fc0faf8SLars Engels
2650fc0faf8SLars Engels# Check if bthidd already knows about this device
2660fc0faf8SLars Engelsbthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \
2670fc0faf8SLars Engels	/usr/bin/grep "${bdaddress}" )
268*e1bd7279SLars Engels
2690fc0faf8SLars Engelsif [ "${bthidd_known}" ]; then
2700fc0faf8SLars Engels	printf "Notice: Device %s already known to bthidd.\n" "${bdaddress}"
271*e1bd7279SLars Engels	return 0
272*e1bd7279SLars Engelsfi
273*e1bd7279SLars Engels
2740fc0faf8SLars Engelsbthidd_config=$( /usr/sbin/sysrc -n bthidd_config )
2750fc0faf8SLars Engelsprintf "Writing HID descriptor block to %s ... " "${bthidd_config}"
2760fc0faf8SLars Engels/usr/sbin/bthidcontrol -a "${bdaddress}" query >> "${bthidd_config}"
2770fc0faf8SLars Engels
2780fc0faf8SLars Engels# Re-read config to see if we succeeded adding the device
2790fc0faf8SLars Engelsbthidd_known=$( /usr/sbin/bthidcontrol -a "${bdaddress}" known | \
2800fc0faf8SLars Engels	grep "${bdaddress}" )
2810fc0faf8SLars Engelsif ! [ "${bthidd_known}" ]; then
2820fc0faf8SLars Engels	printf "failed.\n"
2830fc0faf8SLars Engelselse
2840fc0faf8SLars Engels	printf "success.\nTo re-read its config, bthidd must be restarted.\n"
285*e1bd7279SLars Engels	printf "Warning: If a Bluetooth keyboard is being used, the connection"
286*e1bd7279SLars Engels	printf "might be lost.\n"
287*e1bd7279SLars Engels	printf "It can be manually restarted later with\n"
288*e1bd7279SLars Engels	printf " service bthidd restart\n"
289*e1bd7279SLars Engels	read -p "Restart bthidd now? [yes]: " _r
290*e1bd7279SLars Engels	case "${_r}" in
291*e1bd7279SLars Engels		no|n|NO|N|No|nO) ;;
292*e1bd7279SLars Engels		*) /usr/sbin/service bthidd onerestart;;
2930fc0faf8SLars Engels	esac
2940fc0faf8SLars Engelsfi
2950fc0faf8SLars Engels
2960fc0faf8SLars Engels}
2970fc0faf8SLars Engels
2980fc0faf8SLars Engels# After function definitions, main() can use them
2990fc0faf8SLars Engelsmain "$@"
300*e1bd7279SLars Engelsexit 0
3010fc0faf8SLars Engels
3020fc0faf8SLars Engels# TODO
3030fc0faf8SLars Engels# * If device is a keyboard, offer a text entry test field and if it does
3040fc0faf8SLars Engels#   not succeed, leave some clues for debugging (i.e. if the node responds
3050fc0faf8SLars Engels#   to pings, maybe switch keyboard on/off, etc)
3060fc0faf8SLars Engels# * Same if device is a mouse, i.e. hexdump /dev/sysmouse.
3070fc0faf8SLars Engels# * If device offers DUN profiles, ask the user if an entry in
3080fc0faf8SLars Engels#   /etc/ppp/ppp.conf should be created
3090fc0faf8SLars Engels# * If OPUSH or SPP is offered, refer to the respective man pages to give
3100fc0faf8SLars Engels#   some clues how to continue
311