login_class.c revision 22084
121288Sdavidn/*-
221288Sdavidn * Copyright (c) 1996 by
321288Sdavidn * Sean Eric Fagan <sef@kithrup.com>
421288Sdavidn * David Nugent <davidn@blaze.net.au>
521288Sdavidn * All rights reserved.
621288Sdavidn *
721288Sdavidn * Redistribution and use in source and binary forms, with or without
821288Sdavidn * modification, is permitted provided that the following conditions
921288Sdavidn * are met:
1021288Sdavidn * 1. Redistributions of source code must retain the above copyright
1121288Sdavidn *    notice immediately at the beginning of the file, without modification,
1221288Sdavidn *    this list of conditions, and the following disclaimer.
1321288Sdavidn * 2. Redistributions in binary form must reproduce the above copyright
1421288Sdavidn *    notice, this list of conditions and the following disclaimer in the
1521288Sdavidn *    documentation and/or other materials provided with the distribution.
1621288Sdavidn * 3. This work was done expressly for inclusion into FreeBSD.  Other use
1721288Sdavidn *    is permitted provided this notation is included.
1821288Sdavidn * 4. Absolutely no warranty of function or purpose is made by the authors.
1921288Sdavidn * 5. Modifications may be freely made to this file providing the above
2021288Sdavidn *    conditions are met.
2121288Sdavidn *
2221288Sdavidn * High-level routines relating to use of the user capabilities database
2321288Sdavidn *
2421673Sjkh *	$FreeBSD: head/lib/libutil/login_class.c 22084 1997-01-29 06:02:49Z davidn $
2521288Sdavidn */
2621288Sdavidn
2721288Sdavidn#include <stdio.h>
2821288Sdavidn#include <stdlib.h>
2921288Sdavidn#include <string.h>
3021288Sdavidn#include <unistd.h>
3121288Sdavidn#include <errno.h>
3221288Sdavidn#include <sys/types.h>
3322084Sdavidn#include <sys/stat.h>
3421288Sdavidn#include <sys/time.h>
3521288Sdavidn#include <sys/resource.h>
3621288Sdavidn#include <fcntl.h>
3721288Sdavidn#include <pwd.h>
3821288Sdavidn#include <syslog.h>
3921288Sdavidn#include <login_cap.h>
4021288Sdavidn#include <paths.h>
4121288Sdavidn
4221288Sdavidn
4321288Sdavidn#undef	UNKNOWN
4421288Sdavidn#define	UNKNOWN	"su"
4521288Sdavidn
4621288Sdavidn
4721288Sdavidnstatic struct login_res {
4821288Sdavidn  const char * what;
4921288Sdavidn  rlim_t (*who)(login_cap_t *, const char *, rlim_t, rlim_t);
5021288Sdavidn  int why;
5121288Sdavidn} resources[] = {
5221288Sdavidn  { "cputime",	    login_getcaptime, RLIMIT_CPU      },
5321288Sdavidn  { "filesize",     login_getcapsize, RLIMIT_FSIZE    },
5421288Sdavidn  { "datasize",     login_getcapsize, RLIMIT_DATA     },
5521288Sdavidn  { "stacksize",    login_getcapsize, RLIMIT_STACK    },
5621288Sdavidn  { "coredumpsize", login_getcapsize, RLIMIT_CORE     },
5721288Sdavidn  { "memoryuse",    login_getcapsize, RLIMIT_RSS      },
5821288Sdavidn  { "memorylocked", login_getcapsize, RLIMIT_MEMLOCK  },
5921288Sdavidn  { "maxproc",	    login_getcapnum,  RLIMIT_NPROC    },
6021288Sdavidn  { "openfiles",    login_getcapnum,  RLIMIT_NOFILE   },
6121288Sdavidn  { NULL,	    0,		      0 	      }
6221288Sdavidn};
6321288Sdavidn
6421288Sdavidn
6521288Sdavidn
6621288Sdavidnvoid
6721288Sdavidnsetclassresources(login_cap_t *lc)
6821288Sdavidn{
6921288Sdavidn  struct login_res *lr = resources;
7021288Sdavidn
7121402Sdavidn  if (lc == NULL)
7221402Sdavidn	  return;
7321402Sdavidn
7421288Sdavidn  while (lr->what != NULL) {
7521288Sdavidn    struct rlimit rlim,
7621288Sdavidn		  newlim;
7721288Sdavidn    char	  cur[40],
7821288Sdavidn		  max[40];
7921288Sdavidn    rlim_t	  rcur,
8021288Sdavidn		  rmax;
8121288Sdavidn
8221288Sdavidn    sprintf(cur, "%s-cur", lr->what);
8321288Sdavidn    sprintf(max, "%s-max", lr->what);
8421288Sdavidn
8521288Sdavidn    /*
8621288Sdavidn     * The login.conf file can have <limit>, <limit>-max, and
8721288Sdavidn     * <limit>-cur entries.
8821288Sdavidn     * What we do is get the current current- and maximum- limits.
8921288Sdavidn     * Then, we try to get an entry for <limit> from the capability,
9021288Sdavidn     * using the current and max limits we just got as the
9121288Sdavidn     * default/error values.
9221288Sdavidn     * *Then*, we try looking for <limit>-cur and <limit>-max,
9321288Sdavidn     * again using the appropriate values as the default/error
9421288Sdavidn     * conditions.
9521288Sdavidn     */
9621288Sdavidn
9721288Sdavidn    getrlimit(lr->why, &rlim);
9821288Sdavidn    rcur = rlim.rlim_cur;
9921288Sdavidn    rmax = rlim.rlim_max;
10021288Sdavidn
10121288Sdavidn    rcur = (*lr->who)(lc, lr->what, rcur, rcur);
10221288Sdavidn    rmax = (*lr->who)(lc, lr->what, rmax, rmax);
10321288Sdavidn    newlim.rlim_cur = (*lr->who)(lc, cur, rcur, rcur);
10421288Sdavidn    newlim.rlim_max = (*lr->who)(lc, max, rmax, rmax);
10521288Sdavidn
10621288Sdavidn    if (setrlimit(lr->why, &newlim) == -1)
10721288Sdavidn      syslog(LOG_WARNING, "set class '%s' resource limit %s: %m", lc->lc_class, lr->what);
10821288Sdavidn
10921288Sdavidn    ++lr;
11021288Sdavidn  }
11121288Sdavidn}
11221288Sdavidn
11321288Sdavidnstatic struct login_vars {
11421288Sdavidn  const char * tag;
11521288Sdavidn  const char * var;
11621288Sdavidn  const char * def;
11721288Sdavidn} pathvars[] = {
11821288Sdavidn  { "path",	"PATH",	      NULL    },
11921288Sdavidn  { "manpath",	"MANPATH",    NULL    },
12021288Sdavidn  { NULL,	NULL,	      NULL    }
12121288Sdavidn}, envars[] = {
12221288Sdavidn  { "lang",	"LANG",	      NULL    },
12321288Sdavidn  { "charset",	"MM_CHARSET", NULL    },
12421288Sdavidn  { "timezone", "TZ",	      NULL    },
12521288Sdavidn  { "term",	"TERM",       UNKNOWN },
12621288Sdavidn  { NULL,	NULL,	      NULL    }
12721288Sdavidn};
12821288Sdavidn
12921288Sdavidnstatic char *
13021288Sdavidnsubstvar(char * var, const struct passwd * pwd, int hlen, int pch, int nlen)
13121288Sdavidn{
13221288Sdavidn  char * np = NULL;
13321288Sdavidn
13421288Sdavidn  if (var != NULL) {
13521288Sdavidn    int tildes = 0;
13621288Sdavidn    int dollas = 0;
13721288Sdavidn    char * p;
13821288Sdavidn
13921288Sdavidn    if (pwd != NULL) {
14021288Sdavidn      /*
14121288Sdavidn       * Count the number of ~'s in var to substitute
14221288Sdavidn       */
14321288Sdavidn      p = var;
14421288Sdavidn      while ((p = strchr(p, '~')) != NULL) {
14521288Sdavidn	++tildes;
14621288Sdavidn	++p;
14721288Sdavidn      }
14821288Sdavidn
14921288Sdavidn      /*
15021288Sdavidn       * Count the number of $'s in var to substitute
15121288Sdavidn       */
15221288Sdavidn      p = var;
15321288Sdavidn      while ((p = strchr(p, '$')) != NULL) {
15421288Sdavidn        ++dollas;
15521288Sdavidn        ++p;
15621288Sdavidn      }
15721288Sdavidn    }
15821288Sdavidn
15921288Sdavidn    np = malloc(strlen(var) + (dollas * nlen) - dollas + (tildes * (pch+hlen)) - tildes + 1);
16021288Sdavidn
16121288Sdavidn    if (np != NULL) {
16221288Sdavidn      p = strcpy(np, var);
16321288Sdavidn
16421288Sdavidn      if (pwd != NULL) {
16521288Sdavidn	/*
16621288Sdavidn	 * This loop does user username and homedir substitutions
16721288Sdavidn	 * for unescaped $ (username) and ~ (homedir)
16821288Sdavidn	 */
16921288Sdavidn	while (*(p += strcspn(p, "~$")) != '\0') {
17021288Sdavidn	  int l = strlen(p);
17121288Sdavidn
17221288Sdavidn	  if (p > var && *(p-1) == '\\')  /* Escaped: */
17321402Sdavidn	    memmove(p - 1, p, l + 1);	  /* Slide-out the backslash */
17421288Sdavidn	  else if (*p == '~') {
17521402Sdavidn	    int v = pch && *(p+1) != '/'; /* Avoid double // */
17621402Sdavidn	    memmove(p + hlen + v, p + 1, l);  /* Subst homedir */
17721288Sdavidn	    memmove(p, pwd->pw_dir, hlen);
17821402Sdavidn	    if (v)
17921288Sdavidn      	      p[hlen] = '/';
18021402Sdavidn	    p += hlen + v;
18121288Sdavidn	  }
18221288Sdavidn	  else /* if (*p == '$') */ {
18321402Sdavidn	    memmove(p + nlen, p + 1, l);	/* Subst username */
18421288Sdavidn	    memmove(p, pwd->pw_name, nlen);
18521288Sdavidn	    p += nlen;
18621288Sdavidn	  }
18721288Sdavidn	}
18821288Sdavidn      }
18921288Sdavidn    }
19021288Sdavidn  }
19121288Sdavidn  return np;
19221288Sdavidn}
19321288Sdavidn
19421288Sdavidn
19521288Sdavidnvoid
19621288Sdavidnsetclassenvironment(login_cap_t *lc, const struct passwd * pwd, int paths)
19721288Sdavidn{
19821288Sdavidn  struct login_vars * vars = paths ? pathvars : envars;
19921288Sdavidn  int hlen = pwd ? strlen(pwd->pw_dir) : 0;
20021288Sdavidn  int nlen = pwd ? strlen(pwd->pw_name) : 0;
20121288Sdavidn  char pch = 0;
20221288Sdavidn
20321288Sdavidn  if (hlen && pwd->pw_dir[hlen-1] != '/')
20421288Sdavidn    ++pch;
20521288Sdavidn
20621288Sdavidn  while (vars->tag != NULL) {
20721288Sdavidn    char * var = paths ? login_getpath(lc, vars->tag, NULL)
20821402Sdavidn  		       : login_getcapstr(lc, vars->tag, NULL, NULL);
20921288Sdavidn
21021288Sdavidn    char * np  = substvar(var, pwd, hlen, pch, nlen);
21121288Sdavidn
21221288Sdavidn    if (np != NULL) {
21321288Sdavidn      setenv(vars->var, np, 1);
21421288Sdavidn      free(np);
21521288Sdavidn    } else if (vars->def != NULL) {
21621288Sdavidn      setenv(vars->var, vars->def, 0);
21721288Sdavidn    }
21821288Sdavidn    ++vars;
21921288Sdavidn  }
22021288Sdavidn
22121288Sdavidn  /*
22221288Sdavidn   * If we're not processing paths, then see if there is a setenv list by
22321288Sdavidn   * which the admin and/or user may set an arbitrary set of env vars.
22421288Sdavidn   */
22521288Sdavidn  if (!paths) {
22621288Sdavidn    char ** set_env = login_getcaplist(lc, "setenv", ",");
22721402Sdavidn
22821288Sdavidn    if (set_env != NULL) {
22921288Sdavidn      while (*set_env != NULL) {
23021288Sdavidn	char *p = strchr(*set_env, '=');
23121402Sdavidn	if (p != NULL) {  /* Discard invalid entries */
23221402Sdavidn	  char * np;
23321402Sdavidn
23421402Sdavidn	  *p++ = '\0';
23521402Sdavidn	  if ((np = substvar(p, pwd, hlen, pch, nlen)) != NULL) {
23621402Sdavidn	    setenv(*set_env, np, 1);
23721288Sdavidn	    free(np);
23821288Sdavidn	  }
23921288Sdavidn	}
24021288Sdavidn	++set_env;
24121288Sdavidn      }
24221288Sdavidn    }
24321288Sdavidn  }
24421288Sdavidn}
24521288Sdavidn
24621288Sdavidn
24721288Sdavidn/*
24821288Sdavidn * setclasscontext()
24921288Sdavidn *
25021288Sdavidn * For the login class <class>, set various class context values
25121288Sdavidn * (limits, mainly) to the values for that class.  Which values are
25221288Sdavidn * set are controlled by <flags> -- see <login_class.h> for the
25321288Sdavidn * possible values.
25421288Sdavidn *
25521288Sdavidn * setclasscontext() can only set resources, priority, and umask.
25621288Sdavidn */
25721288Sdavidn
25821288Sdavidnint
25921288Sdavidnsetclasscontext(const char *classname, unsigned int flags)
26021288Sdavidn{
26121288Sdavidn  int rc;
26221288Sdavidn  login_cap_t * lc = login_getclassbyname(classname, NULL);
26321288Sdavidn  flags &= (LOGIN_SETRESOURCES| LOGIN_SETPRIORITY|LOGIN_SETUMASK);
26421288Sdavidn  rc = setusercontext(lc, NULL, 0, flags);
26521288Sdavidn  login_close(lc);
26621288Sdavidn  return rc;
26721288Sdavidn}
26821288Sdavidn
26921288Sdavidn
27021288Sdavidn/*
27121288Sdavidn * setusercontext()
27221288Sdavidn *
27321288Sdavidn * Given a login class <lc> and a user in <pwd>, with a uid <uid>,
27421288Sdavidn * set the context as in setclasscontext().  <flags> controls which
27521288Sdavidn * values are set.
27621288Sdavidn *
27721288Sdavidn * The difference between setclasscontext() and setusercontext() is
27821288Sdavidn * that the former sets things up for an already-existing process,
27921288Sdavidn * while the latter sets things up from a root context.  Such as might
28021288Sdavidn * be called from login(1).
28121288Sdavidn *
28221288Sdavidn */
28321288Sdavidn
28421288Sdavidnint
28521288Sdavidnsetusercontext(login_cap_t *lc, const struct passwd *pwd, uid_t uid, unsigned int flags)
28621288Sdavidn{
28721288Sdavidn  int i;
28821288Sdavidn  login_cap_t * llc = NULL;
28921288Sdavidn
29021288Sdavidn  if (lc == NULL)
29121288Sdavidn  {
29221402Sdavidn    if (pwd != NULL && (lc = login_getclass(pwd)) != NULL)
29321402Sdavidn      llc = lc; /* free this when we're done */
29421288Sdavidn  }
29521288Sdavidn
29621288Sdavidn  if (flags & LOGIN_SETPATH)
29721288Sdavidn    pathvars[0].def = uid ? _PATH_DEFPATH : _PATH_STDPATH;
29821288Sdavidn
29921288Sdavidn  /*
30021288Sdavidn   * Set the process priority
30121288Sdavidn   */
30221288Sdavidn  if (flags & LOGIN_SETPRIORITY) {
30321288Sdavidn    int pri = (int)login_getcapnum(lc, "priority", LOGIN_DEFPRI, LOGIN_DEFPRI);
30421288Sdavidn    pri = (pri < PRIO_MIN) ? PRIO_MIN : (pri > PRIO_MAX) ? PRIO_MAX : pri;
30521288Sdavidn    if (setpriority(PRIO_PROCESS, 0, pri) != 0)
30621288Sdavidn      syslog(LOG_WARNING, "setpriority '%s': %m", pwd->pw_name);
30721288Sdavidn  }
30821288Sdavidn
30921288Sdavidn  /*
31021288Sdavidn   * Set resources
31121288Sdavidn   */
31221288Sdavidn  if (flags & LOGIN_SETRESOURCES)
31321288Sdavidn    setclassresources(lc);
31421288Sdavidn
31521288Sdavidn  /*
31621288Sdavidn   * Set the sessions login
31721288Sdavidn   */
31821288Sdavidn  if ((flags & LOGIN_SETLOGIN) && setlogin(pwd->pw_name) != 0) {
31921288Sdavidn    syslog(LOG_ERR, "setlogin '%s': %m", pwd->pw_name);
32021288Sdavidn    login_close(llc);
32121402Sdavidn    return -1;
32221288Sdavidn  }
32321288Sdavidn
32421288Sdavidn  /*
32521288Sdavidn   * Setup the user's group permissions
32621288Sdavidn   */
32721288Sdavidn  if (flags & LOGIN_SETGROUP) {
32821288Sdavidn    if (setgid(pwd->pw_gid) != 0)
32921288Sdavidn      syslog(LOG_WARNING, "setgid %ld: %m", (long)pwd->pw_gid);
33021288Sdavidn    if (initgroups(pwd->pw_name, pwd->pw_gid) == -1)
33121288Sdavidn      syslog(LOG_WARNING, "initgroups '%s': %m", pwd->pw_name);
33221288Sdavidn  }
33321288Sdavidn
33421288Sdavidn  /*
33521288Sdavidn   * FreeBSD extension: here we (might) loop and do this twice.
33621288Sdavidn   * First, for the class we have been given, and next for
33721288Sdavidn   * any user overrides in ~/.login.conf the user's home directory.
33821288Sdavidn   */
33921288Sdavidn  if (flags & LOGIN_SETUMASK)
34021288Sdavidn    umask(LOGIN_DEFUMASK);    /* Set default umask up front */
34121288Sdavidn
34221288Sdavidn  i = 0;
34321288Sdavidn  while (i < 2 && lc != NULL) {
34421288Sdavidn
34521288Sdavidn    if (flags & LOGIN_SETUMASK) {
34621288Sdavidn      rlim_t tmp = login_getcapnum(lc, "umask", RLIM_INFINITY, RLIM_INFINITY);
34721288Sdavidn      if (tmp != RLIM_INFINITY)
34821288Sdavidn	umask((mode_t)tmp);
34921288Sdavidn    }
35021288Sdavidn
35121288Sdavidn    if (flags & LOGIN_SETPATH)
35221288Sdavidn      setclassenvironment(lc, pwd, 1);
35321288Sdavidn
35421288Sdavidn    if (flags & LOGIN_SETENV)
35521288Sdavidn      setclassenvironment(lc, pwd, 0);
35621288Sdavidn
35721288Sdavidn    if (i++ == 0) /* Play it again, Sam */
35821288Sdavidn      lc = (pwd == NULL) ? NULL : login_getuserclass(pwd);
35921288Sdavidn  }
36021288Sdavidn
36121288Sdavidn  login_close(lc);  /* User's private 'me' class */
36221288Sdavidn  login_close(llc); /* Class we opened */
36321288Sdavidn
36421402Sdavidn  /*
36521402Sdavidn   * This needs to be done after all of the above.
36621402Sdavidn   * Do it last so we avoid getting killed and dropping core
36721402Sdavidn   */
36821402Sdavidn  if ((flags & LOGIN_SETUSER) && setuid(uid) != 0) {
36921402Sdavidn    syslog(LOG_ERR, "setuid %ld: %m", uid);
37021402Sdavidn    login_close(llc);
37121402Sdavidn    return -1;	/* Paranoia again */
37221402Sdavidn  }
37321402Sdavidn
37421288Sdavidn  return 0;
37521288Sdavidn}
37621288Sdavidn
377