Files
znc/src/main.cpp
T
Alexey Sokolov cd1f334f31 Update error message about --datadir
Noone remembers these days that at some point ZNC supported using the
same config directory, but different znc.conf. So now the old message is
just confusing.

However, nowadays many people confuse "/znc foo" in IRC client with
"znc foo" in shell.
2015-04-17 22:56:35 +01:00

456 lines
12 KiB
C++

/*
* Copyright (C) 2004-2015 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>
#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, 0, 'h' },
{ "version", no_argument, 0, 'v' },
{ "debug", no_argument, 0, 'D' },
{ "foreground", no_argument, 0, 'f' },
{ "no-color", no_argument, 0, 'n' },
{ "allow-root", no_argument, 0, 'r' },
{ "makeconf", no_argument, 0, 'c' },
{ "makepass", no_argument, 0, 's' },
{ "makepem", no_argument, 0, 'p' },
{ "datadir", required_argument, 0, 'd' },
{ 0, 0, 0, 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)");
}
static void die(int sig) {
signal(SIGPIPE, SIG_DFL);
CUtils::PrintMessage("Exiting on SIG [" + CString(sig) + "]");
CZNC::DestroyInstance();
exit(sig);
}
static void signalHandler(int sig) {
switch (sig) {
case SIGHUP:
CUtils::PrintMessage("Caught SIGHUP");
CZNC::Get().SetConfigState(CZNC::ECONFIG_NEED_REHASH);
break;
case SIGUSR1:
CUtils::PrintMessage("Caught SIGUSR1");
CZNC::Get().SetConfigState(CZNC::ECONFIG_NEED_VERBOSE_WRITE);
break;
default:
CUtils::PrintMessage("WTF? Signal handler called for a signal it doesn't know?");
}
}
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, NULL) == 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(NULL);
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!
}
struct sigaction sa;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, (struct sigaction*) NULL);
sa.sa_handler = signalHandler;
sigaction(SIGHUP, &sa, (struct sigaction*) NULL);
sigaction(SIGUSR1, &sa, (struct sigaction*) NULL);
// Once this signal is caught, the signal handler is reset
// to SIG_DFL. This avoids endless loop with signals.
sa.sa_flags = SA_RESETHAND;
sa.sa_handler = die;
sigaction(SIGINT, &sa, (struct sigaction*) NULL);
sigaction(SIGQUIT, &sa, (struct sigaction*) NULL);
sigaction(SIGTERM, &sa, (struct sigaction*) NULL);
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()),
NULL,
NULL,
NULL,
NULL
};
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
CZNC::DestroyInstance();
execvp(args[0], args);
CUtils::PrintError("Unable to restart ZNC [" + CString(strerror(errno)) + "]");
} /* Fall through */
default:
iRet = 1;
}
}
CZNC::DestroyInstance();
return iRet;
}