/*
 * 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.
 */

/*
 *   The three NTLMSSP messages, as [MS-NLMP].
 */

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

#include "md5.h"
#include "ntlm.h"
#include "ntlmssp.h"

static char signature[8] = { 'N','T','L','M','S','S','P','\0' };

/*
    What this client is prepared to do.  EXTENDED_SESSIONSECURITY is what
    marks the exchange as the v2 one; without it a server is entitled to
    treat the answer as the old, replayable kind.
*/
#define F_UNICODE        (0x00000001u)
#define F_REQUEST_TARGET (0x00000004u)
#define F_NTLM           (0x00000200u)
#define F_ALWAYS_SIGN    (0x00008000u)
#define F_EXTENDED_SEC   (0x00080000u)
#define F_128            (0x20000000u)
#define F_56             (0x80000000u)

#define CLIENT_FLAGS (F_UNICODE | F_REQUEST_TARGET | F_NTLM | \
                      F_ALWAYS_SIGN | F_EXTENDED_SEC | F_128 | F_56)

#define F_ANONYMOUS  (0x00000800)   /* asking to be let in as nobody */

/* No Version and no MIC, so the payload starts straight after the fields */
#define TYPE3_FIXED (64)

static void put16(unsigned char *p, unsigned int v)
{
    p[0] = (unsigned char) v;
    p[1] = (unsigned char) (v >> 8);
}

static void put32(unsigned char *p, unsigned int v)
{
    p[0] = (unsigned char) v;
    p[1] = (unsigned char) (v >> 8);
    p[2] = (unsigned char) (v >> 16);
    p[3] = (unsigned char) (v >> 24);
}

static unsigned int get16(const unsigned char *p)
{
    return ((unsigned int) p[0]) | (((unsigned int) p[1]) << 8);
}

static unsigned int get32(const unsigned char *p)
{
    return ((unsigned int) p[0]) | (((unsigned int) p[1]) << 8) |
           (((unsigned int) p[2]) << 16) | (((unsigned int) p[3]) << 24);
}

/* Length, maximum length and offset, the triple that describes every
   variable field in these messages */
static void put_field(unsigned char *p, int len, int offset)
{
    put16(p, len);
    put16(p + 2, len);
    put32(p + 4, offset);
}

