]>
Commit | Line | Data |
---|---|---|
a09a7e79 | 1 | /* |
b9b0fd4c | 2 | * Copyright (C) 2004-2011 See the AUTHORS file for details. |
a09a7e79 | 3 | * |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms of the GNU General Public License version 2 as published | |
6 | * by the Free Software Foundation. | |
7 | */ | |
6dcacaa7 | 8 | |
712d4006 | 9 | #include "Client.h" |
e72c4456 | 10 | #include "Chan.h" |
3f24f287 | 11 | #include "FileUtils.h" |
e72c4456 | 12 | #include "IRCSock.h" |
e72c4456 | 13 | #include "User.h" |
9e6d05a0 | 14 | #include "znc.h" |
ad92c58c | 15 | #include "WebModules.h" |
538d3ece | 16 | |
99f1efc8 | 17 | #define CALLMOD(MOD, CLIENT, USER, FUNC) { \ |
18 | CModule* pModule = CZNC::Get().GetModules().FindModule(MOD); \ | |
19 | if (pModule) { \ | |
20 | try { \ | |
21 | pModule->SetClient(CLIENT); \ | |
22 | pModule->SetUser(USER); \ | |
23 | pModule->FUNC; \ | |
24 | pModule->SetClient(NULL); \ | |
25 | pModule->SetUser(NULL); \ | |
26 | } catch (CModule::EModException e) { \ | |
27 | if (e == CModule::UNLOAD) { \ | |
28 | CZNC::Get().GetModules().UnloadModule(MOD); \ | |
29 | } \ | |
30 | } \ | |
31 | } else { \ | |
32 | pModule = (USER)->GetModules().FindModule(MOD); \ | |
33 | if (pModule) { \ | |
34 | try { \ | |
35 | pModule->SetClient(CLIENT); \ | |
36 | pModule->FUNC; \ | |
37 | pModule->SetClient(NULL); \ | |
38 | } catch (CModule::EModException e) { \ | |
39 | if (e == CModule::UNLOAD) { \ | |
40 | (USER)->GetModules().UnloadModule(MOD); \ | |
41 | } \ | |
42 | } \ | |
43 | } else { \ | |
44 | PutStatus("No such module [" + MOD + "]"); \ | |
45 | } \ | |
46 | } \ | |
d6c34d24 | 47 | } |
ecabbd2b | 48 | |
b772e266 | 49 | CClient::~CClient() { |
50 | if (!m_spAuth.IsNull()) { | |
51 | CClientAuth* pAuth = (CClientAuth*) &(*m_spAuth); | |
b0e59f12 | 52 | pAuth->Invalidate(); |
b772e266 | 53 | } |
54 | if (m_pUser != NULL) { | |
55 | m_pUser->AddBytesRead(GetBytesRead()); | |
56 | m_pUser->AddBytesWritten(GetBytesWritten()); | |
57 | } | |
58 | } | |
59 | ||
40b5a5cd KF |
60 | void CClient::SendRequiredPasswordNotice() { |
61 | PutClient(":irc.znc.in 464 " + GetNick() + " :Password required"); | |
62 | PutClient(":irc.znc.in NOTICE AUTH :*** " | |
63 | "You need to send your password. " | |
64 | "Try /quote PASS <username>:<password>"); | |
65 | } | |
66 | ||
712d4006 | 67 | void CClient::ReadLine(const CString& sData) { |
beb5b49b | 68 | CString sLine = sData; |
538d3ece | 69 | |
9933ba9c | 70 | sLine.TrimRight("\n\r"); |
538d3ece | 71 | |
235b10c2 | 72 | DEBUG("(" << ((m_pUser) ? m_pUser->GetUserName() : GetRemoteIP()) << ") CLI -> ZNC [" << sLine << "]"); |
538d3ece | 73 | |
e7bb3e5d | 74 | if (IsAttached()) { |
b740d7cc | 75 | MODULECALL(OnUserRaw(sLine), m_pUser, this, return); |
d84b9c6e | 76 | } else { |
47a5ab37 | 77 | GLOBALMODULECALL(OnUnknownUserRaw(sLine), m_pUser, this, return); |
538d3ece | 78 | } |
538d3ece | 79 | |
24950d24 | 80 | CString sCommand = sLine.Token(0); |
cd831837 | 81 | if (sCommand.Left(1) == ":") { |
82 | // Evil client! Sending a nickmask prefix on client's command | |
83 | // is bad, bad, bad, bad, bad, bad, bad, bad, BAD, B A D! | |
84 | sLine = sLine.Token(1, true); | |
85 | sCommand = sLine.Token(0); | |
86 | } | |
538d3ece | 87 | |
5237a247 | 88 | if (sCommand.Equals("PASS")) { |
e7bb3e5d | 89 | if (!IsAttached()) { |
57f66b5e | 90 | m_bGotPass = true; |
24950d24 | 91 | m_sPass = sLine.Token(1); |
35394275 | 92 | if (m_sPass.Left(1) == ":") |
93 | m_sPass.LeftChomp(); | |
538d3ece | 94 | |
beb5b49b | 95 | if (m_sPass.find(":") != CString::npos) { |
3e0c33b0 | 96 | m_sUser = m_sPass.Token(0, false, ":"); |
97 | m_sPass = m_sPass.Token(1, true, ":"); | |
57f66b5e | 98 | } |
538d3ece | 99 | |
f6f4e6a6 | 100 | AuthUser(); |
99f1efc8 | 101 | return; // Don't forward this msg. ZNC has already registered us. |
7f1feb3c | 102 | } |
5237a247 | 103 | } else if (sCommand.Equals("NICK")) { |
24950d24 | 104 | CString sNick = sLine.Token(1); |
078bbcf0 | 105 | if (sNick.Left(1) == ":") { |
106 | sNick.LeftChomp(); | |
538d3ece | 107 | } |
108 | ||
e7bb3e5d | 109 | if (!IsAttached()) { |
538d3ece | 110 | m_sNick = sNick; |
111 | m_bGotNick = true; | |
112 | ||
f6f4e6a6 | 113 | AuthUser(); |
99f1efc8 | 114 | return; // Don't forward this msg. ZNC will handle nick changes until auth is complete |
538d3ece | 115 | } |
5237a247 | 116 | } else if (sCommand.Equals("USER")) { |
e7bb3e5d | 117 | if (!IsAttached()) { |
7f1feb3c | 118 | if (m_sUser.empty()) { |
119 | m_sUser = sLine.Token(1); | |
120 | } | |
538d3ece | 121 | |
7f1feb3c | 122 | m_bGotUser = true; |
57f66b5e | 123 | |
f6f4e6a6 | 124 | if (m_bGotPass) { |
7f1feb3c | 125 | AuthUser(); |
40b5a5cd KF |
126 | } else if (!m_bInCap) { |
127 | SendRequiredPasswordNotice(); | |
7f1feb3c | 128 | } |
538d3ece | 129 | |
99f1efc8 | 130 | return; // Don't forward this msg. ZNC has already registered us. |
7f1feb3c | 131 | } |
99ea9c6f | 132 | } else if (sCommand.Equals("CAP")) { |
133 | HandleCap(sLine); | |
134 | ||
135 | // Don't let the client talk to the server directly about CAP, | |
7bb4ed34 | 136 | // we don't want anything enabled that ZNC does not support. |
99ea9c6f | 137 | return; |
fb9a062f | 138 | } |
139 | ||
140 | if (!m_pUser) { | |
99ea9c6f | 141 | // Only CAP, NICK, USER and PASS are allowed before login |
2390a315 | 142 | return; |
143 | } | |
144 | ||
5237a247 | 145 | if (sCommand.Equals("ZNC")) { |
051e1f81 | 146 | CString sTarget = sLine.Token(1); |
147 | CString sModCommand; | |
148 | ||
149 | if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { | |
150 | sModCommand = sLine.Token(2, true); | |
151 | } else { | |
152 | sTarget = "status"; | |
153 | sModCommand = sLine.Token(1, true); | |
154 | } | |
155 | ||
5237a247 | 156 | if (sTarget.Equals("status")) { |
051e1f81 | 157 | if (sModCommand.empty()) |
158 | PutStatus("Hello. How may I help you?"); | |
159 | else | |
160 | UserCommand(sModCommand); | |
161 | } else { | |
051e1f81 | 162 | if (sModCommand.empty()) |
163 | CALLMOD(sTarget, this, m_pUser, PutModule("Hello. How may I help you?")) | |
164 | else | |
165 | CALLMOD(sTarget, this, m_pUser, OnModCommand(sModCommand)) | |
051e1f81 | 166 | } |
2390a315 | 167 | return; |
5237a247 | 168 | } else if (sCommand.Equals("DETACH")) { |
9fecae45 | 169 | CString sChannels = sLine.Token(1).TrimPrefix_n(":"); |
2390a315 | 170 | |
9fecae45 | 171 | if (sChannels.empty()) { |
cc00aa23 | 172 | PutStatusNotice("Usage: /detach <#chan>"); |
173 | return; | |
174 | } | |
2390a315 | 175 | |
9fecae45 NL |
176 | VCString vChans; |
177 | sChannels.Split(",", vChans, false); | |
178 | sChannels.clear(); | |
179 | ||
180 | for (VCString::const_iterator channelIterator = vChans.begin(); | |
181 | channelIterator != vChans.end(); | |
182 | ++channelIterator) | |
183 | { | |
184 | CString sChannel = *channelIterator; | |
185 | ||
186 | CChan *pChannel = m_pUser->FindChan(sChannel); | |
187 | if (pChannel) { | |
188 | pChannel->DetachUser(); | |
189 | } else { | |
190 | PutStatusNotice("You are not on [" + sChannel + "]"); | |
191 | } | |
2390a315 | 192 | } |
cc00aa23 | 193 | |
cc00aa23 | 194 | return; |
5237a247 | 195 | } else if (sCommand.Equals("PING")) { |
bc40713b | 196 | // All PONGs are generated by znc. We will still forward this to |
197 | // the ircd, but all PONGs from irc will be blocked. | |
e86008ff | 198 | if (sLine.length() >= 5) |
199 | PutClient(":irc.znc.in PONG irc.znc.in " + sLine.substr(5)); | |
200 | else | |
201 | PutClient(":irc.znc.in PONG irc.znc.in"); | |
5237a247 | 202 | } else if (sCommand.Equals("PONG")) { |
bc40713b | 203 | // Block PONGs, we already responded to the pings |
204 | return; | |
5237a247 | 205 | } else if (sCommand.Equals("JOIN")) { |
fb9a062f | 206 | CString sChans = sLine.Token(1); |
41b5ea43 | 207 | CString sKey = sLine.Token(2); |
208 | ||
fb9a062f | 209 | if (sChans.Left(1) == ":") { |
210 | sChans.LeftChomp(); | |
538d3ece | 211 | } |
212 | ||
cc00aa23 | 213 | VCString vChans; |
214 | sChans.Split(",", vChans, false); | |
215 | sChans.clear(); | |
fb9a062f | 216 | |
cc00aa23 | 217 | for (unsigned int a = 0; a < vChans.size(); a++) { |
218 | CString sChannel = vChans[a]; | |
219 | MODULECALL(OnUserJoin(sChannel, sKey), m_pUser, this, continue); | |
fb9a062f | 220 | |
cc00aa23 | 221 | CChan* pChan = m_pUser->FindChan(sChannel); |
fb9a062f | 222 | |
cc00aa23 | 223 | if (pChan) { |
224 | pChan->JoinUser(false, sKey); | |
225 | continue; | |
fb9a062f | 226 | } |
538d3ece | 227 | |
cc00aa23 | 228 | if (!sChannel.empty()) { |
229 | sChans += (sChans.empty()) ? sChannel : CString("," + sChannel); | |
538d3ece | 230 | } |
cc00aa23 | 231 | } |
fb9a062f | 232 | |
cc00aa23 | 233 | if (sChans.empty()) { |
234 | return; | |
235 | } | |
fb9a062f | 236 | |
cc00aa23 | 237 | sLine = "JOIN " + sChans; |
238 | ||
239 | if (!sKey.empty()) { | |
240 | sLine += " " + sKey; | |
538d3ece | 241 | } |
5237a247 | 242 | } else if (sCommand.Equals("PART")) { |
7ae31aad NL |
243 | CString sChans = sLine.Token(1).TrimPrefix_n(":"); |
244 | CString sMessage = sLine.Token(2, true).TrimPrefix_n(":"); | |
fb9a062f | 245 | |
7ae31aad NL |
246 | VCString vChans; |
247 | sChans.Split(",", vChans, false); | |
248 | sChans.clear(); | |
fb9a062f | 249 | |
7ae31aad NL |
250 | for (VCString::const_iterator it = vChans.begin(); it != vChans.end(); ++it) { |
251 | CString sChan = *it; | |
252 | MODULECALL(OnUserPart(sChan, sMessage), m_pUser, this, continue) | |
fb9a062f | 253 | |
7ae31aad NL |
254 | CChan* pChan = m_pUser->FindChan(sChan); |
255 | ||
256 | if (pChan && !pChan->IsOn()) { | |
257 | PutStatusNotice("Removing channel [" + sChan + "]"); | |
258 | m_pUser->DelChan(sChan); | |
259 | return; | |
260 | } else { | |
261 | sChans += (sChans.empty()) ? sChan : CString("," + sChan); | |
262 | } | |
263 | } | |
fc06004d | 264 | |
7ae31aad | 265 | if (sChans.empty()) { |
cc00aa23 | 266 | return; |
fc06004d | 267 | } |
fb9a062f | 268 | |
7ae31aad | 269 | sLine = "PART " + sChans; |
fb9a062f | 270 | |
271 | if (!sMessage.empty()) { | |
272 | sLine += " :" + sMessage; | |
273 | } | |
5237a247 | 274 | } else if (sCommand.Equals("TOPIC")) { |
442ef47c | 275 | CString sChan = sLine.Token(1); |
276 | CString sTopic = sLine.Token(2, true); | |
442ef47c | 277 | |
0316c6a1 | 278 | if (!sTopic.empty()) { |
279 | if (sTopic.Left(1) == ":") | |
280 | sTopic.LeftChomp(); | |
281 | MODULECALL(OnUserTopic(sChan, sTopic), m_pUser, this, return); | |
282 | sLine = "TOPIC " + sChan + " :" + sTopic; | |
283 | } else { | |
284 | MODULECALL(OnUserTopicRequest(sChan), m_pUser, this, return); | |
442ef47c | 285 | } |
5237a247 | 286 | } else if (sCommand.Equals("MODE")) { |
0a622749 | 287 | CString sTarget = sLine.Token(1); |
288 | CString sModes = sLine.Token(2, true); | |
289 | ||
cc00aa23 | 290 | if (m_pUser->IsChan(sTarget)) { |
3b8134c3 | 291 | CChan *pChan = m_pUser->FindChan(sTarget); |
0a622749 | 292 | |
bc77d8b2 | 293 | // If we are on that channel and already received a |
294 | // /mode reply from the server, we can answer this | |
295 | // request ourself. | |
296 | if (pChan && pChan->IsOn() && sModes.empty() && !pChan->GetModeString().empty()) { | |
3b8134c3 | 297 | PutClient(":" + m_pUser->GetIRCServer() + " 324 " + GetNick() + " " + sTarget + " " + pChan->GetModeString()); |
298 | if (pChan->GetCreationDate() > 0) { | |
299 | PutClient(":" + m_pUser->GetIRCServer() + " 329 " + GetNick() + " " + sTarget + " " + CString(pChan->GetCreationDate())); | |
300 | } | |
d97ef37d | 301 | return; |
3b8134c3 | 302 | } |
0a622749 | 303 | } |
5237a247 | 304 | } else if (sCommand.Equals("QUIT")) { |
cc00aa23 | 305 | m_pUser->UserDisconnected(this); |
538d3ece | 306 | |
99f1efc8 | 307 | Close(Csock::CLT_AFTERWRITE); // Treat a client quit as a detach |
308 | return; // Don't forward this msg. We don't want the client getting us disconnected. | |
5237a247 | 309 | } else if (sCommand.Equals("PROTOCTL")) { |
5d9a22f6 | 310 | VCString vsTokens; |
311 | VCString::const_iterator it; | |
312 | sLine.Token(1, true).Split(" ", vsTokens, false); | |
313 | ||
314 | for (it = vsTokens.begin(); it != vsTokens.end(); ++it) { | |
315 | if (*it == "NAMESX") { | |
0a622749 | 316 | m_bNamesx = true; |
5d9a22f6 | 317 | } else if (*it == "UHNAMES") { |
0a622749 | 318 | m_bUHNames = true; |
319 | } | |
0a622749 | 320 | } |
99f1efc8 | 321 | return; // If the server understands it, we already enabled namesx / uhnames |
5237a247 | 322 | } else if (sCommand.Equals("NOTICE")) { |
24950d24 | 323 | CString sTarget = sLine.Token(1); |
324 | CString sMsg = sLine.Token(2, true); | |
538d3ece | 325 | |
078bbcf0 | 326 | if (sMsg.Left(1) == ":") { |
327 | sMsg.LeftChomp(); | |
538d3ece | 328 | } |
329 | ||
b757a318 | 330 | if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { |
5237a247 | 331 | if (!sTarget.Equals("status")) { |
b757a318 | 332 | CALLMOD(sTarget, this, m_pUser, OnModNotice(sMsg)); |
333 | } | |
538d3ece | 334 | return; |
335 | } | |
336 | ||
0823b27f | 337 | if (sMsg.WildCmp("\001*\001")) { |
beb5b49b | 338 | CString sCTCP = sMsg; |
078bbcf0 | 339 | sCTCP.LeftChomp(); |
340 | sCTCP.RightChomp(); | |
538d3ece | 341 | |
b740d7cc | 342 | MODULECALL(OnUserCTCPReply(sTarget, sCTCP), m_pUser, this, return); |
538d3ece | 343 | |
344 | sMsg = "\001" + sCTCP + "\001"; | |
345 | } else { | |
b740d7cc | 346 | MODULECALL(OnUserNotice(sTarget, sMsg), m_pUser, this, return); |
538d3ece | 347 | } |
538d3ece | 348 | |
b50d944b | 349 | if (!GetIRCSock()) { |
f1e6a41d | 350 | // Some lagmeters do a NOTICE to their own nick, ignore those. |
5237a247 | 351 | if (!sTarget.Equals(m_sNick)) |
f1e6a41d | 352 | PutStatus("Your notice to [" + sTarget + "] got lost, " |
353 | "you are not connected to IRC!"); | |
c4f1eb3d | 354 | return; |
355 | } | |
356 | ||
4c33f794 | 357 | CChan* pChan = m_pUser->FindChan(sTarget); |
358 | ||
6d27d1c0 | 359 | if ((pChan) && (pChan->KeepBuffer())) { |
f601db2c | 360 | pChan->AddBuffer(":" + GetNickMask() + " NOTICE " + sTarget + " :" + m_pUser->AddTimestamp(sMsg)); |
6d27d1c0 | 361 | } |
362 | ||
9170991a | 363 | // Relay to the rest of the clients that may be connected to this user |
cc00aa23 | 364 | if (m_pUser->IsChan(sTarget)) { |
9170991a | 365 | vector<CClient*>& vClients = m_pUser->GetClients(); |
366 | ||
367 | for (unsigned int a = 0; a < vClients.size(); a++) { | |
368 | CClient* pClient = vClients[a]; | |
369 | ||
370 | if (pClient != this) { | |
371 | pClient->PutClient(":" + GetNickMask() + " NOTICE " + sTarget + " :" + sMsg); | |
372 | } | |
373 | } | |
374 | } | |
375 | ||
538d3ece | 376 | PutIRC("NOTICE " + sTarget + " :" + sMsg); |
377 | return; | |
5237a247 | 378 | } else if (sCommand.Equals("PRIVMSG")) { |
24950d24 | 379 | CString sTarget = sLine.Token(1); |
380 | CString sMsg = sLine.Token(2, true); | |
538d3ece | 381 | |
078bbcf0 | 382 | if (sMsg.Left(1) == ":") { |
383 | sMsg.LeftChomp(); | |
538d3ece | 384 | } |
385 | ||
0823b27f | 386 | if (sMsg.WildCmp("\001*\001")) { |
beb5b49b | 387 | CString sCTCP = sMsg; |
078bbcf0 | 388 | sCTCP.LeftChomp(); |
389 | sCTCP.RightChomp(); | |
538d3ece | 390 | |
8f508cb4 | 391 | if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { |
5237a247 | 392 | if (sTarget.Equals("status")) { |
f74f501d | 393 | StatusCTCP(sCTCP); |
b757a318 | 394 | } else { |
b757a318 | 395 | CALLMOD(sTarget, this, m_pUser, OnModCTCP(sCTCP)); |
b757a318 | 396 | } |
538d3ece | 397 | return; |
398 | } | |
399 | ||
2cd52ad3 | 400 | CChan* pChan = m_pUser->FindChan(sTarget); |
401 | ||
5237a247 | 402 | if (sCTCP.Token(0).Equals("ACTION")) { |
f601db2c | 403 | CString sMessage = sCTCP.Token(1, true); |
404 | MODULECALL(OnUserAction(sTarget, sMessage), m_pUser, this, return); | |
a3b405bd | 405 | sCTCP = "ACTION " + sMessage; |
f601db2c | 406 | |
2cd52ad3 | 407 | if (pChan && pChan->KeepBuffer()) { |
f601db2c | 408 | pChan->AddBuffer(":" + GetNickMask() + " PRIVMSG " + sTarget + " :\001ACTION " + m_pUser->AddTimestamp(sMessage) + "\001"); |
2cd52ad3 | 409 | } |
9170991a | 410 | |
411 | // Relay to the rest of the clients that may be connected to this user | |
cc00aa23 | 412 | if (m_pUser->IsChan(sTarget)) { |
9170991a | 413 | vector<CClient*>& vClients = m_pUser->GetClients(); |
414 | ||
415 | for (unsigned int a = 0; a < vClients.size(); a++) { | |
416 | CClient* pClient = vClients[a]; | |
417 | ||
418 | if (pClient != this) { | |
419 | pClient->PutClient(":" + GetNickMask() + " PRIVMSG " + sTarget + " :\001" + sCTCP + "\001"); | |
420 | } | |
421 | } | |
422 | } | |
2cd52ad3 | 423 | } else { |
b740d7cc | 424 | MODULECALL(OnUserCTCP(sTarget, sCTCP), m_pUser, this, return); |
2cd52ad3 | 425 | } |
426 | ||
538d3ece | 427 | PutIRC("PRIVMSG " + sTarget + " :\001" + sCTCP + "\001"); |
428 | return; | |
429 | } | |
430 | ||
b757a318 | 431 | if (sTarget.TrimPrefix(m_pUser->GetStatusPrefix())) { |
5237a247 | 432 | if (sTarget.Equals("status")) { |
b757a318 | 433 | UserCommand(sMsg); |
434 | } else { | |
b757a318 | 435 | CALLMOD(sTarget, this, m_pUser, OnModCommand(sMsg)); |
b757a318 | 436 | } |
538d3ece | 437 | return; |
438 | } | |
439 | ||
b740d7cc | 440 | MODULECALL(OnUserMsg(sTarget, sMsg), m_pUser, this, return); |
4c33f794 | 441 | |
b50d944b | 442 | if (!GetIRCSock()) { |
f1e6a41d | 443 | // Some lagmeters do a PRIVMSG to their own nick, ignore those. |
5237a247 | 444 | if (!sTarget.Equals(m_sNick)) |
f1e6a41d | 445 | PutStatus("Your message to [" + sTarget + "] got lost, " |
446 | "you are not connected to IRC!"); | |
c4f1eb3d | 447 | return; |
448 | } | |
449 | ||
4c33f794 | 450 | CChan* pChan = m_pUser->FindChan(sTarget); |
451 | ||
538d3ece | 452 | if ((pChan) && (pChan->KeepBuffer())) { |
f601db2c | 453 | pChan->AddBuffer(":" + GetNickMask() + " PRIVMSG " + sTarget + " :" + m_pUser->AddTimestamp(sMsg)); |
538d3ece | 454 | } |
455 | ||
538d3ece | 456 | PutIRC("PRIVMSG " + sTarget + " :" + sMsg); |
aaec84a3 | 457 | |
458 | // Relay to the rest of the clients that may be connected to this user | |
459 | ||
cc00aa23 | 460 | if (m_pUser->IsChan(sTarget)) { |
712d4006 | 461 | vector<CClient*>& vClients = m_pUser->GetClients(); |
aaec84a3 | 462 | |
712d4006 | 463 | for (unsigned int a = 0; a < vClients.size(); a++) { |
464 | CClient* pClient = vClients[a]; | |
aaec84a3 | 465 | |
712d4006 | 466 | if (pClient != this) { |
467 | pClient->PutClient(":" + GetNickMask() + " PRIVMSG " + sTarget + " :" + sMsg); | |
aaec84a3 | 468 | } |
469 | } | |
470 | } | |
471 | ||
538d3ece | 472 | return; |
473 | } | |
474 | ||
475 | PutIRC(sLine); | |
476 | } | |
477 | ||
712d4006 | 478 | void CClient::SetNick(const CString& s) { |
03e34ac6 | 479 | m_sNick = s; |
538d3ece | 480 | } |
481 | ||
f99e5190 | 482 | const CIRCSock* CClient::GetIRCSock() const { |
483 | return m_pUser->GetIRCSock(); | |
484 | } | |
485 | ||
486 | CIRCSock* CClient::GetIRCSock() { | |
487 | return m_pUser->GetIRCSock(); | |
488 | } | |
489 | ||
f74f501d | 490 | void CClient::StatusCTCP(const CString& sLine) { |
491 | CString sCommand = sLine.Token(0); | |
492 | ||
5237a247 | 493 | if (sCommand.Equals("PING")) { |
f74f501d | 494 | PutStatusNotice("\001PING " + sLine.Token(1, true) + "\001"); |
5237a247 | 495 | } else if (sCommand.Equals("VERSION")) { |
f74f501d | 496 | PutStatusNotice("\001VERSION " + CZNC::GetTag() + "\001"); |
497 | } | |
498 | } | |
499 | ||
712d4006 | 500 | bool CClient::SendMotd() { |
d2e72850 | 501 | const VCString& vsMotd = CZNC::Get().GetMotd(); |
502 | ||
503 | if (!vsMotd.size()) { | |
504 | return false; | |
505 | } | |
506 | ||
507 | for (unsigned int a = 0; a < vsMotd.size(); a++) { | |
82129aa2 | 508 | PutStatusNotice(m_pUser->ExpandString(vsMotd[a])); |
d2e72850 | 509 | } |
510 | ||
511 | return true; | |
512 | } | |
513 | ||
712d4006 | 514 | void CClient::AuthUser() { |
8af157cc | 515 | if (!m_bGotNick || !m_bGotUser || !m_bGotPass || m_bInCap || IsAttached()) |
f6f4e6a6 | 516 | return; |
517 | ||
be354f11 | 518 | m_spAuth = new CClientAuth(this, m_sUser, m_sPass); |
519 | ||
31feec2c | 520 | CZNC::Get().AuthUser(m_spAuth); |
be354f11 | 521 | } |
522 | ||
5e0c652b | 523 | CClientAuth::CClientAuth(CClient* pClient, const CString& sUsername, const CString& sPassword) |
4e31d492 | 524 | : CAuthBase(sUsername, sPassword, pClient) { |
5e0c652b | 525 | m_pClient = pClient; |
526 | } | |
527 | ||
cbc27f5b | 528 | void CClientAuth::RefusedLogin(const CString& sReason) { |
be354f11 | 529 | if (m_pClient) { |
530 | m_pClient->RefuseLogin(sReason); | |
531 | } | |
cbc27f5b | 532 | } |
533 | ||
4e31d492 | 534 | CString CAuthBase::GetRemoteIP() const { |
535 | if (m_pSock) | |
536 | return m_pSock->GetRemoteIP(); | |
537 | return ""; | |
538 | } | |
539 | ||
b0e59f12 | 540 | void CAuthBase::Invalidate() { |
541 | m_pSock = NULL; | |
542 | } | |
543 | ||
544 | void CAuthBase::AcceptLogin(CUser& User) { | |
545 | if (m_pSock) { | |
546 | AcceptedLogin(User); | |
547 | Invalidate(); | |
548 | } | |
549 | } | |
550 | ||
cbc27f5b | 551 | void CAuthBase::RefuseLogin(const CString& sReason) { |
b0e59f12 | 552 | if (!m_pSock) |
553 | return; | |
554 | ||
9c92d93a | 555 | CUser* pUser = CZNC::Get().FindUser(GetUsername()); |
e5ecd9c9 | 556 | |
0015098a | 557 | // If the username is valid, notify that user that someone tried to |
558 | // login. Use sReason because there are other reasons than "wrong | |
559 | // password" for a login to be rejected (e.g. fail2ban). | |
560 | if (pUser) { | |
d66698e9 | 561 | pUser->PutStatus("A client from [" + GetRemoteIP() + "] attempted " |
562 | "to login as you, but was rejected [" + sReason + "]."); | |
0015098a | 563 | } |
564 | ||
1023d868 | 565 | GLOBALMODULECALL(OnFailedLogin(GetUsername(), GetRemoteIP()), NULL, NULL, NOTHING); |
cbc27f5b | 566 | RefusedLogin(sReason); |
b0e59f12 | 567 | Invalidate(); |
be354f11 | 568 | } |
569 | ||
570 | void CClient::RefuseLogin(const CString& sReason) { | |
571 | PutStatus("Bad username and/or password."); | |
02beef2e | 572 | PutClient(":irc.znc.in 464 " + GetNick() + " :" + sReason); |
1a859f38 | 573 | Close(Csock::CLT_AFTERWRITE); |
be354f11 | 574 | } |
538d3ece | 575 | |
cbc27f5b | 576 | void CClientAuth::AcceptedLogin(CUser& User) { |
be354f11 | 577 | if (m_pClient) { |
578 | m_pClient->AcceptLogin(User); | |
579 | } | |
580 | } | |
538d3ece | 581 | |
be354f11 | 582 | void CClient::AcceptLogin(CUser& User) { |
583 | m_sPass = ""; | |
584 | m_pUser = &User; | |
585 | ||
edb6c42d | 586 | // Set our proper timeout and set back our proper timeout mode |
587 | // (constructor set a different timeout and mode) | |
d2f3b8c5 | 588 | SetTimeout(540, TMO_READ); |
e1bf2d21 | 589 | |
be354f11 | 590 | SetSockName("USR::" + m_pUser->GetUserName()); |
591 | ||
be354f11 | 592 | m_pUser->UserConnected(this); |
593 | ||
594 | SendMotd(); | |
d2e72850 | 595 | |
1023d868 | 596 | MODULECALL(OnClientLogin(), m_pUser, this, NOTHING); |
538d3ece | 597 | } |
598 | ||
edb6c42d | 599 | void CClient::Timeout() { |
e1bf2d21 | 600 | PutClient("ERROR :Closing link [Timeout]"); |
e1bf2d21 | 601 | } |
602 | ||
712d4006 | 603 | void CClient::Connected() { |
235b10c2 | 604 | DEBUG(GetSockName() << " == Connected();"); |
538d3ece | 605 | } |
606 | ||
712d4006 | 607 | void CClient::ConnectionRefused() { |
235b10c2 | 608 | DEBUG(GetSockName() << " == ConnectionRefused()"); |
538d3ece | 609 | } |
610 | ||
712d4006 | 611 | void CClient::Disconnected() { |
235b10c2 | 612 | DEBUG(GetSockName() << " == Disconnected()"); |
e03604de | 613 | if (m_pUser) { |
aaec84a3 | 614 | m_pUser->UserDisconnected(this); |
538d3ece | 615 | } |
616 | ||
1023d868 | 617 | MODULECALL(OnClientDisconnect(), m_pUser, this, NOTHING); |
538d3ece | 618 | } |
619 | ||
18ce52e3 | 620 | void CClient::ReachedMaxBuffer() { |
235b10c2 | 621 | DEBUG(GetSockName() << " == ReachedMaxBuffer()"); |
18ce52e3 | 622 | if (IsAttached()) { |
623 | PutClient("ERROR :Closing link [Too long raw line]"); | |
624 | } | |
625 | Close(); | |
626 | } | |
627 | ||
712d4006 | 628 | void CClient::BouncedOff() { |
57f66b5e | 629 | PutStatusNotice("You are being disconnected because another user just authenticated as you."); |
1a859f38 | 630 | Close(Csock::CLT_AFTERWRITE); |
538d3ece | 631 | } |
632 | ||
712d4006 | 633 | void CClient::PutIRC(const CString& sLine) { |
e87f440d | 634 | m_pUser->PutIRC(sLine); |
538d3ece | 635 | } |
636 | ||
712d4006 | 637 | void CClient::PutClient(const CString& sLine) { |
235b10c2 | 638 | DEBUG("(" << ((m_pUser) ? m_pUser->GetUserName() : GetRemoteIP()) << ") ZNC -> CLI [" << sLine << "]"); |
79aaf3d5 | 639 | Write(sLine + "\r\n"); |
538d3ece | 640 | } |
641 | ||
712d4006 | 642 | void CClient::PutStatusNotice(const CString& sLine) { |
66389db9 | 643 | PutModNotice("status", sLine); |
644 | } | |
645 | ||
fd92e65b | 646 | unsigned int CClient::PutStatus(const CTable& table) { |
647 | unsigned int idx = 0; | |
648 | CString sLine; | |
649 | while (table.GetLine(idx++, sLine)) | |
650 | PutStatus(sLine); | |
651 | return idx - 1; | |
652 | } | |
653 | ||
712d4006 | 654 | void CClient::PutStatus(const CString& sLine) { |
538d3ece | 655 | PutModule("status", sLine); |
656 | } | |
657 | ||
712d4006 | 658 | void CClient::PutModNotice(const CString& sModule, const CString& sLine) { |
66389db9 | 659 | if (!m_pUser) { |
660 | return; | |
661 | } | |
662 | ||
235b10c2 | 663 | DEBUG("(" << m_pUser->GetUserName() << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() + ((sModule.empty()) ? "status" : sModule) + "!znc@znc.in NOTICE " << GetNick() << " :" << sLine << "]"); |
02beef2e | 664 | Write(":" + m_pUser->GetStatusPrefix() + ((sModule.empty()) ? "status" : sModule) + "!znc@znc.in NOTICE " + GetNick() + " :" + sLine + "\r\n"); |
66389db9 | 665 | } |
666 | ||
712d4006 | 667 | void CClient::PutModule(const CString& sModule, const CString& sLine) { |
538d3ece | 668 | if (!m_pUser) { |
669 | return; | |
670 | } | |
671 | ||
235b10c2 | 672 | DEBUG("(" << m_pUser->GetUserName() << ") ZNC -> CLI [:" + m_pUser->GetStatusPrefix() + ((sModule.empty()) ? "status" : sModule) + "!znc@znc.in PRIVMSG " << GetNick() << " :" << sLine << "]"); |
02beef2e | 673 | Write(":" + m_pUser->GetStatusPrefix() + ((sModule.empty()) ? "status" : sModule) + "!znc@znc.in PRIVMSG " + GetNick() + " :" + sLine + "\r\n"); |
538d3ece | 674 | } |
675 | ||
9b671ee9 | 676 | CString CClient::GetNick(bool bAllowIRCNick) const { |
beb5b49b | 677 | CString sRet; |
538d3ece | 678 | |
b50d944b | 679 | if ((bAllowIRCNick) && (IsAttached()) && (GetIRCSock())) { |
680 | sRet = GetIRCSock()->GetNick(); | |
538d3ece | 681 | } |
682 | ||
683 | return (sRet.empty()) ? m_sNick : sRet; | |
684 | } | |
685 | ||
712d4006 | 686 | CString CClient::GetNickMask() const { |
b50d944b | 687 | if (GetIRCSock() && GetIRCSock()->IsAuthed()) { |
688 | return GetIRCSock()->GetNickMask(); | |
538d3ece | 689 | } |
690 | ||
341263f9 | 691 | CString sHost = m_pUser->GetBindHost(); |
3c1e610c | 692 | if (sHost.empty()) { |
02beef2e | 693 | sHost = "irc.znc.in"; |
3c1e610c | 694 | } |
695 | ||
696 | return GetNick() + "!" + m_pUser->GetIdent() + "@" + sHost; | |
538d3ece | 697 | } |
99ea9c6f | 698 | |
699 | void CClient::RespondCap(const CString& sResponse) | |
700 | { | |
701 | PutClient(":irc.znc.in CAP " + GetNick() + " " + sResponse); | |
702 | } | |
703 | ||
704 | void CClient::HandleCap(const CString& sLine) | |
705 | { | |
706 | CString sSubCmd = sLine.Token(1); | |
707 | ||
708 | if (sSubCmd.Equals("LS")) { | |
9d99e4cc | 709 | SCString ssOfferCaps; |
1023d868 | 710 | GLOBALMODULECALL(OnClientCapLs(ssOfferCaps), m_pUser, this, NOTHING); |
9d99e4cc | 711 | CString sRes; |
712 | for (SCString::iterator i = ssOfferCaps.begin(); i != ssOfferCaps.end(); ++i) { | |
713 | sRes += *i + " "; | |
714 | } | |
715 | RespondCap("LS :" + sRes + "userhost-in-names multi-prefix"); | |
99ea9c6f | 716 | m_bInCap = true; |
717 | } else if (sSubCmd.Equals("END")) { | |
718 | m_bInCap = false; | |
40b5a5cd KF |
719 | if (!IsAttached()) { |
720 | if (!m_pUser && m_bGotUser && !m_bGotPass) { | |
721 | SendRequiredPasswordNotice(); | |
722 | } else { | |
723 | AuthUser(); | |
724 | } | |
725 | } | |
99ea9c6f | 726 | } else if (sSubCmd.Equals("REQ")) { |
d7393315 | 727 | VCString vsTokens; |
728 | VCString::iterator it; | |
22a641a0 | 729 | sLine.Token(2, true).TrimPrefix_n(":").Split(" ", vsTokens, false); |
d7393315 | 730 | |
731 | for (it = vsTokens.begin(); it != vsTokens.end(); ++it) { | |
e00fa217 | 732 | bool bVal = true; |
9d99e4cc | 733 | CString sCap = *it; |
734 | if (sCap.TrimPrefix("-")) | |
e00fa217 | 735 | bVal = false; |
736 | ||
9ae959b8 | 737 | bool bAccepted = ("multi-prefix" == sCap) || ("userhost-in-names" == sCap); |
3dd57bdf | 738 | GLOBALMODULECALL(IsClientCapSupported(sCap, bVal), m_pUser, this, bAccepted = true); |
9ae959b8 | 739 | |
740 | if (!bAccepted) { | |
d7393315 | 741 | // Some unsupported capability is requested |
742 | RespondCap("NAK :" + sLine.Token(2, true).TrimPrefix_n(":")); | |
743 | return; | |
744 | } | |
745 | } | |
746 | ||
747 | // All is fine, we support what was requested | |
9d99e4cc | 748 | for (it = vsTokens.begin(); it != vsTokens.end(); ++it) { |
749 | bool bVal = true; | |
750 | if (it->TrimPrefix("-")) | |
751 | bVal = false; | |
752 | ||
753 | if ("multi-prefix" == *it) { | |
754 | m_bNamesx = bVal; | |
755 | } else if ("userhost-in-names" == *it) { | |
756 | m_bUHNames = bVal; | |
9d99e4cc | 757 | } |
1023d868 | 758 | GLOBALMODULECALL(OnClientCapRequest(*it, bVal), m_pUser, this, NOTHING); |
9d99e4cc | 759 | |
760 | if (bVal) { | |
761 | m_ssAcceptedCaps.insert(*it); | |
762 | } else { | |
763 | m_ssAcceptedCaps.erase(*it); | |
764 | } | |
765 | } | |
766 | ||
d7393315 | 767 | RespondCap("ACK :" + sLine.Token(2, true).TrimPrefix_n(":")); |
99ea9c6f | 768 | } else if (sSubCmd.Equals("LIST")) { |
d7393315 | 769 | CString sList = ""; |
9d99e4cc | 770 | for (SCString::iterator i = m_ssAcceptedCaps.begin(); i != m_ssAcceptedCaps.end(); ++i) { |
771 | sList += *i + " "; | |
772 | } | |
d7393315 | 773 | RespondCap("LIST :" + sList.TrimSuffix_n(" ")); |
88daf2fa | 774 | } else if (sSubCmd.Equals("CLEAR")) { |
775 | SCString ssRemoved; | |
776 | for (SCString::iterator i = m_ssAcceptedCaps.begin(); i != m_ssAcceptedCaps.end(); ++i) { | |
777 | bool bRemoving = false; | |
778 | GLOBALMODULECALL(IsClientCapSupported(*i, false), m_pUser, this, bRemoving = true); | |
779 | if (bRemoving) { | |
1023d868 | 780 | GLOBALMODULECALL(OnClientCapRequest(*i, false), m_pUser, this, NOTHING); |
88daf2fa | 781 | ssRemoved.insert(*i); |
782 | } | |
783 | } | |
784 | if (m_bNamesx) { | |
785 | m_bNamesx = false; | |
786 | ssRemoved.insert("multi-prefix"); | |
787 | } | |
788 | if (m_bUHNames) { | |
789 | m_bUHNames = false; | |
790 | ssRemoved.insert("userhost-in-names"); | |
791 | } | |
792 | CString sList = ""; | |
793 | for (SCString::iterator i = ssRemoved.begin(); i != ssRemoved.end(); ++i) { | |
794 | m_ssAcceptedCaps.erase(*i); | |
795 | sList += "-" + *i + " "; | |
796 | } | |
797 | RespondCap("ACK :" + sList.TrimSuffix_n(" ")); | |
99ea9c6f | 798 | } |
799 | } |