diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a247c62..ded8c44 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -78,6 +78,20 @@ fedora-static-analyzers/test:
paths:
- _build/meson-logs/testlog.txt
+fedora:PAM:
+ stage: build
+ before_script:
+ - dbus-uuidgen --ensure
+ script:
+ - meson _build -Dwerror=true -Dc_args=-Wno-error=deprecated-declarations -Dgtk_doc=false -Dpam=true
+ - meson compile -C _build
+ - eval `dbus-launch --sh-syntax`
+ - meson test -C _build --print-errorlogs
+ artifacts:
+ when: on_failure
+ paths:
+ - _build/meson-logs/testlog.txt
+
fedora:coverage:
stage: build
before_script:
diff --git a/meson.build b/meson.build
index 8778532..5fdb038 100644
--- a/meson.build
+++ b/meson.build
@@ -97,6 +97,10 @@ endif
conf.set('WITH_DEBUG', get_option('debugging'))
conf.set('_DEBUG', get_option('debugging'))
conf.set('HAVE_MLOCK', meson.get_compiler('c').has_function('mlock'))
+if get_option('pam')
+ conf.set_quoted('GNOME_KEYRING_DAEMON', get_option('prefix') /
+ get_option('bindir') / 'gnome-keyring-daemon')
+endif
configure_file(output: 'config.h', configuration: conf)
# Test environment
@@ -109,3 +113,6 @@ subdir('egg')
subdir('libsecret')
subdir('tool')
subdir('docs')
+if get_option('pam')
+ subdir('pam')
+endif
diff --git a/meson_options.txt b/meson_options.txt
index 7c6f5ee..936eff8 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -7,3 +7,4 @@ option('introspection', type: 'boolean', value: true, description: 'Create GIR f
option('bashcompdir', type: 'string', value: '', description: 'Override default location for bash completion files')
option('bash_completion', type: 'feature', value: 'auto', description: 'Install bash completion files')
option('tpm2', type: 'boolean', value: false, description: 'With TPM2 Software Stack')
+option('pam', type: 'boolean', value: false, description: 'Build PAM module')
diff --git a/pam/gkd-control-codes.h b/pam/gkd-control-codes.h
new file mode 100644
index 0000000..e4ff7ff
--- /dev/null
+++ b/pam/gkd-control-codes.h
@@ -0,0 +1,38 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2009 Stefan Walter
+ *
+ * This program 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, see
+ * .
+ */
+
+#ifndef __GKD_CONTROL_CODES_H__
+#define __GKD_CONTROL_CODES_H__
+
+enum {
+ GKD_CONTROL_OP_INITIALIZE,
+ GKD_CONTROL_OP_UNLOCK,
+ GKD_CONTROL_OP_CHANGE,
+ GKD_CONTROL_OP_QUIT
+};
+
+enum {
+ GKD_CONTROL_RESULT_OK,
+ GKD_CONTROL_RESULT_DENIED,
+ GKD_CONTROL_RESULT_FAILED,
+ GKD_CONTROL_RESULT_NO_DAEMON
+};
+
+#endif /* __GKD_CONTROL_CODES_H__ */
diff --git a/pam/gkr-pam-client.c b/pam/gkr-pam-client.c
new file mode 100644
index 0000000..2b39b12
--- /dev/null
+++ b/pam/gkr-pam-client.c
@@ -0,0 +1,458 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gkr-pam-client.h - Simple code for communicating with daemon
+
+ Copyright (C) 2007 Stef Walter
+
+ 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 "gkr-pam.h"
+#include "egg/egg-unix-credentials.h"
+#include "egg/egg-buffer.h"
+#include "gkd-control-codes.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if defined(HAVE_GETPEERUCRED)
+#include
+#endif
+
+#if defined(LOCAL_PEERCRED)
+#include
+#include
+#endif
+
+static int
+check_peer_same_uid (struct passwd *pwd,
+ int sock)
+{
+ uid_t uid = -1;
+
+ /*
+ * Certain OS require a message to be sent over the unix socket for the
+ * otherside to get the process credentials. Most uncool.
+ *
+ * The normal gnome-keyring protocol accomodates this and the client
+ * sends a message/byte before sending anything else. This only works
+ * for the daemon verifying the client.
+ *
+ * This code here is used by a client to verify the daemon is running
+ * as the right user. Since we cannot modify the protocol, this only
+ * works on OSs that can do this credentials lookup transparently.
+ */
+
+/* Linux */
+#if defined(SO_PEERCRED)
+ struct ucred cr;
+ socklen_t cr_len = sizeof (cr);
+
+ if (getsockopt (sock, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len) == 0 &&
+ cr_len == sizeof (cr)) {
+ uid = cr.uid;
+ } else {
+ syslog (GKR_LOG_ERR, "could not get gnome-keyring-daemon socket credentials, "
+ "(returned len %d/%d)\n", cr_len, (int) sizeof (cr));
+ return -1;
+ }
+
+
+/* The BSDs */
+#elif defined(LOCAL_PEERCRED)
+ uid_t gid;
+ struct xucred xuc;
+ socklen_t xuc_len = sizeof (xuc);
+
+ if (getsockopt (sock, SOL_SOCKET, LOCAL_PEERCRED, &xuc, &xuc_len) == 0 &&
+ xuc_len == sizeof (xuc)) {
+ uid = xuc.cr_uid;
+ } else {
+ syslog (GKR_LOG_ERR, "could not get gnome-keyring-daemon socket credentials, "
+ "(returned len %d/%d)\n", xuc_len, (int)sizeof (xuc));
+ return -1;
+ }
+
+
+/* NOTE: Add more here */
+#else
+ syslog (GKR_LOG_WARN, "Cannot verify that the process to which we are passing the login"
+ " password is genuinely running as the same user login: not supported on this OS.");
+ uid = pwd->pw_uid;
+
+
+#endif
+
+ if (uid != pwd->pw_uid) {
+ syslog (GKR_LOG_ERR, "The gnome keyring socket is not running with the same "
+ "credentials as the user login. Disconnecting.");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+write_credentials_byte (int sock)
+{
+ for (;;) {
+ if (egg_unix_credentials_write (sock) < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ syslog (GKR_LOG_ERR, "couldn't send credentials to daemon: %s",
+ strerror (errno));
+ return -1;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+static int
+lookup_daemon (struct passwd *pwd,
+ const char *control,
+ struct sockaddr_un *addr)
+{
+ struct stat st;
+
+ if (strlen (control) + 1 > sizeof (addr->sun_path)) {
+ syslog (GKR_LOG_ERR, "gkr-pam: address is too long for unix socket path: %s",
+ control);
+ return GKD_CONTROL_RESULT_FAILED;
+ }
+
+ memset (addr, 0, sizeof (*addr));
+ addr->sun_family = AF_UNIX;
+ strcpy (addr->sun_path, control);
+
+ /* A bunch of checks to make sure nothing funny is going on */
+ if (lstat (addr->sun_path, &st) < 0) {
+ if (errno == ENOENT)
+ return GKD_CONTROL_RESULT_NO_DAEMON;
+
+ syslog (GKR_LOG_ERR, "Couldn't access gnome keyring socket: %s: %s",
+ addr->sun_path, strerror (errno));
+ return GKD_CONTROL_RESULT_FAILED;
+ }
+
+ if (st.st_uid != pwd->pw_uid) {
+ syslog (GKR_LOG_ERR, "The gnome keyring socket is not owned with the same "
+ "credentials as the user login: %s", addr->sun_path);
+ return GKD_CONTROL_RESULT_FAILED;
+ }
+
+ if (S_ISLNK(st.st_mode) || !S_ISSOCK(st.st_mode)) {
+ syslog (GKR_LOG_ERR, "The gnome keyring socket is not a valid simple "
+ "non-linked socket");
+ return GKD_CONTROL_RESULT_FAILED;
+ }
+
+ return GKD_CONTROL_RESULT_OK;
+}
+
+static int
+connect_daemon (struct passwd *pwd,
+ struct sockaddr_un *addr,
+ int *out_sock)
+{
+ int sock;
+
+ /* Now we connect */
+ sock = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ syslog (GKR_LOG_ERR, "couldn't create control socket: %s", strerror (errno));
+ return GKD_CONTROL_RESULT_FAILED;
+ }
+
+ /* close on exec */
+ fcntl (sock, F_SETFD, 1);
+
+ if (connect (sock, (struct sockaddr *)addr, sizeof (*addr)) < 0) {
+ if (errno == ECONNREFUSED) {
+ close (sock);
+ return GKD_CONTROL_RESULT_NO_DAEMON;
+ }
+ syslog (GKR_LOG_ERR, "couldn't connect to gnome-keyring-daemon socket at: %s: %s",
+ addr->sun_path, strerror (errno));
+ close (sock);
+ return GKD_CONTROL_RESULT_FAILED;
+ }
+
+ /* Verify the server is running as the right user */
+
+ if (check_peer_same_uid (pwd, sock) <= 0) {
+ close (sock);
+ return GKD_CONTROL_RESULT_FAILED;
+ }
+
+ /* This lets the server verify us */
+
+ if (write_credentials_byte (sock) < 0) {
+ close (sock);
+ return GKD_CONTROL_RESULT_FAILED;
+ }
+
+ *out_sock = sock;
+ return GKD_CONTROL_RESULT_OK;
+}
+
+static void
+write_part (int fd, const unsigned char *data, int len, int *res)
+{
+ assert (res);
+
+ /* Already an error present */
+ if (*res != GKD_CONTROL_RESULT_OK)
+ return;
+
+ assert (data);
+
+ while (len > 0) {
+ int r = write (fd, data, len);
+ if (r < 0) {
+ if (errno == EAGAIN)
+ continue;
+ syslog (GKR_LOG_ERR, "couldn't send data to gnome-keyring-daemon: %s",
+ strerror (errno));
+ *res = GKD_CONTROL_RESULT_FAILED;
+ return;
+ }
+ data += r;
+ len -= r;
+ }
+}
+
+static int
+read_part (int fd,
+ unsigned char *data,
+ int len,
+ int disconnect_ok)
+{
+ int r, all;
+
+ all = len;
+ while (len > 0) {
+ r = read (fd, data, len);
+ if (r < 0) {
+ if (errno == EAGAIN)
+ continue;
+ if (errno == ECONNRESET && disconnect_ok)
+ return 0;
+ syslog (GKR_LOG_ERR, "couldn't read data from gnome-keyring-daemon: %s",
+ strerror (errno));
+ return -1;
+ }
+ if (r == 0) {
+ if (disconnect_ok)
+ return 0;
+ syslog (GKR_LOG_ERR, "couldn't read data from gnome-keyring-daemon: %s",
+ "unexpected end of data");
+ return -1;
+ }
+
+ data += r;
+ len -= r;
+ }
+
+ return all;
+}
+
+static int
+keyring_daemon_op (struct passwd *pwd,
+ struct sockaddr_un *addr,
+ int op,
+ int argc,
+ const char *argv[])
+{
+ int ret = GKD_CONTROL_RESULT_OK;
+ unsigned char buf[4];
+ int want_disconnect;
+ int i, sock = -1;
+ uint oplen, l;
+
+ assert (addr);
+
+ /*
+ * We only support operations with zero or more strings
+ * and an empty (only result code) return.
+ */
+
+ assert (op == GKD_CONTROL_OP_CHANGE ||
+ op == GKD_CONTROL_OP_UNLOCK ||
+ op == GKD_CONTROL_OP_QUIT);
+
+ ret = connect_daemon (pwd, addr, &sock);
+ if (ret != GKD_CONTROL_RESULT_OK)
+ goto done;
+
+ /* Calculate the packet length */
+ oplen = 8; /* The packet size, and op code */
+ for (i = 0; i < argc; ++i)
+ oplen += 4 + strlen (argv[i]);
+
+ /* Write out the length, and op */
+ egg_buffer_encode_uint32 (buf, oplen);
+ write_part (sock, buf, 4, &ret);
+ egg_buffer_encode_uint32 (buf, op);
+ write_part (sock, buf, 4, &ret);
+
+ /* And now the arguments */
+ for (i = 0; i < argc; ++i) {
+ if (argv[i] == NULL)
+ l = 0x7FFFFFFF;
+ else
+ l = strlen (argv[i]);
+ egg_buffer_encode_uint32 (buf, l);
+ write_part (sock, buf, 4, &ret);
+ if (argv[i] != NULL)
+ write_part (sock, (unsigned char*)argv[i], l, &ret);
+ }
+
+ if (ret != GKD_CONTROL_RESULT_OK)
+ goto done;
+ /*
+ * If we're asking the daemon to quit, then we expect
+ * disconnects after we send the initial request
+ */
+ want_disconnect = (op == GKD_CONTROL_OP_QUIT);
+
+ /* Read the response length */
+ if (read_part (sock, buf, 4, want_disconnect) != 4) {
+ ret = GKD_CONTROL_RESULT_FAILED;
+ goto done;
+ }
+
+ /* We only support simple responses */
+ l = egg_buffer_decode_uint32 (buf);
+ if (l != 8) {
+ syslog (GKR_LOG_ERR, "invalid length response from gnome-keyring-daemon: %d", l);
+ ret = GKD_CONTROL_RESULT_FAILED;
+ goto done;
+ }
+
+ if (read_part (sock, buf, 4, want_disconnect) != 4) {
+ ret = GKD_CONTROL_RESULT_FAILED;
+ goto done;
+ }
+ ret = egg_buffer_decode_uint32 (buf);
+
+ /*
+ * If we asked the daemon to quit, wait for it to disconnect
+ * by waiting until the socket disconnects from the other end.
+ */
+ if (want_disconnect) {
+ while (read (sock, buf, 4) > 0);
+ }
+
+done:
+ if (sock >= 0)
+ close (sock);
+
+ return ret;
+}
+
+int
+gkr_pam_client_run_operation (struct passwd *pwd, const char *control,
+ int op, int argc, const char* argv[])
+{
+ struct sigaction ignpipe, oldpipe, defchld, oldchld;
+ struct sockaddr_un addr;
+ int res;
+ pid_t pid;
+ int status;
+
+ /* Make dumb signals go away */
+ memset (&ignpipe, 0, sizeof (ignpipe));
+ memset (&oldpipe, 0, sizeof (oldpipe));
+ ignpipe.sa_handler = SIG_IGN;
+ sigaction (SIGPIPE, &ignpipe, &oldpipe);
+
+ memset (&defchld, 0, sizeof (defchld));
+ memset (&oldchld, 0, sizeof (oldchld));
+ defchld.sa_handler = SIG_DFL;
+ sigaction (SIGCHLD, &defchld, &oldchld);
+
+ res = lookup_daemon (pwd, control, &addr);
+ if (res != GKD_CONTROL_RESULT_OK)
+ goto out;
+
+ if (pwd->pw_uid == getuid () && pwd->pw_gid == getgid () &&
+ pwd->pw_uid == geteuid () && pwd->pw_gid == getegid ()) {
+
+ /* Already running as the right user, simple */
+ res = keyring_daemon_op (pwd, &addr, op, argc, argv);
+
+ } else {
+
+ /* Otherwise run a child process to do the dirty work */
+ switch (pid = fork ()) {
+ case -1:
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't fork: %s",
+ strerror (errno));
+ res = GKD_CONTROL_RESULT_FAILED;
+ break;
+
+ case 0:
+ /* Setup process credentials */
+ if (setgid (pwd->pw_gid) < 0 || setuid (pwd->pw_uid) < 0 ||
+ setegid (pwd->pw_gid) < 0 || seteuid (pwd->pw_uid) < 0) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't switch to user: %s: %s",
+ pwd->pw_name, strerror (errno));
+ exit (GKD_CONTROL_RESULT_FAILED);
+ }
+
+ res = keyring_daemon_op (pwd, &addr, op, argc, argv);
+ exit (res);
+ return 0; /* Never reached */
+
+ default:
+ /* wait for child process */
+ if (wait (&status) != pid) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't wait on child process: %s",
+ strerror (errno));
+ res = GKD_CONTROL_RESULT_FAILED;
+ }
+
+ res = WEXITSTATUS (status);
+ break;
+ };
+ }
+
+out:
+ sigaction (SIGCHLD, &oldchld, NULL);
+ sigaction (SIGPIPE, &oldpipe, NULL);
+
+ return res;
+}
diff --git a/pam/gkr-pam-module.c b/pam/gkr-pam-module.c
new file mode 100644
index 0000000..ff221a3
--- /dev/null
+++ b/pam/gkr-pam-module.c
@@ -0,0 +1,616 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gkr-pam-module.h - A PAM module for unlocking the keyring
+
+ Copyright (C) 2007 Stef Walter
+
+ 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
+*/
+
+/*
+ * Inspired by pam_keyring:
+ * W. Michael Petullo
+ * Jonathan Nettleton
+ */
+
+#include "config.h"
+#include "gkr-pam.h"
+#include "gkd-control-codes.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if defined(ENABLE_NLS) && defined(__linux__)
+#include
+#define gkr_pam_gettext(msgid) dgettext ("Linux-PAM", msgid)
+#else
+#define gkr_pam_gettext(msgid) (msgid)
+#endif /* ENABLE_NLS */
+
+enum {
+ ARG_AUTO_START = 1 << 0,
+ ARG_IGNORE_SERVICE = 1 << 1,
+ ARG_USE_AUTHTOK = 1 << 2
+};
+
+#define ENV_CONTROL "GNOME_KEYRING_CONTROL"
+
+#define MAX_CONTROL_SIZE (sizeof(((struct sockaddr_un *)0)->sun_path))
+
+/* read & write ends of a pipe */
+#define READ_END 0
+#define WRITE_END 1
+
+/* pre-set file descriptors */
+#define STDIN 0
+#define STDOUT 1
+#define STDERR 2
+
+/* Linux/BSD compatibility */
+#ifndef PAM_AUTHTOK_RECOVER_ERR
+#define PAM_AUTHTOK_RECOVER_ERR PAM_AUTHTOK_RECOVERY_ERR
+#endif
+
+#ifndef PAM_EXTERN
+#ifdef PAM_STATIC
+#define PAM_EXTERN static
+#else
+#define PAM_EXTERN extern
+#endif
+#endif
+
+/* -----------------------------------------------------------------------------
+ * HELPERS
+ */
+
+static void
+free_password (char *password)
+{
+ volatile char *vp;
+ size_t len;
+
+ if (!password)
+ return;
+
+ /* Defeats some optimizations */
+ len = strlen (password);
+ memset (password, 0xAA, len);
+ memset (password, 0xBB, len);
+
+ /* Defeats others */
+ vp = (volatile char*)password;
+ while (*vp)
+ *(vp++) = 0xAA;
+
+ free (password);
+}
+
+/* check for list match. */
+static int
+evaluate_inlist (const char *needle, const char *haystack)
+{
+ const char *item;
+ const char *remaining;
+
+ if (!needle)
+ return 0;
+
+ remaining = haystack;
+
+ for (;;) {
+ item = strstr (remaining, needle);
+ if (item == NULL)
+ break;
+
+ /* is it really the start of an item in the list? */
+ if (item == haystack || *(item - 1) == ',') {
+ item += strlen (needle);
+ /* is item really needle? */
+ if (*item == '\0' || *item == ',')
+ return 1;
+ }
+
+ remaining = strchr (item, ',');
+ if (remaining == NULL)
+ break;
+
+ /* skip ',' */
+ ++remaining;
+ }
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * DAEMON MANAGEMENT
+ */
+
+static const char*
+get_any_env (pam_handle_t *ph, const char *name)
+{
+ const char *env;
+
+ assert (name);
+
+ /* We only return non-empty variables */
+
+ /*
+ * Some PAMs decide to strdup the return value, not sure
+ * how we can detect this.
+ */
+ env = pam_getenv (ph, name);
+ if (env && env[0])
+ return env;
+
+ env = getenv (name);
+ if (env && env[0])
+ return env;
+
+ return NULL;
+}
+
+static void
+cleanup_free_password (pam_handle_t *ph, void *data, int pam_end_status)
+{
+ free_password (data);
+}
+
+/* control must be at least MAX_CONTROL_SIZE */
+static int
+get_control_file (pam_handle_t *ph, char *control)
+{
+ const char *control_root;
+ const char *suffix;
+
+ control_root = get_any_env (ph, ENV_CONTROL);
+ if (control_root == NULL) {
+ control_root = get_any_env (ph, "XDG_RUNTIME_DIR");
+ if (control_root == NULL)
+ return GKD_CONTROL_RESULT_NO_DAEMON;
+ suffix = "/keyring/control";
+ } else {
+ suffix = "/control";
+ }
+
+ if (strlen (control_root) + strlen (suffix) + 1 > MAX_CONTROL_SIZE) {
+ syslog (GKR_LOG_ERR, "gkr-pam: address is too long for unix socket path: %s/%s",
+ control, suffix);
+ return GKD_CONTROL_RESULT_FAILED;
+ }
+
+ strcpy (control, control_root);
+ strcat (control, suffix);
+
+ return GKD_CONTROL_RESULT_OK;
+}
+
+static int
+unlock_keyring (pam_handle_t *ph,
+ struct passwd *pwd,
+ const char *password)
+{
+ char control[MAX_CONTROL_SIZE];
+ int res;
+ const char *argv[2];
+
+ assert (pwd);
+
+ res = get_control_file(ph, control);
+ if (res != GKD_CONTROL_RESULT_OK) {
+ syslog (GKR_LOG_ERR, "gkr-pam: unable to locate daemon control file");
+ return PAM_SERVICE_ERR;
+ }
+
+ argv[0] = password;
+
+ res = gkr_pam_client_run_operation (pwd, control, GKD_CONTROL_OP_UNLOCK,
+ (argv[0] == NULL) ? 0 : 1, argv);
+ /* An error unlocking */
+ if (res == GKD_CONTROL_RESULT_NO_DAEMON) {
+ return PAM_SERVICE_ERR;
+ } else if (res == GKD_CONTROL_RESULT_DENIED) {
+ syslog (GKR_LOG_ERR, "gkr-pam: the password for the login keyring was invalid.");
+ return PAM_SERVICE_ERR;
+ } else if (res != GKD_CONTROL_RESULT_OK) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't unlock the login keyring.");
+ return PAM_SERVICE_ERR;
+ }
+
+ syslog (GKR_LOG_INFO, "gkr-pam: unlocked login keyring");
+ return PAM_SUCCESS;
+}
+
+static int
+change_keyring_password (pam_handle_t *ph,
+ struct passwd *pwd,
+ const char *password,
+ const char *original)
+{
+ char control[MAX_CONTROL_SIZE];
+ const char *argv[3];
+ int res;
+
+ assert (pwd);
+ assert (password);
+ assert (original);
+
+ res = get_control_file(ph, control);
+ if (res != GKD_CONTROL_RESULT_OK) {
+ syslog (GKR_LOG_ERR, "gkr-pam: unable to locate daemon control file");
+ return PAM_SERVICE_ERR;
+ }
+
+ argv[0] = original;
+ argv[1] = password;
+
+ res = gkr_pam_client_run_operation (pwd, control, GKD_CONTROL_OP_CHANGE, 2, argv);
+
+ if (res == GKD_CONTROL_RESULT_NO_DAEMON) {
+ return PAM_SERVICE_ERR;
+ /* No keyring, not an error. Will be created at initial authenticate. */
+ } else if (res == GKD_CONTROL_RESULT_DENIED) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't change password for the login keyring: the passwords didn't match.");
+ return PAM_SERVICE_ERR;
+ } else if (res != GKD_CONTROL_RESULT_OK) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't change password for the login keyring.");
+ return PAM_SERVICE_ERR;
+ }
+
+ syslog (GKR_LOG_NOTICE, "gkr-pam: changed password for login keyring");
+ return PAM_SUCCESS;
+}
+
+/* -----------------------------------------------------------------------------
+ * PAM STUFF
+ */
+
+static int
+prompt_password (pam_handle_t *ph)
+{
+ 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 = gkr_pam_gettext ("Password: ");
+ 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, PAM_AUTHTOK, password);
+ free_password (password);
+
+ if (ret == PAM_SUCCESS)
+ ret = pam_get_item (ph, PAM_AUTHTOK, &item);
+
+ return ret;
+}
+
+static uint
+parse_args (pam_handle_t *ph, int argc, const char **argv)
+{
+ uint args = 0;
+ const void *svc;
+ int only_if_len;
+ int i;
+
+ svc = NULL;
+ if (pam_get_item (ph, PAM_SERVICE, &svc) != PAM_SUCCESS)
+ svc = NULL;
+
+ only_if_len = strlen ("only_if=");
+
+ /* Parse the arguments */
+ for (i = 0; i < argc; i++) {
+ if (strcmp (argv[i], "auto_start") == 0) {
+ args |= ARG_AUTO_START;
+
+ } else if (strncmp (argv[i], "only_if=", only_if_len) == 0) {
+ const char *value = argv[i] + only_if_len;
+ if (!evaluate_inlist (svc, value))
+ args |= ARG_IGNORE_SERVICE;
+
+ } else if (strcmp (argv[i], "use_authtok") == 0) {
+ args |= ARG_USE_AUTHTOK;
+
+ } else {
+ syslog (GKR_LOG_WARN, "gkr-pam: invalid option: %s",
+ argv[i]);
+ }
+ }
+
+ return args;
+}
+
+static int
+stash_password_for_session (pam_handle_t *ph,
+ const char *password)
+{
+ if (pam_set_data (ph, "gkr_system_authtok", strdup (password),
+ cleanup_free_password) != PAM_SUCCESS) {
+ syslog (GKR_LOG_ERR, "gkr-pam: error stashing password for session");
+ return PAM_AUTHTOK_RECOVER_ERR;
+ }
+
+ return PAM_SUCCESS;
+}
+
+PAM_EXTERN int
+pam_sm_authenticate (pam_handle_t *ph, int unused, int argc, const char **argv)
+{
+ struct passwd *pwd;
+ const char *user, *password;
+ uint args;
+ int ret;
+
+ args = parse_args (ph, argc, argv);
+
+ if (args & ARG_IGNORE_SERVICE)
+ return PAM_SUCCESS;
+
+ /* Figure out and/or prompt for the user name */
+ ret = pam_get_user (ph, &user, NULL);
+ if (ret != PAM_SUCCESS) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't get the user name: %s",
+ pam_strerror (ph, ret));
+ return PAM_SERVICE_ERR;
+ }
+
+ pwd = getpwnam (user);
+ if (!pwd) {
+ syslog (GKR_LOG_ERR, "gkr-pam: error looking up user information");
+ return PAM_SERVICE_ERR;
+ }
+
+ /* Look up the password */
+ ret = pam_get_authtok (ph, PAM_AUTHTOK, &password, NULL);
+ if (ret != PAM_SUCCESS || password == NULL) {
+ if (ret == PAM_SUCCESS)
+ syslog (GKR_LOG_WARN, "gkr-pam: no password is available for user");
+ else
+ syslog (GKR_LOG_WARN, "gkr-pam: no password is available for user: %s",
+ pam_strerror (ph, ret));
+ return PAM_SUCCESS;
+ }
+
+ ret = unlock_keyring (ph, pwd, password);
+ if (ret != PAM_SUCCESS) {
+ ret = stash_password_for_session (ph, password);
+ syslog (GKR_LOG_INFO, "gkr-pam: stashed password to try later in open session");
+ }
+
+ return ret;
+}
+
+PAM_EXTERN int
+pam_sm_open_session (pam_handle_t *ph, int flags, int argc, const char **argv)
+{
+ const char *user = NULL, *password = NULL;
+ struct passwd *pwd;
+ int ret;
+ uint args;
+
+ args = parse_args (ph, argc, argv);
+
+ if (args & ARG_IGNORE_SERVICE)
+ return PAM_SUCCESS;
+
+ /* Figure out the user name */
+ ret = pam_get_user (ph, &user, NULL);
+ if (ret != PAM_SUCCESS) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't get the user name: %s",
+ pam_strerror (ph, ret));
+ return PAM_SERVICE_ERR;
+ }
+
+ pwd = getpwnam (user);
+ if (!pwd) {
+ syslog (GKR_LOG_ERR, "gkr-pam: error looking up user information for: %s", user);
+ return PAM_SERVICE_ERR;
+ }
+
+ /* Get the stored authtok here */
+ if (pam_get_data (ph, "gkr_system_authtok", (const void**)&password) != PAM_SUCCESS) {
+ /*
+ * No password, no worries, maybe this (PAM using) application
+ * didn't do authentication, or is hopeless and wants to call
+ * different PAM callbacks from different processes.
+ *
+ * No use complaining
+ */
+ password = NULL;
+ }
+
+ if (args & ARG_AUTO_START || password) {
+ ret = unlock_keyring (ph, pwd, password);
+ if (ret != PAM_SUCCESS)
+ return PAM_SERVICE_ERR;
+ }
+
+ /* Destroy the stored authtok once it has been used */
+ if (password && pam_set_data (ph, "gkr_system_authtok", NULL, NULL) != PAM_SUCCESS) {
+ syslog (GKR_LOG_ERR, "gkr-pam: error destroying the password");
+ return PAM_SERVICE_ERR;
+ }
+
+ return PAM_SUCCESS;
+}
+
+PAM_EXTERN int
+pam_sm_setcred (pam_handle_t * ph, int flags, int argc, const char **argv)
+{
+ return PAM_SUCCESS;
+}
+
+static int
+pam_chauthtok_preliminary (pam_handle_t *ph, struct passwd *pwd)
+{
+ /*
+ * If a super-user is changing a user's password then pam_unix.so
+ * doesn't prompt for the user's current password, which means we
+ * won't have access to that password to change the keyring password.
+ *
+ * So we could prompt for the current user's password except that
+ * most software is broken in this regard, and doesn't use the
+ * prompts properly.
+ *
+ * In addition how would we verify the user's password? We could
+ * verify it against the Gnome Keyring, but if it is mismatched
+ * from teh UNIX password then that would be super confusing.
+ *
+ * So we opt, just to send NULL along with the change password
+ * request and have the user type in their current GNOME Keyring
+ * password at an explanatory prompt.
+ */
+
+ return PAM_IGNORE;
+}
+
+static int
+pam_chauthtok_update (pam_handle_t *ph, struct passwd *pwd, uint args)
+{
+ const char *password, *original;
+ int ret;
+
+ ret = pam_get_authtok (ph, PAM_AUTHTOK, &password, NULL);
+ if (ret != PAM_SUCCESS)
+ password = NULL;
+
+ ret = pam_get_authtok (ph, PAM_OLDAUTHTOK, &original, NULL);
+ if (ret != PAM_SUCCESS || original == NULL) {
+ syslog (GKR_LOG_WARN, "gkr-pam: couldn't update the login keyring password: %s",
+ "no old password was entered");
+ if (password)
+ stash_password_for_session (ph, password);
+ return PAM_IGNORE;
+ }
+
+ if (password == NULL) {
+ /* No password was set, and we can't prompt for it */
+ if (args & ARG_USE_AUTHTOK) {
+ syslog (GKR_LOG_ERR, "gkr-pam: no password set, and use_authtok was specified");
+ return PAM_AUTHTOK_RECOVER_ERR;
+ }
+
+ /* No password was entered, prompt for it */
+ ret = prompt_password (ph);
+ if (ret != PAM_SUCCESS) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't get the password from user: %s",
+ pam_strerror (ph, ret));
+ return PAM_AUTH_ERR;
+ }
+ ret = pam_get_authtok (ph, PAM_AUTHTOK, &password, NULL);
+ if (ret != PAM_SUCCESS || password == NULL) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't get the password from user: %s",
+ ret == PAM_SUCCESS ? "password was null" : pam_strerror (ph, ret));
+ return PAM_AUTHTOK_RECOVER_ERR;
+ }
+ }
+
+ ret = change_keyring_password (ph, pwd, password, original);
+ if (ret != PAM_SUCCESS) {
+ /* Store the password for our session handler */
+ stash_password_for_session (ph, password);
+ syslog (GKR_LOG_INFO, "gkr-pam: stashed password to try later in open session");
+ }
+
+ return ret;
+}
+
+PAM_EXTERN int
+pam_sm_close_session (pam_handle_t *ph, int flags, int argc, const char **argv)
+{
+ /* Nothing to do, but we have to have this function exported */
+ return PAM_SUCCESS;
+}
+
+PAM_EXTERN int
+pam_sm_chauthtok (pam_handle_t *ph, int flags, int argc, const char **argv)
+{
+ const char *user;
+ struct passwd *pwd;
+ uint args;
+ int ret;
+
+ args = parse_args (ph, argc, argv);
+
+ if (args & ARG_IGNORE_SERVICE)
+ return PAM_SUCCESS;
+
+ /* Figure out and/or prompt for the user name */
+ ret = pam_get_user (ph, &user, NULL);
+ if (ret != PAM_SUCCESS) {
+ syslog (GKR_LOG_ERR, "gkr-pam: couldn't get the user name: %s",
+ pam_strerror (ph, ret));
+ return PAM_SERVICE_ERR;
+ }
+
+ pwd = getpwnam (user);
+ if (!pwd) {
+ syslog (GKR_LOG_ERR, "gkr-pam: error looking up user information for: %s", user);
+ return PAM_SERVICE_ERR;
+ }
+
+ if (flags & PAM_PRELIM_CHECK)
+ return pam_chauthtok_preliminary (ph, pwd);
+ else if (flags & PAM_UPDATE_AUTHTOK)
+ return pam_chauthtok_update (ph, pwd, args);
+ else
+ return PAM_IGNORE;
+}
diff --git a/pam/gkr-pam.h b/pam/gkr-pam.h
new file mode 100644
index 0000000..7ef3599
--- /dev/null
+++ b/pam/gkr-pam.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gkr-pam.h - Common PAM definitions
+
+ Copyright (C) 2007 Stef Walter
+
+ 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
+*/
+
+#ifndef GKRPAM_H_
+#define GKRPAM_H_
+
+#include
+
+#ifndef LOG_AUTHPRIV
+#define LOG_AUTHPRIV LOG_AUTH
+#endif
+
+#define GKR_LOG_ERR (LOG_ERR | LOG_AUTHPRIV)
+#define GKR_LOG_WARN (LOG_WARNING | LOG_AUTHPRIV)
+#define GKR_LOG_NOTICE (LOG_NOTICE | LOG_AUTHPRIV)
+#define GKR_LOG_INFO (LOG_INFO | LOG_AUTHPRIV)
+
+int gkr_pam_client_run_operation (struct passwd *pwd, const char *socket,
+ int op, int argc, const char* argv[]);
+
+#endif /*GKRPAM_H_*/
diff --git a/pam/meson.build b/pam/meson.build
new file mode 100644
index 0000000..51657ff
--- /dev/null
+++ b/pam/meson.build
@@ -0,0 +1,19 @@
+# pam source
+pam = dependency('pam', required: true)
+
+pam_gnome_keyring = shared_library('pam_gnome_keyring',
+ sources: [
+ 'gkr-pam-module.c',
+ 'gkr-pam-client.c',
+ ],
+ dependencies: [
+ pam,
+ glib_deps,
+ ],
+ include_directories: config_h_dir,
+ link_with: libegg,
+ c_args: [
+ '-D_GNU_SOURCE',
+ ],
+ name_prefix: '',
+)