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 <stdbool.h> 19 #include <stdint.h> 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <termios.h> 24 #include <unistd.h> 25 26 #include <sys/socket.h> 27 #include <sys/un.h> 28 29 #include "console-server.h" 30 31 enum process_rc { 32 PROCESS_OK = 0, 33 PROCESS_ERR, 34 PROCESS_EXIT, 35 }; 36 37 enum esc_type { 38 ESC_TYPE_SSH, 39 }; 40 41 struct ssh_esc_state { 42 uint8_t state; 43 }; 44 45 struct console_client { 46 int console_sd; 47 int fd_in; 48 int fd_out; 49 bool is_tty; 50 struct termios orig_termios; 51 enum esc_type esc_type; 52 union { 53 struct ssh_esc_state ssh; 54 } esc_state; 55 }; 56 57 static enum process_rc process_ssh_tty( 58 struct console_client *client, const uint8_t *buf, size_t len) 59 { 60 struct ssh_esc_state *esc_state = &client->esc_state.ssh; 61 const uint8_t *out_buf = buf; 62 int rc; 63 64 for (size_t i = 0; i < len; ++i) { 65 switch (buf[i]) 66 { 67 case '.': 68 if (esc_state->state != '~') { 69 esc_state->state = '\0'; 70 break; 71 } 72 return PROCESS_EXIT; 73 case '~': 74 if (esc_state->state != '\r') { 75 esc_state->state = '\0'; 76 break; 77 } 78 esc_state->state = '~'; 79 /* We need to print everything to skip the tilde */ 80 rc = write_buf_to_fd( 81 client->console_sd, out_buf, i-(out_buf-buf)); 82 if (rc < 0) 83 return PROCESS_ERR; 84 out_buf = &buf[i+1]; 85 break; 86 case '\r': 87 esc_state->state = '\r'; 88 break; 89 default: 90 esc_state->state = '\0'; 91 } 92 } 93 94 rc = write_buf_to_fd(client->console_sd, out_buf, len-(out_buf-buf)); 95 return rc < 0 ? PROCESS_ERR : PROCESS_OK; 96 } 97 98 static enum process_rc process_tty(struct console_client *client) 99 { 100 uint8_t buf[4096]; 101 ssize_t len; 102 103 len = read(client->fd_in, buf, sizeof(buf)); 104 if (len < 0) 105 return PROCESS_ERR; 106 if (len == 0) 107 return PROCESS_EXIT; 108 109 switch (client->esc_type) 110 { 111 case ESC_TYPE_SSH: 112 return process_ssh_tty(client, buf, len); 113 default: 114 return PROCESS_ERR; 115 } 116 } 117 118 119 static int process_console(struct console_client *client) 120 { 121 uint8_t buf[4096]; 122 int len, rc; 123 124 len = read(client->console_sd, buf, sizeof(buf)); 125 if (len < 0) { 126 warn("Can't read from server"); 127 return PROCESS_ERR; 128 } 129 if (len == 0) { 130 fprintf(stderr, "Connection closed\n"); 131 return PROCESS_EXIT; 132 } 133 134 rc = write_buf_to_fd(client->fd_out, buf, len); 135 return rc ? PROCESS_ERR : PROCESS_OK; 136 } 137 138 /* 139 * Setup our local file descriptors for IO: use stdin/stdout, and if we're on a 140 * TTY, put it in canonical mode 141 */ 142 static int client_tty_init(struct console_client *client) 143 { 144 struct termios termios; 145 int rc; 146 147 client->fd_in = STDIN_FILENO; 148 client->fd_out = STDOUT_FILENO; 149 client->is_tty = isatty(client->fd_in); 150 151 if (!client->is_tty) 152 return 0; 153 154 rc = tcgetattr(client->fd_in, &termios); 155 if (rc) { 156 warn("Can't get terminal attributes for console"); 157 return -1; 158 } 159 memcpy(&client->orig_termios, &termios, sizeof(client->orig_termios)); 160 cfmakeraw(&termios); 161 162 rc = tcsetattr(client->fd_in, TCSANOW, &termios); 163 if (rc) { 164 warn("Can't set terminal attributes for console"); 165 return -1; 166 } 167 168 return 0; 169 } 170 171 static int client_init(struct console_client *client) 172 { 173 struct sockaddr_un addr; 174 int rc; 175 176 client->console_sd = socket(AF_UNIX, SOCK_STREAM, 0); 177 if (!client->console_sd) { 178 warn("Can't open socket"); 179 return -1; 180 } 181 182 memset(&addr, 0, sizeof(addr)); 183 addr.sun_family = AF_UNIX; 184 memcpy(&addr.sun_path, &console_socket_path, console_socket_path_len); 185 186 rc = connect(client->console_sd, (struct sockaddr *)&addr, 187 sizeof(addr) - sizeof(addr.sun_path) + console_socket_path_len); 188 if (rc) { 189 warn("Can't connect to console server"); 190 close(client->console_sd); 191 return -1; 192 } 193 194 return 0; 195 } 196 197 static void client_fini(struct console_client *client) 198 { 199 if (client->is_tty) 200 tcsetattr(client->fd_in, TCSANOW, &client->orig_termios); 201 close(client->console_sd); 202 } 203 204 int main(void) 205 { 206 struct console_client _client, *client; 207 struct pollfd pollfds[2]; 208 enum process_rc prc; 209 int rc; 210 211 client = &_client; 212 memset(client, 0, sizeof(*client)); 213 client->esc_type = ESC_TYPE_SSH; 214 215 rc = client_init(client); 216 if (rc) 217 return EXIT_FAILURE; 218 219 rc = client_tty_init(client); 220 if (rc) 221 goto out_fini; 222 223 prc = PROCESS_OK; 224 for (;;) { 225 pollfds[0].fd = client->fd_in; 226 pollfds[0].events = POLLIN; 227 pollfds[1].fd = client->console_sd; 228 pollfds[1].events = POLLIN; 229 230 rc = poll(pollfds, 2, -1); 231 if (rc < 0) { 232 warn("Poll failure"); 233 break; 234 } 235 236 if (pollfds[0].revents) 237 prc = process_tty(client); 238 239 if (prc == PROCESS_OK && pollfds[1].revents) 240 prc = process_console(client); 241 242 rc = (prc == PROCESS_ERR) ? -1 : 0; 243 if (prc != PROCESS_OK) 244 break; 245 } 246 247 out_fini: 248 client_fini(client); 249 return rc ? EXIT_FAILURE : EXIT_SUCCESS; 250 } 251 252