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, const char *socket_id) 216 { 217 struct sockaddr_un addr; 218 socket_path_t path; 219 ssize_t len; 220 int rc; 221 222 client->console_sd = socket(AF_UNIX, SOCK_STREAM, 0); 223 if (!client->console_sd) { 224 warn("Can't open socket"); 225 return -1; 226 } 227 228 memset(&addr, 0, sizeof(addr)); 229 addr.sun_family = AF_UNIX; 230 len = console_socket_path(&addr, socket_id); 231 if (len < 0) { 232 if (errno) { 233 warn("Failed to configure socket: %s", strerror(errno)); 234 } else { 235 warn("Socket name length exceeds buffer limits"); 236 } 237 goto cleanup; 238 } 239 240 rc = connect(client->console_sd, (struct sockaddr *)&addr, 241 sizeof(addr) - sizeof(addr.sun_path) + len); 242 if (!rc) { 243 return 0; 244 } 245 246 console_socket_path_readable(&addr, len, path); 247 warn("Can't connect to console server '@%s'", path); 248 cleanup: 249 close(client->console_sd); 250 return -1; 251 } 252 253 static void client_fini(struct console_client *client) 254 { 255 if (client->is_tty) { 256 tcsetattr(client->fd_in, TCSANOW, &client->orig_termios); 257 } 258 close(client->console_sd); 259 } 260 261 int main(int argc, char *argv[]) 262 { 263 struct console_client _client; 264 struct console_client *client; 265 struct pollfd pollfds[2]; 266 enum process_rc prc = PROCESS_OK; 267 const char *config_path = NULL; 268 struct config *config = NULL; 269 const char *socket_id = NULL; 270 const uint8_t *esc = NULL; 271 int rc; 272 273 client = &_client; 274 memset(client, 0, sizeof(*client)); 275 client->esc_type = ESC_TYPE_SSH; 276 277 for (;;) { 278 rc = getopt(argc, argv, "c:e:i:"); 279 if (rc == -1) { 280 break; 281 } 282 283 switch (rc) { 284 case 'c': 285 if (optarg[0] == '\0') { 286 fprintf(stderr, "Config str cannot be empty\n"); 287 return EXIT_FAILURE; 288 } 289 config_path = optarg; 290 break; 291 case 'e': 292 if (optarg[0] == '\0') { 293 fprintf(stderr, "Escape str cannot be empty\n"); 294 return EXIT_FAILURE; 295 } 296 esc = (const uint8_t *)optarg; 297 break; 298 case 'i': 299 if (optarg[0] == '\0') { 300 fprintf(stderr, 301 "Socket ID str cannot be empty\n"); 302 return EXIT_FAILURE; 303 } 304 socket_id = optarg; 305 break; 306 default: 307 fprintf(stderr, 308 "Usage: %s " 309 "[-e <escape sequence>]" 310 "[-i <socket ID>]" 311 "[-c <config>]\n", 312 argv[0]); 313 return EXIT_FAILURE; 314 } 315 } 316 317 if (config_path) { 318 config = config_init(config_path); 319 if (!config) { 320 warnx("Can't read configuration, exiting."); 321 return EXIT_FAILURE; 322 } 323 324 if (!esc) { 325 esc = (const uint8_t *)config_get_value( 326 config, "escape-sequence"); 327 } 328 329 if (!socket_id) { 330 socket_id = config_get_value(config, "socket-id"); 331 } 332 } 333 334 if (esc) { 335 client->esc_type = ESC_TYPE_STR; 336 client->esc_state.str.str = esc; 337 } 338 339 rc = client_init(client, socket_id); 340 if (rc) { 341 goto out_config_fini; 342 } 343 344 rc = client_tty_init(client); 345 if (rc) { 346 goto out_client_fini; 347 } 348 349 for (;;) { 350 pollfds[0].fd = client->fd_in; 351 pollfds[0].events = POLLIN; 352 pollfds[1].fd = client->console_sd; 353 pollfds[1].events = POLLIN; 354 355 rc = poll(pollfds, 2, -1); 356 if (rc < 0) { 357 warn("Poll failure"); 358 break; 359 } 360 361 if (pollfds[0].revents) { 362 prc = process_tty(client); 363 } 364 365 if (prc == PROCESS_OK && pollfds[1].revents) { 366 prc = process_console(client); 367 } 368 369 rc = (prc == PROCESS_ERR) ? -1 : 0; 370 if (prc != PROCESS_OK) { 371 break; 372 } 373 } 374 375 out_client_fini: 376 client_fini(client); 377 378 out_config_fini: 379 if (config_path) { 380 config_fini(config); 381 } 382 383 if (prc == PROCESS_ESC) { 384 return EXIT_ESCAPE; 385 } 386 return rc ? EXIT_FAILURE : EXIT_SUCCESS; 387 } 388