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