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 | * Modified to be able to add or delete users, groups and users to/from groups
|
---|
12 | * by Tito Ragusa <farmatito@tiscali.it>
|
---|
13 | *
|
---|
14 | * Licensed under GPLv2, see file LICENSE in this source tree.
|
---|
15 | */
|
---|
16 | #include "libbb.h"
|
---|
17 |
|
---|
18 | #if ENABLE_SELINUX
|
---|
19 | static void check_selinux_update_passwd(const char *username)
|
---|
20 | {
|
---|
21 | security_context_t context;
|
---|
22 | char *seuser;
|
---|
23 |
|
---|
24 | if (getuid() != (uid_t)0 || is_selinux_enabled() == 0)
|
---|
25 | return; /* No need to check */
|
---|
26 |
|
---|
27 | if (getprevcon_raw(&context) < 0)
|
---|
28 | bb_perror_msg_and_die("getprevcon failed");
|
---|
29 | seuser = strtok(context, ":");
|
---|
30 | if (!seuser)
|
---|
31 | bb_error_msg_and_die("invalid context '%s'", context);
|
---|
32 | if (strcmp(seuser, username) != 0) {
|
---|
33 | if (checkPasswdAccess(PASSWD__PASSWD) != 0)
|
---|
34 | bb_error_msg_and_die("SELinux: access denied");
|
---|
35 | }
|
---|
36 | if (ENABLE_FEATURE_CLEAN_UP)
|
---|
37 | freecon(context);
|
---|
38 | }
|
---|
39 | #else
|
---|
40 | # define check_selinux_update_passwd(username) ((void)0)
|
---|
41 | #endif
|
---|
42 |
|
---|
43 | /*
|
---|
44 | 1) add a user: update_passwd(FILE, USER, REMAINING_PWLINE, NULL)
|
---|
45 | only if CONFIG_ADDUSER=y and applet_name[0] == 'a' like in adduser
|
---|
46 |
|
---|
47 | 2) add a group: update_passwd(FILE, GROUP, REMAINING_GRLINE, NULL)
|
---|
48 | only if CONFIG_ADDGROUP=y and applet_name[0] == 'a' like in addgroup
|
---|
49 |
|
---|
50 | 3) add a user to a group: update_passwd(FILE, GROUP, NULL, MEMBER)
|
---|
51 | only if CONFIG_FEATURE_ADDUSER_TO_GROUP=y, applet_name[0] == 'a'
|
---|
52 | like in addgroup and member != NULL
|
---|
53 |
|
---|
54 | 4) delete a user: update_passwd(FILE, USER, NULL, NULL)
|
---|
55 |
|
---|
56 | 5) delete a group: update_passwd(FILE, GROUP, NULL, NULL)
|
---|
57 |
|
---|
58 | 6) delete a user from a group: update_passwd(FILE, GROUP, NULL, MEMBER)
|
---|
59 | only if CONFIG_FEATURE_DEL_USER_FROM_GROUP=y and member != NULL
|
---|
60 |
|
---|
61 | 7) change user's password: update_passwd(FILE, USER, NEW_PASSWD, NULL)
|
---|
62 | only if CONFIG_PASSWD=y and applet_name[0] == 'p' like in passwd
|
---|
63 | or if CONFIG_CHPASSWD=y and applet_name[0] == 'c' like in chpasswd
|
---|
64 |
|
---|
65 | This function does not validate the arguments fed to it
|
---|
66 | so the calling program should take care of that.
|
---|
67 |
|
---|
68 | Returns number of lines changed, or -1 on error.
|
---|
69 | */
|
---|
70 | int FAST_FUNC update_passwd(const char *filename,
|
---|
71 | const char *name,
|
---|
72 | const char *new_passwd,
|
---|
73 | const char *member)
|
---|
74 | {
|
---|
75 | #if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
|
---|
76 | #define member NULL
|
---|
77 | #endif
|
---|
78 | struct stat sb;
|
---|
79 | struct flock lock;
|
---|
80 | FILE *old_fp;
|
---|
81 | FILE *new_fp;
|
---|
82 | char *fnamesfx;
|
---|
83 | char *sfx_char;
|
---|
84 | char *name_colon;
|
---|
85 | unsigned user_len;
|
---|
86 | int old_fd;
|
---|
87 | int new_fd;
|
---|
88 | int i;
|
---|
89 | int changed_lines;
|
---|
90 | int ret = -1; /* failure */
|
---|
91 | /* used as a bool: "are we modifying /etc/shadow?" */
|
---|
92 | #if ENABLE_FEATURE_SHADOWPASSWDS
|
---|
93 | const char *shadow = strstr(filename, "shadow");
|
---|
94 | #else
|
---|
95 | # define shadow NULL
|
---|
96 | #endif
|
---|
97 |
|
---|
98 | filename = xmalloc_follow_symlinks(filename);
|
---|
99 | if (filename == NULL)
|
---|
100 | return ret;
|
---|
101 |
|
---|
102 | check_selinux_update_passwd(name);
|
---|
103 |
|
---|
104 | /* New passwd file, "/etc/passwd+" for now */
|
---|
105 | fnamesfx = xasprintf("%s+", filename);
|
---|
106 | sfx_char = &fnamesfx[strlen(fnamesfx)-1];
|
---|
107 | name_colon = xasprintf("%s:", name);
|
---|
108 | user_len = strlen(name_colon);
|
---|
109 |
|
---|
110 | if (shadow)
|
---|
111 | old_fp = fopen(filename, "r+");
|
---|
112 | else
|
---|
113 | old_fp = fopen_or_warn(filename, "r+");
|
---|
114 | if (!old_fp) {
|
---|
115 | if (shadow)
|
---|
116 | ret = 0; /* missing shadow is not an error */
|
---|
117 | goto free_mem;
|
---|
118 | }
|
---|
119 | old_fd = fileno(old_fp);
|
---|
120 |
|
---|
121 | selinux_preserve_fcontext(old_fd);
|
---|
122 |
|
---|
123 | /* Try to create "/etc/passwd+". Wait if it exists. */
|
---|
124 | i = 30;
|
---|
125 | do {
|
---|
126 | // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC?
|
---|
127 | new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600);
|
---|
128 | if (new_fd >= 0) goto created;
|
---|
129 | if (errno != EEXIST) break;
|
---|
130 | usleep(100000); /* 0.1 sec */
|
---|
131 | } while (--i);
|
---|
132 | bb_perror_msg("can't create '%s'", fnamesfx);
|
---|
133 | goto close_old_fp;
|
---|
134 |
|
---|
135 | created:
|
---|
136 | if (fstat(old_fd, &sb) == 0) {
|
---|
137 | fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
|
---|
138 | fchown(new_fd, sb.st_uid, sb.st_gid);
|
---|
139 | }
|
---|
140 | errno = 0;
|
---|
141 | new_fp = xfdopen_for_write(new_fd);
|
---|
142 |
|
---|
143 | /* Backup file is "/etc/passwd-" */
|
---|
144 | *sfx_char = '-';
|
---|
145 | /* Delete old backup */
|
---|
146 | i = (unlink(fnamesfx) && errno != ENOENT);
|
---|
147 | /* Create backup as a hardlink to current */
|
---|
148 | if (i || link(filename, fnamesfx))
|
---|
149 | bb_perror_msg("warning: can't create backup copy '%s'",
|
---|
150 | fnamesfx);
|
---|
151 | *sfx_char = '+';
|
---|
152 |
|
---|
153 | /* Lock the password file before updating */
|
---|
154 | lock.l_type = F_WRLCK;
|
---|
155 | lock.l_whence = SEEK_SET;
|
---|
156 | lock.l_start = 0;
|
---|
157 | lock.l_len = 0;
|
---|
158 | if (fcntl(old_fd, F_SETLK, &lock) < 0)
|
---|
159 | bb_perror_msg("warning: can't lock '%s'", filename);
|
---|
160 | lock.l_type = F_UNLCK;
|
---|
161 |
|
---|
162 | /* Read current password file, write updated /etc/passwd+ */
|
---|
163 | changed_lines = 0;
|
---|
164 | while (1) {
|
---|
165 | char *cp, *line;
|
---|
166 |
|
---|
167 | line = xmalloc_fgetline(old_fp);
|
---|
168 | if (!line) /* EOF/error */
|
---|
169 | break;
|
---|
170 | if (strncmp(name_colon, line, user_len) != 0) {
|
---|
171 | fprintf(new_fp, "%s\n", line);
|
---|
172 | goto next;
|
---|
173 | }
|
---|
174 |
|
---|
175 | /* We have a match with "name:"... */
|
---|
176 | cp = line + user_len; /* move past name: */
|
---|
177 |
|
---|
178 | #if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
|
---|
179 | if (member) {
|
---|
180 | /* It's actually /etc/group+, not /etc/passwd+ */
|
---|
181 | if (ENABLE_FEATURE_ADDUSER_TO_GROUP
|
---|
182 | && applet_name[0] == 'a'
|
---|
183 | ) {
|
---|
184 | /* Add user to group */
|
---|
185 | fprintf(new_fp, "%s%s%s\n", line,
|
---|
186 | last_char_is(line, ':') ? "" : ",",
|
---|
187 | member);
|
---|
188 | changed_lines++;
|
---|
189 | } else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP
|
---|
190 | /* && applet_name[0] == 'd' */
|
---|
191 | ) {
|
---|
192 | /* Delete user from group */
|
---|
193 | char *tmp;
|
---|
194 | const char *fmt = "%s";
|
---|
195 |
|
---|
196 | /* find the start of the member list: last ':' */
|
---|
197 | cp = strrchr(line, ':');
|
---|
198 | /* cut it */
|
---|
199 | *cp++ = '\0';
|
---|
200 | /* write the cut line name:passwd:gid:
|
---|
201 | * or name:!:: */
|
---|
202 | fprintf(new_fp, "%s:", line);
|
---|
203 | /* parse the tokens of the member list */
|
---|
204 | tmp = cp;
|
---|
205 | while ((cp = strsep(&tmp, ",")) != NULL) {
|
---|
206 | if (strcmp(member, cp) != 0) {
|
---|
207 | fprintf(new_fp, fmt, cp);
|
---|
208 | fmt = ",%s";
|
---|
209 | } else {
|
---|
210 | /* found member, skip it */
|
---|
211 | changed_lines++;
|
---|
212 | }
|
---|
213 | }
|
---|
214 | fprintf(new_fp, "\n");
|
---|
215 | }
|
---|
216 | } else
|
---|
217 | #endif
|
---|
218 | if ((ENABLE_PASSWD && applet_name[0] == 'p')
|
---|
219 | || (ENABLE_CHPASSWD && applet_name[0] == 'c')
|
---|
220 | ) {
|
---|
221 | /* Change passwd */
|
---|
222 | cp = strchrnul(cp, ':'); /* move past old passwd */
|
---|
223 |
|
---|
224 | if (shadow && *cp == ':') {
|
---|
225 | /* /etc/shadow's field 3 (passwd change date) needs updating */
|
---|
226 | /* move past old change date */
|
---|
227 | cp = strchrnul(cp + 1, ':');
|
---|
228 | /* "name:" + "new_passwd" + ":" + "change date" + ":rest of line" */
|
---|
229 | fprintf(new_fp, "%s%s:%u%s\n", name_colon, new_passwd,
|
---|
230 | (unsigned)(time(NULL)) / (24*60*60), cp);
|
---|
231 | } else {
|
---|
232 | /* "name:" + "new_passwd" + ":rest of line" */
|
---|
233 | fprintf(new_fp, "%s%s%s\n", name_colon, new_passwd, cp);
|
---|
234 | }
|
---|
235 | changed_lines++;
|
---|
236 | } /* else delete user or group: skip the line */
|
---|
237 | next:
|
---|
238 | free(line);
|
---|
239 | }
|
---|
240 |
|
---|
241 | if (changed_lines == 0) {
|
---|
242 | #if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
|
---|
243 | if (member) {
|
---|
244 | if (ENABLE_ADDGROUP && applet_name[0] == 'a')
|
---|
245 | bb_error_msg("can't find %s in %s", name, filename);
|
---|
246 | if (ENABLE_DELGROUP && applet_name[0] == 'd')
|
---|
247 | bb_error_msg("can't find %s in %s", member, filename);
|
---|
248 | }
|
---|
249 | #endif
|
---|
250 | if ((ENABLE_ADDUSER || ENABLE_ADDGROUP)
|
---|
251 | && applet_name[0] == 'a' && !member
|
---|
252 | ) {
|
---|
253 | /* add user or group */
|
---|
254 | fprintf(new_fp, "%s%s\n", name_colon, new_passwd);
|
---|
255 | changed_lines++;
|
---|
256 | }
|
---|
257 | }
|
---|
258 |
|
---|
259 | fcntl(old_fd, F_SETLK, &lock);
|
---|
260 |
|
---|
261 | /* We do want all of them to execute, thus | instead of || */
|
---|
262 | errno = 0;
|
---|
263 | if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
|
---|
264 | || rename(fnamesfx, filename)
|
---|
265 | ) {
|
---|
266 | /* At least one of those failed */
|
---|
267 | bb_perror_nomsg();
|
---|
268 | goto unlink_new;
|
---|
269 | }
|
---|
270 | /* Success: ret >= 0 */
|
---|
271 | ret = changed_lines;
|
---|
272 |
|
---|
273 | unlink_new:
|
---|
274 | if (ret < 0)
|
---|
275 | unlink(fnamesfx);
|
---|
276 |
|
---|
277 | close_old_fp:
|
---|
278 | fclose(old_fp);
|
---|
279 |
|
---|
280 | free_mem:
|
---|
281 | free(fnamesfx);
|
---|
282 | free((char *)filename);
|
---|
283 | free(name_colon);
|
---|
284 | return ret;
|
---|
285 | }
|
---|