Blob Blame History Raw
// SPDX-License-Identifier: GPL-2.0+
/* NetworkManager Applet -- allow user control over networking
 *
 * Dan Williams <dcbw@redhat.com>
 *
 * Copyright 2010 - 2017 Red Hat, Inc.
 */

#include "nm-default.h"

#include <ctype.h>

#include <libsecret/secret.h>

#include "utils.h"
#include "mobile-helpers.h"
#include "applet-dialogs.h"

GdkPixbuf *
mobile_helper_get_status_pixbuf (guint32 quality,
                                 gboolean quality_valid,
                                 guint32 state,
                                 guint32 access_tech,
                                 NMApplet *applet)
{
	GdkPixbuf *pixbuf, *qual_pixbuf, *tmp;

	if (!quality_valid)
		quality = 0;
	qual_pixbuf = nma_icon_check_and_load (mobile_helper_get_quality_icon_name (quality), applet);

	pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
	                         TRUE,
	                         qual_pixbuf ? gdk_pixbuf_get_bits_per_sample (qual_pixbuf) : 8,
	                         qual_pixbuf ? gdk_pixbuf_get_width (qual_pixbuf) : 22,
	                         qual_pixbuf ? gdk_pixbuf_get_height (qual_pixbuf) : 22);
	gdk_pixbuf_fill (pixbuf, 0xFFFFFF00);

	/* Composite the tower icon into the final icon at the bottom layer */
	tmp = nma_icon_check_and_load ("nm-wwan-tower", applet);
	if (tmp) {
		gdk_pixbuf_composite (tmp, pixbuf,
		                      0, 0,
		                      gdk_pixbuf_get_width (tmp),
		                      gdk_pixbuf_get_height (tmp),
		                      0, 0, 1.0, 1.0,
		                      GDK_INTERP_BILINEAR, 255);
	}

	/* Composite the signal quality onto the icon on top of the WWAN tower */
	if (qual_pixbuf) {
		gdk_pixbuf_composite (qual_pixbuf, pixbuf,
		                      0, 0,
		                      gdk_pixbuf_get_width (qual_pixbuf),
		                      gdk_pixbuf_get_height (qual_pixbuf),
		                      0, 0, 1.0, 1.0,
		                      GDK_INTERP_BILINEAR, 255);
	}

	/* And finally the roaming or technology icon */
	if (state == MB_STATE_ROAMING) {
		tmp = nma_icon_check_and_load ("nm-mb-roam", applet);
		if (tmp) {
			gdk_pixbuf_composite (tmp, pixbuf, 0, 0,
			                      gdk_pixbuf_get_width (tmp),
			                      gdk_pixbuf_get_height (tmp),
			                       0, 0, 1.0, 1.0,
			                      GDK_INTERP_BILINEAR, 255);
		}
	} else {
		const gchar *tech_icon_name;

		/* Only try to add the access tech info icon if we get a valid
		 * access tech reported. */
		tech_icon_name = mobile_helper_get_tech_icon_name (access_tech);
		if (tech_icon_name) {
			tmp = nma_icon_check_and_load (tech_icon_name, applet);
			if (tmp) {
				gdk_pixbuf_composite (tmp, pixbuf, 0, 0,
				                      gdk_pixbuf_get_width (tmp),
				                      gdk_pixbuf_get_height (tmp),
				                      0, 0, 1.0, 1.0,
				                      GDK_INTERP_BILINEAR, 255);
			}
		}
	}

	/* 'pixbuf' will be freed by the caller */
	return pixbuf;
}

const char *
mobile_helper_get_quality_icon_name (guint32 quality)
{
	if (quality > 80)
		return "nm-signal-100";
	else if (quality > 55)
		return "nm-signal-75";
	else if (quality > 30)
		return "nm-signal-50";
	else if (quality > 5)
		return "nm-signal-25";
	else
		return "nm-signal-00";
}

