/*
 * mars-nwe-dosutils - NetWare/DOS utility tools.
 *
 * Copyright (C) 2026 Mario Fetka
 * Copyright (C) 1993,1996 Martin Stover, Marburg, Germany
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Purpose: Semantically named NCP API helper implementation used by the NetWare DOS tools.
 * Depends on: net.h, ncpapi.h, ncpcall.c requester/transport helpers, ncp.h requester assembly declarations, and netcall.c for shared requester state. This file contains the protocol API layer above the low-level requester transport helpers.
 */

#include "net.h"
#include "ncpapi.h"

/*
 * Legacy bindery/login NCP API wrappers.
 *
 * These ncp16/ncp17/ncp14 wrappers used to live in ncpcall.c.  They are kept
 * together with the other ncpXX_YY_* APIs here so all protocol wrappers live
 * in one NCP API layer.
 */

/* ----------------  0x16 ----------------------------------- */
/*
 * ncp16_02_get_directory_entry
 *
 * Purpose:
 *   Looks up a directory entry relative to a NetWare directory handle and
 *   returns the canonical subdirectory name, creation timestamp, owner object
 *   ID, subdirectory number and maximum rights mask.
 *
 * NCP:
 *   Function 0x16, subfunction 0x02. MARS-NWE admin documents this old
 *   filesystem call as Scan Directory Information / Get Directory Entry
 *   (F216/02).
 *
 * Requester path:
 *   Net_Call(0xE200), with a 16-bit request and reply length prefix.
 *
 * Returns:
 *   Maximum rights mask on success, or -1 when the requester/NCP call fails.
 */
int ncp16_02_get_directory_entry(int dirhandle,
              uint8  *path,
              int    *sub_dir,
              uint8  *resultpath,
              uint32 *creattime,
              uint32 *owner_id)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   dirhandle;
    uint8   sub_dir[2];
    uint8   pathlen;
    uint8   path[256];
  } req;
  struct {
   uint16 len;
   uint8  sub_dir_name[16];
   uint8  create_date_time[4];
   uint8  owner_id[4];       /* HI LOW */
   uint8  max_right_mask;
   uint8  reserved;          /* Reserved by Novell */
   uint8  sub_dir_nmbr[2];   /* HI LOW */
  } repl = { sizeof(repl) - sizeof(uint16) };
  req.func      = 0x02;
  U16_TO_BE16((sub_dir) ? *sub_dir : 1, req.sub_dir);
  req.dirhandle = (uint8) dirhandle;
  req.pathlen   = (uint8) ((path) ? strlen(path) : 0);
  req.len       = 5 + req.pathlen;
  strmaxcpy(req.path, path, req.pathlen);
  neterrno      = Net_Call(0xE200, &req, &repl);
  if (neterrno)   return(-1);
  if (resultpath) strmaxcpy(resultpath, repl.sub_dir_name, 16);
  if (sub_dir)    *sub_dir   = GET_BE16(repl.sub_dir_nmbr);
  if (creattime)  *creattime = GET_BE32(repl.create_date_time);
  if (owner_id)   *owner_id  = GET_BE32(repl.owner_id);
  return((int) repl.max_right_mask);
}

/* ----------------  0x17 ----------------------------------- */
/*
 * ncp17_02_set_debug_level
 *
 * Purpose:
 *   Sets a MARS-NWE debug level for one server module and returns the previous
 *   debug value. This is a MARS-NWE helper, not a normal Novell client command.
 *
 * NCP:
 *   Function group 0x17, subfunction 0x02.
 *
 * Requester path:
 *   Net_Call(0xE300), with a 16-bit request and reply length prefix.
 *
 * Returns:
 *   Previous debug level on success, or -1 when the requester/NCP call fails.
 */
int ncp17_02_set_debug_level(int module, int debuglevel)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   module;
    uint8   debug;
  } req = { sizeof(req) - sizeof(uint16) };
  struct {
   uint16   len;
   uint8    olddebug;
  } repl     = { sizeof(repl) - sizeof(uint16) };
  req.func   = 0x2;
  req.module = (uint8) module;
  req.debug  = (uint8) debuglevel;
  neterrno   = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);
  return((int) repl.olddebug);
}

/*
 * ncp17_14_login_object_unencrypted
 *
 * Purpose:
 *   Logs in a bindery object using the legacy unencrypted password call.
 *
 * NCP:
 *   Function group 0x17, subfunction 0x14. This is the older unencrypted login
 *   path; encrypted login normally uses the key returned by subfunction 0x17
 *   and the keyed-login call below.
 *
 * Requester path:
 *   Net_Call(0xE300), with object type, counted object name and counted
 *   password in the request body.
 *
 * Returns:
 *   0 on success, or -1 when the requester/NCP call fails.
 */
int ncp17_14_login_object_unencrypted(uint8 *objname, uint16 objtyp, uint8 *password)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   typ[2];
    uint8   namlen;
    uint8   buff[48+1+128];
  } req;
  struct {
   uint16   len;
  } repl= { 0 };
  uint8 *p=req.buff;
  req.func     = 0x14;
  U16_TO_BE16(objtyp, req.typ);
  req.namlen = min(47, strlen(objname));
  memcpy(p,   objname, req.namlen);
  p       += req.namlen;
  *p = (uint8) min(128, strlen(password));
  req.len      = 4 + req.namlen + 1 + *p;
  memcpy(p+1, password, (int) *p);
  neterrno     = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);
  return(0);
}

/*
 * ncp17_17_get_encryption_key
 *
 * Purpose:
 *   Retrieves the 8-byte login encryption key used by the legacy bindery
 *   encrypted login and password-change helpers.
 *
 * NCP:
 *   Function group 0x17, subfunction 0x17.
 *
 * Requester path:
 *   Net_Call(0xE300). The reply contains the raw 8-byte key.
 *
 * Returns:
 *   0 on success, or -1 when the requester/NCP call fails.
 */
int ncp17_17_get_encryption_key(uint8 *key)
{
  struct {
    uint16  len;
    uint8   func;
  } req;
  struct {
   uint16   len;
   uint8    key[8];
  } repl;
  req.len      = 1;
  req.func     = 0x17;
  repl.len     = 8;
  neterrno     = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);
  else {
    memcpy(key, repl.key, 8);
    return(0);
  }
}

/*
 * ncp17_18_keyed_object_login
 *
 * Purpose:
 *   Performs keyed/encrypted bindery object login using the 8-byte challenge
 *   returned by ncp17_17_get_encryption_key().
 *
 * NCP:
 *   Function group 0x17, subfunction 0x18.
 *
 * Requester path:
 *   Net_Call(0xE300), with key, object type and counted object name.
 *
 * Returns:
 *   0 on success, or -1 when the requester/NCP call fails.
 */
int ncp17_18_keyed_object_login(uint8 *cryptkey, uint8 *objname, uint16 objtyp)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   key[8];
    uint8   typ[2];
    uint8   namlen;
    uint8   name[48];
  } req;
  struct {
   uint16   len;
  } repl={ 0 };
  req.len      = sizeof(req) - sizeof(uint16);
  req.func     = 0x18;
  U16_TO_BE16(objtyp, req.typ);
  req.namlen   = min(sizeof(req.name), strlen(objname));
  memcpy(req.key,  cryptkey, 8);
  memcpy(req.name, objname, (int) req.namlen);
  neterrno     = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);
  return(0);
}

/*
 * ncp17_35_get_bindery_object_id
 *
 * Purpose:
 *   Resolves a bindery object name and type to its 32-bit object ID. The
 *   canonical object name returned by the server is copied back to objname.
 *
 * NCP:
 *   Function group 0x17, subfunction 0x35, Get Bindery Object ID
 *   (MARS-NWE admin: F217/35; Novell SDK/WebSDK: Get Bindery Object ID).
 *
 * Requester path:
 *   Net_Call(0xE300), with big-endian object type and counted object name.
 *
 * Returns:
 *   Object ID on success, or 0 when the requester/NCP call fails.
 */
