Send connman mailing list submissions to
        [email protected]

To subscribe or unsubscribe via email, send a message with subject or
body 'help' to
        [email protected]

You can reach the person managing the list at
        [email protected]

When replying, please edit your Subject line so it is more specific
than "Re: Contents of connman digest..."

Today's Topics:

   1. [PATCH v3 8/9] doc: Add new OpenConnect configuration options to VPN 
config format
      (Jussi Laakkonen)
   2. [PATCH v3 7/9] doc: Add new OpenConnect PKCS parameters to VPN agent API
      (Jussi Laakkonen)
   3. [PATCH v6 5/9] openconnect: Rewrite plugin to support more auth methods 
and protocols
      (Jussi Laakkonen)


----------------------------------------------------------------------

Date: Fri, 11 Oct 2019 15:52:58 +0300
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH v3 8/9] doc: Add new OpenConnect configuration options
        to VPN config format
To: [email protected]
Message-ID: <[email protected]>

Documented the new options implemented for OpenConnect and the possible
choices for authentication.
---

Changes since V2:
 * Change PKCS#12 to PKCS in settings and authentication type.

Changes since V3:
 * Rename pkcs12 to pkcs in example config.

 doc/vpn-config-format.txt | 75 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 71 insertions(+), 4 deletions(-)

diff --git a/doc/vpn-config-format.txt b/doc/vpn-config-format.txt
index b9d37fa4..13a88bc7 100644
--- a/doc/vpn-config-format.txt
+++ b/doc/vpn-config-format.txt
@@ -54,8 +54,9 @@ OpenConnect VPN supports following options (see 
openconnect(8) for details):
  OpenConnect.CACert     --cafile           File containing other Certificate
                                            Authorities in addition to the ones
                                            in the system trust database (O)
- OpenConnect.ClientCert --certificate      Client certificate file, if needed
-                                           by web authentication (O)
+ OpenConnect.ClientCert --certificate      Client certificate file, needed
+                                           by web authentication when AuthType
+                                           is set as "publickey" (O)
  VPN.MTU                --mtu              Request MTU from server as the MTU
                                            of the tunnel (O)
  OpenConnect.Cookie     --cookie-on-stdin  Cookie received as a result of the
@@ -68,8 +69,73 @@ OpenConnect VPN supports following options (see 
openconnect(8) for details):
                                            Only usable for extremely simple VPN
                                            configurations and should normally
                                            be set only via the VPN Agent API.
-If OpenConnect.Cookie or OpenConnect.ServerCert are missing, the VPN Agent will
-be contacted to supply the information.
+ OpenConnect.AllowSelfSignedCert none      Additional option to define if self
+                                           signed server certificates are
+                                           allowed. Boolean string and defaults
+                                           to false, value "true" enables the
+                                           option. Affects to the OpenConnect
+                                           internal function only: --servercert
+                                           is not added to startup parameters
+                                           and receiving self signed cert from
+                                           server terminates the connection if
+                                           set as false (or omitted) (O)
+ OpenConnect.AuthType                      Type of authentication used with
+                                           OpenConnect. Applicable values are
+                                           "cookie", "cookie_with_userpass",
+                                           "userpass", "publickey" and
+                                           "pkcs". Value "cookie" is basic
+                                           cookie based authentication. Value
+                                           "cookie_with_userpass" means that
+                                           credentials are used to retrieve the
+                                           connection cookie, which hides the
+                                           username from commandline. With
+                                           value "userpass" username and
+                                           password are used. Value "publickey"
+                                           requires CACert and UserPrivateKey
+                                           to be set. Value "pkcs" uses the
+                                           PKCSClientCert and requests password
+                                           input. Defaults to "cookie" (O)
+    cookie              --cookie-on-stdin  Default cookie based authentication
+    cookie_with_userpass                   Two phased connection, first
+        authentication: --cookieonly       authenticate with credentials then
+                        --passwd-on-stdin  use cookie for connection. Username
+                        --user             is hidden from commandline during
+        connection:     --cookie-on-stdin  connection.
+    userpass            --passwd-on-stdin  Credential based authentication,
+                        --user             username is visible on commandline.
+    publickey           --clientcert       Non-encrypted client certificate and
+                        --sslkey           private key file is used for auth.
+    pkcs                --cliencert        Authenticate with PKCS#1/PKCS#8/
+                                           PKCS#12 client certificate.
+ OpenConnect.DisableIPv6 --disable-ipv6    Do not ask for IPv6 connectivity.
+                                           Boolean string and defaults to
+                                           false, value "true" enables the
+                                           option (O)
+ OpenConnect.NoDTLS      --no-dtls         Disable DTLS and ESP (O)
+ OpenConnect.NoHTTPKeepalive --no-http-keepalive    Disable HTTP connection
+                                           re-use to workaround issues with
+                                           some servers. Boolean string and
+                                           defaults to false, value "true"
+                                           enables the option (O)
+ OpenConnect.PKCSClientCert --certificate  Certificate and private key in
+                                           a PKCS#1/PKCS#8/PKCS#12 structure.
+                                           Needed when AuthType is "pkcs" (O)
+ OpenConnect.Usergroup  --usergroup        Set login usergroup on remote server
+                                           (O)
+ OpenConnect.UserPrivateKey --sslkey       SSL private key file needed by web
+                                           authentication when AuthType is set
+                                           as "publickey" (O)
+
+The VPN agent will be contacted to supply the information based on the
+authentication type as follows:
+ Authentication type    Information requested       Saved with name
+ cookie                 OpenConnect.Cookie          OpenConnect.Cookie
+ cookie_with_userpass   Username                    OpenConnect.Username
+                        Password                    OpenConnect.Password
+ userpass               Username                    OpenConnect.Username
+                        Password                    OpenConnect.Password
+ publickey              <none>
+ pkcs                   OpenConnect.PKCSPassword    OpenConnect.PKCSPassword
 
 OpenVPN VPN supports following options (see openvpn(8) for details):
  Option name            OpenVPN option   Description
