From 578833446dcd8104046c2147cc46944dbe05e8c2 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 27 Apr 2021 16:16:20 +0400 Subject: [PATCH] Add support for write-protected update on Linux --- Telegram/SourceFiles/_other/updater_linux.cpp | 36 +++++++++++++++---- Telegram/SourceFiles/core/update_checker.cpp | 24 +++++++++++-- .../platform/linux/launcher_linux.cpp | 32 ++++++++++++++--- 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index ce4da02be..397f10056 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include #include #include #include @@ -88,7 +89,7 @@ void writeLog(const char *format, ...) { va_end(args); } -bool copyFile(const char *from, const char *to) { +bool copyFile(const char *from, const char *to, bool writeprotected) { FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); if (!ffrom) { if (fto) fclose(fto); @@ -134,7 +135,7 @@ bool copyFile(const char *from, const char *to) { } //update to the same uid/gid - if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { + if (!writeprotected && fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { fclose(ffrom); fclose(fto); return false; @@ -233,7 +234,7 @@ void delFolder() { rmdir(delFolder.c_str()); } -bool update() { +bool update(bool writeprotected) { writeLog("Update started.."); string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready", tdataDir = workDir + "tupdates/temp/tdata"; @@ -346,7 +347,7 @@ bool update() { writeLog("Copying file '%s' to '%s'..", fname.c_str(), tofname.c_str()); int copyTries = 0, triesLimit = 30; do { - if (!copyFile(fname.c_str(), tofname.c_str())) { + if (!copyFile(fname.c_str(), tofname.c_str(), writeprotected)) { ++copyTries; usleep(100000); } else { @@ -381,6 +382,7 @@ int main(int argc, char *argv[]) { bool needupdate = true; bool autostart = false; bool debug = false; + bool writeprotected = false; bool tosettings = false; bool startintray = false; bool testmode = false; @@ -389,6 +391,8 @@ int main(int argc, char *argv[]) { char *key = 0; char *workdir = 0; + char *oldUsername = 0; + char *dbusAddress = 0; for (int i = 1; i < argc; ++i) { if (equal(argv[i], "-noupdate")) { needupdate = false; @@ -408,12 +412,17 @@ int main(int argc, char *argv[]) { customWorkingDir = true; } else if (equal(argv[i], "-key") && ++i < argc) { key = argv[i]; + } else if (equal(argv[i], "-writeprotected") && ++i < argc) { + writeprotected = true; + oldUsername = argv[i]; } else if (equal(argv[i], "-workpath") && ++i < argc) { workDir = workdir = argv[i]; } else if (equal(argv[i], "-exename") && ++i < argc) { exeName = argv[i]; } else if (equal(argv[i], "-exepath") && ++i < argc) { exePath = argv[i]; + } else if (equal(argv[i], "-dbus") && ++i < argc) { + dbusAddress = argv[i]; } } if (exeName.empty() || exeName.find('/') != string::npos) { @@ -427,6 +436,7 @@ int main(int argc, char *argv[]) { } if (needupdate) writeLog("Need to update!"); if (autostart) writeLog("From autostart!"); + if (writeprotected) writeLog("Write Protected folder!"); updaterName = CurrentExecutablePath(argc, argv); writeLog("Updater binary full path is: %s", updaterName.c_str()); @@ -477,7 +487,7 @@ int main(int argc, char *argv[]) { } else { writeLog("Passed workpath is '%s'", workDir.c_str()); } - update(); + update(writeprotected); } } else { writeLog("Error: bad exe name!"); @@ -494,6 +504,15 @@ int main(int argc, char *argv[]) { // Force null-terminated .data() call result. values.push_back(arg + char(0)); }; + if (writeprotected) { // run un-elevated + push("pkexec"); + push("--user"); + push(oldUsername); + push("env"); + push("DBUS_SESSION_BUS_ADDRESS=" + string(dbusAddress)); + push("systemd-run"); // restore environment + push("--user"); + } push(path); push("-noupdate"); if (autostart) push("-autostart"); @@ -523,10 +542,15 @@ int main(int argc, char *argv[]) { writeLog("fork() failed!"); return 1; case 0: - execv(path, args.data()); + execvp(args[0], args.data()); return 1; } + // pkexec needs an alive parent + if (writeprotected) { + waitpid(pid, nullptr, 0); + } + writeLog("Executed Telegram, closing log and quitting.."); closeLog(); diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index a07e29d25..f96d85f58 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -45,6 +45,10 @@ extern "C" { #include #endif // else of Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED +#ifdef Q_OS_UNIX +#include +#endif // Q_OS_UNIX + namespace Core { namespace { @@ -1553,9 +1557,25 @@ bool checkReadyUpdate() { return false; } #elif defined Q_OS_UNIX // Q_OS_MAC + // if the files in the directory are owned by user, while the directory is not, + // update will still fail since it's not possible to remove files + if (unlink(QFile::encodeName(curUpdater).constData())) { + if (errno == EACCES) { + cSetWriteProtected(true); + return true; + } else { + ClearAll(); + return false; + } + } if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) { - ClearAll(); - return false; + if (errno == EACCES) { + cSetWriteProtected(true); + return true; + } else { + ClearAll(); + return false; + } } #endif // Q_OS_UNIX diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index fe219721e..6bb9bffd4 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include #include #include #include @@ -75,12 +76,17 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { return false; } - const auto binaryName = (action == UpdaterLaunch::JustRelaunch) - ? cExeName() - : QStringLiteral("Updater"); + const auto binaryPath = (action == UpdaterLaunch::JustRelaunch) + ? (cExeDir() + cExeName()) + : (cWriteProtected() + ? (cWorkingDir() + qsl("tupdates/temp/Updater")) + : (cExeDir() + qsl("Updater"))); auto argumentsList = Arguments(); - argumentsList.push(QFile::encodeName(cExeDir() + binaryName)); + if (action != UpdaterLaunch::JustRelaunch && cWriteProtected()) { + argumentsList.push("pkexec"); + } + argumentsList.push(QFile::encodeName(binaryPath)); if (cLaunchMode() == LaunchModeAutoStart) { argumentsList.push("-autostart"); @@ -118,6 +124,16 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { if (customWorkingDir()) { argumentsList.push("-workdir_custom"); } + if (cWriteProtected()) { + const auto currentUid = geteuid(); + const auto pw = getpwuid(currentUid); + if (pw) { + argumentsList.push("-writeprotected"); + argumentsList.push(pw->pw_name); + argumentsList.push("-dbus"); + argumentsList.push(qgetenv("DBUS_SESSION_BUS_ADDRESS")); + } + } } Logs::closeMain(); @@ -128,8 +144,14 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { pid_t pid = fork(); switch (pid) { case -1: return false; - case 0: execv(args[0], args); return false; + case 0: execvp(args[0], args); return false; } + + // pkexec needs an alive parent + if (cWriteProtected()) { + waitpid(pid, nullptr, 0); + } + return true; }