/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or  http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *   Portions Copyright RISC OS Developments 2019+, credited to the RISC OS One Project.
 */

/*
 *   NTLMv2 responses, as [MS-NLMP] section 3.3.2.
 */

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

#include "md4.h"
#include "md5.h"
#include "ntlm.h"

/*
    Widen 8 bit text to UTF-16LE, optionally upper casing as it goes,
    stopping at the end of the buffer.  Returns the number of bytes
    written.  Account names are far shorter than the buffer; a name long
    enough to be cut short would fail the logon at the server rather than
    silently authenticate as something else.
*/
static int widen(char *s, int upper, unsigned char *out, int out_max)
{
    int n;

    n = 0;
    while(*s && n + 2 <= out_max)
    {
        out[n++] = (unsigned char) (upper ? toupper(*s) : *s);
        out[n++] = 0;
        s++;
    }
    return n;
}

void NTLMPasswordHash(char *passwd, unsigned char out[NTLM_HASH_LEN])
{
    unsigned char wide[512];
    int n;

    n = 0;
    while(*passwd && n + 2 <= (int) sizeof(wide))
    {
        wide[n++] = (unsigned char) *passwd++;
        wide[n++] = 0;
    }
    md4_get_digest(wide, n, out);
    memset(wide, 0, sizeof(wide));
}

void NTLMv2Key(char *user, char *domain,
               unsigned char nt_hash[NTLM_HASH_LEN],
               unsigned char key[NTLM_HASH_LEN])
{
    unsigned char wide[1024];
    int n;

    if(user == NULL) user = "";
    if(domain == NULL) domain = "";

    n = widen(user, 1, wide, sizeof(wide));
    n += widen(domain, 0, wide + n, (int) sizeof(wide) - n);

    hmac_md5(nt_hash, NTLM_HASH_LEN, wide, n, key);
    memset(wide, 0, sizeof(wide));
}

int NTLMv2Response(unsigned char key[NTLM_HASH_LEN],
                   char *challenge,
                   unsigned char *client_challenge,
                   unsigned int time_low, unsigned int time_high,
                   const unsigned char *target_info, int target_len,
                   unsigned char *out, int out_max)
{
    unsigned char work[NTLM_CHAL_LEN + 640];
    unsigned char *blob;
    int blob_len, n;

    if(target_info == NULL || target_len <= 0)
    {
        target_info = NULL;
        target_len = 0;
    }

    /*
        The blob is the 28 byte fixed part, then the target information
        list, then a 4 byte terminator.  A list supplied by a server ends
        with its own MsvAvEOL pair; with no list we still send one, so the
        4 bytes below stand in for it.
    */
    blob_len = 28 + (target_len ? target_len : 4) + 4;
    if(out_max < NTLM_HASH_LEN + blob_len) return 0;
    if((int) sizeof(work) < NTLM_CHAL_LEN + blob_len) return 0;

    blob = out + NTLM_HASH_LEN;
    n = 0;
    blob[n++] = 1;                      /* Responserversion */
    blob[n++] = 1;                      /* HiResponserversion */
    memset(blob + n, 0, 6); n += 6;     /* Z(6) */
    blob[n++] = (unsigned char) (time_low);
    blob[n++] = (unsigned char) (time_low >> 8);
    blob[n++] = (unsigned char) (time_low >> 16);
    blob[n++] = (unsigned char) (time_low >> 24);
    blob[n++] = (unsigned char) (time_high);
    blob[n++] = (unsigned char) (time_high >> 8);
    blob[n++] = (unsigned char) (time_high >> 16);
    blob[n++] = (unsigned char) (time_high >> 24);
    memcpy(blob + n, client_challenge, NTLM_CHAL_LEN); n += NTLM_CHAL_LEN;
    memset(blob + n, 0, 4); n += 4;     /* Z(4) */
    if(target_len)
    {
        memcpy(blob + n, target_info, target_len);
        n += target_len;
    }
    else
    {
        memset(blob + n, 0, 4); n += 4; /* MsvAvEOL */
    }
    memset(blob + n, 0, 4); n += 4;     /* Z(4) */

    /* NTProofStr = HMAC_MD5(key, server challenge + blob) */
    memcpy(work, challenge, NTLM_CHAL_LEN);
    memcpy(work + NTLM_CHAL_LEN, blob, n);
    hmac_md5(key, NTLM_HASH_LEN, work, NTLM_CHAL_LEN + n, out);

    memset(work, 0, sizeof(work));
    return NTLM_HASH_LEN + n;
}

void LMv2Response(unsigned char key[NTLM_HASH_LEN],
                  char *challenge,
                  unsigned char *client_challenge,
                  unsigned char out[NTLM_LMV2_LEN])
{
    unsigned char work[NTLM_CHAL_LEN * 2];

    memcpy(work, challenge, NTLM_CHAL_LEN);
    memcpy(work + NTLM_CHAL_LEN, client_challenge, NTLM_CHAL_LEN);
    hmac_md5(key, NTLM_HASH_LEN, work, sizeof(work), out);
    memcpy(out + NTLM_HASH_LEN, client_challenge, NTLM_CHAL_LEN);
}
