diff --git a/ChangeLog b/ChangeLog index 6a0095d7..5940ba74 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,32 @@ -2009-01-24 Nicolas François +2010-01-30 Paweł Hajdan, Jr. + + * NEWS: Add support for TCB. + * lib/tcbfuncs.h, lib/tcbfuncs.c, lib/Makefile.am: New library to + support TCB. + * lib/prototypes, libmisc/copydir.c (remove_tree): Add boolean + parameter remove_root. + * configure.in: Add conditional WITH_TCB. + * src/userdel.c, src/usermod.c: Add support for TCB. Update call to + remove_tree(). + * src/pwconv.c, src/pwunconv.c: Should not be used with TCB enabled. + * src/vipw.c: Add support for TCB. Update call to remove_tree(). + * src/useradd.c: Add support for TCB. Open the shadow file outside + of open_files(). + * src/chage.c: Add support for TCB. + * src/Makefile.am: Install passwd sgid shadow when TCB is enabled. + * lib/getdefs.c, man/vipw.8.xml, man/login.defs.5.xml, + man/login.defs/TCB_AUTH_GROUP.xml, man/login.defs/USE_TCB.xml, + man/login.defs/TCB_SYMLINKS.xml, man/generate_mans.mak, + man/generate_mans.deps, man/Makefile.am: New configuration + parameters: TCB_AUTH_GROUP, TCB_SYMLINKS, USE_TCB. + * lib/shadowio.c, lib/commonio.c: Add support for TCB. + +2010-01-24 Nicolas François * libmisc/env.c: Fix sanitize_env() noslash support. This fixes Alioth#311740. -2009-01-24 Nicolas François +2010-01-24 Nicolas François * src/su.c: Do not sanitize the environment. This breaks --preserve-environment. This sanitation was disabled on Debian @@ -12,11 +35,11 @@ Unixes will handle setuid executables properly. This fixes Alioth#312287. -2009-01-24 Nicolas François +2010-01-24 Nicolas François * libmisc/setupenv.c: Fix typo from 2009-11-01. -2009-01-24 Paweł Hajdan, Jr. +2010-01-24 Paweł Hajdan, Jr. * configure.in: Add support for TCB in configure.in. Actual TCB support will follow. diff --git a/NEWS b/NEWS index 474ef8b0..9d4bebfe 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ shadow-4.1.4.2 -> shadow-4.1.4.3 UNRELEASED - general * report usage error to stderr, but report usage help to stdout (and return zero) when explicitly requested (e.g. with --help). + * initial support for tcb (http://openwall.com/tcb/). - groupmod * Fixed groupmod when configured with --enable-account-tools-setuid. diff --git a/configure.in b/configure.in index 4d634f19..f99ab2c7 100644 --- a/configure.in +++ b/configure.in @@ -405,6 +405,7 @@ if test "$with_tcb" != "no"; then with_tcb="no" fi fi +AM_CONDITIONAL(WITH_TCB, test x$with_tcb = xyes) AC_SUBST(LIBPAM) if test "$with_libpam" != "no"; then diff --git a/lib/Makefile.am b/lib/Makefile.am index 024297ef..ee7ec6ee 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -49,6 +49,10 @@ libshadow_la_SOURCES = \ shadowmem.c \ utent.c +if WITH_TCB +libshadow_la_SOURCES += tcbfuncs.c tcbfuncs.h +endif + # These files are unneeded for some reason, listed in # order of appearance: # diff --git a/lib/commonio.c b/lib/commonio.c index 3850da12..618a80e1 100644 --- a/lib/commonio.c +++ b/lib/commonio.c @@ -48,6 +48,9 @@ #ifdef WITH_SELINUX #include #endif +#ifdef WITH_TCB +#include +#endif #include "prototypes.h" #include "commonio.h" @@ -533,6 +536,7 @@ int commonio_open (struct commonio_db *db, int mode) void *eptr = NULL; int flags = mode; size_t buflen; + int fd; int saved_errno; mode &= ~O_CREAT; @@ -553,7 +557,24 @@ int commonio_open (struct commonio_db *db, int mode) db->cursor = NULL; db->changed = false; - db->fp = fopen (db->filename, db->readonly ? "r" : "r+"); + fd = open(db->filename, (db->readonly ? O_RDONLY : O_RDWR) | + O_NOCTTY | O_NONBLOCK | O_NOFOLLOW); + saved_errno = errno; + db->fp = NULL; + if (fd >= 0) { +#ifdef WITH_TCB + if (tcb_is_suspect(fd)) { + close(fd); + errno = EINVAL; + return 0; + } +#endif + db->fp = fdopen(fd, db->readonly ? "r" : "r+"); + saved_errno = errno; + if (!db->fp) + close(fd); + } + errno = saved_errno; /* * If O_CREAT was specified and the file didn't exist, it will be diff --git a/lib/getdef.c b/lib/getdef.c index c36dc15e..19c756bd 100644 --- a/lib/getdef.c +++ b/lib/getdef.c @@ -123,6 +123,11 @@ static struct itemdef def_table[] = { #ifdef USE_SYSLOG {"SYSLOG_SG_ENAB", NULL}, {"SYSLOG_SU_ENAB", NULL}, +#endif +#ifdef WITH_TCB + {"TCB_AUTH_GROUP", NULL}, + {"TCB_SYMLINKS", NULL}, + {"USE_TCB", NULL}, #endif {NULL, NULL} }; diff --git a/lib/prototypes.h b/lib/prototypes.h index c6180191..a4f52421 100644 --- a/lib/prototypes.h +++ b/lib/prototypes.h @@ -117,7 +117,7 @@ extern bool console (const char *); /* copydir.c */ extern int copy_tree (const char *src_root, const char *dst_root, long int uid, long int gid); -extern int remove_tree (const char *root); +extern int remove_tree (const char *root, bool remove_root); #ifdef WITH_SELINUX extern int selinux_file_context (const char *dst_name); diff --git a/lib/shadowio.c b/lib/shadowio.c index 9ea6a55b..cda02c2d 100644 --- a/lib/shadowio.c +++ b/lib/shadowio.c @@ -41,6 +41,10 @@ #include #include "commonio.h" #include "shadowio.h" +#ifdef WITH_TCB +#include +#include "tcbfuncs.h" +#endif static /*@null@*/ /*@only@*/void *shadow_dup (const void *ent) { @@ -120,12 +124,40 @@ bool spw_file_present (void) int spw_lock (void) { - return commonio_lock (&shadow_db); +#ifdef WITH_TCB + int retval = 0; + + if (!getdef_bool("USE_TCB")) +#endif + return commonio_lock (&shadow_db); +#ifdef WITH_TCB + if (!shadowtcb_drop_priv()) + return 0; + if (lckpwdf_tcb(shadow_db.filename) == 0) { + shadow_db.locked = 1; + retval = 1; + } + if (!shadowtcb_gain_priv()) + return 0; + return retval; +#endif } int spw_open (int mode) { - return commonio_open (&shadow_db, mode); + int retval = 0; +#ifdef WITH_TCB + int use_tcb = getdef_bool("USE_TCB"); + + if (use_tcb && !shadowtcb_drop_priv() != 0) + return 0; +#endif + retval = commonio_open (&shadow_db, mode); +#ifdef WITH_TCB + if (use_tcb && !shadowtcb_gain_priv() != 0) + return 0; +#endif + return retval; } /*@observer@*/ /*@null@*/const struct spwd *spw_locate (const char *name) @@ -155,12 +187,40 @@ int spw_rewind (void) int spw_close (void) { - return commonio_close (&shadow_db); + int retval = 0; +#ifdef WITH_TCB + int use_tcb = getdef_bool("USE_TCB"); + + if (use_tcb && !shadowtcb_drop_priv() != 0) + return 0; +#endif + retval = commonio_close (&shadow_db); +#ifdef WITH_TCB + if (use_tcb && !shadowtcb_gain_priv() != 0) + return 0; +#endif + return retval; } int spw_unlock (void) { - return commonio_unlock (&shadow_db); +#ifdef WITH_TCB + int retval = 0; + + if (!getdef_bool("USE_TCB")) +#endif + return commonio_unlock (&shadow_db); +#ifdef WITH_TCB + if (!shadowtcb_drop_priv()) + return 0; + if (ulckpwdf_tcb() == 0) { + shadow_db.locked = 0; + retval = 1; + } + if (!shadowtcb_gain_priv()) + return 0; + return retval; +#endif } struct commonio_entry *__spw_get_head (void) @@ -176,5 +236,9 @@ void __spw_del_entry (const struct commonio_entry *ent) /* Sort with respect to passwd ordering. */ int spw_sort () { +#ifdef WITH_TCB + if (getdef_bool("USE_TCB")) + return 0; +#endif return commonio_sort_wrt (&shadow_db, __pw_get_db ()); } diff --git a/lib/tcbfuncs.c b/lib/tcbfuncs.c new file mode 100644 index 00000000..fdbe93e8 --- /dev/null +++ b/lib/tcbfuncs.c @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2001 Rafal Wojtczuk, Solar Designer + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "defines.h" +#include "getdef.h" + +#define SHADOWTCB_HASH_BY 1000 +#define SHADOWTCB_LOCK_SUFFIX ".lock" + +static char *stored_tcb_user = NULL; + +int shadowtcb_drop_priv() +{ + if (!getdef_bool("USE_TCB")) + return 1; + + if (stored_tcb_user) + return !tcb_drop_priv(stored_tcb_user); + + return 0; +} + +int shadowtcb_gain_priv() +{ + if (!getdef_bool("USE_TCB")) + return 1; + return !tcb_gain_priv(); +} + +/* In case something goes wrong, we return immediately, not polluting the + * code with free(). All errors are fatal, so the application is expected + * to exit soon. + */ +#define OUT_OF_MEMORY do { \ + fprintf(stderr, "Out of memory.\n"); \ + fflush(stderr); \ + return 0; \ +} while(0) + +/* Returns user's tcb directory path relative to TCB_DIR. */ +static char *shadowtcb_path_rel(const char *name, uid_t uid) +{ + char *ret; + + if (!getdef_bool("TCB_SYMLINKS") || uid < SHADOWTCB_HASH_BY) { + asprintf(&ret, "%s", name); + } else if (uid < SHADOWTCB_HASH_BY * SHADOWTCB_HASH_BY) { + asprintf(&ret, ":%dK/%s", uid / SHADOWTCB_HASH_BY, name); + } else { + asprintf(&ret, ":%dM/:%dK/%s", + uid / (SHADOWTCB_HASH_BY * SHADOWTCB_HASH_BY), + (uid % (SHADOWTCB_HASH_BY * SHADOWTCB_HASH_BY)) / SHADOWTCB_HASH_BY, + name); + } + if (!ret) { + OUT_OF_MEMORY; + } + return ret; +} + +static char *shadowtcb_path_rel_existing(const char *name) +{ + char *path, *rval; + struct stat st; + char link[8192]; + int ret; + + asprintf(&path, TCB_DIR "/%s", name); + if (!path) { + OUT_OF_MEMORY; + } + if (lstat(path, &st)) { + fprintf(stderr, "Cannot stat %s: %s\n", path, strerror(errno)); + free(path); + return NULL; + } + if (S_ISDIR(st.st_mode)) { + free(path); + rval = strdup(name); + if (!rval) { + OUT_OF_MEMORY; + } + return rval; + } + if (!S_ISLNK(st.st_mode)) { + fprintf(stderr, "%s is neither a directory, nor a symlink.\n", path); + free(path); + return NULL; + } + ret = readlink(path, link, sizeof(link) - 1); + free(path); + if (ret == -1) { + perror("readlink"); + return NULL; + } + if (ret >= sizeof(link) - 1) { + link[sizeof(link) - 1] = '\0'; + fprintf(stderr, "Suspiciously long symlink: %s\n", link); + return NULL; + } + link[ret] = '\0'; + rval = strdup(link); + if (!rval) { + OUT_OF_MEMORY; + } + return rval; +} + +static char *shadowtcb_path(const char *name, uid_t uid) +{ + char *ret, *rel; + + if (!(rel = shadowtcb_path_rel(name, uid))) + return 0; + asprintf(&ret, TCB_DIR "/%s", rel); + free(rel); + if (!ret) { + OUT_OF_MEMORY; + } + return ret; +} + +static char *shadowtcb_path_existing(const char *name) +{ + char *ret, *rel; + + if (!(rel = shadowtcb_path_rel_existing(name))) + return 0; + asprintf(&ret, TCB_DIR "/%s", rel); + free(rel); + if (!ret) { + OUT_OF_MEMORY; + } + return ret; +} + +static int mkdir_leading(const char *name, uid_t uid) +{ + char *ind, *dir, *ptr, *path = shadowtcb_path_rel(name, uid); + struct stat st; + + if (!path) + return 0; + ptr = path; + if (stat(TCB_DIR, &st)) { + perror("stat"); + goto out_free_path; + } + while ((ind = strchr(ptr, '/'))) { + *ind = 0; + asprintf(&dir, TCB_DIR "/%s", path); + if (!dir) { + OUT_OF_MEMORY; + } + if (mkdir(dir, 0700) && errno != EEXIST) { + perror("mkdir"); + goto out_free_dir; + } + if (chown(dir, 0, st.st_gid)) { + perror("chown"); + goto out_free_dir; + } + if (chmod(dir, 0711)) { + perror("chmod"); + goto out_free_dir; + } + free(dir); + *ind = '/'; + ptr = ind + 1; + } + free(path); + return 1; +out_free_dir: + free(dir); +out_free_path: + free(path); + return 0; +} + +static int unlink_suffs(const char *user) +{ + static char *suffs[] = { "+", "-", SHADOWTCB_LOCK_SUFFIX }; + char *tmp; + int i; + + for (i = 0; i < 3; i++) { + asprintf(&tmp, TCB_FMT "%s", user, suffs[i]); + if (!tmp) { + OUT_OF_MEMORY; + } + if (unlink(tmp) && errno != ENOENT) { + fprintf(stderr, "unlink: %s: %s\n", tmp, + strerror(errno)); + free(tmp); + return 0; + } + free(tmp); + } + + return 1; +} + +/* path should be a relative existing tcb directory */ +static int rmdir_leading(char *path) +{ + char *ind, *dir; + int ret = 1; + while ((ind = strrchr(path, '/'))) { + *ind = 0; + asprintf(&dir, TCB_DIR "/%s", path); + if (!dir) { + OUT_OF_MEMORY; + } + if (rmdir(dir)) { + if (errno != ENOTEMPTY) { + perror("rmdir"); + ret = 0; + } + free(dir); + break; + } + free(dir); + } + return ret; +} + +static int move_dir(const char *user_newname, uid_t user_newid) +{ + char *olddir = NULL, *newdir = NULL; + char *real_old_dir = NULL, *real_new_dir = NULL; + char *real_old_dir_rel = NULL, *real_new_dir_rel = NULL; + uid_t old_uid, the_newid; + struct stat oldmode; + int ret = 0; + + asprintf(&olddir, TCB_DIR "/%s", stored_tcb_user); + if (!olddir) + goto out_free_nomem; + if (stat(olddir, &oldmode)) { + perror("stat"); + goto out_free; + } + old_uid = oldmode.st_uid; + the_newid = (user_newid == -1) ? old_uid : user_newid; + if (!(real_old_dir = shadowtcb_path_existing(stored_tcb_user))) + goto out_free; + if (!(real_new_dir = shadowtcb_path(user_newname, the_newid))) + goto out_free; + if (!strcmp(real_old_dir, real_new_dir)) { + ret = 1; + goto out_free; + } + if (!(real_old_dir_rel = shadowtcb_path_rel_existing(stored_tcb_user))) + goto out_free; + if (!mkdir_leading(user_newname, the_newid)) + goto out_free; + if (rename(real_old_dir, real_new_dir)) { + perror("rename"); + goto out_free; + } + if (!rmdir_leading(real_old_dir_rel)) + goto out_free; + if (unlink(olddir) && errno != ENOENT) { + perror("unlink"); + goto out_free; + } + asprintf(&newdir, TCB_DIR "/%s", user_newname); + if (!newdir) + goto out_free_nomem; + if (!(real_new_dir_rel = shadowtcb_path_rel(user_newname, the_newid))) + goto out_free; + if (strcmp(real_new_dir, newdir) && symlink(real_new_dir_rel, newdir)) { + perror("symlink"); + goto out_free; + } + ret = 1; + goto out_free; +out_free_nomem: + fprintf(stderr, "Out of memory\n"); + fflush(stderr); +out_free: + free(olddir); + free(newdir); + free(real_old_dir); + free(real_new_dir); + free(real_old_dir_rel); + free(real_new_dir_rel); + return ret; +} + +int shadowtcb_set_user(const char* name) +{ + char *buf; + int retval; + + if (!getdef_bool("USE_TCB")) + return 1; + + if (stored_tcb_user) + free(stored_tcb_user); + + stored_tcb_user = strdup(name); + if (!stored_tcb_user) { + OUT_OF_MEMORY; + } + asprintf(&buf, TCB_FMT, name); + if (!buf) { + OUT_OF_MEMORY; + } + + retval = spw_setdbname(buf); + free(buf); + return retval; +} + +/* tcb directory must be empty before shadowtcb_remove is called. */ +int shadowtcb_remove(const char *name) +{ + int ret = 1; + char *path = shadowtcb_path_existing(name); + char *rel = shadowtcb_path_rel_existing(name); + if (!path || !rel || rmdir(path)) + return 0; + if (!rmdir_leading(rel)) + return 0; + free(path); + free(rel); + asprintf(&path, TCB_DIR "/%s", name); + if (!path) { + OUT_OF_MEMORY; + } + if (unlink(path) && errno != ENOENT) + ret = 0; + free(path); + return ret; +} + +int shadowtcb_move(const char *user_newname, uid_t user_newid) +{ + struct stat dirmode, filemode; + char *tcbdir, *shadow; + int ret = 0; + + if (!getdef_bool("USE_TCB")) + return 1; + if (!user_newname) + user_newname = stored_tcb_user; + if (!move_dir(user_newname, user_newid)) + return 0; + if (user_newid == -1) + return 1; + asprintf(&tcbdir, TCB_DIR "/%s", user_newname); + asprintf(&shadow, TCB_FMT, user_newname); + if (!tcbdir || !shadow) { + OUT_OF_MEMORY; + } + if (stat(tcbdir, &dirmode)) { + perror("stat"); + goto out_free; + } + if (chown(tcbdir, 0, 0)) { + perror("chown"); + goto out_free; + } + if (chmod(tcbdir, 0700)) { + perror("chmod"); + goto out_free; + } + if (lstat(shadow, &filemode)) { + if (errno != ENOENT) { + perror("lstat"); + goto out_free; + } + fprintf(stderr, + "Warning, user %s has no tcb shadow file.\n", + user_newname); + } else { + if (!S_ISREG(filemode.st_mode) || + filemode.st_nlink != 1) { + fprintf(stderr, + "Emergency: %s's tcb shadow is not a regular file" + " with st_nlink=1.\n" + "The account is left locked.\n", + user_newname); + goto out_free; + } + if (chown(shadow, user_newid, filemode.st_gid)) { + perror("chown"); + goto out_free; + } + if (chmod(shadow, filemode.st_mode & 07777)) { + perror("chmod"); + goto out_free; + } + } + if (!unlink_suffs(user_newname)) + goto out_free; + if (chown(tcbdir, user_newid, dirmode.st_gid)) { + perror("chown"); + goto out_free; + } + ret = 1; +out_free: + free(tcbdir); + free(shadow); + return ret; +} + +int shadowtcb_create(const char *name, uid_t uid) +{ + char *dir, *shadow; + struct stat tcbdir_stat; + gid_t shadowgid, authgid; + struct group *gr; + int fd, ret = 0; + + if (!getdef_bool("USE_TCB")) + return 1; + if (stat(TCB_DIR, &tcbdir_stat)) { + perror("stat"); + return 0; + } + shadowgid = tcbdir_stat.st_gid; + if (getdef_bool("TCB_AUTH_GROUP") && + (gr = getgrnam("auth"))) { + authgid = gr->gr_gid; + } else { + authgid = shadowgid; + } + + asprintf(&dir, TCB_DIR "/%s", name); + asprintf(&shadow, TCB_FMT, name); + if (!dir || !shadow) { + OUT_OF_MEMORY; + } + if (mkdir(dir, 0700)) { + fprintf(stderr, "mkdir: %s: %s\n", dir, strerror(errno)); + goto out_free; + return 0; + } + fd = open(shadow, O_RDWR | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { + perror("open"); + goto out_free; + } + close(fd); + if (chown(shadow, 0, authgid)) { + perror("chown"); + goto out_free; + } + if (chmod(shadow, authgid == shadowgid ? 0600 : 0640)) { + perror("chmod"); + goto out_free; + } + if (chown(dir, 0, authgid)) { + perror("chown"); + goto out_free; + } + if (chmod(dir, authgid == shadowgid ? 02700 : 02710)) { + perror("chmod"); + goto out_free; + } + if (!shadowtcb_set_user(name) || !shadowtcb_move(NULL, uid)) + goto out_free; + ret = 1; +out_free: + free(dir); + free(shadow); + return ret; +} diff --git a/lib/tcbfuncs.h b/lib/tcbfuncs.h new file mode 100644 index 00000000..51d47824 --- /dev/null +++ b/lib/tcbfuncs.h @@ -0,0 +1,13 @@ +#ifndef _TCBFUNCS_H +#define _TCBFUNCS_H + +#include + +extern int shadowtcb_drop_priv(); +extern int shadowtcb_gain_priv(); +extern int shadowtcb_set_user(const char *name); +extern int shadowtcb_remove(const char *name); +extern int shadowtcb_move(const char *user_newname, uid_t user_newid); +extern int shadowtcb_create(const char *name, uid_t uid); + +#endif diff --git a/libmisc/copydir.c b/libmisc/copydir.c index ad3c9931..3a67e75d 100644 --- a/libmisc/copydir.c +++ b/libmisc/copydir.c @@ -668,7 +668,7 @@ static int copy_file (const char *src, const char *dst, * At the end, it deletes the root directory itself. */ -int remove_tree (const char *root) +int remove_tree (const char *root, bool remove_root) { char *new_name = NULL; int err = 0; @@ -721,7 +721,7 @@ int remove_tree (const char *root) /* * Recursively delete this directory. */ - if (remove_tree (new_name) != 0) { + if (remove_tree (new_name, true) != 0) { err = -1; break; } @@ -740,7 +740,7 @@ int remove_tree (const char *root) } (void) closedir (dir); - if (0 == err) { + if (remove_root && 0 == err) { if (rmdir (root) != 0) { err = -1; } diff --git a/man/Makefile.am b/man/Makefile.am index a8e9a5a6..850f1567 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -149,6 +149,8 @@ login_defs_v = \ SU_WHEEL_ONLY.xml \ SYSLOG_SG_ENAB.xml \ SYSLOG_SU_ENAB.xml \ + TCB_AUTH_GROUP.xml \ + TCB_SYMLINKS.xml \ TTYGROUP.xml \ TTYTYPE_FILE.xml \ UID_MAX.xml \ @@ -156,6 +158,7 @@ login_defs_v = \ UMASK.xml \ USERDEL_CMD.xml \ USERGROUPS_ENAB.xml \ + USE_TCB.xml \ SYS_GID_MAX.xml \ SYS_UID_MAX.xml diff --git a/man/generate_mans.deps b/man/generate_mans.deps index 264fa0ac..32abd8fb 100644 --- a/man/generate_mans.deps +++ b/man/generate_mans.deps @@ -105,6 +105,8 @@ login.defs.5: login.defs.d/SYS_GID_MAX.xml login.defs.5: login.defs.d/SYSLOG_SG_ENAB.xml login.defs.5: login.defs.d/SYSLOG_SU_ENAB.xml login.defs.5: login.defs.d/SYS_UID_MAX.xml +login.defs.5: login.defs.d/TCB_AUTH_GROUP.xml +login.defs.5: login.defs.d/TCB_SYMLINKS.xml login.defs.5: login.defs.d/TTYGROUP.xml login.defs.5: login.defs.d/TTYTYPE_FILE.xml login.defs.5: login.defs.d/UID_MAX.xml @@ -112,6 +114,7 @@ login.defs.5: login.defs.d/ULIMIT.xml login.defs.5: login.defs.d/UMASK.xml login.defs.5: login.defs.d/USERDEL_CMD.xml login.defs.5: login.defs.d/USERGROUPS_ENAB.xml +login.defs.5: login.defs.d/USE_TCB.xml newgrp.1: login.defs.d/SYSLOG_SG_ENAB.xml newusers.8: login.defs.d/ENCRYPT_METHOD.xml newusers.8: login.defs.d/GID_MAX.xml diff --git a/man/generate_mans.mak b/man/generate_mans.mak index 9d4c83e5..82fcfcdb 100644 --- a/man/generate_mans.mak +++ b/man/generate_mans.mak @@ -8,6 +8,11 @@ SHADOWGRP_COND=gshadow else SHADOWGRP_COND=no_gshadow endif +if WITH_TCB +TCB_COND=tcb +else +TCB_COND=no_tcb +endif if USE_SHA_CRYPT SHA_CRYPT_COND=sha_crypt @@ -20,7 +25,7 @@ endif %: %.xml-config Makefile config.xml if ENABLE_REGENERATE_MAN - $(XSLTPROC) --stringparam profile.condition "$(PAM_COND);$(SHADOWGRP_COND);$(SHA_CRYPT_COND)" \ + $(XSLTPROC) --stringparam profile.condition "$(PAM_COND);$(SHADOWGRP_COND);$(TCB_COND);$(SHA_CRYPT_COND)" \ -nonet http://docbook.sourceforge.net/release/xsl/current/manpages/profile-docbook.xsl $< else @echo you need to run configure with --enable-man to generate man pages diff --git a/man/login.defs.5.xml b/man/login.defs.5.xml index ece0c469..f7732c89 100644 --- a/man/login.defs.5.xml +++ b/man/login.defs.5.xml @@ -82,6 +82,8 @@ + + @@ -89,6 +91,7 @@ + ]> @@ -195,6 +198,8 @@ &SYS_UID_MAX; &SYSLOG_SG_ENAB; &SYSLOG_SU_ENAB; + &TCB_AUTH_GROUP; + &TCB_SYMLINKS; &TTYGROUP; &TTYTYPE_FILE; &UID_MAX; @@ -202,6 +207,7 @@ &UMASK; &USERDEL_CMD; &USERGROUPS_ENAB; + &USE_TCB; @@ -381,16 +387,27 @@ PASS_MAX_DAYS PASS_MIN_DAYS PASS_WARN_AGE + USE_TCB pwconv - PASS_MAX_DAYS PASS_MIN_DAYS PASS_WARN_AGE + + PASS_MAX_DAYS PASS_MIN_DAYS PASS_WARN_AGE + USE_TCB + + + + + pwunconv + + + USE_TCB + - su @@ -427,6 +444,7 @@ PASS_MAX_DAYS PASS_MIN_DAYS PASS_WARN_AGE SYS_GID_MAX SYS_GID_MIN SYS_UID_MAX SYS_UID_MIN UID_MAX UID_MIN UMASK + TCB_AUTH_GROUP TCB_SYMLINK USE_TCB @@ -436,6 +454,7 @@ MAIL_DIR MAIL_FILE MAX_MEMBERS_PER_GROUP USERDEL_CMD USERGROUPS_ENAB + USE_TCB @@ -444,10 +463,18 @@ MAIL_DIR MAIL_FILE MAX_MEMBERS_PER_GROUP + USE_TCB + + + + + vipw + + + USE_TCB - diff --git a/man/login.defs.d/TCB_AUTH_GROUP.xml b/man/login.defs.d/TCB_AUTH_GROUP.xml new file mode 100644 index 00000000..f9c9f72c --- /dev/null +++ b/man/login.defs.d/TCB_AUTH_GROUP.xml @@ -0,0 +1,37 @@ + + + (boolean) + + + If yes, newly created tcb shadow files + will be group owned by the auth group. + + + diff --git a/man/login.defs.d/TCB_SYMLINKS.xml b/man/login.defs.d/TCB_SYMLINKS.xml new file mode 100644 index 00000000..76b34fb7 --- /dev/null +++ b/man/login.defs.d/TCB_SYMLINKS.xml @@ -0,0 +1,53 @@ + + + (boolean) + + + If yes, the location of the user tcb + directory to be created will not be automatically set to /etc/tcb/user, + but will be computed depending on the UID of the user, according to + the following algorithm: + +if ( UID is less than 1000) { + use /etc/tcb/user +} else if ( UID is less than 1000000) { + kilos = UID / 1000 + use /etc/tcb/:kilos/user + make symlink /etc/tcb/user to the above directory +} else { + megas = UID / 1000000 + kilos = ( UID / megas * 1000000 ) / 1000 + use /etc/tcb/:megas/:kilos/user + make symlink /etc/tcb/user to the above directory +} + + + + diff --git a/man/login.defs.d/USE_TCB.xml b/man/login.defs.d/USE_TCB.xml new file mode 100644 index 00000000..6fbe4d9f --- /dev/null +++ b/man/login.defs.d/USE_TCB.xml @@ -0,0 +1,38 @@ + + + (boolean) + + + If yes, the + tcb5 + password shadowing scheme will be used. + + + diff --git a/man/vipw.8.xml b/man/vipw.8.xml index d96fcf69..4557e9a5 100644 --- a/man/vipw.8.xml +++ b/man/vipw.8.xml @@ -117,6 +117,12 @@ Edit shadow or gshadow database. + + , + + Indicates which user's tcb shadow file to edit. + + @@ -165,6 +171,9 @@ passwd5 , + + tcb5 + , shadow5 . diff --git a/src/Makefile.am b/src/Makefile.am index 6a3b4c5c..9bfd6ec0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,6 +5,7 @@ EXTRA_DIST = \ ubindir = ${prefix}/bin usbindir = ${prefix}/sbin suidperms = 4755 +sgidperms = 2755 INCLUDES = \ -I${top_srcdir}/lib \ @@ -53,7 +54,13 @@ if ACCT_TOOLS_SETUID suidubins += chage chgpasswd chpasswd groupadd groupdel groupmod newusers useradd userdel usermod endif +if WITH_TCB +suidubins -= passwd +shadowsgidubins = passwd +endif + LDADD = $(INTLLIBS) \ + $(LIBTCB) \ $(top_builddir)/libmisc/libmisc.a \ $(top_builddir)/lib/libshadow.la AM_CPPFLAGS = -DLOCALEDIR=\"$(datadir)/locale\" @@ -114,3 +121,9 @@ install-am: all-am for i in $(suidubins); do \ chmod -f $(suidperms) $(DESTDIR)$(ubindir)/$$i; \ done +if WITH_TCB + for i in $(shadowsgidubins); do \ + chown root:shadow $(DESTDIR)$(ubindir)/$$i; \ + chmod -f $(sgidperms) $(DESTDIR)$(ubindir)/$$i; \ + done +endif diff --git a/src/chage.c b/src/chage.c index f8b27b05..4788a202 100644 --- a/src/chage.c +++ b/src/chage.c @@ -56,6 +56,9 @@ #include "defines.h" #include "pwio.h" #include "shadowio.h" +#ifdef WITH_TCB +#include "tcbfuncs.h" +#endif /*@-exitarg@*/ #include "exitcodes.h" @@ -853,6 +856,10 @@ int main (int argc, char **argv) } STRFCPY (user_name, pw->pw_name); +#ifdef WITH_TCB + if (!shadowtcb_set_user(pw->pw_name)) + fail_exit(E_NOPERM); +#endif user_uid = pw->pw_uid; sp = spw_locate (argv[optind]); diff --git a/src/pwconv.c b/src/pwconv.c index d0a1b399..b59777bd 100644 --- a/src/pwconv.c +++ b/src/pwconv.c @@ -133,6 +133,11 @@ int main (int argc, char **argv) OPENLOG ("pwconv"); + if (getdef_bool("USE_TCB")) { + fprintf(stderr, _("%s: can't work with tcb enabled\n"), Prog); + fail_exit(E_FAILURE); + } + if (pw_lock () == 0) { fprintf (stderr, _("%s: cannot lock %s; try again later.\n"), diff --git a/src/pwunconv.c b/src/pwunconv.c index 7bc49565..f49a9f7a 100644 --- a/src/pwunconv.c +++ b/src/pwunconv.c @@ -93,6 +93,11 @@ int main (int argc, char **argv) OPENLOG ("pwunconv"); + if (getdef_bool("USE_TCB")) { + fprintf(stderr, _("%s: can't work with tcb enabled\n"), Prog); + exit(1); + } + if (!spw_file_present ()) { /* shadow not installed, do nothing */ exit (0); diff --git a/src/useradd.c b/src/useradd.c index 3581b378..cd83ad9e 100644 --- a/src/useradd.c +++ b/src/useradd.c @@ -65,6 +65,9 @@ #include "sgroupio.h" #endif #include "shadowio.h" +#ifdef WITH_TCB +#include "tcbfuncs.h" +#endif #ifndef SKEL_DIR #define SKEL_DIR "/etc/skel" @@ -192,6 +195,7 @@ static void grp_update (void); static void process_flags (int argc, char **argv); static void close_files (void); static void open_files (void); +static void open_shadow (void); static void faillog_reset (uid_t); static void lastlog_reset (uid_t); static void usr_update (void); @@ -1429,21 +1433,8 @@ static void open_files (void) fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ()); fail_exit (E_PW_UPDATE); } - if (is_shadow_pwd) { - if (spw_lock () == 0) { - fprintf (stderr, - _("%s: cannot lock %s; try again later.\n"), - Prog, spw_dbname ()); - fail_exit (E_PW_UPDATE); - } - spw_locked = true; - if (spw_open (O_RDWR) == 0) { - fprintf (stderr, - _("%s: cannot open %s\n"), - Prog, spw_dbname ()); - fail_exit (E_PW_UPDATE); - } - } + + /* shadow file will be opened by open_shadow(); */ /* * Lock and open the group file. @@ -1478,6 +1469,25 @@ static void open_files (void) #endif } +static void open_shadow (void) +{ + if (!is_shadow_pwd) + return; + if (!spw_lock ()) { + fprintf(stderr, + _("%s: cannot lock shadow password file\n"), + Prog); + fail_exit(E_PW_UPDATE); + } + spw_locked = true; + if (!spw_open (O_RDWR)) { + fprintf(stderr, + _("%s: cannot open shadow password file\n"), + Prog); + fail_exit(E_PW_UPDATE); + } +} + static char *empty_list = NULL; /* @@ -1990,6 +2000,16 @@ int main (int argc, char **argv) } } +#ifdef WITH_TCB + if (getdef_bool("USE_TCB")) { + if (!shadowtcb_create(user_name, user_id)) { + fprintf(stderr, "Failed to create tcb directory for %s\n", user_name); + fail_exit (E_UID_IN_USE); + } + } +#endif + open_shadow(); + /* do we have to add a group for that user? This is why we need to * open the group files in the open_files() function --gafton */ if (Uflg) { diff --git a/src/userdel.c b/src/userdel.c index d59eaeb4..3375db58 100644 --- a/src/userdel.c +++ b/src/userdel.c @@ -59,6 +59,10 @@ #ifdef SHADOWGRP #include "sgroupio.h" #endif +#ifdef WITH_TCB +#include +#include "tcbfuncs.h" +#endif /*@-exitarg@*/ #include "exitcodes.h" @@ -107,6 +111,9 @@ static bool path_prefix (const char *, const char *); #endif static int is_owner (uid_t, const char *); static int remove_mailbox (void); +#ifdef WITH_TCB +static int remove_tcbdir (const char *user_name, uid_t user_id); +#endif /* * usage - display usage message and exit @@ -731,6 +738,49 @@ static int remove_mailbox (void) return errors; } +#ifdef WITH_TCB +static int remove_tcbdir (const char *user_name, uid_t user_id) +{ + char *buf; + int ret = 0; + + if (!getdef_bool("USE_TCB")) + return 0; + + buf = malloc(strlen(TCB_DIR) + strlen(user_name) + 2); + if (!buf) { + fprintf(stderr, "Can't allocate memory, " + "tcb entry for %s not removed.\n", + user_name); + return 1; + } + snprintf(buf, strlen(TCB_DIR) + strlen(user_name) + 2, + TCB_DIR "/%s", user_name); + if (!shadowtcb_drop_priv()) { + perror("shadowtcb_drop_priv"); + free(buf); + return 1; + } + /* Only remove directory contents with dropped privileges. + * We will regain them and remove the user's tcb directory afterwards. + */ + if (remove_tree(buf, false)) { + perror("remove_tree"); + shadowtcb_gain_priv(); + free(buf); + return 1; + } + shadowtcb_gain_priv(); + free(buf); + if (!shadowtcb_remove(user_name)) { + fprintf(stderr, "Cannot remove tcb files for %s: %s\n", + user_name, strerror(errno)); + ret = 1; + } + return ret; +} +#endif + /* * main - userdel command */ @@ -851,6 +901,10 @@ int main (int argc, char **argv) user_id = pwd->pw_uid; user_home = xstrdup (pwd->pw_dir); } +#ifdef WITH_TCB + if (!shadowtcb_set_user(user_name)) + exit (E_NOTFOUND); +#endif #ifdef USE_NIS /* @@ -951,7 +1005,7 @@ int main (int argc, char **argv) #endif if (rflg) { - if (remove_tree (user_home) != 0) { + if (remove_tree (user_home, true) != 0) { fprintf (stderr, _("%s: error removing directory %s\n"), Prog, user_home); @@ -996,6 +1050,10 @@ int main (int argc, char **argv) user_cancel (user_name); close_files (); +#ifdef WITH_TCB + errors += remove_tcbdir(user_name, user_id); +#endif + nscd_flush_cache ("passwd"); nscd_flush_cache ("group"); diff --git a/src/usermod.c b/src/usermod.c index ab92009c..d12f5eaa 100644 --- a/src/usermod.c +++ b/src/usermod.c @@ -63,6 +63,9 @@ #include "sgroupio.h" #endif #include "shadowio.h" +#ifdef WITH_TCB +#include "tcbfuncs.h" +#endif /* * exit status values @@ -1438,7 +1441,7 @@ static void move_home (void) if (copy_tree (user_home, user_newhome, uflg ? (long int)user_newid : -1, gflg ? (long int)user_newgid : -1) == 0) { - if (remove_tree (user_home) != 0) { + if (remove_tree (user_home, true) != 0) { fprintf (stderr, _("%s: warning: failed to completely remove old home directory %s"), Prog, user_home); @@ -1456,7 +1459,7 @@ static void move_home (void) /* TODO: do some cleanup if the copy * was started */ - (void) remove_tree (user_newhome); + (void) remove_tree (user_newhome, true); } fprintf (stderr, _("%s: cannot rename directory %s to %s\n"), @@ -1655,7 +1658,7 @@ static void move_mailbox (void) return; } if (uflg) { - if (fchown (fd, user_newid, (gid_t) - 1) < 0) { + if (fchown (fd, user_newid, (gid_t) -1) < 0) { perror (_("failed to change mailbox owner")); } #ifdef WITH_AUDIT @@ -1770,6 +1773,11 @@ int main (int argc, char **argv) #endif /* USE_PAM */ #endif /* ACCT_TOOLS_SETUID */ +#ifdef WITH_TCB + if (!shadowtcb_set_user(user_name)) + exit(E_PW_UPDATE); +#endif + /* * Do the hard stuff - open the files, change the user entries, * change the home directory, then close and update the files. @@ -1784,6 +1792,13 @@ int main (int argc, char **argv) } close_files (); +#ifdef WITH_TCB + if ((user_newname || user_newid != -1) && + !shadowtcb_move(user_newname, user_newid)) { + exit(E_PW_UPDATE); + } +#endif + nscd_flush_cache ("passwd"); nscd_flush_cache ("group"); diff --git a/src/vipw.c b/src/vipw.c index 746da8c1..3c2d3fd2 100644 --- a/src/vipw.c +++ b/src/vipw.c @@ -48,6 +48,10 @@ #include "shadowio.h" /*@-exitarg@*/ #include "exitcodes.h" +#ifdef WITH_TCB +#include +#include "tcbfuncs.h" +#endif #define MSG_WARN_EDIT_OTHER_FILE _( \ "You have modified %s.\n"\ @@ -62,6 +66,8 @@ static bool filelocked = false; static bool createedit = false; static int (*unlock) (void); static bool quiet = false; +static const char *user = NULL; +static bool tcb_mode = false; /* local function prototypes */ static void usage (int status); @@ -83,6 +89,9 @@ static void usage (int status) " -p, --passwd edit passwd database\n" " -q, --quiet quiet mode\n" " -s, --shadow edit shadow or gshadow database\n" +#ifdef WITH_TCB + " -u, --user which user's tcb shadow file to edit\n" +#endif "\n"), (E_SUCCESS != status) ? stderr : stdout); exit (status); } @@ -175,6 +184,8 @@ static void vipwexit (const char *msg, int syserr, int ret) #define DEFAULT_EDITOR "vi" #endif +#define SHADOWTCB_SCRATCHDIR ":tmp" + /* * */ @@ -187,9 +198,23 @@ vipwedit (const char *file, int (*file_lock) (void), int (*file_unlock) (void)) int status; FILE *f; char filebackup[1024], fileedit[1024]; + char *to_rename; snprintf (filebackup, sizeof filebackup, "%s-", file); - snprintf (fileedit, sizeof fileedit, "%s.edit", file); +#ifdef WITH_TCB + if (tcb_mode) { + if (mkdir(TCB_DIR "/" SHADOWTCB_SCRATCHDIR, 0700) && errno != EEXIST) + vipwexit (_("failed to create scratch directory"), errno, 1); + if (!shadowtcb_drop_priv()) + vipwexit (_("failed to drop privileges"), errno, 1); + snprintf(fileedit, sizeof fileedit, + TCB_DIR "/" SHADOWTCB_SCRATCHDIR "/.vipw.shadow.%s", user); + } else { +#endif + snprintf (fileedit, sizeof fileedit, "%s.edit", file); +#ifdef WITH_TCB + } +#endif unlock = file_unlock; filename = file; fileeditname = fileedit; @@ -212,11 +237,19 @@ vipwedit (const char *file, int (*file_lock) (void), int (*file_unlock) (void)) vipwexit (_("setfscreatecon () failed"), errno, 1); } } +#endif +#ifdef WITH_TCB + if (tcb_mode && !shadowtcb_gain_priv()) + vipwexit (_("failed to gain privileges"), errno, 1); #endif if (file_lock () == 0) { vipwexit (_("Couldn't lock file"), errno, 5); } filelocked = true; +#ifdef WITH_TCB + if (tcb_mode && !shadowtcb_drop_priv()) + vipwexit (_("failed to drop privileges"), errno, 1); +#endif /* edited copy has same owners, perm */ if (stat (file, &st1) != 0) { @@ -226,6 +259,10 @@ vipwedit (const char *file, int (*file_lock) (void), int (*file_unlock) (void)) if (NULL == f) { vipwexit (file, 1, 1); } +#ifdef WITH_TCB + if (tcb_mode && !shadowtcb_gain_priv()) + vipwexit (_("failed to gain privileges"), errno, 1); +#endif if (create_backup_file (f, fileedit, &st1) != 0) { vipwexit (_("Couldn't make backup"), errno, 1); } @@ -300,15 +337,49 @@ vipwedit (const char *file, int (*file_lock) (void), int (*file_unlock) (void)) * without saving). Use pwck or grpck to do the check. --marekm */ createedit = false; +#ifdef WITH_TCB + if (tcb_mode) { + if (!(f = fopen(fileedit, "r"))) + vipwexit (_("failed to open scratch file"), errno, 1); + if (unlink(fileedit)) + vipwexit (_("failed to unlink scratch file"), errno, 1); + if (!shadowtcb_drop_priv()) + vipwexit (_("failed to drop privileges"), errno, 1); + if (stat(file, &st1)) + vipwexit (_("failed to stat edited file"), errno, 1); + to_rename = malloc(strlen(file) + 2); + if (!to_rename) + vipwexit (_("failed to allocate memory"), errno, 1); + snprintf(to_rename, strlen(file) + 2, "%s+", file); + if (create_backup_file(f, to_rename, &st1)) { + free(to_rename); + vipwexit (_("failed to create backup file"), errno, 1); + } + } else { +#endif + to_rename = fileedit; +#ifdef WITH_TCB + } +#endif unlink (filebackup); link (file, filebackup); - if (rename (fileedit, file) == -1) { + if (rename (to_rename, file) == -1) { fprintf (stderr, _("%s: can't restore %s: %s (your changes are in %s)\n"), - progname, file, strerror (errno), fileedit); + progname, file, strerror (errno), to_rename); + if (tcb_mode) + free(to_rename); vipwexit (0, 0, 1); } +#ifdef WITH_TCB + if (tcb_mode) { + free(to_rename); + if (!shadowtcb_gain_priv()) + vipwexit (_("failed to gain privileges"), errno, 1); + } +#endif + if ((*file_unlock) () == 0) { fprintf (stderr, _("%s: failed to unlock %s\n"), progname, fileeditname); SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname)); @@ -343,11 +414,18 @@ int main (int argc, char **argv) {"passwd", no_argument, NULL, 'p'}, {"quiet", no_argument, NULL, 'q'}, {"shadow", no_argument, NULL, 's'}, +#ifdef WITH_TCB + {"user", required_argument, NULL, 'u'}, +#endif {NULL, 0, NULL, '\0'} }; - while ((c = - getopt_long (argc, argv, "ghpqs", - long_options, NULL)) != -1) { + while ((c = getopt_long (argc, argv, +#ifdef WITH_TCB + "ghpqsu:", +#else + "ghpqs", +#endif + long_options, NULL)) != -1) { switch (c) { case 'g': do_vipw = false; @@ -364,6 +442,9 @@ int main (int argc, char **argv) case 's': editshadow = true; break; + case 'u': + user = optarg; + break; default: usage (E_USAGE); } @@ -372,9 +453,20 @@ int main (int argc, char **argv) if (do_vipw) { if (editshadow) { - vipwedit (SHADOW_FILE, spw_lock, spw_unlock); +#ifdef WITH_TCB + if (getdef_bool("USE_TCB") && user) { + if (!shadowtcb_set_user(user)) { + fprintf (stderr, + _("%s: failed to find tcb directory for %s\n"), + progname, user); + return E_SHADOW_NOTFOUND; + } + tcb_mode = true; + } +#endif + vipwedit (spw_dbname (), spw_lock, spw_unlock); printf (MSG_WARN_EDIT_OTHER_FILE, - SHADOW_FILE, + spw_dbname (), PASSWD_FILE, "vipw"); } else {