Imported Upstream version 3.2.0
[platform/upstream/libwebsockets.git] / test-apps / test-sshd.c
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
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
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
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
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
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) != 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
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
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
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
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
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
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
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
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
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
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
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
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
642 void sighandler(int sig)
643 {
644         force_exit = 1;
645         lws_cancel_service(context);
646 }
647
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 }