pam: port PAM module from gnome-keyring

These changes port the PAM module from gnome-keyring/pam to libsecret/pam.

Removed `start_daemon` and the dependent code altogether. Because,
gnome-keyring-daemon is launched by systemd.

Replaced calls to `pam_get_item` to retrieve authentication tokens with
`pam_get_authtok`.

Signed-off-by: Dhanuka Warusadura <dhanuka@gnome.org>
This commit is contained in:
Dhanuka Warusadura 2023-11-03 10:07:44 +05:30
parent 9cfa77f967
commit 9a37dc839a
8 changed files with 1193 additions and 0 deletions

View File

@ -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:

View File

@ -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

View File

@ -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')

38
pam/gkd-control-codes.h Normal file
View File

@ -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
* <http://www.gnu.org/licenses/>.
*/
#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__ */

458
pam/gkr-pam-client.c Normal file
View File

@ -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,
<http://www.gnu.org/licenses/>.
Author: Stef Walter <stef@memberwebs.com>
*/
#include "config.h"
#include "gkr-pam.h"
#include "egg/egg-unix-credentials.h"
#include "egg/egg-buffer.h"
#include "gkd-control-codes.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <stdint.h>
#if defined(HAVE_GETPEERUCRED)
#include <ucred.h>
#endif
#if defined(LOCAL_PEERCRED)
#include <sys/param.h>
#include <sys/ucred.h>
#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;
}

616
pam/gkr-pam-module.c Normal file
View File

@ -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,
<http://www.gnu.org/licenses/>.
Author: Stef Walter <stef@memberwebs.com>
*/
/*
* Inspired by pam_keyring:
* W. Michael Petullo <mike@flyn.org>
* Jonathan Nettleton <jon.nettleton@gmail.com>
*/
#include "config.h"
#include "gkr-pam.h"
#include "gkd-control-codes.h"
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#if defined(ENABLE_NLS) && defined(__linux__)
#include <libintl.h>
#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;
}

40
pam/gkr-pam.h Normal file
View File

@ -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,
<http://www.gnu.org/licenses/>.
Author: Stef Walter <stef@memberwebs.com>
*/
#ifndef GKRPAM_H_
#define GKRPAM_H_
#include <pwd.h>
#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_*/

19
pam/meson.build Normal file
View File

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