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>
49 #include <vlc_network.h>
53 #define READ_MODE_PWD 1
54 #define READ_MODE_CMD 2
55 #define WRITE_MODE_PWD 3 // when we write the word "Password:"
56 #define WRITE_MODE_CMD 4
66 /*****************************************************************************
68 *****************************************************************************/
69 static int Open ( vlc_object_t
* );
70 static void Close( vlc_object_t
* );
72 #define TELNETHOST_TEXT N_( "Host" )
73 #define TELNETHOST_LONGTEXT N_( "This is the host on which the " \
74 "interface will listen. It defaults to all network interfaces (0.0.0.0)." \
75 " If you want this interface to be available only on the local " \
76 "machine, enter \"127.0.0.1\"." )
77 #define TELNETPORT_TEXT N_( "Port" )
78 #define TELNETPORT_LONGTEXT N_( "This is the TCP port on which this " \
79 "interface will listen. It defaults to 4212." )
80 #define TELNETPORT_DEFAULT 4212
81 #define TELNETPWD_TEXT N_( "Password" )
82 #define TELNETPWD_LONGTEXT N_( "A single administration password is used " \
83 "to protect this interface. The default value is \"admin\"." )
84 #define TELNETPWD_DEFAULT "admin"
87 set_shortname( "Telnet" )
88 set_category( CAT_INTERFACE
)
89 set_subcategory( SUBCAT_INTERFACE_CONTROL
)
90 add_string( "telnet-host", "localhost", NULL
, TELNETHOST_TEXT
,
91 TELNETHOST_LONGTEXT
, true )
92 add_integer( "telnet-port", TELNETPORT_DEFAULT
, NULL
, TELNETPORT_TEXT
,
93 TELNETPORT_LONGTEXT
, true )
94 add_password( "telnet-password", TELNETPWD_DEFAULT
, NULL
, TELNETPWD_TEXT
,
95 TELNETPWD_LONGTEXT
, true )
96 set_description( N_("VLM remote control interface") )
97 add_category_hint( "VLM", NULL
, false )
98 set_capability( "interface", 0 )
99 set_callbacks( Open
, Close
)
102 /*****************************************************************************
104 *****************************************************************************/
105 static void Run( intf_thread_t
* );
109 int i_mode
; /* read or write */
111 char buffer_read
[1000]; // 1000 byte per command should be sufficient
114 char *p_buffer_write
; // the position in the buffer
115 int i_buffer_write
; // the number of byte we still have to send
116 int i_tel_cmd
; // for specific telnet commands
120 static char *MessageToString( vlm_message_t
*, int );
121 static void Write_message( telnet_client_t
*, vlm_message_t
*, const char *, int );
125 telnet_client_t
**clients
;
132 * getPort: Decide which port to use. There are two possibilities to
133 * specify a port: integrated in the --telnet-host option with :PORT
134 * or using the --telnet-port option. The --telnet-port option has
136 * This code relies upon the fact the url.i_port is 0 if the :PORT
137 * option is missing from --telnet-host.
139 static int getPort(intf_thread_t
*p_intf
, const vlc_url_t
*url
, int i_port
)
141 if (i_port
== TELNETPORT_DEFAULT
&& url
->i_port
!= 0)
142 i_port
= url
->i_port
;
143 if (url
->i_port
!= 0 && url
->i_port
!= i_port
)
144 // Print error if two different ports have been specified
145 msg_Warn( p_intf
, "ignoring port %d (using %d)", url
->i_port
, i_port
);
149 /*****************************************************************************
150 * Open: initialize dummy interface
151 *****************************************************************************/
152 static int Open( vlc_object_t
*p_this
)
154 intf_thread_t
*p_intf
= (intf_thread_t
*) p_this
;
160 if( !(mediatheque
= vlm_New( p_intf
)) )
162 msg_Err( p_intf
, "cannot start VLM" );
166 msg_Info( p_intf
, "using the VLM interface plugin..." );
168 i_telnetport
= var_InheritInteger( p_intf
, "telnet-port" );
169 psz_address
= var_InheritString( p_intf
, "telnet-host" );
170 vlc_UrlParse(&url
, psz_address
, 0);
173 // There might be two ports given, resolve any potentially
175 url
.i_port
= getPort(p_intf
, &url
, i_telnetport
);
177 p_intf
->p_sys
= malloc( sizeof( intf_sys_t
) );
180 vlm_Delete( mediatheque
);
181 vlc_UrlClean( &url
);
184 if( ( p_intf
->p_sys
->pi_fd
= net_ListenTCP( p_intf
, url
.psz_host
, url
.i_port
) ) == NULL
)
186 msg_Err( p_intf
, "cannot listen for telnet" );
187 vlm_Delete( mediatheque
);
188 vlc_UrlClean( &url
);
189 free( p_intf
->p_sys
);
193 "telnet interface started on interface %s %d",
194 url
.psz_host
, url
.i_port
);
196 p_intf
->p_sys
->i_clients
= 0;
197 p_intf
->p_sys
->clients
= NULL
;
198 p_intf
->p_sys
->mediatheque
= mediatheque
;
199 p_intf
->pf_run
= Run
;
201 vlc_UrlClean( &url
);
205 /*****************************************************************************
207 *****************************************************************************/
208 static void Close( vlc_object_t
*p_this
)
210 intf_thread_t
*p_intf
= (intf_thread_t
*)p_this
;
211 intf_sys_t
*p_sys
= p_intf
->p_sys
;
214 for( i
= 0; i
< p_sys
->i_clients
; i
++ )
216 telnet_client_t
*cl
= p_sys
->clients
[i
];
218 free( cl
->buffer_write
);
221 free( p_sys
->clients
);
223 net_ListenClose( p_sys
->pi_fd
);
225 vlm_Delete( p_sys
->mediatheque
);
230 /*****************************************************************************
232 *****************************************************************************/
233 static void Run( intf_thread_t
*p_intf
)
235 intf_sys_t
*p_sys
= p_intf
->p_sys
;
237 unsigned nlisten
= 0;
239 for (const int *pfd
= p_sys
->pi_fd
; *pfd
!= -1; pfd
++)
240 nlisten
++; /* How many listening sockets do we have? */
242 /* FIXME: make sure config_* is cancel-safe */
243 psz_password
= var_InheritString( p_intf
, "telnet-password" );
244 vlc_cleanup_push( free
, psz_password
);
248 unsigned ncli
= p_sys
->i_clients
;
249 struct pollfd ufd
[ncli
+ nlisten
];
251 for (unsigned i
= 0; i
< ncli
; i
++)
253 telnet_client_t
*cl
= p_sys
->clients
[i
];
256 if( (cl
->i_mode
== WRITE_MODE_PWD
) || (cl
->i_mode
== WRITE_MODE_CMD
) )
257 ufd
[i
].events
= POLLOUT
;
259 ufd
[i
].events
= POLLIN
;
263 for (unsigned i
= 0; i
< nlisten
; i
++)
265 ufd
[ncli
+ i
].fd
= p_sys
->pi_fd
[i
];
266 ufd
[ncli
+ i
].events
= POLLIN
;
267 ufd
[ncli
+ i
].revents
= 0;
270 switch (poll (ufd
, sizeof (ufd
) / sizeof (ufd
[0]), -1))
273 if (net_errno
!= EINTR
)
275 msg_Err (p_intf
, "network poll error");
277 pause (); /* We are screwed! */
279 abort (); /* We are even more screwed! (no pause() in win32) */
287 int canc
= vlc_savecancel ();
288 /* check if there is something to do with the socket */
289 for (unsigned i
= 0; i
< ncli
; i
++)
291 telnet_client_t
*cl
= p_sys
->clients
[i
];
293 if (ufd
[i
].revents
& (POLLERR
|POLLHUP
))
297 TAB_REMOVE( p_intf
->p_sys
->i_clients
,
298 p_intf
->p_sys
->clients
, cl
);
299 free( cl
->buffer_write
);
304 if (ufd
[i
].revents
& POLLOUT
&& (cl
->i_buffer_write
> 0))
308 i_len
= send( cl
->fd
, cl
->p_buffer_write
,
309 cl
->i_buffer_write
, 0 );
312 cl
->p_buffer_write
+= i_len
;
313 cl
->i_buffer_write
-= i_len
;
316 if (ufd
[i
].revents
& POLLIN
)
321 while( ((i_recv
=recv( cl
->fd
, cl
->p_buffer_read
, 1, 0 )) > 0) &&
322 ((cl
->p_buffer_read
- cl
->buffer_read
) < 999) )
324 switch( cl
->i_tel_cmd
)
327 switch( *(uint8_t *)cl
->p_buffer_read
)
332 *cl
->p_buffer_read
= '\n';
335 case TEL_IAC
: // telnet specific command
345 switch( *(uint8_t *)cl
->p_buffer_read
)
347 case TEL_WILL
: case TEL_WONT
:
348 case TEL_DO
: case TEL_DONT
:
360 cl
->p_buffer_read
-= 2;
367 if( (cl
->p_buffer_read
- cl
->buffer_read
) == 999 )
369 Write_message( cl
, NULL
, "Line too long\r\n",
374 if( i_recv
<= 0 && WSAGetLastError() == WSAEWOULDBLOCK
)
379 if( i_recv
== 0 || ( i_recv
== -1 && ( end
|| errno
!= EAGAIN
) ) )
384 /* and now we should bidouille the data we received / send */
385 for(int i
= 0 ; i
< p_sys
->i_clients
; i
++ )
387 telnet_client_t
*cl
= p_sys
->clients
[i
];
389 if( cl
->i_mode
>= WRITE_MODE_PWD
&& cl
->i_buffer_write
== 0 )
391 // we have finished to send
392 cl
->i_mode
-= 2; // corresponding READ MODE
394 else if( cl
->i_mode
== READ_MODE_PWD
&&
395 *cl
->p_buffer_read
== '\n' )
397 *cl
->p_buffer_read
= '\0';
398 if( !psz_password
|| !strcmp( psz_password
, cl
->buffer_read
) )
400 Write_message( cl
, NULL
, "\xff\xfc\x01\r\nWelcome, "
401 "Master\r\n> ", WRITE_MODE_CMD
);
406 Write_message( cl
, NULL
,
407 "\r\nWrong password.\r\nPassword: ",
411 else if( cl
->i_mode
== READ_MODE_CMD
&&
412 *cl
->p_buffer_read
== '\n' )
414 /* ok, here is a command line */
415 if( !strncmp( cl
->buffer_read
, "logout", 6 ) ||
416 !strncmp( cl
->buffer_read
, "quit", 4 ) ||
417 !strncmp( cl
->buffer_read
, "exit", 4 ) )
420 TAB_REMOVE( p_intf
->p_sys
->i_clients
,
421 p_intf
->p_sys
->clients
, cl
);
422 free( cl
->buffer_write
);
425 else if( !strncmp( cl
->buffer_read
, "shutdown", 8 ) )
427 msg_Err( p_intf
, "shutdown requested" );
428 libvlc_Quit( p_intf
->p_libvlc
);
430 else if( *cl
->buffer_read
== '@'
431 && strchr( cl
->buffer_read
, ' ' ) )
433 /* Module specific commands (use same syntax as in the
435 char *psz_name
= cl
->buffer_read
+ 1;
436 char *psz_cmd
, *psz_arg
, *psz_msg
;
439 psz_cmd
= strchr( cl
->buffer_read
, ' ' );
440 *psz_cmd
= '\0'; psz_cmd
++;
441 if( ( psz_arg
= strchr( psz_cmd
, '\n' ) ) ) *psz_arg
= '\0';
442 if( ( psz_arg
= strchr( psz_cmd
, '\r' ) ) ) *psz_arg
= '\0';
443 if( ( psz_arg
= strchr( psz_cmd
, ' ' ) )
450 i_ret
= var_Command( p_intf
, psz_name
, psz_cmd
, psz_arg
,
455 vlm_message_t
*message
;
456 message
= vlm_MessageNew( "Module command", "%s", psz_msg
);
457 Write_message( cl
, message
, NULL
, WRITE_MODE_CMD
);
458 vlm_MessageDelete( message
);
464 vlm_message_t
*message
;
466 /* create a standard string */
467 *cl
->p_buffer_read
= '\0';
469 vlm_ExecuteCommand( p_sys
->mediatheque
, cl
->buffer_read
,
471 if( !strncmp( cl
->buffer_read
, "help", 4 ) )
473 vlm_message_t
*p_my_help
=
474 vlm_MessageSimpleNew( "Telnet Specific Commands:" );
475 vlm_MessageAdd( p_my_help
,
476 vlm_MessageSimpleNew( "logout, quit, exit" ) );
477 vlm_MessageAdd( p_my_help
,
478 vlm_MessageSimpleNew( "shutdown" ) );
479 vlm_MessageAdd( p_my_help
,
480 vlm_MessageSimpleNew( "@moduleinstance command argument" ) );
481 vlm_MessageAdd( message
, p_my_help
);
483 Write_message( cl
, message
, NULL
, WRITE_MODE_CMD
);
484 vlm_MessageDelete( message
);
489 /* handle new connections */
490 for (unsigned i
= 0; i
< nlisten
; i
++)
494 if (ufd
[ncli
+ i
].revents
== 0)
497 fd
= net_AcceptSingle (VLC_OBJECT(p_intf
), ufd
[ncli
+ i
].fd
);
501 telnet_client_t
*cl
= calloc( 1, sizeof( telnet_client_t
));
510 cl
->buffer_write
= NULL
;
511 cl
->p_buffer_write
= cl
->buffer_write
;
512 Write_message( cl
, NULL
,
513 "Password: \xff\xfb\x01" , WRITE_MODE_PWD
);
514 TAB_APPEND( p_sys
->i_clients
, p_sys
->clients
, cl
);
516 vlc_restorecancel( canc
);
521 static void Write_message( telnet_client_t
*client
, vlm_message_t
*message
,
522 const char *string_message
, int i_mode
)
526 client
->p_buffer_read
= client
->buffer_read
;
527 (client
->p_buffer_read
)[0] = 0; // if (cl->p_buffer_read)[0] = '\n'
528 free( client
->buffer_write
);
530 /* generate the psz_message string */
533 /* ok, look for vlm_message_t */
534 psz_message
= MessageToString( message
, 0 );
538 /* it is a basic string_message */
539 psz_message
= strdup( string_message
);
542 client
->buffer_write
= client
->p_buffer_write
= psz_message
;
543 client
->i_buffer_write
= strlen( psz_message
);
544 client
->i_mode
= i_mode
;
547 /* We need the level of the message to put a beautiful indentation.
548 * first level is 0 */
549 static char *MessageToString( vlm_message_t
*message
, int i_level
)
551 #define STRING_CR "\r\n"
552 #define STRING_TAIL "> "
555 int i
, i_message
= sizeof( STRING_TAIL
);
557 if( !message
|| !message
->psz_name
)
559 return strdup( STRING_CR STRING_TAIL
);
561 else if( !i_level
&& !message
->i_child
&& !message
->psz_value
)
563 /* A command is successful. Don't write anything */
564 return strdup( /*STRING_CR*/ STRING_TAIL
);
567 i_message
+= strlen( message
->psz_name
) + i_level
* sizeof( " " ) + 1;
568 psz_message
= xmalloc( i_message
);
570 for( i
= 0; i
< i_level
; i
++ ) strcat( psz_message
, " " );
571 strcat( psz_message
, message
->psz_name
);
573 if( message
->psz_value
)
575 i_message
+= sizeof( " : " ) + strlen( message
->psz_value
) +
577 psz_message
= xrealloc( psz_message
, i_message
);
578 strcat( psz_message
, " : " );
579 strcat( psz_message
, message
->psz_value
);
580 strcat( psz_message
, STRING_CR
);
584 i_message
+= sizeof( STRING_CR
);
585 psz_message
= xrealloc( psz_message
, i_message
);
586 strcat( psz_message
, STRING_CR
);
589 for( i
= 0; i
< message
->i_child
; i
++ )
591 char *child_message
=
592 MessageToString( message
->child
[i
], i_level
+ 1 );
594 i_message
+= strlen( child_message
);
595 psz_message
= xrealloc( psz_message
, i_message
);
596 strcat( psz_message
, child_message
);
597 free( child_message
);
600 if( i_level
== 0 ) strcat( psz_message
, STRING_TAIL
);