1/*
2 * Dropbear - a SSH2 server
3 *
4 * Copyright (c) 2002,2003 Matt Johnston
5 * All rights reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE. */
24
25/* This file (agentfwd.c) handles authentication agent forwarding, for OpenSSH
26 * style agents. */
27
28#include "includes.h"
29
30#ifndef DISABLE_AGENTFWD
31
32#include "agentfwd.h"
33#include "session.h"
34#include "ssh.h"
35#include "dbutil.h"
36#include "chansession.h"
37#include "channel.h"
38#include "packet.h"
39#include "buffer.h"
40#include "random.h"
41#include "listener.h"
42
43#define AGENTDIRPREFIX "/tmp/dropbear-"
44
45static int send_msg_channel_open_agent(int fd);
46static int bindagent(int fd, struct ChanSess * chansess);
47static void agentaccept(struct Listener * listener, int sock);
48
49/* Handles client requests to start agent forwarding, sets up listening socket.
50 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
51int agentreq(struct ChanSess * chansess) {
52
53	int fd;
54
55	if (chansess->agentlistener != NULL) {
56		return DROPBEAR_FAILURE;
57	}
58
59	/* create listening socket */
60	fd = socket(PF_UNIX, SOCK_STREAM, 0);
61	if (fd < 0) {
62		goto fail;
63	}
64
65	/* create the unix socket dir and file */
66	if (bindagent(fd, chansess) == DROPBEAR_FAILURE) {
67		goto fail;
68	}
69
70	/* listen */
71	if (listen(fd, 20) < 0) {
72		goto fail;
73	}
74
75	/* set non-blocking */
76	setnonblocking(fd);
77
78	/* pass if off to listener */
79	chansess->agentlistener = new_listener( &fd, 1, 0, chansess,
80								agentaccept, NULL);
81
82	if (chansess->agentlistener == NULL) {
83		goto fail;
84	}
85
86	return DROPBEAR_SUCCESS;
87
88fail:
89	/* cleanup */
90	agentcleanup(chansess);
91
92	return DROPBEAR_FAILURE;
93}
94
95/* accepts a connection on the forwarded socket and opens a new channel for it
96 * back to the client */
97/* returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
98static void agentaccept(struct Listener *UNUSED(listener), int sock) {
99
100	int fd;
101
102	fd = accept(sock, NULL, NULL);
103	if (fd < 0) {
104		TRACE(("accept failed"))
105		return;
106	}
107
108	if (send_msg_channel_open_agent(fd) != DROPBEAR_SUCCESS) {
109		close(fd);
110	}
111
112}
113
114/* set up the environment variable pointing to the socket. This is called
115 * just before command/shell execution, after dropping priveleges */
116void agentset(struct ChanSess * chansess) {
117
118	char *path = NULL;
119	int len;
120
121	if (chansess->agentlistener == NULL) {
122		return;
123	}
124
125	/* 2 for "/" and "\0" */
126	len = strlen(chansess->agentdir) + strlen(chansess->agentfile) + 2;
127
128	path = m_malloc(len);
129	snprintf(path, len, "%s/%s", chansess->agentdir, chansess->agentfile);
130	addnewvar("SSH_AUTH_SOCK", path);
131	m_free(path);
132}
133
134/* close the socket, remove the socket-file */
135void agentcleanup(struct ChanSess * chansess) {
136
137	char *path = NULL;
138	uid_t uid;
139	gid_t gid;
140	int len;
141
142	if (chansess->agentlistener != NULL) {
143		remove_listener(chansess->agentlistener);
144		chansess->agentlistener = NULL;
145	}
146
147	if (chansess->agentfile != NULL && chansess->agentdir != NULL) {
148
149		/* Remove the dir as the user. That way they can't cause problems except
150		 * for themselves */
151		uid = getuid();
152		gid = getgid();
153		if ((setegid(ses.authstate.pw->pw_gid)) < 0 ||
154			(seteuid(ses.authstate.pw->pw_uid)) < 0) {
155			dropbear_exit("failed to set euid");
156		}
157
158		/* 2 for "/" and "\0" */
159		len = strlen(chansess->agentdir) + strlen(chansess->agentfile) + 2;
160
161		path = m_malloc(len);
162		snprintf(path, len, "%s/%s", chansess->agentdir, chansess->agentfile);
163		unlink(path);
164		m_free(path);
165
166		rmdir(chansess->agentdir);
167
168		if ((seteuid(uid)) < 0 ||
169			(setegid(gid)) < 0) {
170			dropbear_exit("failed to revert euid");
171		}
172
173		m_free(chansess->agentfile);
174		m_free(chansess->agentdir);
175	}
176
177}
178
179static const struct ChanType chan_agent = {
180	0, /* sepfds */
181	"auth-agent@openssh.com",
182	NULL,
183	NULL,
184	NULL,
185	NULL
186};
187
188
189/* helper for accepting an agent request */
190static int send_msg_channel_open_agent(int fd) {
191
192	if (send_msg_channel_open_init(fd, &chan_agent) == DROPBEAR_SUCCESS) {
193		encrypt_packet();
194		return DROPBEAR_SUCCESS;
195	} else {
196		return DROPBEAR_FAILURE;
197	}
198}
199
200/* helper for creating the agent socket-file
201   returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
202static int bindagent(int fd, struct ChanSess * chansess) {
203
204	struct sockaddr_un addr;
205	unsigned int prefix;
206	char path[sizeof(addr.sun_path)], sockfile[sizeof(addr.sun_path)];
207	mode_t mode;
208	int i;
209	uid_t uid;
210	gid_t gid;
211	int ret = DROPBEAR_FAILURE;
212
213	/* drop to user privs to make the dir/file */
214	uid = getuid();
215	gid = getgid();
216	if ((setegid(ses.authstate.pw->pw_gid)) < 0 ||
217		(seteuid(ses.authstate.pw->pw_uid)) < 0) {
218		dropbear_exit("failed to set euid");
219	}
220
221	memset((void*)&addr, 0x0, sizeof(addr));
222	addr.sun_family = AF_UNIX;
223
224	mode = S_IRWXU;
225
226	for (i = 0; i < 20; i++) {
227		genrandom((unsigned char*)&prefix, sizeof(prefix));
228		/* we want 32 bits (8 hex digits) - "/tmp/dropbear-f19c62c0" */
229		snprintf(path, sizeof(path), AGENTDIRPREFIX "%.8x", prefix);
230
231		if (mkdir(path, mode) == 0) {
232			goto bindsocket;
233		}
234		if (errno != EEXIST) {
235			break;
236		}
237	}
238	/* couldn't make a dir */
239	goto out;
240
241bindsocket:
242	/* Format is "/tmp/dropbear-0246dead/auth-d00f7654-23".
243	 * The "23" is the file desc, the random data is to avoid collisions
244	 * between subsequent user processes reusing socket fds (odds are now
245	 * 1/(2^64) */
246	genrandom((unsigned char*)&prefix, sizeof(prefix));
247	snprintf(sockfile, sizeof(sockfile), "auth-%.8x-%d", prefix, fd);
248
249	snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", path, sockfile);
250
251	if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
252		chansess->agentdir = m_strdup(path);
253		chansess->agentfile = m_strdup(sockfile);
254		ret = DROPBEAR_SUCCESS;
255	}
256
257
258out:
259	if ((seteuid(uid)) < 0 ||
260		(setegid(gid)) < 0) {
261		dropbear_exit("failed to revert euid");
262	}
263	return ret;
264}
265
266#endif
267