Coverage Report

Created: 2025-12-14 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/vmps/vmps.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program 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
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; 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: 9ca073ffef31f13de986c2f6728d7407c9c6b323 $
19
 *
20
 * @file src/protocols/vmps/vmps.c
21
 * @brief Functions to send/receive VQP packets.
22
 *
23
 * @copyright 2007 Alan DeKok (aland@deployingradius.com)
24
 */
25
26
RCSID("$Id: 9ca073ffef31f13de986c2f6728d7407c9c6b323 $")
27
28
#include <freeradius-devel/io/test_point.h>
29
#include <freeradius-devel/protocol/vmps/vmps.h>
30
#include <freeradius-devel/util/dbuff.h>
31
#include <freeradius-devel/util/proto.h>
32
#include <freeradius-devel/util/udp.h>
33
34
#include "vmps.h"
35
#include "attrs.h"
36
37
/** Used as the decoder ctx
38
 *
39
 */
40
typedef struct {
41
  int   nothing;
42
} fr_vmps_ctx_t;
43
44
/*
45
 *  http://www.openbsd.org/cgi-bin/cvsweb/src/usr.sbin/tcpdump/print-vqp.c
46
 *
47
 *  Chapter 12 of Hacking Cisco Networks Exposed (Vladimirov, Gavrilenko,
48
 *  and Mikhailovsky, McGraw-Hill 2005)  describes some of how it works.
49
 *
50
 * VLAN Query Protocol (VQP)
51
 *
52
 *   0                   1                   2                   3
53
 *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
54
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55
 *  |    Version    |    Opcode     | Response Code |  Data Count   |
56
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
57
 *  |                         Transaction ID                        |
58
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59
 *  |                            Type (1)                           |
60
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
61
 *  |             Length            |            Data               /
62
 *  /                                                               /
63
 *  /                                                               /
64
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
65
 *  |                            Type (n)                           |
66
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
67
 *  |             Length            |            Data               /
68
 *  /                                                               /
69
 *  /                                                               /
70
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
71
 *
72
 * VQP is layered over UDP.  The default destination port is 1589.
73
 *
74
 */
