/*
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,
.
Author: Stef Walter
*/
#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
#include
#include
#include
#include
#include
#include
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)) {
g_error_free (error);
skip_test (test, "missing test pam config");
return;
}
g_assert_no_error (error);
test->directory = egg_tests_create_scratch_directory (NULL, NULL);
g_setenv ("XDG_RUNTIME_DIR", test->directory, TRUE);
test->dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
g_test_dbus_up (test->dbus);
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;
/* We're testing that we create the directory appropriately */
g_unsetenv ("XDG_RUNTIME_DIR");
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_only_session (Test *test,
gconstpointer user_data)
{
const char *pam_conf = user_data;
const gchar *control;
gchar *login_keyring;
if (test->skipping)
return;
/* This is the PAM config that starts the daemon from session handler */
g_assert (strstr (pam_conf, "session-start") != NULL);
/* We're testing that we create the directory appropriately */
g_unsetenv ("XDG_RUNTIME_DIR");
login_keyring = g_build_filename (test->directory, "login.keyring", NULL);
g_assert (!g_file_test (login_keyring, G_FILE_TEST_EXISTS));
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");
/* These verify that the daemon was started */
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;
/* We're testing that we create the directory appropriately */
g_unsetenv ("XDG_RUNTIME_DIR");
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)
{
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 (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);
control = g_strdup_printf ("%s/keyring", test->directory);
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)
{
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");
control = g_strdup_printf ("%s/keyring", test->directory);
env = gkd_test_launch_daemon (test->directory, argv, &pid, NULL);
g_strfreev (env);
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);
/* Start it again */
env = gkd_test_launch_daemon (test->directory, argv, &pid, NULL);
g_strfreev (env);
g_assert (gkd_control_unlock (control, "changed"));
g_assert (gkd_control_quit (control, 0));
g_assert_cmpint (waitpid (pid, NULL, 0), ==, pid);
g_free (control);
}
static void
test_password_changes_starts (Test *test,
gconstpointer user_data)
{
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");
control = g_strdup_printf ("%s/keyring", test->directory);
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);
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);
g_free (control);
}
static void
test_password_change_start_in_session (Test *test,
gconstpointer user_data)
{
const char *pam_conf = user_data;
gchar *control;
if (test->skipping)
return;
/* This is the PAM config that starts the daemon from session handler */
g_assert (strstr (pam_conf, "session-start") != NULL);
egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
control = g_strdup_printf ("%s/keyring", test->directory);
/* First we authenticate, but don't start the keyring here */
test->password = "booo";
g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
test->password = "booo";
test->new_password = "changed";
g_assert_cmpint (pam_chauthtok (test->ph, 0), ==, PAM_SUCCESS);
/* No daemon should be running, chauthtok started/stopped it */
g_assert (gkd_control_quit (control, GKD_CONTROL_QUIET_IF_NO_PEER) == FALSE);
/* Now session should be able to start and unlock the keyring */
g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
/* Initialize the daemon */
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);
g_assert (gkd_control_quit (control, 0));
g_free (control);
}
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/session-starts-without-auth", Test,
"gnome-keyring-test-session-start",
setup, test_starts_only_session, 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",
setup, test_password_changes_starts, teardown);
g_test_add ("/pam/password-change-start-in-session", Test,
"gnome-keyring-test-session-start",
setup, test_password_change_start_in_session, teardown);
return g_test_run ();
}