2 * OpenConnect (SSL + DTLS) VPN client
4 * Copyright © 2008-2010 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
34 #include <openssl/err.h>
35 #include <openssl/ui.h>
37 #include <libxml/parser.h>
38 #include <libxml/tree.h>
40 #include "openconnect-internal.h"
42 static int append_opt(char *body, int bodylen, char *opt, char *name)
44 int len = strlen(body);
47 if (len >= bodylen - 1)
53 if (isalnum((int)(unsigned char)*opt)) {
54 if (len >= bodylen - 1)
58 if (len >= bodylen - 3)
60 sprintf(body+len, "%%%02x", *opt);
66 if (len >= bodylen - 1)
70 while (name && *name) {
71 if (isalnum((int)(unsigned char)*name)) {
72 if (len >= bodylen - 1)
76 if (len >= bodylen - 3)
78 sprintf(body+len, "%%%02X", *name);
88 static int append_form_opts(struct openconnect_info *vpninfo,
89 struct oc_auth_form *form, char *body, int bodylen)
91 struct oc_form_opt *opt;
94 for (opt = form->opts; opt; opt = opt->next) {
95 ret = append_opt(body, bodylen, opt->name, opt->value);
103 * Maybe we should offer this choice to the user. So far we've only
104 * ever seen it offer bogus choices though -- between certificate and
105 * password authentication, when the former has already failed.
106 * So we just accept the first option with an auth-type property.
109 static int parse_auth_choice(struct openconnect_info *vpninfo, struct oc_auth_form *form,
112 struct oc_form_opt_select *opt;
114 opt = calloc(1, sizeof(*opt));
118 opt->form.type = OC_FORM_OPT_SELECT;
119 opt->form.name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
120 opt->form.label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
122 if (!opt->form.name) {
123 vpn_progress(vpninfo, PRG_ERR, _("Form choice has no name\n"));
128 for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
130 struct oc_choice *choice;
132 if (xml_node->type != XML_ELEMENT_NODE)
135 if (strcmp((char *)xml_node->name, "option"))
138 form_id = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
143 opt = realloc(opt, sizeof(*opt) +
144 opt->nr_choices * sizeof(*choice));
148 choice = &opt->choices[opt->nr_choices-1];
150 choice->name = form_id;
151 choice->label = (char *)xmlNodeGetContent(xml_node);
152 choice->auth_type = (char *)xmlGetProp(xml_node, (unsigned char *)"auth-type");
153 choice->override_name = (char *)xmlGetProp(xml_node, (unsigned char *)"override-name");
154 choice->override_label = (char *)xmlGetProp(xml_node, (unsigned char *)"override-label");
157 /* We link the choice _first_ so it's at the top of what we present
159 opt->form.next = form->opts;
160 form->opts = &opt->form;
166 * = 0, when form was cancelled
167 * = 1, when form was parsed
169 static int parse_form(struct openconnect_info *vpninfo, struct oc_auth_form *form,
170 xmlNode *xml_node, char *body, int bodylen)
172 char *input_type, *input_name, *input_label;
174 for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
175 struct oc_form_opt *opt, **p;
177 if (xml_node->type != XML_ELEMENT_NODE)
180 if (!strcmp((char *)xml_node->name, "select")) {
181 if (parse_auth_choice(vpninfo, form, xml_node))
185 if (strcmp((char *)xml_node->name, "input")) {
186 vpn_progress(vpninfo, PRG_TRACE,
187 _("name %s not input\n"), xml_node->name);
191 input_type = (char *)xmlGetProp(xml_node, (unsigned char *)"type");
193 vpn_progress(vpninfo, PRG_INFO,
194 _("No input type in form\n"));
198 if (!strcmp(input_type, "submit") || !strcmp(input_type, "reset")) {
203 input_name = (char *)xmlGetProp(xml_node, (unsigned char *)"name");
205 vpn_progress(vpninfo, PRG_INFO,
206 _("No input name in form\n"));
210 input_label = (char *)xmlGetProp(xml_node, (unsigned char *)"label");
212 opt = calloc(1, sizeof(*opt));
220 if (!strcmp(input_type, "hidden")) {
221 opt->type = OC_FORM_OPT_HIDDEN;
222 opt->value = (char *)xmlGetProp(xml_node, (unsigned char *)"value");
223 } else if (!strcmp(input_type, "text"))
224 opt->type = OC_FORM_OPT_TEXT;
225 else if (!strcmp(input_type, "password"))
226 opt->type = OC_FORM_OPT_PASSWORD;
228 vpn_progress(vpninfo, PRG_INFO,
229 _("Unknown input type %s in form\n"),
239 opt->name = input_name;
240 opt->label = input_label;
249 vpn_progress(vpninfo, PRG_TRACE, _("Fixed options give %s\n"), body);
254 static int process_auth_form(struct openconnect_info *vpninfo,
255 struct oc_auth_form *form);
257 static char *xmlnode_msg(xmlNode *xml_node)
259 char *fmt = (char *)xmlNodeGetContent(xml_node);
260 char *result, *params[2], *pct;
264 if (!fmt || !fmt[0]) {
269 len = strlen(fmt) + 1;
271 params[0] = (char *)xmlGetProp(xml_node, (unsigned char *)"param1");
273 len += strlen(params[0]);
274 params[1] = (char *)xmlGetProp(xml_node, (unsigned char *)"param2");
276 len += strlen(params[1]);
278 result = malloc(len);
287 for (pct = strchr(result, '%'); pct;
288 (pct = strchr(pct, '%'))) {
291 /* We only cope with '%s' */
295 if (params[nr_params]) {
296 paramlen = strlen(params[nr_params]);
297 /* Move rest of fmt string up... */
298 memmove(pct - 1 + paramlen, pct + 2, strlen(pct) - 1);
299 /* ... and put the string parameter in where the '%s' was */
300 memcpy(pct, params[nr_params], paramlen);
305 if (++nr_params == 2)
316 * = 0, when form parsed and POST required
317 * = 1, when response was cancelled by user
318 * = 2, when form indicates that login was already successful
320 int parse_xml_response(struct openconnect_info *vpninfo, char *response,
321 char *request_body, int req_len, const char **method,
322 const char **request_body_type)
324 struct oc_auth_form *form;
328 struct vpn_option *opt, *next;
330 form = calloc(1, sizeof(*form));
334 xml_doc = xmlReadMemory(response, strlen(response), "noname.xml", NULL, 0);
336 vpn_progress(vpninfo, PRG_ERR,
337 _("Failed to parse server response\n"));
338 vpn_progress(vpninfo, PRG_TRACE,
339 _("Response was:%s\n"), response);
344 xml_node = xmlDocGetRootElement(xml_doc);
345 if (xml_node->type != XML_ELEMENT_NODE || strcmp((char *)xml_node->name, "auth")) {
346 vpn_progress(vpninfo, PRG_ERR,
347 _("XML response has no \"auth\" root node\n"));
352 form->auth_id = (char *)xmlGetProp(xml_node, (unsigned char *)"id");
353 if (!strcmp(form->auth_id, "success")) {
358 if (vpninfo->nopasswd) {
359 vpn_progress(vpninfo, PRG_ERR,
360 _("Asked for password but '--no-passwd' set\n"));
365 for (xml_node = xml_node->children; xml_node; xml_node = xml_node->next) {
366 if (xml_node->type != XML_ELEMENT_NODE)
369 if (!strcmp((char *)xml_node->name, "banner")) {
371 form->banner = xmlnode_msg(xml_node);
372 } else if (!strcmp((char *)xml_node->name, "message")) {
374 form->message = xmlnode_msg(xml_node);
375 } else if (!strcmp((char *)xml_node->name, "error")) {
377 form->error = xmlnode_msg(xml_node);
378 } else if (!strcmp((char *)xml_node->name, "form")) {
380 form->method = (char *)xmlGetProp(xml_node, (unsigned char *)"method");
381 form->action = (char *)xmlGetProp(xml_node, (unsigned char *)"action");
382 if (!form->method || !form->action ||
383 strcasecmp(form->method, "POST") || !form->action[0]) {
384 vpn_progress(vpninfo, PRG_ERR,
385 _("Cannot handle form method='%s', action='%s'\n"),
386 form->method, form->action);
390 vpninfo->redirect_url = strdup(form->action);
392 ret = parse_form(vpninfo, form, xml_node, request_body, req_len);
395 } else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, "csd")) {
396 if (!vpninfo->csd_token)
397 vpninfo->csd_token = (char *)xmlGetProp(xml_node,
398 (unsigned char *)"token");
399 if (!vpninfo->csd_ticket)
400 vpninfo->csd_ticket = (char *)xmlGetProp(xml_node,
401 (unsigned char *)"ticket");
402 } else if (!vpninfo->csd_scriptname && !strcmp((char *)xml_node->name, "csdLinux")) {
403 vpninfo->csd_stuburl = (char *)xmlGetProp(xml_node,
404 (unsigned char *)"stuburl");
405 vpninfo->csd_starturl = (char *)xmlGetProp(xml_node,
406 (unsigned char *)"starturl");
407 vpninfo->csd_waiturl = (char *)xmlGetProp(xml_node,
408 (unsigned char *)"waiturl");
409 vpninfo->csd_preurl = strdup(vpninfo->urlpath);
412 if (vpninfo->csd_token && vpninfo->csd_ticket && vpninfo->csd_starturl && vpninfo->csd_waiturl) {
413 /* First, redirect to the stuburl -- we'll need to fetch and run that */
414 vpninfo->redirect_url = strdup(vpninfo->csd_stuburl);
416 /* AB: remove all cookies */
417 for (opt = vpninfo->cookies; opt; opt = next) {
424 vpninfo->cookies = NULL;
431 vpn_progress(vpninfo, PRG_INFO, "%s\n", form->message);
433 vpn_progress(vpninfo, PRG_ERR, "%s\n", form->error);
438 if (vpninfo->process_auth_form)
439 ret = vpninfo->process_auth_form(vpninfo->cbdata, form);
441 ret = process_auth_form(vpninfo, form);
445 ret = append_form_opts(vpninfo, form, request_body, req_len);
448 *request_body_type = "application/x-www-form-urlencoded";
453 struct oc_form_opt *tmp = form->opts->next;
454 if (form->opts->type == OC_FORM_OPT_TEXT ||
455 form->opts->type == OC_FORM_OPT_PASSWORD ||
456 form->opts->type == OC_FORM_OPT_HIDDEN)
457 free(form->opts->value);
458 else if (form->opts->type == OC_FORM_OPT_SELECT) {
459 struct oc_form_opt_select *sel = (void *)form->opts;
462 for (i=0; i < sel->nr_choices; i++) {
463 free(sel->choices[i].name);
464 free(sel->choices[i].label);
465 free(sel->choices[i].auth_type);
466 free(sel->choices[i].override_name);
467 free(sel->choices[i].override_label);
470 free(form->opts->label);
471 free(form->opts->name);
489 * = 0, when form was parsed and POST required
490 * = 1, when response was cancelled by user
492 static int process_auth_form(struct openconnect_info *vpninfo,
493 struct oc_auth_form *form)
496 char banner_buf[1024], msg_buf[1024], err_buf[1024];
497 char choice_prompt[1024], choice_resp[80];
498 int ret = 0, input_count=0;
499 struct oc_form_opt *opt;
500 struct oc_form_opt_select *select_opt = NULL;
505 vpn_progress(vpninfo, PRG_ERR, _("Failed to create UI\n"));
509 banner_buf[1023] = 0;
510 snprintf(banner_buf, 1023, "%s\n", form->banner);
511 UI_add_info_string(ui, banner_buf);
515 snprintf(err_buf, 1023, "%s\n", form->error);
516 UI_add_error_string(ui, err_buf);
520 snprintf(msg_buf, 1023, "%s\n", form->message);
521 UI_add_info_string(ui, msg_buf);
524 /* scan for select options first so they are displayed first */
525 for (opt = form->opts; opt; opt = opt->next) {
526 if (opt->type == OC_FORM_OPT_SELECT) {
527 struct oc_choice *choice = NULL;
530 select_opt = (void *)opt;
532 if (!select_opt->nr_choices)
535 if (vpninfo->authgroup &&
536 !strcmp(opt->name, "group_list")) {
537 for (i = 0; i < select_opt->nr_choices; i++) {
538 choice = &select_opt->choices[i];
540 if (!strcmp(vpninfo->authgroup,
542 opt->value = choice->name;
547 vpn_progress(vpninfo, PRG_ERR,
548 _("Auth choice \"%s\" not available\n"),
551 if (!opt->value && select_opt->nr_choices == 1) {
552 choice = &select_opt->choices[0];
553 opt->value = choice->name;
559 snprintf(choice_prompt, 1023, "%s [", opt->label);
560 for (i = 0; i < select_opt->nr_choices; i++) {
561 choice = &select_opt->choices[i];
563 strncat(choice_prompt, "|", 1023 - strlen(choice_prompt));
565 strncat(choice_prompt, choice->label, 1023 - strlen(choice_prompt));
567 strncat(choice_prompt, "]:", 1023 - strlen(choice_prompt));
569 UI_add_input_string(ui, choice_prompt, UI_INPUT_FLAG_ECHO, choice_resp, 1, 80);
574 for (opt = form->opts; opt; opt = opt->next) {
576 if (opt->type == OC_FORM_OPT_TEXT) {
577 if (vpninfo->username &&
578 !strcmp(opt->name, "username")) {
579 opt->value = strdup(vpninfo->username);
585 opt->value=malloc(80);
590 UI_add_input_string(ui, opt->label, UI_INPUT_FLAG_ECHO, opt->value, 1, 80);
594 } else if (opt->type == OC_FORM_OPT_PASSWORD) {
595 if (vpninfo->password &&
596 !strcmp(opt->name, "password")) {
597 opt->value = strdup(vpninfo->password);
598 vpninfo->password = NULL;
604 opt->value=malloc(80);
609 UI_add_input_string(ui, opt->label, 0, opt->value, 1, 80);
621 switch (UI_process(ui)) {
628 vpn_progress(vpninfo, PRG_ERR, _("Invalid inputs\n"));
638 struct oc_choice *choice = NULL;
641 for (i = 0; i < select_opt->nr_choices; i++) {
642 choice = &select_opt->choices[i];
644 if (!strcmp(choice_resp, choice->label)) {
645 select_opt->form.value = choice->name;
649 if (!select_opt->form.value) {
650 vpn_progress(vpninfo, PRG_ERR,
651 _("Auth choice \"%s\" not valid\n"),
657 if (vpninfo->password) {
658 free(vpninfo->password);
659 vpninfo->password = NULL;