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