const char *
mobile_helper_get_tech_icon_name (guint32 tech)
{
	switch (tech) {
	case MB_TECH_1XRTT:
		return "nm-tech-cdma-1x";
	case MB_TECH_EVDO:
		return "nm-tech-evdo";
	case MB_TECH_GSM:
	case MB_TECH_GPRS:
		return "nm-tech-gprs";
	case MB_TECH_EDGE:
		return "nm-tech-edge";
	case MB_TECH_UMTS:
		return "nm-tech-umts";
	case MB_TECH_HSDPA:
	case MB_TECH_HSUPA:
	case MB_TECH_HSPA:
	case MB_TECH_HSPA_PLUS:
		return "nm-tech-hspa";
	case MB_TECH_LTE:
		return "nm-tech-lte";
	default:
		return NULL;
	}
}

/********************************************************************/

typedef struct {
	AppletNewAutoConnectionCallback callback;
	gpointer callback_data;
	NMDeviceModemCapabilities requested_capability;
} AutoWizardInfo;

static void
mobile_wizard_done (NMAMobileWizard *wizard,
                    gboolean cancelled,
                    NMAMobileWizardAccessMethod *method,
                    gpointer user_data)
{
	AutoWizardInfo *info = user_data;
	NMConnection *connection = NULL;

	if (!cancelled && method) {
		NMSetting *setting;
		char *uuid, *id;
		const char *setting_name;

		if (method->devtype != info->requested_capability) {
			g_warning ("Unexpected device type");
			cancelled = TRUE;
			goto done;
		}

		connection = nm_simple_connection_new ();

		if (method->devtype == NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) {
			setting_name = NM_SETTING_CDMA_SETTING_NAME;
			setting = nm_setting_cdma_new ();
			g_object_set (setting,
			              NM_SETTING_CDMA_NUMBER, "#777",
			              NM_SETTING_CDMA_USERNAME, method->username,
			              NM_SETTING_CDMA_PASSWORD, method->password,
			              NULL);
			nm_connection_add_setting (connection, setting);
		} else if (method->devtype == NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) {
			setting_name = NM_SETTING_GSM_SETTING_NAME;
			setting = nm_setting_gsm_new ();
			g_object_set (setting,
			              NM_SETTING_GSM_NUMBER, "*99#",
			              NM_SETTING_GSM_USERNAME, method->username,
			              NM_SETTING_GSM_PASSWORD, method->password,
			              NM_SETTING_GSM_APN, method->gsm_apn,
			              NULL);
			nm_connection_add_setting (connection, setting);
		} else
			g_assert_not_reached ();

		/* Default to IPv4 & IPv6 'automatic' addressing */
		setting = nm_setting_ip4_config_new ();
		g_object_set (setting, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
		nm_connection_add_setting (connection, setting);

		setting = nm_setting_ip6_config_new ();
		g_object_set (setting, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
		nm_connection_add_setting (connection, setting);

		nm_connection_add_setting (connection, nm_setting_ppp_new ());

		setting = nm_setting_connection_new ();
		id = utils_create_mobile_connection_id (method->provider_name, method->plan_name);
		uuid = nm_utils_uuid_generate ();
		g_object_set (setting,
		              NM_SETTING_CONNECTION_ID, id,
		              NM_SETTING_CONNECTION_TYPE, setting_name,
		              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
		              NM_SETTING_CONNECTION_UUID, uuid,
		              NULL);
		/* Make the new connection available only for the current user */
		nm_setting_connection_add_permission ((NMSettingConnection *) setting,
		                                      "user", g_get_user_name (), NULL);
		g_free (uuid);
		g_free (id);
		nm_connection_add_setting (connection, setting);
	}

done:
	(*(info->callback)) (connection, TRUE, cancelled, info->callback_data);

	if (wizard)
		nma_mobile_wizard_destroy (wizard);
	g_free (info);
}

gboolean
mobile_helper_wizard (NMDeviceModemCapabilities capabilities,
                      AppletNewAutoConnectionCallback callback,
                      gpointer callback_data)
{
	NMAMobileWizard *wizard;
	AutoWizardInfo *info;
	NMAMobileWizardAccessMethod *method;
	NMDeviceModemCapabilities wizard_capability;

	/* Convert the input capabilities mask into a single value */
	if (capabilities & NM_DEVICE_MODEM_CAPABILITY_LTE)
		/* All LTE modems treated as GSM/UMTS for the wizard */
		wizard_capability = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
	else if (capabilities & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS)
		wizard_capability = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
	else if (capabilities & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO)
		wizard_capability = NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO;
	else {
		g_warning ("Unknown modem capabilities (0x%X): can't launch wizard", capabilities);
		return FALSE;
	}

	info = g_malloc0 (sizeof (AutoWizardInfo));
	info->callback = callback;
	info->callback_data = callback_data;
	info->requested_capability = wizard_capability;

	wizard = nma_mobile_wizard_new (NULL,
	                                NULL,
	                                wizard_capability,
	                                FALSE,
	                                mobile_wizard_done,
	                                info);
	if (wizard) {
		nma_mobile_wizard_present (wizard);
		return TRUE;
	}

	/* Fall back to something */
	method = g_malloc0 (sizeof (NMAMobileWizardAccessMethod));
	method->devtype = wizard_capability;

	if (wizard_capability == NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS)
		method->provider_name = _("GSM");
	else if (wizard_capability == NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO)
		method->provider_name = _("CDMA");

	g_assert (   wizard_capability == NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS
	          || wizard_capability == NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO);

	mobile_wizard_done (NULL, FALSE, method, info);
	g_free (method);

	return TRUE;
}

/********************************************************************/

const SecretSchema mobile_secret_schema = {
	"org.freedesktop.NetworkManager.Mobile",
	SECRET_SCHEMA_DONT_MATCH_NAME,
	{
		{ "devid", SECRET_SCHEMA_ATTRIBUTE_STRING },
		{ "simid", SECRET_SCHEMA_ATTRIBUTE_STRING },
		{ NULL, 0 },
	}
};

static void
save_pin_cb (GObject *source,
             GAsyncResult *result,
             gpointer user_data)
{
	GError *error = NULL;
	gchar *error_msg = user_data;

	secret_password_store_finish (result, &error);
	if (error != NULL) {
		g_warning ("%s: %s", error_msg, error->message);
		g_error_free (error);
	}

	g_free (error_msg);
}

void
mobile_helper_save_pin_in_keyring (const char *devid,
                                   const char *simid,
                                   const char *pin)
{
	char *name;
	char *error_msg;

	name = g_strdup_printf (_("PIN code for SIM card “%s” on “%s”"),
	                        simid ? simid : "unknown",
	                        devid);

	error_msg = g_strdup_printf ("Saving PIN code in keyring for devid:%s simid:%s failed",
	                             devid, simid ? simid : "(unknown)");

	secret_password_store (&mobile_secret_schema,
	                       NULL, name, pin,
	                       NULL, save_pin_cb, error_msg,
	                       "devid", devid,
	                       simid ? "simid" : NULL, simid,
	                       NULL);

	g_free (name);
}

void
mobile_helper_delete_pin_in_keyring (const char *devid)
{
	secret_password_clear (&mobile_secret_schema, NULL, NULL, NULL,
	                       "devid", devid,
	                       NULL);
}

/********************************************************************/

static void
free_secrets_info (SecretsRequest *req)
{
	MobileHelperSecretsInfo *info = (MobileHelperSecretsInfo *) req;

	if (info->dialog) {
		gtk_widget_hide (info->dialog);
		gtk_widget_destroy (info->dialog);
	}

	g_free (info->secret_name);
}

static void
get_secrets_cb (GtkDialog *dialog,
                gint response,
                gpointer user_data)
{
	SecretsRequest *req = user_data;
	MobileHelperSecretsInfo *info = (MobileHelperSecretsInfo *) req;
	GError *error = NULL;

	if (response == GTK_RESPONSE_OK) {
		if (info->capability == NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS) {
			NMSettingGsm *setting;

			setting = nm_connection_get_setting_gsm (req->connection);
			if (setting) {
				g_object_set (G_OBJECT (setting),
				              info->secret_name, gtk_entry_get_text (info->secret_entry),
				              NULL);
			} else {
				error = g_error_new (NM_SECRET_AGENT_ERROR,
				                     NM_SECRET_AGENT_ERROR_FAILED,
				                     "%s.%d (%s): no GSM setting",
				                     __FILE__, __LINE__, __func__);
			}
		} else if (info->capability == NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO) {
				NMSettingCdma *setting;

				setting = nm_connection_get_setting_cdma (req->connection);
				if (setting) {
					g_object_set (G_OBJECT (setting),
					              info->secret_name, gtk_entry_get_text (info->secret_entry),
					              NULL);
				} else {
					error = g_error_new (NM_SECRET_AGENT_ERROR,
					                     NM_SECRET_AGENT_ERROR_FAILED,
					                     "%s.%d (%s): no CDMA setting",
					                     __FILE__, __LINE__, __func__);
				}
		} else
			g_assert_not_reached ();
	} else {
		error = g_error_new (NM_SECRET_AGENT_ERROR,
		                     NM_SECRET_AGENT_ERROR_USER_CANCELED,
		                     "%s.%d (%s): canceled",
		                     __FILE__, __LINE__, __func__);
	}

	if (info->capability == NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS)
		applet_secrets_request_complete_setting (req, NM_SETTING_GSM_SETTING_NAME, error);
	else if (info->capability == NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO)
		applet_secrets_request_complete_setting (req, NM_SETTING_CDMA_SETTING_NAME, error);
	else
		g_assert_not_reached ();

	applet_secrets_request_free (req);
	g_clear_error (&error);
}

static void
pin_entry_changed (GtkEditable *editable, gpointer user_data)
{
	GtkWidget *ok_button = GTK_WIDGET (user_data);
	const char *s;
	int i;
	gboolean valid = FALSE;
	guint32 len;

	s = gtk_entry_get_text (GTK_ENTRY (editable));
	if (s) {
		len = strlen (s);
		if ((len >= 4) && (len <= 8)) {
			valid = TRUE;
			for (i = 0; i < len; i++) {
				if (!g_ascii_isdigit (s[i])) {
					valid = FALSE;
					break;
				}
			}
		}
	}

	gtk_widget_set_sensitive (ok_button, valid);
}

static GtkWidget *
ask_for_pin (GtkEntry **out_secret_entry)
{
	GtkDialog *dialog;
	GtkWidget *w = NULL, *ok_button = NULL;
	GtkBox *box = NULL, *vbox = NULL;

	dialog = GTK_DIALOG (gtk_dialog_new ());
	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
	gtk_window_set_title (GTK_WINDOW (dialog), _("PIN code required"));

	gtk_dialog_add_button (dialog, _("_Cancel"), GTK_RESPONSE_REJECT);
	ok_button = gtk_dialog_add_button (dialog, _("_OK"), GTK_RESPONSE_OK);
	gtk_window_set_default (GTK_WINDOW (dialog), ok_button);

	vbox = GTK_BOX (gtk_dialog_get_content_area (dialog));

	w = gtk_label_new (_("PIN code is needed for the mobile broadband device"));
	gtk_box_pack_start (vbox, w, TRUE, TRUE, 0);

	w = gtk_alignment_new (0.5, 0.5, 0, 1.0);
	gtk_box_pack_start (vbox, w, TRUE, TRUE, 0);

	box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6));
	gtk_container_set_border_width (GTK_CONTAINER (box), 6);
	gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (box));

	gtk_box_pack_start (box, gtk_label_new ("PIN:"), FALSE, FALSE, 0);

	w = gtk_entry_new ();
	*out_secret_entry = GTK_ENTRY (w);
	gtk_entry_set_max_length (GTK_ENTRY (w), 8);
	gtk_entry_set_width_chars (GTK_ENTRY (w), 8);
	gtk_entry_set_activates_default (GTK_ENTRY (w), TRUE);
	gtk_entry_set_visibility (GTK_ENTRY (w), FALSE);
	gtk_box_pack_start (box, w, FALSE, FALSE, 0);
	g_signal_connect (w, "changed", G_CALLBACK (pin_entry_changed), ok_button);
	pin_entry_changed (GTK_EDITABLE (w), ok_button);

	gtk_widget_show_all (GTK_WIDGET (vbox));
	return GTK_WIDGET (dialog);
}

