3 |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
4 |
*/ |
*/ |
5 |
|
|
6 |
#include "busybox.h" |
#include "libbb.h" |
7 |
#include <syslog.h> |
#include <syslog.h> |
8 |
|
|
|
|
|
9 |
static void nuke_str(char *str) |
static void nuke_str(char *str) |
10 |
{ |
{ |
11 |
if (str) memset(str, 0, strlen(str)); |
if (str) memset(str, 0, strlen(str)); |
12 |
} |
} |
13 |
|
|
|
|
|
|
static int i64c(int i) |
|
|
{ |
|
|
i &= 0x3f; |
|
|
if (i == 0) |
|
|
return '.'; |
|
|
if (i == 1) |
|
|
return '/'; |
|
|
if (i < 12) |
|
|
return ('0' - 2 + i); |
|
|
if (i < 38) |
|
|
return ('A' - 12 + i); |
|
|
return ('a' - 38 + i); |
|
|
} |
|
|
|
|
|
|
|
|
static void crypt_make_salt(char *p, int cnt) |
|
|
{ |
|
|
unsigned x = x; /* it's pointless to initialize it anyway :) */ |
|
|
|
|
|
x += getpid() + time(NULL) + clock(); |
|
|
do { |
|
|
/* x = (x*1664525 + 1013904223) % 2^32 generator is lame |
|
|
* (low-order bit is not "random", etc...), |
|
|
* but for our purposes it is good enough */ |
|
|
x = x*1664525 + 1013904223; |
|
|
/* BTW, Park and Miller's "minimal standard generator" is |
|
|
* x = x*16807 % ((2^31)-1) |
|
|
* It has no problem with visibly alternating lowest bit |
|
|
* but is also weak in cryptographic sense + needs div, |
|
|
* which needs more code (and slower) on many CPUs */ |
|
|
*p++ = i64c(x >> 16); |
|
|
*p++ = i64c(x >> 22); |
|
|
} while (--cnt); |
|
|
*p = '\0'; |
|
|
} |
|
|
|
|
|
|
|
14 |
static char* new_password(const struct passwd *pw, uid_t myuid, int algo) |
static char* new_password(const struct passwd *pw, uid_t myuid, int algo) |
15 |
{ |
{ |
16 |
char salt[sizeof("$N$XXXXXXXX")]; /* "$N$XXXXXXXX" or "XX" */ |
char salt[sizeof("$N$XXXXXXXX")]; /* "$N$XXXXXXXX" or "XX" */ |
17 |
char *orig = ""; |
char *orig = (char*)""; |
18 |
char *newp = NULL; |
char *newp = NULL; |
|
char *cipher = NULL; |
|
19 |
char *cp = NULL; |
char *cp = NULL; |
20 |
char *ret = NULL; /* failure so far */ |
char *ret = NULL; /* failure so far */ |
21 |
|
|
22 |
if (myuid && pw->pw_passwd[0]) { |
if (myuid && pw->pw_passwd[0]) { |
23 |
|
char *encrypted; |
24 |
|
|
25 |
orig = bb_askpass(0, "Old password:"); /* returns ptr to static */ |
orig = bb_askpass(0, "Old password:"); /* returns ptr to static */ |
26 |
if (!orig) |
if (!orig) |
27 |
goto err_ret; |
goto err_ret; |
28 |
cipher = pw_encrypt(orig, pw->pw_passwd); /* returns ptr to static */ |
encrypted = pw_encrypt(orig, pw->pw_passwd, 1); /* returns malloced str */ |
29 |
if (strcmp(cipher, pw->pw_passwd) != 0) { |
if (strcmp(encrypted, pw->pw_passwd) != 0) { |
30 |
syslog(LOG_WARNING, "incorrect password for '%s'", |
syslog(LOG_WARNING, "incorrect password for %s", |
31 |
pw->pw_name); |
pw->pw_name); |
32 |
bb_do_delay(FAIL_DELAY); |
bb_do_delay(FAIL_DELAY); |
33 |
puts("Incorrect password"); |
puts("Incorrect password"); |
34 |
goto err_ret; |
goto err_ret; |
35 |
} |
} |
36 |
|
if (ENABLE_FEATURE_CLEAN_UP) free(encrypted); |
37 |
} |
} |
38 |
orig = xstrdup(orig); /* or else bb_askpass() will destroy it */ |
orig = xstrdup(orig); /* or else bb_askpass() will destroy it */ |
39 |
newp = bb_askpass(0, "New password:"); /* returns ptr to static */ |
newp = bb_askpass(0, "New password:"); /* returns ptr to static */ |
52 |
goto err_ret; |
goto err_ret; |
53 |
} |
} |
54 |
|
|
55 |
/*memset(salt, 0, sizeof(salt)); - why?*/ |
crypt_make_salt(salt, 1, 0); /* des */ |
|
crypt_make_salt(salt, 1); /* des */ |
|
56 |
if (algo) { /* MD5 */ |
if (algo) { /* MD5 */ |
57 |
strcpy(salt, "$1$"); |
strcpy(salt, "$1$"); |
58 |
crypt_make_salt(salt + 3, 4); |
crypt_make_salt(salt + 3, 4, 0); |
59 |
} |
} |
60 |
ret = xstrdup(pw_encrypt(newp, salt)); /* returns ptr to static */ |
/* pw_encrypt returns malloced str */ |
61 |
|
ret = pw_encrypt(newp, salt, 1); |
62 |
/* whee, success! */ |
/* whee, success! */ |
63 |
|
|
64 |
err_ret: |
err_ret: |
66 |
if (ENABLE_FEATURE_CLEAN_UP) free(orig); |
if (ENABLE_FEATURE_CLEAN_UP) free(orig); |
67 |
nuke_str(newp); |
nuke_str(newp); |
68 |
if (ENABLE_FEATURE_CLEAN_UP) free(newp); |
if (ENABLE_FEATURE_CLEAN_UP) free(newp); |
|
nuke_str(cipher); |
|
69 |
nuke_str(cp); |
nuke_str(cp); |
70 |
return ret; |
return ret; |
71 |
} |
} |
72 |
|
|
73 |
|
int passwd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
74 |
#if 0 |
int passwd_main(int argc UNUSED_PARAM, char **argv) |
|
static int get_algo(char *a) |
|
|
{ |
|
|
/* standard: MD5 */ |
|
|
int x = 1; |
|
|
if (strcasecmp(a, "des") == 0) |
|
|
x = 0; |
|
|
return x; |
|
|
} |
|
|
#endif |
|
|
|
|
|
|
|
|
static int update_passwd(const char *filename, const char *username, |
|
|
const char *new_pw) |
|
|
{ |
|
|
struct stat sb; |
|
|
struct flock lock; |
|
|
FILE *old_fp; |
|
|
FILE *new_fp; |
|
|
char *new_name; |
|
|
char *last_char; |
|
|
unsigned user_len; |
|
|
int old_fd; |
|
|
int new_fd; |
|
|
int i; |
|
|
int ret = 1; /* failure */ |
|
|
|
|
|
logmode = LOGMODE_STDIO; |
|
|
/* New passwd file, "/etc/passwd+" for now */ |
|
|
new_name = xasprintf("%s+", filename); |
|
|
last_char = &new_name[strlen(new_name)-1]; |
|
|
username = xasprintf("%s:", username); |
|
|
user_len = strlen(username); |
|
|
|
|
|
old_fp = fopen(filename, "r+"); |
|
|
if (!old_fp) |
|
|
goto free_mem; |
|
|
old_fd = fileno(old_fp); |
|
|
|
|
|
/* Try to create "/etc/passwd+". Wait if it exists. */ |
|
|
i = 30; |
|
|
do { |
|
|
// FIXME: on last iteration try w/o O_EXCL but with O_TRUNC? |
|
|
new_fd = open(new_name, O_WRONLY|O_CREAT|O_EXCL,0600); |
|
|
if (new_fd >= 0) goto created; |
|
|
if (errno != EEXIST) break; |
|
|
usleep(100000); /* 0.1 sec */ |
|
|
} while (--i); |
|
|
bb_perror_msg("cannot create '%s'", new_name); |
|
|
goto close_old_fp; |
|
|
created: |
|
|
if (!fstat(old_fd, &sb)) { |
|
|
fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */ |
|
|
fchown(new_fd, sb.st_uid, sb.st_gid); |
|
|
} |
|
|
new_fp = fdopen(new_fd, "w"); |
|
|
if (!new_fp) { |
|
|
close(new_fd); |
|
|
goto unlink_new; |
|
|
} |
|
|
|
|
|
/* Backup file is "/etc/passwd-" */ |
|
|
last_char[0] = '-'; |
|
|
/* Delete old one, create new as a hardlink to current */ |
|
|
i = (unlink(new_name) && errno != ENOENT); |
|
|
if (i || link(filename, new_name)) |
|
|
bb_perror_msg("warning: cannot create backup copy '%s'", new_name); |
|
|
last_char[0] = '+'; |
|
|
|
|
|
/* 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(old_fd, F_SETLK, &lock) < 0) |
|
|
bb_perror_msg("warning: cannot lock '%s'", filename); |
|
|
lock.l_type = F_UNLCK; |
|
|
|
|
|
/* Read current password file, write updated one */ |
|
|
while (1) { |
|
|
char *line = xmalloc_fgets(old_fp); |
|
|
if (!line) break; /* EOF/error */ |
|
|
if (strncmp(username, line, user_len) == 0) { |
|
|
/* we have a match with "username:"... */ |
|
|
const char *cp = line + user_len; |
|
|
/* now cp -> old passwd, skip it: */ |
|
|
cp = strchr(cp, ':'); |
|
|
if (!cp) cp = ""; |
|
|
/* now cp -> ':' after old passwd or -> "" */ |
|
|
fprintf(new_fp, "%s%s%s", username, new_pw, cp); |
|
|
/* Erase password in memory */ |
|
|
} else |
|
|
fputs(line, new_fp); |
|
|
free(line); |
|
|
} |
|
|
fcntl(old_fd, F_SETLK, &lock); |
|
|
|
|
|
/* We do want all of them to execute, thus | instead of || */ |
|
|
if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp)) |
|
|
|| rename(new_name, filename) |
|
|
) { |
|
|
/* At least one of those failed */ |
|
|
goto unlink_new; |
|
|
} |
|
|
ret = 0; /* whee, success! */ |
|
|
|
|
|
unlink_new: |
|
|
if (ret) unlink(new_name); |
|
|
|
|
|
close_old_fp: |
|
|
fclose(old_fp); |
|
|
|
|
|
free_mem: |
|
|
if (ENABLE_FEATURE_CLEAN_UP) free(new_name); |
|
|
if (ENABLE_FEATURE_CLEAN_UP) free((char*)username); |
|
|
logmode = LOGMODE_BOTH; |
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
int passwd_main(int argc, char **argv) |
|
75 |
{ |
{ |
76 |
enum { |
enum { |
77 |
OPT_algo = 0x1, /* -a - password algorithm */ |
OPT_algo = 0x1, /* -a - password algorithm */ |
80 |
OPT_delete = 0x8, /* -d - delete password */ |
OPT_delete = 0x8, /* -d - delete password */ |
81 |
OPT_lud = 0xe, |
OPT_lud = 0xe, |
82 |
STATE_ALGO_md5 = 0x10, |
STATE_ALGO_md5 = 0x10, |
83 |
/*STATE_ALGO_des = 0x20, not needed yet */ |
//STATE_ALGO_des = 0x20, not needed yet |
84 |
}; |
}; |
85 |
unsigned opt; |
unsigned opt; |
86 |
char *opt_a = ""; |
int rc; |
87 |
|
const char *opt_a = ""; |
88 |
const char *filename; |
const char *filename; |
89 |
char *myname; |
char *myname; |
90 |
char *name; |
char *name; |
93 |
uid_t myuid; |
uid_t myuid; |
94 |
struct rlimit rlimit_fsize; |
struct rlimit rlimit_fsize; |
95 |
char c; |
char c; |
96 |
|
#if ENABLE_FEATURE_SHADOWPASSWDS |
97 |
|
/* Using _r function to avoid pulling in static buffers */ |
98 |
|
struct spwd spw; |
99 |
|
char buffer[256]; |
100 |
|
#endif |
101 |
|
|
102 |
logmode = LOGMODE_BOTH; |
logmode = LOGMODE_BOTH; |
103 |
openlog(applet_name, LOG_NOWAIT, LOG_AUTH); |
openlog(applet_name, LOG_NOWAIT, LOG_AUTH); |
104 |
opt = getopt32(argc, argv, "a:lud", &opt_a); |
opt = getopt32(argv, "a:lud", &opt_a); |
105 |
argc -= optind; |
//argc -= optind; |
106 |
argv += optind; |
argv += optind; |
107 |
|
|
108 |
if (strcasecmp(opt_a, "des") != 0) /* -a */ |
if (strcasecmp(opt_a, "des") != 0) /* -a */ |
110 |
//else |
//else |
111 |
// opt |= STATE_ALGO_des; |
// opt |= STATE_ALGO_des; |
112 |
myuid = getuid(); |
myuid = getuid(); |
113 |
if ((opt & OPT_lud) && (!argc || myuid)) |
/* -l, -u, -d require root priv and username argument */ |
114 |
|
if ((opt & OPT_lud) && (myuid || !argv[0])) |
115 |
bb_show_usage(); |
bb_show_usage(); |
116 |
|
|
117 |
myname = xstrdup(bb_getpwuid(NULL, myuid, -1)); |
/* Will complain and die if username not found */ |
118 |
name = argc ? argv[0] : myname; |
myname = xstrdup(bb_getpwuid(NULL, -1, myuid)); |
119 |
|
name = argv[0] ? argv[0] : myname; |
120 |
|
|
121 |
pw = getpwnam(name); |
pw = getpwnam(name); |
122 |
if (!pw) bb_error_msg_and_die("unknown user %s", name); |
if (!pw) |
123 |
|
bb_error_msg_and_die("unknown user %s", name); |
124 |
if (myuid && pw->pw_uid != myuid) { |
if (myuid && pw->pw_uid != myuid) { |
125 |
/* LOGMODE_BOTH */ |
/* LOGMODE_BOTH */ |
126 |
bb_error_msg_and_die("%s can't change password for %s", myname, name); |
bb_error_msg_and_die("%s can't change password for %s", myname, name); |
127 |
} |
} |
128 |
|
|
|
filename = bb_path_passwd_file; |
|
129 |
#if ENABLE_FEATURE_SHADOWPASSWDS |
#if ENABLE_FEATURE_SHADOWPASSWDS |
130 |
{ |
{ |
131 |
struct spwd *sp = getspnam(name); |
/* getspnam_r may return 0 yet set result to NULL. |
132 |
if (!sp) { |
* At least glibc 2.4 does this. Be extra paranoid here. */ |
133 |
|
struct spwd *result = NULL; |
134 |
|
if (getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result) |
135 |
|
|| !result || strcmp(result->sp_namp, pw->pw_name) != 0) { |
136 |
/* LOGMODE_BOTH */ |
/* LOGMODE_BOTH */ |
137 |
bb_error_msg("no record of %s in %s, using %s", |
bb_error_msg("no record of %s in %s, using %s", |
138 |
name, bb_path_shadow_file, |
name, bb_path_shadow_file, |
139 |
bb_path_passwd_file); |
bb_path_passwd_file); |
140 |
} else { |
} else { |
141 |
filename = bb_path_shadow_file; |
pw->pw_passwd = result->sp_pwdp; |
|
pw->pw_passwd = sp->sp_pwdp; |
|
142 |
} |
} |
143 |
} |
} |
144 |
#endif |
#endif |
163 |
newp = xasprintf("!%s", pw->pw_passwd); |
newp = xasprintf("!%s", pw->pw_passwd); |
164 |
} else if (opt & OPT_unlock) { |
} else if (opt & OPT_unlock) { |
165 |
if (c) goto skip; /* not '!' */ |
if (c) goto skip; /* not '!' */ |
166 |
|
/* pw->pw_passwd points to static storage, |
167 |
|
* strdup'ing to avoid nasty surprizes */ |
168 |
newp = xstrdup(&pw->pw_passwd[1]); |
newp = xstrdup(&pw->pw_passwd[1]); |
169 |
} else if (opt & OPT_delete) { |
} else if (opt & OPT_delete) { |
170 |
newp = xstrdup(""); |
//newp = xstrdup(""); |
171 |
|
newp = (char*)""; |
172 |
} |
} |
173 |
|
|
174 |
rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000; |
rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000; |
175 |
setrlimit(RLIMIT_FSIZE, &rlimit_fsize); |
setrlimit(RLIMIT_FSIZE, &rlimit_fsize); |
176 |
signal(SIGHUP, SIG_IGN); |
bb_signals(0 |
177 |
signal(SIGINT, SIG_IGN); |
+ (1 << SIGHUP) |
178 |
signal(SIGQUIT, SIG_IGN); |
+ (1 << SIGINT) |
179 |
|
+ (1 << SIGQUIT) |
180 |
|
, SIG_IGN); |
181 |
umask(077); |
umask(077); |
182 |
xsetuid(0); |
xsetuid(0); |
183 |
if (update_passwd(filename, name, newp) != 0) { |
|
184 |
/* LOGMODE_BOTH */ |
#if ENABLE_FEATURE_SHADOWPASSWDS |
185 |
bb_error_msg_and_die("cannot update password file %s", |
filename = bb_path_shadow_file; |
186 |
filename); |
rc = update_passwd(bb_path_shadow_file, name, newp); |
187 |
|
if (rc == 0) /* no lines updated, no errors detected */ |
188 |
|
#endif |
189 |
|
{ |
190 |
|
filename = bb_path_passwd_file; |
191 |
|
rc = update_passwd(bb_path_passwd_file, name, newp); |
192 |
} |
} |
193 |
/* LOGMODE_BOTH */ |
/* LOGMODE_BOTH */ |
194 |
|
if (rc < 0) |
195 |
|
bb_error_msg_and_die("cannot update password file %s", |
196 |
|
filename); |
197 |
bb_info_msg("Password for %s changed by %s", name, myname); |
bb_info_msg("Password for %s changed by %s", name, myname); |
198 |
|
|
199 |
if (ENABLE_FEATURE_CLEAN_UP) free(newp); |
//if (ENABLE_FEATURE_CLEAN_UP) free(newp); |
200 |
skip: |
skip: |
201 |
if (!newp) { |
if (!newp) { |
202 |
bb_error_msg_and_die("password for %s is already %slocked", |
bb_error_msg_and_die("password for %s is already %slocked", |
203 |
name, (opt & OPT_unlock) ? "un" : ""); |
name, (opt & OPT_unlock) ? "un" : ""); |