| /* |
| * |
| * 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); |