plugin util: add set_plugin_long function
[olsrd.git] / lib / info / olsrd_info.c
1 /*
2  * The olsr.org Optimized Link-State Routing daemon(olsrd)
3  * Copyright (c) 2015
4  *
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above copyright
14  *   notice, this list of conditions and the following disclaimer in
15  *   the documentation and/or other materials provided with the
16  *   distribution.
17  * * Neither the name of olsr.org, olsrd nor the names of its
18  *   contributors may be used to endorse or promote products derived
19  *   from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * Visit http://www.olsr.org for more information.
35  *
36  * If you find this software useful feel free to make a donation
37  * to the project. For more information see the website or contact
38  * the copyright holders.
39  *
40  */
41
42 #include <arpa/inet.h>
43 #include <unistd.h>
44 #include <assert.h>
45 #include <ctype.h>
46 #include <stdbool.h>
47
48 #include "olsrd_info.h"
49 #include "olsr.h"
50 #include "scheduler.h"
51 #include "ipcalc.h"
52 #include "http_headers.h"
53
54 #ifdef _WIN32
55 #define close(x) closesocket(x)
56 #endif /* _WIN32 */
57
58 #define MAX_CLIENTS 3
59
60 /*
61  * There is the problem that writing to a network socket can block,
62  * and the olsrd scheduler does not care about write events.
63  *
64  * There was a case that olsrd just froze for minutes when people used
65  * jsoninfo/txtinfo with large topologies over bad WiFi.
66  *
67  * This is the solution that was chosen at that time, unwilling to
68  * rewrite the whole scheduler:
69  * A timer was added and each time it expires each non-empty buffer
70  * in this structure will try to write data into a "non-blocking"
71  * socket until all data is sent, so that no blocking occurs.
72  */
73 typedef struct {
74   int socket[MAX_CLIENTS];
75   char *buffer[MAX_CLIENTS];
76   size_t size[MAX_CLIENTS];
77   size_t written[MAX_CLIENTS];
78   int count;
79 } info_plugin_outbuffer_t;
80
81 static char sink_buffer[AUTOBUFCHUNK];
82
83 static const char * name = NULL;
84
85 static info_plugin_functions_t *functions = NULL;
86
87 static info_plugin_config_t *config = NULL;
88
89 static int ipc_socket = -1;
90
91 static info_plugin_outbuffer_t outbuffer;
92
93 static struct timer_entry *writetimer_entry = NULL;
94
95 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
96
97 static char * skipMultipleSlashes(char * requ) {
98   char * r = requ;
99
100   if ((r[0] == '\0') // zero length
101       || (r[0] != '/') // does not start with a slash
102       || (r[1] != '/')) // does not have another slash
103   {
104     return r;
105   }
106
107   while (r[1] == '/') {
108     r++;
109   }
110   return r;
111 }
112
113 static unsigned long long SIW_ENTRIES_ALL[] = {
114 //
115     SIW_NEIGHBORS,//
116     SIW_LINKS, //
117     SIW_ROUTES, //
118     SIW_HNA, //
119     SIW_MID, //
120     SIW_TOPOLOGY, //
121     SIW_GATEWAYS, //
122     SIW_INTERFACES, //
123     SIW_2HOP, //
124     SIW_SGW, //
125     SIW_RUNTIME_ALL,//
126     SIW_NEIGHBORS_FREIFUNK, //
127     //
128     SIW_VERSION,//
129     SIW_CONFIG, //
130     SIW_PLUGINS, //
131     SIW_STARTUP_ALL, //
132     //
133     SIW_ALL, //
134     //
135     SIW_OLSRD_CONF //
136     };
137
138 static unsigned int determine_single_action(char *requ) {
139   unsigned int i;
140
141   if (!functions->is_command)
142     return 0;
143
144   for (i = 0; i < ARRAY_SIZE(SIW_ENTRIES_ALL); ++i) {
145     unsigned long long siw = SIW_ENTRIES_ALL[i];
146     if (functions->is_command(requ, siw))
147       return siw;
148   }
149
150   return 0;
151 }
152
153 static unsigned int determine_action(char *requ) {
154   if (!functions->is_command) {
155     return 0;
156   }
157
158   if (!requ || (requ[0] == '\0')) {
159     /* no more text */
160     return 0;
161   }
162
163   /* requ is guaranteed to be at least 1 character long */
164
165   if (!functions->supportsCompositeCommands) {
166     /* no support for composite commands */
167     return determine_single_action(requ);
168   }
169
170   /* composite commands */
171
172   {
173     unsigned int action = 0;
174
175     char * requestSegment = requ;
176     while (requestSegment) {
177       requestSegment = skipMultipleSlashes(requestSegment);
178       if (requestSegment[0] == '\0') {
179         /* there is no more text */
180         requestSegment = NULL;
181         continue;
182       }
183
184       /* there is more text */
185
186       {
187         unsigned int r = 0;
188         char * requestSegmentTail = strchr(&requestSegment[1], '/');
189
190         if (!requestSegmentTail) {
191           /* there isn't another slash, process everything that is left */
192           r = determine_single_action(requestSegment);
193         } else {
194           /* there is another slash, process everything before the slash */
195           char savedCharacter = *requestSegmentTail;
196           *requestSegmentTail = '\0';
197           r = determine_single_action(requestSegment);
198           *requestSegmentTail = savedCharacter;
199         }
200
201         if (!r) {
202           /* not found */
203           return 0;
204         }
205
206         action |= r;
207
208         /* process everything that is left in the next iteration */
209         requestSegment = requestSegmentTail;
210       }
211     }
212
213     return action;
214   }
215 }
216
217 static void write_data(void *fullyWritten) {
218   fd_set set;
219   int result, i, max;
220   struct timeval tv;
221
222   if (outbuffer.count <= 0) {
223     /* exit early if there is nothing to send */
224     return;
225   }
226
227   FD_ZERO(&set);
228   max = 0;
229   for (i = 0; i < outbuffer.count; i++) {
230     if (outbuffer.socket[i] < 0) {
231       continue;
232     }
233
234     /* And we cast here since we get a warning on Win32 */
235     FD_SET((unsigned int ) (outbuffer.socket[i]), &set);
236
237     if (outbuffer.socket[i] > max) {
238       max = outbuffer.socket[i];
239     }
240   }
241
242   tv.tv_sec = 0;
243   tv.tv_usec = 0;
244
245   result = select(max + 1, NULL, &set, NULL, &tv);
246   if (result <= 0) {
247     /* exit early if any of the sockets is not ready for writing */
248     return;
249   }
250
251   for (i = 0; i < MAX_CLIENTS; i++) {
252     if (outbuffer.socket[i] < 0) {
253       continue;
254     }
255
256     result = send(outbuffer.socket[i], outbuffer.buffer[i] + outbuffer.written[i], outbuffer.size[i] - outbuffer.written[i], 0);
257     if (result > 0) {
258       outbuffer.written[i] += result;
259     }
260
261     if ((result < 0) && ((errno == EWOULDBLOCK) || (errno == EAGAIN))) {
262       continue;
263     }
264
265     if ((result < 0) || (outbuffer.written[i] >= outbuffer.size[i])) {
266       /* close this socket and cleanup*/
267       close(outbuffer.socket[i]);
268       outbuffer.socket[i] = -1;
269       free(outbuffer.buffer[i]);
270       outbuffer.buffer[i] = NULL;
271       outbuffer.size[i] = 0;
272       outbuffer.written[i] = 0;
273
274       outbuffer.count--;
275       if (fullyWritten) {
276         bool * p = fullyWritten;
277         *p = true;
278       }
279     }
280   }
281
282   if (!outbuffer.count) {
283     olsr_stop_timer(writetimer_entry);
284   }
285 }
286
287 typedef struct {
288   unsigned long long siw;
289   printer_generic func;
290 } SiwLookupTableEntry;
291
292 static void send_info_from_table(struct autobuf *abuf, unsigned int send_what, SiwLookupTableEntry *funcs, unsigned int funcsSize, unsigned int *outputLength) {
293   unsigned int i;
294   unsigned int preLength;
295
296   if (functions->output_start) {
297     functions->output_start(abuf);
298   }
299
300   preLength = abuf->len;
301
302   for (i = 0; i < funcsSize; i++) {
303     if (send_what & funcs[i].siw) {
304       printer_generic func = funcs[i].func;
305       if (func) {
306         func(abuf);
307       }
308     }
309   }
310
311   *outputLength = abuf->len - preLength;
312
313   if (functions->output_end) {
314     functions->output_end(abuf);
315   }
316 }
317
318 static void send_info(const char * req, unsigned int send_what, int the_socket, unsigned int status) {
319   struct autobuf abuf;
320   unsigned int outputLength = 0;
321
322   const char *content_type = functions->determine_mime_type ? functions->determine_mime_type(send_what) : "text/plain; charset=utf-8";
323   int contentLengthIndex = 0;
324   int headerLength = 0;
325
326   abuf_init(&abuf, 2 * AUTOBUFCHUNK);
327
328   if (config->http_headers) {
329     http_header_build(name, status, content_type, &abuf, &contentLengthIndex);
330     headerLength = abuf.len;
331   }
332
333   if (status == INFO_HTTP_OK) {
334     /* OK */
335
336     // only add if normal format
337     if (send_what & SIW_ALL) {
338       SiwLookupTableEntry funcs[] = {
339         { SIW_NEIGHBORS , functions->neighbors  }, //
340         { SIW_LINKS     , functions->links      }, //
341         { SIW_ROUTES    , functions->routes     }, //
342         { SIW_HNA       , functions->hna        }, //
343         { SIW_MID       , functions->mid        }, //
344         { SIW_TOPOLOGY  , functions->topology   }, //
345         { SIW_GATEWAYS  , functions->gateways   }, //
346         { SIW_INTERFACES, functions->interfaces }, //
347         { SIW_2HOP      , functions->twohop     }, //
348         { SIW_SGW       , functions->sgw        }, //
349         //
350         { SIW_VERSION   , functions->version    }, //
351         { SIW_CONFIG    , functions->config     }, //
352         { SIW_PLUGINS   , functions->plugins    } //
353       };
354
355       send_info_from_table(&abuf, send_what, funcs, ARRAY_SIZE(funcs), &outputLength);
356     } else if ((send_what & SIW_OLSRD_CONF) && functions->olsrd_conf) {
357       /* this outputs the olsrd.conf text directly, not normal format */
358       unsigned int preLength = abuf.len;
359       functions->olsrd_conf(&abuf);
360       outputLength = abuf.len - preLength;
361     }
362
363     if (!abuf.len || !outputLength) {
364       status = INFO_HTTP_NOCONTENT;
365       abuf.buf[0] = '\0';
366       abuf.len = 0;
367       if (config->http_headers) {
368         http_header_build(name, status, content_type, &abuf, &contentLengthIndex);
369         headerLength = abuf.len;
370       }
371     }
372   }
373
374   if (status != INFO_HTTP_OK) {
375     if (functions->output_error) {
376       functions->output_error(&abuf, status, req, config->http_headers);
377     } else if (status == INFO_HTTP_NOCONTENT) {
378       /* wget can't handle output of zero length */
379       abuf_puts(&abuf, "\n");
380     }
381   }
382
383   if (config->http_headers) {
384     http_header_adjust_content_length(&abuf, contentLengthIndex, abuf.len - headerLength);
385   }
386
387   /* avoid a memcpy: just move the abuf.buf pointer and clear abuf */
388   outbuffer.buffer[outbuffer.count] = abuf.buf;
389   outbuffer.size[outbuffer.count] = abuf.len;
390   outbuffer.written[outbuffer.count] = 0;
391   outbuffer.socket[outbuffer.count] = the_socket;
392   abuf.buf = NULL;
393   abuf.len = 0;
394   abuf.size = 0;
395
396   outbuffer.count++;
397
398   {
399     bool fullyWritten = false;
400     write_data(&fullyWritten);
401
402     if (!fullyWritten && (outbuffer.count == 1)) {
403       writetimer_entry = olsr_start_timer(10, 0, OLSR_TIMER_PERIODIC, &write_data, NULL, 0);
404     }
405   }
406
407   abuf_free(&abuf);
408 }
409
410 static char * skipLeadingWhitespace(char * requ, size_t *len) {
411   while (isspace(*requ) && (*requ != '\0')) {
412     *len = *len - 1;
413     requ++;
414   }
415   return requ;
416 }
417
418 static char * stripEOLs(char * requ, size_t *len) {
419   while (isspace(requ[*len - 1]) && (requ[*len - 1] != '\0')) {
420     *len = *len - 1;
421     requ[*len] = '\0';
422   }
423   return requ;
424 }
425
426 static char * stripTrailingSlashes(char * requ, size_t *len) {
427   while ((requ[*len - 1] == '/') && (requ[*len - 1] != '\0')) {
428     *len = *len - 1;
429     requ[*len] = '\0';
430   }
431   return requ;
432 }
433
434 static char * cutAtFirstEOL(char * requ, size_t *len) {
435   char * s = requ;
436   size_t l = 0;
437   while (!((*s == '\n') || (*s == '\r')) && (*s != '\0')) {
438     s++;
439     l++;
440   }
441   if ((*s == '\n') || (*s == '\r')) {
442     *s = '\0';
443   }
444   *len = l;
445   return requ;
446 }
447
448 static char * parseRequest(char * requ, size_t *len) {
449   char * req = requ;
450
451   if (!req || !*len) {
452     return requ;
453   }
454
455   req = skipLeadingWhitespace(req, len);
456   req = stripEOLs(req, len);
457
458   /* HTTP request: GET whitespace URI whitespace HTTP/1.1 */
459   if (*len < (3 + 1 + 1 + 1 + 8)) {
460     return req;
461   }
462
463   if (strncasecmp(req, "GET", 3) || !isspace(req[3])) {
464     /* does not start with 'GET ' */
465     return req;
466   }
467
468   /* skip 'GET ' and further leading whitespace */
469   req = skipLeadingWhitespace(&req[4], len);
470   if (!*len) return req;
471
472   /* cut req at the first '\n' */
473   req = cutAtFirstEOL(req, len);
474   if (!*len) return req;
475
476   /* strip req of trailing EOL and whitespace */
477   req = stripEOLs(req, len);
478   if (*len < 9) return req;
479
480   if (!isspace(req[*len - 9]) //
481       || strncasecmp(&req[*len - 8], "HTTP/1.", 7) //
482       || ((req[*len - 1] != '1') && (req[*len - 1] != '0'))) {
483     return req;
484   }
485   *len = *len - 8;
486   req[*len] = '\0';
487   if (!*len) return req;
488
489   /* strip req of trailing EOL and whitespace */
490   req = stripEOLs(req, len);
491   req = stripTrailingSlashes(req, len);
492
493   return req;
494 }
495
496 static void ipc_action(int fd, void *data __attribute__ ((unused)), unsigned int flags __attribute__ ((unused))) {
497 #ifndef NODEBUG
498   char addr[INET6_ADDRSTRLEN];
499 #endif /* NODEBUG */
500
501   char * req = NULL;
502   unsigned int http_status = INFO_HTTP_OK;
503   union olsr_sockaddr sock_addr;
504   socklen_t sock_addr_len = sizeof(sock_addr);
505   fd_set rfds;
506   struct timeval tv;
507   unsigned int send_what = 0;
508   int ipc_connection = -1;
509
510   if (outbuffer.count >= MAX_CLIENTS) {
511     return;
512   }
513
514   if ((ipc_connection = accept(fd, &sock_addr.in, &sock_addr_len)) == -1) {
515 #ifndef NODEBUG
516     olsr_printf(1, "(%s) accept()=%s\n", name, strerror(errno));
517 #endif /* NODEBUG */
518     return;
519   }
520
521 #ifndef NODEBUG
522   if (!inet_ntop( //
523       olsr_cnf->ip_version, //
524       (olsr_cnf->ip_version == AF_INET) ? (void *) &sock_addr.in4.sin_addr : (void *) &sock_addr.in6.sin6_addr, //
525       addr, //
526       sizeof(addr))) {
527     addr[0] = '\0';
528   }
529 #endif /* NODEBUG */
530
531   tv.tv_sec = tv.tv_usec = 0;
532   if (olsr_cnf->ip_version == AF_INET) {
533     if (!ip4equal(&sock_addr.in4.sin_addr, &config->accept_ip.v4) && (config->accept_ip.v4.s_addr != INADDR_ANY) //
534         && (!config->allow_localhost || (ntohl(sock_addr.in4.sin_addr.s_addr) != INADDR_LOOPBACK))) {
535 #ifndef NODEBUG
536       olsr_printf(1, "(%s) From host(%s) not allowed!\n", name, addr);
537 #endif /* NODEBUG */
538       close(ipc_connection);
539       return;
540     }
541   } else {
542     /* Use in6addr_any (::) in olsr.conf to allow anybody. */
543     if (!ip6equal(&sock_addr.in6.sin6_addr, &config->accept_ip.v6) && !ip6equal(&config->accept_ip.v6, &in6addr_any)) {
544 #ifndef NODEBUG
545       olsr_printf(1, "(%s) From host(%s) not allowed!\n", name, addr);
546 #endif /* NODEBUG */
547       close(ipc_connection);
548       return;
549     }
550   }
551
552 #ifndef NODEBUG
553   olsr_printf(2, "(%s) Connect from %s\n", name, addr);
554 #endif /* NODEBUG */
555
556   /* purge read buffer to prevent blocking on linux */
557   FD_ZERO(&rfds);
558   FD_SET((unsigned int ) ipc_connection, &rfds); /* Win32 needs the cast here */
559   if (0 <= select(ipc_connection + 1, &rfds, NULL, NULL, &tv)) {
560     char requ[1024];
561     ssize_t s = recv(ipc_connection, (void *) &requ, sizeof(requ) - 1, 0); /* Win32 needs the cast here */
562
563     if (s >= (ssize_t) (sizeof(requ) - 1)) {
564       /* input was much too long, just skip the rest */
565       while (recv(ipc_connection, (void *) &sink_buffer, sizeof(sink_buffer), 0) == sizeof(sink_buffer))
566         ;
567       s = -1;
568     }
569
570     if (0 <= s) {
571       req = requ;
572       req[s] = '\0';
573       req = parseRequest(req, (size_t*)&s);
574       req = skipMultipleSlashes(req);
575       if ((req[0] == '\0') || ((req[0] == '/') && (req[1] == '\0'))) {
576         /* empty or '/' */
577         send_what = SIW_ALL;
578       } else {
579         send_what = determine_action(req);
580       }
581     }
582
583     if (!send_what) {
584       http_status = INFO_HTTP_NOTFOUND;
585     }
586   }
587
588   send_info(req ? req : "", send_what, ipc_connection, http_status);
589 }
590
591 static int plugin_ipc_init(void) {
592   union olsr_sockaddr sock_addr;
593   uint32_t yes = 1;
594   socklen_t sock_addr_len;
595
596   /* Init ipc socket */
597   if ((ipc_socket = socket(olsr_cnf->ip_version, SOCK_STREAM, 0)) == -1) {
598 #ifndef NODEBUG
599     olsr_printf(1, "(%s) socket()=%s\n", name, strerror(errno));
600 #endif /* NODEBUG */
601     goto error_out;
602   }
603
604   if (setsockopt(ipc_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof(yes)) < 0) {
605 #ifndef NODEBUG
606     olsr_printf(1, "(%s) setsockopt()=%s\n", name, strerror(errno));
607 #endif /* NODEBUG */
608     goto error_out;
609   }
610
611 #if (defined __FreeBSD__ || defined __FreeBSD_kernel__) && defined SO_NOSIGPIPE
612   if (setsockopt(ipc_socket, SOL_SOCKET, SO_NOSIGPIPE, (char *) &yes, sizeof(yes)) < 0) {
613     perror("SO_NOSIGPIPE failed");
614     goto error_out;
615   }
616 #endif /* (defined __FreeBSD__ || defined __FreeBSD_kernel__) && defined SO_NOSIGPIPE */
617
618 #if defined __linux__ && defined IPV6_V6ONLY
619   if (config->ipv6_only && (olsr_cnf->ip_version == AF_INET6) //
620       && (setsockopt(ipc_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &yes, sizeof(yes)) < 0)) {
621     perror("IPV6_V6ONLY failed");
622     goto error_out;
623   }
624 #endif /* defined __linux__ && defined IPV6_V6ONLY */
625
626   /* complete the socket structure */
627   memset(&sock_addr, 0, sizeof(sock_addr));
628   if (olsr_cnf->ip_version == AF_INET) {
629     sock_addr.in4.sin_family = AF_INET;
630     sock_addr_len = sizeof(struct sockaddr_in);
631 #ifdef SIN6_LEN
632     sock_addr.in4.sin_len = sock_addr_len;
633 #endif /* SIN6_LEN */
634     sock_addr.in4.sin_addr.s_addr = config->listen_ip.v4.s_addr;
635     sock_addr.in4.sin_port = htons(config->ipc_port);
636   } else {
637     sock_addr.in6.sin6_family = AF_INET6;
638     sock_addr_len = sizeof(struct sockaddr_in6);
639 #ifdef SIN6_LEN
640     sock_addr.in6.sin6_len = sock_addr_len;
641 #endif /* SIN6_LEN */
642     sock_addr.in6.sin6_addr = config->listen_ip.v6;
643     sock_addr.in6.sin6_port = htons(config->ipc_port);
644   }
645
646   /* bind the socket to the port number */
647   if (bind(ipc_socket, &sock_addr.in, sock_addr_len) == -1) {
648 #ifndef NODEBUG
649     olsr_printf(1, "(%s) bind()=%s\n", name, strerror(errno));
650 #endif /* NODEBUG */
651     goto error_out;
652   }
653
654   /* show that we are willing to listen */
655   if (listen(ipc_socket, 1) == -1) {
656 #ifndef NODEBUG
657     olsr_printf(1, "(%s) listen()=%s\n", name, strerror(errno));
658 #endif /* NODEBUG */
659     goto error_out;
660   }
661
662   /* Register with olsrd */
663   add_olsr_socket(ipc_socket, &ipc_action, NULL, NULL, SP_PR_READ);
664
665 #ifndef NODEBUG
666   olsr_printf(2, "(%s) listening on port %d\n", name, config->ipc_port);
667 #endif /* NODEBUG */
668
669   return 1;
670
671   error_out: //
672   if (ipc_socket >= 0) {
673     close(ipc_socket);
674     ipc_socket = -1;
675   }
676   return 0;
677 }
678
679 int info_plugin_init(const char * plugin_name, info_plugin_functions_t *plugin_functions, info_plugin_config_t *plugin_config) {
680   int i;
681
682   assert(plugin_name);
683   assert(plugin_functions);
684   assert(plugin_config);
685
686   name = plugin_name;
687   functions = plugin_functions;
688   config = plugin_config;
689
690   memset(&outbuffer, 0, sizeof(outbuffer));
691   for (i = 0; i < MAX_CLIENTS; ++i) {
692     outbuffer.socket[i] = -1;
693   }
694
695   ipc_socket = -1;
696
697   if (functions->init) {
698     functions->init(name);
699   }
700
701   plugin_ipc_init();
702   return 1;
703 }
704
705 void info_plugin_exit(void) {
706   int i;
707
708   if (ipc_socket != -1) {
709     close(ipc_socket);
710     ipc_socket = -1;
711   }
712   for (i = 0; i < MAX_CLIENTS; ++i) {
713     if (outbuffer.buffer[i]) {
714       free(outbuffer.buffer[i]);
715       outbuffer.buffer[i] = NULL;
716     }
717     outbuffer.size[i] = 0;
718     outbuffer.written[i] = 0;
719     if (outbuffer.socket[i]) {
720       close(outbuffer.socket[i]);
721       outbuffer.socket[i] = -1;
722     }
723   }
724   outbuffer.count = 0;
725 }