/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2012-2014  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 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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; 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 <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>

#include <glib.h>
#include <gdbus.h>

#include "dbus_helpers.h"
#include "input.h"
#include "services.h"
#include "tethering.h"
#include "peers.h"
#include "commands.h"
#include "agent.h"
#include "vpnconnections.h"

static DBusConnection *connection;
static GHashTable *service_hash;
static GHashTable *vpnconnection_hash;
static GHashTable *peer_hash;
static GHashTable *technology_hash;
static char *session_notify_path;
static char *session_path;
static bool session_connected;

struct connman_option {
	const char *name;
	const char val;
	const char *desc;
};

static char *ipv4[] = {
	"Method",
	"Address",
	"Netmask",
	"Gateway",
	NULL
};

static char *ipv6[] = {
	"Method",
	"Address",
	"PrefixLength",
	"Gateway",
	NULL
};

static int cmd_help(char *args[], int num, struct connman_option *options);

static bool check_dbus_name(const char *name)
{
	/*
	 * Valid dbus chars should be [A-Z][a-z][0-9]_
	 * and should not start with number.
	 */
	unsigned int i;

	if (!name || name[0] == '\0')
		return false;

	for (i = 0; name[i] != '\0'; i++)
		if (!((name[i] >= 'A' && name[i] <= 'Z') ||
				(name[i] >= 'a' && name[i] <= 'z') ||
				(name[i] >= '0' && name[i] <= '9') ||
				name[i] == '_'))
			return false;

	return true;
}

static int parse_boolean(char *arg)
{
	if (!arg)
		return -1;

	if (strcasecmp(arg, "no") == 0 ||
			strcasecmp(arg, "false") == 0 ||
			strcasecmp(arg, "off" ) == 0 ||
			strcasecmp(arg, "disable" ) == 0 ||
			strcasecmp(arg, "n") == 0 ||
			strcasecmp(arg, "f") == 0 ||
			strcasecmp(arg, "0") == 0)
		return 0;

	if (strcasecmp(arg, "yes") == 0 ||
			strcasecmp(arg, "true") == 0 ||
			strcasecmp(arg, "on") == 0 ||
			strcasecmp(arg, "enable" ) == 0 ||
			strcasecmp(arg, "y") == 0 ||
			strcasecmp(arg, "t") == 0 ||
			strcasecmp(arg, "1") == 0)
		return 1;

	return -1;
}

static int parse_args(char *arg, struct connman_option *options)
{
	int i;

	if (!arg)
		return -1;

	for (i = 0; options[i].name; i++) {
		if (strcmp(options[i].name, arg) == 0 ||
				(strncmp(arg, "--", 2) == 0 &&
					strcmp(&arg[2], options[i].name) == 0))
			return options[i].val;
	}

	return '?';
}

static int enable_return(DBusMessageIter *iter, int errnum, const char *error,
		void *user_data)
{
	char *tech = user_data;
	char *str;

	str = strrchr(tech, '/');
	if (str)
		str++;
	else
		str = tech;

	switch (errnum) {
	case 0:
		fprintf(stdout, "Enabled %s\n", str);
		break;
	case -ENODEV:
		fprintf(stderr, "%s is not available\n", str);
		break;
	case -EALREADY:
		fprintf(stderr, "%s is already enabled\n", str);
		break;
	default:
		fprintf(stderr, "Error %s: %s\n", str, error);
	}

	g_free(user_data);

	return 0;
}

static int cmd_enable(char *args[], int num, struct connman_option *options)
{
	char *tech;
	dbus_bool_t b = TRUE;

	if (num > 2)
		return -E2BIG;

	if (num < 2)
		return -EINVAL;

	if (check_dbus_name(args[1]) == false)
		return -EINVAL;

	if (strcmp(args[1], "offline") == 0) {
		tech = g_strdup(args[1]);
		return __connmanctl_dbus_set_property(connection, "/",
				"net.connman.Manager", enable_return, tech,
				"OfflineMode", DBUS_TYPE_BOOLEAN, &b);
	}

	tech = g_strdup_printf("/net/connman/technology/%s", args[1]);
	return __connmanctl_dbus_set_property(connection, tech,
				"net.connman.Technology", enable_return, tech,
				"Powered", DBUS_TYPE_BOOLEAN, &b);
}

static int disable_return(DBusMessageIter *iter, int errnum, const char *error,
		void *user_data)
{
	char *tech = user_data;
	char *str;

	str = strrchr(tech, '/');
	if (str)
		str++;
	else
		str = tech;

	switch (errnum) {
	case 0:
		fprintf(stdout, "Disable %s\n", str);
		break;
	case -ENODEV:
		fprintf(stderr, "%s is not available\n", str);
		break;
	case -EALREADY:
		fprintf(stderr, "%s is already disabled\n", str);
		break;
	default:
		fprintf(stderr, "Error %s: %s\n", str, error);
	}

	g_free(user_data);

	return 0;
}

static int cmd_disable(char *args[], int num, struct connman_option *options)
{
	char *tech;
	dbus_bool_t b = FALSE;

	if (num > 2)
		return -E2BIG;

	if (num < 2)
		return -EINVAL;

	if (check_dbus_name(args[1]) == false)
		return -EINVAL;

	if (strcmp(args[1], "offline") == 0) {
		tech = g_strdup(args[1]);
		return __connmanctl_dbus_set_property(connection, "/",
				"net.connman.Manager", disable_return, tech,
				"OfflineMode", DBUS_TYPE_BOOLEAN, &b);
	}

	tech = g_strdup_printf("/net/connman/technology/%s", args[1]);
	return __connmanctl_dbus_set_property(connection, tech,
				"net.connman.Technology", disable_return, tech,
				"Powered", DBUS_TYPE_BOOLEAN, &b);
}

static int state_print(DBusMessageIter *iter, int errnum, const char *error,
		void *user_data)
{
	DBusMessageIter entry;

	if (error) {
		fprintf(stderr, "Error: %s\n", error);
		return 0;
	}

	dbus_message_iter_recurse(iter, &entry);
	__connmanctl_dbus_print(&entry, "  ", " = ", "\n");
	fprintf(stdout, "\n");

	return 0;
}

static int cmd_state(char *args[], int num, struct connman_option *options)
{
	if (num > 1)
		return -E2BIG;

	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE,
			CONNMAN_PATH, "net.connman.Manager", "GetProperties",
			state_print, NULL, NULL, NULL);
}

static int clock_print(DBusMessageIter *iter, int errnum, const char *error,
		void *user_data)
{
	DBusMessageIter entry;

	if (error) {
		fprintf(stderr, "Error: %s\n", error);
		return 0;
	}

	dbus_message_iter_recurse(iter, &entry);
	__connmanctl_dbus_print(&entry, "  ", " = ", "\n");
	fprintf(stdout, "\n");

	return 0;
}

static int cmd_clock(char *args[], int num, struct connman_option *options)
{
	if (num > 1)
		return -E2BIG;

	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE,
			CONNMAN_PATH, "net.connman.Clock", "GetProperties",
			clock_print, NULL, NULL, NULL);
}

static int services_list(DBusMessageIter *iter, int errnum, const char *error,
		void *user_data)
{
	if (!error) {
		__connmanctl_services_list(iter);
		fprintf(stdout, "\n");
	} else {
		fprintf(stderr, "Error: %s\n", error);
	}

	return 0;
}

static int peers_list(DBusMessageIter *iter, int errnum,
					const char *error, void *user_data)
{
	if (!error) {
		__connmanctl_peers_list(iter);
		fprintf(stdout, "\n");
	} else
		fprintf(stderr, "Error: %s\n", error);

	return 0;
}

static int tethering_clients_list(DBusMessageIter *iter, int errnum,
					const char *error, void *user_data)
{
	if (!error) {
		__connmanctl_tethering_clients_list(iter);
		fprintf(stdout, "\n");
	} else
		fprintf(stderr, "Error: %s\n", error);

	return 0;
}

static int object_properties(DBusMessageIter *iter, int errnum,
					const char *error, void *user_data)
{
	char *path = user_data;
	char *str;
	DBusMessageIter dict;

	if (!error) {
		fprintf(stdout, "%s\n", path);

		dbus_message_iter_recurse(iter, &dict);
		__connmanctl_dbus_print(&dict, "  ", " = ", "\n");

		fprintf(stdout, "\n");

	} else {
		str = strrchr(path, '/');
		if (str)
			str++;
		else
			str = path;

		fprintf(stderr, "Error %s: %s\n", str, error);
	}

	g_free(user_data);

	return 0;
}

static int cmd_services(char *args[], int num, struct connman_option *options)
{
	char *service_name = NULL;
	char *path;
	int c;

	if (num > 3)
		return -E2BIG;

	c = parse_args(args[1], options);
	switch (c) {
	case -1:
		break;
	case 'p':
		if (num < 3)
			return -EINVAL;
		service_name = args[2];
		break;
	default:
		if (num > 2)
			return -E2BIG;
		service_name = args[1];
		break;
	}

	if (!service_name) {
		return __connmanctl_dbus_method_call(connection,
				CONNMAN_SERVICE, CONNMAN_PATH,
				"net.connman.Manager", "GetServices",
				services_list, NULL, NULL, NULL);
	}

	if (check_dbus_name(service_name) == false)
		return -EINVAL;

	path = g_strdup_printf("/net/connman/service/%s", service_name);
	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE, path,
			"net.connman.Service", "GetProperties",
			object_properties, path, NULL, NULL);
}

static int cmd_peers(char *args[], int num, struct connman_option *options)
{
	char *peer_name = NULL;
	char *path;

	if (num > 2)
		return -E2BIG;

	if (num == 2)
		peer_name = args[1];

	if (!peer_name) {
		return __connmanctl_dbus_method_call(connection,
					CONNMAN_SERVICE, CONNMAN_PATH,
					"net.connman.Manager", "GetPeers",
					peers_list, NULL, NULL, NULL);
	}

	if (check_dbus_name(peer_name) == false)
		return -EINVAL;

	path = g_strdup_printf("/net/connman/peer/%s", peer_name);
	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE,
				path, "net.connman.Peer", "GetProperties",
				object_properties, path, NULL, NULL);
}

static int technology_print(DBusMessageIter *iter, int errnum, const char *error,
		void *user_data)
{
	DBusMessageIter array;

	if (error) {
		fprintf(stderr, "Error: %s\n", error);
		return 0;
	}

	dbus_message_iter_recurse(iter, &array);
	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
		DBusMessageIter entry, dict;
		const char *path;

		dbus_message_iter_recurse(&array, &entry);
		dbus_message_iter_get_basic(&entry, &path);
		fprintf(stdout, "%s\n", path);

		dbus_message_iter_next(&entry);

		dbus_message_iter_recurse(&entry, &dict);
		__connmanctl_dbus_print(&dict, "  ", " = ", "\n");
		fprintf(stdout, "\n");

		dbus_message_iter_next(&array);
	}

	return 0;
}

static int cmd_technologies(char *args[], int num,
		struct connman_option *options)
{
	if (num > 1)
		return -E2BIG;

	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE,
			CONNMAN_PATH, "net.connman.Manager", "GetTechnologies",
			technology_print, NULL,	NULL, NULL);
}

struct tether_enable {
	char *path;
	dbus_bool_t enable;
};

static int tether_set_return(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	struct tether_enable *tether = user_data;
	char *str;

	str = strrchr(tether->path, '/');
	if (str)
		str++;
	else
		str = tether->path;

	if (!error) {
		fprintf(stdout, "%s tethering for %s\n",
				tether->enable ? "Enabled" : "Disabled",
				str);
	} else
		fprintf(stderr, "Error %s %s tethering: %s\n",
				tether->enable ?
				"enabling" : "disabling", str, error);

	g_free(tether->path);
	g_free(user_data);

	return 0;
}

static int tether_set(char *technology, int set_tethering)
{
	struct tether_enable *tether = g_new(struct tether_enable, 1);

	switch(set_tethering) {
	case 1:
		tether->enable = TRUE;
		break;
	case 0:
		tether->enable = FALSE;
		break;
	default:
		g_free(tether);
		return 0;
	}

	tether->path = g_strdup_printf("/net/connman/technology/%s",
			technology);

	return __connmanctl_dbus_set_property(connection, tether->path,
			"net.connman.Technology", tether_set_return,
			tether, "Tethering", DBUS_TYPE_BOOLEAN,
			&tether->enable);
}

struct tether_properties {
	int ssid_result;
	int passphrase_result;
	int set_tethering;
	int freq_result;
};

static int tether_update(struct tether_properties *tether)
{
	int ret;

	if (tether->ssid_result == 0 && tether->passphrase_result == 0 &&
			tether->freq_result == 0) {
		ret = tether_set("wifi", tether->set_tethering);
		g_free(tether);
		return ret;
	}

	if (tether->ssid_result != -EINPROGRESS &&
			tether->passphrase_result != -EINPROGRESS &&
			tether->freq_result != -EINPROGRESS) {
		g_free(tether);
		return 0;
	}

	return -EINPROGRESS;
}

static int tether_set_ssid_return(DBusMessageIter *iter, int errnum,
				const char *error, void *user_data)
{
	struct tether_properties *tether = user_data;

	if (!error) {
		fprintf(stdout, "Wifi SSID set\n");
		tether->ssid_result = 0;
	} else {
		fprintf(stderr, "Error setting wifi SSID: %s\n", error);
		tether->ssid_result = -EINVAL;
	}

	return tether_update(tether);
}

static int tether_set_passphrase_return(DBusMessageIter *iter, int errnum,
					const char *error, void *user_data)
{
	struct tether_properties *tether = user_data;

	if (!error) {
		fprintf(stdout, "Wifi passphrase set\n");
		tether->passphrase_result = 0;
	} else {
		fprintf(stderr, "Error setting wifi passphrase: %s\n", error);
		tether->passphrase_result = -EINVAL;
	}

	return tether_update(tether);
}

static int tether_set_freq_return(DBusMessageIter *iter, int errnum,
				  const char *error, void *user_data)
{
	struct tether_properties *tether = user_data;

	if (!error) {
		fprintf(stdout, "Wifi access point frequency set\n");
		tether->freq_result = 0;
	} else {
		fprintf(stderr, "Error setting wifi frequency: %s\n", error);
		tether->freq_result = -EINVAL;
	}

	return tether_update(tether);
}

static int tether_set_ssid(char *ssid, char *passphrase, int set_tethering, int freq)
{
	struct tether_properties *tether = g_new0(struct tether_properties, 1);

	tether->set_tethering = set_tethering;

	tether->ssid_result = __connmanctl_dbus_set_property(connection,
			"/net/connman/technology/wifi",
			"net.connman.Technology",
			tether_set_ssid_return, tether,
			"TetheringIdentifier", DBUS_TYPE_STRING, &ssid);

	tether->passphrase_result =__connmanctl_dbus_set_property(connection,
			"/net/connman/technology/wifi",
			"net.connman.Technology",
			tether_set_passphrase_return, tether,
			"TetheringPassphrase", DBUS_TYPE_STRING, &passphrase);

	if (freq > 0) {
		tether->freq_result =__connmanctl_dbus_set_property(connection,
				"/net/connman/technology/wifi",
				"net.connman.Technology",
				tether_set_freq_return, tether,
				"TetheringFreq", DBUS_TYPE_INT32, &freq);
	}

	if (tether->ssid_result != -EINPROGRESS &&
			tether->passphrase_result != -EINPROGRESS &&
			tether->freq_result != -EINPROGRESS) {
		g_free(tether);
		return -ENXIO;
	}

	return -EINPROGRESS;
}

static int cmd_tether(char *args[], int num, struct connman_option *options)
{
	char *ssid, *passphrase;
	int set_tethering;

	if (num < 3)
		return -EINVAL;

	set_tethering = parse_boolean(args[2]);

	if (strcmp(args[1], "wifi") == 0) {
		int freq = 0;

		if (num > 6)
			return -E2BIG;

		if (num >= 5 && set_tethering == -1)
			return -EINVAL;

		if (num == 4)
			set_tethering = -1;

		if (num == 6) {
			freq = atoi(args[num - 1]);
			num --;
		}

		passphrase = args[num - 1];
		ssid = args[num - 2];

		if (num > 3)
			return tether_set_ssid(ssid, passphrase, set_tethering, freq);
	}

	if (num > 3)
		return -E2BIG;

	if (set_tethering == -1)
		return -EINVAL;

	if (check_dbus_name(args[1]) == false)
		return -EINVAL;

	return tether_set(args[1], set_tethering);
}

static int cmd_tethering_clients(char *args[], int num, struct connman_option *options)
{
	if (num > 1)
		return -E2BIG;

	return __connmanctl_dbus_method_call(connection,
				CONNMAN_SERVICE, CONNMAN_PATH,
				"net.connman.Manager", "GetTetheringClients",
				tethering_clients_list, NULL, NULL, NULL);
}

static int scan_return(DBusMessageIter *iter, int ernnum, const char *error,
		void *user_data)
{
	char *path = user_data;

	if (!error) {
		char *str = strrchr(path, '/');
		str++;
		fprintf(stdout, "Scan completed for %s\n", str);
	} else
		fprintf(stderr, "Error %s: %s\n", path, error);

	g_free(user_data);

	return 0;
}

static int cmd_scan(char *args[], int num, struct connman_option *options)
{
	char *path;

	if (num > 2)
		return -E2BIG;

	if (num < 2)
		return -EINVAL;

	if (check_dbus_name(args[1]) == false)
		return -EINVAL;

	path = g_strdup_printf("/net/connman/technology/%s", args[1]);
	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE, path,
			"net.connman.Technology", "Scan",
			scan_return, path, NULL, NULL);
}

static int connect_return(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	char *path = user_data;

	if (!error) {
		char *str = strrchr(path, '/');
		str++;
		fprintf(stdout, "Connected %s\n", str);
	} else
		fprintf(stderr, "Error %s: %s\n", path, error);

	g_free(user_data);

	return 0;
}

static int cmd_connect(char *args[], int num, struct connman_option *options)
{
	const char *iface = "net.connman.Service";
	char *path;

	if (num > 2)
		return -E2BIG;

	if (num < 2)
		return -EINVAL;

	if (check_dbus_name(args[1]) == false)
		return -EINVAL;

	if (g_strstr_len(args[1], 5, "peer_") == args[1]) {
		iface = "net.connman.Peer";
		path = g_strdup_printf("/net/connman/peer/%s", args[1]);
	} else
		path = g_strdup_printf("/net/connman/service/%s", args[1]);

	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE, path,
			iface, "Connect", connect_return, path, NULL, NULL);
}

static int disconnect_return(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	char *path = user_data;

	if (!error) {
		char *str = strrchr(path, '/');
		str++;
		fprintf(stdout, "Disconnected %s\n", str);
	} else
		fprintf(stderr, "Error %s: %s\n", path, error);

	g_free(user_data);

	return 0;
}

static int cmd_disconnect(char *args[], int num, struct connman_option *options)
{
	const char *iface = "net.connman.Service";
	char *path;

	if (num > 2)
		return -E2BIG;

	if (num < 2)
		return -EINVAL;

	if (check_dbus_name(args[1]) == false)
		return -EINVAL;

	if (g_strstr_len(args[1], 5, "peer_") == args[1]) {
		iface = "net.connman.Peer";
		path = g_strdup_printf("/net/connman/peer/%s", args[1]);
	} else
		path = g_strdup_printf("/net/connman/service/%s", args[1]);

	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE,
					path, iface, "Disconnect",
					disconnect_return, path, NULL, NULL);
}

struct move_service {
	char *service;
	char *target;
};

static int move_before_return(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	struct move_service *services = user_data;
	char *service;
	char *target;

	if (!error) {
		service = strrchr(services->service, '/');
		service++;
		target = strrchr(services->target, '/');
		target++;
		fprintf(stdout, "Moved %s before %s\n", service, target);
	} else
		fprintf(stderr, "Error %s: %s\n", services->service, error);

	g_free(services->service);
	g_free(services->target);
	g_free(user_data);

	return 0;
}

static void move_before_append_args(DBusMessageIter *iter, void *user_data)
{
	char *path = user_data;

	dbus_message_iter_append_basic(iter,
				DBUS_TYPE_OBJECT_PATH, &path);
}

static int cmd_service_move_before(char *args[], int num,
		struct connman_option *options)
{
	const char *iface = "net.connman.Service";
	struct move_service *services;

	if (num > 3)
		return -E2BIG;

	if (num < 3)
		return -EINVAL;

	if (check_dbus_name(args[1]) == false)
		return -EINVAL;

	services = g_new(struct move_service, 1);

