From 4cafe16980e9801a433dab8e2ab20e0bfe4aba38 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Sun, 24 May 2026 20:23:43 +0200 Subject: [PATCH] dosutils: add Novell-style REVOKE and REMOVE trustee tools Implement REVOKE and REMOVE for the Client32 DOS utilities. REVOKE now supports Novell-style syntax: REVOKE rightslist* [FOR path] FROM [USER|GROUP] name [options] and removes rights from explicit trustee assignments. It scans the trustee list first, updates the trustee rights mask, and deletes the trustee entry when no rights remain. REMOVE now supports Novell-style syntax: REMOVE [USER | GROUP] name [FROM path] [option] and deletes explicit trustee assignments for users or groups. Both tools support USER/GROUP lookup, /FILES, /SUBDIRS, /SUBDIRECTORIES, wildcard file targets, recursive directory handling, Novell-style help text and summary output. Missing trustee entries are reported with Novell-style "No trustee for the specified ..." messages. Add shared trustee helpers and Client32 NCP87 trustee scan/delete support. Also adjust GRANT ALL so it matches Novell behavior by not granting Supervisor implicitly; Supervisor must be granted explicitly. --- CMakeLists.txt | 20 ++- c32ncp.c | 182 ++++++++++++++++++++++++++ c32ncp.h | 15 +++ grant.c | 9 +- net.c | 2 + net.exe | Bin 70486 -> 96920 bytes net.h | 4 +- remove.c | 253 ++++++++++++++++++++++++++++++++++++ revoke.c | 301 +++++++++++++++++++++++++++++++++++++++++++ trustee.c | 341 +++++++++++++++++++++++++++++++++++++++++++++++++ trustee.h | 40 ++++++ 11 files changed, 1160 insertions(+), 7 deletions(-) create mode 100644 remove.c create mode 100644 revoke.c create mode 100644 trustee.c create mode 100644 trustee.h diff --git a/CMakeLists.txt b/CMakeLists.txt index beb2fd1..b6935d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,8 @@ set(MARS_DOSUTILS_NEW_ONLY_TOOLS flagdir rights grant + revoke + remove ) if(MARS_NWE_BUILD_DOSUTILS) @@ -59,6 +61,9 @@ if(MARS_NWE_BUILD_DOSUTILS) flagdir.c rights.c grant.c + revoke.c + remove.c + trustee.c c32ncp.c nwcrypt.c nwdebug.c @@ -149,8 +154,19 @@ else() set(MARS_DOSUTILS_SELECTED_LEGACY_EXE "${MARS_DOSUTILS_LEGACY_NET_EXE}") endif() +set(MARS_DOSUTILS_SELECTED_LEGACY_IS_BUILT FALSE) +set(MARS_DOSUTILS_SELECTED_NEW_IS_BUILT FALSE) +if(MARS_NWE_BUILD_DOSUTILS) + if(MARS_DOSUTILS_SELECTED_LEGACY_EXE STREQUAL MARS_DOSUTILS_BUILT_NET_EXE) + set(MARS_DOSUTILS_SELECTED_LEGACY_IS_BUILT TRUE) + endif() + if(MARS_DOSUTILS_SELECTED_NEW_EXE STREQUAL MARS_DOSUTILS_BUILT_NET_EXE) + set(MARS_DOSUTILS_SELECTED_NEW_IS_BUILT TRUE) + endif() +endif() + if(MARS_NWE_INSTALL_DOSUTILS) - if(NOT EXISTS "${MARS_DOSUTILS_SELECTED_LEGACY_EXE}") + if(NOT MARS_DOSUTILS_SELECTED_LEGACY_IS_BUILT AND NOT EXISTS "${MARS_DOSUTILS_SELECTED_LEGACY_EXE}") message(FATAL_ERROR "Selected legacy/default DOS utility missing: ${MARS_DOSUTILS_SELECTED_LEGACY_EXE}. " "Commit dosutils/netold.exe, enable MARS_NWE_INSTALL_NEW_DOSUTILS, " @@ -158,7 +174,7 @@ if(MARS_NWE_INSTALL_DOSUTILS) ) endif() - if(NOT EXISTS "${MARS_DOSUTILS_SELECTED_NEW_EXE}") + if(NOT MARS_DOSUTILS_SELECTED_NEW_IS_BUILT AND NOT EXISTS "${MARS_DOSUTILS_SELECTED_NEW_EXE}") message(FATAL_ERROR "Selected new DOS utility missing: ${MARS_DOSUTILS_SELECTED_NEW_EXE}. " "Commit dosutils/net.exe or enable MARS_NWE_BUILD_DOSUTILS." diff --git a/c32ncp.c b/c32ncp.c index c438c4d..6cc7c70 100644 --- a/c32ncp.c +++ b/c32ncp.c @@ -37,6 +37,14 @@ static uint32 c32_get_dword_lh(uint8 *p) ((uint32)p[3] << 24)); } +static uint32 c32_get_dword_hl(uint8 *p) +{ + return(((uint32)p[0] << 24) | + ((uint32)p[1] << 16) | + ((uint32)p[2] << 8) | + (uint32)p[3]); +} + static UI c32_build_handle_path(uint8 *buf, uint8 dhandle, uint16 dirbase, uint8 style, int count, @@ -593,3 +601,177 @@ int c32_ncp87_add_trustee_rights(const char *path_name, return(0); } + + +int c32_ncp87_find_trustee_rights(const char *path_name, + uint16 dir_handle, + uint32 object_id, + uint16 *rights_out, + uint16 *actual_out, + uint16 *handle_lo_out, + uint16 *handle_hi_out) +{ + uint16 handle_lo, handle_hi; + uint8 hdr[16]; + uint8 path[0x140]; + uint8 rep0[0x120]; + uint8 rep1[0x120]; + uint8 rawout[32]; + uint16 raw_ret_ax, raw_ret_dx; + uint16 actual_lo; + uint32 seq = 0; + UI path_len; + int rc; + + if (rights_out) *rights_out = 0; + if (actual_out) *actual_out = 0; + if (handle_lo_out) *handle_lo_out = 0; + if (handle_hi_out) *handle_hi_out = 0; + + if (!rights_out) + return(1); + + rc = c32_get_ncp_handle(&handle_lo, &handle_hi); + if (rc) + return(10 + rc); + + for (;;) { + uint16 count; + uint16 i; + uint8 *tp; + uint32 next_seq; + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 5; /* NCP87 subfunction 5: scan trustees */ + hdr[1] = 0; /* DOS namespace */ + hdr[2] = 0; /* reserved */ + c32_put_word_lh(hdr + 3, 0x8006); /* SA_ALL: files/subdirs + system + hidden */ + c32_put_dword_lh(hdr + 5, seq); /* search sequence, starts at zero */ + + path_len = c32_build_handle_path_from_dos_path(path, + (uint8)dir_handle, + 0, 0, + path_name); + + 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, + path, path_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(0xff); /* Client32 returns an error when no trustees are present. */ + + next_seq = c32_get_dword_lh(rep0 + 0); + count = c32_get_word_lh(rep0 + 4); + + if (count > 20) + count = 20; + + tp = rep0 + 6; + for (i = 0; i < count; i++) { + uint32 tid = c32_get_dword_hl(tp); + uint16 trights = c32_get_word_lh(tp + 4); + + if (tid == object_id || c32_get_dword_lh(tp) == object_id) { + *rights_out = trights; + return(0); + } + + tp += 6; + } + + if (next_seq == 0xffffffffUL || next_seq == seq) + break; + + seq = next_seq; + } + + return(0xff); /* no trustee found / no more entries */ +} + +int c32_ncp87_delete_trustee_rights(const char *path_name, + uint16 dir_handle, + uint32 object_id, + 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); + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 11; /* NCP87 subfunction 11: delete trustee */ + hdr[1] = 0; /* DOS namespace */ + hdr[2] = 0; /* reserved */ + c32_put_word_lh(hdr + 3, 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); + + if (path_struct_len > 307) + return(2); + + tp = reqpath + 307; + c32_put_dword_hl(tp, object_id); tp += 4; + c32_put_word_lh(tp, 0); 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, 5, + 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 891e474..7cf823d 100644 --- a/c32ncp.h +++ b/c32ncp.h @@ -35,4 +35,19 @@ int c32_ncp87_add_trustee_rights(const char *path_name, uint16 *handle_lo_out, uint16 *handle_hi_out); +int c32_ncp87_find_trustee_rights(const char *path_name, + uint16 dir_handle, + uint32 object_id, + uint16 *rights_out, + uint16 *actual_out, + uint16 *handle_lo_out, + uint16 *handle_hi_out); + +int c32_ncp87_delete_trustee_rights(const char *path_name, + uint16 dir_handle, + uint32 object_id, + uint16 *actual_out, + uint16 *handle_lo_out, + uint16 *handle_hi_out); + #endif diff --git a/grant.c b/grant.c index 2706a5d..b8830c5 100644 --- a/grant.c +++ b/grant.c @@ -16,10 +16,11 @@ #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) +/* Novell GRANT ALL assigns all normal trustee rights, not Supervisor. */ +#define NCP_RIGHT_ALL_386 (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) { diff --git a/net.c b/net.c index cd35b6e..cbee6a3 100644 --- a/net.c +++ b/net.c @@ -37,6 +37,8 @@ static struct s_net_functions { {"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}, +{"REVOKE", "revoke trustee rights", func_revoke , 0}, +{"REMOVE", "remove trustee", func_remove , 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.exe b/net.exe index e7db07f5a2633e6b04352e824352e3c87e12be85..431c3abb8415331f2c8146547d74cc9ff2c5370b 100755 GIT binary patch delta 54904 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≻xTR)OidEiWN=DmDX7W~&5wLk2-0VcG3(NX(~;@-W6wsr^J_HQ#8FK*osgD zW|vhqQy)Tvim7)@1L(z9e7R~uD8H##RT{~lb>4+BEsaD?hIl(_q7pi$xJ3TBgGAnr zEDkcYlT2t5Z8a1HHQO$daUd&l&`ojDp#?hGaYP64&USQioZU%T+DSQA3O zUyhpA(aUJ@WuK{nUWSM--BXI1AjQNE z(@*q%znv<1XU9mZZ^yynM>7lDEMGBY$CGRbYYI;3gd4f!ncC}HE9Tgbbek*vn3x`E zC-^`l*aiZbk5fZF&qdrRQ5Dtlhl%NKmI*e?=Jv{u z)vQJPceZH)+3AVncvGgji&V(WM$EdSO#O&Y>M!G?h9|)FyE@5$3D{ScMMF>B0`h}H zD>e0qEUTqDO#r=50)bSQ6<{Y2s)l0mciSF;I#0OUh_$7O*1euy&r_Z>GWMtF^;fB| zGY#cBxUHGQ)n*K13f=Y&W6Jr%!>5oJ`{!6*IbvfUsla7Hnr)jHoxYTz%vvNqlUk`+ zWH0U-;g$mzI+Q;#Vpuw!9*mKhCi>7&BL`vCShCj$8H#iIg)z>>w}fVg4(@XD#ni_y zV{H;P!N89h8Cx{TnNuZ%jdfXiVNs63x<@FsS$A1u#YLTZBm&W&U@a*#;cx70Yozmrb>#}^wl4_ipv9uXea;V79KD7wMh_^OnVx zg@|G6rh1z52(1$8M*|(&iJmV~Zzt+}Z((+arAT5O-I=`5p3CrGxt5B#UJ`R*hh;@) zf9u)Nud9d7*a!l0g=0lAo+z)>&d?VO^L-lB>)_I*TZeZRSZ+_hF$!WvcA{Su3;c1E zf#=mDC6QnkBl-PfqDI2aq|GP) zMl2NImSa0h7<7Re+27m<=`a(D>3L6=6u!i0(JJB`_){mJl_UA+v3-VMZ90Nx*LQ+b zz;5|@aEM5;rQDRsOb>|IzM~j15`zwfAkOM30o{fX{HC!fT^U~og>Ynqfo~YweSptt zv7(3(gf<%Nl~K!(;p^Xh8uLe1YGkx?D0gPV7T5@0@pNytFF)q#q=iqz>7Kw^I1mj=<#u%C~|hX)Kg&Z+Nmn)|d= zQV9vi`M3$O-9HA2JxUNfjMP9TI%l7|uwR1tvlC{-Cm*NAIMpu9xQ-AXO&fzxnB3q@ ze&8fk34KQM!i34R9sB7-YQ(#E`qwdfx{aq7j!`73wZn#uFwy#on|-t@emlc39UDw9 zM?-;5U~@O+ScuH@O=pWrRD8w6*pWXtF~W}-Xnw!s8mQG2vb1R#6g4loIYNJd_(VO) zr^%ITTZV!Edt!K~<*2A@`=vtdapzOlk=S0z>P_?8Zxi_Jtcaq!4OT^^W&QQMBOOA-Wywn>AH`x$8l{;Nqbl8NpBpUIxzo#`w^Fk~7m<0*KCFe;sN2!ldI z4#kdIb_?}JOe%kEavuc1_NokgNY2-UJ2sX-lJg1e`RT`6dv9*h#+P&iYhy|}dTJFK znQ3$vm;GrS43RKMz4o`;u-Dy!H8&Mh>x(hxj)Eo+Ht?^mf7*+lSv-4889@c?N#Q3< zi9?~Ur5O19DYSjL1W)fx89R4rC-bc-WCIdPI#?}?Nu~OR;486)m8-3y>!Bk;vF}%> zpzkr3E!*2!x^`e;{Ny zAwxQmHH35rQtKl#)poI_SZ>i4ts~2|F8sl%K~Vw`ooNSApAwtf%NScOA%u4a8Tfls zV?+NU8UtvaoqgF#{0QicDt~@jbXc;VJf zozy4a4M6;JdOAgg*9@4Bz=FQH+zT3MNuR&kEB&RlZpHnhM_O;f)FS#b8N}aBPk=19 z(UreX9~dza<&xQI!#cZs2n_*xR0+1he7_kKbv_J5n>ynPiF!1NPoG&2+`J?M?=@=` z;pzwR>t|)qQ@|j8|E!g41K)Rccea|JJX;wG>}&m|+fVi=B;0wjJbtE0eCh1mXjcQn z6!xQDMvVC8oV??v58b6g*M0{6(d=2>5%lhBkeRZnF-cKWLOjumt`JrJ)xM;h6hj_{ zr_1YQ$~CBuP>zEp`O)SAu}!h$eTm)!%rE-!zn6ya0eSAoaH|hGBTr2`idB7RRuKbJ z7quRH|K(?O+gswtuge=;w5hY?vwcbq1!Dwa00z>YV`?9}ecEudg@J+EaI2VsrgG|a zocg5(HI7u7-?X=_nug)B0S8znt+4FnXs+YT^)uIXS)1GnO7$0X&|Hb|v8bQIfDX$F z7Q5~G3x^fuUg`a{MegZ+^XEBos5f!yLdw)6W-=S8s*$AbS>!(vBlA|m`4DcX3-=-6 z9+2Y3yKtWn?svikS~*iP`~Y&OAgPdRGK{fk#5kH-S|rTbgZAG+t#HEG6vDAYveYuu zUxe}P>gtpN-!{Ktrj>tN3ck}yGTM+V-eZr;Z0f(t-_Kh+jJeXjisl7AG~0A&joa{%gth9 z_KaZdm81rm-fz!F`<_kdep14*{rLA6EcX37hU}-f#T~FvG%G(P@Z%Ov@1>Mco4Hwb zGkZ3=*a-dl8us`)*mR&wB?iy&g-@e)jWjA2rN(>|?PLg>=w|6+tNb`XVmSwojfUM< z@igql6xkPVbK+&D1Tr`Ca%qb_P zNLcPb{o+IkGaZ;4F3ea5=79^7;=nLchw2J(4osj66X?JwTo}fI>F>f6-R@zptICDC z5C^ws-*nKi+crqC?z+RBE_%LxW8ilM9N=T954Ay%KI*fkNd)v+>erD&0o6^k%Z`G z{#MFQSrRYVBjwjGN$6sBC1c&SG}w=|OkwqTzSjHZAEf-LCGlMjxCoYkAgC*wX>oH< z)JplNr3sS5QhqFc9g*@c;MZ{}zjbLmcKw}VZhI^i+vq}60-^PwLi(70c45~N8D{K@ z)C%@B)d#V>=d-bh2HlGxZ=b7^nY#Ep`+8|Cby`autM%9;^|!k1>E>V2m_1o$3U-pH zgi*1uWG!{YIN_4ZU?I|%{Ijsm^f;vZw$w!7-2{q9mk(m-ZTk&gPyzP5butTBIh)Qwixnml1Pv1_eGRnslH3@C) zPa2}tRHQeVbQLmRj1pIZ;;JYqxur6zQ8w9}K;vdkGW1M5tu(f!Mg2!WeqWk|#E`;m zVJCKbwOz|Kee5VW}->EC~IgsF|%jpJem?11g&L(JVNtO87idvB?79^E9X*&hzJ`E4pVa@`pdM z!x{{mk@pu;lus|zl`vMf!BHp#r(<@k+S8$=+v-Jt%4Wfz3t4a zR>Ve8lJFBMogZDPxi8Bh_no{Qs?!~ziodcVW#B4jja7mv5;dj;sK0|>8tm8l^Ns^; z&UxM^k~~M+0otRmJ95};)?Y|It8WnfXQi&dJ1h0>%d(Mhq&?wCOZr4}=SR2|bi|jr zTQBJ^>+(9-bM;>K`MXxah{vT@C=BEikXNaS6MX_q@1nlKFF?FR0ly%G&wRd{JI)mD z;NO|Aa7?0q$z`T@of<4GQ;??0Oov>U=?=_!7e?*C2rkT14os^H6YIbP1vsr=fCCff z#1sh~aw-g?0_#dF(dEu%w>x>n98R!ib(k{(&;>)uuM98`!!wJ@7Dvb;C3m(XHYSX(rlU7*u z1Y3zV_Ww$d?j*%rO>AYR37wb$B!1<`8`(Kz>xaPB8^g%fi*~C|W!8@{^n-zo>xrWB&sfijr)hC^Kg>DGie%zZH_Rk0S=}l$50h%j)HvnGsc&%Vo1FSK zr@lkcF7-8e zS&@@J5fqJf=hQ8_(VjRbMKf22kU(x$n9MYtT2$2FYoVeBqhbEUlCDLkc_@5F!yD&` z9^y!m%(Tf#3hNt+?y#QQFJaOOgqIU5HFsq>^xevtt0;|qEPmTShWobgCOK}O7tz9B z-OAQm8!I(85D?4jNW!Ee7P6iU3>5X>-7H!x;#wK)bOiCbsBh~uH@j%==(6tqb4FKi z|DW?Q2V>5s72M4CNXxpReM&R=NhXUOOlrGX`rEjy8(hK7Ckx6DOA6``&&=O+6ui0Z zzh;YBvL{xBu0`Q!85V`{QM4oqmqEkC;L8%WJgTgT;&gOyjg`@IaX5}XQmA`(h=EV~ zDvVCwEs&S1I}oSoURIZ6s~pYJ#214HHynMyqs+!H|0asRX+1K)ked%X~Z3NzHJXL{0jnN_BqZT1v*Smy}x zHUu7$k5az5LR=ef-4Q+DwKlF{&pgLrM> z%zyiB4rHF%&A|KYEWy(jXv1qe6$8e#mefnk5{Sv&!6wUOyt?c1*6Xqg#rNU#z9$v} z(JNd@WV^hxPx5QnxN^x!M?1Y`uGa}tp*6(ddi6uqdF6C_@j%M;Ko=( z&GDTH!u2kc;<-Vv}K+^$MklA#pvW1#CY1vOCv^xO|Nm zCbB!Pg2k8CJqS|i58`vIe9w4%o5a7rKBc=oAoI4b%=E2`QLtD@o#>tx`MTWG?7;u) zo=)<8!ae2QBlnc=UK&7k<-h9(}2(x;N!Mr8N9!ICe(@k%5;coL*7fNW78mu2% zyO-D``R|#785nHGCuhQ>HxZ6=e0?@+i*#oew8?}lU3@63U>pKbac z*brdD4kbeO7QUNdvlZqQUMVSD@0NdwEWlFiE50diDqWWfCy9R{3+P zi!ae^g3{BQ^zFk+&2@^NE+yyfJ!gv|w(d>eZk9SimEwA01U2XLUZR7@*B%=A%y&dC zPS+oLp#!8(Y`Wa)uqsL(+X_R)iBD!a*U5RE7ls$t1n15Xe`luZZYnj?Ow3Cs`={IT z1NPwf)CErUMtDU{^_Fn{*~25c0CUldZ`YM?g!A7Y?%qRuXT|rqc~NtY+c_L6@HC4l z?sm$K!{a3HuHHGWK82P1qTRW%VTeRBd+pABM;A#_>>mVI$leOCOFOhik$C8zCS|sG zaBF&+XM}vMXGLS0=Ud@XTK2efrfK_HY=2vhcjL296i99d@<&b#D?&*J&F@5&)cF@* zy%WzVBjoWF_}MLasY^=7#;h_+nL9|5>Gfu|nnxbav?zVlosV4ui?o{Hjr*~zZjL~1-yKo&0Cq0Y& zid7OhqwVs(n|xP~!!mdI$(VsPd;J+9C6E_RRZ7~W{Mys6NyZ2BzKyeb+zzrnu>2Fm z)xe3iJI_M8{~UaXBzA6Qd9n>>loo;xN0pE1 zo8YGa37!8Sh>URl8162NuW%-W#?1v{SEIiWU*#v~{m%3(@|IWo$*pf&lX1!!QF9qA z^+C|}q(jdZiisoMvebt2zX614mWhU4>jq`VSoq2sD~gw<@|Nd&aWZeYWr_`-<>L6{ zXbB{l>TVfjtEknD_SH9|7|Tjp53CHWtK;g#e0yyvj7=t$W6xWd=P#nfSE`~-79hty z=Z9@v?f*^SEob7>+NIi9@e|?nAiET(wzjMOVDd#&EXZVY|ntZvg&Gd7G0u-Q<_$F`I*g)XbjmQt!PU0x{cjlpLO+#O*}9NMvq_-vn& z?A0jfsH#xtl%NT zyA=GfpEdN-B=YRpv{ZcAp!#0Hztt2g;S>0MO}!-*y?7k`>|WCgpI4#wvPAK>C=!mP z6rW(N#qiOZg`SE0+zWFh>k|2z3*#b)txlP))X7vzh%*RYVlw34pQ+^Inimg(mQ8^o z>1653DwPu9EH0EABLM*^dPNO~oYg8N_t0wBk>sR{R-iBApvo?I?j?dbK zR2v+23?-qoC)b4CO*$My31V}Lbx^64y!_Izlx4k-)7fCr2-s3KDQzhmm9_$33~Q+{ zy|-b`OblzyepLNeXDYsbNaPKdf)l?C&p|f9$4$H`07ZaL$xR8Zmrzc5Xi|&5hC3hM zvZ*?lKYdB**Dlo+bijs+h)0)322SlmV^$^#+B8+?`Rr zu5a2EaWW=q*QAp%f~n_msKI3J0-;UpNZ^GJd(Jp+`PUt)DfDM!BAUia{C0oI9SqOW zcW~mw@o6yii@s_9*6`x^qu~#^gT>OL;qLkiCF^~ee1||a)tSob5z4W4O~v#qF81o_ zItlGUgox2U4b;o|?%>5u^!$&mA1!u#2wA@lP9^p{FTIiyw*7DMYk8;-58+;=sp&=# zT9Kc9C0w>O9BXDUOU6)5KI&98UU#K;#Fn<2c#8(~^iy1& zsCa=?jXO}T){5rh30>TU?FxR^)p*=MLJD85)JZFpReB|O?#;J^{>}e=by&cAxA&}! zG>?kpPL(xoc)T)_&%72P!LQlZ`UH--UH0pq(n#}4O1-n;acLyK6{)3>JHNkH!eT^n zjU%PPOn&(F3`xN)zW92ur1xL^mg{3M;b)shiB1BJ1v{JtTgo80PB~bvlcvgTDFfwI z2BmPb!)_fHDLL6k?T@&nF!Cs_X^H0_8j0OQzKZoV$U`OQhtAvH4w0Oh9Vm+88QY?@5<6j2C^Z zrbN8Ztg_)T6M*Jw>eYyGu)|&7eC{_LoHDL9$yQPuP<++D7p?8{`tg@;D&(k8vo2++ z5*1ga*01%}1+#Zk8&}VyAyjxvHZ>Cupjb-|x!)56J?(h0+47?uFFDkPZ>823_}LL? zsN2@zmW9j{83!Ny z%vs0n?CqkSvJ~2MqkO5kukjoIQc1>d*;)J7BR9$Gz4;&R#h{-t!E$#oSwD1>mMX9z z5rk=17~zX%?<$soIhl96uNgp{TcuZ^p{~cZ6<=Yi@!UAmsq6LxKh~L09IsdKy8A~Z z?pJutgZ+}WUOQziA(E~PgCHKxTmGth#Zb==dH8mCyR@?K28^RYfluh||0)Zc_J5$@ zZ#*pRE&KvISeb_$H~@*mAKvJkAAiA&{Wf>d-UR2WAhxVb!Eb%kkCd?{xhB54Tv41p zG@n(Q*UQNtquDk7vAI-%7h(HjTK3YS*H7e7@7XSAtOfL3V{VtngI|1uXSsr9JnsAr zmnOcdOc7$URUE`O>f9Y9RVV_mEJGZHxb&fOvu(&MGf$Tn<)DCU(=vHQGkxeREbVX> z6($x1)`{x2>IwxyD9s=f_P4>_(}_IV8$2V(AetzLzL~@TZ9IsnkS1^k$BKi(8xHaN zrV4bo;Vf_RaCk&P+SWI|dulSVJhI}f~cEb|Q?qE^}wc`+! zJtxZ)OZ5r~X6_u*zO5Cw{iUD>ANY8>If8$xq{H2J7hIMBmzIM0u}i;&b9ci`wV^e>*E!e`8&db+i8liN@k3?Wz7TbPEsJKJ%CLCfw9E zuD!(Lb@AzJ`{q1S^kiF!svHK=L*H!qfI&O!o1t&hXzwv!qGR;v+_o~9V5?UxtFjLUX%7&M`7P}ERu_bS>Hrn%3;q-T#%Q4%JzYO zX*4qUK{S|F*_L|#58L1VrBVNZd-D%=QG)&xeD3Q<(=1jXUxXR-m)UxJcL5IueiU3J zH0BxMnwN1E%PJ(2AB}pJUByqhJwx)hH~-0Pu4qgYzC5wn@`GqfR%4ILH=5Nv?Dezt zq%|D%yC*6KIgWmG-VyS_32YW?N3p?YqL5cS9EH4=B{t!B$8pnoCCtz9Xzf^>+G1T!X(j|H1c2Q7Os);5~0Ah0N}-UV|4UI_%^I*=)jl zHe;lO?JHSjGft4ONs4n(?+LE2Bt@vkarZ7n>@1BsY%pVq+zY!no3lohLaGZfx{o z<2|spNLLykd$B3QU^0VY?K9!Pj1borUSP^i!+L{O7NVmnb`bi9Iuzr|-Zm*C(2to) z<0@}9^qEX}MI)3j4|G8N^a(5a-qH1*B6SsrRUlH+r`K_4&ghD~4*1B>uMHg%`I;^* zqkP@8-X-;c*1s&DG8PV_0c{|j&DLLpyG-njH@(?tN#+To)Q6273L%T0^o-lM5G=P* z8k|wlpK&SN?Dx50SQy;&I1txP69lB&!Pw9K`EO>&EK ztzi7xhs|JD8gKfrk^Ps2a(D8`@e|a)aP?Lzv&Ji1Nc&~FRgzd!x5h(A`bB)oHhqhD z-^9blA--(Sq8K?>XCFUNti3(fD{&|h7mfKa`Br`2@^bg|+1e}E-ubn7Jv&s}WC{z$ z(1Ns6g6aAXhmZ$#n&qN@{rFfWr(R_;Fp26_oO-4HLU9J8tv09O4hZq?kJYDfu>_|n zK)z5k_~;hp z%B?tyj4ENhTdB)hjmnYu6zA9#WtZ;> zxf}i16yIEN92TogirqOu>@taDLzHoU2-`=J8)dv1!uAgMD#}o)zF%<`p9rA#M-Ln0 zWo)q|GRpX=jGZpo+tnxuWp7F*MH=sfvgt*q_fD)ysF_%EdhgDC^!%Uybv;1uK0i3I zZ@ZqH806W1j&K_nFjoZCi&Q4%ugTwJ{gm3Zsi0KI4*sr7zo{cd6s6Z=3MHn+9 z*)5W1!i`Ol?1C`2P;@Qk*uUEj=e3oqua|ixUBeU=Zp=}#JtcKA<9a1~j#y8QVqa%x zy)MogIgWr97A>4?uVN-GxpV1MH4Qy1fACAEgd2TP8Su5DaRx!3f*W4b*wDO&-`~_6(F8-;QBZoy#QS zg%~!@pSVI4Q2UgqK;ak5Mnph_JlGO9tQpb(YbdPCH6i{NV$6(Xl`+WF3`fx+XBV+Z z+*!fokA>KC@oFrJW#i(AcX3y?VT>=XRvIm_jiCuOzY*@4WwI^xUV$%BHh^I;QPQ&B z9Y*V(;N8JBSU)q)?9M8DU$55{-X$Bw7>m2JsqELrz1`V3cOjiM7_W3^pO3(V#BlYx z(Gu?5+w7fg&1TU7QwiTP#^>VLL0;rKQSMLT*yp+k-i(2L*zK-`>o6*NuqEQep;K;D zR&7!mzwE(2RpcK;!3ggXb(_2OXDhBH6D|9rKvjp|O{4@)yV4NGz zHibC!mkt(*$BMY0g-;oyv^$&BlUmB|tZ+P{(~at(eU_6VYjc2@%S9&}PC zo$<#6cC2KWU{v;Gql+R1>m{eJV~?Sq`3-joDu{kVYr8bxbHk$=g|-(wG}`_<8vsjp zesfFy)0hWGi^5V(JnSdKXll{P_G3`If$+imJIE~3Ws)>W9mnL00Iqbu;>_>n8pY&_ z3)t)3S!A&81Q#4Bq_rKwzWWu4MDp92-&r{2yS}xrAH{Hypx{z&D601>th^&|0Wnei z3`ps{p|wWgj9uJN;8Pt(&gvVAsyCDko`Rx=vLc>A-ln~k_bXE4>eloVU(%Fl&;Hzn zGrwbv-iK=vz77!ahf@TFab7RX-T1w+7u$QznCmz|pF0`%{3KsYJ}L~kjum_^S1T1F zju_DDsH!{Hj3#fyH5S{UK-RyV?OSq_MhnL3Cip8GDc~|1jWgWb2hMp5d}!?97=3!P zGvh(@tFEA>dZQBboNSX45yK{Z-oyCnElP3ct|PwsEv1p`jp-Twya(<=zSuAt7iPj2 z)U;I>!s95WxK!~=isB1!*Dhbn;wd0PvOpuEWS$H zrqzMQ%06t2yYSD~2IIGV*gi$VFM*gAucId`&Qg?lK&1YnJ!F$wnYT!%UQXXoE)OOP zqFWP}q4P!a=Vvy)Gix92J8kbG?zm^7LHx#>`pB}>gD5qYAW>b?T z(rK2CD852M{L(G8c8wp1GUGZ%CH}_!Mtk6(Z2&20Nrc$y4eHf)*WBsjSS2`|(TH>} zj72xgDEqWRfjMJUUsfiG3pVcQ%SMmIbyfD|wJvWq4OSLwq&&LebA>q}7@M-#?3s-_ zFvZQK*(mmoSduWA4)?vFQ;*VV=%b!jzB0!2W93CF{c%VpTUMT@omO#HmSoe=>5vN{ zIoZX9T1M+*>Jx1BvW~OHo07;mB4dSPX>`feKHNuQE4YTIQz0dXA}l#9{-<6iH!N#0 zcD~30)Q~%d$e~VsP57;nX{8 z25`0GaKmh~ZW@b~2hB;fSiHoi$%5tiGPf~q#lt3QyLd+RYD`nNt?{t#&$J$coIBiM zG|}KVtxV?b+;804KDYn}OKUaFzFziiK-xjvm2)y?{P=jCS0;DxX%ut;1(`J{=Au}H zcWR^7J@roh6o(j8+z7EDAfB+CE+G*mMP?jyZqZG0)7P*EW0|ru z(D-LEtHE)HSE|WWi&=H3hcRma8yuEA!6MVG>-E)y+UG<@B7^&2IgP)JFuGF4rrXj%W^d9b5QZY#9{!9@HYC#c%~Mz|{o-vr{S=$1rv3ww&UkV|D#SMq-E2D1`YJo$mGrSkWX>&g&zj;Q

$X5^N&LQ4k=YP(M>Bh~>*pHRiPhfPZXrur? zTPL2Ypoo9?#myJj%k7GU@IE#RtUiv#tOrB817ec(W{#A`ShE|J0J!ncHq*Fj5GyO1 zM%RpYMSK@u59WKI9Y`wpBaYj(%dwgfXTSbL=cB(+cD{TJTZXIKz&NMnkWDKSxo%Kp zMt_CH(Ghmd&B}gYU&`72?Bj50R6_ClR%q#;RTvt$uk;TGWiiSlW+m9K z$uBx4?&WQ@)`}9~=1@e1k=4)Nx}7esvop`~w|5cFJv0NFJ#)kWVye9sTst}%08XG)ICH(U~He8Z%s~R+#&fC899#;5bJ`T$q@(00i z29#@Rat+F{+2HY27dw5kCq1~VN^!(%6{BM#8oiy>I+GjSY=2R&vThrFEN? z&dbht-~RRIwQa7#N>Lbc^-E`Gccn_9DmaEf>nvkB+Z&ATy=Od}&PEjpXWqjF;aE+o zJ205z?m(%qwnWh->JEQ#S*VllQ#4@bPP`i7jqk_+q4)bE1B(9j-?aUJN+EQdZyyV; zAwcmQZ-zs5y047d3Rm*P)4N{n8_x}{9Ob{mxG;m&L!8!a#s?Yfz+|Cvn*pavabx*~ zlE;Mw@nRbw8`&|Z_k;+6cs?7&hi)^@P_g@qWSAy>tHUBIlo5c~NTrwVj`~20f>SB* zu8iEy?&@_i=747;r-~K{?1-tJ$_iP0Bo`kCWS>ZyEG78?2l_F(L0n4DNL{{!OX(eH zmPM8ndb0VR+ua8qwMqumJj~wJcgF{UUX; zq{uqQnFj7ReRCqtnqe%>Jnjl}8$&YL#KA%rUkt_$x~~~GN8nOfsnFNgfLMaK3>K#O z8uY)CuZCdOLwpg01hn9AU*pKXeqzx44f!crTpBeCsx%*$Y(CxoB84BR~gY9TH5 zTOcoleYOQ_at(CVm#$NR;8xIvj$Jzv=V{?%|T*BCZ^K7mhL~|7lDg&GsDsWw?FH z9_VS`;BbRo#x0jX+K;!+-bxen4X!a03Buyz&ffCP=1vQ}9_a9?1dLc#oAAXI;}@gZ zNpdRHF?QS;Yl5gnj(1~-nvGTT{M0IDJC}SEOW4G`j_{);b!^EQMtJ7f?p!syfR(`W z**%6`I9?U0J5y5Yk3SZ0gSM#St0YWfZHW-1uc=Ot9Egt;ycw-~F*8xFvsTpXO=9(px+}!e%&_sCe>^uy zVsaaA>o+tKQz=B=!{WTU=#@8DkBF?!j5KY<;&KyjY#hsm^rb2`c#ep~I_9#DrVC}+ z6`UAJYQbE029~GSL65D<;hFS-{|Md~_%xe1MG*x&?#JW}dmn&sEAU3+&o3}_RyMutL%{p%M9nZ=Ucv}Ki`GZU9Y^lv_{q+~fVIcLq zWgIr1UF$B~=MBcabwqv@!CYTr@L_P1A{Rji%mu9$Q#CqS!~ZFmGF6`^*8t`2f4nvQoo(%ptQ5qFH3Qtcv-dJV{o|Xeb#54|16_hrVD8Izh zw}8XR^mYtSX93qz>FpYxZUVaEj~BsIv(O{}y|GLJo)Q5+45zeFEdFrQc*iTtOvlq4 zfORY-7T~GS@y0T*<7unoO~QPIr(J*}PZ4DUo=yOI%%Hd1cxnOU&!x9W1hl&YMvkYq zBs>iUJQnkf!Bek^_%U=PCC@-yMT`YUjudnY5)y@Q-Je;-vAcC9l-9*TJ{|`KcAO<@aLGse|}U_?Cz{} z>;k_UMY|M#{| +#include + +#ifndef _A_NORMAL +#define _A_NORMAL 0x00 +#endif + +static int remove_last_rc = 0; + +static void remove_usage_error(void) +{ + fprintf(stdout, "Command line arguments violate grammar defined for REMOVE.\n\n"); +} + +static void remove_usage(void) +{ + fprintf(stdout, "Usage: REMOVE [USER | GROUP] name [FROM path] [option]\n"); + fprintf(stdout, "Options: /Subdirs | /Files\n"); +} + +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; + + is_dir = forced_is_file ? 0 : trustee_path_is_dir(path); + + 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_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]; + trustee_header_path(header, path, sizeof(header)); + fprintf(stdout, "%s\n\n", header); + } + + if (objtype == TRUSTEE_BINDERY_GROUP) + fprintf(stdout, "Group \"%s\" no longer a trustee to the specified %s.\n", + objname, is_dir ? "directory" : "file"); + else + fprintf(stdout, "User \"%s\" no longer a trustee to the specified %s.\n", + objname, is_dir ? "directory" : "file"); + + return(0); +} + +static int remove_subdirs(char *path, uint16 dhandle, uint32 object_id, + uint16 objtype, char *objname, int *count) +{ + struct find_t ff; + char pattern[260]; + char child[260]; + int rc = 0; + + 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(child, dhandle, object_id, objtype, objname, count)) + rc = 1; + } + } while (_dos_findnext(&ff) == 0); + } + + return(rc); +} + +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_error(); + remove_usage(); + return(argc < 2 ? 1 : 0); + } + + 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) { + remove_usage_error(); + remove_usage(); + return(1); + } + + objname = argv[i++]; + + if (i < argc && trustee_same(argv[i], "FROM")) { + i++; + if (i >= argc) { + remove_usage_error(); + remove_usage(); + return(1); + } + path = argv[i++]; + } + + while (i < argc) { + if (!trustee_is_option(argv[i])) { + remove_usage_error(); + remove_usage(); + 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_error(); + remove_usage(); + 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, "Group \"%s\" not found.\n", objname); + else if (objtype_given) + fprintf(stdout, "User \"%s\" not found.\n", objname); + else + fprintf(stdout, "User 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", objprint, count); + else if (use_files) + fprintf(stdout, "Trustee \"%s\" removed from %d files.\n", objprint, count); + + return(rc ? (remove_last_rc ? remove_last_rc : 1) : 0); +} diff --git a/revoke.c b/revoke.c new file mode 100644 index 0000000..087cfe7 --- /dev/null +++ b/revoke.c @@ -0,0 +1,301 @@ +/* revoke.c - Novell REVOKE-like DOS utility */ + +#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_error(void) +{ + fprintf(stdout, "Command line arguments violate grammar defined for REVOKE.\n\n"); +} + +static void revoke_usage(void) +{ + fprintf(stdout, "Usage: REVOKE rightslist* [FOR path] FROM [USER|GROUP] name [options]\n"); + fprintf(stdout, "Options: /SubDirectories | /Files\n\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* Use abbreviations listed above, separated by spaces.\n"); +} + +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; + + is_dir = forced_is_file ? 0 : trustee_path_is_dir(path); + + 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, "No trustee for the specified %s.\n", is_dir ? "directory" : "file"); + else + fprintf(stdout, "Error scanning trustee list.\n"); + return(1); + } + + new_rights = (uint16)(old_rights & ~revoke_mask); + + if (new_rights == 0) { + 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 { + 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]; + trustee_header_path(header, path, sizeof(header)); + trustee_rights_bracket(new_rights, bracket); + fprintf(stdout, "%s\n\n", header); + fprintf(stdout, "Trustee's access rights set to [%s]\n", bracket); + } + + return(0); +} + +static int revoke_subdirs(char *path, uint16 dhandle, uint32 object_id, + uint16 revoke_mask, int *count) +{ + struct find_t ff; + char pattern[260]; + char child[260]; + int rc = 0; + + if (revoke_one(path, dhandle, object_id, revoke_mask, 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 (revoke_subdirs(child, dhandle, object_id, revoke_mask, count)) + rc = 1; + } + } while (_dos_findnext(&ff) == 0); + } + + return(rc); +} + +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_error(); + revoke_usage(); + 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"); + revoke_usage(); + return(1); + } + have_rights = 1; + i++; + } + + if (!have_rights || i >= argc) { + revoke_usage_error(); + revoke_usage(); + return(1); + } + + if (trustee_same(argv[i], "FOR")) { + i++; + if (i >= argc) { + revoke_usage_error(); + revoke_usage(); + return(1); + } + path = argv[i++]; + } + + if (i >= argc || !trustee_same(argv[i], "FROM")) { + revoke_usage_error(); + revoke_usage(); + 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_error(); + revoke_usage(); + return(1); + } + + objname = argv[i++]; + + while (i < argc) { + if (!trustee_is_option(argv[i])) { + revoke_usage_error(); + revoke_usage(); + 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_error(); + revoke_usage(); + return(1); + } + + if (use_files && use_subdirs) { + fprintf(stdout, "Revoke 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, "Group \"%s\" not found.\n", objname); + else if (objtype_given) + fprintf(stdout, "User \"%s\" not found.\n", objname); + else + fprintf(stdout, "User or group \"%s\" not found.\n", objname); + return(1); + } + + trustee_upcopy(objprint, objname, sizeof(objprint)); + + 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)) + fprintf(stdout, "Rights for %d directories were changed for %s.\n", count, objprint); + else if (use_files) + fprintf(stdout, "Rights for %d files were changed for %s.\n", count, objprint); + + return(rc ? (revoke_last_rc ? revoke_last_rc : 1) : 0); +} diff --git a/trustee.c b/trustee.c new file mode 100644 index 0000000..2adc2cd --- /dev/null +++ b/trustee.c @@ -0,0 +1,341 @@ +/* trustee.c - shared helpers for GRANT/REVOKE/REMOVE style tools */ + +#include "net.h" +#include "trustee.h" +#include +#include + +#ifndef S_IFDIR +#define S_IFDIR 0040000 +#endif + +int trustee_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); +} + +int trustee_is_help(char *s) +{ + if (!s) return(0); + return(trustee_same(s, "/?") || trustee_same(s, "-?") || trustee_same(s, "?")); +} + +int trustee_is_option(char *s) +{ + if (!s) return(0); + return(s[0] == '/' || s[0] == '-'); +} + +int trustee_is_subdirs_option(char *s) +{ + if (!s) return(0); + return(trustee_same(s, "/SUBDIRS") || trustee_same(s, "-SUBDIRS") || + trustee_same(s, "/SUBDIRECTORIES") || trustee_same(s, "-SUBDIRECTORIES") || + trustee_same(s, "/S") || trustee_same(s, "-S")); +} + +int trustee_is_files_option(char *s) +{ + if (!s) return(0); + return(trustee_same(s, "/FILES") || trustee_same(s, "-FILES") || + trustee_same(s, "/F") || trustee_same(s, "-F")); +} + +static int trustee_get_current_drive(void) +{ + REGS regs; + + regs.h.ah = 0x19; + int86(0x21, ®s, ®s); + return((int)regs.h.al); +} + +int trustee_current_dhandle(uint8 *connid, uint8 *dhandle) +{ + uint8 flags = 0; + int drive = trustee_get_current_drive(); + + if (get_drive_info((uint8)drive, connid, dhandle, &flags)) + return(-1); + + if (!*connid || (flags & 0x80)) + return(-1); + + return(0); +} + +static int trustee_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 = trustee_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); +} + +void trustee_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; +} + +void trustee_basename(char *dst, char *src, int max) +{ + char up[260]; + char *p; + + trustee_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); +} + +void trustee_header_path(char *out, char *path, int max) +{ + char prefix[90]; + char up[260]; + + if (trustee_current_prefix(prefix, sizeof(prefix))) + prefix[0] = '\0'; + + trustee_upcopy(up, path, sizeof(up)); + + strmaxcpy(out, prefix, max - 1); + if ((int)(strlen(out) + strlen(up)) < max - 1) + strcat(out, up); +} + +void trustee_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); +} + +int trustee_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); +} + +int trustee_parse_right_word(char *s, uint16 *rights) +{ + if (trustee_same(s, "ALL")) { + *rights = TRUSTEE_RIGHT_ALL_386; + return(0); + } + + if (trustee_same(s, "N") || trustee_same(s, "NONE")) { + *rights = TRUSTEE_RIGHT_ALL_386; + return(0); + } + + if (trustee_same(s, "S") || trustee_same(s, "SUPERVISOR")) { + *rights |= TRUSTEE_RIGHT_SUPER; + return(0); + } + + if (trustee_same(s, "R") || trustee_same(s, "READ")) { + *rights |= TRUSTEE_RIGHT_READ; + return(0); + } + + if (trustee_same(s, "W") || trustee_same(s, "WRITE")) { + *rights |= TRUSTEE_RIGHT_WRITE; + return(0); + } + + if (trustee_same(s, "C") || trustee_same(s, "CREATE")) { + *rights |= TRUSTEE_RIGHT_CREATE; + return(0); + } + + if (trustee_same(s, "E") || trustee_same(s, "ERASE") || + trustee_same(s, "D") || trustee_same(s, "DELETE")) { + *rights |= TRUSTEE_RIGHT_DELETE; + return(0); + } + + if (trustee_same(s, "M") || trustee_same(s, "MODIFY")) { + *rights |= TRUSTEE_RIGHT_MODIFY; + return(0); + } + + if (trustee_same(s, "F") || trustee_same(s, "FILESCAN") || + trustee_same(s, "FILE") || trustee_same(s, "SCAN")) { + *rights |= TRUSTEE_RIGHT_SEARCH; + return(0); + } + + if (trustee_same(s, "A") || trustee_same(s, "ACCESS") || + trustee_same(s, "CONTROL") || trustee_same(s, "ACCESSCONTROL")) { + *rights |= TRUSTEE_RIGHT_OWNER; + return(0); + } + + return(-1); +} + +void trustee_rights_bracket(uint16 rights, char *out) +{ + out[0] = (rights & TRUSTEE_RIGHT_SUPER) ? 'S' : ' '; + out[1] = (rights & TRUSTEE_RIGHT_READ) ? 'R' : ' '; + out[2] = (rights & TRUSTEE_RIGHT_WRITE) ? 'W' : ' '; + out[3] = (rights & TRUSTEE_RIGHT_CREATE) ? 'C' : ' '; + out[4] = (rights & TRUSTEE_RIGHT_DELETE) ? 'E' : ' '; + out[5] = (rights & TRUSTEE_RIGHT_MODIFY) ? 'M' : ' '; + out[6] = (rights & TRUSTEE_RIGHT_SEARCH) ? 'F' : ' '; + out[7] = (rights & TRUSTEE_RIGHT_OWNER) ? 'A' : ' '; + out[8] = '\0'; +} + +uint32 trustee_lookup_object(char *name, uint16 *objtype, int objtype_given) +{ + uint8 namebuf[48]; + uint32 object_id; + + strmaxcpy(namebuf, name, sizeof(namebuf) - 1); + upstr(namebuf); + + if (objtype_given) { + return(ncp_17_35(namebuf, *objtype)); + } + + *objtype = TRUSTEE_BINDERY_USER; + object_id = ncp_17_35(namebuf, TRUSTEE_BINDERY_USER); + if (object_id) + return(object_id); + + *objtype = TRUSTEE_BINDERY_GROUP; + object_id = ncp_17_35(namebuf, TRUSTEE_BINDERY_GROUP); + return(object_id); +} + +int trustee_path_is_dir(char *path) +{ + struct stat st; + + if (!path || !*path || trustee_same(path, ".") || trustee_same(path, ".\\") || + trustee_same(path, "./")) + return(1); + + if (stat(path, &st) == 0) { + if (st.st_mode & S_IFDIR) + return(1); + } + + return(0); +} + +int trustee_path_has_wildcards(char *path) +{ + if (!path) return(0); + while (*path) { + if (*path == '*' || *path == '?') return(1); + path++; + } + return(0); +} + +void trustee_parent_pattern(char *dir, char *pattern, char *path, int maxdir, int maxpat) +{ + char tmp[260]; + char *p; + + trustee_upcopy(tmp, path, sizeof(tmp)); + p = strrchr(tmp, '\\'); + if (!p) p = strrchr(tmp, ':'); + + if (p) { + char save = *p; + *p = '\0'; + strmaxcpy(pattern, p + 1, maxpat - 1); + if (save == ':') { + *p = ':'; + *(p + 1) = '\0'; + } + strmaxcpy(dir, tmp, maxdir - 1); + } else { + strmaxcpy(dir, ".", maxdir - 1); + strmaxcpy(pattern, tmp, maxpat - 1); + } +} diff --git a/trustee.h b/trustee.h new file mode 100644 index 0000000..0f06608 --- /dev/null +++ b/trustee.h @@ -0,0 +1,40 @@ +#ifndef TRUSTEE_H +#define TRUSTEE_H + +#define TRUSTEE_BINDERY_USER 0x0001 +#define TRUSTEE_BINDERY_GROUP 0x0002 + +#define TRUSTEE_RIGHT_READ 0x0001 +#define TRUSTEE_RIGHT_WRITE 0x0002 +#define TRUSTEE_RIGHT_OPEN 0x0004 +#define TRUSTEE_RIGHT_CREATE 0x0008 +#define TRUSTEE_RIGHT_DELETE 0x0010 +#define TRUSTEE_RIGHT_OWNER 0x0020 +#define TRUSTEE_RIGHT_SEARCH 0x0040 +#define TRUSTEE_RIGHT_MODIFY 0x0080 +#define TRUSTEE_RIGHT_SUPER 0x0100 + +#define TRUSTEE_RIGHT_ALL_386 (TRUSTEE_RIGHT_SUPER | TRUSTEE_RIGHT_READ | \ + TRUSTEE_RIGHT_WRITE | TRUSTEE_RIGHT_CREATE | \ + TRUSTEE_RIGHT_DELETE | TRUSTEE_RIGHT_MODIFY | \ + TRUSTEE_RIGHT_SEARCH | TRUSTEE_RIGHT_OWNER) + +int trustee_same(char *a, char *b); +int trustee_is_help(char *s); +int trustee_is_option(char *s); +int trustee_is_subdirs_option(char *s); +int trustee_is_files_option(char *s); +int trustee_current_dhandle(uint8 *connid, uint8 *dhandle); +void trustee_upcopy(char *dst, char *src, int max); +void trustee_basename(char *dst, char *src, int max); +void trustee_header_path(char *out, char *path, int max); +void trustee_join_path(char *out, char *base, char *name, int max); +int trustee_is_dot_dir(char *name); +int trustee_parse_right_word(char *s, uint16 *rights); +void trustee_rights_bracket(uint16 rights, char *out); +uint32 trustee_lookup_object(char *name, uint16 *objtype, int objtype_given); +int trustee_path_is_dir(char *path); +int trustee_path_has_wildcards(char *path); +void trustee_parent_pattern(char *dir, char *pattern, char *path, int maxdir, int maxpat); + +#endif