Coverage Report

Created: 2026-09-28 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/tacacs/base.c
Line
Count
Source
1
/*
2
 *   This library is free software; you can redistribute it and/or
3
 *   modify it under the terms of the GNU Lesser General Public
4
 *   License as published by the Free Software Foundation; either
5
 *   version 2.1 of the License, or (at your option) any later version.
6
 *
7
 *   This library is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
 *   Lesser General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU Lesser General Public
13
 *   License along with this library; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/**
18
 * $Id: f5c2cb98f66b55d54fb54ca4b84db2485a16456f $
19
 *
20
 * @file protocols/tacacs/encode.c
21
 * @brief Low-Level TACACS+ encode functions
22
 *
23
 * @copyright 2017 The FreeRADIUS server project
24
 * @copyright 2017 Network RADIUS SAS (legal@networkradius.com)
25
 */
26
#include <freeradius-devel/util/net.h>
27
#include <freeradius-devel/util/md5.h>
28
#include <freeradius-devel/util/struct.h>
29
30
#include "tacacs.h"
31
#include "attrs.h"
32
33
static uint32_t instance_count = 0;
34
static bool instantiated = false;
35
36
fr_dict_t const *dict_tacacs;
37
38
extern fr_dict_autoload_t libfreeradius_tacacs_dict[];
39
fr_dict_autoload_t libfreeradius_tacacs_dict[] = {
40
  { .out = &dict_tacacs, .proto = "tacacs" },
41
42
  DICT_AUTOLOAD_TERMINATOR
43
};
44
45
fr_dict_attr_t const *attr_tacacs_accounting_flags;
46
fr_dict_attr_t const *attr_tacacs_accounting_status;
47
fr_dict_attr_t const *attr_tacacs_action;
48
fr_dict_attr_t const *attr_tacacs_authentication_flags;
49
fr_dict_attr_t const *attr_tacacs_authentication_continue_flags;
50
fr_dict_attr_t const *attr_tacacs_authentication_method;
51
fr_dict_attr_t const *attr_tacacs_authentication_service;
52
fr_dict_attr_t const *attr_tacacs_authentication_status;
53
fr_dict_attr_t const *attr_tacacs_authentication_type;
54
fr_dict_attr_t const *attr_tacacs_authorization_status;
55
fr_dict_attr_t const *attr_tacacs_argument_list;
56
fr_dict_attr_t const *attr_tacacs_client_port;
57
fr_dict_attr_t const *attr_tacacs_data;
58
fr_dict_attr_t const *attr_tacacs_flags;
59
fr_dict_attr_t const *attr_tacacs_length;
60
fr_dict_attr_t const *attr_tacacs_packet;
61
fr_dict_attr_t const *attr_tacacs_packet_body_type;
62
fr_dict_attr_t const *attr_tacacs_packet_type;
63
fr_dict_attr_t const *attr_tacacs_privilege_level;
64
fr_dict_attr_t const *attr_tacacs_remote_address;
65
fr_dict_attr_t const *attr_tacacs_sequence_number;
66
fr_dict_attr_t const *attr_tacacs_server_message;
67
fr_dict_attr_t const *attr_tacacs_session_id;
68
fr_dict_attr_t const *attr_tacacs_user_message;
69
fr_dict_attr_t const *attr_tacacs_version_major;
70
fr_dict_attr_t const *attr_tacacs_version_minor;
71
72
fr_dict_attr_t const *attr_tacacs_user_name;
73
fr_dict_attr_t const *attr_tacacs_user_password;
74
fr_dict_attr_t const *attr_tacacs_chap_password;
75
fr_dict_attr_t const *attr_tacacs_chap_challenge;
76
fr_dict_attr_t const *attr_tacacs_mschap_response;
77
fr_dict_attr_t const *attr_tacacs_mschap2_response;
78
fr_dict_attr_t const *attr_tacacs_mschap_challenge;
79
80
extern fr_dict_attr_autoload_t libfreeradius_tacacs_dict_attr[];
81
fr_dict_attr_autoload_t libfreeradius_tacacs_dict_attr[] = {
82
  { .out = &attr_tacacs_accounting_flags, .name = "Accounting-Flags", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
83
  { .out = &attr_tacacs_accounting_status, .name = "Accounting-Status", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
84
  { .out = &attr_tacacs_action, .name = "Action", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
85
  { .out = &attr_tacacs_authentication_flags, .name = "Authentication-Flags", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
86
  { .out = &attr_tacacs_authentication_continue_flags, .name = "Authentication-Continue-Flags", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
87
  { .out = &attr_tacacs_authentication_method, .name = "Authentication-Method", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
88
  { .out = &attr_tacacs_authentication_service, .name = "Authentication-Service", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
89
  { .out = &attr_tacacs_authentication_status, .name = "Authentication-Status", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
90
  { .out = &attr_tacacs_authentication_type, .name = "Authentication-Type", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
91
  { .out = &attr_tacacs_authorization_status, .name = "Authorization-Status", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
92
  { .out = &attr_tacacs_argument_list, .name = "Argument-List", .type = FR_TYPE_STRING, .dict = &dict_tacacs },
93
  { .out = &attr_tacacs_client_port, .name = "Client-Port", .type = FR_TYPE_STRING, .dict = &dict_tacacs },
94
  { .out = &attr_tacacs_data, .name = "Data", .type = FR_TYPE_OCTETS, .dict = &dict_tacacs },
95
  { .out = &attr_tacacs_flags, .name = "Packet.Flags", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
96
  { .out = &attr_tacacs_length, .name = "Packet.Length", .type = FR_TYPE_UINT32, .dict = &dict_tacacs },
97
  { .out = &attr_tacacs_packet, .name = "Packet", .type = FR_TYPE_STRUCT, .dict = &dict_tacacs },
98
  { .out = &attr_tacacs_packet_body_type, .name = "Packet-Body-Type", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
99
  { .out = &attr_tacacs_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_tacacs },
100
  { .out = &attr_tacacs_privilege_level, .name = "Privilege-Level", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
101
  { .out = &attr_tacacs_remote_address, .name = "Remote-Address", .type = FR_TYPE_STRING, .dict = &dict_tacacs },
102
  { .out = &attr_tacacs_sequence_number, .name = "Packet.Sequence-Number", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
103
  { .out = &attr_tacacs_server_message, .name = "Server-Message", .type = FR_TYPE_STRING, .dict = &dict_tacacs },
104
  { .out = &attr_tacacs_session_id, .name = "Packet.Session-Id", .type = FR_TYPE_UINT32, .dict = &dict_tacacs },
105
  { .out = &attr_tacacs_user_message, .name = "User-Message", .type = FR_TYPE_STRING, .dict = &dict_tacacs },
106
  { .out = &attr_tacacs_version_major, .name = "Packet.Version-Major", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
107
  { .out = &attr_tacacs_version_minor, .name = "Packet.Version-Minor", .type = FR_TYPE_UINT8, .dict = &dict_tacacs },
108
109
  { .out = &attr_tacacs_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_tacacs },
110
  { .out = &attr_tacacs_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_tacacs },
111
  { .out = &attr_tacacs_chap_password, .name = "CHAP-Password", .type = FR_TYPE_OCTETS, .dict = &dict_tacacs },
112
  { .out = &attr_tacacs_chap_challenge, .name = "CHAP-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_tacacs },
113
  { .out = &attr_tacacs_mschap_response, .name = "MS-CHAP-Response", .type = FR_TYPE_OCTETS, .dict = &dict_tacacs },
114
  { .out = &attr_tacacs_mschap2_response, .name = "MS-CHAP2-Response", .type = FR_TYPE_OCTETS, .dict = &dict_tacacs },
115
  { .out = &attr_tacacs_mschap_challenge, .name = "MS-CHAP-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_tacacs },
116
  DICT_AUTOLOAD_TERMINATOR
117
};
118
119
char const *fr_tacacs_packet_names[FR_TACACS_CODE_MAX] = {
120
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_START]   = "Authentication-Start",
121
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_PASS]    = "Authentication-Pass",
122
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_FAIL]    = "Authentication-Fail",
123
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_GETDATA]   = "Authentication-GetData",
124
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_GETUSER]   = "Authentication-GetUser",
125
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_GETPASS]   = "Authentication-GetPass",
126
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_RESTART]   = "Authentication-Restart",
127
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_ERROR]   = "Authentication-Error",
128
129
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE]    = "Authentication-Continue",
130
  [FR_PACKET_TYPE_VALUE_AUTHENTICATION_CONTINUE_ABORT]  = "Authentication-Continue-Abort",
131
132
  [FR_PACKET_TYPE_VALUE_AUTHORIZATION_REQUEST]    = "Authorization-Request",
133
  [FR_PACKET_TYPE_VALUE_AUTHORIZATION_PASS_ADD]   = "Authorization-Pass-Add",
134
  [FR_PACKET_TYPE_VALUE_AUTHORIZATION_PASS_REPLACE] = "Authorization-Pass-Replace",
135
  [FR_PACKET_TYPE_VALUE_AUTHORIZATION_FAIL]   = "Authorization-Fail",
136
  [FR_PACKET_TYPE_VALUE_AUTHORIZATION_ERROR]    = "Authorization-Error",
137
138
  [FR_PACKET_TYPE_VALUE_ACCOUNTING_REQUEST]   = "Accounting-Request",
139
  [FR_PACKET_TYPE_VALUE_ACCOUNTING_SUCCESS]   = "Accounting-Success",
140
  [FR_PACKET_TYPE_VALUE_ACCOUNTING_ERROR]     = "Accounting-Error",
141
};
142
143
144
/** XOR the body based on the secret key.
145
 *
146
 *  This function encrypts (or decrypts) TACACS+ packets, and sets the "encrypted" flag.
147
 */
148
int fr_tacacs_body_xor(fr_tacacs_packet_t const *pkt, uint8_t *body, size_t body_len, char const *secret, size_t secret_len)
149
108
{
150
108
  uint8_t pad[MD5_DIGEST_LENGTH];
151
108
  uint8_t *buf, *end;
152
108
  int pad_offset;
153
154
  /*
155
   *  Do some basic sanity checks.
156
   */
157
108
  if (!secret_len) {
158
0
    fr_strerror_const("Failed to encrypt/decrept the packet, as the secret has zero length.");
159
0
    return -1;
160
0
  }
161
162
108
  pad_offset = sizeof(pkt->hdr.session_id) + secret_len + sizeof(pkt->hdr.version) + sizeof(pkt->hdr.seq_no);
163
164
  /* MD5_1 = MD5{session_id, key, version, seq_no} */
165
  /* MD5_n = MD5{session_id, key, version, seq_no, MD5_n-1} */
166
108
  buf = talloc_array(NULL, uint8_t, pad_offset + MD5_DIGEST_LENGTH);
167
108
  if (!buf) return -1;
168
169
108
  memcpy(&buf[0], &pkt->hdr.session_id, sizeof(pkt->hdr.session_id));
170
108
  memcpy(&buf[sizeof(pkt->hdr.session_id)], secret, secret_len);
171
108
  memcpy(&buf[sizeof(pkt->hdr.session_id) + secret_len], &pkt->hdr.version, sizeof(pkt->hdr.version));
172
108
  memcpy(&buf[sizeof(pkt->hdr.session_id) + secret_len + sizeof(pkt->hdr.version)], &pkt->hdr.seq_no, sizeof(pkt->hdr.seq_no));
173
174
108
  fr_md5_calc(pad, buf, pad_offset);
175
176
108
  end = body + body_len;
177
28.5k
  while (body < end) {
178
28.5k
    size_t i;
179
180
484k
    for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
181
456k
      *body ^= pad[i];
182
183
456k
      if (++body == end) goto done;
184
456k
    }
185
186
28.4k
    memcpy(&buf[pad_offset], pad, MD5_DIGEST_LENGTH);
187
28.4k
    fr_md5_calc(pad, buf, pad_offset + MD5_DIGEST_LENGTH);
188
28.4k
  }
189
190
108
done:
191
108
  talloc_free(buf);
192
193
108
  return 0;
194
108
}
195
196
/**
197
 *  Return how long a TACACS+ packet is
198
 *
199
 *  Note that we only look at the 12 byte packet header.  We don't
200
 *  (yet) do validation on authentication / authorization /
201
 *  accounting headers.  The packet may still be determined later
202
 *  to be invalid.
203
 *
204
 * @param buffer  to check
205
 * @param buffer_len  length of the buffer
206
 * @return
207
 *  >0    size of the TACACS+ packet.  We want.  MAY be larger than "buffer_len"
208
 *  <=0   error, packet should be discarded.
209
 */
210
ssize_t fr_tacacs_length(uint8_t const *buffer, size_t buffer_len)
211
0
{
212
0
  fr_tacacs_packet_t const *pkt = (fr_tacacs_packet_t const *) buffer;
213
0
  size_t length, want;
214
215
  /*
216
   *  Check that we have a full TACACS+ header before
217
   *  decoding anything.
218
   */
219
0
  if (buffer_len < sizeof(pkt->hdr)) {
220
0
    return sizeof(pkt->hdr);
221
0
  }
222
223
  /*
224
   *  TACACS major / minor version MUST be 12.0 or 12.1
225
   */
226
0
  if (!((buffer[0] == 0xc0) || (buffer[0] == 0xc1))) {
227
0
    fr_strerror_printf("Unsupported TACACS+ version %02x", buffer[0]);
228
0
    return -1;
229
0
  }
230
231
  /*
232
   *  There's no reason to accept 64K TACACS+ packets.
233
   */
234
0
  if ((buffer[8] != 0) || (buffer[9] != 0)) {
235
0
    fr_strerror_const("Packet is too large.  Our limit is 64K");
236
0
    return -1;
237
0
  }
238
239
  /*
240
   *  There are only 3 types of packets which are supported.
241
   */
242
0
  if (!((pkt->hdr.type == FR_TAC_PLUS_AUTHEN) ||
243
0
        (pkt->hdr.type == FR_TAC_PLUS_AUTHOR) ||
244
0
        (pkt->hdr.type == FR_TAC_PLUS_ACCT))) {
245
0
    fr_strerror_printf("Unknown packet type %d", pkt->hdr.type);
246
0
    return -1;
247
0
  }
248
249
0
  length = sizeof(pkt->hdr) + ntohl(pkt->hdr.length);
250
251
0
  if (buffer_len < length) return length;
252
253
  /*
254
   *  We want at least the headers for the various packet
255
   *  types.  Note that we do NOT check the lengths in the
256
   *  headers against buffer / buffer_len.  That process is
257
   *  complex and error-prone.  It's best to leave it in one
258
   *  place: fr_tacacs_decode().
259
   */
260
0
  switch (pkt->hdr.type) {
261
0
  default:
262
0
    fr_assert(0);  /* should have been caught above */
263
0
    return -1;
264
265
0
  case FR_TAC_PLUS_AUTHEN:
266
0
    if (packet_is_authen_start_request(pkt)) {
267
0
      want = sizeof(pkt->hdr) + sizeof(pkt->authen_start);
268
269
0
    } else if (packet_is_authen_continue(pkt)) {
270
0
      want = sizeof(pkt->hdr) + sizeof(pkt->authen_cont);
271
272
0
    } else {
273
0
      fr_assert(packet_is_authen_reply(pkt));
274
0
      want = sizeof(pkt->hdr) + sizeof(pkt->authen_reply);
275
0
    }
276
0
    break;
277
278
0
  case FR_TAC_PLUS_AUTHOR:
279
0
    if (packet_is_author_request(pkt)) {
280
0
      want = sizeof(pkt->hdr) + sizeof(pkt->author_req);
281
0
    } else {
282
0
      fr_assert(packet_is_author_reply(pkt));
283
0
      want = sizeof(pkt->hdr) + sizeof(pkt->author_reply);
284
0
    }
285
0
    break;
286
287
0
  case FR_TAC_PLUS_ACCT:
288
0
    if (packet_is_acct_request(pkt)) {
289
0
      want = sizeof(pkt->hdr) + sizeof(pkt->acct_req);
290
0
    } else {
291
0
      fr_assert(packet_is_acct_reply(pkt));
292
0
      want = sizeof(pkt->hdr) + sizeof(pkt->acct_reply);
293
0
    }
294
0
    break;
295
0
  }
296
297
0
  if (want > length) {
298
0
    fr_strerror_printf("Packet is too small.  Want %zu, got %zu", want, length);
299
0
    return -1;
300
0
  }
301
302
0
  return length;
303
0
}
304
305
static void print_hex(fr_log_t const *log, char const *file, int line, char const *prefix, uint8_t const *data, size_t datalen)
306
0
{
307
0
  if (!datalen) return;
308
309
0
  fr_log_hex(log, L_DBG, file, line, data, datalen, "%s", prefix);
310
0
}
311
312
static void print_ascii(fr_log_t const *log, char const *file, int line, char const *prefix, uint8_t const *data, size_t datalen)
313
0
{
314
0
  uint8_t const *p;
315
316
0
  if (!datalen) return;
317
318
0
  if (datalen > 80) {
319
0
  hex:
320
0
    print_hex(log, file, line, prefix, data, datalen);
321
0
    return;
322
0
  }
323
324
0
  for (p = data; p < (data + datalen); p++) {
325
0
    if ((*p < 0x20) || (*p > 0x80)) goto hex;
326
0
  }
327
328
0
  fr_log(log, L_DBG, file, line, "%s %.*s", prefix, (int) datalen, (char const *) data);
329
0
}
330
331
0
#define CHECK(_length) do { \
332
0
  size_t plen = _length; \
333
0
  if ((size_t) (end - p) < plen) { \
334
0
    fr_log_hex(log, L_DBG, file, line, p, end - p, "%s", "      TRUNCATED     "); \
335
0
    return; \
336
0
  } \
337
0
  data = p; \
338
0
  data_len = plen; \
339
0
  p += plen; \
340
0
    } while (0)
341
342
#undef ASCII
343
0
#define ASCII(_prefix, _field) do { \
344
0
  CHECK(_field); \
345
0
  print_ascii(log, file, line, _prefix, data, data_len); \
346
0
   } while (0)
347
348
#undef HEXIT
349
0
#define HEXIT(_prefix, _field) do { \
350
0
  CHECK(_field); \
351
0
  print_hex(log, file, line, _prefix, data, data_len); \
352
0
   } while (0)
353
354
0
#define PRINT(_fmt, ...) fr_log(log, L_DBG, file, line, _fmt, ## __VA_ARGS__)
355
356
static void print_args(fr_log_t const *log, char const *file, int line, size_t arg_cnt, uint8_t const *argv, uint8_t const *start, uint8_t const *end)
357
0
{
358
0
  size_t i, data_len;
359
0
  uint8_t const *p;
360
0
  uint8_t const *data;
361
0
  char prefix[64];
362
363
0
  if (argv + arg_cnt > end) {
364
0
    PRINT("      ARG cnt overflows packet");
365
0
    return;
366
0
  }
367
368
0
  p = start;
369
0
  for (i = 0; i < arg_cnt; i++) {
370
0
    if (p == end) {
371
0
      PRINT("      ARG[%zu] is at EOF", i);
372
0
      return;
373
0
    }
374
375
0
    if ((end - p) < argv[i]) {
376
0
      PRINT("      ARG[%zu] overflows packet", i);
377
0
      print_hex(log, file, line, "                     ", p, end - p);
378
0
      return;
379
0
    }
380
381
0
    snprintf(prefix, sizeof(prefix), "      arg[%zu]            ", i);
382
0
    prefix[21] = '\0';
383
384
0
    ASCII(prefix, argv[i]);
385
0
  }
386
0
}
387
388
void _fr_tacacs_packet_log_hex(fr_log_t const *log, fr_tacacs_packet_t const *packet, size_t packet_len, char const *file, int line)
389
0
{
390
0
  size_t length, data_len;
391
0
  uint8_t const *p = (uint8_t const *) packet;
392
0
  uint8_t const *hdr, *end, *args;
393
0
  uint8_t const *data;
394
395
0
  end = ((uint8_t const *) packet) + packet_len;
396
397
0
  if (packet_len < 12) {
398
0
    print_hex(log, file, line, "header ", p, packet_len);
399
0
    return;
400
0
  }
401
402
  /*
403
   *  It has to be at least 12 bytes long.
404
   */
405
0
  PRINT("  major  %d", (p[0] & 0xf0) >> 4);
406
0
  PRINT("  minor  %d", (p[0] & 0x0f));
407
408
0
  PRINT("  type   %02x", p[1]);
409
0
  PRINT("  seq_no %02x", p[2]);
410
0
  PRINT("  flags  %02x", p[3]);
411
412
0
  PRINT("  sessid %08x", fr_nbo_to_uint32(p + 4));
413
0
  PRINT("  length %08x", fr_nbo_to_uint32(p + 8));
414
415
0
  PRINT("  body");
416
0
  length = fr_nbo_to_uint32(p + 8);
417
418
0
  if ((p[3] & 0x01) == 0) {
419
0
    PRINT("  ... encrypted ...");
420
0
    return;
421
0
  }
422
423
0
  if (length > 65535) {
424
0
    PRINT("      TOO LARGE");
425
0
    return;
426
0
  }
427
428
0
  p += 12;
429
0
  hdr = p;
430
431
0
  if ((p + length) != end) {
432
0
    PRINT("length field does not match input packet length %08lx", packet_len - 12);
433
0
    return;
434
0
  }
435
436
0
#define REQUIRE(_length) do { \
437
0
  size_t plen = _length; \
438
0
  if ((size_t) (end - hdr) < plen) { \
439
0
    print_hex(log, file, line, "      TRUNCATED     ", hdr, end - hdr); \
440
0
    return; \
441
0
  } \
442
0
  p = hdr + plen; \
443
0
    } while (0)
444
445
0
  switch (packet->hdr.type) {
446
0
  default:
447
0
    print_hex(log, file, line, "      data   ", p, length);
448
0
    return;
449
450
0
  case FR_TAC_PLUS_AUTHEN:
451
0
    if (packet_is_authen_start_request(packet)) {
452
0
      PRINT("      authentication-start");
453
454
0
      REQUIRE(8);
455
456
0
      PRINT("      action          %02x", hdr[0]);
457
0
      PRINT("      priv_lvl        %02x", hdr[1]);
458
0
      PRINT("      authen_type     %02x", hdr[2]);
459
0
      PRINT("      authen_service  %02x", hdr[3]);
460
0
      PRINT("      user_len        %02x", hdr[4]);
461
0
      PRINT("      port_len        %02x", hdr[5]);
462
0
      PRINT("      rem_addr_len    %02x", hdr[6]);
463
0
      PRINT("      data_len        %02x", hdr[7]);
464
465
0
      ASCII("      user           ", hdr[4]);
466
0
      ASCII("      port           ", hdr[5]);
467
0
      ASCII("      rem_addr       ", hdr[6]);
468
0
      HEXIT("      data           ", hdr[7]); /* common auth flows */
469
470
0
    } else if (packet_is_authen_continue(packet)) {
471
0
      PRINT("      authentication-continue");
472
473
0
      REQUIRE(5);
474
475
0
      PRINT("      user_msg_len    %04x", fr_nbo_to_uint16(hdr));
476
0
      PRINT("      data_len        %04x", fr_nbo_to_uint16(hdr + 2));
477
0
      PRINT("      flags           %02x", hdr[4]);
478
479
0
      ASCII("      user_msg       ", fr_nbo_to_uint16(hdr));
480
0
      HEXIT("      data           ", fr_nbo_to_uint16(hdr + 2));
481
482
0
    } else {
483
0
      fr_assert(packet_is_authen_reply(packet));
484
485
0
      PRINT("      authentication-reply");
486
487
0
      REQUIRE(6);
488
489
0
      PRINT("      status          %02x", hdr[0]);
490
0
      PRINT("      flags           %02x", hdr[1]);
491
0
      PRINT("      server_msg_len  %04x", fr_nbo_to_uint16(hdr + 2));
492
0
      PRINT("      data_len        %04x", fr_nbo_to_uint16(hdr + 4));
493
494
0
      ASCII("      server_msg     ", fr_nbo_to_uint16(hdr + 2));
495
0
      HEXIT("      data           ", fr_nbo_to_uint16(hdr + 4));
496
0
    }
497
498
0
    fr_assert(p == end);
499
0
    break;
500
501
0
  case FR_TAC_PLUS_AUTHOR:
502
0
    if (packet_is_author_request(packet)) {
503
0
      PRINT("      authorization-request");
504
0
      REQUIRE(8);
505
506
0
      PRINT("      auth_method     %02x", hdr[0]);
507
0
      PRINT("      priv_lvl        %02x", hdr[1]);
508
0
      PRINT("      authen_type     %02x", hdr[2]);
509
0
      PRINT("      authen_service  %02x", hdr[3]);
510
0
      PRINT("      user_len        %02x", hdr[4]);
511
0
      PRINT("      port_len        %02x", hdr[5]);
512
0
      PRINT("      rem_addr_len    %02x", hdr[6]);
513
0
      PRINT("      arg_cnt         %02x", hdr[7]);
514
0
      args = p;
515
516
0
      HEXIT("      argc           ", hdr[7]);
517
0
      ASCII("      user           ", hdr[4]);
518
0
      ASCII("      port           ", hdr[5]);
519
0
      ASCII("      rem_addr       ", hdr[6]);
520
521
0
      print_args(log, file, line, hdr[7], args, p, end);
522
523
0
    } else {
524
0
      PRINT("      authorization-reply");
525
526
0
      fr_assert(packet_is_author_reply(packet));
527
528
0
      REQUIRE(6);
529
530
0
      PRINT("      status          %02x", hdr[0]);
531
0
      PRINT("      arg_cnt         %02x", hdr[1]);
532
0
      PRINT("      server_msg_len  %04x", fr_nbo_to_uint16(hdr + 2));
533
0
      PRINT("      data_len        %04x", fr_nbo_to_uint16(hdr + 4));
534
0
      args = p;
535
536
0
      HEXIT("      argc           ", hdr[1]);
537
0
      ASCII("      server_msg     ", fr_nbo_to_uint16(hdr + 2));
538
0
      ASCII("      data           ", fr_nbo_to_uint16(hdr + 4));
539
540
0
      print_args(log, file, line, hdr[1], args, p, end);
541
0
    }
542
0
    break;
543
544
0
  case FR_TAC_PLUS_ACCT:
545
0
    if (packet_is_acct_request(packet)) {
546
0
      PRINT("      accounting-request");
547
548
0
      REQUIRE(9);
549
550
0
      PRINT("      flags           %02x", hdr[0]);
551
0
      PRINT("      auth_method     %02x", hdr[1]);
552
0
      PRINT("      priv_lvl        %02x", hdr[2]);
553
0
      PRINT("      authen_type     %02x", hdr[3]);
554
0
      PRINT("      authen_service  %02x", hdr[4]);
555
0
      PRINT("      user_len        %02x", hdr[5]);
556
0
      PRINT("      port_len        %02x", hdr[6]);
557
0
      PRINT("      rem_addr_len    %02x", hdr[7]);
558
0
      PRINT("      arg_cnt         %02x", hdr[8]);
559
0
      args = p;
560
561
0
      HEXIT("      argc           ", hdr[8]);
562
0
      ASCII("      user           ", hdr[5]);
563
0
      ASCII("      port           ", hdr[6]);
564
0
      ASCII("      rem_addr       ", hdr[7]);
565
566
0
      print_args(log, file, line, hdr[8], args, p, end);
567
0
    } else {
568
0
      PRINT("      accounting-reply");
569
0
      fr_assert(packet_is_acct_reply(packet));
570
571
0
      REQUIRE(5);
572
573
0
      PRINT("      server_msg_len  %04x", fr_nbo_to_uint16(hdr));
574
0
      PRINT("      data_len        %04x", fr_nbo_to_uint16(hdr + 2));
575
0
      PRINT("      status          %02x", hdr[0]);
576
577
0
      ASCII("      server_msg     ", fr_nbo_to_uint16(hdr));
578
0
      HEXIT("      data           ", fr_nbo_to_uint16(hdr + 2));
579
580
0
      fr_assert(p == end);
581
0
    }
582
0
    break;
583
0
  }
584
0
}
585
586
int fr_tacacs_global_init(void)
587
5.01k
{
588
5.01k
  if (instance_count > 0) {
589
5.00k
    instance_count++;
590
5.00k
    return 0;
591
5.00k
  }
592
593
2
  instance_count++;
594
595
2
  if (fr_dict_autoload(libfreeradius_tacacs_dict) < 0) {
596
0
  fail:
597
0
    instance_count--;
598
0
    return -1;
599
0
  }
600
601
2
  if (fr_dict_attr_autoload(libfreeradius_tacacs_dict_attr) < 0) {
602
0
    fr_dict_autofree(libfreeradius_tacacs_dict);
603
0
    goto fail;
604
0
  }
605
606
2
  instantiated = true;
607
2
  return 0;
608
2
}
609
610
void fr_tacacs_global_free(void)
611
5.01k
{
612
5.01k
  if (!instantiated) return;
613
614
5.01k
  fr_assert(instance_count > 0);
615
616
5.01k
  if (--instance_count > 0) return;
617
618
2
  fr_dict_autofree(libfreeradius_tacacs_dict);
619
2
  instantiated = false;
620
2
}
621
622
static bool attr_valid(fr_dict_attr_t *da)
623
174
{
624
174
  fr_dict_attr_flags_t *flags = &da->flags;
625
626
  /*
627
   *  No arrays in TACACS+
628
   */
629
174
  if (flags->array) {
630
0
    fr_strerror_const("Attributes with flag 'array' cannot be used in TACACS+");
631
0
    return false;
632
0
  }
633
634
174
  if ((strcmp(da->name, "Packet") == 0) &&
635
2
      (da->depth == 1)) {
636
2
    if (da->type != FR_TYPE_STRUCT) {
637
0
      fr_strerror_const("The top 'Packet' attribute must of type 'struct'");
638
0
      return false;
639
0
    }
640
641
2
    return true;
642
2
  }
643
644
  /*
645
   *  The top-level Packet is a STRUCT which contains
646
   *  MEMBERs with defined values.
647
   */
648
172
  if (!flags->name_only && (da->parent->type != FR_TYPE_STRUCT)) {
649
0
    fr_strerror_const("Attributes in TACACS+ cannot have assigned values.  Use DEFINE, not ATTRIBUTE");
650
0
    return false;
651
0
  }
652
653
172
  switch (da->type) {
654
0
  case FR_TYPE_STRUCTURAL_EXCEPT_GROUP:
655
0
  case FR_TYPE_INTERNAL:
656
0
    fr_strerror_printf("Attributes of type '%s' cannot be used in TACACS+", fr_type_to_str(da->type));
657
0
    return false;
658
659
172
  default:
660
172
    break;
661
172
  }
662
663
172
  return true;
664
172
}
665
666
extern fr_dict_protocol_t libfreeradius_tacacs_dict_protocol;
667
fr_dict_protocol_t libfreeradius_tacacs_dict_protocol = {
668
  .name = "tacacs",
669
  .default_type_size = 4,
670
  .default_type_length = 4,
671
  .attr = {
672
    .valid = attr_valid,
673
  },
674
675
  .init = fr_tacacs_global_init,
676
  .free = fr_tacacs_global_free,
677
};