/*
 * 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 2026 RISC OS Developments */

/*
 *   Copyright 1996 Warm Silence Software Ltd.  All rights reserved.
 *   Use is subject to license terms.
 */
 
/* PHBG 27/12/97: Initial version
 */

// #include "MemCheck:MemCheck.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "LanMan98BaseLib/error.h"
#include "LanMan98BaseLib/memory.h"
#include "vc.h"
#include "mb.h"
#include "smbcmd.h"
#include "smb.h"
#include "smberr.h"
#include "trans.h"
#include "LanMan98BaseLib/strext.h"
#include "date.h"

#include "lm2.h"

// #define WATCH

#define RETRYS (100)

#define USEFORM

#define SMB_INFO_STANDARD (1)
#define SMB_FIND_FILE_BOTH_DIRECTORY_INFO (0x104)
#define SMB_QUERY_FILE_ALL_INFO (0x107)

#define DIRBUFSIZE (2048)
// #define DIRBUFSIZE (512)

#define MAXENTRIES (2000)
// #define MAXENTRIES (20)

#include "smbsvr.h"

typedef struct
{
    int sid;
    int refs;
} *sid_t;

struct smb_obj_s
{
#include "smbobj.h"
    sid_t sid;
    trans_buf_t dir_buf;
    int entries;
    int end_of_search;
    int dir_index;
    int resume_key;
};

typedef struct
{
    ushort search_attributes;
    ushort search_count;
    ushort flags;
    ushort information_level;
    int search_storage_type;
} first2_req_t;

typedef struct
{
    ushort sid;
    ushort search_count;
    ushort end_of_search;
    ushort ea_error_offset;
    ushort last_name_offset;
} first2_resp_t;

typedef struct
{
    ushort sid;
    ushort search_count;
    ushort information_level;
    ushort resume_key_low;
    ushort resume_key_high;
    ushort flags;
} next2_req_t;

typedef struct
{
    ushort search_count;
    ushort end_of_search;
    ushort ea_error_offset;
    ushort last_name_offset;
} next2_resp_t;

typedef struct
{
    int resume_key;
    ushort creation_date;
    ushort creation_time;
    ushort last_access_date;
    ushort last_access_time;
    ushort last_write_date;
    ushort last_write_time;
    int data_size;
    int allocation_size;
    ushort attributes;
    char file_name_length;
    char file_name[1];
} info_standard_t;

typedef struct
{
    int next_entry_offset;
    int resume_key;
    unsigned creation_lowdate;
    int creation_hightime;
    unsigned last_access_lowtime;
    int last_access_hightime;
    unsigned last_write_lowtime;
    int last_write_hightime;
    unsigned change_lowtime;
    int change_hightime;
    int data_size;
    int data_size_high;
    int allocation_size;
    int allocation_size_high;
    int attributes;
    int file_name_length;
    int ea_size;
    char short_name_length;
    char reserved;
    char short_name[2*12];
    char file_name[1];
} info_both_t;

typedef struct
{
    ushort creation_date;
    ushort creation_time;
    ushort last_access_date;
    ushort last_access_time;
    ushort last_write_date;
    ushort last_write_time;
    int data_size;
    int allocation_size;
    ushort attributes;
} set_path_info_t;

/* SMB_QUERY_FILE_ALL_INFO: the query counterpart of the NT find level,
   carrying FILETIMEs and a 64 bit size instead of DOS dates. */
typedef struct
{
    unsigned creation_lowtime;
    int creation_hightime;
    unsigned last_access_lowtime;
    int last_access_hightime;
    unsigned last_write_lowtime;
    int last_write_hightime;
    unsigned change_lowtime;
    int change_hightime;
    int attributes;
    int reserved;
    int allocation_size;
    int allocation_size_high;
    int data_size;
    int data_size_high;
    int number_of_links;
    char delete_pending;
    char directory;
    ushort reserved2;
    int ea_size;
    int file_name_length;
    char file_name[1];
} info_all_t;

#define FILE_NAME_OFFSET ((int) (((info_standard_t *)0)->file_name))
#define FILE_NAME_OFFSET2 ((int) (((info_both_t *)0)->file_name))

/*
    Which directory information level to ask a server for.

    SMB_INFO_STANDARD carries a DOS date and time, which has two second
    granularity and no time zone, and a 32 bit size.  The NT level carries
    a FILETIME and a 64 bit size, so a timestamp survives the trip intact.
    Every server speaking NT LM 0.12 supports it - it is the level the NT
    client itself used - but the older dialects have only the standard one,
    so that is what they keep.

    LanMan98$FindStandard forces the old level back, for a server that
    answers the NT one badly.
*/
static int find_level(smb_server_t svr)
{
    if(svr->nas_basic_firmware)
        return SMB_FIND_FILE_BOTH_DIRECTORY_INFO;
    if((svr->dialect >= NT) && (getenv("LanMan98$FindStandard") == NULL))
        return SMB_FIND_FILE_BOTH_DIRECTORY_INFO;
    return SMB_INFO_STANDARD;
}

