dosutils: add RIGHTS and finish Novell-style display fixes

- add RIGHTS as a new multi-call DOS utility
- implement Client32 NCP87 effective-rights query helper
- map NCP effective-rights bits to Novell RIGHTS display order
- keep legacy directory-handle based rights lookup as fallback
- match Novell RIGHTS output for directories and files more closely
- remove hardcoded MARS/SYS and SYS display fallbacks from RIGHTS/FLAGDIR
- derive display prefixes from the active network drive where available
- adjust FLAGDIR output formatting to match Novell field alignment
- keep FLAG and SLIST unchanged after checking for hardcoded prefixes
This commit is contained in:
Mario Fetka
2026-05-24 11:20:07 +02:00
parent 456349088e
commit 9ab65e2f00
9 changed files with 682 additions and 13 deletions

View File

@@ -55,6 +55,7 @@ if(MARS_NWE_BUILD_DOSUTILS)
slist.c
flag.c
flagdir.c
rights.c
c32ncp.c
nwcrypt.c
nwdebug.c

View File

@@ -40,6 +40,7 @@ Still to validate or continue:
- Server listing through `SLIST`
- File attribute management through `FLAG`
- Directory attribute management through `FLAGDIR`
- Effective rights display through `RIGHTS`
- Optional mars_nwe debug control hooks
- Developer diagnostics through `TESTS`
@@ -63,6 +64,7 @@ The current command dispatcher includes these built-ins:
- `SLIST`
- `FLAG`
- `FLAGDIR`
- `RIGHTS`
- `DEBUG`
- `ECHO`
- `CD`
@@ -114,6 +116,7 @@ This path is currently used by:
- `FLAG`
- `FLAGDIR`
- `RIGHTS`
The old `Net_Call` / INT 21h requester path is kept as a fallback where appropriate, but Client32 is now preferred for the validated FLAG-family operations.
@@ -363,6 +366,30 @@ MARS/SYS:UDIR
`Private` is intentionally rejected for the current NetWare 386-style path.
### `RIGHTS`
Display effective NetWare rights for a file or directory.
Typical usage:
```text
RIGHTS [path]
```
Supported in this first version:
- directory paths
- file paths, using the parent directory rights for the first read-only implementation
- Novell-like display of the effective rights mask
Rights are shown in the traditional order:
```text
S R W C E M F A
Supervisor, Read, Write, Create, Erase, Modify, File scan, Access Control
```
### `DEBUG`
Set mars_nwe debug levels for selected server-side modules.

173
c32ncp.c
View File

@@ -97,6 +97,85 @@ static UI c32_build_handle_path(uint8 *buf, uint8 dhandle,
return(used);
}
static UI c32_build_handle_path_from_dos_path(uint8 *buf, uint8 dhandle,
uint16 dirbase, uint8 style,
const char *dospath)
{
uint8 *p;
uint8 *countp;
int count = 0;
const char *s;
UI used;
memset(buf, 0, 0x140);
if (dhandle) {
c32_put_word_lh(buf + 1, (uint16)dhandle);
c32_put_word_lh(buf + 3, dirbase);
buf[5] = style;
} else {
buf[5] = 0xff;
}
p = buf + 6;
countp = p++;
s = dospath;
if (!s) s = "";
/*
* DOS tools mostly pass relative paths against the current directory
* handle. Accept simple DOS decoration here so RIGHTS can pass "." or
* ".\\UDIR\\FILE" without constructing path components in the caller.
*/
if (s[0] && s[1] == ':')
s += 2;
while (*s == '\\' || *s == '/')
s++;
while (*s && p < buf + 0x138 && count < 32) {
const char *start;
int len;
while (*s == '\\' || *s == '/')
s++;
if (*s == '.'
&& (s[1] == '\0' || s[1] == '\\' || s[1] == '/')) {
s++;
continue;
}
start = s;
while (*s && *s != '\\' && *s != '/')
s++;
len = (int)(s - start);
if (len <= 0)
continue;
if (len > 255)
len = 255;
if (p + 1 + len >= buf + 0x138)
break;
*p++ = (uint8)len;
memcpy(p, start, len);
p += len;
count++;
}
*countp = (uint8)count;
used = (UI)(p - buf);
c32_put_word_lh(buf + 0x13c, used);
return(used);
}
/*
* Current verified Client32 path for mars-nwe DOS utilities:
*
@@ -307,3 +386,97 @@ int c32_ncp87_modify_dos_attributes(char *name,
}
int c32_ncp87_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 = c32_get_ncp_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 */
c32_put_word_lh(hdr + 3, 0x0006); /* SA_ALL */
c32_put_dword_lh(hdr + 5, 0L); /* reserved */
path_len = c32_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));
C32_NCP87_Raw5_Probe(handle_lo, handle_hi,
hdr, 9,
path, path_len,
rep0, sizeof(rep0),
rep1, sizeof(rep1),
rawout);
raw_ret_ax = c32_get_word_lh(rawout + 14);
raw_ret_dx = c32_get_word_lh(rawout + 16);
actual_lo = c32_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 = c32_get_word_lh(rep0 + 0);
rights4 = c32_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);
}

