/* * 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 . */ /* * 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); }