зеркало из https://github.com/github/putty.git
377 строки
12 KiB
C
377 строки
12 KiB
C
/*
|
|
* Unix implementation of SSH connection-sharing IPC setup.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/file.h>
|
|
|
|
#define DEFINE_PLUG_METHOD_MACROS
|
|
#include "tree234.h"
|
|
#include "putty.h"
|
|
#include "network.h"
|
|
#include "proxy.h"
|
|
#include "ssh.h"
|
|
|
|
#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare"
|
|
#define SALT_FILENAME "salt"
|
|
#define SALT_SIZE 64
|
|
|
|
/*
|
|
* Functions provided by uxnet.c to help connection sharing.
|
|
*/
|
|
SockAddr unix_sock_addr(const char *path);
|
|
Socket new_unix_listener(SockAddr listenaddr, Plug plug);
|
|
|
|
static char *make_parentdir_name(void)
|
|
{
|
|
char *username, *parent;
|
|
|
|
username = get_username();
|
|
parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username);
|
|
sfree(username);
|
|
assert(*parent == '/');
|
|
|
|
return parent;
|
|
}
|
|
|
|
static char *make_dirname(const char *pi_name, char **logtext)
|
|
{
|
|
char *name, *parentdirname, *dirname, *err;
|
|
|
|
/*
|
|
* First, create the top-level directory for all shared PuTTY
|
|
* connections owned by this user.
|
|
*/
|
|
parentdirname = make_parentdir_name();
|
|
if ((err = make_dir_and_check_ours(parentdirname)) != NULL) {
|
|
*logtext = err;
|
|
sfree(parentdirname);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Transform the platform-independent version of the connection
|
|
* identifier into the name we'll actually use for the directory
|
|
* containing the Unix socket.
|
|
*
|
|
* We do this by hashing the identifier with some user-specific
|
|
* secret information, to avoid the privacy leak of having
|
|
* "user@host" strings show up in 'netstat -x'. (Irritatingly, the
|
|
* full pathname of a Unix-domain socket _does_ show up in the
|
|
* 'netstat -x' output, at least on Linux, even if that socket is
|
|
* in a directory not readable to the user running netstat. You'd
|
|
* think putting things inside an 0700 directory would hide their
|
|
* names from other users, but no.)
|
|
*
|
|
* The secret information we use to salt the hash lives in a file
|
|
* inside the top-level directory we just created, so we must
|
|
* first create that file (with some fresh random data in it) if
|
|
* it's not already been done by a previous PuTTY.
|
|
*/
|
|
{
|
|
unsigned char saltbuf[SALT_SIZE];
|
|
char *saltname;
|
|
int saltfd, i, ret;
|
|
|
|
saltname = dupprintf("%s/%s", parentdirname, SALT_FILENAME);
|
|
saltfd = open(saltname, O_RDONLY);
|
|
if (saltfd < 0) {
|
|
char *tmpname;
|
|
int pid;
|
|
|
|
if (errno != ENOENT) {
|
|
*logtext = dupprintf("%s: open: %s", saltname,
|
|
strerror(errno));
|
|
sfree(saltname);
|
|
sfree(parentdirname);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* The salt file doesn't already exist, so try to create
|
|
* it. Another process may be attempting the same thing
|
|
* simultaneously, so we must do this carefully: we write
|
|
* a salt file under a different name, then hard-link it
|
|
* into place, which guarantees that we won't change the
|
|
* contents of an existing salt file.
|
|
*/
|
|
pid = getpid();
|
|
for (i = 0;; i++) {
|
|
tmpname = dupprintf("%s/%s.tmp.%d.%d",
|
|
parentdirname, SALT_FILENAME, pid, i);
|
|
saltfd = open(tmpname, O_WRONLY | O_EXCL | O_CREAT, 0400);
|
|
if (saltfd >= 0)
|
|
break;
|
|
if (errno != EEXIST) {
|
|
*logtext = dupprintf("%s: open: %s", tmpname,
|
|
strerror(errno));
|
|
sfree(tmpname);
|
|
sfree(saltname);
|
|
sfree(parentdirname);
|
|
return NULL;
|
|
}
|
|
sfree(tmpname); /* go round and try again with i+1 */
|
|
}
|
|
/*
|
|
* Invent some random data.
|
|
*/
|
|
for (i = 0; i < SALT_SIZE; i++) {
|
|
saltbuf[i] = random_byte();
|
|
}
|
|
ret = write(saltfd, saltbuf, SALT_SIZE);
|
|
/* POSIX atomicity guarantee: because we wrote less than
|
|
* PIPE_BUF bytes, the write either completed in full or
|
|
* failed. */
|
|
assert(SALT_SIZE < PIPE_BUF);
|
|
assert(ret < 0 || ret == SALT_SIZE);
|
|
if (ret < 0) {
|
|
close(saltfd);
|
|
*logtext = dupprintf("%s: write: %s", tmpname,
|
|
strerror(errno));
|
|
sfree(tmpname);
|
|
sfree(saltname);
|
|
sfree(parentdirname);
|
|
return NULL;
|
|
}
|
|
if (close(saltfd) < 0) {
|
|
*logtext = dupprintf("%s: close: %s", tmpname,
|
|
strerror(errno));
|
|
sfree(tmpname);
|
|
sfree(saltname);
|
|
sfree(parentdirname);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Now attempt to hard-link our temp file into place. We
|
|
* tolerate EEXIST as an outcome, because that just means
|
|
* another PuTTY got their attempt in before we did (and
|
|
* we only care that there is a valid salt file we can
|
|
* agree on, no matter who created it).
|
|
*/
|
|
if (link(tmpname, saltname) < 0 && errno != EEXIST) {
|
|
*logtext = dupprintf("%s: link: %s", saltname,
|
|
strerror(errno));
|
|
sfree(tmpname);
|
|
sfree(saltname);
|
|
sfree(parentdirname);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Whether that succeeded or not, get rid of our temp file.
|
|
*/
|
|
if (unlink(tmpname) < 0) {
|
|
*logtext = dupprintf("%s: unlink: %s", tmpname,
|
|
strerror(errno));
|
|
sfree(tmpname);
|
|
sfree(saltname);
|
|
sfree(parentdirname);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* And now we've arranged for there to be a salt file, so
|
|
* we can try to open it for reading again and this time
|
|
* expect it to work.
|
|
*/
|
|
sfree(tmpname);
|
|
|
|
saltfd = open(saltname, O_RDONLY);
|
|
if (saltfd < 0) {
|
|
*logtext = dupprintf("%s: open: %s", saltname,
|
|
strerror(errno));
|
|
sfree(saltname);
|
|
sfree(parentdirname);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < SALT_SIZE; i++) {
|
|
ret = read(saltfd, saltbuf, SALT_SIZE);
|
|
if (ret <= 0) {
|
|
close(saltfd);
|
|
*logtext = dupprintf("%s: read: %s", saltname,
|
|
ret == 0 ? "unexpected EOF" :
|
|
strerror(errno));
|
|
sfree(saltname);
|
|
sfree(parentdirname);
|
|
return NULL;
|
|
}
|
|
assert(0 < ret && ret <= SALT_SIZE - i);
|
|
i += ret;
|
|
}
|
|
|
|
close(saltfd);
|
|
sfree(saltname);
|
|
|
|
/*
|
|
* Now we've got our salt, hash it with the connection
|
|
* identifier to produce our actual socket name.
|
|
*/
|
|
{
|
|
SHA256_State sha;
|
|
unsigned char digest[32];
|
|
char retbuf[65];
|
|
|
|
SHA256_Init(&sha);
|
|
put_string(&sha, saltbuf, SALT_SIZE);
|
|
put_stringz(&sha, pi_name);
|
|
SHA256_Final(&sha, digest);
|
|
|
|
/*
|
|
* And make it printable.
|
|
*/
|
|
for (i = 0; i < 32; i++) {
|
|
sprintf(retbuf + 2*i, "%02x", digest[i]);
|
|
/* the last of those will also write the trailing NUL */
|
|
}
|
|
|
|
name = dupstr(retbuf);
|
|
}
|
|
|
|
smemclr(saltbuf, sizeof(saltbuf));
|
|
}
|
|
|
|
dirname = dupprintf("%s/%s", parentdirname, name);
|
|
sfree(parentdirname);
|
|
sfree(name);
|
|
|
|
return dirname;
|
|
}
|
|
|
|
int platform_ssh_share(const char *pi_name, Conf *conf,
|
|
Plug downplug, Plug upplug, Socket *sock,
|
|
char **logtext, char **ds_err, char **us_err,
|
|
int can_upstream, int can_downstream)
|
|
{
|
|
char *dirname, *lockname, *sockname, *err;
|
|
int lockfd;
|
|
Socket retsock;
|
|
|
|
/*
|
|
* Sort out what we're going to call the directory in which we
|
|
* keep the socket. This has the side effect of potentially
|
|
* creating its top-level containing dir and/or the salt file
|
|
* within that, if they don't already exist.
|
|
*/
|
|
dirname = make_dirname(pi_name, logtext);
|
|
if (!dirname) {
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
/*
|
|
* Now make sure the subdirectory exists.
|
|
*/
|
|
if ((err = make_dir_and_check_ours(dirname)) != NULL) {
|
|
*logtext = err;
|
|
sfree(dirname);
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
/*
|
|
* Acquire a lock on a file in that directory.
|
|
*/
|
|
lockname = dupcat(dirname, "/lock", (char *)NULL);
|
|
lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600);
|
|
if (lockfd < 0) {
|
|
*logtext = dupprintf("%s: open: %s", lockname, strerror(errno));
|
|
sfree(dirname);
|
|
sfree(lockname);
|
|
return SHARE_NONE;
|
|
}
|
|
if (flock(lockfd, LOCK_EX) < 0) {
|
|
*logtext = dupprintf("%s: flock(LOCK_EX): %s",
|
|
lockname, strerror(errno));
|
|
sfree(dirname);
|
|
sfree(lockname);
|
|
close(lockfd);
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
sockname = dupprintf("%s/socket", dirname);
|
|
|
|
*logtext = NULL;
|
|
|
|
if (can_downstream) {
|
|
retsock = new_connection(unix_sock_addr(sockname),
|
|
"", 0, 0, 1, 0, 0, downplug, conf);
|
|
if (sk_socket_error(retsock) == NULL) {
|
|
sfree(*logtext);
|
|
*logtext = sockname;
|
|
*sock = retsock;
|
|
sfree(dirname);
|
|
sfree(lockname);
|
|
close(lockfd);
|
|
return SHARE_DOWNSTREAM;
|
|
}
|
|
sfree(*ds_err);
|
|
*ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
|
|
sk_close(retsock);
|
|
}
|
|
|
|
if (can_upstream) {
|
|
retsock = new_unix_listener(unix_sock_addr(sockname), upplug);
|
|
if (sk_socket_error(retsock) == NULL) {
|
|
sfree(*logtext);
|
|
*logtext = sockname;
|
|
*sock = retsock;
|
|
sfree(dirname);
|
|
sfree(lockname);
|
|
close(lockfd);
|
|
return SHARE_UPSTREAM;
|
|
}
|
|
sfree(*us_err);
|
|
*us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
|
|
sk_close(retsock);
|
|
}
|
|
|
|
/* One of the above clauses ought to have happened. */
|
|
assert(*logtext || *ds_err || *us_err);
|
|
|
|
sfree(dirname);
|
|
sfree(lockname);
|
|
sfree(sockname);
|
|
close(lockfd);
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
void platform_ssh_share_cleanup(const char *name)
|
|
{
|
|
char *dirname, *filename, *logtext;
|
|
|
|
dirname = make_dirname(name, &logtext);
|
|
if (!dirname) {
|
|
sfree(logtext); /* we can't do much with this */
|
|
return;
|
|
}
|
|
|
|
filename = dupcat(dirname, "/socket", (char *)NULL);
|
|
remove(filename);
|
|
sfree(filename);
|
|
|
|
filename = dupcat(dirname, "/lock", (char *)NULL);
|
|
remove(filename);
|
|
sfree(filename);
|
|
|
|
rmdir(dirname);
|
|
|
|
/*
|
|
* We deliberately _don't_ clean up the parent directory
|
|
* /tmp/putty-connshare.<username>, because if we leave it around
|
|
* then it reduces the ability for other users to be a nuisance by
|
|
* putting their own directory in the way of it. Also, the salt
|
|
* file in it can be reused.
|
|
*/
|
|
|
|
sfree(dirname);
|
|
}
|