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