/*
 * OpenConnect (SSL + DTLS) VPN client
 *
 * Copyright © 2008-2015 Intel Corporation.
 *
 * Author: David Woodhouse <dwmw2@infradead.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program 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
 * Lesser General Public License for more details.
 */

#include <config.h>

#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif
#ifndef _WIN32
#include <sys/wait.h>
#endif

#include "openconnect-internal.h"

#define NTLM_SSO_REQ		2	/* SSO type1 packet sent */
#define NTLM_MANUAL		3	/* SSO challenge/response sent or skipped; manual next */
#define NTLM_MANUAL_REQ		4	/* manual type1 packet sent */

#ifdef _WIN32
static int ntlm_sspi(struct openconnect_info *vpninfo, int proxy,
		     struct http_auth_state *auth_state,
		     struct oc_text_buf *buf, const char *challenge)
{
        SECURITY_STATUS status;
        SecBufferDesc input_desc, output_desc;
        SecBuffer in_token, out_token;
        ULONG ret_flags;

	if (challenge) {
		int token_len = -EINVAL;

		input_desc.cBuffers = 1;
		input_desc.pBuffers = &in_token;
		input_desc.ulVersion = SECBUFFER_VERSION;

		in_token.BufferType = SECBUFFER_TOKEN;
		in_token.pvBuffer = openconnect_base64_decode(&token_len, challenge);
		if (!in_token.pvBuffer)
			return token_len;
		in_token.cbBuffer = token_len;
	}

        output_desc.cBuffers = 1;
        output_desc.pBuffers = &out_token;
        output_desc.ulVersion = SECBUFFER_VERSION;

        out_token.BufferType = SECBUFFER_TOKEN;
        out_token.cbBuffer = 0;
        out_token.pvBuffer = NULL;

	status = InitializeSecurityContextW(&auth_state->ntlm_sspi_cred,
					    challenge ? &auth_state->ntlm_sspi_ctx : NULL,
					    (SEC_WCHAR *)L"",
					    ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONNECTION,
					    0, SECURITY_NETWORK_DREP,
					    challenge ? &input_desc : NULL,
					    0, &auth_state->ntlm_sspi_ctx,
					    &output_desc, &ret_flags, NULL);

	if (status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("InitializeSecurityContext() failed: %lx\n"), status);
		return -EIO;
	}

	buf_append(buf, "%sAuthorization: NTLM ", proxy ? "Proxy-" : "");
	buf_append_base64(buf, out_token.pvBuffer, out_token.cbBuffer);
	buf_append(buf, "\r\n");

	FreeContextBuffer(out_token.pvBuffer);

	return 0;
}

static int ntlm_helper_spawn(struct openconnect_info *vpninfo, int proxy,
			     struct http_auth_state *auth_state,
			     struct oc_text_buf *buf)
{
        SECURITY_STATUS status;
	int ret;

	status = AcquireCredentialsHandleW(NULL, (SEC_WCHAR *)L"NTLM",
					   SECPKG_CRED_OUTBOUND, NULL, NULL,
					   NULL, NULL,
					   &auth_state->ntlm_sspi_cred, NULL);
	if (status != SEC_E_OK) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("AcquireCredentialsHandle() failed: %lx\n"), status);
		return -EIO;
	}

	ret = ntlm_sspi(vpninfo, proxy, auth_state, buf, NULL);
	if (ret)
		FreeCredentialsHandle(&auth_state->ntlm_sspi_cred);

	return ret;
}

static int ntlm_helper_challenge(struct openconnect_info *vpninfo, int proxy,
				 struct http_auth_state *auth_state,
				 struct oc_text_buf *buf)
{
	return ntlm_sspi(vpninfo, proxy, auth_state, buf, auth_state->challenge);
}

void cleanup_ntlm_auth(struct openconnect_info *vpninfo,
		       struct http_auth_state *auth_state)
{
	if (auth_state->state == NTLM_SSO_REQ) {
		FreeCredentialsHandle(&auth_state->ntlm_sspi_cred);
		DeleteSecurityContext(&auth_state->ntlm_sspi_ctx);
	}
}

#else /* !_WIN32 */

static int ntlm_helper_spawn(struct openconnect_info *vpninfo, int proxy,
			     struct http_auth_state *auth_state,
			     struct oc_text_buf *buf)
{
	char *username;
	int pipefd[2];
	pid_t pid;
	char helperbuf[4096];
	int len;

	if (access("/usr/bin/ntlm_auth", X_OK))
		return -errno;

	username = vpninfo->proxy_user;
	if (!username)
		username = getenv("NTLMUSER");
	if (!username)
		username = getenv("USER");
	if (!username)
		return -EINVAL;

#ifdef SOCK_CLOEXEC
	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, pipefd))
