PolicyKit: Branch 'master'

David Zeuthen david at kemper.freedesktop.org
Fri Dec 23 11:38:26 PST 2011


 autogen.sh                                                         |   13 
 configure.ac                                                       |    3 
 docs/man/pklocalauthority.xml                                      |    9 
 docs/polkit/polkit-1-docs.xml                                      |    1 
 docs/polkit/polkit-1-sections.txt                                  |   18 
 docs/polkit/polkit-1.types                                         |    1 
 src/polkit/Makefile.am                                             |    2 
 src/polkit/polkit.h                                                |    1 
 src/polkit/polkitidentity.c                                        |   26 
 src/polkit/polkittypes.h                                           |    3 
 src/polkit/polkitunixnetgroup.c                                    |  242 ++++++
 src/polkit/polkitunixnetgroup.h                                    |   58 +
 src/polkit/polkitunixuser.c                                        |   46 +
 src/polkit/polkitunixuser.h                                        |    1 
 src/polkitbackend/polkitbackendlocalauthority.c                    |   63 +
 src/polkitbackend/polkitbackendlocalauthorizationstore.c           |   34 
 test/Makefile.am                                                   |   21 
 test/data/etc/group                                                |    7 
 test/data/etc/netgroup                                             |    5 
 test/data/etc/passwd                                               |    5 
 test/data/etc/polkit-1/localauthority.conf.d/10-test.conf          |    2 
 test/data/etc/polkit-1/localauthority/10-test/com.example.pkla     |   14 
 test/data/var/lib/polkit-1/localauthority/10-test/com.example.pkla |    6 
 test/mocklibc/AUTHORS                                              |    1 
 test/mocklibc/COPYING                                              |  202 +++++
 test/mocklibc/ChangeLog                                            |   10 
 test/mocklibc/INSTALL                                              |  365 ++++++++++
 test/mocklibc/Makefile.am                                          |    3 
 test/mocklibc/README                                               |  121 +++
 test/mocklibc/bin/Makefile.am                                      |   25 
 test/mocklibc/bin/mocklibc-test.in                                 |  136 +++
 test/mocklibc/bin/mocklibc.in                                      |   34 
 test/mocklibc/configure.ac                                         |   38 +
 test/mocklibc/example/group                                        |    4 
 test/mocklibc/example/netgroup                                     |    5 
 test/mocklibc/example/passwd                                       |    3 
 test/mocklibc/src/Makefile.am                                      |    8 
 test/mocklibc/src/grp.c                                            |  156 ++++
 test/mocklibc/src/netdb.c                                          |  100 ++
 test/mocklibc/src/netgroup-debug.c                                 |   84 ++
 test/mocklibc/src/netgroup-debug.h                                 |   58 +
 test/mocklibc/src/netgroup.c                                       |  342 +++++++++
 test/mocklibc/src/netgroup.h                                       |  144 +++
 test/mocklibc/src/pwd.c                                            |   99 ++
 test/polkit/Makefile.am                                            |    6 
 test/polkit/polkitidentitytest.c                                   |  184 +++--
 test/polkit/polkitunixnetgrouptest.c                               |   76 ++
 test/polkit/polkitunixusertest.c                                   |   50 -
 test/polkitbackend/Makefile.am                                     |    2 
 test/polkitbackend/data/authstore1/10-test/com.example.pkla        |    6 
 test/polkitbackend/data/authstore2/10-test/com.example.pkla        |    6 
 test/polkitbackend/polkitbackendlocalauthoritytest.c               |  120 +++
 test/polkitbackend/polkitbackendlocalauthorizationstoretest.c      |   28 
 test/polkittesthelper.c                                            |   20 
 test/polkittesthelper.h                                            |    2 
 55 files changed, 2895 insertions(+), 124 deletions(-)

New commits:
commit 674357c20c1b6cb421fea6eb6924b274ec477c0e
Author: Nikki VonHollen <vonhollen at google.com>
Date:   Tue Dec 20 15:11:23 2011 -0800

    Bug 43610 - Add netgroup support
    
    https://bugs.freedesktop.org/show_bug.cgi?id=43610
    
    Added netgroup support and additional unit tests with MockLibc support.
    
    Signed-off-by: David Zeuthen <davidz at redhat.com>

diff --git a/autogen.sh b/autogen.sh
index 4d25597..462fc35 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -57,6 +57,16 @@ test -n "$NO_AUTOMAKE" || (aclocal --version) < /dev/null > /dev/null 2>&1 || {
   DIE=1
 }
 
+
+# if no automake, don't bother testing for autoreconf
+test -n "$NO_AUTOMAKE" || (autoreconf --version) < /dev/null > /dev/null 2>&1 || {
+  echo
+  echo "**Error**: You must have autoreconf installed."
+  echo "You can get autoreconf from ..."
+  DIE=1
+}
+
+
 if test "$DIE" -eq 1; then
   exit 1
 fi
@@ -75,6 +85,9 @@ esac
 
       aclocalinclude="$ACLOCAL_FLAGS"
 
+      echo "Running autoreconf on test/mocklibc ..."
+      (cd "test/mocklibc"; autoreconf --install)
+
       if grep "^AM_PROG_LIBTOOL" configure.ac >/dev/null; then
 	if test -z "$NO_LIBTOOLIZE" ; then 
 	  echo "Running libtoolize..."
diff --git a/configure.ac b/configure.ac
index 89f48ca..2ed8401 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8,6 +8,9 @@ AM_MAINTAINER_MODE
 
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
 
+# Include external mocklibc tool for unit testing
+AC_CONFIG_SUBDIRS([test/mocklibc])
+
 # libtool versioning - this applies to all libraries in this package
 #
 # See http://sources.redhat.com/autobook/autobook/autobook_91.html#SEC91 for details
diff --git a/docs/man/pklocalauthority.xml b/docs/man/pklocalauthority.xml
index 02e3f4e..a03a434 100644
--- a/docs/man/pklocalauthority.xml
+++ b/docs/man/pklocalauthority.xml
@@ -80,8 +80,9 @@
       this key is a semi-colon separated list of identities that can
       be used when administrator authentication is required. Users are
       specified by prefixing the user name with
-      <literal>unix-user:</literal> and groups of users are specified
-      by prefixing with <literal>unix-group:</literal>. See
+      <literal>unix-user:</literal>, groups of users are specified by 
+      prefixing with <literal>unix-group:</literal>, and netgroups of
+      users are specified with <literal>unix-netgroup:</literal>. See
       <xref linkend="pklocalauthority-examples"/> for an example of a
       configuration file.
     </para>
@@ -208,7 +209,9 @@
             A semi-colon separated list of globs to match identities. Each glob
             should start with <literal>unix-user:</literal> or
             <literal>unix-group:</literal> to specify whether to match on a
-            UNIX user name or a UNIX group name.
+            UNIX user name or a UNIX group name. Netgroups are supported with
+            the <literal>unix-netgroup:</literal> prefix, but cannot support
+            glob syntax.
           </para>
         </listitem>
       </varlistentry>
diff --git a/docs/polkit/polkit-1-docs.xml b/docs/polkit/polkit-1-docs.xml
index 22092d9..38a69a4 100644
--- a/docs/polkit/polkit-1-docs.xml
+++ b/docs/polkit/polkit-1-docs.xml
@@ -86,6 +86,7 @@
       <xi:include href="xml/polkitidentity.xml"/>
       <xi:include href="xml/polkitunixuser.xml"/>
       <xi:include href="xml/polkitunixgroup.xml"/>
+      <xi:include href="xml/polkitunixnetgroup.xml"/>
     </chapter>
   </part>
 
diff --git a/docs/polkit/polkit-1-sections.txt b/docs/polkit/polkit-1-sections.txt
index 9f4fcf8..ff6a301 100644
--- a/docs/polkit/polkit-1-sections.txt
+++ b/docs/polkit/polkit-1-sections.txt
@@ -5,6 +5,7 @@ polkit_unix_user_new
 polkit_unix_user_new_for_name
 polkit_unix_user_get_uid
 polkit_unix_user_set_uid
+polkit_unix_user_get_name
 <SUBSECTION Standard>
 PolkitUnixUserClass
 POLKIT_UNIX_USER
@@ -121,6 +122,23 @@ POLKIT_UNIX_GROUP_GET_CLASS
 </SECTION>
 
 <SECTION>
+<FILE>polkitunixnetgroup</FILE>
+PolkitUnixNetgroup
+polkit_unix_netgroup_new
+polkit_unix_netgroup_get_name
+polkit_unix_netgroup_set_name
+<SUBSECTION Standard>
+PolkitUnixNetgroupClass
+POLKIT_UNIX_NETGROUP
+POLKIT_IS_UNIX_NETGROUP
+POLKIT_TYPE_UNIX_NETGROUP
+polkit_unix_netgroup_get_type
+POLKIT_UNIX_NETGROUP_CLASS
+POLKIT_IS_UNIX_NETGROUP_CLASS
+POLKIT_UNIX_NETGROUP_GET_CLASS
+</SECTION>
+
+<SECTION>
 <FILE>polkitunixsession</FILE>
 PolkitUnixSession
 polkit_unix_session_new
diff --git a/docs/polkit/polkit-1.types b/docs/polkit/polkit-1.types
index e50812b..b1e13cc 100644
--- a/docs/polkit/polkit-1.types
+++ b/docs/polkit/polkit-1.types
@@ -6,6 +6,7 @@ polkit_implicit_authorization_get_type
 polkit_identity_get_type
 polkit_unix_user_get_type
 polkit_unix_group_get_type
+polkit_unix_netgroup_get_type
 polkit_subject_get_type
 polkit_unix_process_get_type
 polkit_unix_session_get_type
diff --git a/src/polkit/Makefile.am b/src/polkit/Makefile.am
index 9d7c4ce..6c5a586 100644
--- a/src/polkit/Makefile.am
+++ b/src/polkit/Makefile.am
@@ -51,6 +51,7 @@ libpolkit_gobject_1include_HEADERS =                        				\
 	polkitidentity.h								\
 	polkitunixuser.h								\
 	polkitunixgroup.h								\
+	polkitunixnetgroup.h								\
 	polkitauthorizationresult.h							\
 	polkitcheckauthorizationflags.h							\
 	polkitimplicitauthorization.h							\
@@ -73,6 +74,7 @@ libpolkit_gobject_1_la_SOURCES =                                   			\
 	polkitidentity.c			polkitidentity.h			\
 	polkitunixuser.c			polkitunixuser.h			\
 	polkitunixgroup.c			polkitunixgroup.h			\
+	polkitunixnetgroup.c			polkitunixnetgroup.h			\
 	polkitauthorizationresult.c		polkitauthorizationresult.h		\
 	polkitcheckauthorizationflags.c		polkitcheckauthorizationflags.h		\
 	polkitimplicitauthorization.c		polkitimplicitauthorization.h		\
diff --git a/src/polkit/polkit.h b/src/polkit/polkit.h
index f677ca1..bfe4c7d 100644
--- a/src/polkit/polkit.h
+++ b/src/polkit/polkit.h
@@ -33,6 +33,7 @@
 #include <polkit/polkitidentity.h>
 #include <polkit/polkitunixuser.h>
 #include <polkit/polkitunixgroup.h>
+#include <polkit/polkitunixnetgroup.h>
 #include <polkit/polkitsubject.h>
 #include <polkit/polkitunixprocess.h>
 #include <polkit/polkitunixsession.h>
diff --git a/src/polkit/polkitidentity.c b/src/polkit/polkitidentity.c
index 21c326b..dd15b2f 100644
--- a/src/polkit/polkitidentity.c
+++ b/src/polkit/polkitidentity.c
@@ -28,6 +28,7 @@
 #include "polkitidentity.h"
 #include "polkitunixuser.h"
 #include "polkitunixgroup.h"
+#include "polkitunixnetgroup.h"
 #include "polkiterror.h"
 #include "polkitprivate.h"
 
@@ -177,6 +178,10 @@ polkit_identity_from_string  (const gchar   *str,
         identity = polkit_unix_group_new_for_name (str + sizeof "unix-group:" - 1,
                                                   error);
     }
+  else if (g_str_has_prefix (str, "unix-netgroup:"))
+    {
+      identity = polkit_unix_netgroup_new (str + sizeof "unix-netgroup:" - 1);
+    }
 
   if (identity == NULL && (error != NULL && *error == NULL))
     {
@@ -214,6 +219,12 @@ polkit_identity_to_gvariant (PolkitIdentity *identity)
       g_variant_builder_add (&builder, "{sv}", "gid",
                              g_variant_new_uint32 (polkit_unix_group_get_gid (POLKIT_UNIX_GROUP (identity))));
     }
