19326d779SJeremy Kerr /**
29326d779SJeremy Kerr * Copyright © 2016 IBM Corporation
39326d779SJeremy Kerr *
49326d779SJeremy Kerr * Licensed under the Apache License, Version 2.0 (the "License");
59326d779SJeremy Kerr * you may not use this file except in compliance with the License.
69326d779SJeremy Kerr * You may obtain a copy of the License at
79326d779SJeremy Kerr *
89326d779SJeremy Kerr * http://www.apache.org/licenses/LICENSE-2.0
99326d779SJeremy Kerr *
109326d779SJeremy Kerr * Unless required by applicable law or agreed to in writing, software
119326d779SJeremy Kerr * distributed under the License is distributed on an "AS IS" BASIS,
129326d779SJeremy Kerr * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139326d779SJeremy Kerr * See the License for the specific language governing permissions and
149326d779SJeremy Kerr * limitations under the License.
159326d779SJeremy Kerr */
162bd05186SJeremy Kerr
172bd05186SJeremy Kerr #include <err.h>
185e7c0786SAndrew Jeffery #include <errno.h>
1915691c8eSWilliam A. Kennington III #include <getopt.h>
202bd05186SJeremy Kerr #include <stdbool.h>
212bd05186SJeremy Kerr #include <stdint.h>
222bd05186SJeremy Kerr #include <stdio.h>
232bd05186SJeremy Kerr #include <stdlib.h>
242bd05186SJeremy Kerr #include <string.h>
252bd05186SJeremy Kerr #include <termios.h>
262bd05186SJeremy Kerr #include <unistd.h>
272bd05186SJeremy Kerr
282bd05186SJeremy Kerr #include <sys/socket.h>
292bd05186SJeremy Kerr #include <sys/un.h>
302bd05186SJeremy Kerr
312bd05186SJeremy Kerr #include "console-server.h"
32*1e04f449SAlexander Hansen #include "config.h"
332bd05186SJeremy Kerr
348a154357SWilliam A. Kennington III #define EXIT_ESCAPE 2
358a154357SWilliam A. Kennington III
362bd05186SJeremy Kerr enum process_rc {
372bd05186SJeremy Kerr PROCESS_OK = 0,
382bd05186SJeremy Kerr PROCESS_ERR,
392bd05186SJeremy Kerr PROCESS_EXIT,
408a154357SWilliam A. Kennington III PROCESS_ESC,
412bd05186SJeremy Kerr };
422bd05186SJeremy Kerr
43ff569830SWilliam A. Kennington III enum esc_type {
44ff569830SWilliam A. Kennington III ESC_TYPE_SSH,
4515691c8eSWilliam A. Kennington III ESC_TYPE_STR,
46ff569830SWilliam A. Kennington III };
47ff569830SWilliam A. Kennington III
48ff569830SWilliam A. Kennington III struct ssh_esc_state {
49ff569830SWilliam A. Kennington III uint8_t state;
50ff569830SWilliam A. Kennington III };
51ff569830SWilliam A. Kennington III
5215691c8eSWilliam A. Kennington III struct str_esc_state {
5315691c8eSWilliam A. Kennington III const uint8_t *str;
5415691c8eSWilliam A. Kennington III size_t pos;
5515691c8eSWilliam A. Kennington III };
5615691c8eSWilliam A. Kennington III
572bd05186SJeremy Kerr struct console_client {
582bd05186SJeremy Kerr int console_sd;
592bd05186SJeremy Kerr int fd_in;
602bd05186SJeremy Kerr int fd_out;
612bd05186SJeremy Kerr bool is_tty;
622bd05186SJeremy Kerr struct termios orig_termios;
63ff569830SWilliam A. Kennington III enum esc_type esc_type;
64ff569830SWilliam A. Kennington III union {
65ff569830SWilliam A. Kennington III struct ssh_esc_state ssh;
6615691c8eSWilliam A. Kennington III struct str_esc_state str;
67ff569830SWilliam A. Kennington III } esc_state;
682bd05186SJeremy Kerr };
692bd05186SJeremy Kerr
process_ssh_tty(struct console_client * client,const uint8_t * buf,size_t len)70a72711afSAndrew Jeffery static enum process_rc process_ssh_tty(struct console_client *client,
71a72711afSAndrew Jeffery const uint8_t *buf, size_t len)
72ff569830SWilliam A. Kennington III {
73ff569830SWilliam A. Kennington III struct ssh_esc_state *esc_state = &client->esc_state.ssh;
74ff569830SWilliam A. Kennington III const uint8_t *out_buf = buf;
75ff569830SWilliam A. Kennington III int rc;
76ff569830SWilliam A. Kennington III
77ff569830SWilliam A. Kennington III for (size_t i = 0; i < len; ++i) {
78a72711afSAndrew Jeffery switch (buf[i]) {
79ff569830SWilliam A. Kennington III case '.':
80ff569830SWilliam A. Kennington III if (esc_state->state != '~') {
81ff569830SWilliam A. Kennington III esc_state->state = '\0';
82ff569830SWilliam A. Kennington III break;
83ff569830SWilliam A. Kennington III }
848a154357SWilliam A. Kennington III return PROCESS_ESC;
85ff569830SWilliam A. Kennington III case '~':
86ff569830SWilliam A. Kennington III if (esc_state->state != '\r') {
87ff569830SWilliam A. Kennington III esc_state->state = '\0';
88ff569830SWilliam A. Kennington III break;
89ff569830SWilliam A. Kennington III }
90ff569830SWilliam A. Kennington III esc_state->state = '~';
91ff569830SWilliam A. Kennington III /* We need to print everything to skip the tilde */
92a72711afSAndrew Jeffery rc = write_buf_to_fd(client->console_sd, out_buf,
93a72711afSAndrew Jeffery i - (out_buf - buf));
942834c5b1SAndrew Jeffery if (rc < 0) {
95ff569830SWilliam A. Kennington III return PROCESS_ERR;
962834c5b1SAndrew Jeffery }
97ff569830SWilliam A. Kennington III out_buf = &buf[i + 1];
98ff569830SWilliam A. Kennington III break;
99ff569830SWilliam A. Kennington III case '\r':
100ff569830SWilliam A. Kennington III esc_state->state = '\r';
101ff569830SWilliam A. Kennington III break;
102ff569830SWilliam A. Kennington III default:
103ff569830SWilliam A. Kennington III esc_state->state = '\0';
104ff569830SWilliam A. Kennington III }
105ff569830SWilliam A. Kennington III }
106ff569830SWilliam A. Kennington III
107a72711afSAndrew Jeffery rc = write_buf_to_fd(client->console_sd, out_buf,
108a72711afSAndrew Jeffery len - (out_buf - buf));
109ff569830SWilliam A. Kennington III return rc < 0 ? PROCESS_ERR : PROCESS_OK;
110ff569830SWilliam A. Kennington III }
1112bd05186SJeremy Kerr
process_str_tty(struct console_client * client,const uint8_t * buf,size_t len)112a72711afSAndrew Jeffery static enum process_rc process_str_tty(struct console_client *client,
113a72711afSAndrew Jeffery const uint8_t *buf, size_t len)
11415691c8eSWilliam A. Kennington III {
11515691c8eSWilliam A. Kennington III struct str_esc_state *esc_state = &client->esc_state.str;
11615691c8eSWilliam A. Kennington III enum process_rc prc = PROCESS_OK;
11715691c8eSWilliam A. Kennington III size_t i;
11815691c8eSWilliam A. Kennington III
11915691c8eSWilliam A. Kennington III for (i = 0; i < len; ++i) {
1202834c5b1SAndrew Jeffery if (buf[i] == esc_state->str[esc_state->pos]) {
12115691c8eSWilliam A. Kennington III esc_state->pos++;
1222834c5b1SAndrew Jeffery } else {
12315691c8eSWilliam A. Kennington III esc_state->pos = 0;
1242834c5b1SAndrew Jeffery }
12515691c8eSWilliam A. Kennington III
12615691c8eSWilliam A. Kennington III if (esc_state->str[esc_state->pos] == '\0') {
1278a154357SWilliam A. Kennington III prc = PROCESS_ESC;
1285b16dc89SWilliam A. Kennington III ++i;
12915691c8eSWilliam A. Kennington III break;
13015691c8eSWilliam A. Kennington III }
13115691c8eSWilliam A. Kennington III }
13215691c8eSWilliam A. Kennington III
1332834c5b1SAndrew Jeffery if (write_buf_to_fd(client->console_sd, buf, i) < 0) {
13415691c8eSWilliam A. Kennington III return PROCESS_ERR;
1352834c5b1SAndrew Jeffery }
13615691c8eSWilliam A. Kennington III return prc;
13715691c8eSWilliam A. Kennington III }
13815691c8eSWilliam A. Kennington III
process_tty(struct console_client * client)1392bd05186SJeremy Kerr static enum process_rc process_tty(struct console_client *client)
1402bd05186SJeremy Kerr {
141ff569830SWilliam A. Kennington III uint8_t buf[4096];
1422bd05186SJeremy Kerr ssize_t len;
1432bd05186SJeremy Kerr
1442bd05186SJeremy Kerr len = read(client->fd_in, buf, sizeof(buf));
1452834c5b1SAndrew Jeffery if (len < 0) {
1462bd05186SJeremy Kerr return PROCESS_ERR;
1472834c5b1SAndrew Jeffery }
1482834c5b1SAndrew Jeffery if (len == 0) {
1492bd05186SJeremy Kerr return PROCESS_EXIT;
1502834c5b1SAndrew Jeffery }
1512bd05186SJeremy Kerr
152a72711afSAndrew Jeffery switch (client->esc_type) {
153ff569830SWilliam A. Kennington III case ESC_TYPE_SSH:
154ff569830SWilliam A. Kennington III return process_ssh_tty(client, buf, len);
15515691c8eSWilliam A. Kennington III case ESC_TYPE_STR:
15615691c8eSWilliam A. Kennington III return process_str_tty(client, buf, len);
157ff569830SWilliam A. Kennington III default:
1582bd05186SJeremy Kerr return PROCESS_ERR;
159ff569830SWilliam A. Kennington III }
1602bd05186SJeremy Kerr }
1612bd05186SJeremy Kerr
process_console(struct console_client * client)1622bd05186SJeremy Kerr static int process_console(struct console_client *client)
1632bd05186SJeremy Kerr {
1642bd05186SJeremy Kerr uint8_t buf[4096];
1655c359cc6SAndrew Jeffery ssize_t len;
1665c359cc6SAndrew Jeffery int rc;
1672bd05186SJeremy Kerr
1682bd05186SJeremy Kerr len = read(client->console_sd, buf, sizeof(buf));
1692bd05186SJeremy Kerr if (len < 0) {
1702bd05186SJeremy Kerr warn("Can't read from server");
1712bd05186SJeremy Kerr return PROCESS_ERR;
1722bd05186SJeremy Kerr }
1732bd05186SJeremy Kerr if (len == 0) {
1742bd05186SJeremy Kerr fprintf(stderr, "Connection closed\n");
1752bd05186SJeremy Kerr return PROCESS_EXIT;
1762bd05186SJeremy Kerr }
1772bd05186SJeremy Kerr
1782bd05186SJeremy Kerr rc = write_buf_to_fd(client->fd_out, buf, len);
1792bd05186SJeremy Kerr return rc ? PROCESS_ERR : PROCESS_OK;
1802bd05186SJeremy Kerr }
1812bd05186SJeremy Kerr
1822bd05186SJeremy Kerr /*
1832bd05186SJeremy Kerr * Setup our local file descriptors for IO: use stdin/stdout, and if we're on a
1842bd05186SJeremy Kerr * TTY, put it in canonical mode
1852bd05186SJeremy Kerr */
client_tty_init(struct console_client * client)1862bd05186SJeremy Kerr static int client_tty_init(struct console_client *client)
1872bd05186SJeremy Kerr {
1882bd05186SJeremy Kerr struct termios termios;
1892bd05186SJeremy Kerr int rc;
1902bd05186SJeremy Kerr
1912bd05186SJeremy Kerr client->fd_in = STDIN_FILENO;
1922bd05186SJeremy Kerr client->fd_out = STDOUT_FILENO;
1932bd05186SJeremy Kerr client->is_tty = isatty(client->fd_in);
1942bd05186SJeremy Kerr
1952834c5b1SAndrew Jeffery if (!client->is_tty) {
1962bd05186SJeremy Kerr return 0;
1972834c5b1SAndrew Jeffery }
1982bd05186SJeremy Kerr
1992bd05186SJeremy Kerr rc = tcgetattr(client->fd_in, &termios);
2002bd05186SJeremy Kerr if (rc) {
2012bd05186SJeremy Kerr warn("Can't get terminal attributes for console");
2022bd05186SJeremy Kerr return -1;
2032bd05186SJeremy Kerr }
2042bd05186SJeremy Kerr memcpy(&client->orig_termios, &termios, sizeof(client->orig_termios));
2052bd05186SJeremy Kerr cfmakeraw(&termios);
2062bd05186SJeremy Kerr
2072bd05186SJeremy Kerr rc = tcsetattr(client->fd_in, TCSANOW, &termios);
2082bd05186SJeremy Kerr if (rc) {
2092bd05186SJeremy Kerr warn("Can't set terminal attributes for console");
2102bd05186SJeremy Kerr return -1;
2112bd05186SJeremy Kerr }
2122bd05186SJeremy Kerr
2132bd05186SJeremy Kerr return 0;
2142bd05186SJeremy Kerr }
2152bd05186SJeremy Kerr
client_init(struct console_client * client,struct config * config,const char * console_id)2165ba20b5bSNinad Palsule static int client_init(struct console_client *client, struct config *config,
2175ba20b5bSNinad Palsule const char *console_id)
2182bd05186SJeremy Kerr {
2195ba20b5bSNinad Palsule const char *resolved_id = NULL;
2202bd05186SJeremy Kerr struct sockaddr_un addr;
2216ed0e4e7SAndrew Jeffery socket_path_t path;
2225e7c0786SAndrew Jeffery ssize_t len;
2232bd05186SJeremy Kerr int rc;
2242bd05186SJeremy Kerr
2252bd05186SJeremy Kerr client->console_sd = socket(AF_UNIX, SOCK_STREAM, 0);
2262bd05186SJeremy Kerr if (!client->console_sd) {
2272bd05186SJeremy Kerr warn("Can't open socket");
2282bd05186SJeremy Kerr return -1;
2292bd05186SJeremy Kerr }
2302bd05186SJeremy Kerr
2315ba20b5bSNinad Palsule /* Get the console id */
2325ba20b5bSNinad Palsule resolved_id = config_resolve_console_id(config, console_id);
2335ba20b5bSNinad Palsule
2342bd05186SJeremy Kerr memset(&addr, 0, sizeof(addr));
2352bd05186SJeremy Kerr addr.sun_family = AF_UNIX;
2365ba20b5bSNinad Palsule len = console_socket_path(addr.sun_path, resolved_id);
2375e7c0786SAndrew Jeffery if (len < 0) {
2382834c5b1SAndrew Jeffery if (errno) {
2395e7c0786SAndrew Jeffery warn("Failed to configure socket: %s", strerror(errno));
2402834c5b1SAndrew Jeffery } else {
2415e7c0786SAndrew Jeffery warn("Socket name length exceeds buffer limits");
2422834c5b1SAndrew Jeffery }
2435e7c0786SAndrew Jeffery goto cleanup;
2442bd05186SJeremy Kerr }
2452bd05186SJeremy Kerr
2465e7c0786SAndrew Jeffery rc = connect(client->console_sd, (struct sockaddr *)&addr,
2475e7c0786SAndrew Jeffery sizeof(addr) - sizeof(addr.sun_path) + len);
2482834c5b1SAndrew Jeffery if (!rc) {
2492bd05186SJeremy Kerr return 0;
2502834c5b1SAndrew Jeffery }
2515e7c0786SAndrew Jeffery
2526ed0e4e7SAndrew Jeffery console_socket_path_readable(&addr, len, path);
2536ed0e4e7SAndrew Jeffery warn("Can't connect to console server '@%s'", path);
2545e7c0786SAndrew Jeffery cleanup:
2555e7c0786SAndrew Jeffery close(client->console_sd);
2565e7c0786SAndrew Jeffery return -1;
2572bd05186SJeremy Kerr }
2582bd05186SJeremy Kerr
client_fini(struct console_client * client)2592bd05186SJeremy Kerr static void client_fini(struct console_client *client)
2602bd05186SJeremy Kerr {
2612834c5b1SAndrew Jeffery if (client->is_tty) {
2622bd05186SJeremy Kerr tcsetattr(client->fd_in, TCSANOW, &client->orig_termios);
2632834c5b1SAndrew Jeffery }
2642bd05186SJeremy Kerr close(client->console_sd);
2652bd05186SJeremy Kerr }
2662bd05186SJeremy Kerr
main(int argc,char * argv[])26715691c8eSWilliam A. Kennington III int main(int argc, char *argv[])
2682bd05186SJeremy Kerr {
269b70f8713SAndrew Jeffery struct console_client _client;
270b70f8713SAndrew Jeffery struct console_client *client;
2712bd05186SJeremy Kerr struct pollfd pollfds[2];
2728a154357SWilliam A. Kennington III enum process_rc prc = PROCESS_OK;
27371e7a249SAndrew Jeffery const char *config_path = NULL;
27471e7a249SAndrew Jeffery struct config *config = NULL;
2759a8f30ecSAndrew Jeffery const char *console_id = NULL;
27671e7a249SAndrew Jeffery const uint8_t *esc = NULL;
2772bd05186SJeremy Kerr int rc;
2782bd05186SJeremy Kerr
2792bd05186SJeremy Kerr client = &_client;
2802bd05186SJeremy Kerr memset(client, 0, sizeof(*client));
281ff569830SWilliam A. Kennington III client->esc_type = ESC_TYPE_SSH;
2822bd05186SJeremy Kerr
28315691c8eSWilliam A. Kennington III for (;;) {
28471e7a249SAndrew Jeffery rc = getopt(argc, argv, "c:e:i:");
2852834c5b1SAndrew Jeffery if (rc == -1) {
28615691c8eSWilliam A. Kennington III break;
2872834c5b1SAndrew Jeffery }
28815691c8eSWilliam A. Kennington III
28915691c8eSWilliam A. Kennington III switch (rc) {
29071e7a249SAndrew Jeffery case 'c':
29171e7a249SAndrew Jeffery if (optarg[0] == '\0') {
29271e7a249SAndrew Jeffery fprintf(stderr, "Config str cannot be empty\n");
29371e7a249SAndrew Jeffery return EXIT_FAILURE;
29471e7a249SAndrew Jeffery }
29571e7a249SAndrew Jeffery config_path = optarg;
29671e7a249SAndrew Jeffery break;
29715691c8eSWilliam A. Kennington III case 'e':
29815691c8eSWilliam A. Kennington III if (optarg[0] == '\0') {
29915691c8eSWilliam A. Kennington III fprintf(stderr, "Escape str cannot be empty\n");
30015691c8eSWilliam A. Kennington III return EXIT_FAILURE;
30115691c8eSWilliam A. Kennington III }
30271e7a249SAndrew Jeffery esc = (const uint8_t *)optarg;
30315691c8eSWilliam A. Kennington III break;
304ddf2ab7cSAndrew Jeffery case 'i':
305ddf2ab7cSAndrew Jeffery if (optarg[0] == '\0') {
306a72711afSAndrew Jeffery fprintf(stderr,
307a72711afSAndrew Jeffery "Socket ID str cannot be empty\n");
308ddf2ab7cSAndrew Jeffery return EXIT_FAILURE;
309ddf2ab7cSAndrew Jeffery }
3109a8f30ecSAndrew Jeffery console_id = optarg;
311ddf2ab7cSAndrew Jeffery break;
31215691c8eSWilliam A. Kennington III default:
31315691c8eSWilliam A. Kennington III fprintf(stderr,
31415691c8eSWilliam A. Kennington III "Usage: %s "
315ddf2ab7cSAndrew Jeffery "[-e <escape sequence>]"
3169a8f30ecSAndrew Jeffery "[-i <console ID>]"
31771e7a249SAndrew Jeffery "[-c <config>]\n",
31815691c8eSWilliam A. Kennington III argv[0]);
31915691c8eSWilliam A. Kennington III return EXIT_FAILURE;
32015691c8eSWilliam A. Kennington III }
32115691c8eSWilliam A. Kennington III }
32215691c8eSWilliam A. Kennington III
32371e7a249SAndrew Jeffery if (config_path) {
32471e7a249SAndrew Jeffery config = config_init(config_path);
32571e7a249SAndrew Jeffery if (!config) {
32671e7a249SAndrew Jeffery warnx("Can't read configuration, exiting.");
32771e7a249SAndrew Jeffery return EXIT_FAILURE;
32871e7a249SAndrew Jeffery }
32971e7a249SAndrew Jeffery
3302834c5b1SAndrew Jeffery if (!esc) {
331a72711afSAndrew Jeffery esc = (const uint8_t *)config_get_value(
332a72711afSAndrew Jeffery config, "escape-sequence");
3332834c5b1SAndrew Jeffery }
3342834c5b1SAndrew Jeffery }
33571e7a249SAndrew Jeffery
33671e7a249SAndrew Jeffery if (esc) {
33771e7a249SAndrew Jeffery client->esc_type = ESC_TYPE_STR;
33871e7a249SAndrew Jeffery client->esc_state.str.str = esc;
33971e7a249SAndrew Jeffery }
34071e7a249SAndrew Jeffery
3415ba20b5bSNinad Palsule rc = client_init(client, config, console_id);
3422834c5b1SAndrew Jeffery if (rc) {
34371e7a249SAndrew Jeffery goto out_config_fini;
3442834c5b1SAndrew Jeffery }
3452bd05186SJeremy Kerr
3462bd05186SJeremy Kerr rc = client_tty_init(client);
3472834c5b1SAndrew Jeffery if (rc) {
34871e7a249SAndrew Jeffery goto out_client_fini;
3492834c5b1SAndrew Jeffery }
3502bd05186SJeremy Kerr
3512bd05186SJeremy Kerr for (;;) {
3522bd05186SJeremy Kerr pollfds[0].fd = client->fd_in;
3532bd05186SJeremy Kerr pollfds[0].events = POLLIN;
3542bd05186SJeremy Kerr pollfds[1].fd = client->console_sd;
3552bd05186SJeremy Kerr pollfds[1].events = POLLIN;
3562bd05186SJeremy Kerr
3572bd05186SJeremy Kerr rc = poll(pollfds, 2, -1);
3582bd05186SJeremy Kerr if (rc < 0) {
3592bd05186SJeremy Kerr warn("Poll failure");
3602bd05186SJeremy Kerr break;
3612bd05186SJeremy Kerr }
3622bd05186SJeremy Kerr
3632834c5b1SAndrew Jeffery if (pollfds[0].revents) {
3642bd05186SJeremy Kerr prc = process_tty(client);
3652834c5b1SAndrew Jeffery }
3662bd05186SJeremy Kerr
3672834c5b1SAndrew Jeffery if (prc == PROCESS_OK && pollfds[1].revents) {
3682bd05186SJeremy Kerr prc = process_console(client);
3692834c5b1SAndrew Jeffery }
3702bd05186SJeremy Kerr
3712bd05186SJeremy Kerr rc = (prc == PROCESS_ERR) ? -1 : 0;
3722834c5b1SAndrew Jeffery if (prc != PROCESS_OK) {
3732bd05186SJeremy Kerr break;
3742bd05186SJeremy Kerr }
3752834c5b1SAndrew Jeffery }
3762bd05186SJeremy Kerr
37771e7a249SAndrew Jeffery out_client_fini:
3782bd05186SJeremy Kerr client_fini(client);
37971e7a249SAndrew Jeffery
38071e7a249SAndrew Jeffery out_config_fini:
3812834c5b1SAndrew Jeffery if (config_path) {
38271e7a249SAndrew Jeffery config_fini(config);
3832834c5b1SAndrew Jeffery }
38471e7a249SAndrew Jeffery
3852834c5b1SAndrew Jeffery if (prc == PROCESS_ESC) {
3868a154357SWilliam A. Kennington III return EXIT_ESCAPE;
3872834c5b1SAndrew Jeffery }
3882bd05186SJeremy Kerr return rc ? EXIT_FAILURE : EXIT_SUCCESS;
3892bd05186SJeremy Kerr }
390