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

#define _GNU_SOURCE

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <libintl.h>
#include <rpcsvc/ypclnt.h>
#include <rpcsvc/nis.h>

#include "nisaddent.h"

extern int parsed_free (parsed_t *parsed);
#ifndef _
#define _(String) gettext (String)
#endif

static nis_object *
init_entry (const nis_object *pobj)
{
  static nis_object obj;
  entry_col *data;
  entry_obj *eo;
  size_t columns;

  columns = pobj->zo_data.objdata_u.ta_data.ta_maxcol;
  data = (entry_col *) malloc (sizeof (entry_col) * columns);
  if (data == NULL)
    {
      fprintf (stderr, _("Could not alloc memory\n"));
      return NULL;
    }
  memset (&obj, 0, sizeof (obj));
  memset (data, 0, sizeof (entry_col) * columns);

  obj.zo_name = pobj->zo_name;
  obj.zo_owner = pobj->zo_owner;
  obj.zo_group = pobj->zo_group;
  obj.zo_domain = pobj->zo_domain;
  obj.zo_access = pobj->zo_access;
  obj.zo_ttl = pobj->zo_ttl;
  obj.zo_data.zo_type = NIS_ENTRY_OBJ;
  eo = &(obj.EN_data);		/* means zo_data.objdata_u.en_data */
  eo->en_type = pobj->zo_data.objdata_u.ta_data.ta_type;
  eo->en_cols.en_cols_val = data;
  eo->en_cols.en_cols_len = columns;

  return &obj;
}

static int
add_obj (const nis_object *obj)
{
  int status = 0;
  nis_result *res;
  char sname[NIS_MAXNAMELEN + 1];

  snprintf (sname, NIS_MAXNAMELEN, "%s.%s", obj->zo_name, obj->zo_domain);
  res = nis_add_entry (sname, obj, 0);
  switch (res->status)
    {
    case NIS_TRYAGAIN:
      fputs (_("nisaddent: NIS+ server busy, try again later.\n"), stderr);
      exit (1);
    case NIS_PERMISSION:
      fprintf (stderr,
	       _("nisaddent: insufficent permission to update %s table.\n"),
	       obj->zo_name);
      exit (1);
    case NIS_SUCCESS:
      status = 1;
      break;
    default:
      fprintf (stderr, _("nisaddent: error creating entry, NIS+ error: %s.\n"),
	       nis_sperrno (res->status));
      exit (1);
    }
  nis_freeresult (res);
  return status;
}

static int
modify_obj (const nis_object *obj)
{
  int status = 0;
  char sname[NIS_MAXNAMELEN + 1];
  nis_result *res;

  snprintf (sname, NIS_MAXNAMELEN, "%s.%s", obj->zo_name, obj->zo_domain);
  res = nis_modify_entry (sname, obj, 0);
  switch (res->status)
    {
    case NIS_TRYAGAIN:
      fputs (_("nisaddent: NIS+ server busy, try again later.\n"), stderr);
      exit (1);
    case NIS_PERMISSION:
      fprintf (stderr,
	       _("nisaddent: insufficent permission to update %s entry.\n"),
	       obj->zo_name);
      exit (1);
    case NIS_SUCCESS:
      status = 1;
      break;
    default:
      fprintf (stderr,
	 _("nisaddent: error modifying services entry, NIS+ error: %s.\n"),
	       nis_sperrno (res->status));
      exit (1);
    }
  nis_freeresult (res);
  return status;
}

static int
remove_obj (const nis_object *obj)
{
  char sname[NIS_MAXNAMELEN + 3 + 1];
  nis_result *res;

  snprintf (sname, NIS_MAXNAMELEN, "[],%s.%s", obj->zo_name, obj->zo_domain);
  res = nis_remove_entry (sname, NULL, REM_MULTIPLE);
  if (res == NULL)
    {
      fputs (_("Out of memory!\n"), stderr);
      nis_freeresult (res);
      return 0;
    }
  else if (res->status != NIS_SUCCESS)
    {
      nis_perror (res->status, _("Could not remove entry"));
      nis_freeresult (res);
      return 0;
    }
  nis_freeresult (res);
  return 1;
}

