This commit is contained in:
qo-op 2020-05-06 19:42:21 +02:00
parent 95c40e4ae9
commit 04318ec2eb
10 changed files with 2010 additions and 0 deletions

42
.install/sbotc/Makefile Normal file
View File

@ -0,0 +1,42 @@
BIN = sbotc
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man
CFLAGS = -Wall -Werror -Wextra
ifdef STATIC
LDLIBS = -l:libsodium.a
else
LDLIBS = -lsodium
endif
all: $(BIN)
$(BIN): $(BIN).c base64.c jsmn.c
install: all
@mkdir -vp $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
@cp -vf $(BIN) $(DESTDIR)$(BINDIR)
@cp -vf $(BIN).1 $(DESTDIR)$(MANDIR)/man1
link: all
@mkdir -vp $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
@ln -svf $(shell realpath $(BIN)) $(DESTDIR)$(BINDIR)
@ln -svf $(shell realpath $(BIN).1) $(DESTDIR)$(MANDIR)/man1
uninstall:
@rm -vf \
$(DESTDIR)$(BINDIR)/$(BIN) \
$(DESTDIR)$(MANDIR)/man1/$(BIN).1
test-shs1:
@# %lzzcAZlM21slUIoiH4yd/wgDnXu8raNLvwqjxqrU06k=.sha256
shs1testclient ./test-shs-inner.sh $(SHS1_TEST_SEED)
clean:
@rm -vf $(BIN)
.PHONY:
all install link uninstall test-shs1 clean

31
.install/sbotc/README.md Normal file
View File

@ -0,0 +1,31 @@
# sbotc
A command-line SSB client in C. Use like the `sbot` command (except for `server`/`start`).
## Install
Install the dependency, *sodium*. On Debian: `sudo apt-get install libsodium-dev`
Compile and install the program:
```sh
make
sudo make install
```
## Compile options
To build a binary statically linked with libsodium, use `make STATIC=1`
## Usage
```sh
sbotc [-j] [-T] [-l] [-r] [-e]
[ -n | [-c <cap>] [-k <key>] [-K <keypair_seed>] ]
[ [-s <host>] [-p <port>] [ -4 | -6 ] | [-u <socket_path>] ]
[ -a | [-t <type>] <method> [<argument>...] ]
```
Arguments must be explicitly JSON-encoded.
For more information, see the manual page `sbotc(1)`.

118
.install/sbotc/base64.c Normal file
View File

