Files
mars-dosutils/revoke.c
Mario Fetka 9ef8aeccc0 trustee: use shared tool helpers for generic path handling
Remove generic wrapper helpers from trustee.c and switch GRANT/REMOVE/REVOKE
callers to the shared tools.c helpers directly.

Keep trustee.c focused on Trustee/Rights-specific behavior such as option
parsing, rights parsing/formatting, object lookup and trustee path handling.
The generic string, current directory handle, uppercase and path helper logic
now lives in tools.c where it can be reused by other DOS utilities.
2026-05-29 09:44:14 +02:00

376 lines
11 KiB
C

/*
* mars-nwe-dosutils - NetWare/DOS utility tools.
*
* Copyright (C) 2026 Mario Fetka
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* Purpose: REVOKE utility for deleting trustee assignments from NetWare directories and files.
* Depends on: net.h, c32ncp.h, trustee.h/trustee.c trustee helpers, netcall.c requester helpers, tools.c shared utility routines.
*/
#include "net.h"
#include "c32ncp.h"
#include "trustee.h"
#ifndef _A_NORMAL
#define _A_NORMAL 0x00
#endif
static int revoke_last_rc = 0;
static void revoke_usage_short(int leading_blank, int bell_first)
{
if (bell_first)
fprintf(stdout, "\a");
if (leading_blank || bell_first)
fprintf(stdout, "\n");
fprintf(stdout, "Usage: REVOKE rightslist* [FOR path] FROM [USER|GROUP] name [options]\n");
fprintf(stdout, "Options: /SubDirectories | /Files\n");
fprintf(stdout, " ");
if (bell_first)
fprintf(stdout, "\n");
}
static void revoke_usage_full(int leading_blank, int bell_before_tail)
{
if (leading_blank)
fprintf(stdout, "\n");
fprintf(stdout, "Usage: REVOKE rightslist* [FOR path] FROM [USER|GROUP] name [options]\n");
fprintf(stdout, "Options: /SubDirectories | /Files\n");
fprintf(stdout, " \n");
fprintf(stdout, "286 Rights:\t\t386 Rights:\n");
fprintf(stdout, "---------------\t\t--------------------\n");
fprintf(stdout, "ALL = All\t\tALL = All\n");
fprintf(stdout, "R = Read\t\tS = Supervisor\n");
fprintf(stdout, "W = Write\t\tR = Read\n");
fprintf(stdout, "O = Open\t\tW = Write\n");
fprintf(stdout, "C = Create\t\tC = Create\n");
fprintf(stdout, "D = Delete\t\tE = Erase\n");
fprintf(stdout, "P = Parental\t\tM = Modify\n");
fprintf(stdout, "S = Search\t\tF = File Scan\n");
fprintf(stdout, "M = Modify\t\tA = Access Control\n");
fprintf(stdout, "\n");
if (bell_before_tail)
fprintf(stdout, "\a");
fprintf(stdout, "* Use abbreviations listed above, separated by spaces.\n");
}
static void revoke_header_path(char *out, char *path, int max)
{
char *p;
tool_header_path(out, path, max);
/* Novell REVOKE 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 revoke_one(char *path, uint16 dhandle, uint32 object_id,
uint16 revoke_mask, int forced_is_file)
{
uint16 old_rights = 0;
uint16 new_rights;
int is_dir;
int rc;
/* Without /FILES, Novell REVOKE reports directory-oriented messages even
* if the 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);
revoke_last_rc = rc;
if (rc) {
if (rc == 0xff)
fprintf(stdout, "\aNo trustee for the specified %s.\n", is_dir ? "directory" : "file");
else
fprintf(stdout, "\aError scanning trustee list.\n");
return(1);
}
new_rights = (uint16)(old_rights & ~revoke_mask);
if (new_rights == 0) {
/* Novell REVOKE removes the trustee through the old NCP22 trustee path.
* Keep the Client32/NCP87 delete call as a compatibility fallback. */
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);
revoke_last_rc = rc;
if (rc) {
fprintf(stdout, "Error deleting trustee.\n");
return(1);
}
} else {
/* Updating the remaining trustee mask is the same old SetTrustee flow
* used by GRANT. Fall back to NCP87 add-trustee if a server/client
* does not accept NCP22/27. */
rc = c32_ncp22_set_trustee_rights(path, dhandle, object_id, new_rights);
if (rc)
rc = c32_ncp87_add_trustee_rights(path, dhandle, object_id, new_rights,
0xffff, NULL, NULL, NULL);
revoke_last_rc = rc;
if (rc) {
fprintf(stdout, "Fatal error revoking access rights.\n");
return(1);
}
}
{
char header[300];
char bracket[10];
revoke_header_path(header, path, sizeof(header));
trustee_rights_bracket(new_rights, bracket);
fprintf(stdout, "%s\n", header);
fprintf(stdout, "Trustee's access rights set to [%s]\r\n\n", bracket);
}
return(0);
}
static int revoke_subdirs_inner(char *path, uint16 dhandle, uint32 object_id,
uint16 revoke_mask, int *count,
int include_this)
{
struct find_t ff;
char pattern[260];
char child[260];
/* Novell REVOKE /SUBDIRECTORIES follows the REMOVE behaviour: the
* specified start directory is a container for the recursive operation,
* not itself part of the operation. Once a listed subdirectory reports
* no direct trustee, Novell stops after that first failure. */
if (include_this) {
if (revoke_one(path, dhandle, object_id, revoke_mask, 0) == 0)
(*count)++;
else
return(1);
}
tool_join_path(pattern, path, "*.*", sizeof(pattern));
if (_dos_findfirst(pattern, _A_SUBDIR, &ff) == 0) {
do {
if ((ff.attrib & _A_SUBDIR) && !tool_is_dot_dir(ff.name)) {
tool_join_path(child, path, ff.name, sizeof(child));
if (revoke_subdirs_inner(child, dhandle, object_id, revoke_mask,
count, 1))
return(1);
}
} while (_dos_findnext(&ff) == 0);
}
return(0);
}
static int revoke_subdirs(char *path, uint16 dhandle, uint32 object_id,
uint16 revoke_mask, int *count)
{
return(revoke_subdirs_inner(path, dhandle, object_id, revoke_mask,
count, 0));
}
static int revoke_files(char *path, uint16 dhandle, uint32 object_id,
uint16 revoke_mask, 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 (tool_has_wildcards(path)) {
tool_parent_pattern(dir, pat, path, sizeof(dir), sizeof(pat));
} else {
if (revoke_one(path, dhandle, object_id, revoke_mask, 1) == 0)
(*count)++;
else
rc = 1;
return(rc);
}
tool_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)) {
tool_join_path(target, dir, ff.name, sizeof(target));
if (revoke_one(target, dhandle, object_id, revoke_mask, 1) == 0)
(*count)++;
else
rc = 1;
}
} while (_dos_findnext(&ff) == 0);
}
return(rc);
}
int func_revoke(int argc, char *argv[], int mode)
{
uint16 rights = 0;
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 have_rights = 0;
int rc;
(void)mode;
if (argc < 2 || tool_is_help_arg(argv[1])) {
if (argc < 2)
revoke_usage_full(1, 1);
else
revoke_usage_short(1, 0);
return(argc < 2 ? 1 : 0);
}
while (i < argc) {
if (tool_strsame(argv[i], "FOR") || tool_strsame(argv[i], "FROM"))
break;
if (tool_is_option(argv[i]))
break;
if (trustee_parse_right_word(argv[i], &rights)) {
fprintf(stdout, "Invalid right specified.\n\n");
revoke_usage_full(0, 1);
return(1);
}
have_rights = 1;
i++;
}
if (!have_rights || i >= argc) {
revoke_usage_short(0, 1);
return(1);
}
if (tool_strsame(argv[i], "FOR")) {
i++;
if (i >= argc) {
revoke_usage_short(0, 1);
return(1);
}
path = argv[i++];
}
if (i >= argc || !tool_strsame(argv[i], "FROM")) {
revoke_usage_short(0, 1);
return(1);
}
i++;
if (i < argc && tool_strsame(argv[i], "USER")) {
objtype = TRUSTEE_BINDERY_USER;
objtype_given = 1;
i++;
} else if (i < argc && tool_strsame(argv[i], "GROUP")) {
objtype = TRUSTEE_BINDERY_GROUP;
objtype_given = 1;
i++;
}
if (i >= argc) {
revoke_usage_short(0, 1);
return(1);
}
objname = argv[i++];
while (i < argc) {
if (!tool_is_option(argv[i])) {
revoke_usage_short(0, 1);
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;
}
revoke_usage_short(0, 1);
return(1);
}
/* Novell REVOKE does not emit a grammar-specific message for /FILES plus
* /SUBDIRECTORIES. It reports the same directory trustee failure text. */
if (tool_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, "\aGroup \"%s\" not found.\n", objname);
else if (objtype_given)
fprintf(stdout, "\aUser \"%s\" not found.\n", objname);
else
fprintf(stdout, "\aUser or group \"%s\" not found.\n", objname);
return(1);
}
tool_upcopy(objprint, objname, sizeof(objprint));
if (use_files && use_subdirs) {
fprintf(stdout, "\aNo trustee for the specified directory.\n");
revoke_last_rc = 0xff;
return(1);
} else if (use_subdirs)
rc = revoke_subdirs(path, (uint16)dhandle, object_id, rights, &count);
else if (use_files)
rc = revoke_files(path, (uint16)dhandle, object_id, rights, &count);
else {
rc = revoke_one(path, (uint16)dhandle, object_id, rights, 0);
if (!rc)
count = 1;
}
if ((use_subdirs || (!use_files && !rc)) && count > 0)
fprintf(stdout, "Rights for %d directories were changed for %s.\n\n", count, objprint);
else if (use_files && count > 0)
fprintf(stdout, "Rights for %d files were changed for %s.\n\n", count, objprint);
return(rc ? (revoke_last_rc ? revoke_last_rc : 1) : 0);
}