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