static char *
idxname (table_obj *tbl_obj, entry_col *ent_col)
{
  static char buf[NIS_MAXINDEXLEN];
  char *pbuf;
  int rem = NIS_MAXINDEXLEN, i;

  pbuf = stpcpy (buf, "[");
  for (i = 0; i < tbl_obj->ta_maxcol; i++, ent_col++)
    {
      if (tbl_obj->ta_cols.ta_cols_val[i].tc_flags & TA_SEARCHABLE)
	{
	  rem -= strlen (tbl_obj->ta_cols.ta_cols_val[i].tc_name) 
	    + strlen (ent_col->ec_value.ec_value_val) + 2;
	  if (rem < 3)
	    {
	      fprintf (stderr, _("Too long index name: %s = %s\n"),
		       tbl_obj->ta_cols.ta_cols_val[i].tc_name,
		       ent_col->ec_value.ec_value_val);
	      exit (1);
	    }
	  pbuf = stpcpy (pbuf, tbl_obj->ta_cols.ta_cols_val[i].tc_name);
	  pbuf = stpcpy (pbuf, "=");
	  pbuf = stpcpy (pbuf, ent_col->ec_value.ec_value_val);
	  pbuf = stpcpy (pbuf, ",");
	}
    }
  
  pbuf = stpcpy (--pbuf, "]");
  return buf;
}

static nis_error
entry_exists (addinfo_t *addinfo, entry_obj *eo)
{
  nis_result *res;
  nis_error status;
  char sname[NIS_MAXINDEXLEN + NIS_MAXNAMELEN + 1], *idx;

  idx = idxname (&INFO_TA(addinfo), eo->en_cols.en_cols_val);
  if (idx == NULL)
    exit (1); /* error occured */

  snprintf (sname, NIS_MAXINDEXLEN + NIS_MAXNAMELEN, 
	    "%s,%s.%s", idx, addinfo->obj->zo_name, addinfo->obj->zo_domain);
  if (sname[strlen (sname) - 1] != '.')
    strcat (sname, ".");

  res = nis_list (sname, addinfo->flags, NULL, NULL);
  status = res->status;
  switch (status)
    {
    case NIS_PARTIAL:
    case NIS_NOTFOUND:
    case NIS_SUCCESS:
    case NIS_S_SUCCESS:
      break;
    case NIS_TRYAGAIN:
      fputs (_("nisaddent: NIS+ server busy, try again later.\n"), stderr);
      exit (1);
    case NIS_PERMISSION:
      fputs (_("nisaddent: insufficent permission to look at sevices table\n"),
	     stderr);
      exit (1);
    default:
      fprintf (stderr, _("nisaddent: error looking at sevices table, NIS+ error: %s\n"),
	       nis_sperrno (status));
      exit (1);
    }
  nis_freeresult (res);
  return status;
}

int
regent_generic (nis_object *obj, parsed_t *parsed, struct addinfo_t *addinfo)
{
  int i, status = 0;

  while (parsed && status == 0)
    {
      for (i = 0; i < INFO_MAXCOL(addinfo); i++)
	{
	  if (parsed->pf[i])
	    {
	      ENTRY_VAL (obj, i) = parsed->pf[i];
	      ENTRY_LEN (obj, i) = strlen (parsed->pf[i]) + 1;
	    }
	  else
	    {
	      ENTRY_VAL (obj, i) = "";
	      ENTRY_LEN (obj, i) = 0;
	    }
	}
      
      if (entry_exists (addinfo, &(obj->EN_data)) != NIS_SUCCESS)
	{
	  if (add_obj (obj) != 1)
	    status = 1;
	}
      else
	{
	  for (i = 0; i < INFO_MAXCOL(addinfo); i++)
	    obj->EN_data.en_cols.en_cols_val[i].ec_flags |= EN_MODIFIED;
	  if (modify_obj (obj) != 1)
	    status = 1;
	}
      parsed = parsed->next;
    }
  return status;
}

int
regent_shadow (nis_object *obj, parsed_t *parsed, struct addinfo_t *addinfo)
{
  int status = 0;
  unsigned int i;
  int pos[] = {0, 1, 7};

  for (i = 0; i < sizeof (pos) / sizeof (int); i++)
    {
      if (parsed->pf[i])
	{
	  ENTRY_VAL (obj, pos[i]) = parsed->pf[i];
	  ENTRY_LEN (obj, pos[i]) = strlen (parsed->pf[i]) + 1;
	}
      else
	{
	  ENTRY_VAL (obj, pos[i]) = "";
	  ENTRY_LEN (obj, pos[i]) = 0;
	}
    }

  if (entry_exists (addinfo, &(obj->EN_data)) != NIS_SUCCESS)
    {
      fprintf (stderr,
	       _("No passwd entry for %s, could not add shadow part!\n"),
	       parsed->pf[0]);
      return 0;
    }
  else
    {
      obj->EN_data.en_cols.en_cols_val[1].ec_flags |= EN_MODIFIED;
      obj->EN_data.en_cols.en_cols_val[7].ec_flags |= EN_MODIFIED;
      if (modify_obj (obj) != 1)
	status = 1;
    }
  return status;
} 

int
regent_passwd (nis_object *obj, parsed_t *parsed, struct addinfo_t *addinfo)
{
  int i, status = 0;
  
  while (parsed && status == 0)
    {
      for (i = 0; i < INFO_MAXCOL(addinfo); i++)
	{
	  if (parsed->pf[i])
	    {
	      ENTRY_VAL (obj, i) = parsed->pf[i];
	      ENTRY_LEN (obj, i) = strlen (parsed->pf[i]) + 1;
	    }
	  else
	    {
	      ENTRY_VAL (obj, i) = strdup("");
	      ENTRY_LEN (obj, i) = 0;
	    }
	}
      
      ENTRY_VAL (obj, 1) = strdup ("");
      ENTRY_LEN (obj, 1) = 0;
      
      if (entry_exists (addinfo, &(obj->EN_data)) != NIS_SUCCESS)
	{
	  if (add_obj (obj) != 1)
	    status = 1;
	}
      else
	{
	  for (i = 0; i < INFO_MAXCOL(addinfo); i++)
	    obj->EN_data.en_cols.en_cols_val[i].ec_flags |= EN_MODIFIED;
	  if (modify_obj (obj) != 1)
	    status = 1;
	}
      parsed = parsed->next;
    }
  return status;
}

int 
getline_file (gets_t rfrom, char **line)
{
  int n, len;
  char *cp;

  n = getdelim (line, &len, '\n', rfrom.input);
  if (feof (rfrom.input) != 0)
    return -1;

  if ((*line)[n - 1] == '\n')
    (*line)[n - 1] = '\0';
  cp = *line;
  while (isspace (*cp) && *cp != '\0')
    {
      ++cp;
      --n;
    }
  if (*cp == '\0' || *cp == '#')
    n = 0;
  
  return n;
}

int 
getline_yp (gets_t rfrom, char **line)
{
  static char *outkey = NULL, *oldkey = NULL, *cp;
  static int outkeylen, oldkeylen = 0;
  int n, status;

  if (oldkey == NULL)
    status = yp_first (rfrom.ypinfo.ypdomain, rfrom.ypinfo.ypmap, 
		       &outkey, &outkeylen, line, &n);
  else
    status = yp_next (rfrom.ypinfo.ypdomain, rfrom.ypinfo.ypmap, 
		      oldkey, oldkeylen, &outkey, &outkeylen, line, &n);
  if (status != YPERR_SUCCESS)
    {
      if (status == YPERR_NOMORE)
	return -1;
      else
	{
	  /* XXX Print error message here ! */
	  return -1;
	}
    }
  if (oldkey != NULL)
    free (oldkey);
  
  oldkey = outkey;
  oldkeylen = outkeylen;

  if ((*line)[n - 1] == '\n')
    (*line)[n - 1] = '\0';
  cp = *line;
  while (isspace (*cp) && *cp != '\0')
    {
      ++cp;
      --n;
    }
  if (*cp == '\0' || *cp == '#')
    n = 0;
  
  return n;
}

int
insert (addinfo_t *addinfo)
{
  char *line = NULL, *cp;
  int n, lc = 0, status = 1;
  parsed_t *parsed;
  nis_object *obj;

  if (addinfo->mode & CREATE || addinfo->mode & UPDATE)
    if (remove_obj (addinfo->obj) == 0)
      return 1;

  obj = init_entry (addinfo->obj);
  if (obj == NULL)
    return status;

  while (1)
    {
      n = addinfo->getline (addinfo->aq_from, &line);
      if (n < 0)
	break;
      else if (n == 0)
	continue;

      lc++;
      cp = strdup (line);
      parsed = addinfo->parse (line, addinfo);
      if (parsed == NULL)
	{
	  fprintf (stderr,
		   _("Couldn't parse entry in line %d: %s\n"), lc, cp);
	  free (cp);
	  status = 2;
	  continue;
	}
      free (cp);

      status = addinfo->regent (obj, parsed, addinfo);
      parsed_free (parsed);
    }
  return status;
}
