]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
tftp: Add decoder and encoder (#4051)
authorJorge Pereira <jpereira@users.noreply.github.com>
Tue, 1 Jun 2021 14:06:40 +0000 (11:06 -0300)
committerGitHub <noreply@github.com>
Tue, 1 Jun 2021 14:06:40 +0000 (09:06 -0500)
* :gear: tftp: Add TFTP dictionary

* :gear: tftp: Add the main attributes

* :gear: tftp: Add decode function

* :gear: tftp: Add decode test entrypoint

* :gear: tftp: Add encode function

* :gear: tftp: Add encode test entrypoint

* :gear: fuzzer: Add fuzzer for tftp protocol

* :gear: fuzzer: Add TFTP corpus files to git LFS

* :gear: tftp: Add tests for decode and encode

16 files changed:
Makefile
share/dictionary/README.md
share/dictionary/tftp/dictionary [new file with mode: 0644]
share/dictionary/tftp/dictionary.freeradius.internal [new file with mode: 0644]
share/dictionary/tftp/dictionary.rfc1350 [new file with mode: 0644]
src/bin/.gitignore
src/bin/all.mk
src/protocols/tftp/all.mk [new file with mode: 0644]
src/protocols/tftp/attrs.h [new file with mode: 0644]
src/protocols/tftp/base.c [new file with mode: 0644]
src/protocols/tftp/tftp.c [new file with mode: 0644]
src/protocols/tftp/tftp.h [new file with mode: 0644]
src/tests/fuzzer-corpus/.gitignore
src/tests/fuzzer-corpus/tftp.tar [new file with mode: 0644]
src/tests/unit/protocols/tftp/base.txt [new file with mode: 0644]
src/tests/unit/protocols/tftp/error.txt [new file with mode: 0644]

index dda57a1faa99c775007a92af69b964eb29b2d667..a3c9760eae0302ac1b66723389e808fe9b60030b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -77,7 +77,8 @@ PROTOCOLS    := \
        radius \
        snmp \
        tacacs \
-       vmps
+       vmps \
+       tftp
 
 #
 #  If we're building packages or crossbuilding, just do that.
index 5e5de9f6d3e56e14f51906acb45abe7989ce623f..b8dcdc987287f44cc4e1aec1ef413d7f24f4f4e9 100644 (file)
@@ -12,6 +12,7 @@ PROTOCOL        TACACS          5
 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
diff --git a/share/dictionary/tftp/dictionary b/share/dictionary/tftp/dictionary
new file mode 100644 (file)
index 0000000..ac8d882
--- /dev/null
@@ -0,0 +1,17 @@
+# -*- 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
diff --git a/share/dictionary/tftp/dictionary.freeradius.internal b/share/dictionary/tftp/dictionary.freeradius.internal
new file mode 100644 (file)
index 0000000..b9bafda
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- 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
diff --git a/share/dictionary/tftp/dictionary.rfc1350 b/share/dictionary/tftp/dictionary.rfc1350
new file mode 100644 (file)
index 0000000..17b1ba9
--- /dev/null
@@ -0,0 +1,45 @@
+# -*- 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
index 7b64d7715807539244748adb4a383f83eecaacbe..b46e4e5ea4955c44ca7360baba7fac0c3f5def3d 100644 (file)
@@ -6,3 +6,4 @@ fuzzer_dhcpv4.mk
 fuzzer_dhcpv6.mk
 fuzzer_tacacs.mk
 fuzzer_vmps.mk
+fuzzer_tftp.mk
index ca90bc852438e005c36e561bde9bd900c9a5aa5f..1c03926ffdd83543808db365ebcefaf17f2aeb09 100644 (file)
@@ -23,7 +23,7 @@ SUBMAKEFILES := \
 #  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.
diff --git a/src/protocols/tftp/all.mk b/src/protocols/tftp/all.mk
new file mode 100644 (file)
index 0000000..831aa57
--- /dev/null
@@ -0,0 +1,9 @@
+#
+# 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
diff --git a/src/protocols/tftp/attrs.h b/src/protocols/tftp/attrs.h
new file mode 100644 (file)
index 0000000..eda830f
--- /dev/null
@@ -0,0 +1,42 @@
+#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;
diff --git a/src/protocols/tftp/base.c b/src/protocols/tftp/base.c
new file mode 100644 (file)
index 0000000..f2648f0
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ *   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);
+}
diff --git a/src/protocols/tftp/tftp.c b/src/protocols/tftp/tftp.c
new file mode 100644 (file)
index 0000000..a0b6753
--- /dev/null
@@ -0,0 +1,524 @@
+/*
+ *   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
+};
diff --git a/src/protocols/tftp/tftp.h b/src/protocols/tftp/tftp.h
new file mode 100644 (file)
index 0000000..b9df00a
--- /dev/null
@@ -0,0 +1,95 @@
+#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
index 8c0dd6b5265aab9f15ba3eea158ea1577b8bbe8a..f5f7ea41d9be76f1691a9b0a48593ff39490d237 100644 (file)
@@ -2,4 +2,5 @@ radius
 dhcpv4
 dhcpv6
 tacacs
-vmps
\ No newline at end of file
+vmps
+tftp
\ No newline at end of file
diff --git a/src/tests/fuzzer-corpus/tftp.tar b/src/tests/fuzzer-corpus/tftp.tar
new file mode 100644 (file)
index 0000000..3d8b1f6
--- /dev/null
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4fbd93bb92814f5004e0ef23e18acc63b7a1052bce8503248d380784d21aa089
+size 17920
diff --git a/src/tests/unit/protocols/tftp/base.txt b/src/tests/unit/protocols/tftp/base.txt
new file mode 100644 (file)
index 0000000..de6f810
--- /dev/null
@@ -0,0 +1,63 @@
+#  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
diff --git a/src/tests/unit/protocols/tftp/error.txt b/src/tests/unit/protocols/tftp/error.txt
new file mode 100644 (file)
index 0000000..383ac88
--- /dev/null
@@ -0,0 +1,51 @@
+#  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