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

/*
 *   Copyright 1996 Warm Silence Software Ltd.  All rights reserved.
 *   Use is subject to license terms.
 */

/*   PHBG 7/10/96: Initial version
 */


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

// #include "MemCheck:MemCheck.h"


#include "swis.h"
#include "kernel.h"
#include "LanMan98BaseLib/error.h"
#include "nodebug.h"
#include "LanMan98BaseLib/memory.h"
#include "LanMan98BaseLib/callback.h"
#include "LanMan98BaseLib/strext.h"
#include "omni_swis.h"
//#include "LanMan98SWIs.h"
#include "LanMan98FSLib/fspatch.h"
#include "showfree.h"
#include "LanMan98FSLib/fsys.h"
#include "mount.h"
#include "netbios.h"
#include "logon.h"
#include "srvsvc.h"
#include "discover.h"
#include "browse.h"
#include "vc.h"
#include "smb.h"
#include "mapping.h"
#include "omniheader.h"
/*
    Module_FullVersion carries the beta label where there is one; the
    version CMunge puts in the header is the number alone.

    CMunge stamps its own Module_Date from the clock it was run under,
    which is not the one the version file records; the definition is
    dropped rather than allowed to clash, since nothing here uses it.
*/
#undef Module_Date
#include "VersionNum"


#if 0
#define NC_ONLY
#endif

#define MIN(x, y) ((x) < (y) ? (x) : (y))

static char user_reg[512 + 16] = "unencrypted  regRO Devs Open Source Edition";

static void add_fs(void *pw)
{
    FsPatchAddFS(pw);
}

static void remove_fs(void)
{
    FsPatchRemoveFS();
}

static void omni_register(void)
{
    _kernel_swi_regs r;
    static char desc[256];

    /* Info box lines are 32 characters; this one is exactly that */
    sprintf(desc, "CIFS client v" Module_FullVersion "\n Gardiner, RISC OS Developments\n%s", user_reg + 16);
    r.r[0] = LanMan98_OmniOp;     /* Client ID            */
    r.r[1] = 0xf; /* Flags: RC_NEEDS_USERID | RC_NEEDS_PASSWD | RC_NEEDS_MOUNTPATH |
                            RC_DOES_FILES | RC_EXTN_CHAR('.') | RC_DOES_PRINT | RC_NEEDS_PRINTPWD */
    r.r[2] = (int) "omni_lm98";   /* Sprite name - 12 max */ 
    r.r[3] = (int) "LanMan98";    /* Title bar - 24 max   */
    r.r[4] = (int) desc;          /* Info box - 3x32 max  */
    r.r[5] = 0;                   /* Site ID (ignored)    */
    r.r[6] = (int) "LanMan98";    /* FilingSystemName     */
    _kernel_swi(Omni_RegisterClient, &r, &r);
    return;
    r.r[0] = LanMan98_LogonOp;
    r.r[1] = 0x5F;
    r.r[3] = (int) "Logon98";
    _kernel_swi(Omni_RegisterClient, &r, &r);
}

static void omni_deregister(void)
{
    _kernel_swi_regs r;

    r.r[0] = LanMan98_OmniOp;
    _kernel_swi(Omni_DeregisterClient, &r, &r);
    r.r[0] = LanMan98_LogonOp;
    _kernel_swi(Omni_DeregisterClient, &r, &r);
}

_kernel_oserror* finalise(int fatal, int podule, void *pw)
{
    CallBacksOff();
    omni_deregister();
    ShowFreeDeReg(FsysFSNumber);
    remove_fs();
    FsysShut();
//    MemCheck_OutputBlocksInfo();
    // ThreadLibraryFinalise();
    MemFinalise();
    return NULL;
}

#ifdef MemCheck_MEMCHECK
/*
#include "tcp.h"
#include "blocked.h"

static int mem_printf(void *reference, const char *format, va_list args)
{
    static tcp_conn_t conn = NULL;
    static char buf[1024];
    int res, time;

    MemCheck_SetChecking(0, 0);
    ExceptTry
    {
        if(conn == NULL)
        {
            conn = TcpCall(TCP_PORT_ANY, 1777, TcpResolve("10.0.0.1"));
            TcpSetNoDelay(conn);
        }
        res = vsprintf(buf, format, args);
        TcpWrite(buf, strlen(buf), conn);
        time = Time() + 100;
        while(Time() - time < 0)
            BlockedWait();
    }
    ExceptCatch
    {
        return 0;
    }
    MemCheck_SetChecking(1, 1);
    return res;
}
*/

static int mem_printf(void *reference, const char *format, va_list args)
{
    FILE *f;
    int n;

    f = fopen("<LanMan98$Dir>.MemCheck", "ab");
    if(f)
    {
        n = vfprintf(f, format, args);
        fclose(f);
    }
    return n;
}

#endif

#ifdef NC_ONLY
static void nc_check(char *mname, int mver)
{
    int mod_num = 0;
    int rom_sect = -1;
    int regs = _IN(0)|_IN(1)|_IN(2)|_OUT(1)|_OUT(2)|_OUT(3)|_OUT(6);
    char *name;
    int ver;

    while(rom_sect == -1 && NULL == _swix(OS_Module, regs, 20, mod_num, rom_sect, &mod_num, &rom_sect, &name, &ver))
    {
        if(strcmp(mname, name) == 0 && mver <= ver)
            return;
    }
    Error("This version of LanMan98 works only with NCs");
}
#endif


