|
11 | 11 | #include <QUuid> |
12 | 12 | #include <QtTest> |
13 | 13 | #ifndef Q_OS_WIN |
| 14 | +#include <sys/socket.h> |
14 | 15 | #include <sys/stat.h> |
| 16 | +#include <sys/un.h> |
| 17 | +#include <unistd.h> |
| 18 | + |
| 19 | +#include <cstring> |
15 | 20 | #endif |
16 | 21 |
|
17 | 22 | #include "../../../src/enums.h" |
@@ -226,6 +231,11 @@ private Q_SLOTS: |
226 | 231 | void isPathInStoreRejectsEmptyArgs(); |
227 | 232 | // .gpg-id permission hardening (security) |
228 | 233 | void writeGpgIdFileSetsOwnerOnlyPerms(); |
| 234 | + // SSH_AUTH_SOCK override soft-validation (Settings dialog warning) |
| 235 | + void sshAuthSockOverrideStatusDoesNotExist(); |
| 236 | + void sshAuthSockOverrideStatusRegularFileRejected(); |
| 237 | + void sshAuthSockOverrideStatusNotReadable(); |
| 238 | + void sshAuthSockOverrideStatusValid(); |
229 | 239 | }; |
230 | 240 |
|
231 | 241 | /** |
@@ -2327,5 +2337,115 @@ void tst_util::writeGpgIdFileSetsOwnerOnlyPerms() { |
2327 | 2337 | #endif |
2328 | 2338 | } |
2329 | 2339 |
|
| 2340 | +// --------------------------------------------------------------------------- |
| 2341 | +// Util::sshAuthSockOverrideStatus tests |
| 2342 | +// |
| 2343 | +// Backs the Settings dialog soft-warning logic added in #1439. The dialog |
| 2344 | +// shows a non-blocking warning when the user-typed override path is bogus |
| 2345 | +// (missing, unreadable, or not a Unix domain socket) but still saves the |
| 2346 | +// value as entered. These tests drive the pure validation function directly |
| 2347 | +// so we don't need a Qt event loop / QMessageBox spy. |
| 2348 | +// --------------------------------------------------------------------------- |
| 2349 | + |
| 2350 | +/** |
| 2351 | + * @brief A non-existent path is classified DoesNotExist. |
| 2352 | + */ |
| 2353 | +void tst_util::sshAuthSockOverrideStatusDoesNotExist() { |
| 2354 | + QTemporaryDir tmp; |
| 2355 | + QVERIFY2(tmp.isValid(), "tmp dir should be valid"); |
| 2356 | + QCOMPARE(Util::sshAuthSockOverrideStatus(tmp.path() + "/no-such-socket"), |
| 2357 | + Util::SshAuthSockOverrideStatus::DoesNotExist); |
| 2358 | +} |
| 2359 | + |
| 2360 | +/** |
| 2361 | + * @brief A regular file that exists but isn't a Unix domain socket is |
| 2362 | + * rejected on Unix. Skipped on Windows where the socket check is a |
| 2363 | + * no-op (ssh-agent uses a named pipe there). |
| 2364 | + */ |
| 2365 | +void tst_util::sshAuthSockOverrideStatusRegularFileRejected() { |
| 2366 | +#ifdef Q_OS_WIN |
| 2367 | + QSKIP("socket-type check is Unix-only"); |
| 2368 | +#else |
| 2369 | + QTemporaryDir tmp; |
| 2370 | + QVERIFY2(tmp.isValid(), "tmp dir should be valid"); |
| 2371 | + const QString filePath = tmp.path() + "/regular-file"; |
| 2372 | + QFile f(filePath); |
| 2373 | + QVERIFY2(f.open(QIODevice::WriteOnly), "should be able to create a file"); |
| 2374 | + f.close(); |
| 2375 | + QCOMPARE(Util::sshAuthSockOverrideStatus(filePath), |
| 2376 | + Util::SshAuthSockOverrideStatus::NotUnixDomainSocket); |
| 2377 | +#endif |
| 2378 | +} |
| 2379 | + |
| 2380 | +/** |
| 2381 | + * @brief A file that exists but has no read permission is classified |
| 2382 | + * NotReadable. Skipped on Windows because Qt's permission bits |
| 2383 | + * don't round-trip the same way. |
| 2384 | + */ |
| 2385 | +void tst_util::sshAuthSockOverrideStatusNotReadable() { |
| 2386 | +#ifdef Q_OS_WIN |
| 2387 | + QSKIP("Unix permission bits don't round-trip on Windows"); |
| 2388 | +#else |
| 2389 | + // Root user on Linux ignores read-mode bits, so the test would |
| 2390 | + // falsely return Valid. Skip in that case. |
| 2391 | + if (::geteuid() == 0) { |
| 2392 | + QSKIP("root sees all files as readable; skip"); |
| 2393 | + } |
| 2394 | + QTemporaryDir tmp; |
| 2395 | + QVERIFY2(tmp.isValid(), "tmp dir should be valid"); |
| 2396 | + const QString filePath = tmp.path() + "/unreadable"; |
| 2397 | + QFile f(filePath); |
| 2398 | + QVERIFY2(f.open(QIODevice::WriteOnly), "should be able to create a file"); |
| 2399 | + f.close(); |
| 2400 | + QVERIFY2(QFile::setPermissions(filePath, QFile::Permissions{}), |
| 2401 | + "should be able to chmod 0 on the file"); |
| 2402 | + QCOMPARE(Util::sshAuthSockOverrideStatus(filePath), |
| 2403 | + Util::SshAuthSockOverrideStatus::NotReadable); |
| 2404 | + // Restore something so QTemporaryDir can clean up. |
| 2405 | + QFile::setPermissions(filePath, QFile::ReadOwner | QFile::WriteOwner); |
| 2406 | +#endif |
| 2407 | +} |
| 2408 | + |
| 2409 | +/** |
| 2410 | + * @brief A real Unix domain socket (bind(2)) is classified Valid. |
| 2411 | + * Unix-only. |
| 2412 | + */ |
| 2413 | +void tst_util::sshAuthSockOverrideStatusValid() { |
| 2414 | +#ifdef Q_OS_WIN |
| 2415 | + QSKIP("socket creation API differs on Windows"); |
| 2416 | +#else |
| 2417 | + QTemporaryDir tmp; |
| 2418 | + QVERIFY2(tmp.isValid(), "tmp dir should be valid"); |
| 2419 | + const QString sockPath = tmp.path() + "/agent.sock"; |
| 2420 | + const QByteArray sockBytes = sockPath.toLocal8Bit(); |
| 2421 | + |
| 2422 | + // sun_path is typically 108 bytes on Linux; QTemporaryDir gives us a |
| 2423 | + // short /tmp/qttest_XXXXXX prefix, so this should fit easily. |
| 2424 | + QVERIFY2(sockBytes.size() < static_cast<int>(sizeof(sockaddr_un{}.sun_path)), |
| 2425 | + "socket path too long for sockaddr_un"); |
| 2426 | + |
| 2427 | + const int fd = ::socket(AF_UNIX, SOCK_STREAM, 0); |
| 2428 | + QVERIFY2(fd >= 0, "socket(AF_UNIX) failed"); |
| 2429 | + |
| 2430 | + sockaddr_un addr{}; |
| 2431 | + addr.sun_family = AF_UNIX; |
| 2432 | + std::memcpy(addr.sun_path, sockBytes.constData(), |
| 2433 | + static_cast<size_t>(sockBytes.size())); |
| 2434 | + |
| 2435 | + const int br = ::bind(fd, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)); |
| 2436 | + if (br != 0) { |
| 2437 | + ::close(fd); |
| 2438 | + QFAIL("bind(AF_UNIX) failed"); |
| 2439 | + } |
| 2440 | + |
| 2441 | + QCOMPARE(Util::sshAuthSockOverrideStatus(sockPath), |
| 2442 | + Util::SshAuthSockOverrideStatus::Valid); |
| 2443 | + |
| 2444 | + ::close(fd); |
| 2445 | + // QTemporaryDir will rm -rf the directory on destruction, removing the |
| 2446 | + // socket file along with it. |
| 2447 | +#endif |
| 2448 | +} |
| 2449 | + |
2330 | 2450 | QTEST_MAIN(tst_util) |
2331 | 2451 | #include "tst_util.moc" |
0 commit comments