From 1d5aa8cc977f278f30bbc01105bf7ff66cd49596 Mon Sep 17 00:00:00 2001 From: Ben Cohen Date: Sun, 3 Sep 2017 20:54:38 +0100 Subject: [PATCH] Forward ssh-agent data between ssh clients and RDP Add xrdp-ssh-agent.c which forwards ssh-agent protocol over an RDP dynamic virtual channel, just as the normal ssh-agent forwards it over an SSH channel. Usage: Run an RDP client with the corresponding plugin enabled; for example "xfreerdp /ssh-agent ...". In the remote desktop session run xrdp-ssh-agent and evaluate the output in the shell as for ssh-agent to set the required environment variables (specifically $SSH_AUTH_SOCK): eval "$(xrdp-ssh-agent -s)" This is the same as for the normal ssh-agent. You would typically do this in your Xsession or /etc/xrdp/startwm.sh. Limitations: 1. Error checking and handling could be improved. 2. This stays running when the xrdp session closes. This should be fixed using a command line argument as for the real ssh-agent. 3. This is only tested on Linux and will only work on systems where clients talk to the ssh-agent via Unix domain sockets. It won't currently work on Windows but it could be ported. --- xrdpapi/xrdp-ssh-agent.c | 398 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 xrdpapi/xrdp-ssh-agent.c diff --git a/xrdpapi/xrdp-ssh-agent.c b/xrdpapi/xrdp-ssh-agent.c new file mode 100644 index 00000000..b62c4f99 --- /dev/null +++ b/xrdpapi/xrdp-ssh-agent.c @@ -0,0 +1,398 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2012-2013 + * Copyright (C) Laxmikant Rashinkar 2012-2013 + * Copyright (C) Ben Cohen 2017 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Portions are from OpenSSH, under the following license: + * + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * The authentication agent program. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + * + * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * xrdp-ssh-agent.c: program to forward ssh-agent protocol from xrdp session + * + * This performs the equivalent function of ssh-agent on a server you connect + * to via ssh, but the ssh-agent protocol is over an RDP dynamic virtual + * channel and not an SSH channel. + * + * This will print out variables to set in your environment (specifically, + * $SSH_AUTH_SOCK) for ssh clients to find the agent's socket, then it will + * run in the background. This is suitable to run just as you would run the + * normal ssh-agent, e.g. in your Xsession or /etc/xrdp/startwm.sh. + * + * Your RDP client needs to be running a compatible client-side plugin + * that can see a local ssh-agent. + * + * usage (from within an xrdp session): + * xrdp-ssh-agent + * + * build instructions: + * gcc xrdp-ssh-agent.c -o xrdp-ssh-agent -L./.libs -lxrdpapi -Wall + * + * protocol specification: + * Forward data verbatim over RDP dynamic virtual channel named "sshagent" + * between a ssh client on the xrdp server and the real ssh-agent where + * the RDP client is running. Each connection by a separate client to + * xrdp-ssh-agent gets a separate DVC invocation. + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#ifdef __WIN32__ +#include +#endif + +#include "xrdpapi.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _PATH_DEVNULL "/dev/null" + +char socket_name[PATH_MAX]; +char socket_dir[PATH_MAX]; +static int sa_uds_fd = -1; +static int is_going = 1; + + +/* Make a template filename for mk[sd]temp() */ +/* This is from mktemp_proto() in misc.c from openssh */ +void +mktemp_proto(char *s, size_t len) +{ + const char *tmpdir; + int r; + + if ((tmpdir = getenv("TMPDIR")) != NULL) { + r = snprintf(s, len, "%s/ssh-XXXXXXXXXXXX", tmpdir); + if (r > 0 && (size_t)r < len) + return; + } + r = snprintf(s, len, "/tmp/ssh-XXXXXXXXXXXX"); + if (r < 0 || (size_t)r >= len) + { + fprintf(stderr, "%s: template string too short", __func__); + exit(1); + } +} + + +/* This uses parts of main() in ssh-agent.c from openssh */ +static void +setup_ssh_agent(struct sockaddr_un *addr) +{ + int rc; + + /* Create private directory for agent socket */ + mktemp_proto(socket_dir, sizeof(socket_dir)); + if (mkdtemp(socket_dir) == NULL) { + perror("mkdtemp: private socket dir"); + exit(1); + } + snprintf(socket_name, sizeof socket_name, "%s/agent.%ld", socket_dir, + (long)getpid()); + + /* Create unix domain socket */ + unlink(socket_name); + + sa_uds_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sa_uds_fd == -1) + { + fprintf(stderr, "sshagent: socket creation failed"); + exit(2); + } + + memset(addr, 0, sizeof(struct sockaddr_un)); + addr->sun_family = AF_UNIX; + strncpy(addr->sun_path, socket_name, sizeof(addr->sun_path)); + addr->sun_path[sizeof(addr->sun_path) - 1] = 0; + + /* Create with privileges rw------- so other users can't access the UDS */ + mode_t umask_sav = umask(0177); + rc = bind(sa_uds_fd, (struct sockaddr *)addr, sizeof(struct sockaddr_un)); + if (rc != 0) + { + fprintf(stderr, "sshagent: bind failed"); + close(sa_uds_fd); + unlink(socket_name); + exit(3); + } + umask(umask_sav); + + rc = listen(sa_uds_fd, /* backlog = */ 5); + if (rc != 0) + { + fprintf(stderr, "listen failed\n"); + close(sa_uds_fd); + unlink(socket_name); + exit(1); + } + + /* Now fork: the child becomes the ssh-agent daemon and the parent prints + * out the pid and socket name. */ + pid_t pid = fork(); + if (pid == -1) + { + perror("fork"); + exit(1); + } + else if (pid != 0) + { + /* Parent */ + close(sa_uds_fd); + printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n", socket_name); + printf("SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n", pid); + printf("echo Agent pid %d;\n", pid); + exit(0); + } + + /* Child */ + + if (setsid() == -1) + { + fprintf(stderr, "setsid failed"); + exit(1); + } + + (void)chdir("/"); + int devnullfd; + if ((devnullfd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + /* XXX might close listen socket */ + (void)dup2(devnullfd, STDIN_FILENO); + (void)dup2(devnullfd, STDOUT_FILENO); + (void)dup2(devnullfd, STDERR_FILENO); + if (devnullfd > 2) + close(devnullfd); + } + + /* deny core dumps, since memory contains unencrypted private keys */ + struct rlimit rlim; + rlim.rlim_cur = rlim.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rlim) < 0) { + fprintf(stderr, "setrlimit RLIMIT_CORE: %s", strerror(errno)); + exit(1); + } +} + + +static void +handle_connection(int client_fd) +{ + int rdp_fd = -1; + int rc; + void *channel = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, + "SSHAGENT", + WTS_CHANNEL_OPTION_DYNAMIC_PRI_MED); + if (channel == NULL) + { + fprintf(stderr, "WTSVirtualChannelOpenEx() failed\n"); + } + + unsigned int retlen; + int *retdata; + rc = WTSVirtualChannelQuery(channel, + WTSVirtualFileHandle, + (void **)&retdata, + &retlen); + if (!rc) + { + fprintf(stderr, "WTSVirtualChannelQuery() failed\n"); + } + if (retlen != sizeof(rdp_fd)) + { + fprintf(stderr, "WTSVirtualChannelQuery() returned wrong length %d\n", + retlen); + } + rdp_fd = *retdata; + + int client_going = 1; + while (client_going) + { + /* Wait for data from RDP or the client */ + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(client_fd, &readfds); + FD_SET(rdp_fd, &readfds); + select(FD_SETSIZE, &readfds, NULL, NULL, NULL); + + if (FD_ISSET(rdp_fd, &readfds)) + { + /* Read from RDP and write to the client */ + char buffer[4096]; + unsigned int bytes_to_write; + rc = WTSVirtualChannelRead(channel, + /* TimeOut = */ 5000, + buffer, + sizeof(buffer), + &bytes_to_write); + if (rc == 1) + { + char *pos = buffer; + while (bytes_to_write > 0) + { + int bytes_written = send(client_fd, pos, bytes_to_write, 0); + + if (bytes_written > 0) + { + bytes_to_write -= bytes_written; + pos += bytes_written; + } + else if (bytes_written == 0) + { + fprintf(stderr, "send() returned 0!\n"); + } + else + { + /* Error */ + fprintf(stderr, "Error %d on recv\n", errno); + client_going = 0; + } + } + } + else + { + /* Error */ + fprintf(stderr, "WTSVirtualChannelRead() failed: %d\n", errno); + client_going = 0; + } + } + + if (FD_ISSET(client_fd, &readfds)) + { + /* Read from the client and write to RDP */ + char buffer[4096]; + ssize_t bytes_to_write = recv(client_fd, buffer, sizeof(buffer), 0); + if (bytes_to_write > 0) + { + char *pos = buffer; + while (bytes_to_write > 0) + { + unsigned int bytes_written; + int rc = WTSVirtualChannelWrite(channel, + pos, + bytes_to_write, + &bytes_written); + if (rc == 0) + { + fprintf(stderr, "WTSVirtualChannelWrite() failed: %d\n", + errno); + client_going = 0; + } + else + { + bytes_to_write -= bytes_written; + pos += bytes_written; + } + } + } + else if (bytes_to_write == 0) + { + /* Client has closed connection */ + client_going = 0; + } + else + { + /* Error */ + fprintf(stderr, "Error %d on recv\n", errno); + client_going = 0; + } + } + } + WTSVirtualChannelClose(channel); +} + + +int +main(int argc, char **argv) +{ + /* Setup the Unix domain socket and daemon process */ + struct sockaddr_un addr; + setup_ssh_agent(&addr); + + /* Wait for a client to connect to the socket */ + while (is_going) + { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sa_uds_fd, &readfds); + select(FD_SETSIZE, &readfds, NULL, NULL, NULL); + + /* If something connected then get it... + * (You can test this using "socat - UNIX-CONNECT:".) */ + if (FD_ISSET(sa_uds_fd, &readfds)) + { + socklen_t addrsize = sizeof(addr); + int client_fd = accept(sa_uds_fd, + (struct sockaddr*)&addr, + &addrsize); + handle_connection(client_fd); + close(client_fd); + } + } + + close(sa_uds_fd); + unlink(socket_name); + + return 0; +} + +/* vim: set sw=4:ts=4:et: */