_kernel_oserror *initialise(const char *cmd_tail, int podule, void *pw)
{
//    MemCheck_Init();
    // MemCheck_RedirectToFilename("CIFS_Mem");
//    MemCheck_RedirectToFunction(mem_printf, NULL);
//    MemCheck_InterceptSCLStringFunctions();
//    MemCheck_SetQuitting(0, 0);
    // MemCheck_SetQuitting(1, 1);
//    MemCheck_OutputBlocksInfo();
    MemInitialise("LanMan98");
#ifdef NC_ONLY
    ExceptTry
    {
        nc_check("OSVersion", 0x00010600); // 111 aswell
    }
    ExceptCatch
    {
        return ExceptCaught();
    }
#endif
    CallBackInit(pw);
    add_fs(pw);
    ShowFreeReg(FsysFSNumber);
    omni_register();
    MapInit();
    FsysSweepStart();
#ifdef NC_ONLY
    printf("LanMan98 for NC v" Module_FullVersion " (c) Warm Silence Software Ltd.\n(c) RISC OS Developments 2026\nAll rights reserved.\nAuthors: Paul Gardiner, RISC OS Developments\n\nRegistration: %s\n", user_reg+16);
#else
    printf("LanMan98 v" Module_FullVersion " (c) Warm Silence Software Ltd 1996-2019.\n(c) RISC OS Developments 2026\nAll rights reserved.\nAuthors: Paul Gardiner, RISC OS Developments\n\nRegistration: %s\n", user_reg+16);
#endif
    return NULL;
}

void service(int service_number, _kernel_swi_regs *r, void *pw)
{
//    MemCheck_RegisterMiscBlock(r, sizeof(*r));
    switch(service_number)
    {
        case 0x40:
            add_fs(pw);
            break;
        case 0xa0:
            if(r->r[0] == 0)
                omni_register();
            break;
    }
//    MemCheck_UnRegisterMiscBlock(r);
}

static int poll_word;

