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