@ -0,0 +1,118 @@
/*
This code is public domain software.
*/
#include "base64.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// single base64 character conversion
//
static int POS(char c)
{
if (c>='A' && c<='Z') return c - 'A';
if (c>='a' && c<='z') return c - 'a' + 26;
if (c>='0' && c<='9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
if (c == '=') return -1;
return -2;
}
// base64 decoding
//
// s: base64 string
// str_len size of the base64 string
// data: output buffer for decoded data
// data_len expected size of decoded data
// return: 0 on success, -1 on failure
//
int base64_decode(const char* s, size_t str_len, void *data, size_t data_len)
{
const char *p, *str_end;
unsigned char *q, *end;
int n[4] = { 0, 0, 0, 0 };
if (str_len % 4) { errno = EBADMSG; return -1; }
q = (unsigned char*) data;
end = q + data_len;
str_end = s + str_len;
for (p = s; p < str_end; ) {
n[0] = POS(*p++);
n[1] = POS(*p++);
n[2] = POS(*p++);
n[3] = POS(*p++);
if (n[0] == -2 || n[1] == -2 || n[2] == -2 || n[3] == -2)
{ errno = EBADMSG; return -1; }
if (n[0] == -1 || n[1] == -1)
{ errno = EBADMSG; return -1; }
if (n[2] == -1 && n[3] != -1)
{ errno = EBADMSG; return -1; }
if (q >= end) { errno = EMSGSIZE; return -1; }
q[0] = (n[0] << 2) + (n[1] >> 4);
if (n[2] != -1) {
if (q+1 >= end) { errno = EMSGSIZE; return -1; }
q[1] = ((n[1] & 15) << 4) + (n[2] >> 2);
}
if (n[3] != -1) {
if (q+2 >= end) { errno = EMSGSIZE; return -1; }
q[2] = ((n[2] & 3) << 6) + n[3];
}
q += 3;
}
return 0;
}
int base64_encode(const void* buf, size_t size, char *str, size_t out_size) {
static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char* p = str;
const unsigned char* q = (const unsigned char*) buf;
size_t i = 0;
if ((size+3)*4/3 + 1 > out_size) {
errno = EMSGSIZE;
return -1;
}
while (i < size) {
int c = q[i++];
c *= 256;
if (i < size)
c += q[i];
i++;
c *= 256;
if (i < size)
c += q[i];
i++;
*p++ = base64[(c & 0x00fc0000) >> 18];
*p++ = base64[(c & 0x0003f000) >> 12];
if (i > size + 1)
*p++ = '=';
else
*p++ = base64[(c & 0x00000fc0) >> 6];
if (i > size)
*p++ = '=';
else
*p++ = base64[c & 0x0000003f];
}
*p = 0;
return 0;
}

6
.install/sbotc/base64.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include <stddef.h>
int base64_encode(const void* buf, size_t size, char *str, size_t out_size);
int base64_decode(const char *s, size_t str_len, void *data, size_t data_len);

278
.install/sbotc/jsmn.c Normal file
View File

@ -0,0 +1,278 @@
/**
* jsmn
* Copyright (c) 2010 Serge A. Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdlib.h>
#include "jsmn.h"
/**
* Allocates a fresh unused token from the token pull.
*/
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
jsmntok_t *tokens, size_t num_tokens) {
jsmntok_t *tok;
if (parser->toknext >= (int)num_tokens) {
return NULL;
}
tok = &tokens[parser->toknext++];
tok->start = tok->end = -1;
tok->size = 0;
#ifdef JSMN_PARENT_LINKS
tok->parent = -1;
#endif
return tok;
}
/**
* Fills token type and boundaries.
*/
static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
int start, int end) {
token->type = type;
token->start = start;
token->end = end;
token->size = 0;
}
/**
* Fills next available token with JSON primitive.
*/
static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
jsmntok_t *tokens, size_t num_tokens) {
jsmntok_t *token;
int start;
start = parser->pos;
for (; js[parser->pos] != '\0'; parser->pos++) {
switch (js[parser->pos]) {
#ifndef JSMN_STRICT
/* In strict mode primitive must be followed by "," or "}" or "]" */
case ':':
#endif
case '\t' : case '\r' : case '\n' : case ' ' :
case ',' : case ']' : case '}' :
goto found;
}
if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
#ifdef JSMN_STRICT
/* In strict mode primitive must be followed by a comma/object/array */
parser->pos = start;
return JSMN_ERROR_PART;
#endif
found:
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
parser->pos--;
return JSMN_SUCCESS;
}
/**
* Filsl next token with JSON string.
*/
static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
jsmntok_t *tokens, size_t num_tokens) {
jsmntok_t *token;
int start = parser->pos;
parser->pos++;
/* Skip starting quote */
for (; js[parser->pos] != '\0'; parser->pos++) {
char c = js[parser->pos];
/* Quote: end of string */
if (c == '\"') {
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
return JSMN_SUCCESS;
}
/* Backslash: Quoted symbol expected */
if (c == '\\') {
parser->pos++;
switch (js[parser->pos]) {
/* Allowed escaped symbols */
case '\"': case '/' : case '\\' : case 'b' :
case 'f' : case 'r' : case 'n' : case 't' :
break;
/* Allows escaped symbol \uXXXX */
case 'u':
/* TODO */
break;
/* Unexpected symbol */
default:
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
}
parser->pos = start;
return JSMN_ERROR_PART;
}
/**
* Parse JSON string and fill tokens.
*/
jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens,
unsigned int num_tokens) {
jsmnerr_t r;
int i;
jsmntok_t *token;
for (; js[parser->pos] != '\0'; parser->pos++) {
char c;
jsmntype_t type;
c = js[parser->pos];
switch (c) {
case '{': case '[':
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL)
return JSMN_ERROR_NOMEM;
if (parser->toksuper != -1) {
tokens[parser->toksuper].size++;
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
}
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
token->start = parser->pos;
parser->toksuper = parser->toknext - 1;
break;
case '}': case ']':
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
if (parser->toknext < 1) {
return JSMN_ERROR_INVAL;
}
token = &tokens[parser->toknext - 1];
for (;;) {
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
token->end = parser->pos + 1;
parser->toksuper = token->parent;
break;
}
if (token->parent == -1) {
break;
}
token = &tokens[token->parent];
}
#else
for (i = parser->toknext - 1; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
token->end = parser->pos + 1;
break;
}
}
/* Error if unmatched closing bracket */
if (i == -1) return JSMN_ERROR_INVAL;
for (; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
parser->toksuper = i;
break;
}
}
#endif
break;
case '\"':
r = jsmn_parse_string(parser, js, tokens, num_tokens);
if (r < 0) return r;
if (parser->toksuper != -1)
tokens[parser->toksuper].size++;
break;
case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ':
break;
#ifdef JSMN_STRICT
/* In strict mode primitives are: numbers and booleans */
case '-': case '0': case '1' : case '2': case '3' : case '4':
case '5': case '6': case '7' : case '8': case '9':
case 't': case 'f': case 'n' :
#else
/* In non-strict mode every unquoted value is a primitive */
default:
#endif
r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
if (r < 0) return r;
if (parser->toksuper != -1)
tokens[parser->toksuper].size++;
break;
#ifdef JSMN_STRICT
/* Unexpected char in strict mode */
default:
return JSMN_ERROR_INVAL;
#endif
}
}
for (i = parser->toknext - 1; i >= 0; i--) {
/* Unmatched opened object or array */
if (tokens[i].start != -1 && tokens[i].end == -1) {
return JSMN_ERROR_PART;
}
}
return JSMN_SUCCESS;
}
/**
* Creates a new parser based over a given buffer with an array of tokens
* available.
*/
void jsmn_init(jsmn_parser *parser) {
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
}