gboolean
mobile_helper_get_secrets (NMDeviceModemCapabilities capabilities,
                           SecretsRequest *req,
                           GError **error)
{
	MobileHelperSecretsInfo *info = (MobileHelperSecretsInfo *) req;
	GtkWidget *widget;
	GtkEntry *secret_entry = NULL;

	applet_secrets_request_set_free_func (req, free_secrets_info);

	if (!req->hints || !g_strv_length (req->hints)) {
		g_set_error (error,
		             NM_SECRET_AGENT_ERROR,
		             NM_SECRET_AGENT_ERROR_FAILED,
		             "%s.%d (%s): missing secrets hints.",
		             __FILE__, __LINE__, __func__);
		return FALSE;
	}
	info->secret_name = g_strdup (req->hints[0]);

	/* Convert the input capabilities mask into a single value */
	if (capabilities & NM_DEVICE_MODEM_CAPABILITY_LTE)
		/* All LTE modems treated as GSM/UMTS for the settings */
		info->capability = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
	else if (capabilities & NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS)
		info->capability = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
	else if (capabilities & NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO)
		info->capability = NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO;
	else {
		g_set_error (error,
		             NM_SECRET_AGENT_ERROR,
		             NM_SECRET_AGENT_ERROR_FAILED,
		             "%s.%d (%s): unknown modem capabilities (0x%X).",
		             __FILE__, __LINE__, __func__, capabilities);
		return FALSE;
	}

	if (!strcmp (info->secret_name, NM_SETTING_GSM_PIN)) {
		widget = ask_for_pin (&secret_entry);
	} else if (!strcmp (info->secret_name, NM_SETTING_GSM_PASSWORD) ||
	           !strcmp (info->secret_name, NM_SETTING_CDMA_PASSWORD))
		widget = applet_mobile_password_dialog_new (req->connection, &secret_entry);
	else {
		g_set_error (error,
		             NM_SECRET_AGENT_ERROR,
		             NM_SECRET_AGENT_ERROR_FAILED,
		             "%s.%d (%s): unknown secrets hint '%s'.",
		             __FILE__, __LINE__, __func__, info->secret_name);
		return FALSE;
	}
	info->dialog = widget;
	info->secret_entry = secret_entry;

	if (!widget || !secret_entry) {
		g_set_error (error,
		             NM_SECRET_AGENT_ERROR,
		             NM_SECRET_AGENT_ERROR_FAILED,
		             "%s.%d (%s): error asking for mobile secrets.",
		             __FILE__, __LINE__, __func__);
		return FALSE;
	}

	g_signal_connect (widget, "response", G_CALLBACK (get_secrets_cb), info);

	gtk_window_set_position (GTK_WINDOW (widget), GTK_WIN_POS_CENTER_ALWAYS);
	gtk_widget_realize (GTK_WIDGET (widget));
	gtk_window_present (GTK_WINDOW (widget));

	return TRUE;
}

