Files
BPI-CM9-Linux/aml-comp/vendor/amlogic/aml_commonlib/utils/default_agent.c
T

1006 lines
24 KiB
C
Raw Normal View History

2026-11-11 08:50:43 +08:00
/*
* Copyright (c) 2025 Amlogic, Inc. All rights reserved.
*
* This source code is subject to the terms and conditions defined in the
* file 'LICENSE' which is part of this source code package.
*
* default_agent.c - Standalone BlueZ agent daemon
*
* Monitors org.bluez on the system D-Bus, registers itself as the default
* agent (org.bluez.Agent1) and auto-confirms pairing requests.
*
* This agent is intended for embedded unattended devices (e.g. set-top
* boxes) where there is no interactive user: pairing confirmations are
* automatically accepted. Only enable it on trusted products via
* USE_DEFAULT_AGENT=y.
*
* Passkey handling:
* - default passkey is 0000
* - command line option -p <passkey> sets a fixed passkey
* - at runtime, typing "passkey <digits>" on stdin (e.g. serial console)
* updates the passkey; "passkey 000000" restores the default 0000
*
* Build: see Makefile (USE_DEFAULT_AGENT=y)
* Depends only on gio-2.0/glib-2.0/dbus-1.
*/
#include <gio/gio.h>
#include <glib-unix.h>
#include <ctype.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define AGENT_PATH "/org/bluez/agent"
#define AGENT_INTERFACE "org.bluez.Agent1"
#define BLUEZ_NAME "org.bluez"
#define AGENT_MANAGER_PATH "/org/bluez"
#define AGENT_MANAGER_IFACE "org.bluez.AgentManager1"
#define DBUS_NAME "org.freedesktop.DBus"
#define DBUS_PATH "/org/freedesktop/DBus"
#define DBUS_IFACE "org.freedesktop.DBus"
#define DEFAULT_PASSKEY "0000"
#define DEFAULT_CAPABILITY "NoInputNoOutput"
#define AGENT_PROMPT "default-agent: "
#define PASSKEY_MAX_LEN 6
static const gchar *opt_capability = NULL;
static gchar *opt_passkey = NULL;
static gboolean opt_debug = FALSE;
static gchar passkey[PASSKEY_MAX_LEN + 1];
static GDBusConnection *dbus_conn = NULL;
static gboolean agent_registered = FALSE;
static guint agent_reg_id = 0;
static guint name_owner_sub_id = 0;
static guint stdin_watch_id = 0;
static GMainLoop *main_loop = NULL;
static const GDBusInterfaceVTable agent_vtable;
static const GDBusInterfaceInfo agent_interface_info;
/* ------------------------------------------------------------------------- */
/* Logging */
/* ------------------------------------------------------------------------- */
#define info(fmt, ...) g_print(AGENT_PROMPT fmt "\n", ##__VA_ARGS__)
#define err(fmt, ...) g_printerr(AGENT_PROMPT "ERROR: " fmt "\n", ##__VA_ARGS__)
#define dbg(fmt, ...) \
G_STMT_START { \
if (opt_debug) \
g_print(AGENT_PROMPT "DEBUG: " fmt "\n", ##__VA_ARGS__); \
} \
G_STMT_END
/* ------------------------------------------------------------------------- */
/* Passkey */
/* ------------------------------------------------------------------------- */
static gboolean validate_passkey(const gchar *str) {
gsize len;
if (!str)
return FALSE;
len = strlen(str);
if (len == 0 || len > PASSKEY_MAX_LEN)
return FALSE;
if (strspn(str, "0123456789") != len)
return FALSE;
return TRUE;
}
static gboolean set_passkey_internal(const gchar *str, gboolean verbose) {
gsize len;
if (!validate_passkey(str)) {
if (verbose) {
info("invalid passkey '%s', use 1-%d digits", str ? str : "(null)",
PASSKEY_MAX_LEN);
}
return FALSE;
}
len = strlen(str);
memset(passkey, 0, sizeof(passkey));
memcpy(passkey, str, len);
if (verbose)
info("passkey set to %s", passkey);
return TRUE;
}
static void set_passkey_from_command(const gchar *str) {
gchar *tmp;
if (!str) {
info("usage: passkey <1-%d digits>", PASSKEY_MAX_LEN);
return;
}
tmp = g_strdup(str);
g_strstrip(tmp);
if (!*tmp) {
info("usage: passkey <1-%d digits>", PASSKEY_MAX_LEN);
g_free(tmp);
return;
}
set_passkey_internal(tmp, TRUE);
g_free(tmp);
}
/* ------------------------------------------------------------------------- */
/* BlueZ Agent registration */
/* ------------------------------------------------------------------------- */
static gboolean unregister_agent(void) {
GVariant *res;
GError *error = NULL;
if (!agent_registered || !dbus_conn)
return TRUE;
info("unregistering agent...");
res = g_dbus_connection_call_sync(dbus_conn, BLUEZ_NAME, AGENT_MANAGER_PATH,
AGENT_MANAGER_IFACE, "UnregisterAgent",
g_variant_new("(o)", AGENT_PATH), NULL,
G_DBUS_CALL_FLAGS_NONE, 3000, NULL, &error);
if (!res) {
/*
* BlueZ may already have disappeared.
* In that case the registration is already gone.
*/
if (error) {
dbg("UnregisterAgent failed: %s", error->message);
g_error_free(error);
}
} else {
g_variant_unref(res);
}
agent_registered = FALSE;
info("agent unregistered");
return TRUE;
}
static gboolean register_agent(void) {
GVariant *res;
GError *error = NULL;
if (!dbus_conn)
return FALSE;
if (agent_registered) {
dbg("agent already registered");
return TRUE;
}
/*
* Make sure BlueZ is currently available.
*/
res = g_dbus_connection_call_sync(
dbus_conn, DBUS_NAME, DBUS_PATH, DBUS_IFACE, "GetNameOwner",
g_variant_new("(s)", BLUEZ_NAME), G_VARIANT_TYPE("(s)"),
G_DBUS_CALL_FLAGS_NONE, 3000, NULL, &error);
if (!res) {
dbg("BlueZ is not available");
if (error)
g_error_free(error);
return FALSE;
}
g_variant_unref(res);
info("registering agent...");
/*
* RegisterAgent(object, capability)
*/
res = g_dbus_connection_call_sync(
dbus_conn, BLUEZ_NAME, AGENT_MANAGER_PATH, AGENT_MANAGER_IFACE,
"RegisterAgent", g_variant_new("(os)", AGENT_PATH, opt_capability), NULL,
G_DBUS_CALL_FLAGS_NONE, 3000, NULL, &error);
if (!res) {
if (error) {
err("RegisterAgent failed: %s", error->message);
g_error_free(error);
} else {
err("RegisterAgent failed");
}
return FALSE;
}
g_variant_unref(res);
/*
* RegisterAgent succeeded.
*/
agent_registered = TRUE;
/*
* RequestDefaultAgent(object)
*/
res = g_dbus_connection_call_sync(dbus_conn, BLUEZ_NAME, AGENT_MANAGER_PATH,
AGENT_MANAGER_IFACE, "RequestDefaultAgent",
g_variant_new("(o)", AGENT_PATH), NULL,
G_DBUS_CALL_FLAGS_NONE, 3000, NULL, &error);
if (!res) {
if (error) {
err("RequestDefaultAgent failed: %s", error->message);
g_error_free(error);
} else {
err("RequestDefaultAgent failed");
}
/*
* Roll back RegisterAgent.
*/
unregister_agent();
return FALSE;
}
g_variant_unref(res);
info("agent registered as default "
"(capability=%s, passkey=%s)",
opt_capability, passkey);
return TRUE;
}
/* ------------------------------------------------------------------------- */
/* BlueZ NameOwnerChanged */
/* ------------------------------------------------------------------------- */
static void on_name_owner_changed(GDBusConnection *conn,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters, gpointer user_data) {
const gchar *name;
const gchar *old_owner;
const gchar *new_owner;
(void)conn;
(void)sender_name;
(void)object_path;
(void)interface_name;
(void)signal_name;
(void)user_data;
g_variant_get(parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
if (g_strcmp0(name, BLUEZ_NAME) != 0)
return;
dbg("NameOwnerChanged %s: '%s' -> '%s'", name, old_owner, new_owner);
/*
* BlueZ disappeared.
*/
if (!new_owner || *new_owner == '\0') {
if (agent_registered) {
agent_registered = FALSE;
info("bluez disappeared, agent registration lost");
}
return;
}
/*
* BlueZ appeared.
*/
if (!old_owner || *old_owner == '\0') {
info("bluez appeared, registering agent...");
register_agent();
return;
}
/*
* BlueZ owner changed.
*
* Usually means bluetoothd restarted.
*/
if (g_strcmp0(old_owner, new_owner) != 0) {
agent_registered = FALSE;
info("bluez owner changed, registering agent...");
register_agent();
return;
}
}
/* ------------------------------------------------------------------------- */
/* D-Bus setup */
/* ------------------------------------------------------------------------- */
static gboolean setup_dbus(void) {
GError *error = NULL;
/*
* Connect directly to system bus.
*
* We intentionally do NOT use g_bus_own_name().
*/
dbus_conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
if (!dbus_conn) {
err("failed to connect to system bus: %s",
error ? error->message : "unknown error");
if (error)
g_error_free(error);
return FALSE;
}
info("connected to system bus");
/*
* Export Agent1 object.
*/
agent_reg_id = g_dbus_connection_register_object(
dbus_conn, AGENT_PATH, (GDBusInterfaceInfo *)&agent_interface_info,
(GDBusInterfaceVTable *)&agent_vtable, NULL, NULL, &error);
if (agent_reg_id == 0) {
err("failed to register agent object: %s",
error ? error->message : "unknown error");
if (error)
g_error_free(error);
return FALSE;
}
info("agent object exported at %s", AGENT_PATH);
/*
* Watch BlueZ NameOwnerChanged.
*
* This allows us to handle:
*
* 1. BlueZ already running
* 2. BlueZ starts later
* 3. BlueZ restarts
*/
name_owner_sub_id = g_dbus_connection_signal_subscribe(
dbus_conn, DBUS_NAME, DBUS_IFACE, "NameOwnerChanged", DBUS_PATH,
BLUEZ_NAME, G_DBUS_SIGNAL_FLAGS_NONE, on_name_owner_changed, NULL, NULL);
if (name_owner_sub_id == 0) {
err("failed to subscribe NameOwnerChanged");
return FALSE;
}
/*
* Check whether BlueZ is already running.
*/
if (!register_agent()) {
info("bluez is not running yet, waiting for BlueZ...");
}
return TRUE;
}
/* ------------------------------------------------------------------------- */
/* Agent methods */
/* ------------------------------------------------------------------------- */
static void agent_release(void) {
agent_registered = FALSE;
info("agent released by bluez");
}
static void handle_request_pincode(GDBusMethodInvocation *invocation) {
info("RequestPinCode -> %s", passkey);
g_dbus_method_invocation_return_value(invocation,
g_variant_new("(s)", passkey));
}
static void handle_request_passkey(GDBusMethodInvocation *invocation) {
guint32 value = 0;
value = (guint32)g_ascii_strtoull(passkey, NULL, 10);
info("RequestPasskey -> %06u", value);
g_dbus_method_invocation_return_value(invocation,
g_variant_new("(u)", value));
}
static void agent_method_call(GDBusConnection *conn, const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name, GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data) {
(void)conn;
(void)sender;
(void)object_path;
(void)interface_name;
(void)user_data;
if (g_strcmp0(method_name, "Release") == 0) {
agent_release();
g_dbus_method_invocation_return_value(invocation, NULL);
} else if (g_strcmp0(method_name, "RequestPinCode") == 0) {
handle_request_pincode(invocation);
} else if (g_strcmp0(method_name, "RequestPasskey") == 0) {
handle_request_passkey(invocation);
} else if (g_strcmp0(method_name, "DisplayPinCode") == 0) {
const gchar *device;
const gchar *pin;
g_variant_get(parameters, "(&o&s)", &device, &pin);
info("DisplayPinCode device=%s pin=%s", device, pin);
g_dbus_method_invocation_return_value(invocation, NULL);
} else if (g_strcmp0(method_name, "DisplayPasskey") == 0) {
const gchar *device;
guint32 passkey_val;
guint16 entered;
g_variant_get(parameters, "(&ouq)", &device, &passkey_val, &entered);
info("DisplayPasskey device=%s passkey=%06u entered=%u", device,
passkey_val, entered);
g_dbus_method_invocation_return_value(invocation, NULL);
} else if (g_strcmp0(method_name, "RequestConfirmation") == 0) {
const gchar *device;
guint32 passkey_val;
g_variant_get(parameters, "(&ou)", &device, &passkey_val);
/*
* Embedded unattended device:
* automatically accept confirmation.
*/
info("RequestConfirmation device=%s passkey=%06u -> auto accept", device,
passkey_val);
g_dbus_method_invocation_return_value(invocation, NULL);
} else if (g_strcmp0(method_name, "RequestAuthorization") == 0) {
const gchar *device;
g_variant_get(parameters, "(&o)", &device);
info("RequestAuthorization device=%s -> auto accept", device);
g_dbus_method_invocation_return_value(invocation, NULL);
} else if (g_strcmp0(method_name, "AuthorizeService") == 0) {
const gchar *device;
const gchar *uuid;
g_variant_get(parameters, "(&o&s)", &device, &uuid);
info("AuthorizeService device=%s uuid=%s -> auto accept", device, uuid);
g_dbus_method_invocation_return_value(invocation, NULL);
} else if (g_strcmp0(method_name, "Cancel") == 0) {
info("Cancel");
g_dbus_method_invocation_return_value(invocation, NULL);
} else {
err("unknown Agent1 method: %s", method_name);
g_dbus_method_invocation_return_dbus_error(
invocation, "org.bluez.Error.InvalidArgs", "Unknown method");
}
}
/* ------------------------------------------------------------------------- */
/* Agent interface description */
/* ------------------------------------------------------------------------- */
static const GDBusInterfaceVTable agent_vtable = {
.method_call = agent_method_call,
.get_property = NULL,
.set_property = NULL,
};
/*
* Common arguments.
*/
static const GDBusArgInfo arg_device = {-1, "device", "o", NULL};
static const GDBusArgInfo arg_pin = {-1, "pincode", "s", NULL};
static const GDBusArgInfo arg_passkey = {-1, "passkey", "u", NULL};
static const GDBusArgInfo arg_entered = {-1, "entered", "q", NULL};
static const GDBusArgInfo arg_uuid = {-1, "uuid", "s", NULL};
static const GDBusArgInfo arg_out_pin = {-1, "pincode", "s", NULL};
static const GDBusArgInfo arg_out_passkey = {-1, "passkey", "u", NULL};
/*
* RequestPinCode(o) -> (s)
*/
static const GDBusArgInfo *req_pin_in[] = {&arg_device, NULL};
static const GDBusArgInfo *req_pin_out[] = {&arg_out_pin, NULL};
/*
* RequestPasskey(o) -> (u)
*/
static const GDBusArgInfo *req_passkey_in[] = {&arg_device, NULL};
static const GDBusArgInfo *req_passkey_out[] = {&arg_out_passkey, NULL};
/*
* DisplayPinCode(o, s)
*/
static const GDBusArgInfo *display_pin_in[] = {&arg_device, &arg_pin, NULL};
/*
* DisplayPasskey(o, u, q)
*/
static const GDBusArgInfo *display_passkey_in[] = {&arg_device, &arg_passkey,
&arg_entered, NULL};
/*
* RequestConfirmation(o, u)
*/
static const GDBusArgInfo *confirm_in[] = {&arg_device, &arg_passkey, NULL};
/*
* RequestAuthorization(o)
*/
static const GDBusArgInfo *authorize_in[] = {&arg_device, NULL};
/*
* AuthorizeService(o, s)
*/
static const GDBusArgInfo *authorize_service_in[] = {&arg_device, &arg_uuid,
NULL};
static const GDBusMethodInfo m_release = {-1, "Release", NULL, NULL, NULL};
static const GDBusMethodInfo m_req_pin = {-1, "RequestPinCode",
(GDBusArgInfo **)req_pin_in,
(GDBusArgInfo **)req_pin_out, NULL};
static const GDBusMethodInfo m_req_passkey = {
-1, "RequestPasskey", (GDBusArgInfo **)req_passkey_in,
(GDBusArgInfo **)req_passkey_out, NULL};
static const GDBusMethodInfo m_display_pin = {
-1, "DisplayPinCode", (GDBusArgInfo **)display_pin_in, NULL, NULL};
static const GDBusMethodInfo m_display_passkey = {
-1, "DisplayPasskey", (GDBusArgInfo **)display_passkey_in, NULL, NULL};
static const GDBusMethodInfo m_confirm = {
-1, "RequestConfirmation", (GDBusArgInfo **)confirm_in, NULL, NULL};
static const GDBusMethodInfo m_authorize = {
-1, "RequestAuthorization", (GDBusArgInfo **)authorize_in, NULL, NULL};
static const GDBusMethodInfo m_authorize_service = {
-1, "AuthorizeService", (GDBusArgInfo **)authorize_service_in, NULL, NULL};
static const GDBusMethodInfo m_cancel = {-1, "Cancel", NULL, NULL, NULL};
static const GDBusMethodInfo *agent_methods[] = {
&m_release, &m_req_pin, &m_req_passkey, &m_display_pin,
&m_display_passkey, &m_confirm, &m_authorize, &m_authorize_service,
&m_cancel, NULL};
static const GDBusInterfaceInfo agent_interface_info = {
-1, AGENT_INTERFACE, (GDBusMethodInfo **)agent_methods, NULL, NULL, NULL,
NULL};
/* ------------------------------------------------------------------------- */
/* stdin */
/* ------------------------------------------------------------------------- */
static gboolean on_stdin_input(GIOChannel *channel, GIOCondition cond,
gpointer user_data) {
gchar *line = NULL;
gsize len = 0;
GError *error = NULL;
GIOStatus status;
(void)user_data;
if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
stdin_watch_id = 0;
info("stdin closed");
return G_SOURCE_REMOVE;
}
status = g_io_channel_read_line(channel, &line, &len, NULL, &error);
if (status != G_IO_STATUS_NORMAL || !line) {
if (error) {
dbg("stdin read failed: %s", error->message);
g_error_free(error);
}
g_free(line);
if (status == G_IO_STATUS_EOF) {
stdin_watch_id = 0;
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
g_strstrip(line);
if (g_str_has_prefix(line, "passkey")) {
const gchar *arg = line + strlen("passkey");
/*
* Require whitespace or end-of-line.
*/
if (*arg == '\0') {
info("usage: passkey <1-%d digits>", PASSKEY_MAX_LEN);
} else if (g_ascii_isspace(*arg)) {
set_passkey_from_command(arg);
} else {
info("unknown command, supported: passkey <1-%d digits>",
PASSKEY_MAX_LEN);
}
} else if (line[0] != '\0') {
info("unknown command, supported: passkey <1-%d digits>", PASSKEY_MAX_LEN);
}
g_free(line);
return G_SOURCE_CONTINUE;
}
/* ------------------------------------------------------------------------- */
/* Signal handling */
/* ------------------------------------------------------------------------- */
static gboolean on_signal_exit(gpointer user_data) {
GMainLoop *loop = user_data;
info("shutting down");
/*
* Unregister from BlueZ while the D-Bus connection is still alive.
*/
unregister_agent();
if (loop)
g_main_loop_quit(loop);
return G_SOURCE_REMOVE;
}
/* ------------------------------------------------------------------------- */
/* Cleanup */
/* ------------------------------------------------------------------------- */
static void cleanup(void) {
/*
* Remove stdin watch.
*/
if (stdin_watch_id != 0) {
g_source_remove(stdin_watch_id);
stdin_watch_id = 0;
}
/*
* Remove BlueZ NameOwnerChanged subscription.
*/
if (dbus_conn && name_owner_sub_id != 0) {
g_dbus_connection_signal_unsubscribe(dbus_conn, name_owner_sub_id);
name_owner_sub_id = 0;
}
/*
* Unregister Agent from BlueZ.
*/
unregister_agent();
/*
* Unexport Agent1 object.
*/
if (dbus_conn && agent_reg_id != 0) {
g_dbus_connection_unregister_object(dbus_conn, agent_reg_id);
agent_reg_id = 0;
}
/*
* Close our reference to system bus connection.
*/
if (dbus_conn) {
g_object_unref(dbus_conn);
dbus_conn = NULL;
}
}
/* ------------------------------------------------------------------------- */
/* Options */
/* ------------------------------------------------------------------------- */
static const GOptionEntry options[] = {
{"capability", 'c', 0, G_OPTION_ARG_STRING, &opt_capability,
"Agent capability "
"(DisplayOnly, DisplayYesNo, KeyboardOnly, "
"NoInputNoOutput, KeyboardDisplay)",
"CAPABILITY"},
{"passkey", 'p', 0, G_OPTION_ARG_STRING, &opt_passkey,
"Fixed passkey for pairing (default 0000)", "PASSKEY"},
{"debug", 'd', 0, G_OPTION_ARG_NONE, &opt_debug, "Enable debug output",
NULL},
{NULL}};
/* ------------------------------------------------------------------------- */
/* main */
/* ------------------------------------------------------------------------- */
int main(int argc, char *argv[]) {
GOptionContext *context;
GError *error = NULL;
main_loop = g_main_loop_new(NULL, FALSE);
/*
* Initialize default passkey.
*/
g_strlcpy(passkey, DEFAULT_PASSKEY, sizeof(passkey));
/*
* Parse command line.
*/
context = g_option_context_new("- standalone BlueZ auto-confirm agent");
g_option_context_add_main_entries(context, options, NULL);
if (!g_option_context_parse(context, &argc, &argv, &error)) {
err("option parsing failed: %s", error ? error->message : "unknown error");
if (error)
g_error_free(error);
g_option_context_free(context);
g_main_loop_unref(main_loop);
return 1;
}
if (!opt_capability) {
opt_capability = g_strdup(DEFAULT_CAPABILITY);
}
/*
* Validate capability.
*/
{
static const gchar *valid_caps[] = {"DisplayOnly", "DisplayYesNo",
"KeyboardOnly", "NoInputNoOutput",
"KeyboardDisplay", NULL};
gboolean ok = FALSE;
for (int i = 0; valid_caps[i]; i++) {
if (g_strcmp0(opt_capability, valid_caps[i]) == 0) {
ok = TRUE;
break;
}
}
if (!ok) {
err("invalid capability '%s'", opt_capability);
g_option_context_free(context);
g_main_loop_unref(main_loop);
return 1;
}
}
/*
* Validate command-line passkey.
*/
if (opt_passkey) {
gchar *tmp = g_strdup(opt_passkey);
g_strstrip(tmp);
if (!set_passkey_internal(tmp, FALSE)) {
err("invalid passkey '%s', using default %s", tmp, DEFAULT_PASSKEY);
}
g_free(tmp);
}
info("starting");
info("capability=%s", opt_capability);
info("passkey=%s", passkey);
/*
* stdin command interface.
*
* Example:
*
* echo "passkey 123456" > /proc/<pid>/fd/0
*
* or type directly from a serial console.
*/
{
GIOChannel *stdin_channel;
stdin_channel = g_io_channel_unix_new(STDIN_FILENO);
g_io_channel_set_close_on_unref(stdin_channel, FALSE);
stdin_watch_id =
g_io_add_watch(stdin_channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
on_stdin_input, NULL);
g_io_channel_unref(stdin_channel);
}
/*
* Handle SIGINT/SIGTERM.
*/
g_unix_signal_add(SIGINT, on_signal_exit, main_loop);
g_unix_signal_add(SIGTERM, on_signal_exit, main_loop);
/*
* Connect system bus and setup Agent1.
*/
if (!setup_dbus()) {
cleanup();
g_option_context_free(context);
g_main_loop_unref(main_loop);
return 1;
}
/*
* Keep D-Bus connection and Agent object alive.
*/
info("entering main loop");
g_main_loop_run(main_loop);
info("main loop exited");
/*
* Cleanup.
*/
cleanup();
g_free(opt_passkey);
opt_passkey = NULL;
g_free(opt_capability);
opt_capability = NULL;
g_option_context_free(context);
g_main_loop_unref(main_loop);
main_loop = NULL;
return 0;
}