Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2004-2025 ZNC, see the NOTICE file for details. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <znc/znc.h> |
18 | | #include <znc/FileUtils.h> |
19 | | #include <znc/IRCSock.h> |
20 | | #include <znc/Server.h> |
21 | | #include <znc/User.h> |
22 | | #include <znc/IRCNetwork.h> |
23 | | #include <znc/Config.h> |
24 | | #include <time.h> |
25 | | #include <tuple> |
26 | | #include <algorithm> |
27 | | |
28 | | using std::endl; |
29 | | using std::cout; |
30 | | using std::map; |
31 | | using std::set; |
32 | | using std::vector; |
33 | | using std::list; |
34 | | using std::tuple; |
35 | | using std::make_tuple; |
36 | | |
37 | | CZNC::CZNC() |
38 | 0 | : m_TimeStarted(time(nullptr)), |
39 | 0 | m_eConfigState(ECONFIG_NOTHING), |
40 | 0 | m_vpListeners(), |
41 | 0 | m_msUsers(), |
42 | 0 | m_msDelUsers(), |
43 | 0 | m_Manager(), |
44 | 0 | m_sCurPath(""), |
45 | 0 | m_sZNCPath(""), |
46 | 0 | m_sConfigFile(""), |
47 | 0 | m_sSkinName(""), |
48 | 0 | m_sStatusPrefix(""), |
49 | 0 | m_sPidFile(""), |
50 | 0 | m_sSSLCertFile(""), |
51 | 0 | m_sSSLKeyFile(""), |
52 | 0 | m_sSSLDHParamFile(""), |
53 | 0 | m_sSSLCiphers(""), |
54 | 0 | m_sSSLProtocols(""), |
55 | 0 | m_vsBindHosts(), |
56 | 0 | m_vsTrustedProxies(), |
57 | 0 | m_vsMotd(), |
58 | 0 | m_pLockFile(nullptr), |
59 | 0 | m_uiConnectDelay(5), |
60 | 0 | m_uiAnonIPLimit(10), |
61 | 0 | m_uiMaxBufferSize(500), |
62 | 0 | m_uDisabledSSLProtocols(Csock::EDP_SSL | Csock::EDP_TLSv1 | Csock::EDP_TLSv1_1), |
63 | 0 | m_pModules(new CModules), |
64 | 0 | m_uBytesRead(0), |
65 | 0 | m_uBytesWritten(0), |
66 | 0 | m_lpConnectQueue(), |
67 | 0 | m_pConnectQueueTimer(nullptr), |
68 | 0 | m_uiConnectPaused(0), |
69 | 0 | m_uiForceEncoding(0), |
70 | 0 | m_sConnectThrottle(), |
71 | 0 | m_bProtectWebSessions(true), |
72 | 0 | m_bHideVersion(false), |
73 | 0 | m_bAuthOnlyViaModule(false), |
74 | 0 | m_Translation("znc"), |
75 | 0 | m_uiConfigWriteDelay(0), |
76 | 0 | m_pConfigTimer(nullptr) { |
77 | 0 | if (!InitCsocket()) { |
78 | 0 | CUtils::PrintError("Could not initialize Csocket!"); |
79 | 0 | exit(-1); |
80 | 0 | } |
81 | 0 | m_sConnectThrottle.SetTTL(30000); |
82 | 0 | } |
83 | | |
84 | 0 | CZNC::~CZNC() { |
85 | 0 | m_pModules->UnloadAll(); |
86 | |
|
87 | 0 | for (const auto& it : m_msUsers) { |
88 | 0 | it.second->GetModules().UnloadAll(); |
89 | |
|
90 | 0 | const vector<CIRCNetwork*>& networks = it.second->GetNetworks(); |
91 | 0 | for (CIRCNetwork* pNetwork : networks) { |
92 | 0 | pNetwork->GetModules().UnloadAll(); |
93 | 0 | } |
94 | 0 | } |
95 | |
|
96 | 0 | for (CListener* pListener : m_vpListeners) { |
97 | 0 | delete pListener; |
98 | 0 | } |
99 | |
|
100 | 0 | for (const auto& it : m_msUsers) { |
101 | 0 | it.second->SetBeingDeleted(true); |
102 | 0 | } |
103 | |
|
104 | 0 | m_pConnectQueueTimer = nullptr; |
105 | | // This deletes m_pConnectQueueTimer |
106 | 0 | m_Manager.Cleanup(); |
107 | 0 | DeleteUsers(); |
108 | |
|
109 | 0 | delete m_pModules; |
110 | 0 | delete m_pLockFile; |
111 | |
|
112 | 0 | ShutdownCsocket(); |
113 | 0 | DeletePidFile(); |
114 | 0 | } |
115 | | |
116 | 0 | CString CZNC::GetVersion() { |
117 | 0 | return CString(VERSION_STR) + CString(ZNC_VERSION_EXTRA); |
118 | 0 | } |
119 | | |
120 | 0 | CString CZNC::GetTag(bool bIncludeVersion, bool bHTML) { |
121 | 0 | if (!Get().m_bHideVersion) { |
122 | 0 | bIncludeVersion = true; |
123 | 0 | } |
124 | 0 | CString sAddress = bHTML ? "<a href=\"https://znc.in\">https://znc.in</a>" |
125 | 0 | : "https://znc.in"; |
126 | |
|
127 | 0 | if (!bIncludeVersion) { |
128 | 0 | return "ZNC - " + sAddress; |
129 | 0 | } |
130 | | |
131 | 0 | CString sVersion = GetVersion(); |
132 | |
|
133 | 0 | return "ZNC " + sVersion + " - " + sAddress; |
134 | 0 | } |
135 | | |
136 | 0 | CString CZNC::GetCompileOptionsString() { |
137 | 0 | return ZNC_COMPILE_OPTIONS_STRING; |
138 | 0 | } |
139 | | |
140 | 0 | CString CZNC::GetUptime() const { |
141 | 0 | time_t now = time(nullptr); |
142 | 0 | return CString::ToTimeStr(now - TimeStarted()); |
143 | 0 | } |
144 | | |
145 | 0 | bool CZNC::OnBoot() { |
146 | 0 | bool bFail = false; |
147 | 0 | ALLMODULECALL(OnBoot(), &bFail); |
148 | 0 | if (bFail) return false; |
149 | | |
150 | 0 | return true; |
151 | 0 | } |
152 | | |
153 | 0 | bool CZNC::HandleUserDeletion() { |
154 | 0 | if (m_msDelUsers.empty()) return false; |
155 | | |
156 | 0 | for (const auto& it : m_msDelUsers) { |
157 | 0 | CUser* pUser = it.second; |
158 | 0 | pUser->SetBeingDeleted(true); |
159 | |
|
160 | 0 | if (GetModules().OnDeleteUser(*pUser)) { |
161 | 0 | pUser->SetBeingDeleted(false); |
162 | 0 | continue; |
163 | 0 | } |
164 | 0 | m_msUsers.erase(pUser->GetUsername()); |
165 | 0 | CWebSock::FinishUserSessions(*pUser); |
166 | 0 | delete pUser; |
167 | 0 | } |
168 | |
|
169 | 0 | m_msDelUsers.clear(); |
170 | |
|
171 | 0 | return true; |
172 | 0 | } |
173 | | |
174 | | class CConfigWriteTimer : public CCron { |
175 | | public: |
176 | 0 | CConfigWriteTimer(int iSecs) : CCron() { |
177 | 0 | SetName("Config write timer"); |
178 | 0 | Start(iSecs); |
179 | 0 | } |
180 | | |
181 | | protected: |
182 | 0 | void RunJob() override { |
183 | 0 | CZNC::Get().SetConfigState(CZNC::ECONFIG_NEED_WRITE); |
184 | |
|
185 | 0 | CZNC::Get().DisableConfigTimer(); |
186 | 0 | } |
187 | | }; |
188 | | |
189 | 0 | void CZNC::Loop() { |
190 | 0 | while (true) { |
191 | 0 | CString sError; |
192 | |
|
193 | 0 | ConfigState eState = GetConfigState(); |
194 | 0 | switch (eState) { |
195 | 0 | case ECONFIG_NEED_REHASH: |
196 | 0 | SetConfigState(ECONFIG_NOTHING); |
197 | |
|
198 | 0 | if (RehashConfig(sError)) { |
199 | 0 | Broadcast("Rehashing succeeded", true); |
200 | 0 | } else { |
201 | 0 | Broadcast("Rehashing failed: " + sError, true); |
202 | 0 | Broadcast("ZNC is in some possibly inconsistent state!", |
203 | 0 | true); |
204 | 0 | } |
205 | 0 | break; |
206 | 0 | case ECONFIG_DELAYED_WRITE: |
207 | 0 | SetConfigState(ECONFIG_NOTHING); |
208 | |
|
209 | 0 | if (GetConfigWriteDelay() > 0) { |
210 | 0 | if (m_pConfigTimer == nullptr) { |
211 | 0 | m_pConfigTimer = new CConfigWriteTimer(GetConfigWriteDelay()); |
212 | 0 | GetManager().AddCron(m_pConfigTimer); |
213 | 0 | } |
214 | 0 | break; |
215 | 0 | } |
216 | | /* Fall through */ |
217 | 0 | case ECONFIG_NEED_WRITE: |
218 | 0 | case ECONFIG_NEED_VERBOSE_WRITE: |
219 | 0 | SetConfigState(ECONFIG_NOTHING); |
220 | | |
221 | | // stop pending configuration timer |
222 | 0 | DisableConfigTimer(); |
223 | |
|
224 | 0 | if (!WriteConfig()) { |
225 | 0 | Broadcast("Writing the config file failed", true); |
226 | 0 | } else if (eState == ECONFIG_NEED_VERBOSE_WRITE) { |
227 | 0 | Broadcast("Writing the config succeeded", true); |
228 | 0 | } |
229 | 0 | break; |
230 | 0 | case ECONFIG_NOTHING: |
231 | 0 | break; |
232 | 0 | case ECONFIG_NEED_QUIT: |
233 | 0 | return; |
234 | 0 | } |
235 | | |
236 | | // Check for users that need to be deleted |
237 | 0 | if (HandleUserDeletion()) { |
238 | | // Also remove those user(s) from the config file |
239 | 0 | WriteConfig(); |
240 | 0 | } |
241 | | |
242 | | // Csocket wants micro seconds |
243 | | // 100 msec to 5 min |
244 | 0 | m_Manager.DynamicSelectLoop(100 * 1000, 5 * 60 * 1000 * 1000); |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | 0 | CFile* CZNC::InitPidFile() { |
249 | 0 | if (!m_sPidFile.empty()) { |
250 | 0 | CString sFile; |
251 | | |
252 | | // absolute path or relative to the data dir? |
253 | 0 | if (m_sPidFile[0] != '/') |
254 | 0 | sFile = GetZNCPath() + "/" + m_sPidFile; |
255 | 0 | else |
256 | 0 | sFile = m_sPidFile; |
257 | |
|
258 | 0 | return new CFile(sFile); |
259 | 0 | } |
260 | | |
261 | 0 | return nullptr; |
262 | 0 | } |
263 | | |
264 | 0 | bool CZNC::WritePidFile(int iPid) { |
265 | 0 | CFile* File = InitPidFile(); |
266 | 0 | if (File == nullptr) return false; |
267 | | |
268 | 0 | CUtils::PrintAction("Writing pid file [" + File->GetLongName() + "]"); |
269 | |
|
270 | 0 | bool bRet = false; |
271 | 0 | if (File->Open(O_WRONLY | O_TRUNC | O_CREAT)) { |
272 | 0 | File->Write(CString(iPid) + "\n"); |
273 | 0 | File->Close(); |
274 | 0 | bRet = true; |
275 | 0 | } |
276 | |
|
277 | 0 | delete File; |
278 | 0 | CUtils::PrintStatus(bRet); |
279 | 0 | return bRet; |
280 | 0 | } |
281 | | |
282 | 0 | bool CZNC::DeletePidFile() { |
283 | 0 | CFile* File = InitPidFile(); |
284 | 0 | if (File == nullptr) return false; |
285 | | |
286 | 0 | CUtils::PrintAction("Deleting pid file [" + File->GetLongName() + "]"); |
287 | |
|
288 | 0 | bool bRet = File->Delete(); |
289 | |
|
290 | 0 | delete File; |
291 | 0 | CUtils::PrintStatus(bRet); |
292 | 0 | return bRet; |
293 | 0 | } |
294 | | |
295 | 0 | bool CZNC::WritePemFile() { |
296 | 0 | #ifndef HAVE_LIBSSL |
297 | 0 | CUtils::PrintError("ZNC was not compiled with ssl support."); |
298 | 0 | return false; |
299 | | #else |
300 | | CString sPemFile = GetPemLocation(); |
301 | | |
302 | | CUtils::PrintAction("Writing Pem file [" + sPemFile + "]"); |
303 | | #ifndef _WIN32 |
304 | | int fd = creat(sPemFile.c_str(), 0600); |
305 | | if (fd == -1) { |
306 | | CUtils::PrintStatus(false, "Unable to open"); |
307 | | return false; |
308 | | } |
309 | | FILE* f = fdopen(fd, "w"); |
310 | | #else |
311 | | FILE* f = fopen(sPemFile.c_str(), "w"); |
312 | | #endif |
313 | | |
314 | | if (!f) { |
315 | | CUtils::PrintStatus(false, "Unable to open"); |
316 | | return false; |
317 | | } |
318 | | |
319 | | CUtils::GenerateCert(f); |
320 | | fclose(f); |
321 | | |
322 | | CUtils::PrintStatus(true); |
323 | | return true; |
324 | | #endif |
325 | 0 | } |
326 | | |
327 | 0 | void CZNC::DeleteUsers() { |
328 | 0 | for (const auto& it : m_msUsers) { |
329 | 0 | it.second->SetBeingDeleted(true); |
330 | 0 | delete it.second; |
331 | 0 | } |
332 | |
|
333 | 0 | m_msUsers.clear(); |
334 | 0 | DisableConnectQueue(); |
335 | 0 | } |
336 | | |
337 | 0 | bool CZNC::IsHostAllowed(const CString& sHostMask) const { |
338 | 0 | for (const auto& it : m_msUsers) { |
339 | 0 | if (it.second->IsHostAllowed(sHostMask)) { |
340 | 0 | return true; |
341 | 0 | } |
342 | 0 | } |
343 | | |
344 | 0 | return false; |
345 | 0 | } |
346 | | |
347 | 0 | bool CZNC::AllowConnectionFrom(const CString& sIP) const { |
348 | 0 | if (m_uiAnonIPLimit == 0) return true; |
349 | 0 | return (GetManager().GetAnonConnectionCount(sIP) < m_uiAnonIPLimit); |
350 | 0 | } |
351 | | |
352 | 0 | void CZNC::InitDirs(const CString& sArgvPath, const CString& sDataDir) { |
353 | | // If the bin was not ran from the current directory, we need to add that |
354 | | // dir onto our cwd |
355 | 0 | CString::size_type uPos = sArgvPath.rfind('/'); |
356 | 0 | if (uPos == CString::npos) |
357 | 0 | m_sCurPath = "./"; |
358 | 0 | else |
359 | 0 | m_sCurPath = CDir::ChangeDir("./", sArgvPath.Left(uPos), ""); |
360 | | |
361 | | // Try to set the user's home dir, default to binpath on failure |
362 | 0 | CFile::InitHomePath(m_sCurPath); |
363 | |
|
364 | 0 | if (sDataDir.empty()) { |
365 | 0 | m_sZNCPath = CFile::GetHomePath() + "/.znc"; |
366 | 0 | } else { |
367 | 0 | m_sZNCPath = sDataDir; |
368 | 0 | } |
369 | |
|
370 | 0 | m_sSSLCertFile = m_sZNCPath + "/znc.pem"; |
371 | 0 | } |
372 | | |
373 | 0 | CString CZNC::GetConfPath(bool bAllowMkDir) const { |
374 | 0 | CString sConfPath = m_sZNCPath + "/configs"; |
375 | 0 | if (bAllowMkDir && !CFile::Exists(sConfPath)) { |
376 | 0 | CDir::MakeDir(sConfPath); |
377 | 0 | } |
378 | |
|
379 | 0 | return sConfPath; |
380 | 0 | } |
381 | | |
382 | 0 | CString CZNC::GetUserPath() const { |
383 | 0 | CString sUserPath = m_sZNCPath + "/users"; |
384 | 0 | if (!CFile::Exists(sUserPath)) { |
385 | 0 | CDir::MakeDir(sUserPath); |
386 | 0 | } |
387 | |
|
388 | 0 | return sUserPath; |
389 | 0 | } |
390 | | |
391 | 0 | CString CZNC::GetModPath() const { |
392 | 0 | CString sModPath = m_sZNCPath + "/modules"; |
393 | |
|
394 | 0 | return sModPath; |
395 | 0 | } |
396 | | |
397 | 0 | const CString& CZNC::GetCurPath() const { |
398 | 0 | if (!CFile::Exists(m_sCurPath)) { |
399 | 0 | CDir::MakeDir(m_sCurPath); |
400 | 0 | } |
401 | 0 | return m_sCurPath; |
402 | 0 | } |
403 | | |
404 | 0 | const CString& CZNC::GetHomePath() const { return CFile::GetHomePath(); } |
405 | | |
406 | 0 | const CString& CZNC::GetZNCPath() const { |
407 | 0 | if (!CFile::Exists(m_sZNCPath)) { |
408 | 0 | CDir::MakeDir(m_sZNCPath); |
409 | 0 | } |
410 | 0 | return m_sZNCPath; |
411 | 0 | } |
412 | | |
413 | 0 | CString CZNC::GetPemLocation() const { |
414 | 0 | return CDir::ChangeDir("", m_sSSLCertFile); |
415 | 0 | } |
416 | | |
417 | 0 | CString CZNC::GetKeyLocation() const { |
418 | 0 | return CDir::ChangeDir( |
419 | 0 | "", m_sSSLKeyFile.empty() ? m_sSSLCertFile : m_sSSLKeyFile); |
420 | 0 | } |
421 | | |
422 | 0 | CString CZNC::GetDHParamLocation() const { |
423 | 0 | return CDir::ChangeDir( |
424 | 0 | "", m_sSSLDHParamFile.empty() ? m_sSSLCertFile : m_sSSLDHParamFile); |
425 | 0 | } |
426 | | |
427 | 0 | CString CZNC::ExpandConfigPath(const CString& sConfigFile, bool bAllowMkDir) { |
428 | 0 | CString sRetPath; |
429 | |
|
430 | 0 | if (sConfigFile.empty()) { |
431 | 0 | sRetPath = GetConfPath(bAllowMkDir) + "/znc.conf"; |
432 | 0 | } else { |
433 | 0 | if (sConfigFile.StartsWith("./") || sConfigFile.StartsWith("../")) { |
434 | 0 | sRetPath = GetCurPath() + "/" + sConfigFile; |
435 | 0 | } else if (!sConfigFile.StartsWith("/")) { |
436 | 0 | sRetPath = GetConfPath(bAllowMkDir) + "/" + sConfigFile; |
437 | 0 | } else { |
438 | 0 | sRetPath = sConfigFile; |
439 | 0 | } |
440 | 0 | } |
441 | |
|
442 | 0 | return sRetPath; |
443 | 0 | } |
444 | | |
445 | 0 | bool CZNC::WriteConfig() { |
446 | 0 | if (GetConfigFile().empty()) { |
447 | 0 | DEBUG("Config file name is empty?!"); |
448 | 0 | return false; |
449 | 0 | } |
450 | | |
451 | | // We first write to a temporary file and then move it to the right place |
452 | 0 | CFile* pFile = new CFile(GetConfigFile() + "~"); |
453 | |
|
454 | 0 | if (!pFile->Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) { |
455 | 0 | DEBUG("Could not write config to " + GetConfigFile() + "~: " + |
456 | 0 | CString(strerror(errno))); |
457 | 0 | delete pFile; |
458 | 0 | return false; |
459 | 0 | } |
460 | | |
461 | | // We have to "transfer" our lock on the config to the new file. |
462 | | // The old file (= inode) is going away and thus a lock on it would be |
463 | | // useless. These lock should always succeed (races, anyone?). |
464 | 0 | if (!pFile->TryExLock()) { |
465 | 0 | DEBUG("Error while locking the new config file, errno says: " + |
466 | 0 | CString(strerror(errno))); |
467 | 0 | pFile->Delete(); |
468 | 0 | delete pFile; |
469 | 0 | return false; |
470 | 0 | } |
471 | | |
472 | 0 | pFile->Write(MakeConfigHeader() + "\n"); |
473 | |
|
474 | 0 | CConfig config; |
475 | 0 | config.AddKeyValuePair("AnonIPLimit", CString(m_uiAnonIPLimit)); |
476 | 0 | config.AddKeyValuePair("MaxBufferSize", CString(m_uiMaxBufferSize)); |
477 | 0 | config.AddKeyValuePair("SSLCertFile", CString(GetPemLocation())); |
478 | 0 | config.AddKeyValuePair("SSLKeyFile", CString(GetKeyLocation())); |
479 | 0 | config.AddKeyValuePair("SSLDHParamFile", CString(GetDHParamLocation())); |
480 | 0 | config.AddKeyValuePair("ProtectWebSessions", |
481 | 0 | CString(m_bProtectWebSessions)); |
482 | 0 | config.AddKeyValuePair("HideVersion", CString(m_bHideVersion)); |
483 | 0 | config.AddKeyValuePair("AuthOnlyViaModule", CString(m_bAuthOnlyViaModule)); |
484 | 0 | config.AddKeyValuePair("Version", CString(VERSION_STR)); |
485 | 0 | config.AddKeyValuePair("ConfigWriteDelay", CString(m_uiConfigWriteDelay)); |
486 | |
|
487 | 0 | unsigned int l = 0; |
488 | 0 | for (CListener* pListener : m_vpListeners) { |
489 | 0 | CConfig listenerConfig; |
490 | |
|
491 | 0 | listenerConfig.AddKeyValuePair("Host", pListener->GetBindHost()); |
492 | 0 | listenerConfig.AddKeyValuePair("URIPrefix", |
493 | 0 | pListener->GetURIPrefix() + "/"); |
494 | 0 | listenerConfig.AddKeyValuePair("Port", CString(pListener->GetPort())); |
495 | |
|
496 | 0 | listenerConfig.AddKeyValuePair( |
497 | 0 | "IPv4", CString(pListener->GetAddrType() != ADDR_IPV6ONLY)); |
498 | 0 | listenerConfig.AddKeyValuePair( |
499 | 0 | "IPv6", CString(pListener->GetAddrType() != ADDR_IPV4ONLY)); |
500 | |
|
501 | 0 | listenerConfig.AddKeyValuePair("SSL", CString(pListener->IsSSL())); |
502 | |
|
503 | 0 | listenerConfig.AddKeyValuePair( |
504 | 0 | "AllowIRC", |
505 | 0 | CString(pListener->GetAcceptType() != CListener::ACCEPT_HTTP)); |
506 | 0 | listenerConfig.AddKeyValuePair( |
507 | 0 | "AllowWeb", |
508 | 0 | CString(pListener->GetAcceptType() != CListener::ACCEPT_IRC)); |
509 | |
|
510 | 0 | config.AddSubConfig("Listener", "listener" + CString(l++), |
511 | 0 | listenerConfig); |
512 | 0 | } |
513 | |
|
514 | 0 | config.AddKeyValuePair("ConnectDelay", CString(m_uiConnectDelay)); |
515 | 0 | config.AddKeyValuePair("ServerThrottle", |
516 | 0 | CString(m_sConnectThrottle.GetTTL() / 1000)); |
517 | |
|
518 | 0 | if (!m_sPidFile.empty()) { |
519 | 0 | config.AddKeyValuePair("PidFile", m_sPidFile.FirstLine()); |
520 | 0 | } |
521 | |
|
522 | 0 | if (!m_sSkinName.empty()) { |
523 | 0 | config.AddKeyValuePair("Skin", m_sSkinName.FirstLine()); |
524 | 0 | } |
525 | |
|
526 | 0 | if (!m_sStatusPrefix.empty()) { |
527 | 0 | config.AddKeyValuePair("StatusPrefix", m_sStatusPrefix.FirstLine()); |
528 | 0 | } |
529 | |
|
530 | 0 | if (!m_sSSLCiphers.empty()) { |
531 | 0 | config.AddKeyValuePair("SSLCiphers", CString(m_sSSLCiphers)); |
532 | 0 | } |
533 | |
|
534 | 0 | if (!m_sSSLProtocols.empty()) { |
535 | 0 | config.AddKeyValuePair("SSLProtocols", m_sSSLProtocols); |
536 | 0 | } |
537 | |
|
538 | 0 | for (const CString& sLine : m_vsMotd) { |
539 | 0 | config.AddKeyValuePair("Motd", sLine.FirstLine()); |
540 | 0 | } |
541 | |
|
542 | 0 | for (const CString& sProxy : m_vsTrustedProxies) { |
543 | 0 | config.AddKeyValuePair("TrustedProxy", sProxy.FirstLine()); |
544 | 0 | } |
545 | |
|
546 | 0 | CModules& Mods = GetModules(); |
547 | |
|
548 | 0 | for (const CModule* pMod : Mods) { |
549 | 0 | CString sName = pMod->GetModName(); |
550 | 0 | CString sArgs = pMod->GetArgs(); |
551 | |
|
552 | 0 | if (!sArgs.empty()) { |
553 | 0 | sArgs = " " + sArgs.FirstLine(); |
554 | 0 | } |
555 | |
|
556 | 0 | config.AddKeyValuePair("LoadModule", sName.FirstLine() + sArgs); |
557 | 0 | } |
558 | |
|
559 | 0 | for (const auto& it : m_msUsers) { |
560 | 0 | CString sErr; |
561 | |
|
562 | 0 | if (!it.second->IsValid(sErr)) { |
563 | 0 | DEBUG("** Error writing config for user [" << it.first << "] [" |
564 | 0 | << sErr << "]"); |
565 | 0 | continue; |
566 | 0 | } |
567 | | |
568 | 0 | config.AddSubConfig("User", it.second->GetUsername(), |
569 | 0 | it.second->ToConfig()); |
570 | 0 | } |
571 | |
|
572 | 0 | config.Write(*pFile); |
573 | | |
574 | | // If Sync() fails... well, let's hope nothing important breaks.. |
575 | 0 | pFile->Sync(); |
576 | |
|
577 | 0 | if (pFile->HadError()) { |
578 | 0 | DEBUG("Error while writing the config, errno says: " + |
579 | 0 | CString(strerror(errno))); |
580 | 0 | pFile->Delete(); |
581 | 0 | delete pFile; |
582 | 0 | return false; |
583 | 0 | } |
584 | | |
585 | | // We wrote to a temporary name, move it to the right place |
586 | 0 | if (!pFile->Move(GetConfigFile(), true)) { |
587 | 0 | DEBUG( |
588 | 0 | "Error while replacing the config file with a new version, errno " |
589 | 0 | "says " |
590 | 0 | << strerror(errno)); |
591 | 0 | pFile->Delete(); |
592 | 0 | delete pFile; |
593 | 0 | return false; |
594 | 0 | } |
595 | | |
596 | | // Everything went fine, just need to update the saved path. |
597 | 0 | pFile->SetFileName(GetConfigFile()); |
598 | | |
599 | | // Make sure the lock is kept alive as long as we need it. |
600 | 0 | delete m_pLockFile; |
601 | 0 | m_pLockFile = pFile; |
602 | |
|
603 | 0 | return true; |
604 | 0 | } |
605 | | |
606 | 0 | CString CZNC::MakeConfigHeader() { |
607 | 0 | return "// WARNING\n" |
608 | 0 | "//\n" |
609 | 0 | "// Do NOT edit this file while ZNC is running!\n" |
610 | 0 | "// Use webadmin or *controlpanel instead.\n" |
611 | 0 | "//\n" |
612 | 0 | "// Altering this file by hand will forfeit all support.\n" |
613 | 0 | "//\n" |
614 | 0 | "// But if you feel risky, you might want to read help on /znc " |
615 | 0 | "saveconfig and /znc rehash.\n" |
616 | 0 | "// Also check https://wiki.znc.in/Configuration\n"; |
617 | 0 | } |
618 | | |
619 | 0 | bool CZNC::WriteNewConfig(const CString& sConfigFile) { |
620 | 0 | CString sAnswer, sUser, sNetwork; |
621 | 0 | VCString vsLines; |
622 | |
|
623 | 0 | vsLines.push_back(MakeConfigHeader()); |
624 | 0 | vsLines.push_back("Version = " + CString(VERSION_STR)); |
625 | |
|
626 | 0 | m_sConfigFile = ExpandConfigPath(sConfigFile); |
627 | |
|
628 | 0 | if (CFile::Exists(m_sConfigFile)) { |
629 | 0 | CUtils::PrintStatus( |
630 | 0 | false, "WARNING: config [" + m_sConfigFile + "] already exists."); |
631 | 0 | } |
632 | |
|
633 | 0 | CUtils::PrintMessage(""); |
634 | 0 | CUtils::PrintMessage("-- Global settings --"); |
635 | 0 | CUtils::PrintMessage(""); |
636 | | |
637 | | // Listen |
638 | | #ifdef HAVE_IPV6 |
639 | | bool b6 = true; |
640 | | #else |
641 | 0 | bool b6 = false; |
642 | 0 | #endif |
643 | 0 | CString sListenHost; |
644 | 0 | CString sURIPrefix; |
645 | 0 | bool bListenSSL = false; |
646 | 0 | unsigned int uListenPort = 0; |
647 | 0 | bool bSuccess; |
648 | |
|
649 | 0 | do { |
650 | 0 | bSuccess = true; |
651 | 0 | while (true) { |
652 | 0 | if (!CUtils::GetNumInput("Listen on port", uListenPort, 1025, |
653 | 0 | 65534)) { |
654 | 0 | continue; |
655 | 0 | } |
656 | 0 | if (uListenPort == 6667 || uListenPort == 6697) { |
657 | 0 | CUtils::PrintStatus(false, |
658 | 0 | "WARNING: Some web browsers reject ports " |
659 | 0 | "6667 and 6697. If you intend to"); |
660 | 0 | CUtils::PrintStatus(false, |
661 | 0 | "use ZNC's web interface, you might want " |
662 | 0 | "to use another port."); |
663 | 0 | if (!CUtils::GetBoolInput("Proceed anyway?", |
664 | 0 | true)) { |
665 | 0 | continue; |
666 | 0 | } |
667 | 0 | } |
668 | 0 | break; |
669 | 0 | } |
670 | |
|
671 | | #ifdef HAVE_LIBSSL |
672 | | bListenSSL = CUtils::GetBoolInput("Listen using SSL", bListenSSL); |
673 | | #endif |
674 | |
|
675 | | #ifdef HAVE_IPV6 |
676 | | b6 = CUtils::GetBoolInput("Listen using both IPv4 and IPv6", b6); |
677 | | #endif |
678 | | |
679 | | // Don't ask for listen host, it may be configured later if needed. |
680 | |
|
681 | 0 | CUtils::PrintAction("Verifying the listener"); |
682 | 0 | CListener* pListener = new CListener( |
683 | 0 | (unsigned short int)uListenPort, sListenHost, sURIPrefix, |
684 | 0 | bListenSSL, b6 ? ADDR_ALL : ADDR_IPV4ONLY, CListener::ACCEPT_ALL); |
685 | 0 | if (!pListener->Listen()) { |
686 | 0 | CUtils::PrintStatus(false, FormatBindError()); |
687 | 0 | bSuccess = false; |
688 | 0 | } else |
689 | 0 | CUtils::PrintStatus(true); |
690 | 0 | delete pListener; |
691 | 0 | } while (!bSuccess); |
692 | |
|
693 | | #ifdef HAVE_LIBSSL |
694 | | CString sPemFile = GetPemLocation(); |
695 | | if (!CFile::Exists(sPemFile)) { |
696 | | CUtils::PrintMessage("Unable to locate pem file: [" + sPemFile + |
697 | | "], creating it"); |
698 | | WritePemFile(); |
699 | | } |
700 | | #endif |
701 | |
|
702 | 0 | vsLines.push_back("<Listener l>"); |
703 | 0 | vsLines.push_back("\tPort = " + CString(uListenPort)); |
704 | 0 | vsLines.push_back("\tIPv4 = true"); |
705 | 0 | vsLines.push_back("\tIPv6 = " + CString(b6)); |
706 | 0 | vsLines.push_back("\tSSL = " + CString(bListenSSL)); |
707 | 0 | if (!sListenHost.empty()) { |
708 | 0 | vsLines.push_back("\tHost = " + sListenHost); |
709 | 0 | } |
710 | 0 | vsLines.push_back("</Listener>"); |
711 | | // !Listen |
712 | |
|
713 | 0 | set<CModInfo> ssGlobalMods; |
714 | 0 | GetModules().GetDefaultMods(ssGlobalMods, CModInfo::GlobalModule); |
715 | 0 | vector<CString> vsGlobalModNames; |
716 | 0 | for (const CModInfo& Info : ssGlobalMods) { |
717 | 0 | vsGlobalModNames.push_back(Info.GetName()); |
718 | 0 | vsLines.push_back("LoadModule = " + Info.GetName()); |
719 | 0 | } |
720 | 0 | CUtils::PrintMessage( |
721 | 0 | "Enabled global modules [" + |
722 | 0 | CString(", ").Join(vsGlobalModNames.begin(), vsGlobalModNames.end()) + |
723 | 0 | "]"); |
724 | | |
725 | | // User |
726 | 0 | CUtils::PrintMessage(""); |
727 | 0 | CUtils::PrintMessage("-- Admin user settings --"); |
728 | 0 | CUtils::PrintMessage(""); |
729 | |
|
730 | 0 | vsLines.push_back(""); |
731 | 0 | CString sNick; |
732 | 0 | do { |
733 | 0 | CUtils::GetInput("Username", sUser, "", "alphanumeric"); |
734 | 0 | } while (!CUser::IsValidUsername(sUser)); |
735 | |
|
736 | 0 | vsLines.push_back("<User " + sUser + ">"); |
737 | 0 | sAnswer = CUtils::AskSaltedHashPassForConfig(); |
738 | 0 | vsLines.push_back(sAnswer); |
739 | |
|
740 | 0 | vsLines.push_back("\tAdmin = true"); |
741 | |
|
742 | 0 | CUtils::GetInput("Nick", sNick, CUser::MakeCleanUsername(sUser)); |
743 | 0 | vsLines.push_back("\tNick = " + sNick); |
744 | 0 | CUtils::GetInput("Alternate nick", sAnswer, sNick + "_"); |
745 | 0 | if (!sAnswer.empty()) { |
746 | 0 | vsLines.push_back("\tAltNick = " + sAnswer); |
747 | 0 | } |
748 | 0 | CUtils::GetInput("Ident", sAnswer, sUser); |
749 | 0 | vsLines.push_back("\tIdent = " + sAnswer); |
750 | 0 | CUtils::GetInput("Real name", sAnswer, "", "optional"); |
751 | 0 | if (!sAnswer.empty()) { |
752 | 0 | vsLines.push_back("\tRealName = " + sAnswer); |
753 | 0 | } |
754 | 0 | CUtils::GetInput("Bind host", sAnswer, "", "optional"); |
755 | 0 | if (!sAnswer.empty()) { |
756 | 0 | vsLines.push_back("\tBindHost = " + sAnswer); |
757 | 0 | } |
758 | |
|
759 | 0 | set<CModInfo> ssUserMods; |
760 | 0 | GetModules().GetDefaultMods(ssUserMods, CModInfo::UserModule); |
761 | 0 | vector<CString> vsUserModNames; |
762 | 0 | for (const CModInfo& Info : ssUserMods) { |
763 | 0 | vsUserModNames.push_back(Info.GetName()); |
764 | 0 | vsLines.push_back("\tLoadModule = " + Info.GetName()); |
765 | 0 | } |
766 | 0 | CUtils::PrintMessage( |
767 | 0 | "Enabled user modules [" + |
768 | 0 | CString(", ").Join(vsUserModNames.begin(), vsUserModNames.end()) + "]"); |
769 | |
|
770 | 0 | CUtils::PrintMessage(""); |
771 | 0 | if (CUtils::GetBoolInput("Set up a network?", true)) { |
772 | 0 | vsLines.push_back(""); |
773 | |
|
774 | 0 | CUtils::PrintMessage(""); |
775 | 0 | CUtils::PrintMessage("-- Network settings --"); |
776 | 0 | CUtils::PrintMessage(""); |
777 | |
|
778 | 0 | do { |
779 | 0 | CUtils::GetInput("Name", sNetwork, "libera"); |
780 | 0 | } while (!CIRCNetwork::IsValidNetwork(sNetwork)); |
781 | |
|
782 | 0 | vsLines.push_back("\t<Network " + sNetwork + ">"); |
783 | |
|
784 | 0 | set<CModInfo> ssNetworkMods; |
785 | 0 | GetModules().GetDefaultMods(ssNetworkMods, CModInfo::NetworkModule); |
786 | 0 | vector<CString> vsNetworkModNames; |
787 | 0 | for (const CModInfo& Info : ssNetworkMods) { |
788 | 0 | vsNetworkModNames.push_back(Info.GetName()); |
789 | 0 | vsLines.push_back("\t\tLoadModule = " + Info.GetName()); |
790 | 0 | } |
791 | |
|
792 | 0 | CString sHost, sPass, sHint; |
793 | 0 | bool bSSL = false; |
794 | 0 | unsigned int uServerPort = 0; |
795 | |
|
796 | 0 | if (sNetwork.Equals("libera")) { |
797 | 0 | sHost = "irc.libera.chat"; |
798 | | #ifdef HAVE_LIBSSL |
799 | | bSSL = true; |
800 | | #endif |
801 | 0 | } else { |
802 | 0 | sHint = "host only"; |
803 | 0 | } |
804 | |
|
805 | 0 | while (!CUtils::GetInput("Server host", sHost, sHost, sHint) || |
806 | 0 | !CServer::IsValidHostName(sHost)) |
807 | 0 | ; |
808 | | #ifdef HAVE_LIBSSL |
809 | | bSSL = CUtils::GetBoolInput("Server uses SSL?", bSSL); |
810 | | #endif |
811 | 0 | while (!CUtils::GetNumInput("Server port", uServerPort, 1, 65535, |
812 | 0 | bSSL ? 6697 : 6667)) |
813 | 0 | ; |
814 | 0 | CUtils::GetInput("Server password (probably empty)", sPass); |
815 | |
|
816 | 0 | vsLines.push_back("\t\tServer = " + sHost + ((bSSL) ? " +" : " ") + |
817 | 0 | CString(uServerPort) + " " + sPass); |
818 | |
|
819 | 0 | CString sChans; |
820 | 0 | if (CUtils::GetInput("Initial channels", sChans)) { |
821 | 0 | vsLines.push_back(""); |
822 | 0 | VCString vsChans; |
823 | 0 | sChans.Replace(",", " "); |
824 | 0 | sChans.Replace(";", " "); |
825 | 0 | sChans.Split(" ", vsChans, false, "", "", true, true); |
826 | 0 | for (const CString& sChan : vsChans) { |
827 | 0 | vsLines.push_back("\t\t<Chan " + sChan + ">"); |
828 | 0 | vsLines.push_back("\t\t</Chan>"); |
829 | 0 | } |
830 | 0 | } |
831 | |
|
832 | 0 | CUtils::PrintMessage("Enabled network modules [" + |
833 | 0 | CString(", ").Join(vsNetworkModNames.begin(), |
834 | 0 | vsNetworkModNames.end()) + |
835 | 0 | "]"); |
836 | |
|
837 | 0 | vsLines.push_back("\t</Network>"); |
838 | 0 | } |
839 | |
|
840 | 0 | vsLines.push_back("</User>"); |
841 | |
|
842 | 0 | CUtils::PrintMessage(""); |
843 | | // !User |
844 | |
|
845 | 0 | CFile File; |
846 | 0 | bool bFileOK, bFileOpen = false; |
847 | 0 | do { |
848 | 0 | CUtils::PrintAction("Writing config [" + m_sConfigFile + "]"); |
849 | |
|
850 | 0 | bFileOK = true; |
851 | 0 | if (CFile::Exists(m_sConfigFile)) { |
852 | 0 | if (!File.TryExLock(m_sConfigFile)) { |
853 | 0 | CUtils::PrintStatus(false, |
854 | 0 | "ZNC is currently running on this config."); |
855 | 0 | bFileOK = false; |
856 | 0 | } else { |
857 | 0 | File.Close(); |
858 | 0 | CUtils::PrintStatus(false, "This config already exists."); |
859 | 0 | if (CUtils::GetBoolInput( |
860 | 0 | "Are you sure you want to overwrite it?", false)) |
861 | 0 | CUtils::PrintAction("Overwriting config [" + m_sConfigFile + |
862 | 0 | "]"); |
863 | 0 | else |
864 | 0 | bFileOK = false; |
865 | 0 | } |
866 | 0 | } |
867 | |
|
868 | 0 | if (bFileOK) { |
869 | 0 | File.SetFileName(m_sConfigFile); |
870 | 0 | if (File.Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) { |
871 | 0 | bFileOpen = true; |
872 | 0 | } else { |
873 | 0 | CUtils::PrintStatus(false, "Unable to open file"); |
874 | 0 | bFileOK = false; |
875 | 0 | } |
876 | 0 | } |
877 | 0 | if (!bFileOK) { |
878 | 0 | while (!CUtils::GetInput("Please specify an alternate location", |
879 | 0 | m_sConfigFile, "", |
880 | 0 | "or \"stdout\" for displaying the config")) |
881 | 0 | ; |
882 | 0 | if (m_sConfigFile.Equals("stdout")) |
883 | 0 | bFileOK = true; |
884 | 0 | else |
885 | 0 | m_sConfigFile = ExpandConfigPath(m_sConfigFile); |
886 | 0 | } |
887 | 0 | } while (!bFileOK); |
888 | |
|
889 | 0 | if (!bFileOpen) { |
890 | 0 | CUtils::PrintMessage(""); |
891 | 0 | CUtils::PrintMessage("Printing the new config to stdout:"); |
892 | 0 | CUtils::PrintMessage(""); |
893 | 0 | cout << endl << "------------------------------------------------------" |
894 | 0 | "----------------------" << endl << endl; |
895 | 0 | } |
896 | |
|
897 | 0 | for (const CString& sLine : vsLines) { |
898 | 0 | if (bFileOpen) { |
899 | 0 | File.Write(sLine + "\n"); |
900 | 0 | } else { |
901 | 0 | cout << sLine << endl; |
902 | 0 | } |
903 | 0 | } |
904 | |
|
905 | 0 | if (bFileOpen) { |
906 | 0 | File.Close(); |
907 | 0 | if (File.HadError()) |
908 | 0 | CUtils::PrintStatus(false, |
909 | 0 | "There was an error while writing the config"); |
910 | 0 | else |
911 | 0 | CUtils::PrintStatus(true); |
912 | 0 | } else { |
913 | 0 | cout << endl << "------------------------------------------------------" |
914 | 0 | "----------------------" << endl << endl; |
915 | 0 | } |
916 | |
|
917 | 0 | if (File.HadError()) { |
918 | 0 | bFileOpen = false; |
919 | 0 | CUtils::PrintMessage("Printing the new config to stdout instead:"); |
920 | 0 | cout << endl << "------------------------------------------------------" |
921 | 0 | "----------------------" << endl << endl; |
922 | 0 | for (const CString& sLine : vsLines) { |
923 | 0 | cout << sLine << endl; |
924 | 0 | } |
925 | 0 | cout << endl << "------------------------------------------------------" |
926 | 0 | "----------------------" << endl << endl; |
927 | 0 | } |
928 | |
|
929 | 0 | const CString sProtocol(bListenSSL ? "https" : "http"); |
930 | 0 | const CString sSSL(bListenSSL ? "+" : ""); |
931 | 0 | CUtils::PrintMessage(""); |
932 | 0 | CUtils::PrintMessage( |
933 | 0 | "To connect to this ZNC you need to connect to it as your IRC server", |
934 | 0 | true); |
935 | 0 | CUtils::PrintMessage( |
936 | 0 | "using the port that you supplied. You have to supply your login info", |
937 | 0 | true); |
938 | 0 | CUtils::PrintMessage( |
939 | 0 | "as the IRC server password like this: user/network:pass.", true); |
940 | 0 | CUtils::PrintMessage(""); |
941 | 0 | CUtils::PrintMessage("Try something like this in your IRC client...", true); |
942 | 0 | CUtils::PrintMessage("/server <znc_server_ip> " + sSSL + |
943 | 0 | CString(uListenPort) + " " + sUser + ":<pass>", |
944 | 0 | true); |
945 | 0 | CUtils::PrintMessage(""); |
946 | 0 | CUtils::PrintMessage( |
947 | 0 | "To manage settings, users and networks, point your web browser to", |
948 | 0 | true); |
949 | 0 | CUtils::PrintMessage( |
950 | 0 | sProtocol + "://<znc_server_ip>:" + CString(uListenPort) + "/", true); |
951 | 0 | CUtils::PrintMessage(""); |
952 | |
|
953 | 0 | File.UnLock(); |
954 | |
|
955 | 0 | bool bWantLaunch = bFileOpen; |
956 | 0 | if (bWantLaunch) { |
957 | | // "export ZNC_NO_LAUNCH_AFTER_MAKECONF=1" would cause znc --makeconf to |
958 | | // not offer immediate launch. |
959 | | // Useful for distros which want to create config when znc package is |
960 | | // installed. |
961 | | // See https://github.com/znc/znc/pull/257 |
962 | 0 | char* szNoLaunch = getenv("ZNC_NO_LAUNCH_AFTER_MAKECONF"); |
963 | 0 | if (szNoLaunch && *szNoLaunch == '1') { |
964 | 0 | bWantLaunch = false; |
965 | 0 | } |
966 | 0 | } |
967 | 0 | if (bWantLaunch) { |
968 | 0 | bWantLaunch = CUtils::GetBoolInput("Launch ZNC now?", true); |
969 | 0 | } |
970 | 0 | return bWantLaunch; |
971 | 0 | } |
972 | | |
973 | 0 | void CZNC::BackupConfigOnce(const CString& sSuffix) { |
974 | 0 | static bool didBackup = false; |
975 | 0 | if (didBackup) return; |
976 | 0 | didBackup = true; |
977 | |
|
978 | 0 | CUtils::PrintAction("Creating a config backup"); |
979 | |
|
980 | 0 | CString sBackup = CDir::ChangeDir(m_sConfigFile, "../znc.conf." + sSuffix); |
981 | 0 | if (CFile::Copy(m_sConfigFile, sBackup)) |
982 | 0 | CUtils::PrintStatus(true, sBackup); |
983 | 0 | else |
984 | 0 | CUtils::PrintStatus(false, strerror(errno)); |
985 | 0 | } |
986 | | |
987 | 0 | bool CZNC::ParseConfig(const CString& sConfig, CString& sError) { |
988 | 0 | m_sConfigFile = ExpandConfigPath(sConfig, false); |
989 | |
|
990 | 0 | CConfig config; |
991 | 0 | if (!ReadConfig(config, sError)) return false; |
992 | | |
993 | 0 | if (!LoadGlobal(config, sError)) return false; |
994 | | |
995 | 0 | if (!LoadUsers(config, sError)) return false; |
996 | | |
997 | 0 | return true; |
998 | 0 | } |
999 | | |
1000 | 0 | bool CZNC::ReadConfig(CConfig& config, CString& sError) { |
1001 | 0 | sError.clear(); |
1002 | |
|
1003 | 0 | CUtils::PrintAction("Opening config [" + m_sConfigFile + "]"); |
1004 | |
|
1005 | 0 | if (!CFile::Exists(m_sConfigFile)) { |
1006 | 0 | sError = "No such file"; |
1007 | 0 | CUtils::PrintStatus(false, sError); |
1008 | 0 | CUtils::PrintMessage( |
1009 | 0 | "Restart ZNC with the --makeconf option if you wish to create this " |
1010 | 0 | "config."); |
1011 | 0 | return false; |
1012 | 0 | } |
1013 | | |
1014 | 0 | if (!CFile::IsReg(m_sConfigFile)) { |
1015 | 0 | sError = "Not a file"; |
1016 | 0 | CUtils::PrintStatus(false, sError); |
1017 | 0 | return false; |
1018 | 0 | } |
1019 | | |
1020 | 0 | CFile* pFile = new CFile(m_sConfigFile); |
1021 | | |
1022 | | // need to open the config file Read/Write for fcntl() |
1023 | | // exclusive locking to work properly! |
1024 | 0 | if (!pFile->Open(m_sConfigFile, O_RDWR)) { |
1025 | 0 | sError = "Can not open config file"; |
1026 | 0 | CUtils::PrintStatus(false, sError); |
1027 | 0 | delete pFile; |
1028 | 0 | return false; |
1029 | 0 | } |
1030 | | |
1031 | 0 | if (!pFile->TryExLock()) { |
1032 | 0 | sError = "ZNC is already running on this config."; |
1033 | 0 | CUtils::PrintStatus(false, sError); |
1034 | 0 | delete pFile; |
1035 | 0 | return false; |
1036 | 0 | } |
1037 | | |
1038 | | // (re)open the config file |
1039 | 0 | delete m_pLockFile; |
1040 | 0 | m_pLockFile = pFile; |
1041 | 0 | CFile& File = *pFile; |
1042 | |
|
1043 | 0 | if (!config.Parse(File, sError)) { |
1044 | 0 | CUtils::PrintStatus(false, sError); |
1045 | 0 | return false; |
1046 | 0 | } |
1047 | 0 | CUtils::PrintStatus(true); |
1048 | | |
1049 | | // check if config is from old ZNC version and |
1050 | | // create a backup file if necessary |
1051 | 0 | CString sSavedVersion; |
1052 | 0 | config.FindStringEntry("version", sSavedVersion); |
1053 | 0 | config.AddKeyValuePair("version", sSavedVersion); |
1054 | 0 | if (sSavedVersion.empty()) { |
1055 | 0 | CUtils::PrintError( |
1056 | 0 | "Config does not contain a version identifier. It may be be too " |
1057 | 0 | "old or corrupt."); |
1058 | 0 | return false; |
1059 | 0 | } |
1060 | | |
1061 | 0 | tuple<unsigned int, unsigned int> tSavedVersion = |
1062 | 0 | make_tuple(sSavedVersion.Token(0, false, ".").ToUInt(), |
1063 | 0 | sSavedVersion.Token(1, false, ".").ToUInt()); |
1064 | 0 | tuple<unsigned int, unsigned int> tCurrentVersion = |
1065 | 0 | make_tuple(VERSION_MAJOR, VERSION_MINOR); |
1066 | 0 | if (tSavedVersion < tCurrentVersion) { |
1067 | 0 | CUtils::PrintMessage("Found old config from ZNC " + sSavedVersion + |
1068 | 0 | ". Saving a backup of it."); |
1069 | 0 | BackupConfigOnce("pre-" + CString(VERSION_STR)); |
1070 | 0 | } else if (tSavedVersion > tCurrentVersion) { |
1071 | 0 | CUtils::PrintError("Config was saved from ZNC " + sSavedVersion + |
1072 | 0 | ". It may or may not work with current ZNC " + |
1073 | 0 | GetVersion()); |
1074 | 0 | } |
1075 | |
|
1076 | 0 | return true; |
1077 | 0 | } |
1078 | | |
1079 | 0 | bool CZNC::RehashConfig(CString& sError) { |
1080 | 0 | ALLMODULECALL(OnPreRehash(), NOTHING); |
1081 | |
|
1082 | 0 | CConfig config; |
1083 | 0 | if (!ReadConfig(config, sError)) return false; |
1084 | | |
1085 | 0 | if (!LoadGlobal(config, sError)) return false; |
1086 | | |
1087 | | // do not reload users - it's dangerous! |
1088 | | |
1089 | 0 | ALLMODULECALL(OnPostRehash(), NOTHING); |
1090 | 0 | return true; |
1091 | 0 | } |
1092 | | |
1093 | 0 | bool CZNC::LoadGlobal(CConfig& config, CString& sError) { |
1094 | 0 | sError.clear(); |
1095 | |
|
1096 | 0 | CString sSavedVersion; |
1097 | 0 | config.FindStringEntry("version", sSavedVersion); |
1098 | 0 | tuple<unsigned int, unsigned int> tSavedVersion = |
1099 | 0 | make_tuple(sSavedVersion.Token(0, false, ".").ToUInt(), |
1100 | 0 | sSavedVersion.Token(1, false, ".").ToUInt()); |
1101 | |
|
1102 | 0 | MCString msModules; // Modules are queued for later loading |
1103 | |
|
1104 | 0 | VCString vsList; |
1105 | 0 | config.FindStringVector("loadmodule", vsList); |
1106 | | |
1107 | | // Automatically load corecaps if config was upgraded from old version, but |
1108 | | // don't force it if user explicitly unloaded it |
1109 | 0 | if (tSavedVersion < make_tuple(1, 9)) { |
1110 | 0 | vsList.push_back("corecaps"); |
1111 | 0 | } |
1112 | |
|
1113 | 0 | for (const CString& sModLine : vsList) { |
1114 | 0 | CString sModName = sModLine.Token(0); |
1115 | 0 | CString sArgs = sModLine.Token(1, true); |
1116 | | |
1117 | | // compatibility for pre-1.0 configs |
1118 | 0 | if (sModName == "saslauth" && tSavedVersion < make_tuple(0, 207)) { |
1119 | 0 | CUtils::PrintMessage( |
1120 | 0 | "saslauth module was renamed to cyrusauth. Loading cyrusauth " |
1121 | 0 | "instead."); |
1122 | 0 | sModName = "cyrusauth"; |
1123 | 0 | } |
1124 | | // end-compatibility for pre-1.0 configs |
1125 | |
|
1126 | 0 | if (msModules.find(sModName) != msModules.end()) { |
1127 | 0 | sError = "Module [" + sModName + "] already loaded"; |
1128 | 0 | CUtils::PrintError(sError); |
1129 | 0 | return false; |
1130 | 0 | } |
1131 | 0 | CString sModRet; |
1132 | 0 | CModule* pOldMod; |
1133 | |
|
1134 | 0 | pOldMod = GetModules().FindModule(sModName); |
1135 | 0 | if (!pOldMod) { |
1136 | 0 | CUtils::PrintAction("Loading global module [" + sModName + "]"); |
1137 | |
|
1138 | 0 | bool bModRet = |
1139 | 0 | GetModules().LoadModule(sModName, sArgs, CModInfo::GlobalModule, |
1140 | 0 | nullptr, nullptr, sModRet); |
1141 | |
|
1142 | 0 | CUtils::PrintStatus(bModRet, bModRet ? "" : sModRet); |
1143 | 0 | if (!bModRet) { |
1144 | 0 | sError = sModRet; |
1145 | 0 | return false; |
1146 | 0 | } |
1147 | 0 | } else if (pOldMod->GetArgs() != sArgs) { |
1148 | 0 | CUtils::PrintAction("Reloading global module [" + sModName + "]"); |
1149 | |
|
1150 | 0 | bool bModRet = GetModules().ReloadModule(sModName, sArgs, nullptr, |
1151 | 0 | nullptr, sModRet); |
1152 | |
|
1153 | 0 | CUtils::PrintStatus(bModRet, sModRet); |
1154 | 0 | if (!bModRet) { |
1155 | 0 | sError = sModRet; |
1156 | 0 | return false; |
1157 | 0 | } |
1158 | 0 | } else |
1159 | 0 | CUtils::PrintMessage("Module [" + sModName + "] already loaded."); |
1160 | | |
1161 | 0 | msModules[sModName] = sArgs; |
1162 | 0 | } |
1163 | | |
1164 | 0 | m_vsMotd.clear(); |
1165 | 0 | config.FindStringVector("motd", vsList); |
1166 | 0 | for (const CString& sMotd : vsList) { |
1167 | 0 | AddMotd(sMotd); |
1168 | 0 | } |
1169 | |
|
1170 | 0 | if (config.FindStringVector("bindhost", vsList)) { |
1171 | 0 | CUtils::PrintStatus(false, |
1172 | 0 | "WARNING: the global BindHost list is deprecated. " |
1173 | 0 | "Ignoring the following lines:"); |
1174 | 0 | for (const CString& sHost : vsList) { |
1175 | 0 | CUtils::PrintStatus(false, "BindHost = " + sHost); |
1176 | 0 | } |
1177 | 0 | } |
1178 | 0 | if (config.FindStringVector("vhost", vsList)) { |
1179 | 0 | CUtils::PrintStatus(false, |
1180 | 0 | "WARNING: the global vHost list is deprecated. " |
1181 | 0 | "Ignoring the following lines:"); |
1182 | 0 | for (const CString& sHost : vsList) { |
1183 | 0 | CUtils::PrintStatus(false, "vHost = " + sHost); |
1184 | 0 | } |
1185 | 0 | } |
1186 | |
|
1187 | 0 | m_vsTrustedProxies.clear(); |
1188 | 0 | config.FindStringVector("trustedproxy", vsList); |
1189 | 0 | for (const CString& sProxy : vsList) { |
1190 | 0 | AddTrustedProxy(sProxy); |
1191 | 0 | } |
1192 | |
|
1193 | 0 | CString sVal; |
1194 | 0 | if (config.FindStringEntry("pidfile", sVal)) m_sPidFile = sVal; |
1195 | 0 | if (config.FindStringEntry("statusprefix", sVal)) m_sStatusPrefix = sVal; |
1196 | 0 | if (config.FindStringEntry("sslcertfile", sVal)) m_sSSLCertFile = sVal; |
1197 | 0 | if (config.FindStringEntry("sslkeyfile", sVal)) m_sSSLKeyFile = sVal; |
1198 | 0 | if (config.FindStringEntry("ssldhparamfile", sVal)) |
1199 | 0 | m_sSSLDHParamFile = sVal; |
1200 | 0 | if (config.FindStringEntry("sslciphers", sVal)) m_sSSLCiphers = sVal; |
1201 | 0 | if (config.FindStringEntry("skin", sVal)) SetSkinName(sVal); |
1202 | 0 | if (config.FindStringEntry("connectdelay", sVal)) |
1203 | 0 | SetConnectDelay(sVal.ToUInt()); |
1204 | 0 | if (config.FindStringEntry("serverthrottle", sVal)) |
1205 | 0 | m_sConnectThrottle.SetTTL(sVal.ToUInt() * 1000); |
1206 | 0 | if (config.FindStringEntry("anoniplimit", sVal)) |
1207 | 0 | m_uiAnonIPLimit = sVal.ToUInt(); |
1208 | 0 | if (config.FindStringEntry("maxbuffersize", sVal)) |
1209 | 0 | m_uiMaxBufferSize = sVal.ToUInt(); |
1210 | 0 | if (config.FindStringEntry("protectwebsessions", sVal)) |
1211 | 0 | m_bProtectWebSessions = sVal.ToBool(); |
1212 | 0 | if (config.FindStringEntry("hideversion", sVal)) |
1213 | 0 | m_bHideVersion = sVal.ToBool(); |
1214 | 0 | if (config.FindStringEntry("authonlyviamodule", sVal)) |
1215 | 0 | m_bAuthOnlyViaModule = sVal.ToBool(); |
1216 | 0 | if (config.FindStringEntry("sslprotocols", sVal)) { |
1217 | 0 | if (!SetSSLProtocols(sVal)) { |
1218 | 0 | VCString vsProtocols = GetAvailableSSLProtocols(); |
1219 | 0 | CUtils::PrintError("Invalid SSLProtocols value [" + sVal + "]"); |
1220 | 0 | CUtils::PrintError( |
1221 | 0 | "The syntax is [SSLProtocols = [+|-]<protocol> ...]"); |
1222 | 0 | CUtils::PrintError( |
1223 | 0 | "Available protocols are [" + |
1224 | 0 | CString(", ").Join(vsProtocols.begin(), vsProtocols.end()) + |
1225 | 0 | "]"); |
1226 | 0 | return false; |
1227 | 0 | } |
1228 | 0 | } |
1229 | 0 | if (config.FindStringEntry("configwritedelay", sVal)) |
1230 | 0 | m_uiConfigWriteDelay = sVal.ToUInt(); |
1231 | |
|
1232 | 0 | UnloadRemovedModules(msModules); |
1233 | |
|
1234 | 0 | if (!LoadListeners(config, sError)) return false; |
1235 | | |
1236 | 0 | return true; |
1237 | 0 | } |
1238 | | |
1239 | 0 | bool CZNC::LoadUsers(CConfig& config, CString& sError) { |
1240 | 0 | sError.clear(); |
1241 | |
|
1242 | 0 | m_msUsers.clear(); |
1243 | |
|
1244 | 0 | CConfig::SubConfig subConf; |
1245 | 0 | config.FindSubConfig("user", subConf); |
1246 | |
|
1247 | 0 | for (const auto& subIt : subConf) { |
1248 | 0 | const CString& sUsername = subIt.first; |
1249 | 0 | CConfig* pSubConf = subIt.second.m_pSubConfig; |
1250 | |
|
1251 | 0 | CUtils::PrintMessage("Loading user [" + sUsername + "]"); |
1252 | |
|
1253 | 0 | std::unique_ptr<CUser> pUser(new CUser(sUsername)); |
1254 | |
|
1255 | 0 | if (!m_sStatusPrefix.empty()) { |
1256 | 0 | if (!pUser->SetStatusPrefix(m_sStatusPrefix)) { |
1257 | 0 | sError = "Invalid StatusPrefix [" + m_sStatusPrefix + |
1258 | 0 | "] Must be 1-5 chars, no spaces."; |
1259 | 0 | CUtils::PrintError(sError); |
1260 | 0 | return false; |
1261 | 0 | } |
1262 | 0 | } |
1263 | | |
1264 | 0 | if (!pUser->ParseConfig(pSubConf, sError)) { |
1265 | 0 | CUtils::PrintError(sError); |
1266 | 0 | return false; |
1267 | 0 | } |
1268 | | |
1269 | 0 | if (!pSubConf->empty()) { |
1270 | 0 | sError = "Unhandled lines in config for User [" + sUsername + "]!"; |
1271 | 0 | CUtils::PrintError(sError); |
1272 | 0 | DumpConfig(pSubConf); |
1273 | 0 | return false; |
1274 | 0 | } |
1275 | | |
1276 | 0 | CString sErr; |
1277 | 0 | CUser* pRawUser = pUser.release(); |
1278 | 0 | if (!AddUser(pRawUser, sErr, true)) { |
1279 | 0 | sError = "Invalid user [" + sUsername + "] " + sErr; |
1280 | 0 | CUtils::PrintError(sError); |
1281 | 0 | pRawUser->SetBeingDeleted(true); |
1282 | 0 | delete pRawUser; |
1283 | 0 | return false; |
1284 | 0 | } |
1285 | 0 | } |
1286 | | |
1287 | 0 | if (m_msUsers.empty()) { |
1288 | 0 | sError = "You must define at least one user in your config."; |
1289 | 0 | CUtils::PrintError(sError); |
1290 | 0 | return false; |
1291 | 0 | } |
1292 | | |
1293 | 0 | return true; |
1294 | 0 | } |
1295 | | |
1296 | 0 | bool CZNC::LoadListeners(CConfig& config, CString& sError) { |
1297 | 0 | sError.clear(); |
1298 | | |
1299 | | // Delete all listeners |
1300 | 0 | while (!m_vpListeners.empty()) { |
1301 | 0 | delete m_vpListeners[0]; |
1302 | 0 | m_vpListeners.erase(m_vpListeners.begin()); |
1303 | 0 | } |
1304 | | |
1305 | | // compatibility for pre-1.0 configs |
1306 | 0 | const char* szListenerEntries[] = {"listen", "listen6", "listen4", |
1307 | 0 | "listener", "listener6", "listener4"}; |
1308 | |
|
1309 | 0 | VCString vsList; |
1310 | 0 | config.FindStringVector("loadmodule", vsList); |
1311 | | |
1312 | | // This has to be after SSLCertFile is handled since it uses that value |
1313 | 0 | for (const char* szEntry : szListenerEntries) { |
1314 | 0 | config.FindStringVector(szEntry, vsList); |
1315 | 0 | for (const CString& sListener : vsList) { |
1316 | 0 | if (!AddListener(szEntry + CString(" ") + sListener, sError)) |
1317 | 0 | return false; |
1318 | 0 | } |
1319 | 0 | } |
1320 | | // end-compatibility for pre-1.0 configs |
1321 | | |
1322 | 0 | CConfig::SubConfig subConf; |
1323 | 0 | config.FindSubConfig("listener", subConf); |
1324 | |
|
1325 | 0 | for (const auto& subIt : subConf) { |
1326 | 0 | CConfig* pSubConf = subIt.second.m_pSubConfig; |
1327 | 0 | if (!AddListener(pSubConf, sError)) return false; |
1328 | 0 | if (!pSubConf->empty()) { |
1329 | 0 | sError = "Unhandled lines in Listener config!"; |
1330 | 0 | CUtils::PrintError(sError); |
1331 | |
|
1332 | 0 | CZNC::DumpConfig(pSubConf); |
1333 | 0 | return false; |
1334 | 0 | } |
1335 | 0 | } |
1336 | | |
1337 | 0 | if (m_vpListeners.empty()) { |
1338 | 0 | sError = "You must supply at least one Listener in your config."; |
1339 | 0 | CUtils::PrintError(sError); |
1340 | 0 | return false; |
1341 | 0 | } |
1342 | | |
1343 | 0 | return true; |
1344 | 0 | } |
1345 | | |
1346 | 0 | void CZNC::UnloadRemovedModules(const MCString& msModules) { |
1347 | | // unload modules which are no longer in the config |
1348 | |
|
1349 | 0 | set<CString> ssUnload; |
1350 | 0 | for (CModule* pCurMod : GetModules()) { |
1351 | 0 | if (msModules.find(pCurMod->GetModName()) == msModules.end()) |
1352 | 0 | ssUnload.insert(pCurMod->GetModName()); |
1353 | 0 | } |
1354 | |
|
1355 | 0 | for (const CString& sMod : ssUnload) { |
1356 | 0 | if (GetModules().UnloadModule(sMod)) |
1357 | 0 | CUtils::PrintMessage("Unloaded global module [" + sMod + "]"); |
1358 | 0 | else |
1359 | 0 | CUtils::PrintMessage("Could not unload [" + sMod + "]"); |
1360 | 0 | } |
1361 | 0 | } |
1362 | | |
1363 | 0 | void CZNC::DumpConfig(const CConfig* pConfig) { |
1364 | 0 | CConfig::EntryMapIterator eit = pConfig->BeginEntries(); |
1365 | 0 | for (; eit != pConfig->EndEntries(); ++eit) { |
1366 | 0 | const CString& sKey = eit->first; |
1367 | 0 | const VCString& vsList = eit->second; |
1368 | 0 | VCString::const_iterator it = vsList.begin(); |
1369 | 0 | for (; it != vsList.end(); ++it) { |
1370 | 0 | CUtils::PrintError(sKey + " = " + *it); |
1371 | 0 | } |
1372 | 0 | } |
1373 | |
|
1374 | 0 | CConfig::SubConfigMapIterator sit = pConfig->BeginSubConfigs(); |
1375 | 0 | for (; sit != pConfig->EndSubConfigs(); ++sit) { |
1376 | 0 | const CString& sKey = sit->first; |
1377 | 0 | const CConfig::SubConfig& sSub = sit->second; |
1378 | 0 | CConfig::SubConfig::const_iterator it = sSub.begin(); |
1379 | |
|
1380 | 0 | for (; it != sSub.end(); ++it) { |
1381 | 0 | CUtils::PrintError("SubConfig [" + sKey + " " + it->first + "]:"); |
1382 | 0 | DumpConfig(it->second.m_pSubConfig); |
1383 | 0 | } |
1384 | 0 | } |
1385 | 0 | } |
1386 | | |
1387 | 0 | void CZNC::ClearTrustedProxies() { m_vsTrustedProxies.clear(); } |
1388 | | |
1389 | 0 | bool CZNC::AddTrustedProxy(const CString& sHost) { |
1390 | 0 | if (sHost.empty()) { |
1391 | 0 | return false; |
1392 | 0 | } |
1393 | | |
1394 | 0 | for (const CString& sTrustedProxy : m_vsTrustedProxies) { |
1395 | 0 | if (sTrustedProxy.Equals(sHost)) { |
1396 | 0 | return false; |
1397 | 0 | } |
1398 | 0 | } |
1399 | | |
1400 | 0 | m_vsTrustedProxies.push_back(sHost); |
1401 | 0 | return true; |
1402 | 0 | } |
1403 | | |
1404 | 0 | bool CZNC::RemTrustedProxy(const CString& sHost) { |
1405 | 0 | VCString::iterator it; |
1406 | 0 | for (it = m_vsTrustedProxies.begin(); it != m_vsTrustedProxies.end(); |
1407 | 0 | ++it) { |
1408 | 0 | if (sHost.Equals(*it)) { |
1409 | 0 | m_vsTrustedProxies.erase(it); |
1410 | 0 | return true; |
1411 | 0 | } |
1412 | 0 | } |
1413 | | |
1414 | 0 | return false; |
1415 | 0 | } |
1416 | | |
1417 | | void CZNC::Broadcast(const CString& sMessage, bool bAdminOnly, CUser* pSkipUser, |
1418 | 0 | CClient* pSkipClient) { |
1419 | 0 | for (const auto& it : m_msUsers) { |
1420 | 0 | if (bAdminOnly && !it.second->IsAdmin()) continue; |
1421 | | |
1422 | 0 | if (it.second != pSkipUser) { |
1423 | | // TODO: translate message to user's language |
1424 | 0 | CString sMsg = sMessage; |
1425 | |
|
1426 | 0 | bool bContinue = false; |
1427 | 0 | USERMODULECALL(OnBroadcast(sMsg), it.second, nullptr, &bContinue); |
1428 | 0 | if (bContinue) continue; |
1429 | | |
1430 | 0 | it.second->PutStatusNotice("*** " + sMsg, nullptr, pSkipClient); |
1431 | 0 | } |
1432 | 0 | } |
1433 | 0 | } |
1434 | | |
1435 | 0 | CModule* CZNC::FindModule(const CString& sModName, const CString& sUsername) { |
1436 | 0 | if (sUsername.empty()) { |
1437 | 0 | return CZNC::Get().GetModules().FindModule(sModName); |
1438 | 0 | } |
1439 | | |
1440 | 0 | CUser* pUser = FindUser(sUsername); |
1441 | |
|
1442 | 0 | return (!pUser) ? nullptr : pUser->GetModules().FindModule(sModName); |
1443 | 0 | } |
1444 | | |
1445 | 0 | CModule* CZNC::FindModule(const CString& sModName, CUser* pUser) { |
1446 | 0 | if (pUser) { |
1447 | 0 | return pUser->GetModules().FindModule(sModName); |
1448 | 0 | } |
1449 | | |
1450 | 0 | return CZNC::Get().GetModules().FindModule(sModName); |
1451 | 0 | } |
1452 | | |
1453 | 0 | bool CZNC::UpdateModule(const CString& sModule) { |
1454 | 0 | CModule* pModule; |
1455 | |
|
1456 | 0 | map<CUser*, CString> musLoaded; |
1457 | 0 | map<CIRCNetwork*, CString> mnsLoaded; |
1458 | | |
1459 | | // Unload the module for every user and network |
1460 | 0 | for (const auto& it : m_msUsers) { |
1461 | 0 | CUser* pUser = it.second; |
1462 | |
|
1463 | 0 | pModule = pUser->GetModules().FindModule(sModule); |
1464 | 0 | if (pModule) { |
1465 | 0 | musLoaded[pUser] = pModule->GetArgs(); |
1466 | 0 | pUser->GetModules().UnloadModule(sModule); |
1467 | 0 | } |
1468 | | |
1469 | | // See if the user has this module loaded to a network |
1470 | 0 | vector<CIRCNetwork*> vNetworks = pUser->GetNetworks(); |
1471 | 0 | for (CIRCNetwork* pNetwork : vNetworks) { |
1472 | 0 | pModule = pNetwork->GetModules().FindModule(sModule); |
1473 | 0 | if (pModule) { |
1474 | 0 | mnsLoaded[pNetwork] = pModule->GetArgs(); |
1475 | 0 | pNetwork->GetModules().UnloadModule(sModule); |
1476 | 0 | } |
1477 | 0 | } |
1478 | 0 | } |
1479 | | |
1480 | | // Unload the global module |
1481 | 0 | bool bGlobal = false; |
1482 | 0 | CString sGlobalArgs; |
1483 | |
|
1484 | 0 | pModule = GetModules().FindModule(sModule); |
1485 | 0 | if (pModule) { |
1486 | 0 | bGlobal = true; |
1487 | 0 | sGlobalArgs = pModule->GetArgs(); |
1488 | 0 | GetModules().UnloadModule(sModule); |
1489 | 0 | } |
1490 | | |
1491 | | // Lets reload everything |
1492 | 0 | bool bError = false; |
1493 | 0 | CString sErr; |
1494 | | |
1495 | | // Reload the global module |
1496 | 0 | if (bGlobal) { |
1497 | 0 | if (!GetModules().LoadModule(sModule, sGlobalArgs, |
1498 | 0 | CModInfo::GlobalModule, nullptr, nullptr, |
1499 | 0 | sErr)) { |
1500 | 0 | DEBUG("Failed to reload [" << sModule << "] globally [" << sErr |
1501 | 0 | << "]"); |
1502 | 0 | bError = true; |
1503 | 0 | } |
1504 | 0 | } |
1505 | | |
1506 | | // Reload the module for all users |
1507 | 0 | for (const auto& it : musLoaded) { |
1508 | 0 | CUser* pUser = it.first; |
1509 | 0 | const CString& sArgs = it.second; |
1510 | |
|
1511 | 0 | if (!pUser->GetModules().LoadModule( |
1512 | 0 | sModule, sArgs, CModInfo::UserModule, pUser, nullptr, sErr)) { |
1513 | 0 | DEBUG("Failed to reload [" << sModule << "] for [" |
1514 | 0 | << pUser->GetUsername() << "] [" << sErr |
1515 | 0 | << "]"); |
1516 | 0 | bError = true; |
1517 | 0 | } |
1518 | 0 | } |
1519 | | |
1520 | | // Reload the module for all networks |
1521 | 0 | for (const auto& it : mnsLoaded) { |
1522 | 0 | CIRCNetwork* pNetwork = it.first; |
1523 | 0 | const CString& sArgs = it.second; |
1524 | |
|
1525 | 0 | if (!pNetwork->GetModules().LoadModule( |
1526 | 0 | sModule, sArgs, CModInfo::NetworkModule, pNetwork->GetUser(), |
1527 | 0 | pNetwork, sErr)) { |
1528 | 0 | DEBUG("Failed to reload [" |
1529 | 0 | << sModule << "] for [" << pNetwork->GetUser()->GetUsername() |
1530 | 0 | << "/" << pNetwork->GetName() << "] [" << sErr << "]"); |
1531 | 0 | bError = true; |
1532 | 0 | } |
1533 | 0 | } |
1534 | |
|
1535 | 0 | return !bError; |
1536 | 0 | } |
1537 | | |
1538 | 0 | CUser* CZNC::FindUser(const CString& sUsername) { |
1539 | 0 | map<CString, CUser*>::iterator it = m_msUsers.find(sUsername); |
1540 | |
|
1541 | 0 | if (it != m_msUsers.end()) { |
1542 | 0 | return it->second; |
1543 | 0 | } |
1544 | | |
1545 | 0 | return nullptr; |
1546 | 0 | } |
1547 | | |
1548 | 0 | bool CZNC::DeleteUser(const CString& sUsername) { |
1549 | 0 | CUser* pUser = FindUser(sUsername); |
1550 | |
|
1551 | 0 | if (!pUser) { |
1552 | 0 | return false; |
1553 | 0 | } |
1554 | | |
1555 | 0 | m_msDelUsers[pUser->GetUsername()] = pUser; |
1556 | 0 | return true; |
1557 | 0 | } |
1558 | | |
1559 | 0 | bool CZNC::AddUser(CUser* pUser, CString& sErrorRet, bool bStartup) { |
1560 | 0 | if (FindUser(pUser->GetUsername()) != nullptr) { |
1561 | 0 | sErrorRet = t_s("User already exists"); |
1562 | 0 | DEBUG("User [" << pUser->GetUsername() << "] - already exists"); |
1563 | 0 | return false; |
1564 | 0 | } |
1565 | 0 | if (!pUser->IsValid(sErrorRet)) { |
1566 | 0 | DEBUG("Invalid user [" << pUser->GetUsername() << "] - [" << sErrorRet |
1567 | 0 | << "]"); |
1568 | 0 | return false; |
1569 | 0 | } |
1570 | 0 | bool bFailed = false; |
1571 | | |
1572 | | // do not call OnAddUser hook during ZNC startup |
1573 | 0 | if (!bStartup) { |
1574 | 0 | GLOBALMODULECALL(OnAddUser(*pUser, sErrorRet), &bFailed); |
1575 | 0 | } |
1576 | |
|
1577 | 0 | if (bFailed) { |
1578 | 0 | DEBUG("AddUser [" << pUser->GetUsername() << "] aborted by a module [" |
1579 | 0 | << sErrorRet << "]"); |
1580 | 0 | return false; |
1581 | 0 | } |
1582 | 0 | m_msUsers[pUser->GetUsername()] = pUser; |
1583 | 0 | return true; |
1584 | 0 | } |
1585 | | |
1586 | | CListener* CZNC::FindListener(u_short uPort, const CString& sBindHost, |
1587 | 0 | EAddrType eAddr) { |
1588 | 0 | for (CListener* pListener : m_vpListeners) { |
1589 | 0 | if (pListener->GetPort() != uPort) continue; |
1590 | 0 | if (pListener->GetBindHost() != sBindHost) continue; |
1591 | 0 | if (pListener->GetAddrType() != eAddr) continue; |
1592 | 0 | return pListener; |
1593 | 0 | } |
1594 | 0 | return nullptr; |
1595 | 0 | } |
1596 | | |
1597 | 0 | bool CZNC::AddListener(const CString& sLine, CString& sError) { |
1598 | 0 | CString sName = sLine.Token(0); |
1599 | 0 | CString sValue = sLine.Token(1, true); |
1600 | |
|
1601 | 0 | EAddrType eAddr = ADDR_ALL; |
1602 | 0 | if (sName.Equals("Listen4") || sName.Equals("Listen") || |
1603 | 0 | sName.Equals("Listener4")) { |
1604 | 0 | eAddr = ADDR_IPV4ONLY; |
1605 | 0 | } |
1606 | 0 | if (sName.Equals("Listener6")) { |
1607 | 0 | eAddr = ADDR_IPV6ONLY; |
1608 | 0 | } |
1609 | |
|
1610 | 0 | CListener::EAcceptType eAccept = CListener::ACCEPT_ALL; |
1611 | 0 | if (sValue.TrimPrefix("irc_only ")) |
1612 | 0 | eAccept = CListener::ACCEPT_IRC; |
1613 | 0 | else if (sValue.TrimPrefix("web_only ")) |
1614 | 0 | eAccept = CListener::ACCEPT_HTTP; |
1615 | |
|
1616 | 0 | bool bSSL = false; |
1617 | 0 | CString sPort; |
1618 | 0 | CString sBindHost; |
1619 | |
|
1620 | 0 | if (ADDR_IPV4ONLY == eAddr) { |
1621 | 0 | sValue.Replace(":", " "); |
1622 | 0 | } |
1623 | |
|
1624 | 0 | if (sValue.Contains(" ")) { |
1625 | 0 | sBindHost = sValue.Token(0, false, " "); |
1626 | 0 | sPort = sValue.Token(1, true, " "); |
1627 | 0 | } else { |
1628 | 0 | sPort = sValue; |
1629 | 0 | } |
1630 | |
|
1631 | 0 | if (sPort.TrimPrefix("+")) { |
1632 | 0 | bSSL = true; |
1633 | 0 | } |
1634 | | |
1635 | | // No support for URIPrefix for old-style configs. |
1636 | 0 | CString sURIPrefix; |
1637 | 0 | unsigned short uPort = sPort.ToUShort(); |
1638 | 0 | return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept, |
1639 | 0 | sError); |
1640 | 0 | } |
1641 | | |
1642 | | bool CZNC::AddListener(unsigned short uPort, const CString& sBindHost, |
1643 | | const CString& sURIPrefixRaw, bool bSSL, EAddrType eAddr, |
1644 | 0 | CListener::EAcceptType eAccept, CString& sError) { |
1645 | 0 | CString sHostComment; |
1646 | |
|
1647 | 0 | if (!sBindHost.empty()) { |
1648 | 0 | sHostComment = " on host [" + sBindHost + "]"; |
1649 | 0 | } |
1650 | |
|
1651 | 0 | CString sIPV6Comment; |
1652 | |
|
1653 | 0 | switch (eAddr) { |
1654 | 0 | case ADDR_ALL: |
1655 | 0 | sIPV6Comment = ""; |
1656 | 0 | break; |
1657 | 0 | case ADDR_IPV4ONLY: |
1658 | 0 | sIPV6Comment = " using ipv4"; |
1659 | 0 | break; |
1660 | 0 | case ADDR_IPV6ONLY: |
1661 | 0 | sIPV6Comment = " using ipv6"; |
1662 | 0 | } |
1663 | | |
1664 | 0 | CUtils::PrintAction("Binding to port [" + CString((bSSL) ? "+" : "") + |
1665 | 0 | CString(uPort) + "]" + sHostComment + sIPV6Comment); |
1666 | |
|
1667 | 0 | #ifndef HAVE_IPV6 |
1668 | 0 | if (ADDR_IPV6ONLY == eAddr) { |
1669 | 0 | sError = t_s("IPv6 is not enabled"); |
1670 | 0 | CUtils::PrintStatus(false, sError); |
1671 | 0 | return false; |
1672 | 0 | } |
1673 | 0 | #endif |
1674 | | |
1675 | 0 | #ifndef HAVE_LIBSSL |
1676 | 0 | if (bSSL) { |
1677 | 0 | sError = t_s("SSL is not enabled"); |
1678 | 0 | CUtils::PrintStatus(false, sError); |
1679 | 0 | return false; |
1680 | 0 | } |
1681 | | #else |
1682 | | CString sPemFile = GetPemLocation(); |
1683 | | |
1684 | | if (bSSL && !CFile::Exists(sPemFile)) { |
1685 | | sError = t_f("Unable to locate pem file: {1}")(sPemFile); |
1686 | | CUtils::PrintStatus(false, sError); |
1687 | | |
1688 | | // If stdin is e.g. /dev/null and we call GetBoolInput(), |
1689 | | // we are stuck in an endless loop! |
1690 | | if (isatty(0) && |
1691 | | CUtils::GetBoolInput("Would you like to create a new pem file?", |
1692 | | true)) { |
1693 | | sError.clear(); |
1694 | | WritePemFile(); |
1695 | | } else { |
1696 | | return false; |
1697 | | } |
1698 | | |
1699 | | CUtils::PrintAction("Binding to port [+" + CString(uPort) + "]" + |
1700 | | sHostComment + sIPV6Comment); |
1701 | | } |
1702 | | #endif |
1703 | 0 | if (!uPort) { |
1704 | 0 | sError = t_s("Invalid port"); |
1705 | 0 | CUtils::PrintStatus(false, sError); |
1706 | 0 | return false; |
1707 | 0 | } |
1708 | | |
1709 | | // URIPrefix must start with a slash and end without one. |
1710 | 0 | CString sURIPrefix = CString(sURIPrefixRaw); |
1711 | 0 | if (!sURIPrefix.empty()) { |
1712 | 0 | if (!sURIPrefix.StartsWith("/")) { |
1713 | 0 | sURIPrefix = "/" + sURIPrefix; |
1714 | 0 | } |
1715 | 0 | if (sURIPrefix.EndsWith("/")) { |
1716 | 0 | sURIPrefix.TrimRight("/"); |
1717 | 0 | } |
1718 | 0 | } |
1719 | |
|
1720 | 0 | CListener* pListener = |
1721 | 0 | new CListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept); |
1722 | |
|
1723 | 0 | if (!pListener->Listen()) { |
1724 | 0 | sError = FormatBindError(); |
1725 | 0 | CUtils::PrintStatus(false, sError); |
1726 | 0 | delete pListener; |
1727 | 0 | return false; |
1728 | 0 | } |
1729 | | |
1730 | 0 | m_vpListeners.push_back(pListener); |
1731 | 0 | CUtils::PrintStatus(true); |
1732 | |
|
1733 | 0 | return true; |
1734 | 0 | } |
1735 | | |
1736 | 0 | bool CZNC::AddListener(CConfig* pConfig, CString& sError) { |
1737 | 0 | CString sBindHost; |
1738 | 0 | CString sURIPrefix; |
1739 | 0 | bool bSSL; |
1740 | 0 | bool b4; |
1741 | | #ifdef HAVE_IPV6 |
1742 | | bool b6 = true; |
1743 | | #else |
1744 | 0 | bool b6 = false; |
1745 | 0 | #endif |
1746 | 0 | bool bIRC; |
1747 | 0 | bool bWeb; |
1748 | 0 | unsigned short uPort; |
1749 | 0 | if (!pConfig->FindUShortEntry("port", uPort)) { |
1750 | 0 | sError = "No port given"; |
1751 | 0 | CUtils::PrintError(sError); |
1752 | 0 | return false; |
1753 | 0 | } |
1754 | 0 | pConfig->FindStringEntry("host", sBindHost); |
1755 | 0 | pConfig->FindBoolEntry("ssl", bSSL, false); |
1756 | 0 | pConfig->FindBoolEntry("ipv4", b4, true); |
1757 | 0 | pConfig->FindBoolEntry("ipv6", b6, b6); |
1758 | 0 | pConfig->FindBoolEntry("allowirc", bIRC, true); |
1759 | 0 | pConfig->FindBoolEntry("allowweb", bWeb, true); |
1760 | 0 | pConfig->FindStringEntry("uriprefix", sURIPrefix); |
1761 | |
|
1762 | 0 | EAddrType eAddr; |
1763 | 0 | if (b4 && b6) { |
1764 | 0 | eAddr = ADDR_ALL; |
1765 | 0 | } else if (b4 && !b6) { |
1766 | 0 | eAddr = ADDR_IPV4ONLY; |
1767 | 0 | } else if (!b4 && b6) { |
1768 | 0 | eAddr = ADDR_IPV6ONLY; |
1769 | 0 | } else { |
1770 | 0 | sError = "No address family given"; |
1771 | 0 | CUtils::PrintError(sError); |
1772 | 0 | return false; |
1773 | 0 | } |
1774 | | |
1775 | 0 | CListener::EAcceptType eAccept; |
1776 | 0 | if (bIRC && bWeb) { |
1777 | 0 | eAccept = CListener::ACCEPT_ALL; |
1778 | 0 | } else if (bIRC && !bWeb) { |
1779 | 0 | eAccept = CListener::ACCEPT_IRC; |
1780 | 0 | } else if (!bIRC && bWeb) { |
1781 | 0 | eAccept = CListener::ACCEPT_HTTP; |
1782 | 0 | } else { |
1783 | 0 | sError = "Either Web or IRC or both should be selected"; |
1784 | 0 | CUtils::PrintError(sError); |
1785 | 0 | return false; |
1786 | 0 | } |
1787 | | |
1788 | 0 | return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept, |
1789 | 0 | sError); |
1790 | 0 | } |
1791 | | |
1792 | 0 | bool CZNC::AddListener(CListener* pListener) { |
1793 | 0 | if (!pListener->GetRealListener()) { |
1794 | | // Listener doesn't actually listen |
1795 | 0 | delete pListener; |
1796 | 0 | return false; |
1797 | 0 | } |
1798 | | |
1799 | | // We don't check if there is an identical listener already listening |
1800 | | // since one can't listen on e.g. the same port multiple times |
1801 | | |
1802 | 0 | m_vpListeners.push_back(pListener); |
1803 | 0 | return true; |
1804 | 0 | } |
1805 | | |
1806 | 0 | bool CZNC::DelListener(CListener* pListener) { |
1807 | 0 | auto it = std::find(m_vpListeners.begin(), m_vpListeners.end(), pListener); |
1808 | 0 | if (it != m_vpListeners.end()) { |
1809 | 0 | m_vpListeners.erase(it); |
1810 | 0 | delete pListener; |
1811 | 0 | return true; |
1812 | 0 | } |
1813 | | |
1814 | 0 | return false; |
1815 | 0 | } |
1816 | | |
1817 | 0 | CString CZNC::FormatBindError() { |
1818 | 0 | CString sError = (errno == 0 ? t_s(("unknown error, check the host name")) |
1819 | 0 | : CString(strerror(errno))); |
1820 | 0 | return t_f("Unable to bind: {1}")(sError); |
1821 | 0 | } |
1822 | | |
1823 | | static CZNC* s_pZNC = nullptr; |
1824 | | |
1825 | 0 | void CZNC::CreateInstance() { |
1826 | 0 | if (s_pZNC) abort(); |
1827 | | |
1828 | 0 | s_pZNC = new CZNC(); |
1829 | 0 | } |
1830 | | |
1831 | 0 | CZNC& CZNC::Get() { return *s_pZNC; } |
1832 | | |
1833 | 0 | void CZNC::DestroyInstance() { |
1834 | 0 | delete s_pZNC; |
1835 | 0 | s_pZNC = nullptr; |
1836 | 0 | } |
1837 | | |
1838 | | CZNC::TrafficStatsMap CZNC::GetTrafficStats(TrafficStatsPair& Users, |
1839 | | TrafficStatsPair& ZNC, |
1840 | 0 | TrafficStatsPair& Total) { |
1841 | 0 | TrafficStatsMap ret; |
1842 | 0 | unsigned long long uiUsers_in, uiUsers_out, uiZNC_in, uiZNC_out; |
1843 | 0 | const map<CString, CUser*>& msUsers = CZNC::Get().GetUserMap(); |
1844 | |
|
1845 | 0 | uiUsers_in = uiUsers_out = 0; |
1846 | 0 | uiZNC_in = BytesRead(); |
1847 | 0 | uiZNC_out = BytesWritten(); |
1848 | |
|
1849 | 0 | for (const auto& it : msUsers) { |
1850 | 0 | ret[it.first] = |
1851 | 0 | TrafficStatsPair(it.second->BytesRead(), it.second->BytesWritten()); |
1852 | 0 | uiUsers_in += it.second->BytesRead(); |
1853 | 0 | uiUsers_out += it.second->BytesWritten(); |
1854 | 0 | } |
1855 | |
|
1856 | 0 | for (Csock* pSock : m_Manager) { |
1857 | 0 | CUser* pUser = nullptr; |
1858 | 0 | if (pSock->GetSockName().StartsWith("IRC::")) { |
1859 | 0 | pUser = ((CIRCSock*)pSock)->GetNetwork()->GetUser(); |
1860 | 0 | } else if (pSock->GetSockName().StartsWith("USR::")) { |
1861 | 0 | pUser = ((CClient*)pSock)->GetUser(); |
1862 | 0 | } |
1863 | |
|
1864 | 0 | if (pUser) { |
1865 | 0 | ret[pUser->GetUsername()].first += pSock->GetBytesRead(); |
1866 | 0 | ret[pUser->GetUsername()].second += pSock->GetBytesWritten(); |
1867 | 0 | uiUsers_in += pSock->GetBytesRead(); |
1868 | 0 | uiUsers_out += pSock->GetBytesWritten(); |
1869 | 0 | } else { |
1870 | 0 | uiZNC_in += pSock->GetBytesRead(); |
1871 | 0 | uiZNC_out += pSock->GetBytesWritten(); |
1872 | 0 | } |
1873 | 0 | } |
1874 | |
|
1875 | 0 | Users = TrafficStatsPair(uiUsers_in, uiUsers_out); |
1876 | 0 | ZNC = TrafficStatsPair(uiZNC_in, uiZNC_out); |
1877 | 0 | Total = TrafficStatsPair(uiUsers_in + uiZNC_in, uiUsers_out + uiZNC_out); |
1878 | |
|
1879 | 0 | return ret; |
1880 | 0 | } |
1881 | | |
1882 | | CZNC::TrafficStatsMap CZNC::GetNetworkTrafficStats(const CString& sUsername, |
1883 | 0 | TrafficStatsPair& Total) { |
1884 | 0 | TrafficStatsMap Networks; |
1885 | |
|
1886 | 0 | CUser* pUser = FindUser(sUsername); |
1887 | 0 | if (pUser) { |
1888 | 0 | for (const CIRCNetwork* pNetwork : pUser->GetNetworks()) { |
1889 | 0 | Networks[pNetwork->GetName()].first = pNetwork->BytesRead(); |
1890 | 0 | Networks[pNetwork->GetName()].second = pNetwork->BytesWritten(); |
1891 | 0 | Total.first += pNetwork->BytesRead(); |
1892 | 0 | Total.second += pNetwork->BytesWritten(); |
1893 | 0 | } |
1894 | |
|
1895 | 0 | for (Csock* pSock : m_Manager) { |
1896 | 0 | CIRCNetwork* pNetwork = nullptr; |
1897 | 0 | if (pSock->GetSockName().StartsWith("IRC::")) { |
1898 | 0 | pNetwork = ((CIRCSock*)pSock)->GetNetwork(); |
1899 | 0 | } else if (pSock->GetSockName().StartsWith("USR::")) { |
1900 | 0 | pNetwork = ((CClient*)pSock)->GetNetwork(); |
1901 | 0 | } |
1902 | |
|
1903 | 0 | if (pNetwork && pNetwork->GetUser() == pUser) { |
1904 | 0 | Networks[pNetwork->GetName()].first = pSock->GetBytesRead(); |
1905 | 0 | Networks[pNetwork->GetName()].second = pSock->GetBytesWritten(); |
1906 | 0 | Total.first += pSock->GetBytesRead(); |
1907 | 0 | Total.second += pSock->GetBytesWritten(); |
1908 | 0 | } |
1909 | 0 | } |
1910 | 0 | } |
1911 | |
|
1912 | 0 | return Networks; |
1913 | 0 | } |
1914 | | |
1915 | 0 | void CZNC::AuthUser(std::shared_ptr<CAuthBase> AuthClass) { |
1916 | | // TODO unless the auth module calls it, CUser::IsHostAllowed() is not |
1917 | | // honoured |
1918 | 0 | bool bReturn = false; |
1919 | 0 | GLOBALMODULECALL(OnLoginAttempt(AuthClass), &bReturn); |
1920 | 0 | if (bReturn) return; |
1921 | | |
1922 | 0 | CUser* pUser = FindUser(AuthClass->GetUsername()); |
1923 | |
|
1924 | 0 | if (!pUser || !pUser->CheckPass(AuthClass->GetPassword())) { |
1925 | 0 | AuthClass->RefuseLogin("Invalid Password"); |
1926 | 0 | return; |
1927 | 0 | } |
1928 | | |
1929 | 0 | CString sHost = AuthClass->GetRemoteIP(); |
1930 | |
|
1931 | 0 | if (!pUser->IsHostAllowed(sHost)) { |
1932 | 0 | AuthClass->RefuseLogin("Your host [" + sHost + "] is not allowed"); |
1933 | 0 | return; |
1934 | 0 | } |
1935 | | |
1936 | 0 | AuthClass->AcceptLogin(*pUser); |
1937 | 0 | } |
1938 | | |
1939 | | class CConnectQueueTimer : public CCron { |
1940 | | public: |
1941 | 0 | CConnectQueueTimer(int iSecs) : CCron() { |
1942 | 0 | SetName("Connect users"); |
1943 | 0 | Start(iSecs); |
1944 | | // Don't wait iSecs seconds for first timer run |
1945 | 0 | m_bRunOnNextCall = true; |
1946 | 0 | } |
1947 | 0 | ~CConnectQueueTimer() override { |
1948 | | // This is only needed when ZNC shuts down: |
1949 | | // CZNC::~CZNC() sets its CConnectQueueTimer pointer to nullptr and |
1950 | | // calls the manager's Cleanup() which destroys all sockets and |
1951 | | // timers. If something calls CZNC::EnableConnectQueue() here |
1952 | | // (e.g. because a CIRCSock is destroyed), the socket manager |
1953 | | // deletes that timer almost immediately, but CZNC now got a |
1954 | | // dangling pointer to this timer which can crash later on. |
1955 | | // |
1956 | | // Unlikely but possible ;) |
1957 | 0 | CZNC::Get().LeakConnectQueueTimer(this); |
1958 | 0 | } |
1959 | | |
1960 | | protected: |
1961 | 0 | void RunJob() override { |
1962 | 0 | list<CIRCNetwork*> ConnectionQueue; |
1963 | 0 | list<CIRCNetwork*>& RealConnectionQueue = |
1964 | 0 | CZNC::Get().GetConnectionQueue(); |
1965 | | |
1966 | | // Problem: If a network can't connect right now because e.g. it |
1967 | | // is throttled, it will re-insert itself into the connection |
1968 | | // queue. However, we must only give each network a single |
1969 | | // chance during this timer run. |
1970 | | // |
1971 | | // Solution: We move the connection queue to our local list at |
1972 | | // the beginning and work from that. |
1973 | 0 | ConnectionQueue.swap(RealConnectionQueue); |
1974 | |
|
1975 | 0 | while (!ConnectionQueue.empty()) { |
1976 | 0 | CIRCNetwork* pNetwork = ConnectionQueue.front(); |
1977 | 0 | ConnectionQueue.pop_front(); |
1978 | |
|
1979 | 0 | if (pNetwork->Connect()) { |
1980 | 0 | break; |
1981 | 0 | } |
1982 | 0 | } |
1983 | | |
1984 | | /* Now re-insert anything that is left in our local list into |
1985 | | * the real connection queue. |
1986 | | */ |
1987 | 0 | RealConnectionQueue.splice(RealConnectionQueue.begin(), |
1988 | 0 | ConnectionQueue); |
1989 | |
|
1990 | 0 | if (RealConnectionQueue.empty()) { |
1991 | 0 | DEBUG("ConnectQueueTimer done"); |
1992 | 0 | CZNC::Get().DisableConnectQueue(); |
1993 | 0 | } |
1994 | 0 | } |
1995 | | }; |
1996 | | |
1997 | 0 | void CZNC::SetConnectDelay(unsigned int i) { |
1998 | 0 | if (i < 1) { |
1999 | | // Don't hammer server with our failed connects |
2000 | 0 | i = 1; |
2001 | 0 | } |
2002 | 0 | if (m_uiConnectDelay != i && m_pConnectQueueTimer != nullptr) { |
2003 | 0 | m_pConnectQueueTimer->Start(i); |
2004 | 0 | } |
2005 | 0 | m_uiConnectDelay = i; |
2006 | 0 | } |
2007 | | |
2008 | 0 | VCString CZNC::GetAvailableSSLProtocols() { |
2009 | | // NOTE: keep in sync with SetSSLProtocols() |
2010 | 0 | return {"SSLv2", "SSLv3", "TLSv1", "TLSV1.1", "TLSv1.2"}; |
2011 | 0 | } |
2012 | | |
2013 | 0 | bool CZNC::SetSSLProtocols(const CString& sProtocols) { |
2014 | 0 | VCString vsProtocols; |
2015 | 0 | sProtocols.Split(" ", vsProtocols, false, "", "", true, true); |
2016 | |
|
2017 | 0 | unsigned int uDisabledProtocols = Csock::EDP_SSL; |
2018 | 0 | for (CString& sProtocol : vsProtocols) { |
2019 | 0 | unsigned int uFlag = 0; |
2020 | 0 | bool bEnable = sProtocol.TrimPrefix("+"); |
2021 | 0 | bool bDisable = sProtocol.TrimPrefix("-"); |
2022 | | |
2023 | | // NOTE: keep in sync with GetAvailableSSLProtocols() |
2024 | 0 | if (sProtocol.Equals("All")) { |
2025 | 0 | uFlag = ~0; |
2026 | 0 | } else if (sProtocol.Equals("SSLv2")) { |
2027 | 0 | uFlag = Csock::EDP_SSLv2; |
2028 | 0 | } else if (sProtocol.Equals("SSLv3")) { |
2029 | 0 | uFlag = Csock::EDP_SSLv3; |
2030 | 0 | } else if (sProtocol.Equals("TLSv1")) { |
2031 | 0 | uFlag = Csock::EDP_TLSv1; |
2032 | 0 | } else if (sProtocol.Equals("TLSv1.1")) { |
2033 | 0 | uFlag = Csock::EDP_TLSv1_1; |
2034 | 0 | } else if (sProtocol.Equals("TLSv1.2")) { |
2035 | 0 | uFlag = Csock::EDP_TLSv1_2; |
2036 | 0 | } else { |
2037 | 0 | return false; |
2038 | 0 | } |
2039 | | |
2040 | 0 | if (bEnable) { |
2041 | 0 | uDisabledProtocols &= ~uFlag; |
2042 | 0 | } else if (bDisable) { |
2043 | 0 | uDisabledProtocols |= uFlag; |
2044 | 0 | } else { |
2045 | 0 | uDisabledProtocols = ~uFlag; |
2046 | 0 | } |
2047 | 0 | } |
2048 | | |
2049 | 0 | m_sSSLProtocols = sProtocols; |
2050 | 0 | m_uDisabledSSLProtocols = uDisabledProtocols; |
2051 | 0 | return true; |
2052 | 0 | } |
2053 | | |
2054 | 0 | void CZNC::EnableConnectQueue() { |
2055 | 0 | if (!m_pConnectQueueTimer && !m_uiConnectPaused && |
2056 | 0 | !m_lpConnectQueue.empty()) { |
2057 | 0 | m_pConnectQueueTimer = new CConnectQueueTimer(m_uiConnectDelay); |
2058 | 0 | GetManager().AddCron(m_pConnectQueueTimer); |
2059 | 0 | } |
2060 | 0 | } |
2061 | | |
2062 | 0 | void CZNC::DisableConnectQueue() { |
2063 | 0 | if (m_pConnectQueueTimer) { |
2064 | | // This will kill the cron |
2065 | 0 | m_pConnectQueueTimer->Stop(); |
2066 | 0 | m_pConnectQueueTimer = nullptr; |
2067 | 0 | } |
2068 | 0 | } |
2069 | | |
2070 | 0 | void CZNC::PauseConnectQueue() { |
2071 | 0 | DEBUG("Connection queue paused"); |
2072 | 0 | m_uiConnectPaused++; |
2073 | |
|
2074 | 0 | if (m_pConnectQueueTimer) { |
2075 | 0 | m_pConnectQueueTimer->Pause(); |
2076 | 0 | } |
2077 | 0 | } |
2078 | | |
2079 | 0 | void CZNC::ResumeConnectQueue() { |
2080 | 0 | DEBUG("Connection queue resumed"); |
2081 | 0 | m_uiConnectPaused--; |
2082 | |
|
2083 | 0 | EnableConnectQueue(); |
2084 | 0 | if (m_pConnectQueueTimer) { |
2085 | 0 | m_pConnectQueueTimer->UnPause(); |
2086 | 0 | } |
2087 | 0 | } |
2088 | | |
2089 | 0 | void CZNC::ForceEncoding() { |
2090 | 0 | m_uiForceEncoding++; |
2091 | | #ifdef HAVE_ICU |
2092 | | for (Csock* pSock : GetManager()) { |
2093 | | pSock->SetEncoding(FixupEncoding(pSock->GetEncoding())); |
2094 | | } |
2095 | | #endif |
2096 | 0 | } |
2097 | 0 | void CZNC::UnforceEncoding() { m_uiForceEncoding--; } |
2098 | 0 | bool CZNC::IsForcingEncoding() const { return m_uiForceEncoding; } |
2099 | 0 | CString CZNC::FixupEncoding(const CString& sEncoding) const { |
2100 | 0 | if (!m_uiForceEncoding) { |
2101 | 0 | return sEncoding; |
2102 | 0 | } |
2103 | 0 | if (sEncoding.empty()) { |
2104 | 0 | return "UTF-8"; |
2105 | 0 | } |
2106 | 0 | const char* sRealEncoding = sEncoding.c_str(); |
2107 | 0 | if (sEncoding[0] == '*' || sEncoding[0] == '^') { |
2108 | 0 | sRealEncoding++; |
2109 | 0 | } |
2110 | 0 | if (!*sRealEncoding) { |
2111 | 0 | return "UTF-8"; |
2112 | 0 | } |
2113 | | #ifdef HAVE_ICU |
2114 | | UErrorCode e = U_ZERO_ERROR; |
2115 | | UConverter* cnv = ucnv_open(sRealEncoding, &e); |
2116 | | if (cnv) { |
2117 | | ucnv_close(cnv); |
2118 | | } |
2119 | | if (U_FAILURE(e)) { |
2120 | | return "UTF-8"; |
2121 | | } |
2122 | | #endif |
2123 | 0 | return sEncoding; |
2124 | 0 | } |
2125 | | |
2126 | 0 | void CZNC::AddNetworkToQueue(CIRCNetwork* pNetwork) { |
2127 | | // Make sure we are not already in the queue |
2128 | 0 | if (std::find(m_lpConnectQueue.begin(), m_lpConnectQueue.end(), pNetwork) != |
2129 | 0 | m_lpConnectQueue.end()) { |
2130 | 0 | return; |
2131 | 0 | } |
2132 | | |
2133 | 0 | m_lpConnectQueue.push_back(pNetwork); |
2134 | 0 | EnableConnectQueue(); |
2135 | 0 | } |
2136 | | |
2137 | 0 | void CZNC::LeakConnectQueueTimer(CConnectQueueTimer* pTimer) { |
2138 | 0 | if (m_pConnectQueueTimer == pTimer) m_pConnectQueueTimer = nullptr; |
2139 | 0 | } |
2140 | | |
2141 | 0 | bool CZNC::WaitForChildLock() { return m_pLockFile && m_pLockFile->ExLock(); } |
2142 | | |
2143 | 0 | void CZNC::DisableConfigTimer() { |
2144 | 0 | if (m_pConfigTimer) { |
2145 | 0 | m_pConfigTimer->Stop(); |
2146 | 0 | m_pConfigTimer = nullptr; |
2147 | 0 | } |
2148 | 0 | } |