summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNalin Dahyabhai <nalin@dahyabhai.net>2010-04-29 12:38:03 -0400
committerNalin Dahyabhai <nalin@dahyabhai.net>2010-04-29 12:38:03 -0400
commit2d98af25d3968223e27c3f438013bd12beaffe1f (patch)
tree0b0aaff548b0175397600fb900323a5ddbbffd14
parent25304d87f30ebcc5f6382f89d5f99b498dd7ceef (diff)
downloadpam_rps-2d98af25d3968223e27c3f438013bd12beaffe1f.tar.gz
pam_rps-2d98af25d3968223e27c3f438013bd12beaffe1f.tar.xz
pam_rps-2d98af25d3968223e27c3f438013bd12beaffe1f.zip
- okay, the basic stuff is working again
- add "best_of" - add options to control conversation semantics - add "echo" option
-rw-r--r--src/Makefile.am3
-rw-r--r--src/main.c128
-rw-r--r--src/pam_rps.8.in33
-rw-r--r--src/pam_rps.c349
4 files changed, 450 insertions, 63 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index a65ac53..94f5abe 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,3 +2,6 @@ man_MANS = pam_rps.8
securitydir = $(libdir)/security
security_LTLIBRARIES = pam_rps.la
pam_rps_la_LDFLAGS = -module -avoid-version -export-symbols-regex='pam_sm_.*'
+noinst_PROGRAMS = pam_rps
+pam_rps_SOURCES = main.c
+pam_rps_LDADD = pam_rps.lo
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..2dc54a0
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,128 @@
+/*
+ Copyright (c) 2003,2010 Red Hat, Inc.
+
+ 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.
+
+ * Neither the name of Red Hat, Inc., nor the names of its
+ contributors may 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <security/pam_modules.h>
+#include <security/pam_appl.h>
+
+static int
+converse(int n, const struct pam_message **msgs,
+ struct pam_response **resp, void *p)
+{
+ const struct pam_message *msg;
+ char buf[LINE_MAX], *s;
+ int i;
+ if (n > 1) {
+ printf("Best of %d:\n", n);
+ }
+ *resp = malloc(sizeof(struct pam_response) * n);
+ if (*resp == NULL) {
+ return PAM_BUF_ERR;
+ }
+ memset(*resp, 0, sizeof(resp[0]) * n);
+ for (i = 0; i < n; i++) {
+ if (p != NULL) {
+ /* Linux-PAM ABI. */
+ msg = &((*msgs)[i]);
+ } else {
+ /* Sun ABI. */
+ msg = msgs[i];
+ }
+ if (msg->msg_style == PAM_PROMPT_ECHO_OFF) {
+ s = getpass(msg->msg);
+ if (s == NULL) {
+ s = "";
+ }
+ (*resp)[i].resp_retcode = 0;
+ (*resp)[i].resp = strdup(s);
+ } else {
+ fprintf(stderr, "%s", msg->msg);
+ s = fgets(buf, sizeof(buf), stdin);
+ if (s == NULL) {
+ s = "";
+ }
+ s[strcspn(s, "\r\n")] = '\0';
+ (*resp)[i].resp_retcode = 0;
+ (*resp)[i].resp = strdup(s);
+ }
+ }
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ pam_handle_t *pamh;
+ struct pam_conv conv = {
+ .conv = converse,
+ .appdata_ptr = &conv,
+ };
+ /* We'll use "is the appdata not NULL" to indicate Linux-style. */
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "sun") == 0) {
+ conv.appdata_ptr = NULL;
+ }
+ if (strcmp(argv[i], "linux") == 0) {
+ conv.appdata_ptr = &conv;
+ }
+ }
+ /* Start up PAM. We're not going to use it, though. */
+ i = pam_start("login", NULL, &conv, &pamh);
+ if (i != PAM_SUCCESS) {
+ printf("error starting PAM\n");
+ return i;
+ }
+ /* Call our authentication function directly. */
+ i = pam_sm_authenticate(pamh, 0, argc - 1, (const char **) (argv + 1));
+ if (i != PAM_SUCCESS) {
+ fprintf(stderr, "Error: %s\n", pam_strerror(pamh, i));
+ } else {
+ fprintf(stderr, "Succeeded.\n");
+ }
+ /* And we're done. */
+ pam_end(pamh, i);
+ return i;
+}
diff --git a/src/pam_rps.8.in b/src/pam_rps.8.in
index a6e1990..111236e 100644
--- a/src/pam_rps.8.in
+++ b/src/pam_rps.8.in
@@ -1,29 +1,42 @@
-.TH pam_rps 8 2010/4/20 "Red Hat Linux" "System Administrator's Manual"
+.TH pam_rps 8 2010/4/29 "Red Hat Linux" "System Administrator's Manual"
.SH NAME
pam_rps \- challenge-response authentication
.SH SYNOPSIS
-.B auth sufficient pam_rps.so
+.B auth required pam_rps.so
.SH DESCRIPTION
pam_rps.so is designed to provide an easy-to-test challenge-response
authentication mechanism for PAM-enabled applications.
Without pam_rps, successful authentication can only occur for a user if the
-user has previously established an authentication token for use with the
-server. Using pam_rps removes this limitation.
+user has previously established some sort of means of authenticating to the
+local system or a trusted server. Using pam_rps removes this limitation.
.SH ARGUMENTS
.IP debug
-Enable module debugging. The module will log its progress to syslog.
-.IP throw=\fInumber\fP
-The challenge issued to the user is derived from a random number. This
-argument allows the administrator to control which challenge will be presented
-to the user. This argument is meant for use only when debugging.
+Enable module debugging. The module will log its progress to syslog with
+"debug" priority.
+
+.IP best_of=\fInumber\fP
+The user will be issued multiple challenges, and must "win" against more than
+one half of them. If the supplied number is not odd, it will be incremented.
+
+.IP echo
+Signal to the calling application that the user should be able to see
+the response as it is being typed in.
+
+.IP sun
+Follow the conversation conventions consistent with Solaris PAM and not
+Linux-PAM. The default is to attempt to accomodate both.
+
+.IP linux
+Follow the conversation conventions consistent with Linux-PAM and not
+Solaris PAM. The default is to attempt to accomodate both.
.SH NOTES
-Never use this module.
+Do not use this module in production.
.SH BUGS
Let's hope not, but if you find any, please report them via the "Bug Track"
diff --git a/src/pam_rps.c b/src/pam_rps.c
index b3e6ddb..0554f24 100644
--- a/src/pam_rps.c
+++ b/src/pam_rps.c
@@ -43,80 +43,323 @@
#include <unistd.h>
#include <security/pam_modules.h>
-#include <security/_pam_macros.h>
-#include <security/pam_ext.h>
+
+/* These are the rules. */
+struct beater {
+ const char *what, *how;
+};
+struct beater what_beats_rock[] = {
+ {"paper", "covers"},
+};
+struct beater what_beats_paper[] = {
+ {"scissors", "cuts"},
+};
+struct beater what_beats_scissors[] = {
+ {"rock", "blunts"},
+};
+struct rule {
+ const char *challenge;
+ struct beater *beaters;
+};
+struct rule rules[] = {
+ {"rock: ", what_beats_rock},
+ {"paper: ", what_beats_paper},
+ {"scissors: ", what_beats_scissors},
+};
+
+/* Wrappers for pam_get_item() to get some measure of type-safety. */
+static int
+get_text_item(pam_handle_t *pamh, int item, const char **value)
+{
+ return pam_get_item(pamh, item, (const void **) value);
+}
+
+static int
+get_conv_item(pam_handle_t *pamh, const struct pam_conv **value)
+{
+ return pam_get_item(pamh, PAM_CONV, (const void **) value);
+}
+
+/* Read a random byte, or return zero. */
+static int
+get_random_byte(void)
+{
+ unsigned char c;
+ int ret, fd;
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd != -1) {
+ c = 0;
+ do {
+ ret = read(fd, &c, 1);
+ } while ( ((ret == 1) && (c == 0xff)) ||
+ ((ret == -1) && (errno == EINTR)) );
+ /* We exclude 0xff here to avoid a variation on
+ * Bleichenbacher's attack. */
+ ret = c & 0xff;
+ close(fd);
+ } else {
+ ret = 0;
+ }
+ return ret;
+}
+
+/* Select the challenge. */
+static void
+fill(struct pam_message *msg, int style, int n_rules)
+{
+ memset(msg, 0, sizeof(*msg));
+ msg->msg_style = style;
+ msg->msg = rules[get_random_byte() % n_rules].challenge;
+}
+
+static void
+won(int loglevel, const char *challenge, struct beater *beater,
+ const char *response)
+{
+ if (loglevel != 0) {
+ syslog(loglevel, "%s %s %s", response, beater->how, challenge);
+ }
+}
+
+static void
+lost(int loglevel, const char *challenge, const char *response)
+{
+ if (loglevel != 0) {
+ syslog(loglevel, "%s did not beat %s", response, challenge);
+ }
+}
int
pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
- const char *values[] = {
- "\x72\x6f\x63\x6b",
- "\x70\x61\x70\x65\x72",
- "\x73\x63\x69\x73\x73\x6f\x72\x73"};
- char prompt_text[32] = "";
- const char *want = "";
- char *response = NULL;
+ const struct pam_conv *conv;
+ struct pam_response *responses;
+ struct pam_message **msgs, *msg_array, *prompt;
+ const struct pam_message **cmsgs;
+ const char *service;
+ int debug = 0, loglevel, i, j, k, score, best_of, prompt_style;
+ int abi_sun, abi_linux, n_rules, n_winners;
- int debug = 0;
+#ifdef LOG_AUTHPRIV
+ loglevel = LOG_AUTHPRIV | LOG_NOTICE;
+#else
+ loglevel = LOG_AUTH | LOG_NOTICE;
+#endif
- int ret, fd, r, i;
- unsigned char c;
+ /* Retrieve the PAM items that we care about. */
+ service = NULL;
+ i = get_text_item(pamh, PAM_SERVICE, &service);
+ if ((i != PAM_SUCCESS) || (service == NULL)) {
+ /* We only want the service name for logging purposes, but an
+ * error retrieving it would indicate a pretty serious problem
+ * elsewhere. */
+ syslog(loglevel, "error retrieving PAM service name: %s",
+ pam_strerror(pamh, i));
+ return i;
+ }
+ conv = NULL;
+ i = get_conv_item(pamh, &conv);
+ if ((i != PAM_SUCCESS) || (conv == NULL)) {
+ /* We *need* a conversation function. */
+ syslog(loglevel,
+ "error retrieving PAM conversation callback: %s",
+ pam_strerror(pamh, i));
+ return i;
+ }
+
+ /* Parse our debug flag. */
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0) {
- debug = 1;
- break;
+#ifdef LOG_AUTHPRIV
+ debug = LOG_AUTHPRIV | LOG_NOTICE;
+#else
+ debug = LOG_AUTH | LOG_NOTICE;
+#endif
}
}
- r = -1;
+ /* Parse our arguments. */
+ prompt_style = PAM_PROMPT_ECHO_OFF;
+ best_of = 1;
+ abi_sun = 1;
+ abi_linux = 1;
+ n_rules = 3;
+ n_winners = 1;
for (i = 0; i < argc; i++) {
- if (strncmp(argv[i], "throw=", 6) == 0) {
- r = atol(argv[i] + 6) % 3;
- break;
- }
- }
- if (r == -1) {
- r = 0;
- fd = open("/dev/urandom", O_RDONLY);
- if (fd != -1) {
- c = 0;
- do {
- ret = read(fd, &c, 1);
- } while ( ((ret == 1) && (c == 0xff)) ||
- ((ret == -1) && (errno == EINTR)) );
- /* We drop 0xff here to avoid a variation on
- * Bleichenbacher's attack. */
- r = c / 85;
- close(fd);
- } else {
- /* Something is wrong with /dev/urandom */
- return PAM_CONV_ERR;
+ /* Force Linux-PAM-style semantics. */
+ if (strcmp(argv[i], "linux") == 0) {
+ if (debug) {
+ syslog(debug, "requiring Linux-PAM-style "
+ "conversation semantics");
+ }
+ abi_linux = 1;
+ abi_sun = 0;
+ }
+ /* Force Sun-PAM-style semantics. */
+ if (strcmp(argv[i], "sun") == 0) {
+ if (debug) {
+ syslog(debug, "requiring Sun-PAM-style "
+ "conversation semantics");
+ }
+ abi_linux = 0;
+ abi_sun = 1;
+ }
+ /* Change the prompt style. */
+ if (strcmp(argv[i], "echo") == 0) {
+ if (debug) {
+ syslog(debug,
+ "will allow echoing of responses");
+ }
+ prompt_style = PAM_PROMPT_ECHO_ON;
+ }
+ /* Change the number of challenges. */
+ if (strncmp(argv[i], "bestof=", 7) == 0) {
+ best_of = atol(argv[i] + 7);
+ if ((best_of % 2) == 0) {
+ best_of++;
+ }
+ if (debug) {
+ syslog(debug,
+ "requiring best of %d matches", best_of);
+ }
}
}
- strcpy(prompt_text, values[(r % 3)]);
- want = values[((r + 1) % 3)];
- if (debug) {
- pam_syslog(pamh, LOG_DEBUG, "challenge is \"%s\", "
- "expected response is \"%s\"", prompt_text, want);
+ /* Set up the PAM message structure. We want to be able to exercise
+ * the conversation callback using either Linux-style or Sun-style
+ * semantics. */
+ msgs = NULL;
+ if (abi_sun) {
+ /* We need to prepare an array of pointers. */
+ msgs = malloc(sizeof(struct pam_message *) * best_of);
+ if (msgs == NULL) {
+ return PAM_BUF_ERR;
+ }
+ memset(msgs, 0, sizeof(struct pam_message *) * best_of);
}
- ret = pam_prompt(pamh, PAM_PROMPT_ECHO_OFF,
- &response, "%s: ", prompt_text);
- if (ret != PAM_SUCCESS) {
- pam_syslog(pamh, LOG_CRIT, "conversation error");
- return PAM_CONV_ERR;
+ msg_array = NULL;
+ if (abi_linux) {
+ /* We need to prepare an array. */
+ msg_array = malloc(sizeof(struct pam_message) * best_of);
+ if (msg_array == NULL) {
+ return PAM_BUF_ERR;
+ }
+ memset(msg_array, 0, sizeof(struct pam_message) * best_of);
}
- if ((response != NULL) && (strcasecmp(response, want) == 0)) {
- ret = PAM_SUCCESS;
+ if (abi_linux && abi_sun) {
+ /* Make the array of pointers point to the array, and fill the
+ * array. */
+ for (i = 0; i < best_of; i++) {
+ msgs[i] = &msg_array[i];
+ fill(&msg_array[i], prompt_style, n_rules);
+ }
} else {
- ret = PAM_AUTH_ERR;
+ if (abi_linux) {
+ /* Set the pointer to the array, and fill the array. */
+ msgs = &msg_array;
+ for (i = 0; i < best_of; i++) {
+ fill(&msg_array[i], prompt_style, n_rules);
+ }
+ }
+ if (abi_sun) {
+ /* Allocate space for the pointed-to items, and fill
+ * them out. */
+ for (i = 0; i < best_of; i++) {
+ msgs[i] = malloc(sizeof(struct pam_message));
+ if (msgs[i] == NULL) {
+ return PAM_BUF_ERR;
+ }
+ memset(msgs[i], 0, sizeof(struct pam_message));
+ fill(msgs[i], prompt_style, n_rules);
+ }
+ }
+ }
+
+ /* Call the application-supplied conversation function and sanity-check
+ * the responses. */
+ responses = NULL;
+ cmsgs = (const struct pam_message **) msgs;
+ i = (*(conv->conv))(best_of, cmsgs, &responses, conv->appdata_ptr);
+ if ((i != PAM_SUCCESS) || (responses == NULL)) {
+ syslog(loglevel, "conversation error: %s",
+ pam_strerror(pamh, i));
+ return PAM_CONV_ERR;
}
- if (response) {
- _pam_overwrite(response);
- free(response);
+ for (i = 0; i < best_of; i++) {
+ if (responses[i].resp == NULL) {
+ syslog(loglevel, "conversation error: "
+ "response %d of %d was NULL",
+ i + 1, best_of + 1);
+ return PAM_CONV_ERR;
+ }
+ }
+
+ /* Check the answers. */
+ score = 0;
+ for (i = 0; i < best_of; i++) {
+ prompt = NULL;
+ if (abi_linux) {
+ prompt = &msg_array[i];
+ }
+ if (abi_sun) {
+ prompt = msgs[i];
+ }
+ if (prompt == NULL) {
+ continue;
+ }
+ /* Find the matching challenge. */
+ for (j = 0; j < n_rules; j++) {
+ if (strcmp(prompt->msg, rules[j].challenge) == 0) {
+ /* Walk the list of winning responses. */
+ for (k = 0; k < n_winners; k++) {
+ if (strcasecmp(rules[j].beaters[k].what,
+ responses[i].resp) == 0){
+ won(debug,
+ prompt->msg,
+ &rules[j].beaters[k],
+ responses[i].resp);
+ score++;
+ break;
+ }
+ }
+ /* If we ran out of winning responses, then the
+ * user lost. */
+ if (k >= n_winners) {
+ lost(debug,
+ rules[j].challenge,
+ responses[i].resp);
+ }
+ break;
+ }
+ }
+ }
+
+ /* Free the prompts. */
+ if (abi_linux) {
+ free(msg_array);
+ }
+ if (abi_sun) {
+ if (!abi_linux) {
+ for (i = 0; i < best_of; i++) {
+ free(msgs[i]);
+ }
+ }
+ free(msgs);
+ }
+
+ /* Free the responses. */
+ for (i = 0; i < best_of; i++) {
+ free(responses[i].resp);
+ }
+ free(responses);
+
+ /* If the user won, then the user is authenticated. */
+ if ((score * 2) > best_of) {
+ return PAM_SUCCESS;
+ } else {
+ return PAM_AUTH_ERR;
}
- return ret;
}
int