1 /*
2 * Simple implementation of a client/server proxy.
3 */
4 #include <arcan_shmif.h>
5 #include <arcan_shmif_server.h>
6 #include <errno.h>
7 #include <pwd.h>
8 #include <unistd.h>
9 #include <signal.h>
10 #include <poll.h>
11 #include <fcntl.h>
12 #include <inttypes.h>
13 #include <sys/wait.h>
14 #include <stdarg.h>
15 #include <ctype.h>
16
17 #include <sys/socket.h>
18 #include <sys/stat.h>
19 #include <netdb.h>
20
21 #include "a12.h"
22 #include "a12_int.h"
23 #include "a12_helper.h"
24 #include "anet_helper.h"
25
26 enum anet_mode {
27 ANET_SHMIF_CL = 1,
28 ANET_SHMIF_CL_REVERSE = 2,
29 ANET_SHMIF_SRV,
30 ANET_SHMIF_SRV_INHERIT,
31 ANET_SHMIF_EXEC
32 };
33
34 enum mt_mode {
35 MT_SINGLE = 0,
36 MT_FORK = 1
37 };
38
39 struct arcan_net_meta {
40 struct anet_options* opts;
41 int argc;
42 char** argv;
43 char* bin;
44 };
45
46 static struct {
47 bool soft_auth;
48 bool no_default;
49 size_t accept_n_pk_unknown;
50 size_t backpressure;
51 size_t backpressure_soft;
52 } global = {
53 .backpressure_soft = 2,
54 .backpressure = 6
55 };
56
57 static const char* trace_groups[] = {
58 "video",
59 "audio",
60 "system",
61 "event",
62 "debug",
63 "missing",
64 "alloc",
65 "crypto",
66 "vdetail",
67 "btransfer",
68 "security"
69 };
70
tracestr_to_bitmap(char * work)71 static int tracestr_to_bitmap(char* work)
72 {
73 int res = 0;
74 char* pt = strtok(work, ",");
75 while(pt != NULL){
76 for (size_t i = 1; i <= COUNT_OF(trace_groups); i++){
77 if (strcasecmp(trace_groups[i-1], pt) == 0){
78 res |= 1 << (i - 1);
79 break;
80 }
81 }
82 pt = strtok(NULL, ",");
83 }
84 return res;
85 }
86
87 /*
88 * pull in from arcan codebase, chacha based CSPRNG
89 */
90 extern void arcan_random(uint8_t* dst, size_t ntc);
91
92 /*
93 * Since we pull in some functions from the main arcan codebase, we need to
94 * define this symbol, used if the random function has problems with entropy
95 * etc.
96 */
arcan_fatal(const char * msg,...)97 void arcan_fatal(const char* msg, ...)
98 {
99 va_list args;
100 va_start(args, msg);
101 vfprintf(stderr, msg, args);
102 va_end(args);
103 exit(EXIT_FAILURE);
104 }
105
get_keystore_dirfd(const char ** err)106 static int get_keystore_dirfd(const char** err)
107 {
108 char* basedir = getenv("ARCAN_STATEPATH");
109
110 if (!basedir){
111 *err = "Missing keystore (set ARCAN_STATEPATH)";
112 return -1;
113 }
114
115 int dir = open(basedir, O_DIRECTORY);
116 if (-1 == dir){
117 *err = "Error opening basedir, check permissions and type";
118 return -1;
119 }
120
121 return dir;
122 }
123
124
125 /*
126 * in this mode we should really fexec ourselves so we don't risk exposing
127 * aslr or canaries, as well as handle the key-generation
128 */
fork_a12srv(struct a12_state * S,int fd,void * tag)129 static void fork_a12srv(struct a12_state* S, int fd, void* tag)
130 {
131 pid_t fpid = fork();
132 if (fpid == 0){
133 /* Split the log output on debug so we see what is going on */
134 #ifdef _DEBUG
135 char buf[sizeof("cl_log_xxxxxx.log")];
136 snprintf(buf, sizeof(buf), "cl_log_%.6d.log", (int) getpid());
137 FILE* fpek = fopen(buf, "w+");
138 if (fpek){
139 a12_set_trace_level(a12_trace_targets, fpek);
140 }
141 close(STDERR_FILENO);
142 #endif
143 /* we should really re-exec ourselves with the 'socket-passing' setup so that
144 * we won't act as a possible ASLR break */
145 arcan_shmif_privsep(NULL, "shmif", NULL, 0);
146 int rc = a12helper_a12srv_shmifcl(S, NULL, fd, fd);
147 shutdown(fd, SHUT_RDWR);
148 close(fd);
149 exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
150 }
151 else if (fpid == -1){
152 a12int_trace(A12_TRACE_SYSTEM, "couldn't fork/dispatch, ulimits reached?\n");
153 a12_channel_close(S);
154 close(fd);
155 return;
156 }
157 else {
158 /* just ignore and return to caller */
159 a12int_trace(A12_TRACE_SYSTEM, "client handed off to %d", (int)fpid);
160 a12_channel_close(S);
161 close(fd);
162 }
163 }
164
165 extern char** environ;
166
handover_setup(struct a12_state * S,int fd,struct arcan_net_meta * meta,struct shmifsrv_client ** C)167 static bool handover_setup(struct a12_state* S,
168 int fd, struct arcan_net_meta* meta, struct shmifsrv_client** C)
169 {
170 if (meta->opts->mode != ANET_SHMIF_EXEC)
171 return true;
172
173 /* wait for authentication before going for the shmifsrv processing mode */
174 char* msg;
175 if (!anet_authenticate(S, fd, fd, &msg)){
176 a12int_trace(A12_TRACE_SYSTEM, "authentication failed: %s", msg);
177 free(msg);
178 shutdown(fd, SHUT_RDWR);
179 close(fd);
180 return false;
181 }
182 a12int_trace(A12_TRACE_SYSTEM, "client connected, spawning: %s", meta->bin);
183
184 /* connection is ok, tie it to a new shmifsrv_client via the exec arg. The GUID
185 * is left 0 here as the local bound applications tend to not have much of a
186 * perspective on that. Should it become relevant, just stepping Kp with a local
187 * salt through the hash should do the trick. */
188 int socket, errc;
189 struct shmifsrv_envp env = {
190 .init_w = 32, .init_h = 32,
191 .path = meta->bin,
192 .argv = meta->argv, .envv = environ
193 };
194
195 *C = shmifsrv_spawn_client(env, &socket, &errc, 0);
196 if (!*C){
197 shutdown(fd, SHUT_RDWR);
198 close(fd);
199 return false;
200 }
201
202 return true;
203 }
204
vcodec_tuning(struct a12_state * S,int segid,struct shmifsrv_vbuffer * vb,void * tag)205 static struct a12_vframe_opts vcodec_tuning(
206 struct a12_state* S, int segid, struct shmifsrv_vbuffer* vb, void* tag)
207 {
208 struct a12_vframe_opts opts = {
209 .method = VFRAME_METHOD_DZSTD,
210 .bias = VFRAME_BIAS_BALANCED
211 };
212
213 /* missing: here a12_state_iostat could be used to get congestion information
214 * and encoder feedback, then use that to forward new parameters + bias */
215 switch (segid){
216 case SEGID_LWA:
217 opts.method = VFRAME_METHOD_H264;
218 break;
219 case SEGID_GAME:
220 opts.method = VFRAME_METHOD_H264;
221 opts.bias = VFRAME_BIAS_LATENCY;
222 break;
223
224 /* this one is also a possible subject for codec passthrough, that will have
225 * to be implemented in the server util part as we need shmif to propagate if
226 * we can deal with passthrough and then device_fail that if the other end
227 * starts to reject the bitstream */
228 case SEGID_MEDIA:
229 opts.method = VFRAME_METHOD_H264;
230 opts.bias = VFRAME_BIAS_QUALITY;
231 break;
232 }
233
234 /* this is temporary until we establish a config format where the parameters
235 * can be set in a non-commandline friendly way (recall ARCAN_CONNPATH can
236 * result in handover-exec arcan-net */
237 if (opts.method == VFRAME_METHOD_H264){
238 static bool got_opts;
239 static unsigned long cbr = 22;
240 static unsigned long br = 1024;
241
242 /* convert and clamp to the values supported by x264 in ffmpeg */
243 if (!got_opts){
244 char* tmp;
245 if ((tmp = getenv("A12_VENC_CRF"))){
246 cbr = strtoul(tmp, NULL, 10);
247 if (cbr > 55)
248 cbr = 55;
249 }
250 if ((tmp = getenv("A12_VENC_RATE"))){
251 br = strtoul(tmp, NULL, 10);
252 if (br * 1000 > INT_MAX)
253 br = INT_MAX;
254 }
255 got_opts = true;
256 }
257
258 opts.ratefactor = cbr;
259 opts.bitrate = br;
260 }
261
262 return opts;
263 }
264
single_a12srv(struct a12_state * S,int fd,void * tag)265 static void single_a12srv(struct a12_state* S, int fd, void* tag)
266 {
267 struct shmifsrv_client* C = NULL;
268 struct arcan_net_meta* meta = tag;
269
270 if (!handover_setup(S, fd, meta, &C))
271 return;
272
273 if (C){
274 a12helper_a12cl_shmifsrv(S, C, fd, fd, (struct a12helper_opts){
275 .dirfd_temp = -1,
276 .dirfd_cache = -1,
277 .redirect_exit = meta->opts->redirect_exit,
278 .devicehint_cp = meta->opts->devicehint_cp,
279 .vframe_block = global.backpressure,
280 .vframe_soft_block = global.backpressure_soft,
281 .eval_vcodec = vcodec_tuning
282 });
283 shmifsrv_free(C, SHMIFSRV_FREE_NO_DMS);
284 }
285 else{
286 a12helper_a12srv_shmifcl(S, NULL, fd, fd);
287 shutdown(fd, SHUT_RDWR);
288 close(fd);
289 }
290 }
291
a12cl_dispatch(struct anet_options * args,struct a12_state * S,struct shmifsrv_client * cl,int fd)292 static void a12cl_dispatch(
293 struct anet_options* args,
294 struct a12_state* S, struct shmifsrv_client* cl, int fd)
295 {
296 /* note that the a12helper will do the cleanup / free */
297 a12helper_a12cl_shmifsrv(S, cl, fd, fd, (struct a12helper_opts){
298 .dirfd_temp = -1,
299 .dirfd_cache = -1,
300 .vframe_block = global.backpressure,
301 .redirect_exit = args->redirect_exit,
302 .devicehint_cp = args->devicehint_cp
303 });
304 close(fd);
305 }
306
fork_a12cl_dispatch(struct anet_options * args,struct a12_state * S,struct shmifsrv_client * cl,int fd)307 static void fork_a12cl_dispatch(
308 struct anet_options* args,
309 struct a12_state* S, struct shmifsrv_client* cl, int fd)
310 {
311 pid_t fpid = fork();
312 if (fpid == 0){
313 /* missing: extend sandboxing, close stdio */
314 a12helper_a12cl_shmifsrv(S, cl, fd, fd, (struct a12helper_opts){
315 .dirfd_temp = -1,
316 .dirfd_cache = -1,
317 .vframe_block = global.backpressure,
318 .redirect_exit = args->redirect_exit,
319 .devicehint_cp = args->devicehint_cp
320 });
321 exit(EXIT_SUCCESS);
322 }
323 else if (fpid == -1){
324 fprintf(stderr, "fork_a12cl() couldn't fork new process, check ulimits\n");
325 shmifsrv_free(cl, SHMIFSRV_FREE_NO_DMS);
326 a12_channel_close(S);
327 close(fd);
328 return;
329 }
330 else {
331 /* just ignore and return to caller */
332 a12int_trace(A12_TRACE_SYSTEM, "client handed off to %d", (int)fpid);
333 a12_channel_close(S);
334 shmifsrv_free(cl, SHMIFSRV_FREE_LOCAL);
335 close(fd);
336 }
337 }
338
find_connection(struct anet_options * opts,struct shmifsrv_client * cl)339 static struct anet_cl_connection find_connection(
340 struct anet_options* opts, struct shmifsrv_client* cl)
341 {
342 struct anet_cl_connection anet;
343 int rc = opts->retry_count;
344 int timesleep = 1;
345
346 /* connect loop until retry count exceeded */
347 while (rc != 0 && (!cl || (shmifsrv_poll(cl) != CLIENT_DEAD))){
348 anet = anet_cl_setup(opts);
349
350 if (anet.state)
351 break;
352
353 if (!anet.state){
354 if (anet.errmsg){
355 fputs(anet.errmsg, stderr);
356 free(anet.errmsg);
357 anet.errmsg = NULL;
358 }
359
360 if (timesleep < 10)
361 timesleep++;
362
363 if (rc > 0)
364 rc--;
365
366 sleep(timesleep);
367 continue;
368 }
369 }
370
371 return anet;
372 }
373
374 /* connect / authloop shmifsrv */
forward_shmifsrv_cl(struct shmifsrv_client * cl,struct anet_options * opts)375 static struct anet_cl_connection forward_shmifsrv_cl(
376 struct shmifsrv_client* cl, struct anet_options* opts)
377 {
378 struct anet_cl_connection anet = find_connection(opts, cl);
379
380 /* failed, or retry-count exceeded? */
381 if (!anet.state || shmifsrv_poll(cl) == CLIENT_DEAD){
382 shmifsrv_free(cl, SHMIFSRV_FREE_NO_DMS);
383
384 if (anet.state){
385 a12_free(anet.state);
386 close(anet.fd);
387
388 if (anet.errmsg)
389 free(anet.errmsg);
390 }
391 }
392
393 /* wait / block the processing until we know the connection is authentic,
394 * this will callback into keystore and so on */
395 char* msg;
396 if (!anet_authenticate(anet.state,
397 anet.fd, anet.fd, &msg) || shmifsrv_poll(cl) == CLIENT_DEAD){
398 shmifsrv_free(cl, SHMIFSRV_FREE_NO_DMS);
399 if (anet.state){
400 a12_free(anet.state);
401 close(anet.fd);
402 }
403
404 if (anet.errmsg)
405 free(anet.errmsg);
406 }
407
408 return anet;
409 }
410
a12_connect(struct anet_options * args,void (* dispatch)(struct anet_options * args,struct a12_state * S,struct shmifsrv_client * cl,int fd))411 static int a12_connect(struct anet_options* args,
412 void (*dispatch)(
413 struct anet_options* args,
414 struct a12_state* S, struct shmifsrv_client* cl, int fd))
415 {
416 signal(SIGPIPE, SIG_IGN);
417 signal(SIGCHLD, SIG_IGN);
418
419 int shmif_fd = -1;
420 for(;;){
421 struct shmifsrv_client* cl =
422 shmifsrv_allocate_connpoint(args->cp, NULL, S_IRWXU, shmif_fd);
423
424 if (!cl){
425 fprintf(stderr, "couldn't open connection point\n");
426 return EXIT_FAILURE;
427 }
428
429 /* first time, extract the connection point descriptor from the connection */
430 if (-1 == shmif_fd)
431 shmif_fd = shmifsrv_client_handle(cl);
432
433 struct pollfd pfd = {.fd = shmif_fd, .events = POLLIN | POLLERR | POLLHUP};
434
435 /* wait for a connection */
436 for(;;){
437 int pv = poll(&pfd, 1, -1);
438 if (-1 == pv){
439 if (errno != EINTR && errno != EAGAIN){
440 shmifsrv_free(cl, true);
441 fprintf(stderr, "error while waiting for a connection\n");
442 return EXIT_FAILURE;
443 }
444 continue;
445 }
446 else if (pv)
447 break;
448 }
449
450 /* accept it (this will mutate the client_handle internally) */
451 shmifsrv_poll(cl);
452
453 /* setup the connection, we do this after the fact rather than before as remote
454 * is more likely to have a timeout than locally */
455 struct anet_cl_connection anet = forward_shmifsrv_cl(cl, args);
456
457 /* wake the client */
458 a12int_trace(A12_TRACE_SYSTEM, "local connection found, forwarding to dispatch");
459 dispatch(args, anet.state, cl, anet.fd);
460 }
461
462 return EXIT_SUCCESS;
463 }
464
465 /* Special version of a12_connect where we inherit the connection primitive
466 * to the local shmif client, so we can forego most of the domainsocket bits.
467 * The normal use-case for this is where ARCAN_CONNPATH is set to a12://
468 * prefix and shmif execs into arcan-net */
a12_preauth(struct anet_options * args,void (* dispatch)(struct anet_options * args,struct a12_state * S,struct shmifsrv_client * cl,int fd))469 static int a12_preauth(struct anet_options* args,
470 void (*dispatch)(
471 struct anet_options* args,
472 struct a12_state* S, struct shmifsrv_client* cl, int fd))
473 {
474 int sc;
475 struct shmifsrv_client* cl = shmifsrv_inherit_connection(args->sockfd, &sc);
476 if (!cl){
477 fprintf(stderr, "(shmif::arcan-net) "
478 "couldn't build connection from socket (%d)\n", sc);
479 shutdown(args->sockfd, SHUT_RDWR);
480 close(args->sockfd);
481 return EXIT_FAILURE;
482 }
483
484 struct anet_cl_connection anet = forward_shmifsrv_cl(cl, args);
485
486 /* and ack the connection */
487 dispatch(args, anet.state, cl, anet.fd);
488
489 return EXIT_SUCCESS;
490 }
491
tag_host(struct anet_options * anet,char * hoststr,const char ** err)492 static bool tag_host(struct anet_options* anet, char* hoststr, const char** err)
493 {
494 char* toksep = strrchr(hoststr, '@');
495 if (!toksep)
496 return false;
497
498 *toksep = '\0';
499 anet->key = hoststr;
500 anet->keystore.type = A12HELPER_PROVIDER_BASEDIR;
501 anet->keystore.directory.dirfd = get_keystore_dirfd(err);
502
503 return true;
504 }
505
show_usage(const char * msg)506 static bool show_usage(const char* msg)
507 {
508 fprintf(stderr, "%s%sUsage:\n"
509 "Forward local arcan applications (push): \n"
510 " arcan-net [-Xtd] -s connpoint [tag@]host port\n"
511 " (keystore-mode) -s connpoint tag@\n"
512 " (inherit socket) -S fd_no host port\n\n"
513 "Server local arcan application (pull): \n"
514 " -l port [ip] -exec /usr/bin/app arg1 arg2 argn\n\n"
515 "Bridge remote inbound arcan applications (to ARCAN_CONNPATH): \n"
516 " arcan-net [-Xtd] -l port [ip]\n\n"
517 "Bridge remote outbound arcan application: \n"
518 " arcan-net [tag@]host port\n\n"
519 "Forward-local options:\n"
520 "\t-X \t Disable EXIT-redirect to ARCAN_CONNPATH env (if set)\n"
521 "\t-r, --retry n \t Limit retry-reconnect attempts to 'n' tries\n\n"
522 "Options:\n"
523 "\t-a, --auth n \t Read authentication secret from stdin (maxlen:32)\n"
524 "\t --soft-auth \t authentication secret (password) only, ignore keystore\n"
525 "\t \t if [n] is provided, n keys added to trusted\n"
526 "\t-t \t Single- client (no fork/mt - easier troubleshooting)\n"
527 "\t --no-ephem-rt \t Disable ephemeral keypair roundtrip (outbound only)\n"
528 "\t-d bitmap \t Set trace bitmap (bitmask or key1,key2,...)\n\n"
529 "Environment variables:\n"
530 "\tARCAN_STATEPATH\t Used for keystore and state blobs (sensitive)\n"
531 #ifdef WANT_H264_ENC
532 "\tA12_VENC_CRF \t video rate factor (sane=17..28) (0=lossless,51=worst)\n"
533 "\tA12_VENC_RATE \t bitrate in kilobits/s (hintcap to crf)\n"
534 #endif
535 "\tA12_VBP \t backpressure maximium cap (0..8)\n"
536 "\tA12_VBP_SOFT \t backpressure soft (full-frames) cap (< VBP)\n"
537 "\tA12_CACHE_DIR \t Used for caching binary stores (fonts, ...)\n\n"
538 "Keystore mode (ignores connection arguments):\n"
539 "\tAdd/Append key: arcan-net keystore tag host [port=6680]\n"
540 "\t tag=default is reserved\n"
541 "\nTrace groups (stderr):\n"
542 "\tvideo:1 audio:2 system:4 event:8 transfer:16\n"
543 "\tdebug:32 missing:64 alloc:128 crypto:256 vdetail:512\n"
544 "\tbtransfer:1024, security:2048 \n\n", msg ? msg : "", msg ? "\n\n" : ""
545 );
546 return false;
547 }
548
apply_commandline(int argc,char ** argv,struct arcan_net_meta * meta)549 static int apply_commandline(int argc, char** argv, struct arcan_net_meta* meta)
550 {
551 const char* modeerr = "Mixed or multiple -s or -l arguments";
552 struct anet_options* opts = meta->opts;
553
554 /* default-trace security warnings */
555 a12_set_trace_level(2048, stderr);
556
557 size_t i = 1;
558 /* mode defining switches and shared switches */
559 for (; i < argc; i++){
560 if (argv[i][0] != '-')
561 break;
562
563 if (strcmp(argv[i], "-d") == 0){
564 if (i == argc - 1)
565 return show_usage("-d without trace value argument");
566 char* workstr = NULL;
567 unsigned long val = strtoul(argv[++i], &workstr, 10);
568 if (workstr == argv[i]){
569 val = tracestr_to_bitmap(workstr);
570 }
571 a12_set_trace_level(val, stderr);
572 }
573
574 /* a12 client, shmif server */
575 else if (strcmp(argv[i], "-s") == 0){
576 if (opts->mode)
577 return show_usage(modeerr);
578
579 opts->mode = ANET_SHMIF_SRV;
580 if (i >= argc - 1)
581 return show_usage("-s: Missing connpoint argument");
582 opts->cp = argv[++i];
583
584 /* shmif connection points are restricted set */
585 for (size_t ind = 0; opts->cp[ind]; ind++)
586 if (!isalnum(opts->cp[ind]))
587 return show_usage("-s: Invalid character in connpoint [a-Z,0-9]");
588
589 i++;
590 if (i >= argc)
591 return show_usage("-s: Missing tag@ or host port argument");
592
593 const char* err = NULL;
594 if (tag_host(opts, argv[i], &err)){
595 if (err)
596 return show_usage(err);
597 continue;
598 }
599
600 opts->host = argv[i++];
601
602 if (i >= argc)
603 return show_usage("-s: Missing port argument");
604
605 opts->port = argv[i];
606
607 if (i != argc - 1)
608 return show_usage("-s: Trailing arguments after port");
609
610 continue;
611 }
612 /* a12 client, shmif server, inherit primitives */
613 else if (strcmp(argv[i], "-S") == 0){
614 if (opts->mode)
615 return show_usage(modeerr);
616
617 opts->mode = ANET_SHMIF_SRV_INHERIT;
618 if (i >= argc - 1)
619 return show_usage("Invalid arguments, -S without room for ip");
620
621 opts->sockfd = strtoul(argv[++i], NULL, 10);
622 struct stat fdstat;
623
624 if (-1 == fstat(opts->sockfd, &fdstat))
625 return show_usage("Couldn't stat -S descriptor");
626
627 if ((fdstat.st_mode & S_IFMT) != S_IFSOCK)
628 return show_usage("-S descriptor does not point to a socket");
629
630 if (i == argc)
631 return show_usage("-S without room for tag or host/port");
632
633 i++;
634 const char* err = NULL;
635 if (tag_host(opts, argv[i], &err)){
636 if (err)
637 return show_usage(err);
638 continue;
639 }
640
641 opts->host = argv[i++];
642
643 if (i == argc)
644 return show_usage("-S host without room for port argument");
645
646 opts->port = argv[i++];
647
648 if (i < argc)
649 return show_usage("Trailing arguments to -S fd_in host port");
650 }
651 else if (strcmp(argv[i], "--soft-auth") == 0){
652 global.soft_auth = true;
653 }
654 else if (strcmp(argv[i], "--no-ephem-rt") == 0){
655 opts->opts->disable_ephemeral_k = true;
656 }
657 /* a12 server, shmif client */
658 else if (strcmp(argv[i], "-l") == 0){
659 if (opts->mode)
660 return show_usage(modeerr);
661 opts->mode = ANET_SHMIF_CL;
662
663 if (i == argc - 1)
664 return show_usage("-l without room for port argument");
665
666 opts->port = argv[++i];
667 for (size_t ind = 0; opts->port[ind]; ind++)
668 if (opts->port[ind] < '0' || opts->port[ind] > '9')
669 return show_usage("Invalid values in port argument");
670
671 /* more optional / annoying components here, find host if host is there,
672 * then check if we should exec map something to an authenticated connection */
673 if (i == argc - 1)
674 return i;
675
676 if (strcmp(argv[++i], "-exec") != 0){
677 opts->host = argv[i++];
678 if (i == argc - 1)
679 return i;
680 }
681
682 if (strcmp(argv[i], "-exec") != 0){
683 return show_usage("Unexpected trailing argument, expected -exec or end");
684 }
685
686 if (i == argc - 1)
687 return show_usage("-exec without bin arg0 .. argn");
688
689 i++;
690 meta->bin = argv[i];
691 meta->argv = &argv[i];
692 opts->mode = ANET_SHMIF_EXEC;
693
694 return i;
695 }
696 else if (strcmp(argv[i], "-t") == 0){
697 opts->mt_mode = MT_SINGLE;
698 }
699 else if (strcmp(argv[i], "-X") == 0){
700 opts->redirect_exit = NULL;
701 }
702 else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--auth") == 0){
703 char msg[32];
704
705 if (isatty(STDIN_FILENO)){
706 char* pwd = getpass("connection password: ");
707 snprintf(msg, 32, "%s", pwd);
708 while (*pwd){
709 (*pwd) = '\0';
710 pwd++;
711 }
712 }
713 else {
714 fprintf(stdout, "reading passphrase from stdin\n");
715 if (fgets(msg, 32, stdin) <= 0)
716 return show_usage("-a,--auth couldn't read secret from stdin");
717 }
718 size_t len = strlen(msg);
719 if (!len){
720 return show_usage("-a,--auth zero-length secret not permitted");
721 }
722
723 if (msg[len-1] == '\n')
724 msg[len-1] = '\0';
725
726 snprintf(opts->opts->secret, 32, "%s", msg);
727 global.no_default = true;
728
729 if (i < argc - 1 && isdigit(argv[i+1][0])){
730 global.accept_n_pk_unknown = strtoul(argv[i+1], NULL, 10);
731 i++;
732 a12int_trace(A12_TRACE_SECURITY,
733 "trust_first=%zu", global.accept_n_pk_unknown);
734 }
735
736 }
737 else if (strcmp(argv[i], "-B") == 0){
738 if (i == argc - 1)
739 return show_usage("-B without bitrate argument");
740
741 if (!isdigit(argv[i+1][0]))
742 return show_usage("-B bitrate should be a number");
743 }
744 else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--retry") == 0){
745 if (1 < argc - 1){
746 opts->retry_count = (ssize_t) strtol(argv[++i], NULL, 10);
747 }
748 else
749 return show_usage("Missing count argument to -r,--retry");
750 }
751 }
752
753 char* tmp;
754 if ((tmp = getenv("A12_VBP"))){
755 size_t bp = strtoul(tmp, NULL, 10);
756 if (bp >= 0 && bp <= 8)
757 global.backpressure = bp;
758 }
759
760 if ((tmp = getenv("A12_VBP_SOFT"))){
761 size_t bp = strtoul(tmp, NULL, 10);
762 if (bp >= 0 && bp <= global.backpressure)
763 global.backpressure_soft = bp;
764 }
765
766 return i;
767 }
768
769 /* keystore is singleton global */
open_keystore(const char ** err)770 static bool open_keystore(const char** err)
771 {
772 int dir = get_keystore_dirfd(err);
773 if (-1 == dir)
774 return false;
775
776 struct keystore_provider prov = {
777 .directory.dirfd = dir,
778 .type = A12HELPER_PROVIDER_BASEDIR
779 };
780
781 if (!a12helper_keystore_open(&prov)){
782 *err = "Couldn't open keystore from basedir (ARCAN_STATEPATH)";
783 return false;
784 }
785
786 return true;
787 }
788
apply_keystore_command(int argc,char ** argv)789 static int apply_keystore_command(int argc, char** argv)
790 {
791 /* (opt, -b dir) */
792 if (!argc)
793 return show_usage("Missing keystore command arguments");
794
795 const char* err;
796 if (!open_keystore(&err)){
797 return show_usage(err);
798 }
799
800 /* time for tag, host and port */
801 if (!argc || argc < 2){
802 a12helper_keystore_release();
803 return show_usage("Missing tag / host arguments");
804 }
805
806 char* tag = argv[0];
807 char* host = argv[1];
808 argc -= 2;
809 argv += 2;
810
811 unsigned long port = 6680;
812 if (argc){
813 port = strtoul(argv[0], NULL, 10);
814 if (!port || port > 65535){
815 a12helper_keystore_release();
816 return show_usage("Port argument is invalid or out of range");
817 }
818 }
819
820 uint8_t outpub[32];
821 if (!a12helper_keystore_register(tag, host, port, outpub)){
822 return show_usage("Couldn't add/create tag in keystore");
823 }
824
825 size_t outl;
826 unsigned char* b64 = a12helper_tob64(outpub, 32, &outl);
827 fprintf(stdout, "pubk.b64=%s *\n", b64);
828 free(b64);
829
830 a12helper_keystore_release();
831
832 return EXIT_SUCCESS;
833 }
834
835 /*
836 * This is used both for the inbound and outbound connection, the difference
837 * though is that the outbound connection has already provided keymaterial
838 * as that is necessary for constructing the HELLO packet - so the auth is
839 * just against if the public key is known/trusted or not in that case.
840 *
841 * Thus the a12 implementation will only respect the 'authentic' part and
842 * disregard whatever is put in key.
843 *
844 * For inbound, there is an option of differentiation - a different keypair
845 * could be returned based on the public key.
846 */
key_auth_local(uint8_t pk[static32])847 static struct pk_response key_auth_local(uint8_t pk[static 32])
848 {
849 struct pk_response auth = {0};
850 char* tmp;
851 uint16_t tmpport;
852 size_t outl;
853 unsigned char* out = a12helper_tob64(pk, 32, &outl);
854
855 /* don't really care about pk authenticity - password in first HMAC only */
856 if (global.soft_auth){
857 auth.authentic = true;
858 a12helper_keystore_hostkey("default", 0, auth.key, &tmp, &tmpport);
859 a12int_trace(A12_TRACE_SECURITY, "soft-auth-trust=%s", out);
860 }
861 /* or is the key in our trusted set? */
862 else if (a12helper_keystore_accepted(pk, NULL)){
863 auth.authentic = true;
864 a12int_trace(A12_TRACE_SECURITY, "accept=%s", out);
865 a12helper_keystore_hostkey("default", 0, auth.key, &tmp, &tmpport);
866 }
867 /* or should we add the first n unknown as implicitly trusted through pass */
868 else if (global.accept_n_pk_unknown){
869 auth.authentic = true;
870 global.accept_n_pk_unknown--;
871 a12int_trace(A12_TRACE_SECURITY,
872 "left=%zu:accept-unknown=%s", global.accept_n_pk_unknown, out);
873 a12helper_keystore_accept(pk, NULL);
874 a12helper_keystore_hostkey("default", 0, auth.key, &tmp, &tmpport);
875 }
876 else {
877 a12int_trace(A12_TRACE_SECURITY, "reject-unknown=%s", out);
878 }
879
880 free(out);
881 return auth;
882 }
883
main(int argc,char ** argv)884 int main(int argc, char** argv)
885 {
886 struct anet_options anet = {
887 .retry_count = -1,
888 .mt_mode = MT_FORK,
889 /* do note that pk_lookup is left empty == only password auth */
890 };
891
892 struct arcan_net_meta meta = {
893 .opts = &anet
894 };
895
896 anet.opts = a12_sensitive_alloc(sizeof(struct a12_context_options));
897 anet.opts->pk_lookup = key_auth_local;
898
899 /* set this as default, so the remote side can't actually close */
900 anet.redirect_exit = getenv("ARCAN_CONNPATH");
901 anet.devicehint_cp = getenv("ARCAN_CONNPATH");
902
903 if (argc > 1 && strcmp(argv[1], "keystore") == 0){
904 return apply_keystore_command(argc-2, argv+2);
905 }
906
907 if (argc < 2 || (argc == 2 &&
908 (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)))
909 return show_usage(NULL);
910
911 size_t argi = apply_commandline(argc, argv, &meta);
912 if (!argi)
913 return EXIT_FAILURE;
914
915 /* no mode? if there's arguments left, assume it is is the 'reverse' mode
916 * where the connection is outbound but we get the a12 'client' view back
917 * to pair with an arcan-net -exec .. */
918 if (!anet.mode){
919
920 if (argi <= argc - 1){
921 /* Treat as a key- 'tag' for connecting? This act as a namespace separator
922 * so the other option would be to */
923 const char* err = NULL;
924 if (tag_host(&anet, argv[argi], &err)){
925 if (err)
926 return show_usage(err);
927 }
928 /* Or just go host / [port] */
929 else {
930 anet.host = argv[argi++];
931 anet.port = "6680";
932
933 if (argi <= argc - 1){
934 anet.port = argv[argi];
935 }
936 }
937
938 /* Make the outbound connection */
939 struct anet_cl_connection cl = find_connection(&anet, NULL);
940 if (!cl.state){
941 if (anet.key)
942 fprintf(stderr, "couldn't connect to any host for key %s\n", anet.key);
943 else
944 fprintf(stderr, "couldn't connect to %s\n", anet.host);
945
946 return EXIT_FAILURE;
947 }
948
949 int rc = a12helper_a12srv_shmifcl(cl.state, NULL, cl.fd, cl.fd);
950 shutdown(cl.fd, SHUT_RDWR);
951 close(cl.fd);
952
953 return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
954 }
955 else
956 return show_usage("No mode specified, please use -s or -l form");
957 }
958
959 char* errmsg;
960
961 /* enable asymetric encryption and keystore */
962 const char* err;
963 if (global.soft_auth){
964 a12int_trace(A12_TRACE_SECURITY, "weak-security=password only");
965 if (!global.no_default){
966 a12int_trace(A12_TRACE_SECURITY, "no-security=default password");
967 }
968 }
969 else {
970 if (!open_keystore(&err)){
971 return show_usage(err);
972 }
973 /* We have a keystore and are listening for an inbound connection, make sure
974 * that there is a local key defined that we can use for the reply. There is a
975 * point to making this more refined by also allowing the keystore to define
976 * specific reply pubk when marking a client as accepted in order to
977 * differentiate in both direction */
978 uint8_t priv[32];
979 char* outhost;
980 uint16_t outport;
981 if (!a12helper_keystore_hostkey("default", 0, priv, &outhost, &outport)){
982 uint8_t outp[32];
983 a12helper_keystore_register("default", "127.0.0.1", 6680, outp);
984 a12int_trace(A12_TRACE_SECURITY, "key_added=default");
985 }
986 }
987
988 if (anet.mode == ANET_SHMIF_CL || anet.mode == ANET_SHMIF_EXEC){
989 switch (anet.mt_mode){
990 case MT_SINGLE:
991 anet_listen(&anet, &errmsg, single_a12srv, &meta);
992 fprintf(stderr, "%s", errmsg ? errmsg : "");
993 case MT_FORK:
994 anet_listen(&anet, &errmsg, fork_a12srv, &meta);
995 fprintf(stderr, "%s", errmsg ? errmsg : "");
996 free(errmsg);
997 break;
998 default:
999 break;
1000 }
1001 return EXIT_FAILURE;
1002 }
1003 if (anet.mode == ANET_SHMIF_SRV_INHERIT){
1004 return a12_preauth(&anet, a12cl_dispatch);
1005 }
1006
1007 /* ANET_SHMIF_SRV */
1008 switch (anet.mt_mode){
1009 case MT_SINGLE:
1010 return a12_connect(&anet, a12cl_dispatch);
1011 break;
1012 case MT_FORK:
1013 return a12_connect(&anet, fork_a12cl_dispatch);
1014 break;
1015 default:
1016 return EXIT_FAILURE;
1017 break;
1018 }
1019 }
1020