a8dd8ce6c9
Free the actual struct of the removed entry. Example userdel report: Direct leak of 40 byte(s) in 1 object(s) allocated from: #0 0x55b230efe857 in reallocarray (./src/userdel+0xda857) #1 0x55b230f6041f in mallocarray ./lib/./alloc.h:97:9 #2 0x55b230f6041f in commonio_open ./lib/commonio.c:563:7 #3 0x55b230f39098 in open_files ./src/userdel.c:555:6 #4 0x55b230f39098 in main ./src/userdel.c:1189:2 #5 0x7f9b48c64189 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
1139 lines
21 KiB
C
1139 lines
21 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 1990 - 1994, Julianne Frances Haugh
|
|
* SPDX-FileCopyrightText: 1996 - 2001, Marek Michałkiewicz
|
|
* SPDX-FileCopyrightText: 2001 - 2006, Tomasz Kłoczko
|
|
* SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#ident "$Id$"
|
|
|
|
#include "defines.h"
|
|
#include <assert.h>
|
|
#include <sys/stat.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include <utime.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
|
|
#include "alloc.h"
|
|
#include "nscd.h"
|
|
#include "sssd.h"
|
|
#ifdef WITH_TCB
|
|
#include <tcb.h>
|
|
#endif /* WITH_TCB */
|
|
#include "prototypes.h"
|
|
#include "commonio.h"
|
|
#include "shadowlog_internal.h"
|
|
|
|
/* local function prototypes */
|
|
static int lrename (const char *, const char *);
|
|
static /*@null@*/ /*@dependent@*/FILE *fopen_set_perms (
|
|
const char *name,
|
|
const char *mode,
|
|
const struct stat *sb);
|
|
static int create_backup (const char *, FILE *);
|
|
static void free_linked_list (struct commonio_db *);
|
|
static void add_one_entry (
|
|
struct commonio_db *db,
|
|
/*@owned@*/struct commonio_entry *p);
|
|
static bool name_is_nis (const char *name);
|
|
static int write_all (const struct commonio_db *);
|
|
static /*@dependent@*/ /*@null@*/struct commonio_entry *find_entry_by_name (
|
|
struct commonio_db *,
|
|
const char *);
|
|
static /*@dependent@*/ /*@null@*/struct commonio_entry *next_entry_by_name (
|
|
struct commonio_db *,
|
|
/*@null@*/struct commonio_entry *pos,
|
|
const char *);
|
|
|
|
static int lock_count = 0;
|
|
static bool nscd_need_reload = false;
|
|
|
|
/*
|
|
* Simple rename(P) alternative that attempts to rename to symlink
|
|
* target.
|
|
*/
|
|
int lrename (const char *old, const char *new)
|
|
{
|
|
int res;
|
|
char *r = NULL;
|
|
|
|
#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;
|
|
}
|
|
}
|
|
|
|
res = rename (old, new);
|
|
|
|
#ifdef __GLIBC__
|
|
free (r);
|
|
#endif /* __GLIBC__ */
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
if (fchown (fileno (fp), sb->st_uid, sb->st_gid) != 0) {
|
|
goto fail;
|
|
}
|
|
if (fchmod (fileno (fp), sb->st_mode & 0664) != 0) {
|
|
goto fail;
|
|
}
|
|
|
|
return fp;
|
|
|
|
fail:
|
|
(void) fclose (fp);
|
|
/* fopen_set_perms is used for intermediate files */
|
|
(void) unlink (name);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
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) {
|
|
(void) fclose (bkfp);
|
|
/* FIXME: unlink the backup file? */
|
|
return -1;
|
|
}
|
|
if (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;
|
|
}
|
|
|
|
|
|
static void free_linked_list (struct commonio_db *db)
|
|
{
|
|
struct commonio_entry *p;
|
|
|
|
while (NULL != db->head) {
|
|
p = db->head;
|
|
db->head = p->next;
|
|
|
|
free (p->line);
|
|
|
|
if (NULL != p->eptr) {
|
|
db->ops->free (p->eptr);
|
|
}
|
|
|
|
free (p);
|
|
}
|
|
db->tail = NULL;
|
|
}
|
|
|
|
|
|
int commonio_setname (struct commonio_db *db, const char *name)
|
|
{
|
|
snprintf (db->filename, sizeof (db->filename), "%s", name);
|
|
db->setname = true;
|
|
return 1;
|
|
}
|
|
|
|
|
|
bool commonio_present (const struct commonio_db *db)
|
|
{
|
|
return (access (db->filename, F_OK) == 0);
|
|
}
|
|
|
|
|
|
int do_fcntl_lock (const char *file, bool log, short type)
|
|
{
|
|
int fd;
|
|
struct flock lck = {
|
|
.l_type = type,
|
|
.l_whence = SEEK_SET,
|
|
.l_start = 0,
|
|
.l_len = 0,
|
|
};
|
|
|
|
fd = open (file, O_WRONLY, 0600);
|
|
if (-1 == fd) {
|
|
if (log) {
|
|
(void) fprintf (shadow_logfd, "%s: %s: %s\n",
|
|
shadow_progname, file, strerror (errno));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
fcntl (fd, F_OFD_SETLKW, &lck);
|
|
close(fd);
|
|
return(1);
|
|
}
|
|
|
|
int commonio_lock_nowait (struct commonio_db *db, bool log)
|
|
{
|
|
char* file = NULL;
|
|
char* lock = NULL;
|
|
size_t lock_file_len;
|
|
size_t file_len;
|
|
int err = 0;
|
|
|
|
if (db->locked) {
|
|
return 1;
|
|
}
|
|
file_len = strlen(db->filename) + 11;/* %lu max size */
|
|
lock_file_len = strlen(db->filename) + 6; /* sizeof ".lock" */
|
|
file = MALLOCARRAY(file_len, char);
|
|
if (file == NULL) {
|
|
goto cleanup_ENOMEM;
|
|
}
|
|
lock = MALLOCARRAY(lock_file_len, char);
|
|
if (lock == NULL) {
|
|
goto cleanup_ENOMEM;
|
|
}
|
|
snprintf (file, file_len, "%s.%lu",
|
|
db->filename, (unsigned long) getpid ());
|
|
|
|
if (do_fcntl_lock (db->filename, log, F_WRLCK | F_RDLCK) != 0) {
|
|
db->locked = true;
|
|
lock_count++;
|
|
err = 1;
|
|
}
|
|
cleanup_ENOMEM:
|
|
free(file);
|
|
free(lock);
|
|
return err;
|
|
}
|
|
|
|
|
|
int commonio_lock (struct commonio_db *db)
|
|
{
|
|
int i;
|
|
|
|
#ifdef HAVE_LCKPWDF
|
|
/*
|
|
* Only if the system libc has a real lckpwdf() - the one from
|
|
* lockpw.c calls us and would cause infinite recursion!
|
|
* It is also not used with the prefix option.
|
|
*/
|
|
if (!db->setname) {
|
|
/*
|
|
* 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 (shadow_logfd,
|
|
"%s: Permission denied.\n",
|
|
shadow_progname);
|
|
}
|
|
return 0; /* failure */
|
|
}
|
|
}
|
|
|
|
if (commonio_lock_nowait (db, true) != 0) {
|
|
return 1; /* success */
|
|
}
|
|
|
|
ulckpwdf ();
|
|
return 0; /* failure */
|
|
}
|
|
#endif /* !HAVE_LCKPWDF */
|
|
|
|
/*
|
|
* 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 (shadow_logfd, "%s: Permission denied.\n",
|
|
shadow_progname);
|
|
return 0;
|
|
}
|
|
}
|
|
return 0; /* failure */
|
|
}
|
|
|
|
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");
|
|
sssd_flush_cache (SSSD_DB_PASSWD | SSSD_DB_GROUP);
|
|
nscd_need_reload = false;
|
|
}
|
|
#ifdef HAVE_LCKPWDF
|
|
ulckpwdf ();
|
|
#endif /* HAVE_LCKPWDF */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int commonio_unlock (struct commonio_db *db)
|
|
{
|
|
if (db->isopen) {
|
|
db->readonly = true;
|
|
if (commonio_close (db) == 0) {
|
|
if (db->locked) {
|
|
dec_lock_count ();
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
if (db->locked) {
|
|
db->locked = false;
|
|
|
|
do_fcntl_lock (db->filename, false, F_UNLCK);
|
|
dec_lock_count ();
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Add an entry at the end.
|
|
*
|
|
* defines p->next, p->prev
|
|
* (unfortunately, owned special are not supported)
|
|
*/
|
|
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;
|
|
}
|
|
|
|
|
|
static bool name_is_nis (const char *name)
|
|
{
|
|
return (('+' == name[0]) || ('-' == name[0]));
|
|
}
|
|
|
|
|
|
/*
|
|
* New entries are inserted before the first NIS entry. Order is preserved
|
|
* when db is written out.
|
|
*/
|
|
#ifndef KEEP_NIS_AT_END
|
|
#define KEEP_NIS_AT_END 1
|
|
#endif
|
|
|
|
#if KEEP_NIS_AT_END
|
|
static void add_one_entry_nis (struct commonio_db *db,
|
|
/*@owned@*/struct commonio_entry *newp);
|
|
|
|
/*
|
|
* Insert an entry between the regular entries, and the NIS entries.
|
|
*
|
|
* defines newp->next, newp->prev
|
|
* (unfortunately, owned special are not supported)
|
|
*/
|
|
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);
|
|
}
|
|
#endif /* KEEP_NIS_AT_END */
|
|
|
|
/* Initial buffer size, as well as increment if not sufficient
|
|
(for reading very long lines in group files). */
|
|
#define BUFLEN 4096
|
|
|
|
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 = MALLOCARRAY (buflen, char);
|
|
if (NULL == buf) {
|
|
goto cleanup_ENOMEM;
|
|
}
|
|
|
|
while (db->ops->fgets (buf, buflen, db->fp) == buf) {
|
|
while ( ((cp = strrchr (buf, '\n')) == NULL)
|
|
&& (feof (db->fp) == 0)) {
|
|
size_t len;
|
|
|
|
buflen += BUFLEN;
|
|
cp = REALLOCARRAY (buf, buflen, char);
|
|
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 = MALLOC (struct commonio_entry);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Sort given db according to cmp function (usually compares uids)
|
|
*/
|
|
int
|
|
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 = MALLOCARRAY (n, 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;
|
|
}
|
|
|
|
/*
|
|
* Sort entries in db according to order in another.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* write_all - Write the database to its file.
|
|
*
|
|
* It returns 0 if all the entries could be written correctly.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
|
|
int commonio_close (struct commonio_db *db)
|
|
{
|
|
char buf[1024];
|
|
int errors = 0;
|
|
struct stat sb;
|
|
|
|
if (!db->isopen) {
|
|
errno = EINVAL;
|
|
return 0;
|
|
}
|
|
db->isopen = false;
|
|
|
|
if (!db->changed || db->readonly) {
|
|
if (NULL != db->fp) {
|
|
(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 (db->filename, S_IFREG) != 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 (db->filename, S_IFREG) != 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++;
|
|
}
|
|
|
|
if (fsync (fileno (db->fp)) != 0) {
|
|
errors++;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
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 (shadow_logfd, _("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 = MALLOC (struct commonio_entry);
|
|
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;
|
|
}
|
|
|
|
#ifdef ENABLE_SUBIDS
|
|
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 = MALLOC (struct commonio_entry);
|
|
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;
|
|
}
|
|
#endif /* ENABLE_SUBIDS */
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* commonio_remove - Remove the entry of the given name from the database.
|
|
*/
|
|
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 (shadow_logfd, _("Multiple entries named '%s' in %s. Please fix this with pwck or grpck.\n"), name, db->filename);
|
|
return 0;
|
|
}
|
|
|
|
commonio_del_entry (db, p);
|
|
|
|
free (p->line);
|
|
|
|
if (NULL != p->eptr) {
|
|
db->ops->free (p->eptr);
|
|
}
|
|
|
|
free(p);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* commonio_locate - Find the first entry with the specified name in
|
|
* the database.
|
|
*
|
|
* If found, it returns the entry and set the cursor of the database to
|
|
* that entry.
|
|
*
|
|
* Otherwise, it returns NULL.
|
|
*/
|
|
/*@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;
|
|
}
|
|
|
|
/*
|
|
* commonio_rewind - Restore the database cursor to the first entry.
|
|
*
|
|
* It returns 0 on error, 1 on success.
|
|
*/
|
|
int commonio_rewind (struct commonio_db *db)
|
|
{
|
|
if (!db->isopen) {
|
|
errno = EINVAL;
|
|
return 0;
|
|
}
|
|
db->cursor = NULL;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* commonio_next - Return the next entry of the specified database
|
|
*
|
|
* It returns the next entry, or NULL if no other entries could be found.
|
|
*/
|
|
/*@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;
|
|
}
|
|
|