1810 lines
58 KiB
C
1810 lines
58 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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:
|
|
*
|
|
* Copyright (C) 2008 - 2009 Novell, Inc.
|
|
* Copyright (C) 2009 - 2011 Red Hat, Inc.
|
|
* Copyright (C) 2011 Google, Inc.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include <ModemManager.h>
|
|
#define _LIBMM_INSIDE_MM
|
|
#include <libmm-glib.h>
|
|
|
|
#include "mm-iface-modem.h"
|
|
#include "mm-base-sim.h"
|
|
#include "mm-base-modem-at.h"
|
|
#include "mm-base-modem.h"
|
|
#include "mm-log.h"
|
|
#include "mm-modem-helpers.h"
|
|
|
|
static void async_initable_iface_init (GAsyncInitableIface *iface);
|
|
|
|
G_DEFINE_TYPE_EXTENDED (MMBaseSim, mm_base_sim, MM_GDBUS_TYPE_SIM_SKELETON, 0,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
|
async_initable_iface_init))
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_PATH,
|
|
PROP_CONNECTION,
|
|
PROP_MODEM,
|
|
PROP_LAST
|
|
};
|
|
|
|
enum {
|
|
SIGNAL_PIN_LOCK_ENABLED,
|
|
SIGNAL_LAST
|
|
};
|
|
|
|
static GParamSpec *properties[PROP_LAST];
|
|
|
|
struct _MMBaseSimPrivate {
|
|
/* The connection to the system bus */
|
|
GDBusConnection *connection;
|
|
/* The modem which owns this SIM */
|
|
MMBaseModem *modem;
|
|
/* The path where the SIM object is exported */
|
|
gchar *path;
|
|
};
|
|
|
|
static guint signals[SIGNAL_LAST] = { 0 };
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
mm_base_sim_export (MMBaseSim *self)
|
|
{
|
|
static guint id = 0;
|
|
gchar *path;
|
|
|
|
path = g_strdup_printf (MM_DBUS_SIM_PREFIX "/%d", id++);
|
|
g_object_set (self,
|
|
MM_BASE_SIM_PATH, path,
|
|
NULL);
|
|
g_free (path);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* CHANGE PIN (Generic implementation) */
|
|
|
|
static gboolean
|
|
change_pin_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
change_pin_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (modem, res, &error);
|
|
if (error)
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
change_pin (MMBaseSim *self,
|
|
const gchar *old_pin,
|
|
const gchar *new_pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
gchar *command;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
change_pin);
|
|
|
|
command = g_strdup_printf ("+CPWD=\"SC\",\"%s\",\"%s\"",
|
|
old_pin,
|
|
new_pin);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self->priv->modem),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)change_pin_ready,
|
|
result);
|
|
g_free (command);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* CHANGE PIN (DBus call handling) */
|
|
|
|
typedef struct {
|
|
MMBaseSim *self;
|
|
GDBusMethodInvocation *invocation;
|
|
gchar *old_pin;
|
|
gchar *new_pin;
|
|
GError *save_error;
|
|
} HandleChangePinContext;
|
|
|
|
static void
|
|
handle_change_pin_context_free (HandleChangePinContext *ctx)
|
|
{
|
|
g_assert (ctx->save_error == NULL);
|
|
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx->old_pin);
|
|
g_free (ctx->new_pin);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void
|
|
after_change_update_lock_info_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
HandleChangePinContext *ctx)
|
|
{
|
|
/* We just want to ensure that we tried to update the unlock
|
|
* retries, no big issue if it failed */
|
|
mm_iface_modem_update_lock_info_finish (self, res, NULL);
|
|
|
|
if (ctx->save_error) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, ctx->save_error);
|
|
ctx->save_error = NULL;
|
|
} else {
|
|
mm_gdbus_sim_complete_change_pin (MM_GDBUS_SIM (ctx->self), ctx->invocation);
|
|
}
|
|
|
|
handle_change_pin_context_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_change_pin_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
HandleChangePinContext *ctx)
|
|
{
|
|
MMModemLock known_lock = MM_MODEM_LOCK_UNKNOWN;
|
|
|
|
if (!MM_BASE_SIM_GET_CLASS (self)->change_pin_finish (self, res, &ctx->save_error)) {
|
|
if (g_error_matches (ctx->save_error,
|
|
MM_MOBILE_EQUIPMENT_ERROR,
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK))
|
|
known_lock = MM_MODEM_LOCK_SIM_PUK;
|
|
}
|
|
|
|
mm_iface_modem_update_lock_info (
|
|
MM_IFACE_MODEM (self->priv->modem),
|
|
known_lock,
|
|
(GAsyncReadyCallback)after_change_update_lock_info_ready,
|
|
ctx);
|
|
}
|
|
|
|
static void
|
|
handle_change_pin_auth_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
HandleChangePinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_authorize_finish (modem, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_change_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* If changing PIN is not implemented, report an error */
|
|
if (!MM_BASE_SIM_GET_CLASS (ctx->self)->change_pin ||
|
|
!MM_BASE_SIM_GET_CLASS (ctx->self)->change_pin_finish) {
|
|
g_dbus_method_invocation_return_error (ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot change PIN: "
|
|
"operation not supported");
|
|
handle_change_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->change_pin (ctx->self,
|
|
ctx->old_pin,
|
|
ctx->new_pin,
|
|
(GAsyncReadyCallback)handle_change_pin_ready,
|
|
ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_change_pin (MMBaseSim *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *old_pin,
|
|
const gchar *new_pin,
|
|
gboolean changed)
|
|
{
|
|
HandleChangePinContext *ctx;
|
|
|
|
ctx = g_new0 (HandleChangePinContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->old_pin = g_strdup (old_pin);
|
|
ctx->new_pin = g_strdup (new_pin);
|
|
|
|
mm_base_modem_authorize (self->priv->modem,
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)handle_change_pin_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* ENABLE PIN (Generic implementation) */
|
|
|
|
static gboolean
|
|
enable_pin_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
enable_pin_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (modem, res, &error);
|
|
if (error)
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
enable_pin (MMBaseSim *self,
|
|
const gchar *pin,
|
|
gboolean enabled,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
gchar *command;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
enable_pin);
|
|
|
|
command = g_strdup_printf ("+CLCK=\"SC\",%d,\"%s\"",
|
|
enabled ? 1 : 0,
|
|
pin);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self->priv->modem),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)enable_pin_ready,
|
|
result);
|
|
g_free (command);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* ENABLE PIN (DBus call handling) */
|
|
|
|
typedef struct {
|
|
MMBaseSim *self;
|
|
GDBusMethodInvocation *invocation;
|
|
gchar *pin;
|
|
gboolean enabled;
|
|
GError *save_error;
|
|
} HandleEnablePinContext;
|
|
|
|
static void
|
|
handle_enable_pin_context_free (HandleEnablePinContext *ctx)
|
|
{
|
|
g_assert (ctx->save_error == NULL);
|
|
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx->pin);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void
|
|
after_enable_update_lock_info_ready (MMIfaceModem *modem,
|
|
GAsyncResult *res,
|
|
HandleEnablePinContext *ctx)
|
|
{
|
|
/* We just want to ensure that we tried to update the unlock
|
|
* retries, no big issue if it failed */
|
|
mm_iface_modem_update_lock_info_finish (modem, res, NULL);
|
|
|
|
if (ctx->save_error) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, ctx->save_error);
|
|
ctx->save_error = NULL;
|
|
} else {
|
|
/* Signal about the new lock state */
|
|
g_signal_emit (ctx->self, signals[SIGNAL_PIN_LOCK_ENABLED], 0, ctx->enabled);
|
|
mm_gdbus_sim_complete_enable_pin (MM_GDBUS_SIM (ctx->self), ctx->invocation);
|
|
}
|
|
|
|
handle_enable_pin_context_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_enable_pin_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
HandleEnablePinContext *ctx)
|
|
{
|
|
MMModemLock known_lock = MM_MODEM_LOCK_UNKNOWN;
|
|
|
|
if (!MM_BASE_SIM_GET_CLASS (self)->enable_pin_finish (self, res, &ctx->save_error)) {
|
|
if (g_error_matches (ctx->save_error,
|
|
MM_MOBILE_EQUIPMENT_ERROR,
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK))
|
|
known_lock = MM_MODEM_LOCK_SIM_PUK;
|
|
}
|
|
|
|
mm_iface_modem_update_lock_info (
|
|
MM_IFACE_MODEM (self->priv->modem),
|
|
known_lock,
|
|
(GAsyncReadyCallback)after_enable_update_lock_info_ready,
|
|
ctx);
|
|
}
|
|
|
|
static void
|
|
handle_enable_pin_auth_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
HandleEnablePinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_authorize_finish (modem, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_enable_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* If changing PIN is not implemented, report an error */
|
|
if (!MM_BASE_SIM_GET_CLASS (ctx->self)->enable_pin ||
|
|
!MM_BASE_SIM_GET_CLASS (ctx->self)->enable_pin_finish) {
|
|
g_dbus_method_invocation_return_error (ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot enable/disable PIN: "
|
|
"operation not supported");
|
|
handle_enable_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->enable_pin (ctx->self,
|
|
ctx->pin,
|
|
ctx->enabled,
|
|
(GAsyncReadyCallback)handle_enable_pin_ready,
|
|
ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_enable_pin (MMBaseSim *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *pin,
|
|
gboolean enabled)
|
|
{
|
|
HandleEnablePinContext *ctx;
|
|
|
|
ctx = g_new0 (HandleEnablePinContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->pin = g_strdup (pin);
|
|
ctx->enabled = enabled;
|
|
|
|
mm_base_modem_authorize (self->priv->modem,
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)handle_enable_pin_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SEND PIN/PUK (Generic implementation) */
|
|
|
|
static gboolean
|
|
common_send_pin_puk_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
send_pin_puk_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (modem, res, &error);
|
|
if (error)
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
common_send_pin_puk (MMBaseSim *self,
|
|
const gchar *pin,
|
|
const gchar *puk,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
gchar *command;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
common_send_pin_puk);
|
|
|
|
command = (puk ?
|
|
g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin) :
|
|
g_strdup_printf ("+CPIN=\"%s\"", pin));
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self->priv->modem),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)send_pin_puk_ready,
|
|
result);
|
|
g_free (command);
|
|
}
|
|
|
|
static void
|
|
send_puk (MMBaseSim *self,
|
|
const gchar *puk,
|
|
const gchar *new_pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_send_pin_puk (self, new_pin, puk, callback, user_data);
|
|
}
|
|
|
|
static void
|
|
send_pin (MMBaseSim *self,
|
|
const gchar *pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_send_pin_puk (self, pin, NULL, callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SEND PIN/PUK (common logic) */
|
|
|
|
typedef struct {
|
|
MMBaseSim *self;
|
|
GSimpleAsyncResult *result;
|
|
GError *save_error;
|
|
gulong wait_for_unlock_id;
|
|
} SendPinPukContext;
|
|
|
|
static void
|
|
send_pin_puk_context_complete_and_free (SendPinPukContext *ctx)
|
|
{
|
|
if (ctx->wait_for_unlock_id)
|
|
g_signal_handler_disconnect (ctx->self->priv->modem,
|
|
ctx->wait_for_unlock_id);
|
|
if (ctx->save_error)
|
|
g_error_free (ctx->save_error);
|
|
g_simple_async_result_complete (ctx->result);
|
|
g_object_unref (ctx->result);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static GError *
|
|
error_for_unlock_check (MMModemLock lock)
|
|
{
|
|
static const MMMobileEquipmentError errors_for_locks [] = {
|
|
MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, /* MM_MODEM_LOCK_UNKNOWN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, /* MM_MODEM_LOCK_NONE */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN, /* MM_MODEM_LOCK_SIM_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN2, /* MM_MODEM_LOCK_SIM_PIN2 */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK, /* MM_MODEM_LOCK_SIM_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK2, /* MM_MODEM_LOCK_SIM_PUK2 */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN, /* MM_MODEM_LOCK_PH_SP_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK, /* MM_MODEM_LOCK_PH_SP_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN, /* MM_MODEM_LOCK_PH_NET_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK, /* MM_MODEM_LOCK_PH_NET_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN, /* MM_MODEM_LOCK_PH_SIM_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN, /* MM_MODEM_LOCK_PH_CORP_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK, /* MM_MODEM_LOCK_PH_CORP_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN, /* MM_MODEM_LOCK_PH_FSIM_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK, /* MM_MODEM_LOCK_PH_FSIM_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN, /* MM_MODEM_LOCK_PH_NETSUB_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK, /* MM_MODEM_LOCK_PH_NETSUB_PUK */
|
|
};
|
|
|
|
g_assert (lock >= MM_MODEM_LOCK_UNKNOWN);
|
|
g_assert (lock <= MM_MODEM_LOCK_PH_NETSUB_PUK);
|
|
|
|
return g_error_new (MM_MOBILE_EQUIPMENT_ERROR,
|
|
errors_for_locks[lock],
|
|
"Device is locked: '%s'",
|
|
mm_modem_lock_get_string (lock));
|
|
}
|
|
|
|
gboolean
|
|
mm_base_sim_send_pin_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
gboolean
|
|
mm_base_sim_send_puk_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
update_lock_info_ready (MMIfaceModem *modem,
|
|
GAsyncResult *res,
|
|
SendPinPukContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MMModemLock lock;
|
|
|
|
lock = mm_iface_modem_update_lock_info_finish (modem, res, &error);
|
|
/* Even if we may be SIM-PIN2/PUK2 locked, we don't consider this an error
|
|
* in the PIN/PUK sending */
|
|
if (lock != MM_MODEM_LOCK_NONE &&
|
|
lock != MM_MODEM_LOCK_SIM_PIN2 &&
|
|
lock != MM_MODEM_LOCK_SIM_PUK2) {
|
|
/* Device is locked. Now:
|
|
* - If we got an error in the original send-pin action, report it.
|
|
* - If we got an error in the pin-check action, report it.
|
|
* - Otherwise, build our own error from the lock code.
|
|
*/
|
|
if (ctx->save_error) {
|
|
g_simple_async_result_take_error (ctx->result, ctx->save_error);
|
|
ctx->save_error = NULL;
|
|
g_clear_error (&error);
|
|
} else if (error)
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
else
|
|
g_simple_async_result_take_error (ctx->result,
|
|
error_for_unlock_check (lock));
|
|
send_pin_puk_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
|
|
send_pin_puk_context_complete_and_free (ctx);
|
|
}
|
|
|
|
static void
|
|
send_pin_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
SendPinPukContext *ctx)
|
|
{
|
|
MMModemLock known_lock = MM_MODEM_LOCK_UNKNOWN;
|
|
|
|
if (!MM_BASE_SIM_GET_CLASS (self)->send_pin_finish (self, res, &ctx->save_error)) {
|
|
if (g_error_matches (ctx->save_error,
|
|
MM_MOBILE_EQUIPMENT_ERROR,
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK))
|
|
known_lock = MM_MODEM_LOCK_SIM_PUK;
|
|
}
|
|
|
|
/* Once pin/puk has been sent, recheck lock */
|
|
mm_iface_modem_update_lock_info (
|
|
MM_IFACE_MODEM (self->priv->modem),
|
|
known_lock,
|
|
(GAsyncReadyCallback)update_lock_info_ready,
|
|
ctx);
|
|
}
|
|
|
|
static void
|
|
send_puk_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
SendPinPukContext *ctx)
|
|
{
|
|
MM_BASE_SIM_GET_CLASS (self)->send_puk_finish (self, res, &ctx->save_error);
|
|
|
|
/* Once pin/puk has been sent, recheck lock */
|
|
mm_iface_modem_update_lock_info (MM_IFACE_MODEM (self->priv->modem),
|
|
MM_MODEM_LOCK_UNKNOWN, /* ask */
|
|
(GAsyncReadyCallback)update_lock_info_ready,
|
|
ctx);
|
|
}
|
|
|
|
void
|
|
mm_base_sim_send_pin (MMBaseSim *self,
|
|
const gchar *pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SendPinPukContext *ctx;
|
|
|
|
/* If sending PIN is not implemented, report an error */
|
|
if (!MM_BASE_SIM_GET_CLASS (self)->send_pin ||
|
|
!MM_BASE_SIM_GET_CLASS (self)->send_pin_finish) {
|
|
g_simple_async_report_error_in_idle (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot send PIN: "
|
|
"operation not supported");
|
|
return;
|
|
}
|
|
|
|
ctx = g_new0 (SendPinPukContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
mm_base_sim_send_pin);
|
|
|
|
MM_BASE_SIM_GET_CLASS (self)->send_pin (self,
|
|
pin,
|
|
(GAsyncReadyCallback)send_pin_ready,
|
|
ctx);
|
|
}
|
|
|
|
void
|
|
mm_base_sim_send_puk (MMBaseSim *self,
|
|
const gchar *puk,
|
|
const gchar *new_pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SendPinPukContext *ctx;
|
|
|
|
/* If sending PIN is not implemented, report an error */
|
|
if (!MM_BASE_SIM_GET_CLASS (self)->send_puk ||
|
|
!MM_BASE_SIM_GET_CLASS (self)->send_puk_finish) {
|
|
g_simple_async_report_error_in_idle (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot send PUK: "
|
|
"operation not supported");
|
|
return;
|
|
}
|
|
|
|
ctx = g_new0 (SendPinPukContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
mm_base_sim_send_puk);
|
|
|
|
MM_BASE_SIM_GET_CLASS (self)->send_puk (self,
|
|
puk,
|
|
new_pin,
|
|
(GAsyncReadyCallback)send_puk_ready,
|
|
ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SEND PIN (DBus call handling) */
|
|
|
|
typedef struct {
|
|
MMBaseSim *self;
|
|
GDBusMethodInvocation *invocation;
|
|
gchar *pin;
|
|
} HandleSendPinContext;
|
|
|
|
static void
|
|
handle_send_pin_context_free (HandleSendPinContext *ctx)
|
|
{
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx->pin);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_send_pin_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
HandleSendPinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_sim_send_pin_finish (self, res, &error))
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
else
|
|
mm_gdbus_sim_complete_send_pin (MM_GDBUS_SIM (self), ctx->invocation);
|
|
|
|
handle_send_pin_context_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_send_pin_auth_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
HandleSendPinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_authorize_finish (modem, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_send_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_base_sim_send_pin (ctx->self,
|
|
ctx->pin,
|
|
(GAsyncReadyCallback)handle_send_pin_ready,
|
|
ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_send_pin (MMBaseSim *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *pin)
|
|
{
|
|
HandleSendPinContext *ctx;
|
|
|
|
ctx = g_new0 (HandleSendPinContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->pin = g_strdup (pin);
|
|
|
|
mm_base_modem_authorize (self->priv->modem,
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)handle_send_pin_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SEND PUK (DBus call handling) */
|
|
|
|
typedef struct {
|
|
MMBaseSim *self;
|
|
GDBusMethodInvocation *invocation;
|
|
gchar *puk;
|
|
gchar *new_pin;
|
|
} HandleSendPukContext;
|
|
|
|
static void
|
|
handle_send_puk_context_free (HandleSendPukContext *ctx)
|
|
{
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx->puk);
|
|
g_free (ctx->new_pin);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_send_puk_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
HandleSendPukContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_sim_send_puk_finish (self, res, &error))
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
else
|
|
mm_gdbus_sim_complete_send_puk (MM_GDBUS_SIM (self), ctx->invocation);
|
|
|
|
handle_send_puk_context_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_send_puk_auth_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
HandleSendPukContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_authorize_finish (modem, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_send_puk_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_base_sim_send_puk (ctx->self,
|
|
ctx->puk,
|
|
ctx->new_pin,
|
|
(GAsyncReadyCallback)handle_send_puk_ready,
|
|
ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_send_puk (MMBaseSim *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *puk,
|
|
const gchar *new_pin)
|
|
{
|
|
HandleSendPukContext *ctx;
|
|
|
|
ctx = g_new0 (HandleSendPukContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->puk = g_strdup (puk);
|
|
ctx->new_pin = g_strdup (new_pin);
|
|
|
|
mm_base_modem_authorize (self->priv->modem,
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)handle_send_puk_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
sim_dbus_export (MMBaseSim *self)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
/* Handle method invocations */
|
|
g_signal_connect (self,
|
|
"handle-change-pin",
|
|
G_CALLBACK (handle_change_pin),
|
|
NULL);
|
|
g_signal_connect (self,
|
|
"handle-enable-pin",
|
|
G_CALLBACK (handle_enable_pin),
|
|
NULL);
|
|
g_signal_connect (self,
|
|
"handle-send-pin",
|
|
G_CALLBACK (handle_send_pin),
|
|
NULL);
|
|
g_signal_connect (self,
|
|
"handle-send-puk",
|
|
G_CALLBACK (handle_send_puk),
|
|
NULL);
|
|
|
|
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self),
|
|
self->priv->connection,
|
|
self->priv->path,
|
|
&error)) {
|
|
mm_warn ("couldn't export SIM at '%s': '%s'",
|
|
self->priv->path,
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sim_dbus_unexport (MMBaseSim *self)
|
|
{
|
|
/* Only unexport if currently exported */
|
|
if (g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (self)))
|
|
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
const gchar *
|
|
mm_base_sim_get_path (MMBaseSim *self)
|
|
{
|
|
return self->priv->path;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
#undef STR_REPLY_READY_FN
|
|
#define STR_REPLY_READY_FN(NAME) \
|
|
static void \
|
|
NAME##_command_ready (MMBaseModem *modem, \
|
|
GAsyncResult *res, \
|
|
GSimpleAsyncResult *operation_result) \
|
|
{ \
|
|
GError *error = NULL; \
|
|
const gchar *response; \
|
|
\
|
|
response = mm_base_modem_at_command_finish (modem, res, &error); \
|
|
if (error) \
|
|
g_simple_async_result_take_error (operation_result, error); \
|
|
else \
|
|
g_simple_async_result_set_op_res_gpointer (operation_result, \
|
|
(gpointer)response, \
|
|
NULL); \
|
|
\
|
|
g_simple_async_result_complete (operation_result); \
|
|
g_object_unref (operation_result); \
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SIM IDENTIFIER */
|
|
|
|
static gchar *
|
|
parse_iccid (const gchar *response,
|
|
GError **error)
|
|
{
|
|
gchar buf[21];
|
|
const gchar *str;
|
|
gint sw1;
|
|
gint sw2;
|
|
gboolean success = FALSE;
|
|
|
|
memset (buf, 0, sizeof (buf));
|
|
str = mm_strip_tag (response, "+CRSM:");
|
|
if (sscanf (str, "%d,%d,\"%20c\"", &sw1, &sw2, (char *) &buf) == 3)
|
|
success = TRUE;
|
|
else {
|
|
/* May not include quotes... */
|
|
if (sscanf (str, "%d,%d,%20c", &sw1, &sw2, (char *) &buf) == 3)
|
|
success = TRUE;
|
|
}
|
|
|
|
if (!success) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse the CRSM response");
|
|
return NULL;
|
|
}
|
|
|
|
if ((sw1 == 0x90 && sw2 == 0x00) ||
|
|
(sw1 == 0x91) ||
|
|
(sw1 == 0x92) ||
|
|
(sw1 == 0x9f)) {
|
|
return mm_3gpp_parse_iccid (buf, error);
|
|
} else {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM failed to handle CRSM request (sw1 %d sw2 %d)",
|
|
sw1, sw2);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static gchar *
|
|
load_sim_identifier_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
const gchar *result;
|
|
gchar *sim_identifier;
|
|
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
|
|
|
|
sim_identifier = parse_iccid (result, error);
|
|
if (!sim_identifier)
|
|
return NULL;
|
|
|
|
mm_dbg ("loaded SIM identifier: %s", sim_identifier);
|
|
return sim_identifier;
|
|
}
|
|
|
|
STR_REPLY_READY_FN (load_sim_identifier)
|
|
|
|
static void
|
|
load_sim_identifier (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading SIM identifier...");
|
|
|
|
/* READ BINARY of EFiccid (ICC Identification) ETSI TS 102.221 section 13.2 */
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self->priv->modem),
|
|
"+CRSM=176,12258,0,0,10",
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback)load_sim_identifier_command_ready,
|
|
g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
load_sim_identifier));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* IMSI */
|
|
|
|
static gchar *
|
|
parse_imsi (const gchar *response,
|
|
GError **error)
|
|
{
|
|
const gchar *s;
|
|
gint len;
|
|
|
|
g_assert (response != NULL);
|
|
|
|
for (s = mm_strip_tag (response, "+CIMI"), len = 0;
|
|
*s;
|
|
++s, ++len) {
|
|
/* IMSI is a number with 15 or less decimal digits. */
|
|
if (!isdigit (*s) || len > 15) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Invalid +CIMI response '%s'", response ? response : "<null>");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return g_strdup (response);
|
|
}
|
|
|
|
static gchar *
|
|
load_imsi_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
const gchar *result;
|
|
gchar *imsi;
|
|
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
|
|
|
|
imsi = parse_imsi (result, error);
|
|
if (!imsi)
|
|
return NULL;
|
|
|
|
mm_dbg ("loaded IMSI: %s", imsi);
|
|
return imsi;
|
|
}
|
|
|
|
STR_REPLY_READY_FN (load_imsi)
|
|
|
|
static void
|
|
load_imsi (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading IMSI...");
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self->priv->modem),
|
|
"+CIMI",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)load_imsi_command_ready,
|
|
g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
load_imsi));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Operator ID */
|
|
|
|
static guint
|
|
parse_mnc_length (const gchar *response,
|
|
GError **error)
|
|
{
|
|
gint sw1;
|
|
gint sw2;
|
|
gboolean success = FALSE;
|
|
gchar hex[51];
|
|
|
|
memset (hex, 0, sizeof (hex));
|
|
if (sscanf (response, "+CRSM:%d,%d,\"%50c\"", &sw1, &sw2, (char *) &hex) == 3)
|
|
success = TRUE;
|
|
else {
|
|
/* May not include quotes... */
|
|
if (sscanf (response, "+CRSM:%d,%d,%50c", &sw1, &sw2, (char *) &hex) == 3)
|
|
success = TRUE;
|
|
}
|
|
|
|
if (!success) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse the CRSM response");
|
|
return 0;
|
|
}
|
|
|
|
if ((sw1 == 0x90 && sw2 == 0x00) ||
|
|
(sw1 == 0x91) ||
|
|
(sw1 == 0x92) ||
|
|
(sw1 == 0x9f)) {
|
|
gsize buflen = 0;
|
|
guint32 mnc_len;
|
|
gchar *bin;
|
|
|
|
/* Make sure the buffer is only hex characters */
|
|
while (buflen < sizeof (hex) && hex[buflen]) {
|
|
if (!isxdigit (hex[buflen])) {
|
|
hex[buflen] = 0x0;
|
|
break;
|
|
}
|
|
buflen++;
|
|
}
|
|
|
|
/* Convert hex string to binary */
|
|
bin = mm_utils_hexstr2bin (hex, &buflen);
|
|
if (!bin || buflen < 4) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM returned malformed response '%s'",
|
|
hex);
|
|
g_free (bin);
|
|
return 0;
|
|
}
|
|
|
|
/* MNC length is byte 4 of this SIM file */
|
|
mnc_len = bin[3] & 0xFF;
|
|
if (mnc_len == 2 || mnc_len == 3) {
|
|
g_free (bin);
|
|
return mnc_len;
|
|
}
|
|
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM returned invalid MNC length %d (should be either 2 or 3)",
|
|
mnc_len);
|
|
g_free (bin);
|
|
return 0;
|
|
}
|
|
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM failed to handle CRSM request (sw1 %d sw2 %d)",
|
|
sw1, sw2);
|
|
return 0;
|
|
}
|
|
|
|
static gchar *
|
|
load_operator_identifier_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
const gchar *imsi;
|
|
const gchar *result;
|
|
guint mnc_length;
|
|
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
|
|
|
|
imsi = mm_gdbus_sim_get_imsi (MM_GDBUS_SIM (self));
|
|
if (!imsi) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Cannot load Operator ID without IMSI");
|
|
return NULL;
|
|
}
|
|
|
|
mnc_length = parse_mnc_length (result, &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return NULL;
|
|
}
|
|
|
|
/* Build Operator ID */
|
|
return g_strndup (imsi, 3 + mnc_length);
|
|
}
|
|
|
|
STR_REPLY_READY_FN (load_operator_identifier)
|
|
|
|
static void
|
|
load_operator_identifier (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading Operator ID...");
|
|
|
|
/* READ BINARY of EFad (Administrative Data) ETSI 51.011 section 10.3.18 */
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self->priv->modem),
|
|
"+CRSM=176,28589,0,0,4",
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)load_operator_identifier_command_ready,
|
|
g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
load_operator_identifier));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Operator Name (Service Provider Name) */
|
|
|
|
static gchar *
|
|
parse_spn (const gchar *response,
|
|
GError **error)
|
|
{
|
|
gint sw1;
|
|
gint sw2;
|
|
gboolean success = FALSE;
|
|
gchar hex[51];
|
|
|
|
memset (hex, 0, sizeof (hex));
|
|
if (sscanf (response, "+CRSM:%d,%d,\"%50c\"", &sw1, &sw2, (char *) &hex) == 3)
|
|
success = TRUE;
|
|
else {
|
|
/* May not include quotes... */
|
|
if (sscanf (response, "+CRSM:%d,%d,%50c", &sw1, &sw2, (char *) &hex) == 3)
|
|
success = TRUE;
|
|
}
|
|
|
|
if (!success) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse the CRSM response");
|
|
return NULL;
|
|
}
|
|
|
|
if ((sw1 == 0x90 && sw2 == 0x00) ||
|
|
(sw1 == 0x91) ||
|
|
(sw1 == 0x92) ||
|
|
(sw1 == 0x9f)) {
|
|
gsize buflen = 0;
|
|
gchar *bin;
|
|
gchar *utf8;
|
|
|
|
/* Make sure the buffer is only hex characters */
|
|
while (buflen < sizeof (hex) && hex[buflen]) {
|
|
if (!isxdigit (hex[buflen])) {
|
|
hex[buflen] = 0x0;
|
|
break;
|
|
}
|
|
buflen++;
|
|
}
|
|
|
|
/* Convert hex string to binary */
|
|
bin = mm_utils_hexstr2bin (hex, &buflen);
|
|
if (!bin) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM returned malformed response '%s'",
|
|
hex);
|
|
return NULL;
|
|
}
|
|
|
|
/* Remove the FF filler at the end */
|
|
while (buflen > 1 && bin[buflen - 1] == (char)0xff)
|
|
buflen--;
|
|
|
|
/* First byte is metadata; remainder is GSM-7 unpacked into octets; convert to UTF8 */
|
|
utf8 = (gchar *)mm_charset_gsm_unpacked_to_utf8 ((guint8 *)bin + 1, buflen - 1);
|
|
g_free (bin);
|
|
return utf8;
|
|
}
|
|
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM failed to handle CRSM request (sw1 %d sw2 %d)",
|
|
sw1, sw2);
|
|
return NULL;
|
|
}
|
|
|
|
static gchar *
|
|
load_operator_name_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
const gchar *result;
|
|
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
|
|
|
|
return parse_spn (result, error);
|
|
}
|
|
|
|
STR_REPLY_READY_FN (load_operator_name)
|
|
|
|
static void
|
|
load_operator_name (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading Operator Name...");
|
|
|
|
/* READ BINARY of EFspn (Service Provider Name) ETSI 51.011 section 10.3.11 */
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self->priv->modem),
|
|
"+CRSM=176,28486,0,0,17",
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)load_operator_name_command_ready,
|
|
g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
load_operator_name));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct _InitAsyncContext InitAsyncContext;
|
|
static void interface_initialization_step (InitAsyncContext *ctx);
|
|
|
|
typedef enum {
|
|
INITIALIZATION_STEP_FIRST,
|
|
INITIALIZATION_STEP_SIM_IDENTIFIER,
|
|
INITIALIZATION_STEP_IMSI,
|
|
INITIALIZATION_STEP_OPERATOR_ID,
|
|
INITIALIZATION_STEP_OPERATOR_NAME,
|
|
INITIALIZATION_STEP_LAST
|
|
} InitializationStep;
|
|
|
|
struct _InitAsyncContext {
|
|
GSimpleAsyncResult *result;
|
|
GCancellable *cancellable;
|
|
MMBaseSim *self;
|
|
InitializationStep step;
|
|
guint sim_identifier_tries;
|
|
};
|
|
|
|
static void
|
|
init_async_context_free (InitAsyncContext *ctx)
|
|
{
|
|
g_object_unref (ctx->self);
|
|
g_object_unref (ctx->result);
|
|
if (ctx->cancellable)
|
|
g_object_unref (ctx->cancellable);
|
|
g_free (ctx);
|
|
}
|
|
|
|
MMBaseSim *
|
|
mm_base_sim_new_finish (GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GObject *source;
|
|
GObject *sim;
|
|
|
|
source = g_async_result_get_source_object (res);
|
|
sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
|
|
g_object_unref (source);
|
|
|
|
if (!sim)
|
|
return NULL;
|
|
|
|
/* Only export valid SIMs */
|
|
mm_base_sim_export (MM_BASE_SIM (sim));
|
|
|
|
return MM_BASE_SIM (sim);
|
|
}
|
|
|
|
static gboolean
|
|
initable_init_finish (GAsyncInitable *initable,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
|
|
}
|
|
|
|
static void
|
|
load_sim_identifier_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
InitAsyncContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
gchar *simid;
|
|
|
|
simid = MM_BASE_SIM_GET_CLASS (ctx->self)->load_sim_identifier_finish (self, res, &error);
|
|
if (!simid) {
|
|
/* TODO: make the retries gobi-specific? */
|
|
|
|
/* Try one more time... Gobi 1K cards may reply to the first
|
|
* request with '+CRSM: 106,134,""' which is bogus because
|
|
* subsequent requests work fine.
|
|
*/
|
|
if (++ctx->sim_identifier_tries < 2) {
|
|
g_clear_error (&error);
|
|
interface_initialization_step (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_warn ("couldn't load SIM identifier: '%s'",
|
|
error ? error->message : "unknown error");
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
mm_gdbus_sim_set_sim_identifier (MM_GDBUS_SIM (self), simid);
|
|
g_free (simid);
|
|
|
|
/* Go on to next step */
|
|
ctx->step++;
|
|
interface_initialization_step (ctx);
|
|
}
|
|
|
|
#undef STR_REPLY_READY_FN
|
|
#define STR_REPLY_READY_FN(NAME,DISPLAY) \
|
|
static void \
|
|
load_##NAME##_ready (MMBaseSim *self, \
|
|
GAsyncResult *res, \
|
|
InitAsyncContext *ctx) \
|
|
{ \
|
|
GError *error = NULL; \
|
|
gchar *val; \
|
|
\
|
|
val = MM_BASE_SIM_GET_CLASS (ctx->self)->load_##NAME##_finish (self, res, &error); \
|
|
mm_gdbus_sim_set_##NAME (MM_GDBUS_SIM (self), val); \
|
|
g_free (val); \
|
|
\
|
|
if (error) { \
|
|
mm_warn ("couldn't load %s: '%s'", DISPLAY, error->message); \
|
|
g_error_free (error); \
|
|
} \
|
|
\
|
|
/* Go on to next step */ \
|
|
ctx->step++; \
|
|
interface_initialization_step (ctx); \
|
|
}
|
|
|
|
STR_REPLY_READY_FN (imsi, "IMSI")
|
|
STR_REPLY_READY_FN (operator_identifier, "Operator identifier")
|
|
STR_REPLY_READY_FN (operator_name, "Operator name")
|
|
|
|
static void
|
|
interface_initialization_step (InitAsyncContext *ctx)
|
|
{
|
|
if (g_cancellable_is_cancelled (ctx->cancellable)) {
|
|
g_simple_async_result_set_error (ctx->result,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_CANCELLED,
|
|
"Interface initialization cancelled");
|
|
g_simple_async_result_complete_in_idle (ctx->result);
|
|
init_async_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
switch (ctx->step) {
|
|
case INITIALIZATION_STEP_FIRST:
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_SIM_IDENTIFIER:
|
|
/* SIM ID is meant to be loaded only once during the whole
|
|
* lifetime of the modem. Therefore, if we already have them loaded,
|
|
* don't try to load them again. */
|
|
if (mm_gdbus_sim_get_sim_identifier (MM_GDBUS_SIM (ctx->self)) == NULL &&
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_sim_identifier &&
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_sim_identifier_finish) {
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_sim_identifier (
|
|
ctx->self,
|
|
(GAsyncReadyCallback)load_sim_identifier_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_IMSI:
|
|
/* IMSI is meant to be loaded only once during the whole
|
|
* lifetime of the modem. Therefore, if we already have them loaded,
|
|
* don't try to load them again. */
|
|
if (mm_gdbus_sim_get_imsi (MM_GDBUS_SIM (ctx->self)) == NULL &&
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_imsi &&
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_imsi_finish) {
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_imsi (
|
|
ctx->self,
|
|
(GAsyncReadyCallback)load_imsi_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_OPERATOR_ID:
|
|
/* Operator ID is meant to be loaded only once during the whole
|
|
* lifetime of the modem. Therefore, if we already have them loaded,
|
|
* don't try to load them again. */
|
|
if (mm_gdbus_sim_get_operator_identifier (MM_GDBUS_SIM (ctx->self)) == NULL &&
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_operator_identifier &&
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_operator_identifier_finish) {
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_operator_identifier (
|
|
ctx->self,
|
|
(GAsyncReadyCallback)load_operator_identifier_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_OPERATOR_NAME:
|
|
/* Operator Name is meant to be loaded only once during the whole
|
|
* lifetime of the modem. Therefore, if we already have them loaded,
|
|
* don't try to load them again. */
|
|
if (mm_gdbus_sim_get_operator_name (MM_GDBUS_SIM (ctx->self)) == NULL &&
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_operator_name &&
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_operator_name_finish) {
|
|
MM_BASE_SIM_GET_CLASS (ctx->self)->load_operator_name (
|
|
ctx->self,
|
|
(GAsyncReadyCallback)load_operator_name_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_LAST:
|
|
/* We are done without errors! */
|
|
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
|
|
g_simple_async_result_complete_in_idle (ctx->result);
|
|
init_async_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
common_init_async (GAsyncInitable *initable,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
|
|
{
|
|
InitAsyncContext *ctx;
|
|
|
|
ctx = g_new (InitAsyncContext, 1);
|
|
ctx->self = g_object_ref (initable);
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (initable),
|
|
callback,
|
|
user_data,
|
|
common_init_async);
|
|
ctx->cancellable = (cancellable ?
|
|
g_object_ref (cancellable) :
|
|
NULL);
|
|
ctx->step = INITIALIZATION_STEP_FIRST;
|
|
ctx->sim_identifier_tries = 0;
|
|
|
|
interface_initialization_step (ctx);
|
|
}
|
|
|
|
static void
|
|
initable_init_async (GAsyncInitable *initable,
|
|
int io_priority,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_gdbus_sim_set_sim_identifier (MM_GDBUS_SIM (initable), NULL);
|
|
mm_gdbus_sim_set_imsi (MM_GDBUS_SIM (initable), NULL);
|
|
mm_gdbus_sim_set_operator_identifier (MM_GDBUS_SIM (initable), NULL);
|
|
mm_gdbus_sim_set_operator_name (MM_GDBUS_SIM (initable), NULL);
|
|
|
|
common_init_async (initable, cancellable, callback, user_data);
|
|
}
|
|
|
|
void
|
|
mm_base_sim_new (MMBaseModem *modem,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_async_initable_new_async (MM_TYPE_BASE_SIM,
|
|
G_PRIORITY_DEFAULT,
|
|
cancellable,
|
|
callback,
|
|
user_data,
|
|
MM_BASE_SIM_MODEM, modem,
|
|
NULL);
|
|
}
|
|
|
|
gboolean
|
|
mm_base_sim_initialize_finish (MMBaseSim *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
return initable_init_finish (G_ASYNC_INITABLE (self), result, error);
|
|
}
|
|
|
|
void
|
|
mm_base_sim_initialize (MMBaseSim *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_init_async (G_ASYNC_INITABLE (self),
|
|
cancellable,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
static void
|
|
set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MMBaseSim *self = MM_BASE_SIM (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PATH:
|
|
g_free (self->priv->path);
|
|
self->priv->path = g_value_dup_string (value);
|
|
|
|
/* Export when we get a DBus connection AND we have a path */
|
|
if (self->priv->path &&
|
|
self->priv->connection)
|
|
sim_dbus_export (self);
|
|
break;
|
|
case PROP_CONNECTION:
|
|
g_clear_object (&self->priv->connection);
|
|
self->priv->connection = g_value_dup_object (value);
|
|
|
|
/* Export when we get a DBus connection AND we have a path */
|
|
if (!self->priv->connection)
|
|
sim_dbus_unexport (self);
|
|
else if (self->priv->path)
|
|
sim_dbus_export (self);
|
|
break;
|
|
case PROP_MODEM:
|
|
g_clear_object (&self->priv->modem);
|
|
self->priv->modem = g_value_dup_object (value);
|
|
if (self->priv->modem) {
|
|
/* Bind the modem's connection (which is set when it is exported,
|
|
* and unset when unexported) to the SIM's connection */
|
|
g_object_bind_property (self->priv->modem, MM_BASE_MODEM_CONNECTION,
|
|
self, MM_BASE_SIM_CONNECTION,
|
|
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
|
|
}
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MMBaseSim *self = MM_BASE_SIM (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PATH:
|
|
g_value_set_string (value, self->priv->path);
|
|
break;
|
|
case PROP_CONNECTION:
|
|
g_value_set_object (value, self->priv->connection);
|
|
break;
|
|
case PROP_MODEM:
|
|
g_value_set_object (value, self->priv->modem);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
mm_base_sim_init (MMBaseSim *self)
|
|
{
|
|
/* Initialize private data */
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BASE_SIM, MMBaseSimPrivate);
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
MMBaseSim *self = MM_BASE_SIM (object);
|
|
|
|
g_free (self->priv->path);
|
|
|
|
G_OBJECT_CLASS (mm_base_sim_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
MMBaseSim *self = MM_BASE_SIM (object);
|
|
|
|
if (self->priv->connection) {
|
|
/* If we arrived here with a valid connection, make sure we unexport
|
|
* the object */
|
|
sim_dbus_unexport (self);
|
|
g_clear_object (&self->priv->connection);
|
|
}
|
|
|
|
g_clear_object (&self->priv->modem);
|
|
|
|
G_OBJECT_CLASS (mm_base_sim_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
async_initable_iface_init (GAsyncInitableIface *iface)
|
|
{
|
|
iface->init_async = initable_init_async;
|
|
iface->init_finish = initable_init_finish;
|
|
}
|
|
|
|
static void
|
|
mm_base_sim_class_init (MMBaseSimClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (MMBaseSimPrivate));
|
|
|
|
/* Virtual methods */
|
|
object_class->get_property = get_property;
|
|
object_class->set_property = set_property;
|
|
object_class->finalize = finalize;
|
|
object_class->dispose = dispose;
|
|
|
|
klass->load_sim_identifier = load_sim_identifier;
|
|
klass->load_sim_identifier_finish = load_sim_identifier_finish;
|
|
klass->load_imsi = load_imsi;
|
|
klass->load_imsi_finish = load_imsi_finish;
|
|
klass->load_operator_identifier = load_operator_identifier;
|
|
klass->load_operator_identifier_finish = load_operator_identifier_finish;
|
|
klass->load_operator_name = load_operator_name;
|
|
klass->load_operator_name_finish = load_operator_name_finish;
|
|
klass->send_pin = send_pin;
|
|
klass->send_pin_finish = common_send_pin_puk_finish;
|
|
klass->send_puk = send_puk;
|
|
klass->send_puk_finish = common_send_pin_puk_finish;
|
|
klass->enable_pin = enable_pin;
|
|
klass->enable_pin_finish = enable_pin_finish;
|
|
klass->change_pin = change_pin;
|
|
klass->change_pin_finish = change_pin_finish;
|
|
|
|
properties[PROP_CONNECTION] =
|
|
g_param_spec_object (MM_BASE_SIM_CONNECTION,
|
|
"Connection",
|
|
"GDBus connection to the system bus.",
|
|
G_TYPE_DBUS_CONNECTION,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]);
|
|
|
|
properties[PROP_PATH] =
|
|
g_param_spec_string (MM_BASE_SIM_PATH,
|
|
"Path",
|
|
"DBus path of the SIM",
|
|
NULL,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]);
|
|
|
|
properties[PROP_MODEM] =
|
|
g_param_spec_object (MM_BASE_SIM_MODEM,
|
|
"Modem",
|
|
"The Modem which owns this SIM",
|
|
MM_TYPE_BASE_MODEM,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]);
|
|
|
|
/* Signals */
|
|
signals[SIGNAL_PIN_LOCK_ENABLED] =
|
|
g_signal_new (MM_BASE_SIM_PIN_LOCK_ENABLED,
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (MMBaseSimClass, pin_lock_enabled),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_generic,
|
|
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
|
|
}
|