]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/inspsocket.cpp
Remove an extern, partly because it's unused, partly because it then gets shadowed...
[user/henk/code/inspircd.git] / src / inspsocket.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd is copyright (C) 2002-2006 ChatSpike-Dev.
6  *                       E-mail:
7  *                <brain@chatspike.net>
8  *                <Craig@chatspike.net>
9  *     
10  * Written by Craig Edwards, Craig McLure, and others.
11  * This program is free but copyrighted software; see
12  *            the file COPYING for details.
13  *
14  * ---------------------------------------------------
15  */
16
17 #include <string>
18 #include <sstream>
19 #include <iostream>
20 #include <fstream>
21 #include <stdexcept>
22 #include "inspircd_config.h"
23 #include "socket.h"
24 #include "inspircd.h"
25 #include "configreader.h"
26 #include "inspstring.h"
27 #include "helperfuncs.h"
28 #include "socketengine.h"
29
30
31 extern InspIRCd* ServerInstance;
32 extern ServerConfig* Config;
33 extern time_t TIME;
34
35 InspSocket* socket_ref[MAX_DESCRIPTORS];
36
37 InspSocket::InspSocket()
38 {
39         this->state = I_DISCONNECTED;
40         this->fd = -1;
41         this->ClosePending = false;
42 }
43
44 InspSocket::InspSocket(int newfd, char* ip)
45 {
46         this->fd = newfd;
47         this->state = I_CONNECTED;
48         strlcpy(this->IP,ip,MAXBUF);
49         this->ClosePending = false;
50         ServerInstance->SE->AddFd(this->fd,true,X_ESTAB_MODULE);
51         socket_ref[this->fd] = this;
52 }
53
54 InspSocket::InspSocket(const std::string &ahost, int aport, bool listening, unsigned long maxtime) : fd(-1)
55 {
56         strlcpy(host,ahost.c_str(),MAXBUF);
57         this->ClosePending = false;
58         if (listening) {
59                 if ((this->fd = OpenTCPSocket()) == ERROR)
60                 {
61                         this->fd = -1;
62                         this->state = I_ERROR;
63                         this->OnError(I_ERR_SOCKET);
64                         this->ClosePending = true;
65                         log(DEBUG,"OpenTCPSocket() error");
66                         return;
67                 }
68                 else
69                 {
70                         if (!BindSocket(this->fd,this->client,this->server,aport,(char*)ahost.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                                 log(DEBUG,"BindSocket() error %s",strerror(errno));
78                                 return;
79                         }
80                         else
81                         {
82                                 this->state = I_LISTENING;
83                                 ServerInstance->SE->AddFd(this->fd,true,X_ESTAB_MODULE);
84                                 socket_ref[this->fd] = this;
85                                 log(DEBUG,"New socket now in I_LISTENING state");
86                                 return;
87                         }
88                 }                       
89         }
90         else
91         {
92                 strlcpy(this->host,ahost.c_str(),MAXBUF);
93                 this->port = aport;
94
95                 if (!inet_aton(host,&addy))
96                 {
97                         log(DEBUG,"Attempting to resolve %s",this->host);
98                         /* Its not an ip, spawn the resolver */
99                         this->dns.SetNS(std::string(Config->DNSServer));
100                         this->dns.ForwardLookupWithFD(host,fd);
101                         timeout_end = time(NULL) + maxtime;
102                         timeout = false;
103                         this->state = I_RESOLVING;
104                         socket_ref[this->fd] = this;
105                 }
106                 else
107                 {
108                         log(DEBUG,"No need to resolve %s",this->host);
109                         strlcpy(this->IP,host,MAXBUF);
110                         timeout_end = time(NULL) + maxtime;
111                         this->DoConnect();
112                 }
113         }
114 }
115
116 void InspSocket::SetQueues(int nfd)
117 {
118         // attempt to increase socket sendq and recvq as high as its possible
119         int sendbuf = 32768;
120         int recvbuf = 32768;
121         setsockopt(nfd,SOL_SOCKET,SO_SNDBUF,(const void *)&sendbuf,sizeof(sendbuf));
122         setsockopt(nfd,SOL_SOCKET,SO_RCVBUF,(const void *)&recvbuf,sizeof(sendbuf));
123 }
124
125 bool InspSocket::DoResolve()
126 {
127         log(DEBUG,"In DoResolve(), trying to resolve IP");
128         if (this->dns.HasResult())
129         {
130                 log(DEBUG,"Socket has result");
131                 std::string res_ip = dns.GetResultIP();
132                 if (res_ip != "")
133                 {
134                         log(DEBUG,"Socket result set to %s",res_ip.c_str());
135                         strlcpy(this->IP,res_ip.c_str(),MAXBUF);
136                         socket_ref[this->fd] = NULL;
137                 }
138                 else
139                 {
140                         log(DEBUG,"Socket DNS failure");
141                         this->Close();
142                         this->state = I_ERROR;
143                         this->OnError(I_ERR_RESOLVE);
144                         this->fd = -1;
145                         this->ClosePending = true;
146                         return false;
147                 }
148                 return this->DoConnect();
149         }
150         log(DEBUG,"No result for socket yet!");
151         return true;
152 }
153
154 bool InspSocket::DoConnect()
155 {
156         log(DEBUG,"In DoConnect()");
157         if ((this->fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
158         {
159                 log(DEBUG,"Cant socket()");
160                 this->state = I_ERROR;
161                 this->OnError(I_ERR_SOCKET);
162                 this->fd = -1;
163                 return false;
164         }
165
166         log(DEBUG,"Part 2 DoConnect() %s",this->IP);
167         inet_aton(this->IP,&addy);
168         addr.sin_family = AF_INET;
169         addr.sin_addr = addy;
170         addr.sin_port = htons(this->port);
171
172         int flags;
173         flags = fcntl(this->fd, F_GETFL, 0);
174         fcntl(this->fd, F_SETFL, flags | O_NONBLOCK);
175
176         if (connect(this->fd, (sockaddr*)&this->addr,sizeof(this->addr)) == -1)
177         {
178                 if (errno != EINPROGRESS)
179                 {
180                         log(DEBUG,"Error connect() %d: %s",this->fd,strerror(errno));
181                         this->OnError(I_ERR_CONNECT);
182                         this->Close();
183                         this->state = I_ERROR;
184                         this->fd = -1;
185                         this->ClosePending = true;
186                         return false;
187                 }
188         }
189         this->state = I_CONNECTING;
190         ServerInstance->SE->AddFd(this->fd,false,X_ESTAB_MODULE);
191         socket_ref[this->fd] = this;
192         this->SetQueues(this->fd);
193         log(DEBUG,"Returning true from InspSocket::DoConnect");
194         return true;
195 }
196
197
198 void InspSocket::Close()
199 {
200         if (this->fd != -1)
201         {
202                 this->OnClose();
203                 shutdown(this->fd,2);
204                 close(this->fd);
205                 socket_ref[this->fd] = NULL;
206                 this->ClosePending = true;
207                 this->fd = -1;
208         }
209 }
210
211 std::string InspSocket::GetIP()
212 {
213         return this->IP;
214 }
215
216 char* InspSocket::Read()
217 {
218         if ((fd < 0) || (fd > MAX_DESCRIPTORS))
219                 return NULL;
220         int n = recv(this->fd,this->ibuf,sizeof(this->ibuf),0);
221         if ((n > 0) && (n <= (int)sizeof(this->ibuf)))
222         {
223                 ibuf[n] = 0;
224                 return ibuf;
225         }
226         else
227         {
228                 if (errno == EAGAIN)
229                 {
230                         return "";
231                 }
232                 else
233                 {
234                         log(DEBUG,"EOF or error on socket: %s",strerror(errno));
235                         return NULL;
236                 }
237         }
238 }
239
240 void InspSocket::MarkAsClosed()
241 {
242         log(DEBUG,"Marked as closed");
243         this->ClosePending = true;
244 }
245
246 // There are two possible outcomes to this function.
247 // It will either write all of the data, or an undefined amount.
248 // If an undefined amount is written the connection has failed
249 // and should be aborted.
250 int InspSocket::Write(const std::string &data)
251 {
252         if (this->ClosePending)
253                 return false;
254
255         /*int result = write(this->fd,data.c_str(),data.length());
256         if (result < 1)
257                 return false;
258         return true;*/
259
260         /* Try and append the data to the back of the queue, and send it on its way
261          */
262         outbuffer.push_back(data);
263         return (!this->FlushWriteBuffer());
264 }
265
266 bool InspSocket::FlushWriteBuffer()
267 {
268         if (this->ClosePending)
269                 return true;
270
271         if ((this->fd > -1) && (this->state == I_CONNECTED))
272         {
273                 if (outbuffer.size())
274                 {
275                         int result = write(this->fd,outbuffer[0].c_str(),outbuffer[0].length());
276                         if (result > 0)
277                         {
278                                 if ((unsigned int)result == outbuffer[0].length())
279                                 {
280                                         /* The whole block was written (usually a line)
281                                          * Pop the block off the front of the queue
282                                          */
283                                         outbuffer.pop_front();
284                                 }
285                                 else
286                                 {
287                                         std::string temp = outbuffer[0].substr(result);
288                                         outbuffer[0] = temp;
289                                 }
290                         }
291                         else if ((result == -1) && (errno != EAGAIN))
292                         {
293                                 log(DEBUG,"Write error on socket: %s",strerror(errno));
294                                 this->OnError(I_ERR_WRITE);
295                                 this->state = I_ERROR;
296                                 this->ClosePending = true;
297                                 return true;
298                         }
299                 }
300         }
301         return (fd < 0);
302 }
303
304 bool InspSocket::Timeout(time_t current)
305 {
306         if (!socket_ref[this->fd] || !ServerInstance->SE->HasFd(this->fd))
307         {
308                 log(DEBUG,"No FD or socket ref");
309                 return false;
310         }
311
312         if (this->ClosePending)
313         {
314                 log(DEBUG,"Close is pending");
315                 return true;
316         }
317
318         if (((this->state == I_RESOLVING) || (this->state == I_CONNECTING)) && (current > timeout_end))
319         {
320                 log(DEBUG,"Timed out, current=%lu timeout_end=%lu");
321                 // for non-listening sockets, the timeout can occur
322                 // which causes termination of the connection after
323                 // the given number of seconds without a successful
324                 // connection.
325                 this->OnTimeout();
326                 this->OnError(I_ERR_TIMEOUT);
327                 timeout = true;
328                 this->state = I_ERROR;
329                 this->ClosePending = true;
330                 return true;
331         }
332         return this->FlushWriteBuffer();
333 }
334
335 bool InspSocket::Poll()
336 {
337         if (!socket_ref[this->fd] || !ServerInstance->SE->HasFd(this->fd))
338                 return false;
339
340         int incoming = -1;
341         bool n = true;
342
343         if ((fd < 0) || (fd > MAX_DESCRIPTORS) || (this->ClosePending))
344                 return false;
345
346         switch (this->state)
347         {
348                 case I_RESOLVING:
349                         log(DEBUG,"State = I_RESOLVING, calling DoResolve()");
350                         return this->DoResolve();
351                 break;
352                 case I_CONNECTING:
353                         log(DEBUG,"State = I_CONNECTING");
354                         this->SetState(I_CONNECTED);
355                         /* Our socket was in write-state, so delete it and re-add it
356                          * in read-state.
357                          */
358                         ServerInstance->SE->DelFd(this->fd);
359                         ServerInstance->SE->AddFd(this->fd,true,X_ESTAB_MODULE);
360                         return this->OnConnected();
361                 break;
362                 case I_LISTENING:
363                         length = sizeof (client);
364                         incoming = accept (this->fd, (sockaddr*)&client,&length);
365                         this->SetQueues(incoming);
366                         this->OnIncomingConnection(incoming,inet_ntoa(client.sin_addr));
367                         return true;
368                 break;
369                 case I_CONNECTED:
370                         n = this->OnDataReady();
371                         /* Flush any pending, but not till after theyre done with the event
372                          * so there are less write calls involved.
373                          * Both FlushWriteBuffer AND the return result of OnDataReady must
374                          * return true for this to be ok.
375                          */
376                         if (this->FlushWriteBuffer())
377                                 return false;
378                         return n;
379                 break;
380                 default:
381                 break;
382         }
383         return true;
384 }
385
386 void InspSocket::SetState(InspSocketState s)
387 {
388         log(DEBUG,"Socket state change");
389         this->state = s;
390 }
391
392 InspSocketState InspSocket::GetState()
393 {
394         return this->state;
395 }
396
397 int InspSocket::GetFd()
398 {
399         return this->fd;
400 }
401
402 bool InspSocket::OnConnected() { return true; }
403 void InspSocket::OnError(InspSocketError e) { return; }
404 int InspSocket::OnDisconnect() { return 0; }
405 int InspSocket::OnIncomingConnection(int newfd, char* ip) { return 0; }
406 bool InspSocket::OnDataReady() { return true; }
407 void InspSocket::OnTimeout() { return; }
408 void InspSocket::OnClose() { return; }
409
410 InspSocket::~InspSocket()
411 {
412         this->Close();
413 }