1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019 linuxdaemon <linuxdaemon.irc@gmail.com>
5  *   Copyright (C) 2016 Adam <Adam@anope.org>
6  *   Copyright (C) 2015 Daniel Vassdal <shutter@canternet.org>
7  *   Copyright (C) 2013-2014 Attila Molnar <attilamolnar@hush.com>
8  *   Copyright (C) 2013, 2016-2021 Sadie Powell <sadie@witchery.services>
9  *   Copyright (C) 2012 Robby <robby@chatbelgie.be>
10  *   Copyright (C) 2012 ChrisTX <xpipe@hotmail.de>
11  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
12  *   Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
13  *   Copyright (C) 2007, 2009 Dennis Friis <peavey@inspircd.org>
14  *   Copyright (C) 2005, 2008-2010 Craig Edwards <brain@inspircd.org>
15  *
16  * This file is part of InspIRCd.  InspIRCd is free software: you can
17  * redistribute it and/or modify it under the terms of the GNU General Public
18  * License as published by the Free Software Foundation, version 2.
19  *
20  * This program is distributed in the hope that it will be useful, but WITHOUT
21  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
22  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
23  * details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27  */
28 
29 /// $CompilerFlags: execute("mysql_config --include" "MYSQL_CXXFLAGS")
30 /// $LinkerFlags: execute("mysql_config --libs_r" "MYSQL_LDFLAGS" "-lmysqlclient")
31 
32 /// $PackageInfo: require_system("arch") mariadb-libs
33 /// $PackageInfo: require_system("centos" "6.0" "6.99") mysql-devel
34 /// $PackageInfo: require_system("centos" "7.0") mariadb-devel
35 /// $PackageInfo: require_system("darwin") mysql-connector-c
36 /// $PackageInfo: require_system("debian") libmysqlclient-dev
37 /// $PackageInfo: require_system("ubuntu") libmysqlclient-dev
38 
39 #ifdef __GNUC__
40 # pragma GCC diagnostic push
41 #endif
42 
43 // Fix warnings about the use of `long long` on C++03.
44 #if defined __clang__
45 # pragma clang diagnostic ignored "-Wc++11-long-long"
46 #elif defined __GNUC__
47 # pragma GCC diagnostic ignored "-Wlong-long"
48 #endif
49 
50 #include "inspircd.h"
51 #include <mysql.h>
52 #include "modules/sql.h"
53 
54 #ifdef __GNUC__
55 # pragma GCC diagnostic pop
56 #endif
57 
58 #ifdef _WIN32
59 # pragma comment(lib, "mysqlclient.lib")
60 #endif
61 
62 /* VERSION 3 API: With nonblocking (threaded) requests */
63 
64 /* THE NONBLOCKING MYSQL API!
65  *
66  * MySQL provides no nonblocking (asynchronous) API of its own, and its developers recommend
67  * that instead, you should thread your program. This is what i've done here to allow for
68  * asynchronous SQL requests via mysql. The way this works is as follows:
69  *
70  * The module spawns a thread via class Thread, and performs its mysql queries in this thread,
71  * using a queue with priorities. There is a mutex on either end which prevents two threads
72  * adjusting the queue at the same time, and crashing the ircd. Every 50 milliseconds, the
73  * worker thread wakes up, and checks if there is a request at the head of its queue.
74  * If there is, it processes this request, blocking the worker thread but leaving the ircd
75  * thread to go about its business as usual. During this period, the ircd thread is able
76  * to insert further pending requests into the queue.
77  *
78  * Once the processing of a request is complete, it is removed from the incoming queue to
79  * an outgoing queue, and initialized as a 'response'. The worker thread then signals the
80  * ircd thread (via a loopback socket) of the fact a result is available, by sending the
81  * connection ID through the connection.
82  *
83  * The ircd thread then mutexes the queue once more, reads the outbound response off the head
84  * of the queue, and sends it on its way to the original calling module.
85  *
86  * XXX: You might be asking "why doesnt it just send the response from within the worker thread?"
87  * The answer to this is simple. The majority of InspIRCd, and in fact most ircd's are not
88  * threadsafe. This module is designed to be threadsafe and is careful with its use of threads,
89  * however, if we were to call a module's OnRequest even from within a thread which was not the
90  * one the module was originally instantiated upon, there is a chance of all hell breaking loose
91  * if a module is ever put in a reentrant state (stack corruption could occur, crashes, data
92  * corruption, and worse, so DONT think about it until the day comes when InspIRCd is 100%
93  * guaranteed threadsafe!)
94  */
95 
96 class SQLConnection;
97 class MySQLresult;
98 class DispatcherThread;
99 
100 struct QueryQueueItem
101 {
102 	// An SQL database which this query is executed on.
103 	SQLConnection* connection;
104 
105 	// An object which handles the result of the query.
106 	SQL::Query* query;
107 
108 	// The SQL query which is to be executed.
109 	std::string querystr;
110 
QueryQueueItemQueryQueueItem111 	QueryQueueItem(SQL::Query* q, const std::string& s, SQLConnection* c)
112 		: connection(c)
113 		, query(q)
114 		, querystr(s)
115 	{
116 	}
117 };
118 
119 struct ResultQueueItem
120 {
121 	// An object which handles the result of the query.
122 	SQL::Query* query;
123 
124 	// The result returned from executing the MySQL query.
125 	MySQLresult* result;
126 
ResultQueueItemResultQueueItem127 	ResultQueueItem(SQL::Query* q, MySQLresult* r)
128 		: query(q)
129 		, result(r)
130 	{
131 	}
132 };
133 
134 typedef insp::flat_map<std::string, SQLConnection*> ConnMap;
135 typedef std::deque<QueryQueueItem> QueryQueue;
136 typedef std::deque<ResultQueueItem> ResultQueue;
137 
138 /** MySQL module
139  *  */
140 class ModuleSQL : public Module
141 {
142  public:
143 	DispatcherThread* Dispatcher;
144 	QueryQueue qq;       // MUST HOLD MUTEX
145 	ResultQueue rq;      // MUST HOLD MUTEX
146 	ConnMap connections; // main thread only
147 
148 	ModuleSQL();
149 	void init() CXX11_OVERRIDE;
150 	~ModuleSQL();
151 	void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE;
152 	void OnUnloadModule(Module* mod) CXX11_OVERRIDE;
153 	Version GetVersion() CXX11_OVERRIDE;
154 };
155 
156 class DispatcherThread : public SocketThread
157 {
158  private:
159 	ModuleSQL* const Parent;
160  public:
DispatcherThread(ModuleSQL * CreatorModule)161 	DispatcherThread(ModuleSQL* CreatorModule) : Parent(CreatorModule) { }
~DispatcherThread()162 	~DispatcherThread() { }
163 	void Run() CXX11_OVERRIDE;
164 	void OnNotify() CXX11_OVERRIDE;
165 };
166 
167 /** Represents a mysql result set
168  */
169 class MySQLresult : public SQL::Result
170 {
171  public:
172 	SQL::Error err;
173 	int currentrow;
174 	int rows;
175 	std::vector<std::string> colnames;
176 	std::vector<SQL::Row> fieldlists;
177 
MySQLresult(MYSQL_RES * res,int affected_rows)178 	MySQLresult(MYSQL_RES* res, int affected_rows)
179 		: err(SQL::SUCCESS)
180 		, currentrow(0)
181 		, rows(0)
182 	{
183 		if (affected_rows >= 1)
184 		{
185 			rows = affected_rows;
186 			fieldlists.resize(rows);
187 		}
188 		unsigned int field_count = 0;
189 		if (res)
190 		{
191 			MYSQL_ROW row;
192 			int n = 0;
193 			while ((row = mysql_fetch_row(res)))
194 			{
195 				if (fieldlists.size() < (unsigned int)rows+1)
196 				{
197 					fieldlists.resize(fieldlists.size()+1);
198 				}
199 				field_count = 0;
200 				MYSQL_FIELD *fields = mysql_fetch_fields(res);
201 				if(mysql_num_fields(res) == 0)
202 					break;
203 				if (fields && mysql_num_fields(res))
204 				{
205 					colnames.clear();
206 					while (field_count < mysql_num_fields(res))
207 					{
208 						std::string a = (fields[field_count].name ? fields[field_count].name : "");
209 						if (row[field_count])
210 							fieldlists[n].push_back(SQL::Field(row[field_count]));
211 						else
212 							fieldlists[n].push_back(SQL::Field());
213 						colnames.push_back(a);
214 						field_count++;
215 					}
216 					n++;
217 				}
218 				rows++;
219 			}
220 			mysql_free_result(res);
221 		}
222 	}
223 
MySQLresult(SQL::Error & e)224 	MySQLresult(SQL::Error& e)
225 		: err(e)
226 		, currentrow(0)
227 		, rows(0)
228 	{
229 
230 	}
231 
Rows()232 	int Rows() CXX11_OVERRIDE
233 	{
234 		return rows;
235 	}
236 
GetCols(std::vector<std::string> & result)237 	void GetCols(std::vector<std::string>& result) CXX11_OVERRIDE
238 	{
239 		result.assign(colnames.begin(), colnames.end());
240 	}
241 
HasColumn(const std::string & column,size_t & index)242 	bool HasColumn(const std::string& column, size_t& index) CXX11_OVERRIDE
243 	{
244 		for (size_t i = 0; i < colnames.size(); ++i)
245 		{
246 			if (colnames[i] == column)
247 			{
248 				index = i;
249 				return true;
250 			}
251 		}
252 		return false;
253 	}
254 
GetValue(int row,int column)255 	SQL::Field GetValue(int row, int column)
256 	{
257 		if ((row >= 0) && (row < rows) && (column >= 0) && (column < (int)fieldlists[row].size()))
258 		{
259 			return fieldlists[row][column];
260 		}
261 		return SQL::Field();
262 	}
263 
GetRow(SQL::Row & result)264 	bool GetRow(SQL::Row& result) CXX11_OVERRIDE
265 	{
266 		if (currentrow < rows)
267 		{
268 			result.assign(fieldlists[currentrow].begin(), fieldlists[currentrow].end());
269 			currentrow++;
270 			return true;
271 		}
272 		else
273 		{
274 			result.clear();
275 			return false;
276 		}
277 	}
278 };
279 
280 /** Represents a connection to a mysql database
281  */
282 class SQLConnection : public SQL::Provider
283 {
284  private:
EscapeString(SQL::Query * query,const std::string & in,std::string & out)285 	bool EscapeString(SQL::Query* query, const std::string& in, std::string& out)
286 	{
287 		// In the worst case each character may need to be encoded as using two bytes and one
288 		// byte is the NUL terminator.
289 		std::vector<char> buffer(in.length() * 2 + 1);
290 
291 		// The return value of mysql_escape_string() is either an error or the length of the
292 		// encoded string not including the NUL terminator.
293 		//
294 		// Unfortunately, someone genius decided that mysql_escape_string should return an
295 		// unsigned type even though -1 is returned on error so checking whether an error
296 		// happened is a bit cursed.
297 		unsigned long escapedsize = mysql_escape_string(&buffer[0], in.c_str(), in.length());
298 		if (escapedsize == static_cast<unsigned long>(-1))
299 		{
300 			SQL::Error err(SQL::QSEND_FAIL, InspIRCd::Format("%u: %s", mysql_errno(connection), mysql_error(connection)));
301 			query->OnError(err);
302 			return false;
303 		}
304 
305 		out.append(&buffer[0], escapedsize);
306 		return true;
307 	}
308 
309  public:
310 	reference<ConfigTag> config;
311 	MYSQL *connection;
312 	Mutex lock;
313 
314 	// This constructor creates an SQLConnection object with the given credentials, but does not connect yet.
SQLConnection(Module * p,ConfigTag * tag)315 	SQLConnection(Module* p, ConfigTag* tag)
316 		: SQL::Provider(p, tag->getString("id"))
317 		, config(tag)
318 		, connection(NULL)
319 	{
320 	}
321 
~SQLConnection()322 	~SQLConnection()
323 	{
324 		mysql_close(connection);
325 	}
326 
327 	// This method connects to the database using the credentials supplied to the constructor, and returns
328 	// true upon success.
Connect()329 	bool Connect()
330 	{
331 		connection = mysql_init(connection);
332 
333 		// Set the connection timeout.
334 		unsigned int timeout = config->getDuration("timeout", 5, 1, 30);
335 		mysql_options(connection, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
336 
337 		// Attempt to connect to the database.
338 		const std::string host = config->getString("host");
339 		const std::string user = config->getString("user");
340 		const std::string pass = config->getString("pass");
341 		const std::string dbname = config->getString("name");
342 		unsigned int port = config->getUInt("port", 3306, 1, 65535);
343 		if (!mysql_real_connect(connection, host.c_str(), user.c_str(), pass.c_str(), dbname.c_str(), port, NULL, CLIENT_IGNORE_SIGPIPE))
344 		{
345 			ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Unable to connect to the %s MySQL server: %s",
346 				GetId().c_str(), mysql_error(connection));
347 			return false;
348 		}
349 
350 		// Set the default character set.
351 		const std::string charset = config->getString("charset");
352 		if (!charset.empty() && mysql_set_character_set(connection, charset.c_str()))
353 		{
354 			ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Could not set character set for %s to \"%s\": %s",
355 				GetId().c_str(), charset.c_str(), mysql_error(connection));
356 			return false;
357 		}
358 
359 		// Execute the initial SQL query.
360 		const std::string initialquery = config->getString("initialquery");
361 		if (!initialquery.empty() && mysql_real_query(connection, initialquery.data(), initialquery.length()))
362 		{
363 			ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Could not execute initial query \"%s\" for %s: %s",
364 				initialquery.c_str(), name.c_str(), mysql_error(connection));
365 			return false;
366 		}
367 
368 		return true;
369 	}
370 
Parent()371 	ModuleSQL* Parent()
372 	{
373 		return (ModuleSQL*)(Module*)creator;
374 	}
375 
DoBlockingQuery(const std::string & query)376 	MySQLresult* DoBlockingQuery(const std::string& query)
377 	{
378 
379 		/* Parse the command string and dispatch it to mysql */
380 		if (CheckConnection() && !mysql_real_query(connection, query.data(), query.length()))
381 		{
382 			/* Successful query */
383 			MYSQL_RES* res = mysql_use_result(connection);
384 			unsigned long rows = mysql_affected_rows(connection);
385 			return new MySQLresult(res, rows);
386 		}
387 		else
388 		{
389 			/* XXX: See /usr/include/mysql/mysqld_error.h for a list of
390 			 * possible error numbers and error messages */
391 			SQL::Error e(SQL::QREPLY_FAIL, InspIRCd::Format("%u: %s", mysql_errno(connection), mysql_error(connection)));
392 			return new MySQLresult(e);
393 		}
394 	}
395 
CheckConnection()396 	bool CheckConnection()
397 	{
398 		if (!connection || mysql_ping(connection) != 0)
399 			return Connect();
400 		return true;
401 	}
402 
Submit(SQL::Query * q,const std::string & qs)403 	void Submit(SQL::Query* q, const std::string& qs) CXX11_OVERRIDE
404 	{
405 		ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Executing MySQL query: " + qs);
406 		Parent()->Dispatcher->LockQueue();
407 		Parent()->qq.push_back(QueryQueueItem(q, qs, this));
408 		Parent()->Dispatcher->UnlockQueueWakeup();
409 	}
410 
Submit(SQL::Query * call,const std::string & q,const SQL::ParamList & p)411 	void Submit(SQL::Query* call, const std::string& q, const SQL::ParamList& p) CXX11_OVERRIDE
412 	{
413 		std::string res;
414 		unsigned int param = 0;
415 		for(std::string::size_type i = 0; i < q.length(); i++)
416 		{
417 			if (q[i] != '?')
418 				res.push_back(q[i]);
419 			else if (param < p.size() && !EscapeString(call, p[param++], res))
420 				return;
421 		}
422 		Submit(call, res);
423 	}
424 
Submit(SQL::Query * call,const std::string & q,const SQL::ParamMap & p)425 	void Submit(SQL::Query* call, const std::string& q, const SQL::ParamMap& p) CXX11_OVERRIDE
426 	{
427 		std::string res;
428 		for(std::string::size_type i = 0; i < q.length(); i++)
429 		{
430 			if (q[i] != '$')
431 				res.push_back(q[i]);
432 			else
433 			{
434 				std::string field;
435 				i++;
436 				while (i < q.length() && isalnum(q[i]))
437 					field.push_back(q[i++]);
438 				i--;
439 
440 				SQL::ParamMap::const_iterator it = p.find(field);
441 				if (it != p.end() && !EscapeString(call, it->second, res))
442 					return;
443 			}
444 		}
445 		Submit(call, res);
446 	}
447 };
448 
ModuleSQL()449 ModuleSQL::ModuleSQL()
450 	: Dispatcher(NULL)
451 {
452 }
453 
init()454 void ModuleSQL::init()
455 {
456 	if (mysql_library_init(0, NULL, NULL))
457 		throw ModuleException("Unable to initialise the MySQL library!");
458 
459 	Dispatcher = new DispatcherThread(this);
460 	ServerInstance->Threads.Start(Dispatcher);
461 }
462 
~ModuleSQL()463 ModuleSQL::~ModuleSQL()
464 {
465 	if (Dispatcher)
466 	{
467 		Dispatcher->join();
468 		Dispatcher->OnNotify();
469 		delete Dispatcher;
470 	}
471 
472 	for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++)
473 	{
474 		delete i->second;
475 	}
476 
477 	mysql_library_end();
478 }
479 
ReadConfig(ConfigStatus & status)480 void ModuleSQL::ReadConfig(ConfigStatus& status)
481 {
482 	ConnMap conns;
483 	ConfigTagList tags = ServerInstance->Config->ConfTags("database");
484 	for(ConfigIter i = tags.first; i != tags.second; i++)
485 	{
486 		if (!stdalgo::string::equalsci(i->second->getString("module"), "mysql"))
487 			continue;
488 		std::string id = i->second->getString("id");
489 		ConnMap::iterator curr = connections.find(id);
490 		if (curr == connections.end())
491 		{
492 			SQLConnection* conn = new SQLConnection(this, i->second);
493 			conns.insert(std::make_pair(id, conn));
494 			ServerInstance->Modules->AddService(*conn);
495 		}
496 		else
497 		{
498 			conns.insert(*curr);
499 			connections.erase(curr);
500 		}
501 	}
502 
503 	// now clean up the deleted databases
504 	Dispatcher->LockQueue();
505 	SQL::Error err(SQL::BAD_DBID);
506 	for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++)
507 	{
508 		ServerInstance->Modules->DelService(*i->second);
509 		// it might be running a query on this database. Wait for that to complete
510 		i->second->lock.Lock();
511 		i->second->lock.Unlock();
512 		// now remove all active queries to this DB
513 		for (size_t j = qq.size(); j > 0; j--)
514 		{
515 			size_t k = j - 1;
516 			if (qq[k].connection == i->second)
517 			{
518 				qq[k].query->OnError(err);
519 				delete qq[k].query;
520 				qq.erase(qq.begin() + k);
521 			}
522 		}
523 		// finally, nuke the connection
524 		delete i->second;
525 	}
526 	Dispatcher->UnlockQueue();
527 	connections.swap(conns);
528 }
529 
OnUnloadModule(Module * mod)530 void ModuleSQL::OnUnloadModule(Module* mod)
531 {
532 	SQL::Error err(SQL::BAD_DBID);
533 	Dispatcher->LockQueue();
534 	unsigned int i = qq.size();
535 	while (i > 0)
536 	{
537 		i--;
538 		if (qq[i].query->creator == mod)
539 		{
540 			if (i == 0)
541 			{
542 				// need to wait until the query is done
543 				// (the result will be discarded)
544 				qq[i].connection->lock.Lock();
545 				qq[i].connection->lock.Unlock();
546 			}
547 			qq[i].query->OnError(err);
548 			delete qq[i].query;
549 			qq.erase(qq.begin() + i);
550 		}
551 	}
552 	Dispatcher->UnlockQueue();
553 	// clean up any result queue entries
554 	Dispatcher->OnNotify();
555 }
556 
GetVersion()557 Version ModuleSQL::GetVersion()
558 {
559 	return Version("Provides the ability for SQL modules to query a MySQL database.", VF_VENDOR);
560 }
561 
Run()562 void DispatcherThread::Run()
563 {
564 	this->LockQueue();
565 	while (!this->GetExitFlag())
566 	{
567 		if (!Parent->qq.empty())
568 		{
569 			QueryQueueItem i = Parent->qq.front();
570 			i.connection->lock.Lock();
571 			this->UnlockQueue();
572 			MySQLresult* res = i.connection->DoBlockingQuery(i.querystr);
573 			i.connection->lock.Unlock();
574 
575 			/*
576 			 * At this point, the main thread could be working on:
577 			 *  Rehash - delete i.connection out from under us. We don't care about that.
578 			 *  UnloadModule - delete i.query and the qq item. Need to avoid reporting results.
579 			 */
580 
581 			this->LockQueue();
582 			if (!Parent->qq.empty() && Parent->qq.front().query == i.query)
583 			{
584 				Parent->qq.pop_front();
585 				Parent->rq.push_back(ResultQueueItem(i.query, res));
586 				NotifyParent();
587 			}
588 			else
589 			{
590 				// UnloadModule ate the query
591 				delete res;
592 			}
593 		}
594 		else
595 		{
596 			/* We know the queue is empty, we can safely hang this thread until
597 			 * something happens
598 			 */
599 			this->WaitForQueue();
600 		}
601 	}
602 	this->UnlockQueue();
603 }
604 
OnNotify()605 void DispatcherThread::OnNotify()
606 {
607 	// this could unlock during the dispatch, but OnResult isn't expected to take that long
608 	this->LockQueue();
609 	for(ResultQueue::iterator i = Parent->rq.begin(); i != Parent->rq.end(); i++)
610 	{
611 		MySQLresult* res = i->result;
612 		if (res->err.code == SQL::SUCCESS)
613 			i->query->OnResult(*res);
614 		else
615 			i->query->OnError(res->err);
616 		delete i->query;
617 		delete i->result;
618 	}
619 	Parent->rq.clear();
620 	this->UnlockQueue();
621 }
622 
623 MODULE_INIT(ModuleSQL)
624