/src/freeradius-server/src/protocols/radius/base.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library 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 GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; 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: 3edd26c403df7dfcdeef17694de8b9392c7bdae2 $ |
19 | | * |
20 | | * @file protocols/radius/base.c |
21 | | * @brief Functions to send/receive radius packets. |
22 | | * |
23 | | * @copyright 2000-2003,2006 The FreeRADIUS server project |
24 | | */ |
25 | | RCSID("$Id: 3edd26c403df7dfcdeef17694de8b9392c7bdae2 $") |
26 | | |
27 | | #include <fcntl.h> |
28 | | #include <ctype.h> |
29 | | |
30 | | #include "attrs.h" |
31 | | #include "radius.h" |
32 | | |
33 | | #include <freeradius-devel/io/pair.h> |
34 | | #include <freeradius-devel/util/md5.h> |
35 | | #include <freeradius-devel/util/net.h> |
36 | | #include <freeradius-devel/util/proto.h> |
37 | | #include <freeradius-devel/util/table.h> |
38 | | #include <freeradius-devel/util/udp.h> |
39 | | #include <freeradius-devel/protocol/radius/freeradius.internal.h> |
40 | | |
41 | | static uint32_t instance_count = 0; |
42 | | static bool instantiated = false; |
43 | | |
44 | | fr_dict_t const *dict_freeradius; |
45 | | fr_dict_t const *dict_radius; |
46 | | |
47 | | extern fr_dict_autoload_t libfreeradius_radius_dict[]; |
48 | | fr_dict_autoload_t libfreeradius_radius_dict[] = { |
49 | | { .out = &dict_freeradius, .proto = "freeradius" }, |
50 | | { .out = &dict_radius, .proto = "radius" }, |
51 | | |
52 | | DICT_AUTOLOAD_TERMINATOR |
53 | | }; |
54 | | |
55 | | fr_dict_attr_t const *attr_packet_type; |
56 | | fr_dict_attr_t const *attr_packet_authentication_vector; |
57 | | fr_dict_attr_t const *attr_chap_challenge; |
58 | | fr_dict_attr_t const *attr_chargeable_user_identity; |
59 | | fr_dict_attr_t const *attr_eap_message; |
60 | | fr_dict_attr_t const *attr_message_authenticator; |
61 | | fr_dict_attr_t const *attr_state; |
62 | | fr_dict_attr_t const *attr_vendor_specific; |
63 | | fr_dict_attr_t const *attr_nas_filter_rule; |
64 | | |
65 | | extern fr_dict_attr_autoload_t libfreeradius_radius_dict_attr[]; |
66 | | fr_dict_attr_autoload_t libfreeradius_radius_dict_attr[] = { |
67 | | { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius }, |
68 | | { .out = &attr_packet_authentication_vector, .name = "Packet-Authentication-Vector", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, |
69 | | { .out = &attr_chap_challenge, .name = "CHAP-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, |
70 | | { .out = &attr_chargeable_user_identity, .name = "Chargeable-User-Identity", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, |
71 | | |
72 | | { .out = &attr_eap_message, .name = "EAP-Message", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, |
73 | | { .out = &attr_message_authenticator, .name = "Message-Authenticator", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, |
74 | | { .out = &attr_state, .name = "State", .type = FR_TYPE_OCTETS, .dict = &dict_radius }, |
75 | | { .out = &attr_vendor_specific, .name = "Vendor-Specific", .type = FR_TYPE_VSA, .dict = &dict_radius }, |
76 | | { .out = &attr_nas_filter_rule, .name = "NAS-Filter-Rule", .type = FR_TYPE_STRING, .dict = &dict_radius }, |
77 | | |
78 | | DICT_AUTOLOAD_TERMINATOR |
79 | | }; |
80 | | |
81 | | /* |
82 | | * Some messages get printed out only in debugging mode. |
83 | | */ |
84 | 120 | #define FR_DEBUG_STRERROR_PRINTF if (fr_debug_lvl) fr_strerror_printf |
85 | 0 | #define FR_DEBUG_STRERROR_PRINTF_PUSH if (fr_debug_lvl) fr_strerror_printf_push |
86 | | |
87 | | fr_table_num_sorted_t const fr_radius_require_ma_table[] = { |
88 | | { L("auto"), FR_RADIUS_REQUIRE_MA_AUTO }, |
89 | | { L("false"), FR_RADIUS_REQUIRE_MA_NO }, |
90 | | { L("no"), FR_RADIUS_REQUIRE_MA_NO }, |
91 | | { L("true"), FR_RADIUS_REQUIRE_MA_YES }, |
92 | | { L("yes"), FR_RADIUS_REQUIRE_MA_YES }, |
93 | | }; |
94 | | size_t fr_radius_require_ma_table_len = NUM_ELEMENTS(fr_radius_require_ma_table); |
95 | | |
96 | | fr_table_num_sorted_t const fr_radius_limit_proxy_state_table[] = { |
97 | | { L("auto"), FR_RADIUS_LIMIT_PROXY_STATE_AUTO }, |
98 | | { L("false"), FR_RADIUS_LIMIT_PROXY_STATE_NO }, |
99 | | { L("no"), FR_RADIUS_LIMIT_PROXY_STATE_NO }, |
100 | | { L("true"), FR_RADIUS_LIMIT_PROXY_STATE_YES }, |
101 | | { L("yes"), FR_RADIUS_LIMIT_PROXY_STATE_YES }, |
102 | | }; |
103 | | size_t fr_radius_limit_proxy_state_table_len = NUM_ELEMENTS(fr_radius_limit_proxy_state_table); |
104 | | |
105 | | fr_table_num_sorted_t const fr_radius_request_name_table[] = { |
106 | | { L("acct"), FR_RADIUS_CODE_ACCOUNTING_REQUEST }, |
107 | | { L("auth"), FR_RADIUS_CODE_ACCESS_REQUEST }, |
108 | | { L("auto"), FR_RADIUS_CODE_UNDEFINED }, |
109 | | { L("challenge"), FR_RADIUS_CODE_ACCESS_CHALLENGE }, |
110 | | { L("coa"), FR_RADIUS_CODE_COA_REQUEST }, |
111 | | { L("disconnect"), FR_RADIUS_CODE_DISCONNECT_REQUEST }, |
112 | | { L("status"), FR_RADIUS_CODE_STATUS_SERVER } |
113 | | }; |
114 | | size_t fr_radius_request_name_table_len = NUM_ELEMENTS(fr_radius_request_name_table); |
115 | | |
116 | | char const *fr_radius_packet_name[FR_RADIUS_CODE_MAX] = { |
117 | | "", //!< 0 |
118 | | "Access-Request", |
119 | | "Access-Accept", |
120 | | "Access-Reject", |
121 | | "Accounting-Request", |
122 | | "Accounting-Response", |
123 | | "Accounting-Status", |
124 | | "Password-Request", |
125 | | "Password-Accept", |
126 | | "Password-Reject", |
127 | | "Accounting-Message", //!< 10 |
128 | | "Access-Challenge", |
129 | | "Status-Server", |
130 | | "Status-Client", |
131 | | "14", |
132 | | "15", |
133 | | "16", |
134 | | "17", |
135 | | "18", |
136 | | "19", |
137 | | "20", //!< 20 |
138 | | "Resource-Free-Request", |
139 | | "Resource-Free-Response", |
140 | | "Resource-Query-Request", |
141 | | "Resource-Query-Response", |
142 | | "Alternate-Resource-Reclaim-Request", |
143 | | "NAS-Reboot-Request", |
144 | | "NAS-Reboot-Response", |
145 | | "28", |
146 | | "Next-Passcode", |
147 | | "New-Pin", //!< 30 |
148 | | "Terminate-Session", |
149 | | "Password-Expired", |
150 | | "Event-Request", |
151 | | "Event-Response", |
152 | | "35", |
153 | | "36", |
154 | | "37", |
155 | | "38", |
156 | | "39", |
157 | | "Disconnect-Request", //!< 40 |
158 | | "Disconnect-ACK", |
159 | | "Disconnect-NAK", |
160 | | "CoA-Request", |
161 | | "CoA-ACK", |
162 | | "CoA-NAK", |
163 | | "46", |
164 | | "47", |
165 | | "48", |
166 | | "49", |
167 | | "IP-Address-Allocate", //!< 50 |
168 | | "IP-Address-Release", |
169 | | "Protocol-Error", |
170 | | }; |
171 | | |
172 | | |
173 | | /** If we get a reply, the request must come from one of a small |
174 | | * number of packet types. |
175 | | */ |
176 | | static const fr_radius_packet_code_t allowed_replies[FR_RADIUS_CODE_MAX] = { |
177 | | [FR_RADIUS_CODE_ACCESS_ACCEPT] = FR_RADIUS_CODE_ACCESS_REQUEST, |
178 | | [FR_RADIUS_CODE_ACCESS_CHALLENGE] = FR_RADIUS_CODE_ACCESS_REQUEST, |
179 | | [FR_RADIUS_CODE_ACCESS_REJECT] = FR_RADIUS_CODE_ACCESS_REQUEST, |
180 | | |
181 | | [FR_RADIUS_CODE_ACCOUNTING_RESPONSE] = FR_RADIUS_CODE_ACCOUNTING_REQUEST, |
182 | | |
183 | | [FR_RADIUS_CODE_COA_ACK] = FR_RADIUS_CODE_COA_REQUEST, |
184 | | [FR_RADIUS_CODE_COA_NAK] = FR_RADIUS_CODE_COA_REQUEST, |
185 | | |
186 | | [FR_RADIUS_CODE_DISCONNECT_ACK] = FR_RADIUS_CODE_DISCONNECT_REQUEST, |
187 | | [FR_RADIUS_CODE_DISCONNECT_NAK] = FR_RADIUS_CODE_DISCONNECT_REQUEST, |
188 | | |
189 | | [FR_RADIUS_CODE_PROTOCOL_ERROR] = FR_RADIUS_CODE_PROTOCOL_ERROR, /* Any */ |
190 | | }; |
191 | | |
192 | | FR_DICT_ATTR_FLAG_FUNC(fr_radius_attr_flags_t, abinary) |
193 | | FR_DICT_ATTR_FLAG_FUNC(fr_radius_attr_flags_t, concat) |
194 | | |
195 | | static int dict_flag_encrypt(fr_dict_attr_t **da_p, char const *value, UNUSED fr_dict_flag_parser_rule_t const *rules) |
196 | 96 | { |
197 | 96 | static fr_table_num_sorted_t const encrypted[] = { |
198 | 96 | { L("Ascend-Secret"), RADIUS_FLAG_ENCRYPT_ASCEND_SECRET }, |
199 | 96 | { L("Tunnel-Password"), RADIUS_FLAG_ENCRYPT_TUNNEL_PASSWORD }, |
200 | 96 | { L("User-Password"), RADIUS_FLAG_ENCRYPT_USER_PASSWORD} |
201 | 96 | }; |
202 | 96 | static size_t encrypted_len = NUM_ELEMENTS(encrypted); |
203 | | |
204 | 96 | fr_radius_attr_flags_encrypt_t encrypt; |
205 | 96 | fr_radius_attr_flags_t *flags = fr_dict_attr_ext(*da_p, FR_DICT_ATTR_EXT_PROTOCOL_SPECIFIC); |
206 | | |
207 | 96 | encrypt = fr_table_value_by_str(encrypted, value, RADIUS_FLAG_ENCRYPT_INVALID); |
208 | 96 | if (encrypt == RADIUS_FLAG_ENCRYPT_INVALID) { |
209 | 0 | fr_strerror_printf("Unknown encryption type '%s'", value); |
210 | 0 | return -1; |
211 | 0 | } |
212 | | |
213 | 96 | flags->encrypt = encrypt; |
214 | | |
215 | 96 | return 0; |
216 | 96 | } |
217 | | |
218 | | FR_DICT_ATTR_FLAG_FUNC(fr_radius_attr_flags_t, extended) |
219 | | FR_DICT_ATTR_FLAG_FUNC(fr_radius_attr_flags_t, has_tag) |
220 | | FR_DICT_ATTR_FLAG_FUNC(fr_radius_attr_flags_t, long_extended) |
221 | | |
222 | | static fr_dict_flag_parser_t const radius_flags[] = { |
223 | | { L("abinary"), { .func = dict_flag_abinary } }, |
224 | | { L("concat"), { .func = dict_flag_concat } }, |
225 | | { L("encrypt"), { .func = dict_flag_encrypt, .needs_value = true } }, |
226 | | { L("extended"), { .func = dict_flag_extended } }, |
227 | | { L("has_tag"), { .func = dict_flag_has_tag } }, |
228 | | { L("long_extended"), { .func = dict_flag_long_extended } } |
229 | | }; |
230 | | |
231 | | int fr_radius_allow_reply(int code, bool allowed[static FR_RADIUS_CODE_MAX]) |
232 | 0 | { |
233 | 0 | int i; |
234 | |
|
235 | 0 | if ((code <= 0) || (code >= FR_RADIUS_CODE_MAX)) return -1; |
236 | | |
237 | 0 | for (i = 1; i < FR_RADIUS_CODE_MAX; i++) { |
238 | 0 | allowed[i] |= (allowed_replies[i] == (fr_radius_packet_code_t) code); |
239 | 0 | } |
240 | |
|
241 | 0 | return 0; |
242 | 0 | } |
243 | | |
244 | | /** Do Ascend-Send / Recv-Secret calculation. |
245 | | * |
246 | | * The secret is hidden by xoring with a MD5 digest created from |
247 | | * the RADIUS shared secret and the authentication vector. |
248 | | * We put them into MD5 in the reverse order from that used when |
249 | | * encrypting passwords to RADIUS. |
250 | | */ |
251 | | ssize_t fr_radius_ascend_secret(fr_dbuff_t *dbuff, uint8_t const *in, size_t inlen, |
252 | | char const *secret, uint8_t const *vector) |
253 | 298 | { |
254 | 298 | fr_md5_ctx_t *md5_ctx; |
255 | 298 | size_t i; |
256 | 298 | uint8_t digest[MD5_DIGEST_LENGTH]; |
257 | 298 | fr_dbuff_t work_dbuff = FR_DBUFF(dbuff); |
258 | | |
259 | 298 | FR_DBUFF_EXTEND_LOWAT_OR_RETURN(&work_dbuff, sizeof(digest)); |
260 | | |
261 | 298 | md5_ctx = fr_md5_ctx_alloc_from_list(); |
262 | 298 | fr_md5_update(md5_ctx, vector, RADIUS_AUTH_VECTOR_LENGTH); |
263 | 298 | fr_md5_update(md5_ctx, (uint8_t const *) secret, talloc_array_length(secret) - 1); |
264 | 298 | fr_md5_final(digest, md5_ctx); |
265 | 298 | fr_md5_ctx_free_from_list(&md5_ctx); |
266 | | |
267 | 298 | if (inlen > sizeof(digest)) inlen = sizeof(digest); |
268 | 2.78k | for (i = 0; i < inlen; i++) digest[i] ^= in[i]; |
269 | | |
270 | 298 | fr_dbuff_in_memcpy(&work_dbuff, digest, sizeof(digest)); |
271 | | |
272 | 298 | return fr_dbuff_set(dbuff, &work_dbuff); |
273 | 298 | } |
274 | | |
275 | | /** Basic validation of RADIUS packet header |
276 | | * |
277 | | * @note fr_strerror errors are only available if fr_debug_lvl > 0. This is to reduce CPU time |
278 | | * consumed when discarding malformed packet. |
279 | | * |
280 | | * @param[in] sockfd we're reading from. |
281 | | * @param[out] src_ipaddr of the packet. |
282 | | * @param[out] src_port of the packet. |
283 | | * @param[out] code Pointer to where to write the packet code. |
284 | | * @return |
285 | | * - -1 on failure. |
286 | | * - 1 on decode error. |
287 | | * - >= RADIUS_HEADER_LENGTH on success. This is the packet length as specified in the header. |
288 | | */ |
289 | | ssize_t fr_radius_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, unsigned int *code) |
290 | 0 | { |
291 | 0 | ssize_t data_len, packet_len; |
292 | 0 | uint8_t header[4]; |
293 | |
|
294 | 0 | data_len = udp_recv_peek(sockfd, header, sizeof(header), UDP_FLAGS_PEEK, src_ipaddr, src_port); |
295 | 0 | if (data_len < 0) { |
296 | 0 | if ((errno == EAGAIN) || (errno == EINTR)) return 0; |
297 | 0 | return -1; |
298 | 0 | } |
299 | | |
300 | | /* |
301 | | * Too little data is available, discard the packet. |
302 | | */ |
303 | 0 | if (data_len < 4) { |
304 | 0 | char buffer[INET6_ADDRSTRLEN]; |
305 | |
|
306 | 0 | FR_DEBUG_STRERROR_PRINTF("Expected at least 4 bytes of header data, got %zd bytes", data_len); |
307 | 0 | invalid: |
308 | 0 | FR_DEBUG_STRERROR_PRINTF_PUSH("Invalid data from %s", |
309 | 0 | inet_ntop(src_ipaddr->af, &src_ipaddr->addr, buffer, sizeof(buffer))); |
310 | 0 | (void) udp_recv_discard(sockfd); |
311 | |
|
312 | 0 | return 0; |
313 | 0 | } |
314 | | |
315 | | /* |
316 | | * See how long the packet says it is. |
317 | | */ |
318 | 0 | packet_len = (header[2] * 256) + header[3]; |
319 | | |
320 | | /* |
321 | | * The length in the packet says it's less than |
322 | | * a RADIUS header length: discard it. |
323 | | */ |
324 | 0 | if (packet_len < RADIUS_HEADER_LENGTH) { |
325 | 0 | FR_DEBUG_STRERROR_PRINTF("Expected at least " STRINGIFY(RADIUS_HEADER_LENGTH) " bytes of packet " |
326 | 0 | "data, got %zd bytes", packet_len); |
327 | 0 | goto invalid; |
328 | 0 | } |
329 | | |
330 | | /* |
331 | | * Enforce RFC requirements, for sanity. |
332 | | * Anything after 4k will be discarded. |
333 | | */ |
334 | 0 | if (packet_len > MAX_PACKET_LEN) { |
335 | 0 | FR_DEBUG_STRERROR_PRINTF("Length field value too large, expected maximum of " |
336 | 0 | STRINGIFY(MAX_PACKET_LEN) " bytes, got %zd bytes", packet_len); |
337 | 0 | goto invalid; |
338 | 0 | } |
339 | | |
340 | 0 | *code = header[0]; |
341 | | |
342 | | /* |
343 | | * The packet says it's this long, but the actual UDP |
344 | | * size could still be smaller. |
345 | | */ |
346 | 0 | return packet_len; |
347 | 0 | } |
348 | | |
349 | | /** Sign a previously encoded packet |
350 | | * |
351 | | * Calculates the request/response authenticator for packets which need it, and fills |
352 | | * in the message-authenticator value if the attribute is present in the encoded packet. |
353 | | * |
354 | | * @param[in,out] packet (request or response). |
355 | | * @param[in] vector original packet vector to use |
356 | | * @param[in] secret to sign the packet with. |
357 | | * @param[in] secret_len The length of the secret. |
358 | | * @return |
359 | | * - <0 on error |
360 | | * - 0 on success |
361 | | */ |
362 | | int fr_radius_sign(uint8_t *packet, uint8_t const *vector, |
363 | | uint8_t const *secret, size_t secret_len) |
364 | 0 | { |
365 | 0 | uint8_t *msg, *end; |
366 | 0 | size_t packet_len = fr_nbo_to_uint16(packet + 2); |
367 | | |
368 | | /* |
369 | | * No real limit on secret length, this is just |
370 | | * to catch uninitialised fields. |
371 | | */ |
372 | 0 | if (!fr_cond_assert(secret_len <= UINT16_MAX)) { |
373 | 0 | fr_strerror_printf("Secret is too long. Expected <= %u, got %zu", |
374 | 0 | (unsigned int) UINT16_MAX, secret_len); |
375 | 0 | return -1; |
376 | 0 | } |
377 | | |
378 | 0 | if (packet_len < RADIUS_HEADER_LENGTH) { |
379 | 0 | fr_strerror_const("Packet must be encoded before calling fr_radius_sign()"); |
380 | 0 | return -1; |
381 | 0 | } |
382 | | |
383 | | /* |
384 | | * Find Message-Authenticator. Its value has to be |
385 | | * calculated before we calculate the Request |
386 | | * Authenticator or the Response Authenticator. |
387 | | */ |
388 | 0 | msg = packet + RADIUS_HEADER_LENGTH; |
389 | 0 | end = packet + packet_len; |
390 | |
|
391 | 0 | while (msg < end) { |
392 | 0 | if ((end - msg) < 2) goto invalid_attribute; |
393 | | |
394 | 0 | if (msg[0] != FR_MESSAGE_AUTHENTICATOR) { |
395 | 0 | if (msg[1] < 2) goto invalid_attribute; |
396 | | |
397 | 0 | if ((msg + msg[1]) > end) { |
398 | 0 | invalid_attribute: |
399 | 0 | fr_strerror_printf("Invalid attribute at offset %zd", msg - packet); |
400 | 0 | return -1; |
401 | 0 | } |
402 | 0 | msg += msg[1]; |
403 | 0 | continue; |
404 | 0 | } |
405 | | |
406 | 0 | if (msg[1] < 18) { |
407 | 0 | fr_strerror_const("Message-Authenticator is too small"); |
408 | 0 | return -1; |
409 | 0 | } |
410 | | |
411 | 0 | switch (packet[0]) { |
412 | 0 | case FR_RADIUS_CODE_ACCOUNTING_REQUEST: |
413 | 0 | case FR_RADIUS_CODE_DISCONNECT_REQUEST: |
414 | 0 | case FR_RADIUS_CODE_COA_REQUEST: |
415 | 0 | memset(packet + 4, 0, RADIUS_AUTH_VECTOR_LENGTH); |
416 | 0 | break; |
417 | | |
418 | 0 | case FR_RADIUS_CODE_ACCESS_ACCEPT: |
419 | 0 | case FR_RADIUS_CODE_ACCESS_REJECT: |
420 | 0 | case FR_RADIUS_CODE_ACCESS_CHALLENGE: |
421 | 0 | case FR_RADIUS_CODE_ACCOUNTING_RESPONSE: |
422 | 0 | case FR_RADIUS_CODE_DISCONNECT_ACK: |
423 | 0 | case FR_RADIUS_CODE_DISCONNECT_NAK: |
424 | 0 | case FR_RADIUS_CODE_COA_ACK: |
425 | 0 | case FR_RADIUS_CODE_COA_NAK: |
426 | 0 | case FR_RADIUS_CODE_PROTOCOL_ERROR: |
427 | 0 | if (!vector) goto need_original; |
428 | 0 | memcpy(packet + 4, vector, RADIUS_AUTH_VECTOR_LENGTH); |
429 | 0 | break; |
430 | | |
431 | 0 | case FR_RADIUS_CODE_ACCESS_REQUEST: |
432 | 0 | case FR_RADIUS_CODE_STATUS_SERVER: |
433 | | /* packet + 4 MUST be the Request Authenticator filled with random data */ |
434 | 0 | break; |
435 | | |
436 | 0 | default: |
437 | 0 | goto bad_packet; |
438 | 0 | } |
439 | | |
440 | | /* |
441 | | * Force Message-Authenticator to be zero, |
442 | | * calculate the HMAC, and put it into the |
443 | | * Message-Authenticator attribute. |
444 | | */ |
445 | 0 | memset(msg + 2, 0, RADIUS_AUTH_VECTOR_LENGTH); |
446 | 0 | fr_hmac_md5(msg + 2, packet, packet_len, secret, secret_len); |
447 | 0 | break; |
448 | 0 | } |
449 | | |
450 | | /* |
451 | | * Initialize the request authenticator. |
452 | | */ |
453 | 0 | switch (packet[0]) { |
454 | 0 | case FR_RADIUS_CODE_ACCOUNTING_REQUEST: |
455 | 0 | case FR_RADIUS_CODE_DISCONNECT_REQUEST: |
456 | 0 | case FR_RADIUS_CODE_COA_REQUEST: |
457 | 0 | memset(packet + 4, 0, RADIUS_AUTH_VECTOR_LENGTH); |
458 | 0 | break; |
459 | | |
460 | 0 | case FR_RADIUS_CODE_ACCESS_ACCEPT: |
461 | 0 | case FR_RADIUS_CODE_ACCESS_REJECT: |
462 | 0 | case FR_RADIUS_CODE_ACCESS_CHALLENGE: |
463 | 0 | case FR_RADIUS_CODE_ACCOUNTING_RESPONSE: |
464 | 0 | case FR_RADIUS_CODE_DISCONNECT_ACK: |
465 | 0 | case FR_RADIUS_CODE_DISCONNECT_NAK: |
466 | 0 | case FR_RADIUS_CODE_COA_ACK: |
467 | 0 | case FR_RADIUS_CODE_COA_NAK: |
468 | 0 | case FR_RADIUS_CODE_PROTOCOL_ERROR: |
469 | 0 | if (!vector) { |
470 | 0 | need_original: |
471 | 0 | fr_strerror_const("Cannot sign response packet without a request packet"); |
472 | 0 | return -1; |
473 | 0 | } |
474 | 0 | memcpy(packet + 4, vector, RADIUS_AUTH_VECTOR_LENGTH); |
475 | 0 | break; |
476 | | |
477 | | /* |
478 | | * The Request Authenticator is random numbers. |
479 | | * We don't need to sign anything else, so |
480 | | * return. |
481 | | */ |
482 | 0 | case FR_RADIUS_CODE_ACCESS_REQUEST: |
483 | 0 | case FR_RADIUS_CODE_STATUS_SERVER: |
484 | 0 | return 0; |
485 | | |
486 | 0 | default: |
487 | 0 | bad_packet: |
488 | 0 | fr_strerror_printf("Cannot sign unknown packet code %u", packet[0]); |
489 | 0 | return -1; |
490 | 0 | } |
491 | | |
492 | | /* |
493 | | * Request / Response Authenticator = MD5(packet + secret) |
494 | | */ |
495 | 0 | { |
496 | 0 | fr_md5_ctx_t *md5_ctx; |
497 | |
|
498 | 0 | md5_ctx = fr_md5_ctx_alloc_from_list(); |
499 | 0 | fr_md5_update(md5_ctx, packet, packet_len); |
500 | 0 | fr_md5_update(md5_ctx, secret, secret_len); |
501 | 0 | fr_md5_final(packet + 4, md5_ctx); |
502 | 0 | fr_md5_ctx_free_from_list(&md5_ctx); |
503 | 0 | } |
504 | |
|
505 | 0 | return 0; |
506 | 0 | } |
507 | | |
508 | | |
509 | | /** See if the data pointed to by PTR is a valid RADIUS packet. |
510 | | * |
511 | | * @param[in] packet to check. |
512 | | * @param[in,out] packet_len_p The size of the packet data. |
513 | | * @param[in] max_attributes to allow in the packet. |
514 | | * @param[in] require_message_authenticator whether we require Message-Authenticator. |
515 | | * @param[in] reason if not NULL, will have the failure reason written to where it points. |
516 | | * @return |
517 | | * - True on success. |
518 | | * - False on failure. |
519 | | */ |
520 | | bool fr_radius_ok(uint8_t const *packet, size_t *packet_len_p, |
521 | | uint32_t max_attributes, bool require_message_authenticator, fr_radius_decode_fail_t *reason) |
522 | 5.19k | { |
523 | 5.19k | uint8_t const *attr, *end; |
524 | 5.19k | size_t totallen; |
525 | 5.19k | bool seen_ma = false; |
526 | 5.19k | uint32_t num_attributes; |
527 | 5.19k | fr_radius_decode_fail_t failure = DECODE_FAIL_NONE; |
528 | 5.19k | size_t packet_len = *packet_len_p; |
529 | | |
530 | | /* |
531 | | * Check for packets smaller than the packet header. |
532 | | * |
533 | | * RFC 2865, Section 3., subsection 'length' says: |
534 | | * |
535 | | * "The minimum length is 20 ..." |
536 | | */ |
537 | 5.19k | if (packet_len < RADIUS_HEADER_LENGTH) { |
538 | 9 | FR_DEBUG_STRERROR_PRINTF("Packet is too short (received %zu < minimum 20)", |
539 | 9 | packet_len); |
540 | 9 | failure = DECODE_FAIL_MIN_LENGTH_PACKET; |
541 | 9 | goto finish; |
542 | 9 | } |
543 | | |
544 | | |
545 | | /* |
546 | | * Check for packets with mismatched size. |
547 | | * i.e. We've received 128 bytes, and the packet header |
548 | | * says it's 256 bytes long. |
549 | | */ |
550 | 5.18k | totallen = fr_nbo_to_uint16(packet + 2); |
551 | | |
552 | | /* |
553 | | * Code of 0 is not understood. |
554 | | * Code of 16 or greater is not understood. |
555 | | */ |
556 | 5.18k | if ((packet[0] == 0) || |
557 | 5.18k | (packet[0] >= FR_RADIUS_CODE_MAX)) { |
558 | 9 | FR_DEBUG_STRERROR_PRINTF("Unknown packet code %d", packet[0]); |
559 | 9 | failure = DECODE_FAIL_UNKNOWN_PACKET_CODE; |
560 | 9 | goto finish; |
561 | 9 | } |
562 | | |
563 | 5.18k | switch (packet[0]) { |
564 | | /* |
565 | | * Message-Authenticator is required in Status-Server |
566 | | * packets, otherwise they can be trivially forged. |
567 | | */ |
568 | 5 | case FR_RADIUS_CODE_STATUS_SERVER: |
569 | 5 | require_message_authenticator = true; |
570 | 5 | break; |
571 | | |
572 | | /* |
573 | | * Message-Authenticator may or may not be |
574 | | * required for Access-* packets. |
575 | | */ |
576 | 210 | case FR_RADIUS_CODE_ACCESS_REQUEST: |
577 | 375 | case FR_RADIUS_CODE_ACCESS_ACCEPT: |
578 | 476 | case FR_RADIUS_CODE_ACCESS_CHALLENGE: |
579 | 561 | case FR_RADIUS_CODE_ACCESS_REJECT: |
580 | 641 | case FR_RADIUS_CODE_PROTOCOL_ERROR: |
581 | 641 | break; |
582 | | |
583 | | /* |
584 | | * Message-Authenticator is not required for all other packets. |
585 | | */ |
586 | 4.53k | default: |
587 | 4.53k | require_message_authenticator = false; |
588 | 4.53k | break; |
589 | 5.18k | } |
590 | | |
591 | | /* |
592 | | * Repeat the length checks. This time, instead of |
593 | | * looking at the data we received, look at the value |
594 | | * of the 'length' field inside of the packet. |
595 | | * |
596 | | * Check for packets smaller than the packet header. |
597 | | * |
598 | | * RFC 2865, Section 3., subsection 'length' says: |
599 | | * |
600 | | * "The minimum length is 20 ..." |
601 | | */ |
602 | 5.18k | if (totallen < RADIUS_HEADER_LENGTH) { |
603 | 10 | FR_DEBUG_STRERROR_PRINTF("Length in header is too small (length %zu < minimum 20)", |
604 | 10 | totallen); |
605 | 10 | failure = DECODE_FAIL_MIN_LENGTH_FIELD; |
606 | 10 | goto finish; |
607 | 10 | } |
608 | | |
609 | | /* |
610 | | * And again, for the value of the 'length' field. |
611 | | * |
612 | | * RFC 2865, Section 3., subsection 'length' says: |
613 | | * |
614 | | * " ... and maximum length is 4096." |
615 | | * |
616 | | * HOWEVER. This requirement is for the network layer. |
617 | | * If the code gets here, we assume that a well-formed |
618 | | * packet is an OK packet. |
619 | | * |
620 | | * We allow both the UDP data length, and the RADIUS |
621 | | * "length" field to contain up to 64K of data. |
622 | | */ |
623 | | |
624 | | /* |
625 | | * RFC 2865, Section 3., subsection 'length' says: |
626 | | * |
627 | | * "If the packet is shorter than the Length field |
628 | | * indicates, it MUST be silently discarded." |
629 | | * |
630 | | * i.e. No response to the NAS. |
631 | | */ |
632 | 5.17k | if (totallen > packet_len) { |
633 | 18 | FR_DEBUG_STRERROR_PRINTF("Packet is truncated (received %zu < packet header length of %zu)", |
634 | 18 | packet_len, totallen); |
635 | 18 | failure = DECODE_FAIL_MIN_LENGTH_MISMATCH; |
636 | 18 | goto finish; |
637 | 18 | } |
638 | | |
639 | | /* |
640 | | * RFC 2865, Section 3., subsection 'length' says: |
641 | | * |
642 | | * "Octets outside the range of the Length field MUST be |
643 | | * treated as padding and ignored on reception." |
644 | | */ |
645 | 5.15k | if (totallen < packet_len) { |
646 | 126 | *packet_len_p = packet_len = totallen; |
647 | 126 | } |
648 | | |
649 | | /* |
650 | | * Walk through the packet's attributes, ensuring that |
651 | | * they add up EXACTLY to the size of the packet. |
652 | | * |
653 | | * If they don't, then the attributes either under-fill |
654 | | * or over-fill the packet. Any parsing of the packet |
655 | | * is impossible, and will result in unknown side effects. |
656 | | * |
657 | | * This would ONLY happen with buggy RADIUS implementations, |
658 | | * or with an intentional attack. Either way, we do NOT want |
659 | | * to be vulnerable to this problem. |
660 | | */ |
661 | 5.15k | attr = packet + RADIUS_HEADER_LENGTH; |
662 | 5.15k | end = packet + packet_len; |
663 | 5.15k | num_attributes = 0; |
664 | | |
665 | 139k | while (attr < end) { |
666 | | /* |
667 | | * We need at least 2 bytes to check the |
668 | | * attribute header. |
669 | | */ |
670 | 134k | if ((end - attr) < 2) { |
671 | 10 | FR_DEBUG_STRERROR_PRINTF("Attribute header overflows the packet"); |
672 | 10 | failure = DECODE_FAIL_HEADER_OVERFLOW; |
673 | 10 | goto finish; |
674 | 10 | } |
675 | | |
676 | | /* |
677 | | * Attribute number zero is NOT defined. |
678 | | */ |
679 | 134k | if (attr[0] == 0) { |
680 | 15 | FR_DEBUG_STRERROR_PRINTF("Invalid attribute 0 at offset %zd", attr - packet); |
681 | 15 | failure = DECODE_FAIL_INVALID_ATTRIBUTE; |
682 | 15 | goto finish; |
683 | 15 | } |
684 | | |
685 | | /* |
686 | | * Attributes are at LEAST as long as the ID & length |
687 | | * fields. Anything shorter is an invalid attribute. |
688 | | */ |
689 | 134k | if (attr[1] < 2) { |
690 | 4 | FR_DEBUG_STRERROR_PRINTF("Attribute %u is too short at offset %zd", |
691 | 4 | attr[0], attr - packet); |
692 | 4 | failure = DECODE_FAIL_ATTRIBUTE_TOO_SHORT; |
693 | 4 | goto finish; |
694 | 4 | } |
695 | | |
696 | | /* |
697 | | * If there are fewer bytes in the packet than in the |
698 | | * attribute, it's a bad packet. |
699 | | */ |
700 | 134k | if ((attr + attr[1]) > end) { |
701 | 12 | FR_DEBUG_STRERROR_PRINTF("Attribute %u data overflows the packet starting at offset %zd", |
702 | 12 | attr[0], attr - packet); |
703 | 12 | failure = DECODE_FAIL_ATTRIBUTE_OVERFLOW; |
704 | 12 | goto finish; |
705 | 12 | } |
706 | | |
707 | | /* |
708 | | * Sanity check the attributes for length. |
709 | | */ |
710 | 134k | switch (attr[0]) { |
711 | 134k | default: /* don't do anything by default */ |
712 | 134k | break; |
713 | | |
714 | | /* |
715 | | * If there's an EAP-Message, we require |
716 | | * a Message-Authenticator. |
717 | | */ |
718 | 134k | case FR_EAP_MESSAGE: |
719 | 229 | require_message_authenticator = true; |
720 | 229 | break; |
721 | | |
722 | 211 | case FR_MESSAGE_AUTHENTICATOR: |
723 | 211 | if (attr[1] != 2 + RADIUS_AUTH_VECTOR_LENGTH) { |
724 | 12 | FR_DEBUG_STRERROR_PRINTF("Message-Authenticator has invalid length (%d != 18) at offset %zd", |
725 | 12 | attr[1] - 2, attr - packet); |
726 | 12 | failure = DECODE_FAIL_MA_INVALID_LENGTH; |
727 | 12 | goto finish; |
728 | 12 | } |
729 | 199 | seen_ma = true; |
730 | 199 | break; |
731 | 134k | } |
732 | | |
733 | 134k | attr += attr[1]; |
734 | 134k | num_attributes++; /* seen one more attribute */ |
735 | 134k | } |
736 | | |
737 | | /* |
738 | | * If the attributes add up to a packet, it's allowed. |
739 | | * |
740 | | * If not, we complain, and throw the packet away. |
741 | | */ |
742 | 5.09k | if (attr != end) { |
743 | 0 | FR_DEBUG_STRERROR_PRINTF("Attributes do NOT exactly fill the packet"); |
744 | 0 | failure = DECODE_FAIL_ATTRIBUTE_UNDERFLOW; |
745 | 0 | goto finish; |
746 | 0 | } |
747 | | |
748 | | /* |
749 | | * If we're configured to look for a maximum number of |
750 | | * attributes, and we've seen more than that maximum, |
751 | | * then throw the packet away, as a possible DoS. |
752 | | */ |
753 | 5.09k | if ((max_attributes > 0) && |
754 | 5.09k | (num_attributes > max_attributes)) { |
755 | 13 | FR_DEBUG_STRERROR_PRINTF("Possible DoS attack - too many attributes in request (received %u, max %u are allowed).", |
756 | 13 | num_attributes, max_attributes); |
757 | 13 | failure = DECODE_FAIL_TOO_MANY_ATTRIBUTES; |
758 | 13 | goto finish; |
759 | 13 | } |
760 | | |
761 | | /* |
762 | | * http://www.freeradius.org/rfc/rfc2869.html#EAP-Message |
763 | | * |
764 | | * A packet with an EAP-Message attribute MUST also have |
765 | | * a Message-Authenticator attribute. |
766 | | * |
767 | | * A Message-Authenticator all by itself is OK, though. |
768 | | * |
769 | | * Similarly, Status-Server packets MUST contain |
770 | | * Message-Authenticator attributes. |
771 | | */ |
772 | 5.08k | if (require_message_authenticator && !seen_ma) { |
773 | 8 | FR_DEBUG_STRERROR_PRINTF("We require Message-Authenticator attribute, but it is not in the packet"); |
774 | 8 | failure = DECODE_FAIL_MA_MISSING; |
775 | 8 | goto finish; |
776 | 8 | } |
777 | | |
778 | 5.19k | finish: |
779 | | |
780 | 5.19k | if (reason) { |
781 | 5.19k | *reason = failure; |
782 | 5.19k | } |
783 | 5.19k | return (failure == DECODE_FAIL_NONE); |
784 | 5.08k | } |
785 | | |
786 | | |
787 | | /** Verify a request / response packet |
788 | | * |
789 | | * This function does its work by calling fr_radius_sign(), and then |
790 | | * comparing the signature in the packet with the one we calculated. |
791 | | * If they differ, there's a problem. |
792 | | * |
793 | | * @param[in] packet the raw RADIUS packet (request or response) |
794 | | * @param[in] vector the original packet vector |
795 | | * @param[in] secret the shared secret |
796 | | * @param[in] secret_len the length of the secret |
797 | | * @param[in] require_message_authenticator whether we require Message-Authenticator. |
798 | | * @param[in] limit_proxy_state whether we allow Proxy-State without Message-Authenticator. |
799 | | * @return |
800 | | * < <0 on error (negative fr_radius_decode_fail_t) |
801 | | * - 0 on success. |
802 | | */ |
803 | | int fr_radius_verify(uint8_t *packet, uint8_t const *vector, |
804 | | uint8_t const *secret, size_t secret_len, |
805 | | bool require_message_authenticator, bool limit_proxy_state) |
806 | 0 | { |
807 | 0 | bool found_message_authenticator = false; |
808 | 0 | bool found_proxy_state = false; |
809 | 0 | int rcode; |
810 | 0 | int code; |
811 | 0 | uint8_t *msg, *end; |
812 | 0 | size_t packet_len = fr_nbo_to_uint16(packet + 2); |
813 | 0 | uint8_t request_authenticator[RADIUS_AUTH_VECTOR_LENGTH]; |
814 | 0 | uint8_t message_authenticator[RADIUS_AUTH_VECTOR_LENGTH]; |
815 | |
|
816 | 0 | if (packet_len < RADIUS_HEADER_LENGTH) { |
817 | 0 | fr_strerror_printf("invalid packet length %zu", packet_len); |
818 | 0 | return -DECODE_FAIL_MIN_LENGTH_PACKET; |
819 | 0 | } |
820 | | |
821 | 0 | code = packet[0]; |
822 | 0 | if (!code || (code >= FR_RADIUS_CODE_MAX)) { |
823 | 0 | fr_strerror_printf("Unknown reply code %d", code); |
824 | 0 | return -DECODE_FAIL_UNKNOWN_PACKET_CODE; |
825 | 0 | } |
826 | | |
827 | 0 | memcpy(request_authenticator, packet + 4, sizeof(request_authenticator)); |
828 | | |
829 | | /* |
830 | | * Find Message-Authenticator. Its value has to be |
831 | | * calculated before we calculate the Request |
832 | | * Authenticator or the Response Authenticator. |
833 | | */ |
834 | 0 | msg = packet + RADIUS_HEADER_LENGTH; |
835 | 0 | end = packet + packet_len; |
836 | |
|
837 | 0 | while (msg < end) { |
838 | 0 | if ((end - msg) < 2) goto invalid_attribute; |
839 | | |
840 | 0 | if (msg[0] != FR_MESSAGE_AUTHENTICATOR) { |
841 | 0 | if (msg[1] < 2) goto invalid_attribute; |
842 | | |
843 | | /* |
844 | | * If we're not allowing Proxy-State without |
845 | | * Message-authenticator, we need to record |
846 | | * the fact we found Proxy-State. |
847 | | */ |
848 | 0 | if (limit_proxy_state && (msg[0] == FR_PROXY_STATE)) found_proxy_state = true; |
849 | |
|
850 | 0 | if ((msg + msg[1]) > end) { |
851 | 0 | invalid_attribute: |
852 | 0 | fr_strerror_printf("invalid attribute at offset %zd", msg - packet); |
853 | 0 | return -DECODE_FAIL_INVALID_ATTRIBUTE; |
854 | 0 | } |
855 | 0 | msg += msg[1]; |
856 | 0 | continue; |
857 | 0 | } |
858 | | |
859 | 0 | if (msg[1] < 18) { |
860 | 0 | fr_strerror_const("too small Message-Authenticator"); |
861 | 0 | return -DECODE_FAIL_MA_INVALID_LENGTH; |
862 | 0 | } |
863 | | |
864 | | /* |
865 | | * Found it, save a copy. |
866 | | */ |
867 | 0 | memcpy(message_authenticator, msg + 2, sizeof(message_authenticator)); |
868 | 0 | found_message_authenticator = true; |
869 | 0 | break; |
870 | 0 | } |
871 | | |
872 | 0 | if (packet[0] == FR_RADIUS_CODE_ACCESS_REQUEST) { |
873 | 0 | if (limit_proxy_state && found_proxy_state && !found_message_authenticator) { |
874 | 0 | fr_strerror_const("Proxy-State is not allowed without Message-Authenticator"); |
875 | 0 | return -DECODE_FAIL_MA_MISSING; |
876 | 0 | } |
877 | | |
878 | 0 | if (require_message_authenticator && !found_message_authenticator) { |
879 | 0 | fr_strerror_const("Access-Request is missing the required Message-Authenticator attribute"); |
880 | 0 | return -DECODE_FAIL_MA_MISSING; |
881 | 0 | } |
882 | 0 | } |
883 | | |
884 | | /* |
885 | | * Overwrite the contents of Message-Authenticator |
886 | | * with the one we calculate. |
887 | | */ |
888 | 0 | rcode = fr_radius_sign(packet, vector, secret, secret_len); |
889 | 0 | if (rcode < 0) { |
890 | 0 | fr_strerror_const_push("Failed calculating correct authenticator"); |
891 | 0 | return -DECODE_FAIL_VERIFY; |
892 | 0 | } |
893 | | |
894 | | /* |
895 | | * Check the Message-Authenticator first. |
896 | | * |
897 | | * If it's invalid, restore the original |
898 | | * Message-Authenticator and Request Authenticator |
899 | | * fields. |
900 | | * |
901 | | * If it's valid the original and calculated |
902 | | * message authenticators are the same, so we don't |
903 | | * need to do anything. |
904 | | */ |
905 | 0 | if ((msg < end) && |
906 | 0 | (fr_digest_cmp(message_authenticator, msg + 2, sizeof(message_authenticator)) != 0)) { |
907 | 0 | memcpy(msg + 2, message_authenticator, sizeof(message_authenticator)); |
908 | 0 | memcpy(packet + 4, request_authenticator, sizeof(request_authenticator)); |
909 | |
|
910 | 0 | fr_strerror_const("invalid Message-Authenticator (shared secret is incorrect)"); |
911 | 0 | return -DECODE_FAIL_MA_INVALID; |
912 | 0 | } |
913 | | |
914 | | /* |
915 | | * These are random numbers, so there's no point in |
916 | | * comparing them. |
917 | | */ |
918 | 0 | if ((packet[0] == FR_RADIUS_CODE_ACCESS_REQUEST) || (packet[0] == FR_RADIUS_CODE_STATUS_SERVER)) { |
919 | 0 | return 0; |
920 | 0 | } |
921 | | |
922 | | /* |
923 | | * Check the Request Authenticator. |
924 | | */ |
925 | 0 | if (fr_digest_cmp(request_authenticator, packet + 4, sizeof(request_authenticator)) != 0) { |
926 | 0 | memcpy(packet + 4, request_authenticator, sizeof(request_authenticator)); |
927 | 0 | if (vector) { |
928 | 0 | fr_strerror_const("invalid Response Authenticator (shared secret is incorrect)"); |
929 | 0 | } else { |
930 | 0 | fr_strerror_const("invalid Request Authenticator (shared secret is incorrect)"); |
931 | 0 | } |
932 | 0 | return -DECODE_FAIL_VERIFY; |
933 | 0 | } |
934 | | |
935 | 0 | return 0; |
936 | 0 | } |
937 | | |
938 | | void *fr_radius_next_encodable(fr_dcursor_t *cursor, void *current, void *uctx); |
939 | | |
940 | | void *fr_radius_next_encodable(fr_dcursor_t *cursor, void *current, void *uctx) |
941 | 0 | { |
942 | 0 | fr_pair_t *c = current; |
943 | 0 | fr_dict_t *dict = talloc_get_type_abort(uctx, fr_dict_t); |
944 | |
|
945 | 0 | while ((c = fr_dlist_next(cursor->dlist, c))) { |
946 | 0 | PAIR_VERIFY(c); |
947 | 0 | if ((c->da->dict == dict) && |
948 | 0 | (!c->da->flags.internal || ((c->da->attr > FR_TAG_BASE) && (c->da->attr < (FR_TAG_BASE + 0x20))))) { |
949 | 0 | break; |
950 | 0 | } |
951 | 0 | } |
952 | |
|
953 | 0 | return c; |
954 | 0 | } |
955 | | |
956 | | |
957 | | static const bool disallow_tunnel_passwords[FR_RADIUS_CODE_MAX] = { |
958 | | [ FR_RADIUS_CODE_ACCESS_REQUEST ] = true, |
959 | | // can be in Access-Accept |
960 | | [ FR_RADIUS_CODE_ACCESS_REJECT ] = true, |
961 | | [ FR_RADIUS_CODE_ACCESS_CHALLENGE ] = true, |
962 | | |
963 | | [ FR_RADIUS_CODE_ACCOUNTING_REQUEST ] = true, |
964 | | [ FR_RADIUS_CODE_ACCOUNTING_RESPONSE ] = true, |
965 | | |
966 | | [ FR_RADIUS_CODE_STATUS_SERVER ] = true, |
967 | | |
968 | | [ FR_RADIUS_CODE_COA_ACK ] = true, |
969 | | [ FR_RADIUS_CODE_COA_NAK ] = true, |
970 | | |
971 | | [ FR_RADIUS_CODE_DISCONNECT_REQUEST ] = true, |
972 | | [ FR_RADIUS_CODE_DISCONNECT_ACK ] = true, |
973 | | [ FR_RADIUS_CODE_DISCONNECT_NAK ] = true, |
974 | | |
975 | | [ FR_RADIUS_CODE_PROTOCOL_ERROR ] = true, |
976 | | }; |
977 | | |
978 | | ssize_t fr_radius_encode(fr_dbuff_t *dbuff, fr_pair_list_t *vps, fr_radius_encode_ctx_t *packet_ctx) |
979 | 0 | { |
980 | 0 | ssize_t slen; |
981 | 0 | fr_pair_t const *vp; |
982 | 0 | fr_dcursor_t cursor; |
983 | 0 | fr_dbuff_t work_dbuff, length_dbuff; |
984 | |
|
985 | 0 | packet_ctx->disallow_tunnel_passwords = disallow_tunnel_passwords[packet_ctx->code]; |
986 | | |
987 | | /* |
988 | | * The RADIUS header can't do more than 64K of data. |
989 | | */ |
990 | 0 | work_dbuff = FR_DBUFF_MAX(dbuff, 65535); |
991 | |
|
992 | 0 | FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, packet_ctx->code, packet_ctx->id); |
993 | 0 | length_dbuff = FR_DBUFF(&work_dbuff); |
994 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint16_t) RADIUS_HEADER_LENGTH); |
995 | | |
996 | 0 | switch (packet_ctx->code) { |
997 | 0 | case FR_RADIUS_CODE_ACCESS_REQUEST: |
998 | 0 | case FR_RADIUS_CODE_STATUS_SERVER: |
999 | 0 | packet_ctx->request_authenticator = fr_dbuff_current(&work_dbuff); |
1000 | | |
1001 | | /* |
1002 | | * Allow over-rides of the authentication vector for testing. |
1003 | | */ |
1004 | 0 | vp = fr_pair_find_by_da(vps, NULL, attr_packet_authentication_vector); |
1005 | 0 | if (vp && (vp->vp_length >= RADIUS_AUTH_VECTOR_LENGTH)) { |
1006 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_octets, RADIUS_AUTH_VECTOR_LENGTH); |
1007 | 0 | } else { |
1008 | 0 | int i; |
1009 | |
|
1010 | 0 | for (i = 0; i < 4; i++) { |
1011 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) fr_rand()); |
1012 | 0 | } |
1013 | 0 | } |
1014 | 0 | break; |
1015 | | |
1016 | 0 | case FR_RADIUS_CODE_ACCESS_ACCEPT: |
1017 | 0 | case FR_RADIUS_CODE_ACCESS_REJECT: |
1018 | 0 | case FR_RADIUS_CODE_ACCESS_CHALLENGE: |
1019 | 0 | case FR_RADIUS_CODE_ACCOUNTING_RESPONSE: |
1020 | 0 | case FR_RADIUS_CODE_DISCONNECT_ACK: |
1021 | 0 | case FR_RADIUS_CODE_DISCONNECT_NAK: |
1022 | 0 | case FR_RADIUS_CODE_COA_ACK: |
1023 | 0 | case FR_RADIUS_CODE_COA_NAK: |
1024 | 0 | case FR_RADIUS_CODE_PROTOCOL_ERROR: |
1025 | 0 | if (!packet_ctx->request_authenticator) { |
1026 | 0 | fr_strerror_const("Cannot encode response without request"); |
1027 | 0 | return -1; |
1028 | 0 | } |
1029 | 0 | FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, packet_ctx->request_authenticator, RADIUS_AUTH_VECTOR_LENGTH); |
1030 | 0 | break; |
1031 | | |
1032 | 0 | case FR_RADIUS_CODE_ACCOUNTING_REQUEST: |
1033 | 0 | case FR_RADIUS_CODE_DISCONNECT_REQUEST: |
1034 | | /* |
1035 | | * Tunnel-Password encoded attributes are allowed |
1036 | | * in CoA-Request packets, by RFC 5176 Section |
1037 | | * 3.6. HOWEVER, the tunnel passwords are |
1038 | | * "encrypted" using the Request Authenticator, |
1039 | | * which is all zeros! That makes them much |
1040 | | * easier to decrypt. The only solution here is |
1041 | | * to say "don't do that!" |
1042 | | */ |
1043 | 0 | case FR_RADIUS_CODE_COA_REQUEST: |
1044 | 0 | packet_ctx->request_authenticator = fr_dbuff_current(&work_dbuff); |
1045 | |
|
1046 | 0 | FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, RADIUS_AUTH_VECTOR_LENGTH); |
1047 | 0 | break; |
1048 | | |
1049 | 0 | default: |
1050 | 0 | fr_strerror_printf("Cannot encode unknown packet code %d", packet_ctx->code); |
1051 | 0 | return -1; |
1052 | 0 | } |
1053 | | |
1054 | | /* |
1055 | | * Always add Message-Authenticator after the packet |
1056 | | * header for insecure transport protocols. |
1057 | | */ |
1058 | 0 | if (!packet_ctx->common->secure_transport) switch (packet_ctx->code) { |
1059 | 0 | case FR_RADIUS_CODE_ACCESS_REQUEST: |
1060 | 0 | case FR_RADIUS_CODE_ACCESS_ACCEPT: |
1061 | 0 | case FR_RADIUS_CODE_ACCESS_REJECT: |
1062 | 0 | case FR_RADIUS_CODE_ACCESS_CHALLENGE: |
1063 | 0 | case FR_RADIUS_CODE_STATUS_SERVER: |
1064 | 0 | case FR_RADIUS_CODE_PROTOCOL_ERROR: |
1065 | 0 | FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, FR_MESSAGE_AUTHENTICATOR, 0x12, |
1066 | 0 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
1067 | 0 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); |
1068 | 0 | packet_ctx->seen_message_authenticator = true; |
1069 | 0 | break; |
1070 | | |
1071 | 0 | default: |
1072 | 0 | break; |
1073 | 0 | } |
1074 | | |
1075 | | /* |
1076 | | * If we're sending Protocol-Error, add in |
1077 | | * Original-Packet-Code manually. If the user adds it |
1078 | | * later themselves, well, too bad. |
1079 | | */ |
1080 | 0 | if (packet_ctx->code == FR_RADIUS_CODE_PROTOCOL_ERROR) { |
1081 | 0 | FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, FR_EXTENDED_ATTRIBUTE_1, 0x07, 0x04 /* Original-Packet-Code */, |
1082 | 0 | 0x00, 0x00, 0x00, packet_ctx->request_code); |
1083 | 0 | } |
1084 | | |
1085 | | /* |
1086 | | * Loop over the reply attributes for the packet. |
1087 | | */ |
1088 | 0 | fr_pair_dcursor_iter_init(&cursor, vps, fr_radius_next_encodable, dict_radius); |
1089 | 0 | while ((vp = fr_dcursor_current(&cursor))) { |
1090 | 0 | PAIR_VERIFY(vp); |
1091 | | |
1092 | | /* |
1093 | | * Encode an individual VP |
1094 | | */ |
1095 | 0 | slen = fr_radius_encode_pair(&work_dbuff, &cursor, packet_ctx); |
1096 | 0 | if (slen < 0) return slen; |
1097 | 0 | } /* done looping over all attributes */ |
1098 | | |
1099 | | /* |
1100 | | * Add Proxy-State to the end of the packet if the caller requested it. |
1101 | | */ |
1102 | 0 | if (packet_ctx->add_proxy_state) { |
1103 | 0 | FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, FR_PROXY_STATE, (uint8_t) (2 + sizeof(packet_ctx->common->proxy_state))); |
1104 | 0 | FR_DBUFF_IN_RETURN(&work_dbuff, packet_ctx->common->proxy_state); |
1105 | 0 | } |
1106 | | |
1107 | | /* |
1108 | | * Fill in the length field we zeroed out earlier. |
1109 | | * |
1110 | | */ |
1111 | 0 | fr_dbuff_in(&length_dbuff, (uint16_t) (fr_dbuff_used(&work_dbuff))); |
1112 | |
|
1113 | 0 | FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "%s encoded packet", __FUNCTION__); |
1114 | |
|
1115 | 0 | return fr_dbuff_set(dbuff, &work_dbuff); |
1116 | 0 | } |
1117 | | |
1118 | | ssize_t fr_radius_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, |
1119 | | uint8_t *packet, size_t packet_len, |
1120 | | fr_radius_decode_ctx_t *decode_ctx) |
1121 | 5.07k | { |
1122 | 5.07k | ssize_t slen; |
1123 | 5.07k | uint8_t const *attr, *end; |
1124 | 5.07k | static const uint8_t zeros[RADIUS_AUTH_VECTOR_LENGTH] = {}; |
1125 | | |
1126 | 5.07k | if (!decode_ctx->request_authenticator) { |
1127 | 0 | switch (packet[0]) { |
1128 | 0 | case FR_RADIUS_CODE_ACCESS_REQUEST: |
1129 | 0 | case FR_RADIUS_CODE_STATUS_SERVER: |
1130 | 0 | decode_ctx->request_authenticator = packet + 4; |
1131 | 0 | break; |
1132 | | |
1133 | 0 | case FR_RADIUS_CODE_ACCOUNTING_REQUEST: |
1134 | 0 | case FR_RADIUS_CODE_COA_REQUEST: |
1135 | 0 | case FR_RADIUS_CODE_DISCONNECT_REQUEST: |
1136 | 0 | decode_ctx->request_authenticator = zeros; |
1137 | 0 | break; |
1138 | | |
1139 | 0 | default: |
1140 | 0 | fr_strerror_const("No authentication vector passed for packet decode"); |
1141 | 0 | return -1; |
1142 | 0 | } |
1143 | 0 | } |
1144 | | |
1145 | 5.07k | if (decode_ctx->request_code) { |
1146 | 0 | unsigned int code = packet[0]; |
1147 | |
|
1148 | 0 | if (code >= FR_RADIUS_CODE_MAX) { |
1149 | 0 | return -DECODE_FAIL_UNKNOWN_PACKET_CODE; |
1150 | 0 | } |
1151 | 0 | if (decode_ctx->request_code >= FR_RADIUS_CODE_MAX) { |
1152 | 0 | return -DECODE_FAIL_UNKNOWN_PACKET_CODE; |
1153 | 0 | } |
1154 | | |
1155 | 0 | if (!allowed_replies[code]) { |
1156 | 0 | fr_strerror_printf("%s packet received unknown reply code %s", |
1157 | 0 | fr_radius_packet_name[decode_ctx->request_code], fr_radius_packet_name[code]); |
1158 | 0 | return -DECODE_FAIL_UNKNOWN_PACKET_CODE; |
1159 | 0 | } |
1160 | | |
1161 | | /* |
1162 | | * Protocol error can reply to any packet. |
1163 | | * |
1164 | | * Status-Server can get any reply. |
1165 | | * |
1166 | | * Otherwise the reply code must be associated with the request code we sent. |
1167 | | */ |
1168 | 0 | if ((allowed_replies[code] != decode_ctx->request_code) && |
1169 | 0 | (code != FR_RADIUS_CODE_PROTOCOL_ERROR) && |
1170 | 0 | (decode_ctx->request_code != FR_RADIUS_CODE_STATUS_SERVER)) { |
1171 | 0 | fr_strerror_printf("%s packet received invalid reply code %s", |
1172 | 0 | fr_radius_packet_name[decode_ctx->request_code], fr_radius_packet_name[code]); |
1173 | 0 | return -DECODE_FAIL_UNKNOWN_PACKET_CODE; |
1174 | 0 | } |
1175 | 0 | } |
1176 | | |
1177 | | /* |
1178 | | * We can skip verification for dynamic client checks, and where packets are unsigned as with |
1179 | | * RADIUS/1.1. |
1180 | | */ |
1181 | 5.07k | if (decode_ctx->verify) { |
1182 | 0 | if (!decode_ctx->request_authenticator) decode_ctx->request_authenticator = zeros; |
1183 | |
|
1184 | 0 | if (fr_radius_verify(packet, decode_ctx->request_authenticator, |
1185 | 0 | (uint8_t const *) decode_ctx->common->secret, decode_ctx->common->secret_length, |
1186 | 0 | decode_ctx->require_message_authenticator, decode_ctx->limit_proxy_state) < 0) { |
1187 | 0 | return -1; |
1188 | 0 | } |
1189 | 0 | } |
1190 | | |
1191 | 5.07k | attr = packet + 20; |
1192 | 5.07k | end = packet + packet_len; |
1193 | | |
1194 | | /* |
1195 | | * The caller MUST have called fr_radius_ok() first. If |
1196 | | * he doesn't, all hell breaks loose. |
1197 | | */ |
1198 | 68.3k | while (attr < end) { |
1199 | 63.4k | slen = fr_radius_decode_pair(ctx, out, attr, (end - attr), decode_ctx); |
1200 | 63.4k | if (slen < 0) return slen; |
1201 | | |
1202 | | /* |
1203 | | * If slen is larger than the room in the packet, |
1204 | | * all kinds of bad things happen. |
1205 | | */ |
1206 | 63.3k | if (!fr_cond_assert(slen <= (end - attr))) { |
1207 | 0 | return -slen; |
1208 | 0 | } |
1209 | | |
1210 | 63.3k | attr += slen; |
1211 | 63.3k | talloc_free_children(decode_ctx->tmp_ctx); |
1212 | 63.3k | } |
1213 | | |
1214 | | /* |
1215 | | * We've parsed the whole packet, return that. |
1216 | | */ |
1217 | 4.98k | return packet_len; |
1218 | 5.07k | } |
1219 | | |
1220 | | /** Simple wrapper for callers who just need a shared secret |
1221 | | * |
1222 | | */ |
1223 | | ssize_t fr_radius_decode_simple(TALLOC_CTX *ctx, fr_pair_list_t *out, |
1224 | | uint8_t *packet, size_t packet_len, |
1225 | | uint8_t const *vector, char const *secret) |
1226 | 0 | { |
1227 | 0 | ssize_t rcode; |
1228 | 0 | fr_radius_ctx_t common_ctx = {}; |
1229 | 0 | fr_radius_decode_ctx_t packet_ctx = {}; |
1230 | |
|
1231 | 0 | common_ctx.secret = secret; |
1232 | 0 | common_ctx.secret_length = strlen(secret); |
1233 | |
|
1234 | 0 | packet_ctx.common = &common_ctx; |
1235 | 0 | packet_ctx.tmp_ctx = talloc(ctx, uint8_t); |
1236 | 0 | packet_ctx.request_authenticator = vector; |
1237 | 0 | packet_ctx.end = packet + packet_len; |
1238 | |
|
1239 | 0 | rcode = fr_radius_decode(ctx, out, packet, packet_len, &packet_ctx); |
1240 | 0 | talloc_free(packet_ctx.tmp_ctx); |
1241 | |
|
1242 | 0 | return rcode; |
1243 | 0 | } |
1244 | | |
1245 | | int fr_radius_global_init(void) |
1246 | 4 | { |
1247 | 4 | if (instance_count > 0) { |
1248 | 2 | instance_count++; |
1249 | 2 | return 0; |
1250 | 2 | } |
1251 | | |
1252 | 2 | instance_count++; |
1253 | | |
1254 | 2 | if (fr_dict_autoload(libfreeradius_radius_dict) < 0) { |
1255 | 0 | fail: |
1256 | 0 | instance_count--; |
1257 | 0 | return -1; |
1258 | 0 | } |
1259 | | |
1260 | 2 | if (fr_dict_attr_autoload(libfreeradius_radius_dict_attr) < 0) { |
1261 | 0 | fr_dict_autofree(libfreeradius_radius_dict); |
1262 | 0 | goto fail; |
1263 | 0 | } |
1264 | | |
1265 | 2 | instantiated = true; |
1266 | 2 | return 0; |
1267 | 2 | } |
1268 | | |
1269 | | void fr_radius_global_free(void) |
1270 | 4 | { |
1271 | 4 | if (!instantiated) return; |
1272 | | |
1273 | 4 | if (--instance_count != 0) return; |
1274 | | |
1275 | 2 | fr_dict_autofree(libfreeradius_radius_dict); |
1276 | 2 | } |
1277 | | |
1278 | | static bool attr_valid(fr_dict_attr_t *da) |
1279 | 17.0k | { |
1280 | 17.0k | fr_radius_attr_flags_t const *flags = fr_radius_attr_flags(da); |
1281 | | |
1282 | 17.0k | if (da->parent->type == FR_TYPE_STRUCT) { |
1283 | 346 | if (flags->extended) { |
1284 | 0 | fr_strerror_const("Attributes of type 'extended' cannot be used inside of a 'struct'"); |
1285 | 0 | return false; |
1286 | 0 | } |
1287 | | |
1288 | 346 | if (flags->long_extended) { |
1289 | 0 | fr_strerror_const("Attributes of type 'long_extended' cannot be used inside of a 'struct'"); |
1290 | 0 | return false; |
1291 | 0 | } |
1292 | | |
1293 | | |
1294 | 346 | if (flags->concat) { |
1295 | 0 | fr_strerror_const("Attributes of type 'concat' cannot be used inside of a 'struct'"); |
1296 | 0 | return false; |
1297 | 0 | } |
1298 | | |
1299 | 346 | if (flags->has_tag) { |
1300 | 0 | fr_strerror_const("Attributes of type 'concat' cannot be used inside of a 'struct'"); |
1301 | 0 | return false; |
1302 | 0 | } |
1303 | | |
1304 | 346 | if (flags->abinary) { |
1305 | 0 | fr_strerror_const("Attributes of type 'abinary' cannot be used inside of a 'struct'"); |
1306 | 0 | return false; |
1307 | 0 | } |
1308 | | |
1309 | 346 | if (flags->encrypt > 0) { |
1310 | 0 | fr_strerror_const("Attributes of type 'encrypt' cannot be used inside of a 'struct'"); |
1311 | 0 | return false; |
1312 | 0 | } |
1313 | | |
1314 | 346 | return true; |
1315 | 346 | } |
1316 | | |
1317 | 16.6k | if (da->flags.length > 253) { |
1318 | 0 | fr_strerror_printf("Attributes cannot be more than 253 octets in length"); |
1319 | 0 | return false; |
1320 | 0 | } |
1321 | | /* |
1322 | | * Secret things are secret. |
1323 | | */ |
1324 | 16.6k | if (flags->encrypt != 0) da->flags.secret = true; |
1325 | | |
1326 | 16.6k | if (flags->concat) { |
1327 | 8 | if (!da->parent->flags.is_root) { |
1328 | 0 | fr_strerror_const("Attributes with the 'concat' flag MUST be at the root of the dictionary"); |
1329 | 0 | return false; |
1330 | 0 | } |
1331 | | |
1332 | 8 | if (da->type != FR_TYPE_OCTETS) { |
1333 | 0 | fr_strerror_const("Attributes with the 'concat' flag MUST be of data type 'octets'"); |
1334 | 0 | return false; |
1335 | 0 | } |
1336 | | |
1337 | 8 | return true; /* can't use any other flag */ |
1338 | 8 | } |
1339 | | |
1340 | | /* |
1341 | | * Tagged attributes can only be of two data types. They |
1342 | | * can, however, be VSAs. |
1343 | | */ |
1344 | 16.6k | if (flags->has_tag) { |
1345 | 84 | if ((da->type != FR_TYPE_UINT32) && (da->type != FR_TYPE_STRING)) { |
1346 | 0 | fr_strerror_printf("The 'has_tag' flag can only be used for attributes of type 'integer' " |
1347 | 0 | "or 'string'"); |
1348 | 0 | return false; |
1349 | 0 | } |
1350 | | |
1351 | 84 | if (!(da->parent->flags.is_root || |
1352 | 64 | ((da->parent->type == FR_TYPE_VENDOR) && |
1353 | 64 | (da->parent->parent && da->parent->parent->type == FR_TYPE_VSA)))) { |
1354 | 0 | fr_strerror_const("The 'has_tag' flag can only be used with RFC and VSA attributes"); |
1355 | 0 | return false; |
1356 | 0 | } |
1357 | | |
1358 | 84 | return true; |
1359 | 84 | } |
1360 | | |
1361 | 16.5k | if (flags->extended) { |
1362 | 8 | if (da->type != FR_TYPE_TLV) { |
1363 | 0 | fr_strerror_const("The 'long' or 'extended' flag can only be used for attributes of type 'tlv'"); |
1364 | 0 | return false; |
1365 | 0 | } |
1366 | | |
1367 | 8 | if (!da->parent->flags.is_root) { |
1368 | 0 | fr_strerror_const("The 'long' flag can only be used for top-level RFC attributes"); |
1369 | 0 | return false; |
1370 | 0 | } |
1371 | | |
1372 | 8 | return true; |
1373 | 8 | } |
1374 | | |
1375 | | /* |
1376 | | * Stupid hacks for MS-CHAP-MPPE-Keys. The User-Password |
1377 | | * encryption method has no provisions for encoding the |
1378 | | * length of the data. For User-Password, the data is |
1379 | | * (presumably) all printable non-zero data. For |
1380 | | * MS-CHAP-MPPE-Keys, the data is binary crap. So... we |
1381 | | * MUST specify a length in the dictionary. |
1382 | | */ |
1383 | 16.5k | if ((flags->encrypt == RADIUS_FLAG_ENCRYPT_USER_PASSWORD) && (da->type != FR_TYPE_STRING)) { |
1384 | 2 | if (da->type != FR_TYPE_OCTETS) { |
1385 | 0 | fr_strerror_printf("The 'encrypt=User-Password' flag can only be used with " |
1386 | 0 | "attributes of type 'string'"); |
1387 | 0 | return false; |
1388 | 0 | } |
1389 | | |
1390 | 2 | if (da->flags.length == 0) { |
1391 | 0 | fr_strerror_printf("The 'encrypt=User-Password' flag MUST be used with an explicit length for " |
1392 | 0 | "'octets' data types"); |
1393 | 0 | return false; |
1394 | 0 | } |
1395 | 2 | } |
1396 | | |
1397 | 16.5k | switch (da->type) { |
1398 | 5.88k | case FR_TYPE_STRING: |
1399 | 5.88k | break; |
1400 | | |
1401 | 146 | case FR_TYPE_TLV: |
1402 | 794 | case FR_TYPE_IPV4_ADDR: |
1403 | 6.82k | case FR_TYPE_UINT32: |
1404 | 7.51k | case FR_TYPE_OCTETS: |
1405 | 7.51k | if (flags->encrypt != RADIUS_FLAG_ENCRYPT_ASCEND_SECRET) break; |
1406 | 0 | FALL_THROUGH; |
1407 | |
|
1408 | 3.15k | default: |
1409 | 3.15k | if (flags->encrypt) { |
1410 | 0 | fr_strerror_printf("The 'encrypt' flag cannot be used with attributes of type '%s'", |
1411 | 0 | fr_type_to_str(da->type)); |
1412 | 0 | return false; |
1413 | 0 | } |
1414 | 16.5k | } |
1415 | | |
1416 | 16.5k | return true; |
1417 | 16.5k | } |
1418 | | |
1419 | | extern fr_dict_protocol_t libfreeradius_radius_dict_protocol; |
1420 | | fr_dict_protocol_t libfreeradius_radius_dict_protocol = { |
1421 | | .name = "radius", |
1422 | | .default_type_size = 1, |
1423 | | .default_type_length = 1, |
1424 | | .attr = { |
1425 | | .flags = { |
1426 | | .table = radius_flags, |
1427 | | .table_len = NUM_ELEMENTS(radius_flags), |
1428 | | .len = sizeof(fr_radius_attr_flags_t), |
1429 | | }, |
1430 | | .valid = attr_valid, |
1431 | | }, |
1432 | | |
1433 | | .init = fr_radius_global_init, |
1434 | | .free = fr_radius_global_free, |
1435 | | |
1436 | | .decode = fr_radius_decode_foreign, |
1437 | | .encode = fr_radius_encode_foreign, |
1438 | | }; |