@@ -235,6 +301,7 @@ L2TP.User = username
 
 [provider_openconnect]
 Type = OpenConnect
+AuthType = pkcs
 Name = Connection to corporate network using Cisco VPN
 Host = 7.6.5.4
 Domain = corporate.com
-- 
2.20.1

------------------------------

Date: Fri, 11 Oct 2019 15:52:57 +0300
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH v3 7/9] doc: Add new OpenConnect PKCS parameters to
        VPN agent API
To: [email protected]
Message-ID: <[email protected]>

Add new OpenConnect VPN agent parameters to the API documentation.
Following are added:
 Name                           Requirement     OC authentication type
 OpenConnect.PKCSClientCert     informational   pkcs
 OpenConnect.PKCSPassword       mandatory       pkcs
---

Changes since V2:
 * Change PKCS#12 to PKCS and update description accordingly.

Changes since V3:
 * Just a bump for clarity.

 doc/vpn-agent-api.txt | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/doc/vpn-agent-api.txt b/doc/vpn-agent-api.txt
index 26c732d1..c27eddd5 100644
--- a/doc/vpn-agent-api.txt
+++ b/doc/vpn-agent-api.txt
@@ -85,6 +85,17 @@ Fields               string Username
                        Return the OpenConnect cookie value that is used for
                        authenticating the VPN session.
 
+               string OpenConnect.PKCSClientCert
+
+                       Informational field containing a PKCS#1/PKCS#8/PKCS#12
+                       URL or a path name for the PKCS#1/PKCS#8/PKCS#12 client
+                       certificate.
+
+               string OpenConnect.PKCSPassword
+
+                       Password for decrypting PKCS#8/PKCS#12 client
+                       certificate.
+
                string OpenConnect.ServerCert
 
                        Return the OpenConnect server hash used to identify
-- 
2.20.1

------------------------------

Date: Fri, 11 Oct 2019 15:52:55 +0300
From: Jussi Laakkonen <[email protected]>
Subject: [PATCH v6 5/9] openconnect: Rewrite plugin to support more
        auth methods and protocols
To: [email protected]
Cc: Santtu Lakkala <[email protected]>
Message-ID: <[email protected]>

Co-authored-by: Santtu Lakkala <[email protected]>

The purpose of this rewrite is to extend OpenConnect plugin to support
more authentication methods and support settings for them. Also VPN
agent use in the plugin is improved. In order to support self signed
certificate and PKCS pass phrase requests, and to improve detection
of errors stderr is monitored and reacted accordingly. Furthermore, all
the three protocols are supported via provider settings.

Following new option strings were added:
 NAME                                   TYPE (internal) VIA
 OpenConnect.AllowSelfSignedCert        string (bool)   Provider
 OpenConnect.AuthType                   string          Provider
 OpenConnect.DisableIPv6                string (bool)   Provider
 OpenConnect.NoDTLS                     string (bool)   Provider
 OpenConnect.NoHTTPKeepalive            string (bool)   Provider
 OpenConnect.PKCSClientCert             string (path)   Provider
 OpenConnect.PKCSPassword               string          Agent
 OpenConnect.Protocol                   string          Provider
 OpenConnect.Usergroup                  string          Provider
 OpenConnect.UserPrivateKey             string (path)   Provider
 VPN.MTU                                string (int)    Provider

The supported types for authentication types ("OpenConnect.AuthType")
are:
 - Cookie:                                      "cookie"
 - Use cookie after username and password auth: "cookie_with_userpass"
 - Username and password:                       "userpass"
 - Certificate using public key:                "publickey"
 - Use PKCS#1/#8/#12 file:                      "pkcs"

Without defining the authentication type, OpenConnect defaults to "cookie".
The value of the "OpenConnect.AuthType" is saved to provider config and
resulting provider settings file.

In case the "cookie_with_userpass" is used as authentication method
first the username and password are used to get the OpenConnect cookie
(only authentication is performed, OpenConnect is run with --cookieonly)
and then the resulting cookie is used in the next connect. Only with
this authentication method stdout is monitored for cookie string.

With "pkcs" the password is queried via agent for the PKCS file. This
is implemented as separate from the public key authentication as the
separation of the actions required for the authentication are more clear
clear this way. PKCS decryption errors are sent as priority data to
stderr, which must be read in order to detect it. It is imperative to
close the io channel as well since OpenConnect will write a new PKCS
pass phrase request to stderr as well, without newline, which will block
until timeout. It is better to close the channel in case of decryption
error to avoid connman-vpnd freezing.

Username, Password and PKCSPassword are to be retrieved via VPN agent.
These are saved as hidden provider values and are not stored to disk.

ClientCert, UserPrivateKey and PKCSClienCert are all paths to the
corresponding files on local filesystem.

Authentication failures are sent to VPN agent to indicate that saved
credentials should be cleared. For this the changes to auth and
connection error tracking in vpn-provider.c, and the changes to
vpn-agent.c are utilized.

In order to be compatible with the previous version of the plugin the
default authentication, "cookie" is left as is from VPN agent part
except the VPN server after authentication is set as optional. With
other authentication methods server SHA1 fingerprint and VPN server
after authentication are to be set into provider settings.

Option "AllowSelfSignedCert" can be set as string "true" or "false".
The value defaults "false" if value is omitted or invalid. Self signed
server certificates have to be explicitly allowed. If the value is false
the --servercert reported by OpenConnect via stderr is not saved to
provider settings and connection is terminated. Followed by this
OpenConnect requests a reply to indicate that the certificate needs to
be approved, or denied, and depending on the option, answer is written
to stdin ("yes") if self signed certs are allowed and otherwise the
connection is terminated. In case the value is false the task parameter
"--servercert" is also omitted when running the task. In case the value
is true, the self signed certificate is saved, if returned by the
OpenConnect client and the server certificate SHA1 fingerprint is stored
to OpenConnect.ServerCert in provider settings.

The following were added to OpenConnect startup parameters if defined in
provider settings as "true".

