telnet.c 19.9 KB
Newer Older
1 2 3
/*****************************************************************************
 * telnet.c: VLM interface plugin
 *****************************************************************************
4
 * Copyright (C) 2000-2006 the VideoLAN team
5
 * $Id$
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * Authors: Simon Latapie <garf@videolan.org>
 *          Laurent Aimar <fenrir@videolan.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33
#include <vlc/vlc.h>
Clément Stenac's avatar
Clément Stenac committed
34 35
#include <vlc_interface.h>
#include <vlc_input.h>
36

37
#include <stdbool.h>
38 39 40 41 42 43 44 45 46 47 48 49
#include <sys/stat.h>

#include <errno.h>
#include <fcntl.h>

#ifdef HAVE_SYS_TIME_H
#    include <sys/time.h>
#endif

#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#endif
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
50
#ifdef HAVE_POLL
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
51 52
#   include <poll.h>
#endif
53

Clément Stenac's avatar
Clément Stenac committed
54 55 56
#include <vlc_network.h>
#include <vlc_url.h>
#include <vlc_vlm.h>
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

#define READ_MODE_PWD 1
#define READ_MODE_CMD 2
#define WRITE_MODE_PWD 3 // when we write the word "Password:"
#define WRITE_MODE_CMD 4

/* telnet commands */
#define TEL_WILL    251
#define TEL_WONT    252
#define TEL_DO      253
#define TEL_DONT    254
#define TEL_IAC     255
#define TEL_ECHO    1

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );

77 78
#define TELNETHOST_TEXT N_( "Host" )
#define TELNETHOST_LONGTEXT N_( "This is the host on which the " \
79
    "interface will listen. It defaults to all network interfaces (0.0.0.0)." \
80 81 82 83 84
    " If you want this interface to be available only on the local " \
    "machine, enter \"127.0.0.1\"." )
#define TELNETPORT_TEXT N_( "Port" )
#define TELNETPORT_LONGTEXT N_( "This is the TCP port on which this " \
    "interface will listen. It defaults to 4212." )
85
#define TELNETPORT_DEFAULT 4212
86 87 88
#define TELNETPWD_TEXT N_( "Password" )
#define TELNETPWD_LONGTEXT N_( "A single administration password is used " \
    "to protect this interface. The default value is \"admin\"." )
89
#define TELNETPWD_DEFAULT "admin"
90 91

vlc_module_begin();
92
    set_shortname( "Telnet" );
93
    set_category( CAT_INTERFACE );
Clément Stenac's avatar
Clément Stenac committed
94
    set_subcategory( SUBCAT_INTERFACE_CONTROL );
95
    add_string( "telnet-host", "", NULL, TELNETHOST_TEXT,
96 97
                 TELNETHOST_LONGTEXT, VLC_TRUE );
    add_integer( "telnet-port", TELNETPORT_DEFAULT, NULL, TELNETPORT_TEXT,
98
                 TELNETPORT_LONGTEXT, VLC_TRUE );
99
    add_password( "telnet-password", TELNETPWD_DEFAULT, NULL, TELNETPWD_TEXT,
100
                TELNETPWD_LONGTEXT, VLC_TRUE );
101
    set_description( _("VLM remote control interface") );
102
    add_category_hint( "VLM", NULL, VLC_FALSE );
103 104 105 106 107 108 109 110 111 112 113 114 115
    set_capability( "interface", 0 );
    set_callbacks( Open , Close );
vlc_module_end();

/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
static void Run( intf_thread_t * );

typedef struct
{
    int        i_mode; /* read or write */
    int        fd;
116
    char       buffer_read[1000]; // 1000 byte per command should be sufficient
117
    char      *buffer_write;
118 119
    char      *p_buffer_read;
    char      *p_buffer_write; // the position in the buffer
120 121
    int        i_buffer_write; // the number of byte we still have to send
    int        i_tel_cmd; // for specific telnet commands
122

123 124
} telnet_client_t;

125
static char *MessageToString( vlm_message_t *, int );
Laurent Aimar's avatar
Laurent Aimar committed
126
static void Write_message( telnet_client_t *, vlm_message_t *, const char *, int );
127 128 129 130 131

