serval-dna/commandline.c

926 lines
31 KiB
C
Raw Normal View History

/*
Serval Distributed Numbering Architecture (DNA)
Copyright (C) 2010-2012 Paul Gardner-Stephen
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.
*/
#define _GNU_SOURCE // For asprintf()
#include <sys/stat.h>
#include <sys/time.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef HAVE_JNI_H
#include <jni.h>
#endif
#include "serval.h"
#include "rhizome.h"
static int servalNodeRunning(int *pid)
{
char *instancepath = serval_instancepath();
struct stat st;
int r=stat(instancepath,&st);
if (r) {
fprintf(stderr,
"ERROR: Instance path '%s' non existant or not accessable.\n"
" Operating system says: %s (errno=%d)\n",
instancepath,strerror(errno),errno);
fprintf(stderr,
" (Set SERVALINSTANCE_PATH to specify an alternate location.)\n");
return -1;
}
if ((st.st_mode&S_IFMT)!=S_IFDIR) {
fprintf(stderr,
"ERROR: Instance path must be a valid directory.\n"
" '%s' is not a directory.\n",instancepath);
*pid=-1;
return -1;
}
int running=0;
char filename[1024];
if (FORM_SERVAL_INSTANCE_PATH(filename, "serval.pid")) {
FILE *f=fopen(filename,"r");
if (f) {
char line[1024];
line[0]=0; fgets(line,1024,f);
*pid = strtoll(line,NULL,10);
running=*pid;
if (running) {
/* Check that process is really running.
Some systems don't have /proc (including mac),
so we need to find out some otherway.*/
running=1; // assume pid means is running for now
}
fclose(f);
}
}
return running;
}
int cli_usage() {
fprintf(stderr,"\nServal Mesh version <version>.\n");
fprintf(stderr,"Usage:\n");
int i,j;
for(i=0;command_line_options[i].function;i++)
{
for(j=0;command_line_options[i].words[j];j++)
fprintf(stderr," %s",command_line_options[i].words[j]);
fprintf(stderr,"\n %s\n",command_line_options[i].description);
}
return -1;
}
#ifdef HAVE_JNI_H
/* JNI entry point to command line. See org.servalproject.servald.ServalD class for the Java side.
JNI method descriptor: "([Ljava/lang/String;)Lorg/servalproject/servald/ServalDResult;"
*/
JNIEXPORT jobject JNICALL Java_org_servalproject_servald_ServalD_command(JNIEnv *env, jobject this, jobjectArray args)
{
jclass resultClass = NULL;
jclass stringClass = NULL;
jmethodID resultConstructorId = NULL;
jobjectArray outv = NULL;
jint status = 42;
if ((resultClass = (*env)->FindClass(env, "org/servalproject/servald/ServalDResult")) == NULL)
return NULL; // exception
if ((resultConstructorId = (*env)->GetMethodID(env, resultClass, "<init>", "(I[Ljava/lang/String;)V")) == NULL)
return NULL; // exception
if ((stringClass = (*env)->FindClass(env, "java/lang/String")) == NULL)
return NULL; // exception
// Eventually we will return the output buffer, but for now just echo the args.
jsize len = (*env)->GetArrayLength(env, args);
if ((outv = (*env)->NewObjectArray(env, len, stringClass, NULL)) == NULL)
return NULL; // out of memory exception
jsize i;
for (i = 0; i != len; ++i) {
const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i);
const char *str = (*env)->GetStringUTFChars(env, arg, NULL);
if (str == NULL)
return NULL; // out of memory exception
(*env)->SetObjectArrayElement(env, outv, i, (*env)->NewStringUTF(env, str));
(*env)->ReleaseStringUTFChars(env, arg, str);
}
return (*env)->NewObject(env, resultClass, resultConstructorId, status, outv);
}
#endif /* HAVE_JNI_H */
/* args[] excludes command name (unless hardlinks are used to use first words
of command sequences as alternate names of the command. */
int parseCommandLine(int argc, char **args)
{
int i;
int ambiguous=0;
int cli_call=-1;
for(i=0;command_line_options[i].function;i++)
{
int j;
const char *word = NULL;
int optional = 0;
int mandatory = 0;
for (j = 0; (word = command_line_options[i].words[j]); ++j) {
int wordlen = strlen(word);
if (!( (wordlen > 2 && word[0] == '<' && word[wordlen-1] == '>')
|| (wordlen > 4 && word[0] == '[' && word[1] == '<' && word[wordlen-2] == '>' && word[wordlen-1] == ']')
|| (wordlen > 0)
)) {
fprintf(stderr,"Internal error: command_line_options[%d].word[%d]=\"%s\" is malformed\n", i, j, word);
break;
} else if (word[0] == '<') {
++mandatory;
if (optional) {
fprintf(stderr,"Internal error: command_line_options[%d].word[%d]=\"%s\" should be optional\n", i, j, word);
break;
}
} else if (word[0] == '[') {
++optional;
} else {
++mandatory;
if (j < argc && strcasecmp(word, args[j])) // literal words don't match
break;
}
}
if (!word && argc >= mandatory && argc <= mandatory + optional) {
/* A match! We got through the command definition with no internal errors and all literal
args matched and we have a proper number of args. If we have multiple matches, then note
that the call is ambiguous. */
if (cli_call>=0) ambiguous++;
if (ambiguous==1) {
fprintf(stderr,"Ambiguous command line call:\n ");
for(j=0;j<argc;j++) fprintf(stderr," %s",args[j]);
fprintf(stderr,"\nMatches the following known command line calls:\n");
}
if (ambiguous) {
fprintf(stderr," ");
for(j=0;j<argc;j++) fprintf(stderr," %s",command_line_options[i].words[j]);
fprintf(stderr,"\n");
}
cli_call=i;
}
}
/* Don't process ambiguous calls */
if (ambiguous) return -1;
/* Complain if we found no matching calls */
if (cli_call<0) return cli_usage();
/* Otherwise, make call */
setVerbosity(confValueGet("debug",""));
return command_line_options[cli_call].function(argc,args, &command_line_options[cli_call]);
}
int cli_arg(int argc, char **argv, command_line_option *o, char *argname, char **dst, int (*validator)(const char *arg), char *defaultvalue)
{
int arglen = strlen(argname);
int i;
const char *word;
for(i = 0; (word = o->words[i]); ++i) {
int wordlen = strlen(word);
/* No need to check that the "<...>" and "[<...>]" are all intact in the command_line_option,
because that was already checked in parseCommandLine(). */
if (i < argc
&&( (wordlen == arglen + 2 && word[0] == '<' && !strncasecmp(&word[1], argname, arglen))
|| (wordlen == arglen + 4 && word[0] == '[' && !strncasecmp(&word[2], argname, arglen)))
) {
char *value = argv[i];
if (validator && !(*validator)(value)) {
fprintf(stderr, "Invalid argument %d '%s': \"%s\"\n", i, argname, value);
return -1;
}
*dst = value;
return 0;
}
}
/* No matching valid argument was found, so return default value. It might seem that this should
never happen, but it can because more than one version of a command line option may exist, one
with a given argument and another without, and allowing a default value means we can have a
single function handle both in a fairly simple manner. */
*dst = defaultvalue;
return 1;
}
int cli_printf(const char *fmt, ...)
{
va_list ap,ap2;
va_start(ap,fmt);
va_copy(ap2,ap);
//vsnprintf(msg,8192,fmt,ap2);
va_end(ap);
return 0;
}
int app_dna_lookup(int argc,char **argv,struct command_line_option *o)
{
/* Create the instance directory if it does not yet exist */
if (create_serval_instance_dir() == -1)
return -1;
return WHY("Not implemented");
}
int confValueRotor=0;
char confValue[4][128];
char *confValueGet(char *var,char *defaultValue)
{
if (!var) return defaultValue;
int varLen=strlen(var);
char filename[1024];
if (!FORM_SERVAL_INSTANCE_PATH(filename, "serval.conf")) {
fprintf(stderr, "Using default value of %s: %s\n", var, defaultValue);
return defaultValue;
}
FILE *f = fopen(filename,"r");
if (!f) {
fprintf(stderr, "Cannot open serval.conf. Using default value of %s: %s\n", var, defaultValue);
return defaultValue;
}
char line[1024];
line[0]=0; fgets(line,1024,f);
while(line[0]) {
if (!strncasecmp(line,var,varLen)) {
if (line[varLen]=='=') {
fclose(f);
if (strlen(&line[varLen+1])>127) return defaultValue;
/* The rotor is used to pick which of four buffers to return in.
This allows the use of up to four calls to confValueGet() in
a single string formatting exercise, without unexpected side
effect. */
confValueRotor++; confValueRotor&=3;
strcpy(&confValue[confValueRotor][0],&line[varLen+1]);
return &confValue[confValueRotor][0];
}
}
line[0]=0; fgets(line,1024,f);
}
fclose(f);
return defaultValue;
}
int cli_absolute_path(const char *arg)
{
return arg[0] == '/' && arg[1] != '\0';
}
int app_server_start(int argc,char **argv,struct command_line_option *o)
{
/* Process optional arguments */
int foregroundP= (argc >= 3 && !strcasecmp(argv[2], "foreground"));
if (cli_arg(argc, argv, o, "instance path", &thisinstancepath, cli_absolute_path, NULL) == -1)
return -1;
/* 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. */
overlay_interface_args(confValueGet("interfaces",""));
if (strlen(confValueGet("interfaces",""))<1) {
fprintf(stderr,
"WARNING: Noone has told me which network interfaces to listen on.\n"
" You should probably put something in the interfaces setting.\n");
}
int pid=-1;
int running = servalNodeRunning(&pid);
if (running<0) return -1;
if (running>0) {
fprintf(stderr,"ERROR: Serval process already running (pid=%d)\n",pid);
return -1;
}
/* Start the Serval process.
All server settings will be read by the server process from the
instance directory when it starts up.
We can just become the server process ourselves --- no need to fork.
*/
rhizome_datastore_path = serval_instancepath();
rhizome_opendb();
overlayMode=1;
return server(NULL,foregroundP);
}
int app_server_stop(int argc,char **argv,struct command_line_option *o)
{
if (cli_arg(argc, argv, o, "instance path", &thisinstancepath, cli_absolute_path, NULL) == -1)
return -1;
int pid=-1;
int running = servalNodeRunning(&pid);
if (running>0) {
/* Is running, so we can try to kill it.
This is a little complicated by the fact that we catch most signals
so that unexpected aborts just restart.
What we can do is put some code in the signal handler that does abort
the process if a certain file exists, perhaps instance_path/doshutdown,
and removes the file.
*/
if (pid<0) {
WHY("Could not determine process id of Serval process. Stale instance perhaps?");
return -1;
}
char stopfile[1024];
FILE *f;
if (!(FORM_SERVAL_INSTANCE_PATH(stopfile, "doshutdown") && (f = fopen(stopfile, "w")))) {
WHY("Could not create shutdown file");
return -1;
}
fclose(f);
int result=kill(pid,SIGHUP);
if (!result) {
fprintf(stderr,"Stop request sent to Serval process.\n");
} else {
WHY("Could not send SIGHUP to Serval process.");
switch (errno) {
case EINVAL: WHY("This is embarassing, but the operating system says I don't know how to send a signal."); break;
case EPERM: WHY("I don't have permission to stop the Serval process. You could try using sudo, or run the stop command as the appropriate user."); break;
case ESRCH: WHY("The process id I have recorded doesn't seem to exist anymore. Did someone kill the process without telling me?");
/* Clean up any lingering mess */
servalShutdownCleanly();
break;
default:
perror("This is reason given by the operating system");
}
return -1;
}
/* Allow a few seconds for the process to die, and keep an eye on things
while this is happening. */
time_t timeout=time(0)+5;
while(timeout>time(0)) {
pid=-1;
int running = servalNodeRunning(&pid);
if (running<1) {
fprintf(stderr,"Serval process appears to have stopped.\n");
return 0;
}
}
return WHY("I tried to stop it, but it seems that the Serval process is still running.");
} else {
return WHY("Serval process for that instance does not appear to be running.");
}
return WHY("Not implemented");
}
int app_server_status(int argc,char **argv,struct command_line_option *o)
{
if (cli_arg(argc, argv, o, "instance path", &thisinstancepath, cli_absolute_path, NULL) == -1)
return -1;
/* Display configuration information */
char filename[1024];
FILE *f;
if (FORM_SERVAL_INSTANCE_PATH(filename, "serval.conf") && (f = fopen(filename, "r"))) {
char line[1024];
line[0]=0; fgets(line,1024,f);
printf("\nServal Mesh configuration:\n");
while(line[0]) {
printf(" %s",line);
line[0]=0; fgets(line,1024,f);
}
fclose(f);
}
/* Display running status of daemon from serval.pid file */
int pid=-1;
int running = servalNodeRunning(&pid);
if (running<0) return -1;
printf("For Serval Mesh instance %s:\n", serval_instancepath());
if (running)
printf(" Serval mesh process is running (pid=%d)\n",pid);
else
printf(" Serval Mesh process not running\n");
return 0;
}
int app_mdp_ping(int argc,char **argv,struct command_line_option *o)
{
char *sid;
if (cli_arg(argc, argv, o, "SID|broadcast", &sid, validateSid, "broadcast") == -1)
return -1;
overlay_mdp_frame mdp;
/* Bind to MDP socket and await confirmation */
int port=32768+(random()&32767);
mdp.packetTypeAndFlags=MDP_BIND;
if (0)
bzero(&mdp.bind.sid[0],SID_SIZE); // listen on all addressses
else
/* Listen on a local address.
Must be done before setting anything else in mdp.bind, since mdp.bind
and mdp.addrlist share storage as a union in the mdp structure. */
bcopy(&mdp.addrlist.sids[0][0],mdp.bind.sid,SID_SIZE);
unsigned char srcsid[SID_SIZE];
if (overlay_mdp_getmyaddr(0,mdp.bind.sid)) return -1;
bcopy(mdp.bind.sid,srcsid,SID_SIZE);
mdp.bind.port_number=port;
int result=overlay_mdp_send(&mdp,MDP_AWAITREPLY,5000);
if (result) {
if (mdp.packetTypeAndFlags==MDP_ERROR)
fprintf(stderr,"Could not bind to MDP port %d: error=%d, message='%s'\n",
port,mdp.error.error,mdp.error.message);
else
fprintf(stderr,"Could not bind to MDP port %d (no reason given)\n",port);
return -1;
}
/* First sequence number in the echo frames */
unsigned int firstSeq=random();
unsigned int sequence_number=firstSeq;
/* Get SID that we want to ping.
XXX - allow lookup of SID prefixes and telephone numbers
(that would require MDP lookup of phone numbers, which doesn't yet occur) */
int i;
int broadcast=0;
unsigned char ping_sid[SID_SIZE];
if (strcasecmp(sid,"broadcast")) {
stowSid(ping_sid,0,sid);
} else {
for(i=0;i<SID_SIZE;i++) ping_sid[i]=0xff;
broadcast=1;
}
/* XXX Eventually we should try to resolve SID to phone number and vice versa */
printf("MDP PING %s (%s): 12 data bytes\n",
overlay_render_sid(ping_sid),
overlay_render_sid(ping_sid));
long long rx_mintime=-1;
long long rx_maxtime=-1;
long long rx_count=0,tx_count=0;
long long rx_ms=0;
long long rx_times[1024];
if (broadcast)
fprintf(stderr,"WARNING: broadcast ping packets will not be encryped.\n");
while(1) {
/* Now send the ping packets */
mdp.packetTypeAndFlags=MDP_TX;
if (broadcast) mdp.packetTypeAndFlags|=MDP_NOCRYPT;
mdp.out.src.port=port;
bcopy(srcsid,mdp.out.src.sid,SID_SIZE);
bcopy(ping_sid,&mdp.out.dst.sid[0],SID_SIZE);
/* Set port to well known echo port (from /etc/services) */
mdp.out.dst.port=7;
mdp.out.payload_length=4+8;
int *seq=(int *)&mdp.out.payload;
*seq=sequence_number;
long long *txtime=(long long *)&mdp.out.payload[4];
*txtime=overlay_gettime_ms();
int res=overlay_mdp_send(&mdp,0,0);
if (res) {
fprintf(stderr,"ERROR: Could not dispatch PING frame #%d (error %d)\n",
sequence_number-firstSeq,res);
if (mdp.packetTypeAndFlags==MDP_ERROR)
fprintf(stderr," Error message: %s\n",mdp.error.message);
} else tx_count++;
/* Now look for replies until one second has passed, and print any replies
with appropriate information as required */
long long now=overlay_gettime_ms();
long long timeout=now+1000;
while(now<timeout) {
long long timeout_ms=timeout-overlay_gettime_ms();
result = overlay_mdp_client_poll(timeout_ms);
if (result>0) {
int ttl=-1;
while (overlay_mdp_recv(&mdp,&ttl)==0) {
switch(mdp.packetTypeAndFlags&MDP_TYPE_MASK) {
case MDP_ERROR:
fprintf(stderr,"mdpping: overlay_mdp_recv: %s (code %d)\n",
mdp.error.message,mdp.error.error);
break;
case MDP_RX:
{
int *rxseq=(int *)&mdp.in.payload;
long long *txtime=(long long *)&mdp.in.payload[4];
long long delay=overlay_gettime_ms()-*txtime;
printf("%s: seq=%d time=%lld ms%s%s\n",
overlay_render_sid(mdp.in.src.sid),(*rxseq)-firstSeq+1,delay,
mdp.packetTypeAndFlags&MDP_NOCRYPT?"":" ENCRYPTED",
mdp.packetTypeAndFlags&MDP_NOSIGN?"":" SIGNED");
#warning put duplicate pong detection here so that stats work properly
rx_count++;
rx_ms+=delay;
if (rx_mintime>delay||rx_mintime==-1) rx_mintime=delay;
if (delay>rx_maxtime) rx_maxtime=delay;
rx_times[rx_count%1024]=delay;
}
break;
default:
fprintf(stderr,"mdpping: overlay_mdp_recv: Unexpected MDP frame type"
" 0x%x\n",mdp.packetTypeAndFlags);
break;
}
}
}
now=overlay_gettime_ms();
if (servalShutdown) {
float rx_stddev=0;
float rx_mean=rx_ms*1.0/rx_count;
int samples=rx_count;
if (samples>1024) samples=1024;
int i;
for(i=0;i<samples;i++)
rx_stddev+=(rx_mean-rx_times[i])*(rx_mean-rx_times[i]);
rx_stddev/=samples;
rx_stddev=sqrtf(rx_stddev);
/* XXX Report final statistics before going */
fprintf(stderr,"--- %s ping statistics ---\n",overlay_render_sid(ping_sid));
fprintf(stderr,"%lld packets transmitted, %lld packets received, %3.1f%% packet loss\n",
tx_count,rx_count,tx_count?(tx_count-rx_count)*100.0/tx_count:0);
fprintf(stderr,"round-trip min/avg/max/stddev%s = %lld/%.3f/%lld/%.3f ms\n",
(samples<rx_count)?" (stddev calculated from last 1024 samples)":"",
rx_mintime,rx_mean,rx_maxtime,rx_stddev);
return 0;
}
}
sequence_number++;
timeout=now+1000;
}
return 0;
}
2012-03-29 04:51:30 +00:00
static int set_variable(const char *var, const char *val)
{
char conffile[1024];
FILE *in;
if (!FORM_SERVAL_INSTANCE_PATH(conffile, "serval.conf") ||
!((in = fopen(conffile, "r")) || (in = fopen(conffile, "w")))
) {
2012-03-29 04:51:30 +00:00
if (var)
return WHY("could not read configuration file.");
return -1;
}
char tempfile[1024];
FILE *out;
if (!FORM_SERVAL_INSTANCE_PATH(tempfile, "serval.conf.temp") ||
!(out = fopen(tempfile, "w"))
) {
fclose(in);
return WHY("could not write temporary file.");
}
/* Read and write lines of config file, replacing the variable in question
if required. If the variable didn't already exist, then write it out at
the end. */
char line[1024];
int found=0;
int varlen=strlen(var);
line[0]=0; fgets(line,1024,in);
while(line[0]) {
2012-03-29 04:51:30 +00:00
if (!strncasecmp(var, line, varlen) && line[varlen] == '=') {
2012-04-12 08:10:00 +00:00
if (!found && val)
2012-03-29 04:51:30 +00:00
fprintf(out, "%s=%s\n", var, val);
2012-04-12 08:10:00 +00:00
found = 1;
2012-03-29 04:51:30 +00:00
} else
fprintf(out,"%s",line);
line[0]=0; fgets(line,1024,in);
}
2012-03-29 04:51:30 +00:00
if (!found && val)
fprintf(out, "%s=%s\n", var, val);
fclose(in); fclose(out);
2012-03-29 04:51:30 +00:00
if (rename(tempfile,conffile)) {
return WHYF("Failed to rename \"%s\" to \"%s\".", tempfile, conffile);
}
return 0;
}
int cli_configvarname(const char *arg)
{
if (arg[0] == '\0')
return 0;
const char *s;
for (s = arg; *s; ++s)
if (!(isalnum(*s) || *s == '_'))
return 0;
return 1;
}
int app_config_set(int argc,char **argv,struct command_line_option *o)
2012-03-29 04:51:30 +00:00
{
char *var, *val;
if ( cli_arg(argc, argv, o, "variable", &var, cli_configvarname, NULL)
|| cli_arg(argc, argv, o, "value", &val, NULL, ""))
return -1;
2012-03-29 04:51:30 +00:00
if (create_serval_instance_dir() == -1)
return -1;
return set_variable(var, val);
}
int app_config_del(int argc,char **argv,struct command_line_option *o)
2012-03-29 04:51:30 +00:00
{
char *var;
if (cli_arg(argc, argv, o, "variable", &var, cli_configvarname, NULL))
return -1;
2012-04-12 08:10:00 +00:00
if (create_serval_instance_dir() == -1)
return -1;
2012-03-29 04:51:30 +00:00
return set_variable(var, NULL);
}
int app_config_get(int argc,char **argv,struct command_line_option *o)
2012-03-29 04:51:30 +00:00
{
char *var;
if (cli_arg(argc, argv, o, "variable", &var, cli_configvarname, NULL))
return -1;
2012-04-12 08:10:00 +00:00
if (create_serval_instance_dir() == -1)
return -1;
char conffile[1024];
FILE *in;
2012-03-29 04:51:30 +00:00
if (!FORM_SERVAL_INSTANCE_PATH(conffile, "serval.conf") ||
!((in = fopen(conffile, "r")) || (in = fopen(conffile, "w")))
) {
return WHY("could not read configuration file.");
}
/* Read lines of config file. */
char line[1024];
int varlen=strlen(var);
line[0]=0; fgets(line,1024,in);
while(line[0]) {
if (varlen == 0) {
fputs(line, stdout);
}
else if (!strncasecmp(var, line, varlen) && line[varlen] == '=') {
2012-03-29 04:51:30 +00:00
fputs(line, stdout);
break;
}
line[0]=0;
fgets(line,1024,in);
}
fclose(in);
return 0;
}
int app_rhizome_add_file(int argc, char **argv, struct command_line_option *o)
{
char *filepath, *manifestpath;
cli_arg(argc, argv, o, "filepath", &filepath, NULL, "");
cli_arg(argc, argv, o, "manifestpath", &manifestpath, NULL, "");
/* Ensure the Rhizome database exists and is open */
if (create_serval_instance_dir() == -1)
return -1;
rhizome_datastore_path = serval_instancepath();
rhizome_opendb();
/* 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 = NULL;
int manifest_file_supplied = 0;
if (manifestpath[0] && access(manifestpath, R_OK) == 0) {
m = rhizome_read_manifest_file(manifestpath, 0, 0); // no verify
if (!m)
return WHY("Manifest file could not be loaded -- not added to rhizome");
manifest_file_supplied = 1;
} else {
m = rhizome_new_manifest();
if (!m)
return WHY("Manifest struct could not be allocated -- not added to rhizome");
}
/* Fill in a few missing manifest fields, to make it easier to use when adding new files:
- the payload file's basename for "name"
- current time for "date"
*/
if (rhizome_manifest_get(m, "name", NULL, 0) == NULL) {
const char *name = strrchr(filepath, '/');
name = name ? name + 1 : filepath;
rhizome_manifest_set(m, "name", name);
}
if (rhizome_manifest_get(m, "date", NULL, 0) == NULL) {
rhizome_manifest_set_ll(m, "date", overlay_gettime_ms());
}
/* Add the manifest and its associated file to the Rhizome database, generating an "id" in the
* process */
rhizome_manifest *mout = NULL;
int ret = rhizome_add_manifest(m, &mout, filepath,
NULL, // no groups - XXX should allow them
255, // ttl - XXX should read from somewhere
manifest_file_supplied, // int verifyP
1, // int checkFileP
1 // int signP
);
if (ret == -1)
return WHY("Manifest not added to Rhizome database");
/* If successfully added, overwrite the manifest file so that the Java component that is
invoking this command can read it to obtain feedback on the result. */
if (manifestpath[0] && rhizome_write_manifest_file(mout, manifestpath) == -1)
ret = WHY("Could not overwrite manifest file.");
rhizome_manifest_free(m);
if (mout != m)
rhizome_manifest_free(mout);
return ret;
}
int cli_uint(const char *arg)
{
register const char *s = arg;
while (isdigit(*s++))
;
return s != arg && *s == '\0';
}
int app_rhizome_list(int argc, char **argv, struct command_line_option *o)
{
char *offset, *limit;
cli_arg(argc, argv, o, "offset", &offset, cli_uint, "0");
cli_arg(argc, argv, o, "limit", &limit, cli_uint, "0");
/* Create the instance directory if it does not yet exist */
if (create_serval_instance_dir() == -1)
return -1;
rhizome_datastore_path = serval_instancepath();
rhizome_opendb();
return rhizome_list_manifests(atoi(offset), atoi(limit));
}
int app_keyring_create(int argc, char **argv, struct command_line_option *o)
{
char *pin;
cli_arg(argc, argv, o, "pin,pin ...", &pin, NULL, "");
keyring_file *k=keyring_open_with_pins(pin);
if (!k) fprintf(stderr,"keyring create:Failed to create/open keyring file\n");
return 0;
}
int app_keyring_list(int argc, char **argv, struct command_line_option *o)
{
char *pin;
cli_arg(argc, argv, o, "pin,pin ...", &pin, NULL, "");
keyring_file *k=keyring_open_with_pins(pin);
int cn=0;
int in=0;
for(cn=0;cn<k->context_count;cn++)
for(in=0;in<k->contexts[cn]->identity_count;in++)
{
int kpn;
keypair *kp;
unsigned char *sid=NULL,*did=NULL;
for(kpn=0;kpn<k->contexts[cn]->identities[in]->keypair_count;kpn++)
{
kp=k->contexts[cn]->identities[in]->keypairs[kpn];
if (kp->type==KEYTYPE_CRYPTOBOX) sid=kp->public_key;
if (kp->type==KEYTYPE_DID) did=kp->private_key;
}
if (sid||did) {
int i;
if (sid) for(i=0;i<SID_SIZE;i++) printf("%02x",sid[i]);
else printf("<blank SID>");
if (did) printf(":%s",did); else printf(":<no phone number set>");
printf("\n");
}
}
return 0;
}
int app_keyring_add(int argc, char **argv, struct command_line_option *o)
{
char *pin;
cli_arg(argc, argv, o, "pin", &pin, NULL, "");
keyring_file *k=keyring_open_with_pins("");
if (!k) { fprintf(stderr,"keyring add:Failed to create/open keyring file\n");
return -1; }
if (keyring_create_identity(k,k->contexts[0],(char *)pin)==NULL)
{
fprintf(stderr,"Could not create new identity (keyring_create_identity() failed)\n");
return -1;
}
if (keyring_commit(k)) {
fprintf(stderr,"Could not write new identity (keyring_commit() failed)\n");
return -1;
}
keyring_free(k);
return 0;
}
int app_keyring_set_did(int argc, char **argv, struct command_line_option *o)
{
char *sid, *did, *pin;
cli_arg(argc, argv, o, "sid", &sid, NULL, "");
cli_arg(argc, argv, o, "did", &did, NULL, "");
cli_arg(argc, argv, o, "pin", &pin, NULL, "");
if (strlen(did)>31) return WHY("DID too long (31 digits max)");
keyring_file *k=keyring_open_with_pins((char *)pin);
if (!k) return WHY("Could not open keyring file");
unsigned char packedSid[SID_SIZE];
stowSid(packedSid,0,(char *)sid);
int cn=0,in=0,kp=0;
int r=keyring_find_sid(k,&cn,&in,&kp,packedSid);
if (!r) return WHY("No matching SID");
if (keyring_set_did(k->contexts[cn]->identities[in],(char *)did))
return WHY("Could not set DID");
if (keyring_commit(k))
return WHY("Could not write updated keyring record");
return 0;
}
/* NULL marks ends of command structure.
"<anystring>" marks an arg that can take any value.
"[<anystring>]" marks an optional arg that can take any value.
All args following the first optional arg are optional, whether marked or not.
Only exactly matching prototypes will be used.
Together with the description, this makes it easy for us to auto-generate the
list of valid command line formats for display to the user if they try an
invalid one. It also means we can do away with getopt() etc.
Keep this list alphabetically sorted for user convenience.
*/
command_line_option command_line_options[]={
{app_dna_lookup,{"dna","lookup","<did>",NULL},CLIFLAG_NONOVERLAY,
"Lookup the SIP/MDP address of the supplied telephone number (DID)."},
{cli_usage,{"help",NULL},0,
"Display command usage."},
2012-03-29 04:51:30 +00:00
{app_server_start,{"node","start",NULL},CLIFLAG_STANDALONE,
"Start Serval Mesh node process with instance path taken from SERVALINSTANCE_PATH environment variable."},
{app_server_start,{"node","start","in","<instance path>",NULL},CLIFLAG_STANDALONE,
"Start Serval Mesh node process with given instance path."},
2012-03-29 04:51:30 +00:00
{app_server_start,{"node","start","foreground",NULL},CLIFLAG_STANDALONE,
"Start Serval Mesh node process without detatching from foreground."},
{app_server_start,{"node","start","foreground","in","<instance path>",NULL},CLIFLAG_STANDALONE,
"Start Serval Mesh node process with given instance path, without detatching from foreground."},
2012-03-29 04:51:30 +00:00
{app_server_stop,{"node","stop",NULL},0,
"Stop a running Serval Mesh node process with instance path taken from SERVALINSTANCE_PATH environment variable."},
2012-03-29 04:51:30 +00:00
{app_server_stop,{"node","stop","in","<instance path>",NULL},0,
"Stop a running Serval Mesh node process with given instance path."},
2012-03-29 04:51:30 +00:00
{app_server_status,{"node","status",NULL},0,
"Display information about any running Serval Mesh node."},
2012-03-29 04:51:30 +00:00
{app_mdp_ping,{"mdp","ping","<SID|broadcast>",NULL},CLIFLAG_STANDALONE,
"Attempts to ping specified node via Mesh Datagram Protocol (MDP)."},
{app_config_set,{"config","set","<variable>","<value>",NULL},CLIFLAG_STANDALONE,
2012-03-29 04:51:30 +00:00
"Set specified configuration variable."},
{app_config_del,{"config","del","<variable>",NULL},CLIFLAG_STANDALONE,
"Set specified configuration variable."},
{app_config_get,{"config","get","[<variable>]",NULL},CLIFLAG_STANDALONE,
2012-03-29 04:51:30 +00:00
"Get specified configuration variable."},
{app_rhizome_add_file,{"rhizome","add","file","<filepath>","[<manifestpath>]",NULL},CLIFLAG_STANDALONE,
"Add a file to Rhizome and optionally write its manifest to the given path"},
{app_rhizome_list,{"rhizome","list","[<offset>]","[<limit>]",NULL},CLIFLAG_STANDALONE,
"List all manifests and files in Rhizome"},
{app_keyring_create,{"keyring","create",NULL},0,
"Create a new keyring file."},
{app_keyring_list,{"keyring","list","[<pin,pin ...>]",NULL},0,
"List identites in specified key ring that can be accessed using the specified PINs"},
{app_keyring_add,{"keyring","add","[<pin>]",NULL},CLIFLAG_STANDALONE,
"Create a new identity in the keyring protected by the provided PIN"},
{app_keyring_set_did,{"set","did","<sid>","<did>","[<pin>]",NULL},CLIFLAG_STANDALONE,
"Set the DID for the specified SID. Optionally supply PIN to unlock the SID record in the keyring."},
{app_vomp_status,{"vomp","status",NULL},0,
"Display status of any VoMP calls"},
{app_vomp_monitor,{"vomp","monitor",NULL},0,
"Monitor state and audio-flow of VoMP calls"},
{app_vomp_pickup,{"vomp","pickup","<call>",NULL},0,
"Accept specified call (use vomp status to get list of calls)"},
{app_vomp_hangup,{"vomp","hangup","<call>",NULL},0,
"End specified call (use vomp status to get list of calls)"},
{app_vomp_dial,{"vomp","dial","<sid>","<did>","[<callerid>]",NULL},0,
"Attempt to dial the specified sid and did. Optionally supply the calling number"},
{NULL,{NULL}}
};