Commit message: Fix buffer overflow if NULL line is present in db.

If ptr->line == NULL for an entry, the first cycle will exit,
but the second one will happily write past entries buffer.
We actually do not want to exit the first cycle prematurely
on ptr->line == NULL.
Signed-off-by: Tomas Mraz <tmraz@fedoraproject.org>
CVE: CVE-2017-12424
CWE: CWE-787
Vulnerability description: In shadow before 4.5, the newusers tool could be made to manipulate internal data structures in ways unintended by the authors. Malformed input may lead to crashes (with a buffer overflow or other memory corruption) or other unspecified behaviors. This crosses a privilege boundary in, for example, certain web-hosting environments in which a Control Panel allows an unprivileged user account to create subaccounts.

Project: shadow
File: commonio.c

static /*@dependent@*/ /*@null@*/struct commonio_entry *next_entry_by_name (
	struct commonio_db *db,
	/*@null@*/struct commonio_entry *pos,
	const char *name)
{
	struct commonio_entry *p;
	void *ep;

	if (NULL == pos) {
		return NULL;
	}

	for (p = pos; NULL != p; p = p->next) {
		ep = p->eptr;
		if (   (NULL != ep)
		    && (strcmp (db->ops->getname (ep), name) == 0)) {
			break;
		}
	}
	return p;
}

Project: shadow
File: commonio.c

bool commonio_present (const struct commonio_db *db)
{
	return (access (db->filename, F_OK) == 0);
}

Project: shadow
File: commonio.c

static /*@dependent@*/ /*@null@*/struct commonio_entry *find_entry_by_name (
	struct commonio_db *db,
	const char *name)
{
	return next_entry_by_name (db, db->head, name);
}

Project: shadow
File: commonio.c

int commonio_lock (struct commonio_db *db)
{
#ifdef HAVE_LCKPWDF
	/*
	 * only if the system libc has a real lckpwdf() - the one from
	 * lockpw.c calls us and would cause infinite recursion!
	 */

	/*
	 * Call lckpwdf() on the first lock.
	 * If it succeeds, call *_lock() only once
	 * (no retries, it should always succeed).
	 */
	if (0 == lock_count) {
		if (lckpwdf () == -1) {
			if (geteuid () != 0) {
				(void) fprintf (stderr,
				                "%s: Permission denied.\n",
				                Prog);
			}
			return 0;	/* failure */
		}
	}

	if (commonio_lock_nowait (db, true) != 0) {
		return 1;	/* success */
	}

	ulckpwdf ();
	return 0;		/* failure */
#else				/* !HAVE_LCKPWDF */
	int i;

	/*
	 * lckpwdf() not used - do it the old way.
	 */
#ifndef LOCK_TRIES
#define LOCK_TRIES 15
#endif

#ifndef LOCK_SLEEP
#define LOCK_SLEEP 1
#endif
	for (i = 0; i < LOCK_TRIES; i++) {
		if (i > 0) {
			sleep (LOCK_SLEEP);	/* delay between retries */
		}
		if (commonio_lock_nowait (db, i==LOCK_TRIES-1) != 0) {
			return 1;	/* success */
		}
		/* no unnecessary retries on "permission denied" errors */
		if (geteuid () != 0) {
			(void) fprintf (stderr, "%s: Permission denied.\n",
			                Prog);
			return 0;
		}
	}
	return 0;		/* failure */
#endif				/* !HAVE_LCKPWDF */
}

Project: shadow
File: commonio.c

static int write_all (const struct commonio_db *db)
	/*@requires notnull db->fp@*/
{
	const struct commonio_entry *p;
	void *eptr;

	for (p = db->head; NULL != p; p = p->next) {
		if (p->changed) {
			eptr = p->eptr;
			assert (NULL != eptr);
			if (db->ops->put (eptr, db->fp) != 0) {
				return -1;
			}
		} else if (NULL != p->line) {
			if (db->ops->fputs (p->line, db->fp) == EOF) {
				return -1;
			}
			if (putc ('\n', db->fp) == EOF) {
				return -1;
			}
		}
	}
	return 0;
}

Project: shadow
File: commonio.c

