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