Files
znc/src/main.cpp
Alexey Sokolov ae3eb5c219 Revert "Revert "Fix delay between receiving signal in separate thread and handling it""
This reverts commit 615d936b6c.

I forgot the condition before...
2016-01-23 11:57:48 +00:00

554 lines
18 KiB
C++

/*
* Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <znc/znc.h>
#include <signal.h>
#include <time.h>
#include <thread>
#if defined(HAVE_LIBSSL) && defined(HAVE_PTHREAD)
#include <znc/Threads.h>
#include <openssl/crypto.h>
#include <memory>
static std::vector<std::unique_ptr<CMutex>> lock_cs;
static void locking_callback(int mode, int type, const char* file, int line) {
if (mode & CRYPTO_LOCK) {
lock_cs[type]->lock();
} else {
lock_cs[type]->unlock();
}
}
static unsigned long thread_id_callback() {
return (unsigned long)pthread_self();
}
static CRYPTO_dynlock_value* dyn_create_callback(const char* file, int line) {
return (CRYPTO_dynlock_value*)new CMutex;
}
static void dyn_lock_callback(int mode, CRYPTO_dynlock_value* dlock,
const char* file, int line) {
CMutex* mtx = (CMutex*)dlock;
if (mode & CRYPTO_LOCK) {
mtx->lock();
} else {
mtx->unlock();
}
}
static void dyn_destroy_callback(CRYPTO_dynlock_value* dlock, const char* file,
int line) {
CMutex* mtx = (CMutex*)dlock;
delete mtx;
}
static void thread_setup() {
lock_cs.resize(CRYPTO_num_locks());
for (std::unique_ptr<CMutex>& mtx : lock_cs)
mtx = std::unique_ptr<CMutex>(new CMutex());
CRYPTO_set_id_callback(&thread_id_callback);
CRYPTO_set_locking_callback(&locking_callback);
CRYPTO_set_dynlock_create_callback(&dyn_create_callback);
CRYPTO_set_dynlock_lock_callback(&dyn_lock_callback);
CRYPTO_set_dynlock_destroy_callback(&dyn_destroy_callback);
}
#else
#define thread_setup()
#endif
using std::cout;
using std::endl;
using std::set;
#ifdef HAVE_GETOPT_LONG
#include <getopt.h>
#else
#define no_argument 0
#define required_argument 1
#define optional_argument 2
struct option {
const char* a;
int opt;
int* flag;
int val;
};
static inline int getopt_long(int argc, char* const argv[],
const char* optstring, const struct option*,
int*) {
return getopt(argc, argv, optstring);
}
#endif
static const struct option g_LongOpts[] = {
{"help", no_argument, nullptr, 'h'},
{"version", no_argument, nullptr, 'v'},
{"debug", no_argument, nullptr, 'D'},
{"foreground", no_argument, nullptr, 'f'},
{"no-color", no_argument, nullptr, 'n'},
{"allow-root", no_argument, nullptr, 'r'},
{"makeconf", no_argument, nullptr, 'c'},
{"makepass", no_argument, nullptr, 's'},
{"makepem", no_argument, nullptr, 'p'},
{"datadir", required_argument, nullptr, 'd'},
{nullptr, 0, nullptr, 0}};
static void GenerateHelp(const char* appname) {
CUtils::PrintMessage("USAGE: " + CString(appname) + " [options]");
CUtils::PrintMessage("Options are:");
CUtils::PrintMessage(
"\t-h, --help List available command line options (this page)");
CUtils::PrintMessage(
"\t-v, --version Output version information and exit");
CUtils::PrintMessage("\t-f, --foreground Don't fork into the background");
CUtils::PrintMessage(
"\t-D, --debug Output debugging information (Implies -f)");
CUtils::PrintMessage(
"\t-n, --no-color Don't use escape sequences in the output");
CUtils::PrintMessage(
"\t-r, --allow-root Don't complain if ZNC is run as root");
CUtils::PrintMessage(
"\t-c, --makeconf Interactively create a new config");
CUtils::PrintMessage(
"\t-s, --makepass Generates a password for use in config");
#ifdef HAVE_LIBSSL
CUtils::PrintMessage(
"\t-p, --makepem Generates a pemfile for use with SSL");
#endif /* HAVE_LIBSSL */
CUtils::PrintMessage(
"\t-d, --datadir Set a different ZNC repository (default is "
"~/.znc)");
}
class CSignalHandler {
public:
CSignalHandler(CZNC* pZNC) {
if (pipe(m_iPipe)) {
DEBUG("Ouch, can't open pipe for signal handler: "
<< strerror(errno));
exit(1);
}
pZNC->GetManager().MonitorFD(new CSignalHandlerMonitorFD(m_iPipe[0]));
sigset_t signals;
sigfillset(&signals);
pthread_sigmask(SIG_SETMASK, &signals, nullptr);
m_thread = std::thread([=]() { HandleSignals(pZNC); });
}
~CSignalHandler() {
pthread_cancel(m_thread.native_handle());
m_thread.join();
}
private:
class CSignalHandlerMonitorFD : public CSMonitorFD {
// This class just prevents the pipe buffer from filling by clearing it
public:
CSignalHandlerMonitorFD(int fd) { Add(fd, CSockManager::ECT_Read); }
bool FDsThatTriggered(
const std::map<int, short>& miiReadyFds) override {
for (const auto& it : miiReadyFds) {
if (it.second) {
int sig;
read(it.first, &sig, sizeof(sig));
}
}
return true;
}
};
void HandleSignals(CZNC* pZNC) {
sigset_t signals;
sigemptyset(&signals);
sigaddset(&signals, SIGHUP);
sigaddset(&signals, SIGUSR1);
sigaddset(&signals, SIGINT);
sigaddset(&signals, SIGQUIT);
sigaddset(&signals, SIGTERM);
sigaddset(&signals, SIGPIPE);
// Handle only these signals specially; the rest will have their default
// action, but in this thread
pthread_sigmask(SIG_SETMASK, &signals, nullptr);
while (true) {
int sig;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr);
// This thread can be cancelled, but only during this function.
// Such cancel will be the only way to finish this thread.
if (sigwait(&signals, &sig) == -1) continue;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, nullptr);
// TODO probably move switch() to CSignalHandlerMonitorFD?
switch (sig) {
case SIGHUP:
pZNC->SetConfigState(CZNC::ECONFIG_NEED_REHASH);
break;
case SIGUSR1:
pZNC->SetConfigState(CZNC::ECONFIG_NEED_VERBOSE_WRITE);
break;
case SIGINT:
case SIGQUIT:
case SIGTERM:
pZNC->SetConfigState(CZNC::ECONFIG_NEED_QUIT);
// Reset handler to default by:
// * not blocking it
// * not waiting for it
// So, if ^C is pressed, but for some reason it didn't work,
// second ^C will kill the process for sure.
sigdelset(&signals, sig);
pthread_sigmask(SIG_SETMASK, &signals, nullptr);
break;
case SIGPIPE:
default:
break;
}
// This write() must succeed because POSIX guarantees that writes of
// less than PIPE_BUF are atomic (and PIPE_BUF is at least 512).
size_t w = write(m_iPipe[1], &sig, sizeof(sig));
if (w != sizeof(sig)) {
DEBUG(
"Something bad happened during write() to a pipe for "
"signal handler, wrote "
<< w << " bytes: " << strerror(errno));
exit(1);
}
}
}
std::thread m_thread;
// pipe for waking up the main thread
int m_iPipe[2];
};
static bool isRoot() {
// User root? If one of these were root, we could switch the others to root,
// too
return (geteuid() == 0 || getuid() == 0);
}
static void seedPRNG() {
struct timeval tv;
unsigned int seed;
// Try to find a seed which can't be as easily guessed as only time()
if (gettimeofday(&tv, nullptr) == 0) {
seed = (unsigned int)tv.tv_sec;
// This is in [0:1e6], which means that roughly 20 bits are
// actually used, let's try to shuffle the high bits.
seed ^= uint32_t((tv.tv_usec << 10) | tv.tv_usec);
} else
seed = (unsigned int)time(nullptr);
seed ^= rand();
seed ^= getpid();
srand(seed);
}
int main(int argc, char** argv) {
CString sConfig;
CString sDataDir = "";
thread_setup();
seedPRNG();
CDebug::SetStdoutIsTTY(isatty(1));
int iArg, iOptIndex = -1;
bool bMakeConf = false;
bool bMakePass = false;
bool bAllowRoot = false;
bool bForeground = false;
#ifdef ALWAYS_RUN_IN_FOREGROUND
bForeground = true;
#endif
#ifdef HAVE_LIBSSL
bool bMakePem = false;
#endif
CZNC::CreateInstance();
while ((iArg = getopt_long(argc, argv, "hvnrcspd:Df", g_LongOpts,
&iOptIndex)) != -1) {
switch (iArg) {
case 'h':
GenerateHelp(argv[0]);
return 0;
case 'v':
cout << CZNC::GetTag() << endl;
cout << CZNC::GetCompileOptionsString() << endl;
return 0;
case 'n':
CDebug::SetStdoutIsTTY(false);
break;
case 'r':
bAllowRoot = true;
break;
case 'c':
bMakeConf = true;
break;
case 's':
bMakePass = true;
break;
case 'p':
#ifdef HAVE_LIBSSL
bMakePem = true;
break;
#else
CUtils::PrintError("ZNC is compiled without SSL support.");
return 1;
#endif /* HAVE_LIBSSL */
case 'd':
sDataDir = CString(optarg);
break;
case 'f':
bForeground = true;
break;
case 'D':
bForeground = true;
CDebug::SetDebug(true);
break;
case '?':
default:
GenerateHelp(argv[0]);
return 1;
}
}
if (optind < argc) {
CUtils::PrintError("Unrecognized command line arguments.");
CUtils::PrintError("Did you mean to run `/znc " +
CString(argv[optind]) + "' in IRC client instead?");
CUtils::PrintError("Hint: `/znc " + CString(argv[optind]) +
"' is an alias for `/msg *status " +
CString(argv[optind]) + "'");
return 1;
}
CZNC* pZNC = &CZNC::Get();
pZNC->InitDirs(((argc) ? argv[0] : ""), sDataDir);
#ifdef HAVE_LIBSSL
if (bMakePem) {
pZNC->WritePemFile();
CZNC::DestroyInstance();
return 0;
}
#endif /* HAVE_LIBSSL */
if (bMakePass) {
CString sSalt;
CUtils::PrintMessage("Type your new password.");
CString sHash = CUtils::GetSaltedHashPass(sSalt);
CUtils::PrintMessage("Kill ZNC process, if it's running.");
CUtils::PrintMessage(
"Then replace password in the <User> section of your config with "
"this:");
// Not PrintMessage(), to remove [**] from the beginning, to ease
// copypasting
std::cout << "<Pass password>" << std::endl;
std::cout << "\tMethod = " << CUtils::sDefaultHash << std::endl;
std::cout << "\tHash = " << sHash << std::endl;
std::cout << "\tSalt = " << sSalt << std::endl;
std::cout << "</Pass>" << std::endl;
CUtils::PrintMessage(
"After that start ZNC again, and you should be able to login with "
"the new password.");
CZNC::DestroyInstance();
return 0;
}
{
set<CModInfo> ssGlobalMods;
set<CModInfo> ssUserMods;
set<CModInfo> ssNetworkMods;
CUtils::PrintAction("Checking for list of available modules");
pZNC->GetModules().GetAvailableMods(ssGlobalMods,
CModInfo::GlobalModule);
pZNC->GetModules().GetAvailableMods(ssUserMods, CModInfo::UserModule);
pZNC->GetModules().GetAvailableMods(ssNetworkMods,
CModInfo::NetworkModule);
if (ssGlobalMods.empty() && ssUserMods.empty() &&
ssNetworkMods.empty()) {
CUtils::PrintStatus(false, "");
CUtils::PrintError(
"No modules found. Perhaps you didn't install ZNC properly?");
CUtils::PrintError(
"Read http://wiki.znc.in/Installation for instructions.");
if (!CUtils::GetBoolInput(
"Do you really want to run ZNC without any modules?",
false)) {
CZNC::DestroyInstance();
return 1;
}
}
CUtils::PrintStatus(true, "");
}
if (isRoot()) {
CUtils::PrintError(
"You are running ZNC as root! Don't do that! There are not many "
"valid");
CUtils::PrintError(
"reasons for this and it can, in theory, cause great damage!");
if (!bAllowRoot) {
CZNC::DestroyInstance();
return 1;
}
CUtils::PrintError("You have been warned.");
CUtils::PrintError(
"Hit CTRL+C now if you don't want to run ZNC as root.");
CUtils::PrintError("ZNC will start in 30 seconds.");
sleep(30);
}
if (bMakeConf) {
if (!pZNC->WriteNewConfig(sConfig)) {
CZNC::DestroyInstance();
return 0;
}
/* Fall through to normal bootup */
}
CString sConfigError;
if (!pZNC->ParseConfig(sConfig, sConfigError)) {
CUtils::PrintError("Unrecoverable config error.");
CZNC::DestroyInstance();
return 1;
}
if (!pZNC->OnBoot()) {
CUtils::PrintError("Exiting due to module boot errors.");
CZNC::DestroyInstance();
return 1;
}
if (bForeground) {
int iPid = getpid();
CUtils::PrintMessage("Staying open for debugging [pid: " +
CString(iPid) + "]");
pZNC->WritePidFile(iPid);
CUtils::PrintMessage(CZNC::GetTag());
} else {
CUtils::PrintAction("Forking into the background");
int iPid = fork();
if (iPid == -1) {
CUtils::PrintStatus(false, strerror(errno));
CZNC::DestroyInstance();
return 1;
}
if (iPid > 0) {
// We are the parent. We are done and will go to bed.
CUtils::PrintStatus(true, "[pid: " + CString(iPid) + "]");
pZNC->WritePidFile(iPid);
CUtils::PrintMessage(CZNC::GetTag());
/* Don't destroy pZNC here or it will delete the pid file. */
return 0;
}
/* fcntl() locks don't necessarily propagate to forked()
* children. Reacquire the lock here. Use the blocking
* call to avoid race condition with parent exiting.
*/
if (!pZNC->WaitForChildLock()) {
CUtils::PrintError(
"Child was unable to obtain lock on config file.");
CZNC::DestroyInstance();
return 1;
}
// Redirect std in/out/err to /dev/null
close(0);
open("/dev/null", O_RDONLY);
close(1);
open("/dev/null", O_WRONLY);
close(2);
open("/dev/null", O_WRONLY);
CDebug::SetStdoutIsTTY(false);
// We are the child. There is no way we can be a process group
// leader, thus setsid() must succeed.
setsid();
// Now we are in our own process group and session (no
// controlling terminal). We are independent!
}
// Handle all signals in separate thread
std::unique_ptr<CSignalHandler> SignalHandler(new CSignalHandler(pZNC));
int iRet = 0;
try {
pZNC->Loop();
} catch (const CException& e) {
switch (e.GetType()) {
case CException::EX_Shutdown:
iRet = 0;
break;
case CException::EX_Restart: {
// strdup() because GCC is stupid
char* args[] = {
strdup(argv[0]), strdup("--datadir"),
strdup(pZNC->GetZNCPath().c_str()), nullptr,
nullptr, nullptr,
nullptr};
int pos = 3;
if (CDebug::Debug())
args[pos++] = strdup("--debug");
else if (bForeground)
args[pos++] = strdup("--foreground");
if (!CDebug::StdoutIsTTY()) args[pos++] = strdup("--no-color");
if (bAllowRoot) args[pos++] = strdup("--allow-root");
// The above code adds 3 entries to args tops
// which means the array should be big enough
SignalHandler.reset();
CZNC::DestroyInstance();
execvp(args[0], args);
CUtils::PrintError("Unable to restart ZNC [" +
CString(strerror(errno)) + "]");
} /* Fall through */
default:
iRet = 1;
}
}
SignalHandler.reset();
CZNC::DestroyInstance();
CUtils::PrintMessage("Exiting");
return iRet;
}