void commonio_del_entry (struct commonio_db *db, const struct commonio_entry *p)
{
	if (p == db->cursor) {
		db->cursor = p->next;
	}

	if (NULL != p->prev) {
		p->prev->next = p->next;
	} else {
		db->head = p->next;
	}

	if (NULL != p->next) {
		p->next->prev = p->prev;
	} else {
		db->tail = p->prev;
	}

	db->changed = true;
}

Project: shadow
File: commonio.c

int commonio_sort_wrt (struct commonio_db *shadow,
                       const struct commonio_db *passwd)
{
	struct commonio_entry *head = NULL, *pw_ptr, *spw_ptr;
	const char *name;

	if ((NULL == shadow) || (NULL == shadow->head)) {
		return 0;
	}

	for (pw_ptr = passwd->head; NULL != pw_ptr; pw_ptr = pw_ptr->next) {
		if (NULL == pw_ptr->eptr) {
			continue;
		}
		name = passwd->ops->getname (pw_ptr->eptr);
		for (spw_ptr = shadow->head;
		     NULL != spw_ptr;
		     spw_ptr = spw_ptr->next) {
			if (NULL == spw_ptr->eptr) {
				continue;
			}
			if (strcmp (name, shadow->ops->getname (spw_ptr->eptr))
			    == 0) {
				break;
			}
		}
		if (NULL == spw_ptr) {
			continue;
		}
		commonio_del_entry (shadow, spw_ptr);
		spw_ptr->next = head;
		head = spw_ptr;
	}

	for (spw_ptr = head; NULL != spw_ptr; spw_ptr = head) {
		head = head->next;

		if (NULL != shadow->head) {
			shadow->head->prev = spw_ptr;
		}
		spw_ptr->next = shadow->head;
		shadow->head = spw_ptr;
	}

	shadow->head->prev = NULL;
	shadow->changed = true;

	return 0;
}

Project: shadow
File: commonio.c

int commonio_lock_nowait (struct commonio_db *db, bool log)
{
	char file[1024];
	char lock[1024];

	if (db->locked) {
		return 1;
	}

	snprintf (file, sizeof file, "%s.%lu",
	          db->filename, (unsigned long) getpid ());
	snprintf (lock, sizeof lock, "%s.lock", db->filename);
	if (do_lock_file (file, lock, log) != 0) {
		db->locked = true;
		lock_count++;
		return 1;
	}
	return 0;
}

Project: shadow
File: commonio.c

int commonio_update (struct commonio_db *db, const void *eptr)
{
	struct commonio_entry *p;
	void *nentry;

	if (!db->isopen || db->readonly) {
		errno = EINVAL;
		return 0;
	}
	nentry = db->ops->dup (eptr);
	if (NULL == nentry) {
		errno = ENOMEM;
		return 0;
	}
	p = find_entry_by_name (db, db->ops->getname (eptr));
	if (NULL != p) {
		if (next_entry_by_name (db, p->next, db->ops->getname (eptr)) != NULL) {
			fprintf (stderr, _("Multiple entries named '%s' in %s. Please fix this with pwck or grpck.\n"), db->ops->getname (eptr), db->filename);
			db->ops->free (nentry);
			return 0;
		}
		db->ops->free (p->eptr);
		p->eptr = nentry;
		p->changed = true;
		db->cursor = p;

		db->changed = true;
		return 1;
	}
	/* not found, new entry */
	p = (struct commonio_entry *) malloc (sizeof *p);
	if (NULL == p) {
		db->ops->free (nentry);
		errno = ENOMEM;
		return 0;
	}

	p->eptr = nentry;
	p->line = NULL;
	p->changed = true;

#if KEEP_NIS_AT_END
	add_one_entry_nis (db, p);
#else				/* !KEEP_NIS_AT_END */
	add_one_entry (db, p);
#endif				/* !KEEP_NIS_AT_END */

	db->changed = true;
	return 1;
}

Project: shadow
File: commonio.c

