shadow/lib/commonio.c

1090 lines
21 KiB
C
Raw Normal View History

/*
* Copyright (c) 1990 - 1994, Julianne Frances Haugh
* Copyright (c) 1996 - 2001, Marek Michałkiewicz
* Copyright (c) 2001 - 2006, Tomasz Kłoczko
* Copyright (c) 2007 - 2008, Nicolas François
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the copyright holders or contributors may not be used to
* endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <config.h>
#ident "$Id$"
#include "defines.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 "nscd.h"
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
static security_context_t old_context = NULL;
#endif
#include "commonio.h"
/* local function prototypes */
static int lrename (const char *, const char *);
static int check_link_count (const char *file);
static int do_lock_file (const char *file, const char *lock);
static FILE *fopen_set_perms (const char *, const char *, const struct stat *);
static int create_backup (const char *, FILE *);
static void free_linked_list (struct commonio_db *);
static void add_one_entry (struct commonio_db *, struct commonio_entry *);
static bool name_is_nis (const char *name);
static int write_all (const struct commonio_db *);
static struct commonio_entry *find_entry_by_name (struct commonio_db *,
const char *);
static struct commonio_entry *next_entry_by_name (struct commonio_db *,
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)
{
char resolved_path[PATH_MAX];
int res;
#if defined(S_ISLNK)
struct stat sb;
if (lstat (new, &sb) == 0 && S_ISLNK (sb.st_mode)) {
if (realpath (new, resolved_path) == NULL) {
perror ("realpath in lrename()");
} else {
new = resolved_path;
}
}
#endif
res = rename (old, new);
return res;
}
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;
}
static int do_lock_file (const char *file, const char *lock)
{
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) {
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) {
(void) close (fd);
unlink (file);
return 0;
}
close (fd);
if (link (file, lock) == 0) {
retval = check_link_count (file);
unlink (file);
return retval;
}
fd = open (lock, O_RDWR);
if (-1 == fd) {
unlink (file);
errno = EINVAL;
return 0;
}
len = read (fd, buf, sizeof (buf) - 1);
close (fd);
if (len <= 0) {
unlink (file);
errno = EINVAL;
return 0;
}
buf[len] = '\0';
2009-04-11 21:38:00 +05:30
/* FIXME: use a get_pid */
pid = strtol (buf, (char **) 0, 10);
if (0 == pid) {
unlink (file);
errno = EINVAL;
return 0;
}
if (kill (pid, 0) == 0) {
unlink (file);
errno = EEXIST;
return 0;
}
if (unlink (lock) != 0) {
unlink (file);
return 0;
}
retval = 0;
if ((link (file, lock) == 0) && (check_link_count (file) != 0)) {
retval = 1;
}
unlink (file);
return retval;
}
static 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
if (chown (name, sb->st_mode) != 0) {
goto fail;
}
#endif
#ifdef HAVE_FCHMOD
if (fchmod (fileno (fp), sb->st_mode & 0664) != 0) {
goto fail;
}
#else
if (chmod (name, sb->st_mode & 0664) != 0) {
goto fail;
}
#endif
return fp;
fail:
fclose (fp);
unlink (name);
return NULL;
}
static int create_backup (const char *backup, FILE * fp)
{
struct stat sb;
struct utimbuf ub;
FILE *bkfp;
int c;
mode_t mask;
if (fstat (fileno (fp), &sb) != 0) {
return -1;
}
mask = umask (077);
bkfp = fopen (backup, "w");
(void) umask (mask);
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)) {
fclose (bkfp);
return -1;
}
if ( (fsync (fileno (bkfp)) != 0)
|| (fclose (bkfp) != 0)) {
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;
if (NULL != p->line) {
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);
return 1;
}
bool commonio_present (const struct commonio_db *db)
{
return (access (db->filename, F_OK) == 0);
}
int commonio_lock_nowait (struct commonio_db *db)
{
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) != 0) {
db->locked = true;
lock_count++;
return 1;
}
return 0;
}
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) {
return 0; /* failure */
}
}
if (commonio_lock_nowait (db) != 0) {
return 1; /* success */
}
ulckpwdf ();
return 0; /* failure */
#else
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) != 0) {
return 1; /* success */
}
/* no unnecessary retries on "permission denied" errors */
if (geteuid () != 0) {
return 0;
}
}
return 0; /* failure */
#endif
}
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
}
}
}
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;
}
static void add_one_entry (struct commonio_db *db, struct commonio_entry *p)
{
p->next = NULL;
p->prev = db->tail;
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 *, struct commonio_entry *);
/*
* Insert an entry between the regular entries, and the NIS entries.
*/
static void
add_one_entry_nis (struct commonio_db *db, 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)) {
newp->next = p;
newp->prev = p->prev;
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;
int flags = mode;
int buflen;
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 = db->tail = db->cursor = NULL;
db->changed = false;
db->fp = fopen (db->filename, db->readonly ? "r" : "r+");
/*
* 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);
#ifdef WITH_SELINUX
db->scontext = NULL;
if ((is_selinux_enabled () > 0) && (!db->readonly)) {
if (fgetfilecon (fileno (db->fp), &db->scontext) < 0) {
goto cleanup_errno;
}
}
#endif
buflen = BUFLEN;
buf = (char *) malloc (buflen);
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)) {
int 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, 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);
#ifdef WITH_SELINUX
if (db->scontext != NULL) {
freecon (db->scontext);
db->scontext = NULL;
}
#endif
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;
int n = 0, i;
for (ptr = db->head; NULL != ptr; ptr = ptr->next) {
n++;
}
if (n <= 1) {
return 0;
}
entries = malloc (n * sizeof (struct commonio_entry *));
if (entries == NULL) {
return -1;
}
n = 0;
for (ptr = db->head; NULL != ptr; ptr = ptr->next) {
entries[n++] = ptr;
}
qsort (entries, n, sizeof (struct commonio_entry *), cmp);
db->head = entries[0];
db->tail = entries[--n];
db->head->prev = NULL;
db->head->next = entries[1];
db->tail->prev = entries[n - 1];
db->tail->next = NULL;
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, 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 (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)
{
const struct commonio_entry *p;
void *eptr;
for (p = db->head; NULL != p; p = p->next) {
if (p->changed) {
eptr = p->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) {
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) {
fclose (db->fp);
db->fp = NULL;
goto fail;
}
#ifdef WITH_SELINUX
if (db->scontext != NULL) {
if (getfscreatecon (&old_context) < 0) {
errors++;
goto fail;
}
if (setfscreatecon (db->scontext) < 0) {
errors++;
goto fail;
}
}
#endif
/*
* Create backup file.
*/
snprintf (buf, sizeof buf, "%s-", db->filename);
if (create_backup (buf, db->fp) != 0) {
errors++;
}
if (fclose (db->fp) != 0) {
errors++;
}
if (errors != 0) {
db->fp = NULL;
goto fail;
}
} else {
/*
* Default permissions for new [g]shadow files.
* (passwd and group always exist...)
*/
sb.st_mode = 0400;
sb.st_uid = 0;
sb.st_gid = 0;
}
snprintf (buf, sizeof buf, "%s+", db->filename);
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
sync ();
#endif
if (fclose (db->fp) != 0) {
errors++;
}
db->fp = NULL;
if (errors != 0) {
unlink (buf);
goto fail;
}
if (lrename (buf, db->filename) != 0) {
goto fail;
}
nscd_need_reload = true;
goto success;
fail:
errors++;
success:
#ifdef WITH_SELINUX
if (db->scontext != NULL) {
if (setfscreatecon (old_context) < 0) {
errors++;
}
if (NULL != old_context) {
freecon (old_context);
old_context = NULL;
}
freecon (db->scontext);
db->scontext = NULL;
}
#endif
free_linked_list (db);
return errors == 0;
}
static struct commonio_entry *next_entry_by_name (struct commonio_db *db,
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 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 (stderr, _("Multiple entries named '%s' in %s. Please fix this with pwck or grpck.\n"), db->ops->getname (eptr), db->filename);
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
add_one_entry (db, p);
#endif
db->changed = true;
return 1;
}
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 (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;
}
/*
* 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.
*/
/*@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.
*/
/*@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;
}