67
.install/sbotc/jsmn.h Normal file
View File

@ -0,0 +1,67 @@
#ifndef __JSMN_H_
#define __JSMN_H_
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum {
JSMN_PRIMITIVE = 0,
JSMN_OBJECT = 1,
JSMN_ARRAY = 2,
JSMN_STRING = 3
} jsmntype_t;
typedef enum {
/* Not enough tokens were provided */
JSMN_ERROR_NOMEM = -1,
/* Invalid character inside JSON string */
JSMN_ERROR_INVAL = -2,
/* The string is not a full JSON packet, more bytes expected */
JSMN_ERROR_PART = -3,
/* Everything was fine */
JSMN_SUCCESS = 0
} jsmnerr_t;
/**
* JSON token description.
* @param type type (object, array, string etc.)
* @param start start position in JSON data string
* @param end end position in JSON data string
*/
typedef struct {
jsmntype_t type;
int start;
int end;
int size;
#ifdef JSMN_PARENT_LINKS
int parent;
#endif
} jsmntok_t;
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string
*/
typedef struct {
unsigned int pos; /* offset in the JSON string */
int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g parent object or array */
} jsmn_parser;
/**
* Create JSON parser over an array of tokens
*/
void jsmn_init(jsmn_parser *parser);
/**
* Run JSON parser. It parses a JSON data string into and array of tokens, each describing
* a single JSON object.
*/
jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js,
jsmntok_t *tokens, unsigned int num_tokens);
#endif /* __JSMN_H_ */

BIN
.install/sbotc/sbotc Executable file

Binary file not shown.

190
.install/sbotc/sbotc.1 Normal file
View File

