uxc: integrate console into uxc
[project/procd.git] / jail / console.c
1 /*
2 * Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License version 2.1
6 * as published by the Free Software Foundation
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14 #ifndef _GNU_SOURCE
15 #define _GNU_SOURCE
16 #endif
17
18 #include <stdlib.h>
19 #include <fcntl.h>
20 #include <libubox/ustream.h>
21 #include <libubus.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <sys/types.h>
29 #include <termios.h>
30
31 static inline int setup_tios(int fd, struct termios *oldtios)
32 {
33 struct termios newtios;
34
35 if (!isatty(fd)) {
36 return -1;
37 }
38
39 /* Get current termios */
40 if (tcgetattr(fd, oldtios))
41 return -1;
42
43 newtios = *oldtios;
44
45 /* We use the same settings that ssh does. */
46 newtios.c_iflag |= IGNPAR;
47 newtios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
48 newtios.c_lflag &= ~(TOSTOP | ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
49 newtios.c_oflag &= ~ONLCR;
50 newtios.c_oflag |= OPOST;
51 newtios.c_cc[VMIN] = 1;
52 newtios.c_cc[VTIME] = 0;
53
54 /* Set new attributes */
55 if (tcsetattr(fd, TCSAFLUSH, &newtios))
56 return -1;
57
58 return 0;
59 }
60
61
62
63 #define OPT_ARGS "i:c:"
64
65 static struct ustream_fd cufd;
66 static struct ustream_fd lufd;
67
68 static void usage()
69 {
70 fprintf(stderr, "ujail-console -c <container> [-i <instance>]\n");
71 exit(1);
72 }
73
74 static void client_cb(struct ustream *s, int bytes)
75 {
76 char *buf;
77 int len, rv;
78
79 do {
80 buf = ustream_get_read_buf(s, &len);
81 if (!buf)
82 break;
83
84 rv = ustream_write(&lufd.stream, buf, len, false);
85
86 if (rv > 0)
87 ustream_consume(s, rv);
88
89 if (rv <= len)
90 break;
91 } while(1);
92 }
93
94 static void local_cb(struct ustream *s, int bytes)
95 {
96 char *buf;
97 int len, rv;
98
99 do {
100 buf = ustream_get_read_buf(s, &len);
101 if (!buf)
102 break;
103
104 if ((len > 0) && (buf[0] == 2))
105 uloop_end();
106
107 rv = ustream_write(&cufd.stream, buf, len, false);
108
109 if (rv > 0)
110 ustream_consume(s, rv);
111
112 if (rv <= len)
113 break;
114 } while(1);
115 }
116
117 int main(int argc, char **argv)
118 {
119 struct ubus_context *ctx;
120 uint32_t id;
121 static struct blob_buf req;
122 char *container_name = NULL, *instance_name = NULL;
123 int client_fd, server_fd, tty_fd;
124 struct termios oldtermios;
125 int ch;
126
127 while ((ch = getopt(argc, argv, OPT_ARGS)) != -1) {
128 switch (ch) {
129 case 'i':
130 instance_name = optarg;
131 break;
132 case 'c':
133 container_name = optarg;
134 break;
135 default:
136 usage();
137 }
138 }
139
140 if (!container_name)
141 usage();
142
143 ctx = ubus_connect(NULL);
144 if (!ctx) {
145 fprintf(stderr, "can't connect to ubus!\n");
146 return -1;
147 }
148
149 /* open pseudo-terminal pair */
150 client_fd = posix_openpt(O_RDWR | O_NOCTTY);
151 if (client_fd < 0) {
152 fprintf(stderr, "can't create virtual console!\n");
153 ubus_free(ctx);
154 return -1;
155 }
156 setup_tios(client_fd, &oldtermios);
157 grantpt(client_fd);
158 unlockpt(client_fd);
159 server_fd = open(ptsname(client_fd), O_RDWR | O_NOCTTY);
160 if (server_fd < 0) {
161 fprintf(stderr, "can't open virtual console!\n");
162 close(client_fd);
163 ubus_free(ctx);
164 return -1;
165 }
166 setup_tios(server_fd, &oldtermios);
167
168 tty_fd = open("/dev/tty", O_RDWR);
169 if (tty_fd < 0) {
170 fprintf(stderr, "can't open local console!\n");
171 close(server_fd);
172 close(client_fd);
173 ubus_free(ctx);
174 return -1;
175 }
176 setup_tios(tty_fd, &oldtermios);
177
178 /* register server-side with procd */
179 blob_buf_init(&req, 0);
180 blobmsg_add_string(&req, "name", container_name);
181 if (instance_name)
182 blobmsg_add_string(&req, "instance", instance_name);
183
184 if (ubus_lookup_id(ctx, "container", &id) ||
185 ubus_invoke_fd(ctx, id, "console_attach", req.head, NULL, NULL, 3000, server_fd)) {
186 fprintf(stderr, "ubus request failed\n");
187 close(server_fd);
188 close(client_fd);
189 blob_buf_free(&req);
190 ubus_free(ctx);
191 return -2;
192 }
193
194 close(server_fd);
195 blob_buf_free(&req);
196 ubus_free(ctx);
197
198 uloop_init();
199
200 /* forward between stdio and client_fd until detach is requested */
201 lufd.stream.notify_read = local_cb;
202 ustream_fd_init(&lufd, tty_fd);
203
204 cufd.stream.notify_read = client_cb;
205 /* ToDo: handle remote close and other events */
206 // cufd.stream.notify_state = client_state_cb;
207 ustream_fd_init(&cufd, client_fd);
208
209 fprintf(stderr, "attaching to jail console. press [CTRL]+[B] to exit.\n");
210 close(0);
211 close(1);
212 close(2);
213 uloop_run();
214
215 tcsetattr(tty_fd, TCSAFLUSH, &oldtermios);
216 ustream_free(&lufd.stream);
217 ustream_free(&cufd.stream);
218 close(client_fd);
219
220 return 0;
221 }