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