1 /*
2 * Copyright (C) 2004-2020 ZNC, see the NOTICE file for details.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <znc/IRCNetwork.h>
18 #include <znc/IRCSock.h>
19 #include <algorithm>
20
21 #define NV_REQUIRE_AUTH "require_auth"
22 #define NV_MECHANISMS "mechanisms"
23
24 class Mechanisms : public VCString {
25 public:
SetIndex(unsigned int uiIndex)26 void SetIndex(unsigned int uiIndex) { m_uiIndex = uiIndex; }
27
GetIndex() const28 unsigned int GetIndex() const { return m_uiIndex; }
29
HasNext() const30 bool HasNext() const { return size() > (m_uiIndex + 1); }
31
IncrementIndex()32 void IncrementIndex() { m_uiIndex++; }
33
GetCurrent() const34 CString GetCurrent() const { return at(m_uiIndex); }
35
GetNext() const36 CString GetNext() const {
37 if (HasNext()) {
38 return at(m_uiIndex + 1);
39 }
40
41 return "";
42 }
43
44 private:
45 unsigned int m_uiIndex = 0;
46 };
47
48 class CSASLMod : public CModule {
49 const struct {
50 const char* szName;
51 CDelayedTranslation sDescription;
52 bool bDefault;
53 } SupportedMechanisms[2] = {
54 {"EXTERNAL", t_d("TLS certificate, for use with the *cert module"),
55 true},
56 {"PLAIN", t_d("Plain text negotiation, this should work always if the "
57 "network supports SASL"),
58 true}};
59
60 public:
MODCONSTRUCTOR(CSASLMod)61 MODCONSTRUCTOR(CSASLMod) {
62 AddCommand("Help", t_d("search"), t_d("Generate this output"),
63 [=](const CString& sLine) { PrintHelp(sLine); });
64 AddCommand("Set", t_d("[<username> [<password>]]"),
65 t_d("Set username and password for the mechanisms that need "
66 "them. Password is optional. Without parameters, "
67 "returns information about current settings."),
68 [=](const CString& sLine) { Set(sLine); });
69 AddCommand("Mechanism", t_d("[mechanism[ ...]]"),
70 t_d("Set the mechanisms to be attempted (in order)"),
71 [=](const CString& sLine) { SetMechanismCommand(sLine); });
72 AddCommand("RequireAuth", t_d("[yes|no]"),
73 t_d("Don't connect unless SASL authentication succeeds"),
74 [=](const CString& sLine) { RequireAuthCommand(sLine); });
75 AddCommand("Verbose", "yes|no", "Set verbosity level, useful to debug",
76 [&](const CString& sLine) {
77 m_bVerbose = sLine.Token(1, true).ToBool();
78 PutModule("Verbose: " + CString(m_bVerbose));
79 });
80
81 m_bAuthenticated = false;
82 }
83
PrintHelp(const CString & sLine)84 void PrintHelp(const CString& sLine) {
85 HandleHelpCommand(sLine);
86
87 CTable Mechanisms;
88 Mechanisms.AddColumn(t_s("Mechanism"));
89 Mechanisms.AddColumn(t_s("Description"));
90 Mechanisms.SetStyle(CTable::ListStyle);
91
92 for (const auto& it : SupportedMechanisms) {
93 Mechanisms.AddRow();
94 Mechanisms.SetCell(t_s("Mechanism"), it.szName);
95 Mechanisms.SetCell(t_s("Description"), it.sDescription.Resolve());
96 }
97
98 PutModule("");
99 PutModule(t_s("The following mechanisms are available:"));
100 PutModule(Mechanisms);
101 }
102
Set(const CString & sLine)103 void Set(const CString& sLine) {
104 if (sLine.Token(1).empty()) {
105 CString sUsername = GetNV("username");
106 CString sPassword = GetNV("password");
107
108 if (sUsername.empty()) {
109 PutModule(t_s("Username is currently not set"));
110 } else {
111 PutModule(t_f("Username is currently set to '{1}'")(sUsername));
112 }
113 if (sPassword.empty()) {
114 PutModule(t_s("Password was not supplied"));
115 } else {
116 PutModule(t_s("Password was supplied"));
117 }
118 return;
119 }
120
121 SetNV("username", sLine.Token(1));
122 SetNV("password", sLine.Token(2));
123
124 PutModule(t_f("Username has been set to [{1}]")(GetNV("username")));
125 PutModule(t_f("Password has been set to [{1}]")(GetNV("password")));
126 }
127
SetMechanismCommand(const CString & sLine)128 void SetMechanismCommand(const CString& sLine) {
129 CString sMechanisms = sLine.Token(1, true).AsUpper();
130
131 if (!sMechanisms.empty()) {
132 VCString vsMechanisms;
133 sMechanisms.Split(" ", vsMechanisms);
134
135 for (const CString& sMechanism : vsMechanisms) {
136 if (!SupportsMechanism(sMechanism)) {
137 PutModule("Unsupported mechanism: " + sMechanism);
138 return;
139 }
140 }
141
142 SetNV(NV_MECHANISMS, sMechanisms);
143 }
144
145 PutModule(t_f("Current mechanisms set: {1}")(GetMechanismsString()));
146 }
147
RequireAuthCommand(const CString & sLine)148 void RequireAuthCommand(const CString& sLine) {
149 if (!sLine.Token(1).empty()) {
150 SetNV(NV_REQUIRE_AUTH, sLine.Token(1));
151 }
152
153 if (GetNV(NV_REQUIRE_AUTH).ToBool()) {
154 PutModule(t_s("We require SASL negotiation to connect"));
155 } else {
156 PutModule(t_s("We will connect even if SASL fails"));
157 }
158 }
159
SupportsMechanism(const CString & sMechanism) const160 bool SupportsMechanism(const CString& sMechanism) const {
161 for (const auto& it : SupportedMechanisms) {
162 if (sMechanism.Equals(it.szName)) {
163 return true;
164 }
165 }
166
167 return false;
168 }
169
GetMechanismsString() const170 CString GetMechanismsString() const {
171 if (GetNV(NV_MECHANISMS).empty()) {
172 CString sDefaults = "";
173
174 for (const auto& it : SupportedMechanisms) {
175 if (it.bDefault) {
176 if (!sDefaults.empty()) {
177 sDefaults += " ";
178 }
179
180 sDefaults += it.szName;
181 }
182 }
183
184 return sDefaults;
185 }
186
187 return GetNV(NV_MECHANISMS);
188 }
189
CheckRequireAuth()190 void CheckRequireAuth() {
191 if (!m_bAuthenticated && GetNV(NV_REQUIRE_AUTH).ToBool()) {
192 GetNetwork()->SetIRCConnectEnabled(false);
193 PutModule(t_s("Disabling network, we require authentication."));
194 PutModule(t_s("Use 'RequireAuth no' to disable."));
195 }
196 }
197
Authenticate(const CString & sLine)198 void Authenticate(const CString& sLine) {
199 /* Send blank authenticate for other mechanisms (like EXTERNAL). */
200 CString sAuthLine;
201 if (m_Mechanisms.GetCurrent().Equals("PLAIN") && sLine.Equals("+")) {
202 sAuthLine = GetNV("username") + '\0' + GetNV("username") +
203 '\0' + GetNV("password");
204 sAuthLine.Base64Encode();
205 }
206
207 /* The spec requires authentication data to be sent in chunks */
208 const size_t chunkSize = 400;
209 for (size_t offset = 0; offset < sAuthLine.length(); offset += chunkSize) {
210 size_t size = std::min(chunkSize, sAuthLine.length() - offset);
211 PutIRC("AUTHENTICATE " + sAuthLine.substr(offset, size));
212 }
213 if (sAuthLine.length() % chunkSize == 0) {
214 /* Signal end if we have a multiple of the chunk size */
215 PutIRC("AUTHENTICATE +");
216 }
217 }
218
OnServerCapAvailable(const CString & sCap)219 bool OnServerCapAvailable(const CString& sCap) override {
220 return sCap.Equals("sasl");
221 }
222
OnServerCapResult(const CString & sCap,bool bSuccess)223 void OnServerCapResult(const CString& sCap, bool bSuccess) override {
224 if (sCap.Equals("sasl")) {
225 if (bSuccess) {
226 GetMechanismsString().Split(" ", m_Mechanisms);
227
228 if (m_Mechanisms.empty()) {
229 CheckRequireAuth();
230 return;
231 }
232
233 GetNetwork()->GetIRCSock()->PauseCap();
234
235 m_Mechanisms.SetIndex(0);
236 PutIRC("AUTHENTICATE " + m_Mechanisms.GetCurrent());
237 } else {
238 CheckRequireAuth();
239 }
240 }
241 }
242
OnRawMessage(CMessage & msg)243 EModRet OnRawMessage(CMessage& msg) override {
244 if (msg.GetCommand().Equals("AUTHENTICATE")) {
245 Authenticate(msg.GetParam(0));
246 return HALT;
247 }
248 return CONTINUE;
249 }
250
OnNumericMessage(CNumericMessage & msg)251 EModRet OnNumericMessage(CNumericMessage& msg) override {
252 if (msg.GetCode() == 903) {
253 /* SASL success! */
254 if (m_bVerbose) {
255 PutModule(
256 t_f("{1} mechanism succeeded.")(m_Mechanisms.GetCurrent()));
257 }
258 GetNetwork()->GetIRCSock()->ResumeCap();
259 m_bAuthenticated = true;
260 DEBUG("sasl: Authenticated with mechanism ["
261 << m_Mechanisms.GetCurrent() << "]");
262 } else if (msg.GetCode() == 904 ||
263 msg.GetCode() == 905) {
264 DEBUG("sasl: Mechanism [" << m_Mechanisms.GetCurrent()
265 << "] failed.");
266 if (m_bVerbose) {
267 PutModule(
268 t_f("{1} mechanism failed.")(m_Mechanisms.GetCurrent()));
269 }
270
271 if (m_Mechanisms.HasNext()) {
272 m_Mechanisms.IncrementIndex();
273 PutIRC("AUTHENTICATE " + m_Mechanisms.GetCurrent());
274 } else {
275 CheckRequireAuth();
276 GetNetwork()->GetIRCSock()->ResumeCap();
277 }
278 } else if (msg.GetCode() == 906) {
279 /* CAP wasn't paused? */
280 DEBUG("sasl: Reached 906.");
281 CheckRequireAuth();
282 } else if (msg.GetCode() == 907) {
283 m_bAuthenticated = true;
284 GetNetwork()->GetIRCSock()->ResumeCap();
285 DEBUG("sasl: Received 907 -- We are already registered");
286 } else {
287 return CONTINUE;
288 }
289 return HALT;
290 }
291
OnIRCConnected()292 void OnIRCConnected() override {
293 /* Just incase something slipped through, perhaps the server doesn't
294 * respond to our CAP negotiation. */
295
296 CheckRequireAuth();
297 }
298
OnIRCDisconnected()299 void OnIRCDisconnected() override { m_bAuthenticated = false; }
300
GetWebMenuTitle()301 CString GetWebMenuTitle() override { return t_s("SASL"); }
302
OnWebRequest(CWebSock & WebSock,const CString & sPageName,CTemplate & Tmpl)303 bool OnWebRequest(CWebSock& WebSock, const CString& sPageName,
304 CTemplate& Tmpl) override {
305 if (sPageName != "index") {
306 // only accept requests to index
307 return false;
308 }
309
310 if (WebSock.IsPost()) {
311 SetNV("username", WebSock.GetParam("username"));
312 CString sPassword = WebSock.GetParam("password");
313 if (!sPassword.empty()) {
314 SetNV("password", sPassword);
315 }
316 SetNV(NV_REQUIRE_AUTH, WebSock.GetParam("require_auth"));
317 SetNV(NV_MECHANISMS, WebSock.GetParam("mechanisms"));
318 }
319
320 Tmpl["Username"] = GetNV("username");
321 Tmpl["Password"] = GetNV("password");
322 Tmpl["RequireAuth"] = GetNV(NV_REQUIRE_AUTH);
323 Tmpl["Mechanisms"] = GetMechanismsString();
324
325 for (const auto& it : SupportedMechanisms) {
326 CTemplate& Row = Tmpl.AddRow("MechanismLoop");
327 CString sName(it.szName);
328 Row["Name"] = sName;
329 Row["Description"] = it.sDescription.Resolve();
330 }
331
332 return true;
333 }
334
335 private:
336 Mechanisms m_Mechanisms;
337 bool m_bAuthenticated;
338 bool m_bVerbose = false;
339 };
340
341 template <>
TModInfo(CModInfo & Info)342 void TModInfo<CSASLMod>(CModInfo& Info) {
343 Info.SetWikiPage("sasl");
344 }
345
346 NETWORKMODULEDEFS(CSASLMod, t_s("Adds support for sasl authentication "
347 "capability to authenticate to an IRC server"))
348