passwd_pro

前言

要在passwd源码上增加点功能, 先加了一个口令中不能包含用户名,增加了安全性.

试验目的

工程可以改,可以找到修改点。试验环境都留着,以后还可以接着玩。
找修改点时,先用SI建立工程,大致看一下. 然后用gdb -tui –args passwd单步调试,跟着流程走一下,就能想到在哪改合适. 如果确定了修改点,在修改点上下断点,直接跑到修改点,单步调试,细细品一下,就可以动手修改,测试了。

试验环境

fedora22

原始工程版本

未找到fedora22配套的passwd源码,用的是LFS中带的高版本shadow-4.5.tar.xz

工程的编译

shadow-4.5.tar.xz

xz -d shadow-4.5.tar.xz
tar -xvf ./shadow-4.5.tar

cd ./shadow-4.5
./configure
make all
cd src
make passwd

/src/Makefile的修改

./configure之后, 用的是-O2选项,有优化. 为了调试,要将-O2去掉.

# CFLAGS = -g -O2
CFLAGS = -Wall -Werror -g

修改点

\shadow-4.5\src\passwd.c::new_password()

修改后的工程下载点

passwd_pro_2018_0607_2126.7z

修改后的工程预览

passwd.c

/*
 * 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$"

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <syslog.h>

#ifdef WITH_SELINUX
#include <selinux/selinux.h>
#include <selinux/flask.h>
#include <selinux/av_permissions.h>
#include <selinux/context.h>
#endif              /* WITH_SELINUX */

#include <time.h>
#include "defines.h"
#include "getdef.h"
#include "nscd.h"
#include "prototypes.h"
#include "pwauth.h"
#include "pwio.h"
#include "shadowio.h"

// 日志级别 - 调试
#ifndef MYLOG_D

