[T106][ZXW-22]7520V3SCV2.01.01.02P42U09_VEC_V0.8_AP_VEC origin source commit

Change-Id: Ic6e05d89ecd62fc34f82b23dcf306c93764aec4b
diff --git a/ap/app/busybox/src/libbb/update_passwd.c b/ap/app/busybox/src/libbb/update_passwd.c
new file mode 100644
index 0000000..a30af6f
--- /dev/null
+++ b/ap/app/busybox/src/libbb/update_passwd.c
@@ -0,0 +1,285 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * update_passwd
+ *
+ * update_passwd is a common function for passwd and chpasswd applets;
+ * it is responsible for updating password file (i.e. /etc/passwd or
+ * /etc/shadow) for a given user and password.
+ *
+ * Moved from loginutils/passwd.c by Alexander Shishkin <virtuoso@slind.org>
+ *
+ * Modified to be able to add or delete users, groups and users to/from groups
+ * by Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+#include "libbb.h"
+
+#if ENABLE_SELINUX
+static void check_selinux_update_passwd(const char *username)
+{
+	security_context_t context;
+	char *seuser;
+
+	if (getuid() != (uid_t)0 || is_selinux_enabled() == 0)
+		return;  /* No need to check */
+
+	if (getprevcon_raw(&context) < 0)
+		bb_perror_msg_and_die("getprevcon failed");
+	seuser = strtok(context, ":");
+	if (!seuser)
+		bb_error_msg_and_die("invalid context '%s'", context);
+	if (strcmp(seuser, username) != 0) {
+		if (checkPasswdAccess(PASSWD__PASSWD) != 0)
+			bb_error_msg_and_die("SELinux: access denied");
+	}
+	if (ENABLE_FEATURE_CLEAN_UP)
+		freecon(context);
+}
+#else
+# define check_selinux_update_passwd(username) ((void)0)
+#endif
+
+/*
+ 1) add a user: update_passwd(FILE, USER, REMAINING_PWLINE, NULL)
+    only if CONFIG_ADDUSER=y and applet_name[0] == 'a' like in adduser
+
+ 2) add a group: update_passwd(FILE, GROUP, REMAINING_GRLINE, NULL)
+    only if CONFIG_ADDGROUP=y and applet_name[0] == 'a' like in addgroup
+
+ 3) add a user to a group: update_passwd(FILE, GROUP, NULL, MEMBER)
+    only if CONFIG_FEATURE_ADDUSER_TO_GROUP=y, applet_name[0] == 'a'
+    like in addgroup and member != NULL
+
+ 4) delete a user: update_passwd(FILE, USER, NULL, NULL)
+
+ 5) delete a group: update_passwd(FILE, GROUP, NULL, NULL)
+
+ 6) delete a user from a group: update_passwd(FILE, GROUP, NULL, MEMBER)
+    only if CONFIG_FEATURE_DEL_USER_FROM_GROUP=y and member != NULL
+
+ 7) change user's password: update_passwd(FILE, USER, NEW_PASSWD, NULL)
+    only if CONFIG_PASSWD=y and applet_name[0] == 'p' like in passwd
+    or if CONFIG_CHPASSWD=y and applet_name[0] == 'c' like in chpasswd
+
+ This function does not validate the arguments fed to it
+ so the calling program should take care of that.
+
+ Returns number of lines changed, or -1 on error.
+*/
+int FAST_FUNC update_passwd(const char *filename,
+		const char *name,
+		const char *new_passwd,
+		const char *member)
+{
+#if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
+#define member NULL
+#endif
+	struct stat sb;
+	struct flock lock;
+	FILE *old_fp;
+	FILE *new_fp;
+	char *fnamesfx;
+	char *sfx_char;
+	char *name_colon;
+	unsigned user_len;
+	int old_fd;
+	int new_fd;
+	int i;
+	int changed_lines;
+	int ret = -1; /* failure */
+	/* used as a bool: "are we modifying /etc/shadow?" */
+#if ENABLE_FEATURE_SHADOWPASSWDS
+	const char *shadow = strstr(filename, "shadow");
+#else
+# define shadow NULL
+#endif
+
+	filename = xmalloc_follow_symlinks(filename);
+	if (filename == NULL)
+		return ret;
+
+	check_selinux_update_passwd(name);
+
+	/* New passwd file, "/etc/passwd+" for now */
+	fnamesfx = xasprintf("%s+", filename);
+	sfx_char = &fnamesfx[strlen(fnamesfx)-1];
+	name_colon = xasprintf("%s:", name);
+	user_len = strlen(name_colon);
+
+	if (shadow)
+		old_fp = fopen(filename, "r+");
+	else
+		old_fp = fopen_or_warn(filename, "r+");
+	if (!old_fp) {
+		if (shadow)
+			ret = 0; /* missing shadow is not an error */
+		goto free_mem;
+	}
+	old_fd = fileno(old_fp);
+
+	selinux_preserve_fcontext(old_fd);
+
+	/* 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(fnamesfx, 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("can't create '%s'", fnamesfx);
+	goto close_old_fp;
+
+ created:
+	if (fstat(old_fd, &sb) == 0) {
+		fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
+		fchown(new_fd, sb.st_uid, sb.st_gid);
+	}
+	errno = 0;
+	new_fp = xfdopen_for_write(new_fd);
+
+	/* Backup file is "/etc/passwd-" */
+	*sfx_char = '-';
+	/* Delete old backup */
+	i = (unlink(fnamesfx) && errno != ENOENT);
+	/* Create backup as a hardlink to current */
+	if (i || link(filename, fnamesfx))
+		bb_perror_msg("warning: can't create backup copy '%s'",
+				fnamesfx);
+	*sfx_char = '+';
+
+	/* 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: can't lock '%s'", filename);
+	lock.l_type = F_UNLCK;
+
+	/* Read current password file, write updated /etc/passwd+ */
+	changed_lines = 0;
+	while (1) {
+		char *cp, *line;
+
+		line = xmalloc_fgetline(old_fp);
+		if (!line) /* EOF/error */
+			break;
+		if (strncmp(name_colon, line, user_len) != 0) {
+			fprintf(new_fp, "%s\n", line);
+			goto next;
+		}
+
+		/* We have a match with "name:"... */
+		cp = line + user_len; /* move past name: */
+
+#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
+		if (member) {
+			/* It's actually /etc/group+, not /etc/passwd+ */
+			if (ENABLE_FEATURE_ADDUSER_TO_GROUP
+			 && applet_name[0] == 'a'
+			) {
+				/* Add user to group */
+				fprintf(new_fp, "%s%s%s\n", line,
+					last_char_is(line, ':') ? "" : ",",
+					member);
+				changed_lines++;
+			} else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP
+			/* && applet_name[0] == 'd' */
+			) {
+				/* Delete user from group */
+				char *tmp;
+				const char *fmt = "%s";
+
+				/* find the start of the member list: last ':' */
+				cp = strrchr(line, ':');
+				/* cut it */
+				*cp++ = '\0';
+				/* write the cut line name:passwd:gid:
+				 * or name:!:: */
+				fprintf(new_fp, "%s:", line);
+				/* parse the tokens of the member list */
+				tmp = cp;
+				while ((cp = strsep(&tmp, ",")) != NULL) {
+					if (strcmp(member, cp) != 0) {
+						fprintf(new_fp, fmt, cp);
+						fmt = ",%s";
+					} else {
+						/* found member, skip it */
+						changed_lines++;
+					}
+				}
+				fprintf(new_fp, "\n");
+			}
+		} else
+#endif
+		if ((ENABLE_PASSWD && applet_name[0] == 'p')
+		 || (ENABLE_CHPASSWD && applet_name[0] == 'c')
+		) {
+			/* Change passwd */
+			cp = strchrnul(cp, ':'); /* move past old passwd */
+
+			if (shadow && *cp == ':') {
+				/* /etc/shadow's field 3 (passwd change date) needs updating */
+				/* move past old change date */
+				cp = strchrnul(cp + 1, ':');
+				/* "name:" + "new_passwd" + ":" + "change date" + ":rest of line" */
+				fprintf(new_fp, "%s%s:%u%s\n", name_colon, new_passwd,
+					(unsigned)(time(NULL)) / (24*60*60), cp);
+			} else {
+				/* "name:" + "new_passwd" + ":rest of line" */
+				fprintf(new_fp, "%s%s%s\n", name_colon, new_passwd, cp);
+			}
+			changed_lines++;
+		} /* else delete user or group: skip the line */
+ next:
+		free(line);
+	}
+
+	if (changed_lines == 0) {
+#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
+		if (member) {
+			if (ENABLE_ADDGROUP && applet_name[0] == 'a')
+				bb_error_msg("can't find %s in %s", name, filename);
+			if (ENABLE_DELGROUP && applet_name[0] == 'd')
+				bb_error_msg("can't find %s in %s", member, filename);
+		}
+#endif
+		if ((ENABLE_ADDUSER || ENABLE_ADDGROUP)
+		 && applet_name[0] == 'a' && !member
+		) {
+			/* add user or group */
+			fprintf(new_fp, "%s%s\n", name_colon, new_passwd);
+			changed_lines++;
+		}
+	}
+
+	fcntl(old_fd, F_SETLK, &lock);
+
+	/* We do want all of them to execute, thus | instead of || */
+	errno = 0;
+	if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
+	 || rename(fnamesfx, filename)
+	) {
+		/* At least one of those failed */
+		bb_perror_nomsg();
+		goto unlink_new;
+	}
+	/* Success: ret >= 0 */
+	ret = changed_lines;
+
+ unlink_new:
+	if (ret < 0)
+		unlink(fnamesfx);
+
+ close_old_fp:
+	fclose(old_fp);
+
+ free_mem:
+	free(fnamesfx);
+	free((char *)filename);
+	free(name_colon);
+	return ret;
+}