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