/* Test ns_name-related functions. Copyright (C) 2017 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see <http://www.gnu.org/licenses/>. */ /* This test program processes the tst-ns_name.data file. */ #include <ctype.h> #include <resolv.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <support/check.h> #include <support/support.h> #include <support/xstdio.h> /* A byte buffer and its length. */ struct buffer { unsigned char *data; size_t length; }; /* Convert a base64-encoded string to its binary representation. */ static bool base64_to_buffer (const char *base64, struct buffer *result) { /* "-" denotes an empty input. */ if (strcmp (base64, "-") == 0) { result->data = xmalloc (1); result->length = 0; return true; } size_t size = strlen (base64); unsigned char *data = xmalloc (size); int ret = b64_pton (base64, data, size); if (ret < 0 || ret > size) return false; result->data = xrealloc (data, ret); result->length = ret; return true; } /* A test case for ns_name_unpack and ns_name_ntop. */ struct test_case { char *path; size_t lineno; struct buffer input; size_t input_offset; int unpack_result; struct buffer unpack_output; int ntop_result; char *ntop_text; }; /* Deallocate the buffers associated with the test case. */ static void free_test_case (struct test_case *t) { free (t->path); free (t->input.data); free (t->unpack_output.data); free (t->ntop_text); } /* Extract the test case information from a test file line. */ static bool parse_test_case (const char *path, size_t lineno, const char *line, struct test_case *result) { memset (result, 0, sizeof (*result)); result->path = xstrdup (path); result->lineno = lineno; result->ntop_result = -1; char *input = NULL; char *unpack_output = NULL; int ret = sscanf (line, "%ms %zu %d %ms %d %ms", &input, &result->input_offset, &result->unpack_result, &unpack_output, &result->ntop_result, &result->ntop_text); if (ret < 3) { printf ("%s:%zu: error: missing input fields\n", path, lineno); free (input); return false; } if (!base64_to_buffer (input, &result->input)) { printf ("%s:%zu: error: malformed base64 input data\n", path, lineno); free (input); free (unpack_output); free (result->ntop_text); return false; } free (input); if (unpack_output == NULL) result->unpack_output = (struct buffer) { NULL, 0 }; else if (!base64_to_buffer (unpack_output, &result->unpack_output)) { printf ("%s:%zu: error: malformed base64 unpack data\n", path, lineno); free (result->input.data); free (unpack_output); free (result->ntop_text); return false; } free (unpack_output); /* At this point, all allocated buffers have been transferred to *result. */ if (result->input_offset > result->input.length) { printf ("%s:%zu: error: input offset %zu exceeds buffer size %zu\n", path, lineno, result->input_offset, result->input.length); free_test_case (result); return false; } if (result->unpack_result < -1) { printf ("%s:%zu: error: invalid unpack result %d\n", path, lineno, result->unpack_result); free_test_case (result); return false; } if (result->ntop_result < -1) { printf ("%s:%zu: error: invalid ntop result %d\n", path, lineno, result->ntop_result); free_test_case (result); return false; } bool fields_consistent; switch (ret) { case 3: fields_consistent = result->unpack_result == -1; break; case 5: fields_consistent = result->unpack_result != -1 && result->ntop_result == -1; break; case 6: fields_consistent = result->unpack_result != -1 && result->ntop_result != -1; break; default: fields_consistent = false; } if (!fields_consistent) { printf ("%s:%zu: error: wrong number of fields: %d\n", path, lineno, ret); free_test_case (result); return false; } return true; } /* Format the buffer as a hexadecimal string and write it to standard output. */ static void print_hex (const char *label, struct buffer buffer) { printf (" %s ", label); unsigned char *p = buffer.data; unsigned char *end = p + buffer.length; while (p < end) { printf ("%02X", *p & 0xFF); ++p; } putchar ('\n'); } /* Run the test case specified in *T. */ static void run_test_case (struct test_case *t) { /* Test ns_name_unpack. */ unsigned char *unpacked = xmalloc (NS_MAXCDNAME); int consumed = ns_name_unpack (t->input.data, t->input.data + t->input.length, t->input.data + t->input_offset, unpacked, NS_MAXCDNAME); if (consumed != t->unpack_result) { support_record_failure (); printf ("%s:%zu: error: wrong result from ns_name_unpack\n" " expected: %d\n" " actual: %d\n", t->path, t->lineno, t->unpack_result, consumed); return; } if (consumed != -1) { if (memcmp (unpacked, t->unpack_output.data, t->unpack_output.length) != 0) { support_record_failure (); printf ("%s:%zu: error: wrong data from ns_name_unpack\n", t->path, t->lineno); print_hex ("expected:", t->unpack_output); print_hex ("actual: ", (struct buffer) { unpacked, t->unpack_output.length }); return; } /* Test ns_name_ntop. */ char *text = xmalloc (NS_MAXDNAME); int ret = ns_name_ntop (unpacked, text, NS_MAXDNAME); if (ret != t->ntop_result) { support_record_failure (); printf ("%s:%zu: error: wrong result from ns_name_top\n" " expected: %d\n" " actual: %d\n", t->path, t->lineno, t->ntop_result, ret); return; } if (ret != -1) { if (strcmp (text, t->ntop_text) != 0) { support_record_failure (); printf ("%s:%zu: error: wrong data from ns_name_ntop\n", t->path, t->lineno); printf (" expected: \"%s\"\n", t->ntop_text); printf (" actual: \"%s\"\n", text); return; } /* Test ns_name_pton. Unpacking does not check the NS_MAXCDNAME limit, but packing does, so we need to adjust the expected result. */ int expected; if (t->unpack_output.length > NS_MAXCDNAME) expected = -1; else if (strcmp (text, ".") == 0) /* The root domain is fully qualified. */ expected = 1; else /* The domain name is never fully qualified. */ expected = 0; unsigned char *repacked = xmalloc (NS_MAXCDNAME); ret = ns_name_pton (text, repacked, NS_MAXCDNAME); if (ret != expected) { support_record_failure (); printf ("%s:%zu: error: wrong result from ns_name_pton\n" " expected: %d\n" " actual: %d\n", t->path, t->lineno, expected, ret); return; } if (ret >= 0 && memcmp (repacked, unpacked, t->unpack_output.length) != 0) { support_record_failure (); printf ("%s:%zu: error: wrong data from ns_name_pton\n", t->path, t->lineno); print_hex ("expected:", t->unpack_output); print_hex ("actual: ", (struct buffer) { repacked, t->unpack_output.length }); return; } /* Test ns_name_compress, no compression case. */ if (t->unpack_output.length > NS_MAXCDNAME) expected = -1; else expected = t->unpack_output.length; memset (repacked, '$', NS_MAXCDNAME); { enum { ptr_count = 5 }; const unsigned char *dnptrs[ptr_count] = { repacked, }; ret = ns_name_compress (text, repacked, NS_MAXCDNAME, dnptrs, dnptrs + ptr_count); if (ret != expected) { support_record_failure (); printf ("%s:%zu: error: wrong result from ns_name_compress\n" " expected: %d\n" " actual: %d\n", t->path, t->lineno, expected, ret); return; } if (ret < 0) { TEST_VERIFY (dnptrs[0] == repacked); TEST_VERIFY (dnptrs[1] == NULL); } else { if (memcmp (repacked, unpacked, t->unpack_output.length) != 0) { support_record_failure (); printf ("%s:%zu: error: wrong data from ns_name_compress\n", t->path, t->lineno); print_hex ("expected:", t->unpack_output); print_hex ("actual: ", (struct buffer) { repacked, ret }); return; } TEST_VERIFY (dnptrs[0] == repacked); if (unpacked[0] == '\0') /* The root domain is not a compression target. */ TEST_VERIFY (dnptrs[1] == NULL); else { TEST_VERIFY (dnptrs[1] == repacked); TEST_VERIFY (dnptrs[2] == NULL); } } } /* Test ns_name_compress, full compression case. Skip this test for invalid names and the root domain. */ if (expected >= 0 && unpacked[0] != '\0') { /* The destination buffer needs additional room for the offset, the initial name, and the compression reference. */ enum { name_offset = 259 }; size_t target_offset = name_offset + t->unpack_output.length; size_t repacked_size = target_offset + 2; repacked = xrealloc (repacked, repacked_size); memset (repacked, '@', repacked_size); memcpy (repacked + name_offset, t->unpack_output.data, t->unpack_output.length); enum { ptr_count = 5 }; const unsigned char *dnptrs[ptr_count] = { repacked, repacked + name_offset, }; ret = ns_name_compress (text, repacked + target_offset, NS_MAXCDNAME, dnptrs, dnptrs + ptr_count); if (ret != 2) { support_record_failure (); printf ("%s:%zu: error: wrong result from ns_name_compress" " (2)\n" " expected: 2\n" " actual: %d\n", t->path, t->lineno, ret); return; } if (memcmp (repacked + target_offset, "\xc1\x03", 2) != 0) { support_record_failure (); printf ("%s:%zu: error: wrong data from ns_name_compress" " (2)\n" " expected: C103\n", t->path, t->lineno); print_hex ("actual: ", (struct buffer) { repacked + target_offset, ret }); return; } TEST_VERIFY (dnptrs[0] == repacked); TEST_VERIFY (dnptrs[1] == repacked + name_offset); TEST_VERIFY (dnptrs[2] == NULL); } free (repacked); } free (text); } free (unpacked); } /* Open the file at PATH, parse the test cases contained in it, and run them. */ static void run_test_file (const char *path) { FILE *fp = xfopen (path, "re"); char *line = NULL; size_t line_allocated = 0; size_t lineno = 0; while (true) { ssize_t ret = getline (&line, &line_allocated, fp); if (ret < 0) { if (ferror (fp)) { printf ("%s: error reading file: %m\n", path); exit (1); } TEST_VERIFY (feof (fp)); break; } ++lineno; char *p = line; while (isspace (*p)) ++p; if (*p == '\0' || *p == '#') continue; struct test_case test_case; if (!parse_test_case (path, lineno, line, &test_case)) { support_record_failure (); continue; } run_test_case (&test_case); free_test_case (&test_case); } free (line); xfclose (fp); } static int do_test (void) { run_test_file ("tst-ns_name.data"); return 0; } #include <support/test-driver.c>