#define MYLOG_D(fmt, ...) \
do { \
    syslog(LOG_INFO, "[%s : %s.%d : %s()] : " fmt, "MY_DEBUG", __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \
} while (0);

#endif // #ifndef MYLOG_D

/*
 * exit status values
 */
/*@-exitarg@*/
#define E_SUCCESS   0   /* success */
#define E_NOPERM    1   /* permission denied */
#define E_USAGE     2   /* invalid combination of options */
#define E_FAILURE   3   /* unexpected failure, nothing done */
#define E_MISSING   4   /* unexpected failure, passwd file missing */
#define E_PWDBUSY   5   /* passwd file busy, try again later */
#define E_BAD_ARG   6   /* invalid argument to option */
/*
 * Global variables
 */
const char *Prog;       /* Program name */

static char *name;      /* The name of user whose password is being changed */
static char *myname;        /* The current user's name */
static bool amroot;     /* The caller's real UID was 0 */

static bool
    aflg = false,           /* -a - show status for all users */
    dflg = false,           /* -d - delete password */
    eflg = false,           /* -e - force password change */
    iflg = false,           /* -i - set inactive days */
    kflg = false,           /* -k - change only if expired */
    lflg = false,           /* -l - lock the user's password */
    nflg = false,           /* -n - set minimum days */
    qflg = false,           /* -q - quiet mode */
    Sflg = false,           /* -S - show password status */
    uflg = false,           /* -u - unlock the user's password */
    wflg = false,           /* -w - set warning days */
    xflg = false;           /* -x - set maximum days */

/*
 * set to 1 if there are any flags which require root privileges,
 * and require username to be specified
 */
static bool anyflag = false;

static long age_min = 0;    /* Minimum days before change   */
static long age_max = 0;    /* Maximum days until change     */
static long warn = 0;       /* Warning days before change   */
static long inact = 0;      /* Days without change before locked */

#ifndef USE_PAM
static bool do_update_age = false;
#endif              /* ! USE_PAM */

static bool pw_locked = false;
static bool spw_locked = false;

#ifndef USE_PAM
/*
 * Size of the biggest passwd:
 *   $6$  3
 *   rounds=    7
 *   999999999  9
 *   $     1
 *   salt   16
 *   $     1
 *   SHA512 123
 *   nul    1
 *
 *   total  161
 */
static char crypt_passwd[256];
static bool do_update_pwd = false;
#endif              /* !USE_PAM */

/*
 * External identifiers
 */

/* local function prototypes */
static /*@noreturn@*/void usage (int);

#ifndef USE_PAM
static bool reuse (const char *, const struct passwd *);
static int new_password (const struct passwd *);

static void check_password (const struct passwd *, const struct spwd *);
#endif              /* !USE_PAM */
static /*@observer@*/const char *date_to_str (time_t);
static /*@observer@*/const char *pw_status (const char *);
static void print_status (const struct passwd *);
static /*@noreturn@*/void fail_exit (int);
static /*@noreturn@*/void oom (void);
static char *update_crypt_pw (char *);
static void update_noshadow (void);

static void update_shadow (void);
#ifdef WITH_SELINUX
static int check_selinux_access (const char *changed_user,
                                 uid_t changed_uid,
                                 access_vector_t requested_access);
#endif              /* WITH_SELINUX */

static void myfn_show_debug_info();
static bool is_pwd_include_user_name(const char* user_name, const char* pwd);

/*
 * usage - print command usage 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 (_("  -a, --all                     report password status on all accounts\n"), usageout);
    (void) fputs (_("  -d, --delete                  delete the password for the named account\n"), usageout);
    (void) fputs (_("  -e, --expire                  force expire the password for the named account\n"), usageout);
    (void) fputs (_("  -h, --help                    display this help message and exit\n"), usageout);
    (void) fputs (_("  -k, --keep-tokens             change password only if expired\n"), usageout);
    (void) fputs (_("  -i, --inactive INACTIVE       set password inactive after expiration\n"
                    "                                to INACTIVE\n"), usageout);
    (void) fputs (_("  -l, --lock                    lock the password of the named account\n"), usageout);
    (void) fputs (_("  -n, --mindays MIN_DAYS        set minimum number of days before password\n"
                    "                                change to MIN_DAYS\n"), usageout);
    (void) fputs (_("  -q, --quiet                   quiet mode\n"), usageout);
    (void) fputs (_("  -r, --repository REPOSITORY   change password in REPOSITORY repository\n"), usageout);
    (void) fputs (_("  -R, --root CHROOT_DIR         directory to chroot into\n"), usageout);
    (void) fputs (_("  -S, --status                  report password status on the named account\n"), usageout);
    (void) fputs (_("  -u, --unlock                  unlock the password of the named account\n"), usageout);
    (void) fputs (_("  -w, --warndays WARN_DAYS      set expiration warning days to WARN_DAYS\n"), usageout);
    (void) fputs (_("  -x, --maxdays MAX_DAYS        set maximum number of days before password\n"
                    "                                change to MAX_DAYS\n"), usageout);
    (void) fputs ("\n", usageout);
    exit (status);
}

#ifndef USE_PAM
static bool reuse (const char *pass, const struct passwd *pw)
{
#ifdef HAVE_LIBCRACK_HIST
    const char *reason;

#ifdef HAVE_LIBCRACK_PW
    const char *FascistHistoryPw (const char *, const struct passwd *);

    reason = FascistHistory (pass, pw);
#else               /* !HAVE_LIBCRACK_PW */
    const char *FascistHistory (const char *, int);

    reason = FascistHistory (pass, pw->pw_uid);
#endif              /* !HAVE_LIBCRACK_PW */
    if (NULL != reason) {
        (void) printf (_("Bad password: %s.  "), reason);
        return true;
    }
#endif              /* HAVE_LIBCRACK_HIST */
    return false;
}

/*
 * new_password - validate old password and replace with new (both old and
 * new in global "char crypt_passwd[128]")
 */
