xref: /illumos-gate/usr/src/cmd/fs.d/nfs/rquotad/rpc.rquotad.c (revision a38ddfee9c8c6b6c5a2947ff52fd2338362a4444)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <signal.h>
35 #include <syslog.h>
36 #include <string.h>
37 #include <stropts.h>
38 #include <errno.h>
39 #include <sys/netconfig.h>
40 #include <sys/mntent.h>
41 #include <sys/mnttab.h>
42 #include <sys/param.h>
43 #include <sys/time.h>
44 #ifdef notdef
45 #include <netconfig.h>
46 #endif
47 #include <sys/stat.h>
48 #include <sys/file.h>
49 #include <sys/fs/ufs_quota.h>
50 #include <netdir.h>
51 #include <rpc/rpc.h>
52 #include <rpcsvc/rquota.h>
53 #include <tiuser.h>
54 #include <unistd.h>
55 
56 #define	QFNAME		"quotas"	/* name of quota file */
57 #define	RPCSVC_CLOSEDOWN 120		/* 2 minutes */
58 
59 struct fsquot {
60 	struct fsquot *fsq_next;
61 	char *fsq_dir;
62 	char *fsq_devname;
63 	dev_t fsq_dev;
64 };
65 
66 struct fsquot *fsqlist = NULL;
67 
68 typedef struct authunix_parms *authp;
69 
70 static int request_pending;		/* Request in progress ? */
71 
72 void closedown();
73 void dispatch();
74 struct fsquot *findfsq();
75 void freefs();
76 int  getdiskquota();
77 void getquota();
78 int  hasquota();
79 void log_cant_reply();
80 void setupfs();
81 
82 /*ARGSUSED*/
83 int
84 main(int argc, char *argv[])
85 {
86 	register SVCXPRT *transp;
87 
88 	/*
89 	 * If stdin looks like a TLI endpoint, we assume
90 	 * that we were started by a port monitor. If
91 	 * t_getstate fails with TBADF, this is not a
92 	 * TLI endpoint.
93 	 */
94 	if (t_getstate(0) != -1 || t_errno != TBADF) {
95 		char *netid;
96 		struct netconfig *nconf = NULL;
97 
98 		openlog("rquotad", LOG_PID, LOG_DAEMON);
99 
100 		if ((netid = getenv("NLSPROVIDER")) == NULL) {
101 			struct t_info tinfo;
102 
103 			if (t_sync(0) == -1) {
104 				syslog(LOG_ERR, "could not do t_sync");
105 				exit(1);
106 			}
107 			if (t_getinfo(0, &tinfo) == -1) {
108 				syslog(LOG_ERR, "t_getinfo failed");
109 				exit(1);
110 			}
111 			if (tinfo.servtype == T_CLTS) {
112 				if (tinfo.addr == INET_ADDRSTRLEN)
113 					netid = "udp";
114 				else
115 					netid = "udp6";
116 			} else {
117 				syslog(LOG_ERR, "wrong transport");
118 				exit(1);
119 			}
120 		}
121 		if ((nconf = getnetconfigent(netid)) == NULL) {
122 			syslog(LOG_ERR, "cannot get transport info");
123 		}
124 
125 		if ((transp = svc_tli_create(0, nconf, NULL, 0, 0)) == NULL) {
126 			syslog(LOG_ERR, "cannot create server handle");
127 			exit(1);
128 		}
129 		if (nconf)
130 			freenetconfigent(nconf);
131 
132 		if (!svc_reg(transp, RQUOTAPROG, RQUOTAVERS, dispatch, 0)) {
133 			syslog(LOG_ERR,
134 				"unable to register (RQUOTAPROG, RQUOTAVERS).");
135 			exit(1);
136 		}
137 
138 		(void) sigset(SIGALRM, (void(*)(int)) closedown);
139 		(void) alarm(RPCSVC_CLOSEDOWN);
140 
141 		svc_run();
142 		exit(1);
143 		/* NOTREACHED */
144 	}
145 
146 	/*
147 	 * Started from a shell - fork the daemon.
148 	 */
149 
150 	switch (fork()) {
151 	case 0:		/* child */
152 		break;
153 	case -1:
154 		perror("rquotad: can't fork");
155 		exit(1);
156 	default:	/* parent */
157 		exit(0);
158 	}
159 
160 	/*
161 	 * Close existing file descriptors, open "/dev/null" as
162 	 * standard input, output, and error, and detach from
163 	 * controlling terminal.
164 	 */
165 	closefrom(0);
166 	(void) open("/dev/null", O_RDONLY);
167 	(void) open("/dev/null", O_WRONLY);
168 	(void) dup(1);
169 	(void) setsid();
170 
171 	openlog("rquotad", LOG_PID, LOG_DAEMON);
172 
173 	/*
174 	 * Create datagram service
175 	 */
176 	if (svc_create(dispatch, RQUOTAPROG, RQUOTAVERS, "datagram_v") == 0) {
177 		syslog(LOG_ERR, "couldn't register datagram_v service");
178 		exit(1);
179 	}
180 
181 	/*
182 	 * Start serving
183 	 */
184 	svc_run();
185 	syslog(LOG_ERR, "Error: svc_run shouldn't have returned");
186 	return (1);
187 }
188 
189 void
190 dispatch(rqstp, transp)
191 	register struct svc_req *rqstp;
192 	register SVCXPRT *transp;
193 {
194 
195 	request_pending = 1;
196 
197 	switch (rqstp->rq_proc) {
198 	case NULLPROC:
199 		errno = 0;
200 		if (!svc_sendreply(transp, xdr_void, 0))
201 			log_cant_reply(transp);
202 		break;
203 
204 	case RQUOTAPROC_GETQUOTA:
205 	case RQUOTAPROC_GETACTIVEQUOTA:
206 		getquota(rqstp, transp);
207 		break;
208 
209 	default:
210 		svcerr_noproc(transp);
211 		break;
212 	}
213 
214 	request_pending = 0;
215 }
216 
217 void
218 closedown()
219 {
220 	if (!request_pending) {
221 		int i, openfd;
222 		struct t_info tinfo;
223 
224 		if (!t_getinfo(0, &tinfo) && (tinfo.servtype == T_CLTS))
225 			exit(0);
226 
227 		for (i = 0, openfd = 0; i < svc_max_pollfd && openfd < 2; i++) {
228 			if (svc_pollfd[i].fd >= 0)
229 				openfd++;
230 		}
231 
232 		if (openfd <= 1)
233 			exit(0);
234 	}
235 	(void) alarm(RPCSVC_CLOSEDOWN);
236 }
237 
238 void
239 getquota(rqstp, transp)
240 	register struct svc_req *rqstp;
241 	register SVCXPRT *transp;
242 {
243 	struct getquota_args gqa;
244 	struct getquota_rslt gqr;
245 	struct dqblk dqblk;
246 	struct fsquot *fsqp;
247 	struct timeval tv;
248 	bool_t qactive;
249 
250 	gqa.gqa_pathp = NULL;		/* let xdr allocate the storage */
251 	if (!svc_getargs(transp, xdr_getquota_args, (caddr_t)&gqa)) {
252 		svcerr_decode(transp);
253 		return;
254 	}
255 	/*
256 	 * This authentication is really bogus with the current rpc
257 	 * authentication scheme. One day we will have something for real.
258 	 */
259 	if (rqstp->rq_cred.oa_flavor != AUTH_UNIX ||
260 	    (((authp) rqstp->rq_clntcred)->aup_uid != 0 &&
261 		((authp) rqstp->rq_clntcred)->aup_uid != (uid_t)gqa.gqa_uid)) {
262 		gqr.status = Q_EPERM;
263 		goto sendreply;
264 	}
265 	fsqp = findfsq(gqa.gqa_pathp);
266 	if (fsqp == NULL) {
267 		gqr.status = Q_NOQUOTA;
268 		goto sendreply;
269 	}
270 
271 	if (quotactl(Q_GETQUOTA, fsqp->fsq_dir, (uid_t)gqa.gqa_uid, &dqblk) !=
272 	    0) {
273 		qactive = FALSE;
274 		if ((errno == ENOENT) ||
275 			(rqstp->rq_proc != RQUOTAPROC_GETQUOTA)) {
276 			gqr.status = Q_NOQUOTA;
277 			goto sendreply;
278 		}
279 
280 		/*
281 		 * If there is no quotas file, don't bother to sync it.
282 		 */
283 		if (errno != ENOENT) {
284 			if (quotactl(Q_ALLSYNC, fsqp->fsq_dir,
285 			    (uid_t)gqa.gqa_uid, &dqblk) < 0 &&
286 				errno == EINVAL)
287 				syslog(LOG_WARNING,
288 				    "Quotas are not compiled into this kernel");
289 			if (getdiskquota(fsqp, (uid_t)gqa.gqa_uid, &dqblk) ==
290 			    0) {
291 				gqr.status = Q_NOQUOTA;
292 				goto sendreply;
293 			}
294 		}
295 	} else {
296 		qactive = TRUE;
297 	}
298 	/*
299 	 * We send the remaining time instead of the absolute time
300 	 * because clock skew between machines should be much greater
301 	 * than rpc delay.
302 	 */
303 #define	gqrslt getquota_rslt_u.gqr_rquota
304 
305 	gettimeofday(&tv, NULL);
306 	gqr.status = Q_OK;
307 	gqr.gqrslt.rq_active	= qactive;
308 	gqr.gqrslt.rq_bsize	= DEV_BSIZE;
309 	gqr.gqrslt.rq_bhardlimit = dqblk.dqb_bhardlimit;
310 	gqr.gqrslt.rq_bsoftlimit = dqblk.dqb_bsoftlimit;
311 	gqr.gqrslt.rq_curblocks = dqblk.dqb_curblocks;
312 	gqr.gqrslt.rq_fhardlimit = dqblk.dqb_fhardlimit;
313 	gqr.gqrslt.rq_fsoftlimit = dqblk.dqb_fsoftlimit;
314 	gqr.gqrslt.rq_curfiles	= dqblk.dqb_curfiles;
315 	gqr.gqrslt.rq_btimeleft	= dqblk.dqb_btimelimit - tv.tv_sec;
316 	gqr.gqrslt.rq_ftimeleft	= dqblk.dqb_ftimelimit - tv.tv_sec;
317 sendreply:
318 	errno = 0;
319 	if (!svc_sendreply(transp, xdr_getquota_rslt, (caddr_t)&gqr))
320 		log_cant_reply(transp);
321 }
322 
323 int
324 quotactl(cmd, mountp, uid, dqp)
325 	int	cmd;
326 	char	*mountp;
327 	uid_t	uid;
328 	struct dqblk *dqp;
329 {
330 	int 		fd;
331 	int 		status;
332 	struct quotctl 	quota;
333 	char		mountpoint[256];
334 	FILE		*fstab;
335 	struct mnttab	mntp;
336 
337 	if ((mountp == NULL) && (cmd == Q_ALLSYNC)) {
338 		/*
339 		 * Find the mount point of any ufs file system. this is
340 		 * because the ioctl that implements the quotactl call has
341 		 * to go to a real file, and not to the block device.
342 		 */
343 		if ((fstab = fopen(MNTTAB, "r")) == NULL) {
344 			syslog(LOG_ERR, "can not open %s: %m ", MNTTAB);
345 			return (-1);
346 		}
347 		fd = -1;
348 		while ((status = getmntent(fstab, &mntp)) == NULL) {
349 			if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
350 				!(hasmntopt(&mntp, MNTOPT_RQ) ||
351 				hasmntopt(&mntp, MNTOPT_QUOTA)))
352 				continue;
353 			(void) strcpy(mountpoint, mntp.mnt_mountp);
354 			strcat(mountpoint, "/quotas");
355 			if ((fd = open64(mountpoint, O_RDWR)) >= 0)
356 				break;
357 		}
358 		fclose(fstab);
359 		if (fd == -1) {
360 			errno = ENOENT;
361 			return (-1);
362 		}
363 	} else {
364 		if (mountp == NULL || mountp[0] == '\0') {
365 			errno = ENOENT;
366 			return (-1);
367 		}
368 		(void) strcpy(mountpoint, mountp);
369 		strcat(mountpoint, "/quotas");
370 
371 		if ((fd = open64(mountpoint, O_RDONLY)) < 0) {
372 			errno = ENOENT;
373 			syslog(LOG_ERR, "can not open %s: %m ", mountpoint);
374 			return (-1);
375 		}
376 	}
377 	quota.op = cmd;
378 	quota.uid = uid;
379 	quota.addr = (caddr_t)dqp;
380 
381 	status = ioctl(fd, Q_QUOTACTL, &quota);
382 
383 	close(fd);
384 	return (status);
385 }
386 
387 /*
388  * Return the quota information for the given path.  Returns NULL if none
389  * was found.
390  */
391 
392 struct fsquot *
393 findfsq(dir)
394 	char *dir;
395 {
396 	struct stat sb;
397 	register struct fsquot *fsqp;
398 	static time_t lastmtime = 0; 	/* mount table's previous mtime */
399 
400 	/*
401 	 * If we've never looked at the mount table, or it has changed
402 	 * since the last time, rebuild the list of quota'd file systems
403 	 * and remember the current mod time for the mount table.
404 	 */
405 
406 	if (stat(MNTTAB, &sb) < 0) {
407 		syslog(LOG_ERR, "can't stat %s: %m", MNTTAB);
408 		return (NULL);
409 	}
410 	if (lastmtime == 0 || sb.st_mtime != lastmtime) {
411 		freefs();
412 		setupfs();
413 		lastmtime = sb.st_mtime;
414 	}
415 
416 	/*
417 	 * Try to find the given path in the list of file systems with
418 	 * quotas.
419 	 */
420 
421 	if (fsqlist == NULL)
422 		return (NULL);
423 	if (stat(dir, &sb) < 0)
424 		return (NULL);
425 	for (fsqp = fsqlist; fsqp != NULL; fsqp = fsqp->fsq_next) {
426 		if (sb.st_dev == fsqp->fsq_dev)
427 			return (fsqp);
428 	}
429 	return (NULL);
430 }
431 
432 void
433 setupfs()
434 {
435 	register struct fsquot *fsqp;
436 	FILE *mt;
437 	struct mnttab m;
438 	struct stat sb;
439 	char qfilename[MAXPATHLEN];
440 
441 	mt = fopen(MNTTAB, "r");
442 	if (mt == NULL) {
443 		syslog(LOG_ERR, "can't read %s: %m", MNTTAB);
444 		return;
445 	}
446 
447 	while (getmntent(mt, &m) == 0) {
448 		if (strcmp(m.mnt_fstype, MNTTYPE_UFS) != 0)
449 			continue;
450 		if (!hasquota(m.mnt_mntopts)) {
451 			sprintf(qfilename, "%s/%s", m.mnt_mountp, QFNAME);
452 			if (access(qfilename, F_OK) < 0)
453 				continue;
454 		}
455 		if (stat(m.mnt_special, &sb) < 0 ||
456 		    (sb.st_mode & S_IFMT) != S_IFBLK)
457 			continue;
458 		fsqp = (struct fsquot *)malloc(sizeof (struct fsquot));
459 		if (fsqp == NULL) {
460 			syslog(LOG_ERR, "out of memory");
461 			exit(1);
462 		}
463 		fsqp->fsq_next = fsqlist;
464 		fsqp->fsq_dir = (char *)malloc(strlen(m.mnt_mountp) + 1);
465 		fsqp->fsq_devname = (char *)malloc(strlen(m.mnt_special) + 1);
466 		if (fsqp->fsq_dir == NULL || fsqp->fsq_devname == NULL) {
467 			syslog(LOG_ERR, "out of memory");
468 			exit(1);
469 		}
470 		strcpy(fsqp->fsq_dir, m.mnt_mountp);
471 		strcpy(fsqp->fsq_devname, m.mnt_special);
472 		fsqp->fsq_dev = sb.st_rdev;
473 		fsqlist = fsqp;
474 	}
475 	(void) fclose(mt);
476 }
477 
478 /*
479  * Free the memory used by the current list of quota'd file systems.  Nulls
480  * out the list.
481  */
482 
483 void
484 freefs()
485 {
486 	register struct fsquot *fsqp;
487 
488 	while ((fsqp = fsqlist) != NULL) {
489 		fsqlist = fsqp->fsq_next;
490 		free(fsqp->fsq_dir);
491 		free(fsqp->fsq_devname);
492 		free(fsqp);
493 	}
494 }
495 
496 int
497 getdiskquota(fsqp, uid, dqp)
498 	struct fsquot *fsqp;
499 	uid_t uid;
500 	struct dqblk *dqp;
501 {
502 	int fd;
503 	char qfilename[MAXPATHLEN];
504 
505 	sprintf(qfilename, "%s/%s", fsqp->fsq_dir, QFNAME);
506 	if ((fd = open64(qfilename, O_RDONLY)) < 0)
507 		return (0);
508 	(void) llseek(fd, (offset_t)dqoff(uid), L_SET);
509 	if (read(fd, dqp, sizeof (struct dqblk)) != sizeof (struct dqblk)) {
510 		close(fd);
511 		return (0);
512 	}
513 	close(fd);
514 	if (dqp->dqb_bhardlimit == 0 && dqp->dqb_bsoftlimit == 0 &&
515 	    dqp->dqb_fhardlimit == 0 && dqp->dqb_fsoftlimit == 0) {
516 		return (0);
517 	}
518 	return (1);
519 }
520 
521 /*
522  * Get the client's hostname from the transport handle
523  * If the name is not available then return "(anon)".
524  */
525 struct nd_hostservlist *
526 getclientsnames(transp)
527 	SVCXPRT *transp;
528 {
529 	struct netbuf *nbuf;
530 	struct netconfig *nconf;
531 	static struct nd_hostservlist	*serv;
532 	static struct nd_hostservlist	anon_hsl;
533 	static struct nd_hostserv	anon_hs;
534 	static char anon_hname[] = "(anon)";
535 	static char anon_sname[] = "";
536 
537 	/* Set up anonymous client */
538 	anon_hs.h_host = anon_hname;
539 	anon_hs.h_serv = anon_sname;
540 	anon_hsl.h_cnt = 1;
541 	anon_hsl.h_hostservs = &anon_hs;
542 
543 	if (serv) {
544 		netdir_free((char *)serv, ND_HOSTSERVLIST);
545 		serv = NULL;
546 	}
547 	nconf = getnetconfigent(transp->xp_netid);
548 	if (nconf == NULL) {
549 		syslog(LOG_ERR, "%s: getnetconfigent failed",
550 			transp->xp_netid);
551 		return (&anon_hsl);
552 	}
553 
554 	nbuf = svc_getrpccaller(transp);
555 	if (nbuf == NULL) {
556 		freenetconfigent(nconf);
557 		return (&anon_hsl);
558 	}
559 	if (netdir_getbyaddr(nconf, &serv, nbuf)) {
560 		freenetconfigent(nconf);
561 		return (&anon_hsl);
562 	}
563 	freenetconfigent(nconf);
564 	return (serv);
565 }
566 
567 void
568 log_cant_reply(transp)
569 	SVCXPRT *transp;
570 {
571 	int saverrno;
572 	struct nd_hostservlist *clnames;
573 	register char *name;
574 
575 	saverrno = errno;	/* save error code */
576 	clnames = getclientsnames(transp);
577 	if (clnames == NULL)
578 		return;
579 	name = clnames->h_hostservs->h_host;
580 
581 	errno = saverrno;
582 	if (errno == 0)
583 		syslog(LOG_ERR, "couldn't send reply to %s", name);
584 	else
585 		syslog(LOG_ERR, "couldn't send reply to %s: %m", name);
586 }
587 
588 char *mntopts[] = { MNTOPT_QUOTA, NULL };
589 #define	QUOTA    0
590 
591 /*
592  * Return 1 if "quota" appears in the options string
593  */
594 int
595 hasquota(opts)
596 	char *opts;
597 {
598 	char *value;
599 
600 	if (opts == NULL)
601 		return (0);
602 	while (*opts != '\0') {
603 		if (getsubopt(&opts, mntopts, &value) == QUOTA)
604 			return (1);
605 	}
606 
607 	return (0);
608 }
609