PROVIDER SETTING                PARAMETER
OpenConnect.DisableIPv6         --disable-ipv6
OpenConnect.NoDTLS              --no-dtls
OpenConnect.NoHTTPKeepalive     --no-http-keepalive
VPN.MTU                         --base-mtu

Additionally, OpenConnect is set to use syslog for all connection related
output with --syslog. This is because with syslog enabled only the
cookie is delivered via stdout.

All the three different protocols supported by OpenConnect can be
selected with provider setting OpenConnect.Protocol.

VALUE                   DEFINITION
anyconnect              Cisco AnyConnect (default)
nc                      Juniper Network Connect / Pulse Secure
gp                      Palo Alto Networks GlobalProtect
---

Changes since V2:
 * Use stdout only when cookie is to be read from it.
 * React to "Failed to obtain VPN cookie" as an authentication error.
 * Reorder request_input_append() input vars to be more clear.
 * Fix cookie use with VPN agent, retrieve from VPN agent if cookie is "-".
 * Do not use OpenConnect.VPNHost if it is empty string.
 * Save OpenConnect.VPNHost in provider settings.

Changes since V3:
 * Forgot to add Santtu to the V2 of patches as co-author, added now.

Changes since V4:
 * Replace PKCS#12 with PKCS already in this commit for clarity.
 * Improve setting use and saving to be more consistent and sort the options.
 * Replace unnecessary save group retrieval with one vpn_provider_get_string.

Changes since V5:
 * Remove unnecessary parameter setting in run_connect() - these were set to
   be handled with V4 of this commit with task_append_config_data().

Changes since V6:
 * Add fallthrough in oc_error_code().
 * Use stdout only when cookie is really to be read. No use to open stdout
   when cookie has been already written to stdin.

 vpn/plugins/openconnect.c | 1094 +++++++++++++++++++++++++++++++------
 1 file changed, 941 insertions(+), 153 deletions(-)

diff --git a/vpn/plugins/openconnect.c b/vpn/plugins/openconnect.c
index 21224c4a..6657a1f3 100644
--- a/vpn/plugins/openconnect.c
+++ b/vpn/plugins/openconnect.c
@@ -3,6 +3,7 @@
  *  ConnMan VPN daemon
  *
  *  Copyright (C) 2007-2013  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2019  Jolla Ltd. 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 version 2 as
@@ -48,24 +49,134 @@
 
 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
 
+enum opt_type {
+       OPT_STRING      = 0,
+       OPT_BOOL        = 1,
+};
+
 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; // Use as task parameter
+       enum opt_type   type;
 } oc_options[] = {
-       { "OpenConnect.NoCertCheck", "--no-cert-check", 0 },
+       { "OpenConnect.AllowSelfSignedCert", NULL, 1, 0, OPT_BOOL},
+       { "OpenConnect.AuthType", NULL, 1, 0, OPT_STRING},
+       { "OpenConnect.CACert", "--cafile", 1, 1, OPT_STRING},
+       { "OpenConnect.ClientCert", NULL, 1, 0, OPT_STRING},
+       { "OpenConnect.DisableIPv6", "--disable-ipv6", 1, 1, OPT_BOOL},
+       { "OpenConnect.PKCSClientCert", NULL, 1, 0, OPT_STRING},
+       { "OpenConnect.Protocol", "--protocol", 1, 1, OPT_STRING},
+       /* --no-cert-check is disabled in openconnect 8.02 */
+       { "OpenConnect.NoCertCheck", "--no-cert-check", 0, 0, OPT_BOOL},
+       { "OpenConnect.NoHTTPKeepalive", "--no-http-keepalive", 1, 1, OPT_BOOL},
+       { "OpenConnect.NoDTLS", "--no-dtls", 1, 1, OPT_BOOL},
+       { "OpenConnect.ServerCert", "--servercert", 1, 1, OPT_STRING},
+       { "OpenConnect.Usergroup", "--usergroup", 1, 1, OPT_STRING},
+       { "OpenConnect.UserPrivateKey", NULL, 1, 0, OPT_STRING},
+       { "VPN.MTU", "--base-mtu", 1, 1, OPT_STRING},
 };
 
+enum oc_connect_type {
+       OC_CONNECT_COOKIE = 0,
+       OC_CONNECT_COOKIE_WITH_USERPASS,
+       OC_CONNECT_USERPASS,
+       OC_CONNECT_PUBLICKEY,
+       OC_CONNECT_PKCS,
+};
+
+static const char *connect_types[] = {"cookie", "cookie_with_userpass",
+                       "userpass", "publickey", "pkcs", NULL};
+static const char *protocols[] = { "anyconnect", "nc", "gp", NULL};
+
 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;
+       enum oc_connect_type connect_type;
 };
 
