1 /*
2 * OpenConnect (SSL + DTLS) VPN client
3 *
4 * Copyright © 2008-2015 Intel Corporation.
5 *
6 * Author: David Woodhouse <dwmw2@infradead.org>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * version 2.1, as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 */
17
18 /*
19 * Grateful thanks to Tiebing Zhang, who did much of the hard work
20 * of analysing and decoding the protocol.
21 */
22
23 #include <config.h>
24
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <time.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <sys/types.h>
34 #include <stdarg.h>
35 #include <sys/types.h>
36 #ifndef _WIN32
37 #include <sys/wait.h>
38 #endif
39
40 #include <libxml/HTMLparser.h>
41 #include <libxml/HTMLtree.h>
42
43 #include "openconnect-internal.h"
44
45 /* XX: This is actually a lot of duplication with the CSTP version. */
oncp_common_headers(struct openconnect_info * vpninfo,struct oc_text_buf * buf)46 void oncp_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
47 {
48 http_common_headers(vpninfo, buf);
49
50 // buf_append(buf, "Content-Length: 256\r\n");
51 buf_append(buf, "NCP-Version: 3\r\n");
52 // buf_append(buf, "Accept-Encoding: gzip\r\n");
53 }
54
55
htmlnode_next(xmlNodePtr top,xmlNodePtr node)56 static xmlNodePtr htmlnode_next(xmlNodePtr top, xmlNodePtr node)
57 {
58 if (node->children)
59 return node->children;
60
61 while (!node->next) {
62 node = node->parent;
63 if (!node || node == top)
64 return NULL;
65 }
66 return node->next;
67 }
68
oncp_can_gen_tokencode(struct openconnect_info * vpninfo,struct oc_auth_form * form,struct oc_form_opt * opt)69 static int oncp_can_gen_tokencode(struct openconnect_info *vpninfo,
70 struct oc_auth_form *form,
71 struct oc_form_opt *opt)
72 {
73 if (vpninfo->token_mode == OC_TOKEN_MODE_NONE ||
74 vpninfo->token_bypassed)
75 return -EINVAL;
76
77 if (strcmp(form->auth_id, "frmDefender") &&
78 strcmp(form->auth_id, "frmNextToken") &&
79 strcmp(form->auth_id, "frmTotpToken"))
80 return -EINVAL;
81
82 return can_gen_tokencode(vpninfo, form, opt);
83 }
84
85
parse_input_node(struct openconnect_info * vpninfo,struct oc_auth_form * form,xmlNodePtr node,const char * submit_button)86 static int parse_input_node(struct openconnect_info *vpninfo, struct oc_auth_form *form,
87 xmlNodePtr node, const char *submit_button)
88 {
89 char *type = (char *)xmlGetProp(node, (unsigned char *)"type");
90 struct oc_form_opt **p = &form->opts;
91 struct oc_form_opt *opt;
92 int ret = 0;
93
94 if (!type)
95 return -EINVAL;
96
97 opt = calloc(1, sizeof(*opt));
98 if (!opt) {
99 ret = -ENOMEM;
100 goto out;
101 }
102
103 if (!strcasecmp(type, "hidden")) {
104 opt->type = OC_FORM_OPT_HIDDEN;
105 xmlnode_get_prop(node, "name", &opt->name);
106 xmlnode_get_prop(node, "value", &opt->_value);
107 /* XXX: Handle tz_offset / tz */
108 } else if (!strcasecmp(type, "password")) {
109 opt->type = OC_FORM_OPT_PASSWORD;
110 xmlnode_get_prop(node, "name", &opt->name);
111 if (asprintf(&opt->label, "%s:", opt->name) == -1) {
112 ret = -ENOMEM;
113 goto out;
114 }
115 if (!oncp_can_gen_tokencode(vpninfo, form, opt))
116 opt->type = OC_FORM_OPT_TOKEN;
117 } else if (!strcasecmp(type, "text")) {
118 opt->type = OC_FORM_OPT_TEXT;
119 xmlnode_get_prop(node, "name", &opt->name);
120 if (asprintf(&opt->label, "%s:", opt->name) == -1) {
121 ret = -ENOMEM;
122 goto out;
123 }
124 } else if (!strcasecmp(type, "username")) {
125 opt->type = OC_FORM_OPT_TEXT;
126 xmlnode_get_prop(node, "name", &opt->name);
127 if (asprintf(&opt->label, "%s:", opt->name) == -1) {
128 ret = -ENOMEM;
129 goto out;
130 }
131 } else if (!strcasecmp(type, "submit")) {
132 xmlnode_get_prop(node, "name", &opt->name);
133 if (opt->name && (!strcmp(opt->name, submit_button) ||
134 !strcmp(opt->name, "sn-postauth-proceed") ||
135 !strcmp(opt->name, "sn-preauth-proceed") ||
136 !strcmp(opt->name, "secidactionEnter"))) {
137 /* Use this as the 'Submit' action for the form, by
138 implicitly adding it as a hidden option. */
139 xmlnode_get_prop(node, "value", &opt->_value);
140 opt->type = OC_FORM_OPT_HIDDEN;
141 } else {
142 vpn_progress(vpninfo, PRG_DEBUG,
143 _("Ignoring unknown form submit item '%s'\n"),
144 opt->name);
145 ret = -EINVAL;
146 goto out;
147 }
148 } else if (!strcasecmp(type, "checkbox")) {
149 opt->type = OC_FORM_OPT_HIDDEN;
150 xmlnode_get_prop(node, "name", &opt->name);
151 xmlnode_get_prop(node, "value", &opt->_value);
152 } else {
153 vpn_progress(vpninfo, PRG_DEBUG,
154 _("Ignoring unknown form input type '%s'\n"),
155 type);
156 ret = -EINVAL;
157 goto out;
158 }
159
160 /* Append to the existing list */
161 while (*p) {
162 if (!strcmp((*p)->name, opt->name)) {
163 vpn_progress(vpninfo, PRG_DEBUG,
164 _("Discarding duplicate option '%s'\n"),
165 opt->name);
166 free_opt(opt);
167 goto out;
168 }
169 p = &(*p)->next;
170 }
171 *p = opt;
172 out:
173 if (ret)
174 free_opt(opt);
175 free(type);
176 return ret;
177 }
178
parse_select_node(struct openconnect_info * vpninfo,struct oc_auth_form * form,xmlNodePtr node)179 static int parse_select_node(struct openconnect_info *vpninfo, struct oc_auth_form *form,
180 xmlNodePtr node)
181 {
182 xmlNodePtr child;
183 struct oc_form_opt_select *opt;
184 struct oc_choice *choice;
185
186 opt = calloc(1, sizeof(*opt));
187 if (!opt)
188 return -ENOMEM;
189
190 xmlnode_get_prop(node, "name", &opt->form.name);
191 opt->form.label = strdup(opt->form.name);
192 opt->form.type = OC_FORM_OPT_SELECT;
193 if (!strcmp(opt->form.name, "realm"))
194 form->authgroup_opt = opt;
195
196 for (child = node->children; child; child = child->next) {
197 struct oc_choice **new_choices;
198 if (!child->name || strcasecmp((const char *)child->name, "option"))
199 continue;
200
201 choice = calloc(1, sizeof(*choice));
202 if (!choice) {
203 free_opt((void *)opt);
204 return -ENOMEM;
205 }
206
207 xmlnode_get_prop(node, "name", &choice->name);
208 choice->label = (char *)xmlNodeGetContent(child);
209 choice->name = strdup(choice->label);
210 new_choices = realloc(opt->choices, sizeof(opt->choices[0]) * (opt->nr_choices+1));
211 if (!new_choices) {
212 free_opt((void *)opt);
213 free(choice);
214 return -ENOMEM;
215 }
216 opt->choices = new_choices;
217 opt->choices[opt->nr_choices++] = choice;
218 }
219
220 /* Prepend to the existing list */
221 opt->form.next = form->opts;
222 form->opts = &opt->form;
223 return 0;
224 }
225
parse_form_node(struct openconnect_info * vpninfo,xmlNodePtr node,const char * submit_button)226 static struct oc_auth_form *parse_form_node(struct openconnect_info *vpninfo,
227 xmlNodePtr node, const char *submit_button)
228 {
229 struct oc_auth_form *form = calloc(1, sizeof(*form));
230 xmlNodePtr child;
231
232 if (!form)
233 return NULL;
234
235 xmlnode_get_prop(node, "method", &form->method);
236 xmlnode_get_prop(node, "action", &form->action);
237 if (!form->method || strcasecmp(form->method, "POST") ||
238 !form->action || !form->action[0]) {
239 vpn_progress(vpninfo, PRG_ERR,
240 _("Cannot handle form method='%s', action='%s'\n"),
241 form->method, form->action);
242 free(form);
243 return NULL;
244 }
245 xmlnode_get_prop(node, "name", &form->auth_id);
246 form->banner = strdup(form->auth_id);
247
248 for (child = htmlnode_next(node, node); child && child != node; child = htmlnode_next(node, child)) {
249 if (!child->name)
250 continue;
251
252 if (!strcasecmp((char *)child->name, "input"))
253 parse_input_node(vpninfo, form, child, submit_button);
254 else if (!strcasecmp((char *)child->name, "select")) {
255 parse_select_node(vpninfo, form, child);
256 /* Skip its children */
257 while (child->children)
258 child = child->last;
259 } else if (!strcasecmp((char *)child->name, "textarea")) {
260 /* display the post sign-in message, if any */
261 char *fieldname = (char *)xmlGetProp(child, (unsigned char *)"name");
262 if (fieldname && (!strcasecmp(fieldname, "sn-postauth-text") ||
263 !strcasecmp(fieldname, "sn-preauth-text"))) {
264 char *postauth_msg = (char *)xmlNodeGetContent(child);
265 if (postauth_msg) {
266 free(form->banner);
267 form->banner = postauth_msg;
268 }
269 } else {
270 vpn_progress(vpninfo, PRG_ERR,
271 _("Unknown textarea field: '%s'\n"), fieldname);
272 }
273 free(fieldname);
274 }
275 }
276 return form;
277 }
278
find_form_node(xmlDocPtr doc)279 static xmlNodePtr find_form_node(xmlDocPtr doc)
280 {
281 xmlNodePtr root, node;
282
283 for (root = node = xmlDocGetRootElement(doc); node; node = htmlnode_next(root, node)) {
284 if (node->name && !strcasecmp((char *)node->name, "form"))
285 return node;
286 }
287 return NULL;
288 }
289
oncp_send_tncc_command(struct openconnect_info * vpninfo,int start)290 int oncp_send_tncc_command(struct openconnect_info *vpninfo, int start)
291 {
292 const char *dspreauth = vpninfo->csd_token, *dsurl = vpninfo->csd_starturl ? : "null";
293 struct oc_text_buf *buf;
294 buf = buf_alloc();
295
296 if (start) {
297 buf_append(buf, "start\n");
298 buf_append(buf, "IC=%s\n", vpninfo->hostname);
299 buf_append(buf, "Cookie=%s\n", dspreauth);
300 buf_append(buf, "DSSIGNIN=%s\n", dsurl);
301 } else {
302 buf_append(buf, "setcookie\n");
303 buf_append(buf, "Cookie=%s\n", dspreauth);
304 }
305
306 if (buf_error(buf)) {
307 vpn_progress(vpninfo, PRG_ERR,
308 _("Failed to allocate memory for communication with TNCC\n"));
309 return buf_free(buf);
310 }
311 if (cancellable_send(vpninfo, vpninfo->tncc_fd, buf->data, buf->pos) != buf->pos) {
312 vpn_progress(vpninfo, PRG_ERR,
313 _("Failed to send command to TNCC\n"));
314 buf_free(buf);
315 return -EIO;
316 }
317
318 /* Mainloop timers need to know the last Trojan was invoked */
319 vpninfo->last_trojan = time(NULL);
320 return buf_free(buf);
321 }
322
check_cookie_success(struct openconnect_info * vpninfo)323 static int check_cookie_success(struct openconnect_info *vpninfo)
324 {
325 const char *dslast = NULL, *dsfirst = NULL, *dsurl = NULL, *dsid = NULL;
326 struct oc_vpn_option *cookie;
327 struct oc_text_buf *buf;
328
329 for (cookie = vpninfo->cookies; cookie; cookie = cookie->next) {
330 if (!strcmp(cookie->option, "DSFirstAccess"))
331 dsfirst = cookie->value;
332 else if (!strcmp(cookie->option, "DSLastAccess"))
333 dslast = cookie->value;
334 else if (!strcmp(cookie->option, "DSID"))
335 dsid = cookie->value;
336 else if (!strcmp(cookie->option, "DSSignInUrl"))
337 dsurl = cookie->value;
338 else if (!strcmp(cookie->option, "DSSIGNIN")) {
339 free(vpninfo->csd_starturl);
340 vpninfo->csd_starturl = strdup(cookie->value);
341 } else if (!strcmp(cookie->option, "DSPREAUTH")) {
342 free(vpninfo->csd_token);
343 vpninfo->csd_token = strdup(cookie->value);
344 }
345 }
346 if (!dsid)
347 return -ENOENT;
348
349 if (vpninfo->tncc_fd != -1) {
350 /* update TNCC once we get a DSID cookie */
351 oncp_send_tncc_command(vpninfo, 0);
352 }
353
354 /* XXX: Do these need escaping? Could they theoreetically have semicolons in? */
355 buf = buf_alloc();
356 buf_append(buf, "DSID=%s", dsid);
357 if (dsfirst)
358 buf_append(buf, "; DSFirst=%s", dsfirst);
359 if (dslast)
360 buf_append(buf, "; DSLast=%s", dslast);
361 if (dsurl)
362 buf_append(buf, "; DSSignInUrl=%s", dsurl);
363 if (buf_error(buf))
364 return buf_free(buf);
365 free(vpninfo->cookie);
366 vpninfo->cookie = buf->data;
367 buf->data = NULL;
368 buf_free(buf);
369 return 0;
370 }
371 #ifdef _WIN32
tncc_preauth(struct openconnect_info * vpninfo)372 static int tncc_preauth(struct openconnect_info *vpninfo)
373 {
374 vpn_progress(vpninfo, PRG_ERR,
375 _("TNCC support not implemented yet on Windows\n"));
376 return -EOPNOTSUPP;
377 }
378 #else
tncc_preauth(struct openconnect_info * vpninfo)379 static int tncc_preauth(struct openconnect_info *vpninfo)
380 {
381 int sockfd[2];
382 pid_t pid;
383 const char *dspreauth = vpninfo->csd_token;
384 char recvbuf[1024];
385 int len, count, ret;
386
387 if (!dspreauth) {
388 vpn_progress(vpninfo, PRG_ERR,
389 _("No DSPREAUTH cookie; not attempting TNCC\n"));
390 return -EINVAL;
391 }
392
393 #ifdef SOCK_CLOEXEC
394 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockfd))
395 #endif
396 {
397 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd))
398 return -errno;
399
400 set_fd_cloexec(sockfd[0]);
401 set_fd_cloexec(sockfd[1]);
402 }
403 pid = fork();
404 if (pid == -1) {
405 close(sockfd[0]);
406 close(sockfd[1]);
407 return -errno;
408 }
409
410 if (!pid) {
411 int i;
412 /* Fork again to detach grandchild */
413 if (fork())
414 exit(1);
415
416 close(sockfd[1]);
417 /* The duplicated fd does not have O_CLOEXEC */
418 dup2(sockfd[0], 0);
419 /* We really don't want anything going to our stdout.
420 Redirect the child's stdout, to our stderr. */
421 dup2(2, 1);
422 /* And close everything else.*/
423 for (i = 3; i < 1024 ; i++)
424 close(i);
425
426 if (setenv("TNCC_SHA256", openconnect_get_peer_cert_hash(vpninfo)+11, 1)) /* remove initial 'pin-sha256:' */
427 goto out;
428 if (setenv("TNCC_HOSTNAME", vpninfo->localname, 1))
429 goto out;
430 if (!vpninfo->trojan_interval) {
431 char is[32];
432 snprintf(is, 32, "%d", vpninfo->trojan_interval);
433 if (setenv("TNCC_INTERVAL", is, 1))
434 goto out;
435 }
436
437 execl(vpninfo->csd_wrapper, vpninfo->csd_wrapper, vpninfo->hostname, NULL);
438 out:
439 fprintf(stderr, _("Failed to exec TNCC script %s: %s\n"),
440 vpninfo->csd_wrapper, strerror(errno));
441 exit(1);
442 }
443 waitpid(pid, NULL, 0);
444 close(sockfd[0]);
445 vpninfo->tncc_fd = sockfd[1];
446
447 ret = oncp_send_tncc_command(vpninfo, 1);
448 if (ret < 0) {
449 err:
450 close(vpninfo->tncc_fd);
451 vpninfo->tncc_fd = -1;
452 return ret;
453 }
454
455 vpn_progress(vpninfo, PRG_DEBUG,
456 _("Sent start; waiting for response from TNCC\n"));
457
458 /* First line: HTTP-like response code. */
459 len = cancellable_gets(vpninfo, sockfd[1], recvbuf, sizeof(recvbuf));
460 if (len < 0) {
461 respfail:
462 vpn_progress(vpninfo, PRG_ERR,
463 _("Failed to read response from TNCC\n"));
464 ret = -EIO;
465 goto err;
466 }
467
468 if (strcmp(recvbuf, "200")) {
469 vpn_progress(vpninfo, PRG_ERR,
470 _("Received unsuccessful %s response from TNCC\n"),
471 recvbuf);
472 ret = -EINVAL;
473 goto err;
474 }
475
476 vpn_progress(vpninfo, PRG_TRACE, _("TNCC response 200 OK\n"));
477
478 /* We're not sure what the second line is. We ignore it. */
479 len = cancellable_gets(vpninfo, sockfd[1], recvbuf, sizeof(recvbuf));
480 if (len < 0)
481 goto respfail;
482
483 vpn_progress(vpninfo, PRG_TRACE, _("Second line of TNCC response: '%s'\n"),
484 recvbuf);
485
486 /* Third line is the DSPREAUTH cookie */
487 len = cancellable_gets(vpninfo, sockfd[1], recvbuf, sizeof(recvbuf));
488 if (len < 0)
489 goto respfail;
490
491 vpn_progress(vpninfo, PRG_DEBUG,
492 _("Got new DSPREAUTH cookie from TNCC: %s\n"),
493 recvbuf);
494 http_add_cookie(vpninfo, "DSPREAUTH", recvbuf, 1);
495
496 /* Fourth line, if present, is the interval to rerun TNCC */
497 len = cancellable_gets(vpninfo, sockfd[1], recvbuf, sizeof(recvbuf));
498 if (len < 0)
499 goto respfail;
500 if (len > 0) {
501 int interval = atoi(recvbuf);
502 if (interval != 0) {
503 vpninfo->trojan_interval = interval;
504 vpn_progress(vpninfo, PRG_DEBUG,
505 _("Got reauth interval from TNCC: %d seconds\n"),
506 interval);
507 }
508 }
509
510 count = 0;
511 do {
512 len = cancellable_gets(vpninfo, sockfd[1], recvbuf,
513 sizeof(recvbuf));
514 if (len < 0)
515 goto respfail;
516 if (len > 0)
517 vpn_progress(vpninfo, PRG_DEBUG,
518 _("Unexpected non-empty line from TNCC "
519 "after DSPREAUTH cookie: '%s'\n"),
520 recvbuf);
521 } while (len && (count++ < 10));
522
523 if (len > 0) {
524 vpn_progress(vpninfo, PRG_ERR,
525 _("Too many non-empty lines from TNCC after "
526 "DSPREAUTH cookie\n"));
527 goto respfail;
528 }
529
530 return 0;
531 }
532 #endif
533
parse_roles_table_node(xmlNodePtr node)534 static struct oc_auth_form *parse_roles_table_node(xmlNodePtr node)
535 {
536 struct oc_auth_form *form;
537 xmlNodePtr table_itr;
538 xmlNodePtr row_itr;
539 xmlNodePtr data_itr;
540 struct oc_form_opt_select *opt;
541 struct oc_choice *choice;
542
543 form = calloc(1, sizeof(*form));
544 if (!form)
545 return NULL;
546
547 opt = calloc(1, sizeof(*opt));
548 if (!opt) {
549 free(form);
550 return NULL;
551 }
552
553 form->opts = &opt->form;
554 opt->form.label = strdup("frmSelectRoles");
555 opt->form.name = strdup("frmSelectRoles");
556 opt->form.type = OC_FORM_OPT_SELECT;
557
558 for (table_itr = node->children; table_itr; table_itr = table_itr->next) {
559 if (!table_itr->name || strcasecmp((const char *)table_itr->name, "tr"))
560 continue;
561 for (row_itr = table_itr->children; row_itr; row_itr = row_itr->next) {
562 if (!row_itr->name || strcasecmp((const char *)row_itr->name, "td"))
563 continue;
564 for (data_itr = row_itr->children; data_itr; data_itr = data_itr->next) {
565 struct oc_choice **new_choices;
566 char *role_link = NULL;
567 char *role_name = NULL;
568
569 if (!data_itr->name || strcasecmp((const char *)data_itr->name, "a"))
570 continue;
571
572 // Discovered <a> tag with role selection.
573 role_link = (char *)xmlGetProp(data_itr, (unsigned char *)"href");
574 if (!role_link)
575 continue;
576
577 role_name = (char *)xmlNodeGetContent(data_itr);
578 if (!role_name) {
579 // some weird case?
580 free(role_link);
581 continue;
582 }
583
584 choice = calloc(1, sizeof(*choice));
585 if (!choice) {
586 free(role_name);
587 free(role_link);
588 free_auth_form(form);
589 return NULL;
590 }
591
592 choice->label = role_name;
593 choice->name = role_link;
594 new_choices = realloc(opt->choices, sizeof(opt->choices[0]) * (opt->nr_choices+1));
595 if (!new_choices) {
596 free(choice);
597 free(role_name);
598 free(role_link);
599 free_auth_form(form);
600 return NULL;
601 }
602 opt->choices = new_choices;
603 opt->choices[opt->nr_choices++] = choice;
604 }
605 }
606 }
607
608 return form;
609 }
610
parse_roles_form_node(xmlNodePtr node)611 static struct oc_auth_form *parse_roles_form_node(xmlNodePtr node)
612 {
613 struct oc_auth_form *form = NULL;
614 xmlNodePtr child;
615
616 // Set form->action here as a redirect url with keys and ids.
617 for (child = htmlnode_next(node, node); child && child != node;
618 child = htmlnode_next(node, child)) {
619 if (child->name && !strcasecmp((char *)child->name, "table")) {
620 char *table_id = (char *)xmlGetProp(child, (unsigned char *)"id");
621
622 if (table_id) {
623 if (!strcmp(table_id, "TABLE_SelectRole_1"))
624 form = parse_roles_table_node(child);
625
626 free(table_id);
627
628 if (form)
629 break;
630 }
631 }
632 }
633
634 return form;
635 }
636
oncp_obtain_cookie(struct openconnect_info * vpninfo)637 int oncp_obtain_cookie(struct openconnect_info *vpninfo)
638 {
639 int ret;
640 struct oc_text_buf *resp_buf = NULL;
641 xmlDocPtr doc = NULL;
642 xmlNodePtr node;
643 struct oc_auth_form *form = NULL;
644 char *form_id = NULL;
645 int try_tncc = !!vpninfo->csd_wrapper;
646
647 resp_buf = buf_alloc();
648 if (buf_error(resp_buf)) {
649 ret = buf_error(resp_buf);
650 goto out;
651 }
652
653 while (1) {
654 char *form_buf = NULL;
655 int role_select = 0;
656 struct oc_text_buf *url;
657
658 if (resp_buf && resp_buf->pos)
659 ret = do_https_request(vpninfo, "POST",
660 "application/x-www-form-urlencoded",
661 resp_buf, &form_buf, 2);
662 else
663 ret = do_https_request(vpninfo, "GET", NULL, NULL,
664 &form_buf, 2);
665
666 if (ret < 0)
667 break;
668
669 url = buf_alloc();
670 buf_append(url, "https://%s", vpninfo->hostname);
671 if (vpninfo->port != 443)
672 buf_append(url, ":%d", vpninfo->port);
673 buf_append(url, "/");
674 if (vpninfo->urlpath)
675 buf_append(url, "%s", vpninfo->urlpath);
676
677 if (buf_error(url)) {
678 free(form_buf);
679 ret = buf_free(url);
680 break;
681 }
682
683 if (!check_cookie_success(vpninfo)) {
684 buf_free(url);
685 free(form_buf);
686 ret = 0;
687 break;
688 }
689
690 doc = htmlReadMemory(form_buf, ret, url->data, NULL,
691 HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING|HTML_PARSE_NONET);
692 buf_free(url);
693 free(form_buf);
694 if (!doc) {
695 vpn_progress(vpninfo, PRG_ERR,
696 _("Failed to parse HTML document\n"));
697 ret = -EINVAL;
698 break;
699 }
700
701 buf_truncate(resp_buf);
702
703 node = find_form_node(doc);
704 if (!node) {
705 if (try_tncc) {
706 try_tncc = 0;
707 ret = tncc_preauth(vpninfo);
708 if (ret)
709 return ret;
710 goto tncc_done;
711 }
712 vpn_progress(vpninfo, PRG_ERR,
713 _("Failed to find or parse web form in login page\n"));
714 ret = -EINVAL;
715 break;
716 }
717 free(form_id);
718 form_id = (char *)xmlGetProp(node, (unsigned char *)"name");
719 if (!form_id) {
720 vpn_progress(vpninfo, PRG_ERR,
721 _("Encountered form with no ID\n"));
722 goto dump_form;
723 } else if (!strcmp(form_id, "frmLogin")) {
724 form = parse_form_node(vpninfo, node, "btnSubmit");
725 if (!form) {
726 ret = -EINVAL;
727 break;
728 }
729 } else if (!strcmp(form_id, "frmDefender") ||
730 !strcmp(form_id, "frmNextToken")) {
731 form = parse_form_node(vpninfo, node, "btnAction");
732 if (!form) {
733 ret = -EINVAL;
734 break;
735 }
736 } else if (!strcmp(form_id, "frmConfirmation")) {
737 form = parse_form_node(vpninfo, node, "btnContinue");
738 if (!form) {
739 ret = -EINVAL;
740 break;
741 }
742 /* XXX: Actually ask the user? */
743 goto form_done;
744 } else if (!strcmp(form_id, "frmSelectRoles")) {
745 form = parse_roles_form_node(node);
746 if (!form) {
747 ret = -EINVAL;
748 break;
749 }
750 role_select = 1;
751 } else if (!strcmp(form_id, "frmTotpToken")) {
752 form = parse_form_node(vpninfo, node, "totpactionEnter");
753 if (!form) {
754 ret = -EINVAL;
755 break;
756 }
757 } else {
758 vpn_progress(vpninfo, PRG_ERR,
759 _("Unknown form ID '%s'\n"),
760 form_id);
761 dump_form:
762 fprintf(stderr, _("Dumping unknown HTML form:\n"));
763 htmlNodeDumpFileFormat(stderr, node->doc, node, NULL, 1);
764 ret = -EINVAL;
765 break;
766 }
767
768 do {
769 ret = process_auth_form(vpninfo, form);
770 } while (ret == OC_FORM_RESULT_NEWGROUP);
771 if (ret)
772 goto out;
773
774 ret = do_gen_tokencode(vpninfo, form);
775 if (ret) {
776 vpn_progress(vpninfo, PRG_ERR, _("Failed to generate OTP tokencode; disabling token\n"));
777 vpninfo->token_bypassed = 1;
778 goto out;
779 }
780
781 /* frmSelectRoles is special; it's actually *links*, not a form. So
782 * we need to process it differently... */
783 if (role_select) {
784 vpninfo->redirect_url = strdup(form->opts[0]._value);
785 goto do_redirect;
786 }
787 form_done:
788 append_form_opts(vpninfo, form, resp_buf);
789 ret = buf_error(resp_buf);
790 if (ret)
791 break;
792
793 vpninfo->redirect_url = form->action;
794 form->action = NULL;
795 do_redirect:
796 free_auth_form(form);
797 form = NULL;
798 handle_redirect(vpninfo);
799
800 tncc_done:
801 xmlFreeDoc(doc);
802 doc = NULL;
803 }
804 out:
805 if (doc)
806 xmlFreeDoc(doc);
807 free(form_id);
808 if (form)
809 free_auth_form(form);
810 buf_free(resp_buf);
811 return ret;
812 }
813