/*
** yp_if.c                Cover routines for YP (NIS)
**
** Copyright (c) 1993 Signum Support AB, Sweden
**
** This file is part of the NYS Library.
**
** The NYS Library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Library General Public License as
** published by the Free Software Foundation; either version 2 of the
** License, or (at your option) any later version.
**
** The NYS Library 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
** Library General Public License for more details.
** 
** You should have received a copy of the GNU Library General Public
** License along with the NYS Library; see the file COPYING.LIB.  If
** not, write to the Free Software Foundation, Inc., 675 Mass Ave,
** Cambridge, MA 02139, USA.
**
** Author: Peter Eriksson <pen@signum.se>
*/

#include "config.h"

#ifdef ENABLE_YPEMU


#include <stdio.h>
#include <errno.h>
#include <rpc/rpc.h>
#include "yp.h"
#include "yp_conf.h"
#include "rpcsvc/ypclnt.h"

extern char *strdup();

static int ypsetup_flag = 0;


static struct
{
    char *nickname;
    char *realname;
} transtab[] = {
    { "passwd",		"passwd.byname" },
    { "group",		"group.byname" },
    { "networks",	"networks.byaddr" },
    { "hosts",		"hosts.byname" },
    { "protocols",	"protocols.bynumber" },
    { "services",	"services.byname" },
    { "aliases",	"mail.aliases" },
    { "ethers",		"ethers.byname" },
	  
    { "rpc",		"rpc.bynumber" },
    { "netmasks",	"netmasks.byaddr" },
    { "publickey",	"publickey.byname" },
    { "netid",		"netid.byname" },
    { "passwd.adjunct", "passwd.adjunct.byname" },
    { "group.adjunct",	"group.adjunct.byname" },
    { "timezone", 	"timezone.byname" },
    
    { NULL, NULL }
};



static char *xstrdup(char *str, int len)
{
    char *cp;


    cp = malloc(len+2);
    memcpy(cp, str, len);
    cp[len++] = '\n';
    cp[len] = '\0';

    return cp;
}


int ypprot_err(int code)
{
    switch (code)
    {
      case YP_TRUE:
	return 0;

      case YP_NOMORE:
	return YPERR_NOMORE;
	
      case YP_FALSE:
	return YPERR_YPERR;

      case YP_NOMAP:
	return YPERR_MAP;

      case YP_NODOM:
	return YPERR_DOMAIN;

      case YP_NOKEY:
	return YPERR_KEY;

      case YP_BADOP:
	return YPERR_YPERR;

      case YP_BADDB:
	return YPERR_BADDB;

      case YP_YPERR:
	return YPERR_YPERR;

      case YP_BADARGS:
	return YPERR_BADARGS;

      case YP_VERS:
	return YPERR_VERS;
    }

    return -1;
}


char *yperr_string(int code)
{
    switch (code)
    {
      case 0:
	return "NIS operation succeeded";
	
      case YPERR_BADARGS:
	return "args to NIS function are bad";

      case YPERR_RPC:
	return "RPC failure on NIS operation";

      case YPERR_DOMAIN:
	return "can't bind to a server which serves domain";

      case YPERR_MAP:
	return "no such map in server's domain";

      case YPERR_KEY:
	return "no such key in map";

      case YPERR_YPERR:
	return "internal NIS server or client error";

      case YPERR_RESRC:
	return "local resource allocation failure";

      case YPERR_NOMORE:
	return "no more records in map database";

      case YPERR_PMAP:
	return "can't communicate with portmapper";

      case YPERR_YPBIND:
	return "can't communicate with ypbind";

      case YPERR_YPSERV:
	return "can't communicate with ypserv";

      case YPERR_NODOM:
	return "local domain name not set";

      case YPERR_BADDB:
	return "NIS map database is bad";

      case YPERR_VERS:
	return "NIS client/server version mismatch";

      case YPERR_ACCESS:
	return "permission denied";

      case YPERR_BUSY:
	return "database busy";

      default:
	return "unknown NIS client error code";
    }
}


static int yp_setup(void)
{
    if (ypsetup_flag)
	return 0;
    
    if (ypconf_set())
	return YPERR_NODOM;

    ypsetup_flag = 1;
    
    return 0;
}


static void *do_ypcall(const char *domain,
		       void * (*cfh)(void *req, CLIENT *clh),
		       void *req,
		       int *ecode)
{
    void *res;
    CLIENT *clh;
    char *address;

    if ((*ecode = yp_setup()) != 0)
	return NULL;

    clh = ypconf_domain2client(domain, NULL, &address);
    if (!clh)
    {
	*ecode = YPERR_DOMAIN;
	return NULL;
    }

    if ((res = (*cfh)(req, clh)) == NULL)
    {
	*ecode = YPERR_RPC;
	return NULL;
    }

    return res;
}


int yp_get_default_domain(char **outdomain)
{
    int code;
    char buf[1025];
    char *cp;
    
    
    if ((code = yp_setup()) != 0)
	return code;

    if (cp = getenv("YPDOMAIN"))
    {
	*outdomain = strdup(cp);
	return 0;
    }
    
    if (_yp_config->domainname)
    {
	*outdomain = strdup(_yp_config->domainname);
	return 0;
    }

    if (getdomainname(buf, sizeof(buf)) == 0)
    {
	*outdomain = strdup(buf);
	return 0;
    }

    return -1;
}


void yp_prnick2real(FILE *fp)
{
    int i;

    
    fprintf(fp, "NIS map nickname translation table:\n");
    for (i = 0; transtab[i].nickname; i++)
	fprintf(fp, "\t\"%s\" -> \"%s\"\n",
		transtab[i].nickname,
		transtab[i].realname);
}


char *yp_nick2real(char *mname)
{
    int i;


    for (i = 0; transtab[i].nickname; i++)
	if (strcmp(transtab[i].nickname, mname) == 0)
	    return transtab[i].realname;

    return mname;
}


int yp_match(const char *indomain,
	     const char *inmap,
	     const char *inkey,
	     int inkeylen,
	     char **outval,
	     int *outvallen)
{
    ypreq_key ypreq;
    ypresp_val *ypres;
    int code;

    ypreq.domain = (char *) indomain;
    ypreq.map = (char *) inmap;
    ypreq.key.keydat_val = (char *) inkey;
    ypreq.key.keydat_len = inkeylen;

    ypres = (ypresp_val *) do_ypcall(indomain,
				     (void * (*)(void *req, CLIENT *clh))
				     &ypproc_match_2, &ypreq,
				     &code);

    if (ypres == NULL)
	return code;
    
    if (ypres->stat != YP_TRUE)
	return ypprot_err(ypres->stat);

    *outval = xstrdup(ypres->val.valdat_val, ypres->val.valdat_len);
    *outvallen = ypres->val.valdat_len;

    xdr_free(xdr_ypresp_val, (char *) ypres);
    
    return 0;
}



int yp_first(const char *indomain,
	     const char *inmap,
	     char **outkey,
	     int *outkeylen,
	     char **outval,
	     int *outvallen)
{
    ypreq_key ypreq;
    ypresp_key_val *ypres;
    int code;
    

    ypreq.domain = (char *) indomain;
    ypreq.map = (char *) inmap;
    ypreq.key.keydat_val = "";
    ypreq.key.keydat_len = 0;

    ypres = (ypresp_key_val *) do_ypcall(indomain,
					 (void * (*)(void *req, CLIENT *clh))
					 &ypproc_first_2, &ypreq,
					 &code);
    if (ypres == NULL)
	return code;

    if (ypres->stat != YP_TRUE)
	return ypprot_err(ypres->stat);

/* XXX What's going on here??? outval and outkey arrive in the wrong order! */
#ifdef HAVE_BUGGY_YP_X
    *outval = xstrdup(ypres->key.keydat_val, ypres->key.keydat_len);
    *outvallen = ypres->key.keydat_len;
    
    *outkey = xstrdup(ypres->val.valdat_val, ypres->val.valdat_len);
    *outkeylen = ypres->val.valdat_len;
#else
    *outkey = xstrdup(ypres->key.keydat_val, ypres->key.keydat_len);
    *outkeylen = ypres->key.keydat_len;
    
    *outval = xstrdup(ypres->val.valdat_val, ypres->val.valdat_len);
    *outvallen = ypres->val.valdat_len;
#endif
    
    xdr_free(xdr_ypresp_key_val, (char *) ypres);

    return 0;
}


int yp_next(const char *indomain,
	    const char *inmap,
	    const char *inkey,
	    int inkeylen,
	    char **outkey,
	    int *outkeylen,
	    char **outval,
	    int *outvallen)
{
    ypreq_key ypreq;
    ypresp_key_val *ypres;
    int code;
    

    ypreq.domain = (char *) indomain;
    ypreq.map = (char *) inmap;
    ypreq.key.keydat_val = (char *) inkey;
    ypreq.key.keydat_len = inkeylen;

    ypres = (ypresp_key_val *) do_ypcall(indomain,
					 (void * (*)(void *req, CLIENT *clh))
					 &ypproc_next_2, &ypreq,
					 &code);
    if (ypres == NULL)
	return code;
    
    if (ypres->stat != YP_TRUE)
	return ypprot_err(ypres->stat);

/* What's going on here??? outval and outkey arrive in the wrong order! */
#ifdef HAVE_BUGGY_YP_X
    *outval = xstrdup(ypres->key.keydat_val, ypres->key.keydat_len);
    *outvallen = ypres->key.keydat_len;
    
    *outkey = xstrdup(ypres->val.valdat_val, ypres->val.valdat_len);
    *outkeylen = ypres->val.valdat_len;
#else
    *outkey = xstrdup(ypres->key.keydat_val, ypres->key.keydat_len);
    *outkeylen = ypres->key.keydat_len;
    
    *outval = xstrdup(ypres->val.valdat_val, ypres->val.valdat_len);
    *outvallen = ypres->val.valdat_len;
#endif
    
    xdr_free(xdr_ypresp_key_val, (char *) ypres);

    return 0;
}



int yp_bind(char *indomain)
{
    CLIENT *clh;
    char *address;
    int ecode;

    if ((ecode = yp_setup()) != 0)
	return ecode;

    clh = ypconf_domain2client(indomain, NULL, &address);
    if (clh)
	return 0;
    else
	return YPERR_DOMAIN;
}

void yp_unbind(char *indomain)
{
    if (yp_setup())
	return;

    if (ypconf_unbinddomain(indomain) < 0)
	return;
}


/*
** XXXX FIXME! Should be changed to use a TCP link to transfer
** the whole map and not this kludgy emulation...
*/
int yp_all(char *indomain,
	   char *inmap,
	   struct ypall_callback *incbp)
{
    int code;
    char *key;
    int keylen;
    char *val;
    int vallen;


    key = NULL;
    val = NULL;

    code = yp_first(indomain, inmap,
		    &key, &keylen,
		    &val, &vallen);

    while (code == 0)
    {
	char *savekey = key;
	
	if ((*incbp->foreach)(code,
			      key, keylen,
			      val, vallen,
			      incbp->data) != 0)
	{
	    free(key);
	    free(val);

	    return 0;
	}
	

	free(val);
	
	key = NULL;
	val = NULL;
	
	code = yp_next(indomain, inmap,
		       savekey, keylen,
		       &key, &keylen,
		       &val, &vallen);

	free(savekey);
    }

    if (key)
	free(key);
    if (val)
	free(val);

    return (code == YPERR_NOMORE ? 0 : code);
}

int yp_order(char *indomain,
	     char *inmap,
	     int *outorder)
{
    ypreq_nokey ypreq;
    ypresp_order *ypres;
    int code;
    

    ypreq.domain = (char *) indomain;
    ypreq.map = (char *) inmap;

    ypres = (ypresp_order *) do_ypcall(indomain,
				       (void * (*)(void *req, CLIENT *clh))
				       &ypproc_order_2, &ypreq,
				       &code);
    if (ypres == NULL)
	return code;
    
    if (ypres->stat != YP_TRUE)
	return ypprot_err(ypres->stat);
    
    *outorder = ypres->ordernum;
    
    xdr_free(xdr_ypresp_order, (char *) ypres);
    
    return 0;
}

int yp_master(char *indomain,
	      char *inmap,
	      char **outname)
{
    ypreq_nokey ypreq;
    ypresp_master *ypres;
    int code;
    

    ypreq.domain = (char *) indomain;
    ypreq.map = (char *) inmap;

    ypres = (ypresp_master *) do_ypcall(indomain,
					(void * (*)(void *req, CLIENT *clh))
					&ypproc_master_2, &ypreq,
					&code);
    if (ypres == NULL)
	return code;
    
    if (ypres->stat != YP_TRUE)
	return ypprot_err(ypres->stat);
    
    *outname = strdup(ypres->peer);

    xdr_free(xdr_ypresp_master, (char *) ypres);

    return 0;
}


#endif /* ENABLE_YPEMU */
