1 #include "first.h" 2 3 #include "base.h" 4 #include "log.h" 5 #include "buffer.h" 6 7 #include "plugin.h" 8 9 #include <stdlib.h> 10 #include <string.h> 11 12 /* plugin config for all request/connections */ 13 typedef struct { 14 array *alias; 15 } plugin_config; 16 17 typedef struct { 18 PLUGIN_DATA; 19 20 plugin_config **config_storage; 21 22 plugin_config conf; 23 } plugin_data; 24 25 /* init the plugin data */ 26 INIT_FUNC(mod_alias_init) { 27 plugin_data *p; 28 29 p = calloc(1, sizeof(*p)); 30 31 32 33 return p; 34 } 35 36 /* detroy the plugin data */ 37 FREE_FUNC(mod_alias_free) { 38 plugin_data *p = p_d; 39 40 if (!p) return HANDLER_GO_ON; 41 42 if (p->config_storage) { 43 size_t i; 44 45 for (i = 0; i < srv->config_context->used; i++) { 46 plugin_config *s = p->config_storage[i]; 47 48 if (NULL == s) continue; 49 50 array_free(s->alias); 51 52 free(s); 53 } 54 free(p->config_storage); 55 } 56 57 free(p); 58 59 return HANDLER_GO_ON; 60 } 61 62 /* handle plugin config and check values */ 63 64 SETDEFAULTS_FUNC(mod_alias_set_defaults) { 65 plugin_data *p = p_d; 66 size_t i = 0; 67 68 config_values_t cv[] = { 69 { "alias.url", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ 70 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 71 }; 72 73 if (!p) return HANDLER_ERROR; 74 75 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *)); 76 77 for (i = 0; i < srv->config_context->used; i++) { 78 data_config const* config = (data_config const*)srv->config_context->data[i]; 79 plugin_config *s; 80 81 s = calloc(1, sizeof(plugin_config)); 82 s->alias = array_init(); 83 cv[0].destination = s->alias; 84 85 p->config_storage[i] = s; 86 87 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 88 return HANDLER_ERROR; 89 } 90 91 if (!array_is_kvstring(s->alias)) { 92 log_error_write(srv, __FILE__, __LINE__, "s", 93 "unexpected value for alias.url; expected list of \"urlpath\" => \"filepath\""); 94 return HANDLER_ERROR; 95 } 96 97 if (s->alias->used >= 2) { 98 const array *a = s->alias; 99 size_t j, k; 100 101 for (j = 0; j < a->used; j ++) { 102 const buffer *prefix = &a->sorted[j]->key; 103 for (k = j + 1; k < a->used; k ++) { 104 const buffer *key = &a->sorted[k]->key; 105 106 if (buffer_string_length(key) < buffer_string_length(prefix)) { 107 break; 108 } 109 if (memcmp(key->ptr, prefix->ptr, buffer_string_length(prefix)) != 0) { 110 break; 111 } 112 /* ok, they have same prefix. check position */ 113 const data_unset *dj = a->sorted[j]; 114 const data_unset *dk = a->sorted[k]; 115 const data_unset **data = (const data_unset **)a->data; 116 while (*data != dj && *data != dk) ++data; 117 if (*data == dj) { 118 log_error_write(srv, __FILE__, __LINE__, "SBSBS", 119 "url.alias: `", key, "' will never match as `", prefix, "' matched first"); 120 return HANDLER_ERROR; 121 } 122 } 123 } 124 } 125 } 126 127 return HANDLER_GO_ON; 128 } 129 130 #define PATCH(x) \ 131 p->conf.x = s->x; 132 static int mod_alias_patch_connection(server *srv, connection *con, plugin_data *p) { 133 size_t i, j; 134 plugin_config *s = p->config_storage[0]; 135 136 PATCH(alias); 137 138 /* skip the first, the global context */ 139 for (i = 1; i < srv->config_context->used; i++) { 140 if (!config_check_cond(con, i)) continue; /* condition not matched */ 141 142 data_config *dc = (data_config *)srv->config_context->data[i]; 143 s = p->config_storage[i]; 144 145 /* merge config */ 146 for (j = 0; j < dc->value->used; j++) { 147 data_unset *du = dc->value->data[j]; 148 149 if (buffer_is_equal_string(&du->key, CONST_STR_LEN("alias.url"))) { 150 PATCH(alias); 151 } 152 } 153 } 154 155 return 0; 156 } 157 #undef PATCH 158 159 PHYSICALPATH_FUNC(mod_alias_physical_handler) { 160 plugin_data *p = p_d; 161 char *uri_ptr; 162 size_t uri_len = buffer_string_length(con->physical.path); 163 size_t basedir_len, alias_len; 164 data_string *ds; 165 166 if (0 == uri_len) return HANDLER_GO_ON; 167 168 mod_alias_patch_connection(srv, con, p); 169 170 /* do not include trailing slash on basedir */ 171 basedir_len = buffer_string_length(con->physical.basedir); 172 if ('/' == con->physical.basedir->ptr[basedir_len-1]) --basedir_len; 173 uri_len -= basedir_len; 174 uri_ptr = con->physical.path->ptr + basedir_len; 175 176 ds = (!con->conf.force_lowercase_filenames) 177 ? (data_string *)array_match_key_prefix_klen(p->conf.alias, uri_ptr, uri_len) 178 : (data_string *)array_match_key_prefix_nc_klen(p->conf.alias, uri_ptr, uri_len); 179 if (NULL == ds) { return HANDLER_GO_ON; } 180 181 /* matched */ 182 183 /* check for path traversal in url-path following alias if key 184 * does not end in slash, but replacement value ends in slash */ 185 alias_len = buffer_string_length(&ds->key); 186 if (uri_ptr[alias_len] == '.') { 187 char *s = uri_ptr + alias_len + 1; 188 if (*s == '.') ++s; 189 if (*s == '/' || *s == '\0') { 190 size_t vlen = buffer_string_length(&ds->value); 191 if (0 != alias_len && ds->key.ptr[alias_len-1] != '/' 192 && 0 != vlen && ds->value.ptr[vlen-1] == '/') { 193 con->http_status = 403; 194 return HANDLER_FINISHED; 195 } 196 } 197 } 198 199 buffer_copy_buffer(con->physical.basedir, &ds->value); 200 buffer_copy_buffer(srv->tmp_buf, &ds->value); 201 buffer_append_string(srv->tmp_buf, uri_ptr + alias_len); 202 buffer_copy_buffer(con->physical.path, srv->tmp_buf); 203 204 return HANDLER_GO_ON; 205 } 206 207 /* this function is called at dlopen() time and inits the callbacks */ 208 209 int mod_alias_plugin_init(plugin *p); 210 int mod_alias_plugin_init(plugin *p) { 211 p->version = LIGHTTPD_VERSION_ID; 212 p->name = "alias"; 213 214 p->init = mod_alias_init; 215 p->handle_physical= mod_alias_physical_handler; 216 p->set_defaults = mod_alias_set_defaults; 217 p->cleanup = mod_alias_free; 218 219 return 0; 220 } 221