Add GPL-2-or-later license headers to the DOS utility source files and document the purpose and local dependencies of each C, header and assembler file. Preserve the original Martin Stover copyright attribution for the historic MARS-NWE utility sources, including files that did not previously carry an explicit header but are part of the original tool set. Add Mario Fetka as the 2026 copyright holder for the current maintenance work, and use Mario-only headers for files without original Martin Stover ownership. Also add a root-level COPYING file containing the GPL-2 license text.
376 lines
11 KiB
C
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;
|
|
|
|
trustee_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);
|
|
}
|
|
|
|
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 (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 (trustee_path_has_wildcards(path)) {
|
|
trustee_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);
|
|
}
|
|
|
|
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 (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 || trustee_is_help(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 (trustee_same(argv[i], "FOR") || trustee_same(argv[i], "FROM"))
|
|
break;
|
|
if (trustee_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 (trustee_same(argv[i], "FOR")) {
|
|
i++;
|
|
if (i >= argc) {
|
|
revoke_usage_short(0, 1);
|
|
return(1);
|
|
}
|
|
path = argv[i++];
|
|
}
|
|
|
|
if (i >= argc || !trustee_same(argv[i], "FROM")) {
|
|
revoke_usage_short(0, 1);
|
|
return(1);
|
|
}
|
|
i++;
|
|
|
|
if (i < argc && trustee_same(argv[i], "USER")) {
|
|
objtype = TRUSTEE_BINDERY_USER;
|
|
objtype_given = 1;
|
|
i++;
|
|
} else if (i < argc && trustee_same(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 (!trustee_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 (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, "\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);
|
|
}
|
|
|
|
trustee_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);
|
|
}
|