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