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 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 warn("Can't connect to console server"); 239 cleanup: 240 close(client->console_sd); 241 return -1; 242 } 243 244 static void client_fini(struct console_client *client) 245 { 246 if (client->is_tty) 247 tcsetattr(client->fd_in, TCSANOW, &client->orig_termios); 248 close(client->console_sd); 249 } 250 251 int main(int argc, char *argv[]) 252 { 253 struct console_client _client, *client; 254 struct pollfd pollfds[2]; 255 enum process_rc prc = PROCESS_OK; 256 const char *config_path = NULL; 257 struct config *config = NULL; 258 const char *socket_id = NULL; 259 const uint8_t *esc = NULL; 260 int rc; 261 262 client = &_client; 263 memset(client, 0, sizeof(*client)); 264 client->esc_type = ESC_TYPE_SSH; 265 266 for (;;) { 267 rc = getopt(argc, argv, "c:e:i:"); 268 if (rc == -1) 269 break; 270 271 switch (rc) { 272 case 'c': 273 if (optarg[0] == '\0') { 274 fprintf(stderr, "Config str cannot be empty\n"); 275 return EXIT_FAILURE; 276 } 277 config_path = optarg; 278 break; 279 case 'e': 280 if (optarg[0] == '\0') { 281 fprintf(stderr, "Escape str cannot be empty\n"); 282 return EXIT_FAILURE; 283 } 284 esc = (const uint8_t*)optarg; 285 break; 286 case 'i': 287 if (optarg[0] == '\0') { 288 fprintf(stderr, "Socket ID str cannot be empty\n"); 289 return EXIT_FAILURE; 290 } 291 socket_id = optarg; 292 break; 293 default: 294 fprintf(stderr, 295 "Usage: %s " 296 "[-e <escape sequence>]" 297 "[-i <socket ID>]" 298 "[-c <config>]\n", 299 argv[0]); 300 return EXIT_FAILURE; 301 } 302 } 303 304 if (config_path) { 305 config = config_init(config_path); 306 if (!config) { 307 warnx("Can't read configuration, exiting."); 308 return EXIT_FAILURE; 309 } 310 311 if (!esc) 312 esc = (const uint8_t *)config_get_value(config, "escape-sequence"); 313 314 if (!socket_id) 315 socket_id = config_get_value(config, "socket-id"); 316 } 317 318 if (esc) { 319 client->esc_type = ESC_TYPE_STR; 320 client->esc_state.str.str = esc; 321 } 322 323 rc = client_init(client, socket_id); 324 if (rc) 325 goto out_config_fini; 326 327 rc = client_tty_init(client); 328 if (rc) 329 goto out_client_fini; 330 331 for (;;) { 332 pollfds[0].fd = client->fd_in; 333 pollfds[0].events = POLLIN; 334 pollfds[1].fd = client->console_sd; 335 pollfds[1].events = POLLIN; 336 337 rc = poll(pollfds, 2, -1); 338 if (rc < 0) { 339 warn("Poll failure"); 340 break; 341 } 342 343 if (pollfds[0].revents) 344 prc = process_tty(client); 345 346 if (prc == PROCESS_OK && pollfds[1].revents) 347 prc = process_console(client); 348 349 rc = (prc == PROCESS_ERR) ? -1 : 0; 350 if (prc != PROCESS_OK) 351 break; 352 } 353 354 out_client_fini: 355 client_fini(client); 356 357 out_config_fini: 358 if (config_path) 359 config_fini(config); 360 361 if (prc == PROCESS_ESC) 362 return EXIT_ESCAPE; 363 return rc ? EXIT_FAILURE : EXIT_SUCCESS; 364 } 365 366