@ -0,0 +1,190 @@
.Dd 2017-06-03
.Dt SBOTC 1
.Os SSBC
.ds REPO ssb://%133ulDgs/oC1DXjoK04vDFy6DgVBB/Zok15YJmuhD5Q=.sha256
.Sh NAME
.Nm sbotc
.Nd Call a scuttlebot/secret-stack RPC method
.Sh SYNOPSIS
.Nm
.Op Fl j
.Op Fl l
.Op Fl r
.Op Fl T
.Op Fl e
.Op Fl a
.
.Oo
.Fl n
|
.Op Fl c Ar cap
.Op Fl k Ar key
.Op Fl K Ar keypair
.Oc
.
.Oo
.Op Fl s Ar host
.Op Fl p Ar port
.Oo
.Fl 4
|
.Fl 6
.Oc
|
.Op Fl u Ar socket_path
.Oc
.
.Oo
.Fl a
|
.Op Fl t Ar type
.Ar method
.Op Ar argument ...
.Oc
.Sh DESCRIPTION
Connect to a scuttlebot/secret-stack server, and call a method on it, with
standard I/O.
.Sh OPTIONS
.Bl -tag
.It Fl j
Send stdin data as JSON.
.It Fl l
Don't output newlines after string or JSON packets.
.It Fl r
Raw mode. Disables stdin line buffering/editing and echoing. Implies
.Fl l .
.It Fl e
Encode arguments as strings, rather than expecting them to be JSON-encoded.
.It Fl T
Test using shs1-testsuite protocol. Instead of connecting to a server and running
a command, connect to stdio. On successful handshake, output concatenation of
the encryption key, encryption nonce, decryption key and decryption nonce.
.It Fl a
Passthrough mode. Instead of making a muxrpc call, pass through the box-stream
to stdio.
.It Fl n
Noauth mode. Skip secret-handshake authentication and box-stream encryption.
This option makes the
.Fl k ,
.Fl K ,
and
.Fl c
options have no effect and output a warning if used.
.It Fl 4
Connect to server over IPv4 only.
.It Fl 6
Connect to server over IPv6 only.
.It Fl c Ar cap
Capability key for secret-handshake. Default is SSB's capability key,
.Li 1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s= .
.It Fl s Ar host
The hostname to connect to. Default is localhost. If set to localhost and connection to localhost fails,
.Nm
may attempt to connect to other local interface addresses.
.It Fl p Ar port
The port to connect to. Default is 8008.
.It Fl u Ar socket_path
Unix socket path to connect to, instead of TCP socket. Conflicts with
.Fl p
and
.Fl s .
.It Fl k Ar key
The key to connect to. Default is your public key, as read from your
private key file.
.It Fl K Ar keypair
Private key or private key seed to use for secret-handshake. Default is to use the private key
from your
.Pa ~/.ssb/secret
file or other secret file according to the environmental variables described in
.Sx ENVIRONMENT .
.It Fl t Ar type
The type of method:
.Dq async ,
.Dq source ,
.Dq sink ,
or
.Dq duplex .
Default is to look up the method in
.Pa ~/.ssb/manifest.json .
.It Ar method
Method name.
.It Op Ar argument ...
Arguments to pass to the method call. Each argument must be JSON-encoded, unless the
.Fl e
option is used, in which the arguments are treated as strings.
.El
.Sh ENVIRONMENT
.Bl -tag
.It Ev ssb_appname
Name of the app. Default is
.Dq ssb .
Used to construct the app's directory if
.Ev ssb_path
is not present.
.It Ev ssb_path
Path to the app's directory. Default is to use
.Ev ssb_appname to construct the path as
.Dq ~/.<ssb_appname>
.El
.Sh FILES
.Bl -tag -width -indent
.It Pa ~/.ssb/secret
Your private key, used for authenticating to the server with the
secret-handshake protocol.
.It Pa ~/.ssb/manifest.json
A map of method names to method types.
.It Pa ~/.ssb/config
JSON file containing key, host, port, and/or SHS cap key to use if the
.Fl s ,
.Fl p
or
.Fl c
options are not given, respectively.
.It Pa ~/.ssb/socket
UNIX socket stream file for noauth connections.
If none of the options
.Fl s ,
.Fl p ,
.Fl u ,
.Fl c ,
.Fl k ,
.Fl K ,
.Fl c ,
.Fl 4 ,
.Fl 6 ,
or
.Fl T
are specified,
.Nm
will attempt to connect in noauth mode to this socket file. If the socket file
is not present or the connection fails,
.Nm
will fall back to connecting with TCP and secret-handshake according to the
config file - unless the
.Fl n
option is specified, in which case the command will fail.
.El
.Pp
The base path
.Dq ~/.ssb/
of these file names may be changed by setting
.Ev ssb_appname
or
.Ev ssb_path .
.Sh EXIT STATUS
.Bl -tag -width Ds
.It 0
The command completed successfully.
.It 1
An error occurred.
.It 2
The command completed with an error.
.El
.Sh AUTHORS
.Nm
was written by
.An cel Aq @f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519 .
.Sh BUGS
.Pp
Please report any bugs by making a post on SSB mentioning the repo,
.Lk \*[REPO]

1270
.install/sbotc/sbotc.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
#!/bin/sh
cap_hex=${1?shs cap key}
pk_hex=${2?server public key}
cap_b64="$(echo -n "$cap_hex" | xxd -r -p | base64)"
pk_b64="$(echo -n "$pk_hex" | xxd -r -p | base64)"
exec sbotc -T -c "$cap_b64" -k "$pk_b64"