Radcli library 2.0.0
A simple radius library -- new API reference
Loading...
Searching...
No Matches
dict2-parse.c
1/*
2 * Copyright (C) 1995,1996,1997 Lars Fenneberg
3 *
4 * Copyright 1992 Livingston Enterprises, Inc.
5 *
6 * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan
7 * and Merit Network, Inc. All Rights Reserved
8 *
9 * Copyright (C) 2026 Nikos Mavrogiannopoulos
10 *
11 * See the file COPYRIGHT for the respective terms and conditions.
12 *
13 */
14
20
21#include <config.h>
22#include <includes.h>
23#include <limits.h>
24#include <radcli/radcli.h>
25#include <radcli/radcli2.h>
26#include "dict2.h"
27#include "util.h"
28
29/*- Report whether s is a non-empty string of decimal digits.
30 *
31 * @param s the string to check.
32 * @return 1 if s is non-empty and every character is a digit, 0 otherwise.
33 -*/
34static int is_unsigned_decimal(char const *s)
35{
36 if (*s == '\0')
37 return 0;
38 for (; *s != '\0'; s++) {
39 if (!isdigit((unsigned char) *s))
40 return 0;
41 }
42 return 1;
43}
44
45/* dict2_check_*(): looks up an already-loaded entry with the same primary
46 * key (attribute/vendor name, or attribute+numeric-value for a VALUE) as a
47 * freshly-parsed, not-yet-inserted candidate. Returns 1 if the existing
48 * entry's definition is identical to the candidate's (the caller must
49 * discard the candidate instead of inserting it -- this is what makes
50 * loading the same dictionary content twice cost no extra memory), -1 if it
51 * conflicts (the caller must discard the candidate and fail the whole
52 * parse/add call; a warning naming both definitions has already been
53 * logged), or 0 if the key is new (the caller inserts the candidate
54 * normally). *existing_out is set whenever an entry with the same key was
55 * found (both the 1 and -1 cases), NULL otherwise.
56 *
57 * Shared between the file/buffer parser (dict2_parse()) and the
58 * programmatic radcli_dict_*_add() family below, so the two can never
59 * diverge (REQ-DICT-DATA-005). pfilename/line_no are for the warning
60 * message only; line_no <= 0 means "no line to report" (the programmatic
61 * add path).
62 *
63 * Deliberately keyed on name (attrs_by_name/vendors_by_name) or
64 * attribute+value (values_by_attr), never on attrs_by_id/vendors_by_pec:
65 * several distinct *names* may legitimately share one numeric id -- e.g.
66 * the built-in "Password"/"User-Password" alias pair for attribute 2 (see
67 * lib/dict2.h's radcli_dict_flags comment) -- so that is not treated as a
68 * conflict, only a same-name (or same-attribute-same-value, for VALUE)
69 * redefinition is. */
70/*- Check a candidate attribute against any already-loaded entry of the
71 * same name -- see the comment above for the shared dict2_check_*()
72 * contract.
73 *
74 * @param rh a handle to parsed configuration.
75 * @param cand the not-yet-inserted candidate attribute.
76 * @param pfilename the dictionary file name, for the warning message; NULL
77 * for the programmatic add path.
78 * @param line_no the dictionary line number, for the warning message;
79 * <= 0 means "no line to report" (the programmatic add path).
80 * @param existing_out set to the existing entry with the same key, or
81 * NULL if the key is new.
82 * @return 1 if identical to the existing entry (discard cand), -1 if
83 * conflicting (discard cand and fail), 0 if the key is new (insert cand).
84 -*/
85static int dict2_check_attr(rc_handle *rh, const struct radcli_dict_attr *cand,
86 char const *pfilename, int line_no,
87 struct radcli_dict_attr **existing_out)
88{
89 struct radcli_dict_attr *existing = radcli_dict_attr_by_name(rh, cand->name);
90
91 *existing_out = existing;
92 if (existing == NULL)
93 return 0;
94
95 if (existing->value == cand->value && existing->type == cand->type)
96 return 1;
97
98 if (line_no > 0)
99 rc_log(LOG_WARNING,
100 "%s: line %d: attribute %s redefined with a conflicting "
101 "definition (id %u vendor %u type %d -> id %u vendor %u type %d)",
102 pfilename, line_no, cand->name,
103 (unsigned)ATTRID(existing->value), (unsigned)VENDOR(existing->value), existing->type,
104 (unsigned)ATTRID(cand->value), (unsigned)VENDOR(cand->value), cand->type);
105 else
106 rc_log(LOG_WARNING,
107 "attribute %s redefined with a conflicting definition "
108 "(id %u vendor %u type %d -> id %u vendor %u type %d)",
109 cand->name,
110 (unsigned)ATTRID(existing->value), (unsigned)VENDOR(existing->value), existing->type,
111 (unsigned)ATTRID(cand->value), (unsigned)VENDOR(cand->value), cand->type);
112 return -1;
113}
114
115/*- Check a candidate VALUE against any already-loaded entry for the same
116 * attribute+numeric-value -- see dict2_check_attr()'s comment for the
117 * shared dict2_check_*() contract.
118 *
119 * @param rh a handle to parsed configuration.
120 * @param cand the not-yet-inserted candidate VALUE.
121 * @param pfilename the dictionary file name, for the warning message; NULL
122 * for the programmatic add path.
123 * @param line_no the dictionary line number, for the warning message;
124 * <= 0 means "no line to report" (the programmatic add path).
125 * @param existing_out set to the existing entry with the same key, or
126 * NULL if the key is new.
127 * @return 1 if identical to the existing entry (discard cand), -1 if
128 * conflicting (discard cand and fail), 0 if the key is new (insert cand).
129 -*/
130static int dict2_check_value(rc_handle *rh, const struct radcli_dict_value *cand,
131 char const *pfilename, int line_no,
132 struct radcli_dict_value **existing_out)
133{
134 struct radcli_dict_value *existing =
135 radcli_dict_value_by_attr(rh, cand->attrname, cand->value);
136
137 *existing_out = existing;
138 if (existing == NULL)
139 return 0;
140
141 if (strcasecmp(existing->name, cand->name) == 0)
142 return 1;
143
144 if (line_no > 0)
145 rc_log(LOG_WARNING,
146 "%s: line %d: VALUE %u for attribute %s redefined with a "
147 "conflicting name (%s -> %s)",
148 pfilename, line_no, cand->value, cand->attrname,
149 existing->name, cand->name);
150 else
151 rc_log(LOG_WARNING,
152 "VALUE for attribute %s redefined with a conflicting name "
153 "(%s -> %s)", cand->attrname, existing->name, cand->name);
154 return -1;
155}
156
157/*- Check a candidate vendor against any already-loaded entry of the same
158 * name -- see dict2_check_attr()'s comment for the shared
159 * dict2_check_*() contract.
160 *
161 * @param rh a handle to parsed configuration.
162 * @param cand the not-yet-inserted candidate vendor.
163 * @param pfilename the dictionary file name, for the warning message; NULL
164 * for the programmatic add path.
165 * @param line_no the dictionary line number, for the warning message;
166 * <= 0 means "no line to report" (the programmatic add path).
167 * @param existing_out set to the existing entry with the same key, or
168 * NULL if the key is new.
169 * @return 1 if identical to the existing entry (discard cand), -1 if
170 * conflicting (discard cand and fail), 0 if the key is new (insert cand).
171 -*/
172static int dict2_check_vendor(rc_handle *rh, const struct radcli_dict_vendor *cand,
173 char const *pfilename, int line_no,
174 struct radcli_dict_vendor **existing_out)
175{
176 struct radcli_dict_vendor *existing = radcli_dict_vendor_by_name(rh, cand->name);
177
178 *existing_out = existing;
179 if (existing == NULL)
180 return 0;
181
182 if (existing->pec == cand->pec)
183 return 1;
184
185 if (line_no > 0)
186 rc_log(LOG_WARNING,
187 "%s: line %d: VENDOR %s redefined with a conflicting Vendor-Id "
188 "(%u -> %u)", pfilename, line_no, cand->name, existing->pec, cand->pec);
189 else
190 rc_log(LOG_WARNING,
191 "VENDOR %s redefined with a conflicting Vendor-Id (%u -> %u)",
192 cand->name, existing->pec, cand->pec);
193 return -1;
194}
195
196/* Parses the ATTRIBUTE/VALUE/VENDOR/$INCLUDE/BEGIN-VENDOR/END-VENDOR
197 * dictionary grammar into rh->dict. An entry identical to an already-loaded
198 * one is silently deduplicated (no extra memory); an entry that reuses the
199 * same name (or, for VALUE, the same attribute+numeric-value) with a
200 * conflicting definition fails the load, after a warning naming both
201 * definitions (REQ-DICT-DATA-005; see dict2_check_attr()'s comment above for
202 * exactly what counts as a conflict). */
203/*- Parse a dictionary file/buffer into rh->dict.
204 *
205 * @param rh a handle to parsed configuration.
206 * @param dictfd the open dictionary stream to parse.
207 * @param filename the dictionary's file name, for $INCLUDE resolution and
208 * warning messages; NULL for a buffer-sourced load, which per
209 * REQ-DICT-INIT-002 disables $INCLUDE.
210 * @return 0 on success, -1 on a parse error or conflicting redefinition.
211 -*/
212static int dict2_parse(rc_handle *rh, FILE *dictfd, char const *filename)
213{
214 char namestr[AUTH_ID_LEN];
215 char valstr[AUTH_ID_LEN];
216 char attrstr[AUTH_ID_LEN];
217 char typestr[AUTH_ID_LEN];
218 char optstr[AUTH_ID_LEN];
219 char ifilename[RC_MAX(1024, PATH_MAX)] = {0};
220 char *cp;
221 char *saveptr;
222 char *tok;
223 int line_no = 0;
224 struct radcli_dict_attr *attr;
225 struct radcli_dict_value *dval;
226 struct radcli_dict_vendor *dvend;
227 char *buffer = NULL;
228 size_t bufsize = 0;
229 uint32_t value;
230 int type;
231 int encrypt_type;
232 int has_tag_flag;
233 uint32_t gigawords_attrid;
234 unsigned attr_vendorspec = 0;
235 const char *pfilename = filename;
236
237 if (pfilename == NULL)
238 {
239 pfilename = "memory";
240 }
241
242 while (getline (&buffer, &bufsize, dictfd) != -1)
243 {
244 line_no++;
245
246 /* Skip empty space */
247 if (*buffer == '#' || *buffer == '\0' || *buffer == '\n' || \
248 *buffer == '\r')
249 {
250 continue;
251 }
252
253 /* Strip out comments */
254 cp = strchr(buffer, '#');
255 if (cp != NULL)
256 {
257 *cp = '\0';
258 }
259
260 tok = strtok_r(buffer, " \t\r\n", &saveptr);
261 if (tok == NULL)
262 {
263 continue;
264 }
265
266 if (strcmp (tok, "ATTRIBUTE") == 0)
267 {
268 char *name_t, *val_t, *type_t, *opt_t;
269
270 /* Read the ATTRIBUTE line */
271 name_t = strtok_r(NULL, " \t\r\n", &saveptr);
272 val_t = strtok_r(NULL, " \t\r\n", &saveptr);
273 type_t = strtok_r(NULL, " \t\r\n", &saveptr);
274 opt_t = strtok_r(NULL, " \t\r\n", &saveptr);
275 if (name_t == NULL || val_t == NULL || type_t == NULL)
276 {
277 rc_log(LOG_ERR,
278 "rc_dict_init: invalid attribute on line %d of "
279 "dictionary %s", line_no, pfilename);
280 goto error;
281 }
282 /*
283 * Validate all entries. Length checks must run against the
284 * original tokens, before strlcpy() truncates them into the
285 * fixed-size buffers below -- otherwise an over-long name can
286 * never trip the check, since the truncated copy is always
287 * within bounds.
288 */
289 if (strlen (name_t) > RC_NAME_LENGTH)
290 {
291 rc_log(LOG_ERR,
292 "rc_dict_init: invalid name length on line %d of "
293 "dictionary %s", line_no, pfilename);
294 goto error;
295 }
296
297 strlcpy(namestr, name_t, sizeof(namestr));
298 strlcpy(valstr, val_t, sizeof(valstr));
299 strlcpy(typestr, type_t, sizeof(typestr));
300 if (opt_t != NULL)
301 {
302 strlcpy(optstr, opt_t, sizeof(optstr));
303 }
304 else
305 {
306 optstr[0] = '\0';
307 }
308
309 if (!is_unsigned_decimal (valstr))
310 {
311 rc_log(LOG_ERR,
312 "rc_dict_init: invalid value on line %d of dictionary %s",
313 line_no, pfilename);
314 goto error;
315 }
316 value = atoi (valstr);
317
318 if (strcmp (typestr, "string") == 0)
319 {
320 type = PW_TYPE_STRING;
321 }
322 else if (strcmp (typestr, "integer") == 0)
323 {
324 type = PW_TYPE_INTEGER;
325 }
326 else if (strcmp (typestr, "uint32") == 0)
327 {
328 /* FreeRADIUS's newer dictionaries (e.g. dictionary.rfc2868)
329 * spell PW_TYPE_INTEGER "uint32"; same synonym relationship
330 * as ipaddr/ipv4addr above. */
331 type = PW_TYPE_INTEGER;
332 }
333 else if (strcmp (typestr, "enum") == 0)
334 {
335 /* IANA's RADIUS Attribute Types registry uses "enum" as the
336 * Data Type for attributes whose 4-octet integer value is
337 * drawn from a fixed set of named constants (e.g.
338 * Service-Type, NAS-Port-Type) -- it is not one of RFC 8044's
339 * wire data types, just IANA's name for this semantic
340 * category of PW_TYPE_INTEGER. Same synonym relationship as
341 * "uint32" above: resolves to the same RADCLI_TYPE_INTEGER,
342 * with no dedicated radcli_attr_type value of its own. */
343 type = PW_TYPE_INTEGER;
344 }
345 else if (strcmp (typestr, "ipaddr") == 0)
346 {
347 type = PW_TYPE_IPADDR;
348 }
349 else if (strcmp (typestr, "ipv4addr") == 0)
350 {
351 type = PW_TYPE_IPADDR;
352 }
353 else if (strcmp (typestr, "ipv6addr") == 0)
354 {
355 type = PW_TYPE_IPV6ADDR;
356 }
357 else if (strcmp (typestr, "ipv6prefix") == 0)
358 {
359 type = PW_TYPE_IPV6PREFIX;
360 }
361 else if (strcmp (typestr, "date") == 0)
362 {
363 type = PW_TYPE_DATE;
364 }
365 else if (strcmp (typestr, "time") == 0)
366 {
367 /* RFC 8044 SS3.5 names this "time"; it is the same 32-bit
368 * seconds-since-epoch representation as the legacy "date"
369 * keyword/PW_TYPE_DATE/RADCLI_TYPE_DATE, just newer
370 * terminology -- same synonym relationship as ipaddr/
371 * ipv4addr above. */
372 type = PW_TYPE_DATE;
373 }
374 else if (strcmp (typestr, "integer64") == 0)
375 {
376 /* RFC 8044 SS3.3 "integer64" data type (8 octets, network
377 * byte order) -- see radcli2.h's RADCLI_TYPE_INTEGER64.
378 * Internal-only sentinel: PW_TYPE_MAX (6) is not a legal
379 * rc_attr_type value -- rc_dict_addattr() (the public,
380 * programmatic attribute API) rejects any type >= PW_TYPE_MAX,
381 * so no VALUE_PAIR-based caller can ever construct a
382 * DICT_ATTR with this type, only the bundled dictionary file
383 * parsed here can. radcli2.h's RADCLI_TYPE_INTEGER64 is the
384 * only way to see one; see dict_type_to_radcli() in dict.c. */
385 type = PW_TYPE_MAX;
386 }
387 else if (strcmp (typestr, "ipv4prefix") == 0)
388 {
389 /* RFC 8044 SS3.9 "ipv4prefix" data type: reserved(1) +
390 * prefix-len(1) + address(4) -- see radcli2.h's
391 * RADCLI_TYPE_IPV4PREFIX. Internal-only sentinel, same
392 * technique as "integer64" above but a distinct value
393 * (PW_TYPE_MAX itself is already claimed by "integer64"):
394 * still >= PW_TYPE_MAX, so rc_dict_addattr() still rejects
395 * it for legacy/programmatic callers; only the bundled
396 * dictionary file parsed here can produce it. */
397 type = PW_TYPE_MAX + 1;
398 }
399 else if (strcmp (typestr, "text") == 0)
400 {
401 /* RFC 8044 SS3.1 "text" data type: UTF-8 human-readable text,
402 * distinct from "string"'s opaque octets -- see radcli2.h's
403 * RADCLI_TYPE_TEXT. Internal-only sentinel, same technique as
404 * "integer64"/"ipv4prefix" above but a distinct value again;
405 * still >= PW_TYPE_MAX, so rc_dict_addattr() still rejects it
406 * for legacy/programmatic callers; only the bundled
407 * dictionary file parsed here can produce it. */
408 type = PW_TYPE_MAX + 2;
409 }
410 else if (strcmp (typestr, "ifid") == 0)
411 {
412 /* RFC 8044 SS3.7 "ifid" data type: an 8-octet IPv6 interface
413 * identifier in network byte order -- see radcli2.h's
414 * RADCLI_TYPE_IFID. Internal-only sentinel, same technique as
415 * "integer64"/"ipv4prefix"/"text" above but a distinct value
416 * again; still >= PW_TYPE_MAX, so rc_dict_addattr() still
417 * rejects it for legacy/programmatic callers; only the
418 * bundled dictionary file parsed here can produce it. */
419 type = PW_TYPE_MAX + 3;
420 }
421 else
422 {
423 rc_log(LOG_ERR,
424 "rc_dict_init: invalid type on line %d of dictionary %s",
425 line_no, pfilename);
426 goto error;
427 }
428
429 dvend = NULL;
430 encrypt_type = 0;
431 has_tag_flag = 0;
432 gigawords_attrid = 0;
433 if (optstr[0] != '\0') {
434 char *cp1;
435 for (cp1 = optstr; cp1 != NULL; cp1 = cp) {
436 cp = strchr(cp1, ',');
437 if (cp != NULL) {
438 *cp = '\0';
439 cp++;
440 }
441
442 if (strcmp(cp1, "has_tag") == 0) {
443 /* RFC 2868 SS3.1 tunnel-attribute tagging. */
444 has_tag_flag = 1;
445 continue;
446 }
447
448 if (strncmp(cp1, "encrypt=", 8) == 0) {
449 /* FreeRADIUS's dictionaries name an encryption scheme
450 * by the attribute that first defines it, e.g.
451 * "encrypt=Tunnel-Password" for the RFC 2868 SS3.5 /
452 * RFC 2548 SS2.4.2-2.4.3 salt-encryption scheme, and
453 * "encrypt=User-Password" for the RFC 2865 SS5.2
454 * scheme (see share/dictionary/radius/dictionary.rfc2865
455 * and dictionary.rfc2868 upstream) -- matched here so a
456 * real FreeRADIUS dictionary loads unmodified. No
457 * other scheme name has a matching implementation
458 * yet (see radcli_avp_decode()/radcli_avp_encode()
459 * in lib/avp.c). */
460 if (strcmp(cp1 + 8, "User-Password") == 0) {
461 encrypt_type = 1;
462 continue;
463 }
464 if (strcmp(cp1 + 8, "Tunnel-Password") == 0) {
465 encrypt_type = 2;
466 continue;
467 }
468 rc_log(LOG_ERR,
469 "rc_dict_init: unsupported encrypt=%s on line %d "
470 "of dictionary %s (only encrypt=User-Password and "
471 "encrypt=Tunnel-Password are implemented)",
472 cp1 + 8, line_no, pfilename);
473 goto error;
474 }
475
476 if (strncmp(cp1, "gigawords=", 10) == 0) {
477 char *endp;
478 long v = strtol(cp1 + 10, &endp, 10);
479
480 /* RFC 2866 SS5.3/5.4 Octets + RFC 2869 SS5.1/5.2
481 * Gigawords pairing. The attribute id (within
482 * this line's own vendor scope, if any) of this
483 * attribute's Gigawords counterpart -- e.g.
484 * "gigawords=52" on the Acct-Input-Octets (42)
485 * line, naming Acct-Input-Gigawords (52). Not
486 * resolved to a struct radcli_dict_attr* here: the
487 * named counterpart's own ATTRIBUTE line may not
488 * have been parsed yet (e.g. it precedes this one
489 * in etc/dictionary) -- resolution happens lazily,
490 * by radcli_dict_attr_gigawords(), once the whole
491 * dictionary is loaded. */
492 if (*endp != '\0' || v <= 0 || v > 0xff) {
493 rc_log(LOG_ERR,
494 "rc_dict_init: invalid gigawords=%s on line %d "
495 "of dictionary %s", cp1 + 10, line_no, pfilename);
496 goto error;
497 }
498 gigawords_attrid = (uint32_t)v;
499 continue;
500 }
501
502 if (strncmp(cp1, "vendor=", 7) == 0)
503 cp1 += 7;
504 dvend = radcli_dict_vendor_by_name(rh, cp1);
505 if (dvend == NULL) {
506 rc_log(LOG_ERR,
507 "rc_dict_init: unknown Vendor-Id %s on line %d of "
508 "dictionary %s", cp1, line_no, pfilename);
509 goto error;
510 }
511 }
512 }
513
514 /* Create a new attribute */
515 if ((attr = calloc (1, sizeof (*attr))) == NULL)
516 {
517 rc_log(LOG_CRIT, "rc_dict_init: out of memory");
518 goto error;
519 }
520 strlcpy (attr->name, namestr, sizeof(attr->name));
521 dict2_lc(attr->name_key, sizeof(attr->name_key), attr->name);
522 attr->type = type;
523
524 if (dvend != NULL) {
525 attr->value = RADCLI_VENDOR_ATTR_SET(value, dvend->pec);
526 } else {
527 attr->value = RADCLI_VENDOR_ATTR_SET(value, attr_vendorspec);
528 }
529
530 {
531 struct radcli_dict_attr *existing;
532 int cmp = dict2_check_attr(rh, attr, pfilename, line_no, &existing);
533
534 if (cmp < 0) {
535 free(attr);
536 goto error;
537 } else if (cmp > 0) {
538 /* Identical redefinition: keep the already-loaded
539 * entry, whose ->value the side tables below need,
540 * and drop the freshly-parsed duplicate. */
541 free(attr);
542 attr = existing;
543 } else {
544 HASH_ADD(hh_name, rh->dict->attrs_by_name, name_key, strlen(attr->name_key), attr);
545 HASH_ADD(hh_id, rh->dict->attrs_by_id, value, sizeof(attr->value), attr);
546 }
547 }
548
549 if (encrypt_type != 0 || has_tag_flag != 0) {
550 struct radcli_dict_flags *fl = calloc(1, sizeof(*fl));
551
552 if (fl == NULL) {
553 rc_log(LOG_CRIT, "rc_dict_init: out of memory");
554 goto error;
555 }
556 fl->attr_id = attr->value;
557 fl->encrypt_type = encrypt_type;
558 fl->has_tag = has_tag_flag;
559 HASH_ADD(hh, rh->dict->flags_by_attr_id, attr_id, sizeof(fl->attr_id), fl);
560 }
561
562 if (gigawords_attrid != 0) {
563 struct radcli_dict_gigawords *gw = calloc(1, sizeof(*gw));
564
565 if (gw == NULL) {
566 rc_log(LOG_CRIT, "rc_dict_init: out of memory");
567 goto error;
568 }
569 gw->attr_id = attr->value;
570 /* Same vendor scope as this ATTRIBUTE line itself -- a
571 * VSA's gigawords= counterpart is another sub-attribute of
572 * the same vendor, not a standard attribute. */
573 gw->gigawords_attrid = RADCLI_VENDOR_ATTR_SET(gigawords_attrid,
574 VENDOR(attr->value));
575 HASH_ADD(hh, rh->dict->gigawords_by_attr_id, attr_id, sizeof(gw->attr_id), gw);
576 }
577 }
578 else if (strcmp (tok, "VALUE") == 0)
579 {
580 char *attr_t, *name_t, *val_t;
581
582 /* Read the VALUE line */
583 attr_t = strtok_r(NULL, " \t\r\n", &saveptr);
584 name_t = strtok_r(NULL, " \t\r\n", &saveptr);
585 val_t = strtok_r(NULL, " \t\r\n", &saveptr);
586 if (attr_t == NULL || name_t == NULL || val_t == NULL)
587 {
588 rc_log(LOG_ERR,
589 "rc_dict_init: invalid value entry on line %d of "
590 "dictionary %s", line_no, pfilename);
591 goto error;
592 }
593 /*
594 * Validate all entries. Length checks must run against the
595 * original tokens, before strlcpy() truncates them below.
596 */
597 if (strlen (attr_t) > RC_NAME_LENGTH)
598 {
599 rc_log(LOG_ERR,
600 "rc_dict_init: invalid attribute length on line %d of "
601 "dictionary %s", line_no, pfilename);
602 goto error;
603 }
604
605 if (strlen (name_t) > RC_NAME_LENGTH)
606 {
607 rc_log(LOG_ERR,
608 "rc_dict_init: invalid name length on line %d of "
609 "dictionary %s", line_no, pfilename);
610 goto error;
611 }
612
613 strlcpy(attrstr, attr_t, sizeof(attrstr));
614 strlcpy(namestr, name_t, sizeof(namestr));
615 strlcpy(valstr, val_t, sizeof(valstr));
616
617 if (!is_unsigned_decimal (valstr))
618 {
619 rc_log(LOG_ERR,
620 "rc_dict_init: invalid value on line %d of dictionary %s",
621 line_no, pfilename);
622 goto error;
623 }
624 value = atoi (valstr);
625
626 /* Create a new VALUE entry */
627 if ((dval = calloc (1, sizeof (*dval))) == NULL)
628 {
629 rc_log(LOG_CRIT, "rc_dict_init: out of memory");
630 goto error;
631 }
632 strlcpy (dval->attrname, attrstr, sizeof(dval->attrname));
633 strlcpy (dval->name, namestr, sizeof(dval->name));
634 dict2_lc(dval->name_key, sizeof(dval->name_key), dval->name);
635 dict2_lc(dval->attr_key.attrname_key, sizeof(dval->attr_key.attrname_key), dval->attrname);
636 dict2_lc(dval->attr_name_key.attrname_key, sizeof(dval->attr_name_key.attrname_key), dval->attrname);
637 dict2_lc(dval->attr_name_key.name_key, sizeof(dval->attr_name_key.name_key), dval->name);
638 dval->value = value;
639 dval->attr_key.value = value;
640
641 {
642 struct radcli_dict_value *existing;
643 int cmp = dict2_check_value(rh, dval, pfilename, line_no, &existing);
644
645 if (cmp < 0) {
646 free(dval);
647 goto error;
648 } else if (cmp > 0) {
649 free(dval);
650 } else {
651 HASH_ADD(hh_name, rh->dict->values_by_name, name_key, strlen(dval->name_key), dval);
652 HASH_ADD(hh_attr, rh->dict->values_by_attr, attr_key, sizeof(dval->attr_key), dval);
653 HASH_ADD(hh_attr_name, rh->dict->values_by_attr_name, attr_name_key, sizeof(dval->attr_name_key), dval);
654 }
655 }
656 }
657 else if ((filename != NULL) &&
658 (strcmp (tok, "$INCLUDE") == 0))
659 {
660 char *path_t;
661
662 /* Read the $INCLUDE line. The path token is used directly
663 * (strtok_r null-terminates it in place in buffer, which is
664 * large enough for the longest path this parser can see),
665 * copied into ifilename (RC_MAX(1024, PATH_MAX)-sized) since
666 * $INCLUDE paths can legitimately be longer than 63 chars. */
667 path_t = strtok_r(NULL, " \t\r\n", &saveptr);
668 if (path_t == NULL)
669 {
670 rc_log(LOG_ERR,
671 "rc_dict_init: invalid include entry on line %d of "
672 "dictionary %s", line_no, pfilename);
673 goto error;
674 }
675 strlcpy(ifilename, path_t, sizeof(ifilename));
676 /* Append directory if necessary */
677 if (path_t[0] != '/') {
678 cp = strrchr(filename, '/');
679 if (cp != NULL) {
680 *cp = '\0';
681 strlcpy(ifilename, filename, sizeof(ifilename));
682 strlcat(ifilename, "/", sizeof(ifilename));
683 strlcat(ifilename, path_t, sizeof(ifilename));
684 *cp = '/';
685 }
686 }
687 if (radcli2_priv_read_dictionary(rh, ifilename) < 0)
688 {
689 goto error;
690 }
691 }
692 else if (strcmp (tok, "END-VENDOR") == 0)
693 {
694 attr_vendorspec = 0;
695 }
696 else if (strcmp (tok, "BEGIN-VENDOR") == 0)
697 {
698 struct radcli_dict_vendor *v;
699 char *name_t;
700
701 /* Read the vendor name */
702 name_t = strtok_r(NULL, " \t\r\n", &saveptr);
703 if (name_t == NULL)
704 {
705 rc_log(LOG_ERR,
706 "rc_dict_init: invalid Vendor-Id on line %d of "
707 "dictionary %s", line_no, pfilename);
708 goto error;
709 }
710
711 v = radcli_dict_vendor_by_name(rh, name_t);
712 if (v == NULL) {
713 rc_log(LOG_ERR,
714 "rc_dict_init: unknown Vendor %s on line %d of "
715 "dictionary %s", name_t, line_no, pfilename);
716 goto error;
717 }
718
719 attr_vendorspec = v->pec;
720 }
721 else if (strcmp (tok, "VENDOR") == 0)
722 {
723 char *name_t, *val_t;
724
725 /* Read the VENDOR line */
726 name_t = strtok_r(NULL, " \t\r\n", &saveptr);
727 val_t = strtok_r(NULL, " \t\r\n", &saveptr);
728 if (name_t == NULL || val_t == NULL)
729 {
730 rc_log(LOG_ERR,
731 "rc_dict_init: invalid Vendor-Id on line %d of "
732 "dictionary %s", line_no, pfilename);
733 goto error;
734 }
735 /* Validate all entries against the original tokens, before
736 * strlcpy() truncates them below. */
737 if (strlen (name_t) > RC_NAME_LENGTH)
738 {
739 rc_log(LOG_ERR,
740 "rc_dict_init: invalid attribute length on line %d of "
741 "dictionary %s", line_no, pfilename);
742 goto error;
743 }
744
745 strlcpy(attrstr, name_t, sizeof(attrstr));
746 strlcpy(valstr, val_t, sizeof(valstr));
747
748 if (!is_unsigned_decimal (valstr))
749 {
750 rc_log(LOG_ERR,
751 "rc_dict_init: invalid Vendor-Id on line %d of "
752 "dictionary %s", line_no, pfilename);
753 goto error;
754 }
755 value = atoi (valstr);
756
757 /* Create a new VENDOR entry */
758 dvend = calloc(1, sizeof(*dvend));
759 if (dvend == NULL)
760 {
761 rc_log(LOG_CRIT, "rc_dict_init: out of memory");
762 goto error;
763 }
764 strlcpy (dvend->name, attrstr, sizeof(dvend->name));
765 dict2_lc(dvend->name_key, sizeof(dvend->name_key), dvend->name);
766 dvend->pec = value;
767
768 {
769 struct radcli_dict_vendor *existing;
770 int cmp = dict2_check_vendor(rh, dvend, pfilename, line_no, &existing);
771
772 if (cmp < 0) {
773 free(dvend);
774 goto error;
775 } else if (cmp > 0) {
776 free(dvend);
777 } else {
778 HASH_ADD(hh_name, rh->dict->vendors_by_name, name_key, strlen(dvend->name_key), dvend);
779 HASH_ADD(hh_pec, rh->dict->vendors_by_pec, pec, sizeof(dvend->pec), dvend);
780 }
781 }
782 }
783 }
784 free(buffer);
785 return 0;
786
787error:
788 free(buffer);
789 return -1;
790}
791
792/*- Initialize the dictionary from a file.
793 *
794 * @param rh a handle to parsed configuration.
795 * @param filename the name of the dictionary file.
796 * @return 0 on success, -1 on failure.
797 -*/
798int radcli2_priv_read_dictionary (rc_handle *rh, char const *filename)
799{
800 FILE *dictfd;
801 int ret_val = 0;
802
803 if (rh->first_dict_read != NULL && strcmp(filename, rh->first_dict_read) == 0)
804 return 0;
805
806 if (dict2_ensure(rh) < 0)
807 return -1;
808
809 if ((dictfd = fopen (filename, "r")) == NULL)
810 {
811 rc_log(LOG_ERR, "radcli2_priv_read_dictionary couldn't open dictionary %s: %s",
812 filename, strerror(errno));
813 return -1;
814 }
815
816 ret_val = dict2_parse(rh, dictfd, filename);
817
818 fclose (dictfd);
819
820 if (rh->first_dict_read == NULL)
821 rh->first_dict_read = strdup(filename);
822
823 return ret_val;
824}
825
826/*- Initialize the dictionary from an in-memory buffer.
827 *
828 * @param rh a handle to parsed configuration.
829 * @param buf buffer holding dictionary text.
830 * @param size buf's length in bytes.
831 * @return 0 on success, -1 on failure.
832 -*/
833int radcli2_priv_read_dictionary_from_buffer (rc_handle *rh, char const *buf, size_t size)
834{
835 FILE *dictfd;
836 int ret_val = 0;
837
838 if (dict2_ensure(rh) < 0)
839 return -1;
840
841 if ((dictfd = fmemopen ((void *)buf, size, "r")) == NULL)
842 {
843 rc_log(LOG_ERR, "radcli2_priv_read_dictionary_from_buffer failed to read "
844 "input buffer %s", strerror(errno));
845 return -1;
846 }
847
848 ret_val = dict2_parse(rh, dictfd, NULL);
849
850 fclose (dictfd);
851
852 return ret_val;
853}
854
855/*- Add an attribute to rh's dictionary programmatically. A re-add
856 * identical to an already-loaded attribute of the same name returns that
857 * existing attribute rather than adding a duplicate; a re-add of the same
858 * name with a conflicting definition fails, after a warning naming both
859 * definitions.
860 *
861 * @param rh a handle to parsed configuration.
862 * @param name the attribute name.
863 * @param value the attribute's numeric ID.
864 * @param type the attribute's wire type (a PW_TYPE_* value).
865 * @param vendorspec the vendor PEN, or 0 for a standard attribute.
866 * @return the added (or already-existing, identical) attribute, or NULL
867 * on failure or conflict.
868 -*/
869struct radcli_dict_attr *radcli_dict_attr_add(rc_handle *rh, const char *name,
870 uint32_t value, int type, uint32_t vendorspec)
871{
872 struct radcli_dict_attr *attr;
873
874 if (strlen(name) > RC_NAME_LENGTH)
875 {
876 rc_log(LOG_ERR, "rc_dict_addattr: invalid attribute length");
877 return NULL;
878 }
879
880 if (type < 0 || type >= PW_TYPE_MAX)
881 {
882 rc_log(LOG_ERR, "rc_dict_addattr: invalid attribute type");
883 return NULL;
884 }
885
886 if (dict2_ensure(rh) < 0)
887 return NULL;
888
889 if ((attr = calloc(1, sizeof(*attr))) == NULL)
890 {
891 rc_log(LOG_CRIT, "rc_dict_addattr: out of memory");
892 return NULL;
893 }
894
895 strlcpy(attr->name, name, sizeof(attr->name));
896 dict2_lc(attr->name_key, sizeof(attr->name_key), attr->name);
897 attr->value = RADCLI_VENDOR_ATTR_SET(value, vendorspec);
898 attr->type = type;
899
900 {
901 struct radcli_dict_attr *existing;
902 int cmp = dict2_check_attr(rh, attr, NULL, 0, &existing);
903
904 if (cmp < 0) {
905 free(attr);
906 return NULL;
907 } else if (cmp > 0) {
908 free(attr);
909 return existing;
910 }
911 }
912
913 HASH_ADD(hh_name, rh->dict->attrs_by_name, name_key, strlen(attr->name_key), attr);
914 HASH_ADD(hh_id, rh->dict->attrs_by_id, value, sizeof(attr->value), attr);
915 return attr;
916}
917
918/*- Add a VALUE to rh's dictionary programmatically. A re-add identical to
919 * an already-loaded value of the same attribute+number returns that
920 * existing value rather than adding a duplicate; a re-add of the same
921 * attribute+number with a conflicting name fails, after a warning naming
922 * both definitions.
923 *
924 * @param rh a handle to parsed configuration.
925 * @param attrname the attribute name the VALUE belongs to.
926 * @param name the VALUE's name.
927 * @param value the VALUE's numeric value.
928 * @return the added (or already-existing, identical) VALUE, or NULL on
929 * failure or conflict.
930 -*/
931struct radcli_dict_value *radcli_dict_value_add(rc_handle *rh, const char *attrname,
932 const char *name, uint32_t value)
933{
934 struct radcli_dict_value *dval;
935
936 if (strlen(attrname) > RC_NAME_LENGTH)
937 {
938 rc_log(LOG_ERR, "rc_dict_addval: invalid attribute length");
939 return NULL;
940 }
941
942 if (strlen(name) > RC_NAME_LENGTH)
943 {
944 rc_log(LOG_ERR, "rc_dict_addval: invalid name length");
945 return NULL;
946 }
947
948 if (dict2_ensure(rh) < 0)
949 return NULL;
950
951 if ((dval = calloc(1, sizeof(*dval))) == NULL)
952 {
953 rc_log(LOG_CRIT, "rc_dict_addval: out of memory");
954 return NULL;
955 }
956 strlcpy(dval->attrname, attrname, sizeof(dval->attrname));
957 strlcpy(dval->name, name, sizeof(dval->name));
958 dict2_lc(dval->name_key, sizeof(dval->name_key), dval->name);
959 dict2_lc(dval->attr_key.attrname_key, sizeof(dval->attr_key.attrname_key), dval->attrname);
960 dict2_lc(dval->attr_name_key.attrname_key, sizeof(dval->attr_name_key.attrname_key), dval->attrname);
961 dict2_lc(dval->attr_name_key.name_key, sizeof(dval->attr_name_key.name_key), dval->name);
962 dval->value = value;
963 dval->attr_key.value = value;
964
965 {
966 struct radcli_dict_value *existing;
967 int cmp = dict2_check_value(rh, dval, NULL, 0, &existing);
968
969 if (cmp < 0) {
970 free(dval);
971 return NULL;
972 } else if (cmp > 0) {
973 free(dval);
974 return existing;
975 }
976 }
977
978 HASH_ADD(hh_name, rh->dict->values_by_name, name_key, strlen(dval->name_key), dval);
979 HASH_ADD(hh_attr, rh->dict->values_by_attr, attr_key, sizeof(dval->attr_key), dval);
980 HASH_ADD(hh_attr_name, rh->dict->values_by_attr_name, attr_name_key, sizeof(dval->attr_name_key), dval);
981 return dval;
982}
983
984/*- Add a vendor to rh's dictionary programmatically. A re-add identical
985 * to an already-loaded vendor of the same name returns that existing
986 * vendor rather than adding a duplicate; a re-add of the same name with a
987 * conflicting Vendor-Id fails, after a warning naming both definitions.
988 *
989 * @param rh a handle to parsed configuration.
990 * @param name the vendor name.
991 * @param vendorspec the vendor's PEN.
992 * @return the added (or already-existing, identical) vendor, or NULL on
993 * failure or conflict.
994 -*/
995struct radcli_dict_vendor *radcli_dict_vendor_add(rc_handle *rh, const char *name,
996 uint32_t vendorspec)
997{
998 struct radcli_dict_vendor *dvend;
999
1000 if (strlen(name) > RC_NAME_LENGTH)
1001 {
1002 rc_log(LOG_ERR, "rc_dict_addvend: invalid vendor name length");
1003 return NULL;
1004 }
1005
1006 if (dict2_ensure(rh) < 0)
1007 return NULL;
1008
1009 if ((dvend = calloc(1, sizeof(*dvend))) == NULL)
1010 {
1011 rc_log(LOG_CRIT, "rc_dict_addvend: out of memory");
1012 return NULL;
1013 }
1014 strlcpy(dvend->name, name, sizeof(dvend->name));
1015 dict2_lc(dvend->name_key, sizeof(dvend->name_key), dvend->name);
1016 dvend->pec = vendorspec;
1017
1018 {
1019 struct radcli_dict_vendor *existing;
1020 int cmp = dict2_check_vendor(rh, dvend, NULL, 0, &existing);
1021
1022 if (cmp < 0) {
1023 free(dvend);
1024 return NULL;
1025 } else if (cmp > 0) {
1026 free(dvend);
1027 return existing;
1028 }
1029 }
1030
1031 HASH_ADD(hh_name, rh->dict->vendors_by_name, name_key, strlen(dvend->name_key), dvend);
1032 HASH_ADD(hh_pec, rh->dict->vendors_by_pec, pec, sizeof(dvend->pec), dvend);
1033 return dvend;
1034}