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