File indexing completed on 2024-05-19 04:39:57

0001 /*
0002     SPDX-FileCopyrightText: 2011 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <cassert>
0008 #include <cerrno>
0009 #include <cstdlib>
0010 #include <cstring>
0011 #include <fcntl.h>
0012 #include <iostream>
0013 #include <netdb.h>
0014 #include <netinet/in.h>
0015 #include <sstream>
0016 #include <sys/socket.h>
0017 #include <sys/types.h>
0018 #include <sys/un.h>
0019 #include <unistd.h>
0020 
0021 #ifndef HAVE_MSG_NOSIGNAL
0022 #define MSG_NOSIGNAL 0
0023 #endif
0024 
0025 /**
0026  * The goal of this utility is transforming the abstract unix-socket which is used by dbus
0027  * into a TCP socket which can be forwarded to a target machine by ssh tunneling, and then on
0028  * the target machine back into an abstract unix socket.
0029  *
0030  * This tool basically works similar to the "socat" utility, except that it works properly
0031  * for this special case. It is merely responsible for the transformation between abstract unix
0032  * sockets and tcp sockets.
0033  *
0034  * Furthermore, this tool makes the 'EXTERNAL' dbus authentication mechanism work even across
0035  * machines with different user IDs.
0036  *
0037  * This is how the EXTERNAL mechanism works (I found this in a comment of some ruby dbus library):
0038  *   Take the user id (eg integer 1000) make a string out of it "1000", take
0039  *   each character and determine hex value "1" => 0x31, "0" => 0x30. You
0040  *   obtain for "1000" => 31303030 This is what the server is expecting.
0041  *   Why? I dunno. How did I come to that conclusion? by looking at rbus
0042  *   code. I have no idea how he found that out.
0043  *
0044  * The dbus client performs the EXTERNAL authentication by sending "AUTH EXTERNAL 31303030\r\n" once
0045  * after opening the connection, so we can "repair" the authentication by overwriting the token in that
0046  * string through the correct one.
0047  * */
0048 
0049 const bool debug = false;
0050 
0051 /**
0052  * Returns the valid dbus EXTERNAL authentication token for the current user (see above)
0053  * */
0054 std::string getAuthToken()
0055 {
0056     // Get uid
0057     int uid = getuid();
0058 
0059     std::ostringstream uidStream;
0060     uidStream << uid;
0061 
0062     std::string uidStr = uidStream.str();
0063 
0064     const char hexdigits[16] = {
0065         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
0066         'a', 'b', 'c', 'd', 'e', 'f'
0067     };
0068 
0069     std::ostringstream hexStream;
0070     for (char c : uidStr) {
0071         auto byte = (unsigned char)c;
0072         hexStream << hexdigits[byte >> 4] << hexdigits[byte & 0x0f];
0073     }
0074 
0075     return hexStream.str();
0076 }
0077 
0078 /**
0079  * Shuffles all data between the two file-descriptors until one of them fails or reaches EOF.
0080  * */
0081 void shuffleBetweenStreams(int side1, int side2, bool fixSide1AuthToken)
0082 {
0083     char buffer[1000];
0084     char buffer2[1000];
0085 
0086     // Set non-blocking mode
0087     int opts = fcntl(side1, F_GETFL);
0088     opts |= O_NONBLOCK;
0089     fcntl(side1, F_SETFL, opts);
0090 
0091     opts = fcntl(side2, F_GETFL);
0092     opts |= O_NONBLOCK;
0093     fcntl(side2, F_SETFL, opts);
0094 
0095     while (true) {
0096         int r1 = read(side1, buffer, 500); // We read less than 1000, so we have same additional space when changing the auth token
0097         int r2 = read(side2, buffer2, 500);
0098 
0099         if (r1 < -1 || r1 == 0) {
0100             if (debug)
0101                 std::cerr << "stream 1 failed: " << r1 << std::endl;
0102             return;
0103         }
0104         if (r2 < -1 || r2 == 0) {
0105             if (debug)
0106                 std::cerr << "stream 2 failed: " << r2 << std::endl;
0107             return;
0108         }
0109 
0110         if (r1 > 0) {
0111             if (debug)
0112                 std::cerr << "transferring " << r1 << " from 1 to 2" << std::endl;
0113 
0114             if (fixSide1AuthToken) {
0115                 if (r1 > 15 && memcmp(buffer, "\0AUTH EXTERNAL ", 15) == 0) {
0116                     int endPos = -1;
0117                     for (int i = 15; i < r1; ++i) {
0118                         if (buffer[i] == '\r') {
0119                             endPos = i;
0120                             break;
0121                         }
0122                     }
0123 
0124                     if (endPos != -1) {
0125                         std::string oldToken = std::string(buffer + 15, endPos - 15);
0126                         std::string newToken = getAuthToken();
0127 
0128                         int difference = newToken.size() - oldToken.size();
0129                         r1 += difference;
0130                         assert(r1 > 0 && r1 <= 1000);
0131                         memmove(buffer + endPos + difference, buffer + endPos, r1 - difference - endPos);
0132                         memcpy(buffer + 15, newToken.data(), newToken.size());
0133                         assert(buffer[endPos + difference] == '\r');
0134                         assert(buffer[endPos + difference - 1] == newToken[newToken.size() - 1]);
0135                     } else {
0136                         std::cout << "could not fix auth token, not enough data available" << std::endl;
0137                     }
0138                 } else {
0139                     std::cout << "could not fix auth token" << std::endl;
0140                 }
0141                 fixSide1AuthToken = false;
0142             }
0143 
0144             opts = fcntl(side2, F_GETFL);
0145             opts ^= O_NONBLOCK;
0146             fcntl(side2, F_SETFL, opts);
0147 
0148             int w2 = send(side2, buffer, r1, MSG_NOSIGNAL);
0149 
0150             if (w2 < 0) {
0151                 if (debug)
0152                     std::cerr << "writing to side 2 failed, ending: " << w2 << std::endl;
0153                 return;
0154             }
0155             assert(w2 == r1);
0156 
0157             opts = fcntl(side2, F_GETFL);
0158             opts |= O_NONBLOCK;
0159             fcntl(side2, F_SETFL, opts);
0160         }
0161 
0162         if (r2 > 0) {
0163             if (debug)
0164                 std::cerr << "transferring " << r2 << " from 2 to 1" << std::endl;
0165             opts = fcntl(side1, F_GETFL);
0166             opts ^= O_NONBLOCK;
0167             fcntl(side1, F_SETFL, opts);
0168 
0169             int w1 = send(side1, buffer2, r2, MSG_NOSIGNAL);
0170 
0171             if (w1 < 0) {
0172                 if (debug)
0173                     std::cerr << "writing to side 1 failed, ending: " << w1 << std::endl;
0174                 return;
0175             }
0176             assert(w1 == r2);
0177 
0178             opts = fcntl(side1, F_GETFL);
0179             opts |= O_NONBLOCK;
0180             fcntl(side1, F_SETFL, opts);
0181         }
0182         usleep(1000);
0183     }
0184 }
0185 
0186 int main(int argc, char** argv)
0187 {
0188     int serverfd;
0189 
0190     if (argc < 2) {
0191         std::cerr << "need arguments:" << std::endl;
0192         std::cerr <<
0193         "[port]                    -   Open a server on this TCP port and forward connections to the local DBUS session"
0194             "                              (the DBUS_SESSION_BUS_ADDRESS environment variable must be set)";
0195         std::cerr <<
0196         "[port]  [fake dbus path]  -   Open a server on the fake dbus path and forward connections to the given local TCP port";
0197         std::cerr << ""
0198             "The last argument may be the --bind-only option, in which case the application only tries to"
0199             "open the server, but does not wait for clients to connect. This is useful to test whether the"
0200             "server port/path is available.";
0201         return 10;
0202     }
0203 
0204     bool waitForClients = true;
0205 
0206     if (std::string(argv[argc - 1]) == "--bind-only") {
0207         waitForClients = false;
0208         argc -= 1;
0209     }
0210 
0211     std::string dbusAddress(getenv("DBUS_SESSION_BUS_ADDRESS"));
0212 
0213     std::string path;
0214 
0215     if (argc == 2) {
0216         if (waitForClients && debug)
0217             std::cout << "forwarding from the local TCP port " << argv[1] << " to the local DBUS session at " <<
0218             dbusAddress.data() << std::endl;
0219 
0220         if (dbusAddress.empty()) {
0221             std::cerr << "The DBUS_SESSION_BUS_ADDRESS environment variable is not set" << std::endl;
0222             return 1;
0223         }
0224 
0225         // Open a TCP server
0226 
0227         std::string abstractPrefix("unix:abstract=");
0228 
0229         if (dbusAddress.substr(0, abstractPrefix.size()) != abstractPrefix) {
0230             std::cerr << "DBUS_SESSION_BUS_ADDRESS does not seem to use an abstract unix domain socket as expected" <<
0231             std::endl;
0232             return 2;
0233         }
0234 
0235         path = dbusAddress.substr(abstractPrefix.size(), dbusAddress.size() - abstractPrefix.size());
0236         if (path.find(",guid=") != std::string::npos)
0237             path = path.substr(0, path.find(",guid="));
0238 
0239         // Mark it as an abstract unix domain socket
0240         serverfd = socket(AF_INET, SOCK_STREAM, 0);
0241 
0242         if (serverfd < 0) {
0243             if (waitForClients)
0244                 std::cerr << "ERROR opening server socket" << std::endl;
0245             return 3;
0246         }
0247 
0248         int portno = atoi(argv[1]);
0249 
0250         sockaddr_in server_addr;
0251 
0252         memset(&server_addr, 0, sizeof(server_addr));
0253         server_addr.sin_family = AF_INET;
0254         server_addr.sin_addr.s_addr = INADDR_ANY;
0255         server_addr.sin_port = htons(portno);
0256 
0257         if (bind(serverfd, ( struct sockaddr* ) &server_addr,
0258                  sizeof(server_addr)) < 0) {
0259             if (waitForClients)
0260                 std::cerr << "ERROR opening the server" << std::endl;
0261             return 7;
0262         }
0263     } else if (argc == 3) {
0264         if (waitForClients && debug)
0265             std::cout << "forwarding from the local abstract unix domain socket " << argv[2] <<
0266             " to the local TCP port " << argv[1] << std::endl;
0267         // Open a unix domain socket server
0268         serverfd = socket(AF_UNIX, SOCK_STREAM, 0);
0269 
0270         if (serverfd < 0) {
0271             if (waitForClients)
0272                 std::cerr << "ERROR opening server socket" << std::endl;
0273             return 3;
0274         }
0275 
0276         path = std::string(argv[2]);
0277 
0278         sockaddr_un serv_addr;
0279         memset(&serv_addr, 0, sizeof(serv_addr));
0280         serv_addr.sun_family = AF_UNIX;
0281         serv_addr.sun_path[0] = '\0'; // Mark as an abstract socket
0282         strcpy(serv_addr.sun_path + 1, path.data());
0283 
0284         if (debug)
0285             std::cout << "opening at " << path.data() << std::endl;
0286 
0287         if (bind(serverfd, ( sockaddr* ) &serv_addr, sizeof (serv_addr.sun_family) + 1 + path.length()) < 0) {
0288             if (waitForClients)
0289                 std::cerr << "ERROR opening the server" << std::endl;
0290             return 7;
0291         }
0292     } else {
0293         std::cerr << "Wrong arguments";
0294         return 1;
0295     }
0296 
0297     listen(serverfd, 10);
0298 
0299     while (waitForClients) {
0300         if (debug)
0301             std::cerr << "waiting for client" << std::endl;
0302         sockaddr_in cli_addr;
0303         socklen_t clilen = sizeof(cli_addr);
0304         int connectedclientsockfd = accept(serverfd,
0305                                            ( struct sockaddr* ) &cli_addr,
0306                                            &clilen);
0307 
0308         if (connectedclientsockfd < 0) {
0309             std::cerr << "ERROR on accept" << std::endl;
0310             return 8;
0311         }
0312 
0313         if (debug)
0314             std::cerr << "got client" << std::endl;
0315 
0316         int sockfd;
0317 
0318         int addrSize;
0319         sockaddr* useAddr = nullptr;
0320         sockaddr_un serv_addru;
0321         sockaddr_in serv_addrin;
0322 
0323         if (argc == 2) {
0324             sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
0325             if (sockfd < 0) {
0326                 std::cerr << "ERROR opening socket" << std::endl;
0327                 return 3;
0328             }
0329             memset(&serv_addru, 0, sizeof(serv_addru));
0330             serv_addru.sun_family = AF_UNIX;
0331             serv_addru.sun_path[0] = '\0'; // Mark as an abstract socket
0332             strcpy(serv_addru.sun_path + 1, path.data());
0333             addrSize = sizeof (serv_addru.sun_family) + 1 + path.size();
0334             useAddr = ( sockaddr* )&serv_addru;
0335 
0336             if (debug)
0337                 std::cout << "connecting to " << path.data() << std::endl;
0338         } else {
0339             sockfd = socket(AF_INET, SOCK_STREAM, 0);
0340             if (sockfd < 0) {
0341                 std::cerr << "ERROR opening socket" << std::endl;
0342                 return 3;
0343             }
0344             int port = atoi(argv[1]);
0345             hostent* server = gethostbyname("localhost");
0346             if (server == nullptr) {
0347                 std::cerr << "failed to get server" << std::endl;
0348                 return 5;
0349             }
0350             memset(&serv_addrin, 0, sizeof(serv_addrin));
0351             serv_addrin.sin_family = AF_INET;
0352             serv_addrin.sin_addr.s_addr = INADDR_ANY;
0353             serv_addrin.sin_port = htons(port);
0354             memcpy(&serv_addrin.sin_addr.s_addr, server->h_addr, server->h_length);
0355             addrSize = sizeof (serv_addrin);
0356             useAddr = ( sockaddr* )&serv_addrin;
0357 
0358             if (debug)
0359                 std::cout << "connecting to port " << port << std::endl;
0360         }
0361 
0362         if (connect(sockfd, useAddr, addrSize) < 0) {
0363             int res = errno;
0364             if (res == ECONNREFUSED)
0365                 std::cerr << "ERROR while connecting: connection refused" << std::endl;
0366             else if (res == ENOENT)
0367                 std::cerr << "ERROR while connecting: no such file or directory" << std::endl;
0368             else
0369                 std::cerr << "ERROR while connecting" << std::endl;
0370             return 5;
0371         }
0372 
0373         shuffleBetweenStreams(connectedclientsockfd, sockfd, argc == 2);
0374         close(sockfd);
0375         close(connectedclientsockfd);
0376     }
0377     return 0;
0378 }