blob: 3855edb3faa3c164c3f578e1b791ac487e5881e0 [file] [log] [blame]
/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2017-2019 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <ell/ell.h>
#include <ell/dbus.h>
#include "src/dbus.h"
#include "src/simauth.h"
#include "src/module.h"
#include "src/iwd.h"
/*
* This plugin takes care of all the communication with ofono in order to
* provide the needed algorithms for EAP-SIM/AKA/AKA'. Once this plugin is
* started it will start the initial "discovery" stage i.e.
*
* 1. Find ofono DBus service
* 2. Find all modems (and listen for Added/Removed signal)
* 3. For each modem see if SimAuthentication interface exists and get apps
* and NAI
* 4. Create simauth provider for modem if the above succeeds
*
* These steps are chained, as to avoid the complexity of concurrent method
* calls. Once the chain of methods has completed, a new simauth provider is
* created for the modem. Only then will the EAP methods be able to run the
* authentication algorithms ofono provides.
*
* If at any time the above conditions change e.g. SimAuthentication disappears,
* ofono disappears, the modem simauth provider will unregister itself from
* simauth, not allowing any future authentication algorithms to be run until
* the start conditions are met again.
*/
#define OFONO_SIM_AUTHENTICATION_IFACE "org.ofono.SimAuthentication"
#define OFONO_SIM_MANAGER_IFACE "org.ofono.SimManager"
#define OFONO_MODEM_IFACE "org.ofono.Modem"
#define OFONO_USIM_APPLICATION_IFACE "org.ofono.USimApplication"
#define OFONO_ISIM_APPLICATION_IFACE "org.ofono.ISimApplication"
struct sa_data {
char *umts_app_path;
char *ims_app_path;
struct user_cb *pending;
uint32_t serial;
};
struct ofono_modem {
char *path;
int props_watch;
uint32_t props_serial;
uint32_t apps_serial;
bool sim_auth_found : 1;
struct iwd_sim_auth *auth;
};
struct user_cb {
void *cb;
void *data;
bool is_gsm : 1;
};
static uint32_t ofono_watch;
static uint32_t modem_add_watch;
static uint32_t modem_removed_watch;
static struct l_queue *modems;
static struct user_cb *new_cb(void *func, void *data, bool is_gsm)
{
struct user_cb *cbd = l_new(struct user_cb, 1);
cbd->cb = func;
cbd->data = data;
cbd->is_gsm = is_gsm;
return cbd;
}
static void free_cb(void *ptr)
{
struct sa_data *sa_data = ptr;
l_free(sa_data->pending);
sa_data->pending = NULL;
sa_data->serial = 0;
}
/*
* Copy a byte array ("ay") from array into buf
*/
static bool get_byte_array(struct l_dbus_message_iter *array, uint8_t *buf,
int len)
{
int i;
for (i = 0; i < len; i++) {
if (!l_dbus_message_iter_next_entry(array, buf + i))
return false;
}
return true;
}
/*
* Append a byte array ("ay") to a DBus message builder
*/
static bool append_byte_array(struct l_dbus_message_builder *builder,
const uint8_t *data, int len)
{
int i;
if (!l_dbus_message_builder_enter_array(builder, "y"))
return false;
for (i = 0; i < len; i++)
if (!l_dbus_message_builder_append_basic(builder, 'y',
data + i))
return false;
if (!l_dbus_message_builder_leave_array(builder))
return false;
return true;
}
static void ims_auth_cb(struct l_dbus_message *reply, void *user_data)
{
struct sa_data *sa_data = user_data;
struct user_cb *cbd = sa_data->pending;
sim_auth_check_milenage_cb_t cb = cbd->cb;
struct l_dbus_message_iter properties;
struct l_dbus_message_iter value;
const char *prop;
uint8_t res[8];
uint8_t ck[16];
uint8_t ik[16];
uint8_t auts[16];
if (l_dbus_message_is_error(reply)) {
l_debug("ImsAuthenticate error");
goto end;
}
if (!l_dbus_message_get_arguments(reply, "a{say}", &properties))
goto end;
while (l_dbus_message_iter_next_entry(&properties, &prop, &value)) {
if (!strcmp(prop, "RES")) {
if (!get_byte_array(&value, res, 8))
goto end;
} else if (!strcmp(prop, "CK")) {
if (!get_byte_array(&value, ck, 16))
goto end;
} else if (!strcmp(prop, "IK")) {
if (!get_byte_array(&value, ik, 16))
goto end;
} else if (!strcmp(prop, "AUTS")) {
if (!get_byte_array(&value, auts, 14))
goto end;
cb(NULL, NULL, NULL, auts, cbd->data);
return;
}
}
cb(res, ck, ik, NULL, cbd->data);
return;
end:
cb(NULL, NULL, NULL, NULL, cbd->data);
}
static void gsm_auth_cb(struct l_dbus_message *reply, void *user_data)
{
struct sa_data *sa_data = user_data;
struct user_cb *cbd = sa_data->pending;
sim_auth_run_gsm_cb_t cb = cbd->cb;
struct l_dbus_message_iter array;
struct l_dbus_message_iter val;
struct l_dbus_message_iter dict;
const char *prop;
int sres_pos = 0;
int kc_pos = 0;
uint8_t kc[NUM_RANDS_MAX][EAP_SIM_KC_LEN];
uint8_t sres[NUM_RANDS_MAX][EAP_SIM_SRES_LEN];
if (l_dbus_message_is_error(reply)) {
l_debug("GsmAuthenticate error");
goto end;
}
if (!l_dbus_message_get_arguments(reply, "aa{say}", &array))
goto end;
while (l_dbus_message_iter_next_entry(&array, &dict)) {
while (l_dbus_message_iter_next_entry(&dict, &prop, &val)) {
if (sres_pos > NUM_RANDS_MAX || kc_pos > NUM_RANDS_MAX)
goto end;
if (!strcmp(prop, "SRES")) {
if (!get_byte_array(&val, sres[sres_pos++],
EAP_SIM_SRES_LEN))
goto end;
} else if (!strcmp(prop, "Kc")) {
if (!get_byte_array(&val, kc[kc_pos++],
EAP_SIM_KC_LEN))
goto end;
}
}
}
cb((const uint8_t *)sres, (const uint8_t *)kc, cbd->data);
return;
end:
cb(NULL, NULL, cbd->data);
}
static int ofono_sim_auth_run_gsm(struct iwd_sim_auth *auth,
const uint8_t *rands, int num_rands, sim_auth_run_gsm_cb_t cb,
void *data)
{
struct sa_data *sa_data = iwd_sim_auth_get_data(auth);
struct l_dbus *dbus = dbus_get_bus();
struct l_dbus_message *message;
struct l_dbus_message_builder *builder;
int i;
if (num_rands > NUM_RANDS_MAX) {
l_debug("Max number of RAND's is %d", NUM_RANDS_MAX);
return -EINVAL;
}
if (sa_data->pending) {
l_debug("Modem already has outstanding auth request");
return -EBUSY;
}
sa_data->pending = new_cb(cb, data, true);
message = l_dbus_message_new_method_call(dbus, "org.ofono",
sa_data->umts_app_path, OFONO_USIM_APPLICATION_IFACE,
"GsmAuthenticate");
builder = l_dbus_message_builder_new(message);
if (!l_dbus_message_builder_enter_array(builder, "ay"))
goto error;
for (i = 0; i < num_rands; i++) {
if (!append_byte_array(builder, rands + (i * 16), 16))
goto error;
}
if (!l_dbus_message_builder_leave_array(builder))
goto error;
if (!l_dbus_message_builder_finalize(builder))
goto error;
sa_data->serial = l_dbus_send_with_reply(dbus, message, gsm_auth_cb,
sa_data, free_cb);
if (!sa_data->serial)
goto error;
l_dbus_message_builder_destroy(builder);
return 0;
error:
l_dbus_message_builder_destroy(builder);
l_free(sa_data->pending);
sa_data->pending = NULL;
return -EIO;
}
static int ofono_sim_auth_check_milenage(struct iwd_sim_auth *auth,
const uint8_t *rand, const uint8_t *autn,
sim_auth_check_milenage_cb_t cb, void *data)
{
struct sa_data *sa_data = iwd_sim_auth_get_data(auth);
const char *iface = OFONO_ISIM_APPLICATION_IFACE;
const char *method = "ImsAuthenticate";
const char *path;
struct l_dbus *dbus = dbus_get_bus();
struct l_dbus_message *message;
struct l_dbus_message_builder *builder;
if (sa_data->pending) {
l_debug("Modem already has outstanding auth request");
return -EBUSY;
}
sa_data->pending = new_cb(cb, data, false);
/*
* If ISIM is not available, run on USIM application
*/
if (!sa_data->ims_app_path && sa_data->umts_app_path) {
iface = OFONO_USIM_APPLICATION_IFACE;
method = "UmtsAuthenticate";
path = sa_data->umts_app_path;
} else {
path = sa_data->ims_app_path;
}
message = l_dbus_message_new_method_call(dbus, "org.ofono", path,
iface, method);
builder = l_dbus_message_builder_new(message);
if (!append_byte_array(builder, rand, 16))
goto error;
if (!append_byte_array(builder, autn, 16))
goto error;
if (!l_dbus_message_builder_finalize(builder))
goto error;
sa_data->serial = l_dbus_send_with_reply(dbus, message, ims_auth_cb,
sa_data, free_cb);
if (!sa_data->serial)
goto error;
l_dbus_message_builder_destroy(builder);
return 0;
error:
l_dbus_message_builder_destroy(builder);
l_free(sa_data->pending);
sa_data->pending = NULL;
return -EIO;
}
static void ofono_sim_auth_cancel_request(struct iwd_sim_auth *auth, int id)
{
struct sa_data *sa_data = iwd_sim_auth_get_data(auth);
if (sa_data->pending) {
l_dbus_cancel(dbus_get_bus(), sa_data->serial);
sa_data->pending = NULL;
}
}
static void ofono_sim_auth_remove(struct iwd_sim_auth *auth)
{
struct sa_data *sa_data = iwd_sim_auth_get_data(auth);
l_debug("removing auth data %p", sa_data);
if (sa_data->pending) {
struct user_cb *cbd = sa_data->pending;
if (cbd->is_gsm) {
sim_auth_run_gsm_cb_t cb = cbd->cb;
cb(NULL, NULL, cbd->data);
} else {
sim_auth_check_milenage_cb_t cb = cbd->cb;
cb(NULL, NULL, NULL, NULL, cbd->data);
}
l_dbus_cancel(dbus_get_bus(), sa_data->serial);
}
l_free(sa_data->ims_app_path);
l_free(sa_data->umts_app_path);
l_free(sa_data);
}
static struct iwd_sim_auth_driver ofono_driver = {
.name = "oFono SimAuth driver",
.check_milenage = ofono_sim_auth_check_milenage,
.run_gsm = ofono_sim_auth_run_gsm,
.cancel_request = ofono_sim_auth_cancel_request,
.remove = ofono_sim_auth_remove
};
static void modem_destroy(void *data)
{
struct l_dbus *dbus = dbus_get_bus();
struct ofono_modem *modem = data;
if (modem->auth) {
/*
* If an auth instance has been created, simauth will call
* the driver's remove which cleanups the sa_data object
*/
iwd_sim_auth_remove(modem->auth);
}
l_debug("removing modem %s", modem->path);
if (modem->apps_serial)
l_dbus_cancel(dbus, modem->apps_serial);
if (modem->props_serial)
l_dbus_cancel(dbus, modem->props_serial);
l_free(modem->path);
l_dbus_remove_watch(dbus, modem->props_watch);
l_free(modem);
}
static void get_auth_apps_cb(struct l_dbus_message *reply,
void *user_data)
{
struct ofono_modem *modem = user_data;
struct sa_data *sa_data = iwd_sim_auth_get_data(modem->auth);
struct l_dbus_message_iter array;
struct l_dbus_message_iter dict;
struct l_dbus_message_iter variant;
bool sim_supported = false;
bool aka_supported = false;
const char *path;
modem->apps_serial = 0;
if (l_dbus_message_is_error(reply)) {
l_debug("GetApplications error");
goto error;
}
if (!l_dbus_message_get_arguments(reply, "a{oa{sv}}", &array))
goto error;
while (l_dbus_message_iter_next_entry(&array, &path, &dict)) {
const char *type;
const char *label;
const char *key;
while (l_dbus_message_iter_next_entry(&dict, &key, &variant)) {
if (strcmp(key, "Type"))
continue;
if (!l_dbus_message_iter_get_variant(&variant, "s",
&type, &label))
goto error;
if (!strcmp(type, "Umts")) {
sa_data->umts_app_path = l_strdup(path);
sim_supported = true;
aka_supported = true;
} else if (!strcmp(type, "Ims")) {
sa_data->ims_app_path = l_strdup(path);
aka_supported = true;
}
}
}
if (sa_data->umts_app_path || sa_data->ims_app_path) {
iwd_sim_auth_set_capabilities(modem->auth, sim_supported,
aka_supported);
iwd_sim_auth_register(modem->auth);
l_debug("modem %s successfully loaded, sim=%u, aka=%u",
modem->path, sim_supported, aka_supported);
return;
}
/* non supported type */
l_debug("unsupported modem auth capabilities");
error:
iwd_sim_auth_remove(modem->auth);
}
static void get_applications(struct ofono_modem *modem)
{
struct l_dbus *dbus = dbus_get_bus();
struct l_dbus_message *message;
message = l_dbus_message_new_method_call(dbus,
"org.ofono", modem->path,
OFONO_SIM_AUTHENTICATION_IFACE,
"GetApplications");
l_dbus_message_set_arguments(message, "");
modem->apps_serial = l_dbus_send_with_reply(dbus, message,
get_auth_apps_cb, modem, NULL);
}
static void get_auth_props_cb(struct l_dbus_message *reply,
void *user_data)
{
struct ofono_modem *modem = user_data;
struct l_dbus_message_iter array;
struct l_dbus_message_iter variant;
const char *key;
modem->props_serial = 0;
if (l_dbus_message_is_error(reply)) {
l_debug("GetProperties error");
goto error;
}
if (!l_dbus_message_get_arguments(reply, "a{sv}", &array))
goto error;
while (l_dbus_message_iter_next_entry(&array, &key, &variant)) {
if (!strcmp(key, "NetworkAccessIdentity")) {
struct sa_data *sa_data;
const char *id;
if (!l_dbus_message_iter_get_variant(&variant,
"s", &id))
goto error;
modem->auth = iwd_sim_auth_create(&ofono_driver);
sa_data = l_new(struct sa_data, 1);
iwd_sim_auth_set_data(modem->auth, sa_data);
iwd_sim_auth_set_nai(modem->auth, id);
get_applications(modem);
return;
}
}
error:
if (modem->auth)
iwd_sim_auth_remove(modem->auth);
}
static void get_properties(struct ofono_modem *modem)
{
struct l_dbus *dbus = dbus_get_bus();
struct l_dbus_message *message;
message = l_dbus_message_new_method_call(dbus,
"org.ofono", modem->path,
OFONO_SIM_AUTHENTICATION_IFACE,
"GetProperties");
l_dbus_message_set_arguments(message, "");
modem->props_serial = l_dbus_send_with_reply(dbus, message,
get_auth_props_cb, modem, NULL);
}
static void parse_interfaces(struct l_dbus_message_iter *prop,
struct ofono_modem *modem)
{
struct l_dbus_message_iter ifaces;
const char *str;
if (!l_dbus_message_iter_get_variant(prop, "as", &ifaces)) {
l_warn("error parsing modem %s interfaces", modem->path);
return;
}
while (l_dbus_message_iter_next_entry(&ifaces, &str)) {
if (!strcmp(OFONO_SIM_AUTHENTICATION_IFACE, str)) {
if (modem->sim_auth_found)
return;
modem->sim_auth_found = true;
get_properties(modem);
return;
}
}
/* SimAuthentication disappeared */
if (modem->sim_auth_found) {
/* Remove auth provider, this will free the sa_data object */
if (modem->auth)
iwd_sim_auth_remove(modem->auth);
/* put modem back into a 'discovery' state */
modem->sim_auth_found = false;
}
}
static void interfaces_changed_cb(struct l_dbus_message *message,
void *user_data)
{
struct ofono_modem *modem = user_data;
struct l_dbus_message_iter value;
const char *key;
if (!l_dbus_message_get_arguments(message, "sv", &key, &value))
return;
if (!strcmp(key, "Interfaces"))
parse_interfaces(&value, modem);
}
static bool match_modem_by_path(const void *a, const void *b)
{
struct ofono_modem *modem = (struct ofono_modem *)a;
if (strcmp(modem->path, (const char *)b))
return false;
return true;
}
static void parse_modem(const char *path, struct l_dbus_message_iter *props)
{
struct l_dbus_message_iter value;
const char *key;
struct ofono_modem *modem;
l_debug("modem found: %s", path);
if (l_queue_find(modems, match_modem_by_path, path)) {
/* should never happen */
l_error("modem %s already found", path);
return;
}
modem = l_new(struct ofono_modem, 1);
modem->path = l_strdup(path);
while (l_dbus_message_iter_next_entry(props, &key, &value)) {
if (!strcmp(key, "Interfaces"))
parse_interfaces(&value, modem);
}
/*
* add watch in case SimAuthentication goes away
*/
modem->props_watch = l_dbus_add_signal_watch(dbus_get_bus(),
"org.ofono", path, OFONO_MODEM_IFACE,
"PropertyChanged", L_DBUS_MATCH_ARGUMENT(0),
"Interfaces", L_DBUS_MATCH_NONE,
interfaces_changed_cb, modem);
l_queue_push_tail(modems, modem);
}
static void modem_added_cb(struct l_dbus_message *message, void *user_data)
{
struct l_dbus_message_iter props;
const char *path;
l_debug("");
if (!l_dbus_message_get_arguments(message, "oa{sv}", &path, &props))
return;
parse_modem(path, &props);
}
static void modem_removed_cb(struct l_dbus_message *message, void *user_data)
{
const char *path;
struct ofono_modem *modem;
if (!l_dbus_message_get_arguments(message, "o", &path))
return;
modem = l_queue_remove_if(modems, match_modem_by_path, path);
if (!modem) {
l_warn("Cannot remove modem %s, not found", path);
return;
}
modem_destroy(modem);
}
static void get_modems_cb(struct l_dbus_message *reply, void *user_data)
{
struct l_dbus_message_iter props;
struct l_dbus_message_iter modem_list;
const char *path = NULL;
if (l_dbus_message_is_error(reply)) {
l_debug("Error discovering modems");
return;
}
modems = l_queue_new();
if (l_dbus_message_get_arguments(reply, "a(oa{sv})", &modem_list)) {
while (l_dbus_message_iter_next_entry(&modem_list, &path,
&props))
parse_modem(path, &props);
}
/* watch for modems being added/removed */
modem_add_watch = l_dbus_add_signal_watch(dbus_get_bus(),
"org.ofono", "/", "org.ofono.Manager", "ModemAdded",
L_DBUS_MATCH_NONE, modem_added_cb, NULL);
modem_removed_watch = l_dbus_add_signal_watch(dbus_get_bus(),
"org.ofono", "/", "org.ofono.Manager", "ModemRemoved",
L_DBUS_MATCH_NONE, modem_removed_cb, NULL);
}
static void ofono_found(struct l_dbus *dbus, void *user_data)
{
struct l_dbus_message *message;
l_debug("");
/* start by getting all current modems */
message = l_dbus_message_new_method_call(dbus, "org.ofono", "/",
"org.ofono.Manager", "GetModems");
l_dbus_message_set_arguments(message, "");
l_dbus_send_with_reply(dbus, message, get_modems_cb, NULL, NULL);
}
static void ofono_disappeared(struct l_dbus *dbus, void *user_data)
{
l_debug("");
if (modems) {
l_queue_destroy(modems, modem_destroy);
modems = NULL;
l_dbus_remove_watch(dbus, modem_add_watch);
l_dbus_remove_watch(dbus, modem_removed_watch);
}
}
static int ofono_init(void)
{
struct l_dbus *dbus = dbus_get_bus();
ofono_watch = l_dbus_add_service_watch(dbus, "org.ofono", ofono_found,
ofono_disappeared, NULL, NULL);
return 0;
}
static void ofono_exit(void)
{
struct l_dbus *dbus = dbus_get_bus();
if (modems)
ofono_disappeared(dbus, NULL);
l_dbus_remove_watch(dbus, ofono_watch);
}
IWD_MODULE(ofono, ofono_init, ofono_exit);
IWD_MODULE_DEPENDS(ofono, simauth);