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