+static bool is_valid_protocol(const char* protocol)
+{
+       if (!protocol || !*protocol)
+               return false;
+
+       return g_strv_contains(protocols, protocol);
+}
+
+static void oc_connect_done(struct oc_private_data *data, int err)
+{
+       connman_info("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;
+
+       connman_info("data %p channel %p", data, channel);
+
+       if (!data || !channel)
+               return;
+
+       if (data->out_ch == channel) {
+               id = data->out_ch_id;
+               data->out_ch = NULL;
+               data->out_ch_id = 0;
+       } else if (data->err_ch == channel) {
+               id = data->err_ch_id;
+               data->err_ch = NULL;
+               data->err_ch_id = 0;
+       } else {
+               return;
+       }
+
+       if (id)
+               g_source_remove(id);
+
+       g_io_channel_shutdown(channel, FALSE, NULL);
+       g_io_channel_unref(channel);
+}
+
 static void free_private_data(struct oc_private_data *data)
 {
+       connman_info("data %p", data);
+
+       if (!data || !data->provider)
+               return;
+
+       connman_info("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,17 +184,51 @@ 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;
+
+                       /* Add boolean type values only if set as true. */
+                       if (oc_options[i].type == OPT_BOOL) {
+                               if (!vpn_provider_get_boolean(provider,
+                                                       oc_options[i].cm_opt,
+                                                       false))
+                                       continue;
+
+                               /* No option is set for boolean type values. */
+                               option = NULL;
+                       }
+
+                       /* Skip protocol if it is invalid. */
+                       if (!g_strcmp0(oc_options[i].cm_opt,
+                                               "OpenConnect.Protocol")) {
+                               if (!is_valid_protocol(option))
+                                       continue;
+                       }
+               }
+
+               /*
+                * Add server certificate fingerprint only when self signed
+                * certificates are explicitly allowed. Using --servercert as
+                * parameter will accept any server with matching fingerprint,
+                * which would disregard the setting of AllowSelfSignedCert.
+                */
+               if (!g_strcmp0(oc_options[i].cm_opt,
+                                       "OpenConnect.ServerCert")) {
+                       if (!vpn_provider_get_boolean(provider,
+                                       "OpenConnect.AllowSelfSignedCert",
+                                       false))
+                               continue;
+               }
 
                if (connman_task_add_argument(task,
                                oc_options[i].oc_opt,
@@ -103,6 +248,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;
+
+       connman_info("provider %p", provider);
+
+       data = vpn_provider_get_plugin_data(provider);
 
        dbus_message_iter_init(msg, &iter);
 
@@ -111,6 +261,7 @@ 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;
        }
 
@@ -214,106 +365,556 @@ 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)
 {
-       const char *vpnhost, *vpncookie, *servercert, *mtu;
-       int fd, err = 0, 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;
+       }
 
-       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");
+       return len;
+}
 
-       if (!vpncookie || !servercert) {
-               err = -EINVAL;
-               goto done;
+static ssize_t write_data(int fd, const char *data)
+{
+       gchar *buf;
+       ssize_t len;
+
+       if (!data || !*data)
+               return -1;
+
+       buf = g_strdup_printf("%s\n", data);
+
+       len = full_write(fd, buf, strlen(buf));
+
+       g_free(buf);
+
+       return len;
+}
+
+static void oc_died(struct connman_task *task, int exit_code, void *user_data)
+{
+       struct oc_private_data *data = user_data;
+
+       connman_info("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);
+}
 
-       connman_task_add_argument(task, "--servercert", servercert);
+static gboolean io_channel_out_cb(GIOChannel *source, GIOCondition condition,
+                       gpointer user_data)
+{
+       struct oc_private_data *data;
+       char *str;
 
-       mtu = vpn_provider_get_string(provider, "VPN.MTU");
+       data = user_data;
 
-       if (mtu)
-               connman_task_add_argument(task, "--mtu", (char *)mtu);
+       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) {
+
+               g_strchomp(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)) {
+               connman_info("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 void clear_provider_credentials(struct vpn_provider *provider)
+{
+       const char *keys[] = { "OpenConnect.Username",
+                               "OpenConnect.Password",
+                               "OpenConnect.PKCSPassword",
+                               "OpenConnect.Cookie",
+                               NULL
+       };
+       int i;
+
+       connman_info("provider %p", provider);
+
+       for (i = 0; keys[i]; i++) {
+               if (!vpn_provider_get_string_immutable(provider, keys[i]))
+                       vpn_provider_set_string_hide_value(provider, keys[i],
+                                               "-");
+       }
+}
+
+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",
+                               "Failed to obtain WebVPN cookie",
+                               /* 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",
+                               NULL
+       };
+       const char *pkcs_failure = "Failed to decrypt PKCS";
+       const char *server_key_hash = "    --servercert";
+       char *str;
+       bool close = false;
+       int err = 0;
+
+       data = user_data;
+
+       if (!data)
+               return G_SOURCE_REMOVE;
+
+       if (source && data->err_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';
+
+               connman_info("openconnect: %s", str);
+
+               if (g_str_has_prefix(str, server_key_hash)) {
+                       int position;
+                       bool allow_self_signed;
+
+                       allow_self_signed = vpn_provider_get_boolean(
+                                       data->provider,
+                                       "OpenConnect.AllowSelfSignedCert",
+                                       false);
+
+                       if (allow_self_signed) {
+                               position = strlen(server_key_hash) + 1;
+
+                               connman_info("Set server key hash: \"%s\"",
+                                                       str + position);
+
+                               vpn_provider_set_string(data->provider,
+                                               "OpenConnect.ServerCert",
+                                               str + position);
+
+                               /*
+                                * OpenConnect waits for "yes" or "no" as
+                                * response to certificate acceptance request.
+                                */
+                               if (write_data(data->fd_in, "yes") != 0)
+                                       connman_error("cannot write answer to "
+                                               "certificate accept request");
+
+                       } else {
+                               connman_warn("Self signed certificate is not "
+                                                       " allowed");
+
+                               /*
+                                * Close IO channel to avoid deadlock as an
+                                * answer is expected for the certificate
+                                * accept request.
+                                */
+                               close = true;
+                               err = ECONNREFUSED;
+                       }
+               } else if (g_str_has_prefix(str, pkcs_failure)) {
+                       connman_warn("invalid PKCS password: %s", str);
+                       /*
+                        * Close IO channel to avoid deadlock. This is because
+                        * after PKCS file decryption fails OpenConnect will
+                        * wait for a new pass phrase to be written to stdin,
+                        * blocking vpnd as well. Instead of waiting for a
+                        * timeout it is better to close the IO channel here.
+                        */
+                       close = true;
+                       err = EACCES;
+               } else if (strv_contains_prefix(auth_failures, str)) {
+                       connman_warn("authentication failed: %s", str);
+                       err = EACCES;
+               } else if (strv_contains_prefix(conn_failures, str)) {
+                       connman_warn("connection failed: %s", str);
+                       err = ECONNREFUSED;
+               }
+
+               g_free(str);
+       } else if (condition & (G_IO_ERR | G_IO_HUP)) {
+               connman_info("Err channel termination");
+               close = true;
+       }
+
+       if (err) {
+               switch (err) {
+               case EACCES:
+                       clear_provider_credentials(data->provider);
+                       break;
+               case ECONNREFUSED:
+                       /*
+                        * This will trigger VPN_PROVIDER_ERROR_CONNECT_FAILED
+                        * in vpn-provider.c:connect_cb().
+                        */
+               default:
+                       break;
+               }
+
+               oc_connect_done(data, err);
+       }
+
+       if (close) {
+               close_io_channel(data, source);
+               return G_SOURCE_REMOVE;
+       }
+
+       return G_SOURCE_CONTINUE;
+}
+
+static int run_connect(struct oc_private_data *data)
+{
+       struct vpn_provider *provider;
+       struct connman_task *task;
+       const char *vpnhost;
+       const char *vpncookie = NULL;
+       const char *username;
+       const char *password = NULL;
+       const char *certificate = NULL;
+       const char *private_key;
+       bool use_stdout = false;
+       int fd_out;
+       int fd_err;
+       int err = 0;
+
+       if (!data)
+               return -EINVAL;
+
+       provider = data->provider;
+       task = data->task;
+
+       connman_info("provider %p task %p", provider, task);
+
+       switch (data->connect_type) {
+       case OC_CONNECT_COOKIE:
+               vpncookie = vpn_provider_get_string(provider,
+                                       "OpenConnect.Cookie");
+               if (!vpncookie || !g_strcmp0(vpncookie, "-")) {
+                       err = -EACCES;
+                       goto done;
+               }
+
+               connman_task_add_argument(task, "--cookie-on-stdin", NULL);
+               break;
+       case OC_CONNECT_COOKIE_WITH_USERPASS:
+               vpncookie = vpn_provider_get_string(provider,
+                                       "OpenConnect.Cookie");
+               /* No cookie set yet, username and password used first */
+               if (!vpncookie || !g_strcmp0(vpncookie, "-")) {
+                       username = vpn_provider_get_string(provider,
+                                               "OpenConnect.Username");
+                       password = vpn_provider_get_string(provider,
+                                               "OpenConnect.Password");
+                       if (!username || !password ||
+                                               !g_strcmp0(username, "-") ||
+                                               !g_strcmp0(password, "-")) {
+                               err = -EACCES;
+                               goto done;
+                       }
+
+                       connman_task_add_argument(task, "--cookieonly", NULL);
+                       connman_task_add_argument(task, "--user", username);
+                       connman_task_add_argument(task, "--passwd-on-stdin",
+                                               NULL);
+
+                       /* Use stdout only when cookie is to be read. */
+                       use_stdout = true;
+               } else {
+                       connman_task_add_argument(task, "--cookie-on-stdin",
+                                               NULL);
+               }
+
+               break;
+       case OC_CONNECT_USERPASS:
+               username = vpn_provider_get_string(provider,
+                                       "OpenConnect.Username");
+               password = vpn_provider_get_string(provider,
+                                       "OpenConnect.Password");
+               if (!username || !password || !g_strcmp0(username, "-") ||
+                                       !g_strcmp0(password, "-")) {
+                       err = -EACCES;
+                       goto done;
+               }
+
+               connman_task_add_argument(task, "--user", username);
+               connman_task_add_argument(task, "--passwd-on-stdin", NULL);
+               break;
+       case OC_CONNECT_PUBLICKEY:
+               certificate = vpn_provider_get_string(provider,
+                                       "OpenConnect.ClientCert");
+               private_key = vpn_provider_get_string(provider,
+                                       "OpenConnect.UserPrivateKey");
+
+               if (!certificate || !private_key) {
+                       err = -EACCES;
+                       goto done;
+               }
+
+               connman_task_add_argument(task, "--certificate", certificate);
+               connman_task_add_argument(task, "--sslkey", private_key);
+               break;
+       case OC_CONNECT_PKCS:
+               certificate = vpn_provider_get_string(provider,
+                                       "OpenConnect.PKCSClientCert");
+               password = vpn_provider_get_string(data->provider,
+                                       "OpenConnect.PKCSPassword");
+               if (!certificate || !password) {
+                       err = -EACCES;
+                       goto done;
+               }
+
+               connman_task_add_argument(task, "--certificate", certificate);
+               connman_task_add_argument(task, "--passwd-on-stdin", NULL);
+               break;
+       }
+
+       vpnhost = vpn_provider_get_string(provider, "OpenConnect.VPNHost");
+       if (!vpnhost || !*vpnhost)
+               vpnhost = vpn_provider_get_string(provider, "Host");
+
+       task_append_config_data(provider, task);
+
+       connman_task_add_argument(task, "--non-inter", NULL);
+
+       /* Output all to syslog to output cookie only to stdout */
        connman_task_add_argument(task, "--syslog", NULL);
-       connman_task_add_argument(task, "--cookie-on-stdin", NULL);
 
        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, use_stdout ?
+                               &fd_out : NULL, &fd_err);
        if (err < 0) {
-               connman_error("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;
+       switch (data->connect_type) {
+       case OC_CONNECT_COOKIE:
+               if (write_data(data->fd_in, vpncookie) != 0) {
+                       connman_error("openconnect failed to take cookie on "
+                                               "stdin");
+                       err = -EIO;
+               }
+
+               break;
+       case OC_CONNECT_USERPASS:
+               if (write_data(data->fd_in, password) != 0) {
+                       connman_error("openconnect failed to take password on "
+                                               "stdin");
+                       err = -EIO;
+               }
+
+               break;
+       case OC_CONNECT_COOKIE_WITH_USERPASS:
+               if (!vpncookie || !g_strcmp0(vpncookie, "-")) {
+                       if (write_data(data->fd_in, password) != 0) {
+                               connman_error("openconnect failed to take "
+                                                       "password on stdin");
+                               err = -EIO;
+                       }
+               } else {
+                       if (write_data(data->fd_in, vpncookie) != 0) {
+                               connman_error("openconnect failed to take "
+                                                       "cookie on stdin");
+                               err = -EIO;
+                       }
+               }
+
+               break;
+       case OC_CONNECT_PUBLICKEY:
+               break;
+       case OC_CONNECT_PKCS:
+               if (write_data(data->fd_in, password) != 0) {
+                       connman_error("openconnect failed to take PKCS "
+                                               "password on stdin");
+                       err = -EIO;
+               }
+
+               break;
+       }
+
+       if (err) {
+               if (fd_out > 0)
+                       close(fd_out);
+
+               if (fd_err > 0)
+                       close(fd_err);
+
                goto done;
        }
 
+       err = -EINPROGRESS;
+
+       if (use_stdout) {
+               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 | G_IO_PRI,
+                               (GIOFunc)io_channel_err_cb, data);
+
 done:
-       if (cb)
-               cb(provider, user_data, err);
+       clear_provider_credentials(data->provider);
 
        return err;
 }
 
-static void request_input_append_informational(DBusMessageIter *iter,
-               void *user_data)
+static void request_input_append(DBusMessageIter *iter,
+               const char *str_type, const char *str, void *user_data)
 {
-       const char *str;
+       const char *string;
 
-       str = "string";
-       connman_dbus_dict_append_basic(iter, "Type", DBUS_TYPE_STRING, &str);
+       connman_dbus_dict_append_basic(iter, "Type",
+                               DBUS_TYPE_STRING, &str_type);
+       connman_dbus_dict_append_basic(iter, "Requirement",
+                               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);
+       string = user_data;
+       connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING,
+                               &string);
+}
+
+static void request_input_append_informational(DBusMessageIter *iter,
+               void *user_data)
+{
+       request_input_append(iter, "string", "informational", user_data);
 }
 
 static void request_input_append_mandatory(DBusMessageIter *iter,
                void *user_data)
 {
-       char *str = "string";
+       request_input_append(iter, "string", "mandatory", user_data);
+}
 
-       connman_dbus_dict_append_basic(iter, "Type",
-                               DBUS_TYPE_STRING, &str);
-       str = "mandatory";
-       connman_dbus_dict_append_basic(iter, "Requirement",
-                               DBUS_TYPE_STRING, &str);
+static void request_input_append_optional(DBusMessageIter *iter,
+               void *user_data)
+{
+       request_input_append(iter, "string", "optional", user_data);
+}
+
+static void request_input_append_password(DBusMessageIter *iter,
+               void *user_data)
+{
+       request_input_append(iter, "password", "mandatory", user_data);
+}
+
+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);
+       /* Ignore empty informational content */
+       if (!str && function_cb == request_input_append_informational)
+               return;
+
+       /* 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,
+                               str ? (void *)str : NULL);
 }
 
-static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
+static void request_input_credentials_reply(DBusMessage *reply, void 
*user_data)
 {
        struct oc_private_data *data = user_data;
        char *cookie = NULL, *servercert = NULL, *vpnhost = NULL;
-       char *key;
+       const char *username = NULL, *password = NULL, *pkcspassword = NULL;
+       const char *key;
        DBusMessageIter iter, dict;
        int err;
 
-       DBG("provider %p", data->provider);
+       connman_info("provider %p", data->provider);
 
        if (!reply)
                goto err;
@@ -353,7 +954,6 @@ static void request_input_cookie_reply(DBusMessage *reply, 
void *user_data)
                        dbus_message_iter_get_basic(&value, &cookie);
                        vpn_provider_set_string_hide_value(data->provider,
                                        key, cookie);
-
                } else if (g_str_equal(key, "OpenConnect.ServerCert")) {
                        dbus_message_iter_next(&entry);
                        if (dbus_message_iter_get_arg_type(&entry)
@@ -378,44 +978,99 @@ static void request_input_cookie_reply(DBusMessage 
*reply, void *user_data)
                                break;
                        dbus_message_iter_get_basic(&value, &vpnhost);
                        vpn_provider_set_string(data->provider, key, vpnhost);
+               } else if (g_str_equal(key, "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_hide_value(data->provider,
+                                       "OpenConnect.Username", username);
+               } else if (g_str_equal(key, "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,
+                                       "OpenConnect.Password", password);
+               } else if (g_str_equal(key, "OpenConnect.PKCSPassword")) {
+                       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, &pkcspassword);
+                       vpn_provider_set_string_hide_value(data->provider, key,
+                                               pkcspassword);
                }
 
                dbus_message_iter_next(&dict);
        }
 
-       if (!cookie || !servercert || !vpnhost)
-               goto err;
-
-       run_connect(data->provider, data->task, data->if_name, data->cb,
-               data->user_data);
+       switch (data->connect_type) {
+       case OC_CONNECT_COOKIE:
+               if (!cookie)
+                       goto err;
+
+               break;
+       case OC_CONNECT_USERPASS:
+               /* fall through */
+       case OC_CONNECT_COOKIE_WITH_USERPASS:
+               if (!username || !password)
+                       goto err;
+
+               break;
+       case OC_CONNECT_PUBLICKEY:
+               break; // This should not be reached.
+       case OC_CONNECT_PKCS:
+               if (!pkcspassword)
+                       goto err;
+
+               break;
+       }
 