#endif
	{
		if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd))
			return -errno;
		set_fd_cloexec(pipefd[0]);
		set_fd_cloexec(pipefd[1]);
	}
	pid = fork();
	if (pid == -1)
		return -errno;

	if (!pid) {
		int i;
		char *p;
		const char *argv[9];

		/* Fork again to detach grandchild */
		if (fork())
			exit(1);

		close(pipefd[1]);
		/* The duplicated fd does not have O_CLOEXEC */
		dup2(pipefd[0], 0);
		dup2(pipefd[0], 1);
		/* Should we leave stderr open? */
		for (i = 3; i < 1024 ; i++)
			close(i);


		i = 0;
		argv[i++] = "/usr/bin/ntlm_auth";
		argv[i++] = "--helper-protocol";
		argv[i++] = "ntlmssp-client-1";
		argv[i++] = "--use-cached-creds";
		argv[i++] = "--username";
		p = strchr(username, '\\');
		if (p) {
			argv[i++] = p+1;
			argv[i++] = "--domain";
			argv[i++] = strndup(username, p - username);
		} else
			argv[i++] = username;
		argv[i++] = NULL;

		execv(argv[0], (char **)argv);
		exit(1);
	}
	waitpid(pid, NULL, 0);
	close(pipefd[0]);

	if (write(pipefd[1], "YR\n", 3) != 3) {
		close(pipefd[1]);
		return -EIO;
	}

	len = read(pipefd[1], helperbuf, sizeof(helperbuf));
	if (len < 4 || helperbuf[0] != 'Y' || helperbuf[1] != 'R' ||
	    helperbuf[2] != ' ' || helperbuf[len - 1] != '\n') {
		close(pipefd[1]);
		return -EIO;
	}
	helperbuf[len - 1] = 0;
	buf_append(buf, "%sAuthorization: NTLM %s\r\n", proxy ? "Proxy-" : "",
		   helperbuf + 3);
	auth_state->ntlm_helper_fd = pipefd[1];
	return 0;
}

static int ntlm_helper_challenge(struct openconnect_info *vpninfo, int proxy,
				 struct http_auth_state *auth_state,
				 struct oc_text_buf *buf)
{
	char helperbuf[4096];
	int len;

	if (!auth_state->challenge ||
	    write(auth_state->ntlm_helper_fd, "TT ", 3) != 3 ||
	    write(auth_state->ntlm_helper_fd, auth_state->challenge,
		  strlen(auth_state->challenge)) != strlen(auth_state->challenge) ||
	    write(auth_state->ntlm_helper_fd, "\n", 1) != 1) {
	err:
		vpn_progress(vpninfo, PRG_ERR, _("Error communicating with ntlm_auth helper\n"));
		close(auth_state->ntlm_helper_fd);
		auth_state->ntlm_helper_fd = -1;
		return -EAGAIN;
	}
	len = read(auth_state->ntlm_helper_fd, helperbuf, sizeof(helperbuf));
	/* Accept both 'KK' and 'AF'. It should be the latter but see
	   https://bugzilla.samba.org/show_bug.cgi?id=10691 */
	if (len < 4 || (!(helperbuf[0] == 'K' && helperbuf[1] == 'K') &&
			!(helperbuf[0] == 'A' && helperbuf[1] == 'F')) ||
	    helperbuf[2] != ' ' || helperbuf[len - 1] != '\n') {
		goto err;
	}
	helperbuf[len - 1] = 0;
	buf_append(buf, "%sAuthorization: NTLM %s\r\n", proxy ? "Proxy-" : "",
		   helperbuf + 3);

	if (proxy)
		vpn_progress(vpninfo, PRG_INFO,
			     _("Attempting HTTP NTLM authentication to proxy (single-sign-on)\n"));
	else
		vpn_progress(vpninfo, PRG_INFO,
			     _("Attempting HTTP NTLM authentication to server '%s' (single-sign-on)\n"),
			     vpninfo->hostname);
	return 0;

}

void cleanup_ntlm_auth(struct openconnect_info *vpninfo,
		       struct http_auth_state *auth_state)
{
	if (auth_state->state == NTLM_SSO_REQ) {
		close(auth_state->ntlm_helper_fd);
		auth_state->ntlm_helper_fd = -1;
	}
}
#endif /* !_WIN32 */

/*
 * NTLM implementation taken from libsoup / Evolution Data Server
 * Copyright (C) 2007 Red Hat, Inc.
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 */

/* DES */
typedef uint32_t DES_KS[16][2]; /* Single-key DES key schedule */

/*
 * MD4 encoder. (The one everyone else uses is not GPL-compatible;
 * this is a reimplementation from spec.) This doesn't need to be
 * efficient for our purposes, although it would be nice to fix
 * it to not malloc()...
 */

#define F(X,Y,Z) ( ((X)&(Y)) | ((~(X))&(Z)) )
#define G(X,Y,Z) ( ((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)) )
#define H(X,Y,Z) ( (X)^(Y)^(Z) )
#define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) )

static int md4sum (struct oc_text_buf *buf, unsigned char digest[16])
{
	int nbytes = buf->pos;
	unsigned char *M;
	uint32_t A, B, C, D, AA, BB, CC, DD, X[16];
	int pbytes, nbits = nbytes * 8, i, j;

	/* There is *always* padding of at least one bit. */
	pbytes = ((119 - (nbytes % 64)) % 64) + 1;

	if (buf_ensure_space (buf, pbytes + 8))
		return -ENOMEM;

	M = (void *)buf->data;
	memset (M + nbytes, 0, pbytes + 8);
	M[nbytes] = 0x80;
	store_le32(&M[nbytes + pbytes], nbits);

	A = 0x67452301;
	B = 0xEFCDAB89;
	C = 0x98BADCFE;
	D = 0x10325476;

	for (i = 0; i < nbytes + pbytes + 8; i += 64) {
		for (j = 0; j < 16; j++)
			X[j] =  load_le32(&M[i + j * 4]);

		AA = A;
		BB = B;
		CC = C;
		DD = D;

		A = ROT (A + F (B, C, D) + X[0], 3);
		D = ROT (D + F (A, B, C) + X[1], 7);
		C = ROT (C + F (D, A, B) + X[2], 11);
		B = ROT (B + F (C, D, A) + X[3], 19);
		A = ROT (A + F (B, C, D) + X[4], 3);
		D = ROT (D + F (A, B, C) + X[5], 7);
		C = ROT (C + F (D, A, B) + X[6], 11);
		B = ROT (B + F (C, D, A) + X[7], 19);
		A = ROT (A + F (B, C, D) + X[8], 3);
		D = ROT (D + F (A, B, C) + X[9], 7);
		C = ROT (C + F (D, A, B) + X[10], 11);
		B = ROT (B + F (C, D, A) + X[11], 19);
		A = ROT (A + F (B, C, D) + X[12], 3);
		D = ROT (D + F (A, B, C) + X[13], 7);
		C = ROT (C + F (D, A, B) + X[14], 11);
		B = ROT (B + F (C, D, A) + X[15], 19);

		A = ROT (A + G (B, C, D) + X[0] + 0x5A827999, 3);
		D = ROT (D + G (A, B, C) + X[4] + 0x5A827999, 5);
		C = ROT (C + G (D, A, B) + X[8] + 0x5A827999, 9);
		B = ROT (B + G (C, D, A) + X[12] + 0x5A827999, 13);
		A = ROT (A + G (B, C, D) + X[1] + 0x5A827999, 3);
		D = ROT (D + G (A, B, C) + X[5] + 0x5A827999, 5);
		C = ROT (C + G (D, A, B) + X[9] + 0x5A827999, 9);
		B = ROT (B + G (C, D, A) + X[13] + 0x5A827999, 13);
		A = ROT (A + G (B, C, D) + X[2] + 0x5A827999, 3);
		D = ROT (D + G (A, B, C) + X[6] + 0x5A827999, 5);
		C = ROT (C + G (D, A, B) + X[10] + 0x5A827999, 9);
		B = ROT (B + G (C, D, A) + X[14] + 0x5A827999, 13);
		A = ROT (A + G (B, C, D) + X[3] + 0x5A827999, 3);
		D = ROT (D + G (A, B, C) + X[7] + 0x5A827999, 5);
		C = ROT (C + G (D, A, B) + X[11] + 0x5A827999, 9);
		B = ROT (B + G (C, D, A) + X[15] + 0x5A827999, 13);

		A = ROT (A + H (B, C, D) + X[0] + 0x6ED9EBA1, 3);
		D = ROT (D + H (A, B, C) + X[8] + 0x6ED9EBA1, 9);
		C = ROT (C + H (D, A, B) + X[4] + 0x6ED9EBA1, 11);
		B = ROT (B + H (C, D, A) + X[12] + 0x6ED9EBA1, 15);
		A = ROT (A + H (B, C, D) + X[2] + 0x6ED9EBA1, 3);
		D = ROT (D + H (A, B, C) + X[10] + 0x6ED9EBA1, 9);
		C = ROT (C + H (D, A, B) + X[6] + 0x6ED9EBA1, 11);
		B = ROT (B + H (C, D, A) + X[14] + 0x6ED9EBA1, 15);
		A = ROT (A + H (B, C, D) + X[1] + 0x6ED9EBA1, 3);
		D = ROT (D + H (A, B, C) + X[9] + 0x6ED9EBA1, 9);
		C = ROT (C + H (D, A, B) + X[5] + 0x6ED9EBA1, 11);
		B = ROT (B + H (C, D, A) + X[13] + 0x6ED9EBA1, 15);
		A = ROT (A + H (B, C, D) + X[3] + 0x6ED9EBA1, 3);
		D = ROT (D + H (A, B, C) + X[11] + 0x6ED9EBA1, 9);
		C = ROT (C + H (D, A, B) + X[7] + 0x6ED9EBA1, 11);
		B = ROT (B + H (C, D, A) + X[15] + 0x6ED9EBA1, 15);

		A += AA;
		B += BB;
		C += CC;
		D += DD;
	}

	store_le32(digest,      A);
	store_le32(digest + 4,  B);
	store_le32(digest + 8,  C);
	store_le32(digest + 12, D);

	return 0;
}