static int new_password (const struct passwd *pw)
{
    char *clear;        /* Pointer to clear text */
    char *cipher;       /* Pointer to cipher text */
    const char *salt;   /* Pointer to new salt */
    char *cp;       /* Pointer to getpass() response */
    char orig[200];     /* Original password */
    char pass[200];     /* New password */
    int i;          /* Counter for retries */
    bool warned;
    int pass_max_len = -1;
    const char *method;

#ifdef HAVE_LIBCRACK_HIST
    int HistUpdate (const char *, const char *);
#endif              /* HAVE_LIBCRACK_HIST */

    /*
     * Authenticate the user. The user will be prompted for their own
     * password.
     */

    if (!amroot && ('\0' != crypt_passwd[0])) {
        clear = getpass (_("Old password: "));
        if (NULL == clear) {
            return -1;
        }

        cipher = pw_encrypt (clear, crypt_passwd);

        if (NULL == cipher) {
            strzero (clear);
            fprintf (stderr,
                     _("%s: failed to crypt password with previous salt: %s\n"),
                     Prog, strerror (errno));
            SYSLOG ((LOG_INFO,
                     "Failed to crypt password with previous salt of user '%s'",
                     pw->pw_name));
            return -1;
        }

        if (strcmp (cipher, crypt_passwd) != 0) {
            strzero (clear);
            strzero (cipher);
            SYSLOG ((LOG_WARN, "incorrect password for %s",
                     pw->pw_name));
            (void) sleep (1);
            (void) fprintf (stderr,
                            _("Incorrect password for %s.\n"),
                            pw->pw_name);
            return -1;
        }
        STRFCPY (orig, clear);
        strzero (clear);
        strzero (cipher);
    } else {
        orig[0] = '\0';
    }

    /*
     * Get the new password. The user is prompted for the new password
     * and has five tries to get it right. The password will be tested
     * for strength, unless it is the root user. This provides an escape
     * for initial login passwords.
     */
    method = getdef_str ("ENCRYPT_METHOD");
    if (NULL == method) {
        if (!getdef_bool ("MD5_CRYPT_ENAB")) {
            pass_max_len = getdef_num ("PASS_MAX_LEN", 8);
        }
    } else {
        if (   (strcmp (method, "MD5")    == 0)
#ifdef USE_SHA_CRYPT
            || (strcmp (method, "SHA256") == 0)
            || (strcmp (method, "SHA512") == 0)
#endif              /* USE_SHA_CRYPT */
            ) {
            pass_max_len = -1;
        } else {
            pass_max_len = getdef_num ("PASS_MAX_LEN", 8);
        }
    }
    if (!qflg) {
        if (pass_max_len == -1) {
            (void) printf (_(
"Enter the new password (minimum of %d characters)\n"
"Please use a combination of upper and lower case letters and numbers.\n"),
                getdef_num ("PASS_MIN_LEN", 5));
        } else {
            (void) printf (_(
"Enter the new password (minimum of %d, maximum of %d characters)\n"
"Please use a combination of upper and lower case letters and numbers.\n"),
                getdef_num ("PASS_MIN_LEN", 5), pass_max_len);
        }
    }

    warned = false;
    for (i = getdef_num ("PASS_CHANGE_TRIES", 5); i > 0; i--) {
        // 输入新口令
        cp = getpass (_("New password: "));
        if (NULL == cp) {
            memzero (orig, sizeof orig);
            return -1;
        }
        if (warned && (strcmp (pass, cp) != 0)) {
            warned = false;
        }
        STRFCPY (pass, cp);
        strzero (cp);

        if (!amroot && (!obscure (orig, pass, pw) || reuse (pass, pw))) {
            (void) puts (_("Try again."));
            continue;
        }

        /*
         * If enabled, warn about weak passwords even if you are
         * root (enter this password again to use it anyway). 
         * --marekm
         */
        if (amroot && !warned && getdef_bool ("PASS_ALWAYS_WARN")
            && (!obscure (orig, pass, pw) || reuse (pass, pw))) {
            (void) puts (_("\nWarning: weak password (enter it again to use it anyway)."));
            warned = true;
            continue;
        }

        // 判断口令中不能包含用户名
        if (is_pwd_include_user_name((NULL != pw) ? pw->pw_name : NULL, pass)) {
            (void) puts (_("Try again : password can't include user name"));
            continue;
        }

        // 输入新口令的校验口令
        cp = getpass (_("Re-enter new password: "));
        if (NULL == cp) {
            memzero (orig, sizeof orig);
            return -1;
        }
        if (strcmp (cp, pass) != 0) {
            (void) fputs (_("They don't match; try again.\n"), stderr);
        } else {
            strzero (cp);
            break;
        }
    }
    memzero (orig, sizeof orig);

    if (i == 0) {
        // 重试次数到了, 输入的pwd还不符合要求, 返回了
        memzero (pass, sizeof pass);
        return -1;
    }

    /*
     * Encrypt the password, then wipe the cleartext password.
     */
    salt = crypt_make_salt (NULL, NULL);
    cp = pw_encrypt (pass, salt);
    memzero (pass, sizeof pass);

    if (NULL == cp) {
        fprintf (stderr,
                 _("%s: failed to crypt password with salt '%s': %s\n"),
                 Prog, salt, strerror (errno));
        return -1;
    }

#ifdef HAVE_LIBCRACK_HIST
    HistUpdate (pw->pw_name, crypt_passwd);
#endif              /* HAVE_LIBCRACK_HIST */
    STRFCPY (crypt_passwd, cp);
    return 0;
}

/*
 * check_password - test a password to see if it can be changed
 *
 *  check_password() sees if the invoker has permission to change the
 *  password for the given user.
 */
