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 }