teamspeak2 plugin: Renamed some types and variables.
[collectd.git] / src / teamspeak2.c
1 /**
2  * collectd - src/teamspeak2.c
3  * Copyright (C) 2008  Stefan Hacker
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Stefan Hacker <d0t at dbclan dot de>
20  **/
21
22
23 /*
24  * Defines
25  */
26  
27 /* Teamspeak query protocol defines */
28 #define TELNET_BANNER   "[TS]\r\n"
29 #define TELNET_BANNER_LENGTH 5
30 #define TELNET_ERROR   "ERROR"
31 #define TELNET_OK          "OK"
32 #define TELNET_QUIT        "quit\r\n"
33
34 /* Predefined settings */
35 #define TELNET_BUFFSIZE 512
36 #define DEFAULT_HOST    "127.0.0.1"
37 #define DEFAULT_PORT    51234
38
39 /* VServer request defines */
40 #define S_REQUEST          "si\r\n"
41 #define S_USERS_ONLINE "server_currentusers="
42 #define S_PACKETS_SEND "server_packetssend="
43 #define S_PACKETS_REC  "server_packetsreceived="
44 #define S_BYTES_SEND   "server_bytessend="
45 #define S_BYTES_REC        "server_bytesreceived="
46
47 /* Global request defines */
48 #define T_REQUEST          "gi\r\n"
49 #define T_USERS_ONLINE "total_users_online="
50 #define T_PACKETS_SEND "total_packetssend="
51 #define T_PACKETS_REC  "total_packetsreceived="
52 #define T_BYTES_SEND   "total_bytessend="
53 #define T_BYTES_REC        "total_bytesreceived="
54
55 /* Convinience defines */
56 #define INVALID_SOCKET -1
57
58
59 /*
60  * Includes
61  */
62  
63 #include "collectd.h"
64 #include "common.h"
65 #include "plugin.h"
66
67 #include <stdio.h>
68 #include <arpa/inet.h>
69 #include <netinet/in.h>
70 #include <sys/socket.h>
71 #include <sys/select.h>
72 #include <sys/types.h>
73 #include <unistd.h>
74 #include <stdlib.h>
75
76 /*
77  * Variables
78  */
79  
80 /* Server linked list structure */
81 typedef struct vserver_list_s {
82         int port;
83         struct vserver_list_s *next;
84 } vserver_list_t;
85 static vserver_list_t *server_list = NULL;
86
87
88 /* Host data */
89 static char *config_host = NULL;
90 static int   config_port = DEFAULT_PORT;
91
92 static int telnet        = INVALID_SOCKET;
93 static FILE *telnet_in   = NULL;
94
95
96 /* Config data */
97 static const char *config_keys[] =
98 {
99         "Host",
100         "Port",
101         "Server"
102 };
103 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
104
105
106 /*
107  * Functions
108  */
109 static void add_server(vserver_list_t *new_server)
110 {
111         /*
112          * Adds a new server to the linked list 
113          */
114         vserver_list_t *tmp      = NULL;
115         new_server->next = NULL;
116
117         if(server_list == NULL) {
118                 /* Add the server as the first element */
119                 server_list = new_server;
120         }
121         else {
122                 /* Add the server to the end of the list */
123                 tmp = server_list;
124                 while(tmp->next != NULL) {
125                         tmp = tmp->next;
126                 }
127                 tmp->next = new_server;
128         }
129
130         DEBUG("teamspeak2 plugin: Registered new server '%d'", new_server->port); 
131 } /* void add_server */
132
133
134 static int do_connect(void)
135 {
136         /*
137          * Tries to establish a connection to the server
138          */
139         struct sockaddr_in addr;
140         char *host;
141
142         host = (config_host != NULL) ? config_host : DEFAULT_HOST;
143         
144         /* Establish telnet connection */
145         telnet = socket(AF_INET, SOCK_STREAM, 0);
146         
147         addr.sin_family                 = AF_INET;
148         addr.sin_addr.s_addr    = inet_addr(host);
149         addr.sin_port                   = htons(config_port);
150
151         if(connect(telnet, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
152                 /* Connection failed */
153                 return -1;
154         }
155         return 0;
156 } /* int do_connect */
157
158
159 static int do_request(char *request)
160 {
161         /*
162          * Pushes a request
163          */
164         int ret = 0;
165         DEBUG("teamspeak2 plugin: Send Request: '%s'", request);
166         
167         /* Send the request */
168         if((ret = send(telnet, request, strlen(request), 0))==-1) {
169                 /* Send data failed */
170                 if (telnet!=INVALID_SOCKET) {
171                         close(telnet);
172                         telnet = INVALID_SOCKET;
173                 }
174                 char errbuf[1024];
175                 ERROR("teamspeak2 plugin: send data to host '%s' failed: %s",
176                                 config_host,
177                                 sstrerror(errno, errbuf,
178                                                   sizeof(errbuf)));
179                 return -1;
180         }
181         return ret;
182 } /* int do_request */
183
184
185 static int do_recv(char *buffer, int buffer_size, long int usecs)
186 {
187         /*
188          * Tries to receive from the connection 'timeout' seconds
189          */
190         int        ret = 0;
191         fd_set rset;
192         struct timeval timeout;
193
194         timeout.tv_sec     = 0;
195         timeout.tv_usec    = usecs;
196
197         FD_ZERO(&rset);
198         FD_SET(telnet, &rset);
199
200         if (select(FD_SETSIZE, &rset, NULL, NULL, &timeout) == -1) {
201                 /* Select failed */
202                 if (telnet!=INVALID_SOCKET) {
203                         close(telnet);
204                         telnet = INVALID_SOCKET;
205                 }
206                 
207                 char errbuf[1024];
208                 ERROR("teamspeak2 plugin: select failed: %s",
209                                 sstrerror(errno, errbuf,
210                                 sizeof(errbuf)));
211                 return -1;
212         }
213         if (!FD_ISSET(telnet, &rset)) {
214                 /* Timeout for answer reached --> disconnect */
215                 if (telnet!=INVALID_SOCKET) {
216                         close(telnet);
217                         telnet = INVALID_SOCKET;
218                 }
219                 WARNING("teamspeak2 plugin: request timed out (closed connection)");
220                 return -1;
221         }
222         if ((ret = recv(telnet, buffer, buffer_size, 0)) == -1) {
223                 /* Recv failed */
224                 if (telnet!=INVALID_SOCKET) {
225                         close(telnet);
226                         telnet = INVALID_SOCKET;
227                 }
228                 
229                 char errbuf[1024];
230                 ERROR("teamspeak2 plugin: recv failed: %s",
231                           sstrerror(errno, errbuf,
232                           sizeof(errbuf)));
233                 return -1;
234         }
235         return ret;
236 } /* int do_recv */
237
238
239 static int is_eq(char *eq, char *str) {
240         /*
241          * Checks if the given str starts with eq
242         */
243         if (strlen(eq) > strlen(str)) return -1;
244         return strncmp(eq, str, strlen(eq));
245 }
246
247
248 static long int eval_eq(char *eq, char *str) {
249         /*
250          * Returns the value written behind the eq string in str as a long int
251          */
252         return strtol((char*)&str[strlen(eq)], NULL, 10);
253 }
254
255
256 static int do_recv_line(char *buffer, int buffer_size, long int usecs)
257 {
258         /*
259          * Receives a line from the socket
260          */
261          
262         /*
263          * fgets is blocking but much easier then doing anything else
264          * TODO: Non-blocking Version would be safer
265          */
266         if ((fgets(buffer, buffer_size, telnet_in)) == NULL) {
267                 /* Receive line failed */
268                 if (telnet != INVALID_SOCKET) {
269                         close(telnet);
270                         telnet = INVALID_SOCKET;
271                 }
272                 
273                 char errbuf[1024];
274                 ERROR("teamspeak2 plugin: fgets failed: %s",
275                           sstrerror(errno, errbuf,
276                           sizeof(errbuf)));
277                 return -1;
278         }
279         DEBUG("teamspeak2 plugin: Line: %s", buffer);
280         return 0;
281 }
282
283
284 static int tss2_config(const char *key, const char *value)
285 {
286         /*
287          * Configuration interpreter function
288          */
289         char *phost = NULL;
290         
291     if (strcasecmp(key, "host") == 0) {
292         /* Host variable found*/
293                 if ((phost = strdup(value)) == NULL) {
294                         char errbuf[1024];
295                         ERROR("teamspeak2 plugin: strdup failed: %s",
296                                 sstrerror(errno, errbuf,
297                                                   sizeof(errbuf)));
298                         return 1;
299                 }
300                 sfree (config_host);
301                 config_host = phost;
302         }
303         else if (strcasecmp(key, "port") == 0) {
304                 /* Port variable found */
305                 config_port = atoi(value);
306         }
307         else if (strcasecmp(key, "server") == 0) {
308                 /* Server variable found */
309                 vserver_list_t *new_server = NULL;
310
311                 if ((new_server = (vserver_list_t *)malloc(sizeof(vserver_list_t))) == NULL) {
312                         char errbuf[1024];
313                         ERROR("teamspeak2 plugin: malloc failed: %s",
314                                   sstrerror (errno, errbuf,
315                                   sizeof (errbuf)));
316                         return 1;
317                 }
318
319                 new_server->port = atoi(value);
320                 add_server((vserver_list_t *)new_server);
321         }
322         else {
323                 /* Unknow variable found */
324                 return 1;
325         }
326
327         return 0;
328 }
329
330
331 static int tss2_init(void)
332 {
333         /*
334          * Function to initialize the plugin
335          */
336         char buff[TELNET_BANNER_LENGTH + 1]; /*Prepare banner buffer*/
337         
338         /*Connect to telnet*/
339         DEBUG("teamspeak2 plugin: Connecting to '%s:%d'", config_host, config_port);
340         if (do_connect()!=0) {
341                 /* Failed */
342                 char errbuf[1024];
343                 ERROR("teamspeak2 plugin: connect to %s:%d failed: %s",
344                         config_host,
345                         config_port,
346                         sstrerror(errno, errbuf,
347                                           sizeof(errbuf)));
348                 return 1;
349         }
350         else {
351                 DEBUG("teamspeak2 plugin: connection established!");
352         }
353         
354         /*Check if this is the real thing*/
355         if (do_recv(buff, sizeof(buff), 1) == -1) {
356                 /* Failed */
357                 return 1;
358         }
359         DEBUG("teamspeak2 plugin: received banner '%s'", buff);
360         
361         if (strcmp(buff, TELNET_BANNER)!=0) {
362                 /* Received unexpected banner string */
363                 ERROR("teamspeak2 plugin: host %s:%d is no teamspeak2 query port",
364                         config_host, config_port);
365                 return 1;
366         }
367         
368         /*Alright, we are connected now get a file descriptor*/
369         if ((telnet_in = fdopen(telnet, "r")) == NULL) {
370                 /* Failed */
371                 char errbuf[1024];
372                 ERROR("teamspeak2 plugin: fdopen failed",
373                                 sstrerror(errno, errbuf,
374                                 sizeof(errbuf)));
375                 return 1;
376         }
377         DEBUG("teamspeak2 plugin: Connection established");
378     return 0;
379 } /* int tss2_init */
380
381
382 static void tss2_submit (gauge_t users,
383                                            counter_t bytes_send, counter_t bytes_received,
384                                            counter_t packets_send, counter_t packets_received,
385                                            char *server)
386 {
387         /*
388          * Function to submit values to collectd
389          */
390         value_t v_users[1];
391         value_t v_octets[2];
392         value_t v_packets[2];
393         
394         value_list_t vl_users   = VALUE_LIST_INIT;
395         value_list_t vl_octets  = VALUE_LIST_INIT;
396         value_list_t vl_packets = VALUE_LIST_INIT;
397         
398         /* 
399          * Dispatch users gauge
400          */
401         v_users[0].gauge    = users;
402         
403         vl_users.values     = v_users;
404         vl_users.values_len = STATIC_ARRAY_SIZE (v_users);
405         vl_users.time       = time (NULL);
406
407         
408         strcpy(vl_users.host, hostname_g);
409         strcpy(vl_users.plugin, "teamspeak2");
410         
411         if (server != NULL) {
412                 /* VServer values */
413                 strcpy(vl_users.plugin_instance, "");
414                 strncpy(vl_users.type_instance, server, sizeof(vl_users.type_instance));
415         }
416         
417         plugin_dispatch_values ("users", &vl_users);
418         
419         /* 
420          * Dispatch octets counter
421          */
422         v_octets[0].counter  = bytes_send;
423         v_octets[1].counter  = bytes_received;
424         
425         vl_octets.values     = v_octets;
426         vl_octets.values_len = STATIC_ARRAY_SIZE (v_octets);
427         vl_octets.time       = time (NULL);
428
429         strcpy(vl_octets.host, hostname_g);
430         strcpy(vl_octets.plugin, "teamspeak2");
431         
432         if (server != NULL) {
433                 /* VServer values */
434                 strcpy(vl_octets.plugin_instance, "");
435                 strncpy(vl_octets.type_instance, server, sizeof(vl_octets.type_instance));
436         }
437         
438         plugin_dispatch_values ("octets", &vl_octets);
439
440         /* 
441          * Dispatch packets counter
442          */
443         v_packets[0].counter  = packets_send;
444         v_packets[1].counter  = packets_send;
445         
446         vl_packets.values     = v_packets;
447         vl_packets.values_len = STATIC_ARRAY_SIZE (v_packets);
448         vl_packets.time       = time (NULL);
449         
450         strcpy(vl_packets.host, hostname_g);
451         strcpy(vl_packets.plugin, "teamspeak2");
452         
453         if (server != NULL) {
454                 /* VServer values */
455                 strcpy(vl_packets.plugin_instance, "");
456                 strncpy(vl_packets.type_instance, server, sizeof(vl_packets.type_instance));
457         }
458         
459         plugin_dispatch_values("packets", &vl_packets);
460 } /* void tss2_submit */
461
462
463 static int tss2_read(void)
464 {
465         /*
466          * Tries to read the current values from all servers and to submit them
467          */
468         char buff[TELNET_BUFFSIZE];
469         vserver_list_t *tmp;
470     
471         /* Variables for received values */
472         int collected                       = 0;
473         int users_online                        = 0;
474         
475         long int bytes_received         = 0;
476         long int bytes_send                     = 0;
477         long int packets_received       = 0;
478         long int packets_send           = 0;
479         
480         /*Check if we are connected*/
481         if ((telnet == INVALID_SOCKET) && (do_connect() != 0)) {
482                 /* Disconnected and reconnect failed */
483                 char errbuf[1024];
484                 ERROR("teamspeak2 plugin: reconnect to %s:%d failed: %s",
485                         config_host,
486                         config_port,
487                         sstrerror(errno, errbuf,
488                                           sizeof(errbuf)));
489                 return -1;
490         }
491         
492         /* Request global server variables */
493         if (do_request(T_REQUEST) == -1) {
494                 /* Collect global info failed */
495                 ERROR("teamspeak2 plugin: Collect global information request failed");
496                 return -1;
497         }
498
499         collected = 0; /* Counts the number of variables found in the reply */
500         
501         for(;;) {
502                 /* Request a line with a timeout of 200ms */
503                 if (do_recv_line(buff, TELNET_BUFFSIZE, 200000) != 0) {
504                         /* Failed */
505                         ERROR("teamspeak2 plugin: Collect global information failed");
506                         return -1;
507                 }
508                 
509                 /*
510                  * Collect the received data
511                  */
512                 if (is_eq(T_USERS_ONLINE, buff) == 0) {
513                         /* Number of users online */
514                         users_online = (int)eval_eq(T_USERS_ONLINE, buff);
515                         DEBUG("teamspeak2 plugin: users_online: %d", users_online);
516                         collected += 1;
517                 }
518                 else if (is_eq(T_PACKETS_SEND, buff) == 0) {
519                         /* Number of packets send */
520                         packets_send = eval_eq(T_PACKETS_SEND, buff);
521                         DEBUG("teamspeak2 plugin: packets_send: %ld", packets_send);
522                         collected += 1;
523                 }
524                 else if (is_eq(T_PACKETS_REC, buff) == 0) {
525                         /* Number of packets received */
526                         packets_received = eval_eq(T_PACKETS_REC, buff);
527                         DEBUG("teamspeak2 plugin: packets_received: %ld", packets_received);
528                         collected += 1;
529                 }
530                 else if (is_eq(T_BYTES_SEND, buff) == 0) {
531                         /* Number of bytes send */
532                         bytes_send = eval_eq(T_BYTES_SEND, buff);
533                         DEBUG("teamspeak2 plugin: bytes_send: %ld", bytes_send);
534                         collected += 1;
535                 }
536                 else if (is_eq(T_BYTES_REC, buff) == 0) {
537                         /* Number of bytes received */
538                         bytes_received = eval_eq(T_BYTES_REC, buff);
539                         DEBUG("teamspeak2 plugin: byte_received: %ld", bytes_received);
540                         collected += 1;
541                 }
542                 else if (is_eq(TELNET_OK, buff) == 0) {
543                         /* Received end of transmission flag */
544                         if (collected < 5) {
545                                 /* Not all expected values were received */
546                                 ERROR("teamspeak2 plugin: Couldn't collect all values (%d)", collected);
547                                 return -1;
548                         }
549                         /*
550                          * Everything is fine, let's break out of the loop
551                          */
552                         break;
553                 }
554                 else if (is_eq(TELNET_ERROR, buff) == 0) {
555                         /* An error occured on the servers' side */
556                         ERROR("teamspeak2 plugin: host reported error '%s'", buff);
557                         return -1;
558                 }
559         }
560         
561         /* Forward values to collectd */
562         DEBUG("teamspeak2 plugin: Full global dataset received");
563         tss2_submit(users_online, bytes_send, bytes_received, packets_send, packets_received, NULL);
564
565         /* Collect values of servers */
566         tmp = server_list;
567         while(tmp != NULL) {
568                 /* Try to select server */
569                 sprintf(buff, "sel %d\r\n", tmp->port);
570                 
571                 if (do_request(buff) == -1) return -1; /* Send the request */
572                 if (do_recv_line(buff, TELNET_BUFFSIZE, 200000)!=0) return -1; /* Receive the first line */
573                 
574                 if (is_eq(buff,TELNET_ERROR) == 0) {
575                         /*Could not select server, go to the next one*/
576                         WARNING("teamspeak2 plugin: Could not select server '%d'", tmp->port);
577                         tmp = tmp->next;
578                         continue;
579                 }
580                 else if (is_eq(TELNET_OK, buff) == 0) {
581                         /*
582                          * VServer selected, now request its information
583                          */
584                         collected = 0; /* Counts the number of variables found in the reply */
585                         
586                         if (do_request(S_REQUEST) == -1) {
587                                 /* Failed */
588                                 WARNING("teamspeak2 plugin: Collect info about server '%d' failed", tmp->port);
589                                 tmp = tmp->next;
590                                 continue;
591                         }
592
593                         for(;;) {
594                                 /* Request a line with a timeout of 200ms */
595                                 if (do_recv_line(buff, TELNET_BUFFSIZE, 200000) !=0 ) {
596                                         ERROR("teamspeak2 plugin: Connection error");
597                                         return -1;
598                                 }
599                                 
600                                 /*
601                                  * Collect the received data
602                                  */
603                                 if (is_eq(S_USERS_ONLINE, buff) == 0) {
604                                         /* Number of users online */
605                                         users_online = (int)eval_eq(S_USERS_ONLINE, buff);
606                                         collected += 1;
607                                 }
608                                 else if (is_eq(S_PACKETS_SEND, buff) == 0) {
609                                         /* Number of packets send */
610                                         packets_send = eval_eq(S_PACKETS_SEND, buff);
611                                         collected += 1;
612                                 }
613                                 else if (is_eq(S_PACKETS_REC, buff) == 0) {
614                                         /* Number of packets received */
615                                         packets_received = eval_eq(S_PACKETS_REC, buff);
616                                         collected += 1;
617                                 }
618                                 else if (is_eq(S_BYTES_SEND, buff) == 0) {
619                                         /* Number of bytes send */
620                                         bytes_send = eval_eq(S_BYTES_SEND, buff);
621                                         collected += 1;
622                                 }
623                                 else if (is_eq(S_BYTES_REC, buff) == 0) {
624                                         /* Number of bytes received */
625                                         bytes_received = eval_eq(S_BYTES_REC, buff);
626                                         collected += 1;
627                                 }
628                                 else if (is_eq(TELNET_OK, buff) == 0) {
629                                         /*
630                                          * Received end of transmission flag, break the loop
631                                          */
632                                         break;
633                                 }
634                                 else if (is_eq(TELNET_ERROR, buff) == 0) {
635                                         /* Error, not good */
636                                         ERROR("teamspeak2 plugin: server '%d' reported error '%s'", tmp->port, buff);
637                                         return -1;
638                                 }
639                         }
640                         
641                         if (collected < 5) {
642                                 /* Not all expected values were received */
643                                 ERROR("teamspeak2 plugin: Couldn't collect all values of server '%d' (%d)", tmp->port, collected);
644                                 tmp = tmp->next;
645                                 continue; /* Continue with the next VServer */
646                         }
647                         
648                         /* Forward values to connectd */
649                         sprintf(buff,"%d",tmp->port);
650                         tss2_submit(users_online, bytes_send, bytes_received, packets_send, packets_received, buff);
651
652                 }
653                 else {
654                         /*The server send us garbage? wtf???*/
655                         ERROR("teamspeak2 plugin: Server send garbage");
656                         return -1;
657                 }
658                 tmp = tmp->next;
659         }
660
661     return 0;
662 } /* int tss2_read */
663
664
665 static int tss2_shutdown(void)
666 {
667         /*
668          * Shutdown handler
669          */
670         DEBUG("teamspeak2 plugin: Shutdown");
671         vserver_list_t *tmp = NULL;
672         
673         /* Close our telnet socket */
674         if (telnet != INVALID_SOCKET) {
675                 do_request(TELNET_QUIT);
676                 fclose(telnet_in);
677                 telnet_in = NULL;
678                 telnet = INVALID_SOCKET;
679         }
680         
681         /* Release all allocated memory */
682         while(server_list != NULL) {
683                 tmp     = server_list;
684                 server_list = server_list->next;
685                 free(tmp);
686         }
687         
688         /* Get rid of the rest */
689         sfree (config_host);
690         
691     return 0;
692 } /* int tss2_shutdown */
693
694
695 void module_register(void)
696 {
697         /*
698          * Module registrator
699          */
700         plugin_register_config("teamspeak2",
701                             tss2_config,
702                             config_keys,
703                             config_keys_num);
704
705         plugin_register_init("teamspeak2", tss2_init);
706         plugin_register_read("teamspeak2", tss2_read);
707         plugin_register_shutdown("teamspeak2", tss2_shutdown);
708 } /* void module_register */
709
710 /* vim: set sw=4 ts=4 : */