summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLubomir Rintel <lkundrak@v3.sk>2019-03-18 10:53:20 +0100
committerLubomir Rintel <lkundrak@v3.sk>2019-09-11 14:32:05 +0200
commit6632c7709492182c795895c7d32ce2542a8d9e9a (patch)
tree74f7971a5b378effaefe170611b0c049cb499a9d
parentadf02543693c49357631f746e562f5ab30968c42 (diff)
wwan: add service-providers.xml parser
This allows up to look up a default APN if the user doesn't pick one.
-rw-r--r--Makefile.am5
-rw-r--r--src/devices/wwan/meson.build1
-rw-r--r--src/devices/wwan/nm-service-providers.c460
-rw-r--r--src/devices/wwan/nm-service-providers.h24
4 files changed, 489 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index ef275bb440..aea13af505 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3315,7 +3315,10 @@ src_devices_wwan_libnm_wwan_la_SOURCES = \
src/devices/wwan/nm-modem-manager.c \
src/devices/wwan/nm-modem-manager.h \
src/devices/wwan/nm-modem.c \
- src/devices/wwan/nm-modem.h
+ src/devices/wwan/nm-modem.h \
+ src/devices/wwan/nm-service-providers.c \
+ src/devices/wwan/nm-service-providers.h \
+ $(NULL)
if WITH_OFONO
src_devices_wwan_libnm_wwan_la_SOURCES += \
diff --git a/src/devices/wwan/meson.build b/src/devices/wwan/meson.build
index 482dc205fa..7f28265f81 100644
--- a/src/devices/wwan/meson.build
+++ b/src/devices/wwan/meson.build
@@ -2,6 +2,7 @@ sources = files(
'nm-modem-broadband.c',
'nm-modem.c',
'nm-modem-manager.c',
+ 'nm-service-providers.c',
)
deps = [
diff --git a/src/devices/wwan/nm-service-providers.c b/src/devices/wwan/nm-service-providers.c
new file mode 100644
index 0000000000..f363ebe506
--- /dev/null
+++ b/src/devices/wwan/nm-service-providers.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2009 Novell, Inc.
+ * Author: Tambet Ingo (tambet@gmail.com).
+ *
+ * Copyright (C) 2009 - 2019 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#include "nm-default.h"
+
+#include "nm-service-providers.h"
+
+typedef enum {
+ PARSER_TOPLEVEL = 0,
+ PARSER_COUNTRY,
+ PARSER_PROVIDER,
+ PARSER_METHOD_GSM,
+ PARSER_METHOD_GSM_APN,
+ PARSER_METHOD_CDMA,
+ PARSER_DONE,
+ PARSER_ERROR
+} ParseContextState;
+
+typedef struct {
+ char *mccmnc;
+ NMServiceProvidersGsmApnCallback callback;
+ gpointer user_data;
+ GCancellable *cancellable;
+ GMarkupParseContext *ctx;
+ char buffer[4096];
+
+ char *text_buffer;
+ ParseContextState state;
+
+ gboolean mccmnc_matched;
+ gboolean found_internet_apn;
+ char *apn;
+ char *username;
+ char *password;
+ char *gateway;
+ char *auth_method;
+ GSList *dns;
+} ParseContext;
+
+/*****************************************************************************/
+
+static void
+parser_toplevel_start (ParseContext *parse_context,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ int i;
+
+ if (strcmp (name, "serviceproviders") == 0) {
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if (strcmp (attribute_names[i], "format") == 0) {
+ if (strcmp (attribute_values[i], "2.0")) {
+ g_warning ("%s: mobile broadband provider database format '%s'"
+ " not supported.", __func__, attribute_values[i]);
+ parse_context->state = PARSER_ERROR;
+ break;
+ }
+ }
+ }
+ } else if (strcmp (name, "country") == 0) {
+ parse_context->state = PARSER_COUNTRY;
+ }
+}
+
+static void
+parser_country_start (ParseContext *parse_context,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ if (strcmp (name, "provider") == 0)
+ parse_context->state = PARSER_PROVIDER;
+}
+
+static void
+parser_provider_start (ParseContext *parse_context,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ parse_context->mccmnc_matched = FALSE;
+ if (strcmp (name, "gsm") == 0)
+ parse_context->state = PARSER_METHOD_GSM;
+ else if (strcmp (name, "cdma") == 0)
+ parse_context->state = PARSER_METHOD_CDMA;
+}
+
+static void
+parser_gsm_start (ParseContext *parse_context,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ int i;
+
+ if (strcmp (name, "network-id") == 0) {
+ const char *mcc = NULL, *mnc = NULL;
+
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if (strcmp (attribute_names[i], "mcc") == 0)
+ mcc = attribute_values[i];
+ else if (strcmp (attribute_names[i], "mnc") == 0)
+ mnc = attribute_values[i];
+ if (mcc && strlen (mcc) && mnc && strlen (mnc)) {
+ char *mccmnc = g_strdup_printf ("%s%s", mcc, mnc);
+
+ if (strcmp (mccmnc, parse_context->mccmnc) == 0)
+ parse_context->mccmnc_matched = TRUE;
+ g_free (mccmnc);
+ break;
+ }
+ }
+ } else if (strcmp (name, "apn") == 0) {
+ parse_context->found_internet_apn = FALSE;
+ g_clear_pointer (&parse_context->apn, g_free);
+ g_clear_pointer (&parse_context->username, g_free);
+ g_clear_pointer (&parse_context->password, g_free);
+ g_clear_pointer (&parse_context->gateway, g_free);
+ g_clear_pointer (&parse_context->auth_method, g_free);
+ g_slist_free_full (parse_context->dns, g_free);
+ parse_context->dns = NULL;
+
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if (strcmp (attribute_names[i], "value") == 0) {
+ parse_context->state = PARSER_METHOD_GSM_APN;
+ parse_context->apn = g_strstrip (g_strdup (attribute_values[i]));
+ break;
+ }
+ }
+ }
+}
+
+static void
+parser_gsm_apn_start (ParseContext *parse_context,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ int i;
+
+ if (strcmp (name, "usage") == 0) {
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if ( (strcmp (attribute_names[i], "type") == 0)
+ && (strcmp (attribute_values[i], "internet") == 0)) {
+ parse_context->found_internet_apn = TRUE;
+ break;
+ }
+ }
+ } else if (strcmp (name, "authentication") == 0) {
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if (strcmp (attribute_names[i], "method") == 0) {
+ g_clear_pointer (&parse_context->auth_method, g_free);
+ parse_context->auth_method = g_strstrip (g_strdup (attribute_values[i]));
+ break;
+ }
+ }
+ }
+}
+
+static void
+parser_start_element (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseContext *parse_context = user_data;
+
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+
+ switch (parse_context->state) {
+ case PARSER_TOPLEVEL:
+ parser_toplevel_start (parse_context, element_name, attribute_names, attribute_values);
+ break;
+ case PARSER_COUNTRY:
+ parser_country_start (parse_context, element_name, attribute_names, attribute_values);
+ break;
+ case PARSER_PROVIDER:
+ parser_provider_start (parse_context, element_name, attribute_names, attribute_values);
+ break;
+ case PARSER_METHOD_GSM:
+ parser_gsm_start (parse_context, element_name, attribute_names, attribute_values);
+ break;
+ case PARSER_METHOD_GSM_APN:
+ parser_gsm_apn_start (parse_context, element_name, attribute_names, attribute_values);
+ break;
+ case PARSER_METHOD_CDMA:
+ break;
+ case PARSER_ERROR:
+ break;
+ case PARSER_DONE:
+ break;
+ }
+}
+
+static void
+parser_country_end (ParseContext *parse_context,
+ const char *name)
+{
+ if (strcmp (name, "country") == 0) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+ parse_context->state = PARSER_TOPLEVEL;
+ }
+}
+
+static void
+parser_provider_end (ParseContext *parse_context,
+ const char *name)
+{
+ if (strcmp (name, "provider") == 0) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+ parse_context->state = PARSER_COUNTRY;
+ }
+}
+
+static void
+parser_gsm_end (ParseContext *parse_context,
+ const char *name)
+{
+ if (strcmp (name, "gsm") == 0) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+ parse_context->state = PARSER_PROVIDER;
+ }
+}
+
+static void
+parser_gsm_apn_end (ParseContext *parse_context,
+ const char *name)
+{
+ if (strcmp (name, "username") == 0) {
+ g_clear_pointer (&parse_context->username, g_free);
+ parse_context->username = g_steal_pointer (&parse_context->text_buffer);
+ } else if (strcmp (name, "password") == 0) {
+ g_clear_pointer (&parse_context->password, g_free);
+ parse_context->password = g_steal_pointer (&parse_context->text_buffer);
+ } else if (strcmp (name, "dns") == 0) {
+ parse_context->dns = g_slist_prepend (parse_context->dns,
+ g_steal_pointer (&parse_context->text_buffer));
+ } else if (strcmp (name, "gateway") == 0) {
+ g_clear_pointer (&parse_context->gateway, g_free);
+ parse_context->gateway = g_steal_pointer (&parse_context->text_buffer);
+ } else if (strcmp (name, "apn") == 0) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+
+ if (parse_context->mccmnc_matched && parse_context->found_internet_apn)
+ parse_context->state = PARSER_DONE;
+ else
+ parse_context->state = PARSER_METHOD_GSM;
+
+ }
+}
+
+static void
+parser_cdma_end (ParseContext *parse_context,
+ const char *name)
+{
+ if (strcmp (name, "cdma") == 0) {
+ g_clear_pointer (&parse_context->text_buffer, g_free);
+ parse_context->state = PARSER_PROVIDER;
+ }
+}
+
+static void
+parser_end_element (GMarkupParseContext *context,
+ const char *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseContext *parse_context = user_data;
+
+ switch (parse_context->state) {
+ case PARSER_TOPLEVEL:
+ break;
+ case PARSER_COUNTRY:
+ parser_country_end (parse_context, element_name);
+ break;
+ case PARSER_PROVIDER:
+ parser_provider_end (parse_context, element_name);
+ break;
+ case PARSER_METHOD_GSM:
+ parser_gsm_end (parse_context, element_name);
+ break;
+ case PARSER_METHOD_GSM_APN:
+ parser_gsm_apn_end (parse_context, element_name);
+ break;
+ case PARSER_METHOD_CDMA:
+ parser_cdma_end (parse_context, element_name);
+ break;
+ case PARSER_ERROR:
+ break;
+ case PARSER_DONE:
+ break;
+ }
+}
+
+static void
+parser_text (GMarkupParseContext *context,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ ParseContext *parse_context = user_data;
+
+ g_free (parse_context->text_buffer);
+ parse_context->text_buffer = g_strdup (text);
+}
+
+static const GMarkupParser parser = {
+ .start_element = parser_start_element,
+ .end_element = parser_end_element,
+ .text = parser_text,
+ .passthrough = NULL,
+ .error = NULL,
+};
+
+/*****************************************************************************/
+
+static void
+finish_parse_context (ParseContext *parse_context, GError *error)
+{
+ if (parse_context->callback) {
+ if (error) {
+ parse_context->callback (NULL, NULL, NULL, NULL, NULL,
+ NULL, error,
+ parse_context->user_data);
+ } else {
+ parse_context->callback (parse_context->apn,
+ parse_context->username,
+ parse_context->password,
+ parse_context->gateway,
+ parse_context->auth_method,
+ parse_context->dns,
+ error,
+ parse_context->user_data);
+ }
+ }
+
+ g_free (parse_context->mccmnc);
+ g_markup_parse_context_free (parse_context->ctx);
+
+ g_free (parse_context->text_buffer);
+ g_free (parse_context->apn);
+ g_free (parse_context->username);
+ g_free (parse_context->password);
+ g_free (parse_context->gateway);
+ g_free (parse_context->auth_method);
+ g_slist_free_full (parse_context->dns, g_free);
+
+ g_slice_free (ParseContext, parse_context);
+}
+
+static void
+read_next_chunk (GInputStream *stream, ParseContext *parse_context);
+
+static void
+stream_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ GInputStream *stream = G_INPUT_STREAM (source_object);
+ ParseContext *parse_context = user_data;
+ gssize len;
+ GError *error = NULL;
+
+ len = g_input_stream_read_finish (stream, res, &error);
+ if (len == -1) {
+ g_prefix_error (&error, "Error reading service provider database: ");
+ finish_parse_context (parse_context, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ if (len == 0) {
+ g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+ "Operator ID '%s' not found in service provider database",
+ parse_context->mccmnc);
+ finish_parse_context (parse_context, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ if (!g_markup_parse_context_parse (parse_context->ctx, parse_context->buffer, len, &error)) {
+ g_prefix_error (&error, "Error parsing service provider database: ");
+ finish_parse_context (parse_context, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ if (parse_context->state == PARSER_DONE) {
+ finish_parse_context (parse_context, NULL);
+ return;
+ }
+
+ read_next_chunk (stream, parse_context);
+}
+
+static void
+read_next_chunk (GInputStream *stream, ParseContext *parse_context)
+{
+ g_input_stream_read_async (stream,
+ parse_context->buffer,
+ sizeof (parse_context->buffer),
+ G_PRIORITY_DEFAULT,
+ parse_context->cancellable,
+ stream_read_cb,
+ parse_context);
+}
+
+static void
+file_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ GFile *file = G_FILE (source_object);
+ ParseContext *parse_context = user_data;
+ GFileInputStream *stream;
+ gs_free_error GError *error = NULL;
+
+ stream = g_file_read_finish (file, res, &error);
+ if (!stream) {
+ g_prefix_error (&error, "Error opening service provider database: ");
+ finish_parse_context (parse_context, error);
+ return;
+ }
+
+ read_next_chunk (G_INPUT_STREAM (stream), parse_context);
+
+ g_object_unref (stream);
+}
+
+/*****************************************************************************/
+
+void
+nm_service_providers_find_gsm_apn (const char *service_providers,
+ const char *mccmnc,
+ GCancellable *cancellable,
+ NMServiceProvidersGsmApnCallback callback,
+ gpointer user_data)
+{
+ GFile *file;
+ ParseContext *parse_context;
+
+ parse_context = g_slice_new0 (ParseContext);
+ parse_context->mccmnc = g_strdup (mccmnc);
+ parse_context->cancellable = cancellable;
+ parse_context->callback = callback;
+ parse_context->user_data = user_data;
+ parse_context->ctx = g_markup_parse_context_new (&parser, 0, parse_context, NULL);
+
+ file = g_file_new_for_path (service_providers);
+
+ g_file_read_async (file, G_PRIORITY_DEFAULT, cancellable, file_read_cb, parse_context);
+
+ g_object_unref (file);
+}
diff --git a/src/devices/wwan/nm-service-providers.h b/src/devices/wwan/nm-service-providers.h
new file mode 100644
index 0000000000..35ad2fc120
--- /dev/null
+++ b/src/devices/wwan/nm-service-providers.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_SERVICE_PROVIDERS_H__
+#define __NETWORKMANAGER_SERVICE_PROVIDERS_H__
+
+typedef void (*NMServiceProvidersGsmApnCallback) (const char *apn,
+ const char *username,
+ const char *password,
+ const char *gateway,
+ const char *auth_method,
+ const GSList *dns,
+ GError *error,
+ gpointer user_data);
+
+void nm_service_providers_find_gsm_apn (const char *service_providers,
+ const char *mccmnc,
+ GCancellable *cancellable,
+ NMServiceProvidersGsmApnCallback callback,
+ gpointer user_data);
+
+#endif /* __NETWORKMANAGER_SERVICE_PROVIDERS_H__ */