login_class.c revision 98977
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: head/lib/libutil/login_class.c 98977 2002-06-28 14:45:30Z ache $");
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <unistd.h>
32#include <errno.h>
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <sys/time.h>
36#include <sys/resource.h>
37#include <fcntl.h>
38#include <pwd.h>
39#include <syslog.h>
40#include <login_cap.h>
41#include <paths.h>
42#include <sys/rtprio.h>
43
44
45static struct login_res {
46    const char *what;
47    rlim_t (*who)(login_cap_t *, const char *, rlim_t, rlim_t);
48    int why;
49} resources[] = {
50    { "cputime",      login_getcaptime, RLIMIT_CPU      },
51    { "filesize",     login_getcapsize, RLIMIT_FSIZE    },
52    { "datasize",     login_getcapsize, RLIMIT_DATA     },
53    { "stacksize",    login_getcapsize, RLIMIT_STACK    },
54    { "memoryuse",    login_getcapsize, RLIMIT_RSS      },
55    { "memorylocked", login_getcapsize, RLIMIT_MEMLOCK  },
56    { "maxproc",      login_getcapnum,  RLIMIT_NPROC    },
57    { "openfiles",    login_getcapnum,  RLIMIT_NOFILE   },
58    { "coredumpsize", login_getcapsize, RLIMIT_CORE     },
59    { "sbsize",       login_getcapsize,	RLIMIT_SBSIZE	},
60    { "vmemoryuse",   login_getcapsize,	RLIMIT_VMEM	},
61    { NULL,	      0,		0 	        }
62};
63
64
65void
66setclassresources(login_cap_t *lc)
67{
68    struct login_res *lr;
69
70    if (lc == NULL)
71	return;
72
73    for (lr = resources; lr->what != NULL; ++lr) {
74	struct rlimit	rlim;
75
76	/*
77	 * The login.conf file can have <limit>, <limit>-max, and
78	 * <limit>-cur entries.
79	 * What we do is get the current current- and maximum- limits.
80	 * Then, we try to get an entry for <limit> from the capability,
81	 * using the current and max limits we just got as the
82	 * default/error values.
83	 * *Then*, we try looking for <limit>-cur and <limit>-max,
84	 * again using the appropriate values as the default/error
85	 * conditions.
86	 */
87
88	if (getrlimit(lr->why, &rlim) != 0)
89	    syslog(LOG_ERR, "getting %s resource limit: %m", lr->what);
90	else {
91	    char  	name_cur[40];
92	    char	name_max[40];
93	    rlim_t	rcur = rlim.rlim_cur;
94	    rlim_t	rmax = rlim.rlim_max;
95
96	    sprintf(name_cur, "%s-cur", lr->what);
97	    sprintf(name_max, "%s-max", lr->what);
98
99	    rcur = (*lr->who)(lc, lr->what, rcur, rcur);
100	    rmax = (*lr->who)(lc, lr->what, rmax, rmax);
101	    rlim.rlim_cur = (*lr->who)(lc, name_cur, rcur, rcur);
102	    rlim.rlim_max = (*lr->who)(lc, name_max, rmax, rmax);
103
104	    if (setrlimit(lr->why, &rlim) == -1)
105		syslog(LOG_WARNING, "set class '%s' resource limit %s: %m", lc->lc_class, lr->what);
106	}
107    }
108}
109
110
111
112static struct login_vars {
113    const char *tag;
114    const char *var;
115    const char *def;
116    int overwrite;
117} pathvars[] = {
118    { "path",           "PATH",       NULL, 1},
119    { "cdpath",         "CDPATH",     NULL, 1},
120    { "manpath",        "MANPATH",    NULL, 1},
121    { NULL,             NULL,         NULL, 0}
122}, envars[] = {
123    { "lang",           "LANG",       NULL, 1},
124    { "charset",        "MM_CHARSET", NULL, 1},
125    { "timezone",       "TZ",         NULL, 1},
126    { "term",           "TERM",       NULL, 0},
127    { NULL,             NULL,         NULL, 0}
128};
129
130static char *
131substvar(const char * var, const struct passwd * pwd, int hlen, int pch, int nlen)
132{
133    char    *np = NULL;
134
135    if (var != NULL) {
136	int	tildes = 0;
137	int	dollas = 0;
138	char	*p;
139
140	if (pwd != NULL) {
141	    /* Count the number of ~'s in var to substitute */
142	    for (p = (char *)var; (p = strchr(p, '~')) != NULL; p++)
143		++tildes;
144	    /* Count the number of $'s in var to substitute */
145	    for (p = (char *)var; (p = strchr(p, '$')) != NULL; p++)
146		++dollas;
147	}
148
149	np = malloc(strlen(var) + (dollas * nlen)
150		    - dollas + (tildes * (pch+hlen))
151		    - tildes + 1);
152
153	if (np != NULL) {
154	    p = strcpy(np, var);
155
156	    if (pwd != NULL) {
157		/*
158		 * This loop does user username and homedir substitutions
159		 * for unescaped $ (username) and ~ (homedir)
160		 */
161		while (*(p += strcspn(p, "~$")) != '\0') {
162		    int	l = strlen(p);
163
164		    if (p > np && *(p-1) == '\\')  /* Escaped: */
165			memmove(p - 1, p, l + 1); /* Slide-out the backslash */
166		    else if (*p == '~') {
167			int	v = pch && *(p+1) != '/'; /* Avoid double // */
168			memmove(p + hlen + v, p + 1, l);  /* Subst homedir */
169			memmove(p, pwd->pw_dir, hlen);
170			if (v)
171			    p[hlen] = '/';
172			p += hlen + v;
173		    }
174		    else /* if (*p == '$') */ {
175			memmove(p + nlen, p + 1, l);	/* Subst username */
176			memmove(p, pwd->pw_name, nlen);
177			p += nlen;
178		    }
179		}
180	    }
181	}
182    }
183
184    return np;
185}
186
187
188void
189setclassenvironment(login_cap_t *lc, const struct passwd * pwd, int paths)
190{
191    struct login_vars	*vars = paths ? pathvars : envars;
192    int			hlen = pwd ? strlen(pwd->pw_dir) : 0;
193    int			nlen = pwd ? strlen(pwd->pw_name) : 0;
194    char pch = 0;
195
196    if (hlen && pwd->pw_dir[hlen-1] != '/')
197	++pch;
198
199    while (vars->tag != NULL) {
200	const char * var = paths ? login_getpath(lc, vars->tag, NULL)
201				 : login_getcapstr(lc, vars->tag, NULL, NULL);
202
203	char * np  = substvar(var, pwd, hlen, pch, nlen);
204
205	if (np != NULL) {
206	    setenv(vars->var, np, vars->overwrite);
207	    free(np);
208	} else if (vars->def != NULL) {
209	    setenv(vars->var, vars->def, 0);
210	}
211	++vars;
212    }
213
214    /*
215     * If we're not processing paths, then see if there is a setenv list by
216     * which the admin and/or user may set an arbitrary set of env vars.
217     */
218    if (!paths) {
219	char	**set_env = login_getcaplist(lc, "setenv", ",");
220
221	if (set_env != NULL) {
222	    while (*set_env != NULL) {
223		char	*p = strchr(*set_env, '=');
224
225		if (p != NULL) {  /* Discard invalid entries */
226		    char	*np;
227
228		    *p++ = '\0';
229		    if ((np = substvar(p, pwd, hlen, pch, nlen)) != NULL) {
230			setenv(*set_env, np, 1);
231			free(np);
232		    }
233		}
234		++set_env;
235	    }
236	}
237    }
238}
239
240
241/*
242 * setclasscontext()
243 *
244 * For the login class <class>, set various class context values
245 * (limits, mainly) to the values for that class.  Which values are
246 * set are controlled by <flags> -- see <login_class.h> for the
247 * possible values.
248 *
249 * setclasscontext() can only set resources, priority, and umask.
250 */
251
252int
253setclasscontext(const char *classname, unsigned int flags)
254{
255    int		rc;
256    login_cap_t *lc;
257
258    lc = login_getclassbyname(classname, NULL);
259
260    flags &= LOGIN_SETRESOURCES | LOGIN_SETPRIORITY |
261	    LOGIN_SETUMASK | LOGIN_SETPATH;
262
263    rc = lc ? setusercontext(lc, NULL, 0, flags) : -1;
264    login_close(lc);
265    return rc;
266}
267
268
269
270/*
271 * Private functionw which takes care of processing
272 */
273
274static mode_t
275setlogincontext(login_cap_t *lc, const struct passwd *pwd,
276		mode_t mymask, unsigned long flags)
277{
278    if (lc) {
279	/* Set resources */
280	if (flags & LOGIN_SETRESOURCES)
281	    setclassresources(lc);
282	/* See if there's a umask override */
283	if (flags & LOGIN_SETUMASK)
284	    mymask = (mode_t)login_getcapnum(lc, "umask", mymask, mymask);
285	/* Set paths */
286	if (flags & LOGIN_SETPATH)
287	    setclassenvironment(lc, pwd, 1);
288	/* Set environment */
289	if (flags & LOGIN_SETENV)
290	    setclassenvironment(lc, pwd, 0);
291    }
292    return mymask;
293}
294
295
296
297/*
298 * setusercontext()
299 *
300 * Given a login class <lc> and a user in <pwd>, with a uid <uid>,
301 * set the context as in setclasscontext().  <flags> controls which
302 * values are set.
303 *
304 * The difference between setclasscontext() and setusercontext() is
305 * that the former sets things up for an already-existing process,
306 * while the latter sets things up from a root context.  Such as might
307 * be called from login(1).
308 *
309 */
310
311int
312setusercontext(login_cap_t *lc, const struct passwd *pwd, uid_t uid, unsigned int flags)
313{
314    quad_t	p;
315    mode_t	mymask;
316    login_cap_t *llc = NULL;
317#ifndef __NETBSD_SYSCALLS
318    struct rtprio rtp;
319#endif
320
321    if (lc == NULL) {
322	if (pwd != NULL && (lc = login_getpwclass(pwd)) != NULL)
323	    llc = lc; /* free this when we're done */
324    }
325
326    if (flags & LOGIN_SETPATH)
327	pathvars[0].def = uid ? _PATH_DEFPATH : _PATH_STDPATH;
328
329    /* we need a passwd entry to set these */
330    if (pwd == NULL)
331	flags &= ~(LOGIN_SETGROUP | LOGIN_SETLOGIN);
332
333    /* Set the process priority */
334    if (flags & LOGIN_SETPRIORITY) {
335	p = login_getcapnum(lc, "priority", LOGIN_DEFPRI, LOGIN_DEFPRI);
336
337	if(p > PRIO_MAX) {
338#ifndef __NETBSD_SYSCALLS
339	    rtp.type = RTP_PRIO_IDLE;
340	    rtp.prio = p - PRIO_MAX - 1;
341	    p = (rtp.prio > RTP_PRIO_MAX) ? 31 : p;
342	    if(rtprio(RTP_SET, 0, &rtp))
343		syslog(LOG_WARNING, "rtprio '%s' (%s): %m",
344		    pwd->pw_name, lc ? lc->lc_class : LOGIN_DEFCLASS);
345#endif
346	} else if(p < PRIO_MIN) {
347#ifndef __NETBSD_SYSCALLS
348	    rtp.type = RTP_PRIO_REALTIME;
349	    rtp.prio = abs(p - PRIO_MIN + RTP_PRIO_MAX);
350	    p = (rtp.prio > RTP_PRIO_MAX) ? 1 : p;
351	    if(rtprio(RTP_SET, 0, &rtp))
352		syslog(LOG_WARNING, "rtprio '%s' (%s): %m",
353		    pwd->pw_name, lc ? lc->lc_class : LOGIN_DEFCLASS);
354#endif
355	} else {
356	    if (setpriority(PRIO_PROCESS, 0, (int)p) != 0)
357		syslog(LOG_WARNING, "setpriority '%s' (%s): %m",
358		    pwd->pw_name, lc ? lc->lc_class : LOGIN_DEFCLASS);
359	}
360    }
361
362    /* Setup the user's group permissions */
363    if (flags & LOGIN_SETGROUP) {
364	if (setgid(pwd->pw_gid) != 0) {
365	    syslog(LOG_ERR, "setgid(%lu): %m", (u_long)pwd->pw_gid);
366	    login_close(llc);
367	    return -1;
368	}
369	if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
370	    syslog(LOG_ERR, "initgroups(%s,%lu): %m", pwd->pw_name,
371		   (u_long)pwd->pw_gid);
372	    login_close(llc);
373	    return -1;
374	}
375    }
376
377    /* Set the sessions login */
378    if ((flags & LOGIN_SETLOGIN) && setlogin(pwd->pw_name) != 0) {
379	syslog(LOG_ERR, "setlogin(%s): %m", pwd->pw_name);
380	login_close(llc);
381	return -1;
382    }
383
384    mymask = (flags & LOGIN_SETUMASK) ? umask(LOGIN_DEFUMASK) : 0;
385    mymask = setlogincontext(lc, pwd, mymask, flags);
386    login_close(llc);
387
388    /* This needs to be done after anything that needs root privs */
389    if ((flags & LOGIN_SETUSER) && setuid(uid) != 0) {
390	syslog(LOG_ERR, "setuid(%lu): %m", (u_long)uid);
391	return -1;	/* Paranoia again */
392    }
393
394    /*
395     * Now, we repeat some of the above for the user's private entries
396     */
397    if ((lc = login_getuserclass(pwd)) != NULL) {
398	mymask = setlogincontext(lc, pwd, mymask, flags);
399	login_close(lc);
400    }
401
402    /* Finally, set any umask we've found */
403    if (flags & LOGIN_SETUMASK)
404	umask(mymask);
405
406    return 0;
407}
408
409