uint32 ncp17_35_get_bindery_object_id(uint8 *objname, uint16 objtyp)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   typ[2];
    uint8   namlen;
    uint8   name[48];
  } req;
  struct {
   uint16   len;
   uint8    object_id[4];
   uint8    object_type[2];
   uint8    object_name[48];
  } repl;
  req.len      = sizeof(req)  - sizeof(uint16);
  repl.len     = sizeof(repl) - sizeof(uint16);
  req.func     = 0x35;
  U16_TO_BE16(objtyp, req.typ);
  req.namlen   = min(sizeof(req.name), strlen(objname));
  memcpy(req.name, objname, (int) req.namlen);
  neterrno     = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(0L);
  strmaxcpy(objname, repl.object_name, 47);
  return(GET_BE32(repl.object_id));
}

/*
 * ncp17_36_get_bindery_object_name
 *
 * Purpose:
 *   Resolves a 32-bit bindery object ID to its object name and type.
 *
 * NCP:
 *   Function group 0x17, subfunction 0x36, Get Bindery Object Name
 *   (MARS-NWE admin: F217/36; Novell SDK/WebSDK: Get Bindery Object Name).
 *
 * Requester path:
 *   Net_Call(0xE300), with the object ID in big-endian order.
 *
 * Returns:
 *   0 on success, or -1 when the requester/NCP call fails.
 */
int ncp17_36_get_bindery_object_name(uint32 obj_id, uint8 *objname, uint16 *objtyp)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   id[4];
  } req;
  struct {
   uint16   len;
   uint8    object_id[4];
   uint8    object_type[2];
   uint8    object_name[48];
  } repl;
  req.len      = sizeof(req)  - sizeof(uint16);
  repl.len     = sizeof(repl) - sizeof(uint16);
  req.func     = 0x36;
  U32_TO_BE32(obj_id, req.id);
  neterrno     = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);
  if (objname) strmaxcpy(objname, repl.object_name, 47);
  if (objtyp)  *objtyp = GET_BE16(repl.object_type);
  return(0);
}


/*
 * ncp17_40_change_password_unencrypted
 *
 * Purpose:
 *   Changes a bindery object's password using the legacy unencrypted password
 *   call.
 *
 * NCP:
 *   Function group 0x17, subfunction 0x40, Change Bindery Object Password
 *   (MARS-NWE admin: F217/40).
 *
 * Requester path:
 *   Net_Call(0xE300), with object type, counted object name, counted old
 *   password and counted new password.
 *
 * Returns:
 *   0 on success, or -1 when the requester/NCP call fails.
 */
int ncp17_40_change_password_unencrypted(uint8 *objname, uint16 objtyp,
                               uint8 *password, uint8 *newpassword)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   typ[2];
    uint8   namlen;
    uint8   buff[48+1+128+1+128];
  } req;
  struct {
   uint16   len;
  } repl = { 0 };
  uint8 *p=req.buff;
  req.func     = 0x40;
  U16_TO_BE16(objtyp, req.typ);
  req.namlen = min(47, strlen(objname));
  memcpy(p,   objname, req.namlen);
  p       += req.namlen;
  *p = (uint8) min(128, strlen(password));
  req.len      = 4 + req.namlen + 1 + *p;
  memcpy(p+1, password, (int) *p);
  p            += (1 +  *p);
  *p = (uint8) min(128, strlen(newpassword));
  req.len      += (1 + *p);
  memcpy(p+1, newpassword, (int) *p);
  neterrno     = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);
  return(0);
}

/*
 * ncp14_46_get_bindery_access_level
 *
 * Purpose:
 *   Reads the current bindery access level and the logged-in object's object ID.
 *
 * NCP:
 *   Bindery subfunction 0x46, Get Bindery Access Level. The historic local
 *   wrapper was named ncp_14_46; keep the ncp14_ prefix to preserve that
 *   protocol reference even though the call is sent through Net_Call(0xE300).
 *
 * Requester path:
 *   Net_Call(0xE300). The reply contains one access byte followed by the
 *   current object's 32-bit object ID in big-endian order.
 *
 * Returns:
 *   Access level on success, or -1 when the requester/NCP call fails.
 */
int ncp14_46_get_bindery_access_level(uint32 *obj_id)
{
  struct {
    uint16  len;
    uint8   func;
  } req;
  struct {
   uint16   len;
   uint8    access;
   uint8    id[4];
  } repl;
  req.len      = 1;
  req.func     = 0x46;
  repl.len     = 5;
  neterrno     = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);
  else {
    if (obj_id) *obj_id = GET_BE32(repl.id);
    return(repl.access);
  }
}


/*
 * ncp17_4b_keyed_change_password
 *
 * Purpose:
 *   Changes a bindery object's password using the keyed/encrypted password
 *   change path.
 *
 * NCP:
 *   Function group 0x17, subfunction 0x4b, Change Encrypted Bindery Object
 *   Password (MARS-NWE admin: F217/4B).
 *
 * Requester path:
 *   Net_Call(0xE300), with the login encryption key, object type, counted
 *   object name, password length selector and encrypted new password bytes.
 *
 * Returns:
 *   0 on success, or -1 when the requester/NCP call fails.
 */
int ncp17_4b_keyed_change_password(uint8 *cryptkey, uint8 *objname, uint16 objtyp,
                   int passwx, uint8 *newpassword)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   key[8];
    uint8   typ[2];
    uint8   namlen;
    uint8   buff[48+1+16];
  } req;
  struct {
   uint16   len;
  } repl = { 0 };
  uint8 *p     = req.buff;
  req.func     = 0x4b;
  memcpy(req.key,  cryptkey, 8);
  U16_TO_BE16(objtyp, req.typ);
  req.namlen   = (uint8) min(48, strlen(objname));
  req.len      = 12 + req.namlen + 1 + 16;
  memcpy(p, objname, (int) req.namlen);
  p   += req.namlen;
  *p++ = (uint8) passwx;
  memcpy(p, newpassword, 16);
  neterrno     = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);
  return(0);
}


/*
 * ncp17_37_scan_bindery_object
 *
 * Purpose:
 *   Iterates over bindery objects matching an object type and name pattern.
 *   Pass last_id as -1 for the first call and then feed back the object ID
 *   returned in target to continue scanning.
 *
 * NCP:
 *   Function group 0x17, subfunction 0x37, Scan Bindery Object
 *   (MARS-NWE admin: F217/37; Novell SDK/WebSDK: Scan Bindery Object).
 *
 * Requester path:
 *   Net_Call(0xE300), with last object ID, search object type and counted
 *   pattern. A NULL pattern is sent as '*'.
 *
 * Returns:
 *   0 on success, or -1 when the requester/NCP call fails.
 */
int ncp17_37_scan_bindery_object(uint32 last_id, uint16 objtyp, uint8 *pattern,
              BINDERY_OBJECT *target)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   last_id[4];
    uint8   typ[2];
    uint8   patlen;
    uint8   pattern[48];
  } req;
  struct {
   uint16   len;
   uint8    object_id[4];
   uint8    object_type[2];
   uint8    object_name[48];
   uint8    object_flags;
   uint8    object_security;
   uint8    object_has_prop;
  } repl;
  int patlen = (pattern) ? min(48, strlen(pattern)) : 1;

  memset(&req,  0, sizeof(req));
  memset(&repl, 0, sizeof(repl));

  req.func   = 0x37;
  U32_TO_BE32(last_id, req.last_id);
  U16_TO_BE16(objtyp, req.typ);
  req.patlen = (uint8)patlen;
  if (pattern) memcpy(req.pattern, pattern, patlen);
  else req.pattern[0] = '*';
  req.len = 8 + patlen;

  repl.len = sizeof(repl) - sizeof(uint16);

  neterrno = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);

  if (target) {
    target->object_id       = GET_BE32(repl.object_id);
    target->object_type     = GET_BE16(repl.object_type);
    memcpy(target->object_name, repl.object_name, 48);
    target->object_name[48] = '\0';
    deb(target->object_name);
    target->object_flags    = repl.object_flags;
    target->object_security = repl.object_security;
    target->object_has_prop = repl.object_has_prop;
  }
  return(0);
}