/*
    The same choice for a query about one named file.  Both levels have to
    move together: the probe in smart_form below decides whether a server
    reverses its DOS date and time by comparing what a find returned against
    what a query returned, and that comparison is only meaningful when the
    two came from the same kind of clock.
*/
static int query_level(smb_server_t svr)
{
    return (find_level(svr) == SMB_FIND_FILE_BOTH_DIRECTORY_INFO)
             ? SMB_QUERY_FILE_ALL_INFO : SMB_INFO_STANDARD;
}

// static int sids = 0;

sid_t mk_sid(int sid)
{
    sid_t res;

    res = Malloc(sizeof(*res));
    res->sid = sid;
    res->refs = 1;
    // printf("Construct sid %d\n", ++sids);
    return res;
}

void free_sid(sid_t sid, smb_server_t svr)
{
    data_t mb;
    ushort vwv[1];

    --sid->refs;
    if(sid->refs == 0)
    {
        ExceptTry
        {
            mb = VCBuffer(svr->vc);
            MBSetCom(mb, SMBfindclose2);
            vwv[0] = sid->sid;
            MBSetVWV(mb, 1, vwv);
            svr->send(svr->vc);
            svr->receive(svr->vc);
            // printf("Destruct sid %d\n", --sids);
        } ExceptCatch {}
        Free(sid);
    }
}

sid_t copy_sid(sid_t sid)
{
    ++sid->refs;
    return sid;
}

#if 0
static void show_error(int *id, char *tag, _kernel_oserror *err)
{
    static char buf[32];

    sprintf(buf, "LanMan98$%sError%02d", tag, (*id)++);
    VarSet(buf, err->errmess);
}
#endif


/*
static char stack[8*1024];

extern char *_get_stack(void);
extern void _set_stack(char *);

static vc_t saved_vc;
static _kernel_oserror *saved_err;

static void send0(void)
{
    data_t mb;

    ExceptTry
    {
        mb = VCBuffer(saved_vc);
        MBClearError(mb);
        MBSetFlags(mb, 8, SMB_FLAGS2_LONG_NAMES | VCFlags2(saved_vc));
        VCSend(saved_vc, MBMessLen(mb));
        saved_err = NULL;
    }
    ExceptCatch
    {
        saved_err = ExceptCaught();
    }
}

static void send(vc_t vc)
{
    char *low, *high;

    saved_vc = vc;
    MemCheck_SetChecking(0, 0);
    low = _get_stack();
    high = ((char *)(((int)low) & ~0xfffff)) + 8*1024 - 64;
    memcpy(stack, low, high-low);
    _set_stack(high);
    send0();
    _set_stack(low);
    memcpy(low, stack, high-low);
    MemCheck_SetChecking(1, 1);
    if(saved_err) ErrorErr(saved_err);
}

static void receive0(void)
{
    data_t mb;
    unsigned int status;
    int nt, okay, error, sub_error;

    ExceptTry
    {
        VCReceive(saved_vc);
        mb = VCBuffer(saved_vc);
        status = 0;
        nt = MBIsNTStatus(mb);
        if(nt)
        {
            status = MBGetNTStatus(mb);
            SMBStatusToDos(status, &error, &sub_error);
        }
        else
        {
            MBGetError(mb, &error, &sub_error);
        }
        okay = error == 0;
        if(error == 1)
        {
            switch(sub_error)
            {
                case 18:
                    okay = 1;
                    break;
            }
        }
        if(!okay)
        {
            VCFlush(saved_vc);
            if(nt)
                SMBErrorStatus(status);
            else
                SMBError(error, sub_error);
        }
        saved_err = NULL;
    }
    ExceptCatch
    {
        saved_err = ExceptCaught();
    }
}

static void receive(vc_t vc)
{
    char *low, *high;

    saved_vc = vc;
    MemCheck_SetChecking(0, 0);
    low = _get_stack();
    high = ((char *)(((int)low) & ~0xfffff)) + 8*1024 - 64;
    memcpy(stack, low, high-low);
    _set_stack(high);
    receive0();
    _set_stack(low);
    memcpy(low, stack, high-low);
    MemCheck_SetChecking(1, 1);
    if(saved_err) ErrorErr(saved_err);
}
*/

#ifdef WATCH
static char *bad = NULL;
#endif

static void send(vc_t vc)
{
    data_t mb;

    mb = VCBuffer(vc);
    MBClearError(mb);
    MBSetFlags(mb, 8, SMB_FLAGS2_LONG_NAMES | VCFlags2(vc));
#ifdef WATCH
    {
        int len;

        if(bad)
        {
            memset(bad, 0, 4);
            Free(bad);
        }
        len = MBMessLen(mb);
        bad = Malloc(len+8);
        memcpy(bad, "ZWZW", 4);
        memcpy(bad + 4, &len, 4);
        memcpy(bad + 8, mb.buf, len);
    }
#endif
    VCSend(vc, MBMessLen(mb));
}

