Exemplo n.º 1
0
/*
 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

 8) delete a user from all groups: update_passwd(FILE, NULL, NULL, MEMBER)

 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;
	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;

	if (name)
		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 ? name : "");

	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 (!name && member) {
			/* Delete member from all groups */
			/* line is "GROUP:PASSWD:[member1[,member2]...]" */
			unsigned member_len = strlen(member);
			char *list = strrchr(line, ':');
			while (list) {
				list++;
 next_list_element:
				if (is_prefixed_with(list, member)) {
					char c;
					changed_lines++;
					c = list[member_len];
					if (c == '\0') {
						if (list[-1] == ',')
							list--;
						*list = '\0';
						break;
					}
					if (c == ',') {
						overlapping_strcpy(list, list + member_len + 1);
						goto next_list_element;
					}
					changed_lines--;
				}
				list = strchr(list, ',');
			}
			fprintf(new_fp, "%s\n", line);
			goto next;
		}

		cp = is_prefixed_with(line, name_colon);
		if (!cp) {
			fprintf(new_fp, "%s\n", line);
			goto next;
		}

		/* We have a match with "name:"... */
		/* cp points 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;
}
Exemplo n.º 2
0
/* if fn is NULL then input is stdin and output is stdout */
static int convert(char *fn, int ConvType)
{
	int c, fd;
	FILE *in, *out;

	if (fn != NULL) {
		in = bb_xfopen(fn, "rw");
		/*
		The file is then created with mode read/write and
		permissions 0666 for glibc 2.0.6 and earlier or
		0600  for glibc  2.0.7 and  later. 
		*/
		snprintf(tempFn, sizeof(tempFn), "%sXXXXXX", fn);
		/*
		sizeof tempFn is 4096, so it should be big enough to hold the full
		path. however if the output is truncated the subsequent call to
		mkstemp would fail.
		*/
		if ((fd = mkstemp(&tempFn[0])) == -1 || chmod(tempFn, 0600) == -1) {
			bb_perror_nomsg_and_die();
		}
		out = fdopen(fd, "w+");
		if (!out) {
			close(fd);
			remove(tempFn);
		}
	} else {
		in = stdin;
		out = stdout;
	}

	while ((c = fgetc(in)) != EOF) {
		if (c == '\r')
			continue;
		if (c == '\n') {
			if (ConvType == CT_UNIX2DOS)
				fputc('\r', out);
			fputc('\n', out);
			continue;
		}
		fputc(c, out);
	}

	if (fn != NULL) {
		if (fclose(in) < 0 || fclose(out) < 0) {
			bb_perror_nomsg();
			remove(tempFn);
			return -2;
	    }
	
		/* Assume they are both on the same filesystem (which
		* should be true since we put them into the same directory
		* so we _should_ be ok, but you never know... */
		if (rename(tempFn, fn) < 0) {
			bb_perror_msg("cannot rename '%s' as '%s'", tempFn, fn);
			return -1;
	    }
	}

	return 0;
}
// if fn is NULL then input is stdin and output is stdout
static int convert(char *fn, int ConvType)
{
	int c, fd;
	struct timeval tv;
	char tempFn[BUFSIZ];
	static bb_uint64_t value=0;
	FILE *in = stdin, *out = stdout;

	if (fn != NULL) {
		in = bb_xfopen(fn, "rw");
		safe_strncpy(tempFn, fn, sizeof(tempFn));
		c = strlen(tempFn);
		tempFn[c] = '.';
		while(1) {
		    /* tempFn is BUFSIZ so the last addressable spot it BUFSIZ-1.
		     * The loop increments by 2. So this must check for BUFSIZ-3. */
		    if (c >=BUFSIZ-3)
			bb_error_msg_and_die("unique name not found");
		    /* Get some semi random stuff to try and make a
		     * random filename based (and in the same dir as)
		     * the input file... */
		    gettimeofday (&tv, NULL);
		    value += ((bb_uint64_t) tv.tv_usec << 16) ^ tv.tv_sec ^ getpid ();
		    tempFn[++c] = letters[value % 62];
		    tempFn[c+1] = '\0';
		    value /= 62;

		    if ((fd = open(tempFn, O_RDWR | O_CREAT | O_EXCL, 0600)) < 0 ) {
			continue;
		    }
		    out = fdopen(fd, "w+");
		    if (!out) {
			close(fd);
			remove(tempFn);
			continue;
		    }
		    break;
		}
	}

	while ((c = fgetc(in)) != EOF) {
		if (c == '\r') {
			if ((ConvType == CT_UNIX2DOS) && (fn != NULL)) {
				// file is alredy in DOS format so it is not necessery to touch it
				remove(tempFn);
				if (fclose(in) < 0 || fclose(out) < 0) {
					bb_perror_nomsg();
					return -2;
				}
				return 0;
			}
			if (!ConvType)
				ConvType = CT_DOS2UNIX;
			break;
		}
		if (c == '\n') {
			if ((ConvType == CT_DOS2UNIX) && (fn != NULL)) {
				// file is alredy in UNIX format so it is not necessery to touch it
				remove(tempFn);
				if ((fclose(in) < 0) || (fclose(out) < 0)) {
					bb_perror_nomsg();
					return -2;
				}
				return 0;
			}
			if (!ConvType) {
				ConvType = CT_UNIX2DOS;
			}
			if (ConvType == CT_UNIX2DOS) {
				fputc('\r', out);
			}
			fputc('\n', out);
			break;
		}
		fputc(c, out);
	}
	if (c != EOF)
		while ((c = fgetc(in)) != EOF) {
			if (c == '\r')
				continue;
			if (c == '\n') {
				if (ConvType == CT_UNIX2DOS)
					fputc('\r', out);
				fputc('\n', out);
				continue;
			}
		fputc(c, out);
	}

	if (fn != NULL) {
	    if (fclose(in) < 0 || fclose(out) < 0) {
		bb_perror_nomsg();
		remove(tempFn);
		return -2;
	    }

	    /* Assume they are both on the same filesystem (which
	     * should be true since we put them into the same directory
	     * so we _should_ be ok, but you never know... */
	    if (rename(tempFn, fn) < 0) {
		bb_perror_msg("unable to rename '%s' as '%s'", tempFn, fn);
		return -1;
	    }
	}

	return 0;
}