added session manager and support for an external login system
[TransparentIRC.git] / src / IOHandler.c
1 /* IOHandler.c - TransparentIRC 0.1
2  * Copyright (C) 2011-2012  Philipp Kreil (pk910)
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License 
15  * along with this program. If not, see <http://www.gnu.org/licenses/>. 
16  */
17 #include "IOHandler.h"
18 #include "IOEngine.h"
19
20 struct IODescriptor *first_descriptor = NULL;
21
22 /* IO Engines */
23 extern struct IOEngine engine_select; /* select system call (should always be useable) */
24
25 struct IOEngine *engine = NULL;
26
27 static void iohandler_init_engine() {
28     //try other engines
29     
30     if(engine) {
31         //found an useable IO engine
32     } else if (engine_select.init())
33         engine = &engine_select;
34     else {
35         //STDERR: found no useable IO engine
36     }
37 }
38
39 struct IODescriptor *iohandler_add(int sockfd, enum IOType type, iohandler_callback *callback) {
40     //just add a new IODescriptor
41     struct IODescriptor *descriptor = calloc(1, sizeof(*descriptor));
42     if(!descriptor) return NULL;
43     descriptor->fd = sockfd;
44     descriptor->type = type;
45     descriptor->state = IO_CLOSED;
46     descriptor->callback = callback;
47     if(type != IOTYPE_TIMER) {
48         descriptor->readbuf.buffer = malloc(IO_READ_BUFLEN + 2);
49         descriptor->readbuf.bufpos = 0;
50         descriptor->readbuf.buflen = IO_READ_BUFLEN;
51         descriptor->writebuf.buffer = malloc(IO_READ_BUFLEN + 2);
52         descriptor->writebuf.bufpos = 0;
53         descriptor->writebuf.buflen = IO_READ_BUFLEN;
54     }
55     
56     if(!engine) {
57         iohandler_init_engine();
58         if(!engine) {
59             return NULL;
60         }
61     }
62     engine->add(descriptor);
63     
64     //add IODescriptor to the list
65     descriptor->prev = NULL;
66     descriptor->next = first_descriptor;
67     if(first_descriptor)
68         first_descriptor->prev = descriptor;
69     first_descriptor = descriptor;
70     
71     return descriptor;
72 }
73
74 static void iohandler_remove(struct IODescriptor *descriptor, int engine_remove) {
75     //remove IODescriptor from the list
76     if(descriptor->prev)
77         descriptor->prev->next = descriptor->next;
78     else
79         first_descriptor = descriptor->next;
80     if(descriptor->next)
81         descriptor->next->prev = descriptor->prev;
82     if(engine_remove)
83         engine->remove(descriptor);
84     if(descriptor->readbuf.buffer)
85         free(descriptor->readbuf.buffer);
86     if(descriptor->writebuf.buffer)
87         free(descriptor->writebuf.buffer);
88     free(descriptor);
89 }
90
91 static void iohandler_increase_iobuf(struct IOBuffer *iobuf, size_t required) {
92     if(iobuf->buflen >= required) return;
93     char *new_buf = realloc(iobuf->buffer, required + 2);
94     if(new_buf) {
95         iobuf->buffer = new_buf;
96         iobuf->buflen = required;
97     }
98 }
99
100 struct IODescriptor *iohandler_timer(struct timeval timeout, iohandler_callback *callback) {
101     struct IODescriptor *descriptor;
102     descriptor = iohandler_add(-1, IOTYPE_TIMER, callback);
103     if(!descriptor) return NULL;
104     descriptor->timeout = timeout;
105     engine->update(descriptor);
106     return descriptor;
107 }
108
109 struct IODescriptor *iohandler_connect(const char *hostname, unsigned int port, const char *bindhost, iohandler_callback *callback) {
110     //non-blocking connect
111     int sockfd;
112     struct addrinfo hints, *res, *freeres;
113     struct sockaddr_in *ip4 = NULL;
114     struct sockaddr_in6 *ip6 = NULL;
115     size_t dstaddrlen;
116     struct sockaddr *dstaddr = NULL;
117     struct IODescriptor *descriptor;
118     
119     memset (&hints, 0, sizeof (hints));
120     hints.ai_family = PF_UNSPEC;
121     hints.ai_socktype = SOCK_STREAM;
122     hints.ai_flags |= AI_CANONNAME;
123     if (getaddrinfo (hostname, NULL, &hints, &res)) {
124         return NULL;
125     }
126     while (res) {
127         switch (res->ai_family) {
128         case AF_INET:
129             ip4 = (struct sockaddr_in *) res->ai_addr;
130             break;
131         case AF_INET6:
132             ip6 = (struct sockaddr_in6 *) res->ai_addr;
133             break;
134         }
135         freeres = res;
136         res = res->ai_next;
137         freeaddrinfo(res);
138     }
139     
140     if(ip6) {
141         sockfd = socket(AF_INET6, SOCK_STREAM, 0);
142         if(sockfd == -1) return NULL;
143         
144         ip6->sin6_family = AF_INET6;
145         ip6->sin6_port = htons(port);
146         
147         struct sockaddr_in6 *ip6vhost = NULL;
148         if (bindhost && !getaddrinfo(bindhost, NULL, &hints, &res)) {
149             while (res) {
150                 switch (res->ai_family) {
151                 case AF_INET6:
152                     ip6vhost = (struct sockaddr_in6 *) res->ai_addr;
153                     break;
154                 }
155                 freeres = res;
156                 res = res->ai_next;
157                 freeaddrinfo(res);
158             }
159         }
160         if(ip6vhost) {
161             ip6vhost->sin6_family = AF_INET6;
162             ip6vhost->sin6_port = htons(0);
163             bind(sockfd, (struct sockaddr*)ip6vhost, sizeof(*ip6vhost));
164         }
165         dstaddr = (struct sockaddr*)ip6;
166         dstaddrlen = sizeof(*ip6);
167     } else if(ip4) {
168         sockfd = socket(AF_INET, SOCK_STREAM, 0);
169         if(sockfd == -1) return NULL;
170         
171         ip4->sin_family = AF_INET;
172         ip4->sin_port = htons(port);
173         
174         struct sockaddr_in *ip4vhost = NULL;
175         if (bindhost && !getaddrinfo(bindhost, NULL, &hints, &res)) {
176             while (res) {
177                 switch (res->ai_family) {
178                 case AF_INET:
179                     ip4vhost = (struct sockaddr_in *) res->ai_addr;
180                     break;
181                 }
182                 freeres = res;
183                 res = res->ai_next;
184                 freeaddrinfo(res);
185             }
186         }
187         if(ip4vhost) {
188             ip4vhost->sin_family = AF_INET;
189             ip4vhost->sin_port = htons(0);
190             bind(sockfd, (struct sockaddr*)ip4vhost, sizeof(*ip4vhost));
191         }
192         dstaddr = (struct sockaddr*)ip4;
193         dstaddrlen = sizeof(*ip4);
194     } else
195         return NULL;
196     //make sockfd unblocking
197 #if defined(F_GETFL)
198     flags = fcntl(sockfd, F_GETFL);
199     fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
200     flags = fcntl(sockfd, F_GETFD);
201     fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
202 #else
203     /* I hope you're using the Win32 backend or something else that
204      * automatically marks the file descriptor non-blocking...
205      */
206 #endif
207     descriptor = iohandler_add(sockfd, IOTYPE_CLIENT, callback);
208     if(!descriptor) {
209         close(sockfd);
210         return NULL;
211     }
212     connect(sockfd, dstaddr, dstaddrlen); //returns EINPROGRESS here (nonblocking)
213     descriptor->state = IO_CONNECTING;
214     descriptor->read_lines = 1;
215     engine->update(descriptor);
216     return descriptor;
217 }
218
219 struct IODescriptor *iohandler_listen(const char *hostname, unsigned int port, iohandler_callback *callback) {
220     int sockfd;
221     struct addrinfo hints, *res, *freeres;
222     struct sockaddr_in *ip4 = NULL;
223     struct sockaddr_in6 *ip6 = NULL;
224     struct IODescriptor *descriptor;
225     unsigned int opt;
226     
227     memset (&hints, 0, sizeof (hints));
228     hints.ai_family = PF_UNSPEC;
229     hints.ai_socktype = SOCK_STREAM;
230     hints.ai_flags |= AI_CANONNAME;
231     if (getaddrinfo (hostname, NULL, &hints, &res)) {
232         return NULL;
233     }
234     while (res) {
235         switch (res->ai_family) {
236         case AF_INET:
237             ip4 = (struct sockaddr_in *) res->ai_addr;
238             break;
239         case AF_INET6:
240             ip6 = (struct sockaddr_in6 *) res->ai_addr;
241             break;
242         }
243         freeres = res;
244         res = res->ai_next;
245         freeaddrinfo(res);
246     }
247     
248     if(ip6) {
249         sockfd = socket(AF_INET6, SOCK_STREAM, 0);
250         if(sockfd == -1) return NULL;
251         
252         opt = 1;
253         setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
254         
255         ip6->sin6_family = AF_INET6;
256         ip6->sin6_port = htons(port);
257         
258         bind(sockfd, (struct sockaddr*)ip6, sizeof(*ip6));
259     } else if(ip4) {
260         sockfd = socket(AF_INET, SOCK_STREAM, 0);
261         if(sockfd == -1) return NULL;
262         
263         opt = 1;
264         setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
265         
266         ip4->sin_family = AF_INET;
267         ip4->sin_port = htons(port);
268         
269         bind(sockfd, (struct sockaddr*)ip4, sizeof(*ip4));
270     } else
271         return NULL;
272     //make sockfd unblocking
273 #if defined(F_GETFL)
274     flags = fcntl(sockfd, F_GETFL);
275     fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
276     flags = fcntl(sockfd, F_GETFD);
277     fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
278 #else
279     /* I hope you're using the Win32 backend or something else that
280      * automatically marks the file descriptor non-blocking...
281      */
282 #endif
283     descriptor = iohandler_add(sockfd, IOTYPE_SERVER, callback);
284     if(!descriptor) {
285         close(sockfd);
286         return NULL;
287     }
288     listen(sockfd, 1);
289     descriptor->state = IO_LISTENING;
290     engine->update(descriptor);
291     return descriptor;
292 }
293
294 void iohandler_write(struct IODescriptor *iofd, const char *line) {
295     size_t linelen = strlen(line);
296     iohandler_send(iofd, line, linelen);
297 }
298
299 void iohandler_send(struct IODescriptor *iofd, const char *data, size_t datalen) {
300     if(iofd->type == IOTYPE_TIMER || iofd->state == IO_CLOSED) return; //can not write to timer? :D
301     if(iofd->writebuf.buflen < iofd->writebuf.bufpos + datalen) {
302         iohandler_increase_iobuf(&iofd->writebuf, iofd->writebuf.bufpos + datalen);
303         if(iofd->writebuf.buflen < iofd->writebuf.bufpos + datalen)
304             return;
305     }
306     memcpy(iofd->writebuf.buffer + iofd->writebuf.bufpos, data, datalen);
307     iofd->writebuf.bufpos += datalen;
308     engine->update(iofd);
309 }
310
311 void iohandler_printf(struct IODescriptor *iofd, const char *text, ...) {
312     va_list arg_list;
313     char sendBuf[LINELEN];
314     int pos;
315     sendBuf[0] = '\0';
316     va_start(arg_list, text);
317     pos = vsnprintf(sendBuf, LINELEN - 2, text, arg_list);
318     va_end(arg_list);
319     if (pos < 0 || pos > (LINELEN - 2)) pos = LINELEN - 2;
320     sendBuf[pos] = '\n';
321     sendBuf[pos+1] = '\0';
322     iohandler_send(iofd, sendBuf, pos+1);
323 }
324
325 void iohandler_try_write(struct IODescriptor *iofd) {
326     if(!iofd->writebuf.bufpos) return;
327     int res = send(iofd->fd, iofd->writebuf.buffer, iofd->writebuf.bufpos, 0);
328     if(res < 0) {
329         if (errno != EAGAIN) {
330             //error: could not write
331         }
332     } else {
333         iofd->writebuf.bufpos -= res;
334         engine->update(iofd);
335     }
336 }
337
338 void iohandler_close(struct IODescriptor *iofd) {
339     int engine_remove = 1;
340     if(iofd->writebuf.bufpos) {
341         //try to send everything before closing
342 #if defined(F_GETFL)
343         flags = fcntl(sockfd, F_GETFL);
344         fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK);
345         flags = fcntl(sockfd, F_GETFD);
346         fcntl(sockfd, F_SETFD, flags|FD_CLOEXEC);
347 #else
348         engine_remove = 0;
349         engine->remove(iofd);
350 #endif
351         iohandler_try_write(iofd);
352     }
353     //close IODescriptor
354     if(iofd->type == IOTYPE_SERVER || iofd->type == IOTYPE_CLIENT || iofd->type == IOTYPE_STDIN)
355         close(iofd->fd);
356     iohandler_remove(iofd, engine_remove);
357 }
358
359 void iohandler_update(struct IODescriptor *iofd) {
360     engine->update(iofd);
361 }
362
363 static void iohandler_trigger_event(struct IOEvent *event) {
364     if(!event->iofd->callback) return;
365     event->iofd->callback(event);
366 }
367
368 void iohandler_events(struct IODescriptor *iofd, int readable, int writeable) {
369     struct IOEvent callback_event;
370     callback_event.type = IOEVENT_IGNORE;
371     callback_event.iofd = iofd;
372     switch(iofd->state) {
373         case IO_CLOSED:
374             if(iofd->type == IOTYPE_TIMER)
375                 callback_event.type = IOEVENT_TIMEOUT;
376             break;
377         case IO_LISTENING:
378             if(readable) {
379                 callback_event.data.accept_fd = accept(iofd->fd, NULL, 0);
380                 if(callback_event.data.accept_fd < 0) {
381                     //error: could not accept
382                 } else
383                     callback_event.type = IOEVENT_ACCEPT;
384             }
385             break;
386         case IO_CONNECTING:
387             if(readable) { //could not connect
388                 callback_event.type = IOEVENT_NOTCONNECTED;
389                 socklen_t arglen;
390                 arglen = sizeof(callback_event.data.errid);
391                 if (getsockopt(iofd->fd, SOL_SOCKET, SO_ERROR, &callback_event.data.errid, &arglen) < 0)
392                     callback_event.data.errid = errno;
393                 iofd->state = IO_CLOSED;
394             } else if(writeable) {
395                 callback_event.type = IOEVENT_CONNECTED;
396                 iofd->state = IO_CONNECTED;
397                 engine->update(iofd);
398             }
399             break;
400         case IO_CONNECTED:
401             if(readable) {
402                 if(iofd->read_lines) {
403                     int bytes = recv(iofd->fd, iofd->readbuf.buffer + iofd->readbuf.bufpos, iofd->readbuf.buflen - iofd->readbuf.bufpos, 0);
404                     if(bytes <= 0) {
405                         if (errno != EAGAIN) {
406                             iofd->state = IO_CLOSED;
407                             callback_event.type = IOEVENT_CLOSED;
408                             callback_event.data.errid = errno;
409                         }
410                     } else {
411                         int i, used_bytes = 0;
412                         iofd->readbuf.bufpos += bytes;
413                         callback_event.type = IOEVENT_RECV;
414                         for(i = 0; i < iofd->readbuf.bufpos; i++) {
415                             if(iofd->readbuf.buffer[i] == '\r' && iofd->readbuf.buffer[i+1] == '\n')
416                                 iofd->readbuf.buffer[i] = 0;
417                             else if(iofd->readbuf.buffer[i] == '\n' || iofd->readbuf.buffer[i] == '\r') {
418                                 iofd->readbuf.buffer[i] = 0;
419                                 callback_event.data.recv_str = iofd->readbuf.buffer + used_bytes;
420                                 used_bytes = i+1;
421                                 iohandler_trigger_event(&callback_event);
422                             } else if(i + 1 - used_bytes >= LINELEN) { //512 max
423                                 iofd->readbuf.buffer[i] = 0;
424                                 callback_event.data.recv_str = iofd->readbuf.buffer + used_bytes;
425                                 for(; i < iofd->readbuf.bufpos; i++) { //skip the rest of the line
426                                     if(iofd->readbuf.buffer[i] == '\n' || (iofd->readbuf.buffer[i] == '\r' && iofd->readbuf.buffer[i+1] != '\n')) {
427                                         break;
428                                     }
429                                 }
430                                 used_bytes = i+1;
431                                 iohandler_trigger_event(&callback_event);
432                             }
433                         }
434                         if(used_bytes) {
435                             if(used_bytes == iofd->readbuf.bufpos)
436                                 iofd->readbuf.bufpos = 0;
437                             else
438                                 memmove(iofd->readbuf.buffer, iofd->readbuf.buffer + used_bytes, iofd->readbuf.bufpos - used_bytes);
439                         }
440                         callback_event.type = IOEVENT_IGNORE;
441                     }
442                 } else
443                     callback_event.type = IOEVENT_READABLE;
444             }
445             if(writeable) {
446                 iohandler_try_write(iofd);
447             }
448             break;
449     }
450     if(callback_event.type == IOEVENT_IGNORE && !readable && !writeable) 
451         callback_event.type = IOEVENT_TIMEOUT;
452     if(callback_event.type != IOEVENT_IGNORE)
453         iohandler_trigger_event(&callback_event);
454 }
455
456 void iohandler_poll() {
457     if(engine) {
458         struct timeval timeout;
459         timeout.tv_sec = IO_MAX_TIMEOUT;
460         timeout.tv_usec = 0;
461         engine->loop(&timeout);
462     }
463 }
464