/*
 * ncp17_3d_read_property_value
 *
 * Purpose:
 *   Reads one 128-byte segment of a bindery property value and returns the
 *   server's continuation and property flags.
 *
 * NCP:
 *   Function group 0x17, subfunction 0x3d, Read Property Value
 *   (MARS-NWE admin: F217/3D; Novell SDK/WebSDK: Read Property Value).
 *
 * Requester path:
 *   Net_Call(0xE300), with object type, counted object name, segment number
 *   and counted property name.
 *
 * Returns:
 *   0 on success, or -1 when the requester/NCP call fails.
 */
int ncp17_3d_read_property_value(uint16 objtyp, uint8 *objname, int segment,
              uint8 *propname, NW_PROPERTY *target)
{
  struct {
    uint16  len;
    uint8   func;
    uint8   typ[2];
    uint8   buff[1+48+1+1+16];
  } req;
  struct {
   uint16   len;
   uint8    value[128];
   uint8    more_flag;
   uint8    property_flag;
  } repl;
  uint8 *p = req.buff;
  int objlen  = min(48, strlen(objname));
  int proplen = min(16, strlen(propname));

  memset(&req,  0, sizeof(req));
  memset(&repl, 0, sizeof(repl));

  req.func = 0x3d;
  U16_TO_BE16(objtyp, req.typ);

  *p++ = (uint8)objlen;
  memcpy(p, objname, objlen);
  p += objlen;

  *p++ = (uint8)segment;

  *p++ = (uint8)proplen;
  memcpy(p, propname, proplen);

  req.len = 6 + objlen + proplen;
  repl.len = sizeof(repl) - sizeof(uint16);

  neterrno = Net_Call(0xE300, &req, &repl);
  if (neterrno) return(-1);

  if (target) {
    memcpy(target->value, repl.value, 128);
    target->more_flag     = repl.more_flag;
    target->property_flag = repl.property_flag;
  }
  return(0);
}


/*
 * ncp_copy_open_reply_to_handle6
 *
 * Purpose:
 *   Extracts the six-byte server file handle from an NCP87/01 Open/Create
 *   reply fragment for later NCP74 Copy or NCP66 Close calls.
 *
 * Notes:
 *   The exact handle layout is still part of the NCOPY investigation.  This
 *   helper keeps the extraction in one place so the code can be corrected
 *   without touching the callers.
 */
static void ncp_copy_open_reply_to_handle6(NCP_FILE_HANDLE6 *dst,
                                            const uint8 *src)
{
  /*
   * NCP 74 File Server Copy and NCP 66 Close File both take a six-byte
   * server file handle.  Client32's NCP87 Open/Create reply supplies that
   * six-byte handle at the start of the first reply fragment; the following
   * bytes are OpenCreateAction and Reserved.
   *
   * The previous patch only copied four bytes and zero-filled h[4..5].  That
   * is enough to create a visible zero-length target file, but it is not the
   * handle that NCP74/NCP66 own.  The bad close leaves Client32/MARS seeing
   * the target as still open, so the fallback DOS/requester open reports
   * "File in use during a file open" for every file.
   */
  memset(dst->h, 0, sizeof(dst->h));
  memcpy(dst->h, src, 6);
}

/*
 * ncp87_01_open_create_entry
 *
 * Purpose:
 *   Opens or creates a namespace file/directory entry and returns the server
 *   file handle required by copy/close style NCPs.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x01, Open/Create File or Subdirectory.
 *
 * Requester path:
 *   Client32 raw fragment request.
 *
 * Notes:
 *   This wrapper is currently experimental for NCOPY.  Earlier tests showed
 *   that the old INT 21h F257 path reaches MARS-NWE but does not reliably
 *   return the open file handle to the DOS caller.
 */
int ncp87_01_open_create_entry(const char *path_name,
                               uint16 dir_handle,
                               uint8 open_create_mode,
                               uint32 create_attrs,
                               uint16 desired_access,
                               uint16 search_attrs,
                               NCP_FILE_HANDLE6 *handle_out,
                               uint32 *file_size_out,
                               uint8 *open_create_action_out,
                               uint16 *actual_out,
                               uint16 *handle_lo_out,
                               uint16 *handle_hi_out)
{
  uint16 handle_lo, handle_hi;
  uint8 hdr[16];
  uint8 path[0x140];
  uint8 rep0[0x180];
  uint8 rep1[0x40];
  uint8 rawout[32];
  uint16 raw_ret_ax, raw_ret_dx;
  uint16 actual_lo;
  UI path_len;
  int rc;

  if (handle_out)
    memset(handle_out, 0, sizeof(*handle_out));
  if (file_size_out) *file_size_out = 0;
  if (open_create_action_out) *open_create_action_out = 0;
  if (actual_out) *actual_out = 0;
  if (handle_lo_out) *handle_lo_out = 0;
  if (handle_hi_out) *handle_hi_out = 0;

  if (!path_name || !handle_out)
    return(1);

  rc = ncp_get_requester_handle(&handle_lo, &handle_hi);
  if (rc)
    return(10 + rc);

  /*
   * NCP 87 subfunction 1: Open/Create File or Subdirectory.
   *
   * Request payload after the NCP87 subfunction byte:
   *   byte  NameSpace           (0 = DOS)
   *   byte  OpenCreateMode      (OPEN/TRUNCATE/CREATE bits)
   *   word  SearchAttributes    (Lo-Hi)
   *   dword ReturnInfoMask      (Lo-Hi)
   *   dword CreateAttributes    (Lo-Hi)
   *   word  DesiredAccessRights (Lo-Hi)
   *   NWHandlePathStruct
   *
   * The Client32 raw requester path sends the subfunction
   * header as fragment 0 and the NWHandlePathStruct as fragment 1.
   */
  memset(hdr, 0, sizeof(hdr));
  hdr[0] = 1;                         /* NCP87 subfunction 1 */
  hdr[1] = 0;                         /* DOS namespace */
  hdr[2] = open_create_mode;
  tool_put_word_lh(hdr + 3, search_attrs);
  tool_put_dword_lh(hdr + 5, 0x00000FFFUL); /* RIM_ALL, keeps size handy */
  tool_put_dword_lh(hdr + 9, create_attrs);
  tool_put_word_lh(hdr + 13, desired_access);

  path_len = ncp_build_handle_path_from_dos_path(path, (uint8)dir_handle,
                                                 0, 0, path_name);

  memset(rep0, 0, sizeof(rep0));
  memset(rep1, 0, sizeof(rep1));
  memset(rawout, 0, sizeof(rawout));

  ncp_raw_request(handle_lo, handle_hi,
                       hdr, 15,
                       path, path_len,
                       rep0, sizeof(rep0),
                       rep1, sizeof(rep1),
                       rawout);

  raw_ret_ax = tool_get_word_lh(rawout + 14);
  raw_ret_dx = tool_get_word_lh(rawout + 16);
  actual_lo  = tool_get_word_lh(rawout + 18);

  if (actual_out) *actual_out = actual_lo;
  if (handle_lo_out) *handle_lo_out = handle_lo;
  if (handle_hi_out) *handle_hi_out = handle_hi;

  if (raw_ret_ax != 0 || raw_ret_dx != 0)
    return(20);

  ncp_copy_open_reply_to_handle6(handle_out, rep0 + 0);

  if (open_create_action_out)
    *open_create_action_out = rep0[6];

  /* NetWareInfoStruct follows FileHandle[6]/OpenCreateAction/Reserved. */
  if (file_size_out)
    *file_size_out = tool_get_dword_lh(rep0 + 8 + 10);

  return(0);
}

