blob: 990267a3c1e80b58d3620312210da69246463340 [file] [log] [blame]
David Carlier8167f302015-06-01 13:50:06 +02001#include <stdio.h>
2
3#include <common/cfgparse.h>
Willy Tarreau876054d2016-12-21 20:39:16 +01004#include <common/errors.h>
Willy Tarreau491cec22018-10-02 18:37:27 +02005#include <common/http.h>
Willy Tarreauf63386a2015-06-01 15:39:50 +02006#include <proto/arg.h>
Willy Tarreau79e57332018-10-02 16:01:16 +02007#include <proto/http_fetch.h>
David Carlier8167f302015-06-01 13:50:06 +02008#include <proto/log.h>
David Carlier608c65a2015-09-25 14:16:30 +01009#include <proto/proto_http.h>
Willy Tarreauf63386a2015-06-01 15:39:50 +020010#include <proto/sample.h>
Willy Tarreaubee9dde2016-12-21 21:25:06 +010011#include <dac.h>
12
13static struct {
14 void *atlasimgptr;
15 char *jsonpath;
16 char *cookiename;
17 size_t cookienamelen;
18 da_atlas_t atlas;
19 da_evidence_id_t useragentid;
20 da_severity_t loglevel;
21 char separator;
22 unsigned char daset:1;
23} global_deviceatlas = {
24 .loglevel = 0,
25 .jsonpath = 0,
26 .cookiename = 0,
27 .cookienamelen = 0,
28 .useragentid = 0,
29 .daset = 0,
30 .separator = '|',
31};
David Carlier8167f302015-06-01 13:50:06 +020032
33static int da_json_file(char **args, int section_type, struct proxy *curpx,
34 struct proxy *defpx, const char *file, int line,
35 char **err)
36{
37 if (*(args[1]) == 0) {
38 memprintf(err, "deviceatlas json file : expects a json path.\n");
39 return -1;
40 }
Willy Tarreaubee9dde2016-12-21 21:25:06 +010041 global_deviceatlas.jsonpath = strdup(args[1]);
David Carlier8167f302015-06-01 13:50:06 +020042 return 0;
43}
44
45static int da_log_level(char **args, int section_type, struct proxy *curpx,
46 struct proxy *defpx, const char *file, int line,
47 char **err)
48{
49 int loglevel;
50 if (*(args[1]) == 0) {
51 memprintf(err, "deviceatlas log level : expects an integer argument.\n");
52 return -1;
53 }
54
55 loglevel = atol(args[1]);
56 if (loglevel < 0 || loglevel > 3) {
57 memprintf(err, "deviceatlas log level : expects a log level between 0 and 3, %s given.\n", args[1]);
58 } else {
Willy Tarreaubee9dde2016-12-21 21:25:06 +010059 global_deviceatlas.loglevel = (da_severity_t)loglevel;
David Carlier8167f302015-06-01 13:50:06 +020060 }
61
62 return 0;
63}
64
65static int da_property_separator(char **args, int section_type, struct proxy *curpx,
66 struct proxy *defpx, const char *file, int line,
67 char **err)
68{
69 if (*(args[1]) == 0) {
70 memprintf(err, "deviceatlas property separator : expects a character argument.\n");
71 return -1;
72 }
Willy Tarreaubee9dde2016-12-21 21:25:06 +010073 global_deviceatlas.separator = *args[1];
David Carlier8167f302015-06-01 13:50:06 +020074 return 0;
75}
76
David Carlier608c65a2015-09-25 14:16:30 +010077static int da_properties_cookie(char **args, int section_type, struct proxy *curpx,
78 struct proxy *defpx, const char *file, int line,
79 char **err)
80{
81 if (*(args[1]) == 0) {
82 memprintf(err, "deviceatlas cookie name : expects a string argument.\n");
83 return -1;
84 } else {
Willy Tarreaubee9dde2016-12-21 21:25:06 +010085 global_deviceatlas.cookiename = strdup(args[1]);
David Carlier608c65a2015-09-25 14:16:30 +010086 }
Willy Tarreaubee9dde2016-12-21 21:25:06 +010087 global_deviceatlas.cookienamelen = strlen(global_deviceatlas.cookiename);
David Carlier608c65a2015-09-25 14:16:30 +010088 return 0;
89}
90
David Carlier8167f302015-06-01 13:50:06 +020091static size_t da_haproxy_read(void *ctx, size_t len, char *buf)
92{
93 return fread(buf, 1, len, ctx);
94}
95
96static da_status_t da_haproxy_seek(void *ctx, off_t off)
97{
98 return fseek(ctx, off, SEEK_SET) != -1 ? DA_OK : DA_SYS;
99}
100
101static void da_haproxy_log(da_severity_t severity, da_status_t status,
102 const char *fmt, va_list args)
103{
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100104 if (global_deviceatlas.loglevel && severity <= global_deviceatlas.loglevel) {
David Carlier8167f302015-06-01 13:50:06 +0200105 char logbuf[256];
106 vsnprintf(logbuf, sizeof(logbuf), fmt, args);
Christopher Faulet767a84b2017-11-24 16:50:31 +0100107 ha_warning("deviceatlas : %s.\n", logbuf);
David Carlier8167f302015-06-01 13:50:06 +0200108 }
109}
110
David Carlier608c65a2015-09-25 14:16:30 +0100111#define DA_COOKIENAME_DEFAULT "DAPROPS"
112
Willy Tarreau876054d2016-12-21 20:39:16 +0100113/*
114 * module init / deinit functions. Returns 0 if OK, or a combination of ERR_*.
115 */
116static int init_deviceatlas(void)
David Carlier8167f302015-06-01 13:50:06 +0200117{
Willy Tarreau876054d2016-12-21 20:39:16 +0100118 int err_code = 0;
119
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100120 if (global_deviceatlas.jsonpath != 0) {
David Carlier8167f302015-06-01 13:50:06 +0200121 FILE *jsonp;
122 da_property_decl_t extraprops[] = {{0, 0}};
123 size_t atlasimglen;
124 da_status_t status;
125
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100126 jsonp = fopen(global_deviceatlas.jsonpath, "r");
David Carlier8167f302015-06-01 13:50:06 +0200127 if (jsonp == 0) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100128 ha_alert("deviceatlas : '%s' json file has invalid path or is not readable.\n",
129 global_deviceatlas.jsonpath);
Willy Tarreau876054d2016-12-21 20:39:16 +0100130 err_code |= ERR_ALERT | ERR_FATAL;
David Carlier8167f302015-06-01 13:50:06 +0200131 goto out;
132 }
133
134 da_init();
135 da_seterrorfunc(da_haproxy_log);
136 status = da_atlas_compile(jsonp, da_haproxy_read, da_haproxy_seek,
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100137 &global_deviceatlas.atlasimgptr, &atlasimglen);
David Carlier8167f302015-06-01 13:50:06 +0200138 fclose(jsonp);
139 if (status != DA_OK) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100140 ha_alert("deviceatlas : '%s' json file is invalid.\n",
141 global_deviceatlas.jsonpath);
Willy Tarreau876054d2016-12-21 20:39:16 +0100142 err_code |= ERR_ALERT | ERR_FATAL;
David Carlier8167f302015-06-01 13:50:06 +0200143 goto out;
144 }
145
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100146 status = da_atlas_open(&global_deviceatlas.atlas, extraprops,
147 global_deviceatlas.atlasimgptr, atlasimglen);
David Carlier8167f302015-06-01 13:50:06 +0200148
149 if (status != DA_OK) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100150 ha_alert("deviceatlas : data could not be compiled.\n");
Willy Tarreau876054d2016-12-21 20:39:16 +0100151 err_code |= ERR_ALERT | ERR_FATAL;
David Carlier8167f302015-06-01 13:50:06 +0200152 goto out;
153 }
154
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100155 if (global_deviceatlas.cookiename == 0) {
156 global_deviceatlas.cookiename = strdup(DA_COOKIENAME_DEFAULT);
157 global_deviceatlas.cookienamelen = strlen(global_deviceatlas.cookiename);
David Carlier608c65a2015-09-25 14:16:30 +0100158 }
159
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100160 global_deviceatlas.useragentid = da_atlas_header_evidence_id(&global_deviceatlas.atlas,
David Carlier8167f302015-06-01 13:50:06 +0200161 "user-agent");
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100162 global_deviceatlas.daset = 1;
David Carlier8167f302015-06-01 13:50:06 +0200163
164 fprintf(stdout, "Deviceatlas module loaded.\n");
165 }
166
167out:
Willy Tarreau876054d2016-12-21 20:39:16 +0100168 return err_code;
David Carlier8167f302015-06-01 13:50:06 +0200169}
170
Willy Tarreaub149eed2016-12-21 21:03:49 +0100171static void deinit_deviceatlas(void)
David Carlier8167f302015-06-01 13:50:06 +0200172{
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100173 if (global_deviceatlas.jsonpath != 0) {
174 free(global_deviceatlas.jsonpath);
David Carlier8167f302015-06-01 13:50:06 +0200175 }
176
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100177 if (global_deviceatlas.daset == 1) {
178 free(global_deviceatlas.cookiename);
179 da_atlas_close(&global_deviceatlas.atlas);
180 free(global_deviceatlas.atlasimgptr);
David Carlier8167f302015-06-01 13:50:06 +0200181 }
182
183 da_fini();
184}
185
David Carlier608c65a2015-09-25 14:16:30 +0100186static int da_haproxy(const struct arg *args, struct sample *smp, da_deviceinfo_t *devinfo)
David Carlier8167f302015-06-01 13:50:06 +0200187{
Willy Tarreau83061a82018-07-13 11:56:34 +0200188 struct buffer *tmp;
David Carlier8167f302015-06-01 13:50:06 +0200189 da_propid_t prop, *pprop;
David Carlier8167f302015-06-01 13:50:06 +0200190 da_status_t status;
David Carlier608c65a2015-09-25 14:16:30 +0100191 da_type_t proptype;
192 const char *propname;
David Carlier8167f302015-06-01 13:50:06 +0200193 int i;
194
David Carlier8167f302015-06-01 13:50:06 +0200195 tmp = get_trash_chunk();
196 chunk_reset(tmp);
197
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200198 propname = (const char *) args[0].data.str.area;
David Carlier8167f302015-06-01 13:50:06 +0200199 i = 0;
200
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200201 for (; propname != 0; i ++,
202 propname = (const char *) args[i].data.str.area) {
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100203 status = da_atlas_getpropid(&global_deviceatlas.atlas,
David Carlier8167f302015-06-01 13:50:06 +0200204 propname, &prop);
205 if (status != DA_OK) {
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100206 chunk_appendf(tmp, "%c", global_deviceatlas.separator);
David Carlier8167f302015-06-01 13:50:06 +0200207 continue;
208 }
209 pprop = &prop;
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100210 da_atlas_getproptype(&global_deviceatlas.atlas, *pprop, &proptype);
David Carlier8167f302015-06-01 13:50:06 +0200211
212 switch (proptype) {
213 case DA_TYPE_BOOLEAN: {
214 bool val;
David Carlier608c65a2015-09-25 14:16:30 +0100215 status = da_getpropboolean(devinfo, *pprop, &val);
David Carlier8167f302015-06-01 13:50:06 +0200216 if (status == DA_OK) {
217 chunk_appendf(tmp, "%d", val);
218 }
219 break;
220 }
221 case DA_TYPE_INTEGER:
222 case DA_TYPE_NUMBER: {
223 long val;
David Carlier608c65a2015-09-25 14:16:30 +0100224 status = da_getpropinteger(devinfo, *pprop, &val);
David Carlier8167f302015-06-01 13:50:06 +0200225 if (status == DA_OK) {
226 chunk_appendf(tmp, "%ld", val);
227 }
228 break;
229 }
230 case DA_TYPE_STRING: {
231 const char *val;
David Carlier608c65a2015-09-25 14:16:30 +0100232 status = da_getpropstring(devinfo, *pprop, &val);
David Carlier8167f302015-06-01 13:50:06 +0200233 if (status == DA_OK) {
234 chunk_appendf(tmp, "%s", val);
235 }
236 break;
David Carlier608c65a2015-09-25 14:16:30 +0100237 }
David Carlier8167f302015-06-01 13:50:06 +0200238 default:
239 break;
240 }
241
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100242 chunk_appendf(tmp, "%c", global_deviceatlas.separator);
David Carlier8167f302015-06-01 13:50:06 +0200243 }
244
David Carlier608c65a2015-09-25 14:16:30 +0100245 da_close(devinfo);
David Carlier8167f302015-06-01 13:50:06 +0200246
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200247 if (tmp->data) {
248 --tmp->data;
249 tmp->area[tmp->data] = 0;
David Carlier8167f302015-06-01 13:50:06 +0200250 }
251
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200252 smp->data.u.str.area = tmp->area;
253 smp->data.u.str.data = tmp->data;
David Carlier8167f302015-06-01 13:50:06 +0200254
255 return 1;
256}
257
David Carlier608c65a2015-09-25 14:16:30 +0100258static int da_haproxy_conv(const struct arg *args, struct sample *smp, void *private)
259{
260 da_deviceinfo_t devinfo;
261 da_status_t status;
262 const char *useragent;
263 char useragentbuf[1024] = { 0 };
264 int i;
265
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200266 if (global_deviceatlas.daset == 0 || smp->data.u.str.data == 0) {
David Carlier608c65a2015-09-25 14:16:30 +0100267 return 1;
268 }
269
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200270 i = smp->data.u.str.data > sizeof(useragentbuf) ? sizeof(useragentbuf) : smp->data.u.str.data;
271 memcpy(useragentbuf, smp->data.u.str.area, i - 1);
David Carlier608c65a2015-09-25 14:16:30 +0100272 useragentbuf[i - 1] = 0;
273
274 useragent = (const char *)useragentbuf;
275
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100276 status = da_search(&global_deviceatlas.atlas, &devinfo,
277 global_deviceatlas.useragentid, useragent, 0);
David Carlier608c65a2015-09-25 14:16:30 +0100278
279 return status != DA_OK ? 0 : da_haproxy(args, smp, &devinfo);
280}
281
282#define DA_MAX_HEADERS 24
283
284static int da_haproxy_fetch(const struct arg *args, struct sample *smp, const char *kw, void *private)
285{
286 struct hdr_idx *hidx;
287 struct hdr_ctx hctx;
288 const struct http_msg *hmsg;
289 da_evidence_t ev[DA_MAX_HEADERS];
290 da_deviceinfo_t devinfo;
291 da_status_t status;
292 char vbuf[DA_MAX_HEADERS][1024] = {{ 0 }};
293 int i, nbh = 0;
294
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100295 if (global_deviceatlas.daset == 0) {
David Carlier608c65a2015-09-25 14:16:30 +0100296 return 1;
297 }
298
299 CHECK_HTTP_MESSAGE_FIRST();
300 smp->data.type = SMP_T_STR;
301
302 /**
303 * Here we go through the whole list of headers from start
304 * they will be filtered via the DeviceAtlas API itself
305 */
306 hctx.idx = 0;
307 hidx = &smp->strm->txn->hdr_idx;
308 hmsg = &smp->strm->txn->req;
309
310 while (http_find_next_header(hmsg->chn->buf->p, hidx, &hctx) == 1 &&
311 nbh < DA_MAX_HEADERS) {
312 char *pval;
313 size_t vlen;
314 da_evidence_id_t evid = -1;
315 char hbuf[24] = { 0 };
316
317 /* The HTTP headers used by the DeviceAtlas API are not longer */
David Carlier91a88b02017-11-17 08:47:25 +0000318 if (hctx.del >= sizeof(hbuf) || hctx.del <= 0 || hctx.vlen <= 0) {
David Carlier608c65a2015-09-25 14:16:30 +0100319 continue;
320 }
321
322 vlen = hctx.vlen;
323 memcpy(hbuf, hctx.line, hctx.del);
324 hbuf[hctx.del] = 0;
325 pval = (hctx.line + hctx.val);
326
327 if (strcmp(hbuf, "Accept-Language") == 0) {
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100328 evid = da_atlas_accept_language_evidence_id(&global_deviceatlas.
David Carlier608c65a2015-09-25 14:16:30 +0100329 atlas);
330 } else if (strcmp(hbuf, "Cookie") == 0) {
331 char *p, *eval;
332 int pl;
333
334 eval = pval + hctx.vlen;
335 /**
336 * The cookie value, if it exists, is located between the current header's
337 * value position and the next one
338 */
Willy Tarreau491cec22018-10-02 18:37:27 +0200339 if (http_extract_cookie_value(pval, eval, global_deviceatlas.cookiename,
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100340 global_deviceatlas.cookienamelen, 1, &p, &pl) == NULL) {
David Carlier608c65a2015-09-25 14:16:30 +0100341 continue;
342 }
343
344 vlen = (size_t)pl;
345 pval = p;
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100346 evid = da_atlas_clientprop_evidence_id(&global_deviceatlas.atlas);
David Carlier608c65a2015-09-25 14:16:30 +0100347 } else {
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100348 evid = da_atlas_header_evidence_id(&global_deviceatlas.atlas,
David Carlier608c65a2015-09-25 14:16:30 +0100349 hbuf);
350 }
351
352 if (evid == -1) {
353 continue;
354 }
355
356 i = vlen > sizeof(vbuf[nbh]) ? sizeof(vbuf[nbh]) : vlen;
357 memcpy(vbuf[nbh], pval, i - 1);
358 vbuf[nbh][i - 1] = 0;
359 ev[nbh].key = evid;
360 ev[nbh].value = vbuf[nbh];
361 ev[nbh].value[vlen] = 0;
362 ++ nbh;
363 }
364
Willy Tarreaubee9dde2016-12-21 21:25:06 +0100365 status = da_searchv(&global_deviceatlas.atlas, &devinfo,
David Carlier608c65a2015-09-25 14:16:30 +0100366 ev, nbh);
367
368 return status != DA_OK ? 0 : da_haproxy(args, smp, &devinfo);
369}
370
Willy Tarreau0d74f772015-06-01 15:42:29 +0200371static struct cfg_kw_list dacfg_kws = {{ }, {
372 { CFG_GLOBAL, "deviceatlas-json-file", da_json_file },
373 { CFG_GLOBAL, "deviceatlas-log-level", da_log_level },
374 { CFG_GLOBAL, "deviceatlas-property-separator", da_property_separator },
David Carlier608c65a2015-09-25 14:16:30 +0100375 { CFG_GLOBAL, "deviceatlas-properties-cookie", da_properties_cookie },
Willy Tarreau0d74f772015-06-01 15:42:29 +0200376 { 0, NULL, NULL },
377}};
378
Willy Tarreauf63386a2015-06-01 15:39:50 +0200379/* Note: must not be declared <const> as its list will be overwritten */
David Carlier608c65a2015-09-25 14:16:30 +0100380static struct sample_fetch_kw_list fetch_kws = {ILH, {
David Carlier840b0242016-03-16 10:09:55 +0000381 { "da-csv-fetch", da_haproxy_fetch, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV },
David Carlier608c65a2015-09-25 14:16:30 +0100382 { NULL, NULL, 0, 0, 0 },
383}};
384
385/* Note: must not be declared <const> as its list will be overwritten */
Willy Tarreauf63386a2015-06-01 15:39:50 +0200386static struct sample_conv_kw_list conv_kws = {ILH, {
David Carlier840b0242016-03-16 10:09:55 +0000387 { "da-csv-conv", da_haproxy_conv, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_T_STR },
Willy Tarreauf63386a2015-06-01 15:39:50 +0200388 { NULL, NULL, 0, 0, 0 },
389}};
390
391__attribute__((constructor))
392static void __da_init(void)
393{
394 /* register sample fetch and format conversion keywords */
David Carlier608c65a2015-09-25 14:16:30 +0100395 sample_register_fetches(&fetch_kws);
Willy Tarreauf63386a2015-06-01 15:39:50 +0200396 sample_register_convs(&conv_kws);
Willy Tarreau0d74f772015-06-01 15:42:29 +0200397 cfg_register_keywords(&dacfg_kws);
Willy Tarreau3dd483e2016-12-21 18:50:22 +0100398 hap_register_build_opts("Built with DeviceAtlas support.", 0);
Willy Tarreau876054d2016-12-21 20:39:16 +0100399 hap_register_post_check(init_deviceatlas);
Willy Tarreaub149eed2016-12-21 21:03:49 +0100400 hap_register_post_deinit(deinit_deviceatlas);
Willy Tarreauf63386a2015-06-01 15:39:50 +0200401}