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$");
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>
50#include <rpcsvc/ypclnt.h>
51#include <rpcsvc/ypxfrd.h>
52#include "ypxfr_extern.h"
53
54char *progname = "ypxfr";
55char *yp_dir = _PATH_YP;
56int _rpcpmstart = 0;
57int ypxfr_use_yplib = 0; /* Assume the worst. */
58int ypxfr_clear = 1;
59int ypxfr_prognum = 0;
60struct sockaddr_in ypxfr_callback_addr;
61struct yppushresp_xfr ypxfr_resp;
62DB *dbp;
63
64static void
65ypxfr_exit(ypxfrstat retval, char *temp)
66{
67	CLIENT *clnt;
68	int sock = RPC_ANYSOCK;
69	struct timeval timeout;
70
71	/* Clean up no matter what happened previously. */
72	if (temp != NULL) {
73		if (dbp != NULL)
74			(void)(dbp->close)(dbp);
75		if (unlink(temp) == -1) {
76			yp_error("failed to unlink %s",strerror(errno));
77		}
78	}
79
80	if (ypxfr_prognum) {
81		timeout.tv_sec = 20;
82		timeout.tv_usec = 0;
83
84		if ((clnt = clntudp_create(&ypxfr_callback_addr, ypxfr_prognum,
85					1, timeout, &sock)) == NULL) {
86			yp_error("%s", clnt_spcreateerror("failed to "
87			    "establish callback handle"));
88			exit(1);
89		}
90
91		ypxfr_resp.status = (yppush_status)retval;
92
93		if (yppushproc_xfrresp_1(&ypxfr_resp, clnt) == NULL) {
94			yp_error("%s", clnt_sperror(clnt, "callback failed"));
95			clnt_destroy(clnt);
96			exit(1);
97		}
98		clnt_destroy(clnt);
99	} else {
100		yp_error("Exiting: %s", ypxfrerr_string(retval));
101	}
102
103	exit(0);
104}
105
106static void
107usage(void)
108{
109	if (_rpcpmstart) {
110		ypxfr_exit(YPXFR_BADARGS,NULL);
111	} else {
112		fprintf(stderr, "%s\n%s\n%s\n",
113	"usage: ypxfr [-f] [-c] [-d target domain] [-h source host]",
114	"             [-s source domain] [-p path]",
115	"             [-C taskid program-number ipaddr port] mapname");
116		exit(1);
117	}
118}
119
120int
121ypxfr_foreach(int status, char *key, int keylen, char *val, int vallen,
122    char *data)
123{
124	DBT dbkey, dbval;
125
126	if (status != YP_TRUE)
127		return (status);
128
129	/*
130	 * XXX Do not attempt to write zero-length keys or
131	 * data into a Berkeley DB hash database. It causes a
132	 * strange failure mode where sequential searches get
133	 * caught in an infinite loop.
134	 */
135	if (keylen) {
136		dbkey.data = key;
137		dbkey.size = keylen;
138	} else {
139		dbkey.data = "";
140		dbkey.size = 1;
141	}
142	if (vallen) {
143		dbval.data = val;
144		dbval.size = vallen;
145	} else {
146		dbval.data = "";
147		dbval.size = 1;
148	}
149
150	if (yp_put_record(dbp, &dbkey, &dbval, 0) != YP_TRUE)
151		return(yp_errno);
152
153	return (0);
154}
155
156int
157main(int argc, char *argv[])
158{
159	int ch;
160	int ypxfr_force = 0;
161	char *ypxfr_dest_domain = NULL;
162	char *ypxfr_source_host = NULL;
163	char *ypxfr_source_domain = NULL;
164	char *ypxfr_local_domain = NULL;
165	char *ypxfr_master = NULL;
166	unsigned long ypxfr_order = -1, ypxfr_skew_check = -1;
167	char *ypxfr_mapname = NULL;
168	int ypxfr_args = 0;
169	char ypxfr_temp_map[MAXPATHLEN + 2];
170	char tempmap[MAXPATHLEN + 2];
171	char buf[MAXPATHLEN + 2];
172	DBT key, data;
173	int remoteport;
174	int interdom = 0;
175	int secure = 0;
176
177	debug = 1;
178
179	if (!isatty(fileno(stderr))) {
180		openlog("ypxfr", LOG_PID, LOG_DAEMON);
181		_rpcpmstart = 1;
182	}
183
184	if (argc < 2)
185		usage();
186
187	while ((ch = getopt(argc, argv, "fcd:h:s:p:C:")) != -1) {
188		int my_optind;
189		switch (ch) {
190		case 'f':
191			ypxfr_force++;
192			ypxfr_args++;
193			break;
194		case 'c':
195			ypxfr_clear = 0;
196			ypxfr_args++;
197			break;
198		case 'd':
199			ypxfr_dest_domain = optarg;
200			ypxfr_args += 2;
201			break;
202		case 'h':
203			ypxfr_source_host = optarg;
204			ypxfr_args += 2;
205			break;
206		case 's':
207			ypxfr_source_domain = optarg;
208			ypxfr_args += 2;
209			break;
210		case 'p':
211			yp_dir = optarg;
212			ypxfr_args += 2;
213			break;
214		case 'C':
215			/*
216			 * Whoever decided that the -C flag should take
217			 * four arguments is a twit.
218			 */
219			my_optind = optind - 1;
220			if (argv[my_optind] == NULL || !strlen(argv[my_optind])) {
221				yp_error("transaction ID not specified");
222				usage();
223			}
224			ypxfr_resp.transid = atol(argv[my_optind]);
225			my_optind++;
226			if (argv[my_optind] == NULL || !strlen(argv[my_optind])) {
227				yp_error("RPC program number not specified");
228				usage();
229			}
230			ypxfr_prognum = atol(argv[my_optind]);
231			my_optind++;
232			if (argv[my_optind] == NULL || !strlen(argv[my_optind])) {
233				yp_error("address not specified");
234				usage();
235			}
236			if (!inet_aton(argv[my_optind], &ypxfr_callback_addr.sin_addr)) {
237				yp_error("failed to convert '%s' to IP addr",
238					argv[my_optind]);
239				exit(1);
240			}
241			my_optind++;
242			if (argv[my_optind] == NULL || !strlen(argv[my_optind])) {
243				yp_error("port not specified");
244				usage();
245			}
246			ypxfr_callback_addr.sin_port = htons((u_short)atoi(argv[my_optind]));
247			ypxfr_args += 5;
248			break;
249		default:
250			usage();
251			break;
252		}
253	}
254
255	ypxfr_mapname = argv[ypxfr_args + 1];
256
257	if (ypxfr_mapname == NULL) {
258		yp_error("no map name specified");
259		usage();
260	}
261
262	/* Always the case. */
263	ypxfr_callback_addr.sin_family = AF_INET;
264
265	/* Determine if local NIS client facilities are turned on. */
266	if (!yp_get_default_domain(&ypxfr_local_domain) &&
267	    _yp_check(&ypxfr_local_domain))
268		ypxfr_use_yplib = 1;
269
270	/*
271	 * If no destination domain is specified, assume that the
272	 * local default domain is to be used and try to obtain it.
273	 * Fails if NIS client facilities are turned off.
274	 */
275	if (ypxfr_dest_domain == NULL) {
276		if (ypxfr_use_yplib) {
277			yp_get_default_domain(&ypxfr_dest_domain);
278		} else {
279			yp_error("no destination domain specified and \
280the local domain name isn't set");
281			ypxfr_exit(YPXFR_BADARGS,NULL);
282		}
283	}
284
285	/*
286	 * If a source domain is not specified, assume it to
287	 * be the same as the destination domain.
288	 */
289	if (ypxfr_source_domain == NULL) {
290		ypxfr_source_domain = ypxfr_dest_domain;
291	}
292
293	/*
294	 * If the source host is not specified, assume it to be the
295	 * master for the specified map. If local NIS client facilities
296	 * are turned on, we can figure this out using yp_master().
297	 * If not, we have to see if a local copy of the map exists
298	 * and extract its YP_MASTER_NAME record. If _that_ fails,
299	 * we are stuck and must ask the user for more information.
300	 */
301	if (ypxfr_source_host == NULL) {
302		if (!ypxfr_use_yplib) {
303		/*
304		 * Double whammy: NIS isn't turned on and the user
305		 * didn't specify a source host.
306		 */
307			char *dptr;
308			key.data = "YP_MASTER_NAME";
309			key.size = sizeof("YP_MASTER_NAME") - 1;
310
311			if (yp_get_record(ypxfr_dest_domain, ypxfr_mapname,
312					 &key, &data, 1) != YP_TRUE) {
313				yp_error("no source host specified");
314				ypxfr_exit(YPXFR_BADARGS,NULL);
315			}
316			dptr = data.data;
317			dptr[data.size] = '\0';
318			ypxfr_master = ypxfr_source_host = strdup(dptr);
319		}
320	} else {
321		if (ypxfr_use_yplib)
322			ypxfr_use_yplib = 0;
323	}
324
325	if (ypxfr_master == NULL) {
326		if ((ypxfr_master = ypxfr_get_master(ypxfr_source_domain,
327					    	 ypxfr_mapname,
328					     	ypxfr_source_host,
329					     	ypxfr_use_yplib)) == NULL) {
330			yp_error("failed to find master of %s in domain %s: %s",
331				  ypxfr_mapname, ypxfr_source_domain,
332				  ypxfrerr_string((ypxfrstat)yp_errno));
333			ypxfr_exit(YPXFR_MADDR,NULL);
334		}
335	}
336
337	/*
338	 * If we got here and ypxfr_source_host is still undefined,
339	 * it means we had to resort to using yp_master() to find the
340	 * master server for the map. The source host and master should
341	 * be identical.
342	 */
343	if (ypxfr_source_host == NULL)
344		ypxfr_source_host = ypxfr_master;
345
346	/*
347	 * Don't talk to ypservs on unprivileged ports.
348	 */
349	remoteport = getrpcport(ypxfr_source_host, YPPROG, YPVERS, IPPROTO_UDP);
350	if (remoteport >= IPPORT_RESERVED) {
351		yp_error("ypserv on %s not running on reserved port",
352						ypxfr_source_host);
353		ypxfr_exit(YPXFR_REFUSED, NULL);
354	}
355
356	if ((ypxfr_order = ypxfr_get_order(ypxfr_source_domain,
357					     ypxfr_mapname,
358					     ypxfr_master, 0)) == 0) {
359		yp_error("failed to get order number of %s: %s",
360				ypxfr_mapname, yp_errno == YPXFR_SUCC ?
361				"map has order 0" :
362				ypxfrerr_string((ypxfrstat)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" :
538				ypxfrerr_string((ypxfrstat)yp_errno));
539		ypxfr_exit(YPXFR_YPERR,ypxfr_temp_map);
540	}
541
542	if (ypxfr_order != ypxfr_skew_check)
543		ypxfr_exit(YPXFR_SKEW,ypxfr_temp_map);
544
545	/*
546	 * Send a YPPROC_CLEAR to the local ypserv.
547	 */
548	if (ypxfr_clear) {
549		char in = 0;
550		char *out = NULL;
551		int stat;
552		if ((stat = callrpc("localhost",YPPROG,YPVERS,YPPROC_CLEAR,
553			(xdrproc_t)xdr_void, (void *)&in,
554			(xdrproc_t)xdr_void, (void *)out)) != RPC_SUCCESS) {
555			yp_error("failed to send 'clear' to local ypserv: %s",
556				 clnt_sperrno((enum clnt_stat) stat));
557			ypxfr_exit(YPXFR_CLEAR, ypxfr_temp_map);
558		}
559	}
560
561	/*
562	 * Put the new map in place immediately. I'm not sure if the
563	 * kernel does an unlink() and rename() atomically in the event
564	 * that we move a new copy of a map over the top of an existing
565	 * one, but there's less chance of a race condition happening
566	 * than if we were to do the unlink() ourselves.
567	 */
568	if (rename(ypxfr_temp_map, buf) == -1) {
569		yp_error("rename(%s,%s) failed: %s", ypxfr_temp_map, buf,
570							strerror(errno));
571		ypxfr_exit(YPXFR_FILE,NULL);
572	}
573
574	ypxfr_exit(YPXFR_SUCC,NULL);
575
576	return(1);
577}
578