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

/*
 *   SMB2 negotiation, authentication and share connection.
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "LanMan98BaseLib/error.h"
#include "LanMan98BaseLib/memory.h"
#include "date.h"
#include "md4.h"
#include "md5.h"
#include "ntlm.h"
#include "ntlmssp.h"
#include "aes.h"
#include "sha512.h"
#include "sha256.h"
#include "smberr.h"
#include "vc.h"

#include "alphabet.h"
#include "smb2.h"

#define SMB2_HDRSIZE (64)

#define SMB2_NEGOTIATE       (0x0000)
#define SMB2_SESSION_SETUP   (0x0001)
#define SMB2_LOGOFF          (0x0002)
#define SMB2_TREE_CONNECT    (0x0003)
#define SMB2_TREE_DISCONNECT (0x0004)
#define SMB2_CREATE          (0x0005)
#define SMB2_CLOSE           (0x0006)
#define SMB2_READ            (0x0008)
#define SMB2_WRITE           (0x0009)
#define SMB2_QUERY_DIRECTORY (0x000E)
#define SMB2_ECHO            (0x000D)
#define SMB2_QUERY_INFO      (0x0010)
#define SMB2_SET_INFO        (0x0011)
#define SMB2_IOCTL           (0x000B)

/* CreateDisposition, the rest of them */
#define FILE_CREATE          (2)

/* Information classes for SET_INFO */
#define FileBasicInformation       (4)
#define FileRenameInformation      (10)
#define FileDispositionInformation (13)
#define FileEndOfFileInformation   (20)

#define DELETE_ACCESS        (0x00010000u)

#define STATUS_NO_MORE_FILES (0x80000006u)

/* CreateDisposition */
#define FILE_OPEN            (1)
#define FILE_OPEN_IF         (3)

/* SessionFlags in a session setup reply */
#define SESSION_IS_GUEST     (0x0001)
#define SESSION_IS_NULL      (0x0002)
#define FILE_OVERWRITE_IF    (5)

/* CreateOptions */
#define FILE_DIRECTORY_FILE     (0x00000001u)
#define FILE_NON_DIRECTORY_FILE (0x00000040u)

/* DesiredAccess.  FILE_LIST_DIRECTORY and FILE_READ_DATA are the same bit;
   which one it means depends on what is being opened. */
#define FILE_READ_DATA       (0x00000001u)
#define FILE_WRITE_DATA      (0x00000002u)
#define FILE_APPEND_DATA     (0x00000004u)
#define FILE_READ_ATTRS      (0x00000080u)
#define FILE_WRITE_ATTRS     (0x00000100u)
#define SYNCHRONIZE          (0x00100000u)

#define FILE_SHARE_ALL       (0x00000007u)

#define STATUS_SUCCESS               (0x00000000u)
#define STATUS_PENDING               (0x00000103u)
#define STATUS_MORE_PROCESSING       (0xC0000016u)
#define STATUS_BUFFER_OVERFLOW       (0x80000005u)

/* Talk to a named pipe: one call carrying the request and the answer */
#define FSCTL_PIPE_TRANSCEIVE        (0x0011C017u)

/*
    The most one read or write will carry.  SMB1 grew the message buffer to
    what the negotiation agreed; SMB2 states much larger figures than are
    worth allocating for, so this caps them and the buffer is sized to it.
*/
#define SMB2_MAX_XFER (60 * 1024)

#define SIGNING_ENABLED  (0x0001)
#define SIGNING_REQUIRED (0x0002)

/* Header flags.  Only the two that matter here. */
#define SMB2_FLAGS_SIGNED    (0x00000008u)
#define SMB2_FLAGS_ASYNC     (0x00000002u)

/* Where the signature lives, and how much of it there is */
#define SMB2_SIG_OFFSET (48)
#define SMB2_SIG_LEN    (16)

/*
    Encryption, which SMB 3.0 and 3.0.2 do with AES-128-CCM.

    An encrypted message travels inside a transform header: the same four
    byte introducer trick as a plain message, but 0xFD rather than 0xFE,
    then the tag, the nonce, how long the message inside is, and which
    session it belongs to.  Everything from the nonce to the end of that
    header is authenticated but not hidden, because the far end needs it
    to decrypt; the message itself is hidden.
*/
#define SMB2_TRANSFORM_HDRSIZE (52)
#define SMB2_TRANSFORM_AAD_OFF (20)    /* nonce onwards is authenticated */
#define SMB2_TRANSFORM_AAD_LEN (32)
#define SMB2_CCM_NONCE_LEN     (11)
#define SMB2_ENCRYPTED         (0x0001)

#define SMB2_GLOBAL_CAP_ENCRYPTION  (0x00000040u)

/*
    SMB 3.1.1 negotiate contexts.

    3.1.1 stops declaring what it can do in a capabilities word and starts
    listing it: the negotiate carries a list of contexts, each an id, a
    length and its own payload.  Two matter here - which hash protects the
    negotiation, and which cipher encrypts the session.
*/
#define SMB2_PREAUTH_INTEGRITY_CAPABILITIES (0x0001)
#define SMB2_ENCRYPTION_CAPABILITIES        (0x0002)
#define SMB2_HASH_SHA512                    (0x0001)
#define SMB2_CIPHER_AES128_CCM              (0x0001)
#define SMB2_PREAUTH_SALT_LEN               (32)
#define SMB2_SESSION_FLAG_ENCRYPT   (0x0004)
#define SMB2_SHAREFLAG_ENCRYPT_DATA (0x00008000u)

struct smb2_conn_s
{
    vc_t vc;
    unsigned int mid_low, mid_high;
    unsigned int sess_low, sess_high;
    unsigned int tree_id;
    int dialect;
    int credits;
    unsigned int max_read, max_write, max_transact;
    int signing_required;       /* the server will not accept unsigned */
    int signing;                /* signing is active on this connection */
    int verify;                 /* and replies are required to carry one */
    int want_signing;           /* asked for by LanMan98$Sign */
    unsigned char session_key[NTLM_HASH_LEN];
    unsigned char signing_key[AES_KEYLEN];  /* what actually signs */
    unsigned char enc_key[AES_KEYLEN];      /* what this end encrypts with */
    unsigned char dec_key[AES_KEYLEN];      /* and decrypts replies with */
    int can_encrypt;            /* both ends know how */
    int encrypting;             /* and are doing it, from here on */
    int want_encrypt;           /* asked for by LanMan98$Encrypt */
    unsigned int nonce_lo, nonce_hi;        /* never repeated under one key */
    /*
        3.1.1's pre-authentication integrity hash: a running SHA-512 over
        every negotiate and session setup message, which then goes into the
        key derivation, so a listener cannot alter what was agreed without
        both ends deriving different keys and the session failing.
    */
    int preauth_on;                         /* messages are being hashed */
    unsigned char preauth[SHA512_RESULTLEN];
    int allow_311;                          /* 3.1.1 may be offered */
};

/*
    Whether the last negotiate offered 3.1.1 and was refused.

    A server that predates 3.1.1 reads the eight bytes that carry
    NegotiateContextOffset and NegotiateContextCount as ClientStartTime,
    which it requires to be zero, and answers STATUS_INVALID_PARAMETER.
    Nothing in the reply says that is what happened - it is an error and no
    more - so the only way to tell it from a server that cannot do SMB2 at
    all is that it answered in SMB2 to say so.

    The caller uses this to decide whether asking again without 3.1.1 is
    worth a second circuit.  See try_smb2 in c.smb.
*/
static int refused_311 = 0;

int SMB2Refused311(void)
{
    return refused_311;
}

/*
    What the last negotiate was refused with, or zero if it never got an
    answer that parsed.  Only for saying so: "did not answer SMB2" and
    "answered SMB2 and said no" are a very different thing to be told when
    a mount has just failed, and the second one has a number attached.
*/
static unsigned int last_status = 0;

unsigned int SMB2LastStatus(void)
{
    return last_status;
}

/* --- little endian accessors.  Everything on the wire is unaligned as far
       as the ARM is concerned, so all of it goes a byte at a time. --- */

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);
}

