1/*
2 * by Manuel Bouyer (bouyer@ensta.fr)
3 *
4 * There is no copyright, you can use it as you want.
5 */
6
7#include <sys/cdefs.h>
8__FBSDID("$FreeBSD: stable/11/libexec/rpc.rquotad/rquotad.c 339008 2018-09-29 00:44:23Z sef $");
9
10#include <sys/param.h>
11#include <sys/mount.h>
12#include <sys/file.h>
13#include <sys/stat.h>
14#include <sys/socket.h>
15
16#include <ufs/ufs/quota.h>
17#include <rpc/rpc.h>
18#include <rpcsvc/rquota.h>
19#include <arpa/inet.h>
20#include <netdb.h>
21
22#include <ctype.h>
23#include <errno.h>
24#include <fstab.h>
25#include <grp.h>
26#include <libutil.h>
27#include <pwd.h>
28#include <signal.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <err.h>
32#include <string.h>
33#include <syslog.h>
34#include <unistd.h>
35
36static void rquota_service_1(struct svc_req *request, SVCXPRT *transp);
37static void rquota_service_2(struct svc_req *request, SVCXPRT *transp);
38static void sendquota(struct svc_req *request, SVCXPRT *transp);
39static void sendquota_extended(struct svc_req *request, SVCXPRT *transp);
40static int getfsquota(int type, long id, char *path, struct dqblk *dqblk);
41
42static int from_inetd = 1;
43static int debug = 0;
44
45static void
46cleanup(int sig)
47{
48
49	(void)sig;
50	(void)rpcb_unset(RQUOTAPROG, RQUOTAVERS, NULL);
51	exit(0);
52}
53
54int
55main(int argc, char **argv)
56{
57	SVCXPRT *transp;
58	int ok;
59	struct sockaddr_storage from;
60	socklen_t fromlen;
61	int vers;
62	int ch;
63
64	while ((ch = getopt(argc, argv, "d")) != -1) {
65		switch (ch) {
66		case 'd':
67			debug++;
68			break;
69		default:
70			break;
71		}
72	}
73
74	fromlen = sizeof(from);
75	if (getsockname(0, (struct sockaddr *)&from, &fromlen) < 0)
76		from_inetd = 0;
77
78	if (!from_inetd) {
79		if (!debug)
80			daemon(0, 0);
81		(void)rpcb_unset(RQUOTAPROG, RQUOTAVERS, NULL);
82		(void)signal(SIGINT, cleanup);
83		(void)signal(SIGTERM, cleanup);
84		(void)signal(SIGHUP, cleanup);
85	}
86
87	openlog("rpc.rquotad", LOG_CONS|LOG_PID, LOG_DAEMON);
88
89	/* create and register the service */
90	if (from_inetd) {
91		transp = svc_tli_create(0, NULL, NULL, 0, 0);
92		if (transp == NULL) {
93			syslog(LOG_ERR, "couldn't create udp service.");
94			exit(1);
95		}
96		vers = RQUOTAVERS;
97		ok = svc_reg(transp, RQUOTAPROG, RQUOTAVERS,
98		    rquota_service_1, NULL);
99		if (ok) {
100			vers = EXT_RQUOTAVERS;
101			ok = svc_reg(transp, RQUOTAPROG, EXT_RQUOTAVERS,
102				     rquota_service_2, NULL);
103		}
104	} else {
105		vers = RQUOTAVERS;
106		ok = svc_create(rquota_service_1,
107		    RQUOTAPROG, RQUOTAVERS, "udp");
108		if (ok) {
109			vers = EXT_RQUOTAVERS;
110			ok = svc_create(rquota_service_2,
111					RQUOTAPROG, EXT_RQUOTAVERS, "udp");
112
113		}
114	}
115	if (!ok) {
116		syslog(LOG_ERR,
117		    "unable to register (RQUOTAPROG, %s, %s)",
118		       vers == RQUOTAVERS ? "RQUOTAVERS" : "EXT_RQUOTAVERS",
119		       from_inetd ? "(inetd)" : "udp");
120		exit(1);
121	}
122
123	svc_run();
124	syslog(LOG_ERR, "svc_run returned");
125	exit(1);
126}
127
128static void
129rquota_service_2(struct svc_req *request, SVCXPRT *transp)
130{
131
132	switch (request->rq_proc) {
133	case NULLPROC:
134		(void)svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL);
135		break;
136	case RQUOTAPROC_GETQUOTA:
137	case RQUOTAPROC_GETACTIVEQUOTA:
138		sendquota_extended(request, transp);
139		break;
140	default:
141		svcerr_noproc(transp);
142		break;
143	}
144	if (from_inetd)
145		exit(0);
146}
147
148static void
149rquota_service_1(struct svc_req *request, SVCXPRT *transp)
150{
151
152	switch (request->rq_proc) {
153	case NULLPROC:
154		(void)svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL);
155		break;
156	case RQUOTAPROC_GETQUOTA:
157	case RQUOTAPROC_GETACTIVEQUOTA:
158		sendquota(request, transp);
159		break;
160	default:
161		svcerr_noproc(transp);
162		break;
163	}
164	if (from_inetd)
165		exit(0);
166}
167
168/* read quota for the specified id, and send it */
169static void
170sendquota(struct svc_req *request, SVCXPRT *transp)
171{
172	struct getquota_args getq_args;
173	struct getquota_rslt getq_rslt;
174	struct dqblk dqblk;
175	struct timeval timev;
176	int scale;
177
178	bzero(&getq_args, sizeof(getq_args));
179	if (!svc_getargs(transp, (xdrproc_t)xdr_getquota_args, &getq_args)) {
180		svcerr_decode(transp);
181		return;
182	}
183	if (request->rq_cred.oa_flavor != AUTH_UNIX) {
184		/* bad auth */
185		getq_rslt.status = Q_EPERM;
186	} else if (!getfsquota(USRQUOTA, getq_args.gqa_uid, getq_args.gqa_pathp, &dqblk)) {
187		/* failed, return noquota */
188		getq_rslt.status = Q_NOQUOTA;
189	} else {
190		gettimeofday(&timev, NULL);
191		getq_rslt.status = Q_OK;
192		getq_rslt.getquota_rslt_u.gqr_rquota.rq_active = TRUE;
193		scale = 1 << flsll(dqblk.dqb_bhardlimit >> 32);
194		getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize =
195		    DEV_BSIZE * scale;
196		getq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit =
197		    dqblk.dqb_bhardlimit / scale;
198		getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit =
199		    dqblk.dqb_bsoftlimit / scale;
200		getq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks =
201		    dqblk.dqb_curblocks / scale;
202		getq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit =
203		    dqblk.dqb_ihardlimit;
204		getq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit =
205		    dqblk.dqb_isoftlimit;
206		getq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles =
207		    dqblk.dqb_curinodes;
208		getq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft =
209		    dqblk.dqb_btime - timev.tv_sec;
210		getq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft =
211		    dqblk.dqb_itime - timev.tv_sec;
212	}
213	if (!svc_sendreply(transp, (xdrproc_t)xdr_getquota_rslt, &getq_rslt))
214		svcerr_systemerr(transp);
215	if (!svc_freeargs(transp, (xdrproc_t)xdr_getquota_args, &getq_args)) {
216		syslog(LOG_ERR, "unable to free arguments");
217		exit(1);
218	}
219}
220
221static void
222sendquota_extended(struct svc_req *request, SVCXPRT *transp)
223{
224	struct ext_getquota_args getq_args;
225	struct getquota_rslt getq_rslt;
226	struct dqblk dqblk;
227	struct timeval timev;
228	int scale;
229
230	bzero(&getq_args, sizeof(getq_args));
231	if (!svc_getargs(transp, (xdrproc_t)xdr_ext_getquota_args, &getq_args)) {
232		svcerr_decode(transp);
233		return;
234	}
235	if (request->rq_cred.oa_flavor != AUTH_UNIX) {
236		/* bad auth */
237		getq_rslt.status = Q_EPERM;
238	} else if (!getfsquota(getq_args.gqa_type, getq_args.gqa_id, getq_args.gqa_pathp, &dqblk)) {
239		/* failed, return noquota */
240		getq_rslt.status = Q_NOQUOTA;
241	} else {
242		gettimeofday(&timev, NULL);
243		getq_rslt.status = Q_OK;
244		getq_rslt.getquota_rslt_u.gqr_rquota.rq_active = TRUE;
245		scale = 1 << flsll(dqblk.dqb_bhardlimit >> 32);
246		getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize =
247		    DEV_BSIZE * scale;
248		getq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit =
249		    dqblk.dqb_bhardlimit / scale;
250		getq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit =
251		    dqblk.dqb_bsoftlimit / scale;
252		getq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks =
253		    dqblk.dqb_curblocks / scale;
254		getq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit =
255		    dqblk.dqb_ihardlimit;
256		getq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit =
257		    dqblk.dqb_isoftlimit;
258		getq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles =
259		    dqblk.dqb_curinodes;
260		getq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft =
261		    dqblk.dqb_btime - timev.tv_sec;
262		getq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft =
263		    dqblk.dqb_itime - timev.tv_sec;
264	}
265	if (!svc_sendreply(transp, (xdrproc_t)xdr_getquota_rslt, &getq_rslt))
266		svcerr_systemerr(transp);
267	if (!svc_freeargs(transp, (xdrproc_t)xdr_getquota_args, &getq_args)) {
268		syslog(LOG_ERR, "unable to free arguments");
269		exit(1);
270	}
271}
272
273/*
274 * gets the quotas for id, filesystem path.
275 * Return 0 if fail, 1 otherwise
276 */
277static int
278getfsquota(int type, long id, char *path, struct dqblk *dqblk)
279{
280	struct quotafile *qf;
281	/*
282	 * Remote quota checking is limited to mounted filesystems.
283	 * Since UFS and ZFS support the quota system calls, we
284	 * only need to make an fstab object that has the path, and
285	 * a blank name for the filesystem type.
286	 * This allows the quota_open() call to work the way we
287	 * expect it to.
288	 *
289	 * The static char declaration is because compiler warnings
290	 * don't allow passing a const char * to a char *.
291	 */
292	int rv;
293	static char blank[] = "";
294	struct fstab fst;
295
296	fst.fs_file = path;
297	fst.fs_mntops = blank;
298	fst.fs_vfstype = blank;
299
300	if (type != USRQUOTA && type != GRPQUOTA)
301		return (0);
302
303	qf = quota_open(&fst, type, O_RDONLY);
304	if (debug)
305		warnx("quota_open(<%s, %s>, %d) returned %p",
306		      fst.fs_file, fst.fs_mntops, type,
307		      qf);
308	if (qf == NULL)
309		return (0);
310
311	rv = quota_read(qf, dqblk, id) == 0;
312	quota_close(qf);
313	if (debug)
314		warnx("getfsquota(%d, %ld, %s, %p) -> %d",
315		      type, id, path, dqblk, rv);
316	return (rv);
317}
318