/* ==================================================================== * Copyright (c) 1999-2002 Vincent Partington. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. 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. * * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY VINCENT PARTINGTON ``AS IS'' AND ANY * EXPRESSED 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 VINCENT PARTINGTON OR * ITS 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 "apr_strings.h" #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "http_request.h" #if MODULE_MAGIC_NUMBER_MAJOR < 20020128 #error "You need at least Apache 2.0.32 to compile this module" #endif #define COMMUNICATOR_HACK_ENABLED 1 #define ROAMING_FILE "roaming-file" #define ROAMING_HANDLER "roaming-handler" #define ROAMING_FILE_PERMS APR_UREAD|APR_UWRITE #define ROAMING_DIR_PERMS APR_UREAD|APR_UWRITE|APR_UEXECUTE #define LOG_URL_FORM_WARNING() \ ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, \ "Is the URL of the form http://///?"); /* * Configuration data types for mod_roaming. */ module AP_MODULE_DECLARE_DATA roaming_module; typedef struct { apr_array_header_t *aliases; } roaming_config_t; typedef struct { const char *uri; const char *dir; } roaming_alias_t; /* * Creates mod_roaming configuration struct. */ static void *roaming_create_server_config(apr_pool_t *p, server_rec * s) { roaming_config_t *rc; rc = (roaming_config_t *) apr_pcalloc(p, sizeof(roaming_config_t)); rc->aliases = apr_array_make(p, 3, sizeof(roaming_alias_t)); return rc; } /* * Implements RoamingAlias directive by adding the uri->dir mapping * to the list of roaming aliases. */ static const char *roaming_alias(cmd_parms *cmd, void *dummy, const char *uri, const char *dir) { apr_status_t rv; apr_finfo_t dir_info; roaming_config_t *rc; roaming_alias_t *ra; rv = apr_stat(&dir_info, dir, APR_FINFO_TYPE, cmd->pool); if(rv != APR_SUCCESS) { return apr_pstrcat(cmd->pool, "\"", dir, "\" does not exist", NULL); } else if(dir_info.filetype != APR_DIR) { return apr_pstrcat(cmd->pool, "\"", dir, "\" is not a directory", NULL); } rc = ap_get_module_config(cmd->server->module_config, &roaming_module); ra = (roaming_alias_t *) apr_array_push(rc->aliases); ra->uri = uri; if(dir[strlen(dir)-1] == '/') { ra->dir = dir; } else { ra->dir = apr_pstrcat(cmd->pool, dir, "/", NULL); } return NULL; } /* * Tests whether a particular roaming access uri * is being referenced. */ static int roaming_test_uri(const char *request_uri, const char *alias_uri) { const char *request_uri_p, *alias_uri_end; request_uri_p = request_uri; alias_uri_end = alias_uri + strlen(alias_uri); while(alias_uri < alias_uri_end) { if(*alias_uri == '/') { if(*request_uri_p != '/') { return 0; } while(*alias_uri == '/') { alias_uri++; } while(*request_uri_p == '/') { request_uri_p++; } } else { if(*alias_uri++ != *request_uri_p++) { return 0; } } } if(alias_uri[-1] != '/' && *request_uri_p != '\0' && *request_uri_p != '/') { return 0; } return request_uri_p - request_uri; } /* * Catches request for roaming files. */ static int roaming_translate_name(request_rec *r) { roaming_config_t *rc; roaming_alias_t *aliases; int i, l, ret; char *file, *user, *next_slash; rc = ap_get_module_config(r->server->module_config, &roaming_module); aliases = (roaming_alias_t *) rc->aliases->elts; for(i = 0; i < rc->aliases->nelts; i++) { l = roaming_test_uri(r->uri, aliases[i].uri); if(l > 0) { /* Roaming uri's should be of the form: /// and only the user may access that uri. The following uri's are forbidden: // /// //// */ /* determine user part of uri */ file = r->uri + l; ret = ap_unescape_url(file); if(ret != OK) { return ret; } while(*file == '/') { file++; } next_slash = strchr(file, '/'); if(next_slash == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "Roaming uri must contain a userid"); LOG_URL_FORM_WARNING(); return HTTP_FORBIDDEN; } user = apr_pstrndup(r->pool, file, next_slash - file); apr_table_setn(r->notes, "roaming-user", user); apr_table_setn(r->notes, "roaming-user-dir", apr_pstrcat(r->pool, aliases[i].dir, user, NULL)); /* determine user's file part of uri */ file = next_slash; while(*file == '/') { file++; } if(*file == '\0') { /* no directory indexes */ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "Directory listings of roaming uri's are not allowed"); LOG_URL_FORM_WARNING(); return HTTP_FORBIDDEN; } else if(strchr(file, '/') != NULL) { /* no subdirectories */ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "Subdirectories in roaming uri's are not allowed"); LOG_URL_FORM_WARNING(); return HTTP_FORBIDDEN; } /* Ugly hack to work around Communicator's invalid * HTTP request problem */ #if COMMUNICATOR_HACK_ENABLED if(strcmp(file, "IMAP") == 0) { char *real_filename_start, *real_filename_end, *s; real_filename_start = strstr(r->the_request, "/IMAP "); if(real_filename_start != NULL) { real_filename_end = strchr(real_filename_start + 6, ' '); if(real_filename_end != NULL && strcmp(real_filename_end, " HTTP/1.0") == 0) { s = strchr(real_filename_start + 1, '/'); if(s == NULL || s > real_filename_end) { file = apr_pstrndup(r->pool, real_filename_start + 1, (real_filename_end - real_filename_start) - 1); ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, "Fixed filename on invalid HTTP request:" " %s", file); } } } } #endif apr_table_setn(r->notes, ROAMING_FILE, file); /* construct filename */ r->filename = apr_pstrcat(r->pool, aliases[i].dir, user, "/", file, NULL); /* install our own handler */ r->handler = ROAMING_HANDLER; return OK; } } return DECLINED; } /* * Handles the GET, HEAD, PUT, DELETE and MOVE methods for roaming files. */ static int roaming_handler(request_rec *r) { const char *user, *file, *userdir, *new_uri; char *last_uri_slash, *last_new_uri_slash, *last_filename_slash, *new_filename; char buffer[HUGE_STRING_LEN]; apr_file_t *f; apr_status_t rv; apr_finfo_t dir_info; int ret; apr_size_t chars_read; /* check whether this one is for us */ if(strcmp(r->handler, ROAMING_HANDLER) != 0) { return DECLINED; } /* check whether the correct user has logged on */ /* to access these roaming files */ user = apr_table_get(r->notes, "roaming-user"); if(user == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "No roaming-user request note set"); return HTTP_INTERNAL_SERVER_ERROR; } else if(r->user == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "Unauthenticated user has no access to roaming files for %s", user); ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, r, "Have you put a .htaccess file in the roaming directory?"); return HTTP_FORBIDDEN; } else if(strcmp(r->user, user) != 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "User %s has no access to roaming files for %s", r->user, user); return HTTP_FORBIDDEN; } /* get the name of the file being requested */ file = apr_table_get(r->notes, ROAMING_FILE); if(file == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "No roaming-file request note set"); return HTTP_INTERNAL_SERVER_ERROR; } /* create directory to hold user's roaming files (if necessary) */ userdir = apr_table_get(r->notes, "roaming-user-dir"); if(userdir != NULL && apr_stat(&dir_info, userdir, APR_FINFO_TYPE, r->pool) != APR_SUCCESS) { rv = apr_dir_make(userdir, ROAMING_DIR_PERMS, r->pool); if(rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, "Cannot create directory: %s", userdir); return HTTP_FORBIDDEN; } if(r->path_info != NULL && *r->path_info != '\0') { r->filename = apr_pstrcat(r->pool, r->filename, r->path_info, NULL); r->path_info = NULL; } rv = apr_stat(&r->finfo, r->filename, APR_FINFO_NORM, r->pool); if(rv != APR_SUCCESS) { r->finfo.filetype = APR_NOFILE; } } /* check that we have no path_info lying about */ if(r->path_info != NULL && *r->path_info != '\0') { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "File not found: %s%s", r->filename, r->path_info); return HTTP_NOT_FOUND; } /* check that we are about to handle a normal file */ if(r->finfo.filetype != APR_NOFILE && r->finfo.filetype != APR_REG) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "Not a regular file: %s", r->filename); return HTTP_FORBIDDEN; } /* prepare to read the request body */ if(r->method_number == M_PUT) { ret = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR); } else { ret = ap_discard_request_body(r); } if(ret != OK) { return ret; } if(r->method_number == M_GET) { /* GET */ if(r->finfo.filetype == APR_NOFILE) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "File not found: %s", r->filename); return HTTP_NOT_FOUND; } ap_update_mtime(r, r->finfo.mtime); ap_set_last_modified(r); ap_set_content_length(r, r->finfo.size); r->content_type = "text/html"; rv = apr_file_open(&f, r->filename, APR_READ|APR_BINARY, ROAMING_FILE_PERMS, r->pool); if(rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Cannot open file %s", r->filename); return HTTP_FORBIDDEN; } if(!r->header_only) { for(;;) { apr_size_t chars_read = sizeof(buffer); rv = apr_file_read(f, buffer, &chars_read); if(rv == APR_EOF) { break; } if(rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Cannot read file %s", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } ap_rwrite(buffer, chars_read, r); } } rv = apr_file_close(f); if(rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Cannot close file %s", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } return OK; } else if(r->method_number == M_PUT) { /* PUT */ rv = apr_file_open(&f, r->filename, APR_WRITE|APR_CREATE|APR_BINARY, ROAMING_FILE_PERMS, r->pool); if(rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Cannot open file %s", r->filename); return HTTP_FORBIDDEN; } if(ap_should_client_block(r)) { while((chars_read = ap_get_client_block(r, buffer, sizeof(buffer))) > 0 ) { rv = apr_file_write(f, buffer, &chars_read); if(rv != APR_SUCCESS) { while(ap_get_client_block(r, buffer, sizeof(buffer)) > 0) ; ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Cannot write file %s", r->filename); rv = apr_file_close(f); /* return value ignored, already in error state. */ return HTTP_INTERNAL_SERVER_ERROR; } } rv = apr_file_flush(f); if(rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Cannot flush output to file %s", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } rv = apr_file_close(f); if(rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Cannot close file %s", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } } } else if(r->method_number == M_DELETE) { /* DELETE */ rv = apr_file_remove(r->filename, r->pool); if(rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Cannot delete file %s", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } } else if(r->method_number == M_MOVE) { /* MOVE */ new_uri = apr_table_get(r->headers_in, "New-uri"); if(new_uri == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "No New-uri specified"); return HTTP_BAD_REQUEST; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r, "New-uri: %s", new_uri); last_uri_slash = strrchr(r->uri, '/'); last_filename_slash = strrchr(r->filename, '/'); if(last_uri_slash == NULL || last_filename_slash == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "r->uri \"%s\" or r->filename \"%s\" do not contain slashes", r->uri, r->filename); return HTTP_INTERNAL_SERVER_ERROR; } last_new_uri_slash = strrchr(new_uri, '/'); if(last_new_uri_slash == NULL || last_new_uri_slash[1] == '\0') { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "New-uri %s does not contain slash or ends in slash", new_uri); return HTTP_BAD_REQUEST; } if((last_uri_slash - r->uri) != (last_new_uri_slash - new_uri) || strncmp(r->uri, new_uri, (last_uri_slash - r->uri)) != 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "New-uri %s does not refer to the same directory as uri %s", new_uri, r->uri); return HTTP_BAD_REQUEST; } new_filename = apr_pstrcat(r->pool, apr_pstrndup(r->pool, r->filename, (last_filename_slash - r->filename + 1)), last_new_uri_slash+1, NULL); rv = apr_file_rename(r->filename, new_filename, r->pool); if(rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "Cannot rename file %s to %s", r->filename, new_filename); return HTTP_INTERNAL_SERVER_ERROR; } } else { /* Unknown request method. */ return HTTP_METHOD_NOT_ALLOWED; } r->content_type = "text/html"; ap_rprintf(r, "\n" "Success\n" "

%s succesfull

\n" "The %s operation on %s was succesfull.
\n" "\n" "\n", r->method, r->method, r->uri); return OK; } /* * Registers hooks at interesting points in the request handling process. */ static void roaming_register_hooks(apr_pool_t *p) { ap_hook_translate_name(roaming_translate_name, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(roaming_handler, NULL, NULL, APR_HOOK_MIDDLE); } /* * Table of commands for mod_roaming. */ static const command_rec roaming_commands[] = { AP_INIT_TAKE2( "RoamingAlias", roaming_alias, NULL, RSRC_CONF, "the roaming URI and the directory containing the roaming files" ), {NULL} }; /* * Module info for mod_roaming. */ module AP_MODULE_DECLARE_DATA roaming_module = { STANDARD20_MODULE_STUFF, NULL, NULL, roaming_create_server_config, NULL, roaming_commands, roaming_register_hooks };