struct intf_sys_t
{
   telnet_client_t **clients;
   int             i_clients;
132
   int             *pi_fd;
133
   vlm_t           *mediatheque;
134 135
};

136
/*
137 138 139
 * getPort: Decide which port to use. There are two possibilities to
 * specify a port: integrated in the --telnet-host option with :PORT
 * or using the --telnet-port option. The --telnet-port option has
140 141
 * precedence.
 * This code relies upon the fact the url.i_port is 0 if the :PORT
142 143 144
 * option is missing from --telnet-host.
 */
static int getPort(intf_thread_t *p_intf, vlc_url_t url, int i_port)
Jean-Paul Saman's avatar
Jean-Paul Saman committed
145
{
146 147
    // Print error if two different ports have been specified
    if (url.i_port != 0  &&
148
        i_port != TELNETPORT_DEFAULT &&
149 150
        url.i_port != i_port )
    {
151
        msg_Err( p_intf, "ignoring port %d and using %d", url.i_port,
152 153 154 155
                 i_port);
    }
    if (i_port != TELNETPORT_DEFAULT)
    {
156
        return i_port;
157 158 159
    }
    if (url.i_port != 0)
    {
160
         return url.i_port;
161 162 163 164
    }
    return i_port;
}

165 166 167 168 169 170
/*****************************************************************************
 * Open: initialize dummy interface
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t*) p_this;
171
    vlm_t *mediatheque;
172 173
    char *psz_address;
    vlc_url_t url;
174 175
    int i_telnetport;

176 177 178 179 180
    if( !(mediatheque = vlm_New( p_intf )) )
    {
        msg_Err( p_intf, "cannot start VLM" );
        return VLC_EGENERIC;
    }
181

182
    msg_Info( p_intf, "using the VLM interface plugin..." );
183

184
    i_telnetport = config_GetInt( p_intf, "telnet-port" );
185 186 187 188 189 190 191
    psz_address  = config_GetPsz( p_intf, "telnet-host" );

    vlc_UrlParse(&url, psz_address, 0);

    // There might be two ports given, resolve any potentially
    // conflict
    url.i_port = getPort(p_intf, url, i_telnetport);
192 193

    p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
194
    if( ( p_intf->p_sys->pi_fd = net_ListenTCP( p_intf, url.psz_host, url.i_port ) )
195
                == NULL )
196 197
    {
        msg_Err( p_intf, "cannot listen for telnet" );
198 199
        vlc_UrlClean(&url);
        free( psz_address );
200 201 202
        free( p_intf->p_sys );
        return VLC_EGENERIC;
    }
203
    msg_Info( p_intf,
204
              "telnet interface started on interface %s %d",
205
              url.psz_host, url.i_port );
206

207 208
    p_intf->p_sys->i_clients   = 0;
    p_intf->p_sys->clients     = NULL;
209 210
    p_intf->p_sys->mediatheque = mediatheque;
    p_intf->pf_run = Run;
211

212
    vlc_UrlClean(&url);
213
    free( psz_address );
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close:
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_this;
    intf_sys_t    *p_sys  = p_intf->p_sys;
    int i;

    for( i = 0; i < p_sys->i_clients; i++ )
    {
        telnet_client_t *cl = p_sys->clients[i];
        net_Close( cl->fd );
        free( cl );
231
        p_sys->clients[i] = NULL;
232 233 234
    }
    if( p_sys->clients != NULL ) free( p_sys->clients );

235
    net_ListenClose( p_sys->pi_fd );
236 237 238 239 240 241 242 243 244 245 246 247

    vlm_Delete( p_sys->mediatheque );

    free( p_sys );
}

/*****************************************************************************
 * Run: main loop
 *****************************************************************************/
