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 /* plugin config for all request/connections */
14 
15 typedef struct {
16 	buffer *cookie_name;
17 	buffer *cookie_domain;
18 	unsigned int cookie_max_age;
19 } plugin_config;
20 
21 typedef struct {
22 	PLUGIN_DATA;
23 
24 	plugin_config **config_storage;
25 
26 	plugin_config conf;
27 } plugin_data;
28 
29 /* init the plugin data */
INIT_FUNC(mod_usertrack_init)30 INIT_FUNC(mod_usertrack_init) {
31 	plugin_data *p;
32 
33 	p = calloc(1, sizeof(*p));
34 
35 	return p;
36 }
37 
38 /* detroy the plugin data */
FREE_FUNC(mod_usertrack_free)39 FREE_FUNC(mod_usertrack_free) {
40 	plugin_data *p = p_d;
41 
42 	UNUSED(srv);
43 
44 	if (!p) return HANDLER_GO_ON;
45 
46 	if (p->config_storage) {
47 		size_t i;
48 		for (i = 0; i < srv->config_context->used; i++) {
49 			plugin_config *s = p->config_storage[i];
50 
51 			buffer_free(s->cookie_name);
52 			buffer_free(s->cookie_domain);
53 
54 			free(s);
55 		}
56 		free(p->config_storage);
57 	}
58 
59 	free(p);
60 
61 	return HANDLER_GO_ON;
62 }
63 
64 /* handle plugin config and check values */
65 
SETDEFAULTS_FUNC(mod_usertrack_set_defaults)66 SETDEFAULTS_FUNC(mod_usertrack_set_defaults) {
67 	plugin_data *p = p_d;
68 	size_t i = 0;
69 
70 	config_values_t cv[] = {
71 		{ "usertrack.cookie-name",       NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },       /* 0 */
72 		{ "usertrack.cookie-max-age",    NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION },          /* 1 */
73 		{ "usertrack.cookie-domain",     NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },       /* 2 */
74 
75 		{ "usertrack.cookiename",        NULL, T_CONFIG_DEPRECATED, T_CONFIG_SCOPE_CONNECTION },
76 		{ NULL,                          NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
77 	};
78 
79 	if (!p) return HANDLER_ERROR;
80 
81 	p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
82 
83 	for (i = 0; i < srv->config_context->used; i++) {
84 		plugin_config *s;
85 
86 		s = calloc(1, sizeof(plugin_config));
87 		s->cookie_name    = buffer_init();
88 		s->cookie_domain  = buffer_init();
89 		s->cookie_max_age = 0;
90 
91 		cv[0].destination = s->cookie_name;
92 		cv[1].destination = &(s->cookie_max_age);
93 		cv[2].destination = s->cookie_domain;
94 
95 		p->config_storage[i] = s;
96 
97 		if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
98 			return HANDLER_ERROR;
99 		}
100 
101 		if (buffer_is_empty(s->cookie_name)) {
102 			buffer_copy_string_len(s->cookie_name, CONST_STR_LEN("TRACKID"));
103 		} else {
104 			size_t j;
105 			for (j = 0; j < s->cookie_name->used - 1; j++) {
106 				char c = s->cookie_name->ptr[j] | 32;
107 				if (c < 'a' || c > 'z') {
108 					log_error_write(srv, __FILE__, __LINE__, "sb",
109 							"invalid character in usertrack.cookie-name:",
110 							s->cookie_name);
111 
112 					return HANDLER_ERROR;
113 				}
114 			}
115 		}
116 
117 		if (!buffer_is_empty(s->cookie_domain)) {
118 			size_t j;
119 			for (j = 0; j < s->cookie_domain->used - 1; j++) {
120 				char c = s->cookie_domain->ptr[j];
121 				if (c <= 32 || c >= 127 || c == '"' || c == '\\') {
122 					log_error_write(srv, __FILE__, __LINE__, "sb",
123 							"invalid character in usertrack.cookie-domain:",
124 							s->cookie_domain);
125 
126 					return HANDLER_ERROR;
127 				}
128 			}
129 		}
130 	}
131 
132 	return HANDLER_GO_ON;
133 }
134 
135 #define PATCH(x) \
136 	p->conf.x = s->x;
mod_usertrack_patch_connection(server * srv,connection * con,plugin_data * p)137 static int mod_usertrack_patch_connection(server *srv, connection *con, plugin_data *p) {
138 	size_t i, j;
139 	plugin_config *s = p->config_storage[0];
140 
141 	PATCH(cookie_name);
142 	PATCH(cookie_domain);
143 	PATCH(cookie_max_age);
144 
145 	/* skip the first, the global context */
146 	for (i = 1; i < srv->config_context->used; i++) {
147 		data_config *dc = (data_config *)srv->config_context->data[i];
148 		s = p->config_storage[i];
149 
150 		/* condition didn't match */
151 		if (!config_check_cond(srv, con, dc)) continue;
152 
153 		/* merge config */
154 		for (j = 0; j < dc->value->used; j++) {
155 			data_unset *du = dc->value->data[j];
156 
157 			if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookie-name"))) {
158 				PATCH(cookie_name);
159 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookie-max-age"))) {
160 				PATCH(cookie_max_age);
161 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("usertrack.cookie-domain"))) {
162 				PATCH(cookie_domain);
163 			}
164 		}
165 	}
166 
167 	return 0;
168 }
169 #undef PATCH
170 
URIHANDLER_FUNC(mod_usertrack_uri_handler)171 URIHANDLER_FUNC(mod_usertrack_uri_handler) {
172 	plugin_data *p = p_d;
173 	data_string *ds;
174 	unsigned char h[16];
175 	li_MD5_CTX Md5Ctx;
176 	char hh[32];
177 
178 	if (con->uri.path->used == 0) return HANDLER_GO_ON;
179 
180 	mod_usertrack_patch_connection(srv, con, p);
181 
182 	if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "Cookie"))) {
183 		char *g;
184 		/* we have a cookie, does it contain a valid name ? */
185 
186 		/* parse the cookie
187 		 *
188 		 * check for cookiename + (WS | '=')
189 		 *
190 		 */
191 
192 		if (NULL != (g = strstr(ds->value->ptr, p->conf.cookie_name->ptr))) {
193 			char *nc;
194 
195 			/* skip WS */
196 			for (nc = g + p->conf.cookie_name->used-1; *nc == ' ' || *nc == '\t'; nc++);
197 
198 			if (*nc == '=') {
199 				/* ok, found the key of our own cookie */
200 
201 				if (strlen(nc) > 32) {
202 					/* i'm lazy */
203 					return HANDLER_GO_ON;
204 				}
205 			}
206 		}
207 	}
208 
209 	/* set a cookie */
210 	if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
211 		ds = data_response_init();
212 	}
213 	buffer_copy_string_len(ds->key, CONST_STR_LEN("Set-Cookie"));
214 	buffer_copy_string_buffer(ds->value, p->conf.cookie_name);
215 	buffer_append_string_len(ds->value, CONST_STR_LEN("="));
216 
217 
218 	/* taken from mod_auth.c */
219 
220 	/* generate shared-secret */
221 	li_MD5_Init(&Md5Ctx);
222 	li_MD5_Update(&Md5Ctx, (unsigned char *)con->uri.path->ptr, con->uri.path->used - 1);
223 	li_MD5_Update(&Md5Ctx, (unsigned char *)"+", 1);
224 
225 	/* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
226 	LI_ltostr(hh, srv->cur_ts);
227 	li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
228 	li_MD5_Update(&Md5Ctx, (unsigned char *)srv->entropy, sizeof(srv->entropy));
229 	LI_ltostr(hh, rand());
230 	li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
231 
232 	li_MD5_Final(h, &Md5Ctx);
233 
234 	buffer_append_string_encoded(ds->value, (char *)h, 16, ENCODING_HEX);
235 	buffer_append_string_len(ds->value, CONST_STR_LEN("; Path=/"));
236 	buffer_append_string_len(ds->value, CONST_STR_LEN("; Version=1"));
237 
238 	if (!buffer_is_empty(p->conf.cookie_domain)) {
239 		buffer_append_string_len(ds->value, CONST_STR_LEN("; Domain="));
240 		buffer_append_string_encoded(ds->value, CONST_BUF_LEN(p->conf.cookie_domain), ENCODING_REL_URI);
241 	}
242 
243 	if (p->conf.cookie_max_age) {
244 		buffer_append_string_len(ds->value, CONST_STR_LEN("; max-age="));
245 		buffer_append_long(ds->value, p->conf.cookie_max_age);
246 	}
247 
248 	array_insert_unique(con->response.headers, (data_unset *)ds);
249 
250 	return HANDLER_GO_ON;
251 }
252 
253 /* this function is called at dlopen() time and inits the callbacks */
254 
255 int mod_usertrack_plugin_init(plugin *p);
mod_usertrack_plugin_init(plugin * p)256 int mod_usertrack_plugin_init(plugin *p) {
257 	p->version     = LIGHTTPD_VERSION_ID;
258 	p->name        = buffer_init_string("usertrack");
259 
260 	p->init        = mod_usertrack_init;
261 	p->handle_uri_clean  = mod_usertrack_uri_handler;
262 	p->set_defaults  = mod_usertrack_set_defaults;
263 	p->cleanup     = mod_usertrack_free;
264 
265 	p->data        = NULL;
266 
267 	return 0;
268 }
269