/* Public domain DES implementation from Phil Karn */
static const uint32_t Spbox[8][64] = {
	{ 0x01010400, 0x00000000, 0x00010000, 0x01010404,
	  0x01010004, 0x00010404, 0x00000004, 0x00010000,
	  0x00000400, 0x01010400, 0x01010404, 0x00000400,
	  0x01000404, 0x01010004, 0x01000000, 0x00000004,
	  0x00000404, 0x01000400, 0x01000400, 0x00010400,
	  0x00010400, 0x01010000, 0x01010000, 0x01000404,
	  0x00010004, 0x01000004, 0x01000004, 0x00010004,
	  0x00000000, 0x00000404, 0x00010404, 0x01000000,
	  0x00010000, 0x01010404, 0x00000004, 0x01010000,
	  0x01010400, 0x01000000, 0x01000000, 0x00000400,
	  0x01010004, 0x00010000, 0x00010400, 0x01000004,
	  0x00000400, 0x00000004, 0x01000404, 0x00010404,
	  0x01010404, 0x00010004, 0x01010000, 0x01000404,
	  0x01000004, 0x00000404, 0x00010404, 0x01010400,
	  0x00000404, 0x01000400, 0x01000400, 0x00000000,
	  0x00010004, 0x00010400, 0x00000000, 0x01010004 },
	{ 0x80108020, 0x80008000, 0x00008000, 0x00108020,
	  0x00100000, 0x00000020, 0x80100020, 0x80008020,
	  0x80000020, 0x80108020, 0x80108000, 0x80000000,
	  0x80008000, 0x00100000, 0x00000020, 0x80100020,
	  0x00108000, 0x00100020, 0x80008020, 0x00000000,
	  0x80000000, 0x00008000, 0x00108020, 0x80100000,
	  0x00100020, 0x80000020, 0x00000000, 0x00108000,
	  0x00008020, 0x80108000, 0x80100000, 0x00008020,
	  0x00000000, 0x00108020, 0x80100020, 0x00100000,
	  0x80008020, 0x80100000, 0x80108000, 0x00008000,
	  0x80100000, 0x80008000, 0x00000020, 0x80108020,
	  0x00108020, 0x00000020, 0x00008000, 0x80000000,
	  0x00008020, 0x80108000, 0x00100000, 0x80000020,
	  0x00100020, 0x80008020, 0x80000020, 0x00100020,
	  0x00108000, 0x00000000, 0x80008000, 0x00008020,
	  0x80000000, 0x80100020, 0x80108020, 0x00108000 },
	{ 0x00000208, 0x08020200, 0x00000000, 0x08020008,
	  0x08000200, 0x00000000, 0x00020208, 0x08000200,
	  0x00020008, 0x08000008, 0x08000008, 0x00020000,
	  0x08020208, 0x00020008, 0x08020000, 0x00000208,
	  0x08000000, 0x00000008, 0x08020200, 0x00000200,
	  0x00020200, 0x08020000, 0x08020008, 0x00020208,
	  0x08000208, 0x00020200, 0x00020000, 0x08000208,
	  0x00000008, 0x08020208, 0x00000200, 0x08000000,
	  0x08020200, 0x08000000, 0x00020008, 0x00000208,
	  0x00020000, 0x08020200, 0x08000200, 0x00000000,
	  0x00000200, 0x00020008, 0x08020208, 0x08000200,
	  0x08000008, 0x00000200, 0x00000000, 0x08020008,
	  0x08000208, 0x00020000, 0x08000000, 0x08020208,
	  0x00000008, 0x00020208, 0x00020200, 0x08000008,
	  0x08020000, 0x08000208, 0x00000208, 0x08020000,
	  0x00020208, 0x00000008, 0x08020008, 0x00020200 },
	{ 0x00802001, 0x00002081, 0x00002081, 0x00000080,
	  0x00802080, 0x00800081, 0x00800001, 0x00002001,
	  0x00000000, 0x00802000, 0x00802000, 0x00802081,
	  0x00000081, 0x00000000, 0x00800080, 0x00800001,
	  0x00000001, 0x00002000, 0x00800000, 0x00802001,
	  0x00000080, 0x00800000, 0x00002001, 0x00002080,
	  0x00800081, 0x00000001, 0x00002080, 0x00800080,
	  0x00002000, 0x00802080, 0x00802081, 0x00000081,
	  0x00800080, 0x00800001, 0x00802000, 0x00802081,
	  0x00000081, 0x00000000, 0x00000000, 0x00802000,
	  0x00002080, 0x00800080, 0x00800081, 0x00000001,
	  0x00802001, 0x00002081, 0x00002081, 0x00000080,
	  0x00802081, 0x00000081, 0x00000001, 0x00002000,
	  0x00800001, 0x00002001, 0x00802080, 0x00800081,
	  0x00002001, 0x00002080, 0x00800000, 0x00802001,
	  0x00000080, 0x00800000, 0x00002000, 0x00802080 },
	{ 0x00000100, 0x02080100, 0x02080000, 0x42000100,
	  0x00080000, 0x00000100, 0x40000000, 0x02080000,
	  0x40080100, 0x00080000, 0x02000100, 0x40080100,
	  0x42000100, 0x42080000, 0x00080100, 0x40000000,
	  0x02000000, 0x40080000, 0x40080000, 0x00000000,
	  0x40000100, 0x42080100, 0x42080100, 0x02000100,
	  0x42080000, 0x40000100, 0x00000000, 0x42000000,
	  0x02080100, 0x02000000, 0x42000000, 0x00080100,
	  0x00080000, 0x42000100, 0x00000100, 0x02000000,
	  0x40000000, 0x02080000, 0x42000100, 0x40080100,
	  0x02000100, 0x40000000, 0x42080000, 0x02080100,
	  0x40080100, 0x00000100, 0x02000000, 0x42080000,
	  0x42080100, 0x00080100, 0x42000000, 0x42080100,
	  0x02080000, 0x00000000, 0x40080000, 0x42000000,
	  0x00080100, 0x02000100, 0x40000100, 0x00080000,
	  0x00000000, 0x40080000, 0x02080100, 0x40000100 },
	{ 0x20000010, 0x20400000, 0x00004000, 0x20404010,
	  0x20400000, 0x00000010, 0x20404010, 0x00400000,
	  0x20004000, 0x00404010, 0x00400000, 0x20000010,
	  0x00400010, 0x20004000, 0x20000000, 0x00004010,
	  0x00000000, 0x00400010, 0x20004010, 0x00004000,
	  0x00404000, 0x20004010, 0x00000010, 0x20400010,
	  0x20400010, 0x00000000, 0x00404010, 0x20404000,
	  0x00004010, 0x00404000, 0x20404000, 0x20000000,
	  0x20004000, 0x00000010, 0x20400010, 0x00404000,
	  0x20404010, 0x00400000, 0x00004010, 0x20000010,
	  0x00400000, 0x20004000, 0x20000000, 0x00004010,
	  0x20000010, 0x20404010, 0x00404000, 0x20400000,
	  0x00404010, 0x20404000, 0x00000000, 0x20400010,
	  0x00000010, 0x00004000, 0x20400000, 0x00404010,
	  0x00004000, 0x00400010, 0x20004010, 0x00000000,
	  0x20404000, 0x20000000, 0x00400010, 0x20004010 },
	{ 0x00200000, 0x04200002, 0x04000802, 0x00000000,
	  0x00000800, 0x04000802, 0x00200802, 0x04200800,
	  0x04200802, 0x00200000, 0x00000000, 0x04000002,
	  0x00000002, 0x04000000, 0x04200002, 0x00000802,
	  0x04000800, 0x00200802, 0x00200002, 0x04000800,
	  0x04000002, 0x04200000, 0x04200800, 0x00200002,
	  0x04200000, 0x00000800, 0x00000802, 0x04200802,
	  0x00200800, 0x00000002, 0x04000000, 0x00200800,
	  0x04000000, 0x00200800, 0x00200000, 0x04000802,
	  0x04000802, 0x04200002, 0x04200002, 0x00000002,
	  0x00200002, 0x04000000, 0x04000800, 0x00200000,
	  0x04200800, 0x00000802, 0x00200802, 0x04200800,
	  0x00000802, 0x04000002, 0x04200802, 0x04200000,
	  0x00200800, 0x00000000, 0x00000002, 0x04200802,
	  0x00000000, 0x00200802, 0x04200000, 0x00000800,
	  0x04000002, 0x04000800, 0x00000800, 0x00200002 },
	{ 0x10001040, 0x00001000, 0x00040000, 0x10041040,
	  0x10000000, 0x10001040, 0x00000040, 0x10000000,
	  0x00040040, 0x10040000, 0x10041040, 0x00041000,
	  0x10041000, 0x00041040, 0x00001000, 0x00000040,
	  0x10040000, 0x10000040, 0x10001000, 0x00001040,
	  0x00041000, 0x00040040, 0x10040040, 0x10041000,
	  0x00001040, 0x00000000, 0x00000000, 0x10040040,
	  0x10000040, 0x10001000, 0x00041040, 0x00040000,
	  0x00041040, 0x00040000, 0x10041000, 0x00001000,
	  0x00000040, 0x10040040, 0x00001000, 0x00041040,
	  0x10001000, 0x00000040, 0x10000040, 0x10040000,
	  0x10040040, 0x10000000, 0x00040000, 0x10001040,
	  0x00000000, 0x10041040, 0x00040040, 0x10000040,
	  0x10040000, 0x10001000, 0x10001040, 0x00000000,
	  0x10041040, 0x00041000, 0x00041000, 0x00001040,
	  0x00001040, 0x00040040, 0x10000000, 0x10041000 }
};

