1 /*
2  *
3  * (C) 2014-2020 Anope Team
4  * Contact us at team@anope.org
5  *
6  * Please read COPYING and README for further details.
7  */
8 
9 #include "module.h"
10 #include "modules/sasl.h"
11 #include "modules/ns_cert.h"
12 
13 using namespace SASL;
14 
15 class Plain : public Mechanism
16 {
17  public:
Plain(Module * o)18 	Plain(Module *o) : Mechanism(o, "PLAIN") { }
19 
ProcessMessage(Session * sess,const SASL::Message & m)20 	void ProcessMessage(Session *sess, const SASL::Message &m) anope_override
21 	{
22 		if (m.type == "S")
23 		{
24 			sasl->SendMessage(sess, "C", "+");
25 		}
26 		else if (m.type == "C")
27 		{
28 			Anope::string decoded;
29 			Anope::B64Decode(m.data, decoded);
30 
31 			size_t p = decoded.find('\0');
32 			if (p == Anope::string::npos)
33 			{
34 				sasl->Fail(sess);
35 				delete sess;
36 				return;
37 			}
38 			decoded = decoded.substr(p + 1);
39 
40 			p = decoded.find('\0');
41 			if (p == Anope::string::npos)
42 			{
43 				sasl->Fail(sess);
44 				delete sess;
45 				return;
46 			}
47 
48 			Anope::string acc = decoded.substr(0, p),
49 				pass = decoded.substr(p + 1);
50 
51 			if (acc.empty() || pass.empty() || !IRCD->IsNickValid(acc) || pass.find_first_of("\r\n") != Anope::string::npos)
52 			{
53 				sasl->Fail(sess);
54 				delete sess;
55 				return;
56 			}
57 
58 			SASL::IdentifyRequest *req = new SASL::IdentifyRequest(this->owner, m.source, acc, pass, sess->hostname, sess->ip);
59 			FOREACH_MOD(OnCheckAuthentication, (NULL, req));
60 			req->Dispatch();
61 		}
62 	}
63 };
64 
65 class External : public Mechanism
66 {
67 	ServiceReference<CertService> certs;
68 
69 	struct Session : SASL::Session
70 	{
71 		Anope::string cert;
72 
SessionExternal::Session73 		Session(Mechanism *m, const Anope::string &u) : SASL::Session(m, u) { }
74 	};
75 
76  public:
External(Module * o)77 	External(Module *o) : Mechanism(o, "EXTERNAL"), certs("CertService", "certs")
78 	{
79 		if (!IRCD || !IRCD->CanCertFP)
80 			throw ModuleException("No CertFP");
81 	}
82 
CreateSession(const Anope::string & uid)83 	Session* CreateSession(const Anope::string &uid) anope_override
84 	{
85 		return new Session(this, uid);
86 	}
87 
ProcessMessage(SASL::Session * sess,const SASL::Message & m)88 	void ProcessMessage(SASL::Session *sess, const SASL::Message &m) anope_override
89 	{
90 		Session *mysess = anope_dynamic_static_cast<Session *>(sess);
91 
92 		if (m.type == "S")
93 		{
94 			mysess->cert = m.ext;
95 
96 			sasl->SendMessage(sess, "C", "+");
97 		}
98 		else if (m.type == "C")
99 		{
100 			if (!certs || mysess->cert.empty())
101 			{
102 				sasl->Fail(sess);
103 				delete sess;
104 				return;
105 			}
106 
107 			Anope::string user = "A user";
108 			if (!mysess->hostname.empty() && !mysess->ip.empty())
109 				user = mysess->hostname + " (" + mysess->ip + ")";
110 
111 			NickCore *nc = certs->FindAccountFromCert(mysess->cert);
112 			if (!nc || nc->HasExt("NS_SUSPENDED") || nc->HasExt("UNCONFIRMED"))
113 			{
114 				Log(this->owner, "sasl", Config->GetClient("NickServ")) << user << " failed to identify using certificate " << mysess->cert << " using SASL EXTERNAL";
115 				sasl->Fail(sess);
116 				delete sess;
117 				return;
118 			}
119 
120 			Log(this->owner, "sasl", Config->GetClient("NickServ")) << user << " identified to account " << nc->display << " using SASL EXTERNAL";
121 			sasl->Succeed(sess, nc);
122 			delete sess;
123 		}
124 	}
125 };
126 
127 class SASLService : public SASL::Service, public Timer
128 {
129 	std::map<Anope::string, SASL::Session *> sessions;
130 
131  public:
SASLService(Module * o)132 	SASLService(Module *o) : SASL::Service(o), Timer(o, 60, Anope::CurTime, true) { }
133 
~SASLService()134 	~SASLService()
135 	{
136 		for (std::map<Anope::string, Session *>::iterator it = sessions.begin(); it != sessions.end(); it++)
137 			delete it->second;
138 	}
139 
ProcessMessage(const SASL::Message & m)140 	void ProcessMessage(const SASL::Message &m) anope_override
141 	{
142 		if (m.target != "*")
143 		{
144 			Server *s = Server::Find(m.target);
145 			if (s != Me)
146 			{
147 				User *u = User::Find(m.target);
148 				if (!u || u->server != Me)
149 					return;
150 			}
151 		}
152 
153 		Session* session = GetSession(m.source);
154 
155 		if (m.type == "S")
156 		{
157 			ServiceReference<Mechanism> mech("SASL::Mechanism", m.data);
158 			if (!mech)
159 			{
160 				Session tmp(NULL, m.source);
161 
162 				sasl->SendMechs(&tmp);
163 				sasl->Fail(&tmp);
164 				return;
165 			}
166 
167 			Anope::string hostname, ip;
168 			if (session)
169 			{
170 				// Copy over host/ip to mech-specific session
171 				hostname = session->hostname;
172 				ip = session->ip;
173 				delete session;
174 			}
175 
176 			session = mech->CreateSession(m.source);
177 			if (session)
178 			{
179 				session->hostname = hostname;
180 				session->ip = ip;
181 
182 				sessions[m.source] = session;
183 			}
184 		}
185 		else if (m.type == "D")
186 		{
187 			delete session;
188 			return;
189 		}
190 		else if (m.type == "H")
191 		{
192 			if (!session)
193 			{
194 				session = new Session(NULL, m.source);
195 				sessions[m.source] = session;
196 			}
197 			session->hostname = m.data;
198 			session->ip = m.ext;
199 		}
200 
201 		if (session && session->mech)
202 			session->mech->ProcessMessage(session, m);
203 	}
204 
GetAgent()205 	Anope::string GetAgent() anope_override
206 	{
207 		Anope::string agent = Config->GetModule(Service::owner)->Get<Anope::string>("agent", "NickServ");
208 		BotInfo *bi = Config->GetClient(agent);
209 		if (bi)
210 			agent = bi->GetUID();
211 		return agent;
212 	}
213 
GetSession(const Anope::string & uid)214 	Session* GetSession(const Anope::string &uid) anope_override
215 	{
216 		std::map<Anope::string, Session *>::iterator it = sessions.find(uid);
217 		if (it != sessions.end())
218 			return it->second;
219 		return NULL;
220 	}
221 
RemoveSession(Session * sess)222 	void RemoveSession(Session *sess) anope_override
223 	{
224 		sessions.erase(sess->uid);
225 	}
226 
DeleteSessions(Mechanism * mech,bool da)227 	void DeleteSessions(Mechanism *mech, bool da) anope_override
228 	{
229 		for (std::map<Anope::string, Session *>::iterator it = sessions.begin(); it != sessions.end();)
230 		{
231 			std::map<Anope::string, Session *>::iterator del = it++;
232 			if (*del->second->mech == mech)
233 			{
234 				if (da)
235 					this->SendMessage(del->second, "D", "A");
236 				delete del->second;
237 			}
238 		}
239 	}
240 
SendMessage(Session * session,const Anope::string & mtype,const Anope::string & data)241 	void SendMessage(Session *session, const Anope::string &mtype, const Anope::string &data) anope_override
242 	{
243 		SASL::Message msg;
244 		msg.source = this->GetAgent();
245 		msg.target = session->uid;
246 		msg.type = mtype;
247 		msg.data = data;
248 
249 		IRCD->SendSASLMessage(msg);
250 	}
251 
Succeed(Session * session,NickCore * nc)252 	void Succeed(Session *session, NickCore *nc) anope_override
253 	{
254 		// If the user is already introduced then we log them in now.
255 		// Otherwise, we send an SVSLOGIN to log them in later.
256 		User *user = User::Find(session->uid);
257 		NickAlias *na = NickAlias::Find(nc->display);
258 		if (user)
259 		{
260 			user->Identify(na);
261 		}
262 		else
263 		{
264 			IRCD->SendSVSLogin(session->uid, nc->display, na->GetVhostIdent(), na->GetVhostHost());
265 		}
266 		this->SendMessage(session, "D", "S");
267 	}
268 
Fail(Session * session)269 	void Fail(Session *session) anope_override
270 	{
271 		this->SendMessage(session, "D", "F");
272 	}
273 
SendMechs(Session * session)274 	void SendMechs(Session *session) anope_override
275 	{
276 		std::vector<Anope::string> mechs = Service::GetServiceKeys("SASL::Mechanism");
277 		Anope::string buf;
278 		for (unsigned j = 0; j < mechs.size(); ++j)
279 			buf += "," + mechs[j];
280 
281 		this->SendMessage(session, "M", buf.empty() ? "" : buf.substr(1));
282 	}
283 
Tick(time_t)284 	void Tick(time_t) anope_override
285 	{
286 		for (std::map<Anope::string, Session *>::iterator it = sessions.begin(); it != sessions.end();)
287 		{
288 			Anope::string key = it->first;
289 			Session *s = it->second;
290 			++it;
291 
292 			if (!s || s->created + 60 < Anope::CurTime)
293 			{
294 				delete s;
295 				sessions.erase(key);
296 			}
297 		}
298 	}
299 };
300 
301 class ModuleSASL : public Module
302 {
303 	SASLService sasl;
304 
305 	Plain plain;
306 	External *external;
307 
308 	std::vector<Anope::string> mechs;
309 
CheckMechs()310 	void CheckMechs()
311 	{
312 		std::vector<Anope::string> newmechs = ::Service::GetServiceKeys("SASL::Mechanism");
313 		if (newmechs == mechs)
314 			return;
315 
316 		mechs = newmechs;
317 
318 		// If we are connected to the network then broadcast the mechlist.
319 		if (Me && Me->IsSynced())
320 			IRCD->SendSASLMechanisms(mechs);
321 	}
322 
323  public:
ModuleSASL(const Anope::string & modname,const Anope::string & creator)324 	ModuleSASL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
325 		sasl(this), plain(this), external(NULL)
326 	{
327 		try
328 		{
329 			external = new External(this);
330 			CheckMechs();
331 		}
332 		catch (ModuleException &) { }
333 	}
334 
~ModuleSASL()335 	~ModuleSASL()
336 	{
337 		delete external;
338 	}
339 
OnModuleLoad(User *,Module *)340 	void OnModuleLoad(User *, Module *) anope_override
341 	{
342 		CheckMechs();
343 	}
344 
OnModuleUnload(User *,Module *)345 	void OnModuleUnload(User *, Module *) anope_override
346 	{
347 		CheckMechs();
348 	}
349 
OnPreUplinkSync(Server *)350 	void OnPreUplinkSync(Server *) anope_override
351 	{
352 		// We have not yet sent a mechanism list so always do it here.
353 		IRCD->SendSASLMechanisms(mechs);
354 	}
355 };
356 
357 MODULE_INIT(ModuleSASL)
358