1 /** 2 * Copyright © 2016 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include <err.h> 18 #include <errno.h> 19 #include <getopt.h> 20 #include <stdbool.h> 21 #include <stdint.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <termios.h> 26 #include <unistd.h> 27 28 #include <sys/socket.h> 29 #include <sys/un.h> 30 31 #include "console-server.h" 32 #include "config.h" 33 34 #define EXIT_ESCAPE 2 35 36 enum process_rc { 37 PROCESS_OK = 0, 38 PROCESS_ERR, 39 PROCESS_EXIT, 40 PROCESS_ESC, 41 }; 42 43 enum esc_type { 44 ESC_TYPE_SSH, 45 ESC_TYPE_STR, 46 }; 47 48 struct ssh_esc_state { 49 uint8_t state; 50 }; 51 52 struct str_esc_state { 53 const uint8_t *str; 54 size_t pos; 55 }; 56 57 struct console_client { 58 int console_sd; 59 int fd_in; 60 int fd_out; 61 bool is_tty; 62 struct termios orig_termios; 63 enum esc_type esc_type; 64 union { 65 struct ssh_esc_state ssh; 66 struct str_esc_state str; 67 } esc_state; 68 }; 69 70 static enum process_rc process_ssh_tty(struct console_client *client, 71 const uint8_t *buf, size_t len) 72 { 73 struct ssh_esc_state *esc_state = &client->esc_state.ssh; 74 const uint8_t *out_buf = buf; 75 int rc; 76 77 for (size_t i = 0; i < len; ++i) { 78 switch (buf[i]) { 79 case '.': 80 if (esc_state->state != '~') { 81 esc_state->state = '\0'; 82 break; 83 } 84 return PROCESS_ESC; 85 case '~': 86 if (esc_state->state != '\r') { 87 esc_state->state = '\0'; 88 break; 89 } 90 esc_state->state = '~'; 91 /* We need to print everything to skip the tilde */ 92 rc = write_buf_to_fd(client->console_sd, out_buf, 93 i - (out_buf - buf)); 94 if (rc < 0) { 95 return PROCESS_ERR; 96 } 97 out_buf = &buf[i + 1]; 98 break; 99 case '\r': 100 esc_state->state = '\r'; 101 break; 102 default: 103 esc_state->state = '\0'; 104 } 105 } 106 107 rc = write_buf_to_fd(client->console_sd, out_buf, 108 len - (out_buf - buf)); 109 return rc < 0 ? PROCESS_ERR : PROCESS_OK; 110 } 111 112 static enum process_rc process_str_tty(struct console_client *client, 113 const uint8_t *buf, size_t len) 114 { 115 struct str_esc_state *esc_state = &client->esc_state.str; 116 enum process_rc prc = PROCESS_OK; 117 size_t i; 118 119 for (i = 0; i < len; ++i) { 120 if (buf[i] == esc_state->str[esc_state->pos]) { 121 esc_state->pos++; 122 } else { 123 esc_state->pos = 0; 124 } 125 126 if (esc_state->str[esc_state->pos] == '\0') { 127 prc = PROCESS_ESC; 128 ++i; 129 break; 130 } 131 } 132 133 if (write_buf_to_fd(client->console_sd, buf, i) < 0) { 134 return PROCESS_ERR; 135 } 136 return prc; 137 } 138 139 static enum process_rc process_tty(struct console_client *client) 140 { 141 uint8_t buf[4096]; 142 ssize_t len; 143 144 len = read(client->fd_in, buf, sizeof(buf)); 145 if (len < 0) { 146 return PROCESS_ERR; 147 } 148 if (len == 0) { 149 return PROCESS_EXIT; 150 } 151 152 switch (client->esc_type) { 153 case ESC_TYPE_SSH: 154 return process_ssh_tty(client, buf, len); 155 case ESC_TYPE_STR: 156 return process_str_tty(client, buf, len); 157 default: 158 return PROCESS_ERR; 159 } 160 } 161 162 static int process_console(struct console_client *client) 163 { 164 uint8_t buf[4096]; 165 ssize_t len; 166 int rc; 167 168 len = read(client->console_sd, buf, sizeof(buf)); 169 if (len < 0) { 170 warn("Can't read from server"); 171 return PROCESS_ERR; 172 } 173 if (len == 0) { 174 fprintf(stderr, "Connection closed\n"); 175 return PROCESS_EXIT; 176 } 177 178 rc = write_buf_to_fd(client->fd_out, buf, len); 179 return rc ? PROCESS_ERR : PROCESS_OK; 180 } 181 182 /* 183 * Setup our local file descriptors for IO: use stdin/stdout, and if we're on a 184 * TTY, put it in canonical mode 185 */ 186 static int client_tty_init(struct console_client *client) 187 { 188 struct termios termios; 189 int rc; 190 191 client->fd_in = STDIN_FILENO; 192 client->fd_out = STDOUT_FILENO; 193 client->is_tty = isatty(client->fd_in); 194 195 if (!client->is_tty) { 196 return 0; 197 } 198 199 rc = tcgetattr(client->fd_in, &termios); 200 if (rc) { 201 warn("Can't get terminal attributes for console"); 202 return -1; 203 } 204 memcpy(&client->orig_termios, &termios, sizeof(client->orig_termios)); 205 cfmakeraw(&termios); 206 207 rc = tcsetattr(client->fd_in, TCSANOW, &termios); 208 if (rc) { 209 warn("Can't set terminal attributes for console"); 210 return -1; 211 } 212 213 return 0; 214 } 215 216 static int client_init(struct console_client *client, struct config *config, 217 const char *console_id) 218 { 219 const char *resolved_id = NULL; 220 struct sockaddr_un addr; 221 socket_path_t path; 222 ssize_t len; 223 int rc; 224 225 client->console_sd = socket(AF_UNIX, SOCK_STREAM, 0); 226 if (!client->console_sd) { 227 warn("Can't open socket"); 228 return -1; 229 } 230 231 /* Get the console id */ 232 resolved_id = config_resolve_console_id(config, console_id); 233 234 memset(&addr, 0, sizeof(addr)); 235 addr.sun_family = AF_UNIX; 236 len = console_socket_path(addr.sun_path, resolved_id); 237 if (len < 0) { 238 if (errno) { 239 warn("Failed to configure socket: %s", strerror(errno)); 240 } else { 241 warn("Socket name length exceeds buffer limits"); 242 } 243 goto cleanup; 244 } 245 246 rc = connect(client->console_sd, (struct sockaddr *)&addr, 247 sizeof(addr) - sizeof(addr.sun_path) + len); 248 if (!rc) { 249 return 0; 250 } 251 252 console_socket_path_readable(&addr, len, path); 253 warn("Can't connect to console server '@%s'", path); 254 cleanup: 255 close(client->console_sd); 256 return -1; 257 } 258 259 static void client_fini(struct console_client *client) 260 { 261 if (client->is_tty) { 262 tcsetattr(client->fd_in, TCSANOW, &client->orig_termios); 263 } 264 close(client->console_sd); 265 } 266 267 int main(int argc, char *argv[]) 268 { 269 struct console_client _client; 270 struct console_client *client; 271 struct pollfd pollfds[2]; 272 enum process_rc prc = PROCESS_OK; 273 const char *config_path = NULL; 274 struct config *config = NULL; 275 const char *console_id = NULL; 276 const uint8_t *esc = NULL; 277 int rc; 278 279 client = &_client; 280 memset(client, 0, sizeof(*client)); 281 client->esc_type = ESC_TYPE_SSH; 282 283 for (;;) { 284 rc = getopt(argc, argv, "c:e:i:"); 285 if (rc == -1) { 286 break; 287 } 288 289 switch (rc) { 290 case 'c': 291 if (optarg[0] == '\0') { 292 fprintf(stderr, "Config str cannot be empty\n"); 293 return EXIT_FAILURE; 294 } 295 config_path = optarg; 296 break; 297 case 'e': 298 if (optarg[0] == '\0') { 299 fprintf(stderr, "Escape str cannot be empty\n"); 300 return EXIT_FAILURE; 301 } 302 esc = (const uint8_t *)optarg; 303 break; 304 case 'i': 305 if (optarg[0] == '\0') { 306 fprintf(stderr, 307 "Socket ID str cannot be empty\n"); 308 return EXIT_FAILURE; 309 } 310 console_id = optarg; 311 break; 312 default: 313 fprintf(stderr, 314 "Usage: %s " 315 "[-e <escape sequence>]" 316 "[-i <console ID>]" 317 "[-c <config>]\n", 318 argv[0]); 319 return EXIT_FAILURE; 320 } 321 } 322 323 if (config_path) { 324 config = config_init(config_path); 325 if (!config) { 326 warnx("Can't read configuration, exiting."); 327 return EXIT_FAILURE; 328 } 329 330 if (!esc) { 331 esc = (const uint8_t *)config_get_value( 332 config, "escape-sequence"); 333 } 334 } 335 336 if (esc) { 337 client->esc_type = ESC_TYPE_STR; 338 client->esc_state.str.str = esc; 339 } 340 341 rc = client_init(client, config, console_id); 342 if (rc) { 343 goto out_config_fini; 344 } 345 346 rc = client_tty_init(client); 347 if (rc) { 348 goto out_client_fini; 349 } 350 351 for (;;) { 352 pollfds[0].fd = client->fd_in; 353 pollfds[0].events = POLLIN; 354 pollfds[1].fd = client->console_sd; 355 pollfds[1].events = POLLIN; 356 357 rc = poll(pollfds, 2, -1); 358 if (rc < 0) { 359 warn("Poll failure"); 360 break; 361 } 362 363 if (pollfds[0].revents) { 364 prc = process_tty(client); 365 } 366 367 if (prc == PROCESS_OK && pollfds[1].revents) { 368 prc = process_console(client); 369 } 370 371 rc = (prc == PROCESS_ERR) ? -1 : 0; 372 if (prc != PROCESS_OK) { 373 break; 374 } 375 } 376 377 out_client_fini: 378 client_fini(client); 379 380 out_config_fini: 381 if (config_path) { 382 config_fini(config); 383 } 384 385 if (prc == PROCESS_ESC) { 386 return EXIT_ESCAPE; 387 } 388 return rc ? EXIT_FAILURE : EXIT_SUCCESS; 389 } 390