	services->service = g_strdup_printf("/net/connman/service/%s", args[1]);
	services->target = g_strdup_printf("/net/connman/service/%s", args[2]);

	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE,
					services->service, iface, "MoveBefore",
					move_before_return, services,
					move_before_append_args,
					services->target);
}

static int move_after_return(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	struct move_service *services = user_data;
	char *service;
	char *target;

	if (!error) {
		service = strrchr(services->service, '/');
		service++;
		target = strrchr(services->target, '/');
		target++;
		fprintf(stdout, "Moved %s after %s\n", service, target);
	} else
		fprintf(stderr, "Error %s: %s\n", services->service, error);

	g_free(services->service);
	g_free(services->target);
	g_free(user_data);

	return 0;
}

static void move_after_append_args(DBusMessageIter *iter, void *user_data)
{
	char *path = user_data;

	dbus_message_iter_append_basic(iter,
				DBUS_TYPE_OBJECT_PATH, &path);
}

static int cmd_service_move_after(char *args[], int num,
		struct connman_option *options)
{
	const char *iface = "net.connman.Service";
	struct move_service *services;

	if (num > 3)
		return -E2BIG;

	if (num < 3)
		return -EINVAL;

	if (check_dbus_name(args[1]) == false)
		return -EINVAL;

	services = g_new(struct move_service, 1);

	services->service = g_strdup_printf("/net/connman/service/%s", args[1]);
	services->target = g_strdup_printf("/net/connman/service/%s", args[2]);

	return __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE,
					services->service, iface, "MoveAfter",
					move_after_return, services,
					move_after_append_args,
					services->target);
}

static int config_return(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	char *service_name = user_data;

	if (error)
		fprintf(stderr, "Error %s: %s\n", service_name, error);

	g_free(user_data);

	return 0;
}

struct config_append {
	char **opts;
	int values;
};

struct session_options {
	char **args;
	int num;
	char *notify_path;
	struct connman_option *options;
};

static void config_append_ipv4(DBusMessageIter *iter,
		void *user_data)
{
	struct config_append *append = user_data;
	char **opts = append->opts;
	int i = 0;

	if (!opts)
		return;

	while (opts[i] && ipv4[i]) {
		__connmanctl_dbus_append_dict_entry(iter, ipv4[i],
				DBUS_TYPE_STRING, &opts[i]);
		i++;
	}

	append->values = i;
}

static void config_append_ipv6(DBusMessageIter *iter, void *user_data)
{
	struct config_append *append = user_data;
	char **opts = append->opts;

	if (!opts)
		return;

	append->values = 1;

	if (g_strcmp0(opts[0], "auto") == 0) {
		char *str;

		switch (parse_boolean(opts[1])) {
		case 0:
			append->values = 2;

			str = "disabled";
			__connmanctl_dbus_append_dict_entry(iter, "Privacy",
					DBUS_TYPE_STRING, &str);
			break;

		case 1:
			append->values = 2;

			str = "enabled";
			__connmanctl_dbus_append_dict_entry(iter, "Privacy",
					DBUS_TYPE_STRING, &str);
			break;

		default:
			if (opts[1]) {
				append->values = 2;

				if (g_strcmp0(opts[1], "prefered") != 0 &&
						g_strcmp0(opts[1],
							"preferred") != 0) {
					fprintf(stderr, "Error %s: %s\n",
							opts[1],
							strerror(EINVAL));
					return;
				}

				str = "prefered";
				__connmanctl_dbus_append_dict_entry(iter,
						"Privacy", DBUS_TYPE_STRING,
						&str);
			}
			break;
		}
	} else if (g_strcmp0(opts[0], "manual") == 0) {
		int i = 1;

		while (opts[i] && ipv6[i]) {
			if (i == 2) {
				int value = atoi(opts[i]);
				__connmanctl_dbus_append_dict_entry(iter,
						ipv6[i], DBUS_TYPE_BYTE,
						&value);
			} else {
				__connmanctl_dbus_append_dict_entry(iter,
						ipv6[i], DBUS_TYPE_STRING,
						&opts[i]);
			}
			i++;
		}

		append->values = i;

	} else if (g_strcmp0(opts[0], "off") != 0) {
		fprintf(stderr, "Error %s: %s\n", opts[0], strerror(EINVAL));

		return;
	}

	__connmanctl_dbus_append_dict_entry(iter, "Method", DBUS_TYPE_STRING,
				&opts[0]);
}

static void config_append_str(DBusMessageIter *iter, void *user_data)
{
	struct config_append *append = user_data;
	char **opts = append->opts;
	int i = 0;

	if (!opts)
		return;

	while (opts[i]) {
		dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
				&opts[i]);
		i++;
	}

	append->values = i;
}

static void append_servers(DBusMessageIter *iter, void *user_data)
{
	struct config_append *append = user_data;
	char **opts = append->opts;
	int i = 1;

	if (!opts)
		return;

	while (opts[i] && g_strcmp0(opts[i], "--excludes") != 0) {
		dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
				&opts[i]);
		i++;
	}

	append->values = i;
}

static void append_excludes(DBusMessageIter *iter, void *user_data)
{
	struct config_append *append = user_data;
	char **opts = append->opts;
	int i = append->values;

	if (!opts || !opts[i] ||
			g_strcmp0(opts[i], "--excludes") != 0)
		return;

	i++;
	while (opts[i]) {
		dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
				&opts[i]);
		i++;
	}

	append->values = i;
}

static void config_append_proxy(DBusMessageIter *iter, void *user_data)
{
	struct config_append *append = user_data;
	char **opts = append->opts;

	if (!opts)
		return;

	if (g_strcmp0(opts[0], "manual") == 0) {
		__connmanctl_dbus_append_dict_string_array(iter, "Servers",
				append_servers, append);

		__connmanctl_dbus_append_dict_string_array(iter, "Excludes",
				append_excludes, append);

	} else if (g_strcmp0(opts[0], "auto") == 0) {
		if (opts[1]) {
			__connmanctl_dbus_append_dict_entry(iter, "URL",
					DBUS_TYPE_STRING, &opts[1]);
			append->values++;
		}

	} else if (g_strcmp0(opts[0], "direct") != 0)
		return;

	__connmanctl_dbus_append_dict_entry(iter, "Method",DBUS_TYPE_STRING,
			&opts[0]);

	append->values++;
}

static int cmd_config(char *args[], int num, struct connman_option *options)
{
	int result = 0, res = 0, index = 2, oldindex = 0;
	int c;
	char *service_name, *path;
	char **opt_start;
	dbus_bool_t val;
	struct config_append append;

	service_name = args[1];
	if (!service_name)
		return -EINVAL;

	if (check_dbus_name(service_name) == false)
		return -EINVAL;

	while (index < num && args[index]) {
		c = parse_args(args[index], options);
		opt_start = &args[index + 1];
		append.opts = opt_start;
		append.values = 0;

		res = 0;

		oldindex = index;
		path = g_strdup_printf("/net/connman/service/%s", service_name);

		switch (c) {
		case 'a':
			switch (parse_boolean(*opt_start)) {
			case 1:
				val = TRUE;
				break;
			case 0:
				val = FALSE;
				break;
			default:
				res = -EINVAL;
				break;
			}

			index++;

			if (res == 0) {
				res = __connmanctl_dbus_set_property(connection,
						path, "net.connman.Service",
						config_return,
						g_strdup(service_name),
						"AutoConnect",
						DBUS_TYPE_BOOLEAN, &val);
			}
			break;
		case 'i':
			res = __connmanctl_dbus_set_property_dict(connection,
					path, "net.connman.Service",
					config_return, g_strdup(service_name),
					"IPv4.Configuration", DBUS_TYPE_STRING,
					config_append_ipv4, &append);
			index += append.values;
			break;

		case 'v':
			res = __connmanctl_dbus_set_property_dict(connection,
					path, "net.connman.Service",
					config_return, g_strdup(service_name),
					"IPv6.Configuration", DBUS_TYPE_STRING,
					config_append_ipv6, &append);
			index += append.values;
			break;

		case 'n':
			res = __connmanctl_dbus_set_property_array(connection,
					path, "net.connman.Service",
					config_return, g_strdup(service_name),
					"Nameservers.Configuration",
					DBUS_TYPE_STRING, config_append_str,
					&append);
			index += append.values;
			break;

		case 't':
			res = __connmanctl_dbus_set_property_array(connection,
					path, "net.connman.Service",
					config_return, g_strdup(service_name),
					"Timeservers.Configuration",
					DBUS_TYPE_STRING, config_append_str,
					&append);
			index += append.values;
			break;

		case 'd':
			res = __connmanctl_dbus_set_property_array(connection,
					path, "net.connman.Service",
					config_return, g_strdup(service_name),
					"Domains.Configuration",
					DBUS_TYPE_STRING, config_append_str,
					&append);
			index += append.values;
			break;

		case 'x':
			res = __connmanctl_dbus_set_property_dict(connection,
					path, "net.connman.Service",
					config_return, g_strdup(service_name),
					"Proxy.Configuration",
					DBUS_TYPE_STRING, config_append_proxy,
					&append);
			index += append.values;
			break;
		case 'r':
			res = __connmanctl_dbus_method_call(connection,
					CONNMAN_SERVICE, path,
					"net.connman.Service", "Remove",
					config_return, g_strdup(service_name),
					NULL, NULL);
			break;

		case 'm':
			switch (parse_boolean(*opt_start)) {
			case 1:
				val = TRUE;
				break;
			case 0:
				val = FALSE;
				break;
			default:
				res = -EINVAL;
				break;
			}
			if (res == 0) {
				res = __connmanctl_dbus_set_property(connection,
						path, "net.connman.Service",
						config_return,
						g_strdup(service_name),
						"mDNS.Configuration",
						DBUS_TYPE_BOOLEAN, &val);
			}
			index++;
			break;

		default:
			res = -EINVAL;
			break;
		}

		g_free(path);

		if (res < 0) {
			if (res == -EINPROGRESS)
				result = -EINPROGRESS;
			else
				printf("Error '%s': %s\n", args[oldindex],
						strerror(-res));
		} else
			index += res;

		index++;
	}

	return result;
}