static void receive(vc_t vc)
{
    data_t mb;
    unsigned int status;
    int nt, flush, okay, error, sub_error;

    VCReceive(vc);
    mb = VCBuffer(vc);
    status = 0;
    nt = MBIsNTStatus(mb);
    if(nt)
    {
        status = MBGetNTStatus(mb);
        SMBStatusToDos(status, &error, &sub_error);
    }
    else
    {
        MBGetError(mb, &error, &sub_error);
    }
    okay = error == 0;
    flush = !okay;
    switch(error)
    {
        case 1:
            switch(sub_error)
            {
                case 18:
                    okay = 1;
                    // Fall through
                case 2:
                case 3:
                    flush = 0;
                    break;
#ifdef WATCH
                case 0x57:
                    bad = NULL;
                    break;
#endif
            }
            break;
        case 2:
            switch(sub_error)
            {
                case 1:
                    // flush = 0;
                    break;
            }
            break;
    }
#ifdef WATCH
    if(bad)
    {
        memset(bad, 0, 4);
        Free(bad);
        bad = NULL;
    }
#endif
    if(flush)
    {
        VCFlush(vc);
    }
    if(!okay)
    {
        if(nt)
            SMBErrorStatus(status);
        else
            SMBError(error, sub_error);
    }
}

static char *name_map(char *name)
{
    return name;
}

static int quotable(char c, int last)
{
    switch(c)
    {
    case '&':
    case '@':
        return 1;
    case '%':
        return !last;
    default:
        return 0;
    }
}

static int unquotable(char c, int last)
{
    switch(c)
    {
    case '+':
    case '=':
        return 1;
    case '~':
        return !last;
    default:
        return 0;
    }
}

static char quote(char c, int last)
{
    switch(c)
    {
    case '&': return '+';
    case '@': return '=';
    case '%': return last ? c : '~';
    default: return c;
    }
}

static char unquote(char c, int last)
{
    switch(c)
    {
    case '+': return '&';
    case '=': return '@';
    case '~': return last ? c : '%';
    default: return c;
    }
}

static char map(char c)
{
    switch(c)
    {
    case '#': return '?';
    case '?': return '#';
    case '$': return '<';
    case '^': return '>';
    case '.': return '/';
    case '%': return '/';
    case '\\': return '.';
    case ' ': return 0xA0;
    default: return c;
    }
}

static char unmap(char c, int last)
{
    switch(c)
    {
    case '?': return '#';
    case '#': return '?';
    case '<': return '$';
    case '>': return '^';
    case '/': return last ? '%' : '.';
    case '.': return '\\';
    case 0xA0: return ' ';
    default: return c;
    }
}

static char *smb2ro(char *smb)
{
    char *ro, *ro_p;
    int last;

    ro = Malloc(2*strlen(smb)+1);
    ro_p = ro;
    last = 0;
    while(*smb)
    {
        last = *(smb+1) == 0;
        if(quotable(*smb, last))
        {
            *ro_p++ = ';';
            *ro_p++ = quote(*smb++, last);
        }
        else if(*smb == ';')
        {
            *ro_p++ = *smb++;
            while(*smb == '!')
                *ro_p++ = *smb++;
            last = *(smb+1) == 0;
            if(unquotable(*smb, last))
            {
                *ro_p++ = '!';
                *ro_p++ = *smb++;
            }
        }
        else
        {
            *ro_p++ = map(*smb++);
        }
    }
    *ro_p = 0;
    return ro;
}

static char *ro2smb(char *ro)
{
    char *smb, *smb_p;
    int q, last;

    smb = Malloc(strlen(ro)+1);
    smb_p = smb;
    last = 0;
    while(*ro)
    {
        if(*ro == ';')
        {
            *smb_p++ = *ro++;
            q = 0;
            while(*ro == '!')
            {
                *smb_p++ = *ro++;
                q = 1;
            }
            last = *(ro+1) == 0;
            if(unquotable(*ro, last))
            {
                smb_p--;
                *smb_p++ = q ? *ro++ : unquote(*ro++, last);
            }
        }
        else
        {
            last = *(ro+1) == 0;
            *smb_p++ = unmap(*ro++, last);
        }
    }
    *smb_p = 0;
    return smb;
}

static smb_obj_t construct(smb_server_t svr)
{
    smb_obj_t obj;

    obj = Malloc(sizeof(*obj));
    obj->svr = svr;
    obj->path = NULL;
    obj->name = NULL;
    obj->ro_name = NULL;
    obj->dirty = 0;
    obj->sid = NULL;
    obj->dir_buf = NULL;
    obj->entries = 0;
    obj->end_of_search = 0;
    return obj;
}

