Move most console commands to other files

This commit is contained in:
Jeremy Lakeman 2014-08-25 14:24:00 +09:30
parent 9896b6680a
commit a37db8e958
22 changed files with 2652 additions and 2582 deletions

11
cli.h
View File

@ -90,15 +90,4 @@ int cli_interval_ms(const char *arg);
int cli_uint(const char *arg);
int cli_optional_did(const char *text);
int cli_putchar(struct cli_context *context, char c);
int cli_puts(struct cli_context *context, const char *str);
void cli_printf(struct cli_context *context, const char *fmt, ...) __attribute__ (( format(printf,2,3) ));
int cli_delim(struct cli_context *context, const char *opt);
void cli_columns(struct cli_context *context, int columns, const char *names[]);
void cli_row_count(struct cli_context *context, int rows);
void cli_field_name(struct cli_context *context, const char *name, const char *delim);
void cli_put_long(struct cli_context *context, int64_t value, const char *delim);
void cli_put_string(struct cli_context *context, const char *value, const char *delim);
void cli_put_hexvalue(struct cli_context *context, const unsigned char *value, int length, const char *delim);
#endif // __SERVAL_DNA__CLI_H

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,21 @@
/*
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.
*/
#ifndef __SERVAL_DNA__COMMANDLINE_H
#define __SERVAL_DNA__COMMANDLINE_H
@ -9,6 +27,9 @@
#define _APPEND(X,Y) X ## Y
#define _APPEND2(X,Y) _APPEND(X,Y)
struct cli_schema;
struct cli_context;
#define DEFINE_CMD(FUNC, FLAGS, HELP, WORD1, ...) \
static int FUNC(const struct cli_parsed *parsed, struct cli_context *context); \
struct cli_schema _APPEND2(FUNC, __LINE__) \
@ -23,4 +44,17 @@ extern struct cli_schema __start_commands[];
extern struct cli_schema __stop_commands[];
#define CMD_COUNT (__stop_commands - __start_commands)
void cli_flush(struct cli_context *context);
int cli_delim(struct cli_context *context, const char *opt);
int cli_puts(struct cli_context *context, const char *str);
void cli_printf(struct cli_context *context, const char *fmt, ...) __attribute__ (( format(printf,2,3) ));
void cli_columns(struct cli_context *context, int columns, const char *names[]);
void cli_row_count(struct cli_context *context, int rows);
void cli_field_name(struct cli_context *context, const char *name, const char *delim);
void cli_put_long(struct cli_context *context, int64_t value, const char *delim);
void cli_put_string(struct cli_context *context, const char *value, const char *delim);
void cli_put_hexvalue(struct cli_context *context, const unsigned char *value, int length, const char *delim);
int cli_write(struct cli_context *context, const unsigned char *buf, size_t len);
#endif

279
conf_cli.c Normal file
View File

@ -0,0 +1,279 @@
/*
Serval configuration command-line functions
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 "conf.h"
#include "cli.h"
#include "commandline.h"
#include "strbuf.h"
#include "strbuf_helpers.h"
#include "instance.h"
#include "mdp_client.h"
#include "server.h"
DEFINE_CMD(app_config_schema, CLIFLAG_PERMISSIVE_CONFIG,
"Display configuration schema.",
"config", "schema");
static int app_config_schema(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
struct cf_om_node *root = NULL;
if (cf_sch_config_main(&root) == -1) {
cf_om_free_node(&root);
return -1;
}
struct cf_om_iterator it;
for (cf_om_iter_start(&it, root); it.node; cf_om_iter_next(&it))
if (it.node->text || it.node->nodc == 0) {
cli_put_string(context, it.node->fullkey,"=");
cli_put_string(context, it.node->text, "\n");
}
cf_om_free_node(&root);
return 0;
}
DEFINE_CMD(app_config_dump, CLIFLAG_PERMISSIVE_CONFIG,
"Dump configuration settings.",
"config","dump","[--full]");
static int app_config_dump(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
int full = 0 == cli_arg(parsed, "--full", NULL, NULL, NULL);
if (create_serval_instance_dir() == -1)
return -1;
struct cf_om_node *root = NULL;
int ret = cf_fmt_config_main(&root, &config);
if (ret == CFERROR) {
cf_om_free_node(&root);
return -1;
}
struct cf_om_iterator it;
for (cf_om_iter_start(&it, root); it.node; cf_om_iter_next(&it)) {
if (it.node->text && (full || it.node->line_number)) {
cli_put_string(context, it.node->fullkey, "=");
cli_put_string(context, it.node->text, "\n");
}
}
cf_om_free_node(&root);
return ret == CFOK ? 0 : 1;
}
static int mdp_client_sync_config(time_ms_t timeout)
{
/* Bind to MDP socket and await confirmation */
struct mdp_header mdp_header = {
.remote.port = MDP_SYNC_CONFIG,
};
int mdpsock = mdp_socket();
if (mdpsock == -1)
return WHY("cannot create MDP socket");
set_nonblock(mdpsock);
int r = mdp_send(mdpsock, &mdp_header, NULL, 0);
if (r == -1)
return -1;
time_ms_t deadline = gettime_ms() + timeout; // TODO add --timeout option
struct mdp_header rev_header;
do {
ssize_t len = mdp_poll_recv(mdpsock, deadline, &rev_header, NULL, 0);
if (len == -1)
return -1;
if (len == -2) {
WHYF("timeout while synchronising daemon configuration");
return -1;
}
} while (!(rev_header.flags & MDP_FLAG_CLOSE));
return 0;
}
DEFINE_CMD(app_config_set, CLIFLAG_PERMISSIVE_CONFIG,
"Set and del specified configuration variables.",
"config","set","<variable>","<value>","...");
DEFINE_CMD(app_config_set, CLIFLAG_PERMISSIVE_CONFIG,
"Del and set specified configuration variables.",
"config","del","<variable>","...");
DEFINE_CMD(app_config_set, CLIFLAG_PERMISSIVE_CONFIG,
"Synchronise with the daemon's configuration.",
"config","sync","...");
static int app_config_set(const struct cli_parsed *parsed, struct cli_context *UNUSED(context))
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
if (create_serval_instance_dir() == -1)
return -1;
// <kludge>
// This fixes a subtle bug in when upgrading the Batphone app: the servald.conf file does
// not get upgraded. The bug goes like this:
// 1. new Batphone APK is installed, but prior servald.conf is not overwritten because it
// comes in serval.zip;
// 2. new Batphone is started, which calls JNI "stop" command, which reads the old servald.conf
// into memory buffer;
// 3. new Batphone unpacks serval.zip, overwriting servald.conf with new version;
// 4. new Batphone calls JNI "config set rhizome.enable 1", which sets the "rhizome.enable"
// config option in the existing memory buffer and overwrites servald.conf;
// Bingo, the old version of servald.conf is what remains. This kludge intervenes in step 4, by
// reading the new servald.conf into the memory buffer before applying the "rhizome.enable" set
// value and overwriting.
if (cf_om_reload() == -1)
return -1;
// </kludge>
const char *var[parsed->argc - 1];
const char *val[parsed->argc - 1];
unsigned nvar = 0;
unsigned i;
for (i = 1; i < parsed->argc; ++i) {
const char *arg = parsed->args[i];
int iv = -1;
if (strcmp(arg, "set") == 0) {
if (i + 2 > parsed->argc)
return WHYF("malformed command at args[%d]: 'set' not followed by two arguments", i);
var[nvar] = parsed->args[iv = ++i];
val[nvar] = parsed->args[++i];
} else if (strcmp(arg, "del") == 0) {
if (i + 1 > parsed->argc)
return WHYF("malformed command at args[%d]: 'del' not followed by one argument", i);
var[nvar] = parsed->args[iv = ++i];
val[nvar] = NULL;
} else if (strcmp(arg, "sync") == 0)
var[nvar] = val[nvar] = NULL;
else
return WHYF("malformed command at args[%d]: unsupported action '%s'", i, arg);
if (var[nvar] && !is_configvarname(var[nvar]))
return WHYF("malformed command at args[%d]: '%s' is not a valid config option name", iv, var[nvar]);
++nvar;
}
int changed = 0;
for (i = 0; i < nvar; ++i) {
if (var[i]) {
if (cf_om_set(&cf_om_root, var[i], val[i]) == -1)
return -1;
if (val[i])
INFOF("config set %s %s", var[i], alloca_str_toprint(val[i]));
else
INFOF("config del %s", var[i]);
changed = 1;
} else {
if (changed) {
if (cf_om_save() == -1)
return -1;
if (cf_reload() == -1) // logs an error if the new config is bad
return 2;
changed = 0;
}
int pid = server_pid();
if (pid) {
INFO("config sync");
// TODO make timeout configurable with --timeout option.
if (mdp_client_sync_config(10000) == -1)
return 3;
} else
INFO("config sync -- skipped, server not running");
}
}
if (changed) {
if (cf_om_save() == -1)
return -1;
if (cf_reload() == -1) // logs an error if the new config is bad
return 2;
}
return 0;
}
DEFINE_CMD(app_config_get, CLIFLAG_PERMISSIVE_CONFIG,
"Get specified configuration variable.",
"config","get","[<variable>]");
static int app_config_get(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
const char *var;
if (cli_arg(parsed, "variable", &var, is_configvarpattern, NULL) == -1)
return -1;
if (create_serval_instance_dir() == -1)
return -1;
if (cf_om_reload() == -1)
return -1;
if (var && is_configvarname(var)) {
const char *value = cf_om_get(cf_om_root, var);
if (value) {
cli_field_name(context, var, "=");
cli_put_string(context, value, "\n");
}
} else {
struct cf_om_iterator it;
for (cf_om_iter_start(&it, cf_om_root); it.node; cf_om_iter_next(&it)) {
if (var && cf_om_match(var, it.node) <= 0)
continue;
if (it.node->text) {
cli_field_name(context, it.node->fullkey, "=");
cli_put_string(context, it.node->text, "\n");
}
}
}
return 0;
}
DEFINE_CMD(app_config_paths, CLIFLAG_PERMISSIVE_CONFIG,
"Dump file and directory paths.",
"config", "paths");
static int app_config_paths(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
if (cf_om_reload() == -1)
return -1;
char path[1024];
if (FORMF_SERVAL_ETC_PATH(path, NULL)) {
cli_field_name(context, "SERVAL_ETC_PATH", ":");
cli_put_string(context, path, "\n");
}
if (FORMF_SERVAL_RUN_PATH(path, NULL)) {
cli_field_name(context, "SERVAL_RUN_PATH", ":");
cli_put_string(context, path, "\n");
}
if (FORMF_SERVAL_CACHE_PATH(path, NULL)) {
cli_field_name(context, "SERVAL_CACHE_PATH", ":");
cli_put_string(context, path, "\n");
}
strbuf sb = strbuf_local(path, sizeof path);
strbuf_system_log_path(sb);
if (!strbuf_overrun(sb)) {
cli_field_name(context, "SYSTEM_LOG_PATH", ":");
cli_put_string(context, path, "\n");
}
strbuf_reset(sb);
strbuf_serval_log_path(sb);
if (!strbuf_overrun(sb)) {
cli_field_name(context, "SERVAL_LOG_PATH", ":");
cli_put_string(context, path, "\n");
}
if (FORMF_SERVAL_TMP_PATH(path, NULL)) {
cli_field_name(context, "SERVAL_TMP_PATH", ":");
cli_put_string(context, path, "\n");
}
if (FORMF_SERVALD_PROC_PATH(path, NULL)) {
cli_field_name(context, "SERVALD_PROC_PATH", ":");
cli_put_string(context, path, "\n");
}
if (FORMF_RHIZOME_STORE_PATH(path, NULL)) {
cli_field_name(context, "RHIZOME_STORE_PATH", ":");
cli_put_string(context, path, "\n");
}
return 0;
}

