* Copyright (c) 1991 - 1993, Julianne Frances Haugh
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
* Copyright (c) 2000 - 2006, Tomasz Kłoczko
- * Copyright (c) 2007 - 2008, Nicolas François
+ * Copyright (c) 2007 - 2009, Nicolas François
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
#ident "$Id$"
-#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <getopt.h>
/*
* exit status values
*/
+/*@-exitarg@*/
#define E_SUCCESS 0 /* success */
#define E_USAGE 2 /* invalid command syntax */
#define E_BAD_ARG 3 /* invalid argument to option */
/*
* Global variables
*/
-char *Prog;
+const char *Prog;
-static char *group_name;
+static /*@null@*/char *group_name;
static gid_t group_id;
-static char *group_passwd;
-static char *empty_list = NULL;
+static /*@null@*/char *group_passwd;
+static /*@null@*/char *empty_list = NULL;
static bool oflg = false; /* permit non-unique group ID to be specified with -g */
static bool gflg = false; /* ID value for the new group */
#ifdef SHADOWGRP
static bool is_shadow_grp;
-static bool sgr_locked = false;
#endif
-static bool gr_locked = false;
/* local function prototypes */
-static void usage (void);
+static void usage (int status);
static void new_grent (struct group *grent);
#ifdef SHADOWGRP
static void check_new_name (void);
static void close_files (void);
static void open_files (void);
-static void fail_exit (int code);
-static gid_t get_gid (const char *gidstr);
static void process_flags (int argc, char **argv);
static void check_flags (void);
static void check_perms (void);
/*
* usage - display usage message and exit
*/
-static void usage (void)
+static void usage (int status)
{
- fputs (_("Usage: groupadd [options] GROUP\n"
- "\n"
- "Options:\n"
- " -f, --force force exit with success status if the\n"
- " specified group already exists\n"
- " -g, --gid GID use GID for the new group\n"
- " -h, --help display this help message and exit\n"
- " -K, --key KEY=VALUE overrides /etc/login.defs defaults\n"
- " -o, --non-unique allow create group with duplicate\n"
- " (non-unique) GID\n"
- " -p, --password PASSWORD use encrypted password for the new group\n"
- " -r, --system create a system account\n"
- "\n"), stderr);
- exit (E_USAGE);
+ FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
+ (void) fprintf (usageout,
+ _("Usage: %s [options] GROUP\n"
+ "\n"
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -f, --force exit successfully if the group already exists,\n"
+ " and cancel -g if the GID is already used\n"), usageout);
+ (void) fputs (_(" -g, --gid GID use GID for the new group\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -K, --key KEY=VALUE override /etc/login.defs defaults\n"), usageout);
+ (void) fputs (_(" -o, --non-unique allow to create groups with duplicate\n"
+ " (non-unique) GID\n"), usageout);
+ (void) fputs (_(" -p, --password PASSWORD use this encrypted password for the new group\n"), usageout);
+ (void) fputs (_(" -r, --system create a system account\n"), usageout);
+ (void) fputs ("\n", usageout);
+ exit (status);
}
/*
struct sgrp sgrp;
#endif /* SHADOWGRP */
+ /*
+ * To add the group, we need to update /etc/group.
+ * Make sure failures will be reported.
+ */
+ add_cleanup (cleanup_report_add_group_group, group_name);
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ /* We also need to update /etc/gshadow */
+ add_cleanup (cleanup_report_add_group_gshadow, group_name);
+ }
+#endif
+
/*
* Create the initial entries for this new group.
*/
fprintf (stderr,
_("%s: failed to prepare the new %s entry '%s'\n"),
Prog, gr_dbname (), grp.gr_name);
- fail_exit (E_GRP_UPDATE);
+ exit (E_GRP_UPDATE);
}
#ifdef SHADOWGRP
/*
fprintf (stderr,
_("%s: failed to prepare the new %s entry '%s'\n"),
Prog, sgr_dbname (), sgrp.sg_name);
- fail_exit (E_GRP_UPDATE);
+ exit (E_GRP_UPDATE);
}
#endif /* SHADOWGRP */
-#ifdef WITH_AUDIT
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "adding group",
- group_name, (unsigned int) group_id,
- SHADOW_AUDIT_SUCCESS);
-#endif
- SYSLOG ((LOG_INFO, "new group: name=%s, GID=%u",
- group_name, (unsigned int) group_id));
}
/*
*/
static void close_files (void)
{
+ /* First, write the changes in the regular group database */
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 (E_GRP_UPDATE);
+ fprintf (stderr,
+ _("%s: failure while writing changes to %s\n"),
+ Prog, gr_dbname ());
+ exit (E_GRP_UPDATE);
}
- if (gr_unlock () == 0) {
- fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
- SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
#ifdef WITH_AUDIT
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "unlocking group file",
- group_name, AUDIT_NO_ID,
- SHADOW_AUDIT_FAILURE);
+ audit_logger (AUDIT_ADD_GROUP, Prog,
+ "adding group to /etc/group",
+ group_name, (unsigned int) group_id,
+ SHADOW_AUDIT_SUCCESS);
#endif
- /* continue */
- }
- gr_locked = false;
+ SYSLOG ((LOG_INFO, "group added to %s: name=%s, GID=%u",
+ gr_dbname (), group_name, (unsigned int) group_id));
+ del_cleanup (cleanup_report_add_group_group);
+
+ cleanup_unlock_group (NULL);
+ del_cleanup (cleanup_unlock_group);
+
+ /* Now, write the changes in the shadow database */
#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 (E_GRP_UPDATE);
+ _("%s: failure while writing changes to %s\n"),
+ Prog, sgr_dbname ());
+ exit (E_GRP_UPDATE);
}
- if (sgr_unlock () == 0) {
- fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
- SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
#ifdef WITH_AUDIT
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "unlocking gshadow file",
- group_name, AUDIT_NO_ID,
- SHADOW_AUDIT_FAILURE);
+ audit_logger (AUDIT_ADD_GROUP, Prog,
+ "adding group to /etc/gshadow",
+ group_name, (unsigned int) group_id,
+ SHADOW_AUDIT_SUCCESS);
#endif
- /* continue */
- }
- sgr_locked = false;
+ SYSLOG ((LOG_INFO, "group added to %s: name=%s",
+ sgr_dbname (), group_name));
+ del_cleanup (cleanup_report_add_group_gshadow);
+
+ cleanup_unlock_gshadow (NULL);
+ del_cleanup (cleanup_unlock_gshadow);
}
#endif /* SHADOWGRP */
+
+ /* Report success at the system level */
+#ifdef WITH_AUDIT
+ audit_logger (AUDIT_ADD_GROUP, Prog,
+ "",
+ group_name, (unsigned int) group_id,
+ SHADOW_AUDIT_SUCCESS);
+#endif
+ SYSLOG ((LOG_INFO, "new group: name=%s, GID=%u",
+ group_name, (unsigned int) group_id));
+ del_cleanup (cleanup_report_add_group);
}
/*
*/
static void open_files (void)
{
+ /* First, lock the databases */
if (gr_lock () == 0) {
fprintf (stderr,
_("%s: cannot lock %s; try again later.\n"),
Prog, gr_dbname ());
-#ifdef WITH_AUDIT
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "locking group file",
- group_name, AUDIT_NO_ID,
- SHADOW_AUDIT_FAILURE);
-#endif
- fail_exit (E_GRP_UPDATE);
- }
- gr_locked = true;
- if (gr_open (O_RDWR) == 0) {
- fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
- SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
-#ifdef WITH_AUDIT
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "opening group file",
- group_name, AUDIT_NO_ID,
- SHADOW_AUDIT_FAILURE);
-#endif
- fail_exit (E_GRP_UPDATE);
+ exit (E_GRP_UPDATE);
}
+ add_cleanup (cleanup_unlock_group, NULL);
+
#ifdef SHADOWGRP
if (is_shadow_grp) {
if (sgr_lock () == 0) {
fprintf (stderr,
_("%s: cannot lock %s; try again later.\n"),
Prog, sgr_dbname ());
-#ifdef WITH_AUDIT
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "locking gshadow file",
- group_name, AUDIT_NO_ID,
- SHADOW_AUDIT_FAILURE);
-#endif
- fail_exit (E_GRP_UPDATE);
- }
- sgr_locked = true;
- if (sgr_open (O_RDWR) == 0) {
- fprintf (stderr,
- _("%s: cannot open %s\n"), Prog, sgr_dbname ());
- SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
-#ifdef WITH_AUDIT
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "opening gshadow file",
- group_name, AUDIT_NO_ID,
- SHADOW_AUDIT_FAILURE);
-#endif
- fail_exit (E_GRP_UPDATE);
+ exit (E_GRP_UPDATE);
}
+ add_cleanup (cleanup_unlock_gshadow, NULL);
}
#endif /* SHADOWGRP */
-}
-/*
- * fail_exit - exit with an error code after unlocking files
- */
-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 ()));
-#ifdef WITH_AUDIT
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "unlocking group file",
- group_name, AUDIT_NO_ID,
- SHADOW_AUDIT_FAILURE);
-#endif
- /* 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 ()));
-#ifdef WITH_AUDIT
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "unlocking gshadow file",
- group_name, AUDIT_NO_ID,
- SHADOW_AUDIT_FAILURE);
-#endif
- /* continue */
- }
- }
-#endif
+ /*
+ * Now if the group is not added, it's our fault.
+ * Make sure failures will be reported.
+ */
+ add_cleanup (cleanup_report_add_group, group_name);
-#ifdef WITH_AUDIT
- if (code != E_SUCCESS) {
- audit_logger (AUDIT_ADD_GROUP, Prog,
- "adding group",
- group_name, AUDIT_NO_ID,
- SHADOW_AUDIT_FAILURE);
+ /* And now open the databases */
+ if (gr_open (O_RDWR) == 0) {
+ fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
+ exit (E_GRP_UPDATE);
}
-#endif
-
- exit (code);
-}
-/*
- * get_id - validate and get group ID
- */
-static gid_t get_gid (const char *gidstr)
-{
- long val;
- char *errptr;
-
- val = strtol (gidstr, &errptr, 10);
- if (('\0' != *errptr) || (errno == ERANGE) || (val < 0)) {
- fprintf (stderr, _("%s: invalid numeric argument '%s'\n"),
- Prog, gidstr);
- exit (E_BAD_ARG);
+#ifdef SHADOWGRP
+ if (is_shadow_grp) {
+ if (sgr_open (O_RDWR) == 0) {
+ fprintf (stderr,
+ _("%s: cannot open %s\n"),
+ Prog, sgr_dbname ());
+ SYSLOG ((LOG_WARN, "cannot open %s", sgr_dbname ()));
+ exit (E_GRP_UPDATE);
+ }
}
- return (gid_t) val;
+#endif /* SHADOWGRP */
}
/*
break;
case 'g':
gflg = true;
- group_id = get_gid (optarg);
+ if ( (get_gid (optarg, &group_id) == 0)
+ || (group_id == (gid_t)-1)) {
+ fprintf (stderr,
+ _("%s: invalid group ID '%s'\n"),
+ Prog, optarg);
+ exit (E_BAD_ARG);
+ }
break;
case 'h':
- usage ();
+ usage (E_SUCCESS);
break;
case 'K':
/*
rflg = true;
break;
default:
- usage ();
+ usage (E_USAGE);
}
}
* Check the flags consistency
*/
if (optind != argc - 1) {
- usage ();
+ usage (E_USAGE);
}
group_name = argv[optind];
{
/* -o does not make sense without -g */
if (oflg && !gflg) {
- usage ();
+ usage (E_USAGE);
}
check_new_name ();
/* The group already exist */
if (fflg) {
/* OK, no need to do anything */
- fail_exit (E_SUCCESS);
+ exit (E_SUCCESS);
}
- fprintf (stderr, _("%s: group '%s' already exists\n"), Prog, group_name);
- fail_exit (E_NAME_IN_USE);
+ fprintf (stderr,
+ _("%s: group '%s' already exists\n"),
+ Prog, group_name);
+ exit (E_NAME_IN_USE);
}
if (gflg && (getgrgid (group_id) != NULL)) {
/* Turn off -g, we can use any GID */
gflg = false;
} else {
- fprintf (stderr, _("%s: GID '%lu' already exists\n"),
+ fprintf (stderr,
+ _("%s: GID '%lu' already exists\n"),
Prog, (unsigned long int) group_id);
- fail_exit (E_GID_IN_USE);
+ exit (E_GID_IN_USE);
}
}
}
pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
if (NULL == pampw) {
- retval = PAM_USER_UNKNOWN;
- } else {
- retval = pam_start ("groupadd", pampw->pw_name, &conv, &pamh);
+ fprintf (stderr,
+ _("%s: Cannot determine your user name.\n"),
+ Prog);
+ exit (1);
}
+ retval = pam_start ("groupadd", pampw->pw_name, &conv, &pamh);
+
if (PAM_SUCCESS == retval) {
retval = pam_authenticate (pamh, 0);
}
#ifdef WITH_AUDIT
audit_help_open ();
#endif
+ atexit (do_cleanups);
+
/*
* Get my name so that I can use it to report errors.
*/
if (!gflg) {
if (find_new_gid (rflg, &group_id, NULL) < 0) {
- fprintf (stderr, _("%s: can't create group\n"), Prog);
- fail_exit (E_GID_IN_USE);
+ exit (E_GID_IN_USE);
}
}
nscd_flush_cache ("group");
exit (E_SUCCESS);
- /* NOT REACHED */
+ /*@notreached@*/
}