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