1 /*
2 * Copyright 2015-2021 The Regents of the University of California
3 * All rights reserved.
4 *
5 * This file is part of Spoofer.
6 *
7 * Spoofer is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Spoofer is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Spoofer. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <unistd.h>
22 #include <errno.h>
23 #include <signal.h>
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h> // umask()
27 #include <syslog.h> // syslog()
28 #include <sys/wait.h> // waitpid()
29 #include "spoof_qt.h"
30 #include <QSocketNotifier>
31 #include <QLockFile>
32 #include <QCoreApplication>
33 #include "../../config.h"
34 #include "app.h"
35 #include "appunix.h"
36 #include "common.h"
37 static const char cvsid[] ATR_USED = "$Id: appunix.cpp,v 1.34 2021/04/28 17:39:09 kkeys Exp $";
38
39 int AppUnix::psPipe[2]; // pipe for reporting posix signals
40
lastErrorString()41 QString App::lastErrorString()
42 {
43 return QSL("%1 (error %2)")
44 .arg(QString::fromLocal8Bit(strerror(errno)))
45 .arg(errno);
46 }
47
open(QIODevice::OpenMode mode)48 bool AppUnix::Syslog::open(QIODevice::OpenMode mode) {
49 Q_UNUSED(mode);
50 openlog(APPNAME, LOG_PID, LOG_USER);
51 return QIODevice::open(WriteOnly|Unbuffered);
52 }
53
writeData(const char * data,qint64 maxSize)54 qint64 AppUnix::Syslog::writeData(const char *data, qint64 maxSize)
55 {
56 syslog(LOG_NOTICE, "%.*s", (int)maxSize, data);
57 return maxSize;
58 }
59
AppUnix(int & argc,char ** argv)60 AppUnix::AppUnix(int &argc, char **argv) :
61 App(argc, argv), installerPid(0), psNotifier()
62 {
63 isInteractive = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO);
64 // Note: verifyDaemon() ensures the parent doesn't exit too quickly, so
65 // we'll see the real parent and not 1.
66 if (getppid() == 1) { // started by unix init or mac osx launchd
67 errdev.setDevice(new Syslog(), QSL("syslog"));
68 errdev.setTimestampEnabled(false);
69 }
70 }
71
verifyDaemon(pid_t childPid)72 bool AppUnix::verifyDaemon(pid_t childPid)
73 {
74 QLockFile lockFile(config->lockFileName());
75 qDebug() << "waiting for child to lock" << config->lockFileName();
76 qint64 qpid;
77 while (!lockFile.getLockInfo(&qpid, nullptr, nullptr) ||
78 qpid != childPid) // child is not ready
79 {
80 if (waitpid(childPid, nullptr, WNOHANG)) { // child exited
81 sperr << "Daemon exited prematurely" << Qt_endl;
82 return false;
83 }
84 usleep(100000);
85 }
86 return true;
87 }
88
prestart(int & exitCode)89 bool AppUnix::prestart(int &exitCode)
90 {
91 if (optDetach) {
92 // NB: we must NOT detach (daemonize) if started from Mac OSX launchd
93 pid_t pid;
94 if ((pid = fork()) < 0) {
95 exitCode = errno;
96 sperr << "can't fork: " << strerror(errno) << Qt_endl;
97 return false; // skip app.exec()
98 } else if (pid > 0) {
99 // parent
100 pApplabel = QSL(" parent");
101 exitCode = verifyDaemon(pid) ? SP_EXIT_OK : SP_EXIT_DAEMON_FAILED;
102 return false; // skip app.exec() in parent
103 }
104 // child
105 pApplabel = QSL(" daemon");
106 if (errdev.type() == typeid(Syslog)) {
107 // parent was using syslog; child continues to use syslog
108 } else {
109 // parent was using logfile or stderr; child opens new logfile
110 errdev.setDevice(new AppLog());
111 }
112 setsid(); // become session leader
113 }
114
115 umask(022);
116 return true;
117 }
118
psHandler(int sig)119 void AppUnix::psHandler(int sig)
120 {
121 if (::write(psPipe[1], &sig, sizeof(sig)) < 0)
122 return; // shouldn't happen, and there's nothing we can do anyway
123 }
124
psSlot()125 void AppUnix::psSlot()
126 {
127 int sig = 0;
128 psNotifier->setEnabled(false);
129 if (::read(psPipe[0], &sig, sizeof(sig)) < 0)
130 sig = 0; // shouldn't happen
131 sperr << "Scheduler caught signal " << sig << Qt_endl;
132 this->exit(EINTR);
133 psNotifier->setEnabled(true);
134 }
135
initSignals()136 bool AppUnix::initSignals()
137 {
138 // It's not safe to call Qt functions from POSIX signal handlers. So we
139 // convert POSIX signals to Qt signals using a POSIX signal handler that
140 // safely writes to a pipe and a QSocketNotifier that emits a Qt signal
141 // when the pipe has something to read.
142 if (::socketpair(AF_UNIX, SOCK_STREAM, 0, psPipe) < 0)
143 sperr << "socketpair " << strerror(errno) << Qt_endl;
144 psNotifier = new QSocketNotifier(psPipe[0], QSocketNotifier::Read, this);
145 connect(psNotifier, &QSocketNotifier::activated, this, &AppUnix::psSlot);
146
147 struct sigaction act;
148 act.sa_handler = AppUnix::psHandler;
149 sigemptyset(&act.sa_mask);
150 act.sa_flags = SA_RESTART;
151 for (int sig : {SIGTERM, SIGINT, SIGHUP}) {
152 if (sigaction(sig, &act, nullptr) < 0)
153 sperr << "sigaction(" << sig << "): " << strerror(errno) << Qt_endl;
154 }
155 return true;
156 }
157
158 #ifndef EVERYONE_IS_PRIVILEGED
startProber(bool manual)159 void AppUnix::startProber(bool manual)
160 {
161 mode_t oldmask = 07000;
162 if (!config->unprivView.variant().toBool())
163 oldmask = umask(0077);
164 App::startProber(manual);
165 if (oldmask != 07000)
166 umask(oldmask);
167 }
168 #endif
169
170 #ifdef AUTOUPGRADE_ENABLED
installerIsRunning()171 bool AppUnix::installerIsRunning()
172 {
173 int status;
174 pid_t pid = waitpid(installerPid, &status, WNOHANG);
175 if (pid == -1) return false;
176 if (pid == 0) return true;
177 if (WIFEXITED(status))
178 qWarning() << "installer exit code:" << WEXITSTATUS(status);
179 else if (WIFSIGNALED(status))
180 qWarning() << "installer killed by signal" << WTERMSIG(status);
181 return false;
182 }
183
killInstaller()184 void AppUnix::killInstaller()
185 {
186 if (installerPid > 0) {
187 // use negative pid to kill every process in the group
188 if (kill(-installerPid, SIGTERM) < 0)
189 qDebug() << "failed to kill" << installerPid << strerror(errno);
190 else
191 qDebug() << "killed" << installerPid;
192 }
193 }
194
executeInstaller(const QString & installerName)195 void AppUnix::executeInstaller(const QString &installerName)
196 {
197 QString script = QSL("{ "
198 "echo \"Installing $1\"; "
199 "FromTo=\"from " PACKAGE_VERSION " to $4\"; "
200 UPGRADE_CMD "; "
201 "exitcode=$?; "
202 "if test $exitcode -eq 0; then ") %
203 #ifndef UPGRADE_WITHOUT_DOWNLOAD
204 (config->installerKeep() ? QSL("") : QSL("rm \"$1\"; ")) %
205 #endif
206 QSL("msg=\"Successfully upgraded $FromTo\"; "
207 "else "
208 "msg=\"Failed to upgrade $FromTo (exit code $exitcode)\"; "
209 "fi; "
210 "echo \"$msg at `date +'%Y-%m-%d %H:%M:%S'`\" >\"$2\"; "
211 "echo \"$msg\"; "
212 "exit $exitcode; "
213 "} >\"$3/upgrade-log.txt\" 2>&1");
214
215 qDebug().noquote() << QSL("script:") << script;
216 installerPid = fork();
217 if (installerPid < 0) { // error
218 abortInstallation(QSL("fork: ") % lastErrorString());
219 return;
220 } else if (installerPid == 0) { // child
221 setsid(); // so the child won't get the kill signal meant for the parent
222 execl("/bin/sh", "/bin/sh", "-c", script.toStdString().data(),
223 "spoofer-scheduler-upgrade", // $0
224 installerName.toStdString().data(), // $1
225 upFinName().toStdString().data(), // $2
226 dataDir.toStdString().data(), // $3
227 upgradeInfo->vstr.toStdString().data(), // $4
228 nullptr);
229 // UNREACHABLE
230 qCritical() << "Failed to exec sh script:" << lastErrorString();
231 exit(1);
232 } else { // parent
233 qDebug() << "executing sh script, pid" << installerPid;
234 }
235 }
236 #endif // AUTOUPGRADE_ENABLED
237