xref: /openbmc/obmc-console/console-client.c (revision 5c359cc6)
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(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 			out_buf = &buf[i + 1];
96 			break;
97 		case '\r':
98 			esc_state->state = '\r';
99 			break;
100 		default:
101 			esc_state->state = '\0';
102 		}
103 	}
104 
105 	rc = write_buf_to_fd(client->console_sd, out_buf,
106 			     len - (out_buf - buf));
107 	return rc < 0 ? PROCESS_ERR : PROCESS_OK;
108 }
109 
110 static enum process_rc process_str_tty(struct console_client *client,
111 				       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 	case ESC_TYPE_SSH:
148 		return process_ssh_tty(client, buf, len);
149 	case ESC_TYPE_STR:
150 		return process_str_tty(client, buf, len);
151 	default:
152 		return PROCESS_ERR;
153 	}
154 }
155 
156 static int process_console(struct console_client *client)
157 {
158 	uint8_t buf[4096];
159 	ssize_t len;
160 	int rc;
161 
162 	len = read(client->console_sd, buf, sizeof(buf));
163 	if (len < 0) {
164 		warn("Can't read from server");
165 		return PROCESS_ERR;
166 	}
167 	if (len == 0) {
168 		fprintf(stderr, "Connection closed\n");
169 		return PROCESS_EXIT;
170 	}
171 
172 	rc = write_buf_to_fd(client->fd_out, buf, len);
173 	return rc ? PROCESS_ERR : PROCESS_OK;
174 }
175 
176 /*
177  * Setup our local file descriptors for IO: use stdin/stdout, and if we're on a
178  * TTY, put it in canonical mode
179  */
180 static int client_tty_init(struct console_client *client)
181 {
182 	struct termios termios;
183 	int rc;
184 
185 	client->fd_in = STDIN_FILENO;
186 	client->fd_out = STDOUT_FILENO;
187 	client->is_tty = isatty(client->fd_in);
188 
189 	if (!client->is_tty)
190 		return 0;
191 
192 	rc = tcgetattr(client->fd_in, &termios);
193 	if (rc) {
194 		warn("Can't get terminal attributes for console");
195 		return -1;
196 	}
197 	memcpy(&client->orig_termios, &termios, sizeof(client->orig_termios));
198 	cfmakeraw(&termios);
199 
200 	rc = tcsetattr(client->fd_in, TCSANOW, &termios);
201 	if (rc) {
202 		warn("Can't set terminal attributes for console");
203 		return -1;
204 	}
205 
206 	return 0;
207 }
208 
209 static int client_init(struct console_client *client, const char *socket_id)
210 {
211 	struct sockaddr_un addr;
212 	socket_path_t path;
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, socket_id);
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 	console_socket_path_readable(&addr, len, path);
239 	warn("Can't connect to console server '@%s'", path);
240 cleanup:
241 	close(client->console_sd);
242 	return -1;
243 }
244 
245 static void client_fini(struct console_client *client)
246 {
247 	if (client->is_tty)
248 		tcsetattr(client->fd_in, TCSANOW, &client->orig_termios);
249 	close(client->console_sd);
250 }
251 
252 int main(int argc, char *argv[])
253 {
254 	struct console_client _client, *client;
255 	struct pollfd pollfds[2];
256 	enum process_rc prc = PROCESS_OK;
257 	const char *config_path = NULL;
258 	struct config *config = NULL;
259 	const char *socket_id = NULL;
260 	const uint8_t *esc = NULL;
261 	int rc;
262 
263 	client = &_client;
264 	memset(client, 0, sizeof(*client));
265 	client->esc_type = ESC_TYPE_SSH;
266 
267 	for (;;) {
268 		rc = getopt(argc, argv, "c:e:i:");
269 		if (rc == -1)
270 			break;
271 
272 		switch (rc) {
273 		case 'c':
274 			if (optarg[0] == '\0') {
275 				fprintf(stderr, "Config str cannot be empty\n");
276 				return EXIT_FAILURE;
277 			}
278 			config_path = optarg;
279 			break;
280 		case 'e':
281 			if (optarg[0] == '\0') {
282 				fprintf(stderr, "Escape str cannot be empty\n");
283 				return EXIT_FAILURE;
284 			}
285 			esc = (const uint8_t *)optarg;
286 			break;
287 		case 'i':
288 			if (optarg[0] == '\0') {
289 				fprintf(stderr,
290 					"Socket ID str cannot be empty\n");
291 				return EXIT_FAILURE;
292 			}
293 			socket_id = optarg;
294 			break;
295 		default:
296 			fprintf(stderr,
297 				"Usage: %s "
298 				"[-e <escape sequence>]"
299 				"[-i <socket ID>]"
300 				"[-c <config>]\n",
301 				argv[0]);
302 			return EXIT_FAILURE;
303 		}
304 	}
305 
306 	if (config_path) {
307 		config = config_init(config_path);
308 		if (!config) {
309 			warnx("Can't read configuration, exiting.");
310 			return EXIT_FAILURE;
311 		}
312 
313 		if (!esc)
314 			esc = (const uint8_t *)config_get_value(
315 				config, "escape-sequence");
316 
317 		if (!socket_id)
318 			socket_id = config_get_value(config, "socket-id");
319 	}
320 
321 	if (esc) {
322 		client->esc_type = ESC_TYPE_STR;
323 		client->esc_state.str.str = esc;
324 	}
325 
326 	rc = client_init(client, socket_id);
327 	if (rc)
328 		goto out_config_fini;
329 
330 	rc = client_tty_init(client);
331 	if (rc)
332 		goto out_client_fini;
333 
334 	for (;;) {
335 		pollfds[0].fd = client->fd_in;
336 		pollfds[0].events = POLLIN;
337 		pollfds[1].fd = client->console_sd;
338 		pollfds[1].events = POLLIN;
339 
340 		rc = poll(pollfds, 2, -1);
341 		if (rc < 0) {
342 			warn("Poll failure");
343 			break;
344 		}
345 
346 		if (pollfds[0].revents)
347 			prc = process_tty(client);
348 
349 		if (prc == PROCESS_OK && pollfds[1].revents)
350 			prc = process_console(client);
351 
352 		rc = (prc == PROCESS_ERR) ? -1 : 0;
353 		if (prc != PROCESS_OK)
354 			break;
355 	}
356 
357 out_client_fini:
358 	client_fini(client);
359 
360 out_config_fini:
361 	if (config_path)
362 		config_fini(config);
363 
364 	if (prc == PROCESS_ESC)
365 		return EXIT_ESCAPE;
366 	return rc ? EXIT_FAILURE : EXIT_SUCCESS;
367 }
368