From 852ff8ecf6bfb5ce943c0c4583770bb07a0d42ce Mon Sep 17 00:00:00 2001 From: Thomas Porzelt Date: Sat, 17 Apr 2010 15:44:39 +0200 Subject: [PATCH] [modules] Add mod_userdir --- src/CMakeLists.txt | 1 + src/modules/Makefile.am | 5 + src/modules/mod_userdir.c | 251 ++++++++++++++++++++++++++++++++++++++ src/modules/wscript | 1 + 4 files changed, 258 insertions(+) create mode 100644 src/modules/mod_userdir.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0af1609..7336d93 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -322,6 +322,7 @@ ADD_AND_INSTALL_LIBRARY(mod_redirect "modules/mod_redirect.c") ADD_AND_INSTALL_LIBRARY(mod_rewrite "modules/mod_rewrite.c") ADD_AND_INSTALL_LIBRARY(mod_scgi "modules/mod_scgi.c") ADD_AND_INSTALL_LIBRARY(mod_status "modules/mod_status.c") +ADD_AND_INSTALL_LIBRARY(mod_userdir "modules/mod_userdir.c") ADD_AND_INSTALL_LIBRARY(mod_vhost "modules/mod_vhost.c") IF(HAVE_ZLIB OR HAVE_BZIP) diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am index 08fec35..880ea05 100644 --- a/src/modules/Makefile.am +++ b/src/modules/Makefile.am @@ -114,6 +114,11 @@ libmod_status_la_SOURCES = mod_status.c libmod_status_la_LDFLAGS = $(common_ldflags) libmod_status_la_LIBADD = $(common_libadd) +install_libs += libmod_userdir.la +libmod_userdir_la_SOURCES = mod_vhost.c +libmod_userdir_la_LDFLAGS = $(common_ldflags) +libmod_userdir_la_LIBADD = $(common_libadd) + install_libs += libmod_vhost.la libmod_vhost_la_SOURCES = mod_vhost.c libmod_vhost_la_LDFLAGS = $(common_ldflags) diff --git a/src/modules/mod_userdir.c b/src/modules/mod_userdir.c new file mode 100644 index 0000000..a42f24d --- /dev/null +++ b/src/modules/mod_userdir.c @@ -0,0 +1,251 @@ +/* + * mod_userdir - user-specific document roots + * + * Description: + * mod_userdir allows you to have user-specific document roots being accessed through http://domain/~user/ + * + * Setups: + * none + * + * Options: + * none + * + * Actions: + * userdir ; + * - if not starting with a slash, maps a request path of /~user/ to a docroot of ~user// + * - if starting with a slash, maps a request path of /~user/ to a docroot of + * - * in is replaced by the requested username + * - $1-9 are replace by the n-th letter of the requested username + * + * Example config: + * userdir "public_html"; # maps /~lighty/ to ~lighty/public_html/ (e.g. /home/lighty/public_html/ on most systems) + * + * Todo: + * - userdir.exclude / userdir.include options/setups to allow certain users to be excluded or included + * + * Author: + * Copyright (c) 2010 Thomas Porzelt + * License: + * MIT, see COPYING file in the lighttpd 2 tree + */ + +#include +#include + +LI_API gboolean mod_userdir_init(liModules *mods, liModule *mod); +LI_API gboolean mod_userdir_free(liModules *mods, liModule *mod); + +struct userdir_part { + enum { + USERDIR_PART_STRING, + USERDIR_PART_USERNAME, + USERDIR_PART_LETTER + } type; + union { + GString *str; + guint8 ndx; + } data; +}; +typedef struct userdir_part userdir_part; + +static liHandlerResult userdir(liVRequest *vr, gpointer param, gpointer *context) { + userdir_part *part; + gchar *c; + guint i; + GArray *parts = param; + gchar *username; + guint username_len = 0; + + UNUSED(context); + + if (vr->request.uri.path->str[0] != '/' || vr->request.uri.path->str[1] != '~') { + return LI_HANDLER_GO_ON; + } + + /* only allow [a-zA-Z0-9_-] in usernames */ + for (c = vr->request.uri.path->str+2; *c != '\0'; c++) { + if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '_' || *c == '-') + continue; + if (*c == '/') + break; + + return LI_HANDLER_GO_ON; + } + + username = vr->request.uri.path->str + 2; + username_len = c - username; + if (username_len == 0) + return LI_HANDLER_GO_ON; + + g_string_truncate(vr->physical.doc_root, 0); + + part = &g_array_index(parts, userdir_part, 0); + + if (part->type != USERDIR_PART_STRING || part->data.str->str[0] != '/') { + /* pattern not starting with slash, need to lookup user's homedir */ + struct passwd pwd; + struct passwd *result; + gchar c_orig = *(username+username_len); + + /* do not allow root user */ + if (username_len == 4 && username[0] == 'r' && username[1] == 'o' && username[2] == 'o' && username[3] == 't') { + if (!li_vrequest_handle_direct(vr)) + return LI_HANDLER_ERROR; + + vr->response.http_status = 403; + return LI_HANDLER_GO_ON; + } + + *(username+username_len) = '\0'; + while (EINTR == getpwnam_r(username, &pwd, vr->wrk->tmp_str->str, vr->wrk->tmp_str->allocated_len, &result)) { + } + *(username+username_len) = c_orig; + + if (!result) { + if (!li_vrequest_handle_direct(vr)) + return LI_HANDLER_ERROR; + + vr->response.http_status = 404; + return LI_HANDLER_GO_ON; + } + + // user found + g_string_append(vr->physical.doc_root, pwd.pw_dir); + g_string_append_c(vr->physical.doc_root, G_DIR_SEPARATOR); + g_print("home: %s\n", pwd.pw_dir); + } + + for (i = 0; i < parts->len; i++) { + part = &g_array_index(parts, userdir_part, i); + switch (part->type) { + case USERDIR_PART_STRING: + g_string_append_len(vr->physical.doc_root, GSTR_LEN(part->data.str)); + break; + case USERDIR_PART_USERNAME: + g_string_append_len(vr->physical.doc_root, username, username_len); + break; + case USERDIR_PART_LETTER: + if (part->data.ndx <= username_len) + g_string_append_c(vr->physical.doc_root, username[part->data.ndx-1]); + break; + } + } + + /* build physical path: docroot + uri.path */ + g_string_truncate(vr->physical.path, 0); + g_string_append_len(vr->physical.path, GSTR_LEN(vr->physical.doc_root)); + g_string_append_len(vr->physical.path, username + username_len, vr->request.uri.path->str - username - username_len); + + /* rewrite request path to skip username */ + g_string_truncate(vr->wrk->tmp_str, 0); + g_string_append_len(vr->wrk->tmp_str, username + username_len, vr->request.uri.path->str - username - username_len); + g_string_truncate(vr->request.uri.path, 0); + g_string_append_len(vr->request.uri.path, GSTR_LEN(vr->wrk->tmp_str)); + + g_print("phys.path: %s req.path: %s\n", vr->physical.path->str, vr->request.uri.path->str); + + return LI_HANDLER_GO_ON; +} + +static void userdir_free(liServer *srv, gpointer param) { + GArray *parts = param; + guint i; + + UNUSED(srv); + + for (i = 0; i < parts->len; i++) { + userdir_part *part = &g_array_index(parts, userdir_part, i); + if (part->type == USERDIR_PART_STRING) { + g_string_free(part->data.str, TRUE); + } + } + + g_array_free(parts, TRUE); +} + +static liAction* userdir_create(liServer *srv, liPlugin* p, liValue *val, gpointer userdata) { + GString *str; + gchar *c, *c_last; + GArray *parts; + userdir_part part; + + UNUSED(p); + UNUSED(userdata); + + if (!val || val->type != LI_VALUE_STRING) { + ERROR(srv, "%s", "userdir expects a string as parameter"); + return NULL; + } + + // parse pattern + parts = g_array_new(FALSE, FALSE, sizeof(userdir_part)); + str = val->data.string; + for (c_last = c = str->str; c != str->str + str->len; c++) { + if (*c == '*') { + // username + if (c - c_last > 0) { + // push last string + part.type = USERDIR_PART_STRING; + part.data.str = g_string_new_len(c_last, c - c_last); + g_array_append_val(parts, part); + } + + c_last = c+1; + part.type = USERDIR_PART_USERNAME; + g_array_append_val(parts, part); + } else if (*c == '$' && *(c+1) >= '1' && *(c+2) <= '9') { + // letter + if (c - c_last > 0) { + // push last string + part.type = USERDIR_PART_STRING; + part.data.str = g_string_new_len(c_last, c - c_last); + g_array_append_val(parts, part); + } + + c_last = c+2; + part.type = USERDIR_PART_LETTER; + part.data.ndx = *(c+1) - '0'; + g_array_append_val(parts, part); + } + } + + if (c - c_last > 0) { + // push last string + part.type = USERDIR_PART_STRING; + part.data.str = g_string_new_len(c_last, c - c_last); + g_array_append_val(parts, part); + } + + return li_action_new_function(userdir, NULL, userdir_free, parts); +} + +static const liPluginAction actions[] = { + { "userdir", userdir_create, NULL }, + + { NULL, NULL, NULL } +}; + +static void plugin_userdir_init(liServer *srv, liPlugin *p, gpointer userdata) { + UNUSED(srv); UNUSED(userdata); + + p->actions = actions; + +} + + +gboolean mod_userdir_init(liModules *mods, liModule *mod) { + UNUSED(mod); + + MODULE_VERSION_CHECK(mods); + + mod->config = li_plugin_register(mods->main, "mod_userdir", plugin_userdir_init, NULL); + + return mod->config != NULL; +} + +gboolean mod_userdir_free(liModules *mods, liModule *mod) { + if (mod->config) + li_plugin_free(mods->main, mod->config); + + return TRUE; +} diff --git a/src/modules/wscript b/src/modules/wscript index 765078a..bd5d3fd 100644 --- a/src/modules/wscript +++ b/src/modules/wscript @@ -59,4 +59,5 @@ def build(bld): lighty_mod(bld, 'mod_rewrite', 'mod_rewrite.c') lighty_mod(bld, 'mod_scgi', 'mod_scgi.c') lighty_mod(bld, 'mod_status', 'mod_status.c') + lighty_mod(bld, 'mod_userdir', 'mod_userdir.c') lighty_mod(bld, 'mod_vhost', 'mod_vhost.c')