/*
 * ncp74_file_server_copy
 *
 * Purpose:
 *   Copies file data server-side between two already-open NetWare file
 *   handles.
 *
 * NCP:
 *   Function 0x4a, File Server Copy.
 *
 * Requester path:
 *   Direct F24A Net_Call wrapper.
 *
 * Notes:
 *   Offsets and byte count are encoded high-low.  The source and destination
 *   handles must be the six-byte server handles accepted by the file server.
 */
int ncp74_file_server_copy(const NCP_FILE_HANDLE6 *src,
                               const NCP_FILE_HANDLE6 *dst,
                               uint32 src_offset,
                               uint32 dst_offset,
                               uint32 count,
                               uint32 *copied_out)
{
  struct {
    uint16 len;
    uint8  reserved;
    uint8  src_handle[6];
    uint8  dst_handle[6];
    uint8  src_offset[4];
    uint8  dst_offset[4];
    uint8  count[4];
  } req;
  struct {
    uint16 len;
    uint8  copied[4];
  } repl;

  if (copied_out)
    *copied_out = 0;
  if (!src || !dst)
    return(1);

  memset(&req, 0, sizeof(req));
  memset(&repl, 0, sizeof(repl));

  memcpy(req.src_handle, src->h, 6);
  memcpy(req.dst_handle, dst->h, 6);
  tool_put_dword_hl(req.src_offset, src_offset);
  tool_put_dword_hl(req.dst_offset, dst_offset);
  tool_put_dword_hl(req.count, count);
  req.len = 1 + 6 + 6 + 4 + 4 + 4;
  repl.len = 4;

  neterrno = Net_Call(0xF24A, &req, &repl);
  if (neterrno)
    return(-neterrno);

  if (copied_out)
    *copied_out = tool_get_dword_hl(repl.copied);

  return(0);
}

/*
 * ncp66_close_file
 *
 * Purpose:
 *   Closes a NetWare server file handle returned by an open/create wrapper.
 *
 * NCP:
 *   Function 0x42 / decimal 66, Close File.
 *
 * Requester path:
 *   Direct F242 Net_Call wrapper.
 */
int ncp66_close_file(const NCP_FILE_HANDLE6 *handle)
{
  struct {
    uint8 len;
    uint8 reserved;
    uint8 handle[6];
  } req;
  struct { uint8 len; } repl;

  if (!handle)
    return(1);

  memset(&req, 0, sizeof(req));
  memset(&repl, 0, sizeof(repl));

  memcpy(req.handle, handle->h, 6);
  req.len = 1 + 6;
  repl.len = 0;

  neterrno = Net_Call(0xF242, &req, &repl);
  if (neterrno)
    return(-neterrno);

  return(0);
}


/*
 * ncp87_06_obtain_rim_attributes
 *
 * Purpose:
 *   Reads only the DOS attribute field for a directory entry using the
 *   NCP87 return-information-mask mechanism.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x06, Obtain File or Subdirectory
 *   Information, RIM_ATTRIBUTES.
 *
 * Requester path:
 *   Client32 raw fragment request.
 */
int ncp87_06_obtain_rim_attributes(const char *name,
                                     uint16 dir_handle,
                                     uint32 *attr_out,
                                     uint16 *actual_out,
                                     uint16 *handle_lo_out,
                                     uint16 *handle_hi_out)
{
  uint16 handle_lo, handle_hi;
  uint8 hdr[16];
  uint8 path[0x140];
  uint8 rep0[0x60];
  uint8 rep1[0x110];
  uint8 rawout[32];
  uint16 raw_ret_ax, raw_ret_dx;
  uint16 actual_lo;
  int path_len;
  int rc;

  if (!name || !attr_out)
    return(1);

  *attr_out = 0;
  if (actual_out) *actual_out = 0;
  if (handle_lo_out) *handle_lo_out = 0;
  if (handle_hi_out) *handle_hi_out = 0;

  rc = ncp_get_requester_handle(&handle_lo, &handle_hi);
  if (rc)
    return(10 + rc);

  memset(hdr, 0, sizeof(hdr));
  hdr[0] = 6;                              /* NCP87 subfunction 6 */
  hdr[1] = 0;                              /* source namespace DOS */
  hdr[2] = 0;                              /* target namespace DOS */
  tool_put_word_lh(hdr + 3, 0x0006);        /* SA_ALL */
  tool_put_dword_lh(hdr + 5, 0x00000004UL); /* RIM_ATTRIBUTES */

  path_len = ncp_build_handle_path(path, (uint8)dir_handle, 0, 0, 1,
                                    name, NULL, NULL);

  memset(rep0, 0, sizeof(rep0));
  memset(rep1, 0, sizeof(rep1));
  memset(rawout, 0, sizeof(rawout));

  ncp_raw_request(handle_lo, handle_hi,
                       hdr, 9,
                       path, (UI)path_len,
                       rep0, 0x4d,
                       rep1, 0x100,
                       rawout);

  raw_ret_ax = tool_get_word_lh(rawout + 14);
  raw_ret_dx = tool_get_word_lh(rawout + 16);
  actual_lo  = tool_get_word_lh(rawout + 18);

  if (raw_ret_ax != 0 || raw_ret_dx != 0)
    return(20);

  /*
   * Verified reply layout for RIM_ATTRIBUTES:
   *   REP0+4 little-endian dword = DOS attributes
   * Example LOGIN.EXE: 20h archive.
   */
  *attr_out = tool_get_dword_lh(rep0 + 4);

  if (actual_out) *actual_out = actual_lo;
  if (handle_lo_out) *handle_lo_out = handle_lo;
  if (handle_hi_out) *handle_hi_out = handle_hi;

  return(0);
}


/*
 * ncp87_06_obtain_ndir_info
 *
 * Purpose:
 *   Reads the DOS namespace information block used by NDIR for Novell-style
 *   metadata output.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x06, Obtain File or Subdirectory
 *   Information, RIM_ALL.
 *
 * Requester path:
 *   Client32 raw fragment request.
 */