#undef F
#define	F(l,r,key){\
	work = ((r >> 4) | (r << 28)) ^ key[0];\
	l ^= Spbox[6][work & 0x3f];\
	l ^= Spbox[4][(work >> 8) & 0x3f];\
	l ^= Spbox[2][(work >> 16) & 0x3f];\
	l ^= Spbox[0][(work >> 24) & 0x3f];\
	work = r ^ key[1];\
	l ^= Spbox[7][work & 0x3f];\
	l ^= Spbox[5][(work >> 8) & 0x3f];\
	l ^= Spbox[3][(work >> 16) & 0x3f];\
	l ^= Spbox[1][(work >> 24) & 0x3f];\
}

/* Encrypt or decrypt a block of data in ECB mode */
static void des (uint32_t ks[16][2], unsigned char block[8])
{
	uint32_t left, right, work;

	/* Read input block and place in left/right in big-endian order */
	left = load_be32(block);
	right = load_be32(block + 4);

	/* Hoey's clever initial permutation algorithm, from Outerbridge
	 * (see Schneier p 478)
	 *
	 * The convention here is the same as Outerbridge: rotate each
	 * register left by 1 bit, i.e., so that "left" contains permuted
	 * input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32
	 * (using origin-1 numbering as in the FIPS). This allows us to avoid
	 * one of the two rotates that would otherwise be required in each of
	 * the 16 rounds.
	 */
	work = ((left >> 4) ^ right) & 0x0f0f0f0f;
	right ^= work;
	left ^= work << 4;
	work = ((left >> 16) ^ right) & 0xffff;
	right ^= work;
	left ^= work << 16;
	work = ((right >> 2) ^ left) & 0x33333333;
	left ^= work;
	right ^= (work << 2);
	work = ((right >> 8) ^ left) & 0xff00ff;
	left ^= work;
	right ^= (work << 8);
	right = (right << 1) | (right >> 31);
	work = (left ^ right) & 0xaaaaaaaa;
	left ^= work;
	right ^= work;
	left = (left << 1) | (left >> 31);

	/* Now do the 16 rounds */
	F (left,right,ks[0]);
	F (right,left,ks[1]);
	F (left,right,ks[2]);
	F (right,left,ks[3]);
	F (left,right,ks[4]);
	F (right,left,ks[5]);
	F (left,right,ks[6]);
	F (right,left,ks[7]);
	F (left,right,ks[8]);
	F (right,left,ks[9]);
	F (left,right,ks[10]);
	F (right,left,ks[11]);
	F (left,right,ks[12]);
	F (right,left,ks[13]);
	F (left,right,ks[14]);
	F (right,left,ks[15]);

	/* Inverse permutation, also from Hoey via Outerbridge and Schneier */
	right = (right << 31) | (right >> 1);
	work = (left ^ right) & 0xaaaaaaaa;
	left ^= work;
	right ^= work;
	left = (left >> 1) | (left  << 31);
	work = ((left >> 8) ^ right) & 0xff00ff;
	right ^= work;
	left ^= work << 8;
	work = ((left >> 2) ^ right) & 0x33333333;
	right ^= work;
	left ^= work << 2;
	work = ((right >> 16) ^ left) & 0xffff;
	left ^= work;
	right ^= work << 16;
	work = ((right >> 4) ^ left) & 0x0f0f0f0f;
	left ^= work;
	right ^= work << 4;

	/* Put the block back into the user's buffer with final swap */
	store_be32(block, right);
	store_be32(block + 4, left);
}

