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 len; unsigned char lenbuf[4]; unsigned char digest[32]; char retbuf[65]; SHA256_Init(&sha); PUT_32BIT(lenbuf, SALT_SIZE); SHA256_Bytes(&sha, lenbuf, 4); SHA256_Bytes(&sha, saltbuf, SALT_SIZE); len = strlen(pi_name); PUT_32BIT(lenbuf, len); SHA256_Bytes(&sha, lenbuf, 4); SHA256_Bytes(&sha, pi_name, len); 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; }
static char *obfuscate_name(const char *realname) { /* * Windows's named pipes all live in the same namespace, so one * user can see what pipes another user has open. This is an * undesirable privacy leak and in particular permits one user to * know what username@host another user is SSHing to, so we * protect that information by using CryptProtectMemory (which * uses a key built in to each user's account). */ char *cryptdata; int cryptlen; SHA256_State sha; unsigned char lenbuf[4]; unsigned char digest[32]; char retbuf[65]; int i; cryptlen = strlen(realname) + 1; cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1; cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE; cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE; cryptdata = snewn(cryptlen, char); memset(cryptdata, 0, cryptlen); strcpy(cryptdata, realname); /* * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to * use the same key in all processes with this user id, meaning * that the next PuTTY process calling this function with the same * input will get the same data. * * (Contrast with CryptProtectData, which invents a new session * key every time since its API permits returning more data than * was input, so calling _that_ and hashing the output would not * be stable.) * * We don't worry too much if this doesn't work for some reason. * Omitting this step still has _some_ privacy value (in that * another user can test-hash things to confirm guesses as to * where you might be connecting to, but cannot invert SHA-256 in * the absence of any plausible guess). So we don't abort if we * can't call CryptProtectMemory at all, or if it fails. */ if (got_crypt()) p_CryptProtectMemory(cryptdata, cryptlen, CRYPTPROTECTMEMORY_CROSS_PROCESS); /* * We don't want to give away the length of the hostname either, * so having got it back out of CryptProtectMemory we now hash it. */ SHA256_Init(&sha); PUT_32BIT_MSB_FIRST(lenbuf, cryptlen); SHA256_Bytes(&sha, lenbuf, 4); SHA256_Bytes(&sha, cryptdata, cryptlen); SHA256_Final(&sha, digest); sfree(cryptdata); /* * Finally, make printable. */ for (i = 0; i < 32; i++) { sprintf(retbuf + 2*i, "%02x", digest[i]); /* the last of those will also write the trailing NUL */ } return dupstr(retbuf); }
void SHA224_Bytes(SHA256_State *s, const void *p, int len) { SHA256_Bytes(s, p, len); }