/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/* Copyright (C) 2001-2004 Novell, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU Lesser General Public
 * License 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "xntlm.h"
#include "xntlm-des.h"
#include "xntlm-md4.h"

#include <ctype.h>
#include <string.h>


static unsigned char NTLM_NEGOTIATE_MESSAGE[] = {
	 'N',  'T',  'L',  'M',  'S',  'S',  'P', 0x00,
	0x01, 0x00, 0x00, 0x00, 0x06, 0x82, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00
};

/**
 * xntlm_negotiate:
 *
 * Creates an NTLM Type 1 (Negotiate) message
 *
 * Return value: the message
 **/
GByteArray *
xntlm_negotiate (void)
{
	GByteArray *message;

	message = g_byte_array_new ();
	g_byte_array_append (message, NTLM_NEGOTIATE_MESSAGE,
			     sizeof (NTLM_NEGOTIATE_MESSAGE));
	return message;
}


#define GET_SHORTY(p) ((p)[0] + ((p)[1] << 8))

static char *
strip_dup (unsigned char *mem, int len)
{
	char *buf = g_malloc (len / 2 + 1), *p = buf;

	while (len > 0) {
		*p = (char)*mem;
		p++;
		mem += 2;
		len -= 2;
	}

	*p = '\0';
	return buf;
}

#define NTLM_CHALLENGE_NONCE_POS       24
#define NTLM_CHALLENGE_NONCE_LEN        8

#define NTLM_CHALLENGE_DATA_OFFSET_POS 44
#define NTLM_CHALLENGE_DATA_LENGTH_POS 40

#define NTLM_CHALLENGE_DATA_NT_DOMAIN   2
#define NTLM_CHALLENGE_DATA_W2K_DOMAIN  4

#define NTLM_CHALLENGE_BASE_SIZE       48

/**
 * xntlm_parse_challenge:
 * @challenge: buffer containing an NTLM Type 2 (Challenge) message
 * @len: the length of @challenge
 * @nonce: return variable for the challenge nonce, or %NULL
 * @nt_domain: return variable for the server NT domain, or %NULL
 * @w2k_domain: return variable for the server W2k domain, or %NULL
 *
 * Attempts to parse the challenge in @challenge. If @nonce is
 * non-%NULL, the 8-byte nonce from @challenge will be returned in it.
 * Likewise, if @nt_domain and/or @w2k_domain are non-%NULL, the
 * server's domain names will be returned in them. The strings
 * returned must be freed with g_free().
 *
 * Return value: %TRUE if the challenge could be parsed,
 * %FALSE otherwise.
 **/
gboolean
xntlm_parse_challenge (gpointer challenge, int len, char **nonce,
		       char **nt_domain, char **w2k_domain)
{
	unsigned char *chall = (unsigned char *)challenge;
	int off, dlen, doff, type;

	if (len < NTLM_CHALLENGE_BASE_SIZE)
		return FALSE;

	off = GET_SHORTY (chall + NTLM_CHALLENGE_DATA_OFFSET_POS);
	dlen = GET_SHORTY (chall + NTLM_CHALLENGE_DATA_LENGTH_POS);
	if (len < off + dlen)
		return FALSE;

	if (nonce) {
		*nonce = g_memdup (chall + NTLM_CHALLENGE_NONCE_POS,
				   NTLM_CHALLENGE_NONCE_LEN);
	}

	if (!nt_domain && !w2k_domain)
		return TRUE;

	while (off < len - 4) {
		type = GET_SHORTY (chall + off);
		dlen = GET_SHORTY (chall + off + 2);
		doff = off + 4;
		if (doff + dlen > len)
			break;

		switch (type) {
		case NTLM_CHALLENGE_DATA_NT_DOMAIN:
			if (nt_domain)
				*nt_domain = strip_dup (chall + doff, dlen);
			break;
		case NTLM_CHALLENGE_DATA_W2K_DOMAIN:
			if (w2k_domain)
				*w2k_domain = strip_dup (chall + doff, dlen);
			break;
		}

		off = doff + dlen;
	}

	return TRUE;
}


static void
ntlm_set_string (GByteArray *ba, int offset, const char *data, int len)
{
	ba->data[offset    ] = ba->data[offset + 2] =  len       & 0xFF;
	ba->data[offset + 1] = ba->data[offset + 3] = (len >> 8) & 0xFF;
	ba->data[offset + 4] =  ba->len       & 0xFF;
	ba->data[offset + 5] = (ba->len >> 8) & 0xFF;
	g_byte_array_append (ba, data, len);
}

