changeset 0:d541e748cfd8

Initial revision
author Guido Berhoerster <guido+libpws@berhoerster.name>
date Tue, 10 Feb 2015 11:29:54 +0100
parents
children e1309515d111
files Makefile compat.h compat/endian.c compat/endian.h compat/getentropy.c compat/getentropy.h compat/pws-compat.h compat/tree.h deps.sed include/pws.h pws-field.c pws-file.c pws-internal.h pws-random.c pws-record.c pws.c
diffstat 16 files changed, 3703 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,176 @@
+#
+# Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+PACKAGE =	libpws
+VERSION =	1.0.0
+DISTNAME :=	$(PACKAGE)-$(VERSION)
+
+# gcc, clang, icc, Sun/Solaris Studio
+CC :=		$(CC) -std=c99
+COMPILE.c =	$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(TARGET_ARCH) -c
+# gcc, clang, icc
+MAKEDEPEND.c =	$(CC) -MM $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS)
+# Sun/Solaris Studio
+#MAKEDEPEND.c =	$(CC) -xM1 $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS)
+# X makedepend
+#MAKEDEPEND.c =	makedepend -f- -Y -- $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) --
+LINK.c =	$(CC) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(XCPPFLAGS) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH)
+LINK.o =	$(CC) $(LDFLAGS) $(XLDFLAGS) $(TARGET_ARCH)
+AR :=		ar
+RANLIB :=	ranlib
+INSTALL :=	install
+INSTALL.exec :=	$(INSTALL) -D -m 0755
+INSTALL.lib :=	$(INSTALL) -D -m 0644
+INSTALL.data :=	$(INSTALL) -D -m 0644
+PAX :=		pax
+GZIP :=		gzip
+SED :=		sed
+
+DESTDIR ?=
+prefix ?=	/usr/local
+bindir ?=	$(prefix)/bin
+libdir ?=	$(prefix)/lib
+includedir ?=	$(prefix)/include
+datadir ?=	$(prefix)/share
+
+OS_NAME :=	$(shell uname -s)
+OS_RELEASE :=	$(shell uname -r)
+
+ifeq ($(OS_NAME),Linux)
+  HAVE_ARC4RANDOM ?=	0
+  HAVE_ENDIAN_H ?=	1
+  HAVE_SYS_ENDIAN_H ?=	0
+  HAVE_GETENTROPY ?=	0
+  HAVE_SYS_TREE_H ?=	0
+else ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly),)
+  HAVE_ARC4RANDOM ?=	1
+  HAVE_ENDIAN_H ?=	0
+  HAVE_SYS_ENDIAN_H ?=	1
+  HAVE_GETENTROPY ?=	0
+  HAVE_SYS_TREE_H ?=	1
+else ifeq ($(OS_NAME),NetBSD)
+  HAVE_ARC4RANDOM ?=	1
+  HAVE_ENDIAN_H ?=	0
+  HAVE_SYS_ENDIAN_H ?=	1
+  HAVE_GETENTROPY ?=	0
+  HAVE_SYS_TREE_H ?=	1
+else ifeq ($(OS_NAME),OpenBSD)
+  HAVE_ARC4RANDOM ?=	1
+  HAVE_ENDIAN_H ?=	0
+  HAVE_SYS_ENDIAN_H ?=	1
+  HAVE_GETENTROPY ?=	1
+  HAVE_SYS_TREE_H ?=	1
+else ifeq ($(OS_NAME),SunOS)
+  HAVE_ARC4RANDOM ?=	0
+  HAVE_ENDIAN_H ?=	0
+  HAVE_SYS_ENDIAN_H ?=	0
+  HAVE_GETENTROPY ?=	0
+  HAVE_SYS_TREE_H ?=	0
+else
+  HAVE_ARC4RANDOM ?=	0
+  HAVE_ENDIAN_H ?=	0
+  HAVE_SYS_ENDIAN_H ?=	0
+  HAVE_GETENTROPY ?=	0
+  HAVE_SYS_TREE_H ?=	0
+endif
+
+LIBPWS_OBJS =		pws.o \
+			pws-file.o \
+			pws-record.o \
+			pws-field.o \
+			pws-random.o
+LIBPWS_LIB =		$(PACKAGE).a
+LIBPWS_LIB_MEMBERS =	$(LIBPWS_OBJS:%.o=$(LIBPWS_LIB)(%.o))
+
+OBJS =			$(LIBPWS_OBJS)
+
+.DEFAULT_TARGET = all
+
+.PHONY: all clean clobber dist install
+
+all: $(LIBPWS_LIB)
+
+XCPPFLAGS = -Iinclude
+ifeq ($(HAVE_ARC4RANDOM),1)
+  XCPPFLAGS +=	-DHAVE_ARC4RANDOM
+endif
+ifeq ($(HAVE_ENDIAN_H),1)
+  XCPPFLAGS +=	-DHAVE_ENDIAN_H
+else ifeq ($(HAVE_SYS_ENDIAN_H),1)
+  XCPPFLAGS +=	-DHAVE_SYS_ENDIAN_H
+else
+  LIBPWS_OBJS += compat/endian.o
+endif
+ifeq ($(HAVE_GETENTROPY),1)
+  XCPPFLAGS +=	-DHAVE_GETENTROPY
+else
+  LIBPWS_OBJS += compat/getentropy.o
+endif
+ifeq ($(HAVE_SYS_TREE_H),1)
+  XCPPFLAGS +=	-DHAVE_SYS_TREE_H
+endif
+ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly OpenBSD),)
+  XCPPFLAGS +=	-I/usr/local/include
+  XLDFLAGS +=	-L/usr/local/lib
+else ifeq ($(OS_NAME),NetBSD)
+  XCPPFLAGS +=	-I/usr/pkg/include
+  XLDFLAGS +=	-L/usr/pkg/lib
+endif
+ifeq ($(OS_NAME),SunOS)
+  XCPPFLAGS +=	-I/usr/xpg4/include -D__EXTENSIONS__
+  XLDFLAGS +=	-L/usr/xpg4/lib -R/usr/xpg4/lib
+endif
+ifeq ($(findstring $(OS_NAME),FreeBSD DragonFly NetBSD OpenBSD),)
+  XCPPFLAGS +=	-D_XOPEN_SOURCE=600
+endif
+
+$(LIBPWS_LIB): $(LIBPWS_LIB_MEMBERS)
+
+%.o: %.c
+	$(MAKEDEPEND.c) $< | $(SED) -f deps.sed >$*.d
+	$(COMPILE.c) -o $@ $<
+
+(%): %
+	$(AR) $(ARFLAGS) $@ $<
+	$(RANLIB) $@
+
+install:
+	for header in include/*.h; do \
+	    $(INSTALL.data) $${header} \
+	        "$(DESTDIR)$(includedir)/$(PACKAGE)/$${header##*/}"; \
+	done
+	$(INSTALL.lib) $(LIBPWS_LIB) \
+	    "$(DESTDIR)$(libdir)/$(notdir $(LIBPWS_LIB))"
+
+clean:
+	rm -f $(LIBPWS_LIB) $(LIBPWS_OBJS)
+
+clobber: clean
+	rm -f $(patsubst %.o,%.d,$(OBJS))
+
+dist: clobber
+	$(PAX) -w -x ustar -s ',.*/\..*,,' -s ',./[^/]*\.tar\.gz,,' \
+	    -s ',^\.$$,,' -s ',\./,$(DISTNAME)/,' . | \
+	    $(GZIP) > $(DISTNAME).tar.gz
+
+-include $(patsubst %.o,%.d,$(OBJS))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat.h	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	COMPAT_H
+#define	COMPAT_H
+
+#include "compat/pws-compat.h"
+
+/* for FreeBSD getline() */
+#define	_WITH_GETLINE
+
+/* for glibc endian.h */
+#define	_BSD_SOURCE
+
+#if	!defined(HAVE_ENDIAN_H) && !defined(HAVE_SYS_ENDIAN_H)
+#include "compat/endian.h"
+#endif /* !defined(HAVE_ENDIAN_H) && !defined(HAVE_SYS_ENDIAN_H) */
+
+#ifndef	HAVE_GETENTROPY
+#include "compat/getentropy.h"
+#endif /* !HAVE_GETENTROPY */
+
+#ifndef	HAVE_SYS_TREE_H
+#include "compat/tree.h"
+#endif /* !HAVE_SYS_TREE_H */
+
+#endif /* COMPAT_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/endian.c	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+pws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <arpa/inet.h>
+
+#include "pws-compat.h"
+#include "endian.h"
+
+uint16_t
+le16toh(uint16_t little16)
+{
+	unsigned char	*b = (unsigned char *)&little16;
+
+	return ((b[0] << 0) | (b[1] << 8));
+}
+
+uint16_t
+htole16(uint16_t host16)
+{
+	uint16_t	little16;
+	unsigned char	*b = (unsigned char *)&little16;
+
+	b[0] = (unsigned char)(host16 >> 0);
+	b[1] = (unsigned char)(host16 >> 8);
+
+	return (little16);
+}
+
+uint32_t
+le32toh(uint32_t little32)
+{
+	unsigned char	*b = (unsigned char *)&little32;
+
+	return ((b[0] << 0) | (b[1] << 8) | (b[2] << 16) | (b[3] << 24));
+}
+
+uint32_t
+htole32(uint32_t host32)
+{
+	uint32_t	little32;
+	unsigned char	*b = (unsigned char *)&little32;
+
+	b[0] = (unsigned char)(host32 >> 0);
+	b[1] = (unsigned char)(host32 >> 8);
+	b[2] = (unsigned char)(host32 >> 16);
+	b[3] = (unsigned char)(host32 >> 24);
+
+	return (little32);
+}
+
+uint16_t
+be16toh(uint16_t big16)
+{
+	return (ntohs(big16));
+}
+
+uint16_t
+htobe16(uint16_t host16)
+{
+	return (htons(host16));
+}
+
+uint32_t
+be32toh(uint32_t big32)
+{
+	return (ntohl(big32));
+}
+
+uint32_t
+htobe32(uint32_t host32)
+{
+	return (htonl(host32));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/endian.h	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	ENDIAN_H
+#define	ENDIAN_H
+
+#include <stdint.h>
+
+uint16_t	le16toh(uint16_t);
+uint16_t	htole16(uint16_t);
+uint32_t	le32toh(uint32_t);
+uint32_t	htole32(uint32_t);
+uint16_t	be16toh(uint16_t);
+uint16_t	htobe16(uint16_t);
+uint32_t	be32toh(uint32_t);
+uint32_t	htobe32(uint32_t);
+
+#endif /* !ENDIAN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/getentropy.c	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* needed for syscall(2) on Linux */
+#define	_GNU_SOURCE
+
+/* Linux >= 3.17 has getrandom(2) system call */
+#ifdef	__linux__
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <linux/random.h>
+#ifdef	SYS_getrandom
+#define	HAVE_GETRANDOM
+#endif /* SYS_getrandom */
+#endif /* __linux__ */
+/*
+ * on unknown Unix systems without getentropy(2) or Linux without getrandom(2)
+ * fall back to * reading from /dev/(u)random
+ */
+#ifndef	HAVE_GETRANDOM
+#include <stdio.h>
+#ifndef	RANDOM_DEVICE
+#ifdef	__linux__
+/* on Linux /dev/urandom should be good enough */
+#define	RANDOM_DEVICE	"/dev/urandom"
+#else /* __linux__ */
+/* on unknown Unix systems use the possibly blocking /dev/random */
+#define	RANDOM_DEVICE	"/dev/random"
+#endif /* __linux__ */
+#endif /* !RANDOM_DEVICE */
+#endif /* !HAVE_GETRANDOM */
+#include <errno.h>
+
+#include "pws-compat.h"
+
+#ifdef	HAVE_GETRANDOM
+static int
+getentropy_linux_getrandom(void *buf, size_t buf_len)
+{
+	int retval;
+
+	retval = syscall(SYS_getrandom, buf, buf_len, 0);
+	if (retval < 0) {
+		return (-1);
+	} else if ((size_t)retval != buf_len) {
+		errno = EIO;
+		return (-1);
+	}
+
+	return (0);
+}
+#else
+static int
+getentropy_dev_random(void *buf, size_t buf_len)
+{
+	FILE	*fp;
+	int	saved_errno;
+
+	fp = fopen(RANDOM_DEVICE, "r");
+	if (fp == NULL) {
+		return (-1);
+	}
+	if (fread(buf, 1, buf_len, fp) != buf_len) {
+		saved_errno = errno;
+		fclose(fp);
+		errno = saved_errno;
+		return (-1);
+	}
+	if (fclose(fp) != 0) {
+		return (-1);
+	}
+
+	return (0);
+}
+#endif /* HAVE_GETRANDOM */
+
+int
+getentropy(void *buf, size_t buf_len)
+{
+	if (buf_len > 256) {
+		errno = EIO;
+		return (-1);
+	}
+
+	return (
+#ifdef	HAVE_GETRANDOM
+	    getentropy_linux_getrandom(
+#else /* HAVE_GETRANDOM */
+	    getentropy_dev_random(
+#endif /* HAVE_GETRANDOM */
+	    buf, buf_len));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/getentropy.h	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	GETENTROPY_H
+#define	GETENTROPY_H
+
+#include <stddef.h>
+
+int	getentropy(void *, size_t);
+
+#endif /* !GETENTROPY_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/pws-compat.h	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	PWS_COMPAT_H
+#define	PWS_COMPAT_H
+
+#if	!defined(HAVE_ENDIAN_H) && !defined(HAVE_SYS_ENDIAN_H)
+#define	le16toh		pws_compat_le16toh
+#define	htole16		pws_compat_htole16
+#define	le32toh		pws_compat_le32toh
+#define	htole32		pws_compat_htole32
+#define	be16toh		pws_compat_be16toh
+#define	htobe16		pws_compat_htobe16
+#define	be32toh		pws_compat_be32toh
+#define	htobe32		pws_compat_htobe32
+#endif /* !defined(HAVE_ENDIAN_H) && !defined(HAVE_SYS_ENDIAN_H) */
+
+#ifndef	HAVE_GETENTROPY
+#define	getentropy	pws_compat_getentropy
+#endif /* !HAVE_GETENTROPY */
+
+#endif /* PWS_COMPAT_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/tree.h	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,748 @@
+/*	$OpenBSD: tree.h,v 1.13 2011/07/09 00:19:45 pirofti Exp $	*/
+/*
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#ifndef	_SYS_TREE_H_
+#define	_SYS_TREE_H_
+
+/*
+ * This file defines data structures for different types of trees:
+ * splay trees and red-black trees.
+ *
+ * A splay tree is a self-organizing data structure.  Every operation
+ * on the tree causes a splay to happen.  The splay moves the requested
+ * node to the root of the tree and partly rebalances it.
+ *
+ * This has the benefit that request locality causes faster lookups as
+ * the requested nodes move to the top of the tree.  On the other hand,
+ * every lookup causes memory writes.
+ *
+ * The Balance Theorem bounds the total access time for m operations
+ * and n inserts on an initially empty tree as O((m + n)lg n).  The
+ * amortized cost for a sequence of m accesses to a splay tree is O(lg n);
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute.  It fulfills a set of conditions:
+ *	- every search path from the root to a leaf consists of the
+ *	  same number of black nodes,
+ *	- each red node (except for the root) has a black parent,
+ *	- each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+#define SPLAY_HEAD(name, type)						\
+struct name {								\
+	struct type *sph_root; /* root of the tree */			\
+}
+
+#define SPLAY_INITIALIZER(root)						\
+	{ NULL }
+
+#define SPLAY_INIT(root) do {						\
+	(root)->sph_root = NULL;					\
+} while (0)
+
+#define SPLAY_ENTRY(type)						\
+struct {								\
+	struct type *spe_left; /* left element */			\
+	struct type *spe_right; /* right element */			\
+}
+
+#define SPLAY_LEFT(elm, field)		(elm)->field.spe_left
+#define SPLAY_RIGHT(elm, field)		(elm)->field.spe_right
+#define SPLAY_ROOT(head)		(head)->sph_root
+#define SPLAY_EMPTY(head)		(SPLAY_ROOT(head) == NULL)
+
+/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
+#define SPLAY_ROTATE_RIGHT(head, tmp, field) do {			\
+	SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field);	\
+	SPLAY_RIGHT(tmp, field) = (head)->sph_root;			\
+	(head)->sph_root = tmp;						\
+} while (0)
+	
+#define SPLAY_ROTATE_LEFT(head, tmp, field) do {			\
+	SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field);	\
+	SPLAY_LEFT(tmp, field) = (head)->sph_root;			\
+	(head)->sph_root = tmp;						\
+} while (0)
+
+#define SPLAY_LINKLEFT(head, tmp, field) do {				\
+	SPLAY_LEFT(tmp, field) = (head)->sph_root;			\
+	tmp = (head)->sph_root;						\
+	(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);		\
+} while (0)
+
+#define SPLAY_LINKRIGHT(head, tmp, field) do {				\
+	SPLAY_RIGHT(tmp, field) = (head)->sph_root;			\
+	tmp = (head)->sph_root;						\
+	(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);	\
+} while (0)
+
+#define SPLAY_ASSEMBLE(head, node, left, right, field) do {		\
+	SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field);	\
+	SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
+	SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field);	\
+	SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field);	\
+} while (0)
+
+/* Generates prototypes and inline functions */
+
+#define SPLAY_PROTOTYPE(name, type, field, cmp)				\
+void name##_SPLAY(struct name *, struct type *);			\
+void name##_SPLAY_MINMAX(struct name *, int);				\
+struct type *name##_SPLAY_INSERT(struct name *, struct type *);		\
+struct type *name##_SPLAY_REMOVE(struct name *, struct type *);		\
+									\
+/* Finds the node with the same key as elm */				\
+static __inline struct type *						\
+name##_SPLAY_FIND(struct name *head, struct type *elm)			\
+{									\
+	if (SPLAY_EMPTY(head))						\
+		return(NULL);						\
+	name##_SPLAY(head, elm);					\
+	if ((cmp)(elm, (head)->sph_root) == 0)				\
+		return (head->sph_root);				\
+	return (NULL);							\
+}									\
+									\
+static __inline struct type *						\
+name##_SPLAY_NEXT(struct name *head, struct type *elm)			\
+{									\
+	name##_SPLAY(head, elm);					\
+	if (SPLAY_RIGHT(elm, field) != NULL) {				\
+		elm = SPLAY_RIGHT(elm, field);				\
+		while (SPLAY_LEFT(elm, field) != NULL) {		\
+			elm = SPLAY_LEFT(elm, field);			\
+		}							\
+	} else								\
+		elm = NULL;						\
+	return (elm);							\
+}									\
+									\
+static __inline struct type *						\
+name##_SPLAY_MIN_MAX(struct name *head, int val)			\
+{									\
+	name##_SPLAY_MINMAX(head, val);					\
+        return (SPLAY_ROOT(head));					\
+}
+
+/* Main splay operation.
+ * Moves node close to the key of elm to top
+ */
+#define SPLAY_GENERATE(name, type, field, cmp)				\
+struct type *								\
+name##_SPLAY_INSERT(struct name *head, struct type *elm)		\
+{									\
+    if (SPLAY_EMPTY(head)) {						\
+	    SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL;	\
+    } else {								\
+	    int __comp;							\
+	    name##_SPLAY(head, elm);					\
+	    __comp = (cmp)(elm, (head)->sph_root);			\
+	    if(__comp < 0) {						\
+		    SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
+		    SPLAY_RIGHT(elm, field) = (head)->sph_root;		\
+		    SPLAY_LEFT((head)->sph_root, field) = NULL;		\
+	    } else if (__comp > 0) {					\
+		    SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
+		    SPLAY_LEFT(elm, field) = (head)->sph_root;		\
+		    SPLAY_RIGHT((head)->sph_root, field) = NULL;	\
+	    } else							\
+		    return ((head)->sph_root);				\
+    }									\
+    (head)->sph_root = (elm);						\
+    return (NULL);							\
+}									\
+									\
+struct type *								\
+name##_SPLAY_REMOVE(struct name *head, struct type *elm)		\
+{									\
+	struct type *__tmp;						\
+	if (SPLAY_EMPTY(head))						\
+		return (NULL);						\
+	name##_SPLAY(head, elm);					\
+	if ((cmp)(elm, (head)->sph_root) == 0) {			\
+		if (SPLAY_LEFT((head)->sph_root, field) == NULL) {	\
+			(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
+		} else {						\
+			__tmp = SPLAY_RIGHT((head)->sph_root, field);	\
+			(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
+			name##_SPLAY(head, elm);			\
+			SPLAY_RIGHT((head)->sph_root, field) = __tmp;	\
+		}							\
+		return (elm);						\
+	}								\
+	return (NULL);							\
+}									\
+									\
+void									\
+name##_SPLAY(struct name *head, struct type *elm)			\
+{									\
+	struct type __node, *__left, *__right, *__tmp;			\
+	int __comp;							\
+\
+	SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+	__left = __right = &__node;					\
+\
+	while ((__comp = (cmp)(elm, (head)->sph_root))) {		\
+		if (__comp < 0) {					\
+			__tmp = SPLAY_LEFT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if ((cmp)(elm, __tmp) < 0){			\
+				SPLAY_ROTATE_RIGHT(head, __tmp, field);	\
+				if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKLEFT(head, __right, field);		\
+		} else if (__comp > 0) {				\
+			__tmp = SPLAY_RIGHT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if ((cmp)(elm, __tmp) > 0){			\
+				SPLAY_ROTATE_LEFT(head, __tmp, field);	\
+				if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKRIGHT(head, __left, field);		\
+		}							\
+	}								\
+	SPLAY_ASSEMBLE(head, &__node, __left, __right, field);		\
+}									\
+									\
+/* Splay with either the minimum or the maximum element			\
+ * Used to find minimum or maximum element in tree.			\
+ */									\
+void name##_SPLAY_MINMAX(struct name *head, int __comp) \
+{									\
+	struct type __node, *__left, *__right, *__tmp;			\
+\
+	SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+	__left = __right = &__node;					\
+\
+	while (1) {							\
+		if (__comp < 0) {					\
+			__tmp = SPLAY_LEFT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if (__comp < 0){				\
+				SPLAY_ROTATE_RIGHT(head, __tmp, field);	\
+				if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKLEFT(head, __right, field);		\
+		} else if (__comp > 0) {				\
+			__tmp = SPLAY_RIGHT((head)->sph_root, field);	\
+			if (__tmp == NULL)				\
+				break;					\
+			if (__comp > 0) {				\
+				SPLAY_ROTATE_LEFT(head, __tmp, field);	\
+				if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+					break;				\
+			}						\
+			SPLAY_LINKRIGHT(head, __left, field);		\
+		}							\
+	}								\
+	SPLAY_ASSEMBLE(head, &__node, __left, __right, field);		\
+}
+
+#define SPLAY_NEGINF	-1
+#define SPLAY_INF	1
+
+#define SPLAY_INSERT(name, x, y)	name##_SPLAY_INSERT(x, y)
+#define SPLAY_REMOVE(name, x, y)	name##_SPLAY_REMOVE(x, y)
+#define SPLAY_FIND(name, x, y)		name##_SPLAY_FIND(x, y)
+#define SPLAY_NEXT(name, x, y)		name##_SPLAY_NEXT(x, y)
+#define SPLAY_MIN(name, x)		(SPLAY_EMPTY(x) ? NULL	\
+					: name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
+#define SPLAY_MAX(name, x)		(SPLAY_EMPTY(x) ? NULL	\
+					: name##_SPLAY_MIN_MAX(x, SPLAY_INF))
+
+#define SPLAY_FOREACH(x, name, head)					\
+	for ((x) = SPLAY_MIN(name, head);				\
+	     (x) != NULL;						\
+	     (x) = SPLAY_NEXT(name, head, x))
+
+/* Macros that define a red-black tree */
+#define RB_HEAD(name, type)						\
+struct name {								\
+	struct type *rbh_root; /* root of the tree */			\
+}
+
+#define RB_INITIALIZER(root)						\
+	{ NULL }
+
+#define RB_INIT(root) do {						\
+	(root)->rbh_root = NULL;					\
+} while (0)
+
+#define RB_BLACK	0
+#define RB_RED		1
+#define RB_ENTRY(type)							\
+struct {								\
+	struct type *rbe_left;		/* left element */		\
+	struct type *rbe_right;		/* right element */		\
+	struct type *rbe_parent;	/* parent element */		\
+	int rbe_color;			/* node color */		\
+}
+
+#define RB_LEFT(elm, field)		(elm)->field.rbe_left
+#define RB_RIGHT(elm, field)		(elm)->field.rbe_right
+#define RB_PARENT(elm, field)		(elm)->field.rbe_parent
+#define RB_COLOR(elm, field)		(elm)->field.rbe_color
+#define RB_ROOT(head)			(head)->rbh_root
+#define RB_EMPTY(head)			(RB_ROOT(head) == NULL)
+
+#define RB_SET(elm, parent, field) do {					\
+	RB_PARENT(elm, field) = parent;					\
+	RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL;		\
+	RB_COLOR(elm, field) = RB_RED;					\
+} while (0)
+
+#define RB_SET_BLACKRED(black, red, field) do {				\
+	RB_COLOR(black, field) = RB_BLACK;				\
+	RB_COLOR(red, field) = RB_RED;					\
+} while (0)
+
+#ifndef RB_AUGMENT
+#define RB_AUGMENT(x)	do {} while (0)
+#endif
+
+#define RB_ROTATE_LEFT(head, elm, tmp, field) do {			\
+	(tmp) = RB_RIGHT(elm, field);					\
+	if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) {		\
+		RB_PARENT(RB_LEFT(tmp, field), field) = (elm);		\
+	}								\
+	RB_AUGMENT(elm);						\
+	if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {		\
+		if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))	\
+			RB_LEFT(RB_PARENT(elm, field), field) = (tmp);	\
+		else							\
+			RB_RIGHT(RB_PARENT(elm, field), field) = (tmp);	\
+	} else								\
+		(head)->rbh_root = (tmp);				\
+	RB_LEFT(tmp, field) = (elm);					\
+	RB_PARENT(elm, field) = (tmp);					\
+	RB_AUGMENT(tmp);						\
+	if ((RB_PARENT(tmp, field)))					\
+		RB_AUGMENT(RB_PARENT(tmp, field));			\
+} while (0)
+
+#define RB_ROTATE_RIGHT(head, elm, tmp, field) do {			\
+	(tmp) = RB_LEFT(elm, field);					\
+	if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) {		\
+		RB_PARENT(RB_RIGHT(tmp, field), field) = (elm);		\
+	}								\
+	RB_AUGMENT(elm);						\
+	if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {		\
+		if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))	\
+			RB_LEFT(RB_PARENT(elm, field), field) = (tmp);	\
+		else							\
+			RB_RIGHT(RB_PARENT(elm, field), field) = (tmp);	\
+	} else								\
+		(head)->rbh_root = (tmp);				\
+	RB_RIGHT(tmp, field) = (elm);					\
+	RB_PARENT(elm, field) = (tmp);					\
+	RB_AUGMENT(tmp);						\
+	if ((RB_PARENT(tmp, field)))					\
+		RB_AUGMENT(RB_PARENT(tmp, field));			\
+} while (0)
+
+/* Generates prototypes and inline functions */
+#define	RB_PROTOTYPE(name, type, field, cmp)				\
+	RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
+#define	RB_PROTOTYPE_STATIC(name, type, field, cmp)			\
+	RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr)		\
+attr void name##_RB_INSERT_COLOR(struct name *, struct type *);		\
+attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
+attr struct type *name##_RB_REMOVE(struct name *, struct type *);	\
+attr struct type *name##_RB_INSERT(struct name *, struct type *);	\
+attr struct type *name##_RB_FIND(struct name *, struct type *);		\
+attr struct type *name##_RB_NFIND(struct name *, struct type *);	\
+attr struct type *name##_RB_NEXT(struct type *);			\
+attr struct type *name##_RB_PREV(struct type *);			\
+attr struct type *name##_RB_MINMAX(struct name *, int);			\
+									\
+
+/* Main rb operation.
+ * Moves node close to the key of elm to top
+ */
+#define	RB_GENERATE(name, type, field, cmp)				\
+	RB_GENERATE_INTERNAL(name, type, field, cmp,)
+#define	RB_GENERATE_STATIC(name, type, field, cmp)			\
+	RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr)		\
+attr void								\
+name##_RB_INSERT_COLOR(struct name *head, struct type *elm)		\
+{									\
+	struct type *parent, *gparent, *tmp;				\
+	while ((parent = RB_PARENT(elm, field)) &&			\
+	    RB_COLOR(parent, field) == RB_RED) {			\
+		gparent = RB_PARENT(parent, field);			\
+		if (parent == RB_LEFT(gparent, field)) {		\
+			tmp = RB_RIGHT(gparent, field);			\
+			if (tmp && RB_COLOR(tmp, field) == RB_RED) {	\
+				RB_COLOR(tmp, field) = RB_BLACK;	\
+				RB_SET_BLACKRED(parent, gparent, field);\
+				elm = gparent;				\
+				continue;				\
+			}						\
+			if (RB_RIGHT(parent, field) == elm) {		\
+				RB_ROTATE_LEFT(head, parent, tmp, field);\
+				tmp = parent;				\
+				parent = elm;				\
+				elm = tmp;				\
+			}						\
+			RB_SET_BLACKRED(parent, gparent, field);	\
+			RB_ROTATE_RIGHT(head, gparent, tmp, field);	\
+		} else {						\
+			tmp = RB_LEFT(gparent, field);			\
+			if (tmp && RB_COLOR(tmp, field) == RB_RED) {	\
+				RB_COLOR(tmp, field) = RB_BLACK;	\
+				RB_SET_BLACKRED(parent, gparent, field);\
+				elm = gparent;				\
+				continue;				\
+			}						\
+			if (RB_LEFT(parent, field) == elm) {		\
+				RB_ROTATE_RIGHT(head, parent, tmp, field);\
+				tmp = parent;				\
+				parent = elm;				\
+				elm = tmp;				\
+			}						\
+			RB_SET_BLACKRED(parent, gparent, field);	\
+			RB_ROTATE_LEFT(head, gparent, tmp, field);	\
+		}							\
+	}								\
+	RB_COLOR(head->rbh_root, field) = RB_BLACK;			\
+}									\
+									\
+attr void								\
+name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
+{									\
+	struct type *tmp;						\
+	while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) &&	\
+	    elm != RB_ROOT(head)) {					\
+		if (RB_LEFT(parent, field) == elm) {			\
+			tmp = RB_RIGHT(parent, field);			\
+			if (RB_COLOR(tmp, field) == RB_RED) {		\
+				RB_SET_BLACKRED(tmp, parent, field);	\
+				RB_ROTATE_LEFT(head, parent, tmp, field);\
+				tmp = RB_RIGHT(parent, field);		\
+			}						\
+			if ((RB_LEFT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+			    (RB_RIGHT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+				RB_COLOR(tmp, field) = RB_RED;		\
+				elm = parent;				\
+				parent = RB_PARENT(elm, field);		\
+			} else {					\
+				if (RB_RIGHT(tmp, field) == NULL ||	\
+				    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
+					struct type *oleft;		\
+					if ((oleft = RB_LEFT(tmp, field)))\
+						RB_COLOR(oleft, field) = RB_BLACK;\
+					RB_COLOR(tmp, field) = RB_RED;	\
+					RB_ROTATE_RIGHT(head, tmp, oleft, field);\
+					tmp = RB_RIGHT(parent, field);	\
+				}					\
+				RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+				RB_COLOR(parent, field) = RB_BLACK;	\
+				if (RB_RIGHT(tmp, field))		\
+					RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
+				RB_ROTATE_LEFT(head, parent, tmp, field);\
+				elm = RB_ROOT(head);			\
+				break;					\
+			}						\
+		} else {						\
+			tmp = RB_LEFT(parent, field);			\
+			if (RB_COLOR(tmp, field) == RB_RED) {		\
+				RB_SET_BLACKRED(tmp, parent, field);	\
+				RB_ROTATE_RIGHT(head, parent, tmp, field);\
+				tmp = RB_LEFT(parent, field);		\
+			}						\
+			if ((RB_LEFT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+			    (RB_RIGHT(tmp, field) == NULL ||		\
+			    RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+				RB_COLOR(tmp, field) = RB_RED;		\
+				elm = parent;				\
+				parent = RB_PARENT(elm, field);		\
+			} else {					\
+				if (RB_LEFT(tmp, field) == NULL ||	\
+				    RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
+					struct type *oright;		\
+					if ((oright = RB_RIGHT(tmp, field)))\
+						RB_COLOR(oright, field) = RB_BLACK;\
+					RB_COLOR(tmp, field) = RB_RED;	\
+					RB_ROTATE_LEFT(head, tmp, oright, field);\
+					tmp = RB_LEFT(parent, field);	\
+				}					\
+				RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+				RB_COLOR(parent, field) = RB_BLACK;	\
+				if (RB_LEFT(tmp, field))		\
+					RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
+				RB_ROTATE_RIGHT(head, parent, tmp, field);\
+				elm = RB_ROOT(head);			\
+				break;					\
+			}						\
+		}							\
+	}								\
+	if (elm)							\
+		RB_COLOR(elm, field) = RB_BLACK;			\
+}									\
+									\
+attr struct type *							\
+name##_RB_REMOVE(struct name *head, struct type *elm)			\
+{									\
+	struct type *child, *parent, *old = elm;			\
+	int color;							\
+	if (RB_LEFT(elm, field) == NULL)				\
+		child = RB_RIGHT(elm, field);				\
+	else if (RB_RIGHT(elm, field) == NULL)				\
+		child = RB_LEFT(elm, field);				\
+	else {								\
+		struct type *left;					\
+		elm = RB_RIGHT(elm, field);				\
+		while ((left = RB_LEFT(elm, field)))			\
+			elm = left;					\
+		child = RB_RIGHT(elm, field);				\
+		parent = RB_PARENT(elm, field);				\
+		color = RB_COLOR(elm, field);				\
+		if (child)						\
+			RB_PARENT(child, field) = parent;		\
+		if (parent) {						\
+			if (RB_LEFT(parent, field) == elm)		\
+				RB_LEFT(parent, field) = child;		\
+			else						\
+				RB_RIGHT(parent, field) = child;	\
+			RB_AUGMENT(parent);				\
+		} else							\
+			RB_ROOT(head) = child;				\
+		if (RB_PARENT(elm, field) == old)			\
+			parent = elm;					\
+		(elm)->field = (old)->field;				\
+		if (RB_PARENT(old, field)) {				\
+			if (RB_LEFT(RB_PARENT(old, field), field) == old)\
+				RB_LEFT(RB_PARENT(old, field), field) = elm;\
+			else						\
+				RB_RIGHT(RB_PARENT(old, field), field) = elm;\
+			RB_AUGMENT(RB_PARENT(old, field));		\
+		} else							\
+			RB_ROOT(head) = elm;				\
+		RB_PARENT(RB_LEFT(old, field), field) = elm;		\
+		if (RB_RIGHT(old, field))				\
+			RB_PARENT(RB_RIGHT(old, field), field) = elm;	\
+		if (parent) {						\
+			left = parent;					\
+			do {						\
+				RB_AUGMENT(left);			\
+			} while ((left = RB_PARENT(left, field)));	\
+		}							\
+		goto color;						\
+	}								\
+	parent = RB_PARENT(elm, field);					\
+	color = RB_COLOR(elm, field);					\
+	if (child)							\
+		RB_PARENT(child, field) = parent;			\
+	if (parent) {							\
+		if (RB_LEFT(parent, field) == elm)			\
+			RB_LEFT(parent, field) = child;			\
+		else							\
+			RB_RIGHT(parent, field) = child;		\
+		RB_AUGMENT(parent);					\
+	} else								\
+		RB_ROOT(head) = child;					\
+color:									\
+	if (color == RB_BLACK)						\
+		name##_RB_REMOVE_COLOR(head, parent, child);		\
+	return (old);							\
+}									\
+									\
+/* Inserts a node into the RB tree */					\
+attr struct type *							\
+name##_RB_INSERT(struct name *head, struct type *elm)			\
+{									\
+	struct type *tmp;						\
+	struct type *parent = NULL;					\
+	int comp = 0;							\
+	tmp = RB_ROOT(head);						\
+	while (tmp) {							\
+		parent = tmp;						\
+		comp = (cmp)(elm, parent);				\
+		if (comp < 0)						\
+			tmp = RB_LEFT(tmp, field);			\
+		else if (comp > 0)					\
+			tmp = RB_RIGHT(tmp, field);			\
+		else							\
+			return (tmp);					\
+	}								\
+	RB_SET(elm, parent, field);					\
+	if (parent != NULL) {						\
+		if (comp < 0)						\
+			RB_LEFT(parent, field) = elm;			\
+		else							\
+			RB_RIGHT(parent, field) = elm;			\
+		RB_AUGMENT(parent);					\
+	} else								\
+		RB_ROOT(head) = elm;					\
+	name##_RB_INSERT_COLOR(head, elm);				\
+	return (NULL);							\
+}									\
+									\
+/* Finds the node with the same key as elm */				\
+attr struct type *							\
+name##_RB_FIND(struct name *head, struct type *elm)			\
+{									\
+	struct type *tmp = RB_ROOT(head);				\
+	int comp;							\
+	while (tmp) {							\
+		comp = cmp(elm, tmp);					\
+		if (comp < 0)						\
+			tmp = RB_LEFT(tmp, field);			\
+		else if (comp > 0)					\
+			tmp = RB_RIGHT(tmp, field);			\
+		else							\
+			return (tmp);					\
+	}								\
+	return (NULL);							\
+}									\
+									\
+/* Finds the first node greater than or equal to the search key */	\
+attr struct type *							\
+name##_RB_NFIND(struct name *head, struct type *elm)			\
+{									\
+	struct type *tmp = RB_ROOT(head);				\
+	struct type *res = NULL;					\
+	int comp;							\
+	while (tmp) {							\
+		comp = cmp(elm, tmp);					\
+		if (comp < 0) {						\
+			res = tmp;					\
+			tmp = RB_LEFT(tmp, field);			\
+		}							\
+		else if (comp > 0)					\
+			tmp = RB_RIGHT(tmp, field);			\
+		else							\
+			return (tmp);					\
+	}								\
+	return (res);							\
+}									\
+									\
+/* ARGSUSED */								\
+attr struct type *							\
+name##_RB_NEXT(struct type *elm)					\
+{									\
+	if (RB_RIGHT(elm, field)) {					\
+		elm = RB_RIGHT(elm, field);				\
+		while (RB_LEFT(elm, field))				\
+			elm = RB_LEFT(elm, field);			\
+	} else {							\
+		if (RB_PARENT(elm, field) &&				\
+		    (elm == RB_LEFT(RB_PARENT(elm, field), field)))	\
+			elm = RB_PARENT(elm, field);			\
+		else {							\
+			while (RB_PARENT(elm, field) &&			\
+			    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
+				elm = RB_PARENT(elm, field);		\
+			elm = RB_PARENT(elm, field);			\
+		}							\
+	}								\
+	return (elm);							\
+}									\
+									\
+/* ARGSUSED */								\
+attr struct type *							\
+name##_RB_PREV(struct type *elm)					\
+{									\
+	if (RB_LEFT(elm, field)) {					\
+		elm = RB_LEFT(elm, field);				\
+		while (RB_RIGHT(elm, field))				\
+			elm = RB_RIGHT(elm, field);			\
+	} else {							\
+		if (RB_PARENT(elm, field) &&				\
+		    (elm == RB_RIGHT(RB_PARENT(elm, field), field)))	\
+			elm = RB_PARENT(elm, field);			\
+		else {							\
+			while (RB_PARENT(elm, field) &&			\
+			    (elm == RB_LEFT(RB_PARENT(elm, field), field)))\
+				elm = RB_PARENT(elm, field);		\
+			elm = RB_PARENT(elm, field);			\
+		}							\
+	}								\
+	return (elm);							\
+}									\
+									\
+attr struct type *							\
+name##_RB_MINMAX(struct name *head, int val)				\
+{									\
+	struct type *tmp = RB_ROOT(head);				\
+	struct type *parent = NULL;					\
+	while (tmp) {							\
+		parent = tmp;						\
+		if (val < 0)						\
+			tmp = RB_LEFT(tmp, field);			\
+		else							\
+			tmp = RB_RIGHT(tmp, field);			\
+	}								\
+	return (parent);						\
+}
+
+#define RB_NEGINF	-1
+#define RB_INF	1
+
+#define RB_INSERT(name, x, y)	name##_RB_INSERT(x, y)
+#define RB_REMOVE(name, x, y)	name##_RB_REMOVE(x, y)
+#define RB_FIND(name, x, y)	name##_RB_FIND(x, y)
+#define RB_NFIND(name, x, y)	name##_RB_NFIND(x, y)
+#define RB_NEXT(name, x, y)	name##_RB_NEXT(y)
+#define RB_PREV(name, x, y)	name##_RB_PREV(y)
+#define RB_MIN(name, x)		name##_RB_MINMAX(x, RB_NEGINF)
+#define RB_MAX(name, x)		name##_RB_MINMAX(x, RB_INF)
+
+#define RB_FOREACH(x, name, head)					\
+	for ((x) = RB_MIN(name, head);					\
+	     (x) != NULL;						\
+	     (x) = name##_RB_NEXT(x))
+
+#define RB_FOREACH_SAFE(x, name, head, y)				\
+	for ((x) = RB_MIN(name, head);					\
+	    ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1);		\
+	     (x) = (y))
+
+#define RB_FOREACH_REVERSE(x, name, head)				\
+	for ((x) = RB_MAX(name, head);					\
+	     (x) != NULL;						\
+	     (x) = name##_RB_PREV(x))
+
+#define RB_FOREACH_REVERSE_SAFE(x, name, head, y)			\
+	for ((x) = RB_MAX(name, head);					\
+	    ((x) != NULL) && ((y) = name##_RB_PREV(x), 1);		\
+	     (x) = (y))
+
+#endif	/* _SYS_TREE_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deps.sed	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,26 @@
+/^[^:]\{1,\}:.*\\$/{
+    h
+    s/\([^:]\{1,\}:\).*/\1/
+    x
+    s/[^:]\{1,\}://
+}
+/\\$/,/^$/bgen
+/\\$/,/[^\\]$/{
+:gen
+    s/[[:blank:]]*\\$//
+    s/^[[:blank:]]*//
+    G
+    s/\(.*\)\n\(.*\)/\2 \1/
+}
+/^[^:]\{1,\}:[[:blank:]]*$/d
+/^[^:]\{1,\}\.o:/{
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC][[:blank:]]*/ /g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.[cC]$//g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc[[:blank:]]*/ /g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cc$//g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp[[:blank:]]*/ /g
+    s/[[:blank:]]*[^[:blank:]]\{1,\}\.cpp$//g
+    /^[^:]\{1,\}:[[:blank:]]*$/d
+    s/^\([^:]\{1,\}\)\.o[[:blank:]]*:[[:blank:]]*\(.*\)/\1.d: $(wildcard \2)\
+&/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/pws.h	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	PWS_H
+#define	PWS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <stdio.h>
+
+#define	LIBPWS_VERSION_MAJOR	1
+#define	LIBPWS_VERSION_MINOR	0
+#define	LIBPWS_VERSION_MICRO	0
+
+#define	PWS3_VERSION		0x030D
+
+#define	PWS3_MAX_FIELD_SIZE	(16 * 1024)
+#define	PWS3_MAX_PASSWORD_LEN	1023
+#define	PWS3_UUID_SIZE		16
+
+struct pws3_field;
+struct pws3_record;
+struct pws3_file;
+
+enum pws_error_code {
+	PWS_ERR_GENERIC_ERROR,
+	PWS_ERR_NO_MEMORY,
+	PWS_ERR_IO_ERROR,
+	PWS_ERR_TRUNCATED_FILE,
+	PWS_ERR_INVALID_CHECKSUM,
+	PWS_ERR_INVALID_RECORD,
+	PWS_ERR_INVALID_HEADER,
+	PWS_ERR_UNSUPPORTED_VERSION
+};
+
+enum pws_data_type {
+	PWS_DATA_TYPE_BYTES,
+	PWS_DATA_TYPE_UUID,
+	PWS_DATA_TYPE_TEXT,
+	PWS_DATA_TYPE_TIME,
+	PWS_DATA_TYPE_UINT8,
+	PWS_DATA_TYPE_UINT16,
+	PWS_DATA_TYPE_UINT32
+};
+
+enum pws3_header_field_type {
+	PWS3_HEADER_FIELD_VERSION,			/* 0x00 */
+	PWS3_HEADER_FIELD_UUID,				/* 0x01 */
+	PWS3_HEADER_FIELD_NON_DEFAULT_PREFERENCES,	/* 0x02 */
+	PWS3_HEADER_FIELD_TREE_DISPLAY_STATUS,		/* 0x03 */
+	PWS3_HEADER_FIELD_SAVE_TIMESTAMP,		/* 0x04 */
+	PWS3_HEADER_FIELD_SAVE_USER_HOST,		/* 0x05 */
+	PWS3_HEADER_FIELD_SAVE_APPLICATION,		/* 0x06 */
+	PWS3_HEADER_FIELD_SAVE_USER,			/* 0x07 */
+	PWS3_HEADER_FIELD_SAVE_HOST,			/* 0x08 */
+	PWS3_HEADER_FIELD_DATABASE_NAME,		/* 0x09 */
+	PWS3_HEADER_FIELD_DATABASE_DESCRIPTION,		/* 0x0a */
+	PWS3_HEADER_FIELD_DATABASE_FILTERS,		/* 0x0b */
+	PWS3_HEADER_FIELD_RESERVED_1,			/* 0x0c */
+	PWS3_HEADER_FIELD_RESERVED_2,			/* 0x0d */
+	PWS3_HEADER_FIELD_RESERVED_3,			/* 0x0e */
+	PWS3_HEADER_FIELD_RECENTLY_USED_ENTRIES,	/* 0x0f */
+	PWS3_HEADER_FIELD_NAMED_PASSWORD_POLICIES,	/* 0x10 */
+	PWS3_HEADER_FIELD_EMPTY_GROUPS,			/* 0x11 */
+	PWS3_HEADER_FIELD_YUBICO,			/* 0x12 */
+	PWS3_HEADER_FIELD_END = 0xff
+};
+
+enum pws3_record_field_type {
+	PWS3_RECORD_FIELD_UUID = 0x01,
+	PWS3_RECORD_FIELD_GROUP,			/* 0x02 */
+	PWS3_RECORD_FIELD_TITLE,			/* 0x03 */
+	PWS3_RECORD_FIELD_USERNAME,			/* 0x04 */
+	PWS3_RECORD_FIELD_NOTES,			/* 0x05 */
+	PWS3_RECORD_FIELD_PASSWORD,			/* 0x06 */
+	PWS3_RECORD_FIELD_CREATION_TIME,		/* 0x07 */
+	PWS3_RECORD_FIELD_PASSWORD_MODIFICATION_TIME,	/* 0x08 */
+	PWS3_RECORD_FIELD_ACCESS_TIME,			/* 0x09 */
+	PWS3_RECORD_FIELD_PASSWORD_EXPIRY_TIME,		/* 0x0a */
+	PWS3_RECORD_FIELD_RESERVED_1,			/* 0x0b */
+	PWS3_RECORD_FIELD_MODIFICATION_TIME,		/* 0x0c */
+	PWS3_RECORD_FIELD_URL,				/* 0x0d */
+	PWS3_RECORD_FIELD_AUTOTYPE,			/* 0x0e */
+	PWS3_RECORD_FIELD_PASSWORD_HISTORY,		/* 0x0f */
+	PWS3_RECORD_FIELD_PASSWORD_POLICY,		/* 0x10 */
+	PWS3_RECORD_FIELD_PASSWORD_EXPIRY_INTERVAL,	/* 0x11 */
+	PWS3_RECORD_FIELD_RUN_COMMAND,			/* 0x12 */
+	PWS3_RECORD_FIELD_DOUBLE_CLICK_ACTION,		/* 0x13 */
+	PWS3_RECORD_FIELD_EMAIL_ADDRESS,		/* 0x14 */
+	PWS3_RECORD_FIELD_PROTECTED,			/* 0x15 */
+	PWS3_RECORD_FIELD_ALLOWED_PASSWORD_SYMBOLS,	/* 0x16 */
+	PWS3_RECORD_FIELD_SHIFT_DOUBLE_CLICK_ACTION,	/* 0x17 */
+	PWS3_RECORD_FIELD_PASSWORD_POLICY_NAME,		/* 0x18 */
+	PWS3_RECORD_FIELD_KEYBOARD_SHORTCUT,		/* 0x19 */
+	PWS3_RECORD_FIELD_END = 0xff
+};
+
+int		pws_init(void);
+void		pws_finalize(void);
+void		pws_set_alloc_functions(void *(*)(size_t),
+    void *(*)(void *, size_t), void (*)(void *, size_t), void *(*)(size_t),
+    void *(*)(void *, size_t), void (*)(void *, size_t));
+int		pws_generate_uuid(unsigned char [static PWS3_UUID_SIZE]);
+
+struct pws3_field * pws3_field_create(int, uint8_t);
+void		pws3_field_destroy(struct pws3_field *);
+int		pws3_field_is_header(struct pws3_field *);
+uint8_t		pws3_field_get_type(struct pws3_field *);
+enum pws_data_type pws3_field_get_data_type(struct pws3_field *);
+int		pws3_field_set_uuid(struct pws3_field *,
+    const unsigned char [static PWS3_UUID_SIZE]);
+int		pws3_field_set_text(struct pws3_field *, const char [static 1]);
+int		pws3_field_set_time(struct pws3_field *, time_t);
+int		pws3_field_set_uint8(struct pws3_field *, uint8_t);
+int		pws3_field_set_uint16(struct pws3_field *, uint16_t);
+int		pws3_field_set_uint32(struct pws3_field *, uint32_t);
+int		pws3_field_set_bytes(struct pws3_field *,
+    const unsigned char [static 1], size_t);
+const unsigned char * pws3_field_get_uuid(struct pws3_field *);
+const char *	pws3_field_get_text(struct pws3_field *);
+time_t		pws3_field_get_time(struct pws3_field *);
+uint8_t		pws3_field_get_uint8(struct pws3_field *);
+uint16_t	pws3_field_get_uint16(struct pws3_field *);
+uint32_t	pws3_field_get_uint32(struct pws3_field *);
+void		pws3_field_get_bytes(struct pws3_field *,
+    const unsigned char **, size_t *);
+
+void		pws3_record_destroy(struct pws3_record *);
+struct pws3_record * pws3_record_create(void);
+void		pws3_record_set_field(struct pws3_record *,
+    struct pws3_field *);
+struct pws3_field * pws3_record_get_field(struct pws3_record *, uint8_t);
+struct pws3_field * pws3_record_remove_field(struct pws3_record *, uint8_t);
+
+void		pws3_file_destroy(struct pws3_file *);
+struct pws3_file * pws3_file_create(void);
+enum pws_error_code pws3_file_get_error_code(struct pws3_file *);
+const char *	pws3_file_get_error_message(struct pws3_file *);
+int		pws3_file_read_mem(struct pws3_file *, const char *,
+    unsigned char *, size_t);
+int		pws3_file_read_stream(struct pws3_file *, const char *, FILE *);
+int		pws3_file_write_mem(struct pws3_file *, const char *, uint32_t,
+    unsigned char **, size_t *);
+int		pws3_file_write_stream(struct pws3_file *, const char *,
+    uint32_t, FILE *);
+
+void		pws3_file_set_header_field(struct pws3_file *,
+    struct pws3_field *);
+struct pws3_field * pws3_file_get_header_field(struct pws3_file *, uint8_t);
+struct pws3_field * pws3_file_remove_header_field(struct pws3_file *, uint8_t);
+
+void		pws3_file_insert_empty_group(struct pws3_file *,
+    struct pws3_field *);
+struct pws3_field * pws3_file_get_empty_group(struct pws3_file *, const char *);
+struct pws3_field * pws3_file_remove_empty_group(struct pws3_file *,
+    const char *);
+struct pws3_field * pws3_file_first_empty_group(struct pws3_file *);
+struct pws3_field * pws3_file_last_empty_group(struct pws3_file *);
+struct pws3_field * pws3_file_next_empty_group(struct pws3_file *,
+    struct pws3_field *);
+struct pws3_field * pws3_file_prev_empty_group(struct pws3_file *,
+    struct pws3_field *);
+
+void		pws3_file_insert_record(struct pws3_file *,
+    struct pws3_record *);
+struct pws3_record * pws3_file_get_record(struct pws3_file *,
+    const unsigned char [static PWS3_UUID_SIZE]);
+struct pws3_record * pws3_file_remove_record(struct pws3_file *,
+    const unsigned char [static PWS3_UUID_SIZE]);
+struct pws3_record * pws3_file_first_record(struct pws3_file *);
+struct pws3_record * pws3_file_last_record(struct pws3_file *);
+struct pws3_record * pws3_file_next_record(struct pws3_file *,
+    struct pws3_record *);
+struct pws3_record * pws3_file_prev_record(struct pws3_file *,
+    struct pws3_record *);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* !PWS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pws-field.c	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "compat.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "pws-internal.h"
+
+static const enum pws_data_type	header_data_types[256] = {
+	[PWS3_HEADER_FIELD_VERSION] = PWS_DATA_TYPE_UINT16,
+	[PWS3_HEADER_FIELD_UUID] = PWS_DATA_TYPE_UUID,
+	[PWS3_HEADER_FIELD_NON_DEFAULT_PREFERENCES] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_TREE_DISPLAY_STATUS] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_SAVE_TIMESTAMP] = PWS_DATA_TYPE_TIME,
+	[PWS3_HEADER_FIELD_SAVE_USER_HOST] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_SAVE_APPLICATION] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_SAVE_USER] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_SAVE_HOST] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_DATABASE_NAME] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_DATABASE_DESCRIPTION] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_DATABASE_FILTERS] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_RESERVED_1] = PWS_DATA_TYPE_BYTES,
+	[PWS3_HEADER_FIELD_RESERVED_2] = PWS_DATA_TYPE_BYTES,
+	[PWS3_HEADER_FIELD_RESERVED_3] = PWS_DATA_TYPE_BYTES,
+	[PWS3_HEADER_FIELD_RECENTLY_USED_ENTRIES] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_NAMED_PASSWORD_POLICIES] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_EMPTY_GROUPS] = PWS_DATA_TYPE_TEXT,
+	[PWS3_HEADER_FIELD_YUBICO] = PWS_DATA_TYPE_TEXT
+};
+
+static const enum pws_data_type	record_data_types[256] = {
+	[PWS3_RECORD_FIELD_UUID] = PWS_DATA_TYPE_UUID,
+	[PWS3_RECORD_FIELD_GROUP] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_TITLE] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_USERNAME] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_NOTES] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_PASSWORD] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_CREATION_TIME] = PWS_DATA_TYPE_TIME,
+	[PWS3_RECORD_FIELD_PASSWORD_MODIFICATION_TIME] = PWS_DATA_TYPE_TIME,
+	[PWS3_RECORD_FIELD_ACCESS_TIME] = PWS_DATA_TYPE_TIME,
+	[PWS3_RECORD_FIELD_PASSWORD_EXPIRY_TIME] = PWS_DATA_TYPE_TIME,
+	[PWS3_RECORD_FIELD_RESERVED_1] = PWS_DATA_TYPE_BYTES,
+	[PWS3_RECORD_FIELD_MODIFICATION_TIME] = PWS_DATA_TYPE_TIME,
+	[PWS3_RECORD_FIELD_URL] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_AUTOTYPE] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_PASSWORD_HISTORY] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_PASSWORD_POLICY] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_PASSWORD_EXPIRY_INTERVAL] = PWS_DATA_TYPE_UINT32,
+	[PWS3_RECORD_FIELD_RUN_COMMAND] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_DOUBLE_CLICK_ACTION] = PWS_DATA_TYPE_BYTES,
+	[PWS3_RECORD_FIELD_EMAIL_ADDRESS] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_PROTECTED] = PWS_DATA_TYPE_UINT8,
+	[PWS3_RECORD_FIELD_ALLOWED_PASSWORD_SYMBOLS] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_SHIFT_DOUBLE_CLICK_ACTION] = PWS_DATA_TYPE_BYTES,
+	[PWS3_RECORD_FIELD_PASSWORD_POLICY_NAME] = PWS_DATA_TYPE_TEXT,
+	[PWS3_RECORD_FIELD_KEYBOARD_SHORTCUT] = PWS_DATA_TYPE_BYTES
+};
+
+struct pws3_field *
+pws3_field_create(int is_header, uint8_t field_type)
+{
+	struct pws3_field	*field;
+
+	field = pws_alloc(sizeof (struct pws3_field));
+	if (field == NULL) {
+		return (NULL);
+	}
+
+	field->is_header = is_header;
+	field->field_type = field_type;
+	field->size = 0;
+	switch (pws3_field_get_data_type(field)) {
+	case PWS_DATA_TYPE_BYTES:
+		field->value.bytes = NULL;
+		break;
+	case PWS_DATA_TYPE_UUID:
+		memset(field->value.uuid, 0, PWS3_UUID_SIZE);
+		break;
+	case PWS_DATA_TYPE_TEXT:
+		field->value.text = NULL;
+		break;
+	case PWS_DATA_TYPE_UINT8:
+		field->value.uint8 = 0;
+		break;
+	case PWS_DATA_TYPE_UINT16:
+		field->value.uint16 = 0;
+		break;
+	case PWS_DATA_TYPE_TIME:   /* FALLTHROUGH */
+	case PWS_DATA_TYPE_UINT32:
+		field->value.uint32 = 0;
+	}
+
+	return (field);
+}
+
+void
+pws3_field_destroy(struct pws3_field *field)
+{
+	if (field == NULL) {
+		return;
+	}
+
+	switch (pws3_field_get_data_type(field)) {
+	case PWS_DATA_TYPE_BYTES:
+		pws_free(field->value.bytes, field->size);
+		break;
+	case PWS_DATA_TYPE_TEXT:
+		if (!field->is_header &&
+		    (field->field_type == PWS3_RECORD_FIELD_PASSWORD)) {
+			pws_secure_free(field->value.text, field->size + 1);
+		} else {
+			pws_free(field->value.text, field->size + 1);
+		}
+		break;
+	default:
+		break;
+	}
+
+	pws_free(field, sizeof (struct pws3_field));
+}
+
+int
+pws3_field_is_header(struct pws3_field *field)
+{
+	return (field->is_header);
+}
+
+uint8_t
+pws3_field_get_type(struct pws3_field *field)
+{
+	return (field->field_type);
+}
+
+enum pws_data_type
+pws3_field_get_data_type(struct pws3_field *field)
+{
+	return (field->is_header ? header_data_types[field->field_type] :
+	    record_data_types[field->field_type]);
+}
+
+int
+pws3_field_set_uuid(struct pws3_field *field,
+    const unsigned char s[static PWS3_UUID_SIZE])
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_UUID);
+
+	field->size = PWS3_UUID_SIZE;
+	memcpy(field->value.uuid, s, PWS3_UUID_SIZE);
+
+	return (0);
+}
+
+int
+pws3_field_set_text(struct pws3_field *field, const char s[static 1])
+{
+	size_t	len;
+	char	*t;
+
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_TEXT);
+
+	len = strlen(s);
+	if (len > PWS3_MAX_FIELD_SIZE) {
+		return (-1);
+	}
+
+	if (!field->is_header &&
+	    (field->field_type == PWS3_RECORD_FIELD_PASSWORD)) {
+		if (len > PWS3_MAX_PASSWORD_LEN) {
+			return (-1);
+		}
+		t = pws_secure_realloc(field->value.text, len + 1);
+	} else {
+		t = pws_realloc(field->value.text, len + 1);
+	}
+	if (t == NULL) {
+		return (-1);
+	}
+	field->value.text = t;
+	field->size = len + 1;
+	memcpy(field->value.text, s, field->size);
+
+	return (0);
+}
+
+int
+pws3_field_set_time(struct pws3_field *field, time_t time)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_TIME);
+
+	field->size = 4;
+	field->value.uint32 = CLAMP(time, 0, UINT32_MAX);
+
+	return (0);
+}
+
+int
+pws3_field_set_uint8(struct pws3_field *field, uint8_t u8)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_UINT8);
+
+	field->size = 1;
+	field->value.uint8 = u8;
+
+	return (0);
+}
+
+int
+pws3_field_set_uint16(struct pws3_field *field, uint16_t u16)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_UINT16);
+
+	field->size = 2;
+	field->value.uint16 = u16;
+
+	return (0);
+}
+
+int
+pws3_field_set_uint32(struct pws3_field *field, uint32_t u32)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_UINT32);
+
+	field->size = 4;
+	field->value.uint32 = u32;
+
+	return (0);
+}
+
+int
+pws3_field_set_bytes(struct pws3_field *field, const unsigned char s[static 1],
+    size_t n)
+{
+	unsigned char	*t;
+
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_BYTES);
+
+	if (n > PWS3_MAX_FIELD_SIZE) {
+		return (-1);
+	}
+
+	t = pws_realloc(field->value.bytes, n);
+	if (t == NULL) {
+		return (-1);
+	}
+	field->size = n;
+	field->value.bytes = t;
+	memcpy(t, s, n);
+
+	return (0);
+}
+
+const unsigned char *
+pws3_field_get_uuid(struct pws3_field *field)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_UUID);
+
+	return (field->value.uuid);
+}
+
+const char *
+pws3_field_get_text(struct pws3_field *field)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_TEXT);
+
+	return (field->value.text);
+}
+
+time_t
+pws3_field_get_time(struct pws3_field *field)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_TIME);
+
+	/* assume time_t can hold at least a INT32_MAX */
+	return ((time_t)CLAMP(field->value.uint32, 0, INT32_MAX));
+}
+
+uint8_t
+pws3_field_get_uint8(struct pws3_field *field)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_UINT8);
+
+	return (field->value.uint8);
+}
+
+uint16_t
+pws3_field_get_uint16(struct pws3_field *field)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_UINT16);
+
+	return (field->value.uint16);
+}
+
+uint32_t
+pws3_field_get_uint32(struct pws3_field *field)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_UINT32);
+
+	return (field->value.uint32);
+}
+
+void
+pws3_field_get_bytes(struct pws3_field *field, const unsigned char **sp,
+    size_t *np)
+{
+	PWS_ASSERT(pws3_field_get_data_type(field) == PWS_DATA_TYPE_BYTES);
+
+	*sp = field->value.bytes;
+	*np = field->size;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pws-file.c	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,1502 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "compat.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#ifdef	HAVE_ENDIAN_H
+#include <endian.h>
+#endif /* HAVE_ENDIAN_H */
+#ifdef	HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#endif /* HAVE_ENDIAN_H */
+#include <nettle/twofish.h>
+#include <nettle/cbc.h>
+#include <nettle/hmac.h>
+#include <nettle/sha.h>
+
+#include "pws-internal.h"
+
+#define	MAX_ITER		(1 << 22)
+#define	DEFAULT_ITER		10000
+#define	KEY_SIZE		32UL
+#define	SALT_SIZE		32UL
+#define	METADATA_SIZE		(sizeof (psafe3_tag) + SALT_SIZE + 4 +\
+    SHA256_DIGEST_SIZE + KEY_SIZE + KEY_SIZE + TWOFISH_BLOCK_SIZE)
+
+static const unsigned char psafe3_tag[] = { 'P', 'W', 'S', '3' };
+static const unsigned char eof_marker[] = { 'P', 'W', 'S', '3', '-', 'E', 'O',
+    'F', 'P', 'W', 'S', '3', '-', 'E', 'O', 'F' };
+
+RB_HEAD(empty_groups_tree, pws3_field);
+
+RB_HEAD(records_tree, pws3_record);
+
+struct pws3_file {
+	struct pws3_field	*fields[256];
+	struct empty_groups_tree *empty_groups_tree;
+	struct records_tree	*records_tree;
+	struct pws_file_error {
+		enum pws_error_code code;
+		int		errnum;
+		char		*msg;
+	} error;
+};
+
+struct twofish_cbc_ctx CBC_CTX(struct twofish_ctx, TWOFISH_BLOCK_SIZE);
+
+struct pws_file_ctx {
+	FILE		*fp;
+	unsigned char	*mem;
+	size_t		mem_size;
+	size_t		mem_pos;
+	struct twofish_cbc_ctx cipher_ctx;
+	struct hmac_sha256_ctx hmac_ctx;
+	struct pws3_file *pws_file;
+	uint32_t	n_iter;
+};
+
+static int	empty_groups_cmp(struct pws3_field *, struct pws3_field *);
+static int	record_cmp(struct pws3_record *, struct pws3_record *);
+RB_PROTOTYPE_STATIC(empty_groups_tree, pws3_field, tree_entry, empty_groups_cmp)
+RB_PROTOTYPE_STATIC(records_tree, pws3_record, tree_entry, record_cmp)
+
+RB_GENERATE_STATIC(empty_groups_tree, pws3_field, tree_entry, empty_groups_cmp)
+
+RB_GENERATE_STATIC(records_tree, pws3_record, tree_entry, record_cmp)
+
+static int
+empty_groups_cmp(struct pws3_field *field1, struct pws3_field *field2)
+{
+	PWS_ASSERT(pws3_field_is_header(field1) &&
+	    pws3_field_is_header(field2));
+	PWS_ASSERT((pws3_field_get_type(field1) ==
+	    PWS3_HEADER_FIELD_EMPTY_GROUPS) &&
+	    (pws3_field_get_type(field2) == PWS3_HEADER_FIELD_EMPTY_GROUPS));
+
+	return (strcmp(pws3_field_get_text(field1),
+	    pws3_field_get_text(field2)));
+}
+
+static int
+record_cmp(struct pws3_record *record1, struct pws3_record *record2)
+{
+	struct pws3_field	*uuid_field1;
+	struct pws3_field	*uuid_field2;
+
+	uuid_field1 = pws3_record_get_field(record1, PWS3_RECORD_FIELD_UUID);
+	uuid_field2 = pws3_record_get_field(record2, PWS3_RECORD_FIELD_UUID);
+	PWS_ASSERT((uuid_field1 != NULL) && (uuid_field2 != NULL));
+
+	return (memcmp(pws3_field_get_uuid(uuid_field1),
+	    pws3_field_get_uuid(uuid_field2), PWS3_UUID_SIZE));
+}
+
+static void
+pws_set_system_error(struct pws3_file *pws_file, enum pws_error_code code,
+    int errnum, const char *fmt, ...)
+{
+	char	system_error_buf[4096] = "";
+	size_t	system_error_len;
+	int	error_len;
+	va_list	args;
+	va_list	args2;
+
+	pws_file->error.code = code;
+	pws_file->error.errnum = errnum;
+
+	strerror_r(errnum, system_error_buf, sizeof (system_error_buf) - 1);
+	system_error_len = strlen(system_error_buf);
+
+	pws_free(pws_file->error.msg, (pws_file->error.msg != NULL) ?
+	    strlen(pws_file->error.msg) + 1 : 0);
+	if (fmt != NULL) {
+		va_start(args, fmt);
+		va_copy(args2, args);
+		error_len = vsnprintf(NULL, 0, fmt, args);
+		pws_file->error.msg = pws_alloc(error_len + 2 +
+		    system_error_len + 1);
+		if (pws_file->error.msg == NULL) {
+			va_end(args2);
+			va_end(args);
+			return;
+		}
+		vsnprintf(pws_file->error.msg, error_len + 1, fmt, args2);
+		va_end(args2);
+		va_end(args);
+		strcpy(pws_file->error.msg + error_len, ": ");
+		strcpy(pws_file->error.msg + error_len + 2, system_error_buf);
+	} else {
+		pws_file->error.msg = pws_alloc(system_error_len + 1);
+		snprintf(pws_file->error.msg, system_error_len + 1, "%s",
+		    system_error_buf);
+	}
+}
+
+static void
+pws_set_error(struct pws3_file *pws_file, enum pws_error_code code,
+    const char *fmt, ...)
+{
+	va_list	args;
+	va_list	args2;
+	int	error_len;
+
+	pws_file->error.code = code;
+	pws_file->error.errnum = 0;
+
+	pws_free(pws_file->error.msg, (pws_file->error.msg != NULL) ?
+	    strlen(pws_file->error.msg) + 1 : 0);
+	va_start(args, fmt);
+	va_copy(args2, args);
+	error_len = vsnprintf(NULL, 0, fmt, args);
+	pws_file->error.msg = pws_alloc(error_len + 1);
+	if (pws_file->error.msg == NULL) {
+		va_end(args2);
+		va_end(args);
+		return;
+	}
+	vsnprintf(pws_file->error.msg, error_len + 1, fmt, args2);
+	va_end(args2);
+	va_end(args);
+}
+
+static int
+read_buf(struct pws_file_ctx *ctx, unsigned char *buf, size_t buf_size)
+{
+	if (ctx->fp != NULL) {
+		if (fread(buf, 1, buf_size, ctx->fp) != buf_size) {
+			if (ferror(ctx->fp) != 0) {
+				return (-1);
+			} else if (feof(ctx->fp) != 0) {
+				return (1);
+			}
+		}
+	} else {
+		PWS_ASSERT(ctx->mem != NULL);
+		if (ctx->mem_size - ctx->mem_pos < buf_size) {
+			return (1);
+		}
+		memcpy(buf, &ctx->mem[ctx->mem_pos], buf_size);
+		ctx->mem_pos += buf_size;
+	}
+
+	return (0);
+}
+
+static int
+write_buf(struct pws_file_ctx *ctx, const unsigned char *buf, size_t buf_size)
+{
+	size_t		remaining;
+	unsigned char	*tmp;
+
+	if (ctx->fp != NULL) {
+		if (fwrite(buf, 1, buf_size, ctx->fp) != buf_size) {
+			return (-1);
+		}
+		if (fflush(ctx->fp) != 0) {
+			return (-1);
+		}
+	} else {
+		remaining = ctx->mem_size - ctx->mem_pos;
+		if (remaining < buf_size) {
+			tmp = pws_realloc(ctx->mem, ctx->mem_size +
+			    (buf_size - remaining));
+			if (tmp == NULL) {
+				return (-1);
+			}
+			ctx->mem = tmp;
+			ctx->mem_size += (buf_size - remaining);
+		}
+		memcpy(&ctx->mem[ctx->mem_pos], buf, buf_size);
+		ctx->mem_pos += buf_size;
+	}
+
+	return (0);
+}
+
+static void
+pws_file_clear(struct pws3_file *pws_file)
+{
+	size_t			i;
+	struct pws3_field	*empty_group_field;
+	struct pws3_field	*empty_group_field_tmp;
+	struct pws3_record	*record;
+	struct pws3_record	*record_tmp;
+
+	for (i = 0x00; i <= 0xff; i++) {
+		pws3_field_destroy(pws_file->fields[i]);
+		pws_file->fields[i] = NULL;
+	}
+
+	RB_FOREACH_SAFE(empty_group_field, empty_groups_tree,
+	    pws_file->empty_groups_tree, empty_group_field_tmp) {
+		pws3_field_destroy(RB_REMOVE(empty_groups_tree,
+		    pws_file->empty_groups_tree, empty_group_field));
+	}
+
+	RB_FOREACH_SAFE(record, records_tree, pws_file->records_tree,
+	    record_tmp) {
+		pws3_record_destroy(RB_REMOVE(records_tree,
+		    pws_file->records_tree, record));
+	}
+}
+
+void
+pws3_file_destroy(struct pws3_file *pws_file)
+{
+	if (pws_file == NULL) {
+		return;
+	}
+
+	pws_free(pws_file->error.msg, (pws_file->error.msg != NULL) ?
+	    strlen(pws_file->error.msg) + 1 : 0);
+	pws_file_clear(pws_file);
+	pws_free(pws_file->empty_groups_tree,
+	    sizeof (struct empty_groups_tree));
+	pws_free(pws_file->records_tree, sizeof (struct records_tree));
+	pws_free(pws_file, sizeof (struct pws3_file));
+}
+
+struct pws3_file *
+pws3_file_create(void)
+{
+	struct pws3_field	*version_field = NULL;
+	struct pws3_file	*pws_file = NULL;
+	size_t			i;
+
+	/* version field is mandatory */
+	version_field = pws3_field_create(1, PWS3_HEADER_FIELD_VERSION);
+	if (version_field == NULL) {
+		goto err;
+	}
+	pws3_field_set_uint16(version_field, PWS3_VERSION);
+
+	pws_file = pws_alloc(sizeof (struct pws3_file));
+	if (pws_file == NULL) {
+		goto err;
+	}
+	for (i = 0x00; i <= 0xff; i++) {
+		pws_file->fields[i] = NULL;
+	}
+	pws_file->empty_groups_tree = NULL;
+	pws_file->records_tree = NULL;
+	pws_file->error.errnum = 0;
+	pws_file->error.code = 0;
+	pws_file->error.msg = NULL;
+
+	pws_file->empty_groups_tree =
+	    pws_alloc(sizeof (struct empty_groups_tree));
+	if (pws_file->empty_groups_tree == NULL) {
+		goto err;
+	}
+	RB_INIT(pws_file->empty_groups_tree);
+
+	pws_file->records_tree = pws_alloc(sizeof (struct records_tree));
+	if (pws_file->records_tree == NULL) {
+		goto err;
+	}
+	RB_INIT(pws_file->records_tree);
+
+	pws3_file_set_header_field(pws_file, version_field);
+
+	return (pws_file);
+err:
+	pws3_field_destroy(version_field);
+	if (pws_file != NULL) {
+		pws_free(pws_file->records_tree, sizeof (struct records_tree));
+		pws_free(pws_file->empty_groups_tree,
+		    sizeof (struct empty_groups_tree));
+	}
+	pws_free(pws_file, sizeof (struct pws3_file));
+
+	return (NULL);
+}
+
+enum pws_error_code
+pws3_file_get_error_code(struct pws3_file *pws_file)
+{
+	return (pws_file->error.code);
+}
+
+const char *
+pws3_file_get_error_message(struct pws3_file *pws_file)
+{
+	return ((pws_file->error.msg != NULL) ? pws_file->error.msg : "");
+}
+
+static void
+stretch_key(unsigned char *stretched_key, uint32_t n_iter, const char *key,
+    size_t key_size, const unsigned char *salt, size_t salt_size)
+{
+	uint32_t	i;
+	struct sha256_ctx md_ctx;
+
+	sha256_init(&md_ctx);
+	sha256_update(&md_ctx, key_size, (uint8_t *)key);
+	sha256_update(&md_ctx, salt_size, salt);
+	sha256_digest(&md_ctx, SHA256_DIGEST_SIZE, stretched_key);
+
+	for (i = 0; i < n_iter; i++) {
+		sha256_update(&md_ctx, SHA256_DIGEST_SIZE, stretched_key);
+		sha256_digest(&md_ctx, SHA256_DIGEST_SIZE, stretched_key);
+	}
+}
+
+static int
+read_metadata(struct pws_file_ctx *ctx, const char *password)
+{
+	int		retval = -1;
+	unsigned char	buf[METADATA_SIZE];
+	unsigned char	*p = buf;
+	unsigned char	*stretched_key = NULL;
+	unsigned char	*key_k = NULL;
+	unsigned char	*key_l = NULL;
+	int		read_retval;
+	unsigned char	salt[SALT_SIZE];
+	unsigned char	key_digest[SHA256_DIGEST_SIZE];
+	struct sha256_ctx md_ctx;
+	struct twofish_ctx cipher_ctx;
+
+	stretched_key = pws_secure_alloc(SHA256_DIGEST_SIZE);
+	if (stretched_key == NULL) {
+		pws_set_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+		    "out of memory");
+		goto out;
+	}
+
+	key_k = pws_secure_alloc(KEY_SIZE);
+	if (key_k == NULL) {
+		pws_set_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+		    "out of memory");
+		goto out;
+	}
+
+	key_l = pws_secure_alloc(KEY_SIZE);
+	if (key_l == NULL) {
+		pws_set_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+		    "out of memory");
+		goto out;
+	}
+
+	read_retval = read_buf(ctx, buf, sizeof (buf));
+	if (read_retval == 1) {
+		pws_set_error(ctx->pws_file, PWS_ERR_TRUNCATED_FILE,
+		    "unexpected end of file");
+		goto out;
+	} else if (read_retval != 0) {
+		pws_set_system_error(ctx->pws_file, PWS_ERR_IO_ERROR, errno,
+		    NULL);
+		goto out;
+	}
+
+	/* check tag */
+	if (memcmp(p, psafe3_tag, sizeof (psafe3_tag)) != 0) {
+		pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+		    "unknown filetype");
+		goto out;
+	}
+	p += sizeof (psafe3_tag);
+
+	/* salt */
+	memcpy(salt, p, SALT_SIZE);
+	p += SALT_SIZE;
+
+	/* iterations */
+	memcpy(&ctx->n_iter, p, 4);
+	ctx->n_iter = le32toh(ctx->n_iter);
+	if ((ctx->n_iter < 1) || (ctx->n_iter > MAX_ITER)) {
+		pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+		    "invalid number of iterations: %d", ctx->n_iter);
+		goto out;
+	}
+	p += 4;
+
+	/* verify password */
+	stretch_key(stretched_key, ctx->n_iter, password, strlen(password),
+	    salt, SALT_SIZE);
+	sha256_init(&md_ctx);
+	sha256_update(&md_ctx, SHA256_DIGEST_SIZE, stretched_key);
+	sha256_digest(&md_ctx, SHA256_DIGEST_SIZE, key_digest);
+	if (memcmp(key_digest, p, SHA256_DIGEST_SIZE) != 0) {
+		pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+		    "wrong password");
+		goto out;
+	}
+	p += SHA256_DIGEST_SIZE;
+
+	/* decrypt keys */
+	twofish_set_key(&cipher_ctx, KEY_SIZE, stretched_key);
+	twofish_decrypt(&cipher_ctx, KEY_SIZE, key_k, p);
+	p += KEY_SIZE;
+	twofish_decrypt(&cipher_ctx, KEY_SIZE, key_l, p);
+	p += KEY_SIZE;
+
+	/* set key for decryption */
+	twofish_set_key(&ctx->cipher_ctx.ctx, KEY_SIZE, key_k);
+
+	/* set IV */
+	CBC_SET_IV(&ctx->cipher_ctx, p);
+
+	/* set key for HMAC */
+	HMAC_SET_KEY(&ctx->hmac_ctx, &nettle_sha256, KEY_SIZE, key_l);
+
+	retval = 0;
+
+out:
+	pws_secure_free(stretched_key, (stretched_key != NULL) ?
+	    SHA256_DIGEST_SIZE : 0);
+	pws_secure_free(key_l, (key_l != NULL) ? KEY_SIZE : 0);
+	pws_secure_free(key_k, (key_k != NULL) ? KEY_SIZE : 0);
+
+	return (retval);
+}
+
+static int
+read_block(struct pws_file_ctx *ctx, unsigned char *block)
+{
+	unsigned char	buf[TWOFISH_BLOCK_SIZE];
+	int		read_retval;
+
+	read_retval = read_buf(ctx, buf, sizeof (buf));
+	if (read_retval == 1) {
+		pws_set_error(ctx->pws_file, PWS_ERR_TRUNCATED_FILE,
+		    "unexpected end of file");
+		return (-1);
+	} else if (read_retval != 0) {
+		pws_set_system_error(ctx->pws_file, PWS_ERR_IO_ERROR, errno,
+		    NULL);
+		return (-1);
+	}
+
+	/* reached the EOF block marking the end of encrypted records */
+	if (memcmp(buf, eof_marker, TWOFISH_BLOCK_SIZE) == 0) {
+		return (1);
+	}
+
+	CBC_DECRYPT(&ctx->cipher_ctx, twofish_decrypt, TWOFISH_BLOCK_SIZE,
+	    block, buf);
+
+	return (0);
+}
+
+static int
+read_field(struct pws_file_ctx *ctx, struct pws3_field **fieldp, int is_header)
+{
+	int		retval = -1;
+	enum pws_data_type data_type;
+	struct pws3_field *field = NULL;
+	unsigned char	*block_buf = NULL;
+	unsigned char	*p;
+	int		read_retval;
+	unsigned char	*field_buf = NULL;
+	uint32_t	field_size;
+	size_t		remaining;
+	size_t		field_buf_size = 0;
+	uint8_t field_type = 0xff;
+	time_t		data_time;
+	uint32_t	data_uint32;
+	uint16_t	data_uint16;
+	uint8_t		data_uint8;
+
+	block_buf = pws_secure_alloc(TWOFISH_BLOCK_SIZE);
+	if (block_buf == NULL) {
+		pws_set_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+		    "out of memory");
+		goto out;
+	}
+
+next_field:
+	p = block_buf;
+
+	/* read first block */
+	read_retval = read_block(ctx, block_buf);
+	if (read_retval != 0) {
+		retval = read_retval;
+		goto out;
+	}
+
+	/* determine field length */
+	memcpy(&field_size, p, 4);
+	remaining = field_buf_size = field_size = le32toh(field_size);
+	p += 4;
+	/* determine field type */
+	memcpy(&field_type, p, 1);
+	p++;
+
+	/* check for end of header fields or end of record */
+	if ((is_header && (field_type == PWS3_HEADER_FIELD_END)) ||
+	    (!is_header && (field_type == PWS3_RECORD_FIELD_END))) {
+		retval = 1;
+		goto out;
+	}
+
+	/* skip empty fields */
+	if (field_size == 0) {
+		goto next_field;
+	}
+
+	/* determine data type */
+	data_type = pws3_field_get_data_type(&(struct pws3_field){ .is_header =
+	    is_header, .field_type = field_type });
+
+	/* make room for a terminating \0 in text fields */
+	if (data_type == PWS_DATA_TYPE_TEXT) {
+		field_buf_size++;
+	}
+
+	/* validate field length */
+	switch (data_type) {
+	case PWS_DATA_TYPE_UUID:
+		if ((field_size != 0) && (field_size != PWS3_UUID_SIZE)) {
+			pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+			    "invalid field length");
+			goto out;
+		}
+		break;
+	case PWS_DATA_TYPE_TIME:  /* FALLTHROUGH */
+	case PWS_DATA_TYPE_UINT32:
+		if ((field_size != 0) && (field_size != 4)) {
+			pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+			    "invalid field length");
+			goto out;
+		}
+		break;
+	case PWS_DATA_TYPE_UINT8:
+		if ((field_size != 0) && (field_size != 1)) {
+			pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+			    "invalid field length");
+			goto out;
+		}
+		break;
+	case PWS_DATA_TYPE_UINT16:
+		if ((field_size != 0) && (field_size != 2)) {
+			pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+			    "invalid field length");
+			goto out;
+		}
+		break;
+	default:
+		/* text or bytes */
+		if ((!is_header &&
+		    (field_type == PWS3_RECORD_FIELD_PASSWORD) &&
+		    (field_buf_size > PWS3_MAX_PASSWORD_LEN)) ||
+		    (field_buf_size > PWS3_MAX_FIELD_SIZE)) {
+			pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+			    "invalid field length");
+			goto out;
+		}
+	}
+
+	field = pws3_field_create(is_header, field_type);
+	if (field == NULL) {
+		pws_set_system_error(ctx->pws_file, PWS_ERR_NO_MEMORY, errno,
+		    NULL);
+		goto out;
+	}
+
+	/* create field */
+	if (field_buf_size > 0) {
+		if (!is_header && (field_type == PWS3_RECORD_FIELD_PASSWORD)) {
+			field_buf = pws_secure_alloc(field_buf_size);
+		} else {
+			field_buf = pws_alloc(field_buf_size);
+		}
+		if (field_buf == NULL) {
+			pws_set_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+			    "out of memory");
+			goto out;
+		}
+		memset(field_buf, 0, field_buf_size);
+
+		memcpy(field_buf, p, MIN(remaining,
+		    (size_t)TWOFISH_BLOCK_SIZE - (p - block_buf)));
+		remaining -= MIN(remaining,
+		    (size_t)TWOFISH_BLOCK_SIZE - (p - block_buf));
+
+		while (remaining > 0) {
+			read_retval = read_block(ctx, block_buf);
+			if (read_retval != 0) {
+				goto out;
+			}
+			memcpy(field_buf + (field_size - remaining),
+			    block_buf, MIN(remaining, TWOFISH_BLOCK_SIZE));
+			remaining -= MIN(remaining, TWOFISH_BLOCK_SIZE);
+		}
+
+		hmac_sha256_update(&ctx->hmac_ctx, field_size, field_buf);
+
+		switch (data_type) {
+		case PWS_DATA_TYPE_UUID:
+			retval = pws3_field_set_uuid(field, field_buf);
+			break;
+		case PWS_DATA_TYPE_TEXT:
+			retval = pws3_field_set_text(field, (char *)field_buf);
+			break;
+		case PWS_DATA_TYPE_TIME:
+			memcpy(&data_uint32, field_buf, 4);
+			data_time = le32toh(data_uint32);
+			retval = pws3_field_set_time(field, data_time);
+			break;
+		case PWS_DATA_TYPE_UINT8:
+			memcpy(&data_uint8, field_buf, 1);
+			retval = pws3_field_set_uint8(field, data_uint8);
+			break;
+		case PWS_DATA_TYPE_UINT16:
+			memcpy(&data_uint16, field_buf, 2);
+			data_uint16 = le16toh(data_uint16);
+			retval = pws3_field_set_uint16(field, data_uint16);
+			break;
+		case PWS_DATA_TYPE_UINT32:
+			memcpy(&data_uint32, field_buf, 4);
+			data_uint32 = le32toh(data_uint32);
+			retval = pws3_field_set_uint32(field, data_uint32);
+			break;
+		case PWS_DATA_TYPE_BYTES:
+			retval = pws3_field_set_bytes(field, field_buf,
+			    field_buf_size);
+		}
+		if (retval != 0) {
+			goto out;
+		}
+	}
+
+	retval = 0;
+
+out:
+	if (!is_header && (field_type == PWS3_RECORD_FIELD_PASSWORD)) {
+		pws_secure_free(field_buf, field_buf_size);
+	} else {
+		pws_free(field_buf, field_buf_size);
+	}
+	pws_secure_free(block_buf, (block_buf != NULL) ?
+	    (size_t)TWOFISH_BLOCK_SIZE : 0);
+
+	if (retval == 0) {
+		*fieldp = field;
+	} else {
+		pws3_field_destroy(field);
+	}
+
+	return (retval);
+}
+
+static int
+read_header(struct pws_file_ctx *ctx)
+{
+	int		retval;
+	struct pws3_field *field = NULL;
+
+	/* the header must start with a version field */
+	retval = read_field(ctx, &field, 1);
+	if (retval != 0) {
+		/* error or end of headers */
+		pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+		    "header does not start with a version field");
+		return (-1);
+	} else if (field->field_type != PWS3_HEADER_FIELD_VERSION) {
+		/* header does not start with a version field */
+		pws3_field_destroy(field);
+		pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+		    "header does not start with a version field");
+		return (-1);
+	} else if (field->value.uint16 > PWS3_VERSION) {
+		/* unsupported database version */
+		pws3_field_destroy(field);
+		pws_set_error(ctx->pws_file, PWS_ERR_UNSUPPORTED_VERSION,
+		    "unsupported database version");
+		return (-1);
+	}
+	pws3_file_set_header_field(ctx->pws_file, field);
+
+	for (;;) {
+		retval = read_field(ctx, &field, 1);
+		if (retval == 1) {
+			/* end of headers */
+			pws_set_error(ctx->pws_file, PWS_ERR_INVALID_HEADER,
+			    "unexpected end of headers");
+			break;
+		} else if (retval != 0) {
+			return (-1);
+		}
+		pws3_file_set_header_field(ctx->pws_file, field);
+	}
+
+	return (0);
+}
+
+static int
+read_records(struct pws_file_ctx *ctx)
+{
+	int		retval;
+	struct pws3_record *record = NULL;
+	struct pws3_field *field = NULL;
+
+	for (;;) {
+		/*
+		 * a record must consist of at least three fields, instead of
+		 * the first field there could also be an EOF marker
+		 */
+		retval = read_field(ctx, &field, 0);
+		if (retval == 1) {
+			/* EOF marker */
+			retval = 0;
+			goto out;
+		} else if (retval != 0) {
+			/* read error */
+			goto out;
+		} else if (field->field_type == PWS3_RECORD_FIELD_END) {
+			/* empty record */
+			retval = -1;
+			goto out;
+		}
+
+		record = pws3_record_create();
+		if (record == NULL) {
+			pws_set_system_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+			    errno, NULL);
+			goto out;
+		}
+
+		pws3_record_set_field(record, field);
+		field = NULL;
+
+		/* read the remaining fileds */
+		for (;;) {
+			retval = read_field(ctx, &field, 0);
+			if (retval == 1) {
+				/* end of record */
+				break;
+			} else if (retval != 0) {
+				/* read error */
+				retval = -1;
+				goto out;
+			}
+			pws3_record_set_field(record, field);
+			field = NULL;
+		}
+
+		/* check whether UUID is not empty */
+		if (pws3_record_get_field(record, PWS3_RECORD_FIELD_UUID) ==
+		    NULL) {
+			/* record is missing mandatory fields */
+			pws_set_error(ctx->pws_file, PWS_ERR_INVALID_RECORD,
+			    "record is missing mandatory fields");
+			pws3_record_destroy(record);
+			retval = -1;
+			goto out;
+		}
+
+		pws3_file_insert_record(ctx->pws_file, record);
+		record = NULL;
+	}
+
+out:
+	if (retval != 0) {
+		pws3_field_destroy(field);
+		pws3_record_destroy(record);
+	}
+
+	return (retval);
+}
+
+static int
+verify_checksum(struct pws_file_ctx *ctx)
+{
+	int		retval;
+	unsigned char	hmac_file[SHA256_DIGEST_SIZE];
+	unsigned char	hmac[SHA256_DIGEST_SIZE];
+
+	retval = read_buf(ctx, hmac_file, sizeof (hmac_file));
+	if (retval == 1) {
+		pws_set_error(ctx->pws_file, PWS_ERR_TRUNCATED_FILE,
+		    "unexpected end of file");
+		return (-1);
+	} else if (retval != 0) {
+		pws_set_system_error(ctx->pws_file, PWS_ERR_IO_ERROR, errno,
+		    NULL);
+		return (-1);
+	}
+
+	hmac_sha256_digest(&ctx->hmac_ctx, sizeof (hmac), hmac);
+	if (memcmp(hmac_file, hmac, sizeof (hmac_file)) != 0) {
+		/* inconsistent database */
+		pws_set_error(ctx->pws_file, PWS_ERR_INVALID_CHECKSUM,
+		    "checksum failed");
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+pws3_file_read(struct pws3_file *pws_file, const char *password,
+    unsigned char *s, size_t n, FILE *fp)
+{
+	int		retval = -1;
+	struct pws_file_ctx	ctx = {
+		.fp = fp,
+		.mem = s,
+		.mem_size = n,
+		.mem_pos = 0,
+		.pws_file = pws_file,
+	};
+
+	pws_file_clear(pws_file);
+
+	retval = read_metadata(&ctx, password);
+	if (retval != 0) {
+		goto out;
+	}
+
+	retval = read_header(&ctx);
+	if (retval != 0) {
+		goto out;
+	}
+
+	retval = read_records(&ctx);
+	if (retval != 0) {
+		goto out;
+	}
+
+	retval = verify_checksum(&ctx);
+	if (retval != 0) {
+		goto out;
+	}
+
+out:
+	if (retval != 0) {
+		pws_file_clear(ctx.pws_file);
+	}
+
+	return (retval);
+}
+
+int
+pws3_file_read_mem(struct pws3_file *pws_file, const char *password,
+    unsigned char *s, size_t n)
+{
+	return (pws3_file_read(pws_file, password, s, n, NULL));
+}
+
+int
+pws3_file_read_stream(struct pws3_file *pws_file, const char *password,
+    FILE *fp)
+{
+	return (pws3_file_read(pws_file, password, NULL, 0, fp));
+}
+
+static int
+write_metadata(struct pws_file_ctx *ctx, const char *password)
+{
+	int		retval = -1;
+	unsigned char	*stretched_key = NULL;
+	unsigned char	*key_k = NULL;
+	unsigned char	*key_l = NULL;
+	unsigned char	metadata[METADATA_SIZE];
+	unsigned char	*p = metadata;
+	unsigned char	*salt;
+	uint32_t	n_iter_le;
+	struct sha256_ctx md_ctx;
+	unsigned char	*b1;
+	unsigned char	*b3;
+	unsigned char	*iv;
+	struct twofish_ctx cipher_ctx;
+
+	stretched_key = pws_secure_alloc(SHA256_DIGEST_SIZE);
+	if (stretched_key == NULL) {
+		pws_set_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+		    "out of memory");
+		goto out;
+	}
+
+	key_k = pws_secure_alloc(KEY_SIZE);
+	if (key_k == NULL) {
+		pws_set_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+		    "out of memory");
+		goto out;
+	}
+
+	key_l = pws_secure_alloc(KEY_SIZE);
+	if (key_l == NULL) {
+		pws_set_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+		    "out of memory");
+		goto out;
+	}
+
+	/* generate new keys */
+	if (pws_random_bytes(key_k, KEY_SIZE) != 0) {
+		pws_set_error(ctx->pws_file, PWS_ERR_GENERIC_ERROR,
+		    "failed to generate key");
+		goto out;
+	}
+
+	if (pws_random_bytes(key_l, KEY_SIZE) != 0) {
+		pws_set_error(ctx->pws_file, PWS_ERR_GENERIC_ERROR,
+		    "failed to generate key");
+		goto out;
+	}
+
+	/* tag */
+	memcpy(p, psafe3_tag, sizeof (psafe3_tag));
+	p += sizeof (psafe3_tag);
+
+	/* generate new salt */
+	salt = p;
+	if (pws_random_bytes(salt, SALT_SIZE) != 0) {
+		pws_set_error(ctx->pws_file, PWS_ERR_GENERIC_ERROR,
+		    "failed to generate salt");
+		goto out;
+	}
+	p += SALT_SIZE;
+
+	/* number of iterations */
+	n_iter_le = htole32(ctx->n_iter);
+	memcpy(p, &n_iter_le, 4);
+	p += 4;
+
+	/* stretch, hash password */
+	stretch_key(stretched_key, ctx->n_iter, password, strlen(password),
+	    salt, SALT_SIZE);
+	sha256_init(&md_ctx);
+	sha256_update(&md_ctx, SHA256_DIGEST_SIZE, stretched_key);
+	sha256_digest(&md_ctx, SHA256_DIGEST_SIZE, p);
+	p += SHA256_DIGEST_SIZE;
+
+	b1 = p;
+	p += KEY_SIZE;
+
+	b3 = p;
+	p += KEY_SIZE;
+
+	/* generate IV */
+	iv = p;
+	if (pws_random_bytes(iv, TWOFISH_BLOCK_SIZE) != 0) {
+		pws_set_error(ctx->pws_file, PWS_ERR_GENERIC_ERROR,
+		    "failed to generate IV");
+		goto out;
+	}
+
+	/* encrypt keys */
+	twofish_set_key(&cipher_ctx, KEY_SIZE, stretched_key);
+	twofish_encrypt(&cipher_ctx, KEY_SIZE, b1, key_k);
+	twofish_encrypt(&cipher_ctx, KEY_SIZE, b3, key_l);
+
+	/* set key for decryption */
+	twofish_set_key(&ctx->cipher_ctx.ctx, KEY_SIZE, key_k);
+
+	/* set IV */
+	CBC_SET_IV(&ctx->cipher_ctx, p);
+
+	/* set key for HMAC */
+	hmac_sha256_set_key(&ctx->hmac_ctx, KEY_SIZE, key_l);
+
+	/* write metadata */
+	if (write_buf(ctx, metadata, sizeof (metadata)) != 0) {
+		pws_set_system_error(ctx->pws_file, PWS_ERR_IO_ERROR, errno,
+		    NULL);
+		retval = -1;
+		goto out;
+	}
+
+	retval = 0;
+
+out:
+	pws_secure_free(key_k, (key_k != NULL) ? KEY_SIZE : 0);
+	pws_secure_free(key_l, (key_l != NULL) ? KEY_SIZE : 0);
+	pws_secure_free(stretched_key, (stretched_key != NULL) ?
+	    SHA256_DIGEST_SIZE : 0);
+
+	return (retval);
+}
+
+static int
+write_block(struct pws_file_ctx *ctx, unsigned char *block)
+{
+	unsigned char	buf[TWOFISH_BLOCK_SIZE];
+
+	CBC_ENCRYPT(&ctx->cipher_ctx, twofish_encrypt, sizeof (buf), buf,
+	    block);
+
+	if (write_buf(ctx, buf, sizeof (buf)) != 0) {
+		pws_set_system_error(ctx->pws_file, PWS_ERR_IO_ERROR, errno,
+		    NULL);
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+write_field(struct pws_file_ctx *ctx, struct pws3_field *field)
+{
+	int		retval = -1;
+	unsigned char	*buf = NULL;
+	unsigned char	*p;
+	unsigned char	*field_data;
+	enum pws_data_type data_type;
+	size_t		blocks = (field->size + 4 + 1) / TWOFISH_BLOCK_SIZE +
+	    ((field->size + 4 + 1) % TWOFISH_BLOCK_SIZE != 0);
+	size_t		i;
+	size_t		j;
+	uint32_t	len_le;
+
+	buf = pws_secure_alloc(TWOFISH_BLOCK_SIZE);
+	if (buf == NULL) {
+		pws_set_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+		    "out of memory");
+		goto out;
+	}
+
+	data_type = pws3_field_get_data_type(field);
+
+	for (i = 0, j = 0; i < blocks; i++) {
+		p = field_data = buf;
+		if (pws_random_bytes(buf, TWOFISH_BLOCK_SIZE) != 0) {
+			pws_set_error(ctx->pws_file, PWS_ERR_GENERIC_ERROR,
+			    "could not get random numbers");
+			goto out;
+		}
+
+		/* the first block of the field contains the length and type */
+		if (i == 0) {
+			len_le = htole32(field->size);
+			memcpy(p, &len_le, 4);
+			p += 4;
+
+			*p = field->field_type;
+			p++;
+			field_data = p;
+		}
+
+		while ((j < field->size) &&
+		    (p - buf < (ptrdiff_t)TWOFISH_BLOCK_SIZE)) {
+			switch (data_type) {
+			case PWS_DATA_TYPE_UINT8:
+				*p = field->value.uint8;
+				break;
+			case PWS_DATA_TYPE_UINT16:
+				/* little endian */
+				*p = (field->value.uint16 >> (8 * j)) & 0xff;
+				break;
+			case PWS_DATA_TYPE_TIME: /* FALLTHROUGH */
+			case PWS_DATA_TYPE_UINT32:
+				/* little endian */
+				*p = (field->value.uint32 >> (8 * j)) & 0xff;
+				break;
+			case PWS_DATA_TYPE_TEXT:
+				*p = field->value.text[j];
+				break;
+			case PWS_DATA_TYPE_UUID:
+				*p = field->value.uuid[j];
+				break;
+			default:
+				*p = field->value.bytes[j];
+			}
+
+			p++;
+			j++;
+		}
+
+		hmac_sha256_update(&ctx->hmac_ctx, p - field_data, field_data);
+
+		retval = write_block(ctx, buf);
+		if (retval != 0) {
+			goto out;
+		}
+	}
+
+	retval = 0;
+
+out:
+	pws_secure_free(buf, (buf != NULL) ? TWOFISH_BLOCK_SIZE : 0);
+
+	return (retval);
+}
+
+static int
+write_header(struct pws_file_ctx *ctx)
+{
+	int		retval = -1;
+	size_t		i;
+	struct pws3_field *version_field;
+	struct pws3_field *field = NULL;
+	struct pws3_field *end_field = NULL;
+
+	version_field = pws3_file_get_header_field(ctx->pws_file,
+	    PWS3_HEADER_FIELD_VERSION);
+	if (version_field == NULL) {
+		/* add mandatory version header version_field if necessary */
+		version_field = pws3_field_create(1, PWS3_HEADER_FIELD_VERSION);
+		if (version_field == NULL) {
+			pws_set_system_error(ctx->pws_file, PWS_ERR_NO_MEMORY,
+			    errno, NULL);
+			goto out;
+		}
+		pws3_field_set_uint16(version_field, PWS3_VERSION);
+		pws3_file_set_header_field(ctx->pws_file, version_field);
+	}
+	retval = write_field(ctx, version_field);
+	if (retval != 0) {
+		goto out;
+	}
+
+	for (i = 0x01; i < 0xff; i++) {
+		if (ctx->pws_file->fields[i] != NULL) {
+			retval = write_field(ctx, ctx->pws_file->fields[i]);
+			if (retval != 0) {
+				goto out;
+			}
+		}
+	}
+
+	RB_FOREACH(field, empty_groups_tree, ctx->pws_file->empty_groups_tree) {
+		retval = write_field(ctx, field);
+		if (retval != 0) {
+			goto out;
+		}
+	}
+
+	end_field = pws3_field_create(1, PWS3_HEADER_FIELD_END);
+	retval = write_field(ctx, end_field);
+	if (retval != 0) {
+		goto out;
+	}
+
+out:
+	pws3_field_destroy(end_field);
+
+	return (retval);
+}
+
+static int
+write_records(struct pws_file_ctx *ctx)
+{
+	int		retval = -1;
+	struct pws3_field *end_field = NULL;
+	size_t		i;
+	struct pws3_record *record;
+
+	end_field = pws3_field_create(0, PWS3_RECORD_FIELD_END);
+	if (end_field == NULL) {
+		pws_set_system_error(ctx->pws_file, PWS_ERR_NO_MEMORY, errno,
+		    NULL);
+		goto out;
+	}
+
+	RB_FOREACH(record, records_tree, ctx->pws_file->records_tree) {
+		/* record fields */
+		for (i = 0x01; i < 0xff; i++) {
+			if (record->fields[i] != NULL) {
+				retval = write_field(ctx, record->fields[i]);
+				if (retval != 0) {
+					goto out;
+				}
+			}
+		}
+
+		/* end of entry marker */
+		retval = write_field(ctx, end_field);
+		if (retval != 0) {
+			goto out;
+		}
+	}
+
+	/* end of file marker */
+	if (write_buf(ctx, eof_marker, sizeof (eof_marker)) != 0) {
+		pws_set_system_error(ctx->pws_file, PWS_ERR_IO_ERROR, errno,
+		    NULL);
+		retval = -1;
+		goto out;
+	}
+
+	retval = 0;
+
+out:
+	pws3_field_destroy(end_field);
+
+	return (retval);
+}
+
+static int
+write_checksum(struct pws_file_ctx *ctx)
+{
+	unsigned char	hmac[SHA256_DIGEST_SIZE];
+
+	hmac_sha256_digest(&ctx->hmac_ctx, sizeof (hmac), hmac);
+
+	if (write_buf(ctx, hmac, sizeof (hmac)) != 0) {
+		pws_set_system_error(ctx->pws_file, PWS_ERR_IO_ERROR, errno,
+		    NULL);
+		return (-1);
+	}
+
+	return (0);
+}
+
+static int
+pws3_file_write(struct pws3_file *pws_file, const char *password,
+    uint32_t n_iter, unsigned char **memp, size_t *mem_sizep, FILE *fp)
+{
+	int		retval = -1;
+	struct pws_file_ctx	ctx = {
+		.fp = fp,
+		.pws_file = pws_file,
+		.n_iter = n_iter
+	};
+
+	retval = write_metadata(&ctx, password);
+	if (retval != 0) {
+		goto out;
+	}
+
+	retval = write_header(&ctx);
+	if (retval != 0) {
+		goto out;
+	}
+
+	retval = write_records(&ctx);
+	if (retval != 0) {
+		goto out;
+	}
+
+	retval = write_checksum(&ctx);
+	if (retval != 0) {
+		goto out;
+	}
+
+	if (memp != NULL) {
+		*memp = ctx.mem;
+		*mem_sizep = ctx.mem_size;
+	}
+
+out:
+	if (retval != 0) {
+		pws_free(ctx.mem, ctx.mem_size);
+	}
+
+	return (retval);
+}
+
+int
+pws3_file_write_mem(struct pws3_file *pws_file, const char *password,
+    uint32_t n_iter, unsigned char **memp, size_t *mem_sizep)
+{
+	PWS_ASSERT(memp != NULL);
+	PWS_ASSERT(mem_sizep != NULL);
+
+	return (pws3_file_write(pws_file, password, n_iter, memp, mem_sizep,
+	    NULL));
+}
+
+int
+pws3_file_write_stream(struct pws3_file *pws_file, const char *password,
+    uint32_t n_iter, FILE *fp)
+{
+	PWS_ASSERT(fp != NULL);
+
+	return (pws3_file_write(pws_file, password, n_iter, NULL, NULL, fp));
+}
+
+void
+pws3_file_set_header_field(struct pws3_file *pws_file, struct pws3_field *field)
+{
+	PWS_ASSERT(pws3_field_is_header(field));
+	PWS_ASSERT((pws3_field_get_data_type(field) != PWS_DATA_TYPE_TEXT) ||
+	    (field->value.text != NULL));
+	PWS_ASSERT((pws3_field_get_data_type(field) != PWS_DATA_TYPE_BYTES) ||
+	    (field->value.bytes != NULL));
+
+	if (field->field_type == PWS3_HEADER_FIELD_EMPTY_GROUPS) {
+		pws3_file_insert_empty_group(pws_file, field);
+		return;
+	}
+
+	pws3_field_destroy(pws3_file_remove_header_field(pws_file,
+	    field->field_type));
+	pws_file->fields[field->field_type] = field;
+}
+
+struct pws3_field *
+pws3_file_get_header_field(struct pws3_file *pws_file, uint8_t field_type)
+{
+	if (field_type == PWS3_HEADER_FIELD_EMPTY_GROUPS) {
+		return (pws3_file_first_empty_group(pws_file));
+	}
+
+	return (pws_file->fields[field_type]);
+}
+
+struct pws3_field *
+pws3_file_remove_header_field(struct pws3_file *pws_file, uint8_t field_type)
+{
+	struct pws3_field	*field;
+
+	if (field_type == PWS3_HEADER_FIELD_EMPTY_GROUPS) {
+		return (NULL);
+	}
+
+	field = pws3_file_get_header_field(pws_file, field_type);
+	pws_file->fields[field_type] = NULL;
+
+	return (field);
+}
+
+void
+pws3_file_insert_empty_group(struct pws3_file *pws_file,
+    struct pws3_field *field)
+{
+	const char	*group_name;
+
+	PWS_ASSERT(pws3_field_is_header(field));
+	PWS_ASSERT(pws3_field_get_type(field) ==
+	    PWS3_HEADER_FIELD_EMPTY_GROUPS);
+
+	group_name = pws3_field_get_text(field);
+	pws3_field_destroy(pws3_file_remove_empty_group(pws_file, group_name));
+	RB_INSERT(empty_groups_tree, pws_file->empty_groups_tree, field);
+}
+
+struct pws3_field *
+pws3_file_get_empty_group(struct pws3_file *pws_file, const char *group_name)
+{
+	return (RB_FIND(empty_groups_tree, pws_file->empty_groups_tree,
+	    (&(struct pws3_field){ .is_header = 1,
+	    .field_type = PWS3_HEADER_FIELD_EMPTY_GROUPS,
+	    .value.text = (char *)group_name })));
+}
+
+struct pws3_field *
+pws3_file_remove_empty_group(struct pws3_file *pws_file, const char *group_name)
+{
+	struct pws3_field *field;
+
+	field = RB_FIND(empty_groups_tree, pws_file->empty_groups_tree,
+	    (&(struct pws3_field){ .is_header = 1,
+	    .field_type = PWS3_HEADER_FIELD_EMPTY_GROUPS,
+	    .value.text = (char *)group_name }));
+	if (field != NULL) {
+		RB_REMOVE(empty_groups_tree, pws_file->empty_groups_tree,
+		    field);
+	}
+
+	return (field);
+}
+
+struct pws3_field *
+pws3_file_first_empty_group(struct pws3_file *pws_file)
+{
+	return (RB_MIN(empty_groups_tree, pws_file->empty_groups_tree));
+}
+
+struct pws3_field *
+pws3_file_last_empty_group(struct pws3_file *pws_file)
+{
+	return (RB_MAX(empty_groups_tree, pws_file->empty_groups_tree));
+}
+
+struct pws3_field *
+pws3_file_next_empty_group(struct pws3_file *pws_file, struct pws3_field *field)
+{
+	return (RB_NEXT(empty_groups_tree, pws_file->empty_groups_tree, field));
+}
+
+struct pws3_field *
+pws3_file_prev_empty_group(struct pws3_file *pws_file, struct pws3_field *field)
+{
+	return (RB_PREV(empty_groups_tree, pws_file->empty_groups_tree, field));
+}
+
+void
+pws3_file_insert_record(struct pws3_file *pws_file, struct pws3_record *record)
+{
+	struct pws3_field	*uuid_field;
+	const unsigned char	*uuid;
+
+	uuid_field = pws3_record_get_field(record, PWS3_RECORD_FIELD_UUID);
+	PWS_ASSERT(uuid_field != NULL);
+	uuid = pws3_field_get_uuid(uuid_field);
+
+	/* replace existing record */
+	pws3_record_destroy(pws3_file_remove_record(pws_file, uuid));
+
+	RB_INSERT(records_tree, pws_file->records_tree, record);
+}
+
+struct pws3_record *
+pws3_file_get_record(struct pws3_file *pws_file,
+    const unsigned char uuid[static PWS3_UUID_SIZE])
+{
+	struct pws3_field uuid_field = {
+		.is_header = 0,
+		.field_type = PWS3_RECORD_FIELD_UUID
+	};
+	struct pws3_record search_record = {
+		.fields[PWS3_RECORD_FIELD_UUID] = &uuid_field
+	};
+
+	memcpy(uuid_field.value.uuid, uuid, PWS3_UUID_SIZE);
+
+	return (RB_FIND(records_tree, pws_file->records_tree, &search_record));
+}
+
+struct pws3_record *
+pws3_file_remove_record(struct pws3_file *pws_file,
+    const unsigned char uuid[static PWS3_UUID_SIZE])
+{
+	struct pws3_record *record;
+
+	record = pws3_file_get_record(pws_file, uuid);
+	if (record != NULL) {
+		RB_REMOVE(records_tree, pws_file->records_tree, record);
+	}
+
+	return (record);
+}
+
+struct pws3_record *
+pws3_file_first_record(struct pws3_file *pws_file)
+{
+	return (RB_MIN(records_tree, pws_file->records_tree));
+}
+
+struct pws3_record *
+pws3_file_last_record(struct pws3_file *pws_file)
+{
+	return (RB_MAX(records_tree, pws_file->records_tree));
+}
+
+struct pws3_record *
+pws3_file_next_record(struct pws3_file *pws_file, struct pws3_record *record)
+{
+	return (RB_NEXT(records_tree, pws_file->records_tree, record));
+}
+
+struct pws3_record *
+pws3_file_prev_record(struct pws3_file *pws_file, struct pws3_record *record)
+{
+	return (RB_PREV(records_tree, pws_file->records_tree, record));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pws-internal.h	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	PWS_INTERNAL_H
+#define	PWS_INTERNAL_H
+
+#include <signal.h>
+#ifdef	HAVE_SYS_TREE_H
+#include <sys/tree.h>
+#endif /* HAVE_SYS_TREE_H */
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "include/pws.h"
+
+#define	MAX(x, y)		(((x) > (y)) ? (x) : (y))
+#define	MIN(x, y)		(((x) < (y)) ? (x) : (y))
+#define	CLAMP(x, min, max)	(((x) > (max)) ? (max) : (((x) < (min)) ?\
+    (min) : (x)))
+
+#ifdef	NDEBUG
+#include <signal.h>
+#include <unistd.h>
+#define	PWS_ASSERT(cond)	while (cond) {\
+    write(STDERR_FIELNO, "assertion failed: " #cond "\n", \
+    sizeof ("assertion failed: " #cond "\n"));\
+    raise(SIGKILL);\
+    }
+#else
+#include <assert.h>
+#define	PWS_ASSERT(cond)	assert(cond)
+#endif /* NDEBUG */
+
+struct pws3_field {
+	int		is_header;
+	uint8_t		field_type;
+	size_t		size;
+	union {
+		unsigned char	*bytes;
+		unsigned char	uuid[PWS3_UUID_SIZE];
+		char		*text;
+		uint8_t		uint8;
+		uint16_t	uint16;
+		uint32_t	uint32;
+	} value;
+	RB_ENTRY(pws3_field) tree_entry;
+};
+
+struct pws3_record {
+	struct pws3_field *fields[256];
+	RB_ENTRY(pws3_record) tree_entry;
+};
+
+extern void *	(*pws_alloc)(size_t);
+extern void *	(*pws_realloc)(void *, size_t);
+extern void	(*pws_free)(void *, size_t);
+extern void *	(*pws_secure_alloc)(size_t);
+extern void *	(*pws_secure_realloc)(void *, size_t);
+extern void	(*pws_secure_free)(void *, size_t);
+
+int	pws_random_bytes(void *, size_t);
+
+#endif /* PWS_INTERNAL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pws-random.c	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "compat.h"
+#include "pws-internal.h"
+
+#ifdef	HAVE_ARC4RANDOM
+/* prefer system arc4random(3) implementation */
+#include <stdlib.h>
+#else /* HAVE_ARC4RANDOM */
+#ifdef	HAVE_GETENTROPY
+/*
+ * otherwise use system getentropy(2) implementation or fallback to seed
+ * Yarrow-256 PRNG from libnettle
+ */
+#include <unistd.h>
+#endif /* HAVE_GETENTROPY */
+#include <nettle/yarrow.h>
+#endif /* HAVE_ARC4RANDOM */
+
+#include "pws-internal.h"
+
+int
+pws_random_bytes(void *buf, size_t buf_size)
+{
+#ifdef	HAVE_ARC4RANDOM
+	arc4random_buf(buf, buf_size);
+#else /* HAVE_ARC4RANDOM */
+	struct yarrow256_ctx	ctx;
+	unsigned char		seed_buf[YARROW256_SEED_FILE_SIZE];
+
+	if (getentropy(&seed_buf, sizeof (seed_buf)) != 0) {
+		return (-1);
+	}
+	yarrow256_init(&ctx, 0, NULL);
+	yarrow256_seed(&ctx, sizeof (seed_buf), seed_buf);
+	yarrow256_random(&ctx, buf_size, buf);
+#endif /* HAVE_ARC4RANDOM */
+
+	return (0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pws-record.c	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "compat.h"
+
+#include <stdlib.h>
+
+#include "pws-internal.h"
+
+void
+pws3_record_destroy(struct pws3_record *record)
+{
+	size_t		i;
+
+	if (record == NULL) {
+		return;
+	}
+
+	for (i = 0x01; i <= 0xff; i++) {
+		pws3_field_destroy(record->fields[i]);
+	}
+	pws_free(record, sizeof (record));
+}
+
+struct pws3_record *
+pws3_record_create(void)
+{
+	struct pws3_field *uuid_field = NULL;
+	unsigned char	uuid[PWS3_UUID_SIZE];
+	struct pws3_record *record = NULL;
+	size_t		i;
+
+	/*
+	 * always add an UUID field which is mandatory and needed for inserting
+	 * a record into a database
+	 */
+	if (pws_generate_uuid(uuid) != 0) {
+		goto err;
+	}
+	uuid_field = pws3_field_create(0, PWS3_RECORD_FIELD_UUID);
+	if (uuid_field == NULL) {
+		goto err;
+	}
+	pws3_field_set_uuid(uuid_field, uuid);
+
+	record = pws_alloc(sizeof (struct pws3_record));
+	if (record == NULL) {
+		goto err;
+	}
+
+	for (i = 0x00; i <= 0xff; i++) {
+		record->fields[i] = NULL;
+	}
+
+	pws3_record_set_field(record, uuid_field);
+
+	return (record);
+err:
+	pws3_field_destroy(uuid_field);
+	pws_free(record, (record != NULL) ? sizeof (struct pws3_record) : 0);
+
+	return (NULL);
+}
+
+void
+pws3_record_set_field(struct pws3_record *record, struct pws3_field *field)
+{
+	uint8_t	field_type;
+
+	PWS_ASSERT(!pws3_field_is_header(field));
+	PWS_ASSERT((pws3_field_get_data_type(field) != PWS_DATA_TYPE_TEXT) ||
+	    (field->value.text != NULL));
+	PWS_ASSERT((pws3_field_get_data_type(field) != PWS_DATA_TYPE_BYTES) ||
+	    (field->value.bytes != NULL));
+
+	field_type = pws3_field_get_type(field);
+	pws3_field_destroy(pws3_record_remove_field(record, field_type));
+	record->fields[field_type] = field;
+}
+
+struct pws3_field *
+pws3_record_get_field(struct pws3_record *record, uint8_t field_type)
+{
+	return (record->fields[field_type]);
+}
+
+struct pws3_field *
+pws3_record_remove_field(struct pws3_record *record, uint8_t field_type)
+{
+	struct pws3_field *field;
+
+	field = pws3_record_get_field(record, field_type);
+	record->fields[field_type] = NULL;
+
+	return (field);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pws.c	Tue Feb 10 11:29:54 2015 +0100
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 Guido Berhoerster <guido+libpws@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "compat.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "pws-internal.h"
+
+static void	default_free(void *, size_t);
+
+void *	(*pws_alloc)(size_t) = malloc;
+void *	(*pws_realloc)(void *, size_t) = realloc;
+void	(*pws_free)(void *, size_t) = default_free;
+void *	(*pws_secure_alloc)(size_t) = malloc;
+void *	(*pws_secure_realloc)(void *, size_t) = realloc;
+void	(*pws_secure_free)(void *, size_t) = default_free;
+
+int
+pws_init(void)
+{
+	return (0);
+}
+
+void
+pws_finalize(void)
+{
+}
+
+static void
+default_free(void *ptr, size_t n)
+{
+	free(ptr);
+}
+
+void
+pws_set_alloc_functions(void *(*alloc_function)(size_t),
+    void *(*realloc_function)(void *, size_t),
+    void (*free_function)(void *, size_t),
+    void *(*secure_alloc_function)(size_t),
+    void *(*secure_realloc_function)(void *, size_t),
+    void (*secure_free_function)(void *, size_t))
+{
+	pws_alloc = (alloc_function != NULL) ? alloc_function : malloc;
+	pws_realloc = (realloc_function != NULL) ? realloc_function : realloc;
+	pws_free = (free_function != NULL) ? free_function : default_free;
+	pws_secure_alloc = (secure_alloc_function != NULL) ?
+	    secure_alloc_function : malloc;
+	pws_secure_realloc = (secure_realloc_function != NULL) ?
+	    secure_realloc_function : realloc;
+	pws_secure_free = (secure_free_function != NULL) ?
+	    secure_free_function : default_free;
+}
+
+int
+pws_generate_uuid(unsigned char uuid[static PWS3_UUID_SIZE])
+{
+	unsigned char	buf[PWS3_UUID_SIZE];
+
+	if (pws_random_bytes(buf, sizeof (buf)) != 0) {
+		return (-1);
+	}
+
+	/* UUID v4 from RFC 4122, section 4.4 */
+	buf[6] = (buf[6] & 0x0f) | 0x40;
+	buf[8] = (buf[8] & 0x3f) | 0x80;
+	memcpy(uuid, buf, sizeof (buf));
+
+	return (0);
+}