Files: cec6448afd2f0c42739c510e602283f56518510c / sbotc.c
42827 bytesRaw
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 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | write_all(fd, buf, sizeof(buf)-1) |
42 | |
43 | struct boxs_header { |
44 | uint16_t len; |
45 | uint8_t mac[16]; |
46 | }; |
47 | |
48 | struct boxs { |
49 | int s; |
50 | unsigned char encrypt_key[32]; |
51 | unsigned char decrypt_key[32]; |
52 | unsigned char nonce1[24]; |
53 | unsigned char nonce2[24]; |
54 | unsigned char rx_nonce[24]; |
55 | unsigned char rx_buf[BOXS_MAXLEN]; |
56 | size_t rx_buf_pos; |
57 | size_t rx_buf_len; |
58 | bool noauth; |
59 | bool wrote_goodbye; |
60 | }; |
61 | |
62 | enum pkt_type { |
63 | pkt_type_buffer = 0, |
64 | pkt_type_string = 1, |
65 | pkt_type_json = 2, |
66 | }; |
67 | |
68 | enum pkt_flags { |
69 | pkt_flags_buffer = 0, |
70 | pkt_flags_string = 1, |
71 | pkt_flags_json = 2, |
72 | pkt_flags_end = 4, |
73 | pkt_flags_stream = 8, |
74 | }; |
75 | |
76 | enum muxrpc_type { |
77 | muxrpc_type_async, |
78 | muxrpc_type_source, |
79 | muxrpc_type_sink, |
80 | muxrpc_type_duplex, |
81 | }; |
82 | |
83 | enum stream_state { |
84 | stream_state_open, |
85 | stream_state_ended_ok, |
86 | stream_state_ended_error, |
87 | }; |
88 | |
89 | enum ip_family { |
90 | ip_family_ipv4 = AF_INET, |
91 | ip_family_ipv6 = AF_INET6, |
92 | ip_family_any = AF_UNSPEC |
93 | }; |
94 | |
95 | static unsigned char zeros[24] = {0}; |
96 | |
97 | static const unsigned char ssb_cap[] = { |
98 | 0xd4, 0xa1, 0xcb, 0x88, 0xa6, 0x6f, 0x02, 0xf8, |
99 | 0xdb, 0x63, 0x5c, 0xe2, 0x64, 0x41, 0xcc, 0x5d, |
100 | 0xac, 0x1b, 0x08, 0x42, 0x0c, 0xea, 0xac, 0x23, |
101 | 0x08, 0x39, 0xb7, 0x55, 0x84, 0x5a, 0x9f, 0xfb |
102 | }; |
103 | |
104 | struct termios orig_tc; |
105 | |
106 | static void reset_termios() { |
107 | int rc = tcsetattr(STDIN_FILENO, TCSANOW, &orig_tc); |
108 | if (rc < 0) warn("tcsetattr"); |
109 | } |
110 | |
111 | static void usage() { |
112 | fputs("usage: sbotc [-j] [-T] [-l] [-r] [-e]\n" |
113 | " [ -n | [-c <cap>] [-k <key>] [-K <keypair_seed>] ]\n" |
114 | " [ [-s <host>] [-p <port>] [ -4 | -6 ] [-d] | [-u <socket_path>] ]\n" |
115 | " [ -a | [-t <type>] <method> [<argument>...] ]\n", stderr); |
116 | exit(EXIT_FAILURE); |
117 | } |
118 | |
119 | static int connect_localhost(const char *port, enum ip_family ip_family) { |
120 | int rc, family, fd, err; |
121 | struct ifaddrs *ifaddr, *ifa; |
122 | rc = getifaddrs(&ifaddr); |
123 | if (rc < 0) return -1; |
124 | int port_n = htons(atoi(port)); |
125 | |
126 | for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { |
127 | if (ifa->ifa_addr == NULL) continue; |
128 | family = ifa->ifa_addr->sa_family; |
129 | socklen_t addrlen; |
130 | if (family == AF_INET) { |
131 | if (ip_family != ip_family_ipv4 && ip_family != ip_family_any) continue; |
132 | addrlen = sizeof(struct sockaddr_in); |
133 | struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr; |
134 | addr->sin_port = port_n; |
135 | } else if (family == AF_INET6) { |
136 | if (ip_family != ip_family_ipv6 && ip_family != ip_family_any) continue; |
137 | addrlen = sizeof(struct sockaddr_in6); |
138 | struct sockaddr_in6 *addr = (struct sockaddr_in6 *)ifa->ifa_addr; |
139 | addr->sin6_port = port_n; |
140 | } else continue; |
141 | fd = socket(family, SOCK_STREAM, IPPROTO_TCP); |
142 | if (fd < 0) continue; |
143 | if (connect(fd, ifa->ifa_addr, addrlen) == 0) break; |
144 | err = errno; |
145 | close(fd); |
146 | errno = err; |
147 | } |
148 | if (ifa == NULL) fd = -1; |
149 | |
150 | freeifaddrs(ifaddr); |
151 | return fd; |
152 | } |
153 | |
154 | static int tcp_connect(const char *host, const char *port, enum ip_family ip_family, bool server) { |
155 | struct addrinfo hints; |
156 | struct addrinfo *result, *rp; |
157 | int s; |
158 | int fd; |
159 | int err, rc; |
160 | |
161 | memset(&hints, 0, sizeof(hints)); |
162 | hints.ai_family = ip_family; |
163 | hints.ai_protocol = IPPROTO_TCP; |
164 | if (server) hints.ai_flags = AI_PASSIVE; |
165 | |
166 | s = getaddrinfo(host, port, &hints, &result); |
167 | if (s < 0) errx(1, "unable to resolve host: %s", gai_strerror(s)); |
168 | |
169 | for (rp = result; rp; rp = rp->ai_next) { |
170 | fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); |
171 | if (fd < 0) continue; |
172 | if (server) { |
173 | rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); |
174 | if (rc < 0) goto error; |
175 | if (bind(fd, rp->ai_addr, rp->ai_addrlen) < 0) goto error; |
176 | if (listen(fd, 1) == 0) break; |
177 | } else { |
178 | if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) break; |
179 | } |
180 | error: |
181 | err = errno; |
182 | close(fd); |
183 | errno = err; |
184 | } |
185 | if (rp == NULL) fd = -1; |
186 | |
187 | freeaddrinfo(result); |
188 | |
189 | if (!server && fd == -1 && errno == ECONNREFUSED && (host == NULL || !strcmp(host, "localhost"))) { |
190 | return connect_localhost(port, ip_family); |
191 | } |
192 | |
193 | if (server && fd > -1) { |
194 | int client = accept(fd, NULL, NULL); |
195 | err = errno; |
196 | close(fd); |
197 | errno = err; |
198 | return client; |
199 | } |
200 | |
201 | return fd; |
202 | } |
203 | |
204 | static const char *socket_path = NULL; |
205 | |
206 | void cleanup() { |
207 | if (socket_path != NULL) { |
208 | int rc = unlink(socket_path); |
209 | if (rc < 0) warn("unlink"); |
210 | } |
211 | } |
212 | |
213 | static int unix_connect(const char *path, bool server) { |
214 | struct sockaddr_un name; |
215 | const size_t path_len = strlen(path); |
216 | int s, rc; |
217 | if (path_len >= sizeof(name.sun_path)-1) errx(1, "socket path too long"); |
218 | s = socket(AF_UNIX, SOCK_STREAM, 0); |
219 | if (s < 0) return -1; |
220 | memset(&name, 0, sizeof(struct sockaddr_un)); |
221 | name.sun_family = AF_UNIX; |
222 | strncpy(name.sun_path, path, sizeof(name.sun_path) - 1); |
223 | if (server) { |
224 | rc = bind(s, (const struct sockaddr *)&name, sizeof name); |
225 | if (rc < 0) return -1; |
226 | rc = listen(s, 1); |
227 | } else { |
228 | rc = connect(s, (const struct sockaddr *)&name, sizeof name); |
229 | } |
230 | if (rc < 0) return -1; |
231 | |
232 | if (server && rc > -1) { |
233 | socket_path = strdup(path); |
234 | if (atexit(cleanup) < 0) warn("atexit"); |
235 | if (signal(SIGINT, exit) == SIG_ERR) warn("signal"); |
236 | int client = accept(s, NULL, NULL); |
237 | int err = errno; |
238 | close(s); |
239 | errno = err; |
240 | return client; |
241 | } |
242 | |
243 | return s; |
244 | } |
245 | |
246 | static int get_socket_path(char *buf, size_t len, const char *app_dir, bool server) { |
247 | struct stat st; |
248 | int sz = snprintf(buf, len-1, "%s/%s", app_dir, "socket"); |
249 | if (sz < 0 || sz >= (int)len-1) err(1, "failed to get socket path"); |
250 | int rc = stat(buf, &st); |
251 | if (!server) { |
252 | if (rc < 0) return -1; |
253 | if (!(st.st_mode & S_IFSOCK)) { errno = EINVAL; return -1; } |
254 | } |
255 | return 0; |
256 | } |
257 | |
258 | static int read_all(int fd, void *buf, size_t count) { |
259 | ssize_t nbytes; |
260 | while (count > 0) { |
261 | nbytes = read(fd, buf, count); |
262 | if (nbytes == 0) { errno = EPIPE; return -1; } |
263 | if (nbytes < 0 && errno == EINTR) continue; |
264 | if (nbytes < 0) return -1; |
265 | buf += nbytes; |
266 | count -= nbytes; |
267 | } |
268 | return 0; |
269 | } |
270 | |
271 | static int read_some(int fd, unsigned char *buf, size_t *lenp) { |
272 | ssize_t nbytes; |
273 | do nbytes = read(fd, buf, *lenp); |
274 | while (nbytes < 0 && errno == EINTR); |
275 | if (nbytes == 0) { errno = EPIPE; return -1; } |
276 | if (nbytes < 0) return -1; |
277 | *lenp = nbytes; |
278 | return 0; |
279 | } |
280 | |
281 | static int write_all(int fd, const void *buf, size_t count) { |
282 | ssize_t nbytes; |
283 | while (count > 0) { |
284 | nbytes = write(fd, buf, count); |
285 | if (nbytes < 0 && errno == EINTR) continue; |
286 | if (nbytes < 0) return -1; |
287 | buf += nbytes; |
288 | count -= nbytes; |
289 | } |
290 | return 0; |
291 | } |
292 | |
293 | static void shs_connect(int sfd, int infd, int outfd, const unsigned char pubkey[32], const unsigned char seckey[64], const unsigned char appkey[32], const unsigned char remote_pubkey[32], struct boxs *bs, bool client, bool specify_client_key) { |
294 | int rc; |
295 | unsigned char client_pubkey[32]; |
296 | unsigned char local_app_mac[32], remote_app_mac[32]; |
297 | unsigned char kx_pk[32], kx_sk[32], remote_kx_pk[32]; |
298 | unsigned char buf[64]; |
299 | |
300 | if (client) { |
301 | rc = crypto_box_keypair(kx_pk, kx_sk); |
302 | if (rc < 0) errx(1, "failed to generate auth keypair"); |
303 | |
304 | rc = crypto_auth(local_app_mac, kx_pk, 32, appkey); |
305 | if (rc < 0) err(1, "failed to generate app mac"); |
306 | |
307 | // send challenge |
308 | memcpy(buf, local_app_mac, 32); |
309 | memcpy(buf+32, kx_pk, 32); |
310 | rc = write_all(outfd, buf, sizeof(buf)); |
311 | if (rc < 0) err(1, "failed to send challenge"); |
312 | |
313 | } else { |
314 | // recv challenge |
315 | rc = read_all(infd, buf, sizeof(buf)); |
316 | if (rc < 0) err(1, "expected challenge"); |
317 | memcpy(remote_app_mac, buf, 32); |
318 | memcpy(remote_kx_pk, buf+32, 32); |
319 | |
320 | rc = crypto_auth_verify(buf, remote_kx_pk, 32, appkey); |
321 | if (rc < 0) errx(1, "wrong protocol/version"); |
322 | |
323 | } |
324 | |
325 | if (client) { |
326 | // recv challenge |
327 | rc = read_all(infd, buf, sizeof(buf)); |
328 | if (rc < 0) err(1, "challenge not accepted"); |
329 | memcpy(remote_app_mac, buf, 32); |
330 | memcpy(remote_kx_pk, buf+32, 32); |
331 | rc = crypto_auth_verify(buf, remote_kx_pk, 32, appkey); |
332 | if (rc < 0) errx(1, "wrong protocol (version?)"); |
333 | |
334 | } else { |
335 | rc = crypto_box_keypair(kx_pk, kx_sk); |
336 | if (rc < 0) errx(1, "failed to generate auth keypair"); |
337 | |
338 | rc = crypto_auth(local_app_mac, kx_pk, 32, appkey); |
339 | if (rc < 0) err(1, "failed to generate app mac"); |
340 | |
341 | // send challenge |
342 | memcpy(buf, local_app_mac, 32); |
343 | memcpy(buf+32, kx_pk, 32); |
344 | rc = write_all(outfd, buf, sizeof(buf)); |
345 | if (rc < 0) err(1, "failed to send challenge"); |
346 | |
347 | } |
348 | |
349 | unsigned char remote_pk_curve[32]; |
350 | unsigned char hello[96]; |
351 | unsigned char secret2[32]; |
352 | unsigned char boxed_auth[112]; |
353 | unsigned char secret[32]; |
354 | unsigned char a_bob[32]; |
355 | unsigned char secret2a[96]; |
356 | unsigned char shash[32]; |
357 | unsigned char sig[64]; |
358 | unsigned char signed1[96]; |
359 | unsigned char local_sk_curve[32]; |
360 | if (client) { |
361 | // send auth |
362 | |
363 | rc = crypto_scalarmult(secret, kx_sk, remote_kx_pk); |
364 | if (rc < 0) errx(1, "failed to derive shared secret"); |
365 | |
366 | rc = crypto_sign_ed25519_pk_to_curve25519(remote_pk_curve, remote_pubkey); |
367 | if (rc < 0) errx(1, "failed to curvify remote public key"); |
368 | |
369 | rc = crypto_scalarmult(a_bob, kx_sk, remote_pk_curve); |
370 | if (rc < 0) errx(1, "failed to derive a_bob"); |
371 | |
372 | memcpy(secret2a, appkey, 32); |
373 | memcpy(secret2a+32, secret, 32); |
374 | memcpy(secret2a+64, a_bob, 32); |
375 | |
376 | rc = crypto_hash_sha256(secret2, secret2a, sizeof(secret2a)); |
377 | if (rc < 0) errx(1, "failed to hash secret2"); |
378 | |
379 | rc = crypto_hash_sha256(shash, secret, sizeof(secret)); |
380 | if (rc < 0) errx(1, "failed to hash secret"); |
381 | |
382 | unsigned char signed1[96]; |
383 | memcpy(signed1, appkey, 32); |
384 | memcpy(signed1+32, remote_pubkey, 32); |
385 | memcpy(signed1+64, shash, 32); |
386 | |
387 | rc = crypto_sign_detached(sig, NULL, signed1, sizeof(signed1), seckey); |
388 | if (rc < 0) errx(1, "failed to sign inner hello"); |
389 | |
390 | memcpy(hello, sig, 64); |
391 | memcpy(hello+64, pubkey, 32); |
392 | |
393 | rc = crypto_secretbox_easy(boxed_auth, hello, sizeof(hello), zeros, secret2); |
394 | if (rc < 0) errx(1, "failed to box hello"); |
395 | |
396 | rc = write_all(outfd, boxed_auth, sizeof(boxed_auth)); |
397 | if (rc < 0) errx(1, "failed to send auth"); |
398 | |
399 | } else { |
400 | // read auth |
401 | rc = read_all(infd, boxed_auth, sizeof(boxed_auth)); |
402 | if (rc < 0) err(1, "expected hello"); |
403 | |
404 | rc = crypto_scalarmult(secret, kx_sk, remote_kx_pk); |
405 | if (rc < 0) errx(1, "failed to derive shared secret"); |
406 | |
407 | rc = crypto_hash_sha256(shash, secret, sizeof(secret)); |
408 | if (rc < 0) errx(1, "failed to hash secret"); |
409 | |
410 | rc = crypto_sign_ed25519_sk_to_curve25519(local_sk_curve, seckey); |
411 | if (rc < 0) errx(1, "failed to curvify local secret key"); |
412 | |
413 | rc = crypto_scalarmult(a_bob, local_sk_curve, remote_kx_pk); |
414 | if (rc < 0) errx(1, "failed to derive a_bob"); |
415 | |
416 | memcpy(secret2a, appkey, 32); |
417 | memcpy(secret2a+32, secret, 32); |
418 | memcpy(secret2a+64, a_bob, 32); |
419 | |
420 | rc = crypto_hash_sha256(secret2, secret2a, sizeof(secret2a)); |
421 | if (rc < 0) errx(1, "failed to hash secret2"); |
422 | |
423 | rc = crypto_secretbox_open_easy(hello, boxed_auth, sizeof(boxed_auth), zeros, secret2); |
424 | if (rc < 0) errx(1, "failed to unbox client hello"); |
425 | |
426 | memcpy(sig, hello, 64); |
427 | memcpy(client_pubkey, hello+64, 32); |
428 | |
429 | memcpy(signed1, appkey, 32); |
430 | memcpy(signed1+32, pubkey, 32); |
431 | memcpy(signed1+64, shash, 32); |
432 | |
433 | rc = crypto_sign_verify_detached(sig, signed1, sizeof(signed1), client_pubkey); |
434 | if (rc < 0) errx(1, "wrong number"); |
435 | } |
436 | |
437 | unsigned char boxed_okay[80]; |
438 | unsigned char b_alice[32]; |
439 | unsigned char secret3a[128]; |
440 | unsigned char secret3[32]; |
441 | unsigned char signed2[160]; |
442 | if (client) { |
443 | // verify accept |
444 | |
445 | rc = read_all(infd, boxed_okay, sizeof(boxed_okay)); |
446 | if (rc < 0) err(1, "hello not accepted"); |
447 | |
448 | rc = crypto_sign_ed25519_sk_to_curve25519(local_sk_curve, seckey); |
449 | if (rc < 0) errx(1, "failed to curvify local secret key"); |
450 | |
451 | rc = crypto_scalarmult(b_alice, local_sk_curve, remote_kx_pk); |
452 | if (rc < 0) errx(1, "failed to derive b_alice"); |
453 | |
454 | memcpy(secret3a, appkey, 32); |
455 | memcpy(secret3a+32, secret, 32); |
456 | memcpy(secret3a+64, a_bob, 32); |
457 | memcpy(secret3a+96, b_alice, 32); |
458 | |
459 | rc = crypto_hash_sha256(secret3, secret3a, sizeof(secret3a)); |
460 | if (rc < 0) errx(1, "failed to hash secret3"); |
461 | |
462 | rc = crypto_secretbox_open_easy(sig, boxed_okay, sizeof(boxed_okay), zeros, secret3); |
463 | if (rc < 0) errx(1, "failed to unbox the okay"); |
464 | |
465 | memcpy(signed2, appkey, 32); |
466 | memcpy(signed2+32, hello, 96); |
467 | memcpy(signed2+128, shash, 32); |
468 | |
469 | rc = crypto_sign_verify_detached(sig, signed2, sizeof(signed2), remote_pubkey); |
470 | if (rc < 0) errx(1, "server not authenticated"); |
471 | |
472 | } else { |
473 | if (specify_client_key && memcmp(client_pubkey, remote_pubkey, 32)) { |
474 | errx(1, "unexpected client"); |
475 | } |
476 | |
477 | // send accept |
478 | |
479 | rc = crypto_sign_ed25519_pk_to_curve25519(remote_pk_curve, client_pubkey); |
480 | if (rc < 0) errx(1, "failed to curvify remote public key"); |
481 | |
482 | rc = crypto_scalarmult(b_alice, kx_sk, remote_pk_curve); |
483 | if (rc < 0) errx(1, "failed to derive b_alice"); |
484 | |
485 | memcpy(secret3a, appkey, 32); |
486 | memcpy(secret3a+32, secret, 32); |
487 | memcpy(secret3a+64, a_bob, 32); |
488 | memcpy(secret3a+96, b_alice, 32); |
489 | |
490 | rc = crypto_hash_sha256(secret3, secret3a, sizeof(secret3a)); |
491 | if (rc < 0) errx(1, "failed to hash secret3"); |
492 | |
493 | memcpy(signed2, appkey, 32); |
494 | memcpy(signed2+32, hello, 96); |
495 | memcpy(signed2+128, shash, 32); |
496 | |
497 | rc = crypto_sign_detached(sig, NULL, signed2, sizeof(signed2), seckey); |
498 | if (rc < 0) errx(1, "failed to sign inner accept"); |
499 | |
500 | rc = crypto_secretbox_easy(boxed_okay, sig, sizeof(sig), zeros, secret3); |
501 | if (rc < 0) errx(1, "failed to box accept"); |
502 | |
503 | rc = write_all(outfd, boxed_okay, sizeof(boxed_okay)); |
504 | if (rc < 0) errx(1, "failed to send accept"); |
505 | } |
506 | |
507 | rc = crypto_hash_sha256(secret, secret3, 32); |
508 | if (rc < 0) errx(1, "failed to hash secret3"); |
509 | |
510 | unsigned char enc_key_hashed[64]; |
511 | memcpy(enc_key_hashed, secret, 32); |
512 | memcpy(enc_key_hashed+32, client ? remote_pubkey : client_pubkey, 32); |
513 | rc = crypto_hash_sha256(bs->encrypt_key, enc_key_hashed, 64); |
514 | if (rc < 0) errx(1, "failed to hash the encrypt key"); |
515 | |
516 | unsigned char dec_key_hashed[64]; |
517 | memcpy(dec_key_hashed, secret, 32); |
518 | memcpy(dec_key_hashed+32, pubkey, 32); |
519 | rc = crypto_hash_sha256(bs->decrypt_key, dec_key_hashed, 64); |
520 | if (rc < 0) errx(1, "failed to hash the decrypt key"); |
521 | |
522 | memcpy(bs->nonce1, remote_app_mac, 24); |
523 | memcpy(bs->nonce2, remote_app_mac, 24); |
524 | memcpy(bs->rx_nonce, local_app_mac, 24); |
525 | |
526 | bs->rx_buf_pos = 0; |
527 | bs->rx_buf_len = 0; |
528 | bs->s = sfd; |
529 | bs->noauth = false; |
530 | bs->wrote_goodbye = false; |
531 | } |
532 | |
533 | static int pubkey_decode(const char *key_str, unsigned char key[32]) { |
534 | if (!key_str) { errno = EPROTO; return -1; } |
535 | if (!*key_str) { errno = EPROTO; return -1; } |
536 | if (*key_str == '@') key_str++; |
537 | size_t len = strlen(key_str); |
538 | if (len == 52 && strcmp(key_str+44, ".ed25519") == 0) {} |
539 | else if (len != 44) { errno = EMSGSIZE; return -1; } |
540 | return sodium_base642bin( |
541 | (unsigned char *const)key, 32, |
542 | (const char *const)key_str, 44, |
543 | NULL, NULL, NULL, sodium_base64_VARIANT_ORIGINAL); |
544 | } |
545 | |
546 | static int seckey_decode(const char *key_str, unsigned char key[64]) { |
547 | if (!key_str) { errno = EPROTO; return -1; } |
548 | if (!*key_str) { errno = EPROTO; return -1; } |
549 | if (*key_str == '@') key_str++; |
550 | size_t len = strlen(key_str); |
551 | if (len > 8 && memcmp(key_str + len - 8, ".ed25519", 8) == 0) len -= 8; |
552 | return sodium_base642bin( |
553 | (unsigned char *const)key, 64, |
554 | (const char *const)key_str, len, |
555 | NULL, NULL, NULL, sodium_base64_VARIANT_ORIGINAL); |
556 | } |
557 | |
558 | static jsmntok_t *json_lookup(const char *buf, jsmntok_t *tok, const char *prop, size_t prop_len) { |
559 | jsmntok_t *end = tok + tok->size + 1; |
560 | if (tok->type != JSMN_OBJECT) { errno = EPROTO; return NULL; } |
561 | tok++; |
562 | while (tok < end) { |
563 | if (tok + 1 >= end) { errno = EPROTO; return NULL; } |
564 | if (tok->type == JSMN_STRING |
565 | && tok->end - tok->start == (int)prop_len |
566 | && memcmp(buf + tok->start, prop, prop_len) == 0) |
567 | return tok + 1; |
568 | tok += tok->size + 1; |
569 | end += tok->size; |
570 | } |
571 | return NULL; |
572 | } |
573 | |
574 | static ssize_t json_get_value(const char *buf, const char *path, const char **value) { |
575 | static const int num_tokens = 1024; |
576 | jsmntok_t tokens[num_tokens], *tok = tokens; |
577 | jsmn_parser parser; |
578 | |
579 | jsmn_init(&parser); |
580 | switch (jsmn_parse(&parser, buf, tokens, num_tokens)) { |
581 | case JSMN_ERROR_NOMEM: errno = ENOMEM; return -1; |
582 | case JSMN_ERROR_INVAL: errno = EINVAL; return -1; |
583 | case JSMN_ERROR_PART: errno = EMSGSIZE; return -1; |
584 | case JSMN_SUCCESS: break; |
585 | default: errno = EPROTO; return -1; |
586 | } |
587 | |
588 | while (*path) { |
589 | const char *end = strchr(path, '.'); |
590 | size_t part_len = end ? (size_t)end - (size_t)path : strlen(path); |
591 | tok = json_lookup(buf, tok, path, part_len); |
592 | if (!tok) { errno = ENOMSG; return -1; } |
593 | path += part_len; |
594 | if (*path == '.') path++; |
595 | } |
596 | |
597 | *value = buf + tok->start; |
598 | return tok->end - tok->start; |
599 | } |
600 | |
601 | static void get_app_dir(char *dir, size_t len) { |
602 | const char *path, *home, *appname; |
603 | int rc; |
604 | path = getenv("ssb_path"); |
605 | if (path) { |
606 | if (strlen(path) > len) errx(1, "ssb_path too long"); |
607 | strncpy(dir, path, len); |
608 | return; |
609 | } |
610 | home = getenv("HOME"); |
611 | if (!home) home = "."; |
612 | appname = getenv("ssb_appname"); |
613 | if (!appname) appname = "ssb"; |
614 | rc = snprintf(dir, len, "%s/.%s", home, appname); |
615 | if (rc < 0) err(1, "failed to get app dir"); |
616 | if ((size_t)rc >= len) errx(1, "path to app dir too long"); |
617 | } |
618 | |
619 | static ssize_t read_file(char *buf, size_t len, const char *fmt, ...) { |
620 | va_list ap; |
621 | int rc; |
622 | struct stat st; |
623 | int fd; |
624 | |
625 | va_start(ap, fmt); |
626 | rc = vsnprintf(buf, len, fmt, ap); |
627 | va_end(ap); |
628 | if (rc < 0) return -1; |
629 | if ((size_t)rc >= len) { errno = ENAMETOOLONG; return -1; } |
630 | |
631 | rc = stat(buf, &st); |
632 | if (rc < 0) return -1; |
633 | if (st.st_size > (off_t)(len-1)) { errno = EMSGSIZE; return -1; } |
634 | |
635 | fd = open(buf, O_RDONLY); |
636 | if (fd < 0) return -1; |
637 | |
638 | rc = read_all(fd, buf, st.st_size); |
639 | if (rc < 0) return -1; |
640 | buf[st.st_size] = '\0'; |
641 | |
642 | close(fd); |
643 | return st.st_size; |
644 | } |
645 | |
646 | |
647 | static void read_private_key(const char *dir, unsigned char pk[64]) { |
648 | ssize_t len; |
649 | char buf[8192]; |
650 | const char *pk_b64; |
651 | int rc; |
652 | ssize_t key_len; |
653 | char *line; |
654 | |
655 | len = read_file(buf, sizeof(buf), "%s/secret", dir); |
656 | if (len < 0) err(1, "failed to read secret file"); |
657 | |
658 | // strip comments |
659 | for (line = buf; *line; ) { |
660 | if (*line == '#') while (*line && *line != '\n') *line++ = ' '; |
661 | else while (*line && *line++ != '\n'); |
662 | } |
663 | |
664 | key_len = json_get_value(buf, "private", &pk_b64); |
665 | if (key_len < 0) err(1, "unable to read private key"); |
666 | |
667 | if (key_len > 8 && memcmp(pk_b64 + key_len - 8, ".ed25519", 8) == 0) |
668 | key_len -= 8; |
669 | rc = sodium_base642bin( |
670 | (unsigned char *const)pk, 64, |
671 | (const char *const)pk_b64, key_len, |
672 | NULL, NULL, NULL, sodium_base64_VARIANT_ORIGINAL); |
673 | if (rc < 0) err(1, "unable to decode private key"); |
674 | } |
675 | |
676 | static void increment_nonce(uint8_t nonce[24]) { |
677 | int i; |
678 | for (i = 23; i >= 0 && nonce[i] == 0xff; i--) nonce[i] = 0; |
679 | if (i >= 0) nonce[i]++; |
680 | } |
681 | |
682 | static void bs_write_end_box(struct boxs *bs) { |
683 | unsigned char boxed[34]; |
684 | int rc = crypto_secretbox_easy(boxed, zeros, 18, bs->nonce1, bs->encrypt_key); |
685 | if (rc < 0) errx(1, "failed to box packet end header"); |
686 | increment_nonce(bs->nonce1); |
687 | increment_nonce(bs->nonce2); |
688 | rc = write_all(bs->s, boxed, 34); |
689 | if (rc < 0) err(1, "failed to write boxed end header"); |
690 | } |
691 | |
692 | static void bs_write_packet(struct boxs *bs, const unsigned char *buf, uint16_t len) { |
693 | size_t boxed_len = len + 34; |
694 | unsigned char boxed[boxed_len]; |
695 | increment_nonce(bs->nonce2); |
696 | int rc = crypto_secretbox_easy(boxed + 18, buf, len, bs->nonce2, bs->encrypt_key); |
697 | if (rc < 0) errx(1, "failed to box packet data"); |
698 | struct boxs_header header; |
699 | header.len = htons(len); |
700 | memcpy(header.mac, boxed + 18, 16); |
701 | rc = crypto_secretbox_easy(boxed, (unsigned char *)&header, 18, bs->nonce1, bs->encrypt_key); |
702 | if (rc < 0) errx(1, "failed to box packet header"); |
703 | increment_nonce(bs->nonce1); |
704 | increment_nonce(bs->nonce1); |
705 | increment_nonce(bs->nonce2); |
706 | rc = write_all(bs->s, boxed, boxed_len); |
707 | if (rc < 0) err(1, "failed to write boxed packet"); |
708 | } |
709 | |
710 | static void bs_end(struct boxs *bs) { |
711 | if (bs->wrote_goodbye) return; |
712 | bs->wrote_goodbye = true; |
713 | if (!bs->noauth) { |
714 | bs_write_end_box(bs); |
715 | } |
716 | shutdown(bs->s, SHUT_WR); |
717 | } |
718 | |
719 | static int bs_read_packet(struct boxs *bs, void *buf, size_t *lenp) { |
720 | int rc; |
721 | if (bs->noauth) { |
722 | rc = read_some(bs->s, buf, lenp); |
723 | if (rc < 0 && errno == EPIPE) return -1; |
724 | if (rc < 0) err(1, "failed to read packet data"); |
725 | return 0; |
726 | } |
727 | unsigned char boxed_header[34]; |
728 | struct boxs_header header; |
729 | rc = read_all(bs->s, boxed_header, 34); |
730 | if (rc < 0 && errno == EPIPE) errx(1, "unexpected end of parent stream"); |
731 | if (rc < 0) err(1, "failed to read boxed packet header"); |
732 | rc = crypto_secretbox_open_easy((unsigned char *)&header, boxed_header, 34, bs->rx_nonce, bs->decrypt_key); |
733 | if (rc < 0) errx(1, "failed to unbox packet header"); |
734 | increment_nonce(bs->rx_nonce); |
735 | if (header.len == 0 && !memcmp(header.mac, zeros, 16)) { errno = EPIPE; return -1; } |
736 | size_t len = ntohs(header.len); |
737 | if (len > BOXS_MAXLEN) errx(1, "received boxed packet too large"); |
738 | unsigned char boxed_data[len + 16]; |
739 | rc = read_all(bs->s, boxed_data + 16, len); |
740 | if (rc < 0) err(1, "failed to read boxed packet data"); |
741 | memcpy(boxed_data, header.mac, 16); |
742 | rc = crypto_secretbox_open_easy(buf, boxed_data, len+16, bs->rx_nonce, bs->decrypt_key); |
743 | if (rc < 0) errx(1, "failed to unbox packet data"); |
744 | increment_nonce(bs->rx_nonce); |
745 | *lenp = len; |
746 | return 0; |
747 | } |
748 | |
749 | static int bs_read(struct boxs *bs, char *buf, size_t len) { |
750 | if (bs->noauth) { |
751 | int rc = read_all(bs->s, buf, len); |
752 | if (rc < 0) err(1, "failed to read packet data"); |
753 | return 0; |
754 | } |
755 | size_t remaining; |
756 | while (len > 0) { |
757 | remaining = bs->rx_buf_len > len ? len : bs->rx_buf_len; |
758 | if (buf) memcpy(buf, bs->rx_buf + bs->rx_buf_pos, remaining); |
759 | bs->rx_buf_len -= remaining; |
760 | bs->rx_buf_pos += remaining; |
761 | len -= remaining; |
762 | buf += remaining; |
763 | if (len == 0) return 0; |
764 | if (bs_read_packet(bs, bs->rx_buf, &bs->rx_buf_len) < 0) return -1; |
765 | bs->rx_buf_pos = 0; |
766 | } |
767 | return 0; |
768 | } |
769 | |
770 | static enum stream_state bs_read_out_1(struct boxs *bs, int fd) { |
771 | size_t buf[4096]; |
772 | size_t len = sizeof(buf); |
773 | int rc; |
774 | rc = bs_read_packet(bs, buf, &len); |
775 | if (rc < 0 && errno == EPIPE) { |
776 | bs_end(bs); |
777 | return stream_state_ended_ok; |
778 | } |
779 | if (rc < 0) return stream_state_ended_error; |
780 | rc = write_all(fd, buf, len); |
781 | if (rc < 0) return stream_state_ended_error; |
782 | return stream_state_open; |
783 | } |
784 | |
785 | static int bs_read_out(struct boxs *bs, int fd, size_t len) { |
786 | size_t chunk; |
787 | char buf[4096]; |
788 | int rc; |
789 | while (len > 0) { |
790 | chunk = len > sizeof(buf) ? sizeof(buf) : len; |
791 | rc = bs_read(bs, buf, chunk); |
792 | if (rc < 0) return -1; |
793 | rc = write_all(fd, buf, chunk); |
794 | if (rc < 0) return -1; |
795 | len -= chunk; |
796 | } |
797 | return 0; |
798 | } |
799 | |
800 | static int bs_read_error(struct boxs *bs, int errfd, enum pkt_flags flags, size_t len, bool no_newline) { |
801 | // suppress printing "true" indicating end without error |
802 | if (flags & pkt_flags_json && len == 4) { |
803 | char buf[4]; |
804 | if (bs_read(bs, buf, 4) < 0) return -1; |
805 | if (strncmp(buf, "true", 4) == 0) { |
806 | return 0; |
807 | } |
808 | if (write_all(errfd, buf, 4) < 0) return -1; |
809 | } else { |
810 | if (bs_read_out(bs, errfd, len) < 0) return -1; |
811 | } |
812 | if (flags & (pkt_flags_json | pkt_flags_string) && !no_newline) { |
813 | if (write_buf(errfd, "\n") < 0) return -1; |
814 | } |
815 | return 1; |
816 | } |
817 | |
818 | static void bs_write(struct boxs *bs, const unsigned char *buf, size_t len) { |
819 | if (bs->noauth) { |
820 | int rc = write_all(bs->s, buf, len); |
821 | if (rc < 0) err(1, "failed to write packet"); |
822 | return; |
823 | } |
824 | while (len > 0) { |
825 | size_t l = len > BOXS_MAXLEN ? BOXS_MAXLEN : len; |
826 | bs_write_packet(bs, buf, l); |
827 | len -= l; |
828 | buf += l; |
829 | } |
830 | } |
831 | |
832 | static enum stream_state bs_write_in_1(struct boxs *bs, int fd) { |
833 | unsigned char buf[4096]; |
834 | ssize_t sz = read(fd, buf, sizeof(buf)); |
835 | if (sz < 0) err(1, "read"); |
836 | if (sz == 0) { |
837 | bs_end(bs); |
838 | return stream_state_ended_ok; |
839 | } |
840 | bs_write(bs, buf, sz); |
841 | return stream_state_open; |
842 | } |
843 | |
844 | static void ps_write(struct boxs *bs, const char *data, size_t len, enum pkt_type type, int req_id, bool stream, bool end) { |
845 | size_t out_len = 9 + len; |
846 | unsigned char out_buf[out_len]; |
847 | uint32_t len_n = htonl(len); |
848 | int32_t req_n = htonl(req_id); |
849 | out_buf[0] = (stream << 3) | (end << 2) | (type & 3); |
850 | memcpy(out_buf+1, &len_n, 4); |
851 | memcpy(out_buf+5, &req_n, 4); |
852 | memcpy(out_buf+9, data, len); |
853 | bs_write(bs, out_buf, out_len); |
854 | } |
855 | |
856 | static void ps_goodbye(struct boxs *bs) { |
857 | if (bs->wrote_goodbye) return; |
858 | bs->wrote_goodbye = true; |
859 | bs_write(bs, zeros, 9); |
860 | } |
861 | |
862 | static int ps_read_header(struct boxs *bs, size_t *len, int *req_id, enum pkt_flags *flags) { |
863 | char buf[9]; |
864 | uint32_t len_n; |
865 | int32_t req_n; |
866 | if (bs_read(bs, buf, sizeof(buf)) < 0) return -1; |
867 | memcpy(&len_n, buf+1, 4); |
868 | memcpy(&req_n, buf+5, 4); |
869 | if (len) *len = ntohl(len_n); |
870 | if (req_id) *req_id = ntohl(req_n); |
871 | if (flags) *flags = buf[0]; |
872 | return 0; |
873 | } |
874 | |
875 | static void muxrpc_call(struct boxs *bs, const char *method, const char *argument, enum muxrpc_type type, const char *typestr, int req_id) { |
876 | char req[33792]; // 32768 max message value size + 1024 extra |
877 | ssize_t reqlen; |
878 | bool is_request = type == muxrpc_type_async; |
879 | |
880 | if (is_request) { |
881 | reqlen = snprintf(req, sizeof(req), |
882 | "{\"name\":%s,\"args\":%s}", |
883 | method, argument); |
884 | } else { |
885 | reqlen = snprintf(req, sizeof(req), |
886 | "{\"name\":%s,\"args\":%s,\"type\":\"%s\"}", |
887 | method, argument, typestr); |
888 | } |
889 | if (reqlen < 0) err(1, "failed to construct request"); |
890 | if ((size_t)reqlen >= sizeof(req)) errx(1, "request too large"); |
891 | |
892 | ps_write(bs, req, reqlen, pkt_type_json, req_id, !is_request, false); |
893 | } |
894 | |
895 | static int bs_passthrough(struct boxs *bs, int infd, int outfd) { |
896 | int rc; |
897 | fd_set rd; |
898 | int sfd = bs->s; |
899 | int maxfd = infd > sfd ? infd : sfd; |
900 | enum stream_state in = stream_state_open; |
901 | enum stream_state out = stream_state_open; |
902 | |
903 | while (out == stream_state_open) { |
904 | FD_ZERO(&rd); |
905 | if (in == stream_state_open) FD_SET(infd, &rd); |
906 | if (out == stream_state_open) FD_SET(sfd, &rd); |
907 | rc = select(maxfd + 1, &rd, 0, 0, NULL); |
908 | if (rc < 0) err(1, "select"); |
909 | if (FD_ISSET(infd, &rd)) in = bs_write_in_1(bs, infd); |
910 | if (FD_ISSET(sfd, &rd)) out = bs_read_out_1(bs, outfd); |
911 | } |
912 | |
913 | return in != stream_state_ended_error && out == stream_state_ended_ok ? 0 : |
914 | in == stream_state_ended_error || out == stream_state_ended_error ? 2 : 1; |
915 | } |
916 | |
917 | static void ps_reject(struct boxs *bs, size_t len, int32_t req, enum pkt_flags flags) { |
918 | // ignore the packet. if this is a request, the substream on the other end |
919 | // will just have to wait until the rpc connection closes. |
920 | (void)req; |
921 | (void)flags; |
922 | write_buf(STDERR_FILENO, "ignoring packet: "); |
923 | int rc = bs_read_out(bs, STDERR_FILENO, len); |
924 | if (rc < 0) err(1, "bs_read_out"); |
925 | write_buf(STDERR_FILENO, "\n"); |
926 | } |
927 | |
928 | static enum stream_state muxrpc_read_source_1(struct boxs *bs, int outfd, int req_id, bool no_newline) { |
929 | enum pkt_flags flags; |
930 | size_t len; |
931 | int32_t req; |
932 | int rc = ps_read_header(bs, &len, &req, &flags); |
933 | if (rc < 0) err(1, "ps_read_header"); |
934 | if (req == 0 && len == 0) { |
935 | if (bs->wrote_goodbye) return stream_state_ended_ok; |
936 | warnx("unexpected end of parent stream"); |
937 | return stream_state_ended_error; |
938 | } |
939 | if (req != -req_id) { |
940 | ps_reject(bs, len, req, flags); |
941 | return stream_state_open; |
942 | } |
943 | if (flags & pkt_flags_end) { |
944 | rc = bs_read_error(bs, STDERR_FILENO, flags, len, no_newline); |
945 | if (rc < 0) err(1, "bs_read_error"); |
946 | if (rc == 1) return stream_state_ended_error; |
947 | return stream_state_ended_ok; |
948 | } |
949 | rc = bs_read_out(bs, outfd, len); |
950 | if (rc < 0) err(1, "bs_read_out"); |
951 | if (flags & (pkt_flags_json | pkt_flags_string) && !no_newline) { |
952 | rc = write_buf(outfd, "\n"); |
953 | if (rc < 0) err(1, "write_buf"); |
954 | } |
955 | return stream_state_open; |
956 | } |
957 | |
958 | static int muxrpc_read_source(struct boxs *bs, int outfd, int req_id, bool no_newline) { |
959 | enum stream_state state; |
960 | while ((state = muxrpc_read_source_1(bs, outfd, req_id, no_newline)) == stream_state_open); |
961 | return state == stream_state_ended_ok ? 0 : |
962 | state == stream_state_ended_error ? 2 : 1; |
963 | } |
964 | |
965 | static int muxrpc_read_async(struct boxs *bs, int outfd, int req_id, bool no_newline) { |
966 | enum pkt_flags flags; |
967 | size_t len; |
968 | int32_t req; |
969 | int rc; |
970 | |
971 | while (1) { |
972 | rc = ps_read_header(bs, &len, &req, &flags); |
973 | if (rc < 0) err(1, "ps_read_header"); |
974 | if (req == -req_id) break; |
975 | if (req == 0 && len == 0) errx(1, "unexpected end of parent stream"); |
976 | ps_reject(bs, len, req, flags); |
977 | } |
978 | if (flags & pkt_flags_end) { |
979 | rc = bs_read_error(bs, STDERR_FILENO, flags, len, no_newline); |
980 | if (rc < 0) err(1, "bs_read_error"); |
981 | if (rc == 1) return 2; |
982 | return 1; |
983 | } |
984 | rc = bs_read_out(bs, outfd, len); |
985 | if (rc < 0) err(1, "bs_read_out"); |
986 | if (flags & (pkt_flags_json | pkt_flags_string) && !no_newline) { |
987 | rc = write_buf(outfd, "\n"); |
988 | if (rc < 0) err(1, "write_buf"); |
989 | } |
990 | return 0; |
991 | } |
992 | |
993 | static enum stream_state muxrpc_write_sink_1(struct boxs *bs, int infd, |
994 | enum pkt_type ptype, int req_id) { |
995 | char buf[4096]; |
996 | ssize_t sz = read(infd, buf, sizeof(buf)); |
997 | if (sz < 0) err(1, "read"); |
998 | if (sz == 0) { |
999 | ps_write(bs, "true", 4, pkt_type_json, req_id, true, true); |
1000 | return stream_state_ended_ok; |
1001 | } |
1002 | ps_write(bs, buf, sz, ptype, req_id, true, false); |
1003 | return stream_state_open; |
1004 | } |
1005 | |
1006 | static enum stream_state muxrpc_write_sink_1_hashed(struct boxs *bs, int infd, |
1007 | crypto_hash_sha256_state *hash_state, int req_id) { |
1008 | int rc; |
1009 | unsigned char buf[4096]; |
1010 | ssize_t sz = read(infd, buf, sizeof(buf)); |
1011 | if (sz < 0) err(1, "read"); |
1012 | if (sz == 0) { |
1013 | ps_write(bs, "true", 4, pkt_type_json, req_id, true, true); |
1014 | return stream_state_ended_ok; |
1015 | } |
1016 | rc = crypto_hash_sha256_update(hash_state, buf, sz); |
1017 | if (rc < 0) errx(1, "hash update failed"); |
1018 | ps_write(bs, (char *)buf, sz, pkt_type_buffer, req_id, true, false); |
1019 | return stream_state_open; |
1020 | } |
1021 | |
1022 | static int muxrpc_write_sink(struct boxs *bs, int infd, enum pkt_type ptype, int req_id, bool no_newline) { |
1023 | int rc; |
1024 | fd_set rd; |
1025 | int sfd = bs->s; |
1026 | int maxfd = infd > sfd ? infd : sfd; |
1027 | enum stream_state in = stream_state_open; |
1028 | enum stream_state out = stream_state_open; |
1029 | |
1030 | while (out == stream_state_open) { |
1031 | FD_ZERO(&rd); |
1032 | if (in == stream_state_open) FD_SET(infd, &rd); |
1033 | if (out == stream_state_open) FD_SET(sfd, &rd); |
1034 | rc = select(maxfd + 1, &rd, 0, 0, NULL); |
1035 | if (rc < 0) err(1, "select"); |
1036 | if (FD_ISSET(infd, &rd)) in = muxrpc_write_sink_1(bs, infd, ptype, req_id); |
1037 | if (FD_ISSET(sfd, &rd)) out = muxrpc_read_source_1(bs, -1, req_id, no_newline); |
1038 | } |
1039 | |
1040 | return in == stream_state_ended_ok && out == stream_state_ended_ok ? 0 : |
1041 | in == stream_state_ended_error || out == stream_state_ended_error ? 2 : 1; |
1042 | } |
1043 | |
1044 | static int muxrpc_write_blob_add(struct boxs *bs, int infd, int outfd, int req_id, bool no_newline) { |
1045 | int rc; |
1046 | fd_set rd; |
1047 | int sfd = bs->s; |
1048 | int maxfd = infd > sfd ? infd : sfd; |
1049 | enum stream_state in = stream_state_open; |
1050 | enum stream_state out = stream_state_open; |
1051 | crypto_hash_sha256_state hash_state; |
1052 | unsigned char hash[32]; |
1053 | char id[54] = "&"; |
1054 | |
1055 | rc = crypto_hash_sha256_init(&hash_state); |
1056 | if (rc < 0) { errno = EINVAL; return -1; } |
1057 | |
1058 | while (out == stream_state_open) { |
1059 | FD_ZERO(&rd); |
1060 | if (in == stream_state_open) FD_SET(infd, &rd); |
1061 | if (out == stream_state_open) FD_SET(sfd, &rd); |
1062 | rc = select(maxfd + 1, &rd, 0, 0, NULL); |
1063 | if (rc < 0) err(1, "select"); |
1064 | if (FD_ISSET(infd, &rd)) in = muxrpc_write_sink_1_hashed(bs, infd, &hash_state, req_id); |
1065 | if (FD_ISSET(sfd, &rd)) out = muxrpc_read_source_1(bs, -1, req_id, no_newline); |
1066 | } |
1067 | |
1068 | rc = crypto_hash_sha256_final(&hash_state, hash); |
1069 | if (rc < 0) errx(1, "hash finalize failed"); |
1070 | |
1071 | (void)sodium_bin2base64(id+1, sizeof(id)-1, hash, 32, sodium_base64_VARIANT_ORIGINAL); |
1072 | strcpy(id + 45, ".sha256\n"); |
1073 | rc = write_all(outfd, id, sizeof(id)-1); |
1074 | if (rc < 0) err(1, "writing hash failed"); |
1075 | |
1076 | return in == stream_state_ended_ok && out == stream_state_ended_ok ? 0 : |
1077 | in == stream_state_ended_error || out == stream_state_ended_error ? 2 : 1; |
1078 | } |
1079 | |
1080 | static int muxrpc_duplex(struct boxs *bs, int infd, int outfd, enum pkt_type in_ptype, int req_id, bool no_newline) { |
1081 | int rc; |
1082 | fd_set rd; |
1083 | int sfd = bs->s; |
1084 | int maxfd = infd > sfd ? infd : sfd; |
1085 | enum stream_state in = stream_state_open; |
1086 | enum stream_state out = stream_state_open; |
1087 | |
1088 | while (out == stream_state_open) { |
1089 | FD_ZERO(&rd); |
1090 | if (in == stream_state_open) FD_SET(infd, &rd); |
1091 | if (out == stream_state_open) FD_SET(sfd, &rd); |
1092 | rc = select(maxfd + 1, &rd, 0, 0, NULL); |
1093 | if (rc < 0) err(1, "select"); |
1094 | if (FD_ISSET(infd, &rd)) in = muxrpc_write_sink_1(bs, infd, in_ptype, req_id); |
1095 | if (FD_ISSET(sfd, &rd)) out = muxrpc_read_source_1(bs, outfd, req_id, no_newline); |
1096 | } |
1097 | |
1098 | return in == stream_state_ended_ok && out == stream_state_ended_ok ? 0 : |
1099 | in == stream_state_ended_error || out == stream_state_ended_error ? 2 : 1; |
1100 | } |
1101 | |
1102 | static int method_to_json(char *out, size_t outlen, const char *str) { |
1103 | // blobs.get => ["blobs", "get"] |
1104 | size_t i = 0; |
1105 | char c; |
1106 | if (i+2 > outlen) return -1; |
1107 | out[i++] = '['; |
1108 | out[i++] = '"'; |
1109 | while ((c = *str++)) { |
1110 | if (c == '.') { |
1111 | if (i+3 > outlen) return -1; |
1112 | out[i++] = '"'; |
1113 | out[i++] = ','; |
1114 | out[i++] = '"'; |
1115 | } else if (c == '"') { |
1116 | if (i+2 > outlen) return -1; |
1117 | out[i++] = '\\'; |
1118 | out[i++] = '"'; |
1119 | } else { |
1120 | if (i+1 > outlen) return -1; |
1121 | out[i++] = c; |
1122 | } |
1123 | } |
1124 | if (i+3 > outlen) return -1; |
1125 | out[i++] = '"'; |
1126 | out[i++] = ']'; |
1127 | out[i++] = '\0'; |
1128 | return i; |
1129 | } |
1130 | |
1131 | static int args_to_json_length(int argc, char *argv[], bool encode_strings) { |
1132 | int i = 0; |
1133 | int len = 3; // "[]\0" |
1134 | for (i = 0; i < argc; i++) { |
1135 | if (!encode_strings) { |
1136 | len += strlen(argv[i])+1; |
1137 | } else { |
1138 | len += 3; // "\"\"," |
1139 | char *arg = argv[i], c; |
1140 | while ((c = *arg++)) switch (c) { |
1141 | case '"': len += 2; break; |
1142 | case '\\': len += 2; break; |
1143 | default: len++; |
1144 | } |
1145 | } |
1146 | } |
1147 | return len; |
1148 | } |
1149 | |
1150 | static int args_to_json(char *out, size_t outlen, unsigned int argc, char *argv[], bool encode_strings) { |
1151 | size_t i = 0; |
1152 | size_t j; |
1153 | if (i+1 > outlen) return -1; |
1154 | out[i++] = '['; |
1155 | for (j = 0; j < argc; j++) { |
1156 | if (!encode_strings) { |
1157 | size_t len = strlen(argv[j]); |
1158 | if (j > 0) out[i++] = ','; |
1159 | if (i+len > outlen) return -1; |
1160 | strncpy(out+i, argv[j], len); |
1161 | i += len; |
1162 | } else { |
1163 | char *arg = argv[j]; |
1164 | char c; |
1165 | if (j > 0) { |
1166 | if (i+1 > outlen) return -1; |
1167 | out[i++] = ','; |
1168 | } |
1169 | if (i+1 > outlen) return -1; |
1170 | out[i++] = '"'; |
1171 | while ((c = *arg++)) { |
1172 | if (i+2 > outlen) return -1; |
1173 | if (c == '"' || c == '\\') out[i++] = '\\'; |
1174 | out[i++] = c; |
1175 | } |
1176 | if (i+1 > outlen) return -1; |
1177 | out[i++] = '"'; |
1178 | } |
1179 | } |
1180 | if (i+2 > outlen) return -1; |
1181 | out[i++] = ']'; |
1182 | out[i++] = '\0'; |
1183 | return i; |
1184 | } |
1185 | |
1186 | int main(int argc, char *argv[]) { |
1187 | int i, s, infd, outfd, rc; |
1188 | const char *key = NULL; |
1189 | const char *keypair_seed_str = NULL; |
1190 | const char *host = NULL; |
1191 | const char *port = "8008"; |
1192 | const char *typestr = NULL, *methodstr = NULL; |
1193 | const char *shs_cap_key_str = NULL; |
1194 | const char *socket_path = NULL; |
1195 | size_t argument_len; |
1196 | unsigned char private_key[64]; |
1197 | unsigned char public_key[32]; |
1198 | unsigned char remote_key[32]; |
1199 | unsigned char shs_cap_key[32]; |
1200 | enum muxrpc_type type; |
1201 | enum pkt_type ptype = pkt_type_buffer; |
1202 | char method[256]; |
1203 | char app_dir[_POSIX_PATH_MAX]; |
1204 | ssize_t len; |
1205 | bool test = false; |
1206 | bool noauth = false; |
1207 | bool no_newline = false; |
1208 | bool raw = false; |
1209 | bool host_arg = false; |
1210 | bool port_arg = false; |
1211 | bool key_arg = false; |
1212 | bool shs_cap_key_str_arg = false; |
1213 | bool ipv4_arg = false; |
1214 | bool ipv6_arg = false; |
1215 | bool passthrough = false; |
1216 | bool strings = false; |
1217 | bool daemon = false; |
1218 | enum ip_family ip_family; |
1219 | |
1220 | get_app_dir(app_dir, sizeof(app_dir)); |
1221 | |
1222 | char config_buf[8192]; |
1223 | len = read_file(config_buf, sizeof(config_buf), "%s/config", app_dir); |
1224 | if (len > 0) { |
1225 | ssize_t key_len = json_get_value(config_buf, "key", &key); |
1226 | ssize_t host_len = json_get_value(config_buf, "host", &host); |
1227 | ssize_t port_len = json_get_value(config_buf, "port", &port); |
1228 | ssize_t shs_cap_len = json_get_value(config_buf, "caps.shs", &shs_cap_key_str); |
1229 | if (key_len >= 0) ((char *)key)[key_len] = '\0'; |
1230 | if (host_len >= 0) ((char *)host)[host_len] = '\0'; |
1231 | if (port_len >= 0) ((char *)port)[port_len] = '\0'; |
1232 | if (shs_cap_len >= 0) ((char *)shs_cap_key_str)[shs_cap_len] = '\0'; |
1233 | } else if (len < 0 && errno != ENOENT) { |
1234 | err(1, "failed to read config"); |
1235 | } |
1236 | |
1237 | for (i = 1; i < argc && (argv[i][0] == '-'); i++) { |
1238 | switch (argv[i][1]) { |
1239 | case 'c': shs_cap_key_str = argv[++i]; shs_cap_key_str_arg = true; break; |
1240 | case 'j': ptype = pkt_type_json; break; |
1241 | case 'T': test = true; break; |
1242 | case 's': host = argv[++i]; host_arg = true; break; |
1243 | case 'k': key = argv[++i]; key_arg = true; break; |
1244 | case 'K': keypair_seed_str = argv[++i]; break; |
1245 | case 'p': port = argv[++i]; port_arg = true; break; |
1246 | case 'u': socket_path = argv[++i]; break; |
1247 | case 't': typestr = argv[++i]; break; |
1248 | case 'n': noauth = true; break; |
1249 | case '4': ipv4_arg = true; break; |
1250 | case '6': ipv6_arg = true; break; |
1251 | case 'd': daemon = true; break; |
1252 | case 'a': passthrough = true; break; |
1253 | case 'l': no_newline = true; break; |
1254 | case 'r': raw = true; no_newline = true; break; |
1255 | case 'e': strings = true; break; |
1256 | default: usage(); |
1257 | } |
1258 | } |
1259 | if (i < argc) methodstr = argv[i++]; |
1260 | else if (!test && !passthrough) usage(); |
1261 | |
1262 | if (ipv4_arg && ipv6_arg) errx(1, "options -4 and -6 conflict"); |
1263 | ip_family = |
1264 | ipv4_arg ? ip_family_ipv4 : |
1265 | ipv6_arg ? ip_family_ipv6 : |
1266 | ip_family_any; |
1267 | |
1268 | if (shs_cap_key_str) { |
1269 | rc = pubkey_decode(shs_cap_key_str, shs_cap_key); |
1270 | if (rc < 0) err(1, "unable to decode cap key '%s'", shs_cap_key_str); |
1271 | } else { |
1272 | memcpy(shs_cap_key, ssb_cap, 32); |
1273 | } |
1274 | |
1275 | argument_len = test ? 0 : args_to_json_length(argc-i, argv+i, strings); |
1276 | char argument[argument_len]; |
1277 | |
1278 | if (passthrough) { |
1279 | if (methodstr) errx(1, "-a option conflicts with method"); |
1280 | if (typestr) errx(1, "-a option conflicts with -t option"); |
1281 | if (argc-i > 0) errx(1, "-a option conflicts with method arguments"); |
1282 | if (test) errx(1, "-a option conflicts with -T test"); |
1283 | |
1284 | } else if (!test) { |
1285 | rc = args_to_json(argument, sizeof(argument), argc-i, argv+i, strings); |
1286 | if (rc < 0) errx(1, "unable to collect arguments"); |
1287 | |
1288 | char manifest_buf[8192]; |
1289 | if (!typestr) { |
1290 | len = read_file(manifest_buf, sizeof(manifest_buf), |
1291 | "%s/manifest.json", app_dir); |
1292 | if (len < 0) err(1, "failed to read manifest file"); |
1293 | |
1294 | ssize_t type_len = json_get_value(manifest_buf, methodstr, &typestr); |
1295 | if (!typestr && errno == ENOMSG) errx(1, |
1296 | "unable to find method '%s' in manifest", methodstr); |
1297 | if (!typestr) err(1, "unable to read manifest %s/%s", manifest_buf, methodstr); |
1298 | ((char *)typestr)[type_len] = '\0'; |
1299 | } |
1300 | if (strcmp(typestr, "sync") == 0) type = muxrpc_type_async; |
1301 | else if (strcmp(typestr, "async") == 0) type = muxrpc_type_async; |
1302 | else if (strcmp(typestr, "sink") == 0) type = muxrpc_type_sink; |
1303 | else if (strcmp(typestr, "source") == 0) type = muxrpc_type_source; |
1304 | else if (strcmp(typestr, "duplex") == 0) type = muxrpc_type_duplex; |
1305 | else errx(1, "type must be one of <async|sink|source|duplex>"); |
1306 | |
1307 | rc = method_to_json(method, sizeof(method), methodstr); |
1308 | if (rc < 0) errx(0, "unable to convert method name"); |
1309 | } |
1310 | |
1311 | if (keypair_seed_str == NULL) { |
1312 | read_private_key(app_dir, private_key); |
1313 | memcpy(public_key, private_key+32, 32); |
1314 | } else if (strlen(keypair_seed_str) > 55) { |
1315 | rc = seckey_decode(keypair_seed_str, private_key); |
1316 | if (rc < 0) err(1, "unable to decode private key"); |
1317 | memcpy(public_key, private_key+32, 32); |
1318 | } else if (keypair_seed_str) { |
1319 | unsigned char seed[crypto_sign_SEEDBYTES]; |
1320 | unsigned char ed25519_skpk[crypto_sign_ed25519_SECRETKEYBYTES]; |
1321 | |
1322 | rc = pubkey_decode(keypair_seed_str, ed25519_skpk); |
1323 | if (rc < 0) err(1, "unable to decode private key"); |
1324 | rc = crypto_sign_ed25519_sk_to_seed(seed, ed25519_skpk); |
1325 | if (rc < 0) err(1, "unable to convert private key to seed"); |
1326 | rc = crypto_sign_seed_keypair(public_key, private_key, seed); |
1327 | if (rc < 0) err(1, "unable to generate keypair from seed"); |
1328 | } |
1329 | |
1330 | if (key) { |
1331 | rc = pubkey_decode(key, remote_key); |
1332 | if (rc < 0) err(1, "unable to decode remote key '%s'", key); |
1333 | } else { |
1334 | memcpy(remote_key, public_key, 32); |
1335 | } |
1336 | |
1337 | bool implied_tcp = host_arg || port_arg || ipv4_arg || ipv6_arg; |
1338 | bool implied_auth = key_arg || keypair_seed_str || shs_cap_key_str_arg || test; |
1339 | |
1340 | if (test) { |
1341 | infd = STDIN_FILENO; |
1342 | outfd = STDOUT_FILENO; |
1343 | s = -1; |
1344 | |
1345 | } else if (socket_path) { |
1346 | if (implied_tcp) errx(1, "-u option conflicts with host/port options"); |
1347 | s = unix_connect(socket_path, daemon); |
1348 | if (s < 0) err(1, "unix_connect"); |
1349 | infd = outfd = s; |
1350 | |
1351 | } else if (!implied_tcp && !implied_auth) { |
1352 | char socket_path_buf[_POSIX_PATH_MAX]; |
1353 | rc = get_socket_path(socket_path_buf, sizeof(socket_path_buf), app_dir, daemon); |
1354 | if (rc < 0 && noauth) err(1, "get_socket_path"); |
1355 | if (rc < 0) goto do_tcp; |
1356 | s = unix_connect(socket_path_buf, daemon); |
1357 | if (s < 0 && noauth) err(1, "unix_connect"); |
1358 | if (s < 0) goto do_tcp; |
1359 | noauth = true; |
1360 | infd = outfd = s; |
1361 | |
1362 | } else { |
1363 | do_tcp: |
1364 | s = tcp_connect(host, port, ip_family, daemon); |
1365 | if (s < 0) err(1, "tcp_connect"); |
1366 | infd = outfd = s; |
1367 | } |
1368 | |
1369 | struct boxs bs; |
1370 | if (noauth) { |
1371 | bs.s = s; |
1372 | bs.noauth = true; |
1373 | if (implied_auth) errx(1, "-n option conflicts with -k, -K, -c and -T options."); |
1374 | } else { |
1375 | shs_connect(s, infd, outfd, public_key, private_key, shs_cap_key, remote_key, &bs, !daemon, key_arg); |
1376 | } |
1377 | |
1378 | if (test) { |
1379 | rc = write_all(outfd, bs.encrypt_key, sizeof(bs.encrypt_key)); |
1380 | rc |= write_all(outfd, bs.nonce1, sizeof(bs.nonce1)); |
1381 | rc |= write_all(outfd, bs.decrypt_key, sizeof(bs.decrypt_key)); |
1382 | rc |= write_all(outfd, bs.rx_nonce, sizeof(bs.rx_nonce)); |
1383 | if (rc < 0) err(1, "failed to write handshake result"); |
1384 | return 0; |
1385 | } |
1386 | |
1387 | if (passthrough) { |
1388 | rc = bs_passthrough(&bs, STDIN_FILENO, STDOUT_FILENO); |
1389 | close(s); |
1390 | return rc; |
1391 | } |
1392 | |
1393 | muxrpc_call(&bs, method, argument, type, typestr, 1); |
1394 | |
1395 | if (raw) { |
1396 | struct termios raw_tc; |
1397 | rc = tcgetattr(STDIN_FILENO, &orig_tc); |
1398 | if (rc < 0) warn("tcgetattr"); |
1399 | raw_tc = orig_tc; |
1400 | raw_tc.c_lflag &= ~(ICANON | ECHO); |
1401 | rc = tcsetattr(STDIN_FILENO, TCSANOW, &raw_tc); |
1402 | if (rc < 0) warn("tcgetattr"); |
1403 | rc = atexit(reset_termios); |
1404 | if (rc < 0) warn("atexit"); |
1405 | } |
1406 | |
1407 | switch (type) { |
1408 | case muxrpc_type_async: |
1409 | rc = muxrpc_read_async(&bs, STDOUT_FILENO, 1, no_newline); |
1410 | break; |
1411 | case muxrpc_type_source: |
1412 | rc = muxrpc_read_source(&bs, STDOUT_FILENO, 1, no_newline); |
1413 | break; |
1414 | case muxrpc_type_sink: |
1415 | if (!strcmp(methodstr, "blobs.add")) { |
1416 | rc = muxrpc_write_blob_add(&bs, STDIN_FILENO, STDOUT_FILENO, 1, no_newline); |
1417 | } else { |
1418 | rc = muxrpc_write_sink(&bs, STDIN_FILENO, ptype, 1, no_newline); |
1419 | } |
1420 | break; |
1421 | case muxrpc_type_duplex: |
1422 | rc = muxrpc_duplex(&bs, STDIN_FILENO, STDOUT_FILENO, ptype, 1, no_newline); |
1423 | break; |
1424 | } |
1425 | |
1426 | ps_goodbye(&bs); |
1427 | bs_end(&bs); |
1428 | close(s); |
1429 | return rc; |
1430 | } |
1431 |
Built with git-ssb-web