lighttpd 1.4.x https://www.lighttpd.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

286 lines
9.2 KiB

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