diff options
Diffstat (limited to 'nxcomp/Auth.cpp')
-rw-r--r-- | nxcomp/Auth.cpp | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/nxcomp/Auth.cpp b/nxcomp/Auth.cpp new file mode 100644 index 000000000..d8e999132 --- /dev/null +++ b/nxcomp/Auth.cpp @@ -0,0 +1,650 @@ +/**************************************************************************/ +/* */ +/* Copyright (c) 2001, 2010 NoMachine, http://www.nomachine.com/. */ +/* */ +/* NXCOMP, NX protocol compression and NX extensions to this software */ +/* are copyright of NoMachine. Redistribution and use of the present */ +/* software is allowed according to terms specified in the file LICENSE */ +/* which comes in the source distribution. */ +/* */ +/* Check http://www.nomachine.com/licensing.html for applicability. */ +/* */ +/* NX and NoMachine are trademarks of Medialogic S.p.A. */ +/* */ +/* All rights reserved. */ +/* */ +/**************************************************************************/ + +#include "Auth.h" + +#include "Misc.h" +#include "Control.h" +#include "Timestamp.h" +#include "Pipe.h" + +#define DEFAULT_STRING_LIMIT 512 + +// +// Set the verbosity level. +// + +#define PANIC +#define WARNING +#undef TEST +#undef DEBUG + +// +// Store the provided cookie as our 'fake' cookie, then +// read the 'real' cookie from the current X authority +// file. +// + +Auth::Auth(char *display, char *cookie) +{ + display_ = NULL; + + file_ = NULL; + + last_ = nullTimestamp(); + + fakeCookie_ = NULL; + realCookie_ = NULL; + + fakeData_ = NULL; + realData_ = NULL; + + dataSize_ = 0; + + generatedCookie_ = 0; + + if (display == NULL || *display == '\0' || cookie == NULL || + *cookie == '\0' || strlen(cookie) != 32) + { + #ifdef PANIC + *logofs << "Auth: PANIC! Can't create the X authorization data " + << "with cookie '" << cookie << "' and display '" + << display << "'.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Can't create the X authorization data " + << "with cookie '" << cookie << "' and display '" + << display << "'.\n"; + + return; + } + + #ifdef TEST + *logofs << "Auth: Creating X authorization data with cookie '" + << cookie << "' and display '" << display << "'.\n" + << logofs_flush; + #endif + + // + // Get a local copy of all parameters. + // + + display_ = new char[strlen(display) + 1]; + file_ = new char[DEFAULT_STRING_LIMIT]; + + fakeCookie_ = new char[strlen(cookie) + 1]; + realCookie_ = new char[DEFAULT_STRING_LIMIT]; + + if (display_ == NULL || file_ == NULL || + fakeCookie_ == NULL || realCookie_ == NULL) + { + #ifdef PANIC + *logofs << "Auth: PANIC! Cannot allocate memory for the X " + << "authorization data.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Cannot allocate memory for the X " + << "authorization data.\n"; + + return; + } + + strcpy(display_, display); + + *file_ = '\0'; + + strcpy(fakeCookie_, cookie); + + *realCookie_ = '\0'; + + // + // Get the real cookie from the authorization file. + // + + updateCookie(); +} + +Auth::~Auth() +{ + delete [] display_; + delete [] file_; + + delete [] fakeCookie_; + delete [] realCookie_; + + delete [] fakeData_; + delete [] realData_; +} + +// +// At the present moment the cookie is read only once, +// at the time the instance is initialized. If the auth +// file changes along the life of the session, the old +// cookie will be used. This works with X servers beca- +// use of an undocumented "feature". See nx-X11. +// + +int Auth::updateCookie() +{ + if (isTimestamp(last_) == 0) + { + #ifdef TEST + *logofs << "Auth: Reading the X authorization file " + << "with last update at " << strMsTimestamp(last_) + << ".\n" << logofs_flush; + #endif + + if (getCookie() == 1 && validateCookie() == 1) + { + // + // It should rather be the modification time + // the auth file, so we can read it again if + // the file is changed. + // + + #ifdef TEST + *logofs << "Auth: Setting last X authorization file " + << "update at " << strMsTimestamp() << ".\n" + << logofs_flush; + #endif + + last_ = getTimestamp(); + + return 1; + } + + #ifdef PANIC + *logofs << "Auth: PANIC! Cannot read the cookie from the X " + << "authorization file.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Cannot read the cookie from the X " + << "authorization file.\n"; + + return -1; + } + + #ifdef TEST + *logofs << "Auth: WARNING! Skipping check on the X " + << "authorization file.\n" << logofs_flush; + #endif + + return 0; +} + +int Auth::getCookie() +{ + // + // Check the name of the auth file that we are going to use. + // It can be either the value of the XAUTHORITY environment + // or the default .Xauthority file in the user's home. + // + + char *environment; + + environment = getenv("XAUTHORITY"); + + if (environment != NULL && *environment != '\0') + { + strncpy(file_, environment, DEFAULT_STRING_LIMIT - 1); + } + else + { + snprintf(file_, DEFAULT_STRING_LIMIT - 1, "%s/.Xauthority", + control -> HomePath); + } + + *(file_ + DEFAULT_STRING_LIMIT - 1) = '\0'; + + #ifdef TEST + *logofs << "Auth: Using X authorization file '" << file_ + << "'.\n" << logofs_flush; + #endif + + // + // Use the nxauth command on Windows and the Mac, xauth + // on all the other platforms. On Windows and on the Mac + // we assume that the nxauth command is located under + // bin in the client installation directory. On all the + // other platforms we use the default xauth command that + // is in our path. + // + + char command[DEFAULT_STRING_LIMIT]; + + #if defined(__CYGWIN32__) || defined(__APPLE__) + + snprintf(command, DEFAULT_STRING_LIMIT - 1, + "%s/bin/nxauth", control -> SystemPath); + + *(command + DEFAULT_STRING_LIMIT - 1) = '\0'; + + #else + + strcpy(command, "xauth"); + + #endif + + #ifdef TEST + *logofs << "Auth: Using X auth command '" << command + << "'.\n" << logofs_flush; + #endif + + // + // The SSH code forces using the unix:n port when passing localhost:n. + // This is probably because localhost:n can fail to return a valid + // entry on machines where the hostname for localhost doesn't match + // exactly the 'localhost' string. For example, on a freshly installed + // Fedora Core 3 I get a 'localhost.localdomain/unix:0' entry. Query- + // ing 'xauth list localhost:0' results in an empty result, while the + // query 'xauth list unix:0' works as expected. Note anyway that if + // the cookie for the TCP connection on 'localhost' is set to a dif- + // ferent cookie than the one for the Unix connections, both SSH and + // NX will match the wrong cookie and session will fail. + // + + char line[DEFAULT_STRING_LIMIT]; + + if (strncmp(display_, "localhost:", 10) == 0) + { + snprintf(line, DEFAULT_STRING_LIMIT, "unix:%s", display_ + 10); + } + else + { + snprintf(line, DEFAULT_STRING_LIMIT, "%.200s", display_); + } + + const char *parameters[256]; + + parameters[0] = command; + parameters[1] = command; + parameters[2] = "-f"; + parameters[3] = file_; + parameters[4] = "list"; + parameters[5] = line; + parameters[6] = NULL; + + #ifdef TEST + *logofs << "Auth: Executing command "; + + for (int i = 0; i < 256 && parameters[i] != NULL; i++) + { + *logofs << "[" << parameters[i] << "]"; + } + + *logofs << ".\n" << logofs_flush; + #endif + + // + // Use the popen() function to read the result + // of the command. We would better use our own + // implementation. + // + + FILE *data = Popen((char *const *) parameters, "r"); + + int result = -1; + + if (data == NULL) + { + #ifdef PANIC + *logofs << "Auth: PANIC! Failed to execute the X auth command.\n" + << logofs_flush; + #endif + + cerr << "Error" << ": Failed to execute the X auth command.\n"; + + goto AuthGetCookieResult; + } + + if (fgets(line, DEFAULT_STRING_LIMIT, data) == NULL) + { + #ifdef WARNING + *logofs << "Auth: WARNING! Failed to read data from the X " + << "auth command.\n" << logofs_flush; + #endif + + #ifdef TEST + cerr << "Warning" << ": Failed to read data from the X " + << "auth command.\n"; + #endif + + #ifdef PANIC + *logofs << "Auth: WARNING! Generating a fake cookie for " + << "X authentication.\n" << logofs_flush; + #endif + + #ifdef TEST + cerr << "Warning" << ": Generating a fake cookie for " + << "X authentication.\n"; + #endif + + generateCookie(realCookie_); + } + else + { + #ifdef TEST + *logofs << "Auth: Checking cookie in string '" << line + << "'.\n" << logofs_flush; + #endif + + // + // Skip the hostname in the authority entry + // just in case it includes some white spaces. + // + + char *cookie = NULL; + + cookie = index(line, ':'); + + if (cookie == NULL) + { + cookie = line; + } + + if (sscanf(cookie, "%*s %*s %511s", realCookie_) != 1) + { + #ifdef PANIC + *logofs << "Auth: PANIC! Failed to identify the cookie " + << "in string '" << line << "'.\n" + << logofs_flush; + #endif + + cerr << "Error" << ": Failed to identify the cookie " + << "in string '" << line << "'.\n"; + + goto AuthGetCookieResult; + } + + #ifdef TEST + *logofs << "Auth: Got cookie '" << realCookie_ + << "' from file '" << file_ << "'.\n" + << logofs_flush; + #endif + } + + result = 1; + +AuthGetCookieResult: + + if (data != NULL) + { + Pclose(data); + } + + return result; +} + +int Auth::validateCookie() +{ + unsigned int length = strlen(realCookie_); + + if (length > DEFAULT_STRING_LIMIT / 2 - 1 || + strlen(fakeCookie_) != length) + { + #ifdef PANIC + *logofs << "Auth: PANIC! Size mismatch between cookies '" + << realCookie_ << "' and '" << fakeCookie_ << "'.\n" + << logofs_flush; + #endif + + cerr << "Error" << ": Size mismatch between cookies '" + << realCookie_ << "' and '" << fakeCookie_ << "'.\n"; + + goto AuthValidateCookieError; + } + + // + // The length of the resulting data will be + // half the size of the Hex cookie. + // + + length = length / 2; + + fakeData_ = new char[length]; + realData_ = new char[length]; + + if (fakeData_ == NULL || realData_ == NULL) + { + #ifdef PANIC + *logofs << "Auth: PANIC! Cannot allocate memory for the binary X " + << "authorization data.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Cannot allocate memory for the binary X " + << "authorization data.\n"; + + goto AuthValidateCookieError; + } + + // + // Translate the real cookie from Hex data + // to its binary representation. + // + + unsigned int value; + + for (unsigned int i = 0; i < length; i++) + { + if (sscanf(realCookie_ + 2 * i, "%2x", &value) != 1) + { + #ifdef PANIC + *logofs << "Auth: PANIC! Bad X authorization data in real " + << "cookie '" << realCookie_ << "'.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Bad X authorization data in real cookie '" + << realCookie_ << "'.\n"; + + goto AuthValidateCookieError; + } + + realData_[i] = value; + + if (sscanf(fakeCookie_ + 2 * i, "%2x", &value) != 1) + { + #ifdef PANIC + *logofs << "Auth: PANIC! Bad X authorization data in fake " + << "cookie '" << fakeCookie_ << "'.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Bad X authorization data in fake cookie '" + << fakeCookie_ << "'.\n"; + + goto AuthValidateCookieError; + } + + fakeData_[i] = value; + } + + dataSize_ = length; + + #ifdef TEST + *logofs << "Auth: Validated real cookie '" + << realCookie_ << "' and fake cookie '" << fakeCookie_ + << "' with data with size " << dataSize_ << ".\n" + << logofs_flush; + + *logofs << "Auth: Ready to accept incoming connections.\n" + << logofs_flush; + #endif + + return 1; + +AuthValidateCookieError: + + delete [] fakeData_; + delete [] realData_; + + fakeData_ = NULL; + realData_ = NULL; + + dataSize_ = 0; + + return -1; +} + +int Auth::checkCookie(unsigned char *buffer) +{ + if (isValid() != 1) + { + #ifdef PANIC + *logofs << "Auth: PANIC! Attempt to check the X cookie with " + << "invalid authorization data.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Attempt to check the X cookie with " + << "invalid authorization data.\n"; + + return -1; + } + + const char *protoName = "MIT-MAGIC-COOKIE-1"; + int protoSize = strlen(protoName); + + int matchedProtoSize; + int matchedDataSize; + + if (buffer[0] == 0x42) + { + // + // Byte order is MSB first. + // + + matchedProtoSize = 256 * buffer[6] + buffer[7]; + matchedDataSize = 256 * buffer[8] + buffer[9]; + } + else if (buffer[0] == 0x6c) + { + // + // Byte order is LSB first. + // + + matchedProtoSize = buffer[6] + 256 * buffer[7]; + matchedDataSize = buffer[8] + 256 * buffer[9]; + } + else + { + #ifdef WARNING + *logofs << "Auth: WARNING! Bad X connection data in the buffer.\n" + << logofs_flush; + #endif + + cerr << "Warning" << ": Bad X connection data in the buffer.\n"; + + return -1; + } + + // + // Check if both the authentication protocol + // and the fake cookie match our data. + // + + int protoOffset = 12; + + #ifdef TEST + *logofs << "Auth: Received a protocol size of " + << matchedProtoSize << " bytes.\n" + << logofs_flush; + #endif + + if (matchedProtoSize != protoSize || + memcmp(buffer + protoOffset, protoName, protoSize) != 0) + { + #ifdef WARNING + *logofs << "Auth: WARNING! Protocol mismatch or no X " + << "authentication data.\n" << logofs_flush; + #endif + + cerr << "Warning" << ": Protocol mismatch or no X " + << "authentication data.\n"; + + return -1; + } + + int dataOffset = protoOffset + ((matchedProtoSize + 3) & ~3); + + #ifdef TEST + *logofs << "Auth: Received a data size of " + << matchedDataSize << " bytes.\n" + << logofs_flush; + #endif + + if (matchedDataSize != dataSize_ || + memcmp(buffer + dataOffset, fakeData_, dataSize_) != 0) + { + #ifdef WARNING + *logofs << "Auth: WARNING! Cookie mismatch in the X " + << "authentication data.\n" << logofs_flush; + #endif + + cerr << "Warning" << ": Cookie mismatch in the X " + << "authentication data.\n"; + + return -1; + } + + // + // Everything is OK. Replace the fake data. + // + + #ifdef TEST + *logofs << "Auth: Replacing fake X authentication data " + << "with the real data.\n" << logofs_flush; + #endif + + memcpy(buffer + dataOffset, realData_, dataSize_); + + return 1; +} + +void Auth::generateCookie(char *cookie) +{ + // + // Code is from the SSH implementation, except that + // we use a much weaker random number generator. + // This is not critical, anyway, as this is just a + // fake cookie. The X server doesn't have a cookie + // for the display, so it will ignore the value we + // feed to it. + // + + T_timestamp timer = getTimestamp(); + + srand((unsigned int) timer.tv_usec); + + unsigned int data = rand(); + + for (int i = 0; i < 16; i++) + { + if (i % 4 == 0) + { + data = rand(); + } + + snprintf(cookie + 2 * i, 3, "%02x", data & 0xff); + + data >>= 8; + } + + generatedCookie_ = 1; + + #ifdef TEST + *logofs << "Auth: Generated X cookie string '" + << cookie << "'.\n" << logofs_flush; + #endif +} |