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