1 #include "base.h"
2 #include "log.h"
3 #include "buffer.h"
4
5 #include "plugin.h"
6
7 #include <ctype.h>
8 #include <stdlib.h>
9 #include <string.h>
10
11 #include "md5.h"
12
13 #define HASHLEN 16
14 typedef unsigned char HASH[HASHLEN];
15 #define HASHHEXLEN 32
16 typedef char HASHHEX[HASHHEXLEN+1];
17 #ifdef USE_OPENSSL
18 #define IN const
19 #else
20 #define IN
21 #endif
22 #define OUT
23
24
25 /* plugin config for all request/connections */
26
27 typedef struct {
28 buffer *doc_root;
29 buffer *secret;
30 buffer *uri_prefix;
31
32 unsigned int timeout;
33 } plugin_config;
34
35 typedef struct {
36 PLUGIN_DATA;
37
38 buffer *md5;
39
40 plugin_config **config_storage;
41
42 plugin_config conf;
43 } plugin_data;
44
45 /* init the plugin data */
INIT_FUNC(mod_secdownload_init)46 INIT_FUNC(mod_secdownload_init) {
47 plugin_data *p;
48
49 p = calloc(1, sizeof(*p));
50
51 p->md5 = buffer_init();
52
53 return p;
54 }
55
56 /* detroy the plugin data */
FREE_FUNC(mod_secdownload_free)57 FREE_FUNC(mod_secdownload_free) {
58 plugin_data *p = p_d;
59 UNUSED(srv);
60
61 if (!p) return HANDLER_GO_ON;
62
63 if (p->config_storage) {
64 size_t i;
65 for (i = 0; i < srv->config_context->used; i++) {
66 plugin_config *s = p->config_storage[i];
67
68 buffer_free(s->secret);
69 buffer_free(s->doc_root);
70 buffer_free(s->uri_prefix);
71
72 free(s);
73 }
74 free(p->config_storage);
75 }
76
77 buffer_free(p->md5);
78
79 free(p);
80
81 return HANDLER_GO_ON;
82 }
83
84 /* handle plugin config and check values */
85
SETDEFAULTS_FUNC(mod_secdownload_set_defaults)86 SETDEFAULTS_FUNC(mod_secdownload_set_defaults) {
87 plugin_data *p = p_d;
88 size_t i = 0;
89
90 config_values_t cv[] = {
91 { "secdownload.secret", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
92 { "secdownload.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
93 { "secdownload.uri-prefix", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
94 { "secdownload.timeout", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
95 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
96 };
97
98 if (!p) return HANDLER_ERROR;
99
100 p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
101
102 for (i = 0; i < srv->config_context->used; i++) {
103 plugin_config *s;
104
105 s = calloc(1, sizeof(plugin_config));
106 s->secret = buffer_init();
107 s->doc_root = buffer_init();
108 s->uri_prefix = buffer_init();
109 s->timeout = 60;
110
111 cv[0].destination = s->secret;
112 cv[1].destination = s->doc_root;
113 cv[2].destination = s->uri_prefix;
114 cv[3].destination = &(s->timeout);
115
116 p->config_storage[i] = s;
117
118 if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
119 return HANDLER_ERROR;
120 }
121 }
122
123 return HANDLER_GO_ON;
124 }
125
126 /**
127 * checks if the supplied string is a MD5 string
128 *
129 * @param str a possible MD5 string
130 * @return if the supplied string is a valid MD5 string 1 is returned otherwise 0
131 */
132
is_hex_len(const char * str,size_t len)133 static int is_hex_len(const char *str, size_t len) {
134 size_t i;
135
136 if (NULL == str) return 0;
137
138 for (i = 0; i < len && *str; i++, str++) {
139 /* illegal characters */
140 if (!((*str >= '0' && *str <= '9') ||
141 (*str >= 'a' && *str <= 'f') ||
142 (*str >= 'A' && *str <= 'F'))
143 ) {
144 return 0;
145 }
146 }
147
148 return i == len;
149 }
150
151 #define PATCH(x) \
152 p->conf.x = s->x;
mod_secdownload_patch_connection(server * srv,connection * con,plugin_data * p)153 static int mod_secdownload_patch_connection(server *srv, connection *con, plugin_data *p) {
154 size_t i, j;
155 plugin_config *s = p->config_storage[0];
156
157 PATCH(secret);
158 PATCH(doc_root);
159 PATCH(uri_prefix);
160 PATCH(timeout);
161
162 /* skip the first, the global context */
163 for (i = 1; i < srv->config_context->used; i++) {
164 data_config *dc = (data_config *)srv->config_context->data[i];
165 s = p->config_storage[i];
166
167 /* condition didn't match */
168 if (!config_check_cond(srv, con, dc)) continue;
169
170 /* merge config */
171 for (j = 0; j < dc->value->used; j++) {
172 data_unset *du = dc->value->data[j];
173
174 if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.secret"))) {
175 PATCH(secret);
176 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.document-root"))) {
177 PATCH(doc_root);
178 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.uri-prefix"))) {
179 PATCH(uri_prefix);
180 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.timeout"))) {
181 PATCH(timeout);
182 }
183 }
184 }
185
186 return 0;
187 }
188 #undef PATCH
189
190
URIHANDLER_FUNC(mod_secdownload_uri_handler)191 URIHANDLER_FUNC(mod_secdownload_uri_handler) {
192 plugin_data *p = p_d;
193 li_MD5_CTX Md5Ctx;
194 HASH HA1;
195 const char *rel_uri, *ts_str, *md5_str;
196 time_t ts = 0;
197 size_t i;
198
199 if (con->mode != DIRECT) return HANDLER_GO_ON;
200
201 if (con->uri.path->used == 0) return HANDLER_GO_ON;
202
203 mod_secdownload_patch_connection(srv, con, p);
204
205 if (buffer_is_empty(p->conf.uri_prefix)) return HANDLER_GO_ON;
206
207 if (buffer_is_empty(p->conf.secret)) {
208 log_error_write(srv, __FILE__, __LINE__, "s",
209 "secdownload.secret has to be set");
210 return HANDLER_ERROR;
211 }
212
213 if (buffer_is_empty(p->conf.doc_root)) {
214 log_error_write(srv, __FILE__, __LINE__, "s",
215 "secdownload.document-root has to be set");
216 return HANDLER_ERROR;
217 }
218
219 /*
220 * /<uri-prefix>[a-f0-9]{32}/[a-f0-9]{8}/<rel-path>
221 */
222
223 if (0 != strncmp(con->uri.path->ptr, p->conf.uri_prefix->ptr, p->conf.uri_prefix->used - 1)) return HANDLER_GO_ON;
224
225 md5_str = con->uri.path->ptr + p->conf.uri_prefix->used - 1;
226
227 if (!is_hex_len(md5_str, 32)) return HANDLER_GO_ON;
228 if (*(md5_str + 32) != '/') return HANDLER_GO_ON;
229
230 ts_str = md5_str + 32 + 1;
231
232 if (!is_hex_len(ts_str, 8)) return HANDLER_GO_ON;
233 if (*(ts_str + 8) != '/') return HANDLER_GO_ON;
234
235 for (i = 0; i < 8; i++) {
236 ts = (ts << 4) + hex2int(*(ts_str + i));
237 }
238
239 /* timed-out */
240 if ( (srv->cur_ts > ts && (unsigned int) (srv->cur_ts - ts) > p->conf.timeout) ||
241 (srv->cur_ts < ts && (unsigned int) (ts - srv->cur_ts) > p->conf.timeout) ) {
242 /* "Gone" as the url will never be valid again instead of "408 - Timeout" where the request may be repeated */
243 con->http_status = 410;
244
245 return HANDLER_FINISHED;
246 }
247
248 rel_uri = ts_str + 8;
249
250 /* checking MD5
251 *
252 * <secret><rel-path><timestamp-hex>
253 */
254
255 buffer_copy_string_buffer(p->md5, p->conf.secret);
256 buffer_append_string(p->md5, rel_uri);
257 buffer_append_string_len(p->md5, ts_str, 8);
258
259 li_MD5_Init(&Md5Ctx);
260 li_MD5_Update(&Md5Ctx, (unsigned char *)p->md5->ptr, p->md5->used - 1);
261 li_MD5_Final(HA1, &Md5Ctx);
262
263 buffer_copy_string_hex(p->md5, (char *)HA1, 16);
264
265 if (0 != strncasecmp(md5_str, p->md5->ptr, 32)) {
266 con->http_status = 403;
267
268 log_error_write(srv, __FILE__, __LINE__, "sss",
269 "md5 invalid:",
270 md5_str, p->md5->ptr);
271
272 return HANDLER_FINISHED;
273 }
274
275 /* starting with the last / we should have relative-path to the docroot
276 */
277
278 buffer_copy_string_buffer(con->physical.doc_root, p->conf.doc_root);
279 buffer_copy_string(con->physical.rel_path, rel_uri);
280 buffer_copy_string_buffer(con->physical.path, con->physical.doc_root);
281 buffer_append_string_buffer(con->physical.path, con->physical.rel_path);
282
283 return HANDLER_GO_ON;
284 }
285
286 /* this function is called at dlopen() time and inits the callbacks */
287
288 int mod_secdownload_plugin_init(plugin *p);
mod_secdownload_plugin_init(plugin * p)289 int mod_secdownload_plugin_init(plugin *p) {
290 p->version = LIGHTTPD_VERSION_ID;
291 p->name = buffer_init_string("secdownload");
292
293 p->init = mod_secdownload_init;
294 p->handle_physical = mod_secdownload_uri_handler;
295 p->set_defaults = mod_secdownload_set_defaults;
296 p->cleanup = mod_secdownload_free;
297
298 p->data = NULL;
299
300 return 0;
301 }
302