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