View File

@@ -19,4 +19,11 @@ int c32_ncp87_modify_dos_attributes(char *name,
uint16 *handle_lo_out,
uint16 *handle_hi_out);
int c32_ncp87_get_effective_rights(const char *path,
uint16 dir_handle,
uint16 *rights_out,
uint16 *actual_out,
uint16 *handle_lo_out,
uint16 *handle_hi_out);
#endif

View File

@@ -75,6 +75,61 @@ static int fd_current_dhandle(uint8 *dhandle)
return(0);
}
static int fd_current_prefix(char *out, int max)
{
uint8 connid = 0;
uint8 dhandle = 0;
uint8 flags = 0;
int drive;
char server[52];
char path[260];
char volume[32];
char *p;
int i = 0;
if (!out || max < 8)
return(-1);
out[0] = '\0';
drive = fd_get_current_drive();
if (get_drive_info((uint8)drive, &connid, &dhandle, &flags))
return(-1);
if (!connid || (flags & 0x80))
return(-1);
server[0] = '\0';
if (get_fs_name(connid, server))
server[0] = '\0';
path[0] = '\0';
if (get_dir_path(dhandle, path) || !path[0])
return(-1);
p = strchr(path, ':');
if (!p)
return(-1);
while (path + i < p && i < (int)sizeof(volume) - 1) {
volume[i] = path[i];
i++;
}
volume[i] = '\0';
if (!volume[0])
return(-1);
if (server[0])
sprintf(out, "%s/%s:", server, volume);
else
sprintf(out, "%s:", volume);
return(0);
}
static void fd_help(void)
{
fprintf(stdout, "386 Usage: Flagdir [path [option...]]\n");
@@ -118,17 +173,13 @@ static void fd_display(char *path, uint32 attrs)
fd_upcopy(up, path, sizeof(up));
/*
* Novell FLAGDIR style:
*
* MARS/SYS:UDIR
* UDIR System Hidden ...
*
* For now we match the tested MARS/SYS: environment used by PUBLIC/NPUBLIC.
* The NCP logic is independent from this cosmetic header.
*/
fprintf(stdout, "MARS/SYS:%s\n", up);
fprintf(stdout, " %-10s ", up);
{
char prefix[90];
if (fd_current_prefix(prefix, sizeof(prefix)))
prefix[0] = '\0';
fprintf(stdout, "%s%s \n", prefix, up);
}
fprintf(stdout, " %-12.12s ", up);
if (!(attrs & FD_DIR_BITS)) {
fprintf(stdout, "Normal\n");

1
net.c
View File

@@ -36,6 +36,7 @@ static struct s_net_functions {
{"LOGOUT", "logout from server", func_logout , 0},
{"FLAG", "display or modify file attributes", func_flag , 0},
{"FLAGDIR","display or modify directory attributes",func_flagdir, 0},
{"RIGHTS", "display effective file/directory rights",func_rights, 0},
{"SLIST", "list servers", func_slist , 0},
{"PASSWD", "change password", func_passwd , 0},
#if 1

1
net.h
View File

@@ -255,6 +255,7 @@ extern int func_capture(int argc, char *argv[], int mode);
/* flag.c */
extern int func_flag (int argc, char *argv[], int mode);
extern int func_flagdir(int argc, char *argv[], int mode);
extern int func_rights (int argc, char *argv[], int mode);
extern int ncp_17_37(uint32 last_id, uint16 objtyp, uint8 *pattern,

408
rights.c Normal file
View File

@@ -0,0 +1,408 @@
/* rights.c - Novell RIGHTS-like DOS utility, read-only v4 */
#include "net.h"
#include "c32ncp.h"
#include <dos.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef S_IFDIR
#define S_IFDIR 0040000
#endif
#define NW_RIGHT_S 0x01
#define NW_RIGHT_R 0x02
#define NW_RIGHT_W 0x04
#define NW_RIGHT_C 0x08
#define NW_RIGHT_E 0x10
#define NW_RIGHT_M 0x20
#define NW_RIGHT_F 0x40
#define NW_RIGHT_A 0x80
/* NCP effective-rights bits returned by NCP87 subfunction 29. */
#define NCP_RIGHT_READ 0x0001
#define NCP_RIGHT_WRITE 0x0002
#define NCP_RIGHT_OPEN 0x0004
#define NCP_RIGHT_CREATE 0x0008
#define NCP_RIGHT_DELETE 0x0010
#define NCP_RIGHT_OWNER 0x0020
#define NCP_RIGHT_SEARCH 0x0040
#define NCP_RIGHT_MODIFY 0x0080
#define NCP_RIGHT_SUPER 0x0100
static uint8 rights_map_ncp_mask(uint16 ncp_rights);
static int rights_same(char *a, char *b)
{
while (*a || *b) {
int ca = *a++;
int cb = *b++;
if (ca >= 'a' && ca <= 'z') ca -= 32;
if (cb >= 'a' && cb <= 'z') cb -= 32;
if (ca != cb) return(0);
}
return(1);
}
static int rights_is_help(char *s)
{
if (!s) return(0);
return(rights_same(s, "/?") || rights_same(s, "-?") || rights_same(s, "?"));
}
static void rights_usage(void)
{
fprintf(stdout, "Usage: RIGHTS [path]\n\n");
fprintf(stdout, "Rights = All | Supervisor | Read | Write | Create | Erase\n");
fprintf(stdout, " Modify | File scan | Access Control\n");
}
static int rights_get_current_drive(void)
{
REGS regs;
regs.h.ah = 0x19;
int86(0x21, &regs, &regs);
return((int)regs.h.al);
}
static int rights_current_dhandle(uint8 *connid, uint8 *dhandle)
{
uint8 flags = 0;
int drive = rights_get_current_drive();
if (get_drive_info((uint8)drive, connid, dhandle, &flags))
return(-1);
if (!*connid || (flags & 0x80))
return(-1);
return(0);
}
static int rights_is_current_path(char *path)
{
if (!path || !*path) return(1);
if (rights_same(path, ".")) return(1);
if (rights_same(path, ".\\")) return(1);
if (rights_same(path, "./")) return(1);
return(0);
}
static void rights_upcopy(char *dst, char *src, int max)
{
int i = 0;
if (!src) src = "";
while (*src && i < max - 1) {
char c = *src++;
if (c == '/') c = '\\';
if (c >= 'a' && c <= 'z') c -= 32;
dst[i++] = c;
}
dst[i] = 0;
}
static void rights_parent_path(char *dst, char *src, int max)
{
char tmp[260];
char *p;
rights_upcopy(tmp, src, sizeof(tmp));
p = strrchr(tmp, '\\');
if (!p) p = strrchr(tmp, ':');
if (!p) {
dst[0] = '\0';
return;
}
if (*p == ':') {
p++;
*p = '\0';
} else {
*p = '\0';
}
strmaxcpy(dst, tmp, max - 1);
}
static int rights_path_is_dir(char *path)
{
struct stat st;
if (rights_is_current_path(path))
return(1);
if (stat(path, &st) == 0) {
if (st.st_mode & S_IFDIR)
return(1);
}
return(0);
}
static int rights_current_prefix(char *out, int max)
{
uint8 connid = 0;
uint8 dhandle = 0;
uint8 flags = 0;
int drive;
char server[52];
char dpath[260];
char volume[32];
char *p;
int i = 0;
if (!out || max < 8)
return(-1);
out[0] = '\0';
drive = rights_get_current_drive();
if (get_drive_info((uint8)drive, &connid, &dhandle, &flags))
return(-1);
if (!connid || (flags & 0x80))
return(-1);
server[0] = '\0';
if (get_fs_name(connid, server))
server[0] = '\0';
dpath[0] = '\0';
if (get_dir_path(dhandle, dpath) || !dpath[0])
return(-1);
p = strchr(dpath, ':');
if (!p)
return(-1);
while (dpath + i < p && i < (int)sizeof(volume) - 1) {
volume[i] = dpath[i];
i++;
}
volume[i] = '\0';
if (!volume[0])
return(-1);
if (server[0])
sprintf(out, "%s/%s:", server, volume);
else
sprintf(out, "%s:", volume);
return(0);
}
static void rights_make_header_path(char *out, char *path)
{
char up[260];
char prefix[90];
if (rights_current_prefix(prefix, sizeof(prefix)))
prefix[0] = '\0';
if (rights_is_current_path(path)) {
strcpy(out, prefix);
return;
}
rights_upcopy(up, path, sizeof(up));
strcpy(out, prefix);
strcat(out, up);
}
static int rights_effective_mask(char *path, int is_dir, uint8 *mask)
{
uint8 connid = 0;
uint8 dhandle = 0;
uint8 eff = 0;
char usepath[260];
int newhandle;
uint16 ncp_rights = 0;
if (mask) *mask = 0;
if (rights_current_dhandle(&connid, &dhandle))
return(-1);
/*
* Prefer the explicit Client32 NCP87 effective-rights request. This is
* the DOS Client32 equivalent of ncpfs ncp_get_eff_directory_rights().
* It works for both files and directories, so pass the requested path
* itself instead of mapping files to their parent directory.
*/
if (!c32_ncp87_get_effective_rights(rights_is_current_path(path) ? "" : path,
(uint16)dhandle,
&ncp_rights,
NULL, NULL, NULL)) {
if (mask) *mask = rights_map_ncp_mask(ncp_rights);
return(0);
}
/*
* Fallback for older requesters/paths: allocate a temporary directory
* handle and use the returned old-style effective-rights byte.
* This cannot directly target a file, so files are mapped to their parent.
*/
if (rights_is_current_path(path)) {
usepath[0] = '\0';
} else if (is_dir) {
rights_upcopy(usepath, path, sizeof(usepath));
} else {
rights_parent_path(usepath, path, sizeof(usepath));
}
newhandle = alloc_temp_dir_handle(dhandle, usepath, 0, &eff);
if (newhandle < 0) {
if (usepath[0]) {
int subdir = 1;
int r = ncp_16_02(dhandle, (uint8 *)usepath, &subdir,
NULL, NULL, NULL);
if (r >= 0) {
eff = (uint8)r;
} else {
return(-1);
}
} else {
eff = 0xff;
}
} else {
dealloc_dir_handle(newhandle);
}
if (mask) *mask = eff;
return(0);
}
static uint8 rights_map_ncp_mask(uint16 ncp_rights)
{
uint8 mask = 0;
if (ncp_rights & NCP_RIGHT_SUPER) mask |= NW_RIGHT_S;
if (ncp_rights & NCP_RIGHT_READ) mask |= NW_RIGHT_R;
if (ncp_rights & NCP_RIGHT_WRITE) mask |= NW_RIGHT_W;
if (ncp_rights & NCP_RIGHT_CREATE) mask |= NW_RIGHT_C;
if (ncp_rights & NCP_RIGHT_DELETE) mask |= NW_RIGHT_E;
if (ncp_rights & NCP_RIGHT_MODIFY) mask |= NW_RIGHT_M;
if (ncp_rights & NCP_RIGHT_SEARCH) mask |= NW_RIGHT_F;
if (ncp_rights & NCP_RIGHT_OWNER) mask |= NW_RIGHT_A;
return(mask);
}
static void rights_mask_string(uint8 mask, char *out)
{
out[0] = (mask & NW_RIGHT_S) ? 'S' : '-';
out[1] = (mask & NW_RIGHT_R) ? 'R' : '-';
out[2] = (mask & NW_RIGHT_W) ? 'W' : '-';
out[3] = (mask & NW_RIGHT_C) ? 'C' : '-';
out[4] = (mask & NW_RIGHT_E) ? 'E' : '-';
out[5] = (mask & NW_RIGHT_M) ? 'M' : '-';
out[6] = (mask & NW_RIGHT_F) ? 'F' : '-';
out[7] = (mask & NW_RIGHT_A) ? 'A' : '-';
out[8] = '\0';
}
/*
* Novell RIGHTS layout:
* - lines with star: two leading blanks, star, blank, text
* - lines without star: four leading blanks, text
* Keep the right-column letter at a fixed-ish DOS-screen position.
*/
static void rights_print_line(int have, int star, char *text, char letter)
{
(void)have;
if (star)
fprintf(stdout, " * %-43s(%c)\n", text, letter);
else
fprintf(stdout, " %-43s(%c)\n", text, letter);
}
static void rights_display(char *path, int is_dir, uint8 mask)
{
char hdr[300];
char mstr[10];
rights_make_header_path(hdr, path);
rights_mask_string(mask, mstr);
fprintf(stdout, "%s\n", hdr);
if (is_dir)
fprintf(stdout, "Your Effective Rights for this directory are [%s]\n", mstr);
else
fprintf(stdout, "Your Effective Rights for this file are [%s]\n", mstr);
if (is_dir) {
rights_print_line(mask & NW_RIGHT_S, 0, "You have Supervisor Rights to Directory.", 'S');
rights_print_line(mask & NW_RIGHT_R, 1, "May Read from File.", 'R');
rights_print_line(mask & NW_RIGHT_W, 1, "May Write to File.", 'W');
rights_print_line(mask & NW_RIGHT_C, 0, "May Create Subdirectories and Files.", 'C');
rights_print_line(mask & NW_RIGHT_E, 0, "May Erase Directory.", 'E');
rights_print_line(mask & NW_RIGHT_M, 0, "May Modify Directory.", 'M');
rights_print_line(mask & NW_RIGHT_F, 0, "May Scan for Files.", 'F');
rights_print_line(mask & NW_RIGHT_A, 0, "May Change Access Control.", 'A');
fprintf(stdout, "\n");
fprintf(stdout, " * Has no effect on directory.\n\n");
fprintf(stdout, " Entries in Directory May Inherit [%s] rights.\n", mstr);
if (mask == 0xff)
fprintf(stdout, " You have ALL RIGHTS to Directory Entry.\n");
} else {
rights_print_line(mask & NW_RIGHT_S, 0, "You have Supervisor Rights to File.", 'S');
rights_print_line(mask & NW_RIGHT_R, 0, "May Read from File.", 'R');
rights_print_line(mask & NW_RIGHT_W, 0, "May Write to File.", 'W');
rights_print_line(mask & NW_RIGHT_C, 1, "May Create Subdirectories and Files.", 'C');
rights_print_line(mask & NW_RIGHT_E, 0, "May Erase File.", 'E');
rights_print_line(mask & NW_RIGHT_M, 0, "May Modify File.", 'M');
rights_print_line(mask & NW_RIGHT_F, 0, "May Scan for File.", 'F');
rights_print_line(mask & NW_RIGHT_A, 0, "May Change Access Control.", 'A');
fprintf(stdout, "\n");
fprintf(stdout, " * Create is necessary to salvage a file that has been deleted.\n\n");
if (mask == 0xff)
fprintf(stdout, " You have ALL RIGHTS to Directory Entry.\n");
}
}
int func_rights(int argc, char *argv[], int mode)
{
char *path = ".";
uint8 mask = 0;
int is_dir;
(void)mode;
if (argc > 2) {
rights_usage();
return(1);
}
if (argc == 2) {
if (rights_is_help(argv[1])) {
rights_usage();
return(0);
}
path = argv[1];
}
is_dir = rights_path_is_dir(path);
if (rights_effective_mask(path, is_dir, &mask)) {
fprintf(stdout, "Specified path not locatable.\n");
return(1);
}
rights_display(path, is_dir, mask);
return(0);
}

View File

@@ -22,11 +22,11 @@ int main()
close(fd);
_chmod(fn, 1, _chmod(fn, 0) | 0x80 );
stat(fn, &stbuff);
printf("Filesize <20>ber stat =%ld\n", stbuff.st_size);
printf("Filesize <20>ber stat =%ld\n", stbuff.st_size);
fd = open(fn, O_RDWR | O_BINARY |O_DENYNONE);
offset = lseek(fd, 0L, SEEK_END);
printf("Filesize <20>ber lseek =%ld\n", offset);
printf("Filesize <20>ber lseek =%ld\n", offset);
write(fd, buff, strlen(buff));
lseek(fd, 0L, SEEK_SET);