/********************************************************************/

void
mobile_helper_get_icon (NMDevice *device,
                        NMDeviceState state,
                        NMConnection *connection,
                        GdkPixbuf **out_pixbuf,
                        const char **out_icon_name,
                        char **tip,
                        NMApplet *applet,
                        guint32 mb_state,
                        guint32 mb_tech,
                        guint32 quality,
                        gboolean quality_valid)
{
	NMSettingConnection *s_con;
	const char *id;

	g_return_if_fail (out_icon_name && !*out_icon_name);
	g_return_if_fail (tip && !*tip);

	id = nm_device_get_iface (NM_DEVICE (device));
	if (connection) {
		s_con = nm_connection_get_setting_connection (connection);
		id = nm_setting_connection_get_id (s_con);
	}

	switch (state) {
	case NM_DEVICE_STATE_PREPARE:
		*tip = g_strdup_printf (_("Preparing mobile broadband connection “%s”…"), id);
		break;
	case NM_DEVICE_STATE_CONFIG:
		*tip = g_strdup_printf (_("Configuring mobile broadband connection “%s”…"), id);
		break;
	case NM_DEVICE_STATE_NEED_AUTH:
		*tip = g_strdup_printf (_("User authentication required for mobile broadband connection “%s”…"), id);
		break;
	case NM_DEVICE_STATE_IP_CONFIG:
		*tip = g_strdup_printf (_("Requesting a network address for “%s”…"), id);
		break;
	case NM_DEVICE_STATE_ACTIVATED:
		*out_pixbuf = mobile_helper_get_status_pixbuf (quality,
		                                               quality_valid,
		                                               mb_state,
		                                               mb_tech,
		                                               applet);
		*out_icon_name = mobile_helper_get_quality_icon_name (quality_valid ?
		                                                      quality : 0);

		if ((mb_state != MB_STATE_UNKNOWN) && quality_valid) {
			gboolean roaming = (mb_state == MB_STATE_ROAMING);

			*tip = g_strdup_printf (_("Mobile broadband connection “%s” active: (%d%%%s%s)"),
			                        id, quality,
			                        roaming ? ", " : "",
			                        roaming ? _("roaming") : "");
		} else
			*tip = g_strdup_printf (_("Mobile broadband connection “%s” active"), id);
		break;
	default:
		break;
	}
}

