The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_digest.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: dfbb50bbb97289a16bf43d165357505cf370beba $
19 * @file rlm_digest.c
20 * @brief Handles SIP digest authentication requests from Cisco SIP servers.
21 *
22 * @copyright 2002,2006 The FreeRADIUS server project
23 * @copyright 2002 Alan DeKok (aland@freeradius.org)
24 */
25RCSID("$Id: dfbb50bbb97289a16bf43d165357505cf370beba $")
26
27#include <freeradius-devel/server/base.h>
28#include <freeradius-devel/server/module_rlm.h>
29
30#include <freeradius-devel/util/base16.h>
31#include <freeradius-devel/util/md5.h>
32
36
38static fr_dict_t const *dict_radius;
39
42 { .out = &dict_freeradius, .proto = "freeradius" },
43 { .out = &dict_radius, .proto = "radius" },
44 { NULL }
45};
46
49
63
66 { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
67 { .out = &attr_cleartext_password, .name = "Password.Cleartext", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
68
69 { .out = &attr_digest_response, .name = "Digest-Response", .type = FR_TYPE_STRING, .dict = &dict_radius },
70 { .out = &attr_digest_attributes, .name = "Digest-Attributes", .type = FR_TYPE_TLV, .dict = &dict_radius },
71 { .out = &attr_digest_algorithm, .name = "Digest-Attributes.Algorithm", .type = FR_TYPE_STRING, .dict = &dict_radius },
72 { .out = &attr_digest_body_digest, .name = "Digest-Attributes.Body-Digest", .type = FR_TYPE_STRING, .dict = &dict_radius },
73 { .out = &attr_digest_cnonce, .name = "Digest-Attributes.Cnonce", .type = FR_TYPE_STRING, .dict = &dict_radius },
74 { .out = &attr_digest_ha1, .name = "Digest-Attributes.HA1", .type = FR_TYPE_STRING, .dict = &dict_radius },
75 { .out = &attr_digest_method, .name = "Digest-Attributes.Method", .type = FR_TYPE_STRING, .dict = &dict_radius },
76 { .out = &attr_digest_nonce, .name = "Digest-Attributes.Nonce", .type = FR_TYPE_STRING, .dict = &dict_radius },
77 { .out = &attr_digest_nonce_count, .name = "Digest-Attributes.Nonce-Count", .type = FR_TYPE_STRING, .dict = &dict_radius },
78 { .out = &attr_digest_qop, .name = "Digest-Attributes.Qop", .type = FR_TYPE_STRING, .dict = &dict_radius },
79 { .out = &attr_digest_realm, .name = "Digest-Attributes.Realm", .type = FR_TYPE_STRING, .dict = &dict_radius },
80 { .out = &attr_digest_uri, .name = "Digest-Attributes.Uri", .type = FR_TYPE_STRING, .dict = &dict_radius },
81 { .out = &attr_digest_user_name, .name = "Digest-Attributes.User-Name", .type = FR_TYPE_STRING, .dict = &dict_radius },
82
83 { NULL }
84};
85
86static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
87{
88 rlm_digest_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_digest_t);
90
91 /*
92 * Find the first attribute which is parented by Digest-Attributes.
93 */
94 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_digest_attributes);
96
97 if (!inst->auth_type) {
98 WARN("No 'authenticate %s {...}' section or 'Auth-Type = %s' set. Cannot setup Digest authentication",
99 mctx->mi->name, mctx->mi->name);
101 }
102
103 /*
104 * Everything's OK, add a digest authentication type.
105 */
107
109}
110
111/*
112 * Perform all of the wondrous variants of digest authentication.
113 */
114static unlang_action_t CC_HINT(nonnull) mod_authenticate(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
115{
116 size_t a1_len, a2_len, kd_len;
117 uint8_t a1[(FR_MAX_STRING_LEN + 1) * 5]; /* can be 5 attributes */
118 uint8_t a2[(FR_MAX_STRING_LEN + 1) * 3]; /* can be 3 attributes */
119 uint8_t kd[(FR_MAX_STRING_LEN + 1) * 5];
120 uint8_t hash[16]; /* MD5 output */
121 fr_pair_t *vp, *passwd, *algo;
122 fr_pair_t *qop, *nonce;
123 fr_pair_list_t *list;
124
125 /*
126 * We require access to the plain-text password, or to the
127 * Digest-Attributes.HA1 parameter.
128 */
129 passwd = fr_pair_find_by_da_nested(&request->control_pairs, NULL, attr_digest_ha1);
130 if (passwd) {
131 if (passwd->vp_length != 32) {
132 REDEBUG("Digest-Attributes.HA1 has invalid length, authentication failed");
134 }
135 } else {
136 passwd = fr_pair_find_by_da_nested(&request->control_pairs, NULL, attr_cleartext_password);
137 }
138 if (!passwd) {
139 REDEBUG("Password.Cleartext or Digest-Attributes.HA1 is required for authentication");
141 }
142
143 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_digest_attributes);
144 if (!vp) {
145 REDEBUG("Digest-Attributes is required for authentication");
147 }
148 list = &vp->vp_group;
149
150 /*
151 * We require access to the Digest-Attributes.Nonce
152 */
153 nonce = fr_pair_find_by_da_nested(list, NULL, attr_digest_nonce);
154 if (!nonce) {
155 REDEBUG("No Digest-Attributes.Nonce: Cannot perform Digest authentication");
157 }
158
159 /*
160 * A1 = Digest-Attributes.User-Name ":" Realm ":" Password
161 */
163 if (!vp) {
164 REDEBUG("No Digest-Attributes.User-Name: Cannot perform Digest authentication");
166 }
167 memcpy(&a1[0], vp->vp_octets, vp->vp_length);
168 a1_len = vp->vp_length;
169
170 a1[a1_len] = ':';
171 a1_len++;
172
174 if (!vp) {
175 REDEBUG("No Digest-Attributes.Attributes.Realm: Cannot perform Digest authentication");
177 }
178 memcpy(&a1[a1_len], vp->vp_octets, vp->vp_length);
179 a1_len += vp->vp_length;
180
181 a1[a1_len] = ':';
182 a1_len++;
183
184 if (passwd->da == attr_cleartext_password) {
185 memcpy(&a1[a1_len], passwd->vp_octets, passwd->vp_length);
186 a1_len += passwd->vp_length;
187 a1[a1_len] = '\0';
188 RDEBUG2("A1 = %s", a1);
189 } else {
190 a1[a1_len] = '\0';
191 RDEBUG2("A1 = %s (using Digest-Attributes.HA1)", a1);
192 a1_len = 16;
193 }
194
195 /*
196 * See which variant we calculate.
197 * Assume MD5 if no Digest-Algorithm attribute received
198 */
200 if ((!algo) ||
201 (strcasecmp(algo->vp_strvalue, "MD5") == 0)) {
202 /*
203 * Set A1 to Digest-Attributes.HA1 if no User-Password found
204 */
205 if (passwd->da == attr_digest_ha1) {
206 if (fr_base16_decode(NULL, &FR_DBUFF_TMP(&a1[0], sizeof(a1)),
207 &FR_SBUFF_IN(passwd->vp_strvalue, passwd->vp_length), false) != 16) {
208 RDEBUG2("Invalid text in Digest-Attributes.HA1");
210 }
211 }
212
213 } else if (strcasecmp(algo->vp_strvalue, "MD5-sess") == 0) {
214 /*
215 * K1 = H(A1) : Digest-Attributes.Nonce ... : H(A2)
216 *
217 * If we find Digest-Attributes.HA1, we assume it contains
218 * H(A1).
219 */
220 if (passwd->da == attr_cleartext_password) {
221 fr_md5_calc(hash, &a1[0], a1_len);
222 fr_base16_encode(&FR_SBUFF_OUT((char *) &a1[0], 32 + 1), &FR_DBUFF_TMP(hash, 16));
223 } else { /* MUST be Digest-Attributes.HA1 */
224 memcpy(&a1[0], passwd->vp_strvalue, 32);
225 }
226 a1_len = 32;
227
228 a1[a1_len] = ':';
229 a1_len++;
230
231 /*
232 * Tack on the Digest-Attributes.Nonce. Length must be even
233 */
234 if ((nonce->vp_length & 1) != 0) {
235 REDEBUG("Received Digest-Attributes.Nonce hex string with invalid length: Cannot perform Digest authentication");
237 }
238 memcpy(&a1[a1_len], nonce->vp_octets, nonce->vp_length);
239 a1_len += nonce->vp_length;
240
241 a1[a1_len] = ':';
242 a1_len++;
243
245 if (!vp) {
246 REDEBUG("No Digest-Attributes.CNonce: Cannot perform Digest authentication");
248 }
249
250 /*
251 * Digest-Attributes.CNonce length must be even
252 */
253 if ((vp->vp_length & 1) != 0) {
254 REDEBUG("Received Digest-Attributes.CNonce hex string with invalid length: Cannot perform Digest authentication");
256 }
257 memcpy(&a1[a1_len], vp->vp_octets, vp->vp_length);
258 a1_len += vp->vp_length;
259
260 } else if (strcasecmp(algo->vp_strvalue, "MD5") != 0) {
261 /*
262 * We check for "MD5-sess" and "MD5".
263 * Anything else is an error.
264 */
265 REDEBUG("%pP - Unknown Digest-Attributes.Algorithm: Cannot perform Digest authentication", vp);
267 }
268
269 /*
270 * A2 = Digest-Attributes.Method ":" Digest-Attributes.URI
271 */
273 if (!vp) {
274 REDEBUG("No Digest-Attributes.Method: Cannot perform Digest authentication");
276 }
277 memcpy(&a2[0], vp->vp_octets, vp->vp_length);
278 a2_len = vp->vp_length;
279
280 a2[a2_len] = ':';
281 a2_len++;
282
284 if (!vp) {
285 REDEBUG("No Digest-Attributes.URI: Cannot perform Digest authentication");
287 }
288 memcpy(&a2[a2_len], vp->vp_octets, vp->vp_length);
289 a2_len += vp->vp_length;
290
291 /*
292 * QOP is "auth-int", tack on ": Digest-Attributes.Body-Digest"
293 */
295 if (qop) {
296 if (strcasecmp(qop->vp_strvalue, "auth-int") == 0) {
297 fr_pair_t *body;
298
299 /*
300 * Add in Digest-Attributes.Body-Digest
301 */
302 a2[a2_len] = ':';
303 a2_len++;
304
305 /*
306 * Must be a hex representation of an MD5 digest.
307 */
309 if (!body) {
310 REDEBUG("No Digest-Attributes.Body-Digest: Cannot perform Digest authentication");
312 }
313
314 if ((a2_len + body->vp_length) > sizeof(a2)) {
315 REDEBUG("Digest-Attributes.Body-Digest is too long");
317 }
318
319 memcpy(a2 + a2_len, body->vp_octets, body->vp_length);
320 a2_len += body->vp_length;
321
322 } else if (strcasecmp(qop->vp_strvalue, "auth") != 0) {
323 REDEBUG("%pP - Unknown value: Cannot perform Digest authentication", qop);
325 }
326 }
327
328 a2[a2_len] = '\0';
329 RDEBUG2("A2 = %s", a2);
330
331 /*
332 * KD = H(A1) : Digest-Attributes.Nonce ... : H(A2).
333 * Compute MD5 if Digest-Attributes.Algorithm == "MD5-Sess",
334 * or if we found a User-Password.
335 */
336 if (((algo != NULL) && (strcasecmp(algo->vp_strvalue, "MD5-Sess") == 0)) ||
337 (passwd->da == attr_cleartext_password)) {
338 a1[a1_len] = '\0';
339 fr_md5_calc(&hash[0], &a1[0], a1_len);
340 } else {
341 memcpy(&hash[0], &a1[0], a1_len);
342 }
343 fr_base16_encode(&FR_SBUFF_OUT((char *) kd, (sizeof(hash) * 2) + 1), &FR_DBUFF_TMP(hash, sizeof(hash)));
344
345 RHEXDUMP_INLINE3(hash, sizeof(hash), "H(A1)");
346
347 kd_len = 32;
348
349 kd[kd_len] = ':';
350 kd_len++;
351
352 memcpy(&kd[kd_len], nonce->vp_octets, nonce->vp_length);
353 kd_len += nonce->vp_length;
354
355 /*
356 * No QOP defined. Do RFC 2069 compatibility.
357 */
358 if (!qop) {
359 /*
360 * Do nothing here.
361 */
362
363 } else { /* Digest-Attributes.QOP MUST be "auth" or "auth-int" */
364 /*
365 * Tack on ":" Digest-Attributes.Nonce-Count ":" Digest-Attributes.CNonce
366 * ":" Digest-Attributes.QOP
367 */
368 kd[kd_len] = ':';
369 kd_len++;
370
372 if (!vp) {
373 REDEBUG("No Digest-Attributes.Nonce-Count: Cannot perform Digest authentication");
375 }
376 memcpy(&kd[kd_len], vp->vp_octets, vp->vp_length);
377 kd_len += vp->vp_length;
378
379 kd[kd_len] = ':';
380 kd_len++;
381
383 if (!vp) {
384 REDEBUG("No Digest-Attributes.CNonce: Cannot perform Digest authentication");
386 }
387 memcpy(&kd[kd_len], vp->vp_octets, vp->vp_length);
388 kd_len += vp->vp_length;
389
390 kd[kd_len] = ':';
391 kd_len++;
392
393 memcpy(&kd[kd_len], qop->vp_octets, qop->vp_length);
394 kd_len += qop->vp_length;
395 }
396
397 /*
398 * Tack on ":" H(A2)
399 */
400 kd[kd_len] = ':';
401 kd_len++;
402
403 fr_md5_calc(&hash[0], &a2[0], a2_len);
404
405 fr_base16_encode(&FR_SBUFF_OUT((char *) kd + kd_len, (sizeof(hash) * 2) + 1), &FR_DBUFF_TMP(hash, sizeof(hash)));
406
407 RHEXDUMP_INLINE3(hash, sizeof(hash), "H(A2)");
408
409 kd_len += 32;
410
411 kd[kd_len] = 0;
412
413 RDEBUG2("KD = %s\n", &kd[0]);
414
415 /*
416 * Take the hash of KD.
417 */
418 fr_md5_calc(&hash[0], &kd[0], kd_len);
419 memcpy(&kd[0], &hash[0], 16);
420
421 /*
422 * Get the binary value of Digest-Response. This isn't
423 * inside of the Digest-Attributes group.
424 */
425 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_digest_response);
426 if (!vp) {
427 REDEBUG("No Digest-Response attribute in the request. Cannot perform digest authentication");
429 }
430
431 if (fr_base16_decode(NULL, &FR_DBUFF_TMP(&hash[0], sizeof(hash)),
432 &FR_SBUFF_IN(vp->vp_strvalue, vp->vp_length), false) != (ssize_t)(vp->vp_length >> 1)) {
433 RDEBUG2("Invalid text in Digest-Response");
435 }
436
437 RDEBUG3("Comparing hashes, received: %pV, calculated: %pH", &vp->data, fr_box_octets(kd, 16));
438
439 /*
440 * And finally, compare the digest in the packet with KD.
441 */
442 if (memcmp(&kd[0], &hash[0], 16) == 0) RETURN_MODULE_OK;
443
444 REDEBUG("FAILED authentication");
446}
447
448
449/*
450 * Create instance for our module. Allocate space for
451 * instance structure and read configuration parameters
452 */
453static int mod_instantiate(module_inst_ctx_t const *mctx)
454{
455 rlm_digest_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_digest_t);
456
457 inst->auth_type = fr_dict_enum_by_name(attr_auth_type, mctx->mi->name, -1);
458 if (!inst->auth_type) {
459 WARN("Failed to find 'authenticate %s {...}' section. Digest authentication will likely not work",
460 mctx->mi->name);
461 }
462
463 return 0;
464}
465
466/*
467 * The module name should be the only globally exported symbol.
468 * That is, everything else should be 'static'.
469 *
470 * If the module needs to temporarily modify it's instantiation
471 * data, the type should be changed to MODULE_TYPE_THREAD_UNSAFE.
472 * The server will then take care of ensuring that the module
473 * is single-threaded.
474 */
477 .common = {
478 .magic = MODULE_MAGIC_INIT,
479 .name = "digest",
480 .inst_size = sizeof(rlm_digest_t),
482 },
483 .method_group = {
484 .bindings = (module_method_binding_t[]){
485 { .section = SECTION_NAME("authenticate", CF_IDENT_ANY), .method = mod_authenticate },
486 { .section = SECTION_NAME("recv", "Access-Request"), .method = mod_authorize },
488 }
489 }
490};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
#define fr_base16_encode(_out, _in)
Definition base16.h:57
#define fr_base16_decode(_err, _out, _in, _no_trailing)
Definition base16.h:95
#define RCSID(id)
Definition build.h:483
#define UNUSED
Definition build.h:315
#define CF_IDENT_ANY
Definition cf_util.h:78
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:514
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:268
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:281
fr_dict_enum_value_t * fr_dict_enum_by_name(fr_dict_attr_t const *da, char const *name, ssize_t len)
Definition dict_util.c:3395
Specifies an attribute which must be present for the module to function.
Definition dict.h:267
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:280
Value of an enumerated attribute.
Definition dict.h:227
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
#define RDEBUG3(fmt,...)
Definition log.h:343
#define RHEXDUMP_INLINE3(_data, _len, _fmt,...)
Definition log.h:686
@ FR_TYPE_TLV
Contains nested attributes.
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
long int ssize_t
void fr_md5_calc(uint8_t out[static MD5_DIGEST_LENGTH], uint8_t const *in, size_t inlen)
Perform a single digest operation on a single input buffer.
unsigned char uint8_t
int strcasecmp(char *s1, char *s2)
Definition missing.c:66
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
bool module_rlm_section_type_set(request_t *request, fr_dict_attr_t const *type_da, fr_dict_enum_value_t const *enumv)
Set the next section type if it's not already set.
Definition module_rlm.c:427
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
fr_pair_t * fr_pair_find_by_da_nested(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find a pair with a matching fr_dict_attr_t, by walking the nested fr_dict_attr_t tree.
Definition pair.c:770
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:693
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define WARN(fmt,...)
Definition radclient.h:47
#define RETURN_MODULE_REJECT
Definition rcode.h:55
#define RETURN_MODULE_NOOP
Definition rcode.h:62
#define RETURN_MODULE_INVALID
Definition rcode.h:59
#define RETURN_MODULE_OK
Definition rcode.h:57
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
static const fr_dict_attr_t * attr_digest_user_name
Definition rlm_digest.c:62
static const fr_dict_attr_t * attr_digest_body_digest
Definition rlm_digest.c:53
static const fr_dict_attr_t * attr_digest_ha1
Definition rlm_digest.c:55
static const fr_dict_attr_t * attr_digest_method
Definition rlm_digest.c:56
static const fr_dict_attr_t * attr_digest_qop
Definition rlm_digest.c:59
static const fr_dict_attr_t * attr_cleartext_password
Definition rlm_digest.c:48
static const fr_dict_attr_t * attr_digest_attributes
Definition rlm_digest.c:51
static fr_dict_t const * dict_freeradius
Definition rlm_digest.c:37
static unlang_action_t mod_authenticate(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx, request_t *request)
Definition rlm_digest.c:114
module_rlm_t rlm_digest
Definition rlm_digest.c:476
static const fr_dict_attr_t * attr_digest_cnonce
Definition rlm_digest.c:54
static fr_dict_t const * dict_radius
Definition rlm_digest.c:38
static const fr_dict_attr_t * attr_digest_algorithm
Definition rlm_digest.c:52
static const fr_dict_attr_t * attr_digest_response
Definition rlm_digest.c:50
static const fr_dict_attr_t * attr_digest_uri
Definition rlm_digest.c:61
static const fr_dict_attr_t * attr_digest_realm
Definition rlm_digest.c:60
static const fr_dict_attr_t * attr_digest_nonce_count
Definition rlm_digest.c:58
static unlang_action_t mod_authorize(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition rlm_digest.c:86
static const fr_dict_attr_t * attr_auth_type
Definition rlm_digest.c:47
fr_dict_attr_autoload_t rlm_digest_dict_attr[]
Definition rlm_digest.c:65
fr_dict_enum_value_t * auth_type
Definition rlm_digest.c:34
static const fr_dict_attr_t * attr_digest_nonce
Definition rlm_digest.c:57
static int mod_instantiate(module_inst_ctx_t const *mctx)
Definition rlm_digest.c:453
fr_dict_autoload_t rlm_digest_dict[]
Definition rlm_digest.c:41
static unsigned int hash(char const *username, unsigned int tablesize)
Definition rlm_passwd.c:132
static int instantiate(module_inst_ctx_t const *mctx)
Definition rlm_rest.c:1310
#define FR_SBUFF_IN(_start, _len_or_end)
#define FR_SBUFF_OUT(_start, _len_or_end)
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:40
char const * name
Instance name e.g. user_database.
Definition module.h:335
size_t inst_size
Size of the module's instance data.
Definition module.h:203
void * data
Module's instance data.
Definition module.h:271
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:151
Named methods exported by a module.
Definition module.h:173
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
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
#define FR_MAX_STRING_LEN
Definition value.h:30
int nonnull(2, 5))
#define fr_box_octets(_val, _len)
Definition value.h:288