/*
    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: ypxfr.c,v 2.0 1994/01/06 16:58:08 root Exp $";

/*
 *	$Author: root $
 *	$Log: ypxfr.c,v $
 * Revision 2.0  1994/01/06  16:58:08  root
 * Version 2.0
 *
 * Revision 0.20  1994/01/02  21:59:08  root
 * Strict prototypes
 *
 * Revision 0.19  1994/01/02  20:10:08  root
 * Added GPL notice
 *
 * Revision 0.18  1994/01/02  18:00:38  root
 * New arguments for -C flag.
 *
 * Revision 0.17  1993/12/30  22:21:49  root
 * Switch to GDBM
 *
 * Revision 0.16  1993/12/27  23:43:26  root
 * Use dbm directly instead of makedbm
 *
 * Revision 0.15  1993/12/27  21:21:00  root
 * This host should be the default master
 *
 * Revision 0.14  1993/12/19  12:41:55  root
 * *** empty log message ***
 *
 * Revision 0.13  1993/06/12  10:49:35  root
 * Align with include-4.4
 *
 */

#include <rpc/rpc.h>
#ifdef GDBM
#include "dbmcompat.h"
#else
#include <dbm.h>
#endif

#include <rpcsvc/yp.h>
#if 0
/*
 * ypclnt.h does not have a definition for struct dom_binding,
 * although it is used there. It is defined in yp_prot.h, but
 * we cannot use it here.
 */
struct dom_binding {
	void * m;
};
#endif
#define DATUM /* Otherwise ypclnt.h redefines datum */
#include <rpcsvc/ypclnt.h>
/*
 * These are hooks to the ypclnt library in ../lib
 */
extern int _yp_bind(struct sockaddr_in *, char *);
extern int _yp_clear( char *);
#include <netdb.h>
#include <fcntl.h>
#include <ctype.h>
#include <arpa/inet.h>

#ifdef DEBUG
#define PRINTF(x) printf x
#define FWRITE(x) fwrite x
#define PRLINENO fprintf(stderr, "%s(%d): ", __FILE__, __LINE__)
#else
#define PRINTF(x)
#define FWRITE(x)
#define PRLINENO
#endif

#ifndef YPDIR
#define YPDIR "/var/yp"
#endif
#ifndef YPETCDIR
#define YPETCDIR "/usr/local/sbin"
#endif

extern int optind;
extern char *optarg;

static char *SourceHost=NULL, *TargetDomain=NULL, *SourceDomain=NULL;
static struct in_addr IpAddress;
static int Force=0, NoClear=0, TaskId=0, ProgramNumber=0,
	PortNumber=0, Secure=0;

static inline char *
ypxfr_err_string(enum ypxfrstat y) {
	switch(y) {
	case YPXFR_SUCC:	return "Success";
	case YPXFR_AGE:		return "Master's version not newer";
	case YPXFR_NOMAP:	return "Can't find server for map";
	case YPXFR_NODOM:	return "Domain not supported";
	case YPXFR_RSRC:	return "Local resource alloc failure";
	case YPXFR_RPC:		return "RPC failure talking to server";
	case YPXFR_MADDR:	return "Can't get master address";
	case YPXFR_YPERR:	return "YP server/map db error";
	case YPXFR_BADARGS:	return "Request arguments bad";
	case YPXFR_DBM:		return "Local dbm operation failed";
	case YPXFR_FILE:	return "Local file I/O operation failed";
	case YPXFR_SKEW:	return "Map version skew during transfer";
	case YPXFR_CLEAR:	return "Can't send \"Clear\" req to local ypserv";
	case YPXFR_FORCE:	return "No local order number in map  use -f flag.";
	case YPXFR_XFRERR:	return "ypxfr error";
	case YPXFR_REFUSED:	return "Transfer request refused by ypserv";
	}
}

static int
ypxfr_foreach(int status, char *key, int keylen, char *val, int vallen,
	int *data)
{
	datum outKey, outData;

	PRLINENO; PRINTF(("\n"));
	if (status==YP_NOMORE)
		return 0;
	if (status!=YP_TRUE) {
		int s=ypprot_err(status);
		fprintf(stderr, "%s\n", yperr_string(s));
		return 1;
	}
	
	outKey.dptr=keylen?key:""; outKey.dsize=keylen;
	outData.dptr=vallen?val:""; outData.dsize=vallen;
	PRLINENO;
	FWRITE((outKey.dptr, outKey.dsize, 1, stderr));
	PRINTF((" %d\n", outKey.dsize)); PRLINENO;
	PRLINENO;
	FWRITE((outData.dptr, outData.dsize, 1, stderr));
	PRINTF((" %d\n", outData.dsize));
	store(outKey, outData);

	return 0;
}

static enum ypxfrstat
ypxfr(char *mapName) {
#if 0
	CLIENT *masterClient;
	int masterSock;
#endif
	int localOrderNum=0;
	int masterOrderNum=0;
	char *masterName;
	struct sockaddr_in localHost;
	struct sockaddr_in masterHost;
	struct ypall_callback callback;
	char dbName[1024];
	char dbName2[1024];
	int y;

	memset(&localHost, '\0', sizeof localHost);
	localHost.sin_family=AF_INET;
	localHost.sin_addr.s_addr=htonl(INADDR_LOOPBACK);


	if (!SourceHost) {
		if ((y=yp_master(SourceDomain, mapName, &masterName)))
			return YPXFR_MADDR;
		SourceHost=masterName;
	}

	memset(&masterHost, '\0', sizeof masterHost);
	masterHost.sin_family=AF_INET;
	{
		struct hostent *h=gethostbyname(SourceHost);
		if (!h) {
			return YPXFR_MADDR;
		}
		memcpy(&masterHost.sin_addr, h->h_addr,
			sizeof masterHost.sin_addr);
	}

	PRLINENO;
	PRINTF(("%s; %s\n", inet_ntoa(masterHost.sin_addr), SourceDomain));
#if 1
	if ((y=_yp_bind(&masterHost, SourceDomain))) return YPXFR_RPC;
	if ((y=yp_order(SourceDomain, mapName, &masterOrderNum))) return YPXFR_SKEW;
	if ((y=yp_master(SourceDomain, mapName, &masterName))) return YPXFR_MADDR;
#else
	masterSock=RPC_ANYSOCK;
	masterClient=clnttcp_create(&masterHost, YPPROG, YPVERS, &masterSock, 0, 0);
	if (masterClient==NULL) {
		clnt_pcreateerror("");
		return YPXFR_RPC;
	}
	{
		static struct timeval tv = { 25, 0 };
		struct ypreq_nokey req;
		struct ypresp_order resp;
		int y;

		req.domain=SourceDomain;
		req.map=mapName;
		y=clnt_call(masterClient, YPPROC_ORDER, xdr_ypreq_nokey,
			&req, xdr_ypresp_order, &resp, tv);
		if (y!=RPC_SUCCESS || resp.status!=YP_TRUE) {
			clnt_perror(masterClient, "masterOrderNum");
			masterOrderNum=0x7fffffff;
		} else {
			masterOrderNum=resp.ordernum;
		}
		xdr_free(xdr_ypresp_order, (char *)&resp);
	}
#endif

	PRLINENO; PRINTF(("\n"));
	if (!Force) {
		datum inKey, inVal;
		sprintf(dbName, "%s/%s/%s", YPDIR, TargetDomain, mapName);
		if (dbminit(dbName)) {
			fprintf(stderr, "%s: cannot open - ignored.\n", dbName);
			localOrderNum=0;
		} else {
			inKey.dptr="YP_LAST_MODIFIED"; inKey.dsize=strlen(inKey.dptr);
			inVal=fetch(inKey);
			if (inVal.dptr) {
				int i;
				char *d=inVal.dptr;
				for (i=0; i<inVal.dsize; i++, d++) {
					if (!isdigit(*d)) {
						dbmclose();
						return YPXFR_SKEW;
					}
				}
				localOrderNum=atoi(inVal.dptr);
			}
			dbmclose();
		}
		PRLINENO; PRINTF (("%d %d\n", localOrderNum, masterOrderNum));
		if (localOrderNum>=masterOrderNum) return YPXFR_AGE;
	}

	PRLINENO;
	PRINTF(("%s; %s\n", inet_ntoa(masterHost.sin_addr), SourceDomain));
#ifndef GDBM
	sprintf(dbName, "%s/%s/%s~.dir", YPDIR, TargetDomain, mapName);
	{
		int fd=open(dbName, O_WRONLY|O_TRUNC|O_CREAT, 0755);
		if (fd<0) return YPXFR_DBM;
		close(fd);
	}
	sprintf(dbName, "%s/%s/%s~.pag", YPDIR, TargetDomain, mapName);
	{
		int fd=open(dbName, O_WRONLY|O_TRUNC|O_CREAT, 0755);
		if (fd<0) return YPXFR_DBM;
		close(fd);
	}
#endif
	sprintf(dbName, "%s/%s/%s~", YPDIR, TargetDomain, mapName);
	if (dbminit(dbName)) {
		fprintf(stderr, "%s: Cannot open\n", dbName);
		return YPXFR_DBM;
	}

	{
		datum outKey, outData;
		char orderNum[12];
		outKey.dptr="YP_MASTER_NAME"; outKey.dsize=strlen(outKey.dptr);
		outData.dptr=masterName; outData.dsize=strlen(outData.dptr);
		store(outKey, outData);
		sprintf(orderNum, "%d", masterOrderNum);
		outKey.dptr="YP_LAST_MODIFIED"; outKey.dsize=strlen(outKey.dptr);
		outData.dptr=orderNum; outData.dsize=strlen(outData.dptr);
		store(outKey, outData);
	}

	callback.foreach=ypxfr_foreach;
	y=yp_all(SourceDomain, mapName, &callback);

	sprintf(dbName, "%s/%s/%s~.pag", YPDIR, TargetDomain, mapName);
	sprintf(dbName2, "%s/%s/%s.pag", YPDIR, TargetDomain, mapName);
	unlink(dbName2);
	rename(dbName, dbName2);
	sprintf(dbName, "%s/%s/%s~.dir", YPDIR, TargetDomain, mapName);
	sprintf(dbName2, "%s/%s/%s.dir", YPDIR, TargetDomain, mapName);
	unlink(dbName2);
	rename(dbName, dbName2);

	if (!NoClear) {
		memset(&localHost, '\0', sizeof localHost);
		localHost.sin_family=AF_INET;
		localHost.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
		if (_yp_bind(&localHost, TargetDomain) ||
			_yp_clear(TargetDomain)) return YPXFR_CLEAR;
	}
	return y==0?YPXFR_SUCC:YPXFR_YPERR;
}

