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