?? mod_cas.c
字號:
/* * "Require NetID Authentication" module that allows a server to act as a * client of the Central Authentication Service. * * Original author: Shawn Bayern <shawn.bayern@yale.edu> * Original version: February, 2001 * * Current maintainer: Drew Mazurek <drew.mazurek@yale.edu> * * Note: the "LOG" messages are entirely for debugging and should not be * enabled for any production deployment. They're somewhat arbitrary; * we've found them useful at Yale for debugging new features, so I've * left them in in case they're useful. */#include "httpd.h"#include "http_config.h"#include "http_core.h"#include "http_log.h"#include "http_main.h"#include "http_protocol.h"#include "util_script.h"#include "apr_strings.h"#include "http_connection.h"#include "ap_config.h"#include <sys/file.h>#include <sys/mman.h>#include <sys/stat.h>#include <sys/types.h>#include <unistd.h>#include <fcntl.h>#include <stdio.h>#include <grp.h>#include <pwd.h>#include "cas.h"#include "ticketcache.h"#define URLBUFSIZE 4096#define USERBUFSIZE 20#define TICKETBUFSIZE 60// support two different cookie names to allow secure and insecure cookies// to exist side-by-side#define COOKIENAME "MODCASID"#define COOKIENAME_SECURE "MODCASIDS"#define COOKIEPATH "/"#define DEFAULT_CASLocalCacheFile NULL#define DEFAULT_CASLocalCacheSize 1000#define DEFAULT_CASLocalCacheTimeout 3600#define DEFAULT_CASLocalCacheInsecure 0#define DEFAULT_CASLogoutParameter NULL#define DEFAULT_CASLogoutLocalURL NULL//#define DEBUG#undef DEBUG#ifdef DEBUG# define LOG(X) log(X)#else# define LOG(X) #endif/* Utility function declarations */static char *get_service(request_rec *r, char *buf, int buflen);static char *get_ticket(request_rec *r);static char *get_ticket_from_cookies(request_rec *r);static void log(const char *msg);static void logout(request_rec *r);static void cache_invalidate(char *ticket);static int is_user_in_group(const char *user, const char *group, apr_pool_t *p);static int is_parameter_true(request_rec *r, char *param);static void write_lock(int fd);static void read_lock(int fd);static void un_lock(int fd);/* Our exported link to Apache. */module AP_MODULE_DECLARE_DATA cas_module;/* our "configuration record" */typedef struct { char *CASLocalCacheFile; char *CASLogoutParameter; char *CASLogoutLocalURL; char *CASEGDFile; int CASLocalCacheSize; time_t CASLocalCacheTimeout; char CASLocalCacheInsecure;} mod_cas_conf;/* support functions that we want to prototype here */static int check_individual_cookie(void *netid, char *key, char *value);static int check_individual_cookie_for_ticket(void *netid, char *key, char *value);static char *get_netid_from_cookies(request_rec *r);static void create_and_send_new_ticket(request_rec *r);/* effectively per-process local pointer to our mmap()'d ticket cache */static char *ticket_cache = NULL;static size_t ticket_cache_size = 0;static int ticket_cache_fd;/* * Reads CASLocalCacheFile from the configuration file. */static const char *read_CASLocalCacheFile( cmd_parms *cmd, void *dummy, const char *word) { mod_cas_conf *c = (mod_cas_conf *) ap_get_module_config( cmd->server->module_config, &cas_module); c->CASLocalCacheFile = (char *) word; return NULL;}/* * Reads CASLogoutParameter from the configuration file. */static const char *read_CASLogoutParameter( cmd_parms *cmd, void *dummy, const char *word) { mod_cas_conf *c = (mod_cas_conf *) ap_get_module_config( cmd->server->module_config, &cas_module); c->CASLogoutParameter = (char *) word; return NULL;}/* * Reads CASLogoutLocalURL from the configuration file. */static const char *read_CASLogoutLocalURL( cmd_parms *cmd, void *dummy, const char *word) { mod_cas_conf *c = (mod_cas_conf *) ap_get_module_config( cmd->server->module_config, &cas_module); c->CASLogoutLocalURL = (char *) word; return NULL;}/* * Reads CASEGDFile from the configuration file. */static const char *read_CASEGDFile( cmd_parms *cmd, void *dummy, const char *word) { mod_cas_conf *c = (mod_cas_conf *) ap_get_module_config( cmd->server->module_config, &cas_module); c->CASEGDFile = (char *) word; return NULL;}/* * Reads CASLocalCacheSize from the configuration file. */static const char *read_CASLocalCacheSize( cmd_parms *cmd, void *dummy, const char *word) { mod_cas_conf *c = (mod_cas_conf *) ap_get_module_config( cmd->server->module_config, &cas_module); c->CASLocalCacheSize = atoi(word); if (c->CASLocalCacheSize <= 0) return "CASLocalCacheSize must be a positive number"; return NULL;}/* * Reads CASLocalCacheTimeout from the configuration file. */static const char *read_CASLocalCacheTimeout( cmd_parms *cmd, void *dummy, const char *word) { mod_cas_conf *c = (mod_cas_conf *) ap_get_module_config( cmd->server->module_config, &cas_module); c->CASLocalCacheTimeout = atoi(word); if (c->CASLocalCacheTimeout <= 0) return "CASLocalCacheTimeout must be a positive number"; return NULL;}/* * Reads CASLocalCacheInsecure from the configuration file. */static const char *read_CASLocalCacheInsecure( cmd_parms *cmd, void *dummy, int bool) { mod_cas_conf *c = (mod_cas_conf *) ap_get_module_config( cmd->server->module_config, &cas_module); c->CASLocalCacheInsecure = (!(!bool)); return NULL;}/* * Returns a newly allocated server configuration record, filled in with the * default values. Since we provide no "merge" routine, we expect that * more specific settings will occlude less specific settings. This may * be important -- for instance, if there is a virtual server configured * for SSL support on a server that is otherwise unprotected. */static void *init_server_config(apr_pool_t *p, server_rec *s) { mod_cas_conf *c = apr_pcalloc(p, sizeof(mod_cas_conf)); c->CASLocalCacheFile = DEFAULT_CASLocalCacheFile; c->CASLocalCacheSize = DEFAULT_CASLocalCacheSize; c->CASLocalCacheTimeout = DEFAULT_CASLocalCacheTimeout; c->CASLocalCacheInsecure = DEFAULT_CASLocalCacheInsecure; c->CASLogoutParameter = DEFAULT_CASLogoutParameter; c->CASLogoutLocalURL = DEFAULT_CASLogoutLocalURL; return c;}/* * Called upon the exit of a child process. mod_cas currently takes this * opportunity to munmap() our cache database if one was previously * mmap()'d. */static apr_status_t cleanup_child(void *server_rec) { if (ticket_cache) munmap(ticket_cache, ticket_cache_size); return APR_SUCCESS;}/* * Called upon the initializaion of a child process. mod_cas currently * takes this opportunity to mmap() our cache database if one has been * requested. */static void init_child(apr_pool_t *p, server_rec *s) { int fd; mod_cas_conf *c; apr_pool_cleanup_register(p, s, cleanup_child, cleanup_child); LOG("in init_child()\n"); // if ticket_cache is already initialized, don't bother re-mapping it if (ticket_cache) return; // retrieve our configuration c = (mod_cas_conf *) ap_get_module_config(s->module_config, &cas_module); if (c != NULL) LOG("-> c is not null\n"); else LOG("-> c is null\n"); // don't do anything if the server admin doesn't want a cache if (!(c->CASLocalCacheFile)) return; LOG("-> dereferenced c\n"); // if a cache file is requested, mmap() it and store its in-memory // location in our static, per-process cell fd = open(c->CASLocalCacheFile, O_RDWR | O_CREAT, 0600); if (fd < 0) { LOG("-> couldn't open CAS cache file\n"); return; // open() failure } // make sure the file is as large as we need it to be ticket_cache_size = sizeof(int) + (c->CASLocalCacheSize * sizeof(TicketEntry)); lseek(fd, ticket_cache_size, SEEK_SET); write(fd, "", 1); ticket_cache = mmap(0, ticket_cache_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); /* * If mmap() failed, there's really not much we can do other than * abandon the idea of a ticket cache, at least for us. But at least * we can set ticket_cache back to NULL as a marker. */ if (ticket_cache == MAP_FAILED) { ticket_cache = NULL; LOG("-> mmap() failed\n"); return; } ticket_cache_fd = fd; LOG("-> init_child() done\n");}/* * Handles CAS authentication. Redirects to CAS if we can't authenticate * the user, and expects a ticket back. Sets r->user with * a validated user. Operates only when the AuthType is set to "CAS." * * Once past this point, we never directly return DECLINED; we return * success with OK and, if we can't authenticate the user, merely redirect * to the CAS with HTTP_MOVED_TEMPORARILY. On error, we return * HTTP_INTERNAL_SERVER_ERROR. If we return OK, no other modules are * supposed to handle this request during the "check user" phase. */static int do_cas(request_rec *r){ // it's okay to use these buffers since we're single-threaded char *urlbuf = (char *) apr_pcalloc(r->pool, URLBUFSIZE); char *userbuf = (char *) apr_pcalloc(r->connection->pool, USERBUFSIZE); char *service, *ticket; const char *auth_type = ap_auth_type(r); char *p; int validate_status = 0; LOG("in do_cas()\n"); /* We only have something to do if "CAS" is the AuthType. */ if (auth_type && strcmp(auth_type, "CAS")) return DECLINED; /* * Okay, we're relevant. Now, if we've been handed a cookie that * identifies the user (with the help of the ticket cache), we can * simply honor the user's preauthenticated status. (This call * uses the ticket cache behind the scenes. Separately, as a nice * side effect, this sets r->user to NULL if we don't * find a cookie; that way, if we proceed, we know that r->connnection-> * user doesn't have some bogus value in in. */ if (r->user = get_netid_from_cookies(r)) { mod_cas_conf *c; // retrieve our configuration c = (mod_cas_conf *) ap_get_module_config( r->server->module_config, &cas_module); if (c->CASLogoutParameter && c->CASLogoutLocalURL && is_parameter_true(r,c->CASLogoutParameter)) { LOG("-> logout parameter set... logging user out\n"); logout(r); LOG("-> redirecting to logout page\n"); apr_table_set(r->headers_out, "Location", c->CASLogoutLocalURL); return HTTP_MOVED_TEMPORARILY; } else { LOG("-> authenticated from cookie\n"); return OK; } } /* * "CAS" is the AuthType, so we consider the request in terms of * a service and a ticket. */ service = get_service(r, urlbuf, URLBUFSIZE); ticket = get_ticket(r); LOG(" service = '"); LOG(service); LOG("'; ticket = '"); if (ticket) LOG(ticket); else LOG("(null)"); LOG("'\n"); if (service == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "mod_cas: null " "service"); return HTTP_INTERNAL_SERVER_ERROR; /* shouldn't happen */ } LOG("about to call CAS_validate()\n"); if (ticket != NULL && (validate_status = CAS_validate(ticket, service, userbuf, USERBUFSIZE)) == 1) { LOG("-> successful primary authentication for '"); LOG(userbuf); LOG("'\n"); // Successful authentication r->user = userbuf; // If relevant, send a cookie since we authenticated the user with // a real CAS ticket create_and_send_new_ticket(r); // Remove ticket from query string p = r->args; if (p != NULL) if (p == strstr(p, "ticket=")) // a comparison... *p = '\0'; else if (p = strstr(p, "&ticket=")) // yes, we're setting p *p = '\0'; return OK; } else if (validate_status == BAD_CERT) { LOG("-> certificate error\n"); ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "mod_cas: certificate " "error: bad cert or verisignserverca.pem is not available."); return HTTP_INTERNAL_SERVER_ERROR; } else { /* Bad or missing ticket, so bounce the user to CAS. */ char *temp = alloca(strlen(CAS_LOGIN_URL) + strlen("?service=") + strlen(service) + 1); if (!temp) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "mod_cas: alloca " "failed in function create_and_send_new_ticket()"); return HTTP_INTERNAL_SERVER_ERROR; } strcpy(temp, CAS_LOGIN_URL); strcat(temp, "?service="); strcat(temp, service); apr_table_set(r->headers_out, "Location", temp); return HTTP_MOVED_TEMPORARILY; }}/* * Compares the authenticated user (from CAS) with the list of users (and * groups) authorized by the "Require" directives. NOTE: We implement * set union here because that's how Apache modules typically work; the word * "Require" might incorrectly lead you to assume that we perform set
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -