| /* $Id$ */ |
| |
| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| /* |
| ** mod_authz_annotate.c -- Apache sample authz_annotate module |
| ** [Autogenerated via ``apxs -n authz_annotate -g''] |
| ** |
| ** To play with this sample module, first compile it into a |
| ** DSO file and install it into Apache's libexec directory |
| ** by running: |
| ** |
| ** $ apxs -c -i mod_authz_annotate.c |
| ** |
| ** Then activate it in Apache's apache.conf file, for instance |
| ** for the URL /authz_annotate, as follows: |
| ** |
| ** # apache.conf |
| ** LoadModule authz_annotate_module libexec/mod_authz_annotate.so |
| ** <Location /authz_annotate> |
| ** SetHandler authz_annotate |
| ** </Location> |
| ** |
| ** Then after restarting Apache via |
| ** |
| ** $ apachectl restart |
| ** |
| ** you immediately can request the URL /%NAME and watch for the |
| ** output of this module. This can be achieved for instance via: |
| ** |
| ** $ lynx -mime_header http://localhost/authz_annotate |
| ** |
| ** The output should be similar to the following one: |
| ** |
| ** HTTP/1.1 200 OK |
| ** Date: Tue, 31 Mar 1998 14:42:22 GMT |
| ** Server: Apache/1.3.4 (Unix) |
| ** Connection: close |
| ** Content-Type: text/html |
| ** |
| ** The sample page from mod_authz_annotate.c |
| */ |
| |
| |
| /* Apache configuration merging strategy: |
| * |
| * Apache processes <Location> directives in the order in which they |
| * appear in the configuration file(s). |
| * The merge_config() code below now takes each |
| * successive <Location> directive which specifies a given |
| * mod_authz_annotate configuration, with one or more authority urls |
| * to contact and takes the unique union of those authorities. If the |
| * configurations ask for different combinations of ACLs and IDs, the |
| * system favors asking for ACLs and IDs over not (so if one requests |
| * it, and the other does not, the union will be to request it). |
| * |
| * |
| * For example given the configuration below: |
| * |
| * <Location /clients/search/services/saved-search/JSON> |
| * AuthzAnnotateEnable On |
| * AuthzAnnotateIDAclAuthority http://localhost:7039/UserACLs |
| * </Location> |
| * |
| * <Location /clients/search> |
| * AuthzAnnotateEnable On |
| * AuthzAnnotateIDAclAuthority http://localhost:7039/UserACLs |
| * </Location> |
| * |
| */ |
| |
| |
| // global for the current process to track if curl initialization failed |
| static int curl_init_failed; |
| |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_log.h" |
| #include "http_protocol.h" |
| #include "http_request.h" |
| #include "ap_config.h" |
| |
| #include "constants.h" |
| |
| #include <stdio.h> |
| |
| #include <curl/curl.h> |
| |
| #define MODULE_NAME "mod_authz_annotate" |
| #define MODULE_VERSION "4.6.0" |
| |
| #define HERE() fprintf(stderr, "\n" __FILE__ ":%d\n\n", __LINE__); |
| |
| static const char __rcsid_v_[] __attribute__((__unused__)) = "\100(#)" "$Id$"; |
| |
| #define PARAM_USERNAME "username" |
| #define PARAM_ID_NEEDED "idneeded" |
| #define PARAM_ACL_NEEDED "aclneeded" |
| |
| #define TRUE_STRING "true" |
| #define FALSE_STRING "false" |
| |
| /* These are the prefixes for user id and for acl tokens, expected back from the authority services |
| */ |
| #define ID_PREFIX "ID:" |
| #define TOKEN_PREFIX "TOKEN:" |
| #define UNREACHABLE_PREFIX "UNREACHABLEAUTHORITY:" |
| #define NOTFOUND_PREFIX "USERNOTFOUND:" |
| #define UNAUTHORIZED_PREFIX "UNAUTHORIZED:" |
| #define AUTHORIZED_PREFIX "AUTHORIZED:" |
| |
| /* |
| Structure that holds all the connect urls |
| */ |
| struct ConfigURL { |
| struct ConfigURL* next; |
| char* connecturl; |
| int idneeded; |
| int aclneeded; |
| }; |
| |
| /* |
| Configuration information structure. |
| */ |
| struct Config { |
| int activated; |
| int infer_service_authz; |
| // explicit flags are used to favor explicit setting of the flag on over a |
| // default off when merging configs, while still allowing explicit off to work |
| int activated_explicit; |
| int infer_service_authz_explicit; |
| struct ConfigURL* first; |
| }; |
| |
| |
| /*************************************************************************** |
| Macros To Ease Compatibility |
| ***************************************************************************/ |
| #ifdef STANDARD20_MODULE_STUFF |
| #include <apr_strings.h> |
| |
| #define APR_POOL_T apr_pool_t |
| #define APR_TABLE_T apr_table_t |
| #define APR_ARRAY_HEADER_T apr_array_header_t |
| #define APR_TABLE_ENTRY_T apr_table_entry_t |
| #define APACHE_REQUEST_USER r->user |
| |
| #define log_rerror(file_line, level, r, fmt, rest...) \ |
| ap_log_rerror(file_line, level | APLOG_NOERRNO, 0, r, fmt , ## rest); |
| |
| #else |
| |
| #define APR_POOL_T pool |
| #define APR_TABLE_T table |
| #define APR_ARRAY_HEADER_T array_header |
| #define APR_TABLE_ENTRY_T table_entry |
| #define APACHE_REQUEST_USER r->connection->user |
| |
| #define apr_psprintf ap_psprintf |
| #define apr_pstrcat ap_pstrcat |
| #define apr_palloc ap_palloc |
| #define apr_pcalloc ap_pcalloc |
| #define apr_table_make ap_make_table |
| #define apr_table_add ap_table_add |
| #define apr_table_get ap_table_get |
| #define apr_table_set ap_table_set |
| #define apr_table_elts ap_table_elts |
| #define apr_table_unset ap_table_unset |
| #define apr_is_empty_table ap_is_empty_table |
| |
| #define log_rerror(file_line, level, r, fmt, rest...) \ |
| ap_log_rerror(file_line, level | APLOG_NOERRNO, r, fmt , ## rest); |
| |
| #endif /* STANDARD20_MODULE_STUFF */ |
| |
| /* |
| Dynamic data accumulation structure. |
| */ |
| struct DataBlock { |
| int length; |
| void* buffer; |
| int capacity; |
| struct DataBlock* next; |
| }; |
| |
| struct DataBuffer { |
| struct APR_POOL_T* p; |
| struct DataBlock* first; |
| struct DataBlock* last; |
| }; |
| |
| struct DataIterator { |
| struct DataBlock* currentBlock; |
| int currentOffset; |
| }; |
| |
| |
| #ifdef STANDARD20_MODULE_STUFF |
| module AP_MODULE_DECLARE_DATA authz_annotate_module; |
| #else |
| module MODULE_VAR_EXPORT authz_annotate_module; |
| #endif |
| |
| /* Take two lists of connecturls, take the list new and insert it into |
| * merge. If the connecturl is not in merge add it. If the |
| * connecturl is already in merge, union the requested parameters (if |
| * either reqested an id/acl request the id/acl). |
| */ |
| static |
| void |
| merge_url_lists(APR_POOL_T *p, struct Config *merge, const struct Config *new) { |
| struct ConfigURL* new_iter; |
| struct ConfigURL* merge_iter; |
| |
| // for every entry in the new list |
| for(new_iter = new->first; new_iter != NULL; new_iter = new_iter->next) { |
| |
| // search for it in the merged list |
| int found = FALSE; |
| for(merge_iter = merge->first; merge_iter != NULL; merge_iter = merge_iter->next) { |
| |
| // if we find the new url in the merge list |
| if (! strcmp(new_iter->connecturl, merge_iter->connecturl)) { |
| merge_iter->idneeded = merge_iter->idneeded || new_iter->idneeded; |
| merge_iter->aclneeded = merge_iter->aclneeded || new_iter->aclneeded; |
| found = TRUE; |
| break; |
| } |
| } |
| |
| // if the url was not found add it |
| if (!found) { |
| struct ConfigURL* copyURL = (struct ConfigURL*) apr_pcalloc(p, sizeof(struct ConfigURL)); |
| copyURL->connecturl = new_iter->connecturl; |
| copyURL->idneeded = new_iter->idneeded; |
| copyURL->aclneeded = new_iter->aclneeded; |
| copyURL->next = merge->first; |
| merge->first = copyURL; |
| } |
| } |
| } |
| |
| /* insert connecturl into authority list. if the connecturl is not |
| * already in the list add it. if it is in the list already union the |
| * requested parameters (if either requested an id/acl request the |
| * id/acl). |
| */ |
| static |
| void |
| insert_url_into_list(APR_POOL_T *p, struct Config *list, char *connecturl, const int idneeded, const int aclneeded) { |
| struct ConfigURL* list_iter; |
| |
| // search for it in the merged list |
| int found = FALSE; |
| for(list_iter = list->first; list_iter != NULL; list_iter = list_iter->next) { |
| |
| // if we find the new url in the list list |
| if (! strcmp(connecturl, list_iter->connecturl)) { |
| list_iter->idneeded = list_iter->idneeded || idneeded; |
| list_iter->aclneeded = list_iter->aclneeded || aclneeded; |
| found = TRUE; |
| break; |
| } |
| } |
| |
| if (!found) { |
| struct ConfigURL* record = (struct ConfigURL*)apr_pcalloc(p, sizeof(struct ConfigURL)); |
| record->connecturl = connecturl; |
| record->idneeded = idneeded; |
| record->aclneeded = aclneeded; |
| record->next = list->first; |
| list->first = record; |
| } |
| } |
| |
| /* Set whether this mod is activated. |
| */ |
| static |
| const char* |
| activation_flag_cmd(cmd_parms *parms, void *mconfig, int flag) { |
| struct Config* cfg = (struct Config*)mconfig; |
| cfg->activated = flag; |
| cfg->activated_explicit = 1; |
| return NULL; |
| } |
| |
| /* Set whether old-style service authz should be inferred |
| */ |
| static |
| const char* |
| infer_service_authz_cmd(cmd_parms *parms, void *mconfig, int flag) { |
| struct Config* cfg = (struct Config*)mconfig; |
| cfg->infer_service_authz = flag; |
| cfg->infer_service_authz_explicit = 1; |
| return NULL; |
| } |
| |
| /* Set the hostname; no id requested but acls requested. |
| */ |
| static |
| const char* |
| connecturl_noid_yesacl_cmd(cmd_parms* parms, void* mconfig, char* string) { |
| struct Config* cfg = (struct Config*)mconfig; |
| // Add a new record to the start of the string |
| APR_POOL_T* p = parms->pool; |
| insert_url_into_list(p, cfg, string, FALSE, TRUE); |
| return NULL; |
| } |
| |
| /* Set the hostname; id requested but acls not requested. |
| */ |
| static |
| const char* |
| connecturl_yesid_noacl_cmd(cmd_parms* parms, void* mconfig, char* string) { |
| struct Config* cfg = (struct Config*)mconfig; |
| // Add a new record to the start of the string |
| APR_POOL_T * p = parms->pool; |
| insert_url_into_list(p, cfg, string, TRUE, FALSE); |
| return NULL; |
| } |
| |
| /* Set the hostname; id requested and acls requested. |
| */ |
| static |
| const char* |
| connecturl_yesid_yesacl_cmd(cmd_parms* parms, void* mconfig, char* string) { |
| struct Config* cfg = (struct Config*)mconfig; |
| // Add a new record to the start of the string |
| APR_POOL_T* p = parms->pool; |
| insert_url_into_list(p, cfg, string, TRUE, TRUE); |
| return NULL; |
| } |
| |
| /* Create a configuration record for a given dir. |
| */ |
| static |
| void* |
| create_config(APR_POOL_T *p, char *dir) { |
| struct Config* cfg = (struct Config*) apr_pcalloc(p, sizeof(struct Config)); |
| cfg->activated = cfg->activated_explicit = 0; |
| cfg->infer_service_authz = cfg->infer_service_authz_explicit = 0; |
| cfg->first = NULL; |
| return (void *) cfg; |
| } |
| |
| /* Merge two configuration records for a given dir. |
| */ |
| static |
| void* |
| merge_config(APR_POOL_T *p, void* base_conf, void* new_conf) { |
| // According to apache, we can't modify base_conf and new_conf here, so make a copy |
| struct Config* cfg = (struct Config*) apr_pcalloc(p, sizeof(struct Config)); |
| struct Config* oldguy = (struct Config*)base_conf; |
| struct Config* newguy = (struct Config*)new_conf; |
| |
| // If old conf had explicit params and newconf doesn't, use old; otherwise use new |
| // (this way later blocks can explicitly turn it off, but will keep switches from |
| // earlier blocks if later blocks just use the defaults) |
| cfg->activated = |
| (oldguy->activated_explicit && (!newguy->activated_explicit)) ? |
| oldguy->activated : newguy->activated; |
| cfg->infer_service_authz = |
| (oldguy->infer_service_authz_explicit && (!newguy->infer_service_authz_explicit)) ? |
| oldguy->infer_service_authz : newguy->infer_service_authz; |
| cfg->activated_explicit = |
| oldguy->activated_explicit || newguy->activated_explicit; |
| cfg->infer_service_authz_explicit = |
| oldguy->infer_service_authz_explicit || newguy->infer_service_authz_explicit; |
| |
| cfg->first = NULL; |
| if (oldguy->first != NULL) { |
| if (newguy->first != NULL) { |
| // if both lists have something merge them both |
| merge_url_lists(p, cfg, oldguy); |
| merge_url_lists(p, cfg, newguy); |
| } |
| else { |
| // if new is empty; copy old |
| cfg->first = oldguy->first; |
| } |
| } |
| else if (newguy->first != NULL) { |
| // if old is empty copy new |
| cfg->first = newguy->first; |
| } |
| |
| return cfg; |
| } |
| |
| /* Support method for writing request stream. |
| */ |
| static |
| size_t |
| write_data(void *ptr, size_t size, size_t nmemb, void *stream) { |
| struct DataBuffer* dataBuffer = (struct DataBuffer*)stream; |
| int remainingAmount = size * nmemb; |
| while (1) { |
| int amountToCopy; |
| |
| if (remainingAmount == 0) { |
| break; |
| } |
| |
| if (dataBuffer->last == NULL || dataBuffer->last->capacity == dataBuffer->last->length) { |
| struct DataBlock* newBlock; |
| |
| // Allocate new block |
| newBlock = (struct DataBlock*)apr_palloc(dataBuffer->p,sizeof(struct DataBlock)); |
| newBlock->next = NULL; |
| newBlock->length = 0; |
| newBlock->capacity = 4096; |
| newBlock->buffer = (void *)apr_palloc(dataBuffer->p,newBlock->capacity); |
| if (dataBuffer->last == NULL) { |
| dataBuffer->last = newBlock; |
| dataBuffer->first = newBlock; |
| } |
| else { |
| dataBuffer->last->next = newBlock; |
| dataBuffer->last = newBlock; |
| } |
| } |
| |
| // See if the remaining amount fits or not |
| amountToCopy = dataBuffer->last->capacity - dataBuffer->last->length; |
| if (amountToCopy > remainingAmount) { |
| amountToCopy = remainingAmount; |
| } |
| memcpy(((unsigned char*)dataBuffer->last->buffer)+dataBuffer->last->length,ptr,amountToCopy); |
| ptr = ((unsigned char*)ptr) + amountToCopy; |
| dataBuffer->last->length += amountToCopy; |
| remainingAmount -= amountToCopy; |
| } |
| return nmemb; |
| } |
| |
| /* Method to initialize a buffer iterator |
| */ |
| static |
| void |
| iterator_initialize(struct DataIterator* di, struct DataBuffer* dataBuffer) { |
| di->currentBlock = dataBuffer->first; |
| di->currentOffset = 0; |
| } |
| |
| /* Method to read the next line. |
| * Return null if end. |
| */ |
| static |
| char* |
| iterator_getline(struct DataIterator* di, request_rec* r) { |
| // Find length first |
| // This is so we can be precise about allocating each string |
| |
| int length; |
| struct DataBlock* current; |
| int currentPos; |
| char* stringBuffer; |
| char* pointer; |
| |
| current = di->currentBlock; |
| length = 0; |
| currentPos = di->currentOffset; |
| while(1) { |
| char x; |
| |
| if (current == NULL) { |
| if (length == 0) { |
| return NULL; |
| } |
| break; |
| } |
| |
| // log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "Scanning a block of length %i", current->length); |
| |
| x = ((char*)current->buffer)[currentPos++]; |
| // log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "character: %i", (int)x); |
| if (currentPos == current->length) { |
| current = current->next; |
| currentPos = 0; |
| } |
| if (x == '\n') { |
| break; |
| } |
| if (x == '\r') { |
| continue; |
| } |
| length++; |
| } |
| |
| |
| // Allocate a string and fill it |
| stringBuffer = (char*)apr_palloc(r->pool,(length+1)*sizeof(char)); |
| pointer = stringBuffer; |
| while(1) { |
| char x; |
| |
| if (di->currentBlock == NULL){ |
| break; |
| } |
| |
| x = ((char*)di->currentBlock->buffer)[di->currentOffset++]; |
| if (di->currentOffset == di->currentBlock->length) { |
| di->currentBlock = di->currentBlock->next; |
| di->currentOffset = 0; |
| } |
| if (x == '\n') { |
| break; |
| } |
| if (x == '\r') { |
| continue; |
| } |
| *pointer++ = x; |
| } |
| *pointer = 0; |
| |
| return stringBuffer; |
| } |
| |
| |
| /* Check if a prefix matches a string |
| */ |
| static |
| int |
| prefix_matches(const char* prefix, const char* string) { |
| if (strlen(prefix) > strlen(string)) { |
| return FALSE; |
| } |
| while(1) { |
| if (*prefix == 0) { |
| return TRUE; |
| } |
| if (*prefix++ != *string++) { |
| return FALSE; |
| } |
| } |
| } |
| |
| |
| /* Do the work of talking to the service, and getting groups back. |
| */ |
| static |
| int |
| authz_annotate_users_and_groups(request_rec *r, |
| const char *userName, |
| APR_TABLE_T *userTable, |
| APR_TABLE_T *groupTable, |
| APR_TABLE_T *warningTable) { |
| CURLcode curl_error_code; |
| struct DataBuffer dataBuffer; |
| char* escapedUserName; |
| CURL *curl_handle; |
| char* urlString; |
| struct Config* cfg; |
| int rval = DECLINED; |
| int isKnown; |
| struct ConfigURL* currentURL; |
| const char * auth_line = NULL; |
| const char * auth_type = NULL; |
| struct curl_slist * headers = NULL; |
| |
| cfg = (struct Config*) ap_get_module_config(r->per_dir_config, &authz_annotate_module); |
| |
| if(curl_init_failed) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL global initialization failed"); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| curl_handle = curl_easy_init(); |
| if (curl_handle == NULL) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL easy init failed"); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| /* no progress meter */ |
| curl_error_code = curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1); |
| if (curl_error_code != CURLE_OK) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL easy set opt failed: %s", curl_easy_strerror(curl_error_code)); |
| curl_easy_cleanup(curl_handle); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| |
| curl_error_code = curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data); |
| if (curl_error_code != CURLE_OK) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL easy set opt failed: %s", curl_easy_strerror(curl_error_code)); |
| curl_easy_cleanup(curl_handle); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| curl_error_code = curl_easy_setopt(curl_handle, CURLOPT_FILE, &dataBuffer); |
| if (curl_error_code != CURLE_OK) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL easy set opt failed: %s", curl_easy_strerror(curl_error_code)); |
| curl_easy_cleanup(curl_handle); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| auth_line = apr_table_get(r->headers_in, "Authorization"); |
| if (auth_line != NULL) { |
| /* ap_getword_white returns the first whitespace-delimited word in |
| auth_line, or NULL if no non-whitespace is found. */ |
| auth_type = ap_getword_white(r->pool, &auth_line); |
| } |
| |
| /* Check to see if the client sent RFC 4459 SPNEGO authorization |
| data. If so, we need to pass the Authorization header to the |
| authority service so that it can be decoded. Active Directory |
| authorization needs this so that it can get group memberships |
| from the Kerberos PAC. */ |
| if (auth_line != NULL && auth_type != NULL && strcasecmp(auth_type, "Negotiate") == 0) { |
| char * authhdr = apr_psprintf(r->pool, "Authorization: %s", auth_line); |
| if (authhdr == NULL) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "apache failed to allocate Authorization header"); |
| curl_easy_cleanup(curl_handle); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| headers = curl_slist_append(NULL, authhdr); |
| if (headers == NULL) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL failed to allocated slist"); |
| curl_easy_cleanup(curl_handle); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| curl_error_code = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers); |
| if(curl_error_code != CURLE_OK) { |
| log_rerror( APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL easy set opt failed: %s", curl_easy_strerror(curl_error_code)); |
| curl_slist_free_all(headers); |
| curl_easy_cleanup(curl_handle); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| } |
| else { |
| /* Do not include header data in buffer */ |
| curl_error_code = curl_easy_setopt( curl_handle, CURLOPT_HEADER, (long)0); |
| if( curl_error_code != CURLE_OK ) { |
| log_rerror( APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL easy set opt failed: %s", curl_easy_strerror(curl_error_code)); |
| curl_easy_cleanup(curl_handle); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| } |
| |
| escapedUserName = curl_escape(userName,strlen(userName)); |
| if (escapedUserName == NULL) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "curl escape username failed"); |
| curl_slist_free_all(headers); |
| curl_easy_cleanup(curl_handle); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| // Grab the start of the list of authorities |
| currentURL = cfg->first; |
| |
| // If there are no authorities, the isKnown check defaults to letting |
| // the user in since nobody not knowing about them is not a sin. |
| if (currentURL == NULL) { |
| isKnown = TRUE; |
| } |
| else { |
| isKnown = FALSE; |
| } |
| |
| // Loop through all of the configured authorities |
| while (rval == DECLINED && currentURL != NULL) { |
| const char* idneeded_value = (currentURL->idneeded) ? |
| TRUE_STRING : FALSE_STRING; |
| const char* aclneeded_value = (currentURL->aclneeded) ? |
| TRUE_STRING : FALSE_STRING; |
| |
| // Tack on a ? if there are no params in the configured URL, |
| // or an & if there are. |
| const char* initial_delim = (strchr(currentURL->connecturl, '?') == NULL) ? "?" : "&"; |
| |
| urlString = apr_pstrcat(r->pool, |
| currentURL->connecturl, |
| initial_delim, |
| PARAM_USERNAME, "=", escapedUserName, |
| "&", |
| PARAM_ID_NEEDED, "=", idneeded_value, |
| "&", |
| PARAM_ACL_NEEDED, "=", aclneeded_value, |
| NULL); |
| |
| // Initialize dataBuffer |
| dataBuffer.p = r->pool; |
| dataBuffer.first = NULL; |
| dataBuffer.last = NULL; |
| |
| curl_error_code = curl_easy_setopt(curl_handle, CURLOPT_URL, urlString); |
| if (curl_error_code != CURLE_OK) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL easy set opt failed: %s", curl_easy_strerror(curl_error_code)); |
| rval = HTTP_INTERNAL_SERVER_ERROR; |
| break; |
| } |
| |
| log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "About to request %s%s%s from url '%s' (%s)", |
| ((currentURL->idneeded) ? "identifier " : ""), |
| ((currentURL->idneeded && currentURL->aclneeded)? "and " : ""), |
| ((currentURL->aclneeded) ? "acls ":""), |
| currentURL->connecturl, urlString); |
| |
| curl_error_code = curl_easy_perform(curl_handle); |
| if (curl_error_code != CURLE_OK) { |
| // failed to connect to authority service; log error and terminate authorization processing |
| log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "CURL error from url '%s': %s", currentURL->connecturl, curl_easy_strerror(curl_error_code)); |
| rval = HTTP_INTERNAL_SERVER_ERROR; |
| break; |
| } |
| else { |
| |
| // get response code |
| long response_code; |
| curl_error_code = curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &response_code); |
| if (curl_error_code != CURLE_OK) { |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, r, "CURL get response code failed"); |
| rval = HTTP_INTERNAL_SERVER_ERROR; |
| break; |
| } else if(response_code == 403) { |
| // if it's 403, the authority is signaling there's no safe way to reply |
| // (e.g., it may not be able to provide the proper deny token), and so |
| // we need to also slam the door shut, regardless of whether the infer |
| // flag is on or not |
| rval = HTTP_UNAUTHORIZED; |
| break; |
| } |
| |
| char* responseLine; |
| |
| struct DataIterator di; |
| |
| iterator_initialize(&di,&dataBuffer); |
| |
| log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "Decoding response"); |
| |
| while(1) { |
| responseLine = iterator_getline(&di,r); |
| if(responseLine == NULL) { |
| break; |
| } |
| |
| if (prefix_matches(ID_PREFIX,responseLine)) { |
| // Enter string into user table |
| apr_table_set(userTable, responseLine + strlen(ID_PREFIX), ""); |
| } |
| else if (prefix_matches(TOKEN_PREFIX,responseLine)) { |
| // Enter this string into the group table |
| apr_table_set(groupTable, responseLine + strlen(TOKEN_PREFIX), ""); |
| } |
| else if (prefix_matches(UNREACHABLE_PREFIX,responseLine) |
| || prefix_matches(UNAUTHORIZED_PREFIX,responseLine)) { |
| // note in warning table |
| apr_table_set(warningTable, responseLine, ""); |
| // if we're inferring service authz, deny access |
| if(cfg->infer_service_authz) { |
| rval = HTTP_UNAUTHORIZED; |
| break; |
| } |
| } |
| else if (prefix_matches(NOTFOUND_PREFIX,responseLine)) { |
| // note in warning table and do nothing |
| apr_table_set(warningTable, responseLine, ""); |
| } |
| else if (prefix_matches(AUTHORIZED_PREFIX,responseLine)) { |
| // note that at least one authority has now authorized the user |
| isKnown = TRUE; |
| } |
| else { |
| // invalid response from authority service; log error and terminate authorization processing |
| log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, |
| "Invalid string from authority service '%s'", responseLine); |
| rval = HTTP_INTERNAL_SERVER_ERROR; |
| break; |
| } |
| } |
| } |
| |
| // Free the data buffer |
| // no need, since it's in apache pool |
| |
| // Free the full url |
| // no need, since it's in apache pool |
| |
| // Next URL |
| currentURL = currentURL->next; |
| } |
| |
| log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, "Completed requests to services"); |
| |
| curl_easy_cleanup(curl_handle); |
| |
| curl_free(escapedUserName); |
| |
| // if no one returned authorized and we're inferring service authz from that, |
| // deny access now |
| if (cfg->infer_service_authz && rval == DECLINED && isKnown == FALSE) { |
| curl_slist_free_all(headers); |
| return HTTP_UNAUTHORIZED; |
| } |
| |
| curl_slist_free_all(headers); |
| return rval; |
| } |
| |
| |
| /* |
| Main method that does everything. |
| */ |
| static |
| int |
| authz_annotate(request_rec *r, const char *connection_user, |
| APR_TABLE_T **user_table, APR_TABLE_T **group_table, APR_TABLE_T **warning_table) { |
| |
| if (user_table == NULL) { |
| log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "invalid user_table"); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| if (group_table == NULL) { |
| log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "invalid group_table"); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| |
| // For the purposes of the java connector, the connection_user is passed in without change |
| |
| //NOTE: do we need to verify that apr_table_make succeeded? |
| *user_table = (APR_TABLE_T *)apr_table_make(r->pool, DEFAULT_NUM_USERS); |
| *group_table = (APR_TABLE_T *)apr_table_make(r->pool, DEFAULT_NUM_GROUPS); |
| *warning_table = (APR_TABLE_T *)apr_table_make(r->pool, DEFAULT_NUM_WARNING); |
| |
| // Obtain groups via http |
| return authz_annotate_users_and_groups(r,connection_user,*user_table,*group_table,*warning_table); |
| } |
| |
| |
| /* |
| Convert a table to a header. |
| */ |
| static |
| int |
| transform_table_to_http_header(APR_TABLE_T* the_table, |
| request_rec *r, |
| const char *header_name, |
| const unsigned int header_name_length, |
| unsigned int *apache_header_size) { |
| const APR_ARRAY_HEADER_T *the_array; |
| APR_TABLE_ENTRY_T *telts; |
| int i; |
| |
| the_array = apr_table_elts(the_table); |
| telts = (APR_TABLE_ENTRY_T*)the_array->elts; |
| |
| for(i = 0; i < the_array->nelts; i++) { |
| apr_table_add(r->headers_in, header_name, telts[i].key); |
| |
| if (apache_header_size != NULL) { |
| *apache_header_size += header_name_length + HEADER_SEPARATOR_LEN + strlen(telts[i].key); |
| } |
| } |
| |
| return the_array->nelts; |
| } |
| |
| #if 0 |
| // List contents of header |
| static |
| int |
| list_stuff(request_rec *r, const char* key, const char* value) { |
| if (strcmp(key,USER_HEADER_KEY) == 0 || strcmp(key,GROUP_HEADER_KEY) == 0) { |
| log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r, |
| "Found key = %s value = %s",key,value); |
| } |
| return 0; |
| } |
| #endif |
| |
| |
| /* |
| Clean headers handler. This will be done regardless of the authorization configuration. |
| */ |
| static |
| int |
| authz_clean_headers_handler(request_rec *r) { |
| struct Config* cfg; |
| |
| cfg = (struct Config*) ap_get_module_config(r->per_dir_config, &authz_annotate_module); |
| |
| /* Skip processing if we're not active. */ |
| if( cfg->activated == 0 ) { |
| return DECLINED; |
| } |
| |
| /* Get rid of any passed in authz headers to prevent spoofing. */ |
| apr_table_unset(r->headers_in, USER_NAME_HEADER_KEY); |
| apr_table_unset(r->headers_in, USER_HEADER_KEY); |
| apr_table_unset(r->headers_in, GROUP_HEADER_KEY); |
| |
| return OK; |
| } |
| |
| |
| static |
| void |
| authz_annotate_add_username_header(request_rec *r) { |
| // if there is a valid username |
| if (APACHE_REQUEST_USER != NULL) { |
| // escape the username to eliminate leading or trailing spaces |
| // which are dropped in http headers |
| char *escaped_username = ap_escape_uri(r->pool, APACHE_REQUEST_USER); |
| |
| // stuff username into headers for webui (tomcat) |
| apr_table_add(r->headers_in, USER_NAME_HEADER_KEY, escaped_username); |
| } |
| } |
| |
| /* |
| This is the content handler |
| */ |
| static |
| int |
| authz_annotate_authorization_handler(request_rec *r) { |
| struct Config* cfg; |
| int authz_annotate_result; |
| APR_TABLE_T *user_table; |
| APR_TABLE_T *group_table; |
| APR_TABLE_T *warning_table; |
| |
| cfg = (struct Config*) ap_get_module_config(r->per_dir_config, &authz_annotate_module); |
| |
| /* Everything OK by default if we're not even active. */ |
| if( cfg->activated == 0 ) { |
| return DECLINED; |
| } |
| |
| /* We must have a user to authorize against */ |
| if (APACHE_REQUEST_USER == NULL) { |
| return HTTP_UNAUTHORIZED; |
| } |
| |
| /* if this is a subrequest, we've already done our annotation work so DECLINE to do anything with it */ |
| if (! ap_is_initial_req(r)) { |
| return DECLINED; |
| } |
| |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, |
| "Authorization request for user '%s'", |
| APACHE_REQUEST_USER); |
| |
| // add username header |
| authz_annotate_add_username_header(r); |
| |
| authz_annotate_result = authz_annotate(r, APACHE_REQUEST_USER, &user_table, &group_table, &warning_table); |
| |
| if (authz_annotate_result == DECLINED) { |
| unsigned int apache_header_size = 0; |
| unsigned int number_of_users_added = 0; |
| unsigned int number_of_groups_added = 0; |
| unsigned int number_of_warning_added = 0; |
| |
| number_of_users_added = transform_table_to_http_header(user_table, |
| r, |
| USER_HEADER_KEY, |
| STATIC_STRLEN(USER_HEADER_KEY), |
| &apache_header_size); |
| |
| number_of_groups_added = transform_table_to_http_header(group_table, |
| r, |
| GROUP_HEADER_KEY, |
| STATIC_STRLEN(GROUP_HEADER_KEY), |
| &apache_header_size); |
| |
| if(!apr_is_empty_table(warning_table)) { |
| number_of_warning_added = transform_table_to_http_header(warning_table, |
| r, |
| WARNING_HEADER_KEY, |
| STATIC_STRLEN(WARNING_HEADER_KEY), |
| &apache_header_size); |
| } |
| |
| log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, |
| "users(%u) groups(%u) warning(%u) header size: %u", |
| number_of_users_added, |
| number_of_groups_added, |
| number_of_warning_added, |
| apache_header_size); |
| } |
| |
| return authz_annotate_result; |
| } |
| |
| /* |
| Write a table to the request header. |
| */ |
| static |
| void |
| print_table_to_client(APR_TABLE_T* the_table, |
| request_rec *r, |
| const char *header_name) { |
| const APR_ARRAY_HEADER_T *the_array; |
| APR_TABLE_ENTRY_T *telts; |
| int i; |
| |
| the_array = apr_table_elts(the_table); |
| telts = (APR_TABLE_ENTRY_T*)the_array->elts; |
| |
| for(i = 0; i < the_array->nelts; i++) { |
| ap_rprintf(r, "%s: %s\n", header_name, telts[i].key); |
| } |
| } |
| |
| static |
| int |
| authz_annotate_content_handler(request_rec *r) { |
| APR_TABLE_T *user_table; |
| APR_TABLE_T *group_table; |
| APR_TABLE_T *warning_table; |
| int authz_annotate_result; |
| const char *user_name; |
| |
| #ifdef STANDARD20_MODULE_STUFF |
| if (!r->handler || strcmp(r->handler, "authz-annotate")) { |
| return DECLINED; |
| } |
| #endif |
| |
| user_name = apr_table_get(r->headers_in, FAKE_USER_HEADER_KEY); |
| if(user_name == NULL) { |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| authz_annotate_result = authz_annotate(r, user_name, &user_table, &group_table, &warning_table); |
| |
| #ifdef STANDARD20_MODULE_STUFF |
| ap_set_content_type(r, "text/plain"); |
| #else |
| r->content_type = "text/plain"; |
| ap_send_http_header(r); |
| #endif |
| |
| // TODO: for consistency we should REALLY send the USER_NAME_HEADER here as well |
| |
| if (authz_annotate_result == DECLINED) { |
| print_table_to_client(user_table, r, USER_HEADER_KEY); |
| print_table_to_client(group_table, r, GROUP_HEADER_KEY); |
| print_table_to_client(warning_table, r, WARNING_HEADER_KEY); |
| } |
| else { |
| ap_rprintf(r, "ERROR: internal failure (see /var/log/metacarta/error.log) %d\n", authz_annotate_result); |
| } |
| // MUST return ok, otherwise content will come from elsewhere (and get errors) |
| return OK; |
| } |
| |
| |
| #ifdef STANDARD20_MODULE_STUFF |
| #define command(name, func, var, type, usage) \ |
| AP_INIT_ ## type (name, (void*) func, NULL, OR_AUTHCFG | RSRC_CONF, usage) |
| #else |
| #define command(name, func, var, type, usage) \ |
| { name, func, NULL, OR_AUTHCFG | RSRC_CONF, type, usage } |
| #endif |
| |
| /* Configuration command description */ |
| static const command_rec authz_annotate_cmds[] = { |
| command("AuthzAnnotateEnable", activation_flag_cmd, NULL, FLAG,"Toggle state of authority feature"), |
| command("AuthzAnnotateInferServiceAuthz",infer_service_authz_cmd, NULL, FLAG,"Toggle state of authority feature"), |
| command("AuthzAnnotateAuthority", connecturl_noid_yesacl_cmd, NULL, TAKE1, "Protocol, host, port, and service to connect to"), |
| command("AuthzAnnotateACLAuthority", connecturl_noid_yesacl_cmd, NULL, TAKE1, "Protocol, host, port, and service to connect to"), |
| command("AuthzAnnotateIDAuthority", connecturl_yesid_noacl_cmd, NULL, TAKE1, "Protocol, host, port, and service to connect to"), |
| command("AuthzAnnotateIDACLAuthority", connecturl_yesid_yesacl_cmd, NULL, TAKE1, "Protocol, host, port, and service to connect to"), |
| {NULL} |
| }; |
| |
| |
| |
| #ifdef STANDARD20_MODULE_STUFF |
| |
| static |
| apr_status_t |
| authz_annotate_child_cleanup(void *data) { |
| curl_global_cleanup(); |
| return APR_SUCCESS; |
| } |
| |
| /* Child initialization. */ |
| static |
| void |
| authz_annotate_child_init(APR_POOL_T *p, server_rec *s) { |
| CURLcode curl_error; |
| |
| curl_error = curl_global_init(CURL_GLOBAL_NOTHING); |
| if (curl_error != CURLE_OK) { |
| // Write an error to the log |
| ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, 0, s, "Couldn't initialize CURL: %d %s", curl_error, curl_easy_strerror(curl_error)); |
| curl_init_failed = 1; |
| } |
| else { |
| curl_init_failed = 0; |
| } |
| |
| apr_pool_cleanup_register(p, s, authz_annotate_child_cleanup, authz_annotate_child_cleanup); |
| } |
| |
| |
| static |
| int |
| authz_annotate_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { |
| ap_add_version_component(p, MODULE_NAME "/" MODULE_VERSION); |
| return OK; |
| } |
| |
| |
| static |
| void |
| authz_annotate_register_hooks(apr_pool_t *p) { |
| ap_log_perror(APLOG_MARK, APLOG_DEBUG|APLOG_ERR, 0, p, "HERE I AM\n"); |
| |
| ap_hook_post_config(authz_annotate_init_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_child_init(authz_annotate_child_init, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_header_parser(authz_clean_headers_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_auth_checker(authz_annotate_authorization_handler, NULL, NULL, APR_HOOK_FIRST); |
| ap_hook_handler(authz_annotate_content_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| } |
| |
| module AP_MODULE_DECLARE_DATA authz_annotate_module = { |
| STANDARD20_MODULE_STUFF, |
| create_config, /* create per-dir conf structures */ |
| merge_config, /* merge per-dir conf structures */ |
| NULL, /* create per-server conf structures */ |
| NULL, /* merge per-server conf structures */ |
| authz_annotate_cmds, /* table of configuration directives */ |
| authz_annotate_register_hooks /* register hooks */ |
| }; |
| |
| #else |
| |
| /* Child initialization. */ |
| static |
| void |
| authz_annotate_child_init(server_rec *s, APR_POOL_T *p) { |
| CURLcode curl_error; |
| |
| curl_error = curl_global_init(CURL_GLOBAL_NOTHING); |
| if (curl_error != CURLE_OK) { |
| // Write an error to the log |
| ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, s, "Couldn't initialize CURL: %d %s", curl_error, curl_easy_strerror(curl_error)); |
| curl_init_failed = 1; |
| } |
| else { |
| curl_init_failed = 0; |
| } |
| } |
| |
| |
| /* Child exit. */ |
| static |
| void |
| authz_annotate_child_exit(server_rec *s, APR_POOL_T *p) { |
| curl_global_cleanup(); |
| } |
| |
| /* content handlers */ |
| static handler_rec authz_annotate_handlers[] = { |
| { "authz-annotate", authz_annotate_content_handler }, |
| { NULL } |
| }; |
| |
| /* Dispatch list for API hooks */ |
| module MODULE_VAR_EXPORT authz_annotate_module = { |
| STANDARD_MODULE_STUFF, |
| NULL, /* module initializer */ |
| create_config, /* create per-dir config structures */ |
| merge_config, /* merge per-dir config structures */ |
| NULL, /* create per-server config structures */ |
| NULL, /* merge per-server config structures */ |
| authz_annotate_cmds, /* table of config file commands */ |
| authz_annotate_handlers, /* [#8] MIME-typed-dispatched handlers */ |
| NULL, /* [#1] URI to filename translation */ |
| NULL, /* [#4] validate user id from request */ |
| authz_annotate_authorization_handler, /* [#5] check if the user is ok _here_ */ |
| NULL, /* [#3] check access by host address */ |
| NULL, /* [#6] determine MIME type */ |
| NULL, /* [#7] pre-run fixups */ |
| NULL, /* [#9] log a transaction */ |
| authz_clean_headers_handler, /* [#2] header parser */ |
| authz_annotate_child_init, /* child_init */ |
| authz_annotate_child_exit, /* child_exit */ |
| NULL /* [#0] post read-request */ |
| #ifdef EAPI |
| ,NULL, /* EAPI: add_module */ |
| NULL, /* EAPI: remove_module */ |
| NULL, /* EAPI: rewrite_command */ |
| NULL /* EAPI: new_connection */ |
| #endif |
| }; |
| |
| |
| #endif |