xref: /openbmc/obmc-console/console-client.c (revision 8fee4242)
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 
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 
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 
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 
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  */
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 
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 
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 
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