1/*-
2 * Copyright (c) 1996 by
3 * Sean Eric Fagan <sef@kithrup.com>
4 * David Nugent <davidn@blaze.net.au>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, is permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice immediately at the beginning of the file, without modification,
12 *    this list of conditions, and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. This work was done expressly for inclusion into FreeBSD.  Other use
17 *    is permitted provided this notation is included.
18 * 4. Absolutely no warranty of function or purpose is made by the authors.
19 * 5. Modifications may be freely made to this file providing the above
20 *    conditions are met.
21 *
22 * High-level routines relating to use of the user capabilities database
23 */
24
25#include <sys/cdefs.h>
26__FBSDID("$FreeBSD$");
27
28#include <sys/param.h>
29#include <sys/cpuset.h>
30#include <sys/mac.h>
31#include <sys/resource.h>
32#include <sys/rtprio.h>
33#include <sys/stat.h>
34#include <sys/time.h>
35
36#include <ctype.h>
37#include <err.h>
38#include <errno.h>
39#include <fcntl.h>
40#include <login_cap.h>
41#include <paths.h>
42#include <pwd.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <syslog.h>
47#include <unistd.h>
48
49
50static struct login_res {
51    const char *what;
52    rlim_t (*who)(login_cap_t *, const char *, rlim_t, rlim_t);
53    int why;
54} resources[] = {
55    { "cputime",         login_getcaptime, RLIMIT_CPU     },
56    { "filesize",        login_getcapsize, RLIMIT_FSIZE   },
57    { "datasize",        login_getcapsize, RLIMIT_DATA    },
58    { "stacksize",       login_getcapsize, RLIMIT_STACK   },
59    { "memoryuse",       login_getcapsize, RLIMIT_RSS     },
60    { "memorylocked",    login_getcapsize, RLIMIT_MEMLOCK },
61    { "maxproc",         login_getcapnum,  RLIMIT_NPROC   },
62    { "openfiles",       login_getcapnum,  RLIMIT_NOFILE  },
63    { "coredumpsize",    login_getcapsize, RLIMIT_CORE    },
64    { "sbsize",          login_getcapsize, RLIMIT_SBSIZE  },
65    { "vmemoryuse",      login_getcapsize, RLIMIT_VMEM    },
66    { "pseudoterminals", login_getcapnum,  RLIMIT_NPTS    },
67    { "swapuse",         login_getcapsize, RLIMIT_SWAP    },
68    { "kqueues",         login_getcapsize, RLIMIT_KQUEUES },
69    { "umtxp",           login_getcapnum,  RLIMIT_UMTXP   },
70    { NULL,              0,                0              }
71};
72
73
74void
75setclassresources(login_cap_t *lc)
76{
77    struct login_res *lr;
78
79    if (lc == NULL)
80	return;
81
82    for (lr = resources; lr->what != NULL; ++lr) {
83	struct rlimit	rlim;
84
85	/*
86	 * The login.conf file can have <limit>, <limit>-max, and
87	 * <limit>-cur entries.
88	 * What we do is get the current current- and maximum- limits.
89	 * Then, we try to get an entry for <limit> from the capability,
90	 * using the current and max limits we just got as the
91	 * default/error values.
92	 * *Then*, we try looking for <limit>-cur and <limit>-max,
93	 * again using the appropriate values as the default/error
94	 * conditions.
95	 */
96
97	if (getrlimit(lr->why, &rlim) != 0)
98	    syslog(LOG_ERR, "getting %s resource limit: %m", lr->what);
99	else {
100	    char	name_cur[40];
101	    char	name_max[40];
102	    rlim_t	rcur = rlim.rlim_cur;
103	    rlim_t	rmax = rlim.rlim_max;
104
105	    sprintf(name_cur, "%s-cur", lr->what);
106	    sprintf(name_max, "%s-max", lr->what);
107
108	    rcur = (*lr->who)(lc, lr->what, rcur, rcur);
109	    rmax = (*lr->who)(lc, lr->what, rmax, rmax);
110	    rlim.rlim_cur = (*lr->who)(lc, name_cur, rcur, rcur);
111	    rlim.rlim_max = (*lr->who)(lc, name_max, rmax, rmax);
112
113	    if (setrlimit(lr->why, &rlim) == -1)
114		syslog(LOG_WARNING, "set class '%s' resource limit %s: %m", lc->lc_class, lr->what);
115	}
116    }
117}
118
119
120
121static struct login_vars {
122    const char *tag;
123    const char *var;
124    const char *def;
125    int overwrite;
126} pathvars[] = {
127    { "path",           "PATH",       NULL, 1},
128    { "cdpath",         "CDPATH",     NULL, 1},
129    { "manpath",        "MANPATH",    NULL, 1},
130    { NULL,             NULL,         NULL, 0}
131}, envars[] = {
132    { "lang",           "LANG",       NULL, 1},
133    { "charset",        "MM_CHARSET", NULL, 1},
134    { "mail",           "MAIL",       NULL, 1},
135    { "timezone",       "TZ",         NULL, 1},
136    { "term",           "TERM",       NULL, 0},
137    { NULL,             NULL,         NULL, 0}
138};
139
140static char *
141substvar(const char * var, const struct passwd * pwd, int hlen, int pch, int nlen)
142{
143    char    *np = NULL;
144
145    if (var != NULL) {
146	int	tildes = 0;
147	int	dollas = 0;
148	char	*p;
149	const char *q;
150
151	if (pwd != NULL) {
152	    for (q = var; *q != '\0'; ++q) {
153		tildes += (*q == '~');
154		dollas += (*q == '$');
155	    }
156	}
157
158	np = malloc(strlen(var) + (dollas * nlen)
159		    - dollas + (tildes * (pch+hlen))
160		    - tildes + 1);
161
162	if (np != NULL) {
163	    p = strcpy(np, var);
164
165	    if (pwd != NULL) {
166		/*
167		 * This loop does user username and homedir substitutions
168		 * for unescaped $ (username) and ~ (homedir)
169		 */
170		while (*(p += strcspn(p, "~$")) != '\0') {
171		    int	l = strlen(p);
172
173		    if (p > np && *(p-1) == '\\')  /* Escaped: */
174			memmove(p - 1, p, l + 1); /* Slide-out the backslash */
175		    else if (*p == '~') {
176			int	v = pch && *(p+1) != '/'; /* Avoid double // */
177			memmove(p + hlen + v, p + 1, l);  /* Subst homedir */
178			memmove(p, pwd->pw_dir, hlen);
179			if (v)
180			    p[hlen] = '/';
181			p += hlen + v;
182		    }
183		    else /* if (*p == '$') */ {
184			memmove(p + nlen, p + 1, l);	/* Subst username */
185			memmove(p, pwd->pw_name, nlen);
186			p += nlen;
187		    }
188		}
189	    }
190	}
191    }
192
193    return (np);
194}
195
196
197void
198setclassenvironment(login_cap_t *lc, const struct passwd * pwd, int paths)
199{
200    struct login_vars	*vars = paths ? pathvars : envars;
201    int			hlen = pwd ? strlen(pwd->pw_dir) : 0;
202    int			nlen = pwd ? strlen(pwd->pw_name) : 0;
203    char pch = 0;
204
205    if (hlen && pwd->pw_dir[hlen-1] != '/')
206	++pch;
207
208    while (vars->tag != NULL) {
209	const char * var = paths ? login_getpath(lc, vars->tag, NULL)
210				 : login_getcapstr(lc, vars->tag, NULL, NULL);
211
212	char * np  = substvar(var, pwd, hlen, pch, nlen);
213
214	if (np != NULL) {
215	    setenv(vars->var, np, vars->overwrite);
216	    free(np);
217	} else if (vars->def != NULL) {
218	    setenv(vars->var, vars->def, 0);
219	}
220	++vars;
221    }
222
223    /*
224     * If we're not processing paths, then see if there is a setenv list by
225     * which the admin and/or user may set an arbitrary set of env vars.
226     */
227    if (!paths) {
228	const char	**set_env = login_getcaplist(lc, "setenv", ",");
229
230	if (set_env != NULL) {
231	    while (*set_env != NULL) {
232		char	*p = strchr(*set_env, '=');
233
234		if (p != NULL) {  /* Discard invalid entries */
235		    char	*np;
236
237		    *p++ = '\0';
238		    if ((np = substvar(p, pwd, hlen, pch, nlen)) != NULL) {
239			setenv(*set_env, np, 1);
240			free(np);
241		    }
242		}
243		++set_env;
244	    }
245	}
246    }
247}
248
249
250static int
251list2cpuset(const char *list, cpuset_t *mask)
252{
253	enum { NONE, NUM, DASH } state;
254	int lastnum;
255	int curnum;
256	const char *l;
257
258	state = NONE;
259	curnum = lastnum = 0;
260	for (l = list; *l != '\0';) {
261		if (isdigit(*l)) {
262			curnum = atoi(l);
263			if (curnum > CPU_SETSIZE)
264				errx(EXIT_FAILURE,
265				    "Only %d cpus supported", CPU_SETSIZE);
266			while (isdigit(*l))
267				l++;
268			switch (state) {
269			case NONE:
270				lastnum = curnum;
271				state = NUM;
272				break;
273			case DASH:
274				for (; lastnum <= curnum; lastnum++)
275					CPU_SET(lastnum, mask);
276				state = NONE;
277				break;
278			case NUM:
279			default:
280				return (0);
281			}
282			continue;
283		}
284		switch (*l) {
285		case ',':
286			switch (state) {
287			case NONE:
288				break;
289			case NUM:
290				CPU_SET(curnum, mask);
291				state = NONE;
292				break;
293			case DASH:
294				return (0);
295				break;
296			}
297			break;
298		case '-':
299			if (state != NUM)
300				return (0);
301			state = DASH;
302			break;
303		default:
304			return (0);
305		}
306		l++;
307	}
308	switch (state) {
309		case NONE:
310			break;
311		case NUM:
312			CPU_SET(curnum, mask);
313			break;
314		case DASH:
315			return (0);
316	}
317	return (1);
318}
319
320
321void
322setclasscpumask(login_cap_t *lc)
323{
324	const char *maskstr;
325	cpuset_t maskset;
326	cpusetid_t setid;
327
328	maskstr = login_getcapstr(lc, "cpumask", NULL, NULL);
329	CPU_ZERO(&maskset);
330	if (maskstr == NULL)
331		return;
332	if (strcasecmp("default", maskstr) == 0)
333		return;
334	if (!list2cpuset(maskstr, &maskset)) {
335		syslog(LOG_WARNING,
336		    "list2cpuset(%s) invalid mask specification", maskstr);
337		return;
338	}
339
340	if (cpuset(&setid) != 0) {
341		syslog(LOG_ERR, "cpuset(): %s", strerror(errno));
342		return;
343	}
344
345	if (cpuset_setaffinity(CPU_LEVEL_CPUSET, CPU_WHICH_PID, -1,
346	    sizeof(maskset), &maskset) != 0)
347		syslog(LOG_ERR, "cpuset_setaffinity(%s): %s", maskstr,
348		    strerror(errno));
349}
350
351
352/*
353 * setclasscontext()
354 *
355 * For the login class <class>, set various class context values
356 * (limits, mainly) to the values for that class.  Which values are
357 * set are controlled by <flags> -- see <login_class.h> for the
358 * possible values.
359 *
360 * setclasscontext() can only set resources, priority, and umask.
361 */
362
363int
364setclasscontext(const char *classname, unsigned int flags)
365{
366    int		rc;
367    login_cap_t *lc;
368
369    lc = login_getclassbyname(classname, NULL);
370
371    flags &= LOGIN_SETRESOURCES | LOGIN_SETPRIORITY |
372	    LOGIN_SETUMASK | LOGIN_SETPATH;
373
374    rc = lc ? setusercontext(lc, NULL, 0, flags) : -1;
375    login_close(lc);
376    return (rc);
377}
378
379
380
381/*
382 * Private function which takes care of processing
383 */
384
385static mode_t
386setlogincontext(login_cap_t *lc, const struct passwd *pwd,
387		mode_t mymask, unsigned long flags)
388{
389    if (lc) {
390	/* Set resources */
391	if (flags & LOGIN_SETRESOURCES)
392	    setclassresources(lc);
393	/* See if there's a umask override */
394	if (flags & LOGIN_SETUMASK)
395	    mymask = (mode_t)login_getcapnum(lc, "umask", mymask, mymask);
396	/* Set paths */
397	if (flags & LOGIN_SETPATH)
398	    setclassenvironment(lc, pwd, 1);
399	/* Set environment */
400	if (flags & LOGIN_SETENV)
401	    setclassenvironment(lc, pwd, 0);
402	/* Set cpu affinity */
403	if (flags & LOGIN_SETCPUMASK)
404	    setclasscpumask(lc);
405    }
406    return (mymask);
407}
408
409
410
411/*
412 * setusercontext()
413 *
414 * Given a login class <lc> and a user in <pwd>, with a uid <uid>,
415 * set the context as in setclasscontext().  <flags> controls which
416 * values are set.
417 *
418 * The difference between setclasscontext() and setusercontext() is
419 * that the former sets things up for an already-existing process,
420 * while the latter sets things up from a root context.  Such as might
421 * be called from login(1).
422 *
423 */
424
425int
426setusercontext(login_cap_t *lc, const struct passwd *pwd, uid_t uid, unsigned int flags)
427{
428    rlim_t	p;
429    mode_t	mymask;
430    login_cap_t *llc = NULL;
431    struct rtprio rtp;
432    int error;
433
434    if (lc == NULL) {
435	if (pwd != NULL && (lc = login_getpwclass(pwd)) != NULL)
436	    llc = lc; /* free this when we're done */
437    }
438
439    if (flags & LOGIN_SETPATH)
440	pathvars[0].def = uid ? _PATH_DEFPATH : _PATH_STDPATH;
441
442    /* we need a passwd entry to set these */
443    if (pwd == NULL)
444	flags &= ~(LOGIN_SETGROUP | LOGIN_SETLOGIN | LOGIN_SETMAC);
445
446    /* Set the process priority */
447    if (flags & LOGIN_SETPRIORITY) {
448	p = login_getcapnum(lc, "priority", LOGIN_DEFPRI, LOGIN_DEFPRI);
449
450	if (p > PRIO_MAX) {
451	    rtp.type = RTP_PRIO_IDLE;
452	    p -= PRIO_MAX + 1;
453	    rtp.prio = p > RTP_PRIO_MAX ? RTP_PRIO_MAX : p;
454	    if (rtprio(RTP_SET, 0, &rtp))
455		syslog(LOG_WARNING, "rtprio '%s' (%s): %m",
456		    pwd ? pwd->pw_name : "-",
457		    lc ? lc->lc_class : LOGIN_DEFCLASS);
458	} else if (p < PRIO_MIN) {
459	    rtp.type = RTP_PRIO_REALTIME;
460	    p -= PRIO_MIN - RTP_PRIO_MAX;
461	    rtp.prio = p < RTP_PRIO_MIN ? RTP_PRIO_MIN : p;
462	    if (rtprio(RTP_SET, 0, &rtp))
463		syslog(LOG_WARNING, "rtprio '%s' (%s): %m",
464		    pwd ? pwd->pw_name : "-",
465		    lc ? lc->lc_class : LOGIN_DEFCLASS);
466	} else {
467	    if (setpriority(PRIO_PROCESS, 0, (int)p) != 0)
468		syslog(LOG_WARNING, "setpriority '%s' (%s): %m",
469		    pwd ? pwd->pw_name : "-",
470		    lc ? lc->lc_class : LOGIN_DEFCLASS);
471	}
472    }
473
474    /* Setup the user's group permissions */
475    if (flags & LOGIN_SETGROUP) {
476	if (setgid(pwd->pw_gid) != 0) {
477	    syslog(LOG_ERR, "setgid(%lu): %m", (u_long)pwd->pw_gid);
478	    login_close(llc);
479	    return (-1);
480	}
481	if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
482	    syslog(LOG_ERR, "initgroups(%s,%lu): %m", pwd->pw_name,
483		   (u_long)pwd->pw_gid);
484	    login_close(llc);
485	    return (-1);
486	}
487    }
488
489    /* Set up the user's MAC label. */
490    if ((flags & LOGIN_SETMAC) && mac_is_present(NULL) == 1) {
491	const char *label_string;
492	mac_t label;
493
494	label_string = login_getcapstr(lc, "label", NULL, NULL);
495	if (label_string != NULL) {
496	    if (mac_from_text(&label, label_string) == -1) {
497		syslog(LOG_ERR, "mac_from_text('%s') for %s: %m",
498		    pwd->pw_name, label_string);
499		return (-1);
500	    }
501	    if (mac_set_proc(label) == -1)
502		error = errno;
503	    else
504		error = 0;
505	    mac_free(label);
506	    if (error != 0) {
507		syslog(LOG_ERR, "mac_set_proc('%s') for %s: %s",
508		    label_string, pwd->pw_name, strerror(error));
509		return (-1);
510	    }
511	}
512    }
513
514    /* Set the sessions login */
515    if ((flags & LOGIN_SETLOGIN) && setlogin(pwd->pw_name) != 0) {
516	syslog(LOG_ERR, "setlogin(%s): %m", pwd->pw_name);
517	login_close(llc);
518	return (-1);
519    }
520
521    /* Inform the kernel about current login class */
522    if (lc != NULL && lc->lc_class != NULL && (flags & LOGIN_SETLOGINCLASS)) {
523	error = setloginclass(lc->lc_class);
524	if (error != 0) {
525	    syslog(LOG_ERR, "setloginclass(%s): %m", lc->lc_class);
526#ifdef notyet
527	    login_close(llc);
528	    return (-1);
529#endif
530	}
531    }
532
533    mymask = (flags & LOGIN_SETUMASK) ? umask(LOGIN_DEFUMASK) : 0;
534    mymask = setlogincontext(lc, pwd, mymask, flags);
535    login_close(llc);
536
537    /* This needs to be done after anything that needs root privs */
538    if ((flags & LOGIN_SETUSER) && setuid(uid) != 0) {
539	syslog(LOG_ERR, "setuid(%lu): %m", (u_long)uid);
540	return (-1);	/* Paranoia again */
541    }
542
543    /*
544     * Now, we repeat some of the above for the user's private entries
545     */
546    if (getuid() == uid && (lc = login_getuserclass(pwd)) != NULL) {
547	mymask = setlogincontext(lc, pwd, mymask, flags);
548	login_close(lc);
549    }
550
551    /* Finally, set any umask we've found */
552    if (flags & LOGIN_SETUMASK)
553	umask(mymask);
554
555    return (0);
556}
557