Commit 8da36635 authored by Jussi Laakkonen's avatar Jussi Laakkonen Committed by Santtu Lakkala

WIP: Major rehaul of openconnect plugin.

parent 3629617c
......@@ -49,23 +49,105 @@
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
struct {
const char *cm_opt;
const char *oc_opt;
char has_value;
const char *cm_opt;
const char *oc_opt;
bool has_value;
bool enabled;
} oc_options[] = {
{ "OpenConnect.NoCertCheck", "--no-cert-check", 0 },
/* --no-cert-check is disabled in openconnect 8.02 */
{ "OpenConnect.NoCertCheck", "--no-cert-check", 0, 0},
{ "OpenConnect.Syslog", "--syslog", 0, 1 },
{ "OpenConnect.DisableIPv6", "--disable-ipv6", 0, 1 },
{ "OpenConnect.ServerCert", "--servercert", 1, 1 },
{ "VPN.MTU", "--base-mtu", 1, 1 },
{ "OpenConnect.NoHTTPKeepalive", "--no-http-keepalive", 1, 1},
};
struct oc_private_data {
struct vpn_provider *provider;
struct connman_task *task;
char *if_name;
char *dbus_sender;
vpn_provider_connect_cb_t cb;
void *user_data;
int fd_in;
int out_ch_id;
int err_ch_id;
GIOChannel *out_ch;
GIOChannel *err_ch;
};
static void oc_connect_done(struct oc_private_data *data, int err)
{
DBG("data %p err %d/%s", data, err, strerror(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 close_io_channel(struct oc_private_data *data, GIOChannel *channel)
{
int id = 0;
if (!data || !channel)
return;
if (data->out_ch == channel) {
DBG("closing stdout");
id = data->out_ch_id;
data->out_ch = NULL;
data->out_ch_id = 0;
} else if (data->err_ch == channel) {
DBG("closing stderr");
id = data->err_ch_id;
data->err_ch = NULL;
data->err_ch_id = 0;
} else {
return;
}
if (id)
g_source_remove(id);
if (!channel) {
DBG("channel NULL");
return;
}
g_io_channel_shutdown(channel, FALSE, NULL);
g_io_channel_unref(channel);
DBG("done");
}
static void free_private_data(struct oc_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);
if (data->fd_in > 0)
close(data->fd_in);
data->fd_in = -1;
close_io_channel(data, data->out_ch);
close_io_channel(data, data->err_ch);
g_free(data->dbus_sender);
g_free(data->if_name);
g_free(data);
}
......@@ -73,18 +155,21 @@ static void free_private_data(struct oc_private_data *data)
static int task_append_config_data(struct vpn_provider *provider,
struct connman_task *task)
{
const char *option;
const char *option = NULL;
int i;
for (i = 0; i < (int)ARRAY_SIZE(oc_options); i++) {
if (!oc_options[i].oc_opt)
if (!oc_options[i].oc_opt || !oc_options[i].enabled)
continue;
option = vpn_provider_get_string(provider,
oc_options[i].cm_opt);
if (!option)
continue;
if (oc_options[i].has_value) {
option = vpn_provider_get_string(provider,
oc_options[i].cm_opt);
if (!option)
continue;
}
DBG("add argument: %s %s", oc_options[i].oc_opt, option);
if (connman_task_add_argument(task,
oc_options[i].oc_opt,
oc_options[i].has_value ? option : NULL) < 0)
......@@ -103,6 +188,11 @@ static int oc_notify(DBusMessage *msg, struct vpn_provider *provider)
char *netmask = NULL, *gateway = NULL;
unsigned char prefix_len = 0;
struct connman_ipaddress *ipaddress;
struct oc_private_data *data;
DBG("");
data = vpn_provider_get_plugin_data(provider);
dbus_message_iter_init(msg, &iter);
......@@ -111,11 +201,14 @@ static int oc_notify(DBusMessage *msg, struct vpn_provider *provider)
if (!provider) {
connman_error("No provider found");
oc_connect_done(data, ENOENT);
return VPN_STATE_FAILURE;
}
if (strcmp(reason, "connect"))
if (strcmp(reason, "connect")) {
oc_connect_done(data, EIO);
return VPN_STATE_DISCONNECT;
}
domain = g_strdup(vpn_provider_get_string(provider, "VPN.Domain"));
......@@ -214,65 +307,339 @@ static int oc_notify(DBusMessage *msg, struct vpn_provider *provider)
g_free(domain);
connman_ipaddress_free(ipaddress);
oc_connect_done(data, 0);
return VPN_STATE_CONNECT;
}
static int run_connect(struct vpn_provider *provider,
struct connman_task *task, const char *if_name,
vpn_provider_connect_cb_t cb, void *user_data)
static ssize_t full_write(int fd, const void *buf, size_t len)
{
ssize_t byte_write;
while (len) {
byte_write = write(fd, buf, len);
if (byte_write < 0) {
connman_error("failed to write config to openconnect: "
" %s\n", strerror(errno));
return byte_write;
}
len -= byte_write;
buf += byte_write;
}
return len;
}
static ssize_t write_data(int fd, const char *data)
{
const char *vpnhost, *vpncookie, *servercert, *mtu;
int fd, err = 0, len;
gchar *buf;
ssize_t len;
vpnhost = vpn_provider_get_string(provider, "OpenConnect.VPNHost");
if (!vpnhost)
vpnhost = vpn_provider_get_string(provider, "Host");
vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie");
servercert = vpn_provider_get_string(provider,
"OpenConnect.ServerCert");
if (!data || !*data)
return -1;
if (!vpncookie || !servercert) {
err = -EINVAL;
goto done;
buf = g_strdup_printf("%s\n", data);
DBG("writing data: %s", buf);
len = full_write(fd, buf, strlen(buf));
return len;
}
static void oc_died(struct connman_task *task, int exit_code, void *user_data)
{
struct oc_private_data *data = user_data;
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);
}
task_append_config_data(provider, task);
free_private_data(data);
}
static gboolean io_channel_out_cb(GIOChannel *source, GIOCondition condition,
gpointer user_data)
{
struct oc_private_data *data;
char *str;
data = user_data;
if (data->out_ch != source)
return G_SOURCE_REMOVE;
if ((condition & G_IO_IN) &&
g_io_channel_read_line(source, &str, NULL, NULL, NULL) ==
G_IO_STATUS_NORMAL) {
str[strlen(str) - 1] = '\0';
DBG("Cookie: %s", str);
/* Only cookie is printed to stdout */
vpn_provider_set_string_hide_value(data->provider,
"OpenConnect.Cookie", str);
g_free(str);
} else if (condition & (G_IO_ERR | G_IO_HUP)) {
DBG("Out channel termination");
close_io_channel(data, source);
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
static bool strv_contains_prefix(const char *strv[], const char *str)
{
int i;
if (!strv || !str || !*str)
return false;
for (i = 0; strv[i]; i++) {
if (g_str_has_prefix(str, strv[i]))
return true;
}
return false;
}
static gboolean io_channel_err_cb(GIOChannel *source, GIOCondition condition,
gpointer user_data)
{
struct oc_private_data *data;
const char *auth_failures[] = {
/* Login failed */
"Got HTTP response: HTTP/1.1 401 Unauthorized",
/* Cookie not valid */
"Got inappropriate HTTP CONNECT response: "
"HTTP/1.1 401 Unauthorized",
/* Invalid cookie */
"VPN service unavailable",
/* Problem with certificates */
"SSL connection failure",
"Creating SSL connection failed",
"SSL connection cancelled",
NULL
};
const char *conn_failures[] = {
"Failed to connect to",
"Failed to open HTTPS connection to",
"Failed to obtain WebVPN cookie",
NULL
};
const char *server_key_hash = " --servercert";
char *str;
data = user_data;
if (!data)
return G_SOURCE_REMOVE;
if (source && data->err_ch != source) {
DBG("invalid source");
return G_SOURCE_REMOVE;
}
if ((condition & G_IO_IN) &&
g_io_channel_read_line(source, &str, NULL, NULL, NULL) ==
G_IO_STATUS_NORMAL) {
str[strlen(str) - 1] = '\0';
DBG("str: %s", str);
if (g_str_has_prefix(str, server_key_hash)) {
int position = strlen(server_key_hash) + 1;
const char *allow_self_signed;
const char *response = "yes";
DBG("Set server key hash: \"%s\"", str + position);
vpn_provider_set_string(data->provider,
"OpenConnect.ServerCert",
str + position);
DBG("Need to verify self signed cert");
allow_self_signed = vpn_provider_get_string(
data->provider,
"OpenConnect.AllowSelfSignedCert");
if (!g_strcmp0(allow_self_signed, "true"))
response = "yes";
if (write_data(data->fd_in, response) != 0)
DBG("cannot write response to cert accept");
} else if (strv_contains_prefix(auth_failures, str)) {
DBG("authentication failed: %s", str);
vpn_provider_set_string_hide_value(data->provider,
"OpenConnect.Cookie", NULL);
vpn_provider_indicate_error(data->provider,
VPN_PROVIDER_ERROR_AUTH_FAILED);
oc_connect_done(data, ECONNABORTED);
} else if (strv_contains_prefix(conn_failures, str)) {
DBG("connection failed: %s", str);
vpn_provider_indicate_error(data->provider,
VPN_PROVIDER_ERROR_CONNECT_FAILED);
oc_connect_done(data, ECONNABORTED);
}
connman_task_add_argument(task, "--servercert", servercert);
g_free(str);
} else if (condition & (G_IO_ERR | G_IO_HUP)) {
DBG("Err channel termination");
close_io_channel(data, source);
return G_SOURCE_REMOVE;
}
mtu = vpn_provider_get_string(provider, "VPN.MTU");
return G_SOURCE_CONTINUE;
}
if (mtu)
connman_task_add_argument(task, "--mtu", (char *)mtu);
/* TODO: add also token support */
enum oc_connect_type {
OC_CONNECT_CREDENTIALS = 0,
OC_CONNECT_CERTIFICATE,
OC_CONNECT_COOKIE,
};
connman_task_add_argument(task, "--syslog", NULL);
connman_task_add_argument(task, "--cookie-on-stdin", NULL);
static int run_connect(struct oc_private_data *data)
{
struct vpn_provider *provider;
struct connman_task *task;
enum oc_connect_type connect_type;
const char *vpnhost;
const char *vpncookie;
const char *username;
const char *password;
const char *usergroup;
const char *certificate;
const char *credentials[] = {"OpenConnect.Username",
"OpenConnect.Password", NULL};
int fd_out;
int fd_err;
int err = 0;
int i;
if (!data)
return -EINVAL;
DBG("");
/* Default to plain credentials */
connect_type = OC_CONNECT_CREDENTIALS;
provider = data->provider;
task = data->task;
vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie");
if (vpncookie)
connect_type = OC_CONNECT_COOKIE;
switch (connect_type) {
case OC_CONNECT_CREDENTIALS:
username = vpn_provider_get_string(provider,
"OpenConnect.Username");
password = vpn_provider_get_string(provider,
"OpenConnect.Password");
if (!username || !password) {
err = EACCES;
goto done;
}
connman_task_add_argument(task, "--user", username);
connman_task_add_argument(task, "--passwd-on-stdin", NULL);
connman_task_add_argument(task, "--cookieonly", NULL);
break;
case OC_CONNECT_CERTIFICATE:
certificate = vpn_provider_get_string(provider,
"OpenConnect.UserCertificate");
if (certificate)
connman_task_add_argument(task, "--certificate",
certificate);
break;
case OC_CONNECT_COOKIE:
connman_task_add_argument(task, "--cookie-on-stdin", NULL);
break;
}
usergroup = vpn_provider_get_string(provider, "OpenConnect.Usergroup");
if (usergroup)
connman_task_add_argument(task, "--usergroup", usergroup);
vpnhost = vpn_provider_get_string(provider, "OpenConnect.VPNHost");
if (!vpnhost)
vpnhost = vpn_provider_get_string(provider, "Host");
task_append_config_data(provider, task);
connman_task_add_argument(task, "--script", SCRIPTDIR "/vpn-script");
connman_task_add_argument(task, "--interface", if_name);
connman_task_add_argument(task, "--interface", data->if_name);
connman_task_add_argument(task, (char *)vpnhost, NULL);
err = connman_task_run(task, vpn_died, provider,
&fd, NULL, NULL);
err = connman_task_run(task, oc_died, data, &data->fd_in, &fd_out,
&fd_err);
if (err < 0) {
connman_error("openconnect failed to start");
DBG("openconnect failed to start");
err = -EIO;
goto done;
}
len = strlen(vpncookie);
if (write(fd, vpncookie, len) != (ssize_t)len ||
write(fd, "\n", 1) != 1) {
connman_error("openconnect failed to take cookie on stdin");
err = -EIO;
goto done;
switch (connect_type) {
case OC_CONNECT_CREDENTIALS:
password = vpn_provider_get_string(data->provider,
"OpenConnect.Password");
DBG("writing password to openconnect");
if (write_data(data->fd_in, password) != 0) {
DBG("openconnect failed to take password on stdin");
err = -EIO;
}
break;
case OC_CONNECT_CERTIFICATE:
break;
case OC_CONNECT_COOKIE:
DBG("writing cookie to openconnect");
if (write_data(data->fd_in, vpncookie) != 0) {
DBG("openconnect failed to take cookie on stdin");
err = -EIO;
}
break;
}
if (err)
goto done;
err = -EINPROGRESS;
data->out_ch = g_io_channel_unix_new(fd_out);
data->out_ch_id = g_io_add_watch(data->out_ch,
G_IO_IN | G_IO_ERR | G_IO_HUP,
(GIOFunc)io_channel_out_cb, data);
data->err_ch = g_io_channel_unix_new(fd_err);
data->err_ch_id = g_io_add_watch(data->err_ch,
G_IO_IN | G_IO_ERR | G_IO_HUP,
(GIOFunc)io_channel_err_cb, data);
done:
if (cb)
cb(provider, user_data, err);
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;
}
......@@ -289,6 +656,9 @@ static void request_input_append_informational(DBusMessageIter *iter,
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);
}
......@@ -305,11 +675,63 @@ static void request_input_append_mandatory(DBusMessageIter *iter,
DBUS_TYPE_STRING, &str);
}
static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
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_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;
}
DBG("add key:%s str:%s", key, str);
connman_dbus_dict_append_dict(dict, key, function_cb,
str ? (void *)str : NULL);
}
static void request_credential_input_reply(DBusMessage *reply, void *user_data)
{
struct oc_private_data *data = user_data;
char *cookie = NULL, *servercert = NULL, *vpnhost = NULL;
char *key;
char *username = NULL, *password = NULL, *usergroup = NULL;
const char *key;
DBusMessageIter iter, dict;
int err;
......@@ -341,7 +763,9 @@ static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
dbus_message_iter_get_basic(&entry, &key);
if (g_str_equal(key, "OpenConnect.Cookie")) {
DBG("key: %s", key);
if (g_str_equal(key, "OpenConnect.Username")) {
dbus_message_iter_next(&entry);
if (dbus_message_iter_get_arg_type(&entry)
!= DBUS_TYPE_VARIANT)
......@@ -350,11 +774,12 @@ static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
if (dbus_message_iter_get_arg_type(&value)
!= DBUS_TYPE_STRING)
break;
dbus_message_iter_get_basic(&value, &cookie);
dbus_message_iter_get_basic(&value, &username);
vpn_provider_set_string_hide_value(data->provider,
key, cookie);
key, username);
DBG("username: %s", username);
} else if (g_str_equal(key, "OpenConnect.ServerCert")) {
} else if (g_str_equal(key, "OpenConnect.Password")) {
dbus_message_iter_next(&entry);
if (dbus_message_iter_get_arg_type(&entry)
!= DBUS_TYPE_VARIANT)
......@@ -363,11 +788,12 @@ static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
if (dbus_message_iter_get_arg_type(&value)
!= DBUS_TYPE_STRING)
break;
dbus_message_iter_get_basic(&value, &servercert);
vpn_provider_set_string(data->provider, key,
servercert);
dbus_message_iter_get_basic(&value, &password);
vpn_provider_set_string_hide_value(data->provider, key,
password);
DBG("password: %s", password);
} else if (g_str_equal(key, "OpenConnect.VPNHost")) {
} else if (g_str_equal(key, "OpenConnect.Usergroup")) {
dbus_message_iter_next(&entry);
if (dbus_message_iter_get_arg_type(&entry)
!= DBUS_TYPE_VARIANT)
......@@ -376,46 +802,43 @@ static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
if (dbus_message_iter_get_arg_type(&value)
!= DBUS_TYPE_STRING)
break;
dbus_message_iter_get_basic(&value, &vpnhost);
vpn_provider_set_string(data->provider, key, vpnhost);
dbus_message_iter_get_basic(&value, &usergroup);
vpn_provider_set_string(data->provider, key, usergroup);
}
dbus_message_iter_next(&dict);
}
if (!cookie || !servercert || !vpnhost)
if (!username || !password)
goto err;
run_connect(data->provider, data->task, data->if_name, data->cb,
data->user_data);
free_private_data(data);
err = run_connect(data);
if (err != -EINPROGRESS)
goto err;
return;
err:
vpn_provider_indicate_error(data->provider,
VPN_PROVIDER_ERROR_AUTH_FAILED);
oc_connect_done(data, EACCES);
out:
free_private_data(data);
}
static int request_cookie_input(struct vpn_provider *provider,
struct oc_private_data *data,
const char *dbus_sender)
static int request_credential_input(struct oc_private_data *data)
{
DBusMessage *message;
const char *path, *agent_sender, *agent_path;
DBusMessageIter iter;
DBusMessageIter dict;
const char *str;
int err;
void *agent;
agent = connman_agent_get_info(dbus_sender, &agent_sender,
&agent_path);
if (!provider || !agent || !agent_path)
DBG("");
agent = connman_agent_get_info(data->dbus_sender,
&agent_sender, &agent_path);
if (!data || !data->provider || !agent || !agent_path)
return -ESRCH;
message = dbus_message_new_method_call(agent_sender, agent_path,
......@@ -426,50 +849,50 @@ static int request_cookie_input(struct vpn_provider *provider,
dbus_message_iter_init_append(message, &iter);
path = vpn_provider_get_path(provider);
dbus_message_iter_append_basic(&iter,
DBUS_TYPE_OBJECT_PATH, &path);
path = vpn_provider_get_path(data->provider);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
connman_dbus_dict_open(&iter, &dict);
str = vpn_provider_get_string(provider, "OpenConnect.CACert");
if (str)