static smb_obj_t dup(smb_obj_t obj)
{
    smb_obj_t nobj;

    nobj = Malloc(sizeof(*nobj));
    nobj->svr = obj->svr;
    nobj->path = NULL;
    nobj->name = NULL;
    ExceptTry
    {
        nobj->path = obj->path ? strdup(obj->path) : NULL;
        nobj->name = obj->name ? strdup(obj->name) : NULL;
    }
    ExceptCatch
    {
        if(nobj->path) Free(nobj->path);
        if(nobj->name) Free(nobj->name);
        Free(nobj);
        ExceptRethrow();
    }
    nobj->ro_name = NULL;
    nobj->length = obj->length;
    nobj->attribute = obj->attribute;
    nobj->time = obj->time;
    nobj->dirty = obj->dirty;
    nobj->sid = obj->sid ? copy_sid(obj->sid) : NULL;
    nobj->dir_buf = obj->dir_buf ? TransDupBuf(obj->dir_buf) : NULL;
    nobj->entries = obj->entries;
    nobj->end_of_search = obj->end_of_search;
    nobj->dir_index = obj->dir_index;
    nobj->resume_key = obj->resume_key;
    return nobj;
}

static void destruct(smb_obj_t obj)
{
    if(obj->name) Free(obj->name);
    if(obj->ro_name) Free(obj->ro_name);
    if(obj->path) Free(obj->path);
    if(obj->sid) free_sid(obj->sid, obj->svr);
    if(obj->dir_buf) TransFreeBuf(obj->dir_buf);
    Free(obj);
}

/*
    A directory entry is only as long as what actually arrived.

    Every entry names the length of the name in it and the offset of the
    entry after it, and both used to be believed.  A length larger than the
    bufferful copied a name from wherever it pointed - two kilobytes is all
    there is, and the length is a full word - and an offset larger than the
    bufferful walked out of the buffer and kept going.  Both are the
    server's word, and nothing else had checked them.
*/
static void entry_fits(smb_obj_t obj, int fixed, int namelen)
{
    int have;

    have = obj->dir_buf ? obj->dir_buf->out : 0;
    if((obj->dir_index < 0) || (obj->dir_index > have) ||
       (fixed > have - obj->dir_index) ||
       (namelen < 0) || (namelen > have - obj->dir_index - fixed))
        Error("Malformed directory entry from server");
}

/*
    The length of a name that ends where the server chose to end it, which
    may be nowhere: a name running to the end of the bufferful without a
    terminator used to be measured by strlen, off the end of the block.
*/
static int entry_name_len(smb_obj_t obj, int fixed)
{
    char *p;
    int room, i;

    entry_fits(obj, fixed, 0);
    room = obj->dir_buf->out - obj->dir_index - fixed;
    p = ((char *) obj->dir_buf->buf) + obj->dir_index + fixed;
    for(i = 0; i < room; i++)
        if(p[i] == 0)
            return i;
    Error("Unterminated name in directory entry from server");
    return 0;
}

/*
    Where the next entry begins.

    An offset past the end of what arrived means there is no next entry
    here: entries are padded to a boundary and the padding after the last
    one is not always sent, so the last entry of a bufferful can name an
    offset a few bytes beyond it.  That ends the bufferful rather than
    being an error, which is what the SMB2 side does with the same thing.
*/
static void entry_advance(smb_obj_t obj, int next)
{
    int have;

    have = obj->dir_buf ? obj->dir_buf->out : 0;
    if((next < 0) || (next > have - obj->dir_index))
        obj->dir_index = have;
    else
        obj->dir_index += next;
}

static int form(smb_obj_t obj, char *name, int rev)
{
    // static int id = 0;
    trans_buf_t setup, parm;
    ushort *req;
    info_standard_t *info;
    int i;
    volatile int worked;

    setup = TransInitBuf(2,0);
    parm = TransInitBuf(6 + strlen(obj->path) + strlen(name) + 1, 2);
    worked = 0;
    for(i = 0; i < RETRYS && !worked; i++)
    {
        ExceptTry
        {
            if(obj->dir_buf)
            {
                TransFreeBuf(obj->dir_buf);
                obj->dir_buf = NULL;
            }
            obj->dir_buf = TransInitBuf(0, DIRBUFSIZE);

            ((short *)setup->buf)[0] = TRANS2_QUERY_PATH_INFORMATION;

            req = parm->buf;
            req[0] = query_level(obj->svr);
            req[1] = 0;
            req[2] = 0;
            strcpy((char *)(req+3), obj->path);
            strcat((char *)(req+3), name);

            if(obj->sid)
            {
                free_sid(obj->sid, obj->svr);
                obj->sid = NULL;
            }

            TransCommunicate(setup, parm, obj->dir_buf, obj->svr->vc, obj->svr->send, obj->svr->receive);

            obj->dir_index = 0;
            worked = 1;
        }
        ExceptCatch
        {
            if(ExceptCaught()->errnum != 0x20001)
            {
                TransFreeBuf(setup);
                TransFreeBuf(parm);
                ExceptRethrow();
            }
#if 0
            else
            {
                printf("hiccup in Form 0x%8x\n", ExceptCaught()->errnum);
            }
#endif
        }
    }
    TransFreeBuf(setup);
    TransFreeBuf(parm);
    if(!worked)
    {
        // show_error(&id, "Form", ExceptCaught());
        ExceptRethrow();
    }
#if 0
    else if(i > 1)
    {
        static int id = 0;

        ExceptTry
        {
            Error("took %d goes", i);
        }
        ExceptCatch
        {
            show_error(&id, "Form", ExceptCaught());
        }
    }
#endif

    if(query_level(obj->svr) == SMB_QUERY_FILE_ALL_INFO)
    {
        info_all_t *all;

        /* No resume key to step over, and the times need no untangling */
        all = obj->dir_buf->buf;
        obj->time = DateTimetoSMB(all->last_write_lowtime, all->last_write_hightime);
        obj->length = all->data_size;
        obj->attribute = all->attributes;
    }
    else
    {
        info = (info_standard_t *)(((char *)obj->dir_buf->buf) - 4); // -4 because no resume key
        obj->time = DateDOStoSMB(rev ? info->last_write_date : info->last_write_time,
                                 rev ? info->last_write_time : info->last_write_date);
        obj->length = info->data_size;
        obj->attribute = info->attributes;
    }
    obj->resume_key = 0;
    obj->entries = 0;

    return 1;
}