/*
    Widen a RISC OS name to the UTF-16LE that SMB2 uses for every name.

    Not a byte-for-byte widening: &80-&9F are RISC OS characters in their
    own right, not the Latin-1 control codes their values would suggest,
    and a name carrying one has to be converted or the server is given a
    control code where a Euro sign or a quotation mark was meant.  Every
    character in the alphabet is in the basic plane, so each one is still
    exactly one UTF-16 unit and the room needed is unchanged.
*/
static int widen(char *s, unsigned char *out, int out_max)
{
    unsigned int u;
    int n, used;

    n = 0;
    while(*s && n + 2 <= out_max)
    {
        /*
            An escape - a marker and four hexadecimal digits - is one
            character the alphabet has no room for, and goes back on the
            wire as the unit it names.  That is what lets a name with such
            a character in it be opened and saved rather than only looked
            at.  Five characters in, two bytes out, so it only ever makes
            the result shorter.
        */
        used = AlphabetUnescape(s, &u);
        if(used)
            s += used;
        else
            u = AlphabetToUnicode((unsigned char) *s++);
        out[n++] = (unsigned char) (u & 0xFF);
        out[n++] = (unsigned char) (u >> 8);
    }
    return n;
}

/*
    An encrypted message has to be sent with the transform header in front
    of it, so it is built that far into the buffer from the start.  Every
    other piece of code goes through msg() and so neither knows nor cares.
*/
static int hdr_reserve(smb2_conn_t c)
{
    return c->encrypting ? SMB2_TRANSFORM_HDRSIZE : 0;
}

static unsigned char *base(smb2_conn_t c)
{
    return (unsigned char *) VCBuffer(c->vc).buf;
}

static unsigned char *msg(smb2_conn_t c)
{
    return base(c) + hdr_reserve(c);
}

static int msg_size(smb2_conn_t c)
{
    return VCBuffer(c->vc).size - hdr_reserve(c);
}

/*
    The most payload a reply may carry and still fit.

    The transfer sizes are settled once, when the connection is made, from
    the buffer as it was then.  Asking again here costs nothing and means a
    request can never be for more than there is room to receive, whatever
    happened to the buffer in between - which is the difference between a
    transfer that is merely slow and one that fails with a buffer error.
*/
static int room_for_payload(smb2_conn_t c)
{
    int n;

    n = msg_size(c) - SMB2_HDRSIZE - 128;
    return (n > 0) ? n : 0;
}

/*
    Build the fixed header.  The message identifier has to increase across
    the connection and never repeat, and credits have to be asked for or
    the server stops answering after the first few requests.
*/
static void smb2_header(smb2_conn_t c, int cmd)
{
    unsigned char *p;

    p = msg(c);
    memset(p, 0, SMB2_HDRSIZE);
    p[0] = 0xFE; p[1] = 'S'; p[2] = 'M'; p[3] = 'B';
    put16(p + 4, SMB2_HDRSIZE);
    put16(p + 6, 1);                    /* CreditCharge */
    put16(p + 12, cmd);
    put16(p + 14, 64);                  /* ask for more credits */
    put32(p + 24, c->mid_low);
    put32(p + 28, c->mid_high);
    put32(p + 36, c->tree_id);
    put32(p + 40, c->sess_low);
    put32(p + 44, c->sess_high);
}

static void bump_mid(smb2_conn_t c)
{
    unsigned int lo;

    lo = c->mid_low + 1;
    if(lo < c->mid_low)
        c->mid_high++;
    c->mid_low = lo;
}

/*
    Send one request and collect its reply, returning the status.  A server
    that needs longer answers STATUS_PENDING first and the real reply
    follows on the same identifier, so that interim answer is stepped over
    rather than reported.
*/
/*
    Derive a key from the session key, the way SMB 3.x asks for.

    This is the counter mode construction of SP800-108 with HMAC-SHA256 as
    the function, one iteration, producing 128 bits.  The label and the
    context both carry their own terminating zero AND are separated by the
    zero byte the construction puts between them, so there are two zeros
    after the label - which is not obvious from reading the specification
    and was settled here by reproducing a real server's signature.
*/
static void derive_key_ctx(const unsigned char session_key[NTLM_HASH_LEN],
                           const char *label,
                           const unsigned char *context, int context_len,
                           unsigned char out[AES_KEYLEN])
{
    unsigned char buf[128];
    unsigned char digest[SHA256_RESULTLEN];
    int n, i;

    n = 0;
    buf[n++] = 0; buf[n++] = 0; buf[n++] = 0; buf[n++] = 1;   /* counter */
    for(i = 0; label[i]; i++) buf[n++] = (unsigned char) label[i];
    buf[n++] = 0;                       /* the label's own terminator */
    buf[n++] = 0;                       /* and the separator between them */
    for(i = 0; i < context_len; i++) buf[n++] = context[i];
    buf[n++] = 0; buf[n++] = 0; buf[n++] = 0; buf[n++] = 0x80;  /* 128 bits */

    hmac_sha256(session_key, NTLM_HASH_LEN, buf, (size_t) n, digest);
    memcpy(out, digest, AES_KEYLEN);
    memset(digest, 0, sizeof(digest));
    memset(buf, 0, sizeof(buf));
}

/*
    Up to 3.0.2 the context is a string and carries its own terminator;
    3.1.1 replaces it with the pre-authentication hash, which is 64 bytes of
    anything and has no terminator to carry.
*/
static void derive_key(const unsigned char session_key[NTLM_HASH_LEN],
                       const char *label, const char *context,
                       unsigned char out[AES_KEYLEN])
{
    derive_key_ctx(session_key, label, (const unsigned char *) context,
                   (int) strlen(context) + 1, out);
}

/*
    Fold one message into the pre-authentication hash.

    The value starts as sixty-four zeros and each message replaces it with
    the hash of the value and the message together, so the result depends
    on every message and on their order.  Both ends do this independently
    and both feed the result into the key derivation, so any alteration on
    the way shows up as keys that do not match rather than as anything the
    attacker chose.
*/
static void preauth_update(smb2_conn_t c, const unsigned char *p, int len)
{
    struct sha512_context ctx;

    if(!c->preauth_on)
        return;
    sha512_init(&ctx);
    sha512_update(&ctx, c->preauth, sizeof(c->preauth));
    sha512_update(&ctx, p, (size_t) len);
    sha512_final(&ctx, c->preauth);
}

/*
    Settle which key signs, and with what.

    SMB 2.x signs with HMAC-SHA256 under the session key as it stands.
    SMB 3.x signs with AES-CMAC under a key derived from it, so that the
    session key itself is never used directly.
*/
static void set_signing_key(smb2_conn_t c)
{
    if(c->dialect >= SMB2_DIALECT_0311)
        derive_key_ctx(c->session_key, "SMBSigningKey",
                       c->preauth, (int) sizeof(c->preauth), c->signing_key);
    else if(c->dialect >= SMB2_DIALECT_0300)
        derive_key(c->session_key, "SMB2AESCMAC", "SmbSign", c->signing_key);
    else
        memcpy(c->signing_key, c->session_key, AES_KEYLEN);
}

/*
    The keys that encrypt.  Two of them, one per direction, so that a
    message this end sent cannot be replayed back at it as though the
    server had said it.  The names are the server's point of view:
    "ServerIn " is what travels towards the server, and the trailing space
    is part of it.
*/
static void set_encryption_keys(smb2_conn_t c)
{
    if(c->dialect >= SMB2_DIALECT_0311)
    {
        /*
            3.1.1 names the keys by direction rather than by the server's
            point of view, and takes the context from the pre-authentication
            hash instead of a fixed string.
        */
        derive_key_ctx(c->session_key, "SMBC2SCipherKey",
                       c->preauth, (int) sizeof(c->preauth), c->enc_key);
        derive_key_ctx(c->session_key, "SMBS2CCipherKey",
                       c->preauth, (int) sizeof(c->preauth), c->dec_key);
    }
    else if(c->dialect >= SMB2_DIALECT_0300)
    {
        derive_key(c->session_key, "SMB2AESCCM", "ServerIn ", c->enc_key);
        derive_key(c->session_key, "SMB2AESCCM", "ServerOut", c->dec_key);
    }
}

/*
    A nonce that has not been used before under this key.  Counter mode
    fails catastrophically if one is repeated - two messages masked with
    the same keystream reveal each other - so this counts rather than
    taking anything from a clock or the message.
*/
static void next_nonce(smb2_conn_t c, unsigned char n[SMB2_CCM_NONCE_LEN])
{
    memset(n, 0, SMB2_CCM_NONCE_LEN);
    c->nonce_lo++;
    if(c->nonce_lo == 0)
        c->nonce_hi++;
    put32(n, c->nonce_lo);
    put32(n + 4, c->nonce_hi);
}

/* Wrap a built message in its transform header and encrypt it in place.
   Returns how many bytes there now are to send. */
