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
;
133 return HANDLER_GO_ON
;
138 static int mod_userdir_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
140 plugin_config
*s
= p
->config_storage
[0];
149 /* skip the first, the global context */
150 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
151 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
152 s
= p
->config_storage
[i
];
154 /* condition didn't match */
155 if (!config_check_cond(srv
, con
, dc
)) continue;
158 for (j
= 0; j
< dc
->value
->used
; j
++) {
159 data_unset
*du
= dc
->value
->data
[j
];
161 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.path"))) {
163 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.exclude-user"))) {
165 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.include-user"))) {
167 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.basepath"))) {
169 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.letterhomes"))) {
171 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("userdir.active"))) {
181 URIHANDLER_FUNC(mod_userdir_docroot_handler
) {
182 plugin_data
*p
= p_d
;
186 struct passwd
*pwd
= NULL
;
189 if (buffer_is_empty(con
->uri
.path
)) return HANDLER_GO_ON
;
191 mod_userdir_patch_connection(srv
, con
, p
);
193 /* enforce the userdir.path to be set in the config, ugly fix for #1587;
194 * should be replaced with a clean .enabled option in 1.5
196 if (!p
->conf
.active
|| buffer_is_empty(p
->conf
.path
)) return HANDLER_GO_ON
;
198 /* /~user/foo.html -> /home/user/public_html/foo.html */
200 if (con
->uri
.path
->ptr
[0] != '/' ||
201 con
->uri
.path
->ptr
[1] != '~') return HANDLER_GO_ON
;
203 if (NULL
== (rel_url
= strchr(con
->uri
.path
->ptr
+ 2, '/'))) {
204 /* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */
205 http_response_redirect_to_directory(srv
, con
);
207 return HANDLER_FINISHED
;
210 /* /~/ is a empty username, catch it directly */
211 if (0 == rel_url
- (con
->uri
.path
->ptr
+ 2)) {
212 return HANDLER_GO_ON
;
215 buffer_copy_string_len(p
->username
, con
->uri
.path
->ptr
+ 2, rel_url
- (con
->uri
.path
->ptr
+ 2));
217 if (buffer_string_is_empty(p
->conf
.basepath
)
219 && NULL
== (pwd
= getpwnam(p
->username
->ptr
))
223 return HANDLER_GO_ON
;
227 for (k
= 0; k
< p
->conf
.exclude_user
->used
; k
++) {
228 data_string
*ds
= (data_string
*)p
->conf
.exclude_user
->data
[k
];
230 if (buffer_is_equal(ds
->value
, p
->username
)) {
231 /* user in exclude list */
232 return HANDLER_GO_ON
;
236 if (p
->conf
.include_user
->used
) {
238 for (k
= 0; k
< p
->conf
.include_user
->used
; k
++) {
239 data_string
*ds
= (data_string
*)p
->conf
.include_user
->data
[k
];
241 if (buffer_is_equal(ds
->value
, p
->username
)) {
242 /* user in include list */
248 if (!found_user
) return HANDLER_GO_ON
;
251 /* we build the physical path */
253 if (buffer_string_is_empty(p
->conf
.basepath
)) {
255 buffer_copy_string(p
->temp_path
, pwd
->pw_dir
);
259 /* check if the username is valid
260 * a request for /~../ should lead to a directory traversal
261 * limiting to [-_a-z0-9.] should fix it */
263 for (cp
= p
->username
->ptr
; *cp
; cp
++) {
269 (c
>= 'a' && c
<= 'z') ||
270 (c
>= 'A' && c
<= 'Z') ||
271 (c
>= '0' && c
<= '9'))) {
273 return HANDLER_GO_ON
;
276 if (con
->conf
.force_lowercase_filenames
) {
277 buffer_to_lower(p
->username
);
280 buffer_copy_buffer(p
->temp_path
, p
->conf
.basepath
);
281 buffer_append_slash(p
->temp_path
);
282 if (p
->conf
.letterhomes
) {
283 buffer_append_string_len(p
->temp_path
, p
->username
->ptr
, 1);
284 buffer_append_slash(p
->temp_path
);
286 buffer_append_string_buffer(p
->temp_path
, p
->username
);
288 buffer_append_slash(p
->temp_path
);
289 buffer_append_string_buffer(p
->temp_path
, p
->conf
.path
);
291 if (buffer_string_is_empty(p
->conf
.basepath
)) {
295 ret
= stat(p
->temp_path
->ptr
, &st
);
296 if (ret
< 0 || S_ISDIR(st
.st_mode
) != 1) {
297 return HANDLER_GO_ON
;
301 buffer_copy_buffer(con
->physical
.basedir
, p
->temp_path
);
303 /* the physical rel_path is basically the same as uri.path;
304 * but it is converted to lowercase in case of force_lowercase_filenames and some special handling
305 * for trailing '.', ' ' and '/' on windows
306 * we assume that no docroot/physical handler changed this
307 * (docroot should only set the docroot/server name, phyiscal should only change the phyiscal.path;
308 * the exception mod_secdownload doesn't work with userdir anyway)
310 buffer_append_slash(p
->temp_path
);
311 /* if no second '/' is found, we assume that it was stripped from the uri.path for the special handling
313 * we do not care about the trailing slash here on windows, as we already ensured it is a directory
315 * TODO: what to do with trailing dots in usernames on windows? they may result in the same directory
316 * as a username without them.
318 if (NULL
!= (rel_url
= strchr(con
->physical
.rel_path
->ptr
+ 2, '/'))) {
319 buffer_append_string(p
->temp_path
, rel_url
+ 1); /* skip the / */
321 buffer_copy_buffer(con
->physical
.path
, p
->temp_path
);
323 buffer_reset(p
->temp_path
);
325 return HANDLER_GO_ON
;
328 /* this function is called at dlopen() time and inits the callbacks */
330 int mod_userdir_plugin_init(plugin
*p
);
331 int mod_userdir_plugin_init(plugin
*p
) {
332 p
->version
= LIGHTTPD_VERSION_ID
;
333 p
->name
= buffer_init_string("userdir");
335 p
->init
= mod_userdir_init
;
336 p
->handle_physical
= mod_userdir_docroot_handler
;
337 p
->set_defaults
= mod_userdir_set_defaults
;
338 p
->cleanup
= mod_userdir_free
;