1 /*****************************************************************************
2 * telnet.c: VLM interface plugin
3 *****************************************************************************
4 * Copyright (C) 2000-2006 the VideoLAN team
7 * Authors: Simon Latapie <garf@videolan.org>
8 * Laurent Aimar <fenrir@videolan.org>
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_interface.h>
36 #include <vlc_input.h>
44 #ifdef HAVE_SYS_TIME_H
45 # include <sys/time.h>
55 #include <vlc_network.h>
59 #define READ_MODE_PWD 1
60 #define READ_MODE_CMD 2
61 #define WRITE_MODE_PWD 3 // when we write the word "Password:"
62 #define WRITE_MODE_CMD 4
72 /*****************************************************************************
74 *****************************************************************************/
75 static int Open ( vlc_object_t
* );
76 static void Close( vlc_object_t
* );
78 #define TELNETHOST_TEXT N_( "Host" )
79 #define TELNETHOST_LONGTEXT N_( "This is the host on which the " \
80 "interface will listen. It defaults to all network interfaces (0.0.0.0)." \
81 " If you want this interface to be available only on the local " \
82 "machine, enter \"127.0.0.1\"." )
83 #define TELNETPORT_TEXT N_( "Port" )
84 #define TELNETPORT_LONGTEXT N_( "This is the TCP port on which this " \
85 "interface will listen. It defaults to 4212." )
86 #define TELNETPORT_DEFAULT 4212
87 #define TELNETPWD_TEXT N_( "Password" )
88 #define TELNETPWD_LONGTEXT N_( "A single administration password is used " \
89 "to protect this interface. The default value is \"admin\"." )
90 #define TELNETPWD_DEFAULT "admin"
93 set_shortname( "Telnet" )
94 set_category( CAT_INTERFACE
)
95 set_subcategory( SUBCAT_INTERFACE_CONTROL
)
96 add_string( "telnet-host", "", NULL
, TELNETHOST_TEXT
,
97 TELNETHOST_LONGTEXT
, true )
98 add_integer( "telnet-port", TELNETPORT_DEFAULT
, NULL
, TELNETPORT_TEXT
,
99 TELNETPORT_LONGTEXT
, true )
100 add_password( "telnet-password", TELNETPWD_DEFAULT
, NULL
, TELNETPWD_TEXT
,
101 TELNETPWD_LONGTEXT
, true )
102 set_description( N_("VLM remote control interface") )
103 add_category_hint( "VLM", NULL
, false )
104 set_capability( "interface", 0 )
105 set_callbacks( Open
, Close
)
108 /*****************************************************************************
110 *****************************************************************************/
111 static void Run( intf_thread_t
* );
115 int i_mode
; /* read or write */
117 char buffer_read
[1000]; // 1000 byte per command should be sufficient
120 char *p_buffer_write
; // the position in the buffer
121 int i_buffer_write
; // the number of byte we still have to send
122 int i_tel_cmd
; // for specific telnet commands
126 static char *MessageToString( vlm_message_t
*, int );
127 static void Write_message( telnet_client_t
*, vlm_message_t
*, const char *, int );
131 telnet_client_t
**clients
;
138 * getPort: Decide which port to use. There are two possibilities to
139 * specify a port: integrated in the --telnet-host option with :PORT
140 * or using the --telnet-port option. The --telnet-port option has
142 * This code relies upon the fact the url.i_port is 0 if the :PORT
143 * option is missing from --telnet-host.
145 static int getPort(intf_thread_t
*p_intf
, const vlc_url_t
*url
, int i_port
)
147 if (i_port
== TELNETPORT_DEFAULT
&& url
->i_port
!= 0)
148 i_port
= url
->i_port
;
149 if (url
->i_port
!= 0 && url
->i_port
!= i_port
)
150 // Print error if two different ports have been specified
151 msg_Warn( p_intf
, "ignoring port %d (using %d)", url
->i_port
, i_port
);
155 /*****************************************************************************
156 * Open: initialize dummy interface
157 *****************************************************************************/
158 static int Open( vlc_object_t
*p_this
)
160 intf_thread_t
*p_intf
= (intf_thread_t
*) p_this
;
166 if( !(mediatheque
= vlm_New( p_intf
)) )
168 msg_Err( p_intf
, "cannot start VLM" );
172 msg_Info( p_intf
, "using the VLM interface plugin..." );
174 i_telnetport
= config_GetInt( p_intf
, "telnet-port" );
175 psz_address
= config_GetPsz( p_intf
, "telnet-host" );
177 vlc_UrlParse(&url
, psz_address
, 0);
180 // There might be two ports given, resolve any potentially
182 url
.i_port
= getPort(p_intf
, &url
, i_telnetport
);
184 p_intf
->p_sys
= malloc( sizeof( intf_sys_t
) );
187 vlm_Delete( mediatheque
);
188 vlc_UrlClean( &url
);
191 if( ( p_intf
->p_sys
->pi_fd
= net_ListenTCP( p_intf
, url
.psz_host
, url
.i_port
) ) == NULL
)
193 msg_Err( p_intf
, "cannot listen for telnet" );
194 vlm_Delete( mediatheque
);
195 vlc_UrlClean( &url
);
196 free( p_intf
->p_sys
);
200 "telnet interface started on interface %s %d",
201 url
.psz_host
, url
.i_port
);
203 p_intf
->p_sys
->i_clients
= 0;
204 p_intf
->p_sys
->clients
= NULL
;
205 p_intf
->p_sys
->mediatheque
= mediatheque
;
206 p_intf
->pf_run
= Run
;
208 vlc_UrlClean( &url
);
212 /*****************************************************************************
214 *****************************************************************************/
215 static void Close( vlc_object_t
*p_this
)
217 intf_thread_t
*p_intf
= (intf_thread_t
*)p_this
;
218 intf_sys_t
*p_sys
= p_intf
->p_sys
;
221 for( i
= 0; i
< p_sys
->i_clients
; i
++ )
223 telnet_client_t
*cl
= p_sys
->clients
[i
];
225 free( cl
->buffer_write
);
228 free( p_sys
->clients
);
230 net_ListenClose( p_sys
->pi_fd
);
232 vlm_Delete( p_sys
->mediatheque
);
237 /*****************************************************************************
239 *****************************************************************************/
240 static void Run( intf_thread_t
*p_intf
)
242 intf_sys_t
*p_sys
= p_intf
->p_sys
;
244 unsigned nlisten
= 0;
246 for (const int *pfd
= p_sys
->pi_fd
; *pfd
!= -1; pfd
++)
247 nlisten
++; /* How many listening sockets do we have? */
249 /* FIXME: make sure config_* is cancel-safe */
250 psz_password
= config_GetPsz( p_intf
, "telnet-password" );
251 vlc_cleanup_push( free
, psz_password
);
255 unsigned ncli
= p_sys
->i_clients
;
256 struct pollfd ufd
[ncli
+ nlisten
];
258 for (unsigned i
= 0; i
< ncli
; i
++)
260 telnet_client_t
*cl
= p_sys
->clients
[i
];
263 if( (cl
->i_mode
== WRITE_MODE_PWD
) || (cl
->i_mode
== WRITE_MODE_CMD
) )
264 ufd
[i
].events
= POLLOUT
;
266 ufd
[i
].events
= POLLIN
;
270 for (unsigned i
= 0; i
< nlisten
; i
++)
272 ufd
[ncli
+ i
].fd
= p_sys
->pi_fd
[i
];
273 ufd
[ncli
+ i
].events
= POLLIN
;
274 ufd
[ncli
+ i
].revents
= 0;
277 switch (poll (ufd
, sizeof (ufd
) / sizeof (ufd
[0]), -1))
280 if (net_errno
!= EINTR
)
282 msg_Err (p_intf
, "network poll error");
284 pause (); /* We are screwed! */
286 abort (); /* We are even more screwed! (no pause() in win32) */
294 int canc
= vlc_savecancel ();
295 /* check if there is something to do with the socket */
296 for (unsigned i
= 0; i
< ncli
; i
++)
298 telnet_client_t
*cl
= p_sys
->clients
[i
];
300 if (ufd
[i
].revents
& (POLLERR
|POLLHUP
))
304 TAB_REMOVE( p_intf
->p_sys
->i_clients
,
305 p_intf
->p_sys
->clients
, cl
);
306 free( cl
->buffer_write
);
311 if (ufd
[i
].revents
& POLLOUT
&& (cl
->i_buffer_write
> 0))
315 i_len
= send( cl
->fd
, cl
->p_buffer_write
,
316 cl
->i_buffer_write
, 0 );
319 cl
->p_buffer_write
+= i_len
;
320 cl
->i_buffer_write
-= i_len
;
323 if (ufd
[i
].revents
& POLLIN
)
328 while( ((i_recv
=recv( cl
->fd
, cl
->p_buffer_read
, 1, 0 )) > 0) &&
329 ((cl
->p_buffer_read
- cl
->buffer_read
) < 999) )
331 switch( cl
->i_tel_cmd
)
334 switch( *(uint8_t *)cl
->p_buffer_read
)
339 *cl
->p_buffer_read
= '\n';
342 case TEL_IAC
: // telnet specific command
352 switch( *(uint8_t *)cl
->p_buffer_read
)
354 case TEL_WILL
: case TEL_WONT
:
355 case TEL_DO
: case TEL_DONT
:
367 cl
->p_buffer_read
-= 2;
374 if( (cl
->p_buffer_read
- cl
->buffer_read
) == 999 )
376 Write_message( cl
, NULL
, "Line too long\r\n",
381 if( i_recv
<= 0 && WSAGetLastError() == WSAEWOULDBLOCK
)
386 if( i_recv
== 0 || ( i_recv
== -1 && ( end
|| errno
!= EAGAIN
) ) )
391 /* and now we should bidouille the data we received / send */
392 for(int i
= 0 ; i
< p_sys
->i_clients
; i
++ )
394 telnet_client_t
*cl
= p_sys
->clients
[i
];
396 if( cl
->i_mode
>= WRITE_MODE_PWD
&& cl
->i_buffer_write
== 0 )
398 // we have finished to send
399 cl
->i_mode
-= 2; // corresponding READ MODE
401 else if( cl
->i_mode
== READ_MODE_PWD
&&
402 *cl
->p_buffer_read
== '\n' )
404 *cl
->p_buffer_read
= '\0';
405 if( !psz_password
|| !strcmp( psz_password
, cl
->buffer_read
) )
407 Write_message( cl
, NULL
, "\xff\xfc\x01\r\nWelcome, "
408 "Master\r\n> ", WRITE_MODE_CMD
);
413 Write_message( cl
, NULL
,
414 "\r\nWrong password.\r\nPassword: ",
418 else if( cl
->i_mode
== READ_MODE_CMD
&&
419 *cl
->p_buffer_read
== '\n' )
421 /* ok, here is a command line */
422 if( !strncmp( cl
->buffer_read
, "logout", 6 ) ||
423 !strncmp( cl
->buffer_read
, "quit", 4 ) ||
424 !strncmp( cl
->buffer_read
, "exit", 4 ) )
427 TAB_REMOVE( p_intf
->p_sys
->i_clients
,
428 p_intf
->p_sys
->clients
, cl
);
429 free( cl
->buffer_write
);
432 else if( !strncmp( cl
->buffer_read
, "shutdown", 8 ) )
434 msg_Err( p_intf
, "shutdown requested" );
435 libvlc_Quit( p_intf
->p_libvlc
);
437 else if( *cl
->buffer_read
== '@'
438 && strchr( cl
->buffer_read
, ' ' ) )
440 /* Module specific commands (use same syntax as in the
442 char *psz_name
= cl
->buffer_read
+ 1;
443 char *psz_cmd
, *psz_arg
, *psz_msg
;
446 psz_cmd
= strchr( cl
->buffer_read
, ' ' );
447 *psz_cmd
= '\0'; psz_cmd
++;
448 if( ( psz_arg
= strchr( psz_cmd
, '\n' ) ) ) *psz_arg
= '\0';
449 if( ( psz_arg
= strchr( psz_cmd
, '\r' ) ) ) *psz_arg
= '\0';
450 if( ( psz_arg
= strchr( psz_cmd
, ' ' ) )
457 i_ret
= var_Command( p_intf
, psz_name
, psz_cmd
, psz_arg
,
462 vlm_message_t
*message
;
463 message
= vlm_MessageNew( "Module command", "%s", psz_msg
);
464 Write_message( cl
, message
, NULL
, WRITE_MODE_CMD
);
465 vlm_MessageDelete( message
);
471 vlm_message_t
*message
;
473 /* create a standard string */
474 *cl
->p_buffer_read
= '\0';
476 vlm_ExecuteCommand( p_sys
->mediatheque
, cl
->buffer_read
,
478 if( !strncmp( cl
->buffer_read
, "help", 4 ) )
480 vlm_message_t
*p_my_help
=
481 vlm_MessageSimpleNew( "Telnet Specific Commands:" );
482 vlm_MessageAdd( p_my_help
,
483 vlm_MessageSimpleNew( "logout, quit, exit" ) );
484 vlm_MessageAdd( p_my_help
,
485 vlm_MessageSimpleNew( "shutdown" ) );
486 vlm_MessageAdd( p_my_help
,
487 vlm_MessageSimpleNew( "@moduleinstance command argument" ) );
488 vlm_MessageAdd( message
, p_my_help
);
490 Write_message( cl
, message
, NULL
, WRITE_MODE_CMD
);
491 vlm_MessageDelete( message
);
496 /* handle new connections */
497 for (unsigned i
= 0; i
< nlisten
; i
++)
501 if (ufd
[ncli
+ i
].revents
== 0)
504 fd
= net_AcceptSingle (VLC_OBJECT(p_intf
), ufd
[ncli
+ i
].fd
);
508 telnet_client_t
*cl
= calloc( 1, sizeof( telnet_client_t
));
517 cl
->buffer_write
= NULL
;
518 cl
->p_buffer_write
= cl
->buffer_write
;
519 Write_message( cl
, NULL
,
520 "Password: \xff\xfb\x01" , WRITE_MODE_PWD
);
521 TAB_APPEND( p_sys
->i_clients
, p_sys
->clients
, cl
);
523 vlc_restorecancel( canc
);
528 static void Write_message( telnet_client_t
*client
, vlm_message_t
*message
,
529 const char *string_message
, int i_mode
)
533 client
->p_buffer_read
= client
->buffer_read
;
534 (client
->p_buffer_read
)[0] = 0; // if (cl->p_buffer_read)[0] = '\n'
535 free( client
->buffer_write
);
537 /* generate the psz_message string */
540 /* ok, look for vlm_message_t */
541 psz_message
= MessageToString( message
, 0 );
545 /* it is a basic string_message */
546 psz_message
= strdup( string_message
);
549 client
->buffer_write
= client
->p_buffer_write
= psz_message
;
550 client
->i_buffer_write
= strlen( psz_message
);
551 client
->i_mode
= i_mode
;
554 /* We need the level of the message to put a beautiful indentation.
555 * first level is 0 */
556 static char *MessageToString( vlm_message_t
*message
, int i_level
)
558 #define STRING_CR "\r\n"
559 #define STRING_TAIL "> "
562 int i
, i_message
= sizeof( STRING_TAIL
);
564 if( !message
|| !message
->psz_name
)
566 return strdup( STRING_CR STRING_TAIL
);
568 else if( !i_level
&& !message
->i_child
&& !message
->psz_value
)
570 /* A command is successful. Don't write anything */
571 return strdup( /*STRING_CR*/ STRING_TAIL
);
574 i_message
+= strlen( message
->psz_name
) + i_level
* sizeof( " " ) + 1;
575 psz_message
= xmalloc( i_message
);
577 for( i
= 0; i
< i_level
; i
++ ) strcat( psz_message
, " " );
578 strcat( psz_message
, message
->psz_name
);
580 if( message
->psz_value
)
582 i_message
+= sizeof( " : " ) + strlen( message
->psz_value
) +
584 psz_message
= xrealloc( psz_message
, i_message
);
585 strcat( psz_message
, " : " );
586 strcat( psz_message
, message
->psz_value
);
587 strcat( psz_message
, STRING_CR
);
591 i_message
+= sizeof( STRING_CR
);
592 psz_message
= xrealloc( psz_message
, i_message
);
593 strcat( psz_message
, STRING_CR
);
596 for( i
= 0; i
< message
->i_child
; i
++ )
598 char *child_message
=
599 MessageToString( message
->child
[i
], i_level
+ 1 );
601 i_message
+= strlen( child_message
);
602 psz_message
= xrealloc( psz_message
, i_message
);
603 strcat( psz_message
, child_message
);
604 free( child_message
);
607 if( i_level
== 0 ) strcat( psz_message
, STRING_TAIL
);