| /* |
| * oFono - Open Source Telephony |
| * Copyright (C) 2023 Cruise, LLC |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <linux/types.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <errno.h> |
| #include <unistd.h> |
| |
| #include <ell/ell.h> |
| |
| #define OFONO_API_SUBJECT_TO_CHANGE |
| #include <ofono/gprs-context.h> |
| |
| #include "provisiondb.h" |
| |
| struct provision_header { |
| __le64 version; |
| __le64 file_size; |
| __le64 header_size; |
| __le64 node_struct_size; |
| __le64 provision_data_struct_size; |
| __le64 context_struct_size; |
| __le64 nodes_offset; |
| __le64 nodes_size; |
| __le64 contexts_offset; |
| __le64 contexts_size; |
| __le64 strings_offset; |
| __le64 strings_size; |
| |
| /* followed by nodes_size of node structures */ |
| /* followed by contexts_size of context structures */ |
| /* followed by strings_size packed strings */ |
| } __attribute__((packed)); |
| |
| struct node { |
| __le64 bit_offsets[2]; |
| __le32 mccmnc; |
| __le32 diff; /* Signed */ |
| __le64 provision_data_count; |
| /* followed by provision_data_count provision_data structures */ |
| } __attribute__((packed)); |
| |
| struct provision_data { |
| __le64 spn_offset; |
| __le64 context_offset; /* the offset contains count of contexts */ |
| /* followed by context structures */ |
| } __attribute__((packed)); |
| |
| struct context { |
| __le32 type; /* Corresponds to ofono_gprs_context_type bitmap */ |
| __le32 protocol; /* Corresponds to ofono_gprs_proto */ |
| __le32 authentication; /* Corresponds to ofono_gprs_auth_method */ |
| __le32 reserved; |
| __le64 name_offset; |
| __le64 apn_offset; |
| __le64 username_offset; |
| __le64 password_offset; |
| __le64 mmsproxy_offset; |
| __le64 mmsc_offset; |
| __le64 tags_offset; |
| } __attribute__((packed)); |
| |
| struct provision_db { |
| int fd; |
| time_t mtime; |
| size_t size; |
| void *addr; |
| uint64_t nodes_offset; |
| uint64_t nodes_size; |
| uint64_t contexts_offset; |
| uint64_t contexts_size; |
| uint64_t strings_offset; |
| uint64_t strings_size; |
| }; |
| |
| struct provision_db *provision_db_new(const char *pathname) |
| { |
| struct provision_header *hdr; |
| struct provision_db *pdb = NULL; |
| struct stat st; |
| void *addr; |
| size_t size; |
| int fd; |
| |
| if (!pathname) |
| return NULL; |
| |
| fd = open(pathname, O_RDONLY | O_CLOEXEC); |
| if (fd < 0) |
| return NULL; |
| |
| if (fstat(fd, &st) < 0) |
| goto error_close; |
| |
| size = st.st_size; |
| if (size < sizeof(struct provision_header)) |
| goto error_close; |
| |
| addr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); |
| if (addr == MAP_FAILED) |
| goto error_close; |
| |
| hdr = addr; |
| |
| if (L_LE64_TO_CPU(hdr->file_size) != size) |
| goto failed; |
| |
| if (L_LE64_TO_CPU(hdr->header_size) != sizeof(struct provision_header)) |
| goto failed; |
| |
| if (L_LE64_TO_CPU(hdr->node_struct_size) != sizeof(struct node)) |
| goto failed; |
| |
| if (L_LE64_TO_CPU(hdr->provision_data_struct_size) != |
| sizeof(struct provision_data)) |
| goto failed; |
| |
| if (L_LE64_TO_CPU(hdr->context_struct_size) != sizeof(struct context)) |
| goto failed; |
| |
| if (L_LE64_TO_CPU(hdr->header_size) + L_LE64_TO_CPU(hdr->nodes_size) + |
| L_LE64_TO_CPU(hdr->contexts_size) + |
| L_LE64_TO_CPU(hdr->strings_size) != size) |
| goto failed; |
| |
| pdb = l_new(struct provision_db, 1); |
| |
| pdb->fd = fd; |
| pdb->mtime = st.st_mtime; |
| pdb->size = size; |
| pdb->addr = addr; |
| pdb->nodes_offset = L_LE64_TO_CPU(hdr->nodes_offset); |
| pdb->nodes_size = L_LE64_TO_CPU(hdr->nodes_size); |
| pdb->contexts_offset = L_LE64_TO_CPU(hdr->contexts_offset); |
| pdb->contexts_size = L_LE64_TO_CPU(hdr->contexts_size); |
| pdb->strings_offset = L_LE64_TO_CPU(hdr->strings_offset); |
| pdb->strings_size = L_LE64_TO_CPU(hdr->strings_size); |
| |
| return pdb; |
| |
| failed: |
| munmap(addr, st.st_size); |
| error_close: |
| close(fd); |
| return NULL; |
| } |
| |
| struct provision_db *provision_db_new_default(void) |
| { |
| struct provision_db *db = NULL; |
| size_t i; |
| const char * const paths[] = { "/usr/share/ofono/provision.db" }; |
| |
| for (i = 0; !db && i < L_ARRAY_SIZE(paths); i++) |
| db = provision_db_new(paths[i]); |
| |
| return db; |
| } |
| |
| void provision_db_free(struct provision_db *pdb) |
| { |
| if (!pdb) |
| return; |
| |
| munmap(pdb->addr, pdb->size); |
| close(pdb->fd); |
| l_free(pdb); |
| } |
| |
| static int __get_node(struct provision_db *pdb, uint64_t offset, |
| struct node **out_node) |
| { |
| uint64_t count; |
| struct node *node; |
| |
| if (offset + sizeof(struct node) > pdb->nodes_size) |
| return -EPROTO; |
| |
| node = pdb->addr + pdb->nodes_offset + offset; |
| offset += sizeof(struct node); |
| count = L_LE64_TO_CPU(node->provision_data_count); |
| |
| if (offset + count * sizeof(struct provision_data) > pdb->nodes_size) |
| return -EPROTO; |
| |
| *out_node = node; |
| return 0; |
| } |
| |
| static struct provision_data *__get_provision_data(struct node *node) |
| { |
| return ((void *) node) + sizeof(struct node); |
| } |
| |
| static int __get_string(struct provision_db *pdb, uint64_t offset, |
| const char **out_str) |
| { |
| if (!offset) { |
| *out_str = NULL; |
| return 0; |
| } |
| |
| if (offset >= pdb->strings_size) |
| return -EPROTO; |
| |
| *out_str = pdb->addr + pdb->strings_offset + offset; |
| return 0; |
| } |
| |
| static bool tags_match(char **tags_filter, const char *tags) |
| { |
| _auto_(l_strv_free) char **split_tags = 0; |
| unsigned int i; |
| |
| if (!tags_filter || !tags) |
| return true; |
| |
| split_tags = l_strsplit(tags, ','); |
| |
| for (i = 0; tags_filter[i]; i++) |
| if (l_strv_contains(split_tags, tags_filter[i])) |
| return true; |
| |
| return false; |
| } |
| |
| static int __get_contexts(struct provision_db *pdb, uint64_t offset, |
| char **tags_filter, |
| struct provision_db_entry **contexts, |
| size_t *n_contexts) |
| { |
| void *start = pdb->addr + pdb->contexts_offset; |
| uint64_t num; |
| uint64_t i; |
| struct provision_db_entry *ret; |
| int r; |
| uint64_t n_matched = 0; |
| |
| if (offset + sizeof(__le64) >= pdb->contexts_size) |
| return -EPROTO; |
| |
| num = l_get_le64(start + offset); |
| offset += sizeof(__le64); |
| |
| if (offset + num * sizeof(struct context) > pdb->contexts_size) |
| return -EPROTO; |
| |
| ret = l_new(struct provision_db_entry, num); |
| |
| for (i = 0; i < num; i++, offset += sizeof(struct context)) { |
| struct context *context = start + offset; |
| |
| r = __get_string(pdb, L_LE64_TO_CPU(context->tags_offset), |
| &ret[n_matched].tags); |
| if (r < 0) |
| goto fail; |
| |
| if (!tags_match(tags_filter, ret[n_matched].tags)) |
| continue; |
| |
| ret[n_matched].type = L_LE32_TO_CPU(context->type); |
| ret[n_matched].proto = L_LE32_TO_CPU(context->protocol); |
| ret[n_matched].auth_method = |
| L_LE32_TO_CPU(context->authentication); |
| |
| r = __get_string(pdb, L_LE64_TO_CPU(context->name_offset), |
| &ret[n_matched].name); |
| if (r < 0) |
| goto fail; |
| |
| r = __get_string(pdb, L_LE64_TO_CPU(context->apn_offset), |
| &ret[n_matched].apn); |
| if (r < 0) |
| goto fail; |
| |
| r = __get_string(pdb, L_LE64_TO_CPU(context->username_offset), |
| &ret[n_matched].username); |
| if (r < 0) |
| goto fail; |
| |
| r = __get_string(pdb, L_LE64_TO_CPU(context->password_offset), |
| &ret[n_matched].password); |
| if (r < 0) |
| goto fail; |
| |
| r = __get_string(pdb, L_LE64_TO_CPU(context->mmsproxy_offset), |
| &ret[n_matched].message_proxy); |
| if (r < 0) |
| goto fail; |
| |
| r = __get_string(pdb, L_LE64_TO_CPU(context->mmsc_offset), |
| &ret[n_matched].message_center); |
| if (r < 0) |
| goto fail; |
| |
| n_matched += 1; |
| } |
| |
| *contexts = ret; |
| *n_contexts = n_matched; |
| return 0; |
| |
| fail: |
| l_free(ret); |
| return r; |
| } |
| |
| static uint8_t choose(struct node *node, uint32_t key) |
| { |
| return (key >> (31U - L_LE32_TO_CPU(node->diff))) & 1; |
| } |
| |
| static int __find(struct provision_db *pdb, uint32_t key, |
| struct node **out_node) |
| { |
| struct node *child; |
| struct node *parent; |
| int r; |
| |
| r = __get_node(pdb, 0, &parent); |
| if (r < 0) |
| return r; |
| |
| r = __get_node(pdb, L_LE64_TO_CPU(parent->bit_offsets[0]), &child); |
| if (r < 0) |
| return r; |
| |
| while ((int32_t) L_LE32_TO_CPU(parent->diff) < |
| (int32_t) L_LE32_TO_CPU(child->diff)) { |
| uint8_t bit = choose(child, key); |
| uint64_t offset = L_LE64_TO_CPU(child->bit_offsets[bit]); |
| |
| parent = child; |
| |
| r = __get_node(pdb, offset, &child); |
| if (r < 0) |
| return r; |
| } |
| |
| if (L_LE32_TO_CPU(child->mccmnc) != key) |
| return -ENOENT; |
| |
| *out_node = child; |
| return 0; |
| } |
| |
| static int id_as_num(const char *id, size_t len) |
| { |
| uint32_t v = 0; |
| size_t i; |
| |
| for (i = 0; i < len; i++) { |
| if (!l_ascii_isdigit(id[i])) |
| return -EINVAL; |
| |
| v = v * 10 + id[i] - '0'; |
| } |
| |
| return v; |
| } |
| |
| static int key_from_mcc_mnc(const char *mcc, const char *mnc, uint32_t *key) |
| { |
| size_t mcc_len = strlen(mcc); |
| size_t mnc_len = strlen(mnc); |
| uint32_t v; |
| int r; |
| |
| if (mcc_len != 3) |
| return -EINVAL; |
| |
| if (mnc_len != 2 && mnc_len != 3) |
| return -EINVAL; |
| |
| r = id_as_num(mcc, mcc_len); |
| if (r < 0) |
| return r; |
| |
| v = r << 11; |
| |
| r = id_as_num(mnc, mnc_len); |
| if (r < 0) |
| return r; |
| |
| if (mnc_len == 3) |
| v |= 1 << 10; |
| |
| v |= r; |
| |
| *key = v; |
| return 0; |
| } |
| |
| int provision_db_lookup(struct provision_db *pdb, |
| const char *mcc, const char *mnc, const char *match_spn, |
| char **tags_filter, |
| struct provision_db_entry **items, size_t *n_items) |
| { |
| int r; |
| uint32_t key; |
| struct node *node; |
| struct provision_data *data; |
| struct provision_data *found = NULL; |
| uint64_t count; |
| uint64_t i; |
| |
| if (pdb == NULL) |
| return -EBADF; |
| |
| r = key_from_mcc_mnc(mcc, mnc, &key); |
| if (r < 0) |
| return r; |
| |
| /* |
| * Find the target node, then walk the provision_data items to |
| * match the spn. After that it is a matter of allocating the |
| * return contexts and copying over the details. |
| */ |
| |
| r = __find(pdb, key, &node); |
| if (r < 0) |
| return r; |
| |
| count = L_LE64_TO_CPU(node->provision_data_count); |
| data = __get_provision_data(node); |
| |
| if (!count) |
| return -ENOENT; |
| |
| /* |
| * provision_data objects are sorted by SPN, with no SPN (non-MVNO) |
| * being first. Since the provisioning data is imperfect, we try to |
| * match by SPN, but if that fails, we return the non-SPN entry, if |
| * present |
| */ |
| if (data[0].spn_offset == 0) { |
| found = data; |
| data += 1; |
| count -= 1; |
| } |
| |
| for (i = 0; i < count; i++) { |
| const char *spn; |
| |
| r = __get_string(pdb, L_LE64_TO_CPU(data[i].spn_offset), &spn); |
| if (r < 0) |
| return r; |
| |
| if (l_streq0(spn, match_spn)) { |
| found = data + i; |
| break; |
| } |
| } |
| |
| if (!found) |
| return -ENOENT; |
| |
| return __get_contexts(pdb, L_LE64_TO_CPU(found->context_offset), |
| tags_filter, items, n_items); |
| } |