static int encrypt_message(smb2_conn_t c, int len)
{
    unsigned char *t;
    unsigned char tag[SMB2_SIG_LEN];

    t = base(c);
    t[0] = 0xFD; t[1] = 'S'; t[2] = 'M'; t[3] = 'B';
    memset(t + 4, 0, SMB2_SIG_LEN);                 /* filled in below */
    next_nonce(c, t + SMB2_TRANSFORM_AAD_OFF);
    memset(t + SMB2_TRANSFORM_AAD_OFF + SMB2_CCM_NONCE_LEN, 0,
           16 - SMB2_CCM_NONCE_LEN);                /* the unused nonce tail */
    put32(t + 36, (unsigned int) len);
    put16(t + 40, 0);
    put16(t + 42, SMB2_ENCRYPTED);
    put32(t + 44, c->sess_low);
    put32(t + 48, c->sess_high);

    aes_ccm_encrypt(c->enc_key,
                    t + SMB2_TRANSFORM_AAD_OFF, SMB2_CCM_NONCE_LEN,
                    t + SMB2_TRANSFORM_AAD_OFF, SMB2_TRANSFORM_AAD_LEN,
                    t + SMB2_TRANSFORM_HDRSIZE, (size_t) len, tag);
    memcpy(t + 4, tag, SMB2_SIG_LEN);
    memset(tag, 0, sizeof(tag));
    return SMB2_TRANSFORM_HDRSIZE + len;
}

/*
    Unwrap a reply.  Returns the length of the message inside.

    A reply that is not wrapped, once this connection is encrypting, is
    refused rather than read: believing it would let anything in the middle
    turn encryption off by sending in clear, which is the same trap the
    signing check above avoids.
*/
static int decrypt_message(smb2_conn_t c, int len)
{
    unsigned char *t;
    unsigned int inside;

    t = base(c);
    if(len < SMB2_TRANSFORM_HDRSIZE ||
       t[0] != 0xFD || t[1] != 'S' || t[2] != 'M' || t[3] != 'B')
    {
        VCMarkDead(c->vc);
        Error("Server sent an unencrypted reply on an encrypted connection");
    }
    inside = get32(t + 36);
    if((int) inside != len - SMB2_TRANSFORM_HDRSIZE)
    {
        VCMarkDead(c->vc);
        Error("Encrypted reply says it holds %u bytes but carries %d",
              inside, len - SMB2_TRANSFORM_HDRSIZE);
    }
    if(!aes_ccm_decrypt(c->dec_key,
                        t + SMB2_TRANSFORM_AAD_OFF, SMB2_CCM_NONCE_LEN,
                        t + SMB2_TRANSFORM_AAD_OFF, SMB2_TRANSFORM_AAD_LEN,
                        t + SMB2_TRANSFORM_HDRSIZE, (size_t) inside,
                        t + 4))
    {
        VCMarkDead(c->vc);
        Error("Encrypted reply failed its authentication check");
    }
    return (int) inside;
}

/* The signature over a whole message, in whichever form the dialect wants */
static void signature_of(smb2_conn_t c, const unsigned char *p, int len,
                         unsigned char out[SMB2_SIG_LEN])
{
    if(c->dialect >= SMB2_DIALECT_0300)
    {
        aes_cmac(c->signing_key, p, (size_t) len, out);
    }
    else
    {
        unsigned char digest[SHA256_RESULTLEN];

        hmac_sha256(c->signing_key, AES_KEYLEN, p, (size_t) len, digest);
        memcpy(out, digest, SMB2_SIG_LEN);
        memset(digest, 0, sizeof(digest));
    }
}

/*
    Sign a request in place.

    The signature is HMAC-SHA256 of the whole message under the session
    key, with the signature field itself zero while it is computed, and
    the flag set beforehand: the flag is part of what is signed.  Only
    the first sixteen bytes of the thirty-two the hash produces are kept,
    which is what the field holds.
*/
static void sign_message(smb2_conn_t c, int len)
{
    unsigned char *p;
    unsigned char digest[SMB2_SIG_LEN];

    p = msg(c);
    put32(p + 16, get32(p + 16) | SMB2_FLAGS_SIGNED);
    memset(p + SMB2_SIG_OFFSET, 0, SMB2_SIG_LEN);
    signature_of(c, p, len, digest);
    memcpy(p + SMB2_SIG_OFFSET, digest, SMB2_SIG_LEN);
    memset(digest, 0, sizeof(digest));
}

/*
    Check the signature on a reply.

    The server's signature covers the message as it stands with the field
    zeroed, so it is lifted out, the field cleared, the hash taken and the
    field put back - the caller still wants to read the message.

    An interim reply, the STATUS_PENDING one that says "still working", is
    not signed and carries no flag.  Anything else that arrives unsigned
    while signing is in force is refused rather than believed: accepting it
    would let anything in the middle turn signing off by clearing one bit.
*/
static void check_signature(smb2_conn_t c, int len, unsigned int status)
{
    unsigned char *p;
    unsigned char digest[SMB2_SIG_LEN];
    unsigned char got[SMB2_SIG_LEN];
    unsigned int flags;
    int bad;

    p = msg(c);
    flags = get32(p + 16);

    if(!(flags & SMB2_FLAGS_SIGNED))
    {
        if((status == STATUS_PENDING) && (flags & SMB2_FLAGS_ASYNC))
            return;
        VCMarkDead(c->vc);
        Error("Server sent an unsigned reply on a signed connection");
    }
    if(len < SMB2_HDRSIZE)
        Error("Signed reply is too short to hold a signature");

    memcpy(got, p + SMB2_SIG_OFFSET, SMB2_SIG_LEN);
    memset(p + SMB2_SIG_OFFSET, 0, SMB2_SIG_LEN);
    signature_of(c, p, len, digest);
    memcpy(p + SMB2_SIG_OFFSET, got, SMB2_SIG_LEN);

    bad = memcmp(digest, got, SMB2_SIG_LEN);
    memset(digest, 0, sizeof(digest));
    if(bad != 0)
    {
        VCMarkDead(c->vc);
        Error("Reply failed its signature check");
    }
}

static unsigned int smb2_call(smb2_conn_t c, int cmd, int body_len,
                              int *reply_len)
{
    unsigned char *p;
    unsigned int status;
    int len, guard;

    /*
        An encrypted message is not signed as well: the tag over the whole
        of it already proves it was not altered, and a server does not ask
        for both.
    */
    preauth_update(c, msg(c), SMB2_HDRSIZE + body_len);
    if(c->encrypting)
    {
        VCSend(c->vc, encrypt_message(c, SMB2_HDRSIZE + body_len));
    }
    else
    {
        if(c->signing)
            sign_message(c, SMB2_HDRSIZE + body_len);
        VCSend(c->vc, SMB2_HDRSIZE + body_len);
    }
    bump_mid(c);

    for(guard = 0; guard < 64; guard++)
    {
        VCReceive(c->vc);
        len = VCLength(c->vc);
        if(c->encrypting)
            len = decrypt_message(c, len);
        p = msg(c);
        if(len < SMB2_HDRSIZE)
            Error("Short SMB2 reply, %d bytes", len);
        if((p[0] != 0xFE) || (p[1] != 'S') || (p[2] != 'M') || (p[3] != 'B'))
            Error("Reply was not SMB2");
        if((int) get16(p + 12) != cmd)
            Error("SMB2 reply was for command 0x%04X, not 0x%04X",
                  get16(p + 12), cmd);
        c->credits += (int) get16(p + 14);
        status = get32(p + 8);
        if(c->signing && c->verify && !c->encrypting)
            check_signature(c, len, status);
        if(status != STATUS_PENDING)
        {
            /*
                The reply that completes the logon is the one message left
                out of the hash: the keys are settled from the value as it
                stood when the last request went, and the server derives
                them at the same point.  Everything else goes in.
            */
            if(!((cmd == SMB2_SESSION_SETUP) && (status == STATUS_SUCCESS)))
                preauth_update(c, p, len);
            if(reply_len) *reply_len = len - SMB2_HDRSIZE;
            return status;
        }
    }
    Error("Server kept the request pending");
    return 0;
}

/* Body of the reply, having checked its declared size is what is expected */
static unsigned char *reply_body(smb2_conn_t c, int expect, int len)
{
    unsigned char *b;

    if(len < 2)
        Error("SMB2 reply has no body");
    b = msg(c) + SMB2_HDRSIZE;
    /* The low bit of StructureSize is a flag on some replies, not size */
    if((int) (get16(b) & ~1) != (expect & ~1))
        Error("SMB2 reply body is %d bytes, expected %d", get16(b), expect);
    return b;
}