static void check_password (const struct passwd *pw, const struct spwd *sp)
{
    time_t now;
    int exp_status;

    exp_status = isexpired (pw, sp);

    /*
     * If not expired and the "change only if expired" option (idea from
     * PAM) was specified, do nothing. --marekm
     */
    if (kflg && (0 == exp_status)) {
        exit (E_SUCCESS);
    }

    /*
     * Root can change any password any time.
     */
    if (amroot) {
        return;
    }

    (void) time (&now);

    /*
     * Expired accounts cannot be changed ever. Passwords which are
     * locked may not be changed. Passwords where min > max may not be
     * changed. Passwords which have been inactive too long cannot be
     * changed.
     */
    if (   (sp->sp_pwdp[0] == '!')
        || (exp_status > 1)
        || (   (sp->sp_max >= 0)
            && (sp->sp_min > sp->sp_max))) {
        (void) fprintf (stderr,
                        _("The password for %s cannot be changed.\n"),
                        sp->sp_namp);
        SYSLOG ((LOG_WARN, "password locked for '%s'", sp->sp_namp));
        closelog ();
        exit (E_NOPERM);
    }

    /*
     * Passwords may only be changed after sp_min time is up.
     */
    if (sp->sp_lstchg > 0) {
        time_t ok;
        ok = (time_t) sp->sp_lstchg * SCALE;
        if (sp->sp_min > 0) {
            ok += (time_t) sp->sp_min * SCALE;
        }

        if (now < ok) {
            (void) fprintf (stderr,
                            _("The password for %s cannot be changed yet.\n"),
                            pw->pw_name);
            SYSLOG ((LOG_WARN, "now < minimum age for '%s'", pw->pw_name));
            closelog ();
            exit (E_NOPERM);
        }
    }
}
#endif              /* !USE_PAM */

static /*@observer@*/const char *date_to_str (time_t t)
{
    static char buf[80];
    struct tm *tm;

    tm = gmtime (&t);
#ifdef HAVE_STRFTIME
    (void) strftime (buf, sizeof buf, "%m/%d/%Y", tm);
#else               /* !HAVE_STRFTIME */
    (void) snprintf (buf, sizeof buf, "%02d/%02d/%04d",
                     tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900);
#endif              /* !HAVE_STRFTIME */
    return buf;
}

static /*@observer@*/const char *pw_status (const char *pass)
{
    if (*pass == '*' || *pass == '!') {
        return "L";
    }
    if (*pass == '\0') {
        return "NP";
    }
    return "P";
}

/*
 * print_status - print current password status
 */
static void print_status (const struct passwd *pw)
{
    struct spwd *sp;

    sp = getspnam (pw->pw_name); /* local, no need for xgetspnam */
    if (NULL != sp) {
        (void) printf ("%s %s %s %lld %lld %lld %lld\n",
                       pw->pw_name,
                       pw_status (sp->sp_pwdp),
                       date_to_str (sp->sp_lstchg * SCALE),
                       ((long long)sp->sp_min * SCALE) / DAY,
                       ((long long)sp->sp_max * SCALE) / DAY,
                       ((long long)sp->sp_warn * SCALE) / DAY,
                       ((long long)sp->sp_inact * SCALE) / DAY);
    } else {
        (void) printf ("%s %s\n",
                       pw->pw_name, pw_status (pw->pw_passwd));
    }
}


static /*@noreturn@*/void fail_exit (int status)
{
    if (pw_locked) {
        if (pw_unlock () == 0) {
            (void) 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) {
            (void) fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
            SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
            /* continue */
        }
    }

    exit (status);
}

static /*@noreturn@*/void oom (void)
{
    (void) fprintf (stderr, _("%s: out of memory\n"), Prog);
    fail_exit (E_FAILURE);
}

static char *update_crypt_pw (char *cp)
{
#ifndef USE_PAM
    if (do_update_pwd) {
        cp = xstrdup (crypt_passwd);
    }
#endif              /* !USE_PAM */

    if (dflg) {
        *cp = '\0';
    }

    if (uflg && *cp == '!') {
        if (cp[1] == '\0') {
            (void) fprintf (stderr,
                            _("%s: unlocking the password would result in a passwordless account.\n"
                              "You should set a password with usermod -p to unlock the password of this account.\n"),
                            Prog);
            fail_exit (E_FAILURE);
        } else {
            cp++;
        }
    }

    if (lflg && *cp != '!') {
        char *newpw = xmalloc (strlen (cp) + 2);

        strcpy (newpw, "!");
        strcat (newpw, cp);
        cp = newpw;
    }
    return cp;
}