static int first(smb_obj_t obj, char *name)
{
    // static int id = 0;
    trans_buf_t setup, parm;
    first2_req_t *req;
    first2_resp_t *resp;
    int found;
    int i;
    volatile int worked;

    // printf("First %s, %s\n", obj->path, name);
    setup = TransInitBuf(2,0);
    parm = TransInitBuf(sizeof(first2_req_t) + strlen(obj->path)
                                             + strlen(name) + 1,
                       sizeof(first2_resp_t));
    worked = 0;
    for(i = 0; i < RETRYS && !worked; i++)
    {
        ExceptTry
        {
            if(obj->dir_buf)
            {
                TransFreeBuf(obj->dir_buf);
                obj->dir_buf = NULL;
            }
            obj->dir_buf = TransInitBuf(0, DIRBUFSIZE);

            ((short *)setup->buf)[0] = TRANS2_FIND_FIRST2;

            req = parm->buf;
            req->search_attributes = 0x37;
            req->search_count = MAXENTRIES;
            req->flags = (1<<2);
            req->information_level = find_level(obj->svr);
            req->search_storage_type = 0;
            strcpy((char *)(req+1), obj->path);
            strcat((char *)(req+1), name);

            if(obj->sid)
            {
                free_sid(obj->sid, obj->svr);
                obj->sid = NULL;
            }

            TransCommunicate(setup, parm, obj->dir_buf, obj->svr->vc, obj->svr->send, obj->svr->receive);

            resp = parm->buf;
            obj->sid = mk_sid(resp->sid);
            obj->entries = resp->search_count;
            obj->end_of_search = resp->end_of_search;
            obj->dir_index = 0;
            worked = 1;
        }
        ExceptCatch
        {
            if(ExceptCaught()->errnum == 0x10002)
            {
                obj->entries = 0;
                worked = 1;
            }
            else if(ExceptCaught()->errnum != 0x20001)
            {
                TransFreeBuf(setup);
                TransFreeBuf(parm);
                ExceptRethrow();
            }
#if 0
            else
            {
                printf("hiccup in First\n");
            }
#endif
        }
    }
    TransFreeBuf(setup);
    TransFreeBuf(parm);
    if(!worked)
    {
        // show_error(&id, "Find", ExceptCaught());
        ExceptRethrow();
    }
#if 0
    else if(i > 1)
    {
        static int id = 0;

        ExceptTry
        {
            Error("took %d goes", i);
        }
        ExceptCatch
        {
            show_error(&id, "Find", ExceptCaught());
        }
    }
#endif

    found = obj->entries > 0;
    if(found)
    {
        if(obj->name)
        {
            Free(obj->name);
            obj->name = NULL;
        }
        if (find_level(obj->svr) == SMB_FIND_FILE_BOTH_DIRECTORY_INFO)
        {
            info_both_t *info;
            entry_fits(obj, FILE_NAME_OFFSET2, 0);
            info = (info_both_t *) (((char *) obj->dir_buf->buf) + obj->dir_index);
            entry_fits(obj, FILE_NAME_OFFSET2, info->file_name_length);
            obj->name = Malloc(info->file_name_length + 1);
            memcpy(obj->name, info->file_name, info->file_name_length);
            obj->name[info->file_name_length] = '\0';
            obj->time = DateTimetoSMB(info->last_write_lowtime, info->last_write_hightime);
            obj->length = info->data_size;
            obj->attribute = info->attributes;
            obj->resume_key = info->resume_key;
            entry_advance(obj, info->next_entry_offset);
            obj->entries--;
        }
        else
        {
            info_standard_t *info;
            int namelen;

            namelen = entry_name_len(obj, FILE_NAME_OFFSET);
            info = (info_standard_t *) (((char *) obj->dir_buf->buf) + obj->dir_index);
            obj->name = Malloc(namelen + 1);
            memcpy(obj->name, info->file_name, namelen);
            obj->name[namelen] = '\0';
            obj->time = DateDOStoSMB(info->last_write_time, info->last_write_date);
            // printf("\nCreated 0x%04x%04x\n", info->creation_time, info->creation_date);
            // printf("\nAccessed 0x%04x%04x\n", info->last_access_time, info->last_access_date);
            obj->length = info->data_size;
            obj->attribute = info->attributes;
            obj->resume_key = info->resume_key;
            /* the fixed part, the name, and the zero that ended it */
            entry_advance(obj, FILE_NAME_OFFSET + namelen + 1);
            obj->entries--;
        }
    }
    return found;
}

