From c80861b92b00a02091944eb04d964deca8e6c721 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Fri, 29 May 2026 22:06:58 +0000 Subject: [PATCH] nwconn: implement extracted base handle restore Wire NCP 0x16/0x17 Extract a Base Handle and NCP 0x16/0x18 Restore an Extracted Base Handle to connection-local directory-handle state. The WebSDK documents NCP 0x2222/22/23 as taking a DirectoryHandle and returning a 14-byte save buffer composed of a 10-byte ServerNetworkAddress plus a 4-byte HandleID. The same documentation describes NCP 0x2222/22/24 as taking that saved ServerNetworkAddress/HandleID pair and returning a NewDirectoryHandle plus AccessRightsMask. The SDK headers expose these calls as NWSaveDirectoryHandle() and NWRestoreDirectoryHandle(), with the save buffer explicitly documented as 14 bytes. The Rust nwserver and lwared references do not implement this older save/restore pair, and newer clients typically use the normal allocate/set directory-handle calls instead, so keep the MARS-NWE HandleID opaque and connection-local rather than guessing a global NetWare directory-base number. Store extracted base-handle IDs in a small per-connection table that records the saved volume/path tuple. Extract requires a live permanent directory handle, and Restore validates the saved server address against this server before allocating a new permanent directory handle for the saved path. Add the SDK request/reply semantics to the inline endpoint comments and remove the corresponding TODO entry. This enables the documented endpoint path while keeping the saved HandleID conservative and private to MARS-NWE. --- TODO.md | 7 +--- include/connect.h | 3 ++ src/connect.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++ src/nwconn.c | 78 +++++++++++++++++++++++++++-------- 4 files changed, 169 insertions(+), 22 deletions(-) diff --git a/TODO.md b/TODO.md index 1a28b78..94020c6 100644 --- a/TODO.md +++ b/TODO.md @@ -63,11 +63,8 @@ Current status: Follow-up: -- Implement or deliberately reject the following endpoints after client evidence - or direct protocol tests: - - `NCP 0x16/0x18 Restore Directory Handle`: verify the exact SDK - request/reply layout, then implement the documented function 22 / - subfunction 24 directory-handle semantics. +- Implement or deliberately reject remaining endpoint gaps after client + evidence or direct protocol tests. - Keep SDK details close to the corresponding endpoint in `nwconn.c`, and keep broader prioritization/status here in `TODO.md`. diff --git a/include/connect.h b/include/connect.h index 6728e2b..2870cf6 100644 --- a/include/connect.h +++ b/include/connect.h @@ -179,6 +179,9 @@ extern int nw_set_dir_handle(int targetdir, int dir_handle, extern int nw_get_directory_path(int dir_handle, uint8 *name, int size_name); +extern int nw_extract_base_handle(int dir_handle, uint8 *handle_id); +extern int nw_restore_base_handle(uint8 *handle_id, int task, int *eff_rights); + extern int nw_get_vol_number(int dir_handle); diff --git a/src/connect.c b/src/connect.c index d380e80..70bc1fc 100644 --- a/src/connect.c +++ b/src/connect.c @@ -85,6 +85,33 @@ typedef struct { NW_DIR dirs[MAX_NW_DIRS]; int used_dirs=0; + +#define MAX_EXTRACTED_BASE_HANDLES 64 + +typedef struct { + uint32 id; + int volume; + uint8 *path; + dev_t dev; + ino_t inode; + uint8 task; +} EXTRACTED_BASE_HANDLE; + +static EXTRACTED_BASE_HANDLE extracted_base_handles[MAX_EXTRACTED_BASE_HANDLES]; +static uint32 next_extracted_base_handle = 1; + +static void clear_extracted_base_handles(int task) +{ + int j; + for (j = 0; j < MAX_EXTRACTED_BASE_HANDLES; j++) { + EXTRACTED_BASE_HANDLE *ebh = &(extracted_base_handles[j]); + if (ebh->id && (task < 0 || ebh->task == (uint8)task)) { + xfree(ebh->path); + memset(ebh, 0, sizeof(*ebh)); + } + } + if (task < 0) next_extracted_base_handle = 1; +} int act_uid=-1; /* unix uid 0=root */ int act_gid=-1; /* unix gid */ int act_obj_id=0L; /* mars_nwe UID, 0=not logged in, 1=supervisor */ @@ -2370,6 +2397,7 @@ int nw_init_connect(void) } init_file_module(-1); + clear_extracted_base_handles(-1); if (connect_is_init) { k = 0; @@ -2458,6 +2486,7 @@ int nw_free_handles(int task) else { NW_DIR *d = &(dirs[0]); int k = used_dirs; + clear_extracted_base_handles(task); while (k--) { if (d->is_temp && d->task == task) { /* only actual task */ @@ -2834,6 +2863,80 @@ int nw_get_directory_path(int dir_handle, uint8 *name, int size_name) return(result); } + +int nw_extract_base_handle(int dir_handle, uint8 *handle_id) +/* Extract a Base Handle: stores an opaque handle id for Restore Directory Handle */ +{ + int result = -0x9b; + if (dir_handle > 0 && --dir_handle < (int)used_dirs) { + NW_DIR *d = &(dirs[dir_handle]); + if (d->inode && !d->is_temp && d->volume < used_nw_volumes) { + int j; + EXTRACTED_BASE_HANDLE *free_ebh = NULL; + for (j = 0; j < MAX_EXTRACTED_BASE_HANDLES; j++) { + EXTRACTED_BASE_HANDLE *ebh = &(extracted_base_handles[j]); + if (!ebh->id && !free_ebh) free_ebh = ebh; + if (ebh->id && ebh->volume == d->volume && ebh->path + && !strcmp((char*)ebh->path, (char*)d->path)) { + free_ebh = ebh; + break; + } + } + if (free_ebh) { + if (!free_ebh->id) { + free_ebh->id = next_extracted_base_handle++; + if (!next_extracted_base_handle) next_extracted_base_handle = 1; + } + xfree(free_ebh->path); + free_ebh->path = xmalloc(strlen((char*)d->path)+1); + strcpy((char*)free_ebh->path, (char*)d->path); + free_ebh->volume = d->volume; + free_ebh->dev = d->dev; + free_ebh->inode = d->inode; + free_ebh->task = d->task; + U32_TO_BE32(free_ebh->id, handle_id); + result = 0; + } else result = -0x96; /* server out of memory/table space */ + } + } + XDPRINTF((4,0,"Extract base handle from dhandle=%d result=0x%x", dir_handle+1, result)); + return(result); +} + +int nw_restore_base_handle(uint8 *handle_id, int task, int *eff_rights) +/* Restore an Extracted Base Handle: allocate a new permanent handle from a saved id */ +{ + uint32 id = GET_BE32(handle_id); + int j; + int result = -0x9b; + for (j = 0; j < MAX_EXTRACTED_BASE_HANDLES; j++) { + EXTRACTED_BASE_HANDLE *ebh = &(extracted_base_handles[j]); + if (ebh->id == id && ebh->path && ebh->volume >= 0 && ebh->volume < used_nw_volumes) { + NW_PATH nwpath; + struct stat stbuff; + uint8 unixname[257]; + nwpath.volume = ebh->volume; + nwpath.fn[0] = '\0'; + strmaxcpy(nwpath.path, (char*)ebh->path, sizeof(nwpath.path)-1); + xstrcpy(unixname, build_unix_name(&nwpath, 0)); + if (stat((char*)unixname, &stbuff)) { + result = -0x9c; /* invalid path */ + } else if (!S_ISDIR(stbuff.st_mode)) { + result = -0x9c; + } else { + result = xinsert_new_dir(nwpath.volume, nwpath.path, + stbuff.st_dev, stbuff.st_ino, 0, 0, task); + if (result > 0 && eff_rights) { + *eff_rights = tru_get_eff_rights(nwpath.volume, unixname, &stbuff); + } + } + break; + } + } + XDPRINTF((4,0,"Restore base handle id=0x%x task=%d result=0x%x", id, task, result)); + return(result); +} + int nw_get_vol_number(int dir_handle) /* Get Volume Nummmer with Handle */ { diff --git a/src/nwconn.c b/src/nwconn.c index 4f57901..f75633b 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -1015,29 +1015,73 @@ static int handle_ncp_serv(void) } break; -#if 0 - case 0x18 : { /* Restore Directory Handle */ + case 0x17 : { /* Extract a Base Handle */ /* - * TODO(ncp-compat): Implement NCP 0x16/0x18 - * Restore Directory Handle. + * NCP 0x2222/22/23 Extract a Base Handle. * - * SDK context: this is function 22 (0x16), subfunction 24 - * (0x18). It belongs to the old directory-handle family - * and should be implemented only after the exact SDK - * request/reply layout and handle-lifetime semantics are - * verified. It is related to the directory-handle table - * calls implemented nearby: - * 0x12 Alloc Permanent Directory Handle - * 0x13 Alloc Temporary Directory Handle - * 0x14 Deallocate Directory Handle - * 0x16 Alloc Special Temporary Directory Handle + * WebSDK: request carries DirectoryHandle. Reply returns a + * 10-byte ServerNetworkAddress plus a 4-byte opaque + * HandleID. The handle must have been allocated with Alloc + * Permanent Directory Handle. The returned HandleID is used + * by NCP 0x16/0x18 Restore an Extracted Base Handle. * - * Do not guess here: old DOS clients may depend on exact - * permanent-vs-temporary handle lifetime semantics. + * MARS-NWE keeps the HandleID opaque to the client and maps + * it to the current connection's saved volume/path tuple. */ + struct XDATA { + uint8 server_network_address[10]; + uint8 handle_id[4]; + } *xdata = (struct XDATA*)responsedata; + int result; + memcpy(xdata->server_network_address, my_addr.net, 4); + memcpy(xdata->server_network_address+4, my_addr.node, 6); + result = nw_extract_base_handle((int)*(p+1), xdata->handle_id); + if (result) completition = (uint8)-result; + else data_len = sizeof(struct XDATA); + } + break; + + case 0x18 : { /* Restore Directory Handle */ + /* + * NCP 0x2222/22/24 Restore an Extracted Base Handle. + * + * WebSDK: request carries the 10-byte ServerNetworkAddress + * and 4-byte HandleID returned by Extract a Base Handle. + * Reply returns NewDirectoryHandle and AccessRightsMask. + * + * The old SDK API exposes this as NWRestoreDirectoryHandle() + * and uses a 14-byte save buffer produced by + * NWSaveDirectoryHandle(). Rust nwserver and lwared do not + * implement this older save/restore pair; newer clients + * typically use the ordinary allocate/set directory-handle + * calls instead. Keep this implementation conservative and + * connection-local rather than guessing a global NetWare + * directory-base number. + */ + struct INPUT { + uint8 server_network_address[10]; + uint8 handle_id[4]; + } *input = (struct INPUT*)(p+1); + struct XDATA { + uint8 dirhandle; + uint8 right_mask; + } *xdata = (struct XDATA*)responsedata; + int eff_rights = 0; + int result; + if (memcmp(input->server_network_address, my_addr.net, 4) + || memcmp(input->server_network_address+4, my_addr.node, 6)) { + completition = 0xfd; /* Bad Station Number */ + break; + } + result = nw_restore_base_handle(input->handle_id, + (int)(ncprequest->task), &eff_rights); + if (result > -1) { + xdata->dirhandle = (uint8)result; + xdata->right_mask = (uint8)eff_rights; + data_len = sizeof(struct XDATA); + } else completition = (uint8)-result; } break; -#endif case 0x19 : { /* Set Directory Information * Modifies basic directory information as creation date and