int lrename (const char *old, const char *new)
{
	int res;
	char *r = NULL;

#if defined(S_ISLNK)
#ifndef __GLIBC__
	char resolved_path[PATH_MAX];
#endif				/* !__GLIBC__ */
	struct stat sb;
	if (lstat (new, &sb) == 0 && S_ISLNK (sb.st_mode)) {
#ifdef __GLIBC__ /* now a POSIX.1-2008 feature */
		r = realpath (new, NULL);
#else				/* !__GLIBC__ */
		r = realpath (new, resolved_path);
#endif				/* !__GLIBC__ */
		if (NULL == r) {
			perror ("realpath in lrename()");
		} else {
			new = r;
		}
	}
#endif				/* S_ISLNK */

	res = rename (old, new);

#ifdef __GLIBC__
	if (NULL != r) {
		free (r);
	}
#endif				/* __GLIBC__ */

	return res;
}

Project: shadow
File: commonio.c

static /*@null@*/ /*@dependent@*/FILE *fopen_set_perms (
	const char *name,
	const char *mode,
	const struct stat *sb)
{
	FILE *fp;
	mode_t mask;

	mask = umask (0777);
	fp = fopen (name, mode);
	(void) umask (mask);
	if (NULL == fp) {
		return NULL;
	}

#ifdef HAVE_FCHOWN
	if (fchown (fileno (fp), sb->st_uid, sb->st_gid) != 0) {
		goto fail;
	}
#else				/* !HAVE_FCHOWN */
	if (chown (name, sb->st_mode) != 0) {
		goto fail;
	}
#endif				/* !HAVE_FCHOWN */

#ifdef HAVE_FCHMOD
	if (fchmod (fileno (fp), sb->st_mode & 0664) != 0) {
		goto fail;
	}
#else				/* !HAVE_FCHMOD */
	if (chmod (name, sb->st_mode & 0664) != 0) {
		goto fail;
	}
#endif				/* !HAVE_FCHMOD */
	return fp;

      fail:
	(void) fclose (fp);
	/* fopen_set_perms is used for intermediate files */
	(void) unlink (name);
	return NULL;
}

Project: shadow
File: commonio.c

int commonio_rewind (struct commonio_db *db)
{
	if (!db->isopen) {
		errno = EINVAL;
		return 0;
	}
	db->cursor = NULL;
	return 1;
}

Project: shadow
File: commonio.c

/*@observer@*/ /*@null@*/const void *commonio_next (struct commonio_db *db)
{
	void *eptr;

	if (!db->isopen) {
		errno = EINVAL;
		return 0;
	}
	if (NULL == db->cursor) {
		db->cursor = db->head;
	} else {
		db->cursor = db->cursor->next;
	}

	while (NULL != db->cursor) {
		eptr = db->cursor->eptr;
		if (NULL != eptr) {
			return eptr;
		}

		db->cursor = db->cursor->next;
	}
	return NULL;
}

Project: shadow
File: commonio.c

commonio_sort (struct commonio_db *db, int (*cmp) (const void *, const void *))
{
	struct commonio_entry **entries, *ptr;
	size_t n = 0, i;
#if KEEP_NIS_AT_END
	struct commonio_entry *nis = NULL;
#endif

	for (ptr = db->head;
	        (NULL != ptr)
#if KEEP_NIS_AT_END
	     && ((NULL == ptr->line)
	         || (('+' != ptr->line[0])
	             && ('-' != ptr->line[0])))
#endif
	     ;
	     ptr = ptr->next) {
		n++;
	}
#if KEEP_NIS_AT_END
	if (NULL != ptr) {
		nis = ptr;
	}
#endif

	if (n <= 1) {
		return 0;
	}

	entries = malloc (n * sizeof (struct commonio_entry *));
	if (entries == NULL) {
		return -1;
	}

	n = 0;
	for (ptr = db->head;
#if KEEP_NIS_AT_END
	     nis != ptr;
#else
	     NULL != ptr;
#endif
/*@ -nullderef @*/
	     ptr = ptr->next
/*@ +nullderef @*/
	    ) {
		entries[n] = ptr;
		n++;
	}
	qsort (entries, n, sizeof (struct commonio_entry *), cmp);

	/* Take care of the head and tail separately */
	db->head = entries[0];
	n--;
#if KEEP_NIS_AT_END
	if (NULL == nis)
#endif
	{
		db->tail = entries[n];
	}
	db->head->prev = NULL;
	db->head->next = entries[1];
	entries[n]->prev = entries[n - 1];
#if KEEP_NIS_AT_END
	entries[n]->next = nis;
#else
	entries[n]->next = NULL;
#endif

	/* Now other elements have prev and next entries */
	for (i = 1; i < n; i++) {
		entries[i]->prev = entries[i - 1];
		entries[i]->next = entries[i + 1];
	}

	free (entries);
	db->changed = true;

	return 0;
}

