Home | History | Annotate | Line # | Download | only in yppush
yppush.c revision 1.16
      1 /*	$NetBSD: yppush.c,v 1.16 2001/01/11 01:54:08 lukem Exp $	*/
      2 
      3 /*
      4  *
      5  * Copyright (c) 1997 Charles D. Cranor
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  * 1. Redistributions of source code must retain the above copyright
     12  *    notice, this list of conditions and the following disclaimer.
     13  * 2. Redistributions in binary form must reproduce the above copyright
     14  *    notice, this list of conditions and the following disclaimer in the
     15  *    documentation and/or other materials provided with the distribution.
     16  * 3. The name of the author may not be used to endorse or promote products
     17  *    derived from this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /*
     32  * yppush
     33  * author: Chuck Cranor <chuck (at) ccrc.wustl.edu>
     34  * date: 05-Nov-97
     35  *
     36  * notes: this is a full rewrite of Mats O Jansson <moj (at) stacken.kth.se>'s
     37  * yppush.c.   i have restructured and cleaned up the entire file.
     38  */
     39 #include <sys/types.h>
     40 #include <sys/param.h>
     41 #include <sys/stat.h>
     42 #include <sys/time.h>
     43 #include <sys/wait.h>
     44 
     45 #include <ctype.h>
     46 #include <err.h>
     47 #include <errno.h>
     48 #include <fcntl.h>
     49 #include <signal.h>
     50 #include <stdio.h>
     51 #include <stdlib.h>
     52 #include <string.h>
     53 #include <syslog.h>
     54 #include <unistd.h>
     55 
     56 #include <rpc/rpc.h>
     57 #include <rpcsvc/yp_prot.h>
     58 #include <rpcsvc/ypclnt.h>
     59 
     60 #include "ypdb.h"
     61 #include "ypdef.h"
     62 #include "yplib_host.h"
     63 #include "yppush.h"
     64 
     65 /*
     66  * yppush: push a new YP map out YP servers
     67  *
     68  * usage:
     69  *   yppush [-d domain] [-h host] [-v] mapname
     70  *
     71  *   -d: the domainname the map lives in [if different from default]
     72  *   -h: push only to this host [otherwise, push to all hosts]
     73  *   -v: verbose
     74  */
     75 
     76 /*
     77  * structures
     78  */
     79 
     80 struct yppush_info {
     81 	char   *ourdomain;	/* domain of interest */
     82 	char   *map;		/* map we are pushing */
     83 	char   *owner;		/* owner of map */
     84 	int     order;		/* order number of map (version) */
     85 };
     86 /*
     87  * global vars
     88  */
     89 
     90 extern char *__progname;	/* from crt0.o */
     91 int     verbo = 0;		/* verbose */
     92 
     93 /*
     94  * prototypes
     95  */
     96 
     97 int	main __P((int, char *[]));
     98 int	pushit __P((int, char *, int, char *, int, char *));
     99 void	push __P((char *, int, struct yppush_info *));
    100 void	_svc_run __P((void));
    101 void	usage __P((void));
    102 
    103 
    104 /*
    105  * main
    106  */
    107 
    108 int
    109 main(argc, argv)
    110 	int     argc;
    111 	char   *argv[];
    112 
    113 {
    114 	char   *targhost = NULL;
    115 	struct yppush_info ypi = {NULL, NULL, NULL, 0};
    116 	int     c, rv;
    117 	const char *cp;
    118 	char   *master;
    119 	DBM    *ypdb;
    120 	datum   datum;
    121 	CLIENT *ypserv;
    122 	struct timeval tv;
    123 	enum clnt_stat retval;
    124 	struct ypall_callback ypallcb;
    125 
    126 	/*
    127          * parse command line
    128          */
    129 	while ((c = getopt(argc, argv, "d:h:v")) != -1) {
    130 		switch (c) {
    131 		case 'd':
    132 			ypi.ourdomain = optarg;
    133 			break;
    134 		case 'h':
    135 			targhost = optarg;
    136 			break;
    137 		case 'v':
    138 			verbo = 1;
    139 			break;
    140 		default:
    141 			usage();
    142 			/* NOTREACHED */
    143 		}
    144 	}
    145 	argc -= optind;
    146 	argv += optind;
    147 	if (argc != 1)
    148 		usage();
    149 	openlog("yppush", LOG_PID, LOG_DAEMON);
    150 	ypi.map = argv[0];
    151 	if (strlen(ypi.map) > YPMAXMAP)
    152 		errx(1, "%s: map name too long (limit %d)", ypi.map, YPMAXMAP);
    153 
    154 	/*
    155          * ensure we have a domain
    156          */
    157 	if (ypi.ourdomain == NULL) {
    158 		c = yp_get_default_domain(&ypi.ourdomain);
    159 		if (ypi.ourdomain == NULL)
    160 			errx(1, "unable to get default domain: %s",
    161 			    yperr_string(c));
    162 	}
    163 	/*
    164          * verify that the domain and specified database exsists
    165          *
    166          * XXXCDC: this effectively prevents us from pushing from any
    167          * host but the master.   an alternate plan is to find the master
    168          * host for a map, clear it, ask for the order number, and then
    169          * send xfr requests.   if that was used we would not need local
    170          * file access.
    171          */
    172 	if (chdir(YP_DB_PATH) < 0)
    173 		err(1, "%s", YP_DB_PATH);
    174 	if (chdir(ypi.ourdomain) < 0)
    175 		err(1, "%s/%s", YP_DB_PATH, ypi.ourdomain);
    176 
    177 	/*
    178          * now open the database so we can extract "order number"
    179          * (i.e. timestamp) of the map.
    180          */
    181 	ypdb = ypdb_open(ypi.map, 0, O_RDONLY);
    182 	if (ypdb == NULL)
    183 		err(1, "ypdb_open %s/%s/%s", YP_DB_PATH, ypi.ourdomain,
    184 		    ypi.map);
    185 	datum.dptr = YP_LAST_KEY;
    186 	datum.dsize = YP_LAST_LEN;
    187 	datum = ypdb_fetch(ypdb, datum);
    188 	if (datum.dptr == NULL)
    189 		errx(1,
    190 		    "unable to fetch %s key: check database with 'makedbm -u'",
    191 		    YP_LAST_KEY);
    192 	ypi.order = 0;
    193 	cp = datum.dptr;
    194 	while (cp < datum.dptr + datum.dsize) {
    195 		if (!isdigit(*cp))
    196 			errx(1,
    197 		    "invalid order number: check database with 'makedbm -u'");
    198 		ypi.order = (ypi.order * 10) + *cp - '0';
    199 		cp++;
    200 	}
    201 	ypdb_close(ypdb);
    202 
    203 	if (verbo)
    204 		printf("pushing %s [order=%d] in domain %s\n", ypi.map,
    205 		    ypi.order, ypi.ourdomain);
    206 
    207 	/*
    208          * ok, we are ready to do it.   first we send a clear_2 request
    209          * to the local server [should be the master] to make sure it has
    210          * the correct database open.
    211          *
    212          * XXXCDC: note that yp_bind_local exits on failure so ypserv can't
    213          * be null.   this makes it difficult to print a useful error message.
    214          * [it will print "clntudp_create: no contact with localhost"]
    215          */
    216 	tv.tv_sec = 10;
    217 	tv.tv_usec = 0;
    218 	ypserv = yp_bind_local(YPPROG, YPVERS);
    219 	retval = clnt_call(ypserv, YPPROC_CLEAR, xdr_void, 0, xdr_void, 0, tv);
    220 	if (retval != RPC_SUCCESS)
    221 		errx(1, "clnt_call CLEAR to local ypserv: %s",
    222 		    clnt_sperrno(retval));
    223 	clnt_destroy(ypserv);
    224 
    225 	/*
    226          * now use normal yplib functions to bind to the domain.
    227          */
    228 	rv = yp_bind(ypi.ourdomain);
    229 	if (rv)
    230 		errx(1, "error binding to %s: %s", ypi.ourdomain,
    231 		    yperr_string(rv));
    232 
    233 	/*
    234          * find 'owner' of the map (see pushit for usage)
    235          */
    236 	rv = yp_master(ypi.ourdomain, ypi.map, &ypi.owner);
    237 	if (rv)
    238 		errx(1, "error finding master for %s in %s: %s", ypi.map,
    239 		    ypi.ourdomain, yperr_string(rv));
    240 
    241 	/*
    242          * inform user of our progress
    243          */
    244 	if (verbo) {
    245 		printf("pushing map %s in %s: order=%d, owner=%s\n", ypi.map,
    246 		    ypi.ourdomain, ypi.order, ypi.owner);
    247 		printf("pushing to %s\n",
    248 		    (targhost) ? targhost : "<all ypservs>");
    249 	}
    250 
    251 	/*
    252          * finally, do it.
    253          */
    254 	if (targhost) {
    255 		push(targhost, strlen(targhost), &ypi);
    256 	} else {
    257 
    258 		/*
    259 	         * no host specified, do all hosts the master knows about via
    260 	         * the ypservers map.
    261 	         */
    262 		rv = yp_master(ypi.ourdomain, "ypservers", &master);
    263 		if (rv)
    264 			errx(1, "error finding master for ypservers in %s: %s",
    265 			    ypi.ourdomain, yperr_string(rv));
    266 
    267 		if (verbo)
    268 			printf(
    269 		"contacting ypservers %s master on %s for list of ypservs...\n",
    270 			    ypi.ourdomain, master);
    271 
    272 		ypserv = yp_bind_host(master, YPPROG, YPVERS, 0, 1);
    273 
    274 		ypallcb.foreach = pushit;	/* callback function */
    275 		ypallcb.data = (char *) &ypi;	/* data to pass into callback */
    276 
    277 		rv = yp_all_host(ypserv, ypi.ourdomain, "ypservers", &ypallcb);
    278 		if (rv)
    279 			errx(1, "pushing %s in %s failed: %s", ypi.map,
    280 			    ypi.ourdomain, yperr_string(rv));
    281 	}
    282 	exit(0);
    283 }
    284 
    285 /*
    286  * usage: print usage and exit
    287  */
    288 void
    289 usage()
    290 {
    291 	fprintf(stderr, "usage: %s [-d domain] [-h host] [-v] map\n",
    292 	    __progname);
    293 	exit(1);
    294 }
    295 
    296 /*
    297  * pushit: called from yp_all_host to push a specific host.
    298  * the key/value pairs are from the ypservers map.
    299  */
    300 int
    301 pushit(instatus, inkey, inkeylen, inval, invallen, indata)
    302 	int     instatus, inkeylen, invallen;
    303 	char   *inkey, *inval, *indata;
    304 {
    305 	struct yppush_info *ypi = (struct yppush_info *) indata;
    306 
    307 	push(inkey, inkeylen, ypi);		/* do it! */
    308 	return (0);
    309 }
    310 
    311 /*
    312  * push: push a specific map on a specific host
    313  */
    314 void
    315 push(host, hostlen, ypi)
    316 	char   *host;
    317 	int     hostlen;
    318 	struct yppush_info *ypi;
    319 {
    320 	char    target[YPMAXPEER];
    321 	CLIENT *ypserv;
    322 	SVCXPRT *transp;
    323 	int     prog, pid, rv;
    324 	struct timeval tv;
    325 	struct ypreq_xfr req;
    326 
    327 	/*
    328          * get our target host in a null terminated string
    329          */
    330 	snprintf(target, sizeof(target), "%*.*s", hostlen, hostlen, host);
    331 
    332 	/*
    333          * XXXCDC: arg!  we would like to use yp_bind_host here, except that
    334          * it exits on failure and we don't want to give up just because
    335          * one host fails.  thus, we have to do it the hard way.
    336          */
    337 	ypserv = clnt_create(target, YPPROG, YPVERS, "tcp");
    338 	if (ypserv == NULL) {
    339 		clnt_pcreateerror(target);
    340 		return;
    341 	}
    342 
    343 	/*
    344          * our XFR rpc request to the client just starts the transfer.
    345          * when the client is done, it wants to call a procedure that
    346          * we are serving to tell us that it is done.   so we must create
    347          * and register a procedure for us for it to call.
    348          */
    349 	transp = svcudp_create(RPC_ANYSOCK);
    350 	if (transp == NULL) {
    351 		warnx("callback svcudp_create failed");
    352 		goto error;
    353 	}
    354 
    355 	/* register it with portmap */
    356 	for (prog = 0x40000000; prog < 0x5fffffff; prog++) {
    357 		if (svc_register(transp, prog, 1, yppush_xfrrespprog_1,
    358 		    IPPROTO_UDP))
    359 			break;
    360 	}
    361 	if (prog >= 0x5fffffff) {
    362 		warnx("unable to register callback");
    363 		goto error;
    364 	}
    365 
    366 	/*
    367          * now fork off a server to catch our reply
    368          */
    369 	pid = fork();
    370 	if (pid == -1) {
    371 		svc_unregister(prog, 1);	/* drop our mapping with
    372 						 * portmap */
    373 		warn("fork failed");
    374 		goto error;
    375 	}
    376 
    377 	/*
    378          * child process becomes the server
    379          */
    380 	if (pid == 0) {
    381 		_svc_run();
    382 		exit(0);
    383 	}
    384 
    385 	/*
    386          * we are the parent process: send XFR request to server.
    387          * the "owner" field isn't used by ypserv (and shouldn't be, since
    388          * the ypserv has no idea if we are a legitimate yppush or not).
    389          * instead, the owner of the map is determined by the master value
    390          * currently cached on the slave server.
    391          */
    392 	close(transp->xp_fd);	/* close child's socket, we don't need it */
    393 	/* don't wait for anything here, we will wait for child's exit */
    394 	tv.tv_sec = 0;
    395 	tv.tv_usec = 0;
    396 	req.map_parms.domain = ypi->ourdomain;
    397 	req.map_parms.map = ypi->map;
    398 	req.map_parms.owner = ypi->owner;	/* NOT USED */
    399 	req.map_parms.ordernum = ypi->order;
    400 	req.transid = (u_int) pid;
    401 	req.proto = prog;
    402 	req.port = transp->xp_port;
    403 
    404 	if (verbo)
    405 		printf("asking host %s to transfer map (xid=%d)\n", target,
    406 		    req.transid);
    407 
    408 	rv = clnt_call(ypserv, YPPROC_XFR, xdr_ypreq_xfr, &req,
    409 	    		xdr_void, NULL, tv);			/* do it! */
    410 
    411 	if (rv != RPC_SUCCESS && rv != RPC_TIMEDOUT) {
    412 		warnx("unable to xfr to host %s: %s", target, clnt_sperrno(rv));
    413 		kill(pid, SIGTERM);
    414 	}
    415 
    416 	/*
    417          * now wait for child to get the reply and exit
    418          */
    419 	wait4(pid, NULL, 0, NULL);
    420 	svc_unregister(prog, 1);
    421 
    422 	/*
    423          * ... and we are done.   fall through
    424          */
    425 
    426 error:
    427 	if (transp)
    428 		svc_destroy(transp);
    429 	clnt_destroy(ypserv);
    430 	return;
    431 }
    432 
    433 /*
    434  * _svc_run: this is the main loop for the RPC server that we fork off
    435  * to await the reply from ypxfr.
    436  */
    437 void
    438 _svc_run()
    439 {
    440 	fd_set  readfds;
    441 	struct timeval tv;
    442 	int     rv, nfds;
    443 
    444 	nfds = sysconf(_SC_OPEN_MAX);
    445 	while (1) {
    446 
    447 		readfds = svc_fdset;	/* structure copy from global var */
    448 		tv.tv_sec = 60;
    449 		tv.tv_usec = 0;
    450 
    451 		rv = select(nfds, &readfds, NULL, NULL, &tv);
    452 
    453 		if (rv < 0) {
    454 			if (errno == EINTR)
    455 				continue;
    456 			warn("_svc_run: select failed");
    457 			return;
    458 		}
    459 		if (rv == 0)
    460 			errx(0, "_svc_run: callback timed out");
    461 
    462 		/*
    463 	         * got something
    464 	         */
    465 		svc_getreqset(&readfds);
    466 
    467 	}
    468 }
    469