Blame channels/sshagent/server/sshagent_main.c

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