/*
 * Copyright (C) 2013, 2014, 2015 Nikos Mavrogiannopoulos
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * This file is part of ocserv.
 *
 * ocserv is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * ocserv is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#include <gnutls/x509.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <ipc.pb-c.h>
#include <base64-helper.h>

#include <vpn.h>
#include "html.h"
#include <worker.h>
#include <common.h>
#include <tlslib.h>

#include <llhttp.h>

#define VERSION_MSG "<version who=\"sg\">0.1(1)</version>\n"

static const char oc_success_msg_head[] =
	"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
	"<config-auth client=\"vpn\" type=\"complete\">\n" VERSION_MSG
	"<auth id=\"success\">\n"
	"<title>SSL VPN Service</title>";

#define OC_SUCCESS_MSG_FOOT "</auth></config-auth>\n"
#define OC_SUCCESS_MSG_FOOT_PROFILE                     \
	"</auth>\n"                                     \
	"<config client=\"vpn\" type=\"private\">"      \
	"<vpn-profile-manifest>"                        \
	"<vpn rev=\"1.0\">"                             \
	"<file type=\"profile\" service-type=\"user\">" \
	"<uri>/profiles/%s</uri>"                       \
	"<hash type=\"sha1\">%s</hash>"                 \
	"</file>"                                       \
	"</vpn>"                                        \
	"</vpn-profile-manifest>\n"                     \
	"</config>"                                     \
	"</config-auth>"

static const char ocv3_success_msg_head[] =
	"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
	"<auth id=\"success\">\n"
	"<title>SSL VPN Service</title>";

static const char ocv3_success_msg_foot[] = "</auth>\n";

#define OC_LOGIN_START                                                     \
	"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"                     \
	"<config-auth client=\"vpn\" type=\"auth-request\">\n" VERSION_MSG \
	"<auth id=\"main\">\n"

#define OC_LOGIN_FORM_START       \
	"<message>%s</message>\n" \
	"<form method=\"post\" action=\"/auth\">\n"

#define OC_LOGIN_END "</form></auth>\n</config-auth>"

#define OC_LOGIN_FORM_INPUT_USER \
	"<input type=\"text\" name=\"username\" label=\"Username:\" />\n"

#define DEFAULT_PASSWD_LABEL "Password:"
#define OC_LOGIN_FORM_INPUT_PASSWORD                                               \
	"<input type=\"password\" name=\"password\" label=\"" DEFAULT_PASSWD_LABEL \
	"\" />\n"
#define OC_LOGIN_FORM_INPUT_PASSWORD_CTR \
	"<input type=\"password\" name=\"secondary_password\" label=\"Password%u:\" />\n"

#define _OCV3_LOGIN_MSG_START(x)                       \
	"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
	"<auth id=\"" x "\">\n"

#define OCV3_LOGIN_START _OCV3_LOGIN_MSG_START("main")
#define OCV3_PASSWD_START _OCV3_LOGIN_MSG_START("passwd")

#define OCV3_LOGIN_END "</form></auth>\n"

#ifdef SUPPORT_OIDC_AUTH
#define HTTP_AUTH_OIDC_PREFIX "Bearer"
#endif

static int basic_auth_handler(worker_st *ws, unsigned int http_ver,
			      const char *msg);

#ifdef SUPPORT_OIDC_AUTH
static int oidc_auth_handler(worker_st *ws, unsigned int http_ver);
#endif

int ws_switch_auth_to(struct worker_st *ws, unsigned int auth)
{
	unsigned int i;

	if (ws->selected_auth && ws->selected_auth->enabled != 0 &&
	    ws->selected_auth->type & auth)
		return 1;

	for (i = 0; i < WSSCONFIG(ws)->auth_methods; i++) {
		if (WSSCONFIG(ws)->auth[i].enabled &&
		    (WSSCONFIG(ws)->auth[i].type & auth) != 0) {
			ws->selected_auth = &WSSCONFIG(ws)->auth[i];
			return 1;
		}
	}
	return 0;
}

/* disables the selected auth method and switches to the next
 * available authentication method, and returns
 * non-zero on success */
int ws_switch_auth_to_next(struct worker_st *ws)
{
	unsigned int i;

	if (!ws->selected_auth) {
		return 0;
	}

	ws->selected_auth->enabled = 0;

	for (i = 0; i < WSSCONFIG(ws)->auth_methods; i++) {
		if (&WSSCONFIG(ws)->auth[i] != ws->selected_auth &&
		    WSSCONFIG(ws)->auth[i].enabled != 0) {
			ws->selected_auth = &WSSCONFIG(ws)->auth[i];
			return 1;
		}
	}
	return 0;
}

static int append_group_idx(worker_st *ws, str_st *str, unsigned int i)
{
	char temp[128];
	const char *name;
	const char *value;

	value = WSRCONFIG(ws)->group_list[i];
	if (WSRCONFIG(ws)->friendly_group_list != NULL &&
	    WSRCONFIG(ws)->friendly_group_list[i] != NULL)
		name = WSRCONFIG(ws)->friendly_group_list[i];
	else
		name = WSRCONFIG(ws)->group_list[i];

	snprintf(temp, sizeof(temp), "<option value=\"%s\">%s</option>\n",
		 value, name);
	if (str_append_str(str, temp) < 0)
		return -1;

	return 0;
}

static int append_group_str(worker_st *ws, str_st *str, const char *group)
{
	char temp[256];
	const char *name;
	const char *value;
	unsigned int i;

	value = name = group;

	if (WSRCONFIG(ws)->friendly_group_list) {
		for (i = 0; i < WSRCONFIG(ws)->n_group_list; i++) {
			if (strcmp(WSRCONFIG(ws)->group_list[i], group) == 0) {
				if (WSRCONFIG(ws)->friendly_group_list[i] !=
				    NULL)
					name = WSRCONFIG(ws)
						       ->friendly_group_list[i];
				break;
			}
		}
	}

	snprintf(temp, sizeof(temp), "<option value=\"%s\">%s</option>\n",
		 value, name);
	if (str_append_str(str, temp) < 0)
		return -1;

	return 0;
}

