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