Coverage Report

Created: 2026-04-12 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
};