static void update_noshadow (void)
{
    const struct passwd *pw;
    struct passwd *npw;

    if (pw_lock () == 0) {
        (void) fprintf (stderr,
                        _("%s: cannot lock %s; try again later.\n"),
                        Prog, pw_dbname ());
        exit (E_PWDBUSY);
    }
    pw_locked = true;
    if (pw_open (O_CREAT | O_RDWR) == 0) {
        (void) fprintf (stderr,
                        _("%s: cannot open %s\n"),
                        Prog, pw_dbname ());
        SYSLOG ((LOG_WARN, "cannot open %s", pw_dbname ()));
        fail_exit (E_MISSING);
    }
    pw = pw_locate (name);
    if (NULL == pw) {
        (void) fprintf (stderr,
                        _("%s: user '%s' does not exist in %s\n"),
                        Prog, name, pw_dbname ());
        fail_exit (E_NOPERM);
    }
    npw = __pw_dup (pw);
    if (NULL == npw) {
        oom ();
    }
    npw->pw_passwd = update_crypt_pw (npw->pw_passwd);
    if (pw_update (npw) == 0) {
        (void) fprintf (stderr,
                        _("%s: failed to prepare the new %s entry '%s'\n"),
                        Prog, pw_dbname (), npw->pw_name);
        fail_exit (E_FAILURE);
    }
    if (pw_close () == 0) {
        (void) 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);
    }
    if (pw_unlock () == 0) {
        (void) fprintf (stderr,
                        _("%s: failed to unlock %s\n"),
                        Prog, pw_dbname ());
        SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
        /* continue */
    }
    pw_locked = false;
}

