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