static void connect(int argc, const char *args)
{
    char buf[512];                      /* a server's full name can be long */
    int i, len;
    char *tok[5];

    if(argc < 3 || argc > 5)
       Error("Wrong number of arguments");
//    MemCheck_RegisterMiscBlock(args, 80);
    for(len = 0; len < sizeof(buf) - 1 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    tok[3] = "";
    tok[4] = "";
    for(i = 0; i < argc; i++)
        tok[i] = strtok(i == 0 ? buf : NULL, " \t");
    FsysMount(tok[0], tok[1], tok[2], tok[3], tok[4]);
    poll_word = 1;
    _swix(Omni_EnumerateMounts, _IN(0)|_IN(1), LanMan98_OmniOp, 1);
//    MemCheck_UnRegisterMiscBlock(args);
}

/*
    Turn a name into something that can be a file name here.

    Share names come from the other end and may hold characters that mean
    something to this filing system, or nothing at all.  Anything that is
    not plainly a letter or a digit becomes an underscore.
*/
static void safe_name(char *out, const char *in, int max)
{
    int i;

    for(i = 0; (i < max - 1) && in[i]; i++)
    {
        char c = in[i];

        if(((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
           ((c >= '0') && (c <= '9')) || (c == '-') || (c == '_'))
            out[i] = c;
        else
            out[i] = '_';
    }
    out[i] = 0;
}

/*
    Find every server on the network, ask each what it shares, and leave an
    icon for each share that can be connected to - one folder per server,
    so two servers sharing a name (a "Public" on each of them, say) each
    get their own icon rather than one silently overwriting the other.
    Each folder also holds an icon to log on with, so that a server which
    says nothing until somebody has logged on is still there to log on to.

    The icon is the same thing the connect window saves when a share is
    mounted by hand: a few lines saying where it is, with the password left
    out.  Double clicking one opens the connect window with everything but
    the password already filled in, which is the whole point.

    A share that already has an icon is left alone.  The one there may have
    a password kept in it, and finding the share again is no reason to
    throw that away.
*/
/*
    Where a share that has merely been found is put.

    Not in !LanMan98.Discs.  That directory is the ones the user keeps -
    named, and possibly with a password in - and writing every share on the
    network into it fills it with machines nobody asked for, on the local
    disc, permanently.  What is found goes in the scrap directory instead,
    is cleared out at the start of each look, and only becomes a kept icon
    if it is double clicked and the connection succeeds.

    The path is left in LanMan98$Found so the front end can open it without
    having to work it out again.  If there is no scrap directory to use,
    the old place is used rather than failing.
*/
static char *found_dir(void)
{
    static char dir[256];
    char *scrap;

    scrap = getenv("Wimp$ScrapDir");
    if((scrap == NULL) || (*scrap == 0) || (strlen(scrap) + 10 > sizeof(dir)))
    {
        strcpy(dir, "LM98:Discs");
        return dir;
    }
    sprintf(dir, "%s.LanMan98", scrap);
    return dir;
}

static void clear_found(const char *dir)
{
    char cmd[300];

    sprintf(cmd, "CDir %.250s", dir);
    _kernel_oscli(cmd);
    /*
        An empty directory makes Wipe complain that there is nothing to
        delete, which is not a problem worth reporting - the error is
        deliberately dropped.
    */
    sprintf(cmd, "Wipe %.240s.* ~C ~V F", dir);
    _kernel_oscli(cmd);
}

/*
    Where one server's finds go, inside the directory found_dir() returns.

    Two servers can export a share of the same name.  Writing every found
    share into one directory named only after the share meant the second
    server to answer overwrote the icon the first had just been given -
    silently, since fopen("w") does not complain about a file that was
    already there.  Each server gets its own folder here instead, so
    nothing is lost and the folder a share sits in says which server it
    came from.

    Every server that answers gets one, whether or not it will say what it
    shares: most say nothing until somebody has logged on, and a server
    missing from the window is one that cannot be logged on to at all.
*/
static void server_dir(char *out, const char *dir, const char *name,
                       const char *addr)
{
    char leaf[64];
    char cmd[300];

    safe_name(leaf, name ? name : "", sizeof(leaf));
    if(leaf[0] == 0)
        safe_name(leaf, addr, sizeof(leaf));
    sprintf(out, "%.180s.%.60s", dir, leaf);
    sprintf(cmd, "CDir %.250s", out);
    _kernel_oscli(cmd);
}

/*
    Whether a share already kept in Discs is this one.

    The name alone used to be asked: if anything called "Public" was
    already kept, a share called "Public" was not offered again, from any
    server.  That is right when it is the same share seen twice and wrong
    when two servers happen to use the same name.  What was kept is read
    back and its server compared - by address, or by name for one that was
    typed in by hand - so only the same share on the same machine counts.
*/
#define KEPT_NONE       0
#define KEPT_HERE       1
#define KEPT_ELSEWHERE  2

static int kept(const char *leaf, const char *addr, const char *name)
{
    char fname[256];
    char buf[256];
    FILE *fp;
    int where;

    sprintf(fname, "LM98:Discs.%.200s", leaf);
    fp = fopen(fname, "r");
    if(fp == NULL)
        return KEPT_NONE;

    /* One that does not say which server it is for is not risked */
    where = KEPT_ELSEWHERE;
    while(fgets(buf, sizeof(buf), fp))
    {
        if(strncmp(buf, "Server:", 7) == 0)
        {
            char *s = buf + 7;
            char *nl;

            s += strspn(s, " \t");
            nl = strchr(s, '\n');
            if(nl) *nl = 0;
            if((ci_strcmp(s, (char *) addr) == 0) ||
               (name && (ci_strcmp(s, (char *) name) == 0)))
                where = KEPT_HERE;
            break;
        }
    }
    fclose(fp);
    return where;
}

/*
    The names shares have been offered under in this look, from any server.

    A found icon that is double clicked and connects is kept as Discs.<its
    name>.  Two servers both offering "Public" would both be kept as
    Discs.Public, the second writing over the first - password and all.  So
    a name another server has already been offered under, or that is kept
    for another server, has the server's name added: Public for the first,
    Public_OTHERSERVER for the next.
*/
typedef struct offered_s
{
    struct offered_s *next;
    char leaf[64];
} *offered_t;

static int was_offered(offered_t list, const char *leaf)
{
    for(; list; list = list->next)
        if(ci_strcmp(list->leaf, (char *) leaf) == 0)
            return 1;
    return 0;
}

/* Failing to remember one costs only the renaming above, not the look */
static offered_t remember_offered(offered_t list, const char *leaf)
{
    offered_t o = Malloc0(sizeof(*o));

    if(o == NULL)
        return list;
    strcpy(o->leaf, leaf);
    o->next = list;
    return o;
}

static void forget_offered(offered_t list)
{
    while(list)
    {
        offered_t next = list->next;

        Free(list);
        list = next;
    }
}

/*
    Ask the network every way this client knows.

    Three questions, and no two of them find the same machines.  The
    multicast one is answered by Samba, a Mac and most NAS boxes; the
    WS-Discovery one by Windows, which stopped answering anything else when
    the Computer Browser service was retired; and the browse list is the
    only one of the three that crosses a router, because it is a question
    put to one machine rather than shouted at the local wire.

    The browse list is also the only one that costs a connection and a
    logon, and the only one that needs SMB1 at the other end, so it is
    asked last and its failure is not the command's failure: a network with
    no master browser is the ordinary case now, not a fault.

    LanMan98$NoMDNS, LanMan98$NoWSD and LanMan98$NoBrowse each switch one
    off, for a network where one of them misbehaves or simply wastes time.
*/
static found_t find_servers(int wait_cs, char *user, char *passwd)
{
    found_t head;
    found_t volatile browsed;

    head = NULL;
    if(getenv("LanMan98$NoMDNS") == NULL && getenv("LanMan98$NoWSD") == NULL)
        head = DiscoverBoth(wait_cs);       /* both at once, one window */
    else if(getenv("LanMan98$NoMDNS") == NULL)
        DiscoverMerge(&head, DiscoverServers(wait_cs));
    else if(getenv("LanMan98$NoWSD") == NULL)
        DiscoverMerge(&head, DiscoverWSD(wait_cs));
    if(getenv("LanMan98$NoBrowse") == NULL)
    {
        browsed = NULL;
        ExceptTry
        {
            browsed = BrowseServers(NULL, user, passwd);
        }
        ExceptCatch
        {
            browsed = NULL;
        }
        if(browsed)
            DiscoverMerge(&head, (found_t) browsed);
    }
    return head;
}

/*
    The words of a command line.  A "quoted string" is one word, so that a
    name or a password with a space in it can be given, and "" is an empty
    one.  Up to max words are split out in place; how many is returned.
*/
static int split_words(char *buf, char **word, int max)
{
    int n;
    char *p;

    n = 0;
    p = buf;
    while(n < max)
    {
        while((*p == ' ') || (*p == '\t'))
            p++;
        if(*p == 0)
            break;
        if(*p == '"')
        {
            word[n++] = ++p;
            while(*p && (*p != '"'))
                p++;
        }
        else
        {
            word[n++] = p;
            while(*p && (*p != ' ') && (*p != '\t'))
                p++;
        }
        if(*p)
            *p++ = 0;
    }
    return n;
}

/*
    Leave an icon: the few lines the connect window saves, saying where a
    share is.  One with no share in it is an icon for logging on to the
    server rather than for any one of its shares.  A user name that is known
    is filled in; the password is never written, only asked for.
*/
static int leave_icon(const char *fname, const char *addr, const char *share,
                      const char *user)
{
    char cmd[300];
    FILE *fp;

    fp = fopen(fname, "w");
    if(fp == NULL)
        return 0;
    fprintf(fp, "Server: %s\n", addr);
    fprintf(fp, "Share: %s\n", share);
    if(user && *user)
        fprintf(fp, "User: %s\n", user);
    fprintf(fp, "Password: *****\n");
    fclose(fp);
    sprintf(cmd, "SetType %.250s &069", fname);
    _kernel_oscli(cmd);
    return 1;
}

/*
    Leave an icon in sdir for each share in the list that holds files and is
    meant to be seen, saying which as it goes.  How many were left is
    returned.

    The log on icon is called LogOn, and offered starts out holding that
    name, so a share that happens to be called LogOn is renamed out of its
    way like any other clash.
*/
static int leave_share_icons(const char *sdir, share_t list, const char *name,
                             const char *addr, const char *user,
                             offered_t *offered)
{
    char fname[256];
    char leaf[64];
    char plain[64];
    share_t sh;
    int made, k;

    made = 0;
    for(sh = list; sh; sh = sh->next)
    {
        /* Only the ones that hold files, and only the ones the server
           means to be seen */
        if((sh->type & 0x0F) != SHARE_DISK) continue;
        if(sh->type & SHARE_HIDDEN) continue;
        /* A name ending in $ is one the server means to be typed, not seen:
           print$, C$, ADMIN$.  Samba reports them as ordinary discs. */
        if(sh->name[0] && sh->name[strlen(sh->name) - 1] == '$') continue;

        safe_name(leaf, sh->name, sizeof(leaf));
        if(leaf[0] == 0) continue;
        /*
            A share already kept in Discs is left alone and not offered
            again: it may have a password in it, and finding it a second
            time is no reason to throw that away.
        */
        k = kept(leaf, addr, name);
        if(k == KEPT_HERE)
        {
            printf("   %-20s already kept\n", sh->name);
            continue;
        }
        strcpy(plain, leaf);
        if((k == KEPT_ELSEWHERE) || was_offered(*offered, plain))
        {
            char srv[64];

            safe_name(srv, name ? name : "", sizeof(srv));
            if(srv[0] == 0)
                safe_name(srv, addr, sizeof(srv));
            sprintf(leaf, "%.31s_%.31s", plain, srv);
            if(kept(leaf, addr, name) == KEPT_HERE)
            {
                printf("   %-20s already kept\n", sh->name);
                continue;
            }
        }
        *offered = remember_offered(*offered, plain);

        sprintf(fname, "%.180s.%.60s", sdir, leaf);
        if(!leave_icon(fname, addr, sh->name, user))
        {
            printf("   %-20s could not be written\n", sh->name);
            continue;
        }
        printf("   %-20s %s\n", sh->name,
               (sh->remark && *sh->remark) ? sh->remark : "");
        made++;
    }
    return made;
}

/*
    Log on to one server and leave what it shares in its folder.

    This is what the front end asks for when Connect is pressed with no
    share named - usually from a server's LogOn icon.  The server is asked
    with the name and password just given, and without the hurry of a look
    at the whole network.  Only once it has answered is its folder emptied
    and filled again, so a mistyped password loses nothing.  The folder is
    left in LanMan98$FoundServer for the front end to open.

    -into names the folder, as the leaf the LogOn icon was in.  It is only
    ever a leaf: a path would let the command empty any directory at all.
*/
static void discover_server(char *server, char *into, char *user,
                            char *password)
{
    char sdir[256];
    char fname[300];
    char cmd[300];
    char *dir, *addr, *p;
    smb_server_t svr;
    share_t list;
    offered_t offered;
    int made;

    if(into)
    {
        if(*into == 0)
            Error("Not a folder of servers found");
        for(p = into; *p; p++)
            if(!isalnum((unsigned char) *p) && (*p != '_') && (*p != '-'))
                Error("Not a folder of servers found: %.60s", into);
    }

    addr = NetBIOSResolve(server);
    svr = SMBConnectRAP(addr, SMB_PORT_AUTO, server, user, password);
    list = NULL;
    ExceptTry
    {
        list = SRVSVCEnumShares((smb2_conn_t) SMBServerSMB2(svr), server);
    }
    ExceptCatch
    {
        SMBDropServer(svr);
        ExceptRethrow();
    }
    SMBDropServer(svr);

    dir = found_dir();
    sprintf(cmd, "CDir %.250s", dir);
    _kernel_oscli(cmd);
    sprintf(cmd, "Set LanMan98$Found %.230s", dir);
    _kernel_oscli(cmd);
    if(into)
    {
        sprintf(sdir, "%.180s.%.60s", dir, into);
        sprintf(cmd, "CDir %.250s", sdir);
        _kernel_oscli(cmd);
    }
    else
    {
        server_dir(sdir, dir, server, server);
    }
    /* Emptying an empty folder is complained about, and is not a problem */
    sprintf(cmd, "Wipe %.240s.* ~C ~V F", sdir);
    _kernel_oscli(cmd);
    sprintf(cmd, "Set LanMan98$FoundServer %.230s", sdir);
    _kernel_oscli(cmd);

    printf("%s\n", server);
    sprintf(fname, "%.180s.LogOn", sdir);
    leave_icon(fname, server, "", user);
    offered = remember_offered(NULL, "LogOn");
    made = leave_share_icons(sdir, list, server, server, user, &offered);
    forget_offered(offered);
    SRVSVCFreeShares(list);
    printf("\n%d share%s found\n", made, (made == 1) ? "" : "s");
}

/*
    Whether a name is one a machine could be asked for by: letters, digits,
    hyphens, underscores and dots.  What a Mac calls itself in its
    announcements ("Martin's Mac mini") is for showing, not for resolving.
*/
static int resolvable_name(const char *s)
{
    if((s == NULL) || (*s == 0))
        return 0;
    for(; *s; s++)
        if(!isalnum((unsigned char) *s) && (*s != '-') && (*s != '_') &&
           (*s != '.'))
            return 0;
    return 1;
}

/*
    Connect to a server found on the network, by the address it was found at
    and by its name, the way a Windows machine or a NAS reaches another one.

    Found by name alone - from the browse list, or a WS-Discovery answer that
    gave a host name - the name is resolved every way the client knows rather
    than by the naming service alone, and the answer something is actually
    listening at is used.  Found at an address that does not answer, the
    machine is tried by its name as well, in case it has moved; if that
    fails too, what is reported is why the address did not answer.

    A name that worked where the address did not replaces the address in
    f->addr, so the icons left for the machine name it rather than a number
    it no longer has.
*/
static smb_server_t connect_found(found_t f, char *user, char *password)
{
    _kernel_oserror first;
    char *volatile addr;
    smb_server_t volatile svr;
    int volatile failed;
    tcp_port_t port;
    char *name;

    port = f->port ? f->port : SMB_PORT_AUTO;
    svr = NULL;
    failed = 0;
    name = f->addr;
    if(NetBIOSIsAddress(f->addr))
    {
        ExceptTry
        {
            svr = SMBConnectRAP(f->addr, port, f->name, user, password);
        }
        ExceptCatch
        {
            _kernel_oserror *why = ExceptCaught();

            if((why == NULL) || (why->errnum != ERR_SERVER_UNREACHABLE) ||
               !resolvable_name(f->name) || (ci_strcmp(f->name, f->addr) == 0))
                ExceptRethrow();
            first = *why;
            failed = 1;
        }
        if(!failed)
            return svr;
        name = f->name;
    }

    /* Resolved once per look: a second attempt on the same server, with
       no name and no password, need not ask the network again */
    addr = NULL;
    ExceptTry
    {
        if(f->resolved[0])
            addr = strdup(f->resolved);
        else
        {
            addr = NetBIOSResolve(name);
            if(NetBIOSIsAddress((char *) addr) &&
               (strlen((char *) addr) < sizeof(f->resolved)))
                strcpy(f->resolved, (char *) addr);
        }
        svr = SMBConnectRAP((char *) addr, port, f->name, user, password);
    }
    ExceptCatch
    {
        if(addr)
            Free((char *) addr);
        if(failed)
            ErrorErr(&first);
        ExceptRethrow();
    }
    Free((char *) addr);
    if(failed && (strlen(name) < sizeof(f->addr)))
        strcpy(f->addr, name);
    return svr;
}

/*
    Look up every machine that was found by name alone, and drop any that
    turns out to be one already on the list under its address.

    The browse list deals in names and the announcements in addresses, so a
    machine that answers both is found twice - "MACMINI" and "Martin's Mac
    mini", say - and got two folders.  Resolving the names first, before any
    folder is made, lets the two be told apart from two machines.  The one
    kept is the one with the address; it takes the other's name where its
    own was only the address.
*/
static void merge_by_address(found_t *head)
{
    found_t f, t, prev;
    char *addr;

    for(f = *head; f; f = f->next)
    {
        if(NetBIOSIsAddress(f->addr) || !resolvable_name(f->name))
            continue;
        addr = NetBIOSResolve(f->name);
        if(NetBIOSIsAddress(addr) && (strlen(addr) < sizeof(f->resolved)))
            strcpy(f->resolved, addr);
        Free(addr);
    }

    prev = NULL;
    f = *head;
    while(f)
    {
        found_t next = f->next;

        t = NULL;
        if(f->resolved[0])
            for(t = *head; t; t = t->next)
                if((t != f) &&
                   ((ci_strcmp(t->addr, f->resolved) == 0) ||
                    (ci_strcmp(t->resolved, f->resolved) == 0)))
                    break;
        if(t)
        {
            if(ci_strcmp(t->name, t->addr) == 0)
            {
                Free(t->name);
                t->name = f->name;
                f->name = NULL;
            }
            if(t->port == 0)
                t->port = f->port;
            if(prev)
                prev->next = next;
            else
                *head = next;
            f->next = NULL;
            DiscoverFree(f);
        }
        else
        {
            prev = f;
        }
        f = next;
    }
}

static void discover(int argc, const char *args)
{
    char buf[256];
    char *word[8];
    char sdir[256];
    char fname[300];
    char *server, *into, *user, *password;
    char *dir;
    found_t servers, f;
    share_t list;
    offered_t offered;
    int i, n, len, made, seen;

    for(len = 0; len < 255 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    n = split_words(buf, word, 8);
    server = into = user = password = NULL;
    for(i = 0; i < n; i++)
    {
        if((ci_strcmp(word[i], "-server") == 0) && (i + 1 < n))
            server = word[++i];
        else if((ci_strcmp(word[i], "-into") == 0) && (i + 1 < n))
            into = word[++i];
        else if(user == NULL)
            user = word[i];
        else if(password == NULL)
            password = word[i];
        else
            Error("Wrong number of arguments");
    }

    if(server)
    {
        discover_server(server, into,
                        user ? user : LoggedOnUser(server),
                        password ? password : LoggedOnPassword(server));
        return;
    }
    if(into)
        Error("-into is only for use with -server");

    dir = found_dir();
    clear_found(dir);
    sprintf(fname, "Set LanMan98$Found %.230s", dir);
    _kernel_oscli(fname);

    printf("Looking for servers...\n");
    servers = find_servers(300, user ? user : "", password ? password : "");
    if(servers == NULL)
    {
        printf("Nothing on this network answered any of the three ways of "
               "asking\n");
        return;
    }

    merge_by_address(&servers);

    made = 0;
    seen = 0;
    offered = remember_offered(NULL, "LogOn");
    for(f = servers; f; f = f->next)
    {
        smb_server_t svr;
        char *u;

        seen++;
        printf("%s (%s)\n", f->name, f->addr);
        u = user ? user : LoggedOnUser(f->name);

        /*
            Every server that answered gets a folder and an icon to log on
            with, whether or not it goes on to say what it shares.
        */
        server_dir(sdir, dir, f->name, f->addr);
        sprintf(fname, "%.180s.LogOn", sdir);
        leave_icon(fname, f->addr, "", u);

        list = NULL;
        svr = NULL;
        ExceptTry
        {
            /*
                The port came back with the announcement, so there is no
                need to try one and then the other, and no need to wait
                long: a machine that will not answer promptly here is one
                to move past rather than one to sit on.
            */
            VCImpatient(200);
            svr = connect_found(f, u,
                                password ? password : LoggedOnPassword(f->name));
            list = SRVSVCEnumShares((smb2_conn_t) SMBServerSMB2(svr), f->name);
        }
        ExceptCatch
        {
            _kernel_oserror *why = ExceptCaught();

            /* One server that will not say is not a reason to stop: the
               next one may, and the list is worth having either way. */
            VCImpatient(0);
            if(svr) { SMBDropServer(svr); svr = NULL; }

            /*
                A machine nobody is logged on to usually still says what it
                shares if it is asked without a name at all - which is how
                a share list is got from a machine one has no account on.
                Worth one try before giving up on it, but only where the
                name was the thing it objected to.
            */
            if(why && (why->errnum == 0x20002))
            {
                ExceptTry
                {
                    VCImpatient(200);
                    svr = connect_found(f, "", "");
                    list = SRVSVCEnumShares((smb2_conn_t) SMBServerSMB2(svr),
                                            f->name);
                }
                ExceptCatch
                {
                    VCImpatient(0);
                    if(svr) SMBDropServer(svr);
                    printf("   (refused the name and password, and would "
                           "not say without one)\n");
                    continue;
                }
                VCImpatient(0);
            }
            else
            {
                printf("   (%s)\n",
                       (why && why->errmess[0]) ? why->errmess
                                                : "would not say what it shares");
                continue;
            }
        }
        VCImpatient(0);
        SMBDropServer(svr);

        /* Reached by its name instead of its address: say so on the icon */
        leave_icon(fname, f->addr, "", u);
        made += leave_share_icons(sdir, list, f->name, f->addr, u, &offered);
        SRVSVCFreeShares(list);
    }
    VCImpatient(0);
    DiscoverFree(servers);
    forget_offered(offered);
    printf("\n%d server%s, %d share%s found\n",
           seen, (seen == 1) ? "" : "s", made, (made == 1) ? "" : "s");
    printf("Double click a share to connect to it, or LogOn to log on and "
           "list what else the server has\n");
}

/*
    List the servers that announce themselves on this network.
*/
static void servers(int argc, const char *args)
{
    char buf[32];
    found_t list, f;
    int wait, len;

    wait = 300;                         /* three seconds by default */
    if(argc >= 1)
    {
        for(len = 0; len < 31 && !iscntrl(args[len]); len++)
            buf[len] = args[len];
        buf[len] = 0;
        wait = atoi(buf) * 100;
        if(wait < 50) wait = 50;
        if(wait > 3000) wait = 3000;
    }

    list = find_servers(wait, "", "");
    if(list == NULL)
    {
        printf("Nothing on this network answered any of the three ways of "
               "asking\n");
        return;
    }
    for(f = list; f; f = f->next)
        printf("%-24s %-16s port %d\n", f->name, f->addr, f->port);
    DiscoverFree(list);
}

/*
    List what a server shares.

    The connection is made to IPC$, which is where the call that asks lives,
    and dropped again afterwards - this looks, it does not mount.
*/
static void shares(int argc, const char *args)
{
    char buf[160];
    char *tok[3];
    char *addr;
    smb_server_t svr;
    share_t list, sh;
    int i, len;

    if(argc < 1 || argc > 3)
        Error("Wrong number of arguments");
    for(len = 0; len < 159 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    tok[1] = "";
    tok[2] = "";
    for(i = 0; i < argc; i++)
        tok[i] = strtok(i == 0 ? buf : NULL, " \t");

    addr = NetBIOSResolve(tok[0]);
    svr = SMBConnectRAP(addr, SMB_PORT_AUTO, tok[0],
                        (argc > 1) ? tok[1] : LoggedOnUser(tok[0]),
                        (argc > 2) ? tok[2] : LoggedOnPassword(tok[0]));
    list = NULL;
    ExceptTry
    {
        list = SRVSVCEnumShares((smb2_conn_t) SMBServerSMB2(svr), tok[0]);
    }
    ExceptCatch
    {
        SMBDropServer(svr);
        ExceptRethrow();
    }
    SMBDropServer(svr);

    if(list == NULL)
    {
        printf("%s offers nothing that can be connected to\n", tok[0]);
        return;
    }
    for(sh = list; sh; sh = sh->next)
    {
        const char *kind;

        switch(sh->type & 0x0F)
        {
            case SHARE_DISK:    kind = "disc";    break;
            case SHARE_PRINTER: kind = "printer"; break;
            case SHARE_DEVICE:  kind = "device";  break;
            case SHARE_IPC:     kind = "pipe";    break;
            default:            kind = "?";       break;
        }
        /* The top bit marks the ones a server does not mean to advertise */
        printf("%-16s %-8s%s%s%s\n", sh->name, kind,
               (sh->type & SHARE_HIDDEN) ? " (hidden)" : "",
               (sh->remark && *sh->remark) ? "  " : "",
               (sh->remark && *sh->remark) ? sh->remark : "");
    }
    SRVSVCFreeShares(list);
}

static void disconnect(int argc, const char *args)
{
    char buf[80];
    int len;
    mount_t mt;

    if(argc != 1)
       Error("Wrong number of arguments");
//    MemCheck_RegisterMiscBlock(args, 80);
    for(len = 0; len < 79 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    mt = FsysFindMount(buf);
    if(mt == NULL)
        Error("Disc not mounted");
    FsysDismount(mt);
    poll_word = 1;
    _swix(Omni_EnumerateMounts, _IN(0)|_IN(1), LanMan98_OmniOp, 1);
//    MemCheck_UnRegisterMiscBlock(args);
}

static void logon(int argc, const char *args)
{
    char buf[80];
    int i, len;
    char *tok[3];

    if(argc < 2 || argc > 3)
       Error("Wrong number of arguments");
//    MemCheck_RegisterMiscBlock(args, 80);
    for(len = 0; len < 79 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    tok[2] = "";
    for(i = 0; i < argc; i++)
        tok[i] = strtok(i == 0 ? buf : NULL, " \t");
    LogOn(tok[0], tok[1], tok[2]);
//    MemCheck_UnRegisterMiscBlock(args);
}

static void logoff(int argc, const char *args)
{
    LogOff();
}

static char *byte_str(int64 *x)
{
    static char buf[10];

    if(x->high >> 13)
        sprintf(buf, "%dTB", x->high >> 8);
    else if(x->high >> 3)
        sprintf(buf, "%dGB", (x->high << 2) | (x->low >> 30));
    else if(x->high || (x->low >> 25))
        sprintf(buf, "%dMB", (x->high << 12) | (x->low >> 20));
    else if(x->low >> 15)
        sprintf(buf, "%dKB", x->low >> 10);
    else
        sprintf(buf, "%dB", x->low);
    return buf;
}

static void display_free(int argc, const char *args)
{
    char buf[80];
    int len;
    int64 total, free;

    if(argc != 1)
       Error("Disc name not specified");
//    MemCheck_RegisterMiscBlock(args, 80);
    for(len = 0; len < 79 && !iscntrl(args[len]); len++)
        buf[len] = args[len];
    buf[len] = 0;
    FsysSpace(FsysImageFromDiscName(buf, NULL), &total, &free);
    if(total.high || free.high)
    {
        printf("Bytes free  &%04X%08X = %7s\n", free.high, free.low, byte_str(&free));
        printf("Bytes total &%04X%08X = %7s\n", total.high, total.low, byte_str(&total));
    }
    else
    {
        printf("Bytes free  &%08X = %7s\n", free.low, byte_str(&free));
        printf("Bytes total &%08X = %7s\n", total.low, byte_str(&total));
    }
//    MemCheck_UnRegisterMiscBlock(args);
}

static void enumerate_mounts(char **bufp, int size, int *indexp)
{
    mount_t mt;
    int len;

    while((mt = FsysNthMount(*indexp)) != NULL && (len = ((strlen(FsysMountName(mt)) + 12) & ~3)) <= size)
    {
        /* The first word was historically unused by this private frontend
           enumeration. Carry the backend kind so a restarted Filer can
           restore the correct icon without probing the remote service. */
        ((int *) *bufp)[0] = FsysMountIsWebDAV(mt);
        ((int *) *bufp)[1] = (int) mt;
        strcpy(*bufp + 8, FsysMountName(mt));
        *bufp += len;
        size -= len;
        (*indexp)++;
    }
    if(mt == NULL)
        *indexp = 0;
}

_kernel_oserror *command(const char *arg_string, int argc, int cmd_no, void *pw)
{
    _kernel_swi_regs r;
    _kernel_oserror *err;

    /*
        Several of these talk to the network, and the sweep's callback can
        fire in the middle of one.  Keep it out of the way until the command
        has finished, whether it finished well or not.
    */
    err = NULL;
    FsysSweepHold(1);
    ExceptTry
    {
        switch(cmd_no)
        {
            case 0:
                r.r[0] = 14;
                r.r[1] = 0x76;
                _kernel_swi(OS_FSControl, &r, &r);
                break;
            case 1:
                connect(argc, arg_string);
                break;
            case 2:
                disconnect(argc, arg_string);
                break;
            case 3:
                discover(argc, arg_string);
                break;
            case 4:
                servers(argc, arg_string);
                break;
            case 5:
                shares(argc, arg_string);
                break;
            case 6:
                logon(argc, arg_string);
                break;
            case 7:
                logoff(argc, arg_string);
                break;
            case 8:
                display_free(argc, arg_string);
                break;
            case 9:
                MapCommand(arg_string);
                break;
            default:
                Error("Unknown command");
        }
    }
    ExceptCatch
    {
        err = ExceptCaught();
    }
    FsysSweepHold(0);
    return err;
}

/*
#include <stdarg.h>

static int var_printf(char *fmt, ...)
{
    static int i = 0;
    char buf1[32];
    char buf2[64];
    va_list args;
    int ret;

    va_start(args, fmt);
    ret = vsprintf(buf2, fmt, args);
    va_end(args);
    sprintf(buf1, "LanMan98$XXX%03d", i++);
    VarSet(buf1, buf2);
    return ret;
}

static void show(int reg, int val)
{
    if(val == 0)
    {
        var_printf("R%d is NULL", reg);
    }
    else if(strlen((char *) val) > 20)
    {
        var_printf("R%d too long", reg);
    }
    else
    {
        var_printf("R%d = %s", reg, (char *) val);
    }
}
*/

static void omni_op(int op, _kernel_swi_regs *r)
{
    mount_t mt;
    char *name, buf[64];

    switch(op)
    {
        case 0:
            ExceptTry
            {
//                MemCheck_RegisterMiscBlock_String((char *) r->r[1]);
//                MemCheck_RegisterMiscBlock_String((char *) r->r[2]);
//                MemCheck_RegisterMiscBlock_String((char *) r->r[3]);
//                MemCheck_RegisterMiscBlock_String((char *) r->r[4]);
//                MemCheck_RegisterMiscBlock_String((char *) r->r[5]);
                mt = FsysMount((char *) r->r[4],
                               (char *) r->r[1],
                     	       (char *) r->r[5],
                      	       (char *) r->r[2],
                      	       (char *) r->r[3]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[1]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[2]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[3]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[4]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[5]);
                r->r[1] = (int) mt;
                poll_word = 1;
                _swix(Omni_EnumerateMounts, _IN(0)|_IN(1), LanMan98_OmniOp, 1);
            }
            ExceptCatch
            {
//                MemCheck_UnRegisterMiscBlock((char *) r->r[1]);
//               MemCheck_UnRegisterMiscBlock((char *) r->r[2]);
//               MemCheck_UnRegisterMiscBlock((char *) r->r[3]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[4]);
//                MemCheck_UnRegisterMiscBlock((char *) r->r[5]);
                r->r[1] = 0;
                ExceptRethrow();
            }
            break;
        case 1:
            FsysDismount((mount_t) r->r[1]);
            MemMinimalise();
            poll_word = 1;
            _swix(Omni_EnumerateMounts, _IN(0)|_IN(1), LanMan98_OmniOp, 1);
            break;
        case 5:
            enumerate_mounts((char **) &r->r[1], r->r[2], &r->r[3]);
            break;
        case 6:
        case 7:
            name = FsysMountName((mount_t) r->r[1]);
            if(name)
            {
                sprintf(buf, "Filer_OpenDir LanMan98::%s.$", name);
                _kernel_oscli(buf);
            }
            break;
        case 8:
            Error("Not supported");
            r->r[1] = LoggedOn() ? 0x9 : 0xF;
            break;
        case 9:
            mt = (mount_t) r->r[1];
            r->r[1] = (int) FsysMountServer(mt);
            r->r[2] = (int) FsysMountUser(mt);
            r->r[3] = (int) FsysMountName(mt);
            r->r[4] = (int) FsysMountPath(mt);
            r->r[5] = 0;
            r->r[6] = 0;
            break;
        case 10:
            /*
                r1 mount, r2 caller buffer, r3 its length.  The text is
                the share's negotiated dialect, signing and encryption.
            */
            FsysMountStatus((mount_t) r->r[1], (char *) r->r[2], r->r[3]);
            break;
        default:
            ErrorErr(_kernel_swi(LanMan_OmniOp, r, r));
    }
}

static void logon_op(int op, _kernel_swi_regs *r)
{
    switch(op)
    {
        case 0:
            LogOn((char *) r->r[1], (char *) r->r[2], (char *) r->r[3]);
            r->r[0] = -1;
            break;
        /*
        case 3:
            if(r->r[3] == 0)
            {
                ((int *) r->r[1])[0] = 0;
                strcpy((char *)(r->r[1] + 4), "Logon98");
                r->r[1] += 12;
                strcpy((char *)(r->r[1]), "logon98");
                r->r[1] += 8;
                strcpy((char *)(r->r[1]), "Logon Network");
                r->r[1] += 16;
            }
            r->r[3] = 0;
            break;
        */
        case 17:
            LogOff();
            break;
    }
}

static void sub64(int64 *res, int64 *op1, int64 *op2)
{
    res->high = op1->high - op2->high;
    res->low = op1->low - op2->low;
    if(op2->low > op1->low)
        res->high--;
}

static void free_op(_kernel_swi_regs *r)
{
    static int len;

    switch(r->r[0])
    {
        case 0:
            break;
        case 1:
            strcpy((char*) r->r[2], (char*) r->r[3]);
            r->r[0] = len = strlen((char*) r->r[2]);
            break;
        case 2:
            {
                fsys_image_t img;
                int64 total, free, used;
                int *buf;

                img = FsysImageFromDiscName((char *) r->r[3], NULL);
                if(img == NULL)
                    Error("Disc not found...");
                FsysSpace(img, &total, &free);
                sub64(&used, &total, &free);
                buf = (int *) r->r[2];
                buf[0] = total.high ? 0xffffffff : total.low;
                buf[1] = free.high ? 0xffffffff : free.low;
                buf[2] = used.high ? 0xffffffff : used.low;
            }
            break;
        case 3:
            Error("Impossible");
            break;
        case 4:
            {
                fsys_image_t img;
                int64 total, free, used;
                int64 *buf;

                img = FsysImageFromDiscName((char *) r->r[3], NULL);
                if(img == NULL)
                    Error("Disc not found...");
                FsysSpace(img, &total, &free);
                sub64(&used, &total, &free);
                buf = (int64 *) r->r[2];
                buf[0] = total;
                buf[1] = free;
                buf[2] = used;
                r->r[0] = 0;
            }
            break;
        default:
            Error("Bad \"Free\" reason code");
    }
}

_kernel_oserror *swi_handler(int swi_no, _kernel_swi_regs *r, void *pw)
{
    static _kernel_oserror error_ill_swi = {0x10000, "Illegal SWI"};
    _kernel_oserror *err;

//    MemCheck_RegisterMiscBlock(r, sizeof(*r));
    err = NULL;
    FsysSweepHold(1);
    ExceptTry
    {
        switch(swi_no + LanMan98_OmniOp)
        {
            case LanMan98_OmniOp:
                omni_op(r->r[0], r);
                break;
            case LanMan98_Bad1:
                free_op(r);
                break;
            case LanMan98_LogonOp:
                logon_op(r->r[0], r);
                break;
            case LanMan98_PollWord:
                poll_word = 0;
                r->r[0] = (int) &poll_word;
                break;
            default:
                err = &error_ill_swi;
        }
    }
    ExceptCatch
    {
        err = ExceptCaught();
        /*
            Numberless, as every error out of here has always been - except
            the one the front end has to be able to recognise.  See h.vc.
        */
        if(err->errnum != ERR_SERVER_UNREACHABLE)
            err->errnum = 0;
    }
    FsysSweepHold(0);
//    MemCheck_UnRegisterMiscBlock(r);
    return err;
}