int get_auth_handler2(worker_st *ws, unsigned int http_ver, const char *pmsg,
		      unsigned int pcounter)
{
	int ret;
	char context[BASE64_ENCODE_RAW_LENGTH(SID_SIZE) + 1];
	unsigned int i, j;
	str_st str;
	const char *login_start;
	const char *login_end;

	if (ws->req.user_agent_type == AGENT_OPENCONNECT_V3) {
		/* certain v2.x modified clients require a different auth_id
		 * when password is being requested, rather than username */
		if (ws->auth_state == S_AUTH_REQ)
			login_start = OCV3_PASSWD_START;
		else
			login_start = OCV3_LOGIN_START;
		login_end = OCV3_LOGIN_END;
	} else {
		login_start = OC_LOGIN_START;
		login_end = OC_LOGIN_END;
	}

	if ((ws->selected_auth->type & AUTH_TYPE_GSSAPI) &&
	    ws->auth_state < S_AUTH_COOKIE) {
		if (ws->req.authorization == NULL ||
		    ws->req.authorization_size == 0)
			return basic_auth_handler(ws, http_ver, NULL);
		else
			return post_auth_handler(ws, http_ver);
	}

	str_init(&str, ws);

	oclog(ws, LOG_HTTP_DEBUG, "HTTP sending: 200 OK");
	cstp_cork(ws);
	ret = cstp_printf(ws, "HTTP/1.%u 200 OK\r\n", http_ver);
	if (ret < 0)
		return -1;

	if (ws->sid_set != 0) {
		char safe_id[SAFE_ID_SIZE];

		oc_base64_encode((char *)ws->sid, sizeof(ws->sid),
				 (char *)context, sizeof(context));

		ret = cstp_printf(
			ws,
			"Set-Cookie: webvpncontext=%s; Max-Age=%lu; Secure; HttpOnly\r\n",
			context,
			(unsigned long int)WSRCONFIG(ws)->cookie_timeout);
		if (ret < 0)
			return -1;

		oclog(ws, LOG_SENSITIVE, "sent session id: %s",
		      calc_safe_id(ws->sid, sizeof(ws->sid), safe_id,
				   sizeof(safe_id)));
	} else {
		ret = cstp_puts(
			ws,
			"Set-Cookie: webvpncontext=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; Secure; HttpOnly\r\n");
		if (ret < 0)
			return -1;
	}

	ret = cstp_puts(ws, "Content-Type: text/xml\r\n");
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	if (ws->auth_state == S_AUTH_REQ) {
		/* Password Form */
		if (pmsg == NULL ||
		    strncasecmp(pmsg, DEFAULT_PASSWD_LABEL,
				sizeof(DEFAULT_PASSWD_LABEL) - 1) == 0)
			pmsg = "Please enter your password.";

		ret = str_append_str(&str, login_start);
		if (ret < 0) {
			ret = -1;
			goto cleanup;
		}

		ret = str_append_printf(&str, OC_LOGIN_FORM_START, pmsg);
		if (ret < 0) {
			ret = -1;
			goto cleanup;
		}

		if (pcounter > 0)
			ret = str_append_printf(
				&str, OC_LOGIN_FORM_INPUT_PASSWORD_CTR,
				pcounter);
		else
			ret = str_append_str(&str,
					     OC_LOGIN_FORM_INPUT_PASSWORD);
		if (ret < 0) {
			ret = -1;
			goto cleanup;
		}

		ret = str_append_str(&str, login_end);
		if (ret < 0) {
			ret = -1;
			goto cleanup;
		}

	} else {
		/* Username / Groups Form */
		if (pmsg == NULL)
			pmsg = "Please enter your username and password.";

		ret = str_append_str(&str, login_start);
		if (ret < 0) {
			ret = -1;
			goto cleanup;
		}

		if (WSRCONFIG(ws)->pre_login_banner) {
			ret = str_append_printf(
				&str, "<banner>%s</banner>",
				WSRCONFIG(ws)->pre_login_banner);
			if (ret < 0) {
				ret = -1;
				goto cleanup;
			}
		}

		ret = str_append_printf(&str, OC_LOGIN_FORM_START, pmsg);
		if (ret < 0) {
			ret = -1;
			goto cleanup;
		}

		if (ws->selected_auth->type & AUTH_TYPE_USERNAME_PASS) {
			ret = str_append_str(&str, OC_LOGIN_FORM_INPUT_USER);
			if (ret < 0) {
				ret = -1;
				goto cleanup;
			}

			ret = str_append_str(&str,
					     OC_LOGIN_FORM_INPUT_PASSWORD);
			if (ret < 0) {
				ret = -1;
				goto cleanup;
			}
		}

		if ((ws->selected_auth->type & AUTH_TYPE_CERTIFICATE) &&
		    ws->cert_auth_ok != 0) {
			ret = get_cert_info(ws);
			if (ret < 0) {
				ret = -1;
				oclog(ws, LOG_WARNING,
				      "cannot obtain certificate information");
				goto cleanup;
			}
		}

		/* send groups */
		if (WSRCONFIG(ws)->n_group_list > 0 ||
		    ws->cert_groups_size > 0) {
			ret = str_append_str(
				&str,
				"<select name=\"group_list\" label=\"Group:\">\n");
			if (ret < 0) {
				ret = -1;
				goto cleanup;
			}

			/* Several anyconnect clients (and openconnect) submit the group name
			 * separately in that form. In that case they expect that we re-order
			 * the list and we place the group they selected first. WTF! No respect
			 * to server time.
			 */
			if (ws->groupname[0] != 0) {
				ret = append_group_str(ws, &str, ws->groupname);
				if (ret < 0) {
					ret = -1;
					goto cleanup;
				}
			}

			/* we send a list of possible groups only if user is not forcing group e.g. by url to disable dialog on client side */
			if (ws->groupname[0] == 0 &&
			    WSRCONFIG(ws)->default_select_group) {
				ret = str_append_printf(
					&str, "<option>%s</option>\n",
					WSRCONFIG(ws)->default_select_group);
				if (ret < 0) {
					ret = -1;
					goto cleanup;
				}
			}

			/* append any groups available in the certificate */
			if ((ws->selected_auth->type & AUTH_TYPE_CERTIFICATE) &&
			    ws->cert_auth_ok != 0) {
				unsigned int dup;

				for (i = 0; i < ws->cert_groups_size; i++) {
					dup = 0;
					for (j = 0;
					     j < WSRCONFIG(ws)->n_group_list;
					     j++) {
						if (strcmp(ws->cert_groups[i],
							   WSRCONFIG(ws)->group_list
								   [j]) == 0) {
							dup = 1;
							break;
						}
					}

					if (dup == 0 && ws->groupname[0] != 0 &&
					    strcmp(ws->groupname,
						   ws->cert_groups[i]) == 0)
						dup = 1;

					if (dup != 0)
						continue;

					ret = str_append_printf(
						&str, "<option>%s</option>\n",
						ws->cert_groups[i]);
					if (ret < 0) {
						ret = -1;
						goto cleanup;
					}
				}
			}

			/* we send a list of possible groups only if user is not forcing group e.g. by url to disable dialog on client side */
			if (ws->groupname[0] == 0) {
				for (i = 0; i < WSRCONFIG(ws)->n_group_list;
				     i++) {
					if (ws->groupname[0] != 0 &&
					    strcmp(ws->groupname,
						   WSRCONFIG(ws)
							   ->group_list[i]) ==
						    0)
						continue;

					ret = append_group_idx(ws, &str, i);
					if (ret < 0) {
						ret = -1;
						goto cleanup;
					}
				}
			}
			ret = str_append_str(&str, "</select>\n");
			if (ret < 0) {
				ret = -1;
				goto cleanup;
			}
		}

		ret = str_append_str(&str, login_end);
		if (ret < 0) {
			ret = -1;
			goto cleanup;
		}
	}

	ret = cstp_printf(ws, "Content-Length: %u\r\n",
			  (unsigned int)str.length);
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = cstp_puts(ws, "X-Transcend-Version: 1\r\n");
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = add_owasp_headers(ws);
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = cstp_puts(ws, "\r\n");
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = cstp_send(ws, str.data, str.length);
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = cstp_uncork(ws);
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = 0;

cleanup:
	str_clear(&str);
	return ret;
}

int get_auth_handler(worker_st *ws, unsigned int http_ver)
{
	return get_auth_handler2(ws, http_ver, NULL, 0);
}

/* Extract the username from a certificate into ws->cert_username.
 * Returns 0 on success, a negative GnuTLS error code on failure.
 * Keeping the return value unambiguous (never a positive SAN-type code)
 * avoids confusion with gnutls_x509_crt_get_subject_alt_name() which
 * returns the SAN type (a positive integer) on success. */
