blob: 6bdbc2473cee7070dfb4b4bfd8e7ab0dc99f59f5 [file] [log] [blame] [edit]
/*
*
* seeld - Secure Element Manager
*
* Copyright (C) 2013 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>
#include <gdbus.h>
#include <string.h>
#include <glib.h>
#include "package-manager.h"
#include "pkgmgr-info.h"
#include "aul.h"
#include <near/log.h>
#include <near/plugin.h>
#include <near/driver.h>
#include <near/dbus.h>
#define TELEPHONY_SERVICE "org.tizen.telephony"
#define TELEPHONY_DEFAULT_PATH "/org/tizen/telephony"
#define MANAGER_INTERFACE TELEPHONY_SERVICE ".Manager"
#define SIM_INTERFACE TELEPHONY_SERVICE ".Sim"
/* signals */
#define SIM_STATUS "Status"
/* commands */
#define GET_MODEMS "GetModems"
#define GET_INIT_STATUS "GetInitStatus"
#define TRANSFER_APDU "TransferAPDU"
#define SIM_INIT_COMPLETED 0x03
#define MAX_CERT_TYPE 9
static DBusConnection *connection;
struct tapi_modem {
char *path;
bool sim_available;
};
static struct tapi_modem *default_modem;
static GHashTable *modem_hash;
static GHashTable *cert_hash;
static void check_sim_status_reply(DBusPendingCall *call, void *user_data)
{
DBusMessage *reply;
DBusError error;
struct tapi_modem *modem = user_data;
int status;
bool changed;
DBG("");
reply = dbus_pending_call_steal_reply(call);
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, reply) == TRUE) {
DBG("%s", error.message);
dbus_error_free(&error);
goto done;
}
if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_INT32, &status,
DBUS_TYPE_BOOLEAN, &changed,
DBUS_TYPE_INVALID))
goto done;
DBG("sim status %d changed %d", status, changed);
if (status == SIM_INIT_COMPLETED)
modem->sim_available = true;
if (default_modem == NULL && modem->sim_available == true)
default_modem = modem;
done:
dbus_message_unref(reply);
dbus_pending_call_unref(call);
}
static void check_sim_status(gpointer key, gpointer value, gpointer user_data)
{
DBusMessage *message;
DBusPendingCall *call;
struct tapi_modem *modem = value;
DBG("");
if (modem == NULL)
return;
message = dbus_message_new_method_call(TELEPHONY_SERVICE,
modem->path, SIM_INTERFACE,
GET_INIT_STATUS);
if (message == NULL)
return;
if (dbus_connection_send_with_reply(connection, message,
&call, -1) == FALSE) {
DBG("Failed to call GetInitStatus()");
dbus_message_unref(message);
return;
}
if (call == NULL) {
DBG("D-Bus connection not available");
dbus_message_unref(message);
return;
}
dbus_pending_call_set_notify(call, check_sim_status_reply,
modem, NULL);
dbus_message_unref(message);
}
static void add_modem(const char *path)
{
struct tapi_modem *modem;
DBG("modem_path %s", path);
modem = g_hash_table_lookup(modem_hash, path);
if (modem != NULL)
return;
modem = g_try_new0(struct tapi_modem, 1);
if (modem == NULL)
return;
modem->path = g_strdup(path);
modem->sim_available = false;
g_hash_table_insert(modem_hash, g_strdup(modem->path), modem);
}
static void remove_modem(gpointer user_data)
{
struct tapi_modem *modem = user_data;
g_free(modem->path);
g_free(modem);
}
static void get_modems_reply(DBusPendingCall *call, void *user_data)
{
DBusMessage *reply;
DBusError error;
DBusMessageIter array, entry;
DBG("");
reply = dbus_pending_call_steal_reply(call);
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, reply) == TRUE) {
DBG("%s", error.message);
dbus_error_free(&error);
goto done;
}
if (dbus_message_iter_init(reply, &array) == FALSE)
goto done;
if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY)
goto done;
dbus_message_iter_recurse(&array, &entry);
while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) {
const char *name, *path;
dbus_message_iter_get_basic(&entry, &name);
path = g_strdup_printf("%s/%s", TELEPHONY_DEFAULT_PATH, name);
if (path != NULL) {
add_modem(path);
g_free(path);
}
dbus_message_iter_next(&entry);
}
g_hash_table_foreach(modem_hash, check_sim_status, NULL);
done:
dbus_message_unref(reply);
dbus_pending_call_unref(call);
}
static int get_modems(void)
{
DBusMessage *message;
DBusPendingCall *call;
DBG("");
message = dbus_message_new_method_call(TELEPHONY_SERVICE,
TELEPHONY_DEFAULT_PATH,
MANAGER_INTERFACE, GET_MODEMS);
if (message == NULL)
return -ENOMEM;
if (dbus_connection_send_with_reply(connection, message,
&call, -1) == FALSE) {
DBG("Failed to call GetModems()");
dbus_message_unref(message);
return -EINVAL;
}
if (call == NULL) {
DBG("D-Bus connection not available");
dbus_message_unref(message);
return -EINVAL;
}
dbus_pending_call_set_notify(call, get_modems_reply,
NULL, NULL);
dbus_message_unref(message);
return -EINPROGRESS;
}
static void remove_cert_list(gpointer user_data)
{
g_free(user_data);
}
static void remove_cert(gpointer user_data)
{
GSList *cert_list = user_data;
g_slist_free_full(cert_list, remove_cert_list);
}
static void tapi_connect(DBusConnection *conn, void *user_data)
{
DBG("");
modem_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, remove_modem);
if (modem_hash == NULL)
return;
cert_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, remove_cert);
if (cert_hash == NULL)
return;
get_modems();
}
static void tapi_disconnect(DBusConnection *conn, void *user_data)
{
DBG("connection %p", connection);
if (modem_hash == NULL)
return;
g_hash_table_destroy(modem_hash);
modem_hash = NULL;
if (cert_hash == NULL)
return;
g_hash_table_destroy(cert_hash);
cert_hash = NULL;
}
static gboolean sim_changed(DBusConnection *conn, DBusMessage *message,
void *user_data)
{
const char *path = dbus_message_get_path(message);
struct tapi_modem *modem;
int status;
DBG("modem_path %s", path);
modem = g_hash_table_lookup(modem_hash, path);
if (modem == NULL)
return TRUE;
if (!dbus_message_get_args(message, NULL, DBUS_TYPE_INT32, &status,
DBUS_TYPE_INVALID))
return TRUE;
DBG("sim status %d", status);
if (status == SIM_INIT_COMPLETED)
modem->sim_available = true;
else
modem->sim_available = false;
if (default_modem == modem && modem->sim_available == false)
default_modem = NULL;
if (default_modem == NULL && modem->sim_available == true)
default_modem = modem;
return TRUE;
}
struct tapi_transceive_context {
void *context;
transceive_cb_t cb;
};
static void tapi_transfer_apdu_reply(DBusPendingCall *call, void *user_data)
{
struct tapi_transceive_context *ctx = user_data;
DBusMessage *reply;
DBusError error;
DBusMessageIter iter, array, entry;
uint8_t *apdu = NULL;
int result = -EIO, apdu_length = 0;
DBG("");
reply = dbus_pending_call_steal_reply(call);
dbus_error_init(&error);
if (dbus_set_error_from_message(&error, reply) == TRUE) {
DBG("%s", error.message);
dbus_error_free(&error);
goto done;
}
if (dbus_message_iter_init(reply, &iter) == FALSE)
goto done;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32)
goto done;
dbus_message_iter_get_basic(&iter, &result);
dbus_message_iter_next(&iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
goto done;
dbus_message_iter_recurse(&iter, &array);
if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY)
goto done;
dbus_message_iter_recurse(&array, &entry);
dbus_message_iter_get_fixed_array(&entry, &apdu, &apdu_length);
if (apdu_length == 0 || apdu == NULL)
DBG("data is NULL");
DBG("apdu_length %d", apdu_length);
done:
ctx->cb(ctx->context, apdu, apdu_length, result);
g_free(ctx);
dbus_message_unref(reply);
dbus_pending_call_unref(call);
}
static int tapi_transceive(uint8_t ctrl_idx, uint32_t se_idx,
uint8_t *apdu, size_t apdu_length,
transceive_cb_t cb, void *context)
{
DBusMessage *message;
DBusMessageIter iter;
DBusMessageIter value, array;
DBusPendingCall *call;
struct tapi_transceive_context *ctx;
int err;
DBG("%zd APDU %p", apdu_length, apdu);
if (default_modem == NULL) {
err = -EIO;
goto fail;
}
ctx = g_try_malloc0(sizeof(struct tapi_transceive_context));
if (ctx == NULL) {
err = -ENOMEM;
goto fail;
}
ctx->context = context;
ctx->cb = cb;
message = dbus_message_new_method_call(TELEPHONY_SERVICE,
default_modem->path,
SIM_INTERFACE, TRANSFER_APDU);
if (message == NULL) {
err = -ENOMEM;
goto fail;
}
dbus_message_iter_init_append(message, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
DBUS_TYPE_ARRAY_AS_STRING
DBUS_TYPE_BYTE_AS_STRING,
&value);
dbus_message_iter_open_container(&value, DBUS_TYPE_ARRAY,
DBUS_TYPE_BYTE_AS_STRING,
&array);
dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
&apdu, apdu_length);
dbus_message_iter_close_container(&value, &array);
dbus_message_iter_close_container(&iter, &value);
if (dbus_connection_send_with_reply(connection, message,
&call, -1) == FALSE) {
DBG("Failed to Transfer APDU through UICC");
dbus_message_unref(message);
err = -EINVAL;
goto fail;
}
if (call == NULL) {
DBG("D-Bus connection not available");
dbus_message_unref(message);
err = -EINVAL;
goto fail;
}
dbus_pending_call_set_notify(call, tapi_transfer_apdu_reply,
ctx, NULL);
dbus_message_unref(message);
return 0;
fail:
cb(context, NULL, 0, err);
if (ctx != NULL)
g_free(ctx);
return err;
}
static struct seel_io_driver tizen_io_driver = {
.type = SEEL_SE_UICC,
.transceive = tapi_transceive,
};
static uint8_t *digest_cert(const char *cert, int length)
{
GChecksum *checksum;
uint8_t *hash;
gsize digest_len;
if (cert == NULL || length < 0)
return NULL;
digest_len = g_checksum_type_get_length(G_CHECKSUM_SHA1);
hash = g_try_malloc(digest_len);
if (hash == NULL)
return NULL;
checksum = g_checksum_new(G_CHECKSUM_SHA1);
if (checksum == NULL) {
g_free(hash);
return NULL;
}
g_checksum_update(checksum, cert, length);
g_checksum_get_digest(checksum, hash, &digest_len);
DBG("Digest is: ");
__seel_apdu_dump(hash, digest_len);
g_checksum_free(checksum);
return hash;
}
static GSList *tizen_get_hashes(pid_t pid)
{
char pkg_name[256] = { 0, };
pkgmgrinfo_appinfo_h appinfo;
pkgmgr_certinfo_h certinfo;
GSList *cert_list = NULL;
char *pkgid;
int i;
DBG("");
if (aul_app_get_pkgname_bypid(pid, pkg_name, sizeof(pkg_name)) < 0)
return NULL;
if (pkgmgrinfo_appinfo_get_appinfo(pkg_name, &appinfo) < 0)
return NULL;
if (pkgmgrinfo_appinfo_get_pkgid(appinfo, &pkgid) < 0)
goto destroy_appinfo;
DBG("Package ID %s", pkgid);
cert_list = g_hash_table_lookup(cert_hash, pkgid);
if (cert_list != NULL)
return cert_list;
if (pkgmgr_pkginfo_create_certinfo(&certinfo) < 0)
goto destroy_appinfo;
if (pkgmgr_pkginfo_load_certinfo(pkgid, certinfo) < 0)
goto destroy_certinfo;
for (i = 0; i < MAX_CERT_TYPE; i++) {
const char *cert_64 = NULL;
const char *cert_bin = NULL;
uint8_t *hash = NULL;
gsize length;
if (pkgmgr_pkginfo_get_cert_value(certinfo,
(pkgmgr_cert_type)i, &cert_64) < 0)
continue;
if (cert_64 != NULL && strlen(cert_64) > 0) {
cert_bin = g_base64_decode(cert_64, &length);
if (cert_bin != NULL && length > 0)
hash = digest_cert(cert_bin, length);
if (hash != NULL)
cert_list = g_slist_append(cert_list, hash);
}
}
if (cert_list != NULL)
g_hash_table_insert(cert_hash, g_strdup(pkgid), cert_list);
destroy_certinfo:
pkgmgr_pkginfo_destroy_certinfo(certinfo);
destroy_appinfo:
pkgmgrinfo_appinfo_destroy_appinfo(appinfo);
return cert_list;
}
static struct seel_cert_driver tizen_cert_driver = {
.get_hashes = tizen_get_hashes,
};
static guint watch;
static guint sim_watch;
static int tapi_init(void)
{
int err;
DBG("");
connection = near_dbus_get_connection();
if (connection == NULL)
return -EIO;
watch = g_dbus_add_service_watch(connection, TELEPHONY_SERVICE,
tapi_connect, tapi_disconnect,
NULL, NULL);
sim_watch = g_dbus_add_signal_watch(connection, TELEPHONY_SERVICE,
NULL, SIM_INTERFACE, SIM_STATUS,
sim_changed, NULL, NULL);
if (watch == 0 || sim_watch == 0) {
err = -EIO;
goto remove;
}
err = seel_io_driver_register(&tizen_io_driver);
if (err < 0)
goto remove;
err = seel_cert_driver_register(&tizen_cert_driver);
if (err < 0) {
seel_io_driver_unregister(&tizen_io_driver);
goto remove;
}
return 0;
remove:
g_dbus_remove_watch(connection, watch);
g_dbus_remove_watch(connection, sim_watch);
dbus_connection_unref(connection);
return err;
}
static void tapi_exit(void)
{
DBG("");
seel_io_driver_unregister(&tizen_io_driver);
seel_cert_driver_unregister(&tizen_cert_driver);
if (modem_hash != NULL) {
g_hash_table_destroy(modem_hash);
modem_hash = NULL;
}
if (cert_hash != NULL) {
g_hash_table_destroy(cert_hash);
cert_hash = NULL;
}
g_dbus_remove_watch(connection, watch);
g_dbus_remove_watch(connection, sim_watch);
dbus_connection_unref(connection);
}
SEEL_PLUGIN_DEFINE(tizen, "Tizen telephony plugin", VERSION,
SEEL_PLUGIN_PRIORITY_HIGH, tapi_init, tapi_exit)