int ncp87_06_obtain_ndir_info(const char *path_name,
                               uint16 dir_handle,
                               NCP_NDIR_INFO *info_out,
                               uint16 *actual_out,
                               uint16 *handle_lo_out,
                               uint16 *handle_hi_out)
{
  uint16 handle_lo, handle_hi;
  uint8 hdr[16];
  uint8 path[0x140];
  uint8 rep0[0x180];
  uint8 rep1[0x40];
  uint8 rawout[32];
  uint16 raw_ret_ax, raw_ret_dx;
  uint16 actual_lo;
  UI path_len;
  int rc;
  int namelen;

  if (!info_out)
    return(1);

  memset(info_out, 0, sizeof(*info_out));
  if (actual_out) *actual_out = 0;
  if (handle_lo_out) *handle_lo_out = 0;
  if (handle_hi_out) *handle_hi_out = 0;

  rc = ncp_get_requester_handle(&handle_lo, &handle_hi);
  if (rc)
    return(10 + rc);

  /*
   * NCP87 subfunction 6: Obtain File or Subdirectory Information.
   *
   * This asks for the classic DOS info block (RIM_ALL).  ncpfs' old
   * nw_info_struct layout is:
   *   +00 space allocated
   *   +04 attributes
   *   +20 creation time/date/id
   *   +28 modify time/date/id
   *   +36 last access date
   *   +38 archive time/date/id
   *   +46 inherited rights mask
   *   +48 directory numbers
   *   +76 name length/name
   */
  memset(hdr, 0, sizeof(hdr));
  hdr[0] = 6;                       /* NCP87 subfunction 6 */
  hdr[1] = 0;                       /* source namespace DOS */
  hdr[2] = 0;                       /* target namespace DOS */
  tool_put_word_lh(hdr + 3, 0x0006); /* SA_ALL */
  tool_put_dword_lh(hdr + 5, 0x00000FFFUL); /* RIM_ALL */

  path_len = ncp_build_handle_path_from_dos_path(path, (uint8)dir_handle,
                                                 0, 0, path_name);

  memset(rep0, 0, sizeof(rep0));
  memset(rep1, 0, sizeof(rep1));
  memset(rawout, 0, sizeof(rawout));

  ncp_raw_request(handle_lo, handle_hi,
                       hdr, 9,
                       path, path_len,
                       rep0, sizeof(rep0),
                       rep1, sizeof(rep1),
                       rawout);

  raw_ret_ax = tool_get_word_lh(rawout + 14);
  raw_ret_dx = tool_get_word_lh(rawout + 16);
  actual_lo  = tool_get_word_lh(rawout + 18);

  if (actual_out) *actual_out = actual_lo;
  if (handle_lo_out) *handle_lo_out = handle_lo;
  if (handle_hi_out) *handle_hi_out = handle_hi;

  if (raw_ret_ax != 0 || raw_ret_dx != 0)
    return(20);

  info_out->space_allocated   = tool_get_dword_lh(rep0 + 0);
  info_out->attributes        = tool_get_dword_lh(rep0 + 4);
  info_out->flags             = tool_get_word_lh(rep0 + 8);
  info_out->data_size         = tool_get_dword_lh(rep0 + 10);
  info_out->total_size        = tool_get_dword_lh(rep0 + 14);
  info_out->number_of_streams = tool_get_word_lh(rep0 + 18);
  info_out->creation_time     = tool_get_word_lh(rep0 + 20);
  info_out->creation_date     = tool_get_word_lh(rep0 + 22);
  info_out->creator_id        = tool_get_dword_hl(rep0 + 24);
  info_out->modify_time       = tool_get_word_lh(rep0 + 28);
  info_out->modify_date       = tool_get_word_lh(rep0 + 30);
  info_out->modifier_id       = tool_get_dword_hl(rep0 + 32);
  info_out->last_access_date  = tool_get_word_lh(rep0 + 36);
  info_out->archive_time      = tool_get_word_lh(rep0 + 38);
  info_out->archive_date      = tool_get_word_lh(rep0 + 40);
  info_out->archiver_id       = tool_get_dword_hl(rep0 + 42);
  info_out->inherited_rights  = tool_get_word_lh(rep0 + 46);
  info_out->dir_ent_num       = tool_get_dword_lh(rep0 + 48);
  info_out->dos_dir_num       = tool_get_dword_lh(rep0 + 52);
  info_out->vol_number        = tool_get_dword_lh(rep0 + 56);
  info_out->ea_data_size      = tool_get_dword_lh(rep0 + 60);
  info_out->ea_key_count      = tool_get_dword_lh(rep0 + 64);
  info_out->ea_key_size       = tool_get_dword_lh(rep0 + 68);
  info_out->ns_creator        = tool_get_dword_lh(rep0 + 72);

  namelen = rep0[76];
  if (namelen > 255)
    namelen = 255;
  info_out->name_len = (uint8)namelen;
  memcpy(info_out->name, rep0 + 77, namelen);
  info_out->name[namelen] = '\0';

  return(0);
}



/*
 * ncp87_07_modify_dos_info
 *
 * Purpose:
 *   Writes selected DOS namespace metadata fields for a file or directory.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x07, Modify File or Subdirectory DOS
 *   Information.
 *
 * Requester path:
 *   Client32 raw fragment request.
 */
int ncp87_07_modify_dos_info(const char *name,
                              uint16 dir_handle,
                              uint32 change_mask,
                              NCP_DOS_MODIFY_INFO *info,
                              uint16 *actual_out,
                              uint16 *handle_lo_out,
                              uint16 *handle_hi_out)
{
  uint16 handle_lo, handle_hi;
  uint8 modbuf[80];
  uint8 path[0x140];
  uint8 rep0[0x20];
  uint8 rep1[0x20];
  uint8 rawout[32];
  uint8 *p;
  UI mod_len;
  UI path_len;
  uint16 raw_ret_ax, raw_ret_dx;
  uint16 actual_lo;
  int rc;

  if (!name || !info)
    return(1);

  if (actual_out)
    *actual_out = 0;
  if (handle_lo_out)
    *handle_lo_out = 0;
  if (handle_hi_out)
    *handle_hi_out = 0;

  rc = ncp_get_requester_handle(&handle_lo, &handle_hi);
  if (rc)
    return(10 + rc);

  /*
   * NCP 87 subfunction 7: Modify DOS information.
   *
   * DOS info payload layout verified by FLAG and MARS' NCP22/25 handler:
   *   dword attributes
   *   word  creation time
   *   word  creation date
   *   dword creator id        (HI-LOW)
   *   word  modify time
   *   word  modify date
   *   dword modifier id       (HI-LOW)
   *   word  last access date
   *   word  archive time
   *   word  archive date
   *   dword archiver id       (HI-LOW)
   *   word  inherited rights mask
   *   dword maximum space
   */
  memset(modbuf, 0, sizeof(modbuf));
  p = modbuf;

  *p++ = 7;                  /* subfunction: modify DOS info */
  *p++ = 0;                  /* namespace DOS */
  *p++ = 0;                  /* reserved */
  tool_put_word_lh(p, 0x0006); p += 2;       /* SA_ALL */
  tool_put_dword_lh(p, change_mask); p += 4;

  tool_put_dword_lh(p, info->attributes); p += 4;
  tool_put_word_lh(p, info->creation_time); p += 2;
  tool_put_word_lh(p, info->creation_date); p += 2;
  tool_put_dword_hl(p, info->creator_id); p += 4;
  tool_put_word_lh(p, info->modify_time); p += 2;
  tool_put_word_lh(p, info->modify_date); p += 2;
  tool_put_dword_hl(p, info->modifier_id); p += 4;
  tool_put_word_lh(p, info->last_access_date); p += 2;
  tool_put_word_lh(p, info->archive_time); p += 2;
  tool_put_word_lh(p, info->archive_date); p += 2;
  tool_put_dword_hl(p, info->archiver_id); p += 4;
  tool_put_word_lh(p, info->inherited_rights); p += 2;
  tool_put_dword_lh(p, info->maximum_space); p += 4;
  mod_len = (UI)(p - modbuf);

  path_len = ncp_build_handle_path(path, (uint8)dir_handle, 0, 0, 1,
                                  name, NULL, NULL);

  memset(rep0, 0, sizeof(rep0));
  memset(rep1, 0, sizeof(rep1));
  memset(rawout, 0, sizeof(rawout));

  ncp_raw_request(handle_lo, handle_hi,
                       modbuf, mod_len,
                       path, path_len,
                       rep0, sizeof(rep0),
                       rep1, sizeof(rep1),
                       rawout);

  raw_ret_ax = tool_get_word_lh(rawout + 14);
  raw_ret_dx = tool_get_word_lh(rawout + 16);
  actual_lo  = tool_get_word_lh(rawout + 18);

  if (actual_out)
    *actual_out = actual_lo;
  if (handle_lo_out)
    *handle_lo_out = handle_lo;
  if (handle_hi_out)
    *handle_hi_out = handle_hi;

  if (raw_ret_ax != 0 || raw_ret_dx != 0)
    return(20);

  return(0);
}

