2 * Unix implementation of SSH connection-sharing IPC setup.
13 #include <sys/types.h>
16 #define DEFINE_PLUG_METHOD_MACROS
23 #define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare"
24 #define SALT_FILENAME "salt"
28 * Functions provided by uxnet.c to help connection sharing.
30 SockAddr unix_sock_addr(const char *path);
31 Socket new_unix_listener(SockAddr listenaddr, Plug plug);
33 static char *make_parentdir_name(void)
35 char *username, *parent;
37 username = get_username();
38 parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username);
40 assert(*parent == '/');
45 static char *make_dir_and_check_ours(const char *dirname)
50 * Create the directory. We might have created it before, so
51 * EEXIST is an OK error; but anything else is doom.
53 if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
54 return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
57 * Now check that that directory is _owned by us_ and not writable
58 * by anybody else. This protects us against somebody else
59 * previously having created the directory in a way that's
60 * writable to us, and thus manipulating us into creating the
61 * actual socket in a directory they can see so that they can
62 * connect to it and use our authenticated SSH sessions.
64 if (stat(dirname, &st) < 0)
65 return dupprintf("%s: stat: %s", dirname, strerror(errno));
66 if (st.st_uid != getuid())
67 return dupprintf("%s: directory owned by uid %d, not by us",
69 if ((st.st_mode & 077) != 0)
70 return dupprintf("%s: directory has overgenerous permissions %03o"
71 " (expected 700)", dirname, st.st_mode & 0777);
76 static char *make_dirname(const char *pi_name, char **logtext)
78 char *name, *parentdirname, *dirname, *err;
81 * First, create the top-level directory for all shared PuTTY
82 * connections owned by this user.
84 parentdirname = make_parentdir_name();
85 if ((err = make_dir_and_check_ours(parentdirname)) != NULL) {
92 * Transform the platform-independent version of the connection
93 * identifier into the name we'll actually use for the directory
94 * containing the Unix socket.
96 * We do this by hashing the identifier with some user-specific
97 * secret information, to avoid the privacy leak of having
98 * "user@host" strings show up in 'netstat -x'. (Irritatingly, the
99 * full pathname of a Unix-domain socket _does_ show up in the
100 * 'netstat -x' output, at least on Linux, even if that socket is
101 * in a directory not readable to the user running netstat. You'd
102 * think putting things inside an 0700 directory would hide their
103 * names from other users, but no.)
105 * The secret information we use to salt the hash lives in a file
106 * inside the top-level directory we just created, so we must
107 * first create that file (with some fresh random data in it) if
108 * it's not already been done by a previous PuTTY.
111 unsigned char saltbuf[SALT_SIZE];
115 saltname = dupprintf("%s/%s", parentdirname, SALT_FILENAME);
116 saltfd = open(saltname, O_RDONLY);
121 if (errno != ENOENT) {
122 *logtext = dupprintf("%s: open: %s", saltname,
125 sfree(parentdirname);
130 * The salt file doesn't already exist, so try to create
131 * it. Another process may be attempting the same thing
132 * simultaneously, so we must do this carefully: we write
133 * a salt file under a different name, then hard-link it
134 * into place, which guarantees that we won't change the
135 * contents of an existing salt file.
139 tmpname = dupprintf("%s/%s.tmp.%d.%d",
140 parentdirname, SALT_FILENAME, pid, i);
141 saltfd = open(tmpname, O_WRONLY | O_EXCL | O_CREAT, 0400);
144 if (errno != EEXIST) {
145 *logtext = dupprintf("%s: open: %s", tmpname,
149 sfree(parentdirname);
152 sfree(tmpname); /* go round and try again with i+1 */
155 * Invent some random data.
157 for (i = 0; i < SALT_SIZE; i++) {
158 saltbuf[i] = random_byte();
160 ret = write(saltfd, saltbuf, SALT_SIZE);
161 /* POSIX atomicity guarantee: because we wrote less than
162 * PIPE_BUF bytes, the write either completed in full or
164 assert(SALT_SIZE < PIPE_BUF);
165 assert(ret < 0 || ret == SALT_SIZE);
168 *logtext = dupprintf("%s: write: %s", tmpname,
172 sfree(parentdirname);
175 if (close(saltfd) < 0) {
176 *logtext = dupprintf("%s: close: %s", tmpname,
180 sfree(parentdirname);
185 * Now attempt to hard-link our temp file into place. We
186 * tolerate EEXIST as an outcome, because that just means
187 * another PuTTY got their attempt in before we did (and
188 * we only care that there is a valid salt file we can
189 * agree on, no matter who created it).
191 if (link(tmpname, saltname) < 0 && errno != EEXIST) {
192 *logtext = dupprintf("%s: link: %s", saltname,
196 sfree(parentdirname);
201 * Whether that succeeded or not, get rid of our temp file.
203 if (unlink(tmpname) < 0) {
204 *logtext = dupprintf("%s: unlink: %s", tmpname,
208 sfree(parentdirname);
213 * And now we've arranged for there to be a salt file, so
214 * we can try to open it for reading again and this time
219 saltfd = open(saltname, O_RDONLY);
221 *logtext = dupprintf("%s: open: %s", saltname,
224 sfree(parentdirname);
229 for (i = 0; i < SALT_SIZE; i++) {
230 ret = read(saltfd, saltbuf, SALT_SIZE);
233 *logtext = dupprintf("%s: read: %s", saltname,
234 ret == 0 ? "unexpected EOF" :
237 sfree(parentdirname);
240 assert(0 < ret && ret <= SALT_SIZE - i);
248 * Now we've got our salt, hash it with the connection
249 * identifier to produce our actual socket name.
254 unsigned char lenbuf[4];
255 unsigned char digest[32];
259 PUT_32BIT(lenbuf, SALT_SIZE);
260 SHA256_Bytes(&sha, lenbuf, 4);
261 SHA256_Bytes(&sha, saltbuf, SALT_SIZE);
262 len = strlen(pi_name);
263 PUT_32BIT(lenbuf, len);
264 SHA256_Bytes(&sha, lenbuf, 4);
265 SHA256_Bytes(&sha, pi_name, len);
266 SHA256_Final(&sha, digest);
269 * And make it printable.
271 for (i = 0; i < 32; i++) {
272 sprintf(retbuf + 2*i, "%02x", digest[i]);
273 /* the last of those will also write the trailing NUL */
276 name = dupstr(retbuf);
279 smemclr(saltbuf, sizeof(saltbuf));
282 dirname = dupprintf("%s/%s", parentdirname, name);
283 sfree(parentdirname);
289 int platform_ssh_share(const char *pi_name, Conf *conf,
290 Plug downplug, Plug upplug, Socket *sock,
291 char **logtext, char **ds_err, char **us_err,
292 int can_upstream, int can_downstream)
294 char *dirname, *lockname, *sockname, *err;
299 * Sort out what we're going to call the directory in which we
300 * keep the socket. This has the side effect of potentially
301 * creating its top-level containing dir and/or the salt file
302 * within that, if they don't already exist.
304 dirname = make_dirname(pi_name, logtext);
310 * Now make sure the subdirectory exists.
312 if ((err = make_dir_and_check_ours(dirname)) != NULL) {
319 * Acquire a lock on a file in that directory.
321 lockname = dupcat(dirname, "/lock", (char *)NULL);
322 lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600);
324 *logtext = dupprintf("%s: open: %s", lockname, strerror(errno));
329 if (flock(lockfd, LOCK_EX) < 0) {
330 *logtext = dupprintf("%s: flock(LOCK_EX): %s",
331 lockname, strerror(errno));
338 sockname = dupprintf("%s/socket", dirname);
342 if (can_downstream) {
343 retsock = new_connection(unix_sock_addr(sockname),
344 "", 0, 0, 1, 0, 0, downplug, conf);
345 if (sk_socket_error(retsock) == NULL) {
352 return SHARE_DOWNSTREAM;
355 *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
360 retsock = new_unix_listener(unix_sock_addr(sockname), upplug);
361 if (sk_socket_error(retsock) == NULL) {
368 return SHARE_UPSTREAM;
371 *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
375 /* One of the above clauses ought to have happened. */
376 assert(*logtext || *ds_err || *us_err);
385 void platform_ssh_share_cleanup(const char *name)
387 char *dirname, *filename, *logtext;
389 dirname = make_dirname(name, &logtext);
391 sfree(logtext); /* we can't do much with this */
395 filename = dupcat(dirname, "/socket", (char *)NULL);
399 filename = dupcat(dirname, "/lock", (char *)NULL);
406 * We deliberately _don't_ clean up the parent directory
407 * /tmp/putty-connshare.<username>, because if we leave it around
408 * then it reduces the ability for other users to be a nuisance by
409 * putting their own directory in the way of it. Also, the salt
410 * file in it can be reused.