diff options
author | Nalin Dahyabhai <nalin@dahyabhai.net> | 2010-04-29 12:38:03 -0400 |
---|---|---|
committer | Nalin Dahyabhai <nalin@dahyabhai.net> | 2010-04-29 12:38:03 -0400 |
commit | 2d98af25d3968223e27c3f438013bd12beaffe1f (patch) | |
tree | 0b0aaff548b0175397600fb900323a5ddbbffd14 | |
parent | 25304d87f30ebcc5f6382f89d5f99b498dd7ceef (diff) | |
download | pam_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.am | 3 | ||||
-rw-r--r-- | src/main.c | 128 | ||||
-rw-r--r-- | src/pam_rps.8.in | 33 | ||||
-rw-r--r-- | src/pam_rps.c | 349 |
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 |