From f9195fb953def110f074fef9d8ee400d073763ba Mon Sep 17 00:00:00 2001 From: Alexandre De Dommelin Date: Thu, 10 Feb 2011 21:10:21 +0100 Subject: Import libapache2-mod-authn-yubikey_1.0.orig.tar.bz2 [dgit import orig libapache2-mod-authn-yubikey_1.0.orig.tar.bz2] --- .deps | 0 Makefile | 49 ++++ libykclient.c | 301 +++++++++++++++++++++++ libykclient.h | 76 ++++++ mod_authn_yubikey.c | 695 ++++++++++++++++++++++++++++++++++++++++++++++++++++ modules.mk | 6 + 6 files changed, 1127 insertions(+) create mode 100644 .deps create mode 100644 Makefile create mode 100644 libykclient.c create mode 100644 libykclient.h create mode 100755 mod_authn_yubikey.c create mode 100644 modules.mk diff --git a/.deps b/.deps new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..852598e --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +## +## Makefile -- Build procedure for sample authn_yubikey Apache module +## Autogenerated via ``apxs -n authn_yubikey -g''. +## + +builddir=. +top_srcdir=../../apache228-install +top_builddir=../../apache228-install +include ../../apache228-install/build/special.mk + +# the used tools +APXS=apxs +APACHECTL=apachectl + +# additional defines, includes and libraries +#DEFS=-Dmy_define=my_value +DEFS=-DYK_PACKAGE=\"mod_authn_yubikey\" -DYK_PACKAGE_VERSION=\"0.1\" + +#INCLUDES=-Imy/include/dir +#LIBS=-Lmy/lib/dir -lmylib +LIBS=-lcurl + +# the default target +all: local-shared-build install + +# install the shared object file into Apache +install: install-modules-yes + +# cleanup +clean: + -rm -f mod_authn_yubikey.o mod_authn_yubikey.lo mod_authn_yubikey.slo mod_authn_yubikey.la + +# simple test +test: reload + lynx -mime_header http://localhost/authn_yubikey + +# install and activate shared object by reloading Apache to +# force a reload of the shared object file +reload: install restart + +# the general Apache start/restart/stop +# procedures +start: + $(APACHECTL) start +restart: + $(APACHECTL) restart +stop: + $(APACHECTL) stop + diff --git a/libykclient.c b/libykclient.c new file mode 100644 index 0000000..3b14cfa --- /dev/null +++ b/libykclient.c @@ -0,0 +1,301 @@ +/* libykclient.c --- Implementation of Yubikey client library. + * + * Written by Simon Josefsson . + * Copyright (c) 2006, 2007, 2008 Yubico AB + * All rights reserved. + * + * 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. + * + * 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. + * + */ + +#include "libykclient.h" + +#include +#include +#include +#include + +#include + +#ifdef DEBUG +# define D(x) do { \ + printf ("debug: %s:%d (%s): ", __FILE__, __LINE__, __FUNCTION__); \ + printf x; \ + } while (0) +#else +# define D(x) /* nothing */ +#endif + +struct yubikey_client_st +{ + CURL *curl; + unsigned int client_id; + size_t keylen; + const char *key; +}; + +yubikey_client_t +yubikey_client_init (void) +{ + yubikey_client_t p; + + p = malloc (sizeof (*p)); + + if (!p) + return NULL; + + p->curl = curl_easy_init (); + if (!p->curl) + { + free (p); + return NULL; + } + + return p; +} + +void +yubikey_client_set_info (yubikey_client_t client, + unsigned int client_id, + size_t keylen, + const char *key) +{ + client->client_id = client_id; + client->keylen = keylen; + client->key = key; +} + +void +yubikey_client_done (yubikey_client_t *client) +{ + curl_easy_cleanup ((*client)->curl); + free (*client); + *client = NULL; +} + +int +yubikey_client_simple_request (const char *yubikey, + unsigned int client_id, + size_t keylen, + const char *key) +{ + yubikey_client_t p; + int ret; + + p = yubikey_client_init (); + + yubikey_client_set_info (p, client_id, keylen, key); + + ret = yubikey_client_request (p, yubikey); + + yubikey_client_done (&p); + + return ret; +} + +const char * +yubikey_client_strerror (int ret) +{ + const char *p; + + switch (ret) + { + case YUBIKEY_CLIENT_OK: + p = "Success"; + break; + + case YUBIKEY_CLIENT_BAD_OTP: + p = "BAD_OTP"; + break; + + case YUBIKEY_CLIENT_REPLAYED_OTP: + p = "REPLAYED_OTP"; + break; + + case YUBIKEY_CLIENT_BAD_SIGNATURE: + p = "BAD_SIGNATURE"; + break; + + case YUBIKEY_CLIENT_MISSING_PARAMETER: + p = "MISSING_PARAMETER"; + break; + + case YUBIKEY_CLIENT_NO_SUCH_CLIENT: + p = "NO_SUCH_CLIENT"; + break; + + case YUBIKEY_CLIENT_OPERATION_NOT_ALLOWED: + p = "OPERATION_NOT_ALLOWED"; + break; + + case YUBIKEY_CLIENT_BACKEND_ERROR: + p = "BACKEND_ERROR"; + break; + + case YUBIKEY_CLIENT_OUT_OF_MEMORY: + p = "Out of memory"; + break; + + case YUBIKEY_CLIENT_PARSE_ERROR: + p = "Internal parse error"; + break; + + default: + p = "Unknown error"; + break; + } + + return p; +} + +struct MemoryStruct { + char *memory; + size_t size; +}; + +static size_t +curl_callback (void *ptr, size_t size, size_t nmemb, void *data) +{ + size_t realsize = size * nmemb; + struct MemoryStruct *mem = (struct MemoryStruct *)data; + + if (mem->memory) + mem->memory = realloc (mem->memory, mem->size + realsize + 1); + else + mem->memory = malloc (mem->size + realsize + 1); + + if (mem->memory) + { + memcpy(&(mem->memory[mem->size]), ptr, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + } + + return realsize; +} + +int +yubikey_client_request (yubikey_client_t client, + const char *yubikey) +{ + struct MemoryStruct chunk = { NULL, 0 }; + const char *url_template = "http://api.yubico.com/wsapi/verify?id=%d&otp=%s"; + char *url; + char *user_agent = NULL; + char *status; + int out; + /* Proxy support */ + //int proxyPort = 8080; + //char *proxy = "proxy.example.com"; + //char *proxyPwd = "username:password"; + + asprintf (&url, url_template, client->client_id, yubikey); + if (!url) + return YUBIKEY_CLIENT_OUT_OF_MEMORY; + + curl_easy_setopt (client->curl, CURLOPT_URL, url); + //curl_easy_setopt (client->curl, CURLOPT_PROXYPORT, proxyPort); + //curl_easy_setopt (client->curl, CURLOPT_PROXY, proxy); + //curl_easy_setopt (client->curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); + //curl_easy_setopt (client->curl, CURLOPT_PROXYUSERPWD, proxyPwd); + curl_easy_setopt (client->curl, CURLOPT_WRITEFUNCTION, curl_callback); + curl_easy_setopt (client->curl, CURLOPT_WRITEDATA, (void *)&chunk); + + asprintf (&user_agent, "%s/%s", YK_PACKAGE, YK_PACKAGE_VERSION); + if (user_agent) + curl_easy_setopt(client->curl, CURLOPT_USERAGENT, user_agent); + + curl_easy_perform (client->curl); + + if (chunk.size == 0 || chunk.memory == NULL) + { + out = YUBIKEY_CLIENT_PARSE_ERROR; + goto done; + } + + D (("server response (%d): %.*s", chunk.size, chunk.size, chunk.memory)); + + status = strstr (chunk.memory, "status="); + if (!status) + { + out = YUBIKEY_CLIENT_PARSE_ERROR; + goto done; + } + + while (status[strlen (status) - 1] == '\r' + || status[strlen (status) - 1] == '\n') + status[strlen (status) - 1] = '\0'; + + D (("parsed status (%d): %s\n", strlen (status), status)); + + if (strcmp (status, "status=OK") == 0) + { + out = YUBIKEY_CLIENT_OK; + goto done; + } + else if (strcmp (status, "status=BAD_OTP") == 0) + { + out = YUBIKEY_CLIENT_BAD_OTP; + goto done; + } + else if (strcmp (status, "status=REPLAYED_OTP") == 0) + { + out = YUBIKEY_CLIENT_REPLAYED_OTP; + goto done; + } + else if (strcmp (status, "status=BAD_SIGNATURE") == 0) + { + out = YUBIKEY_CLIENT_BAD_SIGNATURE; + goto done; + } + else if (strcmp (status, "status=MISSING_PARAMETER") == 0) + { + out = YUBIKEY_CLIENT_MISSING_PARAMETER; + goto done; + } + else if (strcmp (status, "status=NO_SUCH_CLIENT") == 0) + { + out = YUBIKEY_CLIENT_NO_SUCH_CLIENT; + goto done; + } + else if (strcmp (status, "status=OPERATION_NOT_ALLOWED") == 0) + { + out = YUBIKEY_CLIENT_OPERATION_NOT_ALLOWED; + goto done; + } + else if (strcmp (status, "status=BACKEND_ERROR") == 0) + { + out = YUBIKEY_CLIENT_BACKEND_ERROR; + goto done; + } + + out = YUBIKEY_CLIENT_PARSE_ERROR; + + done: + if (user_agent) + free (user_agent); + + return out; +} diff --git a/libykclient.h b/libykclient.h new file mode 100644 index 0000000..862ee86 --- /dev/null +++ b/libykclient.h @@ -0,0 +1,76 @@ +/* libykclient.h --- Definitions and prototypes for Yubico client library. + * + * Written by Simon Josefsson . + * Copyright (c) 2006, 2007, 2008 Yubico AB + * All rights reserved. + * + * 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. + * + * 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. + * + */ + +#ifndef YUBIKEY_CLIENT_H +# define YUBIKEY_CLIENT_H + +# include +# include + +typedef enum { + /* Official yubikey client API errors. */ + YUBIKEY_CLIENT_OK = 0, + YUBIKEY_CLIENT_BAD_OTP, + YUBIKEY_CLIENT_REPLAYED_OTP, + YUBIKEY_CLIENT_BAD_SIGNATURE, + YUBIKEY_CLIENT_MISSING_PARAMETER, + YUBIKEY_CLIENT_NO_SUCH_CLIENT, + YUBIKEY_CLIENT_OPERATION_NOT_ALLOWED, + YUBIKEY_CLIENT_BACKEND_ERROR, + /* Other implementation specific errors. */ + YUBIKEY_CLIENT_OUT_OF_MEMORY = 100, + YUBIKEY_CLIENT_PARSE_ERROR +} yubikey_client_rc; + +typedef struct yubikey_client_st *yubikey_client_t; + +yubikey_client_t yubikey_client_init (void); +void yubikey_client_done (yubikey_client_t *client); + +void +yubikey_client_set_info (yubikey_client_t client, + unsigned int client_id, + size_t keylen, + const char *key); + +const char *yubikey_client_strerror (int ret); + +int yubikey_client_request (yubikey_client_t client, const char *yubikey); + +/* One call interface. */ +int +yubikey_client_simple_request (const char *yubikey, + unsigned int client_id, + size_t keylen, + const char *key); + +#endif diff --git a/mod_authn_yubikey.c b/mod_authn_yubikey.c new file mode 100755 index 0000000..31eb673 --- /dev/null +++ b/mod_authn_yubikey.c @@ -0,0 +1,695 @@ +/* + * Copyright [2008] [Jens Frey] + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +/* + * If you have any questions feel free to contact me + * at jens.frey@coffeecrew.org + * + */ +#include "httpd.h" +#include "http_config.h" +#include "http_protocol.h" +#include "ap_config.h" +#include "http_log.h" +#include "mod_auth.h" +#include "libykclient.h" +#include "ap_provider.h" +#include "apr_strings.h" +#include "apr_dbm.h" +#include "apr_time.h" +#include "http_core.h" +#include "http_request.h" + +#define APR_WANT_STRFUNC /* for strcasecmp */ +#include "apr_want.h" + + +#define YUBIKEY_TOKEN_LENGTH 44 +#define YUBIKEY_ID_LENGTH 12 +#define LOG_PREFIX "[mod_authn_yubikey] " +#define MOD_AUTHN_YUBIKEY_NAME "mod_authn_yubikey" +#define MOD_AUTHN_YUBIKEY_VERSION "0.1" +#define ERROR_TEXT " " \ +"Error: SSL (https) connection required" \ +"" \ +"
" \ +"\"Lock" \ +"
This site is configured to require an SSL (https) connection.
" \ +"
" \ +"

