equalizer.c: compile fix for old compilers (thx sam)
[vlc.git] / modules / control / telnet.c
1 /*****************************************************************************
2  * telnet.c: VLM interface plugin
3  *****************************************************************************
4  * Copyright (C) 2000, 2001 VideoLAN
5  * $Id$
6  *
7  * Authors: Simon Latapie <garf@videolan.org>
8  *          Laurent Aimar <fenrir@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>                                      /* malloc(), free() */
29
30 #include <vlc/vlc.h>
31 #include <vlc/intf.h>
32
33 #include <vlc/input.h>
34
35 #include <sys/stat.h>
36
37 #include <errno.h>
38 #include <fcntl.h>
39
40 #ifdef HAVE_SYS_TIME_H
41 #    include <sys/time.h>
42 #endif
43
44 #ifdef HAVE_UNISTD_H
45 #   include <unistd.h>
46 #endif
47
48 #if defined( UNDER_CE )
49 #   include <winsock.h>
50 #elif defined( WIN32 )
51 #   include <winsock2.h>
52 #else
53 #   include <sys/socket.h>
54 #endif
55
56 #include "network.h"
57
58 #include "vlc_vlm.h"
59
60 #if defined( WIN32 ) || defined( UNDER_CE )
61 #define SOCKET_CLOSE(a)    closesocket(a)
62 #else
63 #define SOCKET_CLOSE(a)    close(a)
64 #endif
65
66 #define READ_MODE_PWD 1
67 #define READ_MODE_CMD 2
68 #define WRITE_MODE_PWD 3 // when we write the word "Password:"
69 #define WRITE_MODE_CMD 4
70
71 /* telnet commands */
72 #define TEL_WILL    251
73 #define TEL_WONT    252
74 #define TEL_DO      253
75 #define TEL_DONT    254
76 #define TEL_IAC     255
77 #define TEL_ECHO    1
78
79 /*****************************************************************************
80  * Module descriptor
81  *****************************************************************************/
82 static int  Open ( vlc_object_t * );
83 static void Close( vlc_object_t * );
84
85 #define TELNETPORT_TEXT N_( "Telnet Interface port" )
86 #define TELNETPORT_LONGTEXT N_( "Default to 4212" )
87 #define TELNETPWD_TEXT N_( "Telnet Interface password" )
88 #define TELNETPWD_LONGTEXT N_( "Default to admin" )
89
90 vlc_module_begin();
91     add_integer( "telnet-port", 4212, NULL, TELNETPORT_TEXT, TELNETPORT_LONGTEXT, VLC_TRUE );
92     add_string( "telnet-password", "admin", NULL,
93                     TELNETPWD_TEXT, TELNETPWD_LONGTEXT, VLC_TRUE );
94     set_description( _("Telnet remote control interface") );
95     add_category_hint( "VLM", NULL, VLC_FALSE );
96     set_capability( "interface", 0 );
97     set_callbacks( Open , Close );
98 vlc_module_end();
99
100 /*****************************************************************************
101  * Local prototypes.
102  *****************************************************************************/
103 static void Run( intf_thread_t * );
104
105 typedef struct
106 {
107     int        i_mode; /* read or write */
108     int        fd;
109     uint8_t    buffer_read[1000]; // 1000 byte per command should be sufficient
110     char      *buffer_write;
111     uint8_t   *p_buffer_read;
112     uint8_t   *p_buffer_write; // the position in the buffer
113     int        i_buffer_write; // the number of byte we still have to send
114     int        i_tel_cmd; // for specific telnet commands
115 } telnet_client_t;
116
117 static char* MessageToString( vlm_message_t* , int );
118 static void Write_message( telnet_client_t * , vlm_message_t* , char * , int );
119
120 struct intf_sys_t
121 {
122    telnet_client_t **clients;
123    int             i_clients;
124    int             fd;
125    vlm_t          *mediatheque;
126 };
127
128 /*****************************************************************************
129  * Open: initialize dummy interface
130  *****************************************************************************/
131 static int Open( vlc_object_t *p_this )
132 {
133     intf_thread_t *p_intf = (intf_thread_t*) p_this;
134     int i_telnetport;
135
136     i_telnetport = config_GetInt( p_intf, "telnet-port" );
137
138 #ifdef WIN32
139     vlc_bool_t b_quiet;
140     b_quiet = config_GetInt( p_intf, "dummy-quiet" );
141     if( !b_quiet )
142         CONSOLE_INTRO_MSG;
143 #endif
144
145     msg_Info( p_intf, _("Using the VLM interface plugin...") );
146
147     p_intf->pf_run = Run;
148
149     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
150     if( ( p_intf->p_sys->fd = net_ListenTCP( p_intf , "", i_telnetport ) ) < 0 )
151     {
152         msg_Err( p_intf, "cannot listen for telnet" );
153         free( p_intf->p_sys );
154         return VLC_EGENERIC;
155     }
156     msg_Info( p_intf, _("Telnet interface started on port: %d"), i_telnetport );
157
158     p_intf->p_sys->i_clients   = 0;
159     p_intf->p_sys->clients     = NULL;
160     p_intf->p_sys->mediatheque = vlm_New( p_intf );
161
162     return VLC_SUCCESS;
163 }
164
165 /*****************************************************************************
166  * Close:
167  *****************************************************************************/
168 static void Close( vlc_object_t *p_this )
169 {
170     intf_thread_t *p_intf = (intf_thread_t*)p_this;
171     intf_sys_t    *p_sys  = p_intf->p_sys;
172     int i;
173
174     for( i = 0; i < p_sys->i_clients; i++ )
175     {
176         telnet_client_t *cl = p_sys->clients[i];
177
178         net_Close( cl->fd );
179         free( cl );
180     }
181     if( p_sys->clients != NULL ) free( p_sys->clients );
182
183     net_Close( p_sys->fd );
184
185     vlm_Delete( p_sys->mediatheque );
186
187     free( p_sys );
188 }
189
190
191 /*****************************************************************************
192  * Run: main loop
193  *****************************************************************************/
194 static void Run( intf_thread_t *p_intf )
195 {
196     intf_sys_t     *p_sys = p_intf->p_sys;
197     struct timeval  timeout;
198     char           *s_password;
199
200     s_password = config_GetPsz( p_intf, "telnet-password" );
201
202     while( !p_intf->b_die )
203     {
204         fd_set          fds_read;
205         fd_set          fds_write;
206         int             i_handle_max = 0;
207         int             i_ret;
208         int             i_len;
209         int             fd;
210         int             i;
211
212         /* if a new client wants to communicate */
213         fd = accept( p_sys->fd, NULL, NULL );
214         if( fd > 0 )
215         {
216             telnet_client_t *cl;
217
218             /* to be non blockant */
219 #if defined( WIN32 ) || defined( UNDER_CE )
220             {
221                 unsigned long i_dummy = 1;
222                 ioctlsocket( fd, FIONBIO, &i_dummy );
223             }
224 #else
225             fcntl( fd, F_SETFL, O_NONBLOCK );
226 #endif
227             cl = malloc( sizeof( telnet_client_t ));
228             cl->i_tel_cmd = 0;
229             cl->fd = fd;
230             cl->buffer_write = NULL;
231             cl->p_buffer_write = cl->buffer_write;
232             Write_message( cl, NULL, "Password:\xff\xfb\x01", WRITE_MODE_PWD );
233
234             TAB_APPEND( p_sys->i_clients, p_sys->clients, cl );
235         }
236
237         /* to do a proper select */
238         FD_ZERO( &fds_read );
239         FD_ZERO( &fds_write );
240
241         for( i = 0 ; i < p_sys->i_clients ; i++ )
242         {
243             telnet_client_t *cl = p_sys->clients[i];
244
245             if( cl->i_mode == WRITE_MODE_PWD || cl->i_mode == WRITE_MODE_CMD )
246             {
247                 FD_SET( cl->fd , &fds_write );
248             }
249             else
250             {
251                 FD_SET( cl->fd , &fds_read );
252             }
253             i_handle_max = __MAX( i_handle_max, cl->fd );
254         }
255
256         timeout.tv_sec = 0;
257         timeout.tv_usec = 500*1000;
258
259         i_ret = select( i_handle_max + 1, &fds_read, &fds_write, NULL, &timeout );
260         if( i_ret == -1 && errno != EINTR )
261         {
262             msg_Warn( p_intf, "cannot select sockets" );
263             msleep( 1000 );
264             continue;
265         }
266         else if( i_ret <= 0 )
267         {
268             continue;
269         }
270
271         /* check if there is something to do with the socket */
272         for( i = 0 ; i < p_sys->i_clients ; i++ )
273         {
274             telnet_client_t *cl = p_sys->clients[i];
275
276             if( FD_ISSET(cl->fd , &fds_write) && cl->i_buffer_write > 0 )
277             {
278                 i_len = send( cl->fd , cl->p_buffer_write ,
279                               cl->i_buffer_write , 0 );
280                 if( i_len > 0 )
281                 {
282                     cl->p_buffer_write += i_len;
283                     cl->i_buffer_write -= i_len;
284                 }
285             }
286             else if( FD_ISSET( cl->fd, &fds_read) )
287             {
288                 int i_end = 0;
289
290                 while( recv( cl->fd, cl->p_buffer_read, 1, 0 ) > 0 &&
291                        cl->p_buffer_read - cl->buffer_read < 999 )
292                 {
293                     switch( cl->i_tel_cmd )
294                     {
295                         case 0:
296                             switch( *cl->p_buffer_read )
297                             {
298                                 case '\r':
299                                     break;
300                                 case '\n':
301                                     *cl->p_buffer_read = '\n';
302                                     i_end = 1;
303                                     break;
304                                 case TEL_IAC: // telnet specific command
305                                     cl->i_tel_cmd = 1;
306                                     cl->p_buffer_read++;
307                                     break;
308                                 default:
309                                     cl->p_buffer_read++;
310                                     break;
311                             }
312                             break;
313                         case 1:
314                             switch( *cl->p_buffer_read )
315                             {
316                                 case TEL_WILL: case TEL_WONT: case TEL_DO: case TEL_DONT:
317                                     cl->i_tel_cmd++;
318                                     cl->p_buffer_read++;
319                                     break;
320                                 default:
321                                     cl->i_tel_cmd = 0;
322                                     cl->p_buffer_read--;
323                                     break;
324                             }
325                             break;
326                         case 2:
327                             cl->i_tel_cmd = 0;
328                             cl->p_buffer_read -= 2;
329                             break;
330                     }
331
332                     if( i_end != 0 )
333                     {
334                         break;
335                     }
336                 }
337
338                 if( cl->p_buffer_read - cl->buffer_read == 999 ) // too long !
339                 {
340                     Write_message( cl , NULL, "Line too long\n" , cl->i_mode + 2 );
341                 }
342             }
343         }
344
345         /* and now we should bidouille the data we received / send */
346         for( i = 0 ; i < p_sys->i_clients ; i++ )
347         {
348             telnet_client_t *cl = p_sys->clients[i];
349
350             if( cl->i_mode >= WRITE_MODE_PWD && cl->i_buffer_write == 0 ) // we have finished to send
351             {
352                cl->i_mode -= 2; // corresponding READ MODE
353             }
354             else if( cl->i_mode == READ_MODE_PWD && *cl->p_buffer_read == '\n' )
355             {
356                 *cl->p_buffer_read = '\0';
357                 if( strcmp( s_password, cl->buffer_read ) == 0 )
358                 {
359                     Write_message( cl , NULL, "\xff\xfc\x01\nWelcome, Master\n> ", WRITE_MODE_CMD );
360                 }
361                 else
362                 {
363                     /* wrong password */
364                     Write_message( cl , NULL, "\n\rTry again, you polio:\n" , WRITE_MODE_PWD );
365                 }
366             }
367             else if( cl->i_mode == READ_MODE_CMD && *cl->p_buffer_read == '\n' )
368             {
369                 /* ok, here is a command line */
370                 if( !strncmp( cl->buffer_read, "logout", 6 ) ||
371                     !strncmp( cl->buffer_read, "quit", 4 )  ||
372                     !strncmp( cl->buffer_read, "exit", 4 ) )
373                 {
374                     close( cl->fd );
375                     TAB_REMOVE( p_intf->p_sys->i_clients , p_intf->p_sys->clients , cl );
376                     free( cl );
377                 }
378                 else if( !strncmp( cl->buffer_read, "shutdown", 8 ) )
379                 {
380                     msg_Err( p_intf, "shutdown requested" );
381                     p_intf->p_vlc->b_die = VLC_TRUE;
382                 }
383                 else
384                 {
385                     vlm_message_t *message;
386
387                     /* create a standard string */
388                     *cl->p_buffer_read = '\0';
389
390                     vlm_ExecuteCommand( p_sys->mediatheque, cl->buffer_read , &message);
391
392                     Write_message( cl , message, NULL , WRITE_MODE_CMD );
393
394                     vlm_MessageDelete( message );
395                 }
396             }
397         }
398     }
399 }
400
401 static void Write_message( telnet_client_t * client, vlm_message_t * message, char * string_message, int i_mode )
402 {
403     char *psz_message;
404
405     client->p_buffer_read = client->buffer_read;
406     (client->p_buffer_read)[0] = 0; // if (cl->p_buffer_read)[0] = '\n'
407     if( client->buffer_write ) free( client->buffer_write );
408
409     /* generate the psz_message string */
410     if( message != NULL ) /* ok, look for vlm_message_t */
411     {
412         psz_message = MessageToString( message , 0 );
413         psz_message = realloc( psz_message , strlen( psz_message ) + strlen( "\n> " ) + 1 );
414         strcat( psz_message , "\n> " );
415     }
416     else /* it is a basic string_message */
417     {
418         psz_message = strdup( string_message );
419     }
420
421     client->buffer_write = malloc( strlen( psz_message ) + 1 );
422     strcpy( client->buffer_write , psz_message );
423     client->p_buffer_write = client->buffer_write;
424     client->i_buffer_write = strlen( psz_message );
425     client->i_mode = i_mode;
426     free( psz_message );
427 }
428
429 /* we need the level of the message to put a beautiful indentation.
430    first level is 0 */
431 static char* MessageToString( vlm_message_t* message , int i_level )
432 {
433     int i;
434     char *psz_message;
435
436     if( message == NULL )
437     {
438         return strdup( "" );
439     }
440     else if( i_level == 0 && message->i_child == 0 && message->psz_value == NULL  ) /* a command is successful */
441     {
442         /* don't write anything */
443         return strdup( "" );
444     }
445     else
446     {
447         psz_message = strdup( "" );
448         psz_message = realloc( psz_message , strlen( psz_message ) + strlen( message->psz_name ) + i_level * 4 + 1 );
449         for( i = 0 ; i < i_level ; i++ )
450         {
451             strcat( psz_message , "    " );
452         }
453         strcat( psz_message , message->psz_name );
454         if( message->psz_value )
455         {
456             psz_message = realloc( psz_message , strlen( psz_message ) + strlen( message->psz_value ) + 3 + 1 );
457             strcat( psz_message , " : " );
458             strcat( psz_message , message->psz_value );
459         }
460
461         for( i = 0 ; i < message->i_child ; i++ )
462         {
463             char *child_message = MessageToString( message->child[i] , i_level + 1 );
464
465             psz_message = realloc( psz_message , strlen( psz_message ) +  strlen( child_message ) + 1 + 1 );
466             strcat( psz_message, "\n" );
467             strcat( psz_message, child_message );
468             free( child_message );
469         }
470
471         return psz_message;
472     }
473 }