]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_pgsql.cpp
Move everything module-related out of InspIRCd and into ModuleManager, there is a...
[user/henk/code/inspircd.git] / src / modules / extra / m_pgsql.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2007 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include <cstdlib>
16 #include <sstream>
17 #include <libpq-fe.h>
18 #include "users.h"
19 #include "channels.h"
20 #include "modules.h"
21 #include "configreader.h"
22 #include "m_sqlv2.h"
23
24 /* $ModDesc: PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API */
25 /* $CompileFlags: -Iexec("pg_config --includedir") eval("my $s = `pg_config --version`;$s =~ /^.*?(\d+)\.(\d+)\.(\d+).*?$/;my $v = hex(sprintf("0x%02x%02x%02x", $1, $2, $3));print "-DPGSQL_HAS_ESCAPECONN" if(($v >= 0x080104) || ($v >= 0x07030F && $v < 0x070400) || ($v >= 0x07040D && $v < 0x080000) || ($v >= 0x080008 && $v < 0x080100));") */
26 /* $LinkerFlags: -Lexec("pg_config --libdir") -lpq */
27 /* $ModDep: m_sqlv2.h */
28
29
30 /* SQLConn rewritten by peavey to
31  * use EventHandler instead of
32  * InspSocket. This is much neater
33  * and gives total control of destroy
34  * and delete of resources.
35  */
36
37 /* Forward declare, so we can have the typedef neatly at the top */
38 class SQLConn;
39
40 typedef std::map<std::string, SQLConn*> ConnMap;
41
42 /* CREAD,       Connecting and wants read event
43  * CWRITE,      Connecting and wants write event
44  * WREAD,       Connected/Working and wants read event
45  * WWRITE,      Connected/Working and wants write event
46  * RREAD,       Resetting and wants read event
47  * RWRITE,      Resetting and wants write event
48  */
49 enum SQLstatus { CREAD, CWRITE, WREAD, WWRITE, RREAD, RWRITE };
50
51 /** SQLhost::GetDSN() - Overload to return correct DSN for PostgreSQL
52  */
53 std::string SQLhost::GetDSN()
54 {
55         std::ostringstream conninfo("connect_timeout = '2'");
56
57         if (ip.length())
58                 conninfo << " hostaddr = '" << ip << "'";
59
60         if (port)
61                 conninfo << " port = '" << port << "'";
62
63         if (name.length())
64                 conninfo << " dbname = '" << name << "'";
65
66         if (user.length())
67                 conninfo << " user = '" << user << "'";
68
69         if (pass.length())
70                 conninfo << " password = '" << pass << "'";
71
72         if (ssl)
73         {
74                 conninfo << " sslmode = 'require'";
75         }
76         else
77         {
78                 conninfo << " sslmode = 'disable'";
79         }
80
81         return conninfo.str();
82 }
83
84 class ReconnectTimer : public InspTimer
85 {
86   private:
87         Module* mod;
88   public:
89         ReconnectTimer(InspIRCd* SI, Module* m)
90         : InspTimer(5, SI->Time(), false), mod(m)
91         {
92         }
93         virtual void Tick(time_t TIME);
94 };
95
96
97 /** Used to resolve sql server hostnames
98  */
99 class SQLresolver : public Resolver
100 {
101  private:
102         SQLhost host;
103         Module* mod;
104  public:
105         SQLresolver(Module* m, InspIRCd* Instance, const SQLhost& hi, bool &cached)
106         : Resolver(Instance, hi.host, DNS_QUERY_FORWARD, cached, (Module*)m), host(hi), mod(m)
107         {
108         }
109
110         virtual void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached, int resultnum = 0);
111
112         virtual void OnError(ResolverError e, const std::string &errormessage)
113         {
114                 ServerInstance->Log(DEBUG, "PgSQL: DNS lookup failed (%s), dying horribly", errormessage.c_str());
115         }
116 };
117
118 /** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult.
119  * All SQL providers must create their own subclass and define it's methods using that
120  * database library's data retriveal functions. The aim is to avoid a slow and inefficient process
121  * of converting all data to a common format before it reaches the result structure. This way
122  * data is passes to the module nearly as directly as if it was using the API directly itself.
123  */
124
125 class PgSQLresult : public SQLresult
126 {
127         PGresult* res;
128         int currentrow;
129         int rows;
130         int cols;
131
132         SQLfieldList* fieldlist;
133         SQLfieldMap* fieldmap;
134 public:
135         PgSQLresult(Module* self, Module* to, unsigned long id, PGresult* result)
136         : SQLresult(self, to, id), res(result), currentrow(0), fieldlist(NULL), fieldmap(NULL)
137         {
138                 rows = PQntuples(res);
139                 cols = PQnfields(res);
140         }
141
142         ~PgSQLresult()
143         {
144                 /* If we allocated these, free them... */
145                 if(fieldlist)
146                         DELETE(fieldlist);
147
148                 if(fieldmap)
149                         DELETE(fieldmap);
150
151                 PQclear(res);
152         }
153
154         virtual int Rows()
155         {
156                 if(!cols && !rows)
157                 {
158                         return atoi(PQcmdTuples(res));
159                 }
160                 else
161                 {
162                         return rows;
163                 }
164         }
165
166         virtual int Cols()
167         {
168                 return PQnfields(res);
169         }
170
171         virtual std::string ColName(int column)
172         {
173                 char* name = PQfname(res, column);
174
175                 return (name) ? name : "";
176         }
177
178         virtual int ColNum(const std::string &column)
179         {
180                 int n = PQfnumber(res, column.c_str());
181
182                 if(n == -1)
183                 {
184                         throw SQLbadColName();
185                 }
186                 else
187                 {
188                         return n;
189                 }
190         }
191
192         virtual SQLfield GetValue(int row, int column)
193         {
194                 char* v = PQgetvalue(res, row, column);
195
196                 if(v)
197                 {
198                         return SQLfield(std::string(v, PQgetlength(res, row, column)), PQgetisnull(res, row, column));
199                 }
200                 else
201                 {
202                         throw SQLbadColName();
203                 }
204         }
205
206         virtual SQLfieldList& GetRow()
207         {
208                 /* In an effort to reduce overhead we don't actually allocate the list
209                  * until the first time it's needed...so...
210                  */
211                 if(fieldlist)
212                 {
213                         fieldlist->clear();
214                 }
215                 else
216                 {
217                         fieldlist = new SQLfieldList;
218                 }
219
220                 if(currentrow < PQntuples(res))
221                 {
222                         int cols = PQnfields(res);
223
224                         for(int i = 0; i < cols; i++)
225                         {
226                                 fieldlist->push_back(GetValue(currentrow, i));
227                         }
228
229                         currentrow++;
230                 }
231
232                 return *fieldlist;
233         }
234
235         virtual SQLfieldMap& GetRowMap()
236         {
237                 /* In an effort to reduce overhead we don't actually allocate the map
238                  * until the first time it's needed...so...
239                  */
240                 if(fieldmap)
241                 {
242                         fieldmap->clear();
243                 }
244                 else
245                 {
246                         fieldmap = new SQLfieldMap;
247                 }
248
249                 if(currentrow < PQntuples(res))
250                 {
251                         int cols = PQnfields(res);
252
253                         for(int i = 0; i < cols; i++)
254                         {
255                                 fieldmap->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
256                         }
257
258                         currentrow++;
259                 }
260
261                 return *fieldmap;
262         }
263
264         virtual SQLfieldList* GetRowPtr()
265         {
266                 SQLfieldList* fl = new SQLfieldList;
267
268                 if(currentrow < PQntuples(res))
269                 {
270                         int cols = PQnfields(res);
271
272                         for(int i = 0; i < cols; i++)
273                         {
274                                 fl->push_back(GetValue(currentrow, i));
275                         }
276
277                         currentrow++;
278                 }
279
280                 return fl;
281         }
282
283         virtual SQLfieldMap* GetRowMapPtr()
284         {
285                 SQLfieldMap* fm = new SQLfieldMap;
286
287                 if(currentrow < PQntuples(res))
288                 {
289                         int cols = PQnfields(res);
290
291                         for(int i = 0; i < cols; i++)
292                         {
293                                 fm->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
294                         }
295
296                         currentrow++;
297                 }
298
299                 return fm;
300         }
301
302         virtual void Free(SQLfieldMap* fm)
303         {
304                 DELETE(fm);
305         }
306
307         virtual void Free(SQLfieldList* fl)
308         {
309                 DELETE(fl);
310         }
311 };
312
313 /** SQLConn represents one SQL session.
314  */
315 class SQLConn : public EventHandler
316 {
317   private:
318         InspIRCd*               Instance;
319         SQLhost                 confhost;       /* The <database> entry */
320         Module*                 us;                     /* Pointer to the SQL provider itself */
321         PGconn*                 sql;            /* PgSQL database connection handle */
322         SQLstatus               status;         /* PgSQL database connection status */
323         bool                    qinprog;        /* If there is currently a query in progress */
324         QueryQueue              queue;          /* Queue of queries waiting to be executed on this connection */
325         time_t                  idle;           /* Time we last heard from the database */
326
327   public:
328         SQLConn(InspIRCd* SI, Module* self, const SQLhost& hi)
329         : EventHandler(), Instance(SI), confhost(hi), us(self), sql(NULL), status(CWRITE), qinprog(false)
330         {
331                 idle = this->Instance->Time();
332                 if(!DoConnect())
333                 {
334                         Instance->Log(DEFAULT, "WARNING: Could not connect to database with id: " + ConvToStr(hi.id));
335                         DelayReconnect();
336                 }
337         }
338
339         ~SQLConn()
340         {
341                 Close();
342         }
343
344         virtual void HandleEvent(EventType et, int errornum)
345         {
346                 switch (et)
347                 {
348                         case EVENT_READ:
349                                 OnDataReady();
350                         break;
351
352                         case EVENT_WRITE:
353                                 OnWriteReady();
354                         break;
355
356                         case EVENT_ERROR:
357                                 DelayReconnect();
358                         break;
359
360                         default:
361                         break;
362                 }
363         }
364
365         bool DoConnect()
366         {
367                 if(!(sql = PQconnectStart(confhost.GetDSN().c_str())))
368                         return false;
369
370                 if(PQstatus(sql) == CONNECTION_BAD)
371                         return false;
372
373                 if(PQsetnonblocking(sql, 1) == -1)
374                         return false;
375
376                 /* OK, we've initalised the connection, now to get it hooked into the socket engine
377                 * and then start polling it.
378                 */
379                 this->fd = PQsocket(sql);
380
381                 if(this->fd <= -1)
382                         return false;
383
384                 if (!this->Instance->SE->AddFd(this))
385                 {
386                         Instance->Log(DEBUG, "BUG: Couldn't add pgsql socket to socket engine");
387                         return false;
388                 }
389
390                 /* Socket all hooked into the engine, now to tell PgSQL to start connecting */
391                 return DoPoll();
392         }
393
394         bool DoPoll()
395         {
396                 switch(PQconnectPoll(sql))
397                 {
398                         case PGRES_POLLING_WRITING:
399                                 Instance->SE->WantWrite(this);
400                                 status = CWRITE;
401                                 return true;
402                         case PGRES_POLLING_READING:
403                                 status = CREAD;
404                                 return true;
405                         case PGRES_POLLING_FAILED:
406                                 return false;
407                         case PGRES_POLLING_OK:
408                                 status = WWRITE;
409                                 return DoConnectedPoll();
410                         default:
411                                 return true;
412                 }
413         }
414
415         bool DoConnectedPoll()
416         {
417                 if(!qinprog && queue.totalsize())
418                 {
419                         /* There's no query currently in progress, and there's queries in the queue. */
420                         SQLrequest& query = queue.front();
421                         DoQuery(query);
422                 }
423
424                 if(PQconsumeInput(sql))
425                 {
426                         /* We just read stuff from the server, that counts as it being alive
427                          * so update the idle-since time :p
428                          */
429                         idle = this->Instance->Time();
430
431                         if (PQisBusy(sql))
432                         {
433                                 /* Nothing happens here */
434                         }
435                         else if (qinprog)
436                         {
437                                 /* Grab the request we're processing */
438                                 SQLrequest& query = queue.front();
439
440                                 /* Get a pointer to the module we're about to return the result to */
441                                 Module* to = query.GetSource();
442
443                                 /* Fetch the result.. */
444                                 PGresult* result = PQgetResult(sql);
445
446                                 /* PgSQL would allow a query string to be sent which has multiple
447                                  * queries in it, this isn't portable across database backends and
448                                  * we don't want modules doing it. But just in case we make sure we
449                                  * drain any results there are and just use the last one.
450                                  * If the module devs are behaving there will only be one result.
451                                  */
452                                 while (PGresult* temp = PQgetResult(sql))
453                                 {
454                                         PQclear(result);
455                                         result = temp;
456                                 }
457
458                                 if(to)
459                                 {
460                                         /* ..and the result */
461                                         PgSQLresult reply(us, to, query.id, result);
462
463                                         /* Fix by brain, make sure the original query gets sent back in the reply */
464                                         reply.query = query.query.q;
465
466                                         switch(PQresultStatus(result))
467                                         {
468                                                 case PGRES_EMPTY_QUERY:
469                                                 case PGRES_BAD_RESPONSE:
470                                                 case PGRES_FATAL_ERROR:
471                                                         reply.error.Id(QREPLY_FAIL);
472                                                         reply.error.Str(PQresultErrorMessage(result));
473                                                 default:;
474                                                         /* No action, other values are not errors */
475                                         }
476
477                                         reply.Send();
478
479                                         /* PgSQLresult's destructor will free the PGresult */
480                                 }
481                                 else
482                                 {
483                                         /* If the client module is unloaded partway through a query then the provider will set
484                                          * the pointer to NULL. We cannot just cancel the query as the result will still come
485                                          * through at some point...and it could get messy if we play with invalid pointers...
486                                          */
487                                         PQclear(result);
488                                 }
489                                 qinprog = false;
490                                 queue.pop();
491                                 DoConnectedPoll();
492                         }
493                         return true;
494                 }
495                 else
496                 {
497                         /* I think we'll assume this means the server died...it might not,
498                          * but I think that any error serious enough we actually get here
499                          * deserves to reconnect [/excuse]
500                          * Returning true so the core doesn't try and close the connection.
501                          */
502                         DelayReconnect();
503                         return true;
504                 }
505         }
506
507         bool DoResetPoll()
508         {
509                 switch(PQresetPoll(sql))
510                 {
511                         case PGRES_POLLING_WRITING:
512                                 Instance->SE->WantWrite(this);
513                                 status = CWRITE;
514                                 return DoPoll();
515                         case PGRES_POLLING_READING:
516                                 status = CREAD;
517                                 return true;
518                         case PGRES_POLLING_FAILED:
519                                 return false;
520                         case PGRES_POLLING_OK:
521                                 status = WWRITE;
522                                 return DoConnectedPoll();
523                         default:
524                                 return true;
525                 }
526         }
527
528         bool OnDataReady()
529         {
530                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
531                 return DoEvent();
532         }
533
534         bool OnWriteReady()
535         {
536                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
537                 return DoEvent();
538         }
539
540         bool OnConnected()
541         {
542                 return DoEvent();
543         }
544
545         void DelayReconnect();
546
547         bool DoEvent()
548         {
549                 bool ret;
550
551                 if((status == CREAD) || (status == CWRITE))
552                 {
553                         ret = DoPoll();
554                 }
555                 else if((status == RREAD) || (status == RWRITE))
556                 {
557                         ret = DoResetPoll();
558                 }
559                 else
560                 {
561                         ret = DoConnectedPoll();
562                 }
563                 return ret;
564         }
565
566         SQLerror DoQuery(SQLrequest &req)
567         {
568                 if((status == WREAD) || (status == WWRITE))
569                 {
570                         if(!qinprog)
571                         {
572                                 /* Parse the command string and dispatch it */
573
574                                 /* Pointer to the buffer we screw around with substitution in */
575                                 char* query;
576                                 /* Pointer to the current end of query, where we append new stuff */
577                                 char* queryend;
578                                 /* Total length of the unescaped parameters */
579                                 unsigned int paramlen;
580
581                                 paramlen = 0;
582
583                                 for(ParamL::iterator i = req.query.p.begin(); i != req.query.p.end(); i++)
584                                 {
585                                         paramlen += i->size();
586                                 }
587
588                                 /* To avoid a lot of allocations, allocate enough memory for the biggest the escaped query could possibly be.
589                                  * sizeofquery + (totalparamlength*2) + 1
590                                  *
591                                  * The +1 is for null-terminating the string for PQsendQuery()
592                                  */
593
594                                 query = new char[req.query.q.length() + (paramlen*2) + 1];
595                                 queryend = query;
596
597                                 /* Okay, now we have a buffer large enough we need to start copying the query into it and escaping and substituting
598                                  * the parameters into it...
599                                  */
600
601                                 for(unsigned int i = 0; i < req.query.q.length(); i++)
602                                 {
603                                         if(req.query.q[i] == '?')
604                                         {
605                                                 /* We found a place to substitute..what fun.
606                                                  * Use the PgSQL calls to escape and write the
607                                                  * escaped string onto the end of our query buffer,
608                                                  * then we "just" need to make sure queryend is
609                                                  * pointing at the right place.
610                                                  */
611
612                                                 if(req.query.p.size())
613                                                 {
614                                                         int error = 0;
615                                                         size_t len = 0;
616
617 #ifdef PGSQL_HAS_ESCAPECONN
618                                                         len = PQescapeStringConn(sql, queryend, req.query.p.front().c_str(), req.query.p.front().length(), &error);
619 #else
620                                                         len = PQescapeString         (queryend, req.query.p.front().c_str(), req.query.p.front().length());
621 #endif
622                                                         if(error)
623                                                         {
624                                                                 Instance->Log(DEBUG, "BUG: Apparently PQescapeStringConn() failed somehow...don't know how or what to do...");
625                                                         }
626
627                                                         /* Incremenet queryend to the end of the newly escaped parameter */
628                                                         queryend += len;
629
630                                                         /* Remove the parameter we just substituted in */
631                                                         req.query.p.pop_front();
632                                                 }
633                                                 else
634                                                 {
635                                                         Instance->Log(DEBUG, "BUG: Found a substitution location but no parameter to substitute :|");
636                                                         break;
637                                                 }
638                                         }
639                                         else
640                                         {
641                                                 *queryend = req.query.q[i];
642                                                 queryend++;
643                                         }
644                                 }
645
646                                 /* Null-terminate the query */
647                                 *queryend = 0;
648                                 req.query.q = query;
649
650                                 if(PQsendQuery(sql, query))
651                                 {
652                                         qinprog = true;
653                                         delete[] query;
654                                         return SQLerror();
655                                 }
656                                 else
657                                 {
658                                         delete[] query;
659                                         return SQLerror(QSEND_FAIL, PQerrorMessage(sql));
660                                 }
661                         }
662                 }
663                 return SQLerror(BAD_CONN, "Can't query until connection is complete");
664         }
665
666         SQLerror Query(const SQLrequest &req)
667         {
668                 queue.push(req);
669
670                 if(!qinprog && queue.totalsize())
671                 {
672                         /* There's no query currently in progress, and there's queries in the queue. */
673                         SQLrequest& query = queue.front();
674                         return DoQuery(query);
675                 }
676                 else
677                 {
678                         return SQLerror();
679                 }
680         }
681
682         void OnUnloadModule(Module* mod)
683         {
684                 queue.PurgeModule(mod);
685         }
686
687         const SQLhost GetConfHost()
688         {
689                 return confhost;
690         }
691
692         void Close() {
693                 if (!this->Instance->SE->DelFd(this))
694                 {
695                         if (sql && PQstatus(sql) == CONNECTION_BAD)
696                         {
697                                 this->Instance->SE->DelFd(this, true);
698                         }
699                         else
700                         {
701                                 Instance->Log(DEBUG, "BUG: PQsocket cant be removed from socket engine!");
702                         }
703                 }
704
705                 if(sql)
706                 {
707                         PQfinish(sql);
708                         sql = NULL;
709                 }
710         }
711
712 };
713
714 class ModulePgSQL : public Module
715 {
716   private:
717         ConnMap connections;
718         unsigned long currid;
719         char* sqlsuccess;
720         ReconnectTimer* retimer;
721
722   public:
723         ModulePgSQL(InspIRCd* Me)
724         : Module::Module(Me), currid(0)
725         {
726                 ServerInstance->Modules->UseInterface("SQLutils");
727
728                 sqlsuccess = new char[strlen(SQLSUCCESS)+1];
729
730                 strlcpy(sqlsuccess, SQLSUCCESS, strlen(SQLSUCCESS));
731
732                 if (!ServerInstance->Modules->PublishFeature("SQL", this))
733                 {
734                         throw ModuleException("BUG: PgSQL Unable to publish feature 'SQL'");
735                 }
736
737                 ReadConf();
738
739                 ServerInstance->Modules->PublishInterface("SQL", this);
740         }
741
742         virtual ~ModulePgSQL()
743         {
744                 if (retimer)
745                         ServerInstance->Timers->DelTimer(retimer);
746                 ClearAllConnections();
747                 delete[] sqlsuccess;
748                 ServerInstance->Modules->UnpublishInterface("SQL", this);
749                 ServerInstance->Modules->UnpublishFeature("SQL");
750                 ServerInstance->Modules->DoneWithInterface("SQLutils");
751         }
752
753         void Implements(char* List)
754         {
755                 List[I_OnUnloadModule] = List[I_OnRequest] = List[I_OnRehash] = List[I_OnUserRegister] = List[I_OnCheckReady] = List[I_OnUserDisconnect] = 1;
756         }
757
758         virtual void OnRehash(userrec* user, const std::string &parameter)
759         {
760                 ReadConf();
761         }
762
763         bool HasHost(const SQLhost &host)
764         {
765                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
766                 {
767                         if (host == iter->second->GetConfHost())
768                                 return true;
769                 }
770                 return false;
771         }
772
773         bool HostInConf(const SQLhost &h)
774         {
775                 ConfigReader conf(ServerInstance);
776                 for(int i = 0; i < conf.Enumerate("database"); i++)
777                 {
778                         SQLhost host;
779                         host.id         = conf.ReadValue("database", "id", i);
780                         host.host       = conf.ReadValue("database", "hostname", i);
781                         host.port       = conf.ReadInteger("database", "port", i, true);
782                         host.name       = conf.ReadValue("database", "name", i);
783                         host.user       = conf.ReadValue("database", "username", i);
784                         host.pass       = conf.ReadValue("database", "password", i);
785                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
786                         if (h == host)
787                                 return true;
788                 }
789                 return false;
790         }
791
792         void ReadConf()
793         {
794                 ClearOldConnections();
795
796                 ConfigReader conf(ServerInstance);
797                 for(int i = 0; i < conf.Enumerate("database"); i++)
798                 {
799                         SQLhost host;
800                         int ipvalid;
801
802                         host.id         = conf.ReadValue("database", "id", i);
803                         host.host       = conf.ReadValue("database", "hostname", i);
804                         host.port       = conf.ReadInteger("database", "port", i, true);
805                         host.name       = conf.ReadValue("database", "name", i);
806                         host.user       = conf.ReadValue("database", "username", i);
807                         host.pass       = conf.ReadValue("database", "password", i);
808                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
809
810                         if (HasHost(host))
811                                 continue;
812
813 #ifdef IPV6
814                         if (strchr(host.host.c_str(),':'))
815                         {
816                                 in6_addr blargle;
817                                 ipvalid = inet_pton(AF_INET6, host.host.c_str(), &blargle);
818                         }
819                         else
820 #endif
821                         {
822                                 in_addr blargle;
823                                 ipvalid = inet_aton(host.host.c_str(), &blargle);
824                         }
825
826                         if(ipvalid > 0)
827                         {
828                                 /* The conversion succeeded, we were given an IP and we can give it straight to SQLConn */
829                                 host.ip = host.host;
830                                 this->AddConn(host);
831                         }
832                         else if(ipvalid == 0)
833                         {
834                                 /* Conversion failed, assume it's a host */
835                                 SQLresolver* resolver;
836
837                                 try
838                                 {
839                                         bool cached;
840                                         resolver = new SQLresolver(this, ServerInstance, host, cached);
841                                         ServerInstance->AddResolver(resolver, cached);
842                                 }
843                                 catch(...)
844                                 {
845                                         /* THE WORLD IS COMING TO AN END! */
846                                 }
847                         }
848                         else
849                         {
850                                 /* Invalid address family, die horribly. */
851                                 ServerInstance->Log(DEBUG, "BUG: insp_aton failed returning -1, oh noes.");
852                         }
853                 }
854         }
855
856         void ClearOldConnections()
857         {
858                 ConnMap::iterator iter,safei;
859                 for (iter = connections.begin(); iter != connections.end(); iter++)
860                 {
861                         if (!HostInConf(iter->second->GetConfHost()))
862                         {
863                                 DELETE(iter->second);
864                                 safei = iter;
865                                 --iter;
866                                 connections.erase(safei);
867                         }
868                 }
869         }
870
871         void ClearAllConnections()
872         {
873                 ConnMap::iterator i;
874                 while ((i = connections.begin()) != connections.end())
875                 {
876                         connections.erase(i);
877                         DELETE(i->second);
878                 }
879         }
880
881         void AddConn(const SQLhost& hi)
882         {
883                 if (HasHost(hi))
884                 {
885                         ServerInstance->Log(DEFAULT, "WARNING: A pgsql connection with id: %s already exists, possibly due to DNS delay. Aborting connection attempt.", hi.id.c_str());
886                         return;
887                 }
888
889                 SQLConn* newconn;
890
891                 /* The conversion succeeded, we were given an IP and we can give it straight to SQLConn */
892                 newconn = new SQLConn(ServerInstance, this, hi);
893
894                 connections.insert(std::make_pair(hi.id, newconn));
895         }
896
897         void ReconnectConn(SQLConn* conn)
898         {
899                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
900                 {
901                         if (conn == iter->second)
902                         {
903                                 DELETE(iter->second);
904                                 connections.erase(iter);
905                                 break;
906                         }
907                 }
908                 retimer = new ReconnectTimer(ServerInstance, this);
909                 ServerInstance->Timers->AddTimer(retimer);
910         }
911
912         virtual char* OnRequest(Request* request)
913         {
914                 if(strcmp(SQLREQID, request->GetId()) == 0)
915                 {
916                         SQLrequest* req = (SQLrequest*)request;
917                         ConnMap::iterator iter;
918                         if((iter = connections.find(req->dbid)) != connections.end())
919                         {
920                                 /* Execute query */
921                                 req->id = NewID();
922                                 req->error = iter->second->Query(*req);
923
924                                 return (req->error.Id() == NO_ERROR) ? sqlsuccess : NULL;
925                         }
926                         else
927                         {
928                                 req->error.Id(BAD_DBID);
929                                 return NULL;
930                         }
931                 }
932                 return NULL;
933         }
934
935         virtual void OnUnloadModule(Module* mod, const std::string&     name)
936         {
937                 /* When a module unloads we have to check all the pending queries for all our connections
938                  * and set the Module* specifying where the query came from to NULL. If the query has already
939                  * been dispatched then when it is processed it will be dropped if the pointer is NULL.
940                  *
941                  * If the queries we find are not already being executed then we can simply remove them immediately.
942                  */
943                 for(ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
944                 {
945                         iter->second->OnUnloadModule(mod);
946                 }
947         }
948
949         unsigned long NewID()
950         {
951                 if (currid+1 == 0)
952                         currid++;
953
954                 return ++currid;
955         }
956
957         virtual Version GetVersion()
958         {
959                 return Version(1, 1, 0, 0, VF_VENDOR|VF_SERVICEPROVIDER, API_VERSION);
960         }
961 };
962
963 /* move this here to use AddConn, rather that than having the whole
964  * module above SQLConn, since this is buggin me right now :/
965  */
966 void SQLresolver::OnLookupComplete(const std::string &result, unsigned int ttl, bool cached, int resultnum)
967 {
968         if (!resultnum)
969         {
970                 host.ip = result;
971                 ((ModulePgSQL*)mod)->AddConn(host);
972                 ((ModulePgSQL*)mod)->ClearOldConnections();
973         }
974 }
975
976 void ReconnectTimer::Tick(time_t time)
977 {
978         ((ModulePgSQL*)mod)->ReadConf();
979 }
980
981 void SQLConn::DelayReconnect()
982 {
983         ((ModulePgSQL*)us)->ReconnectConn(this);
984 }
985
986 MODULE_INIT(ModulePgSQL);