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) 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, NULL); 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 int rc; 257 258 client = &_client; 259 memset(client, 0, sizeof(*client)); 260 client->esc_type = ESC_TYPE_SSH; 261 262 for (;;) { 263 rc = getopt(argc, argv, "e:"); 264 if (rc == -1) 265 break; 266 267 switch (rc) { 268 case 'e': 269 if (optarg[0] == '\0') { 270 fprintf(stderr, "Escape str cannot be empty\n"); 271 return EXIT_FAILURE; 272 } 273 client->esc_type = ESC_TYPE_STR; 274 client->esc_state.str.str = (const uint8_t*)optarg; 275 break; 276 default: 277 fprintf(stderr, 278 "Usage: %s " 279 "[-e <escape sequence>]\n", 280 argv[0]); 281 return EXIT_FAILURE; 282 } 283 } 284 285 rc = client_init(client); 286 if (rc) 287 return EXIT_FAILURE; 288 289 rc = client_tty_init(client); 290 if (rc) 291 goto out_fini; 292 293 for (;;) { 294 pollfds[0].fd = client->fd_in; 295 pollfds[0].events = POLLIN; 296 pollfds[1].fd = client->console_sd; 297 pollfds[1].events = POLLIN; 298 299 rc = poll(pollfds, 2, -1); 300 if (rc < 0) { 301 warn("Poll failure"); 302 break; 303 } 304 305 if (pollfds[0].revents) 306 prc = process_tty(client); 307 308 if (prc == PROCESS_OK && pollfds[1].revents) 309 prc = process_console(client); 310 311 rc = (prc == PROCESS_ERR) ? -1 : 0; 312 if (prc != PROCESS_OK) 313 break; 314 } 315 316 out_fini: 317 client_fini(client); 318 if (prc == PROCESS_ESC) 319 return EXIT_ESCAPE; 320 return rc ? EXIT_FAILURE : EXIT_SUCCESS; 321 } 322 323