2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008-2011 Intel Corporation.
5 * Copyright © 2008 Nick Andrew <nick@nick-andrew.net>
7 * Author: David Woodhouse <dwmw2@infradead.org>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * version 2.1, as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to:
21 * Free Software Foundation, Inc.
22 * 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301 USA
35 #include <libxml/parser.h>
36 #include <libxml/tree.h>
38 #include "openconnect-internal.h"
40 static int append_opt(char *body, int bodylen, char *opt, char *name)
42 int len = strlen(body);
45 if (len >= bodylen - 1)
51 if (isalnum((int)(unsigned char)*opt)) {
52 if (len >= bodylen - 1)
56 if (len >= bodylen - 3)
58 sprintf(body+len, "%%%02x", *opt);
64 if (len >= bodylen - 1)
68 while (name && *name) {
69 if (isalnum((int)(unsigned char)*name)) {
70 if (len >= bodylen - 1)
74 if (len >= bodylen - 3)
76 sprintf(body+len, "%%%02X", *name);
86 static int append_form_opts(struct openconnect_info *vpninfo,
87 struct oc_auth_form *form, char *body, int bodylen)
89 struct oc_form_opt *opt;
92 for (opt = form->opts; opt; opt = opt->next) {
93 ret = append_opt(body, bodylen, opt->name, opt->value);
101 * Maybe we should offer this choice to the user. So far we've only
102 * ever seen it offer bogus choices though -- between certificate and
103 * password authentication, when the former has already failed.
104 * So we just accept the first option with an auth-type property.
107 static int parse_auth_choice(struct openconnect_info *vpninfo, struct oc_auth_form *form,
110 struct oc_form_opt_select *opt;
112 opt = calloc(1, sizeof(*opt));
116 opt->form.type = OC_FORM_OPT_SELECT;
117 opt->form.name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
118 opt->form.label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
120 if (!opt->form.name) {
121 vpn_progress(vpninfo, PRG_ERR, _("Form choice has no name\n"));
126 for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
128 struct oc_choice *choice;
130 if (xml_node->type != XML_ELEMENT_NODE)
133 if (strcmp((char *)xml_node->name, "option"))
136 form_id = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
141 opt = realloc(opt, sizeof(*opt) +
142 opt->nr_choices * sizeof(*choice));
146 choice = &opt->choices[opt->nr_choices-1];
148 choice->name = form_id;
149 choice->label = (char *)xmlNodeGetContent(xml_node);
150 choice->auth_type = (char *)xmlGetProp(xml_node, (unsigned char *)"auth-type");
151 choice->override_name = (char *)xmlGetProp(xml_node, (unsigned char *)"override-name");
152 choice->override_label = (char *)xmlGetProp(xml_node, (unsigned char *)"override-label");
155 /* We link the choice _first_ so it's at the top of what we present
157 opt->form.next = form->opts;
158 form->opts = &opt->form;
164 * = 0, when form was cancelled
165 * = 1, when form was parsed
167 static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *form,
168 xmlNode *xml_node, char *body, int bodylen)
170 char *input_type, *input_name, *input_label;
172 for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
173 struct oc_form_opt *opt, **p;
175 if (xml_node->type != XML_ELEMENT_NODE)
178 if (!strcmp((char *)xml_node->name, "select")) {
179 if (parse_auth_choice(vpninfo, form, xml_node))
183 if (strcmp((char *)xml_node->name, "input")) {
184 vpn_progress(vpninfo, PRG_TRACE,
185 _("name %s not input\n"), xml_node->name);
189 input_type = (char *)xmlGetProp(xml_node, (unsigned char *)"type");
191 vpn_progress(vpninfo, PRG_INFO,
192 _("No input type in form\n"));
196 if (!strcmp(input_type, "submit") || !strcmp(input_type, "reset")) {
201 input_name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
203 vpn_progress(vpninfo, PRG_INFO,
204 _("No input name in form\n"));
208 input_label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
210 opt = calloc(1, sizeof(*opt));
218 if (!strcmp(input_type, "hidden")) {
219 opt->type = OC_FORM_OPT_HIDDEN;
220 opt->value = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
221 } else if (!strcmp(input_type, "text"))
222 opt->type = OC_FORM_OPT_TEXT;
223 else if (!strcmp(input_type, "password"))
224 opt->type = OC_FORM_OPT_PASSWORD;
226 vpn_progress(vpninfo, PRG_INFO,
227 _("Unknown input type %s in form\n"),
237 opt->name = input_name;
238 opt->label = input_label;
247 vpn_progress(vpninfo, PRG_TRACE, _("Fixed options give %s\n"), body);
252 static char *xmlnode_msg(xmlNode *xml_node)
254 char *fmt = (char *)xmlNodeGetContent(xml_node);
255 char *result, *params[2], *pct;
259 if (!fmt || !fmt[0]) {
264 len = strlen(fmt) + 1;
266 params[0] = (char *)xmlGetProp(xml_node, (unsigned char *)"param1");
268 len += strlen(params[0]);
269 params[1] = (char *)xmlGetProp(xml_node, (unsigned char *)"param2");
271 len += strlen(params[1]);
273 result = malloc(len);
282 for (pct = strchr(result, '%'); pct;
283 (pct = strchr(pct, '%'))) {
286 /* We only cope with '%s' */
290 if (params[nr_params]) {
291 paramlen = strlen(params[nr_params]);
292 /* Move rest of fmt string up... */
293 memmove(pct - 1 + paramlen, pct + 2, strlen(pct) - 1);
294 /* ... and put the string parameter in where the '%s' was */
295 memcpy(pct, params[nr_params], paramlen);
300 if (++nr_params == 2)
311 * = 0, when form parsed and POST required
312 * = 1, when response was cancelled by user
313 * = 2, when form indicates that login was already successful
315 int parse_xml_response(struct openconnect_info *vpninfo, char *response,
316 char *request_body, int req_len, const char **method,
317 const char **request_body_type)
319 struct oc_auth_form *form;
323 struct vpn_option *opt, *next;
325 form = calloc(1, sizeof(*form));
329 xml_doc = xmlReadMemory(response, strlen(response), "noname.xml", NULL, 0);
331 vpn_progress(vpninfo, PRG_ERR,
332 _("Failed to parse server response\n"));
333 vpn_progress(vpninfo, PRG_TRACE,
334 _("Response was:%s\n"), response);
339 xml_node = xmlDocGetRootElement(xml_doc);
340 if (xml_node->type != XML_ELEMENT_NODE || strcmp((char *)xml_node->name, "auth")) {
341 vpn_progress(vpninfo, PRG_ERR,
342 _("XML response has no \"auth\" root node\n"));
347 form->auth_id = (char *)xmlGetProp(xml_node, (unsigned char *)"id");
348 if (!strcmp(form->auth_id, "success")) {
353 if (vpninfo->nopasswd) {
354 vpn_progress(vpninfo, PRG_ERR,
355 _("Asked for password but '--no-passwd' set\n"));
360 for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
361 if (xml_node->type != XML_ELEMENT_NODE)
364 if (!strcmp((char *)xml_node->name, "banner")) {
366 form->banner = xmlnode_msg(xml_node);
367 } else if (!strcmp((char *)xml_node->name, "message")) {
369 form->message = xmlnode_msg(xml_node);
370 } else if (!strcmp((char *)xml_node->name, "error")) {
372 form->error = xmlnode_msg(xml_node);
373 } else if (!strcmp((char *)xml_node->name, "form")) {
375 form->method = (char *)xmlGetProp(xml_node, (unsigned char *)"method");
376 form->action = (char *)xmlGetProp(xml_node, (unsigned char *)"action");
377 if (!form->method || !form->action ||
378 strcasecmp(form->method, "POST") || !form->action[0]) {
379 vpn_progress(vpninfo, PRG_ERR,
380 _("Cannot handle form method='%s', action='%s'\n"),
381 form->method, form->action);
385 vpninfo->redirect_url = strdup(form->action);
387 ret = parse_form(vpninfo, form, xml_node, request_body, req_len);
390 } else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, "csd")) {
391 if (!vpninfo->csd_token)
392 vpninfo->csd_token = (char *)xmlGetProp(xml_node,
393 (unsigned char *)"token");
394 if (!vpninfo->csd_ticket)
395 vpninfo->csd_ticket = (char *)xmlGetProp(xml_node,
396 (unsigned char *)"ticket");
397 } else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, vpninfo->csd_xmltag)) {
398 vpninfo->csd_stuburl = (char *)xmlGetProp(xml_node,
399 (unsigned char *)"stuburl");
400 vpninfo->csd_starturl = (char *)xmlGetProp(xml_node,
401 (unsigned char *)"starturl");
402 vpninfo->csd_waiturl = (char *)xmlGetProp(xml_node,
403 (unsigned char *)"waiturl");
404 vpninfo->csd_preurl = strdup(vpninfo->urlpath);
407 if (vpninfo->csd_token && vpninfo->csd_ticket && vpninfo->csd_starturl && vpninfo->csd_waiturl) {
408 /* First, redirect to the stuburl -- we'll need to fetch and run that */
409 vpninfo->redirect_url = strdup(vpninfo->csd_stuburl);
411 /* AB: remove all cookies */
412 for (opt = vpninfo->cookies; opt; opt = next) {
419 vpninfo->cookies = NULL;
426 vpn_progress(vpninfo, PRG_INFO, "%s\n", form->message);
428 vpn_progress(vpninfo, PRG_ERR, "%s\n", form->error);
433 if (vpninfo->process_auth_form)
434 ret = vpninfo->process_auth_form(vpninfo->cbdata, form);
436 vpn_progress(vpninfo, PRG_ERR, _("No form handler; cannot authenticate."));
442 ret = append_form_opts(vpninfo, form, request_body, req_len);
445 *request_body_type = "application/x-www-form-urlencoded";
450 struct oc_form_opt *tmp = form->opts->next;
451 if (form->opts->type == OC_FORM_OPT_TEXT ||
452 form->opts->type == OC_FORM_OPT_PASSWORD ||
453 form->opts->type == OC_FORM_OPT_HIDDEN)
454 free(form->opts->value);
455 else if (form->opts->type == OC_FORM_OPT_SELECT) {
456 struct oc_form_opt_select *sel = (void *)form->opts;
459 for (i=0; i < sel->nr_choices; i++) {
460 free(sel->choices[i].name);
461 free(sel->choices[i].label);
462 free(sel->choices[i].auth_type);
463 free(sel->choices[i].override_name);
464 free(sel->choices[i].override_label);
467 free(form->opts->label);
468 free(form->opts->name);