static int next(smb_obj_t obj)
{
    // static int id = 0;
    trans_buf_t setup, parm;
    next2_req_t *req;
    next2_resp_t *resp;
    int found, i;
    volatile int worked;

    if(obj->entries <= 0)
    {
        if(obj->end_of_search)
            return 0;

        // printf("Next %s, %s\n", obj->path, obj->name);
        setup = TransInitBuf(2,0);
        parm = TransInitBuf(sizeof(next2_req_t) + strlen(obj->name) + 1,
        // parm = TransInitBuf(sizeof(next2_req_t) + 1,
                           sizeof(next2_resp_t));
        worked = 0;
        for(i = 0; i < RETRYS && !worked; i++)
        {
            ExceptTry
            {
                if(obj->dir_buf)
                {
                    TransFreeBuf(obj->dir_buf);
                    obj->dir_buf = NULL;
                }
                obj->dir_buf = TransInitBuf(0, DIRBUFSIZE);

                ((short *)setup->buf)[0] = TRANS2_FIND_NEXT2;

                req = parm->buf;
                req->sid = obj->sid->sid;
                req->search_count = MAXENTRIES;
                req->information_level = find_level(obj->svr);
                req->resume_key_low = obj->resume_key;
                req->resume_key_high = (obj->resume_key >> 16);
                req->flags = (1<<2);
                strcpy((char *)(req+1), obj->name);
                // strcpy((char *)(req+1), "");

                TransCommunicate(setup, parm, obj->dir_buf, obj->svr->vc, obj->svr->send, obj->svr->receive);

                resp = parm->buf;
                obj->entries = resp->search_count;
                obj->end_of_search = resp->end_of_search;
                obj->dir_index = 0;
                worked = 1;
            }
            ExceptCatch
            {
                if(ExceptCaught()->errnum != 0x20001)
                {
                    TransFreeBuf(setup);
                    TransFreeBuf(parm);
                    ExceptRethrow();
                }
#if 0
                else
                {
                    printf("hiccup in Next\n");
                }
#endif
            }
        }
        TransFreeBuf(setup);
        TransFreeBuf(parm);
        if(!worked)
        {
            // show_error(&id, "Next", ExceptCaught());
            ExceptRethrow();
        }
#if 0
    else if(i > 1)
    {
        static int id = 0;

        ExceptTry
        {
            Error("took %d goes", i);
        }
        ExceptCatch
        {
            show_error(&id, "Next", ExceptCaught());
        }
    }
#endif
    }
    found = obj->entries > 0;
    if(found)
    {
        if(obj->name)
        {
            Free(obj->name);
            obj->name = NULL;
        }

        if (find_level(obj->svr) == SMB_FIND_FILE_BOTH_DIRECTORY_INFO)
        {
            info_both_t info;
            entry_fits(obj, FILE_NAME_OFFSET2, 0);
            memcpy(&info, ((char *) obj->dir_buf->buf) + obj->dir_index, FILE_NAME_OFFSET2);
            entry_fits(obj, FILE_NAME_OFFSET2, info.file_name_length);
            obj->name = Malloc(info.file_name_length + 1);
            memcpy(obj->name, ((char *) obj->dir_buf->buf) + obj->dir_index + FILE_NAME_OFFSET2, info.file_name_length);
            obj->name[info.file_name_length] = '\0';
            obj->time = DateTimetoSMB(info.last_write_lowtime, info.last_write_hightime);
            obj->length = info.data_size;
            obj->attribute = info.attributes;
            obj->resume_key = info.resume_key;
            entry_advance(obj, info.next_entry_offset);
            obj->entries--;
        }
        else
        {
            info_standard_t info;
            int namelen;

            namelen = entry_name_len(obj, FILE_NAME_OFFSET);
            memcpy(&info, ((char *) obj->dir_buf->buf) + obj->dir_index, FILE_NAME_OFFSET);
            obj->name = Malloc(namelen + 1);
            memcpy(obj->name, ((char *) obj->dir_buf->buf) + obj->dir_index + FILE_NAME_OFFSET, namelen);
            obj->name[namelen] = '\0';
            obj->time = DateDOStoSMB(info.last_write_time, info.last_write_date);
            // printf("\nCreated 0x%04x%04x\n", info.creation_time, info.creation_date);
            // printf("\nAccessed 0x%04x%04x\n", info.last_access_time, info.last_access_date);
            obj->length = info.data_size;
            obj->attribute = info.attributes;
            obj->resume_key = info.resume_key;
            /* the fixed part, the name, and the zero that ended it */
            entry_advance(obj, FILE_NAME_OFFSET + namelen + 1);
            obj->entries--;
        }
    }

    return found;
}