static int get_cert_username(worker_st *ws, gnutls_x509_crt_t crt)
{
	char cert_username[MAX_USERNAME_SIZE];
	size_t size;
	unsigned int i;
	int ret;

	if (strcmp(WSRCONFIG(ws)->cert_user_oid, "SAN(rfc822name)") == 0) {
		for (i = 0;; i++) {
			size = sizeof(cert_username);
			ret = gnutls_x509_crt_get_subject_alt_name(
				crt, i, cert_username, &size, NULL);
			if (ret < 0)
				return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
			if (ret == GNUTLS_SAN_RFC822NAME) {
				strlcpy(ws->cert_username, cert_username,
					sizeof(ws->cert_username));
				oclog(ws, LOG_INFO, "RFC822NAME (%s) retrieved",
				      cert_username);
				return 0;
			}
		}
	} else if (WSRCONFIG(ws)->cert_user_oid) {
		size = sizeof(cert_username);
		ret = gnutls_x509_crt_get_dn_by_oid(
			crt, WSRCONFIG(ws)->cert_user_oid, 0, 0, cert_username,
			&size);
		if (ret < 0)
			return ret;
		strlcpy(ws->cert_username, cert_username,
			sizeof(ws->cert_username));
		return 0;
	} else {
		size = sizeof(cert_username);
		ret = gnutls_x509_crt_get_dn(crt, cert_username, &size);
		if (ret < 0)
			return ret;
		strlcpy(ws->cert_username, cert_username,
			sizeof(ws->cert_username));
		return 0;
	}
}

int get_cert_names(worker_st *ws, const gnutls_datum_t *raw)
{
	gnutls_x509_crt_t crt;
	int ret;
	unsigned int i;
	size_t size;

	if (ws->cert_username[0] != 0 || ws->cert_groups_size > 0)
		return 0; /* already read, nothing to do */

	ret = gnutls_x509_crt_init(&crt);
	if (ret < 0) {
		oclog(ws, LOG_ERR, "certificate init error: %s",
		      gnutls_strerror(ret));
		goto fail;
	}

	ret = gnutls_x509_crt_import(crt, raw, GNUTLS_X509_FMT_DER);
	if (ret < 0) {
		oclog(ws, LOG_ERR, "certificate import error: %s",
		      gnutls_strerror(ret));
		goto fail;
	}

	ret = get_cert_username(ws, crt);
	if (ret < 0) {
		if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
			oclog(ws, LOG_ERR,
			      "certificate's username exceeds the maximum buffer size (%u)",
			      (unsigned int)sizeof(ws->cert_username));
		else if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
			oclog(ws, LOG_ERR,
			      "the certificate does not contain %s; cannot determine username",
			      WSRCONFIG(ws)->cert_user_oid);
		else
			oclog(ws, LOG_ERR,
			      "cannot obtain username from certificate (%s): %s",
			      WSRCONFIG(ws)->cert_user_oid,
			      gnutls_strerror(ret));
		goto fail;
	}

	if (WSRCONFIG(ws)->cert_group_oid) {
		i = 0;
		do {
			ws->cert_groups = talloc_realloc(ws, ws->cert_groups,
							 char *, i + 1);
			if (ws->cert_groups == NULL) {
				oclog(ws, LOG_ERR,
				      "cannot allocate memory for cert groups");
				ret = -1;
				goto fail;
			}

			size = 0;
			ret = gnutls_x509_crt_get_dn_by_oid(
				crt, WSRCONFIG(ws)->cert_group_oid, i, 0, NULL,
				&size);
			if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
				break;

			if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) {
				if (ret == 0)
					ret = GNUTLS_E_INTERNAL_ERROR;
				oclog(ws, LOG_ERR,
				      "cannot obtain group from certificate DN(%s): %s",
				      WSRCONFIG(ws)->cert_group_oid,
				      gnutls_strerror(ret));
				goto fail;
			}

			ws->cert_groups[i] = talloc_size(ws->cert_groups, size);
			if (ws->cert_groups[i] == NULL) {
				oclog(ws, LOG_ERR,
				      "cannot allocate memory for cert group");
				ret = -1;
				goto fail;
			}

			ret = gnutls_x509_crt_get_dn_by_oid(
				crt, WSRCONFIG(ws)->cert_group_oid, i, 0,
				ws->cert_groups[i], &size);
			if (ret < 0) {
				oclog(ws, LOG_ERR,
				      "cannot obtain group from certificate DN: %s",
				      gnutls_strerror(ret));
				goto fail;
			}
			i++;
		} while (1);

		ws->cert_groups_size = i;
	}

	ret = 0;

fail:
	gnutls_x509_crt_deinit(crt);
	return ret;
}

static unsigned int check_if_default_route(char **routes,
					   unsigned int routes_size)
{
	unsigned int i;

	for (i = 0; i < routes_size; i++) {
		if (strcmp(routes[i], "default") == 0 ||
		    strcmp(routes[i], "0.0.0.0/0") == 0)
			return 1;
	}

	return 0;
}

