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