11 #include <sys/types.h>
20 /* plugin config for all request/connections */
26 unsigned short letterhomes
;
27 unsigned short active
;
36 plugin_config
**config_storage
;
41 /* init the plugin data */
42 INIT_FUNC(mod_userdir_init
) {
45 p
= calloc(1, sizeof(*p
));
47 p
->username
= buffer_init();
48 p
->temp_path
= buffer_init();
53 /* detroy the plugin data */
54 FREE_FUNC(mod_userdir_free
) {
57 if (!p
) return HANDLER_GO_ON
;
59 if (p
->config_storage
) {
62 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
63 plugin_config
*s
= p
->config_storage
[i
];
65 if (NULL
== s
) continue;
67 array_free(s
->include_user
);
68 array_free(s
->exclude_user
);
70 buffer_free(s
->basepath
);
74 free(p
->config_storage
);
77 buffer_free(p
->username
);
78 buffer_free(p
->temp_path
);
85 /* handle plugin config and check values */
87 SETDEFAULTS_FUNC(mod_userdir_set_defaults
) {
91 config_values_t cv
[] = {
92 { "userdir.path", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
93 { "userdir.exclude-user", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
94 { "userdir.include-user", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
95 { "userdir.basepath", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
96 { "userdir.letterhomes", NULL
, T_CONFIG_BOOLEAN
,T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
97 { "userdir.active", NULL
, T_CONFIG_BOOLEAN
,T_CONFIG_SCOPE_CONNECTION
}, /* 5 */
98 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
101 if (!p
) return HANDLER_ERROR
;
103 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
105 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
106 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
109 s
= calloc(1, sizeof(plugin_config
));
110 s
->exclude_user
= array_init();
111 s
->include_user
= array_init();
112 s
->path
= buffer_init();
113 s
->basepath
= buffer_init();
115 /* enabled by default for backward compatibility; if userdir.path isn't set userdir is disabled too,
116 * but you can't disable it by setting it to an empty string. */
119 cv
[0].destination
= s
->path
;
120 cv
[1].destination
= s
->exclude_user
;
121 cv
[2].destination
= s
->include_user
;
122 cv
[3].destination
= s
->basepath
;
123 cv
[4].destination
= &(s
->letterhomes
);
124 cv
[5].destination
= &(s
->active
);
126 p
->config_storage
[i
] = s
;
128 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
129 return HANDLER_ERROR
;
132 if (!array_is_vlist(s
->exclude_user
)) {
133 log_error_write(srv
, __FILE__
, __LINE__
, "s",
134 "unexpected value for userdir.exclude-user; expected list of \"suffix\"");
135 return HANDLER_ERROR
;
138 if (!array_is_vlist(s
->include_user
)) {
139 log_error_write(srv
, __FILE__
, __LINE__
, "s",
140 "unexpected value for userdir.include-user; expected list of \"suffix\"");
141 return HANDLER_ERROR
;
145 return HANDLER_GO_ON
;
150 static int mod_userdir_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
152 plugin_config
*s
= p
->config_storage
[0];
161 /* skip the first, the global context */
162 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
163 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
164 s
= p
->config_storage
[i
];
166 /* condition didn't match */
167 if (!config_check_cond(srv
, con
, dc
)) continue;
170 for (j
= 0; j
< dc
->value
->used
; j
++) {
171 data_unset
*du
= dc
->value
->data
[j
];
173 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.path"))) {
175 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.exclude-user"))) {
177 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.include-user"))) {
179 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.basepath"))) {
181 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.letterhomes"))) {
183 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.active"))) {
193 URIHANDLER_FUNC(mod_userdir_docroot_handler
) {
194 plugin_data
*p
= p_d
;
198 struct passwd
*pwd
= NULL
;
201 if (buffer_is_empty(con
->uri
.path
)) return HANDLER_GO_ON
;
203 mod_userdir_patch_connection(srv
, con
, p
);
205 /* enforce the userdir.path to be set in the config, ugly fix for #1587;
206 * should be replaced with a clean .enabled option in 1.5
208 if (!p
->conf
.active
|| buffer_is_empty(p
->conf
.path
)) return HANDLER_GO_ON
;
210 /* /~user/foo.html -> /home/user/public_html/foo.html */
212 if (con
->uri
.path
->ptr
[0] != '/' ||
213 con
->uri
.path
->ptr
[1] != '~') return HANDLER_GO_ON
;
215 if (NULL
== (rel_url
= strchr(con
->uri
.path
->ptr
+ 2, '/'))) {
216 /* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */
217 http_response_redirect_to_directory(srv
, con
);
219 return HANDLER_FINISHED
;
222 /* /~/ is a empty username, catch it directly */
223 if (0 == rel_url
- (con
->uri
.path
->ptr
+ 2)) {
224 return HANDLER_GO_ON
;
227 buffer_copy_string_len(p
->username
, con
->uri
.path
->ptr
+ 2, rel_url
- (con
->uri
.path
->ptr
+ 2));
229 if (buffer_string_is_empty(p
->conf
.basepath
)
231 && NULL
== (pwd
= getpwnam(p
->username
->ptr
))
235 return HANDLER_GO_ON
;
239 for (k
= 0; k
< p
->conf
.exclude_user
->used
; k
++) {
240 data_string
*ds
= (data_string
*)p
->conf
.exclude_user
->data
[k
];
242 if (buffer_is_equal(ds
->value
, p
->username
)) {
243 /* user in exclude list */
244 return HANDLER_GO_ON
;
248 if (p
->conf
.include_user
->used
) {
250 for (k
= 0; k
< p
->conf
.include_user
->used
; k
++) {
251 data_string
*ds
= (data_string
*)p
->conf
.include_user
->data
[k
];
253 if (buffer_is_equal(ds
->value
, p
->username
)) {
254 /* user in include list */
260 if (!found_user
) return HANDLER_GO_ON
;
263 /* we build the physical path */
265 if (buffer_string_is_empty(p
->conf
.basepath
)) {
267 buffer_copy_string(p
->temp_path
, pwd
->pw_dir
);
271 /* check if the username is valid
272 * a request for /~../ should lead to a directory traversal
273 * limiting to [-_a-z0-9.] should fix it */
275 for (cp
= p
->username
->ptr
; *cp
; cp
++) {
281 (c
>= 'a' && c
<= 'z') ||
282 (c
>= 'A' && c
<= 'Z') ||
283 (c
>= '0' && c
<= '9'))) {
285 return HANDLER_GO_ON
;
288 if (con
->conf
.force_lowercase_filenames
) {
289 buffer_to_lower(p
->username
);
292 buffer_copy_buffer(p
->temp_path
, p
->conf
.basepath
);
293 buffer_append_slash(p
->temp_path
);
294 if (p
->conf
.letterhomes
) {
295 buffer_append_string_len(p
->temp_path
, p
->username
->ptr
, 1);
296 buffer_append_slash(p
->temp_path
);
298 buffer_append_string_buffer(p
->temp_path
, p
->username
);
300 buffer_append_slash(p
->temp_path
);
301 buffer_append_string_buffer(p
->temp_path
, p
->conf
.path
);
303 if (buffer_string_is_empty(p
->conf
.basepath
)) {
307 ret
= stat(p
->temp_path
->ptr
, &st
);
308 if (ret
< 0 || S_ISDIR(st
.st_mode
) != 1) {
309 return HANDLER_GO_ON
;
313 buffer_copy_buffer(con
->physical
.basedir
, p
->temp_path
);
315 /* the physical rel_path is basically the same as uri.path;
316 * but it is converted to lowercase in case of force_lowercase_filenames and some special handling
317 * for trailing '.', ' ' and '/' on windows
318 * we assume that no docroot/physical handler changed this
319 * (docroot should only set the docroot/server name, phyiscal should only change the phyiscal.path;
320 * the exception mod_secdownload doesn't work with userdir anyway)
322 buffer_append_slash(p
->temp_path
);
323 /* if no second '/' is found, we assume that it was stripped from the uri.path for the special handling
325 * we do not care about the trailing slash here on windows, as we already ensured it is a directory
327 * TODO: what to do with trailing dots in usernames on windows? they may result in the same directory
328 * as a username without them.
330 if (NULL
!= (rel_url
= strchr(con
->physical
.rel_path
->ptr
+ 2, '/'))) {
331 buffer_append_string(p
->temp_path
, rel_url
+ 1); /* skip the / */
333 buffer_copy_buffer(con
->physical
.path
, p
->temp_path
);
335 buffer_reset(p
->temp_path
);
337 return HANDLER_GO_ON
;
340 /* this function is called at dlopen() time and inits the callbacks */
342 int mod_userdir_plugin_init(plugin
*p
);
343 int mod_userdir_plugin_init(plugin
*p
) {
344 p
->version
= LIGHTTPD_VERSION_ID
;
345 p
->name
= buffer_init_string("userdir");
347 p
->init
= mod_userdir_init
;
348 p
->handle_physical
= mod_userdir_docroot_handler
;
349 p
->set_defaults
= mod_userdir_set_defaults
;
350 p
->cleanup
= mod_userdir_free
;