• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * ws protocol handler plugin for sshd demo
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  * These test plugins 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 #if !defined (LWS_PLUGIN_STATIC)
22 #define LWS_DLL
23 #define LWS_INTERNAL
24 #include <libwebsockets.h>
25 #endif
26 
27 #include <lws-ssh.h>
28 
29 #include <string.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 
33 #define TEST_SERVER_KEY_PATH "/etc/lws-test-sshd-server-key"
34 
35 struct per_vhost_data__lws_sshd_demo {
36 	const struct lws_protocols *ssh_base_protocol;
37 	int privileged_fd;
38 };
39 
40 /*
41  *  This is a copy of the lws ssh test public key, you can find it in
42  *  /usr[/local]/share/libwebsockets-test-server/lws-ssh-test-keys.pub
43  *  and the matching private key there too in .../lws-ssh-test-keys
44  *
45  *  If the vhost with this protocol is using localhost:2222, you can test with
46  *  the matching private key like this:
47  *
48  *  ssh -p 2222 -i /usr/local/share/libwebsockets-test-server/lws-ssh-test-keys anyuser@127.0.0.1
49  *
50  *  These keys are distributed for testing!  Don't use them on a real system
51  *  unless you want anyone with a copy of lws to access it.
52  */
53 static const char *authorized_key =
54 	"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnWiP+c+kSD6Lk+C6NA9KruApa45sbt"
55 	"94/dxT0bCITlAA/+PBk6mR1lwWgXYozOMdrHrqx34piqDyXnc4HabqCaOm/FrYhkCPL8z"
56 	"a26PMYqteSosuwKv//5iT6ZWhNnsMwExBwtV6MIq0MxAeWqxRnYNWpNM8iN6sFzkdG/YF"
57 	"dyHrIBTgwzM77NLCMl6GEkJErRCFppC2SwYxGa3BRrgUwX3LkV8HpMIaYHFo1Qgj7Scqm"
58 	"HwS2R75SOqi2aOWDpKjznARg9JgzDWSQi4seBMV2oL0BTwJANSDf+p0sQLsaKGJhpVpBQ"
59 	"yS2wUeyuGyytupWzEluQrajMZq52iotcogv5BfeulfTTFbJP4kuHOsSP0lsQ2lpMDQANS"
60 	"HEvXxzHJLDLXM9gXJzwJ+ZiRt6R+bfmP1nfN3MiWtxcIbBanWwQK6xTCKBe4wPaKta5EU"
61 	"6wsLPeakOIVzoeaOu/HsbtPZlwX0Mu/oUFcfKyKAhlkU15MOAIEfUPo8Yh52bWMlIlpZa"
62 	"4xWbLMGw3GrsrPPdcsAauyqvY4/NjjWQbWhP1SuUfvv5709PIiOUjVKK2HUwmR1ouch6X"
63 	"MQGXfMR1h1Wjvc+bkNs17gCIrQnFilAZLC3Sm3Opiz/4LO99Hw448G0RM2vQn0mJE46w"
64 	"Eu/B10U6Jf4Efojhh1dk85BD1LTIb+N3Q== ssh-test-key@lws";
65 
66 enum states {
67 	SSH_TEST_GREET,
68 	SSH_TEST_PRESSED,
69 	SSH_TEST_DONE,
70 };
71 
72 static const char * const strings[] =
73 	{
74 		/* SSH_TEST_GREET */
75 		"Thanks for logging to lws sshd server demo.\n\r"
76 		"\n\r"
77 		"This demo is very simple, it waits for you to press\n\r"
78 		"a key, and acknowledges it.  Then press another key\n\r"
79 		"and it will exit.  But actually that demos the basic\n\r"
80 		"sshd functions underneath.  You can use the ops struct\n\r"
81 		"members to add a pty / shell or whatever you want.\n\r"
82 		"\n\r"
83 		"Press a key...\n\r",
84 
85 		/* SSH_TEST_PRESSED */
86 		"Thanks for pressing a key.  Press another to exit.\n\r",
87 
88 		/* SSH_TEST_DONE */
89 		"Bye!\n\r"
90 	};
91 
92 struct sshd_instance_priv {
93 	struct lws *wsi;
94 	enum states state;
95 	const char *ptr;
96 	int pos;
97 	int len;
98 };
99 
100 static void
enter_state(struct sshd_instance_priv * priv,enum states state)101 enter_state(struct sshd_instance_priv *priv, enum states state)
102 {
103 	priv->state = state;
104 	priv->ptr = strings[state];
105 	priv->pos = 0;
106 	priv->len = (int)strlen(priv->ptr);
107 
108 	lws_callback_on_writable(priv->wsi);
109 }
110 
111 /* ops: channel lifecycle */
112 
113 static int
ssh_ops_channel_create(struct lws * wsi,void ** _priv)114 ssh_ops_channel_create(struct lws *wsi, void **_priv)
115 {
116 	struct sshd_instance_priv *priv;
117 
118 	priv = malloc(sizeof(struct sshd_instance_priv));
119 	*_priv = priv;
120 	if (!priv)
121 		return 1;
122 
123 	memset(priv, 0, sizeof(*priv));
124 	priv->wsi = wsi;
125 
126 	return 0;
127 }
128 
129 static int
ssh_ops_channel_destroy(void * _priv)130 ssh_ops_channel_destroy(void *_priv)
131 {
132 	struct sshd_instance_priv *priv = _priv;
133 
134 	free(priv);
135 
136 	return 0;
137 }
138 
139 /* ops: IO */
140 
141 static int
ssh_ops_tx_waiting(void * _priv)142 ssh_ops_tx_waiting(void *_priv)
143 {
144 	struct sshd_instance_priv *priv = _priv;
145 
146 	if (priv->state == SSH_TEST_DONE &&
147 	    priv->pos == priv->len)
148 		return -1; /* exit */
149 
150 	if (priv->pos != priv->len)
151 		return LWS_STDOUT;
152 
153 	return 0;
154 }
155 
156 static size_t
ssh_ops_tx(void * _priv,int stdch,uint8_t * buf,size_t len)157 ssh_ops_tx(void *_priv, int stdch, uint8_t *buf, size_t len)
158 {
159 	struct sshd_instance_priv *priv = _priv;
160 	size_t chunk = len;
161 
162 	if (stdch != LWS_STDOUT)
163 		return 0;
164 
165 	if ((size_t)(priv->len - priv->pos) < chunk)
166 		chunk = priv->len - priv->pos;
167 
168 	if (!chunk)
169 		return 0;
170 
171 	memcpy(buf, priv->ptr + priv->pos, chunk);
172 	priv->pos += (int)chunk;
173 
174 	if (priv->state == SSH_TEST_DONE && priv->pos == priv->len) {
175 		/*
176 		 * we are sending the last thing we want to send
177 		 * before exiting.  Make it ask again at ssh_ops_tx_waiting()
178 		 * and we will exit then, after this has been sent
179 		 */
180 		lws_callback_on_writable(priv->wsi);
181 	}
182 
183 	return chunk;
184 }
185 
186 
187 static int
ssh_ops_rx(void * _priv,struct lws * wsi,const uint8_t * buf,uint32_t len)188 ssh_ops_rx(void *_priv, struct lws *wsi, const uint8_t *buf, uint32_t len)
189 {
190 	struct sshd_instance_priv *priv = _priv;
191 
192 	if (priv->state < SSH_TEST_DONE)
193 		enter_state(priv, priv->state + 1);
194 	else
195 		return -1;
196 
197 	return 0;
198 }
199 
200 /* ops: storage for the (autogenerated) persistent server key */
201 
202 static size_t
ssh_ops_get_server_key(struct lws * wsi,uint8_t * buf,size_t len)203 ssh_ops_get_server_key(struct lws *wsi, uint8_t *buf, size_t len)
204 {
205 	struct per_vhost_data__lws_sshd_demo *vhd =
206 			(struct per_vhost_data__lws_sshd_demo *)
207 			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
208 						 lws_get_protocol(wsi));
209 	int n;
210 
211 	if (lseek(vhd->privileged_fd, 0, SEEK_SET) < 0)
212 		return 0;
213 	n = read(vhd->privileged_fd, buf, (int)len);
214 	if (n < 0) {
215 		lwsl_err("%s: read failed: %d\n", __func__, n);
216 		n = 0;
217 	}
218 
219 	return n;
220 }
221 
222 static size_t
ssh_ops_set_server_key(struct lws * wsi,uint8_t * buf,size_t len)223 ssh_ops_set_server_key(struct lws *wsi, uint8_t *buf, size_t len)
224 {
225 	struct per_vhost_data__lws_sshd_demo *vhd =
226 			(struct per_vhost_data__lws_sshd_demo *)
227 			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
228 						 lws_get_protocol(wsi));
229 	int n;
230 
231 	n = write(vhd->privileged_fd, buf, (int)len);
232 	if (n < 0) {
233 		lwsl_err("%s: read failed: %d\n", __func__, errno);
234 		n = 0;
235 	}
236 
237 	return n;
238 }
239 
240 /* ops: auth */
241 
242 static int
ssh_ops_is_pubkey_authorized(const char * username,const char * type,const uint8_t * peer,int peer_len)243 ssh_ops_is_pubkey_authorized(const char *username, const char *type,
244 				 const uint8_t *peer, int peer_len)
245 {
246 	char *aps, *p, *ps;
247 	int n = (int)strlen(type), alen = 2048, ret = 2, len;
248 	size_t s = 0;
249 
250 	lwsl_info("%s: checking pubkey for %s\n", __func__, username);
251 
252 	s = strlen(authorized_key) + 1;
253 
254 	aps = malloc(s);
255 	if (!aps) {
256 		lwsl_notice("OOM 1\n");
257 		goto bail_p1;
258 	}
259 	memcpy(aps, authorized_key, s);
260 
261 	/* we only understand RSA */
262 	if (strcmp(type, "ssh-rsa")) {
263 		lwsl_notice("type is not ssh-rsa\n");
264 		goto bail_p1;
265 	}
266 	p = aps;
267 
268 	if (strncmp(p, type, n)) {
269 		lwsl_notice("lead-in string  does not match %s\n", type);
270 		goto bail_p1;
271 	}
272 
273 	p += n;
274 	if (*p != ' ') {
275 		lwsl_notice("missing space at end of lead-in\n");
276 		goto bail_p1;
277 	}
278 
279 	p++;
280 	ps = malloc(alen);
281 	if (!ps) {
282 		lwsl_notice("OOM 2\n");
283 		free(aps);
284 		goto bail;
285 	}
286 	len = lws_b64_decode_string(p, ps, alen);
287 	free(aps);
288 	if (len < 0) {
289 		lwsl_notice("key too big\n");
290 		goto bail;
291 	}
292 
293 	if (peer_len > len) {
294 		lwsl_notice("peer_len %d bigger than decoded len %d\n",
295 				peer_len, len);
296 		goto bail;
297 	}
298 
299 	/*
300 	 * once we are past that, it's the same <len32>name
301 	 * <len32>E<len32>N that the peer sends us
302 	 */
303 	if (memcmp(peer, ps, peer_len)) {
304 		lwsl_info("%s: factors mismatch, rejecting key\n", __func__);
305 		goto bail;
306 	}
307 
308 	lwsl_info("pubkey authorized\n");
309 
310 	ret = 0;
311 bail:
312 	free(ps);
313 
314 	return ret;
315 
316 bail_p1:
317 	if (aps)
318 		free(aps);
319 
320 	return 1;
321 }
322 
323 static int
ssh_ops_shell(void * _priv,struct lws * wsi,lws_ssh_finish_exec finish,void * finish_handle)324 ssh_ops_shell(void *_priv, struct lws *wsi, lws_ssh_finish_exec finish, void *finish_handle)
325 {
326 	struct sshd_instance_priv *priv = _priv;
327 
328 	/* for this demo, we don't open a real shell */
329 
330 	enter_state(priv, SSH_TEST_GREET);
331 
332 	return 0;
333 }
334 
335 /* ops: banner */
336 
337 static size_t
ssh_ops_banner(char * buf,size_t max_len,char * lang,size_t max_lang_len)338 ssh_ops_banner(char *buf, size_t max_len, char *lang, size_t max_lang_len)
339 {
340 	int n = lws_snprintf(buf, max_len, "\n"
341 		      " |\\---/|  lws-ssh Test Server\n"
342 		      " | o_o |  SSH Terminal Server\n"
343 		      "  \\_^_/   Copyright (C) 2017 Crash Barrier Ltd\n\n");
344 
345 	lws_snprintf(lang, max_lang_len, "en/US");
346 
347 	return n;
348 }
349 
350 static void
ssh_ops_disconnect_reason(uint32_t reason,const char * desc,const char * desc_lang)351 ssh_ops_disconnect_reason(uint32_t reason, const char *desc,
352 			  const char *desc_lang)
353 {
354 	lwsl_notice("DISCONNECT reason 0x%X, %s (lang %s)\n", reason, desc,
355 		    desc_lang);
356 }
357 
358 
359 static const struct lws_ssh_ops ssh_ops = {
360 	.channel_create			= ssh_ops_channel_create,
361 	.channel_destroy		= ssh_ops_channel_destroy,
362 	.tx_waiting			= ssh_ops_tx_waiting,
363 	.tx				= ssh_ops_tx,
364 	.rx				= ssh_ops_rx,
365 	.get_server_key			= ssh_ops_get_server_key,
366 	.set_server_key			= ssh_ops_set_server_key,
367 	.set_env			= NULL,
368 	.pty_req			= NULL,
369 	.child_process_io		= NULL,
370 	.child_process_terminated	= NULL,
371 	.exec				= NULL,
372 	.shell				= ssh_ops_shell,
373 	.is_pubkey_authorized		= ssh_ops_is_pubkey_authorized,
374 	.banner				= ssh_ops_banner,
375 	.disconnect_reason		= ssh_ops_disconnect_reason,
376 	.server_string			= "SSH-2.0-Libwebsockets",
377 	.api_version			= 2,
378 };
379 
380 static int
callback_lws_sshd_demo(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)381 callback_lws_sshd_demo(struct lws *wsi, enum lws_callback_reasons reason,
382 		       void *user, void *in, size_t len)
383 {
384 	struct per_vhost_data__lws_sshd_demo *vhd =
385 			(struct per_vhost_data__lws_sshd_demo *)
386 			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
387 						 lws_get_protocol(wsi));
388 
389 	switch (reason) {
390 	case LWS_CALLBACK_PROTOCOL_INIT:
391 		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
392 						  lws_get_protocol(wsi),
393 				sizeof(struct per_vhost_data__lws_sshd_demo));
394 		/*
395 		 * During this we still have the privs / caps we were started
396 		 * with.  So open an fd on the server key, either just for read
397 		 * or for creat / trunc if doesn't exist.  This allows us to
398 		 * deal with it down /etc/.. when just after this we will lose
399 		 * the privileges needed to read / write /etc/...
400 		 */
401 		vhd->privileged_fd = lws_open(TEST_SERVER_KEY_PATH, O_RDONLY);
402 		if (vhd->privileged_fd == -1)
403 			vhd->privileged_fd = lws_open(TEST_SERVER_KEY_PATH,
404 					O_CREAT | O_TRUNC | O_RDWR, 0600);
405 		if (vhd->privileged_fd == -1) {
406 			lwsl_err("%s: Can't open %s\n", __func__,
407 				 TEST_SERVER_KEY_PATH);
408 			return -1;
409 		}
410 		break;
411 
412 	case LWS_CALLBACK_PROTOCOL_DESTROY:
413 		close(vhd->privileged_fd);
414 		break;
415 
416 	case LWS_CALLBACK_VHOST_CERT_AGING:
417 		break;
418 
419 	case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
420 		break;
421 
422 	default:
423 		if (!vhd->ssh_base_protocol) {
424 			vhd->ssh_base_protocol = lws_vhost_name_to_protocol(
425 							lws_get_vhost(wsi),
426 							"lws-ssh-base");
427 			if (vhd->ssh_base_protocol)
428 				user = lws_adjust_protocol_psds(wsi,
429 				vhd->ssh_base_protocol->per_session_data_size);
430 		}
431 
432 		if (vhd->ssh_base_protocol)
433 			return vhd->ssh_base_protocol->callback(wsi, reason,
434 								user, in, len);
435 		else
436 			lwsl_notice("can't find lws-ssh-base\n");
437 		break;
438 	}
439 
440 	return 0;
441 }
442 
443 #define LWS_PLUGIN_PROTOCOL_LWS_SSHD_DEMO \
444 	{ \
445 		"lws-sshd-demo", \
446 		callback_lws_sshd_demo, \
447 		0, \
448 		1024, /* rx buf size must be >= permessage-deflate rx size */ \
449 		0, (void *)&ssh_ops, 0 \
450 	}
451 
452 #if !defined (LWS_PLUGIN_STATIC)
453 
454 static const struct lws_protocols protocols[] = {
455 		LWS_PLUGIN_PROTOCOL_LWS_SSHD_DEMO
456 };
457 
458 LWS_VISIBLE int
init_protocol_lws_sshd_demo(struct lws_context * context,struct lws_plugin_capability * c)459 init_protocol_lws_sshd_demo(struct lws_context *context,
460 			     struct lws_plugin_capability *c)
461 {
462 	if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
463 		lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
464 			 c->api_magic);
465 		return 1;
466 	}
467 
468 	c->protocols = protocols;
469 	c->count_protocols = LWS_ARRAY_SIZE(protocols);
470 	c->extensions = NULL;
471 	c->count_extensions = 0;
472 
473 	return 0;
474 }
475 
476 LWS_VISIBLE int
destroy_protocol_lws_sshd_demo(struct lws_context * context)477 destroy_protocol_lws_sshd_demo(struct lws_context *context)
478 {
479 	return 0;
480 }
481 
482 #endif
483