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 if (!config_check_cond(con, i)) continue; /* condition not matched */ 165 166 data_config *dc = (data_config *)srv->config_context->data[i]; 167 s = p->config_storage[i]; 168 169 /* merge config */ 170 for (j = 0; j < dc->value->used; j++) { 171 data_unset *du = dc->value->data[j]; 172 173 if (buffer_is_equal_string(&du->key, CONST_STR_LEN("userdir.path"))) { 174 PATCH(path); 175 } else if (buffer_is_equal_string(&du->key, CONST_STR_LEN("userdir.exclude-user"))) { 176 PATCH(exclude_user); 177 } else if (buffer_is_equal_string(&du->key, CONST_STR_LEN("userdir.include-user"))) { 178 PATCH(include_user); 179 } else if (buffer_is_equal_string(&du->key, CONST_STR_LEN("userdir.basepath"))) { 180 PATCH(basepath); 181 } else if (buffer_is_equal_string(&du->key, CONST_STR_LEN("userdir.letterhomes"))) { 182 PATCH(letterhomes); 183 } else if (buffer_is_equal_string(&du->key, CONST_STR_LEN("userdir.active"))) { 184 PATCH(active); 185 } 186 } 187 } 188 189 return 0; 190 } 191 #undef PATCH 192 193 URIHANDLER_FUNC(mod_userdir_docroot_handler) { 194 plugin_data *p = p_d; 195 size_t k; 196 char *rel_url; 197 #ifdef HAVE_PWD_H 198 struct passwd *pwd = NULL; 199 #endif 200 201 if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON; 202 203 mod_userdir_patch_connection(srv, con, p); 204 205 /* enforce the userdir.path to be set in the config, ugly fix for #1587; 206 * should be replaced with a clean .enabled option in 1.5 207 */ 208 if (!p->conf.active || buffer_is_empty(p->conf.path)) return HANDLER_GO_ON; 209 210 /* /~user/foo.html -> /home/user/public_html/foo.html */ 211 212 if (con->uri.path->ptr[0] != '/' || 213 con->uri.path->ptr[1] != '~') return HANDLER_GO_ON; 214 215 if (NULL == (rel_url = strchr(con->uri.path->ptr + 2, '/'))) { 216 /* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */ 217 http_response_redirect_to_directory(srv, con, 301); 218 219 return HANDLER_FINISHED; 220 } 221 222 /* /~/ is a empty username, catch it directly */ 223 if (0 == rel_url - (con->uri.path->ptr + 2)) { 224 return HANDLER_GO_ON; 225 } 226 227 buffer_copy_string_len(p->username, con->uri.path->ptr + 2, rel_url - (con->uri.path->ptr + 2)); 228 229 if (buffer_string_is_empty(p->conf.basepath) 230 #ifdef HAVE_PWD_H 231 && NULL == (pwd = getpwnam(p->username->ptr)) 232 #endif 233 ) { 234 /* user not found */ 235 return HANDLER_GO_ON; 236 } 237 238 239 for (k = 0; k < p->conf.exclude_user->used; k++) { 240 data_string *ds = (data_string *)p->conf.exclude_user->data[k]; 241 242 if (buffer_is_equal(&ds->value, p->username)) { 243 /* user in exclude list */ 244 return HANDLER_GO_ON; 245 } 246 } 247 248 if (p->conf.include_user->used) { 249 int found_user = 0; 250 for (k = 0; k < p->conf.include_user->used; k++) { 251 data_string *ds = (data_string *)p->conf.include_user->data[k]; 252 253 if (buffer_is_equal(&ds->value, p->username)) { 254 /* user in include list */ 255 found_user = 1; 256 break; 257 } 258 } 259 260 if (!found_user) return HANDLER_GO_ON; 261 } 262 263 /* we build the physical path */ 264 buffer_clear(p->temp_path); 265 266 if (buffer_string_is_empty(p->conf.basepath)) { 267 #ifdef HAVE_PWD_H 268 buffer_copy_string(p->temp_path, pwd->pw_dir); 269 #endif 270 } else { 271 char *cp = p->username->ptr; 272 /* check if the username is valid 273 * a request for /~../ should lead to a directory traversal 274 * limiting to [-_a-z0-9.] should fix it */ 275 if (cp[0] == '.' && (cp[1] == '\0' || (cp[1] == '.' && cp[2] == '\0'))) { 276 return HANDLER_GO_ON; 277 } 278 279 for (; *cp; cp++) { 280 char c = *cp; 281 if (!(light_isalnum(c) || c == '-' || c == '_' || c == '.')) { 282 return HANDLER_GO_ON; 283 } 284 } 285 if (con->conf.force_lowercase_filenames) { 286 buffer_to_lower(p->username); 287 } 288 289 buffer_copy_buffer(p->temp_path, p->conf.basepath); 290 if (p->conf.letterhomes) { 291 if (p->username->ptr[0] == '.') return HANDLER_GO_ON; 292 buffer_append_path_len(p->temp_path, p->username->ptr, 1); 293 } 294 buffer_append_path_len(p->temp_path, CONST_BUF_LEN(p->username)); 295 } 296 buffer_append_path_len(p->temp_path, CONST_BUF_LEN(p->conf.path)); 297 298 if (buffer_string_is_empty(p->conf.basepath)) { 299 struct stat st; 300 int ret; 301 302 ret = stat(p->temp_path->ptr, &st); 303 if (ret < 0 || S_ISDIR(st.st_mode) != 1) { 304 return HANDLER_GO_ON; 305 } 306 } 307 308 buffer_copy_buffer(con->physical.basedir, p->temp_path); 309 310 /* the physical rel_path is basically the same as uri.path; 311 * but it is converted to lowercase in case of force_lowercase_filenames and some special handling 312 * for trailing '.', ' ' and '/' on windows 313 * we assume that no docroot/physical handler changed this 314 * (docroot should only set the docroot/server name, phyiscal should only change the phyiscal.path; 315 * the exception mod_secdownload doesn't work with userdir anyway) 316 */ 317 buffer_append_slash(p->temp_path); 318 /* if no second '/' is found, we assume that it was stripped from the uri.path for the special handling 319 * on windows. 320 * we do not care about the trailing slash here on windows, as we already ensured it is a directory 321 * 322 * TODO: what to do with trailing dots in usernames on windows? they may result in the same directory 323 * as a username without them. 324 */ 325 if (NULL != (rel_url = strchr(con->physical.rel_path->ptr + 2, '/'))) { 326 buffer_append_string(p->temp_path, rel_url + 1); /* skip the / */ 327 } 328 buffer_copy_buffer(con->physical.path, p->temp_path); 329 330 return HANDLER_GO_ON; 331 } 332 333 /* this function is called at dlopen() time and inits the callbacks */ 334 335 int mod_userdir_plugin_init(plugin *p); 336 int mod_userdir_plugin_init(plugin *p) { 337 p->version = LIGHTTPD_VERSION_ID; 338 p->name = "userdir"; 339 340 p->init = mod_userdir_init; 341 p->handle_physical = mod_userdir_docroot_handler; 342 p->set_defaults = mod_userdir_set_defaults; 343 p->cleanup = mod_userdir_free; 344 345 return 0; 346 } 347