-       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_input_credentials(struct oc_private_data *data)
 {
        DBusMessage *message;
-       const char *path, *agent_sender, *agent_path;
+       const char *path, *agent_sender, *agent_path, *username;
        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)
+       if (!data)
+               return -ESRCH;
+
+       connman_info("provider %p", data->provider);
+
+       agent = connman_agent_get_info(data->dbus_sender,
+                               &agent_sender, &agent_path);
+       if (!data->provider || !agent || !agent_path)
                return -ESRCH;
 
        message = dbus_message_new_method_call(agent_sender, agent_path,
@@ -426,120 +1081,244 @@ 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)
-               connman_dbus_dict_append_dict(&dict, "OpenConnect.CACert",
+       request_input_append_to_dict(data->provider, &dict,
                                request_input_append_informational,
-                               (void *)str);
-
-       str = vpn_provider_get_string(provider, "OpenConnect.ClientCert");
-       if (str)
-               connman_dbus_dict_append_dict(&dict, "OpenConnect.ClientCert",
+                               "OpenConnect.CACert");
+
+       /*
+        * For backwards compatibility add OpenConnect.ServerCert and
+        * OpenConnect.VPNHost as madnatory only in the default authentication
+        * mode. Otherwise. add the fields as informational. These should be
+        * set in provider settings and not to be queried with every connection
+        * attempt.
+        */
+       request_input_append_to_dict(data->provider, &dict,
+                               data->connect_type == OC_CONNECT_COOKIE ?
+                               request_input_append_optional :
                                request_input_append_informational,
-                               (void *)str);
+                               "OpenConnect.ServerCert");
 
