From 5607d7250357a548f04fe5e31cc960a2e54cf908 Mon Sep 17 00:00:00 2001 From: Tomas Mraz Date: Wed, 13 Feb 2008 12:49:43 +0000 Subject: Relevant BUGIDs: Purpose of commit: bugfix, new feature Commit summary: --------------- 2008-02-13 Tomas Mraz * modules/pam_namespace/Makefile.am: Add argv_parse files and namespace.d dir. * modules/pam_namespace/argv_parse.c: New file. * modules/pam_namespace/argv_parse.h: New file. * modules/pam_namespace/namespace.conf.5.xml: Document new features. * modules/pam_namespace/pam_namespace.8.xml: Likewise. * modules/pam_namespace/pam_namespace.h: Use SECURECONF_DIR define. Define NAMESPACE_D_DIR and NAMESPACE_D_GLOB. Define new option flags and polydir flags. (polydir_s): Add rdir, replace exclusive with flags, add init_script, owner, group, and mode. (instance_data): Add ruser, gid, and ruid. * modules/pam_namespace/pam_namespace.c: Remove now unused copy_ent(). (add_polydir_entry): Add the entry directly, no copy. (del_polydir): New function. (del_polydir_list): Call del_polydir(). (expand_variables, parse_create_params, parse_iscript_params, parse_method): New functions. (process_line): Call expand_variables() on polydir and instance prefix. Call argv_parse() instead of strtok_r(). Allocate struct polydir_s on heap. (parse_config_file): Parse .conf files from namespace.d dir after namespace.conf. (form_context): Call getcon() or get_default_context_with_level() when appropriate flags are set. (poly_name): Handle shared polydir flag. (inst_init): Execute non-default init script when specified. (create_polydir): New function. (create_dirs): Remove the code which checks the polydir. Do not call inst_init() when noinit flag is set. (ns_setup): Check the polydir and eventually create it if the create flag is set. (setup_namespace): Use ruser uid from idata. Set the namespace polydir pam data only when namespace was set up correctly. Unmount polydir based on ruser. (get_user_data): New function. (pam_sm_open_session): Check for use_current_context and use_default_context options. Call get_user_data(). (pam_sm_close_session): Call get_user_data(). --- ChangeLog | 41 ++ NEWS | 3 +- modules/pam_namespace/Makefile.am | 9 +- modules/pam_namespace/argv_parse.c | 165 ++++++ modules/pam_namespace/argv_parse.h | 43 ++ modules/pam_namespace/namespace.conf.5.xml | 68 ++- modules/pam_namespace/pam_namespace.8.xml | 58 +- modules/pam_namespace/pam_namespace.c | 917 +++++++++++++++++++---------- modules/pam_namespace/pam_namespace.h | 38 +- 9 files changed, 1013 insertions(+), 329 deletions(-) create mode 100644 modules/pam_namespace/argv_parse.c create mode 100644 modules/pam_namespace/argv_parse.h diff --git a/ChangeLog b/ChangeLog index fbe3a36b..31a1fd02 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,44 @@ +2008-02-13 Tomas Mraz + + * modules/pam_namespace/Makefile.am: Add argv_parse files and namespace.d + dir. + * modules/pam_namespace/argv_parse.c: New file. + * modules/pam_namespace/argv_parse.h: New file. + * modules/pam_namespace/namespace.conf.5.xml: Document new features. + * modules/pam_namespace/pam_namespace.8.xml: Likewise. + * modules/pam_namespace/pam_namespace.h: Use SECURECONF_DIR define. + Define NAMESPACE_D_DIR and NAMESPACE_D_GLOB. Define new option flags + and polydir flags. + (polydir_s): Add rdir, replace exclusive with flags, add init_script, + owner, group, and mode. + (instance_data): Add ruser, gid, and ruid. + * modules/pam_namespace/pam_namespace.c: Remove now unused copy_ent(). + (add_polydir_entry): Add the entry directly, no copy. + (del_polydir): New function. + (del_polydir_list): Call del_polydir(). + (expand_variables, parse_create_params, parse_iscript_params, + parse_method): New functions. + (process_line): Call expand_variables() on polydir and instance prefix. + Call argv_parse() instead of strtok_r(). Allocate struct polydir_s on heap. + (parse_config_file): Parse .conf files from namespace.d dir after + namespace.conf. + (form_context): Call getcon() or get_default_context_with_level() when + appropriate flags are set. + (poly_name): Handle shared polydir flag. + (inst_init): Execute non-default init script when specified. + (create_polydir): New function. + (create_dirs): Remove the code which checks the polydir. Do not call + inst_init() when noinit flag is set. + (ns_setup): Check the polydir and eventually create it if the create flag + is set. + (setup_namespace): Use ruser uid from idata. Set the namespace polydir + pam data only when namespace was set up correctly. Unmount polydir + based on ruser. + (get_user_data): New function. + (pam_sm_open_session): Check for use_current_context and + use_default_context options. Call get_user_data(). + (pam_sm_close_session): Call get_user_data(). + 2008-02-04 Thorsten Kukuk * libpam/pam_static_modules.h: Add _pam_sepermit_modstruct. diff --git a/NEWS b/NEWS index 6ba96793..ea835334 100644 --- a/NEWS +++ b/NEWS @@ -10,7 +10,8 @@ Linux-PAM NEWS -- history of user-visible changes. by crypt(). * New pam_sepermit.so module for allowing/rejecting access based on SELinux mode. -* Improved functionality of pam_namespace.so module. +* Improved functionality of pam_namespace.so module (method flags, + namespace.d configuration directory, new options). Release 0.99.9.0 * misc_conv no longer blocks SIGINT; applications that don't want diff --git a/modules/pam_namespace/Makefile.am b/modules/pam_namespace/Makefile.am index 002678ba..e8598e8f 100644 --- a/modules/pam_namespace/Makefile.am +++ b/modules/pam_namespace/Makefile.am @@ -15,13 +15,14 @@ endif EXTRA_DIST = README namespace.conf namespace.init $(MAN5) $(MAN8) $(XMLS) tst-pam_namespace -noinst_HEADERS = md5.h +noinst_HEADERS = md5.h argv_parse.h securelibdir = $(SECUREDIR) secureconfdir = $(SCONFIGDIR) +namespaceddir = $(SCONFIGDIR)/namespace.d AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include \ - -DPAM_NAMESPACE_CONFIG=\"$(SCONFIGDIR)/namespace.conf\" + -DSECURECONF_DIR=\"$(SCONFIGDIR)/\" AM_LDFLAGS = -no-undefined -avoid-version -module if HAVE_VERSIONING AM_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map @@ -29,11 +30,13 @@ endif if HAVE_UNSHARE securelib_LTLIBRARIES = pam_namespace.la -pam_namespace_la_SOURCES = pam_namespace.c pam_namespace.h md5.c md5.h +pam_namespace_la_SOURCES = pam_namespace.c md5.c argv_parse.c pam_namespace_la_LIBADD = -L$(top_builddir)/libpam -lpam @LIBSELINUX@ secureconf_DATA = namespace.conf secureconf_SCRIPTS = namespace.init +namespaced_DATA = + TESTS = tst-pam_namespace man_MANS = $(MAN5) $(MAN8) endif diff --git a/modules/pam_namespace/argv_parse.c b/modules/pam_namespace/argv_parse.c new file mode 100644 index 00000000..acc76d74 --- /dev/null +++ b/modules/pam_namespace/argv_parse.c @@ -0,0 +1,165 @@ +/* + * argv_parse.c --- utility function for parsing a string into a + * argc, argv array. + * + * This file defines a function argv_parse() which parsing a + * passed-in string, handling double quotes and backslashes, and + * creates an allocated argv vector which can be freed using the + * argv_free() function. + * + * See argv_parse.h for the formal definition of the functions. + * + * Copyright 1999 by Theodore Ts'o. + * + * Permission to use, copy, modify, and distribute this software for + * any purpose with or without fee is hereby granted, provided that + * the above copyright notice and this permission notice appear in all + * copies. THE SOFTWARE IS PROVIDED "AS IS" AND THEODORE TS'O (THE + * AUTHOR) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. (Isn't + * it sick that the U.S. culture of lawsuit-happy lawyers requires + * this kind of disclaimer?) + * + * Version 1.1, modified 2/27/1999 + */ + +#include +#include +#include +#include "argv_parse.h" + +#define STATE_WHITESPACE 1 +#define STATE_TOKEN 2 +#define STATE_QUOTED 3 + +/* + * Returns 0 on success, -1 on failure. + */ +int argv_parse(const char *in_buf, int *ret_argc, char ***ret_argv) +{ + int argc = 0, max_argc = 0; + char **argv, **new_argv, *buf, ch; + const char *cp = 0; + char *outcp = 0; + int state = STATE_WHITESPACE; + + buf = malloc(strlen(in_buf)+1); + if (!buf) + return -1; + + max_argc = 0; argc = 0; argv = 0; + outcp = buf; + for (cp = in_buf; (ch = *cp); cp++) { + if (state == STATE_WHITESPACE) { + if (isspace((int) ch)) + continue; + /* Not whitespace, so start a new token */ + state = STATE_TOKEN; + if (argc >= max_argc) { + max_argc += 3; + new_argv = realloc(argv, + (max_argc+1)*sizeof(char *)); + if (!new_argv) { + if (argv) free(argv); + free(buf); + return -1; + } + argv = new_argv; + } + argv[argc++] = outcp; + } + if (state == STATE_QUOTED) { + if (ch == '"') + state = STATE_TOKEN; + else + *outcp++ = ch; + continue; + } + /* Must be processing characters in a word */ + if (isspace((int) ch)) { + /* + * Terminate the current word and start + * looking for the beginning of the next word. + */ + *outcp++ = 0; + state = STATE_WHITESPACE; + continue; + } + if (ch == '"') { + state = STATE_QUOTED; + continue; + } + if (ch == '\\') { + ch = *++cp; + switch (ch) { + case '\0': + ch = '\\'; cp--; break; + case 'n': + ch = '\n'; break; + case 't': + ch = '\t'; break; + case 'b': + ch = '\b'; break; + } + } + *outcp++ = ch; + } + if (state != STATE_WHITESPACE) + *outcp++ = '\0'; + if (argv == 0) { + argv = malloc(sizeof(char *)); + free(buf); + } + argv[argc] = 0; + if (ret_argc) + *ret_argc = argc; + if (ret_argv) + *ret_argv = argv; + return 0; +} + +void argv_free(char **argv) +{ + if (*argv) + free(*argv); + free(argv); +} + +#ifdef DEBUG_ARGV_PARSE +/* + * For debugging + */ + +#include + +int main(int argc, char **argv) +{ + int ac, ret; + char **av, **cpp; + char buf[256]; + + while (!feof(stdin)) { + if (fgets(buf, sizeof(buf), stdin) == NULL) + break; + ret = argv_parse(buf, &ac, &av); + if (ret != 0) { + printf("Argv_parse returned %d!\n", ret); + continue; + } + printf("Argv_parse returned %d arguments...\n", ac); + for (cpp = av; *cpp; cpp++) { + if (cpp != av) + printf(", "); + printf("'%s'", *cpp); + } + printf("\n"); + argv_free(av); + } + exit(0); +} +#endif diff --git a/modules/pam_namespace/argv_parse.h b/modules/pam_namespace/argv_parse.h new file mode 100644 index 00000000..c7878fc1 --- /dev/null +++ b/modules/pam_namespace/argv_parse.h @@ -0,0 +1,43 @@ +/* + * argv_parse.h --- header file for the argv parser. + * + * This file defines the interface for the functions argv_parse() and + * argv_free(). + * + *********************************************************************** + * int argv_parse(char *in_buf, int *ret_argc, char ***ret_argv) + * + * This function takes as its first argument a string which it will + * parse into an argv argument vector, with each white-space separated + * word placed into its own slot in the argv. This function handles + * double quotes and backslashes so that the parsed words can contain + * special characters. The count of the number words found in the + * parsed string, as well as the argument vector, are returned into + * ret_argc and ret_argv, respectively. + *********************************************************************** + * extern void argv_free(char **argv); + * + * This function frees the argument vector created by argv_parse(). + *********************************************************************** + * + * Copyright 1999 by Theodore Ts'o. + * + * Permission to use, copy, modify, and distribute this software for + * any purpose with or without fee is hereby granted, provided that + * the above copyright notice and this permission notice appear in all + * copies. THE SOFTWARE IS PROVIDED "AS IS" AND THEODORE TS'O (THE + * AUTHOR) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. (Isn't + * it sick that the U.S. culture of lawsuit-happy lawyers requires + * this kind of disclaimer?) + * + * Version 1.1, modified 2/27/1999 + */ + +extern int argv_parse(const char *in_buf, int *ret_argc, char ***ret_argv); +extern void argv_free(char **argv); diff --git a/modules/pam_namespace/namespace.conf.5.xml b/modules/pam_namespace/namespace.conf.5.xml index 9fbefc49..a1769600 100644 --- a/modules/pam_namespace/namespace.conf.5.xml +++ b/modules/pam_namespace/namespace.conf.5.xml @@ -20,8 +20,9 @@ DESCRIPTION - This module allows setup of private namespaces with polyinstantiated - directories. Directories can be polyinstantiated based on user name + The pam_namespace.so module allows setup of + private namespaces with polyinstantiated directories. + Directories can be polyinstantiated based on user name or, in the case of SELinux, user name, sensitivity level or complete security context. If an executable script /etc/security/namespace.init exists, it is used to initialize the namespace every time a new instance @@ -38,19 +39,23 @@ When someone logs in, the file namespace.conf is - scanned where each non comment line represents one polyinstantiated - directory with space separated fields as follows: + scanned. Comments are marked by # characters. + Each non comment line represents one polyinstantiated + directory. The fields are separated by spaces but can be quoted by + " characters also escape + sequences \b, \n, and + \t are recognized. The fields are as follows: - - polydir instance_prefix method list_of_uids + polydir instance_prefix method list_of_uids The first field, polydir, is the absolute - pathname of the directory to polyinstantiate. Special entry $HOME is - supported to designate user's home directory. This field cannot be - blank. + pathname of the directory to polyinstantiate. The special string + $HOME is replaced with the user's home directory, + and $USER with the username. This field cannot + be blank. @@ -62,12 +67,9 @@ instance directory path. This directory is created if it did not exist already, and is then bind mounted on the <polydir> to provide an instance of <polydir> based on the <method> column. - The special string $HOME is replaced with the user's home directory, - and $USER with the username. This field cannot be blank. - The directory where polyinstantiated instances are to be - created, must exist and must have, by default, the mode of 000. The - requirement that the instance parent be of mode 000 can be overridden - with the command line option ignore_instance_parent_mode + The special string $HOME is replaced with the + user's home directory, and $USER with the username. + This field cannot be blank. @@ -91,6 +93,39 @@ polyinstantiation is performed only for users in the list. + + The method field can contain also following + optional flags separated by : characters. + + + create=mode,owner,group + - create the polyinstantiated directory. The mode, owner and group parameters + are optional. The default for mode is determined by umask, the default + owner is the user whose session is opened, the default group is the + primary group of the user. + + + iscript=path + - path to the instance directory init script. The base directory for relative + paths is /etc/security/namespace.d. + + + noinit + - instance directory init script will not be executed. + + + shared + - the instance directories for "context" and "level" methods will not + contain the user name and will be shared among all users. + + + + The directory where polyinstantiated instances are to be + created, must exist and must have, by default, the mode of 0000. The + requirement that the instance parent be of mode 0000 can be overridden + with the command line option ignore_instance_parent_mode + + In case of context or level polyinstantiation the SELinux context which is used for polyinstantiation is the context used for executing @@ -105,7 +140,7 @@ method and <user name>_<raw directory context> for "context" and "level" methods. If the whole string is too long the end of it is replaced with md5sum of itself. Also when command line option - gen_hash is used the whole string is replaced + gen_hash is used the whole string is replaced with md5sum of itself. @@ -169,6 +204,7 @@ AUTHORS The namespace.conf manual page was written by Janak Desai <janak@us.ibm.com>. + More features added by Tomas Mraz <tmraz@redhat.com>. diff --git a/modules/pam_namespace/pam_namespace.8.xml b/modules/pam_namespace/pam_namespace.8.xml index f47bb81b..32c5359d 100644 --- a/modules/pam_namespace/pam_namespace.8.xml +++ b/modules/pam_namespace/pam_namespace.8.xml @@ -46,6 +46,12 @@ no_unmount_on_close + + use_current_context + + + use_default_context + @@ -200,13 +206,42 @@ + + + + + + + Useful for services which do not change the SELinux context + with setexeccon call. The module will use the current SELinux + context of the calling process for the level and context + polyinstantiation. + + + + + + + + + + + Useful for services which do not use pam_selinux for changing + the SELinux context with setexeccon call. The module will use + the default SELinux context of the user for the level and context + polyinstantiation. + + + + MODULE SERVICES PROVIDED - The service is supported. + The service is supported. The module must not + be called from multithreaded processes. @@ -246,7 +281,21 @@ /etc/security/namespace.conf - Configuration file + Main configuration file + + + + + /etc/security/namespace.d + + Directory for additional configuration files + + + + + /etc/security/namespace.init + + Init script for instance directories @@ -332,7 +381,10 @@ The namespace setup scheme was designed by Stephen Smalley, Janak Desai and Chad Sellers. - The pam_namespace PAM module was developed by Janak Desai <janak@us.ibm.com>, Chad Sellers <csellers@tresys.com> and Steve Grubb <sgrubb@redhat.com>. + The pam_namespace PAM module was developed by Janak Desai <janak@us.ibm.com>, + Chad Sellers <csellers@tresys.com> and Steve Grubb <sgrubb@redhat.com>. + Additional improvements by Xavier Toth <txtoth@gmail.com> and Tomas Mraz + <tmraz@redhat.com>. diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c index a47b0698..d0741fd2 100644 --- a/modules/pam_namespace/pam_namespace.c +++ b/modules/pam_namespace/pam_namespace.c @@ -3,11 +3,13 @@ * establishing a session via PAM. * * (C) Copyright IBM Corporation 2005 - * (C) Copyright Red Hat 2006 + * (C) Copyright Red Hat, Inc. 2006, 2008 * All Rights Reserved. * * Written by: Janak Desai * With Revisions by: Steve Grubb + * Contributions by: Xavier Toth , + * Tomas Mraz * Derived from a namespace setup patch by Chad Sellers * * Permission is hereby granted, free of charge, to any person obtaining a @@ -31,80 +33,36 @@ */ #include "pam_namespace.h" - -/* - * Copies the contents of ent into pent - */ -static int copy_ent(const struct polydir_s *ent, struct polydir_s *pent) -{ - unsigned int i; - - strcpy(pent->dir, ent->dir); - strcpy(pent->instance_prefix, ent->instance_prefix); - pent->method = ent->method; - pent->num_uids = ent->num_uids; - pent->exclusive = ent->exclusive; - if (ent->num_uids) { - uid_t *pptr, *eptr; - - pent->uid = (uid_t *) malloc(ent->num_uids * sizeof(uid_t)); - if (!(pent->uid)) { - return -1; - } - for (i = 0, pptr = pent->uid, eptr = ent->uid; i < ent->num_uids; - i++, eptr++, pptr++) - *pptr = *eptr; - } else - pent->uid = NULL; - return 0; -} +#include "argv_parse.h" /* * Adds an entry for a polyinstantiated directory to the linked list of * polyinstantiated directories. It is called from process_line() while * parsing the namespace configuration file. */ -static int add_polydir_entry(struct instance_data *idata, - const struct polydir_s *ent) +static void add_polydir_entry(struct instance_data *idata, + struct polydir_s *ent) { - struct polydir_s *pent; - int rc = 0; - - /* - * Allocate an entry to hold information about a directory to - * polyinstantiate, populate it with information from 2nd argument - * and add the entry to the linked list of polyinstantiated - * directories. - */ - pent = (struct polydir_s *) malloc(sizeof(struct polydir_s)); - if (!pent) { - rc = -1; - goto out; - } - /* Make copy */ - rc = copy_ent(ent,pent); - if(rc < 0) - goto out_clean; - /* Now attach to linked list */ - pent->next = NULL; + ent->next = NULL; if (idata->polydirs_ptr == NULL) - idata->polydirs_ptr = pent; + idata->polydirs_ptr = ent; else { struct polydir_s *tail; tail = idata->polydirs_ptr; while (tail->next) tail = tail->next; - tail->next = pent; + tail->next = ent; } - goto out; -out_clean: - free(pent); -out: - return rc; } +static void del_polydir(struct polydir_s *poly) +{ + free(poly->uid); + free(poly->init_script); + free(poly); +} /* * Deletes all the entries in the linked list. @@ -116,8 +74,7 @@ static void del_polydir_list(struct polydir_s *polydirs_ptr) while (dptr) { struct polydir_s *tptr = dptr; dptr = dptr->next; - free(tptr->uid); - free(tptr); + del_polydir(tptr); } } @@ -126,6 +83,176 @@ static void cleanup_data(pam_handle_t *pamh UNUSED , void *data, int err UNUSED) del_polydir_list(data); } +static char *expand_variables(const char *orig, const char *var_names[], const char *var_values[]) +{ + const char *src = orig; + char *dst; + char *expanded; + char c; + size_t dstlen = 0; + while (*src) { + if (*src == '$') { + int i; + for (i = 0; var_names[i]; i++) { + int namelen = strlen(var_names[i]); + if (strncmp(var_names[i], src+1, namelen) == 0) { + dstlen += strlen(var_values[i]) - 1; /* $ */ + src += namelen; + break; + } + } + } + ++dstlen; + ++src; + } + if ((dst=expanded=malloc(dstlen + 1)) == NULL) + return NULL; + src = orig; + while ((c=*src) != '\0') { + if (c == '$') { + int i; + for (i = 0; var_names[i]; i++) { + int namelen = strlen(var_names[i]); + if (strncmp(var_names[i], src+1, namelen) == 0) { + dst = stpcpy(dst, var_values[i]); + --dst; + c = *dst; /* replace $ */ + src += namelen; + break; + } + } + } + *dst = c; + ++dst; + ++src; + } + *dst = '\0'; + return expanded; +} + +static int parse_create_params(char *params, struct polydir_s *poly) +{ + char *sptr; + struct passwd *pwd; + struct group *grp; + + poly->mode = (mode_t)ULONG_MAX; + poly->owner = (uid_t)ULONG_MAX; + poly->group = (gid_t)ULONG_MAX; + + if (*params != '=') + return 0; + params++; + + params = strtok_r(params, ",", &sptr); + if (params == NULL) + return 0; + + errno = 0; + poly->mode = (mode_t)strtoul(params, NULL, 0); + if (errno != 0) { + poly->mode = (mode_t)ULONG_MAX; + } + + params = strtok_r(NULL, ",", &sptr); + if (params == NULL) + return 0; + + pwd = getpwnam(params); /* session modules are not reentrant */ + if (pwd == NULL) + return -1; + poly->owner = pwd->pw_uid; + + params = strtok_r(NULL, ",", &sptr); + if (params == NULL) { + poly->group = pwd->pw_gid; + return 0; + } + grp = getgrnam(params); + if (grp == NULL) + return -1; + poly->group = grp->gr_gid; + + return 0; +} + +static int parse_iscript_params(char *params, struct polydir_s *poly) +{ + if (*params != '=') + return 0; + params++; + + if (*params != '\0') { + if (*params != '/') { /* path is relative to NAMESPACE_D_DIR */ + if (asprintf(&poly->init_script, "%s%s", NAMESPACE_D_DIR, params) == -1) + return -1; + } else { + poly->init_script = strdup(params); + } + if (poly->init_script == NULL) + return -1; + } + return 0; +} + +static int parse_method(char *method, struct polydir_s *poly, + struct instance_data *idata) +{ + enum polymethod pm; + char *sptr; + static const char *method_names[] = { "user", "context", "level", "tmpdir", + "tmpfs", NULL }; + static const char *flag_names[] = { "create", "noinit", "iscript", + "shared", NULL }; + static const unsigned int flag_values[] = { POLYDIR_CREATE, POLYDIR_NOINIT, + POLYDIR_ISCRIPT, POLYDIR_SHARED }; + int i; + char *flag; + + method = strtok_r(method, ":", &sptr); + pm = NONE; + + for (i = 0; method_names[i]; i++) { + if (strcmp(method, method_names[i]) == 0) { + pm = i + 1; /* 0 = NONE */ + } + } + + if (pm == NONE) { + pam_syslog(idata->pamh, LOG_NOTICE, "Unknown method"); + return -1; + } + + poly->method = pm; + + while ((flag=strtok_r(NULL, ":", &sptr)) != NULL) { + for (i = 0; flag_names[i]; i++) { + int namelen = strlen(flag_names[i]); + + if (strncmp(flag, flag_names[i], namelen) == 0) { + poly->flags |= flag_values[i]; + switch (flag_values[i]) { + case POLYDIR_CREATE: + if (parse_create_params(flag+namelen, poly) != 0) { + pam_syslog(idata->pamh, LOG_CRIT, "Invalid create parameters"); + return -1; + } + break; + + case POLYDIR_ISCRIPT: + if (parse_iscript_params(flag+namelen, poly) != 0) { + pam_syslog(idata->pamh, LOG_CRIT, "Memory allocation error"); + return -1; + }; + break; + } + } + } + } + + return 0; +} + /* * Called from parse_config_file, this function processes a single line * of the namespace configuration file. It skips over comments and incomplete @@ -134,18 +261,23 @@ static void cleanup_data(pam_handle_t *pamh UNUSED , void *data, int err UNUSED) * polyinstatiated directory structure and then calling add_polydir_entry to * add that entry to the linked list of polyinstantiated directories. */ -static int process_line(char *line, const char *home, +static int process_line(char *line, const char *home, const char *rhome, struct instance_data *idata) { - const char *dir, *instance_prefix; - const char *method, *uids; + char *dir = NULL, *instance_prefix = NULL, *rdir = NULL; + char *method, *uids; char *tptr; - struct polydir_s poly; + struct polydir_s *poly; int retval = 0; + char **config_options = NULL; + static const char *var_names[] = {"HOME", "USER", NULL}; + const char *var_values[] = {home, idata->user}; + const char *rvar_values[] = {rhome, idata->ruser}; + int len; - poly.uid = NULL; - poly.num_uids = 0; - poly.exclusive = 0; + poly = calloc(1, sizeof(*poly)); + if (poly == NULL) + goto erralloc; /* * skip the leading white space @@ -177,19 +309,27 @@ static int process_line(char *line, const char *home, * Initialize and scan the five strings from the line from the * namespace configuration file. */ - dir = strtok_r(line, " \t", &tptr); + retval = argv_parse(line, NULL, &config_options); + if (retval != 0) { + goto erralloc; + } + + dir = config_options[0]; if (dir == NULL) { pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing polydir"); goto skipping; } - instance_prefix = strtok_r(NULL, " \t", &tptr); + instance_prefix = config_options[1]; if (instance_prefix == NULL) { pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing instance_prefix"); + instance_prefix = NULL; goto skipping; } - method = strtok_r(NULL, " \t", &tptr); + method = config_options[2]; if (method == NULL) { pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing method"); + instance_prefix = NULL; + dir = NULL; goto skipping; } @@ -199,93 +339,82 @@ static int process_line(char *line, const char *home, * any of the other fields are blank, the line is incomplete so * skip it. */ - uids = strtok_r(NULL, " \t", &tptr); + uids = config_options[3]; /* - * If the directory being polyinstantiated is the home directory - * of the user who is establishing a session, we have to swap - * the "$HOME" string with the user's home directory that is - * passed in as an argument. + * Expand $HOME and $USER in poly dir and instance dir prefix */ - if (strcmp(dir, "$HOME") == 0) { - dir = home; + if ((rdir=expand_variables(dir, var_names, rvar_values)) == NULL) { + instance_prefix = NULL; + dir = NULL; + goto erralloc; + } + + if ((dir=expand_variables(dir, var_names, var_values)) == NULL) { + instance_prefix = NULL; + goto erralloc; + } + + if ((instance_prefix=expand_variables(instance_prefix, var_names, var_values)) + == NULL) { + goto erralloc; } - /* - * Expand $HOME and $USER in instance dir prefix - */ - if ((tptr = strstr(instance_prefix, "$USER")) != 0) { - /* FIXME: should only support this if method is USER or BOTH */ - char *expanded = alloca(strlen(idata->user) + strlen(instance_prefix)-5+1); - *tptr = 0; - sprintf(expanded, "%s%s%s", instance_prefix, idata->user, tptr+5); - instance_prefix = expanded; + if (idata->flags & PAMNS_DEBUG) { + pam_syslog(idata->pamh, LOG_DEBUG, "Expanded polydir: '%s'", dir); + pam_syslog(idata->pamh, LOG_DEBUG, "Expanded ruser polydir: '%s'", rdir); + pam_syslog(idata->pamh, LOG_DEBUG, "Expanded instance prefix: '%s'", instance_prefix); } - if ((tptr = strstr(instance_prefix, "$HOME")) != 0) { - char *expanded = alloca(strlen(home)+strlen(instance_prefix)-5+1); - *tptr = 0; - sprintf(expanded, "%s%s%s", instance_prefix, home, tptr+5); - instance_prefix = expanded; + + len = strlen(dir); + if (len > 0 && dir[len-1] == '/') { + dir[len-1] = '\0'; } + len = strlen(rdir); + if (len > 0 && rdir[len-1] == '/') { + rdir[len-1] = '\0'; + } + + if (dir[0] == '\0' || rdir[0] == '\0') { + pam_syslog(idata->pamh, LOG_NOTICE, "Invalid polydir"); + goto skipping; + } + /* * Populate polyinstantiated directory structure with appropriate * pathnames and the method with which to polyinstantiate. */ - if (strlen(dir) >= sizeof(poly.dir) - || strlen(instance_prefix) >= sizeof(poly.instance_prefix)) { + if (strlen(dir) >= sizeof(poly->dir) + || strlen(rdir) >= sizeof(poly->rdir) + || strlen(instance_prefix) >= sizeof(poly->instance_prefix)) { pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); goto skipping; } - strcpy(poly.dir, dir); - strcpy(poly.instance_prefix, instance_prefix); + strcpy(poly->dir, dir); + strcpy(poly->rdir, rdir); + strcpy(poly->instance_prefix, instance_prefix); - poly.method = NONE; - if (strcmp(method, "user") == 0) - poly.method = USER; - - if (strcmp(method, "tmpdir") == 0) { - poly.method = TMPDIR; - if (sizeof(poly.instance_prefix) - strlen(poly.instance_prefix) < 7) { - pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); - goto skipping; - } - strcat(poly.instance_prefix, "XXXXXX"); + if (parse_method(method, poly, idata) != 0) { + goto skipping; } - - if (strcmp(method, "tmpfs") == 0) - poly.method = TMPFS; -#ifdef WITH_SELINUX - if (strcmp(method, "level") == 0) { - if (idata->flags & PAMNS_CTXT_BASED_INST) - poly.method = LEVEL; - else - poly.method = USER; - } - - if (strcmp(method, "context") == 0) { - if (idata->flags & PAMNS_CTXT_BASED_INST) - poly.method = CONTEXT; - else - poly.method = USER; - } - -#endif - - if (poly.method == NONE) { - pam_syslog(idata->pamh, LOG_NOTICE, "Illegal method"); - goto skipping; + if (poly->method == TMPDIR) { + if (sizeof(poly->instance_prefix) - strlen(poly->instance_prefix) < 7) { + pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); + goto skipping; + } + strcat(poly->instance_prefix, "XXXXXX"); } /* * Ensure that all pathnames are absolute path names. */ - if ((dir[0] != '/') || (poly.method != TMPFS && instance_prefix[0] != '/')) { + if ((poly->dir[0] != '/') || (poly->method != TMPFS && poly->instance_prefix[0] != '/')) { pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames must start with '/'"); goto skipping; } - if (strstr(dir, "..") || strstr(instance_prefix, "..")) { + if (strstr(dir, "..") || strstr(poly->instance_prefix, "..")) { pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames must not contain '..'"); goto skipping; } @@ -302,18 +431,17 @@ static int process_line(char *line, const char *home, int count, i; if (*uids == '~') { - poly.exclusive = 1; + poly->flags |= POLYDIR_EXCLUSIVE; uids++; } for (count = 0, ustr = sstr = uids; sstr; ustr = sstr + 1, count++) sstr = strchr(ustr, ','); - poly.num_uids = count; - poly.uid = (uid_t *) malloc(count * sizeof (uid_t)); - uidptr = poly.uid; + poly->num_uids = count; + poly->uid = (uid_t *) malloc(count * sizeof (uid_t)); + uidptr = poly->uid; if (uidptr == NULL) { - pam_syslog(idata->pamh, LOG_NOTICE, "out of memory"); - goto skipping; + goto erralloc; } ustr = uids; @@ -327,7 +455,7 @@ static int process_line(char *line, const char *home, pwd = pam_modutil_getpwnam(idata->pamh, ustr); if (pwd == NULL) { pam_syslog(idata->pamh, LOG_ERR, "Unknown user %s in configuration", ustr); - poly.num_uids--; + poly->num_uids--; } else { *uidptr = pwd->pw_uid; uidptr++; @@ -340,20 +468,24 @@ static int process_line(char *line, const char *home, * Add polyinstantiated directory structure to the linked list * of all polyinstantiated directory structures. */ - if (add_polydir_entry(idata, &poly) < 0) { - pam_syslog(idata->pamh, LOG_ERR, "Allocation Error"); - retval = PAM_SERVICE_ERR; - } - free(poly.uid); + add_polydir_entry(idata, poly); goto out; +erralloc: + pam_syslog(idata->pamh, LOG_CRIT, "Memory allocation error"); + skipping: if (idata->flags & PAMNS_IGN_CONFIG_ERR) retval = 0; else retval = PAM_SERVICE_ERR; + del_polydir(poly); out: + free(rdir); + free(dir); + free(instance_prefix); + argv_free(config_options); return retval; } @@ -367,15 +499,15 @@ out: static int parse_config_file(struct instance_data *idata) { FILE *fil; - char *home; + char *home, *rhome; + const char *confname; struct passwd *cpwd; - char *line = NULL; + char *line; int retval; size_t len = 0; - - if (idata->flags & PAMNS_DEBUG) - pam_syslog(idata->pamh, LOG_DEBUG, "Parsing config file %s", - PAM_NAMESPACE_CONFIG); + glob_t globbuf; + const char *oldlocale; + size_t n; /* * Extract the user's home directory to resolve $HOME entries @@ -387,35 +519,86 @@ static int parse_config_file(struct instance_data *idata) "Error getting home dir for '%s'", idata->user); return PAM_SESSION_ERR; } - home = strdupa(cpwd->pw_dir); + if ((home=strdup(cpwd->pw_dir)) == NULL) { + pam_syslog(idata->pamh, LOG_CRIT, + "Memory allocation error"); + return PAM_SESSION_ERR; + } + + cpwd = pam_modutil_getpwnam(idata->pamh, idata->ruser); + if (!cpwd) { + pam_syslog(idata->pamh, LOG_ERR, + "Error getting home dir for '%s'", idata->ruser); + free(home); + return PAM_SESSION_ERR; + } + + if ((rhome=strdup(cpwd->pw_dir)) == NULL) { + pam_syslog(idata->pamh, LOG_CRIT, + "Memory allocation error"); + free(home); + return PAM_SESSION_ERR; + } /* * Open configuration file, read one line at a time and call * process_line to process each line. */ - fil = fopen(PAM_NAMESPACE_CONFIG, "r"); - if (fil == NULL) { - pam_syslog(idata->pamh, LOG_ERR, "Error opening config file"); - return PAM_SERVICE_ERR; - } - /* Use unlocked IO */ - __fsetlocking(fil, FSETLOCKING_BYCALLER); + memset(&globbuf, '\0', sizeof(globbuf)); + oldlocale = setlocale(LC_COLLATE, "C"); + glob(NAMESPACE_D_GLOB, 0, NULL, &globbuf); + if (oldlocale != NULL) + setlocale(LC_COLLATE, oldlocale); - /* loop reading the file */ - while (getline(&line, &len, fil) > 0) { - retval = process_line(line, home, idata); - if (retval) { - pam_syslog(idata->pamh, LOG_ERR, - "Error processing conf file line %s", line); - fclose(fil); - free(line); - return PAM_SERVICE_ERR; - } - } - fclose(fil); - free(line); + confname = PAM_NAMESPACE_CONFIG; + n = 0; + for (;;) { + if (idata->flags & PAMNS_DEBUG) + pam_syslog(idata->pamh, LOG_DEBUG, "Parsing config file %s", + confname); + fil = fopen(confname, "r"); + if (fil == NULL) { + pam_syslog(idata->pamh, LOG_ERR, "Error opening config file %s", + confname); + globfree(&globbuf); + free(rhome); + free(home); + return PAM_SERVICE_ERR; + } + + /* Use unlocked IO */ + __fsetlocking(fil, FSETLOCKING_BYCALLER); + + line = NULL; + /* loop reading the file */ + while (getline(&line, &len, fil) > 0) { + retval = process_line(line, home, rhome, idata); + if (retval) { + pam_syslog(idata->pamh, LOG_ERR, + "Error processing conf file %s line %s", confname, line); + fclose(fil); + free(line); + globfree(&globbuf); + free(rhome); + free(home); + return PAM_SERVICE_ERR; + } + } + fclose(fil); + free(line); + if (n >= globbuf.gl_pathc) + break; + + confname = globbuf.gl_pathv[n]; + n++; + } + + globfree(&globbuf); + free(rhome); + free(home); + /* All done...just some debug stuff */ if (idata->flags & PAMNS_DEBUG) { struct polydir_s *dptr = idata->polydirs_ptr; @@ -456,11 +639,11 @@ static int ns_override(struct polydir_s *polyptr, struct instance_data *idata, for (i = 0; i < polyptr->num_uids; i++) { if (uid == polyptr->uid[i]) { - return !polyptr->exclusive; + return !(polyptr->flags & POLYDIR_EXCLUSIVE); } } - return polyptr->exclusive; + return !!(polyptr->flags & POLYDIR_EXCLUSIVE); } /* @@ -514,7 +697,19 @@ static int form_context(const struct polydir_s *polyptr, if (polyptr->method == USER) return PAM_SUCCESS; - rc = getexeccon(&scon); + if (idata->flags & PAMNS_USE_CURRENT_CONTEXT) { + rc = getcon(&scon); + } else if (idata->flags & PAMNS_USE_DEFAULT_CONTEXT) { + char *seuser = NULL, *level = NULL; + + if ((rc=getseuserbyname(idata->user, &seuser, &level)) == 0) { + rc = get_default_context_with_level(seuser, level, NULL, &scon); + free(seuser); + free(level); + } + } else { + rc = getexeccon(&scon); + } if (rc < 0 || scon == NULL) { pam_syslog(idata->pamh, LOG_ERR, "Error getting exec context, %m"); @@ -589,7 +784,7 @@ static int form_context(const struct polydir_s *polyptr, /* * poly_name returns the name of the polyinstantiated instance directory - * based on the method used for polyinstantiation (user, context or both) + * based on the method used for polyinstantiation (user, context or level) * In addition, the function also returns the security contexts of the * original directory to polyinstantiate and the polyinstantiated instance * directory. @@ -605,6 +800,7 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, { int rc; char *hash = NULL; + enum polymethod pm; #ifdef WITH_SELINUX security_context_t rawcon = NULL; #endif @@ -624,7 +820,23 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, * Set the name of the polyinstantiated instance dir based on the * polyinstantiation method. */ - switch (polyptr->method) { + + pm = polyptr->method; + if (pm == LEVEL || pm == USER) { +#ifdef WITH_SELINUX + if (!(idata->flags & PAMNS_CTXT_BASED_INST)) +#else + pam_syslog(idata->pamh, LOG_NOTICE, + "Context and level methods not available, using user method"); +#endif + if (polyptr->flags & POLYDIR_SHARED) { + rc = PAM_IGNORE; + goto fail; + } + pm = USER; + } + + switch (pm) { case USER: if (asprintf(i_name, "%s", idata->user) < 0) { *i_name = NULL; @@ -638,10 +850,17 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, if (selinux_trans_to_raw_context(*i_context, &rawcon) < 0) { pam_syslog(idata->pamh, LOG_ERR, "Error translating directory context"); goto fail; - } - if (asprintf(i_name, "%s_%s", rawcon, idata->user) < 0) { - *i_name = NULL; - goto fail; + } + if (polyptr->flags & POLYDIR_SHARED) { + if (asprintf(i_name, "%s", rawcon) < 0) { + *i_name = NULL; + goto fail; + } + } else { + if (asprintf(i_name, "%s_%s", rawcon, idata->user) < 0) { + *i_name = NULL; + goto fail; + } } break; @@ -762,6 +981,7 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath, pid_t rc, pid; sighandler_t osighand = NULL; int status; + const char *init_script = NAMESPACE_INIT_SCRIPT; osighand = signal(SIGCHLD, SIG_DFL); if (osighand == SIG_ERR) { @@ -770,8 +990,11 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath, goto out; } - if (access(NAMESPACE_INIT_SCRIPT, F_OK) == 0) { - if (access(NAMESPACE_INIT_SCRIPT, X_OK) < 0) { + if ((polyptr->flags & POLYDIR_ISCRIPT) && polyptr->init_script) + init_script = polyptr->init_script; + + if (access(init_script, F_OK) == 0) { + if (access(init_script, X_OK) < 0) { if (idata->flags & PAMNS_DEBUG) pam_syslog(idata->pamh, LOG_ERR, "Namespace init script not executable"); @@ -786,7 +1009,7 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath, exit(1); } #endif - if (execl(NAMESPACE_INIT_SCRIPT, NAMESPACE_INIT_SCRIPT, + if (execl(init_script, init_script, polyptr->dir, ipath, newdir?"1":"0", idata->user, (char *)NULL) < 0) exit(1); } else if (pid > 0) { @@ -818,47 +1041,116 @@ out: return rc; } +static int create_polydir(struct polydir_s *polyptr, + struct instance_data *idata) +{ + mode_t mode; + int rc; +#ifdef WITH_SELINUX + security_context_t dircon, oldcon = NULL; +#endif + const char *dir = polyptr->dir; + + if (polyptr->mode != (mode_t)ULONG_MAX) + mode = polyptr->mode; + else + mode = 0777; + +#ifdef WITH_SELINUX + if (idata->flags & PAMNS_SELINUX_ENABLED) { + getfscreatecon(&oldcon); + rc = matchpathcon(dir, S_IFDIR, &dircon); + if (rc) { + pam_syslog(idata->pamh, LOG_NOTICE, + "Unable to get default context for directory %s, check your policy: %m", dir); + } else { + if (idata->flags & PAMNS_DEBUG) + pam_syslog(idata->pamh, LOG_DEBUG, + "Polydir %s context: %s", dir, (char *)dircon); + if (setfscreatecon(dircon) != 0) + pam_syslog(idata->pamh, LOG_NOTICE, + "Error setting context for directory %s: %m", dir); + freecon(dircon); + } + matchpathcon_fini(); + } +#endif + + rc = mkdir(dir, mode); + if (rc != 0) { + pam_syslog(idata->pamh, LOG_ERR, + "Error creating directory %s: %m", dir); + return PAM_SESSION_ERR; + } + +#ifdef WITH_SELINUX + if (idata->flags & PAMNS_SELINUX_ENABLED) { + if (setfscreatecon(oldcon) != 0) + pam_syslog(idata->pamh, LOG_NOTICE, + "Error resetting fs create context: %m"); + freecon(oldcon); + } +#endif + + if (idata->flags & PAMNS_DEBUG) + pam_syslog(idata->pamh, LOG_DEBUG, "Created polydir %s", dir); + + if (polyptr->mode != (mode_t)ULONG_MAX) { + /* explicit mode requested */ + if (chmod(dir, mode) != 0) { + pam_syslog(idata->pamh, LOG_ERR, + "Error changing mode of directory %s: %m", dir); + rmdir(dir); + return PAM_SESSION_ERR; + } + } + + if (polyptr->owner != (uid_t)ULONG_MAX) { + if (chown(dir, polyptr->owner, polyptr->group) != 0) { + pam_syslog(idata->pamh, LOG_ERR, + "Unable to change owner on directory %s: %m", dir); + rmdir(dir); + return PAM_SESSION_ERR; + } + if (idata->flags & PAMNS_DEBUG) + pam_syslog(idata->pamh, LOG_DEBUG, + "Polydir owner %u group %u from configuration", polyptr->owner, polyptr->group); + } else { + if (chown(dir, idata->uid, idata->gid) != 0) { + pam_syslog(idata->pamh, LOG_ERR, + "Unable to change owner on directory %s: %m", dir); + rmdir(dir); + return PAM_SESSION_ERR; + } + if (idata->flags & PAMNS_DEBUG) + pam_syslog(idata->pamh, LOG_DEBUG, + "Polydir owner %u group %u", idata->uid, idata->gid); + } + + return PAM_SUCCESS; +} + /* * Create polyinstantiated instance directory (ipath). */ #ifdef WITH_SELINUX -static int create_dirs(struct polydir_s *polyptr, char *ipath, +static int create_dirs(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, security_context_t icontext, security_context_t ocontext, struct instance_data *idata) #else -static int create_dirs(struct polydir_s *polyptr, char *ipath, +static int create_dirs(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, struct instance_data *idata) #endif { - struct stat statbuf, newstatbuf; - int rc, fd; - int newdir = 0; - - /* - * stat the directory to polyinstantiate, so its owner-group-mode - * can be propagated to instance directory - */ - rc = PAM_SUCCESS; - if (stat(polyptr->dir, &statbuf) < 0) { - pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m", - polyptr->dir); - return PAM_SESSION_ERR; - } + struct stat newstatbuf; + int fd; + int newdir = 0; /* - * Make sure we are dealing with a directory + * Check to make sure instance parent is valid. */ - if (!S_ISDIR(statbuf.st_mode)) { - pam_syslog(idata->pamh, LOG_ERR, "poly dir %s is not a dir", - polyptr->dir); - return PAM_SESSION_ERR; - } - - /* - * Check to make sure instance parent is valid. - */ - if (check_inst_parent(ipath, idata)) - return PAM_SESSION_ERR; + if (check_inst_parent(ipath, idata)) + return PAM_SESSION_ERR; /* * Create instance directory and set its security context to the context @@ -923,9 +1215,9 @@ static int create_dirs(struct polydir_s *polyptr, char *ipath, rmdir(ipath); return PAM_SESSION_ERR; } - if (newstatbuf.st_uid != statbuf.st_uid || - newstatbuf.st_gid != statbuf.st_gid) { - if (fchown(fd, statbuf.st_uid, statbuf.st_gid) < 0) { + if (newstatbuf.st_uid != statbuf->st_uid || + newstatbuf.st_gid != statbuf->st_gid) { + if (fchown(fd, statbuf->st_uid, statbuf->st_gid) < 0) { pam_syslog(idata->pamh, LOG_ERR, "Error changing owner for %s, %m", ipath); @@ -934,7 +1226,7 @@ static int create_dirs(struct polydir_s *polyptr, char *ipath, return PAM_SESSION_ERR; } } - if (fchmod(fd, statbuf.st_mode & 07777) < 0) { + if (fchmod(fd, statbuf->st_mode & 07777) < 0) { pam_syslog(idata->pamh, LOG_ERR, "Error changing mode for %s, %m", ipath); close(fd); @@ -951,8 +1243,10 @@ static int create_dirs(struct polydir_s *polyptr, char *ipath, */ inst_init: - rc = inst_init(polyptr, ipath, idata, newdir); - return rc; + if (polyptr->flags & POLYDIR_NOINIT) + return PAM_SUCCESS; + + return inst_init(polyptr, ipath, idata, newdir); } @@ -969,6 +1263,7 @@ static int ns_setup(struct polydir_s *polyptr, int retval = 0; char *inst_dir = NULL; char *instname = NULL; + struct stat statbuf; #ifdef WITH_SELINUX security_context_t instcontext = NULL, origcontext = NULL; #endif @@ -977,6 +1272,27 @@ static int ns_setup(struct polydir_s *polyptr, pam_syslog(idata->pamh, LOG_DEBUG, "Set namespace for directory %s", polyptr->dir); + while (stat(polyptr->dir, &statbuf) < 0) { + if (retval || !(polyptr->flags & POLYDIR_CREATE)) { + pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m", + polyptr->dir); + return PAM_SESSION_ERR; + } else { + if (create_polydir(polyptr, idata) != PAM_SUCCESS) + return PAM_SESSION_ERR; + retval = PAM_SESSION_ERR; /* bail out on next failed stat */ + } + } + + /* + * Make sure we are dealing with a directory + */ + if (!S_ISDIR(statbuf.st_mode)) { + pam_syslog(idata->pamh, LOG_ERR, "Polydir %s is not a dir", + polyptr->dir); + return PAM_SESSION_ERR; + } + if (polyptr->method == TMPFS) { if (mount("tmpfs", polyptr->dir, "tmpfs", 0, NULL) < 0) { pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m", @@ -999,9 +1315,10 @@ static int ns_setup(struct polydir_s *polyptr, retval = poly_name(polyptr, &instname, idata); #endif - if (retval) { - pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name"); - goto error_out; + if (retval != PAM_SUCCESS) { + if (retval != PAM_IGNORE) + pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name"); + goto cleanup; } else { #ifdef WITH_SELINUX if ((idata->flags & PAMNS_DEBUG) && @@ -1023,10 +1340,10 @@ static int ns_setup(struct polydir_s *polyptr, * contexts, owner, group and mode bits. */ #ifdef WITH_SELINUX - retval = create_dirs(polyptr, inst_dir, instcontext, + retval = create_dirs(polyptr, inst_dir, &statbuf, instcontext, origcontext, idata); #else - retval = create_dirs(polyptr, inst_dir, idata); + retval = create_dirs(polyptr, inst_dir, &statbuf, idata); #endif if (retval < 0) { @@ -1155,34 +1472,18 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) int retval = 0, need_poly = 0, changing_dir = 0; char *cptr, *fptr, poly_parent[PATH_MAX]; struct polydir_s *pptr; - uid_t req_uid; - const void *ruser_name; - struct passwd *pwd; if (idata->flags & PAMNS_DEBUG) pam_syslog(idata->pamh, LOG_DEBUG, "Set up namespace for pid %d", getpid()); - retval = pam_get_item(idata->pamh, PAM_RUSER, &ruser_name); - if (ruser_name == NULL || retval != PAM_SUCCESS) { - retval = PAM_SUCCESS; - req_uid = getuid(); - } else { - pwd = pam_modutil_getpwnam(idata->pamh, ruser_name); - if (pwd != NULL) { - req_uid = pwd->pw_uid; - } else { - req_uid = getuid(); - } - } - /* * Cycle through all polyinstantiated directory entries to see if * polyinstantiation is needed at all. */ for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) { if (ns_override(pptr, idata, idata->uid)) { - if (unmnt == NO_UNMNT || ns_override(pptr, idata, req_uid)) { + if (unmnt == NO_UNMNT || ns_override(pptr, idata, idata->ruid)) { if (idata->flags & PAMNS_DEBUG) pam_syslog(idata->pamh, LOG_DEBUG, "Overriding poly for user %d for dir %s", @@ -1191,7 +1492,7 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) if (idata->flags & PAMNS_DEBUG) pam_syslog(idata->pamh, LOG_DEBUG, "Need unmount ns for user %d for dir %s", - idata->uid, pptr->dir); + idata->ruid, pptr->dir); need_poly = 1; break; } @@ -1207,18 +1508,11 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) } /* - * If polyinstnatiation is needed, call the unshare system call to + * If polyinstantiation is needed, call the unshare system call to * disassociate from the parent namespace. */ if (need_poly) { - if (pam_set_data(idata->pamh, NAMESPACE_POLYDIR_DATA, idata->polydirs_ptr, - cleanup_data) != PAM_SUCCESS) { - pam_syslog(idata->pamh, LOG_ERR, - "Unable to set namespace data"); - return PAM_SYSTEM_ERR; - } if (unshare(CLONE_NEWNS) < 0) { - pam_set_data(idata->pamh, NAMESPACE_POLYDIR_DATA, NULL, NULL); pam_syslog(idata->pamh, LOG_ERR, "Unable to unshare from parent namespace, %m"); return PAM_SESSION_ERR; @@ -1235,7 +1529,7 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) { enum unmnt_op dir_unmnt = unmnt; if (ns_override(pptr, idata, idata->uid)) { - if (unmnt == NO_UNMNT || ns_override(pptr, idata, req_uid)) { + if (unmnt == NO_UNMNT || ns_override(pptr, idata, idata->ruid)) { continue; } else { dir_unmnt = UNMNT_ONLY; @@ -1252,7 +1546,7 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) * bind mounted instance_parent directory that we are trying to * umount */ - if ((changing_dir = cwd_in(pptr->dir, idata)) < 0) { + if ((changing_dir = cwd_in(pptr->rdir, idata)) < 0) { retval = PAM_SESSION_ERR; goto out; } else if (changing_dir) { @@ -1265,7 +1559,7 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) * directory where original contents of the polydir * are available from */ - strcpy(poly_parent, pptr->dir); + strcpy(poly_parent, pptr->rdir); fptr = strchr(poly_parent, '/'); cptr = strrchr(poly_parent, '/'); if (fptr && cptr && (fptr == cptr)) @@ -1278,21 +1572,23 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) } } - if (umount(pptr->dir) < 0) { + if (umount(pptr->rdir) < 0) { int saved_errno = errno; pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m", - pptr->dir); + pptr->rdir); if (saved_errno != EINVAL) { retval = PAM_SESSION_ERR; goto out; } } else if (idata->flags & PAMNS_DEBUG) pam_syslog(idata->pamh, LOG_DEBUG, "Umount succeeded %s", - pptr->dir); + pptr->rdir); } if (dir_unmnt != UNMNT_ONLY) { retval = ns_setup(pptr, idata); + if (retval == PAM_IGNORE) + retval = PAM_SUCCESS; if (retval != PAM_SUCCESS) break; } @@ -1300,6 +1596,12 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) out: if (retval != PAM_SUCCESS) cleanup_tmpdirs(idata); + else if (pam_set_data(idata->pamh, NAMESPACE_POLYDIR_DATA, idata->polydirs_ptr, + cleanup_data) != PAM_SUCCESS) { + pam_syslog(idata->pamh, LOG_ERR, "Unable to set namespace data"); + cleanup_tmpdirs(idata); + return PAM_SYSTEM_ERR; + } return retval; } @@ -1354,7 +1656,7 @@ static int orig_namespace(struct instance_data *idata) * The return value from this function is used when selecting the * polyinstantiation method. If context change is not requested then * the polyinstantiation method is set to USER, even if the configuration - * file lists the method as "context" or "both". + * file lists the method as "context" or "level". */ static int ctxt_based_inst_needed(void) { @@ -1372,6 +1674,55 @@ static int ctxt_based_inst_needed(void) #endif +static int get_user_data(struct instance_data *idata) +{ + int retval; + char *user_name; + struct passwd *pwd; + /* + * Lookup user and fill struct items + */ + retval = pam_get_item(idata->pamh, PAM_USER, (void*) &user_name ); + if ( user_name == NULL || retval != PAM_SUCCESS ) { + pam_syslog(idata->pamh, LOG_ERR, "Error recovering pam user name"); + return PAM_SESSION_ERR; + } + + pwd = pam_modutil_getpwnam(idata->pamh, user_name); + if (!pwd) { + pam_syslog(idata->pamh, LOG_ERR, "user unknown '%s'", user_name); + return PAM_USER_UNKNOWN; + } + + /* + * Add the user info to the instance data so we can refer to them later. + */ + idata->user[0] = 0; + strncat(idata->user, user_name, sizeof(idata->user) - 1); + idata->uid = pwd->pw_uid; + idata->gid = pwd->pw_gid; + + /* Fill in RUSER too */ + retval = pam_get_item(idata->pamh, PAM_RUSER, (void*) &user_name ); + if ( user_name != NULL && retval == PAM_SUCCESS && user_name[0] != '\0' ) { + strncat(idata->ruser, user_name, sizeof(idata->ruser) - 1); + pwd = pam_modutil_getpwnam(idata->pamh, user_name); + } else { + pwd = pam_modutil_getpwuid(idata->pamh, getuid()); + } + if (!pwd) { + pam_syslog(idata->pamh, LOG_ERR, "user unknown '%s'", user_name); + return PAM_USER_UNKNOWN; + } + user_name = pwd->pw_name; + + idata->ruser[0] = 0; + strncat(idata->ruser, user_name, sizeof(idata->ruser) - 1); + idata->ruid = pwd->pw_uid; + + return PAM_SUCCESS; +} + /* * Entry point from pam_open_session call. */ @@ -1380,8 +1731,6 @@ PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED, { int i, retval; struct instance_data idata; - char *user_name; - struct passwd *pwd; enum unmnt_op unmnt = NO_UNMNT; /* init instance data */ @@ -1405,6 +1754,14 @@ PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED, idata.flags |= PAMNS_IGN_CONFIG_ERR; if (strcmp(argv[i], "ignore_instance_parent_mode") == 0) idata.flags |= PAMNS_IGN_INST_PARENT_MODE; + if (strcmp(argv[i], "use_current_context") == 0) { + idata.flags |= PAMNS_USE_CURRENT_CONTEXT; + idata.flags |= PAMNS_CTXT_BASED_INST; + } + if (strcmp(argv[i], "use_default_context") == 0) { + idata.flags |= PAMNS_USE_DEFAULT_CONTEXT; + idata.flags |= PAMNS_CTXT_BASED_INST; + } if (strcmp(argv[i], "unmnt_remnt") == 0) unmnt = UNMNT_REMNT; if (strcmp(argv[i], "unmnt_only") == 0) @@ -1420,27 +1777,9 @@ PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED, if (idata.flags & PAMNS_DEBUG) pam_syslog(idata.pamh, LOG_DEBUG, "open_session - start"); - /* - * Lookup user and fill struct items - */ - retval = pam_get_item(idata.pamh, PAM_USER, (void*) &user_name ); - if ( user_name == NULL || retval != PAM_SUCCESS ) { - pam_syslog(idata.pamh, LOG_ERR, "Error recovering pam user name"); - return PAM_SESSION_ERR; - } - - pwd = pam_modutil_getpwnam(idata.pamh, user_name); - if (!pwd) { - pam_syslog(idata.pamh, LOG_ERR, "user unknown '%s'", user_name); - return PAM_SESSION_ERR; - } - - /* - * Add the user info to the instance data so we can refer to them later. - */ - idata.user[0] = 0; - strncat(idata.user, user_name, sizeof(idata.user) - 1); - idata.uid = pwd->pw_uid; + retval = get_user_data(&idata); + if (retval != PAM_SUCCESS) + return retval; /* * Parse namespace configuration file which lists directories to @@ -1480,8 +1819,6 @@ PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED, { int i, retval; struct instance_data idata; - char *user_name; - struct passwd *pwd; void *polyptr; /* init instance data */ @@ -1524,27 +1861,9 @@ PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED, return PAM_SUCCESS; } - /* - * Lookup user and fill struct items - */ - retval = pam_get_item(idata.pamh, PAM_USER, (void*) &user_name ); - if ( user_name == NULL || retval != PAM_SUCCESS ) { - pam_syslog(idata.pamh, LOG_ERR, "Error recovering pam user name"); - return PAM_SESSION_ERR; - } - - pwd = pam_modutil_getpwnam(idata.pamh, user_name); - if (!pwd) { - pam_syslog(idata.pamh, LOG_ERR, "user unknown '%s'", user_name); - return PAM_SESSION_ERR; - } - - /* - * Add the user info to the instance data so we can refer to them later. - */ - idata.user[0] = 0; - strncat(idata.user, user_name, sizeof(idata.user) - 1); - idata.uid = pwd->pw_uid; + retval = get_user_data(&idata); + if (retval != PAM_SUCCESS) + return retval; retval = pam_get_data(idata.pamh, NAMESPACE_POLYDIR_DATA, (const void **)&polyptr); if (retval != PAM_SUCCESS || polyptr == NULL) diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h index 4b438899..bfc0da17 100644 --- a/modules/pam_namespace/pam_namespace.h +++ b/modules/pam_namespace/pam_namespace.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,8 @@ #include #include #include +#include +#include #include "security/pam_modules.h" #include "security/pam_modutil.h" #include "security/pam_ext.h" @@ -63,6 +66,7 @@ #ifdef WITH_SELINUX #include +#include #include #endif @@ -73,14 +77,16 @@ /* * Module defines */ -#ifndef PAM_NAMESPACE_CONFIG -#define PAM_NAMESPACE_CONFIG "/etc/security/namespace.conf" +#ifndef SECURECONF_DIR +#define SECURECONF_DIR "/etc/security/" #endif -#ifndef NAMESPACE_INIT_SCRIPT -#define NAMESPACE_INIT_SCRIPT "/etc/security/namespace.init" -#endif +#define PAM_NAMESPACE_CONFIG (SECURECONF_DIR "namespace.conf") +#define NAMESPACE_INIT_SCRIPT (SECURECONF_DIR "namespace.init") +#define NAMESPACE_D_DIR (SECURECONF_DIR "namespace.d/") +#define NAMESPACE_D_GLOB (SECURECONF_DIR "namespace.d/*.conf") +/* module flags */ #define PAMNS_DEBUG 0x00000100 /* Running in debug mode */ #define PAMNS_SELINUX_ENABLED 0x00000400 /* SELinux is enabled */ #define PAMNS_CTXT_BASED_INST 0x00000800 /* Context based instance needed */ @@ -88,6 +94,16 @@ #define PAMNS_IGN_CONFIG_ERR 0x00004000 /* Ignore format error in conf file */ #define PAMNS_IGN_INST_PARENT_MODE 0x00008000 /* Ignore instance parent mode */ #define PAMNS_NO_UNMOUNT_ON_CLOSE 0x00010000 /* no unmount at session close */ +#define PAMNS_USE_CURRENT_CONTEXT 0x00020000 /* use getcon instead of getexeccon */ +#define PAMNS_USE_DEFAULT_CONTEXT 0x00040000 /* use get_default_context instead of getexeccon */ + +/* polydir flags */ +#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstatiate exclusively for override uids */ +#define POLYDIR_CREATE 0x00000002 /* create the polydir */ +#define POLYDIR_NOINIT 0x00000004 /* no init script */ +#define POLYDIR_SHARED 0x00000008 /* share context/level instances among users */ +#define POLYDIR_ISCRIPT 0x00000010 /* non default init script */ + #define NAMESPACE_MAX_DIR_LEN 80 #define NAMESPACE_POLYDIR_DATA "pam_namespace:polydir_data" @@ -127,11 +143,16 @@ enum unmnt_op { */ struct polydir_s { char dir[PATH_MAX]; /* directory to polyinstantiate */ + char rdir[PATH_MAX]; /* directory to unmount (based on RUSER) */ char instance_prefix[PATH_MAX]; /* prefix for instance dir path name */ enum polymethod method; /* method used to polyinstantiate */ unsigned int num_uids; /* number of override uids */ uid_t *uid; /* list of override uids */ - int exclusive; /* polyinstatiate exclusively for override uids */ + unsigned int flags; /* polydir flags */ + char *init_script; /* path to init script */ + uid_t owner; /* user which should own the polydir */ + gid_t group; /* group which should own the polydir */ + mode_t mode; /* mode of the polydir */ struct polydir_s *next; /* pointer to the next polydir entry */ }; @@ -139,6 +160,9 @@ struct instance_data { pam_handle_t *pamh; /* The pam handle for this instance */ struct polydir_s *polydirs_ptr; /* The linked list pointer */ char user[LOGIN_NAME_MAX]; /* User name */ + char ruser[LOGIN_NAME_MAX]; /* Requesting user name */ uid_t uid; /* The uid of the user */ - unsigned long flags; /* Flags for debug, selinux etc */ + gid_t gid; /* The gid of the user's primary group */ + uid_t ruid; /* The uid of the requesting user */ + unsigned long flags; /* Flags for debug, selinux etc */ }; -- cgit v1.2.3