• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Example embedded sshd server using libwebsockets sshd plugin
3  *
4  * Written in 2010-2019 by Andy Green <andy@warmcat.com>
5  *
6  * This file is made available under the Creative Commons CC0 1.0
7  * Universal Public Domain Dedication.
8  *
9  * The person who associated a work with this deed has dedicated
10  * the work to the public domain by waiving all of his or her rights
11  * to the work worldwide under copyright law, including all related
12  * and neighboring rights, to the extent allowed by law. You can copy,
13  * modify, distribute and perform the work, even for commercial purposes,
14  * all without asking permission.
15  *
16  * The test apps are intended to be adapted for use in your code, which
17  * may be proprietary.	So unlike the library itself, they are licensed
18  * Public Domain.
19  *
20  *
21  * This test app listens on port 2200 for authorized ssh connections.  Run it
22  * using
23  *
24  * $ sudo libwebsockets-test-sshd
25  *
26  * Connect to it using the test private key with:
27  *
28  * $ ssh -p 2200 -i /usr/local/share/libwebsockets-test-server/lws-ssh-test-keys anyuser@127.0.0.1
29  */
30 
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35 #include <signal.h>
36 
37 /* import the whole of lws-plugin-sshd-base statically */
38 #include <lws-plugin-sshd-static-build-includes.h>
39 
40 /*
41  * We store the test server's own key here (will be created with a new
42  * random key if it doesn't exist
43  *
44  * The /etc path is the only reason we have to run as root.
45  */
46 #define TEST_SERVER_KEY_PATH "/etc/lws-test-sshd-server-key"
47 
48 /*
49  *  This is a copy of the lws ssh test public key, you can find it in
50  *  /usr[/local]/share/libwebsockets-test-server/lws-ssh-test-keys.pub
51  *  and the matching private key there too in .../lws-ssh-test-keys
52  *
53  *  These keys are distributed for testing!  Don't use them on a real system
54  *  unless you want anyone with a copy of lws to access it.
55  */
56 static const char *authorized_key =
57 	"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnWiP+c+kSD6Lk+C6NA9KruApa45sbt"
58 	"94/dxT0bCITlAA/+PBk6mR1lwWgXYozOMdrHrqx34piqDyXnc4HabqCaOm/FrYhkCPL8z"
59 	"a26PMYqteSosuwKv//5iT6ZWhNnsMwExBwtV6MIq0MxAeWqxRnYNWpNM8iN6sFzkdG/YF"
60 	"dyHrIBTgwzM77NLCMl6GEkJErRCFppC2SwYxGa3BRrgUwX3LkV8HpMIaYHFo1Qgj7Scqm"
61 	"HwS2R75SOqi2aOWDpKjznARg9JgzDWSQi4seBMV2oL0BTwJANSDf+p0sQLsaKGJhpVpBQ"
62 	"yS2wUeyuGyytupWzEluQrajMZq52iotcogv5BfeulfTTFbJP4kuHOsSP0lsQ2lpMDQANS"
63 	"HEvXxzHJLDLXM9gXJzwJ+ZiRt6R+bfmP1nfN3MiWtxcIbBanWwQK6xTCKBe4wPaKta5EU"
64 	"6wsLPeakOIVzoeaOu/HsbtPZlwX0Mu/oUFcfKyKAhlkU15MOAIEfUPo8Yh52bWMlIlpZa"
65 	"4xWbLMGw3GrsrPPdcsAauyqvY4/NjjWQbWhP1SuUfvv5709PIiOUjVKK2HUwmR1ouch6X"
66 	"MQGXfMR1h1Wjvc+bkNs17gCIrQnFilAZLC3Sm3Opiz/4LO99Hw448G0RM2vQn0mJE46w"
67 	"Eu/B10U6Jf4Efojhh1dk85BD1LTIb+N3Q== ssh-test-key@lws";
68 
69 static struct lws_context *context = NULL;
70 static volatile char force_exit = 0;
71 
72 /*
73  * These are our "ops" that form our customization of, and interface to, the
74  * generic sshd plugin.
75  *
76  * The priv struct contains our data we want to associate to each channel
77  * individually.
78  */
79 
80 struct sshd_instance_priv {
81 	struct lws_protocol_vhost_options *env;
82 	struct lws_ring	*ring_stdout;
83 	struct lws_ring	*ring_stderr;
84 
85 	struct lws 	*wsi_stdout;
86 	struct lws 	*wsi_stderr;
87 
88 	uint32_t	pty_in_bloat_nl_to_crnl:1;
89 	uint32_t	pty_in_echo:1;
90 	uint32_t	pty_in_cr_to_nl:1;
91 
92 	uint32_t	insert_lf:1;
93 };
94 
95 
96 /* ops: channel lifecycle */
97 
98 static int
ssh_ops_channel_create(struct lws * wsi,void ** _priv)99 ssh_ops_channel_create(struct lws *wsi, void **_priv)
100 {
101 	struct sshd_instance_priv *priv;
102 
103 	priv = malloc(sizeof(struct sshd_instance_priv));
104 	*_priv = priv;
105 	if (!priv)
106 		return 1;
107 
108 	memset(priv, 0, sizeof(*priv));
109 
110 	priv->ring_stdout = lws_ring_create(1, 1024, NULL);
111 	if (!priv->ring_stdout) {
112 		free(priv);
113 
114 		return 1;
115 	}
116 
117 	priv->ring_stderr = lws_ring_create(1, 1024, NULL);
118 	if (!priv->ring_stderr) {
119 		lws_ring_destroy(priv->ring_stdout);
120 		free(priv);
121 
122 		return 1;
123 	}
124 
125 	return 0;
126 }
127 
128 static int
ssh_ops_channel_destroy(void * _priv)129 ssh_ops_channel_destroy(void *_priv)
130 {
131 	struct sshd_instance_priv *priv = _priv;
132 	const struct lws_protocol_vhost_options *pvo = priv->env, *pvo1;
133 
134 	while (pvo) {
135 		pvo1 = pvo;
136 		free((char *)pvo->name);
137 		free((char *)pvo->value);
138 		pvo = pvo->next;
139 		free((void *)pvo1);
140 	}
141 	priv->env = NULL;
142 
143 	lws_ring_destroy(priv->ring_stdout);
144 	lws_ring_destroy(priv->ring_stderr);
145 	free(priv);
146 
147 	return 0;
148 }
149 
150 /* ops: IO */
151 
152 static int
ssh_ops_tx_waiting(void * _priv)153 ssh_ops_tx_waiting(void *_priv)
154 {
155 	struct sshd_instance_priv *priv = _priv;
156 	int s = 0;
157 
158 	if (lws_ring_get_count_waiting_elements(priv->ring_stdout, NULL))
159 		s |= LWS_STDOUT;
160 	if (lws_ring_get_count_waiting_elements(priv->ring_stderr, NULL))
161 		s |= LWS_STDERR;
162 
163 	return s;
164 }
165 
166 static size_t
ssh_ops_tx(void * _priv,int stdch,uint8_t * buf,size_t len)167 ssh_ops_tx(void *_priv, int stdch, uint8_t *buf, size_t len)
168 {
169 	struct sshd_instance_priv *priv = _priv;
170 	struct lws_ring *r;
171 	struct lws *wsi;
172 	size_t n;
173 
174 	if (stdch == LWS_STDOUT) {
175 		r = priv->ring_stdout;
176 		wsi = priv->wsi_stdout;
177 	} else {
178 		r = priv->ring_stderr;
179 		wsi = priv->wsi_stderr;
180 	}
181 
182 	n = lws_ring_consume(r, NULL, buf, len);
183 
184 	if (n)
185 		lws_rx_flow_control(wsi, 1);
186 
187 	return n;
188 }
189 
190 
191 static int
ssh_ops_rx(void * _priv,struct lws * wsi,const uint8_t * buf,uint32_t len)192 ssh_ops_rx(void *_priv, struct lws *wsi, const uint8_t *buf, uint32_t len)
193 {
194 	struct sshd_instance_priv *priv = _priv;
195 	struct lws *wsi_stdin = lws_cgi_get_stdwsi(wsi, LWS_STDIN);
196 	int fd;
197 	uint8_t bbuf[256];
198 
199 	if (!wsi_stdin)
200 		return -1;
201 
202 	fd = lws_get_socket_fd(wsi_stdin);
203 
204 	if (*buf != 0x0d) {
205 		if (write(fd, buf, len) != (int)len)
206 			return -1;
207 		if (priv->pty_in_echo) {
208 			if (!lws_ring_insert(priv->ring_stdout, buf, 1))
209 				lwsl_notice("dropping...\n");
210 			lws_callback_on_writable(wsi);
211 		}
212 	} else {
213 		bbuf[0] = 0x0a;
214 		bbuf[1] = 0x0a;
215 		if (write(fd, bbuf, 1) != 1)
216 			return -1;
217 
218 		if (priv->pty_in_echo) {
219 			bbuf[0] = 0x0d;
220 			bbuf[1] = 0x0a;
221 			if (!lws_ring_insert(priv->ring_stdout, bbuf, 2))
222 				lwsl_notice("dropping...\n");
223 			lws_callback_on_writable(wsi);
224 		}
225 	}
226 
227 	return 0;
228 }
229 
230 /* ops: storage for the (autogenerated) persistent server key */
231 
232 static size_t
ssh_ops_get_server_key(struct lws * wsi,uint8_t * buf,size_t len)233 ssh_ops_get_server_key(struct lws *wsi, uint8_t *buf, size_t len)
234 {
235 	int fd = lws_open(TEST_SERVER_KEY_PATH, O_RDONLY), n;
236 
237 	if (fd == -1) {
238 		lwsl_err("%s: unable to open %s for read: %s\n", __func__,
239 				TEST_SERVER_KEY_PATH, strerror(errno));
240 
241 		return 0;
242 	}
243 
244 	n = read(fd, buf, len);
245 	if (n < 0) {
246 		lwsl_err("%s: read failed: %d\n", __func__, n);
247 		n = 0;
248 	}
249 
250 	close(fd);
251 
252 	return n;
253 }
254 
255 static size_t
ssh_ops_set_server_key(struct lws * wsi,uint8_t * buf,size_t len)256 ssh_ops_set_server_key(struct lws *wsi, uint8_t *buf, size_t len)
257 {
258 	int fd = lws_open(TEST_SERVER_KEY_PATH, O_CREAT | O_TRUNC | O_RDWR, 0600);
259 	int n;
260 
261 	lwsl_notice("%s: %d\n", __func__, fd);
262 	if (fd == -1) {
263 		lwsl_err("%s: unable to open %s for write: %s\n", __func__,
264 				TEST_SERVER_KEY_PATH, strerror(errno));
265 
266 		return 0;
267 	}
268 
269 	n = write(fd, buf, len);
270 	if (n < 0) {
271 		lwsl_err("%s: read failed: %d\n", __func__, errno);
272 		n = 0;
273 	}
274 
275 	close(fd);
276 
277 	return n;
278 }
279 
280 /* ops: auth */
281 
282 static int
ssh_ops_is_pubkey_authorized(const char * username,const char * type,const uint8_t * peer,int peer_len)283 ssh_ops_is_pubkey_authorized(const char *username, const char *type,
284 				 const uint8_t *peer, int peer_len)
285 {
286 	char *aps, *p, *ps;
287 	int n = strlen(type), alen = 2048, ret = 2, len;
288 	size_t s = 0;
289 
290 	lwsl_info("%s: checking pubkey for %s\n", __func__, username);
291 
292 	s = strlen(authorized_key) + 1;
293 
294 	aps = malloc(s);
295 	if (!aps) {
296 		lwsl_notice("OOM 1\n");
297 		goto bail_p1;
298 	}
299 	memcpy(aps, authorized_key, s);
300 
301 	/* this is all we understand at the moment */
302 	if (strcmp(type, "ssh-rsa")) {
303 		lwsl_notice("type is not ssh-rsa\n");
304 		goto bail_p1;
305 	}
306 	p = aps;
307 
308 	if (strncmp(p, type, n)) {
309 		lwsl_notice("lead-in string  does not match %s\n", type);
310 		goto bail_p1;
311 	}
312 
313 	p += n;
314 	if (*p != ' ') {
315 		lwsl_notice("missing space at end of lead-in\n");
316 		goto bail_p1;
317 	}
318 
319 
320 	p++;
321 	ps = malloc(alen);
322 	if (!ps) {
323 		lwsl_notice("OOM 2\n");
324 		free(aps);
325 		goto bail;
326 	}
327 	len = lws_b64_decode_string(p, ps, alen);
328 	free(aps);
329 	if (len < 0) {
330 		lwsl_notice("key too big\n");
331 		goto bail;
332 	}
333 
334 	if (peer_len > len) {
335 		lwsl_notice("peer_len %d bigger than decoded len %d\n",
336 				peer_len, len);
337 		goto bail;
338 	}
339 
340 	/*
341 	 * once we are past that, it's the same <len32>name
342 	 * <len32>E<len32>N that the peer sends us
343 	 */
344 
345 	if (lws_timingsafe_bcmp(peer, ps, peer_len)) {
346 		lwsl_info("factors mismatch\n");
347 		goto bail;
348 	}
349 
350 	lwsl_info("pubkey authorized\n");
351 
352 	ret = 0;
353 bail:
354 	free(ps);
355 
356 	return ret;
357 
358 bail_p1:
359 	if (aps)
360 		free(aps);
361 
362 	return 1;
363 }
364 
365 /* ops: spawn subprocess */
366 
367 static int
ssh_cgi_env_add(struct sshd_instance_priv * priv,const char * name,const char * value)368 ssh_cgi_env_add(struct sshd_instance_priv *priv, const char *name,
369 		const char *value)
370 {
371 	struct lws_protocol_vhost_options *pvo = malloc(sizeof(*pvo));
372 
373 	if (!pvo)
374 		return 1;
375 
376 	pvo->name = malloc(strlen(name) + 1);
377 	if (!pvo->name) {
378 		free(pvo);
379 		return 1;
380 	}
381 
382 	pvo->value = malloc(strlen(value) + 1);
383 	if (!pvo->value) {
384 		free((char *)pvo->name);
385 		free(pvo);
386 		return 1;
387 	}
388 
389 	strcpy((char *)pvo->name, name);
390 	strcpy((char *)pvo->value, value);
391 
392 	pvo->next = priv->env;
393 	priv->env = pvo;
394 
395 	lwsl_notice("%s: ENV %s <- %s\n", __func__, name, value);
396 
397 	return 0;
398 }
399 
400 static int
ssh_ops_set_env(void * _priv,const char * name,const char * value)401 ssh_ops_set_env(void *_priv, const char *name, const char *value)
402 {
403 	struct sshd_instance_priv *priv = _priv;
404 
405 	return ssh_cgi_env_add(priv, name, value);
406 }
407 
408 
409 static int
ssh_ops_pty_req(void * _priv,struct lws_ssh_pty * pty)410 ssh_ops_pty_req(void *_priv, struct lws_ssh_pty *pty)
411 {
412 	struct sshd_instance_priv *priv = _priv;
413 	uint8_t *p = (uint8_t *)pty->modes, opc;
414 	uint32_t arg;
415 
416 	lwsl_notice("%s: pty term %s, modes_len %d\n", __func__, pty->term,
417 		    pty->modes_len);
418 
419 	ssh_cgi_env_add(priv, "TERM", pty->term);
420 
421 	while (p < (uint8_t *)pty->modes + pty->modes_len) {
422 		if (*p >= 160)
423 			break;
424 		if (!*p)
425 			break;
426 		opc = *p++;
427 
428 		arg = *p++ << 24;
429 		arg |= *p++ << 16;
430 		arg |= *p++ << 8;
431 		arg |= *p++;
432 
433 		lwsl_debug("pty opc %d: 0x%x\n", opc, arg);
434 
435 		switch (opc) {
436 		case SSHMO_ICRNL:
437 			priv->pty_in_cr_to_nl = !!arg;
438 			lwsl_notice(" SSHMO_ICRNL: %d\n", !!arg);
439 			break;
440 		case SSHMO_ONLCR:
441 			priv->pty_in_bloat_nl_to_crnl = !!arg;
442 			lwsl_notice(" SSHMO_ONLCR: %d\n", !!arg);
443 			break;
444 		case SSHMO_ECHO:
445 //			priv->pty_in_echo = !!arg;
446 			lwsl_notice(" SSHMO_ECHO: %d\n", !!arg);
447 			break;
448 		}
449 	}
450 
451 	return 0;
452 }
453 
454 static int
ssh_ops_child_process_io(void * _priv,struct lws * wsi,struct lws_cgi_args * args)455 ssh_ops_child_process_io(void *_priv, struct lws *wsi,
456 			 struct lws_cgi_args *args)
457 {
458 	struct sshd_instance_priv *priv = _priv;
459 	struct lws_ring *r = priv->ring_stdout;
460 	void *rp;
461 	size_t bytes;
462 	int n, m;
463 
464 	priv->wsi_stdout = args->stdwsi[LWS_STDOUT];
465 	priv->wsi_stderr = args->stdwsi[LWS_STDERR];
466 
467 	switch (args->ch) {
468 	case LWS_STDIN:
469 		lwsl_notice("STDIN\n");
470 		break;
471 
472 	case LWS_STDERR:
473 		r = priv->ring_stderr;
474 		/* fallthru */
475 	case LWS_STDOUT:
476 		if (lws_ring_next_linear_insert_range(r, &rp, &bytes) ||
477 		    bytes < 1) {
478 			lwsl_notice("bytes %d\n", (int)bytes);
479 			/* no room in the fifo */
480 			break;
481 		}
482 		if (priv->pty_in_bloat_nl_to_crnl) {
483 			uint8_t buf[256], *p, *d;
484 
485 			if (bytes != 1)
486 				n = bytes / 2;
487 			else
488 				n = 1;
489 			if (n > (int)sizeof(buf))
490 				n = sizeof(buf);
491 
492 			if (!n)
493 				break;
494 
495 			m = lws_get_socket_fd(args->stdwsi[args->ch]);
496 			if (m < 0)
497 				return -1;
498 			n = read(m, buf, n);
499 			if (n < 0)
500 				return -1;
501 			if (n == 0) {
502 				lwsl_notice("zero length stdin %d\n", n);
503 				break;
504 			}
505 			m = 0;
506 			p = rp;
507 			d = buf;
508 			while (m++ < n) {
509 				if (priv->insert_lf) {
510 					priv->insert_lf = 0;
511 					*p++ = 0x0d;
512 				}
513 				if (*d == 0x0a)
514 					priv->insert_lf = 1;
515 
516 				*p++ = *d++;
517 			}
518 			n = (void *)p - rp;
519 			if (n < (int)bytes && priv->insert_lf) {
520 				priv->insert_lf = 0;
521 				*p++ = 0x0d;
522 				n++;
523 			}
524 		} else {
525 			n = lws_get_socket_fd(args->stdwsi[args->ch]);
526 			if (n < 0)
527 				return -1;
528 			n = read(n, rp, bytes);
529 			if (n < 0)
530 				return -1;
531 		}
532 
533 		lws_rx_flow_control(args->stdwsi[args->ch], 0);
534 
535 		lws_ring_bump_head(r, n);
536 		lws_callback_on_writable(wsi);
537 		break;
538 	}
539 
540 	return 0;
541 }
542 
543 static int
ssh_ops_child_process_terminated(void * priv,struct lws * wsi)544 ssh_ops_child_process_terminated(void *priv, struct lws *wsi)
545 {
546 	lwsl_notice("%s\n", __func__);
547 	return -1;
548 }
549 
550 static int
ssh_ops_exec(void * _priv,struct lws * wsi,const char * command,lws_ssh_finish_exec finish,void * finish_handle)551 ssh_ops_exec(void *_priv, struct lws *wsi, const char *command, lws_ssh_finish_exec finish, void *finish_handle)
552 {
553 	lwsl_notice("%s: EXEC %s\n", __func__, command);
554 
555 	/* we don't want to exec anything */
556 	return 1;
557 }
558 
559 static int
ssh_ops_shell(void * _priv,struct lws * wsi,lws_ssh_finish_exec finish,void * finish_handle)560 ssh_ops_shell(void *_priv, struct lws *wsi, lws_ssh_finish_exec finish, void *finish_handle)
561 {
562 	struct sshd_instance_priv *priv = _priv;
563 	const char *cmd[] = {
564 		"/bin/bash",
565 		"-i",
566 		"-l",
567 		NULL
568 	};
569 	lwsl_notice("%s: SHELL\n", __func__);
570 
571 	if (lws_cgi(wsi, cmd, -1, 0, priv->env)) {
572 		lwsl_notice("shell spawn failed\n");
573 		return -1;
574 	}
575 
576 	return 0;
577 }
578 
579 /* ops: banner */
580 
581 static size_t
ssh_ops_banner(char * buf,size_t max_len,char * lang,size_t max_lang_len)582 ssh_ops_banner(char *buf, size_t max_len, char *lang, size_t max_lang_len)
583 {
584 	int n = lws_snprintf(buf, max_len, "\n"
585 		      " |\\---/|  lws-ssh Test Server\n"
586 		      " | o_o |  SSH Terminal Server\n"
587 		      "  \\_^_/   Copyright (C) 2017 Crash Barrier Ltd\n\n");
588 
589 	lws_snprintf(lang, max_lang_len, "en/US");
590 
591 	return n;
592 }
593 
594 static void
ssh_ops_disconnect_reason(uint32_t reason,const char * desc,const char * desc_lang)595 ssh_ops_disconnect_reason(uint32_t reason, const char *desc,
596 			  const char *desc_lang)
597 {
598 	lwsl_notice("DISCONNECT reason 0x%X, %s (lang %s)\n", reason, desc,
599 			desc_lang);
600 }
601 
602 static const struct lws_ssh_ops ssh_ops = {
603 	.channel_create			= ssh_ops_channel_create,
604 	.channel_destroy		= ssh_ops_channel_destroy,
605 	.tx_waiting			= ssh_ops_tx_waiting,
606 	.tx				= ssh_ops_tx,
607 	.rx				= ssh_ops_rx,
608 	.get_server_key			= ssh_ops_get_server_key,
609 	.set_server_key			= ssh_ops_set_server_key,
610 	.set_env			= ssh_ops_set_env,
611 	.pty_req			= ssh_ops_pty_req,
612 	.child_process_io		= ssh_ops_child_process_io,
613 	.child_process_terminated	= ssh_ops_child_process_terminated,
614 	.exec				= ssh_ops_exec,
615 	.shell				= ssh_ops_shell,
616 	.is_pubkey_authorized		= ssh_ops_is_pubkey_authorized,
617 	.banner				= ssh_ops_banner,
618 	.disconnect_reason		= ssh_ops_disconnect_reason,
619 	.server_string			= "SSH-2.0-Libwebsockets",
620 	.api_version			= 2,
621 };
622 
623 /*
624  * use per-vhost options to bind the ops struct to the instance of the
625  * "lws_raw_sshd" protocol instantiated on our vhost
626  */
627 
628 static const struct lws_protocol_vhost_options pvo_ssh_ops = {
629 	NULL,
630 	NULL,
631 	"ops",
632 	(void *)&ssh_ops
633 };
634 
635 static const struct lws_protocol_vhost_options pvo_ssh = {
636 	NULL,
637 	&pvo_ssh_ops,
638 	"lws-ssh-base",
639 	"" /* ignored, just matches the protocol name above */
640 };
641 
sighandler(int sig)642 void sighandler(int sig)
643 {
644 	force_exit = 1;
645 	lws_cancel_service(context);
646 }
647 
main()648 int main()
649 {
650 	static struct lws_context_creation_info info;
651 	struct lws_vhost *vh_sshd;
652 	int ret = 1, n;
653 
654 	/* info is on the stack, it must be cleared down before use */
655 	memset(&info, 0, sizeof(info));
656 
657 	signal(SIGINT, sighandler);
658 	lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE
659 			/*| LLL_INFO */
660 			/* | LLL_DEBUG */, NULL);
661 
662 	lwsl_notice("lws test-sshd -- Copyright (C) 2017 <andy@warmcat.com>\n");
663 
664 	/* create the lws context */
665 
666 	info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
667 		       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
668 
669 	context = lws_create_context(&info);
670 	if (!context) {
671 		lwsl_err("Failed to create context\n");
672 		return 1;
673 	}
674 
675 	/* create our listening vhost */
676 
677 	info.port = 2200;
678 	info.options = LWS_SERVER_OPTION_ONLY_RAW;
679 	info.vhost_name = "sshd";
680 	info.protocols = protocols_sshd;
681 	info.pvo = &pvo_ssh;
682 
683 	vh_sshd = lws_create_vhost(context, &info);
684 	if (!vh_sshd) {
685 		lwsl_err("Failed to create sshd vhost\n");
686 		goto bail;
687 	}
688 
689 	/* spin doing service */
690 
691 	n = 0;
692 	while (!n  && !force_exit)
693 		n = lws_service(context, 0);
694 
695 	ret = 0;
696 
697 	/* cleanup */
698 
699 bail:
700 	lws_context_destroy(context);
701 	lwsl_notice("exiting...\n");
702 
703 	return ret;
704 }
705