1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2013, 2018-2020 Sadie Powell <sadie@witchery.services>
5  *   Copyright (C) 2012-2015 Attila Molnar <attilamolnar@hush.com>
6  *   Copyright (C) 2012-2013 Robby <robby@chatbelgie.be>
7  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
8  *   Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
9  *   Copyright (C) 2007 John Brooks <special@inspircd.org>
10  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
11  *   Copyright (C) 2005, 2007, 2010 Craig Edwards <brain@inspircd.org>
12  *
13  * This file is part of InspIRCd.  InspIRCd is free software: you can
14  * redistribute it and/or modify it under the terms of the GNU General Public
15  * License as published by the Free Software Foundation, version 2.
16  *
17  * This program is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25 
26 
27 #include "inspircd.h"
28 
29 enum
30 {
31 	// Either the ident lookup has not started yet or the user is registered.
32 	IDENT_UNKNOWN = 0,
33 
34 	// Ident lookups are not enabled and a user has been marked as being skipped.
35 	IDENT_SKIPPED,
36 
37 	// Ident lookups are not enabled and a user has been an insecure ident prefix.
38 	IDENT_PREFIXED,
39 
40 	// An ident lookup was done and an ident was found.
41 	IDENT_FOUND,
42 
43 	// An ident lookup was done but no ident was found
44 	IDENT_MISSING
45 };
46 
47 /* --------------------------------------------------------------
48  * Note that this is the third incarnation of m_ident. The first
49  * two attempts were pretty crashy, mainly due to the fact we tried
50  * to use InspSocket/BufferedSocket to make them work. This class
51  * is ok for more heavyweight tasks, it does a lot of things behind
52  * the scenes that are not good for ident sockets and it has a huge
53  * memory footprint!
54  *
55  * To fix all the issues that we had in the old ident modules (many
56  * nasty race conditions that would cause segfaults etc) we have
57  * rewritten this module to use a simplified socket object based
58  * directly off EventHandler. As EventHandler only has low level
59  * readability, writability and error events tied directly to the
60  * socket engine, this makes our lives easier as nothing happens to
61  * our ident lookup class that is outside of this module, or out-
62  * side of the control of the class. There are no timers, internal
63  * events, or such, which will cause the socket to be deleted,
64  * queued for deletion, etc. In fact, there's not even any queueing!
65  *
66  * Using this framework we have a much more stable module.
67  *
68  * A few things to note:
69  *
70  *   O  The only place that may *delete* an active or inactive
71  *      ident socket is OnUserDisconnect in the module class.
72  *      Because this is out of scope of the socket class there is
73  *      no possibility that the socket may ever try to delete
74  *      itself.
75  *
76  *   O  Closure of the ident socket with the Close() method will
77  *      not cause removal of the socket from memory or detachment
78  *      from its 'parent' User class. It will only flag it as an
79  *      inactive socket in the socket engine.
80  *
81  *   O  Timeouts are handled in OnCheckReady at the same time as
82  *      checking if the ident socket has a result. This is done
83  *      by checking if the age the of the class (its instantiation
84  *      time) plus the timeout value is greater than the current time.
85  *
86  *  O   The ident socket is able to but should not modify its
87  *      'parent' user directly. Instead the ident socket class sets
88  *      a completion flag and during the next call to OnCheckReady,
89  *      the completion flag will be checked and any result copied to
90  *      that user's class. This again ensures a single point of socket
91  *      deletion for safer, neater code.
92  *
93  *  O   The code in the constructor of the ident socket is taken from
94  *      BufferedSocket but majorly thinned down. It works for both
95  *      IPv4 and IPv6.
96  *
97  *  O   In the event that the ident socket throws a ModuleException,
98  *      nothing is done. This is counted as total and complete
99  *      failure to create a connection.
100  * --------------------------------------------------------------
101  */
102 
103 class IdentRequestSocket : public EventHandler
104 {
105  public:
106 	LocalUser *user;			/* User we are attached to */
107 	std::string result;		/* Holds the ident string if done */
108 	time_t age;
109 	bool done;			/* True if lookup is finished */
110 
IdentRequestSocket(LocalUser * u)111 	IdentRequestSocket(LocalUser* u) : user(u)
112 	{
113 		age = ServerInstance->Time();
114 
115 		SetFd(socket(user->server_sa.family(), SOCK_STREAM, 0));
116 		if (!HasFd())
117 			throw ModuleException("Could not create socket");
118 
119 		done = false;
120 
121 		irc::sockets::sockaddrs bindaddr;
122 		irc::sockets::sockaddrs connaddr;
123 
124 		memcpy(&bindaddr, &user->server_sa, sizeof(bindaddr));
125 		memcpy(&connaddr, &user->client_sa, sizeof(connaddr));
126 
127 		if (connaddr.family() == AF_INET6)
128 		{
129 			bindaddr.in6.sin6_port = 0;
130 			connaddr.in6.sin6_port = htons(113);
131 		}
132 		else
133 		{
134 			bindaddr.in4.sin_port = 0;
135 			connaddr.in4.sin_port = htons(113);
136 		}
137 
138 		/* Attempt to bind (ident requests must come from the ip the query is referring to */
139 		if (SocketEngine::Bind(GetFd(), bindaddr) < 0)
140 		{
141 			this->Close();
142 			throw ModuleException("failed to bind()");
143 		}
144 
145 		SocketEngine::NonBlocking(GetFd());
146 
147 		/* Attempt connection (nonblocking) */
148 		if (SocketEngine::Connect(this, connaddr) == -1 && errno != EINPROGRESS)
149 		{
150 			this->Close();
151 			throw ModuleException("connect() failed");
152 		}
153 
154 		/* Add fd to socket engine */
155 		if (!SocketEngine::AddFd(this, FD_WANT_NO_READ | FD_WANT_POLL_WRITE))
156 		{
157 			this->Close();
158 			throw ModuleException("out of fds");
159 		}
160 	}
161 
OnEventHandlerWrite()162 	void OnEventHandlerWrite() CXX11_OVERRIDE
163 	{
164 		SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
165 
166 		char req[32];
167 
168 		/* Build request in the form 'localport,remoteport\r\n' */
169 		int req_size;
170 		if (user->client_sa.family() == AF_INET6)
171 			req_size = snprintf(req, sizeof(req), "%d,%d\r\n",
172 				ntohs(user->client_sa.in6.sin6_port), ntohs(user->server_sa.in6.sin6_port));
173 		else
174 			req_size = snprintf(req, sizeof(req), "%d,%d\r\n",
175 				ntohs(user->client_sa.in4.sin_port), ntohs(user->server_sa.in4.sin_port));
176 
177 		/* Send failed if we didnt write the whole ident request --
178 		 * might as well give up if this happens!
179 		 */
180 		if (SocketEngine::Send(this, req, req_size, 0) < req_size)
181 			done = true;
182 	}
183 
Close()184 	void Close()
185 	{
186 		/* Remove ident socket from engine, and close it, but dont detach it
187 		 * from its parent user class, or attempt to delete its memory.
188 		 */
189 		if (HasFd())
190 		{
191 			ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Close ident socket %d", GetFd());
192 			SocketEngine::Close(this);
193 		}
194 	}
195 
HasResult()196 	bool HasResult()
197 	{
198 		return done;
199 	}
200 
OnEventHandlerRead()201 	void OnEventHandlerRead() CXX11_OVERRIDE
202 	{
203 		/* We don't really need to buffer for incomplete replies here, since IDENT replies are
204 		 * extremely short - there is *no* sane reason it'd be in more than one packet
205 		 */
206 		char ibuf[256];
207 		int recvresult = SocketEngine::Recv(this, ibuf, sizeof(ibuf)-1, 0);
208 
209 		/* Close (but don't delete from memory) our socket
210 		 * and flag as done since the ident lookup has finished
211 		 */
212 		Close();
213 		done = true;
214 
215 		/* Cant possibly be a valid response shorter than 3 chars,
216 		 * because the shortest possible response would look like: '1,1'
217 		 */
218 		if (recvresult < 3)
219 			return;
220 
221 		ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "ReadResponse()");
222 
223 		/* Truncate at the first null character, but first make sure
224 		 * there is at least one null char (at the end of the buffer).
225 		 */
226 		ibuf[recvresult] = '\0';
227 		std::string buf(ibuf);
228 
229 		/* <2 colons: invalid
230 		 *  2 colons: reply is an error
231 		 * >3 colons: there is a colon in the ident
232 		 */
233 		if (std::count(buf.begin(), buf.end(), ':') != 3)
234 			return;
235 
236 		std::string::size_type lastcolon = buf.rfind(':');
237 
238 		/* Truncate the ident at any characters we don't like, skip leading spaces */
239 		for (std::string::const_iterator i = buf.begin()+lastcolon+1; i != buf.end(); ++i)
240 		{
241 			if (result.size() == ServerInstance->Config->Limits.IdentMax)
242 				/* Ident is getting too long */
243 				break;
244 
245 			if (*i == ' ')
246 				continue;
247 
248 			/* Add the next char to the result and see if it's still a valid ident,
249 			 * according to IsIdent(). If it isn't, then erase what we just added and
250 			 * we're done.
251 			 */
252 			result += *i;
253 			if (!ServerInstance->IsIdent(result))
254 			{
255 				result.erase(result.end()-1);
256 				break;
257 			}
258 		}
259 	}
260 
OnEventHandlerError(int errornum)261 	void OnEventHandlerError(int errornum) CXX11_OVERRIDE
262 	{
263 		Close();
264 		done = true;
265 	}
266 
cull()267 	CullResult cull() CXX11_OVERRIDE
268 	{
269 		Close();
270 		return EventHandler::cull();
271 	}
272 };
273 
274 class ModuleIdent : public Module
275 {
276  private:
277 	unsigned int timeout;
278 	bool prefixunqueried;
279 	SimpleExtItem<IdentRequestSocket, stdalgo::culldeleter> socket;
280 	LocalIntExt state;
281 
PrefixIdent(LocalUser * user)282 	static void PrefixIdent(LocalUser* user)
283 	{
284 		// Check that they haven't been prefixed already.
285 		if (user->ident[0] == '~')
286 			return;
287 
288 		// All invalid usernames are prefixed with a tilde.
289 		std::string newident(user->ident);
290 		newident.insert(newident.begin(), '~');
291 
292 		// If the username is too long then truncate it.
293 		if (newident.length() > ServerInstance->Config->Limits.IdentMax)
294 			newident.erase(ServerInstance->Config->Limits.IdentMax);
295 
296 		// Apply the new username.
297 		user->ChangeIdent(newident);
298 	}
299 
300  public:
ModuleIdent()301 	ModuleIdent()
302 		: socket("ident_socket", ExtensionItem::EXT_USER, this)
303 		, state("ident_state", ExtensionItem::EXT_USER, this)
304 	{
305 	}
306 
GetVersion()307 	Version GetVersion() CXX11_OVERRIDE
308 	{
309 		return Version("Allows the usernames (idents) of users to be looked up using the RFC 1413 Identification Protocol.", VF_VENDOR);
310 	}
311 
ReadConfig(ConfigStatus & status)312 	void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
313 	{
314 		ConfigTag* tag = ServerInstance->Config->ConfValue("ident");
315 		timeout = tag->getDuration("timeout", 5, 1, 60);
316 		prefixunqueried = tag->getBool("prefixunqueried");
317 	}
318 
OnSetUserIP(LocalUser * user)319 	void OnSetUserIP(LocalUser* user) CXX11_OVERRIDE
320 	{
321 		IdentRequestSocket* isock = socket.get(user);
322 		if (isock)
323 		{
324 			// If an ident lookup request was in progress then cancel it.
325 			isock->Close();
326 			socket.unset(user);
327 		}
328 
329 		// The ident protocol requires that clients are connecting over a protocol with ports.
330 		if (user->client_sa.family() != AF_INET && user->client_sa.family() != AF_INET6)
331 			return;
332 
333 		// We don't want to look this up once the user has connected.
334 		if (user->registered == REG_ALL || user->quitting)
335 			return;
336 
337 		ConfigTag* tag = user->MyClass->config;
338 		if (!tag->getBool("useident", true))
339 		{
340 			state.set(user, IDENT_SKIPPED);
341 			return;
342 		}
343 
344 		user->WriteNotice("*** Looking up your ident...");
345 
346 		try
347 		{
348 			isock = new IdentRequestSocket(user);
349 			socket.set(user, isock);
350 		}
351 		catch (ModuleException &e)
352 		{
353 			ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Ident exception: " + e.GetReason());
354 		}
355 	}
356 
357 	/* This triggers pretty regularly, we can use it in preference to
358 	 * creating a Timer object and especially better than creating a
359 	 * Timer per ident lookup!
360 	 */
OnCheckReady(LocalUser * user)361 	ModResult OnCheckReady(LocalUser *user) CXX11_OVERRIDE
362 	{
363 		/* Does user have an ident socket attached at all? */
364 		IdentRequestSocket* isock = socket.get(user);
365 		if (!isock)
366 		{
367 			if (prefixunqueried && state.get(user) == IDENT_SKIPPED)
368 			{
369 				PrefixIdent(user);
370 				state.set(user, IDENT_PREFIXED);
371 			}
372 			return MOD_RES_PASSTHRU;
373 		}
374 
375 		time_t compare = isock->age + timeout;
376 
377 		/* Check for timeout of the socket */
378 		if (ServerInstance->Time() >= compare)
379 		{
380 			/* Ident timeout */
381 			state.set(user, IDENT_MISSING);
382 			PrefixIdent(user);
383 			user->WriteNotice("*** Ident lookup timed out, using " + user->ident + " instead.");
384 		}
385 		else if (!isock->HasResult())
386 		{
387 			// time still good, no result yet... hold the registration
388 			return MOD_RES_DENY;
389 		}
390 
391 		/* wooo, got a result (it will be good, or bad) */
392 		else if (isock->result.empty())
393 		{
394 			state.set(user, IDENT_MISSING);
395 			PrefixIdent(user);
396 			user->WriteNotice("*** Could not find your ident, using " + user->ident + " instead.");
397 		}
398 		else
399 		{
400 			state.set(user, IDENT_FOUND);
401 			user->ChangeIdent(isock->result);
402 			user->WriteNotice("*** Found your ident, '" + user->ident + "'");
403 		}
404 
405 		isock->Close();
406 		socket.unset(user);
407 		return MOD_RES_PASSTHRU;
408 	}
409 
OnSetConnectClass(LocalUser * user,ConnectClass * myclass)410 	ModResult OnSetConnectClass(LocalUser* user, ConnectClass* myclass) CXX11_OVERRIDE
411 	{
412 		if (myclass->config->getBool("requireident") && state.get(user) != IDENT_FOUND)
413 		{
414 			ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "The %s connect class is not suitable as it requires an identd response",
415 				myclass->GetName().c_str());
416 			return MOD_RES_DENY;
417 		}
418 		return MOD_RES_PASSTHRU;
419 	}
420 
OnUserConnect(LocalUser * user)421 	void OnUserConnect(LocalUser* user) CXX11_OVERRIDE
422 	{
423 		// Clear this as it is no longer necessary.
424 		state.unset(user);
425 	}
426 };
427 
428 MODULE_INIT(ModuleIdent)
429