/* * 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 */ (void) setlocale (LC_ALL, ""); (void) bindtextdomain (PACKAGE, LOCALEDIR); (void) textdomain (PACKAGE); /* * The program behaves differently when executed by root than when * executed by a normal user. */ amroot = (getuid () == 0); /* * Get the program name. The program name is used as a prefix to * most error messages. */ Prog = Basename (argv[0]); sanitize_env (); OPENLOG ("passwd"); { /* * Parse the command line options. */ int option_index = 0; 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'}, {"root", required_argument, NULL, 'R'}, {"repository", 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, "adei:kln:qR:r:Suw:x:", long_options, &option_index)) != -1) { switch (c) { case 'a': aflg = true; break; case 'd': dflg = true; anyflag = true; break; case 'e': eflg = true; anyflag = true; 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': if ('/' != optarg[0]) { fprintf (stderr, _("%s: invalid chroot path '%s'\n"), Prog, optarg); exit (E_BAD_ARG); } newroot = optarg; if (access (newroot, F_OK) != 0) { fprintf(stderr, _("%s: chroot directory %s does not exist\n"), Prog, newroot); exit (E_BAD_ARG); } if ( chroot(newroot) != 0 ) { fprintf(stderr, _("%s: unable to chroot to directory %s\n"), Prog, newroot); exit (E_BAD_ARG); } 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 'S': Sflg = true; /* ok for users */ break; case 'u': uflg = true; anyflag = true; break; case 'w': if ( (getlong (optarg, &warn) == 0) || (warn < -1)) { 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)) { 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) { 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) { 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) { fprintf (stderr, _("%s: Permission denied.\n"), Prog); exit (E_NOPERM); } pw = xgetpwnam (name); if (NULL == pw) { 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 = "******"; if (getprevcon (&user_context) == 0) { user = user_context; } SYSLOG ((LOG_ALERT, "%s is not authorized to change the password of %s", user, name)); 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 ())) { 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) { 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) { printf (_("Changing password for %s\n"), name); } if (new_password (pw)) { 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) { 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 printf (_("%s: password changed.\n"), Prog); #endif /* USE_PAM */ } else { printf (_("%s: password expiry information changed.\n"), Prog); } } return E_SUCCESS; }
int passwd_main(int argc UNUSED_PARAM, char **argv) { enum { OPT_algo = (1 << 0), /* -a - password algorithm */ OPT_lock = (1 << 1), /* -l - lock account */ OPT_unlock = (1 << 2), /* -u - unlock account */ OPT_delete = (1 << 3), /* -d - delete password */ OPT_lud = OPT_lock | OPT_unlock | OPT_delete, }; unsigned opt; int rc; const char *opt_a = CONFIG_FEATURE_DEFAULT_PASSWD_ALGO; const char *filename; char *myname; char *name; char *newp; struct passwd *pw; uid_t myuid; struct rlimit rlimit_fsize; char c; #if ENABLE_FEATURE_SHADOWPASSWDS /* Using _r function to avoid pulling in static buffers */ struct spwd spw; char buffer[256]; #endif logmode = LOGMODE_BOTH; openlog(applet_name, 0, LOG_AUTH); opt = getopt32(argv, "a:lud", &opt_a); //argc -= optind; argv += optind; myuid = getuid(); /* -l, -u, -d require root priv and username argument */ if ((opt & OPT_lud) && (myuid != 0 || !argv[0])) bb_show_usage(); /* Will complain and die if username not found */ myname = xstrdup(xuid2uname(myuid)); name = argv[0] ? argv[0] : myname; pw = xgetpwnam(name); if (myuid != 0 && pw->pw_uid != myuid) { /* LOGMODE_BOTH */ bb_error_msg_and_die("%s can't change password for %s", myname, name); } #if ENABLE_FEATURE_SHADOWPASSWDS { /* getspnam_r may return 0 yet set result to NULL. * At least glibc 2.4 does this. Be extra paranoid here. */ struct spwd *result = NULL; errno = 0; if (getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result) != 0 || !result /* no error, but no record found either */ || strcmp(result->sp_namp, pw->pw_name) != 0 /* paranoia */ ) { if (errno != ENOENT) { /* LOGMODE_BOTH */ bb_perror_msg("no record of %s in %s, using %s", name, bb_path_shadow_file, bb_path_passwd_file); } /* else: /etc/shadow does not exist, * apparently we are on a shadow-less system, * no surprise there */ } else { pw->pw_passwd = result->sp_pwdp; } } #endif /* Decide what the new password will be */ newp = NULL; c = pw->pw_passwd[0] - '!'; if (!(opt & OPT_lud)) { if (myuid != 0 && !c) { /* passwd starts with '!' */ /* LOGMODE_BOTH */ bb_error_msg_and_die("can't change " "locked password for %s", name); } printf("Changing password for %s\n", name); newp = new_password(pw, myuid, opt_a); if (!newp) { logmode = LOGMODE_STDIO; bb_error_msg_and_die("password for %s is unchanged", name); } } else if (opt & OPT_lock) { if (!c) goto skip; /* passwd starts with '!' */ newp = xasprintf("!%s", pw->pw_passwd); } else if (opt & OPT_unlock) { if (c) goto skip; /* not '!' */ /* pw->pw_passwd points to static storage, * strdup'ing to avoid nasty surprizes */ newp = xstrdup(&pw->pw_passwd[1]); } else if (opt & OPT_delete) { newp = (char*)""; } rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000; setrlimit(RLIMIT_FSIZE, &rlimit_fsize); bb_signals(0 + (1 << SIGHUP) + (1 << SIGINT) + (1 << SIGQUIT) , SIG_IGN); umask(077); xsetuid(0); #if ENABLE_FEATURE_SHADOWPASSWDS filename = bb_path_shadow_file; rc = update_passwd(bb_path_shadow_file, name, newp, NULL); if (rc > 0) /* password in /etc/shadow was updated */ newp = (char*) "x"; if (rc >= 0) /* 0 = /etc/shadow missing (not an error), >0 = passwd changed in /etc/shadow */ #endif { filename = bb_path_passwd_file; rc = update_passwd(bb_path_passwd_file, name, newp, NULL); } /* LOGMODE_BOTH */ if (rc < 0) bb_error_msg_and_die("can't update password file %s", filename); bb_error_msg("password for %s changed by %s", name, myname); /*if (ENABLE_FEATURE_CLEAN_UP) free(newp); - can't, it may be non-malloced */ skip: if (!newp) { bb_error_msg_and_die("password for %s is already %slocked", name, (opt & OPT_unlock) ? "un" : ""); } if (ENABLE_FEATURE_CLEAN_UP) free(myname); return 0; }