+  else if (POLKIT_IS_UNIX_NETGROUP (identity))
+    {
+      kind = "unix-netgroup";
+      g_variant_builder_add (&builder, "{sv}", "name",
+                             g_variant_new_string (polkit_unix_netgroup_get_name (POLKIT_UNIX_NETGROUP (identity))));
+    }
   else
     {
       g_warning ("Unknown class %s implementing PolkitIdentity", g_type_name (G_TYPE_FROM_INSTANCE (identity)));
@@ -326,6 +337,21 @@ polkit_identity_new_for_gvariant (GVariant  *variant,
 
       ret = polkit_unix_group_new (gid);
     }
+  else if (g_strcmp0 (kind, "unix-netgroup") == 0)
+    {
+      GVariant *v;
+      const char *name;
+
+      v = lookup_asv (details_gvariant, "name", G_VARIANT_TYPE_STRING, error);
+      if (v == NULL)
+        {
+          g_prefix_error (error, "Error parsing net identity: ");
+          goto out;
+        }
+      name = g_variant_get_string (v, NULL);
+      ret = polkit_unix_netgroup_new (name);
+      g_variant_unref (v);
+    }
   else
     {
       g_set_error (error,
diff --git a/src/polkit/polkittypes.h b/src/polkit/polkittypes.h
index 636b418..3de1778 100644
--- a/src/polkit/polkittypes.h
+++ b/src/polkit/polkittypes.h
@@ -49,6 +49,9 @@ typedef struct _PolkitUnixUser PolkitUnixUser;
 struct _PolkitUnixGroup;
 typedef struct _PolkitUnixGroup PolkitUnixGroup;
 
+struct _PolkitUnixNetgroup;
+typedef struct _PolkitUnixNetgroup PolkitUnixNetgroup;
+
 struct _PolkitAuthorizationResult;
 typedef struct _PolkitAuthorizationResult PolkitAuthorizationResult;
 
diff --git a/src/polkit/polkitunixnetgroup.c b/src/polkit/polkitunixnetgroup.c
new file mode 100644
index 0000000..e4438eb
--- /dev/null
+++ b/src/polkit/polkitunixnetgroup.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz at redhat.com>
+ * Author: Nikki VonHollen <vonhollen at google.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <string.h>
+#include <errno.h>
+#include "polkitunixnetgroup.h"
+#include "polkitidentity.h"
+#include "polkiterror.h"
+#include "polkitprivate.h"
+
+/**
+ * SECTION:polkitunixnetgroup
+ * @title: PolkitUnixNetgroup
+ * @short_description: Unix netgroups
+ *
+ * An object representing a netgroup identity on a UNIX system.
+ */
+
+/**
+ * PolkitUnixNetgroup:
+ *
+ * The #PolkitUnixNetgroup struct should not be accessed directly.
+ */
+struct _PolkitUnixNetgroup
+{
+  GObject parent_instance;
+
+  gchar *name;
+};
+
+struct _PolkitUnixNetgroupClass
+{
+  GObjectClass parent_class;
+};
+
+enum
+{
+  PROP_0,
+  PROP_NAME,
+};
+
+static void identity_iface_init (PolkitIdentityIface *identity_iface);
+
+G_DEFINE_TYPE_WITH_CODE (PolkitUnixNetgroup, polkit_unix_netgroup, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (POLKIT_TYPE_IDENTITY, identity_iface_init)
+                         );
+
+static void
+polkit_unix_netgroup_init (PolkitUnixNetgroup *net_group)
+{
+  net_group->name = NULL;
+}
+
+static void
+polkit_unix_netgroup_finalize (GObject *object)
+{
+  PolkitUnixNetgroup *net_group = POLKIT_UNIX_NETGROUP (object);
+
+  g_free(net_group->name);
+
+  G_OBJECT_CLASS (polkit_unix_netgroup_parent_class)->finalize (object);
+}
+
+static void
+polkit_unix_netgroup_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  PolkitUnixNetgroup *net_group = POLKIT_UNIX_NETGROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_value_set_string (value, polkit_unix_netgroup_get_name (net_group));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+polkit_unix_netgroup_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  PolkitUnixNetgroup *net_group = POLKIT_UNIX_NETGROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      polkit_unix_netgroup_set_name (net_group, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+polkit_unix_netgroup_class_init (PolkitUnixNetgroupClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = polkit_unix_netgroup_finalize;
+  gobject_class->get_property = polkit_unix_netgroup_get_property;
+  gobject_class->set_property = polkit_unix_netgroup_set_property;
+
+  /**
+   * PolkitUnixNetgroup:name:
+   *
+   * The NIS netgroup name.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_NAME,
+                                   g_param_spec_string ("name",
+                                                        "Group Name",
+                                                        "The NIS netgroup name",
+                                                        NULL,
+                                                        G_PARAM_CONSTRUCT |
+                                                        G_PARAM_READWRITE |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+}
+
+/**
+ * polkit_unix_netgroup_get_name:
+ * @group: A #PolkitUnixNetgroup.
+ *
+ * Gets the netgroup name for @group.
+ *
+ * Returns: A netgroup name string.
+ */
+const gchar *
+polkit_unix_netgroup_get_name (PolkitUnixNetgroup *group)
+{
+  g_return_val_if_fail (POLKIT_IS_UNIX_NETGROUP (group), NULL);
+  return group->name;
+}
+
+/**
+ * polkit_unix_netgroup_set_gid:
+ * @group: A #PolkitUnixNetgroup.
+ * @name: A netgroup name.
+ *
+ * Sets @name for @group.
+ */
+void
+polkit_unix_netgroup_set_name (PolkitUnixNetgroup *group,
+                           const gchar * name)
+{
+  g_return_if_fail (POLKIT_IS_UNIX_NETGROUP (group));
+  g_free(group->name);
+  group->name = g_strdup(name);
+}
+
+/**
+ * polkit_unix_netgroup_new:
+ * @name: A netgroup name.
+ *
+ * Creates a new #PolkitUnixNetgroup object for @name.
+ *
+ * Returns: (transfer full): A #PolkitUnixNetgroup object. Free with g_object_unref().
+ */
+PolkitIdentity *
+polkit_unix_netgroup_new (const gchar *name)
+{
+  g_return_val_if_fail (name != NULL, NULL);
+  return POLKIT_IDENTITY (g_object_new (POLKIT_TYPE_UNIX_NETGROUP,
+                                       "name", name,
+                                       NULL));
+}
+
+static guint
+polkit_unix_netgroup_hash (PolkitIdentity *identity)
+{
+  PolkitUnixNetgroup *group;
+
+  group = POLKIT_UNIX_NETGROUP (identity);
+
+  return g_str_hash(group->name);
+}
+
+static gboolean
+polkit_unix_netgroup_equal (PolkitIdentity *a,
+                        PolkitIdentity *b)
+{
+  PolkitUnixNetgroup *group_a;
+  PolkitUnixNetgroup *group_b;
+
+  group_a = POLKIT_UNIX_NETGROUP (a);
+  group_b = POLKIT_UNIX_NETGROUP (b);
+
+  if (g_strcmp0(group_a->name, group_b->name) == 0)
+    return TRUE;
+  else
+    return FALSE;
+}
+
+static gchar *
+polkit_unix_netgroup_to_string (PolkitIdentity *identity)
+{
+  PolkitUnixNetgroup *group = POLKIT_UNIX_NETGROUP (identity);
+  return g_strconcat("unix-netgroup:", group->name, NULL);
+}
+
+static void
+identity_iface_init (PolkitIdentityIface *identity_iface)
+{
+  identity_iface->hash      = polkit_unix_netgroup_hash;
+  identity_iface->equal     = polkit_unix_netgroup_equal;
+  identity_iface->to_string = polkit_unix_netgroup_to_string;
+}
diff --git a/src/polkit/polkitunixnetgroup.h b/src/polkit/polkitunixnetgroup.h
new file mode 100644
index 0000000..873d428
--- /dev/null
+++ b/src/polkit/polkitunixnetgroup.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz at redhat.com>
+ * Author: Nikki VonHollen <vonhollen at google.com>
+ */
+
+#if !defined (_POLKIT_COMPILATION) && !defined(_POLKIT_INSIDE_POLKIT_H)
+#error "Only <polkit/polkit.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __POLKIT_UNIX_NETGROUP_H
+#define __POLKIT_UNIX_NETGROUP_H
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <polkit/polkittypes.h>
+
+G_BEGIN_DECLS
+
+#define POLKIT_TYPE_UNIX_NETGROUP          (polkit_unix_netgroup_get_type())
+#define POLKIT_UNIX_NETGROUP(o)            (G_TYPE_CHECK_INSTANCE_CAST ((o), POLKIT_TYPE_UNIX_NETGROUP, PolkitUnixNetgroup))
+#define POLKIT_UNIX_NETGROUP_CLASS(k)      (G_TYPE_CHECK_CLASS_CAST((k), POLKIT_TYPE_UNIX_NETGROUP, PolkitUnixNetgroupClass))
+#define POLKIT_UNIX_NETGROUP_GET_CLASS(o)  (G_TYPE_INSTANCE_GET_CLASS ((o), POLKIT_TYPE_UNIX_NETGROUP, PolkitUnixNetgroupClass))
+#define POLKIT_IS_UNIX_NETGROUP(o)         (G_TYPE_CHECK_INSTANCE_TYPE ((o), POLKIT_TYPE_UNIX_NETGROUP))
+#define POLKIT_IS_UNIX_NETGROUP_CLASS(k)   (G_TYPE_CHECK_CLASS_TYPE ((k), POLKIT_TYPE_UNIX_NETGROUP))
+
+#if 0
+typedef struct _PolkitUnixNetgroup PolkitUnixNetgroup;
+#endif
+typedef struct _PolkitUnixNetgroupClass PolkitUnixNetgroupClass;
+
+GType           polkit_unix_netgroup_get_type     (void) G_GNUC_CONST;
+PolkitIdentity *polkit_unix_netgroup_new          (const gchar    *name);
+const gchar    *polkit_unix_netgroup_get_name     (PolkitUnixNetgroup *group);
+void            polkit_unix_netgroup_set_name     (PolkitUnixNetgroup *group,
+                                                    const gchar   *name);
+
+G_END_DECLS
+
+#endif /* __POLKIT_UNIX_NETGROUP_H */
diff --git a/src/polkit/polkitunixuser.c b/src/polkit/polkitunixuser.c
index 1c9cf49..8bfd3a1 100644
--- a/src/polkit/polkitunixuser.c
+++ b/src/polkit/polkitunixuser.c
@@ -49,6 +49,7 @@ struct _PolkitUnixUser
   GObject parent_instance;
 
   gint uid;
+  gchar *name;
 };
 
 struct _PolkitUnixUserClass
@@ -71,6 +72,17 @@ G_DEFINE_TYPE_WITH_CODE (PolkitUnixUser, polkit_unix_user, G_TYPE_OBJECT,
 static void
 polkit_unix_user_init (PolkitUnixUser *unix_user)
 {
+  unix_user->name = NULL;
+}
+
+static void
+polkit_unix_user_finalize (GObject *object)
+{
+  PolkitUnixUser *unix_user = POLKIT_UNIX_USER (object);
+
+  g_free(unix_user->name);
+
+  G_OBJECT_CLASS (polkit_unix_user_parent_class)->finalize (object);
 }
 
 static void
@@ -118,6 +130,7 @@ polkit_unix_user_class_init (PolkitUnixUserClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
+  gobject_class->finalize = polkit_unix_user_finalize;
   gobject_class->get_property = polkit_unix_user_get_property;
   gobject_class->set_property = polkit_unix_user_set_property;
 
@@ -228,6 +241,29 @@ polkit_unix_user_new_for_name (const gchar    *name,
   return identity;
 }
 
+/**
+ * polkit_unix_user_get_name:
+ * @user: A #PolkitUnixUser.
+ *
+ * Get the user's name.
+ *
+ * Returns: (allow-none) (transfer none): User name string or %NULL if user uid not found.
+ */
+const gchar *
+polkit_unix_user_get_name (PolkitUnixUser *user)
+{
+  if (user->name == NULL)
+    {
+      struct passwd *passwd;
+      passwd = getpwuid (user->uid);
+
+      if (passwd != NULL)
+        user->name = g_strdup(passwd->pw_name);
+    }
+
+  return user->name;
+}
+
 static gboolean
 polkit_unix_user_equal (PolkitIdentity *a,
                         PolkitIdentity *b)
@@ -255,14 +291,12 @@ static gchar *
 polkit_unix_user_to_string (PolkitIdentity *identity)
 {
   PolkitUnixUser *user = POLKIT_UNIX_USER (identity);
-  struct passwd *passwd;
-
-  passwd = getpwuid (user->uid);
+  const gchar *user_name = polkit_unix_user_get_name(user);
 
-  if (passwd == NULL)
-    return g_strdup_printf ("unix-user:%d", user->uid);
+  if (user_name != NULL)
+    return g_strdup_printf ("unix-user:%s", user_name);
   else
-    return g_strdup_printf ("unix-user:%s", passwd->pw_name);
+    return g_strdup_printf ("unix-user:%d", user->uid);
 }
 
 static void
diff --git a/src/polkit/polkitunixuser.h b/src/polkit/polkitunixuser.h
index 8d4a9e1..2f227d4 100644
--- a/src/polkit/polkitunixuser.h
+++ b/src/polkit/polkitunixuser.h
@@ -53,6 +53,7 @@ PolkitIdentity *polkit_unix_user_new_for_name (const gchar    *name,
 gint            polkit_unix_user_get_uid      (PolkitUnixUser *user);
 void            polkit_unix_user_set_uid      (PolkitUnixUser *user,
                                                gint            uid);
+const gchar    *polkit_unix_user_get_name     (PolkitUnixUser *user);
 
 G_END_DECLS
 
diff --git a/src/polkitbackend/polkitbackendlocalauthority.c b/src/polkitbackend/polkitbackendlocalauthority.c
index 0f3cd65..b53eda3 100644
--- a/src/polkitbackend/polkitbackendlocalauthority.c
+++ b/src/polkitbackend/polkitbackendlocalauthority.c
@@ -23,6 +23,7 @@
 #include <errno.h>
 #include <pwd.h>
 #include <grp.h>
+#include <netdb.h>
 #include <string.h>
 #include <glib/gstdio.h>
 #include <locale.h>
@@ -52,6 +53,9 @@
 static GList *get_users_in_group (PolkitIdentity              *group,
                                   gboolean                     include_root);
 
+static GList *get_users_in_net_group (PolkitIdentity          *group,
+                                      gboolean                 include_root);
+
 static GList *get_groups_for_user (PolkitIdentity              *user);
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -507,6 +511,10 @@ polkit_backend_local_authority_get_admin_auth_identities (PolkitBackendInteracti
         {
           ret = g_list_concat (ret, get_users_in_group (identity, FALSE));
         }
+      else if (POLKIT_IS_UNIX_NETGROUP (identity))
+        {
+          ret =  g_list_concat (ret, get_users_in_net_group (identity, FALSE));
+        }
       else
         {
           g_warning ("Unsupported identity %s", admin_identities[n]);
@@ -660,7 +668,7 @@ get_users_in_group (PolkitIdentity                    *group,
       PolkitIdentity *user;
       GError *error;
 
-      if (!include_root && strcmp (grp->gr_mem[n], "root") == 0)
+      if (!include_root && g_strcmp0 (grp->gr_mem[n], "root") == 0)
         continue;
 
       error = NULL;
@@ -683,6 +691,59 @@ get_users_in_group (PolkitIdentity                    *group,
 }
 
 static GList *
+get_users_in_net_group (PolkitIdentity                    *group,
+                        gboolean                           include_root)
+{
+  const gchar *name;
+  GList *ret;
+
+  ret = NULL;
+  name = polkit_unix_netgroup_get_name (POLKIT_UNIX_NETGROUP (group));
+
+  if (setnetgrent (name) == 0)
+    {
+      g_warning ("Error looking up net group with name %s: %s", name, g_strerror (errno));
+      goto out;
+    }
+
+  for (;;)
+    {
+      char *hostname, *username, *domainname;
+      PolkitIdentity *user;
+      GError *error = NULL;
+
+      if (getnetgrent (&hostname, &username, &domainname) == 0)
+        break;
+
+      /* Skip NULL entries since we never want to make everyone an admin
+       * Skip "-" entries which mean "no match ever" in netgroup land */
+      if (username == NULL || g_strcmp0 (username, "-") == 0)
+        continue;
+
+      /* TODO: Should we match on hostname? Maybe only allow "-" as a hostname
+       * for safety. */
+
+      user = polkit_unix_user_new_for_name (username, &error);
+      if (user == NULL)
+        {
+          g_warning ("Unknown username '%s' in unix-netgroup: %s", username, error->message);
+          g_error_free (error);
+        }
+      else
+        {
+          ret = g_list_prepend (ret, user);
+        }
+    }
+
+  ret = g_list_reverse (ret);
+
+ out:
+  endnetgrent ();
+  return ret;
+}
+
+
+static GList *
 get_groups_for_user (PolkitIdentity *user)
 {
   uid_t uid;
diff --git a/src/polkitbackend/polkitbackendlocalauthorizationstore.c b/src/polkitbackend/polkitbackendlocalauthorizationstore.c
index d10121f..2ddfe75 100644
--- a/src/polkitbackend/polkitbackendlocalauthorizationstore.c
+++ b/src/polkitbackend/polkitbackendlocalauthorizationstore.c
@@ -21,6 +21,7 @@
 
 #include "config.h"
 
+#include <netdb.h>
 #include <string.h>
 #include <polkit/polkit.h>
 #include "polkitbackendlocalauthorizationstore.h"
@@ -74,7 +75,12 @@ typedef struct
 {
   gchar *id;
 
+  /* Identities with glob support */
   GList *identity_specs;
+
+  /* Netgroup identity strings, which can not support glob syntax */
+  GList *netgroup_identities;
+
   GList *action_specs;
 
   PolkitImplicitAuthorization result_any;
@@ -90,6 +96,7 @@ local_authorization_free (LocalAuthorization *authorization)
   g_free (authorization->id);
   g_list_foreach (authorization->identity_specs, (GFunc) g_pattern_spec_free, NULL);
   g_list_free (authorization->identity_specs);
+  g_list_free_full (authorization->netgroup_identities, g_free);
   g_list_foreach (authorization->action_specs, (GFunc) g_pattern_spec_free, NULL);
   g_list_free (authorization->action_specs);
   if (authorization->return_value != NULL)
@@ -135,8 +142,13 @@ local_authorization_new (GKeyFile      *key_file,
     }
   for (n = 0; identity_strings[n] != NULL; n++)
     {
-      authorization->identity_specs = g_list_prepend (authorization->identity_specs,
-                                                      g_pattern_spec_new (identity_strings[n]));
+      /* Put netgroup entries in a seperate list from other identities who support glob syntax */
+      if (g_str_has_prefix (identity_strings[n], "unix-netgroup:"))
+        authorization->netgroup_identities = g_list_prepend (authorization->netgroup_identities,
+                                                             g_strdup (identity_strings[n] + sizeof "unix-netgroup:" - 1));
+      else
+        authorization->identity_specs = g_list_prepend (authorization->identity_specs,
+                                                        g_pattern_spec_new (identity_strings[n]));
     }
 
   action_strings = g_key_file_get_string_list (key_file,
@@ -704,7 +716,7 @@ polkit_backend_local_authorization_store_lookup (PolkitBackendLocalAuthorization
       if (ll == NULL)
         continue;
 
-      /* then match the identity */
+      /* then match the identity against identity specs */
       if (identity_string == NULL)
         identity_string = polkit_identity_to_string (identity);
       for (ll = authorization->identity_specs; ll != NULL; ll = ll->next)
@@ -712,6 +724,22 @@ polkit_backend_local_authorization_store_lookup (PolkitBackendLocalAuthorization
           if (g_pattern_match_string ((GPatternSpec *) ll->data, identity_string))
             break;
         }
+
+      /* if no identity specs matched and identity is a user, match against netgroups */
+      if (ll == NULL && POLKIT_IS_UNIX_USER (identity))
+        {
+          PolkitUnixUser *user_identity = POLKIT_UNIX_USER (identity);
+          const gchar *user_name = polkit_unix_user_get_name (user_identity);
+          if (!user_name)
+            continue;
+
+          for (ll = authorization->netgroup_identities; ll != NULL; ll = ll->next)
+            {
+              if (innetgr ((const gchar *) ll->data, NULL, user_name, NULL))
+                break;
+            }
+        }
+
       if (ll == NULL)
         continue;
 
diff --git a/test/Makefile.am b/test/Makefile.am
index 9927eab..8426977 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,11 +1,30 @@
 
-SUBDIRS = . polkit polkitbackend
+SUBDIRS = mocklibc . polkit polkitbackend
 AM_CFLAGS = $(GLIB_CFLAGS)
 
 check_LTLIBRARIES = libpolkit-test-helper.la
 libpolkit_test_helper_la_SOURCES = polkittesthelper.c polkittesthelper.h
 libpolkit_test_helper_la_LIBADD = $(GLIB_LIBS)
 
+EXTRA_DIST = data
+
+# Use mocklibc to override NSS services for tests
+export MOCK_PASSWD   := $(abs_top_srcdir)/test/data/etc/passwd
+export MOCK_GROUP    := $(abs_top_srcdir)/test/data/etc/group
+export MOCK_NETGROUP := $(abs_top_srcdir)/test/data/etc/netgroup
+export TESTS_ENVIRONMENT := $(abs_top_builddir)/test/mocklibc/bin/mocklibc
+
+# Include path to mock config files
+export POLKIT_TEST_DATA := $(abs_top_srcdir)/test/data
+
 
 clean-local :
 	rm -f *~
+
+
+# Never install anything in this dir (specifically MockLibc)
+install:; @:
+install-exec:; @:
+install-data:; @:
+uninstall:; @:
+
diff --git a/test/data/etc/group b/test/data/etc/group
new file mode 100644
index 0000000..12ef328
--- /dev/null
+++ b/test/data/etc/group
@@ -0,0 +1,7 @@
+root:x:0:
+users:x:100:john,jane
+admin:x:101:sally,henry
+john:x:500:
+jane:x:501:
+sally:x:502:
+henry:x:503:
diff --git a/test/data/etc/netgroup b/test/data/etc/netgroup
new file mode 100644
index 0000000..21a27f9
--- /dev/null
+++ b/test/data/etc/netgroup
@@ -0,0 +1,5 @@
+foo (-,john,)
+bar (-,jane,)
+baz foo bar
+all (,,)
+none
diff --git a/test/data/etc/passwd b/test/data/etc/passwd
new file mode 100644
index 0000000..8544feb
--- /dev/null
+++ b/test/data/etc/passwd
@@ -0,0 +1,5 @@
+root:x:0:0:root:/root:/bin/bash
+john:x:500:500:John Done:/home/john:/bin/bash
+jane:x:501:501:Jane Smith:/home/jane:/bin/bash
+sally:x:502:502:Sally Derp:/home/sally:/bin/bash
+henry:x:503:503:Henry Herp:/home/henry:/bin/bash
diff --git a/test/data/etc/polkit-1/localauthority.conf.d/10-test.conf b/test/data/etc/polkit-1/localauthority.conf.d/10-test.conf
new file mode 100644
index 0000000..d7a9824
--- /dev/null
+++ b/test/data/etc/polkit-1/localauthority.conf.d/10-test.conf
@@ -0,0 +1,2 @@
+[Configuration]
+AdminIdentities=unix-user:root;unix-netgroup:bar;unix-group:admin
diff --git a/test/data/etc/polkit-1/localauthority/10-test/com.example.pkla b/test/data/etc/polkit-1/localauthority/10-test/com.example.pkla
new file mode 100644
index 0000000..bc64c5e
--- /dev/null
+++ b/test/data/etc/polkit-1/localauthority/10-test/com.example.pkla
@@ -0,0 +1,14 @@
+[Users and Root can do Foo]
+Identity=unix-group:users;unix-user:root
+Action=com.example.awesomeproduct.foo
+ResultAny=no
+ResultInactive=auth_self
+ResultActive=yes
+
+[Users in netgroup baz can do Bar]
+Identity=unix-netgroup:baz
+Action=com.example.awesomeproduct.bar
+ResultAny=no
+ResultInactive=auth_self
+ResultActive=yes
+
diff --git a/test/data/var/lib/polkit-1/localauthority/10-test/com.example.pkla b/test/data/var/lib/polkit-1/localauthority/10-test/com.example.pkla
new file mode 100644
index 0000000..f013c5b
--- /dev/null
+++ b/test/data/var/lib/polkit-1/localauthority/10-test/com.example.pkla
@@ -0,0 +1,6 @@
+[Super Secret Project Permissions]
+Identity=unix-user:root
+Action=com.example.restrictedproduct.*
+ResultAny=no
+ResultInactive=no
+ResultActive=auth_self
diff --git a/test/mocklibc/AUTHORS b/test/mocklibc/AUTHORS
new file mode 100644
index 0000000..c2347f6
--- /dev/null
+++ b/test/mocklibc/AUTHORS
@@ -0,0 +1 @@
+Nikki VonHollen <vonhollen at google.com>
diff --git a/test/mocklibc/COPYING b/test/mocklibc/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/test/mocklibc/COPYING
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/test/mocklibc/ChangeLog b/test/mocklibc/ChangeLog
new file mode 100644
index 0000000..00dd245
--- /dev/null
+++ b/test/mocklibc/ChangeLog
@@ -0,0 +1,10 @@
+2011-12-19 Nikki VonHollen <vonhollen at google.com>
+
+* Added check for 'id' and 'innetgr' commands before running tests that depend
+  on them. 'make check' now passes without them, without running tests.
+
+
+2011-12-14 Nikki VonHollen <vonhollen at google.com>
+
+* Released version 1.0 with basic NSS passwd, group, and netgroup mocks.
+
diff --git a/test/mocklibc/INSTALL b/test/mocklibc/INSTALL
new file mode 100644
index 0000000..7d1c323
--- /dev/null
+++ b/test/mocklibc/INSTALL
@@ -0,0 +1,365 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
+2006, 2007, 2008, 2009 Free Software Foundation, Inc.
+
+   Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved.  This file is offered as-is,
+without warranty of any kind.
+
+Basic Installation
+==================
+
+   Briefly, the shell commands `./configure; make; make install' should
+configure, build, and install this package.  The following
+more-detailed instructions are generic; see the `README' file for
+instructions specific to this package.  Some packages provide this
+`INSTALL' file but do not implement all of the features documented
+below.  The lack of an optional feature in a given package is not
+necessarily a bug.  More recommendations for GNU packages can be found
+in *note Makefile Conventions: (standards)Makefile Conventions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+   It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring.  Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+   The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'.  You need `configure.ac' if
+you want to change it or regenerate `configure' using a newer version
+of `autoconf'.
+
+   The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.
+
+     Running `configure' might take a while.  While running, it prints
+     some messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package, generally using the just-built uninstalled binaries.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.  When installing into a prefix owned by root, it is
+     recommended that the package be configured and built as a regular
+     user, and only the `make install' phase executed with root
+     privileges.
+
+  5. Optionally, type `make installcheck' to repeat any self-tests, but
+     this time using the binaries in their final installed location.
+     This target does not install anything.  Running this target as a
+     regular user, particularly if the prior `make install' required
+     root privileges, verifies that the installation completed
+     correctly.
+
+  6. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+  7. Often, you can also type `make uninstall' to remove the installed
+     files again.  In practice, not all packages have tested that
+     uninstallation works correctly, even though it is required by the
+     GNU Coding Standards.
+
+  8. Some packages, particularly those that use Automake, provide `make
+     distcheck', which can by used by developers to test that all other
+     targets like `make install' and `make uninstall' work correctly.
+     This target is generally not run by end users.
+
+Compilers and Options
+=====================
+
+   Some systems require unusual options for compilation or linking that
+the `configure' script does not know about.  Run `./configure --help'
+for details on some of the pertinent environment variables.
+
+   You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment.  Here
+is an example:
+
+     ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+   *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+   You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you can use GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.  This
+is known as a "VPATH" build.
+
+   With a non-GNU `make', it is safer to compile the package for one
+architecture at a time in the source code directory.  After you have
+installed the package for one architecture, use `make distclean' before
+reconfiguring for another architecture.
+
+   On MacOS X 10.5 and later systems, you can create libraries and
+executables that work on multiple system types--known as "fat" or
+"universal" binaries--by specifying multiple `-arch' options to the
+compiler but only a single `-arch' option to the preprocessor.  Like
+this:
+
+     ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+                 CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+                 CPP="gcc -E" CXXCPP="g++ -E"
+
+   This is not guaranteed to produce working output in all cases, you
+may have to build one architecture at a time and combine the results
+using the `lipo' tool if you have problems.
+
+Installation Names
+==================
+
+   By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc.  You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX', where PREFIX must be an
+absolute file name.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files.  Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.  In general, the
+default for these options is expressed in terms of `${prefix}', so that
+specifying just `--prefix' will affect all of the other directory
+specifications that were not explicitly provided.
+
+   The most portable way to affect installation locations is to pass the
+correct locations to `configure'; however, many packages provide one or
+both of the following shortcuts of passing variable assignments to the
+`make install' command line to change installation locations without
+having to reconfigure or recompile.
+
+   The first method involves providing an override variable for each
+affected directory.  For example, `make install
+prefix=/alternate/directory' will choose an alternate location for all
+directory configuration variables that were expressed in terms of
+`${prefix}'.  Any directories that were specified during `configure',
+but not in terms of `${prefix}', must each be overridden at install
+time for the entire installation to be relocated.  The approach of
+makefile variable overrides for each directory variable is required by
+the GNU Coding Standards, and ideally causes no recompilation.
+However, some platforms have known limitations with the semantics of
+shared libraries that end up requiring recompilation when using this
+method, particularly noticeable in packages that use GNU Libtool.
+
+   The second method involves providing the `DESTDIR' variable.  For
+example, `make install DESTDIR=/alternate/directory' will prepend
+`/alternate/directory' before all installation names.  The approach of
+`DESTDIR' overrides is not required by the GNU Coding Standards, and
+does not work on platforms that have drive letters.  On the other hand,
+it does better at avoiding recompilation issues, and works well even
+when some directory options were not specified in terms of `${prefix}'
+at `configure' time.
+
+Optional Features
+=================
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+   Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+   Some packages offer the ability to configure how verbose the
+execution of `make' will be.  For these packages, running `./configure
+--enable-silent-rules' sets the default to minimal output, which can be
+overridden with `make V=1'; while running `./configure
+--disable-silent-rules' sets the default to verbose, which can be
+overridden with `make V=0'.
+
+Particular systems
+==================
+
+   On HP-UX, the default C compiler is not ANSI C compatible.  If GNU
+CC is not installed, it is recommended to use the following options in
+order to use an ANSI C compiler:
+
+     ./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
+
+and if that doesn't work, install pre-built binaries of GCC for HP-UX.
+
+   On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
+parse its `<wchar.h>' header file.  The option `-nodtk' can be used as
+a workaround.  If GNU CC is not installed, it is therefore recommended
+to try
+
+     ./configure CC="cc"
+
+and if that doesn't work, try
+
+     ./configure CC="cc -nodtk"
+
+   On Solaris, don't put `/usr/ucb' early in your `PATH'.  This
+directory contains several dysfunctional programs; working variants of
+these programs are available in `/usr/bin'.  So, if you need `/usr/ucb'
+in your `PATH', put it _after_ `/usr/bin'.
+
+   On Haiku, software installed for all users goes in `/boot/common',
+not `/usr/local'.  It is recommended to use the following options:
+
+     ./configure --prefix=/boot/common
+
+Specifying the System Type
+==========================
+
+   There may be some features `configure' cannot figure out
+automatically, but needs to determine by the type of machine the package
+will run on.  Usually, assuming the package is built to be run on the
+_same_ architectures, `configure' can figure that out, but if it prints
+a message saying it cannot guess the machine type, give it the
+`--build=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+     CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+     OS
+     KERNEL-OS
+
+   See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+   If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+   If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+   If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+   Variables not defined in a site shell script can be set in the
+environment passed to `configure'.  However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost.  In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'.  For example:
+
+     ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for `CONFIG_SHELL' due to
+an Autoconf bug.  Until the bug is fixed you can use this workaround:
+
+     CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+`configure' Invocation
+======================
+
+   `configure' recognizes the following options to control how it
+operates.
+
+`--help'
+`-h'
+     Print a summary of all of the options to `configure', and exit.
+
+`--help=short'
+`--help=recursive'
+     Print a summary of the options unique to this package's
+     `configure', and exit.  The `short' variant lists options used
+     only in the top level, while the `recursive' variant lists options
+     also present in any nested packages.
+
+`--version'
+`-V'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`--cache-file=FILE'
+     Enable the cache: use and save the results of the tests in FILE,
+     traditionally `config.cache'.  FILE defaults to `/dev/null' to
+     disable caching.
+
+`--config-cache'
+`-C'
+     Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to `/dev/null' (any error
+     messages will still be shown).
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`--prefix=DIR'
+     Use DIR as the installation prefix.  *note Installation Names::
+     for more details, including other options available for fine-tuning
+     the installation locations.
+
+`--no-create'
+`-n'
+     Run the configure checks, but stop before creating any output
+     files.
+
+`configure' also accepts some other, not widely useful, options.  Run
+`configure --help' for more details.
+
diff --git a/test/mocklibc/Makefile.am b/test/mocklibc/Makefile.am
new file mode 100644
index 0000000..3508ecd
--- /dev/null
+++ b/test/mocklibc/Makefile.am
@@ -0,0 +1,3 @@
+
+SUBDIRS = src bin
+EXTRA_DIST = example
diff --git a/test/mocklibc/NEWS b/test/mocklibc/NEWS
new file mode 100644
index 0000000..e69de29
diff --git a/test/mocklibc/README b/test/mocklibc/README
new file mode 100644
index 0000000..2bd44ef
--- /dev/null
+++ b/test/mocklibc/README
@@ -0,0 +1,121 @@
+= MockLibc 1.1 =
+
+Mocks of common libc functions who have global state. Version 1.1 focuses on
+NSS related methods (user, group, and netgroup queries).
+
+This library is a re-implementation of specific libc methods, not a tool for
+creating mock functions. Use MockLibc to create a consistent environment for
+your unit tests, when they need to query system information.
+
+
+== Requirements ==
+
+* Tests require the 'id' and 'innetgr' commands in the PATH
+
+
+== Build ==
+
+$ cd mocklibc-1.1
+$ ./configure
+$ make
+$ make check
+
+
+== Install ==
+
+$ make install
+
+
+== Example Usage ==
+
+$ id foo
+id: foo: No such user
+$ export MOCK_PASSWD=./testdata/passwd
+$ export MOCK_GROUP=./testdata/group
+$ mkdir ./testdata
+$ echo “foo:x:9000:9000::/home/foo:/bin/bash” > “$MOCK_PASSWD”
+$ echo “mockusers:x:9001:foo” > “$MOCK_GROUP”
+$ mocklibc id foo
+uid=9000(foo) gid=9000(foo) groups=9000(foo),9001(mockusers)
+
+
+== Use without install ==
+
+mocklibc can be used directly from the bin directory, without being installed:
+$ cd mocklibc-1.1
+$ ./configure
+$ make
+$ bin/mocklibc id foo
+
+
+== Hacking ==
+
+If using a git checkout instead of a source tarball, always run
+'autogen --install' before './configure'. Whenever a Makefile.am or
+configure.ac is modified, run 'autogen' again without --install.
+
+
+== Mocked Functions ==
+
+NSS Methods completely disregard /etc/nsswitch.conf, similar to using just
+"files", but with modified paths. DNS is not modified and no *_r methods will
+be implemented in this version.
+
+* pwd.h (NSS users, configured with MOCK_PASSWD)
+  * setpwent
+  * getpwent
+  * endpwent
+  * getpwnam
+  * getpwuid
+* grp.h (NSS groups, configured with MOCK_GROUP)
+  * setpwent
+  * getpwent
+  * endpwent
+  * getpwnam
+  * getpwuid
+* netdb.h (NSS netgroups, no DNS, configured with MOCK_NETGROUP)
+  * setnetgrent
+  * getnetgrent
+  * endnetgrent
+  * innetgr
+
+
+== Configuration ==
+
+All configuration is handled through environment variables, though specific
+mocklibc_* methods may be added in the future for things like time and random
+number generation.
+
+Environment Variables:
+* MOCK_PASSWD - Path to /etc/passwd replacement
+* MOCK_GROUP - Path to /etc/group replacement
+* MOCK_NETGROUP - Path to /etc/netgroup replacement
+
+
+== F.A.Q. ==
+
+* Why not use a chroot? Chroot requires root, and forcing unit tests to run as
+  root is not desirable.
+* Is there something that already does this? There are mock frameworks for C,
+  but this library is an implementation of specific common mocks C developers
+  need. A mock of set/get/endgrent still requires some basic code for iterating
+  group objects. This library provides that.
+
+
+== TODO ==
+
+* Add functions to free unused memory in 'netdb_netgroup.c'. It leaks a ton of
+  memory every call. See TODO comments in code.
+
+
+== Future ==
+
+The following may be supported in the future, and I'm taking requests for other
+functionality at 'vonhollen at gmail.com'.
+
+Features:
+* Redirect syslog messages to file at $MOCK_SYSLOG
+* '*_r' methods in pwd.h, grp.h, and netdb.h
+* netdb.h: gethostbyname, gethostbyaddr, getaddrinfo, get/freeaddrinfo
+* Whitelist apps with $MOCK_ONLY (includes list of argv[0] names)
+
diff --git a/test/mocklibc/bin/Makefile.am b/test/mocklibc/bin/Makefile.am
new file mode 100644
index 0000000..a2e65e0
--- /dev/null
+++ b/test/mocklibc/bin/Makefile.am
@@ -0,0 +1,25 @@
+
+bin_SCRIPTS = mocklibc
+
+check_SCRIPTS = mocklibc-test
+TESTS = mocklibc-test
+
+EXTRA_DIST = mocklibc.in mocklibc-test.in
+CLEANFILES = mocklibc mocklibc-test
+
+
+# Substitute build variables in shell scripts
+# See section "4.8.2 Installation Directory Variables" in autoconf manual
+
+edit = sed \
+       -e 's|@libdir[@]|$(libdir)|g' \
+       -e 's|@libname[@]|$(libname)|g' \
+       -e 's|@top_srcdir[@]|$(top_srcdir)|g' \
+       -e 's|@top_builddir[@]|$(top_builddir)|g'
+
+mocklibc mocklibc-test: Makefile
+	$(edit) $(srcdir)/$@.in > $@
+	chmod a+x $@
+
+mocklibc: $(srcdir)/mocklibc.in
+mocklibc-test: $(srcdir)/mocklibc-test.in
diff --git a/test/mocklibc/bin/mocklibc-test.in b/test/mocklibc/bin/mocklibc-test.in
new file mode 100644
index 0000000..9f00a77
--- /dev/null
+++ b/test/mocklibc/bin/mocklibc-test.in
@@ -0,0 +1,136 @@
+#!/bin/bash
+
+#  Copyright 2011 Google Inc. All Rights Reserved.
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  Author: Nikki VonHollen <vonhollen at gmail.com>
+
+
+# Figure out where everything is
+
+MOCKLIBC="@top_builddir@/bin/mocklibc"
+ETCDIR="@top_srcdir@/example"
+
+
+# Setup the mock environment
+
+export MOCK_PASSWD="${ETCDIR}/passwd"
+export MOCK_GROUP="${ETCDIR}/group"
+export MOCK_NETGROUP="${ETCDIR}/netgroup"
+
+
+# Test helper definitions
+
+TESTCOUNT=0
+FAILCOUNT=0
+
+fail () {
+  echo "Test Failed:"
+  echo $@ >&2
+  echo
+  FAILCOUNT=$((FAILCOUNT+1))
+}
+
+finish () {
+  if [[ $FAILCOUNT -gt 0 ]]
+  then
+    echo "Failed $FAILCOUNT of $TESTCOUNT tests."
+    exit 1
+  else
+    echo "Passed $TESTCOUNT tests."
+    exit 0
+  fi
+}
+
+assert_true () {
+  $MOCKLIBC $@ || fail "assert true: $@"
+  TESTCOUNT=$((TESTCOUNT+1))
+}
+
+assert_false () {
+  $MOCKLIBC $@ && fail "assert false: $@"
+  TESTCOUNT=$((TESTCOUNT+1))
+}
+
+assert_grep () {
+  $MOCKLIBC ${@:2} | grep -q "^${1}\$" || fail "'$1' doesn't match output of: ${@:2}"
+  TESTCOUNT=$((TESTCOUNT+1))
+}
+
+
+# Test implementations
+
+test_passwd () {
+  # Test user ids
+  assert_grep "0" id -u root
+  assert_grep "500" id -u john
+  assert_grep "501" id -u jane
+
+  # Test primary groups
+  assert_grep "root" id -gn root
+  assert_grep "john" id -gn john
+  assert_grep "jane" id -gn jane
+}
+
+test_group () {
+  # Test group lists for users
+  assert_grep "root" id -Gn root
+  assert_grep "john users" id -Gn john
+  assert_grep "jane users" id -Gn jane
+}
+
+test_netgroup () {
+  # Test whether each user is each netgroup
+  assert_true innetgr foo -u john
+  assert_false innetgr foo -u jane
+
+  assert_true innetgr bar -u jane
+  assert_false innetgr bar -u john
+
+  assert_true innetgr baz -u john
+  assert_true innetgr baz -u jane
+  assert_false innetgr baz -u henry
+
+  assert_true innetgr all -u john
+  assert_true innetgr all -u jane
+  assert_true innetgr all -u henry
+
+  assert_false innetgr none -u john
+  assert_false innetgr none -u jane
+  assert_false innetgr none -u henry
+
+  assert_false innetgr fake -u john
+}
+
+
+# Run the tests and print a report
+
+if (which id >/dev/null 2>&1)
+then
+  test_passwd
+  test_group
+else
+  echo "No 'id' command found, skipping passwd and group tests." >&2
+fi
+
+if (which innetgr >/dev/null 2>&1)
+then
+  test_netgroup
+else
+  echo "No 'innetgr' command found, skipping netgroup tests." >&2
+fi
+
+
+finish
+
diff --git a/test/mocklibc/bin/mocklibc.in b/test/mocklibc/bin/mocklibc.in
new file mode 100644
index 0000000..dc18855
--- /dev/null
+++ b/test/mocklibc/bin/mocklibc.in
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+#  Copyright 2011 Google Inc. All Rights Reserved.
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  Author: Nikki VonHollen <vonhollen at gmail.com>
+
+
+BASEDIR=`dirname $0`
+LIBDIR="${BASEDIR}/../src/.libs"
+LIBPATH="${LIBDIR}/@libname@"
+
+if [[ -f "$LIBPATH" ]]
+then
+  # Include Mocklibc's project build dir if we can find it
+  export LD_LIBRARY_PATH="${LIBDIR}:${LD_LIBRARY_PATH}"
+else
+  # Use the system version instead, w/o requiring ldconfig
+  export LD_LIBRARY_PATH="@libdir@:${LD_LIBRARY_PATH}"
+fi
+
+# Exec the requested app, replacing this one
+LD_PRELOAD="@libname@" exec $@
diff --git a/test/mocklibc/configure.ac b/test/mocklibc/configure.ac
new file mode 100644
index 0000000..70798ee
--- /dev/null
+++ b/test/mocklibc/configure.ac
@@ -0,0 +1,38 @@
+#                                               -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.65])
+AC_INIT([MockLibc], [1.1], [vonhollen at google.com])
+AC_CONFIG_SRCDIR([src])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE
+
+# Checks for programs.
+AC_PROG_CC
+
+# Checks for libraries.
+AC_PROG_LIBTOOL
+
+# Checks for header files.
+AC_CHECK_HEADERS([netdb.h stdlib.h string.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_UID_T
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+
+# Checks for library functions.
+AC_FUNC_MALLOC
+AC_CHECK_FUNCS([endgrent endpwent memset regcomp strdup])
+
+# Build wrapper scripts from templates
+AC_SUBST([libname], [libmocklibc.so])
+#AC_CONFIG_FILES([bin/mocklibc], [chmod +x bin/mocklibc], [libname=${libname}])
+#AC_CONFIG_FILES([bin/mocklibc-test], [chmod +x bin/mocklibc-test],
+#                [libname=${libname}]))
+
+AC_OUTPUT([
+Makefile
+src/Makefile
+bin/Makefile
+])
diff --git a/test/mocklibc/example/group b/test/mocklibc/example/group
new file mode 100644
index 0000000..e2253f0
--- /dev/null
+++ b/test/mocklibc/example/group
@@ -0,0 +1,4 @@
+root:x:0:
+users:x:100:john,jane
+john:x:500:
+jane:x:501:
diff --git a/test/mocklibc/example/netgroup b/test/mocklibc/example/netgroup
new file mode 100644
index 0000000..21a27f9
--- /dev/null
+++ b/test/mocklibc/example/netgroup
@@ -0,0 +1,5 @@
+foo (-,john,)
+bar (-,jane,)
+baz foo bar
+all (,,)
+none
diff --git a/test/mocklibc/example/passwd b/test/mocklibc/example/passwd
new file mode 100644
index 0000000..62aa6f5
--- /dev/null
+++ b/test/mocklibc/example/passwd
@@ -0,0 +1,3 @@
+root:x:0:0:root:/root:/bin/bash
+john:x:500:500:John Smith:/home/john:/bin/bash
+jane:x:501:501:Jane Doe:/home/jane:/bin/bash
diff --git a/test/mocklibc/src/Makefile.am b/test/mocklibc/src/Makefile.am
new file mode 100644
index 0000000..7cb934e
--- /dev/null
+++ b/test/mocklibc/src/Makefile.am
@@ -0,0 +1,8 @@
+
+lib_LTLIBRARIES = libmocklibc.la
+libmocklibc_la_SOURCES = pwd.c grp.c netdb.c netgroup.c netgroup.h
+
+bin_PROGRAMS = mocklibc-debug-netgroup
+mocklibc_debug_netgroup_SOURCES = netgroup-debug.c netgroup-debug.h
+mocklibc_debug_netgroup_LDADD = libmocklibc.la
+
diff --git a/test/mocklibc/src/grp.c b/test/mocklibc/src/grp.c
new file mode 100644
index 0000000..c671e1f
--- /dev/null
+++ b/test/mocklibc/src/grp.c
@@ -0,0 +1,156 @@
+/**
+ * Copyright 2011 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Nikki VonHollen <vonhollen at gmail.com>
+ */
+
+#include <grp.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#define GROUP_CONFIG_KEY "MOCK_GROUP"
+
+static FILE *global_stream = NULL;
+
+void setgrent(void) {
+  if (global_stream)
+    endgrent();
+
+  const char *path = getenv(GROUP_CONFIG_KEY);
+  if (!path)
+    return;
+
+  global_stream = fopen(path, "r");
+}
+
+struct group *getgrent(void) {
+  if (!global_stream)
+    setgrent();
+
+  if (!global_stream)
+    return NULL;
+
+  return fgetgrent(global_stream);
+}
+
+void endgrent(void) {
+  if (!global_stream)
+    return;
+
+  fclose(global_stream);
+  global_stream = NULL;
+}
+
+struct group *getgrnam(const char *name) {
+  const char *path = getenv(GROUP_CONFIG_KEY);
+  if (!path)
+    return NULL;
+
+  FILE *stream = fopen(path, "r");
+  if (!stream)
+    return NULL;
+
+  struct group *entry;
+  while ((entry = fgetgrent(stream))) {
+    if (strcmp(entry->gr_name, name) == 0) {
+      fclose(stream);
+      return entry;
+    }
+  }
+
+  fclose(stream);
+  return NULL;
+}
+
+struct group *getgrgid(gid_t gid) {
+  const char *path = getenv(GROUP_CONFIG_KEY);
+  if (!path)
+    return NULL;
+
+  FILE *stream = fopen(path, "r");
+  if (!stream)
+    return NULL;
+
+  struct group *entry;
+  while ((entry = fgetgrent(stream))) {
+    if (entry->gr_gid == gid) {
+      fclose(stream);
+      return entry;
+    }
+  }
+
+  fclose(stream);
+  return NULL;
+}
+
+int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups) {
+  const char *path = getenv(GROUP_CONFIG_KEY);
+  if (!path) {
+    *ngroups = 0;
+    return -1;
+  }
+
+  FILE *stream = fopen(path, "r");
+  if (!stream) {
+    *ngroups = 0;
+    return -1;
+  }
+
+  int default_group_found = 0;
+  int groups_found = 0;
+
+  // Loop through all groups
+  struct group *entry;
+  while ((entry = fgetgrent(stream))) {
+    // Loop through all users in group
+    char **cur_user;
+    for (cur_user = entry->gr_mem; *cur_user; cur_user++) {
+      // Skip users who don't match arg 'user'
+      if (strcmp(*cur_user, user))
+        continue;
+
+      // Is this the default group? if so, flag it
+      if (entry->gr_gid == group)
+        default_group_found = 1;
+
+      // Only insert new entries if we have room
+      if (groups_found < *ngroups) {
+        groups[groups_found] = entry->gr_gid;
+      }
+
+      groups_found++;
+    }
+  }
+
+  // Include the default group if it wasn't found
+  if (!default_group_found) {
+    if (groups_found < *ngroups) {
+      groups[groups_found] = group;
+    }
+    groups_found++;
+  }
+
+  // Did we have to leave out some groups? If not, tell how many we found.
+  int retval = (groups_found > *ngroups) ? -1 : groups_found;
+
+  // Always tell the user how many groups we found via *ngroups
+  *ngroups = groups_found;
+
+  fclose(stream);
+  return retval;
+}
diff --git a/test/mocklibc/src/netdb.c b/test/mocklibc/src/netdb.c
new file mode 100644
index 0000000..9442403
--- /dev/null
+++ b/test/mocklibc/src/netdb.c
@@ -0,0 +1,100 @@
+/**
+ * Copyright 2011 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Nikki VonHollen <vonhollen at gmail.com>
+ */
+
+#include "netgroup.h"
+
+#include <netdb.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#define INNETGR_CHECK(match, value) if (match && value && strcmp(match, value)) continue;
+
+/** Private static data. */
+
+static struct netgroup *global_netgroup_head = NULL;
+static struct netgroup_iter global_iter;
+
+/** Public methods */
+
+// REMEMBER: 1 means success, 0 means failure for netgroup methods
+
+int setnetgrent(const char *netgroup) {
+  if (!global_netgroup_head)
+    global_netgroup_head = netgroup_parse_all();
+
+  struct netgroup *group = netgroup_find(global_netgroup_head, netgroup);
+  if (!group) {
+     netgroup_free_all(global_netgroup_head);
+    global_netgroup_head = NULL;
+    return 0;
+  }
+
+  netgroup_iter_init(&global_iter, group);
+  return 1;
+}
+
+void endnetgrent(void) {
+  netgroup_free_all(global_netgroup_head);
+  global_netgroup_head = NULL;
+}
+
+int getnetgrent(char **host, char **user, char **domain) {
+  if (!global_netgroup_head)
+    return 0;
+
+  struct entry *result = netgroup_iter_next(&global_iter);
+  if (!result)
+    return 0;
+
+  *host = result->data.triple.hostname;
+  *user = result->data.triple.username;
+  *domain = result->data.triple.domainname;
+  return 1;
+}
+
+int innetgr(const char *netgroup, const char *host, const char *user,
+    const char *domain) {
+  int retval = 0;
+  struct netgroup *head = netgroup_parse_all();
+  struct netgroup *group = netgroup_find(head, netgroup);
+  if (!group) {
+    // Can't find group
+    netgroup_free_all(head);
+    return 0;
+  }
+
+  struct netgroup_iter iter;
+  netgroup_iter_init(&iter, group);
+
+  struct entry *cur;
+  while ((cur = netgroup_iter_next(&iter))) {
+    INNETGR_CHECK(host, cur->data.triple.hostname);
+    INNETGR_CHECK(user, cur->data.triple.username);
+    INNETGR_CHECK(domain, cur->data.triple.domainname);
+
+    // No INNETGR_CHECK failed, so we matched!
+    retval = 1;
+    break;
+  }
+
+  netgroup_free_all(head);
+  return retval;
+}
diff --git a/test/mocklibc/src/netgroup-debug.c b/test/mocklibc/src/netgroup-debug.c
new file mode 100644
index 0000000..81d6e72
--- /dev/null
+++ b/test/mocklibc/src/netgroup-debug.c
@@ -0,0 +1,84 @@
+/**
+ * Copyright 2011 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Nikki VonHollen <vonhollen at gmail.com>
+ */
+
+#include "netgroup-debug.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void netgroup_debug_print_entry(struct entry *entry, FILE *stream, unsigned int indent) {
+  print_indent(stream, indent);
+
+  if (entry->type == TRIPLE_ENTRY) {
+    fprintf(stream, "triple (%s,%s,%s)\n",
+        entry->data.triple.hostname,
+        entry->data.triple.username,
+        entry->data.triple.domainname);
+  } else if (entry->type == CHILD_ENTRY) {
+    fprintf(stream, "child '%s'\n", entry->data.child.name);
+    struct entry *child;
+    for (child = entry->data.child.head; child; child = child->next) {
+      netgroup_debug_print_entry(child, stream, indent + 1);
+    }
+  } else {
+    fprintf(stream, "UNKNOWN_TYPE");
+  }
+}
+
+void netgroup_debug_print_group(struct netgroup *group, FILE *stream, unsigned int indent) {
+  print_indent(stream, indent);
+  fprintf(stream, "%s\n", group->name);
+  struct entry *entry;
+  for (entry = group->head; entry; entry = entry->next) {
+    netgroup_debug_print_entry(entry, stream, indent + 1);
+  }
+}
+
+void netgroup_debug_print_group_unrolled(struct netgroup *group, FILE *stream, unsigned int indent) {
+  print_indent(stream, indent);
+  fprintf(stream, "%s\n", group->name);
+
+  struct netgroup_iter iter;
+  netgroup_iter_init(&iter, group);
+
+  struct entry *entry;
+  while ((entry = netgroup_iter_next(&iter))) {
+    netgroup_debug_print_entry(entry, stream, indent + 1);
+  }
+}
+
+void netgroup_debug_print_all(struct netgroup *head, FILE *stream, unsigned int indent) {
+  struct netgroup *group;
+  for (group = head; group; group = group->next) {
+    netgroup_debug_print_group(group, stream, indent);
+  }
+}
+
+int main(int argc, char **argv) {
+  struct netgroup *groups = netgroup_parse_all();
+  if (argc == 1)
+    netgroup_debug_print_all(groups, stdout, 0);
+  else if (argc == 2) {
+    struct netgroup *group = netgroup_find(groups, argv[1]);
+    if (!group)
+      return 1;
+    netgroup_debug_print_group_unrolled(group, stdout, 0);
+  }
+
+  return 0;
+}
diff --git a/test/mocklibc/src/netgroup-debug.h b/test/mocklibc/src/netgroup-debug.h
new file mode 100644
index 0000000..d733c95
--- /dev/null
+++ b/test/mocklibc/src/netgroup-debug.h
@@ -0,0 +1,58 @@
+/**
+ * Copyright 2011 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Nikki VonHollen <vonhollen at gmail.com>
+ */
+
+#ifndef NETGROUP_DEBUG_H_
+#define NETGROUP_DEBUG_H_
+
+#include "netgroup.h"
+
+#include <stdio.h>
+
+/**
+ * Print entry and it's children to the given stream.
+ * @param entry Netgroup entry to print
+ * @param stream Stream to print to
+ * @param indent Number of indents to use
+ */
+void netgroup_debug_print_entry(struct entry *entry, FILE *stream, unsigned int indent);
+
+/**
+ * Print a single netgroup to the given stream.
+ * @param group Netgroup to print
+ * @param stream Stream to print to
+ * @param indent Number of indents to use
+ */
+void netgroup_debug_print_group(struct netgroup *group, FILE *stream, unsigned int indent);
+
+/**
+ * Print a single netgroup with all triples included recursively.
+ * @param group Netgroup to print
+ * @param stream Stream to print to
+ * @param indent Number of indents to use
+ */
+void netgroup_debug_print_group_unrolled(struct netgroup *group, FILE *stream, unsigned int indent);
+
+/**
+ * Print all netgroups to the given stream.
+ * @param head Head of list of netgroups
+ * @param stream Stream to print to
+ * @param indent Number of indents to use
+ */
+void netgroup_debug_print_all(struct netgroup *head, FILE *stream, unsigned int indent);
+
+#endif
diff --git a/test/mocklibc/src/netgroup.c b/test/mocklibc/src/netgroup.c
new file mode 100644
index 0000000..f2ee857
--- /dev/null
+++ b/test/mocklibc/src/netgroup.c
@@ -0,0 +1,342 @@
+/**
+ * Copyright 2011 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Nikki VonHollen <vonhollen at gmail.com>
+ */
+
+#include "netgroup.h"
+
+#include <ctype.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#define NETGROUP_CONFIG_KEY "MOCK_NETGROUP"
+#define NETGROUP_TRIPLE_REGEX "\\(([^,]*),([^,]*),([^\\)]*)\\)"
+#define FREE_IF_NOT_NULL(ptr) if (ptr) free(ptr)
+
+/** Private methods. */
+
+/**
+ * Move the given pointer past any whitespace.
+ * @param cur Pointer to string (char *) to advance
+ */
+static void parser_skip_whitespace(char **cur) {
+  for (; isspace(**cur); (*cur)++) {}
+}
+
+/**
+ * Copy the next group of non-space characters and move the pointer past
+ * consumed characters.
+ * @param cur Pointer to string (char *) to search/advance
+ * @return Copy of chars consumed. Must be free'd by user.
+ */
+static char *parser_copy_word(char **cur) {
+  char *value = *cur;
+  size_t i;
+
+  // Find the next non-null non-space character
+  for (i = 0; !isspace(value[i]) && value[i] != '\0'; i++) {}
+
+  // Don't allocate zero-length strings, just die
+  if (i == 0) {
+    return NULL;
+  }
+
+  // Allocate the new string, with room for a null terminator
+  char *result = malloc(i + 1);
+  if (!result) {
+    return NULL;
+  }
+
+  // Set the current pointer past the parsed region
+  *cur += i;
+
+  memcpy(result, value, i);
+  result[i] = '\0';
+  return result;
+}
+
+/**
+ * Print a varaible indentation to the stream.
+ * @param stream Stream to print to
+ * @param indent Number of indents to use
+ */
+void print_indent(FILE *stream, unsigned int indent) {
+  int i;
+  for (i = 0; i < indent; i++)
+    fprintf(stream, "  ");
+}
+
+/**
+ * Connect entries with 'child' type to their child entries.
+ * @param headentry Head of list of entries that need to be connected
+ * @param headgroup Head of list of netgroups to connect child entries to
+ */
+static void netgroup_connect_children(struct entry *headentry, struct netgroup *headgroup) {
+  struct entry *curentry;
+  for (curentry = headentry; curentry; curentry = curentry->next) {
+    // Skip entries that don't have children
+    if (curentry->type != CHILD_ENTRY)
+      continue;
+
+    // Set the entry's children to the head of the netgroup with the same name
+    struct netgroup *group = netgroup_find(headgroup, curentry->data.child.name);
+    if (group)
+      curentry->data.child.head = group->head;
+  }
+}
+
+
+/* Public methods. */
+
+struct netgroup *netgroup_parse_all() {
+  const char *path = getenv(NETGROUP_CONFIG_KEY);
+  if (!path)
+    return NULL;
+
+  FILE *stream = fopen(path, "r");
+  if (!stream)
+    return NULL;
+
+  struct netgroup *headgroup = NULL;
+  struct netgroup *lastgroup = NULL;
+
+  // Parse netgroups but don't fill in child entry pointers
+  for (;;) {
+    size_t line_alloc = 0;
+    char * line = NULL;
+    ssize_t line_size = getline(&line, &line_alloc, stream);
+    if (line_size == -1)
+      break;
+
+    struct netgroup *nextgroup = netgroup_parse_line(line);
+    free(line);
+    if (!nextgroup)
+      continue;
+
+    if (!headgroup) {
+      headgroup = nextgroup;
+      lastgroup = nextgroup;
+    } else {
+      lastgroup->next = nextgroup;
+      lastgroup = nextgroup;
+    }
+  }
+
+  fclose(stream);
+
+  // Fill in child entry pointers
+  struct netgroup *curgroup;
+  for (curgroup = headgroup; curgroup; curgroup = curgroup->next) {
+    netgroup_connect_children(curgroup->head, headgroup);
+  }
+
+  return headgroup;
+}
+
+void netgroup_free_all(struct netgroup *head) {
+  struct netgroup *group = head;
+  struct netgroup *nextgroup;
+  while (group) {
+    nextgroup = group->next;
+    netgroup_free(group);
+    group = nextgroup;
+  }
+}
+
+struct netgroup *netgroup_parse_line(char *line) {
+  char *cur = line;
+
+  // Get the netgroup's name
+  parser_skip_whitespace(&cur);
+  char *group_name = parser_copy_word(&cur);
+  if (!group_name)
+    return NULL;
+
+  // Create new netgroup object
+  struct netgroup *result = malloc(sizeof(struct netgroup));
+  if (!result)
+    return NULL;
+  result->next = NULL;
+  result->name = group_name;
+  result->head = NULL;
+
+  // Fill in netgroup entries
+  struct entry* lastentry = NULL;
+  for (;;) {
+    // Get the next word (anything non-space and non-null)
+    parser_skip_whitespace(&cur);
+    char *word = parser_copy_word(&cur);
+    if (!word)
+      break;
+
+    // Parse the entry
+    struct entry *entry = netgroup_parse_entry(word);
+    free(word);
+    if (!entry)
+      continue;
+
+    // Connect the entries together in a singly-linked list
+    if (lastentry) {
+      lastentry->next = entry;
+    } else {
+      result->head = entry;
+    }
+
+    lastentry = entry;
+  }
+
+  return result;
+}
+
+void netgroup_free(struct netgroup *group) {
+  if (!group)
+    return;
+
+  free(group->name);
+  netgroup_entry_free_all(group->head);
+  free(group);
+}
+
+struct entry *netgroup_parse_entry(const char *value) {
+  // Initialize the regex to match triples only on first call
+  static int regex_needs_init = 1;
+  static regex_t regex_triple;
+  if (regex_needs_init) {
+    if (regcomp(&regex_triple, NETGROUP_TRIPLE_REGEX, REG_EXTENDED))
+      return NULL;
+    regex_needs_init = 0;
+  }
+
+  struct entry *result = malloc(sizeof(struct entry));
+  if (!result)
+    return NULL;
+
+  memset(result, 0, sizeof(struct entry));
+
+  regmatch_t regex_triple_match [4];
+  if (regexec(&regex_triple, value, 4, regex_triple_match, 0) == REG_NOMATCH) {
+    // Match failed, assume entry is a netgroup name
+    result->type = CHILD_ENTRY;
+    result->data.child.name = strdup(value);
+    if (!result->data.child.name) {
+      netgroup_entry_free(result);
+      return NULL;
+    }
+  } else {
+    // Match success, entry is a triple
+    result->type = TRIPLE_ENTRY;
+
+    // Array of pointers to fields to set in triple
+    char ** triple [3] = {
+        &result->data.triple.hostname,
+        &result->data.triple.username,
+        &result->data.triple.domainname };
+    int i;
+
+    // Loop through each potential field in triple
+    for (i = 0; i < 3; i++) {
+      regoff_t start = regex_triple_match[i + 1].rm_so;
+      regoff_t end = regex_triple_match[i + 1].rm_eo;
+      regoff_t len = end - start;
+
+      if (start == -1 || len == 0) {
+        // This field is empty, so it matches anything
+        *triple[i] = NULL;
+      } else {
+        // Allocate and copy new field for triple
+        char *field = malloc(len + 1);
+        if (!field) {
+          netgroup_entry_free(result);
+          return NULL;
+        }
+        memcpy(field, &value[start], len);
+        field[len] = '\0';
+        *triple[i] = field;
+      }
+    }
+  }
+  return result;
+}
+
+void netgroup_entry_free_all(struct entry *head) {
+  struct entry *entry = head;
+  struct entry *nextentry;
+  while (entry) {
+    nextentry = entry->next;
+    netgroup_entry_free(entry);
+    entry = nextentry;
+  }
+}
+
+void netgroup_entry_free(struct entry *entry) {
+  if (!entry)
+    return;
+
+  if (entry->type == TRIPLE_ENTRY) {
+    FREE_IF_NOT_NULL(entry->data.triple.hostname);
+    FREE_IF_NOT_NULL(entry->data.triple.username);
+    FREE_IF_NOT_NULL(entry->data.triple.domainname);
+  } else {
+    FREE_IF_NOT_NULL(entry->data.child.name);
+  }
+
+  free(entry);
+}
+
+struct netgroup *netgroup_find(struct netgroup *head, const char *name) {
+  struct netgroup *group;
+  for (group = head; group && strcmp(group->name, name); group = group->next) {}
+  return group;
+}
+
+void netgroup_iter_init(struct netgroup_iter *iter, struct netgroup *group) {
+  iter->stack[0] = group->head;
+  iter->depth = 0;
+}
+
+struct entry *netgroup_iter_next(struct netgroup_iter *iter) {
+  while (iter->depth >= 0) {
+    struct entry *cur = iter->stack[iter->depth];
+
+    if (!cur) {
+      // Pop current finished entry off stack
+      iter->depth--;
+    } else if (cur->type == CHILD_ENTRY) {
+      // Replace the current location on the stack with the next sibling
+      iter->stack[iter->depth] = cur->next;
+
+      // Grow the stack
+      iter->depth++;
+      if (iter->depth > NETGROUP_MAX_DEPTH) {
+        iter->depth = -1;
+        return NULL; // Too much recursion
+      }
+
+      // Put this entry's children on top of the stack
+      struct entry *child = cur->data.child.head;
+      iter->stack[iter->depth] = child;
+    } else {
+      // Replace the current location on the stack with the next sibling
+      iter->stack[iter->depth] = cur->next;
+      return cur;
+    }
+  }
+
+  return NULL;
+}
diff --git a/test/mocklibc/src/netgroup.h b/test/mocklibc/src/netgroup.h
new file mode 100644
index 0000000..11cf7eb
--- /dev/null
+++ b/test/mocklibc/src/netgroup.h
@@ -0,0 +1,144 @@
+/**
+ * Copyright 2011 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Nikki VonHollen <vonhollen at gmail.com>
+ */
+
+#ifndef NETGROUP_H_
+#define NETGROUP_H_
+
+#define NETGROUP_MAX_DEPTH 32
+
+/**
+ * Netgroup with a name and list of entries.
+ */
+struct netgroup;
+
+/**
+ * Entry in a netgroup, either a triple or sub-group (child).
+ */
+struct entry;
+
+struct netgroup {
+  /* Next netgroup in list. */
+  struct netgroup *next; // Next netgroup in list
+
+  /* Netgroup name. */
+  char *name;
+
+  /* First entry in list of entries. */
+  struct entry *head;
+};
+
+struct entry {
+  /* Next entry in list of entries for the parent netgroup. */
+  struct entry *next;
+
+  /* Entry type is triple (host,user,domain) or child (netgroup name). */
+  enum {CHILD_ENTRY, TRIPLE_ENTRY} type;
+
+  union {
+    /* Child data if entry is a netgroup name. */
+    struct {
+      /* Child netgroup name. */
+      char *name;
+
+      /* Pointer to first entry in child netgroup. */
+      struct entry *head;
+    } child;
+
+    /* Triple data if entry type is triple. */
+    struct {
+      char *hostname;
+      char *username;
+      char *domainname;
+    } triple;
+  } data;
+};
+
+/* Recursive netgroup entry iterator. */
+struct netgroup_iter {
+  struct entry *stack [NETGROUP_MAX_DEPTH];
+  int depth;
+};
+
+
+/**
+ * Load full netgroup database into memory.
+ * @return Head netgroup
+ */
+struct netgroup *netgroup_parse_all();
+
+/**
+ * Free a list of netgroups.
+ * @param head Head of list of netgroups
+ */
+void netgroup_free_all(struct netgroup *head);
+
+/**
+ * Parse a single netgroup.
+ * @param line Line for netgroup definition
+ * @return Single netgroup with list of netgroup entries
+ */
+struct netgroup *netgroup_parse_line(char *line);
+
+/**
+ * Free single netgroup.
+ * @param group Netgroup to free
+ */
+void netgroup_free(struct netgroup *group);
+
+/**
+ * Parse a single netgroup entry.
+ * @param value Entry triple or name as string
+ * @return Single netgroup entry
+ */
+struct entry *netgroup_parse_entry(const char *value);
+
+/**
+ * Free a list of netgroup entries.
+ * @param head Head of list of entries
+ */
+void netgroup_entry_free_all(struct entry *head);
+
+/**
+ * Free a single netgroup entry.
+ * @param entry Netgroup entry to free
+ */
+void netgroup_entry_free(struct entry *entry);
+
+/**
+ * Find netgroup with given name.
+ * @param head Head of list of netgroups
+ * @param name Name to find
+ * @return Netgroup with name or NULL if not found
+ */
+struct netgroup *netgroup_find(struct netgroup *head, const char *name);
+
+/**
+ * Create recursive iterator over all entries in a netgroup.
+ * @param iter Pointer to iterator struct
+ * @param group Group to iterate over
+ */
+void netgroup_iter_init(struct netgroup_iter *iter, struct netgroup *group);
+
+/**
+ * Get the next entry in the netgroup iterator.
+ * @param iter Pointer to iterator struct
+ * @return Netgroup entry of type triple, or NULL if done iterating
+ */
+struct entry *netgroup_iter_next(struct netgroup_iter *iter);
+
+#endif
diff --git a/test/mocklibc/src/pwd.c b/test/mocklibc/src/pwd.c
new file mode 100644
index 0000000..6005a1d
--- /dev/null
+++ b/test/mocklibc/src/pwd.c
@@ -0,0 +1,99 @@
+/**
+ * Copyright 2011 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Nikki VonHollen <vonhollen at gmail.com>
+ */
+
+#include <pwd.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#define PASSWD_CONFIG_KEY "MOCK_PASSWD"
+
+static FILE *global_stream = NULL;
+
+void setpwent(void) {
+  if (global_stream)
+    endpwent();
+
+  const char *path = getenv(PASSWD_CONFIG_KEY);
+  if (!path)
+    return;
+
+  global_stream = fopen(path, "r");
+}
+
+struct passwd *getpwent(void) {
+  if (!global_stream)
+    setpwent();
+
+  if (!global_stream)
+    return NULL;
+
+  return fgetpwent(global_stream);
+}
+
+void endpwent(void) {
+  if (!global_stream)
+    return;
+
+  fclose(global_stream);
+  global_stream = NULL;
+}
+
+struct passwd *getpwnam(const char *name) {
+  const char *path = getenv(PASSWD_CONFIG_KEY);
+  if (!path)
+    return NULL;
+
+  FILE *stream = fopen(path, "r");
+  if (!stream)
+    return NULL;
+
+  struct passwd *entry;
+  while ((entry = fgetpwent(stream))) {
+    if (strcmp(entry->pw_name, name) == 0) {
+      fclose(stream);
+      return entry;
+    }
+  }
+
+  fclose(stream);
+  return NULL;
+}
+
+struct passwd *getpwuid(uid_t uid) {
+  const char *path = getenv(PASSWD_CONFIG_KEY);
+  if (!path)
+    return NULL;
+
+  FILE *stream = fopen(path, "r");
+  if (!stream)
+    return NULL;
+
+  struct passwd *entry;
+  while ((entry = fgetpwent(stream))) {
+    if (entry->pw_uid == uid) {
+      fclose(stream);
+      return entry;
+    }
+  }
+
+  fclose(stream);
+  return NULL;
+}
diff --git a/test/polkit/Makefile.am b/test/polkit/Makefile.am
index 70fbf67..27bcb95 100644
--- a/test/polkit/Makefile.am
+++ b/test/polkit/Makefile.am
@@ -4,6 +4,7 @@ NULL =
 INCLUDES =                                              	\
 	-I$(top_builddir)/src                           	\
 	-I$(top_srcdir)/src                             	\
+	-I$(top_srcdir)/test                             	\
 	-DPACKAGE_LIBEXEC_DIR=\""$(libexecdir)"\"       	\
 	-DPACKAGE_SYSCONF_DIR=\""$(sysconfdir)"\"       	\
 	-DPACKAGE_DATA_DIR=\""$(datadir)"\"             	\
@@ -16,6 +17,8 @@ INCLUDES =                                              	\
 	$(NULL)
 
 AM_CFLAGS =            				\
+	-D_POLKIT_COMPILATION                                   \
+	-D_POLKIT_BACKEND_COMPILATION                           \
 	$(GLIB_CFLAGS)						\
 	$(NULL)
 
@@ -34,6 +37,9 @@ polkitunixusertest_SOURCES = polkitunixusertest.c
 TEST_PROGS += polkitunixgrouptest
 polkitunixgrouptest_SOURCES = polkitunixgrouptest.c
 
+TEST_PROGS += polkitunixnetgrouptest
+polkitunixnetgrouptest_SOURCES = polkitunixnetgrouptest.c
+
 TEST_PROGS += polkitidentitytest
 polkitidentitytest_SOURCES = polkitidentitytest.c
 
diff --git a/test/polkit/polkitidentitytest.c b/test/polkit/polkitidentitytest.c
index edbc765..3b8dd5e 100644
--- a/test/polkit/polkitidentitytest.c
+++ b/test/polkit/polkitidentitytest.c
@@ -21,115 +21,149 @@
 
 #include "glib.h"
 #include <polkit/polkit.h>
+#include <polkit/polkitprivate.h>
 
+/* Test helper types */
 
-static void
-test_user_from_string (void)
-{
-  PolkitIdentity *identity;
-  PolkitUnixUser *user;
-  GError *error = NULL;
-
-  identity = polkit_identity_from_string ("unix-user:root", &error);
-  g_assert (identity);
-  g_assert_no_error (error);
-  g_assert (POLKIT_IS_UNIX_USER (identity));
+struct ComparisonTestData {
+  const gchar *subject_a;
+  const gchar *subject_b;
+  gboolean equal;
+};
 
-  user = POLKIT_UNIX_USER (identity);
-  g_assert (user);
-
-  g_object_unref (user);
-}
 
+/* Test definitions */
 
 static void
-test_group_from_string (void)
+test_string (const void *_subject)
 {
+  const gchar *subject = (const gchar *) _subject;
+
   PolkitIdentity *identity;
-  PolkitUnixGroup *group;
   GError *error = NULL;
+  gchar *subject_new;
 
-  identity = polkit_identity_from_string ("unix-group:root", &error);
+  /* Create the subject from a string */
+  identity = polkit_identity_from_string (subject, &error);
   g_assert (identity);
   g_assert_no_error (error);
-  g_assert (POLKIT_IS_UNIX_GROUP (identity));
 
-  group = POLKIT_UNIX_GROUP (identity);
-  g_assert (group);
+  /* Create new string for identity */
+  subject_new = polkit_identity_to_string (identity);
+
+  /* Make sure they match */
+  g_assert_cmpstr (subject_new, ==, subject);
 
-  g_object_unref (group);
+  g_free (subject_new);
+  g_object_unref (identity);
 }
 
 
 static void
-test_user_to_string (void)
+test_gvariant (const void *_subject)
 {
-  PolkitIdentity *identity;
+  const gchar *subject = (const gchar *) _subject;
+
+  PolkitIdentity *identity, *new_identity;
   GError *error = NULL;
-  gchar *value;
+  GVariant *value;
 
-  identity = polkit_identity_from_string ("unix-user:root", &error);
+  /* Create the subject from a string */
+  identity = polkit_identity_from_string (subject, &error);
+  g_assert_no_error (error);
   g_assert (identity);
+
+  /* Create a GVariant for the subject */
+  value = polkit_identity_to_gvariant (identity);
+  g_assert (value);
+
+  /* Unserialize the subject */
+  new_identity = polkit_identity_new_for_gvariant (value, &error);
   g_assert_no_error (error);
+  g_assert (new_identity);
+  g_variant_unref (value);
 
-  value = polkit_identity_to_string (identity);
-  g_assert_cmpstr (value, ==, "unix-user:root");
+  /* Make sure the two identities are equal */
+  g_assert (new_identity);
+  g_assert (polkit_identity_equal (identity, new_identity));
 
-  g_free (value);
   g_object_unref (identity);
+  g_object_unref (new_identity);
 }
 
 
 static void
-test_group_to_string (void)
+test_comparison (const void *_data)
 {
-  PolkitIdentity *identity;
+  struct ComparisonTestData *data = (struct ComparisonTestData *) _data;
+
+  PolkitIdentity *identity_a, *identity_b;
   GError *error = NULL;
-  gchar *value;
+  guint hash_a, hash_b;
 
-  identity = polkit_identity_from_string ("unix-group:root", &error);
-  g_assert (identity);
+  /* Create identities A and B */
+  identity_a = polkit_identity_from_string (data->subject_a, &error);
   g_assert_no_error (error);
+  g_assert (identity_a);
 
-  value = polkit_identity_to_string (identity);
-  g_assert_cmpstr (value, ==, "unix-group:root");
-
-  g_free (value);
-  g_object_unref (identity);
-}
-
+  identity_b = polkit_identity_from_string (data->subject_b, &error);
+  g_assert_no_error (error);
+  g_assert (identity_b);
 
-static void
-test_equal (void)
-{
-  PolkitIdentity *identity_a, *identity_b;
-  GError *error = NULL;
+  /* Compute their hashes */
+  hash_a = polkit_identity_hash (identity_a);
+  hash_b = polkit_identity_hash (identity_b);
 
-  identity_a = polkit_identity_from_string ("unix-group:root", &error);
-  identity_b = polkit_identity_from_string ("unix-group:root", &error);
-  g_assert (polkit_identity_equal (identity_a, identity_b));
+  /* Comparison to self should always work */
+  g_assert (polkit_identity_equal (identity_a, identity_a));
+
+  /* Are A and B supposed to match? Test hash and comparators */
+  if (data->equal)
+  {
+    g_assert_cmpint (hash_a, ==, hash_b);
+    g_assert (polkit_identity_equal (identity_a, identity_b));
+  }
+  else
+  {
+    g_assert_cmpint (hash_a, !=, hash_b);
+    g_assert (!polkit_identity_equal (identity_a, identity_b));
+  }
 
   g_object_unref (identity_a);
   g_object_unref (identity_b);
 }
 
 
-static void
-test_hash (void)
-{
-  PolkitIdentity *identity_a, *identity_b;
-  guint hash_a, hash_b;
-  GError *error = NULL;
+/* Test helpers */
 
-  identity_a = polkit_identity_from_string ("unix-group:root", &error);
-  identity_b = polkit_identity_from_string ("unix-group:root", &error);
+struct ComparisonTestData comparison_test_data [] = {
+  {"unix-user:root", "unix-user:root", TRUE},
+  {"unix-user:root", "unix-user:john", FALSE},
+  {"unix-user:john", "unix-user:john", TRUE},
 
-  hash_a = polkit_identity_hash (identity_a);
-  hash_b = polkit_identity_hash (identity_b);
-  g_assert_cmpint (hash_a, ==, hash_b);
+  {"unix-group:root", "unix-group:root", TRUE},
+  {"unix-group:root", "unix-group:jane", FALSE},
+  {"unix-group:jane", "unix-group:jane", TRUE},
 
-  g_object_unref (identity_a);
-  g_object_unref (identity_b);
+  {"unix-netgroup:foo", "unix-netgroup:foo", TRUE},
+  {"unix-netgroup:foo", "unix-netgroup:bar", FALSE},
+
+  {"unix-user:root", "unix-group:root", FALSE},
+  {"unix-user:jane", "unix-netgroup:foo", FALSE},
+
+  {NULL},
+};
+
+static void
+add_comparison_tests (void)
+{
+  unsigned int i;
+  for (i = 0; comparison_test_data[i].subject_a != NULL; i++)
+  {
+    struct ComparisonTestData *test_data = &comparison_test_data[i];
+    gchar *test_name = g_strdup_printf ("/PolkitIdentity/comparison_%d", i);
+    g_test_add_data_func (test_name, test_data, test_comparison);
+  }
 }
 
 
@@ -138,11 +172,23 @@ main (int argc, char *argv[])
 {
   g_type_init ();
   g_test_init (&argc, &argv, NULL);
-  g_test_add_func ("/PolkitIdentity/user_from_string", test_user_from_string);
-  g_test_add_func ("/PolkitIdentity/user_to_string", test_user_to_string);
-  g_test_add_func ("/PolkitIdentity/group_from_string", test_group_from_string);
-  g_test_add_func ("/PolkitIdentity/group_to_string", test_group_to_string);
-  g_test_add_func ("/PolkitIdentity/equal", test_equal);
-  g_test_add_func ("/PolkitIdentity/hash", test_hash);
+
+  g_test_add_data_func ("/PolkitIdentity/user_string_0", "unix-user:root", test_string);
+  g_test_add_data_func ("/PolkitIdentity/user_string_1", "unix-user:john", test_string);
+  g_test_add_data_func ("/PolkitIdentity/user_string_2", "unix-user:jane", test_string);
+
+  g_test_add_data_func ("/PolkitIdentity/group_string_0", "unix-group:root", test_string);
+  g_test_add_data_func ("/PolkitIdentity/group_string_1", "unix-group:john", test_string);
+  g_test_add_data_func ("/PolkitIdentity/group_string_2", "unix-group:jane", test_string);
+  g_test_add_data_func ("/PolkitIdentity/group_string_3", "unix-group:users", test_string);
+
+  g_test_add_data_func ("/PolkitIdentity/netgroup_string", "unix-netgroup:foo", test_string);
+
+  g_test_add_data_func ("/PolkitIdentity/user_gvariant", "unix-user:root", test_gvariant);
+  g_test_add_data_func ("/PolkitIdentity/group_gvariant", "unix-group:root", test_gvariant);
+  g_test_add_data_func ("/PolkitIdentity/netgroup_gvariant", "unix-netgroup:foo", test_gvariant);
+
+  add_comparison_tests ();
+
   return g_test_run ();
 }
diff --git a/test/polkit/polkitunixnetgrouptest.c b/test/polkit/polkitunixnetgrouptest.c
new file mode 100644
index 0000000..c67822e
--- /dev/null
+++ b/test/polkit/polkitunixnetgrouptest.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Nikki VonHollen <vonhollen at google.com>
+ */
+
+#include "glib.h"
+#include <polkit/polkit.h>
+#include <string.h>
+
+
+static void
+test_new (void)
+{
+  PolkitUnixNetgroup *netgroup;
+  const char *netgroup_name;
+
+  netgroup = POLKIT_UNIX_NETGROUP (polkit_unix_netgroup_new ("testgroup"));
+  g_assert (netgroup);
+
+  netgroup_name = polkit_unix_netgroup_get_name (netgroup);
+  g_assert_cmpstr (netgroup_name, ==, "testgroup");
+
+  g_object_unref (netgroup);
+}
+
+
+static void
+test_set_name (void)
+{
+  PolkitUnixNetgroup *netgroup;
+  const char *netgroup_name;
+  char new_name_buf [] = "foo";
+
+  netgroup = POLKIT_UNIX_NETGROUP (polkit_unix_netgroup_new ("testgroup"));
+
+  polkit_unix_netgroup_set_name (netgroup, new_name_buf);
+  netgroup_name = polkit_unix_netgroup_get_name (netgroup);
+  g_assert_cmpstr (netgroup_name, ==, "foo");
+
+  memcpy(new_name_buf, "bar", 3);
+  netgroup_name = polkit_unix_netgroup_get_name (netgroup);
+  g_assert_cmpstr (netgroup_name, ==, "foo");
+
+  polkit_unix_netgroup_set_name (netgroup, new_name_buf);
+  netgroup_name = polkit_unix_netgroup_get_name (netgroup);
+  g_assert_cmpstr (netgroup_name, ==, "bar");
+
+  g_object_unref (netgroup);
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+  g_test_add_func ("/PolkitUnixNetgroup/new", test_new);
+  g_test_add_func ("/PolkitUnixNetgroup/set_name", test_set_name);
+  return g_test_run ();
+}
diff --git a/test/polkit/polkitunixusertest.c b/test/polkit/polkitunixusertest.c
index 1ad0a65..ce35088 100644
--- a/test/polkit/polkitunixusertest.c
+++ b/test/polkit/polkitunixusertest.c
@@ -22,36 +22,58 @@
 #include "glib.h"
 #include <polkit/polkit.h>
 
+struct user_entry {
+  const gchar *name;
+  gint uid;
+};
+
+static struct user_entry user_entries [] = {
+  {"root", 0},
+  {"john", 500},
+  {"jane", 501},
+  {NULL},
+};
 
 static void
 test_new (void)
 {
-  PolkitUnixUser *user;
+  unsigned int i;
+  for (i = 0; user_entries[i].name != NULL; i++) {
+    gint uid = user_entries[i].uid;
 
-  user = POLKIT_UNIX_USER (polkit_unix_user_new (0));
-  g_assert (user);
+    PolkitUnixUser *user;
 
-  gint user_uid = polkit_unix_user_get_uid (user);
-  g_assert_cmpint (user_uid, ==, 0);
+    user = POLKIT_UNIX_USER (polkit_unix_user_new (uid));
+    g_assert (user);
 
-  g_object_unref (user);
+    gint user_uid = polkit_unix_user_get_uid (user);
+    g_assert_cmpint (user_uid, ==, uid);
+
+    g_object_unref (user);
+  }
 }
 
 
 static void
 test_new_for_name (void)
 {
-  GError *error = NULL;
-  PolkitUnixUser *user;
+  unsigned int i;
+  for (i = 0; user_entries[i].name != NULL; i++) {
+    const gchar *name = user_entries[i].name;
+    gint expect_uid = user_entries[i].uid;
 
-  user = POLKIT_UNIX_USER (polkit_unix_user_new_for_name ("root", &error));
-  g_assert (user);
-  g_assert_no_error (error);
+    GError *error = NULL;
+    PolkitUnixUser *user;
 
-  gint user_uid = polkit_unix_user_get_uid (user);
-  g_assert_cmpint (user_uid, ==, 0);
+    user = POLKIT_UNIX_USER (polkit_unix_user_new_for_name (name, &error));
+    g_assert (user);
+    g_assert_no_error (error);
 
-  g_object_unref (user);
+    gint user_uid = polkit_unix_user_get_uid (user);
+    g_assert_cmpint (user_uid, ==, expect_uid);
+
+    g_object_unref (user);
+  }
 }
 
 
diff --git a/test/polkitbackend/Makefile.am b/test/polkitbackend/Makefile.am
index 8067232..c611b5b 100644
--- a/test/polkitbackend/Makefile.am
+++ b/test/polkitbackend/Makefile.am
@@ -3,8 +3,8 @@ NULL =
 
 INCLUDES =                                              	\
 	-I$(top_builddir)/src                           	\
-	-I$(top_builddir)/test                          	\
 	-I$(top_srcdir)/src                             	\
+	-I$(top_srcdir)/test                             	\
 	-DPACKAGE_LIBEXEC_DIR=\""$(libexecdir)"\"       	\
 	-DPACKAGE_SYSCONF_DIR=\""$(sysconfdir)"\"       	\
 	-DPACKAGE_DATA_DIR=\""$(datadir)"\"             	\
diff --git a/test/polkitbackend/data/authstore1/10-test/com.example.pkla b/test/polkitbackend/data/authstore1/10-test/com.example.pkla
deleted file mode 100644
index e716465..0000000
--- a/test/polkitbackend/data/authstore1/10-test/com.example.pkla
+++ /dev/null
@@ -1,6 +0,0 @@
-[Normal Staff Permissions]
-Identity=unix-group:users;unix-user:root
-Action=com.example.awesomeproduct.*
-ResultAny=no
-ResultInactive=auth_self
-ResultActive=yes
diff --git a/test/polkitbackend/data/authstore2/10-test/com.example.pkla b/test/polkitbackend/data/authstore2/10-test/com.example.pkla
deleted file mode 100644
index f013c5b..0000000
--- a/test/polkitbackend/data/authstore2/10-test/com.example.pkla
+++ /dev/null
@@ -1,6 +0,0 @@
-[Super Secret Project Permissions]
-Identity=unix-user:root
-Action=com.example.restrictedproduct.*
-ResultAny=no
-ResultInactive=no
-ResultActive=auth_self
diff --git a/test/polkitbackend/polkitbackendlocalauthoritytest.c b/test/polkitbackend/polkitbackendlocalauthoritytest.c
index f76ea41..617c254 100644
--- a/test/polkitbackend/polkitbackendlocalauthoritytest.c
+++ b/test/polkitbackend/polkitbackendlocalauthoritytest.c
@@ -25,9 +25,9 @@
 #include <polkit/polkit.h>
 #include <polkitbackend/polkitbackendlocalauthority.h>
 
-#define TEST_CONFIG_PATH "./data/config"
-#define TEST_AUTH_PATH1 "./data/authstore1"
-#define TEST_AUTH_PATH2 "./data/authstore2"
+#define TEST_CONFIG_PATH "etc/polkit-1/localauthority.conf.d"
+#define TEST_AUTH_PATH1 "etc/polkit-1/localauthority"
+#define TEST_AUTH_PATH2 "var/lib/polkit-1/localauthority"
 
 /* Test helper types */
 
@@ -93,21 +93,96 @@ test_check_authorization_sync (const void *_ctx)
   g_object_unref (out_details);
 }
 
+static void
+test_get_admin_identities (void)
+{
+  /* Note: The implementation for get_admin_identities is called
+   * get_admin_auth_identities in PolkitBackendLocalAuthority */
+
+  PolkitBackendLocalAuthority *authority = create_authority ();
+
+  /* Setup required arguments, but none of their values matter */
+  PolkitSubject *caller = polkit_unix_session_new ("caller-session");
+  g_assert (caller);
+
+  PolkitSubject *subject = polkit_unix_session_new ("subject-session");;
+  g_assert (subject);
+
+  GError *error = NULL;
+  PolkitIdentity *user_for_subject = polkit_identity_from_string ("unix-user:root", &error);
+  g_assert_no_error (error);
+  g_assert (user_for_subject);
+
+  PolkitDetails *details = polkit_details_new ();
+  g_assert (details);
+
+  /* Get the list of PolkitUnixUser objects who are admins */
+  GList *result;
+  result = polkit_backend_interactive_authority_get_admin_identities (
+      POLKIT_BACKEND_INTERACTIVE_AUTHORITY (authority),
+      caller,
+      subject,
+      user_for_subject,
+      "com.example.doesntmatter",
+      details);
+
+  guint result_len = g_list_length (result);
+  g_assert_cmpint (result_len, >, 0);
+
+  /* Test against each of the admins in the following list */
+  const gchar *expect_admins [] = {
+    "unix-user:root",
+    "unix-user:jane",
+    "unix-user:sally",
+    "unix-user:henry",
+    NULL,
+  };
+
+  unsigned int i;
+  for (i = 0; expect_admins[i] != NULL; i++)
+  {
+    g_assert_cmpint (i, <, result_len);
+
+    PolkitIdentity *test_identity = POLKIT_IDENTITY (g_list_nth_data (result, i));
+    g_assert (test_identity);
+
+    gchar *test_identity_str = polkit_identity_to_string (test_identity);
+    g_assert_cmpstr (expect_admins[i], ==, test_identity_str);
+  }
+}
+
 
 /* Factory for mock local authority. */
 static PolkitBackendLocalAuthority *
 create_authority (void)
 {
-  return g_object_new (
+  gchar *config_path = polkit_test_get_data_path (TEST_CONFIG_PATH);
+  gchar *auth_path1 = polkit_test_get_data_path (TEST_AUTH_PATH1);
+  gchar *auth_path2 = polkit_test_get_data_path (TEST_AUTH_PATH2);
+  gchar *auth_paths = g_strconcat (auth_path1, ";", auth_path2, NULL);
+
+  g_assert (config_path);
+  g_assert (auth_path1);
+  g_assert (auth_path2);
+  g_assert (auth_paths);
+  
+  PolkitBackendLocalAuthority *authority = g_object_new (
       POLKIT_BACKEND_TYPE_LOCAL_AUTHORITY,
-      "config-path", TEST_CONFIG_PATH,
-      "auth-store-paths", TEST_AUTH_PATH1 ";" TEST_AUTH_PATH2,
+      "config-path", config_path,
+      "auth-store-paths", auth_paths,
       NULL);
+  
+  g_free (config_path);
+  g_free (auth_path1);
+  g_free (auth_path2);
+  g_free (auth_paths);
+  return authority;
 }
 
 
 /* Variations of the check_authorization_sync */
 struct auth_context check_authorization_test_data [] = {
+  /* Test root, john, and jane on action awesomeproduct.foo (all users are ok) */
   {"unix-user:root", TRUE, TRUE, "com.example.awesomeproduct.foo",
       POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
       POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED},
@@ -117,12 +192,41 @@ struct auth_context check_authorization_test_data [] = {
   {"unix-user:root", FALSE, FALSE, "com.example.awesomeproduct.foo",
       POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
       POLKIT_IMPLICIT_AUTHORIZATION_NOT_AUTHORIZED},
+  {"unix-user:john", TRUE, TRUE, "com.example.awesomeproduct.foo",
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
+      POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED},
+  {"unix-user:jane", TRUE, TRUE, "com.example.awesomeproduct.foo",
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
+      POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED},
+
+  /* Test root, john, and jane on action restrictedproduct.foo (only root is ok) */
   {"unix-user:root", TRUE, TRUE, "com.example.restrictedproduct.foo",
       POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
       POLKIT_IMPLICIT_AUTHORIZATION_AUTHENTICATION_REQUIRED},
+  {"unix-user:john", TRUE, TRUE, "com.example.restrictedproduct.foo",
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN},
+  {"unix-user:jane", TRUE, TRUE, "com.example.restrictedproduct.foo",
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN},
+
+  /* Test root against some missing actions */
   {"unix-user:root", TRUE, TRUE, "com.example.missingproduct.foo",
       POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
       POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN},
+
+  /* Test root, john, and jane against action awesomeproduct.bar
+   * which uses "unix-netgroup:baz" for auth (john and jane are OK, root is not) */
+  {"unix-user:root", TRUE, TRUE, "com.example.awesomeproduct.bar",
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN},
+  {"unix-user:john", TRUE, TRUE, "com.example.awesomeproduct.bar",
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
+      POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED},
+  {"unix-user:jane", TRUE, TRUE, "com.example.awesomeproduct.bar",
+      POLKIT_IMPLICIT_AUTHORIZATION_UNKNOWN,
+      POLKIT_IMPLICIT_AUTHORIZATION_AUTHORIZED},
+
   {NULL},
 };
 
@@ -135,7 +239,7 @@ add_check_authorization_tests (void) {
     struct auth_context *ctx = &check_authorization_test_data[i];
     gchar *test_name = g_strdup_printf (
         "/PolkitBackendLocalAuthority/check_authorization_sync_%d", i);
-    g_test_add_data_func(test_name, ctx, test_check_authorization_sync);
+    g_test_add_data_func (test_name, ctx, test_check_authorization_sync);
   }
 };
 
@@ -154,5 +258,7 @@ main (int argc, char *argv[])
       POLKIT_BACKEND_TYPE_AUTHORITY);
 
   add_check_authorization_tests ();
+  g_test_add_func ("/PolkitBackendLocalAuthority/get_admin_identities", test_get_admin_identities);
+
   return g_test_run ();
 };
diff --git a/test/polkitbackend/polkitbackendlocalauthorizationstoretest.c b/test/polkitbackend/polkitbackendlocalauthorizationstoretest.c
index 617acf9..945e163 100644
--- a/test/polkitbackend/polkitbackendlocalauthorizationstoretest.c
+++ b/test/polkitbackend/polkitbackendlocalauthorizationstoretest.c
@@ -25,16 +25,23 @@
 #include <polkit/polkit.h>
 #include <polkitbackend/polkitbackendlocalauthorizationstore.h>
 
-#define DATA_DIR "./data/authstore1/10-test"
+#define DATA_DIR "etc/polkit-1/localauthority/10-test"
 #define DATA_EXT ".pkla"
 
 static void
 test_new (void)
 {
   PolkitBackendLocalAuthorizationStore *store;
+  gchar *data_dir_path;
   GFile *data_dir;
 
-  data_dir = g_file_new_for_path (DATA_DIR);
+  data_dir_path = polkit_test_get_data_path (DATA_DIR);
+  g_assert (data_dir_path);
+
+  data_dir = g_file_new_for_path (data_dir_path);
+  g_assert (data_dir);
+
+  g_free (data_dir_path);
 
   store = polkit_backend_local_authorization_store_new (data_dir, DATA_EXT);
   g_assert (store);
@@ -44,6 +51,7 @@ test_new (void)
 static void
 test_lookup (void)
 {
+  gchar *data_dir_path;
   GFile *data_dir;
   PolkitBackendLocalAuthorizationStore *store;
   GError *error = NULL;
@@ -54,8 +62,14 @@ test_lookup (void)
   PolkitImplicitAuthorization ret_active;
   PolkitDetails *details;
 
+  // Get auth store path
+  data_dir_path = polkit_test_get_data_path (DATA_DIR);
+  g_assert (data_dir_path);
+
+  data_dir = g_file_new_for_path (data_dir_path);
+  g_assert (data_dir);
+  
   // Create the auth store
-  data_dir = g_file_new_for_path (DATA_DIR);
   store = polkit_backend_local_authorization_store_new (data_dir, DATA_EXT);
   g_assert (store);
 
@@ -63,7 +77,7 @@ test_lookup (void)
   details = polkit_details_new ();
 
   // Create an identity to query with
-  identity = polkit_identity_from_string("unix-group:users", &error);
+  identity = polkit_identity_from_string ("unix-group:users", &error);
   g_assert (identity);
   g_assert_no_error (error);
 
@@ -71,7 +85,7 @@ test_lookup (void)
   ok = polkit_backend_local_authorization_store_lookup (
       store,
       identity,
-      "com.example.awesomeproduct.dofoo",
+      "com.example.awesomeproduct.foo",
       details,
       &ret_any,
       &ret_inactive,
@@ -83,7 +97,7 @@ test_lookup (void)
   g_assert_cmpstr ("yes", ==, polkit_implicit_authorization_to_string (ret_active));
 
   // Create another identity to query with
-  identity = polkit_identity_from_string("unix-user:root", &error);
+  identity = polkit_identity_from_string ("unix-user:root", &error);
   g_assert (identity);
   g_assert_no_error (error);
 
@@ -91,7 +105,7 @@ test_lookup (void)
   ok = polkit_backend_local_authorization_store_lookup (
       store,
       identity,
-      "com.example.awesomeproduct.dofoo",
+      "com.example.awesomeproduct.foo",
       details,
       &ret_any,
       &ret_inactive,
diff --git a/test/polkittesthelper.c b/test/polkittesthelper.c
index 5373bdc..41c4ce5 100644
--- a/test/polkittesthelper.c
+++ b/test/polkittesthelper.c
@@ -20,6 +20,8 @@
  */
 
 #include "polkittesthelper.h"
+#include <stdlib.h>
+
 
 /* TODO: Log handling with unit tests is horrible. Figure out a way to always
  *       show logs, without munging up test output. For now, we hide them
@@ -46,3 +48,21 @@ polkit_test_redirect_logs (void)
   g_log_set_default_handler (polkit_test_log_handler, NULL);
 }
 
+/**
+ * Get absolute path to test data.
+ *
+ * Requires POLKIT_TEST_DATA environment variable to point to root data dir.
+ *
+ * @param relpath Relative path to test data
+ * @return Full path to data as string. Free with g_free().
+ */
+gchar *
+polkit_test_get_data_path (const gchar *relpath)
+{
+  const gchar *root = getenv ("POLKIT_TEST_DATA");
+  if (root == NULL)
+    return NULL;
+
+  return g_strconcat(root, "/", relpath, NULL);
+}
+
diff --git a/test/polkittesthelper.h b/test/polkittesthelper.h
index c8ce161..da49b06 100644
--- a/test/polkittesthelper.h
+++ b/test/polkittesthelper.h
@@ -31,4 +31,6 @@ void polkit_test_log_handler (const gchar *log_domain,
 
 void polkit_test_redirect_logs (void);
 
+gchar *polkit_test_get_data_path (const gchar *relpath);
+
 #endif


More information about the hal-commit mailing list