Project: shadow
File: commonio.c

int commonio_setname (struct commonio_db *db, const char *name)
{
	snprintf (db->filename, sizeof (db->filename), "%s", name);
	return 1;
}

Project: shadow
File: commonio.c

static int do_lock_file (const char *file, const char *lock, bool log)
{
	int fd;
	pid_t pid;
	ssize_t len;
	int retval;
	char buf[32];

	fd = open (file, O_CREAT | O_EXCL | O_WRONLY, 0600);
	if (-1 == fd) {
		if (log) {
			(void) fprintf (stderr,
			                "%s: %s: %s\n",
			                Prog, file, strerror (errno));
		}
		return 0;
	}

	pid = getpid ();
	snprintf (buf, sizeof buf, "%lu", (unsigned long) pid);
	len = (ssize_t) strlen (buf) + 1;
	if (write (fd, buf, (size_t) len) != len) {
		if (log) {
			(void) fprintf (stderr,
			                "%s: %s: %s\n",
			                Prog, file, strerror (errno));
		}
		(void) close (fd);
		unlink (file);
		return 0;
	}
	close (fd);

	if (link (file, lock) == 0) {
		retval = check_link_count (file);
		if ((0==retval) && log) {
			(void) fprintf (stderr,
			                "%s: %s: lock file already used\n",
			                Prog, file);
		}
		unlink (file);
		return retval;
	}

	fd = open (lock, O_RDWR);
	if (-1 == fd) {
		if (log) {
			(void) fprintf (stderr,
			                "%s: %s: %s\n",
			                Prog, lock, strerror (errno));
		}
		unlink (file);
		errno = EINVAL;
		return 0;
	}
	len = read (fd, buf, sizeof (buf) - 1);
	close (fd);
	if (len <= 0) {
		if (log) {
			(void) fprintf (stderr,
			                "%s: existing lock file %s without a PID\n",
			                Prog, lock);
		}
		unlink (file);
		errno = EINVAL;
		return 0;
	}
	buf[len] = '\0';
	if (get_pid (buf, &pid) == 0) {
		if (log) {
			(void) fprintf (stderr,
			                "%s: existing lock file %s with an invalid PID '%s'\n",
			                Prog, lock, buf);
		}
		unlink (file);
		errno = EINVAL;
		return 0;
	}
	if (kill (pid, 0) == 0) {
		if (log) {
			(void) fprintf (stderr,
			                "%s: lock %s already used by PID %lu\n",
			                Prog, lock, (unsigned long) pid);
		}
		unlink (file);
		errno = EEXIST;
		return 0;
	}
	if (unlink (lock) != 0) {
		if (log) {
			(void) fprintf (stderr,
			                "%s: cannot get lock %s: %s\n",
			                Prog, lock, strerror (errno));
		}
		unlink (file);
		return 0;
	}

	retval = 0;
	if (link (file, lock) == 0) {
		retval = check_link_count (file);
		if ((0==retval) && log) {
			(void) fprintf (stderr,
			                "%s: %s: lock file already used\n",
			                Prog, file);
		}
	} else {
		if (log) {
			(void) fprintf (stderr,
			                "%s: cannot get lock %s: %s\n",
			                Prog, lock, strerror (errno));
		}
	}

	unlink (file);
	return retval;
}

Project: shadow
File: commonio.c

static void dec_lock_count (void)
{
	if (lock_count > 0) {
		lock_count--;
		if (lock_count == 0) {
			/* Tell nscd when lock count goes to zero,
			   if any of the files were changed.  */
			if (nscd_need_reload) {
				nscd_flush_cache ("passwd");
				nscd_flush_cache ("group");
				nscd_need_reload = false;
			}
#ifdef HAVE_LCKPWDF
			ulckpwdf ();
#endif				/* HAVE_LCKPWDF */
		}
	}
}

