openwrt/package/utils/fritz-tools/src/fritz_tffs_read.c
Daniel Kestrel 12564c5b86 fritz-tools: fix returning wrong values due to strncmp usage
When having two keys that start with the same characters and the second
key just has one character more nand_tffs_read and tffs_read return the
wrong value for the longer key. This is due to the usage of strncmp in
combination with the length of the shorter key which is usually first in
the list before the longer key and when strncmp matches, the search is
stopped. The problem only occurs when the length of the two keys is
different, not if just the last character is different. The fix is to
use strcmp and as such it will only return the value if the key (name)
and the key to look for (namefilter) have the same value and length. A
sample case returning wrong values is when keys macwlan and macwlan2 are
defined and querying macwlan2 returns the value for macwlan.

Signed-off-by: Daniel Kestrel <kestrel1974@t-online.de>
2021-08-08 19:50:46 +02:00

380 lines
8.5 KiB
C

/*
* A tool for reading the TFFS partitions (a name-value storage usually
* found in AVM Fritz!Box based devices).
*
* Copyright (c) 2015-2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
*
* Based on the TFFS 2.0 kernel driver from AVM:
* Copyright (c) 2004-2007 AVM GmbH <fritzbox_info@avm.de>
* and the OpenWrt TFFS kernel driver:
* Copyright (c) 2013 John Crispin <blogic@openwrt.org>
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <libgen.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#define TFFS_ID_END 0xffff
#define TFFS_ID_TABLE_NAME 0x01ff
static char *progname;
static char *input_file;
static unsigned long tffs_size;
static char *name_filter = NULL;
static bool show_all = false;
static bool print_all_key_names = false;
static bool swap_bytes = false;
struct tffs_entry_header {
uint16_t id;
uint16_t len;
};
struct tffs_entry {
const struct tffs_entry_header *header;
char *name;
uint8_t *val;
};
struct tffs_name_table_entry {
const uint32_t *id;
const char *val;
};
struct tffs_key_name_table {
uint32_t size;
struct tffs_name_table_entry *entries;
};
static inline uint16_t get_header_len(const struct tffs_entry_header *header)
{
if (swap_bytes)
return ntohs(header->len);
return header->len;
}
static inline uint16_t get_header_id(const struct tffs_entry_header *header)
{
if (swap_bytes)
return ntohs(header->id);
return header->id;
}
static inline uint16_t to_entry_header_id(uint32_t name_id)
{
if (swap_bytes)
return ntohl(name_id) & 0xffff;
return name_id & 0xffff;
}
static inline uint32_t get_walk_size(uint32_t entry_len)
{
return (entry_len + 3) & ~0x03;
}
static void print_entry_value(const struct tffs_entry *entry)
{
int i;
/* These are NOT NULL terminated. */
for (i = 0; i < get_header_len(entry->header); i++)
fprintf(stdout, "%c", entry->val[i]);
}
static void parse_entry(uint8_t *buffer, uint32_t pos,
struct tffs_entry *entry)
{
entry->header = (struct tffs_entry_header *) &buffer[pos];
entry->val = &buffer[pos + sizeof(struct tffs_entry_header)];
}
static int find_entry(uint8_t *buffer, uint16_t id, struct tffs_entry *entry)
{
uint32_t pos = 0;
do {
parse_entry(buffer, pos, entry);
if (get_header_id(entry->header) == id)
return 1;
pos += sizeof(struct tffs_entry_header);
pos += get_walk_size(get_header_len(entry->header));
} while (pos < tffs_size && entry->header->id != TFFS_ID_END);
return 0;
}
static void parse_key_names(struct tffs_entry *names_entry,
struct tffs_key_name_table *key_names)
{
uint32_t pos = 0, i = 0;
struct tffs_name_table_entry *name_item;
key_names->entries = calloc(sizeof(*name_item), 1);
do {
name_item = &key_names->entries[i];
name_item->id = (uint32_t *) &names_entry->val[pos];
pos += sizeof(*name_item->id);
name_item->val = (const char *) &names_entry->val[pos];
/*
* There is no "length" field because the string values are
* simply NULL-terminated -> strlen() gives us the size.
*/
pos += get_walk_size(strlen(name_item->val) + 1);
++i;
key_names->entries = realloc(key_names->entries,
sizeof(*name_item) * (i + 1));
} while (pos < get_header_len(names_entry->header));
key_names->size = i;
}
static void show_all_key_names(struct tffs_key_name_table *key_names)
{
int i;
for (i = 0; i < key_names->size; i++)
printf("%s\n", key_names->entries[i].val);
}
static int show_all_key_value_pairs(uint8_t *buffer,
struct tffs_key_name_table *key_names)
{
int i, has_value = 0;
uint16_t id;
struct tffs_entry tmp;
for (i = 0; i < key_names->size; i++) {
id = to_entry_header_id(*key_names->entries[i].id);
if (find_entry(buffer, id, &tmp)) {
printf("%s=", key_names->entries[i].val);
print_entry_value(&tmp);
printf("\n");
has_value++;
}
}
if (!has_value) {
fprintf(stderr, "ERROR: no values found!\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
static int show_matching_key_value(uint8_t *buffer,
struct tffs_key_name_table *key_names)
{
int i;
uint16_t id;
struct tffs_entry tmp;
const char *name;
for (i = 0; i < key_names->size; i++) {
name = key_names->entries[i].val;
if (strcmp(name, name_filter) == 0) {
id = to_entry_header_id(*key_names->entries[i].id);
if (find_entry(buffer, id, &tmp)) {
print_entry_value(&tmp);
printf("\n");
return EXIT_SUCCESS;
} else {
fprintf(stderr,
"ERROR: no value found for name %s!\n",
name);
return EXIT_FAILURE;
}
}
}
fprintf(stderr, "ERROR: Unknown key name %s!\n", name_filter);
return EXIT_FAILURE;
}
static void usage(int status)
{
FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
fprintf(stream,
"\n"
"Options:\n"
" -a list all key value pairs found in the TFFS file/device\n"
" -b swap bytes while parsing the TFFS file/device\n"
" -h show this screen\n"
" -i <file> inspect the given TFFS file/device <file>\n"
" -l list all supported keys\n"
" -n <key name> display the value of the given key\n"
" -s <size> the (max) size of the TFFS file/device <size>\n"
);
exit(status);
}
static int file_exist(char *filename)
{
struct stat buffer;
return stat(filename, &buffer) == 0;
}
static void parse_options(int argc, char *argv[])
{
while (1)
{
int c;
c = getopt(argc, argv, "abhi:ln:s:");
if (c == -1)
break;
switch (c) {
case 'a':
show_all = true;
name_filter = NULL;
print_all_key_names = false;
break;
case 'b':
swap_bytes = 1;
break;
case 'h':
usage(EXIT_SUCCESS);
break;
case 'i':
input_file = optarg;
break;
case 'l':
print_all_key_names = true;
show_all = false;
name_filter = NULL;
break;
case 'n':
name_filter = optarg;
show_all = false;
print_all_key_names = false;
break;
case 's':
tffs_size = strtoul(optarg, NULL, 0);
break;
default:
usage(EXIT_FAILURE);
break;
}
}
if (!input_file) {
fprintf(stderr, "ERROR: No input file (-i <file>) given!\n");
exit(EXIT_FAILURE);
}
if (!file_exist(input_file)) {
fprintf(stderr, "ERROR: %s does not exist\n", input_file);
exit(EXIT_FAILURE);
}
if (!show_all && !name_filter && !print_all_key_names) {
fprintf(stderr,
"ERROR: either -l, -a or -n <key name> is required!\n");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
int ret = EXIT_FAILURE;
uint8_t *buffer;
FILE *fp;
struct tffs_entry name_table;
struct tffs_key_name_table key_names;
progname = basename(argv[0]);
parse_options(argc, argv);
fp = fopen(input_file, "r");
if (!fp) {
fprintf(stderr, "ERROR: Failed to open tffs input file %s\n",
input_file);
goto out;
}
if (tffs_size == 0) {
fseek(fp, 0L, SEEK_END);
tffs_size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
}
buffer = malloc(tffs_size);
if (fread(buffer, 1, tffs_size, fp) != tffs_size) {
fprintf(stderr, "ERROR: Failed read tffs file %s\n",
input_file);
goto out_free;
}
if (!find_entry(buffer, TFFS_ID_TABLE_NAME, &name_table)) {
fprintf(stderr,"ERROR: No name table found in tffs file %s\n",
input_file);
fprintf(stderr," Is byte-swapping (-b) required?\n");
goto out_free;
}
parse_key_names(&name_table, &key_names);
if (key_names.size < 1) {
fprintf(stderr, "ERROR: No name table found in tffs file %s\n",
input_file);
goto out_free_names;
}
if (print_all_key_names) {
show_all_key_names(&key_names);
ret = EXIT_SUCCESS;
} else if (show_all) {
ret = show_all_key_value_pairs(buffer, &key_names);
} else {
ret = show_matching_key_value(buffer, &key_names);
}
out_free_names:
free(key_names.entries);
out_free:
fclose(fp);
free(buffer);
out:
return ret;
}