1 /* vi: set sw=4 ts=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.
9 * Moved from loginutils/passwd.c by Alexander Shishkin <virtuoso@slind.org>
11 * Modified to be able to add or delete users, groups and users to/from groups
12 * by Tito Ragusa <farmatito@tiscali.it>
14 * Licensed under GPLv2, see file LICENSE in this tarball for details.
19 static void check_selinux_update_passwd(const char *username)
21 security_context_t context;
24 if (getuid() != (uid_t)0 || is_selinux_enabled() == 0)
25 return; /* No need to check */
27 if (getprevcon_raw(&context) < 0)
28 bb_perror_msg_and_die("getprevcon failed");
29 seuser = strtok(context, ":");
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");
36 if (ENABLE_FEATURE_CLEAN_UP)
40 # define check_selinux_update_passwd(username) ((void)0)
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
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
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
54 4) delete a user: update_passwd(FILE, USER, NULL, NULL)
56 5) delete a group: update_passwd(FILE, GROUP, NULL, NULL)
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
61 7) change user's passord: 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
65 This function does not validate the arguments fed to it
66 so the calling program should take care of that.
68 Returns number of lines changed, or -1 on error.
70 int FAST_FUNC update_passwd(const char *filename,
72 const char *new_passwd,
75 #if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
89 int ret = -1; /* failure */
91 filename = xmalloc_follow_symlinks(filename);
95 check_selinux_update_passwd(name);
97 /* New passwd file, "/etc/passwd+" for now */
98 fnamesfx = xasprintf("%s+", filename);
99 sfx_char = &fnamesfx[strlen(fnamesfx)-1];
100 name = xasprintf("%s:", name);
101 user_len = strlen(name);
103 if (strstr(filename, "shadow"))
104 old_fp = fopen(filename, "r+");
106 old_fp = fopen_or_warn(filename, "r+");
109 old_fd = fileno(old_fp);
111 selinux_preserve_fcontext(old_fd);
113 /* Try to create "/etc/passwd+". Wait if it exists. */
116 // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC?
117 new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600);
118 if (new_fd >= 0) goto created;
119 if (errno != EEXIST) break;
120 usleep(100000); /* 0.1 sec */
122 bb_perror_msg("can't create '%s'", fnamesfx);
126 if (!fstat(old_fd, &sb)) {
127 fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
128 fchown(new_fd, sb.st_uid, sb.st_gid);
131 new_fp = fdopen(new_fd, "w");
138 /* Backup file is "/etc/passwd-" */
140 /* Delete old backup */
141 i = (unlink(fnamesfx) && errno != ENOENT);
142 /* Create backup as a hardlink to current */
143 if (i || link(filename, fnamesfx))
144 bb_perror_msg("warning: can't create backup copy '%s'",
148 /* Lock the password file before updating */
149 lock.l_type = F_WRLCK;
150 lock.l_whence = SEEK_SET;
153 if (fcntl(old_fd, F_SETLK, &lock) < 0)
154 bb_perror_msg("warning: can't lock '%s'", filename);
155 lock.l_type = F_UNLCK;
157 /* Read current password file, write updated /etc/passwd+ */
162 line = xmalloc_fgetline(old_fp);
163 if (!line) /* EOF/error */
165 if (strncmp(name, line, user_len) != 0) {
166 fprintf(new_fp, "%s\n", line);
170 /* We have a match with "name:"... */
171 cp = line + user_len; /* move past name: */
173 #if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
175 /* It's actually /etc/group+, not /etc/passwd+ */
176 if (ENABLE_FEATURE_ADDUSER_TO_GROUP
177 && applet_name[0] == 'a'
179 /* Add user to group */
180 fprintf(new_fp, "%s%s%s\n", line,
181 last_char_is(line, ':') ? "" : ",",
184 } else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP
185 /* && applet_name[0] == 'd' */
187 /* Delete user from group */
189 const char *fmt = "%s";
191 /* find the start of the member list: last ':' */
192 cp = strrchr(line, ':');
195 /* write the cut line name:passwd:gid:
197 fprintf(new_fp, "%s:", line);
198 /* parse the tokens of the member list */
200 while ((cp = strsep(&tmp, ",")) != NULL) {
201 if (strcmp(member, cp) != 0) {
202 fprintf(new_fp, fmt, cp);
205 /* found member, skip it */
209 fprintf(new_fp, "\n");
213 if ((ENABLE_PASSWD && applet_name[0] == 'p')
214 || (ENABLE_CHPASSWD && applet_name[0] == 'c')
217 cp = strchrnul(cp, ':'); /* move past old passwd */
218 /* name: + new_passwd + :rest of line */
219 fprintf(new_fp, "%s%s%s\n", name, new_passwd, cp);
221 } /* else delete user or group: skip the line */
226 if (changed_lines == 0) {
227 #if ENABLE_FEATURE_DEL_USER_FROM_GROUP
229 bb_error_msg("can't find %s in %s", member, filename);
231 if ((ENABLE_ADDUSER || ENABLE_ADDGROUP)
232 && applet_name[0] == 'a' && !member
234 /* add user or group */
235 fprintf(new_fp, "%s%s\n", name, new_passwd);
240 fcntl(old_fd, F_SETLK, &lock);
242 /* We do want all of them to execute, thus | instead of || */
244 if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
245 || rename(fnamesfx, filename)
247 /* At least one of those failed */
251 /* Success: ret >= 0 */
263 free((char *)filename);