/* Serval DNA - Rhizome command line interface Copyright (C) 2014 Serval Project Inc. 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 "cli.h" #include "conf.h" #include "keyring.h" #include "commandline.h" #include "rhizome.h" #include "instance.h" static void cli_put_manifest(struct cli_context *context, const rhizome_manifest *m) { assert(m->filesize != RHIZOME_SIZE_UNSET); cli_field_name(context, "manifestid", ":"); // TODO rename to "bundleid" or "bid" cli_put_string(context, alloca_tohex_rhizome_bid_t(m->cryptoSignPublic), "\n"); cli_field_name(context, "version", ":"); cli_put_long(context, m->version, "\n"); cli_field_name(context, "filesize", ":"); cli_put_long(context, m->filesize, "\n"); if (m->filesize != 0) { cli_field_name(context, "filehash", ":"); cli_put_string(context, alloca_tohex_rhizome_filehash_t(m->filehash), "\n"); } if (m->has_bundle_key) { cli_field_name(context, "BK", ":"); cli_put_string(context, alloca_tohex_rhizome_bk_t(m->bundle_key), "\n"); } if (m->has_date) { cli_field_name(context, "date", ":"); cli_put_long(context, m->date, "\n"); } switch (m->payloadEncryption) { case PAYLOAD_CRYPT_UNKNOWN: break; case PAYLOAD_CLEAR: cli_field_name(context, "crypt", ":"); cli_put_long(context, 0, "\n"); break; case PAYLOAD_ENCRYPTED: cli_field_name(context, "crypt", ":"); cli_put_long(context, 1, "\n"); break; } if (m->service) { cli_field_name(context, "service", ":"); cli_put_string(context, m->service, "\n"); } if (m->name) { cli_field_name(context, "name", ":"); cli_put_string(context, m->name, "\n"); } cli_field_name(context, ".readonly", ":"); cli_put_long(context, m->haveSecret ? 0 : 1, "\n"); if (m->haveSecret) { char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1]; rhizome_bytes_to_hex_upper(m->cryptoSignSecret, secret, RHIZOME_BUNDLE_KEY_BYTES); cli_field_name(context, ".secret", ":"); cli_put_string(context, secret, "\n"); } if (m->authorship == AUTHOR_AUTHENTIC) { cli_field_name(context, ".author", ":"); cli_put_string(context, alloca_tohex_sid_t(m->author), "\n"); } cli_field_name(context, ".rowid", ":"); cli_put_long(context, m->rowid, "\n"); cli_field_name(context, ".inserttime", ":"); cli_put_long(context, m->inserttime, "\n"); } DEFINE_CMD(app_rhizome_hash_file, 0, "Compute the Rhizome hash of a file", "rhizome","hash","file","<filepath>"); static int app_rhizome_hash_file(const struct cli_parsed *parsed, struct cli_context *context) { if (config.debug.verbose) DEBUG_cli_parsed(parsed); /* compute hash of file. We do this without a manifest, so it will necessarily return the hash of the file unencrypted. */ const char *filepath; cli_arg(parsed, "filepath", &filepath, NULL, ""); rhizome_filehash_t hash; uint64_t size; if (rhizome_hash_file(NULL, filepath, &hash, &size) == -1) return -1; cli_put_string(context, size ? alloca_tohex_rhizome_filehash_t(hash) : "", "\n"); return 0; } DEFINE_CMD(app_rhizome_add_file, 0, "Add a file to Rhizome and optionally write its manifest to the given path", "rhizome","add","file" KEYRING_PIN_OPTIONS,"[--force-new]","<author_sid>","<filepath>","[<manifestpath>]","[<bsk>]","..."); DEFINE_CMD(app_rhizome_add_file, 0, "Append content to a journal bundle", "rhizome", "journal", "append" KEYRING_PIN_OPTIONS, "<author_sid>", "<manifestid>", "<filepath>", "[<bsk>]"); static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *context) { if (config.debug.verbose) DEBUG_cli_parsed(parsed); const char *filepath, *manifestpath, *manifestIdHex, *authorSidHex, *bsktext; int force_new = 0 == cli_arg(parsed, "--force-new", NULL, NULL, NULL); cli_arg(parsed, "filepath", &filepath, NULL, ""); if (cli_arg(parsed, "author_sid", &authorSidHex, cli_optional_sid, "") == -1) return -1; cli_arg(parsed, "manifestpath", &manifestpath, NULL, ""); cli_arg(parsed, "manifestid", &manifestIdHex, NULL, ""); if (cli_arg(parsed, "bsk", &bsktext, cli_optional_bundle_secret_key, NULL) == -1) return -1; sid_t authorSid; if (!authorSidHex || !*authorSidHex) authorSidHex = NULL; else if (str_to_sid_t(&authorSid, authorSidHex) == -1) return WHYF("invalid author_sid: %s", authorSidHex); rhizome_bid_t bid; if (!manifestIdHex || !*manifestIdHex) manifestIdHex = NULL; else if (str_to_rhizome_bid_t(&bid, manifestIdHex) == -1) return WHYF("Invalid bundle ID: %s", alloca_str_toprint(manifestIdHex)); rhizome_bk_t bsk; if (!bsktext || !*bsktext) bsktext = NULL; else if (str_to_rhizome_bsk_t(&bsk, bsktext) == -1) return WHYF("invalid BSK: \"%s\"", bsktext); unsigned nfields = (parsed->varargi == -1) ? 0 : parsed->argc - (unsigned)parsed->varargi; struct rhizome_manifest_field_assignment fields[nfields]; if (nfields) { assert(parsed->varargi >= 0); unsigned i; for (i = 0; i < nfields; ++i) { struct rhizome_manifest_field_assignment *field = &fields[i]; unsigned n = (unsigned)parsed->varargi + i; assert(n < parsed->argc); const char *arg = parsed->args[n]; size_t arglen = strlen(arg); const char *eq; if (arglen > 0 && arg[0] == '!') { field->label = arg + 1; field->labellen = arglen - 1; field->value = NULL; } else if ((eq = strchr(arg, '='))) { field->label = arg; field->labellen = eq - arg; field->value = eq + 1; field->valuelen = (arg + arglen) - field->value; } else return WHYF("invalid manifest field argument: %s", alloca_str_toprint(arg)); if (!rhizome_manifest_field_label_is_valid(field->label, field->labellen)) return WHYF("invalid manifest field label: %s", alloca_toprint(-1, field->label, field->labellen)); if (field->value && !rhizome_manifest_field_value_is_valid(field->value, field->valuelen)) return WHYF("invalid manifest field value: %s", alloca_toprint(-1, field->value, field->valuelen)); } } int appending = strcasecmp(parsed->args[1], "journal")==0; if (create_serval_instance_dir() == -1) return -1; if (!(keyring = keyring_open_instance_cli(parsed))) return -1; int ret = -1; rhizome_manifest *m = NULL; if (rhizome_opendb() == -1) goto finish; /* Create a manifest in memory that to accompany the added file. Initially the manifest is blank. * If a manifest file is supplied, then read and parse it, barfing if it contains any duplicate * fields or invalid values. If it successfully parses, then overwrite it with any command-line * manifest field settings, overriding the values parsed from the file. Barf if any of these new * values are malformed. We don't validate the resulting manifest, it order to allow the user to * supply an incomplete manifest. Any missing fields will be filled in later. */ if ((m = rhizome_new_manifest()) == NULL){ ret = WHY("Manifest struct could not be allocated -- not added"); goto finish; } if (manifestpath && *manifestpath && access(manifestpath, R_OK) == 0) { if (config.debug.rhizome) DEBUGF("reading manifest from %s", manifestpath); if (rhizome_read_manifest_from_file(m, manifestpath) || m->malformed) { ret = WHY("Manifest file could not be loaded -- not added to rhizome"); goto finish; } } /* If a manifest ID (bundle ID) was supplied on the command line, first ensure it does not * contradict any manifest ID present in the supplied manifest file, then insert it into the * manifest. */ if (manifestIdHex) { if (!m->has_id) rhizome_manifest_set_id(m, &bid); else if (cmp_rhizome_bid_t(&m->cryptoSignPublic, &bid) != 0) { ret = WHYF("manifestid=%s does not match manifest id=%s", manifestIdHex, alloca_tohex_rhizome_bid_t(m->cryptoSignPublic)); goto finish; } } /* Create an in-memory manifest for the file being added. */ rhizome_manifest *mout = NULL; enum rhizome_add_file_result result = rhizome_manifest_add_file(appending, m, &mout, bsktext ? &bsk : NULL, authorSidHex ? &authorSid : NULL, filepath, nfields, fields, NULL); int result_valid = 0; switch (result) { case RHIZOME_ADD_FILE_ERROR: ret = -1; goto finish; case RHIZOME_ADD_FILE_OK: result_valid = 1; break; case RHIZOME_ADD_FILE_INVALID: ret = RHIZOME_BUNDLE_STATUS_INVALID; // TODO separate enum for CLI return codes goto finish; case RHIZOME_ADD_FILE_BUSY: ret = RHIZOME_BUNDLE_STATUS_BUSY; // TODO separate enum for CLI return codes goto finish; case RHIZOME_ADD_FILE_REQUIRES_JOURNAL: ret = RHIZOME_BUNDLE_STATUS_INVALID; // TODO separate enum for CLI return codes goto finish; case RHIZOME_ADD_FILE_INVALID_FOR_JOURNAL: ret = RHIZOME_BUNDLE_STATUS_INVALID; // TODO separate enum for CLI return codes goto finish; case RHIZOME_ADD_FILE_WRONG_SECRET: ret = RHIZOME_BUNDLE_STATUS_READONLY; // TODO separate enum for CLI return codes goto finish; } if (!result_valid) FATALF("result = %d", result); assert(mout != NULL); if (mout != m) { rhizome_manifest_free(m); m = mout; } mout = NULL; // Insert the payload into the Rhizome store. enum rhizome_payload_status pstatus; if (appending) { pstatus = rhizome_append_journal_file(m, 0, filepath); if (config.debug.rhizome) DEBUGF("rhizome_append_journal_file() returned %d %s", pstatus, rhizome_payload_status_message(pstatus)); } else { pstatus = rhizome_stat_payload_file(m, filepath); if (config.debug.rhizome) DEBUGF("rhizome_stat_payload_file() returned %d %s", pstatus, rhizome_payload_status_message(pstatus)); assert(m->filesize != RHIZOME_SIZE_UNSET); if (pstatus == RHIZOME_PAYLOAD_STATUS_NEW) { assert(m->filesize > 0); pstatus = rhizome_store_payload_file(m, filepath); if (config.debug.rhizome) DEBUGF("rhizome_store_payload_file() returned %d %s", pstatus, rhizome_payload_status_message(pstatus)); } } enum rhizome_bundle_status status = RHIZOME_BUNDLE_STATUS_ERROR; int pstatus_valid = 0; switch (pstatus) { case RHIZOME_PAYLOAD_STATUS_EMPTY: case RHIZOME_PAYLOAD_STATUS_STORED: case RHIZOME_PAYLOAD_STATUS_NEW: pstatus_valid = 1; status = RHIZOME_BUNDLE_STATUS_NEW; break; case RHIZOME_PAYLOAD_STATUS_TOO_BIG: case RHIZOME_PAYLOAD_STATUS_EVICTED: pstatus_valid = 1; status = RHIZOME_BUNDLE_STATUS_NO_ROOM; INFO("Insufficient space to store payload"); break; case RHIZOME_PAYLOAD_STATUS_ERROR: pstatus_valid = 1; status = RHIZOME_BUNDLE_STATUS_ERROR; break; case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: pstatus_valid = 1; status = RHIZOME_BUNDLE_STATUS_INCONSISTENT; break; case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: pstatus_valid = 1; status = RHIZOME_BUNDLE_STATUS_READONLY; break; } if (!pstatus_valid) FATALF("pstatus = %d", pstatus); if (status == RHIZOME_BUNDLE_STATUS_NEW) { if (!rhizome_manifest_validate(m) || m->malformed) status = RHIZOME_BUNDLE_STATUS_INVALID; else { status = rhizome_manifest_finalise(m, &mout, !force_new); if (mout && mout != m && !rhizome_manifest_validate(mout)) { WHYF("Stored manifest id=%s is invalid -- overwriting", alloca_tohex_rhizome_bid_t(mout->cryptoSignPublic)); status = RHIZOME_BUNDLE_STATUS_NEW; } } } int status_valid = 0; switch (status) { case RHIZOME_BUNDLE_STATUS_NEW: if (mout && mout != m) rhizome_manifest_free(mout); mout = m; // fall through case RHIZOME_BUNDLE_STATUS_SAME: case RHIZOME_BUNDLE_STATUS_DUPLICATE: case RHIZOME_BUNDLE_STATUS_OLD: assert(mout != NULL); cli_put_manifest(context, mout); if ( manifestpath && *manifestpath && rhizome_write_manifest_file(mout, manifestpath, 0) == -1 ) WHYF("Could not write manifest to %s", alloca_str_toprint(manifestpath)); status_valid = 1; break; case RHIZOME_BUNDLE_STATUS_READONLY: case RHIZOME_BUNDLE_STATUS_INCONSISTENT: case RHIZOME_BUNDLE_STATUS_ERROR: case RHIZOME_BUNDLE_STATUS_INVALID: case RHIZOME_BUNDLE_STATUS_FAKE: case RHIZOME_BUNDLE_STATUS_NO_ROOM: case RHIZOME_BUNDLE_STATUS_BUSY: status_valid = 1; break; // Do not use a default: label! With no default, if a new value is added to the enum, then the // compiler will issue a warning on switch statements that do not cover all the values, which is // a valuable tool for the developer. } if (!status_valid) FATALF("status=%d", status); if (mout && mout != m) rhizome_manifest_free(mout); ret = status; finish: rhizome_manifest_free(m); keyring_free(keyring); keyring = NULL; return ret; } DEFINE_CMD(app_rhizome_import_bundle, 0, "Import a payload/manifest pair into Rhizome", "rhizome","import","bundle","<filepath>","<manifestpath>"); static int app_rhizome_import_bundle(const struct cli_parsed *parsed, struct cli_context *context) { if (config.debug.verbose) DEBUG_cli_parsed(parsed); const char *filepath, *manifestpath; cli_arg(parsed, "filepath", &filepath, NULL, ""); cli_arg(parsed, "manifestpath", &manifestpath, NULL, ""); if (rhizome_opendb() == -1) return -1; rhizome_manifest *m = rhizome_new_manifest(); if (!m) return WHY("Out of manifests."); rhizome_manifest *m_out = NULL; enum rhizome_bundle_status status = rhizome_bundle_import_files(m, &m_out, manifestpath, filepath); switch (status) { case RHIZOME_BUNDLE_STATUS_NEW: cli_put_manifest(context, m); break; case RHIZOME_BUNDLE_STATUS_SAME: case RHIZOME_BUNDLE_STATUS_DUPLICATE: case RHIZOME_BUNDLE_STATUS_OLD: cli_put_manifest(context, m_out); break; case RHIZOME_BUNDLE_STATUS_ERROR: case RHIZOME_BUNDLE_STATUS_INVALID: case RHIZOME_BUNDLE_STATUS_INCONSISTENT: break; default: FATALF("rhizome_bundle_import_files() returned %d", status); } if (m_out && m_out != m) rhizome_manifest_free(m_out); rhizome_manifest_free(m); return status; } DEFINE_CMD(app_rhizome_append_manifest, 0, "Append a manifest to the end of the file it belongs to.", "rhizome", "append", "manifest", "<filepath>", "<manifestpath>"); static int app_rhizome_append_manifest(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) { if (config.debug.verbose) DEBUG_cli_parsed(parsed); const char *manifestpath, *filepath; if ( cli_arg(parsed, "manifestpath", &manifestpath, NULL, "") == -1 || cli_arg(parsed, "filepath", &filepath, NULL, "") == -1) return -1; rhizome_manifest *m = rhizome_new_manifest(); if (!m) return WHY("Out of manifests."); int ret = -1; if ( rhizome_read_manifest_from_file(m, manifestpath) != -1 && rhizome_manifest_validate(m) && rhizome_manifest_verify(m) ) { if (rhizome_write_manifest_file(m, filepath, 1) != -1) ret = 0; } rhizome_manifest_free(m); return ret; } DEFINE_CMD(app_rhizome_delete, 0, "Remove the manifest, or payload, or both for the given Bundle ID from the Rhizome store", "rhizome","delete","manifest|payload|bundle","<manifestid>"); DEFINE_CMD(app_rhizome_delete, 0, "Remove the file with the given hash from the Rhizome store", "rhizome","delete","|file","<fileid>"); static int app_rhizome_delete(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) { if (config.debug.verbose) DEBUG_cli_parsed(parsed); const char *manifestid, *fileid; if (cli_arg(parsed, "manifestid", &manifestid, cli_manifestid, NULL) == -1) return -1; if (cli_arg(parsed, "fileid", &fileid, cli_fileid, NULL) == -1) return -1; /* Ensure the Rhizome database exists and is open */ if (create_serval_instance_dir() == -1) return -1; if (rhizome_opendb() == -1) return -1; if (!(keyring = keyring_open_instance_cli(parsed))) return -1; int ret=0; if (cli_arg(parsed, "file", NULL, NULL, NULL) == 0) { if (!fileid){ keyring_free(keyring); keyring = NULL; return WHY("missing <fileid> argument"); } rhizome_filehash_t hash; if (str_to_rhizome_filehash_t(&hash, fileid) == -1){ keyring_free(keyring); keyring = NULL; return WHYF("invalid <fileid> argument: %s", alloca_str_toprint(fileid)); } ret = rhizome_delete_file(&hash); } else { if (!manifestid){ keyring_free(keyring); keyring = NULL; return WHY("missing <manifestid> argument"); } rhizome_bid_t bid; if (str_to_rhizome_bid_t(&bid, manifestid) == -1){ keyring_free(keyring); keyring = NULL; return WHY("Invalid manifest ID"); } if (cli_arg(parsed, "bundle", NULL, NULL, NULL) == 0) ret = rhizome_delete_bundle(&bid); else if (cli_arg(parsed, "manifest", NULL, NULL, NULL) == 0) ret = rhizome_delete_manifest(&bid); else if (cli_arg(parsed, "payload", NULL, NULL, NULL) == 0) ret = rhizome_delete_payload(&bid); else{ keyring_free(keyring); keyring = NULL; return WHY("unrecognised command"); } } keyring_free(keyring); keyring = NULL; return ret; } DEFINE_CMD(app_rhizome_clean, 0, "Remove stale and orphaned content from the Rhizome store", "rhizome","clean","[verify]"); static int app_rhizome_clean(const struct cli_parsed *parsed, struct cli_context *context) { if (config.debug.verbose) DEBUG_cli_parsed(parsed); int verify = cli_arg(parsed, "verify", NULL, NULL, NULL) == 0; /* Ensure the Rhizome database exists and is open */ if (create_serval_instance_dir() == -1) return -1; if (rhizome_opendb() == -1) return -1; if (verify) verify_bundles(); struct rhizome_cleanup_report report; if (rhizome_cleanup(&report) == -1) return -1; cli_field_name(context, "deleted_stale_incoming_files", ":"); cli_put_long(context, report.deleted_stale_incoming_files, "\n"); cli_field_name(context, "deleted_orphan_files", ":"); cli_put_long(context, report.deleted_orphan_files, "\n"); cli_field_name(context, "deleted_orphan_fileblobs", ":"); cli_put_long(context, report.deleted_orphan_fileblobs, "\n"); cli_field_name(context, "deleted_orphan_manifests", ":"); cli_put_long(context, report.deleted_orphan_manifests, "\n"); return 0; } DEFINE_CMD(app_rhizome_extract, 0, "Export a manifest and payload file to the given paths, without decrypting.", "rhizome","export","bundle" KEYRING_PIN_OPTIONS, "<manifestid>","[<manifestpath>]","[<filepath>]"); DEFINE_CMD(app_rhizome_extract, 0, "Export a manifest from Rhizome and write it to the given path", "rhizome","export","manifest" KEYRING_PIN_OPTIONS, "<manifestid>","[<manifestpath>]"); DEFINE_CMD(app_rhizome_extract, 0, "Extract and decrypt a manifest and file to the given paths.", "rhizome","extract","bundle" KEYRING_PIN_OPTIONS, "<manifestid>","[<manifestpath>]","[<filepath>]","[<bsk>]"); DEFINE_CMD(app_rhizome_extract, 0, "Extract and decrypt a file from Rhizome and write it to the given path", "rhizome","extract","file" KEYRING_PIN_OPTIONS, "<manifestid>","[<filepath>]","[<bsk>]"); static int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_context *context) { if (config.debug.verbose) DEBUG_cli_parsed(parsed); const char *manifestpath, *filepath, *manifestid, *bsktext; if ( cli_arg(parsed, "manifestid", &manifestid, cli_manifestid, "") == -1 || cli_arg(parsed, "manifestpath", &manifestpath, NULL, "") == -1 || cli_arg(parsed, "filepath", &filepath, NULL, "") == -1 || cli_arg(parsed, "bsk", &bsktext, cli_optional_bundle_secret_key, NULL) == -1) return -1; int extract = strcasecmp(parsed->args[1], "extract")==0; /* Ensure the Rhizome database exists and is open */ if (create_serval_instance_dir() == -1) return -1; if (rhizome_opendb() == -1) return -1; if (!(keyring = keyring_open_instance_cli(parsed))) return -1; rhizome_manifest *m = NULL; int ret=0; rhizome_bid_t bid; if (str_to_rhizome_bid_t(&bid, manifestid) == -1) { ret = WHY("Invalid manifest ID"); goto finish; } // treat empty string the same as null if (bsktext && !*bsktext) bsktext = NULL; rhizome_bk_t bsk; if (bsktext && str_to_rhizome_bsk_t(&bsk, bsktext) == -1) { ret = WHYF("invalid bsk: \"%s\"", bsktext); goto finish; } if ((m = rhizome_new_manifest()) == NULL) { ret = WHY("Out of manifests"); goto finish; } switch(rhizome_retrieve_manifest(&bid, m)){ case RHIZOME_BUNDLE_STATUS_NEW: ret=1; break; case RHIZOME_BUNDLE_STATUS_SAME: ret=0; break; default: ret=-1; break; } if (ret==0){ assert(m->finalised); if (bsktext) rhizome_apply_bundle_secret(m, &bsk); rhizome_authenticate_author(m); assert(m->authorship != AUTHOR_LOCAL); cli_put_manifest(context, m); } enum rhizome_payload_status pstatus = RHIZOME_PAYLOAD_STATUS_EMPTY; if (ret==0 && m->filesize != 0 && filepath && *filepath){ if (extract){ // Save the file, implicitly decrypting if required. pstatus = rhizome_extract_file(m, filepath); if (pstatus != RHIZOME_PAYLOAD_STATUS_EMPTY && pstatus != RHIZOME_PAYLOAD_STATUS_STORED) WHYF("rhizome_extract_file() returned %d", pstatus); }else{ // Save the file without attempting to decrypt uint64_t length; pstatus = rhizome_dump_file(&m->filehash, filepath, &length); if (pstatus != RHIZOME_PAYLOAD_STATUS_EMPTY && pstatus != RHIZOME_PAYLOAD_STATUS_STORED) WHYF("rhizome_dump_file() returned %d", pstatus); } } if (ret==0 && manifestpath && *manifestpath){ if (strcmp(manifestpath, "-") == 0) { // always extract a manifest to stdout, even if writing the file itself failed. cli_field_name(context, "manifest", ":"); cli_write(context, m->manifestdata, m->manifest_all_bytes); cli_delim(context, "\n"); } else { int append = (strcmp(manifestpath, filepath)==0)?1:0; // don't write out the manifest if we were asked to append it and writing the file failed. if (!append || (pstatus == RHIZOME_PAYLOAD_STATUS_EMPTY || pstatus == RHIZOME_PAYLOAD_STATUS_STORED)) { if (rhizome_write_manifest_file(m, manifestpath, append) == -1) ret = -1; } } } switch (pstatus) { case RHIZOME_PAYLOAD_STATUS_EMPTY: case RHIZOME_PAYLOAD_STATUS_STORED: break; case RHIZOME_PAYLOAD_STATUS_NEW: ret = 1; // payload not found break; case RHIZOME_PAYLOAD_STATUS_ERROR: case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: ret = -1; break; default: FATALF("pstatus = %d", pstatus); } finish: rhizome_manifest_free(m); keyring_free(keyring); keyring = NULL; return ret; } DEFINE_CMD(app_rhizome_export_file, 0, "Export a file from Rhizome and write it to the given path without attempting decryption", "rhizome","export","file","<fileid>","[<filepath>]"); static int app_rhizome_export_file(const struct cli_parsed *parsed, struct cli_context *context) { if (config.debug.verbose) DEBUG_cli_parsed(parsed); const char *fileid, *filepath; if ( cli_arg(parsed, "filepath", &filepath, NULL, "") == -1 || cli_arg(parsed, "fileid", &fileid, cli_fileid, NULL) == -1) return -1; rhizome_filehash_t hash; if (str_to_rhizome_filehash_t(&hash, fileid) == -1) return WHYF("invalid <fileid> argument: %s", alloca_str_toprint(fileid)); if (create_serval_instance_dir() == -1) return -1; if (rhizome_opendb() == -1) return -1; if (!rhizome_exists(&hash)) return 1; uint64_t length; enum rhizome_payload_status pstatus = rhizome_dump_file(&hash, filepath, &length); switch (pstatus) { case RHIZOME_PAYLOAD_STATUS_EMPTY: case RHIZOME_PAYLOAD_STATUS_STORED: break; case RHIZOME_PAYLOAD_STATUS_NEW: return 1; // payload not found case RHIZOME_PAYLOAD_STATUS_ERROR: case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: return -1; default: FATALF("pstatus = %d", pstatus); } cli_field_name(context, "filehash", ":"); cli_put_string(context, alloca_tohex_rhizome_filehash_t(hash), "\n"); cli_field_name(context, "filesize", ":"); cli_put_long(context, length, "\n"); return 0; } DEFINE_CMD(app_rhizome_list, 0, "List all manifests and files in Rhizome", "rhizome","list" KEYRING_PIN_OPTIONS, "[<service>]","[<name>]","[<sender_sid>]","[<recipient_sid>]","[<offset>]","[<limit>]"); static int app_rhizome_list(const struct cli_parsed *parsed, struct cli_context *context) { if (config.debug.verbose) DEBUG_cli_parsed(parsed); const char *service = NULL, *name = NULL, *sender_hex = NULL, *recipient_hex = NULL, *offset_ascii = NULL, *limit_ascii = NULL; cli_arg(parsed, "service", &service, NULL, ""); cli_arg(parsed, "name", &name, NULL, ""); cli_arg(parsed, "sender_sid", &sender_hex, cli_optional_sid, ""); cli_arg(parsed, "recipient_sid", &recipient_hex, cli_optional_sid, ""); cli_arg(parsed, "offset", &offset_ascii, cli_uint, "0"); cli_arg(parsed, "limit", &limit_ascii, cli_uint, "0"); /* Create the instance directory if it does not yet exist */ if (create_serval_instance_dir() == -1) return -1; if (!(keyring = keyring_open_instance_cli(parsed))) return -1; if (rhizome_opendb() == -1) { keyring_free(keyring); keyring = NULL; return -1; } size_t rowlimit = atoi(limit_ascii); size_t rowoffset = atoi(offset_ascii); struct rhizome_list_cursor cursor; bzero(&cursor, sizeof cursor); cursor.service = service && service[0] ? service : NULL; cursor.name = name && name[0] ? name : NULL; if (sender_hex && sender_hex[0]) { if (str_to_sid_t(&cursor.sender, sender_hex) == -1) return WHYF("Invalid <sender>: %s", sender_hex); cursor.is_sender_set = 1; } if (recipient_hex && recipient_hex[0]) { if (str_to_sid_t(&cursor.recipient, recipient_hex) == -1) return WHYF("Invalid <recipient: %s", recipient_hex); cursor.is_recipient_set = 1; } if (rhizome_list_open(&cursor) == -1) { keyring_free(keyring); keyring = NULL; return -1; } const char *headers[]={ "_id", "service", "id", "version", "date", ".inserttime", ".author", ".fromhere", "filesize", "filehash", "sender", "recipient", "name" }; cli_columns(context, NELS(headers), headers); size_t rowcount = 0; int n; while ((n = rhizome_list_next(&cursor)) == 1) { ++rowcount; if (rowcount <= rowoffset) continue; if (rowlimit == 0 || rowcount <= rowoffset + rowlimit) { rhizome_manifest *m = cursor.manifest; assert(m->filesize != RHIZOME_SIZE_UNSET); rhizome_lookup_author(m); cli_put_long(context, m->rowid, ":"); cli_put_string(context, m->service, ":"); cli_put_hexvalue(context, m->cryptoSignPublic.binary, sizeof m->cryptoSignPublic.binary, ":"); cli_put_long(context, m->version, ":"); cli_put_long(context, m->has_date ? m->date : 0, ":"); cli_put_long(context, m->inserttime, ":"); switch (m->authorship) { case AUTHOR_LOCAL: case AUTHOR_AUTHENTIC: cli_put_hexvalue(context, m->author.binary, sizeof m->author.binary, ":"); cli_put_long(context, 1, ":"); break; default: cli_put_string(context, NULL, ":"); cli_put_long(context, 0, ":"); break; } cli_put_long(context, m->filesize, ":"); cli_put_hexvalue(context, m->filesize ? m->filehash.binary : NULL, sizeof m->filehash.binary, ":"); cli_put_hexvalue(context, m->has_sender ? m->sender.binary : NULL, sizeof m->sender.binary, ":"); cli_put_hexvalue(context, m->has_recipient ? m->recipient.binary : NULL, sizeof m->recipient.binary, ":"); cli_put_string(context, m->name, "\n"); } } rhizome_list_release(&cursor); keyring_free(keyring); keyring = NULL; if (n == -1) return -1; cli_row_count(context, rowcount); return 0; }