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

/* The add routine handles both creation and modification
   of tables. */
static db_status
db_add (nis_name name, nis_object *obj, uint32_t *dticks)
{
  XDR xdrs;
  nis_name db_name, newname;
  nis_attr attr;
  db_status db_stat;
  db_result *db_res;
  char *buf1 = NULL, *buf2 = NULL;
  size_t buf1len = 0, buf2len = 0;
  entry_obj dir_ent;
  entry_col dir_ent_col[2];
  char *addr;
  u_long size;

  if (verbose)
    log_msg (LOG_DEBUG, "nis_add_svc/db_add (%s)", name);

  *dticks = 0;

  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);

  newname = nis_leaf_of_r (name, buf1, buf1len);

  /* XDR the supplied object struct. */
  size = xdr_sizeof ((xdrproc_t) xdr_nis_object, obj);
  if ((addr = calloc (1, size)) == NULL)
    return DB_MEMORY_LIMIT;
  xdrmem_create (&xdrs, addr, size, XDR_ENCODE);
  if (!xdr_nis_object (&xdrs, obj))
    {
      xdr_destroy (&xdrs);
      free (addr);
      return DB_INTERNAL_ERROR;
    }

  xdr_destroy (&xdrs);

  /* Create an entry to be stored in the parent directory 'table.' */
  dir_ent_col[0].ec_flags = 0;
  dir_ent_col[0].ec_value.ec_value_val = newname;
  dir_ent_col[0].ec_value.ec_value_len = strlen (newname);
  dir_ent_col[1].ec_flags = EN_BINARY | EN_XDR;
  dir_ent_col[1].ec_value.ec_value_val = addr;
  dir_ent_col[1].ec_value.ec_value_len = size;

  if (__type_of (obj) == DIRECTORY_OBJ)
    {
      dir_ent.en_type = "IN_DIRECTORY";
      attr.zattr_ndx = "IN_DIRECTORY";
    }
  else
    {
      dir_ent.en_type = "IN_TABLE";
      attr.zattr_ndx = "IN_TABLE";
    }

  dir_ent.en_cols.en_cols_len = 2;
  dir_ent.en_cols.en_cols_val = (entry_col *) &dir_ent_col;

  attr.zattr_val.zattr_val_len = strlen (newname);
  attr.zattr_val.zattr_val_val = newname;

  db_res = db_add_entry (db_name, 1, &attr, &dir_ent);

  free (addr);

  if (db_res == NULL)
    return DB_MEMORY_LIMIT;

  db_stat = db_res->status;
  *dticks = db_res->ticks;
  db_free_result (db_res);

  if (__type_of (obj) == TABLE_OBJ)
    db_stat = db_create_table (nis_name_of (name), &obj->TA_data);

  return db_stat;
}

bool_t
nis_add_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;
  db_status db_stat;

  start_clock (SERVER_CLOCK);

  principal = get_nis_principal (rqstp);

  if (verbose)
    {
      rqhost = svc_getcaller (rqstp->rq_xprt);
      log_msg (LOG_DEBUG, "nis_add_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);
      nis_print_object (argp->ns_object.ns_object_val);
    }

  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)
    {
      /* A request to add the root object makes absolutely no sense
	 and is probably a sign that the caller is insane. */
      result->status = NIS_NAMEEXISTS;

      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_add_svc: failed to find parent object!");
      goto bailout;
  }

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

  /* Make sure an object with the same name doesn't already exist. */
  if (nis_ocache_lookup (argp->ns_name, 0, 0, NULL) == NIS_SUCCESS)
    {
      result->status = NIS_NAMEEXISTS;
      goto bailout;
    }

  /* Update the OID. */
  argp->ns_object.ns_object_val->zo_oid.ctime = time(NULL);
  argp->ns_object.ns_object_val->zo_oid.mtime =
    argp->ns_object.ns_object_val->zo_oid.ctime;

  db_stat = db_add (argp->ns_name, argp->ns_object.ns_object_val,
		    &result->dticks);

  if (db_stat != DB_SUCCESS)
    {
      result->status = db2nis_error (db_stat);
      goto bailout;
    }

 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_ADD, result->status == NIS_SUCCESS ? TRUE : FALSE,
                    result->zticks);

  return retval;
}
