The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: a4c1b2f978de7e23354e90a25bcf0c3a2bcd9c9e $
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  */
25 RCSID("$Id: a4c1b2f978de7e23354e90a25bcf0c3a2bcd9c9e $")
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 
32 #include <freeradius-devel/server/map_proc.h>
33 
34 static fr_dict_t const *dict_dhcpv4;
35 
38  { .out = &dict_dhcpv4, .proto = "dhcpv4" },
39  { NULL }
40 };
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 
60  { NULL }
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  */
76 typedef struct {
77  char const *filename;
78  bool debug;
79  bool pedantic;
81 
82  /*
83  * While "host" blocks can appear anywhere, their
84  * definitions are global. We use these hashes for
85  * dedup, and for assigning IP addresses in the `recv`
86  * section. We still need to have host hashes in the
87  * subsections, so that we can apply options from the
88  * bottom up.
89  */
90  fr_hash_table_t *hosts_by_ether; //!< by MAC address
91  fr_hash_table_t *hosts_by_uid; //!< by client identifier
93 
94 /*
95  * A mapping of configuration file names to internal variables.
96  */
97 static const conf_parser_t module_config[] = {
99  { FR_CONF_OFFSET("debug", rlm_isc_dhcp_t, debug) },
100  { FR_CONF_OFFSET("pedantic", rlm_isc_dhcp_t, pedantic) },
102 };
103 
104 #define IDEBUG if (state->debug) DEBUG
105 
106 /*
107  * For developer debugging. Likely not needed
108  */
109 #define DDEBUG(...)
110 
111 /*
112  * The parsing functions return:
113  * <0 on error
114  * 0 for "I did nothing"
115  * 1 for "I did something"
116  *
117  * This pattern allows us to distinguish things like empty files
118  * from full files, and empty subsections from full sections, etc.
119  */
120 
121 /** Holds the state of the current tokenizer
122  *
123  */
124 typedef struct {
125  rlm_isc_dhcp_t *inst; //!< module instance
126  FILE *fp;
127  char const *filename;
128  int lineno;
129  char *line; //!< where the current line started
130 
131  int braces; //!< how many levels deep we are in a { ... }
132  bool saw_semicolon; //!< whether we saw a semicolon
133  bool eof; //!< are we at EOF?
134  bool allow_eof; //!< do we allow EOF? (i.e. braces == 0)
135  bool debug; //!< internal developer debugging
136 
137  char *buffer; //!< read buffer
138  size_t bufsize; //!< size of read buffer
139  char *ptr; //!< pointer into read buffer
140 
141  char *token; //!< current token that we parsed
142  size_t token_len; //!< length of the token
143 
144  char string[256]; //!< double quoted strings go here, so we don't mangle the input buffer
146 
147 
149 typedef int (*rlm_isc_dhcp_apply_t)(rlm_isc_dhcp_t const *inst, request_t *request, rlm_isc_dhcp_info_t *info);
150 
151 typedef enum rlm_isc_dhcp_type_t {
152  ISC_INVALID = 0, //!< we recognize it, but don't implement it
153  ISC_NOOP, //!< we don't do anything with it
154  ISC_IGNORE, //!< we deliberately ignore it
162 
163 /** Describes the commands that we accept, including it's syntax (i.e. name), etc.
164  *
165  */
166 typedef struct {
167  char const *name;
171  int max_argc;
173 
174 /** Holds information about the thing we parsed.
175  *
176  * Note that this parser is forgiving. We would rather accept
177  * things ISC DHCP doesn't accept, than reject things it accepts.
178  *
179  * Since we only implement a tiny portion of it's configuration,
180  * we tend to accept all kinds of things, and then just ignore them.
181  */
184  int argc;
186 
189  void *data; //!< per-thing parsed data.
190 
191  /*
192  * Only for things that have sections
193  */
194  fr_hash_table_t *hosts_by_ether; //!< by MAC address
195  fr_hash_table_t *hosts_by_uid; //!< by client identifier
196  fr_pair_list_t options; //!< DHCP options
199  rlm_isc_dhcp_info_t **last; //!< pointer to last child
200 };
201 
202 static int read_file(rlm_isc_dhcp_t *inst, rlm_isc_dhcp_info_t *parent, char const *filename);
204 
205 static char const *spaces = " ";
206 
207 /** Refills the read buffer with one line from the file.
208  *
209  * This function also takes care of suppressing blank lines, and
210  * lines which only contain comments.
211  */
213 {
214  char *p;
215 
216  if (state->eof) return 0;
217 
218  /*
219  * We've run out of data to parse, reset to the start of
220  * the buffer.
221  */
222  if (!*state->ptr) state->ptr = state->buffer;
223 
224 redo:
225  state->lineno++;
226  state->line = state->ptr;
227 
228  if (!fgets(state->ptr, state->bufsize - (state->ptr - state->buffer), state->fp)) {
229  if (feof(state->fp)) {
230  state->eof = true;
231  return 0;
232  }
233 
234  return -1;
235  }
236 
237  /*
238  * Skip leading spaces
239  */
240  p = state->ptr;
242 
243  /*
244  * The line is all spaces, OR we've hit a comment. Go
245  * get more data.
246  */
247  if (!*p || (*p == '#')) goto redo;
248 
249  /*
250  * Point to the first non-space data.
251  */
252  state->ptr = p;
253 
254  return 1;
255 }
256 
257 static int skip_spaces(rlm_isc_dhcp_tokenizer_t *state, char *p)
258 {
259  char *start = p;
260 
261  state->ptr = p;
262 
263  fr_skip_whitespace(state->ptr);
264 
265  /*
266  * If we ran out of text on this line, re-fill the
267  * buffer. Note that refill() also takes care of
268  * suppressing blank lines and comments. refill() also
269  * takes care of skipping leading spaces, too.
270  */
271  if (!state->eof && !*state->ptr) {
272  int ret;
273 
274  state->ptr = start;
275 
276  ret = refill(state);
277  if (ret < 0) return -1;
278  }
279 
280  /*
281  * Set the semicolon flag as a "peek
282  * ahead", so that the various other
283  * parsers don't need to check it.
284  */
285  if (*state->ptr == ';') state->saw_semicolon = true;
286 
287  return 0;
288 }
289 
290 
291 
292 /*
293  * ISC's double quoted strings allow all kinds of extra magic, so
294  * we re-implement string parsing yet again.
295  */
297 {
298  char *p = state->ptr + 1;
299  char *q = state->string;
300 
301  while (true) {
302  if (!*p) {
303  fr_strerror_const("unterminated string");
304  return -1;
305  }
306 
307  if (*p == '"') {
308  p++;
309  if (isspace((uint8_t) *p)) {
310  if (skip_spaces(state, p) < 0) return -1;
311  break;
312  }
313  }
314 
315  if ((size_t) (q - state->string) >= sizeof(state->string)) {
316  fr_strerror_const("string is too long");
317  return -1;
318  }
319 
320  if (*p != '\\') {
321  *(q++) = *(p++);
322  continue;
323  }
324 
325  // @todo - all of ISC's string escapes, e.g. \x...
326  }
327 
328  *q = '\0';
329 
330  state->token = state->string;
331  state->token_len = (q - state->string);
332  return 1;
333 }
334 
335 
336 /** Reads one token into state->token
337  *
338  * Note that this function *destroys* the input buffer. So if
339  * you need to read two tokens, you have to save the first one
340  * somewhere *outside* of the input buffer.
341  */
342 static int read_token(rlm_isc_dhcp_tokenizer_t *state, fr_token_t hint, int semicolon, bool allow_rcbrace)
343 {
344  char *p;
345 
346 redo:
347  /*
348  * If the buffer is empty, re-fill it.
349  */
350  if (!*state->ptr) {
351  int ret;
352 
353  ret = refill(state);
354  if (ret < 0) return ret;
355 
356  if (ret == 0) {
357  if (!state->allow_eof) {
358  fr_strerror_const("Unexpected EOF");
359  return -1;
360  }
361 
362  return 0;
363  }
364  }
365 
366  /*
367  * The previous token may have ended on a space
368  * or semi-colon. We skip those characters
369  * before looking for the next token.
370  */
371  while (isspace((uint8_t) *state->ptr) || (*state->ptr == ';') || (*state->ptr == ',')) state->ptr++;
372 
373  if (!*state->ptr) goto redo;
374 
375  /*
376  * Start looking for the next token from where we left
377  * off last time.
378  */
379  state->token = state->ptr;
380  state->saw_semicolon = false;
381 
382  /*
383  * Special-case quoted strings.
384  */
385  if (state->token[0] == '"') {
386  if (hint != T_DOUBLE_QUOTED_STRING) {
387  fr_strerror_printf("Unexpected '\"'");
388  return -1;
389  }
390 
391  return read_string(state);
392  }
393 
394  for (p = state->token; *p != '\0'; p++) {
395  /*
396  * "end of word" character. It might be allowed
397  * here, or it might not be.
398  */
399  if (*p == ';') {
400  if (semicolon == NO_SEMICOLON) {
401  fr_strerror_const("unexpected ';'");
402  return -1;
403  }
404 
405  state->ptr = p;
406  state->saw_semicolon = true;
407  break;
408  }
409 
410  /*
411  * For lists of things and code definitions.
412  */
413  if (*p == ',') {
414  state->ptr = p;
415  break;
416  }
417 
418  /*
419  * Allow braces / equal as single character
420  * tokens if they're the first character we saw.
421  * Otherwise, the characters are "end of word"
422  * markers/
423  */
424  if ((*p == '{') || (*p == '}') || (*p == '=')) {
425  if (p == state->token) p++;
426 
427  state->ptr = p;
428  break;
429  }
430 
431  /*
432  * If we find a comment, we ignore everything
433  * until the end of the line.
434  */
435  if (*p == '#') {
436  *p = '\0';
437  state->ptr = p;
438 
439  /*
440  * Nothing here, get more text
441  */
442  if (state->token == state->ptr) goto redo;
443  break;
444  }
445 
446  /*
447  * Whitespace, we're done.
448  */
449  if (isspace((uint8_t) *p)) {
450  if (skip_spaces(state, p) < 0) return -1;
451  break;
452  }
453  }
454 
455  /*
456  * Protect the rest of the code from buffer overflows.
457  */
458  state->token_len = p - state->token;
459 
460  if (state->token_len == 0) {
461  fr_strerror_const("FUCK");
462  return -1;
463  }
464 
465  if (state->token_len >= 256) {
466  fr_strerror_const("token too large");
467  return -1;
468  }
469 
470  /*
471  * Double-check the token against what we were expected
472  * to read.
473  */
474  if (hint == T_LCBRACE) {
475  if (*state->token != '{') {
476  fr_strerror_const("missing '{'");
477  return -1;
478  }
479 
480  if ((size_t) state->braces >= (sizeof(spaces) - 1)) {
481  fr_strerror_const("sections are nested too deep");
482  return -1;
483  }
484 
485  state->braces++;
486  return 1;
487  }
488 
489  if (hint == T_RCBRACE) {
490  if (*state->token != '}') {
491  fr_strerror_const("missing '}'");
492  return -1;
493  }
494 
495  state->braces--;
496  return 1;
497  }
498 
499  /*
500  * If we're inside of a section, we may also allow
501  * right-brace as the first keyword. In that case, it's
502  * the end of the enclosing section.
503  */
504  if (*state->token == '}') {
505  if (!allow_rcbrace) {
506  fr_strerror_const("unexpected '}'");
507  return -1;
508  }
509 
510  state->braces--;
511  return 1;
512  }
513 
514  /*
515  * Don't return left brace if we were looking for a
516  * something else.
517  */
518  if ((hint == T_BARE_WORD) || (hint == T_DOUBLE_QUOTED_STRING)) {
519  if (*state->token == '{') {
520  fr_strerror_const("unexpected '{'");
521  return -1;
522  }
523  }
524 
525 
526  return 1;
527 }
528 
529 /** Recursively match subwords inside of a command string.
530  *
531  */
532 static int match_subword(rlm_isc_dhcp_tokenizer_t *state, char const *cmd, rlm_isc_dhcp_info_t *info)
533 {
534  int ret;
535  fr_type_t type;
536  int semicolon = NO_SEMICOLON;
537  bool multi = false;
538  char *p;
539  char const *q;
540  char const *next;
541  char type_name[64];
542 
543  fr_skip_whitespace(cmd);
544 
545  if (!*cmd) return -1; /* internal error */
546 
547  /*
548  * Remember the next command.
549  */
550  next = q = cmd;
551  while (*next && !isspace((uint8_t) *next) && (*next != ',')) next++;
552  if (!*next) semicolon = YES_SEMICOLON;
553 
554  /*
555  * Matching an in-line word.
556  */
557  if (islower((uint8_t) *q)) {
558  ret = read_token(state, T_BARE_WORD, semicolon, false);
559  if (ret <= 0) return -1;
560 
561  /*
562  * Look for a verbatim word.
563  */
564  for (p = state->token; p < (state->token + state->token_len); p++, q++) {
565  if (*p != *q) {
566  fail:
567  fr_strerror_printf("Expected '%.*s', got unknown text '%.*s'",
568  (int)state->token_len, state->token,
569  (int) (next - cmd), cmd);
570  return -1;
571  }
572  }
573 
574  /*
575  * Matched all of 'q', we're done.
576  */
577  if (!*q) {
578  DDEBUG("... WORD %.*s ", state->token_len, state->token);
579  return 1;
580  }
581 
582  /*
583  * Matched all of this word in 'q', but there are
584  * more words after this one.. Recurse.
585  */
586  if (isspace((uint8_t) *q)) {
587  return match_subword(state, next, info);
588  }
589 
590  /*
591  * Matched all of 'p', but there's more 'q'. Fail.
592  *
593  * e.g. got "foo", but expected "food".
594  */
595  goto fail;
596  }
597 
598  /*
599  * SECTION must be the last thing in the command
600  */
601  if (strcmp(q, "SECTION") == 0) {
602  if (q[7] != '\0') return -1; /* internal error */
603 
604  ret = read_token(state, T_LCBRACE, NO_SEMICOLON, false);
605  if (ret <= 0) return ret;
606 
607  ret = parse_section(state, info);
608  if (ret < 0) return ret;
609 
610  /*
611  * Empty sections are allowed.
612  */
613  return 2; /* SECTION */
614  }
615 
616  /*
617  * Uppercase words are INTEGER or STRING or IPADDR, which
618  * are FreeRADIUS data types.
619  *
620  * We copy the name here because some options allow for
621  * multiple fields.
622  */
623  p = type_name;
624  while (*q && !isspace((uint8_t) *q) && (*q != ',')) {
625  if ((p - type_name) >= (int) sizeof(type_name)) return -1; /* internal error */
626  *(p++) = tolower((uint8_t) *(q++));
627  }
628  *p = '\0';
629 
630  /*
631  * "fixed-address IPADDR," means it can take multiple IP
632  * addresses.
633  *
634  * @todo - pre-parse the field and save the strings
635  * somewhere, so that we can create info->argv of the
636  * right size. Or, just create an array of 2 by default,
637  * and then double it every time we run out... a little
638  * more work, but it doesn't involve further mangling the
639  * parser.
640  *
641  * We could likely just manually parse state->ptr, look
642  * until ';' or '\0', and count the words. That would
643  * work 99% of the time.
644  *
645  * @todo - We should also note that the ISC default is to
646  * allow hostnames, in which case it will add all IPs
647  * associated with that hostname, while we will add only
648  * one. That could likely be fixed, too.
649  */
650  if (*q == ',') {
651  if (q[1]) return -1; /* internal error */
652  multi = true;
653  semicolon = MAYBE_SEMICOLON;
654  }
655 
656  type = fr_type_from_str(type_name);
657  if (type == FR_TYPE_NULL) {
658  fr_strerror_printf("unknown data type '%.*s'",
659  (int) (next - cmd), cmd);
660  return -1; /* internal error */
661  }
662 
663 redo_multi:
664  /*
665  * We were asked to parse a data type, so instead allow
666  * just about anything.
667  *
668  * @todo - if we get fancy, dynamically expand this, too.
669  * ISC doesn't support it, but we can.
670  */
671  ret = read_token(state, T_DOUBLE_QUOTED_STRING, semicolon, false);
672  if (ret <= 0) return ret;
673 
674  DDEBUG("... DATA %.*s ", state->token_len, state->token);
675 
676  /*
677  * BOOLs in ISC are "true", "false", or "ignore".
678  *
679  * Isn't that smart? "ignore" means "ignore this option
680  * as if it was commented out". So we do that.
681  *
682  * I sure wish I was smart enough to allow 3 values for a
683  * boolean data type.
684  */
685  if ((type == FR_TYPE_BOOL) && (state->token_len == 6) &&
686  (strcmp(state->token, "ignore") == 0)) {
687  talloc_free(info);
688  return 2;
689  }
690 
691  /*
692  * Parse the data to its final form.
693  */
694  info->argv[info->argc] = talloc_zero(info, fr_value_box_t);
695 
696  ret = fr_value_box_from_str(info, info->argv[info->argc], type, NULL,
697  state->token, state->token_len, NULL, false);
698  if (ret < 0) return ret;
699 
700  info->argc++;
701 
702  if (multi) {
703  if (state->saw_semicolon) return 1;
704 
705  if (info->argc >= info->cmd->max_argc) {
706  fr_strerror_printf("Too many arguments (%d > %d) for command '%s'",
707  info->argc, info->cmd->max_argc, info->cmd->name);
708  return -1;
709  }
710 
711  goto redo_multi;
712  }
713 
714  /*
715  * No more command to parse, return OK.
716  */
717  if (!*next) return 1;
718 
719  /*
720  * Keep matching more things
721  */
722  return match_subword(state, next, info);
723 }
724 
725 /*
726  * include FILENAME ;
727  */
729 {
730  int ret;
731  char *p, pathname[8192];
732  char const *name = info->argv[0]->vb_strvalue;
733 
734  IDEBUG("%.*s include %s ;", state->braces, spaces, name);
735 
736  p = strrchr(state->filename, '/');
737  if (p) {
738  strlcpy(pathname, state->filename, sizeof(pathname));
739  p = pathname + (p - state->filename) + 1;
740  strlcpy(p, name, sizeof(pathname) - (p - pathname));
741 
742  name = pathname;
743  }
744 
745  /*
746  * Note that we read the included file into the PARENT's
747  * list. i.e. as if the file was included in-place.
748  */
749  ret = read_file(state->inst, info->parent, name);
750  if (ret < 0) return ret;
751 
752  /*
753  * Even if the file was empty, we return "1" to indicate
754  * that we successfully parsed the file. Returning "0"
755  * would indicate that the parent file was at EOF.
756  */
757  return 1;
758 }
759 
760 
761 typedef struct {
762  uint8_t ether[6];
765 
766 static uint32_t host_ether_hash(void const *data)
767 {
768  isc_host_ether_t const *self = data;
769 
770  return fr_hash(self->ether, sizeof(self->ether));
771 }
772 
773 static int8_t host_ether_cmp(void const *one, void const *two)
774 {
775  isc_host_ether_t const *a = one;
776  isc_host_ether_t const *b = two;
777  int ret;
778 
779  ret = memcmp(a->ether, b->ether, 6);
780  return CMP(ret, 0);
781 }
782 
783 typedef struct {
787 
788 static uint32_t host_uid_hash(void const *data)
789 {
790  isc_host_uid_t const *self = data;
791 
792  return fr_hash(self->client->vb_octets, self->client->vb_length);
793 }
794 
795 static int8_t host_uid_cmp(void const *one, void const *two)
796 {
797  isc_host_uid_t const *a = one;
798  isc_host_uid_t const *b = two;
799 
800  MEMCMP_RETURN(a, b, client->vb_octets, client->vb_length);
801  return 0;
802 }
803 
804 
805 /** option space name [ [ code width number ] [ length width number ] [ hash size number ] ] ;
806  *
807  */
809  UNUSED char *name)
810 {
811  // @todo - register the named option space with inst->option_space
812  // and create inst->option_space
813  fr_strerror_const("please implement 'option space name [ [ code width number ] [ length width number ] [ hash size number ] ]'");
814  return -1;
815 }
816 
817 
818 /** Parse one type string.
819  *
820 
821  * boolean
822  * [signed|unsigned] integer [width]
823  * width is 8, 16, or 32
824  * ip-address
825  * ip6-address
826  * text
827  * string
828  * domain-list [compressed]
829  * encapsulate _identifier_
830  */
831 
832 #define TYPE_CHECK(name, type) if ((state->token_len == (sizeof(name) - 1)) && (memcmp(state->token, name, sizeof(name) - 1) == 0)) return type
834 {
835  TYPE_CHECK("boolean", FR_TYPE_BOOL);
836  TYPE_CHECK("integer", FR_TYPE_UINT32);
837  TYPE_CHECK("ip-address", FR_TYPE_IPV4_ADDR);
838  TYPE_CHECK("ip6-address", FR_TYPE_IPV6_ADDR);
839  TYPE_CHECK("text", FR_TYPE_STRING);
840  TYPE_CHECK("string", FR_TYPE_OCTETS);
841 
842  fr_strerror_printf("unknown type '%.*s'", (int)state->token_len, state->token);
843  return FR_TYPE_NULL;
844 }
845 
846 
847 /** option new-name code new-code = definition ;
848  *
849  * "new-name" can also be SPACE.NAME
850  *
851  */
853  char *name)
854 {
855  int ret;
856  char *p;
857  fr_type_t type;
858  fr_dict_attr_t const *da, *root;
859  fr_value_box_t box;
860  fr_dict_attr_flags_t flags;
861 
862  p = strchr(name, '.');
863  if (p) {
864  fr_strerror_const("cannot (yet) define options in spaces");
865  error:
866  talloc_free(name);
867  return -1;
868  }
869 
870  if (parent != state->inst->head) {
871  fr_strerror_const("option definitions cannot be scoped");
872  goto error;
873  }
874 
875  /*
876  * Grab the integer code value.
877  */
878  ret = read_token(state, T_BARE_WORD, NO_SEMICOLON, false);
879  if (ret <= 0) {
880  error_ret:
881  talloc_free(name);
882  return ret;
883  }
884 
886  ret = fr_value_box_from_str(NULL, &box, type, NULL,
887  state->token, state->token_len, NULL, false);
888  if (ret < 0) goto error;
889 
890  /*
891  * Look for '='
892  */
893  ret = read_token(state, T_BARE_WORD, NO_SEMICOLON, false);
894  if (ret <= 0) goto error_ret;
895 
896  if ((state->token_len != 1) || (state->token[0] != '=')) {
897  fr_strerror_printf("expected '=' after code definition got '%.*s'", (int)state->token_len, state->token);
898  goto error;
899  }
900 
901  memset(&flags, 0, sizeof(flags));
902 
903  /*
904  * Data type is:
905  *
906  * TYPE
907  * array of TYPE
908  * { TYPE, ... }
909  *
910  * Note that it also supports
911  *
912  * array of { TYPE, ... }
913  */
914  ret = read_token(state, T_BARE_WORD, MAYBE_SEMICOLON, false);
915  if (ret <= 0) goto error_ret;
916 
917 
918  if ((state->token_len == 5) && (memcmp(state->token, "array", 5) == 0)) {
919  flags.array = 1;
920 
921  ret = read_token(state, T_BARE_WORD, NO_SEMICOLON, false);
922  if (ret <= 0) goto error_ret;
923 
924  if (! ((state->token_len == 2) && (memcmp(state->token, "of", 2) == 0))) {
925  fr_strerror_printf("expected 'array of', not 'array %.*s'",
926  (int)state->token_len, state->token);
927  goto error;
928  }
929 
930  /*
931  * Grab the next token. For now, it MUST have a semicolon
932  */
933  ret = read_token(state, T_BARE_WORD, YES_SEMICOLON, false);
934  if (ret <= 0) goto error_ret;
935  }
936 
937  if ((state->token_len == 1) && (state->token[0] == '{')) {
938  fr_strerror_const("records are not supported in option definition");
939  goto error;
940  }
941 
942  /*
943  * This check is needed only because we have
944  * MAYBE_SEMICOLON above. That's in order to allow
945  * "array of.." statements to product an *array* error,
946  * not a *semicolon* error.
947  */
948  if (!state->saw_semicolon) {
949  fr_strerror_const("expected ';'");
950  goto error;
951  }
952 
953  type = isc2fr_type(state);
954  if (fr_type_is_null(type)) goto error;
955 
956  /*
957  * Now that we've parsed everything, look up the name.
958  * We forbid conflicts, but silently allow duplicates.
959  */
961  if (da &&
962  ((da->attr != box.vb_uint32) || (da->type != type))) {
963  fr_strerror_printf("cannot add different code / type for a pre-existing name '%s'", name);
964  goto error;
965  }
966 
967  /*
968  * And look it up by code, too.
969  *
970  * We allow multiple attributes of the same code / type,
971  * but with different names.
972  */
973  root = fr_dict_root(dict_dhcpv4);
974  da = fr_dict_attr_child_by_num(root, box.vb_uint32);
975  if (da && (da->type != type)) {
976  fr_strerror_printf("cannot add different type for a pre-existing code %d", box.vb_uint32);
977  goto error;
978  }
979 
980  /*
981  * Add it in. Note that this function adds it by name
982  * and by code. So we don't *necessarily* have to do the
983  * name/code checks above. But doing so allows us to
984  * have better error messages.
985  */
986  ret = fr_dict_attr_add(fr_dict_unconst(dict_dhcpv4), root, name, box.vb_uint32, type, &flags);
987  talloc_free(name);
988  if (ret < 0) return ret;
989 
990  /*
991  * Caller doesn't need to do anything else with the thing
992  * we just parsed.
993  */
994  return 2;
995 }
996 
998  fr_dict_attr_t const *da, char *value)
999 {
1000  int ret;
1001  fr_pair_t *vp;
1002 
1003  /*
1004  * The attribute isn't an array, so it MUST have a
1005  * semicolon after it.
1006  */
1007  if (!da->flags.array && !state->saw_semicolon) {
1008  fr_strerror_printf("expected ';' %s", state->ptr);
1009  return -1;
1010  }
1011 
1012  MEM(vp = fr_pair_afrom_da(parent, da));
1013 
1014  /*
1015  * Add in the first value.
1016  */
1017  ret = fr_pair_value_from_str(vp, value, talloc_array_length(value) - 1, NULL, false);
1018  if (ret < 0) {
1019  talloc_free(value);
1020  return ret;
1021  }
1022 
1023  fr_pair_append(&parent->options, vp);
1024 
1025  // @todo - print out ISC names...
1026  IDEBUG("%.*s option %s %s ", state->braces, spaces, da->name, value);
1027  talloc_free(value);
1028 
1029  /*
1030  * We've remembered the option in the parent option list.
1031  * There's no need to add it to the child list here.
1032  */
1033  if (!da->flags.array) return 2;
1034 
1035  /*
1036  * For "array" types, loop through the remaining tokens.
1037  */
1038  while (!state->saw_semicolon) {
1039  ret = read_token(state, T_DOUBLE_QUOTED_STRING, MAYBE_SEMICOLON, false);
1040  if (ret <= 0) return ret;
1041 
1042  MEM(vp = fr_pair_afrom_da(parent, da));
1043 
1044  ret = fr_pair_value_from_str(vp, state->token, state->token_len, NULL, false);
1045  if (ret < 0) return ret;
1046 
1047  fr_pair_append(&parent->options, vp);
1048 
1049  // @todo - print out ISC names...
1050  IDEBUG("%.*s option %s %.*ss ", state->braces, spaces, da->name, (int)state->token_len, state->token);
1051  }
1052 
1053  /*
1054  * We've remembered the option in the parent option list.
1055  * There's no need to add it to the child list here.
1056  */
1057  return 2;
1058 }
1059 
1060 /** Parse "option" command
1061  *
1062  * In any sane system, commands which do different things should
1063  * have different names. In this syntax, it's all miracles and
1064  * unicorns.
1065  *
1066  * option NAME VALUE ;
1067  * option new-name code new-code = definition ;
1068  *
1069  * option space name [ [ code width number ] [ length width number ] [ hash size number ] ] ;
1070  */
1072 {
1073  int ret, argc = 0;
1074  char *argv[2];
1075  char name[FR_DICT_ATTR_MAX_NAME_LEN + 5];
1076 
1077  /*
1078  * Since read_token() mashes the input buffer, we have to save the tokens somewhere.
1079  */
1080  while (!state->saw_semicolon) {
1081  ret = read_token(state, T_BARE_WORD, MAYBE_SEMICOLON, false);
1082  if (ret < 0) return ret;
1083 
1084  argv[argc++] = talloc_strndup(parent, state->token, state->token_len);
1085 
1086  if (argc == 2) break;
1087  }
1088 
1089  /*
1090  * Must have at least two arguments.
1091  */
1092  if (argc < 2) {
1093  fr_strerror_const("unexpected ';'");
1094  return -1;
1095  }
1096 
1097  /*
1098  * Define an option space.
1099  */
1100  if (strcmp(argv[0], "space") == 0) {
1101  talloc_free(argv[0]);
1102  return parse_option_space(parent, state, argv[1]);
1103  }
1104 
1105  /*
1106  * Look up the name. If the option is defined, then
1107  * parse the following options according to the data
1108  * type. Which MAY be a "struct" data type, or an
1109  * "array" data type.
1110  */
1111  if (state->saw_semicolon || (state->ptr[0] == ',')) {
1112  fr_dict_attr_t const *da;
1113 
1114  da = fr_dict_attr_by_name(NULL, fr_dict_root(dict_dhcpv4), argv[0]);
1115  if (da) {
1116  talloc_free(argv[0]);
1117  return parse_option(parent, state, da, argv[1]);
1118  }
1119 
1120  /*
1121  * @todo - nuke this extra step once we have dictionary.isc defined.
1122  */
1123  memcpy(name, "DHCP-", 5);
1124  strlcpy(name + 5, argv[0], sizeof(name) - 5);
1125 
1127  if (da) {
1128  talloc_free(argv[0]);
1129  return parse_option(parent, state, da, argv[1]);
1130  }
1131  }
1132 
1133  /*
1134  * The NAME isn't a known option.
1135  *
1136  * It must be "option NAME code NUMBER = DEFINITION"
1137  */
1138  if (strcmp(argv[1], "code") != 0) {
1139  fr_strerror_printf("unknown option '%s'", argv[0]);
1140  talloc_free(argv[0]);
1141  talloc_free(argv[1]);
1142  return -1;
1143  }
1144 
1145  talloc_free(argv[1]);
1146  return parse_option_definition(parent, state, argv[0]);
1147 }
1148 
1149 
1150 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)
1151 {
1152  int start, end, half;
1153  int semicolon;
1154  int ret;
1155  char const *q = NULL;
1156  rlm_isc_dhcp_info_t *info;
1157 
1158  start = 0;
1159  end = num_tokens - 1;
1160  half = -1;
1161 
1162  /*
1163  * There are no super-short commands.
1164  */
1165  if (state->token_len < 4) goto unknown;
1166 
1167  /*
1168  * Walk over the input token, doing a binary search on
1169  * the token list.
1170  */
1171  while (start <= end) {
1172  half = (start + end) / 2;
1173 
1174  /*
1175  * Skips a function call, and is better for 99%
1176  * of the situations. Since there are no 1 or 2
1177  * character keywords, this always works.
1178  */
1179  ret = state->token[0] - tokens[half].name[0];
1180  if (ret != 0) goto recurse;
1181 
1182  ret = state->token[1] - tokens[half].name[1];
1183  if (ret != 0) goto recurse;
1184 
1185  ret = state->token[2] - tokens[half].name[2];
1186  if (ret != 0) goto recurse;
1187 
1188  /*
1189  * Compare all of the strings.
1190  */
1191  ret = strncmp(state->token, tokens[half].name, state->token_len);
1192 
1193  /*
1194  * Exact match. But maybe we have "foo" input,
1195  * and "food" command?
1196  */
1197  if (ret == 0) {
1198  char c = tokens[half].name[state->token_len];
1199 
1200  /*
1201  * The token exactly matches the command.
1202  */
1203  if (!c || isspace((uint8_t) c)) {
1204  q = &(tokens[half].name[state->token_len]);
1205  break;
1206  }
1207 
1208  /*
1209  * The token is "foo", but the command is
1210  * "food". Go search the lower half of
1211  * the command table.
1212  */
1213  ret = -1;
1214  }
1215 
1216  recurse:
1217  /*
1218  * Token is smaller than the command we checked,
1219  * go check the lower half of the table.
1220  */
1221  if (ret < 0) {
1222  end = half - 1;
1223  } else {
1224  start = half + 1;
1225  }
1226  }
1227 
1228  /*
1229  * Nothing matched, it's a failure.
1230  */
1231  if (!q) {
1232  unknown:
1233  fr_strerror_printf("unknown command '%.*s'", (int)state->token_len, state->token);
1234  return -1;
1235  }
1236 
1237  fr_assert(half >= 0);
1238 
1239  /*
1240  * "option" has multiple parse possibilities, so we treat
1241  * it specially.
1242  */
1243  if (tokens[half].type == ISC_OPTION) {
1244  return parse_options(parent, state);
1245  }
1246 
1247  /*
1248  * Print out more warnings / errors in pedantic mode.
1249  */
1250  if (state->inst->pedantic && !tokens[half].parse) {
1251  if (tokens[half].type == ISC_INVALID) {
1252  ERROR("Command '%.*s' is not supported.",
1253  (int)state->token_len, state->token);
1254  return -1;
1255  }
1256 
1257  /*
1258  * Print out WARNING messages only in debug mode.
1259  * We don't need to spam the main log file every
1260  * time the server starts.
1261  */
1262  if (DEBUG_ENABLED) {
1263  if (tokens[half].type == ISC_NOOP) {
1264  WARN("Command '%.*s' is not yet implemented.",
1265  (int)state->token_len, state->token);
1266  }
1267 
1268  if (tokens[half].type == ISC_IGNORE) {
1269  WARN("Ignoring command '%.*s'. It is not relevant.",
1270  (int)state->token_len, state->token);
1271  }
1272  }
1273  }
1274 
1275  semicolon = YES_SEMICOLON; /* default to always requiring this */
1276 
1277  DDEBUG("... TOKEN %.*s ", (int)state->token_len, state->token);
1278 
1279  info = talloc_zero(parent, rlm_isc_dhcp_info_t);
1280  fr_pair_list_init(&info->options);
1281  if (tokens[half].max_argc) {
1282  info->argv = talloc_zero_array(info, fr_value_box_t *, tokens[half].max_argc);
1283  }
1284 
1285  /*
1286  * Remember which command we parsed.
1287  */
1288  info->parent = parent;
1289  info->cmd = &tokens[half];
1290  info->last = &(info->child);
1291 
1292  /*
1293  * There's more to this command,
1294  * go parse that, too.
1295  */
1296  if (isspace((uint8_t) *q)) {
1297  if (state->saw_semicolon) goto unexpected;
1298 
1299  ret = match_subword(state, q, info);
1300  if (ret <= 0) return ret;
1301 
1302  /*
1303  * SUBSECTION must be at the end
1304  */
1305  if (ret == 2) semicolon = NO_SEMICOLON;
1306  }
1307 
1308  /*
1309  * *q must be empty at this point.
1310  */
1311  if ((semicolon == NO_SEMICOLON) && state->saw_semicolon) {
1312  unexpected:
1313  fr_strerror_const("unexpected ';'");
1314  talloc_free(info);
1315  return -1;
1316  }
1317 
1318  if ((semicolon == YES_SEMICOLON) && !state->saw_semicolon) {
1319  fr_strerror_const("missing ';'");
1320  talloc_free(info);
1321  return -1;
1322  }
1323 
1324  // @todo - print out the thing we parsed
1325 
1326  /*
1327  * Call the "parse" function which should do
1328  * validation, etc.
1329  */
1330  if (tokens[half].parse) {
1331  ret = tokens[half].parse(state, info);
1332  if (ret <= 0) {
1333  talloc_free(info);
1334  return ret;
1335  }
1336 
1337  /*
1338  * The parse function took care of
1339  * remembering the "info" structure. So
1340  * we don't add it to the parent list.
1341  *
1342  * This process ensures that for some
1343  * things (e.g. hosts and subnets), we
1344  * have have O(1) lookups instead of
1345  * O(N).
1346  *
1347  * It also means that the *rest* of the
1348  * commands we parse are in a relatively
1349  * tiny list, which makes the O(N)
1350  * processing of it fairly minor.
1351  */
1352  if (ret == 2) return 1;
1353  }
1354 
1355  /*
1356  * Add the parsed structure to the tail of the
1357  * current list. Note that this portion adds
1358  * only ONE command at a time.
1359  */
1360  *(parent->last) = info;
1361  parent->last = &(info->next);
1362 
1363  /*
1364  * It's a match, and it's OK.
1365  */
1366  return 1;
1367 }
1368 
1369 /** host NAME { ... }
1370  *
1371  * Hosts are global, and are keyed by MAC `hardware ethernet`, and by
1372  * `client-identifier`.
1373  */
1375 {
1376  isc_host_ether_t *my_ether, *old_ether;
1377  isc_host_uid_t *my_uid, *old_uid;
1378  rlm_isc_dhcp_info_t *ether, *child, *parent;
1379  fr_pair_t *vp;
1380 
1381  ether = NULL;
1382  my_uid = NULL;
1383 
1384  /*
1385  * A host MUST have at least one "hardware ethernet" in
1386  * it.
1387  */
1388  for (child = info->child; child != NULL; child = child->next) {
1389  if (child->cmd->type == ISC_HARDWARE_ETHERNET) {
1390  if (ether) {
1391  fr_strerror_const("cannot have two 'hardware ethernet' entries in a 'host'");
1392  return -1;
1393  }
1394 
1395  ether = child;
1396  }
1397  }
1398 
1399  if (!ether) {
1400  fr_strerror_printf("host %s does not contain a 'hardware ethernet' entry",
1401  info->argv[0]->vb_strvalue);
1402  return -1;
1403  }
1404 
1405  /*
1406  * Point directly to the ethernet address.
1407  */
1408  my_ether = talloc_zero(info, isc_host_ether_t);
1409  memcpy(my_ether->ether, &(ether->argv[0]->vb_ether), sizeof(my_ether->ether));
1410  my_ether->host = info;
1411 
1412  /*
1413  * We can't have duplicate ethernet addresses for hosts.
1414  */
1415  old_ether = fr_hash_table_find(state->inst->hosts_by_ether, my_ether);
1416  if (old_ether) {
1417  fr_strerror_printf("'host %s' and 'host %s' contain duplicate 'hardware ethernet' fields",
1418  info->argv[0]->vb_strvalue, old_ether->host->argv[0]->vb_strvalue);
1419  talloc_free(my_ether);
1420  return -1;
1421  }
1422 
1423  /*
1424  * The 'host' entry might not have a client identifier option.
1425  */
1427  if (vp) {
1428  my_uid = talloc_zero(info, isc_host_uid_t);
1429  my_uid->client = &vp->data;
1430  my_uid->host = info;
1431 
1432  old_uid = fr_hash_table_find(state->inst->hosts_by_uid, my_uid);
1433  if (old_uid) {
1434  fr_strerror_printf("'host %s' and 'host %s' contain duplicate 'option client-identifier' fields",
1435  info->argv[0]->vb_strvalue, old_uid->host->argv[0]->vb_strvalue);
1436  talloc_free(my_ether);
1437  talloc_free(my_uid);
1438  return -1;
1439  }
1440  }
1441 
1442  /*
1443  * Insert into the ether hashes.
1444  */
1445  if (!fr_hash_table_insert(state->inst->hosts_by_ether, my_ether)) {
1446  fr_strerror_printf("Failed inserting 'host %s' into hash table",
1447  info->argv[0]->vb_strvalue);
1448  talloc_free(my_ether);
1449  if (my_uid) talloc_free(my_uid);
1450  return -1;
1451  }
1452 
1453  if (my_uid) {
1454  if (!fr_hash_table_insert(state->inst->hosts_by_uid, my_uid)) {
1455  fr_strerror_printf("Failed inserting 'host %s' into hash table",
1456  info->argv[0]->vb_strvalue);
1457  talloc_free(my_uid);
1458  return -1;
1459  }
1460  }
1461 
1462  /*
1463  * The host doesn't have a parent, that's fine..
1464  *
1465  * It typically should tho...
1466  */
1467  if (!info->parent) return 2;
1468 
1469  parent = info->parent;
1470 
1471  /*
1472  * Add the host to the *parents* hash table. That way
1473  * when we apply the parent, we can look up the host in
1474  * its hash table. And avoid the O(N) issue of having
1475  * thousands of "host" entries in the parent->child list.
1476  */
1477  if (!parent->hosts_by_ether) {
1478  parent->hosts_by_ether = fr_hash_table_alloc(parent, host_ether_hash, host_ether_cmp, NULL);
1479  if (!parent->hosts_by_ether) {
1480  return -1;
1481  }
1482  }
1483 
1484  if (!fr_hash_table_insert(parent->hosts_by_ether, my_ether)) {
1485  fr_strerror_printf("Failed inserting 'host %s' into hash table",
1486  info->argv[0]->vb_strvalue);
1487  return -1;
1488  }
1489 
1490  /*
1491  * If we have a UID, insert into the UID hashes.
1492  */
1493  if (my_uid) {
1494  if (!parent->hosts_by_uid) {
1495  parent->hosts_by_uid = fr_hash_table_alloc(parent, host_uid_hash, host_uid_cmp, NULL);
1496  if (!parent->hosts_by_uid) {
1497  return -1;
1498  }
1499  }
1500 
1501 
1502  if (!fr_hash_table_insert(parent->hosts_by_uid, my_uid)) {
1503  fr_strerror_printf("Failed inserting 'host %s' into hash table",
1504  info->argv[0]->vb_strvalue);
1505  return -1;
1506  }
1507  }
1508 
1509  IDEBUG("%.*s host %s { ... }", state->braces, spaces, info->argv[0]->vb_strvalue);
1510 
1511  /*
1512  * We've remembered the host in the parent hosts hash.
1513  * There's no need to add it to the child list here.
1514  */
1515  return 2;
1516 }
1517 
1518 /*
1519  * Utter laziness
1520  */
1521 #define vb_ipv4addr vb_ip.addr.v4.s_addr
1522 
1523 /** subnet IPADDR netmask MASK { ... }
1524  *
1525  */
1527 {
1529  int ret, bits;
1530  uint32_t netmask = info->argv[1]->vb_ipv4addr;
1531 
1532  /*
1533  * Check if argv[1] is a valid netmask
1534  */
1535  if (!(netmask & (~netmask >> 1))) {
1536  fr_strerror_printf("invalid netmask '%pV'", info->argv[1]);
1537  return -1;
1538  }
1539 
1540  /*
1541  * 192.168.2.1/16 is wrong.
1542  */
1543  if ((info->argv[0]->vb_ipv4addr & netmask) != info->argv[0]->vb_ipv4addr) {
1544  fr_strerror_printf("subnet '%pV' does not match netmask '%pV'", info->argv[0], info->argv[1]);
1545  return -1;
1546  }
1547 
1548  /*
1549  * Get number of bits set in netmask.
1550  */
1551  netmask = netmask - ((netmask >> 1) & 0x55555555);
1552  netmask = (netmask & 0x33333333) + ((netmask >> 2) & 0x33333333);
1553  netmask = (netmask + (netmask >> 4)) & 0x0F0F0F0F;
1554  netmask = netmask + (netmask >> 8);
1555  netmask = netmask + (netmask >> 16);
1556  bits = netmask & 0x0000003F;
1557 
1558  parent = info->parent;
1559  if (parent->subnets) {
1560  rlm_isc_dhcp_info_t *old;
1561 
1562  /*
1563  * Duplicate or overlapping "subnet" entries aren't allowed.
1564  */
1565  old = fr_trie_lookup_by_key(parent->subnets, &(info->argv[0]->vb_ipv4addr), bits);
1566  if (old) {
1567  fr_strerror_printf("subnet %pV netmask %pV' overlaps with existing subnet", info->argv[0], info->argv[1]);
1568  return -1;
1569 
1570  }
1571  } else {
1572  parent->subnets = fr_trie_alloc(parent, NULL, NULL);
1573  if (!parent->subnets) return -1;
1574  }
1575 
1576  /*
1577  * Add the subnet to the *parents* trie. That way when
1578  * we apply the parent, we can look up the subnet in its
1579  * trie. And avoid the O(N) issue of having thousands of
1580  * "subnet" entries in the parent->child list.
1581  */
1582 
1583  ret = fr_trie_insert_by_key(parent->subnets, &(info->argv[0]->vb_ipv4addr), bits, info);
1584  if (ret < 0) {
1585  fr_strerror_printf("Failed inserting 'subnet %pV netmask %pV' into trie",
1586  info->argv[0], info->argv[1]);
1587  return -1;
1588  }
1589 
1590  /*
1591  * @todo - if there's no 'option subnet-mask', add one
1592  * from the netmask given here. If there is an 'option
1593  * subnet-mask', then assume that the admin knows what
1594  * he's doing, and don't add one.
1595  */
1596 
1597  IDEBUG("%.*s subnet %pV netmask %pV { ... }", state->braces, spaces, info->argv[0], info->argv[1]);
1598 
1599  /*
1600  * We've remembered the subnet in the parent trie.
1601  * There's no need to add it to the child list here.
1602  */
1603  return 2;
1604 }
1605 
1606 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)
1607 {
1608  fr_pair_t *vp;
1609  isc_host_ether_t *ether, my_ether;
1610  rlm_isc_dhcp_info_t *host = NULL;
1611 
1612  /*
1613  * Look up the host first by client identifier.
1614  * If that doesn't match, use client hardware
1615  * address.
1616  */
1617  vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_client_identifier);
1618  if (vp) {
1619  isc_host_uid_t *client, my_client;
1620 
1621  my_client.client = &(vp->data);
1622 
1623  client = fr_hash_table_find(hosts_by_uid, &my_client);
1624  if (client) {
1625  host = client->host;
1626  goto done;
1627  }
1628  }
1629 
1630 
1631  vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_client_hardware_address);
1632  if (!vp) return NULL;
1633 
1634  memcpy(&my_ether.ether, vp->vp_ether, sizeof(my_ether.ether));
1635 
1636  ether = fr_hash_table_find(hosts_by_ether, &my_ether);
1637  if (!ether) return NULL;
1638 
1639  host = ether->host;
1640 
1641 done:
1642  /*
1643  * @todo - check "fixed-address". This host entry should
1644  * match ONLY if one of the addresses matches the network
1645  * on which the client is booting. OR if there's no
1646  * 'fixed-address' field. OR if there's no 'yiaddr' in
1647  * the request.
1648  */
1649 
1650  return host;
1651 }
1652 
1653 
1655 {
1656  int ret;
1657  fr_pair_t *vp;
1658 
1659  if (!info->parent) return -1; /* internal error */
1660 
1661  MEM(vp = fr_pair_afrom_da(info->parent, da));
1662 
1663  ret = fr_value_box_copy(vp, &(vp->data), info->argv[0]);
1664  if (ret < 0) return ret;
1665 
1666  fr_pair_append(&info->parent->options, vp);
1667 
1668  talloc_free(info);
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  fr_strerror_const("filename is too long");
1681  return -1;
1682  }
1683 
1684  return add_option_by_da(info, attr_boot_filename);
1685 }
1686 
1687 /** server-name STRING
1688  *
1689  */
1691 {
1692  if (info->argv[0]->vb_length > member_size(dhcp_packet_t, sname)) {
1693  fr_strerror_const("filename is too long");
1694  return -1;
1695  }
1696 
1697  return add_option_by_da(info, attr_server_name);
1698 }
1699 
1700 /** server-identifier IPADDR
1701  *
1702  *
1703  * This is really "option dhcp-server-identifier IPADDR"
1704  * But whatever
1705  */
1707 {
1709 }
1710 
1711 /** next-server IPADDR
1712  *
1713  */
1715 {
1717 }
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  */
1742 static int apply_fixed_ip(rlm_isc_dhcp_t const *inst, request_t *request)
1743 {
1744  int ret;
1745  rlm_isc_dhcp_info_t *host, *info;
1746  fr_pair_t *vp;
1747  fr_pair_t *yiaddr;
1748 
1749  /*
1750  * If there's already a fixed IP, don't do anything
1751  */
1752  yiaddr = fr_pair_find_by_da(&request->reply_pairs, NULL, attr_your_ip_address);
1753  if (yiaddr) return 0;
1754 
1755  host = get_host(request, inst->hosts_by_ether, inst->hosts_by_uid);
1756  if (!host) return 0;
1757 
1758  /*
1759  * Find a "fixed-address" sub-statement.
1760  */
1761  for (info = host->child; info != NULL; info = info->next) {
1762  if (!info->cmd) return -1; /* internal error */
1763 
1764  /*
1765  * Skip complex statements
1766  */
1767  if (info->child) continue;
1768 
1769  if (info->cmd->type != ISC_FIXED_ADDRESS) continue;
1770 
1771  MEM(vp = fr_pair_afrom_da(request->reply_ctx, attr_your_ip_address));
1772 
1773  ret = fr_value_box_copy(vp, &(vp->data), info->argv[0]);
1774  if (ret < 0) return ret;
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;
1795  rlm_isc_dhcp_info_t *info;
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 
1818 subnet:
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 
1832 recurse:
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  */
1916 static const rlm_isc_dhcp_cmd_t commands[] = {
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  fr_strerror_printf("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) {
2077  ret = read_token(state, T_BARE_WORD, YES_SEMICOLON, 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 
2086  ret = match_keyword(info, state, commands, NUM_ELEMENTS(commands));
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  */
2103 static int read_file(rlm_isc_dhcp_t *inst, rlm_isc_dhcp_info_t *parent, char const *filename)
2104 {
2105  int ret;
2106  FILE *fp;
2108  rlm_isc_dhcp_info_t **last = parent->last;
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  fr_strerror_printf("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  fr_strerror_printf("Failed reading %s:[%d] - %s",
2148  filename, state.lineno,
2149  fr_strerror());
2150  fclose(fp);
2151  return ret;
2152  }
2153  if (ret == 0) break;
2154 
2155  /*
2156  * This will automatically re-fill the buffer,
2157  * and find a matching token.
2158  */
2160  if (ret < 0) goto fail;
2161  if (ret == 0) break;
2162  }
2163 
2164  fclose(fp);
2165 
2166  /*
2167  * The input "last" pointer didn't change, so we didn't
2168  * read anything.
2169  */
2170  if (!*last) return 0;
2171 
2172  return 1;
2173 }
2174 
2175 static int mod_instantiate(module_inst_ctx_t const *mctx)
2176 {
2177  rlm_isc_dhcp_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_isc_dhcp_t);
2178  CONF_SECTION *conf = mctx->inst->conf;
2179  rlm_isc_dhcp_info_t *info;
2180  int ret;
2181 
2182  MEM(inst->head = info = talloc_zero(inst, rlm_isc_dhcp_info_t));
2183  fr_pair_list_init(&info->options);
2184  info->last = &(info->child);
2185 
2186  inst->hosts_by_ether = fr_hash_table_alloc(inst, host_ether_hash, host_ether_cmp, NULL);
2187  if (!inst->hosts_by_ether) return -1;
2188 
2189  inst->hosts_by_uid = fr_hash_table_alloc(inst, host_uid_hash, host_uid_cmp, NULL);
2190  if (!inst->hosts_by_uid) return -1;
2191 
2192  ret = read_file(inst, info, inst->filename);
2193  if (ret < 0) {
2194  cf_log_err(conf, "%s", fr_strerror());
2195  return -1;
2196  }
2197 
2198  if (ret == 0) {
2199  cf_log_warn(conf, "No configuration read from %s", inst->filename);
2200  return 0;
2201  }
2202 
2203  return 0;
2204 }
2205 
2206 static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
2207 {
2209  int ret;
2210 
2211  ret = apply_fixed_ip(inst, request);
2212  if (ret < 0) RETURN_MODULE_FAIL;
2213  if (ret == 0) RETURN_MODULE_NOOP;
2214 
2215  if (ret == 2) RETURN_MODULE_UPDATED;
2216 
2218 }
2219 
2220 static unlang_action_t CC_HINT(nonnull) mod_post_auth(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
2221 {
2223  int ret;
2224 
2225  ret = apply(inst, request, inst->head);
2226  if (ret < 0) RETURN_MODULE_FAIL;
2227  if (ret == 0) RETURN_MODULE_NOOP;
2228 
2229  // @todo - check for subnet mask option. If none exists, use one from the enclosing network?
2230 
2232 }
2233 
2234 extern module_rlm_t rlm_isc_dhcp;
2236  .common = {
2237  .magic = MODULE_MAGIC_INIT,
2238  .name = "isc_dhcp",
2239  .inst_size = sizeof(rlm_isc_dhcp_t),
2240  .config = module_config,
2242  },
2243  .method_names = (module_method_name_t[]){
2244  { .name1 = "recv", .name2 = CF_IDENT_ANY, .method = mod_authorize },
2245  { .name1 = "send", .name2 = CF_IDENT_ANY, .method = mod_post_auth },
2247  }
2248 };
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition: action.h:35
static int const char char buffer[256]
Definition: acutest.h:574
int const char * file
Definition: acutest.h:702
#define RCSID(id)
Definition: build.h:444
#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:154
#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:110
#define UNUSED
Definition: build.h:313
#define NUM_ELEMENTS(_t)
Definition: build.h:335
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#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:268
#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:256
@ CONF_FLAG_REQUIRED
Error out if no matching CONF_PAIR is found, and no dflt value is set.
Definition: cf_parse.h:406
@ CONF_FLAG_FILE_INPUT
File matching value must exist, and must be readable.
Definition: cf_parse.h:412
@ CONF_FLAG_NOT_EMPTY
CONF_PAIR is required to have a non zero length value.
Definition: cf_parse.h:421
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:563
A section grouping multiple CONF_PAIR.
Definition: cf_priv.h:89
#define cf_log_err(_cf, _fmt,...)
Definition: cf_util.h:265
#define cf_log_warn(_cf, _fmt,...)
Definition: cf_util.h:266
#define CF_IDENT_ANY
Definition: cf_util.h:78
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
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:2860
unsigned int array
Pack multiples into 1 attr.
Definition: dict.h:88
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition: dict.h:250
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition: dict.h:263
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition: dict_util.c:1997
fr_dict_t * fr_dict_unconst(fr_dict_t const *dict)
Coerce to non-const.
Definition: dict_util.c:4179
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:2925
#define FR_DICT_ATTR_MAX_NAME_LEN
Maximum length of a attribute name.
Definition: dict.h:364
int fr_dict_attr_add(fr_dict_t *dict, fr_dict_attr_t const *parent, char const *name, int attr, fr_type_t type, fr_dict_attr_flags_t const *flags))
Add an attribute to the dictionary.
Definition: dict_util.c:1245
Specifies an attribute which must be present for the module to function.
Definition: dict.h:249
Values of the encryption flags.
Definition: merged_model.c:139
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition: dict.h:262
Test enumeration values.
Definition: dict_test.h:92
void *_CONST data
Module instance's parsed configuration.
Definition: dl_module.h:165
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:65
CONF_SECTION *_CONST conf
Module's instance configuration.
Definition: dl_module.h:166
void * fr_hash_table_find(fr_hash_table_t *ht, void const *data)
Find data in a hash table.
Definition: hash.c:428
uint32_t fr_hash(void const *data, size_t size)
Definition: hash.c:806
bool fr_hash_table_insert(fr_hash_table_t *ht, void const *data)
Insert data into a hash table.
Definition: hash.c:466
#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
Definition: merged_model.c:80
@ FR_TYPE_IPV4_ADDR
32 Bit IPv4 Address.
Definition: merged_model.c:86
@ FR_TYPE_ETHERNET
48 Bit Mac-Address.
Definition: merged_model.c:93
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_NULL
Invalid (uninitialised) attribute type.
Definition: merged_model.c:81
@ FR_TYPE_UINT32
32 Bit unsigned integer.
Definition: merged_model.c:99
@ FR_TYPE_IPV6_ADDR
128 Bit IPv6 Address.
Definition: merged_model.c:88
@ FR_TYPE_BOOL
A truth value.
Definition: merged_model.c:95
@ FR_TYPE_OCTETS
Raw octets.
Definition: merged_model.c:84
unsigned int uint32_t
Definition: merged_model.c:33
unsigned char uint8_t
Definition: merged_model.c:30
#define fr_skip_not_whitespace(_p)
Skip everything that's not whitespace ('\t', '\n', '\v', '\f', '\r', ' ')
Definition: misc.h:72
#define fr_skip_whitespace(_p)
Skip whitespace ('\t', '\n', '\v', '\f', '\r', ' ')
Definition: misc.h:59
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:52
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:42
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:51
Specifies a module method identifier.
Definition: module_method.c:36
module_t common
Common fields presented by all modules.
Definition: module_rlm.h:37
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:688
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:278
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:1340
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:484
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, bool tainted)
Convert string value to native attribute value.
Definition: pair.c:2586
static const conf_parser_t config[]
Definition: base.c:188
static bool done
Definition: radclient.c:80
#define WARN(fmt,...)
Definition: radclient.h:47
static rs_t * conf
Definition: radsniff.c:53
#define RETURN_MODULE_NOOP
Definition: rcode.h:62
#define RETURN_MODULE_OK
Definition: rcode.h:57
#define RETURN_MODULE_UPDATED
Definition: rcode.h:63
rlm_rcode_t
Return codes indicating the result of the module call.
Definition: rcode.h:40
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)
char * ptr
pointer into read buffer
Definition: rlm_isc_dhcp.c:139
#define YES_SEMICOLON
Definition: rlm_isc_dhcp.c:66
static int parse_option(rlm_isc_dhcp_info_t *parent, rlm_isc_dhcp_tokenizer_t *state, fr_dict_attr_t const *da, char *value)
Definition: rlm_isc_dhcp.c:997
fr_value_box_t ** argv
Definition: rlm_isc_dhcp.c:185
#define isc_ignore
bool eof
are we at EOF?
Definition: rlm_isc_dhcp.c:133
#define TYPE_CHECK(name, type)
Parse one type string.
Definition: rlm_isc_dhcp.c:832
int(* rlm_isc_dhcp_parse_t)(rlm_isc_dhcp_tokenizer_t *state, rlm_isc_dhcp_info_t *info)
Definition: rlm_isc_dhcp.c:148
bool allow_eof
do we allow EOF? (i.e. braces == 0)
Definition: rlm_isc_dhcp.c:134
fr_dict_attr_autoload_t rlm_isc_dhcp_dict_attr[]
Definition: rlm_isc_dhcp.c:51
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)
Definition: rlm_isc_dhcp.c:728
static int8_t host_ether_cmp(void const *one, void const *two)
Definition: rlm_isc_dhcp.c:773
rlm_isc_dhcp_type_t type
Definition: rlm_isc_dhcp.c:168
static int refill(rlm_isc_dhcp_tokenizer_t *state)
Refills the read buffer with one line from the file.
Definition: rlm_isc_dhcp.c:212
fr_hash_table_t * hosts_by_ether
by MAC address
Definition: rlm_isc_dhcp.c:90
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
Definition: rlm_isc_dhcp.c:142
int(* rlm_isc_dhcp_apply_t)(rlm_isc_dhcp_t const *inst, request_t *request, rlm_isc_dhcp_info_t *info)
Definition: rlm_isc_dhcp.c:149
char * buffer
read buffer
Definition: rlm_isc_dhcp.c:137
rlm_isc_dhcp_t * inst
module instance
Definition: rlm_isc_dhcp.c:125
int braces
how many levels deep we are in a { ... }
Definition: rlm_isc_dhcp.c:131
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
Definition: rlm_isc_dhcp.c:785
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
Definition: rlm_isc_dhcp.c:784
rlm_isc_dhcp_info_t * head
Definition: rlm_isc_dhcp.c:80
char * token
current token that we parsed
Definition: rlm_isc_dhcp.c:141
rlm_isc_dhcp_parse_t parse
Definition: rlm_isc_dhcp.c:169
static fr_dict_attr_t const * attr_server_identifier
Definition: rlm_isc_dhcp.c:48
char const * name
Definition: rlm_isc_dhcp.c:167
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[]
Definition: rlm_isc_dhcp.c:37
fr_trie_t * subnets
Definition: rlm_isc_dhcp.c:197
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
Definition: rlm_isc_dhcp.c:195
fr_hash_table_t * hosts_by_uid
by client identifier
Definition: rlm_isc_dhcp.c:91
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
Definition: rlm_isc_dhcp.c:46
static uint32_t host_uid_hash(void const *data)
Definition: rlm_isc_dhcp.c:788
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 ;
Definition: rlm_isc_dhcp.c:852
rlm_isc_dhcp_info_t ** last
pointer to last child
Definition: rlm_isc_dhcp.c:199
module_rlm_t rlm_isc_dhcp
static int skip_spaces(rlm_isc_dhcp_tokenizer_t *state, char *p)
Definition: rlm_isc_dhcp.c:257
char string[256]
double quoted strings go here, so we don't mangle the input buffer
Definition: rlm_isc_dhcp.c:144
static fr_dict_t const * dict_dhcpv4
Definition: rlm_isc_dhcp.c:34
bool saw_semicolon
whether we saw a semicolon
Definition: rlm_isc_dhcp.c:132
static fr_dict_attr_t const * attr_client_hardware_address
Definition: rlm_isc_dhcp.c:42
static int apply_fixed_ip(rlm_isc_dhcp_t const *inst, request_t *request)
Apply fixed IPs.
char const * filename
Definition: rlm_isc_dhcp.c:77
static fr_dict_attr_t const * attr_your_ip_address
Definition: rlm_isc_dhcp.c:43
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
Definition: rlm_isc_dhcp.c:45
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
Definition: rlm_isc_dhcp.c:129
fr_pair_list_t options
DHCP options.
Definition: rlm_isc_dhcp.c:196
bool debug
internal developer debugging
Definition: rlm_isc_dhcp.c:135
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
Definition: rlm_isc_dhcp.c:188
void * data
per-thing parsed data.
Definition: rlm_isc_dhcp.c:189
static fr_dict_attr_t const * attr_client_identifier
Definition: rlm_isc_dhcp.c:44
static fr_type_t isc2fr_type(rlm_isc_dhcp_tokenizer_t *state)
Definition: rlm_isc_dhcp.c:833
size_t bufsize
size of read buffer
Definition: rlm_isc_dhcp.c:138
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
Definition: rlm_isc_dhcp.c:104
rlm_isc_dhcp_cmd_t const * cmd
Definition: rlm_isc_dhcp.c:183
static fr_dict_attr_t const * attr_server_ip_address
Definition: rlm_isc_dhcp.c:47
static int8_t host_uid_cmp(void const *one, void const *two)
Definition: rlm_isc_dhcp.c:795
#define DDEBUG(...)
Definition: rlm_isc_dhcp.c:109
static char const * spaces
Definition: rlm_isc_dhcp.c:205
#define MAYBE_SEMICOLON
Definition: rlm_isc_dhcp.c:67
rlm_isc_dhcp_apply_t apply
Definition: rlm_isc_dhcp.c:170
#define isc_not_done
static const conf_parser_t module_config[]
Definition: rlm_isc_dhcp.c:97
#define isc_invalid
fr_hash_table_t * hosts_by_ether
by MAC address
Definition: rlm_isc_dhcp.c:194
rlm_isc_dhcp_info_t * parent
Definition: rlm_isc_dhcp.c:187
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)
Definition: rlm_isc_dhcp.c:296
static uint32_t host_ether_hash(void const *data)
Definition: rlm_isc_dhcp.c:766
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.
Definition: rlm_isc_dhcp.c:342
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 ] ] ;
Definition: rlm_isc_dhcp.c:808
rlm_isc_dhcp_info_t * child
Definition: rlm_isc_dhcp.c:198
rlm_isc_dhcp_type_t
Definition: rlm_isc_dhcp.c:151
@ ISC_SUBNET
Definition: rlm_isc_dhcp.c:157
@ ISC_GROUP
Definition: rlm_isc_dhcp.c:155
@ ISC_NOOP
we don't do anything with it
Definition: rlm_isc_dhcp.c:153
@ ISC_OPTION
Definition: rlm_isc_dhcp.c:158
@ ISC_HOST
Definition: rlm_isc_dhcp.c:156
@ ISC_FIXED_ADDRESS
Definition: rlm_isc_dhcp.c:160
@ ISC_IGNORE
we deliberately ignore it
Definition: rlm_isc_dhcp.c:154
@ ISC_HARDWARE_ETHERNET
Definition: rlm_isc_dhcp.c:159
@ ISC_INVALID
we recognize it, but don't implement it
Definition: rlm_isc_dhcp.c:152
#define NO_SEMICOLON
Definition: rlm_isc_dhcp.c:65
#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.
Definition: rlm_isc_dhcp.c:532
rlm_isc_dhcp_info_t * host
Definition: rlm_isc_dhcp.c:763
static unlang_action_t mod_post_auth(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
uint8_t ether[6]
Definition: rlm_isc_dhcp.c:762
Describes the commands that we accept, including it's syntax (i.e.
Definition: rlm_isc_dhcp.c:166
Holds information about the thing we parsed.
Definition: rlm_isc_dhcp.c:182
Holds the state of the current tokenizer.
Definition: rlm_isc_dhcp.c:124
static char const * name
static int instantiate(module_inst_ctx_t const *mctx)
Definition: rlm_rest.c:1312
#define MODULE_NAME_TERMINATOR
Definition: module.h:135
RETURN_MODULE_FAIL
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
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:270
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:743
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:1877
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:1264
static fr_slen_t head
Definition: xlat.h:408
fr_pair_t * fr_pair_list_head(fr_pair_list_t const *list)
Get the head of a valuepair list.
Definition: pair_inline.c:43
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
Definition: pair_inline.c:125
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:70
static fr_slen_t parent
Definition: pair.h:844
char const * fr_strerror(void)
Get the last library error.
Definition: strerror.c:554
#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:326
static fr_type_t fr_type_from_str(char const *type)
Return the constant value representing a type.
Definition: types.h:443
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, bool tainted)
Definition: value.c:5264
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:3689
static fr_slen_t data
Definition: value.h:1259
int nonnull(2, 5))