From 069bbba88c0e6253ee3138965a9c6af35a44af41 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Sat, 30 May 2026 00:41:24 +0000 Subject: [PATCH] tests: resolve AFP smoke volume paths Teach the Linux AFP Entry ID smoke test to treat VOL:PATH arguments like normal NetWare paths instead of sending the full string as the AFP path component. The WebSDK documents AFP Get Entry ID From Path Name as taking a NetWare directory handle plus a path string. A user-supplied path such as SYS:PUBLIC therefore needs a directory handle for the SYS volume root and a relative AFP path of PUBLIC; sending SYS:PUBLIC as the AFP path with directory handle zero makes the server reject the request with Invalid Path before the actual AFP lookup is useful. Use the existing ncpfs/libncp request path to allocate a temporary directory handle for the volume root when the test receives a VOL:PATH argument and no explicit --dir-handle was supplied. Keep --raw-path for callers that want to send the path exactly as typed, and add --allow-invalid-path so negative path-resolution tests can distinguish Invalid Path from Invalid Namespace. Also add failure diagnostics to the server-side AFP path lookup so unsupported-backend, boundary-check, path-resolution, and stat failures are visible in the mars_nwe log. This changes only the Linux smoke test and debug logging; it does not change successful AFP protocol semantics. --- src/nwconn.c | 31 +++++-- tests/linux/afp_entry_id_smoke.c | 136 ++++++++++++++++++++++++++++--- 2 files changed, 149 insertions(+), 18 deletions(-) diff --git a/src/nwconn.c b/src/nwconn.c index cb5c0c7..5072399 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -32,6 +32,7 @@ # define LOC_RW_BUFFERSIZE 512 #endif #include +#include #include #include #if !CALL_NWCONN_OVER_SOCKET @@ -486,19 +487,39 @@ static int afp_get_entry_id_from_path_name(uint8 *afp_req, int afp_len, uint32 entry_id = 0; int result; - if (afp_len < 3) return(-0x7e); /* NCP Boundary Check Failed */ + if (afp_len < 3) { + XDPRINTF((2,0, "AFP Get Entry ID From Path Name rejected: short request len=%d", + afp_len)); + return(-0x7e); /* NCP Boundary Check Failed */ + } dir_handle = afp_req[1]; path_len = (int)afp_req[2]; - if (path_len < 0 || afp_len < 3 + path_len) return(-0x7e); + if (path_len < 0 || afp_len < 3 + path_len) { + XDPRINTF((2,0, "AFP Get Entry ID From Path Name rejected: boundary check len=%d path_len=%d", + afp_len, path_len)); + return(-0x7e); + } - if (!nwatalk_backend_available()) return(-0xbf); /* invalid namespace */ + if (!nwatalk_backend_available()) { + XDPRINTF((3,0, "AFP Get Entry ID From Path Name rejected: libatalk backend unavailable")); + return(-0xbf); /* invalid namespace */ + } volume = conn_get_kpl_unxname(unixname, sizeof(unixname), (int)dir_handle, afp_req + 3, path_len); - if (volume < 0) return(volume); + if (volume < 0) { + XDPRINTF((2,0, "AFP Get Entry ID From Path Name path resolve failed: dh=%d path='%s' result=-0x%x", + (int)dir_handle, visable_data(afp_req + 3, path_len), -volume)); + return(volume); + } - if (stat(unixname, &stbuff)) return(-0x9c); /* Invalid Path */ + if (stat(unixname, &stbuff)) { + XDPRINTF((2,0, "AFP Get Entry ID From Path Name stat failed: dh=%d path='%s' unix='%s' errno=%d", + (int)dir_handle, visable_data(afp_req + 3, path_len), + unixname, errno)); + return(-0x9c); /* Invalid Path */ + } result = nwatalk_get_entry_id(unixname, &entry_id); if (result < 0 || !entry_id) diff --git a/tests/linux/afp_entry_id_smoke.c b/tests/linux/afp_entry_id_smoke.c index b6d5614..de5f8a7 100644 --- a/tests/linux/afp_entry_id_smoke.c +++ b/tests/linux/afp_entry_id_smoke.c @@ -24,20 +24,23 @@ #define AFP_GET_ENTRY_ID_FROM_PATH_NAME 0x0c #define NWE_INVALID_NAMESPACE 0xbf +#define NWE_INVALID_PATH 0x9c +#define AFP_TEMP_DH_NONE 0xff static void usage(const char *prog) { fprintf(stderr, - "Usage: %s [--allow-invalid-namespace] [--dir-handle N] " - "[ncpfs options] PATH\n" + "Usage: %s [--allow-invalid-namespace] [--allow-invalid-path] " + "[--dir-handle N] [--raw-path] [ncpfs options] PATH\n" "\n" "ncpfs options are parsed by ncp_initialize(), for example:\n" " -S SERVER -U USER -P PASSWORD -n\n" "\n" "Examples:\n" " %s -S MARS -U SUPERVISOR -P secret SYS:LOGIN\n" - " %s --allow-invalid-namespace -S MARS SYS:LOGIN\n", - prog, prog, prog); + " %s --allow-invalid-namespace -S MARS SYS:LOGIN\n" + " %s --allow-invalid-path -S MARS SYS:NO_SUCH_PATH\n", + prog, prog, prog, prog); } static int parse_u8(const char *text, unsigned int *value) @@ -58,7 +61,75 @@ static uint32_t be32_to_cpu(const uint8_t p[4]) return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | - ((uint32_t)p[3]); + p[3]; +} + +static int split_volume_path(const char *path, char *volume, size_t volume_size, + const char **relpath) +{ + const char *colon = strchr(path, ':'); + size_t len; + + if (!colon) + return -1; + + len = (size_t)(colon - path); + if (!len || len >= volume_size) + return -1; + + memcpy(volume, path, len); + volume[len] = '\0'; + + *relpath = colon + 1; + while (**relpath == '/' || **relpath == '\\') + (*relpath)++; + + return 0; +} + +static NWCCODE allocate_temp_dir_handle(NWCONN_HANDLE conn, const char *volume, + unsigned int *dir_handle) +{ + uint8_t rq[1 + 1 + 1 + 32]; + uint8_t rpbuf[2]; + NW_FRAGMENT rp; + size_t volume_len = strlen(volume); + NWCCODE err; + + if (volume_len + 1 > 32) + return NWE_INVALID_PATH; + + rq[0] = 0; /* source directory handle */ + rq[1] = 0; /* drive letter, unused by the test */ + rq[2] = (uint8_t)(volume_len + 1); + memcpy(rq + 3, volume, volume_len); + rq[3 + volume_len] = ':'; + + memset(rpbuf, 0, sizeof(rpbuf)); + rp.fragAddr.rw = rpbuf; + rp.fragSize = sizeof(rpbuf); + + err = NWRequestSimple(conn, NCPC_SFN(0x16, 0x13), rq, + 3 + volume_len + 1, &rp); + if (err) + return err; + + if (rp.fragSize < 1) + return NWE_INVALID_NCP_PACKET_LENGTH; + + *dir_handle = rpbuf[0]; + return 0; +} + +static void deallocate_dir_handle(NWCONN_HANDLE conn, unsigned int dir_handle) +{ + uint8_t rq[1]; + + if (dir_handle == AFP_TEMP_DH_NONE) + return; + + rq[0] = (uint8_t)dir_handle; + (void)NWRequestSimple(conn, NCPC_SFN(0x16, 0x14), rq, sizeof(rq), NULL); } int main(int argc, char **argv) @@ -67,10 +138,15 @@ int main(int argc, char **argv) NW_FRAGMENT reply; long init_err = 0; const char *path = NULL; + const char *request_path = NULL; unsigned int dir_handle = 0; + unsigned int allocated_dir_handle = AFP_TEMP_DH_NONE; int allow_invalid_namespace = 0; + int allow_invalid_path = 0; + int raw_path = 0; int i; size_t path_len; + char volume[32]; uint8_t request[1 + 1 + 255]; uint8_t reply_buf[4]; NWCCODE err; @@ -90,6 +166,10 @@ int main(int argc, char **argv) for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--allow-invalid-namespace")) { allow_invalid_namespace = 1; + } else if (!strcmp(argv[i], "--allow-invalid-path")) { + allow_invalid_path = 1; + } else if (!strcmp(argv[i], "--raw-path")) { + raw_path = 1; } else if (!strcmp(argv[i], "--dir-handle")) { if (++i >= argc || parse_u8(argv[i], &dir_handle)) { fprintf(stderr, "invalid --dir-handle value\n"); @@ -117,17 +197,32 @@ int main(int argc, char **argv) return 2; } - path_len = strlen(path); + request_path = path; + if (!raw_path && dir_handle == 0 && + split_volume_path(path, volume, sizeof(volume), &request_path) == 0) { + err = allocate_temp_dir_handle(conn, volume, &dir_handle); + if (err) { + fprintf(stderr, + "Allocate Temp Dir Handle failed: completion=0x%02x (%u) volume=%s path=%s\n", + (unsigned int)err & 0xff, (unsigned int)err, volume, path); + ncp_close(conn); + return 1; + } + allocated_dir_handle = dir_handle; + } + + path_len = strlen(request_path); if (path_len > 255) { fprintf(stderr, "PATH is too long for AFP Get Entry ID From Path Name: %zu\n", path_len); + deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 2; } request[0] = (uint8_t)dir_handle; request[1] = (uint8_t)path_len; - memcpy(request + 2, path, path_len); + memcpy(request + 2, request_path, path_len); memset(reply_buf, 0, sizeof(reply_buf)); reply.fragAddr.rw = reply_buf; @@ -139,32 +234,47 @@ int main(int argc, char **argv) 2 + path_len, &reply); - if (err == NWE_INVALID_NAMESPACE && allow_invalid_namespace) { + if (((unsigned int)err & 0xff) == NWE_INVALID_NAMESPACE && allow_invalid_namespace) { printf("AFP Get Entry ID From Path Name returned invalid namespace " - "as expected: path=%s\n", path); + "as expected: path=%s request_path=%s dir_handle=%u\n", + path, request_path, dir_handle); + deallocate_dir_handle(conn, allocated_dir_handle); + ncp_close(conn); + return 0; + } + + if (((unsigned int)err & 0xff) == NWE_INVALID_PATH && allow_invalid_path) { + printf("AFP Get Entry ID From Path Name returned invalid path " + "as expected: path=%s request_path=%s dir_handle=%u\n", + path, request_path, dir_handle); + deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 0; } if (err) { fprintf(stderr, - "AFP Get Entry ID From Path Name failed: completion=0x%02x (%u) path=%s\n", - (unsigned int)err & 0xff, (unsigned int)err, path); + "AFP Get Entry ID From Path Name failed: completion=0x%02x (%u) path=%s request_path=%s dir_handle=%u\n", + (unsigned int)err & 0xff, (unsigned int)err, path, + request_path, dir_handle); + deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 1; } if (reply.fragSize < 4) { fprintf(stderr, "short AFP reply: %zu bytes\n", reply.fragSize); + deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 1; } - printf("AFP Entry ID path=%s dir_handle=%u entry_id=0x%08x (%u)\n", - path, dir_handle, + printf("AFP Entry ID path=%s request_path=%s dir_handle=%u entry_id=0x%08x (%u)\n", + path, request_path, dir_handle, (unsigned int)be32_to_cpu(reply_buf), (unsigned int)be32_to_cpu(reply_buf)); + deallocate_dir_handle(conn, allocated_dir_handle); ncp_close(conn); return 0; }