" \ +"You may want to try chaning http to https in your address bar.
" \ +"If you think this is an error, please contact the administrator." \ +"

" + +#define HDR_YK_AUTH_TYPE "X-Yubi-Auth-Type" +/* One factor (just token) */ +#define YK_AUTH_TYPE_OF "OneFactor" +/* Two factor , password and token */ +#define YK_AUTH_TYPE_TF "TwoFactor" + +module AP_MODULE_DECLARE_DATA authn_yubikey_module; + +/* Default values */ +#define DEFAULT_TIMEOUT 43200 /* 12h */ +#define DEFAULT_REQUIRE_SECURE (TRUE) +#define DEFAULT_EXTERNAL_ERROR_PAGE (FALSE) +#define DEFAULT_USER_DB "conf/ykUserDb" +#define DEFAULT_TMP_DB "conf/ykTmpDb" +#define UNSET -1 + +typedef struct +{ + /* This is the actual timeout after which the session finally expires, + * there is NO recovery from this, so this timeout is not renewed everytime + * a user makes a request + */ + int timeoutSeconds; + /* This flag requires the protected location to be accessed via an secure Url + * this is especially useful if you use the two factor authentication, + * since passwords would otherwise be sent in the clear. + */ + int requireSecure; + /* If any error happens, this will redirect you to the given error page, + * or an internally generated error page will be shown. + * Use this is you want to customize the error page. + */ + int externalErrorPage; + /* This is the temporary filename authenticated user are saved in. + * This could possibly be done with an in memory version of s.th. similar + * to the database + */ + const char *tmpAuthDbFilename; + /* This is the file where the actual user/password connection happens. So + * the module knows where it can find the file where the tokenId/username + * mapping happens. + */ + const char *userAuthDbFilename; + /* TODO: NYI + * This is required to be given if you want to use another authentication + * provider which supports the yubikey token, but not via yubicos site. + */ + //const char *validationUrl; +} yubiauth_dir_cfg; + +/* A helper */ +static apr_datum_t string2datum(const char * toStore, request_rec *r) +{ + apr_datum_t dt; + dt.dptr = apr_pstrdup(r->pool, (char*) toStore); +#ifndef NETSCAPE_DBM_COMPAT + dt.dsize = strlen(dt.dptr); +#else + dt.dsize = strlen(dt.dptr) + 1; +#endif + + return dt; +} + +static void openDb(apr_dbm_t **userDbm, const char *dbFilename, request_rec *r) +{ + apr_status_t rv; + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, LOG_PREFIX "Opening db ..."); + rv = apr_dbm_open(userDbm, dbFilename, APR_DBM_RWCREATE, APR_FPROT_OS_DEFAULT, r->pool); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX "Error opening db %s ...", dbFilename); + } +} + +static void closeDb(apr_dbm_t *userDbm, request_rec *r) +{ + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, LOG_PREFIX "Closing db ..."); + apr_dbm_close(userDbm); +} + +/* User Key because the username is the key to the db */ +static void deleteKeyFromDb(apr_dbm_t *userDbm, const char *userKey, request_rec *r) +{ + apr_datum_t key; + apr_status_t rv; + key = string2datum(userKey, r); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "Deleting key %s", + key.dptr); + rv = apr_dbm_delete(userDbm, key); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + LOG_PREFIX "Could not delete key %s", + key.dptr); + } +} + +static apr_status_t setUserInDb(apr_dbm_t *userDbm, + const char *user, + const char *password, + request_rec *r) +{ + char *timeAuthenticated = NULL; + char *dbToken = NULL; //This is used to store pw:date + + apr_datum_t key, value; + apr_status_t rv; + + /* Built up some combination of token:time */ + timeAuthenticated = apr_psprintf(r->pool, "%" APR_TIME_T_FMT, (apr_time_t) (apr_time_sec(apr_time_now()))); + dbToken = apr_pstrcat(r->pool, password, ":", timeAuthenticated, NULL); + + /* store OTP:time combo with username as key in DB */ + key = string2datum(user, r); + value = string2datum(dbToken, r); + + /* Pump user into db, store un, cookie value, creation date, + * let this expire sometime + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, LOG_PREFIX "Writing key: %s and value: %s to db", + key.dptr, value.dptr); + rv = apr_dbm_store(userDbm, key, value); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX "Error writing to db ... with key: %s and value: %s", + key.dptr, value.dptr); + } + /* Spit back, so we can decide wheather s.th. went wrong or not */ + return rv; +} + +static int passwordExpired(const char *user, + apr_time_t lookedUpDate, + apr_time_t timeout, + request_rec *r) +{ + if ((apr_time_sec(apr_time_now())) > (lookedUpDate + timeout)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, LOG_PREFIX "Session expired for user %s", user); + return TRUE; + } + + return FALSE; +} + +static int isUserValid(const char *user, + const char *password, + yubiauth_dir_cfg *cfg, + request_rec *r) +{ + + ap_configfile_t *f; + char l[MAX_STRING_LEN]; + apr_status_t status; + char *file_password = NULL; + char *yubiKeyId = NULL; + char *userPassword = NULL; + apr_size_t passwordLength = 0; + char *realName = NULL; + int userWasFound = FALSE; + /* This is TRUE when we store a combination of yubikeyId:username:password, + * we then have a two factor authentication. + */ + int tokenHasPassword = FALSE; + + status = ap_pcfg_openfile(&f, r->pool, cfg->userAuthDbFilename); + + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, + LOG_PREFIX "Could not open AuthYkUserFile file: %s", + cfg->userAuthDbFilename); + return FALSE; + } + + /* Do length check of at least the password part, + * to be a yubikey token, it has to have at least 44 + * characters from where the first 12 are the ID of the user. + */ + if (strlen(password) < YUBIKEY_TOKEN_LENGTH) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + LOG_PREFIX "The entered password cannot be a yubikey generated token"); + ap_cfg_closefile(f); + return FALSE; + } + + + + /* If the password is bigger then 44 characters, then we have an additional password + * set into the field, since the produced token by the yubikey is 44 characters long + */ + passwordLength = (apr_size_t) strlen(password) - YUBIKEY_TOKEN_LENGTH; + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The length of the entered password is: %d", passwordLength); + + /* We have to distinct between a 44 character string which is the + * toke output only and a longer string, which would contain a + * password at its beginning + */ + if (strlen(password) > YUBIKEY_TOKEN_LENGTH) { + /* copy off the password part from the password string */ + userPassword = apr_pstrndup(r->pool, password, passwordLength); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The entered password is: %s", userPassword); + } + + /* Now move the password pointer forward the number of calculatd characters for the userPassword, + * we move the pointer beyond the last read character (not -1), to start reading the real stuff + */ + yubiKeyId = apr_pstrndup(r->pool, &password[passwordLength], (apr_size_t) YUBIKEY_ID_LENGTH); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The calculated YubiKey ID is: %s", yubiKeyId); + + /* Find the TokenID/UN:PW solution in the file */ + while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { + const char *rpw, *w; + char *unPw = NULL; + + /* Skip # or blank lines. */ + if ((l[0] == '#') || (!l[0])) { + continue; + } + + rpw = l; + w = ap_getword(r->pool, &rpw, ':'); + + /* The first 12 chars are the ID which must be available in this file + * else the user might be a yubikey user, but possibly a user we don't + * want. + */ + if (!strncmp(yubiKeyId, w, 12)) { + /* This would fetch the real username, + * after the ID could be located + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "Could find the ID: %s", w); + /* remember, since we are working with the passwd + * utility, this realName is hashed + */ + realName = ap_getword(r->pool, &rpw, '\n'); + apr_table_set(r->headers_in, HDR_YK_AUTH_TYPE, YK_AUTH_TYPE_OF); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The looked up realname is: %s", realName); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The looked up userPassword is: %s", userPassword); + /* this results in username:password as it should be entered in the install dialog */ + if (userPassword) { + unPw = apr_pstrcat(r->pool, user, ":", userPassword, NULL); + apr_table_set(r->headers_in, HDR_YK_AUTH_TYPE, YK_AUTH_TYPE_TF); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The built un:pw combo is: %s", unPw); + } + /* If there is a password set, use the username:password combo, + * else just compare the username + */ + status = apr_password_validate(userPassword?unPw:user, realName); + + if (status == APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "Could map ID %s to User: %s", w, user); + userWasFound = TRUE; + break; + } + } + } + ap_cfg_closefile(f); + + return userWasFound; +} + +/* This does some initial checking, like if we're running on a SSL line or not */ +static int checkInitial(request_rec *r) +{ + yubiauth_dir_cfg *cfg = ap_get_module_config(r->per_dir_config, &authn_yubikey_module); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "requireSecure: %d", cfg->requireSecure); + /* If no securiy is wanted or scheme is already https */ + if (!cfg->requireSecure || !strncmp(ap_http_scheme(r), "https", 5)) { + return OK; + } + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + LOG_PREFIX "The server is configured to use HTTPS on URI: %s", r->uri); + if(cfg->externalErrorPage == TRUE){ + /* We explicitly want that to be overridable */ + //return HTTP_BAD_REQUEST; + return HTTP_NOT_ACCEPTABLE; + } + + /* Tell the user/admin what's going on instead of just showing BAD_REQUEST */ + ap_rputs(DOCTYPE_HTML_4_0T, r); + ap_set_content_type(r, "text/html;"); + ap_rputs(ERROR_TEXT, r); + ap_finalize_request_protocol(r); + return HTTP_BAD_REQUEST; +} + +static authn_status authn_check_otp(request_rec *r, const char *user, + const char *password) +{ + apr_status_t rv; + apr_dbm_t *userDbm = NULL; + yubiauth_dir_cfg *cfg = ap_get_module_config(r->per_dir_config, &authn_yubikey_module); + + apr_datum_t key,dbUserRecord; + key.dptr = NULL; + dbUserRecord.dptr = NULL; + + char *lookedUpToken = NULL; + char *lookedUpPassword = NULL; //This is the OTP token + apr_size_t passwordLength = 0; + apr_time_t lookedUpDate = 0; + + + + /* No username and no password is set */ + if (!*user || !*password) + return AUTH_DENIED; + + /* Since the password field contains possibly a password and the OTP token, we + * have to break that up here + */ + passwordLength = (apr_size_t) strlen(password) - YUBIKEY_TOKEN_LENGTH; + + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "Username is: %s and password is: %s", user, &password[passwordLength]); + + /* Now open the User DB and see if the user really is one of us. + * for that we save the 12char token:username combo. + * Ideally we can fill that with the htpasswd utility + * NOTE: enter full password here + */ + if (!isUserValid(user, password, cfg, r)) { + return AUTH_DENIED; + } + + openDb(&userDbm, cfg->tmpAuthDbFilename, r); + + key = string2datum(user, r); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, LOG_PREFIX "Fetching token (pw:time) for user %s from db ...", user); + rv = apr_dbm_fetch(userDbm, key, &dbUserRecord); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX "unable to fetch the user (%s) from the" + "Database, better abort here.", user); + closeDb(userDbm, r); + return HTTP_INTERNAL_SERVER_ERROR; + } + if (dbUserRecord.dptr != NULL) { + + /* it's separated pw:time here */ + const char *sep = ":"; + char *time; + + lookedUpToken = apr_pstrmemdup(r->pool, dbUserRecord.dptr, dbUserRecord.dsize); + /* Break down the token into it's pw:time components */ + lookedUpPassword = apr_strtok(lookedUpToken, sep, &time); + lookedUpDate = (apr_time_t) apr_atoi64(time); + + + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "We could extrace these values from the token:"); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The looked up token for the user: %s", + user); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The looked up password: %s", + lookedUpPassword); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The looked up time: %" APR_TIME_T_FMT, + lookedUpDate); + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, + LOG_PREFIX "The looked up token: %s", + lookedUpToken); + } + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_DEBUG, 0, r, LOG_PREFIX "Fetched token (%s) ...", lookedUpToken); + + /* password has to be set, if the pw content is NULL or empty, we have + * catched that earlier ... + */ + if (lookedUpPassword != NULL && !strcmp(lookedUpPassword, &password[passwordLength])) { + /* The date expired */ + if (passwordExpired(user, lookedUpDate, cfg->timeoutSeconds, r)) { + /* Delete user record */ + deleteKeyFromDb(userDbm, user, r); + closeDb(userDbm, r); + return AUTH_DENIED; + } + else { + closeDb(userDbm, r); + return AUTH_GRANTED; + } + } + else { + int authenticationSuccessful = 0; + int ret = YUBIKEY_CLIENT_BAD_OTP; + /* We could not lookup the password, verify the sent password */ + ret = yubikey_client_simple_request(&password[passwordLength], 1, 0, NULL); + if (ret == YUBIKEY_CLIENT_OK) { + authenticationSuccessful = 1; + } else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + LOG_PREFIX "Authentication failed, reason: %s", + yubikey_client_strerror(ret)); + return AUTH_DENIED; + } + + /* We could successfully authenticate the user */ + if (authenticationSuccessful) { + /* Try to write the user into the db */ + if (setUserInDb(userDbm, user, &password[passwordLength], r) + != APR_SUCCESS) { + /* Abort, we could not write the user into the db after + * authenticating him ... + */ + closeDb(userDbm, r); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* User could be written to the db*/ + closeDb(userDbm, r); + return AUTH_GRANTED; + } + + /* Could not authenticate successful */ + closeDb(userDbm, r); + return AUTH_DENIED; + } + + /* Something went wrong or we did not think about it, better deny */ + closeDb(userDbm, r); + return AUTH_DENIED; +} + +/* This provides a simple UI for accessing the key database + * used to grant access to users owning a yubikey or not. + * + * This is primarily a web version of htaccess. + */ +static int authn_yubikey_handler(request_rec *r) +{ + ap_configfile_t *f; + char l[MAX_STRING_LEN]; + apr_status_t status; + char *file_password = NULL; + char *yubiKeyId = NULL; + char *realName = NULL; + apr_file_t *dbFormFile = NULL; + yubiauth_dir_cfg *cfg = ap_get_module_config(r->per_dir_config, &authn_yubikey_module); + + if (strcmp(r->handler, "authn_yubikey")) { + return DECLINED; + } + r->content_type = "text/html"; + + /* Post back */ + if (r->method_number == M_POST) { + const char *postbackContent = NULL; + char *tmp = NULL; + char buffer[1024]; + //Read the POST data sent from the client + ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); + + if ( ap_should_client_block(r) == 1 ) { + while ( ap_get_client_block(r, buffer, 1024) > 0 ) { + postbackContent = apr_pstrcat(r->pool, buffer, tmp, NULL); + //tmp = apr_pstrdup(r->pool, postbackContent); + } + } + else { + ap_rputs("No POST data available",r); + } + //We have the data now, now process it and save it into the db + + + ap_set_content_type(r, "text/plain;"); + ap_rprintf(r, "Postback content: %s", postbackContent); + + return OK; + } + + /* Serve content if it's a GET request */ + if (!r->header_only) { + ap_rputs("YubiAuth user management", r); + ap_rputs("

Welcome to the YubiAuth user Mgmt.


", r); + ap_rputs("The following users could be found inside the database:
", r); + //Open userDb file for looped output + status = ap_pcfg_openfile(&f, r->pool, cfg->userAuthDbFilename); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, + LOG_PREFIX "Could not open YubiAuthUserDb file: %s", + cfg->userAuthDbFilename); + return HTTP_INTERNAL_SERVER_ERROR; + } + ap_rputs("\n", r); + while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { + const char *rpw, *w; + + /* Skip # or blank lines. */ + if ((l[0] == '#') || (!l[0])) { + continue; + } + + rpw = l; + w = ap_getword(r->pool, &rpw, ':'); + //yubiKeyId = apr_pstrndup(r->pool, password, (apr_size_t) 12); + //realName = ap_getword(r->pool, &rpw, ':'); + ap_rputs("", r); + ap_rprintf(r, "", w); + ap_rprintf(r, "\n", rpw); + ap_rputs("", r); + } + ap_cfg_closefile(f); + ap_rputs("
TokenIdReal Name
%s%s
", r); + ap_rputs("

Want to add a user?

.", r); + ap_rputs("
", r); + ap_rputs("", r); + ap_rputs("", r); + ap_rputs("", r); + ap_rputs("
", r); + ap_rputs("", r); + } + + //If we receive a POST to this location, we probably want to update the userdb + //be sure to check if there is a user set and that this user is allowed to change information + // inside the userdb, we cn authenticate the user with the OTP token here too. + //We should make the administrative user configurable, by specifying the token which is allowed + // access ... + return OK; +} + +static const authn_provider authn_yubikey_provider = { + &authn_check_otp, + NULL +}; + +static int init_mod_yk(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, + LOG_PREFIX "Version [" MOD_AUTHN_YUBIKEY_VERSION "] initialized"); + + ap_add_version_component(pconf, MOD_AUTHN_YUBIKEY_NAME "/" MOD_AUTHN_YUBIKEY_VERSION); + + return OK; +} + +static void authn_yubikey_register_hooks(apr_pool_t *p) +{ + //static const char *const aszSucc[] = { "mod_authz_user.c", NULL }; + + + //ap_hook_auth_checker(authz_check_yubi_user, NULL, aszSucc, APR_HOOK_MIDDLE); + /* No content handler for this one */ + //ap_hook_handler(authn_yubikey_handler, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_header_parser(checkInitial, NULL, NULL, APR_HOOK_MIDDLE); + ap_register_provider(p, AUTHN_PROVIDER_GROUP, "yubikey", "0", &authn_yubikey_provider); + ap_hook_post_config(init_mod_yk, NULL, NULL, APR_HOOK_MIDDLE); +} + +static const command_rec authn_yubikey_cmds[] = { + AP_INIT_TAKE1("AuthYubiKeyTmpFile", ap_set_file_slot, + (void*) APR_OFFSETOF(yubiauth_dir_cfg, tmpAuthDbFilename), + ACCESS_CONF, "The temporary filename for authenticated users"), + AP_INIT_TAKE1("AuthYubiKeyUserFile", ap_set_file_slot, + (void*) APR_OFFSETOF(yubiauth_dir_cfg, userAuthDbFilename), + ACCESS_CONF, "The filename for allowed users"), + AP_INIT_TAKE1("AuthYubiKeyTimeout", ap_set_int_slot, + (void*) APR_OFFSETOF(yubiauth_dir_cfg, timeoutSeconds), + ACCESS_CONF, "The timeout when users have to reauthenticate (Default 43200 seconds [12h])"), + //AP_INIT_TAKE1("AuthYkValidationUrl", ap_set_int_slot, + // (void*) APR_OFFSETOF(yubiauth_dir_cfg, validationUrl), + // ACCESS_CONF, "The URL of the location where the key can be" \ + // "authenticated"), + AP_INIT_FLAG("AuthYubiKeyExternalErrorPage", ap_set_flag_slot, + (void*) APR_OFFSETOF(yubiauth_dir_cfg, externalErrorPage), + ACCESS_CONF, "If SSL is required display internal error page, or display custom (406) error" \ + "page (Default Off)"), + AP_INIT_FLAG("AuthYubiKeyRequireSecure", ap_set_flag_slot, + (void*) APR_OFFSETOF(yubiauth_dir_cfg, requireSecure), + ACCESS_CONF|RSRC_CONF, + "Whether or not a secure site is required to pass authentication (Default On)"), + {NULL} +}; + +static void *create_yubiauth_dir_cfg(apr_pool_t *pool, char *x) +{ + yubiauth_dir_cfg *dir = apr_pcalloc(pool, sizeof (yubiauth_dir_cfg)); + /* Set defaults configuration here + */ + dir->timeoutSeconds = UNSET; + dir->requireSecure = UNSET; + dir->externalErrorPage = UNSET; + + dir->tmpAuthDbFilename = NULL; + dir->userAuthDbFilename = NULL; + + return dir; +} + +static void *merge_yubiauth_dir_cfg(apr_pool_t *pool, void *BASE, void *ADD) +{ + yubiauth_dir_cfg *base = BASE; + yubiauth_dir_cfg *add = ADD; + yubiauth_dir_cfg *dir = apr_pcalloc(pool, sizeof (yubiauth_dir_cfg)); + + /* merge */ + dir->timeoutSeconds = (add->timeoutSeconds == UNSET) ? base->timeoutSeconds : add->timeoutSeconds; + dir->requireSecure = (add->requireSecure == UNSET) ? base->requireSecure : add->requireSecure; + dir->externalErrorPage = (add->externalErrorPage == UNSET) ? base->externalErrorPage : add->externalErrorPage; + + dir->userAuthDbFilename = (add->userAuthDbFilename == NULL) ? base->userAuthDbFilename : add->userAuthDbFilename; + dir->tmpAuthDbFilename = (add->tmpAuthDbFilename == NULL) ? base->tmpAuthDbFilename : add->tmpAuthDbFilename; + + /* Set defaults configuration here + */ + if (dir->timeoutSeconds == UNSET) { + dir->timeoutSeconds = DEFAULT_TIMEOUT; + } + if (dir->requireSecure == UNSET) { + dir->requireSecure = DEFAULT_REQUIRE_SECURE; + } + if (dir->externalErrorPage == UNSET) { + dir->externalErrorPage = DEFAULT_EXTERNAL_ERROR_PAGE; + } + if (dir->userAuthDbFilename == NULL) { + dir->userAuthDbFilename = ap_server_root_relative(pool, DEFAULT_USER_DB); + } + if (dir->tmpAuthDbFilename == NULL) { + dir->tmpAuthDbFilename = ap_server_root_relative(pool, DEFAULT_TMP_DB); + } + return dir; +} + +/* Dispatch list for API hooks */ +module AP_MODULE_DECLARE_DATA authn_yubikey_module = { + STANDARD20_MODULE_STUFF, + &create_yubiauth_dir_cfg, /* create per-dir config structures */ + &merge_yubiauth_dir_cfg, /* merge per-dir config structures */ + NULL, /* create per-server config structures */ + NULL, /* merge per-server config structures */ + authn_yubikey_cmds, /* table of config file commands */ + authn_yubikey_register_hooks /* register hooks */ +}; + diff --git a/modules.mk b/modules.mk new file mode 100644 index 0000000..f6945d9 --- /dev/null +++ b/modules.mk @@ -0,0 +1,6 @@ +SRC = libykclient.slo mod_authn_yubikey.slo +SRC_LO = libykclient.lo mod_authn_yubikey.lo +mod_authn_yubikey.la: $(SRC) + $(SH_LINK) -rpath $(libexecdir) -module -avoid-version $(SRC_LO) +DISTCLEAN_TARGETS = modules.mk +shared = mod_authn_yubikey.la -- cgit v1.2.3