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