/* * * mod_auth_mellon.c: an authentication apache module * Copyright © 2003-2007 UNINETT (http://www.uninett.no/) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "auth_mellon.h" #include /* The size of the blocks we will allocate. */ #define AM_HC_BLOCK_SIZE 1000 /* This structure describes a single-linked list of downloaded blocks. */ typedef struct am_hc_block_s { /* The next block we have allocated. */ struct am_hc_block_s *next; /* The number of bytes written to this block. */ apr_size_t used; /* The data stored in this block. */ uint8_t data[AM_HC_BLOCK_SIZE]; } am_hc_block_t; /* This structure describes a header for the block list. */ typedef struct { /* The pool we will allocate memory for new blocks from. */ apr_pool_t *pool; /* The first block in the linked list of blocks. */ am_hc_block_t *first; /* The last block in the linked list of blocks. */ am_hc_block_t *last; } am_hc_block_header_t; /* This function allocates and initializes a block for data copying. * * Parameters: * apr_pool_t *pool The pool we should allocate the block from. * * Returns: * The new block we allocated. */ static am_hc_block_t *am_hc_block_alloc(apr_pool_t *pool) { am_hc_block_t *blk; blk = (am_hc_block_t *)apr_palloc(pool, sizeof(am_hc_block_t)); blk->next = NULL; blk->used = 0; return blk; } /* This function adds data to the end of a block, and allocates new blocks * if the data doesn't fit in one block. * * Parameters: * am_hc_block_t *block The block we should begin by appending data to. * apr_pool_t *pool The pool we should allocate memory for new blocks * from. * const uint8_t *data The data we should append to the blocks. * apr_size_t size The length of the data we should append. * * Returns: * The last block written to (i.e. the next block we should write to). */ static am_hc_block_t *am_hc_block_write( am_hc_block_t *block, apr_pool_t *pool, const uint8_t *data, apr_size_t size ) { apr_size_t num_cpy; /* Find the number of bytes we should write to this block. */ num_cpy = AM_HC_BLOCK_SIZE - block->used; if(num_cpy > size) { num_cpy = size; } /* Copy data to this block. */ memcpy(&block->data[block->used], data, num_cpy); block->used += num_cpy; if(block->used == AM_HC_BLOCK_SIZE) { /* This block is full. Allocate a new block, and continue * filling it. */ block->next = am_hc_block_alloc(pool); return am_hc_block_write(block->next, pool, &data[num_cpy], size - num_cpy); } /* The next write should be to this block. */ return block; } /* This function initializes a am_hc_block_header_t structure, which * contains information about the linked list of data blocks. * * Parameters: * am_hc_block_header_t *bh Pointer to the data header whcih we * should initialize. * apr_pool_t *pool The pool we should allocate data from. * * Returns: * Nothing. */ static void am_hc_block_header_init(am_hc_block_header_t *bh, apr_pool_t *pool) { bh->pool = pool; bh->first = am_hc_block_alloc(pool); bh->last = bh->first; } /* This function writes data to the linked list of blocks identified by * the stream-parameter. It matches the prototype required by curl. * * Parameters: * void *data The data that should be written. It is size*nmemb * bytes long. * size_t size The size of each block of data that should * be written. * size_t nmemb The number of blocks of data that should be written. * void *block_header A pointer to a am_hc_block_header_t structure which * identifies the linked list we should store data in. * * Returns: * The number of bytes that have been written. */ static size_t am_hc_data_write(void *data, size_t size, size_t nmemb, void *data_header) { am_hc_block_header_t *bh; bh = (am_hc_block_header_t *)data_header; bh->last = am_hc_block_write(bh->last, bh->pool, (const uint8_t *)data, size * nmemb); return size * nmemb; } /* This function fetches the data which was written to the databuffers * in the linked list which the am_hc_data_t structure keeps track of. * * Parameters: * am_hc_block_header_t *bh The header telling us which data buffers * we should extract data from. * apr_pool_t *pool The pool we should allocate the data * buffer from. * void **buffer A pointer to where we should store a pointer * to the data buffer we allocate. We will * always add a null-terminator to the end of * data buffer. This parameter can't be NULL. * apr_size_t *size This is a pointer to where we will store the * length of the data, not including the * null-terminator we add. This parameter can * be NULL. * * Returns: * Nothing. */ static void am_hc_data_extract(am_hc_block_header_t *bh, apr_pool_t *pool, void **buffer, apr_size_t *size) { am_hc_block_t *blk; apr_size_t length; uint8_t *buf; apr_size_t pos; /* First we find the length of the data. */ length = 0; for(blk = bh->first; blk != NULL; blk = blk->next) { length += blk->used; } /* Allocate memory for the data. Add one to the size in order to * have space for the null-terminator. */ buf = (uint8_t *)apr_palloc(pool, length + 1); /* Copy the data into the buffer. */ pos = 0; for(blk = bh->first; blk != NULL; blk = blk->next) { memcpy(&buf[pos], blk->data, blk->used); pos += blk->used; } /* Add the null-terminator. */ buf[length] = 0; /* Set up the return values. */ *buffer = (void *)buf; if(size != NULL) { *size = length; } } /* This function creates a curl object and performs generic initialization * of it. * * Parameters: * request_rec *r The request we should log errors against. * const char *uri The URI we should request. * am_hc_block_header_t *bh The buffer curl will write response data to. * char *curl_error A buffer of size CURL_ERROR_SIZE where curl * will store error messages. * * Returns: * A initialized curl object on succcess, or NULL on error. */ static CURL *am_httpclient_init_curl(request_rec *r, const char *uri, am_hc_block_header_t *bh, char *curl_error) { am_dir_cfg_rec *cfg = am_get_dir_cfg(r); CURL *curl; CURLcode res; /* Initialize the curl object. */ curl = curl_easy_init(); if(curl == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to initialize a curl object."); return NULL; } /* Set up error reporting. */ res = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_error); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to set curl error buffer: [%u]\n", res); goto cleanup_fail; } /* Disable progress reporting. */ res = curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to disable curl progress reporting: [%u] %s", res, curl_error); goto cleanup_fail; } /* Disable use of signals. */ res = curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to disable signals in curl: [%u] %s", res, curl_error); goto cleanup_fail; } /* Set the timeout of the transfer. It is currently set to two minutes. */ res = curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to set the timeout of the curl download:" " [%u] %s", res, curl_error); goto cleanup_fail; } /* If we have a CA configured, try to use it */ if (cfg->idp_ca_file != NULL) { res = curl_easy_setopt(curl, CURLOPT_CAINFO, cfg->idp_ca_file); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to set SSL CA info %s:" " [%u] %s", cfg->idp_ca_file, res, curl_error); goto cleanup_fail; } } /* Enable fail on http error. */ res = curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to enable failure on http error: [%u] %s", res, curl_error); goto cleanup_fail; } /* Select which uri we should download. */ res = curl_easy_setopt(curl, CURLOPT_URL, uri); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to set curl download uri to \"%s\": [%u] %s", uri, res, curl_error); goto cleanup_fail; } /* Set up data writing. */ /* Set curl write function. */ res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, am_hc_data_write); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to set the curl write function: [%u] %s", res, curl_error); goto cleanup_fail; } /* Set the curl write function parameter. */ res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, bh); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to set the curl write function data: [%u] %s", res, curl_error); goto cleanup_fail; } return curl; cleanup_fail: curl_easy_cleanup(curl); return NULL; } /* This function downloads data from a specified URI, with specified timeout * * Parameters: * request_rec *r The apache request this download is associated * with. It is used for memory allocation and logging. * const char *uri The URI we should download. * void **buffer A pointer to where we should store a pointer to the * downloaded data. We will always add a null-terminator * to the data. This parameter can't be NULL. * apr_size_t *size This is a pointer to where we will store the length * of the downloaded data, not including the * null-terminator we add. This parameter can be NULL. * apr_time_t timeout Timeout in seconds, 0 for no timeout. * long *status Pointer to HTTP status code. * * Returns: * OK on success, or HTTP_INTERNAL_SERVER_ERROR on failure. On failure we * will write a log message describing the error. */ int am_httpclient_get(request_rec *r, const char *uri, void **buffer, apr_size_t *size, apr_time_t timeout, long *status) { am_hc_block_header_t bh; CURL *curl; char curl_error[CURL_ERROR_SIZE]; CURLcode res; /* Initialize the data storage. */ am_hc_block_header_init(&bh, r->pool); /* Initialize the curl object. */ curl = am_httpclient_init_curl(r, uri, &bh, curl_error); if(curl == NULL) { return HTTP_INTERNAL_SERVER_ERROR; } res = curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to download data from the uri \"%s\", " "cannot set timeout to %ld: [%u] %s", uri, (long)timeout, res, curl_error); goto cleanup_fail; } res = curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to download data from the uri \"%s\", " "cannot set connect timeout to %ld: [%u] %s", uri, (long)timeout, res, curl_error); goto cleanup_fail; } /* Do the download. */ res = curl_easy_perform(curl); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to download data from the uri \"%s\", " "transaction aborted: [%u] %s", uri, res, curl_error); goto cleanup_fail; } if (status != NULL) { res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to download data from the uri \"%s\", " "no status report: [%u] %s", uri, res, curl_error); goto cleanup_fail; } } /* Free the curl object. */ curl_easy_cleanup(curl); /* Copy the data. */ am_hc_data_extract(&bh, r->pool, buffer, size); return OK; cleanup_fail: curl_easy_cleanup(curl); return HTTP_INTERNAL_SERVER_ERROR; } /* This function downloads data from a specified URI by issuing a POST * request. * * Parameters: * request_rec *r The apache request this download is * associated with. It is used for memory * allocation and logging. * const char *uri The URI we should post data to. * const void *post_data The POST data we should send. * apr_size_t post_length The length of the POST data. * const char *content_type The content type of the POST data. This * parameter can be NULL, in which case the * content type will be * "application/x-www-form-urlencoded". * void **buffer A pointer to where we should store a pointer * to the downloaded data. We will always add a * null-terminator to the data. This parameter * can't be NULL. * apr_size_t *size This is a pointer to where we will store the * length of the downloaded data, not including * the null-terminator we add. This parameter * can be NULL. * * Returns: * OK on success. On failure we will write a log message describing the * error, and return HTTP_INTERNAL_SERVER_ERROR. */ int am_httpclient_post(request_rec *r, const char *uri, const void *post_data, apr_size_t post_length, const char *content_type, void **buffer, apr_size_t *size) { am_hc_block_header_t bh; CURL *curl; char curl_error[CURL_ERROR_SIZE]; CURLcode res; struct curl_slist *ctheader; /* Initialize the data storage. */ am_hc_block_header_init(&bh, r->pool); /* Initialize the curl object. */ curl = am_httpclient_init_curl(r, uri, &bh, curl_error); if(curl == NULL) { return HTTP_INTERNAL_SERVER_ERROR; } /* Enable POST request. */ res = curl_easy_setopt(curl, CURLOPT_POST, 1L); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to enable POST request: [%u] %s", res, curl_error); goto cleanup_fail; } /* Set POST data size. */ res = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_length); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to set the POST data length: [%u] %s", res, curl_error); goto cleanup_fail; } /* Set POST data. */ res = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to set the POST data: [%u] %s", res, curl_error); goto cleanup_fail; } /* Set the content-type header. */ /* Set default content type if content_type is NULL. */ if(content_type == NULL) { content_type = "application/x-www-form-urlencoded"; } /* Create header list. */ ctheader = NULL; ctheader = curl_slist_append(ctheader, apr_pstrcat( r->pool, "Content-Type: ", content_type, NULL )); /* Set headers. */ res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, ctheader); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to set content-type header to \"%s\": [%u] %s", content_type, res, curl_error); goto cleanup_fail; } /* Do the download. */ res = curl_easy_perform(curl); if(res != CURLE_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to download data from the uri \"%s\": [%u] %s", uri, res, curl_error); goto cleanup_fail; } /* Free the curl object. */ curl_easy_cleanup(curl); /* Free the content-type header. */ curl_slist_free_all(ctheader); /* Copy the data. */ am_hc_data_extract(&bh, r->pool, buffer, size); return OK; cleanup_fail: curl_easy_cleanup(curl); return HTTP_INTERNAL_SERVER_ERROR; } /* This function downloads data from a specified URI by issuing a POST * request. * * Parameters: * request_rec *r The apache request this download is * associated with. It is used for memory * allocation and logging. * const char *uri The URI we should post data to. * const char *post_data The POST data we should send. * const char *content_type The content type of the POST data. This * parameter can be NULL, in which case the * content type will be * "application/x-www-form-urlencoded". * void **buffer A pointer to where we should store a pointer * to the downloaded data. We will always add a * null-terminator to the data. This parameter * can't be NULL. * apr_size_t *size This is a pointer to where we will store the * length of the downloaded data, not including * the null-terminator we add. This parameter * can be NULL. * * Returns: * OK on success. On failure we will write a log message describing the * error, and return HTTP_INTERNAL_SERVER_ERROR. */ int am_httpclient_post_str(request_rec *r, const char *uri, const char *post_data, const char *content_type, void **buffer, apr_size_t *size) { return am_httpclient_post(r, uri, post_data, strlen(post_data), content_type, buffer, size); }