/*
 * 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 2026, 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 */

    /*
        The name first, and the one byte of answer after it.  The name is
        UTF-16 and belongs on an even offset; with the byte in front of it,
        it started on an odd one, and Windows answered the logon with an
        invalid parameter where Samba let it through.  Names before answers
        is also the order Windows sends them in itself.
    */
    off = TYPE3_FIXED;
    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 + 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 + 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;
}
