/*
 * 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: GRANT utility for assigning trustee rights on NetWare directories and files.
 * Depends on: net.h, ncpapi.h, netcall.c requester helpers, ncpapi.c namespace/NCP helpers, tools.c shared utility routines.
 */
/* grant.c - Novell GRANT-like DOS utility, first Client32 implementation */

#include "net.h"
#include "ncpapi.h"
#include "trustee.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

/* 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 void grant_usage_error(void)
{
  fprintf(stdout, "Command line arguments violate grammar defined for GRANT.\n\n");
}

static void grant_invalid_right_error(void)
{
  fprintf(stdout, "Specified rights unreadable or invalid.  Spaces required between each right.\n\n");
}

static void grant_usage_ex(int leading_blanks, int bell_after_options,
                           int trailing_blanks)
{
  int i;

  for (i = 0; i < leading_blanks; i++)
    fprintf(stdout, "\n");

  fprintf(stdout, "Usage:  GRANT rightslist* [FOR path] TO [USER | GROUP] name [options]\n");
  fprintf(stdout, "Options:  /SubDirectories | /Files\n");
  if (bell_after_options)
    fprintf(stdout, "\a");
  fprintf(stdout, "\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");

  for (i = 0; i < trailing_blanks; i++)
    fprintf(stdout, "\n");
}

static void grant_usage(void)
{
  grant_usage_ex(0, 0, 0);
}

static void grant_usage_help(void)
{
  grant_usage_ex(2, 0, 1);
}

static void grant_usage_after_error(void)
{
  grant_usage_ex(0, 1, 2);
}

static void grant_rights_bracket(uint16 rights, char *out)
{
  /* Novell displays Supervisor as the full effective right mask. */
  if (rights & NCP_RIGHT_SUPER)
    rights |= NCP_RIGHT_ALL_386;

  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 uint16 grant_expand_supervisor_rights(uint16 rights)
{
  /* Novell GRANT expands Supervisor to the full trustee mask before
   * storing it.  This keeps the wire value and the printed result in
   * sync with Novell: GRANT S reports and sends [SRWCEMFA], not just
   * [S       ]. */
  if (rights & NCP_RIGHT_SUPER)
    rights |= NCP_RIGHT_ALL_386;
  return(rights);
}

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 (tool_strsame(s, "ALL")) {
    *rights = NCP_RIGHT_ALL_386;
    return(0);
  }

  if (tool_strsame(s, "N") || tool_strsame(s, "NONE")) {
    *rights = 0;
    return(0);
  }

  if (tool_strsame(s, "S") || tool_strsame(s, "SUPERVISOR")) {
    *rights |= NCP_RIGHT_SUPER;
    return(0);
  }

  if (tool_strsame(s, "R") || tool_strsame(s, "READ")) {
    *rights |= NCP_RIGHT_READ;
    return(0);
  }

  if (tool_strsame(s, "W") || tool_strsame(s, "WRITE")) {
    *rights |= NCP_RIGHT_WRITE;
    return(0);
  }

  if (tool_strsame(s, "C") || tool_strsame(s, "CREATE")) {
    *rights |= NCP_RIGHT_CREATE;
    return(0);
  }

  if (tool_strsame(s, "E") || tool_strsame(s, "ERASE")) {
    *rights |= NCP_RIGHT_DELETE;
    return(0);
  }

  if (tool_strsame(s, "M") || tool_strsame(s, "MODIFY")) {
    *rights |= NCP_RIGHT_MODIFY;
    return(0);
  }

  if (tool_strsame(s, "F") || tool_strsame(s, "FILESCAN") ||
      tool_strsame(s, "FILE") || tool_strsame(s, "SCAN")) {
    *rights |= NCP_RIGHT_SEARCH;
    return(0);
  }

  if (tool_strsame(s, "A") || tool_strsame(s, "ACCESS") ||
      tool_strsame(s, "CONTROL") || tool_strsame(s, "ACCESSCONTROL")) {
    *rights |= NCP_RIGHT_OWNER;
    return(0);
  }

  return(-1);
}

static int grant_last_rc = 0;

static int grant_set_one(char *path, uint16 dhandle,
                         uint32 object_id, uint16 rights)
{
  int rc;

  /* Novell GRANT uses the old NCP22/27 SetTrustee path.  Prefer that
   * path and keep the NCP87 add-trustee call only as a compatibility
   * fallback for clients/servers that do not accept the old call. */
  rc = ncp22_27_set_trustee_rights(path, dhandle, object_id, rights);
  if (rc) {
    rc = ncp87_0a_add_trustee_rights(path,
                                      dhandle,
                                      object_id,
                                      rights,
                                      0xffff,
                                      NULL, NULL, NULL);
  }

  grant_last_rc = rc;
  return(rc);
}

