summaryrefslogtreecommitdiff
path: root/modules/pam_timestamp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/pam_timestamp')
-rw-r--r--modules/pam_timestamp/Makefile.am47
-rw-r--r--modules/pam_timestamp/README.xml46
-rw-r--r--modules/pam_timestamp/hmacfile.c157
-rw-r--r--modules/pam_timestamp/hmacsha1.c293
-rw-r--r--modules/pam_timestamp/hmacsha1.h15
-rw-r--r--modules/pam_timestamp/pam_timestamp.8.xml189
-rw-r--r--modules/pam_timestamp/pam_timestamp.c816
-rw-r--r--modules/pam_timestamp/pam_timestamp_check.8.xml208
-rw-r--r--modules/pam_timestamp/pam_timestamp_check.c42
-rw-r--r--modules/pam_timestamp/sha1.c254
-rw-r--r--modules/pam_timestamp/sha1.h60
-rwxr-xr-xmodules/pam_timestamp/tst-pam_timestamp2
12 files changed, 2129 insertions, 0 deletions
diff --git a/modules/pam_timestamp/Makefile.am b/modules/pam_timestamp/Makefile.am
new file mode 100644
index 00000000..51a3c215
--- /dev/null
+++ b/modules/pam_timestamp/Makefile.am
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2005 Thorsten Kukuk <kukuk@suse.de>
+# Copyright (c) 2005 Red Hat, Inc.
+#
+
+CLEANFILES = *~
+
+XMLS = README.xml pam_timestamp.8.xml pam_timestamp_check.8.xml
+man_MANS = pam_timestamp.8 pam_timestamp_check.8
+TESTS = tst-pam_timestamp
+
+EXTRA_DIST = $(man_MANS) hmactest.c $(XMLS) $(TESTS)
+
+securelibdir = $(SECUREDIR)
+secureconfdir = $(SCONFIGDIR)
+
+noinst_HEADERS = hmacsha1.h sha1.h
+
+AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include
+
+pam_timestamp_la_LDFLAGS = -no-undefined -avoid-version -module $(AM_LDFLAGS)
+pam_timestamp_la_LIBADD = -L$(top_builddir)/libpam -lpam
+if HAVE_VERSIONING
+ pam_timestamp_la_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map
+endif
+
+securelib_LTLIBRARIES = pam_timestamp.la
+sbin_PROGRAMS = pam_timestamp_check
+
+pam_timestamp_la_SOURCES = pam_timestamp.c hmacsha1.c sha1.c
+pam_timestamp_la_CFLAGS = $(AM_CFLAGS)
+
+pam_timestamp_check_SOURCES = pam_timestamp_check.c
+pam_timestamp_check_CFLAGS = $(AM_CFLAGS) @PIE_CFLAGS@
+pam_timestamp_check_LDADD = -L$(top_builddir)/libpam -lpam
+pam_timestamp_check_LDFLAGS = @PIE_LDFLAGS@
+
+hmacfile_SOURCES = hmacfile.c hmacsha1.c sha1.c
+hmacfile_LDADD = -L$(top_builddir)/libpam -lpam
+
+if ENABLE_REGENERATE_MAN
+noinst_DATA = README
+README: pam_timestamp.8.xml
+-include $(top_srcdir)/Make.xml.rules
+endif
+
+noinst_PROGRAMS = hmacfile
diff --git a/modules/pam_timestamp/README.xml b/modules/pam_timestamp/README.xml
new file mode 100644
index 00000000..5b72deb1
--- /dev/null
+++ b/modules/pam_timestamp/README.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding='UTF-8'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+"http://www.docbook.org/xml/4.3/docbookx.dtd"
+[
+<!--
+<!ENTITY pamaccess SYSTEM "pam_timestamp.8.xml">
+-->
+]>
+
+<article>
+
+ <articleinfo>
+
+ <title>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+ href="pam_timestamp.8.xml" xpointer='xpointer(//refnamediv[@id = "pam_timestamp-name"]/*)'/>
+ </title>
+
+ </articleinfo>
+
+ <section>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+ href="pam_timestamp.8.xml" xpointer='xpointer(//refsect1[@id = "pam_timestamp-description"]/*)'/>
+ </section>
+
+ <section>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+ href="pam_timestamp.8.xml" xpointer='xpointer(//refsect1[@id = "pam_timestamp-options"]/*)'/>
+ </section>
+
+ <section>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+ href="pam_timestamp.8.xml" xpointer='xpointer(//refsect1[@id = "pam_timestamp-notes"]/*)'/>
+ </section>
+
+ <section>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+ href="pam_timestamp.8.xml" xpointer='xpointer(//refsect1[@id = "pam_timestamp-examples"]/*)'/>
+ </section>
+
+ <section>
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
+ href="pam_timestamp.8.xml" xpointer='xpointer(//refsect1[@id = "pam_timestamp-author"]/*)'/>
+ </section>
+
+</article>
diff --git a/modules/pam_timestamp/hmacfile.c b/modules/pam_timestamp/hmacfile.c
new file mode 100644
index 00000000..963f6f54
--- /dev/null
+++ b/modules/pam_timestamp/hmacfile.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2003,2004 Red Hat, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "hmacsha1.h"
+
+static void
+testvectors(void)
+{
+ void *hmac;
+ size_t hmac_len;
+ size_t i, j;
+ char hex[3];
+ struct vector {
+ const char *key;
+ int key_len;
+ const char *data;
+ int data_len;
+ const char *hmac;
+ } vectors[] = {
+ {
+ "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", 20,
+ "Hi There", 8,
+ "b617318655057264e28bc0b6fb378c8ef146be00",
+ },
+
+ {
+ "Jefe", 4,
+ "what do ya want for nothing?", 28,
+ "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79",
+ },
+
+ {
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", 20,
+ "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd", 50,
+ "125d7342b9ac11cd91a39af48aa17b4f63f175d3",
+ },
+
+ {
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", 25,
+ "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd",
+ 50,
+ "4c9007f4026250c6bc8414f9bf50c86c2d7235da",
+ },
+
+ {
+ "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c", 20,
+ "Test With Truncation", 20,
+ "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04",
+ },
+
+ {
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+ 80,
+ "Test Using Larger Than Block-Size Key - Hash Key First", 54,
+ "aa4ae5e15272d00e95705637ce8a3b55ed402112",
+ },
+
+ {
+ "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+ 80,
+ "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", 73,
+ "e8e99d0f45237d786d6bbaa7965c7808bbff1a91",
+ },
+ };
+ for (i = 0; i < sizeof(vectors) / sizeof(vectors[0]); i++) {
+ hmac = NULL;
+ hmac_len = 0;
+ hmac_sha1_generate(&hmac, &hmac_len,
+ vectors[i].key, vectors[i].key_len,
+ vectors[i].data, vectors[i].data_len);
+ if (hmac != NULL) {
+ unsigned char *hmacc = hmac;
+ for (j = 0; j < hmac_len; j++) {
+ snprintf(hex, sizeof(hex), "%02x",
+ hmacc[j] & 0xff);
+ if (strncasecmp(hex,
+ vectors[i].hmac + 2 * j,
+ 2) != 0) {
+ printf("Incorrect result for vector %lu\n", i + 1);
+ exit(1);
+
+ }
+ }
+ free(hmac);
+ } else {
+ printf("Error in vector %lu.\n", i + 1);
+ exit(1);
+ }
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ void *hmac;
+ size_t maclen;
+ const char *keyfile;
+ int i;
+ size_t j;
+
+ testvectors();
+
+ keyfile = argv[1];
+ for (i = 2; i < argc; i++) {
+ hmac_sha1_generate_file(NULL, &hmac, &maclen, keyfile, -1, -1,
+ argv[i], strlen(argv[i]));
+ if (hmac != NULL) {
+ unsigned char *hmacc = hmac;
+ for (j = 0; j < maclen; j++) {
+ printf("%02x", hmacc[j] & 0xff);
+ }
+ printf(" %s\n", argv[i]);
+ free(hmac);
+ }
+ }
+ return 0;
+}
diff --git a/modules/pam_timestamp/hmacsha1.c b/modules/pam_timestamp/hmacsha1.c
new file mode 100644
index 00000000..5b3774ff
--- /dev/null
+++ b/modules/pam_timestamp/hmacsha1.c
@@ -0,0 +1,293 @@
+/* An implementation of HMAC using SHA-1.
+ *
+ * Copyright (c) 2003 Red Hat, Inc.
+ * Written by Nalin Dahyabhai <nalin@redhat.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ *
+ */
+/* See RFC 2104 for descriptions. */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <security/pam_ext.h>
+#include "hmacsha1.h"
+#include "sha1.h"
+
+#define MINIMUM_KEY_SIZE SHA1_OUTPUT_SIZE
+#define MAXIMUM_KEY_SIZE SHA1_BLOCK_SIZE
+
+static void
+hmac_key_create(pam_handle_t *pamh, const char *filename, size_t key_size,
+ uid_t owner, gid_t group)
+{
+ int randfd, keyfd, i;
+ size_t count;
+ char *key;
+
+ /* Open the destination file. */
+ keyfd = open(filename,
+ O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+ if (keyfd == -1) {
+ pam_syslog(pamh, LOG_ERR, "Cannot create %s: %m", filename);
+ return;
+ }
+
+
+ if (fchown(keyfd, owner, group) == -1) {
+ pam_syslog(pamh, LOG_ERR, "Cannot chown %s: %m", filename);
+ return;
+ }
+
+ /* Open the random device to get key data. */
+ randfd = open("/dev/urandom", O_RDONLY);
+ if (randfd == -1) {
+ pam_syslog(pamh, LOG_ERR, "Cannot open /dev/urandom: %m");
+ close(keyfd);
+ return;
+ }
+
+ /* Read random data for use as the key. */
+ key = malloc(key_size);
+ count = 0;
+ if (!key) {
+ close(keyfd);
+ close(randfd);
+ return;
+ }
+ while (count < key_size) {
+ i = read(randfd, key + count, key_size - count);
+ if ((i == 0) || (i == -1)) {
+ break;
+ }
+ count += i;
+ }
+
+ close(randfd);
+
+ /* If we didn't get enough, stop here. */
+ if (count < key_size) {
+ pam_syslog(pamh, LOG_ERR, "Short read on random device");
+ memset(key, 0, key_size);
+ free(key);
+ close(keyfd);
+ return;
+ }
+
+ /* Now write the key. */
+ count = 0;
+ while (count < key_size) {
+ i = write(keyfd, key + count, key_size - count);
+ if ((i == 0) || (i == -1)) {
+ break;
+ }
+ count += i;
+ }
+ memset(key, 0, key_size);
+ free(key);
+ close(keyfd);
+}
+
+static void
+hmac_key_read(pam_handle_t *pamh, const char *filename, size_t default_key_size,
+ uid_t owner, gid_t group,
+ void **key, size_t *key_size)
+{
+ char *tmp;
+ int keyfd, i, count;
+ struct stat st;
+
+ tmp = NULL;
+ *key = NULL;
+ *key_size = 0;
+
+ /* Try to open the key file. */
+ keyfd = open(filename, O_RDONLY);
+ if (keyfd == -1) {
+ /* No such thing? Create it. */
+ if (errno == ENOENT) {
+ hmac_key_create(pamh, filename, default_key_size,
+ owner, group);
+ keyfd = open(filename, O_RDONLY);
+ } else {
+ pam_syslog(pamh, LOG_ERR, "Cannot open %s: %m", filename);
+ }
+ if (keyfd == -1)
+ return;
+ }
+
+ /* If we failed to open the file, we're done. */
+ if (fstat(keyfd, &st) == -1) {
+ close(keyfd);
+ return;
+ }
+
+ /* Read the contents of the file. */
+ tmp = malloc(st.st_size);
+ if (!tmp) {
+ close(keyfd);
+ return;
+ }
+
+ count = 0;
+ while (count < st.st_size) {
+ i = read(keyfd, tmp + count, st.st_size - count);
+ if ((i == 0) || (i == -1)) {
+ break;
+ }
+ count += i;
+ }
+ close(keyfd);
+
+ /* Require that we got the expected amount of data. */
+ if (count < st.st_size) {
+ memset(tmp, 0, st.st_size);
+ free(tmp);
+ return;
+ }
+
+ /* Pass the key back. */
+ *key = tmp;
+ *key_size = st.st_size;
+}
+
+static void
+xor_block(unsigned char *p, unsigned char byte, size_t length)
+{
+ size_t i;
+ for (i = 0; i < length; i++) {
+ p[i] = p[i] ^ byte;
+ }
+}
+
+void
+hmac_sha1_generate(void **mac, size_t *mac_length,
+ const void *raw_key, size_t raw_key_size,
+ const void *text, size_t text_length)
+{
+ unsigned char key[MAXIMUM_KEY_SIZE], tmp_key[MAXIMUM_KEY_SIZE];
+ size_t maximum_key_size = SHA1_BLOCK_SIZE,
+ minimum_key_size = SHA1_OUTPUT_SIZE;
+ const unsigned char ipad = 0x36, opad = 0x5c;
+ struct sha1_context sha1;
+ unsigned char inner[SHA1_OUTPUT_SIZE], outer[SHA1_OUTPUT_SIZE];
+
+ *mac = NULL;
+ *mac_length = 0;
+
+#ifndef HMAC_ALLOW_SHORT_KEYS
+ /* If the key is too short, don't bother. */
+ if (raw_key_size < minimum_key_size) {
+ return;
+ }
+#endif
+
+ /* If the key is too long, "compress" it, else copy it and pad it
+ * out with zero bytes. */
+ memset(key, 0, sizeof(key));
+ if (raw_key_size > maximum_key_size) {
+ sha1_init(&sha1);
+ sha1_update(&sha1, raw_key, raw_key_size);
+ sha1_output(&sha1, key);
+ } else {
+ memmove(key, raw_key, raw_key_size);
+ }
+
+ /* Generate the inner sum. */
+ memcpy(tmp_key, key, sizeof(tmp_key));
+ xor_block(tmp_key, ipad, sizeof(tmp_key));
+
+ sha1_init(&sha1);
+ sha1_update(&sha1, tmp_key, sizeof(tmp_key));
+ sha1_update(&sha1, text, text_length);
+ sha1_output(&sha1, inner);
+
+ /* Generate the outer sum. */
+ memcpy(tmp_key, key, sizeof(tmp_key));
+ xor_block(tmp_key, opad, sizeof(tmp_key));
+
+ sha1_init(&sha1);
+ sha1_update(&sha1, tmp_key, sizeof(tmp_key));
+ sha1_update(&sha1, inner, sizeof(inner));
+ sha1_output(&sha1, outer);
+
+ /* We don't need any of the keys any more. */
+ memset(key, 0, sizeof(key));
+ memset(tmp_key, 0, sizeof(tmp_key));
+
+ /* Allocate space to store the output. */
+ *mac_length = sizeof(outer);
+ *mac = malloc(*mac_length);
+ if (*mac == NULL) {
+ *mac_length = 0;
+ return;
+ }
+
+ memcpy(*mac, outer, *mac_length);
+}
+
+void
+hmac_sha1_generate_file(pam_handle_t *pamh, void **mac, size_t *mac_length,
+ const char *keyfile, uid_t owner, gid_t group,
+ const void *text, size_t text_length)
+{
+ void *key;
+ size_t key_length;
+
+ hmac_key_read(pamh, keyfile,
+ MAXIMUM_KEY_SIZE, owner, group,
+ &key, &key_length);
+ if (key == NULL) {
+ *mac = NULL;
+ *mac_length = 0;
+ return;
+ }
+ hmac_sha1_generate(mac, mac_length,
+ key, key_length,
+ text, text_length);
+ memset(key, 0, key_length);
+ free(key);
+}
+
+size_t
+hmac_sha1_size(void)
+{
+ return SHA1_OUTPUT_SIZE;
+}
diff --git a/modules/pam_timestamp/hmacsha1.h b/modules/pam_timestamp/hmacsha1.h
new file mode 100644
index 00000000..200d1d06
--- /dev/null
+++ b/modules/pam_timestamp/hmacsha1.h
@@ -0,0 +1,15 @@
+#ifndef pam_timestamp_hmacfile_h
+#define pam_timestamp_hmacfile_h
+
+#include <sys/types.h>
+#include <security/pam_modules.h>
+
+size_t hmac_sha1_size(void);
+void hmac_sha1_generate(void **mac, size_t *mac_length,
+ const void *key, size_t key_length,
+ const void *text, size_t text_length);
+void hmac_sha1_generate_file(pam_handle_t *pamh, void **mac, size_t *mac_length,
+ const char *keyfile, uid_t owner, gid_t group,
+ const void *text, size_t text_length);
+
+#endif
diff --git a/modules/pam_timestamp/pam_timestamp.8.xml b/modules/pam_timestamp/pam_timestamp.8.xml
new file mode 100644
index 00000000..c96424ab
--- /dev/null
+++ b/modules/pam_timestamp/pam_timestamp.8.xml
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding='UTF-8'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
+
+<refentry id="pam_timestamp">
+
+ <refmeta>
+ <refentrytitle>pam_timestamp</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo class="sectdesc">Linux-PAM Manual</refmiscinfo>
+ </refmeta>
+
+ <refnamediv id="pam_timestamp-name">
+ <refname>pam_timestamp</refname>
+ <refpurpose>Authenticate using cached successful authentication attempts</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis id="pam_timestamp-cmdsynopsis">
+ <command>pam_timestamp.so</command>
+ <arg choice="opt">
+ timestamp_timeout=<replaceable>number</replaceable>
+ </arg>
+ <arg choice="opt">
+ verbose
+ </arg>
+ <arg choice="opt">
+ debug
+ </arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id="pam_timestamp-description">
+
+ <title>DESCRIPTION</title>
+
+ <para>
+ In a nutshell, <emphasis>pam_timestamp</emphasis> caches successful
+authentication attempts, and allows you to use a recent successful attempt as
+the basis for authentication. This is similar mechanism which is used in
+<command>sudo</command>.
+ </para>
+ <para>
+ When an application opens a session using <emphasis>pam_timestamp</emphasis>,
+a timestamp file is created in the <emphasis>timestampdir</emphasis> directory
+for the user. When an application attempts to authenticate the user, a
+<emphasis>pam_timestamp</emphasis> will treat a sufficiently recent timestamp
+file as grounds for succeeding.
+ </para>
+ </refsect1>
+
+ <refsect1 id="pam_timestamp-options">
+
+ <title>OPTIONS</title>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>timestamp_timeout=<replaceable>number</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ How long should pam_timestamp treat timestamp as valid after their
+ last modification date (in seconds). Default is 300 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>verbose</option>
+ </term>
+ <listitem>
+ <para>
+ Attempt to inform the user when access is granted.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>debug</option>
+ </term>
+ <listitem>
+ <para>
+ Turns on debugging messages sent to <citerefentry>
+ <refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum>
+ </citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id="pam_timestamp-types">
+ <title>MODULE TYPES PROVIDED</title>
+ <para>
+ The <option>auth</option> and <option>session</option>
+ module types are provided.
+ </para>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp-return_values'>
+ <title>RETURN VALUES</title>
+ <variablelist>
+ <varlistentry>
+ <term>PAM_AUTH_ERR</term>
+ <listitem>
+ <para>
+ The module was not able to retrive the user name or
+ no valid timestamp file was found.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>PAM_SUCCESS</term>
+ <listitem>
+ <para>
+ Everything was successfull.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>PAM_SESSION_ERR</term>
+ <listitem>
+ <para>
+ Timestamp file could not be created or updated.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp-notes'>
+ <title>NOTES</title>
+ <para>
+ Users can get confused when they are not always asked for passwords when
+running a given program. Some users reflexively begin typing information before
+noticing that it is not being asked for.
+ </para>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp-examples'>
+ <title>EXAMPLES</title>
+ <programlisting>
+auth sufficient pam_timestamp.so verbose
+auth required pam_unix.so
+
+session required pam_unix.so
+session optional pam_timestamp.so
+ </programlisting>
+ </refsect1>
+
+ <refsect1 id="pam_timestamp-files">
+ <title>FILES</title>
+ <variablelist>
+ <varlistentry>
+ <term><filename>/var/run/sudo/...</filename></term>
+ <listitem>
+ <para>timestamp files and directories</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp-see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>pam_timestamp_check</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>pam.conf</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>pam.d</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>
+ </para>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp-author'>
+ <title>AUTHOR</title>
+ <para>
+ pam_tally was written by Nalin Dahyabhai.
+ </para>
+ </refsect1>
+
+</refentry>
+
diff --git a/modules/pam_timestamp/pam_timestamp.c b/modules/pam_timestamp/pam_timestamp.c
new file mode 100644
index 00000000..8a01c6f3
--- /dev/null
+++ b/modules/pam_timestamp/pam_timestamp.c
@@ -0,0 +1,816 @@
+/******************************************************************************
+ * A module for Linux-PAM that will cache authentication results, inspired by
+ * (and implemented with an eye toward being mixable with) sudo.
+ *
+ * Copyright (c) 2002 Red Hat, Inc.
+ * Written by Nalin Dahyabhai <nalin@redhat.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ *
+ */
+
+#define PAM_SM_AUTH
+#define PAM_SM_SESSION
+
+#include "config.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <utmp.h>
+#include <syslog.h>
+#include "hmacsha1.h"
+
+#include <security/pam_modules.h>
+#include <security/_pam_macros.h>
+#include <security/pam_ext.h>
+#include <security/pam_modutil.h>
+
+/* The default timeout we use is 5 minutes, which matches the sudo default
+ * for the timestamp_timeout parameter. */
+#define DEFAULT_TIMESTAMP_TIMEOUT (5 * 60)
+#define MODULE "pam_timestamp"
+#define TIMESTAMPDIR "/var/run/sudo"
+#define TIMESTAMPKEY TIMESTAMPDIR "/_pam_timestamp_key"
+
+/* Various buffers we use need to be at least as large as either PATH_MAX or
+ * LINE_MAX, so choose the larger of the two. */
+#if (LINE_MAX > PATH_MAX)
+#define BUFLEN LINE_MAX
+#else
+#define BUFLEN PATH_MAX
+#endif
+
+/* Return PAM_SUCCESS if the given directory looks "safe". */
+static int
+check_dir_perms(pam_handle_t *pamh, const char *tdir)
+{
+ char scratch[BUFLEN];
+ struct stat st;
+ int i;
+ /* Check that the directory is "safe". */
+ if ((tdir == NULL) || (strlen(tdir) == 0)) {
+ return PAM_AUTH_ERR;
+ }
+ /* Iterate over the path, checking intermediate directories. */
+ memset(scratch, 0, sizeof(scratch));
+ for (i = 0; (tdir[i] != '\0') && (i < (int)sizeof(scratch)); i++) {
+ scratch[i] = tdir[i];
+ if ((scratch[i] == '/') || (tdir[i + 1] == '\0')) {
+ /* We now have the name of a directory in the path, so
+ * we need to check it. */
+ if ((lstat(scratch, &st) == -1) && (errno != ENOENT)) {
+ pam_syslog(pamh, LOG_ERR,
+ "unable to read `%s': %m",
+ scratch);
+ return PAM_AUTH_ERR;
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ pam_syslog(pamh, LOG_ERR,
+ "`%s' is not a directory",
+ scratch);
+ return PAM_AUTH_ERR;
+ }
+ if (S_ISLNK(st.st_mode)) {
+ pam_syslog(pamh, LOG_ERR,
+ "`%s' is a symbolic link",
+ scratch);
+ return PAM_AUTH_ERR;
+ }
+ if (st.st_uid != 0) {
+ pam_syslog(pamh, LOG_ERR,
+ "`%s' owner UID != 0",
+ scratch);
+ return PAM_AUTH_ERR;
+ }
+ if (st.st_gid != 0) {
+ pam_syslog(pamh, LOG_ERR,
+ "`%s' owner GID != 0",
+ scratch);
+ return PAM_AUTH_ERR;
+ }
+ if ((st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
+ pam_syslog(pamh, LOG_ERR,
+ "`%s' permissions are lax",
+ scratch);
+ return PAM_AUTH_ERR;
+ }
+ }
+ }
+ return PAM_SUCCESS;
+}
+
+/* Validate a tty pathname as actually belonging to a tty, and return its base
+ * name if it's valid. */
+static const char *
+check_tty(const char *tty)
+{
+ /* Check that we're not being set up to take a fall. */
+ if ((tty == NULL) || (strlen(tty) == 0)) {
+ return NULL;
+ }
+ /* Pull out the meaningful part of the tty's name. */
+ if (strchr(tty, '/') != NULL) {
+ if (strncmp(tty, "/dev/", 5) != 0) {
+ /* Make sure the device node is actually in /dev/,
+ * noted by Michal Zalewski. */
+ return NULL;
+ }
+ tty = strrchr(tty, '/') + 1;
+ }
+ /* Make sure the tty wasn't actually a directory (no basename). */
+ if (strlen(tty) == 0) {
+ return NULL;
+ }
+ return tty;
+}
+
+/* Determine the right path name for a given user's timestamp. */
+static int
+format_timestamp_name(char *path, size_t len,
+ const char *timestamp_dir,
+ const char *tty,
+ const char *ruser,
+ const char *user)
+{
+ if (strcmp(ruser, user) == 0) {
+ return snprintf(path, len, "%s/%s/%s", timestamp_dir,
+ ruser, tty);
+ } else {
+ return snprintf(path, len, "%s/%s/%s:%s", timestamp_dir,
+ ruser, tty, user);
+ }
+}
+
+/* Check if a given timestamp date, when compared to a current time, fits
+ * within the given interval. */
+static int
+timestamp_good(time_t then, time_t now, time_t interval)
+{
+ if (((now >= then) && ((now - then) < interval)) ||
+ ((now < then) && ((then - now) < (2 * interval)))) {
+ return PAM_SUCCESS;
+ }
+ return PAM_AUTH_ERR;
+}
+
+static int
+check_login_time(const char *ruser, time_t timestamp)
+{
+ struct utmp utbuf, *ut;
+ time_t oldest_login = 0;
+
+ setutent();
+ while(!getutent_r(&utbuf, &ut)) {
+ if (ut->ut_type != USER_PROCESS) {
+ continue;
+ }
+ if (strncmp(ruser, ut->ut_user, sizeof(ut->ut_user) != 0)) {
+ continue;
+ }
+ if (oldest_login == 0 || oldest_login > ut->ut_tv.tv_sec) {
+ oldest_login = ut->ut_tv.tv_sec;
+ }
+ }
+ endutent();
+ if(oldest_login == 0 || timestamp < oldest_login) {
+ return PAM_AUTH_ERR;
+ }
+ return PAM_SUCCESS;
+}
+
+#ifndef PAM_TIMESTAMP_MAIN
+static int
+get_ruser(pam_handle_t *pamh, char *ruserbuf, size_t ruserbuflen)
+{
+ const void *ruser;
+ struct passwd *pwd;
+
+ if (ruserbuf == NULL || ruserbuflen < 1)
+ return -2;
+ /* Get the name of the source user. */
+ if (pam_get_item(pamh, PAM_RUSER, &ruser) != PAM_SUCCESS) {
+ ruser = NULL;
+ }
+ if ((ruser == NULL) || (strlen(ruser) == 0)) {
+ /* Barring that, use the current RUID. */
+ pwd = pam_modutil_getpwuid(pamh, getuid());
+ if (pwd != NULL) {
+ ruser = pwd->pw_name;
+ }
+ }
+ if (ruser == NULL || strlen(ruser) >= ruserbuflen) {
+ *ruserbuf = '\0';
+ return -1;
+ }
+ strcpy(ruserbuf, ruser);
+ return 0;
+}
+
+/* Get the path to the timestamp to use. */
+static int
+get_timestamp_name(pam_handle_t *pamh, int argc, const char **argv,
+ char *path, size_t len)
+{
+ const char *user, *tty;
+ const void *void_tty;
+ const char *tdir = TIMESTAMPDIR;
+ char ruser[BUFLEN];
+ int i, debug = 0;
+
+ /* Parse arguments. */
+ for (i = 0; i < argc; i++) {
+ if (strcmp(argv[i], "debug") == 0) {
+ debug = 1;
+ }
+ }
+ for (i = 0; i < argc; i++) {
+ if (strncmp(argv[i], "timestampdir=", 13) == 0) {
+ tdir = argv[i] + 13;
+ if (debug) {
+ pam_syslog(pamh, LOG_DEBUG,
+ "storing timestamps in `%s'",
+ tdir);
+ }
+ }
+ }
+ i = check_dir_perms(pamh, tdir);
+ if (i != PAM_SUCCESS) {
+ return i;
+ }
+ /* Get the name of the target user. */
+ if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS) {
+ user = NULL;
+ }
+ if ((user == NULL) || (strlen(user) == 0)) {
+ return PAM_AUTH_ERR;
+ }
+ if (debug) {
+ pam_syslog(pamh, LOG_DEBUG, "becoming user `%s'", user);
+ }
+ /* Get the name of the source user. */
+ if (get_ruser(pamh, ruser, sizeof(ruser)) || strlen(ruser) == 0) {
+ return PAM_AUTH_ERR;
+ }
+ if (debug) {
+ pam_syslog(pamh, LOG_DEBUG, "currently user `%s'", ruser);
+ }
+ /* Get the name of the terminal. */
+ if (pam_get_item(pamh, PAM_TTY, &void_tty) != PAM_SUCCESS) {
+ tty = NULL;
+ } else {
+ tty = void_tty;
+ }
+ if ((tty == NULL) || (strlen(tty) == 0)) {
+ tty = ttyname(STDIN_FILENO);
+ if ((tty == NULL) || (strlen(tty) == 0)) {
+ tty = ttyname(STDOUT_FILENO);
+ }
+ if ((tty == NULL) || (strlen(tty) == 0)) {
+ tty = ttyname(STDERR_FILENO);
+ }
+ if ((tty == NULL) || (strlen(tty) == 0)) {
+ /* Match sudo's behavior for this case. */
+ tty = "unknown";
+ }
+ }
+ if (debug) {
+ pam_syslog(pamh, LOG_DEBUG, "tty is `%s'", tty);
+ }
+ /* Snip off all but the last part of the tty name. */
+ tty = check_tty(tty);
+ if (tty == NULL) {
+ return PAM_AUTH_ERR;
+ }
+ /* Generate the name of the file used to cache auth results. These
+ * paths should jive with sudo's per-tty naming scheme. */
+ if (format_timestamp_name(path, len, tdir, tty, ruser, user) >= (int)len) {
+ return PAM_AUTH_ERR;
+ }
+ if (debug) {
+ pam_syslog(pamh, LOG_DEBUG, "using timestamp file `%s'", path);
+ }
+ return PAM_SUCCESS;
+}
+
+/* Tell the user that access has been granted. */
+static void
+verbose_success(pam_handle_t *pamh, long diff)
+{
+ pam_info(pamh, _("Access granted (last access was %ld seconds ago)."), diff);
+}
+
+PAM_EXTERN int
+pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
+{
+ struct stat st;
+ time_t interval = DEFAULT_TIMESTAMP_TIMEOUT;
+ int i, fd, debug = 0, verbose = 0;
+ char path[BUFLEN], *p, *message, *message_end;
+ long tmp;
+ const void *void_service;
+ const char *service;
+ time_t now, then;
+
+ /* Parse arguments. */
+ for (i = 0; i < argc; i++) {
+ if (strcmp(argv[i], "debug") == 0) {
+ debug = 1;
+ }
+ }
+ for (i = 0; i < argc; i++) {
+ if (strncmp(argv[i], "timestamp_timeout=", 18) == 0) {
+ tmp = strtol(argv[i] + 18, &p, 0);
+ if ((p != NULL) && (*p == '\0')) {
+ interval = tmp;
+ if (debug) {
+ pam_syslog(pamh, LOG_DEBUG,
+ "setting timeout to %ld"
+ " seconds", (long)interval);
+ }
+ }
+ } else
+ if (strcmp(argv[i], "verbose") == 0) {
+ verbose = 1;
+ if (debug) {
+ pam_syslog(pamh, LOG_DEBUG,
+ "becoming more verbose");
+ }
+ }
+ }
+
+ if (flags & PAM_SILENT) {
+ verbose = 0;
+ }
+
+ /* Get the name of the timestamp file. */
+ if (get_timestamp_name(pamh, argc, argv,
+ path, sizeof(path)) != PAM_SUCCESS) {
+ return PAM_AUTH_ERR;
+ }
+
+ /* Get the name of the service. */
+ if (pam_get_item(pamh, PAM_SERVICE, &void_service) != PAM_SUCCESS) {
+ service = NULL;
+ } else {
+ service = void_service;
+ }
+ if ((service == NULL) || (strlen(service) == 0)) {
+ service = "(unknown)";
+ }
+
+ /* Open the timestamp file. */
+ fd = open(path, O_RDONLY | O_NOFOLLOW);
+ if (fd == -1) {
+ if (debug) {
+ pam_syslog(pamh, LOG_DEBUG,
+ "cannot open timestamp `%s': %m",
+ path);
+ }
+ return PAM_AUTH_ERR;
+ }
+
+ if (fstat(fd, &st) == 0) {
+ int count;
+ void *mac;
+ size_t maclen;
+ char ruser[BUFLEN];
+
+ /* Check that the file is owned by the superuser. */
+ if ((st.st_uid != 0) || (st.st_gid != 0)) {
+ pam_syslog(pamh, LOG_ERR, "timestamp file `%s' is "
+ "not owned by root", path);
+ close(fd);
+ return PAM_AUTH_ERR;
+ }
+
+ /* Check that the file is a normal file. */
+ if (!(S_ISREG(st.st_mode))) {
+ pam_syslog(pamh, LOG_ERR, "timestamp file `%s' is "
+ "not a regular file", path);
+ close(fd);
+ return PAM_AUTH_ERR;
+ }
+
+ /* Check that the file is the expected size. */
+ if (st.st_size == 0) {
+ /* Invalid, but may have been created by sudo. */
+ close(fd);
+ return PAM_AUTH_ERR;
+ }
+ if (st.st_size !=
+ (off_t)(strlen(path) + 1 + sizeof(then) + hmac_sha1_size())) {
+ pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' "
+ "appears to be corrupted", path);
+ close(fd);
+ return PAM_AUTH_ERR;
+ }
+
+ /* Read the file contents. */
+ message = malloc(st.st_size);
+ count = 0;
+ if (!message) {
+ close(fd);
+ return PAM_BUF_ERR;
+ }
+ while (count < st.st_size) {
+ i = read(fd, message + count, st.st_size - count);
+ if ((i == 0) || (i == -1)) {
+ break;
+ }
+ count += i;
+ }
+ if (count < st.st_size) {
+ pam_syslog(pamh, LOG_NOTICE, "error reading timestamp "
+ "file `%s': %m", path);
+ close(fd);
+ free(message);
+ return PAM_AUTH_ERR;
+ }
+ message_end = message + strlen(path) + 1 + sizeof(then);
+
+ /* Regenerate the MAC. */
+ hmac_sha1_generate_file(pamh, &mac, &maclen, TIMESTAMPKEY, 0, 0,
+ message, message_end - message);
+ if ((mac == NULL) ||
+ (memcmp(path, message, strlen(path)) != 0) ||
+ (memcmp(mac, message_end, maclen) != 0)) {
+ pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' is "
+ "corrupted", path);
+ close(fd);
+ free(message);
+ return PAM_AUTH_ERR;
+ }
+ free(mac);
+ memmove(&then, message + strlen(path) + 1, sizeof(then));
+ free(message);
+
+ /* Check oldest login against timestamp */
+ if (get_ruser(pamh, ruser, sizeof(ruser)))
+ {
+ close(fd);
+ return PAM_AUTH_ERR;
+ }
+ if (check_login_time(ruser, then) != PAM_SUCCESS)
+ {
+ pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' is "
+ "older than oldest login, disallowing "
+ "access to %s for user %s",
+ path, service, ruser);
+ close(fd);
+ return PAM_AUTH_ERR;
+ }
+
+ /* Compare the dates. */
+ now = time(NULL);
+ if (timestamp_good(then, now, interval) == PAM_SUCCESS) {
+ close(fd);
+ pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' is "
+ "only %ld seconds old, allowing access to %s "
+ "for user %s", path, (long) (now - st.st_mtime),
+ service, ruser);
+ if (verbose) {
+ verbose_success(pamh, now - st.st_mtime);
+ }
+ return PAM_SUCCESS;
+ } else {
+ close(fd);
+ pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' has "
+ "unacceptable age (%ld seconds), disallowing "
+ "access to %s for user %s",
+ path, (long) (now - st.st_mtime),
+ service, ruser);
+ return PAM_AUTH_ERR;
+ }
+ }
+ close(fd);
+
+ /* Fail by default. */
+ return PAM_AUTH_ERR;
+}
+
+PAM_EXTERN int
+pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED)
+{
+ return PAM_SUCCESS;
+}
+
+PAM_EXTERN int
+pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv)
+{
+ char path[BUFLEN], subdir[BUFLEN], *text, *p;
+ void *mac;
+ size_t maclen;
+ time_t now;
+ int fd, i, debug = 0;
+
+ /* Parse arguments. */
+ for (i = 0; i < argc; i++) {
+ if (strcmp(argv[i], "debug") == 0) {
+ debug = 1;
+ }
+ }
+
+ /* Get the name of the timestamp file. */
+ if (get_timestamp_name(pamh, argc, argv,
+ path, sizeof(path)) != PAM_SUCCESS) {
+ return PAM_SESSION_ERR;
+ }
+
+ /* Create the directory for the timestamp file if it doesn't already
+ * exist. */
+ for (i = 1; path[i] != '\0'; i++) {
+ if (path[i] == '/') {
+ /* Attempt to create the directory. */
+ strncpy(subdir, path, i);
+ subdir[i] = '\0';
+ if (mkdir(subdir, 0700) == 0) {
+ /* Attempt to set the owner to the superuser. */
+ lchown(subdir, 0, 0);
+ } else {
+ if (errno != EEXIST) {
+ if (debug) {
+ pam_syslog(pamh, LOG_DEBUG,
+ "error creating directory `%s': %m",
+ subdir);
+ }
+ return PAM_SESSION_ERR;
+ }
+ }
+ }
+ }
+
+ /* Generate the message. */
+ text = malloc(strlen(path) + 1 + sizeof(now) + hmac_sha1_size());
+ if (text == NULL) {
+ pam_syslog(pamh, LOG_ERR, "unable to allocate memory: %m");
+ return PAM_SESSION_ERR;
+ }
+ p = text;
+
+ strcpy(text, path);
+ p += strlen(path) + 1;
+
+ now = time(NULL);
+ memmove(p, &now, sizeof(now));
+ p += sizeof(now);
+
+ /* Generate the MAC and append it to the plaintext. */
+ hmac_sha1_generate_file(pamh, &mac, &maclen,
+ TIMESTAMPKEY,
+ 0, 0,
+ text, p - text);
+ if (mac == NULL) {
+ pam_syslog(pamh, LOG_ERR, "failure generating MAC: %m");
+ free(text);
+ return PAM_SESSION_ERR;
+ }
+ memmove(p, mac, maclen);
+ p += maclen;
+ free(mac);
+
+ /* Open the file. */
+ fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd == -1) {
+ pam_syslog(pamh, LOG_ERR, "unable to open `%s': %m", path);
+ free(text);
+ return PAM_SESSION_ERR;
+ }
+
+ /* Attempt to set the owner to the superuser. */
+ fchown(fd, 0, 0);
+
+ /* Write the timestamp to the file. */
+ if (write(fd, text, p - text) != p - text) {
+ pam_syslog(pamh, LOG_ERR, "unable to write to `%s': %m", path);
+ close(fd);
+ free(text);
+ return PAM_SESSION_ERR;
+ }
+
+ /* Close the file and return successfully. */
+ close(fd);
+ free(text);
+ pam_syslog(pamh, LOG_DEBUG, "updated timestamp file `%s'", path);
+ return PAM_SUCCESS;
+}
+
+PAM_EXTERN int
+pam_sm_close_session(pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED)
+{
+ return PAM_SUCCESS;
+}
+
+#ifdef PAM_STATIC
+/* static module data */
+
+struct pam_module _pam_timestamp_modstruct = {
+ "pam_timestamp",
+ pam_sm_authenticate,
+ pam_sm_setcred,
+ NULL,
+ pam_sm_open_session,
+ pam_sm_close_session,
+ NULL
+};
+#endif
+
+
+#else /* PAM_TIMESTAMP_MAIN */
+
+#define USAGE "Usage: %s [[-k] | [-d]] [target user]\n"
+#define CHECK_INTERVAL 7
+
+int
+main(int argc, char **argv)
+{
+ int i, pretval = -1, retval = 0, dflag = 0, kflag = 0;
+ const char *target_user = NULL, *user = NULL, *tty = NULL;
+ struct passwd *pwd;
+ struct timeval tv;
+ fd_set write_fds;
+ char path[BUFLEN];
+ struct stat st;
+
+ /* Check that there's nothing funny going on with stdio. */
+ if ((fstat(STDIN_FILENO, &st) == -1) ||
+ (fstat(STDOUT_FILENO, &st) == -1) ||
+ (fstat(STDERR_FILENO, &st) == -1)) {
+ /* Appropriate the "no controlling tty" error code. */
+ return 3;
+ }
+
+ /* Parse arguments. */
+ while ((i = getopt(argc, argv, "dk")) != -1) {
+ switch (i) {
+ case 'd':
+ dflag++;
+ break;
+ case 'k':
+ kflag++;
+ break;
+ default:
+ fprintf(stderr, USAGE, argv[0]);
+ return 1;
+ break;
+ }
+ }
+
+ /* Bail if both -k and -d are given together. */
+ if ((kflag + dflag) > 1) {
+ fprintf(stderr, USAGE, argv[0]);
+ return 1;
+ }
+
+ /* Check that we're setuid. */
+ if (geteuid() != 0) {
+ fprintf(stderr, "%s must be setuid root\n",
+ argv[0]);
+ retval = 2;
+ }
+
+ /* Check that we have a controlling tty. */
+ tty = ttyname(STDIN_FILENO);
+ if ((tty == NULL) || (strlen(tty) == 0)) {
+ tty = ttyname(STDOUT_FILENO);
+ }
+ if ((tty == NULL) || (strlen(tty) == 0)) {
+ tty = ttyname(STDERR_FILENO);
+ }
+ if ((tty == NULL) || (strlen(tty) == 0)) {
+ tty = "unknown";
+ }
+
+ /* Get the name of the invoking (requesting) user. */
+ pwd = getpwuid(getuid());
+ if (pwd == NULL) {
+ retval = 4;
+ }
+
+ /* Get the name of the target user. */
+ user = strdup(pwd->pw_name);
+ if (user == NULL) {
+ retval = 4;
+ } else {
+ target_user = (optind < argc) ? argv[optind] : user;
+ if ((strchr(target_user, '.') != NULL) ||
+ (strchr(target_user, '/') != NULL) ||
+ (strchr(target_user, '%') != NULL)) {
+ fprintf(stderr, "unknown user: %s\n",
+ target_user);
+ retval = 4;
+ }
+ }
+
+ /* Sanity check the tty to make sure we should be checking
+ * for timestamps which pertain to it. */
+ if (retval == 0) {
+ tty = check_tty(tty);
+ if (tty == NULL) {
+ fprintf(stderr, "invalid tty\n");
+ retval = 6;
+ }
+ }
+
+ do {
+ /* Sanity check the timestamp directory itself. */
+ if (retval == 0) {
+ if (check_dir_perms(NULL, TIMESTAMPDIR) != PAM_SUCCESS) {
+ retval = 5;
+ }
+ }
+
+ if (retval == 0) {
+ /* Generate the name of the timestamp file. */
+ format_timestamp_name(path, sizeof(path), TIMESTAMPDIR,
+ tty, user, target_user);
+ }
+
+ if (retval == 0) {
+ if (kflag) {
+ /* Remove the timestamp. */
+ if (lstat(path, &st) != -1) {
+ retval = unlink(path);
+ }
+ } else {
+ /* Check the timestamp. */
+ if (lstat(path, &st) != -1) {
+ /* Check oldest login against timestamp */
+ if (check_login_time(user, st.st_mtime) != PAM_SUCCESS) {
+ retval = 7;
+ } else if (!timestamp_good(st.st_mtime, time(NULL),
+ DEFAULT_TIMESTAMP_TIMEOUT) == PAM_SUCCESS) {
+ retval = 7;
+ }
+ } else {
+ retval = 7;
+ }
+ }
+ }
+
+ if (dflag > 0) {
+ struct timeval now;
+ /* Send the would-be-returned value to our parent. */
+ signal(SIGPIPE, SIG_DFL);
+ fprintf(stdout, "%d\n", retval);
+ fflush(stdout);
+ /* Wait. */
+ gettimeofday(&now, NULL);
+ tv.tv_sec = CHECK_INTERVAL;
+ /* round the sleep time to get woken up on a whole second */
+ tv.tv_usec = 1000000 - now.tv_usec;
+ if (now.tv_usec < 500000)
+ tv.tv_sec--;
+ FD_ZERO(&write_fds);
+ FD_SET(STDOUT_FILENO, &write_fds);
+ select(STDOUT_FILENO + 1,
+ NULL, NULL, &write_fds,
+ &tv);
+ pretval = retval;
+ retval = 0;
+ }
+ } while (dflag > 0);
+
+ return retval;
+}
+
+#endif
diff --git a/modules/pam_timestamp/pam_timestamp_check.8.xml b/modules/pam_timestamp/pam_timestamp_check.8.xml
new file mode 100644
index 00000000..85484a06
--- /dev/null
+++ b/modules/pam_timestamp/pam_timestamp_check.8.xml
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding='UTF-8'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
+
+<refentry id="pam_timestamp_check">
+
+ <refmeta>
+ <refentrytitle>pam_timestamp_check</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo class="sectdesc">Linux-PAM Manual</refmiscinfo>
+ </refmeta>
+
+ <refnamediv id="pam_timestamp_check-name">
+ <refname>pam_timestamp_check</refname>
+ <refpurpose>Check to see if the default timestamp is valid</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis id="pam_timestamp_check-cmdsynopsis">
+ <command>pam_timestamp_check</command>
+ <arg choice="opt">
+ -k
+ </arg>
+ <arg choice="opt">
+ -d
+ </arg>
+ <arg choice="opt">
+ <replaceable>target_user</replaceable>
+ </arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id="pam_timestamp_check-description">
+
+ <title>DESCRIPTION</title>
+
+ <para>
+ With no arguments <command>pam_timestamp_check</command> will check to
+see if the default timestamp is valid, or optionally remove it.
+ </para>
+ </refsect1>
+
+ <refsect1 id="pam_timestamp_check-options">
+
+ <title>OPTIONS</title>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>-k</option>
+ </term>
+ <listitem>
+ <para>
+ Instead of checking the validity of a timestamp, remove it.
+ This is analogous to sudo's <emphasis>-k</emphasis> option.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-d</option>
+ </term>
+ <listitem>
+ <para>
+ Instead of returning validity using an exit status,
+ loop indefinitely, polling regularly and printing the status on
+ standard output.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option><replaceable>target_user</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ By default <command>pam_timestamp_check</command> checks or removes
+ timestamps generated by <emphasis>pam_timestamp</emphasis> when
+ the user authenticates as herself. When the user authenticates as a
+ different user, the name of the timestamp file changes to
+ accomodate this. <replaceable>target_user</replaceable> allows
+ to specify this user name.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp_check-return_values'>
+ <title>RETURN VALUES</title>
+ <variablelist>
+ <varlistentry>
+ <term>0</term>
+ <listitem>
+ <para>
+ The timestamp is valid.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>2</term>
+ <listitem>
+ <para>
+ The binary is not setuid root.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>3</term>
+ <listitem>
+ <para>
+ Invalid invocation.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>4</term>
+ <listitem>
+ <para>
+ User is unknown.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>5</term>
+ <listitem>
+ <para>
+ Permissions error.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>6</term>
+ <listitem>
+ <para>
+ Invalid controlling tty.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>7</term>
+ <listitem>
+ <para>
+ Timestamp is not valid.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp-notes'>
+ <title>NOTES</title>
+ <para>
+ Users can get confused when they are not always asked for passwords when
+running a given program. Some users reflexively begin typing information before
+noticing that it is not being asked for.
+ </para>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp-examples'>
+ <title>EXAMPLES</title>
+ <programlisting>
+auth sufficient pam_timestamp.so verbose
+auth required pam_unix.so
+
+session required pam_unix.so
+session optional pam_timestamp.so
+ </programlisting>
+ </refsect1>
+
+ <refsect1 id="pam_timestamp-files">
+ <title>FILES</title>
+ <variablelist>
+ <varlistentry>
+ <term><filename>/var/run/sudo/...</filename></term>
+ <listitem>
+ <para>timestamp files and directories</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp-see_also'>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>pam_timestamp_check</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>pam.conf</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>pam.d</refentrytitle><manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>
+ </para>
+ </refsect1>
+
+ <refsect1 id='pam_timestamp-author'>
+ <title>AUTHOR</title>
+ <para>
+ pam_tally was written by Nalin Dahyabhai.
+ </para>
+ </refsect1>
+
+</refentry>
+
diff --git a/modules/pam_timestamp/pam_timestamp_check.c b/modules/pam_timestamp/pam_timestamp_check.c
new file mode 100644
index 00000000..52b5a95a
--- /dev/null
+++ b/modules/pam_timestamp/pam_timestamp_check.c
@@ -0,0 +1,42 @@
+/******************************************************************************
+ * A module for Linux-PAM that will cache authentication results, inspired by
+ * (and implemented with an eye toward being mixable with) sudo.
+ *
+ * Copyright (c) 2002 Red Hat, Inc.
+ * Written by Nalin Dahyabhai <nalin@redhat.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ *
+ */
+
+#define PAM_TIMESTAMP_MAIN 1
+#include "pam_timestamp.c"
diff --git a/modules/pam_timestamp/sha1.c b/modules/pam_timestamp/sha1.c
new file mode 100644
index 00000000..e6705eb5
--- /dev/null
+++ b/modules/pam_timestamp/sha1.c
@@ -0,0 +1,254 @@
+/* Yet another SHA-1 implementation.
+ *
+ * Copyright (c) 2003 Red Hat, Inc.
+ * Written by Nalin Dahyabhai <nalin@redhat.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ *
+ */
+/* See http://www.itl.nist.gov/fipspubs/fip180-1.htm for descriptions. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <endian.h>
+#include <unistd.h>
+#include "sha1.h"
+
+static unsigned char
+padding[SHA1_BLOCK_SIZE] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static u_int32_t
+F(u_int32_t b, u_int32_t c, u_int32_t d)
+{
+ return (b & c) | ((~b) & d);
+}
+
+static u_int32_t
+G(u_int32_t b, u_int32_t c, u_int32_t d)
+{
+ return b ^ c ^ d;
+}
+
+static u_int32_t
+H(u_int32_t b, u_int32_t c, u_int32_t d)
+{
+ return (b & c) | (b & d) | (c & d);
+}
+
+static u_int32_t
+RL(u_int32_t n, u_int32_t s)
+{
+ return (n << s) | (n >> (32 - s));
+}
+
+static u_int32_t
+sha1_round(u_int32_t (*FUNC)(u_int32_t, u_int32_t, u_int32_t),
+ u_int32_t a, u_int32_t b, u_int32_t c, u_int32_t d, u_int32_t e,
+ u_int32_t i, u_int32_t n)
+{
+ return RL(a, 5) + FUNC(b, c, d) + e + i + n;
+}
+
+void
+sha1_init(struct sha1_context *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->a = 0x67452301;
+ ctx->b = 0xefcdab89;
+ ctx->c = 0x98badcfe;
+ ctx->d = 0x10325476;
+ ctx->e = 0xc3d2e1f0;
+}
+
+static void
+sha1_process(struct sha1_context *ctx, u_int32_t buffer[SHA1_BLOCK_SIZE / 4])
+{
+ u_int32_t a, b, c, d, e, temp;
+ u_int32_t data[80];
+ int i;
+
+ for (i = 0; i < 16; i++) {
+ data[i] = htonl(buffer[i]);
+ }
+ for (i = 16; i < 80; i++) {
+ data[i] = RL(data[i - 3] ^ data[i - 8] ^ data[i - 14] ^ data[i - 16], 1);
+ }
+
+ a = ctx->a;
+ b = ctx->b;
+ c = ctx->c;
+ d = ctx->d;
+ e = ctx->e;
+
+ for (i = 0; i < 20; i++) {
+ temp = sha1_round(F, a, b, c, d, e, data[i], 0x5a827999);
+ e = d; d = c; c = RL(b, 30); b = a; a = temp;
+ }
+ for (i = 20; i < 40; i++) {
+ temp = sha1_round(G, a, b, c, d, e, data[i], 0x6ed9eba1);
+ e = d; d = c; c = RL(b, 30); b = a; a = temp;
+ }
+ for (i = 40; i < 60; i++) {
+ temp = sha1_round(H, a, b, c, d, e, data[i], 0x8f1bbcdc);
+ e = d; d = c; c = RL(b, 30); b = a; a = temp;
+ }
+ for (i = 60; i < 80; i++) {
+ temp = sha1_round(G, a, b, c, d, e, data[i], 0xca62c1d6);
+ e = d; d = c; c = RL(b, 30); b = a; a = temp;
+ }
+
+ ctx->a += a;
+ ctx->b += b;
+ ctx->c += c;
+ ctx->d += d;
+ ctx->e += e;
+
+ memset(buffer, 0, sizeof(buffer[0]) * SHA1_BLOCK_SIZE / 4);
+ memset(data, 0, sizeof(data));
+}
+
+void
+sha1_update(struct sha1_context *ctx, const unsigned char *data, size_t length)
+{
+ size_t i = 0, l = length, c, t;
+ u_int32_t count = 0;
+
+ /* Process any pending + data blocks. */
+ while (l + ctx->pending_count >= SHA1_BLOCK_SIZE) {
+ c = ctx->pending_count;
+ t = SHA1_BLOCK_SIZE - c;
+ memcpy(ctx->pending + c, &data[i], t);
+ sha1_process(ctx, (u_int32_t*) ctx->pending);
+ i += t;
+ l -= t;
+ ctx->pending_count = 0;
+ }
+
+ /* Save what's left of the data block as a pending data block. */
+ c = ctx->pending_count;
+ memcpy(ctx->pending + c, &data[i], l);
+ ctx->pending_count += l;
+
+ /* Update the message length. */
+ ctx->count += length;
+
+ /* Update our internal counts. */
+ if (length != 0) {
+ count = ctx->counts[0];
+ ctx->counts[0] += length;
+ if (count >= ctx->counts[0]) {
+ ctx->counts[1]++;
+ }
+ }
+}
+
+size_t
+sha1_output(struct sha1_context *ctx, unsigned char *out)
+{
+ struct sha1_context ctx2;
+
+ /* Output the sum. */
+ if (out != NULL) {
+ u_int32_t c;
+ memcpy(&ctx2, ctx, sizeof(ctx2));
+
+ /* Pad this block. */
+ c = ctx2.pending_count;
+ memcpy(ctx2.pending + c,
+ padding, SHA1_BLOCK_SIZE - c);
+
+ /* Do we need to process two blocks now? */
+ if (c >= (SHA1_BLOCK_SIZE - (sizeof(u_int32_t) * 2))) {
+ /* Process this block. */
+ sha1_process(&ctx2,
+ (u_int32_t*) ctx2.pending);
+ /* Set up another block. */
+ ctx2.pending_count = 0;
+ memset(ctx2.pending, 0, SHA1_BLOCK_SIZE);
+ ctx2.pending[0] =
+ (c == SHA1_BLOCK_SIZE) ? 0x80 : 0;
+ }
+
+ /* Process the final block. */
+ ctx2.counts[1] <<= 3;
+ if (ctx2.counts[0] >> 29) {
+ ctx2.counts[1] |=
+ (ctx2.counts[0] >> 29);
+ }
+ ctx2.counts[0] <<= 3;
+ ctx2.counts[0] = htonl(ctx2.counts[0]);
+ ctx2.counts[1] = htonl(ctx2.counts[1]);
+ memcpy(ctx2.pending + 56,
+ &ctx2.counts[1], sizeof(u_int32_t));
+ memcpy(ctx2.pending + 60,
+ &ctx2.counts[0], sizeof(u_int32_t));
+ sha1_process(&ctx2, (u_int32_t*) ctx2.pending);
+
+ /* Output the data. */
+ out[ 3] = (ctx2.a >> 0) & 0xff;
+ out[ 2] = (ctx2.a >> 8) & 0xff;
+ out[ 1] = (ctx2.a >> 16) & 0xff;
+ out[ 0] = (ctx2.a >> 24) & 0xff;
+
+ out[ 7] = (ctx2.b >> 0) & 0xff;
+ out[ 6] = (ctx2.b >> 8) & 0xff;
+ out[ 5] = (ctx2.b >> 16) & 0xff;
+ out[ 4] = (ctx2.b >> 24) & 0xff;
+
+ out[11] = (ctx2.c >> 0) & 0xff;
+ out[10] = (ctx2.c >> 8) & 0xff;
+ out[ 9] = (ctx2.c >> 16) & 0xff;
+ out[ 8] = (ctx2.c >> 24) & 0xff;
+
+ out[15] = (ctx2.d >> 0) & 0xff;
+ out[14] = (ctx2.d >> 8) & 0xff;
+ out[13] = (ctx2.d >> 16) & 0xff;
+ out[12] = (ctx2.d >> 24) & 0xff;
+
+ out[19] = (ctx2.e >> 0) & 0xff;
+ out[18] = (ctx2.e >> 8) & 0xff;
+ out[17] = (ctx2.e >> 16) & 0xff;
+ out[16] = (ctx2.e >> 24) & 0xff;
+ }
+
+ return SHA1_OUTPUT_SIZE;
+}
diff --git a/modules/pam_timestamp/sha1.h b/modules/pam_timestamp/sha1.h
new file mode 100644
index 00000000..667b87ca
--- /dev/null
+++ b/modules/pam_timestamp/sha1.h
@@ -0,0 +1,60 @@
+/* Yet another SHA-1 implementation.
+ *
+ * Copyright (c) 2003 Red Hat, Inc.
+ * Written by Nalin Dahyabhai <nalin@redhat.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ *
+ */
+#ifndef pam_timestamp_sha1_h
+#define pam_timestamp_sha1_h
+
+#include <sys/types.h>
+
+#define SHA1_BLOCK_SIZE 64
+
+struct sha1_context {
+ size_t count;
+ unsigned char pending[SHA1_BLOCK_SIZE];
+ u_int32_t counts[2];
+ size_t pending_count;
+ u_int32_t a, b, c, d, e;
+};
+
+#define SHA1_OUTPUT_SIZE 20
+
+void sha1_init(struct sha1_context *ctx);
+void sha1_update(struct sha1_context *ctx,
+ const unsigned char *data, size_t length);
+size_t sha1_output(struct sha1_context *ctx, unsigned char *out);
+
+#endif
diff --git a/modules/pam_timestamp/tst-pam_timestamp b/modules/pam_timestamp/tst-pam_timestamp
new file mode 100755
index 00000000..1d425b83
--- /dev/null
+++ b/modules/pam_timestamp/tst-pam_timestamp
@@ -0,0 +1,2 @@
+#!/bin/sh
+../../tests/tst-dlopen .libs/pam_timestamp.so