This adds VPN agent support for VPNC plugin. IPSec.Secret,
Xauth.Username and Xauth.Password are queried from agent if not set in
.config of the VPN provider.
By default, VPNC does not save the credentials to provider config. The
VPNC credentials stored by vpn-provider.c in the provider settings
strings are cleared after run_connect() has finished.
The values of IPSec.Secret, Xauth.Username and Xauth.Password are set to
"-" in order to get them retrieved via VPN agent at next connect request.
The credentials read from .config file are not being reset as the
immutable value of them is checked first. This approach supports also
partially defined credentials in .config, leaving some of them to be
retrieved using a VPN agent. The immutable values are sent as
informational, passwords are changed to "********" to hide them since
the values cannot be changed.
The approach for setting the credentials to "-" follows the approach of
OpenVPN plugin, where credential set as "-" is retrieved over management
interface.
If the credential has something else than "-" set it is forwarded to VPN
agent as old information, if at least one of the credentials is missing.
In this case request_input_credentials() is called and a message is sent
to VPN agent if any. request_input_credentials_reply() handles the
message sent by VPN agent, or errors (Error.Canceled = ECONNABORTED and
Error.Timeout = ETIMEDOUT) from VPN_AGENT_INTERFACE. vc_connect_done()
is invoked to call the callback function, which is executed only once,
with the error code, if any. In case of success, run_connect() is called
and VPN is attempted to connect.
When vc_notify() is called, it retrieves the plugin data from provider
and utilizes its callback by calling vc_connect_done() with the proper
error code, 0 being success.
If plugin dies or is disconnected, vc_died() handles the shutdown by
canceling all agent requests and calls vpn_died(). Last, the data
allocated for the connection is free'd.
---
Changes since V2:
* Remove testing debug messages that reveal secret and password.
* Set to send immutable values as informational to VPN agent. Also
hide immutable passwords as "********" when sending to VPN agent as
informational.
Changes since V3:
* If request_input_credentials_reply() gets other than canceled or
timeout error goto err to set error code.
* Move dbus_message_unref() up in request_input_credentials() to have
it only once.
vpn/plugins/vpnc.c | 465 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 435 insertions(+), 30 deletions(-)
diff --git a/vpn/plugins/vpnc.c b/vpn/plugins/vpnc.c
index 87a746cc..d0ac86cf 100644
--- a/vpn/plugins/vpnc.c
+++ b/vpn/plugins/vpnc.c
@@ -39,15 +39,18 @@
#include <connman/task.h>
#include <connman/ipconfig.h>
#include <connman/dbus.h>
+#include <connman/agent.h>
+#include <connman/setting.h>
+#include <connman/vpn-dbus.h>
#include "../vpn-provider.h"
+#include "../vpn-agent.h"
#include "vpn.h"
+#include "../vpn.h"
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
-static DBusConnection *connection;
-
enum {
OPT_STRING = 1,
OPT_BOOLEAN = 2,
@@ -83,14 +86,58 @@ struct {
true },
};
+struct vc_private_data {
+ struct vpn_provider *provider;
+ struct connman_task *task;
+ char *if_name;
+ vpn_provider_connect_cb_t cb;
+ void *user_data;
+};
+
+static void vc_connect_done(struct vc_private_data *data, int err)
+{
+ DBG("data %p err %d", data, err);
+
+ if (data && data->cb) {
+ vpn_provider_connect_cb_t cb = data->cb;
+ void *user_data = data->user_data;
+
+ /* Make sure we don't invoke this callback twice */
+ data->cb = NULL;
+ data->user_data = NULL;
+ cb(data->provider, user_data, err);
+ }
+}
+
+static void free_private_data(struct vc_private_data *data)
+{
+ DBG("data %p", data);
+
+ if (!data || !data->provider)
+ return;
+
+ DBG("provider %p", data->provider);
+
+ if (vpn_provider_get_plugin_data(data->provider) == data)
+ vpn_provider_set_plugin_data(data->provider, NULL);
+
+ vpn_provider_unref(data->provider);
+
+ g_free(data->if_name);
+ g_free(data);
+}
+
static int vc_notify(DBusMessage *msg, struct vpn_provider *provider)
{
DBusMessageIter iter, dict;
char *address = NULL, *netmask = NULL, *gateway = NULL;
struct connman_ipaddress *ipaddress;
const char *reason, *key, *value;
+ struct vc_private_data *data;
int type;
+ data = vpn_provider_get_plugin_data(provider);
+
dbus_message_iter_init(msg, &iter);
type = dbus_message_iter_get_arg_type(&iter);
@@ -104,11 +151,14 @@ static int vc_notify(DBusMessage *msg, struct vpn_provider
*provider)
if (!provider) {
connman_error("No provider found");
+ vc_connect_done(data, ENOENT);
return VPN_STATE_FAILURE;
}
- if (strcmp(reason, "connect"))
+ if (g_strcmp0(reason, "connect")) {
+ vc_connect_done(data, EIO);
return VPN_STATE_DISCONNECT;
+ }
dbus_message_iter_recurse(&iter, &dict);
@@ -160,7 +210,7 @@ static int vc_notify(DBusMessage *msg, struct vpn_provider *provider)
g_free(address);
g_free(netmask);
g_free(gateway);
-
+ vc_connect_done(data, EIO);
return VPN_STATE_FAILURE;
}
@@ -172,6 +222,7 @@ static int vc_notify(DBusMessage *msg, struct vpn_provider *provider)
g_free(gateway);
connman_ipaddress_free(ipaddress);
+ vc_connect_done(data, 0);
return VPN_STATE_CONNECT;
}
@@ -280,27 +331,45 @@ static int vc_save(struct vpn_provider *provider, GKeyFile
*keyfile)
return 0;
}
-static int vc_connect(struct vpn_provider *provider,
- struct connman_task *task, const char *if_name,
- vpn_provider_connect_cb_t cb, const char *dbus_sender,
- void *user_data)
+static void vc_died(struct connman_task *task, int exit_code, void *user_data)
{
- const char *option;
- int err = 0, fd;
+ struct vc_private_data *data = user_data;
- option = vpn_provider_get_string(provider, "Host");
- if (!option) {
- connman_error("Host not set; cannot enable VPN");
- err = -EINVAL;
- goto done;
- }
- option = vpn_provider_get_string(provider, "VPNC.IPSec.ID");
- if (!option) {
- connman_error("Group not set; cannot enable VPN");
- err = -EINVAL;
- goto done;
+ DBG("task %p data %p exit_code %d user_data %p", task, data, exit_code,
+ user_data);
+
+ if (!data)
+ return;
+
+ if (data->provider) {
+ connman_agent_cancel(data->provider);
+
+ if (task)
+ vpn_died(task, exit_code, data->provider);
}
+ free_private_data(data);
+}
+
+static int run_connect(struct vc_private_data *data)
+{
+ struct vpn_provider *provider;
+ struct connman_task *task;
+ const char *credentials[] = {"VPNC.IPSec.Secret",
"VPNC.Xauth.Username",
+ "VPNC.Xauth.Password", NULL};
+ const char *if_name;
+ const char *option;
+ int err;
+ int fd;
+ int i;
+
+ provider = data->provider;
+ task = data->task;
+ if_name = data->if_name;
+
+ DBG("provider %p task %p interface %s user_data %p", provider, task,
+ if_name, data->user_data);
+
connman_task_add_argument(task, "--non-inter", NULL);
connman_task_add_argument(task, "--no-detach", NULL);
@@ -323,8 +392,7 @@ static int vc_connect(struct vpn_provider *provider,
connman_task_add_argument(task, "-", NULL);
- err = connman_task_run(task, vpn_died, provider,
- &fd, NULL, NULL);
+ err = connman_task_run(data->task, vc_died, data, &fd, NULL, NULL);
if (err < 0) {
connman_error("vpnc failed to start");
err = -EIO;
@@ -333,15 +401,356 @@ static int vc_connect(struct vpn_provider *provider,
err = vc_write_config_data(provider, fd);
- close(fd);
+ if (err) {
+ DBG("config write error %s", strerror(err));
+ goto done;
+ }
+
+ err = -EINPROGRESS;
done:
- if (cb)
- cb(provider, user_data, err);
+ close(fd);
+
+ /*
+ * Clear out credentials if they are non-immutable. If this is called
+ * directly from vc_connect() all credentials are read from config and
+ * are set as immutable, so no change is done. In case a VPN agent is
+ * used these values should be reset to "-" in order to retrieve them
+ * from VPN agent next time VPN connection is established. This supports
+ * then partially defined credentials in .config and some can be
+ * retrieved using an agent.
+ */
+ for (i = 0; credentials[i]; i++) {
+ const char *key = credentials[i];
+ if (!vpn_provider_get_string_immutable(provider, key))
+ vpn_provider_set_string(provider, key, "-");
+ }
return err;
}
+static void request_input_append_mandatory(DBusMessageIter *iter,
+ void *user_data)
+{
+ char *str = "string";
+
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &str);
+ str = "mandatory";
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
+
+ if (!user_data)
+ return;
+
+ str = user_data;
+ connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str);
+}
+
+static void request_input_append_password(DBusMessageIter *iter,
+ void *user_data)
+{
+ char *str = "password";
+
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &str);
+ str = "mandatory";
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
+
+ if (!user_data)
+ return;
+
+ str = user_data;
+ connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str);
+}
+
+static void request_input_append_informational(DBusMessageIter *iter,
+ void *user_data)
+{
+ char *str = "password";
+
+ connman_dbus_dict_append_basic(iter, "Type",
+ DBUS_TYPE_STRING, &str);
+ str = "informational";
+ connman_dbus_dict_append_basic(iter, "Requirement",
+ DBUS_TYPE_STRING, &str);
+
+ if (!user_data)
+ return;
+
+ str = user_data;
+ connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str);
+}
+
+static void request_input_append_to_dict(struct vpn_provider *provider,
+ DBusMessageIter *dict,
+ connman_dbus_append_cb_t function_cb, const char *key)
+{
+ const char *str;
+ bool immutable = false;
+
+ if (!provider || !dict || !function_cb || !key)
+ return;
+
+ str = vpn_provider_get_string(provider, key);
+
+ /* If value is "-", it is cleared by VPN agent */
+ if (!g_strcmp0(str, "-"))
+ str = NULL;
+
+ if (str)
+ immutable = vpn_provider_get_string_immutable(provider, key);
+
+ if (immutable) {
+ /* Hide immutable password types */
+ if (function_cb == request_input_append_password)
+ str = "********";
+
+ /* Send immutable as informational */
+ function_cb = request_input_append_informational;
+ }
+
+ connman_dbus_dict_append_dict(dict, key, function_cb, (void *)str);
+}
+
+static void request_input_credentials_reply(DBusMessage *reply, void *user_data)
+{
+ struct vc_private_data *data = user_data;
+ char *secret = NULL, *username = NULL, *password = NULL;
+ const char *key;
+ DBusMessageIter iter, dict;
+ DBusError error;
+ int err_int = 0;
+
+ DBG("provider %p", data->provider);
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, reply)) {
+ if (!g_strcmp0(error.name, VPN_AGENT_INTERFACE
+ ".Error.Canceled"))
+ err_int = ECONNABORTED;
+
+ if (!g_strcmp0(error.name, VPN_AGENT_INTERFACE
+ ".Error.Timeout"))
+ err_int = ETIMEDOUT;
+
+ dbus_error_free(&error);
+
+ if (err_int)
+ goto abort;
+
+ goto err;
+ }
+
+ if (!vpn_agent_check_reply_has_dict(reply))
+ goto err;
+
+ dbus_message_iter_init(reply, &iter);
+ dbus_message_iter_recurse(&iter, &dict);
+ while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
+ DBusMessageIter entry, value;
+
+ dbus_message_iter_recurse(&dict, &entry);
+ if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+ break;
+
+ dbus_message_iter_get_basic(&entry, &key);
+
+ if (g_str_equal(key, "VPNC.IPSec.Secret")) {
+ dbus_message_iter_next(&entry);
+ if (dbus_message_iter_get_arg_type(&entry)
+ != DBUS_TYPE_VARIANT)
+ break;
+ dbus_message_iter_recurse(&entry, &value);
+ if (dbus_message_iter_get_arg_type(&value)
+ != DBUS_TYPE_STRING)
+ break;
+ dbus_message_iter_get_basic(&value, &secret);
+ vpn_provider_set_string_hide_value(data->provider,
+ key, secret);
+
+ } else if (g_str_equal(key, "VPNC.Xauth.Username")) {
+ dbus_message_iter_next(&entry);
+ if (dbus_message_iter_get_arg_type(&entry)
+ != DBUS_TYPE_VARIANT)
+ break;
+ dbus_message_iter_recurse(&entry, &value);
+ if (dbus_message_iter_get_arg_type(&value)
+ != DBUS_TYPE_STRING)
+ break;
+ dbus_message_iter_get_basic(&value, &username);
+ vpn_provider_set_string(data->provider, key, username);
+
+ } else if (g_str_equal(key, "VPNC.Xauth.Password")) {
+ dbus_message_iter_next(&entry);
+ if (dbus_message_iter_get_arg_type(&entry)
+ != DBUS_TYPE_VARIANT)
+ break;
+ dbus_message_iter_recurse(&entry, &value);
+ if (dbus_message_iter_get_arg_type(&value)
+ != DBUS_TYPE_STRING)
+ break;
+ dbus_message_iter_get_basic(&value, &password);
+ vpn_provider_set_string_hide_value(data->provider, key,
+ password);
+ }
+
+ dbus_message_iter_next(&dict);
+ }
+
+ if (!secret || !username || !password)
+ goto err;
+
+ err_int = run_connect(data);
+ if (err_int != -EINPROGRESS)
+ goto err;
+
+ return;
+
+err:
+ err_int = EACCES;
+
+abort:
+ vc_connect_done(data, err_int);
+
+ switch (err_int) {
+ case EACCES:
+ vpn_provider_indicate_error(data->provider,
+ VPN_PROVIDER_ERROR_AUTH_FAILED);
+ break;
+ case ECONNABORTED:
+ case ETIMEDOUT:
+ vpn_provider_indicate_error(data->provider,
+ VPN_PROVIDER_ERROR_UNKNOWN);
+ }
+}
+
+static int request_input_credentials(struct vc_private_data *data,
+ const char* dbus_sender)
+{
+ DBusMessage *message;
+ const char *path, *agent_sender, *agent_path;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ int err;
+ void *agent;
+
+ if (!data || !data->provider)
+ return -ENOENT;
+
+ DBG("data %p provider %p sender %s", data, data->provider, dbus_sender);
+
+ agent = connman_agent_get_info(dbus_sender, &agent_sender, &agent_path);
+ if (!agent || !agent_path)
+ return -ESRCH;
+
+ message = dbus_message_new_method_call(agent_sender, agent_path,
+ VPN_AGENT_INTERFACE,
+ "RequestInput");
+ if (!message)
+ return -ENOMEM;
+
+ dbus_message_iter_init_append(message, &iter);
+
+ path = vpn_provider_get_path(data->provider);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
+
+ connman_dbus_dict_open(&iter, &dict);
+
+ request_input_append_to_dict(data->provider, &dict,
+ request_input_append_password,
+ "VPNC.IPSec.Secret");
+ request_input_append_to_dict(data->provider, &dict,
+ request_input_append_mandatory,
+ "VPNC.Xauth.Username");
+ request_input_append_to_dict(data->provider, &dict,
+ request_input_append_password,
+ "VPNC.Xauth.Password");
+
+ vpn_agent_append_host_and_name(&dict, data->provider);
+
+ connman_dbus_dict_close(&iter, &dict);
+
+ err = connman_agent_queue_message(data->provider, message,
+ connman_timeout_input_request(),
+ request_input_credentials_reply, data, agent);
+
+ dbus_message_unref(message);
+
+ if (err < 0 && err != -EBUSY) {
+ DBG("error %d sending agent request", err);
+ return err;
+ }
+
+ return -EINPROGRESS;
+}
+
+static int vc_connect(struct vpn_provider *provider,
+ struct connman_task *task, const char *if_name,
+ vpn_provider_connect_cb_t cb, const char *dbus_sender,
+ void *user_data)
+{
+ struct vc_private_data *data;
+ const char *option;
+ bool username_set = false;
+ bool password_set = false;
+ bool ipsec_secret_set = false;
+ int err;
+
+ DBG("provider %p if_name %s user_data %p", provider, if_name, user_data);
+
+ option = vpn_provider_get_string(provider, "Host");
+ if (!option) {
+ connman_error("Host not set; cannot enable VPN");
+ return -EINVAL;
+ }
+
+ option = vpn_provider_get_string(provider, "VPNC.IPSec.ID");
+ if (!option) {
+ connman_error("Group not set; cannot enable VPN");
+ return -EINVAL;
+ }
+
+ option = vpn_provider_get_string(provider, "VPNC.IPSec.Secret");
+ if (option && *option && g_strcmp0(option, "-"))
+ ipsec_secret_set = true;
+
+ option = vpn_provider_get_string(provider, "VPNC.Xauth.Username");
+ if (option && *option && g_strcmp0(option, "-"))
+ username_set = true;
+
+ option = vpn_provider_get_string(provider, "VPNC.Xauth.Password");
+ if (option && *option && g_strcmp0(option, "-"))
+ password_set = true;
+
+ data = g_try_new0(struct vc_private_data, 1);
+ if (!data)
+ return -ENOMEM;
+
+ vpn_provider_set_plugin_data(provider, data);
+ data->provider = vpn_provider_ref(provider);
+ data->task = task;
+ data->if_name = g_strdup(if_name);
+ data->cb = cb;
+ data->user_data = user_data;
+
+ if (!ipsec_secret_set || !username_set || !password_set) {
+ err = request_input_credentials(data, dbus_sender);
+ if (err != -EINPROGRESS) {
+ vc_connect_done(data, ECONNABORTED);
+ vpn_provider_indicate_error(data->provider,
+ VPN_PROVIDER_ERROR_LOGIN_FAILED);
+ free_private_data(data);
+ }
+
+ return err;
+ }
+
+ return run_connect(data);
+}
+
static int vc_error_code(struct vpn_provider *provider, int exit_code)
{
switch (exit_code) {
@@ -384,16 +793,12 @@ static struct vpn_driver vpn_driver = {
static int vpnc_init(void)
{
- connection = connman_dbus_get_connection();
-
return vpn_register("vpnc", &vpn_driver, VPNC);
}
static void vpnc_exit(void)
{
vpn_unregister("vpnc");
-
- dbus_connection_unref(connection);
}
CONNMAN_PLUGIN_DEFINE(vpnc, "vpnc plugin", VERSION,
--
2.20.1