/* auth reply from main process */
static int recv_cookie_auth_reply(worker_st *ws)
{
	int ret;
	int socketfd = -1;
	AuthCookieReplyMsg *msg = NULL;

	PROTOBUF_ALLOCATOR(pa, ws);

	ret = recv_socket_msg(ws, ws->cmd_fd, AUTH_COOKIE_REP, &socketfd,
			      (void *)&msg,
			      (unpack_func)auth_cookie_reply_msg__unpack,
			      WSRCONFIG(ws)->auth_timeout);
	if (ret < 0) {
		oclog(ws, LOG_ERR, "error receiving auth reply message");
		return ret;
	}

	oclog(ws, LOG_DEBUG, "received auth reply message (value: %d)",
	      (int)msg->reply);

	switch (msg->reply) {
	case AUTH__REP__OK:
		if (socketfd != -1) {
			ws->tun_fd = socketfd;
			if (tun_claim(ws->tun_fd) != 0) {
				ret = ERR_AUTH_FAIL;
				goto cleanup;
			}
			if (msg->vname == NULL || msg->config == NULL ||
			    msg->user_name == NULL ||
			    msg->sid.len != sizeof(ws->sid)) {
				ret = ERR_AUTH_FAIL;
				goto cleanup;
			}

			/* update our sid */
			memcpy(ws->sid, msg->sid.data, sizeof(ws->sid));
			ws->sid_set = 1;
			ws->session_start_time =
				(time_t)msg->session_start_time;

			if (msg->secmod_addr.len > sizeof(ws->secmod_addr)) {
				oclog(ws, LOG_ERR,
				      "msg->secmod_addr.len too large");
				ret = ERR_AUTH_FAIL;
				goto cleanup;
			}

			ws->secmod_addr_len = msg->secmod_addr.len;
			memcpy(&ws->secmod_addr, msg->secmod_addr.data,
			       msg->secmod_addr.len);

			strlcpy(ws->vinfo.name, msg->vname,
				sizeof(ws->vinfo.name));
			strlcpy(ws->username, msg->user_name,
				sizeof(ws->username));

			if (msg->group_name != NULL) {
				strlcpy(ws->groupname, msg->group_name,
					sizeof(ws->groupname));
			} else {
				ws->groupname[0] = 0;
			}

			if (msg->session_id.len != sizeof(ws->session_id)) {
				oclog(ws, LOG_ERR,
				      "msg->session_id.len unexpected (%zu)",
				      msg->session_id.len);
				ret = ERR_AUTH_FAIL;
				goto cleanup;
			}
			memcpy(ws->session_id, msg->session_id.data,
			       msg->session_id.len);

			ws->user_config = msg->config;

			if (msg->ipv4 != NULL) {
				talloc_free(ws->vinfo.ipv4);
				if (strcmp(msg->ipv4, "0.0.0.0") == 0)
					ws->vinfo.ipv4 = NULL;
				else
					ws->vinfo.ipv4 =
						talloc_strdup(ws, msg->ipv4);
			}

			if (msg->ipv6 != NULL) {
				talloc_free(ws->vinfo.ipv6);
				if (strcmp(msg->ipv6, "::") == 0)
					ws->vinfo.ipv6 = NULL;
				else
					ws->vinfo.ipv6 =
						talloc_strdup(ws, msg->ipv6);
			}

			if (msg->ipv4_local != NULL) {
				talloc_free(ws->vinfo.ipv4_local);
				if (strcmp(msg->ipv4_local, "0.0.0.0") == 0)
					ws->vinfo.ipv4_local = NULL;
				else
					ws->vinfo.ipv4_local = talloc_strdup(
						ws, msg->ipv4_local);
			}

			if (msg->ipv6_local != NULL) {
				talloc_free(ws->vinfo.ipv6_local);
				if (strcmp(msg->ipv6_local, "::") == 0)
					ws->vinfo.ipv6_local = NULL;
				else
					ws->vinfo.ipv6_local = talloc_strdup(
						ws, msg->ipv6_local);
			}

			if (msg->config->has_no_udp)
				WSRCONFIG(ws)->no_udp = msg->config->no_udp;

			/* routes */
			if (check_if_default_route(msg->config->routes,
						   msg->config->n_routes))
				ws->default_route = 1;
		} else {
			oclog(ws, LOG_ERR, "error in received message");
			ret = ERR_AUTH_FAIL;
			goto cleanup;
		}
		break;
	case AUTH__REP__FAILED:
	default:
		if (msg->reply != AUTH__REP__FAILED)
			oclog(ws, LOG_ERR, "unexpected auth reply %d",
			      (int)msg->reply);
		ret = ERR_AUTH_FAIL;
		goto cleanup;
	}

	ret = 0;
cleanup:
	if (ret < 0) {
		/* msg is intentionally kept alive on success: ws->user_config
		 * borrows a pointer into the unpacked protobuf message and must
		 * remain valid for the lifetime of the worker session.  Only
		 * free msg (and clear user_config) on the error path. */
		auth_cookie_reply_msg__free_unpacked(msg, &pa);
		ws->user_config = NULL;
	}
	return ret;
}

/* returns the fd */
int connect_to_secmod(worker_st *ws)
{
	int sd, ret, e;

	sd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (sd == -1) {
		e = errno;
		oclog(ws, LOG_ERR, "error opening unix socket (for sec-mod) %s",
		      strerror(e));
		return -1;
	}

	ret = connect(sd, (struct sockaddr *)&ws->secmod_addr,
		      ws->secmod_addr_len);
	if (ret < 0) {
		e = errno;
		close(sd);
		oclog(ws, LOG_ERR,
		      "error connecting to sec-mod socket '%s': %s",
		      ws->secmod_addr.sun_path, strerror(e));
		return -1;
	}
	return sd;
}

int recv_auth_reply(worker_st *ws, int sd, char **txt, unsigned int *pcounter)
{
	int ret;
	SecAuthReplyMsg *msg = NULL;

	PROTOBUF_ALLOCATOR(pa, ws);

	/* We don't use the default socket timeout here, but rather the
	 * longer WSRCONFIG(ws)->auth_timeout to allow for authentication
	 * methods which require the user input prior to returning a reply */
	ret = recv_msg(ws, sd, CMD_SEC_AUTH_REPLY, (void *)&msg,
		       (unpack_func)sec_auth_reply_msg__unpack,
		       WSRCONFIG(ws)->auth_timeout);
	if (ret < 0) {
		oclog(ws, LOG_ERR, "error receiving auth reply message");
		return ret;
	}

	oclog(ws, LOG_DEBUG, "received auth reply message (value: %d)",
	      (int)msg->reply);

	if (txt)
		*txt = NULL;

	switch (msg->reply) {
	case AUTH__REP__MSG:
		if (msg->msg)
			*txt = talloc_strdup(ws, msg->msg);
		else
			*txt = NULL;

		if (msg->has_passwd_counter)
			*pcounter = msg->passwd_counter;
		else
			*pcounter = 0;

		if (msg->has_sid && msg->sid.len == sizeof(ws->sid)) {
			/* update our sid */
			memcpy(ws->sid, msg->sid.data, sizeof(ws->sid));
			ws->sid_set = 1;
		}

		ret = ERR_AUTH_CONTINUE;
		goto cleanup;
	case AUTH__REP__OK:
		if (msg->user_name == NULL) {
			ret = ERR_AUTH_FAIL;
			goto cleanup;
		}

		strlcpy(ws->username, msg->user_name, sizeof(ws->username));
		if (msg->has_sid && msg->sid.len == sizeof(ws->sid)) {
			/* update our sid */
			memcpy(ws->sid, msg->sid.data, sizeof(ws->sid));
			ws->sid_set = 1;
		}

		if (msg->has_sid == 0 || msg->sid.len != sizeof(ws->cookie) ||
		    msg->dtls_session_id.len != sizeof(ws->session_id)) {
			ret = ERR_AUTH_FAIL;
			goto cleanup;
		}

		memcpy(ws->cookie, msg->sid.data, msg->sid.len);
		ws->cookie_set = 1;

		memcpy(ws->session_id, msg->dtls_session_id.data,
		       msg->dtls_session_id.len);

		if (txt && msg->msg)
			*txt = talloc_strdup(ws, msg->msg);

		break;
	case AUTH__REP__FAILED:
	default:
		if (msg->reply != AUTH__REP__FAILED)
			oclog(ws, LOG_ERR, "unexpected auth reply %d",
			      (int)msg->reply);
		ret = ERR_AUTH_FAIL;
		goto cleanup;
	}

	ret = 0;
cleanup:
	sec_auth_reply_msg__free_unpacked(msg, &pa);
	return ret;
}

/* grabs the username from the session certificate */
int get_cert_info(worker_st *ws)
{
	const gnutls_datum_t *cert;
	unsigned int ncerts;
	int ret;

	if (ws->session == NULL) {
		/* if info has been passed using proxy protocol */
		if (ws->cert_username[0] != 0)
			return 0;
		else
			return -1;
	}

	/* this is superfluous. Verification has already been performed
	 * during handshake. */
	cert = gnutls_certificate_get_peers(ws->session, &ncerts);

	if (cert == NULL) {
		return -1;
	}

	ret = get_cert_names(ws, cert);
	if (ret < 0) {
		if (WSRCONFIG(ws)->cert_user_oid == NULL) {
			oclog(ws, LOG_ERR,
			      "cannot read username from certificate; cert-user-oid is not set");
		} else {
			oclog(ws, LOG_ERR,
			      "cannot read username from certificate");
		}
		return -1;
	}

	return 0;
}

/* This makes sure that the provided cookie is valid,
 * and fills in the ws->user_config.
 */