/* Key schedule-related tables from FIPS-46 */

/* permuted choice table (key) */
static const unsigned char pc1[] = {
	57, 49, 41, 33, 25, 17,  9,
	 1, 58, 50, 42, 34, 26, 18,
	10,  2, 59, 51, 43, 35, 27,
	19, 11,  3, 60, 52, 44, 36,

	63, 55, 47, 39, 31, 23, 15,
	 7, 62, 54, 46, 38, 30, 22,
	14,  6, 61, 53, 45, 37, 29,
	21, 13,  5, 28, 20, 12,  4
};

/* number left rotations of pc1 */
static const unsigned char totrot[] = {
	1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28
};

/* permuted choice key (table) */
static const unsigned char pc2[] = {
	14, 17, 11, 24,  1,  5,
	 3, 28, 15,  6, 21, 10,
	23, 19, 12,  4, 26,  8,
	16,  7, 27, 20, 13,  2,
	41, 52, 31, 37, 47, 55,
	30, 40, 51, 45, 33, 48,
	44, 49, 39, 56, 34, 53,
	46, 42, 50, 36, 29, 32
};

/* End of DES-defined tables */

/* bit 0 is left-most in byte */
static const int bytebit[] = {
	0200,0100,040,020,010,04,02,01
};

/* Generate key schedule for encryption or decryption
 * depending on the value of "decrypt"
 */
static void deskey (DES_KS k, unsigned char *key, int decrypt)
{
	unsigned char pc1m[56];		/* place to modify pc1 into */
	unsigned char pcr[56];		/* place to rotate pc1 into */
	register int i,j,l;
	int m;
	unsigned char ks[8];

	for (j=0; j<56; j++) {		/* convert pc1 to bits of key */
		l=pc1[j]-1;		/* integer bit location	 */
		m = l & 07;		/* find bit		 */
		pc1m[j]=(key[l>>3] &	/* find which key byte l is in */
			bytebit[m])	/* and which bit of that byte */
			? 1 : 0;	/* and store 1-bit result */
	}
	for (i=0; i<16; i++) {		/* key chunk for each iteration */
		memset (ks,0,sizeof (ks));	/* Clear key schedule */
		for (j=0; j<56; j++)	/* rotate pc1 the right amount */
			pcr[j] = pc1m[(l = j + totrot[decrypt? 15 - i : i]) < (j < 28? 28 : 56) ? l: l - 28];
			/* rotate left and right halves independently */
		for (j=0; j<48; j++){	/* select bits individually */
			/* check bit that goes to ks[j] */
			if (pcr[pc2[j]-1]) {
				/* mask it in if it's there */
				l= j % 6;
				ks[j / 6] |= bytebit[l] >> 2;
			}
		}
		/* Now convert to packed odd/even interleaved form */
		k[i][0] = ((uint32_t) ks[0] << 24)
		 | ((uint32_t) ks[2] << 16)
		 | ((uint32_t) ks[4] << 8)
		 | ((uint32_t) ks[6]);
		k[i][1] = ((uint32_t) ks[1] << 24)
		 | ((uint32_t) ks[3] << 16)
		 | ((uint32_t) ks[5] << 8)
		 | ((uint32_t) ks[7]);
	}
}

#define HIKEYBITS(k,s) ((k[(s) / 8] << ((s) % 8)) & 0xFF)
#define LOKEYBITS(k,s) (k[(s) / 8 + 1] >> (8 - (s) % 8))

/* DES utils */
/* Set up a key schedule based on a 56bit key */
static void setup_schedule (const unsigned char *key_56, DES_KS ks)
{
	unsigned char key[8];
	int i, c, bit;

	for (i = 0; i < 8; i++) {
		key[i] = HIKEYBITS (key_56, i * 7);
		/* Mask in the low bits only if they're used. It doesn't
		 * matter if we get an unwanted bit 0; it's going to be
		 * overwritten with parity anyway. */
		if (i && i < 7)
			key[i] |= LOKEYBITS(key_56, i * 7);

		/* Fix parity */
		for (c = bit = 0; bit < 8; bit++)
			if (key[i] & (1 << bit))
				c++;
		if (!(c & 1))
			key[i] ^= 0x01;
	}

	deskey (ks, key, 0);
}

#define LM_PASSWORD_MAGIC "\x4B\x47\x53\x21\x40\x23\x24\x25" \
                          "\x4B\x47\x53\x21\x40\x23\x24\x25" \
			  "\x00\x00\x00\x00\x00"

