--- Makefile.am | 3 + plugins/context-provisioning.c | 418 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 421 insertions(+), 0 deletions(-) create mode 100644 plugins/context-provisioning.c
diff --git a/Makefile.am b/Makefile.am index 42ad86f..18938b6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -347,6 +347,9 @@ builtin_sources += plugins/smart-messaging.c builtin_modules += push_notification builtin_sources += plugins/push-notification.c +builtin_modules += context_provisioning +builtin_sources += plugins/context-provisioning.c + sbin_PROGRAMS = src/ofonod src_ofonod_SOURCES = $(gdbus_sources) $(builtin_sources) src/ofono.ver \ diff --git a/plugins/context-provisioning.c b/plugins/context-provisioning.c new file mode 100644 index 0000000..4dcb373 --- /dev/null +++ b/plugins/context-provisioning.c @@ -0,0 +1,418 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 <stdlib.h> +#include <string.h> +#include <glib.h> + +#include <errno.h> + +#define OFONO_API_SUBJECT_TO_CHANGE + +#include "ofono.h" +#include <ofono/modem.h> +#include <ofono/gprs-provision.h> +#include <ofono/types.h> +#include <ofono/plugin.h> +#include <ofono/log.h> + +#define OPERATOR_SETTINGS_DIR "operator-settings" + +struct parser_data { + const gchar *mcc; + const gchar *mnc; + GSList *candidates; +}; + +struct candidate_data { + struct ofono_gprs_provision_data *entry; + gchar *spn; +}; + +static void candidate_data_free(struct candidate_data *cand) +{ + if (cand == NULL) + return; + + if (cand->entry) + __ofono_gprs_provision_free_settings(cand->entry, 1); + + g_free(cand->spn); + g_free(cand); +} + +static enum ofono_gprs_context_type string_to_gprs_context_type(const char *str) +{ + if (str != NULL) { + if (strcasecmp(str, "internet") == 0) + return OFONO_GPRS_CONTEXT_TYPE_INTERNET; + if (strcasecmp(str, "mms") == 0) + return OFONO_GPRS_CONTEXT_TYPE_MMS; + if (strcasecmp(str, "wap") == 0) + return OFONO_GPRS_CONTEXT_TYPE_WAP; + if (strcasecmp(str, "ims") == 0) + return OFONO_GPRS_CONTEXT_TYPE_IMS; + } + + return OFONO_GPRS_CONTEXT_TYPE_ANY; +} + +static enum ofono_gprs_proto string_to_gprs_proto(const char *str) +{ + if (str != NULL) { + if (strcasecmp(str, "ipv6") == 0) + return OFONO_GPRS_PROTO_IPV6; + if (strcasecmp(str, "ipv4v6") == 0) + return OFONO_GPRS_PROTO_IPV6; // FIXME when possible + } + + return OFONO_GPRS_PROTO_IP; +} + +static struct candidate_data *candidate_data_new(const char *spn, + const struct ofono_gprs_provision_data *data) +{ + struct candidate_data *cand; + cand = g_try_malloc0(sizeof(*cand)); + if (cand == NULL) + return NULL; + + cand->entry = g_try_malloc0(sizeof(*cand->entry)); + if (cand->entry == NULL) { + g_free(cand); + return NULL; + } + + cand->spn = g_strdup(spn); + cand->entry->type = data->type; + cand->entry->apn = g_strdup(data->apn); + cand->entry->name = g_strdup(data->name); + cand->entry->username = g_strdup(data->username); + cand->entry->password = g_strdup(data->password); + cand->entry->proto = data->proto; + cand->entry->message_proxy = g_strdup(data->message_proxy); + cand->entry->message_center = g_strdup(data->message_center); + + return cand; +} + +/* + * Parse <access> element. + * Mandatory attributes in <access>: mcc, mnc, type, name + * If MCC/MNC matches, add entry to candidate entry list. + */ +static void settings_start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + int i; + struct parser_data *parser = user_data; + const char *mcc = NULL, *mnc = NULL, *spn = NULL; + struct ofono_gprs_provision_data entry = {0}; + + if (strcasecmp(element_name, "access") != 0) + return; + + for (i = 0; attribute_names[i] != NULL; i++) { + const char *name = attribute_names[i]; + const char *value = attribute_values[i]; + + if (strcasecmp(name, "mcc") == 0) { + if (strcmp(value, parser->mcc) != 0) + break; + + mcc = value; + } + + if (strcasecmp(name, "mnc") == 0) { + /* + * Match MNC as integers, since settings files + * do not always contain leading zeros in MNC values... + */ + if (atoi(value) != atoi(parser->mnc)) + break; + + mnc = value; + } + + if (strcasecmp(name, "spn") == 0) + spn = value; + + if (strcasecmp(name, "type") == 0) + entry.type = string_to_gprs_context_type(value); + + if (strcasecmp(name, "apn") == 0) + entry.apn = (char *) value; + + if (strcasecmp(name, "name") == 0) + entry.name = (char *) value; + + if (strcasecmp(name, "username") == 0) + entry.username = (char *) value; + + if (strcasecmp(name, "password") == 0) + entry.password = (char *) value; + + if (strcasecmp(name, "protocol") == 0) + entry.proto = string_to_gprs_proto(value); + + if (strcasecmp(name, "proxy") == 0) + entry.message_proxy = (char *) value; + + if (strcasecmp(name, "mmsc") == 0) + entry.message_center = (char *) value; + } + + if (mcc != NULL && mnc != NULL && entry.type != 0 && + entry.name != NULL && entry.apn != NULL) { + struct candidate_data *cand = candidate_data_new(spn, &entry); + if (cand == NULL) + return; + + parser->candidates = g_slist_append(parser->candidates, cand); + } +} + +/* + * Returns list of candidate settings matching mcc/mnc from operator + * access settings file + */ +static GSList *read_access_settings_file(const char *filename, + const char *mcc, const char *mnc) +{ + gchar *contents; + gsize length; + GMarkupParseContext *context; + + struct parser_data parser = {0}; + GMarkupParser settings_parser = { settings_start_element_handler, + NULL, NULL, NULL, NULL }; + + if (filename == NULL || mcc == NULL || mnc == NULL) + return NULL; + + if (g_file_get_contents(filename, &contents, &length, + NULL) == FALSE) { + ofono_warn("Error reading access settings file %s", filename); + return NULL; + } + + DBG("Reading access settings file %s", filename); + + parser.mcc = mcc; + parser.mnc = mnc; + + context = g_markup_parse_context_new(&settings_parser, 0, &parser, + NULL); + + if (g_markup_parse_context_parse(context, contents, length, + NULL) == FALSE) { + ofono_warn("Error parsing XML file %s", filename); + } + + g_markup_parse_context_free(context); + g_free(contents); + + return parser.candidates; +} + +static void append_to_settings(struct ofono_gprs_provision_data **settings, + int *count, + struct ofono_gprs_provision_data **data) +{ + *settings = g_try_renew(struct ofono_gprs_provision_data, *settings, + *count + 1); + if (*settings == NULL) { + *count = 0; + return; + } + + memcpy(*settings + *count, *data, sizeof(**data)); + *count = *count + 1; + + g_free(*data); + *data = NULL; + + return; +} + +/* Compare type and spn (case-insensitively) to a candidate entry */ +static gboolean is_spn_match(struct candidate_data *cand, + enum ofono_gprs_context_type type, + gchar *spn_casefold) +{ + gboolean ret = FALSE; + gchar *candspn_casefold = NULL; + + if (spn_casefold == NULL || cand->spn == NULL) + return FALSE; + + candspn_casefold = g_utf8_casefold(cand->spn, -1); + if (type == cand->entry->type && strcmp(spn_casefold, + candspn_casefold) == 0) + ret = TRUE; + + g_free(candspn_casefold); + return ret; +} + +/* Find an entry from candidates matching type and optionally spn */ +static gboolean get_entry(GSList * candidates, + enum ofono_gprs_context_type type, + const char *spn, + struct ofono_gprs_provision_data **settings, + int *count) +{ + GSList *lp; + gchar *spn_casefold = NULL; + gboolean success = FALSE; + + if (spn != NULL) + spn_casefold = g_utf8_casefold(spn, -1); + + for (lp = candidates; lp != NULL; lp = lp->next) { + struct candidate_data *cand = lp->data; + + if (cand->entry == NULL) + continue; + + if ((spn_casefold == NULL && cand->entry->type == type) || + is_spn_match(cand, type, spn_casefold)) { + DBG("Found: %s", cand->entry->name); + + append_to_settings(settings, count, &cand->entry); + success = TRUE; + break; + } + } + + g_free(spn_casefold); + + /* If match with using spn failed, retry without */ + if (success == FALSE && spn != NULL) + return get_entry(candidates, type, NULL, settings, count); + + return success; +} + + +static void read_operator_settings(const char *filename, const char *mcc, + const char *mnc, const char *spn, + struct ofono_gprs_provision_data **settings, + int *count) +{ + GSList *candidates; + candidates = read_access_settings_file(filename, mcc, mnc); + + /* Get settings for internet and mms contexts */ + get_entry(candidates, OFONO_GPRS_CONTEXT_TYPE_INTERNET, spn, + settings, count); + get_entry(candidates, OFONO_GPRS_CONTEXT_TYPE_MMS, spn, + settings, count); + /* TODO: add wap and ims types */ + + g_slist_foreach(candidates, (GFunc) candidate_data_free, NULL); + g_slist_free(candidates); +} + +/* + * Returns GPRS context settings (internet and mms types) based on + * SIM provided MCC,MNC and Service Provider Name (SPN) values. + * Operator settings for Internet and MMS access points are stored + * in XML formatted files (*.xml) under $CONFIGDIR/operator-settings + */ +static int get_settings(const char *mcc, const char *mnc, const char *spn, + struct ofono_gprs_provision_data **settings, + int *count) +{ + GDir *dir; + GSList *files = NULL, *lp; + const gchar *filename; + + *count = 0; + *settings = NULL; + + ofono_debug("Provisioning settings for MCC %s, MNC %s SPN '%s'", + mcc, mnc, spn); + + /* Find all .xml-files from settings directory */ + dir = g_dir_open(CONFIGDIR "/" OPERATOR_SETTINGS_DIR, 0, NULL); + if (dir == NULL) { + ofono_warn("Error opening settings directory"); + return -ENOENT; + } + + while ((filename = g_dir_read_name(dir)) != NULL) { + if (g_str_has_suffix(filename, ".xml")) { + gchar *fn = g_build_filename(CONFIGDIR, + OPERATOR_SETTINGS_DIR, + filename, NULL); + files = g_slist_append(files, fn); + } + } + + /* Process files in sorted order */ + files = g_slist_sort(files, (GCompareFunc) g_strcmp0); + + for (lp = files; lp != NULL; lp = lp->next) { + gchar *fn = lp->data; + read_operator_settings(fn, mcc, mnc, spn, settings, count); + + if (*count > 0) + break; + } + + g_slist_foreach(files, (GFunc) g_free, NULL); + g_slist_free(files); + g_dir_close(dir); + + return *count > 0 ? 0 : -ENOENT; +} + +static struct ofono_gprs_provision_driver provision_driver = { + .name = "Connection Context Provisioning Plugin", + .priority = OFONO_PLUGIN_PRIORITY_DEFAULT, + .get_settings = get_settings, +}; + +static int provision_init(void) +{ + return ofono_gprs_provision_driver_register(&provision_driver); +} + +static void provision_exit(void) +{ + ofono_gprs_provision_driver_unregister(&provision_driver); +} + +OFONO_PLUGIN_DEFINE(context_provisioning, + "Connection Context Provisioning Plugin", + VERSION, OFONO_PLUGIN_PRIORITY_DEFAULT, + provision_init, + provision_exit) + -- 1.7.1 _______________________________________________ ofono mailing list ofono@ofono.org http://lists.ofono.org/listinfo/ofono