View File

@ -25,6 +25,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "overlay_interface.h"
#include "mem.h"
#include "net.h"
#include "server.h"
#define RHIZOME_SERVER_MAX_LIVE_REQUESTS 32

View File

@ -35,6 +35,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "str.h"
#include "mem.h"
#include "rotbuf.h"
#include "server.h"
static void keyring_free_keypair(keypair *kp);
static void keyring_free_context(keyring_context *c);

472
keyring_cli.c Normal file
View File

@ -0,0 +1,472 @@
/*
Serval keyring command line functions
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 <stdio.h>
#include "serval_types.h"
#include "dataformats.h"
#include "os.h"
#include "conf.h"
#include "mdp_client.h"
#include "cli.h"
#include "commandline.h"
#include "keyring.h"
DEFINE_CMD(app_keyring_create, 0,
"Create a new keyring file.",
"keyring","create");
static int app_keyring_create(const struct cli_parsed *parsed, struct cli_context *UNUSED(context))
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
keyring_file *k = keyring_open_instance();
if (!k)
return -1;
keyring_free(k);
return 0;
}
DEFINE_CMD(app_keyring_dump, 0,
"Dump all keyring identities that can be accessed using the specified PINs",
"keyring","dump" KEYRING_PIN_OPTIONS,"[--secret]","[<file>]");
static int app_keyring_dump(const struct cli_parsed *parsed, struct cli_context *UNUSED(context))
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
const char *path;
if (cli_arg(parsed, "file", &path, cli_path_regular, NULL) == -1)
return -1;
int include_secret = 0 == cli_arg(parsed, "--secret", NULL, NULL, NULL);
keyring_file *k = keyring_open_instance_cli(parsed);
if (!k)
return -1;
FILE *fp = path ? fopen(path, "w") : stdout;
if (fp == NULL) {
WHYF_perror("fopen(%s, \"w\")", alloca_str_toprint(path));
keyring_free(k);
return -1;
}
int ret = keyring_dump(k, XPRINTF_STDIO(fp), include_secret);
if (fp != stdout && fclose(fp) == EOF) {
WHYF_perror("fclose(%s)", alloca_str_toprint(path));
keyring_free(k);
return -1;
}
keyring_free(k);
return ret;
}
DEFINE_CMD(app_keyring_load, 0,
"Load identities from the given dump text and insert them into the keyring using the specified entry PINs",
"keyring","load" KEYRING_PIN_OPTIONS,"<file>","[<keyring-pin>]","[<entry-pin>]...");
static int app_keyring_load(const struct cli_parsed *parsed, struct cli_context *UNUSED(context))
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
const char *path;
if (cli_arg(parsed, "file", &path, cli_path_regular, NULL) == -1)
return -1;
const char *kpin;
if (cli_arg(parsed, "keyring-pin", &kpin, NULL, "") == -1)
return -1;
unsigned pinc = 0;
unsigned i;
for (i = 0; i < parsed->labelc; ++i)
if (strn_str_cmp(parsed->labelv[i].label, parsed->labelv[i].len, "entry-pin") == 0)
++pinc;
const char *pinv[pinc];
unsigned pc = 0;
for (i = 0; i < parsed->labelc; ++i)
if (strn_str_cmp(parsed->labelv[i].label, parsed->labelv[i].len, "entry-pin") == 0) {
assert(pc < pinc);
pinv[pc++] = parsed->labelv[i].text;
}
keyring_file *k = keyring_open_instance_cli(parsed);
if (!k)
return -1;
FILE *fp = path && strcmp(path, "-") != 0 ? fopen(path, "r") : stdin;
if (fp == NULL) {
WHYF_perror("fopen(%s, \"r\")", alloca_str_toprint(path));
keyring_free(k);
return -1;
}
if (keyring_load(k, kpin, pinc, pinv, fp) == -1) {
keyring_free(k);
return -1;
}
if (keyring_commit(k) == -1) {
keyring_free(k);
return WHY("Could not write new identity");
}
keyring_free(k);
return 0;
}
DEFINE_CMD(app_keyring_list, 0,
"List identities that can be accessed using the supplied PINs",
"keyring","list" KEYRING_PIN_OPTIONS);
static int app_keyring_list(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
keyring_file *k = keyring_open_instance_cli(parsed);
if (!k)
return -1;
const char *names[]={
"sid",
"did",
"name"
};
cli_columns(context, 3, names);
size_t rowcount = 0;
unsigned cn, in;
for (cn = 0; cn < k->context_count; ++cn)
for (in = 0; in < k->contexts[cn]->identity_count; ++in) {
const sid_t *sidp = NULL;
const char *did = NULL;
const char *name = NULL;
keyring_identity_extract(k->contexts[cn]->identities[in], &sidp, &did, &name);
if (sidp || did) {
cli_put_string(context, alloca_tohex_sid_t(*sidp), ":");
cli_put_string(context, did, ":");
cli_put_string(context, name, "\n");
rowcount++;
}
}
keyring_free(k);
cli_row_count(context, rowcount);
return 0;
}
static void cli_output_identity(struct cli_context *context, const keyring_identity *id)
{
unsigned i;
for (i=0;i<id->keypair_count;i++){
keypair *kp=id->keypairs[i];
switch(kp->type){
case KEYTYPE_CRYPTOBOX:
cli_field_name(context, "sid", ":");
cli_put_string(context, alloca_tohex(kp->public_key, kp->public_key_len), "\n");
break;
case KEYTYPE_DID:
{
char *str = (char*)kp->private_key;
int l = strlen(str);
if (l){
cli_field_name(context, "did", ":");
cli_put_string(context, str, "\n");
}
str = (char*)kp->public_key;
l=strlen(str);
if (l){
cli_field_name(context, "name", ":");
cli_put_string(context, str, "\n");
}
}
break;
case KEYTYPE_PUBLIC_TAG:
{
const char *name;
const unsigned char *value;
size_t length;
if (keyring_unpack_tag(kp->public_key, kp->public_key_len, &name, &value, &length)==0){
cli_field_name(context, name, ":");
cli_put_string(context, alloca_toprint_quoted(-1, value, length, NULL), "\n");
}
}
break;
}
}
}
DEFINE_CMD(app_keyring_add, 0,
"Create a new identity in the keyring protected by the supplied PIN (empty PIN if not given)",
"keyring","add" KEYRING_PIN_OPTIONS,"[<pin>]");
static int app_keyring_add(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
const char *pin;
cli_arg(parsed, "pin", &pin, NULL, "");
keyring_file *k = keyring_open_instance_cli(parsed);
if (!k)
return -1;
keyring_enter_pin(k, pin);
assert(k->context_count > 0);
const keyring_identity *id = keyring_create_identity(k, k->contexts[k->context_count - 1], pin);
if (id == NULL) {
keyring_free(k);
return WHY("Could not create new identity");
}
const sid_t *sidp = NULL;
const char *did = "";
const char *name = "";
keyring_identity_extract(id, &sidp, &did, &name);
if (!sidp) {
keyring_free(k);
return WHY("New identity has no SID");
}
if (keyring_commit(k) == -1) {
keyring_free(k);
return WHY("Could not write new identity");
}
cli_output_identity(context, id);
keyring_free(k);
return 0;
}
DEFINE_CMD(app_keyring_set_did, 0,
"Set the DID for the specified SID (must supply PIN to unlock the SID record in the keyring)",
"keyring", "set","did" KEYRING_PIN_OPTIONS,"<sid>","<did>","<name>");
static int app_keyring_set_did(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
const char *sidhex, *did, *name;
if (cli_arg(parsed, "sid", &sidhex, str_is_subscriber_id, "") == -1 ||
cli_arg(parsed, "did", &did, cli_optional_did, "") == -1 ||
cli_arg(parsed, "name", &name, NULL, "") == -1)
return -1;
if (strlen(name)>63)
return WHY("Name too long (31 char max)");
sid_t sid;
if (str_to_sid_t(&sid, sidhex) == -1){
keyring_free(keyring);
keyring = NULL;
return WHY("str_to_sid_t() failed");
}
if (!(keyring = keyring_open_instance_cli(parsed)))
return -1;
unsigned cn=0, in=0, kp=0;
int r=0;
if (!keyring_find_sid(keyring, &cn, &in, &kp, &sid))
r=WHY("No matching SID");
else{
if (keyring_set_did(keyring->contexts[cn]->identities[in], did, name))
r=WHY("Could not set DID");
else{
if (keyring_commit(keyring))
r=WHY("Could not write updated keyring record");
else{
cli_output_identity(context, keyring->contexts[cn]->identities[in]);
}
}
}
keyring_free(keyring);
keyring = NULL;
return r;
}
DEFINE_CMD(app_keyring_set_tag, 0,
"Set a named tag for the specified SID (must supply PIN to unlock the SID record in the keyring)",
"keyring", "set","tag" KEYRING_PIN_OPTIONS,"<sid>","<tag>","<value>");
static int app_keyring_set_tag(const struct cli_parsed *parsed, struct cli_context *context)
{
const char *sidhex, *tag, *value;
if (cli_arg(parsed, "sid", &sidhex, str_is_subscriber_id, "") == -1 ||
cli_arg(parsed, "tag", &tag, NULL, "") == -1 ||
cli_arg(parsed, "value", &value, NULL, "") == -1 )
return -1;
if (!(keyring = keyring_open_instance_cli(parsed)))
return -1;
sid_t sid;
if (str_to_sid_t(&sid, sidhex) == -1)
return WHY("str_to_sid_t() failed");
unsigned cn=0, in=0, kp=0;
int r=0;
if (!keyring_find_sid(keyring, &cn, &in, &kp, &sid))
r=WHY("No matching SID");
else{
int length = strlen(value);
if (keyring_set_public_tag(keyring->contexts[cn]->identities[in], tag, (const unsigned char*)value, length))
r=WHY("Could not set tag value");
else{
if (keyring_commit(keyring))
r=WHY("Could not write updated keyring record");
else{
cli_output_identity(context, keyring->contexts[cn]->identities[in]);
}
}
}
keyring_free(keyring);
keyring = NULL;
return r;
}
static int handle_pins(const struct cli_parsed *parsed, struct cli_context *UNUSED(context), int revoke)
{
const char *pin, *sid_hex;
if (cli_arg(parsed, "entry-pin", &pin, NULL, "") == -1 ||
cli_arg(parsed, "sid", &sid_hex, str_is_subscriber_id, "") == -1)
return -1;
int ret=1;
struct mdp_header header={
.remote.port=MDP_IDENTITY,
};
int mdp_sock = mdp_socket();
set_nonblock(mdp_sock);
unsigned char request_payload[1200];
struct mdp_identity_request *request = (struct mdp_identity_request *)request_payload;
if (revoke){
request->action=ACTION_LOCK;
}else{
request->action=ACTION_UNLOCK;
}
size_t len = sizeof(struct mdp_identity_request);
if (pin && *pin) {
request->type=TYPE_PIN;
size_t pin_siz = strlen(pin) + 1;
if (pin_siz + len > sizeof(request_payload))
return WHY("Supplied pin is too long");
bcopy(pin, &request_payload[len], pin_siz);
len += pin_siz;
}else if(sid_hex && *sid_hex){
request->type=TYPE_SID;
sid_t sid;
if (str_to_sid_t(&sid, sid_hex) == -1)
return WHY("str_to_sid_t() failed");
bcopy(sid.binary, &request_payload[len], sizeof(sid));
len += sizeof(sid);
}
if (mdp_send(mdp_sock, &header, request_payload, len) == -1)
goto end;
time_ms_t timeout=gettime_ms()+500;
while(1){
struct mdp_header rev_header;
unsigned char response_payload[1600];
ssize_t len = mdp_poll_recv(mdp_sock, timeout, &rev_header, response_payload, sizeof(response_payload));
if (len==-1)
break;
if (len==-2){
WHYF("Timeout while waiting for response");
break;
}
if (rev_header.flags & MDP_FLAG_CLOSE){
ret=0;
break;
}
}
end:
mdp_close(mdp_sock);
return ret;
}
DEFINE_CMD(app_revoke_pin, 0,
"Unload any identities protected by this pin and drop all routes to them",
"id", "relinquish", "pin", "<entry-pin>");
DEFINE_CMD(app_revoke_pin, 0,
"Unload a specific identity and drop all routes to it",
"id", "relinquish", "sid", "<sid>");
int app_revoke_pin(const struct cli_parsed *parsed, struct cli_context *context)
{
return handle_pins(parsed, context, 1);
}
DEFINE_CMD(app_id_pin, 0,
"Unlock any pin protected identities and enable routing packets to them",
"id", "enter", "pin", "<entry-pin>");
static int app_id_pin(const struct cli_parsed *parsed, struct cli_context *context)
{
return handle_pins(parsed, context, 0);
}
DEFINE_CMD(app_id_list, 0,
"Search unlocked identities based on an optional tag and value",
"id", "list", "[<tag>]", "[<value>]");
static int app_id_list(const struct cli_parsed *parsed, struct cli_context *context)
{
const char *tag, *value;
if (cli_arg(parsed, "tag", &tag, NULL, "") == -1 ||
cli_arg(parsed, "value", &value, NULL, "") == -1 )
return -1;
int ret=-1;
struct mdp_header header={
.remote.port=MDP_SEARCH_IDS,
};
int mdp_sock = mdp_socket();
set_nonblock(mdp_sock);
unsigned char request_payload[1200];
size_t len=0;
if (tag && *tag){
size_t value_len=0;
if (value && *value)
value_len = strlen(value);
len = sizeof(request_payload);
if (keyring_pack_tag(request_payload, &len, tag, (unsigned char*)value, value_len))
goto end;
}
if (mdp_send(mdp_sock, &header, request_payload, len) == -1)
goto end;
const char *names[]={
"sid"
};
cli_columns(context, 1, names);
size_t rowcount=0;
time_ms_t timeout=gettime_ms()+500;
while(1){
struct mdp_header rev_header;
unsigned char response_payload[1600];
ssize_t len = mdp_poll_recv(mdp_sock, timeout, &rev_header, response_payload, sizeof(response_payload));
if (len==-1)
break;
if (len==-2){
WHYF("Timeout while waiting for response");
break;
}
if (len>=SID_SIZE){
rowcount++;
sid_t *id = (sid_t*)response_payload;
cli_put_hexvalue(context, id->binary, sizeof(sid_t), "\n");
// TODO receive and decode other details about this identity
}
if (rev_header.flags & MDP_FLAG_CLOSE){
ret=0;
break;
}
}
cli_row_count(context, rowcount);
end:
mdp_close(mdp_sock);
return ret;
}

View File

@ -161,6 +161,25 @@ int _mdp_poll(struct __sourceloc UNUSED(__whence), int socket, time_ms_t timeout
return overlay_mdp_client_poll(socket, timeout_ms);
}
// returns -1 on error, -2 on timeout, packet length on success.
ssize_t mdp_poll_recv(int mdp_sock, time_ms_t deadline, struct mdp_header *rev_header, unsigned char *payload, size_t buffer_size)
{
time_ms_t now = gettime_ms();
if (now > deadline)
return -2;
int p = mdp_poll(mdp_sock, deadline - now);
if (p == -1)
return WHY_perror("mdp_poll");
if (p == 0)
return -2;
ssize_t len = mdp_recv(mdp_sock, rev_header, payload, buffer_size);
if (len == -1)
return -1;
if (rev_header->flags & MDP_FLAG_ERROR)
return WHY("Operation failed, check the daemon log for more information");
return len;
}
int overlay_mdp_send(int mdp_sockfd, overlay_mdp_frame *mdp, int flags, int timeout_ms)
{
if (mdp_sockfd == -1)

View File

@ -144,6 +144,7 @@ int _mdp_close(struct __sourceloc, int socket);
int _mdp_send(struct __sourceloc, int socket, const struct mdp_header *header, const uint8_t *payload, size_t len);
ssize_t _mdp_recv(struct __sourceloc, int socket, struct mdp_header *header, uint8_t *payload, size_t max_len);
int _mdp_poll(struct __sourceloc, int socket, time_ms_t timeout_ms);
ssize_t mdp_poll_recv(int mdp_sock, time_ms_t deadline, struct mdp_header *rev_header, unsigned char *payload, size_t buffer_size);
#define mdp_socket() _mdp_socket(__WHENCE__)
#define mdp_close(s) _mdp_close(__WHENCE__, (s))
#define mdp_send(s,h,p,l) _mdp_send(__WHENCE__, (s), (h), (p), (l))

View File

@ -27,6 +27,7 @@
#include "conf.h"
#include "mem.h"
#include "str.h"
#include "server.h"
//#define DEBUG_MDP_FILTER_PARSING 1

526
network_cli.c Normal file
View File

@ -0,0 +1,526 @@
/*
Serval network command line functions
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 <unistd.h>
#include <stdint.h>
#include <signal.h>
#include <math.h>
#include "dataformats.h"
#include "mdp_client.h"
#include "conf.h"
#include "cli.h"
#include "commandline.h"
#include "sighandlers.h"
DEFINE_CMD(app_mdp_ping, 0,
"Attempts to ping specified node via Mesh Datagram Protocol (MDP).",
"mdp","ping","[--interval=<ms>]","[--timeout=<seconds>]","[--wait-for-duplicates]",
"<SID>|broadcast","[<count>]");
static int app_mdp_ping(const struct cli_parsed *parsed, struct cli_context *context)
{
int mdp_sockfd;
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
const char *sidhex, *count, *opt_timeout, *opt_interval;
int opt_wait_for_duplicates = 0 == cli_arg(parsed, "--wait-for-duplicates", NULL, NULL, NULL);
if ( cli_arg(parsed, "--timeout", &opt_timeout, cli_interval_ms, "1") == -1
|| cli_arg(parsed, "--interval", &opt_interval, cli_interval_ms, "1") == -1
|| cli_arg(parsed, "SID", &sidhex, str_is_subscriber_id, "broadcast") == -1
|| cli_arg(parsed, "count", &count, cli_uint, "0") == -1)
return -1;
/* Get SID that we want to ping.
TODO - allow lookup of SID prefixes and telephone numbers
(that would require MDP lookup of phone numbers, which doesn't yet occur) */
sid_t ping_sid;
if (str_to_sid_t(&ping_sid, sidhex) == -1)
return WHY("str_to_sid_t() failed");
// assume we wont hear any responses
int ret=1;
unsigned icount = atoi(count);
int64_t timeout_ms = 1000;
str_to_uint64_interval_ms(opt_timeout, &timeout_ms, NULL);
if (timeout_ms == 0)
timeout_ms = 60 * 60000; // 1 hour...
int64_t interval_ms = 1000;
str_to_uint64_interval_ms(opt_interval, &interval_ms, NULL);
if (interval_ms == 0)
interval_ms = 1000;
/* First sequence number in the echo frames */
uint32_t firstSeq = random();
uint32_t sequence_number = firstSeq;
int broadcast = is_sid_t_broadcast(ping_sid);
/* Bind to MDP socket and await confirmation */
if ((mdp_sockfd = mdp_socket()) < 0)
return WHY("Cannot create MDP socket");
set_nonblock(mdp_sockfd);
struct mdp_header mdp_header;
bzero(&mdp_header, sizeof(mdp_header));
mdp_header.local.sid = BIND_PRIMARY;
mdp_header.remote.sid = ping_sid;
mdp_header.remote.port = MDP_PORT_ECHO;
mdp_header.qos = OQ_MESH_MANAGEMENT;
mdp_header.ttl = PAYLOAD_TTL_DEFAULT;
mdp_header.flags = MDP_FLAG_BIND;
if (broadcast)
mdp_header.flags |= MDP_FLAG_NO_CRYPT;
/* TODO Eventually we should try to resolve SID to phone number and vice versa */
cli_printf(context, "MDP PING %s: 12 data bytes", alloca_tohex_sid_t(ping_sid));
cli_delim(context, "\n");
cli_flush(context);
unsigned tx_count = 0;
unsigned missing_pong_count = 0;
unsigned rx_count = 0;
unsigned rx_dupcount = 0;
unsigned rx_igncount = 0;
time_ms_t rx_mintime_ms = -1;
time_ms_t rx_maxtime_ms = -1;
time_ms_t rx_tottime_ms = 0;
struct packet_stat {
uint32_t sequence;
time_ms_t tx_time;
time_ms_t rx_time;
unsigned pong_count;
} stats[1024];
bzero(stats, sizeof stats);
if (broadcast)
WARN("broadcast ping packets will not be encrypted");
sigIntFlag = 0;
signal(SIGINT, sigIntHandler);
while (!sigIntFlag && (icount == 0 || tx_count < icount)) {
time_ms_t now = gettime_ms();
// send a ping packet
if (tx_count == 0 || !(mdp_header.flags & MDP_FLAG_BIND)) {
uint8_t payload[12];
write_uint32(&payload[0], sequence_number);
write_uint64(&payload[4], now);
int r = mdp_send(mdp_sockfd, &mdp_header, payload, sizeof(payload));
if (r != -1) {
if (config.debug.mdprequests)
DEBUGF("ping seq=%lu", (unsigned long)(sequence_number - firstSeq) + 1);
unsigned i = (unsigned long)(sequence_number - firstSeq) % NELS(stats);
assert(i == tx_count % NELS(stats));
struct packet_stat *stat = &stats[i];
if (stat->tx_time && stat->pong_count == 0) {
assert(missing_pong_count > 0);
--missing_pong_count;
}
stat->sequence = sequence_number;
stat->tx_time = now;
stat->pong_count = 0;
++missing_pong_count;
++sequence_number;
++tx_count;
}
}
// Now look for replies ("pongs") until one second has passed, and print any replies with
// appropriate information as required
int all_sent = icount && tx_count >= icount;
time_ms_t finish = now + (all_sent ? timeout_ms : interval_ms);
while (!sigIntFlag && now < finish && (!all_sent || opt_wait_for_duplicates || missing_pong_count)) {
time_ms_t poll_timeout_ms = finish - now;
if (mdp_poll(mdp_sockfd, poll_timeout_ms) <= 0) {
now = gettime_ms();
continue;
}
struct mdp_header mdp_recv_header;
uint8_t recv_payload[12];
ssize_t len = mdp_recv(mdp_sockfd, &mdp_recv_header, recv_payload, sizeof(recv_payload));
if (len == -1)
break;
if (mdp_recv_header.flags & MDP_FLAG_ERROR) {
WHY("error from daemon, please check the log for more information");
continue;
}
if (mdp_recv_header.flags & MDP_FLAG_BIND){
// received port binding confirmation
mdp_header.local = mdp_recv_header.local;
mdp_header.flags &= ~MDP_FLAG_BIND;
if (config.debug.mdprequests)
DEBUGF("bound to %s:%d", alloca_tohex_sid_t(mdp_header.local.sid), mdp_header.local.port);
continue;
}
if ((size_t)len < sizeof(recv_payload)){
if (config.debug.mdprequests)
DEBUGF("ignoring short pong");
continue;
}
uint32_t rxseq = read_uint32(&recv_payload[0]);
time_ms_t txtime = read_uint64(&recv_payload[4]);
int hop_count = 64 - mdp_recv_header.ttl;
now = gettime_ms();
time_ms_t delay = now - txtime;
struct packet_stat *stat = &stats[(unsigned long)(rxseq - firstSeq) % NELS(stats)];
if (stat->sequence != rxseq || stat->tx_time != txtime) {
if (config.debug.mdprequests)
DEBUGF("ignoring spurious pong");
++rx_igncount;
stat = NULL; // old or corrupted reply (either sequence or txtime is wrong)
} else if (stat->pong_count++ == 0) {
assert(missing_pong_count > 0);
--missing_pong_count;
stat->rx_time = now;
rx_tottime_ms += delay;
++rx_count;
if (rx_mintime_ms > delay || rx_mintime_ms == -1)
rx_mintime_ms = delay;
if (delay > rx_maxtime_ms)
rx_maxtime_ms = delay;
} else
++rx_dupcount;
cli_put_hexvalue(context, mdp_recv_header.remote.sid.binary, SID_SIZE, ": seq=");
cli_put_long(context, (unsigned long)(rxseq - firstSeq) + 1, " time=");
cli_put_long(context, delay, "ms hops=");
cli_put_long(context, hop_count, "");
cli_put_string(context, (mdp_recv_header.flags & MDP_FLAG_NO_CRYPT) ? "" : " ENCRYPTED", "");
cli_put_string(context, (mdp_recv_header.flags & MDP_FLAG_NO_SIGN) ? "" : " SIGNED", "\n");
cli_flush(context);
ret=0;
}
}
signal(SIGINT, SIG_DFL);
sigIntFlag = 0;
mdp_close(mdp_sockfd);
{
float rx_stddev = 0;
float rx_mean = rx_tottime_ms * 1.0 / rx_count;
unsigned tx_samples = tx_count < NELS(stats) ? tx_count : NELS(stats);
unsigned rx_samples = 0;
unsigned i;
for (i = 0; i < tx_samples; ++i) {
struct packet_stat *stat = &stats[i];
if (stat->pong_count) {
float dev = rx_mean - (stat->rx_time - stat->tx_time);
rx_stddev += dev * dev;
++rx_samples;
}
}
rx_stddev /= rx_samples;
rx_stddev = sqrtf(rx_stddev);
/* XXX Report final statistics before going */
cli_printf(context, "--- %s ping statistics ---\n", alloca_tohex_sid_t(ping_sid));
cli_printf(context, "%u packets transmitted, %u packets received (plus %u duplicates, %u ignored), %3.1f%% packet loss\n",
tx_count,
rx_count,
rx_dupcount,
rx_igncount,
tx_count ? (tx_count - rx_count) * 100.0 / tx_count : 0
);
if (rx_samples)
cli_printf(context, "round-trip min/avg/max/stddev = %"PRId64"/%.3f/%"PRId64"/%.3f ms (%u samples)\n",
rx_mintime_ms, rx_mean, rx_maxtime_ms, rx_stddev, rx_samples);
cli_delim(context, NULL);
cli_flush(context);
}
return ret;
}
DEFINE_CMD(app_trace, 0,
"Trace through the network to the specified node via MDP.",
"mdp","trace","<SID>");
static int app_trace(const struct cli_parsed *parsed, struct cli_context *context)
{
int mdp_sockfd;
const char *sidhex;
if (cli_arg(parsed, "SID", &sidhex, str_is_subscriber_id, NULL) == -1)
return -1;
sid_t srcsid;
sid_t dstsid;
if (str_to_sid_t(&dstsid, sidhex) == -1)
return WHY("str_to_sid_t() failed");
if ((mdp_sockfd = overlay_mdp_client_socket()) < 0)
return WHY("Cannot create MDP socket");
mdp_port_t port=32768+(random()&32767);
if (overlay_mdp_getmyaddr(mdp_sockfd, 0, &srcsid)) {
overlay_mdp_client_close(mdp_sockfd);
return WHY("Could not get local address");
}
if (overlay_mdp_bind(mdp_sockfd, &srcsid, port)) {
overlay_mdp_client_close(mdp_sockfd);
return WHY("Could not bind to MDP socket");
}
overlay_mdp_frame mdp;
bzero(&mdp, sizeof(mdp));
mdp.out.src.sid = srcsid;
mdp.out.dst.sid = srcsid;
mdp.out.src.port=port;
mdp.out.dst.port=MDP_PORT_TRACE;
mdp.packetTypeAndFlags=MDP_TX;
struct overlay_buffer *b = ob_static(mdp.out.payload, sizeof(mdp.out.payload));
ob_append_byte(b, SID_SIZE);
ob_append_bytes(b, srcsid.binary, SID_SIZE);
ob_append_byte(b, SID_SIZE);
ob_append_bytes(b, dstsid.binary, SID_SIZE);
int ret;
if (ob_overrun(b))
ret = WHY("overlay buffer overrun");
else {
mdp.out.payload_length = ob_position(b);
cli_printf(context, "Tracing the network path from %s to %s",
alloca_tohex_sid_t(srcsid), alloca_tohex_sid_t(dstsid));
cli_delim(context, "\n");
cli_flush(context);
ret = overlay_mdp_send(mdp_sockfd, &mdp, MDP_AWAITREPLY, 5000);
if (ret)
WHYF("overlay_mdp_send returned %d", ret);
}
ob_free(b);
if (ret == 0) {
int offset=0;
{
// skip the first two sid's
int len = mdp.out.payload[offset++];
offset+=len;
len = mdp.out.payload[offset++];
offset+=len;
}
int i=0;
while(offset<mdp.out.payload_length){
int len = mdp.out.payload[offset++];
cli_put_long(context, i, ":");
cli_put_string(context, alloca_tohex(&mdp.out.payload[offset], len), "\n");
offset+=len;
i++;
}
}
overlay_mdp_client_close(mdp_sockfd);
return ret;
}
DEFINE_CMD(app_id_self, 0,
"Return identity(s) as URIs of own node, or of known routable peers, or all known peers",
"id","self|peers|allpeers");
static int app_id_self(const struct cli_parsed *parsed, struct cli_context *context)
{
int mdp_sockfd;
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
/* List my own identities */
overlay_mdp_frame a;
bzero(&a, sizeof(overlay_mdp_frame));
int result;
a.packetTypeAndFlags=MDP_GETADDRS;
const char *arg = parsed->labelc ? parsed->labelv[0].text : "";
if (!strcasecmp(arg,"self"))
a.addrlist.mode = MDP_ADDRLIST_MODE_SELF; /* get own identities */
else if (!strcasecmp(arg,"allpeers"))
a.addrlist.mode = MDP_ADDRLIST_MODE_ALL_PEERS; /* get all known peers */
else if (!strcasecmp(arg,"peers"))
a.addrlist.mode = MDP_ADDRLIST_MODE_ROUTABLE_PEERS; /* get routable (reachable) peers */
else
return WHYF("unsupported arg '%s'", arg);
a.addrlist.first_sid=0;
if ((mdp_sockfd = overlay_mdp_client_socket()) < 0)
return WHY("Cannot create MDP socket");
const char *names[]={
"sid"
};
cli_columns(context, 1, names);
size_t rowcount=0;
do{
result=overlay_mdp_send(mdp_sockfd, &a, MDP_AWAITREPLY, 5000);
if (result) {
if (a.packetTypeAndFlags==MDP_ERROR){
WHYF(" MDP Server error #%d: '%s'",
a.error.error,a.error.message);
} else
WHYF("Could not get list of local MDP addresses");
overlay_mdp_client_close(mdp_sockfd);
return WHY("Failed to get local address list");
}
if ((a.packetTypeAndFlags&MDP_TYPE_MASK)!=MDP_ADDRLIST) {
overlay_mdp_client_close(mdp_sockfd);
return WHY("MDP Server returned something other than an address list");
}
unsigned i;
for(i=0;i<a.addrlist.frame_sid_count;i++) {
rowcount++;
cli_put_string(context, alloca_tohex_sid_t(a.addrlist.sids[i]), "\n");
}
/* get ready to ask for next block of SIDs */
a.packetTypeAndFlags=MDP_GETADDRS;
a.addrlist.first_sid=a.addrlist.last_sid+1;
}while(a.addrlist.frame_sid_count==MDP_MAX_SID_REQUEST);
cli_row_count(context, rowcount);
overlay_mdp_client_close(mdp_sockfd);
return 0;
}
DEFINE_CMD(app_count_peers, 0,
"Return a count of routable peers on the network",
"peer","count");
static int app_count_peers(const struct cli_parsed *parsed, struct cli_context *context)
{
int mdp_sockfd;
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
if ((mdp_sockfd = overlay_mdp_client_socket()) < 0)
return WHY("Cannot create MDP socket");
overlay_mdp_frame a;
bzero(&a, sizeof(overlay_mdp_frame));
a.packetTypeAndFlags=MDP_GETADDRS;
a.addrlist.mode = MDP_ADDRLIST_MODE_ROUTABLE_PEERS;
a.addrlist.first_sid = OVERLAY_MDP_ADDRLIST_MAX_SID_COUNT;
int ret=overlay_mdp_send(mdp_sockfd, &a,MDP_AWAITREPLY,5000);
overlay_mdp_client_close(mdp_sockfd);
if (ret){
if (a.packetTypeAndFlags==MDP_ERROR)
return WHYF(" MDP Server error #%d: '%s'",a.error.error,a.error.message);
return WHYF("Failed to send request");
}
cli_put_long(context, a.addrlist.server_sid_count, "\n");
return 0;
}
DEFINE_CMD(app_route_print, 0,
"Print the routing table",
"route","print");
static int app_route_print(const struct cli_parsed *parsed, struct cli_context *context)
{
int mdp_sockfd;
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
if ((mdp_sockfd = overlay_mdp_client_socket()) < 0)
return WHY("Cannot create MDP socket");
overlay_mdp_frame mdp;
bzero(&mdp,sizeof(mdp));
mdp.packetTypeAndFlags=MDP_ROUTING_TABLE;
overlay_mdp_send(mdp_sockfd, &mdp,0,0);
const char *names[]={
"Subscriber id",
"Routing flags",
"Interface",
"Next hop"
};
cli_columns(context, 4, names);
size_t rowcount=0;
while(overlay_mdp_client_poll(mdp_sockfd, 200)){
overlay_mdp_frame rx;
int ttl;
if (overlay_mdp_recv(mdp_sockfd, &rx, 0, &ttl))
continue;
int ofs=0;
while(ofs + sizeof(struct overlay_route_record) <= rx.out.payload_length){
struct overlay_route_record *p=(struct overlay_route_record *)&rx.out.payload[ofs];
ofs+=sizeof(struct overlay_route_record);
if (p->reachable==REACHABLE_NONE)
continue;
cli_put_string(context, alloca_tohex_sid_t(p->sid), ":");
char flags[32];
strbuf b = strbuf_local(flags, sizeof flags);
switch (p->reachable){
case REACHABLE_SELF:
strbuf_puts(b, "SELF");
break;
case REACHABLE_BROADCAST:
strbuf_puts(b, "BROADCAST");
break;
case REACHABLE_UNICAST:
strbuf_puts(b, "UNICAST");
break;
case REACHABLE_INDIRECT:
strbuf_puts(b, "INDIRECT");
break;
default:
strbuf_sprintf(b, "%d", p->reachable);
}
cli_put_string(context, strbuf_str(b), ":");
cli_put_string(context, p->interface_name, ":");
cli_put_string(context, alloca_tohex_sid_t(p->neighbour), "\n");
rowcount++;
}
}
overlay_mdp_client_close(mdp_sockfd);
cli_row_count(context, rowcount);
return 0;
}
DEFINE_CMD(app_network_scan, 0,
"Scan the network for serval peers. If no argument is supplied, all local addresses will be scanned.",
"scan","[<address>]");
static int app_network_scan(const struct cli_parsed *parsed, struct cli_context *context)
{
int mdp_sockfd;
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
overlay_mdp_frame mdp;
bzero(&mdp,sizeof(mdp));
mdp.packetTypeAndFlags=MDP_SCAN;
struct overlay_mdp_scan *scan = (struct overlay_mdp_scan *)&mdp.raw;
const char *address;
if (cli_arg(parsed, "address", &address, NULL, NULL) == -1)
return -1;
if (address){
if (!inet_aton(address, &scan->addr))
return WHY("Unable to parse the address");
}else
INFO("Scanning local networks");
if ((mdp_sockfd = overlay_mdp_client_socket()) < 0)
return WHY("Cannot create MDP socket");
overlay_mdp_send(mdp_sockfd, &mdp, MDP_AWAITREPLY, 5000);
overlay_mdp_client_close(mdp_sockfd);
if (mdp.packetTypeAndFlags!=MDP_ERROR)
return -1;
cli_put_string(context, mdp.error.message, "\n");
return mdp.error.error;
}

