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