static void update_shadow (void)
{
    const struct spwd *sp;
    struct spwd *nsp;

    if (spw_lock () == 0) {
        (void) fprintf (stderr,
                        _("%s: cannot lock %s; try again later.\n"),
                        Prog, spw_dbname ());
        exit (E_PWDBUSY);
    }
    spw_locked = true;
    if (spw_open (O_CREAT | O_RDWR) == 0) {
        (void) fprintf (stderr,
                        _("%s: cannot open %s\n"),
                        Prog, spw_dbname ());
        SYSLOG ((LOG_WARN, "cannot open %s", spw_dbname ()));
        fail_exit (E_FAILURE);
    }
    sp = spw_locate (name);
    if (NULL == sp) {
        /* Try to update the password in /etc/passwd instead. */
        (void) spw_close ();
        update_noshadow ();
        if (spw_unlock () == 0) {
            (void) fprintf (stderr,
                            _("%s: failed to unlock %s\n"),
                            Prog, spw_dbname ());
            SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
            /* continue */
        }
        spw_locked = false;
        return;
    }
    nsp = __spw_dup (sp);
    if (NULL == nsp) {
        oom ();
    }
    nsp->sp_pwdp = update_crypt_pw (nsp->sp_pwdp);
    if (xflg) {
        nsp->sp_max = (age_max * DAY) / SCALE;
    }
    if (nflg) {
        nsp->sp_min = (age_min * DAY) / SCALE;
    }
    if (wflg) {
        nsp->sp_warn = (warn * DAY) / SCALE;
    }
    if (iflg) {
        nsp->sp_inact = (inact * DAY) / SCALE;
    }
#ifndef USE_PAM
    if (do_update_age) {
        nsp->sp_lstchg = (long) gettime () / SCALE;
        if (0 == nsp->sp_lstchg) {
            /* Better disable aging than requiring a password
             * change */
            nsp->sp_lstchg = -1;
        }
    }
#endif              /* !USE_PAM */

    /*
     * Force change on next login, like SunOS 4.x passwd -e or Solaris
     * 2.x passwd -f. Solaris 2.x seems to do the same thing (set
     * sp_lstchg to 0).
     */
    if (eflg) {
        nsp->sp_lstchg = 0;
    }

    if (spw_update (nsp) == 0) {
        (void) fprintf (stderr,
                        _("%s: failed to prepare the new %s entry '%s'\n"),
                        Prog, spw_dbname (), nsp->sp_namp);
        fail_exit (E_FAILURE);
    }
    if (spw_close () == 0) {
        (void) 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 (spw_unlock () == 0) {
        (void) fprintf (stderr,
                        _("%s: failed to unlock %s\n"),
                        Prog, spw_dbname ());
        SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
        /* continue */
    }
    spw_locked = false;
}

#ifdef WITH_SELINUX
static int check_selinux_access (const char *changed_user,
                                 uid_t changed_uid,
                                 access_vector_t requested_access)
{
    int status = -1;
    security_context_t user_context;
    context_t c;
    const char *user;

    /* if in permissive mode then allow the operation */
    if (security_getenforce() == 0) {
        return 0;
    }

    /* get the context of the process which executed passwd */
    if (getprevcon(&user_context) != 0) {
        return -1;
    }

    /* get the "user" portion of the context (the part before the first
       colon) */
    c = context_new(user_context);
    user = context_user_get(c);

    /* if changing a password for an account with UID==0 or for an account
       where the identity matches then return success */
    if (changed_uid != 0 && strcmp(changed_user, user) == 0) {
        status = 0;
    } else {
        struct av_decision avd;
        int retval;
        retval = security_compute_av(user_context,
                                     user_context,
                                     SECCLASS_PASSWD,
                                     requested_access,
                                     &avd);
        if ((retval == 0) &&
            ((requested_access & avd.allowed) == requested_access)) {
            status = 0;
        }
    }
    context_free(c);
    freecon(user_context);
    return status;
}

#endif              /* WITH_SELINUX */

/*
 * passwd - change a user's password file information
 *
 *  This command controls the password file and commands which are used
 *  to modify it.
 *
 *  The valid options are
 *
 *  -d  delete the password for the named account (*)
 *  -e  expire the password for the named account (*)
 *  -f  execute chfn command to interpret flags
 *  -g  execute gpasswd command to interpret flags
 *  -i #    set sp_inact to # days (*)
 *  -k  change password only if expired
 *  -l  lock the password of the named account (*)
 *  -n #    set sp_min to # days (*)
 *  -r #    change password in # repository
 *  -s  execute chsh command to interpret flags
 *  -S  show password status of named account
 *  -u  unlock the password of the named account (*)
 *  -w #    set sp_warn to # days (*)
 *  -x #    set sp_max to # days (*)
 *
 *  (*) requires root permission to execute.
 *
 *  All of the time fields are entered in days and converted to the
 *  appropriate internal format. For finer resolute the chage
 *  command must be used.
 */
int main (int argc, char **argv)
{
    const struct passwd *pw;    /* Password file entry for user      */

#ifndef USE_PAM
    char *cp;       /* Miscellaneous character pointing  */

    const struct spwd *sp;  /* Shadow file entry for user   */
#endif              /* !USE_PAM */

    sanitize_env ();

    myfn_show_debug_info();

    /*
     * 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);

    /*
     * The program behaves differently when executed by root than when
     * executed by a normal user.
     */
    amroot = (getuid () == 0);

    OPENLOG ("passwd");

    {
        /*
         * Parse the command line options.
         */
        int c;
        static struct option long_options[] = {
            {"all",         no_argument,       NULL, 'a'},
            {"delete",      no_argument,       NULL, 'd'},
            {"expire",      no_argument,       NULL, 'e'},
            {"help",        no_argument,       NULL, 'h'},
            {"inactive",    required_argument, NULL, 'i'},
            {"keep-tokens", no_argument,       NULL, 'k'},
            {"lock",        no_argument,       NULL, 'l'},
            {"mindays",     required_argument, NULL, 'n'},
            {"quiet",       no_argument,       NULL, 'q'},
            {"repository",  required_argument, NULL, 'r'},
            {"root",        required_argument, NULL, 'R'},
            {"status",      no_argument,       NULL, 'S'},
            {"unlock",      no_argument,       NULL, 'u'},
            {"warndays",    required_argument, NULL, 'w'},
            {"maxdays",     required_argument, NULL, 'x'},
            {NULL, 0, NULL, '\0'}
        };

        while ((c = getopt_long (argc, argv, "adehi:kln:qr:R:Suw:x:",
                                 long_options, NULL)) != -1) {
            switch (c) {
            case 'a':
                aflg = true;
                break;
            case 'd':
                dflg = true;
                anyflag = true;
                break;
            case 'e':
                eflg = true;
                anyflag = true;
                break;
            case 'h':
                usage (E_SUCCESS);
                /*@notreached@*/break;
            case 'i':
                if (   (getlong (optarg, &inact) == 0)
                    || (inact < -1)) {
                    fprintf (stderr,
                             _("%s: invalid numeric argument '%s'\n"),
                             Prog, optarg);
                    usage (E_BAD_ARG);
                }
                iflg = true;
                anyflag = true;
                break;
            case 'k':
                /* change only if expired, like Linux-PAM passwd -k. */
                kflg = true;    /* ok for users */
                break;
            case 'l':
                lflg = true;
                anyflag = true;
                break;
            case 'n':
                if (   (getlong (optarg, &age_min) == 0)
                    || (age_min < -1)) {
                    fprintf (stderr,
                             _("%s: invalid numeric argument '%s'\n"),
                             Prog, optarg);
                    usage (E_BAD_ARG);
                }
                nflg = true;
                anyflag = true;
                break;
            case 'q':
                qflg = true;    /* ok for users */
                break;
            case 'r':
                /* -r repository (files|nis|nisplus) */
                /* only "files" supported for now */
                if (strcmp (optarg, "files") != 0) {
                    fprintf (stderr,
                             _("%s: repository %s not supported\n"),
                         Prog, optarg);
                    exit (E_BAD_ARG);
                }
                break;
            case 'R': /* no-op, handled in process_root_flag () */
                break;
            case 'S':
                Sflg = true;    /* ok for users */
                break;
            case 'u':
                uflg = true;
                anyflag = true;
                break;
            case 'w':
                if (   (getlong (optarg, &warn) == 0)
                    || (warn < -1)) {
                    (void) fprintf (stderr,
                                    _("%s: invalid numeric argument '%s'\n"),
                                    Prog, optarg);
                    usage (E_BAD_ARG);
                }
                wflg = true;
                anyflag = true;
                break;
            case 'x':
                if (   (getlong (optarg, &age_max) == 0)
                    || (age_max < -1)) {
                    (void) fprintf (stderr,
                                    _("%s: invalid numeric argument '%s'\n"),
                                    Prog, optarg);
                    usage (E_BAD_ARG);
                }
                xflg = true;
                anyflag = true;
                break;
            default:
                usage (E_BAD_ARG);
            }
        }
    }

    /*
     * Now I have to get the user name. The name will be gotten from the
     * command line if possible. Otherwise it is figured out from the
     * environment.
     */
    pw = get_my_pwent ();
    if (NULL == pw) {
        (void) fprintf (stderr,
                        _("%s: Cannot determine your user name.\n"),
                        Prog);
        SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
                 (unsigned long) getuid ()));
        exit (E_NOPERM);
    }
    myname = xstrdup (pw->pw_name);
    if (optind < argc) {
        name = argv[optind];
    } else {
        name = myname;
    }

    /*
     * Make sure that at most one username was specified.
     */
    if (argc > (optind+1)) {
        usage (E_USAGE);
    }

    /*
     * The -a flag requires -S, no other flags, no username, and
     * you must be root.  --marekm
     */
    if (aflg) {
        if (anyflag || !Sflg || (optind < argc)) {
            usage (E_USAGE);
        }
        if (!amroot) {
            (void) fprintf (stderr,
                            _("%s: Permission denied.\n"),
                            Prog);
            exit (E_NOPERM);
        }
        setpwent ();
        while ( (pw = getpwent ()) != NULL ) {
            print_status (pw);
        }
        endpwent ();
        exit (E_SUCCESS);
    }
