]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_pgsql.cpp
8222890e830557272b95bb99cfd2869a3e52e1a1
[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  * BufferedSocket. 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 Timer
85 {
86   private:
87         Module* mod;
88   public:
89         ReconnectTimer(InspIRCd* SI, Module* m)
90         : Timer(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                 Implementation eventlist[] = { I_OnUnloadModule, I_OnRequest, I_OnRehash, I_OnUserRegister, I_OnCheckReady, I_OnUserDisconnect };
741                 ServerInstance->Modules->Attach(eventlist, this, 6);
742         }
743
744         virtual ~ModulePgSQL()
745         {
746                 if (retimer)
747                         ServerInstance->Timers->DelTimer(retimer);
748                 ClearAllConnections();
749                 delete[] sqlsuccess;
750                 ServerInstance->Modules->UnpublishInterface("SQL", this);
751                 ServerInstance->Modules->UnpublishFeature("SQL");
752                 ServerInstance->Modules->DoneWithInterface("SQLutils");
753         }
754
755
756         virtual void OnRehash(User* user, const std::string &parameter)
757         {
758                 ReadConf();
759         }
760
761         bool HasHost(const SQLhost &host)
762         {
763                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
764                 {
765                         if (host == iter->second->GetConfHost())
766                                 return true;
767                 }
768                 return false;
769         }
770
771         bool HostInConf(const SQLhost &h)
772         {
773                 ConfigReader conf(ServerInstance);
774                 for(int i = 0; i < conf.Enumerate("database"); i++)
775                 {
776                         SQLhost host;
777                         host.id         = conf.ReadValue("database", "id", i);
778                         host.host       = conf.ReadValue("database", "hostname", i);
779                         host.port       = conf.ReadInteger("database", "port", i, true);
780                         host.name       = conf.ReadValue("database", "name", i);
781                         host.user       = conf.ReadValue("database", "username", i);
782                         host.pass       = conf.ReadValue("database", "password", i);
783                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
784                         if (h == host)
785                                 return true;
786                 }
787                 return false;
788         }
789
790         void ReadConf()
791         {
792                 ClearOldConnections();
793
794                 ConfigReader conf(ServerInstance);
795                 for(int i = 0; i < conf.Enumerate("database"); i++)
796                 {
797                         SQLhost host;
798                         int ipvalid;
799
800                         host.id         = conf.ReadValue("database", "id", i);
801                         host.host       = conf.ReadValue("database", "hostname", i);
802                         host.port       = conf.ReadInteger("database", "port", i, true);
803                         host.name       = conf.ReadValue("database", "name", i);
804                         host.user       = conf.ReadValue("database", "username", i);
805                         host.pass       = conf.ReadValue("database", "password", i);
806                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
807
808                         if (HasHost(host))
809                                 continue;
810
811 #ifdef IPV6
812                         if (strchr(host.host.c_str(),':'))
813                         {
814                                 in6_addr blargle;
815                                 ipvalid = inet_pton(AF_INET6, host.host.c_str(), &blargle);
816                         }
817                         else
818 #endif
819                         {
820                                 in_addr blargle;
821                                 ipvalid = inet_aton(host.host.c_str(), &blargle);
822                         }
823
824                         if(ipvalid > 0)
825                         {
826                                 /* The conversion succeeded, we were given an IP and we can give it straight to SQLConn */
827                                 host.ip = host.host;
828                                 this->AddConn(host);
829                         }
830                         else if(ipvalid == 0)
831                         {
832                                 /* Conversion failed, assume it's a host */
833                                 SQLresolver* resolver;
834
835                                 try
836                                 {
837                                         bool cached;
838                                         resolver = new SQLresolver(this, ServerInstance, host, cached);
839                                         ServerInstance->AddResolver(resolver, cached);
840                                 }
841                                 catch(...)
842                                 {
843                                         /* THE WORLD IS COMING TO AN END! */
844                                 }
845                         }
846                         else
847                         {
848                                 /* Invalid address family, die horribly. */
849                                 ServerInstance->Log(DEBUG, "BUG: insp_aton failed returning -1, oh noes.");
850                         }
851                 }
852         }
853
854         void ClearOldConnections()
855         {
856                 ConnMap::iterator iter,safei;
857                 for (iter = connections.begin(); iter != connections.end(); iter++)
858                 {
859                         if (!HostInConf(iter->second->GetConfHost()))
860                         {
861                                 delete iter->second;
862                                 safei = iter;
863                                 --iter;
864                                 connections.erase(safei);
865                         }
866                 }
867         }
868
869         void ClearAllConnections()
870         {
871                 ConnMap::iterator i;
872                 while ((i = connections.begin()) != connections.end())
873                 {
874                         connections.erase(i);
875                         delete i->second;
876                 }
877         }
878
879         void AddConn(const SQLhost& hi)
880         {
881                 if (HasHost(hi))
882                 {
883                         ServerInstance->Log(DEFAULT, "WARNING: A pgsql connection with id: %s already exists, possibly due to DNS delay. Aborting connection attempt.", hi.id.c_str());
884                         return;
885                 }
886
887                 SQLConn* newconn;
888
889                 /* The conversion succeeded, we were given an IP and we can give it straight to SQLConn */
890                 newconn = new SQLConn(ServerInstance, this, hi);
891
892                 connections.insert(std::make_pair(hi.id, newconn));
893         }
894
895         void ReconnectConn(SQLConn* conn)
896         {
897                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
898                 {
899                         if (conn == iter->second)
900                         {
901                                 delete iter->second;
902                                 connections.erase(iter);
903                                 break;
904                         }
905                 }
906                 retimer = new ReconnectTimer(ServerInstance, this);
907                 ServerInstance->Timers->AddTimer(retimer);
908         }
909
910         virtual char* OnRequest(Request* request)
911         {
912                 if(strcmp(SQLREQID, request->GetId()) == 0)
913                 {
914                         SQLrequest* req = (SQLrequest*)request;
915                         ConnMap::iterator iter;
916                         if((iter = connections.find(req->dbid)) != connections.end())
917                         {
918                                 /* Execute query */
919                                 req->id = NewID();
920                                 req->error = iter->second->Query(*req);
921
922                                 return (req->error.Id() == NO_ERROR) ? sqlsuccess : NULL;
923                         }
924                         else
925                         {
926                                 req->error.Id(BAD_DBID);
927                                 return NULL;
928                         }
929                 }
930                 return NULL;
931         }
932
933         virtual void OnUnloadModule(Module* mod, const std::string&     name)
934         {
935                 /* When a module unloads we have to check all the pending queries for all our connections
936                  * and set the Module* specifying where the query came from to NULL. If the query has already
937                  * been dispatched then when it is processed it will be dropped if the pointer is NULL.
938                  *
939                  * If the queries we find are not already being executed then we can simply remove them immediately.
940                  */
941                 for(ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
942                 {
943                         iter->second->OnUnloadModule(mod);
944                 }
945         }
946
947         unsigned long NewID()
948         {
949                 if (currid+1 == 0)
950                         currid++;
951
952                 return ++currid;
953         }
954
955         virtual Version GetVersion()
956         {
957                 return Version(1, 1, 0, 0, VF_VENDOR|VF_SERVICEPROVIDER, API_VERSION);
958         }
959 };
960
961 /* move this here to use AddConn, rather that than having the whole
962  * module above SQLConn, since this is buggin me right now :/
963  */
964 void SQLresolver::OnLookupComplete(const std::string &result, unsigned int ttl, bool cached, int resultnum)
965 {
966         if (!resultnum)
967         {
968                 host.ip = result;
969                 ((ModulePgSQL*)mod)->AddConn(host);
970                 ((ModulePgSQL*)mod)->ClearOldConnections();
971         }
972 }
973
974 void ReconnectTimer::Tick(time_t time)
975 {
976         ((ModulePgSQL*)mod)->ReadConf();
977 }
978
979 void SQLConn::DelayReconnect()
980 {
981         ((ModulePgSQL*)us)->ReconnectConn(this);
982 }
983
984 MODULE_INIT(ModulePgSQL)