/src/freeradius-server/src/protocols/tacacs/decode.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: 15406fd819d491e2576a2cfe8ec4c21846cf336d $ |
19 | | * |
20 | | * @file protocols/tacacs/decode.c |
21 | | * @brief Low-Level TACACS+ decoding functions |
22 | | * |
23 | | * @copyright 2017 The FreeRADIUS server project |
24 | | * @copyright 2017 Network RADIUS SAS (legal@networkradius.com) |
25 | | */ |
26 | | |
27 | | #include <freeradius-devel/io/test_point.h> |
28 | | #include <freeradius-devel/protocol/tacacs/tacacs.h> |
29 | | #include <freeradius-devel/util/debug.h> |
30 | | #include <freeradius-devel/util/net.h> |
31 | | #include <freeradius-devel/util/struct.h> |
32 | | |
33 | | #include "tacacs.h" |
34 | | #include "attrs.h" |
35 | | |
36 | | int fr_tacacs_packet_to_code(fr_tacacs_packet_t const *pkt) |
37 | 0 | { |
38 | 0 | switch (pkt->hdr.type) { |
39 | 0 | case FR_TAC_PLUS_AUTHEN: |
40 | 0 | if (pkt->hdr.seq_no == 1) return FR_PACKET_TYPE_VALUE_AUTHENTICATION_START; |
41 | | |
42 | 0 | if ((pkt->hdr.seq_no & 0x01) == 1) { |
43 | 0 | if (pkt->authen_cont.flags == FR_TAC_PLUS_CONTINUE_FLAG_UNSET) return FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE; |
44 | | |
45 | 0 | if (pkt->authen_cont.flags == FR_TAC_PLUS_CONTINUE_FLAG_ABORT) return FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE_ABORT; |
46 | | |
47 | 0 | fr_strerror_printf("Invalid value %d for authentication continue flag", pkt->authen_cont.flags); |
48 | 0 | return -1; |
49 | 0 | } |
50 | | |
51 | 0 | switch (pkt->authen_reply.status) { |
52 | 0 | case FR_TAC_PLUS_AUTHEN_STATUS_PASS: |
53 | 0 | return FR_PACKET_TYPE_VALUE_AUTHENTICATION_PASS; |
54 | | |
55 | 0 | case FR_TAC_PLUS_AUTHEN_STATUS_FAIL: |
56 | 0 | return FR_PACKET_TYPE_VALUE_AUTHENTICATION_FAIL; |
57 | | |
58 | 0 | case FR_TAC_PLUS_AUTHEN_STATUS_GETDATA: |
59 | 0 | return FR_PACKET_TYPE_VALUE_AUTHENTICATION_GETDATA; |
60 | | |
61 | 0 | case FR_TAC_PLUS_AUTHEN_STATUS_GETUSER: |
62 | 0 | return FR_PACKET_TYPE_VALUE_AUTHENTICATION_GETUSER; |
63 | | |
64 | 0 | case FR_TAC_PLUS_AUTHEN_STATUS_GETPASS: |
65 | 0 | return FR_PACKET_TYPE_VALUE_AUTHENTICATION_GETPASS; |
66 | | |
67 | 0 | case FR_TAC_PLUS_AUTHEN_STATUS_RESTART: |
68 | 0 | return FR_PACKET_TYPE_VALUE_AUTHENTICATION_RESTART; |
69 | | |
70 | 0 | case FR_TAC_PLUS_AUTHEN_STATUS_ERROR: |
71 | 0 | return FR_PACKET_TYPE_VALUE_AUTHENTICATION_ERROR; |
72 | | |
73 | 0 | default: |
74 | 0 | break; |
75 | 0 | } |
76 | | |
77 | 0 | fr_strerror_printf("Invalid value %d for authentication reply status", pkt->authen_reply.status); |
78 | 0 | return -1; |
79 | | |
80 | 0 | case FR_TAC_PLUS_AUTHOR: |
81 | 0 | if ((pkt->hdr.seq_no & 0x01) == 1) { |
82 | 0 | return FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST; |
83 | 0 | } |
84 | | |
85 | 0 | switch (pkt->author_reply.status) { |
86 | 0 | case FR_TAC_PLUS_AUTHOR_STATUS_PASS_ADD: |
87 | 0 | return FR_PACKET_TYPE_VALUE_AUTHORIZATION_PASS_ADD; |
88 | | |
89 | 0 | case FR_TAC_PLUS_AUTHOR_STATUS_PASS_REPL: |
90 | 0 | return FR_PACKET_TYPE_VALUE_AUTHORIZATION_PASS_REPLACE; |
91 | | |
92 | 0 | case FR_TAC_PLUS_AUTHOR_STATUS_FAIL: |
93 | 0 | return FR_PACKET_TYPE_VALUE_AUTHORIZATION_FAIL; |
94 | | |
95 | 0 | default: |
96 | 0 | break; |
97 | 0 | } |
98 | | |
99 | 0 | fr_strerror_printf("Invalid value %d for authorization reply status", pkt->author_reply.status); |
100 | 0 | return -1; |
101 | | |
102 | 0 | case FR_TAC_PLUS_ACCT: |
103 | 0 | if ((pkt->hdr.seq_no & 0x01) == 1) { |
104 | 0 | return FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST; |
105 | 0 | } |
106 | | |
107 | 0 | switch (pkt->acct_reply.status) { |
108 | 0 | case FR_TAC_PLUS_ACCT_STATUS_SUCCESS: |
109 | 0 | return FR_PACKET_TYPE_VALUE_ACCOUNTING_SUCCESS; |
110 | | |
111 | 0 | case FR_TAC_PLUS_ACCT_STATUS_ERROR: |
112 | 0 | return FR_PACKET_TYPE_VALUE_ACCOUNTING_ERROR; |
113 | | |
114 | 0 | default: |
115 | 0 | break; |
116 | 0 | } |
117 | | |
118 | 0 | fr_strerror_printf("Invalid value %d for accounting reply status", pkt->acct_reply.status); |
119 | 0 | return -1; |
120 | | |
121 | 0 | default: |
122 | 0 | fr_strerror_const("Invalid header type"); |
123 | 0 | return -1; |
124 | 0 | } |
125 | 0 | } |
126 | | |
127 | 5.29k | #define PACKET_HEADER_CHECK(_msg, _hdr) do { \ |
128 | 5.29k | p = buffer + FR_HEADER_LENGTH; \ |
129 | 5.29k | if (sizeof(_hdr) > (size_t) (end - p)) { \ |
130 | 37 | fr_strerror_printf("Header for %s is too small (%zu < %zu)", _msg, (size_t) (end - (uint8_t const *) pkt), (size_t) (p - (uint8_t const *) pkt)); \ |
131 | 37 | goto fail; \ |
132 | 37 | } \ |
133 | 5.29k | body = p + sizeof(_hdr); \ |
134 | 5.26k | data_len = sizeof(_hdr); \ |
135 | 5.26k | } while (0) |
136 | | |
137 | | /* |
138 | | * Check argv[i] after the user_msg / server_msg / argc lengths have been added to data_len |
139 | | */ |
140 | 4.99k | #define ARG_COUNT_CHECK(_msg, _hdr) do { \ |
141 | 4.99k | fr_assert(p == (uint8_t const *) &(_hdr)); \ |
142 | 4.99k | if (data_len > (size_t) (end - p)) { \ |
143 | 65 | fr_strerror_printf("Argument count %u overflows the remaining data (%zu) in the %s packet", _hdr.arg_cnt, (size_t) (end - p), _msg); \ |
144 | 65 | goto fail; \ |
145 | 65 | } \ |
146 | 4.99k | argv = body; \ |
147 | 4.92k | attrs = buffer + FR_HEADER_LENGTH + data_len; \ |
148 | 4.92k | body += _hdr.arg_cnt; \ |
149 | 4.92k | p = attrs; \ |
150 | 82.0k | for (unsigned int i = 0; i < _hdr.arg_cnt; i++) { \ |
151 | 77.1k | if (_hdr.arg_len[i] > (size_t) (end - p)) { \ |
152 | 50 | fr_strerror_printf("Argument %u length %u overflows packet", i, _hdr.arg_len[i]); \ |
153 | 50 | goto fail; \ |
154 | 50 | } \ |
155 | 77.1k | p += _hdr.arg_len[i]; \ |
156 | 77.1k | } \ |
157 | 4.92k | } while (0) |
158 | | |
159 | 13.0k | #define DECODE_FIELD_UINT8(_da, _field) do { \ |
160 | 13.0k | vp = fr_pair_afrom_da(ctx, _da); \ |
161 | 13.0k | if (!vp) goto fail; \ |
162 | 13.0k | PAIR_ALLOCED(vp); \ |
163 | 13.0k | vp->vp_uint8 = _field; \ |
164 | 13.0k | fr_pair_append(out, vp); \ |
165 | 13.0k | } while (0) |
166 | | |
167 | 2.52k | #define DECODE_FIELD_STRING8(_da, _field) do { \ |
168 | 2.52k | if (tacacs_decode_field(ctx, out, _da, &p, \ |
169 | 2.52k | _field, end) < 0) goto fail; \ |
170 | 2.52k | } while (0) |
171 | | |
172 | 7.96k | #define DECODE_FIELD_STRING16(_da, _field) do { \ |
173 | 7.96k | if (tacacs_decode_field(ctx, out, _da, &p, \ |
174 | 7.96k | ntohs(_field), end) < 0) goto fail; \ |
175 | 7.96k | } while (0) |
176 | | |
177 | 6 | #define BODY(_x) (((uint8_t const *) pkt) + sizeof(pkt->hdr) + sizeof(pkt->_x)) |
178 | | |
179 | | /** Decode a TACACS+ 'arg_N' fields. |
180 | | * |
181 | | */ |
182 | | static int tacacs_decode_args(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, |
183 | | uint8_t arg_cnt, uint8_t const *argv, uint8_t const *attrs, NDEBUG_UNUSED uint8_t const *end) |
184 | 4.87k | { |
185 | 4.87k | uint8_t i; |
186 | 4.87k | bool append = false; |
187 | 4.87k | uint8_t const *p = attrs; |
188 | 4.87k | fr_pair_t *vp; |
189 | 4.87k | fr_pair_t *vendor = NULL; |
190 | 4.87k | fr_dict_attr_t const *root; |
191 | | |
192 | | /* |
193 | | * No one? Just get out! |
194 | | */ |
195 | 4.87k | if (!arg_cnt) return 0; |
196 | | |
197 | | /* |
198 | | * Try to decode as nested attributes. If we can't, everything is |
199 | | * |
200 | | * Argument-List = "foo=bar" |
201 | | */ |
202 | 4.85k | if (parent) { |
203 | 0 | vendor = fr_pair_find_by_da(out, NULL, parent); |
204 | 0 | if (!vendor) { |
205 | 0 | vendor = fr_pair_afrom_da(ctx, parent); |
206 | 0 | if (!vendor) return -1; |
207 | 0 | PAIR_ALLOCED(vendor); |
208 | |
|
209 | 0 | append = true; |
210 | 0 | } |
211 | 0 | } |
212 | | |
213 | 4.85k | root = fr_dict_root(dict_tacacs); |
214 | | |
215 | | /* |
216 | | * Then, do the dirty job of creating attributes. |
217 | | */ |
218 | 81.4k | for (i = 0; i < arg_cnt; i++) { |
219 | 76.5k | uint8_t const *value, *name_end, *arg_end; |
220 | 76.5k | fr_dict_attr_t const *da; |
221 | 76.5k | fr_pair_list_t *dst; |
222 | 76.5k | uint8_t buffer[256]; |
223 | | |
224 | 76.5k | fr_assert((p + argv[i]) <= end); |
225 | | |
226 | 76.5k | if (argv[i] < 2) goto next; /* skip malformed */ |
227 | | |
228 | 66.6k | memcpy(buffer, p, argv[i]); |
229 | 66.6k | buffer[argv[i]] = '\0'; |
230 | | |
231 | 66.6k | arg_end = buffer + argv[i]; |
232 | | |
233 | 440k | for (value = buffer, name_end = NULL; value < arg_end; value++) { |
234 | | /* |
235 | | * RFC 8907 Section 3.7 says control |
236 | | * characters MUST be excluded. |
237 | | */ |
238 | 435k | if (*value < ' ') goto next; |
239 | | |
240 | 418k | if ((*value == '=') || (*value == '*')) { |
241 | 45.1k | name_end = value; |
242 | 45.1k | buffer[value - buffer] = '\0'; |
243 | 45.1k | value++; |
244 | 45.1k | break; |
245 | 45.1k | } |
246 | 418k | } |
247 | | |
248 | | /* |
249 | | * Skip fields which aren't in "name=value" or "name*value" format. |
250 | | */ |
251 | 49.7k | if (!name_end) goto next; |
252 | | |
253 | | /* |
254 | | * Prefer to decode from the attribute root, first. |
255 | | */ |
256 | 45.1k | da = fr_dict_attr_by_name(NULL, root, (char *) buffer); |
257 | 45.1k | if (da) { |
258 | 21.1k | vp = fr_pair_afrom_da(ctx, da); |
259 | 21.1k | if (!vp) goto oom; |
260 | 21.1k | PAIR_ALLOCED(vp); |
261 | | |
262 | 21.1k | dst = out; |
263 | 21.1k | goto decode; |
264 | 21.1k | } |
265 | | |
266 | | /* |
267 | | * If the attribute isn't in the main dictionary, |
268 | | * maybe it's in the vendor dictionary? |
269 | | */ |
270 | 24.0k | if (vendor) { |
271 | 0 | da = fr_dict_attr_by_name(NULL, parent, (char *) buffer); |
272 | 0 | if (!da) goto raw; |
273 | | |
274 | 0 | vp = fr_pair_afrom_da(vendor, da); |
275 | 0 | if (!vp) goto oom; |
276 | 0 | PAIR_ALLOCED(vp); |
277 | |
|
278 | 0 | dst = &vendor->vp_group; |
279 | |
|
280 | 21.1k | decode: |
281 | | /* |
282 | | * If it's OCTETS or STRING type, then just copy the value verbatim, as the |
283 | | * contents are (should be?) binary-safe. But if it's zero length, then don't need to |
284 | | * copy anything. |
285 | | * |
286 | | * Note that we copy things manually here because |
287 | | * we don't want the OCTETS type to be parsed as |
288 | | * hex. And, we don't want the string type to be |
289 | | * unescaped. |
290 | | */ |
291 | 21.1k | if (da->type == FR_TYPE_OCTETS) { |
292 | 110 | if ((arg_end > value) && |
293 | 83 | (fr_pair_value_memdup(vp, value, arg_end - value, true) < 0)) { |
294 | 0 | goto fail; |
295 | 0 | } |
296 | | |
297 | 21.0k | } else if (da->type == FR_TYPE_STRING) { |
298 | 81 | if ((arg_end > value) && |
299 | 41 | (fr_pair_value_bstrndup(vp, (char const *) value, arg_end - value, true) < 0)) { |
300 | 0 | goto fail; |
301 | 0 | } |
302 | | |
303 | 20.9k | } else if (arg_end == value) { |
304 | | /* |
305 | | * Any other leaf type MUST have non-zero contents. |
306 | | */ |
307 | 105 | talloc_free(vp); |
308 | 105 | goto raw; |
309 | | |
310 | 20.8k | } else { |
311 | | /* |
312 | | * Parse the string, and try to convert it to the |
313 | | * underlying data type. If it can't be |
314 | | * converted as a data type, just convert it as |
315 | | * Argument-List. |
316 | | * |
317 | | * And if that fails, just ignore it completely. |
318 | | */ |
319 | 20.8k | if (fr_pair_value_from_str(vp, (char const *) value, arg_end - value, NULL, true) < 0) { |
320 | 18.1k | talloc_free(vp); |
321 | 18.1k | goto raw; |
322 | 18.1k | } |
323 | | |
324 | | /* |
325 | | * Else it parsed fine, append it to the output vendor list. |
326 | | */ |
327 | 20.8k | } |
328 | | |
329 | 2.86k | fr_pair_append(dst, vp); |
330 | | |
331 | 24.0k | } else { |
332 | 42.2k | raw: |
333 | 42.2k | vp = fr_pair_afrom_da(ctx, attr_tacacs_argument_list); |
334 | 42.2k | if (!vp) { |
335 | 0 | oom: |
336 | 0 | fr_strerror_const("Out of Memory"); |
337 | 0 | fail: |
338 | 0 | if (append) { |
339 | 0 | talloc_free(vendor); |
340 | 0 | } else { |
341 | 0 | talloc_free(vp); |
342 | 0 | } |
343 | 0 | return -1; |
344 | 0 | } |
345 | 42.2k | PAIR_ALLOCED(vp); |
346 | | |
347 | 42.2k | value = p; |
348 | 42.2k | arg_end = p + argv[i]; |
349 | | |
350 | 42.2k | if ((arg_end > value) && |
351 | 42.2k | (fr_pair_value_bstrndup(vp, (char const *) value, arg_end - value, true) < 0)) { |
352 | 0 | goto fail; |
353 | 0 | } |
354 | | |
355 | 42.2k | fr_pair_append(out, vp); |
356 | 42.2k | } |
357 | | |
358 | 76.5k | next: |
359 | 76.5k | p += argv[i]; |
360 | 76.5k | } |
361 | | |
362 | 4.85k | if (append) { |
363 | 0 | if (fr_pair_list_num_elements(&vendor->vp_group) > 0) { |
364 | 0 | fr_pair_append(out, vendor); |
365 | 0 | } else { |
366 | 0 | talloc_free(vendor); |
367 | 0 | } |
368 | 0 | } |
369 | | |
370 | 4.85k | return 0; |
371 | 4.85k | } |
372 | | |
373 | | /** |
374 | | * Decode a TACACS+ field. |
375 | | */ |
376 | | static int tacacs_decode_field(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *da, |
377 | | uint8_t const **field_data, uint16_t field_len, uint8_t const *end) |
378 | 10.4k | { |
379 | 10.4k | uint8_t const *p = *field_data; |
380 | 10.4k | fr_pair_t *vp; |
381 | | |
382 | 10.4k | if (field_len > (end - p)) { |
383 | 0 | fr_strerror_printf("'%s' length %u overflows the remaining data (%zu) in the packet", |
384 | 0 | da->name, field_len, (size_t) (end - p)); |
385 | 0 | return -1; |
386 | 0 | } |
387 | | |
388 | 10.4k | vp = fr_pair_afrom_da(ctx, da); |
389 | 10.4k | if (!vp) { |
390 | 0 | fr_strerror_const("Out of Memory"); |
391 | 0 | return -1; |
392 | 0 | } |
393 | 10.4k | PAIR_ALLOCED(vp); |
394 | | |
395 | 10.4k | if (field_len) { |
396 | 1.20k | if (da->type == FR_TYPE_STRING) { |
397 | 1.08k | fr_pair_value_bstrndup(vp, (char const *)p, field_len, true); |
398 | 1.08k | } else if (da->type == FR_TYPE_OCTETS) { |
399 | 119 | fr_pair_value_memdup(vp, p, field_len, true); |
400 | 119 | } else { |
401 | 0 | fr_assert(0); |
402 | 0 | } |
403 | 1.20k | p += field_len; |
404 | 1.20k | *field_data = p; |
405 | 1.20k | } |
406 | | |
407 | 10.4k | fr_pair_append(out, vp); |
408 | | |
409 | 10.4k | return 0; |
410 | 10.4k | } |
411 | | |
412 | | /** |
413 | | * Decode a TACACS+ packet |
414 | | */ |
415 | | ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *vendor, |
416 | | uint8_t const *buffer, size_t buffer_len, |
417 | | const uint8_t *original, char const * const secret, size_t secret_len, int *code) |
418 | 5.39k | { |
419 | 5.39k | fr_tacacs_packet_t const *pkt; |
420 | 5.39k | fr_pair_t *vp; |
421 | 5.39k | size_t data_len; |
422 | 5.39k | uint8_t const *p, *body, *argv, *attrs, *end; |
423 | 5.39k | uint8_t *decrypted = NULL; |
424 | | |
425 | | /* |
426 | | * 3.4. The TACACS+ Packet Header |
427 | | * |
428 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
429 | | * +----------------+----------------+----------------+----------------+ |
430 | | * |major | minor | | | | |
431 | | * |version| version| type | seq_no | flags | |
432 | | * +----------------+----------------+----------------+----------------+ |
433 | | * | | |
434 | | * | session_id | |
435 | | * +----------------+----------------+----------------+----------------+ |
436 | | * | | |
437 | | * | length | |
438 | | * +----------------+----------------+----------------+----------------+ |
439 | | */ |
440 | 5.39k | pkt = (fr_tacacs_packet_t const *) buffer; |
441 | | |
442 | | /* |
443 | | * p miscellaneous pointer for decoding things |
444 | | * body points to just past the (randomly sized) per-packet header, |
445 | | * where the various user / server messages are. |
446 | | * sometimes this is after "argv". |
447 | | * argv points to the array of argv[i] length entries |
448 | | * attrs points to the attributes we need to decode as "foo=bar". |
449 | | */ |
450 | 5.39k | argv = attrs = NULL; |
451 | 5.39k | end = buffer + buffer_len; |
452 | | |
453 | | /* |
454 | | * Check that we have a full TACACS+ header before |
455 | | * decoding anything. |
456 | | */ |
457 | 5.39k | if (buffer_len < sizeof(pkt->hdr)) { |
458 | 6 | fr_strerror_printf("Packet is too small (%zu < 12) to be TACACS+.", buffer_len); |
459 | 6 | return -1; |
460 | 6 | } |
461 | | |
462 | | /* |
463 | | * TACACS major / minor version MUST be 12.0 or 12.1 |
464 | | */ |
465 | 5.38k | if (!(pkt->hdr.ver.major == 12 && (pkt->hdr.ver.minor == 0 || pkt->hdr.ver.minor == 1))) { |
466 | 14 | fr_strerror_printf("Unsupported TACACS+ version %d.%d (%02x)", pkt->hdr.ver.major, pkt->hdr.ver.minor, buffer[0]); |
467 | 14 | return -1; |
468 | 14 | } |
469 | | |
470 | | /* |
471 | | * There's no reason to accept 64K TACACS+ packets. |
472 | | * |
473 | | * In any case, the largest possible packet has the |
474 | | * header, plus 2 16-bit fields, plus 255 8-bit fields, |
475 | | * which is a bit under 2^18. |
476 | | */ |
477 | 5.37k | if ((buffer[8] != 0) || (buffer[9] != 0)) { |
478 | 16 | fr_strerror_const("Packet is too large. Our limit is 64K"); |
479 | 16 | return -1; |
480 | 16 | } |
481 | | |
482 | | /* |
483 | | * As a stream protocol, the TACACS+ packet MUST fit |
484 | | * exactly into however many bytes we read. |
485 | | */ |
486 | 5.35k | if ((buffer + sizeof(pkt->hdr) + ntohl(pkt->hdr.length)) != end) { |
487 | 42 | fr_strerror_const("Packet does not exactly fill buffer"); |
488 | 42 | return -1; |
489 | 42 | } |
490 | | |
491 | | /* |
492 | | * There are only 3 types of packets which are supported. |
493 | | */ |
494 | 5.31k | if (!((pkt->hdr.type == FR_TAC_PLUS_AUTHEN) || |
495 | 5.09k | (pkt->hdr.type == FR_TAC_PLUS_AUTHOR) || |
496 | 357 | (pkt->hdr.type == FR_TAC_PLUS_ACCT))) { |
497 | 12 | fr_strerror_printf("Unknown packet type %d", pkt->hdr.type); |
498 | 12 | return -1; |
499 | 12 | } |
500 | | |
501 | | /* |
502 | | * Check that the session IDs are correct. |
503 | | */ |
504 | 5.30k | if (original && (memcmp(original + 4, buffer + 4, 4) != 0)) { |
505 | 0 | fr_strerror_printf("Session ID %08x does not match expected number %08x", |
506 | 0 | fr_nbo_to_uint32(buffer + 4), fr_nbo_to_uint32(original + 4)); |
507 | 0 | return -1; |
508 | 0 | } |
509 | | |
510 | 5.30k | if (!secret && packet_is_encrypted(pkt)) { |
511 | 0 | fr_strerror_const("Packet is encrypted, but there is no secret to decrypt it"); |
512 | 0 | return -1; |
513 | 0 | } |
514 | | |
515 | | /* |
516 | | * Call the struct encoder to do the actual work. |
517 | | */ |
518 | 5.30k | if (fr_struct_from_network(ctx, out, attr_tacacs_packet, buffer, buffer_len, NULL, NULL, NULL) < 0) { |
519 | 0 | fr_strerror_printf("Failed decoding TACACS header - %s", fr_strerror()); |
520 | 0 | return -1; |
521 | 0 | } |
522 | | |
523 | | /* |
524 | | * 3.6. Encryption |
525 | | * |
526 | | * If there's a secret, we always decrypt the packets. |
527 | | */ |
528 | 5.30k | if (secret && packet_is_encrypted(pkt)) { |
529 | 105 | size_t length; |
530 | | |
531 | 105 | if (!secret_len) { |
532 | 0 | fr_strerror_const("Packet should be encrypted, but the secret has zero length"); |
533 | 0 | return -1; |
534 | 0 | } |
535 | | |
536 | 105 | length = ntohl(pkt->hdr.length); |
537 | | |
538 | | /* |
539 | | * We need that to decrypt the body content. |
540 | | * |
541 | | * @todo - use thread-local storage to avoid allocations? |
542 | | */ |
543 | 105 | decrypted = talloc_memdup(ctx, buffer, buffer_len); |
544 | 105 | if (!decrypted) { |
545 | 0 | fr_strerror_const("Out of Memory"); |
546 | 0 | return -1; |
547 | 0 | } |
548 | | |
549 | 105 | pkt = (fr_tacacs_packet_t const *) decrypted; |
550 | 105 | end = decrypted + buffer_len; |
551 | | |
552 | 105 | if (fr_tacacs_body_xor(pkt, decrypted + sizeof(pkt->hdr), length, secret, secret_len) < 0) { |
553 | 360 | fail: |
554 | 360 | talloc_free(decrypted); |
555 | 360 | return -1; |
556 | 0 | } |
557 | | |
558 | 105 | decrypted[3] |= FR_TAC_PLUS_UNENCRYPTED_FLAG; |
559 | | |
560 | 105 | FR_PROTO_HEX_DUMP(decrypted, buffer_len, "fr_tacacs_packet_t (unencrypted)"); |
561 | | |
562 | 105 | buffer = decrypted; |
563 | 105 | } |
564 | | |
565 | 5.30k | #ifndef NDEBUG |
566 | 5.30k | if (fr_debug_lvl >= L_DBG_LVL_4) fr_tacacs_packet_log_hex(&default_log, pkt, (end - buffer)); |
567 | 5.30k | #endif |
568 | | |
569 | 5.30k | if (code) { |
570 | 0 | *code = fr_tacacs_packet_to_code((fr_tacacs_packet_t const *) buffer); |
571 | 0 | if (*code < 0) goto fail; |
572 | 0 | } |
573 | | |
574 | 5.30k | switch (pkt->hdr.type) { |
575 | 224 | case FR_TAC_PLUS_AUTHEN: |
576 | 224 | if (packet_is_authen_start_request(pkt)) { |
577 | 93 | uint8_t want; |
578 | 93 | bool raw; |
579 | 93 | fr_dict_attr_t const *da, *challenge; |
580 | | |
581 | | /** |
582 | | * 4.1. The Authentication START Packet Body |
583 | | * |
584 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
585 | | * +----------------+----------------+----------------+----------------+ |
586 | | * | action | priv_lvl | authen_type | authen_service | |
587 | | * +----------------+----------------+----------------+----------------+ |
588 | | * | user_len | port_len | rem_addr_len | data_len | |
589 | | * +----------------+----------------+----------------+----------------+ |
590 | | * | user ... |
591 | | * +----------------+----------------+----------------+----------------+ |
592 | | * | port ... |
593 | | * +----------------+----------------+----------------+----------------+ |
594 | | * | rem_addr ... |
595 | | * +----------------+----------------+----------------+----------------+ |
596 | | * | data... |
597 | | * +----------------+----------------+----------------+----------------+ |
598 | | */ |
599 | 93 | PACKET_HEADER_CHECK("Authentication-Start", pkt->authen_start); |
600 | | |
601 | 88 | data_len += p[4] + p[5] + p[6] + p[7]; |
602 | 88 | if (data_len > (size_t) (end - p)) { |
603 | 127 | overflow: |
604 | 127 | if ((buffer[3] & FR_TAC_PLUS_UNENCRYPTED_FLAG) == 0) { |
605 | 0 | bad_secret: |
606 | 0 | fr_strerror_const("Invalid packet after decryption - is the secret key incorrect?"); |
607 | 0 | goto fail; |
608 | 0 | } |
609 | | |
610 | 127 | fr_strerror_const("Data overflows the packet"); |
611 | 127 | goto fail; |
612 | 127 | } |
613 | 67 | if (data_len < (size_t) (end - p)) { |
614 | 72 | underflow: |
615 | 72 | if ((buffer[3] & FR_TAC_PLUS_UNENCRYPTED_FLAG) == 0) goto bad_secret; |
616 | | |
617 | 72 | fr_strerror_const("Data underflows the packet"); |
618 | 72 | goto fail; |
619 | 72 | } |
620 | | |
621 | 46 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_START); |
622 | | |
623 | | /* |
624 | | * Decode 4 octets of various flags. |
625 | | */ |
626 | 46 | DECODE_FIELD_UINT8(attr_tacacs_action, pkt->authen_start.action); |
627 | 46 | DECODE_FIELD_UINT8(attr_tacacs_privilege_level, pkt->authen_start.priv_lvl); |
628 | 46 | DECODE_FIELD_UINT8(attr_tacacs_authentication_type, pkt->authen_start.authen_type); |
629 | 46 | DECODE_FIELD_UINT8(attr_tacacs_authentication_service, pkt->authen_start.authen_service); |
630 | | |
631 | | /* |
632 | | * Decode 3 fields, based on their "length" |
633 | | * user and rem_addr are optional - indicated by zero length |
634 | | */ |
635 | 46 | p = body; |
636 | 46 | if (pkt->authen_start.user_len > 0) DECODE_FIELD_STRING8(attr_tacacs_user_name, |
637 | 46 | pkt->authen_start.user_len); |
638 | 46 | DECODE_FIELD_STRING8(attr_tacacs_client_port, pkt->authen_start.port_len); |
639 | 46 | if (pkt->authen_start.rem_addr_len > 0) DECODE_FIELD_STRING8(attr_tacacs_remote_address, |
640 | 46 | pkt->authen_start.rem_addr_len); |
641 | | |
642 | | /* |
643 | | * Check the length on the various |
644 | | * authentication types. |
645 | | */ |
646 | 46 | raw = false; |
647 | 46 | challenge = NULL; |
648 | | |
649 | 46 | switch (pkt->authen_start.authen_type) { |
650 | 12 | default: |
651 | 12 | raw = true; |
652 | 12 | want = pkt->authen_start.data_len; |
653 | 12 | da = attr_tacacs_data; |
654 | 12 | break; |
655 | | |
656 | 4 | case FR_AUTHENTICATION_TYPE_VALUE_PAP: |
657 | 4 | want = pkt->authen_start.data_len; |
658 | 4 | da = attr_tacacs_user_password; |
659 | 4 | break; |
660 | | |
661 | 16 | case FR_AUTHENTICATION_TYPE_VALUE_CHAP: |
662 | 16 | want = 1 + 16; /* id + HOPEFULLY 8 octets of challenge + 16 hash */ |
663 | 16 | da = attr_tacacs_chap_password; |
664 | 16 | challenge = attr_tacacs_chap_challenge; |
665 | 16 | break; |
666 | | |
667 | 8 | case FR_AUTHENTICATION_TYPE_VALUE_MSCHAP: |
668 | 8 | want = 1 + 49; /* id + HOPEFULLY 8 octets of challenge + 49 MS-CHAP stuff */ |
669 | 8 | da = attr_tacacs_mschap_response; |
670 | 8 | challenge = attr_tacacs_mschap_challenge; |
671 | 8 | break; |
672 | | |
673 | 6 | case FR_AUTHENTICATION_TYPE_VALUE_MSCHAPV2: |
674 | 6 | want = 1 + 49; /* id + HOPEFULLY 16 octets of challenge + 49 MS-CHAP stuff */ |
675 | 6 | da = attr_tacacs_mschap2_response; |
676 | 6 | challenge = attr_tacacs_mschap_challenge; |
677 | 6 | break; |
678 | 46 | } |
679 | | |
680 | | /* |
681 | | * If we have enough data, decode it as |
682 | | * the claimed authentication type. |
683 | | * |
684 | | * Otherwise, decode the entire field as an unknown |
685 | | * attribute. |
686 | | */ |
687 | 46 | if (raw || (pkt->authen_start.data_len < want)) { |
688 | 22 | fr_dict_attr_t *da_unknown; |
689 | | |
690 | 22 | da_unknown = fr_dict_attr_unknown_raw_afrom_num(ctx, fr_dict_root(dict_tacacs), |
691 | 22 | attr_tacacs_data->attr); |
692 | 22 | if (!da_unknown) goto fail; |
693 | | |
694 | 22 | want = pkt->authen_start.data_len; |
695 | | |
696 | 22 | DECODE_FIELD_STRING8(da_unknown, want); |
697 | 22 | talloc_free(da_unknown); |
698 | | |
699 | 24 | } else if (!challenge) { |
700 | 4 | DECODE_FIELD_STRING8(da, want); |
701 | | |
702 | 20 | } else if (pkt->authen_start.data_len == want) { |
703 | 4 | fr_strerror_printf("%s has zero length", challenge->name); |
704 | 4 | goto fail; |
705 | | |
706 | 16 | } else { /* 1 of ID + ??? of challenge + (want-1) of data */ |
707 | 16 | uint8_t challenge_len = pkt->authen_start.data_len - want; |
708 | 16 | uint8_t hash[50]; |
709 | | |
710 | | /* |
711 | | * Rework things to make sense. |
712 | | * RFC 8079 says that MS-CHAP responses should follow RFC 2433 and 2759 |
713 | | * which have "Flags" at the end. |
714 | | * RADIUS attributes expect "Flags" after the ID as per RFC 2548. |
715 | | * Re-arrange to make things consistent. |
716 | | */ |
717 | 16 | hash[0] = p[0]; |
718 | 16 | switch (pkt->authen_start.authen_type) { |
719 | 2 | case FR_AUTHENTICATION_TYPE_VALUE_MSCHAP: |
720 | 5 | case FR_AUTHENTICATION_TYPE_VALUE_MSCHAPV2: |
721 | 5 | hash[1] = p[want - 1]; |
722 | 5 | memcpy(hash + 2, p + 1 + challenge_len, want - 2); |
723 | 5 | break; |
724 | | |
725 | 11 | default: |
726 | 11 | memcpy(hash + 1, p + 1 + challenge_len, want - 1); |
727 | 11 | break; |
728 | 16 | } |
729 | | |
730 | 16 | vp = fr_pair_afrom_da(ctx, da); |
731 | 16 | if (!vp) goto fail; |
732 | 16 | PAIR_ALLOCED(vp); |
733 | | |
734 | 16 | fr_pair_append(out, vp); |
735 | | |
736 | | /* |
737 | | * ID + hash |
738 | | */ |
739 | 16 | if (fr_pair_value_memdup(vp, hash, want, true) < 0) goto fail; |
740 | | |
741 | | /* |
742 | | * And then the challenge. |
743 | | */ |
744 | 16 | vp = fr_pair_afrom_da(ctx, challenge); |
745 | 16 | if (!vp) goto fail; |
746 | 16 | PAIR_ALLOCED(vp); |
747 | | |
748 | 16 | fr_pair_append(out, vp); |
749 | | |
750 | 16 | if (fr_pair_value_memdup(vp, p + 1, challenge_len, true) < 0) goto fail; |
751 | | |
752 | 16 | p += pkt->authen_start.data_len; |
753 | 16 | } |
754 | | |
755 | 131 | } else if (packet_is_authen_continue(pkt)) { |
756 | | /* |
757 | | * 4.3. The Authentication CONTINUE Packet Body |
758 | | * |
759 | | * This packet is sent from the client to the server following the receipt of |
760 | | * a REPLY packet. |
761 | | * |
762 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
763 | | * +----------------+----------------+----------------+----------------+ |
764 | | * | user_msg len | data_len | |
765 | | * +----------------+----------------+----------------+----------------+ |
766 | | * | flags | user_msg ... |
767 | | * +----------------+----------------+----------------+----------------+ |
768 | | * | data ... |
769 | | * +----------------+ |
770 | | */ |
771 | | |
772 | | /* |
773 | | * Version 1 is ONLY used for PAP / CHAP |
774 | | * / MS-CHAP start and reply packets. |
775 | | */ |
776 | 73 | if (pkt->hdr.ver.minor != 0) { |
777 | 5 | invalid_version: |
778 | 5 | fr_strerror_const("Invalid TACACS+ version"); |
779 | 5 | goto fail; |
780 | 1 | } |
781 | | |
782 | 72 | PACKET_HEADER_CHECK("Authentication-Continue", pkt->authen_cont); |
783 | 66 | data_len += fr_nbo_to_uint16(p) + fr_nbo_to_uint16(p + 2); |
784 | 66 | if (data_len > (size_t) (end - p)) goto overflow; |
785 | 36 | if (data_len < (size_t) (end - p)) goto underflow; |
786 | | |
787 | 16 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_CONTINUE); |
788 | | |
789 | | /* |
790 | | * Decode 2 fields, based on their "length" |
791 | | */ |
792 | 16 | p = body; |
793 | 16 | DECODE_FIELD_STRING16(attr_tacacs_user_message, pkt->authen_cont.user_msg_len); |
794 | 16 | DECODE_FIELD_STRING16(attr_tacacs_data, pkt->authen_cont.data_len); |
795 | | |
796 | | /* |
797 | | * And finally the flags. |
798 | | */ |
799 | 16 | DECODE_FIELD_UINT8(attr_tacacs_authentication_continue_flags, pkt->authen_cont.flags); |
800 | | |
801 | 58 | } else if (packet_is_authen_reply(pkt)) { |
802 | | /* |
803 | | * 4.2. The Authentication REPLY Packet Body |
804 | | * |
805 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
806 | | * +----------------+----------------+----------------+----------------+ |
807 | | * | status | flags | server_msg_len | |
808 | | * +----------------+----------------+----------------+----------------+ |
809 | | * | data_len | server_msg ... |
810 | | * +----------------+----------------+----------------+----------------+ |
811 | | * | data ... |
812 | | * +----------------+----------------+ |
813 | | */ |
814 | | |
815 | | /* |
816 | | * We don't care about versions for replies. |
817 | | * We just echo whatever was sent in the request. |
818 | | */ |
819 | 58 | PACKET_HEADER_CHECK("Authentication-Reply", pkt->authen_reply); |
820 | 50 | data_len += fr_nbo_to_uint16(p + 2) + fr_nbo_to_uint16(p + 4); |
821 | 50 | if (data_len > (size_t) (end - p)) goto overflow; |
822 | 18 | if (data_len < (size_t) (end - p)) goto underflow; |
823 | | |
824 | 2 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_REPLY); |
825 | | |
826 | 2 | DECODE_FIELD_UINT8(attr_tacacs_authentication_status, pkt->authen_reply.status); |
827 | 2 | DECODE_FIELD_UINT8(attr_tacacs_authentication_flags, pkt->authen_reply.flags); |
828 | | |
829 | | /* |
830 | | * Decode 2 fields, based on their "length" |
831 | | */ |
832 | 2 | p = body; |
833 | 2 | DECODE_FIELD_STRING16(attr_tacacs_server_message, pkt->authen_reply.server_msg_len); |
834 | 2 | DECODE_FIELD_STRING16(attr_tacacs_data, pkt->authen_reply.data_len); |
835 | | |
836 | 2 | } else { |
837 | 0 | fr_strerror_const("Unknown authentication packet"); |
838 | 0 | goto fail; |
839 | 0 | } |
840 | 60 | break; |
841 | | |
842 | 4.73k | case FR_TAC_PLUS_AUTHOR: |
843 | 4.73k | if (packet_is_author_request(pkt)) { |
844 | | /* |
845 | | * 5.1. The Authorization REQUEST Packet Body |
846 | | * |
847 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
848 | | * +----------------+----------------+----------------+----------------+ |
849 | | * | authen_method | priv_lvl | authen_type | authen_service | |
850 | | * +----------------+----------------+----------------+----------------+ |
851 | | * | user_len | port_len | rem_addr_len | arg_cnt | |
852 | | * +----------------+----------------+----------------+----------------+ |
853 | | * | arg_1_len | arg_2_len | ... | arg_N_len | |
854 | | * +----------------+----------------+----------------+----------------+ |
855 | | * | user ... |
856 | | * +----------------+----------------+----------------+----------------+ |
857 | | * | port ... |
858 | | * +----------------+----------------+----------------+----------------+ |
859 | | * | rem_addr ... |
860 | | * +----------------+----------------+----------------+----------------+ |
861 | | * | arg_1 ... |
862 | | * +----------------+----------------+----------------+----------------+ |
863 | | * | arg_2 ... |
864 | | * +----------------+----------------+----------------+----------------+ |
865 | | * | ... |
866 | | * +----------------+----------------+----------------+----------------+ |
867 | | * | arg_N ... |
868 | | * +----------------+----------------+----------------+----------------+ |
869 | | */ |
870 | | |
871 | 718 | if (pkt->hdr.ver.minor != 0) goto invalid_version; |
872 | | |
873 | 716 | PACKET_HEADER_CHECK("Authorization-Request", pkt->author_req); |
874 | 711 | data_len += p[4] + p[5] + p[6] + p[7]; |
875 | | |
876 | 711 | ARG_COUNT_CHECK("Authorization-Request", pkt->author_req); |
877 | | |
878 | 667 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_REQUEST); |
879 | | |
880 | | /* |
881 | | * Decode 4 octets of various flags. |
882 | | */ |
883 | 667 | DECODE_FIELD_UINT8(attr_tacacs_authentication_method, pkt->author_req.authen_method); |
884 | 667 | DECODE_FIELD_UINT8(attr_tacacs_privilege_level, pkt->author_req.priv_lvl); |
885 | 667 | DECODE_FIELD_UINT8(attr_tacacs_authentication_type, pkt->author_req.authen_type); |
886 | 667 | DECODE_FIELD_UINT8(attr_tacacs_authentication_service, pkt->author_req.authen_service); |
887 | | |
888 | | /* |
889 | | * Decode 3 fields, based on their "length" |
890 | | * rem_addr is optional - indicated by zero length |
891 | | */ |
892 | 667 | p = body; |
893 | 667 | DECODE_FIELD_STRING8(attr_tacacs_user_name, pkt->author_req.user_len); |
894 | 667 | DECODE_FIELD_STRING8(attr_tacacs_client_port, pkt->author_req.port_len); |
895 | 667 | if (pkt->author_req.rem_addr_len > 0) DECODE_FIELD_STRING8(attr_tacacs_remote_address, |
896 | 667 | pkt->author_req.rem_addr_len); |
897 | | |
898 | | /* |
899 | | * Decode 'arg_N' arguments (horrible format) |
900 | | */ |
901 | 667 | if (tacacs_decode_args(ctx, out, vendor, |
902 | 667 | pkt->author_req.arg_cnt, argv, attrs, end) < 0) goto fail; |
903 | | |
904 | 4.01k | } else if (packet_is_author_reply(pkt)) { |
905 | | /* |
906 | | * 5.2. The Authorization RESPONSE Packet Body |
907 | | * |
908 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
909 | | * +----------------+----------------+----------------+----------------+ |
910 | | * | status | arg_cnt | server_msg len | |
911 | | * +----------------+----------------+----------------+----------------+ |
912 | | * + data_len | arg_1_len | arg_2_len | |
913 | | * +----------------+----------------+----------------+----------------+ |
914 | | * | ... | arg_N_len | server_msg ... |
915 | | * +----------------+----------------+----------------+----------------+ |
916 | | * | data ... |
917 | | * +----------------+----------------+----------------+----------------+ |
918 | | * | arg_1 ... |
919 | | * +----------------+----------------+----------------+----------------+ |
920 | | * | arg_2 ... |
921 | | * +----------------+----------------+----------------+----------------+ |
922 | | * | ... |
923 | | * +----------------+----------------+----------------+----------------+ |
924 | | * | arg_N ... |
925 | | * +----------------+----------------+----------------+----------------+ |
926 | | */ |
927 | | |
928 | | /* |
929 | | * We don't care about versions for replies. |
930 | | * We just echo whatever was sent in the request. |
931 | | */ |
932 | | |
933 | 4.01k | PACKET_HEADER_CHECK("Authorization-Reply", pkt->author_reply); |
934 | 4.01k | data_len += p[1] + fr_nbo_to_uint16(p + 2) + fr_nbo_to_uint16(p + 4); |
935 | | |
936 | 4.01k | ARG_COUNT_CHECK("Authorization-Reply", pkt->author_reply); |
937 | 3.95k | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_RESPONSE); |
938 | | |
939 | | /* |
940 | | * Decode 1 octets |
941 | | */ |
942 | 3.95k | DECODE_FIELD_UINT8(attr_tacacs_authorization_status, pkt->author_reply.status); |
943 | | |
944 | | /* |
945 | | * Decode 2 fields, based on their "length" |
946 | | */ |
947 | 3.95k | p = body; |
948 | 3.95k | DECODE_FIELD_STRING16(attr_tacacs_server_message, pkt->author_reply.server_msg_len); |
949 | 3.95k | DECODE_FIELD_STRING16(attr_tacacs_data, pkt->author_reply.data_len); |
950 | | |
951 | | /* |
952 | | * Decode 'arg_N' arguments (horrible format) |
953 | | */ |
954 | 3.95k | if (tacacs_decode_args(ctx, out, vendor, |
955 | 3.95k | pkt->author_reply.arg_cnt, argv, attrs, end) < 0) goto fail; |
956 | | |
957 | 3.95k | } else { |
958 | 0 | fr_strerror_const("Unknown authorization packet"); |
959 | 0 | goto fail; |
960 | 0 | } |
961 | 4.62k | break; |
962 | | |
963 | 4.62k | case FR_TAC_PLUS_ACCT: |
964 | 345 | if (packet_is_acct_request(pkt)) { |
965 | | /** |
966 | | * 6.1. The Account REQUEST Packet Body |
967 | | * |
968 | | 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
969 | | * +----------------+----------------+----------------+----------------+ |
970 | | * | flags | authen_method | priv_lvl | authen_type | |
971 | | * +----------------+----------------+----------------+----------------+ |
972 | | * | authen_service | user_len | port_len | rem_addr_len | |
973 | | * +----------------+----------------+----------------+----------------+ |
974 | | * | arg_cnt | arg_1_len | arg_2_len | ... | |
975 | | * +----------------+----------------+----------------+----------------+ |
976 | | * | arg_N_len | user ... |
977 | | * +----------------+----------------+----------------+----------------+ |
978 | | * | port ... |
979 | | * +----------------+----------------+----------------+----------------+ |
980 | | * | rem_addr ... |
981 | | * +----------------+----------------+----------------+----------------+ |
982 | | * | arg_1 ... |
983 | | * +----------------+----------------+----------------+----------------+ |
984 | | * | arg_2 ... |
985 | | * +----------------+----------------+----------------+----------------+ |
986 | | * | ... |
987 | | * +----------------+----------------+----------------+----------------+ |
988 | | * | arg_N ... |
989 | | * +----------------+----------------+----------------+----------------+ |
990 | | */ |
991 | | |
992 | 292 | if (pkt->hdr.ver.minor != 0) goto invalid_version; |
993 | | |
994 | 290 | PACKET_HEADER_CHECK("Accounting-Request", pkt->acct_req); |
995 | 286 | data_len += p[5] + p[6] + p[7] + p[8]; |
996 | 286 | if (data_len > (size_t) (end - p)) goto overflow; |
997 | | /* can't check for underflow, as we have argv[argc] */ |
998 | | |
999 | 270 | ARG_COUNT_CHECK("Accounting-Request", pkt->acct_req); |
1000 | | |
1001 | 254 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_REQUEST); |
1002 | | |
1003 | | /* |
1004 | | * Decode 5 octets of various flags. |
1005 | | */ |
1006 | 254 | DECODE_FIELD_UINT8(attr_tacacs_accounting_flags, pkt->acct_req.flags); |
1007 | 254 | DECODE_FIELD_UINT8(attr_tacacs_authentication_method, pkt->acct_req.authen_method); |
1008 | 254 | DECODE_FIELD_UINT8(attr_tacacs_privilege_level, pkt->acct_req.priv_lvl); |
1009 | 254 | DECODE_FIELD_UINT8(attr_tacacs_authentication_type, pkt->acct_req.authen_type); |
1010 | 254 | DECODE_FIELD_UINT8(attr_tacacs_authentication_service, pkt->acct_req.authen_service); |
1011 | | |
1012 | | /* |
1013 | | * Decode 3 fields, based on their "length" |
1014 | | */ |
1015 | 254 | p = body; |
1016 | 254 | DECODE_FIELD_STRING8(attr_tacacs_user_name, pkt->acct_req.user_len); |
1017 | 254 | DECODE_FIELD_STRING8(attr_tacacs_client_port, pkt->acct_req.port_len); |
1018 | 254 | DECODE_FIELD_STRING8(attr_tacacs_remote_address, pkt->acct_req.rem_addr_len); |
1019 | | |
1020 | | /* |
1021 | | * Decode 'arg_N' arguments (horrible format) |
1022 | | */ |
1023 | 254 | if (tacacs_decode_args(ctx, out, vendor, |
1024 | 254 | pkt->acct_req.arg_cnt, argv, attrs, end) < 0) goto fail; |
1025 | | |
1026 | 254 | } else if (packet_is_acct_reply(pkt)) { |
1027 | | /** |
1028 | | * 6.2. The Accounting REPLY Packet Body |
1029 | | * |
1030 | | * 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 |
1031 | | * +----------------+----------------+----------------+----------------+ |
1032 | | * | server_msg len | data_len | |
1033 | | * +----------------+----------------+----------------+----------------+ |
1034 | | * | status | server_msg ... |
1035 | | * +----------------+----------------+----------------+----------------+ |
1036 | | * | data ... |
1037 | | * +----------------+ |
1038 | | */ |
1039 | | |
1040 | | /* |
1041 | | * We don't care about versions for replies. |
1042 | | * We just echo whatever was sent in the request. |
1043 | | */ |
1044 | | |
1045 | 53 | PACKET_HEADER_CHECK("Accounting-Reply", pkt->acct_reply); |
1046 | 49 | data_len += fr_nbo_to_uint16(p) + fr_nbo_to_uint16(p + 2); |
1047 | 49 | if (data_len > (size_t) (end - p)) goto overflow; |
1048 | 21 | if (data_len < (size_t) (end - p)) goto underflow; |
1049 | | |
1050 | 6 | p = BODY(acct_reply); |
1051 | 6 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_REPLY); |
1052 | | |
1053 | | /* |
1054 | | * Decode 2 fields, based on their "length" |
1055 | | */ |
1056 | 6 | DECODE_FIELD_STRING16(attr_tacacs_server_message, pkt->acct_reply.server_msg_len); |
1057 | 6 | DECODE_FIELD_STRING16(attr_tacacs_data, pkt->acct_reply.data_len); |
1058 | | |
1059 | | /* Decode 1 octet */ |
1060 | 6 | DECODE_FIELD_UINT8(attr_tacacs_accounting_status, pkt->acct_reply.status); |
1061 | 6 | } else { |
1062 | 0 | fr_strerror_const("Unknown accounting packet"); |
1063 | 0 | goto fail; |
1064 | 0 | } |
1065 | 260 | break; |
1066 | 260 | default: |
1067 | 0 | fr_strerror_printf("decode: Unsupported packet type %d", pkt->hdr.type); |
1068 | 0 | goto fail; |
1069 | 5.30k | } |
1070 | | |
1071 | 5.30k | talloc_free(decrypted); |
1072 | 4.94k | return buffer_len; |
1073 | 5.30k | } |
1074 | | |
1075 | | /* |
1076 | | * Test points for protocol decode |
1077 | | */ |
1078 | | static ssize_t fr_tacacs_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, void *proto_ctx) |
1079 | 5.39k | { |
1080 | 5.39k | fr_tacacs_ctx_t *test_ctx = talloc_get_type_abort(proto_ctx, fr_tacacs_ctx_t); |
1081 | 5.39k | fr_dict_attr_t const *dv; |
1082 | | |
1083 | 5.39k | dv = fr_dict_attr_by_name(NULL, fr_dict_root(dict_tacacs), "Test"); |
1084 | 5.39k | fr_assert(!dv || (dv->type == FR_TYPE_VENDOR)); |
1085 | | |
1086 | 5.39k | return fr_tacacs_decode(ctx, out, dv, data, data_len, NULL, test_ctx->secret, |
1087 | 5.39k | test_ctx->secret ? talloc_strlen(test_ctx->secret) : 0, NULL); |
1088 | 5.39k | } |
1089 | | |
1090 | | static int _encode_test_ctx(fr_tacacs_ctx_t *test_ctx) |
1091 | 5.39k | { |
1092 | 5.39k | talloc_const_free(test_ctx->secret); |
1093 | | |
1094 | 5.39k | fr_tacacs_global_free(); |
1095 | | |
1096 | 5.39k | return 0; |
1097 | 5.39k | } |
1098 | | |
1099 | | static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict, |
1100 | | UNUSED fr_dict_attr_t const *root_da) |
1101 | 5.74k | { |
1102 | 5.74k | fr_tacacs_ctx_t *test_ctx; |
1103 | | |
1104 | 5.74k | if (fr_tacacs_global_init() < 0) return -1; |
1105 | | |
1106 | 5.74k | test_ctx = talloc_zero(ctx, fr_tacacs_ctx_t); |
1107 | 5.74k | if (!test_ctx) return -1; |
1108 | | |
1109 | 5.74k | test_ctx->secret = talloc_strdup(test_ctx, "testing123"); |
1110 | 5.74k | test_ctx->root = fr_dict_root(dict_tacacs); |
1111 | 5.74k | talloc_set_destructor(test_ctx, _encode_test_ctx); |
1112 | | |
1113 | 5.74k | *out = test_ctx; |
1114 | | |
1115 | 5.74k | return 0; |
1116 | 5.74k | } |
1117 | | |
1118 | | extern fr_test_point_proto_decode_t tacacs_tp_decode_proto; |
1119 | | fr_test_point_proto_decode_t tacacs_tp_decode_proto = { |
1120 | | .test_ctx = decode_test_ctx, |
1121 | | .func = fr_tacacs_decode_proto |
1122 | | }; |