summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Woodhouse <David.Woodhouse@intel.com>2012-11-05 21:00:28 +0000
committerDavid Woodhouse <David.Woodhouse@intel.com>2012-11-05 21:00:28 +0000
commit4a8a947059b9bdfa4f2228c45f868aedd3cecfca (patch)
treef197f2d2e71abe2e56fb635e38dc3c6b2194c7c2
parent70253b3958eb3b12af92cd222c2d98317cbba56c (diff)
parentc7ddd3bd20163efc54f8555cc6d3858425ee1a2b (diff)
downloadopenconnect-4a8a947059b9bdfa4f2228c45f868aedd3cecfca.tar.gz
openconnect-4a8a947059b9bdfa4f2228c45f868aedd3cecfca.tar.bz2
openconnect-4a8a947059b9bdfa4f2228c45f868aedd3cecfca.zip
Merge branch 'xmlpost-v2' of git://github.com/cernekee/openconnect
-rw-r--r--auth.c481
-rw-r--r--http.c620
-rw-r--r--libopenconnect.map.in5
-rw-r--r--library.c41
-rw-r--r--main.c16
-rw-r--r--openconnect-internal.h18
-rw-r--r--openconnect.8.in6
-rw-r--r--openconnect.h4
-rw-r--r--openssl.c2
-rw-r--r--www/changelog.xml5
10 files changed, 927 insertions, 271 deletions
diff --git a/auth.c b/auth.c
index 4c9d623..5634224 100644
--- a/auth.c
+++ b/auth.c
@@ -41,6 +41,8 @@
#include "openconnect-internal.h"
+static int xmlpost_append_form_opts(struct openconnect_info *vpninfo,
+ struct oc_auth_form *form, char *body, int bodylen);
static int can_gen_tokencode(struct openconnect_info *vpninfo, struct oc_form_opt *opt);
static int do_gen_tokencode(struct openconnect_info *vpninfo, struct oc_auth_form *form);
@@ -172,7 +174,7 @@ static int parse_auth_choice(struct openconnect_info *vpninfo, struct oc_auth_fo
* = 1, when form was parsed
*/
static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *form,
- xmlNode *xml_node, char *body, int bodylen)
+ xmlNode *xml_node)
{
char *input_type, *input_name, *input_label;
@@ -255,8 +257,6 @@ static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *for
*p = opt;
}
- vpn_progress(vpninfo, PRG_TRACE, _("Fixed options give %s\n"), body);
-
return 0;
}
@@ -317,21 +317,178 @@ static char *xmlnode_msg(xmlNode *xml_node)
return result;
}
+static int xmlnode_is_named(xmlNode *xml_node, const char *name)
+{
+ return !strcmp((char *)xml_node->name, name);
+}
+
+static int xmlnode_get_prop(xmlNode *xml_node, const char *name, char **var)
+{
+ char *str = (char *)xmlGetProp(xml_node, (unsigned char *)name);
+
+ if (!str)
+ return -ENOENT;
+
+ free(*var);
+ *var = str;
+ return 0;
+}
+
+static int xmlnode_get_text(xmlNode *xml_node, const char *name, char **var)
+{
+ char *str;
+
+ if (name && !xmlnode_is_named(xml_node, name))
+ return -EINVAL;
+
+ str = xmlnode_msg(xml_node);
+ if (!str)
+ return -ENOENT;
+
+ free(*var);
+ *var = str;
+ return 0;
+}
+
+/*
+ * Legacy server response looks like:
+ *
+ * <auth id="<!-- "main" for initial attempt, "success" means we have a cookie -->">
+ * <title><!-- title to display to user --></title>
+ * <csd token="<!-- save to vpninfo->csd_token -->"
+ * ticket="<!-- save to vpninfo->csd_ticket -->" />
+ * <csd stuburl="<!-- ignore -->"
+ * starturl="<!-- ignore -->"
+ * waiturl="<!-- ignore -->"
+ * <csdMac
+ * stuburl="<!-- save to vpninfo->csd_stuburl on Mac only -->"
+ * starturl="<!-- save to vpninfo->csd_starturl on Mac only -->"
+ * waiturl="<!-- save to vpninfo->csd_waiturl on Mac only -->" />
+ * <csdLinux
+ * stuburl="<!-- same as above, for Linux -->"
+ * starturl="<!-- same as above, for Linux -->"
+ * waiturl="<!-- same as above, for Linux -->" />
+ * <banner><!-- display this to the user --></banner>
+ * <message>Please enter your username and password.</message>
+ * <form method="post" action="/+webvpn+/index.html">
+ * <input type="text" name="username" label="Username:" />
+ * <input type="password" name="password" label="Password:" />
+ * <input type="hidden" name="<!-- save these -->" value="<!-- ... -->" />
+ * <input type="submit" name="Login" value="Login" />
+ * <input type="reset" name="Clear" value="Clear" />
+ * </form>
+ * </auth>
+ *
+ * New server response looks like:
+ *
+ * <config-auth>
+ * <version><!-- whatever --></version>
+ * <session-token><!-- if present, save to vpninfo->cookie --></session-token>
+ * <opaque>
+ * <!-- this could contain anything; copy to vpninfo->opaque_srvdata -->
+ * <tunnel-group>foobar</tunnel-group>
+ * <config-hash>1234567</config-hash>
+ * </opaque>
+ * <auth id="<!-- see above -->
+ * <!-- all of our old familiar fields -->
+ * </auth>
+ * <host-scan>
+ * <host-scan-ticket><!-- save to vpninfo->csd_ticket --></host-scan-ticket>
+ * <host-scan-token><!-- save to vpninfo->csd_token --></host-scan-token>
+ * <host-scan-base-uri><!-- save to vpninfo->csd_starturl --></host-scan-base-uri>
+ * <host-scan-wait-uri><!-- save to vpninfo->csd_waiturl --></host-scan-wait-uri>
+ * </host-scan>
+ * </config-auth>
+ *
+ * Notes:
+ *
+ * 1) The new host-scan-*-uri nodes do not map directly to the old CSD fields.
+ *
+ * 2) The new <form> tag tends to omit the method/action properties.
+ */
+
+static int parse_auth_node(struct openconnect_info *vpninfo, xmlNode *xml_node,
+ struct oc_auth_form *form)
+{
+ int ret = 0;
+
+ for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
+ if (xml_node->type != XML_ELEMENT_NODE)
+ continue;
+
+ xmlnode_get_text(xml_node, "banner", &form->banner);
+ xmlnode_get_text(xml_node, "message", &form->message);
+ xmlnode_get_text(xml_node, "error", &form->error);
+
+ if (xmlnode_is_named(xml_node, "form")) {
+
+ /* defaults for new XML POST */
+ form->method = strdup("POST");
+ form->action = strdup("/");
+
+ xmlnode_get_prop(xml_node, "method", &form->method);
+ xmlnode_get_prop(xml_node, "action", &form->action);
+
+ if (!form->method || !form->action ||
+ strcasecmp(form->method, "POST") || !form->action[0]) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Cannot handle form method='%s', action='%s'\n"),
+ form->method, form->action);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = parse_form(vpninfo, form, xml_node);
+ if (ret < 0)
+ goto out;
+ } else if (!vpninfo->csd_scriptname && xmlnode_is_named(xml_node, "csd")) {
+ xmlnode_get_prop(xml_node, "token", &vpninfo->csd_token);
+ xmlnode_get_prop(xml_node, "ticket", &vpninfo->csd_ticket);
+ } else if (!vpninfo->csd_scriptname && xmlnode_is_named(xml_node, vpninfo->csd_xmltag)) {
+ xmlnode_get_prop(xml_node, "stuburl", &vpninfo->csd_stuburl);
+ xmlnode_get_prop(xml_node, "starturl", &vpninfo->csd_starturl);
+ xmlnode_get_prop(xml_node, "waiturl", &vpninfo->csd_waiturl);
+ vpninfo->csd_preurl = strdup(vpninfo->urlpath);
+ }
+ }
+
+out:
+ return ret;
+}
+
+static int parse_host_scan_node(struct openconnect_info *vpninfo, xmlNode *xml_node)
+{
+ /* ignore this whole section if the CSD trojan has already run */
+ if (vpninfo->csd_scriptname)
+ return 0;
+
+ for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
+ if (xml_node->type != XML_ELEMENT_NODE)
+ continue;
+
+ xmlnode_get_text(xml_node, "host-scan-ticket", &vpninfo->csd_ticket);
+ xmlnode_get_text(xml_node, "host-scan-token", &vpninfo->csd_token);
+ xmlnode_get_text(xml_node, "host-scan-base-uri", &vpninfo->csd_starturl);
+ xmlnode_get_text(xml_node, "host-scan-wait-uri", &vpninfo->csd_waiturl);
+ }
+ return 0;
+}
+
/* Return value:
* < 0, on error
- * = 0, when form parsed and POST required
- * = 1, when response was cancelled by user
- * = 2, when form indicates that login was already successful
+ * = 0, on success; *form is populated
*/
-int parse_xml_response(struct openconnect_info *vpninfo, char *response,
- char *request_body, int req_len, const char **method,
- const char **request_body_type)
+int parse_xml_response(struct openconnect_info *vpninfo, char *response, struct oc_auth_form **formp)
{
struct oc_auth_form *form;
xmlDocPtr xml_doc;
xmlNode *xml_node;
- int ret;
- struct vpn_option *opt, *next;
+ int ret = -EINVAL;
+
+ if (*formp) {
+ free_auth_form(*formp);
+ *formp = NULL;
+ }
form = calloc(1, sizeof(*form));
if (!form)
@@ -348,77 +505,77 @@ int parse_xml_response(struct openconnect_info *vpninfo, char *response,
}
xml_node = xmlDocGetRootElement(xml_doc);
- if (xml_node->type != XML_ELEMENT_NODE || strcmp((char *)xml_node->name, "auth")) {
- vpn_progress(vpninfo, PRG_ERR,
- _("XML response has no \"auth\" root node\n"));
- ret = -EINVAL;
- goto out;
- }
+ while (xml_node) {
+ int ret = 0;
- form->auth_id = (char *)xmlGetProp(xml_node, (unsigned char *)"id");
- if (!strcmp(form->auth_id, "success")) {
- ret = 2;
- goto out;
+ if (xml_node->type != XML_ELEMENT_NODE) {
+ xml_node = xml_node->next;
+ continue;
+ }
+ if (xmlnode_is_named(xml_node, "config-auth")) {
+ /* if we do have a config-auth node, it is the root element */
+ xml_node = xml_node->children;
+ continue;
+ } else if (xmlnode_is_named(xml_node, "auth")) {
+ xmlnode_get_prop(xml_node, "id", &form->auth_id);
+ ret = parse_auth_node(vpninfo, xml_node, form);
+ } else if (xmlnode_is_named(xml_node, "opaque")) {
+ if (vpninfo->opaque_srvdata)
+ xmlFreeNode(vpninfo->opaque_srvdata);
+ vpninfo->opaque_srvdata = xmlCopyNode(xml_node, 1);
+ if (!vpninfo->opaque_srvdata)
+ ret = -ENOMEM;
+ } else if (xmlnode_is_named(xml_node, "host-scan")) {
+ ret = parse_host_scan_node(vpninfo, xml_node);
+ } else {
+ xmlnode_get_text(xml_node, "session-token", &vpninfo->cookie);
+ xmlnode_get_text(xml_node, "error", &form->error);
+ }
+
+ if (ret)
+ goto out;
+ xml_node = xml_node->next;
}
- if (vpninfo->nopasswd) {
+ if (!form->auth_id) {
vpn_progress(vpninfo, PRG_ERR,
- _("Asked for password but '--no-passwd' set\n"));
- ret = -EPERM;
+ _("XML response has no \"auth\" node\n"));
goto out;
}
- for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
- if (xml_node->type != XML_ELEMENT_NODE)
- continue;
+ *formp = form;
+ xmlFreeDoc(xml_doc);
+ return 0;
- if (!strcmp((char *)xml_node->name, "banner")) {
- free(form->banner);
- form->banner = xmlnode_msg(xml_node);
- } else if (!strcmp((char *)xml_node->name, "message")) {
- free(form->message);
- form->message = xmlnode_msg(xml_node);
- } else if (!strcmp((char *)xml_node->name, "error")) {
- free(form->error);
- form->error = xmlnode_msg(xml_node);
- } else if (!strcmp((char *)xml_node->name, "form")) {
-
- form->method = (char *)xmlGetProp(xml_node, (unsigned char *)"method");
- form->action = (char *)xmlGetProp(xml_node, (unsigned char *)"action");
- if (!form->method || !form->action ||
- strcasecmp(form->method, "POST") || !form->action[0]) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Cannot handle form method='%s', action='%s'\n"),
- form->method, form->action);
- ret = -EINVAL;
- goto out;
- }
- vpninfo->redirect_url = strdup(form->action);
+ out:
+ xmlFreeDoc(xml_doc);
+ free_auth_form(form);
+ return ret;
+}
- ret = parse_form(vpninfo, form, xml_node, request_body, req_len);
- if (ret < 0)
- goto out;
- } else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, "csd")) {
- if (!vpninfo->csd_token)
- vpninfo->csd_token = (char *)xmlGetProp(xml_node,
- (unsigned char *)"token");
- if (!vpninfo->csd_ticket)
- vpninfo->csd_ticket = (char *)xmlGetProp(xml_node,
- (unsigned char *)"ticket");
- } else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, vpninfo->csd_xmltag)) {
- vpninfo->csd_stuburl = (char *)xmlGetProp(xml_node,
- (unsigned char *)"stuburl");
- vpninfo->csd_starturl = (char *)xmlGetProp(xml_node,
- (unsigned char *)"starturl");
- vpninfo->csd_waiturl = (char *)xmlGetProp(xml_node,
- (unsigned char *)"waiturl");
- vpninfo->csd_preurl = strdup(vpninfo->urlpath);
- }
+/* Return value:
+ * < 0, on error
+ * = 0, when form parsed and POST required
+ * = 1, when response was cancelled by user
+ * = 2, when form indicates that login was already successful
+ */
+int handle_auth_form(struct openconnect_info *vpninfo, struct oc_auth_form *form,
+ char *request_body, int req_len, const char **method,
+ const char **request_body_type, int xmlpost)
+{
+ int ret;
+ struct vpn_option *opt, *next;
+
+ if (!strcmp(form->auth_id, "success"))
+ return 2;
+
+ if (vpninfo->nopasswd) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Asked for password but '--no-passwd' set\n"));
+ return -EPERM;
}
- if (vpninfo->csd_token && vpninfo->csd_ticket && vpninfo->csd_starturl && vpninfo->csd_waiturl) {
- /* First, redirect to the stuburl -- we'll need to fetch and run that */
- vpninfo->redirect_url = strdup(vpninfo->csd_stuburl);
+ if (vpninfo->csd_token && vpninfo->csd_ticket && vpninfo->csd_starturl && vpninfo->csd_waiturl) {
/* AB: remove all cookies */
for (opt = vpninfo->cookies; opt; opt = next) {
next = opt->next;
@@ -428,17 +585,14 @@ int parse_xml_response(struct openconnect_info *vpninfo, char *response,
free(opt);
}
vpninfo->cookies = NULL;
-
- ret = 0;
- goto out;
+ return 0;
}
if (!form->opts) {
if (form->message)
vpn_progress(vpninfo, PRG_INFO, "%s\n", form->message);
if (form->error)
vpn_progress(vpninfo, PRG_ERR, "%s\n", form->error);
- ret = -EPERM;
- goto out;
+ return -EPERM;
}
if (vpninfo->process_auth_form)
@@ -448,25 +602,33 @@ int parse_xml_response(struct openconnect_info *vpninfo, char *response,
ret = 1;
}
if (ret)
- goto out;
+ return ret;
/* tokencode generation is deferred until after username prompts and CSD */
ret = do_gen_tokencode(vpninfo, form);
if (ret)
- goto out;
+ return ret;
- ret = append_form_opts(vpninfo, form, request_body, req_len);
+ ret = xmlpost ?
+ xmlpost_append_form_opts(vpninfo, form, request_body, req_len) :
+ append_form_opts(vpninfo, form, request_body, req_len);
if (!ret) {
*method = "POST";
*request_body_type = "application/x-www-form-urlencoded";
}
- out:
- xmlFreeDoc(xml_doc);
+ return ret;
+}
+
+void free_auth_form(struct oc_auth_form *form)
+{
+ if (!form)
+ return;
while (form->opts) {
struct oc_form_opt *tmp = form->opts->next;
if (form->opts->type == OC_FORM_OPT_TEXT ||
form->opts->type == OC_FORM_OPT_PASSWORD ||
- form->opts->type == OC_FORM_OPT_HIDDEN)
+ form->opts->type == OC_FORM_OPT_HIDDEN ||
+ form->opts->type == OC_FORM_OPT_STOKEN)
free(form->opts->value);
else if (form->opts->type == OC_FORM_OPT_SELECT) {
struct oc_form_opt_select *sel = (void *)form->opts;
@@ -492,9 +654,161 @@ int parse_xml_response(struct openconnect_info *vpninfo, char *response,
free(form->method);
free(form->action);
free(form);
+}
+
+/*
+ * Old submission format is just an HTTP query string:
+ *
+ * password=12345678&username=joe
+ *
+ * New XML format is more complicated:
+ *
+ * <config-auth client="vpn" type="<!-- init or auth-reply -->">
+ * <version who="vpn"><!-- currently just the OpenConnect version --></version>
+ * <device-id><!-- linux, linux-64, mac, win --></device-id>
+ * <opaque is-for="<!-- some name -->">
+ * <!-- just copy this verbatim from whatever the gateway sent us -->
+ * </opaque>
+ *
+ * For init only, add:
+ * <group-access>https://<!-- insert hostname here --></group-access>
+ *
+ * For auth-reply only, add:
+ * <auth>
+ * <username><!-- same treatment as the old form options --></username>
+ * <password><!-- ditto -->
+ * </auth>
+ * <host-scan-token><!-- vpninfo->csd_ticket --></host-scan-token>
+ */
+
+#define XCAST(x) ((const xmlChar *)(x))
+
+static xmlDocPtr xmlpost_new_query(struct openconnect_info *vpninfo, const char *type,
+ xmlNodePtr *rootp)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root, node;
+
+ doc = xmlNewDoc(XCAST("1.0"));
+ if (!doc)
+ return NULL;
+
+ *rootp = root = xmlNewNode(NULL, XCAST("config-auth"));
+ if (!root)
+ goto bad;
+ if (!xmlNewProp(root, XCAST("client"), XCAST("vpn")))
+ goto bad;
+ if (!xmlNewProp(root, XCAST("type"), XCAST(type)))
+ goto bad;
+ xmlDocSetRootElement(doc, root);
+
+ node = xmlNewTextChild(root, NULL, XCAST("version"), XCAST(openconnect_version_str));
+ if (!node)
+ goto bad;
+ if (!xmlNewProp(node, XCAST("who"), XCAST("vpn")))
+ goto bad;
+
+ if (!xmlNewTextChild(root, NULL, XCAST("device-id"), XCAST(vpninfo->platname)))
+ goto bad;
+
+ return doc;
+
+bad:
+ xmlFreeDoc(doc);
+ return NULL;
+}
+
+static int xmlpost_complete(xmlDocPtr doc, char *body, int bodylen)
+{
+ xmlChar *mem = NULL;
+ int len, ret = 0;
+
+ if (!body) {
+ xmlFree(doc);
+ return 0;
+ }
+
+ xmlDocDumpMemoryEnc(doc, &mem, &len, "UTF-8");
+ if (!mem) {
+ xmlFreeDoc(doc);
+ return -ENOMEM;
+ }
+
+ if (len > bodylen)
+ ret = -E2BIG;
+ else {
+ memcpy(body, mem, len);
+ body[len] = 0;
+ }
+
+ xmlFreeDoc(doc);
+ xmlFree(mem);
+
return ret;
}
+int xmlpost_initial_req(struct openconnect_info *vpninfo, char *request_body, int req_len)
+{
+ xmlNodePtr root, node;
+ xmlDocPtr doc = xmlpost_new_query(vpninfo, "init", &root);
+ char *url;
+
+ if (!doc)
+ return -ENOMEM;
+
+ if (asprintf(&url, "https://%s", vpninfo->hostname) == -1)
+ goto bad;
+ node = xmlNewTextChild(root, NULL, XCAST("group-access"), XCAST(url));
+ free(url);
+ if (!node)
+ goto bad;
+
+ return xmlpost_complete(doc, request_body, req_len);
+
+bad:
+ xmlpost_complete(doc, NULL, 0);
+ return -ENOMEM;
+}
+
+static int xmlpost_append_form_opts(struct openconnect_info *vpninfo,
+ struct oc_auth_form *form, char *body, int bodylen)
+{
+ xmlNodePtr root, node;
+ xmlDocPtr doc = xmlpost_new_query(vpninfo, "auth-reply", &root);
+ struct oc_form_opt *opt;
+
+ if (!doc)
+ return -ENOMEM;
+
+ if (vpninfo->opaque_srvdata) {
+ node = xmlCopyNode(vpninfo->opaque_srvdata, 1);
+ if (!node)
+ goto bad;
+ if (!xmlAddChild(root, node))
+ goto bad;
+ }
+
+ node = xmlNewChild(root, NULL, XCAST("auth"), NULL);
+ if (!node)
+ goto bad;
+
+ for (opt = form->opts; opt; opt = opt->next) {
+ if (!xmlNewTextChild(node, NULL, XCAST(opt->name), XCAST(opt->value)))
+ goto bad;
+ }
+
+ if (vpninfo->csd_token &&
+ !xmlNewTextChild(root, NULL, XCAST("host-scan-token"), XCAST(vpninfo->csd_token)))
+ goto bad;
+
+ return xmlpost_complete(doc, body, bodylen);
+
+bad:
+ xmlpost_complete(doc, NULL, 0);
+ return -ENOMEM;
+}
+
+
#ifdef LIBSTOKEN_HDR
static void nuke_opt_values(struct oc_form_opt *opt)
{
@@ -659,8 +973,6 @@ static int can_gen_tokencode(struct openconnect_info *vpninfo, struct oc_form_op
_("Server is rejecting the soft token; switching to manual entry\n"));
return -ENOENT;
}
-
- vpninfo->stoken_tries++;
return 0;
#else
return -EOPNOTSUPP;
@@ -696,6 +1008,7 @@ static int do_gen_tokencode(struct openconnect_info *vpninfo, struct oc_auth_for
return -EIO;
}
+ vpninfo->stoken_tries++;
opt->value = strdup(tokencode);
return opt->value ? 0 : -ENOMEM;
#else
diff --git a/http.c b/http.c
index 38fc9a5..c05340f 100644
--- a/http.c
+++ b/http.c
@@ -35,6 +35,7 @@
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
+#include <stdarg.h>
#include "openconnect-internal.h"
@@ -44,6 +45,85 @@ static int proxy_read(struct openconnect_info *vpninfo, int fd,
unsigned char *buf, size_t len);
#define MAX_BUF_LEN 131072
+#define BUF_CHUNK_SIZE 4096
+
+struct oc_text_buf {
+ char *data;
+ int pos;
+ int buf_len;
+ int error;
+};
+
+static struct oc_text_buf *buf_alloc(void)
+{
+ return calloc(1, sizeof(struct oc_text_buf));
+}
+
+static void buf_append(struct oc_text_buf *buf, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!buf || buf->error)
+ return;
+
+ if (!buf->data) {
+ buf->data = malloc(BUF_CHUNK_SIZE);
+ if (!buf->data) {
+ buf->error = -ENOMEM;
+ return;
+ }
+ buf->buf_len = BUF_CHUNK_SIZE;
+ }
+
+ while (1) {
+ int max_len = buf->buf_len - buf->pos, ret;
+
+ va_start(ap, fmt);
+ ret = vsnprintf(buf->data + buf->pos, max_len, fmt, ap);
+ va_end(ap);
+ if (ret < 0) {
+ buf->error = -EIO;
+ break;
+ } else if (ret < max_len) {
+ buf->pos += ret;
+ break;
+ } else {
+ int new_buf_len = buf->buf_len + BUF_CHUNK_SIZE;
+
+ if (new_buf_len > MAX_BUF_LEN) {
+ /* probably means somebody is messing with us */
+ buf->error = -E2BIG;
+ break;
+ }
+
+ buf->data = realloc(buf->data, new_buf_len);
+ if (!buf->data) {
+ buf->error = -ENOMEM;
+ break;
+ }
+ buf->buf_len = new_buf_len;
+ }
+ }
+}
+
+static int buf_error(struct oc_text_buf *buf)
+{
+ return buf ? buf->error : -ENOMEM;
+}
+
+static int buf_free(struct oc_text_buf *buf)
+{
+ int error = buf_error(buf);
+
+ if (buf) {
+ if (buf->data)
+ free(buf->data);
+ free(buf);
+ }
+
+ return error;
+}
+
/*
* We didn't really want to have to do this for ourselves -- one might have
* thought that it would be available in a library somewhere. But neither
@@ -343,11 +423,30 @@ static int process_http_response(struct openconnect_info *vpninfo, int *result,
return done;
}
+static void add_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
+{
+ struct vpn_option *opt;
+
+ buf_append(buf, "Host: %s\r\n", vpninfo->hostname);
+ buf_append(buf, "User-Agent: %s\r\n", vpninfo->useragent);
+ buf_append(buf, "Accept: */*\r\n");
+ buf_append(buf, "Accept-Encoding: identity\r\n");
+
+ if (vpninfo->cookies) {
+ buf_append(buf, "Cookie: ");
+ for (opt = vpninfo->cookies; opt; opt = opt->next)
+ buf_append(buf, "%s=%s%s", opt->option,
+ opt->value, opt->next ? "; " : "\r\n");
+ }
+ buf_append(buf, "X-Transcend-Version: 1\r\n");
+ buf_append(buf, "X-Aggregate-Auth: 1\r\n");
+ buf_append(buf, "X-AnyConnect-Platform: %s\r\n", vpninfo->platname);
+}
+
static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
char *server_sha1)
{
- struct vpn_option *opt;
- char buf[MAX_BUF_LEN];
+ struct oc_text_buf *buf;
char *config_buf = NULL;
int result, buflen;
unsigned char local_sha1_bin[SHA1_SIZE];
@@ -361,25 +460,21 @@ static int fetch_config(struct openconnect_info *vpninfo, char *fu, char *bu,
return -EINVAL;
}
- sprintf(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
- sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
- sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
- sprintf(buf + strlen(buf), "Accept: */*\r\n");
- sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
+ buf = buf_alloc();
+ buf_append(buf, "GET %s%s HTTP/1.1\r\n", fu, bu);
+ add_common_headers(vpninfo, buf);
+ buf_append(buf, "\r\n");
- if (vpninfo->cookies) {
- sprintf(buf + strlen(buf), "Cookie: ");
- for (opt = vpninfo->cookies; opt; opt = opt->next)
- sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
- opt->value, opt->next ? "; " : "\r\n");
- }
- sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
+ if (buf_error(buf))
+ return buf_free(buf);
- if (openconnect_SSL_write(vpninfo, buf, strlen(buf)) != strlen(buf)) {
+ if (openconnect_SSL_write(vpninfo, buf->data, buf->pos) != buf->pos) {
vpn_progress(vpninfo, PRG_ERR,
_("Failed to send GET request for new config\n"));
+ buf_free(buf);
return -EIO;
}
+ buf_free(buf);
buflen = process_http_response(vpninfo, &result, NULL, &config_buf);
if (buflen < 0) {
@@ -487,28 +582,35 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle
csd_argv[i++] = fname;
csd_argv[i++]= (char *)"-ticket";
if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->csd_ticket) == -1)
- return -ENOMEM;
+ goto out;
csd_argv[i++]= (char *)"-stub";
csd_argv[i++]= (char *)"\"0\"";
csd_argv[i++]= (char *)"-group";
if (asprintf(&csd_argv[i++], "\"%s\"", vpninfo->authgroup?:"") == -1)
- return -ENOMEM;
+ goto out;
openconnect_local_cert_md5(vpninfo, ccertbuf);
scertbuf[0] = 0;
get_cert_md5_fingerprint(vpninfo, vpninfo->peer_cert, scertbuf);
csd_argv[i++]= (char *)"-certhash";
if (asprintf(&csd_argv[i++], "\"%s:%s\"", scertbuf, ccertbuf) == -1)
- return -ENOMEM;
+ goto out;
csd_argv[i++]= (char *)"-url";
if (asprintf(&csd_argv[i++], "\"https://%s%s\"", vpninfo->hostname, vpninfo->csd_starturl) == -1)
- return -ENOMEM;
+ goto out;
csd_argv[i++]= (char *)"-langselen";
csd_argv[i++] = NULL;
+ if (setenv("CSD_TOKEN", vpninfo->csd_token, 1))
+ goto out;
+ if (setenv("CSD_HOSTNAME", vpninfo->hostname, 1))
+ goto out;
+
execv(csd_argv[0], csd_argv);
+
+out:
vpn_progress(vpninfo, PRG_ERR,
_("Failed to exec CSD script %s\n"), csd_argv[0]);
exit(1);
@@ -516,8 +618,10 @@ static int run_csd_script(struct openconnect_info *vpninfo, char *buf, int bufle
free(vpninfo->csd_stuburl);
vpninfo->csd_stuburl = NULL;
+ free(vpninfo->urlpath);
vpninfo->urlpath = strdup(vpninfo->csd_waiturl +
(vpninfo->csd_waiturl[0] == '/' ? 1 : 0));
+ free(vpninfo->csd_waiturl);
vpninfo->csd_waiturl = NULL;
vpninfo->csd_scriptname = strdup(fname);
@@ -591,31 +695,133 @@ int internal_parse_url(char *url, char **res_proto, char **res_host,
return 0;
}
+static void clear_cookies(struct openconnect_info *vpninfo)
+{
+ struct vpn_option *opt, *next;
+
+ for (opt = vpninfo->cookies; opt; opt = next) {
+ next = opt->next;
+
+ free(opt->option);
+ free(opt->value);
+ free(opt);
+ }
+ vpninfo->cookies = NULL;
+}
+
/* Return value:
* < 0, on error
- * > 0, no cookie (user cancel)
- * = 0, obtained cookie
+ * = 0, on success (go ahead and retry with the latest vpninfo->{hostname,urlpath,port,...})
*/
-int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
+static int handle_redirect(struct openconnect_info *vpninfo)
{
- struct vpn_option *opt, *next;
- char buf[MAX_BUF_LEN];
- char *form_buf = NULL;
- int result, buflen;
- char request_body[2048];
- const char *request_body_type = NULL;
- const char *method = "GET";
+ vpninfo->redirect_type = REDIR_TYPE_LOCAL;
- if (vpninfo->use_stoken) {
- result = prepare_stoken(vpninfo);
- if (result)
- return result;
+ if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
+ /* New host. Tear down the existing connection and make a new one */
+ char *host;
+ int port;
+ int ret;
+
+ free(vpninfo->urlpath);
+ vpninfo->urlpath = NULL;
+
+ ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
+ if (ret) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Failed to parse redirected URL '%s': %s\n"),
+ vpninfo->redirect_url, strerror(-ret));
+ free(vpninfo->redirect_url);
+ vpninfo->redirect_url = NULL;
+ return ret;
+ }
+
+ if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
+ free(vpninfo->hostname);
+ vpninfo->hostname = host;
+ vpninfo->port = port;
+
+ /* Kill the existing connection, and a new one will happen */
+ free(vpninfo->peer_addr);
+ vpninfo->peer_addr = NULL;
+ openconnect_close_https(vpninfo, 0);
+ clear_cookies(vpninfo);
+ vpninfo->redirect_type = REDIR_TYPE_NEWHOST;
+ } else
+ free(host);
+
+ free(vpninfo->redirect_url);
+ vpninfo->redirect_url = NULL;
+
+ return 0;
+ } else if (strstr(vpninfo->redirect_url, "://")) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Cannot follow redirection to non-https URL '%s'\n"),
+ vpninfo->redirect_url);
+ free(vpninfo->redirect_url);
+ vpninfo->redirect_url = NULL;
+ return -EINVAL;
+ } else if (vpninfo->redirect_url[0] == '/') {
+ /* Absolute redirect within same host */
+ free(vpninfo->urlpath);
+ vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
+ free(vpninfo->redirect_url);
+ vpninfo->redirect_url = NULL;
+ return 0;
+ } else {
+ char *lastslash = NULL;
+ if (vpninfo->urlpath)
+ lastslash = strrchr(vpninfo->urlpath, '/');
+ if (!lastslash) {
+ free(vpninfo->urlpath);
+ vpninfo->urlpath = vpninfo->redirect_url;
+ vpninfo->redirect_url = NULL;
+ } else {
+ char *oldurl = vpninfo->urlpath;
+ *lastslash = 0;
+ vpninfo->urlpath = NULL;
+ if (asprintf(&vpninfo->urlpath, "%s/%s",
+ oldurl, vpninfo->redirect_url) == -1) {
+ int err = -errno;
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Allocating new path for relative redirect failed: %s\n"),
+ strerror(-err));
+ return err;
+ }
+ free(oldurl);
+ free(vpninfo->redirect_url);
+ vpninfo->redirect_url = NULL;
+ }
+ return 0;
}
+}
+
+/* Inputs:
+ * method: GET or POST
+ * vpninfo->hostname: Host DNS name
+ * vpninfo->port: TCP port, typically 443
+ * vpninfo->urlpath: Relative path, e.g. /+webvpn+/foo.html
+ * request_body_type: Content type for a POST (e.g. text/html). Can be NULL.
+ * request_body: POST content
+ * form_buf: Callee-allocated buffer for server content
+ *
+ * Return value:
+ * < 0, on error
+ * >=0, on success, indicating the length of the data in *form_buf
+ */
+static int do_https_request(struct openconnect_info *vpninfo, const char *method,
+ const char *request_body_type, const char *request_body,
+ char **form_buf, int fetch_redirect)
+{
+ struct oc_text_buf *buf;
+ int result, buflen;
retry:
- if (form_buf) {
- free(form_buf);
- form_buf = NULL;
+ vpninfo->redirect_type = REDIR_TYPE_NONE;
+
+ if (*form_buf) {
+ free(*form_buf);
+ *form_buf = NULL;
}
if (openconnect_open_https(vpninfo)) {
vpn_progress(vpninfo, PRG_ERR,
@@ -633,27 +839,18 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
*
* So we process the HTTP for ourselves...
*/
- sprintf(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
- sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
- sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
- sprintf(buf + strlen(buf), "Accept: */*\r\n");
- sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
+ buf = buf_alloc();
+ buf_append(buf, "%s /%s HTTP/1.1\r\n", method, vpninfo->urlpath ?: "");
+ add_common_headers(vpninfo, buf);
- if (vpninfo->cookies) {
- sprintf(buf + strlen(buf), "Cookie: ");
- for (opt = vpninfo->cookies; opt; opt = opt->next)
- sprintf(buf + strlen(buf), "%s=%s%s", opt->option,
- opt->value, opt->next ? "; " : "\r\n");
- }
if (request_body_type) {
- sprintf(buf + strlen(buf), "Content-Type: %s\r\n",
- request_body_type);
- sprintf(buf + strlen(buf), "Content-Length: %zd\r\n",
- strlen(request_body));
+ buf_append(buf, "Content-Type: %s\r\n", request_body_type);
+ buf_append(buf, "Content-Length: %zd\r\n", strlen(request_body));
}
- sprintf(buf + strlen(buf), "X-Transcend-Version: 1\r\n\r\n");
+ buf_append(buf, "\r\n");
+
if (request_body_type)
- sprintf(buf + strlen(buf), "%s", request_body);
+ buf_append(buf, "%s", request_body);
if (vpninfo->port == 443)
vpn_progress(vpninfo, PRG_INFO, "%s https://%s/%s\n",
@@ -664,148 +861,213 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
method, vpninfo->hostname, vpninfo->port,
vpninfo->urlpath ?: "");
- result = openconnect_SSL_write(vpninfo, buf, strlen(buf));
+ if (buf_error(buf))
+ return buf_free(buf);
+
+ result = openconnect_SSL_write(vpninfo, buf->data, buf->pos);
+ buf_free(buf);
if (result < 0)
return result;
- buflen = process_http_response(vpninfo, &result, NULL, &form_buf);
+ buflen = process_http_response(vpninfo, &result, NULL, form_buf);
if (buflen < 0) {
/* We'll already have complained about whatever offended us */
return buflen;
}
if (result != 200 && vpninfo->redirect_url) {
- redirect:
- if (!strncmp(vpninfo->redirect_url, "https://", 8)) {
- /* New host. Tear down the existing connection and make a new one */
- char *host;
- int port;
- int ret;
+ result = handle_redirect(vpninfo);
+ if (result == 0) {
+ if (!fetch_redirect)
+ return 0;
+ goto retry;
+ }
+ goto out;
+ }
+ if (!*form_buf || result != 200) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Unexpected %d result from server\n"),
+ result);
+ result = -EINVAL;
+ goto out;
+ }
- free(vpninfo->urlpath);
- vpninfo->urlpath = NULL;
+ return buflen;
- ret = internal_parse_url(vpninfo->redirect_url, NULL, &host, &port, &vpninfo->urlpath, 0);
- if (ret) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Failed to parse redirected URL '%s': %s\n"),
- vpninfo->redirect_url, strerror(-ret));
- free(vpninfo->redirect_url);
- vpninfo->redirect_url = NULL;
- free(form_buf);
- return ret;
- }
+ out:
+ free(*form_buf);
+ *form_buf = NULL;
+ return result;
+}
- if (strcasecmp(vpninfo->hostname, host) || port != vpninfo->port) {
- free(vpninfo->hostname);
- vpninfo->hostname = host;
- vpninfo->port = port;
+/* Return value:
+ * < 0, if the data is unrecognized
+ * = 0, if the page contains an XML document
+ * = 1, if the page is a wait/refresh HTML page
+ */
+static int check_response_type(struct openconnect_info *vpninfo, char *form_buf)
+{
+ if (strncmp(form_buf, "<?xml", 5)) {
+ /* Not XML? Perhaps it's HTML with a refresh... */
+ if (strcasestr(form_buf, "http-equiv=\"refresh\""))
+ return 1;
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Unknown response from server\n"));
+ return -EINVAL;
+ }
+ return 0;
+}
- /* Kill the existing connection, and a new one will happen */
- free(vpninfo->peer_addr);
- vpninfo->peer_addr = NULL;
- openconnect_close_https(vpninfo, 0);
+/* Return value:
+ * < 0, on error
+ * > 0, no cookie (user cancel)
+ * = 0, obtained cookie
+ */
+int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
+{
+ struct vpn_option *opt;
+ char *form_buf = NULL;
+ struct oc_auth_form *form = NULL;
+ int result, buflen, tries;
+ char request_body[2048];
+ const char *request_body_type = "application/x-www-form-urlencoded";
+ const char *method = "POST";
+ int xmlpost = 0;
- for (opt = vpninfo->cookies; opt; opt = next) {
- next = opt->next;
+ /* Step 1: Unlock software token (if applicable) */
+ if (vpninfo->use_stoken) {
+ result = prepare_stoken(vpninfo);
+ if (result)
+ return result;
+ }
- free(opt->option);
- free(opt->value);
- free(opt);
- }
- vpninfo->cookies = NULL;
- } else
- free(host);
+ /*
+ * Step 2: Probe for XML POST compatibility
+ *
+ * This can get stuck in a redirect loop, so give up after any of:
+ *
+ * a) HTTP error (e.g. 400 Bad Request)
+ * b) Same-host redirect (e.g. Location: /foo/bar)
+ * c) Three redirects without seeing a plausible login form
+ */
+ result = xmlpost_initial_req(vpninfo, request_body, sizeof(request_body));
+ if (result < 0)
+ return result;
- free(vpninfo->redirect_url);
- vpninfo->redirect_url = NULL;
+ for (tries = 0; ; tries++) {
+ if (tries == 3)
+ break;
+ buflen = do_https_request(vpninfo, method, request_body_type, request_body,
+ &form_buf, 0);
+ if (buflen == -EINVAL)
+ break;
+ if (buflen < 0)
+ return buflen;
- goto retry;
- } else if (strstr(vpninfo->redirect_url, "://")) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Cannot follow redirection to non-https URL '%s'\n"),
- vpninfo->redirect_url);
- free(vpninfo->redirect_url);
- vpninfo->redirect_url = NULL;
+ if (vpninfo->redirect_type == REDIR_TYPE_LOCAL)
+ break;
+ else if (vpninfo->redirect_type != REDIR_TYPE_NONE)
+ continue;
+
+ result = parse_xml_response(vpninfo, form_buf, &form);
+ if (result < 0)
+ break;
+
+ xmlpost = 1;
+ vpn_progress(vpninfo, PRG_INFO, _("XML POST enabled\n"));
+ break;
+ }
+
+ /* Step 3: Fetch and parse the login form, if not using XML POST */
+ if (!xmlpost) {
+ buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
+ if (buflen < 0)
+ return buflen;
+
+ result = parse_xml_response(vpninfo, form_buf, &form);
+ if (result < 0) {
free(form_buf);
- return -EINVAL;
- } else if (vpninfo->redirect_url[0] == '/') {
- /* Absolute redirect within same host */
- free(vpninfo->urlpath);
- vpninfo->urlpath = strdup(vpninfo->redirect_url + 1);
- free(vpninfo->redirect_url);
- vpninfo->redirect_url = NULL;
- goto retry;
- } else {
- char *lastslash = NULL;
- if (vpninfo->urlpath)
- lastslash = strrchr(vpninfo->urlpath, '/');
- if (!lastslash) {
- free(vpninfo->urlpath);
- vpninfo->urlpath = vpninfo->redirect_url;
- vpninfo->redirect_url = NULL;
- } else {
- char *oldurl = vpninfo->urlpath;
- *lastslash = 0;
- vpninfo->urlpath = NULL;
- if (asprintf(&vpninfo->urlpath, "%s/%s",
- oldurl, vpninfo->redirect_url) == -1) {
- int err = -errno;
- vpn_progress(vpninfo, PRG_ERR,
- _("Allocating new path for relative redirect failed: %s\n"),
- strerror(-err));
- return err;
- }
- free(oldurl);
- free(vpninfo->redirect_url);
- vpninfo->redirect_url = NULL;
- }
- goto retry;
+ return result;
}
}
- if (!form_buf || result != 200) {
- vpn_progress(vpninfo, PRG_ERR,
- _("Unexpected %d result from server\n"),
- result);
- free(form_buf);
- return -EINVAL;
- }
- if (vpninfo->csd_stuburl) {
+
+ /* Step 4: Run the CSD trojan, if applicable */
+ if (vpninfo->csd_starturl) {
+ char *form_path = NULL;
+
+ if (vpninfo->urlpath) {
+ form_path = strdup(vpninfo->urlpath);
+ if (!form_path) {
+ result = -ENOMEM;
+ goto out;
+ }
+ }
+
+ /* fetch the CSD program, if available */
+ if (vpninfo->csd_stuburl) {
+ buflen = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
+ if (buflen <= 0) {
+ result = -EINVAL;
+ goto out;
+ }
+ }
+
/* This is the CSD stub script, which we now need to run */
result = run_csd_script(vpninfo, form_buf, buflen);
- if (result) {
- free(form_buf);
- return result;
- }
+ if (result)
+ goto out;
+
+ /* vpninfo->urlpath now points to the wait page */
+ while (1) {
+ result = do_https_request(vpninfo, "GET", NULL, NULL, &form_buf, 0);
+ if (result <= 0)
+ break;
+
+ result = check_response_type(vpninfo, form_buf);
+ if (result <= 0)
+ break;
- /* Now we'll be redirected to the waiturl */
- goto retry;
- }
- if (strncmp(form_buf, "<?xml", 5)) {
- /* Not XML? Perhaps it's HTML with a refresh... */
- if (strcasestr(form_buf, "http-equiv=\"refresh\"")) {
vpn_progress(vpninfo, PRG_INFO,
_("Refreshing %s after 1 second...\n"),
vpninfo->urlpath);
sleep(1);
- goto retry;
}
- vpn_progress(vpninfo, PRG_ERR,
- _("Unknown response from server\n"));
- free(form_buf);
- return -EINVAL;
+ if (result < 0)
+ goto out;
+
+ /* refresh the form page, to see if we're authorized now */
+ free(vpninfo->urlpath);
+ vpninfo->urlpath = form_path;
+
+ result = do_https_request(vpninfo, xmlpost ? "POST" : "GET",
+ request_body_type, request_body, &form_buf, 1);
+ if (result < 0)
+ goto out;
+
+ result = parse_xml_response(vpninfo, form_buf, &form);
+ if (result < 0)
+ goto out;
}
- request_body[0] = 0;
- result = parse_xml_response(vpninfo, form_buf, request_body, sizeof(request_body),
- &method, &request_body_type);
- if (!result)
- goto redirect;
+ /* Step 5: Ask the user to fill in the auth form; repeat as necessary */
+ while (1) {
+ request_body[0] = 0;
+ result = handle_auth_form(vpninfo, form, request_body, sizeof(request_body),
+ &method, &request_body_type, xmlpost);
+ if (result < 0 || result == 1)
+ goto out;
+ if (result == 2)
+ break;
- free(form_buf);
+ result = do_https_request(vpninfo, method, request_body_type, request_body,
+ &form_buf, 1);
+ if (result < 0)
+ goto out;
- if (result != 2)
- return result;
+ result = parse_xml_response(vpninfo, form_buf, &form);
+ if (result < 0)
+ goto out;
+ }
/* A return value of 2 means the XML form indicated
success. We _should_ have a cookie... */
@@ -838,12 +1100,19 @@ int openconnect_obtain_cookie(struct openconnect_info *vpninfo)
fetch_config(vpninfo, bu, fu, sha);
}
}
+ result = 0;
+
+out:
+ free(form_buf);
+ free_auth_form(form);
+
if (vpninfo->csd_scriptname) {
unlink(vpninfo->csd_scriptname);
free(vpninfo->csd_scriptname);
vpninfo->csd_scriptname = NULL;
}
- return 0;
+
+ return result;
}
char *openconnect_create_useragent(const char *base)
@@ -1078,21 +1347,28 @@ static int process_socks_proxy(struct openconnect_info *vpninfo, int ssl_sock)
static int process_http_proxy(struct openconnect_info *vpninfo, int ssl_sock)
{
char buf[MAX_BUF_LEN];
+ struct oc_text_buf *reqbuf;
int buflen, result;
- sprintf(buf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
- sprintf(buf + strlen(buf), "Host: %s\r\n", vpninfo->hostname);
- sprintf(buf + strlen(buf), "User-Agent: %s\r\n", vpninfo->useragent);
- sprintf(buf + strlen(buf), "Proxy-Connection: keep-alive\r\n");
- sprintf(buf + strlen(buf), "Connection: keep-alive\r\n");
- sprintf(buf + strlen(buf), "Accept-Encoding: identity\r\n");
- sprintf(buf + strlen(buf), "\r\n");
+ reqbuf = buf_alloc();
+ buf_append(reqbuf, "CONNECT %s:%d HTTP/1.1\r\n", vpninfo->hostname, vpninfo->port);
+ buf_append(reqbuf, "Host: %s\r\n", vpninfo->hostname);
+ buf_append(reqbuf, "User-Agent: %s\r\n", vpninfo->useragent);
+ buf_append(reqbuf, "Proxy-Connection: keep-alive\r\n");
+ buf_append(reqbuf, "Connection: keep-alive\r\n");
+ buf_append(reqbuf, "Accept-Encoding: identity\r\n");
+ buf_append(reqbuf, "\r\n");
+
+ if (buf_error(reqbuf))
+ return buf_free(reqbuf);
vpn_progress(vpninfo, PRG_INFO,
_("Requesting HTTP proxy connection to %s:%d\n"),
vpninfo->hostname, vpninfo->port);
- result = proxy_write(vpninfo, ssl_sock, (unsigned char *)buf, strlen(buf));
+ result = proxy_write(vpninfo, ssl_sock, (unsigned char *)reqbuf->data, reqbuf->pos);
+ buf_free(reqbuf);
+
if (result) {
vpn_progress(vpninfo, PRG_ERR,
_("Sending proxy request failed: %s\n"),
diff --git a/libopenconnect.map.in b/libopenconnect.map.in
index 2539335..cd4d87c 100644
--- a/libopenconnect.map.in
+++ b/libopenconnect.map.in
@@ -1,3 +1,8 @@
+OPENCONNECT_2.2 {
+ global:
+ openconnect_set_reported_os;
+};
+
OPENCONNECT_2.1 {
global:
openconnect_has_stoken_support;
diff --git a/library.c b/library.c
index c8db968..73e7c54 100644
--- a/library.c
+++ b/library.c
@@ -30,6 +30,8 @@
#include LIBSTOKEN_HDR
#endif
+#include <libxml/tree.h>
+
#include "openconnect-internal.h"
struct openconnect_info *openconnect_vpninfo_new (char *useragent,
@@ -50,11 +52,7 @@ struct openconnect_info *openconnect_vpninfo_new (char *useragent,
vpninfo->progress = progress;
vpninfo->cbdata = privdata?:vpninfo;
vpninfo->cancel_fd = -1;
-#ifdef __APPLE__
- vpninfo->csd_xmltag = "csdMac";
-#else
- vpninfo->csd_xmltag = "csdLinux";
-#endif
+ openconnect_set_reported_os(vpninfo, NULL);
#ifdef ENABLE_NLS
bindtextdomain("openconnect", LOCALEDIR);
@@ -63,6 +61,30 @@ struct openconnect_info *openconnect_vpninfo_new (char *useragent,
return vpninfo;
}
+int openconnect_set_reported_os (struct openconnect_info *vpninfo, const char *os)
+{
+ if (!os) {
+#if defined(__APPLE__)
+ os = "mac";
+#else
+ os = sizeof(long) > 4 ? "linux-64" : "linux";
+#endif
+ }
+
+ /* FIXME: is there a special platname for 64-bit Windows? */
+ if (!strcmp(os, "mac"))
+ vpninfo->csd_xmltag = "csdMac";
+ else if (!strcmp(os, "linux") || !strcmp(os, "linux-64"))
+ vpninfo->csd_xmltag = "csdLinux";
+ else if (!strcmp(os, "win"))
+ vpninfo->csd_xmltag = "csd";
+ else
+ return -EINVAL;
+
+ vpninfo->platname = os;
+ return 0;
+}
+
static void free_optlist (struct vpn_option *opt)
{
struct vpn_option *next;
@@ -87,11 +109,20 @@ void openconnect_vpninfo_free (struct openconnect_info *vpninfo)
free(vpninfo->redirect_url);
free(vpninfo->proxy_type);
free(vpninfo->proxy);
+
if (vpninfo->csd_scriptname) {
unlink(vpninfo->csd_scriptname);
free(vpninfo->csd_scriptname);
}
+ free(vpninfo->csd_token);
+ free(vpninfo->csd_ticket);
free(vpninfo->csd_stuburl);
+ free(vpninfo->csd_starturl);
+ free(vpninfo->csd_waiturl);
+ free(vpninfo->csd_preurl);
+ if (vpninfo->opaque_srvdata)
+ xmlFreeNode(vpninfo->opaque_srvdata);
+
/* These are const in openconnect itself, but for consistency of
the library API we do take ownership of the strings we're given,
and thus we have to free them too. */
diff --git a/main.c b/main.c
index 3ca3bc8..cf89a76 100644
--- a/main.c
+++ b/main.c
@@ -111,6 +111,7 @@ enum {
OPT_NON_INTER,
OPT_DTLS_LOCAL_PORT,
OPT_STOKEN,
+ OPT_OS,
};
#ifdef __sun__
@@ -175,6 +176,7 @@ static struct option long_options[] = {
OPTION("non-inter", 0, OPT_NON_INTER),
OPTION("dtls-local-port", 1, OPT_DTLS_LOCAL_PORT),
OPTION("stoken", 2, OPT_STOKEN),
+ OPTION("os", 1, OPT_OS),
OPTION(NULL, 0, 0)
};
@@ -286,6 +288,7 @@ static void usage(void)
printf(" --reconnect-timeout %s\n", _("Connection retry timeout in seconds"));
printf(" --servercert=FINGERPRINT %s\n", _("Server's certificate SHA1 fingerprint"));
printf(" --useragent=STRING %s\n", _("HTTP header User-Agent: field"));
+ printf(" --os=STRING %s\n", _("OS type (linux,linux-64,mac,win) to report"));
printf(" --dtls-local-port=PORT %s\n", _("Set local port for DTLS datagrams"));
printf("\n");
@@ -479,11 +482,7 @@ int main(int argc, char **argv)
vpninfo->reconnect_timeout = 300;
vpninfo->uid_csd = 0;
/* We could let them override this on the command line some day, perhaps */
-#ifdef __APPLE__
- vpninfo->csd_xmltag = "csdMac";
-#else
- vpninfo->csd_xmltag = "csdLinux";
-#endif
+ openconnect_set_reported_os(vpninfo, NULL);
vpninfo->uid_csd = 0;
vpninfo->uid_csd_given = 0;
vpninfo->validate_peer_cert = validate_peer_cert;
@@ -717,6 +716,13 @@ int main(int argc, char **argv)
use_stoken = 1;
token_str = keep_config_arg();
break;
+ case OPT_OS:
+ if (openconnect_set_reported_os(vpninfo, config_arg)) {
+ fprintf(stderr, _("Invalid OS identity \"%s\"\n"),
+ config_arg);
+ exit(1);
+ }
+ break;
default:
usage();
}
diff --git a/openconnect-internal.h b/openconnect-internal.h
index 413bf3a..4b4c455 100644
--- a/openconnect-internal.h
+++ b/openconnect-internal.h
@@ -74,6 +74,8 @@
#endif
#define N_(s) s
+#include <libxml/tree.h>
+
#define SHA1_SIZE 20
#define MD5_SIZE 16
@@ -127,10 +129,16 @@ struct pin_cache {
#define CERT_TYPE_PKCS12 2
#define CERT_TYPE_TPM 3
+#define REDIR_TYPE_NONE 0
+#define REDIR_TYPE_NEWHOST 1
+#define REDIR_TYPE_LOCAL 2
+
struct openconnect_info {
char *redirect_url;
+ int redirect_type;
const char *csd_xmltag;
+ const char *platname;
char *csd_token;
char *csd_ticket;
char *csd_stuburl;
@@ -139,6 +147,7 @@ struct openconnect_info {
char *csd_preurl;
char *csd_scriptname;
+ xmlNode *opaque_srvdata;
#ifdef LIBPROXY_HDR
pxProxyFactory *proxy_factory;
@@ -402,9 +411,12 @@ extern int killed;
int config_lookup_host(struct openconnect_info *vpninfo, const char *host);
/* auth.c */
-int parse_xml_response(struct openconnect_info *vpninfo, char *response,
- char *request_body, int req_len, const char **method,
- const char **request_body_type);
+int parse_xml_response(struct openconnect_info *vpninfo, char *response, struct oc_auth_form **form);
+int handle_auth_form(struct openconnect_info *vpninfo, struct oc_auth_form *form,
+ char *request_body, int req_len, const char **method,
+ const char **request_body_type, int xmlpost);
+void free_auth_form(struct oc_auth_form *form);
+int xmlpost_initial_req(struct openconnect_info *vpninfo, char *request_body, int req_len);
int prepare_stoken(struct openconnect_info *vpninfo);
/* http.c */
diff --git a/openconnect.8.in b/openconnect.8.in
index 4c5b355..b941144 100644
--- a/openconnect.8.in
+++ b/openconnect.8.in
@@ -53,6 +53,7 @@ openconnect \- Connect to Cisco AnyConnect VPN
.OP \-\-reconnect\-timeout
.OP \-\-servercert sha1
.OP \-\-useragent string
+.OP \-\-os string
.B [https://]\fIserver\fB[:\fIport\fB][/\fIgroup\fB]
.YS
@@ -344,6 +345,11 @@ Use
as 'User\-Agent:' field value in HTTP header.
(e.g. \-\-useragent 'Cisco AnyConnect VPN Agent for Windows 2.2.0133')
.TP
+.B \-\-os=STRING
+OS type to report to gateway. Recognized values are: linux, linux-64, mac,
+win. Reporting a different OS type may affect the security policy applied
+to the VPN session.
+.TP
.B \-\-dtls\-local\-port=PORT
Use
.I PORT
diff --git a/openconnect.h b/openconnect.h
index e034d33..5a528c2 100644
--- a/openconnect.h
+++ b/openconnect.h
@@ -34,6 +34,9 @@
#define OPENCONNECT_API_VERSION_MINOR 1
/*
+ * API version 2.2:
+ * - Add openconnect_set_reported_os()
+ *
* API version 2.1:
* - Add openconnect_set_stoken_mode(), openconnect_has_stoken_support()
*
@@ -175,6 +178,7 @@ void openconnect_set_xmlsha1 (struct openconnect_info *, const char *, int size)
void openconnect_set_cafile (struct openconnect_info *, char *);
void openconnect_setup_csd (struct openconnect_info *, uid_t, int silent, char *wrapper);
+int openconnect_set_reported_os (struct openconnect_info *, const char *os);
void openconnect_set_client_cert (struct openconnect_info *, char *cert, char *sslkey);
/* This is *not* yours and must not be destroyed with X509_free(). It
diff --git a/openssl.c b/openssl.c
index c163265..6acdf5c 100644
--- a/openssl.c
+++ b/openssl.c
@@ -107,7 +107,7 @@ int openconnect_SSL_write(struct openconnect_info *vpninfo, char *buf, size_t le
else if (err == SSL_ERROR_WANT_WRITE)
FD_SET(vpninfo->ssl_fd, &wr_set);
else {
- vpn_progress(vpninfo, PRG_ERR, _("Failed to write to SSL socket"));
+ vpn_progress(vpninfo, PRG_ERR, _("Failed to write to SSL socket\n"));
openconnect_report_ssl_errors(vpninfo);
return -EIO;
}
diff --git a/www/changelog.xml b/www/changelog.xml
index a4185a4..57ca0b4 100644
--- a/www/changelog.xml
+++ b/www/changelog.xml
@@ -17,7 +17,10 @@
<ul>
<li><b>OpenConnect HEAD</b>
<ul>
- <li>Add SecurID token support using <a href="http://sourceforge.net/p/stoken/wiki/Home/">libstoken</a>.</li>
+ <li>Add <tt>--os</tt> switch to report a different OS type to the gateway.</li>
+ <li>Support new XML POST format.</li>
+ <li>Fix buffer overflow on long <tt>Location:</tt> and <tt>Set-Cookie:</tt> headers sent from server.</li>
+ <li>Add SecurID token support using <a href="http://stoken.sourceforge.net/">libstoken</a>.</li>
<li>Fix some harmless issues reported by Coverity.</li>
<li>Improve <tt>"Attempting to connect..."</tt> message to be explicit when it's connecting to a proxy.</li>
</ul><br/>