void
main (int argc, char **argv)
{
	while(1) {
		int c=getopt(argc, argv, "fcd:h:s:C:S");
		if (c==EOF) break;
		switch (c) {
		case 'f':
			Force++;
			break;
		case 'c':
			NoClear++;
			break;
		case 'd':
			TargetDomain=optarg;
			break;
		case 'h':
			SourceHost=optarg;
			break;
		case 's':
			SourceDomain=optarg;
			break;
		case 'C':
#if 0
			TaskId=*((long *)optarg);
			ProgramNumber=*((long *)argv[optind++]);
			IpAddress=*((struct in_addr *)argv[optind++]);
			PortNumber=*((long *)argv[optind++]);
#else
			TaskId=atoi(optarg);
			ProgramNumber=atoi(argv[optind++]);
			IpAddress.s_addr=inet_addr(argv[optind++]);
			PortNumber=atoi(argv[optind++]);
			PRLINENO; PRINTF((" %d\n", TaskId));
			PRLINENO; PRINTF((" %d\n", ProgramNumber));
			PRLINENO; PRINTF((" %s\n", inet_ntoa(IpAddress)));
			PRLINENO; PRINTF((" %d\n", PortNumber));
#endif
			break;
		case 'S':
			Secure++;
			break;
		}
	}
	argc-=optind;
	argv+=optind;

	if (!TargetDomain) {
		yp_get_default_domain(&TargetDomain);
	}
	if (!SourceDomain) {
		SourceDomain=TargetDomain;
	}

	for (; *argv; argv++) {
		enum ypxfrstat y;
		if ((y=ypxfr(*argv))!=YPXFR_SUCC) {
			fprintf(stderr, "%s\n", ypxfr_err_string(y));
		}
		if (TaskId) {
			struct sockaddr_in addr;
			struct timeval wait;
			CLIENT *clnt;
			int s;
			ypresp_xfr resp;
			static struct timeval tv={0,0};

			memset(&addr, '\0', sizeof addr);
			addr.sin_addr=IpAddress;
			addr.sin_port=htons(PortNumber);
			wait.tv_sec=25; wait.tv_usec=0;
			s=RPC_ANYSOCK;

			clnt=clntudp_create(&addr, ProgramNumber, 1, wait, &s);
			if (!clnt) {
				clnt_pcreateerror("ypxfr_callback");
				continue;
			}

			PRLINENO; PRINTF(("%d %d\n", TaskId, y));
			resp.transid=TaskId;
			resp.xfrstat=y;
			switch (clnt_call(clnt, 1, xdr_ypresp_xfr, &resp,
				xdr_void, NULL, tv)) {
			case RPC_SUCCESS:
			case RPC_TIMEDOUT:
				break;
			default:
				clnt_perror(clnt, "ypxfr_callback");
			}

			clnt_destroy(clnt);
		}
	}

}