static void smb2_negotiate(smb2_conn_t c)
{
    unsigned char *b;
    unsigned int status;
    int len, off, n;
    int offering_311 = 0;

    refused_311 = 0;
    last_status = 0;
    smb2_header(c, SMB2_NEGOTIATE);
    b = msg(c) + SMB2_HDRSIZE;
    memset(b, 0, 36);
    put16(b, 36);                       /* StructureSize */
    put16(b + 4, (unsigned int) (SIGNING_ENABLED |
                                (c->want_signing ? SIGNING_REQUIRED : 0)));
    /*
        Capabilities.  Encryption is the only one this client has any use
        for, and it is only meaningful if an SMB3 dialect is on offer at
        all - which LanMan98$SMB2 suppresses.
    */
    put32(b + 8, (unsigned int)
                 ((getenv("LanMan98$SMB2") == NULL)
                      ? SMB2_GLOBAL_CAP_ENCRYPTION : 0));
    /* ClientGuid: distinct per connection, and nothing depends on its
       being unpredictable, so the message identifier and the clock do */
    put32(b + 12, 0x4C614D61);          /* "LaMa" */
    put32(b + 16, 0x6E393800);          /* "n98\0" */
    put32(b + 20, (unsigned int) time(NULL));
    put32(b + 24, (unsigned int) (long) c);
    /*
        Which dialects to offer.

        Normally all of them, and the server picks the best both ends know.
        LanMan98$SMB2 and LanMan98$SMB3 offer one family only, so that a
        level can be tried on its own: a fault that appears at one and not
        another is a great deal easier to place when the level can be
        chosen rather than inferred.
    */
    n = 36;
    if(getenv("LanMan98$SMB3") != NULL)
    {
        put16(b + n, SMB2_DIALECT_0300); n += 2;
        put16(b + n, SMB2_DIALECT_0302); n += 2;
        put16(b + 2, 2);
    }
    else if(getenv("LanMan98$SMB2") != NULL)
    {
        put16(b + n, SMB2_DIALECT_0202); n += 2;
        put16(b + n, SMB2_DIALECT_0210); n += 2;
        put16(b + 2, 2);
    }
    else
    {
        put16(b + n, SMB2_DIALECT_0202); n += 2;
        put16(b + n, SMB2_DIALECT_0210); n += 2;
        put16(b + n, SMB2_DIALECT_0300); n += 2;
        put16(b + n, SMB2_DIALECT_0302); n += 2;
        if(c->allow_311)
        {
            put16(b + n, SMB2_DIALECT_0311); n += 2;
            offering_311 = 1;
        }
        put16(b + 2, (unsigned int) (offering_311 ? 5 : 4));
    }
    /* Round the body up to a multiple of eight.  The contexts below have
       to start on an eight byte boundary, and where there are none every
       other client still sends it aligned. */
    while(n & 7)
        b[n++] = 0;

    /*
        The negotiate contexts, which only exist from 3.1.1.

        The hash one is not optional: a server that settles on 3.1.1 will
        refuse a negotiate that did not say how the negotiation itself is
        to be protected.  The cipher one says AES-128-CCM, which is what
        this client implements; without it a server may pick GCM and
        nothing afterwards can be read.
    */
    if(offering_311)
    {
        int ctx = n, i;

        put16(b + 28 + 0, 0);                   /* NegotiateContextOffset, low */
        put16(b + 28 + 2, 0);                   /* .. high, filled in below */

        /* SMB2_PREAUTH_INTEGRITY_CAPABILITIES */
        put16(b + n, SMB2_PREAUTH_INTEGRITY_CAPABILITIES);
        /*
            Count, salt length, the one algorithm and then the salt: the
            algorithm list is part of the data and has to be counted in its
            length, which is the sort of thing a server answers with
            STATUS_INVALID_PARAMETER rather than an explanation.
        */
        put16(b + n + 2, 2 + 2 + 2 + SMB2_PREAUTH_SALT_LEN);
        put32(b + n + 4, 0);
        put16(b + n + 8, 1);                    /* one hash offered */
        put16(b + n + 10, SMB2_PREAUTH_SALT_LEN);
        put16(b + n + 12, SMB2_HASH_SHA512);
        /*
            The salt only has to differ between connections; nothing is
            derived from it, and it is sent in clear.  The message
            identifier and the clock are what this has to hand.
        */
        for(i = 0; i < SMB2_PREAUTH_SALT_LEN; i += 4)
            put32(b + n + 14 + i,
                  (unsigned int) (time(NULL) + i * 2654435761u
                                  + (unsigned int) (long) c));
        n += 8 + 2 + 2 + 2 + SMB2_PREAUTH_SALT_LEN;
        while(n & 7)
            b[n++] = 0;

        /* SMB2_ENCRYPTION_CAPABILITIES */
        put16(b + n, SMB2_ENCRYPTION_CAPABILITIES);
        put16(b + n + 2, 4);
        put32(b + n + 4, 0);
        put16(b + n + 8, 1);                    /* one cipher offered */
        put16(b + n + 10, SMB2_CIPHER_AES128_CCM);
        n += 8 + 4;
        while(n & 7)
            b[n++] = 0;

        put32(b + 28, (unsigned int) (SMB2_HDRSIZE + ctx));  /* context offset */
        put16(b + 32, 2);                                    /* context count */
        put16(b + 34, 0);

        /*
            From here the negotiation protects itself: this request is the
            first thing folded into the hash, and everything up to the last
            leg of the logon follows it.
        */
        memset(c->preauth, 0, sizeof(c->preauth));
        c->preauth_on = 1;
    }

    status = smb2_call(c, SMB2_NEGOTIATE, n, &len);
    if(status != STATUS_SUCCESS)
    {
        /*
            It answered, in SMB2, to refuse this request - so it speaks the
            protocol and objects to what was asked, which is not the same
            as not speaking it.  If 3.1.1 was in the list, that is far and
            away the likeliest thing objected to.
        */
        last_status = status;
        if(offering_311)
        {
            refused_311 = 1;
            VCLog("     3.1.1 refused (status &%08X); worth asking again "
                  "without it", status);
        }
        SMBErrorStatus(status);
    }

    /*
        Negotiate response, counted from the start of the body:
        2 SecurityMode, 4 DialectRevision, 8 ServerGuid, 24 Capabilities,
        28 MaxTransactSize, 32 MaxReadSize, 36 MaxWriteSize.
    */
    b = reply_body(c, 65, len);
    c->dialect = (int) get16(b + 4);
    c->signing_required = (get16(b + 2) & SIGNING_REQUIRED) ? 1 : 0;
    c->max_transact = get32(b + 28);
    c->max_read = get32(b + 32);
    c->max_write = get32(b + 36);
    /*
        3.1.1 stops advertising encryption in the capabilities word - the
        cipher context is the statement - so a server that settled there is
        taken to encrypt because it accepted the context.
    */
    c->can_encrypt = (c->dialect >= SMB2_DIALECT_0311)
                     || ((c->dialect >= SMB2_DIALECT_0300)
                         && ((get32(b + 24) & SMB2_GLOBAL_CAP_ENCRYPTION) != 0));
    if(c->dialect < SMB2_DIALECT_0311)
        c->preauth_on = 0;      /* offered, not taken up */

    off = (int) get16(b + 56);
    (void) off;

    VCLog("     dialect &%04X, server signing %s, encryption %s", c->dialect,
          c->signing_required ? "required" : "optional",
          c->can_encrypt ? "offered" : "not offered");
    if((c->dialect != SMB2_DIALECT_0202) && (c->dialect != SMB2_DIALECT_0210) &&
       (c->dialect != SMB2_DIALECT_0300) && (c->dialect != SMB2_DIALECT_0302) &&
       (c->dialect != SMB2_DIALECT_0311))
        Error("Server chose SMB2 dialect 0x%04X, which is not one offered",
              c->dialect);
    /* Signing itself is decided once there is a key to sign with, at the
       end of the session setup; nothing is refused here any more. */
}

