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