static DBusHandlerResult monitor_changed(DBusConnection *connection,
		DBusMessage *message, void *user_data)
{
	DBusMessageIter iter;
	const char *interface, *path;

	interface = dbus_message_get_interface(message);
	if (!interface)
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

	if (strncmp(interface, "net.connman.", 12) != 0)
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

	if (!strcmp(interface, "net.connman.Agent") ||
			!strcmp(interface, "net.connman.vpn.Agent") ||
			!strcmp(interface, "net.connman.Session") ||
			!strcmp(interface, "net.connman.Notification"))
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

	interface = strrchr(interface, '.');
	if (interface && *interface != '\0')
		interface++;

	path = strrchr(dbus_message_get_path(message), '/');
	if (path && *path != '\0')
		path++;

	__connmanctl_save_rl();

	if (dbus_message_is_signal(message, "net.connman.Manager",
					"ServicesChanged")) {

		fprintf(stdout, "%-12s %-20s = {\n", interface,
				"ServicesChanged");
		dbus_message_iter_init(message, &iter);
		__connmanctl_services_list(&iter);
		fprintf(stdout, "\n}\n");

		__connmanctl_redraw_rl();

		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	} else if (dbus_message_is_signal(message, "net.connman.Manager",
							"PeersChanged")) {
		fprintf(stdout, "%-12s %-20s = {\n", interface,
							"PeersChanged");
		dbus_message_iter_init(message, &iter);
		__connmanctl_peers_list(&iter);
		fprintf(stdout, "\n}\n");

		__connmanctl_redraw_rl();

		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	} else if (dbus_message_is_signal(message, "net.connman.vpn.Manager",
					"ConnectionAdded") ||
			dbus_message_is_signal(message,
					"net.connman.vpn.Manager",
					"ConnectionRemoved")) {
		interface = "vpn.Manager";
		path = dbus_message_get_member(message);

	} else if (dbus_message_is_signal(message, "net.connman.Manager",
					"TechnologyAdded") ||
			dbus_message_is_signal(message, "net.connman.Manager",
					"TechnologyRemoved"))
		path = dbus_message_get_member(message);

	fprintf(stdout, "%-12s %-20s ", interface, path);
	dbus_message_iter_init(message, &iter);

	__connmanctl_dbus_print(&iter, "", " = ", " = ");
	fprintf(stdout, "\n");

	__connmanctl_redraw_rl();

	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static struct {
	char *interface;
	bool enabled;
} monitor[] = {
	{ "Service", false },
	{ "Technology", false },
	{ "Manager", false },
	{ "vpn.Manager", false },
	{ "vpn.Connection", false },
	{ NULL, },
};

static void monitor_add(char *interface)
{
	bool add_filter = true, found = false;
	int i;
	char *rule;
	DBusError err;

	for (i = 0; monitor[i].interface; i++) {
		if (monitor[i].enabled == true)
			add_filter = false;

		if (g_strcmp0(interface, monitor[i].interface) == 0) {
			if (monitor[i].enabled == true)
				return;

			monitor[i].enabled = true;
			found = true;
		}
	}

	if (found == false)
		return;

	if (add_filter == true)
		dbus_connection_add_filter(connection, monitor_changed,
				NULL, NULL);

	dbus_error_init(&err);
	rule  = g_strdup_printf("type='signal',interface='net.connman.%s'",
			interface);
	dbus_bus_add_match(connection, rule, &err);
	g_free(rule);

	if (dbus_error_is_set(&err))
		fprintf(stderr, "Error: %s\n", err.message);
}

static void monitor_del(char *interface)
{
	bool del_filter = true, found = false;
	int i;
	char *rule;

	for (i = 0; monitor[i].interface; i++) {
		if (g_strcmp0(interface, monitor[i].interface) == 0) {
			if (monitor[i].enabled == false)
				return;

			monitor[i].enabled = false;
			found = true;
		}

		if (monitor[i].enabled == true)
			del_filter = false;
	}

	if (found == false)
		return;

	rule  = g_strdup_printf("type='signal',interface='net.connman.%s'",
			interface);
	dbus_bus_remove_match(connection, rule, NULL);
	g_free(rule);

	if (del_filter == true)
		dbus_connection_remove_filter(connection, monitor_changed,
				NULL);
}

static int cmd_monitor(char *args[], int num, struct connman_option *options)
{
	bool add = true;
	int c;

	if (num > 3)
		return -E2BIG;

	if (num == 3) {
		switch (parse_boolean(args[2])) {
		case 0:
			add = false;
			break;

		default:
			break;
		}
	}

	c = parse_args(args[1], options);
	switch (c) {
	case -1:
		monitor_add("Service");
		monitor_add("Technology");
		monitor_add("Manager");
		monitor_add("vpn.Manager");
		monitor_add("vpn.Connection");
		break;

	case 's':
		if (add == true)
			monitor_add("Service");
		else
			monitor_del("Service");
		break;

	case 'c':
		if (add == true)
			monitor_add("Technology");
		else
			monitor_del("Technology");
		break;

	case 'm':
		if (add == true)
			monitor_add("Manager");
		else
			monitor_del("Manager");
		break;

	case 'M':
		if (add == true)
			monitor_add("vpn.Manager");
		else
			monitor_del("vpn.Manager");
		break;

	case 'C':
		if (add == true)
			monitor_add("vpn.Connection");
		else
			monitor_del("vpn.Connection");
		break;

	default:
		switch(parse_boolean(args[1])) {
		case 0:
			monitor_del("Service");
			monitor_del("Technology");
			monitor_del("Manager");
			monitor_del("vpn.Manager");
			monitor_del("vpn.Connection");
			break;

		case 1:
			monitor_add("Service");
			monitor_add("Technology");
			monitor_add("Manager");
			monitor_add("vpn.Manager");
			monitor_add("vpn.Connection");
			break;

		default:
			return -EINVAL;
		}
	}

	if (add == true)
		return -EINPROGRESS;

	return 0;
}

static int cmd_agent(char *args[], int num, struct connman_option *options)
{
	if (!__connmanctl_is_interactive()) {
		fprintf(stderr, "Error: Not supported in non-interactive "
				"mode\n");
		return 0;
	}

	if (num > 2)
		return -E2BIG;

	if (num < 2)
		return -EINVAL;

	switch(parse_boolean(args[1])) {
	case 0:
		__connmanctl_agent_unregister(connection);
		break;

	case 1:
		if (__connmanctl_agent_register(connection) == -EINPROGRESS)
			return -EINPROGRESS;

		break;

	default:
		return -EINVAL;
		break;
	}

	return 0;
}

static int vpnconnections_properties(DBusMessageIter *iter, int errnum,
				const char *error, void *user_data)
{
	char *path = user_data;
	char *str;
	DBusMessageIter dict;

	if (!error) {
		fprintf(stdout, "%s\n", path);

		dbus_message_iter_recurse(iter, &dict);
		__connmanctl_dbus_print(&dict, "  ", " = ", "\n");

		fprintf(stdout, "\n");

	} else {
		str = strrchr(path, '/');
		if (str)
			str++;
		else
			str = path;

		fprintf(stderr, "Error %s: %s\n", str, error);
	}

	g_free(user_data);

	return 0;
}

static int vpnconnections_list(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	if (!error)
		__connmanctl_vpnconnections_list(iter);
        else
		fprintf(stderr, "Error: %s\n", error);

	return 0;
}

static int cmd_vpnconnections(char *args[], int num,
		struct connman_option *options)
{
	char *vpnconnection_name, *path;

	if (num > 2)
		return -E2BIG;

	vpnconnection_name = args[1];

	if (!vpnconnection_name)
		return __connmanctl_dbus_method_call(connection,
				VPN_SERVICE, VPN_PATH,
				"net.connman.vpn.Manager", "GetConnections",
				vpnconnections_list, NULL,
				NULL, NULL);

	if (check_dbus_name(vpnconnection_name) == false)
		return -EINVAL;

	path = g_strdup_printf("/net/connman/vpn/connection/%s",
			vpnconnection_name);
	return __connmanctl_dbus_method_call(connection, VPN_SERVICE, path,
			"net.connman.vpn.Connection", "GetProperties",
			vpnconnections_properties, path, NULL, NULL);

}

static int cmd_vpnagent(char *args[], int num, struct connman_option *options)
{
	if (!__connmanctl_is_interactive()) {
		fprintf(stderr, "Error: Not supported in non-interactive "
				"mode\n");
		return 0;
	}

	if (num > 2)
		return -E2BIG;

	if (num < 2)
		return -EINVAL;

	switch(parse_boolean(args[1])) {
	case 0:
		__connmanctl_vpn_agent_unregister(connection);
		break;

	case 1:
		if (__connmanctl_vpn_agent_register(connection) ==
				-EINPROGRESS)
			return -EINPROGRESS;

		break;

	default:
		return -EINVAL;
		break;
	}

	return 0;
}

static DBusMessage *session_release(DBusConnection *connection,
		DBusMessage *message, void *user_data)
{
	__connmanctl_save_rl();

	fprintf(stdout, "Session %s released\n", session_path);

	__connmanctl_redraw_rl();

	g_free(session_path);
	session_path = NULL;
	session_connected = false;

	return g_dbus_create_reply(message, DBUS_TYPE_INVALID);
}

static DBusMessage *session_update(DBusConnection *connection,
		DBusMessage *message, void *user_data)
{
	DBusMessageIter iter, dict;

	__connmanctl_save_rl();

	fprintf(stdout, "Session      Update               = {\n");

	dbus_message_iter_init(message, &iter);
	dbus_message_iter_recurse(&iter, &dict);

	__connmanctl_dbus_print(&dict, "", " = ", "\n");
	fprintf(stdout, "\n}\n");

	dbus_message_iter_recurse(&iter, &dict);

	while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, variant;
		char *field, *state;

		dbus_message_iter_recurse(&dict, &entry);

		dbus_message_iter_get_basic(&entry, &field);

		if (dbus_message_iter_get_arg_type(&entry)
				== DBUS_TYPE_STRING
				&& !strcmp(field, "State")) {

			dbus_message_iter_next(&entry);
			dbus_message_iter_recurse(&entry, &variant);
			if (dbus_message_iter_get_arg_type(&variant)
					!= DBUS_TYPE_STRING)
				break;

			dbus_message_iter_get_basic(&variant, &state);

			if (!session_connected && (!strcmp(state, "connected")
					|| !strcmp(state, "online"))) {

				fprintf(stdout, "Session %s connected\n",
					session_path);
				session_connected = true;

				break;
			}

			if (!strcmp(state, "disconnected") &&
					session_connected) {

				fprintf(stdout, "Session %s disconnected\n",
					session_path);
				session_connected = false;
			}
			break;
		}

		dbus_message_iter_next(&dict);
	}

	__connmanctl_redraw_rl();

	return g_dbus_create_reply(message, DBUS_TYPE_INVALID);
}

