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 }