/**************************************************************************/ /* */ /* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com) */ /* Copyright (c) 2008-2014 Oleksandr Shneyder */ /* Copyright (c) 2014-2016 Ulrich Sibiller */ /* Copyright (c) 2014-2016 Mihai Moldovan */ /* Copyright (c) 2011-2016 Mike Gabriel */ /* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com) */ /* */ /* NXCOMP, NX protocol compression and NX extensions to this software */ /* are copyright of the aforementioned persons and companies. */ /* */ /* Redistribution and use of the present software is allowed according */ /* to terms specified in the file LICENSE.nxcomp which comes in the */ /* source distribution. */ /* */ /* All rights reserved. */ /* */ /* NOTE: This software has received contributions from various other */ /* contributors, only the core maintainers and supporters are listed as */ /* copyright holders. Please contact us, if you feel you should be listed */ /* as copyright holder, as well. */ /* */ /**************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #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') { snprintf(file_, DEFAULT_STRING_LIMIT, "%s", environment); } else { snprintf(file_, DEFAULT_STRING_LIMIT, "%s/.Xauthority", control -> HomePath); } #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 we assume that // the nxauth command is located under bin in the client // installation directory. On Mac OS X we assume that the // command is located directly in the client installation // directory, to make bundle shipping easier. On all the // other platforms we use the default xauth command that // is in our path. // char command[DEFAULT_STRING_LIMIT]; #if defined(__CYGWIN32__) snprintf(command, DEFAULT_STRING_LIMIT, "%s/bin/nxauth", control -> SystemPath); #elif defined(__APPLE__) snprintf(command, DEFAULT_STRING_LIMIT, "%s/nxauth", control -> SystemPath); #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]; FILE *data = NULL; int result = -1; if (strncmp(display_, "localhost:", 10) == 0) { snprintf(line, DEFAULT_STRING_LIMIT, "unix:%s", display_ + 10); } else if ((0 == strncasecmp(display_, "/tmp/launch", 11)) || (0 == strncasecmp(display_, "/private/tmp/com.apple.launchd", 30))) { /* * Launchd socket support, mostly for OS X, but maybe also other BSD derivates. */ const char *separator = strrchr(display_, ':'); if ((NULL == separator) || (!isdigit(*(separator + 1)))) { #ifdef PANIC *logofs << "Auth: PANIC! Unable to find separating colon character '" << "in launchd socket path '" << display_ << "'.\n" << logofs_flush; #endif goto AuthGetCookieResult; } snprintf(line, DEFAULT_STRING_LIMIT, "unix:%s", separator + 1); } 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. // data = Popen((char *const *) parameters, "r"); 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 }