wwan: add service-providers.xml parser

This allows up to look up a default APN if the user doesn't pick one.
This commit is contained in:
Lubomir Rintel 2019-03-18 10:53:20 +01:00
parent adf0254369
commit 6632c77094
4 changed files with 489 additions and 1 deletions

View File

@ -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 += \

View File

@ -2,6 +2,7 @@ sources = files(
'nm-modem-broadband.c',
'nm-modem.c',
'nm-modem-manager.c',
'nm-service-providers.c',
)
deps = [

View File

@ -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);
}

View File

@ -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__ */