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: '', +)