static const GDBusMethodTable notification_methods[] = {
	{ GDBUS_METHOD("Release", NULL, NULL, session_release) },
	{ GDBUS_METHOD("Update", GDBUS_ARGS({"settings", "a{sv}"}),
				NULL, session_update) },
	{ },
};

static int session_notify_add(const char *path)
{
	if (session_notify_path)
		return 0;

	if (!g_dbus_register_interface(connection, path,
					"net.connman.Notification",
					notification_methods, NULL, NULL,
					NULL, NULL)) {
		fprintf(stderr, "Error: Failed to register VPN Agent "
				"callbacks\n");
		return -EIO;
	}

	session_notify_path = g_strdup(path);

	return 0;
}

static void session_notify_remove(void)
{
	if (!session_notify_path)
		return;

	g_dbus_unregister_interface(connection, session_notify_path,
			"net.connman.Notification");

	g_free(session_notify_path);
	session_notify_path = NULL;
}

static int session_connect_cb(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	if (error) {
		fprintf(stderr, "Error: %s\n", error);
		return 0;
	}

	return -EINPROGRESS;
}

static int session_connect(void)
{
	return __connmanctl_dbus_method_call(connection, "net.connman",
			session_path, "net.connman.Session", "Connect",
			session_connect_cb, NULL, NULL, NULL);
}

static int session_disconnect_cb(DBusMessageIter *iter, int errnum,
				const char *error, void *user_data)
{
	if (error)
		fprintf(stderr, "Error: %s\n", error);

	return 0;
}

static int session_disconnect(void)
{
	return __connmanctl_dbus_method_call(connection, "net.connman",
			session_path, "net.connman.Session", "Disconnect",
			session_disconnect_cb, NULL, NULL, NULL);
}

static int session_create_cb(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	gboolean connect = GPOINTER_TO_INT(user_data);
	char *str;

	if (error) {
		fprintf(stderr, "Error creating session: %s", error);
		session_notify_remove();
		return 0;
	}

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_OBJECT_PATH) {
		fprintf(stderr, "Error creating session: No session path\n");
		return -EINVAL;
	}

	g_free(session_path);

	dbus_message_iter_get_basic(iter, &str);
	session_path = g_strdup(str);

	fprintf(stdout, "Session %s created\n", session_path);

	if (connect)
		return session_connect();

	return -EINPROGRESS;
}

static void session_config_append_array(DBusMessageIter *iter,
		void *user_data)
{
	struct config_append *append = user_data;
	char **opts = append->opts;
	int i = 1;

	if (!opts)
		return;

	while (opts[i] && strncmp(opts[i], "--", 2) != 0) {
		dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
					       &opts[i]);
		i++;
	}

	append->values = i;
}

static void session_create_append_dict(DBusMessageIter *iter, void *user_data)
{
	struct session_options *args_struct = user_data;
	int index = 0, res = 0;
	struct config_append append;
	char c;
	char *ifname;
	dbus_bool_t source_ip_rule;

	while (index < args_struct->num && args_struct->args[index]) {
		append.opts = &args_struct->args[index];
		append.values = 0;

		c = parse_args(args_struct->args[index], args_struct->options);

		switch (c) {
		case 'b':
			__connmanctl_dbus_append_dict_string_array(iter, "AllowedBearers",
								   session_config_append_array,
								   &append);
			break;
		case 't':
			if (! args_struct->args[index + 1]) {
				res = -EINVAL;
				break;
			}
			__connmanctl_dbus_append_dict_entry(iter, "ConnectionType",
							    DBUS_TYPE_STRING,
							    &args_struct->args[index + 1]);
			append.values = 2;
			break;
		case 'i':
			if (index + 1 <  args_struct->num)
				ifname =  args_struct->args[index + 1];
			else
				ifname = "";
			 __connmanctl_dbus_append_dict_entry(iter, "AllowedInterface",
							     DBUS_TYPE_STRING,
							     &ifname);
			append.values = 2;
			break;
		case 's':
			if (! args_struct->args[index + 1]) {
				res = -EINVAL;
				break;
			}
			switch (parse_boolean( args_struct->args[index + 1])) {
			case 1:
				source_ip_rule = TRUE;
				break;
			case 0:
				source_ip_rule = FALSE;
				break;
			default:
				res = -EINVAL;
				break;
			}
			__connmanctl_dbus_append_dict_entry(iter, "SourceIPRule",
							    DBUS_TYPE_BOOLEAN,
							    &source_ip_rule);
			append.values = 2;
			break;
		case 'c':
			if (!args_struct->args[index + 1]) {
				res = -EINVAL;
				break;
			}
			__connmanctl_dbus_append_dict_entry(iter, "ContextIdentifier",
							    DBUS_TYPE_STRING,
							    &args_struct->args[index + 1]);
			append.values = 2;
			break;
		default:
			res = -EINVAL;
		}

		if (res < 0 && res != -EINPROGRESS) {
			printf("Error '%s': %s\n",  args_struct->args[index],
					strerror(-res));
			return;
		}

		index += append.values;
	}
}

static void session_create_append(DBusMessageIter *iter, void *user_data)
{
	struct session_options *args_struct = user_data;

	__connmanctl_dbus_append_dict(iter, session_create_append_dict,
				      args_struct);

	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
				       &args_struct->notify_path);
}

static int session_create(gboolean connect, char *args[], int num,
			  struct connman_option *options)
{
	int res;
	char *notify_path;
	struct session_options args_struct;
	args_struct.args = args;
	args_struct.num = num;
	args_struct.options = options;

	notify_path = g_strdup_printf("/net/connman/connmanctl%d", getpid());
	session_notify_add(notify_path);
	args_struct.notify_path = notify_path;

	res = __connmanctl_dbus_method_call(connection, "net.connman", "/",
			"net.connman.Manager", "CreateSession",
			session_create_cb, GINT_TO_POINTER(connect),
			session_create_append, &args_struct);

	g_free(notify_path);

	if (res < 0 && res != -EINPROGRESS)
		session_notify_remove();

	return res;
}

static int session_destroy_cb(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	if (error) {
		fprintf(stderr, "Error destroying session: %s", error);
		return 0;
	}

	fprintf(stdout, "Session %s ended\n", session_path);

	g_free(session_path);
	session_path = NULL;
	session_connected = false;

	return 0;
}

static void session_destroy_append(DBusMessageIter *iter, void *user_data)
{
	const char *path = user_data;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
}

static int session_destroy(void)
{
	return __connmanctl_dbus_method_call(connection, "net.connman", "/",
			"net.connman.Manager", "DestroySession",
			session_destroy_cb, NULL,
			session_destroy_append, session_path);
}

static int session_config_return(DBusMessageIter *iter, int errnum,
				const char *error, void *user_data)
{
	char *property_name = user_data;

	if (error)
		fprintf(stderr, "Error setting session %s: %s\n",
				property_name, error);

	return 0;
}

static int session_config(char *args[], int num,
		struct connman_option *options)
{
	int index = 0, res = 0;
	struct config_append append;
	char c;
	char *ifname;
	dbus_bool_t source_ip_rule;

	while (index < num && args[index]) {
		append.opts = &args[index];
		append.values = 0;

		c = parse_args(args[index], options);

		switch (c) {
		case 'b':
			res = __connmanctl_dbus_session_change_array(connection,
					session_path, session_config_return,
					"AllowedBearers", "AllowedBearers",
					session_config_append_array, &append);
			break;
		case 't':
			if (!args[index + 1]) {
				res = -EINVAL;
				break;
			}

			res = __connmanctl_dbus_session_change(connection,
					session_path, session_config_return,
					"ConnectionType", "ConnectionType",
					DBUS_TYPE_STRING, &args[index + 1]);
			append.values = 2;
			break;
		case 'i':
			if (index + 1 < num)
				ifname = args[index + 1];
			else
				ifname = "";

			res = __connmanctl_dbus_session_change(connection,
					session_path, session_config_return,
					"AllowedInterface", "AllowedInterface",
					DBUS_TYPE_STRING, &ifname);
			append.values = 2;
			break;
		case 's':
			if (!args[index + 1]) {
				res = -EINVAL;
				break;
			}
			switch (parse_boolean(args[index + 1])) {
			case 1:
				source_ip_rule = TRUE;
				break;
			case 0:
				source_ip_rule = FALSE;
				break;
			default:
				res = -EINVAL;
				break;
			}

			res = __connmanctl_dbus_session_change(connection,
					session_path, session_config_return,
					"SourceIPRule", "SourceIPRule",
					DBUS_TYPE_BOOLEAN, &source_ip_rule);
			append.values = 2;
			break;
		case 'c':
				if (!args[index + 1]) {
					res = -EINVAL;
					break;
				}

				res = __connmanctl_dbus_session_change(connection,
						session_path, session_config_return,
						"ctxid", "ctxid", DBUS_TYPE_STRING,
						&args[index + 1]);
				append.values = 2;
				break;

		default:
			res = -EINVAL;
		}

		if (res < 0 && res != -EINPROGRESS) {
			printf("Error '%s': %s\n", args[index],
					strerror(-res));
			return 0;
		}

		index += append.values;
	}

	return 0;
}

static int cmd_session(char *args[], int num, struct connman_option *options)
{
	char *command;

	if (num < 2)
		return -EINVAL;

	command = args[1];

	switch(parse_boolean(command)) {
	case 0:
		if (!session_path)
			return -EALREADY;
		return session_destroy();

	case 1:
		if (session_path)
			return -EALREADY;
		return session_create(FALSE, &args[2], num - 2, options);

	default:
		if (!strcmp(command, "connect")) {
			if (!session_path)
				return session_create(TRUE, &args[2], num - 2,
						      options);

			return session_connect();

		} else if (!strcmp(command, "disconnect")) {

			if (!session_path) {
				fprintf(stdout, "Session does not exist\n");
				return 0;
			}

			return session_disconnect();
		} else if (!strcmp(command, "config")) {
			if (!session_path) {
				fprintf(stdout, "Session does not exist\n");
				return 0;
			}

			if (num == 2)
				return -EINVAL;

			return session_config(&args[2], num - 2, options);
		}

	}

	return -EINVAL;
}

static int cmd_exit(char *args[], int num, struct connman_option *options)
{
	return 1;
}

