Files
mars-dosutils/remove.c
Mario Fetka 5da600c2a5 dosutils: match Novell paths for flags and trustees
Move FLAG, GRANT and REMOVE closer to the request paths used by the
Novell tools and extend the DOS comparison tests.

FLAG now reads attributes through the old NCP22 directory scan path and
writes them through NCP22/25 Set Directory/File Information. This keeps
extended attributes such as Delete Inhibit and Rename Inhibit intact and
matches the Novell behavior observed in the server logs.

GRANT now prefers NCP22/27 SetTrustee with an NCP87 fallback. Supervisor
rights are expanded like Novell does, so granting S sends and reports the
full SRWCEMFA mask. The visible output, path formatting and error text
are adjusted to match the Novell baseline.

REMOVE now prefers NCP22/2B DelTrustee with an NCP87 fallback. The
DelTrustee request layout is corrected, /SUBDIRS handling is aligned
with Novell, and the output/error text is matched to the baseline.

The FLAG, FLAGDIR, GRANT and REMOVE tests now compare NPUBLIC baselines
against the PUBLIC implementations and add delayed NOPASSUSER readback
checks using DLYSTRT and the maintainer LOGIN password option.
2026-05-28 07:54:41 +02:00

297 lines
7.8 KiB
C

/* remove.c - Novell REMOVE-like DOS utility */
#include "net.h"
#include "c32ncp.h"
#include "trustee.h"
#include <sys/types.h>
#include <sys/stat.h>
#ifndef _A_NORMAL
#define _A_NORMAL 0x00
#endif
static int remove_last_rc = 0;
static void remove_usage(void)
{
fprintf(stdout, "\n");
fprintf(stdout, "Usage: REMOVE [USER | GROUP] name [FROM path] [option]\n");
fprintf(stdout, "Options: /Subdirs | /Files \n");
}
static void remove_usage_after_error(void)
{
fprintf(stdout, "\007\n");
fprintf(stdout, "Usage: REMOVE [USER | GROUP] name [FROM path] [option]\n");
fprintf(stdout, "Options: /Subdirs | /Files \n");
fprintf(stdout, "\n");
}
static void remove_header_path(char *out, char *path, int max)
{
char *p;
trustee_header_path(out, path, max);
/* Novell REMOVE displays server and volume as SERVER/SYS: while
* RIGHTS uses SERVER\SYS:. Keep the rest of the path with DOS
* backslashes. */
p = strchr(out, '\\');
if (p && strchr(out, ':') && p < strchr(out, ':'))
*p = '/';
}
static int remove_one(char *path, uint16 dhandle, uint32 object_id,
uint16 objtype, char *objname, int forced_is_file)
{
uint16 old_rights = 0;
int is_dir;
int rc;
/* Without /FILES, Novell REMOVE reports directory-oriented messages even
* if the specified path cannot be located. */
is_dir = forced_is_file ? 0 : 1;
rc = c32_ncp87_find_trustee_rights(path, dhandle, object_id, &old_rights,
NULL, NULL, NULL);
remove_last_rc = rc;
if (rc) {
if (rc == 0xff)
fprintf(stdout, "No trustee for the specified %s.\n", is_dir ? "directory" : "file");
else
fprintf(stdout, "Error scanning trustee list.\n");
return(1);
}
rc = c32_ncp22_delete_trustee_rights(path, dhandle, object_id);
if (rc)
rc = c32_ncp87_delete_trustee_rights(path, dhandle, object_id,
NULL, NULL, NULL);
remove_last_rc = rc;
if (rc) {
fprintf(stdout, "Error deleting trustee.\n");
return(1);
}
{
char header[300];
remove_header_path(header, path, sizeof(header));
fprintf(stdout, "%s\n", header);
}
if (objtype == TRUSTEE_BINDERY_GROUP)
fprintf(stdout, "Group \"%s\" no longer a trustee to the specified %s.\n\n",
objname, is_dir ? "directory" : "file");
else
fprintf(stdout, "User \"%s\" no longer a trustee to the specified %s.\n\n",
objname, is_dir ? "directory" : "file");
return(0);
}
static int remove_subdirs_inner(char *path, uint16 dhandle, uint32 object_id,
uint16 objtype, char *objname, int *count,
int include_this)
{
struct find_t ff;
char pattern[260];
char child[260];
int rc = 0;
if (include_this) {
if (remove_one(path, dhandle, object_id, objtype, objname, 0) == 0)
(*count)++;
else
rc = 1;
}
trustee_join_path(pattern, path, "*.*", sizeof(pattern));
if (_dos_findfirst(pattern, _A_SUBDIR, &ff) == 0) {
do {
if ((ff.attrib & _A_SUBDIR) && !trustee_is_dot_dir(ff.name)) {
trustee_join_path(child, path, ff.name, sizeof(child));
if (remove_subdirs_inner(child, dhandle, object_id, objtype, objname,
count, 1))
rc = 1;
}
} while (_dos_findnext(&ff) == 0);
}
return(rc);
}
static int remove_subdirs(char *path, uint16 dhandle, uint32 object_id,
uint16 objtype, char *objname, int *count)
{
/* Novell REMOVE /SUBDIRS affects the subdirectories under the specified
* start directory, not the start directory itself. */
return(remove_subdirs_inner(path, dhandle, object_id, objtype, objname,
count, 0));
}
static int remove_files(char *path, uint16 dhandle, uint32 object_id,
uint16 objtype, char *objname, int *count)
{
struct find_t ff;
char dir[260];
char pat[80];
char spec[260];
char target[260];
int rc = 0;
if (trustee_path_is_dir(path)) {
strmaxcpy(dir, path, sizeof(dir) - 1);
strmaxcpy(pat, "*.*", sizeof(pat) - 1);
} else if (trustee_path_has_wildcards(path)) {
trustee_parent_pattern(dir, pat, path, sizeof(dir), sizeof(pat));
} else {
if (remove_one(path, dhandle, object_id, objtype, objname, 1) == 0)
(*count)++;
else
rc = 1;
return(rc);
}
trustee_join_path(spec, dir, pat, sizeof(spec));
if (_dos_findfirst(spec, _A_NORMAL | _A_HIDDEN | _A_SYSTEM | _A_ARCH, &ff) == 0) {
do {
if (!(ff.attrib & _A_SUBDIR)) {
trustee_join_path(target, dir, ff.name, sizeof(target));
if (remove_one(target, dhandle, object_id, objtype, objname, 1) == 0)
(*count)++;
else
rc = 1;
}
} while (_dos_findnext(&ff) == 0);
}
return(rc);
}
int func_remove(int argc, char *argv[], int mode)
{
char *path = ".";
char *objname = NULL;
char objprint[48];
uint16 objtype = TRUSTEE_BINDERY_USER;
int objtype_given = 0;
uint8 connid = 0;
uint8 dhandle = 0;
uint32 object_id;
int use_subdirs = 0;
int use_files = 0;
int count = 0;
int i = 1;
int rc;
(void)mode;
if (argc < 2 || trustee_is_help(argv[1])) {
if (argc < 2) {
remove_usage_after_error();
return(1);
}
remove_usage();
return(argc < 2 ? 1 : 0);
}
if (i < argc && trustee_same(argv[i], "USER")) {
/* Novell treats "REMOVE USER FROM path" as an object lookup for
* USER, not as a grammar error. */
if ((i + 1) < argc && trustee_same(argv[i + 1], "FROM")) {
objtype_given = 0;
} else {
objtype = TRUSTEE_BINDERY_USER;
objtype_given = 1;
i++;
}
} else if (i < argc && trustee_same(argv[i], "GROUP")) {
if ((i + 1) < argc && trustee_same(argv[i + 1], "FROM")) {
objtype_given = 0;
} else {
objtype = TRUSTEE_BINDERY_GROUP;
objtype_given = 1;
i++;
}
}
if (i >= argc) {
remove_usage_after_error();
return(1);
}
objname = argv[i++];
if (i < argc && trustee_same(argv[i], "FROM")) {
i++;
if (i >= argc) {
remove_usage_after_error();
return(1);
}
path = argv[i++];
}
while (i < argc) {
if (!trustee_is_option(argv[i])) {
remove_usage_after_error();
return(1);
}
if (trustee_is_files_option(argv[i])) {
use_files = 1;
i++;
continue;
}
if (trustee_is_subdirs_option(argv[i])) {
use_subdirs = 1;
i++;
continue;
}
remove_usage_after_error();
return(1);
}
if (use_files && use_subdirs) {
fprintf(stdout, "Remove cannot do both directories and files in a single pass.\n");
return(1);
}
if (trustee_current_dhandle(&connid, &dhandle)) {
fprintf(stdout, "Error: Drive not mapped to network.\n");
return(1);
}
object_id = trustee_lookup_object(objname, &objtype, objtype_given);
if (!object_id) {
if (objtype_given && objtype == TRUSTEE_BINDERY_GROUP)
fprintf(stdout, "\007Group \"%s\" not found.\n", objname);
else if (objtype_given)
fprintf(stdout, "\007User \"%s\" not found.\n", objname);
else
fprintf(stdout, "\007User or group \"%s\" not found.\n", objname);
return(1);
}
trustee_upcopy(objprint, objname, sizeof(objprint));
if (use_subdirs)
rc = remove_subdirs(path, (uint16)dhandle, object_id, objtype, objprint, &count);
else if (use_files)
rc = remove_files(path, (uint16)dhandle, object_id, objtype, objprint, &count);
else {
rc = remove_one(path, (uint16)dhandle, object_id, objtype, objprint, 0);
if (!rc)
count = 1;
}
if (use_subdirs || (!use_files && !rc))
fprintf(stdout, "Trustee \"%s\" removed from %d directories.\n\n", objprint, count);
else if (use_files)
fprintf(stdout, "Trustee \"%s\" removed from %d files.\n\n", objprint, count);
return(rc ? (remove_last_rc ? remove_last_rc : 1) : 0);
}