2 * Example embedded sshd server using libwebsockets sshd plugin
4 * Written in 2010-2019 by Andy Green <andy@warmcat.com>
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
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.
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
21 * This test app listens on port 2200 for authorized ssh connections. Run it
24 * $ sudo libwebsockets-test-sshd
26 * Connect to it using the test private key with:
28 * $ ssh -p 2200 -i /usr/local/share/libwebsockets-test-server/lws-ssh-test-keys anyuser@127.0.0.1
31 #include <sys/types.h>
37 /* import the whole of lws-plugin-sshd-base statically */
38 #include <lws-plugin-sshd-static-build-includes.h>
41 * We store the test server's own key here (will be created with a new
42 * random key if it doesn't exist
44 * The /etc path is the only reason we have to run as root.
46 #define TEST_SERVER_KEY_PATH "/etc/lws-test-sshd-server-key"
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
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.
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";
69 static struct lws_context *context = NULL;
70 static volatile char force_exit = 0;
73 * These are our "ops" that form our customization of, and interface to, the
74 * generic sshd plugin.
76 * The priv struct contains our data we want to associate to each channel
80 struct sshd_instance_priv {
81 struct lws_protocol_vhost_options *env;
82 struct lws_ring *ring_stdout;
83 struct lws_ring *ring_stderr;
85 struct lws *wsi_stdout;
86 struct lws *wsi_stderr;
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;
96 /* ops: channel lifecycle */
99 ssh_ops_channel_create(struct lws *wsi, void **_priv)
101 struct sshd_instance_priv *priv;
103 priv = malloc(sizeof(struct sshd_instance_priv));
108 memset(priv, 0, sizeof(*priv));
110 priv->ring_stdout = lws_ring_create(1, 1024, NULL);
111 if (!priv->ring_stdout) {
117 priv->ring_stderr = lws_ring_create(1, 1024, NULL);
118 if (!priv->ring_stderr) {
119 lws_ring_destroy(priv->ring_stdout);
129 ssh_ops_channel_destroy(void *_priv)
131 struct sshd_instance_priv *priv = _priv;
132 const struct lws_protocol_vhost_options *pvo = priv->env, *pvo1;
136 free((char *)pvo->name);
137 free((char *)pvo->value);
143 lws_ring_destroy(priv->ring_stdout);
144 lws_ring_destroy(priv->ring_stderr);
153 ssh_ops_tx_waiting(void *_priv)
155 struct sshd_instance_priv *priv = _priv;
158 if (lws_ring_get_count_waiting_elements(priv->ring_stdout, NULL))
160 if (lws_ring_get_count_waiting_elements(priv->ring_stderr, NULL))
167 ssh_ops_tx(void *_priv, int stdch, uint8_t *buf, size_t len)
169 struct sshd_instance_priv *priv = _priv;
174 if (stdch == LWS_STDOUT) {
175 r = priv->ring_stdout;
176 wsi = priv->wsi_stdout;
178 r = priv->ring_stderr;
179 wsi = priv->wsi_stderr;
182 n = lws_ring_consume(r, NULL, buf, len);
185 lws_rx_flow_control(wsi, 1);
192 ssh_ops_rx(void *_priv, struct lws *wsi, const uint8_t *buf, uint32_t len)
194 struct sshd_instance_priv *priv = _priv;
195 struct lws *wsi_stdin = lws_cgi_get_stdwsi(wsi, LWS_STDIN);
202 fd = lws_get_socket_fd(wsi_stdin);
205 if (write(fd, buf, len) != len)
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);
215 if (write(fd, bbuf, 1) != 1)
218 if (priv->pty_in_echo) {
221 if (!lws_ring_insert(priv->ring_stdout, bbuf, 2))
222 lwsl_notice("dropping...\n");
223 lws_callback_on_writable(wsi);
230 /* ops: storage for the (autogenerated) persistent server key */
233 ssh_ops_get_server_key(struct lws *wsi, uint8_t *buf, size_t len)
235 int fd = lws_open(TEST_SERVER_KEY_PATH, O_RDONLY), n;
238 lwsl_err("%s: unable to open %s for read: %s\n", __func__,
239 TEST_SERVER_KEY_PATH, strerror(errno));
244 n = read(fd, buf, len);
246 lwsl_err("%s: read failed: %d\n", __func__, n);
256 ssh_ops_set_server_key(struct lws *wsi, uint8_t *buf, size_t len)
258 int fd = lws_open(TEST_SERVER_KEY_PATH, O_CREAT | O_TRUNC | O_RDWR, 0600);
261 lwsl_notice("%s: %d\n", __func__, fd);
263 lwsl_err("%s: unable to open %s for write: %s\n", __func__,
264 TEST_SERVER_KEY_PATH, strerror(errno));
269 n = write(fd, buf, len);
271 lwsl_err("%s: read failed: %d\n", __func__, errno);
283 ssh_ops_is_pubkey_authorized(const char *username, const char *type,
284 const uint8_t *peer, int peer_len)
287 int n = strlen(type), alen = 2048, ret = 2, len;
290 lwsl_info("%s: checking pubkey for %s\n", __func__, username);
292 s = strlen(authorized_key) + 1;
296 lwsl_notice("OOM 1\n");
299 memcpy(aps, authorized_key, s);
301 /* this is all we understand at the moment */
302 if (strcmp(type, "ssh-rsa")) {
303 lwsl_notice("type is not ssh-rsa\n");
308 if (strncmp(p, type, n)) {
309 lwsl_notice("lead-in string does not match %s\n", type);
315 lwsl_notice("missing space at end of lead-in\n");
323 lwsl_notice("OOM 2\n");
327 len = lws_b64_decode_string(p, ps, alen);
330 lwsl_notice("key too big\n");
334 if (peer_len > len) {
335 lwsl_notice("peer_len %d bigger than decoded len %d\n",
341 * once we are past that, it's the same <len32>name
342 * <len32>E<len32>N that the peer sends us
345 if (lws_timingsafe_bcmp(peer, ps, peer_len)) {
346 lwsl_info("factors mismatch\n");
350 lwsl_info("pubkey authorized\n");
365 /* ops: spawn subprocess */
368 ssh_cgi_env_add(struct sshd_instance_priv *priv, const char *name,
371 struct lws_protocol_vhost_options *pvo = malloc(sizeof(*pvo));
376 pvo->name = malloc(strlen(name) + 1);
382 pvo->value = malloc(strlen(value) + 1);
384 free((char *)pvo->name);
389 strcpy((char *)pvo->name, name);
390 strcpy((char *)pvo->value, value);
392 pvo->next = priv->env;
395 lwsl_notice("%s: ENV %s <- %s\n", __func__, name, value);
401 ssh_ops_set_env(void *_priv, const char *name, const char *value)
403 struct sshd_instance_priv *priv = _priv;
405 return ssh_cgi_env_add(priv, name, value);
410 ssh_ops_pty_req(void *_priv, struct lws_ssh_pty *pty)
412 struct sshd_instance_priv *priv = _priv;
413 uint8_t *p = (uint8_t *)pty->modes, opc;
416 lwsl_notice("%s: pty term %s, modes_len %d\n", __func__, pty->term,
419 ssh_cgi_env_add(priv, "TERM", pty->term);
421 while (p < (uint8_t *)pty->modes + pty->modes_len) {
433 lwsl_debug("pty opc %d: 0x%x\n", opc, arg);
437 priv->pty_in_cr_to_nl = !!arg;
438 lwsl_notice(" SSHMO_ICRNL: %d\n", !!arg);
441 priv->pty_in_bloat_nl_to_crnl = !!arg;
442 lwsl_notice(" SSHMO_ONLCR: %d\n", !!arg);
445 // priv->pty_in_echo = !!arg;
446 lwsl_notice(" SSHMO_ECHO: %d\n", !!arg);
455 ssh_ops_child_process_io(void *_priv, struct lws *wsi,
456 struct lws_cgi_args *args)
458 struct sshd_instance_priv *priv = _priv;
459 struct lws_ring *r = priv->ring_stdout;
464 priv->wsi_stdout = args->stdwsi[LWS_STDOUT];
465 priv->wsi_stderr = args->stdwsi[LWS_STDERR];
469 lwsl_notice("STDIN\n");
473 r = priv->ring_stderr;
476 if (lws_ring_next_linear_insert_range(r, &rp, &bytes) ||
478 lwsl_notice("bytes %d\n", (int)bytes);
479 /* no room in the fifo */
482 if (priv->pty_in_bloat_nl_to_crnl) {
483 uint8_t buf[256], *p, *d;
489 if (n > (int)sizeof(buf))
495 m = lws_get_socket_fd(args->stdwsi[args->ch]);
502 lwsl_notice("zero length stdin %d\n", n);
509 if (priv->insert_lf) {
519 if (n < (int)bytes && priv->insert_lf) {
525 n = lws_get_socket_fd(args->stdwsi[args->ch]);
528 n = read(n, rp, bytes);
533 lws_rx_flow_control(args->stdwsi[args->ch], 0);
535 lws_ring_bump_head(r, n);
536 lws_callback_on_writable(wsi);
544 ssh_ops_child_process_terminated(void *priv, struct lws *wsi)
546 lwsl_notice("%s\n", __func__);
551 ssh_ops_exec(void *_priv, struct lws *wsi, const char *command, lws_ssh_finish_exec finish, void *finish_handle)
553 lwsl_notice("%s: EXEC %s\n", __func__, command);
555 /* we don't want to exec anything */
560 ssh_ops_shell(void *_priv, struct lws *wsi, lws_ssh_finish_exec finish, void *finish_handle)
562 struct sshd_instance_priv *priv = _priv;
563 const char *cmd[] = {
569 lwsl_notice("%s: SHELL\n", __func__);
571 if (lws_cgi(wsi, cmd, -1, 0, priv->env)) {
572 lwsl_notice("shell spawn failed\n");
582 ssh_ops_banner(char *buf, size_t max_len, char *lang, size_t max_lang_len)
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");
589 lws_snprintf(lang, max_lang_len, "en/US");
595 ssh_ops_disconnect_reason(uint32_t reason, const char *desc,
596 const char *desc_lang)
598 lwsl_notice("DISCONNECT reason 0x%X, %s (lang %s)\n", reason, desc,
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,
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",
624 * use per-vhost options to bind the ops struct to the instance of the
625 * "lws_raw_sshd" protocol instantiated on our vhost
628 static const struct lws_protocol_vhost_options pvo_ssh_ops = {
635 static const struct lws_protocol_vhost_options pvo_ssh = {
639 "" /* ignored, just matches the protocol name above */
642 void sighandler(int sig)
645 lws_cancel_service(context);
650 static struct lws_context_creation_info info;
651 struct lws_vhost *vh_sshd;
654 /* info is on the stack, it must be cleared down before use */
655 memset(&info, 0, sizeof(info));
657 signal(SIGINT, sighandler);
658 lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE
660 /* | LLL_DEBUG */, NULL);
662 lwsl_notice("lws test-sshd -- Copyright (C) 2017 <andy@warmcat.com>\n");
664 /* create the lws context */
666 info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
667 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
669 context = lws_create_context(&info);
671 lwsl_err("Failed to create context\n");
675 /* create our listening vhost */
678 info.options = LWS_SERVER_OPTION_ONLY_RAW;
679 info.vhost_name = "sshd";
680 info.protocols = protocols_sshd;
683 vh_sshd = lws_create_vhost(context, &info);
685 lwsl_err("Failed to create sshd vhost\n");
689 /* spin doing service */
692 while (!n && !force_exit)
693 n = lws_service(context, 0);
700 lws_context_destroy(context);
701 lwsl_notice("exiting...\n");