Project: shadow
File: commonio.c

/*@observer@*/ /*@null@*/const void *commonio_locate (struct commonio_db *db, const char *name)
{
	struct commonio_entry *p;

	if (!db->isopen) {
		errno = EINVAL;
		return NULL;
	}
	p = find_entry_by_name (db, name);
	if (NULL == p) {
		errno = ENOENT;
		return NULL;
	}
	db->cursor = p;
	return p->eptr;
}

Project: shadow
File: commonio.c

int commonio_remove (struct commonio_db *db, const char *name)
{
	struct commonio_entry *p;

	if (!db->isopen || db->readonly) {
		errno = EINVAL;
		return 0;
	}
	p = find_entry_by_name (db, name);
	if (NULL == p) {
		errno = ENOENT;
		return 0;
	}
	if (next_entry_by_name (db, p->next, name) != NULL) {
		fprintf (stderr, _("Multiple entries named '%s' in %s. Please fix this with pwck or grpck.\n"), name, db->filename);
		return 0;
	}

	commonio_del_entry (db, p);

	if (NULL != p->line) {
		free (p->line);
	}

	if (NULL != p->eptr) {
		db->ops->free (p->eptr);
	}

	return 1;
}

Project: shadow
File: commonio.c

static void add_one_entry_nis (struct commonio_db *db,
                               /*@owned@*/struct commonio_entry *newp)
{
	struct commonio_entry *p;

	for (p = db->head; NULL != p; p = p->next) {
		if (name_is_nis (p->eptr ? db->ops->getname (p->eptr)
		                         : p->line)) {
			/*@-mustfreeonly@*/
			newp->next = p;
			newp->prev = p->prev;
			/*@=mustfreeonly@*/
			if (NULL != p->prev) {
				p->prev->next = newp;
			} else {
				db->head = newp;
			}
			p->prev = newp;
			return;
		}
	}
	add_one_entry (db, newp);
}

Project: shadow
File: commonio.c

int commonio_append (struct commonio_db *db, const void *eptr)
{
	struct commonio_entry *p;
	void *nentry;

	if (!db->isopen || db->readonly) {
		errno = EINVAL;
		return 0;
	}
	nentry = db->ops->dup (eptr);
	if (NULL == nentry) {
		errno = ENOMEM;
		return 0;
	}
	/* new entry */
	p = (struct commonio_entry *) malloc (sizeof *p);
	if (NULL == p) {
		db->ops->free (nentry);
		errno = ENOMEM;
		return 0;
	}

	p->eptr = nentry;
	p->line = NULL;
	p->changed = true;
	add_one_entry (db, p);

	db->changed = true;
	return 1;
}

Project: shadow
File: commonio.c

static int create_backup (const char *backup, FILE * fp)
{
	struct stat sb;
	struct utimbuf ub;
	FILE *bkfp;
	int c;

	if (fstat (fileno (fp), &sb) != 0) {
		return -1;
	}

	bkfp = fopen_set_perms (backup, "w", &sb);
	if (NULL == bkfp) {
		return -1;
	}

	/* TODO: faster copy, not one-char-at-a-time.  --marekm */
	c = 0;
	if (fseek (fp, 0, SEEK_SET) == 0) {
		while ((c = getc (fp)) != EOF) {
			if (putc (c, bkfp) == EOF) {
				break;
			}
		}
	}
	if ((c != EOF) || (ferror (fp) != 0) || (fflush (bkfp) != 0)) {
		(void) fclose (bkfp);
		/* FIXME: unlink the backup file? */
		return -1;
	}
	if (   (fsync (fileno (bkfp)) != 0)
	    || (fclose (bkfp) != 0)) {
		/* FIXME: unlink the backup file? */
		return -1;
	}

	ub.actime = sb.st_atime;
	ub.modtime = sb.st_mtime;
	(void) utime (backup, &ub);
	return 0;
}

Project: shadow
File: commonio.c

