11 #include <sys/types.h>
21 /* plugin config for all request/connections */
27 unsigned short letterhomes
;
28 unsigned short active
;
37 plugin_config
**config_storage
;
42 /* init the plugin data */
43 INIT_FUNC(mod_userdir_init
) {
46 p
= calloc(1, sizeof(*p
));
48 p
->username
= buffer_init();
49 p
->temp_path
= buffer_init();
54 /* detroy the plugin data */
55 FREE_FUNC(mod_userdir_free
) {
58 if (!p
) return HANDLER_GO_ON
;
60 if (p
->config_storage
) {
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
);
71 buffer_free(s
->basepath
);
75 free(p
->config_storage
);
78 buffer_free(p
->username
);
79 buffer_free(p
->temp_path
);
86 /* handle plugin config and check values */
88 SETDEFAULTS_FUNC(mod_userdir_set_defaults
) {
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
];
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();
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. */
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
;
151 static int mod_userdir_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
153 plugin_config
*s
= p
->config_storage
[0];
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;
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"))) {
176 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.exclude-user"))) {
178 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.include-user"))) {
180 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.basepath"))) {
182 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.letterhomes"))) {
184 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.active"))) {
194 URIHANDLER_FUNC(mod_userdir_docroot_handler
) {
195 plugin_data
*p
= p_d
;
199 struct passwd
*pwd
= NULL
;
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
)
232 && NULL
== (pwd
= getpwnam(p
->username
->ptr
))
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
) {
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 */
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
)) {
269 buffer_copy_string(p
->temp_path
, pwd
->pw_dir
);
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
;
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
)) {
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
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
;