source: MondoRescue/branches/3.3/mindi-busybox/libbb/update_passwd.c@ 3647

Last change on this file since 3647 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

  • Property svn:eol-style set to native
File size: 8.4 KB
Line 
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
19static 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 8) delete a user from all groups: update_passwd(FILE, NULL, NULL, MEMBER)
66
67 This function does not validate the arguments fed to it
68 so the calling program should take care of that.
69
70 Returns number of lines changed, or -1 on error.
71*/
72int FAST_FUNC update_passwd(const char *filename,
73 const char *name,
74 const char *new_passwd,
75 const char *member)
76{
77#if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
78#define member NULL
79#endif
80 struct stat sb;
81 struct flock lock;
82 FILE *old_fp;
83 FILE *new_fp;
84 char *fnamesfx;
85 char *sfx_char;
86 char *name_colon;
87 int old_fd;
88 int new_fd;
89 int i;
90 int changed_lines;
91 int ret = -1; /* failure */
92 /* used as a bool: "are we modifying /etc/shadow?" */
93#if ENABLE_FEATURE_SHADOWPASSWDS
94 const char *shadow = strstr(filename, "shadow");
95#else
96# define shadow NULL
97#endif
98
99 filename = xmalloc_follow_symlinks(filename);
100 if (filename == NULL)
101 return ret;
102
103 if (name)
104 check_selinux_update_passwd(name);
105
106 /* New passwd file, "/etc/passwd+" for now */
107 fnamesfx = xasprintf("%s+", filename);
108 sfx_char = &fnamesfx[strlen(fnamesfx)-1];
109 name_colon = xasprintf("%s:", name ? name : "");
110
111 if (shadow)
112 old_fp = fopen(filename, "r+");
113 else
114 old_fp = fopen_or_warn(filename, "r+");
115 if (!old_fp) {
116 if (shadow)
117 ret = 0; /* missing shadow is not an error */
118 goto free_mem;
119 }
120 old_fd = fileno(old_fp);
121
122 selinux_preserve_fcontext(old_fd);
123
124 /* Try to create "/etc/passwd+". Wait if it exists. */
125 i = 30;
126 do {
127 // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC?
128 new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600);
129 if (new_fd >= 0) goto created;
130 if (errno != EEXIST) break;
131 usleep(100000); /* 0.1 sec */
132 } while (--i);
133 bb_perror_msg("can't create '%s'", fnamesfx);
134 goto close_old_fp;
135
136 created:
137 if (fstat(old_fd, &sb) == 0) {
138 fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
139 fchown(new_fd, sb.st_uid, sb.st_gid);
140 }
141 errno = 0;
142 new_fp = xfdopen_for_write(new_fd);
143
144 /* Backup file is "/etc/passwd-" */
145 *sfx_char = '-';
146 /* Delete old backup */
147 i = (unlink(fnamesfx) && errno != ENOENT);
148 /* Create backup as a hardlink to current */
149 if (i || link(filename, fnamesfx))
150 bb_perror_msg("warning: can't create backup copy '%s'",
151 fnamesfx);
152 *sfx_char = '+';
153
154 /* Lock the password file before updating */
155 lock.l_type = F_WRLCK;
156 lock.l_whence = SEEK_SET;
157 lock.l_start = 0;
158 lock.l_len = 0;
159 if (fcntl(old_fd, F_SETLK, &lock) < 0)
160 bb_perror_msg("warning: can't lock '%s'", filename);
161 lock.l_type = F_UNLCK;
162
163 /* Read current password file, write updated /etc/passwd+ */
164 changed_lines = 0;
165 while (1) {
166 char *cp, *line;
167
168 line = xmalloc_fgetline(old_fp);
169 if (!line) /* EOF/error */
170 break;
171
172 if (!name && member) {
173 /* Delete member from all groups */
174 /* line is "GROUP:PASSWD:[member1[,member2]...]" */
175 unsigned member_len = strlen(member);
176 char *list = strrchr(line, ':');
177 while (list) {
178 list++;
179 next_list_element:
180 if (is_prefixed_with(list, member)) {
181 char c;
182 changed_lines++;
183 c = list[member_len];
184 if (c == '\0') {
185 if (list[-1] == ',')
186 list--;
187 *list = '\0';
188 break;
189 }
190 if (c == ',') {
191 overlapping_strcpy(list, list + member_len + 1);
192 goto next_list_element;
193 }
194 changed_lines--;
195 }
196 list = strchr(list, ',');
197 }
198 fprintf(new_fp, "%s\n", line);
199 goto next;
200 }
201
202 cp = is_prefixed_with(line, name_colon);
203 if (!cp) {
204 fprintf(new_fp, "%s\n", line);
205 goto next;
206 }
207
208 /* We have a match with "name:"... */
209 /* cp points past "name:" */
210
211#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
212 if (member) {
213 /* It's actually /etc/group+, not /etc/passwd+ */
214 if (ENABLE_FEATURE_ADDUSER_TO_GROUP
215 && applet_name[0] == 'a'
216 ) {
217 /* Add user to group */
218 fprintf(new_fp, "%s%s%s\n", line,
219 last_char_is(line, ':') ? "" : ",",
220 member);
221 changed_lines++;
222 } else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP
223 /* && applet_name[0] == 'd' */
224 ) {
225 /* Delete user from group */
226 char *tmp;
227 const char *fmt = "%s";
228
229 /* find the start of the member list: last ':' */
230 cp = strrchr(line, ':');
231 /* cut it */
232 *cp++ = '\0';
233 /* write the cut line name:passwd:gid:
234 * or name:!:: */
235 fprintf(new_fp, "%s:", line);
236 /* parse the tokens of the member list */
237 tmp = cp;
238 while ((cp = strsep(&tmp, ",")) != NULL) {
239 if (strcmp(member, cp) != 0) {
240 fprintf(new_fp, fmt, cp);
241 fmt = ",%s";
242 } else {
243 /* found member, skip it */
244 changed_lines++;
245 }
246 }
247 fprintf(new_fp, "\n");
248 }
249 } else
250#endif
251 if ((ENABLE_PASSWD && applet_name[0] == 'p')
252 || (ENABLE_CHPASSWD && applet_name[0] == 'c')
253 ) {
254 /* Change passwd */
255 cp = strchrnul(cp, ':'); /* move past old passwd */
256
257 if (shadow && *cp == ':') {
258 /* /etc/shadow's field 3 (passwd change date) needs updating */
259 /* move past old change date */
260 cp = strchrnul(cp + 1, ':');
261 /* "name:" + "new_passwd" + ":" + "change date" + ":rest of line" */
262 fprintf(new_fp, "%s%s:%u%s\n", name_colon, new_passwd,
263 (unsigned)(time(NULL)) / (24*60*60), cp);
264 } else {
265 /* "name:" + "new_passwd" + ":rest of line" */
266 fprintf(new_fp, "%s%s%s\n", name_colon, new_passwd, cp);
267 }
268 changed_lines++;
269 } /* else delete user or group: skip the line */
270 next:
271 free(line);
272 }
273
274 if (changed_lines == 0) {
275#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
276 if (member) {
277 if (ENABLE_ADDGROUP && applet_name[0] == 'a')
278 bb_error_msg("can't find %s in %s", name, filename);
279 if (ENABLE_DELGROUP && applet_name[0] == 'd')
280 bb_error_msg("can't find %s in %s", member, filename);
281 }
282#endif
283 if ((ENABLE_ADDUSER || ENABLE_ADDGROUP)
284 && applet_name[0] == 'a' && !member
285 ) {
286 /* add user or group */
287 fprintf(new_fp, "%s%s\n", name_colon, new_passwd);
288 changed_lines++;
289 }
290 }
291
292 fcntl(old_fd, F_SETLK, &lock);
293
294 /* We do want all of them to execute, thus | instead of || */
295 errno = 0;
296 if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
297 || rename(fnamesfx, filename)
298 ) {
299 /* At least one of those failed */
300 bb_perror_nomsg();
301 goto unlink_new;
302 }
303 /* Success: ret >= 0 */
304 ret = changed_lines;
305
306 unlink_new:
307 if (ret < 0)
308 unlink(fnamesfx);
309
310 close_old_fp:
311 fclose(old_fp);
312
313 free_mem:
314 free(fnamesfx);
315 free((char *)filename);
316 free(name_colon);
317 return ret;
318}
Note: See TracBrowser for help on using the repository browser.