void cookie_authenticate_or_exit(worker_st *ws)
{
	int ret;

	if (ws->auth_state == S_AUTH_COMPLETE)
		return;

	/* we must be in S_AUTH_COOKIE state */
	if (ws->auth_state != S_AUTH_COOKIE || ws->cookie_set == 0) {
		oclog(ws, LOG_WARNING, "no cookie found");
		cstp_puts(ws, "HTTP/1.1 503 Service Unavailable\r\n\r\n");
		cstp_fatal_close(ws, GNUTLS_A_ACCESS_DENIED);
		exit_worker(ws);
	}

	/* we have authenticated against sec-mod, we need to complete
	 * our authentication by forwarding our cookie to main. */
	ret = auth_cookie(ws, ws->cookie, sizeof(ws->cookie));
	if (ret < 0) {
		oclog(ws, LOG_WARNING, "failed cookie authentication attempt");
		if (WSRCONFIG(ws)->camouflage &&
		    ws->camouflage_check_passed == 0) {
			cstp_puts(ws,
				  "HTTP/1.1 405 Method Not Allowed\r\n\r\n");
		} else if (ret == ERR_AUTH_FAIL) {
			cstp_puts(
				ws,
				"HTTP/1.1 401 Cookie is not acceptable\r\n\r\n");
		} else {
			cstp_puts(ws,
				  "HTTP/1.1 503 Service Unavailable\r\n\r\n");
		}
		cstp_fatal_close(ws, GNUTLS_A_ACCESS_DENIED);
		exit_worker(ws);
	}
	ws->auth_state = S_AUTH_COMPLETE;
}

/* sends a cookie authentication request to main thread and waits for
 * a reply.
 * Returns 0 on success.
 */
int auth_cookie(worker_st *ws, void *cookie, size_t cookie_size)
{
	int ret;
	AuthCookieRequestMsg msg = AUTH_COOKIE_REQUEST_MSG__INIT;

	if ((ws->selected_auth->type & AUTH_TYPE_CERTIFICATE) &&
	    WSRCONFIG(ws)->cisco_client_compat == 0) {
		if (ws->cert_auth_ok == 0) {
			oclog(ws, LOG_INFO,
			      "no certificate provided for cookie authentication");
			return -1;
		} else {
			ret = get_cert_info(ws);
			if (ret < 0) {
				oclog(ws, LOG_INFO,
				      "cannot obtain certificate info");
				return -1;
			}
		}
	}

	msg.cookie.data = cookie;
	msg.cookie.len = cookie_size;
	if (ws->req.hostname[0] != 0)
		msg.hostname = ws->req.hostname;

	ret = send_msg_to_main(
		ws, AUTH_COOKIE_REQ, &msg,
		(pack_size_func)auth_cookie_request_msg__get_packed_size,
		(pack_func)auth_cookie_request_msg__pack);
	if (ret < 0) {
		oclog(ws, LOG_INFO,
		      "error sending cookie authentication request");
		return ret;
	}

	ret = recv_cookie_auth_reply(ws);
	if (ret < 0) {
		oclog(ws, LOG_DEBUG,
		      "error receiving cookie authentication reply");
		return ret;
	}

	return 0;
}

int post_common_handler(worker_st *ws, unsigned int http_ver, const char *imsg)
{
	int ret;
	char str_cookie[BASE64_ENCODE_RAW_LENGTH(sizeof(ws->cookie)) + 1];
	size_t str_cookie_size = sizeof(str_cookie);
	char msg[MAX_BANNER_SIZE + 32];
	const char *success_msg_head;
	char *success_msg_foot;
	unsigned int success_msg_head_size;
	unsigned int success_msg_foot_size;
	unsigned int size;

	if (ws->req.user_agent_type == AGENT_OPENCONNECT_V3) {
		success_msg_head = ocv3_success_msg_head;
		success_msg_foot = talloc_strdup(ws, ocv3_success_msg_foot);
		success_msg_head_size = sizeof(ocv3_success_msg_head) - 1;
		success_msg_foot_size = strlen(success_msg_foot);
	} else {
		success_msg_head = oc_success_msg_head;
		success_msg_foot = NULL;
#ifdef ANYCONNECT_CLIENT_COMPAT
		if (WSRCONFIG(ws)->xml_config_file) {
			success_msg_foot =
				talloc_asprintf(ws, OC_SUCCESS_MSG_FOOT_PROFILE,
						WSRCONFIG(ws)->xml_config_file,
						WSRCONFIG(ws)->xml_config_hash);
		}
#endif

		if (success_msg_foot == NULL) {
			success_msg_foot =
				talloc_strdup(ws, OC_SUCCESS_MSG_FOOT);
		}

		if (success_msg_foot == NULL)
			return -1;

		success_msg_head_size = sizeof(oc_success_msg_head) - 1;
		success_msg_foot_size = strlen(success_msg_foot);
	}

	oc_base64_encode((char *)ws->cookie, sizeof(ws->cookie),
			 (char *)str_cookie, str_cookie_size);

	/* reply */
	oclog(ws, LOG_HTTP_DEBUG, "HTTP sending: 200 OK");

	cstp_cork(ws);
	ret = cstp_printf(ws, "HTTP/1.%u 200 OK\r\n", http_ver);
	if (ret < 0)
		goto fail;

	ret = cstp_puts(ws, "Connection: Keep-Alive\r\n");
	if (ret < 0)
		goto fail;

	if ((ws->selected_auth->type & AUTH_TYPE_GSSAPI) && imsg != NULL &&
	    imsg[0] != 0) {
		ret = cstp_printf(ws, "WWW-Authenticate: Negotiate %s\r\n",
				  imsg);
		if (ret < 0)
			goto fail;
	}

	ret = cstp_puts(ws, "Content-Type: text/xml\r\n");
	if (ret < 0)
		goto fail;

	if (WSRCONFIG(ws)->banner) {
		if (snprintf(msg, sizeof(msg), "<banner>%s</banner>",
			     WSRCONFIG(ws)->banner) <= 0)
			goto fail;
		/* snprintf() returns not a very useful value, so we need to recalculate */
		size = strlen(msg);
	} else {
		msg[0] = 0;
		size = 0;
	}

	size += success_msg_head_size + success_msg_foot_size;

	ret = cstp_printf(ws, "Content-Length: %u\r\n", size);
	if (ret < 0)
		goto fail;

	ret = cstp_puts(ws, "X-Transcend-Version: 1\r\n");
	if (ret < 0)
		goto fail;

	if (ws->sid_set != 0) {
		char context[BASE64_ENCODE_RAW_LENGTH(SID_SIZE) + 1];
		char safe_id[SAFE_ID_SIZE];

		oc_base64_encode((char *)ws->sid, sizeof(ws->sid),
				 (char *)context, sizeof(context));

		ret = cstp_printf(
			ws,
			"Set-Cookie: webvpncontext=%s; Secure; HttpOnly\r\n",
			context);
		if (ret < 0)
			goto fail;

		oclog(ws, LOG_SENSITIVE, "sent session id: %s",
		      calc_safe_id(ws->sid, sizeof(ws->sid), safe_id,
				   sizeof(safe_id)));
	}

	ret = cstp_printf(ws, "Set-Cookie: webvpn=%s; Secure; HttpOnly\r\n",
			  str_cookie);
	if (ret < 0)
		goto fail;

	ret = cstp_puts(
		ws,
		"Set-Cookie: webvpnc=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; Secure; HttpOnly\r\n");
	if (ret < 0)
		goto fail;

	ret = add_owasp_headers(ws);
	if (ret < 0)
		goto fail;

#ifdef ANYCONNECT_CLIENT_COMPAT
	if (WSRCONFIG(ws)->xml_config_file) {
		ret = cstp_printf(
			ws,
			"Set-Cookie: webvpnc=bu:/&p:t&iu:1/&sh:%s&lu:/+CSCOT+/translation-table?textdomain%%3DAnyConnect%%26type%%3Dmanifest&fu:profiles%%2F%s&fh:%s; path=/; Secure; HttpOnly\r\n",
			WSSCONFIG(ws)->cert_hash,
			WSRCONFIG(ws)->xml_config_file,
			WSRCONFIG(ws)->xml_config_hash);
	} else {
		ret = cstp_printf(
			ws,
			"Set-Cookie: webvpnc=bu:/&p:t&iu:1/&sh:%s; path=/; Secure; HttpOnly\r\n",
			WSSCONFIG(ws)->cert_hash);
	}
#endif

	if (ret < 0)
		goto fail;

	ret = cstp_printf(ws, "\r\n%s%s%s", success_msg_head, msg,
			  success_msg_foot);
	if (ret < 0)
		goto fail;

	ret = cstp_uncork(ws);
	if (ret < 0)
		goto fail;

	return 0;

fail:
	talloc_free(success_msg_foot);
	return -1;
}