static void Run( intf_thread_t *p_intf )
{
    intf_sys_t     *p_sys = p_intf->p_sys;
248
    char           *psz_password;
249 250 251 252
    unsigned        nlisten = 0;

    for (const int *pfd = p_sys->pi_fd; *pfd != -1; pfd++)
        nlisten++; /* How many listening sockets do we have? */
253

254
    psz_password = config_GetPsz( p_intf, "telnet-password" );
255

256
    while( !intf_ShouldDie( p_intf ) )
257
    {
258 259
        unsigned ncli = p_sys->i_clients;
        struct pollfd ufd[ncli + nlisten];
260

261
        for (unsigned i = 0; i < ncli; i++)
262 263 264
        {
            telnet_client_t *cl = p_sys->clients[i];

265
            ufd[i].fd = cl->fd;
266
            if( (cl->i_mode == WRITE_MODE_PWD) || (cl->i_mode == WRITE_MODE_CMD) )
267
                ufd[i].events = POLLOUT;
268
            else
269 270
                ufd[i].events = POLLIN;
            ufd[i].revents = 0;
271 272
        }

273
        for (unsigned i = 0; i < nlisten; i++)
274
        {
275 276 277
            ufd[ncli + i].fd = p_sys->pi_fd[i];
            ufd[ncli + i].events = POLLIN;
            ufd[ncli + i].revents = 0;
278
        }
279 280 281

        /* FIXME: arbitrary tick */
        switch (poll (ufd, sizeof (ufd) / sizeof (ufd[0]), 500))
282
        {
283 284 285 286 287 288 289 290 291
            case -1:
                if (net_errno != EINTR)
                {
                    msg_Err (p_intf, "network poll error");
                    msleep (1000);
                    continue;
                }
            case 0:
                continue;
292 293 294
        }

        /* check if there is something to do with the socket */
295
        for (unsigned i = 0; i < ncli; i++)
296 297 298
        {
            telnet_client_t *cl = p_sys->clients[i];

299
            if (ufd[i].revents & (POLLERR|POLLHUP))
300
            {
301 302 303 304 305 306 307 308 309 310 311 312
            drop:
                net_Close( cl->fd );
                TAB_REMOVE( p_intf->p_sys->i_clients ,
                            p_intf->p_sys->clients , cl );
                free( cl );
                continue;
            }

            if (ufd[i].revents & POLLOUT && (cl->i_buffer_write > 0))
            {
                ssize_t i_len;

313 314
                i_len = send( cl->fd, cl->p_buffer_write ,
                              cl->i_buffer_write, 0 );
315 316 317 318 319 320
                if( i_len > 0 )
                {
                    cl->p_buffer_write += i_len;
                    cl->i_buffer_write -= i_len;
                }
            }
321
            if (ufd[i].revents & POLLIN)
322
            {
323 324
                bool end = false;
                ssize_t i_recv;
325

326 327
                while( ((i_recv=recv( cl->fd, cl->p_buffer_read, 1, 0 )) > 0) &&
                       ((cl->p_buffer_read - cl->buffer_read) < 999) )
328 329 330
                {
                    switch( cl->i_tel_cmd )
                    {
331
                    case 0:
332
                        switch( *(uint8_t *)cl->p_buffer_read )
333 334 335 336 337
                        {
                        case '\r':
                            break;
                        case '\n':
                            *cl->p_buffer_read = '\n';
338
                            end = true;
339 340 341 342
                            break;
                        case TEL_IAC: // telnet specific command
                            cl->i_tel_cmd = 1;
                            cl->p_buffer_read++;
343
                            break;
344 345
                        default:
                            cl->p_buffer_read++;
346
                            break;
347 348 349
                        }
                        break;
                    case 1:
350
                        switch( *(uint8_t *)cl->p_buffer_read )
351 352 353 354 355 356 357
                        {
                        case TEL_WILL: case TEL_WONT:
                        case TEL_DO: case TEL_DONT:
                            cl->i_tel_cmd++;
                            cl->p_buffer_read++;
                            break;
                        default:
358
                            cl->i_tel_cmd = 0;
359
                            cl->p_buffer_read--;
360
                            break;
361 362 363 364 365
                        }
                        break;
                    case 2:
                        cl->i_tel_cmd = 0;
                        cl->p_buffer_read -= 2;
366 367
                        break;
                    }
368

369
                    if( end ) break;
370 371
                }

372
                if( (cl->p_buffer_read - cl->buffer_read) == 999 )
373
                {
374
                    Write_message( cl, NULL, "Line too long\r\n",
375
                                   cl->i_mode + 2 );
376
                }
377

378 379
                if (i_recv <= 0)
                    goto drop;
380 381 382 383
            }
        }

        /* and now we should bidouille the data we received / send */
384
        for(int i = 0 ; i < p_sys->i_clients ; i++ )
385 386 387
        {
            telnet_client_t *cl = p_sys->clients[i];

388
            if( cl->i_mode >= WRITE_MODE_PWD && cl->i_buffer_write == 0 )
389
            {
390
               // we have finished to send
391 392
               cl->i_mode -= 2; // corresponding READ MODE
            }
393 394
            else if( cl->i_mode == READ_MODE_PWD &&
                     *cl->p_buffer_read == '\n' )
395 396
            {
                *cl->p_buffer_read = '\0';
397
                if( !psz_password || !strcmp( psz_password, cl->buffer_read ) )
398
                {
399 400
                    Write_message( cl, NULL, "\xff\xfc\x01\r\nWelcome, "
                                   "Master\r\n> ", WRITE_MODE_CMD );
401 402 403 404
                }
                else
                {
                    /* wrong password */
405
                    Write_message( cl, NULL,
406
                                   "\r\nWrong password.\r\nPassword: ",
407
                                   WRITE_MODE_PWD );
408 409
                }
            }
410 411
            else if( cl->i_mode == READ_MODE_CMD &&
                     *cl->p_buffer_read == '\n' )
412 413 414 415 416 417
            {
                /* ok, here is a command line */
                if( !strncmp( cl->buffer_read, "logout", 6 ) ||
                    !strncmp( cl->buffer_read, "quit", 4 )  ||
                    !strncmp( cl->buffer_read, "exit", 4 ) )
                {
418
                    net_Close( cl->fd );
419 420
                    TAB_REMOVE( p_intf->p_sys->i_clients ,
                                p_intf->p_sys->clients , cl );
421 422 423 424 425
                    free( cl );
                }
                else if( !strncmp( cl->buffer_read, "shutdown", 8 ) )
                {
                    msg_Err( p_intf, "shutdown requested" );
426
                    vlc_object_kill( p_intf->p_libvlc );
427
                }
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
                else if( *cl->buffer_read == '@'
                          && strchr( cl->buffer_read, ' ' ) )
                {
                    /* Module specific commands (use same syntax as in the
                     * rc interface) */
                    char *psz_name = cl->buffer_read + 1;
                    char *psz_cmd, *psz_arg, *psz_msg;
                    int i_ret;

                    psz_cmd = strchr( cl->buffer_read, ' ' );
                    *psz_cmd = '\0';  psz_cmd++;
                    if( ( psz_arg = strchr( psz_cmd, '\n' ) ) ) *psz_arg = '\0';
                    if( ( psz_arg = strchr( psz_cmd, '\r' ) ) ) *psz_arg = '\0';
                    if( ( psz_arg = strchr( psz_cmd, ' ' ) )
                        && *psz_arg )
                    {
                        *psz_arg = '\0';
                        psz_arg++;
                    }

                    i_ret = var_Command( p_intf, psz_name, psz_cmd, psz_arg,
                                         &psz_msg );

                    if( psz_msg )
                    {
                        vlm_message_t *message;
                        message = vlm_MessageNew( "Module command", psz_msg );
                        Write_message( cl, message, NULL, WRITE_MODE_CMD );
                        vlm_MessageDelete( message );
                        free( psz_msg );
                    }
                }
460 461
                else
                {
462
                    vlm_message_t *message;
463 464 465 466

                    /* create a standard string */
                    *cl->p_buffer_read = '\0';

467 468
                    vlm_ExecuteCommand( p_sys->mediatheque, cl->buffer_read,
                                        &message );
469 470 471 472 473 474 475 476
                    if( !strncmp( cl->buffer_read, "help", 4 ) )
                    {
                        vlm_message_t *p_my_help =
                            vlm_MessageNew( "Telnet Specific Commands:", NULL );
                        vlm_MessageAdd( p_my_help,
                            vlm_MessageNew( "logout, quit, exit" , NULL ) );
                        vlm_MessageAdd( p_my_help,
                            vlm_MessageNew( "shutdown" , NULL ) );
477 478 479
                        vlm_MessageAdd( p_my_help,
                            vlm_MessageNew( "@moduleinstance command argument",
                                             NULL) );
480 481
                        vlm_MessageAdd( message, p_my_help );
                    }
482
                    Write_message( cl, message, NULL, WRITE_MODE_CMD );
483
                    vlm_MessageDelete( message );
484

485 486 487
                }
            }
        }
488 489 490 491 492 493 494 495 496

        /* handle new connections */
        for (unsigned i = 0; i < nlisten; i++)
        {
            int fd;

            if (ufd[ncli + i].revents == 0)
                continue;

497
            fd = net_AcceptSingle (VLC_OBJECT(p_intf), ufd[ncli + i].fd);
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
            if (fd == -1)
                continue;

            telnet_client_t *cl = malloc( sizeof( telnet_client_t ));
            if (cl == NULL)
            {
                net_Close (fd);
                continue;
            }

            memset( cl, 0, sizeof(telnet_client_t) );
            cl->i_tel_cmd = 0;
            cl->fd = fd;
            cl->buffer_write = NULL;
            cl->p_buffer_write = cl->buffer_write;
            Write_message( cl, NULL,
                           "Password: \xff\xfb\x01" , WRITE_MODE_PWD );
            TAB_APPEND( p_sys->i_clients, p_sys->clients, cl );
        }
517
    }
518 519
    if( psz_password )
        free( psz_password );
520 521
}