76
nonce.c
View File

@ -1,76 +0,0 @@
/*
Copyright (C) 2013 Paul Gardner-Stephen
Copyright (C) 2013 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 "constants.h"
#include "os.h"
#include "commandline.h"
int nonce_initialised=0;
unsigned char nonce_buffer[128];
int generate_nonce(unsigned char *nonce,int bytes)
{
if (bytes<1||bytes>128) return -1;
start:
if (!nonce_initialised) {
if (urandombytes(nonce_buffer,128))
return -1;
nonce_initialised=1;
}
// Increment nonce
int i;
for(i=0;i<128;i++)
{
unsigned char b=nonce_buffer[i]+1;
nonce_buffer[i]=b;
if (b) break;
}
if (i>=128) {
nonce_initialised=0;
goto start;
}
bcopy(nonce_buffer,nonce,bytes);
return 0;
}
DEFINE_CMD(app_nonce_test, 0,
"Run nonce generation test",
"test","nonce");
static int app_nonce_test(const struct cli_parsed *UNUSED(parsed), struct cli_context *context)
{
int i,j;
unsigned char nonces[0x10001][32];
for(i=0;i<0x10001;i++)
{
if (generate_nonce(&nonces[i][0],32))
return WHYF("Failed to generate nonce #%d\n",i);
for(j=0;j<i;j++) {
if (!memcmp(&nonces[i][0],&nonces[j][0],32))
return WHYF("Nonce #%d is the same as nonce #%d\n",i,j);
}
if (!(random()&0xff))
cli_printf(context, "Nonce #%d = %02x%02x%02x%02x...\n",
i,nonces[i][0],nonces[i][1],nonces[i][2],nonces[i][3]);
}
cli_printf(context, "Test passed\n");
return 0;
}

View File

@ -35,6 +35,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "overlay_buffer.h"
#include "overlay_interface.h"
#include "overlay_packet.h"
#include "server.h"
#define MAX_BPIS 1024
#define BPI_MASK 0x3ff

View File

@ -37,6 +37,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "overlay_packet.h"
#include "str.h"
#include "radio_link.h"
#include "server.h"
#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>

View File

@ -749,6 +749,30 @@ void overlay_mdp_encode_ports(struct overlay_buffer *plaintext, mdp_port_t dst_p
ob_append_packed_ui32(plaintext, src_port);
}
static int nonce_initialised=0;
static uint8_t nonce_buffer[128];
static int generate_nonce(uint8_t *nonce, size_t bytes)
{
if (bytes<1||bytes>128) return -1;
if (!nonce_initialised) {
if (urandombytes(nonce_buffer,128))
return -1;
nonce_initialised=1;
}
// Increment nonce
unsigned i;
for(i=0;i<128;i++){
uint8_t b=nonce_buffer[i]+1;
nonce_buffer[i]=b;
if (b) break;
}
bcopy(nonce_buffer,nonce,bytes);
return 0;
}
static struct overlay_buffer * encrypt_payload(
struct subscriber *source,
struct subscriber *dest,

View File

@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "serval.h"
#include "conf.h"
#include "overlay_interface.h"
#include "server.h"
int overlay_packetradio_setup_port(overlay_interface *interface)
{

743
rhizome_cli.c Normal file
View File

@ -0,0 +1,743 @@
/*
Serval Rhizome command line functions
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, *manifestid, *authorSidHex, *bskhex;
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", &manifestid, NULL, "");
if (cli_arg(parsed, "bsk", &bskhex, cli_optional_bundle_key, NULL) == -1)
return -1;
sid_t authorSid;
if (authorSidHex[0] && str_to_sid_t(&authorSid, authorSidHex) == -1)
return WHYF("invalid author_sid: %s", authorSidHex);
rhizome_bk_t bsk;
// treat empty string the same as null
if (bskhex && !*bskhex)
bskhex=NULL;
if (bskhex && str_to_rhizome_bk_t(&bsk, bskhex) == -1)
return WHYF("invalid bsk: \"%s\"", bskhex);
int journal = strcasecmp(parsed->args[1], "journal")==0;
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;
}
/* Create a new manifest that will represent the file. If a manifest file was supplied, then read
* it, otherwise create a blank manifest. */
rhizome_manifest *m = rhizome_new_manifest();
if (!m){
keyring_free(keyring);
keyring = NULL;
return WHY("Manifest struct could not be allocated -- not added to rhizome");
}
if (manifestpath && *manifestpath && access(manifestpath, R_OK) == 0) {
if (config.debug.rhizome)
DEBUGF("reading manifest from %s", manifestpath);
/* Don't verify the manifest, because it will fail if it is incomplete.
This is okay, because we fill in any missing bits and sanity check before
trying to write it out. However, we do insist that whatever we load is
parsed okay and not malformed. */
if (rhizome_read_manifest_from_file(m, manifestpath) || m->malformed) {
rhizome_manifest_free(m);
keyring_free(keyring);
keyring = NULL;
return WHY("Manifest file could not be loaded -- not added to rhizome");
}
} else if (manifestid && *manifestid) {
if (config.debug.rhizome)
DEBUGF("Reading manifest from database");
rhizome_bid_t bid;
if (str_to_rhizome_bid_t(&bid, manifestid) == -1) {
rhizome_manifest_free(m);
keyring_free(keyring);
keyring = NULL;
return WHYF("Invalid bundle ID: %s", alloca_str_toprint(manifestid));
}
if (rhizome_retrieve_manifest(&bid, m)){
rhizome_manifest_free(m);
keyring_free(keyring);
keyring = NULL;
return WHY("Existing manifest could not be loaded -- not added to rhizome");
}
} else {
if (config.debug.rhizome)
DEBUGF("Creating new manifest");
if (journal) {
rhizome_manifest_set_filesize(m, 0);
rhizome_manifest_set_tail(m, 0);
}
}
if (journal && !m->is_journal){
rhizome_manifest_free(m);
keyring_free(keyring);
keyring = NULL;
return WHY("Existing manifest is not a journal");
}
if (!journal && m->is_journal) {
rhizome_manifest_free(m);
keyring_free(keyring);
keyring = NULL;
return WHY("Existing manifest is a journal");
}
if (bskhex)
rhizome_apply_bundle_secret(m, &bsk);
if (m->service == NULL)
rhizome_manifest_set_service(m, RHIZOME_SERVICE_FILE);
if (rhizome_fill_manifest(m, filepath, *authorSidHex ? &authorSid : NULL)) {
rhizome_manifest_free(m);
keyring_free(keyring);
keyring = NULL;
return -1;
}
enum rhizome_bundle_status status = RHIZOME_BUNDLE_STATUS_NEW;
enum rhizome_payload_status pstatus;
if (journal){
pstatus = rhizome_append_journal_file(m, 0, filepath);
} else {
pstatus = rhizome_stat_payload_file(m, filepath);
assert(m->filesize != RHIZOME_SIZE_UNSET);
if (pstatus == RHIZOME_PAYLOAD_STATUS_NEW) {
assert(m->filesize > 0);
pstatus = rhizome_store_payload_file(m, filepath);
}
}
switch (pstatus) {
case RHIZOME_PAYLOAD_STATUS_EMPTY:
case RHIZOME_PAYLOAD_STATUS_STORED:
case RHIZOME_PAYLOAD_STATUS_NEW:
break;
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_EVICTED:
status = RHIZOME_BUNDLE_STATUS_NO_ROOM;
INFO("Insufficient space to store payload");
break;
case RHIZOME_PAYLOAD_STATUS_ERROR:
status = RHIZOME_BUNDLE_STATUS_ERROR;
break;
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
status = RHIZOME_BUNDLE_STATUS_INCONSISTENT;
break;
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
status = RHIZOME_BUNDLE_STATUS_READONLY;
break;
default:
FATALF("pstatus = %d", pstatus);
}
rhizome_manifest *mout = NULL;
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:
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);
rhizome_manifest_free(m);
keyring_free(keyring);
keyring = NULL;
return status;
}
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:
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, *bskhex;
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", &bskhex, cli_optional_bundle_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;
int ret=0;
rhizome_bid_t bid;
if (str_to_rhizome_bid_t(&bid, manifestid) == -1){
keyring_free(keyring);
keyring = NULL;
return WHY("Invalid manifest ID");
}
// treat empty string the same as null
if (bskhex && !*bskhex)
bskhex=NULL;
rhizome_bk_t bsk;
if (bskhex && str_to_rhizome_bk_t(&bsk, bskhex) == -1){
keyring_free(keyring);
keyring = NULL;
return WHYF("invalid bsk: \"%s\"", bskhex);
}
rhizome_manifest *m = rhizome_new_manifest();
if (m==NULL){
keyring_free(keyring);
keyring = NULL;
return WHY("Out of manifests");
}
ret = rhizome_retrieve_manifest(&bid, m);
if (ret==0){
assert(m->finalised);
if (bskhex)
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);
}
if (m)
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;
}