int commonio_close (struct commonio_db *db)
	/*@requires notnull db->fp@*/
{
	char buf[1024];
	int errors = 0;
	struct stat sb;

	if (!db->isopen) {
		errno = EINVAL;
		return 0;
	}
	db->isopen = false;

	if (!db->changed || db->readonly) {
		(void) fclose (db->fp);
		db->fp = NULL;
		goto success;
	}

	if ((NULL != db->ops->close_hook) && (db->ops->close_hook () == 0)) {
		goto fail;
	}

	memzero (&sb, sizeof sb);
	if (NULL != db->fp) {
		if (fstat (fileno (db->fp), &sb) != 0) {
			(void) fclose (db->fp);
			db->fp = NULL;
			goto fail;
		}

		/*
		 * Create backup file.
		 */
		snprintf (buf, sizeof buf, "%s-", db->filename);

#ifdef WITH_SELINUX
		if (set_selinux_file_context (buf) != 0) {
			errors++;
		}
#endif
		if (create_backup (buf, db->fp) != 0) {
			errors++;
		}

		if (fclose (db->fp) != 0) {
			errors++;
		}

#ifdef WITH_SELINUX
		if (reset_selinux_file_context () != 0) {
			errors++;
		}
#endif
		if (errors != 0) {
			db->fp = NULL;
			goto fail;
		}
	} else {
		/*
		 * Default permissions for new [g]shadow files.
		 */
		sb.st_mode = db->st_mode;
		sb.st_uid = db->st_uid;
		sb.st_gid = db->st_gid;
	}

	snprintf (buf, sizeof buf, "%s+", db->filename);

#ifdef WITH_SELINUX
	if (set_selinux_file_context (buf) != 0) {
		errors++;
	}
#endif

	db->fp = fopen_set_perms (buf, "w", &sb);
	if (NULL == db->fp) {
		goto fail;
	}

	if (write_all (db) != 0) {
		errors++;
	}

	if (fflush (db->fp) != 0) {
		errors++;
	}
#ifdef HAVE_FSYNC
	if (fsync (fileno (db->fp)) != 0) {
		errors++;
	}
#else				/* !HAVE_FSYNC */
	sync ();
#endif				/* !HAVE_FSYNC */
	if (fclose (db->fp) != 0) {
		errors++;
	}

	db->fp = NULL;

	if (errors != 0) {
		unlink (buf);
		goto fail;
	}

	if (lrename (buf, db->filename) != 0) {
		goto fail;
	}

#ifdef WITH_SELINUX
	if (reset_selinux_file_context () != 0) {
		goto fail;
	}
#endif

	nscd_need_reload = true;
	goto success;
      fail:
	errors++;
      success:

	free_linked_list (db);
	return errors == 0;
}

Project: shadow
File: commonio.c

