From 175514244ff2edd3ce8ed99eceac2bcdef81aa37 Mon Sep 17 00:00:00 2001 From: Dhanuka Warusadura Date: Fri, 3 Nov 2023 09:48:11 +0530 Subject: [PATCH 1/4] ci: install packages required for the PAM module Signed-off-by: Dhanuka Warusadura --- .gitlab-ci.yml | 2 +- .gitlab-ci/master.Dockerfile | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 10cc3e3..a247c62 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: registry.gitlab.gnome.org/gnome/libsecret/master:v3 +image: registry.gitlab.gnome.org/gnome/libsecret/master:v5 stages: - build diff --git a/.gitlab-ci/master.Dockerfile b/.gitlab-ci/master.Dockerfile index b353a6e..fb52563 100644 --- a/.gitlab-ci/master.Dockerfile +++ b/.gitlab-ci/master.Dockerfile @@ -27,11 +27,14 @@ RUN dnf update -y \ tpm2-tss-devel \ vala \ valgrind-devel \ + pam-devel \ + libpamtest-devel \ + pam_wrapper \ && dnf clean all ARG HOST_USER_ID=5555 ENV HOST_USER_ID ${HOST_USER_ID} -RUN useradd -u $HOST_USER_ID -ms /bin/bash user +RUN useradd -u $HOST_USER_ID -ms /bin/bash -p password user USER user WORKDIR /home/user From 9cfa77f967aab91c08e590b29bcc99efaf78587d Mon Sep 17 00:00:00 2001 From: Dhanuka Warusadura Date: Wed, 15 Nov 2023 13:45:09 +0530 Subject: [PATCH 2/4] pam: port PAM module egg helper functions from gnome-keyring This change is a part of the port PAM module from gnome-keyring patch set. These changes port gnome-keyring/egg/egg-unix-credentials.c to libsecret/egg Furthermore ports gnome-keyring/egg/egg-buffer.c to libsecret/egg Signed-off-by: Dhanuka Warusadura --- egg/egg-buffer.c | 581 +++++++++++++++++++++++++++++++++++++ egg/egg-buffer.h | 196 +++++++++++++ egg/egg-unix-credentials.c | 265 +++++++++++++++++ egg/egg-unix-credentials.h | 50 ++++ egg/meson.build | 5 + 5 files changed, 1097 insertions(+) create mode 100644 egg/egg-buffer.c create mode 100644 egg/egg-buffer.h create mode 100644 egg/egg-unix-credentials.c create mode 100644 egg/egg-unix-credentials.h diff --git a/egg/egg-buffer.c b/egg/egg-buffer.c new file mode 100644 index 0000000..f20588f --- /dev/null +++ b/egg/egg-buffer.c @@ -0,0 +1,581 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* egg-buffer.c - Generic data buffer, used by openssh, gnome-keyring + + Copyright (C) 2007 Stefan 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 +#include + +#include "egg-buffer.h" + +#define DEFAULT_ALLOCATOR ((EggBufferAllocator)realloc) + +int +egg_buffer_init (EggBuffer *buffer, size_t reserve) +{ + return egg_buffer_init_full (buffer, reserve, NULL); +} + +int +egg_buffer_init_full (EggBuffer *buffer, size_t reserve, EggBufferAllocator allocator) +{ + memset (buffer, 0, sizeof (*buffer)); + + if (!allocator) + allocator = DEFAULT_ALLOCATOR; + if (reserve == 0) + reserve = 64; + + buffer->buf = (allocator) (NULL, reserve); + if (!buffer->buf) { + buffer->failures++; + return 0; + } + + buffer->len = 0; + buffer->allocated_len = reserve; + buffer->failures = 0; + buffer->allocator = allocator; + + return 1; +} + +void +egg_buffer_init_static (EggBuffer* buffer, const unsigned char *buf, size_t len) +{ + memset (buffer, 0, sizeof (*buffer)); + + buffer->buf = (unsigned char*)buf; + buffer->len = len; + buffer->allocated_len = len; + buffer->failures = 0; + + /* A null allocator, and the buffer can't change in size */ + buffer->allocator = NULL; +} + +void +egg_buffer_init_allocated (EggBuffer *buffer, unsigned char *buf, size_t len, + EggBufferAllocator allocator) +{ + memset (buffer, 0, sizeof (*buffer)); + + if (!allocator) + allocator = DEFAULT_ALLOCATOR; + + buffer->buf = buf; + buffer->len = len; + buffer->allocated_len = len; + buffer->failures = 0; + buffer->allocator = allocator; +} + +void +egg_buffer_reset (EggBuffer *buffer) +{ + memset (buffer->buf, 0, buffer->allocated_len); + buffer->len = 0; + buffer->failures = 0; +} + +void +egg_buffer_uninit (EggBuffer *buffer) +{ + if (!buffer) + return; + + /* + * Free the memory block using allocator. If no allocator, + * then this memory is ownerd elsewhere and not to be freed. + */ + if (buffer->buf && buffer->allocator) + (buffer->allocator) (buffer->buf, 0); + + memset (buffer, 0, sizeof (*buffer)); +} + +unsigned char* +egg_buffer_uninit_steal (EggBuffer *buffer, size_t *n_result) +{ + unsigned char *result; + + if (n_result) + *n_result = buffer->len; + result = buffer->buf; + + memset (buffer, 0, sizeof (*buffer)); + + return result; +} + +int +egg_buffer_set_allocator (EggBuffer *buffer, EggBufferAllocator allocator) +{ + unsigned char *buf = NULL; + + if (!allocator) + allocator = DEFAULT_ALLOCATOR; + if (buffer->allocator == allocator) + return 1; + + if (buffer->allocated_len) { + /* Reallocate memory block using new allocator */ + buf = (allocator) (NULL, buffer->allocated_len); + if (buf == NULL) + return 0; + + /* Copy stuff into new memory */ + memcpy (buf, buffer->buf, buffer->allocated_len); + } + + /* If old wasn't static, then free it */ + if (buffer->allocator && buffer->buf) + (buffer->allocator) (buffer->buf, 0); + + buffer->buf = buf; + buffer->allocator = allocator; + + return 1; +} + +int +egg_buffer_equal (EggBuffer *b1, EggBuffer *b2) +{ + if (b1->len != b2->len) + return 0; + return memcmp (b1->buf, b2->buf, b1->len) == 0; +} + +int +egg_buffer_reserve (EggBuffer *buffer, size_t len) +{ + unsigned char *newbuf; + size_t newlen; + + if (len < buffer->allocated_len) + return 1; + + /* Calculate a new length, minimize number of buffer allocations */ + newlen = buffer->allocated_len * 2; + if (len > newlen) + newlen += len; + + /* Memory owned elsewhere can't be reallocated */ + if (!buffer->allocator) { + buffer->failures++; + return 0; + } + + /* Reallocate built in buffer using allocator */ + newbuf = (buffer->allocator) (buffer->buf, newlen); + if (!newbuf) { + buffer->failures++; + return 0; + } + + buffer->buf = newbuf; + buffer->allocated_len = newlen; + + return 1; +} + +int +egg_buffer_resize (EggBuffer *buffer, size_t len) +{ + if (!egg_buffer_reserve (buffer, len)) + return 0; + + buffer->len = len; + return 1; +} + +unsigned char* +egg_buffer_add_empty (EggBuffer *buffer, size_t len) +{ + size_t pos = buffer->len; + if (!egg_buffer_reserve (buffer, buffer->len + len)) + return NULL; + buffer->len += len; + return buffer->buf + pos; +} + +int +egg_buffer_append (EggBuffer *buffer, const unsigned char *val, + size_t len) +{ + if (!egg_buffer_reserve (buffer, buffer->len + len)) + return 0; /* failures already incremented */ + memcpy (buffer->buf + buffer->len, val, len); + buffer->len += len; + return 1; +} + +int +egg_buffer_add_byte (EggBuffer *buffer, unsigned char val) +{ + if (!egg_buffer_reserve (buffer, buffer->len + 1)) + return 0; /* failures already incremented */ + buffer->buf[buffer->len] = val; + buffer->len++; + return 1; +} + +int +egg_buffer_get_byte (EggBuffer *buffer, size_t offset, + size_t *next_offset, unsigned char *val) +{ + unsigned char *ptr; + if (buffer->len < 1 || offset > buffer->len - 1) { + buffer->failures++; + return 0; + } + ptr = (unsigned char*)buffer->buf + offset; + if (val != NULL) + *val = *ptr; + if (next_offset != NULL) + *next_offset = offset + 1; + return 1; +} + +void +egg_buffer_encode_uint16 (unsigned char* buf, uint16_t val) +{ + buf[0] = (val >> 8) & 0xff; + buf[1] = (val >> 0) & 0xff; +} + +uint16_t +egg_buffer_decode_uint16 (unsigned char* buf) +{ + uint16_t val = buf[0] << 8 | buf[1]; + return val; +} + +int +egg_buffer_add_uint16 (EggBuffer *buffer, uint16_t val) +{ + if (!egg_buffer_reserve (buffer, buffer->len + 2)) + return 0; /* failures already incremented */ + buffer->len += 2; + egg_buffer_set_uint16 (buffer, buffer->len - 2, val); + return 1; +} + +int +egg_buffer_set_uint16 (EggBuffer *buffer, size_t offset, uint16_t val) +{ + unsigned char *ptr; + if (buffer->len < 2 || offset > buffer->len - 2) { + buffer->failures++; + return 0; + } + ptr = (unsigned char*)buffer->buf + offset; + egg_buffer_encode_uint16 (ptr, val); + return 1; +} + +int +egg_buffer_get_uint16 (EggBuffer *buffer, size_t offset, + size_t *next_offset, uint16_t *val) +{ + unsigned char *ptr; + if (buffer->len < 2 || offset > buffer->len - 2) { + buffer->failures++; + return 0; + } + ptr = (unsigned char*)buffer->buf + offset; + if (val != NULL) + *val = egg_buffer_decode_uint16 (ptr); + if (next_offset != NULL) + *next_offset = offset + 2; + return 1; +} + +void +egg_buffer_encode_uint32 (unsigned char* buf, uint32_t val) +{ + buf[0] = (val >> 24) & 0xff; + buf[1] = (val >> 16) & 0xff; + buf[2] = (val >> 8) & 0xff; + buf[3] = (val >> 0) & 0xff; +} + +uint32_t +egg_buffer_decode_uint32 (unsigned char* ptr) +{ + uint32_t val = (uint32_t) ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; + return val; +} + +int +egg_buffer_add_uint32 (EggBuffer *buffer, uint32_t val) +{ + if (!egg_buffer_reserve (buffer, buffer->len + 4)) + return 0; /* failures already incremented */ + buffer->len += 4; + egg_buffer_set_uint32 (buffer, buffer->len - 4, val); + return 1; +} + +int +egg_buffer_set_uint32 (EggBuffer *buffer, size_t offset, uint32_t val) +{ + unsigned char *ptr; + if (buffer->len < 4 || offset > buffer->len - 4) { + buffer->failures++; + return 0; + } + ptr = (unsigned char*)buffer->buf + offset; + egg_buffer_encode_uint32 (ptr, val); + return 1; +} + +int +egg_buffer_get_uint32 (EggBuffer *buffer, size_t offset, size_t *next_offset, + uint32_t *val) +{ + unsigned char *ptr; + if (buffer->len < 4 || offset > buffer->len - 4) { + buffer->failures++; + return 0; + } + ptr = (unsigned char*)buffer->buf + offset; + if (val != NULL) + *val = egg_buffer_decode_uint32 (ptr); + if (next_offset != NULL) + *next_offset = offset + 4; + return 1; +} + +int +egg_buffer_add_uint64 (EggBuffer *buffer, uint64_t val) +{ + if (!egg_buffer_add_uint32 (buffer, ((val >> 32) & 0xffffffff))) + return 0; + return egg_buffer_add_uint32 (buffer, (val & 0xffffffff)); +} + +int +egg_buffer_get_uint64 (EggBuffer *buffer, size_t offset, + size_t *next_offset, uint64_t *val) +{ + uint32_t a, b; + if (!egg_buffer_get_uint32 (buffer, offset, &offset, &a)) + return 0; + if (!egg_buffer_get_uint32 (buffer, offset, &offset, &b)) + return 0; + if (val != NULL) + *val = ((uint64_t)a) << 32 | b; + if (next_offset != NULL) + *next_offset = offset; + return 1; +} + +int +egg_buffer_add_byte_array (EggBuffer *buffer, const unsigned char *val, + size_t len) +{ + if (val == NULL) + return egg_buffer_add_uint32 (buffer, 0xffffffff); + if (len >= 0x7fffffff) { + buffer->failures++; + return 0; + } + if (!egg_buffer_add_uint32 (buffer, len)) + return 0; + return egg_buffer_append (buffer, val, len); +} + +unsigned char* +egg_buffer_add_byte_array_empty (EggBuffer *buffer, size_t vlen) +{ + if (vlen >= 0x7fffffff) { + buffer->failures++; + return NULL; + } + if (!egg_buffer_add_uint32 (buffer, vlen)) + return NULL; + return egg_buffer_add_empty (buffer, vlen); +} + +int +egg_buffer_get_byte_array (EggBuffer *buffer, size_t offset, + size_t *next_offset, const unsigned char **val, + size_t *vlen) +{ + uint32_t len; + if (!egg_buffer_get_uint32 (buffer, offset, &offset, &len)) + return 0; + if (len == 0xffffffff) { + if (next_offset) + *next_offset = offset; + if (val) + *val = NULL; + if (vlen) + *vlen = 0; + return 1; + } else if (len >= 0x7fffffff) { + buffer->failures++; + return 0; + } + + if (buffer->len < len || offset > buffer->len - len) { + buffer->failures++; + return 0; + } + + if (val) + *val = buffer->buf + offset; + if (vlen) + *vlen = len; + if (next_offset) + *next_offset = offset + len; + + return 1; +} + +int +egg_buffer_add_string (EggBuffer *buffer, const char *str) +{ + if (str == NULL) { + return egg_buffer_add_uint32 (buffer, 0xffffffff); + } else { + size_t len = strlen (str); + if (len >= 0x7fffffff) + return 0; + if (!egg_buffer_add_uint32 (buffer, len)) + return 0; + return egg_buffer_append (buffer, (unsigned char*)str, len); + } +} + +int +egg_buffer_get_string (EggBuffer *buffer, size_t offset, size_t *next_offset, + char **str_ret, EggBufferAllocator allocator) +{ + uint32_t len; + + if (!allocator) + allocator = buffer->allocator; + if (!allocator) + allocator = DEFAULT_ALLOCATOR; + + if (!egg_buffer_get_uint32 (buffer, offset, &offset, &len)) { + return 0; + } + if (len == 0xffffffff) { + *next_offset = offset; + *str_ret = NULL; + return 1; + } else if (len >= 0x7fffffff) { + return 0; + } + + if (buffer->len < len || + offset > buffer->len - len) { + return 0; + } + + /* Make sure no null characters in string */ + if (memchr (buffer->buf + offset, 0, len) != NULL) + return 0; + + /* The passed allocator may be for non-pageable memory */ + *str_ret = (allocator) (NULL, len + 1); + if (!*str_ret) + return 0; + memcpy (*str_ret, buffer->buf + offset, len); + + /* Always zero terminate */ + (*str_ret)[len] = 0; + *next_offset = offset + len; + + return 1; +} + +int +egg_buffer_add_stringv (EggBuffer *buffer, const char** strv) +{ + const char **v; + uint32_t n = 0; + + if (!strv) + return 0; + + /* Add the number of strings coming */ + for (v = strv; *v; ++v) + ++n; + if (!egg_buffer_add_uint32 (buffer, n)) + return 0; + + /* Add the individual strings */ + for (v = strv; *v; ++v) { + if (!egg_buffer_add_string (buffer, *v)) + return 0; + } + + return 1; +} + +int +egg_buffer_get_stringv (EggBuffer *buffer, size_t offset, size_t *next_offset, + char ***strv_ret, EggBufferAllocator allocator) +{ + uint32_t n, i, j; + size_t len; + + if (!allocator) + allocator = buffer->allocator; + if (!allocator) + allocator = DEFAULT_ALLOCATOR; + + /* First the number of environment variable lines */ + if (!egg_buffer_get_uint32 (buffer, offset, &offset, &n)) + return 0; + + /* Then that number of strings */ + len = (n + 1) * sizeof (char*); + *strv_ret = (char**)(allocator) (NULL, len); + if (!*strv_ret) + return 0; + + /* All null strings */ + memset (*strv_ret, 0, len); + + for (i = 0; i < n; ++i) { + if (!egg_buffer_get_string (buffer, offset, &offset, + &((*strv_ret)[i]), allocator)) { + + /* Free all the strings on failure */ + for (j = 0; j < i; ++j) { + if ((*strv_ret)[j]) + (allocator) ((*strv_ret)[j], 0); + } + + return 0; + } + } + + if (next_offset != NULL) + *next_offset = offset; + + return 1; +} diff --git a/egg/egg-buffer.h b/egg/egg-buffer.h new file mode 100644 index 0000000..bd710f3 --- /dev/null +++ b/egg/egg-buffer.h @@ -0,0 +1,196 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* egg-buffer.h - Generic data buffer, used by openssh, gnome-keyring + + Copyright (C) 2007, Stefan 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 EGG_BUFFER_H +#define EGG_BUFFER_H + +#include +#include + +/* ------------------------------------------------------------------- + * EggBuffer + * + * IMPORTANT: This is pure vanila standard C, no glib. We need this + * because certain consumers of this protocol need to be built + * without linking in any special libraries. ie: the PKCS#11 module. + * + * Memory Allocation + * + * Callers can set their own allocator. If NULL is used then standard + * C library heap memory is used and failures will not be fatal. Memory + * failures will instead result in a zero return value or + * egg_buffer_has_error() returning one. + * + * If you use something like g_realloc as the allocator, then memory + * failures become fatal just like in a standard GTK program. + * + * Don't change the allocator manually in the EggBuffer structure. The + * egg_buffer_set_allocator() func will reallocate and handle things + * properly. + * + * Pointers into the Buffer + * + * Any write operation has the posibility of reallocating memory + * and invalidating any direct pointers into the buffer. + */ + +/* The allocator for the EggBuffer. This follows the realloc() syntax and logic */ +typedef void* (*EggBufferAllocator) (void* p, size_t len); + +typedef struct _EggBuffer { + unsigned char *buf; + size_t len; + size_t allocated_len; + int failures; + EggBufferAllocator allocator; +} EggBuffer; + +#define EGG_BUFFER_EMPTY { NULL, 0, 0, 0, NULL } + +int egg_buffer_init (EggBuffer *buffer, size_t reserve); + +int egg_buffer_init_full (EggBuffer *buffer, + size_t reserve, + EggBufferAllocator allocator); + +void egg_buffer_init_static (EggBuffer *buffer, + const unsigned char *buf, + size_t len); + +void egg_buffer_init_allocated (EggBuffer *buffer, + unsigned char *buf, + size_t len, + EggBufferAllocator allocator); + +void egg_buffer_uninit (EggBuffer *buffer); + +unsigned char* egg_buffer_uninit_steal (EggBuffer *buffer, + size_t *n_result); + +int egg_buffer_set_allocator (EggBuffer *buffer, + EggBufferAllocator allocator); + +void egg_buffer_reset (EggBuffer *buffer); + +int egg_buffer_equal (EggBuffer *b1, + EggBuffer *b2); + +int egg_buffer_reserve (EggBuffer *buffer, + size_t len); + +int egg_buffer_resize (EggBuffer *buffer, + size_t len); + +int egg_buffer_append (EggBuffer *buffer, + const unsigned char *val, + size_t len); + +unsigned char* egg_buffer_add_empty (EggBuffer *buffer, + size_t len); + +int egg_buffer_add_byte (EggBuffer *buffer, + unsigned char val); + +int egg_buffer_get_byte (EggBuffer *buffer, + size_t offset, + size_t *next_offset, + unsigned char *val); + +void egg_buffer_encode_uint32 (unsigned char* buf, + uint32_t val); + +uint32_t egg_buffer_decode_uint32 (unsigned char* buf); + +int egg_buffer_add_uint32 (EggBuffer *buffer, + uint32_t val); + +int egg_buffer_set_uint32 (EggBuffer *buffer, + size_t offset, + uint32_t val); + +int egg_buffer_get_uint32 (EggBuffer *buffer, + size_t offset, + size_t *next_offset, + uint32_t *val); + +void egg_buffer_encode_uint16 (unsigned char* buf, + uint16_t val); + +uint16_t egg_buffer_decode_uint16 (unsigned char* buf); + +int egg_buffer_add_uint16 (EggBuffer *buffer, + uint16_t val); + +int egg_buffer_set_uint16 (EggBuffer *buffer, + size_t offset, + uint16_t val); + +int egg_buffer_get_uint16 (EggBuffer *buffer, + size_t offset, + size_t *next_offset, + uint16_t *val); + +int egg_buffer_add_byte_array (EggBuffer *buffer, + const unsigned char *val, + size_t len); + +int egg_buffer_get_byte_array (EggBuffer *buffer, + size_t offset, + size_t *next_offset, + const unsigned char **val, + size_t *vlen); + +unsigned char* egg_buffer_add_byte_array_empty (EggBuffer *buffer, + size_t vlen); + +int egg_buffer_add_string (EggBuffer *buffer, + const char *str); + +int egg_buffer_get_string (EggBuffer *buffer, + size_t offset, + size_t *next_offset, + char **str_ret, + EggBufferAllocator allocator); + +int egg_buffer_add_stringv (EggBuffer *buffer, + const char** strv); + +int egg_buffer_get_stringv (EggBuffer *buffer, + size_t offset, + size_t *next_offset, + char ***strv_ret, + EggBufferAllocator allocator); + +int egg_buffer_add_uint64 (EggBuffer *buffer, + uint64_t val); + +int egg_buffer_get_uint64 (EggBuffer *buffer, + size_t offset, + size_t *next_offset, + uint64_t *val); + +#define egg_buffer_length(b) ((b)->len) + +#define egg_buffer_has_error(b) ((b)->failures > 0) + +#endif /* EGG_BUFFER_H */ + diff --git a/egg/egg-unix-credentials.c b/egg/egg-unix-credentials.c new file mode 100644 index 0000000..6366d29 --- /dev/null +++ b/egg/egg-unix-credentials.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2008 Stefan Walter + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Stef Walter + */ + +#include "config.h" + +#include "egg-unix-credentials.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_GETPEERUCRED) +#include +#endif + +int +egg_unix_credentials_read (int sock, pid_t *pid, uid_t *uid) +{ + struct msghdr msg; + struct iovec iov; + char buf; + int ret; + +#if defined(HAVE_CMSGCRED) || defined(LOCAL_CREDS) + /* Prefer CMSGCRED over LOCAL_CREDS because the former provides the + * remote PID. */ +#if defined(HAVE_CMSGCRED) + struct cmsgcred *cred; +#else /* defined(LOCAL_CREDS) */ + struct sockcred *cred; +#endif + union { + struct cmsghdr hdr; + char cred[CMSG_SPACE (sizeof *cred)]; + } cmsg; +#endif + + *pid = 0; + *uid = 0; + + /* If LOCAL_CREDS are used in this platform, they have already been + * initialized by init_connection prior to sending of the credentials + * byte we receive below. */ + + iov.iov_base = &buf; + iov.iov_len = 1; + + memset (&msg, 0, sizeof (msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + +#if defined(HAVE_CMSGCRED) || defined(LOCAL_CREDS) + memset (&cmsg, 0, sizeof (cmsg)); + msg.msg_control = (caddr_t) &cmsg; + msg.msg_controllen = CMSG_SPACE(sizeof *cred); +#endif + + again: + ret = recvmsg (sock, &msg, 0); + + if (ret < 0) { + if (errno == EINTR) + goto again; + return -1; + + } else if (ret == 0) { + /* Disconnected */ + return -1; + } + + if (buf != '\0') { + fprintf (stderr, "credentials byte was not nul\n"); + return -1; + } + +#if defined(HAVE_CMSGCRED) || defined(LOCAL_CREDS) + if (cmsg.hdr.cmsg_len < CMSG_LEN (sizeof *cred) || + cmsg.hdr.cmsg_type != SCM_CREDS) { + fprintf (stderr, "message from recvmsg() was not SCM_CREDS\n"); + return -1; + } +#endif + + { +#ifdef SO_PEERCRED +#ifndef __OpenBSD__ + struct ucred cr; +#else + struct sockpeercred cr; +#endif + socklen_t cr_len = sizeof (cr); + + if (getsockopt (sock, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len) == 0 && + cr_len == sizeof (cr)) { + *pid = cr.pid; + *uid = cr.uid; + } else { + fprintf (stderr, "failed to getsockopt() credentials, returned len %d/%d\n", + cr_len, (int) sizeof (cr)); + return -1; + } +#elif defined(HAVE_CMSGCRED) + cred = (struct cmsgcred *) CMSG_DATA (&cmsg.hdr); + *pid = cred->cmcred_pid; + *uid = cred->cmcred_euid; +#elif defined(LOCAL_CREDS) + cred = (struct sockcred *) CMSG_DATA (&cmsg.hdr); + *pid = 0; + *uid = cred->sc_euid; + set_local_creds(sock, 0); +#elif defined(HAVE_GETPEEREID) /* OpenBSD */ + uid_t euid; + gid_t egid; + *pid = 0; + + if (getpeereid (sock, &euid, &egid) == 0) { + *uid = euid; + } else { + fprintf (stderr, "getpeereid() failed: %s\n", strerror (errno)); + return -1; + } +#elif defined(HAVE_GETPEERUCRED) + ucred_t *uc = NULL; + + if (getpeerucred (sock, &uc) == 0) { + *pid = ucred_getpid (uc); + *uid = ucred_geteuid (uc); + ucred_free (uc); + } else { + fprintf (stderr, "getpeerucred() failed: %s\n", strerror (errno)); + return -1; + } +#else /* !SO_PEERCRED && !HAVE_CMSGCRED */ + fprintf (stderr, "socket credentials not supported on this OS\n"); + return -1; +#endif + } + + return 0; +} + +int +egg_unix_credentials_write (int socket) +{ + char buf; + int bytes_written; +#if defined(HAVE_CMSGCRED) && (!defined(LOCAL_CREDS) || defined(__FreeBSD__)) + union { + struct cmsghdr hdr; + char cred[CMSG_SPACE (sizeof (struct cmsgcred))]; + } cmsg; + struct iovec iov; + struct msghdr msg; +#endif + + buf = 0; + +#if defined(HAVE_CMSGCRED) && (!defined(LOCAL_CREDS) || defined(__FreeBSD__)) + iov.iov_base = &buf; + iov.iov_len = 1; + + memset (&msg, 0, sizeof (msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_control = (caddr_t) &cmsg; + msg.msg_controllen = CMSG_SPACE (sizeof (struct cmsgcred)); + memset (&cmsg, 0, sizeof (cmsg)); + cmsg.hdr.cmsg_len = CMSG_LEN (sizeof (struct cmsgcred)); + cmsg.hdr.cmsg_level = SOL_SOCKET; + cmsg.hdr.cmsg_type = SCM_CREDS; +#endif + +again: + +#if defined(HAVE_CMSGCRED) && (!defined(LOCAL_CREDS) || defined(__FreeBSD__)) + bytes_written = sendmsg (socket, &msg, 0); +#else + bytes_written = write (socket, &buf, 1); +#endif + + if (bytes_written < 0 && errno == EINTR) + goto again; + + if (bytes_written <= 0) + return -1; + + return 0; +} + +int +egg_unix_credentials_setup (int sock) +{ + int retval = 0; +#if defined(LOCAL_CREDS) && !defined(HAVE_CMSGCRED) + int val = 1; + if (setsockopt (sock, 0, LOCAL_CREDS, &val, sizeof (val)) < 0) { + fprintf (stderr, "unable to set LOCAL_CREDS socket option on fd %d\n", fd); + retval = -1; + } +#endif + return retval; +} + +char* +egg_unix_credentials_executable (pid_t pid) +{ + char *result = NULL; + + /* Try and figure out the path from the pid */ +#if defined(__linux__) || defined(__FreeBSD__) + char path[1024]; + char buffer[64]; + int count; + +#if defined(__linux__) + snprintf (buffer, sizeof (buffer), "/proc/%d/exe", (int)pid); +#elif defined(__FreeBSD__) + snprintf (buffer, sizeof (buffer), "/proc/%d/file", (int)pid); +#endif + + count = readlink (buffer, path, sizeof (path)); + if (count < 0) + fprintf (stderr, "readlink failed for file: %s", buffer); + else + result = strndup (path, count); +#endif + + return result; +} diff --git a/egg/egg-unix-credentials.h b/egg/egg-unix-credentials.h new file mode 100644 index 0000000..64b6329 --- /dev/null +++ b/egg/egg-unix-credentials.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008 Stefan Walter + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Stef Walter + */ + +#ifndef EGGUNIXCREDENTIALS_H_ +#define EGGUNIXCREDENTIALS_H_ + +#include + +int egg_unix_credentials_read (int sock, + pid_t *pid, + uid_t *uid); + +int egg_unix_credentials_write (int sock); + +int egg_unix_credentials_setup (int sock); + +char* egg_unix_credentials_executable (pid_t pid); + +#endif /*EGGUNIXCREDENTIALS_H_*/ diff --git a/egg/meson.build b/egg/meson.build index cf88390..32072f5 100644 --- a/egg/meson.build +++ b/egg/meson.build @@ -1,6 +1,8 @@ libegg_sources = [ 'egg-hex.c', 'egg-secure-memory.c', + 'egg-unix-credentials.c', + 'egg-buffer.c', 'egg-testing.c', ] @@ -42,6 +44,9 @@ endif libegg = static_library('egg', libegg_sources, dependencies: libegg_deps, + c_args: [ + '-D_GNU_SOURCE', + ], include_directories: [config_h_dir, build_dir], ) From 9a37dc839a9be1670afeb647d9f82b6ef1cd0893 Mon Sep 17 00:00:00 2001 From: Dhanuka Warusadura Date: Fri, 3 Nov 2023 10:07:44 +0530 Subject: [PATCH 3/4] 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 --- .gitlab-ci.yml | 14 + meson.build | 7 + meson_options.txt | 1 + pam/gkd-control-codes.h | 38 +++ pam/gkr-pam-client.c | 458 +++++++++++++++++++++++++++++ pam/gkr-pam-module.c | 616 ++++++++++++++++++++++++++++++++++++++++ pam/gkr-pam.h | 40 +++ pam/meson.build | 19 ++ 8 files changed, 1193 insertions(+) create mode 100644 pam/gkd-control-codes.h create mode 100644 pam/gkr-pam-client.c create mode 100644 pam/gkr-pam-module.c create mode 100644 pam/gkr-pam.h create mode 100644 pam/meson.build 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: '', +) From b399f5f631b8a540caf9abf757a5b57ac1f78a1b Mon Sep 17 00:00:00 2001 From: Dhanuka Warusadura Date: Wed, 15 Nov 2023 14:07:31 +0530 Subject: [PATCH 4/4] pam: add tests for the ported PAM module These changes add PAM tests based on pam_wrapper and libpamtest. Signed-off-by: Dhanuka Warusadura --- pam/meson.build | 26 +++++ pam/servicedir/meson.build | 11 ++ pam/servicedir/pam-test-service.in | 3 + pam/test-pam.c | 173 +++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+) create mode 100644 pam/servicedir/meson.build create mode 100644 pam/servicedir/pam-test-service.in create mode 100644 pam/test-pam.c diff --git a/pam/meson.build b/pam/meson.build index 51657ff..8413b58 100644 --- a/pam/meson.build +++ b/pam/meson.build @@ -17,3 +17,29 @@ pam_gnome_keyring = shared_library('pam_gnome_keyring', ], name_prefix: '', ) + +# pam tests +pam_wrapper = dependency('pam_wrapper', required: true) +libpamtest = dependency('libpamtest', required: true) + +subdir('servicedir') + +test_bin = executable('pam_test', + sources: [ + 'test-pam.c', + ], + dependencies: [ + libpamtest, + glib_deps, + ], +) + +test('pam-test', + test_bin, + env: { + 'LD_PRELOAD': 'libpam_wrapper.so', + 'PAM_WRAPPER': '1', + 'PAM_WRAPPER_DEBUGLEVEL': '5', + 'PAM_WRAPPER_SERVICE_DIR': meson.current_build_dir() + '/servicedir', + }, +) diff --git a/pam/servicedir/meson.build b/pam/servicedir/meson.build new file mode 100644 index 0000000..04adb0f --- /dev/null +++ b/pam/servicedir/meson.build @@ -0,0 +1,11 @@ +custom_target('pam-test-service', + command: 'true', + output: 'null', + depend_files: configure_file( + input: 'pam-test-service.in', + output: 'pam-test-service', + configuration: configuration_data({ + 'KEYRING_PAM': pam_gnome_keyring.full_path(), + }), + ), +) diff --git a/pam/servicedir/pam-test-service.in b/pam/servicedir/pam-test-service.in new file mode 100644 index 0000000..3d67797 --- /dev/null +++ b/pam/servicedir/pam-test-service.in @@ -0,0 +1,3 @@ +auth required @KEYRING_PAM@ +password required @KEYRING_PAM@ +session required @KEYRING_PAM@ diff --git a/pam/test-pam.c b/pam/test-pam.c new file mode 100644 index 0000000..19fabe4 --- /dev/null +++ b/pam/test-pam.c @@ -0,0 +1,173 @@ +/* + * libsecret + * + * Copyright (C) 2023 GNOME Foundation Inc. + * + * 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 copies of the GNU General Public License and + * the GNU Lesser General Public License along with this program. If + * not, see http://www.gnu.org/licenses/. + * + * Author: Dhanuka Warusadura + */ + +#include +#include +#include +#include +#include + +#include + +#define SERVICE "pam-test-service" +#define BUFFER_SIZE 100 + +typedef struct { + gchar *control_path; + GSocketAddress *address; + GSocketService *service; + GThread *pam_test; + gboolean success; +} Test; + +static gchar dir_path[] = "/tmp/pam_test_XXXXXX"; + +static gboolean +is_bytes_exchanged (GSocketService *service, + GSocketConnection *connection, + GObject *source_object, + gpointer data) +{ + Test *test = data; + GInputStream *input; + GError *error = NULL; + char buffer[BUFFER_SIZE + 1]; + + g_printf ("Incoming signal detected\n"); + + if (g_socket_service_is_active (service)) + test->success = TRUE; + else + return FALSE; + + input = g_io_stream_get_input_stream (G_IO_STREAM(connection)); + + if (g_input_stream_read (input, + buffer, + BUFFER_SIZE, + NULL, + &error) > 0) + return TRUE; + else + return FALSE; +} + +static void +teardown (Test *test) +{ + g_assert_true (g_socket_service_is_active (test->service)); + g_thread_join (test->pam_test); + g_socket_service_stop (test->service); + g_assert_false (g_socket_service_is_active (test->service)); + + g_object_unref (test->address); + g_unlink (test->control_path); + g_free (test->control_path); + g_free (test); + g_rmdir (dir_path); + + g_printf ("Teardown completed\n"); +} + +static void * +pam_auth_tests (gpointer data) +{ + Test *test = data; + enum pamtest_err ret; + + g_printf ("Executing PAM auth tests\n"); + + const char *auth_tokens[] = { + "password", + NULL + }; + + struct pamtest_conv_data conv_data = { + .in_echo_off = auth_tokens + }; + + struct pam_testcase tests[] = { + pam_test (PAMTEST_AUTHENTICATE, PAM_SUCCESS) + }; + + g_assert_true (g_socket_service_is_active (test->service)); + ret = run_pamtest (SERVICE, g_get_user_name (), &conv_data, tests, NULL); + g_assert_cmpint (ret, ==, PAMTEST_ERR_OK); + + return NULL; +} + +static void +mock_service (void) +{ + GError *error = NULL; + Test *test; + + g_printf ("Starting mock service\n"); + + test = g_malloc (sizeof (Test)); + test->success = FALSE; + + g_mkdtemp (dir_path); + g_setenv ("GNOME_KEYRING_CONTROL", dir_path, TRUE); + test->control_path = g_strconcat (dir_path, "/control", NULL); + + test->address = g_unix_socket_address_new (test->control_path); + test->service = g_socket_service_new (); + g_socket_service_stop (test->service); + g_assert_false (g_socket_service_is_active (test->service)); + + g_signal_connect (test->service, + "incoming", + G_CALLBACK (is_bytes_exchanged), + test); + + g_socket_listener_add_address (G_SOCKET_LISTENER (test->service), + test->address, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + NULL, + NULL, + &error); + g_assert_no_error (error); + + g_socket_service_start (test->service); + g_assert_true (g_socket_service_is_active (test->service)); + + test->pam_test = g_thread_new ("pam_test", pam_auth_tests, test); + + do + g_main_context_iteration (NULL, TRUE); + while (!test->success); + + teardown (test); +} + +int +main(int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/pam/test_pam_authtok", mock_service); + + return g_test_run(); +}