View File

@ -173,15 +173,6 @@ struct overlay_buffer;
struct overlay_frame;
struct broadcast;
extern int serverMode;
int server_pid();
const char *_server_pidfile_path(struct __sourceloc);
#define server_pidfile_path() (_server_pidfile_path(__WHENCE__))
void server_save_argv(int argc, const char *const *argv);
int server(void);
int server_write_proc_state(const char *path, const char *fmt, ...);
int server_get_proc_state(const char *path, char *buff, size_t buff_len);
void overlay_mdp_clean_socket_files();
int overlay_forward_payload(struct overlay_frame *f);
@ -326,6 +317,4 @@ int link_stop_routing(struct subscriber *subscriber);
int link_has_neighbours();
int link_interface_has_neighbours(struct overlay_interface *interface);
int generate_nonce(unsigned char *nonce,int bytes);
#endif // __SERVAL_DNA__SERVAL_H

277
server.c
View File

@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <libgen.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "serval.h"
#include "conf.h"
@ -35,6 +36,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "overlay_packet.h"
#include "server.h"
#include "keyring.h"
#include "commandline.h"
#define PROC_SUBDIR "proc"
#define PIDFILE_NAME "servald.pid"
@ -49,6 +51,9 @@ static int server_write_pid();
static int server_unlink_pid();
static void signal_handler(int signal);
static void serverCleanUp();
static const char *_server_pidfile_path(struct __sourceloc __whence);
#define server_pidfile_path() (_server_pidfile_path(__WHENCE__))
void server_shutdown_check(struct sched_ent *alarm);
/** Return the PID of the currently running server process, return 0 if there is none.
*/
@ -83,7 +88,8 @@ int server_pid()
return 0;
}
const char *_server_pidfile_path(struct __sourceloc __whence)
#define server_pidfile_path() (_server_pidfile_path(__WHENCE__))
static const char *_server_pidfile_path(struct __sourceloc __whence)
{
if (!pidfile_path[0]) {
if (!FORMF_SERVAL_RUN_PATH(pidfile_path, PIDFILE_NAME))
@ -92,7 +98,7 @@ const char *_server_pidfile_path(struct __sourceloc __whence)
return pidfile_path;
}
int server()
static int server()
{
IN();
serverMode = SERVER_RUNNING;
@ -458,7 +464,7 @@ static void serverCleanUp()
clean_proc();
}
void signal_handler(int signal)
static void signal_handler(int signal)
{
switch (signal) {
case SIGHUP:
@ -480,3 +486,268 @@ void signal_handler(int signal)
serverCleanUp();
exit(0);
}
DEFINE_CMD(app_server_start, 0,
"Start daemon with instance path from SERVALINSTANCE_PATH environment variable.",
"start" KEYRING_PIN_OPTIONS, "[foreground|exec <path>]");
static int app_server_start(const struct cli_parsed *parsed, struct cli_context *context)
{
IN();
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
/* Process optional arguments */
int pid=-1;
int cpid=-1;
const char *execpath;
if (cli_arg(parsed, "exec", &execpath, cli_absolute_path, NULL) == -1)
RETURN(-1);
int foregroundP = cli_arg(parsed, "foreground", NULL, NULL, NULL) == 0;
#ifdef HAVE_JNI_H
if (context && context->jni_env && execpath == NULL)
RETURN(WHY("Must supply \"exec <path>\" arguments when invoked via JNI"));
#endif
/* Create the instance directory if it does not yet exist */
if (create_serval_instance_dir() == -1)
RETURN(-1);
/* Now that we know our instance path, we can ask for the default set of
network interfaces that we will take interest in. */
if (config.interfaces.ac == 0)
NOWHENCE(WARN("No network interfaces configured (empty 'interfaces' config option)"));
if (pid == -1)
pid = server_pid();
if (pid < 0)
RETURN(-1);
int ret = -1;
// If the pidfile identifies this process, it probably means we are re-spawning after a SEGV, so
// go ahead and do the fork/exec.
if (pid > 0 && pid != getpid()) {
WARNF("Server already running (pid=%d)", pid);
ret = 10;
} else {
if (foregroundP)
INFOF("Foreground server process %s", execpath ? execpath : "without exec");
else
INFOF("Starting background server %s", execpath ? execpath : "without exec");
/* Start the Serval process. All server settings will be read by the server process from the
instance directory when it starts up. */
// Open the keyring and ensure it contains at least one unlocked identity.
keyring = keyring_open_instance_cli(parsed);
if (!keyring)
RETURN(WHY("Could not open keyring file"));
if (keyring_seed(keyring) == -1) {
WHY("Could not seed keyring");
goto exit;
}
if (foregroundP) {
ret = server();
goto exit;
}
const char *dir = getenv("SERVALD_SERVER_CHDIR");
if (!dir)
dir = config.server.chdir;
switch (cpid = fork()) {
case -1:
/* Main process. Fork failed. There is no child process. */
WHY_perror("fork");
goto exit;
case 0: {
/* Child process. Fork then exit, to disconnect daemon from parent process, so that
when daemon exits it does not live on as a zombie. N.B. On Android, do not return from
within this process; that will unroll the JNI call stack and cause havoc -- call _exit()
instead (not exit(), because we want to avoid any Java atexit(3) callbacks as well). If
_exit() is used on non-Android systems, then source code coverage does not get reported,
because it relies on an atexit() callback to write the accumulated counters into .gcda
files. */
#ifdef ANDROID
# define EXIT_CHILD(n) _exit(n)
#else
# define EXIT_CHILD(n) exit(n)
#endif
// Ensure that all stdio streams are flushed before forking, so that if a child calls
// exit(), it will not result in any buffered output being written twice to the file
// descriptor.
fflush(stdout);
fflush(stderr);
switch (fork()) {
case -1:
EXIT_CHILD(WHY_perror("fork"));
case 0: {
/* Grandchild process. Close logfile (so that it gets re-opened again on demand, with
our own file pointer), disable logging to stderr (about to get redirected to
/dev/null), disconnect from current directory, disconnect standard I/O streams, and
start a new process session so that if we are being started by an adb shell session
on an Android device, then we don't receive a SIGHUP when the adb shell process ends.
*/
close_log_file();
disable_log_stderr();
int fd;
if ((fd = open("/dev/null", O_RDWR, 0)) == -1)
EXIT_CHILD(WHY_perror("open(\"/dev/null\")"));
if (setsid() == -1)
EXIT_CHILD(WHY_perror("setsid"));
if (chdir(dir) == -1)
EXIT_CHILD(WHYF_perror("chdir(%s)", alloca_str_toprint(dir)));
if (dup2(fd, 0) == -1)
EXIT_CHILD(WHYF_perror("dup2(%d,0)", fd));
if (dup2(fd, 1) == -1)
EXIT_CHILD(WHYF_perror("dup2(%d,1)", fd));
if (dup2(fd, 2) == -1)
EXIT_CHILD(WHYF_perror("dup2(%d,2)", fd));
if (fd > 2)
(void)close(fd);
/* The execpath option is provided so that a JNI call to "start" can be made which
creates a new server daemon process with the correct argv[0]. Otherwise, the servald
process appears as a process with argv[0] = "org.servalproject". */
if (execpath) {
/* Need the cast on Solaris because it defines NULL as 0L and gcc doesn't see it as a
sentinal. */
execl(execpath, execpath, "start", "foreground", (void *)NULL);
WHYF_perror("execl(%s,\"start\",\"foreground\")", alloca_str_toprint(execpath));
EXIT_CHILD(-1);
}
EXIT_CHILD(server());
// NOT REACHED
}
}
// TODO wait for server_write_pid() to signal more directly?
EXIT_CHILD(0); // Main process is waitpid()-ing for this.
#undef EXIT_CHILD
}
}
/* Main process. Wait for the child process to fork the grandchild and exit. */
waitpid(cpid, NULL, 0);
/* Allow a few seconds for the grandchild process to report for duty. */
time_ms_t timeout = gettime_ms() + 5000;
do {
sleep_ms(200); // 5 Hz
} while ((pid = server_pid()) == 0 && gettime_ms() < timeout);
if (pid == -1)
goto exit;
if (pid == 0) {
WHY("Server process did not start");
goto exit;
}
ret = 0;
}
const char *ipath = instance_path();
if (ipath) {
cli_field_name(context, "instancepath", ":");
cli_put_string(context, ipath, "\n");
}
cli_field_name(context, "pidfile", ":");
cli_put_string(context, server_pidfile_path(), "\n");
cli_field_name(context, "pid", ":");
cli_put_long(context, pid, "\n");
char buff[256];
if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){
cli_field_name(context, "http_port", ":");
cli_put_string(context, buff, "\n");
}
if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){
cli_field_name(context, "mdp_inet_port", ":");
cli_put_string(context, buff, "\n");
}
cli_flush(context);
/* Sleep before returning if env var is set. This is used in testing, to simulate the situation
on Android phones where the "start" command is invoked via the JNI interface and the calling
process does not die.
*/
const char *post_sleep = getenv("SERVALD_START_POST_SLEEP");
if (post_sleep) {
time_ms_t milliseconds = atoi(post_sleep);
INFOF("Sleeping for %"PRId64" milliseconds", (int64_t) milliseconds);
sleep_ms(milliseconds);
}
exit:
keyring_free(keyring);
keyring = NULL;
RETURN(ret);
OUT();
}
DEFINE_CMD(app_server_stop,CLIFLAG_PERMISSIVE_CONFIG,
"Stop a running daemon with instance path from SERVALINSTANCE_PATH environment variable.",
"stop");
static int app_server_stop(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
int pid, tries, running;
time_ms_t timeout;
const char *ipath = instance_path();
if (ipath) {
cli_field_name(context, "instancepath", ":");
cli_put_string(context, ipath, "\n");
}
cli_field_name(context, "pidfile", ":");
cli_put_string(context, server_pidfile_path(), "\n");
pid = server_pid();
/* Not running, nothing to stop */
if (pid <= 0)
return 1;
INFOF("Stopping server (pid=%d)", pid);
/* Set the stop file and signal the process */
cli_field_name(context, "pid", ":");
cli_put_long(context, pid, "\n");
tries = 0;
running = pid;
while (running == pid) {
if (tries >= 5) {
WHYF("Servald pid=%d (pidfile=%s) did not stop after %d SIGHUP signals",
pid, server_pidfile_path(), tries);
return 253;
}
++tries;
if (kill(pid, SIGHUP) == -1) {
// ESRCH means process is gone, possibly we are racing with another stop, or servald just died
// voluntarily. We DO NOT call serverCleanUp() in this case (once used to!) because that
// would race with a starting server process.
if (errno == ESRCH)
break;
WHY_perror("kill");
WHYF("Error sending SIGHUP to Servald pid=%d (pidfile %s)", pid, server_pidfile_path());
return 252;
}
/* Allow a few seconds for the process to die. */
timeout = gettime_ms() + 2000;
do
sleep_ms(200); // 5 Hz
while ((running = server_pid()) == pid && gettime_ms() < timeout);
}
cli_field_name(context, "tries", ":");
cli_put_long(context, tries, "\n");
return 0;
}
DEFINE_CMD(app_server_status,CLIFLAG_PERMISSIVE_CONFIG,
"Display information about running daemon.",
"status");
static int app_server_status(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
int pid = server_pid();
const char *ipath = instance_path();
if (ipath) {
cli_field_name(context, "instancepath", ":");
cli_put_string(context, ipath, "\n");
}
cli_field_name(context, "pidfile", ":");
cli_put_string(context, server_pidfile_path(), "\n");
cli_field_name(context, "status", ":");
cli_put_string(context, pid > 0 ? "running" : "stopped", "\n");
if (pid > 0) {
cli_field_name(context, "pid", ":");
cli_put_long(context, pid, "\n");
char buff[256];
if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){
cli_field_name(context, "http_port", ":");
cli_put_string(context, buff, "\n");
}
if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){
cli_field_name(context, "mdp_inet_port", ":");
cli_put_string(context, buff, "\n");
}
}
return pid > 0 ? 0 : 1;
}

