]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_sasl.cpp
Fix being able to see the modes of private/secret channels.
[user/henk/code/inspircd.git] / src / modules / m_sasl.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2016 Adam <Adam@anope.org>
5  *   Copyright (C) 2014 Mantas Mikulėnas <grawity@gmail.com>
6  *   Copyright (C) 2013-2016, 2018 Attila Molnar <attilamolnar@hush.com>
7  *   Copyright (C) 2013, 2017-2019 Sadie Powell <sadie@witchery.services>
8  *   Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org>
9  *   Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be>
10  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
11  *   Copyright (C) 2008, 2010 Craig Edwards <brain@inspircd.org>
12  *   Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
13  *
14  * This file is part of InspIRCd.  InspIRCd is free software: you can
15  * redistribute it and/or modify it under the terms of the GNU General Public
16  * License as published by the Free Software Foundation, version 2.
17  *
18  * This program is distributed in the hope that it will be useful, but WITHOUT
19  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
21  * details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26
27
28 #include "inspircd.h"
29 #include "modules/cap.h"
30 #include "modules/account.h"
31 #include "modules/sasl.h"
32 #include "modules/ssl.h"
33 #include "modules/server.h"
34
35 enum
36 {
37         // From IRCv3 sasl-3.1
38         RPL_SASLSUCCESS = 903,
39         ERR_SASLFAIL = 904,
40         ERR_SASLTOOLONG = 905,
41         ERR_SASLABORTED = 906,
42         RPL_SASLMECHS = 908
43 };
44
45 static std::string sasl_target;
46
47 class ServerTracker
48         : public ServerProtocol::LinkEventListener
49 {
50         // Stop GCC warnings about the deprecated OnServerSplit event.
51         using ServerProtocol::LinkEventListener::OnServerSplit;
52
53         bool online;
54
55         void Update(const Server* server, bool linked)
56         {
57                 if (sasl_target == "*")
58                         return;
59
60                 if (InspIRCd::Match(server->GetName(), sasl_target))
61                 {
62                         ServerInstance->Logs->Log(MODNAME, LOG_VERBOSE, "SASL target server \"%s\" %s", sasl_target.c_str(), (linked ? "came online" : "went offline"));
63                         online = linked;
64                 }
65         }
66
67         void OnServerLink(const Server* server) CXX11_OVERRIDE
68         {
69                 Update(server, true);
70         }
71
72         void OnServerSplit(const Server* server, bool error) CXX11_OVERRIDE
73         {
74                 Update(server, false);
75         }
76
77  public:
78         ServerTracker(Module* mod)
79                 : ServerProtocol::LinkEventListener(mod)
80         {
81                 Reset();
82         }
83
84         void Reset()
85         {
86                 if (sasl_target == "*")
87                 {
88                         online = true;
89                         return;
90                 }
91
92                 online = false;
93
94                 ProtocolInterface::ServerList servers;
95                 ServerInstance->PI->GetServerList(servers);
96                 for (ProtocolInterface::ServerList::const_iterator i = servers.begin(); i != servers.end(); ++i)
97                 {
98                         const ProtocolInterface::ServerInfo& server = *i;
99                         if (InspIRCd::Match(server.servername, sasl_target))
100                         {
101                                 online = true;
102                                 break;
103                         }
104                 }
105         }
106
107         bool IsOnline() const { return online; }
108 };
109
110 class SASLCap : public Cap::Capability
111 {
112         std::string mechlist;
113         const ServerTracker& servertracker;
114
115         bool OnRequest(LocalUser* user, bool adding) CXX11_OVERRIDE
116         {
117                 // Servers MUST NAK any sasl capability request if the authentication layer
118                 // is unavailable.
119                 return servertracker.IsOnline();
120         }
121
122         bool OnList(LocalUser* user) CXX11_OVERRIDE
123         {
124                 // Servers MUST NOT advertise the sasl capability if the authentication layer
125                 // is unavailable.
126                 return servertracker.IsOnline();
127         }
128
129         const std::string* GetValue(LocalUser* user) const CXX11_OVERRIDE
130         {
131                 return &mechlist;
132         }
133
134  public:
135         SASLCap(Module* mod, const ServerTracker& tracker)
136                 : Cap::Capability(mod, "sasl")
137                 , servertracker(tracker)
138         {
139         }
140
141         void SetMechlist(const std::string& newmechlist)
142         {
143                 if (mechlist == newmechlist)
144                         return;
145
146                 mechlist = newmechlist;
147                 NotifyValueChange();
148         }
149 };
150
151 enum SaslState { SASL_INIT, SASL_COMM, SASL_DONE };
152 enum SaslResult { SASL_OK, SASL_FAIL, SASL_ABORT };
153
154 static Events::ModuleEventProvider* saslevprov;
155
156 static void SendSASL(LocalUser* user, const std::string& agent, char mode, const std::vector<std::string>& parameters)
157 {
158         CommandBase::Params params;
159         params.push_back(user->uuid);
160         params.push_back(agent);
161         params.push_back(ConvToStr(mode));
162         params.insert(params.end(), parameters.begin(), parameters.end());
163
164         if (!ServerInstance->PI->SendEncapsulatedData(sasl_target, "SASL", params))
165         {
166                 FOREACH_MOD_CUSTOM(*saslevprov, SASLEventListener, OnSASLAuth, (params));
167         }
168 }
169
170 static ClientProtocol::EventProvider* g_protoev;
171
172 /**
173  * Tracks SASL authentication state like charybdis does. --nenolod
174  */
175 class SaslAuthenticator
176 {
177  private:
178         std::string agent;
179         LocalUser* user;
180         SaslState state;
181         SaslResult result;
182         bool state_announced;
183
184         void SendHostIP(UserCertificateAPI& sslapi)
185         {
186                 std::vector<std::string> params;
187                 params.push_back(user->GetRealHost());
188                 params.push_back(user->GetIPString());
189                 params.push_back(sslapi && sslapi->GetCertificate(user) ? "S" : "P");
190
191                 SendSASL(user, "*", 'H', params);
192         }
193
194  public:
195         SaslAuthenticator(LocalUser* user_, const std::string& method, UserCertificateAPI& sslapi)
196                 : user(user_)
197                 , state(SASL_INIT)
198                 , state_announced(false)
199         {
200                 SendHostIP(sslapi);
201
202                 std::vector<std::string> params;
203                 params.push_back(method);
204
205                 const std::string fp = sslapi ? sslapi->GetFingerprint(user) : "";
206                 if (fp.size())
207                         params.push_back(fp);
208
209                 SendSASL(user, "*", 'S', params);
210         }
211
212         SaslResult GetSaslResult(const std::string &result_)
213         {
214                 if (result_ == "F")
215                         return SASL_FAIL;
216
217                 if (result_ == "A")
218                         return SASL_ABORT;
219
220                 return SASL_OK;
221         }
222
223         /* checks for and deals with a state change. */
224         SaslState ProcessInboundMessage(const CommandBase::Params& msg)
225         {
226                 switch (this->state)
227                 {
228                  case SASL_INIT:
229                         this->agent = msg[0];
230                         this->state = SASL_COMM;
231                         /* fall through */
232                  case SASL_COMM:
233                         if (msg[0] != this->agent)
234                                 return this->state;
235
236                         if (msg.size() < 4)
237                                 return this->state;
238
239                         if (msg[2] == "C")
240                         {
241                                 ClientProtocol::Message authmsg("AUTHENTICATE");
242                                 authmsg.PushParamRef(msg[3]);
243
244                                 ClientProtocol::Event authevent(*g_protoev, authmsg);
245                                 LocalUser* const localuser = IS_LOCAL(user);
246                                 if (localuser)
247                                         localuser->Send(authevent);
248                         }
249                         else if (msg[2] == "D")
250                         {
251                                 this->state = SASL_DONE;
252                                 this->result = this->GetSaslResult(msg[3]);
253                         }
254                         else if (msg[2] == "M")
255                                 this->user->WriteNumeric(RPL_SASLMECHS, msg[3], "are available SASL mechanisms");
256                         else
257                                 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Services sent an unknown SASL message \"%s\" \"%s\"", msg[2].c_str(), msg[3].c_str());
258
259                         break;
260                  case SASL_DONE:
261                         break;
262                  default:
263                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WTF: SaslState is not a known state (%d)", this->state);
264                         break;
265                 }
266
267                 return this->state;
268         }
269
270         bool SendClientMessage(const std::vector<std::string>& parameters)
271         {
272                 if (this->state != SASL_COMM)
273                         return true;
274
275                 SendSASL(this->user, this->agent, 'C', parameters);
276
277                 if (parameters[0].c_str()[0] == '*')
278                 {
279                         this->state = SASL_DONE;
280                         this->result = SASL_ABORT;
281                         return false;
282                 }
283
284                 return true;
285         }
286
287         void AnnounceState(void)
288         {
289                 if (this->state_announced)
290                         return;
291
292                 switch (this->result)
293                 {
294                  case SASL_OK:
295                         this->user->WriteNumeric(RPL_SASLSUCCESS, "SASL authentication successful");
296                         break;
297                  case SASL_ABORT:
298                         this->user->WriteNumeric(ERR_SASLABORTED, "SASL authentication aborted");
299                         break;
300                  case SASL_FAIL:
301                         this->user->WriteNumeric(ERR_SASLFAIL, "SASL authentication failed");
302                         break;
303                  default:
304                         break;
305                 }
306
307                 this->state_announced = true;
308         }
309 };
310
311 class CommandAuthenticate : public SplitCommand
312 {
313  private:
314          // The maximum length of an AUTHENTICATE request.
315          static const size_t MAX_AUTHENTICATE_SIZE = 400;
316
317  public:
318         SimpleExtItem<SaslAuthenticator>& authExt;
319         Cap::Capability& cap;
320         UserCertificateAPI sslapi;
321
322         CommandAuthenticate(Module* Creator, SimpleExtItem<SaslAuthenticator>& ext, Cap::Capability& Cap)
323                 : SplitCommand(Creator, "AUTHENTICATE", 1)
324                 , authExt(ext)
325                 , cap(Cap)
326                 , sslapi(Creator)
327         {
328                 works_before_reg = true;
329                 allow_empty_last_param = false;
330         }
331
332         CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
333         {
334                 {
335                         if (!cap.get(user))
336                                 return CMD_FAILURE;
337
338                         if (parameters[0].find(' ') != std::string::npos || parameters[0][0] == ':')
339                                 return CMD_FAILURE;
340
341                         if (parameters[0].length() > MAX_AUTHENTICATE_SIZE)
342                         {
343                                 user->WriteNumeric(ERR_SASLTOOLONG, "SASL message too long");
344                                 return CMD_FAILURE;
345                         }
346
347                         SaslAuthenticator *sasl = authExt.get(user);
348                         if (!sasl)
349                                 authExt.set(user, new SaslAuthenticator(user, parameters[0], sslapi));
350                         else if (sasl->SendClientMessage(parameters) == false)  // IAL abort extension --nenolod
351                         {
352                                 sasl->AnnounceState();
353                                 authExt.unset(user);
354                         }
355                 }
356                 return CMD_FAILURE;
357         }
358 };
359
360 class CommandSASL : public Command
361 {
362  public:
363         SimpleExtItem<SaslAuthenticator>& authExt;
364         CommandSASL(Module* Creator, SimpleExtItem<SaslAuthenticator>& ext) : Command(Creator, "SASL", 2), authExt(ext)
365         {
366                 this->flags_needed = FLAG_SERVERONLY; // should not be called by users
367         }
368
369         CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
370         {
371                 User* target = ServerInstance->FindUUID(parameters[1]);
372                 if (!target)
373                 {
374                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User not found in sasl ENCAP event: %s", parameters[1].c_str());
375                         return CMD_FAILURE;
376                 }
377
378                 SaslAuthenticator *sasl = authExt.get(target);
379                 if (!sasl)
380                         return CMD_FAILURE;
381
382                 SaslState state = sasl->ProcessInboundMessage(parameters);
383                 if (state == SASL_DONE)
384                 {
385                         sasl->AnnounceState();
386                         authExt.unset(target);
387                 }
388                 return CMD_SUCCESS;
389         }
390
391         RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
392         {
393                 return ROUTE_BROADCAST;
394         }
395 };
396
397 class ModuleSASL : public Module
398 {
399         SimpleExtItem<SaslAuthenticator> authExt;
400         ServerTracker servertracker;
401         SASLCap cap;
402         CommandAuthenticate auth;
403         CommandSASL sasl;
404         Events::ModuleEventProvider sasleventprov;
405         ClientProtocol::EventProvider protoev;
406
407  public:
408         ModuleSASL()
409                 : authExt("sasl_auth", ExtensionItem::EXT_USER, this)
410                 , servertracker(this)
411                 , cap(this, servertracker)
412                 , auth(this, authExt, cap)
413                 , sasl(this, authExt)
414                 , sasleventprov(this, "event/sasl")
415                 , protoev(this, auth.name)
416         {
417                 saslevprov = &sasleventprov;
418                 g_protoev = &protoev;
419         }
420
421         void init() CXX11_OVERRIDE
422         {
423                 if (!ServerInstance->Modules->Find("m_services_account.so") || !ServerInstance->Modules->Find("m_cap.so"))
424                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: m_services_account and m_cap are not loaded! m_sasl will NOT function correctly until these two modules are loaded!");
425         }
426
427         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
428         {
429                 std::string target = ServerInstance->Config->ConfValue("sasl")->getString("target");
430                 if (target.empty())
431                         throw ModuleException("<sasl:target> must be set to the name of your services server!");
432
433                 sasl_target = target;
434                 servertracker.Reset();
435         }
436
437         void OnDecodeMetaData(Extensible* target, const std::string& extname, const std::string& extdata) CXX11_OVERRIDE
438         {
439                 if ((target == NULL) && (extname == "saslmechlist"))
440                         cap.SetMechlist(extdata);
441         }
442
443         Version GetVersion() CXX11_OVERRIDE
444         {
445                 return Version("Provides support for IRC Authentication Layer (aka: SASL) via AUTHENTICATE", VF_VENDOR);
446         }
447 };
448
449 MODULE_INIT(ModuleSASL)