/********************************************************************/

char *
mobile_helper_parse_3gpp_operator_name (NMAMobileProvidersDatabase **mpd, /* I/O */
                                        const char *orig,
                                        const char *op_code)
{
	NMAMobileProvider *provider;
	guint i, orig_len;

	g_assert (mpd != NULL);

	/* Some devices return the MCC/MNC if they haven't fully initialized
	 * or gotten all the info from the network yet.  Handle that.
	 */

	orig_len = orig ? strlen (orig) : 0;
	if (orig_len == 0) {
		/* If the operator name isn't valid, maybe we can look up the MCC/MNC
		 * from the operator code instead.
		 */
		if (op_code && strlen (op_code)) {
			orig = op_code;
			orig_len = strlen (orig);
		} else
			return NULL;
	} else if (orig_len < 5 || orig_len > 6)
		return g_strdup (orig);  /* not an MCC/MNC */

	for (i = 0; i < orig_len; i++) {
		if (!isdigit (orig[i]))
			return strdup (orig);
	}

	/* At this point we have a 5 or 6 character all-digit string; that's
	 * probably an MCC/MNC.  Look that up.
	 */

	if (*mpd == NULL) {
		GError *error = NULL;

		*mpd = nma_mobile_providers_database_new_sync (NULL, NULL, NULL, &error);
		if (*mpd == NULL) {
			g_warning ("Couldn't read database: %s", error->message);
			g_error_free (error);
			return strdup (orig);
		}
	}

	provider = nma_mobile_providers_database_lookup_3gpp_mcc_mnc (*mpd, orig);
	return (provider ? g_strdup (nma_mobile_provider_get_name (provider)) : NULL);
}

char *
mobile_helper_parse_3gpp2_operator_name (NMAMobileProvidersDatabase **mpd, /* I/O */
                                         guint32 sid)
{
	NMAMobileProvider *provider;

	g_assert (mpd != NULL);

	if (!sid)
		return NULL;

	if (*mpd == NULL) {
		GError *error = NULL;

		*mpd = nma_mobile_providers_database_new_sync (NULL, NULL, NULL, &error);
		if (*mpd == NULL) {
			g_warning ("Couldn't read database: %s", error->message);
			g_error_free (error);
			return NULL;
		}
	}

	provider = nma_mobile_providers_database_lookup_cdma_sid (*mpd, sid);
	return (provider ? g_strdup (nma_mobile_provider_get_name (provider)) : NULL);
}