git ssb

8+

cel / sbotc



Commit beb573d6ef59d2573f579fe3383846dfca9972db

Initial commit

cel committed on 6/4/2017, 9:09:26 AM

Files changed

Makefileadded
README.mdadded
base64.cadded
base64.hadded
jsmn.cadded
jsmn.hadded
sbotc.1added
sbotc.cadded
MakefileView
@@ -1,0 +1,28 @@
1 +BIN = sbotc
2 +LDLIBS = -lsodium
3 +
4 +PREFIX = /usr/local
5 +BINDIR = $(PREFIX)/bin
6 +MANDIR = $(PREFIX)/share/man
7 +
8 +all: $(BIN)
9 +
10 +$(BIN): $(BIN).c base64.c jsmn.c
11 +
12 +install: all
13 + @mkdir -vp $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
14 + @cp -vf $(BIN) $(DESTDIR)$(BINDIR)
15 + @cp -vf $(BIN).1 $(DESTDIR)$(MANDIR)/man1
16 +
17 +link: all
18 + @mkdir -vp $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
19 + @ln -rsvf $(BIN) $(DESTDIR)$(BINDIR)
20 + @ln -rsvf $(BIN).1 $(DESTDIR)$(MANDIR)/man1
21 +
22 +uninstall:
23 + @rm -vf \
24 + $(DESTDIR)$(BINDIR)/$(BIN) \
25 + $(DESTDIR)$(MANDIR)/man1/$(BIN).1
26 +
27 +clean:
28 + @rm -vf $(BIN)
README.mdView
@@ -1,0 +1,24 @@
1 +# sbotc
2 +
3 +A command-line SSB client in C. Use like the `sbot` command.
4 +
5 +## Install
6 +
7 +Install the dependency, *sodium*. On Debian: `sudo apt-get install libsodium-dev`
8 +
9 +Compile and install the program:
10 +
11 +```sh
12 +make
13 +sudo make install
14 +```
15 +
16 +## Usage
17 +
18 +```sh
19 +sbotc [-s <host>] [-p <port>] [-k <key>] [-t <type>] <method> [<argument>...]
20 +```
21 +
22 +Arguments must be explicitly JSON-encoded.
23 +
24 +For more information, see the manual page `sbotc(1)`.
base64.cView
@@ -1,0 +1,75 @@
1 +/*
2 +
3 + This code is public domain software.
4 +
5 +*/
6 +
7 +#include "base64.h"
8 +
9 +#include <stdlib.h>
10 +#include <string.h>
11 +#include <errno.h>
12 +
13 +
14 +// single base64 character conversion
15 +//
16 +static int POS(char c)
17 +{
18 + if (c>='A' && c<='Z') return c - 'A';
19 + if (c>='a' && c<='z') return c - 'a' + 26;
20 + if (c>='0' && c<='9') return c - '0' + 52;
21 + if (c == '+') return 62;
22 + if (c == '/') return 63;
23 + if (c == '=') return -1;
24 + return -2;
25 +}
26 +
27 +// base64 decoding
28 +//
29 +// s: base64 string
30 +// str_len size of the base64 string
31 +// data: output buffer for decoded data
32 +// data_len expected size of decoded data
33 +// return: 0 on success, -1 on failure
34 +//
35 +int base64_decode(const char* s, size_t str_len, void *data, size_t data_len)
36 +{
37 + const char *p, *str_end;
38 + unsigned char *q, *end;
39 + int n[4] = { 0, 0, 0, 0 };
40 +
41 + if (str_len % 4) { errno = EBADMSG; return -1; }
42 + q = (unsigned char*) data;
43 + end = q + data_len;
44 + str_end = s + str_len;
45 +
46 + for (p = s; p < str_end; ) {
47 + n[0] = POS(*p++);
48 + n[1] = POS(*p++);
49 + n[2] = POS(*p++);
50 + n[3] = POS(*p++);
51 +
52 + if (n[0] == -2 || n[1] == -2 || n[2] == -2 || n[3] == -2)
53 + { errno = EBADMSG; return -1; }
54 +
55 + if (n[0] == -1 || n[1] == -1)
56 + { errno = EBADMSG; return -1; }
57 +
58 + if (n[2] == -1 && n[3] != -1)
59 + { errno = EBADMSG; return -1; }
60 +
61 + if (q >= end) { errno = EMSGSIZE; return -1; }
62 + q[0] = (n[0] << 2) + (n[1] >> 4);
63 + if (n[2] != -1) {
64 + if (q+1 >= end) { errno = EMSGSIZE; return -1; }
65 + q[1] = ((n[1] & 15) << 4) + (n[2] >> 2);
66 + }
67 + if (n[3] != -1) {
68 + if (q+2 >= end) { errno = EMSGSIZE; return -1; }
69 + q[2] = ((n[2] & 3) << 6) + n[3];
70 + }
71 + q += 3;
72 + }
73 +
74 + return 0;
75 +}
base64.hView
@@ -1,0 +1,5 @@
1 +#pragma once
2 +
3 +#include <stddef.h>
4 +
5 +int base64_decode(const char *s, size_t str_len, void *data, size_t data_len);
jsmn.cView
@@ -1,0 +1,278 @@
1 +/**
2 + * jsmn
3 + * Copyright (c) 2010 Serge A. Zaitsev
4 + *
5 + * Permission is hereby granted, free of charge, to any person obtaining a copy
6 + * of this software and associated documentation files (the "Software"), to deal
7 + * in the Software without restriction, including without limitation the rights
8 + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 + * copies of the Software, and to permit persons to whom the Software is
10 + * furnished to do so, subject to the following conditions:
11 + *
12 + * The above copyright notice and this permission notice shall be included in
13 + * all copies or substantial portions of the Software.
14 + *
15 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 + * THE SOFTWARE.
22 + */
23 +
24 +#include <stdlib.h>
25 +
26 +#include "jsmn.h"
27 +
28 +/**
29 + * Allocates a fresh unused token from the token pull.
30 + */
31 +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
32 + jsmntok_t *tokens, size_t num_tokens) {
33 + jsmntok_t *tok;
34 + if (parser->toknext >= num_tokens) {
35 + return NULL;
36 + }
37 + tok = &tokens[parser->toknext++];
38 + tok->start = tok->end = -1;
39 + tok->size = 0;
40 +#ifdef JSMN_PARENT_LINKS
41 + tok->parent = -1;
42 +#endif
43 + return tok;
44 +}
45 +
46 +/**
47 + * Fills token type and boundaries.
48 + */
49 +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
50 + int start, int end) {
51 + token->type = type;
52 + token->start = start;
53 + token->end = end;
54 + token->size = 0;
55 +}
56 +
57 +/**
58 + * Fills next available token with JSON primitive.
59 + */
60 +static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
61 + jsmntok_t *tokens, size_t num_tokens) {
62 + jsmntok_t *token;
63 + int start;
64 +
65 + start = parser->pos;
66 +
67 + for (; js[parser->pos] != '\0'; parser->pos++) {
68 + switch (js[parser->pos]) {
69 +#ifndef JSMN_STRICT
70 + /* In strict mode primitive must be followed by "," or "}" or "]" */
71 + case ':':
72 +#endif
73 + case '\t' : case '\r' : case '\n' : case ' ' :
74 + case ',' : case ']' : case '}' :
75 + goto found;
76 + }
77 + if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
78 + parser->pos = start;
79 + return JSMN_ERROR_INVAL;
80 + }
81 + }
82 +#ifdef JSMN_STRICT
83 + /* In strict mode primitive must be followed by a comma/object/array */
84 + parser->pos = start;
85 + return JSMN_ERROR_PART;
86 +#endif
87 +
88 +found:
89 + token = jsmn_alloc_token(parser, tokens, num_tokens);
90 + if (token == NULL) {
91 + parser->pos = start;
92 + return JSMN_ERROR_NOMEM;
93 + }
94 + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
95 +#ifdef JSMN_PARENT_LINKS
96 + token->parent = parser->toksuper;
97 +#endif
98 + parser->pos--;
99 + return JSMN_SUCCESS;
100 +}
101 +
102 +/**
103 + * Filsl next token with JSON string.
104 + */
105 +static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
106 + jsmntok_t *tokens, size_t num_tokens) {
107 + jsmntok_t *token;
108 +
109 + int start = parser->pos;
110 +
111 + parser->pos++;
112 +
113 + /* Skip starting quote */
114 + for (; js[parser->pos] != '\0'; parser->pos++) {
115 + char c = js[parser->pos];
116 +
117 + /* Quote: end of string */
118 + if (c == '\"') {
119 + token = jsmn_alloc_token(parser, tokens, num_tokens);
120 + if (token == NULL) {
121 + parser->pos = start;
122 + return JSMN_ERROR_NOMEM;
123 + }
124 + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
125 +#ifdef JSMN_PARENT_LINKS
126 + token->parent = parser->toksuper;
127 +#endif
128 + return JSMN_SUCCESS;
129 + }
130 +
131 + /* Backslash: Quoted symbol expected */
132 + if (c == '\\') {
133 + parser->pos++;
134 + switch (js[parser->pos]) {
135 + /* Allowed escaped symbols */
136 + case '\"': case '/' : case '\\' : case 'b' :
137 + case 'f' : case 'r' : case 'n' : case 't' :
138 + break;
139 + /* Allows escaped symbol \uXXXX */
140 + case 'u':
141 + /* TODO */
142 + break;
143 + /* Unexpected symbol */
144 + default:
145 + parser->pos = start;
146 + return JSMN_ERROR_INVAL;
147 + }
148 + }
149 + }
150 + parser->pos = start;
151 + return JSMN_ERROR_PART;
152 +}
153 +
154 +/**
155 + * Parse JSON string and fill tokens.
156 + */
157 +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens,
158 + unsigned int num_tokens) {
159 + jsmnerr_t r;
160 + int i;
161 + jsmntok_t *token;
162 +
163 + for (; js[parser->pos] != '\0'; parser->pos++) {
164 + char c;
165 + jsmntype_t type;
166 +
167 + c = js[parser->pos];
168 + switch (c) {
169 + case '{': case '[':
170 + token = jsmn_alloc_token(parser, tokens, num_tokens);
171 + if (token == NULL)
172 + return JSMN_ERROR_NOMEM;
173 + if (parser->toksuper != -1) {
174 + tokens[parser->toksuper].size++;
175 +#ifdef JSMN_PARENT_LINKS
176 + token->parent = parser->toksuper;
177 +#endif
178 + }
179 + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
180 + token->start = parser->pos;
181 + parser->toksuper = parser->toknext - 1;
182 + break;
183 + case '}': case ']':
184 + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
185 +#ifdef JSMN_PARENT_LINKS
186 + if (parser->toknext < 1) {
187 + return JSMN_ERROR_INVAL;
188 + }
189 + token = &tokens[parser->toknext - 1];
190 + for (;;) {
191 + if (token->start != -1 && token->end == -1) {
192 + if (token->type != type) {
193 + return JSMN_ERROR_INVAL;
194 + }
195 + token->end = parser->pos + 1;
196 + parser->toksuper = token->parent;
197 + break;
198 + }
199 + if (token->parent == -1) {
200 + break;
201 + }
202 + token = &tokens[token->parent];
203 + }
204 +#else
205 + for (i = parser->toknext - 1; i >= 0; i--) {
206 + token = &tokens[i];
207 + if (token->start != -1 && token->end == -1) {
208 + if (token->type != type) {
209 + return JSMN_ERROR_INVAL;
210 + }
211 + parser->toksuper = -1;
212 + token->end = parser->pos + 1;
213 + break;
214 + }
215 + }
216 + /* Error if unmatched closing bracket */
217 + if (i == -1) return JSMN_ERROR_INVAL;
218 + for (; i >= 0; i--) {
219 + token = &tokens[i];
220 + if (token->start != -1 && token->end == -1) {
221 + parser->toksuper = i;
222 + break;
223 + }
224 + }
225 +#endif
226 + break;
227 + case '\"':
228 + r = jsmn_parse_string(parser, js, tokens, num_tokens);
229 + if (r < 0) return r;
230 + if (parser->toksuper != -1)
231 + tokens[parser->toksuper].size++;
232 + break;
233 + case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ':
234 + break;
235 +#ifdef JSMN_STRICT
236 + /* In strict mode primitives are: numbers and booleans */
237 + case '-': case '0': case '1' : case '2': case '3' : case '4':
238 + case '5': case '6': case '7' : case '8': case '9':
239 + case 't': case 'f': case 'n' :
240 +#else
241 + /* In non-strict mode every unquoted value is a primitive */
242 + default:
243 +#endif
244 + r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
245 + if (r < 0) return r;
246 + if (parser->toksuper != -1)
247 + tokens[parser->toksuper].size++;
248 + break;
249 +
250 +#ifdef JSMN_STRICT
251 + /* Unexpected char in strict mode */
252 + default:
253 + return JSMN_ERROR_INVAL;
254 +#endif
255 +
256 + }
257 + }
258 +
259 + for (i = parser->toknext - 1; i >= 0; i--) {
260 + /* Unmatched opened object or array */
261 + if (tokens[i].start != -1 && tokens[i].end == -1) {
262 + return JSMN_ERROR_PART;
263 + }
264 + }
265 +
266 + return JSMN_SUCCESS;
267 +}
268 +
269 +/**
270 + * Creates a new parser based over a given buffer with an array of tokens
271 + * available.
272 + */
273 +void jsmn_init(jsmn_parser *parser) {
274 + parser->pos = 0;
275 + parser->toknext = 0;
276 + parser->toksuper = -1;
277 +}
278 +
jsmn.hView
@@ -1,0 +1,67 @@
1 +#ifndef __JSMN_H_
2 +#define __JSMN_H_
3 +
4 +/**
5 + * JSON type identifier. Basic types are:
6 + * o Object
7 + * o Array
8 + * o String
9 + * o Other primitive: number, boolean (true/false) or null
10 + */
11 +typedef enum {
12 + JSMN_PRIMITIVE = 0,
13 + JSMN_OBJECT = 1,
14 + JSMN_ARRAY = 2,
15 + JSMN_STRING = 3
16 +} jsmntype_t;
17 +
18 +typedef enum {
19 + /* Not enough tokens were provided */
20 + JSMN_ERROR_NOMEM = -1,
21 + /* Invalid character inside JSON string */
22 + JSMN_ERROR_INVAL = -2,
23 + /* The string is not a full JSON packet, more bytes expected */
24 + JSMN_ERROR_PART = -3,
25 + /* Everything was fine */
26 + JSMN_SUCCESS = 0
27 +} jsmnerr_t;
28 +
29 +/**
30 + * JSON token description.
31 + * @param type type (object, array, string etc.)
32 + * @param start start position in JSON data string
33 + * @param end end position in JSON data string
34 + */
35 +typedef struct {
36 + jsmntype_t type;
37 + int start;
38 + int end;
39 + int size;
40 +#ifdef JSMN_PARENT_LINKS
41 + int parent;
42 +#endif
43 +} jsmntok_t;
44 +
45 +/**
46 + * JSON parser. Contains an array of token blocks available. Also stores
47 + * the string being parsed now and current position in that string
48 + */
49 +typedef struct {
50 + unsigned int pos; /* offset in the JSON string */
51 + int toknext; /* next token to allocate */
52 + int toksuper; /* superior token node, e.g parent object or array */
53 +} jsmn_parser;
54 +
55 +/**
56 + * Create JSON parser over an array of tokens
57 + */
58 +void jsmn_init(jsmn_parser *parser);
59 +
60 +/**
61 + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
62 + * a single JSON object.
63 + */
64 +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js,
65 + jsmntok_t *tokens, unsigned int num_tokens);
66 +
67 +#endif /* __JSMN_H_ */
sbotc.1View
@@ -1,0 +1,91 @@
1 +.Dd 2017-06-03
2 +.Dt SBOTC 1
3 +.Os SSBC
4 +.ds REPO ssb://%133ulDgs/oC1DXjoK04vDFy6DgVBB/Zok15YJmuhD5Q=.sha256
5 +.Sh NAME
6 +.Nm sbotc
7 +.Nd Call a scuttlebot/secret-stack RPC method
8 +.Sh SYNOPSIS
9 +.Nm
10 +.Op Fl s Ar host
11 +.Op Fl p Ar port
12 +.Op Fl k Ar key
13 +.Op Fl t Ar type
14 +.Ar method
15 +.Op Ar argument ...
16 +.Sh DESCRIPTION
17 +Connect to a scuttlebot/secret-stack server, and call a method on it, with
18 +standard I/O.
19 +.Sh OPTIONS
20 +.Bl -tag
21 +.It Fl s Ar host
22 +The hostname to connect to. Default is localhost.
23 +.It Fl p Ar port
24 +The port to connect to. Default is 8008.
25 +.It Fl k Ar key
26 +The key to connect to. Default is your public key.
27 +.It Fl t Ar type
28 +The type of method:
29 +.Dq async ,
30 +.Dq source ,
31 +.Dq sink ,
32 +or
33 +.Dq duplex .
34 +Default is to look up the method in
35 +.Pa ~/.ssb/manifest.json .
36 +.It Ar method
37 +Method name.
38 +.It Op Ar argument ...
39 +Arguments to pass to the method call. Each argument must be JSON-encoded.
40 +.El
41 +.Sh ENVIRONMENT
42 +.Bl -tag
43 +.It Ev ssb_appname
44 +Name of the app. Default is
45 +.Dq ssb .
46 +Used to construct the app's directory if
47 +.Ev ssb_path
48 +is not present.
49 +.It Ev ssb_path
50 +Path to the app's directory. Default is to use
51 +.Ev ssb_appname to construct the path as
52 +.Dq ~/.<ssb_appname>
53 +.El
54 +.Sh FILES
55 +.Bl -tag -width -indent
56 +.It Pa ~/.ssb/secret
57 +Your private key, used for authenticating to the server with the
58 +secret-handshake protocol.
59 +.It Pa ~/.ssb/manifest.json
60 +A map of method names to method types.
61 +.It Pa ~/.ssb/config
62 +JSON file containing host and port to use if the
63 +.Ar -s
64 +or
65 +.Ar -p
66 +options are not given.
67 +.El
68 +.Pp
69 +The base path
70 +.Dq ~/.ssb/
71 +of these file names may be changed by setting
72 +.Ev ssb_appname
73 +or
74 +.Ev ssb_path .
75 +.Sh EXIT STATUS
76 +.Bl -tag -width Ds
77 +.It 0
78 +The command completed successfully.
79 +.It 1
80 +An error occurred.
81 +.It 2
82 +The command completed with an error.
83 +.El
84 +.Sh AUTHORS
85 +.Nm
86 +was written by
87 +.An cel Aq @f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519 .
88 +.Sh BUGS
89 +.Pp
90 +Please report any bugs by making a post on SSB mentioning the repo,
91 +.Lk \*[REPO]
sbotc.cView
@@ -1,0 +1,856 @@
1 +/*
2 + * sbotc.c
3 + * Copyright (c) 2017 Secure Scuttlebutt Consortium
4 + *
5 + * Usage of the works is permitted provided that this instrument is
6 + * retained with the works, so that any entity that uses the works is
7 + * notified of this instrument.
8 + *
9 + * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
10 + */
11 +
12 +#include <ctype.h>
13 +#include <err.h>
14 +#include <errno.h>
15 +#include <fcntl.h>
16 +#include <limits.h>
17 +#include <netdb.h>
18 +#include <netinet/in.h>
19 +#include <stdarg.h>
20 +#include <stdbool.h>
21 +#include <stdio.h>
22 +#include <stdlib.h>
23 +#include <string.h>
24 +#include <sys/select.h>
25 +#include <sys/socket.h>
26 +#include <sys/stat.h>
27 +#include <unistd.h>
28 +
29 +#include <sodium.h>
30 +
31 +#include "base64.h"
32 +#include "jsmn.h"
33 +
34 +#define BOXS_MAXLEN 4096
35 +
36 +#define write_buf(fd, buf) \
37 + write_all(fd, buf, sizeof(buf))
38 +
39 +struct boxs_header {
40 + uint16_t len;
41 + uint8_t mac[16];
42 +};
43 +
44 +struct boxs {
45 + int s;
46 + unsigned char encrypt_key[32];
47 + unsigned char decrypt_key[32];
48 + unsigned char nonce1[24];
49 + unsigned char nonce2[24];
50 + unsigned char rx_nonce[24];
51 + unsigned char rx_buf[BOXS_MAXLEN];
52 + size_t rx_buf_pos;
53 + size_t rx_buf_len;
54 +};
55 +
56 +enum pkt_type {
57 + pkt_type_buffer = 0,
58 + pkt_type_string = 1,
59 + pkt_type_json = 2,
60 +};
61 +
62 +enum pkt_flags {
63 + pkt_flags_buffer = 0,
64 + pkt_flags_string = 1,
65 + pkt_flags_json = 2,
66 + pkt_flags_end = 4,
67 + pkt_flags_stream = 8,
68 +};
69 +
70 +struct pkt_header {
71 + uint32_t len;
72 + int32_t req;
73 +} __attribute__((packed));
74 +
75 +enum muxrpc_type {
76 + muxrpc_type_async,
77 + muxrpc_type_source,
78 + muxrpc_type_sink,
79 + muxrpc_type_duplex,
80 +};
81 +
82 +enum stream_state {
83 + stream_state_open,
84 + stream_state_ended_ok,
85 + stream_state_ended_error,
86 +};
87 +
88 +static unsigned char zeros[24] = {0};
89 +
90 +static const unsigned char ssb_cap[] = {
91 + 0xd4, 0xa1, 0xcb, 0x88, 0xa6, 0x6f, 0x02, 0xf8,
92 + 0xdb, 0x63, 0x5c, 0xe2, 0x64, 0x41, 0xcc, 0x5d,
93 + 0xac, 0x1b, 0x08, 0x42, 0x0c, 0xea, 0xac, 0x23,
94 + 0x08, 0x39, 0xb7, 0x55, 0x84, 0x5a, 0x9f, 0xfb
95 +};
96 +
97 +static void usage() {
98 + fputs("usage: sbotc [-s <host>] [-p <port>] [-k <key>] [-t <type>] "
99 + "<method> [<argument>...]\n", stderr);
100 + exit(EXIT_FAILURE);
101 +}
102 +
103 +static int auth_keypair(unsigned char *pk, unsigned char *sk, unsigned char *seed) {
104 + unsigned char pk_ed[32], sk_ed[32];
105 + int rc = crypto_sign_seed_keypair(pk_ed, sk_ed, seed);
106 + rc |= crypto_sign_ed25519_pk_to_curve25519(pk, pk_ed);
107 + rc |= crypto_sign_ed25519_sk_to_curve25519(sk, sk_ed);
108 + return rc;
109 +}
110 +
111 +static int tcp_connect(const char *host, const char *port) {
112 + struct addrinfo hints;
113 + struct addrinfo *result, *rp;
114 + int s;
115 + int fd;
116 + int err;
117 +
118 + memset(&hints, 0, sizeof(hints));
119 + hints.ai_family = AF_UNSPEC;
120 + hints.ai_protocol = IPPROTO_TCP;
121 +
122 + s = getaddrinfo(host, port, &hints, &result);
123 + if (s < 0) errx(1, "unable to resolve host: %s", gai_strerror(s));
124 +
125 + for (rp = result; rp; rp = rp->ai_next) {
126 + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
127 + if (fd < 0) continue;
128 + if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) break;
129 + err = errno;
130 + close(fd);
131 + errno = err;
132 + }
133 + if (rp == NULL) fd = -1;
134 +
135 + freeaddrinfo(result);
136 + return fd;
137 +}
138 +
139 +static int read_all(int fd, void *buf, size_t count) {
140 + ssize_t nbytes;
141 + while (count > 0) {
142 + nbytes = read(fd, buf, count);
143 + if (nbytes == 0) { errno = EPIPE; return -1; }
144 + if (nbytes < 0 && errno == EINTR) continue;
145 + if (nbytes < 0) return -1;
146 + buf += nbytes;
147 + count -= nbytes;
148 + }
149 + return 0;
150 +}
151 +
152 +static int write_all(int fd, const void *buf, size_t count) {
153 + ssize_t nbytes;
154 + while (count > 0) {
155 + nbytes = write(fd, buf, count);
156 + if (nbytes < 0 && errno == EINTR) continue;
157 + if (nbytes < 0) return -1;
158 + buf += nbytes;
159 + count -= nbytes;
160 + }
161 + return 0;
162 +}
163 +
164 +static void shs_connect(int s, const unsigned char pubkey[32], const unsigned char seckey[64], const unsigned char appkey[32], const unsigned char server_pubkey[32], struct boxs *bs) {
165 + int rc;
166 + unsigned char local_app_mac[32], remote_app_mac[32];
167 +
168 + unsigned char kx_pk[32], kx_sk[32];
169 + unsigned char seed[32];
170 + randombytes_buf(seed, sizeof(seed));
171 + rc = auth_keypair(kx_pk, kx_sk, seed);
172 + if (rc < 0) errx(1, "failed to generate auth keypair");
173 +
174 + rc = crypto_auth(local_app_mac, kx_pk, 32, appkey);
175 + if (rc < 0) err(1, "failed to generate app mac");
176 +
177 + // send challenge
178 + unsigned char buf[64];
179 + memcpy(buf, local_app_mac, 32);
180 + memcpy(buf+32, kx_pk, 32);
181 + rc = write_all(s, buf, sizeof(buf));
182 + if (rc < 0) err(1, "failed to send challenge");
183 +
184 + // recv challenge
185 + unsigned char remote_kx_pk[32];
186 + rc = read_all(s, buf, sizeof(buf));
187 + if (rc < 0) err(1, "challenge not accepted");
188 + memcpy(remote_app_mac, buf, 32);
189 + memcpy(remote_kx_pk, buf+32, 32);
190 + rc = crypto_auth_verify(buf, remote_kx_pk, 32, appkey);
191 + if (rc < 0) errx(1, "wrong protocol (version?)");
192 +
193 + // send auth
194 +
195 + unsigned char secret[32];
196 + rc = crypto_scalarmult(secret, kx_sk, remote_kx_pk);
197 + if (rc < 0) errx(1, "failed to derive shared secret");
198 +
199 + unsigned char remote_pk_curve[32];
200 + rc = crypto_sign_ed25519_pk_to_curve25519(remote_pk_curve, server_pubkey);
201 + if (rc < 0) errx(1, "failed to curvify remote public key");
202 +
203 + unsigned char a_bob[32];
204 + rc = crypto_scalarmult(a_bob, kx_sk, remote_pk_curve);
205 + if (rc < 0) errx(1, "failed to derive a_bob");
206 +
207 + unsigned char secret2a[96];
208 + memcpy(secret2a, appkey, 32);
209 + memcpy(secret2a+32, secret, 32);
210 + memcpy(secret2a+64, a_bob, 32);
211 +
212 + unsigned char secret2[32];
213 + rc = crypto_hash_sha256(secret2, secret2a, sizeof(secret2a));
214 + if (rc < 0) errx(1, "failed to hash secret2");
215 +
216 + unsigned char shash[32];
217 + rc = crypto_hash_sha256(shash, secret, sizeof(secret));
218 + if (rc < 0) errx(1, "failed to hash secret");
219 +
220 + unsigned char signed1[96];
221 + memcpy(signed1, appkey, 32);
222 + memcpy(signed1+32, server_pubkey, 32);
223 + memcpy(signed1+64, shash, 32);
224 +
225 + unsigned char sig[64];
226 + rc = crypto_sign_detached(sig, NULL, signed1, sizeof(signed1), seckey);
227 + if (rc < 0) errx(1, "failed to sign inner hello");
228 +
229 + unsigned char hello[96];
230 + memcpy(hello, sig, 64);
231 + memcpy(hello+64, pubkey, 32);
232 +
233 + unsigned char boxed_auth[112];
234 + rc = crypto_secretbox_easy(boxed_auth, hello, sizeof(hello), zeros, secret2);
235 + if (rc < 0) errx(1, "failed to box hello");
236 +
237 + rc = write_all(s, boxed_auth, sizeof(boxed_auth));
238 + if (rc < 0) errx(1, "failed to send auth");
239 +
240 + // verify accept
241 +
242 + unsigned char boxed_okay[80];
243 + rc = read_all(s, boxed_okay, sizeof(boxed_okay));
244 + if (rc < 0) err(1, "hello not accepted");
245 +
246 + unsigned char local_sk_curve[32];
247 + rc = crypto_sign_ed25519_sk_to_curve25519(local_sk_curve, seckey);
248 + if (rc < 0) errx(1, "failed to curvify local secret key");
249 +
250 + unsigned char b_alice[32];
251 + rc = crypto_scalarmult(b_alice, local_sk_curve, remote_kx_pk);
252 + if (rc < 0) errx(1, "failed to derive b_alice");
253 +
254 + unsigned char secret3a[128];
255 + memcpy(secret3a, appkey, 32);
256 + memcpy(secret3a+32, secret, 32);
257 + memcpy(secret3a+64, a_bob, 32);
258 + memcpy(secret3a+96, b_alice, 32);
259 +
260 + unsigned char secret3[32];
261 + rc = crypto_hash_sha256(secret3, secret3a, sizeof(secret3a));
262 + if (rc < 0) errx(1, "failed to hash secret3");
263 +
264 + rc = crypto_secretbox_open_easy(sig, boxed_okay, sizeof(boxed_okay), zeros, secret3);
265 + if (rc < 0) errx(1, "failed to unbox the okay");
266 +
267 + unsigned char signed2[160];
268 + memcpy(signed2, appkey, 32);
269 + memcpy(signed2+32, hello, 96);
270 + memcpy(signed2+128, shash, 32);
271 +
272 + rc = crypto_sign_verify_detached(sig, signed2, sizeof(signed2), server_pubkey);
273 + if (rc < 0) errx(1, "server not authenticated");
274 +
275 + rc = crypto_hash_sha256(secret, secret3, 32);
276 + if (rc < 0) errx(1, "failed to hash secret3");
277 +
278 + unsigned char enc_key_hashed[64];
279 + memcpy(enc_key_hashed, secret, 32);
280 + memcpy(enc_key_hashed+32, server_pubkey, 32);
281 + rc = crypto_hash_sha256(bs->encrypt_key, enc_key_hashed, 64);
282 + if (rc < 0) errx(1, "failed to hash the encrypt key");
283 +
284 + unsigned char dec_key_hashed[64];
285 + memcpy(dec_key_hashed, secret, 32);
286 + memcpy(dec_key_hashed+32, pubkey, 32);
287 + rc = crypto_hash_sha256(bs->decrypt_key, dec_key_hashed, 64);
288 + if (rc < 0) errx(1, "failed to hash the decrypt key");
289 +
290 + memcpy(bs->nonce1, remote_app_mac, 24);
291 + memcpy(bs->nonce2, remote_app_mac, 24);
292 + memcpy(bs->rx_nonce, local_app_mac, 24);
293 +
294 + bs->rx_buf_pos = 0;
295 + bs->rx_buf_len = 0;
296 + bs->s = s;
297 +}
298 +
299 +static int pubkey_decode(const char *key_str, unsigned char key[32]) {
300 + if (!key_str) { errno = EPROTO; return -1; }
301 + if (!*key_str) { errno = EPROTO; return -1; }
302 + if (*key_str == '@') key_str++;
303 + size_t len = strlen(key_str);
304 + if (len == 52 && strcmp(key_str+44, ".ed25519") == 0) {}
305 + else if (len != 44) { errno = EMSGSIZE; return -1; }
306 + return base64_decode(key_str, 44, key, 32);
307 +}
308 +
309 +static jsmntok_t *json_lookup(const char *buf, jsmntok_t *tok, const char *prop, size_t prop_len) {
310 + jsmntok_t *end = tok + tok->size + 1;
311 + if (tok->type != JSMN_OBJECT) { errno = EPROTO; return NULL; }
312 + tok++;
313 + while (tok < end) {
314 + if (tok + 1 >= end) { errno = EPROTO; return NULL; }
315 + if (tok->type == JSMN_STRING
316 + && tok->end - tok->start == (int)prop_len
317 + && memcmp(buf + tok->start, prop, prop_len) == 0)
318 + return tok + 1;
319 + tok += tok->size + 1;
320 + end += tok->size;
321 + }
322 + return NULL;
323 +}
324 +
325 +static ssize_t json_get_value(const char *buf, const char *path, const char **value) {
326 + static const int num_tokens = 1024;
327 + jsmntok_t tokens[num_tokens], *tok = tokens;
328 + jsmn_parser parser;
329 +
330 + jsmn_init(&parser);
331 + switch (jsmn_parse(&parser, buf, tokens, num_tokens)) {
332 + case JSMN_ERROR_NOMEM: errno = ENOMEM; return -1;
333 + case JSMN_ERROR_INVAL: errno = EINVAL; return -1;
334 + case JSMN_ERROR_PART: errno = EMSGSIZE; return -1;
335 + case JSMN_SUCCESS: break;
336 + default: errno = EPROTO; return -1;
337 + }
338 +
339 + while (*path) {
340 + const char *end = strchr(path, '.');
341 + size_t part_len = end ? end - path : strlen(path);
342 + tok = json_lookup(buf, tok, path, part_len);
343 + if (!tok) { errno = ENOMSG; return -1; }
344 + path += part_len;
345 + if (*path == '.') path++;
346 + }
347 +
348 + *value = buf + tok->start;
349 + return tok->end - tok->start;
350 +}
351 +
352 +static void get_app_dir(char *dir, size_t len) {
353 + const char *path, *home, *appname;
354 + int rc;
355 + path = getenv("ssb_path");
356 + if (path) {
357 + if (strlen(path) > len) errx(1, "ssb_path too long");
358 + strncpy(dir, path, len);
359 + return;
360 + }
361 + home = getenv("HOME");
362 + if (!home) home = ".";
363 + appname = getenv("ssb_appname");
364 + if (!appname) appname = "ssb";
365 + rc = snprintf(dir, len, "%s/.%s", home, appname);
366 + if (rc < 0) err(1, "failed to get app dir");
367 + if (rc >= len) errx(1, "path to app dir too long");
368 +}
369 +
370 +static ssize_t read_file(char *buf, size_t len, const char *fmt, ...) {
371 + va_list ap;
372 + int rc;
373 + struct stat st;
374 + int fd;
375 +
376 + va_start(ap, fmt);
377 + rc = vsnprintf(buf, len, fmt, ap);
378 + va_end(ap);
379 + if (rc < 0) return -1;
380 + if (rc >= len) { errno = ENAMETOOLONG; return -1; }
381 +
382 + rc = stat(buf, &st);
383 + if (rc < 0) return -1;
384 + if (st.st_size > len-1) { errno = EMSGSIZE; return -1; }
385 +
386 + fd = open(buf, O_RDONLY);
387 + if (fd < 0) return -1;
388 +
389 + rc = read_all(fd, buf, st.st_size);
390 + if (rc < 0) return -1;
391 + buf[st.st_size] = '\0';
392 +
393 + close(fd);
394 + return st.st_size;
395 +}
396 +
397 +
398 +static void read_private_key(const char *dir, unsigned char pk[64]) {
399 + ssize_t len;
400 + char buf[8192];
401 + const char *pk_b64;
402 + int rc;
403 + ssize_t key_len;
404 + char *line;
405 +
406 + len = read_file(buf, sizeof(buf), "%s/secret", dir);
407 + if (len < 0) err(1, "failed to read secret file");
408 +
409 + // strip comments
410 + for (line = buf; *line; ) {
411 + if (*line == '#') while (*line && *line != '\n') *line++ = ' ';
412 + else while (*line && *line++ != '\n');
413 + }
414 +
415 + key_len = json_get_value(buf, "private", &pk_b64);
416 + if (key_len < 0) err(1, "unable to read private key");
417 +
418 + if (key_len > 8 && memcmp(pk_b64 + key_len - 8, ".ed25519", 8) == 0)
419 + key_len -= 8;
420 + rc = base64_decode(pk_b64, key_len, pk, 64);
421 + if (rc < 0) err(1, "unable to decode private key");
422 +}
423 +
424 +static void increment_nonce(uint8_t nonce[24]) {
425 + int i;
426 + for (i = 23; i >= 0 && nonce[i] == 0xff; i--) nonce[i] = 0;
427 + if (i >= 0) nonce[i]++;
428 +}
429 +
430 +static void bs_write_packet(struct boxs *bs, const unsigned char *buf, uint16_t len) {
431 + size_t boxed_len = len + 34;
432 + unsigned char boxed[boxed_len];
433 + increment_nonce(bs->nonce2);
434 + int rc = crypto_secretbox_easy(boxed + 18, buf, len, bs->nonce2, bs->encrypt_key);
435 + if (rc < 0) errx(1, "failed to box packet data");
436 + struct boxs_header header;
437 + header.len = htons(len);
438 + memcpy(header.mac, boxed + 18, 16);
439 + rc = crypto_secretbox_easy(boxed, (unsigned char *)&header, 18, bs->nonce1, bs->encrypt_key);
440 + if (rc < 0) errx(1, "failed to box packet header");
441 + increment_nonce(bs->nonce1);
442 + increment_nonce(bs->nonce1);
443 + increment_nonce(bs->nonce2);
444 + rc = write_all(bs->s, boxed, boxed_len);
445 + if (rc < 0) err(1, "failed to write boxed packet");
446 +}
447 +
448 +static int bs_read_packet(struct boxs *bs, void *buf, size_t *lenp) {
449 + unsigned char boxed_header[34];
450 + struct boxs_header header;
451 + int rc = read_all(bs->s, boxed_header, 34);
452 + if (rc < 0 && errno == EPIPE) errx(1, "unexpected end of parent stream");
453 + if (rc < 0) err(1, "failed to read boxed packet header");
454 + rc = crypto_secretbox_open_easy((unsigned char *)&header, boxed_header, 34, bs->rx_nonce, bs->decrypt_key);
455 + if (rc < 0) errx(1, "failed to unbox packet header");
456 + increment_nonce(bs->rx_nonce);
457 + if (header.len == 0 && !memcmp(header.mac, zeros, 16)) { errno = EPIPE; return -1; }
458 + size_t len = ntohs(header.len);
459 + if (len > BOXS_MAXLEN) errx(1, "received boxed packet too large");
460 + unsigned char boxed_data[len + 16];
461 + rc = read_all(bs->s, boxed_data + 16, len);
462 + if (rc < 0) err(1, "failed to read boxed packet data");
463 + memcpy(boxed_data, header.mac, 16);
464 + rc = crypto_secretbox_open_easy(buf, boxed_data, len+16, bs->rx_nonce, bs->decrypt_key);
465 + if (rc < 0) errx(1, "failed to unbox packet data");
466 + increment_nonce(bs->rx_nonce);
467 + *lenp = len;
468 + return 0;
469 +}
470 +
471 +static int bs_read(struct boxs *bs, char *buf, size_t len) {
472 + size_t remaining;
473 + while (len > 0) {
474 + remaining = bs->rx_buf_len > len ? len : bs->rx_buf_len;
475 + if (buf) memcpy(buf, bs->rx_buf + bs->rx_buf_pos, remaining);
476 + bs->rx_buf_len -= remaining;
477 + bs->rx_buf_pos += remaining;
478 + len -= remaining;
479 + buf += remaining;
480 + if (len == 0) return 0;
481 + if (bs_read_packet(bs, bs->rx_buf, &bs->rx_buf_len) < 0) return -1;
482 + bs->rx_buf_pos = 0;
483 + }
484 + return 0;
485 +}
486 +
487 +static int bs_read_out(struct boxs *bs, int fd, size_t len) {
488 + size_t chunk;
489 + char buf[4096];
490 + int rc;
491 + while (len > 0) {
492 + chunk = len > sizeof(buf) ? sizeof(buf) : len;
493 + rc = bs_read(bs, buf, chunk);
494 + if (rc < 0) return -1;
495 + rc = write_all(fd, buf, chunk);
496 + if (rc < 0) return -1;
497 + len -= chunk;
498 + }
499 + return 0;
500 +}
501 +
502 +static int bs_read_error(struct boxs *bs, int errfd, enum pkt_flags flags, size_t len) {
503 + // suppress printing "true" indicating end without error
504 + if (flags & pkt_flags_json && len == 4) {
505 + char buf[4];
506 + if (bs_read(bs, buf, 4) < 0) return -1;
507 + if (strncmp(buf, "true", 0) == 0) {
508 + return 0;
509 + }
510 + if (write_all(errfd, buf, 4) < 0) return -1;
511 + } else {
512 + if (bs_read_out(bs, errfd, len) < 0) return -1;
513 + }
514 + if (flags & (pkt_flags_json | pkt_flags_string)) {
515 + if (write_buf(errfd, "\n") < 0) return -1;
516 + }
517 + return 1;
518 +}
519 +
520 +static void bs_write(struct boxs *bs, const unsigned char *buf, size_t len) {
521 + while (len > 0) {
522 + size_t l = len > BOXS_MAXLEN ? BOXS_MAXLEN : len;
523 + bs_write_packet(bs, buf, l);
524 + len -= l;
525 + buf += l;
526 + }
527 +}
528 +
529 +static void ps_write(struct boxs *bs, const char *data, size_t len, enum pkt_type type, int req_id, bool stream, bool end) {
530 + size_t out_len = 9 + len;
531 + unsigned char out_buf[out_len];
532 + struct pkt_header header = {htonl(len), htonl(req_id)};
533 + out_buf[0] = (stream << 3) | (end << 2) | (type & 3);
534 + memcpy(out_buf+1, &header, 8);
535 + memcpy(out_buf+9, data, len);
536 + bs_write(bs, out_buf, out_len);
537 +}
538 +
539 +static int ps_read_header(struct boxs *bs, size_t *len, int *req_id, enum pkt_flags *flags) {
540 + char buf[9];
541 + struct pkt_header header;
542 + if (bs_read(bs, buf, sizeof(buf)) < 0) return -1;
543 + memcpy(&header, buf+1, 8);
544 + if (len) *len = ntohl(header.len);
545 + if (req_id) *req_id = ntohl(header.req);
546 + if (flags) *flags = buf[0];
547 + return 0;
548 +}
549 +
550 +static void muxrpc_call(struct boxs *bs, const char *method, const char *argument, enum muxrpc_type type, const char *typestr, int req_id) {
551 + char req[8096];
552 + ssize_t reqlen;
553 + bool is_request = type == muxrpc_type_async;
554 +
555 + if (is_request) {
556 + reqlen = snprintf(req, sizeof(req),
557 + "{\"name\":%s,\"args\":%s}",
558 + method, argument);
559 + } else {
560 + reqlen = snprintf(req, sizeof(req),
561 + "{\"name\":%s,\"args\":%s,\"type\":\"%s\"}",
562 + method, argument, typestr);
563 + }
564 + if (reqlen < 0) err(1, "failed to construct request");
565 + if (reqlen >= sizeof(req)) errx(1, "request too large");
566 +
567 + ps_write(bs, req, reqlen, pkt_type_json, req_id, !is_request, false);
568 +}
569 +
570 +static void ps_reject(struct boxs *bs, size_t len, int32_t req, enum pkt_flags flags) {
571 + // ignore the packet. if this is a request, the substream on the other end
572 + // will just have to wait until the rpc connection closes.
573 + write_buf(STDERR_FILENO, "ignoring packet: ");
574 + int rc = bs_read_out(bs, STDERR_FILENO, len);
575 + if (rc < 0) err(1, "bs_read_out");
576 + write_buf(STDERR_FILENO, "\n");
577 +}
578 +
579 +static enum stream_state muxrpc_read_source_1(struct boxs *bs, int outfd, int req_id) {
580 + enum pkt_flags flags;
581 + size_t len;
582 + int32_t req;
583 + int rc = ps_read_header(bs, &len, &req, &flags);
584 + if (rc < 0) err(1, "ps_read_header");
585 + if (req == 0 && len == 0) {
586 + warnx("unexpected end of parent stream");
587 + return stream_state_ended_error;
588 + }
589 + if (req != -req_id) {
590 + ps_reject(bs, len, req, flags);
591 + return stream_state_open;
592 + }
593 + if (flags & pkt_flags_end) {
594 + rc = bs_read_error(bs, STDERR_FILENO, flags, len);
595 + if (rc < 0) err(1, "bs_read_error");
596 + if (rc == 1) return stream_state_ended_error;
597 + return stream_state_ended_ok;
598 + }
599 + rc = bs_read_out(bs, outfd, len);
600 + if (rc < 0) err(1, "bs_read_out");
601 + if (flags & (pkt_flags_json | pkt_flags_string)) {
602 + rc = write_buf(outfd, "\n");
603 + if (rc < 0) err(1, "write_buf");
604 + }
605 + return stream_state_open;
606 +}
607 +
608 +static int muxrpc_read_source(struct boxs *bs, int outfd, int req_id) {
609 + enum stream_state state;
610 + while ((state = muxrpc_read_source_1(bs, outfd, req_id)) == stream_state_open);
611 + return state == stream_state_ended_ok ? 0 :
612 + state == stream_state_ended_error ? 2 : 1;
613 +}
614 +
615 +static int muxrpc_read_async(struct boxs *bs, int outfd, int req_id) {
616 + enum pkt_flags flags;
617 + size_t len;
618 + int32_t req;
619 + int rc;
620 +
621 + while (1) {
622 + rc = ps_read_header(bs, &len, &req, &flags);
623 + if (rc < 0) err(1, "ps_read_header");
624 + if (req == -req_id) break;
625 + if (req == 0 && len == 0) errx(1, "unexpected end of parent stream");
626 + ps_reject(bs, len, req, flags);
627 + }
628 + if (flags & pkt_flags_end) {
629 + rc = bs_read_error(bs, STDERR_FILENO, flags, len);
630 + if (rc < 0) err(1, "bs_read_error");
631 + if (rc == 1) return 2;
632 + return 1;
633 + }
634 + rc = bs_read_out(bs, outfd, len);
635 + if (rc < 0) err(1, "bs_read_out");
636 + if (flags & (pkt_flags_json | pkt_flags_string)) {
637 + rc = write_buf(outfd, "\n");
638 + if (rc < 0) err(1, "write_buf");
639 + }
640 + return 0;
641 +}
642 +
643 +static enum stream_state muxrpc_write_sink_1(struct boxs *bs, int infd, int req_id) {
644 + char buf[4096];
645 + ssize_t sz = read(infd, buf, sizeof(buf));
646 + if (sz < 0) err(1, "read");
647 + if (sz == 0) {
648 + ps_write(bs, "true", 4, pkt_type_json, req_id, true, true);
649 + return stream_state_ended_ok;
650 + }
651 + ps_write(bs, buf, sz, pkt_type_buffer, req_id, true, false);
652 + return stream_state_open;
653 +}
654 +
655 +static int muxrpc_write_sink(struct boxs *bs, int infd, int req_id) {
656 + int rc;
657 + fd_set rd;
658 + int sfd = bs->s;
659 + int maxfd = infd > sfd ? infd : sfd;
660 + enum stream_state in = stream_state_open;
661 + enum stream_state out = stream_state_open;
662 +
663 + while (out == stream_state_open) {
664 + FD_ZERO(&rd);
665 + if (in == stream_state_open) FD_SET(infd, &rd);
666 + if (out == stream_state_open) FD_SET(sfd, &rd);
667 + rc = select(maxfd + 1, &rd, 0, 0, NULL);
668 + if (rc < 0) err(1, "select");
669 + if (FD_ISSET(infd, &rd)) in = muxrpc_write_sink_1(bs, infd, req_id);
670 + if (FD_ISSET(sfd, &rd)) out = muxrpc_read_source_1(bs, -1, req_id);
671 + }
672 +
673 + return in == stream_state_ended_ok && out == stream_state_ended_ok ? 0 :
674 + in == stream_state_ended_error || out == stream_state_ended_error ? 2 : 1;
675 +}
676 +
677 +static int muxrpc_duplex(struct boxs *bs, int infd, int outfd, int req_id) {
678 + int rc;
679 + fd_set rd;
680 + int sfd = bs->s;
681 + int maxfd = infd > sfd ? infd : sfd;
682 + enum stream_state in = stream_state_open;
683 + enum stream_state out = stream_state_open;
684 +
685 + while (out == stream_state_open
686 + || (in == stream_state_open && out != stream_state_ended_error)) {
687 + FD_ZERO(&rd);
688 + if (in == stream_state_open) FD_SET(infd, &rd);
689 + if (out == stream_state_open) FD_SET(sfd, &rd);
690 + rc = select(maxfd + 1, &rd, 0, 0, NULL);
691 + if (rc < 0) err(1, "select");
692 + if (FD_ISSET(infd, &rd)) in = muxrpc_write_sink_1(bs, infd, req_id);
693 + if (FD_ISSET(sfd, &rd)) out = muxrpc_read_source_1(bs, outfd, req_id);
694 + }
695 +
696 + return in == stream_state_ended_ok && out == stream_state_ended_ok ? 0 :
697 + in == stream_state_ended_error || out == stream_state_ended_error ? 2 : 1;
698 +}
699 +
700 +static int method_to_json(char *out, size_t outlen, const char *str) {
701 + // blobs.get => ["blobs", "get"]
702 + size_t i = 0;
703 + char c;
704 + if (i+2 > outlen) return -1;
705 + out[i++] = '[';
706 + out[i++] = '"';
707 + while ((c = *str++)) {
708 + if (c == '.') {
709 + if (i+3 > outlen) return -1;
710 + out[i++] = '"';
711 + out[i++] = ',';
712 + out[i++] = '"';
713 + } else if (c == '"') {
714 + if (i+2 > outlen) return -1;
715 + out[i++] = '\\';
716 + out[i++] = '"';
717 + } else {
718 + if (i+1 > outlen) return -1;
719 + out[i++] = c;
720 + }
721 + }
722 + if (i+3 > outlen) return -1;
723 + out[i++] = '"';
724 + out[i++] = ']';
725 + out[i++] = '\0';
726 + return i;
727 +}
728 +
729 +static int args_to_json_length(int argc, char *argv[]) {
730 + int i = 0;
731 + int len = 3; // "[]\0"
732 + for (i = 0; i < argc; i++)
733 + len += strlen(argv[i])+1;
734 + return len;
735 +}
736 +
737 +static int args_to_json(char *out, size_t outlen, unsigned int argc, char *argv[]) {
738 + size_t i = 0;
739 + size_t j;
740 + if (i+1 > outlen) return -1;
741 + out[i++] = '[';
742 + for (j = 0; j < argc; j++) {
743 + size_t len = strlen(argv[j]);
744 + if (j > 0) out[i++] = ',';
745 + if (i+len > outlen) return -1;
746 + strncpy(out+i, argv[j], len);
747 + i += len;
748 + }
749 + if (i+2 > outlen) return -1;
750 + out[i++] = ']';
751 + out[i++] = '\0';
752 + return i;
753 +}
754 +
755 +int main(int argc, char *argv[]) {
756 + int i, s, rc;
757 + const char *key = NULL;
758 + const char *host = NULL;
759 + const char *port = "8008";
760 + const char *typestr = NULL, *methodstr;
761 + size_t argument_len;
762 + unsigned char private_key[64];
763 + unsigned char public_key[32];
764 + unsigned char remote_key[32];
765 + enum muxrpc_type type;
766 + char method[256];
767 + char app_dir[_POSIX_PATH_MAX];
768 + ssize_t len;
769 +
770 + get_app_dir(app_dir, sizeof(app_dir));
771 +
772 + char config_buf[8192];
773 + len = read_file(config_buf, sizeof(config_buf), "%s/config", app_dir);
774 + if (len > 0) {
775 + ssize_t host_len = json_get_value(config_buf, "host", &host);
776 + ssize_t port_len = json_get_value(config_buf, "port", &port);
777 + if (host_len >= 0) ((char *)host)[host_len] = '\0';
778 + if (port_len >= 0) ((char *)port)[port_len] = '\0';
779 + } else if (len < 0 && errno != ENOENT) {
780 + err(1, "failed to read config");
781 + }
782 +
783 + for (i = 1; (i + 1 < argc) && (argv[i][0] == '-'); i++) {
784 + switch (argv[i][1]) {
785 + case 's': host = argv[++i]; break;
786 + case 'k': key = argv[++i]; break;
787 + case 'p': port = argv[++i]; break;
788 + case 't': typestr = argv[++i]; break;
789 + default: usage();
790 + }
791 + }
792 + if (i < argc) methodstr = argv[i++]; else usage();
793 +
794 + argument_len = args_to_json_length(argc-i, argv+i);
795 + char argument[argument_len];
796 + rc = args_to_json(argument, sizeof(argument), argc-i, argv+i);
797 + if (rc < 0) errx(0, "unable to collect arguments");
798 +
799 + char manifest_buf[8192];
800 + if (!typestr) {
801 + len = read_file(manifest_buf, sizeof(manifest_buf),
802 + "%s/manifest.json", app_dir);
803 + if (len < 0) err(1, "failed to read manifest file");
804 +
805 + ssize_t type_len = json_get_value(manifest_buf, methodstr, &typestr);
806 + if (!typestr && errno == ENOMSG) errx(1,
807 + "unable to find method '%s' in manifest", methodstr);
808 + if (!typestr) err(1, "unable to read manifest");
809 + ((char *)typestr)[type_len] = '\0';
810 + }
811 + if (strcmp(typestr, "sync") == 0) type = muxrpc_type_async;
812 + else if (strcmp(typestr, "async") == 0) type = muxrpc_type_async;
813 + else if (strcmp(typestr, "sink") == 0) type = muxrpc_type_sink;
814 + else if (strcmp(typestr, "source") == 0) type = muxrpc_type_source;
815 + else if (strcmp(typestr, "duplex") == 0) type = muxrpc_type_duplex;
816 + else errx(1, "type must be one of <async|sink|source|duplex>");
817 +
818 + rc = method_to_json(method, sizeof(method), methodstr);
819 + if (rc < 0) errx(0, "unable to convert method name");
820 +
821 + read_private_key(app_dir, private_key);
822 +
823 + memcpy(public_key, private_key+32, 32);
824 + if (key) {
825 + rc = pubkey_decode(key, remote_key);
826 + if (rc < 0) err(1, "unable to decode remote key '%s'", key);
827 + } else {
828 + memcpy(remote_key, public_key, 32);
829 + }
830 +
831 + s = tcp_connect(host, port);
832 + if (s < 0) err(1, "tcp_connect");
833 +
834 + struct boxs bs;
835 + shs_connect(s, public_key, private_key, ssb_cap, remote_key, &bs);
836 +
837 + muxrpc_call(&bs, method, argument, type, typestr, 1);
838 +
839 + switch (type) {
840 + case muxrpc_type_async:
841 + rc = muxrpc_read_async(&bs, STDOUT_FILENO, 1);
842 + break;
843 + case muxrpc_type_source:
844 + rc = muxrpc_read_source(&bs, STDOUT_FILENO, 1);
845 + break;
846 + case muxrpc_type_sink:
847 + rc = muxrpc_write_sink(&bs, STDIN_FILENO, 1);
848 + break;
849 + case muxrpc_type_duplex:
850 + rc = muxrpc_duplex(&bs, STDIN_FILENO, STDOUT_FILENO, 1);
851 + break;
852 + }
853 +
854 + close(s);
855 + return rc;
856 +}

Built with git-ssb-web