1 #include "base.h"
2 #include "log.h"
3 #include "buffer.h"
4
5 #include "response.h"
6
7 #include "plugin.h"
8
9 #include <sys/types.h>
10
11 #include <stdlib.h>
12 #include <string.h>
13
14 #ifdef HAVE_PWD_H
15 # include <pwd.h>
16 #endif
17
18 /* plugin config for all request/connections */
19 typedef struct {
20 array *exclude_user;
21 array *include_user;
22 buffer *path;
23 buffer *basepath;
24 unsigned short letterhomes;
25 } plugin_config;
26
27 typedef struct {
28 PLUGIN_DATA;
29
30 buffer *username;
31 buffer *temp_path;
32
33 plugin_config **config_storage;
34
35 plugin_config conf;
36 } plugin_data;
37
38 /* init the plugin data */
INIT_FUNC(mod_userdir_init)39 INIT_FUNC(mod_userdir_init) {
40 plugin_data *p;
41
42 p = calloc(1, sizeof(*p));
43
44 p->username = buffer_init();
45 p->temp_path = buffer_init();
46
47 return p;
48 }
49
50 /* detroy the plugin data */
FREE_FUNC(mod_userdir_free)51 FREE_FUNC(mod_userdir_free) {
52 plugin_data *p = p_d;
53
54 if (!p) return HANDLER_GO_ON;
55
56 if (p->config_storage) {
57 size_t i;
58
59 for (i = 0; i < srv->config_context->used; i++) {
60 plugin_config *s = p->config_storage[i];
61
62 array_free(s->include_user);
63 array_free(s->exclude_user);
64 buffer_free(s->path);
65 buffer_free(s->basepath);
66
67 free(s);
68 }
69 free(p->config_storage);
70 }
71
72 buffer_free(p->username);
73 buffer_free(p->temp_path);
74
75 free(p);
76
77 return HANDLER_GO_ON;
78 }
79
80 /* handle plugin config and check values */
81
SETDEFAULTS_FUNC(mod_userdir_set_defaults)82 SETDEFAULTS_FUNC(mod_userdir_set_defaults) {
83 plugin_data *p = p_d;
84 size_t i;
85
86 config_values_t cv[] = {
87 { "userdir.path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
88 { "userdir.exclude-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
89 { "userdir.include-user", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
90 { "userdir.basepath", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
91 { "userdir.letterhomes", NULL, T_CONFIG_BOOLEAN,T_CONFIG_SCOPE_CONNECTION }, /* 4 */
92 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
93 };
94
95 if (!p) return HANDLER_ERROR;
96
97 p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
98
99 for (i = 0; i < srv->config_context->used; i++) {
100 plugin_config *s;
101
102 s = calloc(1, sizeof(plugin_config));
103 s->exclude_user = array_init();
104 s->include_user = array_init();
105 s->path = buffer_init();
106 s->basepath = buffer_init();
107 s->letterhomes = 0;
108
109 cv[0].destination = s->path;
110 cv[1].destination = s->exclude_user;
111 cv[2].destination = s->include_user;
112 cv[3].destination = s->basepath;
113 cv[4].destination = &(s->letterhomes);
114
115 p->config_storage[i] = s;
116
117 if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
118 return HANDLER_ERROR;
119 }
120 }
121
122 return HANDLER_GO_ON;
123 }
124
125 #define PATCH(x) \
126 p->conf.x = s->x;
mod_userdir_patch_connection(server * srv,connection * con,plugin_data * p)127 static int mod_userdir_patch_connection(server *srv, connection *con, plugin_data *p) {
128 size_t i, j;
129 plugin_config *s = p->config_storage[0];
130
131 PATCH(path);
132 PATCH(exclude_user);
133 PATCH(include_user);
134 PATCH(basepath);
135 PATCH(letterhomes);
136
137 /* skip the first, the global context */
138 for (i = 1; i < srv->config_context->used; i++) {
139 data_config *dc = (data_config *)srv->config_context->data[i];
140 s = p->config_storage[i];
141
142 /* condition didn't match */
143 if (!config_check_cond(srv, con, dc)) continue;
144
145 /* merge config */
146 for (j = 0; j < dc->value->used; j++) {
147 data_unset *du = dc->value->data[j];
148
149 if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.path"))) {
150 PATCH(path);
151 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.exclude-user"))) {
152 PATCH(exclude_user);
153 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.include-user"))) {
154 PATCH(include_user);
155 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.basepath"))) {
156 PATCH(basepath);
157 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("userdir.letterhomes"))) {
158 PATCH(letterhomes);
159 }
160 }
161 }
162
163 return 0;
164 }
165 #undef PATCH
166
URIHANDLER_FUNC(mod_userdir_docroot_handler)167 URIHANDLER_FUNC(mod_userdir_docroot_handler) {
168 plugin_data *p = p_d;
169 size_t k;
170 char *rel_url;
171 #ifdef HAVE_PWD_H
172 struct passwd *pwd = NULL;
173 #endif
174
175 if (con->uri.path->used == 0) return HANDLER_GO_ON;
176
177 mod_userdir_patch_connection(srv, con, p);
178
179 /* enforce the userdir.path to be set in the config, ugly fix for #1587;
180 * should be replaced with a clean .enabled option in 1.5
181 */
182 if (p->conf.path->used == 0) return HANDLER_GO_ON;
183
184 /* /~user/foo.html -> /home/user/public_html/foo.html */
185
186 if (con->uri.path->ptr[0] != '/' ||
187 con->uri.path->ptr[1] != '~') return HANDLER_GO_ON;
188
189 if (NULL == (rel_url = strchr(con->uri.path->ptr + 2, '/'))) {
190 /* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */
191 http_response_redirect_to_directory(srv, con);
192
193 return HANDLER_FINISHED;
194 }
195
196 /* /~/ is a empty username, catch it directly */
197 if (0 == rel_url - (con->uri.path->ptr + 2)) {
198 return HANDLER_GO_ON;
199 }
200
201 buffer_copy_string_len(p->username, con->uri.path->ptr + 2, rel_url - (con->uri.path->ptr + 2));
202
203 if (buffer_is_empty(p->conf.basepath)
204 #ifdef HAVE_PWD_H
205 && NULL == (pwd = getpwnam(p->username->ptr))
206 #endif
207 ) {
208 /* user not found */
209 return HANDLER_GO_ON;
210 }
211
212
213 for (k = 0; k < p->conf.exclude_user->used; k++) {
214 data_string *ds = (data_string *)p->conf.exclude_user->data[k];
215
216 if (buffer_is_equal(ds->value, p->username)) {
217 /* user in exclude list */
218 return HANDLER_GO_ON;
219 }
220 }
221
222 if (p->conf.include_user->used) {
223 int found_user = 0;
224 for (k = 0; k < p->conf.include_user->used; k++) {
225 data_string *ds = (data_string *)p->conf.include_user->data[k];
226
227 if (buffer_is_equal(ds->value, p->username)) {
228 /* user in include list */
229 found_user = 1;
230 break;
231 }
232 }
233
234 if (!found_user) return HANDLER_GO_ON;
235 }
236
237 /* we build the physical path */
238
239 if (buffer_is_empty(p->conf.basepath)) {
240 #ifdef HAVE_PWD_H
241 buffer_copy_string(p->temp_path, pwd->pw_dir);
242 #endif
243 } else {
244 char *cp;
245 /* check if the username is valid
246 * a request for /~../ should lead to a directory traversal
247 * limiting to [-_a-z0-9.] should fix it */
248
249 for (cp = p->username->ptr; *cp; cp++) {
250 char c = *cp;
251
252 if (!(c == '-' ||
253 c == '_' ||
254 c == '.' ||
255 (c >= 'a' && c <= 'z') ||
256 (c >= 'A' && c <= 'Z') ||
257 (c >= '0' && c <= '9'))) {
258
259 return HANDLER_GO_ON;
260 }
261 }
262 if (con->conf.force_lowercase_filenames) {
263 buffer_to_lower(p->username);
264 }
265
266 buffer_copy_string_buffer(p->temp_path, p->conf.basepath);
267 BUFFER_APPEND_SLASH(p->temp_path);
268 if (p->conf.letterhomes) {
269 buffer_append_string_len(p->temp_path, p->username->ptr, 1);
270 BUFFER_APPEND_SLASH(p->temp_path);
271 }
272 buffer_append_string_buffer(p->temp_path, p->username);
273 }
274 BUFFER_APPEND_SLASH(p->temp_path);
275 buffer_append_string_buffer(p->temp_path, p->conf.path);
276
277 if (buffer_is_empty(p->conf.basepath)) {
278 struct stat st;
279 int ret;
280
281 ret = stat(p->temp_path->ptr, &st);
282 if (ret < 0 || S_ISDIR(st.st_mode) != 1) {
283 return HANDLER_GO_ON;
284 }
285 }
286
287 /* the physical rel_path is basically the same as uri.path;
288 * but it is converted to lowercase in case of force_lowercase_filenames and some special handling
289 * for trailing '.', ' ' and '/' on windows
290 * we assume that no docroot/physical handler changed this
291 * (docroot should only set the docroot/server name, phyiscal should only change the phyiscal.path;
292 * the exception mod_secure_download doesn't work with userdir anyway)
293 */
294 BUFFER_APPEND_SLASH(p->temp_path);
295 /* if no second '/' is found, we assume that it was stripped from the uri.path for the special handling
296 * on windows.
297 * we do not care about the trailing slash here on windows, as we already ensured it is a directory
298 *
299 * TODO: what to do with trailing dots in usernames on windows? they may result in the same directory
300 * as a username without them.
301 */
302 if (NULL != (rel_url = strchr(con->physical.rel_path->ptr + 2, '/'))) {
303 buffer_append_string(p->temp_path, rel_url + 1); /* skip the / */
304 }
305 buffer_copy_string_buffer(con->physical.path, p->temp_path);
306
307 buffer_reset(p->temp_path);
308
309 return HANDLER_GO_ON;
310 }
311
312 /* this function is called at dlopen() time and inits the callbacks */
313
314 int mod_userdir_plugin_init(plugin *p);
mod_userdir_plugin_init(plugin * p)315 int mod_userdir_plugin_init(plugin *p) {
316 p->version = LIGHTTPD_VERSION_ID;
317 p->name = buffer_init_string("userdir");
318
319 p->init = mod_userdir_init;
320 p->handle_physical = mod_userdir_docroot_handler;
321 p->set_defaults = mod_userdir_set_defaults;
322 p->cleanup = mod_userdir_free;
323
324 p->data = NULL;
325
326 return 0;
327 }
328