/*
 * 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.
 *
 * CDDL HEADER END
 */

/*
 *   Portions Copyright RISC OS Developments 2019+, credited to the RISC OS One Project.
 */

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

#include "LanMan98BaseLib/error.h"
#include "LanMan98BaseLib/memory.h"
#include "smb2.h"
#include "srvsvc.h"

/* Enough for a good many shares; a server with more than this says so and
   the rest are not listed rather than the buffer being grown to fit. */
#define PIPE_BUF_SIZE (16 * 1024)

#define PDU_REQUEST  (0)
#define PDU_RESPONSE (2)
#define PDU_BIND     (11)
#define PDU_BIND_ACK (12)

#define OPNUM_NETRSHAREENUM (15)

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

/*
    The two interface identifiers the bind has to name: the server service
    itself, and the way its arguments are laid out.  Both are written the
    way the wire wants them, which is not the order they are usually
    printed in - the first three fields are little endian and the rest is
    not - so they are given here as bytes rather than assembled.
*/
static const unsigned char srvsvc_uuid[16] =
{
    0xc8, 0x4f, 0x32, 0x4b, 0x70, 0x16, 0xd3, 0x01,
    0x12, 0x78, 0x5a, 0x47, 0xbf, 0x6e, 0xe1, 0x88
};
static const unsigned char ndr_uuid[16] =
{
    0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11,
    0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60
};

/* The header every message down the pipe starts with */
static int rpc_header(unsigned char *p, int type, int len, int call_id)
{
    p[0] = 5;                   /* version */
    p[1] = 0;                   /* version minor */
    p[2] = (unsigned char) type;
    p[3] = 0x03;                /* first and last fragment */
    put32(p + 4, 0x00000010);   /* how the numbers are laid out */
    put16(p + 8, (unsigned int) len);
    put16(p + 10, 0);           /* no authentication trailer */
    put32(p + 12, (unsigned int) call_id);
    return 16;
}

static int build_bind(unsigned char *p)
{
    int n;

    n = 16;
    put16(p + n, 4280); n += 2;          /* most we will send */
    put16(p + n, 4280); n += 2;          /* most we will take */
    put32(p + n, 0); n += 4;             /* no association group */
    put32(p + n, 1); n += 4;             /* one interface asked for */
    put16(p + n, 0); n += 2;             /* context identifier */
    p[n++] = 1;                          /* one way of laying it out */
    p[n++] = 0;
    memcpy(p + n, srvsvc_uuid, 16); n += 16;
    put16(p + n, 3); n += 2;             /* interface version */
    put16(p + n, 0); n += 2;
    memcpy(p + n, ndr_uuid, 16); n += 16;
    put16(p + n, 2); n += 2;
    put16(p + n, 0); n += 2;
    rpc_header(p, PDU_BIND, n, 1);
    return n;
}

/* A string as the call machinery wants it: how many, from where, how many
   again, then the characters, two bytes each, padded out to a multiple of
   four. */
static int put_string(unsigned char *p, const char *s)
{
    int n, i, len;

    len = (int) strlen(s) + 1;          /* the terminator is counted */
    n = 0;
    put32(p + n, (unsigned int) len); n += 4;
    put32(p + n, 0); n += 4;
    put32(p + n, (unsigned int) len); n += 4;
    for(i = 0; i < len; i++)
    {
        p[n++] = (unsigned char) s[i];
        p[n++] = 0;
    }
    while(n & 3)
        p[n++] = 0;
    return n;
}

static int build_enum(unsigned char *p, char *server)
{
    char name[128];
    int n, stub;

    sprintf(name, "\\\\%.100s", server);

    n = 24;                              /* header plus the request part */
    stub = n;
    put32(p + n, 0x00020000); n += 4;    /* the server name follows */
    n += put_string(p + n, name);
    put32(p + n, 1); n += 4;             /* level of detail wanted */
    put32(p + n, 1); n += 4;             /* and again, for the union */
    put32(p + n, 0x00020004); n += 4;    /* a container follows */
    put32(p + n, 0); n += 4;             /* holding nothing yet */
    put32(p + n, 0); n += 4;             /* and no array yet */
    put32(p + n, 0xFFFFFFFFu); n += 4;   /* as much as it will give */
    put32(p + n, 0x00020008); n += 4;    /* a resume point follows */
    put32(p + n, 0); n += 4;             /* starting at the beginning */

    rpc_header(p, PDU_REQUEST, n, 2);
    put32(p + 16, (unsigned int) (n - stub));   /* how much follows */
    put16(p + 20, 0);                           /* context identifier */
    put16(p + 22, OPNUM_NETRSHAREENUM);
    return n;
}

/* One string out of the answer, at *pos, advanced past it */
static char *take_string(const unsigned char *d, int len, int *pos)
{
    unsigned int count;
    char *s;
    int p, i;

    p = *pos;
    if(p + 12 > len)
        Error("Share list ended in the middle of a name");
    count = get32(d + p + 8);           /* how many characters there are */
    p += 12;
    if((count > 1024) || (p + (int) count * 2 > len))
        Error("Share list ended in the middle of a name");
    s = Malloc((int) count + 1);
    for(i = 0; i < (int) count; i++)
    {
        unsigned int ch;

        ch = d[p + i * 2] | (((unsigned int) d[p + i * 2 + 1]) << 8);
        s[i] = (char) ((ch < 256) ? ch : '.');
    }
    s[count] = 0;
    /* the terminator is part of the count; drop it */
    if((count > 0) && (s[count - 1] == 0))
        s[count - 1] = 0;
    p += (int) count * 2;
    while(p & 3) p++;
    *pos = p;
    return s;
}

static share_t parse_shares(const unsigned char *d, int len)
{
    share_t head, tail, sh;
    unsigned int entries, array;
    int p, i;
    unsigned int *types;

    if(len < 24 + 24)
        Error("The server gave a share list too short to be one");
    if(d[2] != PDU_RESPONSE)
        Error("The server would not list its shares");

    p = 24;
    p += 4;                             /* level, which we asked for */
    p += 4;                             /* and the union tag for it */
    if(get32(d + p) == 0)
        return NULL;                    /* no container: nothing shared */
    p += 4;
    entries = get32(d + p); p += 4;
    array = get32(d + p); p += 4;
    if((array == 0) || (entries == 0))
        return NULL;
    if(entries > 1024)
        Error("The server claims %u shares, which is not believable", entries);
    p += 4;                             /* how many the array holds */

    /* The fixed part of every entry comes first, then all the strings */
    types = Malloc((int) entries * (int) sizeof(unsigned int));
    head = NULL; tail = NULL;
    ExceptTry
    {
        for(i = 0; i < (int) entries; i++)
        {
            if(p + 12 > len)
                Error("Share list ended early");
            types[i] = get32(d + p + 4);
            p += 12;
            sh = Malloc(sizeof(*sh));
            memset(sh, 0, sizeof(*sh));
            sh->type = types[i];
            if(head == NULL) head = sh; else tail->next = sh;
            tail = sh;
        }
        for(sh = head; sh; sh = sh->next)
        {
            sh->name = take_string(d, len, &p);
            sh->remark = take_string(d, len, &p);
        }
    }
    ExceptCatch
    {
        Free(types);
        SRVSVCFreeShares(head);
        ExceptRethrow();
    }
    Free(types);
    return head;
}

void SRVSVCFreeShares(share_t list)
{
    share_t next;

    while(list)
    {
        next = list->next;
        if(list->name) Free(list->name);
        if(list->remark) Free(list->remark);
        Free(list);
        list = next;
    }
}

share_t SRVSVCEnumShares(smb2_conn_t c, char *server)
{
    unsigned char fileid[16];
    unsigned char *buf;
    share_t list;
    int n;

    if(c == NULL)
        Error("Listing shares needs SMB2; the older protocol has no way to ask");

    buf = Malloc(PIPE_BUF_SIZE);
    list = NULL;
    ExceptTry
    {
        SMB2OpenPipe(c, "srvsvc", fileid);
    }
    ExceptCatch
    {
        Free(buf);
        ExceptRethrow();
    }

    ExceptTry
    {
        n = build_bind(buf);
        n = SMB2Transceive(c, fileid, buf, n, buf, PIPE_BUF_SIZE);
        if((n < 4) || (buf[2] != PDU_BIND_ACK))
            Error("The server would not accept a request for its share list");

        n = build_enum(buf, server);
        n = SMB2Transceive(c, fileid, buf, n, buf, PIPE_BUF_SIZE);
        list = parse_shares(buf, n);
    }
    ExceptCatch
    {
        SMB2Close(c, fileid);
        Free(buf);
        ExceptRethrow();
    }
    SMB2Close(c, fileid);
    Free(buf);
    return list;
}
