sbotc.cView |
---|
| 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 … | + |
| 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 … | + |
| 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 … | + |
| 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 … | + |
| 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 … | + |
| 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 … | + |
| 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 … | + |
| 572 … | + |
| 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 … | + |
| 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; |
| 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 … | +} |