/*
 * ncp87_07_modify_dos_attributes
 *
 * Purpose:
 *   Convenience wrapper around ncp87_07_modify_dos_info() for changing only
 *   the DOS attributes field.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x07 with DM_ATTRIBUTES.
 */
int ncp87_07_modify_dos_attributes(char *name,
                                    uint16 dir_handle,
                                    uint32 attrs,
                                    uint16 *actual_out,
                                    uint16 *handle_lo_out,
                                    uint16 *handle_hi_out)
{
  NCP_DOS_MODIFY_INFO info;

  memset(&info, 0, sizeof(info));
  info.attributes = attrs;

  /*
   * NCP 87 modify DOS information uses DM_ATTRIBUTES (0x00000002).
   * RIM_ATTRIBUTES (0x00000004) is the read/obtain mask; using it here
   * leaves high attribute bits such as Delete/Rename Inhibit unchanged or
   * cleared on some servers/clients.
   */
  return(ncp87_07_modify_dos_info(name, dir_handle, 0x00000002UL,
                                   &info, actual_out,
                                   handle_lo_out, handle_hi_out));
}


/*
 * ncp87_1d_get_effective_rights
 *
 * Purpose:
 *   Reads the effective rights mask for a path.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x1d, Get Effective Rights.
 *
 * Requester path:
 *   Client32 raw fragment request with an NWHandlePathStruct.
 */
int ncp87_1d_get_effective_rights(const char *path_name,
                                   uint16 dir_handle,
                                   uint16 *rights_out,
                                   uint16 *actual_out,
                                   uint16 *handle_lo_out,
                                   uint16 *handle_hi_out)
{
  uint16 handle_lo, handle_hi;
  uint8 hdr[16];
  uint8 path[0x140];
  uint8 rep0[0x20];
  uint8 rep1[0x20];
  uint8 rawout[32];
  uint16 raw_ret_ax, raw_ret_dx;
  uint16 actual_lo;
  uint16 rights0;
  uint16 rights4;
  UI path_len;
  int rc;

  if (!rights_out)
    return(1);

  *rights_out = 0;
  if (actual_out) *actual_out = 0;
  if (handle_lo_out) *handle_lo_out = 0;
  if (handle_hi_out) *handle_hi_out = 0;

  rc = ncp_get_requester_handle(&handle_lo, &handle_hi);
  if (rc)
    return(10 + rc);

  /*
   * NCP 87 subfunction 29: Get effective rights.
   *
   * This mirrors ncpfs ncp_get_eff_directory_rights():
   *   byte  29
   *   byte  source namespace
   *   byte  target namespace
   *   word  search attributes, little endian
   *   dword reserved, zero
   *   handle/path
   *
   * Reply is a little-endian word with NCP rights bits.
   */
  memset(hdr, 0, sizeof(hdr));
  hdr[0] = 29;
  hdr[1] = 0;                       /* source namespace DOS */
  hdr[2] = 0;                       /* target namespace DOS */
  tool_put_word_lh(hdr + 3, 0x0006); /* SA_ALL */
  tool_put_dword_lh(hdr + 5, 0L);    /* reserved */

  path_len = ncp_build_handle_path_from_dos_path(path, (uint8)dir_handle,
                                                 0, 0, path_name);

  memset(rep0, 0, sizeof(rep0));
  memset(rep1, 0, sizeof(rep1));
  memset(rawout, 0, sizeof(rawout));

  ncp_raw_request(handle_lo, handle_hi,
                       hdr, 9,
                       path, path_len,
                       rep0, sizeof(rep0),
                       rep1, sizeof(rep1),
                       rawout);

  raw_ret_ax = tool_get_word_lh(rawout + 14);
  raw_ret_dx = tool_get_word_lh(rawout + 16);
  actual_lo  = tool_get_word_lh(rawout + 18);

  if (actual_out) *actual_out = actual_lo;
  if (handle_lo_out) *handle_lo_out = handle_lo;
  if (handle_hi_out) *handle_hi_out = handle_hi;

  if (raw_ret_ax != 0 || raw_ret_dx != 0)
    return(20);

  rights0 = tool_get_word_lh(rep0 + 0);
  rights4 = tool_get_word_lh(rep0 + 4);

  /*
   * Most NCP replies start at REP0+0.  The existing RIM_ATTRIBUTES helper
   * found attributes at REP0+4 on Client32.  Accept the +4 location only
   * if +0 is empty, so restricted rights value 0 still works when +4 is
   * also zero.
   */
  if (rights0 == 0 && rights4 != 0)
    rights0 = rights4;

  *rights_out = rights0;
  return(0);
}


/*
 * ncp87_1d_get_effective_rights_by_dirent
 *
 * Purpose:
 *   Reads effective rights using a volume number and DOS directory number
 *   instead of a textual path.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x1d, Get Effective Rights.
 *
 * Notes:
 *   NDIR uses this form after obtaining directory-entry metadata.
 */
int ncp87_1d_get_effective_rights_by_dirent(uint8 vol_number,
                                             uint32 dos_dir_number,
                                             uint16 *rights_out,
                                             uint16 *actual_out,
                                             uint16 *handle_lo_out,
                                             uint16 *handle_hi_out)
{
  uint16 handle_lo, handle_hi;
  uint8 hdr[16];
  uint8 path[16];
  uint8 rep0[0x20];
  uint8 rep1[0x20];
  uint8 rawout[32];
  uint16 raw_ret_ax, raw_ret_dx;
  uint16 actual_lo;
  uint16 rights0;
  uint16 rights4;
  int rc;

  if (!rights_out)
    return(1);

  *rights_out = 0;
  if (actual_out) *actual_out = 0;
  if (handle_lo_out) *handle_lo_out = 0;
  if (handle_hi_out) *handle_hi_out = 0;

  rc = ncp_get_requester_handle(&handle_lo, &handle_hi);
  if (rc)
    return(10 + rc);

  /*
   * SDK/ncpfs-style NCP87 subfunction 29:
   *   byte  0x1D
   *   byte  source namespace DOS
   *   byte  reserved/target namespace
   *   word  SA_ALL
   *   dword 0
   *   byte  volume number
   *   dword directory number
   *   byte  dirstyle 1
   *   byte  component count 0
   */
  memset(hdr, 0, sizeof(hdr));
  hdr[0] = 0x1d;
  hdr[1] = 0;
  hdr[2] = 0;
  tool_put_word_lh(hdr + 3, 0x0006);
  tool_put_dword_lh(hdr + 5, 0L);

  memset(path, 0, sizeof(path));
  path[0] = vol_number;
  tool_put_dword_lh(path + 1, dos_dir_number);
  path[5] = 1;
  path[6] = 0;

  memset(rep0, 0, sizeof(rep0));
  memset(rep1, 0, sizeof(rep1));
  memset(rawout, 0, sizeof(rawout));

  ncp_raw_request(handle_lo, handle_hi,
                       hdr, 9,
                       path, 7,
                       rep0, sizeof(rep0),
                       rep1, sizeof(rep1),
                       rawout);

  raw_ret_ax = tool_get_word_lh(rawout + 14);
  raw_ret_dx = tool_get_word_lh(rawout + 16);
  actual_lo  = tool_get_word_lh(rawout + 18);

  if (actual_out) *actual_out = actual_lo;
  if (handle_lo_out) *handle_lo_out = handle_lo;
  if (handle_hi_out) *handle_hi_out = handle_hi;

  if (raw_ret_ax != 0 || raw_ret_dx != 0)
    return(20);

  rights0 = (uint16)(rep0[0] | ((uint16)rep0[1] << 8));
  rights4 = (uint16)(rep0[4] | ((uint16)rep0[5] << 8));
  if (rights0 == 0 && rights4 != 0)
    rights0 = rights4;

  *rights_out = rights0;
  return(0);
}