#if 0
    /*
     * Allow certain users (administrators) to change passwords of
     * certain users. Not implemented yet. --marekm
     */
    if (may_change_passwd (myname, name))
        amroot = 1;
#endif

    /*
     * If any of the flags were given, a user name must be supplied on
     * the command line. Only an unadorned command line doesn't require
     * the user's name be given. Also, -x, -n, -w, -i, -e, -d,
     * -l, -u may appear with each other. -S, -k must appear alone.
     */

    /*
     * -S now ok for normal users (check status of my own account), and
     * doesn't require username.  --marekm
     */
    if (anyflag && optind >= argc) {
        usage (E_USAGE);
    }

    if (   (Sflg && kflg)
        || (anyflag && (Sflg || kflg))) {
        usage (E_USAGE);
    }

    if (anyflag && !amroot) {
        (void) fprintf (stderr, _("%s: Permission denied.\n"), Prog);
        exit (E_NOPERM);
    }

    pw = xgetpwnam (name);
    if (NULL == pw) {
        (void) fprintf (stderr,
                        _("%s: user '%s' does not exist\n"),
                        Prog, name);
        exit (E_NOPERM);
    }
#ifdef WITH_SELINUX
    /* only do this check when getuid()==0 because it's a pre-condition for
       changing a password without entering the old one */
    if ((is_selinux_enabled() > 0) && (getuid() == 0) &&
        (check_selinux_access (name, pw->pw_uid, PASSWD__PASSWD) != 0)) {
        security_context_t user_context = NULL;
        const char *user = "Unknown user context";
        if (getprevcon (&user_context) == 0) {
            user = user_context; /* FIXME: use context_user_get? */
        }
        SYSLOG ((LOG_ALERT,
                 "%s is not authorized to change the password of %s",
                 user, name));
        (void) fprintf(stderr,
                       _("%s: %s is not authorized to change the password of %s\n"),
                       Prog, user, name);
        if (NULL != user_context) {
            freecon (user_context);
        }
        exit (E_NOPERM);
    }
