diff --git a/ENDPOINTS.md b/ENDPOINTS.md index 99dfec1..da8e6f2 100644 --- a/ENDPOINTS.md +++ b/ENDPOINTS.md @@ -152,12 +152,31 @@ MARS routes many NCP 23 selectors through `src/nwbind.c`; some FSE monitor/admin ### Direct file and lock NCPs +The WebSDK `NCP Calls` index for NetWare 1.x/2.x/3.x selectors before the +Directory Services group (`22`) currently yields these direct or nested groups: + +- `0x2222/03`-`0x2222/0E`: old file and logical-record log/lock/release/clear. +- `0x2222/17/06` and `0x2222/17/10`: direct print/printer status queue helpers. +- `0x2222/18`: Get Volume Info With Number. +- `0x2222/20`: Get File Server Date And Time. +- `0x2222/21/00`, `/01`, `/02`, `/03`, `/09`, `/10`, `/11`: old/new broadcast + and broadcast-mode/message calls. + +`tests/ncp/ncp_pre22_smoke.c` directly covers the stable old compatibility +surface for `18`, `19`, `20`, and `21/00`-`21/03`. The lock and direct-print +families need separate semantic tests because they depend on file-lock state or +spool/printer backend behaviour. + | PDF decimal / code | Name | MARS status | Handler / notes | | --- | --- | --- | --- | | `0x2222/03` | Log File | implemented | `src/nwconn.c`; old lock/log path. | | `0x2222/04`, `0x2222/6A` | Lock File Set old/new | partial | Lock semantics are compatibility-level. | | `0x2222/05`, `0x2222/06`, `0x2222/07`, `0x2222/08` | Release/Clear File and File Set | partial | Check old-client locking behaviour. | | `0x2222/09`-`0x2222/0E`, `0x2222/6B`, `0x2222/6C` | Logical record log/lock/release/clear | partial | Active compatibility paths; tests needed. | +| `0x2222/11` / `17/06`, `17/10` | Direct Print / Printer Status Queue helpers | open | `src/nwconn.c` has only a disabled documentation stub; queue printing exists elsewhere, but these direct old spool NCPs still need implementation/tests. | +| `0x2222/12` / `18` | Get Volume Info With Number | implemented | Direct old call covered by `ncp.pre22.smoke`. | +| `0x2222/14` / `20` | Get File Server Date And Time | implemented | Direct old call covered by `ncp.pre22.smoke`. | +| `0x2222/15` / `21/00`-`21/03`, `21/09`-`21/11` | Message / Broadcast group | implemented | Forwarded to `src/nwbind.c`; old send/get and enable/disable covered by `ncp.pre22.smoke`. | | `0x2222/1A`-`0x2222/1F`, `0x2222/6D`, `0x2222/6E` | Physical record log/lock/release/clear | partial | Active compatibility paths; tests needed. | | `0x2222/41`-`0x2222/4D`, `0x2222/54` | Old direct file open/create/close/read/write/rename/erase | implemented/partial | Active in `src/nwconn.c`; file write now covered indirectly by quota smokes. | | `0x2222/4F` | Set File Extended Attributes | stub/no-op | Active compatibility stub; document request layout before changing. | diff --git a/src/nwconn.c b/src/nwconn.c index 9d9b302..83207c3 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -3501,7 +3501,7 @@ static int handle_ncp_serv(void) U16_TO_BE16(fsp.fsu_files, xdata->total_dirs); U16_TO_BE16(fsp.fsu_ffree, xdata->avail_dirs); if ( get_volume_options(volume) & VOL_OPTION_REMOUNT) { - U16_TO_BE16(1, xdata->removable); + U16_TO_BE16(0xffff, xdata->removable); } else { U16_TO_BE16(0, xdata->removable); } diff --git a/tests/README.md b/tests/README.md index 448b87c..97c5968 100644 --- a/tests/README.md +++ b/tests/README.md @@ -47,6 +47,12 @@ builds list them, but they print `SKIP/WARN` and exit with CTest skip code 77 unless `MARS_NWE_RUN_NETWORKED_TESTS=1` is set. This gives the test report a warning-like skipped entry instead of making normal offline builds fail. +`ncp.pre22.smoke` is the direct NetWare 1.x/2.x/3.x compatibility smoke for +selected NCPs before the NCP 22 Directory Services group. It uses raw +`NWRequestSimple` calls for `18` Get Volume Info With Number, `19` Get Station +Number, `20` Get File Server Date And Time, and the old `21/00`-`21/03` +broadcast paths. + Run only offline tests with: ```sh diff --git a/tests/ncp/CMakeLists.txt b/tests/ncp/CMakeLists.txt index ec80a32..b7240db 100644 --- a/tests/ncp/CMakeLists.txt +++ b/tests/ncp/CMakeLists.txt @@ -17,6 +17,28 @@ add_executable(ncp22_directory_smoke ncp22_directory_smoke.c) target_include_directories(ncp22_directory_smoke PRIVATE ${NCPFS_INCLUDE_DIR}) target_link_libraries(ncp22_directory_smoke ${NCPFS_LIBRARY}) +add_executable(ncp_pre22_smoke ncp_pre22_smoke.c) +target_include_directories(ncp_pre22_smoke PRIVATE ${NCPFS_INCLUDE_DIR}) +target_link_libraries(ncp_pre22_smoke ${NCPFS_LIBRARY}) + +add_test(NAME ncp.pre22.smoke + COMMAND sh ${MARS_NWE_NETWORKED_CTEST_GATE} + ncp.pre22.smoke + "a running mars-nwe server, NCPFS tools, credentials, and a readable NetWare volume path" + MARS_NWE_TEST_SERVER + MARS_NWE_TEST_ADMIN + MARS_NWE_TEST_PASSWORD + -- + sh -c "exec \"\$1\" -S \"\$MARS_NWE_TEST_SERVER\" -U \"\$MARS_NWE_TEST_ADMIN\" -P \"\$MARS_NWE_TEST_PASSWORD\" \"\${MARS_NWE_TEST_NCP22_PATH:-SYS:PUBLIC}\"" + sh + $ +) +set_tests_properties(ncp.pre22.smoke PROPERTIES + LABELS "networked;integration;ncp;pre22" + SKIP_RETURN_CODE 77 + TIMEOUT 120 +) + add_test(NAME ncp22.directory.smoke COMMAND sh ${MARS_NWE_NETWORKED_CTEST_GATE} ncp22.directory.smoke diff --git a/tests/ncp/ncp_pre22_smoke.c b/tests/ncp/ncp_pre22_smoke.c new file mode 100644 index 0000000..ad0b2a0 --- /dev/null +++ b/tests/ncp/ncp_pre22_smoke.c @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Direct network smoke test for NetWare 1.x/2.x/3.x-compatible NCPs before + * the NCP 22 Directory Services group. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifndef NCPC_SUBFUNCTION +#define NCPC_SUBFUNCTION 0x10000 +#endif +#ifndef NCPC_SFN +#define NCPC_SFN(FN, SFN) ((FN) | ((SFN) << 8) | NCPC_SUBFUNCTION) +#endif + +#define NWE_INVALID_PATH 0x9c + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [ncpfs options] VOL:PATH\n" + "\n" + "Exercises selected pre-22 NetWare 1.x/2.x/3.x-compatible NCPs:\n" + " 18 Get Volume Info With Number\n" + " 19 Get Station Number\n" + " 20 Get File Server Date And Time\n" + " 21/00 Send Broadcast Message (old)\n" + " 21/01 Get Broadcast Message (old)\n" + " 21/02 Disable Broadcasts, 21/03 Enable Broadcasts\n" + "\n" + "Example:\n" + " %s -S MARS -U SUPERVISOR -P secret SYS:PUBLIC\n", + prog, prog); +} + +static uint16_t be16_to_cpu(const uint8_t p[2]) +{ + return ((uint16_t)p[0] << 8) | (uint16_t)p[1]; +} + +static long get_station_number(NWCONN_HANDLE conn, uint8_t *station) +{ + uint8_t reply_buf[3]; + NW_FRAGMENT reply; + long err; + + memset(reply_buf, 0, sizeof(reply_buf)); + reply.fragAddr.rw = reply_buf; + reply.fragSize = sizeof(reply_buf); + + err = NWRequestSimple(conn, 19, NULL, 0, &reply); + if (err) + return err; + if (reply.fragSize < 1 || reply_buf[0] == 0) + return NWE_INVALID_PATH; + + *station = reply_buf[0]; + printf("NCP 19 station=%u reply_len=%lu\n", + (unsigned int)*station, (unsigned long)reply.fragSize); + return 0; +} + +static long check_volume_info(NWCONN_HANDLE conn, const char *path) +{ + char volume_name[256]; + uint8_t request[1]; + uint8_t reply_buf[28]; + NW_FRAGMENT reply; + char returned_name[17]; + int volume_number = -1; + char *colon; + long err; + + snprintf(volume_name, sizeof(volume_name), "%s", path); + colon = strchr(volume_name, ':'); + if (colon) + *colon = '\0'; + if (!volume_name[0]) + return NWE_INVALID_PATH; + + err = ncp_get_volume_number(conn, volume_name, &volume_number); + if (err) + return err; + if (volume_number < 0 || volume_number > 255) + return NWE_INVALID_PATH; + + request[0] = (uint8_t)volume_number; + memset(reply_buf, 0, sizeof(reply_buf)); + reply.fragAddr.rw = reply_buf; + reply.fragSize = sizeof(reply_buf); + + err = NWRequestSimple(conn, 18, request, sizeof(request), &reply); + if (err) + return err; + if (reply.fragSize < sizeof(reply_buf)) + return NWE_INVALID_PATH; + + memcpy(returned_name, reply_buf + 10, 16); + returned_name[16] = '\0'; + printf("NCP 18 volume=%d name=%s sectors_per_cluster=%u total_clusters=%u avail_clusters=%u removable=%u\n", + volume_number, returned_name, + (unsigned int)be16_to_cpu(reply_buf + 0), + (unsigned int)be16_to_cpu(reply_buf + 2), + (unsigned int)be16_to_cpu(reply_buf + 4), + (unsigned int)be16_to_cpu(reply_buf + 26)); + if (strncasecmp(returned_name, volume_name, strlen(volume_name))) + return NWE_INVALID_PATH; + if (be16_to_cpu(reply_buf + 26) != 0 && + be16_to_cpu(reply_buf + 26) != 0xffff) + return NWE_INVALID_PATH; + return 0; +} + +static long check_datetime(NWCONN_HANDLE conn) +{ + uint8_t reply_buf[7]; + NW_FRAGMENT reply; + long err; + + memset(reply_buf, 0, sizeof(reply_buf)); + reply.fragAddr.rw = reply_buf; + reply.fragSize = sizeof(reply_buf); + + err = NWRequestSimple(conn, 20, NULL, 0, &reply); + if (err) + return err; + if (reply.fragSize < sizeof(reply_buf)) + return NWE_INVALID_PATH; + + printf("NCP 20 date=%u-%02u-%02u time=%02u:%02u:%02u dow=%u\n", + 1900U + (unsigned int)reply_buf[0], + (unsigned int)reply_buf[1], (unsigned int)reply_buf[2], + (unsigned int)reply_buf[3], (unsigned int)reply_buf[4], + (unsigned int)reply_buf[5], (unsigned int)reply_buf[6]); + + if (reply_buf[1] < 1 || reply_buf[1] > 12 || + reply_buf[2] < 1 || reply_buf[2] > 31 || + reply_buf[3] > 23 || reply_buf[4] > 59 || + reply_buf[5] > 59 || reply_buf[6] > 6) + return NWE_INVALID_PATH; + return 0; +} + +static long check_broadcasts(NWCONN_HANDLE conn, uint8_t station) +{ + static const char message[] = "mars_nwe pre22 broadcast"; + uint8_t request[1 + 1 + sizeof(message)]; + uint8_t reply_buf[64]; + NW_FRAGMENT reply; + long err; + size_t message_len = strlen(message); + + err = NWRequestSimple(conn, NCPC_SFN(21, 0x02), NULL, 0, NULL); + if (err) + return err; + err = NWRequestSimple(conn, NCPC_SFN(21, 0x03), NULL, 0, NULL); + if (err) + return err; + + request[0] = 1; + request[1] = station; + request[2] = (uint8_t)message_len; + memcpy(request + 3, message, message_len); + + memset(reply_buf, 0, sizeof(reply_buf)); + reply.fragAddr.rw = reply_buf; + reply.fragSize = sizeof(reply_buf); + + err = NWRequestSimple(conn, NCPC_SFN(21, 0x00), + request, 3 + message_len, &reply); + if (err) + return err; + if (reply.fragSize < 2 || reply_buf[0] != 1 || reply_buf[1] != 0) + return NWE_INVALID_PATH; + + memset(reply_buf, 0, sizeof(reply_buf)); + reply.fragAddr.rw = reply_buf; + reply.fragSize = sizeof(reply_buf); + + err = NWRequestSimple(conn, NCPC_SFN(21, 0x01), NULL, 0, &reply); + if (err) + return err; + if (reply.fragSize < 1 + message_len || + reply_buf[0] != message_len || + memcmp(reply_buf + 1, message, message_len)) + return NWE_INVALID_PATH; + + printf("NCP 21/00+01 old broadcast roundtrip station=%u message=%s\n", + (unsigned int)station, message); + return 0; +} + +int main(int argc, char **argv) +{ + NWCONN_HANDLE conn; + long init_err = 0; + const char *path = NULL; + uint8_t station = 0; + long err; + int i; + + if (NWCallsInit(NULL, NULL)) { + fprintf(stderr, "NWCallsInit failed\n"); + return 2; + } + + conn = ncp_initialize(&argc, argv, 1, &init_err); + if (!conn) { + fprintf(stderr, "ncp_initialize/login failed: %ld\n", init_err); + usage(argv[0]); + return 2; + } + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + usage(argv[0]); + ncp_close(conn); + return 0; + } else if (!path) { + path = argv[i]; + } else { + fprintf(stderr, "unexpected argument: %s\n", argv[i]); + usage(argv[0]); + ncp_close(conn); + return 2; + } + } + + if (!path || !strchr(path, ':')) { + usage(argv[0]); + ncp_close(conn); + return 2; + } + + err = check_volume_info(conn, path); + if (err) { + fprintf(stderr, "NCP 18 failed: path=%s error=0x%04x\n", + path, (unsigned int)err); + ncp_close(conn); + return 1; + } + + err = get_station_number(conn, &station); + if (err) { + fprintf(stderr, "NCP 19 failed: error=0x%04x\n", (unsigned int)err); + ncp_close(conn); + return 1; + } + + err = check_datetime(conn); + if (err) { + fprintf(stderr, "NCP 20 failed: error=0x%04x\n", (unsigned int)err); + ncp_close(conn); + return 1; + } + + err = check_broadcasts(conn, station); + if (err) { + fprintf(stderr, "NCP 21 broadcast check failed: error=0x%04x\n", + (unsigned int)err); + ncp_close(conn); + return 1; + } + + ncp_close(conn); + return 0; +}