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