blob: daa789abb6dfa5823a662155547ad1c616d23068 [file] [log] [blame]
Willy Tarreau79e57332018-10-02 16:01:16 +02001/*
2 * HTTP actions
3 *
4 * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <sys/types.h>
14
15#include <ctype.h>
16#include <string.h>
17#include <time.h>
18
19#include <common/chunk.h>
20#include <common/compat.h>
21#include <common/config.h>
22#include <common/debug.h>
23#include <common/http.h>
Willy Tarreau0108d902018-11-25 19:14:37 +010024#include <common/initcall.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020025#include <common/memory.h>
26#include <common/standard.h>
27#include <common/version.h>
28
29#include <types/capture.h>
30#include <types/global.h>
31
32#include <proto/acl.h>
33#include <proto/arg.h>
Willy Tarreau61c112a2018-10-02 16:43:32 +020034#include <proto/http_rules.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020035#include <proto/log.h>
36#include <proto/proto_http.h>
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +010037#include <proto/stream_interface.h>
Willy Tarreau79e57332018-10-02 16:01:16 +020038
39
40/* This function executes one of the set-{method,path,query,uri} actions. It
41 * builds a string in the trash from the specified format string. It finds
42 * the action to be performed in <http.action>, previously filled by function
43 * parse_set_req_line(). The replacement action is excuted by the function
44 * http_action_set_req_line(). It always returns ACT_RET_CONT. If an error
45 * occurs the action is canceled, but the rule processing continue.
46 */
47static enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px,
48 struct session *sess, struct stream *s, int flags)
49{
50 struct buffer *replace;
51 enum act_return ret = ACT_RET_ERR;
52
53 replace = alloc_trash_chunk();
54 if (!replace)
55 goto leave;
56
57 /* If we have to create a query string, prepare a '?'. */
58 if (rule->arg.http.action == 2)
59 replace->area[replace->data++] = '?';
60 replace->data += build_logline(s, replace->area + replace->data,
61 replace->size - replace->data,
62 &rule->arg.http.logfmt);
63
64 http_replace_req_line(rule->arg.http.action, replace->area,
65 replace->data, px, s);
66
67 ret = ACT_RET_CONT;
68
69leave:
70 free_trash_chunk(replace);
71 return ret;
72}
73
74/* parse an http-request action among :
75 * set-method
76 * set-path
77 * set-query
78 * set-uri
79 *
80 * All of them accept a single argument of type string representing a log-format.
81 * The resulting rule makes use of arg->act.p[0..1] to store the log-format list
82 * head, and p[2] to store the action as an int (0=method, 1=path, 2=query, 3=uri).
83 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
84 */
85static enum act_parse_ret parse_set_req_line(const char **args, int *orig_arg, struct proxy *px,
86 struct act_rule *rule, char **err)
87{
88 int cur_arg = *orig_arg;
89
90 rule->action = ACT_CUSTOM;
91
92 switch (args[0][4]) {
93 case 'm' :
94 rule->arg.http.action = 0;
95 rule->action_ptr = http_action_set_req_line;
96 break;
97 case 'p' :
98 rule->arg.http.action = 1;
99 rule->action_ptr = http_action_set_req_line;
100 break;
101 case 'q' :
102 rule->arg.http.action = 2;
103 rule->action_ptr = http_action_set_req_line;
104 break;
105 case 'u' :
106 rule->arg.http.action = 3;
107 rule->action_ptr = http_action_set_req_line;
108 break;
109 default:
110 memprintf(err, "internal error: unhandled action '%s'", args[0]);
111 return ACT_RET_PRS_ERR;
112 }
113
114 if (!*args[cur_arg] ||
115 (*args[cur_arg + 1] && strcmp(args[cur_arg + 1], "if") != 0 && strcmp(args[cur_arg + 1], "unless") != 0)) {
116 memprintf(err, "expects exactly 1 argument <format>");
117 return ACT_RET_PRS_ERR;
118 }
119
120 LIST_INIT(&rule->arg.http.logfmt);
121 px->conf.args.ctx = ARGC_HRQ;
122 if (!parse_logformat_string(args[cur_arg], px, &rule->arg.http.logfmt, LOG_OPT_HTTP,
123 (px->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, err)) {
124 return ACT_RET_PRS_ERR;
125 }
126
127 (*orig_arg)++;
128 return ACT_RET_PRS_OK;
129}
130
131/* This function is just a compliant action wrapper for "set-status". */
132static enum act_return action_http_set_status(struct act_rule *rule, struct proxy *px,
133 struct session *sess, struct stream *s, int flags)
134{
135 http_set_status(rule->arg.status.code, rule->arg.status.reason, s);
136 return ACT_RET_CONT;
137}
138
139/* parse set-status action:
140 * This action accepts a single argument of type int representing
141 * an http status code. It returns ACT_RET_PRS_OK on success,
142 * ACT_RET_PRS_ERR on error.
143 */
144static enum act_parse_ret parse_http_set_status(const char **args, int *orig_arg, struct proxy *px,
145 struct act_rule *rule, char **err)
146{
147 char *error;
148
149 rule->action = ACT_CUSTOM;
150 rule->action_ptr = action_http_set_status;
151
152 /* Check if an argument is available */
153 if (!*args[*orig_arg]) {
154 memprintf(err, "expects 1 argument: <status>; or 3 arguments: <status> reason <fmt>");
155 return ACT_RET_PRS_ERR;
156 }
157
158 /* convert status code as integer */
159 rule->arg.status.code = strtol(args[*orig_arg], &error, 10);
160 if (*error != '\0' || rule->arg.status.code < 100 || rule->arg.status.code > 999) {
161 memprintf(err, "expects an integer status code between 100 and 999");
162 return ACT_RET_PRS_ERR;
163 }
164
165 (*orig_arg)++;
166
167 /* set custom reason string */
168 rule->arg.status.reason = NULL; // If null, we use the default reason for the status code.
169 if (*args[*orig_arg] && strcmp(args[*orig_arg], "reason") == 0 &&
170 (*args[*orig_arg + 1] && strcmp(args[*orig_arg + 1], "if") != 0 && strcmp(args[*orig_arg + 1], "unless") != 0)) {
171 (*orig_arg)++;
172 rule->arg.status.reason = strdup(args[*orig_arg]);
173 (*orig_arg)++;
174 }
175
176 return ACT_RET_PRS_OK;
177}
178
179/* This function executes the "reject" HTTP action. It clears the request and
180 * response buffer without sending any response. It can be useful as an HTTP
181 * alternative to the silent-drop action to defend against DoS attacks, and may
182 * also be used with HTTP/2 to close a connection instead of just a stream.
183 * The txn status is unchanged, indicating no response was sent. The termination
184 * flags will indicate "PR". It always returns ACT_RET_STOP.
185 */
186static enum act_return http_action_reject(struct act_rule *rule, struct proxy *px,
187 struct session *sess, struct stream *s, int flags)
188{
Willy Tarreau0f9cd7b2019-01-31 19:02:43 +0100189 si_must_kill_conn(chn_prod(&s->req));
Willy Tarreau79e57332018-10-02 16:01:16 +0200190 channel_abort(&s->req);
191 channel_abort(&s->res);
192 s->req.analysers = 0;
193 s->res.analysers = 0;
194
Olivier Houcharda798bf52019-03-08 18:52:00 +0100195 _HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
196 _HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200197 if (sess->listener && sess->listener->counters)
Olivier Houcharda798bf52019-03-08 18:52:00 +0100198 _HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
Willy Tarreau79e57332018-10-02 16:01:16 +0200199
200 if (!(s->flags & SF_ERR_MASK))
201 s->flags |= SF_ERR_PRXCOND;
202 if (!(s->flags & SF_FINST_MASK))
203 s->flags |= SF_FINST_R;
204
Willy Tarreau11c90fb2019-05-28 08:26:17 +0200205 return ACT_RET_STOP;
Willy Tarreau79e57332018-10-02 16:01:16 +0200206}
207
208/* parse the "reject" action:
209 * This action takes no argument and returns ACT_RET_PRS_OK on success,
210 * ACT_RET_PRS_ERR on error.
211 */
212static enum act_parse_ret parse_http_action_reject(const char **args, int *orig_arg, struct proxy *px,
213 struct act_rule *rule, char **err)
214{
215 rule->action = ACT_CUSTOM;
216 rule->action_ptr = http_action_reject;
217 return ACT_RET_PRS_OK;
218}
219
Olivier Houchard602bf7d2019-05-10 13:59:15 +0200220/* This function executes the "disable-l7-retry" HTTP action.
221 * It disables L7 retries (all retry except for a connection failure). This
222 * can be useful for example to avoid retrying on POST requests.
223 * It just removes the L7 retry flag on the stream_interface, and always
224 * return ACT_RET_CONT;
225 */
226static enum act_return http_req_disable_l7_retry(struct act_rule *rule, struct proxy *px,
227 struct session *sess, struct stream *s, int flags)
228{
229 struct stream_interface *si = &s->si[1];
230
231 /* In theory, the SI_FL_L7_RETRY flags isn't set at this point, but
232 * let's be future-proof and remove it anyway.
233 */
234 si->flags &= ~SI_FL_L7_RETRY;
235 si->flags |= SI_FL_D_L7_RETRY;
236 return ACT_RET_CONT;
237}
238
239/* parse the "disable-l7-retry" action:
240 * This action takes no argument and returns ACT_RET_PRS_OK on success,
241 * ACT_RET_PRS_ERR on error.
242 */
243static enum act_parse_ret parse_http_req_disable_l7_retry(const char **args,
244 int *orig_args, struct proxy *px,
245 struct act_rule *rule, char **err)
246{
247 rule->action = ACT_CUSTOM;
248 rule->action_ptr = http_req_disable_l7_retry;
249 return ACT_RET_PRS_OK;
250}
251
Willy Tarreau79e57332018-10-02 16:01:16 +0200252/* This function executes the "capture" action. It executes a fetch expression,
253 * turns the result into a string and puts it in a capture slot. It always
254 * returns 1. If an error occurs the action is cancelled, but the rule
255 * processing continues.
256 */
257static enum act_return http_action_req_capture(struct act_rule *rule, struct proxy *px,
258 struct session *sess, struct stream *s, int flags)
259{
260 struct sample *key;
261 struct cap_hdr *h = rule->arg.cap.hdr;
262 char **cap = s->req_cap;
263 int len;
264
265 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.cap.expr, SMP_T_STR);
266 if (!key)
267 return ACT_RET_CONT;
268
269 if (cap[h->index] == NULL)
270 cap[h->index] = pool_alloc(h->pool);
271
272 if (cap[h->index] == NULL) /* no more capture memory */
273 return ACT_RET_CONT;
274
275 len = key->data.u.str.data;
276 if (len > h->len)
277 len = h->len;
278
279 memcpy(cap[h->index], key->data.u.str.area, len);
280 cap[h->index][len] = 0;
281 return ACT_RET_CONT;
282}
283
284/* This function executes the "capture" action and store the result in a
285 * capture slot if exists. It executes a fetch expression, turns the result
286 * into a string and puts it in a capture slot. It always returns 1. If an
287 * error occurs the action is cancelled, but the rule processing continues.
288 */
289static enum act_return http_action_req_capture_by_id(struct act_rule *rule, struct proxy *px,
290 struct session *sess, struct stream *s, int flags)
291{
292 struct sample *key;
293 struct cap_hdr *h;
294 char **cap = s->req_cap;
295 struct proxy *fe = strm_fe(s);
296 int len;
297 int i;
298
299 /* Look for the original configuration. */
300 for (h = fe->req_cap, i = fe->nb_req_cap - 1;
301 h != NULL && i != rule->arg.capid.idx ;
302 i--, h = h->next);
303 if (!h)
304 return ACT_RET_CONT;
305
306 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
307 if (!key)
308 return ACT_RET_CONT;
309
310 if (cap[h->index] == NULL)
311 cap[h->index] = pool_alloc(h->pool);
312
313 if (cap[h->index] == NULL) /* no more capture memory */
314 return ACT_RET_CONT;
315
316 len = key->data.u.str.data;
317 if (len > h->len)
318 len = h->len;
319
320 memcpy(cap[h->index], key->data.u.str.area, len);
321 cap[h->index][len] = 0;
322 return ACT_RET_CONT;
323}
324
325/* Check an "http-request capture" action.
326 *
327 * The function returns 1 in success case, otherwise, it returns 0 and err is
328 * filled.
329 */
330static int check_http_req_capture(struct act_rule *rule, struct proxy *px, char **err)
331{
332 if (rule->action_ptr != http_action_req_capture_by_id)
333 return 1;
334
335 if (rule->arg.capid.idx >= px->nb_req_cap) {
336 memprintf(err, "unable to find capture id '%d' referenced by http-request capture rule",
337 rule->arg.capid.idx);
338 return 0;
339 }
340
341 return 1;
342}
343
344/* parse an "http-request capture" action. It takes a single argument which is
345 * a sample fetch expression. It stores the expression into arg->act.p[0] and
346 * the allocated hdr_cap struct or the preallocated "id" into arg->act.p[1].
347 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
348 */
349static enum act_parse_ret parse_http_req_capture(const char **args, int *orig_arg, struct proxy *px,
350 struct act_rule *rule, char **err)
351{
352 struct sample_expr *expr;
353 struct cap_hdr *hdr;
354 int cur_arg;
355 int len = 0;
356
357 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
358 if (strcmp(args[cur_arg], "if") == 0 ||
359 strcmp(args[cur_arg], "unless") == 0)
360 break;
361
362 if (cur_arg < *orig_arg + 3) {
363 memprintf(err, "expects <expression> [ 'len' <length> | id <idx> ]");
364 return ACT_RET_PRS_ERR;
365 }
366
367 cur_arg = *orig_arg;
368 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
369 if (!expr)
370 return ACT_RET_PRS_ERR;
371
372 if (!(expr->fetch->val & SMP_VAL_FE_HRQ_HDR)) {
373 memprintf(err,
374 "fetch method '%s' extracts information from '%s', none of which is available here",
375 args[cur_arg-1], sample_src_names(expr->fetch->use));
376 free(expr);
377 return ACT_RET_PRS_ERR;
378 }
379
380 if (!args[cur_arg] || !*args[cur_arg]) {
381 memprintf(err, "expects 'len or 'id'");
382 free(expr);
383 return ACT_RET_PRS_ERR;
384 }
385
386 if (strcmp(args[cur_arg], "len") == 0) {
387 cur_arg++;
388
389 if (!(px->cap & PR_CAP_FE)) {
390 memprintf(err, "proxy '%s' has no frontend capability", px->id);
391 return ACT_RET_PRS_ERR;
392 }
393
394 px->conf.args.ctx = ARGC_CAP;
395
396 if (!args[cur_arg]) {
397 memprintf(err, "missing length value");
398 free(expr);
399 return ACT_RET_PRS_ERR;
400 }
401 /* we copy the table name for now, it will be resolved later */
402 len = atoi(args[cur_arg]);
403 if (len <= 0) {
404 memprintf(err, "length must be > 0");
405 free(expr);
406 return ACT_RET_PRS_ERR;
407 }
408 cur_arg++;
409
Willy Tarreau79e57332018-10-02 16:01:16 +0200410 hdr = calloc(1, sizeof(*hdr));
411 hdr->next = px->req_cap;
412 hdr->name = NULL; /* not a header capture */
413 hdr->namelen = 0;
414 hdr->len = len;
415 hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
416 hdr->index = px->nb_req_cap++;
417
418 px->req_cap = hdr;
419 px->to_log |= LW_REQHDR;
420
421 rule->action = ACT_CUSTOM;
422 rule->action_ptr = http_action_req_capture;
423 rule->arg.cap.expr = expr;
424 rule->arg.cap.hdr = hdr;
425 }
426
427 else if (strcmp(args[cur_arg], "id") == 0) {
428 int id;
429 char *error;
430
431 cur_arg++;
432
433 if (!args[cur_arg]) {
434 memprintf(err, "missing id value");
435 free(expr);
436 return ACT_RET_PRS_ERR;
437 }
438
439 id = strtol(args[cur_arg], &error, 10);
440 if (*error != '\0') {
441 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
442 free(expr);
443 return ACT_RET_PRS_ERR;
444 }
445 cur_arg++;
446
447 px->conf.args.ctx = ARGC_CAP;
448
449 rule->action = ACT_CUSTOM;
450 rule->action_ptr = http_action_req_capture_by_id;
451 rule->check_ptr = check_http_req_capture;
452 rule->arg.capid.expr = expr;
453 rule->arg.capid.idx = id;
454 }
455
456 else {
457 memprintf(err, "expects 'len' or 'id', found '%s'", args[cur_arg]);
458 free(expr);
459 return ACT_RET_PRS_ERR;
460 }
461
462 *orig_arg = cur_arg;
463 return ACT_RET_PRS_OK;
464}
465
466/* This function executes the "capture" action and store the result in a
467 * capture slot if exists. It executes a fetch expression, turns the result
468 * into a string and puts it in a capture slot. It always returns 1. If an
469 * error occurs the action is cancelled, but the rule processing continues.
470 */
471static enum act_return http_action_res_capture_by_id(struct act_rule *rule, struct proxy *px,
472 struct session *sess, struct stream *s, int flags)
473{
474 struct sample *key;
475 struct cap_hdr *h;
476 char **cap = s->res_cap;
477 struct proxy *fe = strm_fe(s);
478 int len;
479 int i;
480
481 /* Look for the original configuration. */
482 for (h = fe->rsp_cap, i = fe->nb_rsp_cap - 1;
483 h != NULL && i != rule->arg.capid.idx ;
484 i--, h = h->next);
485 if (!h)
486 return ACT_RET_CONT;
487
488 key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, rule->arg.capid.expr, SMP_T_STR);
489 if (!key)
490 return ACT_RET_CONT;
491
492 if (cap[h->index] == NULL)
493 cap[h->index] = pool_alloc(h->pool);
494
495 if (cap[h->index] == NULL) /* no more capture memory */
496 return ACT_RET_CONT;
497
498 len = key->data.u.str.data;
499 if (len > h->len)
500 len = h->len;
501
502 memcpy(cap[h->index], key->data.u.str.area, len);
503 cap[h->index][len] = 0;
504 return ACT_RET_CONT;
505}
506
507/* Check an "http-response capture" action.
508 *
509 * The function returns 1 in success case, otherwise, it returns 0 and err is
510 * filled.
511 */
512static int check_http_res_capture(struct act_rule *rule, struct proxy *px, char **err)
513{
514 if (rule->action_ptr != http_action_res_capture_by_id)
515 return 1;
516
517 if (rule->arg.capid.idx >= px->nb_rsp_cap) {
518 memprintf(err, "unable to find capture id '%d' referenced by http-response capture rule",
519 rule->arg.capid.idx);
520 return 0;
521 }
522
523 return 1;
524}
525
526/* parse an "http-response capture" action. It takes a single argument which is
527 * a sample fetch expression. It stores the expression into arg->act.p[0] and
528 * the allocated hdr_cap struct od the preallocated id into arg->act.p[1].
529 * It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
530 */
531static enum act_parse_ret parse_http_res_capture(const char **args, int *orig_arg, struct proxy *px,
532 struct act_rule *rule, char **err)
533{
534 struct sample_expr *expr;
535 int cur_arg;
536 int id;
537 char *error;
538
539 for (cur_arg = *orig_arg; cur_arg < *orig_arg + 3 && *args[cur_arg]; cur_arg++)
540 if (strcmp(args[cur_arg], "if") == 0 ||
541 strcmp(args[cur_arg], "unless") == 0)
542 break;
543
544 if (cur_arg < *orig_arg + 3) {
545 memprintf(err, "expects <expression> id <idx>");
546 return ACT_RET_PRS_ERR;
547 }
548
549 cur_arg = *orig_arg;
550 expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
551 if (!expr)
552 return ACT_RET_PRS_ERR;
553
554 if (!(expr->fetch->val & SMP_VAL_FE_HRS_HDR)) {
555 memprintf(err,
556 "fetch method '%s' extracts information from '%s', none of which is available here",
557 args[cur_arg-1], sample_src_names(expr->fetch->use));
558 free(expr);
559 return ACT_RET_PRS_ERR;
560 }
561
562 if (!args[cur_arg] || !*args[cur_arg]) {
563 memprintf(err, "expects 'id'");
564 free(expr);
565 return ACT_RET_PRS_ERR;
566 }
567
568 if (strcmp(args[cur_arg], "id") != 0) {
569 memprintf(err, "expects 'id', found '%s'", args[cur_arg]);
570 free(expr);
571 return ACT_RET_PRS_ERR;
572 }
573
574 cur_arg++;
575
576 if (!args[cur_arg]) {
577 memprintf(err, "missing id value");
578 free(expr);
579 return ACT_RET_PRS_ERR;
580 }
581
582 id = strtol(args[cur_arg], &error, 10);
583 if (*error != '\0') {
584 memprintf(err, "cannot parse id '%s'", args[cur_arg]);
585 free(expr);
586 return ACT_RET_PRS_ERR;
587 }
588 cur_arg++;
589
590 px->conf.args.ctx = ARGC_CAP;
591
592 rule->action = ACT_CUSTOM;
593 rule->action_ptr = http_action_res_capture_by_id;
594 rule->check_ptr = check_http_res_capture;
595 rule->arg.capid.expr = expr;
596 rule->arg.capid.idx = id;
597
598 *orig_arg = cur_arg;
599 return ACT_RET_PRS_OK;
600}
601
602/************************************************************************/
603/* All supported http-request action keywords must be declared here. */
604/************************************************************************/
605
606static struct action_kw_list http_req_actions = {
607 .kw = {
608 { "capture", parse_http_req_capture },
609 { "reject", parse_http_action_reject },
Olivier Houchard602bf7d2019-05-10 13:59:15 +0200610 { "disable-l7-retry", parse_http_req_disable_l7_retry },
Willy Tarreau79e57332018-10-02 16:01:16 +0200611 { "set-method", parse_set_req_line },
612 { "set-path", parse_set_req_line },
613 { "set-query", parse_set_req_line },
614 { "set-uri", parse_set_req_line },
615 { NULL, NULL }
616 }
617};
618
Willy Tarreau0108d902018-11-25 19:14:37 +0100619INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_actions);
620
Willy Tarreau79e57332018-10-02 16:01:16 +0200621static struct action_kw_list http_res_actions = {
622 .kw = {
623 { "capture", parse_http_res_capture },
624 { "set-status", parse_http_set_status },
625 { NULL, NULL }
626 }
627};
628
Willy Tarreau0108d902018-11-25 19:14:37 +0100629INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_actions);
Willy Tarreau79e57332018-10-02 16:01:16 +0200630
631/*
632 * Local variables:
633 * c-indent-level: 8
634 * c-basic-offset: 8
635 * End:
636 */