| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2006-2010 Nokia Corporation |
| * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <ctype.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <time.h> |
| #include <sys/file.h> |
| #include <sys/stat.h> |
| |
| #include <glib.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/sdp.h> |
| #include <bluetooth/sdp_lib.h> |
| #include <bluetooth/uuid.h> |
| |
| #include "textfile.h" |
| #include "glib-helper.h" |
| #include "storage.h" |
| |
| /* When all services should trust a remote device */ |
| #define GLOBAL_TRUST "[all]" |
| |
| struct match { |
| GSList *keys; |
| char *pattern; |
| }; |
| |
| static inline int create_filename(char *buf, size_t size, |
| const bdaddr_t *bdaddr, const char *name) |
| { |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| |
| return create_name(buf, size, STORAGEDIR, addr, name); |
| } |
| |
| int read_device_alias(const char *src, const char *dst, uint8_t dst_type, |
| char *alias, size_t size) |
| { |
| char filename[PATH_MAX + 1], *tmp; |
| char key[20]; |
| int err; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "aliases"); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu", dst, dst_type); |
| |
| tmp = textfile_get(filename, key); |
| if (tmp != NULL) |
| goto done; |
| |
| /* Try old format (address only) */ |
| key[17] = '\0'; |
| |
| tmp = textfile_get(filename, key); |
| if (tmp == NULL) |
| return -ENXIO; |
| |
| done: |
| err = snprintf(alias, size, "%s", tmp); |
| |
| free(tmp); |
| |
| return err < 0 ? -EIO : 0; |
| } |
| |
| int write_device_alias(const char *src, const char *dst, uint8_t dst_type, |
| const char *alias) |
| { |
| char filename[PATH_MAX + 1]; |
| char key[20]; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "aliases"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu", dst, dst_type); |
| |
| return textfile_put(filename, key, alias); |
| } |
| |
| int write_discoverable_timeout(const bdaddr_t *bdaddr, int timeout) |
| { |
| char filename[PATH_MAX + 1], str[32]; |
| |
| snprintf(str, sizeof(str), "%d", timeout); |
| |
| create_filename(filename, PATH_MAX, bdaddr, "config"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| return textfile_put(filename, "discovto", str); |
| } |
| |
| int read_discoverable_timeout(const char *src, int *timeout) |
| { |
| char filename[PATH_MAX + 1], *str; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); |
| |
| str = textfile_get(filename, "discovto"); |
| if (!str) |
| return -ENOENT; |
| |
| if (sscanf(str, "%d", timeout) != 1) { |
| free(str); |
| return -ENOENT; |
| } |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int write_pairable_timeout(const bdaddr_t *bdaddr, int timeout) |
| { |
| char filename[PATH_MAX + 1], str[32]; |
| |
| snprintf(str, sizeof(str), "%d", timeout); |
| |
| create_filename(filename, PATH_MAX, bdaddr, "config"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| return textfile_put(filename, "pairto", str); |
| } |
| |
| int read_pairable_timeout(const char *src, int *timeout) |
| { |
| char filename[PATH_MAX + 1], *str; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); |
| |
| str = textfile_get(filename, "pairto"); |
| if (!str) |
| return -ENOENT; |
| |
| if (sscanf(str, "%d", timeout) != 1) { |
| free(str); |
| return -ENOENT; |
| } |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int write_device_mode(const bdaddr_t *bdaddr, const char *mode) |
| { |
| char filename[PATH_MAX + 1]; |
| |
| create_filename(filename, PATH_MAX, bdaddr, "config"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| if (strcmp(mode, "off") != 0) |
| textfile_put(filename, "onmode", mode); |
| |
| return textfile_put(filename, "mode", mode); |
| } |
| |
| int read_device_mode(const char *src, char *mode, int length) |
| { |
| char filename[PATH_MAX + 1], *str; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); |
| |
| str = textfile_get(filename, "mode"); |
| if (!str) |
| return -ENOENT; |
| |
| strncpy(mode, str, length); |
| mode[length - 1] = '\0'; |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int read_on_mode(const char *src, char *mode, int length) |
| { |
| char filename[PATH_MAX + 1], *str; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); |
| |
| str = textfile_get(filename, "onmode"); |
| if (!str) |
| return -ENOENT; |
| |
| strncpy(mode, str, length); |
| mode[length - 1] = '\0'; |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int write_local_name(const bdaddr_t *bdaddr, const char *name) |
| { |
| char filename[PATH_MAX + 1], str[249]; |
| int i; |
| |
| memset(str, 0, sizeof(str)); |
| for (i = 0; i < 248 && name[i]; i++) |
| if ((unsigned char) name[i] < 32 || name[i] == 127) |
| str[i] = '.'; |
| else |
| str[i] = name[i]; |
| |
| create_filename(filename, PATH_MAX, bdaddr, "config"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| return textfile_put(filename, "name", str); |
| } |
| |
| int read_local_name(const bdaddr_t *bdaddr, char *name) |
| { |
| char filename[PATH_MAX + 1], *str; |
| int len; |
| |
| create_filename(filename, PATH_MAX, bdaddr, "config"); |
| |
| str = textfile_get(filename, "name"); |
| if (!str) |
| return -ENOENT; |
| |
| len = strlen(str); |
| if (len > HCI_MAX_NAME_LENGTH) |
| str[HCI_MAX_NAME_LENGTH] = '\0'; |
| strcpy(name, str); |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int write_local_class(const bdaddr_t *bdaddr, uint8_t *class) |
| { |
| char filename[PATH_MAX + 1], str[9]; |
| |
| sprintf(str, "0x%2.2x%2.2x%2.2x", class[2], class[1], class[0]); |
| |
| create_filename(filename, PATH_MAX, bdaddr, "config"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| return textfile_put(filename, "class", str); |
| } |
| |
| int read_local_class(const bdaddr_t *bdaddr, uint8_t *class) |
| { |
| char filename[PATH_MAX + 1], tmp[3], *str; |
| int i; |
| |
| create_filename(filename, PATH_MAX, bdaddr, "config"); |
| |
| str = textfile_get(filename, "class"); |
| if (!str) |
| return -ENOENT; |
| |
| memset(tmp, 0, sizeof(tmp)); |
| for (i = 0; i < 3; i++) { |
| memcpy(tmp, str + (i * 2) + 2, 2); |
| class[2 - i] = (uint8_t) strtol(tmp, NULL, 16); |
| } |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int read_remote_appearance(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t bdaddr_type, uint16_t *appearance) |
| { |
| char filename[PATH_MAX + 1], key[20], *str; |
| |
| create_filename(filename, PATH_MAX, local, "appearances"); |
| |
| ba2str(peer, key); |
| sprintf(&key[17], "#%hhu", bdaddr_type); |
| |
| str = textfile_get(filename, key); |
| if (!str) |
| return -ENOENT; |
| |
| if (sscanf(str, "%hx", appearance) != 1) { |
| free(str); |
| return -ENOENT; |
| } |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int write_remote_appearance(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t bdaddr_type, uint16_t appearance) |
| { |
| char filename[PATH_MAX + 1], key[20], str[7]; |
| |
| create_filename(filename, PATH_MAX, local, "appearances"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(peer, key); |
| sprintf(&key[17], "#%hhu", bdaddr_type); |
| |
| sprintf(str, "0x%4.4x", appearance); |
| |
| return textfile_put(filename, key, str); |
| } |
| |
| int write_remote_class(const bdaddr_t *local, const bdaddr_t *peer, |
| uint32_t class) |
| { |
| char filename[PATH_MAX + 1], addr[18], str[9]; |
| |
| create_filename(filename, PATH_MAX, local, "classes"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(peer, addr); |
| sprintf(str, "0x%6.6x", class); |
| |
| return textfile_put(filename, addr, str); |
| } |
| |
| int read_remote_class(const bdaddr_t *local, const bdaddr_t *peer, |
| uint32_t *class) |
| { |
| char filename[PATH_MAX + 1], addr[18], *str; |
| |
| create_filename(filename, PATH_MAX, local, "classes"); |
| |
| ba2str(peer, addr); |
| |
| str = textfile_get(filename, addr); |
| if (!str) |
| return -ENOENT; |
| |
| if (sscanf(str, "%x", class) != 1) { |
| free(str); |
| return -ENOENT; |
| } |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int write_device_name(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t peer_type, const char *name) |
| { |
| char filename[PATH_MAX + 1], key[20], str[HCI_MAX_NAME_LENGTH + 1]; |
| int i; |
| |
| memset(str, 0, sizeof(str)); |
| for (i = 0; i < HCI_MAX_NAME_LENGTH && name[i]; i++) |
| if ((unsigned char) name[i] < 32 || name[i] == 127) |
| str[i] = '.'; |
| else |
| str[i] = name[i]; |
| |
| create_filename(filename, PATH_MAX, local, "names"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(peer, key); |
| sprintf(&key[17], "#%hhu", peer_type); |
| |
| return textfile_put(filename, key, str); |
| } |
| |
| int read_device_name(const char *src, const char *dst, uint8_t dst_type, |
| char *name) |
| { |
| char filename[PATH_MAX + 1], *str, key[20]; |
| int len; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "names"); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu", dst, dst_type); |
| |
| str = textfile_get(filename, key); |
| if (str != NULL) |
| goto done; |
| |
| /* Try old format (address only) */ |
| key[17] = '\0'; |
| |
| str = textfile_get(filename, key); |
| if (str == NULL) |
| return -ENOENT; |
| |
| done: |
| len = strlen(str); |
| if (len > HCI_MAX_NAME_LENGTH) |
| str[HCI_MAX_NAME_LENGTH] = '\0'; |
| strcpy(name, str); |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int write_lastseen_info(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t peer_type, struct tm *tm) |
| { |
| char filename[PATH_MAX + 1], key[20], str[24]; |
| |
| memset(str, 0, sizeof(str)); |
| strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %Z", tm); |
| |
| create_filename(filename, PATH_MAX, local, "lastseen"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(peer, key); |
| sprintf(&key[17], "#%hhu", peer_type); |
| |
| return textfile_put(filename, key, str); |
| } |
| |
| int write_lastused_info(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t peer_type, struct tm *tm) |
| { |
| char filename[PATH_MAX + 1], key[20], str[24]; |
| |
| memset(str, 0, sizeof(str)); |
| strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %Z", tm); |
| |
| create_filename(filename, PATH_MAX, local, "lastused"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(peer, key); |
| sprintf(&key[17], "#%hhu", peer_type); |
| |
| return textfile_put(filename, key, str); |
| } |
| |
| int write_link_key(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t peer_type, unsigned char *key, |
| uint8_t type, int length) |
| { |
| char filename[PATH_MAX + 1], addr[20], str[38]; |
| int i; |
| |
| memset(str, 0, sizeof(str)); |
| for (i = 0; i < 16; i++) |
| sprintf(str + (i * 2), "%2.2X", key[i]); |
| sprintf(str + 32, " %d %d", type, length); |
| |
| create_filename(filename, PATH_MAX, local, "linkkeys"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR); |
| |
| ba2str(peer, addr); |
| sprintf(&addr[17], "#%hhu", peer_type); |
| |
| if (length < 0) { |
| char *tmp = textfile_get(filename, addr); |
| if (tmp) { |
| if (strlen(tmp) > 34) |
| memcpy(str + 34, tmp + 34, 3); |
| free(tmp); |
| } |
| } |
| |
| return textfile_put(filename, addr, str); |
| } |
| |
| int read_link_key(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t peer_type, unsigned char *key, |
| uint8_t *type) |
| { |
| char filename[PATH_MAX + 1], addr[20], tmp[3], *str; |
| int i; |
| |
| create_filename(filename, PATH_MAX, local, "linkkeys"); |
| |
| ba2str(peer, addr); |
| sprintf(&addr[17], "#%hhu", peer_type); |
| |
| str = textfile_get(filename, addr); |
| if (str != NULL) |
| goto done; |
| |
| /* Try old format (address only) */ |
| addr[17] = '\0'; |
| |
| str = textfile_get(filename, addr); |
| if (!str) |
| return -ENOENT; |
| |
| done: |
| if (!key) { |
| free(str); |
| return 0; |
| } |
| |
| memset(tmp, 0, sizeof(tmp)); |
| for (i = 0; i < 16; i++) { |
| memcpy(tmp, str + (i * 2), 2); |
| key[i] = (uint8_t) strtol(tmp, NULL, 16); |
| } |
| |
| if (type) { |
| memcpy(tmp, str + 33, 2); |
| *type = (uint8_t) strtol(tmp, NULL, 10); |
| } |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| ssize_t read_pin_code(const bdaddr_t *local, const bdaddr_t *peer, char *pin) |
| { |
| char filename[PATH_MAX + 1], addr[18], *str; |
| ssize_t len; |
| |
| create_filename(filename, PATH_MAX, local, "pincodes"); |
| |
| ba2str(peer, addr); |
| str = textfile_get(filename, addr); |
| if (!str) |
| return -ENOENT; |
| |
| strncpy(pin, str, 16); |
| len = strlen(pin); |
| |
| free(str); |
| |
| return len; |
| } |
| |
| int write_trust(const char *src, const char *addr, uint8_t addr_type, |
| gboolean trust) |
| { |
| char filename[PATH_MAX + 1], key[20]; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "trusts"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu", addr, addr_type); |
| |
| if (trust == FALSE) |
| return textfile_casedel(filename, key); |
| |
| return textfile_caseput(filename, key, GLOBAL_TRUST); |
| } |
| |
| gboolean read_trust(const bdaddr_t *local, const char *addr, uint8_t addr_type) |
| { |
| char filename[PATH_MAX + 1], key[20], *str; |
| gboolean ret; |
| |
| create_filename(filename, PATH_MAX, local, "trusts"); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu", addr, addr_type); |
| |
| str = textfile_caseget(filename, key); |
| if (str == NULL) |
| /* Try old format (address only) */ |
| str = textfile_caseget(filename, addr); |
| |
| if (str == NULL) |
| return FALSE; |
| |
| if (strcmp(str, GLOBAL_TRUST) == 0) |
| ret = TRUE; |
| else |
| ret = FALSE; |
| |
| free(str); |
| return ret; |
| } |
| |
| int write_device_profiles(const bdaddr_t *src, const bdaddr_t *dst, |
| uint8_t dst_type, const char *profiles) |
| { |
| char filename[PATH_MAX + 1], key[20]; |
| |
| if (!profiles) |
| return -EINVAL; |
| |
| create_filename(filename, PATH_MAX, src, "profiles"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(dst, key); |
| sprintf(&key[17], "#%hhu", dst_type); |
| |
| return textfile_put(filename, key, profiles); |
| } |
| |
| int delete_entry(const bdaddr_t *src, const char *storage, const bdaddr_t *dst, |
| uint8_t dst_type) |
| { |
| char filename[PATH_MAX + 1], key[20]; |
| int err, ret; |
| |
| ba2str(dst, key); |
| sprintf(&key[17], "#%hhu", dst_type); |
| |
| create_filename(filename, PATH_MAX, src, storage); |
| |
| err = 0; |
| ret = textfile_del(filename, key); |
| if (ret) |
| err = ret; |
| |
| /* Trying without address type */ |
| key[17] = '\0'; |
| ret = textfile_del(filename, key); |
| if (ret) |
| err = ret; |
| |
| return err; |
| } |
| |
| int store_record(const gchar *src, const gchar *dst, uint8_t dst_type, |
| sdp_record_t *rec) |
| { |
| char filename[PATH_MAX + 1], key[30]; |
| sdp_buf_t buf; |
| int err, size, i; |
| char *str; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu#%08X", dst, dst_type, |
| rec->handle); |
| |
| if (sdp_gen_record_pdu(rec, &buf) < 0) |
| return -1; |
| |
| size = buf.data_size; |
| |
| str = g_malloc0(size*2+1); |
| |
| for (i = 0; i < size; i++) |
| sprintf(str + (i * 2), "%02X", buf.data[i]); |
| |
| err = textfile_put(filename, key, str); |
| |
| free(buf.data); |
| g_free(str); |
| |
| return err; |
| } |
| |
| sdp_record_t *record_from_string(const gchar *str) |
| { |
| sdp_record_t *rec; |
| int size, i, len; |
| uint8_t *pdata; |
| char tmp[3]; |
| |
| size = strlen(str)/2; |
| pdata = g_malloc0(size); |
| |
| tmp[2] = 0; |
| for (i = 0; i < size; i++) { |
| memcpy(tmp, str + (i * 2), 2); |
| pdata[i] = (uint8_t) strtol(tmp, NULL, 16); |
| } |
| |
| rec = sdp_extract_pdu(pdata, size, &len); |
| g_free(pdata); |
| |
| return rec; |
| } |
| |
| |
| sdp_record_t *fetch_record(const gchar *src, const gchar *dst, |
| uint8_t dst_type, const uint32_t handle) |
| { |
| char filename[PATH_MAX + 1], old_key[28], key[30], *str; |
| sdp_record_t *rec; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp"); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu#%08X", dst, dst_type, handle); |
| snprintf(old_key, sizeof(old_key), "%17s#%08X", dst, handle); |
| |
| str = textfile_get(filename, key); |
| if (str != NULL) |
| goto done; |
| |
| /* Try old format (address#handle) */ |
| str = textfile_get(filename, old_key); |
| if (str == NULL) |
| return NULL; |
| |
| done: |
| rec = record_from_string(str); |
| free(str); |
| |
| return rec; |
| } |
| |
| int delete_record(const gchar *src, const gchar *dst, uint8_t dst_type, |
| const uint32_t handle) |
| { |
| char filename[PATH_MAX + 1], old_key[28], key[30]; |
| int err, ret; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp"); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu#%08X", dst, dst_type, handle); |
| snprintf(old_key, sizeof(old_key), "%17s#%08X", dst, handle); |
| |
| err = 0; |
| ret = textfile_del(filename, key); |
| if (ret) |
| err = ret; |
| |
| /* Try old format (address#handle) */ |
| ret = textfile_del(filename, old_key); |
| if (ret) |
| err = ret; |
| |
| return err; |
| } |
| |
| struct record_list { |
| sdp_list_t *recs; |
| const gchar *addr; |
| }; |
| |
| static void create_stored_records_from_keys(char *key, char *value, |
| void *user_data) |
| { |
| struct record_list *rec_list = user_data; |
| const gchar *addr = rec_list->addr; |
| sdp_record_t *rec; |
| |
| if (strncmp(key, addr, 17)) |
| return; |
| |
| rec = record_from_string(value); |
| |
| rec_list->recs = sdp_list_append(rec_list->recs, rec); |
| } |
| |
| void delete_all_records(const bdaddr_t *src, const bdaddr_t *dst, |
| uint8_t dst_type) |
| { |
| sdp_list_t *records, *seq; |
| char srcaddr[18], dstaddr[18]; |
| |
| ba2str(src, srcaddr); |
| ba2str(dst, dstaddr); |
| |
| records = read_records(src, dst); |
| |
| for (seq = records; seq; seq = seq->next) { |
| sdp_record_t *rec = seq->data; |
| delete_record(srcaddr, dstaddr, dst_type, rec->handle); |
| } |
| |
| if (records) |
| sdp_list_free(records, (sdp_free_func_t) sdp_record_free); |
| } |
| |
| sdp_list_t *read_records(const bdaddr_t *src, const bdaddr_t *dst) |
| { |
| char filename[PATH_MAX + 1]; |
| struct record_list rec_list; |
| char srcaddr[18], dstaddr[18]; |
| |
| ba2str(src, srcaddr); |
| ba2str(dst, dstaddr); |
| |
| rec_list.addr = dstaddr; |
| rec_list.recs = NULL; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "sdp"); |
| textfile_foreach(filename, create_stored_records_from_keys, &rec_list); |
| |
| return rec_list.recs; |
| } |
| |
| sdp_record_t *find_record_in_list(sdp_list_t *recs, const char *uuid) |
| { |
| sdp_list_t *seq; |
| |
| for (seq = recs; seq; seq = seq->next) { |
| sdp_record_t *rec = (sdp_record_t *) seq->data; |
| sdp_list_t *svcclass = NULL; |
| char *uuid_str; |
| |
| if (sdp_get_service_classes(rec, &svcclass) < 0) |
| continue; |
| |
| /* Extract the uuid */ |
| uuid_str = bt_uuid2string(svcclass->data); |
| if (!uuid_str) |
| continue; |
| |
| if (!strcasecmp(uuid_str, uuid)) { |
| sdp_list_free(svcclass, free); |
| free(uuid_str); |
| return rec; |
| } |
| |
| sdp_list_free(svcclass, free); |
| free(uuid_str); |
| } |
| return NULL; |
| } |
| |
| int store_device_id(const gchar *src, const gchar *dst, uint8_t dst_type, |
| const uint16_t source, const uint16_t vendor, |
| const uint16_t product, const uint16_t version) |
| { |
| char filename[PATH_MAX + 1], key[20], str[20]; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "did"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu", dst, dst_type); |
| |
| snprintf(str, sizeof(str), "%04X %04X %04X %04X", source, |
| vendor, product, version); |
| |
| return textfile_put(filename, key, str); |
| } |
| |
| static int read_device_id_from_did(const gchar *src, const gchar *dst, |
| uint8_t dst_type, uint16_t *source, |
| uint16_t *vendor, uint16_t *product, |
| uint16_t *version) |
| { |
| char filename[PATH_MAX + 1]; |
| char key[20], *str, *vendor_str, *product_str, *version_str; |
| |
| create_name(filename, PATH_MAX, STORAGEDIR, src, "did"); |
| |
| snprintf(key, sizeof(key), "%17s#%hhu", dst, dst_type); |
| |
| str = textfile_get(filename, key); |
| if (str != NULL) |
| goto done; |
| |
| /* Try old format (address only) */ |
| str = textfile_get(filename, dst); |
| if (!str) |
| return -ENOENT; |
| |
| done: |
| vendor_str = strchr(str, ' '); |
| if (!vendor_str) { |
| free(str); |
| return -ENOENT; |
| } |
| *(vendor_str++) = 0; |
| |
| product_str = strchr(vendor_str, ' '); |
| if (!product_str) { |
| free(str); |
| return -ENOENT; |
| } |
| *(product_str++) = 0; |
| |
| version_str = strchr(product_str, ' '); |
| if (!version_str) { |
| free(str); |
| return -ENOENT; |
| } |
| *(version_str++) = 0; |
| |
| if (source) |
| *source = (uint16_t) strtol(str, NULL, 16); |
| if (vendor) |
| *vendor = (uint16_t) strtol(vendor_str, NULL, 16); |
| if (product) |
| *product = (uint16_t) strtol(product_str, NULL, 16); |
| if (version) |
| *version = (uint16_t) strtol(version_str, NULL, 16); |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| int read_device_id(const gchar *srcaddr, const gchar *dstaddr, |
| uint8_t dst_type, uint16_t *source, uint16_t *vendor, |
| uint16_t *product, uint16_t *version) |
| { |
| uint16_t lsource, lvendor, lproduct, lversion; |
| sdp_list_t *recs; |
| sdp_record_t *rec; |
| bdaddr_t src, dst; |
| int err; |
| |
| err = read_device_id_from_did(srcaddr, dstaddr, dst_type, &lsource, |
| vendor, product, |
| version); |
| if (!err) { |
| if (lsource == 0xffff) |
| err = -ENOENT; |
| |
| return err; |
| } |
| |
| str2ba(srcaddr, &src); |
| str2ba(dstaddr, &dst); |
| |
| recs = read_records(&src, &dst); |
| rec = find_record_in_list(recs, PNP_UUID); |
| |
| if (rec) { |
| sdp_data_t *pdlist; |
| |
| pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID_SOURCE); |
| lsource = pdlist ? pdlist->val.uint16 : 0x0000; |
| |
| pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); |
| lvendor = pdlist ? pdlist->val.uint16 : 0x0000; |
| |
| pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); |
| lproduct = pdlist ? pdlist->val.uint16 : 0x0000; |
| |
| pdlist = sdp_data_get(rec, SDP_ATTR_VERSION); |
| lversion = pdlist ? pdlist->val.uint16 : 0x0000; |
| |
| err = 0; |
| } |
| |
| sdp_list_free(recs, (sdp_free_func_t)sdp_record_free); |
| |
| if (err) { |
| /* FIXME: We should try EIR data if we have it, too */ |
| |
| /* If we don't have the data, we don't want to go through the |
| * above search every time. */ |
| lsource = 0xffff; |
| lvendor = 0x0000; |
| lproduct = 0x0000; |
| lversion = 0x0000; |
| } |
| |
| store_device_id(srcaddr, dstaddr, dst_type, lsource, lvendor, |
| lproduct, lversion); |
| |
| if (err) |
| return err; |
| |
| if (source) |
| *source = lsource; |
| if (vendor) |
| *vendor = lvendor; |
| if (product) |
| *product = lproduct; |
| if (version) |
| *version = lversion; |
| |
| return 0; |
| } |
| |
| int write_device_pairable(const bdaddr_t *bdaddr, gboolean mode) |
| { |
| char filename[PATH_MAX + 1]; |
| |
| create_filename(filename, PATH_MAX, bdaddr, "config"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| return textfile_put(filename, "pairable", mode ? "yes" : "no"); |
| } |
| |
| int read_device_pairable(const bdaddr_t *bdaddr, gboolean *mode) |
| { |
| char filename[PATH_MAX + 1], *str; |
| |
| create_filename(filename, PATH_MAX, bdaddr, "config"); |
| |
| str = textfile_get(filename, "pairable"); |
| if (!str) |
| return -ENOENT; |
| |
| *mode = strcmp(str, "yes") == 0 ? TRUE : FALSE; |
| |
| free(str); |
| |
| return 0; |
| } |
| |
| gboolean read_blocked(const bdaddr_t *local, const bdaddr_t *remote, |
| uint8_t remote_type) |
| { |
| char filename[PATH_MAX + 1], *str, key[20]; |
| |
| create_filename(filename, PATH_MAX, local, "blocked"); |
| |
| ba2str(remote, key); |
| sprintf(&key[17], "#%hhu", remote_type); |
| |
| str = textfile_caseget(filename, key); |
| if (str != NULL) |
| goto done; |
| |
| /* Try old format (address only) */ |
| key[17] = '\0'; |
| |
| str = textfile_caseget(filename, key); |
| if (str == NULL) |
| return FALSE; |
| |
| done: |
| free(str); |
| |
| return TRUE; |
| } |
| |
| int write_blocked(const bdaddr_t *local, const bdaddr_t *remote, |
| uint8_t remote_type, gboolean blocked) |
| { |
| char filename[PATH_MAX + 1], key[20]; |
| |
| create_filename(filename, PATH_MAX, local, "blocked"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(remote, key); |
| sprintf(&key[17], "#%hhu", remote_type); |
| |
| if (blocked == FALSE) |
| return textfile_casedel(filename, key); |
| |
| return textfile_caseput(filename, key, ""); |
| } |
| |
| int write_device_services(const bdaddr_t *sba, const bdaddr_t *dba, |
| uint8_t bdaddr_type, const char *services) |
| { |
| char filename[PATH_MAX + 1], key[20]; |
| |
| create_filename(filename, PATH_MAX, sba, "primaries"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(dba, key); |
| sprintf(&key[17], "#%hhu", bdaddr_type); |
| |
| return textfile_put(filename, key, services); |
| } |
| |
| static void filter_keys(char *key, char *value, void *data) |
| { |
| struct match *match = data; |
| |
| if (strncasecmp(key, match->pattern, strlen(match->pattern)) == 0) |
| match->keys = g_slist_append(match->keys, g_strdup(key)); |
| } |
| |
| static void delete_by_pattern(const char *filename, char *pattern) |
| { |
| struct match match; |
| GSList *l; |
| int err; |
| |
| memset(&match, 0, sizeof(match)); |
| match.pattern = pattern; |
| |
| err = textfile_foreach(filename, filter_keys, &match); |
| if (err < 0) |
| goto done; |
| |
| for (l = match.keys; l; l = l->next) { |
| const char *key = l->data; |
| textfile_del(filename, key); |
| } |
| |
| done: |
| g_slist_free_full(match.keys, g_free); |
| } |
| |
| int delete_device_service(const bdaddr_t *sba, const bdaddr_t *dba, |
| uint8_t bdaddr_type) |
| { |
| char filename[PATH_MAX + 1], key[20]; |
| |
| memset(key, 0, sizeof(key)); |
| |
| ba2str(dba, key); |
| sprintf(&key[17], "#%hhu", bdaddr_type); |
| |
| /* Deleting all characteristics of a given key */ |
| create_filename(filename, PATH_MAX, sba, "characteristics"); |
| delete_by_pattern(filename, key); |
| |
| /* Deleting all attributes values of a given key */ |
| create_filename(filename, PATH_MAX, sba, "attributes"); |
| delete_by_pattern(filename, key); |
| |
| /* Deleting all CCC values of a given key */ |
| create_filename(filename, PATH_MAX, sba, "ccc"); |
| delete_by_pattern(filename, key); |
| |
| create_filename(filename, PATH_MAX, sba, "primaries"); |
| |
| return textfile_del(filename, key); |
| } |
| |
| char *read_device_services(const bdaddr_t *sba, const bdaddr_t *dba, |
| uint8_t bdaddr_type) |
| { |
| char filename[PATH_MAX + 1], key[20]; |
| |
| create_filename(filename, PATH_MAX, sba, "primaries"); |
| |
| ba2str(dba, key); |
| sprintf(&key[17], "#%hhu", bdaddr_type); |
| |
| return textfile_caseget(filename, key); |
| } |
| |
| int write_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba, |
| uint8_t bdaddr_type, uint16_t handle, |
| const char *chars) |
| { |
| char filename[PATH_MAX + 1], addr[18], key[25]; |
| |
| create_filename(filename, PATH_MAX, sba, "characteristics"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(dba, addr); |
| snprintf(key, sizeof(key), "%17s#%hhu#%04X", addr, bdaddr_type, handle); |
| |
| return textfile_put(filename, key, chars); |
| } |
| |
| char *read_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba, |
| uint8_t bdaddr_type, uint16_t handle) |
| { |
| char filename[PATH_MAX + 1], addr[18], key[25]; |
| |
| create_filename(filename, PATH_MAX, sba, "characteristics"); |
| |
| ba2str(dba, addr); |
| snprintf(key, sizeof(key), "%17s#%hhu#%04X", addr, bdaddr_type, handle); |
| |
| return textfile_caseget(filename, key); |
| } |
| |
| int write_device_attribute(const bdaddr_t *sba, const bdaddr_t *dba, |
| uint8_t bdaddr_type, uint16_t handle, |
| const char *chars) |
| { |
| char filename[PATH_MAX + 1], addr[18], key[25]; |
| |
| create_filename(filename, PATH_MAX, sba, "attributes"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(dba, addr); |
| snprintf(key, sizeof(key), "%17s#%hhu#%04X", addr, bdaddr_type, handle); |
| |
| return textfile_put(filename, key, chars); |
| } |
| |
| int read_device_attributes(const bdaddr_t *sba, textfile_cb func, void *data) |
| { |
| char filename[PATH_MAX + 1]; |
| |
| create_filename(filename, PATH_MAX, sba, "attributes"); |
| |
| return textfile_foreach(filename, func, data); |
| } |
| |
| int read_device_ccc(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t bdaddr_type, uint16_t handle, |
| uint16_t *value) |
| { |
| char filename[PATH_MAX + 1], addr[18], key[25]; |
| char *str; |
| unsigned int config; |
| int err = 0; |
| |
| create_filename(filename, PATH_MAX, local, "ccc"); |
| |
| ba2str(peer, addr); |
| snprintf(key, sizeof(key), "%17s#%hhu#%04X", addr, bdaddr_type, handle); |
| |
| str = textfile_caseget(filename, key); |
| if (str == NULL) |
| return -ENOENT; |
| |
| if (sscanf(str, "%04X", &config) != 1) |
| err = -ENOENT; |
| else |
| *value = config; |
| |
| free(str); |
| |
| return err; |
| } |
| |
| int write_device_ccc(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t bdaddr_type, uint16_t handle, |
| uint16_t value) |
| { |
| char filename[PATH_MAX + 1], addr[18], key[25], config[5]; |
| |
| create_filename(filename, PATH_MAX, local, "ccc"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(peer, addr); |
| snprintf(key, sizeof(key), "%17s#%hhu#%04X", addr, bdaddr_type, handle); |
| |
| snprintf(config, sizeof(config), "%04X", value); |
| |
| return textfile_put(filename, key, config); |
| } |
| |
| void delete_device_ccc(const bdaddr_t *local, const bdaddr_t *peer) |
| { |
| char filename[PATH_MAX + 1], addr[18]; |
| |
| ba2str(peer, addr); |
| |
| /* Deleting all CCC values of a given address */ |
| create_filename(filename, PATH_MAX, local, "ccc"); |
| delete_by_pattern(filename, addr); |
| } |
| |
| int write_longtermkeys(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t bdaddr_type, const char *key) |
| { |
| char filename[PATH_MAX + 1], addr[20]; |
| |
| if (!key) |
| return -EINVAL; |
| |
| create_filename(filename, PATH_MAX, local, "longtermkeys"); |
| |
| create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| ba2str(peer, addr); |
| sprintf(&addr[17], "#%hhu", bdaddr_type); |
| |
| return textfile_put(filename, addr, key); |
| } |
| |
| gboolean has_longtermkeys(const bdaddr_t *local, const bdaddr_t *peer, |
| uint8_t bdaddr_type) |
| { |
| char filename[PATH_MAX + 1], key[20], *str; |
| |
| create_filename(filename, PATH_MAX, local, "longtermkeys"); |
| |
| ba2str(peer, key); |
| sprintf(&key[17], "#%hhu", bdaddr_type); |
| |
| str = textfile_caseget(filename, key); |
| if (str) { |
| free(str); |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |