/*
    YPS-0.2, NIS-Server for Linux
    Copyright (C) 1994  Tobias Reber

    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, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
static char rcsid[]="(#)$Id: ypbind.c,v 2.2 1994/01/07 22:25:43 root Exp $";
/*
 *	$Author: root $
 *	$Log: ypbind.c,v $
 * Revision 2.2  1994/01/07  22:25:43  root
 * Fixed flock problem
 *
 * Revision 2.0  1994/01/06  16:58:08  root
 * Version 2.0
 *
 * Revision 0.22  1994/01/02  21:59:08  root
 * Strict prototypes
 *
 * Revision 0.21  1994/01/02  20:10:08  root
 * Added GPL notice
 *
 * Revision 0.20  1994/01/02  18:00:38  root
 * Different wait times if subprocess is running
 *
 * Revision 0.19  1993/12/29  00:35:54  root
 * Handle SIGCHLD
 *
 * Revision 0.18  1993/12/28  13:08:35  root
 * Serialized child process
 *
 * Revision 0.17  1993/12/27  19:22:42  dok235
 * several fixes
 *
 * Revision 0.16  1993/06/15  22:52:55  root
 * Implemented ypset
 *
 * Revision 0.15  1993/06/15  21:19:09  root
 * Initialize bindings from status directory
 *
 * Revision 0.14  1993/06/15  15:44:15  root
 * Enable yp broadcast
 *
 * Revision 0.13  1993/06/12  10:49:35  root
 * Align with include-4.4
 *
 */

/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/dir.h>
#include <sys/file.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/yp.h>
#include <arpa/inet.h>
 
#define OFFSET(struct, element) (((char *)&((struct)->element)) - \
	((char *)(struct)))

#ifndef YPDIR
#define YPDIR "/var/yp"
#endif

struct bindings {
	int fd;
	bool_t locked;
	struct bindings *next;
	struct sockaddr_in bindingAddress;
	char domainname[YPMAXDOMAIN];
	struct ypbind_binding binding;
	bool_t valid;
};

static
struct bindings *bindingList=NULL;

static struct sockaddr_in resultAddress;

static bool_t
eachBinding(void *out, void *addr)
{
	resultAddress=*(struct sockaddr_in *)addr;
	return TRUE;
}

static void
readBinding(domainname *d, struct bindings *b)
{
#if 0
	char filename[MAXPATHLEN];
	FILE *f;

	sprintf(filename, "%s/binding/%s.%d", YPDIR, *d, YPVERS);
	f=fopen(filename, "rb");
	if (f) fread(&b->bindingAddress,
		sizeof (*b)-OFFSET(b, bindingAddress), 1, f);
	fclose(f);
#else
	if (b->fd<0) return;
	lseek(b->fd, 0, 0);
	read(b->fd, &b->bindingAddress, sizeof(*b)-OFFSET(b, bindingAddress));
	if (b->valid==TRUE) {
		if (b->locked==FALSE) {
			struct flock f;
			f.l_type=F_EXLCK;
			f.l_whence=SEEK_SET;
			f.l_start=f.l_len=0L;
			b->locked=(fcntl(b->fd, F_SETLK, &f)==0)?TRUE:FALSE;
		}
	} else {
		struct flock f;
		f.l_type=F_UNLCK;
		f.l_whence=SEEK_SET;
		f.l_start=f.l_len=0L;
		fcntl(b->fd, F_SETLK, &f);
		b->locked=FALSE;
	}
#endif
}

static void
writeBinding(domainname *d, bool_t valid, struct sockaddr_in *serverAddress)
{
	char filename[MAXPATHLEN];
	FILE *f;
	struct bindings b;

	strncpy(b.domainname, *d, sizeof b.domainname);
	b.next=NULL;
	b.valid=valid;
	b.bindingAddress=*serverAddress;
	memcpy(b.binding.ypbind_binding_addr, &serverAddress->sin_addr,
		sizeof b.binding.ypbind_binding_addr);
	memcpy(b.binding.ypbind_binding_port, &serverAddress->sin_port,
		sizeof b.binding.ypbind_binding_port);

	sprintf(filename, "%s/binding/%s.%d", YPDIR, *d, YPVERS);
	f=fopen(filename, "wb");
	if (f)
		fwrite(&b.bindingAddress,
			sizeof (b)-OFFSET(&b, bindingAddress), 1, f);
	fclose(f);
}

static bool_t
testBinding(domainname *d, long host, struct sockaddr_in *serverAddress)
{
	bool_t res;
	CLIENT *c;
	struct sockaddr_in addr;
	int s;
	struct timeval t1, t2;

	memset(&addr, '\0', sizeof addr);
	addr.sin_family=AF_INET;
	addr.sin_addr.s_addr=host;
	s=RPC_ANYSOCK;

	t1.tv_sec=25; t1.tv_usec=0;
	c=clntudp_create(&addr, YPPROG, YPVERS, t1, &s);
	if (!c) {
		fprintf(stderr, "YPBIND: %s: ", inet_ntoa(addr.sin_addr));
		clnt_pcreateerror("clntudp_create");
		return FALSE;
	}

	if (serverAddress)
		clnt_control(c, CLGET_SERVER_ADDR, serverAddress);

	t2.tv_sec=5; t2.tv_usec=0;
	if (clnt_call(c, YPPROC_DOMAIN, xdr_domainname, d, xdr_bool, (char *)&res, &t2)
		!=RPC_SUCCESS) {
		clnt_perror(c, "YPBIND: clnt_call");
		res=FALSE;
	}
	clnt_destroy(c);

	if (!res) fprintf(stderr, "YPBIND: domain %s not bound. Still trying\n", *d);
	return res;
}

static void
findBinding(domainname *d)
{
	struct sockaddr_in serverAddress;
	if (testBinding(d, htonl(INADDR_LOOPBACK), &serverAddress)) {
		writeBinding(d, TRUE, &serverAddress);
	} else {
		bool_t res=FALSE;
		clnt_broadcast(YPPROG, YPVERS, YPPROC_DOMAIN_NONACK, xdr_domainname,
			(char *)d, xdr_bool, (char *)&res, eachBinding);
		writeBinding(d, res, &resultAddress);
	}
}
 

static inline int
openBinding(domainname *d)
{
	char filename[MAXPATHLEN];
	sprintf(filename, "%s/binding/%s.%d", YPDIR, *d, YPVERS);
	return open(filename, O_RDWR|O_CREAT, 0755);
}

static struct bindings *
addBinding(domainname *d)
{
	struct bindings *n;

	for (n=bindingList; n; n=n->next)
		if (!strncmp(n->domainname, *d, sizeof n->domainname))
			return n;
	n=(struct bindings *)malloc(sizeof (*n));
	if (!n) return n;
	strncpy(n->domainname, *d, sizeof n->domainname);
	n->next=bindingList;
	n->valid=FALSE;
	n->fd=openBinding(d);
	n->locked=FALSE;
	bindingList=n;
	return n;
}

static void
doRebind(void)
{
	struct bindings *b;
	for (b=bindingList; b; b=b->next) {
		domainname d=(b->domainname);
		struct sockaddr_in addr;
		if (b->valid && testBinding(&d,
			*(long *)(b->binding.ypbind_binding_addr), &addr)) {
			writeBinding(&d, b->valid, &addr);
			continue;
		}
		findBinding(&d);
	}
}

static void
doBinding(void) {
	struct bindings *b;
	for (b=bindingList; b; b=b->next) {
		domainname d=(b->domainname);
		struct bindings *n=b->next;
		readBinding(&d, b);
		b->next=n;
	}
}

static void
initBinding(void)
{
	DIR *DomainDir;
	struct dirent *dp;
	char dirName[MAXNAMLEN+1];

	strcpy(dirName, YPDIR);
	strcat(dirName, "/");
	strcat(dirName, "binding");

	if ((DomainDir=opendir(dirName))==NULL) {
		return;
	}

	for (dp=readdir(DomainDir); dp!=NULL; dp=readdir(DomainDir)) {
		if ((dp->d_namlen>2)
			&& (strcmp(".2", dp->d_name+(dp->d_namlen-2))==0)) {
			struct bindings *b;
			domainname d;

			b=(struct bindings *)malloc(sizeof *b);
			d=(domainname)malloc(dp->d_namlen-1);
			strncpy(d, dp->d_name, dp->d_namlen-2);
			d[dp->d_namlen-2]='\0';
			b->fd=openBinding(&d);
			b->locked=FALSE;
			readBinding(&d, b);
			b->bindingAddress.sin_port=0;
			writeBinding(&d, b->valid, &b->bindingAddress);
			b->next=bindingList;
			bindingList=b;
		}
	}
	closedir(DomainDir);
}

static pid_t ypRebindPid=0;

static void
childHandler( int i) {
	union wait s;
	struct rusage r;
	int w=wait4(ypRebindPid, &s, WNOHANG, &r);
	if (w<0) perror("YPBIND: wait4");
	if (w<=0) return;
	ypRebindPid=0;
}

static bool_t
yp_rebind(void) {
	if (ypRebindPid>0) return FALSE;
	switch(ypRebindPid=fork()) {
	case (-1):
		perror("fork");
		exit(1);
	case 0:
		doRebind();
		exit(0);
	default:
		doBinding();
		return TRUE;
	}
}

void * 
ypbindproc_null_2(void *argp, struct svc_req *rqstp)
{

	static char* result;
	return((void*) &result);
}

ypbind_resp * 
ypbindproc_domain_2(domainname *argp, struct svc_req *rqstp)
{

	static ypbind_resp  result;
	struct bindings *b;

	for (b=bindingList; b; b=b->next) {
		if ((!strncmp(*argp, b->domainname, sizeof b->domainname)) &&
			b->valid==TRUE) {
			result.ypbind_status=YPBIND_SUCC_VAL;
			result.ypbind_resp_u.ypbind_bindinfo=b->binding;
			return(&result);
		}
	}

	(void)addBinding(argp);
	(void)yp_rebind();

	result.ypbind_status=YPBIND_FAIL_VAL;
	result.ypbind_resp_u.ypbind_error=YPBIND_ERR_NOSERV;

	return(&result);
}

void * 
ypbindproc_setdom_2(ypbind_setdom *argp, struct svc_req *rqstp)
{

	static char* result;
	struct bindings *n;
	struct sockaddr_in serverAddress;

	n=addBinding(&argp->ypsetdom_domain);
	if (!n) {
		n->binding=argp->ypsetdom_binding;
		n->valid=TRUE;
	}

	memset(&serverAddress, '\0', sizeof serverAddress);
	serverAddress.sin_addr.s_addr=
		*((long *)(argp->ypsetdom_binding.ypbind_binding_addr));
	writeBinding(&(argp->ypsetdom_domain), TRUE, &serverAddress);
	(void)yp_rebind();

	return((void*) &result);
}

/*
 * This is the rpc server side idle loop
 * Wait for input, call server program.
 */

void
svc_run(void)
{
#ifdef FD_SETSIZE
	fd_set readfds;
#else
        int readfds;
#endif /* def FD_SETSIZE */
	extern int errno;
	struct timeval t;
	struct sigaction a;

	switch(fork()) {
	case 0:         /* child */
        	{
                	int fd=open("/dev/tty", O_RDWR);
                	ioctl(fd, TIOCNOTTY, NULL);
                	close(fd);
        	}
                break;
	case (-1):
        	perror("fork");
        	exit(1);
	default:
        	exit(0);
	}

	/* signal(SIGCLD, SIG_IGN); */
	
	a.sa_handler=childHandler;
	a.sa_mask=0;
	a.sa_flags=SA_NOMASK;
	a.sa_restorer=NULL;
	sigaction(SIGCHLD, &a, NULL);
	initBinding();
	(void)yp_rebind();

	t.tv_sec=120; t.tv_usec=0;

	for (;;) {
#ifdef FD_SETSIZE
		readfds = svc_fdset;
#else
		readfds = svc_fds;
#endif /* def FD_SETSIZE */
		switch (select(_rpc_dtablesize(), &readfds, (void *)0,
			(void *)0, &t)) {
		case -1:
			if (errno == EINTR) {
				continue;
			}
			perror("svc_run: - select failed");
			return;
		case 0:
			t.tv_sec=120; t.tv_usec=0;
			if (yp_rebind()!=TRUE)
				t.tv_sec=5;
		default:
			svc_getreqset(&readfds);
		}
	}
}
