diff options
author | David Woodhouse <David.Woodhouse@intel.com> | 2012-11-05 21:00:28 +0000 |
---|---|---|
committer | David Woodhouse <David.Woodhouse@intel.com> | 2012-11-05 21:00:28 +0000 |
commit | 4a8a947059b9bdfa4f2228c45f868aedd3cecfca (patch) | |
tree | f197f2d2e71abe2e56fb635e38dc3c6b2194c7c2 | |
parent | 70253b3958eb3b12af92cd222c2d98317cbba56c (diff) | |
parent | c7ddd3bd20163efc54f8555cc6d3858425ee1a2b (diff) | |
download | openconnect-4a8a947059b9bdfa4f2228c45f868aedd3cecfca.tar.gz openconnect-4a8a947059b9bdfa4f2228c45f868aedd3cecfca.tar.bz2 openconnect-4a8a947059b9bdfa4f2228c45f868aedd3cecfca.zip |
Merge branch 'xmlpost-v2' of git://github.com/cernekee/openconnect
-rw-r--r-- | auth.c | 481 | ||||
-rw-r--r-- | http.c | 620 | ||||
-rw-r--r-- | libopenconnect.map.in | 5 | ||||
-rw-r--r-- | library.c | 41 | ||||
-rw-r--r-- | main.c | 16 | ||||
-rw-r--r-- | openconnect-internal.h | 18 | ||||
-rw-r--r-- | openconnect.8.in | 6 | ||||
-rw-r--r-- | openconnect.h | 4 | ||||
-rw-r--r-- | openssl.c | 2 | ||||
-rw-r--r-- | www/changelog.xml | 5 |
10 files changed, 927 insertions, 271 deletions
@@ -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 @@ -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; @@ -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. */ @@ -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 @@ -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/> |