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