]>
jfr.im git - irc/rizon/znc.git/blob - IRCSock.cpp
2 * Copyright (C) 2004-2011 See the AUTHORS file for details.
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.
16 // These are used in OnGeneralCTCP()
17 const time_t CIRCSock::m_uCTCPFloodTime
= 5;
18 const unsigned int CIRCSock::m_uCTCPFloodCount
= 5;
20 CIRCSock::CIRCSock(CUser
* pUser
) : CZNCSock() {
26 m_Nick
.SetIdent(pUser
->GetIdent());
27 m_Nick
.SetHost(pUser
->GetBindHost());
34 m_sPermModes
= "qaohv";
35 m_mueChanModes
['b'] = ListArg
;
36 m_mueChanModes
['e'] = ListArg
;
37 m_mueChanModes
['I'] = ListArg
;
38 m_mueChanModes
['k'] = HasArg
;
39 m_mueChanModes
['l'] = ArgWhenSet
;
40 m_mueChanModes
['p'] = NoArg
;
41 m_mueChanModes
['s'] = NoArg
;
42 m_mueChanModes
['t'] = NoArg
;
43 m_mueChanModes
['i'] = NoArg
;
44 m_mueChanModes
['n'] = NoArg
;
46 pUser
->SetIRCSocket(this);
48 // RFC says a line can have 512 chars max, but we don't care ;)
49 SetMaxBufferThreshold(1024);
52 CIRCSock::~CIRCSock() {
54 MODULECALL(OnIRCConnectionError(this), m_pUser
, NULL
, NOTHING
);
57 const vector
<CChan
*>& vChans
= m_pUser
->GetChans();
58 for (unsigned int a
= 0; a
< vChans
.size(); a
++) {
62 m_pUser
->IRCDisconnected();
64 for (map
<CString
, CChan
*>::iterator a
= m_msChans
.begin(); a
!= m_msChans
.end(); ++a
) {
70 GetUser()->AddBytesRead(GetBytesRead());
71 GetUser()->AddBytesWritten(GetBytesWritten());
74 void CIRCSock::Quit(const CString
& sQuitMsg
) {
79 if (!sQuitMsg
.empty()) {
80 PutIRC("QUIT :" + sQuitMsg
);
82 PutIRC("QUIT :" + m_pUser
->ExpandString(m_pUser
->GetQuitMsg()));
84 Close(CLT_AFTERWRITE
);
87 void CIRCSock::ReadLine(const CString
& sData
) {
88 CString sLine
= sData
;
90 sLine
.TrimRight("\n\r");
92 DEBUG("(" << m_pUser
->GetUserName() << ") IRC -> ZNC [" << sLine
<< "]");
94 MODULECALL(OnRaw(sLine
), m_pUser
, NULL
, return);
96 if (sLine
.Equals("PING ", false, 5)) {
97 // Generate a reply and don't forward this to any user,
98 // we don't want any PING forwarded
99 PutIRC("PONG " + sLine
.substr(5));
101 } else if (sLine
.Token(1).Equals("PONG")) {
102 // Block PONGs, we already responded to the pings
104 } else if (sLine
.Equals("ERROR ", false, 6)) {
105 //ERROR :Closing Link: nick[24.24.24.24] (Excess Flood)
106 CString
sError(sLine
.substr(6));
108 if (sError
.Left(1) == ":") {
112 m_pUser
->PutStatus("Error from Server [" + sError
+ "]");
116 CString sCmd
= sLine
.Token(1);
118 if ((sCmd
.length() == 3) && (isdigit(sCmd
[0])) && (isdigit(sCmd
[1])) && (isdigit(sCmd
[2]))) {
119 CString sServer
= sLine
.Token(0); sServer
.LeftChomp();
120 unsigned int uRaw
= sCmd
.ToUInt();
121 CString sNick
= sLine
.Token(2);
122 CString sRest
= sLine
.Token(3, true);
125 case 1: { // :irc.server.com 001 nick :Welcome to the Internet Relay Network nick
126 if (m_bAuthed
&& sServer
== "irc.znc.in") {
127 // m_bAuthed == true => we already received another 001 => we might be in a traffic loop
128 m_pUser
->PutStatus("ZNC seems to be connected to itself, disconnecting...");
133 m_pUser
->SetIRCServer(sServer
);
134 SetTimeout(540, TMO_READ
); // Now that we are connected, let nature take its course
135 PutIRC("WHO " + sNick
);
138 m_pUser
->PutStatus("Connected!");
140 vector
<CClient
*>& vClients
= m_pUser
->GetClients();
142 for (unsigned int a
= 0; a
< vClients
.size(); a
++) {
143 CClient
* pClient
= vClients
[a
];
144 CString sClientNick
= pClient
->GetNick(false);
146 if (!sClientNick
.Equals(sNick
)) {
147 // If they connected with a nick that doesn't match the one we got on irc, then we need to update them
148 pClient
->PutClient(":" + sClientNick
+ "!" + m_Nick
.GetIdent() + "@" + m_Nick
.GetHost() + " NICK :" + sNick
);
154 MODULECALL(OnIRCConnected(), m_pUser
, NULL
, NOTHING
);
156 m_pUser
->ClearRawBuffer();
157 m_pUser
->AddRawBuffer(":" + sServer
+ " " + sCmd
+ " ", " " + sRest
);
162 ParseISupport(sRest
);
163 m_pUser
->UpdateExactRawBuffer(":" + sServer
+ " " + sCmd
+ " ", " " + sRest
);
165 case 10: { // :irc.server.com 010 nick <hostname> <port> :<info>
166 CString sHost
= sRest
.Token(0);
167 CString sPort
= sRest
.Token(1);
168 CString sInfo
= sRest
.Token(2, true).TrimPrefix_n(":");
169 m_pUser
->PutStatus("Server [" + m_pUser
->GetCurrentServer()->GetString(false) +
170 "] redirects us to [" + sHost
+ ":" + sPort
+ "] with reason [" + sInfo
+ "]");
171 m_pUser
->PutStatus("Perhaps you want to add it as a new server.");
172 // Don't send server redirects to the client
178 case 250: // highest connection count
179 case 251: // user count
180 case 252: // oper count
181 case 254: // channel count
182 case 255: // client count
183 case 265: // local users
184 case 266: // global users
185 m_pUser
->UpdateRawBuffer(":" + sServer
+ " " + sCmd
+ " ", " " + sRest
);
188 m_pUser
->SetIRCAway(false);
191 m_pUser
->SetIRCAway(true);
195 CChan
* pChan
= m_pUser
->FindChan(sRest
.Token(0));
198 pChan
->SetModes(sRest
.Token(1, true));
200 // We don't SetModeKnown(true) here,
201 // because a 329 will follow
202 if (!pChan
->IsModeKnown()) {
203 // When we JOIN, we send a MODE
204 // request. This makes sure the
205 // reply isn't forwarded.
213 CChan
* pChan
= m_pUser
->FindChan(sRest
.Token(0));
216 unsigned long ulDate
= sLine
.Token(4).ToULong();
217 pChan
->SetCreationDate(ulDate
);
219 if (!pChan
->IsModeKnown()) {
220 pChan
->SetModeKnown(true);
221 // When we JOIN, we send a MODE
222 // request. This makes sure the
223 // reply isn't forwarded.
230 // :irc.server.com 331 yournick #chan :No topic is set.
231 CChan
* pChan
= m_pUser
->FindChan(sLine
.Token(3));
240 // :irc.server.com 332 yournick #chan :This is a topic
241 CChan
* pChan
= m_pUser
->FindChan(sLine
.Token(3));
244 CString sTopic
= sLine
.Token(4, true);
246 pChan
->SetTopic(sTopic
);
252 // :irc.server.com 333 yournick #chan setternick 1112320796
253 CChan
* pChan
= m_pUser
->FindChan(sLine
.Token(3));
256 sNick
= sLine
.Token(4);
257 unsigned long ulDate
= sLine
.Token(5).ToULong();
259 pChan
->SetTopicOwner(sNick
);
260 pChan
->SetTopicDate(ulDate
);
266 // :irc.yourserver.com 352 yournick #chan ident theirhost.com irc.theirserver.com theirnick H :0 Real Name
267 sServer
= sLine
.Token(0);
268 sNick
= sLine
.Token(7);
269 CString sIdent
= sLine
.Token(4);
270 CString sHost
= sLine
.Token(5);
274 if (sNick
.Equals(GetNick())) {
275 m_Nick
.SetIdent(sIdent
);
276 m_Nick
.SetHost(sHost
);
279 m_pUser
->SetIRCNick(m_Nick
);
280 m_pUser
->SetIRCServer(sServer
);
282 const vector
<CChan
*>& vChans
= m_pUser
->GetChans();
284 for (unsigned int a
= 0; a
< vChans
.size(); a
++) {
285 vChans
[a
]->OnWho(sNick
, sIdent
, sHost
);
292 // Todo: allow for non @+= server msgs
293 CChan
* pChan
= m_pUser
->FindChan(sRest
.Token(1));
294 // If we don't know that channel, some client might have
295 // requested a /names for it and we really should forward this.
297 CString sNicks
= sRest
.Token(2, true);
298 if (sNicks
.Left(1) == ":") {
302 pChan
->AddNicks(sNicks
);
305 ForwardRaw353(sLine
);
307 // We forwarded it already, so return
310 case 366: { // end of names list
311 m_pUser
->PutUser(sLine
); // First send them the raw
313 // :irc.server.com 366 nick #chan :End of /NAMES list.
314 CChan
* pChan
= m_pUser
->FindChan(sRest
.Token(0));
318 // If we are the only one in the chan, set our default modes
319 if (pChan
->GetNickCount() == 1) {
320 CString sModes
= pChan
->GetDefaultModes();
322 if (sModes
.empty()) {
323 sModes
= m_pUser
->GetDefaultChanModes();
326 if (!sModes
.empty()) {
327 PutIRC("MODE " + pChan
->GetName() + " " + sModes
);
333 return; // return so we don't send them the raw twice
335 case 375: // begin motd
336 case 422: // MOTD File is missing
337 m_pUser
->ClearMotdBuffer();
339 case 376: // end motd
340 m_pUser
->AddMotdBuffer(":" + sServer
+ " " + sCmd
+ " ", " " + sRest
);
343 // :irc.server.net 437 * badnick :Nick/channel is temporarily unavailable
344 // :irc.server.net 437 mynick badnick :Nick/channel is temporarily unavailable
345 // :irc.server.net 437 mynick badnick :Cannot change nickname while banned on channel
346 if (m_pUser
->IsChan(sRest
.Token(0)) || sNick
!= "*")
348 case 432: // :irc.server.com 432 * nick :Erroneous Nickname: Illegal characters
350 CString sBadNick
= sRest
.Token(0);
353 SendAltNick(sBadNick
);
359 // :irc.server.com 451 CAP :You have not registered
360 // Servers that dont support CAP will give us this error, dont send it to the client
361 if (sNick
.Equals("CAP"))
364 // :irc.unreal.net 470 mynick [Link] #chan1 has become full, so you are automatically being transferred to the linked channel #chan2
365 // :mccaffrey.freenode.net 470 mynick #electronics ##electronics :Forwarding to another channel
367 // freenode style numeric
368 CChan
* pChan
= m_pUser
->FindChan(sRest
.Token(0));
370 // unreal style numeric
371 pChan
= m_pUser
->FindChan(sRest
.Token(1));
375 m_pUser
->PutStatus("Channel [" + pChan
->GetName() + "] is linked to "
376 "another channel and was thus disabled.");
382 CNick
Nick(sLine
.Token(0).LeftChomp_n());
383 sCmd
= sLine
.Token(1);
384 CString sRest
= sLine
.Token(2, true);
386 if (sCmd
.Equals("NICK")) {
387 CString sNewNick
= sRest
;
388 bool bIsVisible
= false;
390 if (sNewNick
.Left(1) == ":") {
391 sNewNick
.LeftChomp();
394 vector
<CChan
*> vFoundChans
;
395 const vector
<CChan
*>& vChans
= m_pUser
->GetChans();
397 for (unsigned int a
= 0; a
< vChans
.size(); a
++) {
398 CChan
* pChan
= vChans
[a
];
400 if (pChan
->ChangeNick(Nick
.GetNick(), sNewNick
)) {
401 vFoundChans
.push_back(pChan
);
403 if (!pChan
->IsDetached()) {
409 // Todo: use nick compare function here
410 if (Nick
.GetNick().Equals(GetNick())) {
411 // We are changing our own nick, the clients always must see this!
416 MODULECALL(OnNick(Nick
, sNewNick
, vFoundChans
), m_pUser
, NULL
, NOTHING
);
421 } else if (sCmd
.Equals("QUIT")) {
422 CString sMessage
= sRest
;
423 bool bIsVisible
= false;
425 if (sMessage
.Left(1) == ":") {
426 sMessage
.LeftChomp();
429 // :nick!ident@host.com QUIT :message
431 if (Nick
.GetNick().Equals(GetNick())) {
432 m_pUser
->PutStatus("You quit [" + sMessage
+ "]");
433 // We don't call module hooks and we don't
434 // forward this quit to clients (Some clients
435 // disconnect if they receive such a QUIT)
439 vector
<CChan
*> vFoundChans
;
440 const vector
<CChan
*>& vChans
= m_pUser
->GetChans();
442 for (unsigned int a
= 0; a
< vChans
.size(); a
++) {
443 CChan
* pChan
= vChans
[a
];
445 if (pChan
->RemNick(Nick
.GetNick())) {
446 vFoundChans
.push_back(pChan
);
448 if (!pChan
->IsDetached()) {
454 MODULECALL(OnQuit(Nick
, sMessage
, vFoundChans
), m_pUser
, NULL
, NOTHING
);
459 } else if (sCmd
.Equals("JOIN")) {
460 CString sChan
= sRest
.Token(0);
461 if (sChan
.Left(1) == ":") {
467 // Todo: use nick compare function
468 if (Nick
.GetNick().Equals(GetNick())) {
469 m_pUser
->AddChan(sChan
, false);
470 pChan
= m_pUser
->FindChan(sChan
);
473 pChan
->SetIsOn(true);
474 PutIRC("MODE " + sChan
);
477 pChan
= m_pUser
->FindChan(sChan
);
481 pChan
->AddNick(Nick
.GetNickMask());
482 MODULECALL(OnJoin(Nick
.GetNickMask(), *pChan
), m_pUser
, NULL
, NOTHING
);
484 if (pChan
->IsDetached()) {
488 } else if (sCmd
.Equals("PART")) {
489 CString sChan
= sRest
.Token(0);
490 if (sChan
.Left(1) == ":") {
493 CString sMsg
= sRest
.Token(1, true).TrimPrefix_n(":");
495 CChan
* pChan
= m_pUser
->FindChan(sChan
);
496 bool bDetached
= false;
498 pChan
->RemNick(Nick
.GetNick());
499 MODULECALL(OnPart(Nick
.GetNickMask(), *pChan
, sMsg
), m_pUser
, NULL
, NOTHING
);
501 if (pChan
->IsDetached())
505 // Todo: use nick compare function
506 if (Nick
.GetNick().Equals(GetNick())) {
507 m_pUser
->DelChan(sChan
);
511 * We use this boolean because
512 * m_pUser->DelChan() will delete this channel
513 * and thus we would dereference an
514 * already-freed pointer!
519 } else if (sCmd
.Equals("MODE")) {
520 CString sTarget
= sRest
.Token(0);
521 CString sModes
= sRest
.Token(1, true);
522 if (sModes
.Left(1) == ":")
523 sModes
= sModes
.substr(1);
525 CChan
* pChan
= m_pUser
->FindChan(sTarget
);
527 pChan
->ModeChange(sModes
, &Nick
);
529 if (pChan
->IsDetached()) {
532 } else if (sTarget
== m_Nick
.GetNick()) {
533 CString sModeArg
= sModes
.Token(0);
535 /* no module call defined (yet?)
536 MODULECALL(OnRawUserMode(*pOpNick, *this, sModeArg, sArgs), m_pUser, NULL, );
538 for (unsigned int a
= 0; a
< sModeArg
.size(); a
++) {
539 const unsigned char& uMode
= sModeArg
[a
];
543 } else if (uMode
== '-') {
547 m_scUserModes
.insert(uMode
);
549 m_scUserModes
.erase(uMode
);
554 } else if (sCmd
.Equals("KICK")) {
555 // :opnick!ident@host.com KICK #chan nick :msg
556 CString sChan
= sRest
.Token(0);
557 CString sKickedNick
= sRest
.Token(1);
558 CString sMsg
= sRest
.Token(2, true);
561 CChan
* pChan
= m_pUser
->FindChan(sChan
);
564 MODULECALL(OnKick(Nick
, sKickedNick
, *pChan
, sMsg
), m_pUser
, NULL
, NOTHING
);
565 // do not remove the nick till after the OnKick call, so modules
566 // can do Chan.FindNick or something to get more info.
567 pChan
->RemNick(sKickedNick
);
570 if (GetNick().Equals(sKickedNick
) && pChan
) {
571 pChan
->SetIsOn(false);
573 // Don't try to rejoin!
577 if ((pChan
) && (pChan
->IsDetached())) {
580 } else if (sCmd
.Equals("NOTICE")) {
581 // :nick!ident@host.com NOTICE #chan :Message
582 CString sTarget
= sRest
.Token(0);
583 CString sMsg
= sRest
.Token(1, true);
586 if (sMsg
.WildCmp("\001*\001")) {
590 if (sTarget
.Equals(GetNick())) {
591 if (OnCTCPReply(Nick
, sMsg
)) {
596 m_pUser
->PutUser(":" + Nick
.GetNickMask() + " NOTICE " + sTarget
+ " :\001" + sMsg
+ "\001");
599 if (sTarget
.Equals(GetNick())) {
600 if (OnPrivNotice(Nick
, sMsg
)) {
604 if (OnChanNotice(Nick
, sTarget
, sMsg
)) {
610 if (Nick
.GetNick().Equals(m_pUser
->GetIRCServer())) {
611 m_pUser
->PutUser(":" + Nick
.GetNick() + " NOTICE " + sTarget
+ " :" + sMsg
);
613 m_pUser
->PutUser(":" + Nick
.GetNickMask() + " NOTICE " + sTarget
+ " :" + sMsg
);
617 } else if (sCmd
.Equals("TOPIC")) {
618 // :nick!ident@host.com TOPIC #chan :This is a topic
619 CChan
* pChan
= m_pUser
->FindChan(sLine
.Token(2));
622 CString sTopic
= sLine
.Token(3, true);
625 MODULECALL(OnTopic(Nick
, *pChan
, sTopic
), m_pUser
, NULL
, return)
627 pChan
->SetTopicOwner(Nick
.GetNick());
628 pChan
->SetTopicDate((unsigned long) time(NULL
));
629 pChan
->SetTopic(sTopic
);
631 if (pChan
->IsDetached()) {
632 return; // Don't forward this
635 sLine
= ":" + Nick
.GetNickMask() + " TOPIC " + pChan
->GetName() + " :" + sTopic
;
637 } else if (sCmd
.Equals("PRIVMSG")) {
638 // :nick!ident@host.com PRIVMSG #chan :Message
639 CString sTarget
= sRest
.Token(0);
640 CString sMsg
= sRest
.Token(1, true);
642 if (sMsg
.Left(1) == ":") {
646 if (sMsg
.WildCmp("\001*\001")) {
650 if (sTarget
.Equals(GetNick())) {
651 if (OnPrivCTCP(Nick
, sMsg
)) {
655 if (OnChanCTCP(Nick
, sTarget
, sMsg
)) {
660 m_pUser
->PutUser(":" + Nick
.GetNickMask() + " PRIVMSG " + sTarget
+ " :\001" + sMsg
+ "\001");
663 if (sTarget
.Equals(GetNick())) {
664 if (OnPrivMsg(Nick
, sMsg
)) {
668 if (OnChanMsg(Nick
, sTarget
, sMsg
)) {
673 m_pUser
->PutUser(":" + Nick
.GetNickMask() + " PRIVMSG " + sTarget
+ " :" + sMsg
);
676 } else if (sCmd
.Equals("WALLOPS")) {
677 // :blub!dummy@rox-8DBEFE92 WALLOPS :this is a test
678 CString sMsg
= sRest
.Token(0, true);
680 if (sMsg
.Left(1) == ":") {
684 if (!m_pUser
->IsUserAttached()) {
685 m_pUser
->AddQueryBuffer(":" + Nick
.GetNickMask() + " WALLOPS ", ":" + m_pUser
->AddTimestamp(sMsg
), false);
687 } else if (sCmd
.Equals("CAP")) {
688 // CAPs are supported only before authorization.
690 // sRest.Token(0) is most likely "*". No idea why, the
691 // CAP spec don't mention this, but all implementations
692 // I've seen add this extra asterisk
693 CString sSubCmd
= sRest
.Token(1);
695 // If the caplist of a reply is too long, it's split
696 // into multiple replies. A "*" is prepended to show
697 // that the list was split into multiple replies.
698 // This is useful mainly for LS. For ACK and NAK
699 // replies, there's no real need for this, because
700 // we request only 1 capability per line.
701 // If we will need to support broken servers or will
702 // send several requests per line, need to delay ACK
703 // actions until all ACK lines are received and
704 // to recognize past request of NAK by 100 chars
707 if (sRest
.Token(2) == "*") {
708 sArgs
= sRest
.Token(3, true).TrimPrefix_n(":");
710 sArgs
= sRest
.Token(2, true).TrimPrefix_n(":");
713 if (sSubCmd
== "LS") {
715 VCString::iterator it
;
716 sArgs
.Split(" ", vsTokens
, false);
718 for (it
= vsTokens
.begin(); it
!= vsTokens
.end(); ++it
) {
719 if (OnServerCapAvailable(*it
) || *it
== "multi-prefix" || *it
== "userhost-in-names") {
720 m_ssPendingCaps
.insert(*it
);
723 } else if (sSubCmd
== "ACK") {
725 MODULECALL(OnServerCapResult(sArgs
, true), m_pUser
, NULL
, NOTHING
);
726 if ("multi-prefix" == sArgs
) {
728 } else if ("userhost-in-names" == sArgs
) {
731 m_ssAcceptedCaps
.insert(sArgs
);
732 } else if (sSubCmd
== "NAK") {
733 // This should work because there's no [known]
734 // capability with length of name more than 100 characters.
736 MODULECALL(OnServerCapResult(sArgs
, false), m_pUser
, NULL
, NOTHING
);
741 // Don't forward any CAP stuff to the client
746 m_pUser
->PutUser(sLine
);
749 void CIRCSock::SendNextCap() {
751 if (m_ssPendingCaps
.empty()) {
752 // We already got all needed ACK/NAK replies.
755 CString sCap
= *m_ssPendingCaps
.begin();
756 m_ssPendingCaps
.erase(m_ssPendingCaps
.begin());
757 PutIRC("CAP REQ :" + sCap
);
762 void CIRCSock::PauseCap() {
766 void CIRCSock::ResumeCap() {
771 bool CIRCSock::OnServerCapAvailable(const CString
& sCap
) {
772 MODULECALL(OnServerCapAvailable(sCap
), m_pUser
, NULL
, return true);
776 bool CIRCSock::OnCTCPReply(CNick
& Nick
, CString
& sMessage
) {
777 MODULECALL(OnCTCPReply(Nick
, sMessage
), m_pUser
, NULL
, return true);
782 bool CIRCSock::OnPrivCTCP(CNick
& Nick
, CString
& sMessage
) {
783 MODULECALL(OnPrivCTCP(Nick
, sMessage
), m_pUser
, NULL
, return true);
785 if (sMessage
.TrimPrefix("ACTION ")) {
786 MODULECALL(OnPrivAction(Nick
, sMessage
), m_pUser
, NULL
, return true);
788 if (!m_pUser
->IsUserAttached()) {
789 // If the user is detached, add to the buffer
790 m_pUser
->AddQueryBuffer(":" + Nick
.GetNickMask() + " PRIVMSG ", " :\001ACTION " + m_pUser
->AddTimestamp(sMessage
) + "\001");
793 sMessage
= "ACTION " + sMessage
;
796 // This handles everything which wasn't handled yet
797 return OnGeneralCTCP(Nick
, sMessage
);
800 bool CIRCSock::OnGeneralCTCP(CNick
& Nick
, CString
& sMessage
) {
801 const MCString
& mssCTCPReplies
= m_pUser
->GetCTCPReplies();
802 CString sQuery
= sMessage
.Token(0).AsUpper();
803 MCString::const_iterator it
= mssCTCPReplies
.find(sQuery
);
804 bool bHaveReply
= false;
807 if (it
!= mssCTCPReplies
.end()) {
808 sReply
= m_pUser
->ExpandString(it
->second
);
811 if (sReply
.empty()) {
816 if (!bHaveReply
&& !m_pUser
->IsUserAttached()) {
817 if (sQuery
== "VERSION") {
818 sReply
= CZNC::GetTag();
819 } else if (sQuery
== "PING") {
820 sReply
= sMessage
.Token(1, true);
824 if (!sReply
.empty()) {
825 time_t now
= time(NULL
);
826 // If the last CTCP is older than m_uCTCPFloodTime, reset the counter
827 if (m_lastCTCP
+ m_uCTCPFloodTime
< now
)
830 // If we are over the limit, don't reply to this CTCP
831 if (m_uNumCTCP
>= m_uCTCPFloodCount
) {
832 DEBUG("CTCP flood detected - not replying to query");
837 PutIRC("NOTICE " + Nick
.GetNick() + " :\001" + sQuery
+ " " + sReply
+ "\001");
844 bool CIRCSock::OnPrivNotice(CNick
& Nick
, CString
& sMessage
) {
845 MODULECALL(OnPrivNotice(Nick
, sMessage
), m_pUser
, NULL
, return true);
847 if (!m_pUser
->IsUserAttached()) {
848 // If the user is detached, add to the buffer
849 m_pUser
->AddQueryBuffer(":" + Nick
.GetNickMask() + " NOTICE ", " :" + m_pUser
->AddTimestamp(sMessage
));
855 bool CIRCSock::OnPrivMsg(CNick
& Nick
, CString
& sMessage
) {
856 MODULECALL(OnPrivMsg(Nick
, sMessage
), m_pUser
, NULL
, return true);
858 if (!m_pUser
->IsUserAttached()) {
859 // If the user is detached, add to the buffer
860 m_pUser
->AddQueryBuffer(":" + Nick
.GetNickMask() + " PRIVMSG ", " :" + m_pUser
->AddTimestamp(sMessage
));
866 bool CIRCSock::OnChanCTCP(CNick
& Nick
, const CString
& sChan
, CString
& sMessage
) {
867 CChan
* pChan
= m_pUser
->FindChan(sChan
);
869 MODULECALL(OnChanCTCP(Nick
, *pChan
, sMessage
), m_pUser
, NULL
, return true);
872 if (sMessage
.TrimPrefix("ACTION ")) {
873 MODULECALL(OnChanAction(Nick
, *pChan
, sMessage
), m_pUser
, NULL
, return true);
874 if (pChan
->KeepBuffer() || !m_pUser
->IsUserAttached() || pChan
->IsDetached()) {
875 pChan
->AddBuffer(":" + Nick
.GetNickMask() + " PRIVMSG " + sChan
+ " :\001ACTION " + m_pUser
->AddTimestamp(sMessage
) + "\001");
877 sMessage
= "ACTION " + sMessage
;
881 if (OnGeneralCTCP(Nick
, sMessage
))
884 return (pChan
&& pChan
->IsDetached());
887 bool CIRCSock::OnChanNotice(CNick
& Nick
, const CString
& sChan
, CString
& sMessage
) {
888 CChan
* pChan
= m_pUser
->FindChan(sChan
);
890 MODULECALL(OnChanNotice(Nick
, *pChan
, sMessage
), m_pUser
, NULL
, return true);
892 if (pChan
->KeepBuffer() || !m_pUser
->IsUserAttached() || pChan
->IsDetached()) {
893 pChan
->AddBuffer(":" + Nick
.GetNickMask() + " NOTICE " + sChan
+ " :" + m_pUser
->AddTimestamp(sMessage
));
897 return ((pChan
) && (pChan
->IsDetached()));
900 bool CIRCSock::OnChanMsg(CNick
& Nick
, const CString
& sChan
, CString
& sMessage
) {
901 CChan
* pChan
= m_pUser
->FindChan(sChan
);
903 MODULECALL(OnChanMsg(Nick
, *pChan
, sMessage
), m_pUser
, NULL
, return true);
905 if (pChan
->KeepBuffer() || !m_pUser
->IsUserAttached() || pChan
->IsDetached()) {
906 pChan
->AddBuffer(":" + Nick
.GetNickMask() + " PRIVMSG " + sChan
+ " :" + m_pUser
->AddTimestamp(sMessage
));
910 return ((pChan
) && (pChan
->IsDetached()));
913 void CIRCSock::PutIRC(const CString
& sLine
) {
914 DEBUG("(" << m_pUser
->GetUserName() << ") ZNC -> IRC [" << sLine
<< "]");
915 Write(sLine
+ "\r\n");
918 void CIRCSock::SetNick(const CString
& sNick
) {
919 m_Nick
.SetNick(sNick
);
920 m_pUser
->SetIRCNick(m_Nick
);
923 void CIRCSock::Connected() {
924 DEBUG(GetSockName() << " == Connected()");
926 CString sPass
= m_sPass
;
927 CString sNick
= m_pUser
->GetNick();
928 CString sIdent
= m_pUser
->GetIdent();
929 CString sRealName
= m_pUser
->GetRealName();
931 MODULECALL(OnIRCRegistration(sPass
, sNick
, sIdent
, sRealName
), m_pUser
, NULL
, return);
935 if (!sPass
.empty()) {
936 PutIRC("PASS " + sPass
);
939 PutIRC("NICK " + sNick
);
940 PutIRC("USER " + sIdent
+ " \"" + sIdent
+ "\" \"" + sIdent
+ "\" :" + sRealName
);
942 // SendAltNick() needs this
943 m_Nick
.SetNick(sNick
);
946 void CIRCSock::Disconnected() {
947 MODULECALL(OnIRCDisconnected(), m_pUser
, NULL
, NOTHING
);
949 DEBUG(GetSockName() << " == Disconnected()");
950 if (!m_pUser
->IsBeingDeleted() && m_pUser
->GetIRCConnectEnabled() &&
951 m_pUser
->GetServers().size() != 0) {
952 m_pUser
->PutStatus("Disconnected from IRC. Reconnecting...");
954 m_pUser
->ClearRawBuffer();
955 m_pUser
->ClearMotdBuffer();
959 // send a "reset user modes" cmd to the client.
960 // otherwise, on reconnect, it might think it still
961 // had user modes that it actually doesn't have.
963 for (set
<unsigned char>::const_iterator it
= m_scUserModes
.begin(); it
!= m_scUserModes
.end(); ++it
) {
966 if (!sUserMode
.empty()) {
967 m_pUser
->PutUser(":" + m_pUser
->GetIRCNick().GetNickMask() + " MODE " + m_pUser
->GetIRCNick().GetNick() + " :-" + sUserMode
);
970 // also clear the user modes in our space:
971 m_scUserModes
.clear();
974 void CIRCSock::SockError(int iErrno
) {
977 if (iErrno
== EDOM
) {
978 sError
= "Your bind host could not be resolved";
979 } else if (iErrno
== EADDRNOTAVAIL
) {
980 // Csocket uses this if it can't resolve the dest host name
981 // ...but it also does generate this if bind() fails -.-
982 sError
= strerror(iErrno
);
983 if (GetBindHost().empty())
984 sError
+= " (Is your IRC server's host name valid?)";
986 sError
+= " (Is your IRC server's host name and ZNC bind host valid?)";
988 sError
= strerror(iErrno
);
991 DEBUG(GetSockName() << " == SockError(" << iErrno
<< " "
993 if (!m_pUser
->IsBeingDeleted()) {
994 if (GetConState() != CST_OK
)
995 m_pUser
->PutStatus("Cannot connect to IRC (" +
996 sError
+ "). Retrying...");
998 m_pUser
->PutStatus("Disconnected from IRC (" +
999 sError
+ "). Reconnecting...");
1001 m_pUser
->ClearRawBuffer();
1002 m_pUser
->ClearMotdBuffer();
1005 m_scUserModes
.clear();
1008 void CIRCSock::Timeout() {
1009 DEBUG(GetSockName() << " == Timeout()");
1010 if (!m_pUser
->IsBeingDeleted()) {
1011 m_pUser
->PutStatus("IRC connection timed out. Reconnecting...");
1013 m_pUser
->ClearRawBuffer();
1014 m_pUser
->ClearMotdBuffer();
1017 m_scUserModes
.empty();
1020 void CIRCSock::ConnectionRefused() {
1021 DEBUG(GetSockName() << " == ConnectionRefused()");
1022 if (!m_pUser
->IsBeingDeleted()) {
1023 m_pUser
->PutStatus("Connection Refused. Reconnecting...");
1025 m_pUser
->ClearRawBuffer();
1026 m_pUser
->ClearMotdBuffer();
1029 void CIRCSock::ReachedMaxBuffer() {
1030 DEBUG(GetSockName() << " == ReachedMaxBuffer()");
1031 m_pUser
->PutStatus("Received a too long line from the IRC server!");
1035 void CIRCSock::ParseISupport(const CString
& sLine
) {
1037 VCString::iterator it
;
1039 sLine
.Split(" ", vsTokens
, false);
1041 for (it
= vsTokens
.begin(); it
!= vsTokens
.end(); ++it
) {
1042 CString sName
= it
->Token(0, false, "=");
1043 CString sValue
= it
->Token(1, true, "=");
1045 if (sName
.Equals("PREFIX")) {
1046 CString sPrefixes
= sValue
.Token(1, false, ")");
1047 CString sPermModes
= sValue
.Token(0, false, ")");
1048 sPermModes
.TrimLeft("(");
1050 if (!sPrefixes
.empty() && sPermModes
.size() == sPrefixes
.size()) {
1051 m_sPerms
= sPrefixes
;
1052 m_sPermModes
= sPermModes
;
1054 } else if (sName
.Equals("CHANTYPES")) {
1055 m_pUser
->SetChanPrefixes(sValue
);
1056 } else if (sName
.Equals("NICKLEN")) {
1057 unsigned int uMax
= sValue
.ToUInt();
1060 m_uMaxNickLen
= uMax
;
1062 } else if (sName
.Equals("CHANMODES")) {
1063 if (!sValue
.empty()) {
1064 m_mueChanModes
.clear();
1066 for (unsigned int a
= 0; a
< 4; a
++) {
1067 CString sModes
= sValue
.Token(a
, false, ",");
1069 for (unsigned int b
= 0; b
< sModes
.size(); b
++) {
1070 m_mueChanModes
[sModes
[b
]] = (EChanModeArgs
) a
;
1074 } else if (sName
.Equals("NAMESX")) {
1078 PutIRC("PROTOCTL NAMESX");
1079 } else if (sName
.Equals("UHNAMES")) {
1083 PutIRC("PROTOCTL UHNAMES");
1088 void CIRCSock::ForwardRaw353(const CString
& sLine
) const {
1089 vector
<CClient
*>& vClients
= m_pUser
->GetClients();
1090 vector
<CClient
*>::iterator it
;
1092 for (it
= vClients
.begin(); it
!= vClients
.end(); ++it
) {
1093 ForwardRaw353(sLine
, *it
);
1097 void CIRCSock::ForwardRaw353(const CString
& sLine
, CClient
* pClient
) const {
1098 CString sNicks
= sLine
.Token(5, true);
1099 if (sNicks
.Left(1) == ":")
1102 if ((!m_bNamesx
|| pClient
->HasNamesx()) && (!m_bUHNames
|| pClient
->HasUHNames())) {
1103 // Client and server have both the same UHNames and Namesx stuff enabled
1104 m_pUser
->PutUser(sLine
, pClient
);
1106 // Get everything except the actual user list
1107 CString sTmp
= sLine
.Token(0, false, " :") + " :";
1110 VCString::const_iterator it
;
1112 // This loop runs once for every nick on the channel
1113 sNicks
.Split(" ", vsNicks
, false);
1114 for (it
= vsNicks
.begin(); it
!= vsNicks
.end(); ++it
) {
1115 CString sNick
= *it
;
1119 if (m_bNamesx
&& !pClient
->HasNamesx() && IsPermChar(sNick
[0])) {
1120 // Server has, client doesn't have NAMESX, so we just use the first perm char
1121 size_t pos
= sNick
.find_first_not_of(GetPerms());
1122 if (pos
>= 2 && pos
!= CString::npos
) {
1123 sNick
= sNick
[0] + sNick
.substr(pos
);
1127 if (m_bUHNames
&& !pClient
->HasUHNames()) {
1128 // Server has, client hasnt UHNAMES,
1129 // so we strip away ident and host.
1130 sNick
= sNick
.Token(0, false, "!");
1133 sTmp
+= sNick
+ " ";
1135 // Strip away the spaces we inserted at the end
1136 sTmp
.TrimRight(" ");
1137 m_pUser
->PutUser(sTmp
, pClient
);
1141 void CIRCSock::SendAltNick(const CString
& sBadNick
) {
1142 const CString
& sLastNick
= m_Nick
.GetNick();
1144 // We don't know the maximum allowed nick length yet, but we know which
1145 // nick we sent last. If sBadNick is shorter than that, we assume the
1146 // server truncated our nick.
1147 if (sBadNick
.length() < sLastNick
.length())
1148 m_uMaxNickLen
= sBadNick
.length();
1150 unsigned int uMax
= m_uMaxNickLen
;
1152 const CString
& sConfNick
= m_pUser
->GetNick();
1153 const CString
& sAltNick
= m_pUser
->GetAltNick();
1156 if (sLastNick
.Equals(sConfNick
)) {
1157 if ((!sAltNick
.empty()) && (!sConfNick
.Equals(sAltNick
))) {
1158 sNewNick
= sAltNick
;
1160 sNewNick
= sConfNick
.Left(uMax
- 1) + "-";
1162 } else if (sLastNick
.Equals(sAltNick
)) {
1163 sNewNick
= sConfNick
.Left(uMax
-1) + "-";
1164 } else if (sLastNick
.Equals(CString(sConfNick
.Left(uMax
-1) + "-"))) {
1165 sNewNick
= sConfNick
.Left(uMax
-1) + "|";
1166 } else if (sLastNick
.Equals(CString(sConfNick
.Left(uMax
-1) + "|"))) {
1167 sNewNick
= sConfNick
.Left(uMax
-1) + "^";
1168 } else if (sLastNick
.Equals(CString(sConfNick
.Left(uMax
-1) + "^"))) {
1169 sNewNick
= sConfNick
.Left(uMax
-1) + "a";
1172 if (sBadNick
.empty()) {
1173 m_pUser
->PutUser("No free nick available");
1178 cLetter
= sBadNick
.Right(1)[0];
1180 if (cLetter
== 'z') {
1181 m_pUser
->PutUser("No free nick found");
1186 sNewNick
= sConfNick
.Left(uMax
-1) + ++cLetter
;
1188 PutIRC("NICK " + sNewNick
);
1189 m_Nick
.SetNick(sNewNick
);
1192 unsigned char CIRCSock::GetPermFromMode(unsigned char uMode
) const {
1193 if (m_sPermModes
.size() == m_sPerms
.size()) {
1194 for (unsigned int a
= 0; a
< m_sPermModes
.size(); a
++) {
1195 if (m_sPermModes
[a
] == uMode
) {
1204 CIRCSock::EChanModeArgs
CIRCSock::GetModeType(unsigned char uMode
) const {
1205 map
<unsigned char, EChanModeArgs
>::const_iterator it
= m_mueChanModes
.find(uMode
);
1207 if (it
== m_mueChanModes
.end()) {
1214 void CIRCSock::ResetChans() {
1215 for (map
<CString
, CChan
*>::iterator a
= m_msChans
.begin(); a
!= m_msChans
.end(); ++a
) {