#endif              /* WITH_SELINUX */

    /*
     * If the UID of the user does not match the current real UID,
     * check if I'm root.
     */
    if (!amroot && (pw->pw_uid != getuid ())) {
        (void) fprintf (stderr,
                        _("%s: You may not view or modify password information for %s.\n"),
                        Prog, name);
        SYSLOG ((LOG_WARN,
                 "%s: can't view or modify password information for %s",
                 Prog, name));
        closelog ();
        exit (E_NOPERM);
    }

    if (Sflg) {
        print_status (pw);
        exit (E_SUCCESS);
    }
#ifndef USE_PAM
    /*
     * The user name is valid, so let's get the shadow file entry.
     */
    sp = getspnam (name); /* !USE_PAM, no need for xgetspnam */
    if (NULL == sp) {
        if (errno == EACCES) {
            (void) fprintf (stderr,
                            _("%s: Permission denied.\n"),
                            Prog);
            exit (E_NOPERM);
        }
        sp = pwd_to_spwd (pw);
    }

    cp = sp->sp_pwdp;

    /*
     * If there are no other flags, just change the password.
     */
    if (!anyflag) {
        STRFCPY (crypt_passwd, cp);

        /*
         * See if the user is permitted to change the password. 
         * Otherwise, go ahead and set a new password.
         */
        check_password (pw, sp);

        /*
         * Let the user know whose password is being changed.
         */
        if (!qflg) {
            (void) printf (_("Changing password for %s\n"), name);
        }

        if (new_password (pw) != 0) {
            (void) fprintf (stderr,
                            _("The password for %s is unchanged.\n"),
                            name);
            closelog ();
            exit (E_NOPERM);
        }
        do_update_pwd = true;
        do_update_age = true;
    }
#endif              /* !USE_PAM */
    /*
     * 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.
     */
    pwd_init ();

#ifdef USE_PAM
    /*
     * Don't set the real UID for PAM...
     */
    if (!anyflag) {
        do_pam_passwd (name, qflg, kflg);
        exit (E_SUCCESS);
    }
#endif              /* USE_PAM */
    if (setuid (0) != 0) {
        (void) fputs (_("Cannot change ID to root.\n"), stderr);
        SYSLOG ((LOG_ERR, "can't setuid(0)"));
        closelog ();
        exit (E_NOPERM);
    }
    if (spw_file_present ()) {
        update_shadow ();
    } else {
        update_noshadow ();
    }

    nscd_flush_cache ("passwd");
    nscd_flush_cache ("group");

    SYSLOG ((LOG_INFO, "password for '%s' changed by '%s'", name, myname));
    closelog ();
    if (!qflg) {
        if (!anyflag) {
#ifndef USE_PAM
            (void) printf (_("%s: password changed.\n"), Prog);
#endif              /* USE_PAM */
        } else {
            (void) printf (_("%s: password expiry information changed.\n"), Prog);
        }
    }

    return E_SUCCESS;
}

static void myfn_show_debug_info()
{
    MYLOG_D("passwd pro on 2018-0607-1615");

#ifdef WITH_SELINUX
    MYLOG_D("WITH_SELINUX");
#endif // #ifdef WITH_SELINUX

#ifdef USE_PAM
    MYLOG_D("USE_PAM");
#endif // #ifdef USE_PAM

#ifdef HAVE_LIBCRACK_HIST
    MYLOG_D("HAVE_LIBCRACK_HIST");
#endif // #ifdef HAVE_LIBCRACK_HIST

#ifdef HAVE_LIBCRACK_PW
    MYLOG_D("HAVE_LIBCRACK_PW");
#endif // #ifdef HAVE_LIBCRACK_PW

#ifdef USE_SHA_CRYPT
    MYLOG_D("USE_SHA_CRYPT");
#endif // #ifdef USE_SHA_CRYPT

#ifdef HAVE_STRFTIME
    MYLOG_D("HAVE_STRFTIME");
#endif // #ifdef HAVE_STRFTIME
}

static bool is_pwd_include_user_name(const char* user_name, const char* pwd)
{
    bool b_rc = false;
    int len_name = 0;
    int len_pwd = 0;
    const char* find = NULL;

    do {
        if ((NULL == user_name) || (NULL == pwd)) {
            break;
        }

        len_name = strlen(user_name);
        len_pwd = strlen(pwd);
        if (len_pwd < len_name) {
            break;
        }

        find = strstr(pwd, user_name);
        if (NULL == find) {
            break;
        }

        b_rc = true;
    } while (0);

    return b_rc;
}

猜你喜欢

转载自blog.csdn.net/lostspeed/article/details/80615621
今日推荐