static int smart_form(smb_obj_t obj, char *name)
{
    char *name2;
    int res;

    if(obj->svr->date_time_reversed != -1)
        return form(obj, name, obj->svr->date_time_reversed == 1);

    /* Nothing to probe for: the NT levels carry an unambiguous FILETIME */
    if(query_level(obj->svr) == SMB_QUERY_FILE_ALL_INFO)
    {
        obj->svr->date_time_reversed = 0;
        return form(obj, name, 0);
    }

    name2 = strdup(name);
    ExceptTry
    {
        int tnonrev, trev, tfind;

        if(!first(obj, name2)) Error("X");
        tfind = obj->time;
        if(!form(obj, name2, 0)) Error("Y");
        tnonrev = obj->time;
        if(!form(obj, name2, 1)) Error("Z");
        trev = obj->time;

        if(trev != tnonrev)
        {
            if(trev == tfind)
            {
                // printf("Dates reversed\n");
                obj->svr->date_time_reversed = 1;
            }
            if(tnonrev == tfind)
            {
                // printf("Dates not reversed\n");
                obj->svr->date_time_reversed = 0;
            }
        }
    } ExceptCatch {}

    ExceptTry
    {
        res = form(obj, name2, obj->svr->date_time_reversed == 1);
    }
    ExceptCatch
    {
        Free(name2);
        ExceptRethrow();
    }
    Free(name2);

    return res;
}

static void set_info(smb_obj_t obj, int fid)
{
    volatile trans_buf_t setup, parm, data;
    set_path_info_t *new_info;

    setup = parm = data = NULL;
    if(obj->dirty)
    {
        ExceptTry
        {
            int lw_t, lw_d;

            setup = TransInitBuf(2, 0);
            parm = TransInitBuf(6, 2);
            data = TransInitBuf(22, 0);
            ((short *)setup->buf)[0] = TRANS2_SET_FILE_INFORMATION;
            ((short *)parm->buf)[0] = fid;
            ((short *)parm->buf)[1] = 0x1;
            ((short *)parm->buf)[2] = 0;
            new_info = (set_path_info_t *) data->buf;
            DateSMBtoDOS(obj->time, &lw_t, &lw_d);
            new_info->creation_date = (ushort) lw_d;
            new_info->creation_time = (ushort) lw_t;
            new_info->last_access_date = (ushort) lw_d;
            new_info->last_access_time = (ushort) lw_t;
            new_info->last_write_date = (ushort) lw_d;
            new_info->last_write_time = (ushort) lw_t;
            new_info->data_size = obj->length;
            new_info->allocation_size = 0;
            new_info->attributes = (ushort) obj->attribute;
            TransCommunicate(setup, parm, data, obj->svr->vc, obj->svr->send, obj->svr->receive);
        }
        ExceptCatch
        {
            TransFreeBuf(setup);
            TransFreeBuf(parm);
            TransFreeBuf(data);
            ExceptRethrow();
        }
        TransFreeBuf(setup);
        TransFreeBuf(parm);
        TransFreeBuf(data);
        obj->dirty = 0;
    }
}

static smb_handle_t NTCreate(smb_obj_t obj)
{
    smb_handle_t h;
    data_t mb;
    unsigned char vwv[2*24];
    short len;

    mb = VCBuffer(obj->svr->vc);
    MBSetCom(mb, SMBntcreate);
    memset(vwv, 0, 2*24);
    vwv[0] = 0xff; // ANDX command
    len = strlen(obj->path) + strlen(obj->name) + 1;
    vwv[5] = len & 0xff;
    vwv[6] = (len >> 8) & 0xff;
    vwv[15] = 0x00; // Access mask
    vwv[16] = 0x01;
    vwv[17] = 0x10;
    vwv[18] = 0x00;
    vwv[35] = 1; // Create disposition
    vwv[43] = 2; // Impersonation
    MBSetVWV(mb, 24, (ushort *)vwv);
    MBAddRaw(mb, strlen(obj->path), obj->path);
    MBAddRaw(mb, strlen(obj->name) + 1, obj->name);

    obj->svr->send(obj->svr->vc);
    obj->svr->receive(obj->svr->vc);

    MBGetVWV(mb, 4, (ushort *)vwv);
    h = Malloc(sizeof(*h));
    h->svr = obj->svr;
    h->fid = vwv[5] | (vwv[6] << 8);

    return h;
}

