1 /* +------------------------------------+
2 * | Inspire Internet Relay Chat Daemon |
3 * +------------------------------------+
5 * InspIRCd is copyright (C) 2002-2004 ChatSpike-Dev.
7 * <brain@chatspike.net>
8 * <Craig@chatspike.net>
11 * Written by Craig Edwards, Craig McLure, and others.
12 * This program is free but copyrighted software; see
13 * the file COPYING for details.
15 * ---------------------------------------------------
27 #include "helperfuncs.h"
29 #include "configreader.h"
33 /* $ModDesc: PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API */
34 /* $CompileFlags: -I`pg_config --includedir` */
35 /* $LinkerFlags: -L`pg_config --libdir` -lpq */
37 /* UGH, UGH, UGH, UGH, UGH, UGH
38 * I'm having trouble seeing how I
39 * can avoid this. The core-defined
40 * constructors for InspSocket just
41 * aren't suitable...and if I'm
42 * reimplementing them I need this so
43 * I can access the socket engine :\
45 extern InspIRCd* ServerInstance;
46 InspSocket* socket_ref[MAX_DESCRIPTORS];
48 /* Forward declare, so we can have the typedef neatly at the top */
51 typedef std::map<std::string, SQLConn*> ConnMap;
53 /* CREAD, Connecting and wants read event
54 * CWRITE, Connecting and wants write event
55 * WREAD, Connected/Working and wants read event
56 * WWRITE, Connected/Working and wants write event
58 enum SQLstatus { CREAD, CWRITE, WREAD, WWRITE };
60 inline std::string pop_front_r(std::deque<std::string> &d)
62 std::string r = d.front();
68 /** QueryQueue, a queue of queries waiting to be executed.
69 * This maintains two queues internally, one for 'priority'
70 * queries and one for less important ones. Each queue has
71 * new queries appended to it and ones to execute are popped
72 * off the front. This keeps them flowing round nicely and no
73 * query should ever get 'stuck' for too long. If there are
74 * queries in the priority queue they will be executed first,
75 * 'unimportant' queries will only be executed when the
76 * priority queue is empty.
82 std::deque<std::string> priority; /* The priority queue */
83 std::deque<std::string> normal; /* The 'normal' queue */
91 void push_back(const std::string &q, bool pri = false)
93 log(DEBUG, "QueryQueue::push_back(): Adding %s query to queue: %s", ((pri) ? "priority" : "non-priority"), q.c_str());
96 priority.push_back(q);
101 inline std::string pop_front()
107 return pop_front_r(priority);
109 else if(normal.size())
111 return pop_front_r(normal);
119 std::pair<int, int> size()
121 return std::make_pair(priority.size(), normal.size());
125 /** SQLConn represents one SQL session.
126 * Each session has its own persistent connection to the database.
127 * This is a subclass of InspSocket so it can easily recieve read/write events from the core socket
128 * engine, unlike the original MySQL module this module does not block. Ever. It gets a mild stabbing
132 class SQLConn : public InspSocket
135 Server* Srv; /* Server* for..uhm..something, maybe */
136 std::string dbhost; /* Database server hostname */
137 unsigned int dbport; /* Database server port */
138 std::string dbname; /* Database name */
139 std::string dbuser; /* Database username */
140 std::string dbpass; /* Database password */
141 bool ssl; /* If we should require SSL */
142 PGconn* sql; /* PgSQL database connection handle */
143 SQLstatus status; /* PgSQL database connection status */
146 QueryQueue queue; /* Queue of queries waiting to be executed on this connection */
148 /* This class should only ever be created inside this module, using this constructor, so we don't have to worry about the default ones */
150 SQLConn(Server* srv, const std::string &h, unsigned int p, const std::string &d, const std::string &u, const std::string &pwd, bool s)
151 : InspSocket::InspSocket(), Srv(srv), dbhost(h), dbport(p), dbname(d), dbuser(u), dbpass(pwd), ssl(s), sql(NULL), status(CWRITE)
153 log(DEBUG, "Creating new PgSQL connection to database %s on %s:%u (%s/%s)", dbname.c_str(), dbhost.c_str(), dbport, dbuser.c_str(), dbpass.c_str());
155 /* Some of this could be reviewed, unsure if I need to fill 'host' etc...
156 * just copied this over from the InspSocket constructor.
158 strlcpy(this->host, dbhost.c_str(), MAXBUF);
161 this->ClosePending = false;
163 if(!inet_aton(this->host, &this->addy))
165 /* Its not an ip, spawn the resolver.
166 * PgSQL doesn't do nonblocking DNS
167 * lookups, so we do it for it.
170 log(DEBUG,"Attempting to resolve %s", this->host);
172 this->dns.SetNS(Srv->GetConfig()->DNSServer);
173 this->dns.ForwardLookupWithFD(this->host, fd);
175 this->state = I_RESOLVING;
176 socket_ref[this->fd] = this;
182 log(DEBUG,"No need to resolve %s", this->host);
183 strlcpy(this->IP, this->host, MAXBUF);
185 if(!this->DoConnect())
187 throw ModuleException("Connect failed");
199 log(DEBUG, "Checking for DNS lookup result");
201 if(this->dns.HasResult())
203 std::string res_ip = dns.GetResultIP();
207 log(DEBUG, "Got result: %s", res_ip.c_str());
209 strlcpy(this->IP, res_ip.c_str(), MAXBUF);
212 socket_ref[this->fd] = NULL;
214 return this->DoConnect();
218 log(DEBUG, "DNS lookup failed, dying horribly");
225 log(DEBUG, "No result for lookup yet!");
232 log(DEBUG, "SQLConn::DoConnect()");
234 if(!(sql = PQconnectStart(MkInfoStr().c_str())))
236 log(DEBUG, "Couldn't allocate PGconn structure, aborting: %s", PQerrorMessage(sql));
241 if(PQstatus(sql) == CONNECTION_BAD)
243 log(DEBUG, "PQconnectStart failed: %s", PQerrorMessage(sql));
250 if(PQsetnonblocking(sql, 1) == -1)
252 log(DEBUG, "Couldn't set connection nonblocking: %s", PQerrorMessage(sql));
257 /* OK, we've initalised the connection, now to get it hooked into the socket engine
258 * and then start polling it.
261 log(DEBUG, "Old DNS socket: %d", this->fd);
262 this->fd = PQsocket(sql);
263 log(DEBUG, "New SQL socket: %d", this->fd);
267 log(DEBUG, "PQsocket says we have an invalid FD: %d", this->fd);
272 this->state = I_CONNECTING;
273 ServerInstance->SE->AddFd(this->fd,false,X_ESTAB_MODULE);
274 socket_ref[this->fd] = this;
276 /* Socket all hooked into the engine, now to tell PgSQL to start connecting */
284 this->state = I_ERROR;
285 this->OnError(I_ERR_SOCKET);
286 this->ClosePending = true;
287 log(DEBUG,"SQLConn::Close");
300 switch(PQconnectPoll(sql))
302 case PGRES_POLLING_WRITING:
303 log(DEBUG, "PGconnectPoll: PGRES_POLLING_WRITING");
308 case PGRES_POLLING_READING:
309 log(DEBUG, "PGconnectPoll: PGRES_POLLING_READING");
312 case PGRES_POLLING_FAILED:
313 log(DEBUG, "PGconnectPoll: PGRES_POLLING_FAILED: %s", PQerrorMessage(sql));
316 case PGRES_POLLING_OK:
317 log(DEBUG, "PGconnectPoll: PGRES_POLLING_OK");
321 log(DEBUG, "PGconnectPoll: wtf?");
330 if(PQconsumeInput(sql))
332 log(DEBUG, "PQconsumeInput succeeded");
336 log(DEBUG, "Still busy processing command though");
340 log(DEBUG, "Looks like we have a result to process!");
342 while(PGresult* result = PQgetResult(sql))
344 int cols = PQnfields(result);
346 log(DEBUG, "Got result! :D");
347 log(DEBUG, "%d rows, %d columns checking now what the column names are", PQntuples(result), cols);
349 for(int i = 0; i < cols; i++)
351 log(DEBUG, "Column name: %s (%d)", PQfname(result, i));
361 log(DEBUG, "PQconsumeInput failed: %s", PQerrorMessage(sql));
367 switch(PQstatus(sql))
369 case CONNECTION_STARTED:
370 log(DEBUG, "PQstatus: CONNECTION_STARTED: Waiting for connection to be made.");
373 case CONNECTION_MADE:
374 log(DEBUG, "PQstatus: CONNECTION_MADE: Connection OK; waiting to send.");
377 case CONNECTION_AWAITING_RESPONSE:
378 log(DEBUG, "PQstatus: CONNECTION_AWAITING_RESPONSE: Waiting for a response from the server.");
381 case CONNECTION_AUTH_OK:
382 log(DEBUG, "PQstatus: CONNECTION_AUTH_OK: Received authentication; waiting for backend start-up to finish.");
385 case CONNECTION_SSL_STARTUP:
386 log(DEBUG, "PQstatus: CONNECTION_SSL_STARTUP: Negotiating SSL encryption.");
389 case CONNECTION_SETENV:
390 log(DEBUG, "PQstatus: CONNECTION_SETENV: Negotiating environment-driven parameter settings.");
394 log(DEBUG, "PQstatus: ???");
398 virtual bool OnDataReady()
400 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
401 log(DEBUG, "OnDataReady(): status = %s", StatusStr());
406 virtual bool OnWriteReady()
408 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
409 log(DEBUG, "OnWriteReady(): status = %s", StatusStr());
414 virtual bool OnConnected()
416 log(DEBUG, "OnConnected(): status = %s", StatusStr());
423 if((status == CREAD) || (status == CWRITE))
435 log(DEBUG, "Error flushing write queue: %s", PQerrorMessage(sql));
438 log(DEBUG, "Successfully flushed write queue (or there was nothing to write)");
441 log(DEBUG, "Not all of the write queue written, triggering write event so we can have another go");
449 std::string MkInfoStr()
451 /* XXX - This needs nonblocking DNS lookups */
453 std::ostringstream conninfo("connect_timeout = '2'");
456 conninfo << " hostaddr = '" << dbhost << "'";
459 conninfo << " port = '" << dbport << "'";
462 conninfo << " dbname = '" << dbname << "'";
465 conninfo << " user = '" << dbuser << "'";
468 conninfo << " password = '" << dbpass << "'";
471 conninfo << " sslmode = 'require'";
473 return conninfo.str();
476 const char* StatusStr()
478 if(status == CREAD) return "CREAD";
479 if(status == CWRITE) return "CWRITE";
480 if(status == WREAD) return "WREAD";
481 if(status == WWRITE) return "WWRITE";
482 return "Err...what, erm..BUG!";
485 bool Query(const std::string &query)
487 if((status == WREAD) || (status == WWRITE))
489 if(PQsendQuery(sql, query.c_str()))
491 log(DEBUG, "Dispatched query: %s", query.c_str());
496 log(DEBUG, "Failed to dispatch query: %s", PQerrorMessage(sql));
501 log(DEBUG, "Can't query until connection is complete");
505 virtual void OnClose()
507 /* Close PgSQL connection */
510 virtual void OnError(InspSocketError e)
512 /* Unsure if we need this, we should be reading/writing via the PgSQL API rather than the insp one... */
515 virtual void OnTimeout()
517 /* Unused, I think */
521 class ModulePgSQL : public Module
528 ModulePgSQL(Server* Me)
529 : Module::Module(Me), Srv(Me)
531 log(DEBUG, "%s 'SQL' feature", Srv->PublishFeature("SQL", this) ? "Published" : "Couldn't publish");
532 log(DEBUG, "%s 'PgSQL' feature", Srv->PublishFeature("PgSQL", this) ? "Published" : "Couldn't publish");
537 void Implements(char* List)
539 List[I_OnRequest] = List[I_OnRehash] = List[I_OnUserRegister] = List[I_OnCheckReady] = List[I_OnUserDisconnect] = 1;
542 virtual void OnRehash(const std::string ¶meter)
546 /* Delete all the SQLConn objects in the connection lists,
547 * this will call their destructors where they can handle
548 * closing connections and such.
550 for(ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
552 DELETE(iter->second);
555 /* Empty out our list of connections */
558 for(int i = 0; i < conf.Enumerate("database"); i++)
563 id = conf.ReadValue("database", "id", i);
564 newconn = new SQLConn(Srv, conf.ReadValue("database", "hostname", i),
565 conf.ReadInteger("database", "port", i, true),
566 conf.ReadValue("database", "name", i),
567 conf.ReadValue("database", "username", i),
568 conf.ReadValue("database", "password", i),
569 conf.ReadFlag("database", "ssl", i));
571 connections.insert(std::make_pair(id, newconn));
575 virtual char* OnRequest(Request* request)
577 if(strcmp(SQLREQID, request->GetData()) == 0)
579 SQLrequest* req = (SQLrequest*)request;
580 ConnMap::iterator iter;
582 log(DEBUG, "Got query: '%s' on id '%s'", req->query.c_str(), req->dbid.c_str());
584 if((iter = connections.find(req->dbid)) != connections.end())
587 iter->second->queue.push_back(req->query, req->pri);
593 req->error.Id(BAD_DBID);
598 log(DEBUG, "Got unsupported API version string: %s", request->GetData());
603 virtual Version GetVersion()
605 return Version(1, 0, 0, 0, VF_VENDOR|VF_SERVICEPROVIDER);
608 virtual ~ModulePgSQL()
613 class ModulePgSQLFactory : public ModuleFactory
620 ~ModulePgSQLFactory()
624 virtual Module * CreateModule(Server* Me)
626 return new ModulePgSQL(Me);
631 extern "C" void * init_module( void )
633 return new ModulePgSQLFactory;