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