blob: 1cad3e03626a23e9b2483bed52dda3e9309e5f86 [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2013 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License 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 <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <gdbus.h>
#include "input.h"
#include "dbus_helpers.h"
#include "agent.h"
#define AGENT_INTERFACE "net.connman.Agent"
#define VPN_AGENT_INTERFACE "net.connman.vpn.Agent"
static DBusConnection *agent_connection;
struct agent_input_data {
const char *attribute;
bool requested;
char *prompt;
connmanctl_input_func_t func;
};
struct agent_data {
struct agent_input_data *input;
char *interface;
bool registered;
DBusMessage *message;
DBusMessage *reply;
DBusMessageIter iter;
DBusMessageIter dict;
GDBusMethodFunction pending_function;
};
static void request_input_ssid_return(char *input, void *user_data);
static void request_input_passphrase_return(char *input, void *user_data);
static void request_input_string_return(char *input, void *user_data);
enum requestinput {
SSID = 0,
IDENTITY = 1,
PASSPHRASE = 2,
WPS = 3,
WISPR_USERNAME = 4,
WISPR_PASSPHRASE = 5,
REQUEST_INPUT_MAX = 6,
};
static struct agent_input_data agent_input_handler[] = {
{ "Name", false, "Hidden SSID name? ", request_input_ssid_return },
{ "Identity", false, "EAP username? ", request_input_string_return },
{ "Passphrase", false, "Passphrase? ",
request_input_passphrase_return },
{ "WPS", false, "WPS PIN (empty line for pushbutton)? " ,
request_input_string_return },
{ "Username", false, "WISPr username? ", request_input_string_return },
{ "Password", false, "WISPr password? ", request_input_string_return },
{ },
};
static struct agent_data agent_request = {
agent_input_handler,
AGENT_INTERFACE,
};
static struct agent_input_data vpnagent_input_handler[] = {
{ "OpenConnect.Cookie", false, "OpenConnect Cookie? ",
request_input_string_return },
{ "OpenConnect.ServerCert", false,
"OpenConnect server certificate hash? ",
request_input_string_return },
{ "OpenConnect.VPNHost", false, "OpenConnect VPN server? ",
request_input_string_return },
{ "Username", false, "VPN username? ", request_input_string_return },
{ "Password", false, "VPN password? ", request_input_string_return },
{ },
};
static struct agent_data vpn_agent_request = {
vpnagent_input_handler,
VPN_AGENT_INTERFACE,
};
static int confirm_input(char *input)
{
int i;
if (!input)
return -1;
for (i = 0; input[i] != '\0'; i++)
if (isspace(input[i]) == 0)
break;
if (strcasecmp(&input[i], "yes") == 0 ||
strcasecmp(&input[i], "y") == 0)
return 1;
if (strcasecmp(&input[i], "no") == 0 ||
strcasecmp(&input[i], "n") == 0)
return 0;
return -1;
}
static char *strip_path(char *path)
{
char *name = strrchr(path, '/');
if (name)
name++;
else
name = path;
return name;
}
static char *agent_path(void)
{
static char *path = NULL;
if (!path)
path = g_strdup_printf("/net/connman/connmanctl%d", getpid());
return path;
}
static void pending_message_remove(struct agent_data *request)
{
if (request->message) {
dbus_message_unref(request->message);
request->message = NULL;
}
if (request->reply) {
dbus_message_unref(request->reply);
request->reply = NULL;
}
}
static void pending_command_complete(char *message)
{
struct agent_data *next_request = NULL;
DBusMessage *pending_message;
GDBusMethodFunction pending_function;
__connmanctl_save_rl();
fprintf(stdout, "%s", message);
__connmanctl_redraw_rl();
if (__connmanctl_is_interactive() == true)
__connmanctl_command_mode();
else
__connmanctl_agent_mode("", NULL, NULL);
if (agent_request.message)
next_request = &agent_request;
else if (vpn_agent_request.message)
next_request = &vpn_agent_request;
if (!next_request)
return;
pending_message = next_request->message;
pending_function = next_request->pending_function;
next_request->pending_function = NULL;
pending_function(agent_connection, next_request->message,
next_request);
dbus_message_unref(pending_message);
}
static bool handle_message(DBusMessage *message, struct agent_data *request,
GDBusMethodFunction function)
{
if (!agent_request.pending_function &&
!vpn_agent_request.pending_function)
return true;
request->message = dbus_message_ref(message);
request->pending_function = function;
return false;
}
static DBusMessage *agent_release(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
struct agent_data *request = user_data;
if (handle_message(message, request, agent_release) == false)
return NULL;
g_dbus_unregister_interface(connection, agent_path(),
request->interface);
request->registered = false;
pending_message_remove(request);
if (strcmp(request->interface, AGENT_INTERFACE) == 0)
pending_command_complete("Agent unregistered by ConnMan\n");
else
pending_command_complete("VPN Agent unregistered by ConnMan "
"VPNd\n");
if (__connmanctl_is_interactive() == false)
__connmanctl_quit();
return dbus_message_new_method_return(message);
}
static DBusMessage *agent_cancel(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
struct agent_data *request = user_data;
if (handle_message(message, request, agent_cancel) == false)
return NULL;
pending_message_remove(request);
if (strcmp(request->interface, AGENT_INTERFACE) == 0)
pending_command_complete("Agent request cancelled by "
"ConnMan\n");
else
pending_command_complete("VPN Agent request cancelled by "
"ConnMan VPNd\n");
return dbus_message_new_method_return(message);
}
static void request_browser_return(char *input, void *user_data)
{
struct agent_data *request = user_data;
switch (confirm_input(input)) {
case 1:
g_dbus_send_reply(agent_connection, request->message,
DBUS_TYPE_INVALID);
break;
case 0:
g_dbus_send_error(agent_connection, request->message,
"net.connman.Agent.Error.Canceled", NULL);
break;
default:
return;
}
pending_message_remove(request);
pending_command_complete("");
}
static DBusMessage *agent_request_browser(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
struct agent_data *request = user_data;
DBusMessageIter iter;
char *service, *url;
if (handle_message(message, request, agent_request_browser) == false)
return NULL;
dbus_message_iter_init(message, &iter);
dbus_message_iter_get_basic(&iter, &service);
dbus_message_iter_next(&iter);
dbus_message_iter_get_basic(&iter, &url);
__connmanctl_save_rl();
fprintf(stdout, "Agent RequestBrowser %s\n", strip_path(service));
fprintf(stdout, " %s\n", url);
__connmanctl_redraw_rl();
request->message = dbus_message_ref(message);
__connmanctl_agent_mode("Connected (yes/no)? ",
request_browser_return, request);
return NULL;
}
static void report_error_return(char *input, void *user_data)
{
struct agent_data *request = user_data;
switch (confirm_input(input)) {
case 1:
if (strcmp(request->interface, AGENT_INTERFACE) == 0)
g_dbus_send_error(agent_connection, request->message,
"net.connman.Agent.Error.Retry", NULL);
else
g_dbus_send_error(agent_connection, request->message,
"net.connman.vpn.Agent.Error.Retry",
NULL);
break;
case 0:
g_dbus_send_reply(agent_connection, request->message,
DBUS_TYPE_INVALID);
break;
default:
return;
}
pending_message_remove(request);
pending_command_complete("");
}
static DBusMessage *agent_report_error(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
struct agent_data *request = user_data;
DBusMessageIter iter;
char *path, *service, *error;
if (handle_message(message, request, agent_report_error) == false)
return NULL;
dbus_message_iter_init(message, &iter);
dbus_message_iter_get_basic(&iter, &path);
service = strip_path(path);
dbus_message_iter_next(&iter);
dbus_message_iter_get_basic(&iter, &error);
__connmanctl_save_rl();
if (strcmp(request->interface, AGENT_INTERFACE) == 0)
fprintf(stdout, "Agent ReportError %s\n", service);
else
fprintf(stdout, "VPN Agent ReportError %s\n", service);
fprintf(stdout, " %s\n", error);
__connmanctl_redraw_rl();
request->message = dbus_message_ref(message);
__connmanctl_agent_mode("Retry (yes/no)? ", report_error_return,
request);
return NULL;
}
static DBusMessage *agent_report_peer_error(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
struct agent_data *request = user_data;
char *path, *peer, *error;
DBusMessageIter iter;
if (handle_message(message, request,
agent_report_peer_error) == false)
return NULL;
dbus_message_iter_init(message, &iter);
dbus_message_iter_get_basic(&iter, &path);
peer = strip_path(path);
dbus_message_iter_next(&iter);
dbus_message_iter_get_basic(&iter, &error);
__connmanctl_save_rl();
fprintf(stdout, "Agent ReportPeerError %s\n", peer);
fprintf(stdout, " %s\n", error);
__connmanctl_redraw_rl();
request->message = dbus_message_ref(message);
__connmanctl_agent_mode("Retry (yes/no)? ",
report_error_return, request);
return NULL;
}
static void request_input_next(struct agent_data *request)
{
int i;
for (i = 0; request->input[i].attribute; i++) {
if (request->input[i].requested == true) {
if (request->input[i].func)
__connmanctl_agent_mode(request->input[i].prompt,
request->input[i].func,
request);
else
request->input[i].requested = false;
return;
}
}
dbus_message_iter_close_container(&request->iter, &request->dict);
g_dbus_send_message(agent_connection, request->reply);
request->reply = NULL;
pending_message_remove(request);
pending_command_complete("");
__connmanctl_redraw_rl();
}
static void request_input_append(struct agent_data *request,
const char *attribute, char *value)
{
__connmanctl_dbus_append_dict_entry(&request->dict, attribute,
DBUS_TYPE_STRING, &value);
}
static void request_input_ssid_return(char *input,
void *user_data)
{
struct agent_data *request = user_data;
int len = 0;
if (input)
len = strlen(input);
if (len > 0 && len <= 32) {
request->input[SSID].requested = false;
request_input_append(request, request->input[SSID].attribute,
input);
request_input_next(request);
}
}
static void request_input_passphrase_return(char *input, void *user_data)
{
struct agent_data *request = user_data;
int len = 0;
/* TBD passphrase length checking */
if (input)
len = strlen(input);
if (len == 0 && request->input[WPS].requested == false)
return;
request->input[PASSPHRASE].requested = false;
if (len > 0) {
request_input_append(request,
request->input[PASSPHRASE].attribute, input);
request->input[WPS].requested = false;
}
request_input_next(request);
}
static void request_input_string_return(char *input, void *user_data)
{
struct agent_data *request = user_data;
int i;
for (i = 0; request->input[i].attribute; i++) {
if (request->input[i].requested == true) {
request_input_append(request,
request->input[i].attribute, input);
request->input[i].requested = false;
break;
}
}
request_input_next(request);
}
static void parse_agent_request(struct agent_data *request,
DBusMessageIter *iter)
{
DBusMessageIter dict, entry, variant, dict_entry;
DBusMessageIter field_entry, field_value;
char *field, *argument, *value;
char *attr_type = NULL;
int i;
dbus_message_iter_recurse(iter, &dict);
while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
dbus_message_iter_recurse(&dict, &entry);
dbus_message_iter_get_basic(&entry, &field);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &variant);
dbus_message_iter_recurse(&variant, &dict_entry);
while (dbus_message_iter_get_arg_type(&dict_entry)
== DBUS_TYPE_DICT_ENTRY) {
dbus_message_iter_recurse(&dict_entry, &field_entry);
dbus_message_iter_get_basic(&field_entry, &argument);
dbus_message_iter_next(&field_entry);
dbus_message_iter_recurse(&field_entry, &field_value);
if (strcmp(argument, "Type") == 0) {
dbus_message_iter_get_basic(&field_value,
&value);
attr_type = g_strdup(value);
}
dbus_message_iter_next(&dict_entry);
}
for (i = 0; request->input[i].attribute; i++) {
if (strcmp(field, request->input[i].attribute) == 0) {
request->input[i].requested = true;
break;
}
}
g_free(attr_type);
attr_type = NULL;
dbus_message_iter_next(&dict);
}
}
static DBusMessage *agent_request_input(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
struct agent_data *request = user_data;
DBusMessageIter iter, dict;
char *service, *str;
if (handle_message(message, request, agent_request_input) == false)
return NULL;
dbus_message_iter_init(message, &iter);
dbus_message_iter_get_basic(&iter, &str);
service = strip_path(str);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &dict);
__connmanctl_save_rl();
if (strcmp(request->interface, AGENT_INTERFACE) == 0)
fprintf(stdout, "Agent RequestInput %s\n", service);
else
fprintf(stdout, "VPN Agent RequestInput %s\n", service);
__connmanctl_dbus_print(&dict, " ", " = ", "\n");
fprintf(stdout, "\n");
parse_agent_request(request, &iter);
request->reply = dbus_message_new_method_return(message);
dbus_message_iter_init_append(request->reply, &request->iter);
dbus_message_iter_open_container(&request->iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&request->dict);
request_input_next(request);
return NULL;
}
static void request_authorization_return(char *input, void *user_data)
{
struct agent_data *request = user_data;
switch (confirm_input(input)) {
case 1:
request->reply = dbus_message_new_method_return(
request->message);
dbus_message_iter_init_append(request->reply, &request->iter);
dbus_message_iter_open_container(&request->iter,
DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&request->dict);
dbus_message_iter_close_container(&request->iter,
&request->dict);
g_dbus_send_message(agent_connection, request->reply);
request->reply = NULL;
break;
case 0:
g_dbus_send_error(agent_connection, request->message,
"net.connman.Agent.Error.Rejected", NULL);
break;
default:
g_dbus_send_error(agent_connection, request->message,
"net.connman.Agent.Error.Canceled", NULL);
break;
}
pending_message_remove(request);
pending_command_complete("");
}
static DBusMessage *
agent_request_peer_authorization(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
struct agent_data *request = user_data;
DBusMessageIter iter, dict;
char *peer, *str;
bool input;
int i;
if (handle_message(message, request, agent_request_peer_authorization)
== false)
return NULL;
dbus_message_iter_init(message, &iter);
dbus_message_iter_get_basic(&iter, &str);
peer = strip_path(str);
dbus_message_iter_next(&iter);
dbus_message_iter_recurse(&iter, &dict);
__connmanctl_save_rl();
fprintf(stdout, "Agent RequestPeerAuthorization %s\n", peer);
__connmanctl_dbus_print(&dict, " ", " = ", "\n");
fprintf(stdout, "\n");
parse_agent_request(request, &iter);
for (input = false, i = 0; request->input[i].attribute; i++) {
if (request->input[i].requested == true) {
input = true;
break;
}
}
if (!input) {
request->message = dbus_message_ref(message);
__connmanctl_agent_mode("Accept connection (yes/no)? ",
request_authorization_return, request);
return NULL;
}
request->reply = dbus_message_new_method_return(message);
dbus_message_iter_init_append(request->reply, &request->iter);
dbus_message_iter_open_container(&request->iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&request->dict);
request_input_next(request);
return NULL;
}
static const GDBusMethodTable agent_methods[] = {
{ GDBUS_ASYNC_METHOD("Release", NULL, NULL, agent_release) },
{ GDBUS_ASYNC_METHOD("Cancel", NULL, NULL, agent_cancel) },
{ GDBUS_ASYNC_METHOD("RequestBrowser",
GDBUS_ARGS({ "service", "o" },
{ "url", "s" }),
NULL, agent_request_browser) },
{ GDBUS_ASYNC_METHOD("ReportError",
GDBUS_ARGS({ "service", "o" },
{ "error", "s" }),
NULL, agent_report_error) },
{ GDBUS_ASYNC_METHOD("ReportPeerError",
GDBUS_ARGS({ "peer", "o" },
{ "error", "s" }),
NULL, agent_report_peer_error) },
{ GDBUS_ASYNC_METHOD("RequestInput",
GDBUS_ARGS({ "service", "o" },
{ "fields", "a{sv}" }),
GDBUS_ARGS({ "fields", "a{sv}" }),
agent_request_input) },
{ GDBUS_ASYNC_METHOD("RequestPeerAuthorization",
GDBUS_ARGS({ "peer", "o" },
{ "fields", "a{sv}" }),
GDBUS_ARGS({ "fields", "a{sv}" }),
agent_request_peer_authorization) },
{ },
};
static int agent_register_return(DBusMessageIter *iter, int errnum,
const char *error, void *user_data)
{
DBusConnection *connection = user_data;
if (error) {
g_dbus_unregister_interface(connection, agent_path(),
AGENT_INTERFACE);
fprintf(stderr, "Error registering Agent: %s\n", error);
return 0;
}
agent_request.registered = true;
fprintf(stdout, "Agent registered\n");
return -EINPROGRESS;
}
static void append_path(DBusMessageIter *iter, void *user_data)
{
const char *path = user_data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
}
int __connmanctl_agent_register(DBusConnection *connection)
{
char *path = agent_path();
int result;
if (agent_request.registered == true) {
fprintf(stderr, "Agent already registered\n");
return -EALREADY;
}
agent_connection = connection;
if (!g_dbus_register_interface(connection, path,
AGENT_INTERFACE, agent_methods,
NULL, NULL, &agent_request, NULL)) {
fprintf(stderr, "Error: Failed to register Agent callbacks\n");
return 0;
}
result = __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE,
CONNMAN_PATH, "net.connman.Manager", "RegisterAgent",
agent_register_return, connection, append_path, path);
if (result != -EINPROGRESS) {
g_dbus_unregister_interface(connection, agent_path(),
AGENT_INTERFACE);
fprintf(stderr, "Error: Failed to register Agent\n");
}
return result;
}
static int agent_unregister_return(DBusMessageIter *iter, int errnum,
const char *error, void *user_data)
{
if (error) {
fprintf(stderr, "Error unregistering Agent: %s\n", error);
return 0;
}
agent_request.registered = false;
fprintf(stdout, "Agent unregistered\n");
return 0;
}
int __connmanctl_agent_unregister(DBusConnection *connection)
{
char *path = agent_path();
int result;
if (agent_request.registered == false) {
fprintf(stderr, "Agent not registered\n");
return -EALREADY;
}
g_dbus_unregister_interface(connection, agent_path(), AGENT_INTERFACE);
result = __connmanctl_dbus_method_call(connection, CONNMAN_SERVICE,
CONNMAN_PATH, "net.connman.Manager", "UnregisterAgent",
agent_unregister_return, NULL, append_path, path);
if (result != -EINPROGRESS)
fprintf(stderr, "Error: Failed to unregister Agent\n");
return result;
}
static const GDBusMethodTable vpn_agent_methods[] = {
{ GDBUS_ASYNC_METHOD("Release", NULL, NULL, agent_release) },
{ GDBUS_ASYNC_METHOD("Cancel", NULL, NULL, agent_cancel) },
{ GDBUS_ASYNC_METHOD("ReportError",
GDBUS_ARGS({ "service", "o" },
{ "error", "s" }),
NULL, agent_report_error) },
{ GDBUS_ASYNC_METHOD("RequestInput",
GDBUS_ARGS({ "service", "o" },
{ "fields", "a{sv}" }),
GDBUS_ARGS({ "fields", "a{sv}" }),
agent_request_input) },
{ },
};
static int vpn_agent_register_return(DBusMessageIter *iter, int errnum,
const char *error, void *user_data)
{
DBusConnection *connection = user_data;
if (error) {
g_dbus_unregister_interface(connection, agent_path(),
VPN_AGENT_INTERFACE);
fprintf(stderr, "Error registering VPN Agent: %s\n", error);
return 0;
}
vpn_agent_request.registered = true;
fprintf(stdout, "VPN Agent registered\n");
return -EINPROGRESS;
}
int __connmanctl_vpn_agent_register(DBusConnection *connection)
{
char *path = agent_path();
int result;
if (vpn_agent_request.registered == true) {
fprintf(stderr, "VPN Agent already registered\n");
return -EALREADY;
}
agent_connection = connection;
if (!g_dbus_register_interface(connection, path,
VPN_AGENT_INTERFACE, vpn_agent_methods,
NULL, NULL, &vpn_agent_request, NULL)) {
fprintf(stderr, "Error: Failed to register VPN Agent "
"callbacks\n");
return 0;
}
result = __connmanctl_dbus_method_call(connection, VPN_SERVICE,
VPN_PATH, "net.connman.vpn.Manager", "RegisterAgent",
vpn_agent_register_return, connection, append_path,
path);
if (result != -EINPROGRESS) {
g_dbus_unregister_interface(connection, agent_path(),
VPN_AGENT_INTERFACE);
fprintf(stderr, "Error: Failed to register VPN Agent\n");
}
return result;
}
static int vpn_agent_unregister_return(DBusMessageIter *iter, int errnum,
const char *error, void *user_data)
{
if (error) {
fprintf(stderr, "Error unregistering VPN Agent: %s\n", error);
return 0;
}
vpn_agent_request.registered = false;
fprintf(stdout, "VPN Agent unregistered\n");
return 0;
}
int __connmanctl_vpn_agent_unregister(DBusConnection *connection)
{
char *path = agent_path();
int result;
if (vpn_agent_request.registered == false) {
fprintf(stderr, "VPN Agent not registered\n");
return -EALREADY;
}
g_dbus_unregister_interface(connection, agent_path(),
VPN_AGENT_INTERFACE);
result = __connmanctl_dbus_method_call(connection, VPN_SERVICE,
VPN_PATH, "net.connman.vpn.Manager", "UnregisterAgent",
vpn_agent_unregister_return, NULL, append_path, path);
if (result != -EINPROGRESS)
fprintf(stderr, "Error: Failed to unregister VPN Agent\n");
return result;
}