View File

@ -28,4 +28,10 @@ DECLARE_ALARM(server_config_reload);
DECLARE_ALARM(rhizome_sync_announce);
DECLARE_ALARM(fd_periodicstats);
extern int serverMode;
int server_pid();
int server_write_proc_state(const char *path, const char *fmt, ...);
int server_get_proc_state(const char *path, char *buff, size_t buff_len);
#endif // __SERVAL_DNA__SERVER_H

View File

@ -45,6 +45,11 @@ SQLITE3_SOURCES = \
# The source files for building the Serval DNA daemon.
SERVAL_DAEMON_SOURCES = \
commandline.c \
conf_cli.c \
rhizome_cli.c \
keyring_cli.c \
network_cli.c \
test_cli.c \
crypto.c \
directory_client.c \
dna_helper.c \
@ -63,7 +68,6 @@ SERVAL_DAEMON_SOURCES = \
monitor.c \
monitor-client.c \
monitor-cli.c \
nonce.c \
overlay_address.c \
overlay_buffer.c \
overlay_interface.c \

262
test_cli.c Normal file
View File

@ -0,0 +1,262 @@
/*
Serval testing command line functions
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 "serval_types.h"
#include "dataformats.h"
#include "os.h"
#include "cli.h"
#include "conf.h"
#include "commandline.h"
DEFINE_CMD(app_byteorder_test, 0,
"Run byte order handling test",
"test","byteorder");
static int app_byteorder_test(const struct cli_parsed *UNUSED(parsed), struct cli_context *UNUSED(context))
{
uint64_t in=0x1234;
uint64_t out;
unsigned char bytes[8];
write_uint64(&bytes[0],in);
out=read_uint64(&bytes[0]);
if (in!=out)
cli_printf(context,"Byte order mangled (0x%016"PRIx64" should have been %016"PRIx64")\n",
out,in);
else cli_printf(context,"Byte order preserved.\n");
return -1;
}
DEFINE_CMD(app_crypt_test, 0,
"Run cryptography speed test",
"test","crypt");
static int app_crypt_test(const struct cli_parsed *parsed, struct cli_context *context)
{
if (config.debug.verbose)
DEBUG_cli_parsed(parsed);
unsigned char nonce[crypto_box_curve25519xsalsa20poly1305_NONCEBYTES];
unsigned char k[crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES];
unsigned char plain_block[65536];
urandombytes(nonce,sizeof(nonce));
urandombytes(k,sizeof(k));
int len,i;
cli_printf(context, "Benchmarking CryptoBox Auth-Cryption:\n");
int count=1024;
for(len=16;len<=16384;len*=2) {
time_ms_t start = gettime_ms();
for (i=0;i<count;i++) {
bzero(&plain_block[0],crypto_box_curve25519xsalsa20poly1305_ZEROBYTES);
crypto_box_curve25519xsalsa20poly1305_afternm
(plain_block,plain_block,len,nonce,k);
}
time_ms_t end = gettime_ms();
double each=(end - start) * 1.0 / i;
cli_printf(context, "%d bytes - %d tests took %"PRId64"ms - mean time = %.2fms\n",
len, i, (int64_t)(end - start), each);
/* Auto-reduce number of repeats so that it doesn't take too long on the phone */
if (each>1.00) count/=2;
}
cli_printf(context, "Benchmarking CryptoSign signature verification:\n");
{
unsigned char sign_pk[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES];
unsigned char sign_sk[crypto_sign_edwards25519sha512batch_SECRETKEYBYTES];
if (crypto_sign_edwards25519sha512batch_keypair(sign_pk,sign_sk))
return WHY("crypto_sign_curve25519xsalsa20poly1305_keypair() failed.\n");
unsigned char plainTextIn[1024];
unsigned char cipherText[1024];
unsigned char plainTextOut[1024];
unsigned long long cipherLen=0;
unsigned long long plainLenOut;
bzero(plainTextIn,1024);
bzero(cipherText,1024);
snprintf((char *)&plainTextIn[0],1024,"%s","No casaba melons allowed in the lab.");
int plainLenIn=64;
time_ms_t start = gettime_ms();
for(i=0;i<10;i++) {
int r=crypto_sign_edwards25519sha512batch(cipherText,&cipherLen,
plainTextIn,plainLenIn,
sign_sk);
if (r)
return WHY("crypto_sign_edwards25519sha512batch() failed.\n");
}
time_ms_t end=gettime_ms();
cli_printf(context, "mean signature generation time = %.2fms\n",
(end-start)*1.0/i);
start = gettime_ms();
for(i=0;i<10;i++) {
bzero(&plainTextOut,1024); plainLenOut=0;
int r=crypto_sign_edwards25519sha512batch_open(plainTextOut,&plainLenOut,
&cipherText[0],cipherLen,
sign_pk);
if (r)
return WHYF("crypto_sign_edwards25519sha512batch_open() failed (r=%d, i=%d).\n",
r,i);
}
end = gettime_ms();
cli_printf(context, "mean signature verification time = %.2fms\n",
(end-start)*1.0/i);
}
/* We can't do public signing with a crypto_box key, but we should be able to
do shared-secret generation using crypto_sign keys. */
{
cli_printf(context, "Testing supercop-20120525 Ed25519 CryptoSign implementation:\n");
unsigned char sign1_pk[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES];
unsigned char sign1_sk[crypto_sign_edwards25519sha512batch_SECRETKEYBYTES];
if (crypto_sign_edwards25519sha512batch_keypair(sign1_pk,sign1_sk))
return WHY("crypto_sign_edwards25519sha512batch_keypair() failed.\n");
/* Try calculating public key from secret key */
unsigned char pk[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES];
/* New Ed25519 implementation has public key as 2nd half of private key. */
bcopy(&sign1_sk[32],pk,32);
if (memcmp(pk, sign1_pk, crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES)) {
WHY("Could not calculate public key from private key.\n");
dump("calculated",&pk,sizeof(pk));
dump("original",&sign1_pk,sizeof(sign1_pk));
} else
cli_printf(context, "Can calculate public key from private key.\n");
/* Now use a pre-tested keypair and make sure that we can sign and verify with
it, and that the signatures are as expected. */
unsigned char key[64]={
0xf6,0x70,0x6b,0x8a,0x4e,0x1e,0x4b,0x01,
0x11,0x56,0x85,0xac,0x63,0x46,0x67,0x5f,
0xc1,0x44,0xcf,0xdf,0x98,0x5c,0x2b,0x8b,
0x18,0xff,0x70,0x9c,0x12,0x71,0x48,0xb9,
0x32,0x2a,0x88,0xba,0x9c,0xdd,0xed,0x35,
0x8f,0x01,0x18,0xf7,0x60,0x1b,0xfb,0x80,
0xaf,0xce,0x74,0xe0,0x85,0x39,0xac,0x13,
0x15,0xf6,0x79,0xaa,0x68,0xef,0x5d,0xc6};
unsigned char plainTextIn[1024];
unsigned char plainTextOut[1024];
unsigned char cipherText[1024];
unsigned long long cipherLen=0;
unsigned long long plainLenOut;
bzero(plainTextIn,1024);
bzero(cipherText,1024);
snprintf((char *)&plainTextIn[0],1024,"%s","No casaba melons allowed in the lab.");
int plainLenIn=64;
int r=crypto_sign_edwards25519sha512batch(cipherText,&cipherLen,
plainTextIn,plainLenIn,
key);
if (r)
return WHY("crypto_sign_edwards25519sha512batch() failed.\n");
dump("signature",cipherText,cipherLen);
unsigned char casabamelons[128]={
0xa4,0xea,0xd0,0x7f,0x11,0x65,0x28,0x3f,0x90,0x45,0x87,0xbf,0xe5,0xb9,0x15,0x2a,0x9a,0x2d,0x99,0x35,0x0d,0x0e,0x7b,0xb0,0xcd,0x15,0x2e,0xe8,0xeb,0xb3,0xc2,0xb1,0x13,0x8e,0xe3,0x82,0x55,0x6c,0x6e,0x34,0x44,0xe4,0xbc,0xa3,0xd5,0xe0,0x7a,0x6a,0x67,0x61,0xda,0x79,0x67,0xb6,0x1c,0x2e,0x48,0xc7,0x28,0x5b,0xd8,0xd0,0x54,0x0c,0x4e,0x6f,0x20,0x63,0x61,0x73,0x61,0x62,0x61,0x20,0x6d,0x65,0x6c,0x6f,0x6e,0x73,0x20,0x61,0x6c,0x6c,0x6f,0x77,0x65,0x64,0x20,0x69,0x6e,0x20,0x74,0x68,0x65,0x20,0x6c,0x61,0x62,0x2e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
if (cipherLen!=128||memcmp(casabamelons, cipherText, 128)) {
WHY("Computed signature for stored key+message does not match expected value.\n");
dump("expected signature",casabamelons,sizeof(casabamelons));
}
bzero(&plainTextOut,1024); plainLenOut=0;
r=crypto_sign_edwards25519sha512batch_open(plainTextOut,&plainLenOut,
&casabamelons[0],128,
/* the public key, which is the 2nd
half of the secret key. */
&key[32]);
if (r)
WHY("Cannot open rearranged ref/ version of signature.\n");
else
cli_printf(context, "Signature open fine.\n");
}
return 0;
}
void context_switch_test(int);
DEFINE_CMD(app_mem_test, 0,
"Run memory speed test",
"test","memory");
static int app_mem_test(const struct cli_parsed *UNUSED(parsed), struct cli_context *UNUSED(context))
{
size_t mem_size;
size_t addr;
uint64_t count;
// First test context switch speed
context_switch_test(1);
for(mem_size=1024;mem_size<=(128*1024*1024);mem_size*=2) {
uint8_t *mem=malloc(mem_size);
if (!mem) {
fprintf(stderr,"Could not allocate %zdKB memory -- stopping test.\n",mem_size/1024);
return -1;
}
// Fill memory with random stuff so that we don't have memory page-in
// delays when doing the reads
for(addr=0;addr<mem_size;addr++) mem[addr]=random()&0xff;
time_ms_t end_time=gettime_ms()+100;
uint64_t total=0;
size_t mem_mask=mem_size-1;
for(count=0;gettime_ms()<end_time;count++) {
addr=random()&mem_mask;
total+=mem[addr];
}
printf("Memory size = %8zdKB : %"PRId64" random reads per second (irrelevant sum is %016"PRIx64")\n",
mem_size/1024,count*10,
/* use total so that compiler doesn't optimise away our memory accesses */
total);
end_time=gettime_ms()+100;
for(count=0;gettime_ms()<end_time;count++) {
addr=random()&mem_mask;
mem[addr]=3;
}
printf("Memory size = %8zdKB : %"PRId64" random writes per second (irrelevant sum is %016"PRIx64")\n",
mem_size/1024,count*10,
/* use total so that compiler doesn't optimise away our memory accesses */
total);
free(mem);
}
return 0;
}