/*
 * ncp22_build_trustee_path
 *
 * Purpose:
 *   Builds the volume-qualified path format required by old NCP22 trustee
 *   calls from the DOS utility display/header path.
 *
 * Notes:
 *   This helper intentionally strips the server component and leaves a
 *   SYS:DIR\FILE style path for directory handle 0 requests.
 */
static int ncp22_build_trustee_path(char *out, const char *path_name, int max)
{
  char header[300];
  char *p;
  char *colon;

  if (!out || max < 2)
    return(-1);

  out[0] = '\0';

  /* Build the same server/volume display path used by the DOS tools, then
   * strip the server component.  NCP22/27 accepts a volume-qualified path
   * such as SYS:DIR\FILE with directory handle 0, matching Novell GRANT's
   * old trustee call as seen in the server trace. */
  tool_header_path(header, (char *)(path_name ? path_name : "."), sizeof(header));

  colon = strchr(header, ':');
  if (!colon)
    return(-1);

  p = header;
  while (*p && p < colon) {
    if (*p == '\\' || *p == '/') {
      p++;
      break;
    }
    p++;
  }

  if (!*p || p >= colon)
    p = header;

  strmaxcpy(out, p, max - 1);
  return(0);
}

/*
 * ncp22_27_set_trustee_rights
 *
 * Purpose:
 *   Adds or updates a trustee assignment using the old directory services NCP
 *   fallback path.
 *
 * NCP:
 *   Function 0x16 / subfunction 0x27, Add Extended Trustee.
 *
 * Requester path:
 *   E200 Net_Call wrapper with a volume-qualified path.
 */
int ncp22_27_set_trustee_rights(const char *path_name,
                                 uint16 dir_handle,
                                 uint32 object_id,
                                 uint16 rights)
{
  struct {
    uint16 len;
    uint8  func;
    uint8  dirhandle;
    uint8  trustee_id[4];
    uint8  trustee_rights[2];
    uint8  pathlen;
    uint8  path[255];
  } req;
  struct { uint16 len; } repl;
  char ncppath[260];
  int pathlen;

  (void)dir_handle;

  if (ncp22_build_trustee_path(ncppath, path_name, sizeof(ncppath)))
    return(30);

  pathlen = strlen(ncppath);
  if (pathlen > 255)
    return(31);

  memset(&req, 0, sizeof(req));
  memset(&repl, 0, sizeof(repl));

  req.func = 0x27;                 /* NCP22/27 Add Ext Trustee */
  req.dirhandle = 0;               /* volume-qualified path follows */
  tool_put_dword_hl(req.trustee_id, object_id);
  tool_put_word_lh(req.trustee_rights, rights);
  req.pathlen = (uint8)pathlen;
  memcpy(req.path, ncppath, pathlen);
  req.len = 9 + pathlen;

  repl.len = 0;
  neterrno = Net_Call(0xE200, &req, &repl);
  if (neterrno)
    return(-neterrno);

  return(0);
}


/*
 * ncp22_2b_delete_trustee_rights
 *
 * Purpose:
 *   Deletes a trustee assignment using the old directory services NCP
 *   fallback path.
 *
 * NCP:
 *   Function 0x16 / subfunction 0x2b, Delete Trustee.
 *
 * Requester path:
 *   E200 Net_Call wrapper with a volume-qualified path.
 */
int ncp22_2b_delete_trustee_rights(const char *path_name,
                                    uint16 dir_handle,
                                    uint32 object_id)
{
  struct {
    uint16 len;
    uint8  func;
    uint8  dirhandle;
    uint8  trustee_id[4];
    uint8  reserved;
    uint8  pathlen;
    uint8  path[255];
  } req;
  struct { uint16 len; } repl;
  char ncppath[260];
  int pathlen;

  (void)dir_handle;

  if (ncp22_build_trustee_path(ncppath, path_name, sizeof(ncppath)))
    return(30);

  pathlen = strlen(ncppath);
  if (pathlen > 255)
    return(31);

  memset(&req, 0, sizeof(req));
  memset(&repl, 0, sizeof(repl));

  req.func = 0x2b;                 /* NCP22/43 Delete Trustee */
  req.dirhandle = 0;               /* volume-qualified path follows */
  tool_put_dword_hl(req.trustee_id, object_id);
  req.reserved = 0;                /* NCP22/2B has a reserved byte before pathlen */
  req.pathlen = (uint8)pathlen;
  memcpy(req.path, ncppath, pathlen);
  req.len = 8 + pathlen;

  repl.len = 0;
  neterrno = Net_Call(0xE200, &req, &repl);
  if (neterrno)
    return(-neterrno);

  return(0);
}

/*
 * ncp87_0a_add_trustee_rights
 *
 * Purpose:
 *   Adds trustee rights using the namespace-aware NCP87 trustee API.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x0a, Add Trustee Set.
 *
 * Requester path:
 *   Client32 raw fragment request.
 */
int ncp87_0a_add_trustee_rights(const char *path_name,
                                 uint16 dir_handle,
                                 uint32 object_id,
                                 uint16 rights,
                                 uint16 rights_mask,
                                 uint16 *actual_out,
                                 uint16 *handle_lo_out,
                                 uint16 *handle_hi_out)
{
  uint16 handle_lo, handle_hi;
  uint8 hdr[16];
  uint8 reqpath[0x180];
  uint8 rep0[0x20];
  uint8 rep1[0x20];
  uint8 rawout[32];
  uint16 raw_ret_ax, raw_ret_dx;
  uint16 actual_lo;
  UI path_struct_len;
  UI reqpath_len;
  uint8 *tp;
  int rc;

  if (actual_out) *actual_out = 0;
  if (handle_lo_out) *handle_lo_out = 0;
  if (handle_hi_out) *handle_hi_out = 0;

  rc = ncp_get_requester_handle(&handle_lo, &handle_hi);
  if (rc)
    return(10 + rc);

  /*
   * NCP 87 subfunction 10: Add trustee.
   *
   * This mirrors ncpfs ncp_ns_trustee_add():
   *   byte  10
   *   byte  namespace DOS
   *   byte  reserved
   *   word  search attributes, little endian
   *   word  rights mask, little endian
   *   word  object count, little endian
   *   handle/path
   *   trustee array at request offset 16 + 307
   *
   * Client32 raw has two request fragments, so the second fragment carries
   * the handle/path and the padded trustee record.
   */
  memset(hdr, 0, sizeof(hdr));
  hdr[0] = 10;
  hdr[1] = 0;                         /* DOS namespace */
  hdr[2] = 0;                         /* reserved */
  tool_put_word_lh(hdr + 3, 0x8006);   /* SA_ALL: files/subdirs + system + hidden */
  tool_put_word_lh(hdr + 5, rights_mask);
  tool_put_word_lh(hdr + 7, 1);        /* one trustee */

  memset(reqpath, 0, sizeof(reqpath));
  path_struct_len = ncp_build_handle_path_from_dos_path(reqpath,
                                                        (uint8)dir_handle,
                                                        0, 0,
                                                        path_name);

  /*
   * ncpfs seeks to absolute packet offset 16+307 before writing the
   * trustee list.  The NCP request header is 7 bytes, so inside the NCP
   * payload the trustee list begins at:
   *
   *   (16 + 307) - 7 = 316
   *
   * Our first raw request fragment is the 9-byte subfunction header,
   * so the trustee list begins in the second fragment at:
   *
   *   316 - 9 = 307
   */
  if (path_struct_len > 307)
    return(2);

  tp = reqpath + 307;
  tool_put_dword_hl(tp, object_id); tp += 4;
  tool_put_word_lh(tp, rights);     tp += 2;
  reqpath_len = (UI)(tp - reqpath);

  memset(rep0, 0, sizeof(rep0));
  memset(rep1, 0, sizeof(rep1));
  memset(rawout, 0, sizeof(rawout));

  ncp_raw_request(handle_lo, handle_hi,
                       hdr, 9,
                       reqpath, reqpath_len,
                       rep0, sizeof(rep0),
                       rep1, sizeof(rep1),
                       rawout);

  raw_ret_ax = tool_get_word_lh(rawout + 14);
  raw_ret_dx = tool_get_word_lh(rawout + 16);
  actual_lo  = tool_get_word_lh(rawout + 18);

  if (actual_out) *actual_out = actual_lo;
  if (handle_lo_out) *handle_lo_out = handle_lo;
  if (handle_hi_out) *handle_hi_out = handle_hi;

  if (raw_ret_ax != 0 || raw_ret_dx != 0)
    return(20);

  return(0);
}



