blob: a214b319cf6f8da802f00dcf5c1e2f6dad8b27b7 [file] [log] [blame]
David Carlier8167f302015-06-01 13:50:06 +02001#include <stdio.h>
2
3#include <common/cfgparse.h>
Willy Tarreauf63386a2015-06-01 15:39:50 +02004#include <proto/arg.h>
David Carlier8167f302015-06-01 13:50:06 +02005#include <proto/log.h>
David Carlier608c65a2015-09-25 14:16:30 +01006#include <proto/proto_http.h>
Willy Tarreauf63386a2015-06-01 15:39:50 +02007#include <proto/sample.h>
David Carlier8167f302015-06-01 13:50:06 +02008#include <import/da.h>
9
10static int da_json_file(char **args, int section_type, struct proxy *curpx,
11 struct proxy *defpx, const char *file, int line,
12 char **err)
13{
14 if (*(args[1]) == 0) {
15 memprintf(err, "deviceatlas json file : expects a json path.\n");
16 return -1;
17 }
18 global.deviceatlas.jsonpath = strdup(args[1]);
19 return 0;
20}
21
22static int da_log_level(char **args, int section_type, struct proxy *curpx,
23 struct proxy *defpx, const char *file, int line,
24 char **err)
25{
26 int loglevel;
27 if (*(args[1]) == 0) {
28 memprintf(err, "deviceatlas log level : expects an integer argument.\n");
29 return -1;
30 }
31
32 loglevel = atol(args[1]);
33 if (loglevel < 0 || loglevel > 3) {
34 memprintf(err, "deviceatlas log level : expects a log level between 0 and 3, %s given.\n", args[1]);
35 } else {
36 global.deviceatlas.loglevel = (da_severity_t)loglevel;
37 }
38
39 return 0;
40}
41
42static int da_property_separator(char **args, int section_type, struct proxy *curpx,
43 struct proxy *defpx, const char *file, int line,
44 char **err)
45{
46 if (*(args[1]) == 0) {
47 memprintf(err, "deviceatlas property separator : expects a character argument.\n");
48 return -1;
49 }
50 global.deviceatlas.separator = *args[1];
51 return 0;
52}
53
David Carlier608c65a2015-09-25 14:16:30 +010054static int da_properties_cookie(char **args, int section_type, struct proxy *curpx,
55 struct proxy *defpx, const char *file, int line,
56 char **err)
57{
58 if (*(args[1]) == 0) {
59 memprintf(err, "deviceatlas cookie name : expects a string argument.\n");
60 return -1;
61 } else {
62 global.deviceatlas.cookiename = strdup(args[1]);
63 }
64 global.deviceatlas.cookienamelen = strlen(global.deviceatlas.cookiename);
65 return 0;
66}
67
David Carlier8167f302015-06-01 13:50:06 +020068static size_t da_haproxy_read(void *ctx, size_t len, char *buf)
69{
70 return fread(buf, 1, len, ctx);
71}
72
73static da_status_t da_haproxy_seek(void *ctx, off_t off)
74{
75 return fseek(ctx, off, SEEK_SET) != -1 ? DA_OK : DA_SYS;
76}
77
78static void da_haproxy_log(da_severity_t severity, da_status_t status,
79 const char *fmt, va_list args)
80{
Willy Tarreau6bd42e72015-06-01 15:25:46 +020081 if (global.deviceatlas.loglevel && severity <= global.deviceatlas.loglevel) {
David Carlier8167f302015-06-01 13:50:06 +020082 char logbuf[256];
83 vsnprintf(logbuf, sizeof(logbuf), fmt, args);
84 Warning("deviceatlas : %s.\n", logbuf);
85 }
86}
87
David Carlier608c65a2015-09-25 14:16:30 +010088#define DA_COOKIENAME_DEFAULT "DAPROPS"
89
David Carlier8167f302015-06-01 13:50:06 +020090int init_deviceatlas(void)
91{
92 da_status_t status = DA_SYS;
93 if (global.deviceatlas.jsonpath != 0) {
94 FILE *jsonp;
95 da_property_decl_t extraprops[] = {{0, 0}};
96 size_t atlasimglen;
97 da_status_t status;
98
99 jsonp = fopen(global.deviceatlas.jsonpath, "r");
100 if (jsonp == 0) {
101 Alert("deviceatlas : '%s' json file has invalid path or is not readable.\n",
102 global.deviceatlas.jsonpath);
103 goto out;
104 }
105
106 da_init();
107 da_seterrorfunc(da_haproxy_log);
108 status = da_atlas_compile(jsonp, da_haproxy_read, da_haproxy_seek,
109 &global.deviceatlas.atlasimgptr, &atlasimglen);
110 fclose(jsonp);
111 if (status != DA_OK) {
112 Alert("deviceatlas : '%s' json file is invalid.\n",
113 global.deviceatlas.jsonpath);
114 goto out;
115 }
116
117 status = da_atlas_open(&global.deviceatlas.atlas, extraprops,
118 global.deviceatlas.atlasimgptr, atlasimglen);
119
120 if (status != DA_OK) {
121 Alert("deviceatlas : data could not be compiled.\n");
122 goto out;
123 }
124
David Carlier608c65a2015-09-25 14:16:30 +0100125 if (global.deviceatlas.cookiename == 0) {
126 global.deviceatlas.cookiename = strdup(DA_COOKIENAME_DEFAULT);
127 global.deviceatlas.cookienamelen = strlen(global.deviceatlas.cookiename);
128 }
129
David Carlier8167f302015-06-01 13:50:06 +0200130 global.deviceatlas.useragentid = da_atlas_header_evidence_id(&global.deviceatlas.atlas,
131 "user-agent");
David Carlier608c65a2015-09-25 14:16:30 +0100132 global.deviceatlas.daset = 1;
David Carlier8167f302015-06-01 13:50:06 +0200133
134 fprintf(stdout, "Deviceatlas module loaded.\n");
135 }
136
137out:
138 return status == DA_OK;
139}
140
141void deinit_deviceatlas(void)
142{
143 if (global.deviceatlas.jsonpath != 0) {
144 free(global.deviceatlas.jsonpath);
145 }
146
David Carlier608c65a2015-09-25 14:16:30 +0100147 if (global.deviceatlas.daset == 1) {
148 free(global.deviceatlas.cookiename);
David Carlier8167f302015-06-01 13:50:06 +0200149 da_atlas_close(&global.deviceatlas.atlas);
150 free(global.deviceatlas.atlasimgptr);
151 }
152
153 da_fini();
154}
155
David Carlier608c65a2015-09-25 14:16:30 +0100156static int da_haproxy(const struct arg *args, struct sample *smp, da_deviceinfo_t *devinfo)
David Carlier8167f302015-06-01 13:50:06 +0200157{
158 struct chunk *tmp;
David Carlier8167f302015-06-01 13:50:06 +0200159 da_propid_t prop, *pprop;
David Carlier8167f302015-06-01 13:50:06 +0200160 da_status_t status;
David Carlier608c65a2015-09-25 14:16:30 +0100161 da_type_t proptype;
162 const char *propname;
David Carlier8167f302015-06-01 13:50:06 +0200163 int i;
164
David Carlier8167f302015-06-01 13:50:06 +0200165 tmp = get_trash_chunk();
166 chunk_reset(tmp);
167
David Carlier8167f302015-06-01 13:50:06 +0200168 propname = (const char *)args[0].data.str.str;
169 i = 0;
170
David Carlier8167f302015-06-01 13:50:06 +0200171 for (; propname != 0; i ++, propname = (const char *)args[i].data.str.str) {
172 status = da_atlas_getpropid(&global.deviceatlas.atlas,
173 propname, &prop);
174 if (status != DA_OK) {
175 chunk_appendf(tmp, "%c", global.deviceatlas.separator);
176 continue;
177 }
178 pprop = &prop;
179 da_atlas_getproptype(&global.deviceatlas.atlas, *pprop, &proptype);
180
181 switch (proptype) {
182 case DA_TYPE_BOOLEAN: {
183 bool val;
David Carlier608c65a2015-09-25 14:16:30 +0100184 status = da_getpropboolean(devinfo, *pprop, &val);
David Carlier8167f302015-06-01 13:50:06 +0200185 if (status == DA_OK) {
186 chunk_appendf(tmp, "%d", val);
187 }
188 break;
189 }
190 case DA_TYPE_INTEGER:
191 case DA_TYPE_NUMBER: {
192 long val;
David Carlier608c65a2015-09-25 14:16:30 +0100193 status = da_getpropinteger(devinfo, *pprop, &val);
David Carlier8167f302015-06-01 13:50:06 +0200194 if (status == DA_OK) {
195 chunk_appendf(tmp, "%ld", val);
196 }
197 break;
198 }
199 case DA_TYPE_STRING: {
200 const char *val;
David Carlier608c65a2015-09-25 14:16:30 +0100201 status = da_getpropstring(devinfo, *pprop, &val);
David Carlier8167f302015-06-01 13:50:06 +0200202 if (status == DA_OK) {
203 chunk_appendf(tmp, "%s", val);
204 }
205 break;
David Carlier608c65a2015-09-25 14:16:30 +0100206 }
David Carlier8167f302015-06-01 13:50:06 +0200207 default:
208 break;
209 }
210
211 chunk_appendf(tmp, "%c", global.deviceatlas.separator);
212 }
213
David Carlier608c65a2015-09-25 14:16:30 +0100214 da_close(devinfo);
David Carlier8167f302015-06-01 13:50:06 +0200215
216 if (tmp->len) {
217 --tmp->len;
218 tmp->str[tmp->len] = 0;
219 }
220
Thierry FOURNIER136f9d32015-08-19 09:07:19 +0200221 smp->data.u.str.str = tmp->str;
David Carlier608c65a2015-09-25 14:16:30 +0100222 smp->data.u.str.len = tmp->len;
David Carlier8167f302015-06-01 13:50:06 +0200223
224 return 1;
225}
226
David Carlier608c65a2015-09-25 14:16:30 +0100227static int da_haproxy_conv(const struct arg *args, struct sample *smp, void *private)
228{
229 da_deviceinfo_t devinfo;
230 da_status_t status;
231 const char *useragent;
232 char useragentbuf[1024] = { 0 };
233 int i;
234
David Carlier3b711382015-12-02 12:05:42 +0000235 if (global.deviceatlas.daset == 0 || smp->data.u.str.len == 0) {
David Carlier608c65a2015-09-25 14:16:30 +0100236 return 1;
237 }
238
239 i = smp->data.u.str.len > sizeof(useragentbuf) ? sizeof(useragentbuf) : smp->data.u.str.len;
240 memcpy(useragentbuf, smp->data.u.str.str, i - 1);
241 useragentbuf[i - 1] = 0;
242
243 useragent = (const char *)useragentbuf;
244
245 status = da_search(&global.deviceatlas.atlas, &devinfo,
246 global.deviceatlas.useragentid, useragent, 0);
247
248 return status != DA_OK ? 0 : da_haproxy(args, smp, &devinfo);
249}
250
251#define DA_MAX_HEADERS 24
252
253static int da_haproxy_fetch(const struct arg *args, struct sample *smp, const char *kw, void *private)
254{
255 struct hdr_idx *hidx;
256 struct hdr_ctx hctx;
257 const struct http_msg *hmsg;
258 da_evidence_t ev[DA_MAX_HEADERS];
259 da_deviceinfo_t devinfo;
260 da_status_t status;
261 char vbuf[DA_MAX_HEADERS][1024] = {{ 0 }};
262 int i, nbh = 0;
263
264 if (global.deviceatlas.daset == 0) {
265 return 1;
266 }
267
268 CHECK_HTTP_MESSAGE_FIRST();
269 smp->data.type = SMP_T_STR;
270
271 /**
272 * Here we go through the whole list of headers from start
273 * they will be filtered via the DeviceAtlas API itself
274 */
275 hctx.idx = 0;
276 hidx = &smp->strm->txn->hdr_idx;
277 hmsg = &smp->strm->txn->req;
278
279 while (http_find_next_header(hmsg->chn->buf->p, hidx, &hctx) == 1 &&
280 nbh < DA_MAX_HEADERS) {
281 char *pval;
282 size_t vlen;
283 da_evidence_id_t evid = -1;
284 char hbuf[24] = { 0 };
285
286 /* The HTTP headers used by the DeviceAtlas API are not longer */
287 if (hctx.del >= sizeof(hbuf)) {
288 continue;
289 }
290
291 vlen = hctx.vlen;
292 memcpy(hbuf, hctx.line, hctx.del);
293 hbuf[hctx.del] = 0;
294 pval = (hctx.line + hctx.val);
295
296 if (strcmp(hbuf, "Accept-Language") == 0) {
297 evid = da_atlas_accept_language_evidence_id(&global.deviceatlas.
298 atlas);
299 } else if (strcmp(hbuf, "Cookie") == 0) {
300 char *p, *eval;
301 int pl;
302
303 eval = pval + hctx.vlen;
304 /**
305 * The cookie value, if it exists, is located between the current header's
306 * value position and the next one
307 */
308 if (extract_cookie_value(pval, eval, global.deviceatlas.cookiename,
309 global.deviceatlas.cookienamelen, 1, &p, &pl) == NULL) {
310 continue;
311 }
312
313 vlen = (size_t)pl;
314 pval = p;
315 evid = da_atlas_clientprop_evidence_id(&global.deviceatlas.atlas);
316 } else {
317 evid = da_atlas_header_evidence_id(&global.deviceatlas.atlas,
318 hbuf);
319 }
320
321 if (evid == -1) {
322 continue;
323 }
324
325 i = vlen > sizeof(vbuf[nbh]) ? sizeof(vbuf[nbh]) : vlen;
326 memcpy(vbuf[nbh], pval, i - 1);
327 vbuf[nbh][i - 1] = 0;
328 ev[nbh].key = evid;
329 ev[nbh].value = vbuf[nbh];
330 ev[nbh].value[vlen] = 0;
331 ++ nbh;
332 }
333
334 status = da_searchv(&global.deviceatlas.atlas, &devinfo,
335 ev, nbh);
336
337 return status != DA_OK ? 0 : da_haproxy(args, smp, &devinfo);
338}
339
Willy Tarreau0d74f772015-06-01 15:42:29 +0200340static struct cfg_kw_list dacfg_kws = {{ }, {
341 { CFG_GLOBAL, "deviceatlas-json-file", da_json_file },
342 { CFG_GLOBAL, "deviceatlas-log-level", da_log_level },
343 { CFG_GLOBAL, "deviceatlas-property-separator", da_property_separator },
David Carlier608c65a2015-09-25 14:16:30 +0100344 { CFG_GLOBAL, "deviceatlas-properties-cookie", da_properties_cookie },
Willy Tarreau0d74f772015-06-01 15:42:29 +0200345 { 0, NULL, NULL },
346}};
347
Willy Tarreauf63386a2015-06-01 15:39:50 +0200348/* Note: must not be declared <const> as its list will be overwritten */
David Carlier608c65a2015-09-25 14:16:30 +0100349static struct sample_fetch_kw_list fetch_kws = {ILH, {
350 { "da-csv-fetch", da_haproxy_fetch, ARG5(1,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV },
351 { NULL, NULL, 0, 0, 0 },
352}};
353
354/* Note: must not be declared <const> as its list will be overwritten */
Willy Tarreauf63386a2015-06-01 15:39:50 +0200355static struct sample_conv_kw_list conv_kws = {ILH, {
David Carlier608c65a2015-09-25 14:16:30 +0100356 { "da-csv-conv", da_haproxy_conv, ARG5(1,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_T_STR },
Willy Tarreauf63386a2015-06-01 15:39:50 +0200357 { NULL, NULL, 0, 0, 0 },
358}};
359
360__attribute__((constructor))
361static void __da_init(void)
362{
363 /* register sample fetch and format conversion keywords */
David Carlier608c65a2015-09-25 14:16:30 +0100364 sample_register_fetches(&fetch_kws);
Willy Tarreauf63386a2015-06-01 15:39:50 +0200365 sample_register_convs(&conv_kws);
Willy Tarreau0d74f772015-06-01 15:42:29 +0200366 cfg_register_keywords(&dacfg_kws);
Willy Tarreauf63386a2015-06-01 15:39:50 +0200367}