radius \
snmp \
tacacs \
- vmps
+ vmps \
+ tftp
#
# If we're building packages or crossbuilding, just do that.
PROTOCOL VMPS 6 format=2
PROTOCOL SNMP 7 format=4
PROTOCOL ARP 8
+PROTOCOL TFTP 9
PROTOCOL EAP-SIM 101
PROTOCOL EAP-AKA 102
PROTOCOL Control 255
--- /dev/null
+# -*- text -*-
+# Copyright (C) 2020 The FreeRADIUS Server project and contributors
+# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0
+# Version $Id$
+#
+# The FreeRADIUS Vendor-Specific dictionary for TFTP.
+#
+# Version: $Id$
+#
+
+PROTOCOL TFTP 9
+BEGIN-PROTOCOL TFTP
+
+$INCLUDE dictionary.freeradius.internal
+$INCLUDE dictionary.rfc1350
+
+END-PROTOCOL TFTP
--- /dev/null
+# -*- text -*-
+# Copyright (C) 2020 The FreeRADIUS Server project and contributors
+# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0
+# Version $Id$
+##############################################################################
+#
+# Internal FreeRADIUS TFTP dictionary.
+#
+# $Id$
+#
+##############################################################################
+
+#
+# TFTP packet header fields
+#
+FLAGS internal
+
+ATTRIBUTE Packet-Type 65536 uint32
+
+VALUE Packet-Type Read-Request 1
+VALUE Packet-Type Write-Request 2
+VALUE Packet-Type Data 3
+VALUE Packet-Type Acknowledgement 4
+VALUE Packet-Type Error 5
+VALUE Packet-Type Do-Not-Respond 6
--- /dev/null
+# -*- text -*-
+# Copyright (C) 2020 The FreeRADIUS Server project and contributors
+# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0
+# Version $Id$
+##############################################################################
+#
+# TFTP (Trivial File Transfer Protocol)
+#
+# The default destination port is 69.
+#
+# $Id$
+#
+##############################################################################
+
+#
+# TFTPD attributes for the packet header
+#
+ATTRIBUTE Opcode 1 uint16
+ATTRIBUTE Filename 2 string
+ATTRIBUTE Mode 3 uint8
+ATTRIBUTE Block-Size 4 uint16
+
+ATTRIBUTE Block 5 uint16
+ATTRIBUTE Data 6 octets
+ATTRIBUTE Error-Code 7 uint16
+ATTRIBUTE Error-Message 8 string
+
+VALUE Opcode Read-Request 0x0001
+VALUE Opcode Write-Request 0x0002
+VALUE Opcode Data 0x0003
+VALUE Opcode Acknowledgement 0x0004
+VALUE Opcode Error 0x0005
+
+VALUE Mode INVALID 0
+VALUE Mode ASCII 1
+VALUE Mode OCTET 2
+
+VALUE Error-Code Not-Defined 0
+VALUE Error-Code File-Not-Found 1
+VALUE Error-Code Access-Violation 2
+VALUE Error-Code Disk-Full 3
+VALUE Error-Code Illegal-Operation 4
+VALUE Error-Code Unknown-transfer-ID 5
+VALUE Error-Code File-Already-Exists 6
+VALUE Error-Code No-Such-User 7
fuzzer_dhcpv6.mk
fuzzer_tacacs.mk
fuzzer_vmps.mk
+fuzzer_tftp.mk
# The fuzzer binary needs special magic to run, as it doesn't parse
# command-line options. See fuzzer.mk for details.
#
-FUZZER_PROTOCOLS = radius dhcpv4 dhcpv6 tacacs vmps
+FUZZER_PROTOCOLS = radius dhcpv4 dhcpv6 tacacs vmps tftp
#
# Add the fuzzer only if everything was built with the fuzzing flags.
--- /dev/null
+#
+# Makefile
+#
+# Version: $Id$
+#
+TARGET := libfreeradius-tftp.a
+SOURCES := tftp.c base.c
+SRC_CFLAGS := -I$(top_builddir)/src -DNO_ASSERT
+TGT_PREREQS := libfreeradius-util.a
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file src/protocols/tftp/attrs.h
+ * @brief TFTP protocol.
+ * @author Jorge Pereira <jpereira@freeradius.org>
+ *
+ * @copyright 2021 The FreeRADIUS server project.
+ * @copyright 2021 Network RADIUS SARL (legal@networkradius.com)
+ */
+RCSIDH(tftp_attrs_h, "$Id$")
+
+#include <freeradius-devel/util/dict.h>
+
+extern fr_dict_t const *dict_tftp;
+
+extern fr_dict_attr_t const *attr_tftp_block;
+extern fr_dict_attr_t const *attr_tftp_block_size;
+extern fr_dict_attr_t const *attr_tftp_data;
+extern fr_dict_attr_t const *attr_tftp_error_code;
+extern fr_dict_attr_t const *attr_tftp_error_message;
+extern fr_dict_attr_t const *attr_tftp_filename;
+extern fr_dict_attr_t const *attr_tftp_opcode;
+extern fr_dict_attr_t const *attr_tftp_mode;
+
+extern fr_dict_attr_t const *attr_packet_type;
--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file src/protocols/tftp/base.c
+ * @brief TFTP protocol.
+ * @author Jorge Pereira <jpereira@freeradius.org>
+ *
+ * @copyright 2021 The FreeRADIUS server project.
+ * @copyright 2021 Network RADIUS SARL (legal@networkradius.com)
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/util/base.h>
+#include "tftp.h"
+#include "attrs.h"
+
+static uint32_t instance_count = 0;
+
+fr_dict_t const *dict_tftp;
+
+extern fr_dict_autoload_t libfreeradius_tftp[];
+fr_dict_autoload_t libfreeradius_tftp[] = {
+ { .out = &dict_tftp, .proto = "tftp" },
+ { NULL }
+};
+
+fr_dict_attr_t const *attr_tftp_block;
+fr_dict_attr_t const *attr_tftp_block_size;
+fr_dict_attr_t const *attr_tftp_data;
+fr_dict_attr_t const *attr_tftp_error_code;
+fr_dict_attr_t const *attr_tftp_error_message;
+fr_dict_attr_t const *attr_tftp_filename;
+fr_dict_attr_t const *attr_tftp_opcode;
+fr_dict_attr_t const *attr_tftp_mode;
+
+fr_dict_attr_t const *attr_packet_type;
+
+extern fr_dict_attr_autoload_t libfreeradius_tftp_dict_attr[];
+fr_dict_attr_autoload_t libfreeradius_tftp_dict_attr[] = {
+ { .out = &attr_tftp_block, .name = "Block", .type = FR_TYPE_UINT16, .dict = &dict_tftp },
+ { .out = &attr_tftp_block_size, .name = "Block-Size", .type = FR_TYPE_UINT16, .dict = &dict_tftp },
+ { .out = &attr_tftp_data, .name = "Data", .type = FR_TYPE_OCTETS, .dict = &dict_tftp },
+ { .out = &attr_tftp_error_code, .name = "Error-Code", .type = FR_TYPE_UINT16, .dict = &dict_tftp },
+ { .out = &attr_tftp_error_message, .name = "Error-Message", .type = FR_TYPE_STRING, .dict = &dict_tftp },
+ { .out = &attr_tftp_filename, .name = "Filename", .type = FR_TYPE_STRING, .dict = &dict_tftp },
+ { .out = &attr_tftp_opcode, .name = "Opcode", .type = FR_TYPE_UINT16, .dict = &dict_tftp },
+ { .out = &attr_tftp_mode, .name = "Mode", .type = FR_TYPE_UINT8, .dict = &dict_tftp },
+
+ { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_tftp },
+
+ { NULL }
+};
+
+char const *fr_tftp_codes[FR_TFTP_MAX_CODE] = {
+ [FR_PACKET_TYPE_VALUE_READ_REQUEST] = "Read-Request",
+ [FR_PACKET_TYPE_VALUE_WRITE_REQUEST] = "Write-Request",
+ [FR_PACKET_TYPE_VALUE_DATA] = "Data",
+ [FR_PACKET_TYPE_VALUE_ACKNOWLEDGEMENT] = "Acknowledgement",
+ [FR_PACKET_TYPE_VALUE_ERROR] = "Error",
+ [FR_PACKET_TYPE_VALUE_DO_NOT_RESPOND] = "Do-Not-Respond"
+};
+
+char const *fr_tftp_error_codes[FR_TFTP_MAX_ERROR_CODE] = {
+ [FR_ERROR_CODE_VALUE_FILE_NOT_FOUND] = "File not found",
+ [FR_ERROR_CODE_VALUE_ACCESS_VIOLATION] = "Access violation",
+ [FR_ERROR_CODE_VALUE_DISK_FULL] = "Disk Full",
+ [FR_ERROR_CODE_VALUE_ILLEGAL_OPERATION] = "Illegal TFTP operation",
+ [FR_ERROR_CODE_VALUE_UNKNOWN_TRANSFER_ID] = "Unknown transfer ID",
+ [FR_ERROR_CODE_VALUE_FILE_ALREADY_EXISTS] = "File already exists",
+ [FR_ERROR_CODE_VALUE_NO_SUCH_USER] = "No such user"
+};
+
+int fr_tftp_init(void)
+{
+ if (instance_count > 0) {
+ instance_count++;
+ return 0;
+ }
+
+ if (fr_dict_autoload(libfreeradius_tftp) < 0) {
+ fr_strerror_printf_push("Failed loading the 'tftp' dictionary");
+ return -1;
+ }
+
+ if (fr_dict_attr_autoload(libfreeradius_tftp_dict_attr) < 0) {
+ fr_strerror_printf("Failed loading the 'tftp' attributes");
+ fr_dict_autofree(libfreeradius_tftp);
+ return -1;
+ }
+
+ instance_count++;
+
+ return 0;
+}
+
+void fr_tftp_free(void)
+{
+ if (--instance_count > 0) return;
+
+ fr_dict_autofree(libfreeradius_tftp);
+}
--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file src/protocols/tftp/tftp.c
+ * @brief Functions to encode/decode TFTP packets.
+ * @author Jorge Pereira <jpereira@freeradius.org>
+ *
+ * @copyright 2021 The FreeRADIUS server project.
+ * @copyright 2021 Network RADIUS SARL (legal@networkradius.com)
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/server/base.h>
+#include <freeradius-devel/util/dbuff.h>
+#include <freeradius-devel/util/udp.h>
+
+#include <freeradius-devel/io/test_point.h>
+
+#include "tftp.h"
+#include "attrs.h"
+
+/*
+ * https://tools.ietf.org/html/rfc1350
+ *
+ * Order of Headers
+ *
+ * 2 bytes
+ * ----------------------------------------------------------
+ * | Local Medium | Internet | Datagram | TFTP Opcode |
+ * ----------------------------------------------------------
+ *
+ * TFTP Formats
+ *
+ * Type Op # Format without header
+ *
+ * 2 bytes string 1 byte string 1 byte
+ * -----------------------------------------------
+ * RRQ/ | 01/02 | Filename | 0 | Mode | 0 |
+ * WRQ -----------------------------------------------
+ * 2 bytes 2 bytes n bytes
+ * ---------------------------------
+ * DATA | 03 | Block # | Data |
+ * ---------------------------------
+ * 2 bytes 2 bytes
+ * -------------------
+ * ACK | 04 | Block # |
+ * --------------------
+ * 2 bytes 2 bytes string 1 byte
+ * ----------------------------------------
+ * ERROR | 05 | ErrorCode | ErrMsg | 0 |
+ * ----------------------------------------
+ *
+ * Initial Connection Protocol for reading a file
+ *
+ * 1. Host A sends a "RRQ" to host B with source= A's TID,
+ * destination= 69.
+ *
+ * 2. Host B sends a "DATA" (with block number= 1) to host A with
+ * source= B's TID, destination= A's TID.
+ */
+int fr_tftp_decode(TALLOC_CTX *ctx, uint8_t const *data, size_t data_len, fr_dcursor_t *cursor)
+{
+ uint8_t const *q, *p, *end;
+ uint16_t opcode;
+ fr_pair_t *vp = NULL;
+
+ if (data_len == 0) return -1;
+
+ if (data_len < FR_TFTP_HDR_LEN) {
+ fr_strerror_printf("TFTP packet is too small. (%zu < %d)", data_len, FR_TFTP_HDR_LEN);
+ error:
+ return -1;
+ }
+
+ p = data;
+ end = (data + data_len);
+
+ /* Opcode */
+ opcode = fr_net_to_uint16(p);
+ vp = fr_pair_afrom_da(ctx, attr_tftp_opcode);
+ if (!vp) goto error;
+
+ vp->vp_uint16 = opcode;
+ fr_dcursor_append(cursor, vp);
+ p += 2;
+
+ switch (opcode) {
+ case FR_OPCODE_VALUE_READ_REQUEST:
+ case FR_OPCODE_VALUE_WRITE_REQUEST:
+ /*
+ * 2 bytes string 1 byte string 1 byte string 1 byte string 1 byte
+ * +------------------------------------------------------------------------------------+
+ * | Opcode | Filename | 0 | Mode | 0 | blksize | 0 | #blksize | 0 |
+ * +------------------------------------------------------------------------------------+
+ * Figure 5-1: RRQ/WRQ packet
+ */
+
+ /* first of all, here we should have always a '\0' */
+ if (*(end - 1) != '\0') goto error_malformed;
+
+ /* first character should be alpha numeric */
+ if (!isalnum(p[0])) {
+ fr_strerror_printf("Invalid Filename");
+ goto error;
+ }
+
+ /* <filename> */
+ q = memchr(p, '\0', (end - p));
+ if (!(q && q[0] == '\0')) {
+ error_malformed:
+ fr_strerror_printf("Packet contains malformed attribute");
+ return -1;
+ }
+
+ vp = fr_pair_afrom_da(ctx, attr_tftp_filename);
+ if (!vp) goto error;
+
+ fr_pair_value_bstrndup(vp, (char const *)p, (q - p), true);
+ fr_dcursor_append(cursor, vp);
+ p += (q - p) + 1 /* \0 */;
+
+ /* <mode> */
+ q = memchr(p, '\0', (end - p));
+ if (!(q && q[0] == '\0')) goto error_malformed;
+
+ vp = fr_pair_afrom_da(ctx, attr_tftp_mode);
+ if (!vp) goto error;
+
+ /* (netascii || ascii || octet) + \0 */
+ if ((q - p) == 5 && !memcmp(p, "octet", 5)) {
+ p += 5;
+ vp->vp_uint8 = FR_MODE_VALUE_OCTET;
+ } else if ((q - p) == 5 && !memcmp(p, "ascii", 5)) {
+ p += 5;
+ vp->vp_uint8 = FR_MODE_VALUE_ASCII;
+ } else if ((q - p) == 8 && !memcmp(p, "netascii", 8)) {
+ p += 8;
+ vp->vp_uint8 = FR_MODE_VALUE_ASCII;
+ } else {
+ fr_strerror_printf("Invalid Mode");
+ goto error;
+ }
+
+ fr_dcursor_append(cursor, vp);
+ p += 1 /* \0 */;
+
+ if (p >= end) goto done;
+
+ /*
+ * Once here, the next 'blksize' is optional.
+ * At least: | blksize | \0 | #blksize | \0 |
+ */
+ if ((end - p) < 10) goto error_malformed;
+
+ if (!memcmp(p, "blksize\0", 8)) {
+ char *p_end = NULL;
+ long blksize;
+
+ p += 8;
+
+ if (p >= end || (end - p) < 1 || (end - p) > 6) goto error_malformed;
+
+ vp = fr_pair_afrom_da(ctx, attr_tftp_block_size);
+ if (!vp) goto error;
+
+ blksize = strtol((const char *)p, &p_end, 10);
+
+ if (p == (const uint8_t *)p_end || blksize > FR_TFTP_BLOCK_MAX_SIZE) {
+ fr_strerror_printf("Invalid Block-Size %ld value", blksize);
+ goto error;
+ }
+
+ vp->vp_uint16 = (uint16_t)blksize;
+ fr_dcursor_append(cursor, vp);
+ }
+
+ break;
+
+ case FR_OPCODE_VALUE_ACKNOWLEDGEMENT:
+ case FR_OPCODE_VALUE_DATA:
+ /**
+ * 2 bytes 2 bytes
+ * ---------------------
+ * | Opcode | Block # |
+ * ---------------------
+ * Figure 5-3: ACK packet
+ */
+
+ vp = fr_pair_afrom_da(ctx, attr_tftp_block);
+ if (!vp) goto error;
+
+ vp->vp_uint16 = fr_net_to_uint16(p);
+
+ fr_dcursor_append(cursor, vp);
+
+ /*
+ * From that point...
+ *
+ * 2 bytes 2 bytes n bytes
+ * ----------------------------------
+ * | Opcode | Block # | Data |
+ * ----------------------------------
+ * Figure 5-2: DATA packet
+ */
+ if (opcode != FR_OPCODE_VALUE_DATA) goto done;
+
+ if ((p + 2) >= end) goto error_malformed;
+
+ p += 2;
+
+ vp = fr_pair_afrom_da(ctx, attr_tftp_data);
+ if (!vp) goto error;
+
+ fr_pair_value_memdup(vp, p, (end - p), true);
+ fr_dcursor_append(cursor, vp);
+
+ break;
+
+ case FR_OPCODE_VALUE_ERROR:
+ /**
+ * 2 bytes 2 bytes string 1 byte
+ * -----------------------------------------
+ * | Opcode | ErrorCode | ErrMsg | 0 |
+ * -----------------------------------------
+ *
+ * Figure 5-4: ERROR packet
+ */
+
+ if ((p + 2) >= end) goto error_malformed;
+
+ vp = fr_pair_afrom_da(ctx, attr_tftp_error_code);
+ if (!vp) goto error;
+
+ vp->vp_uint16 = fr_net_to_uint16(p);
+
+ fr_dcursor_append(cursor, vp);
+
+ p += 2; /* <ErrorCode> */
+ q = memchr(p, '\0', (end - p));
+ if (!q || q[0] != '\0') goto error_malformed;
+
+ vp = fr_pair_afrom_da(ctx, attr_tftp_error_message);
+ if (!vp) goto error;
+
+ fr_pair_value_bstrndup(vp, (char const *)p, (q - p), true);
+ fr_dcursor_append(cursor, vp);
+
+ break;
+
+ default:
+ fr_strerror_printf("Invalid TFTP opcode %#04x", opcode);
+ goto error;
+ }
+
+done:
+ return data_len;
+}
+
+ssize_t fr_tftp_encode(fr_dbuff_t *dbuff, fr_pair_list_t *vps)
+{
+ fr_dbuff_t work_dbuff = FR_DBUFF_MAX_NO_ADVANCE(dbuff, FR_TFTP_BLOCK_MAX_SIZE);
+ fr_pair_t *vp;
+ uint16_t opcode;
+ char const *buf;
+
+ vp = fr_pair_find_by_da(vps, attr_tftp_opcode, 0);
+ if (!vp) {
+ fr_strerror_printf("Cannot send TFTP packet without %s", attr_tftp_opcode->name);
+ return -1;
+ }
+
+ opcode = vp->vp_uint16;
+ fr_dbuff_in(&work_dbuff, opcode);
+
+ switch (opcode) {
+ case FR_OPCODE_VALUE_READ_REQUEST:
+ case FR_OPCODE_VALUE_WRITE_REQUEST:
+ /*
+ * 2 bytes string 1 byte string 1 byte string 1 byte string 1 byte
+ * +------------------------------------------------------------------------------------+
+ * | Opcode | Filename | 0 | Mode | 0 | blksize | 0 | #blksize | 0 |
+ * +------------------------------------------------------------------------------------+
+ * Figure 5-1: RRQ/WRQ packet
+ */
+
+ /* <Filename> */
+ vp = fr_pair_find_by_da(vps, attr_tftp_filename, 0);
+ if (!vp) {
+ fr_strerror_printf("Invalid TFTP packet without %s", attr_tftp_filename->name);
+ return -1;
+ }
+
+ FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, vp->vp_length);
+ fr_dbuff_in_bytes(&work_dbuff, '\0');
+
+ /* <mode> */
+ vp = fr_pair_find_by_da(vps, attr_tftp_mode, 0);
+ if (!vp) {
+ fr_strerror_printf("Invalid TFTP packet without %s", attr_tftp_mode->name);
+ return -1;
+ }
+
+ switch(vp->vp_uint16) {
+ case FR_MODE_VALUE_ASCII: buf = "ascii"; break;
+ case FR_MODE_VALUE_OCTET: buf = "octet"; break;
+ default:
+ fr_strerror_printf("Invalid %s value", attr_tftp_mode->name);
+ return -1;
+ }
+
+ FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, buf, 5);
+ fr_dbuff_in_bytes(&work_dbuff, '\0');
+
+ /* <blksize> is optional */
+ vp = fr_pair_find_by_da(vps, attr_tftp_block_size, 0);
+ if (vp) {
+ char tmp[5+1]; /* max: 65535 */
+
+ FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, "blksize", 7);
+ fr_dbuff_in_bytes(&work_dbuff, '\0');
+
+ snprintf(tmp, sizeof(tmp), "%d", vp->vp_uint16); /* #blksize */
+ FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, tmp, strlen(tmp));
+ fr_dbuff_in_bytes(&work_dbuff, '\0');
+ }
+
+ break;
+
+ case FR_OPCODE_VALUE_ACKNOWLEDGEMENT:
+ case FR_OPCODE_VALUE_DATA:
+ /**
+ * 2 bytes 2 bytes
+ * ---------------------
+ * | Opcode | Block # |
+ * ---------------------
+ * Figure 5-3: ACK packet
+ */
+
+ /* <Block> */
+ vp = fr_pair_find_by_da(vps, attr_tftp_block, 0);
+ if (!vp) {
+ fr_strerror_printf("Invalid TFTP packet without %s", attr_tftp_block->name);
+ return -1;
+ }
+
+ fr_dbuff_in(&work_dbuff, vp->vp_uint16);
+
+ /*
+ * From that point...
+ *
+ * 2 bytes 2 bytes n bytes
+ * ----------------------------------
+ * | Opcode | Block # | Data |
+ * ----------------------------------
+ * Figure 5-2: DATA packet
+ */
+ if (opcode != FR_OPCODE_VALUE_DATA) goto done;
+
+ /* <Data> */
+ vp = fr_pair_find_by_da(vps, attr_tftp_data, 0);
+ if (!vp) {
+ fr_strerror_printf("Invalid TFTP packet without %s", attr_tftp_data->name);
+ return -1;
+ }
+
+ FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_octets, vp->vp_length);
+
+ break;
+
+ case FR_OPCODE_VALUE_ERROR:
+ {
+ /**
+ * 2 bytes 2 bytes string 1 byte
+ * -----------------------------------------
+ * | Opcode | ErrorCode | ErrMsg | 0 |
+ * -----------------------------------------
+ *
+ * Figure 5-4: ERROR packet
+ */
+ uint16_t error_code;
+ char const *error_msg;
+ size_t error_msg_len;
+
+ /* <ErroCode> */
+ vp = fr_pair_find_by_da(vps, attr_tftp_error_code, 0);
+ if (!vp) {
+ fr_strerror_printf("Invalid TFTP packet without %s", attr_tftp_error_code->name);
+ return -1;
+ }
+
+ error_code = vp->vp_uint16;
+ fr_dbuff_in(&work_dbuff, error_code);
+
+ /* <ErrMsg> */
+ vp = fr_pair_find_by_da(vps, attr_tftp_error_message, 0);
+ if (vp) {
+ error_msg = vp->vp_strvalue;
+ error_msg_len = vp->vp_length;
+ } else {
+ error_msg = fr_tftp_error_codes[error_code] ? fr_tftp_error_codes[error_code] : "Invalid ErrorCode";
+ error_msg_len = strlen(error_msg);
+ }
+
+ FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, error_msg, error_msg_len);
+ fr_dbuff_in_bytes(&work_dbuff, '\0');
+ break;
+ }
+
+ default:
+ fr_strerror_printf("Invalid TFTP opcode %#04x", opcode);
+ return -1;
+ }
+
+done:
+ fr_dbuff_set(dbuff, &work_dbuff);
+
+ return fr_dbuff_used(dbuff);
+}
+
+/**
+ * Used as the decoder ctx.
+ */
+typedef struct {
+ fr_dict_attr_t const *root;
+} fr_tftp_ctx_t;
+
+/*
+ * Test points for protocol decode
+ */
+static ssize_t fr_tftp_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *list, uint8_t const *data, size_t data_len, UNUSED void *proto_ctx)
+{
+ fr_dcursor_t cursor;
+
+ fr_pair_list_init(list);
+ fr_dcursor_init(&cursor, list);
+
+ return fr_tftp_decode(ctx, data, data_len, &cursor);
+}
+
+static int _decode_test_ctx(UNUSED fr_tftp_ctx_t *proto_ctx)
+{
+ fr_tftp_free();
+
+ return 0;
+}
+
+static int decode_test_ctx(void **out, TALLOC_CTX *ctx)
+{
+ fr_tftp_ctx_t *test_ctx;
+
+ if (fr_tftp_init() < 0) return -1;
+
+ test_ctx = talloc_zero(ctx, fr_tftp_ctx_t);
+ if (!test_ctx) return -1;
+
+ test_ctx->root = fr_dict_root(dict_tftp);
+ talloc_set_destructor(test_ctx, _decode_test_ctx);
+
+ *out = test_ctx;
+
+ return 0;
+}
+
+extern fr_test_point_proto_decode_t tftp_tp_decode_proto;
+fr_test_point_proto_decode_t tftp_tp_decode_proto = {
+ .test_ctx = decode_test_ctx,
+ .func = fr_tftp_decode_proto
+};
+
+/*
+ * Test points for protocol encode
+ */
+static ssize_t fr_tftp_encode_proto(UNUSED TALLOC_CTX *ctx, fr_pair_list_t *vps, uint8_t *data, size_t data_len, UNUSED void *proto_ctx)
+{
+ return fr_tftp_encode(&FR_DBUFF_TMP(data, data_len), vps);
+}
+
+static int _encode_test_ctx(UNUSED fr_tftp_ctx_t *proto_ctx)
+{
+ fr_tftp_free();
+
+ return 0;
+}
+
+static int encode_test_ctx(void **out, TALLOC_CTX *ctx)
+{
+ fr_tftp_ctx_t *test_ctx;
+
+ if (fr_tftp_init() < 0) return -1;
+
+ test_ctx = talloc_zero(ctx, fr_tftp_ctx_t);
+ if (!test_ctx) return -1;
+
+ test_ctx->root = fr_dict_root(dict_tftp);
+ talloc_set_destructor(test_ctx, _encode_test_ctx);
+
+ *out = test_ctx;
+
+ return 0;
+}
+
+/*
+ * Test points
+ */
+extern fr_test_point_proto_encode_t tftp_tp_encode_proto;
+fr_test_point_proto_encode_t tftp_tp_encode_proto = {
+ .test_ctx = encode_test_ctx,
+ .func = fr_tftp_encode_proto
+};
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file src/protocols/tftp/tftp.h
+ * @brief Functions to encode/decode TFTP packets.
+ * @author Jorge Pereira <jpereira@freeradius.org>
+ *
+ * @copyright 2021 The FreeRADIUS server project.
+ * @copyright 2021 Network RADIUS SARL (legal@networkradius.com)
+ */
+
+RCSIDH(tftp_h, "$Id$")
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <freeradius-devel/protocol/tftp/freeradius.internal.h>
+#include <freeradius-devel/protocol/tftp/rfc1350.h>
+
+#include <freeradius-devel/util/dbuff.h>
+
+#define FR_TFTP_MAX_CODE (FR_PACKET_TYPE_VALUE_DO_NOT_RESPOND+1)
+#define FR_TFTP_MAX_ERROR_CODE (FR_ERROR_CODE_VALUE_NO_SUCH_USER+1)
+#define FR_TFTP_HDR_LEN (4) /* at least: 2-bytes opcode + 2-bytes */
+
+/*
+ * 2. Overview of the Protocol
+ *
+ * Any transfer begins with a request to read or write a file, which
+ * also serves to request a connection. If the server grants the
+ * request, the connection is opened and the file is sent in fixed
+ * length blocks of 512 bytes. Each data packet contains one block of
+ * data, and must be acknowledged by an acknowledgment packet before the
+ * next packet can be sent. A data packet of less than 512 bytes
+ * signals termination of a transfer. If a packet gets lost in the
+ * network, the intended recipient will timeout and may retransmit his
+ * last packet (which may be data or an acknowledgment), thus causing
+ * the sender of the lost packet to retransmit that lost packet. The
+ * sender has to keep just one packet on hand for retransmission, since
+ * the lock step acknowledgment guarantees that all older packets have
+ * been received. Notice that both machines involved in a transfer are
+ * considered senders and receivers. One sends data and receives
+ * acknowledgments, the other sends acknowledgments and receives data.
+ */
+#define FR_TFTP_DEFAULT_BLOCK_SIZE 1024
+
+/*
+ * As described in https://tools.ietf.org/html/rfc2348
+ *
+ * The number of octets in a block, specified in ASCII. Valid
+ * values range between "8" and "65464" octets, inclusive. The
+ * blocksize refers to the number of data octets; it does not
+ * include the four octets of TFTP header.
+ */
+#define FR_TFTP_BLOCK_MIN_SIZE 8
+#define FR_TFTP_BLOCK_MAX_SIZE 65464
+
+/*
+ * The original protocol has a transfer file size limit of 512 bytes/block x 65535 blocks = 32 MB.
+ * In 1998 this limit was extended to 65535 bytes/block x 65535 blocks = 4 GB
+ * by TFTP Blocksize Option RFC 2348.
+ */
+#define FR_TFTP_MAX_FILESIZE (FR_TFTP_BLOCK_MAX_SIZE * FR_TFTP_BLOCK_MAX_SIZE)
+
+/* tftp.c */
+int fr_tftp_decode(TALLOC_CTX *ctx, uint8_t const *data, size_t data_len, fr_dcursor_t *cursor) CC_HINT(nonnull(2,4));
+ssize_t fr_tftp_encode(fr_dbuff_t *dbuff, fr_pair_list_t *vps) CC_HINT(nonnull(1,2));
+
+/* base.c */
+extern char const *fr_tftp_codes[FR_TFTP_MAX_CODE];
+extern char const *fr_tftp_error_codes[FR_TFTP_MAX_ERROR_CODE];
+
+int fr_tftp_init(void);
+void fr_tftp_free(void);
+
+#ifdef __cplusplus
+}
+#endif
dhcpv4
dhcpv6
tacacs
-vmps
\ No newline at end of file
+vmps
+tftp
\ No newline at end of file
--- /dev/null
+version https://git-lfs.github.com/spec/v1
+oid sha256:4fbd93bb92814f5004e0ef23e18acc63b7a1052bce8503248d380784d21aa089
+size 17920
--- /dev/null
+# Test vectors for TFTP attributes
+#
+# Copyright 2021 Network RADIUS SARL <legal@networkradius.com>
+#
+proto tftp
+proto-dictionary tftp
+
+#
+# Client -> Server (Read-Request) - Without block-size
+#
+decode-proto 00 01 73 63 61 6c 64 5f 6c 61 77 2e 74 78 74 00 61 73 63 69 69 00
+match Opcode = Read-Request, Filename = "scald_law.txt", Mode = ASCII
+
+encode-proto -
+match 00 01 73 63 61 6c 64 5f 6c 61 77 2e 74 78 74 00 61 73 63 69 69 00
+
+#
+# Client -> Server (Read-Request) - With block-size
+#
+decode-proto 00 01 73 63 61 6c 64 5f 6c 61 77 2e 74 78 74 00 61 73 63 69 69 00 62 6c 6b 73 69 7a 65 00 31 32 33 34 35 00
+match Opcode = Read-Request, Filename = "scald_law.txt", Mode = ASCII, Block-Size = 12345
+
+encode-proto -
+match 00 01 73 63 61 6c 64 5f 6c 61 77 2e 74 78 74 00 61 73 63 69 69 00 62 6c 6b 73 69 7a 65 00 31 32 33 34 35 00
+
+#
+# Client -> Server (Write-Request)
+#
+decode-proto 00 02 73 63 61 6c 64 5f 6c 61 77 2e 74 78 74 00 61 73 63 69 69 00
+match Opcode = Write-Request, Filename = "scald_law.txt", Mode = ASCII
+
+encode-proto -
+match 00 02 73 63 61 6c 64 5f 6c 61 77 2e 74 78 74 00 61 73 63 69 69 00
+
+#
+# Client -> Server (Data)
+#
+decode-proto 00 03 12 34 31 32 33 34 35
+match Opcode = Data, Block = 4660, Data = 0x3132333435
+
+encode-proto -
+match 00 03 12 34 31 32 33 34 35
+
+#
+# Client -> Server (Acknowledgement)
+#
+decode-proto 00 04 12 34
+match Opcode = Acknowledgement, Block = 4660
+
+encode-proto -
+match 00 04 12 34
+
+#
+# Server -> Client (Error)
+#
+decode-proto 00 05 00 04 4b 61 6c 6f 73 20 46 61 75 6c 74 00
+match Opcode = Error, Error-Code = Illegal-Operation, Error-Message = "Kalos Fault"
+
+encode-proto -
+match 00 05 00 04 4b 61 6c 6f 73 20 46 61 75 6c 74 00
+
+count
+match 26
--- /dev/null
+# Test vectors for TFTP attributes
+#
+# Copyright 2021 Network RADIUS SARL <legal@networkradius.com>
+#
+proto tftp
+proto-dictionary tftp
+
+#
+# Invalid blksize
+#
+decode-proto 00 01 34 00 6f 63 74 65 74 00 62 6c 6b 73 69 7a 65 00 31 32
+match Packet contains malformed attribute
+
+decode-proto 00 01 34 00 6f 63 74 65 74 00 62 6c 6b 73 69 7a 65 00 31 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32
+match Packet contains malformed attribute
+
+#
+# Invalid filename
+#
+decode-proto 00 01 00 6f 63 74 65 74 00 62 6c 6b 73 69 7a 65 7d 35 35 35 35 35 35 35
+match Packet contains malformed attribute
+
+#
+# Client -> Server (Read-Request) - With invalid block-size. (max is 65464)
+#
+decode-proto 00 01 73 63 61 6c 64 5f 6c 61 77 2e 74 78 74 00 61 73 63 69 69 00 62 6c 6b 73 69 7a 65 00 36 35 34 36 35 00
+match Invalid Block-Size 65465 value
+
+#
+# Client -> Server (Read-Request) - With invalid mode. (max is 65464)
+#
+decode-proto 00 01 73 63 61 6c 64 5f 6c 61 77 2e 74 78 74 00 01 02 03 04 05 00
+match Invalid Mode
+
+#
+# Invalid opcode
+#
+decode-proto 00 66 73 63 61 6c 64 5f 6c 61 77 2e 74 78 74 00 01 02 03 04 05 00
+match Invalid TFTP opcode 0x66
+
+#
+# Invalid packet
+#
+decode-proto 00 11
+match TFTP packet is too small. (2 < 4)
+
+decode-proto 00 00 25 88
+match Invalid TFTP opcode 0000
+
+count
+match 18