/* vi: set sw=4 ts=4: */ /* * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. */ #include "busybox.h" #include static char crypt_passwd[128]; static int create_backup(const char *backup, FILE * fp); static int new_password(const struct passwd *pw, int amroot, int algo); static void set_filesize_limit(int blocks); static int get_algo(char *a) { int x = 1; /* standard: MD5 */ if (strcasecmp(a, "des") == 0) x = 0; return x; } static int update_passwd(const struct passwd *pw, const char *crypt_pw) { char filename[1024]; char buf[1025]; char buffer[80]; char username[32]; char *pw_rest; int mask; int continued; FILE *fp; FILE *out_fp; struct stat sb; struct flock lock; #if ENABLE_FEATURE_SHADOWPASSWDS if (access(bb_path_shadow_file, F_OK) == 0) { snprintf(filename, sizeof filename, "%s", bb_path_shadow_file); } else #endif { snprintf(filename, sizeof filename, "%s", bb_path_passwd_file); } fp = fopen(filename, "r+"); if (fp == 0 || fstat(fileno(fp), &sb)) { /* return 0; */ return 1; } /* Lock the password file before updating */ lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(fileno(fp), F_SETLK, &lock) < 0) { bb_perror_msg("%s", filename); return 1; } lock.l_type = F_UNLCK; snprintf(buf, sizeof buf, "%s-", filename); if (create_backup(buf, fp)) { fcntl(fileno(fp), F_SETLK, &lock); fclose(fp); return 1; } snprintf(buf, sizeof buf, "%s+", filename); mask = umask(0777); out_fp = fopen(buf, "w"); umask(mask); if ((!out_fp) || (fchmod(fileno(out_fp), sb.st_mode & 0777)) || (fchown(fileno(out_fp), sb.st_uid, sb.st_gid))) { fcntl(fileno(fp), F_SETLK, &lock); fclose(fp); fclose(out_fp); return 1; } continued = 0; snprintf(username, sizeof username, "%s:", pw->pw_name); rewind(fp); while (!feof(fp)) { fgets(buffer, sizeof buffer, fp); if (!continued) { /* Check to see if we're updating this line. */ if (strncmp(username, buffer, strlen(username)) == 0) { /* we have a match. */ pw_rest = strchr(buffer, ':'); *pw_rest++ = '\0'; pw_rest = strchr(pw_rest, ':'); fprintf(out_fp, "%s:%s%s", buffer, crypt_pw, pw_rest); } else { fputs(buffer, out_fp); } } else { fputs(buffer, out_fp); } if (buffer[strlen(buffer) - 1] == '\n') { continued = 0; } else { continued = 1; } memset(buffer, 0, sizeof buffer); } if (fflush(out_fp) || fsync(fileno(out_fp)) || fclose(out_fp)) { unlink(buf); fcntl(fileno(fp), F_SETLK, &lock); fclose(fp); return 1; } if (rename(buf, filename) < 0) { fcntl(fileno(fp), F_SETLK, &lock); fclose(fp); return 1; } else { fcntl(fileno(fp), F_SETLK, &lock); fclose(fp); return 0; } } int passwd_main(int argc, char **argv) { enum { OPT_algo = 0x1, /* -a - password algorithm */ OPT_lock = 0x2, /* -l - lock account */ OPT_unlock = 0x4, /* -u - unlock account */ OPT_delete = 0x8, /* -d - delete password */ OPT_lud = 0xe, }; unsigned opt; char *opt_a; int amroot; char *cp; char *np; char *name; char *myname; int algo = 1; const struct passwd *pw; amroot = (getuid() == 0); openlog("passwd", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH); opt = getopt32(argc, argv, "a:lud", &opt_a); argc -= optind; argv += optind; if (opt & OPT_algo) algo = get_algo(opt_a); // -a if ((opt & OPT_lud) && (!argc || !amroot)) bb_show_usage(); myname = xstrdup(bb_getpwuid(NULL, getuid(), -1)); name = myname; if (argc) name = argv[0]; pw = getpwnam(name); if (!pw) { bb_error_msg_and_die("unknown user %s", name); } if (!amroot && pw->pw_uid != getuid()) { syslog(LOG_WARNING, "can't change pwd for '%s'", name); bb_error_msg_and_die("permission denied"); } if (ENABLE_FEATURE_SHADOWPASSWDS) { struct spwd *sp = getspnam(name); if (!sp) bb_error_msg_and_die("unknown user %s", name); cp = sp->sp_pwdp; } else cp = pw->pw_passwd; np = name; safe_strncpy(crypt_passwd, cp, sizeof(crypt_passwd)); if (!(opt & OPT_lud)) { if (!amroot) { if (cp[0] == '!') { syslog(LOG_WARNING, "password locked for '%s'", np); bb_error_msg_and_die("the password for %s cannot be changed", np); } } printf("Changing password for %s\n", name); if (new_password(pw, amroot, algo)) { bb_error_msg_and_die("the password for %s is unchanged", name); } } else if (opt & OPT_lock) { if (crypt_passwd[0] != '!') { memmove(&crypt_passwd[1], crypt_passwd, sizeof crypt_passwd - 1); crypt_passwd[sizeof crypt_passwd - 1] = '\0'; crypt_passwd[0] = '!'; } } else if (opt & OPT_unlock) { if (crypt_passwd[0] == '!') { memmove(crypt_passwd, &crypt_passwd[1], sizeof crypt_passwd - 1); } } else if (opt & OPT_delete) { crypt_passwd[0] = '\0'; } set_filesize_limit(30000); signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); umask(077); xsetuid(0); if (!update_passwd(pw, crypt_passwd)) { syslog(LOG_INFO, "password for '%s' changed by user '%s'", name, myname); puts("Password changed"); } else { syslog(LOG_WARNING, "cannot update password file"); bb_error_msg_and_die("cannot update password file"); } if (ENABLE_FEATURE_CLEAN_UP) free(myname); return 0; } static int create_backup(const char *backup, FILE * fp) { struct stat sb; struct utimbuf ub; FILE *bkfp; int c, mask; if (fstat(fileno(fp), &sb)) /* return -1; */ return 1; mask = umask(077); bkfp = fopen(backup, "w"); umask(mask); if (!bkfp) /* return -1; */ return 1; /* TODO: faster copy, not one-char-at-a-time. --marekm */ rewind(fp); while ((c = getc(fp)) != EOF) { if (putc(c, bkfp) == EOF) break; } if (c != EOF || fflush(bkfp)) { fclose(bkfp); /* return -1; */ return 1; } if (fclose(bkfp)) /* return -1; */ return 1; ub.actime = sb.st_atime; ub.modtime = sb.st_mtime; utime(backup, &ub); return 0; } static int i64c(int i) { if (i <= 0) return ('.'); if (i == 1) return ('/'); if (i >= 2 && i < 12) return ('0' - 2 + i); if (i >= 12 && i < 38) return ('A' - 12 + i); if (i >= 38 && i < 63) return ('a' - 38 + i); return ('z'); } static char *crypt_make_salt(void) { time_t now; static unsigned long x; static char result[3]; time(&now); x += now + getpid() + clock(); result[0] = i64c(((x >> 18) ^ (x >> 6)) & 077); result[1] = i64c(((x >> 12) ^ x) & 077); result[2] = '\0'; return result; } static int new_password(const struct passwd *pw, int amroot, int algo) { char *clear; char *cipher; char *cp; char salt[12]; /* "$N$XXXXXXXX" or "XX" */ char orig[200]; char pass[200]; if (!amroot && crypt_passwd[0]) { clear = bb_askpass(0, "Old password:"); if (!clear) { /* return -1; */ return 1; } cipher = pw_encrypt(clear, crypt_passwd); if (strcmp(cipher, crypt_passwd) != 0) { syslog(LOG_WARNING, "incorrect password for '%s'", pw->pw_name); bb_do_delay(FAIL_DELAY); puts("Incorrect password"); /* return -1; */ return 1; } safe_strncpy(orig, clear, sizeof(orig)); memset(clear, 0, strlen(clear)); memset(cipher, 0, strlen(cipher)); } else { orig[0] = '\0'; } cp = bb_askpass(0, "Enter the new password (minimum of 5 characters).\n" "Please use a combination of upper and lower case letters and numbers.\n" "Enter new password: "); if (!cp ) { memset(orig, 0, sizeof orig); /* return -1; */ return 1; } safe_strncpy(pass, cp, sizeof(pass)); memset(cp, 0, strlen(cp)); /* if (!obscure(orig, pass, pw)) { */ if (obscure(orig, pass, pw)) { if (amroot) { puts("\nWarning: weak password (continuing)"); } else { /* return -1; */ return 1; } } cp = bb_askpass(0, "Re-enter new password: "); if (!cp) { memset(orig, 0, sizeof orig); /* return -1; */ return 1; } if (strcmp(cp, pass)) { puts("Passwords do not match"); /* return -1; */ return 1; } memset(cp, 0, strlen(cp)); memset(orig, 0, sizeof(orig)); memset(salt, 0, sizeof(salt)); if (algo == 1) { strcpy(salt, "$1$"); strcat(salt, crypt_make_salt()); strcat(salt, crypt_make_salt()); strcat(salt, crypt_make_salt()); } strcat(salt, crypt_make_salt()); cp = pw_encrypt(pass, salt); memset(pass, 0, sizeof pass); safe_strncpy(crypt_passwd, cp, sizeof(crypt_passwd)); return 0; } static void set_filesize_limit(int blocks) { struct rlimit rlimit_fsize; rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * blocks; setrlimit(RLIMIT_FSIZE, &rlimit_fsize); }