diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e2ee53..beb2fd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,8 @@ set(MARS_DOSUTILS_NEW_ONLY_TOOLS slist flag flagdir + rights + grant ) if(MARS_NWE_BUILD_DOSUTILS) @@ -56,6 +58,7 @@ if(MARS_NWE_BUILD_DOSUTILS) flag.c flagdir.c rights.c + grant.c c32ncp.c nwcrypt.c nwdebug.c diff --git a/c32ncp.c b/c32ncp.c index b3b56a3..c438c4d 100644 --- a/c32ncp.c +++ b/c32ncp.c @@ -16,6 +16,14 @@ static void c32_put_dword_lh(uint8 *p, uint32 v) p[3] = (uint8)((v >> 24) & 0xff); } +static void c32_put_dword_hl(uint8 *p, uint32 v) +{ + p[0] = (uint8)((v >> 24) & 0xff); + p[1] = (uint8)((v >> 16) & 0xff); + p[2] = (uint8)((v >> 8) & 0xff); + p[3] = (uint8)(v & 0xff); +} + static uint16 c32_get_word_lh(uint8 *p) { return((uint16)(p[0] | ((uint16)p[1] << 8))); @@ -480,3 +488,108 @@ int c32_ncp87_get_effective_rights(const char *path_name, } +int c32_ncp87_add_trustee_rights(const char *path_name, + uint16 dir_handle, + uint32 object_id, + uint16 rights, + uint16 rights_mask, + uint16 *actual_out, + uint16 *handle_lo_out, + uint16 *handle_hi_out) +{ + uint16 handle_lo, handle_hi; + uint8 hdr[16]; + uint8 reqpath[0x180]; + uint8 rep0[0x20]; + uint8 rep1[0x20]; + uint8 rawout[32]; + uint16 raw_ret_ax, raw_ret_dx; + uint16 actual_lo; + UI path_struct_len; + UI reqpath_len; + uint8 *tp; + int rc; + + 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 10: Add trustee. + * + * This mirrors ncpfs ncp_ns_trustee_add(): + * byte 10 + * byte namespace DOS + * byte reserved + * word search attributes, little endian + * word rights mask, little endian + * word object count, little endian + * handle/path + * trustee array at request offset 16 + 307 + * + * Client32 Raw5 has two request fragments, so the second fragment carries + * the handle/path and the padded trustee record. + */ + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 10; + hdr[1] = 0; /* DOS namespace */ + hdr[2] = 0; /* reserved */ + c32_put_word_lh(hdr + 3, 0x8006); /* SA_ALL: files/subdirs + system + hidden */ + c32_put_word_lh(hdr + 5, rights_mask); + c32_put_word_lh(hdr + 7, 1); /* one trustee */ + + memset(reqpath, 0, sizeof(reqpath)); + path_struct_len = c32_build_handle_path_from_dos_path(reqpath, + (uint8)dir_handle, + 0, 0, + path_name); + + /* + * ncpfs seeks to absolute packet offset 16+307 before writing the + * trustee list. The NCP request header is 7 bytes, so inside the NCP + * payload the trustee list begins at: + * + * (16 + 307) - 7 = 316 + * + * Our first Raw5 request fragment is the 9-byte subfunction header, + * so the trustee list begins in the second fragment at: + * + * 316 - 9 = 307 + */ + if (path_struct_len > 307) + return(2); + + tp = reqpath + 307; + c32_put_dword_hl(tp, object_id); tp += 4; + c32_put_word_lh(tp, rights); tp += 2; + reqpath_len = (UI)(tp - reqpath); + + 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, + reqpath, reqpath_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); + + return(0); +} + diff --git a/c32ncp.h b/c32ncp.h index 17d97d0..891e474 100644 --- a/c32ncp.h +++ b/c32ncp.h @@ -26,4 +26,13 @@ int c32_ncp87_get_effective_rights(const char *path, uint16 *handle_lo_out, uint16 *handle_hi_out); +int c32_ncp87_add_trustee_rights(const char *path_name, + uint16 dir_handle, + uint32 object_id, + uint16 rights, + uint16 rights_mask, + uint16 *actual_out, + uint16 *handle_lo_out, + uint16 *handle_hi_out); + #endif diff --git a/grant.c b/grant.c new file mode 100644 index 0000000..2706a5d --- /dev/null +++ b/grant.c @@ -0,0 +1,519 @@ +/* grant.c - Novell GRANT-like DOS utility, first Client32 implementation */ + +#include "net.h" +#include "c32ncp.h" + +#define GRANT_BINDERY_USER 0x0001 +#define GRANT_BINDERY_GROUP 0x0002 + +#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 + +#define NCP_RIGHT_ALL_386 (NCP_RIGHT_SUPER | NCP_RIGHT_READ | \ + NCP_RIGHT_WRITE | NCP_RIGHT_CREATE | \ + NCP_RIGHT_DELETE | NCP_RIGHT_MODIFY | \ + NCP_RIGHT_SEARCH | NCP_RIGHT_OWNER) + +static int grant_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 grant_is_help(char *s) +{ + if (!s) return(0); + return(grant_same(s, "/?") || grant_same(s, "-?") || grant_same(s, "?")); +} + +static void grant_usage_error(void) +{ + fprintf(stdout, "Command line arguments violate grammar defined for GRANT.\n\n"); +} + +static void grant_usage(void) +{ + fprintf(stdout, "Usage: GRANT rightslist* [FOR path] TO [USER | GROUP] name [options]\n"); + fprintf(stdout, "Options: /SubDirectories | /Files\n\n"); + fprintf(stdout, "386 Rights:\n"); + fprintf(stdout, "--------------------\n"); + fprintf(stdout, "ALL = All\n"); + fprintf(stdout, "N = No Rights\n"); + fprintf(stdout, "S = Supervisor\n"); + fprintf(stdout, "R = Read\n"); + fprintf(stdout, "W = Write\n"); + fprintf(stdout, "C = Create\n"); + fprintf(stdout, "E = Erase\n"); + fprintf(stdout, "M = Modify\n"); + fprintf(stdout, "F = File Scan\n"); + fprintf(stdout, "A = Access Control\n"); +} + +static int grant_get_current_drive(void) +{ + REGS regs; + + regs.h.ah = 0x19; + int86(0x21, ®s, ®s); + return((int)regs.h.al); +} + +static int grant_current_dhandle(uint8 *connid, uint8 *dhandle) +{ + uint8 flags = 0; + int drive = grant_get_current_drive(); + + if (get_drive_info((uint8)drive, connid, dhandle, &flags)) + return(-1); + + if (!*connid || (flags & 0x80)) + return(-1); + + return(0); +} + + +static int grant_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 = grant_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 grant_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 grant_basename(char *dst, char *src, int max) +{ + char up[260]; + char *p; + + grant_upcopy(up, src, sizeof(up)); + p = strrchr(up, '\\'); + if (!p) p = strrchr(up, ':'); + + if (p) + strmaxcpy(dst, p + 1, max - 1); + else + strmaxcpy(dst, up, max - 1); +} + +static void grant_header_path(char *out, char *path, int max) +{ + char prefix[90]; + char up[260]; + + if (grant_current_prefix(prefix, sizeof(prefix))) + prefix[0] = '\0'; + + grant_upcopy(up, path, sizeof(up)); + + strmaxcpy(out, prefix, max - 1); + if ((int)(strlen(out) + strlen(up)) < max - 1) + strcat(out, up); +} + +static void grant_rights_bracket(uint16 rights, char *out) +{ + out[0] = (rights & NCP_RIGHT_SUPER) ? 'S' : ' '; + out[1] = (rights & NCP_RIGHT_READ) ? 'R' : ' '; + out[2] = (rights & NCP_RIGHT_WRITE) ? 'W' : ' '; + out[3] = (rights & NCP_RIGHT_CREATE) ? 'C' : ' '; + out[4] = (rights & NCP_RIGHT_DELETE) ? 'E' : ' '; + out[5] = (rights & NCP_RIGHT_MODIFY) ? 'M' : ' '; + out[6] = (rights & NCP_RIGHT_SEARCH) ? 'F' : ' '; + out[7] = (rights & NCP_RIGHT_OWNER) ? 'A' : ' '; + out[8] = '\0'; +} + + +static void grant_rights_string(uint16 rights, char *out) +{ + char *p = out; + + if (rights == 0) { + strcpy(out, "N"); + return; + } + + if ((rights & NCP_RIGHT_ALL_386) == NCP_RIGHT_ALL_386) { + strcpy(out, "ALL"); + return; + } + + if (rights & NCP_RIGHT_SUPER) *p++ = 'S'; + if (rights & NCP_RIGHT_READ) *p++ = 'R'; + if (rights & NCP_RIGHT_WRITE) *p++ = 'W'; + if (rights & NCP_RIGHT_CREATE) *p++ = 'C'; + if (rights & NCP_RIGHT_DELETE) *p++ = 'E'; + if (rights & NCP_RIGHT_MODIFY) *p++ = 'M'; + if (rights & NCP_RIGHT_SEARCH) *p++ = 'F'; + if (rights & NCP_RIGHT_OWNER) *p++ = 'A'; + *p = '\0'; +} + +static int grant_add_right_word(char *s, uint16 *rights) +{ + if (grant_same(s, "ALL")) { + *rights = NCP_RIGHT_ALL_386; + return(0); + } + + if (grant_same(s, "N") || grant_same(s, "NONE")) { + *rights = 0; + return(0); + } + + if (grant_same(s, "S") || grant_same(s, "SUPERVISOR")) { + *rights |= NCP_RIGHT_SUPER; + return(0); + } + + if (grant_same(s, "R") || grant_same(s, "READ")) { + *rights |= NCP_RIGHT_READ; + return(0); + } + + if (grant_same(s, "W") || grant_same(s, "WRITE")) { + *rights |= NCP_RIGHT_WRITE; + return(0); + } + + if (grant_same(s, "C") || grant_same(s, "CREATE")) { + *rights |= NCP_RIGHT_CREATE; + return(0); + } + + if (grant_same(s, "E") || grant_same(s, "ERASE")) { + *rights |= NCP_RIGHT_DELETE; + return(0); + } + + if (grant_same(s, "M") || grant_same(s, "MODIFY")) { + *rights |= NCP_RIGHT_MODIFY; + return(0); + } + + if (grant_same(s, "F") || grant_same(s, "FILESCAN") || + grant_same(s, "FILE") || grant_same(s, "SCAN")) { + *rights |= NCP_RIGHT_SEARCH; + return(0); + } + + if (grant_same(s, "A") || grant_same(s, "ACCESS") || + grant_same(s, "CONTROL") || grant_same(s, "ACCESSCONTROL")) { + *rights |= NCP_RIGHT_OWNER; + return(0); + } + + return(-1); +} + +static int grant_is_option(char *s) +{ + if (!s) return(0); + return(s[0] == '/' || s[0] == '-'); +} + + +static int grant_last_rc = 0; + +static int grant_set_one(char *path, uint16 dhandle, + uint32 object_id, uint16 rights) +{ + int rc; + + rc = c32_ncp87_add_trustee_rights(path, + dhandle, + object_id, + rights, + 0xffff, + NULL, NULL, NULL); + grant_last_rc = rc; + return(rc); +} + +static int grant_is_dot_dir(char *name) +{ + if (!name) return(0); + if (name[0] == '.' && name[1] == '\0') return(1); + if (name[0] == '.' && name[1] == '.' && name[2] == '\0') return(1); + return(0); +} + +static void grant_join_path(char *out, char *base, char *name, int max) +{ + int len; + + out[0] = '\0'; + strmaxcpy(out, base, max - 1); + len = strlen(out); + + if (len > 0 && out[len - 1] != '\\' && out[len - 1] != '/' && + out[len - 1] != ':') { + if (len < max - 1) { + out[len++] = '\\'; + out[len] = '\0'; + } + } + + if ((int)(strlen(out) + strlen(name)) < max - 1) + strcat(out, name); +} + +/* + * Apply the grant to PATH and every directory below it. + * + * This intentionally walks through the DOS redirector, not through another + * NCP search helper, so it works with the same mapped-drive view that the + * user passed to GRANT. + */ +static int grant_set_subdirs(char *path, uint16 dhandle, + uint32 object_id, uint16 rights) +{ + struct find_t ff; + char pattern[260]; + char child[260]; + int rc = 0; + + if (grant_set_one(path, dhandle, object_id, rights)) + rc = 1; + + grant_join_path(pattern, path, "*.*", sizeof(pattern)); + + if (_dos_findfirst(pattern, _A_SUBDIR, &ff) == 0) { + do { + if ((ff.attrib & _A_SUBDIR) && !grant_is_dot_dir(ff.name)) { + grant_join_path(child, path, ff.name, sizeof(child)); + if (grant_set_subdirs(child, dhandle, object_id, rights)) + rc = 1; + } + } while (_dos_findnext(&ff) == 0); + } + + return(rc); +} + + +int func_grant(int argc, char *argv[], int mode) +{ + uint16 rights = 0; + char *path = "."; + char *objname = NULL; + uint16 objtype = GRANT_BINDERY_USER; + uint8 connid = 0; + uint8 dhandle = 0; + uint8 namebuf[48]; + uint32 object_id; + int recurse_subdirs = 0; + int i = 1; + int have_rights = 0; + int rc; + + (void)mode; + + if (argc < 2 || grant_is_help(argv[1])) { + if (argc < 2) + grant_usage_error(); + grant_usage(); + return(argc < 2 ? 1 : 0); + } + + /* + * GRANT rightslist* [FOR path] TO [USER|GROUP] name [options] + */ + while (i < argc) { + if (grant_same(argv[i], "FOR") || grant_same(argv[i], "TO")) + break; + + if (grant_is_option(argv[i])) + break; + + if (grant_add_right_word(argv[i], &rights)) { + grant_usage_error(); + grant_usage(); + return(1); + } + have_rights = 1; + i++; + } + + if (!have_rights || i >= argc) { + grant_usage_error(); + grant_usage(); + return(1); + } + + if (grant_same(argv[i], "FOR")) { + i++; + if (i >= argc) { + grant_usage_error(); + grant_usage(); + return(1); + } + path = argv[i++]; + } + + if (i >= argc || !grant_same(argv[i], "TO")) { + grant_usage_error(); + grant_usage(); + return(1); + } + i++; + + if (i < argc && grant_same(argv[i], "USER")) { + objtype = GRANT_BINDERY_USER; + i++; + } else if (i < argc && grant_same(argv[i], "GROUP")) { + objtype = GRANT_BINDERY_GROUP; + i++; + } + + if (i >= argc) { + grant_usage_error(); + grant_usage(); + return(1); + } + + objname = argv[i++]; + + while (i < argc) { + if (!grant_is_option(argv[i])) { + grant_usage_error(); + grant_usage(); + return(1); + } + + /* + * /FILES is harmless because the explicit path is passed to the + * NCP87 trustee-add call. /SUBDIRECTORIES recursively applies the + * same grant to all subdirectories below the given path. + */ + if (grant_same(argv[i], "/FILES") || grant_same(argv[i], "-FILES") || + grant_same(argv[i], "/F") || grant_same(argv[i], "-F")) { + i++; + continue; + } + + if (grant_same(argv[i], "/SUBDIRECTORIES") || + grant_same(argv[i], "-SUBDIRECTORIES") || + grant_same(argv[i], "/S") || grant_same(argv[i], "-S")) { + recurse_subdirs = 1; + i++; + continue; + } + + grant_usage_error(); + grant_usage(); + return(1); + } + + if (grant_current_dhandle(&connid, &dhandle)) { + fprintf(stdout, "Specified path not locatable.\n"); + return(1); + } + + strmaxcpy(namebuf, objname, sizeof(namebuf) - 1); + upstr(namebuf); + object_id = ncp_17_35(namebuf, objtype); + if (!object_id) { + fprintf(stdout, "Object not found.\n"); + return(1); + } + + if (recurse_subdirs) + rc = grant_set_subdirs(path, (uint16)dhandle, object_id, rights); + else + rc = grant_set_one(path, (uint16)dhandle, object_id, rights); + + if (rc) { + fprintf(stdout, "Could not add trustee rights. rc=%d\n", grant_last_rc); + return(grant_last_rc ? grant_last_rc : 1); + } + + { + char header[300]; + char base[80]; + char bracket[10]; + + grant_header_path(header, path, sizeof(header)); + grant_basename(base, path, sizeof(base)); + grant_rights_bracket(rights, bracket); + + fprintf(stdout, "%s\n", header); + fprintf(stdout, "%-29.29sRights set to [%s]\n", base, bracket); + } + + return(0); +} diff --git a/net.c b/net.c index 03d9b29..cd35b6e 100644 --- a/net.c +++ b/net.c @@ -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}, +{"GRANT", "grant trustee rights", func_grant , 0}, {"RIGHTS", "display effective file/directory rights",func_rights, 0}, {"SLIST", "list servers", func_slist , 0}, {"PASSWD", "change password", func_passwd , 0}, diff --git a/net.h b/net.h index a9e600f..869cbaa 100644 --- a/net.h +++ b/net.h @@ -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_grant(int argc, char *argv[], int mode); extern int func_rights (int argc, char *argv[], int mode);