The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_isc_dhcp.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 156523385ff662b0a595433bba588c0f9ac2e3a8 $
19 * @file rlm_isc_dhcp.c
20 * @brief Read ISC DHCP configuration files
21 *
22 * @copyright 2019 The FreeRADIUS server project
23 * @copyright 2019 Alan DeKok (aland@freeradius.org)
24 */
25RCSID("$Id: 156523385ff662b0a595433bba588c0f9ac2e3a8 $")
26
27#include <freeradius-devel/server/base.h>
28#include <freeradius-devel/server/module_rlm.h>
29#include <freeradius-devel/dhcpv4/dhcpv4.h>
30#include <freeradius-devel/util/debug.h>
31#include <freeradius-devel/util/skip.h>
32
33
34static fr_dict_t const *dict_dhcpv4;
35
41
49
52 { .out = &attr_client_hardware_address, .name = "Client-Hardware-Address", .type = FR_TYPE_ETHERNET, .dict = &dict_dhcpv4},
53 { .out = &attr_your_ip_address, .name = "Your-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4},
54 { .out = &attr_client_identifier, .name = "Client-Identifier", .type = FR_TYPE_OCTETS, .dict = &dict_dhcpv4},
55 { .out = &attr_server_name, .name = "Server-Host-Name", .type = FR_TYPE_STRING, .dict = &dict_dhcpv4},
56 { .out = &attr_boot_filename, .name = "Boot-Filename", .type = FR_TYPE_STRING, .dict = &dict_dhcpv4},
57 { .out = &attr_server_ip_address, .name = "Server-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4},
58 { .out = &attr_server_identifier, .name = "Server-Identifier", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4},
59
61};
62
64
65#define NO_SEMICOLON (0)
66#define YES_SEMICOLON (1)
67#define MAYBE_SEMICOLON (2)
68
69/*
70 * Define a structure for our module configuration.
71 *
72 * These variables do not need to be in a structure, but it's
73 * a lot cleaner to do so, and a pointer to the structure can
74 * be used as the instance handle.
75 */
76typedef struct {
77 char const *filename;
78 bool debug;
81
82 /*
83 * While "host" blocks can appear anywhere, their
84 * definitions are global. We use these hashes for
85 * dedup, and for assigning IP addresses in the `recv`
86 * section. We still need to have host hashes in the
87 * subsections, so that we can apply options from the
88 * bottom up.
89 */
90 fr_hash_table_t *hosts_by_ether; //!< by MAC address
91 fr_hash_table_t *hosts_by_uid; //!< by client identifier
93
94/*
95 * A mapping of configuration file names to internal variables.
96 */
97static const conf_parser_t module_config[] = {
99 { FR_CONF_OFFSET("debug", rlm_isc_dhcp_t, debug) },
100 { FR_CONF_OFFSET("pedantic", rlm_isc_dhcp_t, pedantic) },
102};
103
104#define IDEBUG if (state->debug) DEBUG
105
106/*
107 * For developer debugging. Likely not needed
108 */
109#define DDEBUG(...)
110
111/*
112 * The parsing functions return:
113 * <0 on error
114 * 0 for "I did nothing"
115 * 1 for "I did something"
116 *
117 * This pattern allows us to distinguish things like empty files
118 * from full files, and empty subsections from full sections, etc.
119 */
120
121/** Holds the state of the current tokenizer
122 *
123 */
124typedef struct {
125 rlm_isc_dhcp_t *inst; //!< module instance
126 FILE *fp;
127 char const *filename;
129 char *line; //!< where the current line started
130
131 int braces; //!< how many levels deep we are in a { ... }
132 bool saw_semicolon; //!< whether we saw a semicolon
133 bool eof; //!< are we at EOF?
134 bool allow_eof; //!< do we allow EOF? (i.e. braces == 0)
135 bool debug; //!< internal developer debugging
136
137 char *buffer; //!< read buffer
138 size_t bufsize; //!< size of read buffer
139 char *ptr; //!< pointer into read buffer
140
141 char *token; //!< current token that we parsed
142 size_t token_len; //!< length of the token
143
144 char string[256]; //!< double quoted strings go here, so we don't mangle the input buffer
146
147
150
152 ISC_INVALID = 0, //!< we recognize it, but don't implement it
153 ISC_NOOP, //!< we don't do anything with it
154 ISC_IGNORE, //!< we deliberately ignore it
162
163/** Describes the commands that we accept, including it's syntax (i.e. name), etc.
164 *
165 */
173
174/** Holds information about the thing we parsed.
175 *
176 * Note that this parser is forgiving. We would rather accept
177 * things ISC DHCP doesn't accept, than reject things it accepts.
178 *
179 * Since we only implement a tiny portion of it's configuration,
180 * we tend to accept all kinds of things, and then just ignore them.
181 */
184 int argc;
186
189 void *data; //!< per-thing parsed data.
190
191 /*
192 * Only for things that have sections
193 */
194 fr_hash_table_t *hosts_by_ether; //!< by MAC address
195 fr_hash_table_t *hosts_by_uid; //!< by client identifier
196 fr_pair_list_t options; //!< DHCP options
199 rlm_isc_dhcp_info_t **last; //!< pointer to last child
200};
201
202static int read_file(rlm_isc_dhcp_t *inst, rlm_isc_dhcp_info_t *parent, char const *filename);
204
205static char const *spaces = " ";
206
207/** Refills the read buffer with one line from the file.
208 *
209 * This function also takes care of suppressing blank lines, and
210 * lines which only contain comments.
211 */
213{
214 char *p;
215
216 if (state->eof) return 0;
217
218 /*
219 * We've run out of data to parse, reset to the start of
220 * the buffer.
221 */
222 if (!*state->ptr) state->ptr = state->buffer;
223
224redo:
225 state->lineno++;
226 state->line = state->ptr;
227
228 if (!fgets(state->ptr, state->bufsize - (state->ptr - state->buffer), state->fp)) {
229 if (feof(state->fp)) {
230 state->eof = true;
231 return 0;
232 }
233
234 return -1;
235 }
236
237 /*
238 * Skip leading spaces
239 */
240 p = state->ptr;
242
243 /*
244 * The line is all spaces, OR we've hit a comment. Go
245 * get more data.
246 */
247 if (!*p || (*p == '#')) goto redo;
248
249 /*
250 * Point to the first non-space data.
251 */
252 state->ptr = p;
253
254 return 1;
255}
256
257static int skip_spaces(rlm_isc_dhcp_tokenizer_t *state, char *p)
258{
259 char *start = p;
260
261 state->ptr = p;
262
263 fr_skip_whitespace(state->ptr);
264
265 /*
266 * If we ran out of text on this line, re-fill the
267 * buffer. Note that refill() also takes care of
268 * suppressing blank lines and comments. refill() also
269 * takes care of skipping leading spaces, too.
270 */
271 if (!state->eof && !*state->ptr) {
272 int ret;
273
274 state->ptr = start;
275
276 ret = refill(state);
277 if (ret < 0) return -1;
278 }
279
280 /*
281 * Set the semicolon flag as a "peek
282 * ahead", so that the various other
283 * parsers don't need to check it.
284 */
285 if (*state->ptr == ';') state->saw_semicolon = true;
286
287 return 0;
288}
289
290
291
292/*
293 * ISC's double quoted strings allow all kinds of extra magic, so
294 * we re-implement string parsing yet again.
295 */
297{
298 char *p = state->ptr + 1;
299 char *q = state->string;
300
301 while (true) {
302 if (!*p) {
303 fr_strerror_const("unterminated string");
304 return -1;
305 }
306
307 if (*p == '"') {
308 p++;
309 if (isspace((uint8_t) *p)) {
310 if (skip_spaces(state, p) < 0) return -1;
311 break;
312 }
313 }
314
315 if ((size_t) (q - state->string) >= sizeof(state->string) - 1) {
316 fr_strerror_const("string is too long");
317 return -1;
318 }
319
320 if (*p != '\\') {
321 *(q++) = *(p++);
322 continue;
323 }
324
325 // @todo - all of ISC's string escapes, e.g. \x...
326 }
327
328 *q = '\0';
329
330 state->token = state->string;
331 state->token_len = (q - state->string);
332 return 1;
333}
334
335
336/** Reads one token into state->token
337 *
338 * Note that this function *destroys* the input buffer. So if
339 * you need to read two tokens, you have to save the first one
340 * somewhere *outside* of the input buffer.
341 */
342static int read_token(rlm_isc_dhcp_tokenizer_t *state, fr_token_t hint, int semicolon, bool allow_rcbrace)
343{
344 char *p;
345
346redo:
347 /*
348 * If the buffer is empty, re-fill it.
349 */
350 if (!*state->ptr) {
351 int ret;
352
353 ret = refill(state);
354 if (ret < 0) return ret;
355
356 if (ret == 0) {
357 if (!state->allow_eof) {
358 fr_strerror_const("Unexpected EOF");
359 return -1;
360 }
361
362 return 0;
363 }
364 }
365
366 /*
367 * The previous token may have ended on a space
368 * or semi-colon. We skip those characters
369 * before looking for the next token.
370 */
371 while (isspace((uint8_t) *state->ptr) || (*state->ptr == ';') || (*state->ptr == ',')) state->ptr++;
372
373 if (!*state->ptr) goto redo;
374
375 /*
376 * Start looking for the next token from where we left
377 * off last time.
378 */
379 state->token = state->ptr;
380 state->saw_semicolon = false;
381
382 /*
383 * Special-case quoted strings.
384 */
385 if (state->token[0] == '"') {
386 if (hint != T_DOUBLE_QUOTED_STRING) {
387 fr_strerror_printf("Unexpected '\"'");
388 return -1;
389 }
390
391 return read_string(state);
392 }
393
394 for (p = state->token; *p != '\0'; p++) {
395 /*
396 * "end of word" character. It might be allowed
397 * here, or it might not be.
398 */
399 if (*p == ';') {
400 if (semicolon == NO_SEMICOLON) {
401 fr_strerror_const("unexpected ';'");
402 return -1;
403 }
404
405 state->ptr = p;
406 state->saw_semicolon = true;
407 break;
408 }
409
410 /*
411 * For lists of things and code definitions.
412 */
413 if (*p == ',') {
414 state->ptr = p;
415 break;
416 }
417
418 /*
419 * Allow braces / equal as single character
420 * tokens if they're the first character we saw.
421 * Otherwise, the characters are "end of word"
422 * markers/
423 */
424 if ((*p == '{') || (*p == '}') || (*p == '=')) {
425 if (p == state->token) p++;
426
427 state->ptr = p;
428 break;
429 }
430
431 /*
432 * If we find a comment, we ignore everything
433 * until the end of the line.
434 */
435 if (*p == '#') {
436 *p = '\0';
437 state->ptr = p;
438
439 /*
440 * Nothing here, get more text
441 */
442 if (state->token == state->ptr) goto redo;
443 break;
444 }
445
446 /*
447 * Whitespace, we're done.
448 */
449 if (isspace((uint8_t) *p)) {
450 if (skip_spaces(state, p) < 0) return -1;
451 break;
452 }
453 }
454
455 /*
456 * Protect the rest of the code from buffer overflows.
457 */
458 state->token_len = p - state->token;
459
460 if (state->token_len == 0) {
461 fr_strerror_const("Failed to find token");
462 return -1;
463 }
464
465 if (state->token_len >= 256) {
466 fr_strerror_const("token too large");
467 return -1;
468 }
469
470 /*
471 * Double-check the token against what we were expected
472 * to read.
473 */
474 if (hint == T_LCBRACE) {
475 if (*state->token != '{') {
476 fr_strerror_const("missing '{'");
477 return -1;
478 }
479
480 if ((size_t) state->braces >= (sizeof(spaces) - 1)) {
481 fr_strerror_const("sections are nested too deep");
482 return -1;
483 }
484
485 state->braces++;
486 return 1;
487 }
488
489 if (hint == T_RCBRACE) {
490 if (*state->token != '}') {
491 fr_strerror_const("missing '}'");
492 return -1;
493 }
494
495 state->braces--;
496 return 1;
497 }
498
499 /*
500 * If we're inside of a section, we may also allow
501 * right-brace as the first keyword. In that case, it's
502 * the end of the enclosing section.
503 */
504 if (*state->token == '}') {
505 if (!allow_rcbrace) {
506 fr_strerror_const("unexpected '}'");
507 return -1;
508 }
509
510 state->braces--;
511 return 1;
512 }
513
514 /*
515 * Don't return left brace if we were looking for a
516 * something else.
517 */
518 if ((hint == T_BARE_WORD) || (hint == T_DOUBLE_QUOTED_STRING)) {
519 if (*state->token == '{') {
520 fr_strerror_const("unexpected '{'");
521 return -1;
522 }
523 }
524
525
526 return 1;
527}
528
529/** Recursively match subwords inside of a command string.
530 *
531 */
532static int match_subword(rlm_isc_dhcp_tokenizer_t *state, char const *cmd, rlm_isc_dhcp_info_t *info)
533{
534 int ret;
536 int semicolon = NO_SEMICOLON;
537 bool multi = false;
538 char *p;
539 char const *q;
540 char const *next;
541 char type_name[64];
542
544
545 if (!*cmd) return -1; /* internal error */
546
547 /*
548 * Remember the next command.
549 */
550 next = q = cmd;
551 while (*next && !isspace((uint8_t) *next) && (*next != ',')) next++;
552 if (!*next) semicolon = YES_SEMICOLON;
553
554 /*
555 * Matching an in-line word.
556 */
557 if (islower((uint8_t) *q)) {
558 ret = read_token(state, T_BARE_WORD, semicolon, false);
559 if (ret <= 0) return -1;
560
561 /*
562 * Look for a verbatim word.
563 */
564 for (p = state->token; p < (state->token + state->token_len); p++, q++) {
565 if (*p != *q) {
566 fail:
567 fr_strerror_printf("Expected '%.*s', got unknown text '%.*s'",
568 (int)state->token_len, state->token,
569 (int) (next - cmd), cmd);
570 return -1;
571 }
572 }
573
574 /*
575 * Matched all of 'q', we're done.
576 */
577 if (!*q) {
578 DDEBUG("... WORD %.*s ", state->token_len, state->token);
579 return 1;
580 }
581
582 /*
583 * Matched all of this word in 'q', but there are
584 * more words after this one.. Recurse.
585 */
586 if (isspace((uint8_t) *q)) {
587 return match_subword(state, next, info);
588 }
589
590 /*
591 * Matched all of 'p', but there's more 'q'. Fail.
592 *
593 * e.g. got "foo", but expected "food".
594 */
595 goto fail;
596 }
597
598 /*
599 * SECTION must be the last thing in the command
600 */
601 if (strcmp(q, "SECTION") == 0) {
602 if (q[7] != '\0') return -1; /* internal error */
603
604 ret = read_token(state, T_LCBRACE, NO_SEMICOLON, false);
605 if (ret <= 0) return ret;
606
607 ret = parse_section(state, info);
608 if (ret < 0) return ret;
609
610 /*
611 * Empty sections are allowed.
612 */
613 return 2; /* SECTION */
614 }
615
616 /*
617 * Uppercase words are INTEGER or STRING or IPADDR, which
618 * are FreeRADIUS data types.
619 *
620 * We copy the name here because some options allow for
621 * multiple fields.
622 */
623 p = type_name;
624 while (*q && !isspace((uint8_t) *q) && (*q != ',')) {
625 if ((p - type_name) >= (int) sizeof(type_name)) return -1; /* internal error */
626 *(p++) = tolower((uint8_t) *(q++));
627 }
628 *p = '\0';
629
630 /*
631 * "fixed-address IPADDR," means it can take multiple IP
632 * addresses.
633 *
634 * @todo - pre-parse the field and save the strings
635 * somewhere, so that we can create info->argv of the
636 * right size. Or, just create an array of 2 by default,
637 * and then double it every time we run out... a little
638 * more work, but it doesn't involve further mangling the
639 * parser.
640 *
641 * We could likely just manually parse state->ptr, look
642 * until ';' or '\0', and count the words. That would
643 * work 99% of the time.
644 *
645 * @todo - We should also note that the ISC default is to
646 * allow hostnames, in which case it will add all IPs
647 * associated with that hostname, while we will add only
648 * one. That could likely be fixed, too.
649 */
650 if (*q == ',') {
651 if (q[1]) return -1; /* internal error */
652 multi = true;
653 semicolon = MAYBE_SEMICOLON;
654 }
655
656 type = fr_type_from_str(type_name);
657 if (type == FR_TYPE_NULL) {
658 fr_strerror_printf("unknown data type '%.*s'",
659 (int) (next - cmd), cmd);
660 return -1; /* internal error */
661 }
662
663redo_multi:
664 /*
665 * We were asked to parse a data type, so instead allow
666 * just about anything.
667 *
668 * @todo - if we get fancy, dynamically expand this, too.
669 * ISC doesn't support it, but we can.
670 */
671 ret = read_token(state, T_DOUBLE_QUOTED_STRING, semicolon, false);
672 if (ret <= 0) return ret;
673
674 DDEBUG("... DATA %.*s ", state->token_len, state->token);
675
676 /*
677 * BOOLs in ISC are "true", "false", or "ignore".
678 *
679 * Isn't that smart? "ignore" means "ignore this option
680 * as if it was commented out". So we do that.
681 *
682 * I sure wish I was smart enough to allow 3 values for a
683 * boolean data type.
684 */
685 if ((type == FR_TYPE_BOOL) && (state->token_len == 6) &&
686 (strcmp(state->token, "ignore") == 0)) {
687 talloc_free(info);
688 return 2;
689 }
690
691 /*
692 * Parse the data to its final form.
693 */
694 info->argv[info->argc] = talloc_zero(info, fr_value_box_t);
695
696 ret = fr_value_box_from_str(info, info->argv[info->argc], type, NULL,
697 state->token, state->token_len, NULL);
698 if (ret < 0) return ret;
699
700 info->argc++;
701
702 if (multi) {
703 if (state->saw_semicolon) return 1;
704
705 if (info->argc >= info->cmd->max_argc) {
706 fr_strerror_printf("Too many arguments (%d > %d) for command '%s'",
707 info->argc, info->cmd->max_argc, info->cmd->name);
708 return -1;
709 }
710
711 goto redo_multi;
712 }
713
714 /*
715 * No more command to parse, return OK.
716 */
717 if (!*next) return 1;
718
719 /*
720 * Keep matching more things
721 */
722 return match_subword(state, next, info);
723}
724
725/*
726 * include FILENAME ;
727 */
729{
730 int ret;
731 char pathname[8192];
732 char const *sep;
733 char const *name = info->argv[0]->vb_strvalue;
734
735 IDEBUG("%.*s include %s ;", state->braces, spaces, name);
736
737 sep = strrchr(state->filename, '/');
738 if (sep) {
739 char *p;
740
741 strlcpy(pathname, state->filename, sizeof(pathname));
742 p = pathname + (sep - state->filename) + 1;
743 strlcpy(p, name, sizeof(pathname) - (p - pathname));
744
745 name = pathname;
746 }
747
748 /*
749 * Note that we read the included file into the PARENT's
750 * list. i.e. as if the file was included in-place.
751 */
752 ret = read_file(state->inst, info->parent, name);
753 if (ret < 0) return ret;
754
755 /*
756 * Even if the file was empty, we return "1" to indicate
757 * that we successfully parsed the file. Returning "0"
758 * would indicate that the parent file was at EOF.
759 */
760 return 1;
761}
762
763
764typedef struct {
765 uint8_t ether[6];
768
769static uint32_t host_ether_hash(void const *data)
770{
771 isc_host_ether_t const *self = data;
772
773 return fr_hash(self->ether, sizeof(self->ether));
774}
775
776static int8_t host_ether_cmp(void const *one, void const *two)
777{
778 isc_host_ether_t const *a = one;
779 isc_host_ether_t const *b = two;
780 int ret;
781
782 ret = memcmp(a->ether, b->ether, 6);
783 return CMP(ret, 0);
784}
785
790
791static uint32_t host_uid_hash(void const *data)
792{
793 isc_host_uid_t const *self = data;
794
795 return fr_hash(self->client->vb_octets, self->client->vb_length);
796}
797
798static int8_t host_uid_cmp(void const *one, void const *two)
799{
800 isc_host_uid_t const *a = one;
801 isc_host_uid_t const *b = two;
802
803 MEMCMP_RETURN(a, b, client->vb_octets, client->vb_length);
804 return 0;
805}
806
807
808/** option space name [ [ code width number ] [ length width number ] [ hash size number ] ] ;
809 *
810 */
812 UNUSED char *name)
813{
814 // @todo - register the named option space with inst->option_space
815 // and create inst->option_space
816 fr_strerror_const("please implement 'option space name [ [ code width number ] [ length width number ] [ hash size number ] ]'");
817 return -1;
818}
819
820
821/** Parse one type string.
822 *
823
824 * boolean
825 * [signed|unsigned] integer [width]
826 * width is 8, 16, or 32
827 * ip-address
828 * ip6-address
829 * text
830 * string
831 * domain-list [compressed]
832 * encapsulate _identifier_
833 */
834
835#define TYPE_CHECK(name, type) if ((state->token_len == (sizeof(name) - 1)) && (memcmp(state->token, name, sizeof(name) - 1) == 0)) return type
837{
838 TYPE_CHECK("boolean", FR_TYPE_BOOL);
839 TYPE_CHECK("integer", FR_TYPE_UINT32);
840 TYPE_CHECK("ip-address", FR_TYPE_IPV4_ADDR);
841 TYPE_CHECK("ip6-address", FR_TYPE_IPV6_ADDR);
842 TYPE_CHECK("text", FR_TYPE_STRING);
843 TYPE_CHECK("string", FR_TYPE_OCTETS);
844
845 fr_strerror_printf("unknown type '%.*s'", (int)state->token_len, state->token);
846 return FR_TYPE_NULL;
847}
848
849
850/** option new-name code new-code = definition ;
851 *
852 * "new-name" can also be SPACE.NAME
853 *
854 */
856 char *name)
857{
858 int ret;
859 char *p;
861 fr_dict_attr_t const *da, *root;
862 fr_value_box_t box;
864
865 p = strchr(name, '.');
866 if (p) {
867 fr_strerror_const("cannot (yet) define options in spaces");
868 error:
870 return -1;
871 }
872
873 if (parent != state->inst->head) {
874 fr_strerror_const("option definitions cannot be scoped");
875 goto error;
876 }
877
878 /*
879 * Grab the integer code value.
880 */
881 ret = read_token(state, T_BARE_WORD, NO_SEMICOLON, false);
882 if (ret <= 0) {
883 error_ret:
885 return ret;
886 }
887
889 ret = fr_value_box_from_str(NULL, &box, type, NULL,
890 state->token, state->token_len, NULL);
891 if (ret < 0) goto error;
892
893 /*
894 * Look for '='
895 */
896 ret = read_token(state, T_BARE_WORD, NO_SEMICOLON, false);
897 if (ret <= 0) goto error_ret;
898
899 if ((state->token_len != 1) || (state->token[0] != '=')) {
900 fr_strerror_printf("expected '=' after code definition got '%.*s'", (int)state->token_len, state->token);
901 goto error;
902 }
903
904 memset(&flags, 0, sizeof(flags));
905
906 /*
907 * Data type is:
908 *
909 * TYPE
910 * array of TYPE
911 * { TYPE, ... }
912 *
913 * Note that it also supports
914 *
915 * array of { TYPE, ... }
916 */
917 ret = read_token(state, T_BARE_WORD, MAYBE_SEMICOLON, false);
918 if (ret <= 0) goto error_ret;
919
920
921 if ((state->token_len == 5) && (memcmp(state->token, "array", 5) == 0)) {
922 flags.array = 1;
923
924 ret = read_token(state, T_BARE_WORD, NO_SEMICOLON, false);
925 if (ret <= 0) goto error_ret;
926
927 if (! ((state->token_len == 2) && (memcmp(state->token, "of", 2) == 0))) {
928 fr_strerror_printf("expected 'array of', not 'array %.*s'",
929 (int)state->token_len, state->token);
930 goto error;
931 }
932
933 /*
934 * Grab the next token. For now, it MUST have a semicolon
935 */
936 ret = read_token(state, T_BARE_WORD, YES_SEMICOLON, false);
937 if (ret <= 0) goto error_ret;
938 }
939
940 if ((state->token_len == 1) && (state->token[0] == '{')) {
941 fr_strerror_const("records are not supported in option definition");
942 goto error;
943 }
944
945 /*
946 * This check is needed only because we have
947 * MAYBE_SEMICOLON above. That's in order to allow
948 * "array of.." statements to product an *array* error,
949 * not a *semicolon* error.
950 */
951 if (!state->saw_semicolon) {
952 fr_strerror_const("expected ';'");
953 goto error;
954 }
955
956 type = isc2fr_type(state);
957 if (fr_type_is_null(type)) goto error;
958
959 /*
960 * Now that we've parsed everything, look up the name.
961 * We forbid conflicts, but silently allow duplicates.
962 */
964 if (da &&
965 ((da->attr != box.vb_uint32) || (da->type != type))) {
966 fr_strerror_printf("cannot add different code / type for a pre-existing name '%s'", name);
967 goto error;
968 }
969
970 /*
971 * And look it up by code, too.
972 *
973 * We allow multiple attributes of the same code / type,
974 * but with different names.
975 */
977 da = fr_dict_attr_child_by_num(root, box.vb_uint32);
978 if (da && (da->type != type)) {
979 fr_strerror_printf("cannot add different type for a pre-existing code %d", box.vb_uint32);
980 goto error;
981 }
982
983 /*
984 * Add it in. Note that this function adds it by name
985 * and by code. So we don't *necessarily* have to do the
986 * name/code checks above. But doing so allows us to
987 * have better error messages.
988 */
989 ret = fr_dict_attr_add(fr_dict_unconst(dict_dhcpv4), root, name, box.vb_uint32, type, &flags);
991 if (ret < 0) return ret;
992
993 /*
994 * Caller doesn't need to do anything else with the thing
995 * we just parsed.
996 */
997 return 2;
998}
999
1001 fr_dict_attr_t const *da, char *value)
1002{
1003 int ret;
1004 fr_pair_t *vp;
1005
1006 /*
1007 * The attribute isn't an array, so it MUST have a
1008 * semicolon after it.
1009 */
1010 if (!da->flags.array && !state->saw_semicolon) {
1011 fr_strerror_printf("expected ';' %s", state->ptr);
1012 return -1;
1013 }
1014
1015 MEM(vp = fr_pair_afrom_da(parent, da));
1016
1017 /*
1018 * Add in the first value.
1019 */
1020 ret = fr_pair_value_from_str(vp, value, talloc_strlen(value), NULL, false);
1021 if (ret < 0) {
1023 return ret;
1024 }
1025
1026 fr_pair_append(&parent->options, vp);
1027
1028 // @todo - print out ISC names...
1029 IDEBUG("%.*s option %s %s ", state->braces, spaces, da->name, value);
1031
1032 /*
1033 * We've remembered the option in the parent option list.
1034 * There's no need to add it to the child list here.
1035 */
1036 if (!da->flags.array) return 2;
1037
1038 /*
1039 * For "array" types, loop through the remaining tokens.
1040 */
1041 while (!state->saw_semicolon) {
1043 if (ret <= 0) return ret;
1044
1045 MEM(vp = fr_pair_afrom_da(parent, da));
1046
1047 ret = fr_pair_value_from_str(vp, state->token, state->token_len, NULL, false);
1048 if (ret < 0) return ret;
1049
1050 fr_pair_append(&parent->options, vp);
1051
1052 // @todo - print out ISC names...
1053 IDEBUG("%.*s option %s %.*ss ", state->braces, spaces, da->name, (int)state->token_len, state->token);
1054 }
1055
1056 /*
1057 * We've remembered the option in the parent option list.
1058 * There's no need to add it to the child list here.
1059 */
1060 return 2;
1061}
1062
1063/** Parse "option" command
1064 *
1065 * In any sane system, commands which do different things should
1066 * have different names. In this syntax, it's all miracles and
1067 * unicorns.
1068 *
1069 * option NAME VALUE ;
1070 * option new-name code new-code = definition ;
1071 *
1072 * option space name [ [ code width number ] [ length width number ] [ hash size number ] ] ;
1073 */
1075{
1076 int ret, argc = 0;
1077 char *argv[2];
1079
1080 /*
1081 * Since read_token() mashes the input buffer, we have to save the tokens somewhere.
1082 */
1083 while (!state->saw_semicolon) {
1084 ret = read_token(state, T_BARE_WORD, MAYBE_SEMICOLON, false);
1085 if (ret < 0) return ret;
1086
1087 argv[argc++] = talloc_strndup(parent, state->token, state->token_len);
1088
1089 if (argc == 2) break;
1090 }
1091
1092 /*
1093 * Must have at least two arguments.
1094 */
1095 if (argc < 2) {
1096 fr_strerror_const("unexpected ';'");
1097 return -1;
1098 }
1099
1100 /*
1101 * Define an option space.
1102 */
1103 if (strcmp(argv[0], "space") == 0) {
1104 talloc_free(argv[0]);
1105 return parse_option_space(parent, state, argv[1]);
1106 }
1107
1108 /*
1109 * Look up the name. If the option is defined, then
1110 * parse the following options according to the data
1111 * type. Which MAY be a "struct" data type, or an
1112 * "array" data type.
1113 */
1114 if (state->saw_semicolon || (state->ptr[0] == ',')) {
1115 fr_dict_attr_t const *da;
1116
1117 da = fr_dict_attr_by_name(NULL, fr_dict_root(dict_dhcpv4), argv[0]);
1118 if (da) {
1119 talloc_free(argv[0]);
1120 return parse_option(parent, state, da, argv[1]);
1121 }
1122
1123 /*
1124 * @todo - nuke this extra step once we have dictionary.isc defined.
1125 */
1126 memcpy(name, "DHCP-", 5);
1127 strlcpy(name + 5, argv[0], sizeof(name) - 5);
1128
1130 if (da) {
1131 talloc_free(argv[0]);
1132 return parse_option(parent, state, da, argv[1]);
1133 }
1134 }
1135
1136 /*
1137 * The NAME isn't a known option.
1138 *
1139 * It must be "option NAME code NUMBER = DEFINITION"
1140 */
1141 if (strcmp(argv[1], "code") != 0) {
1142 fr_strerror_printf("unknown option '%s'", argv[0]);
1143 talloc_free(argv[0]);
1144 talloc_free(argv[1]);
1145 return -1;
1146 }
1147
1148 talloc_free(argv[1]);
1149 return parse_option_definition(parent, state, argv[0]);
1150}
1151
1152
1154{
1155 int start, end, half;
1156 int semicolon;
1157 int ret;
1158 char const *q = NULL;
1159 rlm_isc_dhcp_info_t *info;
1160
1161 start = 0;
1162 end = num_tokens - 1;
1163 half = -1;
1164
1165 /*
1166 * There are no super-short commands.
1167 */
1168 if (state->token_len < 4) goto unknown;
1169
1170 /*
1171 * Walk over the input token, doing a binary search on
1172 * the token list.
1173 */
1174 while (start <= end) {
1175 half = (start + end) / 2;
1176
1177 /*
1178 * Skips a function call, and is better for 99%
1179 * of the situations. Since there are no 1 or 2
1180 * character keywords, this always works.
1181 */
1182 ret = state->token[0] - tokens[half].name[0];
1183 if (ret != 0) goto recurse;
1184
1185 ret = state->token[1] - tokens[half].name[1];
1186 if (ret != 0) goto recurse;
1187
1188 ret = state->token[2] - tokens[half].name[2];
1189 if (ret != 0) goto recurse;
1190
1191 /*
1192 * Compare all of the strings.
1193 */
1194 ret = strncmp(state->token, tokens[half].name, state->token_len);
1195
1196 /*
1197 * Exact match. But maybe we have "foo" input,
1198 * and "food" command?
1199 */
1200 if (ret == 0) {
1201 char c = tokens[half].name[state->token_len];
1202
1203 /*
1204 * The token exactly matches the command.
1205 */
1206 if (!c || isspace((uint8_t) c)) {
1207 q = &(tokens[half].name[state->token_len]);
1208 break;
1209 }
1210
1211 /*
1212 * The token is "foo", but the command is
1213 * "food". Go search the lower half of
1214 * the command table.
1215 */
1216 ret = -1;
1217 }
1218
1219 recurse:
1220 /*
1221 * Token is smaller than the command we checked,
1222 * go check the lower half of the table.
1223 */
1224 if (ret < 0) {
1225 end = half - 1;
1226 } else {
1227 start = half + 1;
1228 }
1229 }
1230
1231 /*
1232 * Nothing matched, it's a failure.
1233 */
1234 if (!q) {
1235 unknown:
1236 fr_strerror_printf("unknown command '%.*s'", (int)state->token_len, state->token);
1237 return -1;
1238 }
1239
1240 fr_assert(half >= 0);
1241
1242 /*
1243 * "option" has multiple parse possibilities, so we treat
1244 * it specially.
1245 */
1246 if (tokens[half].type == ISC_OPTION) {
1247 return parse_options(parent, state);
1248 }
1249
1250 /*
1251 * Print out more warnings / errors in pedantic mode.
1252 */
1253 if (state->inst->pedantic && !tokens[half].parse) {
1254 if (tokens[half].type == ISC_INVALID) {
1255 ERROR("Command '%.*s' is not supported.",
1256 (int)state->token_len, state->token);
1257 return -1;
1258 }
1259
1260 /*
1261 * Print out WARNING messages only in debug mode.
1262 * We don't need to spam the main log file every
1263 * time the server starts.
1264 */
1265 if (DEBUG_ENABLED) {
1266 if (tokens[half].type == ISC_NOOP) {
1267 WARN("Command '%.*s' is not yet implemented.",
1268 (int)state->token_len, state->token);
1269 }
1270
1271 if (tokens[half].type == ISC_IGNORE) {
1272 WARN("Ignoring command '%.*s'. It is not relevant.",
1273 (int)state->token_len, state->token);
1274 }
1275 }
1276 }
1277
1278 semicolon = YES_SEMICOLON; /* default to always requiring this */
1279
1280 DDEBUG("... TOKEN %.*s ", (int)state->token_len, state->token);
1281
1282 info = talloc_zero(parent, rlm_isc_dhcp_info_t);
1283 fr_pair_list_init(&info->options);
1284 if (tokens[half].max_argc) {
1285 info->argv = talloc_zero_array(info, fr_value_box_t *, tokens[half].max_argc);
1286 }
1287
1288 /*
1289 * Remember which command we parsed.
1290 */
1291 info->parent = parent;
1292 info->cmd = &tokens[half];
1293 info->last = &(info->child);
1294
1295 /*
1296 * There's more to this command,
1297 * go parse that, too.
1298 */
1299 if (isspace((uint8_t) *q)) {
1300 if (state->saw_semicolon) goto unexpected;
1301
1302 ret = match_subword(state, q, info);
1303 if (ret <= 0) return ret;
1304
1305 /*
1306 * SUBSECTION must be at the end
1307 */
1308 if (ret == 2) semicolon = NO_SEMICOLON;
1309 }
1310
1311 /*
1312 * *q must be empty at this point.
1313 */
1314 if ((semicolon == NO_SEMICOLON) && state->saw_semicolon) {
1315 unexpected:
1316 fr_strerror_const("unexpected ';'");
1317 talloc_free(info);
1318 return -1;
1319 }
1320
1321 if ((semicolon == YES_SEMICOLON) && !state->saw_semicolon) {
1322 fr_strerror_const("missing ';'");
1323 talloc_free(info);
1324 return -1;
1325 }
1326
1327 // @todo - print out the thing we parsed
1328
1329 /*
1330 * Call the "parse" function which should do
1331 * validation, etc.
1332 */
1333 if (tokens[half].parse) {
1334 ret = tokens[half].parse(state, info);
1335 if (ret <= 0) {
1336 talloc_free(info);
1337 return ret;
1338 }
1339
1340 /*
1341 * The parse function took care of
1342 * remembering the "info" structure. So
1343 * we don't add it to the parent list.
1344 *
1345 * This process ensures that for some
1346 * things (e.g. hosts and subnets), we
1347 * have have O(1) lookups instead of
1348 * O(N).
1349 *
1350 * It also means that the *rest* of the
1351 * commands we parse are in a relatively
1352 * tiny list, which makes the O(N)
1353 * processing of it fairly minor.
1354 */
1355 if (ret == 2) return 1;
1356 }
1357
1358 /*
1359 * Add the parsed structure to the tail of the
1360 * current list. Note that this portion adds
1361 * only ONE command at a time.
1362 */
1363 *(parent->last) = info;
1364 parent->last = &(info->next);
1365
1366 /*
1367 * It's a match, and it's OK.
1368 */
1369 return 1;
1370}
1371
1372/** host NAME { ... }
1373 *
1374 * Hosts are global, and are keyed by MAC `hardware ethernet`, and by
1375 * `client-identifier`.
1376 */
1378{
1379 isc_host_ether_t *my_ether, *old_ether;
1380 isc_host_uid_t *my_uid, *old_uid;
1381 rlm_isc_dhcp_info_t *ether, *child, *parent;
1382 fr_pair_t *vp;
1383
1384 ether = NULL;
1385 my_uid = NULL;
1386
1387 /*
1388 * A host MUST have at least one "hardware ethernet" in
1389 * it.
1390 */
1391 for (child = info->child; child != NULL; child = child->next) {
1392 if (child->cmd->type == ISC_HARDWARE_ETHERNET) {
1393 if (ether) {
1394 fr_strerror_const("cannot have two 'hardware ethernet' entries in a 'host'");
1395 return -1;
1396 }
1397
1398 ether = child;
1399 }
1400 }
1401
1402 if (!ether) {
1403 fr_strerror_printf("host %s does not contain a 'hardware ethernet' entry",
1404 info->argv[0]->vb_strvalue);
1405 return -1;
1406 }
1407
1408 /*
1409 * Point directly to the ethernet address.
1410 */
1411 my_ether = talloc_zero(info, isc_host_ether_t);
1412 memcpy(my_ether->ether, &(ether->argv[0]->vb_ether), sizeof(my_ether->ether));
1413 my_ether->host = info;
1414
1415 /*
1416 * We can't have duplicate ethernet addresses for hosts.
1417 */
1418 old_ether = fr_hash_table_find(state->inst->hosts_by_ether, my_ether);
1419 if (old_ether) {
1420 fr_strerror_printf("'host %s' and 'host %s' contain duplicate 'hardware ethernet' fields",
1421 info->argv[0]->vb_strvalue, old_ether->host->argv[0]->vb_strvalue);
1422 talloc_free(my_ether);
1423 return -1;
1424 }
1425
1426 /*
1427 * Insert into the ether hashes.
1428 */
1429 if (!fr_hash_table_insert(state->inst->hosts_by_ether, my_ether)) {
1430 fr_strerror_printf("Failed inserting 'host %s' into hash table",
1431 info->argv[0]->vb_strvalue);
1432 talloc_free(my_ether);
1433 return -1;
1434 }
1435
1436 /*
1437 * The 'host' entry might not have a client identifier option.
1438 */
1440 if (vp) {
1441 my_uid = talloc_zero(info, isc_host_uid_t);
1442 my_uid->client = &vp->data;
1443 my_uid->host = info;
1444
1445 old_uid = fr_hash_table_find(state->inst->hosts_by_uid, my_uid);
1446 if (old_uid) {
1447 fr_strerror_printf("'host %s' and 'host %s' contain duplicate 'option client-identifier' fields",
1448 info->argv[0]->vb_strvalue, old_uid->host->argv[0]->vb_strvalue);
1449 fail:
1450 (void) fr_hash_table_delete(state->inst->hosts_by_ether, my_ether);
1451 talloc_free(my_uid);
1452 return -1;
1453 }
1454
1455 if (!fr_hash_table_insert(state->inst->hosts_by_uid, my_uid)) {
1456 fr_strerror_printf("Failed inserting 'host %s' into hash table",
1457 info->argv[0]->vb_strvalue);
1458 goto fail;
1459 }
1460 }
1461
1462 /*
1463 * The host doesn't have a parent, that's fine..
1464 *
1465 * It typically should tho...
1466 */
1467 if (!info->parent) return 2;
1468
1469 parent = info->parent;
1470
1471 /*
1472 * Add the host to the *parents* hash table. That way
1473 * when we apply the parent, we can look up the host in
1474 * its hash table. And avoid the O(N) issue of having
1475 * thousands of "host" entries in the parent->child list.
1476 */
1477 if (!parent->hosts_by_ether) {
1479 if (!parent->hosts_by_ether) {
1480 return -1;
1481 }
1482 }
1483
1484 if (!fr_hash_table_insert(parent->hosts_by_ether, my_ether)) {
1485 fr_strerror_printf("Failed inserting 'host %s' into hash table",
1486 info->argv[0]->vb_strvalue);
1487 return -1;
1488 }
1489
1490 /*
1491 * If we have a UID, insert into the UID hashes.
1492 */
1493 if (my_uid) {
1494 if (!parent->hosts_by_uid) {
1496 if (!parent->hosts_by_uid) {
1497 return -1;
1498 }
1499 }
1500
1501 if (!fr_hash_table_insert(parent->hosts_by_uid, my_uid)) {
1502 (void) fr_hash_table_remove(parent->hosts_by_ether, my_ether); /* remove and don't free */
1503 fr_strerror_printf("Failed inserting 'host %s' into hash table",
1504 info->argv[0]->vb_strvalue);
1505 return -1;
1506 }
1507 }
1508
1509 IDEBUG("%.*s host %s { ... }", state->braces, spaces, info->argv[0]->vb_strvalue);
1510
1511 /*
1512 * We've remembered the host in the parent hosts hash.
1513 * There's no need to add it to the child list here.
1514 */
1515 return 2;
1516}
1517
1518/** subnet IPADDR netmask MASK { ... }
1519 *
1520 */
1522{
1524 int ret, bits;
1525 uint32_t netmask = info->argv[1]->vb_ipv4addr;
1526
1527 /*
1528 * Check if argv[1] is a valid netmask
1529 */
1530 if (netmask & (~netmask >> 1)) {
1531 fr_strerror_printf("invalid netmask '%pV'", info->argv[1]);
1532 return -1;
1533 }
1534
1535 /*
1536 * 192.168.2.1/16 is wrong.
1537 */
1538 if ((info->argv[0]->vb_ipv4addr & netmask) != info->argv[0]->vb_ipv4addr) {
1539 fr_strerror_printf("subnet '%pV' does not match netmask '%pV'", info->argv[0], info->argv[1]);
1540 return -1;
1541 }
1542
1543 /*
1544 * Get number of bits set in netmask.
1545 */
1546 netmask = netmask - ((netmask >> 1) & 0x55555555);
1547 netmask = (netmask & 0x33333333) + ((netmask >> 2) & 0x33333333);
1548 netmask = (netmask + (netmask >> 4)) & 0x0F0F0F0F;
1549 netmask = netmask + (netmask >> 8);
1550 netmask = netmask + (netmask >> 16);
1551 bits = netmask & 0x0000003F;
1552
1553 parent = info->parent;
1554 if (parent->subnets) {
1556
1557 /*
1558 * Duplicate or overlapping "subnet" entries aren't allowed.
1559 */
1560 old = fr_trie_lookup_by_key(parent->subnets, &(info->argv[0]->vb_ipv4addr), bits);
1561 if (old) {
1562 fr_strerror_printf("subnet %pV netmask %pV' overlaps with existing subnet", info->argv[0], info->argv[1]);
1563 return -1;
1564
1565 }
1566 } else {
1567 parent->subnets = fr_trie_alloc(parent, NULL, NULL);
1568 if (!parent->subnets) return -1;
1569 }
1570
1571 /*
1572 * Add the subnet to the *parents* trie. That way when
1573 * we apply the parent, we can look up the subnet in its
1574 * trie. And avoid the O(N) issue of having thousands of
1575 * "subnet" entries in the parent->child list.
1576 */
1577
1578 ret = fr_trie_insert_by_key(parent->subnets, &(info->argv[0]->vb_ipv4addr), bits, info);
1579 if (ret < 0) {
1580 fr_strerror_printf("Failed inserting 'subnet %pV netmask %pV' into trie",
1581 info->argv[0], info->argv[1]);
1582 return -1;
1583 }
1584
1585 /*
1586 * @todo - if there's no 'option subnet-mask', add one
1587 * from the netmask given here. If there is an 'option
1588 * subnet-mask', then assume that the admin knows what
1589 * he's doing, and don't add one.
1590 */
1591
1592 IDEBUG("%.*s subnet %pV netmask %pV { ... }", state->braces, spaces, info->argv[0], info->argv[1]);
1593
1594 /*
1595 * We've remembered the subnet in the parent trie.
1596 * There's no need to add it to the child list here.
1597 */
1598 return 2;
1599}
1600
1601static rlm_isc_dhcp_info_t *get_host(request_t *request, fr_hash_table_t *hosts_by_ether, fr_hash_table_t *hosts_by_uid)
1602{
1603 fr_pair_t *vp;
1604 isc_host_ether_t *ether, my_ether;
1605 rlm_isc_dhcp_info_t *host = NULL;
1606
1607 /*
1608 * Look up the host first by client identifier.
1609 * If that doesn't match, use client hardware
1610 * address.
1611 */
1612 if (hosts_by_uid) {
1613 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_client_identifier);
1614 if (vp) {
1615 isc_host_uid_t *client, my_client;
1616
1617 my_client.client = &(vp->data);
1618
1619 client = fr_hash_table_find(hosts_by_uid, &my_client);
1620 if (client) {
1621 host = client->host;
1622 goto done;
1623 }
1624 }
1625 }
1626
1627 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_client_hardware_address);
1628 if (!vp) return NULL;
1629
1630 memcpy(&my_ether.ether, vp->vp_ether, sizeof(my_ether.ether));
1631
1632 ether = fr_hash_table_find(hosts_by_ether, &my_ether);
1633 if (!ether) return NULL;
1634
1635 host = ether->host;
1636
1637done:
1638 /*
1639 * @todo - check "fixed-address". This host entry should
1640 * match ONLY if one of the addresses matches the network
1641 * on which the client is booting. OR if there's no
1642 * 'fixed-address' field. OR if there's no 'yiaddr' in
1643 * the request.
1644 */
1645
1646 return host;
1647}
1648
1649
1651{
1652 fr_pair_t *vp;
1653
1654 if (!info->parent) return -1; /* internal error */
1655
1656 MEM(vp = fr_pair_afrom_da(info->parent, da));
1657
1658 /* TLS error buffer is checked */
1659 if (unlikely(fr_value_box_copy(vp, &(vp->data), info->argv[0]) < 0)) {
1660 talloc_free(vp);
1661 return -1;
1662 }
1663
1664 fr_pair_append(&info->parent->options, vp);
1665
1666 talloc_free(info);
1667 return 2;
1668}
1669
1670#define member_size(type, member) sizeof(((type *)0)->member)
1671
1672/** filename STRING
1673 *
1674 */
1676{
1677 if (info->argv[0]->vb_length > member_size(dhcp_packet_t, file)) {
1678 fr_strerror_const("filename is too long");
1679 return -1;
1680 }
1681
1683}
1684
1685/** server-name STRING
1686 *
1687 */
1689{
1690 if (info->argv[0]->vb_length > member_size(dhcp_packet_t, sname)) {
1691 fr_strerror_const("server name is too long");
1692 return -1;
1693 }
1694
1695 return add_option_by_da(info, attr_server_name);
1696}
1697
1698/** server-identifier IPADDR
1699 *
1700 *
1701 * This is really "option dhcp-server-identifier IPADDR"
1702 * But whatever
1703 */
1708
1709/** next-server IPADDR
1710 *
1711 */
1716
1717/*
1718 * When a client is to be booted, its boot parameters are determined
1719 * by consulting that client’s host declaration (if any), and then
1720 * consulting any class declarations matching the client, followed by
1721 * the pool, subnet and shared-network declarations for the IP
1722 * address assigned to the client. Each of these declarations itself
1723 * appears within a lexical scope, and all declarations at less
1724 * specific lexical scopes are also consulted for client option
1725 * declarations. Scopes are never considered twice, and if parameters
1726 * are declared in more than one scope, the parameter declared in the
1727 * most specific scope is the one that is used.
1728 *
1729 * When dhcpd tries to find a host declaration for a client, it first
1730 * looks for a host declaration which has a fixed-address declaration
1731 * that lists an IP address that is valid for the subnet or shared
1732 * network on which the client is booting. If it doesn’t find any
1733 * such entry, it tries to find an entry which has no fixed-address
1734 * declaration.
1735 */
1736
1737/** Apply fixed IPs
1738 *
1739 */
1740static int apply_fixed_ip(rlm_isc_dhcp_t const *inst, request_t *request)
1741{
1742 rlm_isc_dhcp_info_t *host, *info;
1743 fr_pair_t *vp;
1744 fr_pair_t *yiaddr;
1745
1746 /*
1747 * If there's already a fixed IP, don't do anything
1748 */
1749 yiaddr = fr_pair_find_by_da(&request->reply_pairs, NULL, attr_your_ip_address);
1750 if (yiaddr) return 0;
1751
1752 host = get_host(request, inst->hosts_by_ether, inst->hosts_by_uid);
1753 if (!host) return 0;
1754
1755 /*
1756 * Find a "fixed-address" sub-statement.
1757 */
1758 for (info = host->child; info != NULL; info = info->next) {
1759 if (!info->cmd) return -1; /* internal error */
1760
1761 /*
1762 * Skip complex statements
1763 */
1764 if (info->child) continue;
1765
1766 if (info->cmd->type != ISC_FIXED_ADDRESS) continue;
1767
1768 MEM(vp = fr_pair_afrom_da(request->reply_ctx, attr_your_ip_address));
1769
1770 if (unlikely(fr_value_box_copy(vp, &(vp->data), info->argv[0]) < 0)) {
1771 RPEDEBUG("Failed assigning Your-IP-Address");
1772 return -1;
1773 }
1774 fr_pair_append(&request->reply_pairs, vp);
1775
1776 /*
1777 * If we've found a fixed IP, then tell
1778 * the parent to stop iterating over
1779 * children.
1780 */
1781 return 2;
1782 }
1783
1784 return 0;
1785}
1786
1787/** Apply all rules *except* fixed IP
1788 *
1789 */
1791{
1792 int ret, child_ret;
1793 rlm_isc_dhcp_info_t *info;
1794 fr_pair_t *yiaddr;
1795
1796 ret = 0;
1797 yiaddr = fr_pair_find_by_da(&request->reply_pairs, NULL, attr_your_ip_address);
1798
1799 /*
1800 * First, apply any "host" options
1801 */
1802 if (head->hosts_by_ether) {
1803 rlm_isc_dhcp_info_t *host = NULL;
1804
1805 host = get_host(request, head->hosts_by_ether, head->hosts_by_uid);
1806 if (!host) goto subnet;
1807
1808 /*
1809 * Apply any options in the "host" section.
1810 */
1811 child_ret = apply(inst, request, host);
1812 if (child_ret < 0) return child_ret;
1813 if (child_ret == 1) ret = 1;
1814 }
1815
1816subnet:
1817 /*
1818 * Look in the trie for matching subnets, and apply any
1819 * subnets that match.
1820 */
1821 if (head->subnets && yiaddr) {
1822 info = fr_trie_lookup_by_key(head->subnets, &yiaddr->vp_ipv4addr, 32);
1823 if (!info) goto recurse;
1824
1825 child_ret = apply(inst, request, info);
1826 if (child_ret < 0) return child_ret;
1827 if (child_ret == 1) ret = 1;
1828 }
1829
1830recurse:
1831 for (info = head->child; info != NULL; info = info->next) {
1832 if (!info->cmd) return -1; /* internal error */
1833
1834 if (!info->cmd->apply) continue;
1835
1836 child_ret = info->cmd->apply(inst, request, info);
1837 if (child_ret < 0) return child_ret;
1838 if (child_ret == 0) continue;
1839
1840 ret = 1;
1841 }
1842
1843 /*
1844 * Now that our children have added options, see if we
1845 * can add some, too.
1846 */
1847 if (!fr_pair_list_empty(&head->options)) {
1848 fr_pair_t *vp = NULL;
1849
1850 /*
1851 * Walk over the input list, adding the options
1852 * only if they don't already exist in the reply.
1853 *
1854 * Yes, we know that this is O(R*P*D), complexity
1855 * is (reply VPs * option VPs * depth of options).
1856 *
1857 * Unless we make the code a lot smarter, this is
1858 * the best we can do. Since there are likely
1859 * only a few options (i.e. less than 100), this
1860 * is deemed to be OK.
1861 *
1862 * In order to fix this, we would need to sort
1863 * all of the options first, sort the reply VPs,
1864 * then walk over the reply VPs, and look at each
1865 * option list in turn, seeing if there are
1866 * options that match. This would likely be
1867 * faster.
1868 */
1869 for (vp = fr_pair_list_head(&head->options);
1870 vp != NULL;
1871 vp = fr_pair_list_next(&head->options, vp)) {
1872 fr_pair_t *reply;
1873
1874 reply = fr_pair_find_by_da(&request->reply_pairs, NULL, vp->da);
1875 if (reply) continue;
1876
1877 /*
1878 * Copy all of the same options to the
1879 * reply.
1880 */
1881 while (vp) {
1882 fr_pair_t *next, *copy;
1883
1884 copy = fr_pair_copy(request->reply_ctx, vp);
1885 if (!copy) return -1;
1886
1887 fr_pair_append(&request->reply_pairs, copy);
1888
1889 next = fr_pair_list_next(&head->options, vp);
1890 if (!next) break;
1891 if (next->da != vp->da) break;
1892
1893 vp = fr_pair_list_next(&head->options, vp);
1894 }
1895 }
1896
1897 /*
1898 * We applied some options.
1899 */
1900 ret = 1;
1901 }
1902
1903 return ret;
1904}
1905
1906#define isc_not_done ISC_NOOP, NULL, NULL
1907#define isc_ignore ISC_IGNORE, NULL, NULL
1908#define isc_invalid ISC_INVALID, NULL, NULL
1909
1910
1911/** Table of commands that we allow.
1912 *
1913 */
1915 { "abandon-lease-time INTEGER", isc_not_done, 1},
1916 { "adaptive-lease-time-threshold INTEGER", isc_not_done, 1},
1917 { "allow-booting BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1918 { "allow-bootp BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1919 { "always-broadcast BOOL", isc_not_done, 1},
1920 { "always-reply-rfc1048 BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1921 { "authoritative", isc_not_done, 0},
1922 { "bind-local-address6 BOOL", isc_ignore, 1}, // boolean can be true, false or ignore
1923 { "boot-unknown-clients BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1924 { "check-secs-byte-order BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1925 { "class STRING SECTION", isc_invalid, 1}, // put systems into different classes
1926 { "client-updates BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1927 { "ddns-domainname STRING", isc_not_done, 1}, // text string
1928 { "ddns-dual-stack-mixed-mode BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1929 { "ddns-guard-id-must-match BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1930 { "ddns-hostname STRING", isc_not_done, 1}, // text string
1931 { "ddns-local-address4 IPADDR", isc_not_done, 1}, // ipaddr or hostname
1932 { "ddns-local-address6 IPADDR6", isc_not_done, 1}, // ipv6 addr
1933 { "ddns-other-guard-is-dynamic BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1934 { "ddns-rev-domainname STRING", isc_not_done, 1}, // text string
1935 { "ddns-ttl UINT32", isc_not_done, 1}, // Lease time interval
1936 { "ddns-update-style STRING,", isc_not_done, 1}, // string options. e.g: opt1, opt2 or opt3 [arg1, ... ]
1937 { "ddns-updates BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1938 { "declines BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1939 { "default-lease-time INTEGER", isc_not_done, 1},
1940 { "delayed-ack UINT16", isc_invalid, 1},
1941 { "dhcp-cache-threshold UINT8", isc_not_done, 1}, // integer uint8_t
1942 { "dhcpv6-lease-file-name STRING", isc_ignore, 1}, // text string
1943 { "dhcpv6-pid-file-name STRING", isc_ignore, 1}, // text string
1944 { "dhcpv6-set-tee-times BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1945 { "do-forward-updates BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1946 { "do-reverse-updates BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1947 { "dont-use-fsync BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1948 { "duplicates BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1949 { "dynamic-bootp BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1950 { "dynamic-bootp-lease-cutoff UINT32", isc_not_done, 1}, // Lease time interval
1951 { "dynamic-bootp-lease-length UINT32", isc_not_done, 1}, // integer uint32_t
1952 { "echo-client-id BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1953 { "filename STRING", ISC_NOOP, parse_filename, NULL, 1},
1954 { "fixed-address IPADDR,", ISC_FIXED_ADDRESS, NULL, NULL, 16},
1955 { "fqdn-reply BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1956 { "get-lease-hostnames BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1957 { "group SECTION", ISC_GROUP, NULL, NULL, 1},
1958 { "hardware ethernet ETHER", ISC_HARDWARE_ETHERNET, NULL, NULL, 1},
1959 { "host STRING SECTION", ISC_HOST, parse_host, NULL, 1},
1960 { "ignore-client-uids BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1961 { "include STRING", ISC_NOOP, parse_include, NULL, 1},
1962 { "infinite-is-reserved BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1963
1964 /*
1965 * Group configuration into sections? Why the heck would
1966 * we do that? A flat name space worked for Fortran 77.
1967 * It should be good enough for us here.
1968 */
1969 { "ldap-base-dn STRING", isc_ignore, 1}, // text string
1970 { "ldap-debug-file STRING", isc_ignore, 1}, // text string
1971 { "ldap-dhcp-server-cn STRING", isc_ignore, 1}, // text string
1972 { "ldap-gssapi-keytab STRING", isc_ignore, 1}, // text string
1973 { "ldap-gssapi-principal STRING", isc_ignore, 1}, // text string
1974 { "ldap-init-retry STRING", isc_ignore, 1}, // domain name
1975 { "ldap-method STRING,", isc_ignore, 1}, // string options. e.g: opt1, opt2 or opt3 [arg1, ... ]
1976 { "ldap-password STRING", isc_ignore, 1}, // text string
1977 { "ldap-port STRING", isc_ignore, 1}, // domain name
1978 { "ldap-referrals BOOL", isc_ignore, 1}, // boolean can be true, false or ignore
1979 { "ldap-server STRING", isc_ignore, 1}, // text string
1980 { "ldap-ssl STRING,", isc_ignore, 1}, // string options. e.g: opt1, opt2 or opt3 [arg1, ... ]
1981 { "ldap-tls-ca-dir STRING", isc_ignore, 1}, // text string
1982 { "ldap-tls-ca-file STRING", isc_ignore, 1}, // text string
1983 { "ldap-TLS-Certificate STRING", isc_ignore, 1}, // text string
1984 { "ldap-tls-ciphers STRING", isc_ignore, 1}, // text string
1985 { "ldap-tls-crlcheck STRING,", isc_ignore, 1}, // string options. e.g: opt1, opt2 or opt3 [arg1, ... ]
1986 { "ldap-tls-key STRING", isc_ignore, 1}, // text string
1987 { "ldap-tls-randfile STRING", isc_ignore, 1}, // text string
1988 { "ldap-tls-reqcert STRING,", isc_ignore, 1}, // string options. e.g: opt1, opt2 or opt3 [arg1, ... ]
1989 { "ldap-username STRING", isc_ignore, 1}, // text string
1990
1991 { "lease-file-name STRING", isc_ignore, 1}, // text string
1992 { "leasequery BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
1993 { "limit-addrs-per-ia UINT32", isc_not_done, 1}, // integer uint32_t
1994 { "limit-prefs-per-ia UINT32", isc_not_done, 1}, // integer uint32_t
1995 { "limited-broadcast-address IPADDR", isc_not_done, 1}, // ipaddr or hostname
1996 { "local-address IPADDR", isc_ignore, 1}, // ipaddr or hostname
1997 { "local-address6 IPADDR6", isc_ignore, 1}, // ipv6 addr
1998 { "local-port UINT16", isc_ignore, 1}, // integer uint16_t
1999 { "log-facility STRING,", isc_ignore, 1}, // string options. e.g: opt1, opt2 or opt3 [arg1, ... ]
2000 { "log-threshold-high UINT8", isc_ignore, 1}, // integer uint8_t
2001 { "log-threshold-low UINT8", isc_ignore, 1}, // integer uint8_t
2002 { "match", isc_invalid, 0}, // we don't do this at all yet
2003 { "max-ack-delay UINT32", isc_invalid, 1},
2004 { "max-lease-time INTEGER", isc_not_done, 1},
2005 { "min-lease-time INTEGER", isc_not_done, 1},
2006 { "min-secs UINT8", isc_not_done, 1}, // integer uint8_t
2007 { "next-server IPADDR", ISC_NOOP, parse_next_server, NULL, 1}, // ipaddr or hostname
2008 { "not authoritative", isc_not_done, 0},
2009 { "omapi-key STRING", isc_ignore, 1}, // domain name
2010 { "omapi-port UINT16", isc_ignore, 1}, // integer uint16_t
2011 { "one-lease-per-client BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2012 { "option STRING STRING,", ISC_OPTION, NULL, NULL, 16},
2013 { "pid-file-name STRING", isc_ignore, 1}, // text string
2014 { "ping-check BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2015 { "ping-timeout UINT32", isc_not_done, 1}, // Lease time interval
2016 { "pool SECTION", isc_invalid, 0}, // sub pools
2017 { "preferred-lifetime UINT32", isc_not_done, 1}, // Lease time interval
2018 { "prefix-length-mode STRING,", isc_not_done, 1}, // string options. e.g: opt1, opt2 or opt3 [arg1, ... ]
2019 { "range IPADDR IPADDR", isc_not_done, 2},
2020 { "release-on-roam BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2021 { "remote-port UINT16", isc_ignore, 1}, // integer uint16_t
2022 { "server-id-check BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2023 { "server-identifier IPADDR", ISC_NOOP, parse_server_identifier, NULL, 1}, // ipaddr or host name
2024 { "server-name STRING", ISC_NOOP, parse_server_name, NULL, 1}, // text string
2025 { "shared-network STRING SECTION", isc_not_done, 1},
2026 { "site-option-space STRING", isc_invalid, 1}, // vendor option declaration statement
2027 { "stash-agent-options BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2028 { "subnet IPADDR netmask IPADDR SECTION", ISC_SUBNET, parse_subnet, NULL, 2},
2029 { "update-conflict-detection BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2030 { "update-optimization BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2031 { "update-static-leases BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2032 { "use-host-decl-names BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2033 { "use-lease-addr-for-default-route BOOL", isc_not_done, 1}, // boolean can be true, false or ignore
2034 { "vendor-option-space STRING", isc_invalid, 1}, // vendor option declaration
2035};
2036
2037/** Parse a section { ... }
2038 *
2039 */
2041{
2042 int ret;
2043 int entries = 0;
2044
2045 /*
2046 * We allow "group" inside of "group". But we don't
2047 * allow other sections to nest.
2048 */
2049 if (info->cmd->type != ISC_GROUP) {
2051
2052 for (parent = info->parent; parent != NULL; parent = parent->parent) {
2053 char const *q;
2054
2055 if (!parent->cmd) break; /* top level */
2056
2057 if (parent->cmd != info->cmd) continue;
2058
2059 /*
2060 * Be gentle to the end user
2061 */
2062 q = parent->cmd->name;
2064
2065 fr_strerror_printf("cannot nest '%.*s' statements",
2066 (int) (q - parent->cmd->name), parent->cmd->name);
2067 return -1;
2068 }
2069 }
2070
2071 IDEBUG("%.*s {", state->braces - 1, spaces); /* "braces" was already incremented */
2072 state->allow_eof = false; /* can't have EOF in the middle of a section */
2073
2074 while (true) {
2075 ret = read_token(state, T_BARE_WORD, YES_SEMICOLON, true);
2076 if (ret < 0) return ret;
2077 if (ret == 0) break;
2078
2079 /*
2080 * End of section is allowed here.
2081 */
2082 if (*state->token == '}') break;
2083
2084 ret = match_keyword(info, state, commands, NUM_ELEMENTS(commands));
2085 if (ret < 0) return ret;
2086 if (ret == 0) break;
2087
2088 entries = 1;
2089 }
2090
2091 state->allow_eof = (state->braces == 0);
2092
2093 IDEBUG("%.*s }", state->braces, spaces);
2094
2095 return entries;
2096}
2097
2098/** Open a file and read it into a parent.
2099 *
2100 */
2101static int read_file(rlm_isc_dhcp_t *inst, rlm_isc_dhcp_info_t *parent, char const *filename)
2102{
2103 int ret;
2104 FILE *fp;
2107 char buffer[8192];
2108
2109 /*
2110 * Read the file line by line.
2111 *
2112 * The configuration file format is based off of
2113 * keywords, so we write a simple parser to check that.
2114 */
2115 fp = fopen(filename, "r");
2116 if (!fp) {
2117 fr_strerror_printf("Error opening filename %s: %s", filename, fr_syserror(errno));
2118 return -1;
2119 }
2120
2121 memset(&state, 0, sizeof(state));
2122 state.inst = inst;
2123 state.fp = fp;
2124 state.filename = filename;
2125 state.buffer = buffer;
2126 state.bufsize = sizeof(buffer);
2127 state.lineno = 0;
2128
2129 state.braces = 0;
2130 state.ptr = buffer;
2131 state.token = NULL;
2132
2133 state.debug = inst->debug;
2134 state.allow_eof = true;
2135
2136 /*
2137 * Tell the state machine that the buffer is empty.
2138 */
2139 *state.ptr = '\0';
2140
2141 while (true) {
2142 ret = read_token(&state, T_BARE_WORD, YES_SEMICOLON, false);
2143 if (ret < 0) {
2144 fail:
2145 fr_strerror_printf("Failed reading %s:[%d] - %s",
2146 filename, state.lineno,
2147 fr_strerror());
2148 fclose(fp);
2149 return ret;
2150 }
2151 if (ret == 0) break;
2152
2153 /*
2154 * This will automatically re-fill the buffer,
2155 * and find a matching token.
2156 */
2158 if (ret < 0) goto fail;
2159 if (ret == 0) break;
2160 }
2161
2162 fclose(fp);
2163
2164 /*
2165 * The input "last" pointer didn't change, so we didn't
2166 * read anything.
2167 */
2168 if (!*last) return 0;
2169
2170 return 1;
2171}
2172
2173static int mod_instantiate(module_inst_ctx_t const *mctx)
2174{
2175 rlm_isc_dhcp_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_isc_dhcp_t);
2176 CONF_SECTION *conf = mctx->mi->conf;
2177 rlm_isc_dhcp_info_t *info;
2178 int ret;
2179
2180 MEM(inst->head = info = talloc_zero(inst, rlm_isc_dhcp_info_t));
2181 fr_pair_list_init(&info->options);
2182 info->last = &(info->child);
2183
2184 inst->hosts_by_ether = fr_hash_table_alloc(inst, host_ether_hash, host_ether_cmp, NULL);
2185 if (!inst->hosts_by_ether) return -1;
2186
2187 inst->hosts_by_uid = fr_hash_table_alloc(inst, host_uid_hash, host_uid_cmp, NULL);
2188 if (!inst->hosts_by_uid) return -1;
2189
2190 ret = read_file(inst, info, inst->filename);
2191 if (ret < 0) {
2192 cf_log_err(conf, "%s", fr_strerror());
2193 return -1;
2194 }
2195
2196 if (ret == 0) {
2197 cf_log_warn(conf, "No configuration read from %s", inst->filename);
2198 return 0;
2199 }
2200
2201 return 0;
2202}
2203
2204static unlang_action_t CC_HINT(nonnull) mod_authorize(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
2205{
2207 int ret;
2208
2209 ret = apply_fixed_ip(inst, request);
2210 if (ret < 0) RETURN_UNLANG_FAIL;
2211 if (ret == 0) RETURN_UNLANG_NOOP;
2212
2213 if (ret == 2) RETURN_UNLANG_UPDATED;
2214
2216}
2217
2218static unlang_action_t CC_HINT(nonnull) mod_post_auth(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
2219{
2221 int ret;
2222
2223 ret = apply(inst, request, inst->head);
2224 if (ret < 0) RETURN_UNLANG_FAIL;
2225 if (ret == 0) RETURN_UNLANG_NOOP;
2226
2227 // @todo - check for subnet mask option. If none exists, use one from the enclosing network?
2228
2230}
2231
2234 .common = {
2235 .magic = MODULE_MAGIC_INIT,
2236 .name = "isc_dhcp",
2237 .inst_size = sizeof(rlm_isc_dhcp_t),
2239 .instantiate = mod_instantiate
2240 },
2241 .method_group = {
2242 .bindings = (module_method_binding_t[]){
2243 { .section = SECTION_NAME("recv", CF_IDENT_ANY), .method = mod_authorize },
2244 { .section = SECTION_NAME("send", CF_IDENT_ANY), .method = mod_post_auth },
2246 }
2247 }
2248};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
static int const char char buffer[256]
Definition acutest.h:576
int const char * file
Definition acutest.h:702
#define RCSID(id)
Definition build.h:512
#define MEMCMP_RETURN(_a, _b, _field, _len_field)
Return if the contents of the specified field is not identical between the specified structures.
Definition build.h:164
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:113
#define unlikely(_x)
Definition build.h:407
#define UNUSED
Definition build.h:336
#define NUM_ELEMENTS(_t)
Definition build.h:358
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:669
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:280
#define FR_CONF_OFFSET_FLAGS(_name, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:268
@ CONF_FLAG_REQUIRED
Error out if no matching CONF_PAIR is found, and no dflt value is set.
Definition cf_parse.h:429
@ CONF_FLAG_NOT_EMPTY
CONF_PAIR is required to have a non zero length value.
Definition cf_parse.h:447
@ CONF_FLAG_FILE_READABLE
File matching value must exist, and must be readable.
Definition cf_parse.h:435
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:606
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:345
#define cf_log_warn(_cf, _fmt,...)
Definition cf_util.h:346
#define CF_IDENT_ANY
Definition cf_util.h:80
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:40
fr_dict_t * fr_dict_unconst(fr_dict_t const *dict)
Coerce to non-const.
Definition dict_util.c:4897
fr_dict_attr_t const * fr_dict_attr_by_name(fr_dict_attr_err_t *err, fr_dict_attr_t const *parent, char const *attr))
Locate a fr_dict_attr_t by its name.
Definition dict_util.c:3522
unsigned int array
Pack multiples into 1 attr.
Definition dict.h:90
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2659
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:292
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:305
int fr_dict_attr_add(fr_dict_t *dict, fr_dict_attr_t const *parent, char const *name, unsigned int attr, fr_type_t type, fr_dict_attr_flags_t const *flags))
Add an attribute to the dictionary.
Definition dict_util.c:1962
#define DICT_AUTOLOAD_TERMINATOR
Definition dict.h:311
fr_dict_attr_t const * fr_dict_attr_child_by_num(fr_dict_attr_t const *parent, unsigned int attr)
Check if a child attribute exists in a parent using an attribute number.
Definition dict_util.c:3587
#define FR_DICT_ATTR_MAX_NAME_LEN
Maximum length of a attribute name.
Definition dict.h:501
Specifies an attribute which must be present for the module to function.
Definition dict.h:291
Values of the encryption flags.
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:304
Test enumeration values.
Definition dict_test.h:92
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
void * fr_hash_table_find(fr_hash_table_t *ht, void const *data)
Find data in a hash table.
Definition hash.c:450
uint32_t fr_hash(void const *data, size_t size)
Definition hash.c:847
bool fr_hash_table_insert(fr_hash_table_t *ht, void const *data)
Insert data into a hash table.
Definition hash.c:489
void * fr_hash_table_remove(fr_hash_table_t *ht, void const *data)
Remove an entry from the hash table, without freeing the data.
Definition hash.c:582
bool fr_hash_table_delete(fr_hash_table_t *ht, void const *data)
Remove and free data (if a free function was specified)
Definition hash.c:617
#define fr_hash_table_alloc(_ctx, _hash_node, _cmp_node, _free_node)
Definition hash.h:61
talloc_free(hp)
#define DEBUG_ENABLED
True if global debug level 1 messages are enabled.
Definition log.h:257
#define RPEDEBUG(fmt,...)
Definition log.h:388
fr_type_t
@ FR_TYPE_IPV4_ADDR
32 Bit IPv4 Address.
@ FR_TYPE_ETHERNET
48 Bit Mac-Address.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_IPV6_ADDR
128 Bit IPv6 Address.
@ FR_TYPE_BOOL
A truth value.
@ FR_TYPE_OCTETS
Raw octets.
unsigned int uint32_t
unsigned char uint8_t
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, UNUSED bool tainted)
Convert string value to native attribute value.
Definition pair.c:2617
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:707
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1352
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:290
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
fr_pair_t * fr_pair_copy(TALLOC_CTX *ctx, fr_pair_t const *vp)
Copy a single valuepair.
Definition pair.c:503
static const conf_parser_t config[]
Definition base.c:163
#define fr_assert(_expr)
Definition rad_assert.h:37
#define WARN(fmt,...)
static bool done
Definition radclient.c:80
static rs_t * conf
Definition radsniff.c:52
#define RETURN_UNLANG_UPDATED
Definition rcode.h:70
#define RETURN_UNLANG_FAIL
Definition rcode.h:63
#define RETURN_UNLANG_OK
Definition rcode.h:64
#define RETURN_UNLANG_NOOP
Definition rcode.h:69
char * ptr
pointer into read buffer
#define YES_SEMICOLON
static int parse_option(rlm_isc_dhcp_info_t *parent, rlm_isc_dhcp_tokenizer_t *state, fr_dict_attr_t const *da, char *value)
fr_value_box_t ** argv
#define isc_ignore
bool eof
are we at EOF?
#define TYPE_CHECK(name, type)
Parse one type string.
int(* rlm_isc_dhcp_parse_t)(rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
static unlang_action_t mod_post_auth(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
bool allow_eof
do we allow EOF? (i.e. braces == 0)
fr_dict_attr_autoload_t rlm_isc_dhcp_dict_attr[]
static int match_keyword(rlm_isc_dhcp_info_t *parent, rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_cmd_t const *tokens, int num_tokens)
static int parse_include(rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
static int8_t host_ether_cmp(void const *one, void const *two)
rlm_isc_dhcp_type_t type
static int refill(rlm_isc_dhcp_tokenizer_t *state)
Refills the read buffer with one line from the file.
fr_hash_table_t * hosts_by_ether
by MAC address
static int parse_options(rlm_isc_dhcp_info_t *parent, rlm_isc_dhcp_tokenizer_t *state)
Parse "option" command.
size_t token_len
length of the token
int(* rlm_isc_dhcp_apply_t)(rlm_isc_dhcp_t const *inst, request_t *request, rlm_isc_dhcp_info_t *info)
char * buffer
read buffer
rlm_isc_dhcp_t * inst
module instance
int braces
how many levels deep we are in a { ... }
static int parse_subnet(rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
subnet IPADDR netmask MASK { ... }
rlm_isc_dhcp_info_t * host
static int parse_server_identifier(UNUSED rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
server-identifier IPADDR
fr_value_box_t * client
rlm_isc_dhcp_info_t * head
char * token
current token that we parsed
rlm_isc_dhcp_parse_t parse
static fr_dict_attr_t const * attr_server_identifier
char const * name
static int parse_host(rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
host NAME { ... }
fr_dict_autoload_t rlm_isc_dhcp_dict[]
static int parse_section(rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
Parse a section { ... }.
fr_hash_table_t * hosts_by_uid
by client identifier
static rlm_isc_dhcp_info_t * get_host(request_t *request, fr_hash_table_t *hosts_by_ether, fr_hash_table_t *hosts_by_uid)
fr_hash_table_t * hosts_by_uid
by client identifier
static int parse_server_name(UNUSED rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
server-name STRING
static int parse_next_server(UNUSED rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
next-server IPADDR
static fr_dict_attr_t const * attr_boot_filename
static uint32_t host_uid_hash(void const *data)
static unlang_action_t mod_authorize(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static int parse_option_definition(rlm_isc_dhcp_info_t *parent, rlm_isc_dhcp_tokenizer_t *state, char *name)
option new-name code new-code = definition ;
rlm_isc_dhcp_info_t ** last
pointer to last child
module_rlm_t rlm_isc_dhcp
static int skip_spaces(rlm_isc_dhcp_tokenizer_t *state, char *p)
char string[256]
double quoted strings go here, so we don't mangle the input buffer
static fr_dict_t const * dict_dhcpv4
bool saw_semicolon
whether we saw a semicolon
static fr_dict_attr_t const * attr_client_hardware_address
static int apply_fixed_ip(rlm_isc_dhcp_t const *inst, request_t *request)
Apply fixed IPs.
char const * filename
static fr_dict_attr_t const * attr_your_ip_address
static int parse_filename(UNUSED rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
filename STRING
static const rlm_isc_dhcp_cmd_t commands[]
Table of commands that we allow.
static fr_dict_attr_t const * attr_server_name
char * line
where the current line started
fr_pair_list_t options
DHCP options.
bool debug
internal developer debugging
static int read_file(rlm_isc_dhcp_t *inst, rlm_isc_dhcp_info_t *parent, char const *filename)
Open a file and read it into a parent.
rlm_isc_dhcp_info_t * next
void * data
per-thing parsed data.
static fr_dict_attr_t const * attr_client_identifier
static fr_type_t isc2fr_type(rlm_isc_dhcp_tokenizer_t *state)
size_t bufsize
size of read buffer
static int apply(rlm_isc_dhcp_t const *inst, request_t *request, rlm_isc_dhcp_info_t *head)
Apply all rules except fixed IP.
#define IDEBUG
rlm_isc_dhcp_cmd_t const * cmd
static fr_dict_attr_t const * attr_server_ip_address
static int8_t host_uid_cmp(void const *one, void const *two)
#define DDEBUG(...)
static char const * spaces
#define MAYBE_SEMICOLON
rlm_isc_dhcp_apply_t apply
#define isc_not_done
static const conf_parser_t module_config[]
#define isc_invalid
fr_hash_table_t * hosts_by_ether
by MAC address
rlm_isc_dhcp_info_t * parent
static int add_option_by_da(rlm_isc_dhcp_info_t *info, fr_dict_attr_t const *da)
static int read_string(rlm_isc_dhcp_tokenizer_t *state)
static uint32_t host_ether_hash(void const *data)
static int mod_instantiate(module_inst_ctx_t const *mctx)
static int read_token(rlm_isc_dhcp_tokenizer_t *state, fr_token_t hint, int semicolon, bool allow_rcbrace)
Reads one token into state->token.
static int parse_option_space(UNUSED rlm_isc_dhcp_info_t *parent, UNUSED rlm_isc_dhcp_tokenizer_t *state, UNUSED char *name)
option space name [ [ code width number ] [ length width number ] [ hash size number ] ] ;
rlm_isc_dhcp_info_t * child
rlm_isc_dhcp_type_t
@ ISC_SUBNET
@ ISC_GROUP
@ ISC_NOOP
we don't do anything with it
@ ISC_OPTION
@ ISC_HOST
@ ISC_FIXED_ADDRESS
@ ISC_IGNORE
we deliberately ignore it
@ ISC_HARDWARE_ETHERNET
@ ISC_INVALID
we recognize it, but don't implement it
#define NO_SEMICOLON
#define member_size(type, member)
static int match_subword(rlm_isc_dhcp_tokenizer_t *state, char const *cmd, rlm_isc_dhcp_info_t *info)
Recursively match subwords inside of a command string.
rlm_isc_dhcp_info_t * host
Describes the commands that we accept, including it's syntax (i.e.
Holds information about the thing we parsed.
Holds the state of the current tokenizer.
static char const * name
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:39
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:351
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:293
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:152
Named methods exported by a module.
Definition module.h:174
#define fr_skip_not_whitespace(_p)
Skip everything that's not whitespace ('\t', '\n', '\v', '\f', '\r', ' ')
Definition skip.h:49
#define fr_skip_whitespace(_p)
Skip whitespace ('\t', '\n', '\v', '\f', '\r', ' ')
Definition skip.h:36
eap_aka_sim_process_conf_t * inst
fr_aka_sim_id_type_t type
fr_pair_t * vp
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition strlcpy.c:34
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition pair.h:69
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define talloc_get_type_abort_const
Definition talloc.h:117
#define talloc_strndup(_ctx, _str, _len)
Definition talloc.h:150
static size_t talloc_strlen(char const *s)
Returns the length of a talloc array containing a string.
Definition talloc.h:143
enum fr_token fr_token_t
@ T_RCBRACE
Definition token.h:40
@ T_BARE_WORD
Definition token.h:118
@ T_LCBRACE
Definition token.h:39
@ T_DOUBLE_QUOTED_STRING
Definition token.h:119
fr_trie_t * fr_trie_alloc(TALLOC_CTX *ctx, fr_trie_key_t get_key, fr_free_t free_data)
Allocate a trie.
Definition trie.c:741
void * fr_trie_lookup_by_key(fr_trie_t const *ft, void const *key, size_t keylen)
Lookup a key in a trie and return user ctx, if any.
Definition trie.c:1265
int fr_trie_insert_by_key(fr_trie_t *ft, void const *key, size_t keylen, void const *data)
Insert a key and user ctx into a trie.
Definition trie.c:1878
static fr_slen_t head
Definition xlat.h:420
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
fr_pair_t * fr_pair_list_next(fr_pair_list_t const *list, fr_pair_t const *item))
Get the next item in a valuepair list after a specific entry.
Definition pair_inline.c:69
fr_pair_t * fr_pair_list_head(fr_pair_list_t const *list)
Get the head of a valuepair list.
Definition pair_inline.c:42
static fr_slen_t parent
Definition pair.h:858
char const * fr_strerror(void)
Get the last library error.
Definition strerror.c:558
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_const(_msg)
Definition strerror.h:223
#define fr_type_is_null(_x)
Definition types.h:347
static fr_type_t fr_type_from_str(char const *type)
Return the constant value representing a type.
Definition types.h:479
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
Definition value.c:4394
ssize_t fr_value_box_from_str(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, char const *in, size_t inlen, fr_sbuff_unescape_rules_t const *erules)
Definition value.c:6068
static fr_slen_t data
Definition value.h:1340
int nonnull(2, 5))