]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_pgsql.cpp
f11940309dcdb5d4460bedf1e44cec6e3d06fe44
[user/henk/code/inspircd.git] / src / modules / extra / m_pgsql.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2007 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include <cstdlib>
15 #include <sstream>
16 #include <string>
17 #include <deque>
18 #include <map>
19 #include <libpq-fe.h>
20
21 #include "users.h"
22 #include "channels.h"
23 #include "modules.h"
24
25 #include "inspircd.h"
26 #include "configreader.h"
27
28 #include "m_sqlv2.h"
29
30 /* $ModDesc: PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API */
31 /* $CompileFlags: -I`pg_config --includedir` `perl extra/pgsql_config.pl` */
32 /* $LinkerFlags: -L`pg_config --libdir` -lpq */
33 /* $ModDep: m_sqlv2.h */
34
35
36 /* SQLConn rewritten by peavey to
37  * use EventHandler instead of
38  * InspSocket. This is much neater
39  * and gives total control of destroy
40  * and delete of resources.
41  */
42
43 /* Forward declare, so we can have the typedef neatly at the top */
44 class SQLConn;
45
46 typedef std::map<std::string, SQLConn*> ConnMap;
47
48 /* CREAD,       Connecting and wants read event
49  * CWRITE,      Connecting and wants write event
50  * WREAD,       Connected/Working and wants read event
51  * WWRITE,      Connected/Working and wants write event
52  * RREAD,       Resetting and wants read event
53  * RWRITE,      Resetting and wants write event
54  */
55 enum SQLstatus { CREAD, CWRITE, WREAD, WWRITE, RREAD, RWRITE };
56
57 /** SQLhost::GetDSN() - Overload to return correct DSN for PostgreSQL
58  */
59 std::string SQLhost::GetDSN()
60 {
61         std::ostringstream conninfo("connect_timeout = '2'");
62
63         if (ip.length())
64                 conninfo << " hostaddr = '" << ip << "'";
65
66         if (port)
67                 conninfo << " port = '" << port << "'";
68
69         if (name.length())
70                 conninfo << " dbname = '" << name << "'";
71
72         if (user.length())
73                 conninfo << " user = '" << user << "'";
74
75         if (pass.length())
76                 conninfo << " password = '" << pass << "'";
77
78         if (ssl)
79                 conninfo << " sslmode = 'require'";
80
81         return conninfo.str();
82 }
83
84 /** Used to resolve sql server hostnames
85  */
86 class SQLresolver : public Resolver
87 {
88  private:
89         SQLhost host;
90         Module* mod;
91  public:
92         SQLresolver(Module* m, InspIRCd* Instance, const SQLhost& hi, bool &cached)
93         : Resolver(Instance, hi.host, DNS_QUERY_FORWARD, cached, (Module*)m), host(hi), mod(m)
94         {
95         }
96
97         virtual void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached);
98
99         virtual void OnError(ResolverError e, const std::string &errormessage)
100         {
101                 ServerInstance->Log(DEBUG, "DNS lookup failed (%s), dying horribly", errormessage.c_str());
102         }
103 };
104
105 /** QueryQueue, a queue of queries waiting to be executed.
106  * This maintains two queues internally, one for 'priority'
107  * queries and one for less important ones. Each queue has
108  * new queries appended to it and ones to execute are popped
109  * off the front. This keeps them flowing round nicely and no
110  * query should ever get 'stuck' for too long. If there are
111  * queries in the priority queue they will be executed first,
112  * 'unimportant' queries will only be executed when the
113  * priority queue is empty.
114  *
115  * We store lists of SQLrequest's here, by value as we want to avoid storing
116  * any data allocated inside the client module (in case that module is unloaded
117  * while the query is in progress).
118  *
119  * Because we want to work on the current SQLrequest in-situ, we need a way
120  * of accessing the request we are currently processing, QueryQueue::front(),
121  * but that call needs to always return the same request until that request
122  * is removed from the queue, this is what the 'which' variable is. New queries are
123  * always added to the back of one of the two queues, but if when front()
124  * is first called then the priority queue is empty then front() will return
125  * a query from the normal queue, but if a query is then added to the priority
126  * queue then front() must continue to return the front of the *normal* queue
127  * until pop() is called.
128  */
129
130 class QueryQueue : public classbase
131 {
132 private:
133         typedef std::deque<SQLrequest> ReqDeque;
134
135         ReqDeque priority;      /* The priority queue */
136         ReqDeque normal;        /* The 'normal' queue */
137         enum { PRI, NOR, NON } which;   /* Which queue the currently active element is at the front of */
138
139 public:
140         QueryQueue()
141         : which(NON)
142         {
143         }
144
145         void push(const SQLrequest &q)
146         {
147                 //ServerInstance->Log(DEBUG, "QueryQueue::push(): Adding %s query to queue: %s", ((q.pri) ? "priority" : "non-priority"), q.query.q.c_str());
148
149                 if(q.pri)
150                         priority.push_back(q);
151                 else
152                         normal.push_back(q);
153         }
154
155         void pop()
156         {
157                 if((which == PRI) && priority.size())
158                 {
159                         priority.pop_front();
160                 }
161                 else if((which == NOR) && normal.size())
162                 {
163                         normal.pop_front();
164                 }
165
166                 /* Reset this */
167                 which = NON;
168
169                 /* Silently do nothing if there was no element to pop() */
170         }
171
172         SQLrequest& front()
173         {
174                 switch(which)
175                 {
176                         case PRI:
177                                 return priority.front();
178                         case NOR:
179                                 return normal.front();
180                         default:
181                                 if(priority.size())
182                                 {
183                                         which = PRI;
184                                         return priority.front();
185                                 }
186
187                                 if(normal.size())
188                                 {
189                                         which = NOR;
190                                         return normal.front();
191                                 }
192
193                                 /* This will probably result in a segfault,
194                                  * but the caller should have checked totalsize()
195                                  * first so..meh - moron :p
196                                  */
197
198                                 return priority.front();
199                 }
200         }
201
202         std::pair<int, int> size()
203         {
204                 return std::make_pair(priority.size(), normal.size());
205         }
206
207         int totalsize()
208         {
209                 return priority.size() + normal.size();
210         }
211
212         void PurgeModule(Module* mod)
213         {
214                 DoPurgeModule(mod, priority);
215                 DoPurgeModule(mod, normal);
216         }
217
218 private:
219         void DoPurgeModule(Module* mod, ReqDeque& q)
220         {
221                 for(ReqDeque::iterator iter = q.begin(); iter != q.end(); iter++)
222                 {
223                         if(iter->GetSource() == mod)
224                         {
225                                 if(iter->id == front().id)
226                                 {
227                                         /* It's the currently active query.. :x */
228                                         iter->SetSource(NULL);
229                                 }
230                                 else
231                                 {
232                                         /* It hasn't been executed yet..just remove it */
233                                         iter = q.erase(iter);
234                                 }
235                         }
236                 }
237         }
238 };
239
240 /** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult.
241  * All SQL providers must create their own subclass and define it's methods using that
242  * database library's data retriveal functions. The aim is to avoid a slow and inefficient process
243  * of converting all data to a common format before it reaches the result structure. This way
244  * data is passes to the module nearly as directly as if it was using the API directly itself.
245  */
246
247 class PgSQLresult : public SQLresult
248 {
249         PGresult* res;
250         int currentrow;
251         int rows;
252         int cols;
253
254         SQLfieldList* fieldlist;
255         SQLfieldMap* fieldmap;
256 public:
257         PgSQLresult(Module* self, Module* to, unsigned long id, PGresult* result)
258         : SQLresult(self, to, id), res(result), currentrow(0), fieldlist(NULL), fieldmap(NULL)
259         {
260                 rows = PQntuples(res);
261                 cols = PQnfields(res);
262
263                 //ServerInstance->Log(DEBUG, "Created new PgSQL result; %d rows, %d columns, %s affected", rows, cols, PQcmdTuples(res));
264         }
265
266         ~PgSQLresult()
267         {
268                 /* If we allocated these, free them... */
269                 if(fieldlist)
270                         DELETE(fieldlist);
271
272                 if(fieldmap)
273                         DELETE(fieldmap);
274
275                 PQclear(res);
276         }
277
278         virtual int Rows()
279         {
280                 if(!cols && !rows)
281                 {
282                         return atoi(PQcmdTuples(res));
283                 }
284                 else
285                 {
286                         return rows;
287                 }
288         }
289
290         virtual int Cols()
291         {
292                 return PQnfields(res);
293         }
294
295         virtual std::string ColName(int column)
296         {
297                 char* name = PQfname(res, column);
298
299                 return (name) ? name : "";
300         }
301
302         virtual int ColNum(const std::string &column)
303         {
304                 int n = PQfnumber(res, column.c_str());
305
306                 if(n == -1)
307                 {
308                         throw SQLbadColName();
309                 }
310                 else
311                 {
312                         return n;
313                 }
314         }
315
316         virtual SQLfield GetValue(int row, int column)
317         {
318                 char* v = PQgetvalue(res, row, column);
319
320                 if(v)
321                 {
322                         return SQLfield(std::string(v, PQgetlength(res, row, column)), PQgetisnull(res, row, column));
323                 }
324                 else
325                 {
326                         //ServerInstance->Log(DEBUG, "PQgetvalue returned a null pointer..nobody wants to tell us what this means");
327                         throw SQLbadColName();
328                 }
329         }
330
331         virtual SQLfieldList& GetRow()
332         {
333                 /* In an effort to reduce overhead we don't actually allocate the list
334                  * until the first time it's needed...so...
335                  */
336                 if(fieldlist)
337                 {
338                         fieldlist->clear();
339                 }
340                 else
341                 {
342                         fieldlist = new SQLfieldList;
343                 }
344
345                 if(currentrow < PQntuples(res))
346                 {
347                         int cols = PQnfields(res);
348
349                         for(int i = 0; i < cols; i++)
350                         {
351                                 fieldlist->push_back(GetValue(currentrow, i));
352                         }
353
354                         currentrow++;
355                 }
356
357                 return *fieldlist;
358         }
359
360         virtual SQLfieldMap& GetRowMap()
361         {
362                 /* In an effort to reduce overhead we don't actually allocate the map
363                  * until the first time it's needed...so...
364                  */
365                 if(fieldmap)
366                 {
367                         fieldmap->clear();
368                 }
369                 else
370                 {
371                         fieldmap = new SQLfieldMap;
372                 }
373
374                 if(currentrow < PQntuples(res))
375                 {
376                         int cols = PQnfields(res);
377
378                         for(int i = 0; i < cols; i++)
379                         {
380                                 fieldmap->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
381                         }
382
383                         currentrow++;
384                 }
385
386                 return *fieldmap;
387         }
388
389         virtual SQLfieldList* GetRowPtr()
390         {
391                 SQLfieldList* fl = new SQLfieldList;
392
393                 if(currentrow < PQntuples(res))
394                 {
395                         int cols = PQnfields(res);
396
397                         for(int i = 0; i < cols; i++)
398                         {
399                                 fl->push_back(GetValue(currentrow, i));
400                         }
401
402                         currentrow++;
403                 }
404
405                 return fl;
406         }
407
408         virtual SQLfieldMap* GetRowMapPtr()
409         {
410                 SQLfieldMap* fm = new SQLfieldMap;
411
412                 if(currentrow < PQntuples(res))
413                 {
414                         int cols = PQnfields(res);
415
416                         for(int i = 0; i < cols; i++)
417                         {
418                                 fm->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
419                         }
420
421                         currentrow++;
422                 }
423
424                 return fm;
425         }
426
427         virtual void Free(SQLfieldMap* fm)
428         {
429                 DELETE(fm);
430         }
431
432         virtual void Free(SQLfieldList* fl)
433         {
434                 DELETE(fl);
435         }
436 };
437
438 /** SQLConn represents one SQL session.
439  */
440 class SQLConn : public EventHandler
441 {
442   private:
443         InspIRCd*               Instance;
444         SQLhost                 confhost;       /* The <database> entry */
445         Module*                 us;                     /* Pointer to the SQL provider itself */
446         PGconn*                 sql;            /* PgSQL database connection handle */
447         SQLstatus               status;         /* PgSQL database connection status */
448         bool                    qinprog;        /* If there is currently a query in progress */
449         QueryQueue              queue;          /* Queue of queries waiting to be executed on this connection */
450         time_t                  idle;           /* Time we last heard from the database */
451
452         bool                    wantwrite;
453
454   public:
455         SQLConn(InspIRCd* SI, Module* self, const SQLhost& hi)
456         : EventHandler(), Instance(SI), confhost(hi), us(self), sql(NULL), status(CWRITE), qinprog(false)
457         {
458                 wantwrite = false;
459                 idle = this->Instance->Time();
460
461                 if(!DoConnect())
462                 {
463                         Instance->Log(DEFAULT, "WARNING: Could not connect to database with id: " + ConvToStr(hi.id));
464                 }
465         }
466
467         ~SQLConn()
468         {
469                 Close();
470         }
471
472         virtual bool Writeable()
473         {
474                 return wantwrite;
475         }
476
477         virtual bool Readable()
478         {
479                 return !wantwrite;
480         }
481
482         virtual void HandleEvent(EventType et, int errornum)
483         {
484                 switch (et)
485                 {
486                         case EVENT_READ:
487                                 OnDataReady();
488                                 wantwrite = true;
489                         break;
490
491                         case EVENT_WRITE:
492                                 OnWriteReady();
493                                 wantwrite = false;
494                         break;
495
496                         case EVENT_ERROR:
497                                 Reconnect();
498                         break;
499
500                         default:
501                         break;
502                 }
503         }
504
505         bool DoConnect()
506         {
507                 if(!(sql = PQconnectStart(confhost.GetDSN().c_str())))
508                 {
509                         Instance->Log(DEBUG, "Couldn't allocate PGconn structure, aborting: %s", PQerrorMessage(sql));
510                         Close();
511                         return false;
512                 }
513                 if(PQstatus(sql) == CONNECTION_BAD)
514                 {
515                         Instance->Log(DEBUG, "PQconnectStart failed: %s", PQerrorMessage(sql));
516                         Close();
517                         return false;
518                 }
519                 ShowStatus();
520                 if(PQsetnonblocking(sql, 1) == -1)
521                 {
522                         Instance->Log(DEBUG, "Couldn't set connection nonblocking: %s", PQerrorMessage(sql));
523                         Close();
524                         return false;
525                 }
526                 this->fd = PQsocket(sql);
527
528                 Instance->Log(DEBUG, "New SQL socket: %d", this->fd);
529
530                 if(this->fd <= -1)
531                 {
532                         Instance->Log(DEBUG, "PQsocket says we have an invalid FD: %d", this->fd);
533                         Close();
534                         return false;
535                 }
536
537                 if (!this->Instance->SE->AddFd(this))
538                 {
539                         Instance->Log(DEBUG, "A PQsocket cant be added to the socket engine!");
540                         Close();
541                         return false;
542                 }
543
544                 /* Socket all hooked into the engine, now to tell PgSQL to start connecting */
545
546                 return DoPoll();
547         }
548
549         bool DoPoll()
550         {
551                 switch(PQconnectPoll(sql))
552                 {
553                         case PGRES_POLLING_WRITING:
554                                 wantwrite = true;
555                                 status = CWRITE;
556                                 return DoPoll();
557                         case PGRES_POLLING_READING:
558                                 status = CREAD;
559                                 return true;
560                         case PGRES_POLLING_FAILED:
561                                 return false;
562                         case PGRES_POLLING_OK:
563                                 status = WWRITE;
564                                 return DoConnectedPoll();
565                         default:
566                                 return true;
567                 }
568         }
569
570         bool DoConnectedPoll()
571         {
572                 if(!qinprog && queue.totalsize())
573                 {
574                         /* There's no query currently in progress, and there's queries in the queue. */
575                         SQLrequest& query = queue.front();
576                         DoQuery(query);
577                 }
578
579                 if(PQconsumeInput(sql))
580                 {
581                         Instance->Log(DEBUG, "PQconsumeInput succeeded");
582
583                         /* We just read stuff from the server, that counts as it being alive
584                          * so update the idle-since time :p
585                          */
586                         idle = this->Instance->Time();
587
588                         if(PQisBusy(sql))
589                         {
590                                 //ServerInstance->Log(DEBUG, "Still busy processing command though");
591                         }
592                         else if(qinprog)
593                         {
594                                 //ServerInstance->Log(DEBUG, "Looks like we have a result to process!");
595
596                                 /* Grab the request we're processing */
597                                 SQLrequest& query = queue.front();
598
599                                 Instance->Log(DEBUG, "ID is %lu", query.id);
600
601                                 /* Get a pointer to the module we're about to return the result to */
602                                 Module* to = query.GetSource();
603
604                                 /* Fetch the result.. */
605                                 PGresult* result = PQgetResult(sql);
606
607                                 /* PgSQL would allow a query string to be sent which has multiple
608                                  * queries in it, this isn't portable across database backends and
609                                  * we don't want modules doing it. But just in case we make sure we
610                                  * drain any results there are and just use the last one.
611                                  * If the module devs are behaving there will only be one result.
612                                  */
613                                 while (PGresult* temp = PQgetResult(sql))
614                                 {
615                                         PQclear(result);
616                                         result = temp;
617                                 }
618
619                                 if(to)
620                                 {
621                                         /* ..and the result */
622                                         PgSQLresult reply(us, to, query.id, result);
623
624                                         /* Fix by brain, make sure the original query gets sent back in the reply */
625                                         reply.query = query.query.q;
626
627                                         Instance->Log(DEBUG, "Got result, status code: %s; error message: %s", PQresStatus(PQresultStatus(result)), PQresultErrorMessage(result));
628
629                                         switch(PQresultStatus(result))
630                                         {
631                                                 case PGRES_EMPTY_QUERY:
632                                                 case PGRES_BAD_RESPONSE:
633                                                 case PGRES_FATAL_ERROR:
634                                                         reply.error.Id(QREPLY_FAIL);
635                                                         reply.error.Str(PQresultErrorMessage(result));
636                                                 default:;
637                                                         /* No action, other values are not errors */
638                                         }
639
640                                         reply.Send();
641
642                                         /* PgSQLresult's destructor will free the PGresult */
643                                 }
644                                 else
645                                 {
646                                         /* If the client module is unloaded partway through a query then the provider will set
647                                          * the pointer to NULL. We cannot just cancel the query as the result will still come
648                                          * through at some point...and it could get messy if we play with invalid pointers...
649                                          */
650                                         Instance->Log(DEBUG, "Looks like we're handling a zombie query from a module which unloaded before it got a result..fun. ID: %lu", query.id);
651                                         PQclear(result);
652                                 }
653                                 qinprog = false;
654                                 queue.pop();
655                                 DoConnectedPoll();
656                         }
657                         else
658                         {
659                                 Instance->Log(DEBUG, "Eh!? We just got a read event, and connection isn't busy..but no result :(");
660                         }
661                         return true;
662                 }
663                 else
664                 {
665                         /* I think we'll assume this means the server died...it might not,
666                          * but I think that any error serious enough we actually get here
667                          * deserves to reconnect [/excuse]
668                          * Returning true so the core doesn't try and close the connection.
669                          */
670                         Instance->Log(DEBUG, "PQconsumeInput failed: %s", PQerrorMessage(sql));
671                         Reconnect();
672                         return true;
673                 }
674         }
675
676         bool DoResetPoll()
677         {
678                 switch(PQresetPoll(sql))
679                 {
680                         case PGRES_POLLING_WRITING:
681                                 //ServerInstance->Log(DEBUG, "PGresetPoll: PGRES_POLLING_WRITING");
682                                 wantwrite = true;
683                                 status = CWRITE;
684                                 return DoPoll();
685                         case PGRES_POLLING_READING:
686                                 //ServerInstance->Log(DEBUG, "PGresetPoll: PGRES_POLLING_READING");
687                                 status = CREAD;
688                                 return true;
689                         case PGRES_POLLING_FAILED:
690                                 //ServerInstance->Log(DEBUG, "PGresetPoll: PGRES_POLLING_FAILED: %s", PQerrorMessage(sql));
691                                 return false;
692                         case PGRES_POLLING_OK:
693                                 //ServerInstance->Log(DEBUG, "PGresetPoll: PGRES_POLLING_OK");
694                                 status = WWRITE;
695                                 return DoConnectedPoll();
696                         default:
697                                 //ServerInstance->Log(DEBUG, "PGresetPoll: wtf?");
698                                 return true;
699                 }
700         }
701
702         void ShowStatus()
703         {
704                 switch(PQstatus(sql))
705                 {
706                         case CONNECTION_STARTED:
707                                 Instance->Log(DEBUG, "PQstatus: CONNECTION_STARTED: Waiting for connection to be made.");
708                                 break;
709
710                         case CONNECTION_MADE:
711                                 Instance->Log(DEBUG, "PQstatus: CONNECTION_MADE: Connection OK; waiting to send.");
712                                 break;
713
714                         case CONNECTION_AWAITING_RESPONSE:
715                                 Instance->Log(DEBUG, "PQstatus: CONNECTION_AWAITING_RESPONSE: Waiting for a response from the server.");
716                                 break;
717
718                         case CONNECTION_AUTH_OK:
719                                 Instance->Log(DEBUG, "PQstatus: CONNECTION_AUTH_OK: Received authentication; waiting for backend start-up to finish.");
720                                 break;
721
722                         case CONNECTION_SSL_STARTUP:
723                                 Instance->Log(DEBUG, "PQstatus: CONNECTION_SSL_STARTUP: Negotiating SSL encryption.");
724                                 break;
725
726                         case CONNECTION_SETENV:
727                                 Instance->Log(DEBUG, "PQstatus: CONNECTION_SETENV: Negotiating environment-driven parameter settings.");
728                                 break;
729
730                         default:
731                                 Instance->Log(DEBUG, "PQstatus: ???");
732                 }
733         }
734
735         bool OnDataReady()
736         {
737                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
738                 Instance->Log(DEBUG, "OnDataReady(): status = %s", StatusStr());
739                 return DoEvent();
740         }
741
742         bool OnWriteReady()
743         {
744                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
745                 Instance->Log(DEBUG, "OnWriteReady(): status = %s", StatusStr());
746                 return DoEvent();
747         }
748
749         bool OnConnected()
750         {
751                 Instance->Log(DEBUG, "OnConnected(): status = %s", StatusStr());
752                 return DoEvent();
753         }
754
755         bool Reconnect()
756         {
757                 Instance->Log(DEBUG, "Initiating reconnect");
758                 if(PQresetStart(sql))
759                 {
760                         /* Successfully initiatied database reconnect,
761                          * set flags so PQresetPoll() will be called appropriately
762                          */
763                         status = RWRITE;
764                         qinprog = false;
765                         return true;
766                 }
767                 else
768                 {
769                         Instance->Log(DEBUG, "Failed to initiate reconnect...fun");
770                         return false;
771                 }
772         }
773
774         bool DoEvent()
775         {
776                 bool ret;
777
778                 if((status == CREAD) || (status == CWRITE))
779                 {
780                         ret = DoPoll();
781                 }
782                 else if((status == RREAD) || (status == RWRITE))
783                 {
784                         ret = DoResetPoll();
785                 }
786                 else
787                 {
788                         ret = DoConnectedPoll();
789                 }
790
791                 switch(PQflush(sql))
792                 {
793                         case -1:
794                                 Instance->Log(DEBUG, "Error flushing write queue: %s", PQerrorMessage(sql));
795                                 break;
796                         case 0:
797                                 Instance->Log(DEBUG, "Successfully flushed write queue (or there was nothing to write)");
798                                 break;
799                         case 1:
800                                 Instance->Log(DEBUG, "Not all of the write queue written, triggering write event so we can have another go");
801                                 wantwrite = true;
802                                 break;
803                 }
804
805                 return ret;
806         }
807
808         const char* StatusStr()
809         {
810                 if(status == CREAD) return "CREAD";
811                 if(status == CWRITE) return "CWRITE";
812                 if(status == WREAD) return "WREAD";
813                 if(status == WWRITE) return "WWRITE";
814                 return "Err...what, erm..BUG!";
815         }
816
817         SQLerror DoQuery(SQLrequest &req)
818         {
819                 if((status == WREAD) || (status == WWRITE))
820                 {
821                         if(!qinprog)
822                         {
823                                 /* Parse the command string and dispatch it */
824
825                                 /* Pointer to the buffer we screw around with substitution in */
826                                 char* query;
827                                 /* Pointer to the current end of query, where we append new stuff */
828                                 char* queryend;
829                                 /* Total length of the unescaped parameters */
830                                 unsigned int paramlen;
831
832                                 paramlen = 0;
833
834                                 for(ParamL::iterator i = req.query.p.begin(); i != req.query.p.end(); i++)
835                                 {
836                                         paramlen += i->size();
837                                 }
838
839                                 /* To avoid a lot of allocations, allocate enough memory for the biggest the escaped query could possibly be.
840                                  * sizeofquery + (totalparamlength*2) + 1
841                                  *
842                                  * The +1 is for null-terminating the string for PQsendQuery()
843                                  */
844
845                                 query = new char[req.query.q.length() + (paramlen*2) + 1];
846                                 queryend = query;
847
848                                 /* Okay, now we have a buffer large enough we need to start copying the query into it and escaping and substituting
849                                  * the parameters into it...
850                                  */
851
852                                 for(unsigned int i = 0; i < req.query.q.length(); i++)
853                                 {
854                                         if(req.query.q[i] == '?')
855                                         {
856                                                 /* We found a place to substitute..what fun.
857                                                  * Use the PgSQL calls to escape and write the
858                                                  * escaped string onto the end of our query buffer,
859                                                  * then we "just" need to make sure queryend is
860                                                  * pointing at the right place.
861                                                  */
862
863                                                 if(req.query.p.size())
864                                                 {
865                                                         int error = 0;
866                                                         size_t len = 0;
867
868 #ifdef PGSQL_HAS_ESCAPECONN
869                                                         len = PQescapeStringConn(sql, queryend, req.query.p.front().c_str(), req.query.p.front().length(), &error);
870 #else
871                                                         len = PQescapeString         (queryend, req.query.p.front().c_str(), req.query.p.front().length());
872 #endif
873                                                         if(error)
874                                                         {
875                                                                 Instance->Log(DEBUG, "Apparently PQescapeStringConn() failed somehow...don't know how or what to do...");
876                                                         }
877
878                                                         Instance->Log(DEBUG, "Appended %d bytes of escaped string onto the query", len);
879
880                                                         /* Incremenet queryend to the end of the newly escaped parameter */
881                                                         queryend += len;
882
883                                                         /* Remove the parameter we just substituted in */
884                                                         req.query.p.pop_front();
885                                                 }
886                                                 else
887                                                 {
888                                                         Instance->Log(DEBUG, "Found a substitution location but no parameter to substitute :|");
889                                                         break;
890                                                 }
891                                         }
892                                         else
893                                         {
894                                                 *queryend = req.query.q[i];
895                                                 queryend++;
896                                         }
897                                 }
898
899                                 /* Null-terminate the query */
900                                 *queryend = 0;
901
902                                 Instance->Log(DEBUG, "Attempting to dispatch query: %s", query);
903
904                                 req.query.q = query;
905
906                                 if(PQsendQuery(sql, query))
907                                 {
908                                         Instance->Log(DEBUG, "Dispatched query successfully");
909                                         qinprog = true;
910                                         delete[] query;
911                                         return SQLerror();
912                                 }
913                                 else
914                                 {
915                                         Instance->Log(DEBUG, "Failed to dispatch query: %s", PQerrorMessage(sql));
916                                         delete[] query;
917                                         return SQLerror(QSEND_FAIL, PQerrorMessage(sql));
918                                 }
919                         }
920                 }
921
922                 return SQLerror(BAD_CONN, "Can't query until connection is complete");
923         }
924
925         SQLerror Query(const SQLrequest &req)
926         {
927                 queue.push(req);
928
929                 if(!qinprog && queue.totalsize())
930                 {
931                         /* There's no query currently in progress, and there's queries in the queue. */
932                         SQLrequest& query = queue.front();
933                         return DoQuery(query);
934                 }
935                 else
936                 {
937                         return SQLerror();
938                 }
939         }
940
941
942         void OnUnloadModule(Module* mod)
943         {
944                 queue.PurgeModule(mod);
945         }
946
947         const SQLhost GetConfHost()
948         {
949                 return confhost;
950         }
951
952         void Close() {
953                 DoClose();
954         }
955
956         void DoClose()
957         {
958                 Instance->Log(DEBUG,"SQLConn::Close");
959
960                 if (!this->Instance->SE->DelFd(this))
961                 {
962                         Instance->Log(DEBUG, "PQsocket cant be removed from the socket engine!");
963                 }
964                 this->fd = -1;
965
966                 if(sql)
967                 {
968                         PQfinish(sql);
969                         sql = NULL;
970                 }
971         }
972
973 };
974
975
976 class ModulePgSQL : public Module
977 {
978   private:
979         ConnMap connections;
980         ConnMap deadconnections;
981         unsigned long currid;
982         char* sqlsuccess;
983
984   public:
985         ModulePgSQL(InspIRCd* Me)
986         : Module::Module(Me), currid(0)
987         {
988                 ServerInstance->UseInterface("SQLutils");
989
990                 sqlsuccess = new char[strlen(SQLSUCCESS)+1];
991
992                 strlcpy(sqlsuccess, SQLSUCCESS, strlen(SQLSUCCESS));
993
994                 if (!ServerInstance->PublishFeature("SQL", this))
995                 {
996                         throw ModuleException("m_pgsql: Unable to publish feature 'SQL'");
997                 }
998
999                 ReadConf();
1000
1001                 ServerInstance->PublishInterface("SQL", this);
1002         }
1003
1004         virtual ~ModulePgSQL()
1005         {
1006                 ClearAllConnections();
1007                 delete[] sqlsuccess;
1008                 ServerInstance->UnpublishInterface("SQL", this);
1009                 ServerInstance->UnpublishFeature("SQL");
1010                 ServerInstance->DoneWithInterface("SQLutils");
1011         }
1012
1013         void Implements(char* List)
1014         {
1015                 List[I_OnUnloadModule] = List[I_OnRequest] = List[I_OnRehash] = List[I_OnUserRegister] = List[I_OnCheckReady] = List[I_OnUserDisconnect] = 1;
1016         }
1017
1018         virtual void OnRehash(userrec* user, const std::string &parameter)
1019         {
1020                 ReadConf();
1021         }
1022
1023         bool HasHost(const SQLhost &host)
1024         {
1025                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
1026                 {
1027                         if (host == iter->second->GetConfHost())
1028                                 return true;
1029                 }
1030                 return false;
1031         }
1032
1033         bool HostInConf(const SQLhost &h)
1034         {
1035                 ConfigReader conf(ServerInstance);
1036                 for(int i = 0; i < conf.Enumerate("database"); i++)
1037                 {
1038                         SQLhost host;
1039                         host.id         = conf.ReadValue("database", "id", i);
1040                         host.host       = conf.ReadValue("database", "hostname", i);
1041                         host.port       = conf.ReadInteger("database", "port", i, true);
1042                         host.name       = conf.ReadValue("database", "name", i);
1043                         host.user       = conf.ReadValue("database", "username", i);
1044                         host.pass       = conf.ReadValue("database", "password", i);
1045                         host.ssl        = conf.ReadFlag("database", "ssl", i);
1046                         if (h == host)
1047                                 return true;
1048                 }
1049                 return false;
1050         }
1051
1052         void ReadConf()
1053         {
1054                 ClearOldConnections();
1055
1056                 ConfigReader conf(ServerInstance);
1057                 for(int i = 0; i < conf.Enumerate("database"); i++)
1058                 {
1059                         SQLhost host;
1060                         int ipvalid;
1061                         insp_inaddr blargle;
1062
1063                         host.id         = conf.ReadValue("database", "id", i);
1064                         host.host       = conf.ReadValue("database", "hostname", i);
1065                         host.port       = conf.ReadInteger("database", "port", i, true);
1066                         host.name       = conf.ReadValue("database", "name", i);
1067                         host.user       = conf.ReadValue("database", "username", i);
1068                         host.pass       = conf.ReadValue("database", "password", i);
1069                         host.ssl        = conf.ReadFlag("database", "ssl", i);
1070
1071                         if (HasHost(host))
1072                                 continue;
1073
1074                         ipvalid = insp_aton(host.host.c_str(), &blargle);
1075
1076                         if(ipvalid > 0)
1077                         {
1078                                 /* The conversion succeeded, we were given an IP and we can give it straight to SQLConn */
1079                                 host.ip = host.host;
1080                                 this->AddConn(host);
1081                         }
1082                         else if(ipvalid == 0)
1083                         {
1084                                 /* Conversion failed, assume it's a host */
1085                                 SQLresolver* resolver;
1086
1087                                 try
1088                                 {
1089                                         bool cached;
1090                                         resolver = new SQLresolver(this, ServerInstance, host, cached);
1091                                         ServerInstance->AddResolver(resolver, cached);
1092                                 }
1093                                 catch(...)
1094                                 {
1095                                         /* THE WORLD IS COMING TO AN END! */
1096                                         ServerInstance->Log(DEBUG, "Couldn't make a SQLresolver..this connection is gonna diiiiiie...actually we just won't create it");
1097                                 }
1098                         }
1099                         else
1100                         {
1101                                 /* Invalid address family, die horribly. */
1102                                 ServerInstance->Log(DEBUG, "insp_aton failed returning -1, oh noes.");
1103                         }
1104                 }
1105         }
1106
1107         void ClearOldConnections()
1108         {
1109                 ConnMap::iterator iter,safei;
1110                 for (iter = connections.begin(); iter != connections.end(); iter++)
1111                 {
1112                         if (!HostInConf(iter->second->GetConfHost()))
1113                         {
1114                                 DELETE(iter->second);
1115                                 safei = iter;
1116                                 --iter;
1117                                 connections.erase(safei);
1118                         }
1119                 }
1120         }
1121
1122         void ClearAllConnections()
1123         {
1124                 ConnMap::iterator i;
1125                 while ((i = connections.begin()) != connections.end())
1126                 {
1127                         connections.erase(i);
1128                         DELETE(i->second);
1129                 }
1130         }
1131
1132         void AddConn(const SQLhost& hi)
1133         {
1134                 if (HasHost(hi))
1135                 {
1136                         ServerInstance->Log(DEFAULT, "WARNING: A pgsql connection with id: %s already exists, possibly due to DNS delay. Aborting connection attempt.", hi.id.c_str());
1137                         return;
1138                 }
1139
1140                 SQLConn* newconn;
1141
1142                 /* The conversion succeeded, we were given an IP and we can give it straight to SQLConn */
1143                 newconn = new SQLConn(ServerInstance, this, hi);
1144
1145                 connections.insert(std::make_pair(hi.id, newconn));
1146         }
1147
1148         virtual char* OnRequest(Request* request)
1149         {
1150                 if(strcmp(SQLREQID, request->GetId()) == 0)
1151                 {
1152                         SQLrequest* req = (SQLrequest*)request;
1153                         ConnMap::iterator iter;
1154
1155                         ServerInstance->Log(DEBUG, "Got query: '%s' with %d replacement parameters on id '%s'", req->query.q.c_str(), req->query.p.size(), req->dbid.c_str());
1156
1157                         if((iter = connections.find(req->dbid)) != connections.end())
1158                         {
1159                                 /* Execute query */
1160                                 req->id = NewID();
1161                                 req->error = iter->second->Query(*req);
1162
1163                                 return (req->error.Id() == NO_ERROR) ? sqlsuccess : NULL;
1164                         }
1165                         else
1166                         {
1167                                 req->error.Id(BAD_DBID);
1168                                 return NULL;
1169                         }
1170                 }
1171
1172                 ServerInstance->Log(DEBUG, "Got unsupported API version string: %s", request->GetId());
1173
1174                 return NULL;
1175         }
1176
1177         virtual void OnUnloadModule(Module* mod, const std::string&     name)
1178         {
1179                 /* When a module unloads we have to check all the pending queries for all our connections
1180                  * and set the Module* specifying where the query came from to NULL. If the query has already
1181                  * been dispatched then when it is processed it will be dropped if the pointer is NULL.
1182                  *
1183                  * If the queries we find are not already being executed then we can simply remove them immediately.
1184                  */
1185                 for(ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
1186                 {
1187                         iter->second->OnUnloadModule(mod);
1188                 }
1189         }
1190
1191         unsigned long NewID()
1192         {
1193                 if (currid+1 == 0)
1194                         currid++;
1195
1196                 return ++currid;
1197         }
1198
1199         virtual Version GetVersion()
1200         {
1201                 return Version(1, 1, 0, 0, VF_VENDOR|VF_SERVICEPROVIDER, API_VERSION);
1202         }
1203 };
1204
1205 /* move this here to use AddConn, rather that than having the whole
1206  * module above SQLConn, since this is buggin me right now :/
1207  */
1208 void SQLresolver::OnLookupComplete(const std::string &result, unsigned int ttl, bool cached)
1209 {
1210         host.ip = result;
1211         ((ModulePgSQL*)mod)->AddConn(host);
1212         ((ModulePgSQL*)mod)->ClearOldConnections();
1213 }
1214
1215 class ModulePgSQLFactory : public ModuleFactory
1216 {
1217  public:
1218         ModulePgSQLFactory()
1219         {
1220         }
1221
1222         ~ModulePgSQLFactory()
1223         {
1224         }
1225
1226         virtual Module * CreateModule(InspIRCd* Me)
1227         {
1228                 return new ModulePgSQL(Me);
1229         }
1230 };
1231
1232
1233 extern "C" void * init_module( void )
1234 {
1235         return new ModulePgSQLFactory;
1236 }