static int widen(char *s, int upper, unsigned char *out, int out_max)
{
    int n;

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

int NTLMSSPNegotiate(unsigned char *out, int out_max)
{
    if(out_max < 32) return 0;

    memcpy(out, signature, 8);
    put32(out + 8, 1);                  /* NtLmNegotiate */
    put32(out + 12, CLIENT_FLAGS);
    put_field(out + 16, 0, 32);         /* DomainName: let the server choose */
    put_field(out + 24, 0, 32);         /* Workstation: likewise */
    return 32;
}

int NTLMSSPParseChallenge(const unsigned char *msg, int len,
                          unsigned char challenge[NTLM_CHAL_LEN],
                          const unsigned char **target_info,
                          int *target_len)
{
    int tlen, toff;

    *target_info = NULL;
    *target_len = 0;

    if(len < 48) return 0;
    if(memcmp(msg, signature, 8) != 0) return 0;
    if(get32(msg + 8) != 2) return 0;

    memcpy(challenge, msg + 24, NTLM_CHAL_LEN);

    /* The target information list, which has to go into the answer exactly
       as it arrived: the server checks the answer against its own copy. */
    tlen = (int) get16(msg + 40);
    toff = (int) get32(msg + 44);
    if((tlen > 0) && (toff >= 0) && (toff + tlen <= len))
    {
        *target_info = msg + toff;
        *target_len = tlen;
    }
    return 1;
}

/*
    The answer that asks for nothing: no name, no password, no proof.

    A server that allows it lets the connection in as a guest, or as
    nobody at all, and says which in the session flags of its reply.  It is
    how a share list is got from a machine one has no account on, and how
    guest sharing on a Mac is reached - a machine that refuses a name it
    does not know will often still answer to no name at all.

    The shape of it is laid down: the anonymous flag set, one zero byte
    where the old-style answer goes, nothing at all where the new-style
    answer goes, and no name and no domain.  There is no session key, so
    nothing that follows can be signed.
*/
int NTLMSSPAnonymous(char *workstation, unsigned char *out, int out_max)
{
    unsigned char wwork[256];
    int wlen, off;

    if(workstation == NULL) workstation = "";
    wlen = widen(workstation, 0, wwork, sizeof(wwork));

    if(out_max < TYPE3_FIXED + 1 + wlen)
        return 0;

    memcpy(out, signature, 8);
    put32(out + 8, 3);                  /* NtLmAuthenticate */

    off = TYPE3_FIXED;
    put_field(out + 12, 1, off);        /* LM answer: a single zero byte */
    out[off] = 0;
    off += 1;

    put_field(out + 20, 0, off);        /* NT answer: none */
    put_field(out + 28, 0, off);        /* no domain */
    put_field(out + 36, 0, off);        /* no user */

    put_field(out + 44, wlen, off);
    memcpy(out + off, wwork, wlen);
    off += wlen;

    put_field(out + 52, 0, off);        /* no key exchange */
    put32(out + 60, CLIENT_FLAGS | F_ANONYMOUS);
    return off;
}

int NTLMSSPAuthenticate(char *user, char *domain, char *workstation,
                        unsigned char nt_hash[NTLM_HASH_LEN],
                        unsigned char challenge[NTLM_CHAL_LEN],
                        unsigned char *client_challenge,
                        unsigned int time_low, unsigned int time_high,
                        const unsigned char *target_info, int target_len,
                        unsigned char *session_key,
                        unsigned char *out, int out_max)
{
    unsigned char key[NTLM_HASH_LEN];
    unsigned char lm[NTLM_LMV2_LEN];
    unsigned char nt[NTLM_NTV2_MIN + 1024];
    unsigned char wdom[256], wuser[256], wwork[256];
    int ntlen, dlen, ulen, wlen, off;

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

    NTLMv2Key(user, domain, nt_hash, key);
    LMv2Response(key, (char *) challenge, client_challenge, lm);
    ntlen = NTLMv2Response(key, (char *) challenge, client_challenge,
                           time_low, time_high, target_info, target_len,
                           nt, sizeof(nt));
    if(ntlen == 0) return 0;

    dlen = widen(domain, 0, wdom, sizeof(wdom));
    ulen = widen(user, 0, wuser, sizeof(wuser));
    wlen = widen(workstation, 0, wwork, sizeof(wwork));

    if(out_max < TYPE3_FIXED + NTLM_LMV2_LEN + ntlen + dlen + ulen + wlen)
        return 0;

    memcpy(out, signature, 8);
    put32(out + 8, 3);                  /* NtLmAuthenticate */

    off = TYPE3_FIXED;
    put_field(out + 12, NTLM_LMV2_LEN, off);
    memcpy(out + off, lm, NTLM_LMV2_LEN);
    off += NTLM_LMV2_LEN;

    put_field(out + 20, ntlen, off);
    memcpy(out + off, nt, ntlen);
    off += ntlen;

    put_field(out + 28, dlen, off);
    memcpy(out + off, wdom, dlen);
    off += dlen;

    put_field(out + 36, ulen, off);
    memcpy(out + off, wuser, ulen);
    off += ulen;

    put_field(out + 44, wlen, off);
    memcpy(out + off, wwork, wlen);
    off += wlen;

    /* No key exchange: the session base key is used as it stands */
    put_field(out + 52, 0, off);
    put32(out + 60, CLIENT_FLAGS);

    /*
        The session base key, which everything later is derived from, is
        the keyed hash of the proof - the first sixteen bytes of the NTLMv2
        answer - under the same key that produced it.
    */
    if(session_key)
        hmac_md5(key, NTLM_HASH_LEN, nt, NTLM_HASH_LEN, session_key);

    memset(key, 0, sizeof(key));
    return off;
}
