File indexing completed on 2024-04-28 16:59:41
0001 /* 0002 Copyright (C) 2013, 2014 Andreas Hartmetz <ahartmetz@gmail.com> 0003 0004 This library is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; either 0007 version 2 of the License, or (at your option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LGPL. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 Boston, MA 02110-1301, USA. 0018 0019 Alternatively, this file is available under the Mozilla Public License 0020 Version 1.1. You may obtain a copy of the License at 0021 http://www.mozilla.org/MPL/ 0022 */ 0023 0024 #include "connectaddress.h" 0025 0026 #include "stringtools.h" 0027 0028 #include <cassert> 0029 #include <cstdint> 0030 #include <cstdlib> 0031 #include <fstream> 0032 #include <iostream> 0033 #include <vector> 0034 0035 #include <sys/types.h> 0036 0037 #ifdef __unix__ 0038 #include <pwd.h> 0039 #include <unistd.h> 0040 #endif 0041 0042 #ifdef _WIN32 0043 #define WIN32_LEAN_AND_MEAN 0044 #include <windows.h> 0045 #include <sddl.h> 0046 #include <mbstring.h> 0047 #include "winutil.h" 0048 #endif 0049 0050 #ifdef __unix__ 0051 static std::string homeDir() 0052 { 0053 const char *home = getenv("HOME"); // this overrides the entry in /etc/passwd 0054 if (!home) { 0055 // from /etc/passwd (or a similar mechanism) 0056 // ### user's storage is static; consider using getpwuid_r though! 0057 struct passwd *user = getpwuid(getuid()); 0058 if (user) { 0059 home = user->pw_dir; 0060 } 0061 } 0062 assert(home); 0063 return std::string(home); 0064 } 0065 0066 static std::string sessionInfoFile() 0067 { 0068 static const int numMachineUuidFilenames = 2; 0069 static const char *machineUuidFilenames[numMachineUuidFilenames] = { 0070 "/var/lib/dbus/machine-id", 0071 "/etc/machine-id" 0072 }; 0073 0074 std::string uuid; 0075 for (int i = 0; i < numMachineUuidFilenames && uuid.empty(); i++) { 0076 std::ifstream uuidFile(machineUuidFilenames[i]); 0077 uuidFile >> uuid; 0078 // TODO check that uuid consists of lowercase hex chars 0079 } 0080 if (uuid.length() != 32) { 0081 return std::string(); 0082 } 0083 0084 const char *displayChar = getenv("DISPLAY"); 0085 if (!displayChar) { 0086 // TODO error message "no X11 session blah" 0087 return std::string(); 0088 } 0089 std::string display = displayChar; 0090 // TODO from the original: "Note that we leave the hostname in the display most of the time" 0091 size_t lastColon = display.rfind(':'); 0092 if (lastColon == std::string::npos) { 0093 return std::string(); 0094 } 0095 display.erase(0, lastColon + 1); 0096 0097 static const char *pathInHome = "/.dbus/session-bus/"; 0098 return homeDir() + pathInHome + uuid + '-' + display; 0099 } 0100 #endif 0101 0102 #ifdef _WIN32 0103 /* 0104 What do on Windows: 0105 - Get the machine UUID (just for completeness or necessary is yet to be seen) 0106 - Get the server bus address, which includes "noncefile=", the path to the nonce file. 0107 It is obtained from a shared memory segment with a well-known name, and the liveness 0108 of the server process is checked using a mutex AFAIU. 0109 - Read 16 bytes from the nonce file, the credentials for the planned TCP connection 0110 - Implement the nonce-data dialog in AuthClient / something it can use 0111 */ 0112 static std::string hashOfInstallRoot() 0113 { 0114 // Using the non-Unicode API for bug compatibility with libdbus pathname hashes, for now. 0115 // This requires us to be installed to the same folder, which is a little weird, so maybe 0116 // drop this compatibility later. 0117 std::string ret; 0118 #if 1 0119 char path[MAX_PATH * 2] = "C:\\Program Files (x86)\\D-Bus\\bin\\dbus-monitor.exe)"; 0120 size_t pathLength = 0; 0121 #else 0122 char path[MAX_PATH * 2]; // * 2 because of multibyte (limited to double byte really) charsets 0123 HMODULE hm = nullptr; 0124 GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 0125 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 0126 reinterpret_cast<LPCSTR>(&hashOfInstallRoot), &hm); 0127 DWORD pathLength = GetModuleFileNameA(hm, path, sizeof(path)); 0128 if (!pathLength) { 0129 // blah 0130 return ret; 0131 } 0132 #endif 0133 // remove binary name to obtain the path 0134 char *lastBackslash = reinterpret_cast<char *>( 0135 _mbsrchr(reinterpret_cast<unsigned char *>(path), '\\')); 0136 if (!lastBackslash) { 0137 return ret; 0138 } 0139 pathLength = lastBackslash - path + 1; 0140 0141 // remove possible "\bin", "\bin\debug", "\bin\release" 0142 if (pathLength >= 4 && _strnicmp(lastBackslash - 4, "\\bin", 4) == 0) { 0143 pathLength -= 4; 0144 } else if (pathLength >= 10 && _strnicmp(lastBackslash - 10, "\\bin\\debug", 10) == 0) { 0145 pathLength -= 10; 0146 } else if (pathLength >= 12 && _strnicmp(lastBackslash - 12, "\\bin\\release", 12) == 0) { 0147 pathLength -= 12; 0148 } 0149 0150 // super crappy tolower(), also known as _dbus_string_tolower_ascii() 0151 for (size_t i = 0; i < pathLength; i++) { 0152 if (path[i] >= 'A' && path[i] <= 'Z') { 0153 path[i] += 'a' - 'A'; 0154 } 0155 } 0156 0157 std::string pathString(path, pathLength); 0158 return sha1Hex(pathString); 0159 } 0160 0161 // Returns something like: 0162 // "tcp:host=localhost,port=52933,family=ipv4,guid=0fcf91a66520469005539fb2000001a7" 0163 // Implementation along the lines od libdbus-1 dbus-sysdeps-win.c, _dbus_get_autolaunch_shm and 0164 // CreateMutexA / WaitForSingleObject in its callers 0165 static std::string sessionBusAddressFromShm() 0166 { 0167 std::string ret; 0168 std::string shmNamePostfix; 0169 0170 if (true /* scope == "*install-path" */) { 0171 shmNamePostfix = hashOfInstallRoot(); 0172 } else { 0173 // scope == "*user" 0174 shmNamePostfix = fetchWindowsSid(); 0175 } 0176 0177 // the SID corresponds to the "*user" "autolaunch method" or whatever apparently; 0178 // the default seems to be "install-path", for with the shm name postfix comes from 0179 // _dbus_get_install_root_as_hash - reimplement that one! 0180 0181 // TODO check that the daemon is available using the mutex 0182 0183 HANDLE sharedMem; 0184 std::string shmName = "DBusDaemonAddressInfo-"; 0185 shmName += shmNamePostfix; 0186 // full shm name looks something like "DBusDaemonAddressInfo-395c81f0c8140cfdeab22831b0faf4ec0ebcaae5" 0187 // full mutex name looks something like "DBusDaemonMutex-395c81f0c8140cfdeab22831b0faf4ec0ebcaae5" 0188 0189 // read shm 0190 for (int i = 0 ; i < 20; i++) { 0191 // we know that dbus-daemon is available, so we wait until shm is available 0192 sharedMem = OpenFileMappingA(FILE_MAP_READ, FALSE, shmName.c_str()); 0193 if (sharedMem != nullptr) { 0194 break; 0195 } 0196 std::cerr << "Retrying OpenFileMappingA\n"; 0197 Sleep(100); 0198 } 0199 0200 if (sharedMem == nullptr) 0201 return ret; 0202 0203 const void *addrView = MapViewOfFile(sharedMem, FILE_MAP_READ, 0, 0, 0); 0204 if (!addrView) { 0205 return ret; 0206 } 0207 ret = static_cast<const char *>(addrView); 0208 0209 // cleanup 0210 UnmapViewOfFile(addrView); 0211 CloseHandle(sharedMem); 0212 0213 return ret; 0214 } 0215 #endif 0216 0217 static std::string fetchSessionBusInfo() 0218 { 0219 std::string ret; 0220 0221 // TODO: on X11, the spec requires a special way to find the session bus 0222 // (but nobody seems to use it?) 0223 0224 // try the environment variable 0225 const char *envAddress = getenv("DBUS_SESSION_BUS_ADDRESS"); 0226 if (envAddress) { 0227 ret = envAddress; 0228 } else { 0229 #ifdef __unix__ 0230 // try it using a byzantine system involving files... 0231 std::ifstream infoFile(sessionInfoFile().c_str()); 0232 const std::string busAddressPrefix = "DBUS_SESSION_BUS_ADDRESS="; 0233 while (getline(infoFile, ret)) { 0234 // TODO do we need any of the other information in the file? 0235 if (ret.find(busAddressPrefix) == 0 ) { 0236 ret = ret.substr(busAddressPrefix.length()); 0237 break; 0238 } 0239 } 0240 #endif 0241 #ifdef _WIN32 0242 ret = sessionBusAddressFromShm(); 0243 #endif 0244 } 0245 return ret; 0246 } 0247 0248 static bool isSomeTcpType(ConnectAddress::Type t) 0249 { 0250 return t == ConnectAddress::Type::Tcp || t == ConnectAddress::Type::Tcp4 || 0251 t == ConnectAddress::Type::Tcp6; 0252 } 0253 0254 class ConnectAddress::Private 0255 { 0256 public: 0257 Private() 0258 : m_addrType(ConnectAddress::Type::None), 0259 m_role(ConnectAddress::Role::None), 0260 m_port(-1) 0261 {} 0262 0263 ConnectAddress::Type m_addrType; 0264 ConnectAddress::Role m_role; 0265 std::string m_path; 0266 std::string m_hostname; 0267 int m_port; 0268 std::string m_guid; 0269 }; 0270 0271 ConnectAddress::ConnectAddress() 0272 : d(new Private) 0273 { 0274 } 0275 0276 ConnectAddress::ConnectAddress(StandardBus bus) 0277 : d(new Private) 0278 { 0279 d->m_role = Role::BusClient; 0280 0281 if (bus == StandardBus::Session) { 0282 setAddressFromString(fetchSessionBusInfo()); 0283 } else { 0284 assert(bus == StandardBus::System); 0285 #ifdef __unix__ 0286 // ### does the __unix__ version actually apply to non-Linux? 0287 d->m_addrType = Type::UnixPath; 0288 d->m_path = "/var/run/dbus/system_bus_socket"; 0289 #else 0290 // Windows... it doesn't really have a system bus 0291 d->m_addrType = Type::None; 0292 #endif 0293 } 0294 } 0295 0296 ConnectAddress::ConnectAddress(const ConnectAddress &other) 0297 : d(new Private(*other.d)) 0298 { 0299 } 0300 0301 ConnectAddress &ConnectAddress::operator=(const ConnectAddress &other) 0302 { 0303 if (this != &other) { 0304 *d = *other.d; 0305 } 0306 return *this; 0307 } 0308 0309 ConnectAddress::~ConnectAddress() 0310 { 0311 delete d; 0312 d = nullptr; 0313 } 0314 0315 bool ConnectAddress::operator==(const ConnectAddress &other) const 0316 { 0317 // first, check everything that doesn't depend on address type 0318 if (d->m_addrType != other.d->m_addrType || d->m_role != other.d->m_role || 0319 d->m_guid != other.d->m_guid) { 0320 return false; 0321 } 0322 // then check the data that matters for each address type (this is defensive coding, the irrelevant 0323 // data should be zero / null / empty). 0324 if (isSomeTcpType(d->m_addrType)) { 0325 return d->m_port == other.d->m_port; 0326 } else { 0327 return d->m_path == other.d->m_path; 0328 } 0329 } 0330 0331 void ConnectAddress::setType(Type addrType) 0332 { 0333 d->m_addrType = addrType; 0334 } 0335 0336 ConnectAddress::Type ConnectAddress::type() const 0337 { 0338 return d->m_addrType; 0339 } 0340 0341 void ConnectAddress::setRole(Role role) 0342 { 0343 d->m_role = role; 0344 } 0345 0346 ConnectAddress::Role ConnectAddress::role() const 0347 { 0348 return d->m_role; 0349 } 0350 0351 void ConnectAddress::setPath(const std::string &path) 0352 { 0353 d->m_path = path; 0354 } 0355 0356 std::string ConnectAddress::path() const 0357 { 0358 return d->m_path; 0359 } 0360 0361 void ConnectAddress::setHostname(const std::string &hostname) 0362 { 0363 d->m_hostname = hostname; 0364 } 0365 0366 std::string ConnectAddress::hostname() const 0367 { 0368 return d->m_hostname; 0369 } 0370 0371 void ConnectAddress::setPort(int port) 0372 { 0373 d->m_port = port; 0374 } 0375 0376 int ConnectAddress::port() const 0377 { 0378 return d->m_port; 0379 } 0380 0381 void ConnectAddress::setGuid(const std::string &guid) 0382 { 0383 d->m_guid = guid; 0384 } 0385 0386 std::string ConnectAddress::guid() const 0387 { 0388 return d->m_guid; 0389 } 0390 0391 class UniqueCheck 0392 { 0393 public: 0394 enum Key 0395 { 0396 Path = 1 << 0, 0397 Host = 1 << 1, 0398 Port = 1 << 2, 0399 Family = 1 << 3, 0400 Guid = 1 << 4 0401 }; 0402 0403 inline bool claim(Key key) 0404 { 0405 const uint32_t oldClaimed = m_claimed; 0406 m_claimed |= key; 0407 return !(oldClaimed & key); 0408 } 0409 0410 private: 0411 uint32_t m_claimed = 0; 0412 }; 0413 0414 bool ConnectAddress::setAddressFromString(const std::string &addr) 0415 { 0416 d->m_addrType = Type::None; 0417 d->m_path.clear(); 0418 d->m_port = -1; 0419 d->m_guid.clear(); 0420 0421 const size_t addrStart = 0; 0422 const size_t addrEnd = addr.length(); 0423 0424 // ### The algorithm is a copy of libdbus's, which is kind of dumb (it parses each character 0425 // several times), but simple and works. This way, the errors for malformed input will be similar. 0426 0427 // TODO: double check which strings may be empty in libdbus and emulate that 0428 // TODO: lots of off-by-one errors in length checks 0429 0430 UniqueCheck unique; 0431 0432 const size_t kvListStart = addr.find(':', addrStart); 0433 // ### if we're going to check the method immediately, we can omit the last condition 0434 if (kvListStart == std::string::npos || kvListStart >= addrEnd || kvListStart == addrStart) { 0435 return false; 0436 } 0437 const std::string method = addr.substr(addrStart, kvListStart - addrStart); 0438 0439 if (method == "unix") { 0440 d->m_addrType = Type::UnixPath; 0441 } else if (method == "unixexec") { 0442 d->m_addrType = Type::UnixPath; // ### ??? 0443 } else if (method == "tcp") { 0444 d->m_addrType = Type::Tcp; 0445 } else { 0446 return false; 0447 } 0448 0449 size_t keyStart = kvListStart + 1; 0450 while (keyStart < addrEnd) { 0451 size_t valueEnd = addr.find(',', keyStart); 0452 if (valueEnd == std::string::npos) { // ### check zero length key-value pairs? 0453 valueEnd = addrEnd; 0454 } 0455 0456 const size_t keyEnd = addr.find('=', keyStart); 0457 if (keyEnd == std::string::npos || keyEnd == keyStart) { 0458 return false; 0459 } 0460 const size_t valueStart = keyEnd + 1; // skip '=' 0461 if (valueStart >= valueEnd) { 0462 return false; 0463 } 0464 0465 const std::string key = addr.substr(keyStart, keyEnd - keyStart); 0466 const std::string value = addr.substr(valueStart, valueEnd - valueStart); 0467 0468 // libdbus-1 takes the value from the first occurrence of a key because that is the simplest way to 0469 // handle it with its key list scanning approach. So it accidentally allows to specify the same key 0470 // multiple times... but it does not allow to specify contradictory keys, e.g. "path" and "abstract". 0471 // Instead of imitating that weirdness, we reject any address string with duplicate *or* 0472 // contradictory keys. By using the same "uniqueness category" Path for all the path-type keys, this 0473 // is easy to implement - and it makes more sense anyway. 0474 0475 Type newAddressType = Type::None; 0476 if (key == "path") { 0477 newAddressType = Type::UnixPath; 0478 } else if (key == "abstract") { 0479 newAddressType = Type::AbstractUnixPath; 0480 } else if (key == "dir") { 0481 newAddressType = Type::UnixDir; 0482 } else if (key == "tmpdir") { 0483 newAddressType = Type::TmpDir; 0484 } else if (key == "runtime") { 0485 newAddressType = Type::RuntimeDir; 0486 if (value != "yes") { 0487 return false; 0488 } 0489 } 0490 0491 if (newAddressType != Type::None) { 0492 if (!unique.claim(UniqueCheck::Path)) { 0493 return false; 0494 } 0495 if (d->m_addrType != Type::UnixPath) { 0496 return false; 0497 } 0498 d->m_addrType = newAddressType; 0499 if (newAddressType == Type::RuntimeDir) { 0500 // Ensure that no one somehow opens a socket called "yes" 0501 d->m_path.clear(); 0502 } else { 0503 d->m_path = value; 0504 } 0505 } else if (key == "host") { 0506 if (!unique.claim(UniqueCheck::Host)) { 0507 return false; 0508 } 0509 if (!isSomeTcpType(d->m_addrType)) { 0510 return false; 0511 } 0512 d->m_hostname = value; 0513 } else if (key == "port") { 0514 if (!unique.claim(UniqueCheck::Port)) { 0515 return false; 0516 } 0517 if (!isSomeTcpType(d->m_addrType)) { 0518 return false; 0519 } 0520 const bool convertOk = dfFromString(value, &d->m_port); 0521 if (!convertOk || d->m_port < 1 || d->m_port > 65535) { 0522 return false; 0523 } 0524 } else if (key == "family") { 0525 if (!unique.claim(UniqueCheck::Family)) { 0526 return false; 0527 } 0528 if (d->m_addrType != Type::Tcp) { 0529 return false; 0530 } 0531 if (value == "ipv4") { 0532 d->m_addrType = Type::Tcp4; 0533 } else if (value == "ipv6") { 0534 d->m_addrType = Type::Tcp6; 0535 } else { 0536 return false; 0537 } 0538 } else if (key == "guid") { 0539 if (!unique.claim(UniqueCheck::Guid)) { 0540 return false; 0541 } 0542 d->m_guid = value; 0543 } else { 0544 return false; 0545 } 0546 0547 keyStart = valueEnd + 1; 0548 } 0549 0550 0551 // Don't try to fully validate everything: the OS knows best how to fully check path validity, and 0552 // runtime errors still need to be handled in any case (e.g. access rights, etc) 0553 // ... what about the *Dir types, though?! 0554 if (d->m_addrType == Type::UnixPath || d->m_addrType == Type::AbstractUnixPath) { 0555 if (d->m_path.empty()) { 0556 return false; 0557 } 0558 } else if (isSomeTcpType(d->m_addrType)) { 0559 // port -1 is allowed for server-only addresses (the server picks a port) 0560 if (unique.claim(UniqueCheck::Host) /* we don't save the actual hostname */) { 0561 return false; 0562 } 0563 } 0564 0565 return true; 0566 } 0567 0568 #if 0 0569 // Does anyone need this? The "try connections in order" things in the DBus spec seems ill-advised 0570 // and nobody / nothing seems to be using it. 0571 0572 // static member function 0573 std::vector<ConnectAddress> ConnectAddress::parseAddressList(const std::string &addrString) 0574 { 0575 std::vector<ConnectAddress> ret; 0576 while (find next semicolon) { 0577 ConnectAddress addr; 0578 addr.setAddressFromString(substr); 0579 if (addr.error()) { 0580 return std::vector<ConnectAddress>(); 0581 } 0582 ret.push_back(addr); 0583 } 0584 return ret; 0585 } 0586 #endif 0587 0588 std::string ConnectAddress::toString() const 0589 { 0590 std::string ret; 0591 // no need to check bus and role, they are ignored here anyway 0592 // TODO consistency check between connectable vs listen address and role? 0593 0594 switch (d->m_addrType) { 0595 case Type::UnixPath: 0596 ret = "unix:path="; 0597 break; 0598 case Type::AbstractUnixPath: 0599 ret = "unix:abstract="; 0600 break; 0601 case Type::UnixDir: 0602 ret = "unix:dir="; 0603 break; 0604 case Type::TmpDir: 0605 ret = "unix:tmpdir="; 0606 break; 0607 case Type::RuntimeDir: 0608 ret = "unix:runtime=yes"; 0609 break; 0610 case Type::Tcp: 0611 ret = "tcp:host=localhost,port="; 0612 break; 0613 case Type::Tcp4: 0614 ret = "tcp:host=localhost,family=ipv4,port="; 0615 break; 0616 case Type::Tcp6: 0617 ret = "tcp:host=localhost,family=ipv6,port="; 0618 break; 0619 default: 0620 // invalid 0621 return ret; 0622 } 0623 0624 if (isSomeTcpType(d->m_addrType)) { 0625 ret += dfToString(d->m_port); 0626 } else if (d->m_addrType != Type::RuntimeDir) { 0627 ret += d->m_path; 0628 } 0629 0630 if (!d->m_guid.empty()) { 0631 ret += ",guid="; 0632 ret += d->m_guid; 0633 } 0634 0635 return ret; 0636 } 0637 0638 bool ConnectAddress::isServerOnly() const 0639 { 0640 switch (d->m_addrType) { 0641 #ifdef __unix__ 0642 case Type::UnixDir: // fall through 0643 case Type::RuntimeDir: // fall through 0644 #ifdef __linux__ 0645 case Type::TmpDir: 0646 #endif 0647 return true; 0648 #endif 0649 case Type::Tcp: 0650 return d->m_port == -1; 0651 default: 0652 return false; 0653 } 0654 }