diff options
Diffstat (limited to 'src/newusers.c')
-rw-r--r-- | src/newusers.c | 1344 |
1 files changed, 1344 insertions, 0 deletions
diff --git a/src/newusers.c b/src/newusers.c new file mode 100644 index 0000000..deeb361 --- /dev/null +++ b/src/newusers.c @@ -0,0 +1,1344 @@ +/* + * SPDX-FileCopyrightText: 1990 - 1993, Julianne Frances Haugh + * SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz + * SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko + * SPDX-FileCopyrightText: 2007 - 2011, Nicolas François + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * newusers - create users from a batch file + * + * newusers creates a collection of entries in /etc/passwd + * and related files by reading a passwd-format file and + * adding entries in the related directories. + */ + +#include <config.h> + +#ident "$Id$" + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <pwd.h> +#include <grp.h> +#include <fcntl.h> +#include <getopt.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#ifdef ACCT_TOOLS_SETUID +#ifdef USE_PAM +#include "pam_defs.h" +#endif /* USE_PAM */ +#endif /* ACCT_TOOLS_SETUID */ +#include "prototypes.h" +#include "defines.h" +#include "getdef.h" +#include "groupio.h" +#include "nscd.h" +#include "sssd.h" +#include "pwio.h" +#include "sgroupio.h" +#include "shadowio.h" +#ifdef ENABLE_SUBIDS +#include "subordinateio.h" +#endif /* ENABLE_SUBIDS */ +#include "chkname.h" +#include "shadowlog.h" + +/* + * Global variables + */ +const char *Prog; + +static bool rflg = false; /* create a system account */ +#ifndef USE_PAM +static /*@null@*//*@observer@*/char *crypt_method = NULL; +#define cflg (NULL != crypt_method) +#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) +static bool sflg = false; +#endif +#ifdef USE_SHA_CRYPT +static long sha_rounds = 5000; +#endif /* USE_SHA_CRYPT */ +#ifdef USE_BCRYPT +static long bcrypt_rounds = 13; +#endif /* USE_BCRYPT */ +#ifdef USE_YESCRYPT +static long yescrypt_cost = 5; +#endif /* USE_YESCRYPT */ +#endif /* !USE_PAM */ + +static bool is_shadow; +#ifdef SHADOWGRP +static bool is_shadow_grp; +static bool sgr_locked = false; +#endif +static bool pw_locked = false; +static bool gr_locked = false; +static bool spw_locked = false; +#ifdef ENABLE_SUBIDS +static bool is_sub_uid = false; +static bool is_sub_gid = false; +static bool sub_uid_locked = false; +static bool sub_gid_locked = false; +#endif /* ENABLE_SUBIDS */ + +/* local function prototypes */ +static void usage (int status); +static void fail_exit (int); +static int add_group (const char *, const char *, gid_t *, gid_t); +static int get_user_id (const char *, uid_t *); +static int add_user (const char *, uid_t, gid_t); +#ifndef USE_PAM +static int update_passwd (struct passwd *, const char *); +#endif /* !USE_PAM */ +static int add_passwd (struct passwd *, const char *); +static void process_flags (int argc, char **argv); +static void check_flags (void); +static void check_perms (void); +static void open_files (void); +static void close_files (void); + +extern int allow_bad_names; + +/* + * usage - display usage message and exit + */ +static void usage (int status) +{ + FILE *usageout = (EXIT_SUCCESS != status) ? stderr : stdout; + (void) fprintf (usageout, + _("Usage: %s [options]\n" + "\n" + "Options:\n"), + Prog); + (void) fputs (_(" -b, --badname allow bad names\n"), usageout); +#ifndef USE_PAM + (void) fprintf (usageout, + _(" -c, --crypt-method METHOD the crypt method (one of %s)\n"), + "NONE DES MD5" +#if defined(USE_SHA_CRYPT) + " SHA256 SHA512" +#endif +#if defined(USE_BCRYPT) + " BCRYPT" +#endif +#if defined(USE_YESCRYPT) + " YESCRYPT" +#endif + ); +#endif /* !USE_PAM */ + (void) fputs (_(" -h, --help display this help message and exit\n"), usageout); + (void) fputs (_(" -r, --system create system accounts\n"), usageout); + (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout); +#ifndef USE_PAM +#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + (void) fputs (_(" -s, --sha-rounds number of rounds for the SHA, BCRYPT\n" + " or YESCRYPT crypt algorithms\n"), + usageout); +#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ +#endif /* !USE_PAM */ + (void) fputs ("\n", usageout); + + exit (status); +} + +/* + * fail_exit - undo as much as possible + */ +static void fail_exit (int code) +{ + if (spw_locked) { + 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 */ + } + } + if (pw_locked) { + if (pw_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ())); + /* continue */ + } + } + if (gr_locked) { + if (gr_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ())); + /* continue */ + } + } +#ifdef SHADOWGRP + if (sgr_locked) { + if (sgr_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ())); + /* continue */ + } + } +#endif +#ifdef ENABLE_SUBIDS + if (sub_uid_locked) { + if (sub_uid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ())); + /* continue */ + } + } + if (sub_gid_locked) { + if (sub_gid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ())); + /* continue */ + } + } +#endif /* ENABLE_SUBIDS */ + + exit (code); +} + +/* + * add_group - create a new group or add a user to an existing group + */ +static int add_group (const char *name, const char *gid, gid_t *ngid, uid_t uid) +{ + const struct group *grp; + struct group grent; + char *members[1]; +#ifdef SHADOWGRP + const struct sgrp *sg; +#endif + + /* + * Start by seeing if the named group already exists. This will be + * very easy to deal with if it does. + */ + grp = getgrnam (gid); + if (NULL == grp) { + grp = gr_locate (gid); + } + if (NULL != grp) { + /* The user will use this ID for her primary group */ + *ngid = grp->gr_gid; + /* Don't check gshadow */ + return 0; + } + + if (isdigit (gid[0])) { + /* + * The GID is a number, which means either this is a brand + * new group, or an existing group. + */ + + if (get_gid (gid, &grent.gr_gid) == 0) { + fprintf (stderr, + _("%s: invalid group ID '%s'\n"), + Prog, gid); + return -1; + } + + /* Look in both the system database (getgrgid) and in the + * internal database (gr_locate_gid), which may contain + * uncommitted changes */ + if ( (getgrgid ((gid_t) grent.gr_gid) != NULL) + || (gr_locate_gid ((gid_t) grent.gr_gid) != NULL)) { + /* The user will use this ID for her + * primary group */ + *ngid = (gid_t) grent.gr_gid; + return 0; + } + + /* Do not create groups with GID == (gid_t)-1 */ + if (grent.gr_gid == (gid_t)-1) { + fprintf (stderr, + _("%s: invalid group ID '%s'\n"), + Prog, gid); + return -1; + } + } else { + /* The gid parameter can be "" or a name which is not + * already the name of an existing group. + * In both cases, figure out what group ID can be used. + */ + if (find_new_gid(rflg, &grent.gr_gid, &uid) < 0) { + return -1; + } + } + + /* + * Now I have all of the fields required to create the new group. + */ + if (('\0' != gid[0]) && (!isdigit (gid[0]))) { + grent.gr_name = xstrdup (gid); + } else { + grent.gr_name = xstrdup (name); +/* FIXME: check if the group exists */ + } + + /* Check if this is a valid group name */ + if (!is_valid_group_name (grent.gr_name)) { + fprintf (stderr, + _("%s: invalid group name '%s'\n"), + Prog, grent.gr_name); + free (grent.gr_name); + return -1; + } + + grent.gr_passwd = "*"; /* XXX warning: const */ + members[0] = NULL; + grent.gr_mem = members; + + *ngid = grent.gr_gid; + +#ifdef SHADOWGRP + if (is_shadow_grp) { + sg = sgr_locate (grent.gr_name); + + if (NULL != sg) { + fprintf (stderr, + _("%s: group '%s' is a shadow group, but does not exist in /etc/group\n"), + Prog, grent.gr_name); + return -1; + } + } +#endif + +#ifdef SHADOWGRP + if (is_shadow_grp) { + struct sgrp sgrent; + char *admins[1]; + sgrent.sg_name = grent.gr_name; + sgrent.sg_passwd = "*"; /* XXX warning: const */ + grent.gr_passwd = "x"; /* XXX warning: const */ + admins[0] = NULL; + sgrent.sg_adm = admins; + sgrent.sg_mem = members; + + if (sgr_update (&sgrent) == 0) { + return -1; + } + } +#endif + + if (gr_update (&grent) == 0) { + return -1; + } + + return 0; +} + +static int get_user_id (const char *uid, uid_t *nuid) { + + /* + * The first guess for the UID is either the numerical UID that the + * caller provided, or the next available UID. + */ + if (isdigit (uid[0])) { + if ((get_uid (uid, nuid) == 0) || (*nuid == (uid_t)-1)) { + fprintf (stderr, + _("%s: invalid user ID '%s'\n"), + Prog, uid); + return -1; + } + } else { + if ('\0' != uid[0]) { + const struct passwd *pwd; + /* local, no need for xgetpwnam */ + pwd = getpwnam (uid); + if (NULL == pwd) { + pwd = pw_locate (uid); + } + + if (NULL != pwd) { + *nuid = pwd->pw_uid; + } else { + fprintf (stderr, + _("%s: user '%s' does not exist\n"), + Prog, uid); + return -1; + } + } else { + if (find_new_uid (rflg, nuid, NULL) < 0) { + return -1; + } + } + } + + return 0; +} + +/* + * add_user - create a new user ID + */ +static int add_user (const char *name, uid_t uid, gid_t gid) +{ + struct passwd pwent; + + /* Check if this is a valid user name */ + if (!is_valid_user_name (name)) { + fprintf (stderr, + _("%s: invalid user name '%s': use --badname to ignore\n"), + Prog, name); + return -1; + } + + /* + * I don't want to fill in the entire password structure members + * JUST YET, since there is still more data to be added. So, I fill + * in the parts that I have. + */ + pwent.pw_name = xstrdup (name); + pwent.pw_uid = uid; + pwent.pw_passwd = "x"; /* XXX warning: const */ + pwent.pw_gid = gid; + pwent.pw_gecos = ""; /* XXX warning: const */ + pwent.pw_dir = ""; /* XXX warning: const */ + pwent.pw_shell = ""; /* XXX warning: const */ + + return (pw_update (&pwent) == 0) ? -1 : 0; +} + +#ifndef USE_PAM +/* + * update_passwd - update the password in the passwd entry + * + * Return 0 if successful. + */ +static int update_passwd (struct passwd *pwd, const char *password) +{ + void *crypt_arg = NULL; + char *cp; + if (NULL != crypt_method) { +#if defined(USE_SHA_CRYPT) + if (sflg) { + if ( (0 == strcmp (crypt_method, "SHA256")) + || (0 == strcmp (crypt_method, "SHA512"))) { + crypt_arg = &sha_rounds; + } + } +#endif /* USE_SHA_CRYPT */ +#if defined(USE_BCRYPT) + if (sflg) { + if (0 == strcmp (crypt_method, "BCRYPT")) { + crypt_arg = &bcrypt_rounds; + } + } +#endif /* USE_BCRYPT */ +#if defined(USE_YESCRYPT) + if (sflg) { + if (0 == strcmp (crypt_method, "YESCRYPT")) { + crypt_arg = &yescrypt_cost; + } + } +#endif /* USE_YESCRYPT */ + } + + if ((NULL != crypt_method) && (0 == strcmp(crypt_method, "NONE"))) { + pwd->pw_passwd = (char *)password; + } else { + const char *salt = crypt_make_salt (crypt_method, crypt_arg); + cp = pw_encrypt (password, salt); + if (NULL == cp) { + fprintf (stderr, + _("%s: failed to crypt password with salt '%s': %s\n"), + Prog, salt, strerror (errno)); + return 1; + } + pwd->pw_passwd = cp; + } + + return 0; +} +#endif /* !USE_PAM */ + +/* + * add_passwd - add or update the encrypted password + */ +static int add_passwd (struct passwd *pwd, const char *password) +{ + const struct spwd *sp; + struct spwd spent; +#ifndef USE_PAM + char *cp; +#endif /* !USE_PAM */ + +#ifndef USE_PAM + void *crypt_arg = NULL; + if (NULL != crypt_method) { +#if defined(USE_SHA_CRYPT) + if (sflg) { + if ( (0 == strcmp (crypt_method, "SHA256")) + || (0 == strcmp (crypt_method, "SHA512"))) { + crypt_arg = &sha_rounds; + } + } +#endif /* USE_SHA_CRYPT */ +#if defined(USE_BCRYPT) + if (sflg) { + if (0 == strcmp (crypt_method, "BCRYPT")) { + crypt_arg = &bcrypt_rounds; + } + } +#endif /* USE_BCRYPT */ +#if defined(USE_YESCRYPT) + if (sflg) { + if (0 == strcmp (crypt_method, "YESCRYPT")) { + crypt_arg = &yescrypt_cost; + } + } +#endif /* USE_PAM */ + } + + /* + * In the case of regular password files, this is real easy - pwd + * points to the entry in the password file. Shadow files are + * harder since there are zillions of things to do ... + */ + if (!is_shadow) { + return update_passwd (pwd, password); + } +#endif /* USE_PAM */ + + /* + * Do the first and easiest shadow file case. The user already + * exists in the shadow password file. + */ + sp = spw_locate (pwd->pw_name); +#ifndef USE_PAM + if (NULL != sp) { + spent = *sp; + if ( (NULL != crypt_method) + && (0 == strcmp(crypt_method, "NONE"))) { + spent.sp_pwdp = (char *)password; + } else { + const char *salt = crypt_make_salt (crypt_method, + crypt_arg); + cp = pw_encrypt (password, salt); + if (NULL == cp) { + fprintf (stderr, + _("%s: failed to crypt password with salt '%s': %s\n"), + Prog, salt, strerror (errno)); + return 1; + } + spent.sp_pwdp = cp; + } + spent.sp_lstchg = (long) gettime () / SCALE; + if (0 == spent.sp_lstchg) { + /* Better disable aging than requiring a password + * change */ + spent.sp_lstchg = -1; + } + return (spw_update (&spent) == 0); + } + + /* + * Pick the next easiest case - the user has an encrypted password + * which isn't equal to "x". The password was set to "x" earlier + * when the entry was created, so this user would have to have had + * the password set someplace else. + */ + if (strcmp (pwd->pw_passwd, "x") != 0) { + return update_passwd (pwd, password); + } +#else /* USE_PAM */ + /* + * If there is already a shadow entry, do not touch it. + * If there is already a passwd entry with a password, do not + * touch it. + * The password will be updated later for all users using PAM. + */ + if ( (NULL != sp) + || (strcmp (pwd->pw_passwd, "x") != 0)) { + return 0; + } +#endif /* USE_PAM */ + + /* + * Now the really hard case - I need to create an entirely new + * shadow password file entry. + */ + spent.sp_namp = pwd->pw_name; +#ifndef USE_PAM + if ((crypt_method != NULL) && (0 == strcmp(crypt_method, "NONE"))) { + spent.sp_pwdp = (char *)password; + } else { + const char *salt = crypt_make_salt (crypt_method, crypt_arg); + cp = pw_encrypt (password, salt); + if (NULL == cp) { + fprintf (stderr, + _("%s: failed to crypt password with salt '%s': %s\n"), + Prog, salt, strerror (errno)); + return 1; + } + spent.sp_pwdp = cp; + } +#else + /* + * Lock the password. + * The password will be updated later for all users using PAM. + */ + spent.sp_pwdp = "!"; +#endif + spent.sp_lstchg = (long) gettime () / SCALE; + if (0 == spent.sp_lstchg) { + /* Better disable aging than requiring a password change */ + spent.sp_lstchg = -1; + } + spent.sp_min = getdef_num ("PASS_MIN_DAYS", 0); + /* 10000 is infinity this week */ + spent.sp_max = getdef_num ("PASS_MAX_DAYS", 10000); + spent.sp_warn = getdef_num ("PASS_WARN_AGE", -1); + spent.sp_inact = -1; + spent.sp_expire = -1; + spent.sp_flag = SHADOW_SP_FLAG_UNSET; + + return (spw_update (&spent) == 0); +} + +/* + * process_flags - parse the command line options + * + * It will not return if an error is encountered. + */ +static void process_flags (int argc, char **argv) +{ + int c; +#ifndef USE_PAM +#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + int bad_s; +#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ +#endif /* !USE_PAM */ + static struct option long_options[] = { + {"badname", no_argument, NULL, 'b'}, +#ifndef USE_PAM + {"crypt-method", required_argument, NULL, 'c'}, +#endif /* !USE_PAM */ + {"help", no_argument, NULL, 'h'}, + {"system", no_argument, NULL, 'r'}, + {"root", required_argument, NULL, 'R'}, +#ifndef USE_PAM +#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + {"sha-rounds", required_argument, NULL, 's'}, +#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ +#endif /* !USE_PAM */ + {NULL, 0, NULL, '\0'} + }; + + while ((c = getopt_long (argc, argv, +#ifndef USE_PAM +#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + "c:bhrs:", +#else /* !USE_SHA_CRYPT && !USE_BCRYPT && !USE_YESCRYPT */ + "c:bhr", +#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ +#else /* USE_PAM */ + "bhr", +#endif + long_options, NULL)) != -1) { + switch (c) { + case 'b': + allow_bad_names = true; + break; +#ifndef USE_PAM + case 'c': + crypt_method = optarg; + break; +#endif /* !USE_PAM */ + case 'h': + usage (EXIT_SUCCESS); + break; + case 'r': + rflg = true; + break; + case 'R': /* no-op, handled in process_root_flag () */ + break; +#ifndef USE_PAM +#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + case 's': + sflg = true; + bad_s = 0; +#if defined(USE_SHA_CRYPT) + if ( ( ((0 == strcmp (crypt_method, "SHA256")) || (0 == strcmp (crypt_method, "SHA512"))) + && (0 == getlong(optarg, &sha_rounds)))) { + bad_s = 1; + } +#endif /* USE_SHA_CRYPT */ +#if defined(USE_BCRYPT) + if (( (0 == strcmp (crypt_method, "BCRYPT")) + && (0 == getlong(optarg, &bcrypt_rounds)))) { + bad_s = 1; + } +#endif /* USE_BCRYPT */ +#if defined(USE_YESCRYPT) + if (( (0 == strcmp (crypt_method, "YESCRYPT")) + && (0 == getlong(optarg, &yescrypt_cost)))) { + bad_s = 1; + } +#endif /* USE_YESCRYPT */ + if (bad_s != 0) { + fprintf (stderr, + _("%s: invalid numeric argument '%s'\n"), + Prog, optarg); + usage (EXIT_FAILURE); + } + break; +#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ +#endif /* !USE_PAM */ + default: + usage (EXIT_FAILURE); + break; + } + } + + if ( (optind != argc) + && (optind + 1 != argc)) { + usage (EXIT_FAILURE); + } + + if (argv[optind] != NULL) { + if (freopen (argv[optind], "r", stdin) == NULL) { + char buf[BUFSIZ]; + snprintf (buf, sizeof buf, "%s: %s", Prog, argv[1]); + perror (buf); + fail_exit (EXIT_FAILURE); + } + } + + /* validate options */ + check_flags (); +} + +/* + * check_flags - check flags and parameters consistency + * + * It will not return if an error is encountered. + */ +static void check_flags (void) +{ +#ifndef USE_PAM +#if defined(USE_SHA_CRYPT) || defined(USE_BCRYPT) || defined(USE_YESCRYPT) + if (sflg && !cflg) { + fprintf (stderr, + _("%s: %s flag is only allowed with the %s flag\n"), + Prog, "-s", "-c"); + usage (EXIT_FAILURE); + } +#endif /* USE_SHA_CRYPT || USE_BCRYPT || USE_YESCRYPT */ + + if (cflg) { + if ( (0 != strcmp (crypt_method, "DES")) + && (0 != strcmp (crypt_method, "MD5")) + && (0 != strcmp (crypt_method, "NONE")) +#ifdef USE_SHA_CRYPT + && (0 != strcmp (crypt_method, "SHA256")) + && (0 != strcmp (crypt_method, "SHA512")) +#endif /* USE_SHA_CRYPT */ +#ifdef USE_BCRYPT + && (0 != strcmp (crypt_method, "BCRYPT")) +#endif /* USE_BCRYPT */ +#ifdef USE_YESCRYPT + && (0 != strcmp (crypt_method, "YESCRYPT")) +#endif /* USE_YESCRYPT */ + ) { + fprintf (stderr, + _("%s: unsupported crypt method: %s\n"), + Prog, crypt_method); + usage (EXIT_FAILURE); + } + } +#endif /* !USE_PAM */ +} + +/* + * check_perms - check if the caller is allowed to add a group + * + * With PAM support, the setuid bit can be set on groupadd to allow + * non-root users to groups. + * Without PAM support, only users who can write in the group databases + * can add groups. + * + * It will not return if the user is not allowed. + */ +static void check_perms (void) +{ +#ifdef ACCT_TOOLS_SETUID +#ifdef USE_PAM + pam_handle_t *pamh = NULL; + int retval; + struct passwd *pampw; + + pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */ + if (NULL == pampw) { + fprintf (stderr, + _("%s: Cannot determine your user name.\n"), + Prog); + fail_exit (EXIT_FAILURE); + } + + retval = pam_start ("newusers", pampw->pw_name, &conv, &pamh); + + if (PAM_SUCCESS == retval) { + retval = pam_authenticate (pamh, 0); + } + + if (PAM_SUCCESS == retval) { + retval = pam_acct_mgmt (pamh, 0); + } + + if (PAM_SUCCESS != retval) { + fprintf (stderr, _("%s: PAM: %s\n"), + Prog, pam_strerror (pamh, retval)); + SYSLOG((LOG_ERR, "%s", pam_strerror (pamh, retval))); + if (NULL != pamh) { + (void) pam_end (pamh, retval); + } + fail_exit (EXIT_FAILURE); + } + (void) pam_end (pamh, retval); +#endif /* USE_PAM */ +#endif /* ACCT_TOOLS_SETUID */ +} + +/* + * open_files - lock and open the password, group and shadow databases + */ +static void open_files (void) +{ + /* + * Lock the password files and open them for update. This will bring + * all of the entries into memory where they may be searched for an + * modified, or new entries added. The password file is the key - if + * it gets locked, assume the others can be locked right away. + */ + if (pw_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, pw_dbname ()); + fail_exit (EXIT_FAILURE); + } + pw_locked = true; + if (is_shadow) { + if (spw_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, spw_dbname ()); + fail_exit (EXIT_FAILURE); + } + spw_locked = true; + } + if (gr_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, gr_dbname ()); + fail_exit (EXIT_FAILURE); + } + gr_locked = true; +#ifdef SHADOWGRP + if (is_shadow_grp) { + if (sgr_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sgr_dbname ()); + fail_exit (EXIT_FAILURE); + } + sgr_locked = true; + } +#endif +#ifdef ENABLE_SUBIDS + if (is_sub_uid) { + if (sub_uid_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sub_uid_dbname ()); + fail_exit (EXIT_FAILURE); + } + sub_uid_locked = true; + } + if (is_sub_gid) { + if (sub_gid_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sub_gid_dbname ()); + fail_exit (EXIT_FAILURE); + } + sub_gid_locked = true; + } +#endif /* ENABLE_SUBIDS */ + + if (pw_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ()); + fail_exit (EXIT_FAILURE); + } + if (is_shadow && (spw_open (O_CREAT | O_RDWR) == 0)) { + fprintf (stderr, _("%s: cannot open %s\n"), Prog, spw_dbname ()); + fail_exit (EXIT_FAILURE); + } + if (gr_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ()); + fail_exit (EXIT_FAILURE); + } +#ifdef SHADOWGRP + if (is_shadow_grp && (sgr_open (O_CREAT | O_RDWR) == 0)) { + fprintf (stderr, _("%s: cannot open %s\n"), Prog, sgr_dbname ()); + fail_exit (EXIT_FAILURE); + } +#endif +#ifdef ENABLE_SUBIDS + if (is_sub_uid) { + if (sub_uid_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, + _("%s: cannot open %s\n"), + Prog, sub_uid_dbname ()); + fail_exit (EXIT_FAILURE); + } + } + if (is_sub_gid) { + if (sub_gid_open (O_CREAT | O_RDWR) == 0) { + fprintf (stderr, + _("%s: cannot open %s\n"), + Prog, sub_gid_dbname ()); + fail_exit (EXIT_FAILURE); + } + } +#endif /* ENABLE_SUBIDS */ +} + +/* + * close_files - close and unlock the password, group and shadow databases + */ +static void close_files (void) +{ + if (pw_close () == 0) { + fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ())); + fail_exit (EXIT_FAILURE); + } + if (pw_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ())); + /* continue */ + } + pw_locked = false; + + if (is_shadow) { + if (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 ())); + fail_exit (EXIT_FAILURE); + } + 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_locked = false; + } + + if (gr_close () == 0) { + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), + Prog, gr_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ())); + fail_exit (EXIT_FAILURE); + } +#ifdef ENABLE_SUBIDS + if (is_sub_uid && (sub_uid_close () == 0)) { + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), Prog, sub_uid_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_uid_dbname ())); + fail_exit (EXIT_FAILURE); + } + if (is_sub_gid && (sub_gid_close () == 0)) { + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), Prog, sub_gid_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", sub_gid_dbname ())); + fail_exit (EXIT_FAILURE); + } +#endif /* ENABLE_SUBIDS */ + + if (gr_unlock () == 0) { + fprintf (stderr, + _("%s: failed to unlock %s\n"), + Prog, gr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ())); + /* continue */ + } + gr_locked = false; + +#ifdef SHADOWGRP + if (is_shadow_grp) { + if (sgr_close () == 0) { + fprintf (stderr, + _("%s: failure while writing changes to %s\n"), + Prog, sgr_dbname ()); + SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ())); + fail_exit (EXIT_FAILURE); + } + if (sgr_unlock () == 0) { + fprintf (stderr, + _("%s: failed to unlock %s\n"), + Prog, sgr_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ())); + /* continue */ + } + sgr_locked = false; + } +#endif +#ifdef ENABLE_SUBIDS + if (is_sub_uid) { + if (sub_uid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_uid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_uid_dbname ())); + /* continue */ + } + sub_uid_locked = false; + } + if (is_sub_gid) { + if (sub_gid_unlock () == 0) { + fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sub_gid_dbname ()); + SYSLOG ((LOG_ERR, "failed to unlock %s", sub_gid_dbname ())); + /* continue */ + } + sub_gid_locked = false; + } +#endif /* ENABLE_SUBIDS */ +} + +static bool want_subuids(void) +{ + if (get_subid_nss_handle() != NULL) + return false; + if (getdef_ulong ("SUB_UID_COUNT", 65536) == 0) + return false; + return true; +} + +static bool want_subgids(void) +{ + if (get_subid_nss_handle() != NULL) + return false; + if (getdef_ulong ("SUB_GID_COUNT", 65536) == 0) + return false; + return true; +} + +int main (int argc, char **argv) +{ + char buf[BUFSIZ]; + char *fields[8]; + int nfields; + char *cp; + const struct passwd *pw; + struct passwd newpw; + int errors = 0; + int line = 0; + uid_t uid; + gid_t gid; +#ifdef USE_PAM + int *lines = NULL; + char **usernames = NULL; + char **passwords = NULL; + unsigned int nusers = 0; +#endif /* USE_PAM */ + + Prog = Basename (argv[0]); + log_set_progname(Prog); + log_set_logfd(stderr); + + (void) setlocale (LC_ALL, ""); + (void) bindtextdomain (PACKAGE, LOCALEDIR); + (void) textdomain (PACKAGE); + + /* FIXME: will not work with an input file */ + process_root_flag ("-R", argc, argv); + + OPENLOG ("newusers"); + + process_flags (argc, argv); + + check_perms (); + + is_shadow = spw_file_present (); + +#ifdef SHADOWGRP + is_shadow_grp = sgr_file_present (); +#endif +#ifdef ENABLE_SUBIDS + is_sub_uid = sub_uid_file_present () && !rflg; + is_sub_gid = sub_gid_file_present () && !rflg; +#endif /* ENABLE_SUBIDS */ + + open_files (); + + /* + * Read each line. The line has the same format as a password file + * entry, except that certain fields are not constrained to be + * numerical values. If a group ID is entered which does not already + * exist, an attempt is made to allocate the same group ID as the + * numerical user ID. Should that fail, the next available group ID + * over 100 is allocated. The pw_gid field will be updated with that + * value. + */ + while (fgets (buf, (int) sizeof buf, stdin) != (char *) 0) { + line++; + cp = strrchr (buf, '\n'); + if (NULL != cp) { + *cp = '\0'; + } else { + if (feof (stdin) == 0) { + fprintf (stderr, + _("%s: line %d: line too long\n"), + Prog, line); + errors++; + continue; + } + } + + /* + * Break the string into fields and screw around with them. + * There MUST be 7 colon separated fields, although the + * values aren't that particular. + */ + for (cp = buf, nfields = 0; nfields < 7; nfields++) { + fields[nfields] = cp; + cp = strchr (cp, ':'); + if (NULL != cp) { + *cp = '\0'; + cp++; + } else { + break; + } + } + if (nfields != 6) { + fprintf (stderr, _("%s: line %d: invalid line\n"), + Prog, line); + errors++; + continue; + } + + /* + * First check if we have to create or update an user + */ + pw = pw_locate (fields[0]); + /* local, no need for xgetpwnam */ + if ( (NULL == pw) + && (getpwnam (fields[0]) != NULL)) { + fprintf (stderr, _("%s: cannot update the entry of user %s (not in the passwd database)\n"), Prog, fields[0]); + errors++; + continue; + } + + if ( (NULL == pw) + && (get_user_id (fields[2], &uid) != 0)) { + fprintf (stderr, + _("%s: line %d: can't create user\n"), + Prog, line); + errors++; + continue; + } + + /* + * Processed is the group name. A new group will be + * created if the group name is non-numeric and does not + * already exist. If the group name is a number (which is not + * an existing GID), a group with the same name as the user + * will be created, with the given GID. The given or created + * group will be the primary group of the user. If + * there is no named group to be a member of, the UID will + * be figured out and that value will be a candidate for a + * new group, if that group ID exists, a whole new group ID + * will be made up. + */ + if ( (NULL == pw) + && (add_group (fields[0], fields[3], &gid, uid) != 0)) { + fprintf (stderr, + _("%s: line %d: can't create group\n"), + Prog, line); + errors++; + continue; + } + + /* + * Now we work on the user ID. It has to be specified either + * as a numerical value, or left blank. If it is a numerical + * value, that value will be used, otherwise the next + * available user ID is computed and used. After this there + * will at least be a (struct passwd) for the user. + */ + if ( (NULL == pw) + && (add_user (fields[0], uid, gid) != 0)) { + fprintf (stderr, + _("%s: line %d: can't create user\n"), + Prog, line); + errors++; + continue; + } + + /* + * The password, gecos field, directory, and shell fields + * all come next. + */ + pw = pw_locate (fields[0]); + if (NULL == pw) { + fprintf (stderr, + _("%s: line %d: user '%s' does not exist in %s\n"), + Prog, line, fields[0], pw_dbname ()); + errors++; + continue; + } + newpw = *pw; + +#ifdef USE_PAM + /* keep the list of user/password for later update by PAM */ + nusers++; + lines = realloc (lines, sizeof (lines[0]) * nusers); + usernames = realloc (usernames, sizeof (usernames[0]) * nusers); + passwords = realloc (passwords, sizeof (passwords[0]) * nusers); + lines[nusers-1] = line; + usernames[nusers-1] = strdup (fields[0]); + passwords[nusers-1] = strdup (fields[1]); +#endif /* USE_PAM */ + if (add_passwd (&newpw, fields[1]) != 0) { + fprintf (stderr, + _("%s: line %d: can't update password\n"), + Prog, line); + errors++; + continue; + } + if ('\0' != fields[4][0]) { + newpw.pw_gecos = fields[4]; + } + + if ('\0' != fields[5][0]) { + newpw.pw_dir = fields[5]; + } + + if ('\0' != fields[6][0]) { + newpw.pw_shell = fields[6]; + } + + if ( ('\0' != fields[5][0]) + && (access (newpw.pw_dir, F_OK) != 0)) { +/* FIXME: should check for directory */ + mode_t mode = getdef_num ("HOME_MODE", + 0777 & ~getdef_num ("UMASK", GETDEF_DEFAULT_UMASK)); + if (newpw.pw_dir[0] != '/') { + fprintf(stderr, + _("%s: line %d: homedir must be an absolute path\n"), + Prog, line); + errors++; + continue; + }; + if (mkdir (newpw.pw_dir, mode) != 0) { + fprintf (stderr, + _("%s: line %d: mkdir %s failed: %s\n"), + Prog, line, newpw.pw_dir, + strerror (errno)); + } else if (chown (newpw.pw_dir, + newpw.pw_uid, + newpw.pw_gid) != 0) { + fprintf (stderr, + _("%s: line %d: chown %s failed: %s\n"), + Prog, line, newpw.pw_dir, + strerror (errno)); + } + } + + /* + * Update the password entry with the new changes made. + */ + if (pw_update (&newpw) == 0) { + fprintf (stderr, + _("%s: line %d: can't update entry\n"), + Prog, line); + errors++; + continue; + } + +#ifdef ENABLE_SUBIDS + /* + * Add subordinate uids if the user does not have them. + */ + if (is_sub_uid && want_subuids() && !local_sub_uid_assigned(fields[0])) { + uid_t sub_uid_start = 0; + unsigned long sub_uid_count = 0; + if (find_new_sub_uids(&sub_uid_start, &sub_uid_count) == 0) { + if (sub_uid_add(fields[0], sub_uid_start, sub_uid_count) == 0) { + fprintf (stderr, + _("%s: failed to prepare new %s entry\n"), + Prog, sub_uid_dbname ()); + } + } else { + fprintf (stderr, + _("%s: can't find subordinate user range\n"), + Prog); + errors++; + } + } + + /* + * Add subordinate gids if the user does not have them. + */ + if (is_sub_gid && want_subgids() && !local_sub_gid_assigned(fields[0])) { + gid_t sub_gid_start = 0; + unsigned long sub_gid_count = 0; + if (find_new_sub_gids(&sub_gid_start, &sub_gid_count) == 0) { + if (sub_gid_add(fields[0], sub_gid_start, sub_gid_count) == 0) { + fprintf (stderr, + _("%s: failed to prepare new %s entry\n"), + Prog, sub_uid_dbname ()); + } + } else { + fprintf (stderr, + _("%s: can't find subordinate group range\n"), + Prog); + errors++; + } + } +#endif /* ENABLE_SUBIDS */ + } + + /* + * Any detected errors will cause the entire set of changes to be + * aborted. Unlocking the password file will cause all of the + * changes to be ignored. Otherwise the file is closed, causing the + * changes to be written out all at once, and then unlocked + * afterwards. + */ + if (0 != errors) { + fprintf (stderr, + _("%s: error detected, changes ignored\n"), Prog); + fail_exit (EXIT_FAILURE); + } + + close_files (); + + nscd_flush_cache ("passwd"); + nscd_flush_cache ("group"); + sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP); + +#ifdef USE_PAM + unsigned int i; + /* Now update the passwords using PAM */ + for (i = 0; i < nusers; i++) { + if (do_pam_passwd_non_interactive ("newusers", usernames[i], passwords[i]) != 0) { + fprintf (stderr, + _("%s: (line %d, user %s) password not changed\n"), + Prog, lines[i], usernames[i]); + errors++; + } + } +#endif /* USE_PAM */ + + return ((0 == errors) ? EXIT_SUCCESS : EXIT_FAILURE); +} + |