#define UNPARSEABLE_PRIVATE
#include "core/or/or.h"
#include "app/config/config.h"
#include "feature/dirparse/unparseable.h"
#include "lib/sandbox/sandbox.h"
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
STATIC smartlist_t *descs_dumped = NULL;
STATIC uint64_t len_descs_dumped = 0;
static int have_dump_desc_dir = 0;
static int problem_with_dump_desc_dir = 0;
#define DESC_DUMP_DATADIR_SUBDIR "unparseable-descs"
#define DESC_DUMP_BASE_FILENAME "unparseable-desc"
void
dump_desc_init(void)
{
char *dump_desc_dir;
dump_desc_dir = get_datadir_fname(DESC_DUMP_DATADIR_SUBDIR);
if (check_private_dir(dump_desc_dir, CPD_CHECK, get_options()->User) < 0) {
log_notice(LD_DIR,
"Doesn't look like we'll be able to create descriptor dump "
"directory %s; dumps will be disabled.",
dump_desc_dir);
problem_with_dump_desc_dir = 1;
tor_free(dump_desc_dir);
return;
}
switch (file_status(dump_desc_dir)) {
case FN_DIR:
have_dump_desc_dir = 1;
break;
case FN_NOENT:
have_dump_desc_dir = 0;
break;
case FN_ERROR:
log_notice(LD_DIR,
"Couldn't check whether descriptor dump directory %s already"
" exists: %s",
dump_desc_dir, strerror(errno));
problem_with_dump_desc_dir = 1;
break;
case FN_FILE:
case FN_EMPTY:
default:
log_notice(LD_DIR,
"Descriptor dump directory %s already exists and isn't a "
"directory",
dump_desc_dir);
problem_with_dump_desc_dir = 1;
}
if (have_dump_desc_dir && !problem_with_dump_desc_dir) {
dump_desc_populate_fifo_from_directory(dump_desc_dir);
}
tor_free(dump_desc_dir);
}
static void
dump_desc_create_dir(void)
{
char *dump_desc_dir;
if (problem_with_dump_desc_dir) return;
if (!have_dump_desc_dir) {
dump_desc_dir = get_datadir_fname(DESC_DUMP_DATADIR_SUBDIR);
if (check_private_dir(dump_desc_dir, CPD_CREATE,
get_options()->User) < 0) {
log_notice(LD_DIR,
"Failed to create descriptor dump directory %s",
dump_desc_dir);
problem_with_dump_desc_dir = 1;
}
have_dump_desc_dir = 1;
tor_free(dump_desc_dir);
}
}
static void
dump_desc_fifo_add_and_clean(char *filename, const uint8_t *digest_sha256,
size_t len)
{
dumped_desc_t *ent = NULL, *tmp;
uint64_t max_len;
tor_assert(filename != NULL);
tor_assert(digest_sha256 != NULL);
if (descs_dumped == NULL) {
tor_assert(len_descs_dumped == 0);
descs_dumped = smartlist_new();
}
ent = tor_malloc_zero(sizeof(*ent));
ent->filename = filename;
ent->len = len;
ent->when = time(NULL);
memcpy(ent->digest_sha256, digest_sha256, DIGEST256_LEN);
max_len = get_options()->MaxUnparseableDescSizeToLog;
while (len > max_len - len_descs_dumped &&
smartlist_len(descs_dumped) > 0) {
tmp = (dumped_desc_t *)(smartlist_get(descs_dumped, 0));
if (strcmp(tmp->filename, filename) != 0) {
tor_unlink(tmp->filename);
tor_assert(len_descs_dumped >= tmp->len);
len_descs_dumped -= tmp->len;
log_info(LD_DIR,
"Deleting old unparseable descriptor dump %s due to "
"space limits",
tmp->filename);
} else {
tor_assert(len_descs_dumped >= tmp->len);
len_descs_dumped -= tmp->len;
log_info(LD_DIR,
"Replacing old descriptor dump %s with new identical one",
tmp->filename);
}
smartlist_del_keeporder(descs_dumped, 0);
tor_free(tmp->filename);
tor_free(tmp);
}
smartlist_add(descs_dumped, ent);
len_descs_dumped += len;
}
static int
dump_desc_fifo_bump_hash(const uint8_t *digest_sha256)
{
dumped_desc_t *match = NULL;
tor_assert(digest_sha256);
if (descs_dumped) {
SMARTLIST_FOREACH_BEGIN(descs_dumped, dumped_desc_t *, ent) {
if (ent &&
tor_memeq(ent->digest_sha256, digest_sha256, DIGEST256_LEN)) {
match = ent;
SMARTLIST_DEL_CURRENT_KEEPORDER(descs_dumped, ent);
break;
}
} SMARTLIST_FOREACH_END(ent);
if (match) {
match->when = time(NULL);
smartlist_add(descs_dumped, match);
return 1;
}
}
return 0;
}
void
dump_desc_fifo_cleanup(void)
{
if (descs_dumped) {
SMARTLIST_FOREACH_BEGIN(descs_dumped, dumped_desc_t *, ent) {
tor_assert(ent);
tor_free(ent->filename);
tor_free(ent);
} SMARTLIST_FOREACH_END(ent);
smartlist_free(descs_dumped);
descs_dumped = NULL;
len_descs_dumped = 0;
}
}
MOCK_IMPL(STATIC dumped_desc_t *,
dump_desc_populate_one_file, (const char *dirname, const char *f))
{
dumped_desc_t *ent = NULL;
char *path = NULL, *desc = NULL;
const char *digest_str;
char digest[DIGEST256_LEN], content_digest[DIGEST256_LEN];
const char *f_pfx = DESC_DUMP_BASE_FILENAME ".";
struct stat st;
tor_assert(dirname != NULL);
tor_assert(f != NULL);
tor_asprintf(&path, "%s" PATH_SEPARATOR "%s", dirname, f);
if (!strcmpstart(f, f_pfx)) {
digest_str = f + strlen(f_pfx);
if (base16_decode(digest, DIGEST256_LEN,
digest_str, strlen(digest_str)) != DIGEST256_LEN) {
digest_str = NULL;
}
} else {
digest_str = NULL;
}
if (!digest_str) {
log_notice(LD_DIR,
"Removing unrecognized filename %s from unparseable "
"descriptors directory", f);
tor_unlink(path);
goto done;
}
desc = read_file_to_str(path, RFTS_IGNORE_MISSING|RFTS_BIN, &st);
if (!desc) {
log_notice(LD_DIR,
"Failed to read %s from unparseable descriptors directory; "
"attempting to remove it.", f);
tor_unlink(path);
goto done;
}
#if SIZE_MAX > UINT64_MAX
if (BUG((uint64_t)st.st_size > (uint64_t)SIZE_MAX)) {
goto done;
}
#endif
if (BUG(st.st_size < 0)) {
goto done;
}
if (crypto_digest256((char *)content_digest, desc, (size_t) st.st_size,
DIGEST_SHA256) < 0) {
log_info(LD_DIR,
"Unable to hash content of %s from unparseable descriptors "
"directory", f);
tor_unlink(path);
goto done;
}
if (tor_memneq(digest, content_digest, DIGEST256_LEN)) {
log_info(LD_DIR,
"Hash of %s from unparseable descriptors directory didn't "
"match its filename; removing it", f);
tor_unlink(path);
goto done;
}
ent = tor_malloc_zero(sizeof(dumped_desc_t));
ent->filename = path;
memcpy(ent->digest_sha256, digest, DIGEST256_LEN);
ent->len = (size_t) st.st_size;
ent->when = st.st_mtime;
path = NULL;
done:
tor_free(desc);
tor_free(path);
return ent;
}
static int
dump_desc_compare_fifo_entries(const void **a_v, const void **b_v)
{
const dumped_desc_t **a = (const dumped_desc_t **)a_v;
const dumped_desc_t **b = (const dumped_desc_t **)b_v;
if ((a != NULL) && (*a != NULL)) {
if ((b != NULL) && (*b != NULL)) {
if ((*a)->when < (*b)->when) {
return -1;
} else if ((*a)->when == (*b)->when) {
return 0;
} else {
return 1;
}
} else {
return 1;
}
} else {
return -1;
}
}
STATIC void
dump_desc_populate_fifo_from_directory(const char *dirname)
{
smartlist_t *files = NULL;
dumped_desc_t *ent = NULL;
tor_assert(dirname != NULL);
files = tor_listdir(dirname);
if (!files) {
log_notice(LD_DIR,
"Unable to get contents of unparseable descriptor dump "
"directory %s",
dirname);
return;
}
SMARTLIST_FOREACH_BEGIN(files, char *, f) {
ent = dump_desc_populate_one_file(dirname, f);
if (ent) {
if (!descs_dumped) {
descs_dumped = smartlist_new();
len_descs_dumped = 0;
}
smartlist_add(descs_dumped, ent);
len_descs_dumped += ent->len;
}
} SMARTLIST_FOREACH_END(f);
if (descs_dumped != NULL) {
smartlist_sort(descs_dumped, dump_desc_compare_fifo_entries);
log_info(LD_DIR,
"Reloaded unparseable descriptor dump FIFO with %d dump(s) "
"totaling %"PRIu64 " bytes",
smartlist_len(descs_dumped), (len_descs_dumped));
}
SMARTLIST_FOREACH(files, char *, f, tor_free(f));
smartlist_free(files);
}
MOCK_IMPL(void,
dump_desc,(const char *desc, const char *type))
{
tor_assert(desc);
tor_assert(type);
size_t len;
uint8_t digest_sha256[DIGEST256_LEN];
char digest_sha256_hex[HEX_DIGEST256_LEN+1];
char *debugfile, *debugfile_base;
len = strlen(desc);
if (crypto_digest256((char *)digest_sha256, desc, len,
DIGEST_SHA256) < 0) {
log_info(LD_DIR,
"Unable to parse descriptor of type %s, and unable to even hash"
" it!", type);
goto err;
}
base16_encode(digest_sha256_hex, sizeof(digest_sha256_hex),
(const char *)digest_sha256, sizeof(digest_sha256));
tor_asprintf(&debugfile_base,
DESC_DUMP_BASE_FILENAME ".%s", digest_sha256_hex);
debugfile = get_datadir_fname2(DESC_DUMP_DATADIR_SUBDIR, debugfile_base);
if (!(sandbox_is_active() || get_options()->Sandbox)) {
if (len <= get_options()->MaxUnparseableDescSizeToLog) {
if (!dump_desc_fifo_bump_hash(digest_sha256)) {
dump_desc_create_dir();
if (have_dump_desc_dir && !problem_with_dump_desc_dir) {
write_str_to_file(debugfile, desc, 1);
log_info(LD_DIR,
"Unable to parse descriptor of type %s with hash %s and "
"length %lu. See file %s in data directory for details.",
type, digest_sha256_hex, (unsigned long)len,
debugfile_base);
dump_desc_fifo_add_and_clean(debugfile, digest_sha256, len);
debugfile = NULL;
} else {
log_info(LD_DIR,
"Unable to parse descriptor of type %s with hash %s and "
"length %lu. Descriptor not dumped because we had a "
"problem creating the " DESC_DUMP_DATADIR_SUBDIR
" subdirectory",
type, digest_sha256_hex, (unsigned long)len);
}
} else {
log_info(LD_DIR,
"Unable to parse descriptor of type %s with hash %s and "
"length %lu. Descriptor not dumped because one with that "
"hash has already been dumped.",
type, digest_sha256_hex, (unsigned long)len);
}
} else {
log_info(LD_DIR,
"Unable to parse descriptor of type %s with hash %s and "
"length %lu. Descriptor not dumped because it exceeds maximum"
" log size all by itself.",
type, digest_sha256_hex, (unsigned long)len);
}
} else {
log_info(LD_DIR,
"Unable to parse descriptor of type %s with hash %s and "
"length %lu. Descriptor not dumped because the sandbox is "
"configured",
type, digest_sha256_hex, (unsigned long)len);
}
tor_free(debugfile_base);
tor_free(debugfile);
err:
return;
}