Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
- * Copyriht (C) 2016 WBNC
- *
- * 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 Make ZNC foreground. Best used with screen/tmux.");
- 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 WZNC 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 WZNC config directory (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("WZNC 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 the WZNC 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 WZNC 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 WZNC properly?");
- CUtils::PrintError(
- "Read http://wiki.znc.in/Installation for instructions.");
- if (!CUtils::GetBoolInput(
- "Do you really want to run WZNC without any modules?",
- false)) {
- CZNC::DestroyInstance();
- return 1;
- }
- }
- CUtils::PrintStatus(true, "");
- }
- if (isRoot()) {
- CUtils::PrintError(
- "You are running WZNC 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 WZNC as root.");
- CUtils::PrintError("WZNC 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("Error: Unable to restart WZNC [" +
- CString(strerror(errno)) + "]");
- } /* Fall through */
- default:
- iRet = 1;
- }
- }
- SignalHandler.reset();
- CZNC::DestroyInstance();
- CUtils::PrintMessage("Exiting");
- return iRet;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement