/* ==================================================================== * Copyright (c) 1999 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 "httpd.h" #include "http_config.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "http_request.h" #include #include #include #include #include #if MODULE_MAGIC_NUMBER_MAJOR < 19980806 #error "You need at least Apache 1.3.2 to compile this" #endif /* * Configuration data types for mod_roaming. */ module roaming_module; typedef struct { array_header *aliases; } roaming_config_t; typedef struct { char *uri; char *dir; } roaming_alias_t; /* * Creates mod_roaming configuration struct. */ static void *roaming_create_config(pool *p, server_rec * s) { roaming_config_t *rc; rc = (roaming_config_t *) ap_pcalloc(p, sizeof(roaming_config_t)); rc->aliases = ap_make_array(p, 10, 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, char *uri, char *dir) { struct stat file_info; roaming_config_t *rc; roaming_alias_t *ra; if(stat(dir, &file_info) == -1) { return ap_pstrcat(cmd->pool, "\"", dir, "\" does not exist", NULL); } rc = ap_get_module_config(cmd->server->module_config, &roaming_module); ra = (roaming_alias_t *) ap_push_array(rc->aliases); ra->uri = uri; if(dir[strlen(dir)-1] == '/') { ra->dir = dir; } else { ra->dir = ap_pstrcat(cmd->pool, dir, "/", NULL); } return NULL; } /* * Tests whether a particular roaming access uri * is being referenced. */ static int roaming_test_uri(char *request_uri, char *alias_uri) { 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_uri(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, r, "Roaming uri must contain a userid"); ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r, "Is the URL of the form http://///?"); return HTTP_FORBIDDEN; } user = ap_pstrndup(r->pool, file, next_slash - file); ap_table_setn(r->notes, "roaming-user", user); ap_table_setn(r->notes, "roaming-user-dir", ap_pstrcat(r->pool, aliases[i].dir, user, NULL)); /* determine user's file part of uri */ while(*next_slash == '/') { next_slash++; } if(*next_slash == '\0') { /* no directory indexes */ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, "Directory listings of roaming uri's not allowed"); ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r, "Is the URL of the form http://///?"); return HTTP_FORBIDDEN; } else if(strchr(next_slash, '/') != NULL) { /* no subdirectories */ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, "Subdirectories in roaming uri's not allowed"); ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r, "Is the URL of the form http://///?"); return HTTP_FORBIDDEN; } /* ugly hack to work around Communicator's invalid HTTP request problem */ #ifndef NO_UGLY_COMMUNICATOR_HACK if(strcmp(next_slash, "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) { next_slash = ap_pstrndup(r->pool, real_filename_start + 1, (real_filename_end - real_filename_start) - 1); ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r, "Fixed filename on invalid HTTP request: %s", next_slash); } } } } #endif /* construct filename */ r->filename = ap_pstrcat(r->pool, aliases[i].dir, user, "/", next_slash, NULL); /* install our own handler */ r->handler = "roaming-file"; 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, *userdir; char *new_uri = NULL, *last_uri_slash, *last_new_uri_slash, *last_filename_slash, *new_filename; FILE *f; struct stat file_info; int i, ret; array_header *hdr_arr; table_entry *headers; /* Checks whether the correct user has logged on to access these roaming files. */ user = ap_table_get(r->notes, "roaming-user"); if(user == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, "No roaming-user request note set"); return HTTP_INTERNAL_SERVER_ERROR; } else if(r->connection->user == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, "Unauthenticated user has no access to roaming files for %s", user); ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r, "Have you put a .htaccess file in the roaming directory for user %s?", user); return HTTP_FORBIDDEN; } else if(strcmp(r->connection->user, user) != 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, "User %s has no access to roaming files for %s", r->connection->user, user); return HTTP_FORBIDDEN; } /* Create directory to hold user's roaming files (if necessary) */ userdir = ap_table_get(r->notes, "roaming-user-dir"); if(userdir != NULL && stat(userdir, &file_info) != 0) { if(mkdir(userdir, 0700) == 0) { if(r->path_info != NULL && *r->path_info != '\0') { r->filename = ap_pstrcat(r->pool, r->filename, r->path_info, NULL); r->path_info = NULL; } if(stat(r->filename, &r->finfo) < 0) { r->finfo.st_mode = 0; } } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Cannot create directory: %s", userdir); return HTTP_FORBIDDEN; } } /* 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, 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.st_mode != 0 && !S_ISREG(r->finfo.st_mode)) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 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.st_mode == 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, "File not found: %s", r->filename); return HTTP_NOT_FOUND; } ap_update_mtime(r, r->finfo.st_mtime); ap_set_last_modified(r); ret = ap_set_content_length(r, r->finfo.st_size); if(ret != OK) { return ret; } r->content_type = "text/plain"; f = ap_pfopen(r->pool, r->filename, "rb"); if(f == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Cannot open file %s", r->filename); return HTTP_FORBIDDEN; } ap_soft_timeout("send roaming file", r); ap_send_http_header(r); if(!r->header_only) { char buffer[HUGE_STRING_LEN]; size_t chars_read; while((chars_read = fread(buffer, sizeof(char), sizeof(buffer), f)) > 0) { ap_rwrite(buffer, chars_read, r); } } ap_kill_timeout(r); ap_pfclose(r->pool, f); return OK; } else if(r->method_number == M_PUT) { /* PUT */ f = ap_pfopen(r->pool, r->filename, "wb"); if(f == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Cannot open file %s", r->filename); return HTTP_FORBIDDEN; } if(ap_should_client_block(r)) { char buffer[HUGE_STRING_LEN]; size_t chars_read; while((chars_read = ap_get_client_block(r, buffer, sizeof(buffer))) > 0 ) { ap_reset_timeout(r); if(fwrite(buffer, sizeof(char), chars_read, f) < chars_read) { while(ap_get_client_block(r, buffer, sizeof(buffer)) > 0) ; ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Cannot write file %s", r->filename); ap_pfclose(r->pool, f); return HTTP_INTERNAL_SERVER_ERROR; } } fflush(f); ap_pfclose(r->pool, f); } } else if(r->method_number == M_DELETE) { /* DELETE */ if(unlink(r->filename) == -1) { ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Cannot delete file %s", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } #if MODULE_MAGIC_NUMBER_MAJOR < 19981108 } else if(strcmp(r->method, "MOVE") == 0) { #else } else if(r->method_number == M_MOVE) { #endif /* MOVE */ hdr_arr = ap_table_elts(r->headers_in); headers = (table_entry *) hdr_arr->elts; for(i = 0; i < hdr_arr->nelts; i++) { if(strcasecmp(headers[i].key, "New-uri") == 0) { new_uri = headers[i].val; } } if(new_uri == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, "No New-uri specified"); return HTTP_BAD_REQUEST; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 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, 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, 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, r, "New-uri %s does not refer to the same directory as uri %s", new_uri, r->uri); return HTTP_BAD_REQUEST; } new_filename = ap_pstrcat(r->pool, ap_pstrndup(r->pool, r->filename, (last_filename_slash - r->filename + 1)), last_new_uri_slash+1, NULL); if(rename(r->filename, new_filename) == -1) { ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Cannot rename file %s to %s", r->filename, new_filename); return HTTP_INTERNAL_SERVER_ERROR; } } else { return HTTP_METHOD_NOT_ALLOWED; } r->content_type = "text/html"; ap_soft_timeout("send roaming response", r); ap_send_http_header(r); 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); ap_kill_timeout(r); return OK; } /* * Table of handlers for mod_roaming. */ handler_rec roaming_handlers[] = { {"roaming-file", roaming_handler}, {NULL} }; /* * Table of commands for mod_roaming. */ command_rec roaming_commands[] = { {"RoamingAlias", roaming_alias, NULL, RSRC_CONF, TAKE2, "the roaming URI and the directory containing the roaming files"}, {NULL} }; /* * Module info for mod_roaming. */ module roaming_module = { STANDARD_MODULE_STUFF, NULL, NULL, NULL, roaming_create_config, NULL, roaming_commands, roaming_handlers, roaming_translate_uri, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };