1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019-2020 Matt Schatz <genius3000@g3k.solutions>
5  *   Copyright (C) 2018 linuxdaemon <linuxdaemon.irc@gmail.com>
6  *   Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org>
7  *   Copyright (C) 2013 Adam <Adam@anope.org>
8  *   Copyright (C) 2012-2015 Attila Molnar <attilamolnar@hush.com>
9  *   Copyright (C) 2012-2014, 2017-2018, 2020 Sadie Powell <sadie@witchery.services>
10  *   Copyright (C) 2012, 2018 Robby <robby@chatbelgie.be>
11  *   Copyright (C) 2012 ChrisTX <xpipe@hotmail.de>
12  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
13  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
14  *   Copyright (C) 2006-2008 Robin Burchell <robin+git@viroteck.net>
15  *   Copyright (C) 2005, 2007, 2010 Craig Edwards <brain@inspircd.org>
16  *
17  * This file is part of InspIRCd.  InspIRCd is free software: you can
18  * redistribute it and/or modify it under the terms of the GNU General Public
19  * License as published by the Free Software Foundation, version 2.
20  *
21  * This program is distributed in the hope that it will be useful, but WITHOUT
22  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
23  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
24  * details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28  */
29 
30 
31 #ifdef _WIN32
32 #define _CRT_RAND_S
33 #include <stdlib.h>
34 #endif
35 
36 #include "inspircd.h"
37 #include "xline.h"
38 
39 /* Find a user record by nickname and return a pointer to it */
FindNick(const std::string & nick)40 User* InspIRCd::FindNick(const std::string &nick)
41 {
42 	if (!nick.empty() && isdigit(*nick.begin()))
43 		return FindUUID(nick);
44 	return FindNickOnly(nick);
45 }
46 
FindNickOnly(const std::string & nick)47 User* InspIRCd::FindNickOnly(const std::string &nick)
48 {
49 	user_hash::iterator iter = this->Users->clientlist.find(nick);
50 
51 	if (iter == this->Users->clientlist.end())
52 		return NULL;
53 
54 	return iter->second;
55 }
56 
FindUUID(const std::string & uid)57 User *InspIRCd::FindUUID(const std::string &uid)
58 {
59 	user_hash::iterator finduuid = this->Users->uuidlist.find(uid);
60 
61 	if (finduuid == this->Users->uuidlist.end())
62 		return NULL;
63 
64 	return finduuid->second;
65 }
66 /* find a channel record by channel name and return a pointer to it */
67 
FindChan(const std::string & chan)68 Channel* InspIRCd::FindChan(const std::string &chan)
69 {
70 	chan_hash::iterator iter = chanlist.find(chan);
71 
72 	if (iter == chanlist.end())
73 		/* Couldn't find it */
74 		return NULL;
75 
76 	return iter->second;
77 }
78 
IsValidMask(const std::string & mask)79 bool InspIRCd::IsValidMask(const std::string &mask)
80 {
81 	const char* dest = mask.c_str();
82 	int exclamation = 0;
83 	int atsign = 0;
84 
85 	for (const char* i = dest; *i; i++)
86 	{
87 		/* out of range character, bad mask */
88 		if (*i < 32 || *i > 126)
89 		{
90 			return false;
91 		}
92 
93 		switch (*i)
94 		{
95 			case '!':
96 				exclamation++;
97 				break;
98 			case '@':
99 				atsign++;
100 				break;
101 		}
102 	}
103 
104 	/* valid masks only have 1 ! and @ */
105 	if (exclamation != 1 || atsign != 1)
106 		return false;
107 
108 	if (mask.length() > ServerInstance->Config->Limits.GetMaxMask())
109 		return false;
110 
111 	return true;
112 }
113 
StripColor(std::string & sentence)114 void InspIRCd::StripColor(std::string &sentence)
115 {
116 	/* refactor this completely due to SQUIT bug since the old code would strip last char and replace with \0 --peavey */
117 	int seq = 0;
118 
119 	for (std::string::iterator i = sentence.begin(); i != sentence.end();)
120 	{
121 		if (*i == 3)
122 			seq = 1;
123 		else if (seq && (( ((*i >= '0') && (*i <= '9')) || (*i == ',') ) ))
124 		{
125 			seq++;
126 			if ( (seq <= 4) && (*i == ',') )
127 				seq = 1;
128 			else if (seq > 3)
129 				seq = 0;
130 		}
131 		else
132 			seq = 0;
133 
134 		// Strip all control codes too except \001 for CTCP
135 		if (seq || ((*i >= 0) && (*i < 32) && (*i != 1)))
136 			i = sentence.erase(i);
137 		else
138 			++i;
139 	}
140 }
141 
ProcessColors(file_cache & input)142 void InspIRCd::ProcessColors(file_cache& input)
143 {
144 	/*
145 	 * Replace all color codes from the special[] array to actual
146 	 * color code chars using C++ style escape sequences. You
147 	 * can append other chars to replace if you like -- Justasic
148 	 */
149 	static struct special_chars
150 	{
151 		std::string character;
152 		std::string replace;
153 		special_chars(const std::string& c, const std::string& r)
154 			: character(c)
155 			, replace(r)
156 		{
157 		}
158 	} special[] = {
159 		special_chars("\\b", "\x02"), // Bold
160 		special_chars("\\c", "\x03"), // Color
161 		special_chars("\\i", "\x1D"), // Italic
162 		special_chars("\\m", "\x11"), // Monospace
163 		special_chars("\\r", "\x16"), // Reverse
164 		special_chars("\\s", "\x1E"), // Strikethrough
165 		special_chars("\\u", "\x1F"), // Underline
166 		special_chars("\\x", "\x0F"), // Reset
167 		special_chars("", "")
168 	};
169 
170 	for(file_cache::iterator it = input.begin(), it_end = input.end(); it != it_end; it++)
171 	{
172 		std::string ret = *it;
173 		for(int i = 0; special[i].character.empty() == false; ++i)
174 		{
175 			std::string::size_type pos = ret.find(special[i].character);
176 			if(pos == std::string::npos) // Couldn't find the character, skip this line
177 				continue;
178 
179 			if((pos > 0) && (ret[pos-1] == '\\') && (ret[pos] == '\\'))
180 				continue; // Skip double slashes.
181 
182 			// Replace all our characters in the array
183 			while(pos != std::string::npos)
184 			{
185 				ret = ret.substr(0, pos) + special[i].replace + ret.substr(pos + special[i].character.size());
186 				pos = ret.find(special[i].character, pos + special[i].replace.size());
187 			}
188 		}
189 
190 		// Replace double slashes with a single slash before we return
191 		std::string::size_type pos = ret.find("\\\\");
192 		while(pos != std::string::npos)
193 		{
194 			ret = ret.substr(0, pos) + "\\" + ret.substr(pos + 2);
195 			pos = ret.find("\\\\", pos + 1);
196 		}
197 		*it = ret;
198 	}
199 }
200 
201 /* true for valid channel name, false else */
DefaultIsChannel(const std::string & chname)202 bool InspIRCd::DefaultIsChannel(const std::string& chname)
203 {
204 	if (chname.empty() || chname.length() > ServerInstance->Config->Limits.ChanMax)
205 		return false;
206 
207 	if (chname[0] != '#')
208 		return false;
209 
210 	for (std::string::const_iterator i = chname.begin()+1; i != chname.end(); ++i)
211 	{
212 		switch (*i)
213 		{
214 			case ' ':
215 			case ',':
216 			case 7:
217 				return false;
218 		}
219 	}
220 
221 	return true;
222 }
223 
224 /* true for valid nickname, false else */
DefaultIsNick(const std::string & n)225 bool InspIRCd::DefaultIsNick(const std::string& n)
226 {
227 	if (n.empty() || n.length() > ServerInstance->Config->Limits.NickMax)
228 		return false;
229 
230 	for (std::string::const_iterator i = n.begin(); i != n.end(); ++i)
231 	{
232 		if ((*i >= 'A') && (*i <= '}'))
233 		{
234 			/* "A"-"}" can occur anywhere in a nickname */
235 			continue;
236 		}
237 
238 		if ((((*i >= '0') && (*i <= '9')) || (*i == '-')) && (i != n.begin()))
239 		{
240 			/* "0"-"9", "-" can occur anywhere BUT the first char of a nickname */
241 			continue;
242 		}
243 
244 		/* invalid character! abort */
245 		return false;
246 	}
247 
248 	return true;
249 }
250 
251 /* return true for good ident, false else */
DefaultIsIdent(const std::string & n)252 bool InspIRCd::DefaultIsIdent(const std::string& n)
253 {
254 	if (n.empty())
255 		return false;
256 
257 	for (std::string::const_iterator i = n.begin(); i != n.end(); ++i)
258 	{
259 		if ((*i >= 'A') && (*i <= '}'))
260 		{
261 			continue;
262 		}
263 
264 		if (((*i >= '0') && (*i <= '9')) || (*i == '-') || (*i == '.'))
265 		{
266 			continue;
267 		}
268 
269 		return false;
270 	}
271 
272 	return true;
273 }
274 
IsHost(const std::string & host)275 bool InspIRCd::IsHost(const std::string& host)
276 {
277 	// Hostnames must be non-empty and shorter than the maximum hostname length.
278 	if (host.empty() || host.length() > ServerInstance->Config->Limits.MaxHost)
279 		return false;
280 
281 	unsigned int numdashes = 0;
282 	unsigned int numdots = 0;
283 	bool seendot = false;
284 	const std::string::const_iterator hostend = host.end() - 1;
285 	for (std::string::const_iterator iter = host.begin(); iter != host.end(); ++iter)
286 	{
287 		unsigned char chr = static_cast<unsigned char>(*iter);
288 
289 		// If the current character is a label separator.
290 		if (chr == '.')
291 		{
292 			numdots++;
293 
294 			// Consecutive separators are not allowed and dashes can not exist at the start or end
295 			// of labels and separators must only exist between labels.
296 			if (seendot || numdashes || iter == host.begin() || iter == hostend)
297 				return false;
298 
299 			seendot = true;
300 			continue;
301 		}
302 
303 		// If this point is reached then the character is not a dot.
304 		seendot = false;
305 
306 		// If the current character is a dash.
307 		if (chr == '-')
308 		{
309 			// Consecutive separators are not allowed and dashes can not exist at the start or end
310 			// of labels and separators must only exist between labels.
311 			if (seendot || numdashes >= 2 || iter == host.begin() || iter == hostend)
312 				return false;
313 
314 			numdashes += 1;
315 			continue;
316 		}
317 
318 		// If this point is reached then the character is not a dash.
319 		numdashes = 0;
320 
321 		// Alphanumeric characters are allowed at any position.
322 		if ((chr >= '0' && chr <= '9') || (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z'))
323 			continue;
324 
325 		return false;
326 	}
327 
328 	// Whilst simple hostnames (e.g. localhost) are valid we do not allow the server to use
329 	// them to prevent issues with clients that differentiate between short client and server
330 	// prefixes by checking whether the nickname contains a dot.
331 	return numdots;
332 }
333 
IsSID(const std::string & str)334 bool InspIRCd::IsSID(const std::string &str)
335 {
336 	/* Returns true if the string given is exactly 3 characters long,
337 	 * starts with a digit, and the other two characters are A-Z or digits
338 	 */
339 	return ((str.length() == 3) && isdigit(str[0]) &&
340 			((str[1] >= 'A' && str[1] <= 'Z') || isdigit(str[1])) &&
341 			((str[2] >= 'A' && str[2] <= 'Z') || isdigit(str[2])));
342 }
343 
344 /** A lookup table of values for multiplier characters used by
345  * InspIRCd::Duration(). In this lookup table, the indexes for
346  * the ascii values 'm' and 'M' have the value '60', the indexes
347  * for the ascii values 'D' and 'd' have a value of '86400', etc.
348  */
349 static const unsigned int duration_multi[] =
350 {
351 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
352 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
353 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
354 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
355 	0, 0, 0, 0, 86400, 0, 0, 0, 3600, 0, 0, 0, 0, 60, 0, 0,
356 	0, 0, 0, 1, 0, 0, 0, 604800, 0, 31557600, 0, 0, 0, 0, 0, 0,
357 	0, 0, 0, 0, 86400, 0, 0, 0, 3600, 0, 0, 0, 0, 60, 0, 0,
358 	0, 0, 0, 1, 0, 0, 0, 604800, 0, 31557600, 0, 0, 0, 0, 0, 0,
359 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
360 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
361 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
362 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
363 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
364 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
365 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
366 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
367 };
368 
Duration(const std::string & str,unsigned long & duration)369 bool InspIRCd::Duration(const std::string& str, unsigned long& duration)
370 {
371 	unsigned long total = 0;
372 	unsigned long subtotal = 0;
373 
374 	/* Iterate each item in the string, looking for number or multiplier */
375 	for (std::string::const_iterator i = str.begin(); i != str.end(); ++i)
376 	{
377 		/* Found a number, queue it onto the current number */
378 		if ((*i >= '0') && (*i <= '9'))
379 		{
380 			subtotal = (subtotal * 10) + (*i - '0');
381 		}
382 		else
383 		{
384 			/* Found something that's not a number, find out how much
385 			 * it multiplies the built up number by, multiply the total
386 			 * and reset the built up number.
387 			 */
388 			unsigned int multiplier = duration_multi[static_cast<unsigned char>(*i)];
389 			if (multiplier == 0)
390 				return false;
391 
392 			total += subtotal * multiplier;
393 
394 			/* Next subtotal please */
395 			subtotal = 0;
396 		}
397 	}
398 	/* Any trailing values built up are treated as raw seconds */
399 	duration = total + subtotal;
400 	return true;
401 }
402 
Duration(const std::string & str)403 unsigned long InspIRCd::Duration(const std::string& str)
404 {
405 	unsigned long out = 0;
406 	InspIRCd::Duration(str, out);
407 	return out;
408 }
409 
IsValidDuration(const std::string & duration)410 bool InspIRCd::IsValidDuration(const std::string& duration)
411 {
412 	for (std::string::const_iterator i = duration.begin(); i != duration.end(); ++i)
413 	{
414 		unsigned char c = *i;
415 		if (((c >= '0') && (c <= '9')))
416 			continue;
417 
418 		if (!duration_multi[c])
419 			return false;
420 	}
421 	return true;
422 }
423 
DurationString(time_t duration)424 std::string InspIRCd::DurationString(time_t duration)
425 {
426 	if (duration == 0)
427 		return "0s";
428 
429 	time_t years = duration / 31449600;
430 	time_t weeks = (duration / 604800) % 52;
431 	time_t days = (duration / 86400) % 7;
432 	time_t hours = (duration / 3600) % 24;
433 	time_t minutes = (duration / 60) % 60;
434 	time_t seconds = duration % 60;
435 
436 	std::string ret;
437 
438 	if (years)
439 		ret = ConvToStr(years) + "y";
440 	if (weeks)
441 		ret += ConvToStr(weeks) + "w";
442 	if (days)
443 		ret += ConvToStr(days) + "d";
444 	if (hours)
445 		ret += ConvToStr(hours) + "h";
446 	if (minutes)
447 		ret += ConvToStr(minutes) + "m";
448 	if (seconds)
449 		ret += ConvToStr(seconds) + "s";
450 
451 	return ret;
452 }
453 
Format(va_list & vaList,const char * formatString)454 std::string InspIRCd::Format(va_list& vaList, const char* formatString)
455 {
456 	static std::vector<char> formatBuffer(1024);
457 
458 	while (true)
459 	{
460 		va_list dst;
461 		va_copy(dst, vaList);
462 
463 		int vsnret = vsnprintf(&formatBuffer[0], formatBuffer.size(), formatString, dst);
464 		va_end(dst);
465 
466 		if (vsnret > 0 && static_cast<unsigned>(vsnret) < formatBuffer.size())
467 		{
468 			break;
469 		}
470 
471 		formatBuffer.resize(formatBuffer.size() * 2);
472 	}
473 
474 	return std::string(&formatBuffer[0]);
475 }
476 
Format(const char * formatString,...)477 std::string InspIRCd::Format(const char* formatString, ...)
478 {
479 	std::string ret;
480 	VAFORMAT(ret, formatString, formatString);
481 	return ret;
482 }
483 
TimeString(time_t curtime,const char * format,bool utc)484 std::string InspIRCd::TimeString(time_t curtime, const char* format, bool utc)
485 {
486 #ifdef _WIN32
487 	if (curtime < 0)
488 		curtime = 0;
489 #endif
490 
491 	struct tm* timeinfo = utc ? gmtime(&curtime) : localtime(&curtime);
492 	if (!timeinfo)
493 	{
494 		curtime = 0;
495 		timeinfo = localtime(&curtime);
496 	}
497 
498 	// If the calculated year exceeds four digits or is less than the year 1000,
499 	// the behavior of asctime() is undefined
500 	if (timeinfo->tm_year + 1900 > 9999)
501 		timeinfo->tm_year = 9999 - 1900;
502 	else if (timeinfo->tm_year + 1900 < 1000)
503 		timeinfo->tm_year = 0;
504 
505 	// This is the default format used by asctime without the terminating new line.
506 	if (!format)
507 		format = "%a %b %d %Y %H:%M:%S";
508 
509 	char buffer[512];
510 	if (!strftime(buffer, sizeof(buffer), format, timeinfo))
511 		buffer[0] = '\0';
512 
513 	return buffer;
514 }
515 
GenRandomStr(unsigned int length,bool printable)516 std::string InspIRCd::GenRandomStr(unsigned int length, bool printable)
517 {
518 	std::vector<char> str(length);
519 	GenRandom(&str[0], length);
520 	if (printable)
521 		for (size_t i = 0; i < length; i++)
522 			str[i] = 0x3F + (str[i] & 0x3F);
523 	return std::string(&str[0], str.size());
524 }
525 
526 // NOTE: this has a slight bias for lower values if max is not a power of 2.
527 // Don't use it if that matters.
GenRandomInt(unsigned long max)528 unsigned long InspIRCd::GenRandomInt(unsigned long max)
529 {
530 	unsigned long rv;
531 	GenRandom((char*)&rv, sizeof(rv));
532 	return rv % max;
533 }
534 
535 // This is overridden by a higher-quality algorithm when TLS (SSL) support is loaded
DefaultGenRandom(char * output,size_t max)536 void InspIRCd::DefaultGenRandom(char* output, size_t max)
537 {
538 #if defined HAS_ARC4RANDOM_BUF
539 	arc4random_buf(output, max);
540 #else
541 	for (unsigned int i = 0; i < max; ++i)
542 # ifdef _WIN32
543 	{
544 		unsigned int uTemp;
545 		if(rand_s(&uTemp) != 0)
546 			output[i] = rand();
547 		else
548 			output[i] = uTemp;
549 	}
550 # else
551 		output[i] = random();
552 # endif
553 #endif
554 }
555