1 #include "first.h"
2
3 #include "base.h"
4 #include "array.h"
5 #include "buffer.h"
6 #include "log.h"
7
8 #include "plugin.h"
9
10 #include <stdlib.h>
11 #include <string.h>
12
13 typedef struct {
14 const array *alias;
15 } plugin_config;
16
17 typedef struct {
18 PLUGIN_DATA;
19 plugin_config defaults;
20 plugin_config conf;
21 } plugin_data;
22
INIT_FUNC(mod_alias_init)23 INIT_FUNC(mod_alias_init) {
24 return ck_calloc(1, sizeof(plugin_data));
25 }
26
mod_alias_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)27 static void mod_alias_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
28 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
29 case 0: /* alias.url */
30 pconf->alias = cpv->v.a;
31 break;
32 default:/* should not happen */
33 return;
34 }
35 }
36
mod_alias_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)37 static void mod_alias_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
38 do {
39 mod_alias_merge_config_cpv(pconf, cpv);
40 } while ((++cpv)->k_id != -1);
41 }
42
mod_alias_patch_config(request_st * const r,plugin_data * const p)43 static void mod_alias_patch_config(request_st * const r, plugin_data * const p) {
44 p->conf = p->defaults; /* copy small struct instead of memcpy() */
45 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
46 for (int i = 1, used = p->nconfig; i < used; ++i) {
47 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
48 mod_alias_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
49 }
50 }
51
mod_alias_check_order(server * const srv,const array * const a)52 static int mod_alias_check_order(server * const srv, const array * const a) {
53 for (uint32_t j = 0; j < a->used; ++j) {
54 const buffer * const prefix = &a->data[j]->key;
55 const size_t plen = buffer_clen(prefix);
56 for (uint32_t k = j + 1; k < a->used; ++k) {
57 const buffer * const key = &a->data[k]->key;
58 if (buffer_clen(key) < plen) {
59 break;
60 }
61 if (memcmp(key->ptr, prefix->ptr, plen) != 0) {
62 break;
63 }
64 /* ok, they have same prefix. check position */
65 const data_unset *dj = a->data[j];
66 const data_unset *dk = a->data[k];
67 const data_unset **data = (const data_unset **)a->data;
68 while (*data != dj && *data != dk) ++data;
69 if (*data == dj) {
70 log_error(srv->errh, __FILE__, __LINE__,
71 "alias.url: `%s' will never match as `%s' matched first",
72 key->ptr, prefix->ptr);
73 return 0;
74 }
75 }
76 }
77 return 1;
78 }
79
SETDEFAULTS_FUNC(mod_alias_set_defaults)80 SETDEFAULTS_FUNC(mod_alias_set_defaults) {
81 static const config_plugin_keys_t cpk[] = {
82 { CONST_STR_LEN("alias.url"),
83 T_CONFIG_ARRAY_KVSTRING,
84 T_CONFIG_SCOPE_CONNECTION }
85 ,{ NULL, 0,
86 T_CONFIG_UNSET,
87 T_CONFIG_SCOPE_UNSET }
88 };
89
90 plugin_data * const p = p_d;
91 if (!config_plugin_values_init(srv, p, cpk, "mod_alias"))
92 return HANDLER_ERROR;
93
94 /* process and validate config directives
95 * (init i to 0 if global context; to 1 to skip empty global context) */
96 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
97 const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
98 for (; -1 != cpv->k_id; ++cpv) {
99 switch (cpv->k_id) {
100 case 0: /* alias.url */
101 if (cpv->v.a->used >= 2 && !mod_alias_check_order(srv,cpv->v.a))
102 return HANDLER_ERROR;
103 break;
104 default:/* should not happen */
105 break;
106 }
107 }
108 }
109
110 /* initialize p->defaults from global config context */
111 if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
112 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
113 if (-1 != cpv->k_id)
114 mod_alias_merge_config(&p->defaults, cpv);
115 }
116
117 return HANDLER_GO_ON;
118 }
119
120 static handler_t
mod_alias_remap(request_st * const r,const array * const aliases)121 mod_alias_remap (request_st * const r, const array * const aliases)
122 {
123 /* do not include trailing slash on basedir */
124 uint32_t basedir_len = buffer_clen(&r->physical.basedir);
125 if (buffer_has_pathsep_suffix(&r->physical.basedir)) --basedir_len;
126
127 const uint32_t path_len = buffer_clen(&r->physical.path);
128 if (0 == path_len || path_len < basedir_len) return HANDLER_GO_ON;
129
130 const uint32_t uri_len = path_len - basedir_len;
131 const char *uri_ptr = r->physical.path.ptr + basedir_len;
132 data_string * const ds = (data_string *)
133 (!r->conf.force_lowercase_filenames
134 ? array_match_key_prefix_klen(aliases, uri_ptr, uri_len)
135 : array_match_key_prefix_nc_klen(aliases, uri_ptr, uri_len));
136 if (NULL == ds) return HANDLER_GO_ON;
137
138 /* matched */
139
140 const uint32_t alias_len = buffer_clen(&ds->key);
141 const uint32_t vlen = buffer_clen(&ds->value);
142
143 /* check for path traversal in url-path following alias if key
144 * does not end in slash, but replacement value ends in slash */
145 if (uri_ptr[alias_len] == '.') {
146 const char *s = uri_ptr + alias_len + 1;
147 if (*s == '.') ++s;
148 if (*s == '/' || *s == '\0') {
149 if (0 != alias_len && ds->key.ptr[alias_len-1] != '/'
150 && 0 != vlen && ds->value.ptr[vlen-1] == '/') {
151 r->http_status = 403;
152 return HANDLER_FINISHED;
153 }
154 }
155 }
156
157 /*(not buffer_append_path_len();
158 * alias could be prefix instead of complete path segment,
159 * (though resulting r->physical.basedir would not be a dir))*/
160 if (vlen != basedir_len + alias_len) {
161 const uint32_t nlen = vlen + uri_len - alias_len;
162 if (path_len + buffer_string_space(&r->physical.path) < nlen) {
163 buffer_string_prepare_append(&r->physical.path, nlen - path_len);
164 uri_ptr = r->physical.path.ptr + basedir_len;/*(refresh if alloc)*/
165 }
166 memmove(r->physical.path.ptr + vlen,
167 uri_ptr + alias_len, uri_len - alias_len);
168 buffer_truncate(&r->physical.path, nlen);
169 }
170 memcpy(r->physical.path.ptr, ds->value.ptr, vlen);
171
172 buffer_copy_string_len(&r->physical.basedir, ds->value.ptr, vlen);
173
174 return HANDLER_GO_ON;
175 }
176
PHYSICALPATH_FUNC(mod_alias_physical_handler)177 PHYSICALPATH_FUNC(mod_alias_physical_handler) {
178 plugin_data * const p = p_d;
179 mod_alias_patch_config(r, p);
180 return p->conf.alias ? mod_alias_remap(r, p->conf.alias) : HANDLER_GO_ON;
181 }
182
183
184 __attribute_cold__
185 int mod_alias_plugin_init(plugin *p);
mod_alias_plugin_init(plugin * p)186 int mod_alias_plugin_init(plugin *p) {
187 p->version = LIGHTTPD_VERSION_ID;
188 p->name = "alias";
189
190 p->init = mod_alias_init;
191 p->handle_physical= mod_alias_physical_handler;
192 p->set_defaults = mod_alias_set_defaults;
193
194 return 0;
195 }
196