diff options
author | Dirk Engling <erdgeist@erdgeist.org> | 2016-05-02 00:07:51 +0200 |
---|---|---|
committer | Dirk Engling <erdgeist@erdgeist.org> | 2016-05-02 00:07:51 +0200 |
commit | b8ca6f7735b9735c2ce2b28c337eae3054146feb (patch) | |
tree | 0ede64a0cd3cd89d47f8b024bc45b0925a8ab0f0 |
-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 | ||