Index: libmisc/copydir.c =================================================================== --- libmisc/copydir.c (revision 3088) +++ libmisc/copydir.c (working copy) @@ -668,7 +668,7 @@ * 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 @@ /* * Recursively delete this directory. */ - if (remove_tree (new_name) != 0) { + if (remove_tree (new_name, true) != 0) { err = -1; break; } @@ -740,7 +740,7 @@ } (void) closedir (dir); - if (0 == err) { + if (remove_root && 0 == err) { if (rmdir (root) != 0) { err = -1; } Index: configure.in =================================================================== --- configure.in (revision 3088) +++ configure.in (working copy) @@ -405,6 +405,7 @@ with_tcb="no" fi fi +AM_CONDITIONAL(WITH_TCB, test x$with_tcb = xyes) AC_SUBST(LIBPAM) if test "$with_libpam" != "no"; then Index: src/userdel.c =================================================================== --- src/userdel.c (revision 3088) +++ src/userdel.c (working copy) @@ -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 @@ #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,47 @@ 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; + + asprintf(&buf, TCB_DIR "/%s", user_name); + if (!buf) { + fprintf(stderr, "Can't allocate memory, " + "tcb entry for %s not removed.\n", + user_name); + return 1; + } + 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 +899,10 @@ 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 +1003,7 @@ #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 +1048,10 @@ 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"); Index: src/pwconv.c =================================================================== --- src/pwconv.c (revision 3088) +++ src/pwconv.c (working copy) @@ -133,6 +133,11 @@ 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"), Index: src/usermod.c =================================================================== --- src/usermod.c (revision 3088) +++ src/usermod.c (working copy) @@ -63,6 +63,9 @@ #include "sgroupio.h" #endif #include "shadowio.h" +#ifdef WITH_TCB +#include "tcbfuncs.h" +#endif /* * exit status values @@ -89,10 +92,10 @@ char *Prog; static char *user_name; -static char *user_newname; +static char *user_newname = NULL; static char *user_pass; static uid_t user_id; -static uid_t user_newid; +static uid_t user_newid = -1; static gid_t user_gid; static gid_t user_newgid; static char *user_comment; @@ -1438,7 +1441,7 @@ 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 @@ /* 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"), @@ -1770,6 +1773,11 @@ #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 @@ } 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"); Index: src/pwunconv.c =================================================================== --- src/pwunconv.c (revision 3088) +++ src/pwunconv.c (working copy) @@ -93,6 +93,11 @@ 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); Index: src/vipw.c =================================================================== --- src/vipw.c (revision 3088) +++ src/vipw.c (working copy) @@ -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 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 @@ " -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 @@ #define DEFAULT_EDITOR "vi" #endif +#define SHADOWTCB_SCRATCHDIR ":tmp" + /* * */ @@ -187,9 +198,23 @@ 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; @@ -213,10 +238,18 @@ } } #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 @@ 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,41 @@ * 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 gain privileges"), errno, 1); + if (stat(file, &st1)) + vipwexit (_("failed to stat edited file"), errno, 1); + asprintf(&to_rename, "%s+", file); + if (!to_rename) + vipwexit (_("failed to allocate memory"), errno, 1); + if (create_backup_file(f, to_rename, &st1)) + 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); vipwexit (0, 0, 1); } +#ifdef WITH_TCB + if (tcb_mode && !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 +406,18 @@ {"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 +434,9 @@ case 's': editshadow = true; break; + case 'u': + user = optarg; + break; default: usage (E_USAGE); } @@ -372,9 +445,20 @@ 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 { Index: src/useradd.c =================================================================== --- src/useradd.c (revision 3088) +++ src/useradd.c (working copy) @@ -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 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,22 +1433,9 @@ 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 @@ #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 @@ } } +#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) { Index: src/pwck.c =================================================================== --- src/pwck.c (revision 3088) +++ src/pwck.c (working copy) @@ -47,6 +47,9 @@ #include "shadowio.h" #include "getdef.h" #include "nscd.h" +#ifdef WITH_TCB +#include "tcbfuncs.h" +#endif /* * Exit codes @@ -72,6 +75,9 @@ static bool is_shadow = false; +static bool pw_opened = false; +static bool spw_opened = false; + static bool pw_locked = false; static bool spw_locked = false; @@ -192,6 +198,11 @@ */ static void open_files (void) { + bool use_tcb = false; +#ifdef WITH_TCB + use_tcb = getdef_bool("USE_TCB"); +#endif + /* * Lock the files if we aren't in "read-only" mode */ @@ -203,11 +214,11 @@ fail_exit (E_CANTLOCK); } pw_locked = true; - if (is_shadow) { + if (is_shadow && !use_tcb) { if (spw_lock () == 0) { fprintf (stderr, _("%s: cannot lock %s; try again later.\n"), - Prog, spw_file); + Prog, spw_dbname()); fail_exit (E_CANTLOCK); } spw_locked = true; @@ -226,13 +237,17 @@ } fail_exit (E_CANTOPEN); } - if (is_shadow && (spw_open (read_only ? O_RDONLY : O_RDWR) == 0)) { - fprintf (stderr, _("%s: cannot open %s\n"), - Prog, spw_file); - if (use_system_spw_file) { - SYSLOG ((LOG_WARN, "cannot open %s", spw_file)); + pw_opened = true; + if (is_shadow && !use_tcb) { + if (spw_open (read_only ? O_RDONLY : O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), + Prog, spw_dbname()); + if (use_system_spw_file) { + SYSLOG ((LOG_WARN, "cannot open %s", spw_dbname())); + } + fail_exit (E_CANTOPEN); } - fail_exit (E_CANTOPEN); + spw_opened = true; } } @@ -250,18 +265,20 @@ * changes to the files. */ if (changed) { - if (pw_close () == 0) { + if (pw_opened && pw_close () == 0) { fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pwd_file); SYSLOG ((LOG_ERR, "failure while writing changes to %s", pwd_file)); fail_exit (E_CANTUPDATE); } - if (is_shadow && (spw_close () == 0)) { + pw_opened = false; + if (is_shadow && spw_opened && (spw_close () == 0)) { fprintf (stderr, _("%s: failure while writing changes to %s\n"), - Prog, spw_file); - SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_file)); + Prog, spw_dbname()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname())); fail_exit (E_CANTUPDATE); } + spw_opened = false; } /* @@ -450,12 +467,49 @@ */ if (is_shadow) { +#ifdef WITH_TCB + if (getdef_bool("USE_TCB")) { + if (!shadowtcb_set_user (pwd->pw_name)) { + printf(_("no tcb directory for %s\n"), pwd->pw_name); + printf(_("create tcb directory for %s?"), pwd->pw_name); + *errors += 1; + if (yes_or_no (read_only)) { + if (!shadowtcb_create(pwd->pw_name, pwd->pw_uid)) { + *errors += 1; + printf(_("failed to create tcb directory for %s\n"), pwd->pw_name); + continue; + } + } else { + continue; + } + } + if (spw_lock () == 0) { + *errors += 1; + fprintf (stderr, + _("%s: cannot lock %s.\n"), + Prog, spw_dbname()); + continue; + } + spw_locked = true; + if (spw_open (read_only ? O_RDONLY : O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), + Prog, spw_dbname()); + *errors += 1; + if (spw_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ())); + } + continue; + } + spw_opened = true; + } +#endif spw = (struct spwd *) spw_locate (pwd->pw_name); if (NULL == spw) { printf (_("no matching password file entry in %s\n"), - spw_file); + spw_dbname()); printf (_("add user '%s' in %s? "), - pwd->pw_name, spw_file); + pwd->pw_name, spw_dbname()); *errors += 1; if (yes_or_no (read_only)) { struct spwd sp; @@ -494,7 +548,7 @@ fprintf (stderr, _("%s: failed to prepare the new %s entry '%s'\n"), Prog, pw_dbname (), pw.pw_name); - exit (E_CANTUPDATE); + fail_exit (E_CANTUPDATE); } } } else { @@ -503,11 +557,28 @@ */ if (strcmp (pwd->pw_passwd, SHADOW_PASSWD_STRING) != 0) { printf (_("user %s has an entry in %s, but its password field in %s is not set to 'x'\n"), - pwd->pw_name, spw_file, pwd_file); + pwd->pw_name, spw_dbname(), pwd_file); *errors += 1; } } } +#ifdef WITH_TCB + if (getdef_bool("USE_TCB") && spw_locked) { + if (spw_opened && spw_close () == 0) { + fprintf (stderr, _("%s: failure while writing changes to %s\n"), + Prog, spw_dbname()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname())); + } else { + spw_opened = false; + } + if (spw_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ())); + } else { + spw_locked = false; + } + } +#endif } } Index: src/chage.c =================================================================== --- src/chage.c (revision 3088) +++ src/chage.c (working copy) @@ -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 @@ } 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]); Index: src/Makefile.am =================================================================== --- src/Makefile.am (revision 3088) +++ src/Makefile.am (working copy) @@ -5,6 +5,7 @@ ubindir = ${prefix}/bin usbindir = ${prefix}/sbin suidperms = 4755 +sgidperms = 2755 INCLUDES = \ -I${top_srcdir}/lib \ @@ -53,7 +54,13 @@ 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 @@ 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 Index: lib/getdef.c =================================================================== --- lib/getdef.c (revision 3088) +++ lib/getdef.c (working copy) @@ -124,6 +124,11 @@ {"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} }; Index: lib/tcbfuncs.c =================================================================== --- lib/tcbfuncs.c (revision 0) +++ lib/tcbfuncs.c (revision 0) @@ -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; +} Index: lib/tcbfuncs.h =================================================================== --- lib/tcbfuncs.h (revision 0) +++ lib/tcbfuncs.h (revision 0) @@ -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 Index: lib/shadowio.c =================================================================== --- lib/shadowio.c (revision 3088) +++ lib/shadowio.c (working copy) @@ -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 @@ 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_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 @@ /* 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 ()); } Index: lib/commonio.c =================================================================== --- lib/commonio.c (revision 3088) +++ lib/commonio.c (working copy) @@ -48,6 +48,9 @@ #ifdef WITH_SELINUX #include #endif +#ifdef WITH_TCB +#include +#endif #include "prototypes.h" #include "commonio.h" @@ -533,6 +536,7 @@ void *eptr = NULL; int flags = mode; size_t buflen; + int fd; int saved_errno; mode &= ~O_CREAT; @@ -553,7 +557,24 @@ 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 Index: lib/Makefile.am =================================================================== --- lib/Makefile.am (revision 3088) +++ lib/Makefile.am (working copy) @@ -49,6 +49,10 @@ 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: # Index: lib/prototypes.h =================================================================== --- lib/prototypes.h (revision 3088) +++ lib/prototypes.h (working copy) @@ -117,7 +117,7 @@ /* 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); Index: man/login.defs.d/TCB_SYMLINKS.xml =================================================================== --- man/login.defs.d/TCB_SYMLINKS.xml (revision 0) +++ man/login.defs.d/TCB_SYMLINKS.xml (revision 0) @@ -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 +} + + + + Index: man/login.defs.d/TCB_AUTH_GROUP.xml =================================================================== --- man/login.defs.d/TCB_AUTH_GROUP.xml (revision 0) +++ man/login.defs.d/TCB_AUTH_GROUP.xml (revision 0) @@ -0,0 +1,37 @@ + + + (boolean) + + + If yes, newly created tcb shadow files + will be group owned by the auth group. + + + Index: man/login.defs.d/USE_TCB.xml =================================================================== --- man/login.defs.d/USE_TCB.xml (revision 0) +++ man/login.defs.d/USE_TCB.xml (revision 0) @@ -0,0 +1,38 @@ + + + (boolean) + + + If yes, the + tcb5 + password shadowing scheme will be used. + + + Index: man/generate_mans.mak =================================================================== --- man/generate_mans.mak (revision 3088) +++ man/generate_mans.mak (working copy) @@ -8,6 +8,11 @@ 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 @@ %: %.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 Index: man/login.defs.5.xml =================================================================== --- man/login.defs.5.xml (revision 3088) +++ man/login.defs.5.xml (working copy) @@ -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 + + + Index: man/vipw.8.xml =================================================================== --- man/vipw.8.xml (revision 3088) +++ man/vipw.8.xml (working copy) @@ -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 . Index: man/generate_mans.deps =================================================================== --- man/generate_mans.deps (revision 3088) +++ man/generate_mans.deps (working copy) @@ -105,6 +105,8 @@ 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/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 Index: man/Makefile.am =================================================================== --- man/Makefile.am (revision 3088) +++ man/Makefile.am (working copy) @@ -149,6 +149,8 @@ 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 @@ UMASK.xml \ USERDEL_CMD.xml \ USERGROUPS_ENAB.xml \ + USE_TCB.xml \ SYS_GID_MAX.xml \ SYS_UID_MAX.xml Index: NEWS =================================================================== --- NEWS (revision 3088) +++ NEWS (working copy) @@ -5,6 +5,7 @@ - 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.