static void ntlm_lanmanager_hash (const char *password, char hash[21])
{
	unsigned char lm_password[15];
	DES_KS ks;
	int i;

	for (i = 0; i < 14 && password[i]; i++)
		lm_password[i] = toupper ((unsigned char) password[i]);

	for (; i < 15; i++)
		lm_password[i] = '\0';

	memcpy (hash, LM_PASSWORD_MAGIC, 21);

	setup_schedule (lm_password, ks);
	des (ks, (unsigned char *) hash);

	setup_schedule (lm_password + 7, ks);
	des (ks, (unsigned char *) hash + 8);

	memset(lm_password, 0, sizeof(lm_password));
}

static int ntlm_nt_hash (const char *pass, char hash[21])
{
	struct oc_text_buf *utf16pass = buf_alloc();
	int ret;

	/* Preallocate just to ensure md4sum() doesn't have to realloc, which
	   would leave a copy of the password lying around. There is always
	   at least one byte of padding, then 8 bytes of length, and round up
	   to the next multiple of 64. */
	ret = buf_ensure_space(utf16pass, ((strlen(pass) * 2) + 1 + 8 + 63) & ~63);
	if (ret)
		goto out;

	ret = buf_append_utf16le(utf16pass, pass);
	if (ret < 0)
		goto wipe;

	ret = buf_error(utf16pass);
	if (ret)
		goto wipe;

	ret = md4sum(utf16pass, (unsigned char *) hash);
	if (ret)
		goto wipe;

	memset(hash + 16, 0, 5);
 wipe:
	memset(utf16pass->data, 0, utf16pass->pos);
 out:
	buf_free(utf16pass);
	return 0;
}

static void ntlm_calc_response (const unsigned char key[21],
				const unsigned char plaintext[8],
				unsigned char results[24])
{
	DES_KS ks;

	memcpy (results, plaintext, 8);
	memcpy (results + 8, plaintext, 8);
	memcpy (results + 16, plaintext, 8);

	setup_schedule (key, ks);
	des (ks, results);

	setup_schedule (key + 7, ks);
	des (ks, results + 8);

	setup_schedule (key + 14, ks);
	des (ks, results + 16);
}

#define NTLM_CHALLENGE_DOMAIN_OFFSET		12
#define NTLM_CHALLENGE_FLAGS_OFFSET		20
#define NTLM_CHALLENGE_NONCE_OFFSET		24

#define NTLM_RESPONSE_BASE_SIZE      64
#define NTLM_RESPONSE_LM_RESP_OFFSET 12
#define NTLM_RESPONSE_NT_RESP_OFFSET 20
#define NTLM_RESPONSE_DOMAIN_OFFSET  28
#define NTLM_RESPONSE_USER_OFFSET    36
#define NTLM_RESPONSE_HOST_OFFSET    44
#define NTLM_RESPONSE_FLAGS_OFFSET   60

static const char ntlm_response_base[NTLM_RESPONSE_BASE_SIZE] = {
	'N',  'T',  'L',  'M',  'S',  'S',  'P',  0x00,
	0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x82, 0x01, 0x00, 0x00
};

static void ntlm_set_string_utf8(struct oc_text_buf *buf, int offset,
				 const char *data)
{
	int oldpos = buf->pos;
	int len = buf_append_utf16le(buf, data);

	/* Fill in the SecurityBuffer pointing to the string */
	store_le16(buf->data + offset, len);		/* len */
	store_le16(buf->data + offset + 2, len);	/* allocated */
	store_le32(buf->data + offset + 4, oldpos);	/* offset */
}

static void ntlm_set_string_binary(struct oc_text_buf *buf, int offset,
				   const void *data, int len)
{
	/* Fill in the SecurityBuffer pointing to the string */
	store_le16(buf->data + offset, len);		/* len */
	store_le16(buf->data + offset + 2, len);	/* allocated */
	store_le32(buf->data + offset + 4, buf->pos);	/* offset */

	buf_append_bytes(buf, data, len);
}