/*
 * 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;

  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 (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 || tool_is_help_arg(argv[1])) {
    if (argc < 2) {
      fprintf(stdout, "\n");
      grant_usage_error();
      grant_usage_after_error();
      return(1);
    }
    grant_usage_help();
    return(0);
  }

  /*
   * GRANT rightslist* [FOR path] TO [USER|GROUP] name [options]
   */
  while (i < argc) {
    if (tool_strsame(argv[i], "FOR") || tool_strsame(argv[i], "TO"))
      break;

    if (tool_is_option(argv[i]))
      break;

    if (grant_add_right_word(argv[i], &rights)) {
      fprintf(stdout, "\n");
      grant_invalid_right_error();
      grant_usage_after_error();
      return(1);
    }
    have_rights = 1;
    i++;
  }

  if (!have_rights || i >= argc) {
    fprintf(stdout, "\n");
    grant_usage_error();
    grant_usage_after_error();
    return(1);
  }

  if (tool_strsame(argv[i], "FOR")) {
    i++;
    if (i >= argc) {
      fprintf(stdout, "\n");
      grant_usage_error();
      grant_usage_after_error();
      return(1);
    }
    path = argv[i++];
  }

  if (i >= argc || !tool_strsame(argv[i], "TO")) {
    fprintf(stdout, "\n");
    grant_usage_error();
    grant_usage_after_error();
    return(1);
  }
  i++;

  if (i < argc && tool_strsame(argv[i], "USER")) {
    objtype = GRANT_BINDERY_USER;
    i++;
  } else if (i < argc && tool_strsame(argv[i], "GROUP")) {
    objtype = GRANT_BINDERY_GROUP;
    i++;
  }

  if (i >= argc) {
    fprintf(stdout, "\n");
    grant_usage_error();
    grant_usage_after_error();
    return(1);
  }

  objname = argv[i++];

  while (i < argc) {
    if (!tool_is_option(argv[i])) {
      fprintf(stdout, "\n");
      grant_usage_error();
      grant_usage_after_error();
      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 (tool_strsame(argv[i], "/FILES") || tool_strsame(argv[i], "-FILES") ||
        tool_strsame(argv[i], "/F") || tool_strsame(argv[i], "-F")) {
      i++;
      continue;
    }

    if (tool_strsame(argv[i], "/SUBDIRECTORIES") ||
        tool_strsame(argv[i], "-SUBDIRECTORIES") ||
        tool_strsame(argv[i], "/S") || tool_strsame(argv[i], "-S")) {
      recurse_subdirs = 1;
      i++;
      continue;
    }

    fprintf(stdout, "\n");
    grant_usage_error();
    grant_usage_after_error();
    return(1);
  }

  rights = grant_expand_supervisor_rights(rights);

  if (tool_current_dhandle(&connid, &dhandle)) {
    fprintf(stdout, "Specified path not locatable.\n");
    return(1);
  }

  strmaxcpy(namebuf, objname, sizeof(namebuf) - 1);
  upstr(namebuf);
  object_id = ncp17_35_get_bindery_object_id(namebuf, objtype);
  if (!object_id) {
    char header[300];

    trustee_header_path(header, path, sizeof(header));
    fprintf(stdout, "\n%s\n", header);
    if (objtype == GRANT_BINDERY_GROUP)
      fprintf(stdout, "\aGroup \"%s\" not found.\n", objname);
    else
      fprintf(stdout, "\aUser \"%s\" not found.\n", objname);
    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) {
    char header[300];

    trustee_header_path(header, path, sizeof(header));
    fprintf(stdout, "\n%s\n", header);
    fprintf(stdout, "Invalid path or no match for pattern specified.\n\n");
    return(grant_last_rc ? grant_last_rc : 1);
  }

  {
    char header[300];
    char base[80];
    char bracket[10];

    trustee_header_path(header, path, sizeof(header));
    tool_basename(base, path, sizeof(base));
    grant_rights_bracket(rights, bracket);

    fprintf(stdout, "\n%s\n", header);
    fprintf(stdout, "%-33.33sRights set to [%s]\n\n", base, bracket);
  }

  return(0);
}
