1 /*
2    Copyright (C) 2014 Red Hat Inc
3 
4    The Gnome Keyring Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8 
9    The Gnome Keyring Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public
15    License along with the Gnome Library; see the file COPYING.LIB.  If not,
16    <http://www.gnu.org/licenses/>.
17 
18    Author: Stef Walter <stefw@gnome.org>
19 */
20 
21 #include "config.h"
22 
23 #include "daemon/control/gkd-control.h"
24 #include "daemon/gkd-test.h"
25 
26 #include "egg/egg-testing.h"
27 #include "egg/egg-secure-memory.h"
28 
29 #include <security/pam_appl.h>
30 
31 #include <glib.h>
32 #include <glib/gstdio.h>
33 #include <gio/gio.h>
34 
35 #include <sys/wait.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 
39 EGG_SECURE_DEFINE_GLIB_GLOBALS ();
40 
41 typedef struct {
42 	GTestDBus *dbus;
43 	GDBusConnection *connection;
44 	gchar *directory;
45 	GPid pid;
46 	gboolean skipping;
47 	pam_handle_t *ph;
48 	struct pam_conv conv;
49 	const gchar *password;
50 	const gchar *new_password;
51 } Test;
52 
53 const gchar *PASS_ENVIRON[] = {
54 	"DBUS_SESSION_ADDRESS",
55 	"XDG_RUNTIME_DIR",
56 	"XDG_DATA_HOME",
57 	NULL
58 };
59 
60 static void
skip_test(Test * test,const gchar * reason)61 skip_test (Test *test,
62            const gchar *reason)
63 {
64 	test->skipping = TRUE;
65 #if GLIB_CHECK_VERSION(2, 40, 0)
66 	g_test_skip (reason);
67 #else
68 	if (g_test_verbose ())
69 		g_print ("GTest: skipping: %s\n", reason);
70 	else
71 		g_print ("SKIP: %s ", reason);
72 #endif
73 }
74 
75 static int
conv_func(int n,const struct pam_message ** msg,struct pam_response ** resp,void * arg)76 conv_func (int n,
77            const struct pam_message **msg,
78            struct pam_response **resp,
79            void *arg)
80 {
81 	struct pam_response *aresp;
82 	Test *test = arg;
83 	int i;
84 
85 	g_assert (n > 0 && n < PAM_MAX_NUM_MSG);
86 	aresp = g_new0(struct pam_response, n);
87 
88 	for (i = 0; i < n; ++i) {
89 		aresp[i].resp_retcode = 0;
90 		aresp[i].resp = NULL;
91 		switch (msg[i]->msg_style) {
92 		case PAM_PROMPT_ECHO_OFF:
93 			if (test->password) {
94 				aresp[i].resp = strdup (test->password);
95 				test->password = NULL;
96 			} else if (test->new_password) {
97 				aresp[i].resp = strdup (test->new_password);
98 				test->new_password = NULL;
99 			}
100 			g_assert (aresp[i].resp != NULL);
101 			break;
102 		case PAM_PROMPT_ECHO_ON:
103 			aresp[i].resp = strdup (test->password);
104 			g_assert (aresp[i].resp != NULL);
105 			break;
106 		case PAM_ERROR_MSG:
107 			fputs(msg[i]->msg, stderr);
108 			if (strlen(msg[i]->msg) > 0 &&
109 			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
110 				fputc('\n', stderr);
111 			break;
112 		case PAM_TEXT_INFO:
113 			fprintf(stdout, "# %s", msg[i]->msg);
114 			if (strlen(msg[i]->msg) > 0 &&
115 			    msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
116 				fputc('\n', stdout);
117 			break;
118 		default:
119 			return PAM_CONV_ERR;
120 		}
121 	}
122 	*resp = aresp;
123 	return PAM_SUCCESS;
124 }
125 
126 static void
setup(Test * test,gconstpointer user_data)127 setup (Test *test,
128        gconstpointer user_data)
129 {
130 	const gchar *pam_conf = user_data;
131 	GError *error = NULL;
132 	gchar *contents;
133 	gboolean found;
134 	gchar *filename;
135 	gchar *env;
136 	int ret;
137 
138 	/* First check if we have the right pam config */
139 	filename = g_build_filename (SYSCONFDIR, "pam.d", pam_conf, NULL);
140 	g_file_get_contents (filename, &contents, NULL, &error);
141 	g_free (filename);
142 
143 	if (error == NULL) {
144 		found = (strstr (contents, BUILDDIR) &&
145 		         strstr (contents, "pam_gnome_keyring.so"));
146 		g_free (contents);
147 		if (!found) {
148 			skip_test (test, "test pam config contents invalid");
149 			return;
150 		}
151 	} else if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
152 		g_error_free (error);
153 		skip_test (test, "missing test pam config");
154 		return;
155 	}
156 
157 	g_assert_no_error (error);
158 
159 	test->directory = egg_tests_create_scratch_directory (NULL, NULL);
160 
161 	g_setenv ("XDG_RUNTIME_DIR", test->directory, TRUE);
162 
163 	test->dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
164 	g_test_dbus_up (test->dbus);
165 
166 	test->conv.conv = conv_func;
167 	test->conv.appdata_ptr = test;
168 	ret = pam_start (pam_conf, g_get_user_name (), &test->conv, &test->ph);
169 	g_assert_cmpint (ret, ==, PAM_SUCCESS);
170 
171 	g_unsetenv ("GNOME_KEYRING_CONTROL");
172 
173 	g_assert_cmpint (pam_putenv (test->ph, "GSETTINGS_SCHEMA_DIR=" BUILDDIR "/schema"), ==, PAM_SUCCESS);
174 	g_assert_cmpint (pam_putenv (test->ph, "G_DEBUG=fatal-warnings,fatal-criticals"), ==, PAM_SUCCESS);
175 
176 	env = g_strdup_printf ("GNOME_KEYRING_TEST_PATH=%s", test->directory);
177 	g_assert_cmpint (pam_putenv (test->ph, env), ==, PAM_SUCCESS);
178 	g_free (env);
179 
180 	env = g_strdup_printf ("DBUS_SESSION_BUS_ADDRESS=%s", g_test_dbus_get_bus_address (test->dbus));
181 	g_assert_cmpint (pam_putenv (test->ph, env), ==, PAM_SUCCESS);
182 	g_free (env);
183 
184 	test->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
185 	g_assert_no_error (error);
186 }
187 
188 static void
teardown(Test * test,gconstpointer unused)189 teardown (Test *test,
190           gconstpointer unused)
191 {
192 	if (test->skipping)
193 		return;
194 
195 	g_object_unref (test->connection);
196 
197 	pam_end (test->ph, PAM_SUCCESS);
198 
199 	if (test->pid) {
200 		if (waitpid (test->pid, NULL, WNOHANG) != test->pid) {
201 			kill (test->pid, SIGTERM);
202 			g_assert_cmpint (waitpid (test->pid, NULL, 0), ==, test->pid);
203 		}
204 		g_spawn_close_pid (test->pid);
205 	}
206 
207 	egg_tests_remove_scratch_directory (test->directory);
208 	g_free (test->directory);
209 
210 	g_test_dbus_down (test->dbus);
211 	g_object_unref (test->dbus);
212 }
213 
214 static gboolean
check_if_login_keyring_locked(Test * test)215 check_if_login_keyring_locked (Test *test)
216 {
217 	GVariant *retval;
218 	GError *error = NULL;
219 	GVariant *prop;
220 	gboolean ret;
221 
222 	retval = g_dbus_connection_call_sync (test->connection,
223 	                                      "org.gnome.keyring",
224 	                                      "/org/freedesktop/secrets/collection/login",
225 	                                      "org.freedesktop.DBus.Properties",
226 	                                      "Get",
227 	                                      g_variant_new ("(ss)",
228 	                                                     "org.freedesktop.Secret.Collection", "Locked"),
229 	                                      G_VARIANT_TYPE ("(v)"),
230 	                                      G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
231 	                                      NULL, &error);
232 	g_assert_no_error (error);
233 	g_variant_get (retval, "(@v)", &prop);
234 	ret = g_variant_get_boolean (g_variant_get_variant (prop));
235 	g_variant_unref (retval);
236 
237 	return ret;
238 }
239 
240 static gboolean
check_if_login_item_1_exists(Test * test)241 check_if_login_item_1_exists (Test *test)
242 {
243 	GVariant *retval;
244 	GError *error = NULL;
245 	gchar *remote;
246 
247 	retval = g_dbus_connection_call_sync (test->connection,
248 	                                      "org.gnome.keyring",
249 	                                      "/org/freedesktop/secrets/collection/login/1",
250 	                                      "org.freedesktop.DBus.Properties",
251 	                                      "Get",
252 	                                      g_variant_new ("(ss)",
253 	                                                     "org.freedesktop.Secret.Item", "Locked"),
254 	                                      G_VARIANT_TYPE ("(v)"),
255 	                                      G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
256 	                                      NULL, &error);
257 
258 	if (error) {
259 		remote = g_dbus_error_get_remote_error (error);
260 		if (!remote || !g_str_equal (remote, "org.freedesktop.Secret.Error.NoSuchObject"))
261 			g_assert_no_error (error);
262 		g_error_free (error);
263 		return FALSE;
264 	}
265 
266 	g_variant_unref (retval);
267 	return TRUE;
268 }
269 
270 static void
test_starts_creates(Test * test,gconstpointer user_data)271 test_starts_creates (Test *test,
272                      gconstpointer user_data)
273 {
274 	const char *pam_conf = user_data;
275 	gboolean start_in_session;
276 	const gchar *control;
277 	gchar *login_keyring;
278 
279 	if (test->skipping)
280 		return;
281 
282 	/* We're testing that we create the directory appropriately */
283 	g_unsetenv ("XDG_RUNTIME_DIR");
284 
285 	start_in_session = (strstr (pam_conf, "session") != NULL);
286 
287 	login_keyring = g_build_filename (test->directory, "login.keyring", NULL);
288 	g_assert (!g_file_test (login_keyring, G_FILE_TEST_EXISTS));
289 
290 	test->password = "booo";
291 	g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
292 
293 	if (start_in_session)
294 		g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
295 
296 	g_assert (pam_getenv (test->ph, "GNOME_KEYRING_CONTROL") != NULL);
297 	control = pam_getenv (test->ph, "GNOME_KEYRING_CONTROL");
298 
299 	/* Initialize the daemon for real */
300 	g_assert (gkd_control_initialize (control, "secrets", PASS_ENVIRON));
301 
302 	/* The keyring was created */
303 	g_assert (g_file_test (login_keyring, G_FILE_TEST_IS_REGULAR));
304 	g_free (login_keyring);
305 
306 	g_assert (check_if_login_keyring_locked (test) == FALSE);
307 	g_assert (check_if_login_item_1_exists (test) == FALSE);
308 
309 	if (!start_in_session)
310 		g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
311 
312 	g_assert (gkd_control_quit (control, 0));
313 }
314 
315 static void
test_starts_only_session(Test * test,gconstpointer user_data)316 test_starts_only_session (Test *test,
317 		          gconstpointer user_data)
318 {
319 	const char *pam_conf = user_data;
320 	const gchar *control;
321 	gchar *login_keyring;
322 
323 	if (test->skipping)
324 		return;
325 
326 	/* This is the PAM config that starts the daemon from session handler */
327 	g_assert (strstr (pam_conf, "session-start") != NULL);
328 
329 	/* We're testing that we create the directory appropriately */
330 	g_unsetenv ("XDG_RUNTIME_DIR");
331 
332 	login_keyring = g_build_filename (test->directory, "login.keyring", NULL);
333 	g_assert (!g_file_test (login_keyring, G_FILE_TEST_EXISTS));
334 
335 	g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
336 
337 	g_assert (pam_getenv (test->ph, "GNOME_KEYRING_CONTROL") != NULL);
338 	control = pam_getenv (test->ph, "GNOME_KEYRING_CONTROL");
339 
340 	/* These verify that the daemon was started */
341 	g_assert (gkd_control_quit (control, 0));
342 }
343 
344 static void
test_starts_exists(Test * test,gconstpointer user_data)345 test_starts_exists (Test *test,
346                     gconstpointer user_data)
347 {
348 	const gchar *pam_conf = user_data;
349 	const gchar *control;
350 	gboolean start_in_session;
351 
352 	if (test->skipping)
353 		return;
354 
355 	/* We're testing that we create the directory appropriately */
356 	g_unsetenv ("XDG_RUNTIME_DIR");
357 
358 	start_in_session = (strstr (pam_conf, "session") != NULL);
359 
360 	egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
361 
362 	test->password = "booo";
363 	g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
364 
365 	if (start_in_session)
366 		g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
367 
368 	g_assert (pam_getenv (test->ph, "GNOME_KEYRING_CONTROL") != NULL);
369 	control = pam_getenv (test->ph, "GNOME_KEYRING_CONTROL");
370 
371 	/* Initialize the daemon for real */
372 	g_assert (gkd_control_initialize (control, "secrets", PASS_ENVIRON));
373 
374 	/* Lookup the item */
375 	g_assert (check_if_login_keyring_locked (test) == FALSE);
376 	g_assert (check_if_login_item_1_exists (test) == TRUE);
377 
378 	if (!start_in_session)
379 		g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
380 
381 	g_assert (gkd_control_quit (control, 0));
382 }
383 
384 static void
test_auth_nostart(Test * test,gconstpointer user_data)385 test_auth_nostart (Test *test,
386                    gconstpointer user_data)
387 {
388 	gchar *login_keyring;
389 
390 	if (test->skipping)
391 		return;
392 
393 	test->password = "booo";
394 	g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
395 
396 	g_assert (pam_getenv (test->ph, "GNOME_KEYRING_CONTROL") == NULL);
397 
398 	login_keyring = g_build_filename (test->directory, "login.keyring", NULL);
399 	g_assert (!g_file_test (login_keyring, G_FILE_TEST_EXISTS));
400 	g_free (login_keyring);
401 }
402 
403 static void
test_auth_running_unlocks(Test * test,gconstpointer user_data)404 test_auth_running_unlocks (Test *test,
405                            gconstpointer user_data)
406 {
407 	gchar *control;
408 	gchar **env;
409 	GPid pid;
410 
411 	const gchar *argv[] = {
412 		BUILDDIR "/gnome-keyring-daemon", "--foreground", NULL,
413 	};
414 
415 	if (test->skipping)
416 		return;
417 
418 	egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
419 
420 	env = gkd_test_launch_daemon (test->directory, argv, &pid, NULL);
421 
422 	g_assert (check_if_login_keyring_locked (test) == TRUE);
423 
424 	test->password = "booo";
425 	g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
426 
427 	/* Lookup the item */
428 	g_assert (check_if_login_keyring_locked (test) == FALSE);
429 	g_assert (check_if_login_item_1_exists (test) == TRUE);
430 
431 	control = g_strdup_printf ("%s/keyring", test->directory);
432 	g_assert (gkd_control_quit (control, 0));
433 	g_assert_cmpint (waitpid (pid, NULL, 0), ==, pid);
434 
435 	g_strfreev (env);
436 }
437 
438 static void
test_password_changes_running(Test * test,gconstpointer user_data)439 test_password_changes_running (Test *test,
440                                gconstpointer user_data)
441 {
442 	gchar *control;
443 	gchar **env;
444 	GPid pid;
445 
446 	const gchar *argv[] = {
447 		BUILDDIR "/gnome-keyring-daemon", "--foreground", NULL
448 	};
449 
450 	if (test->skipping)
451 		return;
452 
453 	egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
454 	control = g_strdup_printf ("%s/keyring", test->directory);
455 
456 	env = gkd_test_launch_daemon (test->directory, argv, &pid, NULL);
457 	g_strfreev (env);
458 
459 	test->password = "booo";
460 	test->new_password = "changed";
461 	g_assert_cmpint (pam_chauthtok (test->ph, 0), ==, PAM_SUCCESS);
462 
463 	/* Quit the daemon */
464 	g_assert (gkd_control_quit (control, 0));
465 	g_assert_cmpint (waitpid (pid, NULL, 0), ==, pid);
466 
467 	/* Start it again */
468 	env = gkd_test_launch_daemon (test->directory, argv, &pid, NULL);
469 	g_strfreev (env);
470 
471 	g_assert (gkd_control_unlock (control, "changed"));
472 	g_assert (gkd_control_quit (control, 0));
473 	g_assert_cmpint (waitpid (pid, NULL, 0), ==, pid);
474 
475 	g_free (control);
476 }
477 
478 static void
test_password_changes_starts(Test * test,gconstpointer user_data)479 test_password_changes_starts (Test *test,
480                               gconstpointer user_data)
481 {
482 	gchar *control;
483 	gchar **env;
484 	GPid pid;
485 
486 	const gchar *argv[] = {
487 		BUILDDIR "/gnome-keyring-daemon", "--foreground", NULL,
488 	};
489 
490 	if (test->skipping)
491 		return;
492 
493 	egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
494 	control = g_strdup_printf ("%s/keyring", test->directory);
495 
496 	test->password = "booo";
497 	test->new_password = "changed";
498 	g_assert_cmpint (pam_chauthtok (test->ph, 0), ==, PAM_SUCCESS);
499 
500 	/* Start it again */
501 	env = gkd_test_launch_daemon (test->directory, argv, &pid,
502 	                              "GNOME_KEYRING_TEST_SERVICE", "another.Bus.Name",
503 	                              NULL);
504 
505 	g_assert (gkd_control_unlock (control, "changed"));
506 	g_assert (gkd_control_quit (control, 0));
507 	g_assert_cmpint (waitpid (pid, NULL, 0), ==, pid);
508 
509 	g_strfreev (env);
510 	g_free (control);
511 }
512 
513 static void
test_password_change_start_in_session(Test * test,gconstpointer user_data)514 test_password_change_start_in_session (Test *test,
515                                        gconstpointer user_data)
516 {
517 	const char *pam_conf = user_data;
518 	gchar *control;
519 
520 	if (test->skipping)
521 		return;
522 
523 	/* This is the PAM config that starts the daemon from session handler */
524 	g_assert (strstr (pam_conf, "session-start") != NULL);
525 
526 	egg_tests_copy_scratch_file (test->directory, SRCDIR "/pam/fixtures/login.keyring");
527 	control = g_strdup_printf ("%s/keyring", test->directory);
528 
529 	/* First we authenticate, but don't start the keyring here */
530 	test->password = "booo";
531 	g_assert_cmpint (pam_authenticate (test->ph, 0), ==, PAM_SUCCESS);
532 
533 	test->password = "booo";
534 	test->new_password = "changed";
535 	g_assert_cmpint (pam_chauthtok (test->ph, 0), ==, PAM_SUCCESS);
536 
537 	/* No daemon should be running, chauthtok started/stopped it */
538 	g_assert (gkd_control_quit (control, GKD_CONTROL_QUIET_IF_NO_PEER) == FALSE);
539 
540 	/* Now session should be able to start and unlock the keyring */
541 	g_assert_cmpint (pam_open_session (test->ph, 0), ==, PAM_SUCCESS);
542 
543 	/* Initialize the daemon */
544 	g_assert (gkd_control_initialize (control, "secrets", PASS_ENVIRON));
545 
546 	/* Lookup the item */
547 	g_assert (check_if_login_keyring_locked (test) == FALSE);
548 	g_assert (check_if_login_item_1_exists (test) == TRUE);
549 
550 	g_assert (gkd_control_quit (control, 0));
551 	g_free (control);
552 }
553 
554 int
main(int argc,char ** argv)555 main (int argc, char **argv)
556 {
557 	g_test_init (&argc, &argv, NULL);
558 
559 	g_test_add ("/pam/auth-no-start", Test,
560 	            "gnome-keyring-test-no-start",
561 	            setup, test_auth_nostart, teardown);
562 
563 	g_test_add ("/pam/auth-starts-creates-keyring", Test,
564 	            "gnome-keyring-test-auth-start",
565 	            setup, test_starts_creates, teardown);
566 	g_test_add ("/pam/session-starts-creates-keyring", Test,
567 	            "gnome-keyring-test-session-start",
568 	            setup, test_starts_creates, teardown);
569 
570 	g_test_add ("/pam/auth-starts-unlocks-existing", Test,
571 	            "gnome-keyring-test-auth-start",
572 	            setup, test_starts_exists, teardown);
573 	g_test_add ("/pam/session-starts-unlocks-existing", Test,
574 	            "gnome-keyring-test-session-start",
575 	            setup, test_starts_exists, teardown);
576 
577 	g_test_add ("/pam/session-starts-without-auth", Test,
578 	            "gnome-keyring-test-session-start",
579 	            setup, test_starts_only_session, teardown);
580 
581 	g_test_add ("/pam/auth-running-unlocks-existing", Test,
582 	            "gnome-keyring-test-no-start",
583 	            setup, test_auth_running_unlocks, teardown);
584 
585 	g_test_add ("/pam/password-changes-running", Test,
586 	            "gnome-keyring-test-no-start",
587 	            setup, test_password_changes_running, teardown);
588 	g_test_add ("/pam/password-changes-starts", Test,
589 	            "gnome-keyring-test-no-start",
590 	            setup, test_password_changes_starts, teardown);
591 
592 	g_test_add ("/pam/password-change-start-in-session", Test,
593 	            "gnome-keyring-test-session-start",
594 	            setup, test_password_change_start_in_session, teardown);
595 
596 	return g_test_run ();
597 }
598