/*
 * 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: REMOVE utility for removing trustee rights from NetWare directories and files.
 * Depends on: net.h, ncpapi.h, trustee.h/trustee.c trustee helpers, netcall.c requester helpers, tools.c shared utility routines.
 */

#include "net.h"
#include "ncpapi.h"
#include "trustee.h"
#include <sys/types.h>
#include <sys/stat.h>

#ifndef _A_NORMAL
#define _A_NORMAL 0x00
#endif

static int remove_last_rc = 0;

static void remove_usage(void)
{
  fprintf(stdout, "\n");
  fprintf(stdout, "Usage:    REMOVE [USER | GROUP] name [FROM path] [option]\n");
  fprintf(stdout, "Options:  /Subdirs | /Files \n");
}

static void remove_usage_after_error(void)
{
  fprintf(stdout, "\007\n");
  fprintf(stdout, "Usage:    REMOVE [USER | GROUP] name [FROM path] [option]\n");
  fprintf(stdout, "Options:  /Subdirs | /Files \n");
  fprintf(stdout, "\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;

  /* Without /FILES, Novell REMOVE reports directory-oriented messages even
   * if the specified path cannot be located. */
  is_dir = forced_is_file ? 0 : 1;

  rc = ncp87_05_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 = ncp22_2b_delete_trustee_rights(path, dhandle, object_id);
  if (rc)
    rc = ncp87_0b_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", header);
  }

  if (objtype == TRUSTEE_BINDERY_GROUP)
    fprintf(stdout, "Group \"%s\" no longer a trustee to the specified %s.\n\n",
            objname, is_dir ? "directory" : "file");
  else
    fprintf(stdout, "User \"%s\" no longer a trustee to the specified %s.\n\n",
            objname, is_dir ? "directory" : "file");

  return(0);
}

static int remove_subdirs_inner(char *path, uint16 dhandle, uint32 object_id,
                                uint16 objtype, char *objname, int *count,
                                int include_this)
{
  struct find_t ff;
  char pattern[260];
  char child[260];
  int rc = 0;

  if (include_this) {
    if (remove_one(path, dhandle, object_id, objtype, objname, 0) == 0)
      (*count)++;
    else
      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 (remove_subdirs_inner(child, dhandle, object_id, objtype, objname,
                                 count, 1))
          rc = 1;
      }
    } while (_dos_findnext(&ff) == 0);
  }

  return(rc);
}

static int remove_subdirs(char *path, uint16 dhandle, uint32 object_id,
                          uint16 objtype, char *objname, int *count)
{
  /* Novell REMOVE /SUBDIRS affects the subdirectories under the specified
   * start directory, not the start directory itself. */
  return(remove_subdirs_inner(path, dhandle, object_id, objtype, objname,
                              count, 0));
}

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 (tool_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 (remove_one(path, dhandle, object_id, objtype, objname, 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 (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 || tool_is_help_arg(argv[1])) {
    if (argc < 2) {
      remove_usage_after_error();
      return(1);
    }
    remove_usage();
    return(argc < 2 ? 1 : 0);
  }

  if (i < argc && tool_strsame(argv[i], "USER")) {
    /* Novell treats "REMOVE USER FROM path" as an object lookup for
     * USER, not as a grammar error. */
    if ((i + 1) < argc && tool_strsame(argv[i + 1], "FROM")) {
      objtype_given = 0;
    } else {
      objtype = TRUSTEE_BINDERY_USER;
      objtype_given = 1;
      i++;
    }
  } else if (i < argc && tool_strsame(argv[i], "GROUP")) {
    if ((i + 1) < argc && tool_strsame(argv[i + 1], "FROM")) {
      objtype_given = 0;
    } else {
      objtype = TRUSTEE_BINDERY_GROUP;
      objtype_given = 1;
      i++;
    }
  }

  if (i >= argc) {
    remove_usage_after_error();
    return(1);
  }

  objname = argv[i++];

  if (i < argc && tool_strsame(argv[i], "FROM")) {
    i++;
    if (i >= argc) {
      remove_usage_after_error();
      return(1);
    }
    path = argv[i++];
  }

  while (i < argc) {
    if (!tool_is_option(argv[i])) {
      remove_usage_after_error();
      return(1);
    }

    if (tool_is_files_option(argv[i])) {
      use_files = 1;
      i++;
      continue;
    }

    if (tool_is_subdirs_option(argv[i])) {
      use_subdirs = 1;
      i++;
      continue;
    }

    remove_usage_after_error();
    return(1);
  }

  if (use_files && use_subdirs) {
    fprintf(stdout, "Remove cannot do both directories and files in a single pass.\n");
    return(1);
  }

  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, "\007Group \"%s\" not found.\n", objname);
    else if (objtype_given)
      fprintf(stdout, "\007User \"%s\" not found.\n", objname);
    else
      fprintf(stdout, "\007User or group \"%s\" not found.\n", objname);
    return(1);
  }

  tool_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\n", objprint, count);
  else if (use_files)
    fprintf(stdout, "Trustee \"%s\" removed from %d files.\n\n", objprint, count);

  return(rc ? (remove_last_rc ? remove_last_rc : 1) : 0);
}