75
char const *fr_vmps_packet_names[FR_VMPS_CODE_MAX] = {
76
  [FR_PACKET_TYPE_VALUE_JOIN_REQUEST] = "Join-Request",
77
  [FR_PACKET_TYPE_VALUE_JOIN_RESPONSE] = "Join-Response",
78
  [FR_PACKET_TYPE_VALUE_RECONFIRM_REQUEST] = "Reconfirm-Request",
79
  [FR_PACKET_TYPE_VALUE_RECONFIRM_RESPONSE] = "Reconfirm-Response",
80
};
81
82
83
bool fr_vmps_ok(uint8_t const *packet, size_t *packet_len)
84
0
{
85
0
  uint8_t const *ptr;
86
0
  ssize_t   data_len;
87
0
  int   attrlen;
88
89
0
  if (*packet_len == FR_VQP_HDR_LEN) return true;
90
91
  /*
92
   *  Skip the header.
93
   */
94
0
  ptr = packet + FR_VQP_HDR_LEN;
95
0
  data_len = *packet_len - FR_VQP_HDR_LEN;
96
97
0
  while (data_len > 0) {
98
0
    if (data_len < 7) {
99
0
      fr_strerror_const("Packet contains malformed attribute");
100
0
      return false;
101
0
    }
102
103
    /*
104
     *  Attributes are 4 bytes
105
     *  0x00000c01 ... 0x00000c08
106
     */
107
0
    if ((ptr[0] != 0) || (ptr[1] != 0) ||
108
0
        (ptr[2] != 0x0c) || (ptr[3] < 1) || (ptr[3] > 8)) {
109
0
      fr_strerror_const("Packet contains invalid attribute");
110
0
      return false;
111
0
    }
112
113
    /*
114
     *  Length is 2 bytes
115
     */
116
0
    attrlen = fr_nbo_to_uint16(ptr + 4);
117
118
    /*
119
     *  Total of attribute lengths shouldn't exceed *packet_len - header length,
120
     *  which happens iff at some point, attrlen exceeds data_lan.
121
     */
122
0
    if (attrlen > data_len) {
123
0
      fr_strerror_printf("Packet attributes cause total length "
124
0
             "plus header length to exceed packet length %lx",
125
0
             *packet_len);
126
0
      return false;
127
0
    }
128
129
    /*
130
     *  We support short lengths, as there's no reason
131
     *  for bigger lengths to exist... admins won't be
132
     *  typing in a 32K vlan name.
133
     *
134
     *  It's OK for ethernet frames to be longer.
135
     */
136
0
    if ((ptr[3] != 5) && (attrlen > 250)) {
137
0
      fr_strerror_printf("Packet contains attribute with invalid length %02x %02x", ptr[4], ptr[5]);
138
0
      return false;
139
0
    }
140
141
0
    ptr += 6 + attrlen;
142
0
    data_len -= (6 + attrlen);
143
0
  }
144
145
  /*
146
   *  UDP reads may return too much data, so we truncate it.
147
   */
148
0
  *packet_len = ptr - packet;
149
150
0
  return true;
151
0
}
152
153
154
int fr_vmps_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, unsigned int *code)
155
406
{
156
406
  uint8_t const   *ptr, *end;
157
406
  int   attr;
158
406
  size_t    attr_len;
159
406
  fr_pair_t *vp;
160
161
406
  if (data_len < FR_VQP_HDR_LEN) return -1;
162
163
401
  vp = fr_pair_afrom_da(ctx, attr_packet_type);
164
401
  if (!vp) {
165
0
  oom:
166
0
    fr_strerror_const("Out of Memory");
167
0
    return -1;
168
0
  }
169
401
  vp->vp_uint32 = data[1];
170
401
  if (code) *code = data[1];
171
401
  vp->vp_tainted = true;
172
401
  fr_pair_append(out, vp);
173
174
401
  vp = fr_pair_afrom_da(ctx, attr_error_code);
175
401
  if (!vp) goto oom;
176
401
  vp->vp_uint32 = data[2];
177
401
  vp->vp_tainted = true;
178
401
  fr_pair_append(out, vp);
179
180
401
  vp = fr_pair_afrom_da(ctx, attr_sequence_number);
181
401
  if (!vp) goto oom;
182
183
401
  vp->vp_uint32 = fr_nbo_to_uint32(data + 4);
184
401
  vp->vp_tainted = true;
185
401
  fr_pair_append(out, vp);
186
187
401
  ptr = data + FR_VQP_HDR_LEN;
188
401
  end = data + data_len;
189
190
  /*
191
   *  Note that vmps_recv() MUST ensure that the packet is
192
   *  formatted in a way we expect, and that vmps_recv() MUST
193
   *  be called before vmps_decode().
194
   */
195
1.39M
  while (ptr < end) {
196
1.39M
    if ((end - ptr) < 6) {
197
34
      fr_strerror_printf("Packet is too small. (%ld < 6)", (end - ptr));
198
34
      return -1;
199
34
    }
200
201
1.39M
    attr = fr_nbo_to_uint16(ptr + 2);
202
1.39M
    attr_len = fr_nbo_to_uint16(ptr + 4);
203
1.39M
    ptr += 6;
204
205
    /*
206
     *  fr_vmps_ok() should have checked this already,
207
     *  but it doesn't hurt to do it again.
208
     */
209
1.39M
    if (attr_len > (size_t) (end - ptr)) {
210
44
      fr_strerror_const("Attribute length exceeds received data");
211
44
      return -1;
212
44
    }
213
214
    /*
215
     *  Create the VP.
216
     */
217
1.39M
    vp = fr_pair_afrom_child_num(ctx, fr_dict_root(dict_vmps), attr);
218
1.39M
    if (!vp) {
219
0
      fr_strerror_const("No memory");
220
0
      return -1;
221
0
    }
222
223
    /*
224
     *  Rely on value_box to do the work.
225
     *
226
     *  @todo - if the attribute is malformed, create a "raw" one.
227
     */
228
1.39M
    if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
229
1.39M
                &FR_DBUFF_TMP(ptr, attr_len), attr_len, true) < 0) {
230
52
      talloc_free(vp);
231
52
      return -1;
232
52
    }
233
234
1.39M
    ptr += attr_len;
235
1.39M
    vp->vp_tainted = true;
236
1.39M
    fr_pair_append(out, vp);
237
1.39M
  }
238
239
  /*
240
   *  FIXME: Map attributes to Calling-Station-Id, etc...
241
   */
242
243
271
  return data_len;
