diff options
Diffstat (limited to 'src/groupmems.c')
-rw-r--r-- | src/groupmems.c | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/src/groupmems.c b/src/groupmems.c new file mode 100644 index 00000000..e4f107f9 --- /dev/null +++ b/src/groupmems.c @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2000 , International Business Machines + * George Kraft IV, gk4@us.ibm.com, 03/23/2000 + * Copyright (c) 2000 - 2006, Tomasz Kłoczko + * Copyright (c) 2007 - 2011, Nicolas François + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the copyright holders or contributors may not be used to + * endorse or promote products derived from this software without + * specific prior written permission. + * + * 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. + */ + +#include <config.h> + +#include <fcntl.h> +#include <getopt.h> +#include <grp.h> +#include <stdio.h> +#include <sys/types.h> +#ifdef USE_PAM +#include "pam_defs.h" +#endif /* USE_PAM */ +#include <pwd.h> +#include "defines.h" +#include "prototypes.h" +#include "groupio.h" +#ifdef SHADOWGRP +#include "sgroupio.h" +#endif + +/* Exit Status Values */ +/*@-exitarg@*/ +#define EXIT_SUCCESS 0 /* success */ +#define EXIT_USAGE 1 /* invalid command syntax */ +#define EXIT_GROUP_FILE 2 /* group file access problems */ +#define EXIT_NOT_ROOT 3 /* not superuser */ +#define EXIT_NOT_EROOT 4 /* not effective superuser */ +#define EXIT_NOT_PRIMARY 5 /* not primary owner of group */ +#define EXIT_NOT_MEMBER 6 /* member of group does not exist */ +#define EXIT_MEMBER_EXISTS 7 /* member of group already exists */ +#define EXIT_INVALID_USER 8 /* specified user does not exist */ +#define EXIT_INVALID_GROUP 9 /* specified group does not exist */ + +/* + * Global variables + */ +const char *Prog; + +static char *adduser = NULL; +static char *deluser = NULL; +static char *thisgroup = NULL; +static bool purge = false; +static bool list = false; +static int exclusive = 0; +static bool gr_locked = false; +#ifdef SHADOWGRP +/* Indicate if shadow groups are enabled on the system + * (/etc/gshadow present) */ +static bool is_shadowgrp; +static bool sgr_locked = false; +#endif + +/* local function prototypes */ +static char *whoami (void); +static void add_user (const char *user, + const struct group *grp); +static void remove_user (const char *user, + const struct group *grp); +static void purge_members (const struct group *grp); +static void display_members (const char *const *members); +static /*@noreturn@*/void usage (int status); +static void process_flags (int argc, char **argv); +static void check_perms (void); +static void fail_exit (int code); +#define isroot() (getuid () == 0) + +static char *whoami (void) +{ + /* local, no need for xgetgrgid */ + struct group *grp = getgrgid (getgid ()); + /* local, no need for xgetpwuid */ + struct passwd *usr = getpwuid (getuid ()); + + if ( (NULL != usr) + && (NULL != grp) + && (0 == strcmp (usr->pw_name, grp->gr_name))) { + return xstrdup (usr->pw_name); + } else { + return NULL; + } +} + +/* + * add_user - Add an user to the specified group + */ +static void add_user (const char *user, + const struct group *grp) +{ + struct group *newgrp; + + /* Make sure the user is not already part of the group */ + if (is_on_list (grp->gr_mem, user)) { + fprintf (stderr, + _("%s: user '%s' is already a member of '%s'\n"), + Prog, user, grp->gr_name); + fail_exit (EXIT_MEMBER_EXISTS); + } + + newgrp = __gr_dup(grp); + if (NULL == newgrp) { + fprintf (stderr, + _("%s: Out of memory. Cannot update %s.\n"), + Prog, gr_dbname ()); + fail_exit (13); + } + + /* Add the user to the /etc/group group */ + newgrp->gr_mem = add_list (newgrp->gr_mem, user); + +#ifdef SHADOWGRP + if (is_shadowgrp) { + const struct sgrp *sg = sgr_locate (newgrp->gr_name); + struct sgrp *newsg; + + if (NULL == sg) { + /* Create a shadow group based on this group */ + static struct sgrp sgrent; + sgrent.sg_name = xstrdup (newgrp->gr_name); + sgrent.sg_mem = dup_list (newgrp->gr_mem); + sgrent.sg_adm = (char **) xmalloc (sizeof (char *)); +#ifdef FIRST_MEMBER_IS_ADMIN + if (sgrent.sg_mem[0]) { + sgrent.sg_adm[0] = xstrdup (sgrent.sg_mem[0]); + sgrent.sg_adm[1] = NULL; + } else +#endif + { + sgrent.sg_adm[0] = NULL; + } + + /* Move any password to gshadow */ + sgrent.sg_passwd = newgrp->gr_passwd; + newgrp->gr_passwd = SHADOW_PASSWD_STRING; + + newsg = &sgrent; + } else { + newsg = __sgr_dup (sg); + if (NULL == newsg) { + fprintf (stderr, + _("%s: Out of memory. Cannot update %s.\n"), + Prog, sgr_dbname ()); + fail_exit (13); + } + /* Add the user to the members */ + newsg->sg_mem = add_list (newsg->sg_mem, user); + /* Do not touch the administrators */ + } + + if (sgr_update (newsg) == 0) { + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, sgr_dbname (), newsg->sg_name); + fail_exit (13); + } + } +#endif + + if (gr_update (newgrp) == 0) { + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, gr_dbname (), newgrp->gr_name); + fail_exit (13); + } +} + +/* + * remove_user - Remove an user from a given group + */ +static void remove_user (const char *user, + const struct group *grp) +{ + struct group *newgrp; + + /* Check if the user is a member of the specified group */ + if (!is_on_list (grp->gr_mem, user)) { + fprintf (stderr, + _("%s: user '%s' is not a member of '%s'\n"), + Prog, user, grp->gr_name); + fail_exit (EXIT_NOT_MEMBER); + } + + newgrp = __gr_dup (grp); + if (NULL == newgrp) { + fprintf (stderr, + _("%s: Out of memory. Cannot update %s.\n"), + Prog, gr_dbname ()); + fail_exit (13); + } + + /* Remove the user from the /etc/group group */ + newgrp->gr_mem = del_list (newgrp->gr_mem, user); + +#ifdef SHADOWGRP + if (is_shadowgrp) { + const struct sgrp *sg = sgr_locate (newgrp->gr_name); + struct sgrp *newsg; + + if (NULL == sg) { + /* Create a shadow group based on this group */ + static struct sgrp sgrent; + sgrent.sg_name = xstrdup (newgrp->gr_name); + sgrent.sg_mem = dup_list (newgrp->gr_mem); + sgrent.sg_adm = (char **) xmalloc (sizeof (char *)); +#ifdef FIRST_MEMBER_IS_ADMIN + if (sgrent.sg_mem[0]) { + sgrent.sg_adm[0] = xstrdup (sgrent.sg_mem[0]); + sgrent.sg_adm[1] = NULL; + } else +#endif + { + sgrent.sg_adm[0] = NULL; + } + + /* Move any password to gshadow */ + sgrent.sg_passwd = newgrp->gr_passwd; + newgrp->gr_passwd = SHADOW_PASSWD_STRING; + + newsg = &sgrent; + } else { + newsg = __sgr_dup (sg); + if (NULL == newsg) { + fprintf (stderr, + _("%s: Out of memory. Cannot update %s.\n"), + Prog, sgr_dbname ()); + fail_exit (13); + } + /* Remove the user from the members */ + newsg->sg_mem = del_list (newsg->sg_mem, user); + /* Remove the user from the administrators */ + newsg->sg_adm = del_list (newsg->sg_adm, user); + } + + if (sgr_update (newsg) == 0) { + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, sgr_dbname (), newsg->sg_name); + fail_exit (13); + } + } +#endif + + if (gr_update (newgrp) == 0) { + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, gr_dbname (), newgrp->gr_name); + fail_exit (13); + } +} + +/* + * purge_members - Rmeove every members of the specified group + */ +static void purge_members (const struct group *grp) +{ + struct group *newgrp = __gr_dup (grp); + + if (NULL == newgrp) { + fprintf (stderr, + _("%s: Out of memory. Cannot update %s.\n"), + Prog, gr_dbname ()); + fail_exit (13); + } + + /* Remove all the members of the /etc/group group */ + newgrp->gr_mem[0] = NULL; + +#ifdef SHADOWGRP + if (is_shadowgrp) { + const struct sgrp *sg = sgr_locate (newgrp->gr_name); + struct sgrp *newsg; + + if (NULL == sg) { + /* Create a shadow group based on this group */ + static struct sgrp sgrent; + sgrent.sg_name = xstrdup (newgrp->gr_name); + sgrent.sg_mem = (char **) xmalloc (sizeof (char *)); + sgrent.sg_mem[0] = NULL; + sgrent.sg_adm = (char **) xmalloc (sizeof (char *)); + sgrent.sg_adm[0] = NULL; + + /* Move any password to gshadow */ + sgrent.sg_passwd = newgrp->gr_passwd; + newgrp->gr_passwd = xstrdup(SHADOW_PASSWD_STRING); + + newsg = &sgrent; + } else { + newsg = __sgr_dup (sg); + if (NULL == newsg) { + fprintf (stderr, + _("%s: Out of memory. Cannot update %s.\n"), + Prog, sgr_dbname ()); + fail_exit (13); + } + /* Remove all the members of the /etc/gshadow + * group */ + newsg->sg_mem[0] = NULL; + /* Remove all the administrators of the + * /etc/gshadow group */ + newsg->sg_adm[0] = NULL; + } + + if (sgr_update (newsg) == 0) { + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, sgr_dbname (), newsg->sg_name); + fail_exit (13); + } + } +#endif + + if (gr_update (newgrp) == 0) { + fprintf (stderr, + _("%s: failed to prepare the new %s entry '%s'\n"), + Prog, gr_dbname (), newgrp->gr_name); + fail_exit (13); + } +} + +static void display_members (const char *const *members) +{ + int i; + + for (i = 0; NULL != members[i]; i++) { + printf ("%s ", members[i]); + + if (NULL == members[i + 1]) { + printf ("\n"); + } else { + printf (" "); + } + } +} + +static /*@noreturn@*/void usage (int status) +{ + FILE *usageout = (EXIT_SUCCESS != status) ? stderr : stdout; + (void) fprintf (usageout, + _("Usage: %s [options] [action]\n" + "\n" + "Options:\n"), + Prog); + (void) fputs (_(" -g, --group groupname change groupname instead of the user's group\n" + " (root only)\n"), usageout); + (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout); + (void) fputs (_("\n"), usageout); + (void) fputs (_("Actions:\n"), usageout); + (void) fputs (_(" -a, --add username add username to the members of the group\n"), usageout); + (void) fputs (_(" -d, --delete username remove username from the members of the group\n"), usageout); + (void) fputs (_(" -h, --help display this help message and exit\n"), usageout); + (void) fputs (_(" -p, --purge purge all members from the group\n"), usageout); + (void) fputs (_(" -l, --list list the members of the group\n"), usageout); + exit (status); +} + +/* + * process_flags - perform command line argument setting + */ +static void process_flags (int argc, char **argv) +{ + int c; + static struct option long_options[] = { + {"add", required_argument, NULL, 'a'}, + {"delete", required_argument, NULL, 'd'}, + {"group", required_argument, NULL, 'g'}, + {"help", no_argument, NULL, 'h'}, + {"list", no_argument, NULL, 'l'}, + {"purge", no_argument, NULL, 'p'}, + {"root", required_argument, NULL, 'R'}, + {NULL, 0, NULL, '\0'} + }; + + while ((c = getopt_long (argc, argv, "a:d:g:hlpR:", + long_options, NULL)) != EOF) { + switch (c) { + case 'a': + adduser = xstrdup (optarg); + ++exclusive; + break; + case 'd': + deluser = xstrdup (optarg); + ++exclusive; + break; + case 'g': + thisgroup = xstrdup (optarg); + break; + case 'h': + usage (EXIT_SUCCESS); + /*@notreached@*/break; + case 'l': + list = true; + ++exclusive; + break; + case 'p': + purge = true; + ++exclusive; + break; + case 'R': /* no-op, handled in process_root_flag () */ + break; + default: + usage (EXIT_USAGE); + } + } + + if ((exclusive > 1) || (optind < argc)) { + usage (EXIT_USAGE); + } + + /* local, no need for xgetpwnam */ + if ( (NULL != adduser) + && (getpwnam (adduser) == NULL)) { + fprintf (stderr, _("%s: user '%s' does not exist\n"), + Prog, adduser); + fail_exit (EXIT_INVALID_USER); + } + +} + +static void check_perms (void) +{ + if (!list) { +#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 (1); + } + + retval = pam_start ("groupmems", 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 (1); + } + (void) pam_end (pamh, retval); +#endif + } +} + +static void fail_exit (int code) +{ + 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 + + exit (code); +} + +static void open_files (void) +{ + if (!list) { + if (gr_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, gr_dbname ()); + fail_exit (EXIT_GROUP_FILE); + } + gr_locked = true; + +#ifdef SHADOWGRP + if (is_shadowgrp) { + if (sgr_lock () == 0) { + fprintf (stderr, + _("%s: cannot lock %s; try again later.\n"), + Prog, sgr_dbname ()); + fail_exit (EXIT_GROUP_FILE); + } + sgr_locked = true; + } +#endif + } + + if (gr_open (list ? O_RDONLY : O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ()); + fail_exit (EXIT_GROUP_FILE); + } + +#ifdef SHADOWGRP + if (is_shadowgrp) { + if (sgr_open (list ? O_RDONLY : O_RDWR) == 0) { + fprintf (stderr, _("%s: cannot open %s\n"), Prog, sgr_dbname ()); + fail_exit (EXIT_GROUP_FILE); + } + } +#endif +} + +static void close_files (void) +{ + if ((gr_close () == 0) && !list) { + 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_GROUP_FILE); + } + 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 */ + } + gr_locked = false; + } + +#ifdef SHADOWGRP + if (is_shadowgrp) { + if ((sgr_close () == 0) && !list) { + 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_GROUP_FILE); + } + 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 */ + } + sgr_locked = false; + } + } +#endif +} + +int main (int argc, char **argv) +{ + char *name; + const struct group *grp; + + /* + * Get my name so that I can use it to report errors. + */ + Prog = Basename (argv[0]); + + (void) setlocale (LC_ALL, ""); + (void) bindtextdomain (PACKAGE, LOCALEDIR); + (void) textdomain (PACKAGE); + + process_root_flag ("-R", argc, argv); + + OPENLOG ("groupmems"); + +#ifdef SHADOWGRP + is_shadowgrp = sgr_file_present (); +#endif + + process_flags (argc, argv); + + if (NULL == thisgroup) { + name = whoami (); + if (!list && (NULL == name)) { + fprintf (stderr, _("%s: your groupname does not match your username\n"), Prog); + fail_exit (EXIT_NOT_PRIMARY); + } + } else { + name = thisgroup; + if (!list && !isroot ()) { + fprintf (stderr, _("%s: only root can use the -g/--group option\n"), Prog); + fail_exit (EXIT_NOT_ROOT); + } + } + + check_perms (); + + open_files (); + + grp = gr_locate (name); + if (NULL == grp) { + fprintf (stderr, _("%s: group '%s' does not exist in %s\n"), + Prog, name, gr_dbname ()); + fail_exit (EXIT_INVALID_GROUP); + } + + if (list) { + display_members ((const char *const *)grp->gr_mem); + } else if (NULL != adduser) { + add_user (adduser, grp); + } else if (NULL != deluser) { + remove_user (deluser, grp); + } else if (purge) { + purge_members (grp); + } + + close_files (); + + exit (EXIT_SUCCESS); +} + |