Imported Upstream version 4.1.5.1
This commit is contained in:
5
src/.indent.pro
vendored
Normal file
5
src/.indent.pro
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
-kr
|
||||
-i8
|
||||
-bad
|
||||
-pcs
|
||||
-l80
|
||||
129
src/Makefile.am
Normal file
129
src/Makefile.am
Normal file
@@ -0,0 +1,129 @@
|
||||
|
||||
EXTRA_DIST = \
|
||||
.indent.pro
|
||||
|
||||
ubindir = ${prefix}/bin
|
||||
usbindir = ${prefix}/sbin
|
||||
suidperms = 4755
|
||||
sgidperms = 2755
|
||||
|
||||
INCLUDES = \
|
||||
-I${top_srcdir}/lib \
|
||||
-I$(top_srcdir)/libmisc
|
||||
|
||||
# XXX why are login and su in /bin anyway (other than for
|
||||
# historical reasons)?
|
||||
#
|
||||
# if the system is screwed so badly that it can't mount /usr,
|
||||
# you can (hopefully) boot single user, and then you're root
|
||||
# so you don't need these programs for recovery.
|
||||
#
|
||||
# also /lib/libshadow.so.x.xx (if any) could be moved to /usr/lib
|
||||
# and installation would be much simpler (just two directories,
|
||||
# $prefix/bin and $prefix/sbin, no install-data hacks...)
|
||||
|
||||
bin_PROGRAMS = groups login su
|
||||
sbin_PROGRAMS = nologin
|
||||
ubin_PROGRAMS = faillog lastlog chage chfn chsh expiry gpasswd newgrp passwd
|
||||
usbin_PROGRAMS = \
|
||||
chgpasswd \
|
||||
chpasswd \
|
||||
groupadd \
|
||||
groupdel \
|
||||
groupmems \
|
||||
groupmod \
|
||||
grpck \
|
||||
grpconv \
|
||||
grpunconv \
|
||||
logoutd \
|
||||
newusers \
|
||||
pwck \
|
||||
pwconv \
|
||||
pwunconv \
|
||||
useradd \
|
||||
userdel \
|
||||
usermod \
|
||||
vipw
|
||||
|
||||
# id and groups are from gnu, sulogin from sysvinit
|
||||
noinst_PROGRAMS = id sulogin
|
||||
|
||||
suidbins = su
|
||||
suidubins = chage chfn chsh expiry gpasswd newgrp passwd
|
||||
if ACCT_TOOLS_SETUID
|
||||
suidubins += chage chgpasswd chpasswd groupadd groupdel groupmod newusers useradd userdel usermod
|
||||
endif
|
||||
|
||||
if WITH_TCB
|
||||
suidubins -= passwd
|
||||
shadowsgidubins = passwd
|
||||
endif
|
||||
|
||||
LDADD = $(INTLLIBS) \
|
||||
$(LIBTCB) \
|
||||
$(top_builddir)/libmisc/libmisc.a \
|
||||
$(top_builddir)/lib/libshadow.la
|
||||
AM_CPPFLAGS = -DLOCALEDIR=\"$(datadir)/locale\"
|
||||
|
||||
if ACCT_TOOLS_SETUID
|
||||
LIBPAM_SUID = $(LIBPAM)
|
||||
else
|
||||
LIBPAM_SUID =
|
||||
endif
|
||||
|
||||
if USE_PAM
|
||||
LIBCRYPT_NOPAM =
|
||||
else
|
||||
LIBCRYPT_NOPAM = $(LIBCRYPT)
|
||||
endif
|
||||
|
||||
chage_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
|
||||
chfn_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
|
||||
chgpasswd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBSELINUX) $(LIBCRYPT)
|
||||
chsh_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
|
||||
chpasswd_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT)
|
||||
gpasswd_LDADD = $(LDADD) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT)
|
||||
groupadd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
|
||||
groupdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
|
||||
groupmems_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX)
|
||||
groupmod_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX)
|
||||
grpck_LDADD = $(LDADD) $(LIBSELINUX)
|
||||
grpconv_LDADD = $(LDADD) $(LIBSELINUX)
|
||||
grpunconv_LDADD = $(LDADD) $(LIBSELINUX)
|
||||
login_SOURCES = \
|
||||
login.c \
|
||||
login_nopam.c
|
||||
login_LDADD = $(LDADD) $(LIBPAM) $(LIBAUDIT) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
|
||||
newgrp_LDADD = $(LDADD) $(LIBAUDIT) $(LIBCRYPT)
|
||||
newusers_LDADD = $(LDADD) $(LIBPAM) $(LIBSELINUX) $(LIBCRYPT)
|
||||
nologin_LDADD =
|
||||
passwd_LDADD = $(LDADD) $(LIBPAM) $(LIBCRACK) $(LIBAUDIT) $(LIBSELINUX) $(LIBCRYPT_NOPAM)
|
||||
pwck_LDADD = $(LDADD) $(LIBSELINUX)
|
||||
pwconv_LDADD = $(LDADD) $(LIBSELINUX)
|
||||
pwunconv_LDADD = $(LDADD) $(LIBSELINUX)
|
||||
su_SOURCES = \
|
||||
su.c \
|
||||
suauth.c
|
||||
su_LDADD = $(LDADD) $(LIBPAM) $(LIBCRYPT_NOPAM) $(LIBSKEY) $(LIBMD)
|
||||
sulogin_LDADD = $(LDADD) $(LIBCRYPT)
|
||||
useradd_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBACL) $(LIBATTR)
|
||||
userdel_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE)
|
||||
usermod_LDADD = $(LDADD) $(LIBPAM_SUID) $(LIBAUDIT) $(LIBSELINUX) $(LIBSEMANAGE) $(LIBACL) $(LIBATTR)
|
||||
vipw_LDADD = $(LDADD) $(LIBSELINUX)
|
||||
|
||||
install-am: all-am
|
||||
$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
|
||||
ln -sf newgrp $(DESTDIR)$(ubindir)/sg
|
||||
ln -sf vipw $(DESTDIR)$(usbindir)/vigr
|
||||
for i in $(suidbins); do \
|
||||
chmod -f $(suidperms) $(DESTDIR)$(bindir)/$$i; \
|
||||
done
|
||||
for i in $(suidubins); do \
|
||||
chmod -f $(suidperms) $(DESTDIR)$(ubindir)/$$i; \
|
||||
done
|
||||
if WITH_TCB
|
||||
for i in $(shadowsgidubins); do \
|
||||
chown root:shadow $(DESTDIR)$(ubindir)/$$i; \
|
||||
chmod -f $(sgidperms) $(DESTDIR)$(ubindir)/$$i; \
|
||||
done
|
||||
endif
|
||||
1099
src/Makefile.in
Normal file
1099
src/Makefile.in
Normal file
File diff suppressed because it is too large
Load Diff
948
src/chage.c
Normal file
948
src/chage.c
Normal file
@@ -0,0 +1,948 @@
|
||||
/*
|
||||
* Copyright (c) 1989 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* 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>
|
||||
|
||||
#ident "$Id: chage.c 3642 2011-11-19 21:56:10Z nekral-guest $"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
#include "pam_defs.h"
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
#include <pwd.h>
|
||||
#ifdef WITH_SELINUX
|
||||
#include <selinux/selinux.h>
|
||||
#include <selinux/av_permissions.h>
|
||||
#endif
|
||||
#include "prototypes.h"
|
||||
#include "defines.h"
|
||||
#include "pwio.h"
|
||||
#include "shadowio.h"
|
||||
#ifdef WITH_TCB
|
||||
#include "tcbfuncs.h"
|
||||
#endif
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static bool
|
||||
dflg = false, /* set last password change date */
|
||||
Eflg = false, /* set account expiration date */
|
||||
Iflg = false, /* set password inactive after expiration */
|
||||
lflg = false, /* show account aging information */
|
||||
mflg = false, /* set minimum number of days before password change */
|
||||
Mflg = false, /* set maximum number of days before password change */
|
||||
Wflg = false; /* set expiration warning days */
|
||||
static bool amroot = false;
|
||||
|
||||
static bool pw_locked = false; /* Indicate if the password file is locked */
|
||||
static bool spw_locked = false; /* Indicate if the shadow file is locked */
|
||||
/* The name and UID of the user being worked on */
|
||||
static char user_name[BUFSIZ] = "";
|
||||
static uid_t user_uid = -1;
|
||||
|
||||
static long mindays;
|
||||
static long maxdays;
|
||||
static long lstchgdate;
|
||||
static long warndays;
|
||||
static long inactdays;
|
||||
static long expdate;
|
||||
|
||||
/* local function prototypes */
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void date_to_str (char *buf, size_t maxsize, time_t date);
|
||||
static int new_fields (void);
|
||||
static void print_date (time_t date);
|
||||
static void list_fields (void);
|
||||
static void process_flags (int argc, char **argv);
|
||||
static void check_flags (int argc, int opt_index);
|
||||
static void check_perms (void);
|
||||
static void open_files (bool readonly);
|
||||
static void close_files (void);
|
||||
static /*@noreturn@*/void fail_exit (int code);
|
||||
|
||||
/*
|
||||
* fail_exit - do some cleanup and exit with the given error code
|
||||
*/
|
||||
static /*@noreturn@*/void fail_exit (int code)
|
||||
{
|
||||
if (spw_locked) {
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
if (pw_locked) {
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
closelog ();
|
||||
|
||||
#ifdef WITH_AUDIT
|
||||
if (E_SUCCESS != code) {
|
||||
audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
|
||||
"change age",
|
||||
user_name, (unsigned int) user_uid, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
exit (code);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage - print command line syntax and exit
|
||||
*/
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options] LOGIN\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(void) fputs (_(" -d, --lastday LAST_DAY set date of last password change to LAST_DAY\n"), usageout);
|
||||
(void) fputs (_(" -E, --expiredate EXPIRE_DATE set account expiration date to EXPIRE_DATE\n"), usageout);
|
||||
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs (_(" -I, --inactive INACTIVE set password inactive after expiration\n"
|
||||
" to INACTIVE\n"), usageout);
|
||||
(void) fputs (_(" -l, --list show account aging information\n"), usageout);
|
||||
(void) fputs (_(" -m, --mindays MIN_DAYS set minimum number of days before password\n"
|
||||
" change to MIN_DAYS\n"), usageout);
|
||||
(void) fputs (_(" -M, --maxdays MAX_DAYS set maximim number of days before password\n"
|
||||
" change to MAX_DAYS\n"), usageout);
|
||||
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
(void) fputs (_(" -W, --warndays WARN_DAYS set expiration warning days to WARN_DAYS\n"), usageout);
|
||||
(void) fputs ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
static void date_to_str (char *buf, size_t maxsize, time_t date)
|
||||
{
|
||||
struct tm *tp;
|
||||
|
||||
tp = gmtime (&date);
|
||||
#ifdef HAVE_STRFTIME
|
||||
(void) strftime (buf, maxsize, "%Y-%m-%d", tp);
|
||||
#else
|
||||
(void) snprintf (buf, maxsize, "%04d-%02d-%02d",
|
||||
tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday);
|
||||
#endif /* HAVE_STRFTIME */
|
||||
}
|
||||
|
||||
/*
|
||||
* new_fields - change the user's password aging information interactively.
|
||||
*
|
||||
* prompt the user for all of the password age values. set the fields
|
||||
* from the user's response, or leave alone if nothing was entered. The
|
||||
* value (-1) is used to indicate the field should be removed if possible.
|
||||
* any other negative value is an error. very large positive values will
|
||||
* be handled elsewhere.
|
||||
*/
|
||||
static int new_fields (void)
|
||||
{
|
||||
char buf[200];
|
||||
|
||||
(void) puts (_("Enter the new value, or press ENTER for the default"));
|
||||
(void) puts ("");
|
||||
|
||||
(void) snprintf (buf, sizeof buf, "%ld", mindays);
|
||||
change_field (buf, sizeof buf, _("Minimum Password Age"));
|
||||
if ( (getlong (buf, &mindays) == 0)
|
||||
|| (mindays < -1)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
(void) snprintf (buf, sizeof buf, "%ld", maxdays);
|
||||
change_field (buf, sizeof buf, _("Maximum Password Age"));
|
||||
if ( (getlong (buf, &maxdays) == 0)
|
||||
|| (maxdays < -1)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (-1 == lstchgdate) {
|
||||
strcpy (buf, "-1");
|
||||
} else {
|
||||
date_to_str (buf, sizeof buf, (time_t) lstchgdate * SCALE);
|
||||
}
|
||||
|
||||
change_field (buf, sizeof buf, _("Last Password Change (YYYY-MM-DD)"));
|
||||
|
||||
if (strcmp (buf, "-1") == 0) {
|
||||
lstchgdate = -1;
|
||||
} else {
|
||||
lstchgdate = strtoday (buf);
|
||||
if (lstchgdate <= -1) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
(void) snprintf (buf, sizeof buf, "%ld", warndays);
|
||||
change_field (buf, sizeof buf, _("Password Expiration Warning"));
|
||||
if ( (getlong (buf, &warndays) == 0)
|
||||
|| (warndays < -1)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
(void) snprintf (buf, sizeof buf, "%ld", inactdays);
|
||||
change_field (buf, sizeof buf, _("Password Inactive"));
|
||||
if ( (getlong (buf, &inactdays) == 0)
|
||||
|| (inactdays < -1)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (-1 == expdate) {
|
||||
strcpy (buf, "-1");
|
||||
} else {
|
||||
date_to_str (buf, sizeof buf, (time_t) expdate * SCALE);
|
||||
}
|
||||
|
||||
change_field (buf, sizeof buf,
|
||||
_("Account Expiration Date (YYYY-MM-DD)"));
|
||||
|
||||
if (strcmp (buf, "-1") == 0) {
|
||||
expdate = -1;
|
||||
} else {
|
||||
expdate = strtoday (buf);
|
||||
if (expdate <= -1) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void print_date (time_t date)
|
||||
{
|
||||
#ifdef HAVE_STRFTIME
|
||||
struct tm *tp;
|
||||
char buf[80];
|
||||
|
||||
tp = gmtime (&date);
|
||||
if (NULL == tp) {
|
||||
(void) printf ("time_t: %lu\n", (unsigned long)date);
|
||||
} else {
|
||||
(void) strftime (buf, sizeof buf, "%b %d, %Y", tp);
|
||||
(void) puts (buf);
|
||||
}
|
||||
#else
|
||||
struct tm *tp;
|
||||
char *cp = NULL;
|
||||
|
||||
tp = gmtime (&date);
|
||||
if (NULL != tp) {
|
||||
cp = asctime (tp);
|
||||
}
|
||||
if (NULL != cp) {
|
||||
(void) printf ("%6.6s, %4.4s\n", cp + 4, cp + 20);
|
||||
} else {
|
||||
(void) printf ("time_t: %lu\n", date);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* list_fields - display the current values of the expiration fields
|
||||
*
|
||||
* display the password age information from the password fields. Date
|
||||
* values will be displayed as a calendar date, or the word "never" if
|
||||
* the date is 1/1/70, which is day number 0.
|
||||
*/
|
||||
static void list_fields (void)
|
||||
{
|
||||
long changed = 0;
|
||||
long expires;
|
||||
|
||||
/*
|
||||
* The "last change" date is either "never" or the date the password
|
||||
* was last modified. The date is the number of days since 1/1/1970.
|
||||
*/
|
||||
(void) fputs (_("Last password change\t\t\t\t\t: "), stdout);
|
||||
if (lstchgdate < 0) {
|
||||
(void) puts (_("never"));
|
||||
} else if (lstchgdate == 0) {
|
||||
(void) puts (_("password must be changed"));
|
||||
} else {
|
||||
changed = lstchgdate * SCALE;
|
||||
print_date ((time_t) changed);
|
||||
}
|
||||
|
||||
/*
|
||||
* The password expiration date is determined from the last change
|
||||
* date plus the number of days the password is valid for.
|
||||
*/
|
||||
(void) fputs (_("Password expires\t\t\t\t\t: "), stdout);
|
||||
if (lstchgdate == 0) {
|
||||
(void) puts (_("password must be changed"));
|
||||
} else if ( (lstchgdate < 0)
|
||||
|| (maxdays >= (10000 * (DAY / SCALE)))
|
||||
|| (maxdays < 0)) {
|
||||
(void) puts (_("never"));
|
||||
} else {
|
||||
expires = changed + maxdays * SCALE;
|
||||
print_date ((time_t) expires);
|
||||
}
|
||||
|
||||
/*
|
||||
* The account becomes inactive if the password is expired for more
|
||||
* than "inactdays". The expiration date is calculated and the
|
||||
* number of inactive days is added. The resulting date is when the
|
||||
* active will be disabled.
|
||||
*/
|
||||
(void) fputs (_("Password inactive\t\t\t\t\t: "), stdout);
|
||||
if (lstchgdate == 0) {
|
||||
(void) puts (_("password must be changed"));
|
||||
} else if ( (lstchgdate < 0)
|
||||
|| (inactdays < 0)
|
||||
|| (maxdays >= (10000 * (DAY / SCALE)))
|
||||
|| (maxdays < 0)) {
|
||||
(void) puts (_("never"));
|
||||
} else {
|
||||
expires = changed + (maxdays + inactdays) * SCALE;
|
||||
print_date ((time_t) expires);
|
||||
}
|
||||
|
||||
/*
|
||||
* The account will expire on the given date regardless of the
|
||||
* password expiring or not.
|
||||
*/
|
||||
(void) fputs (_("Account expires\t\t\t\t\t\t: "), stdout);
|
||||
if (expdate < 0) {
|
||||
(void) puts (_("never"));
|
||||
} else {
|
||||
expires = expdate * SCALE;
|
||||
print_date ((time_t) expires);
|
||||
}
|
||||
|
||||
/*
|
||||
* Start with the easy numbers - the number of days before the
|
||||
* password can be changed, the number of days after which the
|
||||
* password must be chaged, the number of days before the password
|
||||
* expires that the user is told, and the number of days after the
|
||||
* password expires that the account becomes unusable.
|
||||
*/
|
||||
printf (_("Minimum number of days between password change\t\t: %ld\n"),
|
||||
mindays);
|
||||
printf (_("Maximum number of days between password change\t\t: %ld\n"),
|
||||
maxdays);
|
||||
printf (_("Number of days of warning before password expires\t: %ld\n"),
|
||||
warndays);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"lastday", required_argument, NULL, 'd'},
|
||||
{"expiredate", required_argument, NULL, 'E'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"inactive", required_argument, NULL, 'I'},
|
||||
{"list", no_argument, NULL, 'l'},
|
||||
{"mindays", required_argument, NULL, 'm'},
|
||||
{"maxdays", required_argument, NULL, 'M'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{"warndays", required_argument, NULL, 'W'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv, "d:E:hI:lm:M:R:W:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'd':
|
||||
dflg = true;
|
||||
lstchgdate = strtoday (optarg);
|
||||
if (lstchgdate < -1) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid date '%s'\n"),
|
||||
Prog, optarg);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
break;
|
||||
case 'E':
|
||||
Eflg = true;
|
||||
expdate = strtoday (optarg);
|
||||
if (expdate < -1) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid date '%s'\n"),
|
||||
Prog, optarg);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'I':
|
||||
Iflg = true;
|
||||
if ( (getlong (optarg, &inactdays) == 0)
|
||||
|| (inactdays < -1)) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
lflg = true;
|
||||
break;
|
||||
case 'm':
|
||||
mflg = true;
|
||||
if ( (getlong (optarg, &mindays) == 0)
|
||||
|| (mindays < -1)) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
break;
|
||||
case 'M':
|
||||
Mflg = true;
|
||||
if ( (getlong (optarg, &maxdays) == 0)
|
||||
|| (maxdays < -1)) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
case 'W':
|
||||
Wflg = true;
|
||||
if ( (getlong (optarg, &warndays) == 0)
|
||||
|| (warndays < -1)) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
check_flags (argc, optind);
|
||||
}
|
||||
|
||||
/*
|
||||
* check_flags - check flags and parameters consistency
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void check_flags (int argc, int opt_index)
|
||||
{
|
||||
/*
|
||||
* Make certain the flags do not conflict and that there is a user
|
||||
* name on the command line.
|
||||
*/
|
||||
|
||||
if (argc != opt_index + 1) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
if (lflg && (mflg || Mflg || dflg || Wflg || Iflg || Eflg)) {
|
||||
fprintf (stderr,
|
||||
_("%s: do not include \"l\" with other flags\n"),
|
||||
Prog);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_perms - check if the caller is allowed to add a group
|
||||
*
|
||||
* Non-root users are only allowed to display their aging information.
|
||||
* (we will later make sure that the user is only listing her aging
|
||||
* information)
|
||||
*
|
||||
* With PAM support, the setuid bit can be set on chage to allow
|
||||
* non-root users to groups.
|
||||
* Without PAM support, only users who can write in the group databases
|
||||
* can add groups.
|
||||
*
|
||||
* It will not return if the user is not allowed.
|
||||
*/
|
||||
static void check_perms (void)
|
||||
{
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
pam_handle_t *pamh = NULL;
|
||||
struct passwd *pampw;
|
||||
int retval;
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
|
||||
/*
|
||||
* An unprivileged user can ask for their own aging information, but
|
||||
* only root can change it, or list another user's aging
|
||||
* information.
|
||||
*/
|
||||
|
||||
if (!amroot && !lflg) {
|
||||
fprintf (stderr, _("%s: Permission denied.\n"), Prog);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
|
||||
if (NULL == pampw) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot determine your user name.\n"),
|
||||
Prog);
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
|
||||
retval = pam_start ("chage", 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 (E_NOPERM);
|
||||
}
|
||||
(void) pam_end (pamh, retval);
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
}
|
||||
|
||||
/*
|
||||
* open_files - open the shadow database
|
||||
*
|
||||
* In read-only mode, the databases are not locked and are opened
|
||||
* only for reading.
|
||||
*/
|
||||
static void open_files (bool readonly)
|
||||
{
|
||||
/*
|
||||
* Lock and open the password file. This loads all of the password
|
||||
* file entries into memory. Then we get a pointer to the password
|
||||
* file entry for the requested user.
|
||||
*/
|
||||
if (!readonly) {
|
||||
if (pw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, pw_dbname ());
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
pw_locked = true;
|
||||
}
|
||||
if (pw_open (readonly ? O_RDONLY: O_RDWR) == 0) {
|
||||
fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
|
||||
/*
|
||||
* For shadow password files we have to lock the file and read in
|
||||
* the entries as was done for the password file. The user entries
|
||||
* does not have to exist in this case; a new entry will be created
|
||||
* for this user if one does not exist already.
|
||||
*/
|
||||
if (!readonly) {
|
||||
if (spw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, spw_dbname ());
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
spw_locked = true;
|
||||
}
|
||||
if (spw_open (readonly ? O_RDONLY: O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_WARN, "cannot open %s", spw_dbname ()));
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* close_files - close and unlock the password/shadow databases
|
||||
*/
|
||||
static void close_files (void)
|
||||
{
|
||||
/*
|
||||
* Now close the shadow password file, which will cause all of the
|
||||
* entries to be re-written.
|
||||
*/
|
||||
if (spw_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the password file. If any entries were modified, the file
|
||||
* will be re-written.
|
||||
*/
|
||||
if (pw_close () == 0) {
|
||||
fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
spw_locked = false;
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
pw_locked = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* update_age - update the aging information in the database
|
||||
*
|
||||
* It will not return in case of error
|
||||
*/
|
||||
static void update_age (/*@null@*/const struct spwd *sp,
|
||||
/*@notnull@*/const struct passwd *pw)
|
||||
{
|
||||
struct spwd spwent;
|
||||
|
||||
/*
|
||||
* There was no shadow entry. The new entry will have the encrypted
|
||||
* password transferred from the normal password file along with the
|
||||
* aging information.
|
||||
*/
|
||||
if (NULL == sp) {
|
||||
struct passwd pwent = *pw;
|
||||
|
||||
memzero (&spwent, sizeof spwent);
|
||||
spwent.sp_namp = xstrdup (pwent.pw_name);
|
||||
spwent.sp_pwdp = xstrdup (pwent.pw_passwd);
|
||||
spwent.sp_flag = SHADOW_SP_FLAG_UNSET;
|
||||
|
||||
pwent.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
|
||||
if (pw_update (&pwent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"), Prog, pw_dbname (), pwent.pw_name);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
} else {
|
||||
spwent.sp_namp = xstrdup (sp->sp_namp);
|
||||
spwent.sp_pwdp = xstrdup (sp->sp_pwdp);
|
||||
spwent.sp_flag = sp->sp_flag;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy the fields back to the shadow file entry and write the
|
||||
* modified entry back to the shadow file. Closing the shadow and
|
||||
* password files will commit any changes that have been made.
|
||||
*/
|
||||
spwent.sp_max = maxdays;
|
||||
spwent.sp_min = mindays;
|
||||
spwent.sp_lstchg = lstchgdate;
|
||||
spwent.sp_warn = warndays;
|
||||
spwent.sp_inact = inactdays;
|
||||
spwent.sp_expire = expdate;
|
||||
|
||||
if (spw_update (&spwent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"), Prog, spw_dbname (), spwent.sp_namp);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* get_defaults - get the value of the fields not set from the command line
|
||||
*/
|
||||
static void get_defaults (/*@null@*/const struct spwd *sp)
|
||||
{
|
||||
/*
|
||||
* Set the fields that aren't being set from the command line from
|
||||
* the password file.
|
||||
*/
|
||||
if (NULL != sp) {
|
||||
if (!Mflg) {
|
||||
maxdays = sp->sp_max;
|
||||
}
|
||||
if (!mflg) {
|
||||
mindays = sp->sp_min;
|
||||
}
|
||||
if (!dflg) {
|
||||
lstchgdate = sp->sp_lstchg;
|
||||
}
|
||||
if (!Wflg) {
|
||||
warndays = sp->sp_warn;
|
||||
}
|
||||
if (!Iflg) {
|
||||
inactdays = sp->sp_inact;
|
||||
}
|
||||
if (!Eflg) {
|
||||
expdate = sp->sp_expire;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Use default values that will not change the behavior of the
|
||||
* account.
|
||||
*/
|
||||
if (!Mflg) {
|
||||
maxdays = -1;
|
||||
}
|
||||
if (!mflg) {
|
||||
mindays = -1;
|
||||
}
|
||||
if (!dflg) {
|
||||
lstchgdate = -1;
|
||||
}
|
||||
if (!Wflg) {
|
||||
warndays = -1;
|
||||
}
|
||||
if (!Iflg) {
|
||||
inactdays = -1;
|
||||
}
|
||||
if (!Eflg) {
|
||||
expdate = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* chage - change a user's password aging information
|
||||
*
|
||||
* This command controls the password aging information.
|
||||
*
|
||||
* The valid options are
|
||||
*
|
||||
* -d set last password change date (*)
|
||||
* -E set account expiration date (*)
|
||||
* -I set password inactive after expiration (*)
|
||||
* -l show account aging information
|
||||
* -M set maximim number of days before password change (*)
|
||||
* -m set minimum number of days before password change (*)
|
||||
* -W set expiration warning days (*)
|
||||
*
|
||||
* (*) requires root permission to execute.
|
||||
*
|
||||
* All of the time fields are entered in the internal format which is
|
||||
* either seconds or days.
|
||||
*/
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
const struct spwd *sp;
|
||||
uid_t ruid;
|
||||
gid_t rgid;
|
||||
const struct passwd *pw;
|
||||
|
||||
/*
|
||||
* Get the program name so that error messages can use it.
|
||||
*/
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
sanitize_env ();
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
#ifdef WITH_AUDIT
|
||||
audit_help_open ();
|
||||
#endif
|
||||
OPENLOG ("chage");
|
||||
|
||||
ruid = getuid ();
|
||||
rgid = getgid ();
|
||||
amroot = (ruid == 0);
|
||||
#ifdef WITH_SELINUX
|
||||
if (amroot && (is_selinux_enabled () > 0)) {
|
||||
amroot = (selinux_check_passwd_access (PASSWD__ROOTOK) == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
check_perms ();
|
||||
|
||||
if (!spw_file_present ()) {
|
||||
fprintf (stderr,
|
||||
_("%s: the shadow password file is not present\n"),
|
||||
Prog);
|
||||
SYSLOG ((LOG_WARN, "can't find the shadow password file"));
|
||||
closelog ();
|
||||
exit (E_SHADOW_NOTFOUND);
|
||||
}
|
||||
|
||||
open_files (lflg);
|
||||
/* Drop privileges */
|
||||
if (lflg && ( (setregid (rgid, rgid) != 0)
|
||||
|| (setreuid (ruid, ruid) != 0))) {
|
||||
fprintf (stderr, _("%s: failed to drop privileges (%s)\n"),
|
||||
Prog, strerror (errno));
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
|
||||
pw = pw_locate (argv[optind]);
|
||||
if (NULL == pw) {
|
||||
fprintf (stderr, _("%s: user '%s' does not exist in %s\n"),
|
||||
Prog, argv[optind], pw_dbname ());
|
||||
closelog ();
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
|
||||
STRFCPY (user_name, pw->pw_name);
|
||||
#ifdef WITH_TCB
|
||||
if (shadowtcb_set_user (pw->pw_name) == SHADOWTCB_FAILURE) {
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
#endif
|
||||
user_uid = pw->pw_uid;
|
||||
|
||||
sp = spw_locate (argv[optind]);
|
||||
get_defaults (sp);
|
||||
|
||||
/*
|
||||
* Print out the expiration fields if the user has requested the
|
||||
* list option.
|
||||
*/
|
||||
if (lflg) {
|
||||
if (!amroot && (ruid != user_uid)) {
|
||||
fprintf (stderr, _("%s: Permission denied.\n"), Prog);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
|
||||
"display aging info",
|
||||
user_name, (unsigned int) user_uid, 1);
|
||||
#endif
|
||||
list_fields ();
|
||||
fail_exit (E_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* If none of the fields were changed from the command line, let the
|
||||
* user interactively change them.
|
||||
*/
|
||||
if (!mflg && !Mflg && !dflg && !Wflg && !Iflg && !Eflg) {
|
||||
printf (_("Changing the aging information for %s\n"),
|
||||
user_name);
|
||||
if (new_fields () == 0) {
|
||||
fprintf (stderr, _("%s: error changing fields\n"),
|
||||
Prog);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
#ifdef WITH_AUDIT
|
||||
else {
|
||||
audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
|
||||
"change all aging information",
|
||||
user_name, (unsigned int) user_uid, 1);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#ifdef WITH_AUDIT
|
||||
if (Mflg) {
|
||||
audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
|
||||
"change max age",
|
||||
user_name, (unsigned int) user_uid, 1);
|
||||
}
|
||||
if (mflg) {
|
||||
audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
|
||||
"change min age",
|
||||
user_name, (unsigned int) user_uid, 1);
|
||||
}
|
||||
if (dflg) {
|
||||
audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
|
||||
"change last change date",
|
||||
user_name, (unsigned int) user_uid, 1);
|
||||
}
|
||||
if (Wflg) {
|
||||
audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
|
||||
"change passwd warning",
|
||||
user_name, (unsigned int) user_uid, 1);
|
||||
}
|
||||
if (Iflg) {
|
||||
audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
|
||||
"change inactive days",
|
||||
user_name, (unsigned int) user_uid, 1);
|
||||
}
|
||||
if (Eflg) {
|
||||
audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
|
||||
"change passwd expiration",
|
||||
user_name, (unsigned int) user_uid, 1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
update_age (sp, pw);
|
||||
|
||||
close_files ();
|
||||
|
||||
SYSLOG ((LOG_INFO, "changed password expiry for %s", user_name));
|
||||
|
||||
closelog ();
|
||||
exit (E_SUCCESS);
|
||||
}
|
||||
|
||||
753
src/chfn.c
Normal file
753
src/chfn.c
Normal file
@@ -0,0 +1,753 @@
|
||||
/*
|
||||
* Copyright (c) 1989 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 - 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>
|
||||
|
||||
#ident "$Id: chfn.c 3576 2011-11-13 16:24:57Z nekral-guest $"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <getopt.h>
|
||||
#ifdef WITH_SELINUX
|
||||
#include <selinux/selinux.h>
|
||||
#include <selinux/av_permissions.h>
|
||||
#endif
|
||||
#include "defines.h"
|
||||
#include "getdef.h"
|
||||
#include "nscd.h"
|
||||
#ifdef USE_PAM
|
||||
#include "pam_defs.h"
|
||||
#endif
|
||||
#include "prototypes.h"
|
||||
#include "pwauth.h"
|
||||
#include "pwio.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/*
|
||||
* Global variables.
|
||||
*/
|
||||
const char *Prog;
|
||||
static char fullnm[BUFSIZ];
|
||||
static char roomno[BUFSIZ];
|
||||
static char workph[BUFSIZ];
|
||||
static char homeph[BUFSIZ];
|
||||
static char slop[BUFSIZ];
|
||||
static bool amroot;
|
||||
/* Flags */
|
||||
static bool fflg = false; /* -f - set full name */
|
||||
static bool rflg = false; /* -r - set room number */
|
||||
static bool wflg = false; /* -w - set work phone number */
|
||||
static bool hflg = false; /* -h - set home phone number */
|
||||
static bool oflg = false; /* -o - set other information */
|
||||
static bool pw_locked = false;
|
||||
|
||||
/*
|
||||
* External identifiers
|
||||
*/
|
||||
|
||||
/* local function prototypes */
|
||||
static void fail_exit (int code);
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static bool may_change_field (int);
|
||||
static void new_fields (void);
|
||||
static char *copy_field (char *, char *, char *);
|
||||
static void process_flags (int argc, char **argv);
|
||||
static void check_perms (const struct passwd *pw);
|
||||
static void update_gecos (const char *user, char *gecos);
|
||||
static void get_old_fields (const char *gecos);
|
||||
|
||||
/*
|
||||
* fail_exit - exit with an error and do some cleanup
|
||||
*/
|
||||
static void fail_exit (int code)
|
||||
{
|
||||
if (pw_locked) {
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
pw_locked = false;
|
||||
|
||||
closelog ();
|
||||
|
||||
exit (code);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage - print command line syntax and exit
|
||||
*/
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options] [LOGIN]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(void) fputs (_(" -f, --full-name FULL_NAME change user's full name\n"), usageout);
|
||||
(void) fputs (_(" -h, --home-phone HOME_PHONE change user's home phone number\n"), usageout);
|
||||
(void) fputs (_(" -o, --other OTHER_INFO change user's other GECOS information\n"), usageout);
|
||||
(void) fputs (_(" -r, --room ROOM_NUMBER change user's room number\n"), usageout);
|
||||
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
(void) fputs (_(" -u, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs (_(" -w, --work-phone WORK_PHONE change user's office phone number\n"), usageout);
|
||||
(void) fputs ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* may_change_field - indicate if the user is allowed to change a given field
|
||||
* of her gecos information
|
||||
*
|
||||
* root can change any field.
|
||||
*
|
||||
* field should be one of 'f', 'r', 'w', 'h'
|
||||
*
|
||||
* Return true if the user can change the field and false otherwise.
|
||||
*/
|
||||
static bool may_change_field (int field)
|
||||
{
|
||||
const char *cp;
|
||||
|
||||
/*
|
||||
* CHFN_RESTRICT can now specify exactly which fields may be changed
|
||||
* by regular users, by using any combination of the following
|
||||
* letters:
|
||||
* f - full name
|
||||
* r - room number
|
||||
* w - work phone
|
||||
* h - home phone
|
||||
*
|
||||
* This makes it possible to disallow changing the room number
|
||||
* information, for example - this feature was suggested by Maciej
|
||||
* 'Tycoon' Majchrowski.
|
||||
*
|
||||
* For backward compatibility, "yes" is equivalent to "rwh",
|
||||
* "no" is equivalent to "frwh". Only root can change anything
|
||||
* if the string is empty or not defined at all.
|
||||
*/
|
||||
if (amroot) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cp = getdef_str ("CHFN_RESTRICT");
|
||||
if (NULL == cp) {
|
||||
cp = "";
|
||||
} else if (strcmp (cp, "yes") == 0) {
|
||||
cp = "rwh";
|
||||
} else if (strcmp (cp, "no") == 0) {
|
||||
cp = "frwh";
|
||||
}
|
||||
|
||||
if (strchr (cp, field) != NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* new_fields - change the user's GECOS information interactively
|
||||
*
|
||||
* prompt the user for each of the four fields and fill in the fields from
|
||||
* the user's response, or leave alone if nothing was entered.
|
||||
*/
|
||||
static void new_fields (void)
|
||||
{
|
||||
puts (_("Enter the new value, or press ENTER for the default"));
|
||||
|
||||
if (may_change_field ('f')) {
|
||||
change_field (fullnm, sizeof fullnm, _("Full Name"));
|
||||
} else {
|
||||
printf (_("\t%s: %s\n"), _("Full Name"), fullnm);
|
||||
}
|
||||
|
||||
if (may_change_field ('r')) {
|
||||
change_field (roomno, sizeof roomno, _("Room Number"));
|
||||
} else {
|
||||
printf (_("\t%s: %s\n"), _("Room Number"), fullnm);
|
||||
}
|
||||
|
||||
if (may_change_field ('w')) {
|
||||
change_field (workph, sizeof workph, _("Work Phone"));
|
||||
} else {
|
||||
printf (_("\t%s: %s\n"), _("Work Phone"), fullnm);
|
||||
}
|
||||
|
||||
if (may_change_field ('h')) {
|
||||
change_field (homeph, sizeof homeph, _("Home Phone"));
|
||||
} else {
|
||||
printf (_("\t%s: %s\n"), _("Home Phone"), fullnm);
|
||||
}
|
||||
|
||||
if (amroot) {
|
||||
change_field (slop, sizeof slop, _("Other"));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* copy_field - get the next field from the gecos field
|
||||
*
|
||||
* copy_field copies the next field from the gecos field, returning a
|
||||
* pointer to the field which follows, or NULL if there are no more fields.
|
||||
*
|
||||
* in - the current GECOS field
|
||||
* out - where to copy the field to
|
||||
* extra - fields with '=' get copied here
|
||||
*/
|
||||
static char *copy_field (char *in, char *out, char *extra)
|
||||
{
|
||||
char *cp = NULL;
|
||||
|
||||
while (NULL != in) {
|
||||
cp = strchr (in, ',');
|
||||
if (NULL != cp) {
|
||||
*cp++ = '\0';
|
||||
}
|
||||
|
||||
if (strchr (in, '=') == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (NULL != extra) {
|
||||
if ('\0' != extra[0]) {
|
||||
strcat (extra, ",");
|
||||
}
|
||||
|
||||
strcat (extra, in);
|
||||
}
|
||||
in = cp;
|
||||
}
|
||||
if ((NULL != in) && (NULL != out)) {
|
||||
strcpy (out, in);
|
||||
}
|
||||
|
||||
return cp;
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
int c; /* flag currently being processed */
|
||||
static struct option long_options[] = {
|
||||
{"full-name", required_argument, NULL, 'f'},
|
||||
{"home-phone", required_argument, NULL, 'h'},
|
||||
{"other", required_argument, NULL, 'o'},
|
||||
{"room", required_argument, NULL, 'r'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{"help", no_argument, NULL, 'u'},
|
||||
{"work-phone", required_argument, NULL, 'w'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
/*
|
||||
* The remaining arguments will be processed one by one and executed
|
||||
* by this command. The name is the last argument if it does not
|
||||
* begin with a "-", otherwise the name is determined from the
|
||||
* environment and must agree with the real UID. Also, the UID will
|
||||
* be checked for any commands which are restricted to root only.
|
||||
*/
|
||||
while ((c = getopt_long (argc, argv, "f:h:o:r:R:uw:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'f':
|
||||
if (!may_change_field ('f')) {
|
||||
fprintf (stderr,
|
||||
_("%s: Permission denied.\n"), Prog);
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
fflg = true;
|
||||
STRFCPY (fullnm, optarg);
|
||||
break;
|
||||
case 'h':
|
||||
if (!may_change_field ('h')) {
|
||||
fprintf (stderr,
|
||||
_("%s: Permission denied.\n"), Prog);
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
hflg = true;
|
||||
STRFCPY (homeph, optarg);
|
||||
break;
|
||||
case 'o':
|
||||
if (!amroot) {
|
||||
fprintf (stderr,
|
||||
_("%s: Permission denied.\n"), Prog);
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
oflg = true;
|
||||
STRFCPY (slop, optarg);
|
||||
break;
|
||||
case 'r':
|
||||
if (!may_change_field ('r')) {
|
||||
fprintf (stderr,
|
||||
_("%s: Permission denied.\n"), Prog);
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
rflg = true;
|
||||
STRFCPY (roomno, optarg);
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
case 'u':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'w':
|
||||
if (!may_change_field ('w')) {
|
||||
fprintf (stderr,
|
||||
_("%s: Permission denied.\n"), Prog);
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
wflg = true;
|
||||
STRFCPY (workph, optarg);
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_perms - check if the caller is allowed to add a group
|
||||
*
|
||||
* Non-root users are only allowed to change their gecos field.
|
||||
* (see also may_change_field())
|
||||
*
|
||||
* Non-root users must be authenticated.
|
||||
*
|
||||
* It will not return if the user is not allowed.
|
||||
*/
|
||||
static void check_perms (const struct passwd *pw)
|
||||
{
|
||||
#ifdef USE_PAM
|
||||
pam_handle_t *pamh = NULL;
|
||||
int retval;
|
||||
struct passwd *pampw;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Non-privileged users are only allowed to change the gecos field
|
||||
* if the UID of the user matches the current real UID.
|
||||
*/
|
||||
if (!amroot && pw->pw_uid != getuid ()) {
|
||||
fprintf (stderr, _("%s: Permission denied.\n"), Prog);
|
||||
closelog ();
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
#ifdef WITH_SELINUX
|
||||
/*
|
||||
* If the UID of the user does not match the current real UID,
|
||||
* check if the change is allowed by SELinux policy.
|
||||
*/
|
||||
if ((pw->pw_uid != getuid ())
|
||||
&& (is_selinux_enabled () > 0)
|
||||
&& (selinux_check_passwd_access (PASSWD__CHFN) != 0)) {
|
||||
fprintf (stderr, _("%s: Permission denied.\n"), Prog);
|
||||
closelog ();
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef USE_PAM
|
||||
/*
|
||||
* Non-privileged users are optionally authenticated (must enter the
|
||||
* password of the user whose information is being changed) before
|
||||
* any changes can be made. Idea from util-linux chfn/chsh.
|
||||
* --marekm
|
||||
*/
|
||||
if (!amroot && getdef_bool ("CHFN_AUTH")) {
|
||||
passwd_check (pw->pw_name, pw->pw_passwd, "chfn");
|
||||
}
|
||||
|
||||
#else /* !USE_PAM */
|
||||
pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
|
||||
if (NULL == pampw) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot determine your user name.\n"),
|
||||
Prog);
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
|
||||
retval = pam_start ("chfn", 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);
|
||||
}
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
(void) pam_end (pamh, retval);
|
||||
#endif /* USE_PAM */
|
||||
}
|
||||
|
||||
/*
|
||||
* update_gecos - update the gecos fields in the password database
|
||||
*
|
||||
* Commit the user's entry after changing her gecos field.
|
||||
*/
|
||||
static void update_gecos (const char *user, char *gecos)
|
||||
{
|
||||
const struct passwd *pw; /* The user's password file entry */
|
||||
struct passwd pwent; /* modified password file entry */
|
||||
|
||||
/*
|
||||
* Before going any further, raise the ulimit to prevent colliding
|
||||
* into a lowered ulimit, and set the real UID to root to protect
|
||||
* against unexpected signals. Any keyboard signals are set to be
|
||||
* ignored.
|
||||
*/
|
||||
if (setuid (0) != 0) {
|
||||
fputs (_("Cannot change ID to root.\n"), stderr);
|
||||
SYSLOG ((LOG_ERR, "can't setuid(0)"));
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
pwd_init ();
|
||||
|
||||
/*
|
||||
* The passwd entry is now ready to be committed back to the
|
||||
* password file. Get a lock on the file and open it.
|
||||
*/
|
||||
if (pw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, pw_dbname ());
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
pw_locked = true;
|
||||
if (pw_open (O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"), Prog, pw_dbname ());
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the entry to update using pw_locate() - we want the real one
|
||||
* from /etc/passwd, not the one from getpwnam() which could contain
|
||||
* the shadow password if (despite the warnings) someone enables
|
||||
* AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm
|
||||
*/
|
||||
pw = pw_locate (user);
|
||||
if (NULL == pw) {
|
||||
fprintf (stderr,
|
||||
_("%s: user '%s' does not exist in %s\n"),
|
||||
Prog, user, pw_dbname ());
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a copy of the entry, then change the gecos field. The other
|
||||
* fields remain unchanged.
|
||||
*/
|
||||
pwent = *pw;
|
||||
pwent.pw_gecos = gecos;
|
||||
|
||||
/*
|
||||
* Update the passwd file entry. If there is a DBM file, update that
|
||||
* entry as well.
|
||||
*/
|
||||
if (pw_update (&pwent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, pw_dbname (), pwent.pw_name);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
|
||||
/*
|
||||
* Changes have all been made, so commit them and unlock the file.
|
||||
*/
|
||||
if (pw_close () == 0) {
|
||||
fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
pw_locked = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_old_fields - parse the old gecos and use the old value for the fields
|
||||
* which are not set on the command line
|
||||
*/
|
||||
static void get_old_fields (const char *gecos)
|
||||
{
|
||||
char *cp; /* temporary character pointer */
|
||||
char old_gecos[BUFSIZ]; /* buffer for old GECOS fields */
|
||||
STRFCPY (old_gecos, gecos);
|
||||
|
||||
/*
|
||||
* Now get the full name. It is the first comma separated field in
|
||||
* the GECOS field.
|
||||
*/
|
||||
cp = copy_field (old_gecos, fflg ? (char *) 0 : fullnm, slop);
|
||||
|
||||
/*
|
||||
* Now get the room number. It is the next comma separated field,
|
||||
* if there is indeed one.
|
||||
*/
|
||||
if (NULL != cp) {
|
||||
cp = copy_field (cp, rflg ? (char *) 0 : roomno, slop);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now get the work phone number. It is the third field.
|
||||
*/
|
||||
if (NULL != cp) {
|
||||
cp = copy_field (cp, wflg ? (char *) 0 : workph, slop);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now get the home phone number. It is the fourth field.
|
||||
*/
|
||||
if (NULL != cp) {
|
||||
cp = copy_field (cp, hflg ? (char *) 0 : homeph, slop);
|
||||
}
|
||||
|
||||
/*
|
||||
* Anything left over is "slop".
|
||||
*/
|
||||
if ((NULL != cp) && !oflg) {
|
||||
if ('\0' != slop[0]) {
|
||||
strcat (slop, ",");
|
||||
}
|
||||
|
||||
strcat (slop, cp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_fields - check all of the fields for valid information
|
||||
*
|
||||
* It will not return if a field is not valid.
|
||||
*/
|
||||
static void check_fields (void)
|
||||
{
|
||||
int err;
|
||||
err = valid_field (fullnm, ":,=\n");
|
||||
if (err > 0) {
|
||||
fprintf (stderr, _("%s: name with non-ASCII characters: '%s'\n"), Prog, fullnm);
|
||||
} else if (err < 0) {
|
||||
fprintf (stderr, _("%s: invalid name: '%s'\n"), Prog, fullnm);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
err = valid_field (roomno, ":,=\n");
|
||||
if (err > 0) {
|
||||
fprintf (stderr, _("%s: room number with non-ASCII characters: '%s'\n"), Prog, roomno);
|
||||
} else if (err < 0) {
|
||||
fprintf (stderr, _("%s: invalid room number: '%s'\n"),
|
||||
Prog, roomno);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
if (valid_field (workph, ":,=\n") != 0) {
|
||||
fprintf (stderr, _("%s: invalid work phone: '%s'\n"),
|
||||
Prog, workph);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
if (valid_field (homeph, ":,=\n") != 0) {
|
||||
fprintf (stderr, _("%s: invalid home phone: '%s'\n"),
|
||||
Prog, homeph);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
err = valid_field (slop, ":\n");
|
||||
if (err > 0) {
|
||||
fprintf (stderr, _("%s: '%s' contains non-ASCII characters\n"), Prog, slop);
|
||||
} else if (err < 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: '%s' contains illegal characters\n"),
|
||||
Prog, slop);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* chfn - change a user's password file information
|
||||
*
|
||||
* This command controls the GECOS field information in the password
|
||||
* file entry.
|
||||
*
|
||||
* The valid options are
|
||||
*
|
||||
* -f full name
|
||||
* -r room number
|
||||
* -w work phone number
|
||||
* -h home phone number
|
||||
* -o other information (*)
|
||||
*
|
||||
* (*) requires root permission to execute.
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
const struct passwd *pw; /* password file entry */
|
||||
char new_gecos[BUFSIZ]; /* buffer for new GECOS fields */
|
||||
char *user;
|
||||
|
||||
/*
|
||||
* Get the program name. The program name is used as a
|
||||
* prefix to most error messages.
|
||||
*/
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
sanitize_env ();
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
/*
|
||||
* This command behaves different for root and non-root
|
||||
* users.
|
||||
*/
|
||||
amroot = (getuid () == 0);
|
||||
|
||||
OPENLOG ("chfn");
|
||||
|
||||
/* parse the command line options */
|
||||
process_flags (argc, argv);
|
||||
|
||||
/*
|
||||
* Get the name of the user to check. It is either the command line
|
||||
* name, or the name getlogin() returns.
|
||||
*/
|
||||
if (optind < argc) {
|
||||
user = argv[optind];
|
||||
pw = xgetpwnam (user);
|
||||
if (NULL == pw) {
|
||||
fprintf (stderr, _("%s: user '%s' does not exist\n"), Prog,
|
||||
user);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
} else {
|
||||
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 ()));
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
user = xstrdup (pw->pw_name);
|
||||
}
|
||||
|
||||
#ifdef USE_NIS
|
||||
/*
|
||||
* Now we make sure this is a LOCAL password entry for this user ...
|
||||
*/
|
||||
if (__ispwNIS ()) {
|
||||
char *nis_domain;
|
||||
char *nis_master;
|
||||
|
||||
fprintf (stderr,
|
||||
_("%s: cannot change user '%s' on NIS client.\n"),
|
||||
Prog, user);
|
||||
|
||||
if (!yp_get_default_domain (&nis_domain) &&
|
||||
!yp_master (nis_domain, "passwd.byname", &nis_master)) {
|
||||
fprintf (stderr,
|
||||
_
|
||||
("%s: '%s' is the NIS master for this client.\n"),
|
||||
Prog, nis_master);
|
||||
}
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Check that the caller is allowed to change the gecos of the
|
||||
* specified user */
|
||||
check_perms (pw);
|
||||
|
||||
/* If some fields were not set on the command line, load the value from
|
||||
* the old gecos fields. */
|
||||
get_old_fields (pw->pw_gecos);
|
||||
|
||||
/*
|
||||
* If none of the fields were changed from the command line, let the
|
||||
* user interactively change them.
|
||||
*/
|
||||
if (!fflg && !rflg && !wflg && !hflg && !oflg) {
|
||||
printf (_("Changing the user information for %s\n"), user);
|
||||
new_fields ();
|
||||
}
|
||||
|
||||
/*
|
||||
* Check all of the fields for valid information
|
||||
*/
|
||||
check_fields ();
|
||||
|
||||
/*
|
||||
* Build the new GECOS field by plastering all the pieces together,
|
||||
* if they will fit ...
|
||||
*/
|
||||
if ((strlen (fullnm) + strlen (roomno) + strlen (workph) +
|
||||
strlen (homeph) + strlen (slop)) > (unsigned int) 80) {
|
||||
fprintf (stderr, _("%s: fields too long\n"), Prog);
|
||||
fail_exit (E_NOPERM);
|
||||
}
|
||||
snprintf (new_gecos, sizeof new_gecos, "%s,%s,%s,%s%s%s",
|
||||
fullnm, roomno, workph, homeph,
|
||||
('\0' != slop[0]) ? "," : "", slop);
|
||||
|
||||
/* Rewrite the user's gecos in the passwd file */
|
||||
update_gecos (user, new_gecos);
|
||||
|
||||
SYSLOG ((LOG_INFO, "changed user '%s' information", user));
|
||||
|
||||
nscd_flush_cache ("passwd");
|
||||
|
||||
closelog ();
|
||||
exit (E_SUCCESS);
|
||||
}
|
||||
|
||||
580
src/chgpasswd.c
Normal file
580
src/chgpasswd.c
Normal file
@@ -0,0 +1,580 @@
|
||||
/*
|
||||
* Copyright (c) 1990 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 2006 , Tomasz Kłoczko
|
||||
* Copyright (c) 2006 , Jonas Meurer
|
||||
* 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>
|
||||
|
||||
#ident "$Id: chgpasswd.c 3652 2011-12-09 21:31:39Z nekral-guest $"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
#include "pam_defs.h"
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
#include "defines.h"
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
#include "groupio.h"
|
||||
#ifdef SHADOWGRP
|
||||
#include "sgroupio.h"
|
||||
#endif
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
static bool eflg = false;
|
||||
static bool md5flg = false;
|
||||
#ifdef USE_SHA_CRYPT
|
||||
static bool sflg = false;
|
||||
#endif
|
||||
|
||||
static /*@null@*//*@observer@*/const char *crypt_method = NULL;
|
||||
#define cflg (NULL != crypt_method)
|
||||
#ifdef USE_SHA_CRYPT
|
||||
static long sha_rounds = 5000;
|
||||
#endif
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
static bool is_shadow_grp;
|
||||
static bool sgr_locked = false;
|
||||
#endif
|
||||
static bool gr_locked = false;
|
||||
|
||||
/* local function prototypes */
|
||||
static void fail_exit (int code);
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void process_flags (int argc, char **argv);
|
||||
static void check_flags (void);
|
||||
static void check_perms (void);
|
||||
static void open_files (void);
|
||||
static void close_files (void);
|
||||
|
||||
/*
|
||||
* fail_exit - exit with a failure code after unlocking the 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 ()));
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage - display usage message and exit
|
||||
*/
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(void) fprintf (usageout,
|
||||
_(" -c, --crypt-method METHOD the crypt method (one of %s)\n"),
|
||||
#ifndef USE_SHA_CRYPT
|
||||
"NONE DES MD5"
|
||||
#else /* USE_SHA_CRYPT */
|
||||
"NONE DES MD5 SHA256 SHA512"
|
||||
#endif /* USE_SHA_CRYPT */
|
||||
);
|
||||
(void) fputs (_(" -e, --encrypted supplied passwords are encrypted\n"), usageout);
|
||||
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs (_(" -m, --md5 encrypt the clear text password using\n"
|
||||
" the MD5 algorithm\n"),
|
||||
usageout);
|
||||
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
#ifdef USE_SHA_CRYPT
|
||||
(void) fputs (_(" -s, --sha-rounds number of SHA rounds for the SHA*\n"
|
||||
" crypt algorithms\n"),
|
||||
usageout);
|
||||
#endif /* USE_SHA_CRYPT */
|
||||
(void) fputs ("\n", usageout);
|
||||
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"crypt-method", required_argument, NULL, 'c'},
|
||||
{"encrypted", no_argument, NULL, 'e'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"md5", no_argument, NULL, 'm'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
#ifdef USE_SHA_CRYPT
|
||||
{"sha-rounds", required_argument, NULL, 's'},
|
||||
#endif
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv,
|
||||
#ifdef USE_SHA_CRYPT
|
||||
"c:ehmR:s:",
|
||||
#else
|
||||
"c:ehmR:",
|
||||
#endif
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
crypt_method = optarg;
|
||||
break;
|
||||
case 'e':
|
||||
eflg = true;
|
||||
break;
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'm':
|
||||
md5flg = true;
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
#ifdef USE_SHA_CRYPT
|
||||
case 's':
|
||||
sflg = true;
|
||||
if (getlong(optarg, &sha_rounds) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
/*@notreached@*/break;
|
||||
}
|
||||
}
|
||||
|
||||
/* validate options */
|
||||
check_flags ();
|
||||
}
|
||||
|
||||
/*
|
||||
* check_flags - check flags and parameters consistency
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void check_flags (void)
|
||||
{
|
||||
#ifdef USE_SHA_CRYPT
|
||||
if (sflg && !cflg) {
|
||||
fprintf (stderr,
|
||||
_("%s: %s flag is only allowed with the %s flag\n"),
|
||||
Prog, "-s", "-c");
|
||||
usage (E_USAGE);
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((eflg && (md5flg || cflg)) ||
|
||||
(md5flg && cflg)) {
|
||||
fprintf (stderr,
|
||||
_("%s: the -c, -e, and -m flags are exclusive\n"),
|
||||
Prog);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
if (cflg) {
|
||||
if ( (0 != strcmp (crypt_method, "DES"))
|
||||
&& (0 != strcmp (crypt_method, "MD5"))
|
||||
&& (0 != strcmp (crypt_method, "NONE"))
|
||||
#ifdef USE_SHA_CRYPT
|
||||
&& (0 != strcmp (crypt_method, "SHA256"))
|
||||
&& (0 != strcmp (crypt_method, "SHA512"))
|
||||
#endif
|
||||
) {
|
||||
fprintf (stderr,
|
||||
_("%s: unsupported crypt method: %s\n"),
|
||||
Prog, crypt_method);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_perms - check if the caller is allowed to add a group
|
||||
*
|
||||
* With PAM support, the setuid bit can be set on chgpasswd to allow
|
||||
* non-root users to groups.
|
||||
* Without PAM support, only users who can write in the group databases
|
||||
* can add groups.
|
||||
*
|
||||
* It will not return if the user is not allowed.
|
||||
*/
|
||||
static void check_perms (void)
|
||||
{
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
pam_handle_t *pamh = NULL;
|
||||
int retval;
|
||||
struct passwd *pampw;
|
||||
|
||||
pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
|
||||
if (NULL == pampw) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot determine your user name.\n"),
|
||||
Prog);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
retval = pam_start ("chgpasswd", 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);
|
||||
}
|
||||
exit (1);
|
||||
}
|
||||
(void) pam_end (pamh, retval);
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
}
|
||||
|
||||
/*
|
||||
* open_files - lock and open the group databases
|
||||
*/
|
||||
static void open_files (void)
|
||||
{
|
||||
/*
|
||||
* Lock the group file and open it for reading and writing. This will
|
||||
* bring all of the entries into memory where they may be updated.
|
||||
*/
|
||||
if (gr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, gr_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
gr_locked = true;
|
||||
if (gr_open (O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"), Prog, gr_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
/* Do the same for the shadowed database, if it exist */
|
||||
if (is_shadow_grp) {
|
||||
if (sgr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, sgr_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
sgr_locked = true;
|
||||
if (sgr_open (O_RDWR) == 0) {
|
||||
fprintf (stderr, _("%s: cannot open %s\n"),
|
||||
Prog, sgr_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* close_files - close and unlock the group databases
|
||||
*/
|
||||
static void close_files (void)
|
||||
{
|
||||
#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 (1);
|
||||
}
|
||||
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
|
||||
|
||||
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 (1);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char *name;
|
||||
char *newpwd;
|
||||
char *cp;
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
const struct sgrp *sg;
|
||||
struct sgrp newsg;
|
||||
#endif
|
||||
|
||||
const struct group *gr;
|
||||
struct group newgr;
|
||||
int errors = 0;
|
||||
int line = 0;
|
||||
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
OPENLOG ("chgpasswd");
|
||||
|
||||
check_perms ();
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
is_shadow_grp = sgr_file_present ();
|
||||
#endif
|
||||
|
||||
open_files ();
|
||||
|
||||
/*
|
||||
* Read each line, separating the group name from the password. The
|
||||
* group entry for each group will be looked up in the appropriate
|
||||
* file (gshadow or group) and the password changed.
|
||||
*/
|
||||
while (fgets (buf, (int) sizeof buf, stdin) != (char *) 0) {
|
||||
line++;
|
||||
cp = strrchr (buf, '\n');
|
||||
if (NULL != cp) {
|
||||
*cp = '\0';
|
||||
} else {
|
||||
fprintf (stderr, _("%s: line %d: line too long\n"),
|
||||
Prog, line);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* The group's name is the first field. It is separated from
|
||||
* the password with a ":" character which is replaced with a
|
||||
* NUL to give the new password. The new password will then
|
||||
* be encrypted in the normal fashion with a new salt
|
||||
* generated, unless the '-e' is given, in which case it is
|
||||
* assumed to already be encrypted.
|
||||
*/
|
||||
|
||||
name = buf;
|
||||
cp = strchr (name, ':');
|
||||
if (NULL != cp) {
|
||||
*cp = '\0';
|
||||
cp++;
|
||||
} else {
|
||||
fprintf (stderr,
|
||||
_("%s: line %d: missing new password\n"),
|
||||
Prog, line);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
newpwd = cp;
|
||||
if ( (!eflg)
|
||||
&& ( (NULL == crypt_method)
|
||||
|| (0 != strcmp (crypt_method, "NONE")))) {
|
||||
void *arg = NULL;
|
||||
if (md5flg) {
|
||||
crypt_method = "MD5";
|
||||
}
|
||||
#ifdef USE_SHA_CRYPT
|
||||
if (sflg) {
|
||||
arg = &sha_rounds;
|
||||
}
|
||||
#endif
|
||||
cp = pw_encrypt (newpwd,
|
||||
crypt_make_salt (crypt_method, arg));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the group file entry for this group. The group must
|
||||
* already exist.
|
||||
*/
|
||||
gr = gr_locate (name);
|
||||
if (NULL == gr) {
|
||||
fprintf (stderr,
|
||||
_("%s: line %d: group '%s' does not exist\n"), Prog,
|
||||
line, name);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
#ifdef SHADOWGRP
|
||||
if (is_shadow_grp) {
|
||||
/* The gshadow entry should be updated if the
|
||||
* group entry has a password set to 'x'.
|
||||
* But on the other hand, if there is already both
|
||||
* a group and a gshadow password, it's preferable
|
||||
* to update both.
|
||||
*/
|
||||
sg = sgr_locate (name);
|
||||
|
||||
if ( (NULL == sg)
|
||||
&& (strcmp (gr->gr_passwd,
|
||||
SHADOW_PASSWD_STRING) == 0)) {
|
||||
static char *empty = NULL;
|
||||
/* If the password is set to 'x' in
|
||||
* group, but there are no entries in
|
||||
* gshadow, create one.
|
||||
*/
|
||||
newsg.sg_name = name;
|
||||
/* newsg.sg_passwd = NULL; will be set later */
|
||||
newsg.sg_adm = ∅
|
||||
newsg.sg_mem = dup_list (gr->gr_mem);
|
||||
sg = &newsg;
|
||||
}
|
||||
} else {
|
||||
sg = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The freshly encrypted new password is merged into the
|
||||
* group's entry.
|
||||
*/
|
||||
#ifdef SHADOWGRP
|
||||
if (NULL != sg) {
|
||||
newsg = *sg;
|
||||
newsg.sg_passwd = cp;
|
||||
}
|
||||
if ( (NULL == sg)
|
||||
|| (strcmp (gr->gr_passwd, SHADOW_PASSWD_STRING) != 0))
|
||||
#endif
|
||||
{
|
||||
newgr = *gr;
|
||||
newgr.gr_passwd = cp;
|
||||
}
|
||||
|
||||
/*
|
||||
* The updated group file entry is then put back and will
|
||||
* be written to the group file later, after all the
|
||||
* other entries have been updated as well.
|
||||
*/
|
||||
#ifdef SHADOWGRP
|
||||
if (NULL != sg) {
|
||||
if (sgr_update (&newsg) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: line %d: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, line, sgr_dbname (), newsg.sg_name);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ( (NULL == sg)
|
||||
|| (strcmp (gr->gr_passwd, SHADOW_PASSWD_STRING) != 0))
|
||||
#endif
|
||||
{
|
||||
if (gr_update (&newgr) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: line %d: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, line, gr_dbname (), newgr.gr_name);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Any detected errors will cause the entire set of changes to be
|
||||
* aborted. Unlocking the group file will cause all of the
|
||||
* changes to be ignored. Otherwise the file is closed, causing the
|
||||
* changes to be written out all at once, and then unlocked
|
||||
* afterwards.
|
||||
*/
|
||||
if (0 != errors) {
|
||||
fprintf (stderr,
|
||||
_("%s: error detected, changes ignored\n"), Prog);
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
close_files ();
|
||||
|
||||
nscd_flush_cache ("group");
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
623
src/chpasswd.c
Normal file
623
src/chpasswd.c
Normal file
@@ -0,0 +1,623 @@
|
||||
/*
|
||||
* Copyright (c) 1990 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* 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>
|
||||
|
||||
#ident "$Id: chpasswd.c 3652 2011-12-09 21:31:39Z nekral-guest $"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef USE_PAM
|
||||
#include "pam_defs.h"
|
||||
#endif /* USE_PAM */
|
||||
#include "defines.h"
|
||||
#include "nscd.h"
|
||||
#include "getdef.h"
|
||||
#include "prototypes.h"
|
||||
#include "pwio.h"
|
||||
#include "shadowio.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
static bool eflg = false;
|
||||
static bool md5flg = false;
|
||||
#ifdef USE_SHA_CRYPT
|
||||
static bool sflg = false;
|
||||
#endif /* USE_SHA_CRYPT */
|
||||
|
||||
static /*@null@*//*@observer@*/const char *crypt_method = NULL;
|
||||
#define cflg (NULL != crypt_method)
|
||||
#ifdef USE_SHA_CRYPT
|
||||
static long sha_rounds = 5000;
|
||||
#endif /* USE_SHA_CRYPT */
|
||||
|
||||
static bool is_shadow_pwd;
|
||||
static bool pw_locked = false;
|
||||
static bool spw_locked = false;
|
||||
|
||||
/* local function prototypes */
|
||||
static void fail_exit (int code);
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void process_flags (int argc, char **argv);
|
||||
static void check_flags (void);
|
||||
static void check_perms (void);
|
||||
static void open_files (void);
|
||||
static void close_files (void);
|
||||
|
||||
/*
|
||||
* fail_exit - exit with a failure code after unlocking the files
|
||||
*/
|
||||
static void fail_exit (int code)
|
||||
{
|
||||
if (pw_locked) {
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
|
||||
if (spw_locked) {
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
|
||||
exit (code);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage - display usage message and exit
|
||||
*/
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(void) fprintf (usageout,
|
||||
_(" -c, --crypt-method METHOD the crypt method (one of %s)\n"),
|
||||
#ifndef USE_SHA_CRYPT
|
||||
"NONE DES MD5"
|
||||
#else /* USE_SHA_CRYPT */
|
||||
"NONE DES MD5 SHA256 SHA512"
|
||||
#endif /* USE_SHA_CRYPT */
|
||||
);
|
||||
(void) fputs (_(" -e, --encrypted supplied passwords are encrypted\n"), usageout);
|
||||
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs (_(" -m, --md5 encrypt the clear text password using\n"
|
||||
" the MD5 algorithm\n"),
|
||||
usageout);
|
||||
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
#ifdef USE_SHA_CRYPT
|
||||
(void) fputs (_(" -s, --sha-rounds number of SHA rounds for the SHA*\n"
|
||||
" crypt algorithms\n"),
|
||||
usageout);
|
||||
#endif /* USE_SHA_CRYPT */
|
||||
(void) fputs ("\n", usageout);
|
||||
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"crypt-method", required_argument, NULL, 'c'},
|
||||
{"encrypted", no_argument, NULL, 'e'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"md5", no_argument, NULL, 'm'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
#ifdef USE_SHA_CRYPT
|
||||
{"sha-rounds", required_argument, NULL, 's'},
|
||||
#endif /* USE_SHA_CRYPT */
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv,
|
||||
#ifdef USE_SHA_CRYPT
|
||||
"c:ehmR:s:",
|
||||
#else /* !USE_SHA_CRYPT */
|
||||
"c:ehmR:",
|
||||
#endif /* !USE_SHA_CRYPT */
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
crypt_method = optarg;
|
||||
break;
|
||||
case 'e':
|
||||
eflg = true;
|
||||
break;
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'm':
|
||||
md5flg = true;
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
#ifdef USE_SHA_CRYPT
|
||||
case 's':
|
||||
sflg = true;
|
||||
if (getlong(optarg, &sha_rounds) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
break;
|
||||
#endif /* USE_SHA_CRYPT */
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
/*@notreached@*/break;
|
||||
}
|
||||
}
|
||||
|
||||
/* validate options */
|
||||
check_flags ();
|
||||
}
|
||||
|
||||
/*
|
||||
* check_flags - check flags and parameters consistency
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void check_flags (void)
|
||||
{
|
||||
#ifdef USE_SHA_CRYPT
|
||||
if (sflg && !cflg) {
|
||||
fprintf (stderr,
|
||||
_("%s: %s flag is only allowed with the %s flag\n"),
|
||||
Prog, "-s", "-c");
|
||||
usage (E_USAGE);
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((eflg && (md5flg || cflg)) ||
|
||||
(md5flg && cflg)) {
|
||||
fprintf (stderr,
|
||||
_("%s: the -c, -e, and -m flags are exclusive\n"),
|
||||
Prog);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
if (cflg) {
|
||||
if ( (0 != strcmp (crypt_method, "DES"))
|
||||
&& (0 != strcmp (crypt_method, "MD5"))
|
||||
&& (0 != strcmp (crypt_method, "NONE"))
|
||||
#ifdef USE_SHA_CRYPT
|
||||
&& (0 != strcmp (crypt_method, "SHA256"))
|
||||
&& (0 != strcmp (crypt_method, "SHA512"))
|
||||
#endif /* USE_SHA_CRYPT */
|
||||
) {
|
||||
fprintf (stderr,
|
||||
_("%s: unsupported crypt method: %s\n"),
|
||||
Prog, crypt_method);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_perms - check if the caller is allowed to add a group
|
||||
*
|
||||
* With PAM support, the setuid bit can be set on chpasswd to allow
|
||||
* non-root users to groups.
|
||||
* Without PAM support, only users who can write in the group databases
|
||||
* can add groups.
|
||||
*
|
||||
* It will not return if the user is not allowed.
|
||||
*/
|
||||
static void check_perms (void)
|
||||
{
|
||||
#ifdef USE_PAM
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
/* If chpasswd uses PAM and is SUID, check the permissions,
|
||||
* otherwise, the permissions are enforced by the access to the
|
||||
* passwd and shadow files.
|
||||
*/
|
||||
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);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
retval = pam_start ("chpasswd", 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);
|
||||
}
|
||||
exit (1);
|
||||
}
|
||||
(void) pam_end (pamh, retval);
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
#endif /* USE_PAM */
|
||||
}
|
||||
|
||||
/*
|
||||
* open_files - lock and open the password databases
|
||||
*/
|
||||
static void open_files (void)
|
||||
{
|
||||
/*
|
||||
* Lock the password file and open it for reading and writing. This
|
||||
* will bring all of the entries into memory where they may be updated.
|
||||
*/
|
||||
if (pw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, pw_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
pw_locked = true;
|
||||
if (pw_open (O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"), Prog, pw_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
/* Do the same for the shadowed database, if it exist */
|
||||
if (is_shadow_pwd) {
|
||||
if (spw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, spw_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
spw_locked = true;
|
||||
if (spw_open (O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* close_files - close and unlock the password databases
|
||||
*/
|
||||
static void close_files (void)
|
||||
{
|
||||
if (is_shadow_pwd) {
|
||||
if (spw_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
|
||||
fail_exit (1);
|
||||
}
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
spw_locked = false;
|
||||
}
|
||||
|
||||
if (pw_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
|
||||
fail_exit (1);
|
||||
}
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
pw_locked = false;
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char *name;
|
||||
char *newpwd;
|
||||
char *cp;
|
||||
|
||||
#ifdef USE_PAM
|
||||
bool use_pam = true;
|
||||
#endif /* USE_PAM */
|
||||
|
||||
int errors = 0;
|
||||
int line = 0;
|
||||
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
#ifdef USE_PAM
|
||||
if (md5flg || eflg || cflg) {
|
||||
use_pam = false;
|
||||
}
|
||||
#endif /* USE_PAM */
|
||||
|
||||
OPENLOG ("chpasswd");
|
||||
|
||||
check_perms ();
|
||||
|
||||
#ifdef USE_PAM
|
||||
if (!use_pam)
|
||||
#endif /* USE_PAM */
|
||||
{
|
||||
is_shadow_pwd = spw_file_present ();
|
||||
|
||||
open_files ();
|
||||
}
|
||||
|
||||
/*
|
||||
* Read each line, separating the user name from the password. The
|
||||
* password entry for each user will be looked up in the appropriate
|
||||
* file (shadow or passwd) and the password changed. For shadow
|
||||
* files the last change date is set directly, for passwd files the
|
||||
* last change date is set in the age only if aging information is
|
||||
* present.
|
||||
*/
|
||||
while (fgets (buf, (int) sizeof buf, stdin) != (char *) 0) {
|
||||
line++;
|
||||
cp = strrchr (buf, '\n');
|
||||
if (NULL != cp) {
|
||||
*cp = '\0';
|
||||
} else {
|
||||
if (feof (stdin) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: line %d: line too long\n"),
|
||||
Prog, line);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The username is the first field. It is separated from the
|
||||
* password with a ":" character which is replaced with a
|
||||
* NUL to give the new password. The new password will then
|
||||
* be encrypted in the normal fashion with a new salt
|
||||
* generated, unless the '-e' is given, in which case it is
|
||||
* assumed to already be encrypted.
|
||||
*/
|
||||
|
||||
name = buf;
|
||||
cp = strchr (name, ':');
|
||||
if (NULL != cp) {
|
||||
*cp = '\0';
|
||||
cp++;
|
||||
} else {
|
||||
fprintf (stderr,
|
||||
_("%s: line %d: missing new password\n"),
|
||||
Prog, line);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
newpwd = cp;
|
||||
|
||||
#ifdef USE_PAM
|
||||
if (use_pam){
|
||||
if (do_pam_passwd_non_interractive ("chpasswd", name, newpwd) != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: (line %d, user %s) password not changed\n"),
|
||||
Prog, line, name);
|
||||
errors++;
|
||||
}
|
||||
} else
|
||||
#endif /* USE_PAM */
|
||||
{
|
||||
const struct spwd *sp;
|
||||
struct spwd newsp;
|
||||
const struct passwd *pw;
|
||||
struct passwd newpw;
|
||||
|
||||
if ( !eflg
|
||||
&& ( (NULL == crypt_method)
|
||||
|| (0 != strcmp (crypt_method, "NONE")))) {
|
||||
void *arg = NULL;
|
||||
if (md5flg) {
|
||||
crypt_method = "MD5";
|
||||
}
|
||||
#ifdef USE_SHA_CRYPT
|
||||
if (sflg) {
|
||||
arg = &sha_rounds;
|
||||
}
|
||||
#endif
|
||||
cp = pw_encrypt (newpwd,
|
||||
crypt_make_salt(crypt_method, arg));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the password file entry for this user. The user must
|
||||
* already exist.
|
||||
*/
|
||||
pw = pw_locate (name);
|
||||
if (NULL == pw) {
|
||||
fprintf (stderr,
|
||||
_("%s: line %d: user '%s' does not exist\n"), Prog,
|
||||
line, name);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
if (is_shadow_pwd) {
|
||||
/* The shadow entry should be updated if the
|
||||
* passwd entry has a password set to 'x'.
|
||||
* But on the other hand, if there is already both
|
||||
* a passwd and a shadow password, it's preferable
|
||||
* to update both.
|
||||
*/
|
||||
sp = spw_locate (name);
|
||||
|
||||
if ( (NULL == sp)
|
||||
&& (strcmp (pw->pw_passwd,
|
||||
SHADOW_PASSWD_STRING) == 0)) {
|
||||
/* If the password is set to 'x' in
|
||||
* passwd, but there are no entries in
|
||||
* shadow, create one.
|
||||
*/
|
||||
newsp.sp_namp = name;
|
||||
/* newsp.sp_pwdp = NULL; will be set later */
|
||||
/* newsp.sp_lstchg= 0; will be set later */
|
||||
newsp.sp_min = getdef_num ("PASS_MIN_DAYS", -1);
|
||||
newsp.sp_max = getdef_num ("PASS_MAX_DAYS", -1);
|
||||
newsp.sp_warn = getdef_num ("PASS_WARN_AGE", -1);
|
||||
newsp.sp_inact = -1;
|
||||
newsp.sp_expire= -1;
|
||||
newsp.sp_flag = SHADOW_SP_FLAG_UNSET;
|
||||
sp = &newsp;
|
||||
}
|
||||
} else {
|
||||
sp = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The freshly encrypted new password is merged into the
|
||||
* user's password file entry and the last password change
|
||||
* date is set to the current date.
|
||||
*/
|
||||
if (NULL != sp) {
|
||||
newsp = *sp;
|
||||
newsp.sp_pwdp = cp;
|
||||
newsp.sp_lstchg = (long) time ((time_t *)NULL) / SCALE;
|
||||
if (0 == newsp.sp_lstchg) {
|
||||
/* Better disable aging than requiring a
|
||||
* password change */
|
||||
newsp.sp_lstchg = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (NULL == sp)
|
||||
|| (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) != 0)) {
|
||||
newpw = *pw;
|
||||
newpw.pw_passwd = cp;
|
||||
}
|
||||
|
||||
/*
|
||||
* The updated password file entry is then put back and will
|
||||
* be written to the password file later, after all the
|
||||
* other entries have been updated as well.
|
||||
*/
|
||||
if (NULL != sp) {
|
||||
if (spw_update (&newsp) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: line %d: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, line, spw_dbname (), newsp.sp_namp);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ( (NULL == sp)
|
||||
|| (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) != 0)) {
|
||||
if (pw_update (&newpw) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: line %d: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, line, pw_dbname (), newpw.pw_name);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Any detected errors will cause the entire set of changes to be
|
||||
* aborted. Unlocking the password file will cause all of the
|
||||
* changes to be ignored. Otherwise the file is closed, causing the
|
||||
* changes to be written out all at once, and then unlocked
|
||||
* afterwards.
|
||||
*
|
||||
* With PAM, it is not possible to delay the update of the
|
||||
* password database.
|
||||
*/
|
||||
if (0 != errors) {
|
||||
#ifdef USE_PAM
|
||||
if (!use_pam)
|
||||
#endif /* USE_PAM */
|
||||
{
|
||||
fprintf (stderr,
|
||||
_("%s: error detected, changes ignored\n"),
|
||||
Prog);
|
||||
}
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
#ifdef USE_PAM
|
||||
if (!use_pam)
|
||||
#endif /* USE_PAM */
|
||||
{
|
||||
/* Save the changes */
|
||||
close_files ();
|
||||
}
|
||||
|
||||
nscd_flush_cache ("passwd");
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
564
src/chsh.c
Normal file
564
src/chsh.c
Normal file
@@ -0,0 +1,564 @@
|
||||
/*
|
||||
* Copyright (c) 1989 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 - 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>
|
||||
|
||||
#ident "$Id: chsh.c 3640 2011-11-19 21:51:52Z nekral-guest $"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef WITH_SELINUX
|
||||
#include <selinux/selinux.h>
|
||||
#include <selinux/av_permissions.h>
|
||||
#endif
|
||||
#include "defines.h"
|
||||
#include "getdef.h"
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
#include "pwauth.h"
|
||||
#include "pwio.h"
|
||||
#ifdef USE_PAM
|
||||
#include "pam_defs.h"
|
||||
#endif
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
#ifndef SHELLS_FILE
|
||||
#define SHELLS_FILE "/etc/shells"
|
||||
#endif
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog; /* Program name */
|
||||
static bool amroot; /* Real UID is root */
|
||||
static char loginsh[BUFSIZ]; /* Name of new login shell */
|
||||
/* command line options */
|
||||
static bool sflg = false; /* -s - set shell from command line */
|
||||
static bool pw_locked = false;
|
||||
|
||||
/* external identifiers */
|
||||
|
||||
/* local function prototypes */
|
||||
static /*@noreturn@*/void fail_exit (int code);
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void new_fields (void);
|
||||
static bool shell_is_listed (const char *);
|
||||
static bool is_restricted_shell (const char *);
|
||||
static void process_flags (int argc, char **argv);
|
||||
static void check_perms (const struct passwd *pw);
|
||||
static void update_shell (const char *user, char *loginsh);
|
||||
|
||||
/*
|
||||
* fail_exit - do some cleanup and exit with the given error code
|
||||
*/
|
||||
static /*@noreturn@*/void fail_exit (int code)
|
||||
{
|
||||
if (pw_locked) {
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
|
||||
closelog ();
|
||||
|
||||
exit (code);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage - print command line syntax and exit
|
||||
*/
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options] [LOGIN]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(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, --shell SHELL new login shell for the user account\n"), usageout);
|
||||
(void) fputs ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* new_fields - change the user's login shell information interactively
|
||||
*
|
||||
* prompt the user for the login shell and change it according to the
|
||||
* response, or leave it alone if nothing was entered.
|
||||
*/
|
||||
static void new_fields (void)
|
||||
{
|
||||
puts (_("Enter the new value, or press ENTER for the default"));
|
||||
change_field (loginsh, sizeof loginsh, _("Login Shell"));
|
||||
}
|
||||
|
||||
/*
|
||||
* is_restricted_shell - return true if the shell is restricted
|
||||
*
|
||||
*/
|
||||
static bool is_restricted_shell (const char *sh)
|
||||
{
|
||||
/*
|
||||
* Shells not listed in /etc/shells are considered to be restricted.
|
||||
* Changed this to avoid confusion with "rc" (the plan9 shell - not
|
||||
* restricted despite the name starting with 'r'). --marekm
|
||||
*/
|
||||
return !shell_is_listed (sh);
|
||||
}
|
||||
|
||||
/*
|
||||
* shell_is_listed - see if the user's login shell is listed in /etc/shells
|
||||
*
|
||||
* The /etc/shells file is read for valid names of login shells. If the
|
||||
* /etc/shells file does not exist the user cannot set any shell unless
|
||||
* they are root.
|
||||
*
|
||||
* If getusershell() is available (Linux, *BSD, possibly others), use it
|
||||
* instead of re-implementing it.
|
||||
*/
|
||||
static bool shell_is_listed (const char *sh)
|
||||
{
|
||||
char *cp;
|
||||
bool found = false;
|
||||
|
||||
#ifndef HAVE_GETUSERSHELL
|
||||
char buf[BUFSIZ];
|
||||
FILE *fp;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GETUSERSHELL
|
||||
setusershell ();
|
||||
while ((cp = getusershell ())) {
|
||||
if (strcmp (cp, sh) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
endusershell ();
|
||||
#else
|
||||
fp = fopen (SHELLS_FILE, "r");
|
||||
if (NULL == fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (fgets (buf, sizeof (buf), fp) == buf) {
|
||||
cp = strrchr (buf, '\n');
|
||||
if (NULL != cp) {
|
||||
*cp = '\0';
|
||||
}
|
||||
|
||||
if (buf[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp (buf, sh) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose (fp);
|
||||
#endif
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{"shell", required_argument, NULL, 's'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv, "hR:s:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
case 's':
|
||||
sflg = true;
|
||||
STRFCPY (loginsh, optarg);
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* There should be only one remaining argument at most and it should
|
||||
* be the user's name.
|
||||
*/
|
||||
if (argc > (optind + 1)) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_perms - check if the caller is allowed to add a group
|
||||
*
|
||||
* Non-root users are only allowed to change their shell, if their current
|
||||
* shell is not a restricted shell.
|
||||
*
|
||||
* Non-root users must be authenticated.
|
||||
*
|
||||
* It will not return if the user is not allowed.
|
||||
*/
|
||||
static void check_perms (const struct passwd *pw)
|
||||
{
|
||||
#ifdef USE_PAM
|
||||
pam_handle_t *pamh = NULL;
|
||||
int retval;
|
||||
struct passwd *pampw;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Non-privileged users are only allowed to change the shell if the
|
||||
* UID of the user matches the current real UID.
|
||||
*/
|
||||
if (!amroot && pw->pw_uid != getuid ()) {
|
||||
SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
|
||||
fprintf (stderr,
|
||||
_("You may not change the shell for '%s'.\n"),
|
||||
pw->pw_name);
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Non-privileged users are only allowed to change the shell if it
|
||||
* is not a restricted one.
|
||||
*/
|
||||
if (!amroot && is_restricted_shell (pw->pw_shell)) {
|
||||
SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
|
||||
fprintf (stderr,
|
||||
_("You may not change the shell for '%s'.\n"),
|
||||
pw->pw_name);
|
||||
fail_exit (1);
|
||||
}
|
||||
#ifdef WITH_SELINUX
|
||||
/*
|
||||
* If the UID of the user does not match the current real UID,
|
||||
* check if the change is allowed by SELinux policy.
|
||||
*/
|
||||
if ((pw->pw_uid != getuid ())
|
||||
&& (is_selinux_enabled () > 0)
|
||||
&& (selinux_check_passwd_access (PASSWD__CHSH) != 0)) {
|
||||
SYSLOG ((LOG_WARN, "can't change shell for '%s'", pw->pw_name));
|
||||
fprintf (stderr,
|
||||
_("You may not change the shell for '%s'.\n"),
|
||||
pw->pw_name);
|
||||
fail_exit (1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef USE_PAM
|
||||
/*
|
||||
* Non-privileged users are optionally authenticated (must enter
|
||||
* the password of the user whose information is being changed)
|
||||
* before any changes can be made. Idea from util-linux
|
||||
* chfn/chsh. --marekm
|
||||
*/
|
||||
if (!amroot && getdef_bool ("CHSH_AUTH")) {
|
||||
passwd_check (pw->pw_name, pw->pw_passwd, "chsh");
|
||||
}
|
||||
|
||||
#else /* !USE_PAM */
|
||||
pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
|
||||
if (NULL == pampw) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot determine your user name.\n"),
|
||||
Prog);
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
|
||||
retval = pam_start ("chsh", 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);
|
||||
}
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
(void) pam_end (pamh, retval);
|
||||
#endif /* USE_PAM */
|
||||
}
|
||||
|
||||
/*
|
||||
* update_shell - update the user's shell in the passwd database
|
||||
*
|
||||
* Commit the user's entry after changing her shell field.
|
||||
*
|
||||
* It will not return in case of error.
|
||||
*/
|
||||
static void update_shell (const char *user, char *newshell)
|
||||
{
|
||||
const struct passwd *pw; /* Password entry from /etc/passwd */
|
||||
struct passwd pwent; /* New password entry */
|
||||
|
||||
/*
|
||||
* Before going any further, raise the ulimit to prevent
|
||||
* colliding into a lowered ulimit, and set the real UID
|
||||
* to root to protect against unexpected signals. Any
|
||||
* keyboard signals are set to be ignored.
|
||||
*/
|
||||
if (setuid (0) != 0) {
|
||||
SYSLOG ((LOG_ERR, "can't setuid(0)"));
|
||||
fputs (_("Cannot change ID to root.\n"), stderr);
|
||||
fail_exit (1);
|
||||
}
|
||||
pwd_init ();
|
||||
|
||||
/*
|
||||
* The passwd entry is now ready to be committed back to
|
||||
* the password file. Get a lock on the file and open it.
|
||||
*/
|
||||
if (pw_lock () == 0) {
|
||||
fprintf (stderr, _("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, pw_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
pw_locked = true;
|
||||
if (pw_open (O_RDWR) == 0) {
|
||||
fprintf (stderr, _("%s: cannot open %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the entry to update using pw_locate() - we want the real
|
||||
* one from /etc/passwd, not the one from getpwnam() which could
|
||||
* contain the shadow password if (despite the warnings) someone
|
||||
* enables AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm
|
||||
*/
|
||||
pw = pw_locate (user);
|
||||
if (NULL == pw) {
|
||||
fprintf (stderr,
|
||||
_("%s: user '%s' does not exist in %s\n"),
|
||||
Prog, user, pw_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a copy of the entry, then change the shell field. The other
|
||||
* fields remain unchanged.
|
||||
*/
|
||||
pwent = *pw;
|
||||
pwent.pw_shell = newshell;
|
||||
|
||||
/*
|
||||
* Update the passwd file entry. If there is a DBM file, update
|
||||
* that entry as well.
|
||||
*/
|
||||
if (pw_update (&pwent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, pw_dbname (), pwent.pw_name);
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Changes have all been made, so commit them and unlock the file.
|
||||
*/
|
||||
if (pw_close () == 0) {
|
||||
fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
|
||||
fail_exit (1);
|
||||
}
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
pw_locked= false;
|
||||
}
|
||||
|
||||
/*
|
||||
* chsh - this command controls changes to the user's shell
|
||||
*
|
||||
* The only supported option is -s which permits the the login shell to
|
||||
* be set from the command line.
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
char *user; /* User name */
|
||||
const struct passwd *pw; /* Password entry from /etc/passwd */
|
||||
|
||||
sanitize_env ();
|
||||
|
||||
/*
|
||||
* Get the program name. The program name is used as a prefix to
|
||||
* most error messages.
|
||||
*/
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
/*
|
||||
* This command behaves different for root and non-root users.
|
||||
*/
|
||||
amroot = (getuid () == 0);
|
||||
|
||||
OPENLOG ("chsh");
|
||||
|
||||
/* parse the command line options */
|
||||
process_flags (argc, argv);
|
||||
|
||||
/*
|
||||
* Get the name of the user to check. It is either the command line
|
||||
* name, or the name getlogin() returns.
|
||||
*/
|
||||
if (optind < argc) {
|
||||
user = argv[optind];
|
||||
pw = xgetpwnam (user);
|
||||
if (NULL == pw) {
|
||||
fprintf (stderr,
|
||||
_("%s: user '%s' does not exist\n"), Prog, user);
|
||||
fail_exit (1);
|
||||
}
|
||||
} else {
|
||||
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 ()));
|
||||
fail_exit (1);
|
||||
}
|
||||
user = xstrdup (pw->pw_name);
|
||||
}
|
||||
|
||||
#ifdef USE_NIS
|
||||
/*
|
||||
* Now we make sure this is a LOCAL password entry for this user ...
|
||||
*/
|
||||
if (__ispwNIS ()) {
|
||||
char *nis_domain;
|
||||
char *nis_master;
|
||||
|
||||
fprintf (stderr,
|
||||
_("%s: cannot change user '%s' on NIS client.\n"),
|
||||
Prog, user);
|
||||
|
||||
if (!yp_get_default_domain (&nis_domain) &&
|
||||
!yp_master (nis_domain, "passwd.byname", &nis_master)) {
|
||||
fprintf (stderr,
|
||||
_("%s: '%s' is the NIS master for this client.\n"),
|
||||
Prog, nis_master);
|
||||
}
|
||||
fail_exit (1);
|
||||
}
|
||||
#endif
|
||||
|
||||
check_perms (pw);
|
||||
|
||||
/*
|
||||
* Now get the login shell. Either get it from the password
|
||||
* file, or use the value from the command line.
|
||||
*/
|
||||
if (!sflg) {
|
||||
STRFCPY (loginsh, pw->pw_shell);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the login shell was not set on the command line, let the user
|
||||
* interactively change it.
|
||||
*/
|
||||
if (!sflg) {
|
||||
printf (_("Changing the login shell for %s\n"), user);
|
||||
new_fields ();
|
||||
}
|
||||
|
||||
/*
|
||||
* Check all of the fields for valid information. The shell
|
||||
* field may not contain any illegal characters. Non-privileged
|
||||
* users are restricted to using the shells in /etc/shells.
|
||||
* The shell must be executable by the user.
|
||||
*/
|
||||
if (valid_field (loginsh, ":,=\n") != 0) {
|
||||
fprintf (stderr, _("%s: Invalid entry: %s\n"), Prog, loginsh);
|
||||
fail_exit (1);
|
||||
}
|
||||
if ( !amroot
|
||||
&& ( is_restricted_shell (loginsh)
|
||||
|| (access (loginsh, X_OK) != 0))) {
|
||||
fprintf (stderr, _("%s: %s is an invalid shell\n"), Prog, loginsh);
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
/* Even for root, warn if an invalid shell is specified. */
|
||||
if (access (loginsh, F_OK) != 0) {
|
||||
fprintf (stderr, _("%s: Warning: %s does not exist\n"), Prog, loginsh);
|
||||
} else if (access (loginsh, X_OK) != 0) {
|
||||
fprintf (stderr, _("%s: Warning: %s is not executable\n"), Prog, loginsh);
|
||||
}
|
||||
|
||||
update_shell (user, loginsh);
|
||||
|
||||
SYSLOG ((LOG_INFO, "changed user '%s' shell to '%s'", user, loginsh));
|
||||
|
||||
nscd_flush_cache ("passwd");
|
||||
|
||||
closelog ();
|
||||
exit (E_SUCCESS);
|
||||
}
|
||||
|
||||
210
src/expiry.c
Normal file
210
src/expiry.c
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (c) 1994 , Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 - 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>
|
||||
|
||||
#ident "$Id: expiry.c 3640 2011-11-19 21:51:52Z nekral-guest $"
|
||||
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <getopt.h>
|
||||
#include "defines.h"
|
||||
#include "prototypes.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/* Global variables */
|
||||
const char *Prog;
|
||||
static bool cflg = false;
|
||||
|
||||
/* local function prototypes */
|
||||
static RETSIGTYPE catch_signals (unused int sig);
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void process_flags (int argc, char **argv);
|
||||
|
||||
/*
|
||||
* catch_signals - signal catcher
|
||||
*/
|
||||
static RETSIGTYPE catch_signals (unused int sig)
|
||||
{
|
||||
exit (10);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage - print syntax message and exit
|
||||
*/
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(void) fputs (_(" -c, --check check the user's password expiration\n"), usageout);
|
||||
(void) fputs (_(" -f, --force force password change if the user's password\n"
|
||||
" is expired\n"), usageout);
|
||||
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
bool fflg = false;
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"check", no_argument, NULL, 'c'},
|
||||
{"force", no_argument, NULL, 'f'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv, "cfh",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
cflg = true;
|
||||
break;
|
||||
case 'f':
|
||||
fflg = true;
|
||||
break;
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (! (cflg || fflg)) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
if (cflg && fflg) {
|
||||
fprintf (stderr,
|
||||
_("%s: options %s and %s conflict\n"),
|
||||
Prog, "-c", "-f");
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
if (argc != optind) {
|
||||
fprintf (stderr,
|
||||
_("%s: unexpected argument: %s\n"),
|
||||
Prog, argv[optind]);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* expiry - check and enforce password expiration policy
|
||||
*
|
||||
* expiry checks (-c) the current password expiration and forces (-f)
|
||||
* changes when required. It is callable as a normal user command.
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
struct passwd *pwd;
|
||||
struct spwd *spwd;
|
||||
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
sanitize_env ();
|
||||
|
||||
/*
|
||||
* Start by disabling all of the keyboard signals.
|
||||
*/
|
||||
(void) signal (SIGHUP, catch_signals);
|
||||
(void) signal (SIGINT, catch_signals);
|
||||
(void) signal (SIGQUIT, catch_signals);
|
||||
#ifdef SIGTSTP
|
||||
(void) signal (SIGTSTP, catch_signals);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* expiry takes one of two arguments. The default action is to give
|
||||
* the usage message.
|
||||
*/
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
OPENLOG ("expiry");
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
/*
|
||||
* Get user entries for /etc/passwd and /etc/shadow
|
||||
*/
|
||||
pwd = get_my_pwent ();
|
||||
if (NULL == pwd) {
|
||||
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 (10);
|
||||
}
|
||||
spwd = getspnam (pwd->pw_name); /* !USE_PAM, No need for xgetspnam */
|
||||
|
||||
/*
|
||||
* If checking accounts, use agecheck() function.
|
||||
*/
|
||||
if (cflg) {
|
||||
/*
|
||||
* Print out number of days until expiration.
|
||||
*/
|
||||
agecheck (spwd);
|
||||
|
||||
/*
|
||||
* Exit with status indicating state of account.
|
||||
*/
|
||||
exit (isexpired (pwd, spwd));
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, force a password change with the expire() function.
|
||||
* It will force the change or give a message indicating what to
|
||||
* do.
|
||||
* It won't return unless the account is unexpired.
|
||||
*/
|
||||
(void) expire (pwd, spwd);
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
741
src/faillog.c
Normal file
741
src/faillog.c
Normal file
@@ -0,0 +1,741 @@
|
||||
/*
|
||||
* Copyright (c) 1989 - 1993, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2002 - 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>
|
||||
|
||||
#ident "$Id: faillog.c 3639 2011-11-19 21:44:34Z nekral-guest $"
|
||||
|
||||
#include <getopt.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
#include "defines.h"
|
||||
#include "faillog.h"
|
||||
#include "prototypes.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/* local function prototypes */
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void print_one (/*@null@*/const struct passwd *pw, bool force);
|
||||
static void set_locktime (long locktime);
|
||||
static bool set_locktime_one (uid_t uid, long locktime);
|
||||
static void setmax (short max);
|
||||
static bool setmax_one (uid_t uid, short max);
|
||||
static void print (void);
|
||||
static bool reset_one (uid_t uid);
|
||||
static void reset (void);
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog; /* Program name */
|
||||
static FILE *fail; /* failure file stream */
|
||||
static time_t seconds; /* that number of days in seconds */
|
||||
static unsigned long umin; /* if uflg and has_umin, only display users with uid >= umin */
|
||||
static bool has_umin = false;
|
||||
static unsigned long umax; /* if uflg and has_umax, only display users with uid <= umax */
|
||||
static bool has_umax = false;
|
||||
static bool errors = false;
|
||||
|
||||
static bool aflg = false; /* set if all users are to be printed always */
|
||||
static bool uflg = false; /* set if user is a valid user id */
|
||||
static bool tflg = false; /* print is restricted to most recent days */
|
||||
static bool lflg = false; /* set the locktime */
|
||||
static bool mflg = false; /* set maximum failed login counters */
|
||||
static bool rflg = false; /* reset the counters of login failures */
|
||||
|
||||
static struct stat statbuf; /* fstat buffer for file size */
|
||||
|
||||
#define NOW (time((time_t *) 0))
|
||||
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(void) fputs (_(" -a, --all display faillog records for all users\n"), usageout);
|
||||
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs (_(" -l, --lock-secs SEC after failed login lock account for SEC seconds\n"), usageout);
|
||||
(void) fputs (_(" -m, --maximum MAX set maximum failed login counters to MAX\n"), usageout);
|
||||
(void) fputs (_(" -r, --reset reset the counters of login failures\n"), usageout);
|
||||
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
(void) fputs (_(" -t, --time DAYS display faillog records more recent than DAYS\n"), usageout);
|
||||
(void) fputs (_(" -u, --user LOGIN/RANGE display faillog record or maintains failure\n"
|
||||
" counters and limits (if used with -r, -m,\n"
|
||||
" or -l) only for the specified LOGIN(s)\n"), usageout);
|
||||
(void) fputs ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
static void print_one (/*@null@*/const struct passwd *pw, bool force)
|
||||
{
|
||||
static bool once = false;
|
||||
struct tm *tm;
|
||||
off_t offset;
|
||||
struct faillog fl;
|
||||
time_t now;
|
||||
|
||||
#ifdef HAVE_STRFTIME
|
||||
char *cp;
|
||||
char ptime[80];
|
||||
#endif
|
||||
|
||||
if (NULL == pw) {
|
||||
return;
|
||||
}
|
||||
|
||||
offset = (off_t) pw->pw_uid * sizeof (fl);
|
||||
if (offset + sizeof (fl) <= statbuf.st_size) {
|
||||
/* fseeko errors are not really relevant for us. */
|
||||
int err = fseeko (fail, offset, SEEK_SET);
|
||||
assert (0 == err);
|
||||
/* faillog is a sparse file. Even if no entries were
|
||||
* entered for this user, which should be able to get the
|
||||
* empty entry in this case.
|
||||
*/
|
||||
if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
|
||||
fprintf (stderr,
|
||||
_("%s: Failed to get the entry for UID %lu\n"),
|
||||
Prog, (unsigned long int)pw->pw_uid);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/* Outsize of the faillog file.
|
||||
* Behave as if there were a missing entry (same behavior
|
||||
* as if we were reading an non existing entry in the
|
||||
* sparse faillog file).
|
||||
*/
|
||||
memzero (&fl, sizeof (fl));
|
||||
}
|
||||
|
||||
/* Nothing to report */
|
||||
if (!force && (0 == fl.fail_time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void) time(&now);
|
||||
|
||||
/* Filter out entries that do not match with the -t option */
|
||||
if (tflg && ((now - fl.fail_time) > seconds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Print the header only once */
|
||||
if (!once) {
|
||||
puts (_("Login Failures Maximum Latest On\n"));
|
||||
once = true;
|
||||
}
|
||||
|
||||
tm = localtime (&fl.fail_time);
|
||||
#ifdef HAVE_STRFTIME
|
||||
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 */
|
||||
printf ("%s %s", cp, fl.fail_line);
|
||||
if (0 != fl.fail_locktime) {
|
||||
if ( ((fl.fail_time + fl.fail_locktime) > now)
|
||||
&& (0 != fl.fail_cnt)) {
|
||||
printf (_(" [%lus left]"),
|
||||
(unsigned long) fl.fail_time + fl.fail_locktime - now);
|
||||
} else {
|
||||
printf (_(" [%lds lock]"),
|
||||
fl.fail_locktime);
|
||||
}
|
||||
}
|
||||
putchar ('\n');
|
||||
}
|
||||
|
||||
static void print (void)
|
||||
{
|
||||
if (uflg && has_umin && has_umax && (umin==umax)) {
|
||||
print_one (getpwuid ((uid_t)umin), true);
|
||||
} else {
|
||||
/* We only print records for existing users.
|
||||
* Loop based on the user database instead of reading the
|
||||
* whole file. We will have to query the database anyway
|
||||
* so except for very small ranges and large user
|
||||
* database, this should not be a performance issue.
|
||||
*/
|
||||
struct passwd *pwent;
|
||||
|
||||
setpwent ();
|
||||
while ( (pwent = getpwent ()) != NULL ) {
|
||||
if ( uflg
|
||||
&& ( (has_umin && (pwent->pw_uid < (uid_t)umin))
|
||||
|| (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
|
||||
continue;
|
||||
}
|
||||
print_one (pwent, aflg);
|
||||
}
|
||||
endpwent ();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* reset_one - Reset the fail count for one user
|
||||
*
|
||||
* This returns a boolean indicating if an error occurred.
|
||||
*/
|
||||
static bool reset_one (uid_t uid)
|
||||
{
|
||||
off_t offset;
|
||||
struct faillog fl;
|
||||
|
||||
offset = (off_t) uid * sizeof (fl);
|
||||
if (offset + sizeof (fl) <= statbuf.st_size) {
|
||||
/* fseeko errors are not really relevant for us. */
|
||||
int err = fseeko (fail, offset, SEEK_SET);
|
||||
assert (0 == err);
|
||||
/* faillog is a sparse file. Even if no entries were
|
||||
* entered for this user, which should be able to get the
|
||||
* empty entry in this case.
|
||||
*/
|
||||
if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
|
||||
fprintf (stderr,
|
||||
_("%s: Failed to get the entry for UID %lu\n"),
|
||||
Prog, (unsigned long int)uid);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
/* Outsize of the faillog file.
|
||||
* Behave as if there were a missing entry (same behavior
|
||||
* as if we were reading an non existing entry in the
|
||||
* sparse faillog file).
|
||||
*/
|
||||
memzero (&fl, sizeof (fl));
|
||||
}
|
||||
|
||||
if (0 == fl.fail_cnt) {
|
||||
/* If the count is already null, do not write in the file.
|
||||
* This avoids writing 0 when no entries were present for
|
||||
* the user.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
fl.fail_cnt = 0;
|
||||
|
||||
if ( (fseeko (fail, offset, SEEK_SET) == 0)
|
||||
&& (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
|
||||
(void) fflush (fail);
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf (stderr,
|
||||
_("%s: Failed to reset fail count for UID %lu\n"),
|
||||
Prog, (unsigned long int)uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void reset (void)
|
||||
{
|
||||
if (uflg && has_umin && has_umax && (umin==umax)) {
|
||||
if (reset_one ((uid_t)umin)) {
|
||||
errors = true;
|
||||
}
|
||||
} else {
|
||||
/* There is no need to reset outside of the faillog
|
||||
* database.
|
||||
*/
|
||||
uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
|
||||
if (uidmax > 1) {
|
||||
uidmax--;
|
||||
}
|
||||
if (has_umax && (uid_t)umax < uidmax) {
|
||||
uidmax = (uid_t)umax;
|
||||
}
|
||||
|
||||
/* Reset all entries in the specified range.
|
||||
* Non existing entries will not be touched.
|
||||
*/
|
||||
if (aflg) {
|
||||
/* Entries for non existing users are also reset.
|
||||
*/
|
||||
uid_t uid = 0;
|
||||
|
||||
/* Make sure we stay in the umin-umax range if specified */
|
||||
if (has_umin) {
|
||||
uid = (uid_t)umin;
|
||||
}
|
||||
|
||||
while (uid <= uidmax) {
|
||||
if (reset_one (uid)) {
|
||||
errors = true;
|
||||
}
|
||||
uid++;
|
||||
}
|
||||
} else {
|
||||
/* Only reset records for existing users.
|
||||
*/
|
||||
struct passwd *pwent;
|
||||
|
||||
setpwent ();
|
||||
while ( (pwent = getpwent ()) != NULL ) {
|
||||
if ( uflg
|
||||
&& ( (has_umin && (pwent->pw_uid < (uid_t)umin))
|
||||
|| (pwent->pw_uid > (uid_t)uidmax))) {
|
||||
continue;
|
||||
}
|
||||
if (reset_one (pwent->pw_uid)) {
|
||||
errors = true;
|
||||
}
|
||||
}
|
||||
endpwent ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* setmax_one - Set the maximum failed login counter for one user
|
||||
*
|
||||
* This returns a boolean indicating if an error occurred.
|
||||
*/
|
||||
static bool setmax_one (uid_t uid, short max)
|
||||
{
|
||||
off_t offset;
|
||||
struct faillog fl;
|
||||
|
||||
offset = (off_t) uid * sizeof (fl);
|
||||
if (offset + sizeof (fl) <= statbuf.st_size) {
|
||||
/* fseeko errors are not really relevant for us. */
|
||||
int err = fseeko (fail, offset, SEEK_SET);
|
||||
assert (0 == err);
|
||||
/* faillog is a sparse file. Even if no entries were
|
||||
* entered for this user, which should be able to get the
|
||||
* empty entry in this case.
|
||||
*/
|
||||
if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
|
||||
fprintf (stderr,
|
||||
_("%s: Failed to get the entry for UID %lu\n"),
|
||||
Prog, (unsigned long int)uid);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
/* Outsize of the faillog file.
|
||||
* Behave as if there were a missing entry (same behavior
|
||||
* as if we were reading an non existing entry in the
|
||||
* sparse faillog file).
|
||||
*/
|
||||
memzero (&fl, sizeof (fl));
|
||||
}
|
||||
|
||||
if (max == fl.fail_max) {
|
||||
/* If the max is already set to the right value, do not
|
||||
* write in the file.
|
||||
* This avoids writing 0 when no entries were present for
|
||||
* the user and the max argument is 0.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
fl.fail_max = max;
|
||||
|
||||
if ( (fseeko (fail, offset, SEEK_SET) == 0)
|
||||
&& (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
|
||||
(void) fflush (fail);
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf (stderr,
|
||||
_("%s: Failed to set max for UID %lu\n"),
|
||||
Prog, (unsigned long int)uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void setmax (short max)
|
||||
{
|
||||
if (uflg && has_umin && has_umax && (umin==umax)) {
|
||||
if (setmax_one ((uid_t)umin, max)) {
|
||||
errors = true;
|
||||
}
|
||||
} else {
|
||||
/* Set max for entries in the specified range.
|
||||
* If max is unchanged for an entry, the entry is not touched.
|
||||
* If max is null, and no entries exist for this user, no
|
||||
* entries will be created.
|
||||
*/
|
||||
if (aflg) {
|
||||
/* Entries for non existing user are also taken into
|
||||
* account (in order to define policy for future users).
|
||||
*/
|
||||
uid_t uid = 0;
|
||||
/* The default umax value is based on the size of the
|
||||
* faillog database.
|
||||
*/
|
||||
uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
|
||||
if (uidmax > 1) {
|
||||
uidmax--;
|
||||
}
|
||||
|
||||
/* Make sure we stay in the umin-umax range if specified */
|
||||
if (has_umin) {
|
||||
uid = (uid_t)umin;
|
||||
}
|
||||
if (has_umax) {
|
||||
uidmax = (uid_t)umax;
|
||||
}
|
||||
|
||||
while (uid <= uidmax) {
|
||||
if (setmax_one (uid, max)) {
|
||||
errors = true;
|
||||
}
|
||||
uid++;
|
||||
}
|
||||
} else {
|
||||
/* Only change records for existing users.
|
||||
*/
|
||||
struct passwd *pwent;
|
||||
|
||||
setpwent ();
|
||||
while ( (pwent = getpwent ()) != NULL ) {
|
||||
if ( uflg
|
||||
&& ( (has_umin && (pwent->pw_uid < (uid_t)umin))
|
||||
|| (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
|
||||
continue;
|
||||
}
|
||||
if (setmax_one (pwent->pw_uid, max)) {
|
||||
errors = true;
|
||||
}
|
||||
}
|
||||
endpwent ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* set_locktime_one - Set the locktime for one user
|
||||
*
|
||||
* This returns a boolean indicating if an error occurred.
|
||||
*/
|
||||
static bool set_locktime_one (uid_t uid, long locktime)
|
||||
{
|
||||
off_t offset;
|
||||
struct faillog fl;
|
||||
|
||||
offset = (off_t) uid * sizeof (fl);
|
||||
if (offset + sizeof (fl) <= statbuf.st_size) {
|
||||
/* fseeko errors are not really relevant for us. */
|
||||
int err = fseeko (fail, offset, SEEK_SET);
|
||||
assert (0 == err);
|
||||
/* faillog is a sparse file. Even if no entries were
|
||||
* entered for this user, which should be able to get the
|
||||
* empty entry in this case.
|
||||
*/
|
||||
if (fread ((char *) &fl, sizeof (fl), 1, fail) != 1) {
|
||||
fprintf (stderr,
|
||||
_("%s: Failed to get the entry for UID %lu\n"),
|
||||
Prog, (unsigned long int)uid);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
/* Outsize of the faillog file.
|
||||
* Behave as if there were a missing entry (same behavior
|
||||
* as if we were reading an non existing entry in the
|
||||
* sparse faillog file).
|
||||
*/
|
||||
memzero (&fl, sizeof (fl));
|
||||
}
|
||||
|
||||
if (locktime == fl.fail_locktime) {
|
||||
/* If the locktime is already set to the right value, do not
|
||||
* write in the file.
|
||||
* This avoids writing 0 when no entries were present for
|
||||
* the user and the locktime argument is 0.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
fl.fail_locktime = locktime;
|
||||
|
||||
if ( (fseeko (fail, offset, SEEK_SET) == 0)
|
||||
&& (fwrite ((char *) &fl, sizeof (fl), 1, fail) == 1)) {
|
||||
(void) fflush (fail);
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf (stderr,
|
||||
_("%s: Failed to set locktime for UID %lu\n"),
|
||||
Prog, (unsigned long int)uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void set_locktime (long locktime)
|
||||
{
|
||||
if (uflg && has_umin && has_umax && (umin==umax)) {
|
||||
if (set_locktime_one ((uid_t)umin, locktime)) {
|
||||
errors = true;
|
||||
}
|
||||
} else {
|
||||
/* Set locktime for entries in the specified range.
|
||||
* If locktime is unchanged for an entry, the entry is not touched.
|
||||
* If locktime is null, and no entries exist for this user, no
|
||||
* entries will be created.
|
||||
*/
|
||||
if (aflg) {
|
||||
/* Entries for non existing user are also taken into
|
||||
* account (in order to define policy for future users).
|
||||
*/
|
||||
uid_t uid = 0;
|
||||
/* The default umax value is based on the size of the
|
||||
* faillog database.
|
||||
*/
|
||||
uid_t uidmax = statbuf.st_size / sizeof (struct faillog);
|
||||
if (uidmax > 1) {
|
||||
uidmax--;
|
||||
}
|
||||
|
||||
/* Make sure we stay in the umin-umax range if specified */
|
||||
if (has_umin) {
|
||||
uid = (uid_t)umin;
|
||||
}
|
||||
if (has_umax) {
|
||||
uidmax = (uid_t)umax;
|
||||
}
|
||||
|
||||
while (uid <= uidmax) {
|
||||
if (set_locktime_one (uid, locktime)) {
|
||||
errors = true;
|
||||
}
|
||||
uid++;
|
||||
}
|
||||
} else {
|
||||
/* Only change records for existing users.
|
||||
*/
|
||||
struct passwd *pwent;
|
||||
|
||||
setpwent ();
|
||||
while ( (pwent = getpwent ()) != NULL ) {
|
||||
if ( uflg
|
||||
&& ( (has_umin && (pwent->pw_uid < (uid_t)umin))
|
||||
|| (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
|
||||
continue;
|
||||
}
|
||||
if (set_locktime_one (pwent->pw_uid, locktime)) {
|
||||
errors = true;
|
||||
}
|
||||
}
|
||||
endpwent ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
long fail_locktime;
|
||||
short fail_max;
|
||||
long days;
|
||||
|
||||
/*
|
||||
* Get the program name. The program name is used as a prefix to
|
||||
* most error messages.
|
||||
*/
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
{
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"all", no_argument, NULL, 'a'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"lock-secs", required_argument, NULL, 'l'},
|
||||
{"maximum", required_argument, NULL, 'm'},
|
||||
{"reset", no_argument, NULL, 'r'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{"time", required_argument, NULL, 't'},
|
||||
{"user", required_argument, NULL, 'u'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
while ((c = getopt_long (argc, argv, "ahl:m:rR:t:u:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
aflg = true;
|
||||
break;
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'l':
|
||||
if (getlong (optarg, &fail_locktime) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
exit (E_BAD_ARG);
|
||||
}
|
||||
lflg = true;
|
||||
break;
|
||||
case 'm':
|
||||
{
|
||||
long int lmax;
|
||||
if ( (getlong (optarg, &lmax) == 0)
|
||||
|| ((long int)(short) lmax != lmax)) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
exit (E_BAD_ARG);
|
||||
}
|
||||
fail_max = (short) lmax;
|
||||
mflg = true;
|
||||
break;
|
||||
}
|
||||
case 'r':
|
||||
rflg = true;
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
case 't':
|
||||
if (getlong (optarg, &days) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
exit (E_BAD_ARG);
|
||||
}
|
||||
seconds = (time_t) days * DAY;
|
||||
tflg = true;
|
||||
break;
|
||||
case 'u':
|
||||
{
|
||||
/*
|
||||
* The user can be:
|
||||
* - a login name
|
||||
* - numerical
|
||||
* - a numerical login ID
|
||||
* - a range (-x, x-, x-y)
|
||||
*/
|
||||
struct passwd *pwent;
|
||||
|
||||
uflg = true;
|
||||
/* local, no need for xgetpwnam */
|
||||
pwent = getpwnam (optarg);
|
||||
if (NULL != pwent) {
|
||||
umin = (unsigned long) pwent->pw_uid;
|
||||
has_umin = true;
|
||||
umax = umin;
|
||||
has_umax = true;
|
||||
} else {
|
||||
if (getrange (optarg,
|
||||
&umin, &has_umin,
|
||||
&umax, &has_umax) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: Unknown user or range: %s\n"),
|
||||
Prog, optarg);
|
||||
exit (E_BAD_ARG);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
if (argc > optind) {
|
||||
fprintf (stderr,
|
||||
_("%s: unexpected argument: %s\n"),
|
||||
Prog, argv[optind]);
|
||||
usage (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (tflg && (lflg || mflg || rflg)) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
/* Open the faillog database */
|
||||
if (lflg || mflg || rflg) {
|
||||
fail = fopen (FAILLOG_FILE, "r+");
|
||||
} else {
|
||||
fail = fopen (FAILLOG_FILE, "r");
|
||||
}
|
||||
if (NULL == fail) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot open %s: %s\n"),
|
||||
Prog, FAILLOG_FILE, strerror (errno));
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
|
||||
/* Get the size of the faillog */
|
||||
if (fstat (fileno (fail), &statbuf) != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot get the size of %s: %s\n"),
|
||||
Prog, FAILLOG_FILE, strerror (errno));
|
||||
exit (E_NOPERM);
|
||||
}
|
||||
|
||||
if (lflg) {
|
||||
set_locktime (fail_locktime);
|
||||
}
|
||||
|
||||
if (mflg) {
|
||||
setmax (fail_max);
|
||||
}
|
||||
|
||||
if (rflg) {
|
||||
reset ();
|
||||
}
|
||||
|
||||
if (!(lflg || mflg || rflg)) {
|
||||
print ();
|
||||
}
|
||||
|
||||
if (lflg || mflg || rflg) {
|
||||
if ( (ferror (fail) != 0)
|
||||
|| (fflush (fail) != 0)
|
||||
|| (fsync (fileno (fail)) != 0)
|
||||
|| (fclose (fail) != 0)) {
|
||||
fprintf (stderr,
|
||||
_("%s: Failed to write %s: %s\n"),
|
||||
Prog, FAILLOG_FILE, strerror (errno));
|
||||
(void) fclose (fail);
|
||||
errors = true;
|
||||
}
|
||||
} else {
|
||||
(void) fclose (fail);
|
||||
}
|
||||
|
||||
exit (errors ? E_NOPERM : E_SUCCESS);
|
||||
}
|
||||
|
||||
1200
src/gpasswd.c
Normal file
1200
src/gpasswd.c
Normal file
File diff suppressed because it is too large
Load Diff
624
src/groupadd.c
Normal file
624
src/groupadd.c
Normal file
@@ -0,0 +1,624 @@
|
||||
/*
|
||||
* Copyright (c) 1991 - 1993, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* 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>
|
||||
|
||||
#ident "$Id: groupadd.c 3576 2011-11-13 16:24:57Z nekral-guest $"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <grp.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
#include "pam_defs.h"
|
||||
#include <pwd.h>
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
#include "chkname.h"
|
||||
#include "defines.h"
|
||||
#include "getdef.h"
|
||||
#include "groupio.h"
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
#ifdef SHADOWGRP
|
||||
#include "sgroupio.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
#define E_GID_IN_USE 4 /* gid not unique (when -o not used) */
|
||||
#define E_NAME_IN_USE 9 /* group name not unique */
|
||||
#define E_GRP_UPDATE 10 /* can't update group file */
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static /*@null@*/char *group_name;
|
||||
static gid_t group_id;
|
||||
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 */
|
||||
static bool fflg = false; /* if group already exists, do nothing and exit(0) */
|
||||
static bool rflg = false; /* create a system account */
|
||||
static bool pflg = false; /* new encrypted password */
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
static bool is_shadow_grp;
|
||||
#endif
|
||||
|
||||
/* local function prototypes */
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void new_grent (struct group *grent);
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
static void new_sgent (struct sgrp *sgent);
|
||||
#endif
|
||||
static void grp_update (void);
|
||||
static void check_new_name (void);
|
||||
static void close_files (void);
|
||||
static void open_files (void);
|
||||
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 /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
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 (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
(void) fputs ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* new_grent - initialize the values in a group file entry
|
||||
*
|
||||
* new_grent() takes all of the values that have been entered and fills
|
||||
* in a (struct group) with them.
|
||||
*/
|
||||
static void new_grent (struct group *grent)
|
||||
{
|
||||
memzero (grent, sizeof *grent);
|
||||
grent->gr_name = group_name;
|
||||
if (pflg) {
|
||||
grent->gr_passwd = group_passwd;
|
||||
} else {
|
||||
grent->gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
|
||||
}
|
||||
grent->gr_gid = group_id;
|
||||
grent->gr_mem = &empty_list;
|
||||
}
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
/*
|
||||
* new_sgent - initialize the values in a shadow group file entry
|
||||
*
|
||||
* new_sgent() takes all of the values that have been entered and fills
|
||||
* in a (struct sgrp) with them.
|
||||
*/
|
||||
static void new_sgent (struct sgrp *sgent)
|
||||
{
|
||||
memzero (sgent, sizeof *sgent);
|
||||
sgent->sg_name = group_name;
|
||||
if (pflg) {
|
||||
sgent->sg_passwd = group_passwd;
|
||||
} else {
|
||||
sgent->sg_passwd = "!"; /* XXX warning: const */
|
||||
}
|
||||
sgent->sg_adm = &empty_list;
|
||||
sgent->sg_mem = &empty_list;
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
/*
|
||||
* grp_update - add new group file entries
|
||||
*
|
||||
* grp_update() writes the new records to the group files.
|
||||
*/
|
||||
static void grp_update (void)
|
||||
{
|
||||
struct group grp;
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
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.
|
||||
*/
|
||||
new_grent (&grp);
|
||||
#ifdef SHADOWGRP
|
||||
new_sgent (&sgrp);
|
||||
if (is_shadow_grp && pflg) {
|
||||
grp.gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
/*
|
||||
* Write out the new group file entry.
|
||||
*/
|
||||
if (gr_update (&grp) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, gr_dbname (), grp.gr_name);
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
#ifdef SHADOWGRP
|
||||
/*
|
||||
* Write out the new shadow group entries as well.
|
||||
*/
|
||||
if (is_shadow_grp && (sgr_update (&sgrp) == 0)) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, sgr_dbname (), sgrp.sg_name);
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
}
|
||||
|
||||
/*
|
||||
* check_new_name - check the new name for validity
|
||||
*
|
||||
* check_new_name() insures that the new name doesn't contain any
|
||||
* illegal characters.
|
||||
*/
|
||||
static void check_new_name (void)
|
||||
{
|
||||
if (is_valid_group_name (group_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* All invalid group names land here.
|
||||
*/
|
||||
|
||||
fprintf (stderr, _("%s: '%s' is not a valid group name\n"),
|
||||
Prog, group_name);
|
||||
|
||||
exit (E_BAD_ARG);
|
||||
}
|
||||
|
||||
/*
|
||||
* close_files - close all of the files that were opened
|
||||
*
|
||||
* close_files() closes all of the files that were opened for this new
|
||||
* group. This causes any modified entries to be written out.
|
||||
*/
|
||||
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 ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_ADD_GROUP, Prog,
|
||||
"adding group to /etc/group",
|
||||
group_name, (unsigned int) group_id,
|
||||
SHADOW_AUDIT_SUCCESS);
|
||||
#endif
|
||||
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 ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_ADD_GROUP, Prog,
|
||||
"adding group to /etc/gshadow",
|
||||
group_name, (unsigned int) group_id,
|
||||
SHADOW_AUDIT_SUCCESS);
|
||||
#endif
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* open_files - lock and open the group files
|
||||
*
|
||||
* open_files() opens the two group files.
|
||||
*/
|
||||
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 ());
|
||||
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 ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
add_cleanup (cleanup_unlock_gshadow, NULL);
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
char *cp;
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"force", no_argument, NULL, 'f'},
|
||||
{"gid", required_argument, NULL, 'g'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"key", required_argument, NULL, 'K'},
|
||||
{"non-unique", no_argument, NULL, 'o'},
|
||||
{"password", required_argument, NULL, 'p'},
|
||||
{"system", no_argument, NULL, 'r'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv, "fg:hK:op:rR:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'f':
|
||||
/*
|
||||
* "force" - do nothing, just exit(0), if the
|
||||
* specified group already exists. With -g, if
|
||||
* specified gid already exists, choose another
|
||||
* (unique) gid (turn off -g). Based on the RedHat's
|
||||
* patch from shadow-utils-970616-9.
|
||||
*/
|
||||
fflg = true;
|
||||
break;
|
||||
case 'g':
|
||||
gflg = true;
|
||||
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 (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'K':
|
||||
/*
|
||||
* override login.defs defaults (-K name=value)
|
||||
* example: -K GID_MIN=100 -K GID_MAX=499
|
||||
* note: -K GID_MIN=10,GID_MAX=499 doesn't work yet
|
||||
*/
|
||||
cp = strchr (optarg, '=');
|
||||
if (NULL == cp) {
|
||||
fprintf (stderr,
|
||||
_("%s: -K requires KEY=VALUE\n"),
|
||||
Prog);
|
||||
exit (E_BAD_ARG);
|
||||
}
|
||||
/* terminate name, point to value */
|
||||
*cp++ = '\0';
|
||||
if (putdef_str (optarg, cp) < 0) {
|
||||
exit (E_BAD_ARG);
|
||||
}
|
||||
break;
|
||||
case 'o':
|
||||
oflg = true;
|
||||
break;
|
||||
case 'p':
|
||||
pflg = true;
|
||||
group_passwd = optarg;
|
||||
break;
|
||||
case 'r':
|
||||
rflg = true;
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the flags consistency
|
||||
*/
|
||||
if (optind != argc - 1) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
group_name = argv[optind];
|
||||
|
||||
check_flags ();
|
||||
}
|
||||
|
||||
/*
|
||||
* check_flags - check flags and parameters consistency
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void check_flags (void)
|
||||
{
|
||||
/* -o does not make sense without -g */
|
||||
if (oflg && !gflg) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
check_new_name ();
|
||||
|
||||
/*
|
||||
* Check if the group already exist.
|
||||
*/
|
||||
/* local, no need for xgetgrnam */
|
||||
if (getgrnam (group_name) != NULL) {
|
||||
/* The group already exist */
|
||||
if (fflg) {
|
||||
/* OK, no need to do anything */
|
||||
exit (E_SUCCESS);
|
||||
}
|
||||
fprintf (stderr,
|
||||
_("%s: group '%s' already exists\n"),
|
||||
Prog, group_name);
|
||||
exit (E_NAME_IN_USE);
|
||||
}
|
||||
|
||||
if (gflg && (getgrgid (group_id) != NULL)) {
|
||||
/* A GID was specified, and a group already exist with that GID
|
||||
* - either we will use this GID anyway (-o)
|
||||
* - either we ignore the specified GID and
|
||||
* we will use another one (-f)
|
||||
* - either it is a failure
|
||||
*/
|
||||
if (oflg) {
|
||||
/* Continue with this GID */
|
||||
} else if (fflg) {
|
||||
/* Turn off -g, we can use any GID */
|
||||
gflg = false;
|
||||
} else {
|
||||
fprintf (stderr,
|
||||
_("%s: GID '%lu' already exists\n"),
|
||||
Prog, (unsigned long int) group_id);
|
||||
exit (E_GID_IN_USE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_perms - check if the caller is allowed to add a group
|
||||
*
|
||||
* With PAM support, the setuid bit can be set on groupadd to allow
|
||||
* non-root users to groups.
|
||||
* Without PAM support, only users who can write in the group databases
|
||||
* can add groups.
|
||||
*
|
||||
* It will not return if the user is not allowed.
|
||||
*/
|
||||
static void check_perms (void)
|
||||
{
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
pam_handle_t *pamh = NULL;
|
||||
int retval;
|
||||
struct passwd *pampw;
|
||||
|
||||
pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
|
||||
if (NULL == pampw) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot determine your user name.\n"),
|
||||
Prog);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
retval = pam_start ("groupadd", 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);
|
||||
}
|
||||
exit (1);
|
||||
}
|
||||
(void) pam_end (pamh, retval);
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
}
|
||||
|
||||
/*
|
||||
* main - groupadd command
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
/*
|
||||
* 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 ("groupadd");
|
||||
#ifdef WITH_AUDIT
|
||||
audit_help_open ();
|
||||
#endif
|
||||
|
||||
if (atexit (do_cleanups) != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot setup cleanup service.\n"),
|
||||
Prog);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
process_flags (argc, argv);
|
||||
|
||||
check_perms ();
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
is_shadow_grp = sgr_file_present ();
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Do the hard stuff - open the files, create the group entries,
|
||||
* then close and update the files.
|
||||
*/
|
||||
open_files ();
|
||||
|
||||
if (!gflg) {
|
||||
if (find_new_gid (rflg, &group_id, NULL) < 0) {
|
||||
exit (E_GID_IN_USE);
|
||||
}
|
||||
}
|
||||
|
||||
grp_update ();
|
||||
close_files ();
|
||||
|
||||
nscd_flush_cache ("group");
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
484
src/groupdel.c
Normal file
484
src/groupdel.c
Normal file
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Copyright (c) 1991 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* 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>
|
||||
|
||||
#ident "$Id: groupdel.c 3576 2011-11-13 16:24:57Z nekral-guest $"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
#include "pam_defs.h"
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <getopt.h>
|
||||
#include "defines.h"
|
||||
#include "groupio.h"
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
#ifdef SHADOWGRP
|
||||
#include "sgroupio.h"
|
||||
#endif
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static char *group_name;
|
||||
static gid_t group_id = -1;
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
static bool is_shadow_grp;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* exit status values
|
||||
*/
|
||||
/*@-exitarg@*/
|
||||
#define E_SUCCESS 0 /* success */
|
||||
#define E_USAGE 2 /* invalid command syntax */
|
||||
#define E_NOTFOUND 6 /* specified group doesn't exist */
|
||||
#define E_GROUP_BUSY 8 /* can't remove user's primary group */
|
||||
#define E_GRP_UPDATE 10 /* can't update group file */
|
||||
|
||||
/* local function prototypes */
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void grp_update (void);
|
||||
static void close_files (void);
|
||||
static void open_files (void);
|
||||
static void group_busy (gid_t gid);
|
||||
static void process_flags (int argc, char **argv);
|
||||
|
||||
/*
|
||||
* usage - display usage message and exit
|
||||
*/
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options] GROUP\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(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 ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* grp_update - update group file entries
|
||||
*
|
||||
* grp_update() writes the new records to the group files.
|
||||
*/
|
||||
static void grp_update (void)
|
||||
{
|
||||
/*
|
||||
* To add the group, we need to update /etc/group.
|
||||
* Make sure failures will be reported.
|
||||
*/
|
||||
add_cleanup (cleanup_report_del_group_group, group_name);
|
||||
#ifdef SHADOWGRP
|
||||
if (is_shadow_grp) {
|
||||
/* We also need to update /etc/gshadow */
|
||||
add_cleanup (cleanup_report_del_group_gshadow, group_name);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Delete the group entry.
|
||||
*/
|
||||
if (gr_remove (group_name) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot remove entry '%s' from %s\n"),
|
||||
Prog, group_name, gr_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
/*
|
||||
* Delete the shadow group entries as well.
|
||||
*/
|
||||
if (is_shadow_grp && (sgr_locate (group_name) != NULL)) {
|
||||
if (sgr_remove (group_name) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot remove entry '%s' from %s\n"),
|
||||
Prog, group_name, sgr_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
}
|
||||
|
||||
/*
|
||||
* close_files - close all of the files that were opened
|
||||
*
|
||||
* close_files() closes all of the files that were opened for this
|
||||
* new group. This causes any modified entries to be written out.
|
||||
*/
|
||||
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 ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_DEL_GROUP, Prog,
|
||||
"removing group from /etc/group",
|
||||
group_name, (unsigned int) group_id,
|
||||
SHADOW_AUDIT_SUCCESS);
|
||||
#endif
|
||||
SYSLOG ((LOG_INFO,
|
||||
"group '%s' removed from %s",
|
||||
group_name, gr_dbname ()));
|
||||
del_cleanup (cleanup_report_del_group_group);
|
||||
|
||||
cleanup_unlock_group (NULL);
|
||||
del_cleanup (cleanup_unlock_group);
|
||||
|
||||
|
||||
/* Then, 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 ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_DEL_GROUP, Prog,
|
||||
"removing group from /etc/gshadow",
|
||||
group_name, (unsigned int) group_id,
|
||||
SHADOW_AUDIT_SUCCESS);
|
||||
#endif
|
||||
SYSLOG ((LOG_INFO,
|
||||
"group '%s' removed from %s",
|
||||
group_name, sgr_dbname ()));
|
||||
del_cleanup (cleanup_report_del_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_DEL_GROUP, Prog,
|
||||
"",
|
||||
group_name, (unsigned int) group_id,
|
||||
SHADOW_AUDIT_SUCCESS);
|
||||
#endif
|
||||
SYSLOG ((LOG_INFO, "group '%s' removed\n", group_name));
|
||||
del_cleanup (cleanup_report_del_group);
|
||||
}
|
||||
|
||||
/*
|
||||
* open_files - lock and open the group files
|
||||
*
|
||||
* open_files() opens the two group files.
|
||||
*/
|
||||
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 ());
|
||||
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 ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
add_cleanup (cleanup_unlock_gshadow, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Now, if the group is not removed, it's our fault.
|
||||
* Make sure failures will be reported.
|
||||
*/
|
||||
add_cleanup (cleanup_report_del_group, group_name);
|
||||
|
||||
/* An 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);
|
||||
}
|
||||
#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);
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
}
|
||||
|
||||
/*
|
||||
* group_busy - check if this is any user's primary group
|
||||
*
|
||||
* group_busy verifies that this group is not the primary group
|
||||
* for any user. You must remove all users before you remove
|
||||
* the group.
|
||||
*/
|
||||
static void group_busy (gid_t gid)
|
||||
{
|
||||
struct passwd *pwd;
|
||||
|
||||
/*
|
||||
* Nice slow linear search.
|
||||
*/
|
||||
|
||||
setpwent ();
|
||||
|
||||
while ( ((pwd = getpwent ()) != NULL) && (pwd->pw_gid != gid) );
|
||||
|
||||
endpwent ();
|
||||
|
||||
/*
|
||||
* If pwd isn't NULL, it stopped because the gid's matched.
|
||||
*/
|
||||
|
||||
if (pwd == (struct passwd *) 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Can't remove the group.
|
||||
*/
|
||||
fprintf (stderr,
|
||||
_("%s: cannot remove the primary group of user '%s'\n"),
|
||||
Prog, pwd->pw_name);
|
||||
exit (E_GROUP_BUSY);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv, "hR:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
group_name = argv[optind];
|
||||
}
|
||||
|
||||
/*
|
||||
* main - groupdel command
|
||||
*
|
||||
* The syntax of the groupdel command is
|
||||
*
|
||||
* groupdel group
|
||||
*
|
||||
* The named group will be deleted.
|
||||
*/
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
pam_handle_t *pamh = NULL;
|
||||
int retval;
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
|
||||
/*
|
||||
* 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 ("groupdel");
|
||||
#ifdef WITH_AUDIT
|
||||
audit_help_open ();
|
||||
#endif
|
||||
|
||||
if (atexit (do_cleanups) != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot setup cleanup service.\n"),
|
||||
Prog);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
{
|
||||
struct passwd *pampw;
|
||||
pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
|
||||
if (pampw == NULL) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot determine your user name.\n"),
|
||||
Prog);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
retval = pam_start ("groupdel", 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);
|
||||
}
|
||||
exit (1);
|
||||
}
|
||||
(void) pam_end (pamh, retval);
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
is_shadow_grp = sgr_file_present ();
|
||||
#endif
|
||||
|
||||
{
|
||||
struct group *grp;
|
||||
/*
|
||||
* Start with a quick check to see if the group exists.
|
||||
*/
|
||||
grp = getgrnam (group_name); /* local, no need for xgetgrnam */
|
||||
if (NULL == grp) {
|
||||
fprintf (stderr,
|
||||
_("%s: group '%s' does not exist\n"),
|
||||
Prog, group_name);
|
||||
exit (E_NOTFOUND);
|
||||
}
|
||||
|
||||
group_id = grp->gr_gid;
|
||||
}
|
||||
|
||||
#ifdef USE_NIS
|
||||
/*
|
||||
* Make sure this isn't a NIS group
|
||||
*/
|
||||
if (__isgrNIS ()) {
|
||||
char *nis_domain;
|
||||
char *nis_master;
|
||||
|
||||
fprintf (stderr,
|
||||
_("%s: group '%s' is a NIS group\n"),
|
||||
Prog, group_name);
|
||||
|
||||
if (!yp_get_default_domain (&nis_domain) &&
|
||||
!yp_master (nis_domain, "group.byname", &nis_master)) {
|
||||
fprintf (stderr,
|
||||
_("%s: %s is the NIS master\n"),
|
||||
Prog, nis_master);
|
||||
}
|
||||
exit (E_NOTFOUND);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Make sure this isn't the primary group of anyone.
|
||||
*/
|
||||
group_busy (group_id);
|
||||
|
||||
/*
|
||||
* Do the hard stuff - open the files, delete the group entries,
|
||||
* then close and update the files.
|
||||
*/
|
||||
open_files ();
|
||||
|
||||
grp_update ();
|
||||
|
||||
close_files ();
|
||||
|
||||
nscd_flush_cache ("group");
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
652
src/groupmems.c
Normal file
652
src/groupmems.c
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
871
src/groupmod.c
Normal file
871
src/groupmod.c
Normal file
@@ -0,0 +1,871 @@
|
||||
/*
|
||||
* Copyright (c) 1991 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* 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>
|
||||
|
||||
#ident "$Id: groupmod.c 3576 2011-11-13 16:24:57Z nekral-guest $"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <grp.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
#include "pam_defs.h"
|
||||
#include <pwd.h>
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
#include "chkname.h"
|
||||
#include "defines.h"
|
||||
#include "groupio.h"
|
||||
#include "pwio.h"
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
#ifdef SHADOWGRP
|
||||
#include "sgroupio.h"
|
||||
#endif
|
||||
/*
|
||||
* 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 */
|
||||
#define E_GID_IN_USE 4 /* gid already in use (and no -o) */
|
||||
#define E_NOTFOUND 6 /* specified group doesn't exist */
|
||||
#define E_NAME_IN_USE 9 /* group name already in use */
|
||||
#define E_GRP_UPDATE 10 /* can't update group file */
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
static bool is_shadow_grp;
|
||||
#endif /* SHADOWGRP */
|
||||
static char *group_name;
|
||||
static char *group_newname;
|
||||
static char *group_passwd;
|
||||
static gid_t group_id;
|
||||
static gid_t group_newid;
|
||||
|
||||
static struct cleanup_info_mod info_passwd;
|
||||
static struct cleanup_info_mod info_group;
|
||||
#ifdef SHADOWGRP
|
||||
static struct cleanup_info_mod info_gshadow;
|
||||
#endif
|
||||
|
||||
static bool
|
||||
oflg = false, /* permit non-unique group ID to be specified with -g */
|
||||
gflg = false, /* new ID value for the group */
|
||||
nflg = false, /* a new name has been specified for the group */
|
||||
pflg = false; /* new encrypted password */
|
||||
|
||||
/* local function prototypes */
|
||||
static void usage (int status);
|
||||
static void new_grent (struct group *);
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
static void new_sgent (struct sgrp *);
|
||||
#endif
|
||||
static void grp_update (void);
|
||||
static void check_new_gid (void);
|
||||
static void check_new_name (void);
|
||||
static void process_flags (int, char **);
|
||||
static void lock_files (void);
|
||||
static void prepare_failure_reports (void);
|
||||
static void open_files (void);
|
||||
static void close_files (void);
|
||||
static void update_primary_groups (gid_t ogid, gid_t ngid);
|
||||
|
||||
/*
|
||||
* usage - display usage message and exit
|
||||
*/
|
||||
|
||||
static void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options] GROUP\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(void) fputs (_(" -g, --gid GID change the group ID to GID\n"), usageout);
|
||||
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs (_(" -n, --new-name NEW_GROUP change the name to NEW_GROUP\n"), usageout);
|
||||
(void) fputs (_(" -o, --non-unique allow to use a duplicate (non-unique) GID\n"), usageout);
|
||||
(void) fputs (_(" -p, --password PASSWORD change the password to this (encrypted)\n"
|
||||
" PASSWORD\n"), usageout);
|
||||
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
(void) fputs ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* new_grent - updates the values in a group file entry
|
||||
*
|
||||
* new_grent() takes all of the values that have been entered and fills
|
||||
* in a (struct group) with them.
|
||||
*/
|
||||
static void new_grent (struct group *grent)
|
||||
{
|
||||
if (nflg) {
|
||||
grent->gr_name = xstrdup (group_newname);
|
||||
}
|
||||
|
||||
if (gflg) {
|
||||
grent->gr_gid = group_newid;
|
||||
}
|
||||
|
||||
if ( pflg
|
||||
#ifdef SHADOWGRP
|
||||
&& ( (!is_shadow_grp)
|
||||
|| (strcmp (grent->gr_passwd, SHADOW_PASSWD_STRING) != 0))
|
||||
#endif
|
||||
) {
|
||||
/* Update the password in group if there is no gshadow
|
||||
* file or if the password is currently in group
|
||||
* (gr_passwd != "x"). We do not force the usage of
|
||||
* shadow passwords if it was not the case before.
|
||||
*/
|
||||
grent->gr_passwd = group_passwd;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
/*
|
||||
* new_sgent - updates the values in a shadow group file entry
|
||||
*
|
||||
* new_sgent() takes all of the values that have been entered and fills
|
||||
* in a (struct sgrp) with them.
|
||||
*/
|
||||
static void new_sgent (struct sgrp *sgent)
|
||||
{
|
||||
if (nflg) {
|
||||
sgent->sg_name = xstrdup (group_newname);
|
||||
}
|
||||
|
||||
/* Always update the shadowed password if there is a shadow entry
|
||||
* (even if shadowed passwords might not be enabled for this group
|
||||
* (gr_passwd != "x")).
|
||||
* It seems better to update the password in both places in case a
|
||||
* shadow and a non shadow entry exist.
|
||||
* This might occur only if there were already both entries.
|
||||
*/
|
||||
if (pflg) {
|
||||
sgent->sg_passwd = group_passwd;
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
/*
|
||||
* grp_update - update group file entries
|
||||
*
|
||||
* grp_update() updates the new records in the memory databases.
|
||||
*/
|
||||
static void grp_update (void)
|
||||
{
|
||||
struct group grp;
|
||||
const struct group *ogrp;
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
struct sgrp sgrp;
|
||||
const struct sgrp *osgrp = NULL;
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
/*
|
||||
* Get the current settings for this group.
|
||||
*/
|
||||
ogrp = gr_locate (group_name);
|
||||
if (NULL == ogrp) {
|
||||
fprintf (stderr,
|
||||
_("%s: group '%s' does not exist in %s\n"),
|
||||
Prog, group_name, gr_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
grp = *ogrp;
|
||||
new_grent (&grp);
|
||||
#ifdef SHADOWGRP
|
||||
if ( is_shadow_grp
|
||||
&& (pflg || nflg)) {
|
||||
osgrp = sgr_locate (group_name);
|
||||
if (NULL != osgrp) {
|
||||
sgrp = *osgrp;
|
||||
new_sgent (&sgrp);
|
||||
} else if ( pflg
|
||||
&& (strcmp (grp.gr_passwd, SHADOW_PASSWD_STRING) == 0)) {
|
||||
static char *empty = NULL;
|
||||
/* If there is a gshadow file with no entries for
|
||||
* the group, but the group file indicates a
|
||||
* shadowed password, we force the creation of a
|
||||
* gshadow entry when a new password is requested.
|
||||
*/
|
||||
memset (&sgrp, 0, sizeof sgrp);
|
||||
sgrp.sg_name = xstrdup (grp.gr_name);
|
||||
sgrp.sg_passwd = xstrdup (grp.gr_passwd);
|
||||
sgrp.sg_adm = ∅
|
||||
sgrp.sg_mem = dup_list (grp.gr_mem);
|
||||
new_sgent (&sgrp);
|
||||
osgrp = &sgrp; /* entry needs to be committed */
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
if (gflg) {
|
||||
update_primary_groups (ogrp->gr_gid, group_newid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write out the new group file entry.
|
||||
*/
|
||||
if (gr_update (&grp) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, gr_dbname (), grp.gr_name);
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
if (nflg && (gr_remove (group_name) == 0)) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot remove entry '%s' from %s\n"),
|
||||
Prog, grp.gr_name, gr_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
/*
|
||||
* Make sure there was a shadow entry to begin with.
|
||||
*/
|
||||
if (NULL != osgrp) {
|
||||
/*
|
||||
* Write out the new shadow group entries as well.
|
||||
*/
|
||||
if (sgr_update (&sgrp) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, sgr_dbname (), sgrp.sg_name);
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
if (nflg && (sgr_remove (group_name) == 0)) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot remove entry '%s' from %s\n"),
|
||||
Prog, group_name, sgr_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
}
|
||||
|
||||
/*
|
||||
* check_new_gid - check the new GID value for uniqueness
|
||||
*
|
||||
* check_new_gid() insures that the new GID value is unique.
|
||||
*/
|
||||
static void check_new_gid (void)
|
||||
{
|
||||
/*
|
||||
* First, the easy stuff. If the ID can be duplicated, or if the ID
|
||||
* didn't really change, just return. If the ID didn't change, turn
|
||||
* off those flags. No sense doing needless work.
|
||||
*/
|
||||
if (group_id == group_newid) {
|
||||
gflg = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (oflg ||
|
||||
(getgrgid (group_newid) == NULL) /* local, no need for xgetgrgid */
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the user what they did wrong.
|
||||
*/
|
||||
fprintf (stderr,
|
||||
_("%s: GID '%lu' already exists\n"),
|
||||
Prog, (unsigned long int) group_newid);
|
||||
exit (E_GID_IN_USE);
|
||||
}
|
||||
|
||||
/*
|
||||
* check_new_name - check the new name for uniqueness
|
||||
*
|
||||
* check_new_name() insures that the new name does not exist already.
|
||||
* You can't have the same name twice, period.
|
||||
*/
|
||||
static void check_new_name (void)
|
||||
{
|
||||
/*
|
||||
* Make sure they are actually changing the name.
|
||||
*/
|
||||
if (strcmp (group_name, group_newname) == 0) {
|
||||
nflg = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_valid_group_name (group_newname)) {
|
||||
|
||||
/*
|
||||
* If the entry is found, too bad.
|
||||
*/
|
||||
/* local, no need for xgetgrnam */
|
||||
if (getgrnam (group_newname) != NULL) {
|
||||
fprintf (stderr,
|
||||
_("%s: group '%s' already exists\n"),
|
||||
Prog, group_newname);
|
||||
exit (E_NAME_IN_USE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* All invalid group names land here.
|
||||
*/
|
||||
|
||||
fprintf (stderr,
|
||||
_("%s: invalid group name '%s'\n"),
|
||||
Prog, group_newname);
|
||||
exit (E_BAD_ARG);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - perform command line argument setting
|
||||
*
|
||||
* process_flags() interprets the command line arguments and sets the
|
||||
* values that the user will be created with accordingly. The values
|
||||
* are checked for sanity.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"gid", required_argument, NULL, 'g'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"new-name", required_argument, NULL, 'n'},
|
||||
{"non-unique", no_argument, NULL, 'o'},
|
||||
{"password", required_argument, NULL, 'p'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
while ((c = getopt_long (argc, argv, "g:hn:op:R:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'g':
|
||||
gflg = true;
|
||||
if ( (get_gid (optarg, &group_newid) == 0)
|
||||
|| (group_newid == (gid_t)-1)) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid group ID '%s'\n"),
|
||||
Prog, optarg);
|
||||
exit (E_BAD_ARG);
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
break;
|
||||
case 'n':
|
||||
nflg = true;
|
||||
group_newname = optarg;
|
||||
break;
|
||||
case 'o':
|
||||
oflg = true;
|
||||
break;
|
||||
case 'p':
|
||||
group_passwd = optarg;
|
||||
pflg = true;
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (oflg && !gflg) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
if (optind != (argc - 1)) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
group_name = argv[argc - 1];
|
||||
}
|
||||
|
||||
/*
|
||||
* close_files - close all of the files that were opened
|
||||
*
|
||||
* close_files() closes all of the files that were opened for this new
|
||||
* group. This causes any modified entries to be written out.
|
||||
*/
|
||||
static void close_files (void)
|
||||
{
|
||||
if (gr_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, gr_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_USER_ACCT, Prog,
|
||||
info_group.audit_msg,
|
||||
group_name, AUDIT_NO_ID,
|
||||
SHADOW_AUDIT_SUCCESS);
|
||||
#endif
|
||||
SYSLOG ((LOG_INFO,
|
||||
"group changed in %s (%s)",
|
||||
gr_dbname (), info_group.action));
|
||||
del_cleanup (cleanup_report_mod_group);
|
||||
|
||||
cleanup_unlock_group (NULL);
|
||||
del_cleanup (cleanup_unlock_group);
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
if ( is_shadow_grp
|
||||
&& (pflg || nflg)) {
|
||||
if (sgr_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, sgr_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_USER_ACCT, Prog,
|
||||
info_gshadow.audit_msg,
|
||||
group_name, AUDIT_NO_ID,
|
||||
SHADOW_AUDIT_SUCCESS);
|
||||
#endif
|
||||
SYSLOG ((LOG_INFO,
|
||||
"group changed in %s (%s)",
|
||||
sgr_dbname (), info_gshadow.action));
|
||||
del_cleanup (cleanup_report_mod_gshadow);
|
||||
|
||||
cleanup_unlock_gshadow (NULL);
|
||||
del_cleanup (cleanup_unlock_gshadow);
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
if (gflg) {
|
||||
if (pw_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_USER_ACCT, Prog,
|
||||
info_passwd.audit_msg,
|
||||
group_name, AUDIT_NO_ID,
|
||||
SHADOW_AUDIT_SUCCESS);
|
||||
#endif
|
||||
SYSLOG ((LOG_INFO,
|
||||
"group changed in %s (%s)",
|
||||
pw_dbname (), info_passwd.action));
|
||||
del_cleanup (cleanup_report_mod_passwd);
|
||||
|
||||
cleanup_unlock_passwd (NULL);
|
||||
del_cleanup (cleanup_unlock_passwd);
|
||||
}
|
||||
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_USER_ACCT, Prog,
|
||||
"modifying group",
|
||||
group_name, AUDIT_NO_ID,
|
||||
SHADOW_AUDIT_SUCCESS);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* prepare_failure_reports - Prepare the cleanup_info structure for logging
|
||||
* of success and failure to syslog or audit.
|
||||
*/
|
||||
static void prepare_failure_reports (void)
|
||||
{
|
||||
info_group.name = group_name;
|
||||
#ifdef SHADOWGRP
|
||||
info_gshadow.name = group_name;
|
||||
#endif
|
||||
info_passwd.name = group_name;
|
||||
|
||||
info_group.audit_msg = xmalloc (512);
|
||||
#ifdef SHADOWGRP
|
||||
info_gshadow.audit_msg = xmalloc (512);
|
||||
#endif
|
||||
info_passwd.audit_msg = xmalloc (512);
|
||||
|
||||
(void) snprintf (info_group.audit_msg, 511,
|
||||
"changing %s; ", gr_dbname ());
|
||||
#ifdef SHADOWGRP
|
||||
(void) snprintf (info_gshadow.audit_msg, 511,
|
||||
"changing %s; ", sgr_dbname ());
|
||||
#endif
|
||||
(void) snprintf (info_passwd.audit_msg, 511,
|
||||
"changing %s; ", pw_dbname ());
|
||||
|
||||
info_group.action = info_group.audit_msg
|
||||
+ strlen (info_group.audit_msg);
|
||||
#ifdef SHADOWGRP
|
||||
info_gshadow.action = info_gshadow.audit_msg
|
||||
+ strlen (info_gshadow.audit_msg);
|
||||
#endif
|
||||
info_passwd.action = info_passwd.audit_msg
|
||||
+ strlen (info_passwd.audit_msg);
|
||||
|
||||
(void) snprintf (info_group.action,
|
||||
511 - strlen (info_group.audit_msg),
|
||||
"group %s/%lu",
|
||||
group_name, (unsigned long int) group_id);
|
||||
#ifdef SHADOWGRP
|
||||
(void) snprintf (info_gshadow.action,
|
||||
511 - strlen (info_group.audit_msg),
|
||||
"group %s", group_name);
|
||||
#endif
|
||||
(void) snprintf (info_passwd.action,
|
||||
511 - strlen (info_group.audit_msg),
|
||||
"group %s/%lu",
|
||||
group_name, (unsigned long int) group_id);
|
||||
|
||||
if (nflg) {
|
||||
strncat (info_group.action, ", new name: ",
|
||||
511 - strlen (info_group.audit_msg));
|
||||
strncat (info_group.action, group_newname,
|
||||
511 - strlen (info_group.audit_msg));
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
strncat (info_gshadow.action, ", new name: ",
|
||||
511 - strlen (info_gshadow.audit_msg));
|
||||
strncat (info_gshadow.action, group_newname,
|
||||
511 - strlen (info_gshadow.audit_msg));
|
||||
#endif
|
||||
|
||||
strncat (info_passwd.action, ", new name: ",
|
||||
511 - strlen (info_passwd.audit_msg));
|
||||
strncat (info_passwd.action, group_newname,
|
||||
511 - strlen (info_passwd.audit_msg));
|
||||
}
|
||||
if (pflg) {
|
||||
strncat (info_group.action, ", new password",
|
||||
511 - strlen (info_group.audit_msg));
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
strncat (info_gshadow.action, ", new password",
|
||||
511 - strlen (info_gshadow.audit_msg));
|
||||
#endif
|
||||
}
|
||||
if (gflg) {
|
||||
strncat (info_group.action, ", new gid: ",
|
||||
511 - strlen (info_group.audit_msg));
|
||||
(void) snprintf (info_group.action+strlen (info_group.action),
|
||||
511 - strlen (info_group.audit_msg),
|
||||
"%lu", (unsigned long int) group_newid);
|
||||
|
||||
strncat (info_passwd.action, ", new gid: ",
|
||||
511 - strlen (info_passwd.audit_msg));
|
||||
(void) snprintf (info_passwd.action+strlen (info_passwd.action),
|
||||
511 - strlen (info_passwd.audit_msg),
|
||||
"%lu", (unsigned long int) group_newid);
|
||||
}
|
||||
info_group.audit_msg[511] = '\0';
|
||||
#ifdef SHADOWGRP
|
||||
info_gshadow.audit_msg[511] = '\0';
|
||||
#endif
|
||||
info_passwd.audit_msg[511] = '\0';
|
||||
|
||||
// FIXME: add a system cleanup
|
||||
add_cleanup (cleanup_report_mod_group, &info_group);
|
||||
#ifdef SHADOWGRP
|
||||
if ( is_shadow_grp
|
||||
&& (pflg || nflg)) {
|
||||
add_cleanup (cleanup_report_mod_gshadow, &info_gshadow);
|
||||
}
|
||||
#endif
|
||||
if (gflg) {
|
||||
add_cleanup (cleanup_report_mod_passwd, &info_passwd);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* lock_files - lock the accounts databases
|
||||
*
|
||||
* lock_files() locks the group, gshadow, and passwd databases.
|
||||
*/
|
||||
static void lock_files (void)
|
||||
{
|
||||
if (gr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, gr_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
add_cleanup (cleanup_unlock_group, NULL);
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
if ( is_shadow_grp
|
||||
&& (pflg || nflg)) {
|
||||
if (sgr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, sgr_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
add_cleanup (cleanup_unlock_gshadow, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gflg) {
|
||||
if (pw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, pw_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
add_cleanup (cleanup_unlock_passwd, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* open_files - open the accounts databases
|
||||
*
|
||||
* open_files() opens the group, gshadow, and passwd databases.
|
||||
*/
|
||||
static void open_files (void)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
if ( is_shadow_grp
|
||||
&& (pflg || nflg)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
if (gflg) {
|
||||
if (pw_open (O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_WARN, "cannot open %s", gr_dbname ()));
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_primary_groups (gid_t ogid, gid_t ngid)
|
||||
{
|
||||
struct passwd *pwd;
|
||||
|
||||
setpwent ();
|
||||
while ((pwd = getpwent ()) != NULL) {
|
||||
if (pwd->pw_gid == ogid) {
|
||||
const struct passwd *lpwd;
|
||||
struct passwd npwd;
|
||||
lpwd = pw_locate (pwd->pw_name);
|
||||
if (NULL == lpwd) {
|
||||
fprintf (stderr,
|
||||
_("%s: user '%s' does not exist in %s\n"),
|
||||
Prog, pwd->pw_name, pw_dbname ());
|
||||
exit (E_GRP_UPDATE);
|
||||
} else {
|
||||
npwd = *lpwd;
|
||||
npwd.pw_gid = ngid;
|
||||
if (pw_update (&npwd) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, pw_dbname (), npwd.pw_name);
|
||||
exit (E_GRP_UPDATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
endpwent ();
|
||||
}
|
||||
|
||||
/*
|
||||
* main - groupmod command
|
||||
*
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
pam_handle_t *pamh = NULL;
|
||||
int retval;
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
|
||||
/*
|
||||
* 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 ("groupmod");
|
||||
#ifdef WITH_AUDIT
|
||||
audit_help_open ();
|
||||
#endif
|
||||
|
||||
if (atexit (do_cleanups) != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot setup cleanup service.\n"),
|
||||
Prog);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
#ifdef ACCT_TOOLS_SETUID
|
||||
#ifdef USE_PAM
|
||||
{
|
||||
struct passwd *pampw;
|
||||
pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
|
||||
if (NULL == pampw) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot determine your user name.\n"),
|
||||
Prog);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
retval = pam_start ("groupmod", 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);
|
||||
}
|
||||
exit (1);
|
||||
}
|
||||
(void) pam_end (pamh, retval);
|
||||
#endif /* USE_PAM */
|
||||
#endif /* ACCT_TOOLS_SETUID */
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
is_shadow_grp = sgr_file_present ();
|
||||
#endif
|
||||
{
|
||||
struct group *grp;
|
||||
/*
|
||||
* Start with a quick check to see if the group exists.
|
||||
*/
|
||||
grp = getgrnam (group_name); /* local, no need for xgetgrnam */
|
||||
if (NULL == grp) {
|
||||
fprintf (stderr,
|
||||
_("%s: group '%s' does not exist\n"),
|
||||
Prog, group_name);
|
||||
exit (E_NOTFOUND);
|
||||
} else {
|
||||
group_id = grp->gr_gid;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_NIS
|
||||
/*
|
||||
* Now make sure it isn't an NIS group.
|
||||
*/
|
||||
if (__isgrNIS ()) {
|
||||
char *nis_domain;
|
||||
char *nis_master;
|
||||
|
||||
fprintf (stderr,
|
||||
_("%s: group %s is a NIS group\n"),
|
||||
Prog, group_name);
|
||||
|
||||
if (!yp_get_default_domain (&nis_domain) &&
|
||||
!yp_master (nis_domain, "group.byname", &nis_master)) {
|
||||
fprintf (stderr,
|
||||
_("%s: %s is the NIS master\n"),
|
||||
Prog, nis_master);
|
||||
}
|
||||
exit (E_NOTFOUND);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gflg) {
|
||||
check_new_gid ();
|
||||
}
|
||||
|
||||
if (nflg) {
|
||||
check_new_name ();
|
||||
}
|
||||
|
||||
lock_files ();
|
||||
|
||||
/*
|
||||
* Now if the group is not changed, it's our fault.
|
||||
* Make sure failures will be reported.
|
||||
*/
|
||||
prepare_failure_reports ();
|
||||
|
||||
/*
|
||||
* Do the hard stuff - open the files, create the group entries,
|
||||
* then close and update the files.
|
||||
*/
|
||||
open_files ();
|
||||
|
||||
grp_update ();
|
||||
|
||||
close_files ();
|
||||
|
||||
nscd_flush_cache ("group");
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
218
src/groups.c
Normal file
218
src/groups.c
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (c) 1991 - 1993, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 - 2006, Tomasz Kłoczko
|
||||
* Copyright (c) 2007 - 2008, 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>
|
||||
|
||||
#ident "$Id: groups.c 3233 2010-08-22 19:36:09Z nekral-guest $"
|
||||
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include "defines.h"
|
||||
#include "prototypes.h"
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
/* local function prototypes */
|
||||
static void print_groups (const char *member);
|
||||
|
||||
/*
|
||||
* print_groups - print the groups which the named user is a member of
|
||||
*
|
||||
* print_groups() scans the groups file for the list of groups which
|
||||
* the user is listed as being a member of.
|
||||
*/
|
||||
static void print_groups (const char *member)
|
||||
{
|
||||
int groups = 0;
|
||||
struct group *grp;
|
||||
struct passwd *pwd;
|
||||
bool flag = false;
|
||||
|
||||
pwd = getpwnam (member); /* local, no need for xgetpwnam */
|
||||
if (NULL == pwd) {
|
||||
(void) fprintf (stderr, _("%s: unknown user %s\n"),
|
||||
Prog, member);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
setgrent ();
|
||||
while ((grp = getgrent ()) != NULL) {
|
||||
if (is_on_list (grp->gr_mem, member)) {
|
||||
if (0 != groups) {
|
||||
(void) putchar (' ');
|
||||
}
|
||||
groups++;
|
||||
|
||||
(void) printf ("%s", grp->gr_name);
|
||||
if (grp->gr_gid == pwd->pw_gid) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
endgrent ();
|
||||
|
||||
/* The user may not be in the list of members of its primary group */
|
||||
if (!flag) {
|
||||
grp = getgrgid (pwd->pw_gid); /* local, no need for xgetgrgid */
|
||||
if (NULL != grp) {
|
||||
if (0 != groups) {
|
||||
(void) putchar (' ');
|
||||
}
|
||||
groups++;
|
||||
|
||||
(void) printf ("%s", grp->gr_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 != groups) {
|
||||
(void) putchar ('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* groups - print out the groups a process is a member of
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
#ifdef HAVE_GETGROUPS
|
||||
long sys_ngroups;
|
||||
GETGROUPS_T *groups;
|
||||
#else
|
||||
char *logname;
|
||||
char *getlogin ();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GETGROUPS
|
||||
sys_ngroups = sysconf (_SC_NGROUPS_MAX);
|
||||
groups = (GETGROUPS_T *) malloc (sizeof (GETGROUPS_T) * sys_ngroups);
|
||||
#endif
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
/*
|
||||
* Get the program name so that error messages can use it.
|
||||
*/
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
if (argc == 1) {
|
||||
|
||||
/*
|
||||
* Called with no arguments - give the group set for the
|
||||
* current user.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_GETGROUPS
|
||||
int i;
|
||||
int pri_grp; /* TODO: should be GETGROUPS_T */
|
||||
/*
|
||||
* This system supports concurrent group sets, so I can ask
|
||||
* the system to tell me which groups are currently set for
|
||||
* this process.
|
||||
*/
|
||||
int ngroups = getgroups (sys_ngroups, groups);
|
||||
if (ngroups < 0) {
|
||||
perror ("getgroups");
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* The groupset includes the primary group as well.
|
||||
*/
|
||||
pri_grp = getegid ();
|
||||
for (i = 0; i < ngroups; i++) {
|
||||
if (pri_grp == (int) groups[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i != ngroups) {
|
||||
pri_grp = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print out the name of every group in the current group
|
||||
* set. Unknown groups are printed as their decimal group ID
|
||||
* values.
|
||||
*/
|
||||
if (-1 != pri_grp) {
|
||||
struct group *gr;
|
||||
/* local, no need for xgetgrgid */
|
||||
gr = getgrgid (pri_grp);
|
||||
if (NULL != gr) {
|
||||
(void) printf ("%s", gr->gr_name);
|
||||
} else {
|
||||
(void) printf ("%d", pri_grp);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ngroups; i++) {
|
||||
struct group *gr;
|
||||
if ((0 != i) || (-1 != pri_grp)) {
|
||||
(void) putchar (' ');
|
||||
}
|
||||
|
||||
/* local, no need for xgetgrgid */
|
||||
gr = getgrgid (groups[i]);
|
||||
if (NULL != gr) {
|
||||
(void) printf ("%s", gr->gr_name);
|
||||
} else {
|
||||
(void) printf ("%ld", (long) groups[i]);
|
||||
}
|
||||
}
|
||||
(void) putchar ('\n');
|
||||
#else
|
||||
/*
|
||||
* This system does not have the getgroups() system call, so
|
||||
* I must check the groups file directly.
|
||||
*/
|
||||
logname = getlogin ();
|
||||
if (NULL != logname) {
|
||||
print_groups (logname);
|
||||
} else {
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
|
||||
/*
|
||||
* The invoker wanted to know about some other user. Use
|
||||
* that name to look up the groups instead.
|
||||
*/
|
||||
print_groups (argv[1]);
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
887
src/grpck.c
Normal file
887
src/grpck.c
Normal file
@@ -0,0 +1,887 @@
|
||||
/*
|
||||
* Copyright (c) 1992 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 , Michał Moskal
|
||||
* Copyright (c) 2001 - 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>
|
||||
|
||||
#ident "$Id: grpck.c 3559 2011-11-06 18:39:53Z nekral-guest $"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <getopt.h>
|
||||
#include "chkname.h"
|
||||
#include "commonio.h"
|
||||
#include "defines.h"
|
||||
#include "groupio.h"
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
#include "sgroupio.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Exit codes
|
||||
*/
|
||||
/*@-exitarg@*/
|
||||
#define E_OKAY 0
|
||||
#define E_SUCCESS 0
|
||||
#define E_USAGE 1
|
||||
#define E_BAD_ENTRY 2
|
||||
#define E_CANT_OPEN 3
|
||||
#define E_CANT_LOCK 4
|
||||
#define E_CANT_UPDATE 5
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static const char *grp_file = GROUP_FILE;
|
||||
static bool use_system_grp_file = true;
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
static const char *sgr_file = SGROUP_FILE;
|
||||
static bool use_system_sgr_file = true;
|
||||
static bool is_shadow = false;
|
||||
static bool sgr_locked = false;
|
||||
#endif
|
||||
static bool gr_locked = false;
|
||||
/* Options */
|
||||
static bool read_only = false;
|
||||
static bool sort_mode = false;
|
||||
|
||||
/* local function prototypes */
|
||||
static void fail_exit (int status);
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void delete_member (char **, const char *);
|
||||
static void process_flags (int argc, char **argv);
|
||||
static void open_files (void);
|
||||
static void close_files (bool changed);
|
||||
static int check_members (const char *groupname,
|
||||
char **members,
|
||||
const char *fmt_info,
|
||||
const char *fmt_prompt,
|
||||
const char *fmt_syslog,
|
||||
int *errors);
|
||||
static void check_grp_file (int *errors, bool *changed);
|
||||
#ifdef SHADOWGRP
|
||||
static void compare_members_lists (const char *groupname,
|
||||
char **members,
|
||||
char **other_members,
|
||||
const char *file,
|
||||
const char *other_file);
|
||||
static void check_sgr_file (int *errors, bool *changed);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* fail_exit - exit with an error code after unlocking files
|
||||
*/
|
||||
static void fail_exit (int status)
|
||||
{
|
||||
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
|
||||
|
||||
closelog ();
|
||||
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* usage - print syntax message and exit
|
||||
*/
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
#ifdef SHADOWGRP
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options] [group [gshadow]]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
#else /* !SHADOWGRP */
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options] [group]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
#endif /* !SHADOWGRP */
|
||||
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs (_(" -r, --read-only display errors and warnings\n"
|
||||
" but do not change files\n"), usageout);
|
||||
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
(void) fputs (_(" -s, --sort sort entries by UID\n"), usageout);
|
||||
(void) fputs ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* delete_member - delete an entry in a list of members
|
||||
*
|
||||
* It only deletes the first entry with the given name.
|
||||
* The member is defined by its address, no string comparison are
|
||||
* performed.
|
||||
*/
|
||||
static void delete_member (char **list, const char *member)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; NULL != list[i]; i++) {
|
||||
if (list[i] == member) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (; NULL != list[i]; i++) {
|
||||
list[i] = list[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"quiet", no_argument, NULL, 'q'},
|
||||
{"read-only", no_argument, NULL, 'r'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{"sort", no_argument, NULL, 's'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
/*
|
||||
* Parse the command line arguments
|
||||
*/
|
||||
while ((c = getopt_long (argc, argv, "hqrR:s",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'q':
|
||||
/* quiet - ignored for now */
|
||||
break;
|
||||
case 'r':
|
||||
read_only = true;
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
case 's':
|
||||
sort_mode = true;
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (sort_mode && read_only) {
|
||||
fprintf (stderr, _("%s: -s and -r are incompatible\n"), Prog);
|
||||
exit (E_USAGE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make certain we have the right number of arguments
|
||||
*/
|
||||
#ifdef SHADOWGRP
|
||||
if (argc > (optind + 2))
|
||||
#else
|
||||
if (argc > (optind + 1))
|
||||
#endif
|
||||
{
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
/*
|
||||
* If there are two left over filenames, use those as the group and
|
||||
* group password filenames.
|
||||
*/
|
||||
if (optind != argc) {
|
||||
grp_file = argv[optind];
|
||||
gr_setdbname (grp_file);
|
||||
use_system_grp_file = false;
|
||||
}
|
||||
#ifdef SHADOWGRP
|
||||
if ((optind + 2) == argc) {
|
||||
sgr_file = argv[optind + 1];
|
||||
sgr_setdbname (sgr_file);
|
||||
is_shadow = true;
|
||||
use_system_sgr_file = false;
|
||||
} else if (optind == argc) {
|
||||
is_shadow = sgr_file_present ();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* open_files - open the shadow database
|
||||
*
|
||||
* In read-only mode, the databases are not locked and are opened
|
||||
* only for reading.
|
||||
*/
|
||||
static void open_files (void)
|
||||
{
|
||||
/*
|
||||
* Lock the files if we aren't in "read-only" mode
|
||||
*/
|
||||
if (!read_only) {
|
||||
if (gr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, grp_file);
|
||||
fail_exit (E_CANT_LOCK);
|
||||
}
|
||||
gr_locked = true;
|
||||
#ifdef SHADOWGRP
|
||||
if (is_shadow) {
|
||||
if (sgr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, sgr_file);
|
||||
fail_exit (E_CANT_LOCK);
|
||||
}
|
||||
sgr_locked = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Open the files. Use O_RDONLY if we are in read_only mode,
|
||||
* O_RDWR otherwise.
|
||||
*/
|
||||
if (gr_open (read_only ? O_RDONLY : O_RDWR) == 0) {
|
||||
fprintf (stderr, _("%s: cannot open %s\n"), Prog,
|
||||
grp_file);
|
||||
if (use_system_grp_file) {
|
||||
SYSLOG ((LOG_WARN, "cannot open %s", grp_file));
|
||||
}
|
||||
fail_exit (E_CANT_OPEN);
|
||||
}
|
||||
#ifdef SHADOWGRP
|
||||
if (is_shadow && (sgr_open (read_only ? O_RDONLY : O_RDWR) == 0)) {
|
||||
fprintf (stderr, _("%s: cannot open %s\n"), Prog,
|
||||
sgr_file);
|
||||
if (use_system_sgr_file) {
|
||||
SYSLOG ((LOG_WARN, "cannot open %s", sgr_file));
|
||||
}
|
||||
fail_exit (E_CANT_OPEN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* close_files - close and unlock the group/gshadow databases
|
||||
*
|
||||
* If changed is not set, the databases are not closed, and no
|
||||
* changes are committed in the databases. The databases are
|
||||
* unlocked anyway.
|
||||
*/
|
||||
static void close_files (bool changed)
|
||||
{
|
||||
/*
|
||||
* All done. If there were no change we can just abandon any
|
||||
* changes to the files.
|
||||
*/
|
||||
if (changed) {
|
||||
if (gr_close () == 0) {
|
||||
fprintf (stderr, _("%s: failure while writing changes to %s\n"),
|
||||
Prog, grp_file);
|
||||
fail_exit (E_CANT_UPDATE);
|
||||
}
|
||||
#ifdef SHADOWGRP
|
||||
if (is_shadow && (sgr_close () == 0)) {
|
||||
fprintf (stderr, _("%s: failure while writing changes to %s\n"),
|
||||
Prog, sgr_file);
|
||||
fail_exit (E_CANT_UPDATE);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't be anti-social - unlock the files when you're done.
|
||||
*/
|
||||
#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 */
|
||||
}
|
||||
sgr_locked = false;
|
||||
}
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_members - check that every members of a group exist
|
||||
*
|
||||
* If an error is detected, *errors is incremented.
|
||||
*
|
||||
* The user will be prompted for the removal of the non-existent
|
||||
* user.
|
||||
*
|
||||
* If any changes are performed, the return value will be 1,
|
||||
* otherwise check_members() returns 0.
|
||||
*
|
||||
* fmt_info, fmt_prompt, and fmt_syslog are used for logging.
|
||||
* * fmt_info must contain two string flags (%s): for the group's
|
||||
* name and the missing member.
|
||||
* * fmt_prompt must contain one string flags (%s): the missing
|
||||
* member.
|
||||
* * fmt_syslog must contain two string flags (%s): for the
|
||||
* group's name and the missing member.
|
||||
*/
|
||||
static int check_members (const char *groupname,
|
||||
char **members,
|
||||
const char *fmt_info,
|
||||
const char *fmt_prompt,
|
||||
const char *fmt_syslog,
|
||||
int *errors)
|
||||
{
|
||||
int i;
|
||||
int members_changed = 0;
|
||||
|
||||
/*
|
||||
* Make sure each member exists
|
||||
*/
|
||||
for (i = 0; NULL != members[i]; i++) {
|
||||
/* local, no need for xgetpwnam */
|
||||
if (getpwnam (members[i]) != NULL) {
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* Can't find this user. Remove them
|
||||
* from the list.
|
||||
*/
|
||||
*errors += 1;
|
||||
printf (fmt_info, groupname, members[i]);
|
||||
printf (fmt_prompt, members[i]);
|
||||
|
||||
if (!yes_or_no (read_only)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SYSLOG ((LOG_INFO, fmt_syslog, members[i], groupname));
|
||||
members_changed = 1;
|
||||
delete_member (members, members[i]);
|
||||
|
||||
/* Rewind in case of removal */
|
||||
i--;
|
||||
}
|
||||
|
||||
return members_changed;
|
||||
}
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
/*
|
||||
* compare_members_lists - make sure the list of members is contained in
|
||||
* another list.
|
||||
*
|
||||
* compare_members_lists() checks that all the members of members are
|
||||
* also in other_members.
|
||||
* file and other_file are used for logging.
|
||||
*
|
||||
* TODO: No changes are performed on the lists.
|
||||
*/
|
||||
static void compare_members_lists (const char *groupname,
|
||||
char **members,
|
||||
char **other_members,
|
||||
const char *file,
|
||||
const char *other_file)
|
||||
{
|
||||
char **pmem, **other_pmem;
|
||||
|
||||
for (pmem = members; NULL != *pmem; pmem++) {
|
||||
for (other_pmem = other_members; NULL != *other_pmem; other_pmem++) {
|
||||
if (strcmp (*pmem, *other_pmem) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*other_pmem == NULL) {
|
||||
printf
|
||||
("'%s' is a member of the '%s' group in %s but not in %s\n",
|
||||
*pmem, groupname, file, other_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
/*
|
||||
* check_grp_file - check the content of the group file
|
||||
*/
|
||||
static void check_grp_file (int *errors, bool *changed)
|
||||
{
|
||||
struct commonio_entry *gre, *tgre;
|
||||
struct group *grp;
|
||||
#ifdef SHADOWGRP
|
||||
struct sgrp *sgr;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Loop through the entire group file.
|
||||
*/
|
||||
for (gre = __gr_get_head (); NULL != gre; gre = gre->next) {
|
||||
/*
|
||||
* Skip all NIS entries.
|
||||
*/
|
||||
|
||||
if ((gre->line[0] == '+') || (gre->line[0] == '-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start with the entries that are completely corrupt. They
|
||||
* have no (struct group) entry because they couldn't be
|
||||
* parsed properly.
|
||||
*/
|
||||
if (NULL == gre->eptr) {
|
||||
|
||||
/*
|
||||
* Tell the user this entire line is bogus and ask
|
||||
* them to delete it.
|
||||
*/
|
||||
(void) puts (_("invalid group file entry"));
|
||||
printf (_("delete line '%s'? "), gre->line);
|
||||
*errors += 1;
|
||||
|
||||
/*
|
||||
* prompt the user to delete the entry or not
|
||||
*/
|
||||
if (!yes_or_no (read_only)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* All group file deletions wind up here. This code
|
||||
* removes the current entry from the linked list.
|
||||
* When done, it skips back to the top of the loop
|
||||
* to try out the next list element.
|
||||
*/
|
||||
delete_gr:
|
||||
SYSLOG ((LOG_INFO, "delete group line '%s'",
|
||||
gre->line));
|
||||
*changed = true;
|
||||
|
||||
__gr_del_entry (gre);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Group structure is good, start using it.
|
||||
*/
|
||||
grp = gre->eptr;
|
||||
|
||||
/*
|
||||
* Make sure this entry has a unique name.
|
||||
*/
|
||||
for (tgre = __gr_get_head (); NULL != tgre; tgre = tgre->next) {
|
||||
|
||||
const struct group *ent = tgre->eptr;
|
||||
|
||||
/*
|
||||
* Don't check this entry
|
||||
*/
|
||||
if (tgre == gre) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't check invalid entries.
|
||||
*/
|
||||
if (NULL == ent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp (grp->gr_name, ent->gr_name) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the user this entry is a duplicate of
|
||||
* another and ask them to delete it.
|
||||
*/
|
||||
(void) puts (_("duplicate group entry"));
|
||||
printf (_("delete line '%s'? "), gre->line);
|
||||
*errors += 1;
|
||||
|
||||
/*
|
||||
* prompt the user to delete the entry or not
|
||||
*/
|
||||
if (yes_or_no (read_only)) {
|
||||
goto delete_gr;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for invalid group names. --marekm
|
||||
*/
|
||||
if (!is_valid_group_name (grp->gr_name)) {
|
||||
*errors += 1;
|
||||
printf (_("invalid group name '%s'\n"), grp->gr_name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for invalid group ID.
|
||||
*/
|
||||
if (grp->gr_gid == (gid_t)-1) {
|
||||
printf (_("invalid group ID '%lu'\n"), (long unsigned int)grp->gr_gid);
|
||||
*errors += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Workaround for a NYS libc 5.3.12 bug on RedHat 4.2 -
|
||||
* groups with no members are returned as groups with one
|
||||
* member "", causing grpck to fail. --marekm
|
||||
*/
|
||||
if ( (NULL != grp->gr_mem[0])
|
||||
&& (NULL == grp->gr_mem[1])
|
||||
&& ('\0' == grp->gr_mem[0][0])) {
|
||||
grp->gr_mem[0] = NULL;
|
||||
}
|
||||
|
||||
if (check_members (grp->gr_name, grp->gr_mem,
|
||||
_("group %s: no user %s\n"),
|
||||
_("delete member '%s'? "),
|
||||
"delete member '%s' from group '%s'",
|
||||
errors) == 1) {
|
||||
*changed = true;
|
||||
gre->changed = true;
|
||||
__gr_set_changed ();
|
||||
}
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
/*
|
||||
* Make sure this entry exists in the /etc/gshadow file.
|
||||
*/
|
||||
|
||||
if (is_shadow) {
|
||||
sgr = (struct sgrp *) sgr_locate (grp->gr_name);
|
||||
if (sgr == NULL) {
|
||||
printf (_("no matching group file entry in %s\n"),
|
||||
sgr_file);
|
||||
printf (_("add group '%s' in %s? "),
|
||||
grp->gr_name, sgr_file);
|
||||
*errors += 1;
|
||||
if (yes_or_no (read_only)) {
|
||||
struct sgrp sg;
|
||||
struct group gr;
|
||||
static char *empty = NULL;
|
||||
|
||||
sg.sg_name = grp->gr_name;
|
||||
sg.sg_passwd = grp->gr_passwd;
|
||||
sg.sg_adm = ∅
|
||||
sg.sg_mem = grp->gr_mem;
|
||||
SYSLOG ((LOG_INFO,
|
||||
"add group '%s' to '%s'",
|
||||
grp->gr_name, sgr_file));
|
||||
*changed = true;
|
||||
|
||||
if (sgr_update (&sg) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, sgr_dbname (), sg.sg_name);
|
||||
fail_exit (E_CANT_UPDATE);
|
||||
}
|
||||
/* remove password from /etc/group */
|
||||
gr = *grp;
|
||||
gr.gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
|
||||
if (gr_update (&gr) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, gr_dbname (), gr.gr_name);
|
||||
fail_exit (E_CANT_UPDATE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Verify that all the members defined in /etc/group are also
|
||||
* present in /etc/gshadow.
|
||||
*/
|
||||
compare_members_lists (grp->gr_name,
|
||||
grp->gr_mem, sgr->sg_mem,
|
||||
grp_file, sgr_file);
|
||||
|
||||
/* The group entry has a gshadow counterpart.
|
||||
* Make sure no passwords are in group.
|
||||
*/
|
||||
if (strcmp (grp->gr_passwd, SHADOW_PASSWD_STRING) != 0) {
|
||||
printf (_("group %s has an entry in %s, but its password field in %s is not set to 'x'\n"),
|
||||
grp->gr_name, sgr_file, grp_file);
|
||||
*errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
/*
|
||||
* check_sgr_file - check the content of the shadowed group file (gshadow)
|
||||
*/
|
||||
static void check_sgr_file (int *errors, bool *changed)
|
||||
{
|
||||
struct group *grp;
|
||||
struct commonio_entry *sge, *tsge;
|
||||
struct sgrp *sgr;
|
||||
|
||||
/*
|
||||
* Loop through the entire shadow group file.
|
||||
*/
|
||||
for (sge = __sgr_get_head (); NULL != sge; sge = sge->next) {
|
||||
|
||||
/*
|
||||
* Start with the entries that are completely corrupt. They
|
||||
* have no (struct sgrp) entry because they couldn't be
|
||||
* parsed properly.
|
||||
*/
|
||||
if (NULL == sge->eptr) {
|
||||
|
||||
/*
|
||||
* Tell the user this entire line is bogus and ask
|
||||
* them to delete it.
|
||||
*/
|
||||
(void) puts (_("invalid shadow group file entry"));
|
||||
printf (_("delete line '%s'? "), sge->line);
|
||||
*errors += 1;
|
||||
|
||||
/*
|
||||
* prompt the user to delete the entry or not
|
||||
*/
|
||||
if (!yes_or_no (read_only)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* All shadow group file deletions wind up here.
|
||||
* This code removes the current entry from the
|
||||
* linked list. When done, it skips back to the top
|
||||
* of the loop to try out the next list element.
|
||||
*/
|
||||
delete_sg:
|
||||
SYSLOG ((LOG_INFO, "delete shadow line '%s'",
|
||||
sge->line));
|
||||
*changed = true;
|
||||
|
||||
__sgr_del_entry (sge);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Shadow group structure is good, start using it.
|
||||
*/
|
||||
sgr = sge->eptr;
|
||||
|
||||
/*
|
||||
* Make sure this entry has a unique name.
|
||||
*/
|
||||
for (tsge = __sgr_get_head (); NULL != tsge; tsge = tsge->next) {
|
||||
|
||||
const struct sgrp *ent = tsge->eptr;
|
||||
|
||||
/*
|
||||
* Don't check this entry
|
||||
*/
|
||||
if (tsge == sge) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't check invalid entries.
|
||||
*/
|
||||
if (NULL == ent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp (sgr->sg_name, ent->sg_name) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the user this entry is a duplicate of
|
||||
* another and ask them to delete it.
|
||||
*/
|
||||
(void) puts (_("duplicate shadow group entry"));
|
||||
printf (_("delete line '%s'? "), sge->line);
|
||||
*errors += 1;
|
||||
|
||||
/*
|
||||
* prompt the user to delete the entry or not
|
||||
*/
|
||||
if (yes_or_no (read_only)) {
|
||||
goto delete_sg;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure this entry exists in the /etc/group file.
|
||||
*/
|
||||
grp = (struct group *) gr_locate (sgr->sg_name);
|
||||
if (grp == NULL) {
|
||||
printf (_("no matching group file entry in %s\n"),
|
||||
grp_file);
|
||||
printf (_("delete line '%s'? "), sge->line);
|
||||
*errors += 1;
|
||||
if (yes_or_no (read_only)) {
|
||||
goto delete_sg;
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Verify that the all members defined in /etc/gshadow are also
|
||||
* present in /etc/group.
|
||||
*/
|
||||
compare_members_lists (sgr->sg_name,
|
||||
sgr->sg_mem, grp->gr_mem,
|
||||
sgr_file, grp_file);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure each administrator exists
|
||||
*/
|
||||
if (check_members (sgr->sg_name, sgr->sg_adm,
|
||||
_("shadow group %s: no administrative user %s\n"),
|
||||
_("delete administrative member '%s'? "),
|
||||
"delete admin '%s' from shadow group '%s'",
|
||||
errors) == 1) {
|
||||
*changed = true;
|
||||
sge->changed = true;
|
||||
__sgr_set_changed ();
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure each member exists
|
||||
*/
|
||||
if (check_members (sgr->sg_name, sgr->sg_mem,
|
||||
_("shadow group %s: no user %s\n"),
|
||||
_("delete member '%s'? "),
|
||||
"delete member '%s' from shadow group '%s'",
|
||||
errors) == 1) {
|
||||
*changed = true;
|
||||
sge->changed = true;
|
||||
__sgr_set_changed ();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
|
||||
/*
|
||||
* grpck - verify group file integrity
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
int errors = 0;
|
||||
bool changed = false;
|
||||
|
||||
/*
|
||||
* 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 ("grpck");
|
||||
|
||||
/* Parse the command line arguments */
|
||||
process_flags (argc, argv);
|
||||
|
||||
open_files ();
|
||||
|
||||
if (sort_mode) {
|
||||
gr_sort ();
|
||||
#ifdef SHADOWGRP
|
||||
if (is_shadow) {
|
||||
sgr_sort ();
|
||||
}
|
||||
changed = true;
|
||||
#endif
|
||||
} else {
|
||||
check_grp_file (&errors, &changed);
|
||||
#ifdef SHADOWGRP
|
||||
if (is_shadow) {
|
||||
check_sgr_file (&errors, &changed);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Commit the change in the database if needed */
|
||||
close_files (changed);
|
||||
|
||||
nscd_flush_cache ("group");
|
||||
|
||||
/*
|
||||
* Tell the user what we did and exit.
|
||||
*/
|
||||
if (0 != errors) {
|
||||
if (changed) {
|
||||
printf (_("%s: the files have been updated\n"), Prog);
|
||||
} else {
|
||||
printf (_("%s: no changes\n"), Prog);
|
||||
}
|
||||
}
|
||||
|
||||
return ((0 != errors) ? E_BAD_ENTRY : E_OKAY);
|
||||
}
|
||||
|
||||
286
src/grpconv.c
Normal file
286
src/grpconv.c
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2002 - 2006, Tomasz Kłoczko
|
||||
* Copyright (c) 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* grpconv - create or update /etc/gshadow with information from
|
||||
* /etc/group.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#ident "$Id: grpconv.c 3640 2011-11-19 21:51:52Z nekral-guest $"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <grp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
#ifdef SHADOWGRP
|
||||
#include "groupio.h"
|
||||
#include "sgroupio.h"
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static bool gr_locked = false;
|
||||
static bool sgr_locked = false;
|
||||
|
||||
/* local function prototypes */
|
||||
static void fail_exit (int status);
|
||||
static void usage (int status);
|
||||
static void process_flags (int argc, char **argv);
|
||||
|
||||
static void fail_exit (int status)
|
||||
{
|
||||
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 */
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
}
|
||||
}
|
||||
|
||||
exit (status);
|
||||
}
|
||||
|
||||
static void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(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 ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv, "hR:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
const struct group *gr;
|
||||
struct group grent;
|
||||
const struct sgrp *sg;
|
||||
struct sgrp sgent;
|
||||
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
OPENLOG ("grpconv");
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
if (gr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, gr_dbname ());
|
||||
fail_exit (5);
|
||||
}
|
||||
gr_locked = true;
|
||||
if (gr_open (O_RDWR) == 0) {
|
||||
fprintf (stderr, _("%s: cannot open %s\n"), Prog, gr_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
if (sgr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, sgr_dbname ());
|
||||
fail_exit (5);
|
||||
}
|
||||
sgr_locked = true;
|
||||
if (sgr_open (O_CREAT | O_RDWR) == 0) {
|
||||
fprintf (stderr, _("%s: cannot open %s\n"), Prog, sgr_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove /etc/gshadow entries for groups not in /etc/group.
|
||||
*/
|
||||
(void) sgr_rewind ();
|
||||
while ((sg = sgr_next ()) != NULL) {
|
||||
if (gr_locate (sg->sg_name) != NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sgr_remove (sg->sg_name) == 0) {
|
||||
/*
|
||||
* This shouldn't happen (the entry exists) but...
|
||||
*/
|
||||
fprintf (stderr,
|
||||
_("%s: cannot remove entry '%s' from %s\n"),
|
||||
Prog, sg->sg_name, sgr_dbname ());
|
||||
fail_exit (3);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update shadow group passwords if non-shadow password is not "x".
|
||||
* Add any missing shadow group entries.
|
||||
*/
|
||||
(void) gr_rewind ();
|
||||
while ((gr = gr_next ()) != NULL) {
|
||||
sg = sgr_locate (gr->gr_name);
|
||||
if (NULL != sg) {
|
||||
/* update existing shadow group entry */
|
||||
sgent = *sg;
|
||||
if (strcmp (gr->gr_passwd, SHADOW_PASSWD_STRING) != 0)
|
||||
sgent.sg_passwd = gr->gr_passwd;
|
||||
} else {
|
||||
static char *empty = 0;
|
||||
|
||||
/* add new shadow group entry */
|
||||
memset (&sgent, 0, sizeof sgent);
|
||||
sgent.sg_name = gr->gr_name;
|
||||
sgent.sg_passwd = gr->gr_passwd;
|
||||
sgent.sg_adm = ∅
|
||||
}
|
||||
/*
|
||||
* XXX - sg_mem is redundant, it is currently always a copy
|
||||
* of gr_mem. Very few programs actually use sg_mem, and all
|
||||
* of them are in the shadow suite. Maybe this field could
|
||||
* be used for something else? Any suggestions?
|
||||
*/
|
||||
sgent.sg_mem = gr->gr_mem;
|
||||
|
||||
if (sgr_update (&sgent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, sgr_dbname (), sgent.sg_name);
|
||||
fail_exit (3);
|
||||
}
|
||||
/* remove password from /etc/group */
|
||||
grent = *gr;
|
||||
grent.gr_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
|
||||
if (gr_update (&grent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, gr_dbname (), grent.gr_name);
|
||||
fail_exit (3);
|
||||
}
|
||||
}
|
||||
|
||||
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 (3);
|
||||
}
|
||||
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 (3);
|
||||
}
|
||||
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 */
|
||||
}
|
||||
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 */
|
||||
}
|
||||
|
||||
nscd_flush_cache ("group");
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else /* !SHADOWGRP */
|
||||
int main (int unused(argc), char **argv)
|
||||
{
|
||||
fprintf (stderr,
|
||||
"%s: not configured for shadow group support.\n", argv[0]);
|
||||
exit (1);
|
||||
}
|
||||
#endif /* !SHADOWGRP */
|
||||
|
||||
250
src/grpunconv.c
Normal file
250
src/grpunconv.c
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (c) 1996 , Michael Meskes
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2002 - 2006, Tomasz Kłoczko
|
||||
* Copyright (c) 2008 - 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* grpunconv - update /etc/group with information from /etc/gshadow.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#ident "$Id: grpunconv.c 3726 2012-05-18 19:32:32Z nekral-guest $"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <grp.h>
|
||||
#include <getopt.h>
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
#ifdef SHADOWGRP
|
||||
#include "groupio.h"
|
||||
#include "sgroupio.h"
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static bool gr_locked = false;
|
||||
static bool sgr_locked = false;
|
||||
|
||||
/* local function prototypes */
|
||||
static void fail_exit (int status);
|
||||
static void usage (int status);
|
||||
static void process_flags (int argc, char **argv);
|
||||
|
||||
static void fail_exit (int status)
|
||||
{
|
||||
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 */
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
}
|
||||
}
|
||||
|
||||
exit (status);
|
||||
}
|
||||
|
||||
static void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(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 ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv, "hR:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
const struct group *gr;
|
||||
struct group grent;
|
||||
const struct sgrp *sg;
|
||||
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
OPENLOG ("grpunconv");
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
if (sgr_file_present () == 0) {
|
||||
exit (0); /* no /etc/gshadow, nothing to do */
|
||||
}
|
||||
|
||||
if (gr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, gr_dbname ());
|
||||
fail_exit (5);
|
||||
}
|
||||
gr_locked = true;
|
||||
if (gr_open (O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"), Prog, gr_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
if (sgr_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, sgr_dbname ());
|
||||
fail_exit (5);
|
||||
}
|
||||
sgr_locked = true;
|
||||
if (sgr_open (O_RDONLY) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"), Prog, sgr_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update group passwords if non-shadow password is "x".
|
||||
*/
|
||||
(void) gr_rewind ();
|
||||
while ((gr = gr_next ()) != NULL) {
|
||||
sg = sgr_locate (gr->gr_name);
|
||||
if ( (NULL != sg)
|
||||
&& (strcmp (gr->gr_passwd, SHADOW_PASSWD_STRING) == 0)) {
|
||||
/* add password to /etc/group */
|
||||
grent = *gr;
|
||||
grent.gr_passwd = sg->sg_passwd;
|
||||
if (gr_update (&grent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, gr_dbname (), grent.gr_name);
|
||||
fail_exit (3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(void) sgr_close (); /* was only open O_RDONLY */
|
||||
|
||||
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 (3);
|
||||
}
|
||||
|
||||
if (unlink (SGROUP_FILE) != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot delete %s\n"),
|
||||
Prog, SGROUP_FILE);
|
||||
SYSLOG ((LOG_ERR, "cannot delete %s", SGROUP_FILE));
|
||||
fail_exit (3);
|
||||
}
|
||||
|
||||
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 */
|
||||
}
|
||||
|
||||
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 */
|
||||
}
|
||||
|
||||
nscd_flush_cache ("group");
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else /* !SHADOWGRP */
|
||||
int main (int unused(argc), char **argv)
|
||||
{
|
||||
fprintf (stderr,
|
||||
"%s: not configured for shadow group support.\n", argv[0]);
|
||||
exit (1);
|
||||
}
|
||||
#endif /* !SHADOWGRP */
|
||||
|
||||
207
src/id.c
Normal file
207
src/id.c
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (c) 1991 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 - 2006, Tomasz Kłoczko
|
||||
* Copyright (c) 2007 - 2008, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* id - print current process user identification information
|
||||
*
|
||||
* Print the current process identifiers. This includes the
|
||||
* UID, GID, effective-UID and effective-GID. Optionally print
|
||||
* the concurrent group set if the current system supports it.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#ident "$Id: id.c 3182 2010-03-23 11:26:34Z nekral-guest $"
|
||||
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include "defines.h"
|
||||
/* local function prototypes */
|
||||
static void usage (void);
|
||||
|
||||
static void usage (void)
|
||||
{
|
||||
#ifdef HAVE_GETGROUPS
|
||||
(void) fputs (_("Usage: id [-a]\n"), stderr);
|
||||
#else
|
||||
(void) fputs (_("Usage: id\n"), stderr);
|
||||
#endif
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*ARGSUSED*/ int main (int argc, char **argv)
|
||||
{
|
||||
uid_t ruid, euid;
|
||||
gid_t rgid, egid;
|
||||
long sys_ngroups;
|
||||
|
||||
/*
|
||||
* This block of declarations is particularly strained because of several
|
||||
* different ways of doing concurrent groups. Old BSD systems used int for
|
||||
* gid's, but short for the type passed to getgroups(). Newer systems use
|
||||
* gid_t for everything. Some systems have a small and fixed NGROUPS,
|
||||
* usually about 16 or 32. Others use bigger values.
|
||||
*/
|
||||
#ifdef HAVE_GETGROUPS
|
||||
GETGROUPS_T *groups;
|
||||
int ngroups;
|
||||
bool aflg = 0;
|
||||
#endif
|
||||
struct passwd *pw;
|
||||
struct group *gr;
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
/*
|
||||
* Dynamically get the maximum number of groups from system, instead
|
||||
* of using the symbolic constant NGROUPS_MAX. This ensures that the
|
||||
* group limit is not hard coded into the binary, so it will still
|
||||
* work if the system library is recompiled.
|
||||
*/
|
||||
sys_ngroups = sysconf (_SC_NGROUPS_MAX);
|
||||
#ifdef HAVE_GETGROUPS
|
||||
groups = (GETGROUPS_T *) malloc (sizeof (GETGROUPS_T) * sys_ngroups);
|
||||
/*
|
||||
* See if the -a flag has been given to print out the concurrent
|
||||
* group set.
|
||||
*/
|
||||
|
||||
if (argc > 1) {
|
||||
if ((argc > 2) || (strcmp (argv[1], "-a") != 0)) {
|
||||
usage ();
|
||||
} else {
|
||||
aflg = true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (argc > 1) {
|
||||
usage ();
|
||||
}
|
||||
#endif
|
||||
|
||||
ruid = getuid ();
|
||||
euid = geteuid ();
|
||||
rgid = getgid ();
|
||||
egid = getegid ();
|
||||
|
||||
/*
|
||||
* Print out the real user ID and group ID. If the user or group
|
||||
* does not exist, just give the numerical value.
|
||||
*/
|
||||
|
||||
pw = getpwuid (ruid); /* local, no need for xgetpwuid */
|
||||
if (NULL != pw) {
|
||||
(void) printf ("UID=%lu(%s)",
|
||||
(unsigned long) ruid, pw->pw_name);
|
||||
} else {
|
||||
(void) printf ("UID=%lu", (unsigned long) ruid);
|
||||
}
|
||||
|
||||
gr = getgrgid (rgid);; /* local, no need for xgetgrgid */
|
||||
if (NULL != gr) {
|
||||
(void) printf (" GID=%lu(%s)",
|
||||
(unsigned long) rgid, gr->gr_name);
|
||||
} else {
|
||||
(void) printf (" GID=%lu", (unsigned long) rgid);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print out the effective user ID and group ID if they are
|
||||
* different from the real values.
|
||||
*/
|
||||
|
||||
if (ruid != euid) {
|
||||
pw = getpwuid (euid); /* local, no need for xgetpwuid */
|
||||
if (NULL != pw) {
|
||||
(void) printf (" EUID=%lu(%s)",
|
||||
(unsigned long) euid, pw->pw_name);
|
||||
} else {
|
||||
(void) printf (" EUID=%lu", (unsigned long) euid);
|
||||
}
|
||||
}
|
||||
if (rgid != egid) {
|
||||
gr = getgrgid (egid); /* local, no need for xgetgrgid */
|
||||
if (NULL != gr) {
|
||||
(void) printf (" EGID=%lu(%s)",
|
||||
(unsigned long) egid, gr->gr_name);
|
||||
} else {
|
||||
(void) printf (" EGID=%lu", (unsigned long) egid);
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_GETGROUPS
|
||||
/*
|
||||
* Print out the concurrent group set if the user has requested it.
|
||||
* The group numbers will be printed followed by their names.
|
||||
*/
|
||||
if (aflg && (ngroups = getgroups (sys_ngroups, groups)) != -1) {
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Start off the group message. It will be of the format
|
||||
*
|
||||
* groups=###(aaa),###(aaa),###(aaa)
|
||||
*
|
||||
* where "###" is a numerical value and "aaa" is the
|
||||
* corresponding name for each respective numerical value.
|
||||
*/
|
||||
(void) puts (_(" groups="));
|
||||
for (i = 0; i < ngroups; i++) {
|
||||
if (0 != i)
|
||||
(void) putchar (',');
|
||||
|
||||
/* local, no need for xgetgrgid */
|
||||
gr = getgrgid (groups[i]);
|
||||
if (NULL != gr) {
|
||||
(void) printf ("%lu(%s)",
|
||||
(unsigned long) groups[i],
|
||||
gr->gr_name);
|
||||
} else {
|
||||
(void) printf ("%lu",
|
||||
(unsigned long) groups[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
free (groups);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Finish off the line.
|
||||
*/
|
||||
(void) putchar ('\n');
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
319
src/lastlog.c
Normal file
319
src/lastlog.c
Normal file
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (c) 1989 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* 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>
|
||||
|
||||
#ident "$Id: lastlog.c 3560 2011-11-06 18:39:59Z nekral-guest $"
|
||||
|
||||
#include <getopt.h>
|
||||
#include <lastlog.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
#include "defines.h"
|
||||
#include "prototypes.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/*
|
||||
* Needed for MkLinux DR1/2/2.1 - J.
|
||||
*/
|
||||
#ifndef LASTLOG_FILE
|
||||
#define LASTLOG_FILE "/var/log/lastlog"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog; /* Program name */
|
||||
static FILE *lastlogfile; /* lastlog file stream */
|
||||
static unsigned long umin; /* if uflg and has_umin, only display users with uid >= umin */
|
||||
static bool has_umin = false;
|
||||
static unsigned long umax; /* if uflg and has_umax, only display users with uid <= umax */
|
||||
static bool has_umax = false;
|
||||
static time_t seconds; /* that number of days in seconds */
|
||||
static time_t inverse_seconds; /* that number of days in seconds */
|
||||
static struct stat statbuf; /* fstat buffer for file size */
|
||||
|
||||
|
||||
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 */
|
||||
|
||||
#define NOW (time ((time_t *) 0))
|
||||
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(void) fputs (_(" -b, --before DAYS print only lastlog records older than DAYS\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 (_(" -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);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
static void print_one (/*@null@*/const struct passwd *pw)
|
||||
{
|
||||
static bool once = false;
|
||||
char *cp;
|
||||
struct tm *tm;
|
||||
time_t ll_time;
|
||||
off_t offset;
|
||||
struct lastlog ll;
|
||||
|
||||
#ifdef HAVE_STRFTIME
|
||||
char ptime[80];
|
||||
#endif
|
||||
|
||||
if (NULL == pw) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
offset = (off_t) pw->pw_uid * sizeof (ll);
|
||||
if (offset + sizeof (ll) <= statbuf.st_size) {
|
||||
/* fseeko errors are not really relevant for us. */
|
||||
int err = fseeko (lastlogfile, offset, SEEK_SET);
|
||||
assert (0 == err);
|
||||
/* lastlog is a sparse file. Even if no entries were
|
||||
* entered for this user, which should be able to get the
|
||||
* empty entry in this case.
|
||||
*/
|
||||
if (fread ((char *) &ll, sizeof (ll), 1, lastlogfile) != 1) {
|
||||
fprintf (stderr,
|
||||
_("%s: Failed to get the entry for UID %lu\n"),
|
||||
Prog, (unsigned long int)pw->pw_uid);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
/* Outsize of the lastlog file.
|
||||
* Behave as if there were a missing entry (same behavior
|
||||
* as if we were reading an non existing entry in the
|
||||
* sparse lastlog file).
|
||||
*/
|
||||
memzero (&ll, sizeof (ll));
|
||||
}
|
||||
|
||||
/* Filter out entries that do not match with the -t or -b options */
|
||||
if (tflg && ((NOW - ll.ll_time) > seconds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bflg && ((NOW - ll.ll_time) < inverse_seconds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Print the header only once */
|
||||
if (!once) {
|
||||
#ifdef HAVE_LL_HOST
|
||||
puts (_("Username Port From Latest"));
|
||||
#else
|
||||
puts (_("Username Port Latest"));
|
||||
#endif
|
||||
once = true;
|
||||
}
|
||||
|
||||
ll_time = ll.ll_time;
|
||||
tm = localtime (&ll_time);
|
||||
#ifdef HAVE_STRFTIME
|
||||
strftime (ptime, sizeof (ptime), "%a %b %e %H:%M:%S %z %Y", tm);
|
||||
cp = ptime;
|
||||
#else
|
||||
cp = asctime (tm);
|
||||
cp[24] = '\0';
|
||||
#endif
|
||||
|
||||
if (ll.ll_time == (time_t) 0) {
|
||||
cp = _("**Never logged in**\0");
|
||||
}
|
||||
|
||||
#ifdef HAVE_LL_HOST
|
||||
printf ("%-16s %-8.8s %-16.16s %s\n",
|
||||
pw->pw_name, ll.ll_line, ll.ll_host, cp);
|
||||
#else
|
||||
printf ("%-16s\t%-8.8s %s\n",
|
||||
pw->pw_name, ll.ll_line, cp);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void print (void)
|
||||
{
|
||||
const struct passwd *pwent;
|
||||
if (uflg && has_umin && has_umax && (umin == umax)) {
|
||||
print_one (getpwuid ((uid_t)umin));
|
||||
} else {
|
||||
setpwent ();
|
||||
while ( (pwent = getpwent ()) != NULL ) {
|
||||
if ( uflg
|
||||
&& ( (has_umin && (pwent->pw_uid < (uid_t)umin))
|
||||
|| (has_umax && (pwent->pw_uid > (uid_t)umax)))) {
|
||||
continue;
|
||||
}
|
||||
print_one (pwent);
|
||||
}
|
||||
endpwent ();
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
/*
|
||||
* Get the program name. The program name is used as a prefix to
|
||||
* most error messages.
|
||||
*/
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
{
|
||||
int c;
|
||||
static struct option const longopts[] = {
|
||||
{"before", required_argument, NULL, 'b'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{"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,
|
||||
NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'b':
|
||||
{
|
||||
unsigned long inverse_days;
|
||||
if (getulong (optarg, &inverse_days) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
inverse_seconds = (time_t) inverse_days * DAY;
|
||||
bflg = true;
|
||||
break;
|
||||
}
|
||||
case 'h':
|
||||
usage (EXIT_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
case 't':
|
||||
{
|
||||
unsigned long days;
|
||||
if (getulong (optarg, &days) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: invalid numeric argument '%s'\n"),
|
||||
Prog, optarg);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
seconds = (time_t) days * DAY;
|
||||
tflg = true;
|
||||
break;
|
||||
}
|
||||
case 'u':
|
||||
{
|
||||
const struct passwd *pwent;
|
||||
/*
|
||||
* The user can be:
|
||||
* - a login name
|
||||
* - numerical
|
||||
* - a numerical login ID
|
||||
* - a range (-x, x-, x-y)
|
||||
*/
|
||||
uflg = true;
|
||||
/* local, no need for xgetpwnam */
|
||||
pwent = getpwnam (optarg);
|
||||
if (NULL != pwent) {
|
||||
umin = (unsigned long) pwent->pw_uid;
|
||||
has_umin = true;
|
||||
umax = umin;
|
||||
has_umax = true;
|
||||
} else {
|
||||
if (getrange (optarg,
|
||||
&umin, &has_umin,
|
||||
&umax, &has_umax) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: Unknown user or range: %s\n"),
|
||||
Prog, optarg);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
usage (EXIT_FAILURE);
|
||||
/*@notreached@*/break;
|
||||
}
|
||||
}
|
||||
if (argc > optind) {
|
||||
fprintf (stderr,
|
||||
_("%s: unexpected argument: %s\n"),
|
||||
Prog, argv[optind]);
|
||||
usage (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
lastlogfile = fopen (LASTLOG_FILE, "r");
|
||||
if (NULL == lastlogfile) {
|
||||
perror (LASTLOG_FILE);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Get the lastlog size */
|
||||
if (fstat (fileno (lastlogfile), &statbuf) != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: Cannot get the size of %s: %s\n"),
|
||||
Prog, LASTLOG_FILE, strerror (errno));
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
print ();
|
||||
|
||||
(void) fclose (lastlogfile);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
1336
src/login.c
Normal file
1336
src/login.c
Normal file
File diff suppressed because it is too large
Load Diff
336
src/login_nopam.c
Normal file
336
src/login_nopam.c
Normal file
@@ -0,0 +1,336 @@
|
||||
/* Taken from logdaemon-5.0, only minimal changes. --marekm */
|
||||
|
||||
/************************************************************************
|
||||
* Copyright 1995 by Wietse Venema. All rights reserved. Individual files
|
||||
* may be covered by other copyrights (as noted in the file itself.)
|
||||
*
|
||||
* This material was originally written and compiled by Wietse Venema at
|
||||
* Eindhoven University of Technology, The Netherlands, in 1990, 1991,
|
||||
* 1992, 1993, 1994 and 1995.
|
||||
*
|
||||
* Redistribution and use in source and binary forms are permitted
|
||||
* provided that this entire copyright notice is duplicated in all such
|
||||
* copies.
|
||||
*
|
||||
* This software is provided "as is" and without any expressed or implied
|
||||
* warranties, including, without limitation, the implied warranties of
|
||||
* merchantibility and fitness for any particular purpose.
|
||||
************************************************************************/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifndef USE_PAM
|
||||
#ident "$Id: login_nopam.c 3182 2010-03-23 11:26:34Z nekral-guest $"
|
||||
|
||||
#include "prototypes.h"
|
||||
/*
|
||||
* This module implements a simple but effective form of login access
|
||||
* control based on login names and on host (or domain) names, internet
|
||||
* addresses (or network numbers), or on terminal line names in case of
|
||||
* non-networked logins. Diagnostics are reported through syslog(3).
|
||||
*
|
||||
* Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <syslog.h>
|
||||
#include <ctype.h>
|
||||
#ifdef HAVE_NETDB_H
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
#include <grp.h>
|
||||
#ifdef PRIMARY_GROUP_MATCH
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h> /* for inet_ntoa() */
|
||||
|
||||
#if !defined(MAXHOSTNAMELEN) || (MAXHOSTNAMELEN < 64)
|
||||
#undef MAXHOSTNAMELEN
|
||||
#define MAXHOSTNAMELEN 256
|
||||
#endif
|
||||
|
||||
/* Path name of the access control file. */
|
||||
#ifndef TABLE
|
||||
#define TABLE "/etc/login.access"
|
||||
#endif
|
||||
|
||||
/* Delimiters for fields and for lists of users, ttys or hosts. */
|
||||
static char fs[] = ":"; /* field separator */
|
||||
static char sep[] = ", \t"; /* list-element separator */
|
||||
|
||||
static bool list_match (char *list, const char *item, bool (*match_fn) (const char *, const char *));
|
||||
static bool user_match (const char *tok, const char *string);
|
||||
static bool from_match (const char *tok, const char *string);
|
||||
static bool string_match (const char *tok, const char *string);
|
||||
static const char *resolve_hostname (const char *string);
|
||||
|
||||
/* login_access - match username/group and host/tty with access control file */
|
||||
int login_access (const char *user, const char *from)
|
||||
{
|
||||
FILE *fp;
|
||||
char line[BUFSIZ];
|
||||
char *perm; /* becomes permission field */
|
||||
char *users; /* becomes list of login names */
|
||||
char *froms; /* becomes list of terminals or hosts */
|
||||
bool match = false;
|
||||
|
||||
/*
|
||||
* Process the table one line at a time and stop at the first match.
|
||||
* Blank lines and lines that begin with a '#' character are ignored.
|
||||
* Non-comment lines are broken at the ':' character. All fields are
|
||||
* mandatory. The first field should be a "+" or "-" character. A
|
||||
* non-existing table means no access control.
|
||||
*/
|
||||
fp = fopen (TABLE, "r");
|
||||
if (NULL != fp) {
|
||||
int lineno = 0; /* for diagnostics */
|
||||
while ( !match
|
||||
&& (fgets (line, (int) sizeof (line), fp) == line)) {
|
||||
int end;
|
||||
lineno++;
|
||||
end = (int) strlen (line) - 1;
|
||||
if (line[end] != '\n') {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s: line %d: missing newline or line too long",
|
||||
TABLE, lineno));
|
||||
continue;
|
||||
}
|
||||
if (line[0] == '#') {
|
||||
continue; /* comment line */
|
||||
}
|
||||
while (end > 0 && isspace (line[end - 1])) {
|
||||
end--;
|
||||
}
|
||||
line[end] = '\0'; /* strip trailing whitespace */
|
||||
if (line[0] == '\0') { /* skip blank lines */
|
||||
continue;
|
||||
}
|
||||
if ( ((perm = strtok (line, fs)) == NULL)
|
||||
|| ((users = strtok ((char *) 0, fs)) == NULL)
|
||||
|| ((froms = strtok ((char *) 0, fs)) == NULL)
|
||||
|| (strtok ((char *) 0, fs) != NULL)) {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s: line %d: bad field count",
|
||||
TABLE, lineno));
|
||||
continue;
|
||||
}
|
||||
if (perm[0] != '+' && perm[0] != '-') {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s: line %d: bad first field",
|
||||
TABLE, lineno));
|
||||
continue;
|
||||
}
|
||||
match = ( list_match (froms, from, from_match)
|
||||
&& list_match (users, user, user_match));
|
||||
}
|
||||
(void) fclose (fp);
|
||||
} else if (errno != ENOENT) {
|
||||
int err = errno;
|
||||
SYSLOG ((LOG_ERR, "cannot open %s: %s", TABLE, strerror (err)));
|
||||
}
|
||||
return (!match || (line[0] == '+'))?1:0;
|
||||
}
|
||||
|
||||
/* list_match - match an item against a list of tokens with exceptions */
|
||||
static bool list_match (char *list, const char *item, bool (*match_fn) (const char *, const char*))
|
||||
{
|
||||
char *tok;
|
||||
bool match = false;
|
||||
|
||||
/*
|
||||
* Process tokens one at a time. We have exhausted all possible matches
|
||||
* when we reach an "EXCEPT" token or the end of the list. If we do find
|
||||
* a match, look for an "EXCEPT" list and recurse to determine whether
|
||||
* the match is affected by any exceptions.
|
||||
*/
|
||||
for (tok = strtok (list, sep); tok != NULL; tok = strtok ((char *) 0, sep)) {
|
||||
if (strcasecmp (tok, "EXCEPT") == 0) { /* EXCEPT: give up */
|
||||
break;
|
||||
}
|
||||
match = (*match_fn) (tok, item);
|
||||
if (match) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Process exceptions to matches. */
|
||||
if (match) {
|
||||
while ( ((tok = strtok ((char *) 0, sep)) != NULL)
|
||||
&& (strcasecmp (tok, "EXCEPT") != 0))
|
||||
/* VOID */ ;
|
||||
if (tok == 0 || !list_match ((char *) 0, item, match_fn)) {
|
||||
return (match);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* myhostname - figure out local machine name */
|
||||
static char *myhostname (void)
|
||||
{
|
||||
static char name[MAXHOSTNAMELEN + 1] = "";
|
||||
|
||||
if (name[0] == '\0') {
|
||||
gethostname (name, sizeof (name));
|
||||
name[MAXHOSTNAMELEN] = '\0';
|
||||
}
|
||||
return (name);
|
||||
}
|
||||
|
||||
#if HAVE_INNETGR
|
||||
/* netgroup_match - match group against machine or user */
|
||||
static bool
|
||||
netgroup_match (const char *group, const char *machine, const char *user)
|
||||
{
|
||||
static char *mydomain = (char *)0;
|
||||
|
||||
if (mydomain == (char *)0) {
|
||||
static char domain[MAXHOSTNAMELEN + 1];
|
||||
|
||||
getdomainname (domain, MAXHOSTNAMELEN);
|
||||
mydomain = domain;
|
||||
}
|
||||
|
||||
return (innetgr (group, machine, user, mydomain) != 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* user_match - match a username against one token */
|
||||
static bool user_match (const char *tok, const char *string)
|
||||
{
|
||||
struct group *group;
|
||||
|
||||
#ifdef PRIMARY_GROUP_MATCH
|
||||
struct passwd *userinf;
|
||||
#endif
|
||||
char *at;
|
||||
|
||||
/*
|
||||
* If a token has the magic value "ALL" the match always succeeds.
|
||||
* Otherwise, return true if the token fully matches the username, or if
|
||||
* the token is a group that contains the username.
|
||||
*/
|
||||
at = strchr (tok + 1, '@');
|
||||
if (NULL != at) { /* split user@host pattern */
|
||||
*at = '\0';
|
||||
return ( user_match (tok, string)
|
||||
&& from_match (at + 1, myhostname ()));
|
||||
#if HAVE_INNETGR
|
||||
} else if (tok[0] == '@') { /* netgroup */
|
||||
return (netgroup_match (tok + 1, (char *) 0, string));
|
||||
#endif
|
||||
} else if (string_match (tok, string)) { /* ALL or exact match */
|
||||
return true;
|
||||
/* local, no need for xgetgrnam */
|
||||
} else if ((group = getgrnam (tok)) != NULL) { /* try group membership */
|
||||
int i;
|
||||
for (i = 0; NULL != group->gr_mem[i]; i++) {
|
||||
if (strcasecmp (string, group->gr_mem[i]) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#ifdef PRIMARY_GROUP_MATCH
|
||||
/*
|
||||
* If the string is an user whose initial GID matches the token,
|
||||
* accept it. May avoid excessively long lines in /etc/group.
|
||||
* Radu-Adrian Feurdean <raf@licj.soroscj.ro>
|
||||
*
|
||||
* XXX - disabled by default for now. Need to verify that
|
||||
* getpwnam() doesn't have some nasty side effects. --marekm
|
||||
*/
|
||||
/* local, no need for xgetpwnam */
|
||||
userinf = getpwnam (string);
|
||||
if (NULL != userinf) {
|
||||
if (userinf->pw_gid == group->gr_gid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *resolve_hostname (const char *string)
|
||||
{
|
||||
/*
|
||||
* Resolve hostname to numeric IP address, as suggested
|
||||
* by Dave Hagewood <admin@arrowweb.com>. --marekm
|
||||
*/
|
||||
struct hostent *hp;
|
||||
|
||||
hp = gethostbyname (string);
|
||||
if (NULL != hp) {
|
||||
return inet_ntoa (*((struct in_addr *) *(hp->h_addr_list)));
|
||||
}
|
||||
|
||||
SYSLOG ((LOG_ERR, "%s - unknown host", string));
|
||||
return string;
|
||||
}
|
||||
|
||||
/* from_match - match a host or tty against a list of tokens */
|
||||
|
||||
static bool from_match (const char *tok, const char *string)
|
||||
{
|
||||
size_t tok_len;
|
||||
|
||||
/*
|
||||
* If a token has the magic value "ALL" the match always succeeds. Return
|
||||
* true if the token fully matches the string. If the token is a domain
|
||||
* name, return true if it matches the last fields of the string. If the
|
||||
* token has the magic value "LOCAL", return true if the string does not
|
||||
* contain a "." character. If the token is a network number, return true
|
||||
* if it matches the head of the string.
|
||||
*/
|
||||
#if HAVE_INNETGR
|
||||
if (tok[0] == '@') { /* netgroup */
|
||||
return (netgroup_match (tok + 1, string, (char *) 0));
|
||||
} else
|
||||
#endif
|
||||
if (string_match (tok, string)) { /* ALL or exact match */
|
||||
return true;
|
||||
} else if (tok[0] == '.') { /* domain: match last fields */
|
||||
size_t str_len;
|
||||
str_len = strlen (string);
|
||||
tok_len = strlen (tok);
|
||||
if ( (str_len > tok_len)
|
||||
&& (strcasecmp (tok, string + str_len - tok_len) == 0)) {
|
||||
return true;
|
||||
}
|
||||
} else if (strcasecmp (tok, "LOCAL") == 0) { /* local: no dots */
|
||||
if (strchr (string, '.') == NULL) {
|
||||
return true;
|
||||
}
|
||||
} else if ( (tok[(tok_len = strlen (tok)) - 1] == '.') /* network */
|
||||
&& (strncmp (tok, resolve_hostname (string), tok_len) == 0)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* string_match - match a string against one token */
|
||||
static bool string_match (const char *tok, const char *string)
|
||||
{
|
||||
|
||||
/*
|
||||
* If the token has the magic value "ALL" the match always succeeds.
|
||||
* Otherwise, return true if the token fully matches the string.
|
||||
*/
|
||||
if (strcasecmp (tok, "ALL") == 0) { /* all: always matches */
|
||||
return true;
|
||||
} else if (strcasecmp (tok, string) == 0) { /* try exact match */
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#else /* !USE_PAM */
|
||||
extern int errno; /* warning: ANSI C forbids an empty source file */
|
||||
#endif /* !USE_PAM */
|
||||
299
src/logoutd.c
Normal file
299
src/logoutd.c
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (c) 1991 - 1993, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 - 2006, Tomasz Kłoczko
|
||||
* 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>
|
||||
|
||||
#ident "$Id: logoutd.c 3233 2010-08-22 19:36:09Z nekral-guest $"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include "defines.h"
|
||||
#include "prototypes.h"
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
#ifndef DEFAULT_HUP_MESG
|
||||
#define DEFAULT_HUP_MESG _("login time exceeded\n\n")
|
||||
#endif
|
||||
|
||||
#ifndef HUP_MESG_FILE
|
||||
#define HUP_MESG_FILE "/etc/logoutd.mesg"
|
||||
#endif
|
||||
|
||||
/* local function prototypes */
|
||||
#ifdef USE_UTMPX
|
||||
static int check_login (const struct utmpx *ut);
|
||||
#else /* !USE_UTMPX */
|
||||
static int check_login (const struct utmp *ut);
|
||||
#endif /* !USE_UTMPX */
|
||||
static void send_mesg_to_tty (int tty_fd);
|
||||
|
||||
/*
|
||||
* check_login - check if user (struct utmpx/utmp) allowed to stay logged in
|
||||
*/
|
||||
#ifdef USE_UTMPX
|
||||
static int check_login (const struct utmpx *ut)
|
||||
#else /* !USE_UTMPX */
|
||||
static int check_login (const struct utmp *ut)
|
||||
#endif /* !USE_UTMPX */
|
||||
{
|
||||
char user[sizeof (ut->ut_user) + 1];
|
||||
time_t now;
|
||||
|
||||
/*
|
||||
* ut_user may not have the terminating NUL.
|
||||
*/
|
||||
strncpy (user, ut->ut_user, sizeof (ut->ut_user));
|
||||
user[sizeof (ut->ut_user)] = '\0';
|
||||
|
||||
(void) time (&now);
|
||||
|
||||
/*
|
||||
* Check if they are allowed to be logged in right now.
|
||||
*/
|
||||
if (!isttytime (user, ut->ut_line, now)) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static void send_mesg_to_tty (int tty_fd)
|
||||
{
|
||||
TERMIO oldt, newt;
|
||||
FILE *mesg_file, *tty_file;
|
||||
bool is_tty;
|
||||
|
||||
tty_file = fdopen (tty_fd, "w");
|
||||
if (NULL == tty_file) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_tty = (GTTY (tty_fd, &oldt) == 0);
|
||||
if (is_tty) {
|
||||
/* Suggested by Ivan Nejgebauar <ian@unsux.ns.ac.yu>:
|
||||
set OPOST before writing the message. */
|
||||
newt = oldt;
|
||||
newt.c_oflag |= OPOST;
|
||||
STTY (tty_fd, &newt);
|
||||
}
|
||||
|
||||
mesg_file = fopen (HUP_MESG_FILE, "r");
|
||||
if (NULL != mesg_file) {
|
||||
int c;
|
||||
while ((c = getc (mesg_file)) != EOF) {
|
||||
if (c == '\n') {
|
||||
putc ('\r', tty_file);
|
||||
}
|
||||
putc (c, tty_file);
|
||||
}
|
||||
fclose (mesg_file);
|
||||
} else {
|
||||
fputs (DEFAULT_HUP_MESG, tty_file);
|
||||
}
|
||||
fflush (tty_file);
|
||||
fclose (tty_file);
|
||||
|
||||
if (is_tty) {
|
||||
STTY (tty_fd, &oldt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* logoutd - logout daemon to enforce /etc/porttime file policy
|
||||
*
|
||||
* logoutd is started at system boot time and enforces the login
|
||||
* time and port restrictions specified in /etc/porttime. The
|
||||
* utmpx/utmp file is periodically scanned and offending users are logged
|
||||
* off from the system.
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
int status;
|
||||
pid_t pid;
|
||||
|
||||
#ifdef USE_UTMPX
|
||||
struct utmpx *ut;
|
||||
#else /* !USE_UTMPX */
|
||||
struct utmp *ut;
|
||||
#endif /* !USE_UTMPX */
|
||||
char user[sizeof (ut->ut_user) + 1]; /* terminating NUL */
|
||||
char tty_name[sizeof (ut->ut_line) + 6]; /* /dev/ + NUL */
|
||||
int tty_fd;
|
||||
|
||||
if (1 != argc) {
|
||||
(void) fputs (_("Usage: logoutd\n"), stderr);
|
||||
}
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
#ifndef DEBUG
|
||||
for (i = 0; close (i) == 0; i++);
|
||||
|
||||
setpgrp ();
|
||||
|
||||
/*
|
||||
* Put this process in the background.
|
||||
*/
|
||||
pid = fork ();
|
||||
if (pid > 0) {
|
||||
/* parent */
|
||||
exit (EXIT_SUCCESS);
|
||||
} else if (pid < 0) {
|
||||
/* error */
|
||||
perror ("fork");
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
#endif /* !DEBUG */
|
||||
|
||||
/*
|
||||
* Start syslogging everything
|
||||
*/
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
OPENLOG ("logoutd");
|
||||
|
||||
/*
|
||||
* Scan the utmpx/utmp file once per minute looking for users that
|
||||
* are not supposed to still be logged in.
|
||||
*/
|
||||
while (true) {
|
||||
|
||||
/*
|
||||
* Attempt to re-open the utmpx/utmp file. The file is only
|
||||
* open while it is being used.
|
||||
*/
|
||||
#ifdef USE_UTMPX
|
||||
setutxent ();
|
||||
#else /* !USE_UTMPX */
|
||||
setutent ();
|
||||
#endif /* !USE_UTMPX */
|
||||
|
||||
/*
|
||||
* Read all of the entries in the utmpx/utmp file. The entries
|
||||
* for login sessions will be checked to see if the user
|
||||
* is permitted to be signed on at this time.
|
||||
*/
|
||||
#ifdef USE_UTMPX
|
||||
while ((ut = getutxent ()) != NULL)
|
||||
#else /* !USE_UTMPX */
|
||||
while ((ut = getutent ()) != NULL)
|
||||
#endif /* !USE_UTMPX */
|
||||
{
|
||||
if (ut->ut_type != USER_PROCESS) {
|
||||
continue;
|
||||
}
|
||||
if (ut->ut_user[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
if (check_login (ut)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Put the rest of this in a child process. This
|
||||
* keeps the scan from waiting on other ports to die.
|
||||
*/
|
||||
|
||||
pid = fork ();
|
||||
if (pid > 0) {
|
||||
/* parent */
|
||||
continue;
|
||||
} else if (pid < 0) {
|
||||
/* failed - give up until the next scan */
|
||||
break;
|
||||
}
|
||||
/* child */
|
||||
|
||||
if (strncmp (ut->ut_line, "/dev/", 5) != 0) {
|
||||
strcpy (tty_name, "/dev/");
|
||||
} else {
|
||||
tty_name[0] = '\0';
|
||||
}
|
||||
|
||||
strcat (tty_name, ut->ut_line);
|
||||
#ifndef O_NOCTTY
|
||||
#define O_NOCTTY 0
|
||||
#endif
|
||||
tty_fd =
|
||||
open (tty_name, O_WRONLY | O_NDELAY | O_NOCTTY);
|
||||
if (tty_fd != -1) {
|
||||
send_mesg_to_tty (tty_fd);
|
||||
close (tty_fd);
|
||||
sleep (10);
|
||||
}
|
||||
|
||||
if (ut->ut_pid > 1) {
|
||||
kill (-ut->ut_pid, SIGHUP);
|
||||
sleep (10);
|
||||
kill (-ut->ut_pid, SIGKILL);
|
||||
}
|
||||
|
||||
strncpy (user, ut->ut_user, sizeof (user) - 1);
|
||||
user[sizeof (user) - 1] = '\0';
|
||||
|
||||
SYSLOG ((LOG_NOTICE,
|
||||
"logged off user '%s' on '%s'", user,
|
||||
tty_name));
|
||||
|
||||
/*
|
||||
* This child has done all it can, drop dead.
|
||||
*/
|
||||
exit (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
#ifdef USE_UTMPX
|
||||
endutxent ();
|
||||
#else /* !USE_UTMPX */
|
||||
endutent ();
|
||||
#endif /* !USE_UTMPX */
|
||||
|
||||
#ifndef DEBUG
|
||||
sleep (60);
|
||||
#endif
|
||||
/*
|
||||
* Reap any dead babies ...
|
||||
*/
|
||||
while (wait (&status) != -1);
|
||||
}
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
838
src/newgrp.c
Normal file
838
src/newgrp.c
Normal file
@@ -0,0 +1,838 @@
|
||||
/*
|
||||
* Copyright (c) 1990 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 - 2006, Tomasz Kłoczko
|
||||
* Copyright (c) 2007 - 2008, 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>
|
||||
|
||||
#ident "$Id: newgrp.c 3458 2011-07-30 01:41:56Z nekral-guest $"
|
||||
|
||||
#include <errno.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "defines.h"
|
||||
#include "getdef.h"
|
||||
#include "prototypes.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
extern char **newenvp;
|
||||
extern char **environ;
|
||||
|
||||
#ifdef HAVE_SETGROUPS
|
||||
static int ngroups;
|
||||
static /*@null@*/ /*@only@*/GETGROUPS_T *grouplist;
|
||||
#endif
|
||||
|
||||
static bool is_newgrp;
|
||||
|
||||
#ifdef WITH_AUDIT
|
||||
static char audit_buf[80];
|
||||
#endif
|
||||
|
||||
/* local function prototypes */
|
||||
static void usage (void);
|
||||
static void check_perms (const struct group *grp,
|
||||
struct passwd *pwd,
|
||||
const char *groupname);
|
||||
static void syslog_sg (const char *name, const char *group);
|
||||
|
||||
/*
|
||||
* usage - print command usage message
|
||||
*/
|
||||
static void usage (void)
|
||||
{
|
||||
if (is_newgrp) {
|
||||
(void) fputs (_("Usage: newgrp [-] [group]\n"), stderr);
|
||||
} else {
|
||||
(void) fputs (_("Usage: sg group [[-c] command]\n"), stderr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* find_matching_group - search all groups of a given group id for
|
||||
* membership of a given username
|
||||
*/
|
||||
static /*@null@*/struct group *find_matching_group (const char *name, gid_t gid)
|
||||
{
|
||||
struct group *gr;
|
||||
char **look;
|
||||
bool notfound = true;
|
||||
|
||||
setgrent ();
|
||||
while ((gr = getgrent ()) != NULL) {
|
||||
if (gr->gr_gid != gid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* A group with matching GID was found.
|
||||
* Test for membership of 'name'.
|
||||
*/
|
||||
look = gr->gr_mem;
|
||||
while ((NULL != *look) && notfound) {
|
||||
notfound = (strcmp (*look, name) != 0);
|
||||
look++;
|
||||
}
|
||||
if (!notfound) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
endgrent ();
|
||||
return gr;
|
||||
}
|
||||
|
||||
/*
|
||||
* check_perms - check if the user is allowed to switch to this group
|
||||
*
|
||||
* If needed, the user will be authenticated.
|
||||
*
|
||||
* It will not return if the user could not be authenticated.
|
||||
*/
|
||||
static void check_perms (const struct group *grp,
|
||||
struct passwd *pwd,
|
||||
const char *groupname)
|
||||
{
|
||||
bool needspasswd = false;
|
||||
struct spwd *spwd;
|
||||
char *cp;
|
||||
const char *cpasswd;
|
||||
|
||||
/*
|
||||
* see if she is a member of this group (i.e. in the list of
|
||||
* members of the group, or if the group is her primary group).
|
||||
*
|
||||
* If she isn't a member, she needs to provide the group password.
|
||||
* If there is no group password, she will be denied access
|
||||
* anyway.
|
||||
*
|
||||
*/
|
||||
if ( (grp->gr_gid != pwd->pw_gid)
|
||||
&& !is_on_list (grp->gr_mem, pwd->pw_name)) {
|
||||
needspasswd = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* If she does not have either a shadowed password, or a regular
|
||||
* password, and the group has a password, she needs to give the
|
||||
* group password.
|
||||
*/
|
||||
spwd = xgetspnam (pwd->pw_name);
|
||||
if (NULL != spwd) {
|
||||
pwd->pw_passwd = spwd->sp_pwdp;
|
||||
}
|
||||
|
||||
if ((pwd->pw_passwd[0] == '\0') && (grp->gr_passwd[0] != '\0')) {
|
||||
needspasswd = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now I see about letting her into the group she requested. If she
|
||||
* is the root user, I'll let her in without having to prompt for
|
||||
* the password. Otherwise I ask for a password if she flunked one
|
||||
* of the tests above.
|
||||
*/
|
||||
if ((getuid () != 0) && needspasswd) {
|
||||
/*
|
||||
* get the password from her, and set the salt for
|
||||
* the decryption from the group file.
|
||||
*/
|
||||
cp = getpass (_("Password: "));
|
||||
if (NULL == cp) {
|
||||
goto failure;
|
||||
}
|
||||
|
||||
/*
|
||||
* encrypt the key she gave us using the salt from the
|
||||
* password in the group file. The result of this encryption
|
||||
* must match the previously encrypted value in the file.
|
||||
*/
|
||||
cpasswd = pw_encrypt (cp, grp->gr_passwd);
|
||||
strzero (cp);
|
||||
|
||||
if (grp->gr_passwd[0] == '\0' ||
|
||||
strcmp (cpasswd, grp->gr_passwd) != 0) {
|
||||
#ifdef WITH_AUDIT
|
||||
snprintf (audit_buf, sizeof(audit_buf),
|
||||
"authentication new-gid=%lu",
|
||||
(unsigned long) grp->gr_gid);
|
||||
audit_logger (AUDIT_GRP_AUTH, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
#endif
|
||||
SYSLOG ((LOG_INFO,
|
||||
"Invalid password for group '%s' from '%s'",
|
||||
groupname, pwd->pw_name));
|
||||
(void) sleep (1);
|
||||
(void) fputs (_("Invalid password.\n"), stderr);
|
||||
goto failure;
|
||||
}
|
||||
#ifdef WITH_AUDIT
|
||||
snprintf (audit_buf, sizeof(audit_buf),
|
||||
"authentication new-gid=%lu",
|
||||
(unsigned long) grp->gr_gid);
|
||||
audit_logger (AUDIT_GRP_AUTH, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
failure:
|
||||
/* The closelog is probably unnecessary, but it does no
|
||||
* harm. -- JWP
|
||||
*/
|
||||
closelog ();
|
||||
#ifdef WITH_AUDIT
|
||||
if (groupname) {
|
||||
snprintf (audit_buf, sizeof(audit_buf),
|
||||
"changing new-group=%s", groupname);
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
} else {
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
"changing", NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
}
|
||||
#endif
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef USE_SYSLOG
|
||||
/*
|
||||
* syslog_sg - log the change of group to syslog
|
||||
*
|
||||
* The loggout will also be logged when the user will quit the
|
||||
* sg/newgrp session.
|
||||
*/
|
||||
static void syslog_sg (const char *name, const char *group)
|
||||
{
|
||||
const char *loginname = getlogin ();
|
||||
const char *tty = ttyname (0);
|
||||
|
||||
if (loginname != NULL) {
|
||||
loginname = xstrdup (loginname);
|
||||
}
|
||||
if (tty != NULL) {
|
||||
tty = xstrdup (tty);
|
||||
}
|
||||
|
||||
if (loginname == NULL) {
|
||||
loginname = "???";
|
||||
}
|
||||
if (tty == NULL) {
|
||||
tty = "???";
|
||||
} else if (strncmp (tty, "/dev/", 5) == 0) {
|
||||
tty += 5;
|
||||
}
|
||||
SYSLOG ((LOG_INFO,
|
||||
"user '%s' (login '%s' on %s) switched to group '%s'",
|
||||
name, loginname, tty, group));
|
||||
#ifdef USE_PAM
|
||||
/*
|
||||
* We want to fork and exec the new shell in the child, leaving the
|
||||
* parent waiting to log the session close.
|
||||
*
|
||||
* The parent must ignore signals generated from the console
|
||||
* (SIGINT, SIGQUIT, SIGHUP) which might make the parent terminate
|
||||
* before its child. When bash is exec'ed as the subshell, it
|
||||
* generates a new process group id for itself, and consequently
|
||||
* only SIGHUP, which is sent to all process groups in the session,
|
||||
* can reach the parent. However, since arbitrary programs can be
|
||||
* specified as login shells, there is no such guarantee in general.
|
||||
* For the same reason, we must also ignore stop signals generated
|
||||
* from the console (SIGTSTP, SIGTTIN, and SIGTTOU) in order to
|
||||
* avoid any possibility of the parent being stopped when it
|
||||
* receives SIGCHLD from the terminating subshell. -- JWP
|
||||
*/
|
||||
{
|
||||
pid_t child;
|
||||
|
||||
/* Ignore these signals. The signal handlers will later be
|
||||
* restored to the default handlers. */
|
||||
(void) signal (SIGINT, SIG_IGN);
|
||||
(void) signal (SIGQUIT, SIG_IGN);
|
||||
(void) signal (SIGHUP, SIG_IGN);
|
||||
(void) signal (SIGTSTP, SIG_IGN);
|
||||
(void) signal (SIGTTIN, SIG_IGN);
|
||||
(void) signal (SIGTTOU, SIG_IGN);
|
||||
child = fork ();
|
||||
if ((pid_t)-1 == child) {
|
||||
/* error in fork() */
|
||||
fprintf (stderr, _("%s: failure forking: %s\n"),
|
||||
is_newgrp ? "newgrp" : "sg", strerror (errno));
|
||||
#ifdef WITH_AUDIT
|
||||
if (group) {
|
||||
snprintf (audit_buf, sizeof(audit_buf),
|
||||
"changing new-group=%s", group);
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
} else {
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
"changing", NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
}
|
||||
#endif
|
||||
exit (EXIT_FAILURE);
|
||||
} else if (child != 0) {
|
||||
/* parent - wait for child to finish, then log session close */
|
||||
int cst = 0;
|
||||
gid_t gid = getgid();
|
||||
struct group *grp = getgrgid (gid);
|
||||
pid_t pid;
|
||||
|
||||
do {
|
||||
errno = 0;
|
||||
pid = waitpid (child, &cst, WUNTRACED);
|
||||
if ((pid == child) && (WIFSTOPPED (cst) != 0)) {
|
||||
/* The child (shell) was suspended.
|
||||
* Suspend sg/newgrp. */
|
||||
kill (getpid (), SIGSTOP);
|
||||
/* wake child when resumed */
|
||||
kill (child, SIGCONT);
|
||||
}
|
||||
} while ( ((pid == child) && (WIFSTOPPED (cst) != 0))
|
||||
|| ((pid != child) && (errno == EINTR)));
|
||||
/* local, no need for xgetgrgid */
|
||||
if (NULL != grp) {
|
||||
SYSLOG ((LOG_INFO,
|
||||
"user '%s' (login '%s' on %s) returned to group '%s'",
|
||||
name, loginname, tty, grp->gr_name));
|
||||
} else {
|
||||
SYSLOG ((LOG_INFO,
|
||||
"user '%s' (login '%s' on %s) returned to group '%lu'",
|
||||
name, loginname, tty,
|
||||
(unsigned long) gid));
|
||||
/* Either the user's passwd entry has a
|
||||
* GID that does not match with any group,
|
||||
* or the group was deleted while the user
|
||||
* was in a newgrp session.*/
|
||||
SYSLOG ((LOG_WARN,
|
||||
"unknown GID '%lu' used by user '%s'",
|
||||
(unsigned long) gid, name));
|
||||
}
|
||||
closelog ();
|
||||
exit ((0 != WIFEXITED (cst)) ? WEXITSTATUS (cst)
|
||||
: WTERMSIG (cst) + 128);
|
||||
}
|
||||
|
||||
/* child - restore signals to their default state */
|
||||
(void) signal (SIGINT, SIG_DFL);
|
||||
(void) signal (SIGQUIT, SIG_DFL);
|
||||
(void) signal (SIGHUP, SIG_DFL);
|
||||
(void) signal (SIGTSTP, SIG_DFL);
|
||||
(void) signal (SIGTTIN, SIG_DFL);
|
||||
(void) signal (SIGTTOU, SIG_DFL);
|
||||
}
|
||||
#endif /* USE_PAM */
|
||||
}
|
||||
#endif /* USE_SYSLOG */
|
||||
|
||||
/*
|
||||
* newgrp - change the invokers current real and effective group id
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
bool initflag = false;
|
||||
int i;
|
||||
bool cflag = false;
|
||||
int err = 0;
|
||||
gid_t gid;
|
||||
char *cp;
|
||||
const char *name, *prog;
|
||||
char *group = NULL;
|
||||
char *command = NULL;
|
||||
char **envp = environ;
|
||||
struct passwd *pwd;
|
||||
/*@null@*/struct group *grp;
|
||||
|
||||
#ifdef SHADOWGRP
|
||||
struct sgrp *sgrp;
|
||||
#endif
|
||||
|
||||
#ifdef WITH_AUDIT
|
||||
audit_help_open ();
|
||||
#endif
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
/*
|
||||
* Save my name for error messages and save my real gid incase of
|
||||
* errors. If there is an error i have to exec a new login shell for
|
||||
* the user since her old shell won't have fork'd to create the
|
||||
* process. Skip over the program name to the next command line
|
||||
* argument.
|
||||
*
|
||||
* This historical comment, and the code itself, suggest that the
|
||||
* behavior of the system/shell on which it was written differed
|
||||
* significantly from the one I am using. If this process was
|
||||
* started from a shell (including the login shell), it was fork'ed
|
||||
* and exec'ed as a child by that shell. In order to get the user
|
||||
* back to that shell, it is only necessary to exit from this
|
||||
* process which terminates the child of the fork. The parent shell,
|
||||
* which is blocked waiting for a signal, will then receive a
|
||||
* SIGCHLD and will continue; any changes made to the process
|
||||
* persona or the environment after the fork never occurred in the
|
||||
* parent process.
|
||||
*
|
||||
* Bottom line: we want to save the name and real gid for messages,
|
||||
* but we do not need to restore the previous process persona and we
|
||||
* don't need to re-exec anything. -- JWP
|
||||
*/
|
||||
Prog = Basename (argv[0]);
|
||||
is_newgrp = (strcmp (Prog, "newgrp") == 0);
|
||||
OPENLOG (is_newgrp ? "newgrp" : "sg");
|
||||
gid = getgid ();
|
||||
argc--;
|
||||
argv++;
|
||||
|
||||
initenv ();
|
||||
|
||||
pwd = get_my_pwent ();
|
||||
if (NULL == pwd) {
|
||||
fprintf (stderr, _("%s: Cannot determine your user name.\n"),
|
||||
Prog);
|
||||
#ifdef WITH_AUDIT
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
"changing", NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
#endif
|
||||
SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
|
||||
(unsigned long) getuid ()));
|
||||
closelog ();
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
name = pwd->pw_name;
|
||||
|
||||
/*
|
||||
* Parse the command line. There are two accepted flags. The first
|
||||
* is "-", which for newgrp means to re-create the entire
|
||||
* environment as though a login had been performed, and "-c", which
|
||||
* for sg causes a command string to be executed.
|
||||
*
|
||||
* The next argument, if present, must be the new group name. Any
|
||||
* remaining remaining arguments will be used to execute a command
|
||||
* as the named group. If the group name isn't present, I just use
|
||||
* the login group ID of the current user.
|
||||
*
|
||||
* The valid syntax are
|
||||
* newgrp [-] [groupid]
|
||||
* newgrp [-l] [groupid]
|
||||
* sg [-]
|
||||
* sg [-] groupid [[-c command]
|
||||
*/
|
||||
if ( (argc > 0)
|
||||
&& ( (strcmp (argv[0], "-") == 0)
|
||||
|| (strcmp (argv[0], "-l") == 0))) {
|
||||
argc--;
|
||||
argv++;
|
||||
initflag = true;
|
||||
}
|
||||
if (!is_newgrp) {
|
||||
/*
|
||||
* Do the command line for everything that is
|
||||
* not "newgrp".
|
||||
*/
|
||||
if ((argc > 0) && (argv[0][0] != '-')) {
|
||||
group = argv[0];
|
||||
argc--;
|
||||
argv++;
|
||||
} else {
|
||||
usage ();
|
||||
closelog ();
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
if (argc > 0) {
|
||||
|
||||
/*
|
||||
* skip -c if specified so both forms work:
|
||||
* "sg group -c command" (as in the man page) or
|
||||
* "sg group command" (as in the usage message).
|
||||
*/
|
||||
if ((argc > 1) && (strcmp (argv[0], "-c") == 0)) {
|
||||
command = argv[1];
|
||||
} else {
|
||||
command = argv[0];
|
||||
}
|
||||
cflag = true;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Do the command line for "newgrp". It's just making sure
|
||||
* there aren't any flags and getting the new group name.
|
||||
*/
|
||||
if ((argc > 0) && (argv[0][0] == '-')) {
|
||||
usage ();
|
||||
goto failure;
|
||||
} else if (argv[0] != (char *) 0) {
|
||||
group = argv[0];
|
||||
} else {
|
||||
/*
|
||||
* get the group file entry for her login group id.
|
||||
* the entry must exist, simply to be annoying.
|
||||
*
|
||||
* Perhaps in the past, but the default behavior now depends on the
|
||||
* group entry, so it had better exist. -- JWP
|
||||
*/
|
||||
grp = xgetgrgid (pwd->pw_gid);
|
||||
if (NULL == grp) {
|
||||
fprintf (stderr,
|
||||
_("%s: GID '%lu' does not exist\n"),
|
||||
Prog, (unsigned long) pwd->pw_gid);
|
||||
SYSLOG ((LOG_CRIT, "GID '%lu' does not exist",
|
||||
(unsigned long) pwd->pw_gid));
|
||||
goto failure;
|
||||
} else {
|
||||
group = grp->gr_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_SETGROUPS
|
||||
/*
|
||||
* get the current users groupset. The new group will be added to
|
||||
* the concurrent groupset if there is room, otherwise you get a
|
||||
* nasty message but at least your real and effective group id's are
|
||||
* set.
|
||||
*/
|
||||
/* don't use getgroups(0, 0) - it doesn't work on some systems */
|
||||
i = 16;
|
||||
for (;;) {
|
||||
grouplist = (GETGROUPS_T *) xmalloc (i * sizeof (GETGROUPS_T));
|
||||
ngroups = getgroups (i, grouplist);
|
||||
if (i > ngroups && !(ngroups == -1 && errno == EINVAL)) {
|
||||
break;
|
||||
}
|
||||
/* not enough room, so try allocating a larger buffer */
|
||||
free (grouplist);
|
||||
i *= 2;
|
||||
}
|
||||
if (ngroups < 0) {
|
||||
perror ("getgroups");
|
||||
#ifdef WITH_AUDIT
|
||||
if (group) {
|
||||
snprintf (audit_buf, sizeof(audit_buf),
|
||||
"changing new-group=%s", group);
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
} else {
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
"changing", NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
}
|
||||
#endif
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
#endif /* HAVE_SETGROUPS */
|
||||
|
||||
/*
|
||||
* now we put her in the new group. The password file entry for her
|
||||
* current user id has been gotten. If there was no optional group
|
||||
* argument she will have her real and effective group id set to the
|
||||
* set to the value from her password file entry.
|
||||
*
|
||||
* If run as newgrp, or as sg with no command, this process exec's
|
||||
* an interactive subshell with the effective GID of the new group.
|
||||
* If run as sg with a command, that command is exec'ed in this
|
||||
* subshell. When this process terminates, either because the user
|
||||
* exits, or the command completes, the parent of this process
|
||||
* resumes with the current GID.
|
||||
*
|
||||
* If a group is explicitly specified on the command line, the
|
||||
* interactive shell or command is run with that effective GID.
|
||||
* Access will be denied if no entry for that group can be found in
|
||||
* /etc/group. If the current user name appears in the members list
|
||||
* for that group, access will be granted immediately; if not, the
|
||||
* user will be challenged for that group's password. If the
|
||||
* password response is incorrect, if the specified group does not
|
||||
* have a password, or if that group has been locked by gpasswd -R,
|
||||
* access will be denied. This is true even if the group specified
|
||||
* has the user's login GID (as shown in /etc/passwd). If no group
|
||||
* is explicitly specified on the command line, the effect is
|
||||
* exactly the same as if a group name matching the user's login GID
|
||||
* had been explicitly specified. Root, however, is never
|
||||
* challenged for passwords, and is always allowed access.
|
||||
*
|
||||
* The previous behavior was to allow access to the login group if
|
||||
* no explicit group was specified, irrespective of the group
|
||||
* control file(s). This behavior is usually not desirable. A user
|
||||
* wishing to return to the login group has only to exit back to the
|
||||
* login shell. Generating yet more shell levels in order to
|
||||
* provide a convenient "return" to the default group has the
|
||||
* undesirable side effects of confusing the user, scrambling the
|
||||
* history file, and consuming system resources. The default now is
|
||||
* to lock out such behavior. A sys admin can allow it by explicitly
|
||||
* including the user's name in the member list of the user's login
|
||||
* group. -- JWP
|
||||
*/
|
||||
grp = getgrnam (group); /* local, no need for xgetgrnam */
|
||||
if (NULL == grp) {
|
||||
fprintf (stderr, _("%s: group '%s' does not exist\n"), Prog, group);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
/*
|
||||
* For splitted groups (due to limitations of NIS), check all
|
||||
* groups of the same GID like the requested group for
|
||||
* membership of the current user.
|
||||
*/
|
||||
grp = find_matching_group (name, grp->gr_gid);
|
||||
if (NULL == grp) {
|
||||
/*
|
||||
* No matching group found. As we already know that
|
||||
* the group exists, this happens only in the case
|
||||
* of a requested group where the user is not member.
|
||||
*
|
||||
* Re-read the group entry for further processing.
|
||||
*/
|
||||
grp = xgetgrnam (group);
|
||||
assert (NULL != grp);
|
||||
}
|
||||
#ifdef SHADOWGRP
|
||||
sgrp = getsgnam (group);
|
||||
if (NULL != sgrp) {
|
||||
grp->gr_passwd = sgrp->sg_passwd;
|
||||
grp->gr_mem = sgrp->sg_mem;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Check if the user is allowed to access this group.
|
||||
*/
|
||||
check_perms (grp, pwd, group);
|
||||
|
||||
/*
|
||||
* all successful validations pass through this point. The group id
|
||||
* will be set, and the group added to the concurrent groupset.
|
||||
*/
|
||||
#ifdef USE_SYSLOG
|
||||
if (getdef_bool ("SYSLOG_SG_ENAB")) {
|
||||
syslog_sg (name, group);
|
||||
}
|
||||
#endif /* USE_SYSLOG */
|
||||
|
||||
gid = grp->gr_gid;
|
||||
|
||||
#ifdef HAVE_SETGROUPS
|
||||
/*
|
||||
* I am going to try to add her new group id to her concurrent group
|
||||
* set. If the group id is already present i'll just skip this part.
|
||||
* If the group doesn't fit, i'll complain loudly and skip this
|
||||
* part.
|
||||
*/
|
||||
for (i = 0; i < ngroups; i++) {
|
||||
if (gid == grouplist[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ngroups) {
|
||||
if (ngroups >= sysconf (_SC_NGROUPS_MAX)) {
|
||||
(void) fputs (_("too many groups\n"), stderr);
|
||||
} else {
|
||||
grouplist[ngroups++] = gid;
|
||||
if (setgroups (ngroups, grouplist) != 0) {
|
||||
perror ("setgroups");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Close all files before changing the user/group IDs.
|
||||
*
|
||||
* The needed structure should have been copied before, or
|
||||
* permission to read the database will be required.
|
||||
*/
|
||||
endspent ();
|
||||
#ifdef SHADOWGRP
|
||||
endsgent ();
|
||||
#endif
|
||||
endpwent ();
|
||||
endgrent ();
|
||||
|
||||
/*
|
||||
* Set the effective GID to the new group id and the effective UID
|
||||
* to the real UID. For root, this also sets the real GID to the
|
||||
* new group id.
|
||||
*/
|
||||
if (setgid (gid) != 0) {
|
||||
perror ("setgid");
|
||||
#ifdef WITH_AUDIT
|
||||
snprintf (audit_buf, sizeof(audit_buf),
|
||||
"changing new-gid=%lu", (unsigned long) gid);
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
#endif
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (setuid (getuid ()) != 0) {
|
||||
perror ("setuid");
|
||||
#ifdef WITH_AUDIT
|
||||
snprintf (audit_buf, sizeof(audit_buf),
|
||||
"changing new-gid=%lu", (unsigned long) gid);
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
#endif
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* See if the "-c" flag was used. If it was, i just create a shell
|
||||
* command for her using the argument that followed the "-c" flag.
|
||||
*/
|
||||
if (cflag) {
|
||||
closelog ();
|
||||
execl (SHELL, "sh", "-c", command, (char *) 0);
|
||||
#ifdef WITH_AUDIT
|
||||
snprintf (audit_buf, sizeof(audit_buf),
|
||||
"changing new-gid=%lu", (unsigned long) gid);
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
#endif
|
||||
perror (SHELL);
|
||||
exit ((errno == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
|
||||
}
|
||||
|
||||
/*
|
||||
* I have to get the pathname of her login shell. As a favor, i'll
|
||||
* try her environment for a $SHELL value first, and then try the
|
||||
* password file entry. Obviously this shouldn't be in the
|
||||
* restricted command directory since it could be used to leave the
|
||||
* restricted environment.
|
||||
*
|
||||
* Note that the following assumes this user's entry in /etc/passwd
|
||||
* does not have a chroot * prefix. If it does, the * will be copied
|
||||
* verbatim into the exec path. This is probably not an issue
|
||||
* because if this user is operating in a chroot jail, her entry in
|
||||
* the version of /etc/passwd that is accessible here should
|
||||
* probably never have a chroot shell entry (but entries for other
|
||||
* users might). If I have missed something, and this causes you a
|
||||
* problem, try using $SHELL as a workaround; also please notify me
|
||||
* at jparmele@wildbear.com -- JWP
|
||||
*/
|
||||
cp = getenv ("SHELL");
|
||||
if (!initflag && (NULL != cp)) {
|
||||
prog = cp;
|
||||
} else if ((NULL != pwd->pw_shell) && ('\0' != pwd->pw_shell[0])) {
|
||||
prog = pwd->pw_shell;
|
||||
} else {
|
||||
prog = SHELL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now I try to find the basename of the login shell. This will
|
||||
* become argv[0] of the spawned command.
|
||||
*/
|
||||
cp = Basename ((char *) prog);
|
||||
|
||||
/*
|
||||
* Switch back to her home directory if i am doing login
|
||||
* initialization.
|
||||
*/
|
||||
if (initflag) {
|
||||
if (chdir (pwd->pw_dir) != 0) {
|
||||
perror ("chdir");
|
||||
}
|
||||
|
||||
while (NULL != *envp) {
|
||||
if (strncmp (*envp, "PATH=", 5) == 0 ||
|
||||
strncmp (*envp, "HOME=", 5) == 0 ||
|
||||
strncmp (*envp, "SHELL=", 6) == 0 ||
|
||||
strncmp (*envp, "TERM=", 5) == 0)
|
||||
addenv (*envp, NULL);
|
||||
|
||||
envp++;
|
||||
}
|
||||
} else {
|
||||
while (NULL != *envp) {
|
||||
addenv (*envp, NULL);
|
||||
envp++;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_AUDIT
|
||||
snprintf (audit_buf, sizeof(audit_buf), "changing new-gid=%lu",
|
||||
(unsigned long) gid);
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 1);
|
||||
#endif
|
||||
/*
|
||||
* Exec the login shell and go away. We are trying to get back to
|
||||
* the previous environment which should be the user's login shell.
|
||||
*/
|
||||
err = shell (prog, initflag ? (char *) 0 : cp, newenvp);
|
||||
exit ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
|
||||
/*@notreached@*/
|
||||
failure:
|
||||
|
||||
/*
|
||||
* The previous code, when run as newgrp, re-exec'ed the shell in
|
||||
* the current process with the original gid on error conditions.
|
||||
* See the comment above. This historical behavior now has the
|
||||
* effect of creating unlogged extraneous shell layers when the
|
||||
* command line has an error or there is an authentication failure.
|
||||
* We now just want to exit with error status back to the parent
|
||||
* process. The closelog is probably unnecessary, but it does no
|
||||
* harm. -- JWP
|
||||
*/
|
||||
closelog ();
|
||||
#ifdef WITH_AUDIT
|
||||
if (NULL != group) {
|
||||
snprintf (audit_buf, sizeof(audit_buf),
|
||||
"changing new-group=%s", group);
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
audit_buf, NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
} else {
|
||||
audit_logger (AUDIT_CHGRP_ID, Prog,
|
||||
"changing", NULL,
|
||||
(unsigned int) getuid (), 0);
|
||||
}
|
||||
#endif
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
1082
src/newusers.c
Normal file
1082
src/newusers.c
Normal file
File diff suppressed because it is too large
Load Diff
55
src/nologin.c
Normal file
55
src/nologin.c
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2004 The FreeBSD Project.
|
||||
* 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.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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>
|
||||
|
||||
#ident "$Id: nologin.c 2862 2009-05-09 13:14:23Z nekral-guest $"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
const char *user, *tty;
|
||||
|
||||
tty = ttyname (0);
|
||||
if (NULL == tty) {
|
||||
tty = "UNKNOWN";
|
||||
}
|
||||
user = getlogin ();
|
||||
if (NULL == user) {
|
||||
user = "UNKNOWN";
|
||||
}
|
||||
openlog ("nologin", LOG_CONS, LOG_AUTH);
|
||||
syslog (LOG_CRIT, "Attempted login by %s on %s", user, tty);
|
||||
closelog ();
|
||||
|
||||
printf ("%s", "This account is currently not available.\n");
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
1147
src/passwd.c
Normal file
1147
src/passwd.c
Normal file
File diff suppressed because it is too large
Load Diff
893
src/pwck.c
Normal file
893
src/pwck.c
Normal file
@@ -0,0 +1,893 @@
|
||||
/*
|
||||
* Copyright (c) 1992 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 , Michał Moskal
|
||||
* Copyright (c) 2001 - 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>
|
||||
|
||||
#ident "$Id: pwck.c 3574 2011-11-13 16:24:39Z nekral-guest $"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <getopt.h>
|
||||
#include "chkname.h"
|
||||
#include "commonio.h"
|
||||
#include "defines.h"
|
||||
#include "prototypes.h"
|
||||
#include "pwio.h"
|
||||
#include "shadowio.h"
|
||||
#include "getdef.h"
|
||||
#include "nscd.h"
|
||||
#ifdef WITH_TCB
|
||||
#include "tcbfuncs.h"
|
||||
#endif /* WITH_TCB */
|
||||
|
||||
/*
|
||||
* Exit codes
|
||||
*/
|
||||
/*@-exitarg@*/
|
||||
#define E_OKAY 0
|
||||
#define E_SUCCESS 0
|
||||
#define E_USAGE 1
|
||||
#define E_BADENTRY 2
|
||||
#define E_CANTOPEN 3
|
||||
#define E_CANTLOCK 4
|
||||
#define E_CANTUPDATE 5
|
||||
#define E_CANTSORT 6
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static bool use_system_pw_file = true;
|
||||
static bool use_system_spw_file = true;
|
||||
|
||||
static bool is_shadow = false;
|
||||
|
||||
static bool spw_opened = false;
|
||||
|
||||
static bool pw_locked = false;
|
||||
static bool spw_locked = false;
|
||||
|
||||
/* Options */
|
||||
static bool read_only = false;
|
||||
static bool sort_mode = false;
|
||||
static bool quiet = false; /* don't report warnings, only errors */
|
||||
|
||||
/* local function prototypes */
|
||||
static void fail_exit (int code);
|
||||
static /*@noreturn@*/void usage (int status);
|
||||
static void process_flags (int argc, char **argv);
|
||||
static void open_files (void);
|
||||
static void close_files (bool changed);
|
||||
static void check_pw_file (int *errors, bool *changed);
|
||||
static void check_spw_file (int *errors, bool *changed);
|
||||
|
||||
/*
|
||||
* fail_exit - do some cleanup and exit with the given error code
|
||||
*/
|
||||
static void fail_exit (int code)
|
||||
{
|
||||
if (spw_locked) {
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
|
||||
if (use_system_spw_file) {
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s",
|
||||
spw_dbname ()));
|
||||
}
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
|
||||
if (pw_locked) {
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
if (use_system_pw_file) {
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s",
|
||||
pw_dbname ()));
|
||||
}
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
|
||||
closelog ();
|
||||
|
||||
exit (code);
|
||||
}
|
||||
/*
|
||||
* usage - print syntax message and exit
|
||||
*/
|
||||
static /*@noreturn@*/void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
#ifdef WITH_TCB
|
||||
if (getdef_bool ("USE_TCB")) {
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options] [passwd]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
} else
|
||||
#endif /* WITH_TCB */
|
||||
{
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options] [passwd [shadow]]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
}
|
||||
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs (_(" -q, --quiet report errors only\n"), usageout);
|
||||
(void) fputs (_(" -r, --read-only display errors and warnings\n"
|
||||
" but do not change files\n"), usageout);
|
||||
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
#ifdef WITH_TCB
|
||||
if (!getdef_bool ("USE_TCB"))
|
||||
#endif /* !WITH_TCB */
|
||||
{
|
||||
(void) fputs (_(" -s, --sort sort entries by UID\n"), usageout);
|
||||
}
|
||||
(void) fputs ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"quiet", no_argument, NULL, 'q'},
|
||||
{"read-only", no_argument, NULL, 'r'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{"sort", no_argument, NULL, 's'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
/*
|
||||
* Parse the command line arguments
|
||||
*/
|
||||
while ((c = getopt_long (argc, argv, "ehqrR:s",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'e': /* added for Debian shadow-961025-2 compatibility */
|
||||
case 'q':
|
||||
quiet = true;
|
||||
break;
|
||||
case 'r':
|
||||
read_only = true;
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
case 's':
|
||||
sort_mode = true;
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (sort_mode && read_only) {
|
||||
fprintf (stderr, _("%s: -s and -r are incompatible\n"), Prog);
|
||||
exit (E_USAGE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make certain we have the right number of arguments
|
||||
*/
|
||||
if (argc > (optind + 2)) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
|
||||
/*
|
||||
* If there are two left over filenames, use those as the password
|
||||
* and shadow password filenames.
|
||||
*/
|
||||
if (optind != argc) {
|
||||
pw_setdbname (argv[optind]);
|
||||
use_system_pw_file = false;
|
||||
}
|
||||
if ((optind + 2) == argc) {
|
||||
#ifdef WITH_TCB
|
||||
if (getdef_bool ("USE_TCB")) {
|
||||
fprintf (stderr,
|
||||
_("%s: no alternative shadow file allowed when USE_TCB is enabled.\n"),
|
||||
Prog);
|
||||
usage (E_USAGE);
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
spw_setdbname (argv[optind + 1]);
|
||||
is_shadow = true;
|
||||
use_system_spw_file = false;
|
||||
} else if (optind == argc) {
|
||||
is_shadow = spw_file_present ();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* open_files - open the shadow database
|
||||
*
|
||||
* In read-only mode, the databases are not locked and are opened
|
||||
* only for reading.
|
||||
*/
|
||||
static void open_files (void)
|
||||
{
|
||||
bool use_tcb = false;
|
||||
#ifdef WITH_TCB
|
||||
use_tcb = getdef_bool ("USE_TCB");
|
||||
#endif /* WITH_TCB */
|
||||
|
||||
/*
|
||||
* Lock the files if we aren't in "read-only" mode
|
||||
*/
|
||||
if (!read_only) {
|
||||
if (pw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, pw_dbname ());
|
||||
fail_exit (E_CANTLOCK);
|
||||
}
|
||||
pw_locked = true;
|
||||
if (is_shadow && !use_tcb) {
|
||||
if (spw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, spw_dbname ());
|
||||
fail_exit (E_CANTLOCK);
|
||||
}
|
||||
spw_locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Open the files. Use O_RDONLY if we are in read_only mode, O_RDWR
|
||||
* otherwise.
|
||||
*/
|
||||
if (pw_open (read_only ? O_RDONLY : O_RDWR) == 0) {
|
||||
fprintf (stderr, _("%s: cannot open %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
if (use_system_pw_file) {
|
||||
SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
|
||||
}
|
||||
fail_exit (E_CANTOPEN);
|
||||
}
|
||||
if (is_shadow && !use_tcb) {
|
||||
if (spw_open (read_only ? O_RDONLY : O_RDWR) == 0) {
|
||||
fprintf (stderr, _("%s: cannot open %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
if (use_system_spw_file) {
|
||||
SYSLOG ((LOG_WARN, "cannot open %s",
|
||||
spw_dbname ()));
|
||||
}
|
||||
fail_exit (E_CANTOPEN);
|
||||
}
|
||||
spw_opened = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* close_files - close and unlock the password/shadow databases
|
||||
*
|
||||
* If changed is not set, the databases are not closed, and no
|
||||
* changes are committed in the databases. The databases are
|
||||
* unlocked anyway.
|
||||
*/
|
||||
static void close_files (bool changed)
|
||||
{
|
||||
/*
|
||||
* All done. If there were no change we can just abandon any
|
||||
* changes to the files.
|
||||
*/
|
||||
if (changed) {
|
||||
if (pw_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
if (use_system_pw_file) {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"failure while writing changes to %s",
|
||||
pw_dbname ()));
|
||||
}
|
||||
fail_exit (E_CANTUPDATE);
|
||||
}
|
||||
if (spw_opened && (spw_close () == 0)) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
if (use_system_spw_file) {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"failure while writing changes to %s",
|
||||
spw_dbname ()));
|
||||
}
|
||||
fail_exit (E_CANTUPDATE);
|
||||
}
|
||||
spw_opened = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't be anti-social - unlock the files when you're done.
|
||||
*/
|
||||
if (spw_locked) {
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to unlock %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
if (use_system_spw_file) {
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s",
|
||||
spw_dbname ()));
|
||||
}
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
spw_locked = false;
|
||||
if (pw_locked) {
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to unlock %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
if (use_system_pw_file) {
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s",
|
||||
pw_dbname ()));
|
||||
}
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
pw_locked = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* check_pw_file - check the content of the passwd file
|
||||
*/
|
||||
static void check_pw_file (int *errors, bool *changed)
|
||||
{
|
||||
struct commonio_entry *pfe, *tpfe;
|
||||
struct passwd *pwd;
|
||||
struct spwd *spw;
|
||||
|
||||
/*
|
||||
* Loop through the entire password file.
|
||||
*/
|
||||
for (pfe = __pw_get_head (); NULL != pfe; pfe = pfe->next) {
|
||||
/*
|
||||
* If this is a NIS line, skip it. You can't "know" what NIS
|
||||
* is going to do without directly asking NIS ...
|
||||
*/
|
||||
if (('+' == pfe->line[0]) || ('-' == pfe->line[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start with the entries that are completely corrupt. They
|
||||
* have no (struct passwd) entry because they couldn't be
|
||||
* parsed properly.
|
||||
*/
|
||||
if (NULL == pfe->eptr) {
|
||||
/*
|
||||
* Tell the user this entire line is bogus and ask
|
||||
* them to delete it.
|
||||
*/
|
||||
puts (_("invalid password file entry"));
|
||||
printf (_("delete line '%s'? "), pfe->line);
|
||||
*errors += 1;
|
||||
|
||||
/*
|
||||
* prompt the user to delete the entry or not
|
||||
*/
|
||||
if (!yes_or_no (read_only)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* All password file deletions wind up here. This
|
||||
* code removes the current entry from the linked
|
||||
* list. When done, it skips back to the top of the
|
||||
* loop to try out the next list element.
|
||||
*/
|
||||
delete_pw:
|
||||
if (use_system_pw_file) {
|
||||
SYSLOG ((LOG_INFO, "delete passwd line '%s'",
|
||||
pfe->line));
|
||||
}
|
||||
*changed = true;
|
||||
|
||||
__pw_del_entry (pfe);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Password structure is good, start using it.
|
||||
*/
|
||||
pwd = pfe->eptr;
|
||||
|
||||
/*
|
||||
* Make sure this entry has a unique name.
|
||||
*/
|
||||
for (tpfe = __pw_get_head (); NULL != tpfe; tpfe = tpfe->next) {
|
||||
const struct passwd *ent = tpfe->eptr;
|
||||
|
||||
/*
|
||||
* Don't check this entry
|
||||
*/
|
||||
if (tpfe == pfe) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't check invalid entries.
|
||||
*/
|
||||
if (NULL == ent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp (pwd->pw_name, ent->pw_name) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the user this entry is a duplicate of
|
||||
* another and ask them to delete it.
|
||||
*/
|
||||
puts (_("duplicate password entry"));
|
||||
printf (_("delete line '%s'? "), pfe->line);
|
||||
*errors += 1;
|
||||
|
||||
/*
|
||||
* prompt the user to delete the entry or not
|
||||
*/
|
||||
if (yes_or_no (read_only)) {
|
||||
goto delete_pw;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for invalid usernames. --marekm
|
||||
*/
|
||||
if (!is_valid_user_name (pwd->pw_name)) {
|
||||
printf (_("invalid user name '%s'\n"), pwd->pw_name);
|
||||
*errors += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for invalid user ID.
|
||||
*/
|
||||
if (pwd->pw_uid == (uid_t)-1) {
|
||||
printf (_("invalid user ID '%lu'\n"), (long unsigned int)pwd->pw_uid);
|
||||
*errors += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the primary group exists
|
||||
*/
|
||||
/* local, no need for xgetgrgid */
|
||||
if (!quiet && (NULL == getgrgid (pwd->pw_gid))) {
|
||||
|
||||
/*
|
||||
* No primary group, just give a warning
|
||||
*/
|
||||
|
||||
printf (_("user '%s': no group %lu\n"),
|
||||
pwd->pw_name, (unsigned long) pwd->pw_gid);
|
||||
*errors += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the home directory exists
|
||||
*/
|
||||
if (!quiet && (access (pwd->pw_dir, F_OK) != 0)) {
|
||||
/*
|
||||
* Home directory doesn't exist, give a warning
|
||||
*/
|
||||
printf (_("user '%s': directory '%s' does not exist\n"),
|
||||
pwd->pw_name, pwd->pw_dir);
|
||||
*errors += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the login shell is executable
|
||||
*/
|
||||
if ( !quiet
|
||||
&& ('\0' != pwd->pw_shell[0])
|
||||
&& (access (pwd->pw_shell, F_OK) != 0)) {
|
||||
|
||||
/*
|
||||
* Login shell doesn't exist, give a warning
|
||||
*/
|
||||
printf (_("user '%s': program '%s' does not exist\n"),
|
||||
pwd->pw_name, pwd->pw_shell);
|
||||
*errors += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure this entry exists in the /etc/shadow file.
|
||||
*/
|
||||
|
||||
if (is_shadow) {
|
||||
#ifdef WITH_TCB
|
||||
if (getdef_bool ("USE_TCB")) {
|
||||
if (shadowtcb_set_user (pwd->pw_name) == SHADOWTCB_FAILURE) {
|
||||
printf (_("no tcb directory for %s\n"),
|
||||
pwd->pw_name);
|
||||
printf (_("create tcb directory for %s?"),
|
||||
pwd->pw_name);
|
||||
*errors += 1;
|
||||
if (yes_or_no (read_only)) {
|
||||
if (shadowtcb_create (pwd->pw_name, pwd->pw_uid) == SHADOWTCB_FAILURE) {
|
||||
*errors += 1;
|
||||
printf (_("failed to create tcb directory for %s\n"), pwd->pw_name);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (spw_lock () == 0) {
|
||||
*errors += 1;
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s.\n"),
|
||||
Prog, spw_dbname ());
|
||||
continue;
|
||||
}
|
||||
spw_locked = true;
|
||||
if (spw_open (read_only ? O_RDONLY : O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
*errors += 1;
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to unlock %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
if (use_system_spw_file) {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"failed to unlock %s",
|
||||
spw_dbname ()));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
spw_opened = true;
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
spw = (struct spwd *) spw_locate (pwd->pw_name);
|
||||
if (NULL == spw) {
|
||||
printf (_("no matching password file entry in %s\n"),
|
||||
spw_dbname ());
|
||||
printf (_("add user '%s' in %s? "),
|
||||
pwd->pw_name, spw_dbname ());
|
||||
*errors += 1;
|
||||
if (yes_or_no (read_only)) {
|
||||
struct spwd sp;
|
||||
struct passwd pw;
|
||||
|
||||
sp.sp_namp = pwd->pw_name;
|
||||
sp.sp_pwdp = pwd->pw_passwd;
|
||||
sp.sp_min =
|
||||
getdef_num ("PASS_MIN_DAYS", -1);
|
||||
sp.sp_max =
|
||||
getdef_num ("PASS_MAX_DAYS", -1);
|
||||
sp.sp_warn =
|
||||
getdef_num ("PASS_WARN_AGE", -1);
|
||||
sp.sp_inact = -1;
|
||||
sp.sp_expire = -1;
|
||||
sp.sp_flag = SHADOW_SP_FLAG_UNSET;
|
||||
sp.sp_lstchg = (long) time ((time_t *) 0) / SCALE;
|
||||
if (0 == sp.sp_lstchg) {
|
||||
/* Better disable aging than
|
||||
* requiring a password change
|
||||
*/
|
||||
sp.sp_lstchg = -1;
|
||||
}
|
||||
*changed = true;
|
||||
|
||||
if (spw_update (&sp) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, spw_dbname (), sp.sp_namp);
|
||||
fail_exit (E_CANTUPDATE);
|
||||
}
|
||||
/* remove password from /etc/passwd */
|
||||
pw = *pwd;
|
||||
pw.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
|
||||
if (pw_update (&pw) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, pw_dbname (), pw.pw_name);
|
||||
fail_exit (E_CANTUPDATE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* The passwd entry has a shadow counterpart.
|
||||
* Make sure no passwords are in passwd.
|
||||
*/
|
||||
if ( !quiet
|
||||
&& (strcmp (pwd->pw_passwd,
|
||||
SHADOW_PASSWD_STRING) != 0)) {
|
||||
printf (_("user %s has an entry in %s, but its password field in %s is not set to 'x'\n"),
|
||||
pwd->pw_name, spw_dbname (), pw_dbname ());
|
||||
*errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef WITH_TCB
|
||||
if (getdef_bool ("USE_TCB") && spw_locked) {
|
||||
if (spw_opened && (spw_close () == 0)) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
if (use_system_spw_file) {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"failure while writing changes to %s",
|
||||
spw_dbname ()));
|
||||
}
|
||||
} else {
|
||||
spw_opened = false;
|
||||
}
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to unlock %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
if (use_system_spw_file) {
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s",
|
||||
spw_dbname ()));
|
||||
}
|
||||
} else {
|
||||
spw_locked = false;
|
||||
}
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check_spw_file - check the content of the shadowed password file (shadow)
|
||||
*/
|
||||
static void check_spw_file (int *errors, bool *changed)
|
||||
{
|
||||
struct commonio_entry *spe, *tspe;
|
||||
struct spwd *spw;
|
||||
|
||||
/*
|
||||
* Loop through the entire shadow password file.
|
||||
*/
|
||||
for (spe = __spw_get_head (); NULL != spe; spe = spe->next) {
|
||||
/*
|
||||
* Do not treat lines which were missing in shadow
|
||||
* and were added earlier.
|
||||
*/
|
||||
if (NULL == spe->line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is a NIS line, skip it. You can't "know" what NIS
|
||||
* is going to do without directly asking NIS ...
|
||||
*/
|
||||
if (('+' == spe->line[0]) || ('-' == spe->line[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start with the entries that are completely corrupt. They
|
||||
* have no (struct spwd) entry because they couldn't be
|
||||
* parsed properly.
|
||||
*/
|
||||
if (NULL == spe->eptr) {
|
||||
/*
|
||||
* Tell the user this entire line is bogus and ask
|
||||
* them to delete it.
|
||||
*/
|
||||
puts (_("invalid shadow password file entry"));
|
||||
printf (_("delete line '%s'? "), spe->line);
|
||||
*errors += 1;
|
||||
|
||||
/*
|
||||
* prompt the user to delete the entry or not
|
||||
*/
|
||||
if (!yes_or_no (read_only)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* All shadow file deletions wind up here. This code
|
||||
* removes the current entry from the linked list.
|
||||
* When done, it skips back to the top of the loop
|
||||
* to try out the next list element.
|
||||
*/
|
||||
delete_spw:
|
||||
if (use_system_spw_file) {
|
||||
SYSLOG ((LOG_INFO, "delete shadow line '%s'",
|
||||
spe->line));
|
||||
}
|
||||
*changed = true;
|
||||
|
||||
__spw_del_entry (spe);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Shadow password structure is good, start using it.
|
||||
*/
|
||||
spw = spe->eptr;
|
||||
|
||||
/*
|
||||
* Make sure this entry has a unique name.
|
||||
*/
|
||||
for (tspe = __spw_get_head (); NULL != tspe; tspe = tspe->next) {
|
||||
const struct spwd *ent = tspe->eptr;
|
||||
|
||||
/*
|
||||
* Don't check this entry
|
||||
*/
|
||||
if (tspe == spe) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't check invalid entries.
|
||||
*/
|
||||
if (NULL == ent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp (spw->sp_namp, ent->sp_namp) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the user this entry is a duplicate of
|
||||
* another and ask them to delete it.
|
||||
*/
|
||||
puts (_("duplicate shadow password entry"));
|
||||
printf (_("delete line '%s'? "), spe->line);
|
||||
*errors += 1;
|
||||
|
||||
/*
|
||||
* prompt the user to delete the entry or not
|
||||
*/
|
||||
if (yes_or_no (read_only)) {
|
||||
goto delete_spw;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure this entry exists in the /etc/passwd
|
||||
* file.
|
||||
*/
|
||||
if (pw_locate (spw->sp_namp) == NULL) {
|
||||
/*
|
||||
* Tell the user this entry has no matching
|
||||
* /etc/passwd entry and ask them to delete it.
|
||||
*/
|
||||
printf (_("no matching password file entry in %s\n"),
|
||||
pw_dbname ());
|
||||
printf (_("delete line '%s'? "), spe->line);
|
||||
*errors += 1;
|
||||
|
||||
/*
|
||||
* prompt the user to delete the entry or not
|
||||
*/
|
||||
if (yes_or_no (read_only)) {
|
||||
goto delete_spw;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Warn if last password change in the future. --marekm
|
||||
*/
|
||||
if (!quiet) {
|
||||
time_t t = time ((time_t *) 0);
|
||||
if ( (t != 0)
|
||||
&& (spw->sp_lstchg > (long) t / SCALE)) {
|
||||
printf (_("user %s: last password change in the future\n"),
|
||||
spw->sp_namp);
|
||||
*errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* pwck - verify password file integrity
|
||||
*/
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
int errors = 0;
|
||||
bool changed = false;
|
||||
|
||||
/*
|
||||
* 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 ("pwck");
|
||||
|
||||
/* Parse the command line arguments */
|
||||
process_flags (argc, argv);
|
||||
|
||||
open_files ();
|
||||
|
||||
if (sort_mode) {
|
||||
if (pw_sort () != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot sort entries in %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
fail_exit (E_CANTSORT);
|
||||
}
|
||||
if (is_shadow) {
|
||||
if (spw_sort () != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot sort entries in %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
fail_exit (E_CANTSORT);
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
} else {
|
||||
check_pw_file (&errors, &changed);
|
||||
|
||||
if (is_shadow) {
|
||||
check_spw_file (&errors, &changed);
|
||||
}
|
||||
}
|
||||
|
||||
close_files (changed);
|
||||
|
||||
nscd_flush_cache ("passwd");
|
||||
|
||||
/*
|
||||
* Tell the user what we did and exit.
|
||||
*/
|
||||
if (0 != errors) {
|
||||
printf (changed ?
|
||||
_("%s: the files have been updated\n") :
|
||||
_("%s: no changes\n"), Prog);
|
||||
}
|
||||
|
||||
closelog ();
|
||||
return ((0 != errors) ? E_BADENTRY : E_OKAY);
|
||||
}
|
||||
|
||||
332
src/pwconv.c
Normal file
332
src/pwconv.c
Normal file
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2002 - 2006, Tomasz Kłoczko
|
||||
* Copyright (c) 2009 - 2012, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* pwconv - create or update /etc/shadow with information from
|
||||
* /etc/passwd.
|
||||
*
|
||||
* It is more like SysV pwconv, slightly different from the original Shadow
|
||||
* pwconv. Depends on "x" as password in /etc/passwd which means that the
|
||||
* password has already been moved to /etc/shadow. There is no need to move
|
||||
* /etc/npasswd to /etc/passwd, password files are updated using library
|
||||
* routines with proper locking.
|
||||
*
|
||||
* Can be used to update /etc/shadow after adding/deleting users by editing
|
||||
* /etc/passwd. There is no man page yet, but this program should be close
|
||||
* to pwconv(1M) on Solaris 2.x.
|
||||
*
|
||||
* Warning: make sure that all users have "x" as the password in /etc/passwd
|
||||
* before running this program for the first time on a system which already
|
||||
* has shadow passwords. Anything else (like "*" from old versions of the
|
||||
* shadow suite) will replace the user's encrypted password in /etc/shadow.
|
||||
*
|
||||
* Doesn't currently support pw_age information in /etc/passwd, and doesn't
|
||||
* support DBM files. Add it if you need it...
|
||||
*
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#ident "$Id: pwconv.c 3743 2012-05-25 11:51:53Z nekral-guest $"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include "defines.h"
|
||||
#include "getdef.h"
|
||||
#include "prototypes.h"
|
||||
#include "pwio.h"
|
||||
#include "shadowio.h"
|
||||
#include "nscd.h"
|
||||
|
||||
/*
|
||||
* exit status values
|
||||
*/
|
||||
/*@-exitarg@*/
|
||||
#define E_SUCCESS 0 /* success */
|
||||
#define E_NOPERM 1 /* permission denied */
|
||||
#define E_USAGE 2 /* invalid command syntax */
|
||||
#define E_FAILURE 3 /* unexpected failure, nothing done */
|
||||
#define E_MISSING 4 /* unexpected failure, passwd file missing */
|
||||
#define E_PWDBUSY 5 /* passwd file(s) busy */
|
||||
#define E_BADENTRY 6 /* bad shadow entry */
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static bool spw_locked = false;
|
||||
static bool pw_locked = false;
|
||||
|
||||
/* local function prototypes */
|
||||
static void fail_exit (int status);
|
||||
static void usage (int status);
|
||||
static void process_flags (int argc, char **argv);
|
||||
|
||||
static void fail_exit (int status)
|
||||
{
|
||||
if (pw_locked) {
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
|
||||
if (spw_locked) {
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
|
||||
exit (status);
|
||||
}
|
||||
|
||||
static void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(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 ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv, "hR:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
const struct passwd *pw;
|
||||
struct passwd pwent;
|
||||
const struct spwd *sp;
|
||||
struct spwd spent;
|
||||
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
OPENLOG ("pwconv");
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
#ifdef WITH_TCB
|
||||
if (getdef_bool("USE_TCB")) {
|
||||
fprintf (stderr, _("%s: can't work with tcb enabled\n"), Prog);
|
||||
exit (E_FAILURE);
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
|
||||
if (pw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, pw_dbname ());
|
||||
fail_exit (E_PWDBUSY);
|
||||
}
|
||||
pw_locked = true;
|
||||
if (pw_open (O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"), Prog, pw_dbname ());
|
||||
fail_exit (E_MISSING);
|
||||
}
|
||||
|
||||
if (spw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, spw_dbname ());
|
||||
fail_exit (E_PWDBUSY);
|
||||
}
|
||||
spw_locked = true;
|
||||
if (spw_open (O_CREAT | O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"), Prog, spw_dbname ());
|
||||
fail_exit (E_FAILURE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove /etc/shadow entries for users not in /etc/passwd.
|
||||
*/
|
||||
(void) spw_rewind ();
|
||||
while ((sp = spw_next ()) != NULL) {
|
||||
if (pw_locate (sp->sp_namp) != NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (spw_remove (sp->sp_namp) == 0) {
|
||||
/*
|
||||
* This shouldn't happen (the entry exists) but...
|
||||
*/
|
||||
fprintf (stderr,
|
||||
_("%s: cannot remove entry '%s' from %s\n"),
|
||||
Prog, sp->sp_namp, spw_dbname ());
|
||||
fail_exit (E_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update shadow entries which don't have "x" as pw_passwd. Add any
|
||||
* missing shadow entries.
|
||||
*/
|
||||
(void) pw_rewind ();
|
||||
while ((pw = pw_next ()) != NULL) {
|
||||
sp = spw_locate (pw->pw_name);
|
||||
if (NULL != sp) {
|
||||
/* do we need to update this entry? */
|
||||
if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
|
||||
continue;
|
||||
}
|
||||
/* update existing shadow entry */
|
||||
spent = *sp;
|
||||
} else {
|
||||
/* add new shadow entry */
|
||||
memset (&spent, 0, sizeof spent);
|
||||
spent.sp_namp = pw->pw_name;
|
||||
spent.sp_min = getdef_num ("PASS_MIN_DAYS", -1);
|
||||
spent.sp_max = getdef_num ("PASS_MAX_DAYS", -1);
|
||||
spent.sp_warn = getdef_num ("PASS_WARN_AGE", -1);
|
||||
spent.sp_inact = -1;
|
||||
spent.sp_expire = -1;
|
||||
spent.sp_flag = SHADOW_SP_FLAG_UNSET;
|
||||
}
|
||||
spent.sp_pwdp = pw->pw_passwd;
|
||||
spent.sp_lstchg = (long) time ((time_t *) 0) / SCALE;
|
||||
if (0 == spent.sp_lstchg) {
|
||||
/* Better disable aging than requiring a password
|
||||
* change */
|
||||
spent.sp_lstchg = -1;
|
||||
}
|
||||
if (spw_update (&spent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, spw_dbname (), spent.sp_namp);
|
||||
fail_exit (E_FAILURE);
|
||||
}
|
||||
|
||||
/* remove password from /etc/passwd */
|
||||
pwent = *pw;
|
||||
pwent.pw_passwd = SHADOW_PASSWD_STRING; /* XXX warning: const */
|
||||
if (pw_update (&pwent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, pw_dbname (), pwent.pw_name);
|
||||
fail_exit (E_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (spw_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
|
||||
fail_exit (E_FAILURE);
|
||||
}
|
||||
if (pw_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
|
||||
fail_exit (E_FAILURE);
|
||||
}
|
||||
|
||||
/* /etc/passwd- (backup file) */
|
||||
if (chmod (PASSWD_FILE "-", 0600) != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to change the mode of %s to 0600\n"),
|
||||
Prog, PASSWD_FILE "-");
|
||||
SYSLOG ((LOG_ERR, "failed to change the mode of %s to 0600", PASSWD_FILE "-"));
|
||||
/* continue */
|
||||
}
|
||||
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
|
||||
nscd_flush_cache ("passwd");
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
256
src/pwunconv.c
Normal file
256
src/pwunconv.c
Normal file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (c) 1989 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2001 - 2005, Tomasz Kłoczko
|
||||
* Copyright (c) 2008 - 2012, 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>
|
||||
|
||||
#ident "$Id: pwunconv.c 3743 2012-05-25 11:51:53Z nekral-guest $"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include "defines.h"
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
#include "pwio.h"
|
||||
#include "shadowio.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static bool spw_locked = false;
|
||||
static bool pw_locked = false;
|
||||
|
||||
/* local function prototypes */
|
||||
static void fail_exit (int status);
|
||||
static void usage (int status);
|
||||
static void process_flags (int argc, char **argv);
|
||||
|
||||
static void fail_exit (int status)
|
||||
{
|
||||
if (spw_locked) {
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
if (pw_locked) {
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
exit (status);
|
||||
}
|
||||
|
||||
static void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (usageout,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(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 ("\n", usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
* process_flags - parse the command line options
|
||||
*
|
||||
* It will not return if an error is encountered.
|
||||
*/
|
||||
static void process_flags (int argc, char **argv)
|
||||
{
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
|
||||
while ((c = getopt_long (argc, argv, "hR:",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
/*@notreached@*/break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc) {
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
const struct passwd *pw;
|
||||
struct passwd pwent;
|
||||
const struct spwd *spwd;
|
||||
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
OPENLOG ("pwunconv");
|
||||
|
||||
process_flags (argc, argv);
|
||||
|
||||
#ifdef WITH_TCB
|
||||
if (getdef_bool("USE_TCB")) {
|
||||
fprintf (stderr, _("%s: can't work with tcb enabled\n"), Prog);
|
||||
exit (1);
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
|
||||
if (!spw_file_present ()) {
|
||||
/* shadow not installed, do nothing */
|
||||
exit (0);
|
||||
}
|
||||
|
||||
if (pw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, pw_dbname ());
|
||||
fail_exit (5);
|
||||
}
|
||||
pw_locked = true;
|
||||
if (pw_open (O_RDWR) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
if (spw_lock () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot lock %s; try again later.\n"),
|
||||
Prog, spw_dbname ());
|
||||
fail_exit (5);
|
||||
}
|
||||
spw_locked = true;
|
||||
if (spw_open (O_RDONLY) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot open %s\n"),
|
||||
Prog, spw_dbname ());
|
||||
fail_exit (1);
|
||||
}
|
||||
|
||||
(void) pw_rewind ();
|
||||
while ((pw = pw_next ()) != NULL) {
|
||||
spwd = spw_locate (pw->pw_name);
|
||||
if (NULL == spwd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pwent = *pw;
|
||||
|
||||
/*
|
||||
* Update password if non-shadow is "x".
|
||||
*/
|
||||
if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
|
||||
pwent.pw_passwd = spwd->sp_pwdp;
|
||||
}
|
||||
|
||||
/*
|
||||
* Password aging works differently in the two different
|
||||
* systems. With shadow password files you apparently must
|
||||
* have some aging information. The maxweeks or minweeks
|
||||
* may not map exactly. In pwconv we set max == 10000,
|
||||
* which is about 30 years. Here we have to undo that
|
||||
* kludge. So, if maxdays == 10000, no aging information is
|
||||
* put into the new file. Otherwise, the days are converted
|
||||
* to weeks and so on.
|
||||
*/
|
||||
if (pw_update (&pwent) == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to prepare the new %s entry '%s'\n"),
|
||||
Prog, pw_dbname (), pwent.pw_name);
|
||||
fail_exit (3);
|
||||
}
|
||||
}
|
||||
|
||||
(void) spw_close (); /* was only open O_RDONLY */
|
||||
|
||||
if (pw_close () == 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: failure while writing changes to %s\n"),
|
||||
Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
|
||||
fail_exit (3);
|
||||
}
|
||||
|
||||
if (unlink (SHADOW) != 0) {
|
||||
fprintf (stderr,
|
||||
_("%s: cannot delete %s\n"), Prog, SHADOW);
|
||||
SYSLOG ((LOG_ERR, "cannot delete %s", SHADOW));
|
||||
fail_exit (3);
|
||||
}
|
||||
|
||||
if (spw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
if (pw_unlock () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
|
||||
/* continue */
|
||||
}
|
||||
|
||||
nscd_flush_cache ("passwd");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
239
src/suauth.c
Normal file
239
src/suauth.c
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright (c) 1990 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2002 - 2005, Tomasz Kłoczko
|
||||
* Copyright (c) 2007 - 2008, 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 <errno.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include "defines.h"
|
||||
#include "prototypes.h"
|
||||
|
||||
#ifndef SUAUTHFILE
|
||||
#define SUAUTHFILE "/etc/suauth"
|
||||
#endif
|
||||
|
||||
#define NOACTION 0
|
||||
#define NOPWORD 1
|
||||
#define DENY -1
|
||||
#define OWNPWORD 2
|
||||
|
||||
#ifdef SU_ACCESS
|
||||
|
||||
/* Really, I could do with a few const char's here defining all the
|
||||
* strings output to the user or the syslog. -- chris
|
||||
*/
|
||||
static int applies (const char *, char *);
|
||||
|
||||
static int isgrp (const char *, const char *);
|
||||
|
||||
static int lines = 0;
|
||||
|
||||
|
||||
int check_su_auth (const char *actual_id,
|
||||
const char *wanted_id,
|
||||
bool su_to_root)
|
||||
{
|
||||
int posn, endline;
|
||||
const char field[] = ":";
|
||||
FILE *authfile_fd;
|
||||
char temp[1024];
|
||||
char *to_users;
|
||||
char *from_users;
|
||||
char *action;
|
||||
|
||||
if (!(authfile_fd = fopen (SUAUTHFILE, "r"))) {
|
||||
int err = errno;
|
||||
/*
|
||||
* If the file doesn't exist - default to the standard su
|
||||
* behaviour (no access control). If open fails for some
|
||||
* other reason - maybe someone is trying to fool us with
|
||||
* file descriptors limit etc., so deny access. --marekm
|
||||
*/
|
||||
if (ENOENT == err) {
|
||||
return NOACTION;
|
||||
}
|
||||
SYSLOG ((LOG_ERR,
|
||||
"could not open/read config file '%s': %s\n",
|
||||
SUAUTHFILE, strerror (err)));
|
||||
return DENY;
|
||||
}
|
||||
|
||||
while (fgets (temp, sizeof (temp), authfile_fd) != NULL) {
|
||||
lines++;
|
||||
|
||||
if (temp[endline = strlen (temp) - 1] != '\n') {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s, line %d: line too long or missing newline",
|
||||
SUAUTHFILE, lines));
|
||||
continue;
|
||||
}
|
||||
|
||||
while (endline > 0 && (temp[endline - 1] == ' '
|
||||
|| temp[endline - 1] == '\t'
|
||||
|| temp[endline - 1] == '\n'))
|
||||
endline--;
|
||||
temp[endline] = '\0';
|
||||
|
||||
posn = 0;
|
||||
while (temp[posn] == ' ' || temp[posn] == '\t')
|
||||
posn++;
|
||||
|
||||
if (temp[posn] == '\n' || temp[posn] == '#'
|
||||
|| temp[posn] == '\0') {
|
||||
continue;
|
||||
}
|
||||
if (!(to_users = strtok (temp + posn, field))
|
||||
|| !(from_users = strtok ((char *) NULL, field))
|
||||
|| !(action = strtok ((char *) NULL, field))
|
||||
|| strtok ((char *) NULL, field)) {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s, line %d. Bad number of fields.\n",
|
||||
SUAUTHFILE, lines));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!applies (wanted_id, to_users))
|
||||
continue;
|
||||
if (!applies (actual_id, from_users))
|
||||
continue;
|
||||
if (!strcmp (action, "DENY")) {
|
||||
SYSLOG ((su_to_root ? LOG_WARN : LOG_NOTICE,
|
||||
"DENIED su from '%s' to '%s' (%s)\n",
|
||||
actual_id, wanted_id, SUAUTHFILE));
|
||||
fputs (_("Access to su to that account DENIED.\n"),
|
||||
stderr);
|
||||
fclose (authfile_fd);
|
||||
return DENY;
|
||||
} else if (!strcmp (action, "NOPASS")) {
|
||||
SYSLOG ((su_to_root ? LOG_NOTICE : LOG_INFO,
|
||||
"NO password asked for su from '%s' to '%s' (%s)\n",
|
||||
actual_id, wanted_id, SUAUTHFILE));
|
||||
fputs (_("Password authentication bypassed.\n"),stderr);
|
||||
fclose (authfile_fd);
|
||||
return NOPWORD;
|
||||
} else if (!strcmp (action, "OWNPASS")) {
|
||||
SYSLOG ((su_to_root ? LOG_NOTICE : LOG_INFO,
|
||||
"su from '%s' to '%s': asking for user's own password (%s)\n",
|
||||
actual_id, wanted_id, SUAUTHFILE));
|
||||
fputs (_("Please enter your OWN password as authentication.\n"),
|
||||
stderr);
|
||||
fclose (authfile_fd);
|
||||
return OWNPWORD;
|
||||
} else {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s, line %d: unrecognised action!\n",
|
||||
SUAUTHFILE, lines));
|
||||
}
|
||||
}
|
||||
fclose (authfile_fd);
|
||||
return NOACTION;
|
||||
}
|
||||
|
||||
static int applies (const char *single, char *list)
|
||||
{
|
||||
const char split[] = ", ";
|
||||
char *tok;
|
||||
|
||||
int state = 0;
|
||||
|
||||
for (tok = strtok (list, split); tok != NULL;
|
||||
tok = strtok (NULL, split)) {
|
||||
|
||||
if (!strcmp (tok, "ALL")) {
|
||||
if (state != 0) {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s, line %d: ALL in bad place\n",
|
||||
SUAUTHFILE, lines));
|
||||
return 0;
|
||||
}
|
||||
state = 1;
|
||||
} else if (!strcmp (tok, "EXCEPT")) {
|
||||
if (state != 1) {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s, line %d: EXCEPT in bas place\n",
|
||||
SUAUTHFILE, lines));
|
||||
return 0;
|
||||
}
|
||||
state = 2;
|
||||
} else if (!strcmp (tok, "GROUP")) {
|
||||
if ((state != 0) && (state != 2)) {
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s, line %d: GROUP in bad place\n",
|
||||
SUAUTHFILE, lines));
|
||||
return 0;
|
||||
}
|
||||
state = (state == 0) ? 3 : 4;
|
||||
} else {
|
||||
switch (state) {
|
||||
case 0: /* No control words yet */
|
||||
if (!strcmp (tok, single))
|
||||
return 1;
|
||||
break;
|
||||
case 1: /* An all */
|
||||
SYSLOG ((LOG_ERR,
|
||||
"%s, line %d: expect another token after ALL\n",
|
||||
SUAUTHFILE, lines));
|
||||
return 0;
|
||||
case 2: /* All except */
|
||||
if (!strcmp (tok, single))
|
||||
return 0;
|
||||
break;
|
||||
case 3: /* Group */
|
||||
if (isgrp (single, tok))
|
||||
return 1;
|
||||
break;
|
||||
case 4: /* All except group */
|
||||
if (isgrp (single, tok))
|
||||
return 0;
|
||||
/* FALL THRU */
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((state != 0) && (state != 3))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isgrp (const char *name, const char *group)
|
||||
{
|
||||
struct group *grp;
|
||||
|
||||
grp = getgrnam (group); /* local, no need for xgetgrnam */
|
||||
|
||||
if (!grp || !grp->gr_mem)
|
||||
return 0;
|
||||
|
||||
return is_on_list (grp->gr_mem, name);
|
||||
}
|
||||
#endif /* SU_ACCESS */
|
||||
257
src/sulogin.c
Normal file
257
src/sulogin.c
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (c) 1989 - 1994, Julianne Frances Haugh
|
||||
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
||||
* Copyright (c) 2002 - 2006, Tomasz Kłoczko
|
||||
* Copyright (c) 2007 - 2010, 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>
|
||||
|
||||
#ident "$Id: sulogin.c 3521 2011-10-18 20:28:01Z nekral-guest $"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "defines.h"
|
||||
#include "getdef.h"
|
||||
#include "prototypes.h"
|
||||
#include "pwauth.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static char name[BUFSIZ];
|
||||
static char pass[BUFSIZ];
|
||||
|
||||
static struct passwd pwent;
|
||||
|
||||
extern char **newenvp;
|
||||
extern size_t newenvc;
|
||||
|
||||
extern char **environ;
|
||||
|
||||
#ifndef ALARM
|
||||
#define ALARM 60
|
||||
#endif
|
||||
|
||||
/* local function prototypes */
|
||||
static RETSIGTYPE catch_signals (int);
|
||||
|
||||
static RETSIGTYPE catch_signals (unused int sig)
|
||||
{
|
||||
exit (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* syslogd is usually not running at the time when sulogin is typically
|
||||
* called, cluttering the screen with unnecessary messages. Suggested by
|
||||
* Ivan Nejgebauer <ian@unsux.ns.ac.yu>. --marekm
|
||||
*/
|
||||
#undef USE_SYSLOG
|
||||
|
||||
/*ARGSUSED*/ int main (int argc, char **argv)
|
||||
{
|
||||
#ifndef USE_PAM
|
||||
const char *env;
|
||||
#endif /* !USE_PAM */
|
||||
char **envp = environ;
|
||||
TERMIO termio;
|
||||
int err = 0;
|
||||
|
||||
#ifdef USE_TERMIO
|
||||
ioctl (0, TCGETA, &termio);
|
||||
termio.c_iflag |= (ICRNL | IXON);
|
||||
termio.c_oflag |= (OPOST | ONLCR);
|
||||
termio.c_cflag |= (CREAD);
|
||||
termio.c_lflag |= (ISIG | ICANON | ECHO | ECHOE | ECHOK);
|
||||
ioctl (0, TCSETAF, &termio);
|
||||
#endif
|
||||
#ifdef USE_TERMIOS
|
||||
tcgetattr (0, &termio);
|
||||
termio.c_iflag |= (ICRNL | IXON);
|
||||
termio.c_oflag |= (CREAD);
|
||||
termio.c_lflag |= (ECHO | ECHOE | ECHOK | ICANON | ISIG);
|
||||
tcsetattr (0, TCSANOW, &termio);
|
||||
#endif
|
||||
|
||||
Prog = Basename (argv[0]);
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
#ifdef USE_SYSLOG
|
||||
OPENLOG ("sulogin");
|
||||
#endif
|
||||
initenv ();
|
||||
if (argc > 1) {
|
||||
close (0);
|
||||
close (1);
|
||||
close (2);
|
||||
|
||||
if (open (argv[1], O_RDWR) >= 0) {
|
||||
dup (0);
|
||||
dup (0);
|
||||
} else {
|
||||
#ifdef USE_SYSLOG
|
||||
SYSLOG (LOG_WARN, "cannot open %s\n", argv[1]);
|
||||
closelog ();
|
||||
#endif
|
||||
exit (1);
|
||||
}
|
||||
}
|
||||
if (access (PASSWD_FILE, F_OK) == -1) { /* must be a password file! */
|
||||
(void) puts (_("No password file"));
|
||||
#ifdef USE_SYSLOG
|
||||
SYSLOG (LOG_WARN, "No password file\n");
|
||||
closelog ();
|
||||
#endif
|
||||
exit (1);
|
||||
}
|
||||
#if !defined(DEBUG) && defined(SULOGIN_ONLY_INIT)
|
||||
if (getppid () != 1) { /* parent must be INIT */
|
||||
#ifdef USE_SYSLOG
|
||||
SYSLOG (LOG_WARN, "Pid == %d, not 1\n", getppid ());
|
||||
closelog ();
|
||||
#endif
|
||||
exit (1);
|
||||
}
|
||||
#endif
|
||||
if ((isatty (0) == 0) || (isatty (1) == 0) || (isatty (2) == 0)) {
|
||||
#ifdef USE_SYSLOG
|
||||
closelog ();
|
||||
#endif
|
||||
exit (1); /* must be a terminal */
|
||||
}
|
||||
/* If we were init, we need to start a new session */
|
||||
if (getppid() == 1) {
|
||||
setsid();
|
||||
if (ioctl(0, TIOCSCTTY, 1) != 0) {
|
||||
(void) fputs (_("TIOCSCTTY failed"), stderr);
|
||||
}
|
||||
}
|
||||
while (NULL != *envp) { /* add inherited environment, */
|
||||
addenv (*envp, NULL); /* some variables change later */
|
||||
envp++;
|
||||
}
|
||||
|
||||
#ifndef USE_PAM
|
||||
env = getdef_str ("ENV_TZ");
|
||||
if (NULL != env) {
|
||||
addenv (('/' == *env) ? tz (env) : env, NULL);
|
||||
}
|
||||
env = getdef_str ("ENV_HZ");
|
||||
if (NULL != env) {
|
||||
addenv (env, NULL); /* set the default $HZ, if one */
|
||||
}
|
||||
#endif /* !USE_PAM */
|
||||
|
||||
(void) strcpy (name, "root"); /* KLUDGE!!! */
|
||||
|
||||
(void) signal (SIGALRM, catch_signals); /* exit if the timer expires */
|
||||
(void) alarm (ALARM); /* only wait so long ... */
|
||||
|
||||
while (true) { /* repeatedly get login/password pairs */
|
||||
char *cp;
|
||||
pw_entry (name, &pwent); /* get entry from password file */
|
||||
if (pwent.pw_name == (char *) 0) {
|
||||
/*
|
||||
* Fail secure
|
||||
*/
|
||||
(void) puts (_("No password entry for 'root'"));
|
||||
#ifdef USE_SYSLOG
|
||||
SYSLOG (LOG_WARN, "No password entry for 'root'\n");
|
||||
closelog ();
|
||||
#endif
|
||||
exit (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Here we prompt for the root password, or if no password
|
||||
* is given we just exit.
|
||||
*/
|
||||
|
||||
/* get a password for root */
|
||||
cp = getpass (_(
|
||||
"\n"
|
||||
"Type control-d to proceed with normal startup,\n"
|
||||
"(or give root password for system maintenance):"));
|
||||
/*
|
||||
* XXX - can't enter single user mode if root password is
|
||||
* empty. I think this doesn't happen very often :-). But
|
||||
* it will work with standard getpass() (no NULL on EOF).
|
||||
* --marekm
|
||||
*/
|
||||
if ((NULL == cp) || ('\0' == *cp)) {
|
||||
#ifdef USE_SYSLOG
|
||||
SYSLOG (LOG_INFO, "Normal startup\n");
|
||||
closelog ();
|
||||
#endif
|
||||
(void) puts ("");
|
||||
#ifdef TELINIT
|
||||
execl (PATH_TELINIT, "telinit", RUNLEVEL, (char *) 0);
|
||||
#endif
|
||||
exit (0);
|
||||
} else {
|
||||
STRFCPY (pass, cp);
|
||||
strzero (cp);
|
||||
}
|
||||
if (valid (pass, &pwent)) { /* check encrypted passwords ... */
|
||||
break; /* ... encrypted passwords matched */
|
||||
}
|
||||
|
||||
#ifdef USE_SYSLOG
|
||||
SYSLOG (LOG_WARN, "Incorrect root password\n");
|
||||
#endif
|
||||
sleep (2);
|
||||
(void) puts (_("Login incorrect"));
|
||||
}
|
||||
strzero (pass);
|
||||
(void) alarm (0);
|
||||
(void) signal (SIGALRM, SIG_DFL);
|
||||
environ = newenvp; /* make new environment active */
|
||||
|
||||
(void) puts (_("Entering System Maintenance Mode"));
|
||||
#ifdef USE_SYSLOG
|
||||
SYSLOG (LOG_INFO, "System Maintenance Mode\n");
|
||||
#endif
|
||||
|
||||
#ifdef USE_SYSLOG
|
||||
closelog ();
|
||||
#endif
|
||||
/* exec the shell finally. */
|
||||
err = shell (pwent.pw_shell, (char *) 0, environ);
|
||||
|
||||
return ((err == ENOENT) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
|
||||
}
|
||||
|
||||
2081
src/useradd.c
Normal file
2081
src/useradd.c
Normal file
File diff suppressed because it is too large
Load Diff
1152
src/userdel.c
Normal file
1152
src/userdel.c
Normal file
File diff suppressed because it is too large
Load Diff
1965
src/usermod.c
Normal file
1965
src/usermod.c
Normal file
File diff suppressed because it is too large
Load Diff
539
src/vipw.c
Normal file
539
src/vipw.c
Normal file
@@ -0,0 +1,539 @@
|
||||
/*
|
||||
vipw, vigr edit the password or group file
|
||||
with -s will edit shadow or gshadow file
|
||||
|
||||
Copyright (c) 1997 , Guy Maor <maor@ece.utexas.edu>
|
||||
Copyright (c) 1999 - 2000, Marek Michałkiewicz
|
||||
Copyright (c) 2002 - 2006, Tomasz Kłoczko
|
||||
Copyright (c) 2007 - 2011, Nicolas François
|
||||
All rights reserved.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110-1301, USA. */
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#ident "$Id: vipw.c 3654 2011-12-09 21:35:57Z nekral-guest $"
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#ifdef WITH_SELINUX
|
||||
#include <selinux/selinux.h>
|
||||
#endif /* WITH_SELINUX */
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <utime.h>
|
||||
#include "defines.h"
|
||||
#include "groupio.h"
|
||||
#include "nscd.h"
|
||||
#include "prototypes.h"
|
||||
#include "pwio.h"
|
||||
#include "sgroupio.h"
|
||||
#include "shadowio.h"
|
||||
/*@-exitarg@*/
|
||||
#include "exitcodes.h"
|
||||
#ifdef WITH_TCB
|
||||
#include <tcb.h>
|
||||
#include "tcbfuncs.h"
|
||||
#endif /* WITH_TCB */
|
||||
|
||||
#define MSG_WARN_EDIT_OTHER_FILE _( \
|
||||
"You have modified %s.\n"\
|
||||
"You may need to modify %s for consistency.\n"\
|
||||
"Please use the command '%s' to do so.\n")
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
const char *Prog;
|
||||
|
||||
static const char *filename, *fileeditname;
|
||||
static bool filelocked = false;
|
||||
static bool createedit = false;
|
||||
static int (*unlock) (void);
|
||||
static bool quiet = false;
|
||||
#ifdef WITH_TCB
|
||||
static const char *user = NULL;
|
||||
static bool tcb_mode = false;
|
||||
#define SHADOWTCB_SCRATCHDIR ":tmp"
|
||||
#endif /* WITH_TCB */
|
||||
|
||||
/* local function prototypes */
|
||||
static void usage (int status);
|
||||
static int create_backup_file (FILE *, const char *, struct stat *);
|
||||
static void vipwexit (const char *msg, int syserr, int ret);
|
||||
static void vipwedit (const char *, int (*)(void), int (*)(void));
|
||||
|
||||
/*
|
||||
* usage - display usage message and exit
|
||||
*/
|
||||
static void usage (int status)
|
||||
{
|
||||
FILE *usageout = (E_SUCCESS != status) ? stderr : stdout;
|
||||
(void) fprintf (stderr,
|
||||
_("Usage: %s [options]\n"
|
||||
"\n"
|
||||
"Options:\n"),
|
||||
Prog);
|
||||
(void) fputs (_(" -g, --group edit group database\n"), usageout);
|
||||
(void) fputs (_(" -h, --help display this help message and exit\n"), usageout);
|
||||
(void) fputs (_(" -p, --passwd edit passwd database\n"), usageout);
|
||||
(void) fputs (_(" -q, --quiet quiet mode\n"), usageout);
|
||||
(void) fputs (_(" -R, --root CHROOT_DIR directory to chroot into\n"), usageout);
|
||||
(void) fputs (_(" -s, --shadow edit shadow or gshadow database\n"), usageout);
|
||||
#ifdef WITH_TCB
|
||||
(void) fputs (_(" -u, --user which user's tcb shadow file to edit\n"), usageout);
|
||||
#endif /* WITH_TCB */
|
||||
(void) fputs (_("\n"), usageout);
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
static int create_backup_file (FILE * fp, const char *backup, struct stat *sb)
|
||||
{
|
||||
struct utimbuf ub;
|
||||
FILE *bkfp;
|
||||
int c;
|
||||
mode_t mask;
|
||||
|
||||
mask = umask (077);
|
||||
bkfp = fopen (backup, "w");
|
||||
(void) umask (mask);
|
||||
if (NULL == bkfp) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
c = 0;
|
||||
if (fseeko (fp, 0, SEEK_SET) == 0)
|
||||
while ((c = getc (fp)) != EOF) {
|
||||
if (putc (c, bkfp) == EOF) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((EOF != c) || (ferror (fp) != 0) || (fflush (bkfp) != 0)) {
|
||||
fclose (bkfp);
|
||||
unlink (backup);
|
||||
return -1;
|
||||
}
|
||||
if (fsync (fileno (bkfp)) != 0) {
|
||||
(void) fclose (bkfp);
|
||||
unlink (backup);
|
||||
return -1;
|
||||
}
|
||||
if (fclose (bkfp) != 0) {
|
||||
unlink (backup);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ub.actime = sb->st_atime;
|
||||
ub.modtime = sb->st_mtime;
|
||||
if ( (utime (backup, &ub) != 0)
|
||||
|| (chmod (backup, sb->st_mode) != 0)
|
||||
|| (chown (backup, sb->st_uid, sb->st_gid) != 0)) {
|
||||
unlink (backup);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
static void vipwexit (const char *msg, int syserr, int ret)
|
||||
{
|
||||
int err = errno;
|
||||
|
||||
if (createedit) {
|
||||
if (unlink (fileeditname) != 0) {
|
||||
fprintf (stderr, _("%s: failed to remove %s\n"), Prog, fileeditname);
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
if (filelocked) {
|
||||
if ((*unlock) () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, fileeditname);
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname));
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
if (NULL != msg) {
|
||||
fprintf (stderr, "%s: %s", Prog, msg);
|
||||
}
|
||||
if (0 != syserr) {
|
||||
fprintf (stderr, ": %s", strerror (err));
|
||||
}
|
||||
(void) fputs ("\n", stderr);
|
||||
if (!quiet) {
|
||||
fprintf (stdout, _("%s: %s is unchanged\n"), Prog,
|
||||
filename);
|
||||
}
|
||||
exit (ret);
|
||||
}
|
||||
|
||||
#ifndef DEFAULT_EDITOR
|
||||
#define DEFAULT_EDITOR "vi"
|
||||
#endif
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
static void
|
||||
vipwedit (const char *file, int (*file_lock) (void), int (*file_unlock) (void))
|
||||
{
|
||||
const char *editor;
|
||||
pid_t pid;
|
||||
struct stat st1, st2;
|
||||
int status;
|
||||
FILE *f;
|
||||
/* FIXME: the following should have variable sizes */
|
||||
char filebackup[1024], fileedit[1024];
|
||||
char *to_rename;
|
||||
|
||||
snprintf (filebackup, sizeof filebackup, "%s-", file);
|
||||
#ifdef WITH_TCB
|
||||
if (tcb_mode) {
|
||||
if ( (mkdir (TCB_DIR "/" SHADOWTCB_SCRATCHDIR, 0700) != 0)
|
||||
&& (errno != EEXIST)) {
|
||||
vipwexit (_("failed to create scratch directory"), errno, 1);
|
||||
}
|
||||
if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) {
|
||||
vipwexit (_("failed to drop privileges"), errno, 1);
|
||||
}
|
||||
snprintf (fileedit, sizeof fileedit,
|
||||
TCB_DIR "/" SHADOWTCB_SCRATCHDIR "/.vipw.shadow.%s",
|
||||
user);
|
||||
} else {
|
||||
#endif /* WITH_TCB */
|
||||
snprintf (fileedit, sizeof fileedit, "%s.edit", file);
|
||||
#ifdef WITH_TCB
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
unlock = file_unlock;
|
||||
filename = file;
|
||||
fileeditname = fileedit;
|
||||
|
||||
if (access (file, F_OK) != 0) {
|
||||
vipwexit (file, 1, 1);
|
||||
}
|
||||
#ifdef WITH_SELINUX
|
||||
/* if SE Linux is enabled then set the context of all new files
|
||||
to be the context of the file we are editing */
|
||||
if (is_selinux_enabled () != 0) {
|
||||
security_context_t passwd_context=NULL;
|
||||
int ret = 0;
|
||||
if (getfilecon (file, &passwd_context) < 0) {
|
||||
vipwexit (_("Couldn't get file context"), errno, 1);
|
||||
}
|
||||
ret = setfscreatecon (passwd_context);
|
||||
freecon (passwd_context);
|
||||
if (0 != ret) {
|
||||
vipwexit (_("setfscreatecon () failed"), errno, 1);
|
||||
}
|
||||
}
|
||||
#endif /* WITH_SELINUX */
|
||||
#ifdef WITH_TCB
|
||||
if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE)) {
|
||||
vipwexit (_("failed to gain privileges"), errno, 1);
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
if (file_lock () == 0) {
|
||||
vipwexit (_("Couldn't lock file"), errno, 5);
|
||||
}
|
||||
filelocked = true;
|
||||
#ifdef WITH_TCB
|
||||
if (tcb_mode && (shadowtcb_drop_priv () == SHADOWTCB_FAILURE)) {
|
||||
vipwexit (_("failed to drop privileges"), errno, 1);
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
|
||||
/* edited copy has same owners, perm */
|
||||
if (stat (file, &st1) != 0) {
|
||||
vipwexit (file, 1, 1);
|
||||
}
|
||||
f = fopen (file, "r");
|
||||
if (NULL == f) {
|
||||
vipwexit (file, 1, 1);
|
||||
}
|
||||
#ifdef WITH_TCB
|
||||
if (tcb_mode && (shadowtcb_gain_priv () == SHADOWTCB_FAILURE))
|
||||
vipwexit (_("failed to gain privileges"), errno, 1);
|
||||
#endif /* WITH_TCB */
|
||||
if (create_backup_file (f, fileedit, &st1) != 0) {
|
||||
vipwexit (_("Couldn't make backup"), errno, 1);
|
||||
}
|
||||
(void) fclose (f);
|
||||
createedit = true;
|
||||
|
||||
editor = getenv ("VISUAL");
|
||||
if (NULL == editor) {
|
||||
editor = getenv ("EDITOR");
|
||||
}
|
||||
if (NULL == editor) {
|
||||
editor = DEFAULT_EDITOR;
|
||||
}
|
||||
|
||||
pid = fork ();
|
||||
if (-1 == pid) {
|
||||
vipwexit ("fork", 1, 1);
|
||||
} else if (0 == pid) {
|
||||
/* use the system() call to invoke the editor so that it accepts
|
||||
command line args in the EDITOR and VISUAL environment vars */
|
||||
char *buf;
|
||||
|
||||
buf = (char *) malloc (strlen (editor) + strlen (fileedit) + 2);
|
||||
snprintf (buf, strlen (editor) + strlen (fileedit) + 2,
|
||||
"%s %s", editor, fileedit);
|
||||
if (system (buf) != 0) {
|
||||
fprintf (stderr, "%s: %s: %s\n", Prog, editor,
|
||||
strerror (errno));
|
||||
exit (1);
|
||||
} else {
|
||||
exit (0);
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
pid = waitpid (pid, &status, WUNTRACED);
|
||||
if ((pid != -1) && (WIFSTOPPED (status) != 0)) {
|
||||
/* The child (editor) was suspended.
|
||||
* Suspend vipw. */
|
||||
kill (getpid (), SIGSTOP);
|
||||
/* wake child when resumed */
|
||||
kill (pid, SIGCONT);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (-1 == pid)
|
||||
|| (WIFEXITED (status) == 0)
|
||||
|| (WEXITSTATUS (status) != 0)) {
|
||||
vipwexit (editor, 1, 1);
|
||||
}
|
||||
|
||||
if (stat (fileedit, &st2) != 0) {
|
||||
vipwexit (fileedit, 1, 1);
|
||||
}
|
||||
if (st1.st_mtime == st2.st_mtime) {
|
||||
vipwexit (0, 0, 0);
|
||||
}
|
||||
#ifdef WITH_SELINUX
|
||||
/* unset the fscreatecon */
|
||||
if (is_selinux_enabled () != 0) {
|
||||
if (setfscreatecon (NULL) != 0) {
|
||||
vipwexit (_("setfscreatecon () failed"), errno, 1);
|
||||
}
|
||||
}
|
||||
#endif /* WITH_SELINUX */
|
||||
|
||||
/*
|
||||
* XXX - here we should check fileedit for errors; if there are any,
|
||||
* ask the user what to do (edit again, save changes anyway, or quit
|
||||
* without saving). Use pwck or grpck to do the check. --marekm
|
||||
*/
|
||||
createedit = false;
|
||||
#ifdef WITH_TCB
|
||||
if (tcb_mode) {
|
||||
f = fopen (fileedit, "r");
|
||||
if (NULL == f) {
|
||||
vipwexit (_("failed to open scratch file"), errno, 1);
|
||||
}
|
||||
if (unlink (fileedit) != 0) {
|
||||
vipwexit (_("failed to unlink scratch file"), errno, 1);
|
||||
}
|
||||
if (shadowtcb_drop_priv () == SHADOWTCB_FAILURE) {
|
||||
vipwexit (_("failed to drop privileges"), errno, 1);
|
||||
}
|
||||
if (stat (file, &st1) != 0) {
|
||||
vipwexit (_("failed to stat edited file"), errno, 1);
|
||||
}
|
||||
to_rename = malloc (strlen (file) + 2);
|
||||
if (NULL == to_rename) {
|
||||
vipwexit (_("failed to allocate memory"), errno, 1);
|
||||
}
|
||||
snprintf (to_rename, strlen (file) + 2, "%s+", file);
|
||||
if (create_backup_file (f, to_rename, &st1) != 0) {
|
||||
free (to_rename);
|
||||
vipwexit (_("failed to create backup file"), errno, 1);
|
||||
}
|
||||
(void) fclose (f);
|
||||
} else {
|
||||
#endif /* WITH_TCB */
|
||||
to_rename = fileedit;
|
||||
#ifdef WITH_TCB
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
unlink (filebackup);
|
||||
link (file, filebackup);
|
||||
if (rename (to_rename, file) == -1) {
|
||||
fprintf (stderr,
|
||||
_("%s: can't restore %s: %s (your changes are in %s)\n"),
|
||||
Prog, file, strerror (errno), to_rename);
|
||||
#ifdef WITH_TCB
|
||||
if (tcb_mode) {
|
||||
free (to_rename);
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
vipwexit (0, 0, 1);
|
||||
}
|
||||
|
||||
#ifdef WITH_TCB
|
||||
if (tcb_mode) {
|
||||
free (to_rename);
|
||||
if (shadowtcb_gain_priv () == SHADOWTCB_FAILURE) {
|
||||
vipwexit (_("failed to gain privileges"), errno, 1);
|
||||
}
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
|
||||
if ((*file_unlock) () == 0) {
|
||||
fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, fileeditname);
|
||||
SYSLOG ((LOG_ERR, "failed to unlock %s", fileeditname));
|
||||
/* continue */
|
||||
}
|
||||
SYSLOG ((LOG_INFO, "file %s edited", fileeditname));
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
bool editshadow = false;
|
||||
bool do_vipw;
|
||||
|
||||
Prog = Basename (argv[0]);
|
||||
|
||||
(void) setlocale (LC_ALL, "");
|
||||
(void) bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
(void) textdomain (PACKAGE);
|
||||
|
||||
process_root_flag ("-R", argc, argv);
|
||||
|
||||
do_vipw = (strcmp (Prog, "vigr") != 0);
|
||||
|
||||
OPENLOG (do_vipw ? "vipw" : "vigr");
|
||||
|
||||
{
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
int c;
|
||||
static struct option long_options[] = {
|
||||
{"group", no_argument, NULL, 'g'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"passwd", no_argument, NULL, 'p'},
|
||||
{"quiet", no_argument, NULL, 'q'},
|
||||
{"root", required_argument, NULL, 'R'},
|
||||
{"shadow", no_argument, NULL, 's'},
|
||||
#ifdef WITH_TCB
|
||||
{"user", required_argument, NULL, 'u'},
|
||||
#endif /* WITH_TCB */
|
||||
{NULL, 0, NULL, '\0'}
|
||||
};
|
||||
while ((c = getopt_long (argc, argv,
|
||||
#ifdef WITH_TCB
|
||||
"ghpqR:su:",
|
||||
#else /* !WITH_TCB */
|
||||
"ghpqR:s",
|
||||
#endif /* !WITH_TCB */
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'g':
|
||||
do_vipw = false;
|
||||
break;
|
||||
case 'h':
|
||||
usage (E_SUCCESS);
|
||||
break;
|
||||
case 'p':
|
||||
do_vipw = true;
|
||||
break;
|
||||
case 'q':
|
||||
quiet = true;
|
||||
break;
|
||||
case 'R': /* no-op, handled in process_root_flag () */
|
||||
break;
|
||||
case 's':
|
||||
editshadow = true;
|
||||
break;
|
||||
#ifdef WITH_TCB
|
||||
case 'u':
|
||||
user = optarg;
|
||||
break;
|
||||
#endif /* WITH_TCB */
|
||||
default:
|
||||
usage (E_USAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (do_vipw) {
|
||||
if (editshadow) {
|
||||
#ifdef WITH_TCB
|
||||
if (getdef_bool ("USE_TCB") && (NULL != user)) {
|
||||
if (shadowtcb_set_user (user) == SHADOWTCB_FAILURE) {
|
||||
fprintf (stderr,
|
||||
_("%s: failed to find tcb directory for %s\n"),
|
||||
Prog, user);
|
||||
return E_SHADOW_NOTFOUND;
|
||||
}
|
||||
tcb_mode = true;
|
||||
}
|
||||
#endif /* WITH_TCB */
|
||||
vipwedit (spw_dbname (), spw_lock, spw_unlock);
|
||||
printf (MSG_WARN_EDIT_OTHER_FILE,
|
||||
spw_dbname (),
|
||||
pw_dbname (),
|
||||
"vipw");
|
||||
} else {
|
||||
vipwedit (pw_dbname (), pw_lock, pw_unlock);
|
||||
if (spw_file_present ()) {
|
||||
printf (MSG_WARN_EDIT_OTHER_FILE,
|
||||
pw_dbname (),
|
||||
spw_dbname (),
|
||||
"vipw -s");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#ifdef SHADOWGRP
|
||||
if (editshadow) {
|
||||
vipwedit (sgr_dbname (), sgr_lock, sgr_unlock);
|
||||
printf (MSG_WARN_EDIT_OTHER_FILE,
|
||||
sgr_dbname (),
|
||||
gr_dbname (),
|
||||
"vigr");
|
||||
} else {
|
||||
#endif /* SHADOWGRP */
|
||||
vipwedit (gr_dbname (), gr_lock, gr_unlock);
|
||||
#ifdef SHADOWGRP
|
||||
if (sgr_file_present ()) {
|
||||
printf (MSG_WARN_EDIT_OTHER_FILE,
|
||||
gr_dbname (),
|
||||
sgr_dbname (),
|
||||
"vigr -s");
|
||||
}
|
||||
}
|
||||
#endif /* SHADOWGRP */
|
||||
}
|
||||
|
||||
nscd_flush_cache ("passwd");
|
||||
nscd_flush_cache ("group");
|
||||
|
||||
return E_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user