static int ntlm_manual_challenge(struct openconnect_info *vpninfo, int proxy,
				 struct http_auth_state *auth_state,
				 struct oc_text_buf *hdrbuf,
				 const char *domuser, const char *pass)
{
	struct oc_text_buf *resp;
	char *user;
	unsigned char nonce[8], hash[21], lm_resp[24], nt_resp[24];
	unsigned char *token;
	int token_len = -EINVAL;
	int ntlmver;

	if (!auth_state->challenge)
		return -EINVAL;

	if (ntlm_nt_hash (pass, (char *) hash))
		return -EINVAL;

	token = openconnect_base64_decode(&token_len,
					  auth_state->challenge);
	if (!token)
		return token_len;

	if (token_len < NTLM_CHALLENGE_NONCE_OFFSET + 8 || token[0] != 'N' ||
	    token[1] != 'T' || token[2] != 'L' || token[3] != 'M' ||
	    token[4] != 'S' || token[5] != 'S' || token[6] != 'P' ||
	    token[7] || token[8] != 2 || token[9] || token[10] || token[11]) {
		free(token);
		return -EINVAL;
	}

	/* 0x00080000: Negotiate NTLM2 Key */
	if (token[NTLM_CHALLENGE_FLAGS_OFFSET + 2] & 8) {
		/* NTLM2 session response */
		struct {
			uint32_t srv[2];
			uint32_t clnt[2];
		} sess_nonce;
		unsigned char digest[16];

		ntlmver = 2;
		if (openconnect_random(sess_nonce.clnt, sizeof(sess_nonce.clnt))) {
			free(token);
			return -EIO;
		}

		/* LM response is 8-byte client nonce, NUL-padded to 24 */
		memcpy (lm_resp, sess_nonce.clnt, 8);
		memset (lm_resp + 8, 0, 16);

		/* Session nonce is client nonce + server nonce */
		memcpy (sess_nonce.srv,
			token + NTLM_CHALLENGE_NONCE_OFFSET, 8);

		/* Take MD5 of session nonce */
		if (openconnect_md5(digest, &sess_nonce, sizeof(sess_nonce))) {
			free(token);
			return -EIO;
		}
		ntlm_calc_response (hash, digest, nt_resp);
	} else {
		/* NTLM1 */
		ntlmver = 1;
		memcpy (nonce, token + NTLM_CHALLENGE_NONCE_OFFSET, 8);
		ntlm_calc_response (hash, nonce, nt_resp);
		ntlm_lanmanager_hash (pass, (char *) hash);
		ntlm_calc_response (hash, nonce, lm_resp);
	}

	resp = buf_alloc();
	buf_append_bytes(resp, ntlm_response_base, sizeof(ntlm_response_base));
	if (buf_error(resp)) {
		free(token);
		return buf_free(resp);
	}
	/* Mask in the NTLM2SESSION flag */
	resp->data[NTLM_RESPONSE_FLAGS_OFFSET + 2] = token[NTLM_CHALLENGE_FLAGS_OFFSET + 2] & 8;

	user = strchr(domuser, '\\');
	if (user) {
		*user = 0;
		ntlm_set_string_utf8(resp, NTLM_RESPONSE_DOMAIN_OFFSET, domuser);
		*user = '\\';
		user++;
	} else {
		int offset = load_le32(token + NTLM_CHALLENGE_DOMAIN_OFFSET + 4);
		int len = load_le16(token + NTLM_CHALLENGE_DOMAIN_OFFSET);
		if (!len || offset + len >= token_len) {
			free(token);
			buf_free(resp);
			return -EINVAL;
		}
		ntlm_set_string_binary(resp, NTLM_RESPONSE_DOMAIN_OFFSET, token + offset, len);

		user = (char *)domuser;
	}

	ntlm_set_string_utf8(resp, NTLM_RESPONSE_USER_OFFSET, user);
	ntlm_set_string_utf8(resp, NTLM_RESPONSE_HOST_OFFSET, "UNKNOWN");
	ntlm_set_string_binary(resp, NTLM_RESPONSE_LM_RESP_OFFSET, lm_resp, sizeof(lm_resp));
	ntlm_set_string_binary(resp, NTLM_RESPONSE_NT_RESP_OFFSET, nt_resp, sizeof(nt_resp));

	free(token);

	if (buf_error(resp))
		return buf_free(resp);

	buf_append(hdrbuf, "%sAuthorization: NTLM ", proxy ? "Proxy-" : "");
	buf_append_base64(hdrbuf, resp->data, resp->pos);
	buf_append(hdrbuf, "\r\n");

	buf_free(resp);
	if (proxy)
		vpn_progress(vpninfo, PRG_INFO,
			     _("Attempting HTTP NTLMv%d authentication to proxy\n"),
			     ntlmver);
	else
		vpn_progress(vpninfo, PRG_INFO,
			     _("Attempting HTTP NTLMv%d authentication to server '%s'\n"),
			     ntlmver, vpninfo->hostname);
	return 0;
}

int ntlm_authorization(struct openconnect_info *vpninfo, int proxy,
		       struct http_auth_state *auth_state, struct oc_text_buf *buf)
{
	const char *user, *pass;

	if (proxy) {
		user = vpninfo->proxy_user;
		pass = vpninfo->proxy_pass;
	} else {
		user = pass = NULL;
	}

	if (auth_state->state == AUTH_AVAILABLE) {
		auth_state->state = NTLM_MANUAL;
		/* Don't attempt automatic NTLM auth if we were given a password */
		if (!pass && !ntlm_helper_spawn(vpninfo, proxy, auth_state, buf)) {
			auth_state->state = NTLM_SSO_REQ;
			return 0;
		}
	}
	if (auth_state->state == NTLM_SSO_REQ) {
		int ret;
		ret = ntlm_helper_challenge(vpninfo, proxy, auth_state, buf);
		/* Clean up after it. We're done here, whether it worked or not */
		cleanup_ntlm_auth(vpninfo, auth_state);
		auth_state->state = NTLM_MANUAL;
		if (ret == -EAGAIN) {
			/* Don't let it reset our state when it reconnects */
			if (proxy)
				vpninfo->proxy_close_during_auth = 1;
			return ret;
		}
		if (!ret)
			return ret;
	}
	if (auth_state->state == NTLM_MANUAL && user && pass) {
		buf_append(buf, "%sAuthorization: NTLM %s\r\n", proxy ? "Proxy-" : "",
			   "TlRMTVNTUAABAAAABYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA");
		auth_state->state = NTLM_MANUAL_REQ;
		return 0;
	}
	if (auth_state->state == NTLM_MANUAL_REQ && user && pass &&
	    !ntlm_manual_challenge(vpninfo, proxy, auth_state, buf, user, pass)) {
		/* Leave the state as it is. If we come back there'll be no
		   challenge string and we'll fail then. */
		return 0;
	}
	auth_state->state = AUTH_FAILED;
	return -EAGAIN;
}
