/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: 869469061b21171c508d7ab3d674e05bd51bf5cb $ |
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 | | fr_tacacs_packet_code_t fr_tacacs_packet_to_code(fr_tacacs_packet_t const *pkt) |
37 | 4.74k | { |
38 | 4.74k | switch (pkt->hdr.type) { |
39 | 86 | case FR_TAC_PLUS_AUTHEN: |
40 | 86 | if (pkt->hdr.seq_no == 1) return FR_TACACS_CODE_AUTH_START; |
41 | | |
42 | 39 | if ((pkt->hdr.seq_no & 0x01) == 1) { |
43 | 30 | if (pkt->authen_cont.flags == FR_TAC_PLUS_CONTINUE_FLAG_UNSET) return FR_TACACS_CODE_AUTH_CONT; |
44 | | |
45 | 26 | if (pkt->authen_cont.flags == FR_TAC_PLUS_CONTINUE_FLAG_ABORT) return FR_TACACS_CODE_AUTH_CONT_ABORT; |
46 | | |
47 | 25 | fr_strerror_printf("Invalid value %d for authentication continue flag", pkt->authen_cont.flags); |
48 | 25 | return FR_TACACS_CODE_INVALID; |
49 | 26 | } |
50 | | |
51 | 9 | switch (pkt->authen_reply.status) { |
52 | 1 | case FR_TAC_PLUS_AUTHEN_STATUS_PASS: |
53 | 1 | return FR_TACACS_CODE_AUTH_PASS; |
54 | | |
55 | 1 | case FR_TAC_PLUS_AUTHEN_STATUS_FAIL: |
56 | 1 | return FR_TACACS_CODE_AUTH_FAIL; |
57 | | |
58 | 1 | case FR_TAC_PLUS_AUTHEN_STATUS_GETDATA: |
59 | 1 | return FR_TACACS_CODE_AUTH_GETDATA; |
60 | | |
61 | 1 | case FR_TAC_PLUS_AUTHEN_STATUS_GETUSER: |
62 | 1 | return FR_TACACS_CODE_AUTH_GETUSER; |
63 | | |
64 | 1 | case FR_TAC_PLUS_AUTHEN_STATUS_GETPASS: |
65 | 1 | return FR_TACACS_CODE_AUTH_GETPASS; |
66 | | |
67 | 1 | case FR_TAC_PLUS_AUTHEN_STATUS_RESTART: |
68 | 1 | return FR_TACACS_CODE_AUTH_RESTART; |
69 | | |
70 | 1 | case FR_TAC_PLUS_AUTHEN_STATUS_ERROR: |
71 | 1 | return FR_TACACS_CODE_AUTH_ERROR; |
72 | | |
73 | 1 | case FR_TAC_PLUS_AUTHEN_STATUS_FOLLOW: |
74 | 1 | return FR_TACACS_CODE_AUTH_FAIL; |
75 | | |
76 | 1 | default: |
77 | 1 | break; |
78 | 9 | } |
79 | | |
80 | 1 | fr_strerror_printf("Invalid value %d for authentication reply status", pkt->authen_reply.status); |
81 | 1 | return FR_TACACS_CODE_INVALID; |
82 | | |
83 | 4.28k | case FR_TAC_PLUS_AUTHOR: |
84 | 4.28k | if ((pkt->hdr.seq_no & 0x01) == 1) { |
85 | 556 | return FR_TACACS_CODE_AUTZ_REQUEST; |
86 | 556 | } |
87 | | |
88 | 3.73k | switch (pkt->author_reply.status) { |
89 | 37 | case FR_TAC_PLUS_AUTHOR_STATUS_PASS_ADD: |
90 | 37 | return FR_TACACS_CODE_AUTZ_PASS_ADD; |
91 | | |
92 | 27 | case FR_TAC_PLUS_AUTHOR_STATUS_PASS_REPL: |
93 | 27 | return FR_TACACS_CODE_AUTZ_PASS_REPLACE; |
94 | | |
95 | 25 | case FR_TAC_PLUS_AUTHOR_STATUS_FAIL: |
96 | 108 | case FR_TAC_PLUS_AUTHOR_STATUS_FOLLOW: |
97 | 108 | return FR_TACACS_CODE_AUTZ_FAIL; |
98 | | |
99 | 44 | case FR_TAC_PLUS_AUTHOR_STATUS_ERROR: |
100 | 44 | return FR_TACACS_CODE_AUTZ_ERROR; |
101 | | |
102 | 3.51k | default: |
103 | 3.51k | break; |
104 | 3.73k | } |
105 | | |
106 | 3.51k | fr_strerror_printf("Invalid value %d for authorization reply status", pkt->author_reply.status); |
107 | 3.51k | return FR_TACACS_CODE_INVALID; |
108 | | |
109 | 368 | case FR_TAC_PLUS_ACCT: |
110 | 368 | if ((pkt->hdr.seq_no & 0x01) == 1) { |
111 | 360 | return FR_TACACS_CODE_ACCT_REQUEST; |
112 | 360 | } |
113 | | |
114 | 8 | switch (pkt->acct_reply.status) { |
115 | 1 | case FR_TAC_PLUS_ACCT_STATUS_SUCCESS: |
116 | 1 | return FR_TACACS_CODE_ACCT_SUCCESS; |
117 | | |
118 | 4 | case FR_TAC_PLUS_ACCT_STATUS_ERROR: |
119 | 6 | case FR_TAC_PLUS_ACCT_STATUS_FOLLOW: |
120 | 6 | return FR_TACACS_CODE_ACCT_ERROR; |
121 | | |
122 | 1 | default: |
123 | 1 | break; |
124 | 8 | } |
125 | | |
126 | 1 | fr_strerror_printf("Invalid value %d for accounting reply status", pkt->acct_reply.status); |
127 | 1 | return FR_TACACS_CODE_INVALID; |
128 | | |
129 | 0 | default: |
130 | 0 | fr_strerror_const("Invalid header type"); |
131 | 0 | return FR_TACACS_CODE_INVALID; |
132 | 4.74k | } |
133 | 4.74k | } |
134 | | |
135 | 5.10k | #define PACKET_HEADER_CHECK(_msg, _hdr) do { \ |
136 | 5.10k | p = buffer + FR_HEADER_LENGTH; \ |
137 | 5.10k | if (sizeof(_hdr) > (size_t) (end - p)) { \ |
138 | 37 | fr_strerror_printf("Header for %s is too small (%zu < %zu)", _msg, (size_t) (end - p), sizeof(_hdr)); \ |
139 | 37 | goto fail; \ |
140 | 37 | } \ |
141 | 5.10k | body = p + sizeof(_hdr); \ |
142 | 5.06k | data_len = sizeof(_hdr); \ |
143 | 5.06k | } while (0) |
144 | | |
145 | | /* |
146 | | * Check argv[i] after the user_msg / server_msg / argc lengths have been added to data_len |
147 | | */ |
148 | 4.76k | #define ARG_COUNT_CHECK(_msg, _hdr) do { \ |
149 | 4.76k | fr_assert(p == (uint8_t const *) &(_hdr)); \ |
150 | 4.76k | if (data_len > (size_t) (end - p)) { \ |
151 | 60 | fr_strerror_printf("Argument count %u overflows the remaining data (%zu) in the %s packet", _hdr.arg_cnt, (size_t) (end - p), _msg); \ |
152 | 60 | goto fail; \ |
153 | 60 | } \ |
154 | 4.76k | argv = body; \ |
155 | 4.70k | attrs = buffer + FR_HEADER_LENGTH + data_len; \ |
156 | 4.70k | body += _hdr.arg_cnt; \ |
157 | 4.70k | p = attrs; \ |
158 | 78.7k | for (unsigned int i = 0; i < _hdr.arg_cnt; i++) { \ |
159 | 74.0k | if (_hdr.arg_len[i] > (size_t) (end - p)) { \ |
160 | 55 | fr_strerror_printf("Argument %u length %u overflows packet", i, _hdr.arg_len[i]); \ |
161 | 55 | goto fail; \ |
162 | 55 | } \ |
163 | 74.0k | p += _hdr.arg_len[i]; \ |
164 | 74.0k | } \ |
165 | 4.70k | } while (0) |
166 | | |
167 | 12.7k | #define DECODE_FIELD_UINT8(_da, _field) do { \ |
168 | 12.7k | vp = fr_pair_afrom_da(ctx, _da); \ |
169 | 12.7k | if (!vp) goto fail; \ |
170 | 12.7k | vp->vp_uint8 = _field; \ |
171 | 12.7k | FR_PAIR_APPEND(out, vp); \ |
172 | 12.7k | } while (0) |
173 | | |
174 | 2.52k | #define DECODE_FIELD_STRING8(_da, _field) do { \ |
175 | 2.52k | if (tacacs_decode_field(ctx, out, _da, &p, \ |
176 | 2.52k | _field, end) < 0) goto fail; \ |
177 | 2.52k | } while (0) |
178 | | |
179 | 7.55k | #define DECODE_FIELD_STRING16(_da, _field) do { \ |
180 | 7.55k | if (tacacs_decode_field(ctx, out, _da, &p, \ |
181 | 7.55k | ntohs(_field), end) < 0) goto fail; \ |
182 | 7.55k | } while (0) |
183 | | |
184 | 8 | #define BODY(_x) (((uint8_t const *) pkt) + sizeof(pkt->hdr) + sizeof(pkt->_x)) |
185 | | |
186 | | /** Decode a TACACS+ 'arg_N' fields. |
187 | | * |
188 | | */ |
189 | | static int tacacs_decode_args(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, |
190 | | uint8_t arg_cnt, uint8_t const *argv, uint8_t const *attrs, uint8_t const *end) |
191 | 4.64k | { |
192 | 4.64k | uint8_t i; |
193 | 4.64k | bool append = false; |
194 | 4.64k | uint8_t const *p = attrs; |
195 | 4.64k | fr_pair_t *vp; |
196 | 4.64k | fr_pair_t *vendor = NULL; |
197 | 4.64k | fr_dict_attr_t const *root; |
198 | | |
199 | | /* |
200 | | * No one? Just get out! |
201 | | */ |
202 | 4.64k | if (!arg_cnt) return 0; |
203 | | |
204 | | /* |
205 | | * Try to decode as nested attributes. If we can't, everything is |
206 | | * |
207 | | * Argument-List = "foo=bar" |
208 | | */ |
209 | 4.61k | if (parent) { |
210 | 0 | vendor = fr_pair_find_by_da(out, NULL, parent); |
211 | 0 | if (!vendor) { |
212 | 0 | vendor = fr_pair_afrom_da(ctx, parent); |
213 | 0 | if (!vendor) return -1; |
214 | 0 | PAIR_ALLOCED(vendor); |
215 | |
|
216 | 0 | append = true; |
217 | 0 | } |
218 | 0 | } |
219 | | |
220 | 4.61k | root = fr_dict_root(dict_tacacs); |
221 | | |
222 | | /* |
223 | | * Then, do the dirty job of creating attributes. |
224 | | */ |
225 | 78.2k | for (i = 0; i < arg_cnt; i++) { |
226 | 73.6k | uint8_t const *value, *name_end, *arg_end; |
227 | 73.6k | fr_dict_attr_t const *da; |
228 | 73.6k | fr_pair_list_t *dst; |
229 | 73.6k | uint8_t buffer[256]; |
230 | | |
231 | 73.6k | if (argv[i] > (end - p)) return -1; |
232 | | |
233 | 73.6k | if (argv[i] < 2) goto next; /* skip malformed */ |
234 | | |
235 | 64.6k | memcpy(buffer, p, argv[i]); |
236 | 64.6k | buffer[argv[i]] = '\0'; |
237 | | |
238 | 64.6k | arg_end = buffer + argv[i]; |
239 | | |
240 | 435k | for (value = buffer, name_end = NULL; value < arg_end; value++) { |
241 | | /* |
242 | | * RFC 8907 Section 3.7 says control |
243 | | * characters MUST be excluded. |
244 | | */ |
245 | 432k | if (*value < ' ') goto next; |
246 | | |
247 | 416k | if ((*value == '=') || (*value == '*')) { |
248 | 45.9k | name_end = value; |
249 | 45.9k | buffer[value - buffer] = '\0'; |
250 | 45.9k | value++; |
251 | 45.9k | break; |
252 | 45.9k | } |
253 | 416k | } |
254 | | |
255 | | /* |
256 | | * Skip fields which aren't in "name=value" or "name*value" format. |
257 | | */ |
258 | 49.2k | if (!name_end) goto next; |
259 | | |
260 | | /* |
261 | | * Prefer to decode from the attribute root, first. |
262 | | */ |
263 | 45.9k | da = fr_dict_attr_by_name(NULL, root, (char *) buffer); |
264 | 45.9k | if (da) { |
265 | 21.1k | vp = fr_pair_afrom_da(ctx, da); |
266 | 21.1k | if (!vp) goto oom; |
267 | 21.1k | PAIR_ALLOCED(vp); |
268 | | |
269 | 21.1k | dst = out; |
270 | 21.1k | goto decode; |
271 | 21.1k | } |
272 | | |
273 | | /* |
274 | | * If the attribute isn't in the main dictionary, |
275 | | * maybe it's in the vendor dictionary? |
276 | | */ |
277 | 24.7k | if (vendor) { |
278 | 0 | da = fr_dict_attr_by_name(NULL, parent, (char *) buffer); |
279 | 0 | if (!da) goto raw; |
280 | | |
281 | 0 | vp = fr_pair_afrom_da(vendor, da); |
282 | 0 | if (!vp) goto oom; |
283 | 0 | PAIR_ALLOCED(vp); |
284 | |
|
285 | 0 | dst = &vendor->vp_group; |
286 | |
|
287 | 21.1k | decode: |
288 | | /* |
289 | | * If it's OCTETS or STRING type, then just copy the value verbatim, as the |
290 | | * contents are (should be?) binary-safe. But if it's zero length, then don't need to |
291 | | * copy anything. |
292 | | * |
293 | | * Note that we copy things manually here because |
294 | | * we don't want the OCTETS type to be parsed as |
295 | | * hex. And, we don't want the string type to be |
296 | | * unescaped. |
297 | | */ |
298 | 21.1k | if (da->type == FR_TYPE_OCTETS) { |
299 | 112 | if ((arg_end > value) && |
300 | 74 | (fr_pair_value_memdup(vp, value, arg_end - value, true) < 0)) { |
301 | 0 | goto fail; |
302 | 0 | } |
303 | | |
304 | 21.0k | } else if (da->type == FR_TYPE_STRING) { |
305 | 236 | if ((arg_end > value) && |
306 | 79 | (fr_pair_value_bstrndup(vp, (char const *) value, arg_end - value, true) < 0)) { |
307 | 0 | goto fail; |
308 | 0 | } |
309 | | |
310 | 20.8k | } else if (arg_end == value) { |
311 | | /* |
312 | | * Any other leaf type MUST have non-zero contents. |
313 | | */ |
314 | 106 | talloc_free(vp); |
315 | 106 | goto raw; |
316 | | |
317 | 20.7k | } else { |
318 | | /* |
319 | | * Parse the string, and try to convert it to the |
320 | | * underlying data type. If it can't be |
321 | | * converted as a data type, just convert it as |
322 | | * Argument-List. |
323 | | * |
324 | | * And if that fails, just ignore it completely. |
325 | | */ |
326 | 20.7k | if (fr_pair_value_from_str(vp, (char const *) value, arg_end - value, NULL, true) < 0) { |
327 | 18.2k | talloc_free(vp); |
328 | 18.2k | goto raw; |
329 | 18.2k | } |
330 | | |
331 | | /* |
332 | | * Else it parsed fine, append it to the output vendor list. |
333 | | */ |
334 | 20.7k | } |
335 | | |
336 | 2.86k | fr_pair_append(dst, vp); |
337 | | |
338 | 24.7k | } else { |
339 | 43.1k | raw: |
340 | 43.1k | vp = fr_pair_afrom_da(ctx, attr_tacacs_argument_list); |
341 | 43.1k | if (!vp) { |
342 | 0 | oom: |
343 | 0 | fr_strerror_const("Out of Memory"); |
344 | 0 | fail: |
345 | 0 | if (append) { |
346 | 0 | talloc_free(vendor); |
347 | 0 | } else { |
348 | 0 | talloc_free(vp); |
349 | 0 | } |
350 | 0 | return -1; |
351 | 0 | } |
352 | 43.1k | PAIR_ALLOCED(vp); |
353 | | |
354 | 43.1k | value = p; |
355 | 43.1k | arg_end = p + argv[i]; |
356 | | |
357 | 43.1k | if ((arg_end > value) && |
358 | 43.1k | (fr_pair_value_bstrndup(vp, (char const *) value, arg_end - value, true) < 0)) { |
359 | 0 | goto fail; |
360 | 0 | } |
361 | | |
362 | 43.1k | fr_pair_append(out, vp); |
363 | 43.1k | } |
364 | | |
365 | 73.6k | next: |
366 | 73.6k | p += argv[i]; |
367 | 73.6k | } |
368 | | |
369 | 4.61k | if (append) { |
370 | 0 | if (fr_pair_list_num_elements(&vendor->vp_group) > 0) { |
371 | 0 | fr_pair_append(out, vendor); |
372 | 0 | } else { |
373 | 0 | talloc_free(vendor); |
374 | 0 | } |
375 | 0 | } |
376 | | |
377 | 4.61k | return 0; |
378 | 4.61k | } |
379 | | |
380 | | /** |
381 | | * Decode a TACACS+ field. |
382 | | */ |
383 | | static int tacacs_decode_field(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *da, |
384 | | uint8_t const **field_data, uint16_t field_len, uint8_t const *end) |
385 | 10.0k | { |
386 | 10.0k | uint8_t const *p = *field_data; |
387 | 10.0k | fr_pair_t *vp; |
388 | | |
389 | 10.0k | if (field_len > (end - p)) { |
390 | 0 | fr_strerror_printf("'%s' length %u overflows the remaining data (%zu) in the packet", |
391 | 0 | da->name, field_len, (size_t) (end - p)); |
392 | 0 | return -1; |
393 | 0 | } |
394 | | |
395 | 10.0k | vp = fr_pair_afrom_da(ctx, da); |
396 | 10.0k | if (!vp) { |
397 | 0 | fr_strerror_const("Out of Memory"); |
398 | 0 | return -1; |
399 | 0 | } |
400 | 10.0k | PAIR_ALLOCED(vp); |
401 | | |
402 | 10.0k | if (field_len) { |
403 | 1.15k | if (da->type == FR_TYPE_STRING) { |
404 | 1.04k | fr_pair_value_bstrndup(vp, (char const *)p, field_len, true); |
405 | 1.04k | } else if (da->type == FR_TYPE_OCTETS) { |
406 | 111 | fr_pair_value_memdup(vp, p, field_len, true); |
407 | 111 | } else { |
408 | 0 | fr_assert(0); |
409 | 0 | } |
410 | 1.15k | p += field_len; |
411 | 1.15k | *field_data = p; |
412 | 1.15k | } |
413 | | |
414 | 10.0k | fr_pair_append(out, vp); |
415 | | |
416 | 10.0k | return 0; |
417 | 10.0k | } |
418 | | |
419 | | /** |
420 | | * Decode a TACACS+ packet |
421 | | */ |
422 | | ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *vendor, |
423 | | uint8_t const *buffer, size_t buffer_len, |
424 | | const uint8_t *original, char const * const secret, size_t secret_len, fr_tacacs_packet_code_t *code) |
425 | 5.19k | { |
426 | 5.19k | fr_tacacs_packet_t const *pkt; |
427 | 5.19k | fr_pair_t *vp; |
428 | 5.19k | size_t data_len; |
429 | 5.19k | uint8_t const *p, *body, *argv, *attrs, *end; |
430 | 5.19k | uint8_t *decrypted = NULL; |
431 | | |
432 | | /* |
433 | | * 3.4. The TACACS+ Packet Header |
434 | | * |
435 | | * 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 |
436 | | * +----------------+----------------+----------------+----------------+ |
437 | | * |major | minor | | | | |
438 | | * |version| version| type | seq_no | flags | |
439 | | * +----------------+----------------+----------------+----------------+ |
440 | | * | | |
441 | | * | session_id | |
442 | | * +----------------+----------------+----------------+----------------+ |
443 | | * | | |
444 | | * | length | |
445 | | * +----------------+----------------+----------------+----------------+ |
446 | | */ |
447 | 5.19k | pkt = (fr_tacacs_packet_t const *) buffer; |
448 | | |
449 | | /* |
450 | | * p miscellaneous pointer for decoding things |
451 | | * body points to just past the (randomly sized) per-packet header, |
452 | | * where the various user / server messages are. |
453 | | * sometimes this is after "argv". |
454 | | * argv points to the array of argv[i] length entries |
455 | | * attrs points to the attributes we need to decode as "foo=bar". |
456 | | */ |
457 | 5.19k | argv = attrs = NULL; |
458 | 5.19k | end = buffer + buffer_len; |
459 | | |
460 | | /* |
461 | | * Check that we have a full TACACS+ header before |
462 | | * decoding anything. |
463 | | */ |
464 | 5.19k | if (buffer_len < sizeof(pkt->hdr)) { |
465 | 6 | fr_strerror_printf("Packet is too small (%zu < 12) to be TACACS+.", buffer_len); |
466 | 6 | return -1; |
467 | 6 | } |
468 | | |
469 | | /* |
470 | | * TACACS major / minor version MUST be 12.0 or 12.1 |
471 | | */ |
472 | 5.18k | if (!(pkt->hdr.ver.major == 12 && (pkt->hdr.ver.minor == 0 || pkt->hdr.ver.minor == 1))) { |
473 | 14 | fr_strerror_printf("Unsupported TACACS+ version %d.%d (%02x)", pkt->hdr.ver.major, pkt->hdr.ver.minor, buffer[0]); |
474 | 14 | return -1; |
475 | 14 | } |
476 | | |
477 | | /* |
478 | | * There's no reason to accept 64K TACACS+ packets. |
479 | | * |
480 | | * In any case, the largest possible packet has the |
481 | | * header, plus 2 16-bit fields, plus 255 8-bit fields, |
482 | | * which is a bit under 2^18. |
483 | | */ |
484 | 5.17k | if ((buffer[8] != 0) || (buffer[9] != 0)) { |
485 | 18 | fr_strerror_const("Packet is too large. Our limit is 64K"); |
486 | 18 | return -1; |
487 | 18 | } |
488 | | |
489 | | /* |
490 | | * As a stream protocol, the TACACS+ packet MUST fit |
491 | | * exactly into however many bytes we read. |
492 | | */ |
493 | 5.15k | if ((buffer + sizeof(pkt->hdr) + ntohl(pkt->hdr.length)) != end) { |
494 | 40 | fr_strerror_const("Packet does not exactly fill buffer"); |
495 | 40 | return -1; |
496 | 40 | } |
497 | | |
498 | | /* |
499 | | * There are only 3 types of packets which are supported. |
500 | | */ |
501 | 5.11k | if (!((pkt->hdr.type == FR_TAC_PLUS_AUTHEN) || |
502 | 4.87k | (pkt->hdr.type == FR_TAC_PLUS_AUTHOR) || |
503 | 485 | (pkt->hdr.type == FR_TAC_PLUS_ACCT))) { |
504 | 13 | fr_strerror_printf("Unknown packet type %d", pkt->hdr.type); |
505 | 13 | return -1; |
506 | 13 | } |
507 | | |
508 | | /* |
509 | | * Check that the session IDs are correct. |
510 | | */ |
511 | 5.10k | if (original && (memcmp(original + 4, buffer + 4, 4) != 0)) { |
512 | 0 | fr_strerror_printf("Session ID %08x does not match expected number %08x", |
513 | 0 | fr_nbo_to_uint32(buffer + 4), fr_nbo_to_uint32(original + 4)); |
514 | 0 | return -1; |
515 | 0 | } |
516 | | |
517 | 5.10k | if (!secret && packet_is_encrypted(pkt)) { |
518 | 0 | fr_strerror_const("Packet is encrypted, but there is no secret to decrypt it"); |
519 | 0 | return -1; |
520 | 0 | } |
521 | | |
522 | | /* |
523 | | * Call the struct encoder to do the actual work. |
524 | | */ |
525 | 5.10k | if (fr_struct_from_network(ctx, out, attr_tacacs_packet, buffer, buffer_len, NULL, NULL, NULL) < 0) { |
526 | 0 | fr_strerror_printf_push("Failed decoding TACACS header"); |
527 | 0 | return -1; |
528 | 0 | } |
529 | | |
530 | | /* |
531 | | * 3.6. Encryption |
532 | | * |
533 | | * If there's a secret, we always decrypt the packets. |
534 | | */ |
535 | 5.10k | if (secret && packet_is_encrypted(pkt)) { |
536 | 115 | size_t length; |
537 | | |
538 | 115 | if (!secret_len) { |
539 | 0 | fr_strerror_const("Packet should be encrypted, but the secret has zero length"); |
540 | 0 | return -1; |
541 | 0 | } |
542 | | |
543 | 115 | length = ntohl(pkt->hdr.length); |
544 | | |
545 | | /* |
546 | | * We need that to decrypt the body content. |
547 | | * |
548 | | * @todo - use thread-local storage to avoid allocations? |
549 | | */ |
550 | 115 | decrypted = talloc_memdup(ctx, buffer, buffer_len); |
551 | 115 | if (!decrypted) { |
552 | 0 | fr_strerror_const("Out of Memory"); |
553 | 0 | return -1; |
554 | 0 | } |
555 | | |
556 | 115 | pkt = (fr_tacacs_packet_t const *) decrypted; |
557 | 115 | end = decrypted + buffer_len; |
558 | | |
559 | 115 | if (fr_tacacs_body_xor(pkt, decrypted + sizeof(pkt->hdr), length, secret, secret_len) < 0) { |
560 | 3.90k | fail: |
561 | 3.90k | talloc_free(decrypted); |
562 | 3.90k | return -1; |
563 | 0 | } |
564 | | |
565 | 115 | decrypted[3] |= FR_TAC_PLUS_UNENCRYPTED_FLAG; |
566 | | |
567 | 115 | FR_PROTO_HEX_DUMP(decrypted, buffer_len, "fr_tacacs_packet_t (unencrypted)"); |
568 | | |
569 | 115 | buffer = decrypted; |
570 | 115 | } |
571 | | |
572 | 5.10k | #ifndef NDEBUG |
573 | 5.10k | if (fr_debug_lvl >= L_DBG_LVL_4) fr_tacacs_packet_log_hex(&default_log, pkt, (end - buffer)); |
574 | 5.10k | #endif |
575 | | |
576 | 5.10k | switch (pkt->hdr.type) { |
577 | 240 | case FR_TAC_PLUS_AUTHEN: |
578 | 240 | if (packet_is_authen_start_request(pkt)) { |
579 | 83 | uint8_t want; |
580 | 83 | bool raw; |
581 | 83 | fr_dict_attr_t const *da, *challenge; |
582 | | |
583 | | /** |
584 | | * 4.1. The Authentication START Packet Body |
585 | | * |
586 | | * 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 |
587 | | * +----------------+----------------+----------------+----------------+ |
588 | | * | action | priv_lvl | authen_type | authen_service | |
589 | | * +----------------+----------------+----------------+----------------+ |
590 | | * | user_len | port_len | rem_addr_len | data_len | |
591 | | * +----------------+----------------+----------------+----------------+ |
592 | | * | user ... |
593 | | * +----------------+----------------+----------------+----------------+ |
594 | | * | port ... |
595 | | * +----------------+----------------+----------------+----------------+ |
596 | | * | rem_addr ... |
597 | | * +----------------+----------------+----------------+----------------+ |
598 | | * | data... |
599 | | * +----------------+----------------+----------------+----------------+ |
600 | | */ |
601 | 83 | PACKET_HEADER_CHECK("Authentication-Start", pkt->authen_start); |
602 | | |
603 | 78 | data_len += p[4] + p[5] + p[6] + p[7]; |
604 | 78 | if (data_len > (size_t) (end - p)) { |
605 | 135 | overflow: |
606 | 135 | if ((buffer[3] & FR_TAC_PLUS_UNENCRYPTED_FLAG) == 0) { |
607 | 0 | bad_secret: |
608 | 0 | fr_strerror_const("Invalid packet after decryption - is the secret key incorrect?"); |
609 | 0 | goto fail; |
610 | 0 | } |
611 | | |
612 | 135 | fr_strerror_const("Data overflows the packet"); |
613 | 135 | goto fail; |
614 | 135 | } |
615 | 62 | if (data_len < (size_t) (end - p)) { |
616 | 70 | underflow: |
617 | 70 | if ((buffer[3] & FR_TAC_PLUS_UNENCRYPTED_FLAG) == 0) goto bad_secret; |
618 | | |
619 | 70 | fr_strerror_const("Data underflows the packet"); |
620 | 70 | goto fail; |
621 | 70 | } |
622 | | |
623 | 48 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_START); |
624 | | |
625 | | /* |
626 | | * Decode 4 octets of various flags. |
627 | | */ |
628 | 48 | DECODE_FIELD_UINT8(attr_tacacs_action, pkt->authen_start.action); |
629 | 48 | DECODE_FIELD_UINT8(attr_tacacs_privilege_level, pkt->authen_start.priv_lvl); |
630 | 48 | DECODE_FIELD_UINT8(attr_tacacs_authentication_type, pkt->authen_start.authen_type); |
631 | 48 | DECODE_FIELD_UINT8(attr_tacacs_authentication_service, pkt->authen_start.authen_service); |
632 | | |
633 | | /* |
634 | | * Decode 3 fields, based on their "length" |
635 | | * user and rem_addr are optional - indicated by zero length |
636 | | */ |
637 | 48 | p = body; |
638 | 48 | if (pkt->authen_start.user_len > 0) DECODE_FIELD_STRING8(attr_tacacs_user_name, |
639 | 48 | pkt->authen_start.user_len); |
640 | 48 | DECODE_FIELD_STRING8(attr_tacacs_client_port, pkt->authen_start.port_len); |
641 | 48 | if (pkt->authen_start.rem_addr_len > 0) DECODE_FIELD_STRING8(attr_tacacs_remote_address, |
642 | 48 | pkt->authen_start.rem_addr_len); |
643 | | |
644 | | /* |
645 | | * Check the length on the various |
646 | | * authentication types. |
647 | | */ |
648 | 48 | raw = false; |
649 | 48 | challenge = NULL; |
650 | | |
651 | 48 | switch (pkt->authen_start.authen_type) { |
652 | 14 | default: |
653 | 14 | raw = true; |
654 | 14 | want = pkt->authen_start.data_len; |
655 | 14 | da = attr_tacacs_data; |
656 | 14 | break; |
657 | | |
658 | 9 | case FR_AUTHENTICATION_TYPE_VALUE_PAP: |
659 | 9 | want = pkt->authen_start.data_len; |
660 | 9 | da = attr_tacacs_user_password; |
661 | 9 | break; |
662 | | |
663 | 15 | case FR_AUTHENTICATION_TYPE_VALUE_CHAP: |
664 | 15 | want = 1 + 16; /* id + HOPEFULLY 8 octets of challenge + 16 hash */ |
665 | 15 | da = attr_tacacs_chap_password; |
666 | 15 | challenge = attr_tacacs_chap_challenge; |
667 | 15 | break; |
668 | | |
669 | 5 | case FR_AUTHENTICATION_TYPE_VALUE_MSCHAP: |
670 | 5 | want = 1 + 49; /* id + HOPEFULLY 8 octets of challenge + 49 MS-CHAP stuff */ |
671 | 5 | da = attr_tacacs_mschap_response; |
672 | 5 | challenge = attr_tacacs_mschap_challenge; |
673 | 5 | break; |
674 | | |
675 | 5 | case FR_AUTHENTICATION_TYPE_VALUE_MSCHAPV2: |
676 | 5 | want = 1 + 49; /* id + HOPEFULLY 16 octets of challenge + 49 MS-CHAP stuff */ |
677 | 5 | da = attr_tacacs_mschap2_response; |
678 | 5 | challenge = attr_tacacs_mschap_challenge; |
679 | 5 | break; |
680 | 48 | } |
681 | | |
682 | | /* |
683 | | * If we have enough data, decode it as |
684 | | * the claimed authentication type. |
685 | | * |
686 | | * Otherwise, decode the entire field as an unknown |
687 | | * attribute. |
688 | | */ |
689 | 48 | if (raw || (pkt->authen_start.data_len < want)) { |
690 | 25 | fr_dict_attr_t *da_unknown; |
691 | | |
692 | 25 | da_unknown = fr_dict_attr_unknown_raw_afrom_num(ctx, fr_dict_root(dict_tacacs), |
693 | 25 | attr_tacacs_data->attr); |
694 | 25 | if (!da_unknown) goto fail; |
695 | | |
696 | 25 | want = pkt->authen_start.data_len; |
697 | | |
698 | 25 | DECODE_FIELD_STRING8(da_unknown, want); |
699 | 25 | talloc_free(da_unknown); |
700 | | |
701 | 25 | } else if (!challenge) { |
702 | 9 | DECODE_FIELD_STRING8(da, want); |
703 | | |
704 | 14 | } else if (pkt->authen_start.data_len == want) { |
705 | 1 | fr_strerror_printf("%s has zero length", challenge->name); |
706 | 1 | goto fail; |
707 | | |
708 | 13 | } else { /* 1 of ID + ??? of challenge + (want-1) of data */ |
709 | 13 | uint8_t challenge_len = pkt->authen_start.data_len - want; |
710 | 13 | uint8_t hash[50]; |
711 | | |
712 | | /* |
713 | | * Rework things to make sense. |
714 | | * RFC 8079 says that MS-CHAP responses should follow RFC 2433 and 2759 |
715 | | * which have "Flags" at the end. |
716 | | * RADIUS attributes expect "Flags" after the ID as per RFC 2548. |
717 | | * Re-arrange to make things consistent. |
718 | | */ |
719 | 13 | hash[0] = p[0]; |
720 | 13 | switch (pkt->authen_start.authen_type) { |
721 | 1 | case FR_AUTHENTICATION_TYPE_VALUE_MSCHAP: |
722 | 4 | case FR_AUTHENTICATION_TYPE_VALUE_MSCHAPV2: |
723 | 4 | hash[1] = p[want - 1]; |
724 | 4 | memcpy(hash + 2, p + 1 + challenge_len, want - 2); |
725 | 4 | break; |
726 | | |
727 | 9 | default: |
728 | 9 | memcpy(hash + 1, p + 1 + challenge_len, want - 1); |
729 | 9 | break; |
730 | 13 | } |
731 | | |
732 | 13 | vp = fr_pair_afrom_da(ctx, da); |
733 | 13 | if (!vp) goto fail; |
734 | | |
735 | 13 | FR_PAIR_APPEND(out, vp); |
736 | | |
737 | | /* |
738 | | * ID + hash |
739 | | */ |
740 | 13 | if (fr_pair_value_memdup(vp, hash, want, true) < 0) goto fail; |
741 | | |
742 | | /* |
743 | | * And then the challenge. |
744 | | */ |
745 | 13 | vp = fr_pair_afrom_da(ctx, challenge); |
746 | 13 | if (!vp) goto fail; |
747 | | |
748 | 13 | FR_PAIR_APPEND(out, vp); |
749 | | |
750 | 13 | if (fr_pair_value_memdup(vp, p + 1, challenge_len, true) < 0) goto fail; |
751 | | |
752 | 13 | p += pkt->authen_start.data_len; |
753 | 13 | } |
754 | | |
755 | 157 | } 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 | 89 | if (pkt->hdr.ver.minor != 0) { |
777 | 4 | invalid_version: |
778 | 4 | fr_strerror_const("Invalid TACACS+ version"); |
779 | 4 | goto fail; |
780 | 2 | } |
781 | | |
782 | 87 | PACKET_HEADER_CHECK("Authentication-Continue", pkt->authen_cont); |
783 | 81 | data_len += fr_nbo_to_uint16(p) + fr_nbo_to_uint16(p + 2); |
784 | 81 | if (data_len > (size_t) (end - p)) goto overflow; |
785 | 49 | if (data_len < (size_t) (end - p)) goto underflow; |
786 | | |
787 | 30 | 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 | 30 | p = body; |
793 | 30 | DECODE_FIELD_STRING16(attr_tacacs_user_message, pkt->authen_cont.user_msg_len); |
794 | 30 | DECODE_FIELD_STRING16(attr_tacacs_data, pkt->authen_cont.data_len); |
795 | | |
796 | | /* |
797 | | * And finally the flags. |
798 | | */ |
799 | 30 | DECODE_FIELD_UINT8(attr_tacacs_authentication_continue_flags, pkt->authen_cont.flags); |
800 | | |
801 | 68 | } 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 | 68 | PACKET_HEADER_CHECK("Authentication-Reply", pkt->authen_reply); |
820 | 60 | data_len += fr_nbo_to_uint16(p + 2) + fr_nbo_to_uint16(p + 4); |
821 | 60 | if (data_len > (size_t) (end - p)) goto overflow; |
822 | 28 | if (data_len < (size_t) (end - p)) goto underflow; |
823 | | |
824 | 9 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_REPLY); |
825 | | |
826 | 9 | DECODE_FIELD_UINT8(attr_tacacs_authentication_status, pkt->authen_reply.status); |
827 | 9 | DECODE_FIELD_UINT8(attr_tacacs_authentication_flags, pkt->authen_reply.flags); |
828 | | |
829 | | /* |
830 | | * Decode 2 fields, based on their "length" |
831 | | */ |
832 | 9 | p = body; |
833 | 9 | DECODE_FIELD_STRING16(attr_tacacs_server_message, pkt->authen_reply.server_msg_len); |
834 | 9 | DECODE_FIELD_STRING16(attr_tacacs_data, pkt->authen_reply.data_len); |
835 | | |
836 | 9 | } else { |
837 | 0 | fr_strerror_const("Unknown authentication packet"); |
838 | 0 | goto fail; |
839 | 0 | } |
840 | 86 | break; |
841 | | |
842 | 4.39k | case FR_TAC_PLUS_AUTHOR: |
843 | 4.39k | 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 | 605 | if (pkt->hdr.ver.minor != 0) goto invalid_version; |
872 | | |
873 | 604 | PACKET_HEADER_CHECK("Authorization-Request", pkt->author_req); |
874 | 599 | data_len += p[4] + p[5] + p[6] + p[7]; |
875 | | |
876 | 599 | ARG_COUNT_CHECK("Authorization-Request", pkt->author_req); |
877 | | |
878 | 556 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_REQUEST); |
879 | | |
880 | | /* |
881 | | * Decode 4 octets of various flags. |
882 | | */ |
883 | 556 | DECODE_FIELD_UINT8(attr_tacacs_authentication_method, pkt->author_req.authen_method); |
884 | 556 | DECODE_FIELD_UINT8(attr_tacacs_privilege_level, pkt->author_req.priv_lvl); |
885 | 556 | DECODE_FIELD_UINT8(attr_tacacs_authentication_type, pkt->author_req.authen_type); |
886 | 556 | 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 | 556 | p = body; |
893 | 556 | DECODE_FIELD_STRING8(attr_tacacs_user_name, pkt->author_req.user_len); |
894 | 556 | DECODE_FIELD_STRING8(attr_tacacs_client_port, pkt->author_req.port_len); |
895 | 556 | if (pkt->author_req.rem_addr_len > 0) DECODE_FIELD_STRING8(attr_tacacs_remote_address, |
896 | 556 | pkt->author_req.rem_addr_len); |
897 | | |
898 | | /* |
899 | | * Decode 'arg_N' arguments (horrible format) |
900 | | */ |
901 | 556 | if (tacacs_decode_args(ctx, out, vendor, |
902 | 556 | pkt->author_req.arg_cnt, argv, attrs, end) < 0) goto fail; |
903 | | |
904 | 3.78k | } 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 | 3.78k | PACKET_HEADER_CHECK("Authorization-Reply", pkt->author_reply); |
934 | 3.78k | data_len += p[1] + fr_nbo_to_uint16(p + 2) + fr_nbo_to_uint16(p + 4); |
935 | | |
936 | 3.78k | ARG_COUNT_CHECK("Authorization-Reply", pkt->author_reply); |
937 | 3.73k | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_RESPONSE); |
938 | | |
939 | | /* |
940 | | * Decode 1 octets |
941 | | */ |
942 | 3.73k | DECODE_FIELD_UINT8(attr_tacacs_authorization_status, pkt->author_reply.status); |
943 | | |
944 | | /* |
945 | | * Decode 2 fields, based on their "length" |
946 | | */ |
947 | 3.73k | p = body; |
948 | 3.73k | DECODE_FIELD_STRING16(attr_tacacs_server_message, pkt->author_reply.server_msg_len); |
949 | 3.73k | DECODE_FIELD_STRING16(attr_tacacs_data, pkt->author_reply.data_len); |
950 | | |
951 | | /* |
952 | | * Decode 'arg_N' arguments (horrible format) |
953 | | */ |
954 | 3.73k | if (tacacs_decode_args(ctx, out, vendor, |
955 | 3.73k | pkt->author_reply.arg_cnt, argv, attrs, end) < 0) goto fail; |
956 | | |
957 | 3.73k | } else { |
958 | 0 | fr_strerror_const("Unknown authorization packet"); |
959 | 0 | goto fail; |
960 | 0 | } |
961 | 4.28k | break; |
962 | | |
963 | 4.28k | case FR_TAC_PLUS_ACCT: |
964 | 472 | 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 | 407 | if (pkt->hdr.ver.minor != 0) goto invalid_version; |
993 | | |
994 | 406 | PACKET_HEADER_CHECK("Accounting-Request", pkt->acct_req); |
995 | 401 | data_len += p[5] + p[6] + p[7] + p[8]; |
996 | 401 | if (data_len > (size_t) (end - p)) goto overflow; |
997 | | /* can't check for underflow, as we have argv[argc] */ |
998 | | |
999 | 381 | ARG_COUNT_CHECK("Accounting-Request", pkt->acct_req); |
1000 | | |
1001 | 360 | DECODE_FIELD_UINT8(attr_tacacs_packet_body_type, FR_PACKET_BODY_TYPE_REQUEST); |
1002 | | |
1003 | | /* |
1004 | | * Decode 5 octets of various flags. |
1005 | | */ |
1006 | 360 | DECODE_FIELD_UINT8(attr_tacacs_accounting_flags, pkt->acct_req.flags); |
1007 | 360 | DECODE_FIELD_UINT8(attr_tacacs_authentication_method, pkt->acct_req.authen_method); |
1008 | 360 | DECODE_FIELD_UINT8(attr_tacacs_privilege_level, pkt->acct_req.priv_lvl); |
1009 | 360 | DECODE_FIELD_UINT8(attr_tacacs_authentication_type, pkt->acct_req.authen_type); |
1010 | 360 | DECODE_FIELD_UINT8(attr_tacacs_authentication_service, pkt->acct_req.authen_service); |
1011 | | |
1012 | | /* |
1013 | | * Decode 3 fields, based on their "length" |
1014 | | */ |
1015 | 360 | p = body; |
1016 | 360 | DECODE_FIELD_STRING8(attr_tacacs_user_name, pkt->acct_req.user_len); |
1017 | 360 | DECODE_FIELD_STRING8(attr_tacacs_client_port, pkt->acct_req.port_len); |
1018 | 360 | DECODE_FIELD_STRING8(attr_tacacs_remote_address, pkt->acct_req.rem_addr_len); |
1019 | | |
1020 | | /* |
1021 | | * Decode 'arg_N' arguments (horrible format) |
1022 | | */ |
1023 | 360 | if (tacacs_decode_args(ctx, out, vendor, |
1024 | 360 | pkt->acct_req.arg_cnt, argv, attrs, end) < 0) goto fail; |
1025 | | |
1026 | 360 | } 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 | 65 | PACKET_HEADER_CHECK("Accounting-Reply", pkt->acct_reply); |
1046 | 61 | data_len += fr_nbo_to_uint16(p) + fr_nbo_to_uint16(p + 2); |
1047 | 61 | if (data_len > (size_t) (end - p)) goto overflow; |
1048 | 26 | if (data_len < (size_t) (end - p)) goto underflow; |
1049 | | |
1050 | 8 | p = BODY(acct_reply); |
1051 | 8 | 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 | 8 | DECODE_FIELD_STRING16(attr_tacacs_server_message, pkt->acct_reply.server_msg_len); |
1057 | 8 | DECODE_FIELD_STRING16(attr_tacacs_data, pkt->acct_reply.data_len); |
1058 | | |
1059 | | /* Decode 1 octet */ |
1060 | 8 | DECODE_FIELD_UINT8(attr_tacacs_accounting_status, pkt->acct_reply.status); |
1061 | 8 | } else { |
1062 | 0 | fr_strerror_const("Unknown accounting packet"); |
1063 | 0 | goto fail; |
1064 | 0 | } |
1065 | 368 | break; |
1066 | 368 | default: |
1067 | 0 | fr_strerror_printf("decode: Unsupported packet type %d", pkt->hdr.type); |
1068 | 0 | goto fail; |
1069 | 5.10k | } |
1070 | | |
1071 | | /* |
1072 | | * fr_tacacs_packet_to_code() reads the status or flags field |
1073 | | * of the body. The switch above has checked that the body is |
1074 | | * long enough to hold that field. |
1075 | | */ |
1076 | 4.74k | if (code) { |
1077 | 4.74k | *code = fr_tacacs_packet_to_code((fr_tacacs_packet_t const *) buffer); |
1078 | 4.74k | if (*code == FR_TACACS_CODE_INVALID) goto fail; |
1079 | 4.74k | } |
1080 | | |
1081 | 4.74k | talloc_free(decrypted); |
1082 | 1.19k | return buffer_len; |
1083 | 4.74k | } |
1084 | | |
1085 | | /* |
1086 | | * Test points for protocol decode |
1087 | | */ |
1088 | | 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) |
1089 | 5.19k | { |
1090 | 5.19k | fr_tacacs_ctx_t *test_ctx = talloc_get_type_abort(proto_ctx, fr_tacacs_ctx_t); |
1091 | 5.19k | fr_dict_attr_t const *dv; |
1092 | 5.19k | fr_tacacs_packet_code_t code; |
1093 | 5.19k | fr_pair_t *vp; |
1094 | 5.19k | ssize_t slen; |
1095 | | |
1096 | 5.19k | dv = fr_dict_attr_by_name(NULL, fr_dict_root(dict_tacacs), "Test"); |
1097 | 5.19k | fr_assert(!dv || (dv->type == FR_TYPE_VENDOR)); |
1098 | | |
1099 | 5.19k | slen = fr_tacacs_decode(ctx, out, dv, data, data_len, NULL, test_ctx->secret, |
1100 | 5.19k | test_ctx->secret ? talloc_strlen(test_ctx->secret) : 0, &code); |
1101 | 5.19k | if (slen < 0) return slen; |
1102 | | |
1103 | 1.19k | MEM(vp = fr_pair_afrom_da(ctx, attr_tacacs_packet_type)); |
1104 | 1.19k | vp->vp_uint32 = code; |
1105 | 1.19k | fr_pair_append(out, vp); |
1106 | | |
1107 | 1.19k | return slen; |
1108 | 5.19k | } |
1109 | | |
1110 | | static int _encode_test_ctx(fr_tacacs_ctx_t *test_ctx) |
1111 | 5.19k | { |
1112 | 5.19k | talloc_const_free(test_ctx->secret); |
1113 | | |
1114 | 5.19k | fr_tacacs_global_free(); |
1115 | | |
1116 | 5.19k | return 0; |
1117 | 5.19k | } |
1118 | | |
1119 | | static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict, |
1120 | | UNUSED fr_dict_attr_t const *root_da) |
1121 | 5.59k | { |
1122 | 5.59k | fr_tacacs_ctx_t *test_ctx; |
1123 | | |
1124 | 5.59k | if (fr_tacacs_global_init() < 0) return -1; |
1125 | | |
1126 | 5.59k | test_ctx = talloc_zero(ctx, fr_tacacs_ctx_t); |
1127 | 5.59k | if (!test_ctx) return -1; |
1128 | | |
1129 | 5.59k | test_ctx->secret = talloc_strdup(test_ctx, "testing123"); |
1130 | 5.59k | test_ctx->root = fr_dict_root(dict_tacacs); |
1131 | 5.59k | talloc_set_destructor(test_ctx, _encode_test_ctx); |
1132 | | |
1133 | 5.59k | *out = test_ctx; |
1134 | | |
1135 | 5.59k | return 0; |
1136 | 5.59k | } |
1137 | | |
1138 | | extern fr_test_point_proto_decode_t tacacs_tp_decode_proto; |
1139 | | fr_test_point_proto_decode_t tacacs_tp_decode_proto = { |
1140 | | .test_ctx = decode_test_ctx, |
1141 | | .func = fr_tacacs_decode_proto |
1142 | | }; |