/* vi: set sw=4 ts=4: */ /* * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. */ #include <fcntl.h> #include <stdio.h> #include <string.h> #include <signal.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <utime.h> #include <syslog.h> #include <time.h> #include <sys/resource.h> #include <errno.h> #include "busybox.h" 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); } if (((fp = fopen(filename, "r+")) == 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) { fprintf(stderr, "%s: %s\n", filename, strerror(errno)); 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) { int amroot; char *cp; char *np; char *name; char *myname; int flag; int algo = 1; /* -a - password algorithm */ int lflg = 0; /* -l - lock account */ int uflg = 0; /* -u - unlock account */ int dflg = 0; /* -d - delete password */ const struct passwd *pw; amroot = (getuid() == 0); openlog("passwd", LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH); while ((flag = getopt(argc, argv, "a:dlu")) != EOF) { switch (flag) { case 'a': algo = get_algo(optarg); break; case 'd': dflg++; break; case 'l': lflg++; break; case 'u': uflg++; break; default: bb_show_usage(); } } myname = (char *) bb_xstrdup(bb_getpwuid(NULL, getuid(), -1)); /* exits on error */ if (optind < argc) { name = argv[optind]; } else { name = myname; } if ((lflg || uflg || dflg) && (optind >= argc || !amroot)) { bb_show_usage(); } pw = getpwnam(name); if (!pw) { bb_error_msg_and_die("Unknown user %s\n", name); } if (!amroot && pw->pw_uid != getuid()) { syslog(LOG_WARNING, "can't change pwd for `%s'", name); bb_error_msg_and_die("Permission denied.\n"); } 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 (!(dflg || lflg || uflg)) { 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.\n", 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.\n", name); } } else if (lflg) { 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 (uflg) { if (crypt_passwd[0] == '!') { memmove(crypt_passwd, &crypt_passwd[1], sizeof crypt_passwd - 1); } } else if (dflg) { 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); printf("Password changed.\n"); } else { syslog(LOG_WARNING, "an error occurred updating the password file"); bb_error_msg_and_die("An error occurred updating the password file.\n"); } 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]) { if (!(clear = bb_askpass(0, "Old password:"))) { /* 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); fprintf(stderr, "Incorrect password.\n"); /* return -1; */ return 1; } safe_strncpy(orig, clear, sizeof(orig)); memset(clear, 0, strlen(clear)); memset(cipher, 0, strlen(cipher)); } else { orig[0] = '\0'; } if (! (cp=bb_askpass(0, "Enter the new password (minimum of 5, maximum of 8 characters)\n" "Please use a combination of upper and lower case letters and numbers.\n" "Enter new password: "))) { 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) { printf("\nWarning: weak password (continuing).\n"); } else { /* return -1; */ return 1; } } if (!(cp = bb_askpass(0, "Re-enter new password: "))) { memset(orig, 0, sizeof orig); /* return -1; */ return 1; } if (strcmp(cp, pass)) { fprintf(stderr, "Passwords do not match.\n"); /* 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); }