static char *lookup_key_from_table(GHashTable *hash, const char *text,
					int state)
{
	static int len = 0;
	static GHashTableIter iter;
	gpointer key, value;

	if (state == 0) {
		g_hash_table_iter_init(&iter, hash);
		len = strlen(text);
	}

	while (g_hash_table_iter_next(&iter, &key, &value))
		if (strncmp(text, key, len) == 0)
			return strdup(key);

	return NULL;
}

static char *lookup_service_arg(const char *text, int state)
{
	if (__connmanctl_input_calc_level() > 1) {
		__connmanctl_input_lookup_end();
		return NULL;
	}

	return lookup_key_from_table(service_hash, text, state);
}

static char *lookup_peer(const char *text, int state)
{
	static GHashTableIter iter;
	gpointer key, value;
	static int len = 0;

	if (state == 0) {
		g_hash_table_iter_init(&iter, peer_hash);
		len = strlen(text);
	}

	while (g_hash_table_iter_next(&iter, &key, &value)) {
		const char *peer = key;
		if (strncmp(text, peer, len) == 0)
			return strdup(peer);
	}

	return NULL;
}

static char *lookup_peer_arg(const char *text, int state)
{
	if (__connmanctl_input_calc_level() > 1) {
		__connmanctl_input_lookup_end();
		return NULL;
	}

	return lookup_peer(text, state);
}

static char *lookup_technology(const char *text, int state)
{
	static int len = 0;
	static GHashTableIter iter;
	gpointer key, value;

	if (state == 0) {
		g_hash_table_iter_init(&iter, technology_hash);
		len = strlen(text);
	}

	while (g_hash_table_iter_next(&iter, &key, &value)) {
		const char *technology = key;
		if (strncmp(text, technology, len) == 0)
			return strdup(technology);
	}

	return NULL;
}

static char *lookup_technology_arg(const char *text, int state)
{
	if (__connmanctl_input_calc_level() > 1) {
		__connmanctl_input_lookup_end();
		return NULL;
	}

	return lookup_technology(text, state);
}

static char *lookup_technology_offline(const char *text, int state)
{
	static int len = 0;
	static bool end = false;
	char *str;

	if (__connmanctl_input_calc_level() > 1) {
		__connmanctl_input_lookup_end();
		return NULL;
	}

	if (state == 0) {
		len = strlen(text);
		end = false;
	}

	if (end)
		return NULL;

	str = lookup_technology(text, state);
	if (str)
		return str;

	end = true;

	if (strncmp(text, "offline", len) == 0)
		return strdup("offline");

	return NULL;
}

static char *lookup_on_off(const char *text, int state)
{
	char *onoff[] = { "on", "off", NULL };
	static int idx = 0;
	static int len = 0;

	char *str;

	if (!state) {
		idx = 0;
		len = strlen(text);
	}

	while (onoff[idx]) {
		str = onoff[idx];
		idx++;

		if (!strncmp(text, str, len))
			return strdup(str);
	}

	return NULL;
}

static char *lookup_tether(const char *text, int state)
{
	int level;

	level = __connmanctl_input_calc_level();
	if (level < 2)
		return lookup_technology(text, state);

	if (level == 2)
		return lookup_on_off(text, state);

	__connmanctl_input_lookup_end();

	return NULL;
}

static char *lookup_agent(const char *text, int state)
{
	if (__connmanctl_input_calc_level() > 1) {
		__connmanctl_input_lookup_end();
		return NULL;
	}

	return lookup_on_off(text, state);
}

static char *lookup_vpnconnection_arg(const char *text, int state)
{
	if (__connmanctl_input_calc_level() > 1) {
		__connmanctl_input_lookup_end();
		return NULL;
	}

	return lookup_key_from_table(vpnconnection_hash, text, state);
}

static struct connman_option service_options[] = {
	{"properties", 'p', "[<service>]      (obsolete)"},
	{ NULL, }
};

static struct connman_option config_options[] = {
	{"nameservers", 'n', "<dns1> [<dns2>] [<dns3>]"},
	{"timeservers", 't', "<ntp1> [<ntp2>] [...]"},
	{"domains", 'd', "<domain1> [<domain2>] [...]"},
	{"mdns", 'm', "yes|no"},
	{"ipv6", 'v', "off|auto [enable|disable|preferred]|\n"
	              "\t\t\tmanual <address> <prefixlength> <gateway>"},
	{"proxy", 'x', "direct|auto <URL>|manual <URL1> [<URL2>] [...]\n"
	               "\t\t\t[exclude <exclude1> [<exclude2>] [...]]"},
	{"autoconnect", 'a', "yes|no"},
	{"ipv4", 'i', "off|dhcp|manual <address> <netmask> <gateway>"},
	{"remove", 'r', "                 Remove service"},
	{ NULL, }
};

static struct connman_option monitor_options[] = {
	{"services", 's', "[off]            Monitor only services"},
	{"tech", 'c', "[off]            Monitor only technologies"},
	{"manager", 'm', "[off]            Monitor only manager interface"},
	{"vpnmanager", 'M', "[off]            Monitor only VPN manager "
	 "interface"},
	{"vpnconnection", 'C', "[off]            Monitor only VPN "
	 "connections" },
	{ NULL, }
};

static struct connman_option session_options[] = {
	{"bearers", 'b', "<technology1> [<technology2> [...]]"},
	{"type", 't', "local|internet|any"},
	{"ifname", 'i', "[<interface_name>]"},
	{"srciprule", 's', "yes|no"},
	{"ctxid", 'c', "<context_identifier>"},
	{ NULL, }
};

static char *lookup_options(struct connman_option *options, const char *text,
		int state)
{
	static int idx = 0;
	static int len = 0;
	const char *str;

	if (state == 0) {
		idx = 0;
		len = strlen(text);
	}

	while (options[idx].name) {
		str = options[idx].name;
		idx++;

		if (str && strncmp(text, str, len) == 0)
			return strdup(str);
	}

	return NULL;
}

static char *lookup_monitor(const char *text, int state)
{
	int level;

	level = __connmanctl_input_calc_level();

	if (level < 2)
		return lookup_options(monitor_options, text, state);

	if (level == 2)
		return lookup_on_off(text, state);

	__connmanctl_input_lookup_end();
	return NULL;
}

static char *lookup_config(const char *text, int state)
{
	if (__connmanctl_input_calc_level() < 2)
		return lookup_key_from_table(service_hash, text, state);

	return lookup_options(config_options, text, state);
}

static char *lookup_session(const char *text, int state)
{
	return lookup_options(session_options, text, state);
}

static int peer_service_cb(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	bool registration = GPOINTER_TO_INT(user_data);

	if (error)
		fprintf(stderr, "Error %s peer service: %s\n",
			registration ? "registering" : "unregistering", error);
	else
		fprintf(stdout, "Peer service %s\n",
			registration ? "registered" : "unregistered");

	return 0;
}

struct _peer_service {
	unsigned char *bjr_query;
	int bjr_query_len;
	unsigned char *bjr_response;
	int bjr_response_len;
	unsigned char *wfd_ies;
	int wfd_ies_len;
	char *upnp_service;
	int version;
	int master;
};

static void append_dict_entry_fixed_array(DBusMessageIter *iter,
			const char *property, void *value, int length)
{
	DBusMessageIter dict_entry, variant, array;

	dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY,
							NULL, &dict_entry);
	dbus_message_iter_append_basic(&dict_entry, DBUS_TYPE_STRING,
								&property);
	dbus_message_iter_open_container(&dict_entry, DBUS_TYPE_VARIANT,
			DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_BYTE_AS_STRING,
			&variant);
	dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
					DBUS_TYPE_BYTE_AS_STRING, &array);
	dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
							value, length);
	dbus_message_iter_close_container(&variant, &array);
	dbus_message_iter_close_container(&dict_entry, &variant);
	dbus_message_iter_close_container(iter, &dict_entry);
}

static void append_peer_service_dict(DBusMessageIter *iter, void *user_data)
{
	struct _peer_service *service = user_data;

	if (service->bjr_query && service->bjr_response) {
		append_dict_entry_fixed_array(iter, "BonjourQuery",
			&service->bjr_query, service->bjr_query_len);
		append_dict_entry_fixed_array(iter, "BonjourResponse",
			&service->bjr_response, service->bjr_response_len);
	} else if (service->upnp_service && service->version) {
		__connmanctl_dbus_append_dict_entry(iter, "UpnpVersion",
					DBUS_TYPE_INT32, &service->version);
		__connmanctl_dbus_append_dict_entry(iter, "UpnpService",
				DBUS_TYPE_STRING, &service->upnp_service);
	} else if (service->wfd_ies) {
		append_dict_entry_fixed_array(iter, "WiFiDisplayIEs",
				&service->wfd_ies, service->wfd_ies_len);
	}
}

static void peer_service_append(DBusMessageIter *iter, void *user_data)
{
	struct _peer_service *service = user_data;
	dbus_bool_t master;

	__connmanctl_dbus_append_dict(iter, append_peer_service_dict, service);

	if (service->master < 0)
		return;

	master = service->master == 1 ? TRUE : FALSE;
	dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &master);
}

static struct _peer_service *fill_in_peer_service(unsigned char *bjr_query,
				int bjr_query_len, unsigned char *bjr_response,
				int bjr_response_len, char *upnp_service,
				int version, unsigned char *wfd_ies,
				int wfd_ies_len)
{
	struct _peer_service *service;

	service = dbus_malloc0(sizeof(*service));

	if (bjr_query_len && bjr_response_len) {
		service->bjr_query = dbus_malloc0(bjr_query_len);
		memcpy(service->bjr_query, bjr_query, bjr_query_len);
		service->bjr_query_len = bjr_query_len;

		service->bjr_response = dbus_malloc0(bjr_response_len);
		memcpy(service->bjr_response, bjr_response, bjr_response_len);
		service->bjr_response_len = bjr_response_len;
	} else if (upnp_service && version) {
		service->upnp_service = strdup(upnp_service);
		service->version = version;
	} else if (wfd_ies && wfd_ies_len) {
		service->wfd_ies = dbus_malloc0(wfd_ies_len);
		memcpy(service->wfd_ies, wfd_ies, wfd_ies_len);
		service->wfd_ies_len = wfd_ies_len;
	} else {
		dbus_free(service);
		service = NULL;
	}

	return service;
}

