]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/inspsocket.cpp
Fix crashbug in latest svn introduced by inspsocket cull list, which can cause a...
[user/henk/code/inspircd.git] / src / inspsocket.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 "socket.h"
15 #include "configreader.h"
16 #include "inspstring.h"
17 #include "socketengine.h"
18 #include "inspircd.h"
19
20 using irc::sockets::OpenTCPSocket;
21
22 bool InspSocket::Readable()
23 {
24         return ((this->state != I_CONNECTING) && (this->WaitingForWriteEvent == false));
25 }
26
27 InspSocket::InspSocket(InspIRCd* SI)
28 {
29         this->Timeout = NULL;
30         this->state = I_DISCONNECTED;
31         this->fd = -1;
32         this->WaitingForWriteEvent = false;
33         this->Instance = SI;
34         this->IsIOHooked = false;
35 }
36
37 InspSocket::InspSocket(InspIRCd* SI, int newfd, const char* ip)
38 {
39         this->Timeout = NULL;
40         this->fd = newfd;
41         this->state = I_CONNECTED;
42         strlcpy(this->IP,ip,MAXBUF);
43         this->WaitingForWriteEvent = false;
44         this->Instance = SI;
45         this->IsIOHooked = false;
46         if (this->fd > -1)
47                 this->Instance->SE->AddFd(this);
48 }
49
50 InspSocket::InspSocket(InspIRCd* SI, const std::string &ipaddr, int aport, bool listening, unsigned long maxtime, const std::string &connectbindip)
51 {
52         this->cbindip = connectbindip;
53         this->fd = -1;
54         this->Instance = SI;
55         strlcpy(host,ipaddr.c_str(),MAXBUF);
56         this->WaitingForWriteEvent = false;
57         this->IsIOHooked = false;
58         this->Timeout = NULL;
59         if (listening)
60         {
61                 if ((this->fd = OpenTCPSocket(host)) == ERROR)
62                 {
63                         this->fd = -1;
64                         this->state = I_ERROR;
65                         this->OnError(I_ERR_SOCKET);
66                         return;
67                 }
68                 else
69                 {
70                         if (!SI->BindSocket(this->fd,aport,(char*)ipaddr.c_str()))
71                         {
72                                 this->Close();
73                                 this->fd = -1;
74                                 this->state = I_ERROR;
75                                 this->OnError(I_ERR_BIND);
76                                 this->ClosePending = true;
77                                 return;
78                         }
79                         else
80                         {
81                                 this->state = I_LISTENING;
82                                 if (this->fd > -1)
83                                 {
84                                         if (!this->Instance->SE->AddFd(this))
85                                         {
86                                                 this->Close();
87                                                 this->state = I_ERROR;
88                                                 this->OnError(I_ERR_NOMOREFDS);
89                                         }
90                                 }
91                                 return;
92                         }
93                 }
94         }
95         else
96         {
97                 strlcpy(this->host,ipaddr.c_str(),MAXBUF);
98                 this->port = aport;
99
100                 bool ipvalid = true;
101 #ifdef IPV6
102                 if (strchr(host,':'))
103                 {
104                         in6_addr n;
105                         if (inet_pton(AF_INET6, host, &n) < 1)
106                                 ipvalid = false;
107                 }
108                 else
109                 {
110                         in_addr n;
111                         if (inet_aton(host,&n) < 1)
112                                 ipvalid = false;
113                 }
114 #else
115                 in_addr n;
116                 if (inet_aton(host,&n) < 1)
117                         ipvalid = false;
118 #endif
119                 if (!ipvalid)
120                 {
121                         this->Instance->Log(DEBUG,"BUG: Hostname passed to InspSocket, rather than an IP address!");
122                         this->Close();
123                         this->fd = -1;
124                         this->state = I_ERROR;
125                         this->OnError(I_ERR_RESOLVE);
126                         return;
127                 }
128                 else
129                 {
130                         strlcpy(this->IP,host,MAXBUF);
131                         timeout_val = maxtime;
132                         if (!this->DoConnect())
133                         {
134                                 this->Close();
135                                 this->fd = -1;
136                                 this->state = I_ERROR;
137                                 this->OnError(I_ERR_CONNECT);
138                                 return;
139                         }
140                 }
141         }
142 }
143
144 void InspSocket::WantWrite()
145 {
146         this->Instance->SE->WantWrite(this);
147         this->WaitingForWriteEvent = true;
148 }
149
150 void InspSocket::SetQueues(int nfd)
151 {
152         // attempt to increase socket sendq and recvq as high as its possible
153         int sendbuf = 32768;
154         int recvbuf = 32768;
155         setsockopt(nfd,SOL_SOCKET,SO_SNDBUF,(const void *)&sendbuf,sizeof(sendbuf));
156         setsockopt(nfd,SOL_SOCKET,SO_RCVBUF,(const void *)&recvbuf,sizeof(sendbuf));
157 }
158
159 /* Most irc servers require you to specify the ip you want to bind to.
160  * If you dont specify an IP, they rather dumbly bind to the first IP
161  * of the box (e.g. INADDR_ANY). In InspIRCd, we scan thought the IP
162  * addresses we've bound server ports to, and we try and bind our outbound
163  * connections to the first usable non-loopback and non-any IP we find.
164  * This is easier to configure when you have a lot of links and a lot
165  * of servers to configure.
166  */
167 bool InspSocket::BindAddr(const std::string &ip)
168 {
169         ConfigReader Conf(this->Instance);
170         socklen_t size = sizeof(sockaddr_in);
171 #ifdef IPV6
172         bool v6 = false;
173         /* Are we looking for a binding to fit an ipv6 host? */
174         if ((ip.empty()) || (ip.find(':') != std::string::npos))
175                 v6 = true;
176 #endif
177         int j = 0;
178         while (j < Conf.Enumerate("bind") || (!ip.empty()))
179         {
180                 std::string IP = ip.empty() ? Conf.ReadValue("bind","address",j) : ip;
181                 if (!ip.empty() || Conf.ReadValue("bind","type",j) == "servers")
182                 {
183                         if (!ip.empty() || ((IP != "*") && (IP != "127.0.0.1") && (IP != "") && (IP != "::1")))
184                         {
185                                 sockaddr* s = new sockaddr[2];
186 #ifdef IPV6
187                                 if (v6)
188                                 {
189                                         in6_addr n;
190                                         if (inet_pton(AF_INET6, IP.c_str(), &n) > 0)
191                                         {
192                                                 memcpy(&((sockaddr_in6*)s)->sin6_addr, &n, sizeof(n));
193                                                 ((sockaddr_in6*)s)->sin6_port = 0;
194                                                 ((sockaddr_in6*)s)->sin6_family = AF_INET6;
195                                                 size = sizeof(sockaddr_in6);
196                                         }
197                                         else
198                                         {
199                                                 delete[] s;
200                                                 j++;
201                                                 continue;
202                                         }
203                                 }
204                                 else
205                                 {
206                                         in_addr n;
207                                         if (inet_aton(IP.c_str(), &n) > 0)
208                                         {
209                                                 ((sockaddr_in*)s)->sin_addr = n;
210                                                 ((sockaddr_in*)s)->sin_port = 0;
211                                                 ((sockaddr_in*)s)->sin_family = AF_INET;
212                                         }
213                                         else
214                                         {
215                                                 delete[] s;
216                                                 j++;
217                                                 continue;
218                                         }
219                                 }
220 #else
221                                 in_addr n;
222                                 if (insp_aton(IP.c_str(), &n) > 0)
223                                 {
224                                         ((sockaddr_in*)s)->sin_addr = n;
225                                         ((sockaddr_in*)s)->sin_port = 0;
226                                         ((sockaddr_in*)s)->sin_family = AF_INET;
227                                 }
228                                 else
229                                 {
230                                         delete[] s;
231                                         j++;
232                                         continue;
233                                 }
234 #endif
235
236                                 if (bind(this->fd, s, size) < 0)
237                                 {
238                                         this->state = I_ERROR;
239                                         this->OnError(I_ERR_BIND);
240                                         this->fd = -1;
241                                         delete[] s;
242                                         return false;
243                                 }
244
245                                 delete[] s;
246                                 return true;
247                         }
248                 }
249                 j++;
250         }
251         return true;
252 }
253
254 bool InspSocket::DoConnect()
255 {
256         sockaddr* addr = new sockaddr[2];
257         socklen_t size = sizeof(sockaddr_in);
258 #ifdef IPV6
259         bool v6 = false;
260         if ((!*this->host) || strchr(this->host, ':'))
261                 v6 = true;
262
263         if (v6)
264         {
265                 this->fd = socket(AF_INET6, SOCK_STREAM, 0);
266                 if ((this->fd > -1) && ((strstr(this->IP,"::ffff:") != (char*)&this->IP) && (strstr(this->IP,"::FFFF:") != (char*)&this->IP)))
267                 {
268                         if (!this->BindAddr(this->cbindip))
269                         {
270                                 delete[] addr;
271                                 return false;
272                         }
273                 }
274         }
275         else
276         {
277                 this->fd = socket(AF_INET, SOCK_STREAM, 0);
278                 if (this->fd > -1)
279                 {
280                         if (!this->BindAddr(this->cbindip))
281                         {
282                                 delete[] addr;
283                                 return false;
284                         }
285                 }
286         }
287 #else
288         this->fd = socket(AF_INET, SOCK_STREAM, 0);
289         if (this->fd > -1)
290         {
291                 if (!this->BindAddr(this->cbindip))
292                 {
293                         delete[] addr;
294                         return false;
295                 }
296         }
297 #endif
298
299         if (this->fd == -1)
300         {
301                 this->state = I_ERROR;
302                 this->OnError(I_ERR_SOCKET);
303                 delete[] addr;
304                 return false;
305         }
306
307 #ifdef IPV6
308         if (v6)
309         {
310                 in6_addr addy;
311                 if (inet_pton(AF_INET6, this->host, &addy) > 0)
312                 {
313                         ((sockaddr_in6*)addr)->sin6_family = AF_INET6;
314                         memcpy(&((sockaddr_in6*)addr)->sin6_addr, &addy, sizeof(addy));
315                         ((sockaddr_in6*)addr)->sin6_port = htons(this->port);
316                         size = sizeof(sockaddr_in6);
317                 }
318         }
319         else
320         {
321                 in_addr addy;
322                 if (inet_aton(this->host, &addy) > 0)
323                 {
324                         ((sockaddr_in*)addr)->sin_family = AF_INET;
325                         ((sockaddr_in*)addr)->sin_addr = addy;
326                         ((sockaddr_in*)addr)->sin_port = htons(this->port);
327                 }
328         }
329 #else
330         in_addr addy;
331         if (inet_aton(this->host, &addy) > 0)
332         {
333                 ((sockaddr_in*)addr)->sin_family = AF_INET;
334                 ((sockaddr_in*)addr)->sin_addr = addy;
335                 ((sockaddr_in*)addr)->sin_port = htons(this->port);
336         }
337 #endif
338
339         int flags = fcntl(this->fd, F_GETFL, 0);
340         fcntl(this->fd, F_SETFL, flags | O_NONBLOCK);
341
342         if (connect(this->fd, (sockaddr*)addr, size) == -1)
343         {
344                 if (errno != EINPROGRESS)
345                 {
346                         this->OnError(I_ERR_CONNECT);
347                         this->Close();
348                         this->state = I_ERROR;
349                         return false;
350                 }
351
352                 this->Timeout = new SocketTimeout(this->GetFd(), this->Instance, this, timeout_val, this->Instance->Time());
353                 this->Instance->Timers->AddTimer(this->Timeout);
354         }
355         this->state = I_CONNECTING;
356         if (this->fd > -1)
357         {
358                 if (!this->Instance->SE->AddFd(this))
359                 {
360                         this->OnError(I_ERR_NOMOREFDS);
361                         this->Close();
362                         this->state = I_ERROR;
363                         return false;
364                 }
365                 this->SetQueues(this->fd);
366         }
367         return true;
368 }
369
370
371 void InspSocket::Close()
372 {
373         /* Save this, so we dont lose it,
374          * otherise on failure, error messages
375          * might be inaccurate.
376          */
377         int save = errno;
378         if (this->fd > -1)
379         {
380                 if (this->IsIOHooked && Instance->Config->GetIOHook(this))
381                 {
382                         try
383                         {
384                                 Instance->Config->GetIOHook(this)->OnRawSocketClose(this->fd);
385                         }
386                         catch (CoreException& modexcept)
387                         {
388                                 Instance->Log(DEFAULT,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
389                         }
390                 }
391                 this->OnClose();
392                 shutdown(this->fd,2);
393                 close(this->fd);
394         }
395         errno = save;
396 }
397
398 std::string InspSocket::GetIP()
399 {
400         return this->IP;
401 }
402
403 char* InspSocket::Read()
404 {
405         if ((fd < 0) || (fd > MAX_DESCRIPTORS))
406                 return NULL;
407
408         int n = 0;
409
410         if (this->IsIOHooked)
411         {
412                 int result2 = 0;
413                 int MOD_RESULT = 0;
414                 try
415                 {
416                         MOD_RESULT = Instance->Config->GetIOHook(this)->OnRawSocketRead(this->fd,this->ibuf,sizeof(this->ibuf),result2);
417                 }
418                 catch (CoreException& modexcept)
419                 {
420                         Instance->Log(DEFAULT,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
421                 }
422                 if (MOD_RESULT < 0)
423                 {
424                         n = -1;
425                         errno = EAGAIN;
426                 }
427                 else
428                 {
429                         n = result2;
430                 }
431         }
432         else
433         {
434                 n = recv(this->fd,this->ibuf,sizeof(this->ibuf),0);
435         }
436
437         if ((n > 0) && (n <= (int)sizeof(this->ibuf)))
438         {
439                 ibuf[n] = 0;
440                 return ibuf;
441         }
442         else
443         {
444                 int err = errno;
445                 if (err == EAGAIN)
446                         return "";
447                 else
448                         return NULL;
449         }
450 }
451
452 void InspSocket::MarkAsClosed()
453 {
454 }
455
456 // There are two possible outcomes to this function.
457 // It will either write all of the data, or an undefined amount.
458 // If an undefined amount is written the connection has failed
459 // and should be aborted.
460 int InspSocket::Write(const std::string &data)
461 {
462         /* Try and append the data to the back of the queue, and send it on its way
463          */
464         outbuffer.push_back(data);
465         this->Instance->SE->WantWrite(this);
466         return (!this->FlushWriteBuffer());
467 }
468
469 bool InspSocket::FlushWriteBuffer()
470 {
471         errno = 0;
472         if ((this->fd > -1) && (this->state == I_CONNECTED))
473         {
474                 if (this->IsIOHooked)
475                 {
476                         while (outbuffer.size() && (errno != EAGAIN))
477                         {
478                                 try
479                                 {
480                                         int result = Instance->Config->GetIOHook(this)->OnRawSocketWrite(this->fd, outbuffer[0].c_str(), outbuffer[0].length());
481                                         if (result > 0)
482                                         {
483                                                 if ((unsigned int)result >= outbuffer[0].length())
484                                                 {
485                                                         outbuffer.pop_front();
486                                                 }
487                                                 else
488                                                 {
489                                                         std::string temp = outbuffer[0].substr(result);
490                                                         outbuffer[0] = temp;
491                                                         errno = EAGAIN;
492                                                 }
493                                         }
494                                         else if (((result == -1) && (errno != EAGAIN)) || (result == 0))
495                                         {
496                                                 this->OnError(I_ERR_WRITE);
497                                                 this->state = I_ERROR;
498                                                 this->Instance->SE->DelFd(this);
499                                                 this->Close();
500                                                 return true;
501                                         }
502                                 }
503                                 catch (CoreException& modexcept)
504                                 {
505                                         Instance->Log(DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
506                                         return true;
507                                 }
508                         }
509                 }
510                 else
511                 {
512                         /* If we have multiple lines, try to send them all,
513                          * not just the first one -- Brain
514                          */
515                         while (outbuffer.size() && (errno != EAGAIN))
516                         {
517                                 /* Send a line */
518                                 int result = write(this->fd,outbuffer[0].c_str(),outbuffer[0].length());
519                                 if (result > 0)
520                                 {
521                                         if ((unsigned int)result >= outbuffer[0].length())
522                                         {
523                                                 /* The whole block was written (usually a line)
524                                                  * Pop the block off the front of the queue,
525                                                  * dont set errno, because we are clear of errors
526                                                  * and want to try and write the next block too.
527                                                  */
528                                                 outbuffer.pop_front();
529                                         }
530                                         else
531                                         {
532                                                 std::string temp = outbuffer[0].substr(result);
533                                                 outbuffer[0] = temp;
534                                                 /* We didnt get the whole line out. arses.
535                                                  * Try again next time, i guess. Set errno,
536                                                  * because we shouldnt be writing any more now,
537                                                  * until the socketengine says its safe to do so.
538                                                  */
539                                                 errno = EAGAIN;
540                                         }
541                                 }
542                                 else if ((result == -1) && (errno != EAGAIN))
543                                 {
544                                         this->OnError(I_ERR_WRITE);
545                                         this->state = I_ERROR;
546                                         this->Instance->SE->DelFd(this);
547                                         this->Close();
548                                         return true;
549                                 }
550                         }
551                 }
552         }
553
554         if ((errno == EAGAIN) && (fd > -1))
555         {
556                 this->Instance->SE->WantWrite(this);
557         }
558
559         return (fd < 0);
560 }
561
562 void SocketTimeout::Tick(time_t now)
563 {
564         if (ServerInstance->SE->GetRef(this->sfd) != this->sock)
565                 return;
566
567         if (this->sock->state == I_CONNECTING)
568         {
569                 // for non-listening sockets, the timeout can occur
570                 // which causes termination of the connection after
571                 // the given number of seconds without a successful
572                 // connection.
573                 this->sock->OnTimeout();
574                 this->sock->OnError(I_ERR_TIMEOUT);
575                 this->sock->timeout = true;
576
577                 /* NOTE: We must set this AFTER DelFd, as we added
578                  * this socket whilst writeable. This means that we
579                  * must DELETE the socket whilst writeable too!
580                  */
581                 this->sock->state = I_ERROR;
582
583                 if (ServerInstance->SocketCull.find(this->sock) == ServerInstance->SocketCull.end())
584                         ServerInstance->SocketCull[this->sock] = this->sock;
585         }
586
587         this->sock->Timeout = NULL;
588 }
589
590 bool InspSocket::Poll()
591 {
592         if (this->Instance->SE->GetRef(this->fd) != this)
593                 return false;
594
595         int incoming = -1;
596
597         if ((fd < 0) || (fd > MAX_DESCRIPTORS))
598                 return false;
599
600         switch (this->state)
601         {
602                 case I_CONNECTING:
603                         /* Our socket was in write-state, so delete it and re-add it
604                          * in read-state.
605                          */
606                         if (this->fd > -1)
607                         {
608                                 this->Instance->SE->DelFd(this);
609                                 this->SetState(I_CONNECTED);
610                                 if (!this->Instance->SE->AddFd(this))
611                                         return false;
612                         }
613                         if (Instance->Config->GetIOHook(this))
614                         {
615                                 try
616                                 {
617                                         Instance->Config->GetIOHook(this)->OnRawSocketConnect(this->fd);
618                                 }
619                                 catch (CoreException& modexcept)
620                                 {
621                                         Instance->Log(DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
622                                 }
623                         }
624                         return this->OnConnected();
625                 break;
626                 case I_LISTENING:
627                 {
628                         sockaddr* client = new sockaddr[2];
629                         length = sizeof (sockaddr_in);
630                         std::string recvip;
631 #ifdef IPV6
632                         if ((!*this->host) || strchr(this->host, ':'))
633                                 length = sizeof(sockaddr_in6);
634 #endif
635                         incoming = accept (this->fd, client, &length);
636 #ifdef IPV6
637                         if ((!*this->host) || strchr(this->host, ':'))
638                         {
639                                 char buf[1024];
640                                 recvip = inet_ntop(AF_INET6, &((sockaddr_in6*)client)->sin6_addr, buf, sizeof(buf));
641                         }
642                         else
643                         {
644                                 recvip = inet_ntoa(((sockaddr_in*)client)->sin_addr);
645                         }
646 #else
647                         recvip = inet_ntoa(((sockaddr_in*)client)->sin_addr);
648 #endif
649                         this->OnIncomingConnection(incoming, (char*)recvip.c_str());
650
651                         if (this->IsIOHooked)
652                         {
653                                 try
654                                 {
655                                         Instance->Config->GetIOHook(this)->OnRawSocketAccept(incoming, recvip.c_str(), this->port);
656                                 }
657                                 catch (CoreException& modexcept)
658                                 {
659                                         Instance->Log(DEBUG,"%s threw an exception: %s", modexcept.GetSource(), modexcept.GetReason());
660                                 }
661                         }
662
663                         this->SetQueues(incoming);
664
665                         delete[] client;
666                         return true;
667                 }
668                 break;
669                 case I_CONNECTED:
670                         /* Process the read event */
671                         return this->OnDataReady();
672                 break;
673                 default:
674                 break;
675         }
676         return true;
677 }
678
679 void InspSocket::SetState(InspSocketState s)
680 {
681         this->state = s;
682 }
683
684 InspSocketState InspSocket::GetState()
685 {
686         return this->state;
687 }
688
689 int InspSocket::GetFd()
690 {
691         return this->fd;
692 }
693
694 bool InspSocket::OnConnected() { return true; }
695 void InspSocket::OnError(InspSocketError e) { return; }
696 int InspSocket::OnDisconnect() { return 0; }
697 int InspSocket::OnIncomingConnection(int newfd, char* ip) { return 0; }
698 bool InspSocket::OnDataReady() { return true; }
699 bool InspSocket::OnWriteReady() { return true; }
700 void InspSocket::OnTimeout() { return; }
701 void InspSocket::OnClose() { return; }
702
703 InspSocket::~InspSocket()
704 {
705         this->Close();
706         if (Timeout)
707         {
708                 Instance->Timers->DelTimer(Timeout);
709                 Timeout = NULL;
710         }
711 }
712
713 void InspSocket::HandleEvent(EventType et, int errornum)
714 {
715         switch (et)
716         {
717                 case EVENT_ERROR:
718                         if (this->Instance->SocketCull.find(this) == this->Instance->SocketCull.end())
719                                 this->Instance->SocketCull[this] = this;
720                         return;
721                 break;
722                 case EVENT_READ:
723                         if (!this->Poll())
724                         {
725                                 if (this->Instance->SocketCull.find(this) == this->Instance->SocketCull.end())
726                                         this->Instance->SocketCull[this] = this;
727                                 return;
728                         }
729                 break;
730                 case EVENT_WRITE:
731                         if (this->WaitingForWriteEvent)
732                         {
733                                 this->WaitingForWriteEvent = false;
734                                 if (!this->OnWriteReady())
735                                 {
736                                         if (this->Instance->SocketCull.find(this) == this->Instance->SocketCull.end())
737                                                 this->Instance->SocketCull[this] = this;
738                                         return;
739                                 }
740                         }
741                         if (this->state == I_CONNECTING)
742                         {
743                                 /* This might look wrong as if we should be actually calling
744                                  * with EVENT_WRITE, but trust me it is correct. There are some
745                                  * writeability-state things in the read code, because of how
746                                  * InspSocket used to work regarding write buffering in previous
747                                  * versions of InspIRCd. - Brain
748                                  */
749                                 this->HandleEvent(EVENT_READ);
750                                 return;
751                         }
752                         else
753                         {
754                                 if (this->FlushWriteBuffer())
755                                 {
756                                         if (this->Instance->SocketCull.find(this) == this->Instance->SocketCull.end())
757                                                 this->Instance->SocketCull[this] = this;
758                                         return;
759                                 }
760                         }
761                 break;
762         }
763 }
764