ypxfr_main.c revision 121538
1/*
2 * Copyright (c) 1995
3 *	Bill Paul <wpaul@ctr.columbia.edu>.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by Bill Paul.
16 * 4. Neither the name of the author nor the names of any co-contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: head/libexec/ypxfr/ypxfr_main.c 121538 2003-10-26 04:32:53Z peter $");
35
36#include <errno.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <syslog.h>
41#include <unistd.h>
42#include <sys/types.h>
43#include <sys/param.h>
44#include <sys/socket.h>
45#include <netinet/in.h>
46#include <arpa/inet.h>
47#include <rpc/rpc.h>
48#include <rpc/clnt.h>
49#include <rpcsvc/yp.h>
50struct dom_binding {};
51#include <rpcsvc/ypclnt.h>
52#include <rpcsvc/ypxfrd.h>
53#include "ypxfr_extern.h"
54
55char *progname = "ypxfr";
56char *yp_dir = _PATH_YP;
57int _rpcpmstart = 0;
58int ypxfr_use_yplib = 0; /* Assume the worst. */
59int ypxfr_clear = 1;
60int ypxfr_prognum = 0;
61struct sockaddr_in ypxfr_callback_addr;
62struct yppushresp_xfr ypxfr_resp;
63DB *dbp;
64
65static void
66ypxfr_exit(ypxfrstat retval, char *temp)
67{
68	CLIENT *clnt;
69	int sock = RPC_ANYSOCK;
70	struct timeval timeout;
71
72	/* Clean up no matter what happened previously. */
73	if (temp != NULL) {
74		if (dbp != NULL)
75			(void)(dbp->close)(dbp);
76		if (unlink(temp) == -1) {
77			yp_error("failed to unlink %s",strerror(errno));
78		}
79	}
80
81	if (ypxfr_prognum) {
82		timeout.tv_sec = 20;
83		timeout.tv_usec = 0;
84
85		if ((clnt = clntudp_create(&ypxfr_callback_addr, ypxfr_prognum,
86					1, timeout, &sock)) == NULL) {
87			yp_error("%s", clnt_spcreateerror("failed to "
88			    "establish callback handle"));
89			exit(1);
90		}
91
92		ypxfr_resp.status = retval;
93
94		if (yppushproc_xfrresp_1(&ypxfr_resp, clnt) == NULL) {
95			yp_error("%s", clnt_sperror(clnt, "callback failed"));
96			clnt_destroy(clnt);
97			exit(1);
98		}
99		clnt_destroy(clnt);
100	} else {
101		yp_error("Exiting: %s", ypxfrerr_string(retval));
102	}
103
104	exit(0);
105}
106
107static void
108usage(void)
109{
110	if (_rpcpmstart) {
111		ypxfr_exit(YPXFR_BADARGS,NULL);
112	} else {
113		fprintf(stderr, "%s\n%s\n%s\n",
114	"usage: ypxfr [-f] [-c] [-d target domain] [-h source host]",
115	"             [-s source domain] [-p path]",
116	"             [-C taskid program-number ipaddr port] mapname");
117		exit(1);
118	}
119}
120
121int
122ypxfr_foreach(int status, char *key, int keylen, char *val, int vallen,
123    char *data)
124{
125	DBT dbkey, dbval;
126
127	if (status != YP_TRUE)
128		return (status);
129
130	/*
131	 * XXX Do not attempt to write zero-length keys or
132	 * data into a Berkeley DB hash database. It causes a
133	 * strange failure mode where sequential searches get
134	 * caught in an infinite loop.
135	 */
136	if (keylen) {
137		dbkey.data = key;
138		dbkey.size = keylen;
139	} else {
140		dbkey.data = "";
141		dbkey.size = 1;
142	}
143	if (vallen) {
144		dbval.data = val;
145		dbval.size = vallen;
146	} else {
147		dbval.data = "";
148		dbval.size = 1;
149	}
150
151	if (yp_put_record(dbp, &dbkey, &dbval, 0) != YP_TRUE)
152		return(yp_errno);
153
154	return (0);
155}
156
157int
158main(int argc, char *argv[])
159{
160	int ch;
161	int ypxfr_force = 0;
162	char *ypxfr_dest_domain = NULL;
163	char *ypxfr_source_host = NULL;
164	char *ypxfr_source_domain = NULL;
165	char *ypxfr_local_domain = NULL;
166	char *ypxfr_master = NULL;
167	unsigned long ypxfr_order = -1, ypxfr_skew_check = -1;
168	char *ypxfr_mapname = NULL;
169	int ypxfr_args = 0;
170	char ypxfr_temp_map[MAXPATHLEN + 2];
171	char tempmap[MAXPATHLEN + 2];
172	char buf[MAXPATHLEN + 2];
173	DBT key, data;
174	int remoteport;
175	int interdom = 0;
176	int secure = 0;
177
178	debug = 1;
179
180	if (!isatty(fileno(stderr))) {
181		openlog("ypxfr", LOG_PID, LOG_DAEMON);
182		_rpcpmstart = 1;
183	}
184
185	if (argc < 2)
186		usage();
187
188	while ((ch = getopt(argc, argv, "fcd:h:s:p:C:")) != -1) {
189		int my_optind;
190		switch (ch) {
191		case 'f':
192			ypxfr_force++;
193			ypxfr_args++;
194			break;
195		case 'c':
196			ypxfr_clear = 0;
197			ypxfr_args++;
198			break;
199		case 'd':
200			ypxfr_dest_domain = optarg;
201			ypxfr_args += 2;
202			break;
203		case 'h':
204			ypxfr_source_host = optarg;
205			ypxfr_args += 2;
206			break;
207		case 's':
208			ypxfr_source_domain = optarg;
209			ypxfr_args += 2;
210			break;
211		case 'p':
212			yp_dir = optarg;
213			ypxfr_args += 2;
214			break;
215		case 'C':
216			/*
217			 * Whoever decided that the -C flag should take
218			 * four arguments is a twit.
219			 */
220			my_optind = optind - 1;
221			if (argv[my_optind] == NULL || !strlen(argv[my_optind])) {
222				yp_error("transaction ID not specified");
223				usage();
224			}
225			ypxfr_resp.transid = atol(argv[my_optind]);
226			my_optind++;
227			if (argv[my_optind] == NULL || !strlen(argv[my_optind])) {
228				yp_error("RPC program number not specified");
229				usage();
230			}
231			ypxfr_prognum = atol(argv[my_optind]);
232			my_optind++;
233			if (argv[my_optind] == NULL || !strlen(argv[my_optind])) {
234				yp_error("address not specified");
235				usage();
236			}
237			if (!inet_aton(argv[my_optind], &ypxfr_callback_addr.sin_addr)) {
238				yp_error("failed to convert '%s' to IP addr",
239					argv[my_optind]);
240				exit(1);
241			}
242			my_optind++;
243			if (argv[my_optind] == NULL || !strlen(argv[my_optind])) {
244				yp_error("port not specified");
245				usage();
246			}
247			ypxfr_callback_addr.sin_port = htons((u_short)atoi(argv[my_optind]));
248			ypxfr_args += 5;
249			break;
250		default:
251			usage();
252			break;
253		}
254	}
255
256	ypxfr_mapname = argv[ypxfr_args + 1];
257
258	if (ypxfr_mapname == NULL) {
259		yp_error("no map name specified");
260		usage();
261	}
262
263	/* Always the case. */
264	ypxfr_callback_addr.sin_family = AF_INET;
265
266	/* Determine if local NIS client facilities are turned on. */
267	if (!yp_get_default_domain(&ypxfr_local_domain) &&
268	    _yp_check(&ypxfr_local_domain))
269		ypxfr_use_yplib = 1;
270
271	/*
272	 * If no destination domain is specified, assume that the
273	 * local default domain is to be used and try to obtain it.
274	 * Fails if NIS client facilities are turned off.
275	 */
276	if (ypxfr_dest_domain == NULL) {
277		if (ypxfr_use_yplib) {
278			yp_get_default_domain(&ypxfr_dest_domain);
279		} else {
280			yp_error("no destination domain specified and \
281the local domain name isn't set");
282			ypxfr_exit(YPXFR_BADARGS,NULL);
283		}
284	}
285
286	/*
287	 * If a source domain is not specified, assume it to
288	 * be the same as the destination domain.
289	 */
290	if (ypxfr_source_domain == NULL) {
291		ypxfr_source_domain = ypxfr_dest_domain;
292	}
293
294	/*
295	 * If the source host is not specified, assume it to be the
296	 * master for the specified map. If local NIS client facilities
297	 * are turned on, we can figure this out using yp_master().
298	 * If not, we have to see if a local copy of the map exists
299	 * and extract its YP_MASTER_NAME record. If _that_ fails,
300	 * we are stuck and must ask the user for more information.
301	 */
302	if (ypxfr_source_host == NULL) {
303		if (!ypxfr_use_yplib) {
304		/*
305		 * Double whammy: NIS isn't turned on and the user
306		 * didn't specify a source host.
307		 */
308			char *dptr;
309			key.data = "YP_MASTER_NAME";
310			key.size = sizeof("YP_MASTER_NAME") - 1;
311
312			if (yp_get_record(ypxfr_dest_domain, ypxfr_mapname,
313					 &key, &data, 1) != YP_TRUE) {
314				yp_error("no source host specified");
315				ypxfr_exit(YPXFR_BADARGS,NULL);
316			}
317			dptr = data.data;
318			dptr[data.size] = '\0';
319			ypxfr_master = ypxfr_source_host = strdup(dptr);
320		}
321	} else {
322		if (ypxfr_use_yplib)
323			ypxfr_use_yplib = 0;
324	}
325
326	if (ypxfr_master == NULL) {
327		if ((ypxfr_master = ypxfr_get_master(ypxfr_source_domain,
328					    	 ypxfr_mapname,
329					     	ypxfr_source_host,
330					     	ypxfr_use_yplib)) == NULL) {
331			yp_error("failed to find master of %s in domain %s: %s",
332				  ypxfr_mapname, ypxfr_source_domain,
333				  ypxfrerr_string(yp_errno));
334			ypxfr_exit(YPXFR_MADDR,NULL);
335		}
336	}
337
338	/*
339	 * If we got here and ypxfr_source_host is still undefined,
340	 * it means we had to resort to using yp_master() to find the
341	 * master server for the map. The source host and master should
342	 * be identical.
343	 */
344	if (ypxfr_source_host == NULL)
345		ypxfr_source_host = ypxfr_master;
346
347	/*
348	 * Don't talk to ypservs on unprivileged ports.
349	 */
350	remoteport = getrpcport(ypxfr_source_host, YPPROG, YPVERS, IPPROTO_UDP);
351	if (remoteport >= IPPORT_RESERVED) {
352		yp_error("ypserv on %s not running on reserved port",
353						ypxfr_source_host);
354		ypxfr_exit(YPXFR_REFUSED, NULL);
355	}
356
357	if ((ypxfr_order = ypxfr_get_order(ypxfr_source_domain,
358					     ypxfr_mapname,
359					     ypxfr_master, 0)) == 0) {
360		yp_error("failed to get order number of %s: %s",
361				ypxfr_mapname, yp_errno == YPXFR_SUCC ?
362				"map has order 0" : ypxfrerr_string(yp_errno));
363		ypxfr_exit(YPXFR_YPERR,NULL);
364	}
365
366	if (ypxfr_match(ypxfr_master, ypxfr_source_domain, ypxfr_mapname,
367			"YP_INTERDOMAIN", sizeof("YP_INTERDOMAIN") - 1))
368		interdom++;
369
370	if (ypxfr_match(ypxfr_master, ypxfr_source_domain, ypxfr_mapname,
371			"YP_SECURE", sizeof("YP_SECURE") - 1))
372		secure++;
373
374	key.data = "YP_LAST_MODIFIED";
375	key.size = sizeof("YP_LAST_MODIFIED") - 1;
376
377	/* The order number is immaterial when the 'force' flag is set. */
378
379	if (!ypxfr_force) {
380		int ignore = 0;
381		if (yp_get_record(ypxfr_dest_domain,ypxfr_mapname,&key,&data,1) != YP_TRUE) {
382			switch (yp_errno) {
383			case YP_NOKEY:
384				ypxfr_exit(YPXFR_FORCE,NULL);
385				break;
386			case YP_NOMAP:
387				/*
388				 * If the map doesn't exist, we're
389				 * creating it. Ignore the error.
390				 */
391				ignore++;
392				break;
393			case YP_BADDB:
394			default:
395				ypxfr_exit(YPXFR_DBM,NULL);
396				break;
397			}
398		}
399		if (!ignore && ypxfr_order <= atoi(data.data))
400			ypxfr_exit(YPXFR_AGE, NULL);
401
402	}
403
404	/* Construct a temporary map file name */
405	snprintf(tempmap, sizeof(tempmap), "%s.%d",ypxfr_mapname, getpid());
406	snprintf(ypxfr_temp_map, sizeof(ypxfr_temp_map), "%s/%s/%s", yp_dir,
407		 ypxfr_dest_domain, tempmap);
408
409	if ((remoteport = getrpcport(ypxfr_source_host, YPXFRD_FREEBSD_PROG,
410					YPXFRD_FREEBSD_VERS, IPPROTO_TCP))) {
411
412		/* Don't talk to rpc.ypxfrds on unprovileged ports. */
413		if (remoteport >= IPPORT_RESERVED) {
414			yp_error("rpc.ypxfrd on %s not using privileged port",
415							ypxfr_source_host);
416			ypxfr_exit(YPXFR_REFUSED, NULL);
417		}
418
419		/* Try to send using ypxfrd. If it fails, use old method. */
420		if (!ypxfrd_get_map(ypxfr_source_host, ypxfr_mapname,
421					ypxfr_source_domain, ypxfr_temp_map))
422			goto leave;
423	}
424
425	/* Open the temporary map read/write. */
426	if ((dbp = yp_open_db_rw(ypxfr_dest_domain, tempmap, 0)) == NULL) {
427		yp_error("failed to open temporary map file");
428		ypxfr_exit(YPXFR_DBM,NULL);
429	}
430
431	/*
432	 * Fill in the keys we already know, such as the order number,
433	 * master name, input file name (we actually make up a bogus
434	 * name for that) and output file name.
435	 */
436	snprintf(buf, sizeof(buf), "%lu", ypxfr_order);
437	data.data = buf;
438	data.size = strlen(buf);
439
440	if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) {
441		yp_error("failed to write order number to database");
442		ypxfr_exit(YPXFR_DBM,ypxfr_temp_map);
443	}
444
445	key.data = "YP_MASTER_NAME";
446	key.size = sizeof("YP_MASTER_NAME") - 1;
447	data.data = ypxfr_master;
448	data.size = strlen(ypxfr_master);
449
450	if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) {
451		yp_error("failed to write master name to database");
452		ypxfr_exit(YPXFR_DBM,ypxfr_temp_map);
453	}
454
455	key.data = "YP_DOMAIN_NAME";
456	key.size = sizeof("YP_DOMAIN_NAME") - 1;
457	data.data = ypxfr_dest_domain;
458	data.size = strlen(ypxfr_dest_domain);
459
460	if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) {
461		yp_error("failed to write domain name to database");
462		ypxfr_exit(YPXFR_DBM,ypxfr_temp_map);
463	}
464
465	snprintf (buf, sizeof(buf), "%s:%s", ypxfr_source_host, ypxfr_mapname);
466
467	key.data = "YP_INPUT_NAME";
468	key.size = sizeof("YP_INPUT_NAME") - 1;
469	data.data = &buf;
470	data.size = strlen(buf);
471
472	if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) {
473		yp_error("failed to write input name to database");
474		ypxfr_exit(YPXFR_DBM,ypxfr_temp_map);
475
476	}
477
478	snprintf(buf, sizeof(buf), "%s/%s/%s", yp_dir, ypxfr_dest_domain,
479							ypxfr_mapname);
480
481	key.data = "YP_OUTPUT_NAME";
482	key.size = sizeof("YP_OUTPUT_NAME") - 1;
483	data.data = &buf;
484	data.size = strlen(buf);
485
486	if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) {
487		yp_error("failed to write output name to database");
488		ypxfr_exit(YPXFR_DBM,ypxfr_temp_map);
489	}
490
491	if (interdom) {
492		key.data = "YP_INTERDOMAIN";
493		key.size = sizeof("YP_INTERDOMAIN") - 1;
494		data.data = "";
495		data.size = 0;
496
497		if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) {
498			yp_error("failed to add interdomain flag to database");
499			ypxfr_exit(YPXFR_DBM,ypxfr_temp_map);
500		}
501	}
502
503	if (secure) {
504		key.data = "YP_SECURE";
505		key.size = sizeof("YP_SECURE") - 1;
506		data.data = "";
507		data.size = 0;
508
509		if (yp_put_record(dbp, &key, &data, 0) != YP_TRUE) {
510			yp_error("failed to add secure flag to database");
511			ypxfr_exit(YPXFR_DBM,ypxfr_temp_map);
512		}
513	}
514
515	/* Now suck over the contents of the map from the master. */
516
517	if (ypxfr_get_map(ypxfr_mapname,ypxfr_source_domain,
518			  ypxfr_source_host, ypxfr_foreach)){
519		yp_error("failed to retrieve map from source host");
520		ypxfr_exit(YPXFR_YPERR,ypxfr_temp_map);
521	}
522
523	(void)(dbp->close)(dbp);
524	dbp = NULL; /* <- yes, it seems this is necessary. */
525
526leave:
527
528	snprintf(buf, sizeof(buf), "%s/%s/%s", yp_dir, ypxfr_dest_domain,
529							ypxfr_mapname);
530
531	/* Peek at the order number again and check for skew. */
532	if ((ypxfr_skew_check = ypxfr_get_order(ypxfr_source_domain,
533					     ypxfr_mapname,
534					     ypxfr_master, 0)) == 0) {
535		yp_error("failed to get order number of %s: %s",
536				ypxfr_mapname, yp_errno == YPXFR_SUCC ?
537				"map has order 0" : ypxfrerr_string(yp_errno));
538		ypxfr_exit(YPXFR_YPERR,ypxfr_temp_map);
539	}
540
541	if (ypxfr_order != ypxfr_skew_check)
542		ypxfr_exit(YPXFR_SKEW,ypxfr_temp_map);
543
544	/*
545	 * Send a YPPROC_CLEAR to the local ypserv.
546	 */
547	if (ypxfr_clear) {
548		char in = 0;
549		char *out = NULL;
550		int stat;
551		if ((stat = callrpc("localhost",YPPROG,YPVERS,YPPROC_CLEAR,
552			(xdrproc_t)xdr_void, (void *)&in,
553			(xdrproc_t)xdr_void, (void *)out)) != RPC_SUCCESS) {
554			yp_error("failed to send 'clear' to local ypserv: %s",
555				 clnt_sperrno((enum clnt_stat) stat));
556			ypxfr_exit(YPXFR_CLEAR, ypxfr_temp_map);
557		}
558	}
559
560	/*
561	 * Put the new map in place immediately. I'm not sure if the
562	 * kernel does an unlink() and rename() atomically in the event
563	 * that we move a new copy of a map over the top of an existing
564	 * one, but there's less chance of a race condition happening
565	 * than if we were to do the unlink() ourselves.
566	 */
567	if (rename(ypxfr_temp_map, buf) == -1) {
568		yp_error("rename(%s,%s) failed: %s", ypxfr_temp_map, buf,
569							strerror(errno));
570		ypxfr_exit(YPXFR_FILE,NULL);
571	}
572
573	ypxfr_exit(YPXFR_SUCC,NULL);
574
575	return(1);
576}
577