static void free_peer_service(struct _peer_service *service)
{
	dbus_free(service->bjr_query);
	dbus_free(service->bjr_response);
	dbus_free(service->wfd_ies);
	free(service->upnp_service);
	dbus_free(service);
}

static int peer_service_register(unsigned char *bjr_query, int bjr_query_len,
			unsigned char *bjr_response, int bjr_response_len,
			char *upnp_service, int version,
			unsigned char *wfd_ies, int wfd_ies_len, int master)
{
	struct _peer_service *service;
	bool registration = true;
	int ret;

	service = fill_in_peer_service(bjr_query, bjr_query_len, bjr_response,
				bjr_response_len, upnp_service, version,
				wfd_ies, wfd_ies_len);
	if (!service)
		return -EINVAL;

	service->master = master;

	ret = __connmanctl_dbus_method_call(connection, "net.connman", "/",
			"net.connman.Manager", "RegisterPeerService",
			peer_service_cb, GINT_TO_POINTER(registration),
			peer_service_append, service);

	free_peer_service(service);

	return ret;
}

static int peer_service_unregister(unsigned char *bjr_query, int bjr_query_len,
			unsigned char *bjr_response, int bjr_response_len,
			char *upnp_service, int version,
			unsigned char *wfd_ies, int wfd_ies_len)
{
	struct _peer_service *service;
	bool registration = false;
	int ret;

	service = fill_in_peer_service(bjr_query, bjr_query_len, bjr_response,
				bjr_response_len, upnp_service, version,
				wfd_ies, wfd_ies_len);
	if (!service)
		return -EINVAL;

	service->master = -1;

	ret = __connmanctl_dbus_method_call(connection, "net.connman", "/",
			"net.connman.Manager", "UnregisterPeerService",
			peer_service_cb, GINT_TO_POINTER(registration),
			peer_service_append, service);

	free_peer_service(service);

	return ret;
}

static int parse_spec_array(char *command, unsigned char spec[1024])
{
	int length, pos, end;
	char b[3] = {};
	char *e;

	end = strlen(command);
	for (e = NULL, length = pos = 0; command[pos] != '\0'; length++) {
		if (pos+2 > end)
			return -EINVAL;

		b[0] = command[pos];
		b[1] = command[pos+1];

		spec[length] = strtol(b, &e, 16);
		if (e && *e != '\0')
			return -EINVAL;

		pos += 2;
	}

	return length;
}

static int cmd_peer_service(char *args[], int num,
				struct connman_option *options)
{
	unsigned char bjr_query[1024] = {};
	unsigned char bjr_response[1024] = {};
	unsigned char wfd_ies[1024] = {};
	char *upnp_service = NULL;
	int bjr_query_len = 0, bjr_response_len = 0;
	int version = 0, master = 0, wfd_ies_len = 0;
	int limit;

	if (num < 4)
		return -EINVAL;

	if (!strcmp(args[2], "wfd_ies")) {
		wfd_ies_len = parse_spec_array(args[3], wfd_ies);
		if (wfd_ies_len == -EINVAL)
			return -EINVAL;
		limit = 5;
		goto master;
	}

	if (num < 6)
		return -EINVAL;

	limit = 7;
	if (!strcmp(args[2], "bjr_query")) {
		if (strcmp(args[4], "bjr_response"))
			return -EINVAL;
		bjr_query_len = parse_spec_array(args[3], bjr_query);
		bjr_response_len = parse_spec_array(args[5], bjr_response);

		if (bjr_query_len == -EINVAL || bjr_response_len == -EINVAL)
			return -EINVAL;
	} else if (!strcmp(args[2], "upnp_service")) {
		char *e = NULL;

		if (strcmp(args[4], "upnp_version"))
			return -EINVAL;
		upnp_service = args[3];
		version = strtol(args[5], &e, 10);
		if (*e != '\0')
			return -EINVAL;
	}

master:
	if (num == limit) {
		master = parse_boolean(args[6]);
		if (master < 0)
			return -EINVAL;
	}

	if (!strcmp(args[1], "register")) {
		return peer_service_register(bjr_query, bjr_query_len,
				bjr_response, bjr_response_len, upnp_service,
				version, wfd_ies, wfd_ies_len, master);
	} else if (!strcmp(args[1], "unregister")) {
		return peer_service_unregister(bjr_query, bjr_query_len,
				bjr_response, bjr_response_len, upnp_service,
				version, wfd_ies, wfd_ies_len);
	}

	return -EINVAL;
}

static const struct {
        const char *cmd;
	const char *argument;
        struct connman_option *options;
        int (*func) (char *args[], int num, struct connman_option *options);
        const char *desc;
	__connmanctl_lookup_cb cb;
} cmd_table[] = {
	{ "state",        NULL,           NULL,            cmd_state,
	  "Shows if the system is online or offline", NULL },
	{ "technologies", NULL,           NULL,            cmd_technologies,
	  "Display technologies", NULL },
	{ "clock",        NULL,           NULL,            cmd_clock,
	  "Get System Clock Properties", NULL },
	{ "enable",       "<technology>|offline", NULL,    cmd_enable,
	  "Enables given technology or offline mode",
	  lookup_technology_offline },
	{ "disable",      "<technology>|offline", NULL,    cmd_disable,
	  "Disables given technology or offline mode",
	  lookup_technology_offline },
	{ "tether", "<technology> on|off\n"
	            "            wifi [on|off] <ssid> <passphrase> [<freq>] ",
	                                  NULL,            cmd_tether,
	  "Enable, disable tethering, set SSID and passphrase for wifi",
	  lookup_tether },
	{ "tethering_clients", NULL,      NULL,            cmd_tethering_clients,
	  "Display tethering clients", NULL },
	{ "services",     "[<service>]",  service_options, cmd_services,
	  "Display services", lookup_service_arg },
	{ "peers",        "[peer]",       NULL,            cmd_peers,
	  "Display peers", lookup_peer_arg },
	{ "scan",         "<technology>", NULL,            cmd_scan,
	  "Scans for new services for given technology",
	  lookup_technology_arg },
	{ "connect",      "<service/peer>", NULL,          cmd_connect,
	  "Connect a given service or peer", lookup_service_arg },
	{ "disconnect",   "<service/peer>", NULL,          cmd_disconnect,
	  "Disconnect a given service or peer", lookup_service_arg },
	{ "move-before",   "<service> <target service>	", NULL,
	  cmd_service_move_before, "Move <service> before <target service>",
	  lookup_service_arg },
	{ "move-after",   "<service> <target service>	", NULL,
	  cmd_service_move_after, "Move <service> after <target service>",
	  lookup_service_arg },
	{ "config",       "<service>",    config_options,  cmd_config,
	  "Set service configuration options", lookup_config },
	{ "monitor",      "[off]",        monitor_options, cmd_monitor,
	  "Monitor signals from interfaces", lookup_monitor },
	{ "agent", "on|off",              NULL,            cmd_agent,
	  "Agent mode", lookup_agent },
	{ "vpnconnections", "[<connection>]", NULL,        cmd_vpnconnections,
	  "Display VPN connections", lookup_vpnconnection_arg },
	{ "vpnagent",     "on|off",     NULL,            cmd_vpnagent,
	  "VPN Agent mode", lookup_agent },
	{ "session",      "on|off|connect|disconnect|config", session_options,
	  cmd_session, "Enable or disable a session", lookup_session },
	{ "peer_service", "register|unregister <specs> <master>\n"
			  "Where specs are:\n"
			  "\tbjr_query <query> bjr_response <response>\n"
			  "\tupnp_service <service> upnp_version <version>\n"
			  "\twfd_ies <ies>\n", NULL,
	  cmd_peer_service, "(Un)Register a Peer Service", NULL },
	{ "help",         NULL,           NULL,            cmd_help,
	  "Show help", NULL },
	{ "exit",         NULL,           NULL,            cmd_exit,
	  "Exit", NULL },
	{ "quit",         NULL,           NULL,            cmd_exit,
	  "Quit", NULL },
	{  NULL, },
};

static int cmd_help(char *args[], int num, struct connman_option *options)
{
	bool interactive = __connmanctl_is_interactive();
	int i, j;

	if (interactive == false)
		fprintf(stdout, "Usage: connmanctl [[command] [args]]\n");

	for (i = 0; cmd_table[i].cmd; i++) {
		const char *cmd = cmd_table[i].cmd;
		const char *argument = cmd_table[i].argument;
		const char *desc = cmd_table[i].desc;

		printf("%-16s%-22s%s\n", cmd? cmd: "",
				argument? argument: "",
				desc? desc: "");

		if (cmd_table[i].options) {
			for (j = 0; cmd_table[i].options[j].name;
			     j++) {
				const char *options_desc =
					cmd_table[i].options[j].desc ?
					cmd_table[i].options[j].desc: "";

				printf("   --%-16s%s\n",
						cmd_table[i].options[j].name,
						options_desc);
			}
		}
	}

	if (interactive == false)
		fprintf(stdout, "\nNote: arguments and output are considered "
				"EXPERIMENTAL for now.\n");

	return 0;
}

__connmanctl_lookup_cb __connmanctl_get_lookup_func(const char *text)
{
	int i, cmdlen, textlen;

	if (!text)
		return NULL;

	textlen = strlen(text);

	for (i = 0; cmd_table[i].cmd; i++) {
		cmdlen = strlen(cmd_table[i].cmd);

		if (textlen > cmdlen && text[cmdlen] != ' ')
			continue;

		if (strncmp(cmd_table[i].cmd, text, cmdlen) == 0)
			return cmd_table[i].cb;
	}

	return NULL;
}

int __connmanctl_commands(DBusConnection *dbus_conn, char *argv[], int argc)
{
	int i, result;

	connection = dbus_conn;

	for (i = 0; cmd_table[i].cmd; i++) {
		if (g_strcmp0(cmd_table[i].cmd, argv[0]) == 0 &&
				cmd_table[i].func) {
			result = cmd_table[i].func(argv, argc,
					cmd_table[i].options);
			if (result < 0 && result != -EINPROGRESS)
				fprintf(stderr, "Error '%s': %s\n", argv[0],
						strerror(-result));
			return result;
		}
	}

	fprintf(stderr, "Error '%s': Unknown command\n", argv[0]);
	return -EINVAL;
}