522
static void Write_message( telnet_client_t *client, vlm_message_t *message,
Laurent Aimar's avatar
Laurent Aimar committed
523
                           const char *string_message, int i_mode )
524
{
525 526
    char *psz_message;

527 528 529
    client->p_buffer_read = client->buffer_read;
    (client->p_buffer_read)[0] = 0; // if (cl->p_buffer_read)[0] = '\n'
    if( client->buffer_write ) free( client->buffer_write );
530 531

    /* generate the psz_message string */
532
    if( message )
533
    {
534 535
        /* ok, look for vlm_message_t */
        psz_message = MessageToString( message, 0 );
536
    }
537
    else
538
    {
539
        /* it is a basic string_message */
540 541 542
        psz_message = strdup( string_message );
    }

543
    client->buffer_write = client->p_buffer_write = psz_message;
544
    client->i_buffer_write = strlen( psz_message );
545 546 547
    client->i_mode = i_mode;
}

548 549 550
/* We need the level of the message to put a beautiful indentation.
 * first level is 0 */
static char *MessageToString( vlm_message_t *message, int i_level )
551
{
552 553 554
#define STRING_CR "\r\n"
#define STRING_TAIL "> "

555
    char *psz_message;
556
    int i, i_message = sizeof( STRING_TAIL );
557

558
    if( !message || !message->psz_name )
559
    {
560
        return strdup( STRING_CR STRING_TAIL );
561
    }
562
    else if( !i_level && !message->i_child && !message->psz_value  )
563
    {
564
        /* A command is successful. Don't write anything */
565
        return strdup( /*STRING_CR*/ STRING_TAIL );
566 567 568
    }

    i_message += strlen( message->psz_name ) + i_level * sizeof( "    " ) + 1;
569 570
    psz_message = malloc( i_message );
    *psz_message = 0;
571 572 573 574 575 576 577 578 579 580 581
    for( i = 0; i < i_level; i++ ) strcat( psz_message, "    " );
    strcat( psz_message, message->psz_name );

    if( message->psz_value )
    {
        i_message += sizeof( " : " ) + strlen( message->psz_value ) +
            sizeof( STRING_CR );
        psz_message = realloc( psz_message, i_message );
        strcat( psz_message, " : " );
        strcat( psz_message, message->psz_value );
        strcat( psz_message, STRING_CR );
582 583 584
    }
    else
    {
585 586 587 588
        i_message += sizeof( STRING_CR );
        psz_message = realloc( psz_message, i_message );
        strcat( psz_message, STRING_CR );
    }
589

590 591 592 593
    for( i = 0; i < message->i_child; i++ )
    {
        char *child_message =
            MessageToString( message->child[i], i_level + 1 );
594

595 596 597 598
        i_message += strlen( child_message );
        psz_message = realloc( psz_message, i_message );
        strcat( psz_message, child_message );
        free( child_message );
599
    }
600 601 602 603

    if( i_level == 0 ) strcat( psz_message, STRING_TAIL );

    return psz_message;
604
}