244
401
}
245
246
#if 0
247
/*
248
 *  These are the MUST HAVE contents for a VQP packet.
249
 *
250
 *  We don't allow the caller to give less than these, because
251
 *  it won't work.  We don't encode more than these, because the
252
 *  clients will ignore it.
253
 *
254
 *  FIXME: Be more generous?  Look for CISCO + VQP attributes?
255
 *
256
 *  @todo - actually use these again...
257
 */
258
static int contents[5][VQP_MAX_ATTRIBUTES] = {
259
  { 0,      0,      0,      0,      0,      0 },
260
  { 0x0c01, 0x0c02, 0x0c03, 0x0c04, 0x0c07, 0x0c05 }, /* Join request */
261
  { 0x0c03, 0x0c08, 0,      0,      0,      0 },  /* Join Response */
262
  { 0x0c01, 0x0c02, 0x0c03, 0x0c04, 0x0c07, 0x0c08 }, /* Reconfirm */
263
  { 0x0c03, 0x0c08, 0,      0,      0,      0 }
264
};
265
#endif
266
267
ssize_t fr_vmps_encode(fr_dbuff_t *dbuff, uint8_t const *original,
268
           int code, uint32_t seq_no, fr_dcursor_t *cursor)
269
271
{
270
271
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
271
271
  fr_pair_t     *vp;
272
271
  fr_dbuff_marker_t hdr, m;
273
271
  uint8_t     data_count = 0;
274
275
  /*
276
   *  Let's keep a reference for packet header, and another
277
   *  to let us write to to the encoding as needed.
278
   */
279
271
  fr_dbuff_marker(&hdr, &work_dbuff);
280
271
  fr_dbuff_marker(&m, &work_dbuff);
281
282
  /*
283
   *  Create the header
284
   */
285
271
  FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, FR_VQP_VERSION,     /* Version */
286
271
                code,       /* Opcode */
287
271
                FR_ERROR_CODE_VALUE_NO_ERROR, /* Response Code */
288
271
                data_count);      /* Data Count */
289
290
271
  if (original) {
291
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, original + 4, 4);
292
271
  } else {
293
271
    FR_DBUFF_IN_RETURN(&work_dbuff, seq_no);
294
271
  }
295
296
  /*
297
   *  Encode the VP's.
298
   */
299
74.2k
  while ((vp = fr_dcursor_current(cursor))) {
300
74.0k
    ssize_t slen;
301
302
74.0k
    if (vp->da == attr_packet_type) {
303
1.18k
      fr_dbuff_set(&m, fr_dbuff_current(&hdr) + 1);
304
1.18k
      FR_DBUFF_IN_RETURN(&m, (uint8_t)vp->vp_uint32);
305
1.18k
      fr_dcursor_next(cursor);
306
1.18k
      continue;
307
1.18k
    }
308
309
72.8k
    if (vp->da == attr_error_code) {
310
1.20k
      fr_dbuff_set(&m, fr_dbuff_current(&hdr) + 2);
311
1.20k
      FR_DBUFF_IN_RETURN(&m, vp->vp_uint8);
312
1.20k
      fr_dcursor_next(cursor);
313
1.20k
      continue;
314
1.20k
    }
315
316
71.6k
    if (!original && (vp->da == attr_sequence_number)) {
317
361
      fr_dbuff_set(&m, fr_dbuff_current(&hdr) + 4);
318
361
      FR_DBUFF_IN_RETURN(&m, vp->vp_uint32);
319
361
      fr_dcursor_next(cursor);
320
361
      continue;
321
361
    }
322
323
71.2k
    if (!fr_type_is_leaf(vp->vp_type)) continue;
324
325
    /*
326
     *  Type.  Note that we look at only the lower 8
327
     *  bits, as the upper 8 bits have been hacked.
328
     *  See also dictionary.vmps
329
     */
330
331
    /* Type */
332
71.2k
    FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, 0x00, 0x00, 0x0c, (vp->da->attr & 0xff));
333
334
    /* Length */
335
71.2k
    fr_dbuff_set(&m, fr_dbuff_current(&work_dbuff));
336
71.2k
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint16_t) 0);
337
338
    /* Data */
339
71.2k
    slen = fr_value_box_to_network(&work_dbuff, &vp->data);
340
71.2k
    if (slen < 0) return slen;
341
342
71.2k
    FR_DBUFF_IN_RETURN(&m, (uint16_t) slen);
