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