/* Copyright (c) 1999 Thorsten Kukuk
   Author: Thorsten Kukuk <kukuk@suse.de>

   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, 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, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <assert.h>
#include <locale.h>
#include <libintl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <rpcsvc/nis.h>
#include "nis_db.h"
#include "nis_xdr.h"
#include "nisd.h"
#include "log_msg.h"

#ifndef _
#define _(String) gettext (String)
#endif

static inline void
db_remove (const_nis_name name, nis_result *result)
{
  nis_attr attr;
  char *db_name, *oldname;
  db_result *db_res;
  char *buf1 = NULL, *buf2 = NULL;
  size_t buf1len = 0, buf2len = 0;

  if (verbose)
    log_msg (LOG_DEBUG, "nis_remove_svc/db_remove (%s)", name);

  buf1len = strlen (name) + 1;
  buf1 = alloca (buf1len);
  db_name = nis_domain_of_r (name, buf1, buf1len);
  if (nis_dir_cmp (db_name, root_obj->DI_data.do_name) == SAME_NAME)
    db_name = "root_dir";
  else
    {
      buf2len = strlen (db_name) + 1;
      buf2 = alloca (buf2len);
      db_name = nis_name_of_r (db_name, buf2, buf2len);
    }
  assert (db_name);

  oldname = nis_leaf_of_r (name, buf1, buf1len);

  attr.zattr_ndx = "IN_DIRECTORY";
  attr.zattr_val.zattr_val_len = strlen(oldname);
  attr.zattr_val.zattr_val_val = oldname;

  db_res = db_remove_entry (db_name, 1, &attr);

  if (db_res == NULL)
    result->status = DB_MEMORY_LIMIT;
  else
    {
      result->status = db_res->status;
      result->dticks += db_res->ticks;
      db_free_result (db_res);
    }
}

bool_t
nis_remove_3_r (ns_request *argp, nis_result *result, struct svc_req *rqstp)
{
  bool_t retval = TRUE;
  struct sockaddr_in *rqhost;
  SERVER_CLOCK_DECLS;
  char *principal;
  nis_object *obj;

  start_clock (SERVER_CLOCK);

  principal = get_nis_principal (rqstp);

  if (verbose)
    {
      rqhost = svc_getcaller (rqstp->rq_xprt);
      log_msg (LOG_DEBUG, "nis_remove_3() [From: %s:%d,%s]",
	       inet_ntoa (rqhost->sin_addr), ntohs (rqhost->sin_port),
               principal ? principal : "{error}");

      log_msg (LOG_DEBUG, "\t\tns_name     = \"%s\"", argp->ns_name);
      log_msg (LOG_DEBUG, "\t\t# ns_object = %u",
	       argp->ns_object.ns_object_len);
    }

  memset (result, '\0', sizeof (nis_result));

  if (readonly)
    {
      result->status = NIS_TRYAGAIN;
      goto bailout;
    }

  /* We should never see an indexed name. */
  if (strchr (argp->ns_name, '[') != NULL)
    {
      result->status = NIS_BADNAME;
      goto bailout;
    }

  /* Do some sanity checks */
  result->status = nis_validname (argp->ns_name);
  if (result->status != NIS_SUCCESS)
    goto bailout;

  /* We should never see an entry here. */
  if (argp->ns_object.ns_object_val != NULL &&
      __type_of (argp->ns_object.ns_object_val) == ENTRY_OBJ)
    {
      result->status = NIS_BADOBJECT;
      goto bailout;
    }

  /* If zo_name exists, it must be a leaf (i.e. no dots). */
  if (argp->ns_object.ns_object_val != NULL &&
      strchr (argp->ns_object.ns_object_val->zo_name, '.') != NULL)
    {
      result->status = NIS_INVALIDOBJ;
      goto bailout;
    }

  /* Special handling for the root object: */
  if (nis_dir_cmp (argp->ns_name, root_obj->DI_data.do_name) == SAME_NAME)
    {
      /* I'm not altogether sure about this, but: if we are a root
	 replica, it is conceivable that we could be removed as a replica,
	 in which case we'd no longer need a copy of the root object around.
	 Note that we check permissions using the root object itself rather
	 that the permissions of the parent directory since the root
	 directory, by definition, can't have a parent.
      */
      if (nis_can_destroy (principal, root_obj))
	return NIS_PERMISSION;

      if (nisd_i_am_replica (&root_obj->DI_data) == FALSE)
	return NIS_BADREQUEST;

      nis_destroy_object (root_obj);
      root_obj = NULL;
      unlink (_PATH_NIS_ROOT_OBJ);
      remove_from_cache (argp->ns_name);
      goto bailout;
    }

  /* Find the parent directory object. We need this for all the operations
     that change the namespace, usually for permissions tests.
     And if the parent directory object doesn't exist, we're in trouble and
     can't proceed with the operation anyway. (This also verifies that we
     are in fact the master server for the directory to be updated.) */
  {
    char *buffer, *cp;
    size_t buflen;

    buflen = strlen (argp->ns_name) + 1;
    buffer = alloca (buflen);
    cp = nis_domain_of_r (argp->ns_name, buffer, buflen);

    result->status = nis_ocache_lookup (cp, CHECK_MASTER, MASTER_ONLY, &obj);
  }

  if (result->status != NIS_SUCCESS)
    {
      log_msg (LOG_ERR, "nis_remove_svc: failed to find parent object!");
      goto bailout;
    }

  if (!nis_can_destroy (principal, obj))
    {
      result->status = NIS_PERMISSION;
      nis_free_object (obj);
      goto bailout;
    }
  else
    nis_free_object (obj);

  /* Need the existing object here for a sanity test. */
  result->status = nis_ocache_lookup (argp->ns_name, CHECK_MASTER, MASTER_ONLY,
				      &obj);

  if (result->status != NIS_SUCCESS)
    {
      nis_free_object (obj);
      goto bailout;
    }

  /* If the object is a table, the table must be empty.
     The test to avoid the removal of directories containing
     subdirectories is done by nis_rmdir_3_r. */
  if (__type_of(obj) == TABLE_OBJ)
    {
      db_result *dres = db_first_entry (argp->ns_name, 0, NULL);
      result->dticks += dres->ticks;
      if (dres->status == DB_SUCCESS)
	{
	  result->status = db2nis_error (dres->status);
	  db_free_result (dres);
	  nis_free_object (obj);
	  return NIS_NOTEMPTY;
	}
      else
	db_free_result (dres);
    }
  nis_free_object (obj);

  db_remove (argp->ns_name, result);

  remove_from_cache (argp->ns_name);

 bailout:
  free (principal);

  if (verbose)
    log_msg (LOG_DEBUG, "\tresult = %s", nis_sperrno (result->status));

  result->zticks = stop_clock (SERVER_CLOCK);

  nis_update_stats (NIS_REMOVE, result->status == NIS_SUCCESS ? TRUE : FALSE,
                    result->zticks);

  return retval;
}
