1 /*
2 * remote_ssh_helper.c: a netcat replacement for proxying ssh tunnel to daemon
3 *
4 * Copyright (C) 2020 Red Hat, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see
18 * <http://www.gnu.org/licenses/>.
19 */
20
21 #include <config.h>
22
23 #include <unistd.h>
24
25 #include "rpc/virnetsocket.h"
26 #include "viralloc.h"
27 #include "virlog.h"
28 #include "virgettext.h"
29 #include "virfile.h"
30
31 #include "remote_sockets.h"
32
33 #define VIR_FROM_THIS VIR_FROM_REMOTE
34
35 #define SSH_BUF_SIZE (1024 * 1024)
36
37 VIR_LOG_INIT("remote.remote_ssh_helper");
38
39 struct virRemoteSSHHelperBuffer {
40 size_t length;
41 size_t offset;
42 char *data;
43 };
44
45 typedef struct virRemoteSSHHelper virRemoteSSHHelper;
46 struct virRemoteSSHHelper {
47 bool quit;
48 virNetSocket *sock;
49 int sockEvents;
50 int stdinWatch;
51 int stdinEvents;
52 int stdoutWatch;
53 int stdoutEvents;
54
55 struct virRemoteSSHHelperBuffer sockToTerminal;
56 struct virRemoteSSHHelperBuffer terminalToSock;
57 };
58
59
60 static void
virRemoteSSHHelperShutdown(virRemoteSSHHelper * proxy)61 virRemoteSSHHelperShutdown(virRemoteSSHHelper *proxy)
62 {
63 if (proxy->sock) {
64 virNetSocketRemoveIOCallback(proxy->sock);
65 virNetSocketClose(proxy->sock);
66 virObjectUnref(proxy->sock);
67 proxy->sock = NULL;
68 }
69 VIR_FREE(proxy->sockToTerminal.data);
70 VIR_FREE(proxy->terminalToSock.data);
71 if (proxy->stdinWatch != -1)
72 virEventRemoveHandle(proxy->stdinWatch);
73 if (proxy->stdoutWatch != -1)
74 virEventRemoveHandle(proxy->stdoutWatch);
75 proxy->stdinWatch = -1;
76 proxy->stdoutWatch = -1;
77 if (!proxy->quit)
78 proxy->quit = true;
79 }
80
81
82 static void
virRemoteSSHHelperUpdateEvents(virRemoteSSHHelper * proxy)83 virRemoteSSHHelperUpdateEvents(virRemoteSSHHelper *proxy)
84 {
85 int sockEvents = 0;
86 int stdinEvents = 0;
87 int stdoutEvents = 0;
88
89 if (proxy->terminalToSock.offset != 0)
90 sockEvents |= VIR_EVENT_HANDLE_WRITABLE;
91 if (proxy->terminalToSock.offset < proxy->terminalToSock.length)
92 stdinEvents |= VIR_EVENT_HANDLE_READABLE;
93
94 if (proxy->sockToTerminal.offset != 0)
95 stdoutEvents |= VIR_EVENT_HANDLE_WRITABLE;
96 if (proxy->sockToTerminal.offset < proxy->sockToTerminal.length)
97 sockEvents |= VIR_EVENT_HANDLE_READABLE;
98
99 if (sockEvents != proxy->sockEvents) {
100 VIR_DEBUG("Update sock events %d -> %d", proxy->sockEvents, sockEvents);
101 virNetSocketUpdateIOCallback(proxy->sock, sockEvents);
102 proxy->sockEvents = sockEvents;
103 }
104 if (stdinEvents != proxy->stdinEvents) {
105 VIR_DEBUG("Update stdin events %d -> %d", proxy->stdinEvents, stdinEvents);
106 virEventUpdateHandle(proxy->stdinWatch, stdinEvents);
107 proxy->stdinEvents = stdinEvents;
108 }
109 if (stdoutEvents != proxy->stdoutEvents) {
110 VIR_DEBUG("Update stdout events %d -> %d", proxy->stdoutEvents, stdoutEvents);
111 virEventUpdateHandle(proxy->stdoutWatch, stdoutEvents);
112 proxy->stdoutEvents = stdoutEvents;
113 }
114 }
115
116 static void
virRemoteSSHHelperEventOnSocket(virNetSocket * sock,int events,void * opaque)117 virRemoteSSHHelperEventOnSocket(virNetSocket *sock,
118 int events,
119 void *opaque)
120 {
121 virRemoteSSHHelper *proxy = opaque;
122
123 /* we got late event after proxy was shutdown */
124 if (!proxy->sock)
125 return;
126
127 if (events & VIR_EVENT_HANDLE_READABLE) {
128 size_t avail = proxy->sockToTerminal.length -
129 proxy->sockToTerminal.offset;
130 int got;
131
132 if (avail == 0) {
133 VIR_DEBUG("Unexpectedly called with no space in buffer");
134 goto cleanup;
135 }
136
137 got = virNetSocketRead(sock,
138 proxy->sockToTerminal.data +
139 proxy->sockToTerminal.offset,
140 avail);
141 if (got == -2)
142 return; /* blocking */
143 if (got == 0) {
144 VIR_DEBUG("EOF on socket, shutting down");
145 virRemoteSSHHelperShutdown(proxy);
146 return;
147 }
148 if (got < 0) {
149 virRemoteSSHHelperShutdown(proxy);
150 return;
151 }
152 proxy->sockToTerminal.offset += got;
153 }
154
155 if (events & VIR_EVENT_HANDLE_WRITABLE &&
156 proxy->terminalToSock.offset) {
157 ssize_t done;
158 done = virNetSocketWrite(proxy->sock,
159 proxy->terminalToSock.data,
160 proxy->terminalToSock.offset);
161 if (done == -2)
162 return; /* blocking */
163 if (done < 0) {
164 virRemoteSSHHelperShutdown(proxy);
165 return;
166 }
167
168 memmove(proxy->terminalToSock.data,
169 proxy->terminalToSock.data + done,
170 proxy->terminalToSock.offset - done);
171 proxy->terminalToSock.offset -= done;
172 }
173
174 if (events & VIR_EVENT_HANDLE_ERROR ||
175 events & VIR_EVENT_HANDLE_HANGUP) {
176 virRemoteSSHHelperShutdown(proxy);
177 return;
178 }
179
180 cleanup:
181 virRemoteSSHHelperUpdateEvents(proxy);
182 }
183
184
185 static void
virRemoteSSHHelperEventOnStdin(int watch G_GNUC_UNUSED,int fd G_GNUC_UNUSED,int events,void * opaque)186 virRemoteSSHHelperEventOnStdin(int watch G_GNUC_UNUSED,
187 int fd G_GNUC_UNUSED,
188 int events,
189 void *opaque)
190 {
191 virRemoteSSHHelper *proxy = opaque;
192
193 /* we got late event after console was shutdown */
194 if (!proxy->sock)
195 return;
196
197 if (events & VIR_EVENT_HANDLE_READABLE) {
198 size_t avail = proxy->terminalToSock.length -
199 proxy->terminalToSock.offset;
200 int got;
201
202 if (avail == 0) {
203 VIR_DEBUG("Unexpectedly called with no space in buffer");
204 goto cleanup;
205 }
206
207 got = read(fd,
208 proxy->terminalToSock.data +
209 proxy->terminalToSock.offset,
210 avail);
211 if (got < 0) {
212 if (errno != EAGAIN) {
213 virReportSystemError(errno, "%s", _("cannot read from stdin"));
214 virRemoteSSHHelperShutdown(proxy);
215 }
216 return;
217 }
218 if (got == 0) {
219 VIR_DEBUG("EOF on stdin, shutting down");
220 virRemoteSSHHelperShutdown(proxy);
221 return;
222 }
223
224 proxy->terminalToSock.offset += got;
225 }
226
227 if (events & VIR_EVENT_HANDLE_ERROR) {
228 virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IO error on stdin"));
229 virRemoteSSHHelperShutdown(proxy);
230 return;
231 }
232
233 if (events & VIR_EVENT_HANDLE_HANGUP) {
234 virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdin"));
235 virRemoteSSHHelperShutdown(proxy);
236 return;
237 }
238
239 cleanup:
240 virRemoteSSHHelperUpdateEvents(proxy);
241 }
242
243
244 static void
virRemoteSSHHelperEventOnStdout(int watch G_GNUC_UNUSED,int fd,int events,void * opaque)245 virRemoteSSHHelperEventOnStdout(int watch G_GNUC_UNUSED,
246 int fd,
247 int events,
248 void *opaque)
249 {
250 virRemoteSSHHelper *proxy = opaque;
251
252 /* we got late event after console was shutdown */
253 if (!proxy->sock)
254 return;
255
256 if (events & VIR_EVENT_HANDLE_WRITABLE &&
257 proxy->sockToTerminal.offset) {
258 ssize_t done;
259 done = write(fd,
260 proxy->sockToTerminal.data,
261 proxy->sockToTerminal.offset);
262 if (done < 0) {
263 if (errno != EAGAIN) {
264 virReportSystemError(errno, "%s", _("cannot write to stdout"));
265 virRemoteSSHHelperShutdown(proxy);
266 }
267 return;
268 }
269 memmove(proxy->sockToTerminal.data,
270 proxy->sockToTerminal.data + done,
271 proxy->sockToTerminal.offset - done);
272 proxy->sockToTerminal.offset -= done;
273 }
274
275 if (events & VIR_EVENT_HANDLE_ERROR) {
276 virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IO error stdout"));
277 virRemoteSSHHelperShutdown(proxy);
278 return;
279 }
280
281 if (events & VIR_EVENT_HANDLE_HANGUP) {
282 virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdout"));
283 virRemoteSSHHelperShutdown(proxy);
284 return;
285 }
286
287 virRemoteSSHHelperUpdateEvents(proxy);
288 }
289
290
291 static int
virRemoteSSHHelperRun(virNetSocket * sock)292 virRemoteSSHHelperRun(virNetSocket *sock)
293 {
294 int ret = -1;
295 virRemoteSSHHelper proxy = {
296 .sock = sock,
297 .sockEvents = VIR_EVENT_HANDLE_READABLE,
298 .stdinWatch = -1,
299 .stdinEvents = VIR_EVENT_HANDLE_READABLE,
300 .stdoutWatch = -1,
301 .stdoutEvents = 0,
302 .sockToTerminal = {
303 .offset = 0,
304 .length = SSH_BUF_SIZE,
305 .data = g_new0(char, SSH_BUF_SIZE),
306 },
307 .terminalToSock = {
308 .offset = 0,
309 .length = SSH_BUF_SIZE,
310 .data = g_new0(char, SSH_BUF_SIZE),
311 },
312 };
313
314 virEventRegisterDefaultImpl();
315
316 if ((proxy.stdinWatch = virEventAddHandle(STDIN_FILENO,
317 VIR_EVENT_HANDLE_READABLE,
318 virRemoteSSHHelperEventOnStdin,
319 &proxy,
320 NULL)) < 0)
321 goto cleanup;
322
323 if ((proxy.stdoutWatch = virEventAddHandle(STDOUT_FILENO,
324 0,
325 virRemoteSSHHelperEventOnStdout,
326 &proxy,
327 NULL)) < 0)
328 goto cleanup;
329
330 if (virNetSocketAddIOCallback(proxy.sock,
331 VIR_EVENT_HANDLE_READABLE,
332 virRemoteSSHHelperEventOnSocket,
333 &proxy,
334 NULL) < 0)
335 goto cleanup;
336
337 while (!proxy.quit)
338 virEventRunDefaultImpl();
339
340 if (virGetLastErrorCode() != VIR_ERR_OK)
341 goto cleanup;
342
343 ret = 0;
344 cleanup:
345 if (proxy.stdinWatch != -1)
346 virEventRemoveHandle(proxy.stdinWatch);
347 if (proxy.stdoutWatch != -1)
348 virEventRemoveHandle(proxy.stdoutWatch);
349 return ret;
350 }
351
main(int argc,char ** argv)352 int main(int argc, char **argv)
353 {
354 const char *uri_str = NULL;
355 g_autoptr(virURI) uri = NULL;
356 g_autofree char *driver = NULL;
357 remoteDriverTransport transport;
358 gboolean version = false;
359 gboolean readonly = false;
360 g_autofree char *sock_path = NULL;
361 g_autofree char *daemon_path = NULL;
362 g_autoptr(virNetSocket) sock = NULL;
363 GError *error = NULL;
364 g_autoptr(GOptionContext) context = NULL;
365 GOptionEntry entries[] = {
366 { "readonly", 'r', 0, G_OPTION_ARG_NONE, &readonly, "Connect read-only", NULL },
367 { "version", 'V', 0, G_OPTION_ARG_NONE, &version, "Display version information", NULL },
368 { NULL, '\0', 0, 0, NULL, NULL, NULL }
369 };
370 unsigned int flags;
371
372 context = g_option_context_new("- libvirt socket proxy");
373 g_option_context_add_main_entries(context, entries, PACKAGE);
374 if (!g_option_context_parse(context, &argc, &argv, &error)) {
375 g_printerr(_("option parsing failed: %s\n"), error->message);
376 exit(EXIT_FAILURE);
377 }
378
379 if (version) {
380 g_print("%s (%s) %s\n", argv[0], PACKAGE_NAME, PACKAGE_VERSION);
381 exit(EXIT_SUCCESS);
382 }
383
384 virSetErrorFunc(NULL, NULL);
385 virSetErrorLogPriorityFunc(NULL);
386
387 if (virGettextInitialize() < 0 ||
388 virErrorInitialize() < 0) {
389 g_printerr(_("%s: initialization failed\n"), argv[0]);
390 exit(EXIT_FAILURE);
391 }
392
393 virFileActivateDirOverrideForProg(argv[0]);
394
395 /* Initialize the log system */
396 virLogSetFromEnv();
397
398 if (optind != (argc - 1)) {
399 g_printerr("%s: expected a URI\n", argv[0]);
400 exit(EXIT_FAILURE);
401 }
402
403 uri_str = argv[optind];
404 VIR_DEBUG("Using URI %s", uri_str);
405
406 if (!(uri = virURIParse(uri_str))) {
407 g_printerr(("%s: cannot parse '%s': %s\n"),
408 argv[0], uri_str, virGetLastErrorMessage());
409 exit(EXIT_FAILURE);
410 }
411
412 if (remoteSplitURIScheme(uri, &driver, &transport) < 0) {
413 g_printerr(_("%s: cannot parse URI transport '%s': %s\n"),
414 argv[0], uri_str, virGetLastErrorMessage());
415 exit(EXIT_FAILURE);
416 }
417
418 if (transport != REMOTE_DRIVER_TRANSPORT_UNIX) {
419 g_printerr(_("%s: unexpected URI transport '%s'\n"),
420 argv[0], uri_str);
421 exit(EXIT_FAILURE);
422 }
423
424 remoteGetURIDaemonInfo(uri, transport, &flags);
425 if (readonly)
426 flags |= REMOTE_DRIVER_OPEN_RO;
427
428 sock_path = remoteGetUNIXSocket(transport,
429 REMOTE_DRIVER_MODE_AUTO,
430 driver,
431 flags,
432 &daemon_path);
433
434 if (virNetSocketNewConnectUNIX(sock_path, daemon_path, &sock) < 0) {
435 g_printerr(_("%s: cannot connect to '%s': %s\n"),
436 argv[0], sock_path, virGetLastErrorMessage());
437 exit(EXIT_FAILURE);
438 }
439
440 if (virRemoteSSHHelperRun(sock) < 0) {
441 g_printerr(_("%s: could not proxy traffic: %s\n"),
442 argv[0], virGetLastErrorMessage());
443 exit(EXIT_FAILURE);
444 }
445
446 exit(EXIT_SUCCESS);
447 }
448