static void flush(smb_obj_t obj)
{
    volatile trans_buf_t setup, parm, data;
    set_path_info_t *new_info;

    setup = parm = data = NULL;
    if(obj->dirty)
    {
        ExceptTry
        {
            ExceptTry
            {
                int lw_t, lw_d;

                setup = TransInitBuf(2, 0);
                parm = TransInitBuf(6 + strlen(obj->path) + strlen(obj->name) + 1, 2);
                data = TransInitBuf(22, 0);
                ((short *)setup->buf)[0] = TRANS2_SET_PATH_INFORMATION;
                ((short *)parm->buf)[0] = 0x1;
                ((short *)parm->buf)[1] = 0;
                ((short *)parm->buf)[2] = 0;
                strcpy((char *)parm->buf + 6, obj->path);
                strcat((char *)parm->buf + 6, obj->name);
                new_info = (set_path_info_t *) data->buf;
                DateSMBtoDOS(obj->time, &lw_t, &lw_d);
                new_info->creation_date = (ushort) lw_d;
                new_info->creation_time = (ushort) lw_t;
                new_info->last_access_date = (ushort) lw_d;
                new_info->last_access_time = (ushort) lw_t;
                new_info->last_write_date = (ushort) lw_d;
                new_info->last_write_time = (ushort) lw_t;
                new_info->data_size = obj->length;
                new_info->allocation_size = 0;
                new_info->attributes = (ushort) obj->attribute;
                TransCommunicate(setup, parm, data, obj->svr->vc, obj->svr->send, obj->svr->receive);

            }
            ExceptCatch
            {
               if (obj->svr->dialect >= NT)
               {
                    // Some NASes don't support SET_PATH_INFO so try SET_FILE_INFO instead
                    smb_handle_t h;
                    h = NTCreate(obj);
                    ExceptTry
                    {
                        set_info(obj, h->fid);
                    }
                    ExceptCatch
                    {
                        SMBClose(h);
                        ExceptRethrow();
                    }
                    SMBClose(h);
                }
                else
                {
                    ExceptRethrow();
                }
            }

        }
        ExceptCatch
        {
            TransFreeBuf(setup);
            TransFreeBuf(parm);
            TransFreeBuf(data);
            ExceptRethrow();
        }
        TransFreeBuf(setup);
        TransFreeBuf(parm);
        TransFreeBuf(data);
        obj->dirty = 0;
    }
}

static void disk_info(smb_server_t svr, smb_disc_info_t *dinfo)
{
    volatile trans_buf_t setup, parm, data;
    unsigned int *block;

    setup = parm = data = NULL;
    ExceptTry
    {
        setup = TransInitBuf(2, 0);
        parm = TransInitBuf(4, 0);
        data = TransInitBuf(0, 20);
        ((short *)setup->buf)[0] = TRANS2_QUERY_FS_INFORMATION;
        ((int *)parm->buf)[0] = 0x1;
        TransCommunicate(setup, parm, data, svr->vc, svr->send, svr->receive);
        block = (unsigned int *) data->buf;
//        MemCheck_Printf("block[0] = 0x%08x\n", block[0]);
//        MemCheck_Printf("block[1] = 0x%08x\n", block[1]);
//        MemCheck_Printf("block[2] = 0x%08x\n", block[2]);
//        MemCheck_Printf("block[3] = 0x%08x\n", block[3]);
//        MemCheck_Printf("block[4] = 0x%08x\n\n", block[4]);
        dinfo->total = block[2];
        dinfo->free = block[3];
        dinfo->units = block[1] * (block[4] & 0xffff);
    }
    ExceptCatch
    {
        TransFreeBuf(setup);
        TransFreeBuf(parm);
        TransFreeBuf(data);
        ExceptRethrow();
    }
    TransFreeBuf(setup);
    TransFreeBuf(parm);
    TransFreeBuf(data);
}


smb_server_t LM2Upgrade(smb_server_t svr, int user_level)
{
    svr->send      = send;
    svr->receive   = receive;
    svr->name_map  = name_map;
    svr->smb2ro    = smb2ro;
    svr->ro2smb	   = ro2smb;
    svr->construct = construct;
    svr->dup	   = dup;
    svr->destruct  = destruct;
#ifdef USEFORM
    svr->form	   = smart_form;
    svr->date_time_reversed = -1;
#else
    svr->form      = first;
#endif
    svr->first	   = first;
    svr->next	   = next;
    svr->disk_info = disk_info;
    // Some systems using only share level security (e.g. Win98) do not
    // support the newer set information calls used by flush
    if (user_level)
      svr->flush   = flush;
    return svr;
}

char *LM2ConvertPath(char *s)
{
    return smb2ro(s);
}