/*
    Size the buffer to the largest transfer that will be attempted, and then
    the transfers to the buffer that was actually got, so the two cannot
    disagree.  Without this the buffer stays at the couple of kilobytes VC()
    starts with and the first read of a whole block arrives too big.
*/
static void smb2_size_buffer(smb2_conn_t c)
{
    int n;

    if(c->max_read > SMB2_MAX_XFER)  c->max_read = SMB2_MAX_XFER;
    if(c->max_write > SMB2_MAX_XFER) c->max_write = SMB2_MAX_XFER;
    if(c->max_transact > SMB2_MAX_XFER) c->max_transact = SMB2_MAX_XFER;
    if(c->max_read == 0)  c->max_read = SMB2_MAX_XFER;
    if(c->max_write == 0) c->max_write = SMB2_MAX_XFER;
    if(c->max_transact == 0) c->max_transact = SMB2_MAX_XFER;

    VCLog("     server offers: read %u write %u transact %u",
          c->max_read, c->max_write, c->max_transact);
    n = (int) ((c->max_read > c->max_write) ? c->max_read : c->max_write);
    ExceptTry
    {
        VCResizeBuffer(c->vc, n + 1024);
    }
    ExceptCatch
    {
        /* Settle for what the heap will give */
    }
    n = msg_size(c) - SMB2_HDRSIZE - 128;
    if(n < 0) n = 0;
    if(c->max_read > (unsigned int) n)  c->max_read = (unsigned int) n;
    if(c->max_write > (unsigned int) n) c->max_write = (unsigned int) n;
    if(c->max_transact > (unsigned int) n) c->max_transact = (unsigned int) n;
    VCLog("     settled on:    read %u write %u transact %u, buffer %d",
          c->max_read, c->max_write, c->max_transact, msg_size(c));
    if((c->max_read == 0) || (c->max_write == 0) || (c->max_transact == 0))
        Error("No room for an SMB2 transfer buffer");
}

/*
    Two rounds of NTLMSSP inside the security blob.  The first is answered
    with STATUS_MORE_PROCESSING_REQUIRED and the challenge; the second
    either succeeds or fails outright.
*/
static void smb2_session_setup(smb2_conn_t c, char *user, char *domain,
                               char *passwd)
{
    unsigned char *b, *blob;
    unsigned char nt_hash[NTLM_HASH_LEN];
    unsigned char challenge[NTLM_CHAL_LEN];
    unsigned char nonce[NTLM_CHAL_LEN];
    const unsigned char *target;
    unsigned char *chal_copy;
    unsigned int status, ft_low, ft_high;
    int len, n, blob_len, target_len, off;
    int anonymous = 0;

    /* --- first leg --- */
    smb2_header(c, SMB2_SESSION_SETUP);
    b = msg(c) + SMB2_HDRSIZE;
    memset(b, 0, 24);
    put16(b, 25);
    b[2] = 0;                           /* Flags */
    b[3] = (unsigned char) (SIGNING_ENABLED |
                            (c->want_signing ? SIGNING_REQUIRED : 0));
    put32(b + 4, 0);                    /* Capabilities */
    put32(b + 8, 0);                    /* Channel */
    blob = b + 24;
    blob_len = NTLMSSPNegotiate(blob, msg_size(c) - SMB2_HDRSIZE - 24);
    if(blob_len == 0)
        Error("No room for the NTLMSSP negotiate message");
    put16(b + 12, SMB2_HDRSIZE + 24);   /* SecurityBufferOffset */
    put16(b + 14, blob_len);
    put32(b + 16, 0);                   /* PreviousSessionId */
    put32(b + 20, 0);

    status = smb2_call(c, SMB2_SESSION_SETUP, 24 + blob_len, &len);
    if(status != STATUS_MORE_PROCESSING)
    {
        if(status == STATUS_SUCCESS)
            Error("Server accepted the session without a challenge");
        SMBErrorStatus(status);
    }

    /* The session identifier is set by this reply and used from here on */
    c->sess_low = get32(msg(c) + 40);
    c->sess_high = get32(msg(c) + 44);

    b = reply_body(c, 9, len);
    off = (int) get16(b + 4);
    n = (int) get16(b + 6);
    if((off < SMB2_HDRSIZE) || (n <= 0) ||
       (off + n > SMB2_HDRSIZE + len))
        Error("SMB2 challenge is not where the reply says it is");

    /*
        The challenge has to survive being overwritten: the next request is
        built in the same buffer.  Both it and the target information are
        copied out first.
    */
    chal_copy = Malloc(n);
    memcpy(chal_copy, msg(c) + off, n);

    ExceptTry
    {
        if(!NTLMSSPParseChallenge(chal_copy, n, challenge, &target,
                                  &target_len))
            Error("Server did not answer with an NTLMSSP challenge");

        NTLMPasswordHash(passwd, nt_hash);
        {
            /* A client challenge that differs from one logon to the next.
               No better source than this is available here; the note in
               c.smb about the same thing applies. */
            unsigned char seed[16], digest[MD5_RESULTLEN];

            put32(seed, (unsigned int) time(NULL));
            put32(seed + 4, (unsigned int) clock());
            put32(seed + 8, c->mid_low);
            put32(seed + 12, (unsigned int) (long) &seed);
            md5_get_digest(seed, sizeof(seed), digest);
            memcpy(nonce, digest, NTLM_CHAL_LEN);
        }
        DateSecondsToFileTime((unsigned int) time(NULL), &ft_low, &ft_high);

        /* --- second leg --- */
        smb2_header(c, SMB2_SESSION_SETUP);
        b = msg(c) + SMB2_HDRSIZE;
        memset(b, 0, 24);
        put16(b, 25);
        b[3] = (unsigned char) (SIGNING_ENABLED |
                                (c->want_signing ? SIGNING_REQUIRED : 0));
        blob = b + 24;
        /*
            No name means no name: ask to be let in as nobody rather than
            offering an empty one.  A server refuses an empty name the way
            it refuses any name it does not know, but will often allow a
            connection that asks for nothing - which is how a share list is
            got from a machine one has no account on, and how guest sharing
            on a Mac is reached.
        */
        if((user == NULL) || (*user == 0))
        {
            anonymous = 1;
            blob_len = NTLMSSPAnonymous("RISCOS", blob,
                                        msg_size(c) - SMB2_HDRSIZE - 24);
        }
        else
            blob_len = NTLMSSPAuthenticate(user, domain, "RISCOS", nt_hash,
                                       challenge, nonce, ft_low, ft_high,
                                       target, target_len, c->session_key,
                                       blob,
                                       msg_size(c) - SMB2_HDRSIZE - 24);
        if(blob_len == 0)
            Error("No room for the NTLMSSP authenticate message");
        put16(b + 12, SMB2_HDRSIZE + 24);
        put16(b + 14, blob_len);

        status = smb2_call(c, SMB2_SESSION_SETUP, 24 + blob_len, &len);
    }
    ExceptCatch
    {
        Free(chal_copy);
        memset(nt_hash, 0, sizeof(nt_hash));
        ExceptRethrow();
    }
    Free(chal_copy);
    memset(nt_hash, 0, sizeof(nt_hash));

    if(status != STATUS_SUCCESS)
        SMBErrorStatus(status);

    /*
        Neither leg of the session setup is signed.  The key that would sign
        it is the thing the exchange is establishing, and a server handed a
        signed request for a session it has not created yet drops the
        connection rather than answering.

        The reply to the last leg is signed, though - it is the first thing
        the server signs - and checking it is what proves the server derived
        the same key.  Without that check a listener in the middle could
        have cleared the signing bits in the negotiate and neither end would
        notice.  From here on both directions are signed.
    */
    {
        unsigned char *rb = reply_body(c, 9, len);
        unsigned int sflags = get16(rb + 2);

        /*
            A session the server let in as a guest, or without a name at
            all, has no key behind it: the key that signs a session is
            derived from the password, and there was not one.  Such a
            session is never signed, by either end, whatever the server
            said it required in the negotiate - so signing it, or looking
            for a signature on the reply, fails a connection that the
            server was perfectly happy with.

            This is how a share list is got from a machine one has no
            account on, and how guest sharing on a Mac is reached: the Mac
            asks for signing and then hands out an unsigned guest session.
        */
        /*
            Whether this session can be signed at all is settled here.

            A connection that asked for nothing has no key behind it - the
            key that signs a session is derived from the password, and
            there was not one - so neither end can sign, whatever the
            server said it required in the negotiate.  Servers are supposed
            to say so in the session flags, and some do; Samba returns zero
            there for a session it let in anonymously, so the flags alone
            cannot be relied on.  What can be relied on is that this end
            knows it sent no name.
        */
        if(anonymous || (sflags & (SESSION_IS_GUEST | SESSION_IS_NULL)))
        {
            VCLog("     let in as %s (session flags &%04X); not signed",
                  anonymous ? "nobody"
                            : ((sflags & SESSION_IS_GUEST) ? "a guest"
                                                           : "nobody"),
                  sflags);
            c->signing = 0;
            c->verify = 0;
        }
        else if(c->signing_required || c->want_signing
                                    || (c->dialect >= SMB2_DIALECT_0311))
        {
            /*
                3.1.1 signs whatever the server said about signing in the
                negotiate.  The point of the pre-authentication hash is that
                the keys prove what was agreed, and that is only worth
                anything if the messages carrying it are signed; a server
                that settled on 3.1.1 and is then sent unsigned requests
                refuses the tree connect with ACCESS_DENIED and says no more
                about it.
            */
            set_signing_key(c);
            check_signature(c, SMB2_HDRSIZE + len, status);
            c->signing = 1;
            c->verify = 1;
        }

        /*
            Encryption, which the same session key feeds.

            A session with no key behind it cannot be encrypted any more
            than it can be signed, so the anonymous case above rules it out
            too.  Otherwise it is turned on when the server asks for it in
            the session flags, or when this end was told to insist.

            From the next message onwards everything is wrapped, including
            the tree connect - which is the point: the share name is one of
            the things worth not sending in clear.
        */
        if(!anonymous && !(sflags & (SESSION_IS_GUEST | SESSION_IS_NULL)))
        {
            int wanted = c->want_encrypt
                         || ((sflags & SMB2_SESSION_FLAG_ENCRYPT) != 0);

            if(wanted && !c->can_encrypt)
                Error("Encryption was asked for, but the server does not "
                      "offer it at SMB dialect &%04X", c->dialect);
            if(wanted)
            {
                set_encryption_keys(c);
                c->encrypting = 1;
                VCLog("     encrypting from here on (AES-128-CCM)");
            }
        }

        /*
            The keys are settled, so the hash has done its work.  Leaving it
            running would cost a SHA-512 over every message from here to the
            end of the connection for nothing.
        */
        c->preauth_on = 0;
    }
}

static void smb2_tree_connect(smb2_conn_t c, char *share)
{
    unsigned char *b;
    unsigned int status;
    int len, n;

    smb2_header(c, SMB2_TREE_CONNECT);
    b = msg(c) + SMB2_HDRSIZE;
    memset(b, 0, 8);
    put16(b, 9);
    put16(b + 2, 0);                    /* Flags */
    n = widen(share, b + 8, msg_size(c) - SMB2_HDRSIZE - 8);
    put16(b + 4, SMB2_HDRSIZE + 8);     /* PathOffset */
    put16(b + 6, n);                    /* PathLength */

    status = smb2_call(c, SMB2_TREE_CONNECT, 8 + n, &len);
    if(status != STATUS_SUCCESS)
        SMBErrorStatus(status);

    c->tree_id = get32(msg(c) + 36);

    /*
        A share can be configured to require encryption even where the
        session was not.  Turning it on here is too late for this reply,
        which arrived in clear, but it costs nothing and everything that
        touches the share's contents comes after it.
    */
    if(!c->encrypting &&
       (get32(reply_body(c, 16, len) + 4) & SMB2_SHAREFLAG_ENCRYPT_DATA))
    {
        if(!c->can_encrypt)
            Error("This share requires encryption, which the server did "
                  "not offer at SMB dialect &%04X", c->dialect);
        set_encryption_keys(c);
        c->encrypting = 1;
        VCLog("     share requires encryption; on from here (AES-128-CCM)");
    }
}

smb2_conn_t SMB2Connect(vc_t vc, char *share, char *user, char *domain,
                        char *passwd, int allow_311)
{
    smb2_conn_t c;
    volatile int answered;

    c = Malloc(sizeof(*c));
    memset(c, 0, sizeof(*c));
    c->vc = vc;
    c->credits = 1;
    c->want_signing = (getenv("LanMan98$Sign") != NULL);
    c->want_encrypt = (getenv("LanMan98$Encrypt") != NULL);
    c->allow_311 = allow_311;

    /*
        The negotiate is the only place a fallback is possible.  A server
        that does not speak SMB2 will not answer it in a way that parses,
        and the caller can still try SMB1 on a fresh circuit; after this
        point the connection is committed.
    */
    answered = 0;
    ExceptTry
    {
        smb2_negotiate(c);
        answered = 1;
    }
    ExceptCatch
    {
        if(!answered)
        {
            Free(c);
            return NULL;
        }
        Free(c);
        ExceptRethrow();
    }

    ExceptTry
    {
        smb2_size_buffer(c);
        smb2_session_setup(c, user, domain, passwd);
        smb2_tree_connect(c, share);
    }
    ExceptCatch
    {
        Free(c);
        ExceptRethrow();
    }
    return c;
}

void SMB2Disconnect(smb2_conn_t c)
{
    if(c == NULL) return;
    ExceptTry
    {
        if(c->tree_id)
        {
            smb2_header(c, SMB2_TREE_DISCONNECT);
            put16(msg(c) + SMB2_HDRSIZE, 4);
            put16(msg(c) + SMB2_HDRSIZE + 2, 0);
            smb2_call(c, SMB2_TREE_DISCONNECT, 4, NULL);
        }
    }
    ExceptCatch
    {
    }
    memset(c->session_key, 0, sizeof(c->session_key));
    memset(c->signing_key, 0, sizeof(c->signing_key));
    memset(c->enc_key, 0, sizeof(c->enc_key));
    memset(c->dec_key, 0, sizeof(c->dec_key));
    Free(c);
}



/* --- file operations --- */

/*
    CREATE with everything spelled out.  SMB2Open below is the common case;
    deleting, renaming and making a directory each need a different access
    or disposition, and all of them go through here.
*/
static int smb2_create(smb2_conn_t c, char *path, int directory,
                       unsigned int access, int disposition,
                       unsigned char fileid[SMB2_FILEID_LEN],
                       unsigned int *size_low, unsigned int *size_high,
                       unsigned int *attrs,
                       unsigned int *write_low, unsigned int *write_high)
{
    unsigned char *b;
    unsigned int status;
    int len, n;

    smb2_header(c, SMB2_CREATE);
    b = msg(c) + SMB2_HDRSIZE;
    memset(b, 0, 56);
    put16(b, 57);                       /* StructureSize, buffer included */
    put32(b + 4, 2);                    /* ImpersonationLevel: Impersonation */

    put32(b + 24, access);
    put32(b + 28, 0);                   /* FileAttributes */
    put32(b + 32, FILE_SHARE_ALL);
    put32(b + 36, disposition);
    /* directory < 0 means "whichever it is", which is what a stat wants:
       insisting either way fails on the other kind. */
    put32(b + 40, (directory < 0) ? 0
                    : (directory ? FILE_DIRECTORY_FILE
                                 : FILE_NON_DIRECTORY_FILE));

    n = widen(path, b + 56, msg_size(c) - SMB2_HDRSIZE - 56);
    put16(b + 44, SMB2_HDRSIZE + 56);   /* NameOffset */
    put16(b + 46, n);                   /* NameLength */
    put32(b + 48, 0);                   /* CreateContextsOffset */
    put32(b + 52, 0);

    /* A name of zero length still needs a byte of buffer to point at */
    status = smb2_call(c, SMB2_CREATE, 56 + (n ? n : 1), &len);
    if(status != STATUS_SUCCESS)
        SMBErrorStatus(status);

    b = reply_body(c, 89, len);
    memcpy(fileid, b + 64, SMB2_FILEID_LEN);
    if(size_low)   *size_low   = get32(b + 48);
    if(size_high)  *size_high  = get32(b + 52);
    if(attrs)      *attrs      = get32(b + 56);
    if(write_low)  *write_low  = get32(b + 24);
    if(write_high) *write_high = get32(b + 28);
    return 1;
}

int SMB2Open(smb2_conn_t c, char *path, int directory, int writable,
             unsigned char fileid[SMB2_FILEID_LEN],
             unsigned int *size_low, unsigned int *size_high,
             unsigned int *attrs,
             unsigned int *write_low, unsigned int *write_high)
{
    unsigned int access;

    access = FILE_READ_DATA | FILE_READ_ATTRS | SYNCHRONIZE;
    if(writable)
        access |= FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRS;
    /*
        FILE_OPEN, whether or not writing was asked for.  Wanting to write
        to a file is not wanting to destroy it first: this asked for
        FILE_OVERWRITE_IF when the caller wanted write access, which tells
        the server to truncate the file to nothing before handing it back.

        Anything that opens an archive to modify it - an archiver, a
        database, anything that rewrites part of a file - therefore lost
        the file by opening it.  Worse, the length was still cached from
        before, so on close the extent was set back to what it had been and
        the file came out its original size and full of zeros: not visibly
        missing, just quietly emptied.  A run of zeros is also a valid
        empty tar archive, which is why such a file came back typed Tar.

        The old protocol never did this - its open sets OpenFunction to 1,
        "open the existing file" - so this was true over SMB2 only.

        FILE_OPEN_IF rather than plain FILE_OPEN because the layer above
        cannot tell the two writing cases apart: FileSwitch asks for
        "create and open" and for "open for update" through the same entry
        point, and the library below reduces both to a single flag.  So an
        open that is meant to make a new file has to be able to, while an
        open of a file that is already there must leave it alone.  Opening
        without truncating and creating when there is nothing there is both
        of those at once.  Where a file genuinely is being replaced, the
        length is set afterwards, as it always was - that is what shortens
        a file, not the open.
    */
    return smb2_create(c, path, directory, access,
                       writable ? FILE_OPEN_IF : FILE_OPEN,
                       fileid, size_low, size_high, attrs,
                       write_low, write_high);
}