char *__connmanctl_lookup_command(const char *text, int state)
{
	static int i = 0;
	static int len = 0;

	if (state == 0) {
		i = 0;
		len = strlen(text);
	}

	while (cmd_table[i].cmd) {
		const char *command = cmd_table[i].cmd;

		i++;

		if (strncmp(text, command, len) == 0)
			return strdup(command);
	}

	return NULL;
}

static char *get_path(char *full_path)
{
	char *path;

	path = strrchr(full_path, '/');
	if (path && *path != '\0')
		path++;
	else
		path = full_path;

	return path;
}

static void add_service_id(const char *path)
{
	g_hash_table_replace(service_hash, g_strdup(path),
			GINT_TO_POINTER(TRUE));
}

static void remove_service_id(const char *path)
{
	g_hash_table_remove(service_hash, path);
}

static void services_added(DBusMessageIter *iter)
{
	DBusMessageIter array;
	char *path = NULL;

	while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRUCT) {

		dbus_message_iter_recurse(iter, &array);
		if (dbus_message_iter_get_arg_type(&array) !=
						DBUS_TYPE_OBJECT_PATH)
			return;

		dbus_message_iter_get_basic(&array, &path);
		add_service_id(get_path(path));

		dbus_message_iter_next(iter);
	}
}

static void update_services(DBusMessageIter *iter)
{
	DBusMessageIter array;
	char *path;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return;

	dbus_message_iter_recurse(iter, &array);
	services_added(&array);

	dbus_message_iter_next(iter);
	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return;

	dbus_message_iter_recurse(iter, &array);
	while (dbus_message_iter_get_arg_type(&array) ==
						DBUS_TYPE_OBJECT_PATH) {
		dbus_message_iter_get_basic(&array, &path);
		remove_service_id(get_path(path));

		dbus_message_iter_next(&array);
	}
}

static int populate_service_hash(DBusMessageIter *iter, int errnum,
				const char *error, void *user_data)
{
	if (error) {
		fprintf(stderr, "Error getting services: %s", error);
		return 0;
	}

	update_services(iter);
	return 0;
}

static void add_vpnconnection_id(const char *path)
{
	g_hash_table_replace(vpnconnection_hash, g_strdup(path),
			GINT_TO_POINTER(TRUE));
}

static void remove_vpnconnection_id(const char *path)
{
	g_hash_table_remove(vpnconnection_hash, path);
}

static void vpnconnection_added(DBusMessageIter *iter)
{
	char *path = NULL;

	dbus_message_iter_get_basic(iter, &path);
	add_vpnconnection_id(get_path(path));
}

static void vpnconnection_removed(DBusMessageIter *iter)
{
	char *path = NULL;

	dbus_message_iter_get_basic(iter, &path);
	remove_vpnconnection_id(get_path(path));
}

static void add_vpnconnections(DBusMessageIter *iter)
{
	DBusMessageIter array;
	char *path = NULL;

	while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRUCT) {

		dbus_message_iter_recurse(iter, &array);
		if (dbus_message_iter_get_arg_type(&array) !=
						DBUS_TYPE_OBJECT_PATH)
			return;

		dbus_message_iter_get_basic(&array, &path);
		add_vpnconnection_id(get_path(path));

		dbus_message_iter_next(iter);
	}
}

static int populate_vpnconnection_hash(DBusMessageIter *iter, int errnum,
				const char *error, void *user_data)
{
	DBusMessageIter array;

	if (error) {
		fprintf(stderr, "Error getting VPN connections: %s", error);
		return 0;
	}

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return 0;

	dbus_message_iter_recurse(iter, &array);

	add_vpnconnections(&array);

	return 0;
}

static void add_peer_id(const char *path)
{
	g_hash_table_replace(peer_hash, g_strdup(path),	GINT_TO_POINTER(TRUE));
}

static void remove_peer_id(const char *path)
{
	g_hash_table_remove(peer_hash, path);
}

static void peers_added(DBusMessageIter *iter)
{
	DBusMessageIter array;
	char *path = NULL;

	while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRUCT) {

		dbus_message_iter_recurse(iter, &array);
		if (dbus_message_iter_get_arg_type(&array) !=
						DBUS_TYPE_OBJECT_PATH)
			return;

		dbus_message_iter_get_basic(&array, &path);
		add_peer_id(get_path(path));

		dbus_message_iter_next(iter);
	}
}

static void update_peers(DBusMessageIter *iter)
{
	DBusMessageIter array;
	char *path;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return;

	dbus_message_iter_recurse(iter, &array);
	peers_added(&array);

	dbus_message_iter_next(iter);
	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return;

	dbus_message_iter_recurse(iter, &array);
	while (dbus_message_iter_get_arg_type(&array) ==
						DBUS_TYPE_OBJECT_PATH) {
		dbus_message_iter_get_basic(&array, &path);
		remove_peer_id(get_path(path));

		dbus_message_iter_next(&array);
	}
}

static int populate_peer_hash(DBusMessageIter *iter, int errnum,
			const char *error, void *user_data)
{
	if (error) {
		fprintf(stderr, "Error getting peers: %s", error);
		return 0;
	}

	update_peers(iter);
	return 0;
}

static void add_technology_id(const char *path)
{
	g_hash_table_replace(technology_hash, g_strdup(path),
			GINT_TO_POINTER(TRUE));
}

static void remove_technology_id(const char *path)
{
	g_hash_table_remove(technology_hash, path);
}

static void remove_technology(DBusMessageIter *iter)
{
	char *path = NULL;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_OBJECT_PATH)
		return;

	dbus_message_iter_get_basic(iter, &path);
	remove_technology_id(get_path(path));
}

static void add_technology(DBusMessageIter *iter)
{
	char *path = NULL;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_OBJECT_PATH)
		return;

	dbus_message_iter_get_basic(iter, &path);
	add_technology_id(get_path(path));
}

static void update_technologies(DBusMessageIter *iter)
{
	DBusMessageIter array;

	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
		return;

	dbus_message_iter_recurse(iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
		DBusMessageIter object_path;

		dbus_message_iter_recurse(&array, &object_path);

		add_technology(&object_path);

		dbus_message_iter_next(&array);
	}
}

static int populate_technology_hash(DBusMessageIter *iter, int errnum,
				const char *error, void *user_data)
{
	if (error) {
		fprintf(stderr, "Error getting technologies: %s\n", error);
		return 0;
	}

	update_technologies(iter);

	return 0;
}

static DBusHandlerResult monitor_completions_changed(
		DBusConnection *connection,
		DBusMessage *message, void *user_data)
{
	bool *enabled = user_data;
	DBusMessageIter iter;
	DBusHandlerResult handled;

	if (*enabled)
		handled = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	else
		handled = DBUS_HANDLER_RESULT_HANDLED;

	if (dbus_message_is_signal(message, "net.connman.Manager",
					"ServicesChanged")) {
		dbus_message_iter_init(message, &iter);
		update_services(&iter);
		return handled;
	}

	if (dbus_message_is_signal(message, "net.connman.vpn.Manager",
					"ConnectionAdded")) {
		dbus_message_iter_init(message, &iter);
		vpnconnection_added(&iter);
		return handled;
	}

	if (dbus_message_is_signal(message, "net.connman.vpn.Manager",
					"ConnectionRemoved")) {
		dbus_message_iter_init(message, &iter);
		vpnconnection_removed(&iter);
		return handled;
	}

	if (dbus_message_is_signal(message, "net.connman.Manager",
						"PeersChanged")) {
		dbus_message_iter_init(message, &iter);
		update_peers(&iter);
		return handled;
	}

	if (dbus_message_is_signal(message, "net.connman.Manager",
					"TechnologyAdded")) {
		dbus_message_iter_init(message, &iter);
		add_technology(&iter);
		return handled;
	}

	if (dbus_message_is_signal(message, "net.connman.Manager",
					"TechnologyRemoved")) {
		dbus_message_iter_init(message, &iter);
		remove_technology(&iter);
		return handled;
	}

	if (!g_strcmp0(dbus_message_get_interface(message),
					"net.connman.Manager"))
		return handled;

	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

void __connmanctl_monitor_completions(DBusConnection *dbus_conn)
{
	bool *manager_enabled = NULL;
	DBusError err;
	int i;

	for (i = 0; monitor[i].interface; i++) {
		if (!strcmp(monitor[i].interface, "Manager")) {
			manager_enabled = &monitor[i].enabled;
			break;
		}
	}

	if (!dbus_conn) {
		g_hash_table_destroy(service_hash);
		g_hash_table_destroy(vpnconnection_hash);
		g_hash_table_destroy(technology_hash);

		dbus_bus_remove_match(connection,
			"type='signal',interface='net.connman.Manager'", NULL);
		dbus_bus_remove_match(connection,
			"type='signal',interface='net.connman.vpn.Manager'",
			NULL);
		dbus_connection_remove_filter(connection,
					monitor_completions_changed,
					manager_enabled);
		return;
	}

	connection = dbus_conn;

	service_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
								g_free, NULL);

	vpnconnection_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
								g_free, NULL);

	peer_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
								g_free, NULL);

	technology_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
								g_free, NULL);

	__connmanctl_dbus_method_call(connection,
				CONNMAN_SERVICE, CONNMAN_PATH,
				"net.connman.Manager", "GetServices",
				populate_service_hash, NULL, NULL, NULL);

	__connmanctl_dbus_method_call(connection,
				VPN_SERVICE, CONNMAN_PATH,
				"net.connman.vpn.Manager", "GetConnections",
				populate_vpnconnection_hash, NULL, NULL, NULL);

	__connmanctl_dbus_method_call(connection,
				CONNMAN_SERVICE, CONNMAN_PATH,
				"net.connman.Manager", "GetPeers",
				populate_peer_hash, NULL, NULL, NULL);

	__connmanctl_dbus_method_call(connection,
				CONNMAN_SERVICE, CONNMAN_PATH,
				"net.connman.Manager", "GetTechnologies",
				populate_technology_hash, NULL, NULL, NULL);

	dbus_connection_add_filter(connection,
				monitor_completions_changed, manager_enabled,
			NULL);

	dbus_error_init(&err);
	dbus_bus_add_match(connection,
			"type='signal',interface='net.connman.Manager'", &err);

	if (dbus_error_is_set(&err)) {
		fprintf(stderr, "Error: %s\n", err.message);
		return;
	}

	dbus_bus_add_match(connection,
			"type='signal',interface='net.connman.vpn.Manager'",
			&err);

	if (dbus_error_is_set(&err))
		fprintf(stderr, "Error: %s\n", err.message);
}
