Skip to content

Commit 1291242

Browse files
committed
upstream: missing file in previous commit
OpenBSD-Commit-ID: e526c97fcb2fd9f0b7b229720972426ab437d7eb
1 parent 80162f9 commit 1291242

File tree

1 file changed

+329
-0
lines changed

1 file changed

+329
-0
lines changed

misc-agent.c

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
/*
2+
* Copyright (c) 2025 Damien Miller <djm@mindrot.org>
3+
*
4+
* Permission to use, copy, modify, and distribute this software for any
5+
* purpose with or without fee is hereby granted, provided that the above
6+
* copyright notice and this permission notice appear in all copies.
7+
*
8+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15+
*/
16+
17+
#include <sys/types.h>
18+
#include <sys/socket.h>
19+
#include <sys/stat.h>
20+
#include <sys/un.h>
21+
22+
#include <dirent.h>
23+
#include <errno.h>
24+
#include <fcntl.h>
25+
#include <netdb.h>
26+
#include <stdlib.h>
27+
#include <string.h>
28+
#include <unistd.h>
29+
30+
#include "digest.h"
31+
#include "log.h"
32+
#include "misc.h"
33+
#include "pathnames.h"
34+
#include "ssh.h"
35+
#include "xmalloc.h"
36+
37+
/* stuff shared by agent listeners (ssh-agent and sshd agent forwarding) */
38+
39+
#define SOCKET_HOSTNAME_HASHLEN 10 /* length of hostname hash in socket path */
40+
41+
/* used for presenting random strings in unix_listener_tmp and hostname_hash */
42+
static const char presentation_chars[] =
43+
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
44+
45+
/* returns a text-encoded hash of the hostname of specified length (max 64) */
46+
static char *
47+
hostname_hash(size_t len)
48+
{
49+
char hostname[NI_MAXHOST], p[65];
50+
u_char hash[64];
51+
int r;
52+
size_t l, i;
53+
54+
l = ssh_digest_bytes(SSH_DIGEST_SHA512);
55+
if (len > 64) {
56+
error_f("bad length %zu > max %zd", len, l - 1);
57+
return NULL;
58+
}
59+
if (gethostname(hostname, sizeof(hostname)) == -1) {
60+
error_f("gethostname: %s", strerror(errno));
61+
return NULL;
62+
}
63+
if ((r = ssh_digest_memory(SSH_DIGEST_SHA512,
64+
hostname, strlen(hostname), hash, sizeof(hash))) != 0) {
65+
error_fr(r, "ssh_digest_memory");
66+
return NULL;
67+
}
68+
memset(p, '\0', sizeof(p));
69+
for (i = 0; i < l; i++)
70+
p[i] = presentation_chars[
71+
hash[i] % (sizeof(presentation_chars) - 1)];
72+
/* debug3_f("hostname \"%s\" => hash \"%s\"", hostname, p); */
73+
p[len] = '\0';
74+
return xstrdup(p);
75+
}
76+
77+
char *
78+
agent_hostname_hash(void)
79+
{
80+
return hostname_hash(SOCKET_HOSTNAME_HASHLEN);
81+
}
82+
83+
/*
84+
* Creates a unix listener at a mkstemp(3)-style path, e.g. "/dir/sock.XXXXXX"
85+
* Supplied path is modified to the actual one used.
86+
*/
87+
static int
88+
unix_listener_tmp(char *path, int backlog)
89+
{
90+
struct sockaddr_un sunaddr;
91+
int good, sock = -1;
92+
size_t i, xstart;
93+
mode_t prev_mask;
94+
95+
/* Find first 'X' template character back from end of string */
96+
xstart = strlen(path);
97+
while (xstart > 0 && path[xstart - 1] == 'X')
98+
xstart--;
99+
100+
memset(&sunaddr, 0, sizeof(sunaddr));
101+
sunaddr.sun_family = AF_UNIX;
102+
prev_mask = umask(0177);
103+
for (good = 0; !good;) {
104+
sock = -1;
105+
/* Randomise path suffix */
106+
for (i = xstart; path[i] != '\0'; i++) {
107+
path[i] = presentation_chars[
108+
arc4random_uniform(sizeof(presentation_chars)-1)];
109+
}
110+
debug_f("trying path \"%s\"", path);
111+
112+
if (strlcpy(sunaddr.sun_path, path,
113+
sizeof(sunaddr.sun_path)) >= sizeof(sunaddr.sun_path)) {
114+
error_f("path \"%s\" too long for Unix domain socket",
115+
path);
116+
break;
117+
}
118+
119+
if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
120+
error_f("socket: %.100s", strerror(errno));
121+
break;
122+
}
123+
if (bind(sock, (struct sockaddr *)&sunaddr,
124+
sizeof(sunaddr)) == -1) {
125+
if (errno == EADDRINUSE) {
126+
error_f("bind \"%s\": %.100s",
127+
path, strerror(errno));
128+
close(sock);
129+
sock = -1;
130+
continue;
131+
}
132+
error_f("bind \"%s\": %.100s", path, strerror(errno));
133+
break;
134+
}
135+
if (listen(sock, backlog) == -1) {
136+
error_f("listen \"%s\": %s", path, strerror(errno));
137+
break;
138+
}
139+
good = 1;
140+
}
141+
umask(prev_mask);
142+
if (good) {
143+
debug3_f("listening on unix socket \"%s\" as fd=%d",
144+
path, sock);
145+
} else if (sock != -1) {
146+
close(sock);
147+
sock = -1;
148+
}
149+
return sock;
150+
}
151+
152+
/*
153+
* Create a subdirectory under the supplied home directory if it
154+
* doesn't already exist
155+
*/
156+
static int
157+
ensure_mkdir(const char *homedir, const char *subdir)
158+
{
159+
char *path;
160+
161+
xasprintf(&path, "%s/%s", homedir, subdir);
162+
if (mkdir(path, 0700) == 0)
163+
debug("created directory %s", path);
164+
else if (errno != EEXIST) {
165+
error_f("mkdir %s: %s", path, strerror(errno));
166+
return -1;
167+
}
168+
free(path);
169+
return 0;
170+
}
171+
172+
static int
173+
agent_prepare_sockdir(const char *homedir)
174+
{
175+
if (homedir == NULL || *homedir == '\0' ||
176+
ensure_mkdir(homedir, _PATH_SSH_USER_DIR) != 0 ||
177+
ensure_mkdir(homedir, _PATH_SSH_AGENT_SOCKET_DIR) != 0)
178+
return -1;
179+
return 0;
180+
}
181+
182+
183+
/* Get a path template for an agent socket in the user's homedir */
184+
static char *
185+
agent_socket_template(const char *homedir, const char *tag)
186+
{
187+
char *hostnamehash, *ret;
188+
189+
if ((hostnamehash = hostname_hash(SOCKET_HOSTNAME_HASHLEN)) == NULL)
190+
return NULL;
191+
xasprintf(&ret, "%s/%s/s.%s.%s.XXXXXXXXXX",
192+
homedir, _PATH_SSH_AGENT_SOCKET_DIR, hostnamehash, tag);
193+
free(hostnamehash);
194+
return ret;
195+
}
196+
197+
int
198+
agent_listener(const char *homedir, const char *tag, int *sockp, char **pathp)
199+
{
200+
int sock;
201+
char *path;
202+
203+
*sockp = -1;
204+
*pathp = NULL;
205+
206+
if (agent_prepare_sockdir(homedir) != 0)
207+
return -1; /* error already logged */
208+
if ((path = agent_socket_template(homedir, tag)) == NULL)
209+
return -1; /* error already logged */
210+
if ((sock = unix_listener_tmp(path, SSH_LISTEN_BACKLOG)) == -1) {
211+
free(path);
212+
return -1; /* error already logged */
213+
}
214+
/* success */
215+
*sockp = sock;
216+
*pathp = path;
217+
return 0;
218+
}
219+
220+
static int
221+
socket_is_stale(const char *path)
222+
{
223+
int fd, r;
224+
struct sockaddr_un sun;
225+
socklen_t l = sizeof(r);
226+
227+
/* attempt non-blocking connect on socket */
228+
memset(&sun, '\0', sizeof(sun));
229+
sun.sun_family = AF_UNIX;
230+
if (strlcpy(sun.sun_path, path,
231+
sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) {
232+
debug_f("path for \"%s\" too long for sockaddr_un", path);
233+
return 0;
234+
}
235+
if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
236+
error_f("socket: %s", strerror(errno));
237+
return 0;
238+
}
239+
set_nonblock(fd);
240+
/* a socket without a listener should yield an error immediately */
241+
if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
242+
debug_f("connect \"%s\": %s", path, strerror(errno));
243+
close(fd);
244+
return 1;
245+
}
246+
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &r, &l) == -1) {
247+
debug_f("getsockopt: %s", strerror(errno));
248+
close(fd);
249+
return 0;
250+
}
251+
if (r != 0) {
252+
debug_f("socket error on %s: %s", path, strerror(errno));
253+
close(fd);
254+
return 1;
255+
}
256+
close(fd);
257+
debug_f("socket %s seems still active", path);
258+
return 0;
259+
}
260+
261+
void
262+
agent_cleanup_stale(const char *homedir, int ignore_hosthash)
263+
{
264+
DIR *d;
265+
struct dirent *dp;
266+
struct stat sb;
267+
char *prefix = NULL, *dirpath, *path;
268+
struct timespec now, sub;
269+
270+
/* Only consider sockets last modified > 1 hour ago */
271+
if (clock_gettime(CLOCK_REALTIME, &now) != 0) {
272+
error_f("clock_gettime: %s", strerror(errno));
273+
return;
274+
}
275+
sub.tv_sec = 60 * 60;
276+
sub.tv_nsec = 0;
277+
timespecsub(&now, &sub, &now);
278+
279+
/* Only consider sockets from the same hostname */
280+
if (!ignore_hosthash) {
281+
if ((path = agent_hostname_hash()) == NULL) {
282+
error_f("couldn't get hostname hash");
283+
return;
284+
}
285+
xasprintf(&prefix, "s.%s.", path);
286+
free(path);
287+
}
288+
289+
xasprintf(&dirpath, "%s/%s", homedir, _PATH_SSH_AGENT_SOCKET_DIR);
290+
if ((d = opendir(dirpath)) == NULL) {
291+
if (errno != ENOENT)
292+
error_f("opendir \"%s\": %s", dirpath, strerror(errno));
293+
free(dirpath);
294+
return;
295+
}
296+
while ((dp = readdir(d)) != NULL) {
297+
if (dp->d_type != DT_SOCK && dp->d_type != DT_UNKNOWN)
298+
continue;
299+
if (fstatat(dirfd(d), dp->d_name,
300+
&sb, AT_SYMLINK_NOFOLLOW) != 0 && errno != ENOENT) {
301+
error_f("stat \"%s/%s\": %s",
302+
dirpath, dp->d_name, strerror(errno));
303+
continue;
304+
}
305+
if (!S_ISSOCK(sb.st_mode))
306+
continue;
307+
if (timespeccmp(&sb.st_mtim, &now, >)) {
308+
debug3_f("Ignoring recent socket \"%s/%s\"",
309+
dirpath, dp->d_name);
310+
continue;
311+
}
312+
if (!ignore_hosthash &&
313+
strncmp(dp->d_name, prefix, strlen(prefix)) != 0) {
314+
debug3_f("Ignoring socket \"%s/%s\" "
315+
"from different host", dirpath, dp->d_name);
316+
continue;
317+
}
318+
xasprintf(&path, "%s/%s", dirpath, dp->d_name);
319+
if (socket_is_stale(path)) {
320+
debug_f("cleanup stale socket %s", path);
321+
unlinkat(dirfd(d), dp->d_name, 0);
322+
}
323+
free(path);
324+
}
325+
closedir(d);
326+
free(dirpath);
327+
free(prefix);
328+
}
329+

0 commit comments

Comments
 (0)