1 #include "first.h" 2 3 #include "base.h" 4 #include "array.h" 5 #include "buffer.h" 6 #include "log.h" 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 typedef struct { 22 const array *exclude_user; 23 const array *include_user; 24 const buffer *path; 25 const buffer *basepath; 26 unsigned short letterhomes; 27 unsigned short active; 28 } plugin_config; 29 30 typedef struct { 31 PLUGIN_DATA; 32 plugin_config defaults; 33 plugin_config conf; 34 } plugin_data; 35 36 INIT_FUNC(mod_userdir_init) { 37 return calloc(1, sizeof(plugin_data)); 38 } 39 40 static void mod_userdir_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 41 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 42 case 0: /* userdir.path */ 43 pconf->path = cpv->v.b; 44 break; 45 case 1: /* userdir.exclude-user */ 46 pconf->exclude_user = cpv->v.a; 47 break; 48 case 2: /* userdir.include-user */ 49 pconf->include_user = cpv->v.a; 50 break; 51 case 3: /* userdir.basepath */ 52 pconf->basepath = cpv->v.b; 53 break; 54 case 4: /* userdir.letterhomes */ 55 pconf->letterhomes = cpv->v.u; 56 break; 57 case 5: /* userdir.active */ 58 pconf->active = cpv->v.u; 59 break; 60 default:/* should not happen */ 61 return; 62 } 63 } 64 65 static void mod_userdir_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 66 do { 67 mod_userdir_merge_config_cpv(pconf, cpv); 68 } while ((++cpv)->k_id != -1); 69 } 70 71 static void mod_userdir_patch_config(request_st * const r, plugin_data * const p) { 72 memcpy(&p->conf, &p->defaults, sizeof(plugin_config)); 73 for (int i = 1, used = p->nconfig; i < used; ++i) { 74 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 75 mod_userdir_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]); 76 } 77 } 78 79 SETDEFAULTS_FUNC(mod_userdir_set_defaults) { 80 static const config_plugin_keys_t cpk[] = { 81 { CONST_STR_LEN("userdir.path"), 82 T_CONFIG_STRING, 83 T_CONFIG_SCOPE_CONNECTION } 84 ,{ CONST_STR_LEN("userdir.exclude-user"), 85 T_CONFIG_ARRAY_VLIST, 86 T_CONFIG_SCOPE_CONNECTION } 87 ,{ CONST_STR_LEN("userdir.include-user"), 88 T_CONFIG_ARRAY_VLIST, 89 T_CONFIG_SCOPE_CONNECTION } 90 ,{ CONST_STR_LEN("userdir.basepath"), 91 T_CONFIG_STRING, 92 T_CONFIG_SCOPE_CONNECTION } 93 ,{ CONST_STR_LEN("userdir.letterhomes"), 94 T_CONFIG_BOOL, 95 T_CONFIG_SCOPE_CONNECTION } 96 ,{ CONST_STR_LEN("userdir.active"), 97 T_CONFIG_BOOL, 98 T_CONFIG_SCOPE_CONNECTION } 99 ,{ NULL, 0, 100 T_CONFIG_UNSET, 101 T_CONFIG_SCOPE_UNSET } 102 }; 103 104 plugin_data * const p = p_d; 105 if (!config_plugin_values_init(srv, p, cpk, "mod_userdir")) 106 return HANDLER_ERROR; 107 108 /* enabled by default for backward compatibility; 109 * if userdir.path isn't set userdir is disabled too, 110 * but you can't disable it by setting it to an empty string. */ 111 p->defaults.active = 1; 112 113 /* initialize p->defaults from global config context */ 114 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 115 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 116 if (-1 != cpv->k_id) 117 mod_userdir_merge_config(&p->defaults, cpv); 118 } 119 120 return HANDLER_GO_ON; 121 } 122 123 static int mod_userdir_in_vlist_nc(const array * const a, const char * const k, const size_t klen) { 124 for (uint32_t i = 0, used = a->used; i < used; ++i) { 125 const data_string * const ds = (const data_string *)a->data[i]; 126 if (buffer_eq_icase_slen(&ds->value, k, klen)) return 1; 127 } 128 return 0; 129 } 130 131 static int mod_userdir_in_vlist(const array * const a, const char * const k, const size_t klen) { 132 for (uint32_t i = 0, used = a->used; i < used; ++i) { 133 const data_string * const ds = (const data_string *)a->data[i]; 134 if (buffer_eq_slen(&ds->value, k, klen)) return 1; 135 } 136 return 0; 137 } 138 139 __attribute_noinline__ 140 static handler_t mod_userdir_docroot_construct(request_st * const r, plugin_data * const p, const char * const uptr, const size_t ulen) { 141 char u[256]; 142 if (ulen >= sizeof(u)) return HANDLER_GO_ON; 143 144 memcpy(u, uptr, ulen); 145 u[ulen] = '\0'; 146 147 /* we build the physical path */ 148 buffer * const b = r->tmp_buf; 149 150 if (buffer_string_is_empty(p->conf.basepath)) { 151 #ifdef HAVE_PWD_H 152 /* XXX: future: might add cache; getpwnam() lookup is expensive */ 153 struct passwd *pwd = getpwnam(u); 154 if (pwd) { 155 struct stat st; 156 buffer_copy_string(b, pwd->pw_dir); 157 buffer_append_path_len(b, CONST_BUF_LEN(p->conf.path)); 158 if (0 != stat(b->ptr, &st) || !S_ISDIR(st.st_mode)) { 159 return HANDLER_GO_ON; 160 } 161 } 162 else /* user not found */ 163 #endif 164 return HANDLER_GO_ON; 165 } else { 166 /* check if the username is valid 167 * a request for /~../ should lead to a directory traversal 168 * limiting to [-_a-z0-9.] should fix it */ 169 if (ulen <= 2 && (u[0] == '.' && (1 == ulen || u[1] == '.'))) { 170 return HANDLER_GO_ON; 171 } 172 173 for (size_t i = 0; i < ulen; ++i) { 174 const int c = u[i]; 175 if (!(light_isalnum(c) || c == '-' || c == '_' || c == '.')) { 176 return HANDLER_GO_ON; 177 } 178 } 179 180 if (r->conf.force_lowercase_filenames) { 181 for (size_t i = 0; i < ulen; ++i) { 182 if (light_isupper(u[i])) u[i] |= 0x20; 183 } 184 } 185 186 buffer_copy_buffer(b, p->conf.basepath); 187 if (p->conf.letterhomes) { 188 if (u[0] == '.') return HANDLER_GO_ON; 189 buffer_append_path_len(b, u, 1); 190 } 191 buffer_append_path_len(b, u, ulen); 192 buffer_append_path_len(b, CONST_BUF_LEN(p->conf.path)); 193 } 194 195 buffer_copy_buffer(&r->physical.basedir, b); 196 buffer_copy_buffer(&r->physical.path, b); 197 198 /* the physical rel_path is basically the same as uri.path; 199 * but it is converted to lowercase in case of force_lowercase_filenames 200 * and some special handling for trailing '.', ' ' and '/' on windows 201 * we assume that no docroot/physical handler changed this 202 * (docroot should only set the docroot/server name, physical should only 203 * change the physical.path; 204 * the exception mod_secdownload doesn't work with userdir anyway) 205 */ 206 buffer_append_slash(&r->physical.path); 207 /* if no second '/' is found, we assume that it was stripped from the 208 * uri.path for the special handling on windows. we do not care about the 209 * trailing slash here on windows, as we already ensured it is a directory 210 * 211 * TODO: what to do with trailing dots in usernames on windows? 212 * they may result in the same directory as a username without them. 213 */ 214 char *rel_url; 215 if (NULL != (rel_url = strchr(r->physical.rel_path.ptr + 2, '/'))) { 216 buffer_append_string(&r->physical.path, rel_url + 1); /* skip the / */ 217 } 218 219 return HANDLER_GO_ON; 220 } 221 222 URIHANDLER_FUNC(mod_userdir_docroot_handler) { 223 /* /~user/foo.html -> /home/user/public_html/foo.html */ 224 225 #ifdef __COVERITY__ 226 if (buffer_is_empty(&r->uri.path)) return HANDLER_GO_ON; 227 #endif 228 229 if (r->uri.path.ptr[0] != '/' || 230 r->uri.path.ptr[1] != '~') return HANDLER_GO_ON; 231 232 plugin_data * const p = p_d; 233 mod_userdir_patch_config(r, p); 234 235 /* enforce the userdir.path to be set in the config, ugly fix for #1587; 236 * should be replaced with a clean .enabled option in 1.5 237 */ 238 if (!p->conf.active || buffer_is_empty(p->conf.path)) return HANDLER_GO_ON; 239 240 const char * const uptr = r->uri.path.ptr + 2; 241 const char * const rel_url = strchr(uptr, '/'); 242 if (NULL == rel_url) { 243 if (!*uptr) return HANDLER_GO_ON; /* "/~" is not a valid userdir path */ 244 /* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */ 245 http_response_redirect_to_directory(r, 301); 246 return HANDLER_FINISHED; 247 } 248 249 /* /~/ is a empty username, catch it directly */ 250 const size_t ulen = (size_t)(rel_url - uptr); 251 if (0 == ulen) return HANDLER_GO_ON; 252 253 /* vlists could be turned into sorted array at config time, 254 * but these lists are expected to be relatively short in most cases 255 * so there is not a huge benefit to doing so in the common case */ 256 257 if (p->conf.exclude_user) { 258 /* use case-insensitive comparison for exclude list 259 * if r->conf.force_lowercase_filenames */ 260 if (!r->conf.force_lowercase_filenames 261 ? mod_userdir_in_vlist(p->conf.exclude_user, uptr, ulen) 262 : mod_userdir_in_vlist_nc(p->conf.exclude_user, uptr, ulen)) 263 return HANDLER_GO_ON; /* user in exclude list */ 264 } 265 266 if (p->conf.include_user) { 267 if (!mod_userdir_in_vlist(p->conf.include_user, uptr, ulen)) 268 return HANDLER_GO_ON; /* user not in include list */ 269 } 270 271 return mod_userdir_docroot_construct(r, p, uptr, ulen); 272 } 273 274 275 int mod_userdir_plugin_init(plugin *p); 276 int mod_userdir_plugin_init(plugin *p) { 277 p->version = LIGHTTPD_VERSION_ID; 278 p->name = "userdir"; 279 280 p->init = mod_userdir_init; 281 p->handle_physical = mod_userdir_docroot_handler; 282 p->set_defaults = mod_userdir_set_defaults; 283 284 return 0; 285 } 286