343
344
71.2k
    data_count++;
345
346
71.2k
    fr_dcursor_next(cursor);
347
71.2k
  }
348
349
  /*
350
   *  Update the Data Count
351
   */
352
230
  fr_dbuff_set(&m, fr_dbuff_current(&hdr) + 3);
353
230
  FR_DBUFF_IN_RETURN(&m, data_count);
354
355
230
  return fr_dbuff_set(dbuff, &work_dbuff);
356
230
}
357
358
359
/** See how big of a packet is in the buffer.
360
 *
361
 * Packet is not 'const * const' because we may update data_len, if there's more data
362
 * in the UDP packet than in the VMPS packet.
363
 *
364
 * @param data pointer to the packet buffer
365
 * @param data_len length of the packet buffer
366
 * @return
367
 *  <= 0 packet is bad.
368
 *      >0 how much of the data is a packet (can be larger than data_len)
369
 */
370
ssize_t fr_vmps_packet_size(uint8_t const *data, size_t data_len)
371
0
{
372
0
  int attributes;
373
0
  uint8_t const *ptr, *end;
374
375
0
  if (data_len < FR_VQP_HDR_LEN) return FR_VQP_HDR_LEN;
376
377
  /*
378
   *  No attributes.
379
   */
380
0
  if (data[3] == 0) return FR_VQP_HDR_LEN;
381
382
  /*
383
   *  Too many attributes.  Return an error indicating that
384
   *  there's a problem with octet 3.
385
   */
386
0
  if (data[3] > 30) return -3;
387
388
  /*
389
   *  Look for attributes.
390
   */
391
0
  ptr = data + FR_VQP_HDR_LEN;
392
0
  attributes = data[3];
393
394
0
  end = data + data_len;
395
396
0
  while (attributes > 0) {
397
0
    size_t attr_len;
398
399
    /*
400
     *  Not enough room for the attribute headers, we
401
     *  want at least those.
402
     */
403
0
    if ((end - ptr) < 6) {
404
0
      return 6 * attributes;
405
0
    }
406
407
    /*
408
     *  Length of the data NOT including the header.
409
     */
410
0
    attr_len = fr_nbo_to_uint16(ptr + 4);
411
412
0
    ptr += 6 + attr_len;
413
414
    /*
415
     *  We don't want to read infinite amounts of data.
416
     *
417
     *  Return an error indicating that there's a
418
     *  problem with the final octet
419
     */
420
0
    if ((ptr - data) > 4096) {
421
0
      return -(ptr - data);
422
0
    }
423
424
    /*
425
     *  This attribute has been checked.
426
     */
427
0
    attributes--;
428
429
    /*
430
     *  The packet we want is larger than the input
431
     *  buffer, so we return the length of the current
432
     *  attribute, plus the length of the remaining
433
     *  headers.
434
     */
435
0
    if (ptr > end) return (6 * attributes) + ptr - data;
436
0
  }
437
438
  /*
439
   *  We've reached the end of the packet.
440
   */
441
0
  return ptr - data;
442
0
}
443
444
static void print_hex_data(uint8_t const *ptr, int attrlen, int depth)
445
0
{
446
0
  int i;
447
0
  static char const tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
448
449
0
  for (i = 0; i < attrlen; i++) {
450
0
    if ((i > 0) && ((i & 0x0f) == 0x00))
451
0
      fprintf(fr_log_fp, "%.*s", depth, tabs);
452
0
    fprintf(fr_log_fp, "%02x ", ptr[i]);
453
0
    if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n");
454
0
  }
455
0
  if ((i & 0x0f) != 0) fprintf(fr_log_fp, "\n");
456
0
}
457
458
459
/** Print a raw VMPS packet as hex.
460
 *
461
 */
