Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2004-2023 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/Client.h> |
18 | | #include <znc/Chan.h> |
19 | | #include <znc/IRCSock.h> |
20 | | #include <znc/User.h> |
21 | | #include <znc/IRCNetwork.h> |
22 | | #include <znc/Query.h> |
23 | | |
24 | | using std::set; |
25 | | using std::map; |
26 | | using std::vector; |
27 | | |
28 | | #define CALLMOD(MOD, CLIENT, USER, NETWORK, FUNC) \ |
29 | 0 | { \ |
30 | 0 | CModule* pModule = nullptr; \ |
31 | 0 | if (NETWORK && (pModule = (NETWORK)->GetModules().FindModule(MOD))) { \ |
32 | 0 | try { \ |
33 | 0 | CClient* pOldClient = pModule->GetClient(); \ |
34 | 0 | pModule->SetClient(CLIENT); \ |
35 | 0 | pModule->FUNC; \ |
36 | 0 | pModule->SetClient(pOldClient); \ |
37 | 0 | } catch (const CModule::EModException& e) { \ |
38 | 0 | if (e == CModule::UNLOAD) { \ |
39 | 0 | (NETWORK)->GetModules().UnloadModule(MOD); \ |
40 | 0 | } \ |
41 | 0 | } \ |
42 | 0 | } else if ((pModule = (USER)->GetModules().FindModule(MOD))) { \ |
43 | 0 | try { \ |
44 | 0 | CClient* pOldClient = pModule->GetClient(); \ |
45 | 0 | CIRCNetwork* pOldNetwork = pModule->GetNetwork(); \ |
46 | 0 | pModule->SetClient(CLIENT); \ |
47 | 0 | pModule->SetNetwork(NETWORK); \ |
48 | 0 | pModule->FUNC; \ |
49 | 0 | pModule->SetClient(pOldClient); \ |
50 | 0 | pModule->SetNetwork(pOldNetwork); \ |
51 | 0 | } catch (const CModule::EModException& e) { \ |
52 | 0 | if (e == CModule::UNLOAD) { \ |
53 | 0 | (USER)->GetModules().UnloadModule(MOD); \ |
54 | 0 | } \ |
55 | 0 | } \ |
56 | 0 | } else if ((pModule = CZNC::Get().GetModules().FindModule(MOD))) { \ |
57 | 0 | try { \ |
58 | 0 | CClient* pOldClient = pModule->GetClient(); \ |
59 | 0 | CIRCNetwork* pOldNetwork = pModule->GetNetwork(); \ |
60 | 0 | CUser* pOldUser = pModule->GetUser(); \ |
61 | 0 | pModule->SetClient(CLIENT); \ |
62 | 0 | pModule->SetNetwork(NETWORK); \ |
63 | 0 | pModule->SetUser(USER); \ |
64 | 0 | pModule->FUNC; \ |
65 | 0 | pModule->SetClient(pOldClient); \ |
66 | 0 | pModule->SetNetwork(pOldNetwork); \ |
67 | 0 | pModule->SetUser(pOldUser); \ |
68 | 0 | } catch (const CModule::EModException& e) { \ |
69 | 0 | if (e == CModule::UNLOAD) { \ |
70 | 0 | CZNC::Get().GetModules().UnloadModule(MOD); \ |
71 | 0 | } \ |
72 | 0 | } \ |
73 | 0 | } else { \ |
74 | 0 | PutStatus(t_f("No such module {1}")(MOD)); \ |
75 | 0 | } \ |
76 | 0 | } |
77 | | |
78 | 0 | CClient::~CClient() { |
79 | 0 | if (m_spAuth) { |
80 | 0 | CClientAuth* pAuth = (CClientAuth*)&(*m_spAuth); |
81 | 0 | pAuth->Invalidate(); |
82 | 0 | } |
83 | 0 | if (m_pUser != nullptr) { |
84 | 0 | m_pUser->AddBytesRead(GetBytesRead()); |
85 | 0 | m_pUser->AddBytesWritten(GetBytesWritten()); |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | 0 | void CClient::SendRequiredPasswordNotice() { |
90 | 0 | PutClient(":irc.znc.in 464 " + GetNick() + " :Password required"); |
91 | 0 | PutClient( |
92 | 0 | ":irc.znc.in NOTICE " + GetNick() + " :*** " |
93 | 0 | "You need to send your password. " |
94 | 0 | "Configure your client to send a server password."); |
95 | 0 | PutClient( |
96 | 0 | ":irc.znc.in NOTICE " + GetNick() + " :*** " |
97 | 0 | "To connect now, you can use /quote PASS <username>:<password>, " |
98 | 0 | "or /quote PASS <username>/<network>:<password> to connect to a " |
99 | 0 | "specific network."); |
100 | 0 | } |
101 | | |
102 | 0 | void CClient::ReadLine(const CString& sData) { |
103 | 0 | CLanguageScope user_lang(GetUser() ? GetUser()->GetLanguage() : ""); |
104 | 0 | CString sLine = sData; |
105 | |
|
106 | 0 | sLine.Replace("\n", ""); |
107 | 0 | sLine.Replace("\r", ""); |
108 | |
|
109 | 0 | DEBUG("(" << GetFullName() << ") CLI -> ZNC [" |
110 | 0 | << CDebug::Filter(sLine) << "]"); |
111 | |
|
112 | 0 | bool bReturn = false; |
113 | 0 | if (IsAttached()) { |
114 | 0 | NETWORKMODULECALL(OnUserRaw(sLine), m_pUser, m_pNetwork, this, |
115 | 0 | &bReturn); |
116 | 0 | } else { |
117 | 0 | GLOBALMODULECALL(OnUnknownUserRaw(this, sLine), &bReturn); |
118 | 0 | } |
119 | 0 | if (bReturn) return; |
120 | | |
121 | 0 | CMessage Message(sLine); |
122 | 0 | Message.SetClient(this); |
123 | |
|
124 | 0 | if (IsAttached()) { |
125 | 0 | NETWORKMODULECALL(OnUserRawMessage(Message), m_pUser, m_pNetwork, this, |
126 | 0 | &bReturn); |
127 | 0 | } else { |
128 | 0 | GLOBALMODULECALL(OnUnknownUserRawMessage(Message), &bReturn); |
129 | 0 | } |
130 | 0 | if (bReturn) return; |
131 | | |
132 | 0 | CString sCommand = Message.GetCommand(); |
133 | |
|
134 | 0 | if (!IsAttached()) { |
135 | | // The following commands happen before authentication with ZNC |
136 | 0 | if (sCommand.Equals("PASS")) { |
137 | 0 | m_bGotPass = true; |
138 | |
|
139 | 0 | CString sAuthLine = Message.GetParam(0); |
140 | 0 | ParsePass(sAuthLine); |
141 | |
|
142 | 0 | AuthUser(); |
143 | | // Don't forward this msg. ZNC has already registered us. |
144 | 0 | return; |
145 | 0 | } else if (sCommand.Equals("NICK")) { |
146 | 0 | CString sNick = Message.GetParam(0); |
147 | |
|
148 | 0 | m_sNick = sNick; |
149 | 0 | m_bGotNick = true; |
150 | |
|
151 | 0 | AuthUser(); |
152 | | // Don't forward this msg. ZNC will handle nick changes until auth |
153 | | // is complete |
154 | 0 | return; |
155 | 0 | } else if (sCommand.Equals("USER")) { |
156 | 0 | CString sAuthLine = Message.GetParam(0); |
157 | |
|
158 | 0 | if (m_sUser.empty() && !sAuthLine.empty()) { |
159 | 0 | ParseUser(sAuthLine); |
160 | 0 | } |
161 | |
|
162 | 0 | m_bGotUser = true; |
163 | 0 | if (m_bGotPass) { |
164 | 0 | AuthUser(); |
165 | 0 | } else if (!m_bInCap) { |
166 | 0 | SendRequiredPasswordNotice(); |
167 | 0 | } |
168 | | |
169 | | // Don't forward this msg. ZNC has already registered us. |
170 | 0 | return; |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | 0 | if (Message.GetType() == CMessage::Type::Capability) { |
175 | 0 | HandleCap(Message); |
176 | | |
177 | | // Don't let the client talk to the server directly about CAP, |
178 | | // we don't want anything enabled that ZNC does not support. |
179 | 0 | return; |
180 | 0 | } |
181 | | |
182 | 0 | if (!m_pUser) { |
183 | | // Only CAP, NICK, USER and PASS are allowed before login |
184 | 0 | return; |
185 | 0 | } |
186 | | |
187 | 0 | switch (Message.GetType()) { |
188 | 0 | case CMessage::Type::Action: |
189 | 0 | bReturn = OnActionMessage(Message); |
190 | 0 | break; |
191 | 0 | case CMessage::Type::CTCP: |
192 | 0 | bReturn = OnCTCPMessage(Message); |
193 | 0 | break; |
194 | 0 | case CMessage::Type::Join: |
195 | 0 | bReturn = OnJoinMessage(Message); |
196 | 0 | break; |
197 | 0 | case CMessage::Type::Mode: |
198 | 0 | bReturn = OnModeMessage(Message); |
199 | 0 | break; |
200 | 0 | case CMessage::Type::Notice: |
201 | 0 | bReturn = OnNoticeMessage(Message); |
202 | 0 | break; |
203 | 0 | case CMessage::Type::Part: |
204 | 0 | bReturn = OnPartMessage(Message); |
205 | 0 | break; |
206 | 0 | case CMessage::Type::Ping: |
207 | 0 | bReturn = OnPingMessage(Message); |
208 | 0 | break; |
209 | 0 | case CMessage::Type::Pong: |
210 | 0 | bReturn = OnPongMessage(Message); |
211 | 0 | break; |
212 | 0 | case CMessage::Type::Quit: |
213 | 0 | bReturn = OnQuitMessage(Message); |
214 | 0 | break; |
215 | 0 | case CMessage::Type::Text: |
216 | 0 | bReturn = OnTextMessage(Message); |
217 | 0 | break; |
218 | 0 | case CMessage::Type::Topic: |
219 | 0 | bReturn = OnTopicMessage(Message); |
220 | 0 | break; |
221 | 0 | default: |
222 | 0 | bReturn = OnOtherMessage(Message); |
223 | 0 | break; |
224 | 0 | } |
225 | | |
226 | 0 | if (bReturn) return; |
227 | | |
228 | 0 | PutIRC(Message.ToString(CMessage::ExcludePrefix | CMessage::ExcludeTags)); |
229 | 0 | } |
230 | | |
231 | 0 | void CClient::SetNick(const CString& s) { m_sNick = s; } |
232 | | |
233 | | void CClient::SetNetwork(CIRCNetwork* pNetwork, bool bDisconnect, |
234 | 0 | bool bReconnect) { |
235 | 0 | if (m_pNetwork) { |
236 | 0 | m_pNetwork->ClientDisconnected(this); |
237 | |
|
238 | 0 | if (bDisconnect) { |
239 | 0 | ClearServerDependentCaps(); |
240 | | // Tell the client they are no longer in these channels. |
241 | 0 | const vector<CChan*>& vChans = m_pNetwork->GetChans(); |
242 | 0 | for (const CChan* pChan : vChans) { |
243 | 0 | if (!(pChan->IsDetached())) { |
244 | 0 | PutClient(":" + m_pNetwork->GetIRCNick().GetNickMask() + |
245 | 0 | " PART " + pChan->GetName()); |
246 | 0 | } |
247 | 0 | } |
248 | 0 | } |
249 | 0 | } else if (m_pUser) { |
250 | 0 | m_pUser->UserDisconnected(this); |
251 | 0 | } |
252 | |
|
253 | 0 | m_pNetwork = pNetwork; |
254 | |
|
255 | 0 | if (bReconnect) { |
256 | 0 | if (m_pNetwork) { |
257 | 0 | m_pNetwork->ClientConnected(this); |
258 | 0 | } else if (m_pUser) { |
259 | 0 | m_pUser->UserConnected(this); |
260 | 0 | } |
261 | 0 | } |
262 | 0 | } |
263 | | |
264 | 0 | const vector<CClient*>& CClient::GetClients() const { |
265 | 0 | if (m_pNetwork) { |
266 | 0 | return m_pNetwork->GetClients(); |
267 | 0 | } |
268 | | |
269 | 0 | return m_pUser->GetUserClients(); |
270 | 0 | } |
271 | | |
272 | 0 | const CIRCSock* CClient::GetIRCSock() const { |
273 | 0 | if (m_pNetwork) { |
274 | 0 | return m_pNetwork->GetIRCSock(); |
275 | 0 | } |
276 | | |
277 | 0 | return nullptr; |
278 | 0 | } |
279 | | |
280 | 0 | CIRCSock* CClient::GetIRCSock() { |
281 | 0 | if (m_pNetwork) { |
282 | 0 | return m_pNetwork->GetIRCSock(); |
283 | 0 | } |
284 | | |
285 | 0 | return nullptr; |
286 | 0 | } |
287 | | |
288 | 0 | void CClient::StatusCTCP(const CString& sLine) { |
289 | 0 | CString sCommand = sLine.Token(0); |
290 | |
|
291 | 0 | if (sCommand.Equals("PING")) { |
292 | 0 | PutStatusNotice("\001PING " + sLine.Token(1, true) + "\001"); |
293 | 0 | } else if (sCommand.Equals("VERSION")) { |
294 | 0 | PutStatusNotice("\001VERSION " + CZNC::GetTag() + "\001"); |
295 | 0 | } |
296 | 0 | } |
297 | | |
298 | 0 | bool CClient::SendMotd() { |
299 | 0 | const VCString& vsMotd = CZNC::Get().GetMotd(); |
300 | |
|
301 | 0 | if (!vsMotd.size()) { |
302 | 0 | return false; |
303 | 0 | } |
304 | | |
305 | 0 | for (const CString& sLine : vsMotd) { |
306 | 0 | if (m_pNetwork) { |
307 | 0 | PutStatusNotice(m_pNetwork->ExpandString(sLine)); |
308 | 0 | } else { |
309 | 0 | PutStatusNotice(m_pUser->ExpandString(sLine)); |
310 | 0 | } |
311 | 0 | } |
312 | |
|
313 | 0 | return true; |
314 | 0 | } |
315 | | |
316 | 0 | void CClient::AuthUser() { |
317 | 0 | if (!m_bGotNick || !m_bGotUser || !m_bGotPass || m_bInCap || IsAttached()) |
318 | 0 | return; |
319 | | |
320 | 0 | m_spAuth = std::make_shared<CClientAuth>(this, m_sUser, m_sPass); |
321 | |
|
322 | 0 | CZNC::Get().AuthUser(m_spAuth); |
323 | 0 | } |
324 | | |
325 | | CClientAuth::CClientAuth(CClient* pClient, const CString& sUsername, |
326 | | const CString& sPassword) |
327 | 0 | : CAuthBase(sUsername, sPassword, pClient), m_pClient(pClient) {} |
328 | | |
329 | 0 | void CClientAuth::RefusedLogin(const CString& sReason) { |
330 | 0 | if (m_pClient) { |
331 | 0 | m_pClient->RefuseLogin(sReason); |
332 | 0 | } |
333 | 0 | } |
334 | | |
335 | 0 | CString CAuthBase::GetRemoteIP() const { |
336 | 0 | if (m_pSock) return m_pSock->GetRemoteIP(); |
337 | 0 | return ""; |
338 | 0 | } |
339 | | |
340 | 0 | void CAuthBase::Invalidate() { m_pSock = nullptr; } |
341 | | |
342 | 0 | void CAuthBase::AcceptLogin(CUser& User) { |
343 | 0 | if (m_pSock) { |
344 | 0 | AcceptedLogin(User); |
345 | 0 | Invalidate(); |
346 | 0 | } |
347 | 0 | } |
348 | | |
349 | 0 | void CAuthBase::RefuseLogin(const CString& sReason) { |
350 | 0 | if (!m_pSock) return; |
351 | | |
352 | 0 | CUser* pUser = CZNC::Get().FindUser(GetUsername()); |
353 | | |
354 | | // If the username is valid, notify that user that someone tried to |
355 | | // login. Use sReason because there are other reasons than "wrong |
356 | | // password" for a login to be rejected (e.g. fail2ban). |
357 | 0 | if (pUser) { |
358 | 0 | pUser->PutStatusNotice(t_f( |
359 | 0 | "A client from {1} attempted to login as you, but was rejected: " |
360 | 0 | "{2}")(GetRemoteIP(), sReason)); |
361 | 0 | } |
362 | |
|
363 | 0 | GLOBALMODULECALL(OnFailedLogin(GetUsername(), GetRemoteIP()), NOTHING); |
364 | 0 | RefusedLogin(sReason); |
365 | 0 | Invalidate(); |
366 | 0 | } |
367 | | |
368 | 0 | void CClient::RefuseLogin(const CString& sReason) { |
369 | 0 | PutStatus("Bad username and/or password."); |
370 | 0 | PutClient(":irc.znc.in 464 " + GetNick() + " :" + sReason); |
371 | 0 | Close(Csock::CLT_AFTERWRITE); |
372 | 0 | } |
373 | | |
374 | 0 | void CClientAuth::AcceptedLogin(CUser& User) { |
375 | 0 | if (m_pClient) { |
376 | 0 | m_pClient->AcceptLogin(User); |
377 | 0 | } |
378 | 0 | } |
379 | | |
380 | 0 | void CClient::AcceptLogin(CUser& User) { |
381 | 0 | m_sPass = ""; |
382 | 0 | m_pUser = &User; |
383 | | |
384 | | // Set our proper timeout and set back our proper timeout mode |
385 | | // (constructor set a different timeout and mode) |
386 | 0 | SetTimeout(User.GetNoTrafficTimeout(), TMO_READ); |
387 | |
|
388 | 0 | SetSockName("USR::" + m_pUser->GetUsername()); |
389 | 0 | SetEncoding(m_pUser->GetClientEncoding()); |
390 | |
|
391 | 0 | if (!m_sNetwork.empty()) { |
392 | 0 | m_pNetwork = m_pUser->FindNetwork(m_sNetwork); |
393 | 0 | if (!m_pNetwork) { |
394 | 0 | PutStatus(t_f("Network {1} doesn't exist.")(m_sNetwork)); |
395 | 0 | } |
396 | 0 | } else if (!m_pUser->GetNetworks().empty()) { |
397 | | // If a user didn't supply a network, and they have a network called |
398 | | // "default" then automatically use this network. |
399 | 0 | m_pNetwork = m_pUser->FindNetwork("default"); |
400 | | // If no "default" network, try "user" network. It's for compatibility |
401 | | // with early network stuff in ZNC, which converted old configs to |
402 | | // "user" network. |
403 | 0 | if (!m_pNetwork) m_pNetwork = m_pUser->FindNetwork("user"); |
404 | | // Otherwise, just try any network of the user. |
405 | 0 | if (!m_pNetwork) m_pNetwork = *m_pUser->GetNetworks().begin(); |
406 | 0 | if (m_pNetwork && m_pUser->GetNetworks().size() > 1) { |
407 | 0 | PutStatusNotice( |
408 | 0 | t_s("You have several networks configured, but no network was " |
409 | 0 | "specified for the connection.")); |
410 | 0 | PutStatusNotice( |
411 | 0 | t_f("Selecting network {1}. To see list of all configured " |
412 | 0 | "networks, use /znc ListNetworks")(m_pNetwork->GetName())); |
413 | 0 | PutStatusNotice(t_f( |
414 | 0 | "If you want to choose another network, use /znc JumpNetwork " |
415 | 0 | "<network>, or connect to ZNC with username {1}/<network> " |
416 | 0 | "(instead of just {1})")(m_pUser->GetUsername())); |
417 | 0 | } |
418 | 0 | } else { |
419 | 0 | PutStatusNotice( |
420 | 0 | t_s("You have no networks configured. Use /znc AddNetwork " |
421 | 0 | "<network> to add one.")); |
422 | 0 | } |
423 | |
|
424 | 0 | SetNetwork(m_pNetwork, false); |
425 | |
|
426 | 0 | SendMotd(); |
427 | |
|
428 | 0 | NETWORKMODULECALL(OnClientLogin(), m_pUser, m_pNetwork, this, NOTHING); |
429 | 0 | } |
430 | | |
431 | 0 | void CClient::Timeout() { PutClient("ERROR :" + t_s("Closing link: Timeout")); } |
432 | | |
433 | 0 | void CClient::Connected() { DEBUG(GetSockName() << " == Connected();"); } |
434 | | |
435 | 0 | void CClient::ConnectionRefused() { |
436 | 0 | DEBUG(GetSockName() << " == ConnectionRefused()"); |
437 | 0 | } |
438 | | |
439 | 0 | void CClient::Disconnected() { |
440 | 0 | DEBUG(GetSockName() << " == Disconnected()"); |
441 | 0 | CIRCNetwork* pNetwork = m_pNetwork; |
442 | 0 | SetNetwork(nullptr, false, false); |
443 | |
|
444 | 0 | if (m_pUser) { |
445 | 0 | NETWORKMODULECALL(OnClientDisconnect(), m_pUser, pNetwork, this, |
446 | 0 | NOTHING); |
447 | 0 | } |
448 | 0 | } |
449 | | |
450 | 0 | void CClient::ReachedMaxBuffer() { |
451 | 0 | DEBUG(GetSockName() << " == ReachedMaxBuffer()"); |
452 | 0 | if (IsAttached()) { |
453 | 0 | PutClient("ERROR :" + t_s("Closing link: Too long raw line")); |
454 | 0 | } |
455 | 0 | Close(); |
456 | 0 | } |
457 | | |
458 | 0 | void CClient::BouncedOff() { |
459 | 0 | PutStatusNotice( |
460 | 0 | t_s("You are being disconnected because another user just " |
461 | 0 | "authenticated as you.")); |
462 | 0 | Close(Csock::CLT_AFTERWRITE); |
463 | 0 | } |
464 | | |
465 | 0 | void CClient::PutIRC(const CString& sLine) { |
466 | 0 | if (m_pNetwork) { |
467 | 0 | m_pNetwork->PutIRC(sLine); |
468 | 0 | } |
469 | 0 | } |
470 | | |
471 | 0 | CString CClient::GetFullName() const { |
472 | 0 | if (!m_pUser) return GetRemoteIP(); |
473 | 0 | CString sFullName = m_pUser->GetUsername(); |
474 | 0 | if (!m_sIdentifier.empty()) sFullName += "@" + m_sIdentifier; |
475 | 0 | if (m_pNetwork) sFullName += "/" + m_pNetwork->GetName(); |
476 | 0 | return sFullName; |
477 | 0 | } |
478 | | |
479 | 0 | void CClient::PutClient(const CString& sLine) { |
480 | 0 | PutClient(CMessage(sLine)); |
481 | 0 | } |
482 | | |
483 | 0 | bool CClient::PutClient(const CMessage& Message) { |
484 | 0 | if (!m_bAwayNotify && Message.GetType() == CMessage::Type::Away) { |
485 | 0 | return false; |
486 | 0 | } else if (!m_bAccountNotify && |
487 | 0 | Message.GetType() == CMessage::Type::Account) { |
488 | 0 | return false; |
489 | 0 | } |
490 | | |
491 | 0 | CMessage Msg(Message); |
492 | |
|
493 | 0 | const CIRCSock* pIRCSock = GetIRCSock(); |
494 | 0 | if (pIRCSock) { |
495 | 0 | if (Msg.GetType() == CMessage::Type::Numeric) { |
496 | 0 | unsigned int uCode = Msg.As<CNumericMessage>().GetCode(); |
497 | |
|
498 | 0 | if (uCode == 352) { // RPL_WHOREPLY |
499 | 0 | if (!m_bNamesx && pIRCSock->HasNamesx()) { |
500 | | // The server has NAMESX, but the client doesn't, so we need |
501 | | // to remove extra prefixes |
502 | 0 | CString sNick = Msg.GetParam(6); |
503 | 0 | if (sNick.size() > 1 && pIRCSock->IsPermChar(sNick[1])) { |
504 | 0 | CString sNewNick = sNick; |
505 | 0 | size_t pos = |
506 | 0 | sNick.find_first_not_of(pIRCSock->GetPerms()); |
507 | 0 | if (pos >= 2 && pos != CString::npos) { |
508 | 0 | sNewNick = sNick[0] + sNick.substr(pos); |
509 | 0 | } |
510 | 0 | Msg.SetParam(6, sNewNick); |
511 | 0 | } |
512 | 0 | } |
513 | 0 | } else if (uCode == 353) { // RPL_NAMES |
514 | 0 | if ((!m_bNamesx && pIRCSock->HasNamesx()) || |
515 | 0 | (!m_bUHNames && pIRCSock->HasUHNames())) { |
516 | | // The server has either UHNAMES or NAMESX, but the client |
517 | | // is missing either or both |
518 | 0 | CString sNicks = Msg.GetParam(3); |
519 | 0 | VCString vsNicks; |
520 | 0 | sNicks.Split(" ", vsNicks, false); |
521 | |
|
522 | 0 | for (CString& sNick : vsNicks) { |
523 | 0 | if (sNick.empty()) break; |
524 | | |
525 | 0 | if (!m_bNamesx && pIRCSock->HasNamesx() && |
526 | 0 | pIRCSock->IsPermChar(sNick[0])) { |
527 | | // The server has NAMESX, but the client doesn't, so |
528 | | // we just use the first perm char |
529 | 0 | size_t pos = |
530 | 0 | sNick.find_first_not_of(pIRCSock->GetPerms()); |
531 | 0 | if (pos >= 2 && pos != CString::npos) { |
532 | 0 | sNick = sNick[0] + sNick.substr(pos); |
533 | 0 | } |
534 | 0 | } |
535 | |
|
536 | 0 | if (!m_bUHNames && pIRCSock->HasUHNames()) { |
537 | | // The server has UHNAMES, but the client doesn't, |
538 | | // so we strip away ident and host |
539 | 0 | sNick = sNick.Token(0, false, "!"); |
540 | 0 | } |
541 | 0 | } |
542 | |
|
543 | 0 | Msg.SetParam( |
544 | 0 | 3, CString(" ").Join(vsNicks.begin(), vsNicks.end())); |
545 | 0 | } |
546 | 0 | } |
547 | 0 | } else if (Msg.GetType() == CMessage::Type::Join) { |
548 | 0 | if (!m_bExtendedJoin && pIRCSock->HasExtendedJoin()) { |
549 | 0 | Msg.SetParams({Msg.As<CJoinMessage>().GetTarget()}); |
550 | 0 | } |
551 | 0 | } |
552 | 0 | } |
553 | |
|
554 | 0 | MCString mssTags; |
555 | |
|
556 | 0 | for (const auto& it : Msg.GetTags()) { |
557 | 0 | if (IsTagEnabled(it.first)) { |
558 | 0 | mssTags[it.first] = it.second; |
559 | 0 | } |
560 | 0 | } |
561 | |
|
562 | 0 | if (HasServerTime()) { |
563 | | // If the server didn't set the time tag, manually set it |
564 | 0 | mssTags.emplace("time", CUtils::FormatServerTime(Msg.GetTime())); |
565 | 0 | } |
566 | |
|
567 | 0 | Msg.SetTags(mssTags); |
568 | 0 | Msg.SetClient(this); |
569 | 0 | Msg.SetNetwork(m_pNetwork); |
570 | |
|
571 | 0 | bool bReturn = false; |
572 | 0 | NETWORKMODULECALL(OnSendToClientMessage(Msg), m_pUser, m_pNetwork, this, |
573 | 0 | &bReturn); |
574 | 0 | if (bReturn) return false; |
575 | | |
576 | 0 | return PutClientRaw(Msg.ToString()); |
577 | 0 | } |
578 | | |
579 | 0 | bool CClient::PutClientRaw(const CString& sLine) { |
580 | 0 | CString sCopy = sLine; |
581 | 0 | bool bReturn = false; |
582 | 0 | NETWORKMODULECALL(OnSendToClient(sCopy, *this), m_pUser, m_pNetwork, this, |
583 | 0 | &bReturn); |
584 | 0 | if (bReturn) return false; |
585 | | |
586 | 0 | DEBUG("(" << GetFullName() << ") ZNC -> CLI [" |
587 | 0 | << CDebug::Filter(sCopy) << "]"); |
588 | 0 | Write(sCopy + "\r\n"); |
589 | 0 | return true; |
590 | 0 | } |
591 | | |
592 | 0 | void CClient::PutStatusNotice(const CString& sLine) { |
593 | 0 | PutModNotice("status", sLine); |
594 | 0 | } |
595 | | |
596 | 0 | unsigned int CClient::PutStatus(const CTable& table) { |
597 | 0 | unsigned int idx = 0; |
598 | 0 | CString sLine; |
599 | 0 | while (table.GetLine(idx++, sLine)) PutStatus(sLine); |
600 | 0 | return idx - 1; |
601 | 0 | } |
602 | | |
603 | 0 | void CClient::PutStatus(const CString& sLine) { PutModule("status", sLine); } |
604 | | |
605 | 0 | void CClient::PutModNotice(const CString& sModule, const CString& sLine) { |
606 | 0 | if (!m_pUser) { |
607 | 0 | return; |
608 | 0 | } |
609 | | |
610 | 0 | DEBUG("(" << GetFullName() |
611 | 0 | << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() + |
612 | 0 | ((sModule.empty()) ? "status" : sModule) + "!" + |
613 | 0 | ((sModule.empty()) ? "status" : sModule) + |
614 | 0 | "@znc.in NOTICE " |
615 | 0 | << GetNick() << " :" << sLine << "]"); |
616 | 0 | Write(":" + m_pUser->GetStatusPrefix() + |
617 | 0 | ((sModule.empty()) ? "status" : sModule) + "!" + |
618 | 0 | ((sModule.empty()) ? "status" : sModule) + "@znc.in NOTICE " + |
619 | 0 | GetNick() + " :" + sLine + "\r\n"); |
620 | 0 | } |
621 | | |
622 | 0 | void CClient::PutModule(const CString& sModule, const CString& sLine) { |
623 | 0 | if (!m_pUser) { |
624 | 0 | return; |
625 | 0 | } |
626 | | |
627 | 0 | DEBUG("(" << GetFullName() |
628 | 0 | << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() + |
629 | 0 | ((sModule.empty()) ? "status" : sModule) + "!" + |
630 | 0 | ((sModule.empty()) ? "status" : sModule) + |
631 | 0 | "@znc.in PRIVMSG " |
632 | 0 | << GetNick() << " :" << sLine << "]"); |
633 | |
|
634 | 0 | VCString vsLines; |
635 | 0 | sLine.Split("\n", vsLines); |
636 | 0 | for (const CString& s : vsLines) { |
637 | 0 | Write(":" + m_pUser->GetStatusPrefix() + |
638 | 0 | ((sModule.empty()) ? "status" : sModule) + "!" + |
639 | 0 | ((sModule.empty()) ? "status" : sModule) + "@znc.in PRIVMSG " + |
640 | 0 | GetNick() + " :" + s + "\r\n"); |
641 | 0 | } |
642 | 0 | } |
643 | | |
644 | 0 | CString CClient::GetNick(bool bAllowIRCNick) const { |
645 | 0 | CString sRet; |
646 | |
|
647 | 0 | const CIRCSock* pSock = GetIRCSock(); |
648 | 0 | if (bAllowIRCNick && pSock && pSock->IsAuthed()) { |
649 | 0 | sRet = pSock->GetNick(); |
650 | 0 | } |
651 | |
|
652 | 0 | return (sRet.empty()) ? m_sNick : sRet; |
653 | 0 | } |
654 | | |
655 | 0 | CString CClient::GetNickMask() const { |
656 | 0 | if (GetIRCSock() && GetIRCSock()->IsAuthed()) { |
657 | 0 | return GetIRCSock()->GetNickMask(); |
658 | 0 | } |
659 | | |
660 | 0 | CString sHost = |
661 | 0 | m_pNetwork ? m_pNetwork->GetBindHost() : m_pUser->GetBindHost(); |
662 | 0 | if (sHost.empty()) { |
663 | 0 | sHost = "irc.znc.in"; |
664 | 0 | } |
665 | |
|
666 | 0 | return GetNick() + "!" + |
667 | 0 | (m_pNetwork ? m_pNetwork->GetIdent() : m_pUser->GetIdent()) + "@" + |
668 | 0 | sHost; |
669 | 0 | } |
670 | | |
671 | 0 | bool CClient::IsValidIdentifier(const CString& sIdentifier) { |
672 | | // ^[-\w]+$ |
673 | |
|
674 | 0 | if (sIdentifier.empty()) { |
675 | 0 | return false; |
676 | 0 | } |
677 | | |
678 | 0 | const char* p = sIdentifier.c_str(); |
679 | 0 | while (*p) { |
680 | 0 | if (*p != '_' && *p != '-' && !isalnum(*p)) { |
681 | 0 | return false; |
682 | 0 | } |
683 | | |
684 | 0 | p++; |
685 | 0 | } |
686 | | |
687 | 0 | return true; |
688 | 0 | } |
689 | | |
690 | 0 | void CClient::RespondCap(const CString& sResponse) { |
691 | 0 | PutClient(":irc.znc.in CAP " + GetNick() + " " + sResponse); |
692 | 0 | } |
693 | | |
694 | 0 | void CClient::HandleCap(const CMessage& Message) { |
695 | 0 | CString sSubCmd = Message.GetParam(0); |
696 | |
|
697 | 0 | if (sSubCmd.Equals("LS")) { |
698 | 0 | SCString ssOfferCaps; |
699 | 0 | for (const auto& it : m_mCoreCaps) { |
700 | 0 | bool bServerDependent = std::get<0>(it.second); |
701 | 0 | if (!bServerDependent || |
702 | 0 | m_ssServerDependentCaps.count(it.first) > 0) |
703 | 0 | ssOfferCaps.insert(it.first); |
704 | 0 | } |
705 | 0 | GLOBALMODULECALL(OnClientCapLs(this, ssOfferCaps), NOTHING); |
706 | 0 | CString sRes = |
707 | 0 | CString(" ").Join(ssOfferCaps.begin(), ssOfferCaps.end()); |
708 | 0 | RespondCap("LS :" + sRes); |
709 | 0 | m_bInCap = true; |
710 | 0 | if (Message.GetParam(1).ToInt() >= 302) { |
711 | 0 | m_bCapNotify = true; |
712 | 0 | } |
713 | 0 | } else if (sSubCmd.Equals("END")) { |
714 | 0 | m_bInCap = false; |
715 | 0 | if (!IsAttached()) { |
716 | 0 | if (!m_pUser && m_bGotUser && !m_bGotPass) { |
717 | 0 | SendRequiredPasswordNotice(); |
718 | 0 | } else { |
719 | 0 | AuthUser(); |
720 | 0 | } |
721 | 0 | } |
722 | 0 | } else if (sSubCmd.Equals("REQ")) { |
723 | 0 | VCString vsTokens; |
724 | 0 | Message.GetParam(1).Split(" ", vsTokens, false); |
725 | |
|
726 | 0 | for (const CString& sToken : vsTokens) { |
727 | 0 | bool bVal = true; |
728 | 0 | CString sCap = sToken; |
729 | 0 | if (sCap.TrimPrefix("-")) bVal = false; |
730 | |
|
731 | 0 | bool bAccepted = false; |
732 | 0 | const auto& it = m_mCoreCaps.find(sCap); |
733 | 0 | if (m_mCoreCaps.end() != it) { |
734 | 0 | bool bServerDependent = std::get<0>(it->second); |
735 | 0 | bAccepted = !bServerDependent || |
736 | 0 | m_ssServerDependentCaps.count(sCap) > 0; |
737 | 0 | } |
738 | 0 | GLOBALMODULECALL(IsClientCapSupported(this, sCap, bVal), |
739 | 0 | &bAccepted); |
740 | |
|
741 | 0 | if (!bAccepted) { |
742 | | // Some unsupported capability is requested |
743 | 0 | RespondCap("NAK :" + Message.GetParam(1)); |
744 | 0 | return; |
745 | 0 | } |
746 | 0 | } |
747 | | |
748 | | // All is fine, we support what was requested |
749 | 0 | for (const CString& sToken : vsTokens) { |
750 | 0 | bool bVal = true; |
751 | 0 | CString sCap = sToken; |
752 | 0 | if (sCap.TrimPrefix("-")) bVal = false; |
753 | |
|
754 | 0 | auto handler_it = m_mCoreCaps.find(sCap); |
755 | 0 | if (m_mCoreCaps.end() != handler_it) { |
756 | 0 | const auto& handler = std::get<1>(handler_it->second); |
757 | 0 | handler(bVal); |
758 | 0 | } |
759 | 0 | GLOBALMODULECALL(OnClientCapRequest(this, sCap, bVal), NOTHING); |
760 | |
|
761 | 0 | if (bVal) { |
762 | 0 | m_ssAcceptedCaps.insert(sCap); |
763 | 0 | } else { |
764 | 0 | m_ssAcceptedCaps.erase(sCap); |
765 | 0 | } |
766 | 0 | } |
767 | |
|
768 | 0 | RespondCap("ACK :" + Message.GetParam(1)); |
769 | 0 | } else if (sSubCmd.Equals("LIST")) { |
770 | 0 | CString sList = |
771 | 0 | CString(" ").Join(m_ssAcceptedCaps.begin(), m_ssAcceptedCaps.end()); |
772 | 0 | RespondCap("LIST :" + sList); |
773 | 0 | } else { |
774 | 0 | PutClient(":irc.znc.in 410 " + GetNick() + " " + sSubCmd + |
775 | 0 | " :Invalid CAP subcommand"); |
776 | 0 | } |
777 | 0 | } |
778 | | |
779 | 0 | void CClient::ParsePass(const CString& sAuthLine) { |
780 | | // [user[@identifier][/network]:]password |
781 | |
|
782 | 0 | const size_t uColon = sAuthLine.find(":"); |
783 | 0 | if (uColon != CString::npos) { |
784 | 0 | m_sPass = sAuthLine.substr(uColon + 1); |
785 | |
|
786 | 0 | ParseUser(sAuthLine.substr(0, uColon)); |
787 | 0 | } else { |
788 | 0 | m_sPass = sAuthLine; |
789 | 0 | } |
790 | 0 | } |
791 | | |
792 | 0 | void CClient::ParseUser(const CString& sAuthLine) { |
793 | | // user[@identifier][/network] |
794 | |
|
795 | 0 | const size_t uSlash = sAuthLine.rfind("/"); |
796 | 0 | if (uSlash != CString::npos) { |
797 | 0 | m_sNetwork = sAuthLine.substr(uSlash + 1); |
798 | |
|
799 | 0 | ParseIdentifier(sAuthLine.substr(0, uSlash)); |
800 | 0 | } else { |
801 | 0 | ParseIdentifier(sAuthLine); |
802 | 0 | } |
803 | 0 | } |
804 | | |
805 | 0 | void CClient::ParseIdentifier(const CString& sAuthLine) { |
806 | | // user[@identifier] |
807 | |
|
808 | 0 | const size_t uAt = sAuthLine.rfind("@"); |
809 | 0 | if (uAt != CString::npos) { |
810 | 0 | const CString sId = sAuthLine.substr(uAt + 1); |
811 | |
|
812 | 0 | if (IsValidIdentifier(sId)) { |
813 | 0 | m_sIdentifier = sId; |
814 | 0 | m_sUser = sAuthLine.substr(0, uAt); |
815 | 0 | } else { |
816 | 0 | m_sUser = sAuthLine; |
817 | 0 | } |
818 | 0 | } else { |
819 | 0 | m_sUser = sAuthLine; |
820 | 0 | } |
821 | 0 | } |
822 | | |
823 | 0 | void CClient::SetTagSupport(const CString& sTag, bool bState) { |
824 | 0 | if (bState) { |
825 | 0 | m_ssSupportedTags.insert(sTag); |
826 | 0 | } else { |
827 | 0 | m_ssSupportedTags.erase(sTag); |
828 | 0 | } |
829 | 0 | } |
830 | | |
831 | 0 | void CClient::NotifyServerDependentCaps(const SCString& ssCaps) { |
832 | 0 | for (const CString& sCap : ssCaps) { |
833 | 0 | const auto& it = m_mCoreCaps.find(sCap); |
834 | 0 | if (m_mCoreCaps.end() != it) { |
835 | 0 | bool bServerDependent = std::get<0>(it->second); |
836 | 0 | if (bServerDependent) { |
837 | 0 | m_ssServerDependentCaps.insert(sCap); |
838 | 0 | } |
839 | 0 | } |
840 | 0 | } |
841 | |
|
842 | 0 | if (HasCapNotify() && !m_ssServerDependentCaps.empty()) { |
843 | 0 | CString sCaps = CString(" ").Join(m_ssServerDependentCaps.begin(), |
844 | 0 | m_ssServerDependentCaps.end()); |
845 | 0 | PutClient(":irc.znc.in CAP " + GetNick() + " NEW :" + sCaps); |
846 | 0 | } |
847 | 0 | } |
848 | | |
849 | 0 | void CClient::ClearServerDependentCaps() { |
850 | 0 | if (HasCapNotify() && !m_ssServerDependentCaps.empty()) { |
851 | 0 | CString sCaps = CString(" ").Join(m_ssServerDependentCaps.begin(), |
852 | 0 | m_ssServerDependentCaps.end()); |
853 | 0 | PutClient(":irc.znc.in CAP " + GetNick() + " DEL :" + sCaps); |
854 | |
|
855 | 0 | for (const CString& sCap : m_ssServerDependentCaps) { |
856 | 0 | const auto& it = m_mCoreCaps.find(sCap); |
857 | 0 | if (m_mCoreCaps.end() != it) { |
858 | 0 | const auto& handler = std::get<1>(it->second); |
859 | 0 | handler(false); |
860 | 0 | } |
861 | 0 | m_ssAcceptedCaps.erase(sCap); |
862 | 0 | } |
863 | 0 | } |
864 | |
|
865 | 0 | m_ssServerDependentCaps.clear(); |
866 | 0 | } |
867 | | |
868 | | template <typename T> |
869 | 0 | void CClient::AddBuffer(const T& Message) { |
870 | 0 | if (!m_pNetwork) { |
871 | 0 | return; |
872 | 0 | } |
873 | 0 | const CString sTarget = Message.GetTarget(); |
874 | |
|
875 | 0 | T Format; |
876 | 0 | Format.Clone(Message); |
877 | 0 | Format.SetNick(CNick(_NAMEDFMT(GetNickMask()))); |
878 | 0 | Format.SetTarget(_NAMEDFMT(sTarget)); |
879 | 0 | Format.SetText("{text}"); |
880 | |
|
881 | 0 | CChan* pChan = m_pNetwork->FindChan(sTarget); |
882 | 0 | if (pChan) { |
883 | 0 | if (!pChan->AutoClearChanBuffer() || !m_pNetwork->IsUserOnline()) { |
884 | 0 | pChan->AddBuffer(Format, Message.GetText()); |
885 | 0 | } |
886 | 0 | } else if (Message.GetType() != CMessage::Type::Notice) { |
887 | 0 | if (!m_pUser->AutoClearQueryBuffer() || !m_pNetwork->IsUserOnline()) { |
888 | 0 | CQuery* pQuery = m_pNetwork->AddQuery(sTarget); |
889 | 0 | if (pQuery) { |
890 | 0 | pQuery->AddBuffer(Format, Message.GetText()); |
891 | 0 | } |
892 | 0 | } |
893 | 0 | } |
894 | 0 | } Unexecuted instantiation: void CClient::AddBuffer<CActionMessage>(CActionMessage const&) Unexecuted instantiation: void CClient::AddBuffer<CNoticeMessage>(CNoticeMessage const&) Unexecuted instantiation: void CClient::AddBuffer<CTextMessage>(CTextMessage const&) |
895 | | |
896 | 0 | void CClient::EchoMessage(const CMessage& Message) { |
897 | 0 | CMessage EchoedMessage = Message; |
898 | 0 | for (CClient* pClient : GetClients()) { |
899 | 0 | if (pClient->HasEchoMessage() || |
900 | 0 | (pClient != this && ((m_pNetwork && m_pNetwork->IsChan(Message.GetParam(0))) || |
901 | 0 | pClient->HasSelfMessage()))) { |
902 | 0 | EchoedMessage.SetNick(GetNickMask()); |
903 | 0 | pClient->PutClient(EchoedMessage); |
904 | 0 | } |
905 | 0 | } |
906 | 0 | } |
907 | | |
908 | 0 | set<CChan*> CClient::MatchChans(const CString& sPatterns) const { |
909 | 0 | if (!m_pNetwork) { |
910 | 0 | return {}; |
911 | 0 | } |
912 | 0 | VCString vsPatterns; |
913 | 0 | sPatterns.Replace_n(",", " ") |
914 | 0 | .Split(" ", vsPatterns, false, "", "", true, true); |
915 | |
|
916 | 0 | set<CChan*> sChans; |
917 | 0 | for (const CString& sPattern : vsPatterns) { |
918 | 0 | vector<CChan*> vChans = m_pNetwork->FindChans(sPattern); |
919 | 0 | sChans.insert(vChans.begin(), vChans.end()); |
920 | 0 | } |
921 | 0 | return sChans; |
922 | 0 | } |
923 | | |
924 | 0 | unsigned int CClient::AttachChans(const std::set<CChan*>& sChans) { |
925 | 0 | unsigned int uAttached = 0; |
926 | 0 | for (CChan* pChan : sChans) { |
927 | 0 | if (!pChan->IsDetached()) continue; |
928 | 0 | uAttached++; |
929 | 0 | pChan->AttachUser(); |
930 | 0 | } |
931 | 0 | return uAttached; |
932 | 0 | } |
933 | | |
934 | 0 | unsigned int CClient::DetachChans(const std::set<CChan*>& sChans) { |
935 | 0 | unsigned int uDetached = 0; |
936 | 0 | for (CChan* pChan : sChans) { |
937 | 0 | if (pChan->IsDetached()) continue; |
938 | 0 | uDetached++; |
939 | 0 | pChan->DetachUser(); |
940 | 0 | } |
941 | 0 | return uDetached; |
942 | 0 | } |
943 | | |
944 | 0 | bool CClient::OnActionMessage(CActionMessage& Message) { |
945 | 0 | CString sTargets = Message.GetTarget(); |
946 | |
|
947 | 0 | VCString vTargets; |
948 | 0 | sTargets.Split(",", vTargets, false); |
949 | |
|
950 | 0 | for (CString& sTarget : vTargets) { |
951 | 0 | Message.SetTarget(sTarget); |
952 | 0 | if (m_pNetwork) { |
953 | | // May be nullptr. |
954 | 0 | Message.SetChan(m_pNetwork->FindChan(sTarget)); |
955 | 0 | } |
956 | |
|
957 | 0 | bool bContinue = false; |
958 | 0 | NETWORKMODULECALL(OnUserActionMessage(Message), m_pUser, m_pNetwork, |
959 | 0 | this, &bContinue); |
960 | 0 | if (bContinue) continue; |
961 | | |
962 | 0 | if (m_pNetwork) { |
963 | 0 | AddBuffer(Message); |
964 | 0 | EchoMessage(Message); |
965 | 0 | PutIRC(Message.ToString(CMessage::ExcludePrefix | |
966 | 0 | CMessage::ExcludeTags)); |
967 | 0 | } |
968 | 0 | } |
969 | |
|
970 | 0 | return true; |
971 | 0 | } |
972 | | |
973 | 0 | bool CClient::OnCTCPMessage(CCTCPMessage& Message) { |
974 | 0 | CString sTargets = Message.GetTarget(); |
975 | |
|
976 | 0 | VCString vTargets; |
977 | 0 | sTargets.Split(",", vTargets, false); |
978 | |
|
979 | 0 | if (Message.IsReply()) { |
980 | 0 | CString sCTCP = Message.GetText(); |
981 | 0 | if (sCTCP.Token(0) == "VERSION") { |
982 | | // There are 2 different scenarios: |
983 | | // |
984 | | // a) CTCP reply for VERSION is not set. |
985 | | // 1. ZNC receives CTCP VERSION from someone |
986 | | // 2. ZNC forwards CTCP VERSION to client |
987 | | // 3. Client replies with something |
988 | | // 4. ZNC adds itself to the reply |
989 | | // 5. ZNC sends the modified reply to whoever asked |
990 | | // |
991 | | // b) CTCP reply for VERSION is set. |
992 | | // 1. ZNC receives CTCP VERSION from someone |
993 | | // 2. ZNC replies with the configured reply (or just drops it if |
994 | | // empty), without forwarding anything to client |
995 | | // 3. Client does not see any CTCP request, and does not reply |
996 | | // |
997 | | // So, if user doesn't want "via ZNC" in CTCP VERSION reply, they |
998 | | // can set custom reply. |
999 | | // |
1000 | | // See more bikeshedding at github issues #820 and #1012 |
1001 | 0 | Message.SetText(sCTCP + " via " + CZNC::GetTag(false)); |
1002 | 0 | } |
1003 | 0 | } |
1004 | |
|
1005 | 0 | for (CString& sTarget : vTargets) { |
1006 | 0 | Message.SetTarget(sTarget); |
1007 | 0 | if (m_pNetwork) { |
1008 | | // May be nullptr. |
1009 | 0 | Message.SetChan(m_pNetwork->FindChan(sTarget)); |
1010 | 0 | } |
1011 | |
|
1012 | 0 | bool bContinue = false; |
1013 | 0 | if (Message.IsReply()) { |
1014 | 0 | NETWORKMODULECALL(OnUserCTCPReplyMessage(Message), m_pUser, |
1015 | 0 | m_pNetwork, this, &bContinue); |
1016 | 0 | } else { |
1017 | 0 | NETWORKMODULECALL(OnUserCTCPMessage(Message), m_pUser, m_pNetwork, |
1018 | 0 | this, &bContinue); |
1019 | 0 | } |
1020 | 0 | if (bContinue) continue; |
1021 | | |
1022 | 0 | if (!GetIRCSock()) { |
1023 | | // Some lagmeters do a NOTICE to their own nick, ignore those. |
1024 | 0 | if (!sTarget.Equals(m_sNick)) |
1025 | 0 | PutStatus(t_f( |
1026 | 0 | "Your CTCP to {1} got lost, you are not connected to IRC!")( |
1027 | 0 | Message.GetTarget())); |
1028 | 0 | continue; |
1029 | 0 | } |
1030 | | |
1031 | 0 | if (m_pNetwork) { |
1032 | 0 | PutIRC(Message.ToString(CMessage::ExcludePrefix | |
1033 | 0 | CMessage::ExcludeTags)); |
1034 | 0 | } |
1035 | 0 | } |
1036 | |
|
1037 | 0 | return true; |
1038 | 0 | } |
1039 | | |
1040 | 0 | bool CClient::OnJoinMessage(CJoinMessage& Message) { |
1041 | 0 | CString sChans = Message.GetTarget(); |
1042 | 0 | CString sKeys = Message.GetKey(); |
1043 | |
|
1044 | 0 | VCString vsChans; |
1045 | 0 | sChans.Split(",", vsChans, false); |
1046 | 0 | sChans.clear(); |
1047 | |
|
1048 | 0 | VCString vsKeys; |
1049 | 0 | sKeys.Split(",", vsKeys, true); |
1050 | 0 | sKeys.clear(); |
1051 | |
|
1052 | 0 | for (unsigned int a = 0; a < vsChans.size(); a++) { |
1053 | 0 | Message.SetTarget(vsChans[a]); |
1054 | 0 | Message.SetKey((a < vsKeys.size()) ? vsKeys[a] : ""); |
1055 | 0 | if (m_pNetwork) { |
1056 | | // May be nullptr. |
1057 | 0 | Message.SetChan(m_pNetwork->FindChan(vsChans[a])); |
1058 | 0 | } |
1059 | 0 | bool bContinue = false; |
1060 | 0 | NETWORKMODULECALL(OnUserJoinMessage(Message), m_pUser, m_pNetwork, this, |
1061 | 0 | &bContinue); |
1062 | 0 | if (bContinue) continue; |
1063 | | |
1064 | 0 | CString sChannel = Message.GetTarget(); |
1065 | 0 | CString sKey = Message.GetKey(); |
1066 | |
|
1067 | 0 | if (m_pNetwork) { |
1068 | 0 | CChan* pChan = m_pNetwork->FindChan(sChannel); |
1069 | 0 | if (pChan) { |
1070 | 0 | if (pChan->IsDetached()) |
1071 | 0 | pChan->AttachUser(this); |
1072 | 0 | else |
1073 | 0 | pChan->JoinUser(sKey); |
1074 | 0 | continue; |
1075 | 0 | } else if (!sChannel.empty()) { |
1076 | 0 | pChan = new CChan(sChannel, m_pNetwork, false); |
1077 | 0 | if (m_pNetwork->AddChan(pChan)) { |
1078 | 0 | pChan->SetKey(sKey); |
1079 | 0 | } |
1080 | 0 | } |
1081 | 0 | } |
1082 | | |
1083 | 0 | if (!sChannel.empty()) { |
1084 | 0 | sChans += (sChans.empty()) ? sChannel : CString("," + sChannel); |
1085 | |
|
1086 | 0 | if (!vsKeys.empty()) { |
1087 | 0 | sKeys += (sKeys.empty()) ? sKey : CString("," + sKey); |
1088 | 0 | } |
1089 | 0 | } |
1090 | 0 | } |
1091 | |
|
1092 | 0 | Message.SetTarget(sChans); |
1093 | 0 | Message.SetKey(sKeys); |
1094 | |
|
1095 | 0 | return sChans.empty(); |
1096 | 0 | } |
1097 | | |
1098 | 0 | bool CClient::OnModeMessage(CModeMessage& Message) { |
1099 | 0 | CString sTarget = Message.GetTarget(); |
1100 | |
|
1101 | 0 | if (m_pNetwork && m_pNetwork->IsChan(sTarget) && !Message.HasModes()) { |
1102 | | // If we are on that channel and already received a |
1103 | | // /mode reply from the server, we can answer this |
1104 | | // request ourself. |
1105 | |
|
1106 | 0 | CChan* pChan = m_pNetwork->FindChan(sTarget); |
1107 | 0 | if (pChan && pChan->IsOn() && !pChan->GetModeString().empty()) { |
1108 | 0 | PutClient(":" + m_pNetwork->GetIRCServer() + " 324 " + GetNick() + |
1109 | 0 | " " + sTarget + " " + pChan->GetModeString()); |
1110 | 0 | if (pChan->GetCreationDate() > 0) { |
1111 | 0 | PutClient(":" + m_pNetwork->GetIRCServer() + " 329 " + |
1112 | 0 | GetNick() + " " + sTarget + " " + |
1113 | 0 | CString(pChan->GetCreationDate())); |
1114 | 0 | } |
1115 | 0 | return true; |
1116 | 0 | } |
1117 | 0 | } |
1118 | | |
1119 | 0 | return false; |
1120 | 0 | } |
1121 | | |
1122 | 0 | bool CClient::OnNoticeMessage(CNoticeMessage& Message) { |
1123 | 0 | CString sTargets = Message.GetTarget(); |
1124 | |
|
1125 | 0 | VCString vTargets; |
1126 | 0 | sTargets.Split(",", vTargets, false); |
1127 | |
|
1128 | 0 | for (CString& sTarget : vTargets) { |
1129 | 0 | Message.SetTarget(sTarget); |
1130 | 0 | if (m_pNetwork) { |
1131 | | // May be nullptr. |
1132 | 0 | Message.SetChan(m_pNetwork->FindChan(sTarget)); |
1133 | 0 | } |
1134 | |
|
1135 | 0 | if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { |
1136 | 0 | if (!sTarget.Equals("status")) { |
1137 | 0 | CALLMOD(sTarget, this, m_pUser, m_pNetwork, |
1138 | 0 | OnModNotice(Message.GetText())); |
1139 | 0 | } |
1140 | 0 | continue; |
1141 | 0 | } |
1142 | | |
1143 | 0 | bool bContinue = false; |
1144 | 0 | NETWORKMODULECALL(OnUserNoticeMessage(Message), m_pUser, m_pNetwork, |
1145 | 0 | this, &bContinue); |
1146 | 0 | if (bContinue) continue; |
1147 | | |
1148 | 0 | if (!GetIRCSock()) { |
1149 | | // Some lagmeters do a NOTICE to their own nick, ignore those. |
1150 | 0 | if (!sTarget.Equals(m_sNick)) |
1151 | 0 | PutStatus( |
1152 | 0 | t_f("Your notice to {1} got lost, you are not connected to " |
1153 | 0 | "IRC!")(Message.GetTarget())); |
1154 | 0 | continue; |
1155 | 0 | } |
1156 | | |
1157 | 0 | if (m_pNetwork) { |
1158 | 0 | AddBuffer(Message); |
1159 | 0 | EchoMessage(Message); |
1160 | 0 | PutIRC(Message.ToString(CMessage::ExcludePrefix | |
1161 | 0 | CMessage::ExcludeTags)); |
1162 | 0 | } |
1163 | 0 | } |
1164 | | |
1165 | 0 | return true; |
1166 | 0 | } |
1167 | | |
1168 | 0 | bool CClient::OnPartMessage(CPartMessage& Message) { |
1169 | 0 | CString sChans = Message.GetTarget(); |
1170 | |
|
1171 | 0 | VCString vsChans; |
1172 | 0 | sChans.Split(",", vsChans, false); |
1173 | 0 | sChans.clear(); |
1174 | |
|
1175 | 0 | for (CString& sChan : vsChans) { |
1176 | 0 | bool bContinue = false; |
1177 | 0 | Message.SetTarget(sChan); |
1178 | 0 | if (m_pNetwork) { |
1179 | | // May be nullptr. |
1180 | 0 | Message.SetChan(m_pNetwork->FindChan(sChan)); |
1181 | 0 | } |
1182 | 0 | NETWORKMODULECALL(OnUserPartMessage(Message), m_pUser, m_pNetwork, this, |
1183 | 0 | &bContinue); |
1184 | 0 | if (bContinue) continue; |
1185 | | |
1186 | 0 | sChan = Message.GetTarget(); |
1187 | |
|
1188 | 0 | CChan* pChan = m_pNetwork ? m_pNetwork->FindChan(sChan) : nullptr; |
1189 | |
|
1190 | 0 | if (pChan && !pChan->IsOn()) { |
1191 | 0 | PutStatusNotice(t_f("Removing channel {1}")(sChan)); |
1192 | 0 | m_pNetwork->DelChan(sChan); |
1193 | 0 | } else { |
1194 | 0 | sChans += (sChans.empty()) ? sChan : CString("," + sChan); |
1195 | 0 | } |
1196 | 0 | } |
1197 | |
|
1198 | 0 | if (sChans.empty()) { |
1199 | 0 | return true; |
1200 | 0 | } |
1201 | | |
1202 | 0 | Message.SetTarget(sChans); |
1203 | |
|
1204 | 0 | return false; |
1205 | 0 | } |
1206 | | |
1207 | 0 | bool CClient::OnPingMessage(CMessage& Message) { |
1208 | | // All PONGs are generated by ZNC. We will still forward this to |
1209 | | // the ircd, but all PONGs from irc will be blocked. |
1210 | 0 | if (!Message.GetParams().empty()) |
1211 | 0 | PutClient(":irc.znc.in PONG irc.znc.in " + Message.GetParamsColon(0)); |
1212 | 0 | else |
1213 | 0 | PutClient(":irc.znc.in PONG irc.znc.in"); |
1214 | 0 | return false; |
1215 | 0 | } |
1216 | | |
1217 | 0 | bool CClient::OnPongMessage(CMessage& Message) { |
1218 | | // Block PONGs, we already responded to the pings |
1219 | 0 | return true; |
1220 | 0 | } |
1221 | | |
1222 | 0 | bool CClient::OnQuitMessage(CQuitMessage& Message) { |
1223 | 0 | bool bReturn = false; |
1224 | 0 | NETWORKMODULECALL(OnUserQuitMessage(Message), m_pUser, m_pNetwork, this, |
1225 | 0 | &bReturn); |
1226 | 0 | if (!bReturn) { |
1227 | 0 | Close(Csock::CLT_AFTERWRITE); // Treat a client quit as a detach |
1228 | 0 | } |
1229 | | // Don't forward this msg. We don't want the client getting us |
1230 | | // disconnected. |
1231 | 0 | return true; |
1232 | 0 | } |
1233 | | |
1234 | 0 | bool CClient::OnTextMessage(CTextMessage& Message) { |
1235 | 0 | CString sTargets = Message.GetTarget(); |
1236 | |
|
1237 | 0 | VCString vTargets; |
1238 | 0 | sTargets.Split(",", vTargets, false); |
1239 | |
|
1240 | 0 | for (CString& sTarget : vTargets) { |
1241 | 0 | Message.SetTarget(sTarget); |
1242 | 0 | if (m_pNetwork) { |
1243 | | // May be nullptr. |
1244 | 0 | Message.SetChan(m_pNetwork->FindChan(sTarget)); |
1245 | 0 | } |
1246 | |
|
1247 | 0 | if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { |
1248 | 0 | EchoMessage(Message); |
1249 | |
|
1250 | 0 | if (sTarget.Equals("status")) { |
1251 | 0 | CString sMsg = Message.GetText(); |
1252 | 0 | UserCommand(sMsg); |
1253 | 0 | } else { |
1254 | 0 | CALLMOD(sTarget, this, m_pUser, m_pNetwork, |
1255 | 0 | OnModCommand(Message.GetText())); |
1256 | 0 | } |
1257 | 0 | continue; |
1258 | 0 | } |
1259 | | |
1260 | 0 | bool bContinue = false; |
1261 | 0 | NETWORKMODULECALL(OnUserTextMessage(Message), m_pUser, m_pNetwork, this, |
1262 | 0 | &bContinue); |
1263 | 0 | if (bContinue) continue; |
1264 | | |
1265 | 0 | if (!GetIRCSock()) { |
1266 | | // Some lagmeters do a PRIVMSG to their own nick, ignore those. |
1267 | 0 | if (!sTarget.Equals(m_sNick)) |
1268 | 0 | PutStatus( |
1269 | 0 | t_f("Your message to {1} got lost, you are not connected " |
1270 | 0 | "to IRC!")(Message.GetTarget())); |
1271 | 0 | continue; |
1272 | 0 | } |
1273 | | |
1274 | 0 | if (m_pNetwork) { |
1275 | 0 | AddBuffer(Message); |
1276 | 0 | EchoMessage(Message); |
1277 | 0 | PutIRC(Message.ToString(CMessage::ExcludePrefix | |
1278 | 0 | CMessage::ExcludeTags)); |
1279 | 0 | } |
1280 | 0 | } |
1281 | | |
1282 | 0 | return true; |
1283 | 0 | } |
1284 | | |
1285 | 0 | bool CClient::OnTopicMessage(CTopicMessage& Message) { |
1286 | 0 | bool bReturn = false; |
1287 | 0 | CString sChan = Message.GetTarget(); |
1288 | 0 | CString sTopic = Message.GetTopic(); |
1289 | 0 | if (m_pNetwork) { |
1290 | | // May be nullptr. |
1291 | 0 | Message.SetChan(m_pNetwork->FindChan(sChan)); |
1292 | 0 | } |
1293 | |
|
1294 | 0 | if (!sTopic.empty()) { |
1295 | 0 | NETWORKMODULECALL(OnUserTopicMessage(Message), m_pUser, m_pNetwork, |
1296 | 0 | this, &bReturn); |
1297 | 0 | } else { |
1298 | 0 | NETWORKMODULECALL(OnUserTopicRequest(sChan), m_pUser, m_pNetwork, this, |
1299 | 0 | &bReturn); |
1300 | 0 | Message.SetTarget(sChan); |
1301 | 0 | } |
1302 | | |
1303 | 0 | return bReturn; |
1304 | 0 | } |
1305 | | |
1306 | 0 | bool CClient::OnOtherMessage(CMessage& Message) { |
1307 | 0 | const CString& sCommand = Message.GetCommand(); |
1308 | |
|
1309 | 0 | if (sCommand.Equals("ZNC")) { |
1310 | 0 | CString sTarget = Message.GetParam(0); |
1311 | 0 | CString sModCommand; |
1312 | |
|
1313 | 0 | if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { |
1314 | 0 | sModCommand = Message.GetParamsColon(1); |
1315 | 0 | } else { |
1316 | 0 | sTarget = "status"; |
1317 | 0 | sModCommand = Message.GetParamsColon(0); |
1318 | 0 | } |
1319 | |
|
1320 | 0 | if (sTarget.Equals("status")) { |
1321 | 0 | if (sModCommand.empty()) |
1322 | 0 | PutStatus(t_s("Hello. How may I help you?")); |
1323 | 0 | else |
1324 | 0 | UserCommand(sModCommand); |
1325 | 0 | } else { |
1326 | 0 | if (sModCommand.empty()) |
1327 | 0 | CALLMOD(sTarget, this, m_pUser, m_pNetwork, |
1328 | 0 | PutModule(t_s("Hello. How may I help you?"))) |
1329 | 0 | else |
1330 | 0 | CALLMOD(sTarget, this, m_pUser, m_pNetwork, |
1331 | 0 | OnModCommand(sModCommand)) |
1332 | 0 | } |
1333 | 0 | return true; |
1334 | 0 | } else if (sCommand.Equals("ATTACH")) { |
1335 | 0 | if (!m_pNetwork) { |
1336 | 0 | return true; |
1337 | 0 | } |
1338 | | |
1339 | 0 | CString sPatterns = Message.GetParamsColon(0); |
1340 | |
|
1341 | 0 | if (sPatterns.empty()) { |
1342 | 0 | PutStatusNotice(t_s("Usage: /attach <#chans>")); |
1343 | 0 | return true; |
1344 | 0 | } |
1345 | | |
1346 | 0 | set<CChan*> sChans = MatchChans(sPatterns); |
1347 | 0 | unsigned int uAttachedChans = AttachChans(sChans); |
1348 | |
|
1349 | 0 | PutStatusNotice(t_p("There was {1} channel matching [{2}]", |
1350 | 0 | "There were {1} channels matching [{2}]", |
1351 | 0 | sChans.size())(sChans.size(), sPatterns)); |
1352 | 0 | PutStatusNotice(t_p("Attached {1} channel", "Attached {1} channels", |
1353 | 0 | uAttachedChans)(uAttachedChans)); |
1354 | |
|
1355 | 0 | return true; |
1356 | 0 | } else if (sCommand.Equals("DETACH")) { |
1357 | 0 | if (!m_pNetwork) { |
1358 | 0 | return true; |
1359 | 0 | } |
1360 | | |
1361 | 0 | CString sPatterns = Message.GetParamsColon(0); |
1362 | |
|
1363 | 0 | if (sPatterns.empty()) { |
1364 | 0 | PutStatusNotice(t_s("Usage: /detach <#chans>")); |
1365 | 0 | return true; |
1366 | 0 | } |
1367 | | |
1368 | 0 | set<CChan*> sChans = MatchChans(sPatterns); |
1369 | 0 | unsigned int uDetached = DetachChans(sChans); |
1370 | |
|
1371 | 0 | PutStatusNotice(t_p("There was {1} channel matching [{2}]", |
1372 | 0 | "There were {1} channels matching [{2}]", |
1373 | 0 | sChans.size())(sChans.size(), sPatterns)); |
1374 | 0 | PutStatusNotice(t_p("Detached {1} channel", "Detached {1} channels", |
1375 | 0 | uDetached)(uDetached)); |
1376 | |
|
1377 | 0 | return true; |
1378 | 0 | } else if (sCommand.Equals("PROTOCTL")) { |
1379 | 0 | for (const CString& sParam : Message.GetParams()) { |
1380 | 0 | if (sParam == "NAMESX") { |
1381 | 0 | m_bNamesx = true; |
1382 | 0 | } else if (sParam == "UHNAMES") { |
1383 | 0 | m_bUHNames = true; |
1384 | 0 | } |
1385 | 0 | } |
1386 | 0 | return true; // If the server understands it, we already enabled namesx |
1387 | | // / uhnames |
1388 | 0 | } |
1389 | | |
1390 | 0 | return false; |
1391 | 0 | } |