-       connman_dbus_dict_append_dict(&dict, "OpenConnect.ServerCert",
-                       request_input_append_mandatory, NULL);
-
-       connman_dbus_dict_append_dict(&dict, "OpenConnect.VPNHost",
-                       request_input_append_mandatory, NULL);
+       request_input_append_to_dict(data->provider, &dict,
+                               data->connect_type == OC_CONNECT_COOKIE ?
+                               request_input_append_optional :
+                               request_input_append_informational,
+                               "OpenConnect.VPNHost");
+
+       if (vpn_provider_get_authentication_errors(data->provider))
+               vpn_agent_append_auth_failure(&dict, data->provider, NULL);
+
+       switch (data->connect_type) {
+       case OC_CONNECT_COOKIE:
+               request_input_append_to_dict(data->provider, &dict,
+                                       request_input_append_mandatory,
+                                       "OpenConnect.Cookie");
+               break;
+       /*
+        * The authentication is done with username and password to get the
+        * cookie for connection.
+        */
+       case OC_CONNECT_COOKIE_WITH_USERPASS:
+               /* fallthrough */
+       case OC_CONNECT_USERPASS:
+               username = vpn_provider_get_string(data->provider,
+                                       "OpenConnect.Username");
+               vpn_agent_append_user_info(&dict, data->provider, username);
+               break;
+       case OC_CONNECT_PUBLICKEY:
+               return -EINVAL;
+       case OC_CONNECT_PKCS:
+               request_input_append_to_dict(data->provider, &dict,
+                               request_input_append_informational,
+                               "OpenConnect.PKCSClientCert");
 
-       connman_dbus_dict_append_dict(&dict, "OpenConnect.Cookie",
-                       request_input_append_mandatory, NULL);
+               request_input_append_to_dict(data->provider, &dict,
+                                       request_input_append_password,
+                                       "OpenConnect.PKCSPassword");
+               break;
+       }
 