462
void fr_vmps_print_hex(FILE *fp, uint8_t const *packet, size_t packet_len)
463
0
{
464
0
  uint32_t length;
465
0
  uint8_t const *attr, *end;
466
0
  uint32_t id;
467
468
0
  if (packet_len < 8) return;
469
470
0
  fprintf(fp, "  Version:\t\t%u\n", packet[0]);
471
472
0
  if ((packet[1] > 0) && (packet[1] < FR_VMPS_CODE_MAX) && fr_vmps_packet_names[packet[1]]) {
473
0
    fprintf(fp, "  OpCode:\t\t%s\n", fr_vmps_packet_names[packet[1]]);
474
0
  } else {
475
0
    fprintf(fp, "  OpCode:\t\t%u\n", packet[1]);
476
0
  }
477
478
0
  if ((packet[2] > 0) && (packet[2] < FR_VMPS_CODE_MAX) && fr_vmps_packet_names[packet[2]]) {
479
0
    fprintf(fp, "  OpCode:\t\t%s\n", fr_vmps_packet_names[packet[2]]);
480
0
  } else {
481
0
    fprintf(fp, "  OpCode:\t\t%u\n", packet[2]);
482
0
  }
483
484
0
  fprintf(fp, "  Data Count:\t\t%u\n", packet[3]);
485
486
0
  memcpy(&id, packet + 4, 4);
487
0
  id = ntohl(id);
488
489
0
  fprintf(fp, "  ID:\t%08x\n", id);
490
491
0
  if (packet_len == 8) return;
492
493
0
  for (attr = packet + 8, end = packet + packet_len;
494
0
       attr < end;
495
0
       attr += length) {
496
0
    memcpy(&id, attr, 4);
497
0
    id = ntohl(id);
498
499
0
    length = fr_nbo_to_uint16(attr + 4);
500
0
    if (length > (end - attr)) break;
501
502
0
    fprintf(fp, "\t\t%08x  %04x  ", id, length);
503
504
0
    print_hex_data(attr + 6, length - 6, 3);
505
0
  }
506
0
}
507
508
/*
509
 *  Test points for protocol decode
510
 */
511
static ssize_t fr_vmps_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out,
512
            uint8_t const *data, size_t data_len, void *proto_ctx)
513
406
{
514
406
  return fr_vmps_decode(ctx, out, data, data_len, proto_ctx);
515
406
}
516
517
static int _decode_test_ctx(UNUSED fr_vmps_ctx_t *proto_ctx)
518
406
{
519
406
  fr_vmps_global_free();
520
521
406
  return 0;
522
406
}
523
524
static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict,
525
         UNUSED fr_dict_attr_t const *root_da)
526
406
{
527
406
  fr_vmps_ctx_t *test_ctx;
528
529
406
  if (fr_vmps_global_init() < 0) return -1;
530
531
406
  test_ctx = talloc_zero(ctx, fr_vmps_ctx_t);
532
406
  if (!test_ctx) return -1;
533
534
406
  talloc_set_destructor(test_ctx, _decode_test_ctx);
535
536
406
  *out = test_ctx;
537
538
406
  return 0;
539
406
}
540
541
extern fr_test_point_proto_decode_t vmps_tp_decode_proto;
542
fr_test_point_proto_decode_t vmps_tp_decode_proto = {
543
  .test_ctx = decode_test_ctx,
544
  .func   = fr_vmps_decode_proto
545
};
546
547
/*
548
 *  Test points for protocol encode
549
 */
550
static ssize_t fr_vmps_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vps, uint8_t *data, size_t data_len, UNUSED void *proto_ctx)
551
271
{
552
271
  fr_dcursor_t cursor;
553
554
271
  fr_pair_dcursor_iter_init(&cursor, vps, fr_proto_next_encodable, dict_vmps);
555
556
271
  return fr_vmps_encode(&FR_DBUFF_TMP(data, data_len), NULL, -1, -1, &cursor);
557
271
}
558
559
static int _encode_test_ctx(UNUSED fr_vmps_ctx_t *proto_ctx)
560
406
{
561
406
  fr_vmps_global_free();
562
563
406
  return 0;
564
406
}
565
566
static int encode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict,
567
         UNUSED fr_dict_attr_t const *root_da)
568
406
{
569
406
  fr_vmps_ctx_t *test_ctx;
570
571
406
  if (fr_vmps_global_init() < 0) return -1;
572
573
406
  test_ctx = talloc_zero(ctx, fr_vmps_ctx_t);
574
406
  if (!test_ctx) return -1;
575
576
406
  talloc_set_destructor(test_ctx, _encode_test_ctx);
577
578
406
  *out = test_ctx;
579
580
406
  return 0;
581
406
}
582
583
extern fr_test_point_proto_encode_t vmps_tp_encode_proto;
584
fr_test_point_proto_encode_t vmps_tp_encode_proto = {
585
  .test_ctx = encode_test_ctx,
586
  .func   = fr_vmps_encode_proto
587
};