+ 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],
+ "-");
+ }
+}
+
+typedef void (* request_input_reply_cb_t) (DBusMessage *reply,
+ void *user_data);
+
+static int request_input_credentials(struct oc_private_data *data,
+ request_input_reply_cb_t cb);
+
+
+static void request_input_pkcs_reply(DBusMessage *reply, void *user_data)
+{
+ struct oc_private_data *data = user_data;
+ const char *password = NULL;
+ const char *key;
+ DBusMessageIter iter, dict;
+ int err;
+
+ connman_info("provider %p", data->provider);
+
+ if (!reply)
+ goto err;
+
+ err = vpn_agent_check_and_process_reply_error(reply, data->provider,
+ data->task, data->cb, data->user_data);
+ if (err) {
+ /* Ensure cb is called only once */
+ data->cb = NULL;
+ data->user_data = NULL;
+ 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, "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, &password);
+ vpn_provider_set_string_hide_value(data->provider, key,
+ password);
+ }
+
+ dbus_message_iter_next(&dict);
+ }
+
+ if (data->connect_type != OC_CONNECT_PKCS || !password)
+ goto err;
+
+ if (write_data(data->fd_in, password) != 0) {
+ connman_error("openconnect failed to take PKCS pass phrase on"
+ " stdin");
+ goto err;
+ }
+
+ clear_provider_credentials(data->provider);
+
+ return;
+err:
+ oc_connect_done(data, EACCES);
+}
+
+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
+ };
+ /* Handle both PKCS#12 and PKCS#8 failures */
+ const char *pkcs_failures[] = {
+ "Failed to decrypt PKCS#12 certificate file",
+ "Failed to decrypt PKCS#8 certificate file",
+ NULL
+ };
+ /* Handle both PKCS#12 and PKCS#8 requests */
+ const char *pkcs_requests[] = {
+ "Enter PKCS#12 pass phrase",
+ "Enter PKCS#8 pass phrase",
+ NULL
+ };
+ 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)) {
+ gsize len;
+ int pos;
+
+ if (!data->interactive) {
+ if (g_io_channel_read_line(source, &str, &len, NULL,
+ NULL) != G_IO_STATUS_NORMAL)
+ err = EIO;
+ else
+ str[len - 1] = '\0';
+ } else {
+ GIOStatus status;
+ str = g_try_new0(char, OC_MAX_READBUF_LEN);
+ if (!str)
+ return G_SOURCE_REMOVE;
+
+ for (pos = 0; pos < OC_MAX_READBUF_LEN - 1 ; ++pos) {
+ status = g_io_channel_read_chars(source,
+ str+pos, 1, &len, NULL);
+
+ if (status == G_IO_STATUS_EOF) {
+ break;
+ } else if (status != G_IO_STATUS_NORMAL) {
+ err = EIO;
+ break;
+ }
+
+ /* Ignore control chars and digits at start */
+ if (!pos && (g_ascii_iscntrl(str[pos]) ||
+ g_ascii_isdigit(str[pos])))
+ --pos;
+
+ /* Read zero length or no more to read */
+ if (!len || g_io_channel_get_buffer_condition(
+ source) != G_IO_IN ||
+ str[pos] == '\n')
+ break;
+ }
+
+ /*
+ * When self signed certificates are allowed and server
+ * SHA1 fingerprint is printed to stderr there is a
+ * newline char at the end of SHA1 fingerprint.
+ */
+ if (str[pos] == '\n')
+ str[pos] = '\0';
+ }
+
+ connman_info("openconnect: %s", str);
+
+ if (err || !str || !*str) {
+ connman_info("error reading from openconnect");
+ } else if (g_str_has_prefix(str, server_key_hash)) {
+ const char *fingerprint;
+ 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;
+ fingerprint = g_strstrip(str + position);
+
+ connman_info("Set server key hash: \"%s\"",
+ fingerprint);
+
+ vpn_provider_set_string(data->provider,
+ "OpenConnect.ServerCert",
+ fingerprint);
+
+ /*
+ * OpenConnect waits for "yes" or "no" as
+ * response to certificate acceptance request.
+ */
+ if (write_data(data->fd_in, "yes") != 0)
+ connman_error("openconnect: 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 (strv_contains_prefix(pkcs_failures, str)) {
+ connman_warn("PKCS failure: %s", str);
+ close = true;
+ err = EACCES;
+ } else if (strv_contains_prefix(pkcs_requests, str)) {
+ connman_info("PKCS file pass phrase request: %s", str);
+ err = request_input_credentials(data,
+ request_input_pkcs_reply);
+
+ if (err != -EINPROGRESS) {
+ err = EACCES;
+ close = true;
+ } else {
+ err = 0;
+ }
+ } 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;
+ const char *setting_str;
+ bool setting;
+ bool use_stdout = false;
+ int fd_out = -1;
+ 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");
+ if (!certificate) {
+ err = -EACCES;
+ goto done;
+ }
+
+ connman_task_add_argument(task, "--certificate", certificate);
+
+ password = vpn_provider_get_string(data->provider,
+ "OpenConnect.PKCSPassword");
+ /* Add password only if it is has been set */
+ if (!password || !g_strcmp0(password, "-"))
+ break;
+
+ 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);
+
+ /*
+ * To clarify complex situation, if cookie is expected to be printed
+ * to stdout all other output must go to syslog. But with PKCS all
+ * output must be caught in order to get message about file decryption
+ * error. For this reason, the mode has to be interactive as well.
+ */
+ switch (data->connect_type) {
+ case OC_CONNECT_COOKIE:
+ /* fall through */
+ case OC_CONNECT_COOKIE_WITH_USERPASS:
+ /* fall through */
+ case OC_CONNECT_USERPASS:
+ /* fall through */
+ case OC_CONNECT_PUBLICKEY:
+ connman_task_add_argument(task, "--syslog", NULL);
+
+ setting = vpn_provider_get_boolean(provider,
+ "OpenConnect.AllowSelfSignedCert",
+ false);
+ setting_str = vpn_provider_get_string(provider,
+ "OpenConnect.ServerCert");
+
+ /*
+ * Run in interactive mode if self signed certificates are
+ * allowed and there is no set server SHA1 fingerprint.
+ */
+ if (setting_str || !setting)
+ connman_task_add_argument(task, "--non-inter", NULL);
+ else
+ data->interactive = true;
+ break;
+ case OC_CONNECT_PKCS:
+ data->interactive = true;
+ break;
+ }
+
+ connman_task_add_argument(task, "--script", SCRIPTDIR "/vpn-script");
+
+ connman_task_add_argument(task, "--interface", data->if_name);