/* SET_INFO, with the buffer the class in question expects */
static void smb2_set_info(smb2_conn_t c, unsigned char fileid[SMB2_FILEID_LEN],
                          int class, const unsigned char *data, int len)
{
    unsigned char *b;
    unsigned int status;

    if(len > msg_size(c) - SMB2_HDRSIZE - 32)
        Error("Information of %d bytes will not fit the buffer", len);

    smb2_header(c, SMB2_SET_INFO);
    b = msg(c) + SMB2_HDRSIZE;
    memset(b, 0, 32);
    put16(b, 33);
    b[2] = 1;                           /* SMB2_0_INFO_FILE */
    b[3] = (unsigned char) class;
    put32(b + 4, len);                  /* BufferLength */
    put16(b + 8, SMB2_HDRSIZE + 32);    /* BufferOffset */
    memcpy(b + 16, fileid, SMB2_FILEID_LEN);
    if(len) memcpy(b + 32, data, len);

    status = smb2_call(c, SMB2_SET_INFO, 32 + (len ? len : 1), NULL);
    if(status != STATUS_SUCCESS)
        SMBErrorStatus(status);
}

int SMB2OpenPipe(smb2_conn_t c, char *name,
                 unsigned char fileid[SMB2_FILEID_LEN])
{
    /* Read and write both: a pipe is asked something and answers */
    return smb2_create(c, name, 0,
                       FILE_READ_DATA | FILE_WRITE_DATA | FILE_READ_ATTRS |
                       SYNCHRONIZE,
                       FILE_OPEN,
                       fileid, NULL, NULL, NULL, NULL, NULL);
}

int SMB2Transceive(smb2_conn_t c, unsigned char fileid[SMB2_FILEID_LEN],
                   const unsigned char *in, int in_len,
                   unsigned char *out, int out_max)
{
    unsigned char *b;
    unsigned int status;
    int len, off, cnt;

    if(in_len > (msg_size(c) - SMB2_HDRSIZE - 56))
        Error("Pipe request does not fit in the buffer");

    smb2_header(c, SMB2_IOCTL);
    b = msg(c) + SMB2_HDRSIZE;
    memset(b, 0, 56);
    put16(b, 57);
    put32(b + 4, FSCTL_PIPE_TRANSCEIVE);
    memcpy(b + 8, fileid, SMB2_FILEID_LEN);
    put32(b + 24, SMB2_HDRSIZE + 56);       /* InputOffset */
    put32(b + 28, (unsigned int) in_len);   /* InputCount */
    put32(b + 44, (unsigned int) out_max);  /* MaxOutputResponse */
    put32(b + 48, 1);                       /* this is an FSCTL */
    memcpy(b + 56, in, (size_t) in_len);

    status = smb2_call(c, SMB2_IOCTL, 56 + in_len, &len);
    /*
        A pipe with more to say than was asked for says so rather than
        failing.  What arrived is still good, and is all there was room
        for, so it is taken and the rest is not asked for again.
    */
    if((status != STATUS_SUCCESS) && (status != STATUS_BUFFER_OVERFLOW))
        SMBErrorStatus(status);

    b = reply_body(c, 49, len);
    off = (int) get32(b + 32);
    cnt = (int) get32(b + 36);
    if((off < SMB2_HDRSIZE) || (cnt < 0) ||
       (off + cnt > SMB2_HDRSIZE + len))
        Error("Pipe answer is not where the reply says it is");
    if(cnt > out_max)
        Error("Pipe answer does not fit: %d in %d", cnt, out_max);
    memcpy(out, msg(c) + off, (size_t) cnt);
    return cnt;
}

int SMB2OpenPrint(smb2_conn_t c, char *name,
                  unsigned char fileid[SMB2_FILEID_LEN])
{
    /*
        Write access only.  A print share has nothing to read back, and a
        server is within its rights to refuse an open that asks for more
        than the share can give.
    */
    return smb2_create(c, name, 0,
                       FILE_WRITE_DATA | SYNCHRONIZE,
                       FILE_OVERWRITE_IF,
                       fileid, NULL, NULL, NULL, NULL, NULL);
}

int SMB2MakeFile(smb2_conn_t c, char *path)
{
    unsigned char fileid[SMB2_FILEID_LEN];

    smb2_create(c, path, 0,
                FILE_READ_ATTRS | FILE_WRITE_DATA | FILE_WRITE_ATTRS |
                  SYNCHRONIZE,
                FILE_OVERWRITE_IF, fileid, NULL, NULL, NULL, NULL, NULL);
    SMB2Close(c, fileid);
    return 1;
}

int SMB2MakeDir(smb2_conn_t c, char *path)
{
    unsigned char fileid[SMB2_FILEID_LEN];

    smb2_create(c, path, 1,
                FILE_READ_ATTRS | FILE_WRITE_ATTRS | SYNCHRONIZE,
                FILE_CREATE, fileid, NULL, NULL, NULL, NULL, NULL);
    SMB2Close(c, fileid);
    return 1;
}

/*
    Deleting is an open with delete access, a note that the handle should
    take the file with it, and a close.  There is no delete request.
*/
int SMB2Delete(smb2_conn_t c, char *path, int directory)
{
    unsigned char fileid[SMB2_FILEID_LEN];
    unsigned char pending;

    smb2_create(c, path, directory, DELETE_ACCESS | SYNCHRONIZE,
                FILE_OPEN, fileid, NULL, NULL, NULL, NULL, NULL);
    pending = 1;
    ExceptTry
    {
        smb2_set_info(c, fileid, FileDispositionInformation, &pending, 1);
    }
    ExceptCatch
    {
        SMB2Close(c, fileid);
        ExceptRethrow();
    }
    SMB2Close(c, fileid);
    return 1;
}

int SMB2Rename(smb2_conn_t c, char *path, char *newpath, int directory)
{
    unsigned char fileid[SMB2_FILEID_LEN];
    unsigned char *info;
    int n, len;

    smb2_create(c, path, directory, DELETE_ACCESS | SYNCHRONIZE,
                FILE_OPEN, fileid, NULL, NULL, NULL, NULL, NULL);

    len = 20 + 2 * strlen(newpath);
    info = Malloc(len);
    memset(info, 0, 20);
    info[0] = 1;                        /* ReplaceIfExists */
    n = widen(newpath, info + 20, len - 20);
    put32(info + 16, n);                /* FileNameLength */

    ExceptTry
    {
        smb2_set_info(c, fileid, FileRenameInformation, info, 20 + n);
    }
    ExceptCatch
    {
        Free(info);
        SMB2Close(c, fileid);
        ExceptRethrow();
    }
    Free(info);
    SMB2Close(c, fileid);
    return 1;
}

int SMB2SetEnd(smb2_conn_t c, unsigned char fileid[SMB2_FILEID_LEN],
               unsigned int low, unsigned int high)
{
    unsigned char info[8];

    put32(info, low);
    put32(info + 4, high);
    smb2_set_info(c, fileid, FileEndOfFileInformation, info, 8);
    return 1;
}

/*
    Times and attributes.  A time of zero means "leave this one alone", so
    only the one being changed is filled in.
*/
int SMB2SetBasic(smb2_conn_t c, char *path, int directory,
                 unsigned int write_low, unsigned int write_high,
                 unsigned int attrs)
{
    unsigned char fileid[SMB2_FILEID_LEN];
    unsigned char info[40];

    smb2_create(c, path, directory,
                FILE_READ_ATTRS | FILE_WRITE_ATTRS | SYNCHRONIZE,
                FILE_OPEN, fileid, NULL, NULL, NULL, NULL, NULL);

    memset(info, 0, sizeof(info));
    put32(info + 16, write_low);        /* LastWriteTime */
    put32(info + 20, write_high);
    put32(info + 32, attrs);

    ExceptTry
    {
        smb2_set_info(c, fileid, FileBasicInformation, info, sizeof(info));
    }
    ExceptCatch
    {
        SMB2Close(c, fileid);
        ExceptRethrow();
    }
    SMB2Close(c, fileid);
    return 1;
}