static void ntlm_lanmanager_hash (const char *password, char hash[21]);
static void ntlm_nt_hash         (const char *password, char hash[21]);
static void ntlm_calc_response   (const guchar key[21],
				  const guchar plaintext[8],
				  guchar results[24]);

static unsigned char NTLM_RESPONSE_MESSAGE_HEADER[] = {
	 'N',  'T',  'L',  'M',  'S',  'S',  'P', 0x00,
	0x03, 0x00, 0x00, 0x00, 0x02, 0x82, 0x00, 0x00
};

#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_WORKSTATION_OFFSET    44

/**
 * xntlm_authenticate:
 * @nonce: the nonce from an NTLM Type 2 (Challenge) message
 * @domain: the NT domain to authenticate against
 * @user: the name of the user in @domain
 * @password: @user's password
 * @workstation: the name of the local workstation authenticated
 * against, or %NULL.
 *
 * Generates an NTLM Type 3 (Authenticate) message from the given
 * data. @workstation is provided for completeness, but can basically
 * always be left %NULL.
 *
 * Return value: the NTLM Type 3 message
 **/
GByteArray *
xntlm_authenticate (const char *nonce, const char *domain,
		    const char *user, const char *password,
		    const char *workstation)
{
	GByteArray *message;
	guchar hash[21], lm_resp[24], nt_resp[24];

	if (!workstation)
		workstation = "";

	message = g_byte_array_new ();

	ntlm_lanmanager_hash (password, hash);
	ntlm_calc_response (hash, nonce, lm_resp);
	ntlm_nt_hash (password, hash);
	ntlm_calc_response (hash, nonce, nt_resp);

	g_byte_array_set_size (message, NTLM_RESPONSE_BASE_SIZE);
	memset (message->data, 0, NTLM_RESPONSE_BASE_SIZE);
	memcpy (message->data, NTLM_RESPONSE_MESSAGE_HEADER,
		sizeof (NTLM_RESPONSE_MESSAGE_HEADER));

	ntlm_set_string (message, NTLM_RESPONSE_DOMAIN_OFFSET,
			 domain, strlen (domain));
	ntlm_set_string (message, NTLM_RESPONSE_USER_OFFSET,
			 user, strlen (user));
	ntlm_set_string (message, NTLM_RESPONSE_WORKSTATION_OFFSET,
			 workstation, strlen (workstation));
	ntlm_set_string (message, NTLM_RESPONSE_LM_RESP_OFFSET,
			 lm_resp, sizeof (lm_resp));
	ntlm_set_string (message, NTLM_RESPONSE_NT_RESP_OFFSET,
			 nt_resp, sizeof (nt_resp));

	return message;
}


static void
setup_schedule (const guchar *key_56, XNTLM_DES_KS ks)
{
	guchar key[8];
	int i, c, bit;

	key[0] = (key_56[0])                                 ;
	key[1] = (key_56[1] >> 1) | ((key_56[0] << 7) & 0xFF);
	key[2] = (key_56[2] >> 2) | ((key_56[1] << 6) & 0xFF);
	key[3] = (key_56[3] >> 3) | ((key_56[2] << 5) & 0xFF);
	key[4] = (key_56[4] >> 4) | ((key_56[3] << 4) & 0xFF);
	key[5] = (key_56[5] >> 5) | ((key_56[4] << 3) & 0xFF);
	key[6] = (key_56[6] >> 6) | ((key_56[5] << 2) & 0xFF);
	key[7] =                    ((key_56[6] << 1) & 0xFF);

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

        xntlm_deskey (ks, key, XNTLM_DES_ENCRYPT);
}

static unsigned char LM_PASSWORD_MAGIC[] = {
	0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25,
	0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25,
	0x00, 0x00, 0x00, 0x00, 0x00
};

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

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

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

	memcpy (hash, LM_PASSWORD_MAGIC, sizeof (LM_PASSWORD_MAGIC));

	setup_schedule (lm_password, ks);
	xntlm_des (ks, hash);

	setup_schedule (lm_password + 7, ks);
	xntlm_des (ks, hash + 8);
}

static void
ntlm_nt_hash (const char *password, char hash[21])
{
	unsigned char *buf, *p;

	p = buf = g_malloc (strlen (password) * 2);

	while (*password) {
		*p++ = *password++;
		*p++ = '\0';
	}

	xntlm_md4sum (buf, p - buf, hash);
	memset (hash + 16, 0, 5);

	g_free (buf);
}

static void
ntlm_calc_response (const guchar key[21], const guchar plaintext[8],
		    guchar results[24])
{
        XNTLM_DES_KS ks;

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

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

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

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


