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