From 6632c7709492182c795895c7d32ce2542a8d9e9a Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 18 Mar 2019 10:53:20 +0100 Subject: [PATCH] wwan: add service-providers.xml parser This allows up to look up a default APN if the user doesn't pick one. --- Makefile.am | 5 +- src/devices/wwan/meson.build | 1 + src/devices/wwan/nm-service-providers.c | 460 ++++++++++++++++++++++++ src/devices/wwan/nm-service-providers.h | 24 ++ 4 files changed, 489 insertions(+), 1 deletion(-) create mode 100644 src/devices/wwan/nm-service-providers.c create mode 100644 src/devices/wwan/nm-service-providers.h 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__ */