6a6a253885
Added doc dir. and manual as .pdf restored deleted files - yep I screwed the pooch.
767 lines
14 KiB
C
767 lines
14 KiB
C
/*****************************************************************************
|
|
*
|
|
* $Id: commonio.c,v 1.3 2005/07/14 19:25:58 mbse Exp $
|
|
* Purpose ...............: MBSE BBS Shadow Password Suite
|
|
* Original Source .......: Shadow Password Suite
|
|
* Original Copyright ....: Julianne Frances Haugh and others.
|
|
*
|
|
*****************************************************************************
|
|
* Copyright (C) 1997-2005
|
|
*
|
|
* Michiel Broek FIDO: 2:280/2802
|
|
* Beekmansbos 10
|
|
* 1971 BV IJmuiden
|
|
* the Netherlands
|
|
*
|
|
* This file is part of MBSE BBS.
|
|
*
|
|
* This BBS is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2, or (at your option) any
|
|
* later version.
|
|
*
|
|
* MBSE BBS is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with MBSE BBS; see the file COPYING. If not, write to the Free
|
|
* Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
*****************************************************************************/
|
|
|
|
#include "../config.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <utime.h>
|
|
#include <sys/time.h>
|
|
#ifdef SHADOW_PASSWORD
|
|
#include <shadow.h>
|
|
#endif
|
|
#include "commonio.h"
|
|
|
|
/* local function prototypes */
|
|
static int check_link_count (const char *);
|
|
static int do_lock_file (const char *, const char *);
|
|
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 int name_is_nis (const char *);
|
|
static int write_all (const struct commonio_db *);
|
|
static struct commonio_entry *find_entry_by_name (struct commonio_db *, const char *);
|
|
|
|
#ifdef HAVE_LCKPWDF
|
|
static int lock_count = 0;
|
|
#endif
|
|
|
|
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;
|
|
int pid;
|
|
int len;
|
|
int retval;
|
|
char buf[32];
|
|
|
|
if ((fd = open(file, O_CREAT|O_EXCL|O_WRONLY, 0600)) == -1)
|
|
return 0;
|
|
|
|
pid = getpid();
|
|
snprintf(buf, sizeof buf, "%d", pid);
|
|
len = strlen(buf) + 1;
|
|
if (write (fd, buf, len) != len) {
|
|
close(fd);
|
|
unlink(file);
|
|
return 0;
|
|
}
|
|
close(fd);
|
|
|
|
if (link(file, lock) == 0) {
|
|
retval = check_link_count(file);
|
|
unlink(file);
|
|
return retval;
|
|
}
|
|
|
|
if ((fd = open(lock, O_RDWR)) == -1) {
|
|
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';
|
|
if ((pid = strtol(buf, (char **) 0, 10)) == 0) {
|
|
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))
|
|
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);
|
|
umask(mask);
|
|
if (!fp)
|
|
return NULL;
|
|
|
|
#ifdef HAVE_FCHOWN
|
|
if (fchown(fileno(fp), sb->st_uid, sb->st_gid))
|
|
goto fail;
|
|
#else
|
|
if (chown(name, sb->st_mode))
|
|
goto fail;
|
|
#endif
|
|
|
|
#ifdef HAVE_FCHMOD
|
|
if (fchmod(fileno(fp), sb->st_mode & 0664))
|
|
goto fail;
|
|
#else
|
|
if (chmod(name, sb->st_mode & 0664))
|
|
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))
|
|
return -1;
|
|
|
|
mask = umask(077);
|
|
bkfp = fopen(backup, "w");
|
|
umask(mask);
|
|
if (!bkfp)
|
|
return -1;
|
|
|
|
/* TODO: faster copy, not one-char-at-a-time. --marekm */
|
|
rewind(fp);
|
|
while ((c = getc(fp)) != EOF) {
|
|
if (putc(c, bkfp) == EOF)
|
|
break;
|
|
}
|
|
if (c != EOF || fflush(bkfp)) {
|
|
fclose(bkfp);
|
|
return -1;
|
|
}
|
|
if (fclose(bkfp))
|
|
return -1;
|
|
|
|
ub.actime = sb.st_atime;
|
|
ub.modtime = sb.st_mtime;
|
|
utime(backup, &ub);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static void free_linked_list(struct commonio_db *db)
|
|
{
|
|
struct commonio_entry *p;
|
|
|
|
while (db->head) {
|
|
p = db->head;
|
|
db->head = p->next;
|
|
|
|
if (p->line)
|
|
free(p->line);
|
|
|
|
if (p->entry)
|
|
db->ops->free(p->entry);
|
|
|
|
free(p);
|
|
}
|
|
db->tail = NULL;
|
|
}
|
|
|
|
|
|
|
|
int commonio_setname(struct commonio_db *db, const char *name)
|
|
{
|
|
strcpy(db->filename, name);
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int 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.%ld", db->filename, (long) getpid());
|
|
snprintf(lock, sizeof lock, "%s.lock", db->filename);
|
|
if (do_lock_file(file, lock)) {
|
|
db->locked = 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
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!
|
|
*/
|
|
if (db->use_lckpwdf) {
|
|
/*
|
|
* Call lckpwdf() on the first lock.
|
|
* If it succeeds, call *_lock() only once
|
|
* (no retries, it should always succeed).
|
|
*/
|
|
if (lock_count == 0) {
|
|
if (lckpwdf() == -1)
|
|
return 0; /* failure */
|
|
}
|
|
if (!commonio_lock_nowait(db)) {
|
|
ulckpwdf();
|
|
return 0; /* failure */
|
|
}
|
|
lock_count++;
|
|
return 1; /* success */
|
|
}
|
|
#endif
|
|
/*
|
|
* 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))
|
|
return 1; /* success */
|
|
/* no unnecessary retries on "permission denied" errors */
|
|
if (geteuid() != 0)
|
|
return 0;
|
|
}
|
|
return 0; /* failure */
|
|
}
|
|
|
|
|
|
|
|
int commonio_unlock(struct commonio_db *db)
|
|
{
|
|
char lock[1024];
|
|
|
|
if (db->isopen) {
|
|
db->readonly = 1;
|
|
if (!commonio_close(db))
|
|
return 0;
|
|
}
|
|
if (db->locked) {
|
|
/*
|
|
* Unlock in reverse order: remove the lock file,
|
|
* then call ulckpwdf() (if used) on last unlock.
|
|
*/
|
|
db->locked = 0;
|
|
snprintf(lock, sizeof lock, "%s.lock", db->filename);
|
|
unlink(lock);
|
|
#ifdef HAVE_LCKPWDF
|
|
if (db->use_lckpwdf && lock_count > 0) {
|
|
lock_count--;
|
|
if (lock_count == 0)
|
|
ulckpwdf();
|
|
}
|
|
#endif
|
|
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 (!db->head)
|
|
db->head = p;
|
|
if (db->tail)
|
|
db->tail->next = p;
|
|
db->tail = p;
|
|
}
|
|
|
|
|
|
|
|
static int name_is_nis(const char *n)
|
|
{
|
|
return (n[0] == '+' || n[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
|
|
/* prototype */
|
|
static void add_one_entry_nis (struct commonio_db *, struct commonio_entry *);
|
|
|
|
static void add_one_entry_nis(struct commonio_db *db, struct commonio_entry *new)
|
|
{
|
|
struct commonio_entry *p;
|
|
|
|
for (p = db->head; p; p = p->next) {
|
|
if (name_is_nis(p->entry ? db->ops->getname(p->entry) : p->line)) {
|
|
new->next = p;
|
|
new->prev = p->prev;
|
|
if (p->prev)
|
|
p->prev->next = new;
|
|
else
|
|
db->head = new;
|
|
p->prev = new;
|
|
return;
|
|
}
|
|
}
|
|
add_one_entry(db, new);
|
|
}
|
|
#endif /* KEEP_NIS_AT_END */
|
|
|
|
|
|
|
|
int commonio_open(struct commonio_db *db, int mode)
|
|
{
|
|
char buf[8192];
|
|
char *cp;
|
|
char *line;
|
|
struct commonio_entry *p;
|
|
void *entry;
|
|
int flags = mode;
|
|
|
|
mode &= ~O_CREAT;
|
|
|
|
if (db->isopen || (mode != O_RDONLY && mode != O_RDWR)) {
|
|
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 = 0;
|
|
|
|
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 (!db->fp) {
|
|
if ((flags & O_CREAT) && errno == ENOENT) {
|
|
db->isopen++;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
while (db->ops->fgets(buf, sizeof buf, db->fp)) {
|
|
if ((cp = strrchr(buf, '\n')))
|
|
*cp = '\0';
|
|
|
|
if (!(line = strdup(buf)))
|
|
goto cleanup;
|
|
|
|
if (name_is_nis(line)) {
|
|
entry = NULL;
|
|
} else if ((entry = db->ops->parse(line))) {
|
|
entry = db->ops->dup(entry);
|
|
if (!entry)
|
|
goto cleanup_line;
|
|
}
|
|
|
|
p = (struct commonio_entry *) malloc(sizeof *p);
|
|
if (!p)
|
|
goto cleanup_entry;
|
|
|
|
p->entry = entry;
|
|
p->line = line;
|
|
p->changed = 0;
|
|
|
|
add_one_entry(db, p);
|
|
}
|
|
|
|
db->isopen++;
|
|
return 1;
|
|
|
|
cleanup_entry:
|
|
if (entry)
|
|
db->ops->free(entry);
|
|
cleanup_line:
|
|
free(line);
|
|
cleanup:
|
|
free_linked_list(db);
|
|
fclose(db->fp);
|
|
db->fp = NULL;
|
|
errno = ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static int write_all(const struct commonio_db *db)
|
|
{
|
|
const struct commonio_entry *p;
|
|
void *entry;
|
|
|
|
for (p = db->head; p; p = p->next) {
|
|
if (p->changed) {
|
|
entry = p->entry;
|
|
if (db->ops->put(entry, db->fp))
|
|
return -1;
|
|
} else if (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 = 0;
|
|
|
|
if (!db->changed || db->readonly) {
|
|
fclose(db->fp);
|
|
db->fp = NULL;
|
|
goto success;
|
|
}
|
|
|
|
memset(&sb, 0, sizeof sb);
|
|
if (db->fp) {
|
|
if (fstat(fileno(db->fp), &sb)) {
|
|
fclose(db->fp);
|
|
db->fp = NULL;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Create backup file.
|
|
*/
|
|
snprintf(buf, sizeof buf, "%s-", db->filename);
|
|
|
|
if (create_backup(buf, db->fp))
|
|
errors++;
|
|
|
|
if (fclose(db->fp))
|
|
errors++;
|
|
|
|
if (errors) {
|
|
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 (!db->fp)
|
|
goto fail;
|
|
|
|
if (write_all(db))
|
|
errors++;
|
|
|
|
if (fflush(db->fp))
|
|
errors++;
|
|
#ifdef HAVE_FSYNC
|
|
if (fsync(fileno(db->fp)))
|
|
errors++;
|
|
#else
|
|
sync();
|
|
#endif
|
|
if (fclose(db->fp))
|
|
errors++;
|
|
|
|
db->fp = NULL;
|
|
|
|
if (errors) {
|
|
unlink(buf);
|
|
goto fail;
|
|
}
|
|
|
|
if (rename(buf, db->filename))
|
|
goto fail;
|
|
|
|
success:
|
|
free_linked_list(db);
|
|
return 1;
|
|
|
|
fail:
|
|
free_linked_list(db);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static struct commonio_entry * find_entry_by_name(struct commonio_db *db, const char *name)
|
|
{
|
|
struct commonio_entry *p;
|
|
void *ep;
|
|
|
|
for (p = db->head; p; p = p->next) {
|
|
ep = p->entry;
|
|
if (ep && strcmp(db->ops->getname(ep), name) == 0)
|
|
break;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
|
|
|
|
int commonio_update(struct commonio_db *db, const void *entry)
|
|
{
|
|
struct commonio_entry *p;
|
|
void *nentry;
|
|
|
|
if (!db->isopen || db->readonly) {
|
|
errno = EINVAL;
|
|
return 0;
|
|
}
|
|
if (!(nentry = db->ops->dup(entry))) {
|
|
errno = ENOMEM;
|
|
return 0;
|
|
}
|
|
p = find_entry_by_name(db, db->ops->getname(entry));
|
|
if (p) {
|
|
db->ops->free(p->entry);
|
|
p->entry = nentry;
|
|
p->changed = 1;
|
|
db->cursor = p;
|
|
|
|
db->changed = 1;
|
|
return 1;
|
|
}
|
|
/* not found, new entry */
|
|
p = (struct commonio_entry *) malloc(sizeof *p);
|
|
if (!p) {
|
|
db->ops->free(nentry);
|
|
errno = ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
p->entry = nentry;
|
|
p->line = NULL;
|
|
p->changed = 1;
|
|
|
|
#if KEEP_NIS_AT_END
|
|
add_one_entry_nis(db, p);
|
|
#else
|
|
add_one_entry(db, p);
|
|
#endif
|
|
|
|
db->changed = 1;
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
void commonio_del_entry(struct commonio_db *db, const struct commonio_entry *p)
|
|
{
|
|
if (p == db->cursor)
|
|
db->cursor = p->next;
|
|
|
|
if (p->prev)
|
|
p->prev->next = p->next;
|
|
else
|
|
db->head = p->next;
|
|
|
|
if (p->next)
|
|
p->next->prev = p->prev;
|
|
else
|
|
db->tail = p->prev;
|
|
|
|
db->changed = 1;
|
|
}
|
|
|
|
|
|
|
|
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 (!p) {
|
|
errno = ENOENT;
|
|
return 0;
|
|
}
|
|
|
|
commonio_del_entry(db, p);
|
|
|
|
if (p->line)
|
|
free(p->line);
|
|
|
|
if (p->entry)
|
|
db->ops->free(p->entry);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
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 (!p) {
|
|
errno = ENOENT;
|
|
return NULL;
|
|
}
|
|
db->cursor = p;
|
|
return p->entry;
|
|
}
|
|
|
|
|
|
|
|
int commonio_rewind(struct commonio_db *db)
|
|
{
|
|
if (!db->isopen) {
|
|
errno = EINVAL;
|
|
return 0;
|
|
}
|
|
db->cursor = NULL;
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
const void * commonio_next(struct commonio_db *db)
|
|
{
|
|
void *entry;
|
|
|
|
if (!db->isopen) {
|
|
errno = EINVAL;
|
|
return 0;
|
|
}
|
|
if (db->cursor == NULL)
|
|
db->cursor = db->head;
|
|
else
|
|
db->cursor = db->cursor->next;
|
|
|
|
while (db->cursor) {
|
|
entry = db->cursor->entry;
|
|
if (entry)
|
|
return entry;
|
|
|
|
db->cursor = db->cursor->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|