void SMB2Close(smb2_conn_t c, unsigned char fileid[SMB2_FILEID_LEN])
{
    unsigned char *b;

    ExceptTry
    {
        smb2_header(c, SMB2_CLOSE);
        b = msg(c) + SMB2_HDRSIZE;
        memset(b, 0, 24);
        put16(b, 24);
        memcpy(b + 8, fileid, SMB2_FILEID_LEN);
        smb2_call(c, SMB2_CLOSE, 24, NULL);
    }
    ExceptCatch
    {
        /* A close that fails leaves nothing worth reporting: the handle is
           gone either way once the session is. */
    }
}

int SMB2QueryDir(smb2_conn_t c, unsigned char fileid[SMB2_FILEID_LEN],
                 char *pattern, int restart,
                 unsigned char *out, int out_max)
{
    unsigned char *b;
    unsigned int status;
    int len, n, off, outlen;

    smb2_header(c, SMB2_QUERY_DIRECTORY);
    b = msg(c) + SMB2_HDRSIZE;
    memset(b, 0, 32);
    put16(b, 33);
    b[2] = 3;                           /* FileBothDirectoryInformation */
    b[3] = (unsigned char) (restart ? 0x01 : 0x00);   /* SMB2_RESTART_SCANS */
    put32(b + 4, 0);                    /* FileIndex */
    memcpy(b + 8, fileid, SMB2_FILEID_LEN);
    n = widen(pattern, b + 32, msg_size(c) - SMB2_HDRSIZE - 32);
    put16(b + 24, SMB2_HDRSIZE + 32);   /* FileNameOffset */
    put16(b + 26, n);
    put32(b + 28, out_max);             /* OutputBufferLength */

    status = smb2_call(c, SMB2_QUERY_DIRECTORY, 32 + (n ? n : 1), &len);
    if(status == STATUS_NO_MORE_FILES)
        return 0;
    if(status != STATUS_SUCCESS)
        SMBErrorStatus(status);

    b = reply_body(c, 9, len);
    off = (int) get16(b + 2);
    outlen = (int) get32(b + 4);
    if((off < SMB2_HDRSIZE) || (outlen < 0) ||
       (off + outlen > SMB2_HDRSIZE + len))
        Error("Directory data is not where the reply says it is");
    if(outlen > out_max)
        Error("Directory data does not fit: %d in %d", outlen, out_max);
    memcpy(out, msg(c) + off, outlen);
    return outlen;
}

int SMB2Read(smb2_conn_t c, unsigned char fileid[SMB2_FILEID_LEN],
             unsigned int off_low, unsigned int off_high,
             unsigned char *buf, int len)
{
    unsigned char *b;
    unsigned int status;
    int rlen, off, got;

    if(len > (int) c->max_read)
        len = (int) c->max_read;
    if(len > room_for_payload(c))
    {
        VCLog("     read cut from %d to %d by the buffer", len,
              room_for_payload(c));
        len = room_for_payload(c);
    }
    if(len <= 0)
        Error("No room to receive a read of any size");

    smb2_header(c, SMB2_READ);
    b = msg(c) + SMB2_HDRSIZE;
    memset(b, 0, 49);
    put16(b, 49);
    put32(b + 4, len);                  /* Length */
    put32(b + 8, off_low);
    put32(b + 12, off_high);
    memcpy(b + 16, fileid, SMB2_FILEID_LEN);
    put32(b + 32, 1);                   /* MinimumCount */

    status = smb2_call(c, SMB2_READ, 49, &rlen);
    if(status == 0xC0000011u)           /* STATUS_END_OF_FILE */
        return 0;
    if(status != STATUS_SUCCESS)
        SMBErrorStatus(status);

    b = reply_body(c, 17, rlen);
    off = (int) b[2];                   /* DataOffset, one byte here */
    got = (int) get32(b + 4);
    if((off < SMB2_HDRSIZE) || (got < 0) || (off + got > SMB2_HDRSIZE + rlen))
        Error("Read data is not where the reply says it is");
    if(got > len)
        got = len;
    memcpy(buf, msg(c) + off, got);
    return got;
}

int SMB2Write(smb2_conn_t c, unsigned char fileid[SMB2_FILEID_LEN],
              unsigned int off_low, unsigned int off_high,
              const unsigned char *buf, int len)
{
    unsigned char *b;
    unsigned int status;
    int rlen;

    if(len > (int) c->max_write)
        len = (int) c->max_write;
    /*
        Send less rather than refusing.  The caller writes what is left on
        the next turn round its loop, so a buffer smaller than expected
        makes a transfer slower and not impossible.
    */
    if(len > room_for_payload(c))
    {
        VCLog("     write cut from %d to %d by the buffer", len,
              room_for_payload(c));
        len = room_for_payload(c);
    }
    if(len <= 0)
        Error("No room to send a write of any size");

    smb2_header(c, SMB2_WRITE);
    b = msg(c) + SMB2_HDRSIZE;
    memset(b, 0, 48);
    put16(b, 49);
    put16(b + 2, SMB2_HDRSIZE + 48);    /* DataOffset */
    put32(b + 4, len);
    put32(b + 8, off_low);
    put32(b + 12, off_high);
    memcpy(b + 16, fileid, SMB2_FILEID_LEN);
    memcpy(b + 48, buf, len);

    status = smb2_call(c, SMB2_WRITE, 48 + len, &rlen);
    if(status != STATUS_SUCCESS)
        SMBErrorStatus(status);

    b = reply_body(c, 17, rlen);
    return (int) get32(b + 4);          /* Count */
}

void SMB2Echo(smb2_conn_t c)
{
    unsigned char *b;

    smb2_header(c, SMB2_ECHO);
    b = msg(c) + SMB2_HDRSIZE;
    put16(b, 4);
    put16(b + 2, 0);
    smb2_call(c, SMB2_ECHO, 4, NULL);
}

/*
    Free space, as FileFsFullSizeInformation.  The query is against a
    handle, so the share root is opened for it and closed again.
*/
int SMB2FsInfo(smb2_conn_t c, unsigned int *total, unsigned int *avail,
               unsigned int *unit)
{
    unsigned char fileid[SMB2_FILEID_LEN];
    unsigned char *b;
    unsigned int status;
    int len, off, outlen;

    SMB2Open(c, "", 1, 0, fileid, NULL, NULL, NULL, NULL, NULL);
    ExceptTry
    {
        smb2_header(c, SMB2_QUERY_INFO);
        b = msg(c) + SMB2_HDRSIZE;
        memset(b, 0, 40);
        put16(b, 41);
        b[2] = 2;                       /* SMB2_0_INFO_FILESYSTEM */
        b[3] = 7;                       /* FileFsFullSizeInformation */
        put32(b + 4, 64);               /* OutputBufferLength */
        memcpy(b + 24, fileid, SMB2_FILEID_LEN);

        status = smb2_call(c, SMB2_QUERY_INFO, 40 + 1, &len);
        if(status != STATUS_SUCCESS)
            SMBErrorStatus(status);

        b = reply_body(c, 9, len);
        off = (int) get16(b + 2);
        outlen = (int) get32(b + 4);
        if((off < SMB2_HDRSIZE) || (outlen < 32) ||
           (off + outlen > SMB2_HDRSIZE + len))
            Error("Filesystem information is not where the reply says");
        b = msg(c) + off;
        /* Only the low halves are kept: the interface above counts in
           units and cannot hold more than 32 bits of them anyway. */
        *total = get32(b);
        *avail = get32(b + 16);
        *unit  = get32(b + 24) * get32(b + 28);
    }
    ExceptCatch
    {
        SMB2Close(c, fileid);
        ExceptRethrow();
    }
    SMB2Close(c, fileid);
    return 1;
}


int SMB2Dialect(smb2_conn_t c)          { return c ? c->dialect : 0; }
unsigned int SMB2MaxRead(smb2_conn_t c) { return c ? c->max_read : 0; }
unsigned int SMB2MaxWrite(smb2_conn_t c){ return c ? c->max_write : 0; }
unsigned int SMB2MaxTransact(smb2_conn_t c){ return c ? c->max_transact : 0; }
