blob: 3dc31908b15b37b002249dc70ebdc6f473b82819 [file] [log] [blame]
/**
* Copyright © 2016 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "console-server.h"
#define EXIT_ESCAPE 2
enum process_rc {
PROCESS_OK = 0,
PROCESS_ERR,
PROCESS_EXIT,
PROCESS_ESC,
};
enum esc_type {
ESC_TYPE_SSH,
ESC_TYPE_STR,
};
struct ssh_esc_state {
uint8_t state;
};
struct str_esc_state {
const uint8_t *str;
size_t pos;
};
struct console_client {
int console_sd;
int fd_in;
int fd_out;
bool is_tty;
struct termios orig_termios;
enum esc_type esc_type;
union {
struct ssh_esc_state ssh;
struct str_esc_state str;
} esc_state;
};
static enum process_rc process_ssh_tty(struct console_client *client,
const uint8_t *buf, size_t len)
{
struct ssh_esc_state *esc_state = &client->esc_state.ssh;
const uint8_t *out_buf = buf;
int rc;
for (size_t i = 0; i < len; ++i) {
switch (buf[i]) {
case '.':
if (esc_state->state != '~') {
esc_state->state = '\0';
break;
}
return PROCESS_ESC;
case '~':
if (esc_state->state != '\r') {
esc_state->state = '\0';
break;
}
esc_state->state = '~';
/* We need to print everything to skip the tilde */
rc = write_buf_to_fd(client->console_sd, out_buf,
i - (out_buf - buf));
if (rc < 0) {
return PROCESS_ERR;
}
out_buf = &buf[i + 1];
break;
case '\r':
esc_state->state = '\r';
break;
default:
esc_state->state = '\0';
}
}
rc = write_buf_to_fd(client->console_sd, out_buf,
len - (out_buf - buf));
return rc < 0 ? PROCESS_ERR : PROCESS_OK;
}
static enum process_rc process_str_tty(struct console_client *client,
const uint8_t *buf, size_t len)
{
struct str_esc_state *esc_state = &client->esc_state.str;
enum process_rc prc = PROCESS_OK;
size_t i;
for (i = 0; i < len; ++i) {
if (buf[i] == esc_state->str[esc_state->pos]) {
esc_state->pos++;
} else {
esc_state->pos = 0;
}
if (esc_state->str[esc_state->pos] == '\0') {
prc = PROCESS_ESC;
++i;
break;
}
}
if (write_buf_to_fd(client->console_sd, buf, i) < 0) {
return PROCESS_ERR;
}
return prc;
}
static enum process_rc process_tty(struct console_client *client)
{
uint8_t buf[4096];
ssize_t len;
len = read(client->fd_in, buf, sizeof(buf));
if (len < 0) {
return PROCESS_ERR;
}
if (len == 0) {
return PROCESS_EXIT;
}
switch (client->esc_type) {
case ESC_TYPE_SSH:
return process_ssh_tty(client, buf, len);
case ESC_TYPE_STR:
return process_str_tty(client, buf, len);
default:
return PROCESS_ERR;
}
}
static int process_console(struct console_client *client)
{
uint8_t buf[4096];
ssize_t len;
int rc;
len = read(client->console_sd, buf, sizeof(buf));
if (len < 0) {
warn("Can't read from server");
return PROCESS_ERR;
}
if (len == 0) {
fprintf(stderr, "Connection closed\n");
return PROCESS_EXIT;
}
rc = write_buf_to_fd(client->fd_out, buf, len);
return rc ? PROCESS_ERR : PROCESS_OK;
}
/*
* Setup our local file descriptors for IO: use stdin/stdout, and if we're on a
* TTY, put it in canonical mode
*/
static int client_tty_init(struct console_client *client)
{
struct termios termios;
int rc;
client->fd_in = STDIN_FILENO;
client->fd_out = STDOUT_FILENO;
client->is_tty = isatty(client->fd_in);
if (!client->is_tty) {
return 0;
}
rc = tcgetattr(client->fd_in, &termios);
if (rc) {
warn("Can't get terminal attributes for console");
return -1;
}
memcpy(&client->orig_termios, &termios, sizeof(client->orig_termios));
cfmakeraw(&termios);
rc = tcsetattr(client->fd_in, TCSANOW, &termios);
if (rc) {
warn("Can't set terminal attributes for console");
return -1;
}
return 0;
}
static int client_init(struct console_client *client, struct config *config,
const char *console_id)
{
const char *resolved_id = NULL;
struct sockaddr_un addr;
socket_path_t path;
ssize_t len;
int rc;
client->console_sd = socket(AF_UNIX, SOCK_STREAM, 0);
if (!client->console_sd) {
warn("Can't open socket");
return -1;
}
/* Get the console id */
resolved_id = config_resolve_console_id(config, console_id);
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
len = console_socket_path(addr.sun_path, resolved_id);
if (len < 0) {
if (errno) {
warn("Failed to configure socket: %s", strerror(errno));
} else {
warn("Socket name length exceeds buffer limits");
}
goto cleanup;
}
rc = connect(client->console_sd, (struct sockaddr *)&addr,
sizeof(addr) - sizeof(addr.sun_path) + len);
if (!rc) {
return 0;
}
console_socket_path_readable(&addr, len, path);
warn("Can't connect to console server '@%s'", path);
cleanup:
close(client->console_sd);
return -1;
}
static void client_fini(struct console_client *client)
{
if (client->is_tty) {
tcsetattr(client->fd_in, TCSANOW, &client->orig_termios);
}
close(client->console_sd);
}
int main(int argc, char *argv[])
{
struct console_client _client;
struct console_client *client;
struct pollfd pollfds[2];
enum process_rc prc = PROCESS_OK;
const char *config_path = NULL;
struct config *config = NULL;
const char *console_id = NULL;
const uint8_t *esc = NULL;
int rc;
client = &_client;
memset(client, 0, sizeof(*client));
client->esc_type = ESC_TYPE_SSH;
for (;;) {
rc = getopt(argc, argv, "c:e:i:");
if (rc == -1) {
break;
}
switch (rc) {
case 'c':
if (optarg[0] == '\0') {
fprintf(stderr, "Config str cannot be empty\n");
return EXIT_FAILURE;
}
config_path = optarg;
break;
case 'e':
if (optarg[0] == '\0') {
fprintf(stderr, "Escape str cannot be empty\n");
return EXIT_FAILURE;
}
esc = (const uint8_t *)optarg;
break;
case 'i':
if (optarg[0] == '\0') {
fprintf(stderr,
"Socket ID str cannot be empty\n");
return EXIT_FAILURE;
}
console_id = optarg;
break;
default:
fprintf(stderr,
"Usage: %s "
"[-e <escape sequence>]"
"[-i <console ID>]"
"[-c <config>]\n",
argv[0]);
return EXIT_FAILURE;
}
}
if (config_path) {
config = config_init(config_path);
if (!config) {
warnx("Can't read configuration, exiting.");
return EXIT_FAILURE;
}
if (!esc) {
esc = (const uint8_t *)config_get_value(
config, "escape-sequence");
}
}
if (esc) {
client->esc_type = ESC_TYPE_STR;
client->esc_state.str.str = esc;
}
rc = client_init(client, config, console_id);
if (rc) {
goto out_config_fini;
}
rc = client_tty_init(client);
if (rc) {
goto out_client_fini;
}
for (;;) {
pollfds[0].fd = client->fd_in;
pollfds[0].events = POLLIN;
pollfds[1].fd = client->console_sd;
pollfds[1].events = POLLIN;
rc = poll(pollfds, 2, -1);
if (rc < 0) {
warn("Poll failure");
break;
}
if (pollfds[0].revents) {
prc = process_tty(client);
}
if (prc == PROCESS_OK && pollfds[1].revents) {
prc = process_console(client);
}
rc = (prc == PROCESS_ERR) ? -1 : 0;
if (prc != PROCESS_OK) {
break;
}
}
out_client_fini:
client_fini(client);
out_config_fini:
if (config_path) {
config_fini(config);
}
if (prc == PROCESS_ESC) {
return EXIT_ESCAPE;
}
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}