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