+/* +------------------------------------+
+ * | Inspire Internet Relay Chat Daemon |
+ * +------------------------------------+
+ *
+ * InspIRCd: (C) 2002-2007 InspIRCd Development Team
+ * See: http://www.inspircd.org/wiki/index.php/Credits
+ *
+ * This program is free but copyrighted software; see
+ * the file COPYING for details.
+ *
+ * ---------------------------------------------------
+ */
+
#include "configreader.h"
#include "users.h"
#include "channels.h"
#include "wildcard.h"
#include "xline.h"
#include "transport.h"
+#include "m_hash.h"
#include "socketengine.h"
#include "m_spanningtree/main.h"
#include "m_spanningtree/resolvers.h"
#include "m_spanningtree/handshaketimer.h"
-/* $ModDep: m_spanningtree/timesynctimer.h m_spanningtree/resolvers.h m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/link.h m_spanningtree/treesocket.h */
+/* $ModDep: m_spanningtree/timesynctimer.h m_spanningtree/resolvers.h m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/link.h m_spanningtree/treesocket.h m_hash.h */
+
/** Because most of the I/O gubbins are encapsulated within
* InspSocket, we just call the superclass constructor for
{
myhost = host;
this->LinkState = LISTENER;
+ theirchallenge = ourchallenge = "";
if (listening && Hook)
InspSocketHookRequest(this, (Module*)Utils->Creator, Hook).Send();
}
: InspSocket(SI, host, port, listening, maxtime, bindto), Utils(Util), Hook(HookMod)
{
myhost = ServerName;
+ theirchallenge = ourchallenge = "";
this->LinkState = CONNECTING;
if (Hook)
InspSocketHookRequest(this, (Module*)Utils->Creator, Hook).Send();
: InspSocket(SI, newfd, ip), Utils(Util), Hook(HookMod)
{
this->LinkState = WAIT_AUTH_1;
+ theirchallenge = ourchallenge = "";
/* If we have a transport module hooked to the parent, hook the same module to this
* socket, and set a timer waiting for handshake before we send CAPAB etc.
*/
if (Hook)
- {
InspSocketHookRequest(this, (Module*)Utils->Creator, Hook).Send();
- Instance->Timers->AddTimer(new HandshakeTimer(Instance, this, &(Utils->LinkBlocks[0]), this->Utils));
- }
- else
- {
- /* Otherwise, theres no lower layer transport in plain TCP/IP,
- * so just send the capabilities right now.
- */
- this->SendCapabilities();
- }
+
+ Instance->Timers->AddTimer(new HandshakeTimer(Instance, this, &(Utils->LinkBlocks[0]), this->Utils, 1));
}
ServerState TreeSocket::GetLinkState()
InspSocketUnhookRequest(this, (Module*)Utils->Creator, Hook).Send();
}
+const std::string& TreeSocket::GetOurChallenge()
+{
+ return this->ourchallenge;
+}
+
+void TreeSocket::SetOurChallenge(const std::string &c)
+{
+ this->ourchallenge = c;
+}
+
+const std::string& TreeSocket::GetTheirChallenge()
+{
+ return this->theirchallenge;
+}
+
+void TreeSocket::SetTheirChallenge(const std::string &c)
+{
+ this->theirchallenge = c;
+}
+
+std::string TreeSocket::MakePass(const std::string &password, const std::string &challenge)
+{
+ /* This is a simple (maybe a bit hacky?) HMAC algorithm, thanks to jilles for
+ * suggesting the use of HMAC to secure the password against various attacks.
+ *
+ * Note: If m_sha256.so is not loaded, we MUST fall back to plaintext with no
+ * HMAC challenge/response.
+ */
+ Module* sha256 = Instance->FindModule("m_sha256.so");
+ if (Utils->ChallengeResponse && sha256 && !challenge.empty())
+ {
+ /* XXX: This is how HMAC is supposed to be done:
+ *
+ * sha256( (pass xor 0x5c) + sha256((pass xor 0x36) + m) )
+ *
+ * Note that we are encoding the hex hash, not the binary
+ * output of the hash which is slightly different to standard.
+ *
+ * Don't ask me why its always 0x5c and 0x36... it just is.
+ */
+ std::string hmac1, hmac2;
+
+ for (size_t n = 0; n < password.length(); n++)
+ {
+ hmac1 += static_cast<char>(password[n] ^ 0x5C);
+ hmac2 += static_cast<char>(password[n] ^ 0x36);
+ }
+
+ HashResetRequest(Utils->Creator, sha256).Send();
+ hmac2 = HashSumRequest(Utils->Creator, sha256, hmac2).Send();
+
+ HashResetRequest(Utils->Creator, sha256).Send();
+ std::string hmac = hmac1 + hmac2 + challenge;
+ hmac = HashSumRequest(Utils->Creator, sha256, hmac).Send();
+
+ return "HMAC-SHA256:"+ hmac;
+ }
+ else if (!challenge.empty() && !sha256)
+ Instance->Log(DEFAULT,"Not authenticating to server using SHA256/HMAC because we don't have m_sha256 loaded!");
+
+ return password;
+}
+
/** When an outbound connection finishes connecting, we receive
* this event, and must send our SERVER string to the other
* side. If the other side is happy, as outlined in the server
InspSocketHookRequest(this, (Module*)Utils->Creator, Hook).Send();
this->Instance->SNO->WriteToSnoMask('l',"Connection to \2"+myhost+"\2["+(x->HiddenFromStats ? "<hidden>" : this->GetIP())+"] using transport \2"+x->Hook+"\2");
}
- else
- this->SendCapabilities();
+ this->OutboundPass = x->SendPass;
/* found who we're supposed to be connecting to, send the neccessary gubbins. */
- if (Hook)
- Instance->Timers->AddTimer(new HandshakeTimer(Instance, this, &(*x), this->Utils));
- else
- this->WriteLine(std::string("SERVER ")+this->Instance->Config->ServerName+" "+x->SendPass+" 0 :"+this->Instance->Config->ServerDesc);
+ Instance->Timers->AddTimer(new HandshakeTimer(Instance, this, &(*x), this->Utils, 2));
return true;
}
}
return capabilities;
}
+std::string TreeSocket::RandString(unsigned int length)
+{
+ char* randombuf = new char[length+1];
+ std::string out;
+ int fd = open("/dev/urandom", O_RDONLY, 0);
+
+ if (fd >= 0)
+ {
+ read(fd, randombuf, length);
+ close(fd);
+ }
+ else
+ {
+ for (unsigned int i = 0; i < length; i++)
+ randombuf[i] = rand();
+ }
+
+ for (unsigned int i = 0; i < length; i++)
+ out += static_cast<char>((randombuf[i] & 0x7F) | 0x21);
+
+ delete[] randombuf;
+ return out;
+}
+
void TreeSocket::SendCapabilities()
{
irc::commasepstream modulelist(MyCapabilities());
#ifdef SUPPORT_IP6LINKS
ip6support = 1;
#endif
- this->WriteLine("CAPAB CAPABILITIES :NICKMAX="+ConvToStr(NICKMAX)+" HALFOP="+ConvToStr(this->Instance->Config->AllowHalfop)+" CHANMAX="+ConvToStr(CHANMAX)+" MAXMODES="+ConvToStr(MAXMODES)+" IDENTMAX="+ConvToStr(IDENTMAX)+" MAXQUIT="+ConvToStr(MAXQUIT)+" MAXTOPIC="+ConvToStr(MAXTOPIC)+" MAXKICK="+ConvToStr(MAXKICK)+" MAXGECOS="+ConvToStr(MAXGECOS)+" MAXAWAY="+ConvToStr(MAXAWAY)+" IP6NATIVE="+ConvToStr(ip6)+" IP6SUPPORT="+ConvToStr(ip6support)+" PROTOCOL="+ConvToStr(ProtocolVersion));
+ std::string extra;
+ /* Do we have sha256 available? If so, we send a challenge */
+ if (Utils->ChallengeResponse && (Instance->FindModule("m_sha256.so")))
+ {
+ this->SetOurChallenge(RandString(20));
+ extra = " CHALLENGE=" + this->GetOurChallenge();
+ }
+
+ this->WriteLine("CAPAB CAPABILITIES :NICKMAX="+ConvToStr(NICKMAX)+" HALFOP="+ConvToStr(this->Instance->Config->AllowHalfop)+" CHANMAX="+ConvToStr(CHANMAX)+" MAXMODES="+ConvToStr(MAXMODES)+" IDENTMAX="+ConvToStr(IDENTMAX)+" MAXQUIT="+ConvToStr(MAXQUIT)+" MAXTOPIC="+ConvToStr(MAXTOPIC)+" MAXKICK="+ConvToStr(MAXKICK)+" MAXGECOS="+ConvToStr(MAXGECOS)+" MAXAWAY="+ConvToStr(MAXAWAY)+" IP6NATIVE="+ConvToStr(ip6)+" IP6SUPPORT="+ConvToStr(ip6support)+" PROTOCOL="+ConvToStr(ProtocolVersion)+extra);
this->WriteLine("CAPAB END");
}
reason = "Maximum GECOS (fullname) lengths differ or remote GECOS length not specified";
if (((this->CapKeys.find("MAXAWAY") == this->CapKeys.end()) || ((this->CapKeys.find("MAXAWAY") != this->CapKeys.end()) && (this->CapKeys.find("MAXAWAY")->second != ConvToStr(MAXAWAY)))))
reason = "Maximum awaymessage lengths differ or remote awaymessage length not specified";
+
+ /* Challenge response, store their challenge for our password */
+ std::map<std::string,std::string>::iterator n = this->CapKeys.find("CHALLENGE");
+ if (Utils->ChallengeResponse && (n != this->CapKeys.end()) && (Instance->FindModule("m_sha256.so")))
+ {
+ /* Challenge-response is on now */
+ this->SetTheirChallenge(n->second);
+ if (!this->GetOurChallenge().empty() && (this->LinkState == CONNECTING))
+ {
+ this->WriteLine(std::string("SERVER ")+this->Instance->Config->ServerName+" "+this->MakePass(OutboundPass, this->GetTheirChallenge())+" 0 :"+this->Instance->Config->ServerDesc);
+ }
+ }
+ else
+ {
+ /* They didnt specify a challenge or we don't have m_sha256.so, we use plaintext */
+ if (this->LinkState == CONNECTING)
+ this->WriteLine(std::string("SERVER ")+this->Instance->Config->ServerName+" "+OutboundPass+" 0 :"+this->Instance->Config->ServerDesc);
+ }
+
if (reason.length())
{
this->WriteLine("ERROR :CAPAB negotiation failed: "+reason);
else if ((params[0] == "CAPABILITIES") && (params.size() == 2))
{
irc::tokenstream capabs(params[1]);
- std::string item = "*";
- while ((item = capabs.GetToken()) != "")
+ std::string item;
+ bool more = true;
+ while ((more = capabs.GetToken(item)))
{
/* Process each key/value pair */
std::string::size_type equals = item.rfind('=');
userrec* who = NULL; /* User we are currently checking */
std::string channel = params[0]; /* Channel name, as a string */
time_t TS = atoi(params[1].c_str()); /* Timestamp given to us for remote side */
+ std::string nicklist = params[2];
bool created = false;
/* Try and find the channel */
ourTS = chan->age;
else
created = true; /* don't perform deops, and set TS to correct time after processing. */
+
+ /* do this first, so our mode reversals are correctly received by other servers
+ * if there is a TS collision.
+ */
+ params[2] = ":" + params[2];
+ Utils->DoOneToAllButSender(source,"FJOIN",params,source);
+
/* In 1.1, if they have the newer channel, we immediately clear
* all status modes from our users. We then accept their modes.
* If WE have the newer channel its the other side's job to do this.
}
}
/* Put the final parameter of the FJOIN into a tokenstream ready to split it */
- irc::tokenstream users(params[2]);
- std::string item = "*";
- /* do this first, so our mode reversals are correctly received by other servers
- * if there is a TS collision.
- */
- params[2] = ":" + params[2];
- Utils->DoOneToAllButSender(source,"FJOIN",params,source);
+ irc::tokenstream users(nicklist);
+ std::string item;
+
/* Now, process every 'prefixes,nick' pair */
- while (item != "")
+ while (users.GetToken(item))
{
/* Find next user */
- item = users.GetToken();
const char* usr = item.c_str();
/* Safety check just to make sure someones not sent us an FJOIN full of spaces
* (is this even possible?) */
/* Finally, we can actually place the user into the channel.
* We're sure its right. Final answer, phone a friend.
*/
- chanrec::JoinUser(this->Instance, who, channel.c_str(), true, "");
+ if (created)
+ chanrec::JoinUser(this->Instance, who, channel.c_str(), true, "", TS);
+ else
+ chanrec::JoinUser(this->Instance, who, channel.c_str(), true, "");
/* Have we already queued up MAXMODES modes with parameters
* (+qaohv) ready to be sent to the server?
*/
for (unsigned int f = 2; f < modectr; f++)
free(mode_users[f]);
}
- /* if we newly created the channel, set it's TS properly. */
- if (created)
- {
- /* find created channel .. */
- chan = this->Instance->FindChan(channel);
- if (chan)
- /* w00t said this shouldnt be needed but it is.
- * This isnt strictly true, as chan can be NULL
- * if a nick collision has occured and therefore
- * the channel was never created.
- */
- chan->age = TS;
- }
/* All done. That wasnt so bad was it, you can wipe
* the sweat from your forehead now. :-)
*/
/** NICK command */
bool TreeSocket::IntroduceClient(const std::string &source, std::deque<std::string> ¶ms)
{
- if (params.size() < 8)
- return true;
- if (params.size() > 8)
+ /** Do we have enough parameters:
+ * NICK age nick host dhost ident +modes ip :gecos
+ */
+ if (params.size() != 8)
{
this->WriteLine(std::string(":")+this->Instance->Config->ServerName+" KILL "+params[1]+" :Invalid client introduction ("+params[1]+"?)");
return true;
}
- // NICK age nick host dhost ident +modes ip :gecos
- // 0 1 2 3 4 5 6 7
- time_t age = atoi(params[0].c_str());
+ time_t age = atoi(params[0].c_str());
const char* tempnick = params[1].c_str();
- Instance->Log(DEBUG,"New remote client %s",tempnick);
+ /** Check parameters for validity before introducing the client, discovered by dmb.
+ * XXX: Can we make this neater?
+ */
+ if (!age)
+ {
+ this->WriteLine(std::string(":")+this->Instance->Config->ServerName+" KILL "+params[1]+" :Invalid client introduction (Invalid TS?)");
+ return true;
+ }
+ else if (params[1].length() > NICKMAX)
+ {
+ this->WriteLine(std::string(":")+this->Instance->Config->ServerName+" KILL "+params[1]+" :Invalid client introduction ("+params[1]+" > NICKMAX?)");
+ return true;
+ }
+ else if (params[2].length() > 64)
+ {
+ this->WriteLine(std::string(":")+this->Instance->Config->ServerName+" KILL "+params[1]+" :Invalid client introduction ("+params[2]+" > 64?)");
+ return true;
+ }
+ else if (params[3].length() > 64)
+ {
+ this->WriteLine(std::string(":")+this->Instance->Config->ServerName+" KILL "+params[1]+" :Invalid client introduction ("+params[3]+" > 64?)");
+ return true;
+ }
+ else if (params[4].length() > IDENTMAX)
+ {
+ this->WriteLine(std::string(":")+this->Instance->Config->ServerName+" KILL "+params[1]+" :Invalid client introduction ("+params[4]+" > IDENTMAX?)");
+ return true;
+ }
+ else if (params[7].length() > MAXGECOS)
+ {
+ this->WriteLine(std::string(":")+this->Instance->Config->ServerName+" KILL "+params[1]+" :Invalid client introduction ("+params[7]+" > MAXGECOS?)");
+ return true;
+ }
+
+ /** Our client looks ok, lets introduce it now
+ */
+ Instance->Log(DEBUG,"New remote client %s",tempnick);
user_hash::iterator iter = this->Instance->clientlist->find(tempnick);
if (iter != this->Instance->clientlist->end())
{
- // nick collision
+ /* nick collision */
this->WriteLine(std::string(":")+this->Instance->Config->ServerName+" KILL "+tempnick+" :Nickname collision");
userrec::QuitUser(this->Instance, iter->second, "Nickname collision");
return true;
_new->registered = REG_ALL;
_new->signon = age;
- /*
- * we need to remove the + from the modestring, so we can do our stuff
- */
+ /* we need to remove the + from the modestring, so we can do our stuff */
std::string::size_type pos_after_plus = params[5].find_first_not_of('+');
if (pos_after_plus != std::string::npos)
params[5] = params[5].substr(pos_after_plus);
_new->SetSockAddr(AF_INET, params[6].c_str(), 0);
Instance->AddGlobalClone(_new);
- this->Instance->SNO->WriteToSnoMask('C',"Client connecting at %s: %s!%s@%s [%s]",_new->server,_new->nick,_new->ident,_new->host, _new->GetIPString());
+
+ if (!this->Instance->SilentULine(_new->server))
+ this->Instance->SNO->WriteToSnoMask('C',"Client connecting at %s: %s!%s@%s [%s]",_new->server,_new->nick,_new->ident,_new->host, _new->GetIPString());
params[7] = ":" + params[7];
Utils->DoOneToAllButSender(source,"NICK", params, source);
*/
void TreeSocket::DoBurst(TreeServer* s)
{
+ std::string name = s->GetName();
std::string burst = "BURST "+ConvToStr(Instance->Time(true));
std::string endburst = "ENDBURST";
- // Because by the end of the netburst, it could be gone!
- std::string name = s->GetName();
- this->Instance->SNO->WriteToSnoMask('l',"Bursting to \2"+name+"\2.");
+ this->Instance->SNO->WriteToSnoMask('l',"Bursting to \2%s\2 (Authentication: %s).", name.c_str(), this->GetTheirChallenge().empty() ? "plaintext password" : "SHA256-HMAC challenge-response");
this->WriteLine(burst);
/* send our version string */
this->WriteLine(std::string(":")+this->Instance->Config->ServerName+" VERSION :"+this->Instance->GetVersionString());