diff --git a/SOURCES/shadow-4.1.5.1-chgrp-guard.patch b/SOURCES/shadow-4.1.5.1-chgrp-guard.patch
new file mode 100644
index 0000000..220884c
--- /dev/null
+++ b/SOURCES/shadow-4.1.5.1-chgrp-guard.patch
@@ -0,0 +1,44 @@
+diff -up shadow-4.1.5.1/man/usermod.8.xml.chgrp-guard shadow-4.1.5.1/man/usermod.8.xml
+--- shadow-4.1.5.1/man/usermod.8.xml.chgrp-guard 2016-05-04 13:44:17.267917583 +0200
++++ shadow-4.1.5.1/man/usermod.8.xml 2016-05-04 13:44:17.284917968 +0200
+@@ -198,6 +198,12 @@
+ The group ownership of files outside of the user's home directory
+ must be fixed manually.
+
++
++ The change of the group ownership of files inside of the user's
++ home directory is also not done if the home dir owner uid is
++ different from the current or new user id. This is safety measure
++ for special home directories such as /.
++
+
+
+
+@@ -364,6 +370,12 @@
+ must be fixed manually.
+
+
++ The change of the user ownership of files inside of the user's
++ home directory is also not done if the home dir owner uid is
++ different from the current or new user id. This is safety measure
++ for special home directories such as /.
++
++
+ No checks will be performed with regard to the
+ , ,
+ , or
+diff -up shadow-4.1.5.1/src/usermod.c.chgrp-guard shadow-4.1.5.1/src/usermod.c
+--- shadow-4.1.5.1/src/usermod.c.chgrp-guard 2016-05-04 13:44:17.280917877 +0200
++++ shadow-4.1.5.1/src/usermod.c 2016-05-04 13:44:17.285917991 +0200
+@@ -1971,7 +1971,10 @@ int main (int argc, char **argv)
+ }
+
+ if (!mflg && (uflg || gflg)) {
+- if (access (dflg ? user_newhome : user_home, F_OK) == 0) {
++ struct stat sb;
++
++ if (stat (dflg ? user_newhome : user_home, &sb) == 0 &&
++ ((uflg && sb.st_uid == user_newid) || sb.st_uid == user_id)) {
+ /*
+ * Change the UID on all of the files owned by
+ * `user_id' to `user_newid' in the user's home
diff --git a/SOURCES/shadow-4.1.5.1-goodname.patch b/SOURCES/shadow-4.1.5.1-goodname.patch
index 1fdd84f..49cba21 100644
--- a/SOURCES/shadow-4.1.5.1-goodname.patch
+++ b/SOURCES/shadow-4.1.5.1-goodname.patch
@@ -56,41 +56,6 @@ diff -up shadow-4.1.5.1/man/groupadd.8.xml.goodname shadow-4.1.5.1/man/groupadd.
Groupnames may only be up to &GROUP_NAME_MAX_LENGTH; characters long.
-diff -up shadow-4.1.5.1/man/man8/groupadd.8.goodname shadow-4.1.5.1/man/man8/groupadd.8
---- shadow-4.1.5.1/man/man8/groupadd.8.goodname 2012-05-25 13:58:40.000000000 +0200
-+++ shadow-4.1.5.1/man/man8/groupadd.8 2012-09-19 18:44:42.175123079 +0200
-@@ -190,9 +190,7 @@ Shadow password suite configuration\&.
- .RE
- .SH "CAVEATS"
- .PP
--Groupnames must start with a lower case letter or an underscore, followed by lower case letters, digits, underscores, or dashes\&. They can end with a dollar sign\&. In regular expression terms: [a\-z_][a\-z0\-9_\-]*[$]?
--.PP
--Groupnames may only be up to 16 characters long\&.
-+Groupnames may only be up to 32 characters long\&.
- .PP
- You may not add a NIS or LDAP group\&. This must be performed on the corresponding server\&.
- .PP
-diff -up shadow-4.1.5.1/man/man8/useradd.8.goodname shadow-4.1.5.1/man/man8/useradd.8
---- shadow-4.1.5.1/man/man8/useradd.8.goodname 2012-05-25 13:59:28.000000000 +0200
-+++ shadow-4.1.5.1/man/man8/useradd.8 2012-09-19 18:46:09.249033949 +0200
-@@ -224,7 +224,7 @@ is not enabled, no home directories are
- .PP
- \fB\-M\fR
- .RS 4
--Do no create the user\*(Aqs home directory, even if the system wide setting from
-+Do not create the user\*(Aqs home directory, even if the system wide setting from
- /etc/login\&.defs
- (\fBCREATE_HOME\fR) is set to
- \fIyes\fR\&.
-@@ -430,8 +430,6 @@ Similarly, if the username already exist
- \fBuseradd\fR
- will deny the user account creation request\&.
- .PP
--Usernames must start with a lower case letter or an underscore, followed by lower case letters, digits, underscores, or dashes\&. They can end with a dollar sign\&. In regular expression terms: [a\-z_][a\-z0\-9_\-]*[$]?
--.PP
- Usernames may only be up to 32 characters long\&.
- .SH "CONFIGURATION"
- .PP
diff -up shadow-4.1.5.1/man/useradd.8.xml.goodname shadow-4.1.5.1/man/useradd.8.xml
--- shadow-4.1.5.1/man/useradd.8.xml.goodname 2012-05-25 13:45:29.000000000 +0200
+++ shadow-4.1.5.1/man/useradd.8.xml 2012-09-19 18:43:53.493160675 +0200
diff --git a/SOURCES/shadow-4.1.5.1-id-alloc.patch b/SOURCES/shadow-4.1.5.1-id-alloc.patch
new file mode 100644
index 0000000..6c55739
--- /dev/null
+++ b/SOURCES/shadow-4.1.5.1-id-alloc.patch
@@ -0,0 +1,1213 @@
+Previously, this allocation was optimized for an outdated
+deployment style (that of /etc/group alongside nss_db). The issue
+here is that this results in extremely poor performance when using
+SSSD, Winbind or nss_ldap.
+
+There were actually two serious bugs here that have been addressed:
+
+1) Running getgrent() loops won't work in most SSSD or Winbind
+environments, as full group enumeration is disabled by default.
+This could easily result in auto-allocating a group that was
+already in use. (This might result in a security issue as well, if
+the shared GID is a privileged group).
+
+2) For system groups, the loop was always iterating through the
+complete SYS_GID_MIN->SYS_GID_MAX range. On SSSD and Winbind, this
+means hundreds of round-trips to LDAP (unless the GIDs were
+specifically configured to be ignored by the SSSD or winbindd).
+To a user with a slow connection to their LDAP server, this would
+appear as if groupadd -r was hung. (Though it would eventually
+complete).
+
+This patch changes the algorithm to be more favorable for LDAP
+environments, at the expense of some performance when using nss_db.
+Given that the DB is a local service, this should have a negligible
+effect from a user's perspective.
+
+With the new algorithm, we simply first iterate through all entries
+in the local database with gr_next(), recording the IDs that are in
+use. We then start from the highest presumed-available entry and
+call getgrgid() to see if it is available. We continue this until
+we come to the first unused GID. We then select that and return it.
+
+If we make it through all the remaining IDs without finding a free
+one, we start over from the beginning of the range and try to find
+room in one of the gaps in the range.
+
+The patch was originally written by Stephen Gallagher and applied
+identically also to the user allocation by Tomáš Mráz.
+
+diff -up shadow-4.1.5.1/libmisc/find_new_gid.c.id-alloc shadow-4.1.5.1/libmisc/find_new_gid.c
+--- shadow-4.1.5.1/libmisc/find_new_gid.c.id-alloc 2014-09-10 10:25:41.165524986 +0200
++++ shadow-4.1.5.1/libmisc/find_new_gid.c 2014-09-10 10:25:41.195525677 +0200
+@@ -39,6 +39,118 @@
+ #include "getdef.h"
+
+ /*
++ * get_ranges - Get the minimum and maximum ID ranges for the search
++ *
++ * This function will return the minimum and maximum ranges for IDs
++ *
++ * 0: The function completed successfully
++ * EINVAL: The provided ranges are impossible (such as maximum < minimum)
++ *
++ * preferred_min: The special-case minimum value for a specifically-
++ * requested ID, which may be lower than the standard min_id
++ */
++static int get_ranges(bool sys_group, gid_t *min_id, gid_t *max_id,
++ gid_t *preferred_min)
++{
++ gid_t gid_def_max = 0;
++
++ if (sys_group) {
++ /* System groups */
++
++ /* A requested ID is allowed to be below the autoselect range */
++ *preferred_min = (gid_t) 1;
++
++ /* Get the minimum ID range from login.defs or default to 101 */
++ *min_id = (gid_t) getdef_ulong("SYS_GID_MIN", 101UL);
++
++ /*
++ * If SYS_GID_MAX is unspecified, we should assume it to be one
++ * less than the GID_MIN (which is reserved for non-system accounts)
++ */
++ gid_def_max = (gid_t) getdef_ulong("GID_MIN", 1000UL) - 1;
++ *max_id = (gid_t) getdef_ulong("SYS_GID_MAX",
++ (unsigned long) gid_def_max);
++
++ /* Check that the ranges make sense */
++ if (*max_id < *min_id) {
++ (void) fprintf (stderr,
++ _("%s: Invalid configuration: SYS_GID_MIN (%lu), "
++ "GID_MIN (%lu), SYS_GID_MAX (%lu)\n"),
++ Prog, (unsigned long) *min_id,
++ getdef_ulong ("GID_MIN", 1000UL),
++ (unsigned long) *max_id);
++ return EINVAL;
++ }
++ } else {
++ /* Non-system groups */
++
++ /* Get the values from login.defs or use reasonable defaults */
++ *min_id = (gid_t) getdef_ulong("GID_MIN", 1000UL);
++ *max_id = (gid_t) getdef_ulong("GID_MAX", 60000UL);
++
++ /*
++ * The preferred minimum should match the standard ID minimum
++ * for non-system groups.
++ */
++ *preferred_min = *min_id;
++
++ /* Check that the ranges make sense */
++ if (*max_id < *min_id) {
++ (void) fprintf(stderr,
++ _("%s: Invalid configuration: GID_MIN (%lu), "
++ "GID_MAX (%lu)\n"),
++ Prog, (unsigned long) *min_id,
++ (unsigned long) *max_id);
++ return EINVAL;
++ }
++ }
++
++ return 0;
++}
++
++/*
++ * check_gid - See if the requested GID is available
++ *
++ * On success, return 0
++ * If the ID is in use, return EEXIST
++ * If the ID is outside the range, return ERANGE
++ * In other cases, return errno from getgrgid()
++ */
++static int check_gid(const gid_t gid,
++ const gid_t gid_min,
++ const gid_t gid_max,
++ bool *used_gids)
++{
++ /* First test that the preferred ID is in the range */
++ if (gid < gid_min || gid > gid_max) {
++ return ERANGE;
++ }
++
++ /*
++ * Check whether we already detected this GID
++ * using the gr_next() loop
++ */
++ if (used_gids != NULL && used_gids[gid]) {
++ return EEXIST;
++ }
++ /* Check if the GID exists according to NSS */
++ errno = 0;
++ if (getgrgid(gid) != NULL) {
++ return EEXIST;
++ } else {
++ /* getgrgid() was NULL, check whether this was
++ * due to an error, so we can report it.
++ */
++ /* ignore errors for now * if (errno != 0) {
++ return errno;
++ } */
++ }
++
++ /* If we've made it here, the GID must be available */
++ return 0;
++}
++
++/*
+ * find_new_gid - Find a new unused GID.
+ *
+ * If successful, find_new_gid provides an unused group ID in the
+@@ -48,166 +160,339 @@
+ *
+ * Return 0 on success, -1 if no unused GIDs are available.
+ */
+-int find_new_gid (bool sys_group,
+- gid_t *gid,
+- /*@null@*/gid_t const *preferred_gid)
++int find_new_gid(bool sys_group,
++ gid_t *gid,
++ /*@null@*/gid_t const *preferred_gid)
+ {
+- const struct group *grp;
+- gid_t gid_min, gid_max, group_id;
+ bool *used_gids;
++ const struct group *grp;
++ gid_t gid_min, gid_max, preferred_min;
++ gid_t group_id, id;
++ gid_t lowest_found, highest_found;
++ int result;
++ int nospam = 0;
+
+- assert (gid != NULL);
++ assert(gid != NULL);
+
+- if (!sys_group) {
+- gid_min = (gid_t) getdef_ulong ("GID_MIN", 1000UL);
+- gid_max = (gid_t) getdef_ulong ("GID_MAX", 60000UL);
+- if (gid_max < gid_min) {
+- (void) fprintf (stderr,
+- _("%s: Invalid configuration: GID_MIN (%lu), GID_MAX (%lu)\n"),
+- Prog, (unsigned long) gid_min, (unsigned long) gid_max);
+- return -1;
+- }
+- } else {
+- gid_min = (gid_t) 1;
+- gid_max = (gid_t) getdef_ulong ("GID_MIN", 1000UL) - 1;
+- gid_max = (gid_t) getdef_ulong ("SYS_GID_MAX", (unsigned long) gid_max);
+- if (gid_max < gid_min) {
+- (void) fprintf (stderr,
+- _("%s: Invalid configuration: SYS_GID_MIN (%lu), GID_MIN (%lu), SYS_GID_MAX (%lu)\n"),
+- Prog, (unsigned long) gid_min, getdef_ulong ("GID_MIN", 1000UL), (unsigned long) gid_max);
++ /*
++ * First, figure out what ID range is appropriate for
++ * automatic assignment
++ */
++ result = get_ranges(sys_group, &gid_min, &gid_max, &preferred_min);
++ if (result == EINVAL) {
++ return -1;
++ }
++
++ /* Check if the preferred GID is available */
++ if (preferred_gid) {
++ result = check_gid(*preferred_gid, preferred_min, gid_max, NULL);
++ if (result == 0) {
++ /*
++ * Make sure the GID isn't queued for use already
++ */
++ if (gr_locate_gid (*preferred_gid) == NULL) {
++ *gid = *preferred_gid;
++ return 0;
++ }
++ /*
++ * gr_locate_gid() found the GID in an as-yet uncommitted
++ * entry. We'll proceed below and auto-set a GID.
++ */
++ } else if (result == EEXIST || result == ERANGE) {
++ /*
++ * Continue on below. At this time, we won't
++ * treat these two cases differently.
++ */
++ } else {
++ /*
++ * An unexpected error occurred. We should report
++ * this and fail the group creation.
++ * This differs from the automatic creation
++ * behavior below, since if a specific GID was
++ * requested and generated an error, the user is
++ * more likely to want to stop and address the
++ * issue.
++ */
++ fprintf(stderr,
++ _("%s: Encountered error attempting to use "
++ "preferred GID: %s\n"),
++ Prog, strerror(result));
+ return -1;
+ }
+ }
++
++ /*
++ * Search the entire group file,
++ * looking for the next unused value.
++ *
++ * We first check the local database with gr_rewind/gr_next to find
++ * all local values that are in use.
++ *
++ * We then compare the next free value to all databases (local and
++ * remote) and iterate until we find a free one. If there are free
++ * values beyond the lowest (system groups) or highest (non-system
++ * groups), we will prefer those and avoid potentially reclaiming a
++ * deleted group (which can be a security issue, since it may grant
++ * access to files belonging to that former group).
++ *
++ * If there are no GIDs available at the end of the search, we will
++ * have no choice but to iterate through the range looking for gaps.
++ *
++ */
++
++ /* Create an array to hold all of the discovered GIDs */
+ used_gids = malloc (sizeof (bool) * (gid_max +1));
+ if (NULL == used_gids) {
+ fprintf (stderr,
+- _("%s: failed to allocate memory: %s\n"),
+- Prog, strerror (errno));
++ _("%s: failed to allocate memory: %s\n"),
++ Prog, strerror (errno));
+ return -1;
+ }
+ memset (used_gids, false, sizeof (bool) * (gid_max + 1));
+
+- if ( (NULL != preferred_gid)
+- && (*preferred_gid >= gid_min)
+- && (*preferred_gid <= gid_max)
+- /* Check if the user exists according to NSS */
+- && (getgrgid (*preferred_gid) == NULL)
+- /* Check also the local database in case of uncommitted
+- * changes */
+- && (gr_locate_gid (*preferred_gid) == NULL)) {
+- *gid = *preferred_gid;
+- free (used_gids);
+- return 0;
+- }
+-
+- /* if we did not find free preffered system gid, we start to look for
+- * one in the range assigned to dynamic system IDs */
+- if (sys_group)
+- gid_min = (gid_t) getdef_ulong ("SYS_GID_MIN", 101UL);
++ /* First look for the lowest and highest value in the local database */
++ (void) gr_rewind ();
++ highest_found = gid_min;
++ lowest_found = gid_max;
++ while ((grp = gr_next ()) != NULL) {
++ /*
++ * Does this entry have a lower GID than the lowest we've found
++ * so far?
++ */
++ if ((grp->gr_gid <= lowest_found) && (grp->gr_gid >= gid_min)) {
++ lowest_found = grp->gr_gid - 1;
++ }
++
++ /*
++ * Does this entry have a higher GID than the highest we've found
++ * so far?
++ */
++ if ((grp->gr_gid >= highest_found) && (grp->gr_gid <= gid_max)) {
++ highest_found = grp->gr_gid + 1;
++ }
++
++ /* create index of used GIDs */
++ if (grp->gr_gid >= gid_min
++ && grp->gr_gid <= gid_max) {
++
++ used_gids[grp->gr_gid] = true;
++ }
++ }
+
+- /*
+- * Search the entire group file,
+- * looking for the largest unused value.
+- *
+- * We check the list of groups according to NSS (setgrent/getgrent),
+- * but we also check the local database (gr_rewind/gr_next) in case
+- * some groups were created but the changes were not committed yet.
+- */
+ if (sys_group) {
+- gid_t id;
+- /* setgrent / getgrent / endgrent can be very slow with
+- * LDAP configurations (and many accounts).
+- * Since there is a limited amount of IDs to be tested
+- * for system accounts, we just check the existence
+- * of IDs with getgrgid.
+- */
+- group_id = gid_max;
+- for (id = gid_max; id >= gid_min; id--) {
+- if (getgrgid (id) != NULL) {
+- group_id = id - 1;
+- used_gids[id] = true;
+- }
++ /*
++ * For system groups, we want to start from the
++ * top of the range and work downwards.
++ */
++
++ /*
++ * At the conclusion of the gr_next() search, we will either
++ * have a presumed-free GID or we will be at GID_MIN - 1.
++ */
++ if (lowest_found < gid_min) {
++ /*
++ * In this case, a GID is in use at GID_MIN.
++ *
++ * We will reset the search to GID_MAX and proceed down
++ * through all the GIDs (skipping those we detected with
++ * used_gids) for a free one. It is a known issue that
++ * this may result in reusing a previously-deleted GID,
++ * so administrators should be instructed to use this
++ * auto-detection with care (and prefer to assign GIDs
++ * explicitly).
++ */
++ lowest_found = gid_max;
+ }
+
+- (void) gr_rewind ();
+- while ((grp = gr_next ()) != NULL) {
+- if ((grp->gr_gid <= group_id) && (grp->gr_gid >= gid_min)) {
+- group_id = grp->gr_gid - 1;
+- }
+- /* create index of used GIDs */
+- if (grp->gr_gid <= gid_max) {
+- used_gids[grp->gr_gid] = true;
++ /* Search through all of the IDs in the range */
++ for (id = lowest_found; id >= gid_min; id--) {
++ result = check_gid(id, gid_min, gid_max, used_gids);
++ if (result == 0) {
++ /* This GID is available. Return it. */
++ *gid = id;
++ free(used_gids);
++ return 0;
++ } else if (result == EEXIST) {
++ /* This GID is in use, we'll continue to the next */
++ } else {
++ /*
++ * An unexpected error occurred.
++ *
++ * Only report it the first time to avoid spamming
++ * the logs
++ *
++ */
++ if (!nospam) {
++ fprintf(stderr,
++ _("%s: Can't get unique system GID (%s). "
++ "Suppressing additional messages.\n"),
++ Prog, strerror(result));
++ SYSLOG((LOG_ERR,
++ "Error checking available GIDs: %s",
++ strerror(result)));
++ nospam = 1;
++ }
++ /*
++ * We will continue anyway. Hopefully a later GID
++ * will work properly.
++ */
+ }
+ }
+- } else {
+- group_id = gid_min;
+- setgrent ();
+- while ((grp = getgrent ()) != NULL) {
+- if ((grp->gr_gid >= group_id) && (grp->gr_gid <= gid_max)) {
+- group_id = grp->gr_gid + 1;
+- }
+- /* create index of used GIDs */
+- if (grp->gr_gid <= gid_max) {
+- used_gids[grp->gr_gid] = true;
++
++ /*
++ * If we get all the way through the loop, try again from GID_MAX,
++ * unless that was where we previously started. (NOTE: the worst-case
++ * scenario here is that we will run through (GID_MAX - GID_MIN - 1)
++ * cycles *again* if we fall into this case with lowest_found as
++ * GID_MAX - 1, all groups in the range in use and maintained by
++ * network services such as LDAP.)
++ */
++ if (lowest_found != gid_max) {
++ for (id = gid_max; id >= gid_min; id--) {
++ result = check_gid(id, gid_min, gid_max, used_gids);
++ if (result == 0) {
++ /* This GID is available. Return it. */
++ *gid = id;
++ free(used_gids);
++ return 0;
++ } else if (result == EEXIST) {
++ /* This GID is in use, we'll continue to the next */
++ } else {
++ /*
++ * An unexpected error occurred.
++ *
++ * Only report it the first time to avoid spamming
++ * the logs
++ *
++ */
++ if (!nospam) {
++ fprintf(stderr,
++ _("%s: Can't get unique system GID (%s). "
++ "Suppressing additional messages.\n"),
++ Prog, strerror(result));
++ SYSLOG((LOG_ERR,
++ "Error checking available GIDs: %s",
++ strerror(result)));
++ nospam = 1;
++ }
++ /*
++ * We will continue anyway. Hopefully a later GID
++ * will work properly.
++ */
++ }
+ }
+ }
+- endgrent ();
++ } else { /* !sys_group */
++ /*
++ * For non-system groups, we want to start from the
++ * bottom of the range and work upwards.
++ */
+
+- (void) gr_rewind ();
+- while ((grp = gr_next ()) != NULL) {
+- if ((grp->gr_gid >= group_id) && (grp->gr_gid <= gid_max)) {
+- group_id = grp->gr_gid + 1;
+- }
+- /* create index of used GIDs */
+- if (grp->gr_gid <= gid_max) {
+- used_gids[grp->gr_gid] = true;
+- }
++ /*
++ * At the conclusion of the gr_next() search, we will either
++ * have a presumed-free GID or we will be at GID_MAX + 1.
++ */
++ if (highest_found > gid_max) {
++ /*
++ * In this case, a GID is in use at GID_MAX.
++ *
++ * We will reset the search to GID_MIN and proceed up
++ * through all the GIDs (skipping those we detected with
++ * used_gids) for a free one. It is a known issue that
++ * this may result in reusing a previously-deleted GID,
++ * so administrators should be instructed to use this
++ * auto-detection with care (and prefer to assign GIDs
++ * explicitly).
++ */
++ highest_found = gid_min;
+ }
+- }
+
+- /*
+- * If a group (resp. system group) with GID equal to GID_MAX (resp.
+- * GID_MIN) exists, the above algorithm will give us GID_MAX+1
+- * (resp. GID_MIN-1) even if not unique. Search for the first free
+- * GID starting with GID_MIN (resp. GID_MAX).
+- */
+- if (sys_group) {
+- if (group_id < gid_min) {
+- for (group_id = gid_max; group_id >= gid_min; group_id--) {
+- if (false == used_gids[group_id]) {
+- break;
++ /* Search through all of the IDs in the range */
++ for (id = highest_found; id <= gid_max; id++) {
++ result = check_gid(id, gid_min, gid_max, used_gids);
++ if (result == 0) {
++ /* This GID is available. Return it. */
++ *gid = id;
++ free(used_gids);
++ return 0;
++ } else if (result == EEXIST) {
++ /* This GID is in use, we'll continue to the next */
++ } else {
++ /*
++ * An unexpected error occurred.
++ *
++ * Only report it the first time to avoid spamming
++ * the logs
++ *
++ */
++ if (!nospam) {
++ fprintf(stderr,
++ _("%s: Can't get unique GID (%s). "
++ "Suppressing additional messages.\n"),
++ Prog, strerror(result));
++ SYSLOG((LOG_ERR,
++ "Error checking available GIDs: %s",
++ strerror(result)));
++ nospam = 1;
+ }
+- }
+- if (group_id < gid_min) {
+- fprintf (stderr,
+- _("%s: Can't get unique system GID (no more available GIDs)\n"),
+- Prog);
+- SYSLOG ((LOG_WARN,
+- "no more available GID on the system"));
+- free (used_gids);
+- return -1;
++ /*
++ * We will continue anyway. Hopefully a later GID
++ * will work properly.
++ */
+ }
+ }
+- } else {
+- if (group_id > gid_max) {
+- for (group_id = gid_min; group_id <= gid_max; group_id++) {
+- if (false == used_gids[group_id]) {
+- break;
++
++ /*
++ * If we get all the way through the loop, try again from GID_MIN,
++ * unless that was where we previously started. (NOTE: the worst-case
++ * scenario here is that we will run through (GID_MAX - GID_MIN - 1)
++ * cycles *again* if we fall into this case with highest_found as
++ * GID_MIN + 1, all groups in the range in use and maintained by
++ * network services such as LDAP.)
++ */
++ if (highest_found != gid_min) {
++ for (id = gid_min; id <= gid_max; id++) {
++ result = check_gid(id, gid_min, gid_max, used_gids);
++ if (result == 0) {
++ /* This GID is available. Return it. */
++ *gid = id;
++ free(used_gids);
++ return 0;
++ } else if (result == EEXIST) {
++ /* This GID is in use, we'll continue to the next */
++ } else {
++ /*
++ * An unexpected error occurred.
++ *
++ * Only report it the first time to avoid spamming
++ * the logs
++ *
++ */
++ if (!nospam) {
++ fprintf(stderr,
++ _("%s: Can't get unique GID (%s). "
++ "Suppressing additional messages.\n"),
++ Prog, strerror(result));
++ SYSLOG((LOG_ERR,
++ "Error checking available GIDs: %s",
++ strerror(result)));
++ nospam = 1;
++ }
++ /*
++ * We will continue anyway. Hopefully a later GID
++ * will work properly.
++ */
+ }
+ }
+- if (group_id > gid_max) {
+- fprintf (stderr,
+- _("%s: Can't get unique GID (no more available GIDs)\n"),
+- Prog);
+- SYSLOG ((LOG_WARN, "no more available GID on the system"));
+- free (used_gids);
+- return -1;
+- }
+ }
+ }
+
+- free (used_gids);
+- *gid = group_id;
+- return 0;
++ /* The code reached here and found no available IDs in the range */
++ fprintf(stderr,
++ _("%s: Can't get unique GID (no more available GIDs)\n"),
++ Prog);
++ SYSLOG((LOG_WARN, "no more available GIDs on the system"));
++ free(used_gids);
++ return -1;
+ }
+
+diff -up shadow-4.1.5.1/libmisc/find_new_uid.c.id-alloc shadow-4.1.5.1/libmisc/find_new_uid.c
+--- shadow-4.1.5.1/libmisc/find_new_uid.c.id-alloc 2011-07-29 17:39:16.000000000 +0200
++++ shadow-4.1.5.1/libmisc/find_new_uid.c 2014-10-17 16:52:30.481217270 +0200
+@@ -39,6 +39,118 @@
+ #include "getdef.h"
+
+ /*
++ * get_ranges - Get the minimum and maximum ID ranges for the search
++ *
++ * This function will return the minimum and maximum ranges for IDs
++ *
++ * 0: The function completed successfully
++ * EINVAL: The provided ranges are impossible (such as maximum < minimum)
++ *
++ * preferred_min: The special-case minimum value for a specifically-
++ * requested ID, which may be lower than the standard min_id
++ */
++static int get_ranges(bool sys_user, uid_t *min_id, uid_t *max_id,
++ uid_t *preferred_min)
++{
++ uid_t uid_def_max = 0;
++
++ if (sys_user) {
++ /* System users */
++
++ /* A requested ID is allowed to be below the autoselect range */
++ *preferred_min = (uid_t) 1;
++
++ /* Get the minimum ID range from login.defs or default to 101 */
++ *min_id = (uid_t) getdef_ulong("SYS_UID_MIN", 101UL);
++
++ /*
++ * If SYS_UID_MAX is unspecified, we should assume it to be one
++ * less than the UID_MIN (which is reserved for non-system accounts)
++ */
++ uid_def_max = (uid_t) getdef_ulong("UID_MIN", 1000UL) - 1;
++ *max_id = (uid_t) getdef_ulong("SYS_UID_MAX",
++ (unsigned long) uid_def_max);
++
++ /* Check that the ranges make sense */
++ if (*max_id < *min_id) {
++ (void) fprintf (stderr,
++ _("%s: Invalid configuration: SYS_UID_MIN (%lu), "
++ "UID_MIN (%lu), SYS_UID_MAX (%lu)\n"),
++ Prog, (unsigned long) *min_id,
++ getdef_ulong ("UID_MIN", 1000UL),
++ (unsigned long) *max_id);
++ return EINVAL;
++ }
++ } else {
++ /* Non-system users */
++
++ /* Get the values from login.defs or use reasonable defaults */
++ *min_id = (uid_t) getdef_ulong("UID_MIN", 1000UL);
++ *max_id = (uid_t) getdef_ulong("UID_MAX", 60000UL);
++
++ /*
++ * The preferred minimum should match the standard ID minimum
++ * for non-system users.
++ */
++ *preferred_min = *min_id;
++
++ /* Check that the ranges make sense */
++ if (*max_id < *min_id) {
++ (void) fprintf(stderr,
++ _("%s: Invalid configuration: UID_MIN (%lu), "
++ "UID_MAX (%lu)\n"),
++ Prog, (unsigned long) *min_id,
++ (unsigned long) *max_id);
++ return EINVAL;
++ }
++ }
++
++ return 0;
++}
++
++/*
++ * check_uid - See if the requested UID is available
++ *
++ * On success, return 0
++ * If the ID is in use, return EEXIST
++ * If the ID is outside the range, return ERANGE
++ * In other cases, return errno from getpwuid()
++ */
++static int check_uid(const uid_t uid,
++ const uid_t uid_min,
++ const uid_t uid_max,
++ bool *used_uids)
++{
++ /* First test that the preferred ID is in the range */
++ if (uid < uid_min || uid > uid_max) {
++ return ERANGE;
++ }
++
++ /*
++ * Check whether we already detected this UID
++ * using the pw_next() loop
++ */
++ if (used_uids != NULL && used_uids[uid]) {
++ return EEXIST;
++ }
++ /* Check if the UID exists according to NSS */
++ errno = 0;
++ if (getpwuid(uid) != NULL) {
++ return EEXIST;
++ } else {
++ /* getpwuid() was NULL, check whether this was
++ * due to an error, so we can report it.
++ */
++ /* ignore errors for now * if (errno != 0) {
++ return errno;
++ } */
++ }
++
++ /* If we've made it here, the UID must be available */
++ return 0;
++}
++
++/*
+ * find_new_uid - Find a new unused UID.
+ *
+ * If successful, find_new_uid provides an unused user ID in the
+@@ -48,162 +160,339 @@
+ *
+ * Return 0 on success, -1 if no unused UIDs are available.
+ */
+-int find_new_uid (bool sys_user,
+- uid_t *uid,
+- /*@null@*/uid_t const *preferred_uid)
++int find_new_uid(bool sys_user,
++ uid_t *uid,
++ /*@null@*/uid_t const *preferred_uid)
+ {
+- const struct passwd *pwd;
+- uid_t uid_min, uid_max, user_id;
+ bool *used_uids;
++ const struct passwd *pwd;
++ uid_t uid_min, uid_max, preferred_min;
++ uid_t user_id, id;
++ uid_t lowest_found, highest_found;
++ int result;
++ int nospam = 0;
+
+ assert (uid != NULL);
+
+- if (!sys_user) {
+- uid_min = (uid_t) getdef_ulong ("UID_MIN", 1000UL);
+- uid_max = (uid_t) getdef_ulong ("UID_MAX", 60000UL);
+- if (uid_max < uid_min) {
+- (void) fprintf (stderr,
+- _("%s: Invalid configuration: UID_MIN (%lu), UID_MAX (%lu)\n"),
+- Prog, (unsigned long) uid_min, (unsigned long) uid_max);
+- return -1;
+- }
+- } else {
+- uid_min = (uid_t) getdef_ulong ("SYS_UID_MIN", 101UL);
+- uid_max = (uid_t) getdef_ulong ("UID_MIN", 1000UL) - 1;
+- uid_max = (uid_t) getdef_ulong ("SYS_UID_MAX", (unsigned long) uid_max);
+- if (uid_max < uid_min) {
+- (void) fprintf (stderr,
+- _("%s: Invalid configuration: SYS_UID_MIN (%lu), UID_MIN (%lu), SYS_UID_MAX (%lu)\n"),
+- Prog, (unsigned long) uid_min, getdef_ulong ("UID_MIN", 1000UL), (unsigned long) uid_max);
++ /*
++ * First, figure out what ID range is appropriate for
++ * automatic assignment
++ */
++ result = get_ranges(sys_user, &uid_min, &uid_max, &preferred_min);
++ if (result == EINVAL) {
++ return -1;
++ }
++
++ /* Check if the preferred UID is available */
++ if (preferred_uid) {
++ result = check_uid(*preferred_uid, preferred_min, uid_max, NULL);
++ if (result == 0) {
++ /*
++ * Make sure the UID isn't queued for use already
++ */
++ if (pw_locate_uid (*preferred_uid) == NULL) {
++ *uid = *preferred_uid;
++ return 0;
++ }
++ /*
++ * pw_locate_uid() found the UID in an as-yet uncommitted
++ * entry. We'll proceed below and auto-set an UID.
++ */
++ } else if (result == EEXIST || result == ERANGE) {
++ /*
++ * Continue on below. At this time, we won't
++ * treat these two cases differently.
++ */
++ } else {
++ /*
++ * An unexpected error occurred. We should report
++ * this and fail the user creation.
++ * This differs from the automatic creation
++ * behavior below, since if a specific UID was
++ * requested and generated an error, the user is
++ * more likely to want to stop and address the
++ * issue.
++ */
++ fprintf(stderr,
++ _("%s: Encountered error attempting to use "
++ "preferred UID: %s\n"),
++ Prog, strerror(result));
+ return -1;
+ }
+ }
++
++ /*
++ * Search the entire passwd file,
++ * looking for the next unused value.
++ *
++ * We first check the local database with pw_rewind/pw_next to find
++ * all local values that are in use.
++ *
++ * We then compare the next free value to all databases (local and
++ * remote) and iterate until we find a free one. If there are free
++ * values beyond the lowest (system users) or highest (non-system
++ * users), we will prefer those and avoid potentially reclaiming a
++ * deleted user (which can be a security issue, since it may grant
++ * access to files belonging to that former user).
++ *
++ * If there are no UIDs available at the end of the search, we will
++ * have no choice but to iterate through the range looking for gaps.
++ *
++ */
++
++ /* Create an array to hold all of the discovered UIDs */
+ used_uids = malloc (sizeof (bool) * (uid_max +1));
+ if (NULL == used_uids) {
+ fprintf (stderr,
+- _("%s: failed to allocate memory: %s\n"),
+- Prog, strerror (errno));
++ _("%s: failed to allocate memory: %s\n"),
++ Prog, strerror (errno));
+ return -1;
+ }
+ memset (used_uids, false, sizeof (bool) * (uid_max + 1));
+
+- if ( (NULL != preferred_uid)
+- && (*preferred_uid >= uid_min)
+- && (*preferred_uid <= uid_max)
+- /* Check if the user exists according to NSS */
+- && (getpwuid (*preferred_uid) == NULL)
+- /* Check also the local database in case of uncommitted
+- * changes */
+- && (pw_locate_uid (*preferred_uid) == NULL)) {
+- *uid = *preferred_uid;
+- free (used_uids);
+- return 0;
+- }
++ /* First look for the lowest and highest value in the local database */
++ (void) pw_rewind ();
++ highest_found = uid_min;
++ lowest_found = uid_max;
++ while ((pwd = pw_next ()) != NULL) {
++ /*
++ * Does this entry have a lower UID than the lowest we've found
++ * so far?
++ */
++ if ((pwd->pw_uid <= lowest_found) && (pwd->pw_uid >= uid_min)) {
++ lowest_found = pwd->pw_uid - 1;
++ }
+
++ /*
++ * Does this entry have a higher UID than the highest we've found
++ * so far?
++ */
++ if ((pwd->pw_uid >= highest_found) && (pwd->pw_uid <= uid_max)) {
++ highest_found = pwd->pw_uid + 1;
++ }
++
++ /* create index of used UIDs */
++ if (pwd->pw_uid >= uid_min
++ && pwd->pw_uid <= uid_max) {
++
++ used_uids[pwd->pw_uid] = true;
++ }
++ }
+
+- /*
+- * Search the entire password file,
+- * looking for the largest unused value.
+- *
+- * We check the list of users according to NSS (setpwent/getpwent),
+- * but we also check the local database (pw_rewind/pw_next) in case
+- * some users were created but the changes were not committed yet.
+- */
+ if (sys_user) {
+- uid_t id;
+- /* setpwent / getpwent / endpwent can be very slow with
+- * LDAP configurations (and many accounts).
+- * Since there is a limited amount of IDs to be tested
+- * for system accounts, we just check the existence
+- * of IDs with getpwuid.
+- */
+- user_id = uid_max;
+- for (id = uid_max; id >= uid_min; id--) {
+- if (getpwuid (id) != NULL) {
+- user_id = id - 1;
+- used_uids[id] = true;
+- }
++ /*
++ * For system users, we want to start from the
++ * top of the range and work downwards.
++ */
++
++ /*
++ * At the conclusion of the pw_next() search, we will either
++ * have a presumed-free UID or we will be at UID_MIN - 1.
++ */
++ if (lowest_found < uid_min) {
++ /*
++ * In this case, an UID is in use at UID_MIN.
++ *
++ * We will reset the search to UID_MAX and proceed down
++ * through all the UIDs (skipping those we detected with
++ * used_uids) for a free one. It is a known issue that
++ * this may result in reusing a previously-deleted UID,
++ * so administrators should be instructed to use this
++ * auto-detection with care (and prefer to assign UIDs
++ * explicitly).
++ */
++ lowest_found = uid_max;
+ }
+
+- (void) pw_rewind ();
+- while ((pwd = pw_next ()) != NULL) {
+- if ((pwd->pw_uid <= user_id) && (pwd->pw_uid >= uid_min)) {
+- user_id = pwd->pw_uid - 1;
+- }
+- /* create index of used UIDs */
+- if (pwd->pw_uid <= uid_max) {
+- used_uids[pwd->pw_uid] = true;
++ /* Search through all of the IDs in the range */
++ for (id = lowest_found; id >= uid_min; id--) {
++ result = check_uid(id, uid_min, uid_max, used_uids);
++ if (result == 0) {
++ /* This UID is available. Return it. */
++ *uid = id;
++ free(used_uids);
++ return 0;
++ } else if (result == EEXIST) {
++ /* This UID is in use, we'll continue to the next */
++ } else {
++ /*
++ * An unexpected error occurred.
++ *
++ * Only report it the first time to avoid spamming
++ * the logs
++ *
++ */
++ if (!nospam) {
++ fprintf(stderr,
++ _("%s: Can't get unique system UID (%s). "
++ "Suppressing additional messages.\n"),
++ Prog, strerror(result));
++ SYSLOG((LOG_ERR,
++ "Error checking available UIDs: %s",
++ strerror(result)));
++ nospam = 1;
++ }
++ /*
++ * We will continue anyway. Hopefully a later UID
++ * will work properly.
++ */
+ }
+ }
+- } else {
+- user_id = uid_min;
+- setpwent ();
+- while ((pwd = getpwent ()) != NULL) {
+- if ((pwd->pw_uid >= user_id) && (pwd->pw_uid <= uid_max)) {
+- user_id = pwd->pw_uid + 1;
+- }
+- /* create index of used UIDs */
+- if (pwd->pw_uid <= uid_max) {
+- used_uids[pwd->pw_uid] = true;
++
++ /*
++ * If we get all the way through the loop, try again from UID_MAX,
++ * unless that was where we previously started. (NOTE: the worst-case
++ * scenario here is that we will run through (UID_MAX - UID_MIN - 1)
++ * cycles *again* if we fall into this case with lowest_found as
++ * UID_MAX - 1, all users in the range in use and maintained by
++ * network services such as LDAP.)
++ */
++ if (lowest_found != uid_max) {
++ for (id = uid_max; id >= uid_min; id--) {
++ result = check_uid(id, uid_min, uid_max, used_uids);
++ if (result == 0) {
++ /* This UID is available. Return it. */
++ *uid = id;
++ free(used_uids);
++ return 0;
++ } else if (result == EEXIST) {
++ /* This UID is in use, we'll continue to the next */
++ } else {
++ /*
++ * An unexpected error occurred.
++ *
++ * Only report it the first time to avoid spamming
++ * the logs
++ *
++ */
++ if (!nospam) {
++ fprintf(stderr,
++ _("%s: Can't get unique system UID (%s). "
++ "Suppressing additional messages.\n"),
++ Prog, strerror(result));
++ SYSLOG((LOG_ERR,
++ "Error checking available UIDs: %s",
++ strerror(result)));
++ nospam = 1;
++ }
++ /*
++ * We will continue anyway. Hopefully a later UID
++ * will work properly.
++ */
++ }
+ }
+ }
+- endpwent ();
++ } else { /* !sys_user */
++ /*
++ * For non-system users, we want to start from the
++ * bottom of the range and work upwards.
++ */
+
+- (void) pw_rewind ();
+- while ((pwd = pw_next ()) != NULL) {
+- if ((pwd->pw_uid >= user_id) && (pwd->pw_uid <= uid_max)) {
+- user_id = pwd->pw_uid + 1;
+- }
+- /* create index of used UIDs */
+- if (pwd->pw_uid <= uid_max) {
+- used_uids[pwd->pw_uid] = true;
+- }
++ /*
++ * At the conclusion of the pw_next() search, we will either
++ * have a presumed-free UID or we will be at UID_MAX + 1.
++ */
++ if (highest_found > uid_max) {
++ /*
++ * In this case, a UID is in use at UID_MAX.
++ *
++ * We will reset the search to UID_MIN and proceed up
++ * through all the UIDs (skipping those we detected with
++ * used_uids) for a free one. It is a known issue that
++ * this may result in reusing a previously-deleted UID,
++ * so administrators should be instructed to use this
++ * auto-detection with care (and prefer to assign UIDs
++ * explicitly).
++ */
++ highest_found = uid_min;
+ }
+- }
+
+- /*
+- * If a user (resp. system user) with UID equal to UID_MAX (resp.
+- * UID_MIN) exists, the above algorithm will give us UID_MAX+1
+- * (resp. UID_MIN-1) even if not unique. Search for the first free
+- * UID starting with UID_MIN (resp. UID_MAX).
+- */
+- if (sys_user) {
+- if (user_id < uid_min) {
+- for (user_id = uid_max; user_id >= uid_min; user_id--) {
+- if (false == used_uids[user_id]) {
+- break;
++ /* Search through all of the IDs in the range */
++ for (id = highest_found; id <= uid_max; id++) {
++ result = check_uid(id, uid_min, uid_max, used_uids);
++ if (result == 0) {
++ /* This UID is available. Return it. */
++ *uid = id;
++ free(used_uids);
++ return 0;
++ } else if (result == EEXIST) {
++ /* This UID is in use, we'll continue to the next */
++ } else {
++ /*
++ * An unexpected error occurred.
++ *
++ * Only report it the first time to avoid spamming
++ * the logs
++ *
++ */
++ if (!nospam) {
++ fprintf(stderr,
++ _("%s: Can't get unique UID (%s). "
++ "Suppressing additional messages.\n"),
++ Prog, strerror(result));
++ SYSLOG((LOG_ERR,
++ "Error checking available UIDs: %s",
++ strerror(result)));
++ nospam = 1;
+ }
+- }
+- if (user_id < uid_min ) {
+- fprintf (stderr,
+- _("%s: Can't get unique system UID (no more available UIDs)\n"),
+- Prog);
+- SYSLOG ((LOG_WARN,
+- "no more available UID on the system"));
+- free (used_uids);
+- return -1;
++ /*
++ * We will continue anyway. Hopefully a later UID
++ * will work properly.
++ */
+ }
+ }
+- } else {
+- if (user_id > uid_max) {
+- for (user_id = uid_min; user_id <= uid_max; user_id++) {
+- if (false == used_uids[user_id]) {
+- break;
++
++ /*
++ * If we get all the way through the loop, try again from UID_MIN,
++ * unless that was where we previously started. (NOTE: the worst-case
++ * scenario here is that we will run through (UID_MAX - UID_MIN - 1)
++ * cycles *again* if we fall into this case with highest_found as
++ * UID_MIN + 1, all users in the range in use and maintained by
++ * network services such as LDAP.)
++ */
++ if (highest_found != uid_min) {
++ for (id = uid_min; id <= uid_max; id++) {
++ result = check_uid(id, uid_min, uid_max, used_uids);
++ if (result == 0) {
++ /* This UID is available. Return it. */
++ *uid = id;
++ free(used_uids);
++ return 0;
++ } else if (result == EEXIST) {
++ /* This UID is in use, we'll continue to the next */
++ } else {
++ /*
++ * An unexpected error occurred.
++ *
++ * Only report it the first time to avoid spamming
++ * the logs
++ *
++ */
++ if (!nospam) {
++ fprintf(stderr,
++ _("%s: Can't get unique UID (%s). "
++ "Suppressing additional messages.\n"),
++ Prog, strerror(result));
++ SYSLOG((LOG_ERR,
++ "Error checking available UIDs: %s",
++ strerror(result)));
++ nospam = 1;
++ }
++ /*
++ * We will continue anyway. Hopefully a later UID
++ * will work properly.
++ */
+ }
+ }
+- if (user_id > uid_max) {
+- fprintf (stderr,
+- _("%s: Can't get unique UID (no more available UIDs)\n"),
+- Prog);
+- SYSLOG ((LOG_WARN, "no more available UID on the system"));
+- free (used_uids);
+- return -1;
+- }
+ }
+ }
+
+- free (used_uids);
+- *uid = user_id;
+- return 0;
++ /* The code reached here and found no available IDs in the range */
++ fprintf(stderr,
++ _("%s: Can't get unique UID (no more available UIDs)\n"),
++ Prog);
++ SYSLOG((LOG_WARN, "no more available UIDs on the system"));
++ free(used_uids);
++ return -1;
+ }
+
diff --git a/SOURCES/shadow-4.1.5.1-info-parent-dir.patch b/SOURCES/shadow-4.1.5.1-info-parent-dir.patch
index d2bc009..b05e5bb 100644
--- a/SOURCES/shadow-4.1.5.1-info-parent-dir.patch
+++ b/SOURCES/shadow-4.1.5.1-info-parent-dir.patch
@@ -1,15 +1,3 @@
-diff -up shadow-4.1.5.1/man/man8/newusers.8.info-parent-dir shadow-4.1.5.1/man/man8/newusers.8
---- shadow-4.1.5.1/man/man8/newusers.8.info-parent-dir 2012-05-25 13:59:09.000000000 +0200
-+++ shadow-4.1.5.1/man/man8/newusers.8 2012-09-19 18:47:17.203525237 +0200
-@@ -99,7 +99,7 @@ This field is copied in the GECOS field
- .RS 4
- This field is used to define the home directory of the user\&.
- .sp
--If this field does not specify an existing directory, the specified directory is created, with ownership set to the user being created or updated and its primary group\&.
-+If this field does not specify an existing directory, the specified directory is created, with ownership set to the user being created or updated and its primary group\&. Note that newusers does not create parent directories of the new user's home directory. The newusers command will fail to create the home directory if the parent directories do not exist, and will send a message to stderr informing the user of the failure. The newusers command will not halt or return a failure to the calling shell if it fails to create the home directory, it will continue to process the batch of new users specified\&.
- .sp
- If the home directory of an existing user is changed,
- \fBnewusers\fR
diff -up shadow-4.1.5.1/man/newusers.8.xml.info-parent-dir shadow-4.1.5.1/man/newusers.8.xml
--- shadow-4.1.5.1/man/newusers.8.xml.info-parent-dir 2012-05-25 13:45:28.000000000 +0200
+++ shadow-4.1.5.1/man/newusers.8.xml 2012-09-19 18:46:35.651613365 +0200
diff --git a/SOURCES/shadow-4.1.5.1-ja-translation.patch b/SOURCES/shadow-4.1.5.1-ja-translation.patch
new file mode 100644
index 0000000..407cc3f
--- /dev/null
+++ b/SOURCES/shadow-4.1.5.1-ja-translation.patch
@@ -0,0 +1,25 @@
+From f2ce4cc54edc7dfeb6b12f3e8fff98255a9f477d Mon Sep 17 00:00:00 2001
+From: Taizo Ito
+Date: Tue, 17 Mar 2015 13:51:27 +0900
+Subject: [PATCH 1/1] typo in japanese man page of useradd
+
+---
+ po/ja.po | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/po/ja.po b/po/ja.po
+index a68a698..0c21c29 100644
+--- a/po/ja.po
++++ b/po/ja.po
+@@ -2047,7 +2047,7 @@ msgid " -s, --shell SHELL login shell of the new account\n"
+ msgstr " -s, --shell SHELL 新アカウントのログインシェル\n"
+
+ msgid " -u, --uid UID user ID of the new account\n"
+-msgstr " -u, --iud UID 新アカウントのユーザ ID\n"
++msgstr " -u, --uid UID 新アカウントのユーザ ID\n"
+
+ msgid ""
+ " -U, --user-group create a group with the same name as the "
+--
+1.8.3.1
+
diff --git a/SOURCES/shadow-4.1.5.1-lastlog-unexpire.patch b/SOURCES/shadow-4.1.5.1-lastlog-unexpire.patch
new file mode 100644
index 0000000..6a7ae2a
--- /dev/null
+++ b/SOURCES/shadow-4.1.5.1-lastlog-unexpire.patch
@@ -0,0 +1,263 @@
+diff -up shadow-4.1.5.1/man/lastlog.8.xml.unexpire shadow-4.1.5.1/man/lastlog.8.xml
+--- shadow-4.1.5.1/man/lastlog.8.xml.unexpire 2012-05-25 13:45:28.000000000 +0200
++++ shadow-4.1.5.1/man/lastlog.8.xml 2016-04-28 15:09:11.026084219 +0200
+@@ -105,6 +105,17 @@
+
+
+
++ ,
++
++
++
++ Clear lastlog record of an user. This option can be used only together
++ with ()).
++
++
++
++
++
+ ,
+
+
+@@ -124,6 +135,17 @@
+
+
+
++
++
++ ,
++
++
++
++ Set lastlog record of an user to the current time. This option can be
++ used only together with ()).
++
++
++
+
+
+ ,
+diff -up shadow-4.1.5.1/src/lastlog.c.unexpire shadow-4.1.5.1/src/lastlog.c
+--- shadow-4.1.5.1/src/lastlog.c.unexpire 2011-11-06 21:54:18.000000000 +0100
++++ shadow-4.1.5.1/src/lastlog.c 2016-04-28 15:49:30.253371990 +0200
+@@ -55,6 +55,13 @@
+ #endif
+
+ /*
++ * Needed for systems with older audit library.
++ */
++#ifndef AUDIT_ACCT_UNLOCK
++#define AUDIT_ACCT_UNLOCK 1136
++#endif
++
++/*
+ * Global variables
+ */
+ const char *Prog; /* Program name */
+@@ -71,6 +78,8 @@ static struct stat statbuf; /* fstat buf
+ static bool uflg = false; /* print only an user of range of users */
+ static bool tflg = false; /* print is restricted to most recent days */
+ static bool bflg = false; /* print excludes most recent days */
++static bool Cflg = false; /* clear record for user */
++static bool Sflg = false; /* set record for user */
+
+ #define NOW (time ((time_t *) 0))
+
+@@ -83,8 +92,10 @@ static /*@noreturn@*/void usage (int sta
+ "Options:\n"),
+ Prog);
+ (void) fputs (_(" -b, --before DAYS print only lastlog records older than DAYS\n"), usageout);
++ (void) fputs (_(" -C, --clear clear lastlog record of an user (usable only with -u)\n"), usageout);
+ (void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
+ (void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
++ (void) fputs (_(" -S, --set set lastlog record to current time (usable only with -u)\n"), usageout);
+ (void) fputs (_(" -t, --time DAYS print only lastlog records more recent than DAYS\n"), usageout);
+ (void) fputs (_(" -u, --user LOGIN print lastlog record of the specified LOGIN\n"), usageout);
+ (void) fputs ("\n", usageout);
+@@ -194,6 +205,80 @@ static void print (void)
+ }
+ }
+
++static void update_one (/*@null@*/const struct passwd *pw)
++{
++ off_t offset;
++ struct lastlog ll;
++ int err;
++
++ if (NULL == pw) {
++ return;
++ }
++
++ offset = (off_t) pw->pw_uid * sizeof (ll);
++ /* fseeko errors are not really relevant for us. */
++ err = fseeko (lastlogfile, offset, SEEK_SET);
++ assert (0 == err);
++
++ memzero (&ll, sizeof (ll));
++
++ if (Sflg) {
++ ll.ll_time = NOW;
++#ifdef HAVE_LL_HOST
++ strcpy (ll.ll_host, "localhost");
++#endif
++ strcpy (ll.ll_line, "lastlog");
++#ifdef WITH_AUDIT
++ audit_logger (AUDIT_ACCT_UNLOCK, Prog,
++ "clearing-lastlog",
++ pw->pw_name, (unsigned int) pw->pw_uid, SHADOW_AUDIT_SUCCESS);
++#endif
++ }
++#ifdef WITH_AUDIT
++ else {
++ audit_logger (AUDIT_ACCT_UNLOCK, Prog,
++ "refreshing-lastlog",
++ pw->pw_name, (unsigned int) pw->pw_uid, SHADOW_AUDIT_SUCCESS);
++ }
++#endif
++
++ if (fwrite (&ll, sizeof(ll), 1, lastlogfile) != 1) {
++ fprintf (stderr,
++ _("%s: Failed to update the entry for UID %lu\n"),
++ Prog, (unsigned long int)pw->pw_uid);
++ exit (EXIT_FAILURE);
++ }
++}
++
++static void update (void)
++{
++ const struct passwd *pwent;
++
++ if (!uflg) /* safety measure */
++ return;
++
++ if (has_umin && has_umax && (umin == umax)) {
++ update_one (getpwuid ((uid_t)umin));
++ } else {
++ setpwent ();
++ while ( (pwent = getpwent ()) != NULL ) {
++ if ((has_umin && (pwent->pw_uid < (uid_t)umin))
++ || (has_umax && (pwent->pw_uid > (uid_t)umax))) {
++ continue;
++ }
++ update_one (pwent);
++ }
++ endpwent ();
++ }
++
++ if (fflush (lastlogfile) != 0 || fsync (fileno (lastlogfile)) != 0) {
++ fprintf (stderr,
++ _("%s: Failed to update the lastlog file\n"),
++ Prog);
++ exit (EXIT_FAILURE);
++ }
++}
++
+ int main (int argc, char **argv)
+ {
+ /*
+@@ -208,18 +293,24 @@ int main (int argc, char **argv)
+
+ process_root_flag ("-R", argc, argv);
+
++#ifdef WITH_AUDIT
++ audit_help_open ();
++#endif
++
+ {
+ int c;
+ static struct option const longopts[] = {
+ {"before", required_argument, NULL, 'b'},
++ {"clear", no_argument, NULL, 'C'},
+ {"help", no_argument, NULL, 'h'},
+ {"root", required_argument, NULL, 'R'},
++ {"set", no_argument, NULL, 'S'},
+ {"time", required_argument, NULL, 't'},
+ {"user", required_argument, NULL, 'u'},
+ {NULL, 0, NULL, '\0'}
+ };
+
+- while ((c = getopt_long (argc, argv, "b:hR:t:u:", longopts,
++ while ((c = getopt_long (argc, argv, "b:ChR:St:u:", longopts,
+ NULL)) != -1) {
+ switch (c) {
+ case 'b':
+@@ -235,11 +326,21 @@ int main (int argc, char **argv)
+ bflg = true;
+ break;
+ }
++ case 'C':
++ {
++ Cflg = true;
++ break;
++ }
+ case 'h':
+ usage (EXIT_SUCCESS);
+ /*@notreached@*/break;
+ case 'R': /* no-op, handled in process_root_flag () */
+ break;
++ case 'S':
++ {
++ Sflg = true;
++ break;
++ }
+ case 't':
+ {
+ unsigned long days;
+@@ -294,9 +395,21 @@ int main (int argc, char **argv)
+ Prog, argv[optind]);
+ usage (EXIT_FAILURE);
+ }
++ if (Cflg && Sflg) {
++ fprintf (stderr,
++ _("%s: Option -C cannot be used together with option -S\n"),
++ Prog);
++ usage (EXIT_FAILURE);
++ }
++ if ((Cflg || Sflg) && !uflg) {
++ fprintf (stderr,
++ _("%s: Options -C and -S require option -u to specify the user\n"),
++ Prog);
++ usage (EXIT_FAILURE);
++ }
+ }
+
+- lastlogfile = fopen (LASTLOG_FILE, "r");
++ lastlogfile = fopen (LASTLOG_FILE, (Cflg || Sflg)?"r+":"r");
+ if (NULL == lastlogfile) {
+ perror (LASTLOG_FILE);
+ exit (EXIT_FAILURE);
+@@ -310,7 +423,10 @@ int main (int argc, char **argv)
+ exit (EXIT_FAILURE);
+ }
+
+- print ();
++ if (Cflg || Sflg)
++ update ();
++ else
++ print ();
+
+ (void) fclose (lastlogfile);
+
+diff -up shadow-4.1.5.1/src/Makefile.am.unexpire shadow-4.1.5.1/src/Makefile.am
+--- shadow-4.1.5.1/src/Makefile.am.unexpire 2011-11-18 22:23:30.000000000 +0100
++++ shadow-4.1.5.1/src/Makefile.am 2016-04-28 15:09:11.027084241 +0200
+@@ -90,6 +90,7 @@ groupmod_LDADD = $(LDADD) $(LIBPAM_SUID)
+ grpck_LDADD = $(LDADD) $(LIBSELINUX)
+ grpconv_LDADD = $(LDADD) $(LIBSELINUX)
+ grpunconv_LDADD = $(LDADD) $(LIBSELINUX)
++lastlog_LDADD = $(LDADD) $(LIBAUDIT)
+ login_SOURCES = \
+ login.c \
+ login_nopam.c
+diff -up shadow-4.1.5.1/src/Makefile.in.unexpire shadow-4.1.5.1/src/Makefile.in
+--- shadow-4.1.5.1/src/Makefile.in.unexpire 2012-05-25 13:56:51.000000000 +0200
++++ shadow-4.1.5.1/src/Makefile.in 2016-04-28 15:09:11.027084241 +0200
+@@ -162,7 +162,7 @@ id_DEPENDENCIES = $(am__DEPENDENCIES_1)
+ $(top_builddir)/lib/libshadow.la
+ lastlog_SOURCES = lastlog.c
+ lastlog_OBJECTS = lastlog.$(OBJEXT)
+-lastlog_LDADD = $(LDADD)
++lastlog_LDADD = $(LDADD) $(LIBAUDIT)
+ lastlog_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(top_builddir)/libmisc/libmisc.a \
+ $(top_builddir)/lib/libshadow.la
diff --git a/SOURCES/shadow-4.1.5.1-manfix.patch b/SOURCES/shadow-4.1.5.1-manfix.patch
index 2963c98..29623ab 100644
--- a/SOURCES/shadow-4.1.5.1-manfix.patch
+++ b/SOURCES/shadow-4.1.5.1-manfix.patch
@@ -1,3 +1,20 @@
+diff -up shadow-4.1.5.1/man/groupmems.8.xml.manfix shadow-4.1.5.1/man/groupmems.8.xml
+--- shadow-4.1.5.1/man/groupmems.8.xml.manfix 2012-05-25 13:45:28.000000000 +0200
++++ shadow-4.1.5.1/man/groupmems.8.xml 2015-12-18 12:27:08.466909647 +0100
+@@ -194,6 +194,13 @@
+ $ chown root.groups groupmems
+ $ groupmems -g groups -a gk4
+
++
++
++ In the Red Hat Enterprise Linux 7 the groupmems
++ command is not setuid and regular users cannot use it to manipulate
++ the membership of their own group. This might change in future
++ major releases of the Red Hat Enterprise Linux.
++
+
+
+
diff -up shadow-4.1.5.1/man/chage.1.xml.manfix shadow-4.1.5.1/man/chage.1.xml
--- shadow-4.1.5.1/man/chage.1.xml.manfix 2012-05-25 13:45:27.000000000 +0200
+++ shadow-4.1.5.1/man/chage.1.xml 2014-08-29 13:36:57.713167654 +0200
@@ -11,6 +28,20 @@ diff -up shadow-4.1.5.1/man/chage.1.xml.manfix shadow-4.1.5.1/man/chage.1.xml
+diff -up shadow-4.1.5.1/man/ja/man5/login.defs.5.manfix shadow-4.1.5.1/man/ja/man5/login.defs.5
+--- shadow-4.1.5.1/man/ja/man5/login.defs.5.manfix 2012-05-25 13:45:27.000000000 +0200
++++ shadow-4.1.5.1/man/ja/man5/login.defs.5 2015-12-18 12:34:08.080715842 +0100
+@@ -147,10 +147,6 @@ 以下の参照表は、
+ shadow パスワード機能のどのプログラムが
+ どのパラメータを使用するかを示したものである。
+ .na
+-.IP chfn 12
+-CHFN_AUTH CHFN_RESTRICT
+-.IP chsh 12
+-CHFN_AUTH
+ .IP groupadd 12
+ GID_MAX GID_MIN
+ .IP newusers 12
diff -up shadow-4.1.5.1/man/login.defs.5.xml.manfix shadow-4.1.5.1/man/login.defs.5.xml
--- shadow-4.1.5.1/man/login.defs.5.xml.manfix 2012-05-25 13:45:28.000000000 +0200
+++ shadow-4.1.5.1/man/login.defs.5.xml 2014-08-29 13:31:38.364812323 +0200
@@ -32,194 +63,148 @@ diff -up shadow-4.1.5.1/man/login.defs.5.xml.manfix shadow-4.1.5.1/man/login.def
The following configuration items are provided:
-diff -up shadow-4.1.5.1/man/man1/chage.1.manfix shadow-4.1.5.1/man/man1/chage.1
---- shadow-4.1.5.1/man/man1/chage.1.manfix 2012-05-25 13:58:18.000000000 +0200
-+++ shadow-4.1.5.1/man/man1/chage.1 2014-08-29 13:36:31.303559366 +0200
-@@ -45,7 +45,11 @@ command are:
- .PP
- \fB\-d\fR, \fB\-\-lastday\fR \fILAST_DAY\fR
- .RS 4
--Set the number of days since January 1st, 1970 when the password was last changed\&. The date may also be expressed in the format YYYY\-MM\-DD (or the format more commonly used in your area)\&.
-+Set the number of days since January 1st, 1970 when the password was last changed\&. The date may also be expressed in the format YYYY\-MM\-DD (or the format more commonly used in your area)\&. If the
-+\fILAST_DAY\fR
-+is set to
-+\fB0\fR
-+the user is forced to change his password on the next log on\&.
- .RE
- .PP
- \fB\-E\fR, \fB\-\-expiredate\fR \fIEXPIRE_DATE\fR
-diff -up shadow-4.1.5.1/man/man5/login.defs.5.manfix shadow-4.1.5.1/man/man5/login.defs.5
---- shadow-4.1.5.1/man/man5/login.defs.5.manfix 2012-05-25 13:59:03.000000000 +0200
-+++ shadow-4.1.5.1/man/man5/login.defs.5 2014-08-29 13:31:38.364812323 +0200
-@@ -46,6 +46,14 @@ value\&. Numbers (both regular and long)
- \fI0\fR) or hexadecimal values (precede the value with
- \fI0x\fR)\&. The maximum value of the regular and long numeric parameters is machine\-dependent\&.
- .PP
-+Please note that the parameters in this configuration file control the
-+behavior of the tools from the shadow-utils component\&. None of these
-+tools uses the PAM mechanism, and the utilities that use PAM (such as the
-+passwd command) should be configured elsewhere\&. The only values that
-+affect PAM modules are \fBENCRYPT_METHOD\fR and \fBSHA_CRYPT_MAX_ROUNDS\fR for pam_unix module,
-+\fBFAIL_DELAY\fR for pam_faildelay module, and \fBUMASK\fR for pam_umask module\&. Refer to
-+pam(8) for more information\&.
-+.PP
- The following configuration items are provided:
- .PP
- \fBCHFN_AUTH\fR (boolean)
-@@ -625,20 +633,6 @@ will create by default a group with the
- .PP
- The following cross references show which programs in the shadow password suite use which parameters\&.
- .PP
--chfn
--.RS 4
--
--CHFN_AUTH
--CHFN_RESTRICT
--LOGIN_STRING
--.RE
--.PP
--chgpasswd
--.RS 4
--ENCRYPT_METHOD MAX_MEMBERS_PER_GROUP MD5_CRYPT_ENAB
--SHA_CRYPT_MAX_ROUNDS SHA_CRYPT_MIN_ROUNDS
--.RE
--.PP
- chpasswd
- .RS 4
-
-@@ -646,11 +640,6 @@ ENCRYPT_METHOD MD5_CRYPT_ENAB
- SHA_CRYPT_MAX_ROUNDS SHA_CRYPT_MIN_ROUNDS
- .RE
- .PP
--chsh
--.RS 4
--CHSH_AUTH LOGIN_STRING
--.RE
--.PP
- gpasswd
- .RS 4
- ENCRYPT_METHOD MAX_MEMBERS_PER_GROUP MD5_CRYPT_ENAB
-@@ -692,29 +681,6 @@ grpunconv
- MAX_MEMBERS_PER_GROUP
- .RE
- .PP
--login
--.RS 4
--
--CONSOLE
--CONSOLE_GROUPS DEFAULT_HOME
--ENV_HZ ENV_PATH ENV_SUPATH ENV_TZ ENVIRON_FILE
--ERASECHAR FAIL_DELAY
--FAILLOG_ENAB
--FAKE_SHELL
--FTMP_FILE
--HUSHLOGIN_FILE
--ISSUE_FILE
--KILLCHAR
--LASTLOG_ENAB
--LOGIN_RETRIES
--LOGIN_STRING
--LOGIN_TIMEOUT LOG_OK_LOGINS LOG_UNKFAIL_ENAB
--MAIL_CHECK_ENAB MAIL_DIR MAIL_FILE MOTD_FILE NOLOGINS_FILE PORTTIME_CHECKS_ENAB QUOTAS_ENAB
--TTYGROUP TTYPERM TTYTYPE_FILE
--ULIMIT UMASK
--USERGROUPS_ENAB
--.RE
--.PP
- newgrp / sg
- .RS 4
- SYSLOG_SG_ENAB
-@@ -727,12 +693,6 @@ SHA_CRYPT_MAX_ROUNDS SHA_CRYPT_MIN_ROUND
- SYS_GID_MAX SYS_GID_MIN SYS_UID_MAX SYS_UID_MIN UID_MAX UID_MIN UMASK
- .RE
- .PP
--passwd
--.RS 4
--ENCRYPT_METHOD MD5_CRYPT_ENAB OBSCURE_CHECKS_ENAB PASS_ALWAYS_WARN PASS_CHANGE_TRIES PASS_MAX_LEN PASS_MIN_LEN
--SHA_CRYPT_MAX_ROUNDS SHA_CRYPT_MIN_ROUNDS
--.RE
--.PP
- pwck
- .RS 4
- PASS_MAX_DAYS PASS_MIN_DAYS PASS_WARN_AGE
-@@ -743,26 +703,6 @@ pwconv
- PASS_MAX_DAYS PASS_MIN_DAYS PASS_WARN_AGE
- .RE
- .PP
--su
--.RS 4
--
--CONSOLE
--CONSOLE_GROUPS DEFAULT_HOME
--ENV_HZ ENVIRON_FILE
--ENV_PATH ENV_SUPATH
--ENV_TZ LOGIN_STRING MAIL_CHECK_ENAB MAIL_DIR MAIL_FILE QUOTAS_ENAB
--SULOG_FILE SU_NAME
--SU_WHEEL_ONLY
--SYSLOG_SU_ENAB
--USERGROUPS_ENAB
--.RE
--.PP
--sulogin
--.RS 4
--ENV_HZ
--ENV_TZ
--.RE
--.PP
- useradd
- .RS 4
- CREATE_HOME GID_MAX GID_MIN MAIL_DIR MAX_MEMBERS_PER_GROUP 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
-diff -up shadow-4.1.5.1/man/man8/useradd.8.manfix shadow-4.1.5.1/man/man8/useradd.8
---- shadow-4.1.5.1/man/man8/useradd.8.manfix 2014-08-29 13:31:38.347811932 +0200
-+++ shadow-4.1.5.1/man/man8/useradd.8 2014-08-29 13:31:38.364812323 +0200
-@@ -85,7 +85,7 @@ by default\&.
- Any text string\&. It is generally a short description of the login, and is currently used as the field for the user\*(Aqs full name\&.
- .RE
- .PP
--\fB\-d\fR, \fB\-\-home\fR \fIHOME_DIR\fR
-+\fB\-d\fR, \fB\-\-home\-dir\fR \fIHOME_DIR\fR
- .RS 4
- The new user will be created using
- \fIHOME_DIR\fR
-@@ -220,9 +220,13 @@ option) will be copied to the home direc
- By default, if this option is not specified and
- \fBCREATE_HOME\fR
- is not enabled, no home directories are created\&.
-+.sp
-+The directory where the user\*(Aqs home directory is created must
-+exist and have proper SELinux context and permissions\&. Otherwise
-+the user\*(Aqs home directory cannot be created or accessed\&.
- .RE
- .PP
--\fB\-M\fR
-+\fB\-M\fR, \fB\-\-no\-create\-home\fR
- .RS 4
- Do not create the user\*(Aqs home directory, even if the system wide setting from
- /etc/login\&.defs
-diff -up shadow-4.1.5.1/man/man8/usermod.8.manfix shadow-4.1.5.1/man/man8/usermod.8
---- shadow-4.1.5.1/man/man8/usermod.8.manfix 2012-05-25 13:59:33.000000000 +0200
-+++ shadow-4.1.5.1/man/man8/usermod.8 2014-08-29 13:35:27.343086211 +0200
-@@ -63,7 +63,7 @@ The user\*(Aqs new login directory\&.
- .sp
- If the
- \fB\-m\fR
--option is given, the contents of the current home directory will be moved to the new home directory, which is created if it does not already exist\&.
-+option is given, the contents of the current home directory will be moved to the new home directory, which is created if it does not already exist\&. If the current home directory does not exist the new home directory will not be created\&.
- .RE
- .PP
- \fB\-e\fR, \fB\-\-expiredate\fR \fIEXPIRE_DATE\fR
-@@ -143,7 +143,7 @@ Move the content of the user\*(Aqs home
- This option is only valid in combination with the
- \fB\-d\fR
- (or
--\fB\-\-home\fR) option\&.
-+\fB\-\-home\fR) option\&. If the current home directory does not exist the new home directory will not be created\&.
- .sp
-
- \fBusermod\fR
+@@ -248,26 +258,6 @@
+
+
+
+- chfn
+-
+-
+- CHFN_AUTH
+- CHFN_RESTRICT
+- LOGIN_STRING
+-
+-
+-
+-
+- chgpasswd
+-
+-
+- ENCRYPT_METHOD MAX_MEMBERS_PER_GROUP MD5_CRYPT_ENAB
+- SHA_CRYPT_MAX_ROUNDS
+- SHA_CRYPT_MIN_ROUNDS
+-
+-
+-
+-
+ chpasswd
+
+
+@@ -278,14 +268,6 @@
+
+
+
+-
+- chsh
+-
+-
+- CHSH_AUTH LOGIN_STRING
+-
+-
+-
+
+
+
+@@ -346,34 +328,6 @@
+
+
+
+-
+- login
+-
+-
+- CONSOLE
+- CONSOLE_GROUPS DEFAULT_HOME
+- ENV_HZ ENV_PATH ENV_SUPATH
+- ENV_TZ ENVIRON_FILE
+- ERASECHAR FAIL_DELAY
+- FAILLOG_ENAB
+- FAKE_SHELL
+- FTMP_FILE
+- HUSHLOGIN_FILE
+- ISSUE_FILE
+- KILLCHAR
+- LASTLOG_ENAB
+- LOGIN_RETRIES
+- LOGIN_STRING
+- LOGIN_TIMEOUT LOG_OK_LOGINS LOG_UNKFAIL_ENAB
+- MAIL_CHECK_ENAB MAIL_DIR MAIL_FILE
+- MOTD_FILE NOLOGINS_FILE PORTTIME_CHECKS_ENAB
+- QUOTAS_ENAB
+- TTYGROUP TTYPERM TTYTYPE_FILE
+- ULIMIT UMASK
+- USERGROUPS_ENAB
+-
+-
+-
+
+
+ newgrp / sg
+@@ -399,17 +353,6 @@
+
+
+
+-
+- passwd
+-
+-
+- ENCRYPT_METHOD MD5_CRYPT_ENAB OBSCURE_CHECKS_ENAB
+- PASS_ALWAYS_WARN PASS_CHANGE_TRIES PASS_MAX_LEN PASS_MIN_LEN
+- SHA_CRYPT_MAX_ROUNDS
+- SHA_CRYPT_MIN_ROUNDS
+-
+-
+-
+
+ pwck
+
+@@ -436,32 +379,6 @@
+
+
+
+-
+- su
+-
+-
+- CONSOLE
+- CONSOLE_GROUPS DEFAULT_HOME
+- ENV_HZ ENVIRON_FILE
+- ENV_PATH ENV_SUPATH
+- ENV_TZ LOGIN_STRING MAIL_CHECK_ENAB
+- MAIL_DIR MAIL_FILE QUOTAS_ENAB
+- SULOG_FILE SU_NAME
+- SU_WHEEL_ONLY
+- SYSLOG_SU_ENAB
+- USERGROUPS_ENAB
+-
+-
+-
+-
+- sulogin
+-
+-
+- ENV_HZ
+- ENV_TZ
+-
+-
+-
+
+ useradd
+
diff -up shadow-4.1.5.1/man/useradd.8.xml.manfix shadow-4.1.5.1/man/useradd.8.xml
---- shadow-4.1.5.1/man/useradd.8.xml.manfix 2014-08-29 13:31:38.347811932 +0200
-+++ shadow-4.1.5.1/man/useradd.8.xml 2014-08-29 13:31:38.364812323 +0200
+--- shadow-4.1.5.1/man/useradd.8.xml.manfix 2015-12-17 14:05:47.930742412 +0100
++++ shadow-4.1.5.1/man/useradd.8.xml 2015-12-17 14:05:47.945742754 +0100
+@@ -134,8 +134,8 @@
+ HOME_DIR is not specified.
+ BASE_DIR is
+ concatenated with the account name to define the home directory.
+- If the option is not used,
+- BASE_DIR must exist.
++ The BASE_DIR must exist otherwise
++ the home directory cannot be created.
+
+
+ If this option is not specified, useradd
@@ -161,7 +161,7 @@
@@ -229,7 +214,17 @@ diff -up shadow-4.1.5.1/man/useradd.8.xml.manfix shadow-4.1.5.1/man/useradd.8.xm
HOME_DIR
-@@ -358,11 +358,16 @@
+@@ -171,8 +171,7 @@
+ login directory. The default is to append the
+ LOGIN name to
+ BASE_DIR and use that as the login
+- directory name. The directory HOME_DIR
+- does not have to exist but will not be created if it is missing.
++ directory name.
+
+
+
+@@ -358,11 +357,16 @@
is not enabled, no home
directories are created.
diff --git a/SOURCES/shadow-4.1.5.1-null-tm.patch b/SOURCES/shadow-4.1.5.1-null-tm.patch
new file mode 100644
index 0000000..e531d14
--- /dev/null
+++ b/SOURCES/shadow-4.1.5.1-null-tm.patch
@@ -0,0 +1,86 @@
+diff -up shadow-4.1.5.1/src/faillog.c.null-tm shadow-4.1.5.1/src/faillog.c
+--- shadow-4.1.5.1/src/faillog.c.null-tm 2011-11-19 23:54:47.000000000 +0100
++++ shadow-4.1.5.1/src/faillog.c 2016-06-14 11:54:58.582314219 +0200
+@@ -163,10 +163,14 @@ static void print_one (/*@null@*/const s
+ }
+
+ tm = localtime (&fl.fail_time);
++ if (tm == NULL) {
++ cp = "(unknown)";
++ } else {
+ #ifdef HAVE_STRFTIME
+- strftime (ptime, sizeof (ptime), "%D %H:%M:%S %z", tm);
+- cp = ptime;
++ strftime (ptime, sizeof (ptime), "%D %H:%M:%S %z", tm);
++ cp = ptime;
+ #endif
++ }
+ printf ("%-9s %5d %5d ",
+ pw->pw_name, fl.fail_cnt, fl.fail_max);
+ /* FIXME: cp is not defined ifndef HAVE_STRFTIME */
+diff -up shadow-4.1.5.1/src/chage.c.null-tm shadow-4.1.5.1/src/chage.c
+--- shadow-4.1.5.1/src/chage.c.null-tm 2016-05-04 13:44:55.639787900 +0200
++++ shadow-4.1.5.1/src/chage.c 2016-06-14 11:54:58.583314243 +0200
+@@ -168,6 +168,10 @@ static void date_to_str (char *buf, size
+ struct tm *tp;
+
+ tp = gmtime (&date);
++ if (tp == NULL) {
++ (void) snprintf (buf, maxsize, "(unknown)");
++ return;
++ }
+ #ifdef HAVE_STRFTIME
+ (void) strftime (buf, maxsize, "%Y-%m-%d", tp);
+ #else
+diff -up shadow-4.1.5.1/src/lastlog.c.null-tm shadow-4.1.5.1/src/lastlog.c
+--- shadow-4.1.5.1/src/lastlog.c.null-tm 2016-05-04 13:44:55.647788082 +0200
++++ shadow-4.1.5.1/src/lastlog.c 2016-06-14 11:54:58.584314267 +0200
+@@ -165,13 +165,17 @@ static void print_one (/*@null@*/const s
+
+ ll_time = ll.ll_time;
+ tm = localtime (&ll_time);
++ if (tm == NULL) {
++ cp = "(unknown)";
++ } else {
+ #ifdef HAVE_STRFTIME
+- strftime (ptime, sizeof (ptime), "%a %b %e %H:%M:%S %z %Y", tm);
+- cp = ptime;
++ strftime (ptime, sizeof (ptime), "%a %b %e %H:%M:%S %z %Y", tm);
++ cp = ptime;
+ #else
+- cp = asctime (tm);
+- cp[24] = '\0';
++ cp = asctime (tm);
++ cp[24] = '\0';
+ #endif
++ }
+
+ if (ll.ll_time == (time_t) 0) {
+ cp = _("**Never logged in**\0");
+diff -up shadow-4.1.5.1/src/passwd.c.null-tm shadow-4.1.5.1/src/passwd.c
+--- shadow-4.1.5.1/src/passwd.c.null-tm 2016-05-04 13:44:55.634787787 +0200
++++ shadow-4.1.5.1/src/passwd.c 2016-06-14 11:54:58.584314267 +0200
+@@ -438,6 +438,9 @@ static /*@observer@*/const char *date_to
+ struct tm *tm;
+
+ tm = gmtime (&t);
++ if (tm == NULL) {
++ return "(unknown)";
++ }
+ #ifdef HAVE_STRFTIME
+ (void) strftime (buf, sizeof buf, "%m/%d/%Y", tm);
+ #else /* !HAVE_STRFTIME */
+diff -up shadow-4.1.5.1/src/usermod.c.null-tm shadow-4.1.5.1/src/usermod.c
+--- shadow-4.1.5.1/src/usermod.c.null-tm 2016-05-04 13:44:55.648788104 +0200
++++ shadow-4.1.5.1/src/usermod.c 2016-06-14 11:54:58.585314291 +0200
+@@ -186,6 +186,10 @@ static void date_to_str (/*@unique@*//*@
+ } else {
+ time_t t = (time_t) date;
+ tp = gmtime (&t);
++ if (tp == NULL) {
++ strncpy (buf, "unknown", maxsize);
++ return;
++ }
+ #ifdef HAVE_STRFTIME
+ strftime (buf, maxsize, "%Y-%m-%d", tp);
+ #else
diff --git a/SOURCES/shadow-4.1.5.1-selinux-perms.patch b/SOURCES/shadow-4.1.5.1-selinux-perms.patch
new file mode 100644
index 0000000..3c70296
--- /dev/null
+++ b/SOURCES/shadow-4.1.5.1-selinux-perms.patch
@@ -0,0 +1,289 @@
+diff -up shadow-4.1.5.1/src/chgpasswd.c.selinux-perms shadow-4.1.5.1/src/chgpasswd.c
+--- shadow-4.1.5.1/src/chgpasswd.c.selinux-perms 2016-05-04 13:44:55.633787764 +0200
++++ shadow-4.1.5.1/src/chgpasswd.c 2016-05-30 12:01:30.421587253 +0200
+@@ -39,6 +39,13 @@
+ #include
+ #include
+ #include
++#ifdef WITH_SELINUX
++#include
++#include
++#endif
++#ifdef WITH_LIBAUDIT
++#include
++#endif
+ #ifdef ACCT_TOOLS_SETUID
+ #ifdef USE_PAM
+ #include "pam_defs.h"
+@@ -76,6 +83,9 @@ static bool sgr_locked = false;
+ #endif
+ static bool gr_locked = false;
+
++/* The name of the caller */
++static char *myname = NULL;
++
+ /* local function prototypes */
+ static void fail_exit (int code);
+ static /*@noreturn@*/void usage (int status);
+@@ -300,6 +310,63 @@ static void check_perms (void)
+ #endif /* ACCT_TOOLS_SETUID */
+ }
+
++#ifdef WITH_SELINUX
++static int
++log_callback (int type, const char *fmt, ...)
++{
++ int audit_fd;
++ va_list ap;
++
++ va_start(ap, fmt);
++#ifdef WITH_AUDIT
++ audit_fd = audit_open();
++
++ if (audit_fd >= 0) {
++ char *buf;
++
++ if (vasprintf (&buf, fmt, ap) < 0)
++ goto ret;
++ audit_log_user_avc_message(audit_fd, AUDIT_USER_AVC, buf, NULL, NULL,
++ NULL, 0);
++ audit_close(audit_fd);
++ free(buf);
++ goto ret;
++ }
++
++#endif
++ vsyslog (LOG_USER | LOG_INFO, fmt, ap);
++ret:
++ va_end(ap);
++ return 0;
++}
++
++static void
++selinux_check_root (void)
++{
++ int status = -1;
++ security_context_t user_context;
++ union selinux_callback old_callback;
++
++ if (is_selinux_enabled() < 1)
++ return;
++
++ old_callback = selinux_get_callback(SELINUX_CB_LOG);
++ /* setup callbacks */
++ selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) &log_callback);
++ if ((status = getprevcon(&user_context)) < 0) {
++ selinux_set_callback(SELINUX_CB_LOG, old_callback);
++ exit(1);
++ }
++
++ status = selinux_check_access(user_context, user_context, "passwd", "passwd", NULL);
++
++ selinux_set_callback(SELINUX_CB_LOG, old_callback);
++ freecon(user_context);
++ if (status != 0 && security_getenforce() != 0)
++ exit(1);
++}
++#endif
++
+ /*
+ * open_files - lock and open the group databases
+ */
+@@ -393,6 +460,7 @@ int main (int argc, char **argv)
+
+ const struct group *gr;
+ struct group newgr;
++ struct passwd *pw = NULL;
+ int errors = 0;
+ int line = 0;
+
+@@ -408,8 +476,33 @@ int main (int argc, char **argv)
+
+ OPENLOG ("chgpasswd");
+
++#ifdef WITH_AUDIT
++ audit_help_open ();
++#endif
++
++ /*
++ * Determine the name of the user that invoked this command. This
++ * is really hit or miss because there are so many ways that command
++ * can be executed and so many ways to trip up the routines that
++ * report the user name.
++ */
++ pw = get_my_pwent ();
++ if (NULL == pw) {
++ fprintf (stderr, _("%s: Cannot determine your user name.\n"),
++ Prog);
++ SYSLOG ((LOG_WARN,
++ "Cannot determine the user name of the caller (UID %lu)",
++ (unsigned long) getuid ()));
++ exit (E_NOPERM);
++ }
++ myname = xstrdup (pw->pw_name);
++
+ check_perms ();
+
++#ifdef WITH_SELINUX
++ selinux_check_root ();
++#endif
++
+ #ifdef SHADOWGRP
+ is_shadow_grp = sgr_file_present ();
+ #endif
+@@ -533,6 +626,15 @@ int main (int argc, char **argv)
+ newgr.gr_passwd = cp;
+ }
+
++#ifdef WITH_AUDIT
++ {
++
++ audit_logger_with_group (AUDIT_GRP_CHAUTHTOK, Prog,
++ "change-password",
++ myname, AUDIT_NO_ID, gr->gr_name,
++ SHADOW_AUDIT_SUCCESS);
++ }
++#endif
+ /*
+ * The updated group file entry is then put back and will
+ * be written to the group file later, after all the
+diff -up shadow-4.1.5.1/src/chpasswd.c.selinux-perms shadow-4.1.5.1/src/chpasswd.c
+--- shadow-4.1.5.1/src/chpasswd.c.selinux-perms 2016-05-04 13:44:55.633787764 +0200
++++ shadow-4.1.5.1/src/chpasswd.c 2016-05-30 12:01:42.877859957 +0200
+@@ -39,6 +39,13 @@
+ #include
+ #include
+ #include
++#ifdef WITH_SELINUX
++#include
++#include
++#endif
++#ifdef WITH_LIBAUDIT
++#include
++#endif
+ #ifdef USE_PAM
+ #include "pam_defs.h"
+ #endif /* USE_PAM */
+@@ -297,6 +304,63 @@ static void check_perms (void)
+ #endif /* USE_PAM */
+ }
+
++#ifdef WITH_SELINUX
++static int
++log_callback (int type, const char *fmt, ...)
++{
++ int audit_fd;
++ va_list ap;
++
++ va_start(ap, fmt);
++#ifdef WITH_AUDIT
++ audit_fd = audit_open();
++
++ if (audit_fd >= 0) {
++ char *buf;
++
++ if (vasprintf (&buf, fmt, ap) < 0)
++ goto ret;
++ audit_log_user_avc_message(audit_fd, AUDIT_USER_AVC, buf, NULL, NULL,
++ NULL, 0);
++ audit_close(audit_fd);
++ free(buf);
++ goto ret;
++ }
++
++#endif
++ vsyslog (LOG_USER | LOG_INFO, fmt, ap);
++ret:
++ va_end(ap);
++ return 0;
++}
++
++static void
++selinux_check_root (void)
++{
++ int status = -1;
++ security_context_t user_context;
++ union selinux_callback old_callback;
++
++ if (is_selinux_enabled() < 1)
++ return;
++
++ old_callback = selinux_get_callback(SELINUX_CB_LOG);
++ /* setup callbacks */
++ selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) &log_callback);
++ if ((status = getprevcon(&user_context)) < 0) {
++ selinux_set_callback(SELINUX_CB_LOG, old_callback);
++ exit(1);
++ }
++
++ status = selinux_check_access(user_context, user_context, "passwd", "passwd", NULL);
++
++ selinux_set_callback(SELINUX_CB_LOG, old_callback);
++ freecon(user_context);
++ if (status != 0 && security_getenforce() != 0)
++ exit(1);
++}
++#endif
++
+ /*
+ * open_files - lock and open the password databases
+ */
+@@ -405,8 +469,16 @@ int main (int argc, char **argv)
+
+ OPENLOG ("chpasswd");
+
++#ifdef WITH_AUDIT
++ audit_help_open ();
++#endif
++
+ check_perms ();
+
++#ifdef WITH_SELINUX
++ selinux_check_root ();
++#endif
++
+ #ifdef USE_PAM
+ if (!use_pam)
+ #endif /* USE_PAM */
+@@ -563,6 +635,11 @@ int main (int argc, char **argv)
+ newpw.pw_passwd = cp;
+ }
+
++#ifdef WITH_AUDIT
++ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
++ "updating-password",
++ pw->pw_name, (unsigned int) pw->pw_uid, 1);
++#endif
+ /*
+ * The updated password file entry is then put back and will
+ * be written to the password file later, after all the
+diff -up shadow-4.1.5.1/src/Makefile.am.selinux-perms shadow-4.1.5.1/src/Makefile.am
+--- shadow-4.1.5.1/src/Makefile.am.selinux-perms 2016-05-04 13:44:55.647788082 +0200
++++ shadow-4.1.5.1/src/Makefile.am 2016-05-27 16:04:49.446582632 +0200
+@@ -79,9 +79,9 @@ endif
+
+ chage_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
+ chfn_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
+-chgpasswd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBSELINUX) $(LIBCRYPT)
++chgpasswd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBSELINUX) $(LIBAUDIT) $(LIBCRYPT)
+ chsh_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
+-chpasswd_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT)
++chpasswd_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBAUDIT) $(LIBCRYPT)
+ gpasswd_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT)
+ groupadd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
+ groupdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
+diff -up shadow-4.1.5.1/src/Makefile.in.selinux-perms shadow-4.1.5.1/src/Makefile.in
+--- shadow-4.1.5.1/src/Makefile.in.selinux-perms 2016-05-04 13:44:55.647788082 +0200
++++ shadow-4.1.5.1/src/Makefile.in 2016-05-27 16:04:49.447582654 +0200
+@@ -437,9 +437,9 @@ AM_CPPFLAGS = -DLOCALEDIR=\"$(datadir)/l
+ @USE_PAM_TRUE@LIBCRYPT_NOPAM =
+ chage_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
+ chfn_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
+-chgpasswd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBSELINUX) $(LIBCRYPT)
++chgpasswd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBSELINUX) $(LIBAUDIT) $(LIBCRYPT)
+ chsh_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
+-chpasswd_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT)
++chpasswd_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBAUDIT) $(LIBCRYPT)
+ gpasswd_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT)
+ groupadd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
+ groupdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
diff --git a/SOURCES/shadow-4.1.5.1-usermod-passwd.patch b/SOURCES/shadow-4.1.5.1-usermod-passwd.patch
new file mode 100644
index 0000000..8096cfd
--- /dev/null
+++ b/SOURCES/shadow-4.1.5.1-usermod-passwd.patch
@@ -0,0 +1,63 @@
+diff -up shadow-4.1.5.1/src/usermod.c.passwd shadow-4.1.5.1/src/usermod.c
+--- shadow-4.1.5.1/src/usermod.c.passwd 2015-12-17 14:05:47.959743073 +0100
++++ shadow-4.1.5.1/src/usermod.c 2015-12-18 12:42:28.290405529 +0100
+@@ -360,14 +360,17 @@ static char *new_pw_passwd (char *pw_pas
+ strcat (buf, pw_pass);
+ pw_pass = buf;
+ } else if (Uflg && pw_pass[0] == '!') {
+- char *s;
++ char *s = pw_pass;
+
+- if (pw_pass[1] == '\0') {
++ while ('!' == *s)
++ ++s;
++
++ if (*s == '\0') {
+ fprintf (stderr,
+ _("%s: unlocking the user's password would result in a passwordless account.\n"
+ "You should set a password with usermod -p to unlock this user's password.\n"),
+ Prog);
+- return pw_pass;
++ return NULL;
+ }
+
+ #ifdef WITH_AUDIT
+@@ -376,12 +379,15 @@ static char *new_pw_passwd (char *pw_pas
+ user_newname, (unsigned int) user_newid, 1);
+ #endif
+ SYSLOG ((LOG_INFO, "unlock user '%s' password", user_newname));
+- s = pw_pass;
+- while ('\0' != *s) {
+- *s = *(s + 1);
+- s++;
+- }
++ memmove (pw_pass, s, strlen (s) + 1);
+ } else if (pflg) {
++ if (strchr (user_pass, ':') != NULL) {
++ fprintf (stderr,
++ _("%s: The password field cannot contain a colon character.\n"),
++ Prog);
++ return NULL;
++
++ }
+ #ifdef WITH_AUDIT
+ audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
+ "updating-password",
+@@ -430,6 +436,8 @@ static void new_pwent (struct passwd *pw
+ if ( (!is_shadow_pwd)
+ || (strcmp (pwent->pw_passwd, SHADOW_PASSWD_STRING) != 0)) {
+ pwent->pw_passwd = new_pw_passwd (pwent->pw_passwd);
++ if (pwent->pw_passwd == NULL)
++ fail_exit (E_PW_UPDATE);
+ }
+
+ if (uflg) {
+@@ -544,6 +552,8 @@ static void new_spent (struct spwd *spen
+ * + aging has been requested
+ */
+ spent->sp_pwdp = new_pw_passwd (spent->sp_pwdp);
++ if (spent->sp_pwdp == NULL)
++ fail_exit(E_PW_UPDATE);
+
+ if (pflg) {
+ spent->sp_lstchg = (long) time ((time_t *) 0) / SCALE;
diff --git a/SPECS/shadow-utils.spec b/SPECS/shadow-utils.spec
index 313f6cc..e7f98da 100644
--- a/SPECS/shadow-utils.spec
+++ b/SPECS/shadow-utils.spec
@@ -1,7 +1,7 @@
Summary: Utilities for managing accounts and shadow password files
Name: shadow-utils
Version: 4.1.5.1
-Release: 18%{?dist}
+Release: 24%{?dist}
Epoch: 2
URL: http://pkg-shadow.alioth.debian.org/
Source0: http://pkg-shadow.alioth.debian.org/releases/shadow-%{version}.tar.bz2
@@ -28,6 +28,13 @@ Patch18: shadow-4.1.5.1-date-parsing.patch
Patch19: shadow-4.1.5.1-ingroup.patch
Patch20: shadow-4.1.5.1-move-home.patch
Patch21: shadow-4.1.5.1-audit-update.patch
+Patch22: shadow-4.1.5.1-ja-translation.patch
+Patch23: shadow-4.1.5.1-usermod-passwd.patch
+Patch24: shadow-4.1.5.1-id-alloc.patch
+Patch25: shadow-4.1.5.1-lastlog-unexpire.patch
+Patch26: shadow-4.1.5.1-chgrp-guard.patch
+Patch27: shadow-4.1.5.1-selinux-perms.patch
+Patch28: shadow-4.1.5.1-null-tm.patch
License: BSD and GPLv2+
Group: System Environment/Base
@@ -35,6 +42,7 @@ BuildRequires: libselinux-devel >= 1.25.2-1
BuildRequires: audit-libs-devel >= 1.6.5
BuildRequires: libsemanage-devel
BuildRequires: libacl-devel libattr-devel
+BuildRequires: gnome-doc-utils docbook-style-xsl gettext
#BuildRequires: autoconf, automake, libtool, gettext-devel
Requires: libselinux >= 1.25.2-1
Requires: audit-libs >= 1.6.5
@@ -77,6 +85,13 @@ are used for managing group accounts.
%patch19 -p1 -b .ingroup
%patch20 -p1 -b .move-home
%patch21 -p1 -b .audit-update
+%patch22 -p1 -b .ja-translation
+%patch23 -p1 -b .usermod-passwd
+%patch24 -p1 -b .id-alloc
+%patch25 -p1 -b .unexpire
+%patch26 -p1 -b .chgrp-guard
+%patch27 -p1 -b .selinux-perms
+%patch28 -p1 -b .null-tm
iconv -f ISO88591 -t utf-8 doc/HOWTO > doc/HOWTO.utf8
cp -f doc/HOWTO.utf8 doc/HOWTO
@@ -99,8 +114,10 @@ export CFLAGS="$RPM_OPT_FLAGS -fpie"
export LDFLAGS="-pie -Wl,-z,relro -Wl,-z,now"
%endif
+export LC_ALL=C
%configure \
--enable-shadowgrp \
+ --enable-man \
--with-audit \
--with-sha-crypt \
--with-selinux \
@@ -108,6 +125,8 @@ export LDFLAGS="-pie -Wl,-z,relro -Wl,-z,now"
--without-libpam \
--disable-shared \
--with-group-name-max-length=32
+# update the japanese translation
+(cd po; make ja.gmo)
make
%install
@@ -230,6 +249,26 @@ rm -rf $RPM_BUILD_ROOT
%{_mandir}/man8/vigr.8*
%changelog
+* Tue Jun 28 2016 Tomáš Mráz - 2:4.1.5.1-24
+- useradd: fix typo in japanese translation (#1202629)
+
+* Tue Jun 14 2016 Tomáš Mráz - 2:4.1.5.1-23
+- guard for localtime() and gmtime() failure (#1341167)
+
+* Mon May 30 2016 Tomáš Mráz - 2:4.1.5.1-22
+- chpasswd: add selinux_check_access() call (#1336902)
+
+* Wed May 4 2016 Tomáš Mráz - 2:4.1.5.1-20
+- usermod: guard against unsafe change of ownership of
+ special home directories (#1225560)
+
+* Thu Apr 28 2016 Tomáš Mráz - 2:4.1.5.1-19
+- documentation fixes (#1292820)
+- usermod: make password unlocking compatible with passwd (#1185425)
+- usermod: disallow ':' in raw password setting (#1292815)
+- improve the gid and uid allocation mechanism (#1279321)
+- lastlog: add options to unexpire unused accounts (#1285547)
+
* Tue Nov 25 2014 Tomáš Mráz - 2:4.1.5.1-18
- properly audit modification of gshadow