busybox/loginutils/passwd.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   4 */
   5//config:config PASSWD
   6//config:       bool "passwd (21 kb)"
   7//config:       default y
   8//config:       select FEATURE_SYSLOG
   9//config:       help
  10//config:       passwd changes passwords for user and group accounts. A normal user
  11//config:       may only change the password for his/her own account, the super user
  12//config:       may change the password for any account. The administrator of a group
  13//config:       may change the password for the group.
  14//config:
  15//config:       Note that busybox binary must be setuid root for this applet to
  16//config:       work properly.
  17//config:
  18//config:config FEATURE_PASSWD_WEAK_CHECK
  19//config:       bool "Check new passwords for weakness"
  20//config:       default y
  21//config:       depends on PASSWD
  22//config:       help
  23//config:       With this option passwd will refuse new passwords which are "weak".
  24
  25//applet:/* Needs to be run by root or be suid root - needs to change /etc/{passwd,shadow}: */
  26//applet:IF_PASSWD(APPLET(passwd, BB_DIR_USR_BIN, BB_SUID_REQUIRE))
  27
  28//kbuild:lib-$(CONFIG_PASSWD) += passwd.o
  29
  30//usage:#define passwd_trivial_usage
  31//usage:       "[OPTIONS] [USER]"
  32//usage:#define passwd_full_usage "\n\n"
  33//usage:       "Change USER's password (default: current user)"
  34//usage:     "\n"
  35//usage:     "\n        -a ALG  "CRYPT_METHODS_HELP_STR
  36//usage:     "\n        -d      Set password to ''"
  37//usage:     "\n        -l      Lock (disable) account"
  38//usage:     "\n        -u      Unlock (enable) account"
  39
  40#include "libbb.h"
  41#include <syslog.h>
  42
  43static char* new_password(const struct passwd *pw, uid_t myuid, const char *algo)
  44{
  45        char salt[MAX_PW_SALT_LEN];
  46        char *orig = NULL;
  47        char *newp = NULL;
  48        char *cp = NULL;
  49        char *ret = NULL; /* failure so far */
  50
  51        if (myuid != 0 && pw->pw_passwd[0]) {
  52                char *encrypted;
  53
  54                orig = bb_ask_noecho_stdin("Old password: "); /* returns malloced str */
  55                if (!orig)
  56                        goto err_ret;
  57                encrypted = pw_encrypt(orig, pw->pw_passwd, 1); /* returns malloced str */
  58                if (strcmp(encrypted, pw->pw_passwd) != 0) {
  59                        syslog(LOG_WARNING, "incorrect password for %s", pw->pw_name);
  60                        bb_do_delay(LOGIN_FAIL_DELAY);
  61                        puts("Incorrect password");
  62                        goto err_ret;
  63                }
  64                if (ENABLE_FEATURE_CLEAN_UP)
  65                        free(encrypted);
  66        }
  67        newp = bb_ask_noecho_stdin("New password: "); /* returns malloced str */
  68        if (!newp)
  69                goto err_ret;
  70        if (ENABLE_FEATURE_PASSWD_WEAK_CHECK
  71         && obscure(orig, newp, pw) /* NB: passing NULL orig is ok */
  72         && myuid != 0
  73        ) {
  74                goto err_ret; /* non-root is not allowed to have weak passwd */
  75        }
  76
  77        cp = bb_ask_noecho_stdin("Retype password: ");
  78        if (!cp)
  79                goto err_ret;
  80        if (strcmp(cp, newp) != 0) {
  81                puts("Passwords don't match");
  82                goto err_ret;
  83        }
  84
  85        crypt_make_pw_salt(salt, algo);
  86
  87        /* pw_encrypt returns malloced str */
  88        ret = pw_encrypt(newp, salt, 1);
  89        /* whee, success! */
  90
  91 err_ret:
  92        nuke_str(orig);
  93        if (ENABLE_FEATURE_CLEAN_UP) free(orig);
  94
  95        nuke_str(newp);
  96        if (ENABLE_FEATURE_CLEAN_UP) free(newp);
  97
  98        nuke_str(cp);
  99        if (ENABLE_FEATURE_CLEAN_UP) free(cp);
 100        return ret;
 101}
 102
 103int passwd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 104int passwd_main(int argc UNUSED_PARAM, char **argv)
 105{
 106        enum {
 107                OPT_algo   = (1 << 0), /* -a - password algorithm */
 108                OPT_lock   = (1 << 1), /* -l - lock account */
 109                OPT_unlock = (1 << 2), /* -u - unlock account */
 110                OPT_delete = (1 << 3), /* -d - delete password */
 111                OPT_lud    = OPT_lock | OPT_unlock | OPT_delete,
 112        };
 113        unsigned opt;
 114        int rc;
 115        const char *opt_a = CONFIG_FEATURE_DEFAULT_PASSWD_ALGO;
 116        const char *filename;
 117        char *myname;
 118        char *name;
 119        char *newp;
 120        struct passwd *pw;
 121        uid_t myuid;
 122        struct rlimit rlimit_fsize;
 123        char c;
 124#if ENABLE_FEATURE_SHADOWPASSWDS
 125        /* Using _r function to avoid pulling in static buffers */
 126        struct spwd spw;
 127        char buffer[256];
 128#endif
 129
 130        logmode = LOGMODE_BOTH;
 131        openlog(applet_name, 0, LOG_AUTH);
 132        opt = getopt32(argv, "a:lud", &opt_a);
 133        //argc -= optind;
 134        argv += optind;
 135
 136        myuid = getuid();
 137        /* -l, -u, -d require root priv and username argument */
 138        if ((opt & OPT_lud) && (myuid != 0 || !argv[0]))
 139                bb_show_usage();
 140
 141        /* Will complain and die if username not found */
 142        myname = xstrdup(xuid2uname(myuid));
 143        name = argv[0] ? argv[0] : myname;
 144
 145        pw = xgetpwnam(name);
 146        if (myuid != 0 && pw->pw_uid != myuid) {
 147                /* LOGMODE_BOTH */
 148                bb_error_msg_and_die("%s can't change password for %s", myname, name);
 149        }
 150
 151#if ENABLE_FEATURE_SHADOWPASSWDS
 152        {
 153                /* getspnam_r may return 0 yet set result to NULL.
 154                 * At least glibc 2.4 does this. Be extra paranoid here. */
 155                struct spwd *result = NULL;
 156                errno = 0;
 157                if (getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result) != 0
 158                 || !result /* no error, but no record found either */
 159                 || strcmp(result->sp_namp, pw->pw_name) != 0 /* paranoia */
 160                ) {
 161                        if (errno != ENOENT) {
 162                                /* LOGMODE_BOTH */
 163                                bb_perror_msg("no record of %s in %s, using %s",
 164                                        name, bb_path_shadow_file,
 165                                        bb_path_passwd_file);
 166                        }
 167                        /* else: /etc/shadow does not exist,
 168                         * apparently we are on a shadow-less system,
 169                         * no surprise there */
 170                } else {
 171                        pw->pw_passwd = result->sp_pwdp;
 172                }
 173        }
 174#endif
 175
 176        /* Decide what the new password will be */
 177        newp = NULL;
 178        c = pw->pw_passwd[0] - '!';
 179        if (!(opt & OPT_lud)) {
 180                if (myuid != 0 && !c) { /* passwd starts with '!' */
 181                        /* LOGMODE_BOTH */
 182                        bb_error_msg_and_die("can't change "
 183                                        "locked password for %s", name);
 184                }
 185                printf("Changing password for %s\n", name);
 186                newp = new_password(pw, myuid, opt_a);
 187                if (!newp) {
 188                        logmode = LOGMODE_STDIO;
 189                        bb_error_msg_and_die("password for %s is unchanged", name);
 190                }
 191        } else if (opt & OPT_lock) {
 192                if (!c)
 193                        goto skip; /* passwd starts with '!' */
 194                newp = xasprintf("!%s", pw->pw_passwd);
 195        } else if (opt & OPT_unlock) {
 196                if (c)
 197                        goto skip; /* not '!' */
 198                /* pw->pw_passwd points to static storage,
 199                 * strdup'ing to avoid nasty surprizes */
 200                newp = xstrdup(&pw->pw_passwd[1]);
 201        } else if (opt & OPT_delete) {
 202                newp = (char*)"";
 203        }
 204
 205        rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000;
 206        setrlimit(RLIMIT_FSIZE, &rlimit_fsize);
 207        bb_signals(0
 208                + (1 << SIGHUP)
 209                + (1 << SIGINT)
 210                + (1 << SIGQUIT)
 211                , SIG_IGN);
 212        umask(077);
 213        xsetuid(0);
 214
 215#if ENABLE_FEATURE_SHADOWPASSWDS
 216        filename = bb_path_shadow_file;
 217        rc = update_passwd(bb_path_shadow_file, name, newp, NULL);
 218        if (rc > 0)
 219                /* password in /etc/shadow was updated */
 220                newp = (char*) "x";
 221        if (rc >= 0)
 222                /* 0 = /etc/shadow missing (not an error), >0 = passwd changed in /etc/shadow */
 223#endif
 224        {
 225                filename = bb_path_passwd_file;
 226                rc = update_passwd(bb_path_passwd_file, name, newp, NULL);
 227        }
 228        /* LOGMODE_BOTH */
 229        if (rc < 0)
 230                bb_error_msg_and_die("can't update password file %s", filename);
 231        bb_info_msg("password for %s changed by %s", name, myname);
 232
 233        /*if (ENABLE_FEATURE_CLEAN_UP) free(newp); - can't, it may be non-malloced */
 234 skip:
 235        if (!newp) {
 236                bb_error_msg_and_die("password for %s is already %slocked",
 237                        name, (opt & OPT_unlock) ? "un" : "");
 238        }
 239
 240        if (ENABLE_FEATURE_CLEAN_UP)
 241                free(myname);
 242        return 0;
 243}
 244