/* Returns the contents of the password field in a newly allocated
 * string, or a negative value on error.
 *
 * @body: is the string to search the xml field at, should be null-terminated.
 * @value: the value that was found
 */
static int match_password_in_reply(worker_st *ws, char *body,
				   unsigned int body_length, char **value)
{
	char *p;
	unsigned int len, xml = 0;

	if (body == NULL || body_length == 0)
		return -1;

	if (memmem(body, body_length, "<?xml", 5) != 0) {
		xml = 1;

		/* body should contain <password?>test</password?> or <xxx_password>test</xxx_password> */
		*value = strcasestr(body, "<password");
		if (*value == NULL)
			*value = strcasestr(body, "_password>");

		if (*value == NULL) {
			oclog(ws, LOG_HTTP_DEBUG,
			      "cannot find password in client XML message");
			return -1;
		}
		/* find terminator */
		p = strchr(*value, '>');
		if (p == NULL) {
			oclog(ws, LOG_HTTP_DEBUG,
			      "unterminated password in client XML message");
			return -1;
		}
		p++;

		*value = p;
		len = 0;
		while (*p != 0) {
			if (*p == '<' && *(p + 1) == '/') {
				break;
			}
			p++;
			len++;
		}
	} else { /* non-xml version */
		/* body should be "username=test&password?=test" */
		*value = strcasestr(body, "password");
		if (*value == NULL) {
			oclog(ws, LOG_HTTP_DEBUG,
			      "cannot find password in client message");
			return -1;
		}

		p = strchr(*value, '=');
		if (p == NULL) {
			oclog(ws, LOG_HTTP_DEBUG,
			      "unterminated password in client message");
			return -1;
		}
		p++;

		*value = p;
		len = 0;
		while (*p != 0) {
			if (*p == '&') {
				break;
			}
			p++;
			len++;
		}
	}

	if (len == 0) {
		*value = talloc_strdup(ws->req.body, "");
		if (*value != NULL)
			return 0;
		return -1;
	}
	if (xml)
		*value = unescape_html(ws->req.body, *value, len, NULL);
	else
		*value = unescape_url(ws->req.body, *value, len, NULL);

	if (*value == NULL) {
		oclog(ws, LOG_ERR,
		      "password requested but no such field in client message");
		return -1;
	}

	return 0;
}

/* Returns the contents of the provided fields in a newly allocated
 * string, or a negative value on error.
 *
 * @body: is the string to search the xml field at, should be null-terminated.
 * @xml_field: the XML field to check for (e.g., MYFIELD)
 * @value: the value that was found
 */
int parse_reply(worker_st *ws, char *body, unsigned int body_length,
		const char *field, unsigned int field_size,
		const char *xml_field, unsigned int xml_field_size,
		char **value)
{
	char *p;
	char temp1[64];
	char temp2[64];
	unsigned int temp2_len, temp1_len;
	unsigned int len, xml = 0;

	if (body == NULL || body_length == 0)
		return -1;

	if (memmem(body, body_length, "<?xml", 5) != 0) {
		xml = 1;
		if (xml_field) {
			field = xml_field;
			/*field_size = xml_field_size;*/
		}

		snprintf(temp1, sizeof(temp1), "<%s>", field);
		snprintf(temp2, sizeof(temp2), "</%s>", field);

		temp1_len = strlen(temp1);
		temp2_len = strlen(temp2);

		/* body should contain <field>test</field> */
		*value = strcasestr(body, temp1);
		if (*value == NULL) {
			oclog(ws, LOG_HTTP_DEBUG,
			      "cannot find '%s' in client XML message", field);
			return -1;
		}
		*value += temp1_len;

		p = *value;
		len = 0;
		while (*p != 0) {
			if (*p == '<' &&
			    (strncasecmp(p, temp2, temp2_len) == 0)) {
				break;
			}
			p++;
			len++;
		}
	} else { /* non-xml version */
		snprintf(temp1, sizeof(temp1), "%s=", field);
		temp1_len = strlen(temp1);

		/* body should be "username=test&password=test" */
		*value = strcasestr(body, temp1);
		if (*value == NULL) {
			oclog(ws, LOG_HTTP_DEBUG,
			      "cannot find '%s' in client message", field);
			return -1;
		}

		*value += temp1_len;

		p = *value;
		len = 0;
		while (*p != 0) {
			if (*p == '&') {
				break;
			}
			p++;
			len++;
		}
	}

	if (len == 0) {
		*value = talloc_strdup(ws->req.body, "");
		if (*value != NULL)
			return 0;
		return -1;
	}
	if (xml)
		*value = unescape_html(ws->req.body, *value, len, NULL);
	else
		*value = unescape_url(ws->req.body, *value, len, NULL);

	if (*value == NULL) {
		oclog(ws, LOG_ERR,
		      "%s requested but no such field in client message",
		      field);
		return -1;
	}

	return 0;
}

#define SPNEGO_MSG "<html><body>Please authenticate using GSSAPI</body></html>"
static int basic_auth_handler(worker_st *ws, unsigned int http_ver,
			      const char *msg)
{
	int ret;

	oclog(ws, LOG_HTTP_DEBUG, "HTTP sending: 401 Unauthorized");
	cstp_cork(ws);
	ret = cstp_printf(ws, "HTTP/1.%u 401 Unauthorized\r\n", http_ver);
	if (ret < 0)
		return -1;

	if (WSSCONFIG(ws)->auth_methods > 1) {
		ret = cstp_puts(ws, "X-HTTP-Auth-Support: fallback\r\n");
		if (ret < 0)
			return -1;
	}

	if (msg == NULL) {
		oclog(ws, LOG_HTTP_DEBUG,
		      "HTTP sending: WWW-Authenticate: Negotiate");
		ret = cstp_puts(ws, "WWW-Authenticate: Negotiate\r\n");
	} else {
		oclog(ws, LOG_HTTP_DEBUG,
		      "HTTP sending: WWW-Authenticate: Negotiate %s", msg);
		ret = cstp_printf(ws, "WWW-Authenticate: Negotiate %s\r\n",
				  msg);
	}
	if (ret < 0)
		return -1;

	ret = cstp_puts(ws, "Content-Length: 0\r\n");
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = cstp_puts(ws, "\r\n");
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = cstp_uncork(ws);
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = 0;

cleanup:
	return ret;
}

