[core] extend dir redirection to take HTTP status
[lighttpd.git] / src / mod_userdir.c
blobe2ea08db04acf113c8eb0e8d1245d969427bc779
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
7 #include "response.h"
9 #include "plugin.h"
11 #include <sys/types.h>
12 #include <sys/stat.h>
14 #include <stdlib.h>
15 #include <string.h>
17 #ifdef HAVE_PWD_H
18 # include <pwd.h>
19 #endif
21 /* plugin config for all request/connections */
22 typedef struct {
23 array *exclude_user;
24 array *include_user;
25 buffer *path;
26 buffer *basepath;
27 unsigned short letterhomes;
28 unsigned short active;
29 } plugin_config;
31 typedef struct {
32 PLUGIN_DATA;
34 buffer *username;
35 buffer *temp_path;
37 plugin_config **config_storage;
39 plugin_config conf;
40 } plugin_data;
42 /* init the plugin data */
43 INIT_FUNC(mod_userdir_init) {
44 plugin_data *p;
46 p = calloc(1, sizeof(*p));
48 p->username = buffer_init();
49 p->temp_path = buffer_init();
51 return p;
54 /* detroy the plugin data */
55 FREE_FUNC(mod_userdir_free) {
56 plugin_data *p = p_d;
58 if (!p) return HANDLER_GO_ON;
60 if (p->config_storage) {
61 size_t i;
63 for (i = 0; i < srv->config_context->used; i++) {
64 plugin_config *s = p->config_storage[i];
66 if (NULL == s) continue;
68 array_free(s->include_user);
69 array_free(s->exclude_user);
70 buffer_free(s->path);
71 buffer_free(s->basepath);
73 free(s);
75 free(p->config_storage);
78 buffer_free(p->username);
79 buffer_free(p->temp_path);
81 free(p);
83 return HANDLER_GO_ON;
86 /* handle plugin config and check values */
88 SETDEFAULTS_FUNC(mod_userdir_set_defaults) {
89 plugin_data *p = p_d;
90 size_t i;
92 config_values_t cv[] = {
93 { "userdir.path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
94 { "userdir.exclude-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
95 { "userdir.include-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
96 { "userdir.basepath", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
97 { "userdir.letterhomes", NULL, T_CONFIG_BOOLEAN,T_CONFIG_SCOPE_CONNECTION }, /* 4 */
98 { "userdir.active", NULL, T_CONFIG_BOOLEAN,T_CONFIG_SCOPE_CONNECTION }, /* 5 */
99 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
102 if (!p) return HANDLER_ERROR;
104 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
106 for (i = 0; i < srv->config_context->used; i++) {
107 data_config const* config = (data_config const*)srv->config_context->data[i];
108 plugin_config *s;
110 s = calloc(1, sizeof(plugin_config));
111 s->exclude_user = array_init();
112 s->include_user = array_init();
113 s->path = buffer_init();
114 s->basepath = buffer_init();
115 s->letterhomes = 0;
116 /* enabled by default for backward compatibility; if userdir.path isn't set userdir is disabled too,
117 * but you can't disable it by setting it to an empty string. */
118 s->active = 1;
120 cv[0].destination = s->path;
121 cv[1].destination = s->exclude_user;
122 cv[2].destination = s->include_user;
123 cv[3].destination = s->basepath;
124 cv[4].destination = &(s->letterhomes);
125 cv[5].destination = &(s->active);
127 p->config_storage[i] = s;
129 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
130 return HANDLER_ERROR;
133 if (!array_is_vlist(s->exclude_user)) {
134 log_error_write(srv, __FILE__, __LINE__, "s",
135 "unexpected value for userdir.exclude-user; expected list of \"suffix\"");
136 return HANDLER_ERROR;
139 if (!array_is_vlist(s->include_user)) {
140 log_error_write(srv, __FILE__, __LINE__, "s",
141 "unexpected value for userdir.include-user; expected list of \"suffix\"");
142 return HANDLER_ERROR;
146 return HANDLER_GO_ON;
149 #define PATCH(x) \
150 p->conf.x = s->x;
151 static int mod_userdir_patch_connection(server *srv, connection *con, plugin_data *p) {
152 size_t i, j;
153 plugin_config *s = p->config_storage[0];
155 PATCH(path);
156 PATCH(exclude_user);
157 PATCH(include_user);
158 PATCH(basepath);
159 PATCH(letterhomes);
160 PATCH(active);
162 /* skip the first, the global context */
163 for (i = 1; i < srv->config_context->used; i++) {
164 data_config *dc = (data_config *)srv->config_context->data[i];
165 s = p->config_storage[i];
167 /* condition didn't match */
168 if (!config_check_cond(srv, con, dc)) continue;
170 /* merge config */
171 for (j = 0; j < dc->value->used; j++) {
172 data_unset *du = dc->value->data[j];
174 if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.path"))) {
175 PATCH(path);
176 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.exclude-user"))) {
177 PATCH(exclude_user);
178 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.include-user"))) {
179 PATCH(include_user);
180 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.basepath"))) {
181 PATCH(basepath);
182 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.letterhomes"))) {
183 PATCH(letterhomes);
184 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.active"))) {
185 PATCH(active);
190 return 0;
192 #undef PATCH
194 URIHANDLER_FUNC(mod_userdir_docroot_handler) {
195 plugin_data *p = p_d;
196 size_t k;
197 char *rel_url;
198 #ifdef HAVE_PWD_H
199 struct passwd *pwd = NULL;
200 #endif
202 if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON;
204 mod_userdir_patch_connection(srv, con, p);
206 /* enforce the userdir.path to be set in the config, ugly fix for #1587;
207 * should be replaced with a clean .enabled option in 1.5
209 if (!p->conf.active || buffer_is_empty(p->conf.path)) return HANDLER_GO_ON;
211 /* /~user/foo.html -> /home/user/public_html/foo.html */
213 if (con->uri.path->ptr[0] != '/' ||
214 con->uri.path->ptr[1] != '~') return HANDLER_GO_ON;
216 if (NULL == (rel_url = strchr(con->uri.path->ptr + 2, '/'))) {
217 /* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */
218 http_response_redirect_to_directory(srv, con, 301);
220 return HANDLER_FINISHED;
223 /* /~/ is a empty username, catch it directly */
224 if (0 == rel_url - (con->uri.path->ptr + 2)) {
225 return HANDLER_GO_ON;
228 buffer_copy_string_len(p->username, con->uri.path->ptr + 2, rel_url - (con->uri.path->ptr + 2));
230 if (buffer_string_is_empty(p->conf.basepath)
231 #ifdef HAVE_PWD_H
232 && NULL == (pwd = getpwnam(p->username->ptr))
233 #endif
235 /* user not found */
236 return HANDLER_GO_ON;
240 for (k = 0; k < p->conf.exclude_user->used; k++) {
241 data_string *ds = (data_string *)p->conf.exclude_user->data[k];
243 if (buffer_is_equal(ds->value, p->username)) {
244 /* user in exclude list */
245 return HANDLER_GO_ON;
249 if (p->conf.include_user->used) {
250 int found_user = 0;
251 for (k = 0; k < p->conf.include_user->used; k++) {
252 data_string *ds = (data_string *)p->conf.include_user->data[k];
254 if (buffer_is_equal(ds->value, p->username)) {
255 /* user in include list */
256 found_user = 1;
257 break;
261 if (!found_user) return HANDLER_GO_ON;
264 /* we build the physical path */
265 buffer_clear(p->temp_path);
267 if (buffer_string_is_empty(p->conf.basepath)) {
268 #ifdef HAVE_PWD_H
269 buffer_copy_string(p->temp_path, pwd->pw_dir);
270 #endif
271 } else {
272 char *cp = p->username->ptr;
273 /* check if the username is valid
274 * a request for /~../ should lead to a directory traversal
275 * limiting to [-_a-z0-9.] should fix it */
276 if (cp[0] == '.' && (cp[1] == '\0' || (cp[1] == '.' && cp[2] == '\0'))) {
277 return HANDLER_GO_ON;
280 for (; *cp; cp++) {
281 char c = *cp;
282 if (!(light_isalnum(c) || c == '-' || c == '_' || c == '.')) {
283 return HANDLER_GO_ON;
286 if (con->conf.force_lowercase_filenames) {
287 buffer_to_lower(p->username);
290 buffer_copy_buffer(p->temp_path, p->conf.basepath);
291 if (p->conf.letterhomes) {
292 if (p->username->ptr[0] == '.') return HANDLER_GO_ON;
293 buffer_append_path_len(p->temp_path, p->username->ptr, 1);
295 buffer_append_path_len(p->temp_path, CONST_BUF_LEN(p->username));
297 buffer_append_path_len(p->temp_path, CONST_BUF_LEN(p->conf.path));
299 if (buffer_string_is_empty(p->conf.basepath)) {
300 struct stat st;
301 int ret;
303 ret = stat(p->temp_path->ptr, &st);
304 if (ret < 0 || S_ISDIR(st.st_mode) != 1) {
305 return HANDLER_GO_ON;
309 buffer_copy_buffer(con->physical.basedir, p->temp_path);
311 /* the physical rel_path is basically the same as uri.path;
312 * but it is converted to lowercase in case of force_lowercase_filenames and some special handling
313 * for trailing '.', ' ' and '/' on windows
314 * we assume that no docroot/physical handler changed this
315 * (docroot should only set the docroot/server name, phyiscal should only change the phyiscal.path;
316 * the exception mod_secdownload doesn't work with userdir anyway)
318 buffer_append_slash(p->temp_path);
319 /* if no second '/' is found, we assume that it was stripped from the uri.path for the special handling
320 * on windows.
321 * we do not care about the trailing slash here on windows, as we already ensured it is a directory
323 * TODO: what to do with trailing dots in usernames on windows? they may result in the same directory
324 * as a username without them.
326 if (NULL != (rel_url = strchr(con->physical.rel_path->ptr + 2, '/'))) {
327 buffer_append_string(p->temp_path, rel_url + 1); /* skip the / */
329 buffer_copy_buffer(con->physical.path, p->temp_path);
331 return HANDLER_GO_ON;
334 /* this function is called at dlopen() time and inits the callbacks */
336 int mod_userdir_plugin_init(plugin *p);
337 int mod_userdir_plugin_init(plugin *p) {
338 p->version = LIGHTTPD_VERSION_ID;
339 p->name = buffer_init_string("userdir");
341 p->init = mod_userdir_init;
342 p->handle_physical = mod_userdir_docroot_handler;
343 p->set_defaults = mod_userdir_set_defaults;
344 p->cleanup = mod_userdir_free;
346 p->data = NULL;
348 return 0;