aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/yppush/yppush_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/yppush/yppush_main.c')
-rw-r--r--usr.sbin/yppush/yppush_main.c633
1 files changed, 633 insertions, 0 deletions
diff --git a/usr.sbin/yppush/yppush_main.c b/usr.sbin/yppush/yppush_main.c
new file mode 100644
index 000000000000..2b167846d1b6
--- /dev/null
+++ b/usr.sbin/yppush/yppush_main.c
@@ -0,0 +1,633 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 1995
+ * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Bill Paul.
+ * 4. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/fcntl.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+#include <rpc/rpc.h>
+#include <rpc/clnt.h>
+#include <rpc/pmap_clnt.h>
+#include <rpcsvc/yp.h>
+#include <rpcsvc/ypclnt.h>
+#include "ypxfr_extern.h"
+#include "yppush_extern.h"
+
+char *progname = "yppush";
+int debug = 1;
+int _rpcpmstart = 0;
+char *yp_dir = _PATH_YP;
+
+static char *yppush_mapname = NULL; /* Map to transfer. */
+static char *yppush_domain = NULL; /* Domain in which map resides. */
+static char *yppush_master = NULL; /* Master NIS server for said domain. */
+static int skip_master = 0; /* Do not attempt to push map to master. */
+static int verbose = 0; /* Toggle verbose mode. */
+static unsigned long yppush_transid = 0;
+static int yppush_timeout = 80; /* Default timeout. */
+static int yppush_jobs = 1; /* Number of allowed concurrent jobs. */
+static int yppush_running_jobs = 0; /* Number of currently running jobs. */
+
+/* Structure for holding information about a running job. */
+struct jobs {
+ unsigned long tid;
+ int port;
+ ypxfrstat stat;
+ unsigned long prognum;
+ char *server;
+ char *map;
+ int polled;
+ struct jobs *next;
+};
+
+static struct jobs *yppush_joblist; /* Linked list of running jobs. */
+static int yppush_svc_run(int);
+
+/*
+ * Local error messages.
+ */
+static const char *
+yppusherr_string(int err)
+{
+ switch (err) {
+ case YPPUSH_TIMEDOUT:
+ return("transfer or callback timed out");
+ case YPPUSH_YPSERV:
+ return("failed to contact ypserv");
+ case YPPUSH_NOHOST:
+ return("no such host");
+ case YPPUSH_PMAP:
+ return("portmapper failure");
+ default:
+ return("unknown error code");
+ }
+}
+
+/*
+ * Report state of a job.
+ */
+static int
+yppush_show_status(ypxfrstat status, unsigned long tid)
+{
+ struct jobs *job;
+
+ job = yppush_joblist;
+
+ while (job != NULL) {
+ if (job->tid == tid)
+ break;
+ job = job->next;
+ }
+
+ if (job == NULL) {
+ yp_error("warning: received callback with invalid transaction ID: %lu",
+ tid);
+ return (0);
+ }
+
+ if (job->polled) {
+ yp_error("warning: received callback with duplicate transaction ID: %lu",
+ tid);
+ return (0);
+ }
+
+ if (verbose > 1) {
+ yp_error("checking return status: transaction ID: %lu",
+ job->tid);
+ }
+
+ if (status != YPXFR_SUCC || verbose) {
+ yp_error("transfer of map %s to server %s %s",
+ job->map, job->server, status == YPXFR_SUCC ?
+ "succeeded" : "failed");
+ yp_error("status returned by ypxfr: %s", status > YPXFR_AGE ?
+ yppusherr_string(status) :
+ ypxfrerr_string(status));
+ }
+
+ job->polled = 1;
+
+ svc_unregister(job->prognum, 1);
+
+ yppush_running_jobs--;
+ return(0);
+}
+
+/* Exit routine. */
+static void
+yppush_exit(int now)
+{
+ struct jobs *jptr;
+ int still_pending = 1;
+
+ /* Let all the information trickle in. */
+ while (!now && still_pending) {
+ jptr = yppush_joblist;
+ still_pending = 0;
+ while (jptr) {
+ if (jptr->polled == 0) {
+ still_pending++;
+ if (verbose > 1)
+ yp_error("%s has not responded",
+ jptr->server);
+ } else {
+ if (verbose > 1)
+ yp_error("%s has responded",
+ jptr->server);
+ }
+ jptr = jptr->next;
+ }
+ if (still_pending) {
+ if (verbose > 1)
+ yp_error("%d transfer%sstill pending",
+ still_pending,
+ still_pending > 1 ? "s " : " ");
+ if (yppush_svc_run (YPPUSH_RESPONSE_TIMEOUT) == 0) {
+ yp_error("timed out");
+ now = 1;
+ }
+ } else {
+ if (verbose)
+ yp_error("all transfers complete");
+ break;
+ }
+ }
+
+
+ /* All stats collected and reported -- kill all the stragglers. */
+ jptr = yppush_joblist;
+ while (jptr) {
+ if (!jptr->polled)
+ yp_error("warning: exiting with transfer \
+to %s (transid = %lu) still pending", jptr->server, jptr->tid);
+ svc_unregister(jptr->prognum, 1);
+ jptr = jptr->next;
+ }
+
+ exit(0);
+}
+
+/*
+ * Handler for 'normal' signals.
+ */
+
+static void
+handler(int sig)
+{
+ yppush_exit (1);
+}
+
+/*
+ * Dispatch loop for callback RPC services.
+ * Return value:
+ * -1 error
+ * 0 timeout
+ * >0 request serviced
+ */
+static int
+yppush_svc_run(int timeout_secs)
+{
+ int rc;
+ fd_set readfds;
+ struct timeval timeout;
+
+ timeout.tv_usec = 0;
+ timeout.tv_sec = timeout_secs;
+
+retry:
+ readfds = svc_fdset;
+ rc = select(svc_maxfd + 1, &readfds, NULL, NULL, &timeout);
+ switch (rc) {
+ case -1:
+ if (errno == EINTR)
+ goto retry;
+ yp_error("select failed: %s", strerror(errno));
+ break;
+ case 0:
+ yp_error("select() timed out");
+ break;
+ default:
+ svc_getreqset(&readfds);
+ break;
+ }
+ return rc;
+}
+
+/*
+ * RPC service routines for callbacks.
+ */
+void *
+yppushproc_null_1_svc(void *argp, struct svc_req *rqstp)
+{
+ static char * result;
+ /* Do nothing -- RPC conventions call for all a null proc. */
+ return((void *) &result);
+}
+
+void *
+yppushproc_xfrresp_1_svc(yppushresp_xfr *argp, struct svc_req *rqstp)
+{
+ static char * result;
+ yppush_show_status(argp->status, argp->transid);
+ return((void *) &result);
+}
+
+/*
+ * Transmit a YPPROC_XFR request to ypserv.
+ */
+static int
+yppush_send_xfr(struct jobs *job)
+{
+ ypreq_xfr req;
+/* ypresp_xfr *resp; */
+ DBT key, data;
+ CLIENT *clnt;
+ struct rpc_err err;
+ struct timeval timeout;
+
+ timeout.tv_usec = 0;
+ timeout.tv_sec = 0;
+
+ /*
+ * The ypreq_xfr structure has a member of type map_parms,
+ * which seems to require the order number of the map.
+ * It isn't actually used at the other end (at least the
+ * FreeBSD ypserv doesn't use it) but we fill it in here
+ * for the sake of completeness.
+ */
+ key.data = "YP_LAST_MODIFIED";
+ key.size = sizeof ("YP_LAST_MODIFIED") - 1;
+
+ if (yp_get_record(yppush_domain, yppush_mapname, &key, &data,
+ 1) != YP_TRUE) {
+ yp_error("failed to read order number from %s: %s: %s",
+ yppush_mapname, yperr_string(yp_errno),
+ strerror(errno));
+ return(1);
+ }
+
+ /* Fill in the request arguments */
+ req.map_parms.ordernum = atoi(data.data);
+ req.map_parms.domain = yppush_domain;
+ req.map_parms.peer = yppush_master;
+ req.map_parms.map = job->map;
+ req.transid = job->tid;
+ req.prog = job->prognum;
+ req.port = job->port;
+
+ /* Get a handle to the remote ypserv. */
+ if ((clnt = clnt_create(job->server, YPPROG, YPVERS, "udp")) == NULL) {
+ yp_error("%s: %s",job->server,clnt_spcreateerror("couldn't \
+create udp handle to NIS server"));
+ switch (rpc_createerr.cf_stat) {
+ case RPC_UNKNOWNHOST:
+ job->stat = YPPUSH_NOHOST;
+ break;
+ case RPC_PMAPFAILURE:
+ job->stat = YPPUSH_PMAP;
+ break;
+ default:
+ job->stat = YPPUSH_RPC;
+ break;
+ }
+ return(1);
+ }
+
+ /*
+ * Reduce timeout to nothing since we may not
+ * get a response from ypserv and we don't want to block.
+ */
+ if (clnt_control(clnt, CLSET_TIMEOUT, (char *)&timeout) == FALSE)
+ yp_error("failed to set timeout on ypproc_xfr call");
+
+ /* Invoke the ypproc_xfr service. */
+ if (ypproc_xfr_2(&req, clnt) == NULL) {
+ clnt_geterr(clnt, &err);
+ if (err.re_status != RPC_SUCCESS &&
+ err.re_status != RPC_TIMEDOUT) {
+ yp_error("%s: %s", job->server, clnt_sperror(clnt,
+ "yp_xfr failed"));
+ job->stat = YPPUSH_YPSERV;
+ clnt_destroy(clnt);
+ return(1);
+ }
+ }
+
+ clnt_destroy(clnt);
+
+ return(0);
+}
+
+/*
+ * Main driver function. Register the callback service, add the transfer
+ * request to the internal list, send the YPPROC_XFR request to ypserv
+ * do other magic things.
+ */
+static int
+yp_push(char *server, char *map, unsigned long tid)
+{
+ unsigned long prognum;
+ int sock = RPC_ANYSOCK;
+ SVCXPRT *xprt;
+ struct jobs *job;
+
+ /* Register the job in our linked list of jobs. */
+
+ /* First allocate job structure */
+ if ((job = (struct jobs *)malloc(sizeof (struct jobs))) == NULL) {
+ yp_error("malloc failed");
+ yppush_exit (1);
+ }
+
+ /*
+ * Register the callback service on the first free transient
+ * program number.
+ */
+ xprt = svcudp_create(sock);
+ for (prognum = 0x40000000; prognum < 0x5FFFFFFF; prognum++) {
+ if (svc_register(xprt, prognum, 1,
+ yppush_xfrrespprog_1, IPPROTO_UDP) == TRUE)
+ break;
+ }
+ if (prognum == 0x5FFFFFFF) {
+ yp_error ("can't register yppush_xfrrespprog_1");
+ yppush_exit (1);
+ }
+
+ /* Initialize the info for this job. */
+ job->stat = 0;
+ job->tid = tid;
+ job->port = xprt->xp_port;
+ job->server = strdup(server);
+ job->map = strdup(map);
+ job->prognum = prognum;
+ job->polled = 0;
+ job->next = yppush_joblist;
+ yppush_joblist = job;
+
+ if (verbose) {
+ yp_error("initiating transfer: %s -> %s (transid = %lu)",
+ yppush_mapname, server, tid);
+ }
+
+ /*
+ * Send the XFR request to ypserv. We don't have to wait for
+ * a response here since we handle them asynchronously.
+ */
+
+ if (yppush_send_xfr(job)){
+ /* Transfer request blew up. */
+ yppush_show_status(job->stat ? job->stat :
+ YPPUSH_YPSERV,job->tid);
+ } else {
+ if (verbose > 1)
+ yp_error("%s has been called", server);
+ }
+
+ return(0);
+}
+
+/*
+ * Called for each entry in the ypservers map from yp_get_map(), which
+ * is our private yp_all() routine.
+ */
+static int
+yppush_foreach(int status, char *key, int keylen, char *val, int vallen,
+ char *data)
+{
+ char *server;
+
+ if (status != YP_TRUE)
+ return (status);
+
+ asprintf(&server, "%.*s", vallen, val);
+
+ /*
+ * Do not stop the iteration on the allocation failure. We
+ * cannot usefully react on low memory condition anyway, and
+ * the failure is more likely due to insane val.
+ */
+ if (server == NULL)
+ return (0);
+
+ if (skip_master && strcasecmp(server, yppush_master) == 0) {
+ free(server);
+ return (0);
+ }
+
+ /*
+ * Restrict the number of concurrent jobs: if yppush_jobs number
+ * of jobs have already been dispatched and are still pending,
+ * wait for one of them to finish so we can reuse its slot.
+ */
+ while (yppush_running_jobs >= yppush_jobs && (yppush_svc_run (yppush_timeout) > 0))
+ ;
+
+ /* Cleared for takeoff: set everything in motion. */
+ if (yp_push(server, yppush_mapname, yppush_transid)) {
+ free(server);
+ return(yp_errno);
+ }
+
+ /* Bump the job counter and transaction ID. */
+ yppush_running_jobs++;
+ yppush_transid++;
+ free(server);
+ return (0);
+}
+
+static void
+usage()
+{
+ fprintf (stderr, "%s\n%s\n",
+ "usage: yppush [-d domain] [-t timeout] [-j #parallel jobs] [-h host]",
+ " [-p path] mapname");
+ exit(1);
+}
+
+/*
+ * Entry point. (About time!)
+ */
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ DBT key, data;
+ char myname[MAXHOSTNAMELEN];
+ struct hostlist {
+ char *name;
+ struct hostlist *next;
+ };
+ struct hostlist *yppush_hostlist = NULL;
+ struct hostlist *tmp;
+
+ while ((ch = getopt(argc, argv, "d:j:p:h:t:v")) != -1) {
+ switch (ch) {
+ case 'd':
+ yppush_domain = optarg;
+ break;
+ case 'j':
+ yppush_jobs = atoi(optarg);
+ if (yppush_jobs <= 0)
+ yppush_jobs = 1;
+ break;
+ case 'p':
+ yp_dir = optarg;
+ break;
+ case 'h': /* we can handle multiple hosts */
+ if ((tmp = (struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) {
+ yp_error("malloc failed");
+ yppush_exit(1);
+ }
+ tmp->name = strdup(optarg);
+ tmp->next = yppush_hostlist;
+ yppush_hostlist = tmp;
+ break;
+ case 't':
+ yppush_timeout = atoi(optarg);
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ yppush_mapname = argv[0];
+
+ if (yppush_mapname == NULL) {
+ /* "No guts, no glory." */
+ usage();
+ }
+
+ /*
+ * If no domain was specified, try to find the default
+ * domain. If we can't find that, we're doomed and must bail.
+ */
+ if (yppush_domain == NULL) {
+ char *yppush_check_domain;
+ if (!yp_get_default_domain(&yppush_check_domain) &&
+ !_yp_check(&yppush_check_domain)) {
+ yp_error("no domain specified and NIS not running");
+ usage();
+ } else
+ yp_get_default_domain(&yppush_domain);
+ }
+
+ /* Check to see that we are the master for this map. */
+
+ if (gethostname ((char *)&myname, sizeof(myname))) {
+ yp_error("failed to get name of local host: %s",
+ strerror(errno));
+ yppush_exit(1);
+ }
+
+ key.data = "YP_MASTER_NAME";
+ key.size = sizeof("YP_MASTER_NAME") - 1;
+
+ if (yp_get_record(yppush_domain, yppush_mapname,
+ &key, &data, 1) != YP_TRUE) {
+ yp_error("couldn't open %s map: %s", yppush_mapname,
+ strerror(errno));
+ yppush_exit(1);
+ }
+
+ if (strncasecmp(myname, data.data, data.size) == 0) {
+ /* I am master server, and no explicit host list was
+ specified: do not push map to myself -- this will
+ fail with YPPUSH_AGE anyway. */
+ if (yppush_hostlist == NULL)
+ skip_master = 1;
+ } else {
+ yp_error("warning: this host is not the master for %s",
+ yppush_mapname);
+#ifdef NITPICKY
+ yppush_exit(1);
+#endif
+ }
+
+ yppush_master = malloc(data.size + 1);
+ strncpy(yppush_master, data.data, data.size);
+ yppush_master[data.size] = '\0';
+
+ /* Install some handy handlers. */
+ signal(SIGTERM, handler);
+ signal(SIGINT, handler);
+
+ /* set initial transaction ID */
+ yppush_transid = time((time_t *)NULL);
+
+ if (yppush_hostlist) {
+ /*
+ * Host list was specified on the command line:
+ * kick off the transfers by hand.
+ */
+ tmp = yppush_hostlist;
+ while (tmp) {
+ yppush_foreach(YP_TRUE, NULL, 0, tmp->name,
+ strlen(tmp->name), NULL);
+ tmp = tmp->next;
+ }
+ } else {
+ /*
+ * Do a yp_all() on the ypservers map and initiate a ypxfr
+ * for each one.
+ */
+ ypxfr_get_map("ypservers", yppush_domain,
+ "localhost", yppush_foreach);
+ }
+
+ if (verbose > 1)
+ yp_error("all jobs dispatched");
+
+ /* All done -- normal exit. */
+ yppush_exit(0);
+
+ /* Just in case. */
+ exit(0);
+}