xref: /freebsd/usr.bin/quota/quota.c (revision 5ca8e32633c4ffbbcd6762e5888b6a4ba0708c6c)
1 /*
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1980, 1990, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Robert Elz at The University of Melbourne.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 /*
36  * Disk quota reporting program.
37  */
38 
39 #include <sys/param.h>
40 #include <sys/types.h>
41 #include <sys/file.h>
42 #include <sys/stat.h>
43 #include <sys/mount.h>
44 #include <sys/socket.h>
45 
46 #include <rpc/rpc.h>
47 #include <rpc/pmap_prot.h>
48 #include <rpcsvc/rquota.h>
49 
50 #include <ufs/ufs/quota.h>
51 
52 #include <ctype.h>
53 #include <err.h>
54 #include <fstab.h>
55 #include <grp.h>
56 #include <libutil.h>
57 #include <netdb.h>
58 #include <pwd.h>
59 #include <stdio.h>
60 #include <stdint.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <time.h>
64 #include <unistd.h>
65 
66 static const char *qfextension[] = INITQFNAMES;
67 
68 struct quotause {
69 	struct	quotause *next;
70 	long	flags;
71 	struct	dqblk dqblk;
72 	char	fsname[MAXPATHLEN + 1];
73 };
74 
75 static char *timeprt(int64_t seconds);
76 static struct quotause *getprivs(long id, int quotatype);
77 static void usage(void) __dead2;
78 static int showuid(u_long uid);
79 static int showgid(u_long gid);
80 static int showusrname(char *name);
81 static int showgrpname(char *name);
82 static int showquotas(int type, u_long id, const char *name);
83 static void showrawquotas(int type, u_long id, struct quotause *qup);
84 static void heading(int type, u_long id, const char *name, const char *tag);
85 static int getufsquota(struct fstab *fs, struct quotause *qup, long id,
86 	int quotatype);
87 static int getnfsquota(struct statfs *fst, struct quotause *qup, long id,
88 	int quotatype);
89 static enum clnt_stat callaurpc(char *host, int prognum, int versnum, int procnum,
90 	xdrproc_t inproc, char *in, xdrproc_t outproc, char *out);
91 static int alldigits(char *s);
92 
93 static int	hflag;
94 static int	lflag;
95 static int	rflag;
96 static int	qflag;
97 static int	vflag;
98 static char	*filename = NULL;
99 
100 int
101 main(int argc, char *argv[])
102 {
103 	int ngroups;
104 	gid_t mygid, gidset[NGROUPS];
105 	int i, ch, gflag = 0, uflag = 0, errflag = 0;
106 
107 	while ((ch = getopt(argc, argv, "f:ghlrquv")) != -1) {
108 		switch(ch) {
109 		case 'f':
110 			filename = optarg;
111 			break;
112 		case 'g':
113 			gflag++;
114 			break;
115 		case 'h':
116 			hflag++;
117 			break;
118 		case 'l':
119 			lflag++;
120 			break;
121 		case 'q':
122 			qflag++;
123 			break;
124 		case 'r':
125 			rflag++;
126 			break;
127 		case 'u':
128 			uflag++;
129 			break;
130 		case 'v':
131 			vflag++;
132 			break;
133 		default:
134 			usage();
135 		}
136 	}
137 	argc -= optind;
138 	argv += optind;
139 	if (!uflag && !gflag)
140 		uflag++;
141 	if (argc == 0) {
142 		if (uflag)
143 			errflag += showuid(getuid());
144 		if (gflag) {
145 			mygid = getgid();
146 			ngroups = getgroups(NGROUPS, gidset);
147 			if (ngroups < 0)
148 				err(1, "getgroups");
149 			errflag += showgid(mygid);
150 			for (i = 0; i < ngroups; i++)
151 				if (gidset[i] != mygid)
152 					errflag += showgid(gidset[i]);
153 		}
154 		return(errflag);
155 	}
156 	if (uflag && gflag)
157 		usage();
158 	if (uflag) {
159 		for (; argc > 0; argc--, argv++) {
160 			if (alldigits(*argv))
161 				errflag += showuid(atoi(*argv));
162 			else
163 				errflag += showusrname(*argv);
164 		}
165 		return(errflag);
166 	}
167 	if (gflag) {
168 		for (; argc > 0; argc--, argv++) {
169 			if (alldigits(*argv))
170 				errflag += showgid(atoi(*argv));
171 			else
172 				errflag += showgrpname(*argv);
173 		}
174 	}
175 	return(errflag);
176 }
177 
178 static void
179 usage(void)
180 {
181 
182 	fprintf(stderr, "%s\n%s\n%s\n",
183 	    "usage: quota [-ghlu] [-f path] [-v | -q | -r]",
184 	    "       quota [-hlu] [-f path] [-v | -q | -r] user ...",
185 	    "       quota -g [-hl] [-f path] [-v | -q | -r] group ...");
186 	exit(1);
187 }
188 
189 /*
190  * Print out quotas for a specified user identifier.
191  */
192 static int
193 showuid(u_long uid)
194 {
195 	struct passwd *pwd = getpwuid(uid);
196 	const char *name;
197 
198 	if (pwd == NULL)
199 		name = "(no account)";
200 	else
201 		name = pwd->pw_name;
202 	return(showquotas(USRQUOTA, uid, name));
203 }
204 
205 /*
206  * Print out quotas for a specified user name.
207  */
208 static int
209 showusrname(char *name)
210 {
211 	struct passwd *pwd = getpwnam(name);
212 
213 	if (pwd == NULL) {
214 		warnx("%s: unknown user", name);
215 		return(1);
216 	}
217 	return(showquotas(USRQUOTA, pwd->pw_uid, name));
218 }
219 
220 /*
221  * Print out quotas for a specified group identifier.
222  */
223 static int
224 showgid(u_long gid)
225 {
226 	struct group *grp = getgrgid(gid);
227 	const char *name;
228 
229 	if (grp == NULL)
230 		name = "(no entry)";
231 	else
232 		name = grp->gr_name;
233 	return(showquotas(GRPQUOTA, gid, name));
234 }
235 
236 /*
237  * Print out quotas for a specified group name.
238  */
239 static int
240 showgrpname(char *name)
241 {
242 	struct group *grp = getgrnam(name);
243 
244 	if (grp == NULL) {
245 		warnx("%s: unknown group", name);
246 		return(1);
247 	}
248 	return(showquotas(GRPQUOTA, grp->gr_gid, name));
249 }
250 
251 static void
252 prthumanval(int len, u_int64_t bytes)
253 {
254 	char buf[len + 1];
255 
256 	/*
257 	 * Limit the width to 5 bytes as that is what users expect.
258 	 */
259 	humanize_number(buf, MIN(sizeof(buf), 5), bytes, "",
260 			HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
261 
262 	(void)printf(" %*s", len, buf);
263 }
264 
265 static int
266 showquotas(int type, u_long id, const char *name)
267 {
268 	struct quotause *qup;
269 	struct quotause *quplist;
270 	const char *msgi, *msgb;
271 	const char *nam;
272 	char *bgrace = NULL, *igrace = NULL;
273 	int lines = 0, overquota = 0;
274 	static time_t now;
275 
276 	if (now == 0)
277 		time(&now);
278 	quplist = getprivs(id, type);
279 	for (qup = quplist; qup; qup = qup->next) {
280 		msgi = NULL;
281 		if (qup->dqblk.dqb_ihardlimit &&
282 		    qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit) {
283 			overquota++;
284 			msgi = "File limit reached on";
285 		}
286 		else if (qup->dqblk.dqb_isoftlimit &&
287 		    qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit) {
288 			overquota++;
289 			if (qup->dqblk.dqb_itime > now)
290 				msgi = "In file grace period on";
291 			else
292 				msgi = "Over file quota on";
293 		}
294 		msgb = NULL;
295 		if (qup->dqblk.dqb_bhardlimit &&
296 		    qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit) {
297 			overquota++;
298 			msgb = "Block limit reached on";
299 		}
300 		else if (qup->dqblk.dqb_bsoftlimit &&
301 		    qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit) {
302 			overquota++;
303 			if (qup->dqblk.dqb_btime > now)
304 				msgb = "In block grace period on";
305 			else
306 				msgb = "Over block quota on";
307 		}
308 		if (rflag) {
309 			showrawquotas(type, id, qup);
310 			continue;
311 		}
312 		if (!vflag &&
313 		    qup->dqblk.dqb_isoftlimit == 0 &&
314 		    qup->dqblk.dqb_ihardlimit == 0 &&
315 		    qup->dqblk.dqb_bsoftlimit == 0 &&
316 		    qup->dqblk.dqb_bhardlimit == 0)
317 			continue;
318 		if (qflag) {
319 			if ((msgi != NULL || msgb != NULL) &&
320 			    lines++ == 0)
321 				heading(type, id, name, "");
322 			if (msgi != NULL)
323 				printf("\t%s %s\n", msgi, qup->fsname);
324 			if (msgb != NULL)
325 				printf("\t%s %s\n", msgb, qup->fsname);
326 			continue;
327 		}
328 		if (!vflag &&
329 		    qup->dqblk.dqb_curblocks == 0 &&
330 		    qup->dqblk.dqb_curinodes == 0)
331 			continue;
332 		if (lines++ == 0)
333 			heading(type, id, name, "");
334 		nam = qup->fsname;
335 		if (strlen(qup->fsname) > 15) {
336 			printf("%s\n", qup->fsname);
337 			nam = "";
338 		}
339 		printf("%-15s", nam);
340 		if (hflag) {
341 			prthumanval(7, dbtob(qup->dqblk.dqb_curblocks));
342 			printf("%c", (msgb == NULL) ? ' ' : '*');
343 			prthumanval(7, dbtob(qup->dqblk.dqb_bsoftlimit));
344 			prthumanval(7, dbtob(qup->dqblk.dqb_bhardlimit));
345 		} else {
346 			printf(" %7ju%c %7ju %7ju",
347 			    (uintmax_t)dbtob(qup->dqblk.dqb_curblocks)
348 				/ 1024,
349 			    (msgb == NULL) ? ' ' : '*',
350 			    (uintmax_t)dbtob(qup->dqblk.dqb_bsoftlimit)
351 				/ 1024,
352 			    (uintmax_t)dbtob(qup->dqblk.dqb_bhardlimit)
353 				/ 1024);
354 		}
355 		if (msgb != NULL)
356 			bgrace = timeprt(qup->dqblk.dqb_btime);
357 		if (msgi != NULL)
358 			igrace = timeprt(qup->dqblk.dqb_itime);
359 		printf("%8s %6ju%c %6ju %6ju%8s\n"
360 			, (msgb == NULL) ? "" : bgrace
361 			, (uintmax_t)qup->dqblk.dqb_curinodes
362 			, (msgi == NULL) ? ' ' : '*'
363 			, (uintmax_t)qup->dqblk.dqb_isoftlimit
364 			, (uintmax_t)qup->dqblk.dqb_ihardlimit
365 			, (msgi == NULL) ? "" : igrace
366 		);
367 		if (msgb != NULL)
368 			free(bgrace);
369 		if (msgi != NULL)
370 			free(igrace);
371 	}
372 	if (!qflag && !rflag && lines == 0)
373 		heading(type, id, name, "none");
374 	return (overquota);
375 }
376 
377 static void
378 showrawquotas(int type, u_long id, struct quotause *qup)
379 {
380 	time_t t;
381 
382 	printf("Raw %s quota information for id %lu on %s\n",
383 	    type == USRQUOTA ? "user" : "group", id, qup->fsname);
384 	printf("block hard limit:     %ju\n",
385 	    (uintmax_t)qup->dqblk.dqb_bhardlimit);
386 	printf("block soft limit:     %ju\n",
387 	    (uintmax_t)qup->dqblk.dqb_bsoftlimit);
388 	printf("current block count:  %ju\n",
389 	    (uintmax_t)qup->dqblk.dqb_curblocks);
390 	printf("i-node hard limit:    %ju\n",
391 	    (uintmax_t)qup->dqblk.dqb_ihardlimit);
392 	printf("i-node soft limit:    %ju\n",
393 	    (uintmax_t)qup->dqblk.dqb_isoftlimit);
394 	printf("current i-node count: %ju\n",
395 	    (uintmax_t)qup->dqblk.dqb_curinodes);
396 	printf("block grace time:     %jd",
397 	    (intmax_t)qup->dqblk.dqb_btime);
398 	if (qup->dqblk.dqb_btime != 0) {
399 		t = qup->dqblk.dqb_btime;
400 		printf(" %s", ctime(&t));
401 	} else {
402 		printf("\n");
403 	}
404 	printf("i-node grace time:    %jd", (intmax_t)qup->dqblk.dqb_itime);
405 	if (qup->dqblk.dqb_itime != 0) {
406 		t = qup->dqblk.dqb_itime;
407 		printf(" %s", ctime(&t));
408 	} else {
409 		printf("\n");
410 	}
411 }
412 
413 
414 static void
415 heading(int type, u_long id, const char *name, const char *tag)
416 {
417 
418 	printf("Disk quotas for %s %s (%cid %lu): %s\n", qfextension[type],
419 	    name, *qfextension[type], id, tag);
420 	if (!qflag && tag[0] == '\0') {
421 		printf("%-15s %7s %8s %7s %7s %6s %7s %6s%8s\n"
422 			, "Filesystem"
423 			, "usage"
424 			, "quota"
425 			, "limit"
426 			, "grace"
427 			, "files"
428 			, "quota"
429 			, "limit"
430 			, "grace"
431 		);
432 	}
433 }
434 
435 /*
436  * Calculate the grace period and return a printable string for it.
437  */
438 static char *
439 timeprt(int64_t seconds)
440 {
441 	time_t hours, minutes;
442 	char *buf;
443 	static time_t now;
444 
445 	if (now == 0)
446 		time(&now);
447 	if (now > seconds) {
448 		if ((buf = strdup("none")) == NULL)
449 			errx(1, "strdup() failed in timeprt()");
450 		return (buf);
451 	}
452 	seconds -= now;
453 	minutes = (seconds + 30) / 60;
454 	hours = (minutes + 30) / 60;
455 	if (hours >= 36) {
456 		if (asprintf(&buf, "%lddays", ((long)hours + 12) / 24) < 0)
457 			errx(1, "asprintf() failed in timeprt(1)");
458 		return (buf);
459 	}
460 	if (minutes >= 60) {
461 		if (asprintf(&buf, "%2ld:%ld", (long)minutes / 60,
462 		    (long)minutes % 60) < 0)
463 			errx(1, "asprintf() failed in timeprt(2)");
464 		return (buf);
465 	}
466 	if (asprintf(&buf, "%2ld", (long)minutes) < 0)
467 		errx(1, "asprintf() failed in timeprt(3)");
468 	return (buf);
469 }
470 
471 /*
472  * Collect the requested quota information.
473  */
474 static struct quotause *
475 getprivs(long id, int quotatype)
476 {
477 	struct quotause *qup, *quptail = NULL;
478 	struct fstab *fs;
479 	struct quotause *quphead;
480 	struct statfs *fst;
481 	int nfst, i;
482 	struct statfs sfb;
483 
484 	qup = quphead = (struct quotause *)0;
485 
486 	if (filename != NULL && statfs(filename, &sfb) != 0)
487 		err(1, "cannot statfs %s", filename);
488 	nfst = getmntinfo(&fst, MNT_NOWAIT);
489 	if (nfst == 0)
490 		errx(2, "no filesystems mounted!");
491 	setfsent();
492 	for (i = 0; i < nfst; i++) {
493 		if (qup == NULL) {
494 			if ((qup = (struct quotause *)malloc(sizeof *qup))
495 			    == NULL)
496 				errx(2, "out of memory");
497 		}
498 		/*
499 		 * See if the user requested a specific file system
500 		 * or specified a file inside a mounted file system.
501 		 */
502 		if (filename != NULL &&
503 		    strcmp(sfb.f_mntonname, fst[i].f_mntonname) != 0)
504 			continue;
505 		if (strcmp(fst[i].f_fstypename, "nfs") == 0) {
506 			if (lflag)
507 				continue;
508 			if (getnfsquota(&fst[i], qup, id, quotatype) == 0)
509 				continue;
510 		} else if (strcmp(fst[i].f_fstypename, "ufs") == 0) {
511 			/*
512 			 * XXX
513 			 * UFS filesystems must be in /etc/fstab, and must
514 			 * indicate that they have quotas on (?!) This is quite
515 			 * unlike SunOS where quotas can be enabled/disabled
516 			 * on a filesystem independent of /etc/fstab, and it
517 			 * will still print quotas for them.
518 			 */
519 			if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL)
520 				continue;
521 			if (getufsquota(fs, qup, id, quotatype) == 0)
522 				continue;
523 		} else
524 			continue;
525 		strcpy(qup->fsname, fst[i].f_mntonname);
526 		if (quphead == NULL)
527 			quphead = qup;
528 		else
529 			quptail->next = qup;
530 		quptail = qup;
531 		quptail->next = 0;
532 		qup = NULL;
533 	}
534 	if (qup)
535 		free(qup);
536 	endfsent();
537 	return (quphead);
538 }
539 
540 /*
541  * Check to see if a particular quota is available.
542  */
543 static int
544 getufsquota(struct fstab *fs, struct quotause *qup, long id, int quotatype)
545 {
546 	struct quotafile *qf;
547 
548 	if ((qf = quota_open(fs, quotatype, O_RDONLY)) == NULL)
549 		return (0);
550 	if (quota_read(qf, &qup->dqblk, id) != 0)
551 		return (0);
552 	quota_close(qf);
553 	return (1);
554 }
555 
556 static int
557 getnfsquota(struct statfs *fst, struct quotause *qup, long id, int quotatype)
558 {
559 	struct ext_getquota_args gq_args;
560 	struct getquota_args old_gq_args;
561 	struct getquota_rslt gq_rslt;
562 	struct dqblk *dqp = &qup->dqblk;
563 	struct timeval tv;
564 	char *cp, host[NI_MAXHOST];
565 	enum clnt_stat call_stat;
566 
567 	if (fst->f_flags & MNT_LOCAL)
568 		return (0);
569 
570 	/*
571 	 * must be some form of "hostname:/path"
572 	 */
573 	cp = fst->f_mntfromname;
574 	do {
575 		cp = strrchr(cp, ':');
576 	} while (cp != NULL && *(cp + 1) != '/');
577 	if (cp == NULL) {
578 		warnx("cannot find hostname for %s", fst->f_mntfromname);
579 		return (0);
580 	}
581 	memset(host, 0, sizeof(host));
582 	memcpy(host, fst->f_mntfromname, cp - fst->f_mntfromname);
583 	host[sizeof(host) - 1] = '\0';
584 
585 	/* Avoid attempting the RPC for special amd(8) filesystems. */
586 	if (strncmp(fst->f_mntfromname, "pid", 3) == 0 &&
587 	    strchr(fst->f_mntfromname, '@') != NULL)
588 		return (0);
589 
590 	gq_args.gqa_pathp = cp + 1;
591 	gq_args.gqa_id = id;
592 	gq_args.gqa_type = quotatype;
593 
594 	call_stat = callaurpc(host, RQUOTAPROG, EXT_RQUOTAVERS,
595 			      RQUOTAPROC_GETQUOTA, (xdrproc_t)xdr_ext_getquota_args, (char *)&gq_args,
596 			      (xdrproc_t)xdr_getquota_rslt, (char *)&gq_rslt);
597 	if (call_stat == RPC_PROGVERSMISMATCH || call_stat == RPC_PROGNOTREGISTERED) {
598 		if (quotatype == USRQUOTA) {
599 			old_gq_args.gqa_pathp = cp + 1;
600 			old_gq_args.gqa_uid = id;
601 			call_stat = callaurpc(host, RQUOTAPROG, RQUOTAVERS,
602 					      RQUOTAPROC_GETQUOTA, (xdrproc_t)xdr_getquota_args, (char *)&old_gq_args,
603 					      (xdrproc_t)xdr_getquota_rslt, (char *)&gq_rslt);
604 		} else {
605 			/* Old rpc quota does not support group type */
606 			return (0);
607 		}
608 	}
609 	if (call_stat != 0)
610 		return (call_stat);
611 
612 	switch (gq_rslt.status) {
613 	case Q_NOQUOTA:
614 		break;
615 	case Q_EPERM:
616 		warnx("quota permission error, host: %s",
617 			fst->f_mntfromname);
618 		break;
619 	case Q_OK:
620 		gettimeofday(&tv, NULL);
621 			/* blocks*/
622 		dqp->dqb_bhardlimit =
623 		    ((uint64_t)gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
624 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize) / DEV_BSIZE;
625 		dqp->dqb_bsoftlimit =
626 		    ((uint64_t)gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit *
627 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize) / DEV_BSIZE;
628 		dqp->dqb_curblocks =
629 		    ((uint64_t)gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks *
630 		    gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize) / DEV_BSIZE;
631 			/* inodes */
632 		dqp->dqb_ihardlimit =
633 			gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
634 		dqp->dqb_isoftlimit =
635 			gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit;
636 		dqp->dqb_curinodes =
637 			gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles;
638 			/* grace times */
639 		dqp->dqb_btime =
640 		    tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft;
641 		dqp->dqb_itime =
642 		    tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft;
643 		return (1);
644 	default:
645 		warnx("bad rpc result, host: %s", fst->f_mntfromname);
646 		break;
647 	}
648 
649 	return (0);
650 }
651 
652 static enum clnt_stat
653 callaurpc(char *host, int prognum, int versnum, int procnum,
654     xdrproc_t inproc, char *in, xdrproc_t outproc, char *out)
655 {
656 	enum clnt_stat clnt_stat;
657 	struct timeval timeout, tottimeout;
658 
659 	CLIENT *client = NULL;
660 
661  	client = clnt_create(host, prognum, versnum, "udp");
662 	if (client == NULL)
663 		return ((int)rpc_createerr.cf_stat);
664 	timeout.tv_usec = 0;
665 	timeout.tv_sec = 6;
666 	CLNT_CONTROL(client, CLSET_RETRY_TIMEOUT, (char *)(void *)&timeout);
667 
668 	client->cl_auth = authunix_create_default();
669 	tottimeout.tv_sec = 25;
670 	tottimeout.tv_usec = 0;
671 	clnt_stat = clnt_call(client, procnum, inproc, in,
672 	    outproc, out, tottimeout);
673 	return (clnt_stat);
674 }
675 
676 static int
677 alldigits(char *s)
678 {
679 	int c;
680 
681 	c = *s++;
682 	do {
683 		if (!isdigit(c))
684 			return (0);
685 	} while ((c = *s++));
686 	return (1);
687 }
688