-       vpn_agent_append_host_and_name(&dict, provider);
+       vpn_agent_append_host_and_name(&dict, data->provider);
 
        connman_dbus_dict_close(&iter, &dict);
 
-       err = connman_agent_queue_message(provider, message,
+       err = connman_agent_queue_message(data->provider, message,
                        connman_timeout_input_request(),
-                       request_input_cookie_reply, data, agent);
+                       request_input_credentials_reply, data, agent);
 
-       if (err < 0 && err != -EBUSY) {
-               DBG("error %d sending agent request", err);
-               dbus_message_unref(message);
+       dbus_message_unref(message);
 
+       if (err < 0 && err != -EBUSY) {
+               connman_error("cannot send agent request, error: %d", err);
                return err;
        }
 
-       dbus_message_unref(message);
-
        return -EINPROGRESS;
 }
 
+static enum oc_connect_type get_authentication_type(
+                       struct vpn_provider *provider)
+{
+       const char *auth;
+       enum oc_connect_type type;
+
+       auth = vpn_provider_get_string(provider, "OpenConnect.AuthType");
+       if (!auth)
+               goto out;
+
+       for (type = 0; connect_types[type]; type++) {
+               if (!g_strcmp0(auth, connect_types[type])) {
+                       connman_info("auth type %d/%s", type,
+                                               connect_types[type]);
+                       return type;
+               }
+       }
+
+out:
+       /* Default to cookie */
+       return OC_CONNECT_COOKIE;
+}
+
 static int oc_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)
 {
-       const char *vpnhost, *vpncookie, *servercert;
+       struct oc_private_data *data;
+       const char *vpnhost, *vpncookie, *certificate, *username, *password;
+       const char *private_key;
        int err;
 
+       connman_info("provider %p task %p", provider, task);
+
        vpnhost = vpn_provider_get_string(provider, "Host");
        if (!vpnhost) {
                connman_error("Host not set; cannot enable VPN");
                return -EINVAL;
        }
 
-       vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie");
-       servercert = vpn_provider_get_string(provider,
-                       "OpenConnect.ServerCert");
-       if (!vpncookie || !servercert) {
-               struct oc_private_data *data;
-
-               data = g_try_new0(struct oc_private_data, 1);
-               if (!data)
-                       return -ENOMEM;
-
-               data->provider = provider;
-               data->task = task;
-               data->if_name = g_strdup(if_name);
-               data->cb = cb;
-               data->user_data = user_data;
-
-               err = request_cookie_input(provider, data, dbus_sender);
-               if (err != -EINPROGRESS) {
-                       vpn_provider_indicate_error(data->provider,
-                                       VPN_PROVIDER_ERROR_LOGIN_FAILED);
+       data = g_try_new0(struct oc_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->dbus_sender = g_strdup(dbus_sender);
+       data->cb = cb;
+       data->user_data = user_data;
+       data->connect_type = get_authentication_type(provider);
+
+       switch (data->connect_type) {
+       case OC_CONNECT_COOKIE:
+               vpncookie = vpn_provider_get_string(provider,
+                                       "OpenConnect.Cookie");
+               if (!vpncookie || !g_strcmp0(vpncookie, "-"))
+                       goto request_input;
+
+               break;
+       case OC_CONNECT_USERPASS:
+               username = vpn_provider_get_string(provider,
+                                       "OpenConnect.Username");
+               password = vpn_provider_get_string(provider,
+                                       "OpenConnect.Password");
+               if (!username || !password || !g_strcmp0(username, "-") ||
+                                       !g_strcmp0(password, "-"))
+                       goto request_input;
+
+               break;
+       case OC_CONNECT_COOKIE_WITH_USERPASS:
+               vpncookie = vpn_provider_get_string(provider,
+                                       "OpenConnect.Cookie");
+               /* Username and password must be set if cookie is missing */
+               if (!vpncookie) {
+                       username = vpn_provider_get_string(provider,
+                                               "OpenConnect.Username");
+                       password = vpn_provider_get_string(provider,
+                                               "OpenConnect.Password");
+
+                       if (!username || !password ||
+                                               !g_strcmp0(username, "-") ||
+                                               !g_strcmp0(password, "-"))
+                               goto request_input;
+               } else if (!g_strcmp0(vpncookie, "-")) {
+                       goto request_input;
+               }
+
+               break;
+       case OC_CONNECT_PUBLICKEY:
+               certificate = vpn_provider_get_string(provider,
+                               "OpenConnect.ClientCert");
+               private_key = vpn_provider_get_string(provider,
+                               "OpenConnect.UserPrivateKey");
+
+               if (!certificate || !private_key) {
+                       connman_warn("missing certificate and/or private key");
+                       oc_connect_done(data, EACCES);
                        free_private_data(data);
+                       return -EACCES;
                }
-               return err;
+
+               break;
+       case OC_CONNECT_PKCS:
+               certificate = vpn_provider_get_string(provider,
+                               "OpenConnect.PKCSClientCert");
+               if (certificate) {
+                       password = vpn_provider_get_string(provider,
+                               "OpenConnect.PKCSPassword");
+                       if (!password || !g_strcmp0(password, "-"))
+                               goto request_input;
+               } else {
+                       DBG("missing PKCS certificate");
+                       oc_connect_done(data, EACCES);
+                       free_private_data(data);
+                       return -EACCES;
+               }
+
+               break;
        }
 
-       return run_connect(provider, task, if_name, cb, user_data);
+       return run_connect(data);
+
+request_input:
+       err = request_input_credentials(data);
+       if (err != -EINPROGRESS) {
+               oc_connect_done(data, err);
+               vpn_provider_indicate_error(data->provider,
+                                       VPN_PROVIDER_ERROR_LOGIN_FAILED);
+               free_private_data(data);
+       }
+
+       return err;
+}
+
+static void oc_disconnect(struct vpn_provider *provider)
+{
+       connman_info("provider %p", provider);
+
+       if (!provider)
+               return;
+
+       /*
+       * OpenConnect may be disconnect by timeout in connmand before running
+       * the openconnect process. In such case it is important to cancel the
+       * agent request to avoid having multiple ones visible.
+       */
+       connman_agent_cancel(provider);
 }
 
 static int oc_save(struct vpn_provider *provider, GKeyFile *keyfile)
 {
-       const char *setting, *option;
+       const char *save_group;
+       const char *option;
        int i;
 
-       setting = vpn_provider_get_string(provider,
-                                       "OpenConnect.ServerCert");
-       if (setting)
-               g_key_file_set_string(keyfile,
-                               vpn_provider_get_save_group(provider),
-                               "OpenConnect.ServerCert", setting);
-
-       setting = vpn_provider_get_string(provider,
-                                       "OpenConnect.CACert");
-       if (setting)
-               g_key_file_set_string(keyfile,
-                               vpn_provider_get_save_group(provider),
-                               "OpenConnect.CACert", setting);
-
-       setting = vpn_provider_get_string(provider,
-                                       "VPN.MTU");
-       if (setting)
-               g_key_file_set_string(keyfile,
-                               vpn_provider_get_save_group(provider),
-                               "VPN.MTU", setting);
+       save_group = vpn_provider_get_save_group(provider);
 
        for (i = 0; i < (int)ARRAY_SIZE(oc_options); i++) {
                if (strncmp(oc_options[i].cm_opt, "OpenConnect.", 12) == 0) {
@@ -548,8 +1327,7 @@ static int oc_save(struct vpn_provider *provider, GKeyFile 
*keyfile)
                        if (!option)
                                continue;
 
-                       g_key_file_set_string(keyfile,
-                                       vpn_provider_get_save_group(provider),
+                       g_key_file_set_string(keyfile, save_group,
                                        oc_options[i].cm_opt, option);
                }
        }
@@ -559,13 +1337,22 @@ static int oc_save(struct vpn_provider *provider, 
GKeyFile *keyfile)
 
 static int oc_error_code(struct vpn_provider *provider, int exit_code)
 {
+       connman_info("%d", exit_code);
+
+       /* OpenConnect process return values are ambigious in definition
+        * https://github.com/openconnect/openconnect/blob/master/main.c#L1693
+        * and it is safer not to rely on them. Login error cannot be
+        * differentiated from connection errors, e.g., when self signed
+        * certificate is rejected by user setting.
+        */
 
        switch (exit_code) {
-       case 1:
        case 2:
-               vpn_provider_set_string_hide_value(provider,
-                               "OpenConnect.Cookie", NULL);
+               /* Cookie has failed */
+               clear_provider_credentials(provider);
                return VPN_PROVIDER_ERROR_LOGIN_FAILED;
+       case 1:
+               /* fall through */
        default:
                return VPN_PROVIDER_ERROR_UNKNOWN;
        }
@@ -603,6 +1390,7 @@ static int oc_route_env_parse(struct vpn_provider 
*provider, const char *key,
 static struct vpn_driver vpn_driver = {
        .notify         = oc_notify,
        .connect        = oc_connect,
+       .disconnect     = oc_disconnect,
        .error_code     = oc_error_code,
        .save           = oc_save,
        .route_env_parse = oc_route_env_parse,
-- 
2.20.1

------------------------------

Subject: Digest Footer

_______________________________________________
connman mailing list -- [email protected]
To unsubscribe send an email to [email protected]


------------------------------

End of connman Digest, Vol 48, Issue 19
***************************************

Reply via email to