diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 10cc3e3..ded8c44 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
@@ -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/.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
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],
)
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..8413b58
--- /dev/null
+++ b/pam/meson.build
@@ -0,0 +1,45 @@
+# 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: '',
+)
+
+# 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();
+}