Commit 02d7fd26 authored by Stef Walter's avatar Stef Walter

pam: Add some tests for the PAM module

These require you to install some pam configs into /etc/pam.d. You
can do it with the following commands:

$ make enable-pam-tests
$ make disable-pam-tests
parent 86bd8c5a
...@@ -101,6 +101,12 @@ You check for memory errors by doing: ...@@ -101,6 +101,12 @@ You check for memory errors by doing:
$ make check-memory $ make check-memory
To test the pam code, you must first place some custom PAM configuration
in your /etc/pam.d. This will not be used by anything else. To do this:
$ sudo make enable-pam-tests
$ sudo make disable-pam-tests
---------------------------------------------------------------------------------- ----------------------------------------------------------------------------------
CODING STYLE CODING STYLE
......
...@@ -129,6 +129,8 @@ SED_SUBST = sed \ ...@@ -129,6 +129,8 @@ SED_SUBST = sed \
-e 's,[@]prefix[@],$(prefix),g' \ -e 's,[@]prefix[@],$(prefix),g' \
-e 's,[@]abs_srcdir[@],$(abs_srcdir),g' \ -e 's,[@]abs_srcdir[@],$(abs_srcdir),g' \
-e 's,[@]srcdir[@],$(srcdir),g' \ -e 's,[@]srcdir[@],$(srcdir),g' \
-e 's,[@]builddir[@],$(builddir),g' \
-e 's,[@]abs_builddir[@],$(abs_builddir),g' \
-e 's,[@]PACKAGE[@],$(PACKAGE),g' \ -e 's,[@]PACKAGE[@],$(PACKAGE),g' \
-e 's,[@]VERSION[@],$(VERSION),g' \ -e 's,[@]VERSION[@],$(VERSION),g' \
$(NULL) $(NULL)
......
...@@ -18,3 +18,57 @@ pam_gnome_keyring_la_LIBADD = \ ...@@ -18,3 +18,57 @@ pam_gnome_keyring_la_LIBADD = \
pam_gnome_keyring_la_LDFLAGS = \ pam_gnome_keyring_la_LDFLAGS = \
-module -avoid-version \ -module -avoid-version \
-export-symbols-regex 'pam_sm_' -export-symbols-regex 'pam_sm_'
# -----------------------------------------------------------------------------
# PAM tests
pam_CONFIGS = \
pam/fixtures/gnome-keyring-test-auth-start \
pam/fixtures/gnome-keyring-test-no-start \
pam/fixtures/gnome-keyring-test-session-start \
$(NULL)
EXTRA_DIST += pam/fixtures
enable-pam-tests:
for t in $(notdir $(pam_CONFIGS)); do \
$(SED_SUBST) $(srcdir)/pam/fixtures/$$t > $(sysconfdir)/pam.d/$$t; \
done
disable-pam-tests:
for t in $(notdir $(pam_CONFIGS)); do \
$(RM) -f $(sysconfdir)/pam.d/$$t; \
done
noinst_LTLIBRARIES += pam_mock.la
pam_mock_la_SOURCES = pam/mock-pam.c
pam_mock_la_LIBADD = \
-lpam
pam_mock_la_LDFLAGS = \
-module -avoid-version \
-export-symbols-regex 'pam_sm_' \
-rpath /force/shared
pam_CFLAGS = \
-DSYSCONFDIR=\"$(sysconfdir)\" \
$(NULL)
pam_LIBS = \
libgkd-control-client.la \
libgkd-test.la \
libegg.la \
libegg-test.la \
$(GIO_LIBS) \
$(GLIB_LIBS) \
-lpam
pam_TESTS = \
test-pam
test_pam_SOURCES = pam/test-pam.c
test_pam_LDADD = $(pam_LIBS)
test_pam_CFLAGS = $(pam_CFLAGS)
check_PROGRAMS += $(pam_TESTS)
TESTS += $(pam_TESTS)
auth required @abs_builddir@/.libs/pam_mock.so
auth required @abs_builddir@/.libs/pam_gnome_keyring.so auto_start
password required @abs_builddir@/.libs/pam_mock.so
password required @abs_builddir@/.libs/pam_gnome_keyring.so use_authtok
session required @abs_builddir@/.libs/pam_mock.so
session required @abs_builddir@/.libs/pam_gnome_keyring.so
auth required @abs_builddir@/.libs/pam_mock.so
auth required @abs_builddir@/.libs/pam_gnome_keyring.so
password required @abs_builddir@/.libs/pam_mock.so
password required @abs_builddir@/.libs/pam_gnome_keyring.so use_authtok
session required @abs_builddir@/.libs/pam_mock.so
session required @abs_builddir@/.libs/pam_gnome_keyring.so
auth required @abs_builddir@/.libs/pam_mock.so
auth required @abs_builddir@/.libs/pam_gnome_keyring.so
password required @abs_builddir@/.libs/pam_mock.so
password required @abs_builddir@/.libs/pam_gnome_keyring.so use_authtok
session required @abs_builddir@/.libs/pam_mock.so
session required @abs_builddir@/.libs/pam_gnome_keyring.so auto_start
/*
Copyright (C) 2014 Red Hat Inc.
The Gnome Keyring Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The Gnome Keyring 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the Gnome Library; see the file COPYING.LIB. If not,
<http://www.gnu.org/licenses/>.
Author: Stef Walter <stefw@gnome.org>
*/
#include "config.h"
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <stdlib.h>
#include <string.h>
static int
prompt_password (pam_handle_t *ph,
const char *prompt,
int password_type)
{
const struct pam_conv *conv;
struct pam_message msg;
struct pam_response *resp;
const struct pam_message *msgs[1];
const void *item;
char *password;
int ret;
/* Get the conversation function */
ret = pam_get_item (ph, PAM_CONV, &item);
if (ret != PAM_SUCCESS)
return ret;
/* Setup a message */
memset (&msg, 0, sizeof (msg));
memset (&resp, 0, sizeof (resp));
msg.msg_style = PAM_PROMPT_ECHO_OFF;
msg.msg = prompt;
msgs[0] = &msg;
/* Call away */
conv = (const struct pam_conv*)item;
ret = (conv->conv) (1, msgs, &resp, conv->appdata_ptr);
if (ret != PAM_SUCCESS)
return ret;
password = resp[0].resp;
free (resp);
if (password == NULL)
return PAM_CONV_ERR;
/* Store it away for later use */
ret = pam_set_item (ph, password_type, password);
free (password);
if (ret == PAM_SUCCESS)
ret = pam_get_item (ph, password_type, &item);
return ret;
}
PAM_EXTERN int
pam_sm_authenticate (pam_handle_t *ph,
int unused,
int argc,
const char **argv)
{
/* Just prompt for the password, accept any result */
return prompt_password (ph, "Password: ", PAM_AUTHTOK);
}
PAM_EXTERN int
pam_sm_open_session (pam_handle_t *ph,
int flags,
int argc,
const char **argv)
{
return PAM_IGNORE;
}
PAM_EXTERN int
pam_sm_close_session (pam_handle_t *ph,
int flags,
int argc,
const char **argv)
{
return PAM_IGNORE;
}
PAM_EXTERN int
pam_sm_setcred (pam_handle_t *ph,
int flags,
int argc,
const char **argv)
{
return PAM_SUCCESS;
}
PAM_EXTERN int
pam_sm_chauthtok (pam_handle_t *ph,
int flags,
int argc,
const char **argv)
{
if (flags & PAM_PRELIM_CHECK)
return prompt_password (ph, "Old Password: ", PAM_OLDAUTHTOK);
else if (flags & PAM_UPDATE_AUTHTOK)
return prompt_password (ph, "New Password: ", PAM_AUTHTOK);
else
return PAM_IGNORE;
}
/*
Copyright (C) 2014 Red Hat Inc
The Gnome Keyring Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The Gnome Keyring 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the Gnome Library; see the file COPYING.LIB. If not,
<http://www.gnu.org/licenses/>.
Author: Stef Walter <stefw@gnome.org>
*/
#include "config.h"
#include "daemon/control/gkd-control.h"
#include "daemon/gkd-test.h"
#include "egg/egg-testing.h"
#include "egg/egg-secure-memory.h"
#include <security/pam_appl.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
EGG_SECURE_DEFINE_GLIB_GLOBALS ();
typedef struct {
GTestDBus *dbus;
GDBusConnection *connection;
gchar *directory;
GPid pid;
gboolean skipping;
pam_handle_t *ph;
struct pam_conv conv;
const gchar *password;
const gchar *new_password;
} Test;
const gchar *PASS_ENVIRON[] = {
"DBUS_SESSION_ADDRESS",
"XDG_RUNTIME_DIR",
"XDG_DATA_HOME",
NULL
};
static void
skip_test (Test *test,
const gchar *reason)
{
test->skipping = TRUE;
#if GLIB_CHECK_VERSION(2, 40, 0)
g_test_skip (reason);
#else
if (g_test_verbose ())
g_print ("GTest: skipping: %s\n", reason);
else
g_print ("SKIP: %s ", reason);
#endif
}
static int
conv_func (int n,
const struct pam_message **msg,
struct pam_response **resp,
void *arg)
{
struct pam_response *aresp;
Test *test = arg;
int i;
g_assert (n > 0 && n < PAM_MAX_NUM_MSG);
aresp = g_new0(struct pam_response, n);
for (i = 0; i < n; ++i) {
aresp[i].resp_retcode = 0;
aresp[i].resp = NULL;
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
if (test->password) {
aresp[i].resp = strdup (test->password);
test->password = NULL;
} else if (test->new_password) {
aresp[i].resp = strdup (test->new_password);
test->new_password = NULL;
}
g_assert (aresp[i].resp != NULL);
break;
case PAM_PROMPT_ECHO_ON:
aresp[i].resp = strdup (test->password);
g_assert (aresp[i].resp != NULL);
break;
case PAM_ERROR_MSG:
fputs(msg[i]->msg, stderr);
if (strlen(msg[i]->msg) > 0 &&
msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
fputc('\n', stderr);
break;
case PAM_TEXT_INFO:
fprintf(stdout, "# %s", msg[i]->msg);
if (strlen(msg[i]->msg) > 0 &&
msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
fputc('\n', stdout);
break;
default:
return PAM_CONV_ERR;
}
}
*resp = aresp;
return PAM_SUCCESS;
}
static void
setup (Test *test,
gconstpointer user_data)
{
const gchar *pam_conf = user_data;
GError *error = NULL;
gchar *contents;
gboolean found;
gchar *filename;
gchar *env;
int ret;
/* First check if we have the right pam config */
filename = g_build_filename (SYSCONFDIR, "pam.d", pam_conf, NULL);
g_file_get_contents (filename, &contents, NULL, &error);
g_free (filename);
if (error == NULL) {
found = (strstr (contents, BUILDDIR) &&
strstr (contents, "pam_gnome_keyring.so"));
g_free (contents);
if (!found) {
skip_test (test, "test pam config contents invalid");
return;
}
} else if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
skip_test (test, "missing test pam config");
return;
}
g_assert_no_error (error);
test->dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
g_test_dbus_up (test->dbus);
test->directory = egg_tests_create_scratch_directory (NULL, NULL);
g_setenv ("XDG_RUNTIME_DIR", test->directory, TRUE);
test->conv.conv = conv_func;
test->conv.appdata_ptr = test;
ret = pam_start (pam_conf, g_get_user_name (), &test->conv, &test->ph);
g_assert_cmpint (ret, ==, PAM_SUCCESS);
g_unsetenv ("GNOME_KEYRING_CONTROL");
g_assert_cmpint (pam_putenv (test->ph, "GSETTINGS_SCHEMA_DIR=" BUILDDIR "/schema"), ==, PAM_SUCCESS);
g_assert_cmpint (pam_putenv (test->ph, "G_DEBUG=fatal-warnings,fatal-criticals"), ==, PAM_SUCCESS);
env = g_strdup_printf ("GNOME_KEYRING_TEST_PATH=%s", test->directory);
g_assert_cmpint (pam_putenv (test->ph, env), ==, PAM_SUCCESS);
g_free (env);
env = g_strdup_printf ("DBUS_SESSION_BUS_ADDRESS=%s", g_test_dbus_get_bus_address (test->dbus));
g_assert_cmpint (pam_putenv (test->ph, env), ==, PAM_SUCCESS);
g_free (env);
test->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_assert_no_error (error);
}
static void
teardown (Test *test,
gconstpointer unused)
{
if (test->skipping)
return;
g_object_unref (test->connection);
pam_end (test->ph, PAM_SUCCESS);
if (test->pid) {
if (waitpid (test->pid, NULL, WNOHANG) != test->pid) {
kill (test->pid, SIGTERM);
g_assert_cmpint (waitpid (test->pid, NULL, 0), ==, test->pid);
}
g_spawn_close_pid (test->pid);
}
egg_tests_remove_scratch_directory (test->directory);
g_free (test->directory);
g_test_dbus_down (test->dbus);
g_object_unref (test->dbus);
}
static gboolean
check_if_login_keyring_locked (Test *test)
{
GVariant *retval;
GError *error = NULL;
GVariant *prop;
gboolean ret;
retval = g_dbus_connection_call_sync (test->connection,
"org.gnome.keyring",
"/org/freedesktop/secrets/collection/login",
"org.freedesktop.DBus.Properties",
"Get",
g_variant_new ("(ss)",
"org.freedesktop.Secret.Collection", "Locked"),
G_VARIANT_TYPE ("(v)"),
G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
NULL, &error);
g_assert_no_error (error);
g_variant_get (retval, "(@v)", &prop);
ret = g_variant_get_boolean (g_variant_get_variant (prop));
g_variant_unref (retval);
return ret;
}
static gboolean
check_if_login_item_1_exists (Test *test)
{
GVariant *retval;
GError *error = NULL;
gchar *remote;
retval = g_dbus_connection_call_sync (test->connection,
"org.gnome.keyring",
"/org/freedesktop/secrets/collection/login/1",
"org.freedesktop.DBus.Properties",
"Get",
g_variant_new ("(ss)",
"org.freedesktop.Secret.Item", "Locked"),
G_VARIANT_TYPE ("(v)"),
G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
NULL, &error);
if (error) {
remote = g_dbus_error_get_remote_error (error);
if (!remote || !g_str_equal (remote, "org.freedesktop.Secret.Error.NoSuchObject"))
g_assert_no_error (error);
g_error_free (error);
return FALSE;
}
g_variant_unref (retval);
return TRUE;
}
static void
test_starts_creates (Test *test,
gconstpointer user_data)
{
const char *pam_conf = user_data;
gboolean start_in_session;
const gchar *control;
gchar *login_keyring;
if (test->skipping)
return;
start_in_session = (strstr (pam_conf, "session") != NULL);
login_keyring = g_build_filename (test->directory, "login.keyring", NULL);
g_assert (!g_file_test (login_keyring, G_FILE_TEST_EXISTS));
test->password = "booo";
g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
if (start_in_session)
g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
g_assert (pam_getenv (test->ph, "GNOME_KEYRING_CONTROL") != NULL);
control = pam_getenv (test->ph, "GNOME_KEYRING_CONTROL");
/* Initialize the daemon for real */
g_assert (gkd_control_initialize (control, "secrets", PASS_ENVIRON));
/* The keyring was created */
g_assert (g_file_test (login_keyring, G_FILE_TEST_IS_REGULAR));
g_free (login_keyring);
g_assert (check_if_login_keyring_locked (test) == FALSE);
g_assert (check_if_login_item_1_exists (test) == FALSE);
if (!start_in_session)
g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
g_assert (gkd_control_quit (control, 0));
}
static void
test_starts_exists (Test *test,
gconstpointer user_data)
{
const gchar *pam_conf = user_data;
const gchar *control;
gboolean start_in_session;
if (test->skipping)
return;
start_in_session = (strstr (pam_conf, "session") != NULL);
egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
test->password = "booo";
g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
if (start_in_session)
g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
g_assert (pam_getenv (test->ph, "GNOME_KEYRING_CONTROL") != NULL);
control = pam_getenv (test->ph, "GNOME_KEYRING_CONTROL");
/* Initialize the daemon for real */
g_assert (gkd_control_initialize (control, "secrets", PASS_ENVIRON));
/* Lookup the item */
g_assert (check_if_login_keyring_locked (test) == FALSE);
g_assert (check_if_login_item_1_exists (test) == TRUE);
if (!start_in_session)
g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
g_assert (gkd_control_quit (control, 0));
}
static void
test_auth_nostart (Test *test,
gconstpointer user_data)
{
gchar *login_keyring;
if (test->skipping)
return;
test->password = "booo";
g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
g_assert (pam_getenv (test->ph, "GNOME_KEYRING_CONTROL") == NULL);
login_keyring = g_build_filename (test->directory, "login.keyring", NULL);
g_assert (!g_file_test (login_keyring, G_FILE_TEST_EXISTS));
g_free (login_keyring);
}
static void
test_auth_running_unlocks (Test *test,
gconstpointer user_data)
{
const gchar *control;
gchar **env;
GPid pid;
const gchar *argv[] = {
BUILDDIR "/gnome-keyring-daemon", "--foreground", NULL,
};
if (test->skipping)
return;
egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
env = gkd_test_launch_daemon (test->directory, argv, &pid, NULL);
g_assert_cmpstr (g_environ_getenv (env, "GNOME_KEYRING_CONTROL"), !=, NULL);
control = g_environ_getenv (env, "GNOME_KEYRING_CONTROL");
g_setenv ("GNOME_KEYRING_CONTROL", control, TRUE);
g_assert (check_if_login_keyring_locked (test) == TRUE);
test->password = "booo";
g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
/* Lookup the item */
g_assert (check_if_login_keyring_locked (test) == FALSE);
g_assert (check_if_login_item_1_exists (test) == TRUE);
g_assert (gkd_control_quit (control, 0));
g_assert_cmpint (waitpid (pid, NULL, 0), ==, pid);
g_strfreev (env);
}
static void
test_password_changes_running (Test *test,
gconstpointer user_data)
{
const gchar *control;
gchar **env;
GPid pid;
const gchar *argv[] = {
BUILDDIR "/gnome-keyring-daemon", "--foreground", NULL
};
if (test->skipping)
return;
egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
env = gkd_test_launch_daemon (test->directory, argv, &pid, NULL);
g_assert_cmpstr (g_environ_getenv (env, "GNOME_KEYRING_CONTROL"), !=, NULL);
control = g_environ_getenv (env, "GNOME_KEYRING_CONTROL");
g_setenv ("GNOME_KEYRING_CONTROL", control, TRUE);
test->password = "booo";
test->new_password = "changed";
g_assert_cmpint (pam_chauthtok (test->ph, 0), ==, PAM_SUCCESS);
/* Quit the daemon */
g_assert (gkd_control_quit (control, 0));
g_assert_cmpint (waitpid (pid, NULL, 0), ==, pid);
g_strfreev (env);
/* Start it again */
env = gkd_test_launch_daemon (test->directory, argv, &pid, NULL);
control = g_environ_getenv (env, "GNOME_KEYRING_CONTROL");
g_setenv ("GNOME_KEYRING_CONTROL", control, TRUE);
g_assert (gkd_control_unlock (control, "changed"));
g_assert (gkd_control_quit (control, 0));
g_assert_cmpint (waitpid (pid, NULL, 0), ==, pid);
g_strfreev (env);
}
static void
test_password_changes_starts (Test *test,
gconstpointer user_data)
{
const gchar *control;
gchar **env;
GPid pid;
const gchar *argv[] = {
BUILDDIR "/gnome-keyring-daemon", "--foreground", NULL,
};
if (test->skipping)
return;
egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
test->password = "booo";
test->new_password = "changed";
g_assert_cmpint (pam_chauthtok (test->ph, 0), ==, PAM_SUCCESS);
/* Start it again */
env = gkd_test_launch_daemon (test->directory, argv, &pid,
"GNOME_KEYRING_TEST_SERVICE", "another.Bus.Name",
NULL);
control = g_environ_getenv (env, "GNOME_KEYRING_CONTROL");
g_setenv ("GNOME_KEYRING_CONTROL", control, TRUE);
g_assert (gkd_control_unlock (control, "changed"));
g_assert (gkd_control_quit (control, 0));
g_assert_cmpint (waitpid (pid, NULL, 0), ==, pid);
g_strfreev (env);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add ("/pam/auth-no-start", Test,
"gnome-keyring-test-no-start",
setup, test_auth_nostart, teardown);
g_test_add ("/pam/auth-starts-creates-keyring", Test,
"gnome-keyring-test-auth-start",
setup, test_starts_creates, teardown);
g_test_add ("/pam/session-starts-creates-keyring", Test,
"gnome-keyring-test-session-start",
setup, test_starts_creates, teardown);
g_test_add ("/pam/auth-starts-unlocks-existing", Test,
"gnome-keyring-test-auth-start",
setup, test_starts_exists, teardown);
g_test_add ("/pam/session-starts-unlocks-existing", Test,
"gnome-keyring-test-session-start",
setup, test_starts_exists, teardown);
g_test_add ("/pam/auth-running-unlocks-existing", Test,
"gnome-keyring-test-no-start",
setup, test_auth_running_unlocks, teardown);
g_test_add ("/pam/password-changes-running", Test,
"gnome-keyring-test-no-start",
setup, test_password_changes_running, teardown);
g_test_add ("/pam/password-changes-starts", Test,
"gnome-keyring-test-no-start",