/*
 * ncp87_05_find_trustee_rights
 *
 * Purpose:
 *   Scans trustee entries for a path until the requested bindery object id is
 *   found.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x05, Scan Trustee Set.
 *
 * Requester path:
 *   Client32 raw fragment request.
 */
int ncp87_05_find_trustee_rights(const char *path_name,
                                  uint16 dir_handle,
                                  uint32 object_id,
                                  uint16 *rights_out,
                                  uint16 *actual_out,
                                  uint16 *handle_lo_out,
                                  uint16 *handle_hi_out)
{
  uint16 handle_lo, handle_hi;
  uint8 hdr[16];
  uint8 path[0x140];
  uint8 rep0[0x120];
  uint8 rep1[0x120];
  uint8 rawout[32];
  uint16 raw_ret_ax, raw_ret_dx;
  uint16 actual_lo;
  uint32 seq = 0;
  UI path_len;
  int rc;

  if (rights_out) *rights_out = 0;
  if (actual_out) *actual_out = 0;
  if (handle_lo_out) *handle_lo_out = 0;
  if (handle_hi_out) *handle_hi_out = 0;

  if (!rights_out)
    return(1);

  rc = ncp_get_requester_handle(&handle_lo, &handle_hi);
  if (rc)
    return(10 + rc);

  for (;;) {
    uint16 count;
    uint16 i;
    uint8 *tp;
    uint32 next_seq;

    memset(hdr, 0, sizeof(hdr));
    hdr[0] = 5;                         /* NCP87 subfunction 5: scan trustees */
    hdr[1] = 0;                         /* DOS namespace */
    hdr[2] = 0;                         /* reserved */
    tool_put_word_lh(hdr + 3, 0x8006);   /* SA_ALL: files/subdirs + system + hidden */
    tool_put_dword_lh(hdr + 5, seq);     /* search sequence, starts at zero */

    path_len = ncp_build_handle_path_from_dos_path(path,
                                                   (uint8)dir_handle,
                                                   0, 0,
                                                   path_name);

    memset(rep0, 0, sizeof(rep0));
    memset(rep1, 0, sizeof(rep1));
    memset(rawout, 0, sizeof(rawout));

    ncp_raw_request(handle_lo, handle_hi,
                         hdr, 9,
                         path, path_len,
                         rep0, sizeof(rep0),
                         rep1, sizeof(rep1),
                         rawout);

    raw_ret_ax = tool_get_word_lh(rawout + 14);
    raw_ret_dx = tool_get_word_lh(rawout + 16);
    actual_lo  = tool_get_word_lh(rawout + 18);

    if (actual_out) *actual_out = actual_lo;
    if (handle_lo_out) *handle_lo_out = handle_lo;
    if (handle_hi_out) *handle_hi_out = handle_hi;

    if (raw_ret_ax != 0 || raw_ret_dx != 0)
      return(0xff);  /* Client32 returns an error when no trustees are present. */

    next_seq = tool_get_dword_lh(rep0 + 0);
    count = tool_get_word_lh(rep0 + 4);

    if (count > 20)
      count = 20;

    tp = rep0 + 6;
    for (i = 0; i < count; i++) {
      uint32 tid = tool_get_dword_hl(tp);
      uint16 trights = tool_get_word_lh(tp + 4);

      if (tid == object_id || tool_get_dword_lh(tp) == object_id) {
        *rights_out = trights;
        return(0);
      }

      tp += 6;
    }

    if (next_seq == 0xffffffffUL || next_seq == seq)
      break;

    seq = next_seq;
  }

  return(0xff);  /* no trustee found / no more entries */
}

/*
 * ncp87_0b_delete_trustee_rights
 *
 * Purpose:
 *   Deletes trustee rights using the namespace-aware NCP87 trustee API.
 *
 * NCP:
 *   Function 0x57 / subfunction 0x0b, Delete Trustee Set.
 *
 * Requester path:
 *   Client32 raw fragment request.
 */
int ncp87_0b_delete_trustee_rights(const char *path_name,
                                    uint16 dir_handle,
                                    uint32 object_id,
                                    uint16 *actual_out,
                                    uint16 *handle_lo_out,
                                    uint16 *handle_hi_out)
{
  uint16 handle_lo, handle_hi;
  uint8 hdr[16];
  uint8 reqpath[0x180];
  uint8 rep0[0x20];
  uint8 rep1[0x20];
  uint8 rawout[32];
  uint16 raw_ret_ax, raw_ret_dx;
  uint16 actual_lo;
  UI path_struct_len;
  UI reqpath_len;
  uint8 *tp;
  int rc;

  if (actual_out) *actual_out = 0;
  if (handle_lo_out) *handle_lo_out = 0;
  if (handle_hi_out) *handle_hi_out = 0;

  rc = ncp_get_requester_handle(&handle_lo, &handle_hi);
  if (rc)
    return(10 + rc);

  memset(hdr, 0, sizeof(hdr));
  hdr[0] = 11;                        /* NCP87 subfunction 11: delete trustee */
  hdr[1] = 0;                         /* DOS namespace */
  hdr[2] = 0;                         /* reserved */
  tool_put_word_lh(hdr + 3, 1);        /* one trustee */

  memset(reqpath, 0, sizeof(reqpath));
  path_struct_len = ncp_build_handle_path_from_dos_path(reqpath,
                                                        (uint8)dir_handle,
                                                        0, 0,
                                                        path_name);

  if (path_struct_len > 307)
    return(2);

  tp = reqpath + 307;
  tool_put_dword_hl(tp, object_id); tp += 4;
  tool_put_word_lh(tp, 0);         tp += 2;
  reqpath_len = (UI)(tp - reqpath);

  memset(rep0, 0, sizeof(rep0));
  memset(rep1, 0, sizeof(rep1));
  memset(rawout, 0, sizeof(rawout));

  ncp_raw_request(handle_lo, handle_hi,
                       hdr, 5,
                       reqpath, reqpath_len,
                       rep0, sizeof(rep0),
                       rep1, sizeof(rep1),
                       rawout);

  raw_ret_ax = tool_get_word_lh(rawout + 14);
  raw_ret_dx = tool_get_word_lh(rawout + 16);
  actual_lo  = tool_get_word_lh(rawout + 18);

  if (actual_out) *actual_out = actual_lo;
  if (handle_lo_out) *handle_lo_out = handle_lo;
  if (handle_hi_out) *handle_hi_out = handle_hi;

  if (raw_ret_ax != 0 || raw_ret_dx != 0)
    return(20);

  return(0);
}
