diff options
Diffstat (limited to 'libbb')
-rw-r--r-- | libbb/update_passwd.c | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/libbb/update_passwd.c b/libbb/update_passwd.c new file mode 100644 index 000000000..5572db547 --- /dev/null +++ b/libbb/update_passwd.c | |||
@@ -0,0 +1,119 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * update_passwd | ||
4 | * | ||
5 | * update_passwd is a common function for passwd and chpasswd applets; | ||
6 | * it is responsible for updating password file (i.e. /etc/passwd or | ||
7 | * /etc/shadow) for a given user and password. | ||
8 | * | ||
9 | * Moved from loginutils/passwd.c by Alexander Shishkin <virtuoso@slind.org> | ||
10 | */ | ||
11 | |||
12 | #include "libbb.h" | ||
13 | |||
14 | int update_passwd(const char *filename, const char *username, | ||
15 | const char *new_pw) | ||
16 | { | ||
17 | struct stat sb; | ||
18 | struct flock lock; | ||
19 | FILE *old_fp; | ||
20 | FILE *new_fp; | ||
21 | char *new_name; | ||
22 | char *last_char; | ||
23 | unsigned user_len; | ||
24 | int old_fd; | ||
25 | int new_fd; | ||
26 | int i; | ||
27 | int cnt = 0; | ||
28 | int ret = 1; /* failure */ | ||
29 | |||
30 | /* New passwd file, "/etc/passwd+" for now */ | ||
31 | new_name = xasprintf("%s+", filename); | ||
32 | last_char = &new_name[strlen(new_name)-1]; | ||
33 | username = xasprintf("%s:", username); | ||
34 | user_len = strlen(username); | ||
35 | |||
36 | old_fp = fopen(filename, "r+"); | ||
37 | if (!old_fp) | ||
38 | goto free_mem; | ||
39 | old_fd = fileno(old_fp); | ||
40 | |||
41 | /* Try to create "/etc/passwd+". Wait if it exists. */ | ||
42 | i = 30; | ||
43 | do { | ||
44 | // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC? | ||
45 | new_fd = open(new_name, O_WRONLY|O_CREAT|O_EXCL,0600); | ||
46 | if (new_fd >= 0) goto created; | ||
47 | if (errno != EEXIST) break; | ||
48 | usleep(100000); /* 0.1 sec */ | ||
49 | } while (--i); | ||
50 | bb_perror_msg("cannot create '%s'", new_name); | ||
51 | goto close_old_fp; | ||
52 | |||
53 | created: | ||
54 | if (!fstat(old_fd, &sb)) { | ||
55 | fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */ | ||
56 | fchown(new_fd, sb.st_uid, sb.st_gid); | ||
57 | } | ||
58 | new_fp = fdopen(new_fd, "w"); | ||
59 | if (!new_fp) { | ||
60 | close(new_fd); | ||
61 | goto unlink_new; | ||
62 | } | ||
63 | |||
64 | /* Backup file is "/etc/passwd-" */ | ||
65 | last_char[0] = '-'; | ||
66 | /* Delete old one, create new as a hardlink to current */ | ||
67 | i = (unlink(new_name) && errno != ENOENT); | ||
68 | if (i || link(filename, new_name)) | ||
69 | bb_perror_msg("warning: cannot create backup copy '%s'", new_name); | ||
70 | last_char[0] = '+'; | ||
71 | |||
72 | /* Lock the password file before updating */ | ||
73 | lock.l_type = F_WRLCK; | ||
74 | lock.l_whence = SEEK_SET; | ||
75 | lock.l_start = 0; | ||
76 | lock.l_len = 0; | ||
77 | if (fcntl(old_fd, F_SETLK, &lock) < 0) | ||
78 | bb_perror_msg("warning: cannot lock '%s'", filename); | ||
79 | lock.l_type = F_UNLCK; | ||
80 | |||
81 | /* Read current password file, write updated one */ | ||
82 | while (1) { | ||
83 | char *line = xmalloc_fgets(old_fp); | ||
84 | if (!line) break; /* EOF/error */ | ||
85 | if (strncmp(username, line, user_len) == 0) { | ||
86 | /* we have a match with "username:"... */ | ||
87 | const char *cp = line + user_len; | ||
88 | /* now cp -> old passwd, skip it: */ | ||
89 | cp = strchr(cp, ':'); | ||
90 | if (!cp) cp = ""; | ||
91 | /* now cp -> ':' after old passwd or -> "" */ | ||
92 | fprintf(new_fp, "%s%s%s", username, new_pw, cp); | ||
93 | cnt++; | ||
94 | } else | ||
95 | fputs(line, new_fp); | ||
96 | free(line); | ||
97 | } | ||
98 | fcntl(old_fd, F_SETLK, &lock); | ||
99 | |||
100 | /* We do want all of them to execute, thus | instead of || */ | ||
101 | if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp)) | ||
102 | || rename(new_name, filename) | ||
103 | ) { | ||
104 | /* At least one of those failed */ | ||
105 | goto unlink_new; | ||
106 | } | ||
107 | ret = cnt; /* whee, success! */ | ||
108 | |||
109 | unlink_new: | ||
110 | if (ret) unlink(new_name); | ||
111 | |||
112 | close_old_fp: | ||
113 | fclose(old_fp); | ||
114 | |||
115 | free_mem: | ||
116 | if (ENABLE_FEATURE_CLEAN_UP) free(new_name); | ||
117 | if (ENABLE_FEATURE_CLEAN_UP) free((char*)username); | ||
118 | return ret; | ||
119 | } | ||