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