mirror of
https://gitlab.gnome.org/GNOME/libsecret.git
synced 2024-12-22 12:48:51 +00:00
9a37dc839a
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>
459 lines
11 KiB
C
459 lines
11 KiB
C
/* -*- 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;
|
|
}
|