int commonio_open (struct commonio_db *db, int mode)
{
	char *buf;
	char *cp;
	char *line;
	struct commonio_entry *p;
	void *eptr = NULL;
	int flags = mode;
	size_t buflen;
	int fd;
	int saved_errno;

	mode &= ~O_CREAT;

	if (   db->isopen
	    || (   (O_RDONLY != mode)
	        && (O_RDWR != mode))) {
		errno = EINVAL;
		return 0;
	}
	db->readonly = (mode == O_RDONLY);
	if (!db->readonly && !db->locked) {
		errno = EACCES;
		return 0;
	}

	db->head = NULL;
	db->tail = NULL;
	db->cursor = NULL;
	db->changed = false;

	fd = open (db->filename,
	             (db->readonly ? O_RDONLY : O_RDWR)
	           | O_NOCTTY | O_NONBLOCK | O_NOFOLLOW);
	saved_errno = errno;
	db->fp = NULL;
	if (fd >= 0) {
#ifdef WITH_TCB
		if (tcb_is_suspect (fd) != 0) {
			(void) close (fd);
			errno = EINVAL;
			return 0;
		}
#endif				/* WITH_TCB */
		db->fp = fdopen (fd, db->readonly ? "r" : "r+");
		saved_errno = errno;
		if (NULL == db->fp) {
			(void) close (fd);
		}
	}
	errno = saved_errno;

	/*
	 * If O_CREAT was specified and the file didn't exist, it will be
	 * created by commonio_close().  We have no entries to read yet.  --marekm
	 */
	if (NULL == db->fp) {
		if (((flags & O_CREAT) != 0) && (ENOENT == errno)) {
			db->isopen = true;
			return 1;
		}
		return 0;
	}

	/* Do not inherit fd in spawned processes (e.g. nscd) */
	fcntl (fileno (db->fp), F_SETFD, FD_CLOEXEC);

	buflen = BUFLEN;
	buf = (char *) malloc (buflen);
	if (NULL == buf) {
		goto cleanup_ENOMEM;
	}

	while (db->ops->fgets (buf, (int) buflen, db->fp) == buf) {
		while (   ((cp = strrchr (buf, '\n')) == NULL)
		       && (feof (db->fp) == 0)) {
			size_t len;

			buflen += BUFLEN;
			cp = (char *) realloc (buf, buflen);
			if (NULL == cp) {
				goto cleanup_buf;
			}
			buf = cp;
			len = strlen (buf);
			if (db->ops->fgets (buf + len,
			                    (int) (buflen - len),
			                    db->fp) == NULL) {
				goto cleanup_buf;
			}
		}
		cp = strrchr (buf, '\n');
		if (NULL != cp) {
			*cp = '\0';
		}

		line = strdup (buf);
		if (NULL == line) {
			goto cleanup_buf;
		}

		if (name_is_nis (line)) {
			eptr = NULL;
		} else {
			eptr = db->ops->parse (line);
			if (NULL != eptr) {
				eptr = db->ops->dup (eptr);
				if (NULL == eptr) {
					goto cleanup_line;
				}
			}
		}

		p = (struct commonio_entry *) malloc (sizeof *p);
		if (NULL == p) {
			goto cleanup_entry;
		}

		p->eptr = eptr;
		p->line = line;
		p->changed = false;

		add_one_entry (db, p);
	}

	free (buf);

	if (ferror (db->fp) != 0) {
		goto cleanup_errno;
	}

	if ((NULL != db->ops->open_hook) && (db->ops->open_hook () == 0)) {
		goto cleanup_errno;
	}

	db->isopen = true;
	return 1;

      cleanup_entry:
	if (NULL != eptr) {
		db->ops->free (eptr);
	}
      cleanup_line:
	free (line);
      cleanup_buf:
	free (buf);
      cleanup_ENOMEM:
	errno = ENOMEM;
      cleanup_errno:
	saved_errno = errno;
	free_linked_list (db);
	fclose (db->fp);
	db->fp = NULL;
	errno = saved_errno;
	return 0;
}

Project: shadow
File: commonio.c

static int check_link_count (const char *file)
{
	struct stat sb;

	if (stat (file, &sb) != 0) {
		return 0;
	}

	if (sb.st_nlink != 2) {
		return 0;
	}

	return 1;
}

Project: shadow
File: commonio.c

static bool name_is_nis (const char *name)
{
	return (('+' == name[0]) || ('-' == name[0]));
}

Project: shadow
File: commonio.c

int commonio_unlock (struct commonio_db *db)
{
	char lock[1024];

	if (db->isopen) {
		db->readonly = true;
		if (commonio_close (db) == 0) {
			if (db->locked) {
				dec_lock_count ();
			}
			return 0;
		}
	}
	if (db->locked) {
		/*
		 * Unlock in reverse order: remove the lock file,
		 * then call ulckpwdf() (if used) on last unlock.
		 */
		db->locked = false;
		snprintf (lock, sizeof lock, "%s.lock", db->filename);
		unlink (lock);
		dec_lock_count ();
		return 1;
	}
	return 0;
}

Project: shadow
File: commonio.c

static void add_one_entry (struct commonio_db *db,
                           /*@owned@*/struct commonio_entry *p)
{
	/*@-mustfreeonly@*/
	p->next = NULL;
	p->prev = db->tail;
	/*@=mustfreeonly@*/
	if (NULL == db->head) {
		db->head = p;
	}
	if (NULL != db->tail) {
		db->tail->next = p;
	}
	db->tail = p;
}

Project: shadow
File: commonio.c

static void free_linked_list (struct commonio_db *db)
{
	struct commonio_entry *p;

	while (NULL != db->head) {
		p = db->head;
		db->head = p->next;

		if (NULL != p->line) {
			free (p->line);
		}

		if (NULL != p->eptr) {
			db->ops->free (p->eptr);
		}

		free (p);
	}
	db->tail = NULL;
}