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