#ifdef SUPPORT_OIDC_AUTH
static int oidc_auth_handler(worker_st *ws, unsigned int http_ver)
{
	int ret;

	oclog(ws, LOG_HTTP_DEBUG, "HTTP sending: 401 Unauthorized");
	cstp_cork(ws);
	ret = cstp_printf(ws, "HTTP/1.%u 401 Unauthorized\r\n", http_ver);
	if (ret < 0)
		return -1;

	oclog(ws, LOG_HTTP_DEBUG, "HTTP sending: WWW-Authenticate: %s",
	      HTTP_AUTH_OIDC_PREFIX);
	ret = cstp_printf(ws, "WWW-Authenticate: %s\r\n",
			  HTTP_AUTH_OIDC_PREFIX);

	if (ret < 0)
		return -1;

	ret = cstp_puts(ws, "Content-Length: 0\r\n");
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = cstp_puts(ws, "\r\n");
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = cstp_uncork(ws);
	if (ret < 0) {
		ret = -1;
		goto cleanup;
	}

	ret = 0;

cleanup:
	return ret;
}
#endif

#define USERNAME_FIELD "username"
#define GROUPNAME_FIELD "group%5flist"
#define GROUPNAME_FIELD2 "group_list"
#define GROUPNAME_FIELD_XML "group-select"

#define MSG_INTERNAL_ERROR "Internal error"
#define MSG_CERT_READ_ERROR "Could not read certificate"
#define MSG_NO_CERT_ERROR "No certificate"
#define MSG_NO_PASSWORD_ERROR "No password"

