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