int post_auth_handler(worker_st *ws, unsigned int http_ver)
{
	int ret = -1, sd = -1;
	struct http_req_st *req = &ws->req;
	const char *reason = "Authentication failed";
	char *username = NULL;
	char *password = NULL;
	char *groupname = NULL;
	char *msg = NULL;
	unsigned int def_group = 0;
	unsigned int pcounter = 0;

	if (req->body_length > 0) {
		oclog(ws, LOG_HTTP_DEBUG, "POST body: '%.*s'",
		      (int)req->body_length, req->body);
	}

	if (ws->auth_state == S_AUTH_INACTIVE) {
		SecAuthInitMsg ireq = SEC_AUTH_INIT_MSG__INIT;

		/* If the URL is not a known one and more than a character, we parse it as a group indicator */
		if (WSRCONFIG(ws)->select_group_by_url != 0 &&
		    http_post_known_service_check(ws, req->url) == NULL &&
		    strlen(req->url) > 1) {
			groupname = talloc_strdup(ws->req.body, req->url + 1);
			ret = 0;
		}

		if (ret < 0) {
			ret = parse_reply(ws, req->body, req->body_length,
					  GROUPNAME_FIELD,
					  sizeof(GROUPNAME_FIELD) - 1,
					  GROUPNAME_FIELD_XML,
					  sizeof(GROUPNAME_FIELD_XML) - 1,
					  &groupname);

			if (ret < 0) {
				ret = parse_reply(
					ws, req->body, req->body_length,
					GROUPNAME_FIELD2,
					sizeof(GROUPNAME_FIELD2) - 1,
					GROUPNAME_FIELD_XML,
					sizeof(GROUPNAME_FIELD_XML) - 1,
					&groupname);
			}
		}

		if (ret < 0) {
			oclog(ws, LOG_HTTP_DEBUG, "failed reading groupname");
		} else {
			if (WSRCONFIG(ws)->default_select_group != NULL &&
			    strcmp(groupname,
				   WSRCONFIG(ws)->default_select_group) == 0) {
				def_group = 1;
			} else {
				/* Some anyconnect clients send the group friendly name instead of
				 * the actual value; see #267 */
				ws->groupname[0] = 0;
				if (WSRCONFIG(ws)->friendly_group_list !=
				    NULL) {
					unsigned int found = 0, i;

					for (i = 0;
					     i < WSRCONFIG(ws)->n_group_list;
					     i++) {
						if (strcmp(WSRCONFIG(ws)
								   ->group_list
									   [i],
							   groupname) == 0) {
							found = 1;
							break;
						}
					}

					if (!found)
						for (i = 0;
						     i <
						     WSRCONFIG(ws)->n_group_list;
						     i++) {
							if (WSRCONFIG(ws)->friendly_group_list
									    [i] !=
								    NULL &&
							    strcmp(WSRCONFIG(ws)->friendly_group_list
									   [i],
								   groupname) ==
								    0) {
								strlcpy(ws->groupname,
									WSRCONFIG(
										ws)
										->group_list
											[i],
									sizeof(ws->groupname));
								break;
							}
						}
				}

				if (ws->groupname[0] == 0)
					strlcpy(ws->groupname, groupname,
						sizeof(ws->groupname));
				ireq.group_name = ws->groupname;
			}
		}
		talloc_free(groupname);

#ifdef SUPPORT_OIDC_AUTH
		if (ws->selected_auth->type & AUTH_TYPE_OIDC) {
			if (req->authorization == NULL ||
			    req->authorization_size == 0)
				return oidc_auth_handler(ws, http_ver);

			if ((req->authorization_size >
			     (sizeof(HTTP_AUTH_OIDC_PREFIX) - 1)) &&
			    strncasecmp(
				    req->authorization, HTTP_AUTH_OIDC_PREFIX,
				    sizeof(HTTP_AUTH_OIDC_PREFIX) - 1) == 0) {
				ireq.auth_type |= AUTH_TYPE_OIDC;
				ireq.user_name = req->authorization +
						 sizeof(HTTP_AUTH_OIDC_PREFIX);
			} else {
				oclog(ws, LOG_HTTP_DEBUG,
				      "Invalid authorization data: %.*s",
				      req->authorization_size,
				      req->authorization);
				goto auth_fail;
			}
		}
#endif

		if (ws->selected_auth->type & AUTH_TYPE_GSSAPI) {
			if (req->authorization == NULL ||
			    req->authorization_size == 0)
				return basic_auth_handler(ws, http_ver, NULL);

			if (req->authorization_size > 10) {
				ireq.user_name = req->authorization + 10;
				ireq.auth_type |= AUTH_TYPE_GSSAPI;
			} else {
				oclog(ws, LOG_HTTP_DEBUG,
				      "Invalid authorization data: %.*s",
				      req->authorization_size,
				      req->authorization);
				goto auth_fail;
			}
		}

		if (ws->selected_auth->type & AUTH_TYPE_USERNAME_PASS) {
			ret = parse_reply(ws, req->body, req->body_length,
					  USERNAME_FIELD,
					  sizeof(USERNAME_FIELD) - 1, NULL, 0,
					  &username);
			if (ret < 0) {
				oclog(ws, LOG_HTTP_DEBUG,
				      "failed reading username");
				goto ask_auth;
			}

			strlcpy(ws->username, username, sizeof(ws->username));
			talloc_free(username);
			ireq.user_name = ws->username;
			ireq.auth_type |= AUTH_TYPE_USERNAME_PASS;
		}

		if (ws->selected_auth->type & AUTH_TYPE_CERTIFICATE) {
			if (ws->cert_auth_ok == 0) {
				reason = MSG_NO_CERT_ERROR;
				oclog(ws, LOG_INFO,
				      "no certificate provided for authentication");

				if (ws_switch_auth_to_next(ws) == 0)
					goto auth_fail;

				ws->auth_state = S_AUTH_INACTIVE;
				ws->sid_set = 0;
				goto ask_auth;
			} else {
				ret = get_cert_info(ws);
				if (ret < 0) {
					reason = MSG_CERT_READ_ERROR;
					oclog(ws, LOG_ERR,
					      "failed reading certificate info");
					goto auth_fail;
				}
			}

			if (def_group == 0 && ws->cert_groups_size > 0 &&
			    ws->groupname[0] == 0) {
				oclog(ws, LOG_HTTP_DEBUG,
				      "user has not selected a group");
				return get_auth_handler2(
					ws, http_ver,
					"Please select your group.", 0);
			}

			ireq.tls_auth_ok = ws->cert_auth_ok;
			ireq.cert_user_name = ws->cert_username;
			ireq.cert_group_names = ws->cert_groups;
			ireq.n_cert_group_names = ws->cert_groups_size;
			ireq.auth_type |= AUTH_TYPE_CERTIFICATE;
		}

		ireq.vhost = ws->vhost->name;

		/* if proxy protocol is in use we need a saved version of remote_ip_str or
		 * sec-mod will not authenticate the client. */
		ireq.remote_ip = ws->remote_ip_str;
		ireq.orig_remote_ip = ws->orig_remote_ip_str;
		ireq.our_ip = ws->our_ip_str;
		ireq.session_start_time = ws->session_start_time;
		ireq.hmac.data = (uint8_t *)ws->sec_auth_init_hmac;
		ireq.hmac.len = sizeof(ws->sec_auth_init_hmac);
		if (req->user_agent[0] != 0)
			ireq.user_agent = req->user_agent;

		if (req->devtype[0] != 0)
			ireq.device_type = req->devtype;

		if (req->devplatform[0] != 0)
			ireq.device_platform = req->devplatform;

		sd = connect_to_secmod(ws);
		if (sd == -1) {
			reason = MSG_INTERNAL_ERROR;
			oclog(ws, LOG_ERR, "failed connecting to sec mod");
			goto auth_fail;
		}

		ret = send_msg_to_secmod(
			ws, sd, CMD_SEC_AUTH_INIT, &ireq,
			(pack_size_func)sec_auth_init_msg__get_packed_size,
			(pack_func)sec_auth_init_msg__pack);
		if (ret < 0) {
			reason = MSG_INTERNAL_ERROR;
			oclog(ws, LOG_ERR,
			      "failed sending auth init message to sec mod");
			goto auth_fail;
		}

		ws->auth_state = S_AUTH_INIT;
	} else if (ws->auth_state == S_AUTH_INIT ||
		   ws->auth_state == S_AUTH_REQ) {
		SecAuthContMsg areq = SEC_AUTH_CONT_MSG__INIT;

		areq.ip = ws->remote_ip_str;
		if (ws->selected_auth->type & AUTH_TYPE_GSSAPI) {
			if (req->authorization == NULL ||
			    req->authorization_size <= 10) {
				if (req->authorization != NULL)
					oclog(ws, LOG_HTTP_DEBUG,
					      "Invalid authorization data: %.*s",
					      req->authorization_size,
					      req->authorization);
				else
					oclog(ws, LOG_HTTP_DEBUG,
					      "No authorization data");
				goto auth_fail;
			}
			areq.password = req->authorization + 10;
		}

		if (areq.password == NULL &&
		    (ws->selected_auth->type & AUTH_TYPE_USERNAME_PASS)) {
			ret = match_password_in_reply(
				ws, req->body, req->body_length, &password);
			if (ret < 0) {
				reason = MSG_NO_PASSWORD_ERROR;
				oclog(ws, LOG_ERR, "failed reading password");
				goto auth_fail;
			}

			areq.password = password;
		}

		if (areq.password != NULL) {
			if (ws->sid_set != 0) {
				areq.sid.data = ws->sid;
				areq.sid.len = sizeof(ws->sid);
			}

			sd = connect_to_secmod(ws);
			if (sd == -1) {
				reason = MSG_INTERNAL_ERROR;
				oclog(ws, LOG_ERR,
				      "failed connecting to sec mod");
				goto auth_fail;
			}

			ret = send_msg_to_secmod(
				ws, sd, CMD_SEC_AUTH_CONT, &areq,
				(pack_size_func)
					sec_auth_cont_msg__get_packed_size,
				(pack_func)sec_auth_cont_msg__pack);
			talloc_free(password);

			if (ret < 0) {
				reason = MSG_INTERNAL_ERROR;
				oclog(ws, LOG_ERR,
				      "failed sending auth req message to main");
				goto auth_fail;
			}

			ws->auth_state = S_AUTH_REQ;
		} else {
			oclog(ws, LOG_ERR, "No password provided");
			goto auth_fail;
		}
	} else {
		oclog(ws, LOG_ERR, "unexpected POST request in auth state %u",
		      (unsigned int)ws->auth_state);
		goto auth_fail;
	}

	ret = recv_auth_reply(ws, sd, &msg, &pcounter);
	if (sd != -1) {
		close(sd);
		sd = -1;
	}

	if (ret == ERR_AUTH_CONTINUE) {
		oclog(ws, LOG_DEBUG, "continuing authentication for '%s'",
		      ws->username);
		ws->auth_state = S_AUTH_REQ;

		if (ws->selected_auth->type & AUTH_TYPE_GSSAPI) {
			ret = basic_auth_handler(ws, http_ver, msg);
		} else {
			if (username != NULL) {
				/* Process the same request a second time to get the password
				   and do the necessary secmod requests */
				ret = post_auth_handler(ws, http_ver);
			} else {
				ret = get_auth_handler2(ws, http_ver, msg,
							pcounter);
			}
		}
		goto cleanup;
	} else if (ret < 0) {
		if (ws->selected_auth->type & AUTH_TYPE_GSSAPI) {
			/* Fallback from GSSAPI to USERNAME-PASSWORD */
			oclog(ws, LOG_ERR, "failed gssapi authentication");
			if (ws_switch_auth_to_next(ws) == 0)
				goto auth_fail;

			ws->auth_state = S_AUTH_INACTIVE;
			ws->sid_set = 0;
			goto ask_auth;
		} else {
			oclog(ws, LOG_ERR, "failed authentication for '%s'",
			      ws->username);
			goto auth_fail;
		}
	}

	oclog(ws, LOG_HTTP_DEBUG, "user '%s' obtained cookie", ws->username);
	ws->auth_state = S_AUTH_COOKIE;

	ret = post_common_handler(ws, http_ver, msg);
	goto cleanup;

ask_auth:

	return get_auth_handler(ws, http_ver);

auth_fail:

	if (sd != -1)
		close(sd);
	oclog(ws, LOG_HTTP_DEBUG, "HTTP sending: 401 Unauthorized");
	ret = cstp_printf(ws, "HTTP/1.%u 401 %s\r\nContent-Length: 0\r\n\r\n",
			  http_ver, reason);
	if (ret >= 0)
		cstp_fatal_close(ws, GNUTLS_A_ACCESS_DENIED);
	talloc_free(msg);
	exit_worker(ws);
cleanup:
	talloc_free(msg);
	return ret;
}
