telnet.c 15.4 KB
Newer Older
1
2
3
4
/*****************************************************************************
 * telnet.c: VLM interface plugin
 *****************************************************************************
 * Copyright (C) 2000, 2001 VideoLAN
hartman's avatar
hartman committed
5
 * $Id$
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
 *
 * 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
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>                                      /* malloc(), free() */

#include <vlc/vlc.h>
#include <vlc/intf.h>

#include <vlc/input.h>

#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

#if defined( UNDER_CE )
#   include <winsock.h>
#elif defined( WIN32 )
#   include <winsock2.h>
#else
#   include <sys/socket.h>
#endif

#include "network.h"

58
#include "vlc_vlm.h"
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

#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 * );

#define TELNETPORT_TEXT N_( "Telnet Interface port" )
#define TELNETPORT_LONGTEXT N_( "Default to 4212" )
#define TELNETPWD_TEXT N_( "Telnet Interface password" )
#define TELNETPWD_LONGTEXT N_( "Default to admin" )

vlc_module_begin();
85
    set_shortname( "Telnet" );
86
87
    set_category( CAT_INTERFACE );
    set_subcategory( SUBCAT_INTERFACE_GENERAL );
88
89
90
91
    add_integer( "telnet-port", 4212, NULL, TELNETPORT_TEXT,
                 TELNETPORT_LONGTEXT, VLC_TRUE );
    add_string( "telnet-password", "admin", NULL, TELNETPWD_TEXT,
                TELNETPWD_LONGTEXT, VLC_TRUE );
92
    set_description( _("VLM remote control interface") );
93
    add_category_hint( "VLM", NULL, VLC_FALSE );
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
    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;
    uint8_t    buffer_read[1000]; // 1000 byte per command should be sufficient
    char      *buffer_write;
    uint8_t   *p_buffer_read;
    uint8_t   *p_buffer_write; // the position in the buffer
    int        i_buffer_write; // the number of byte we still have to send
    int        i_tel_cmd; // for specific telnet commands
113

114
115
} telnet_client_t;

116
117
static char *MessageToString( vlm_message_t *, int );
static void Write_message( telnet_client_t *, vlm_message_t *, char *, int );
118
119
120
121
122
123

struct intf_sys_t
{
   telnet_client_t **clients;
   int             i_clients;
   int             fd;
124
   vlm_t           *mediatheque;
125
126
127
128
129
130
131
132
};

/*****************************************************************************
 * Open: initialize dummy interface
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t*) p_this;
133
    vlm_t *mediatheque;
134
135
    int i_telnetport;

136
137
138
139
140
    if( !(mediatheque = vlm_New( p_intf )) )
    {
        msg_Err( p_intf, "cannot start VLM" );
        return VLC_EGENERIC;
    }
141

142
    msg_Info( p_intf, "Using the VLM interface plugin..." );
143

144
    i_telnetport = config_GetInt( p_intf, "telnet-port" );
145
146

    p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
147
    if( ( p_intf->p_sys->fd = net_ListenTCP( p_intf , "", i_telnetport ) ) < 0 )
148
149
150
151
152
    {
        msg_Err( p_intf, "cannot listen for telnet" );
        free( p_intf->p_sys );
        return VLC_EGENERIC;
    }
153
    msg_Info( p_intf, "Telnet interface started on port: %d", i_telnetport );
hartman's avatar
hartman committed
154

155
156
    p_intf->p_sys->i_clients   = 0;
    p_intf->p_sys->clients     = NULL;
157
158
    p_intf->p_sys->mediatheque = mediatheque;
    p_intf->pf_run = Run;
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194

    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 );
    }
    if( p_sys->clients != NULL ) free( p_sys->clients );

    net_Close( p_sys->fd );

    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;
    struct timeval  timeout;
195
    char           *psz_password;
196

197
    psz_password = config_GetPsz( p_intf, "telnet-password" );
198
199
200

    while( !p_intf->b_die )
    {
201
202
203
        fd_set fds_read, fds_write;
        int    i_handle_max = 0;
        int    i_ret, i_len, fd, i;
204
205

        /* if a new client wants to communicate */
206
        fd = net_Accept( p_intf, p_sys->fd, p_sys->i_clients > 0 ? 0 : -1 );
207
208
209
210
        if( fd > 0 )
        {
            telnet_client_t *cl;

211
            /* to be non blocking */
212
213
214
215
216
217
218
219
220
221
222
223
224
#if defined( WIN32 ) || defined( UNDER_CE )
            {
                unsigned long i_dummy = 1;
                ioctlsocket( fd, FIONBIO, &i_dummy );
            }
#else
            fcntl( fd, F_SETFL, O_NONBLOCK );
#endif
            cl = malloc( sizeof( telnet_client_t ));
            cl->i_tel_cmd = 0;
            cl->fd = fd;
            cl->buffer_write = NULL;
            cl->p_buffer_write = cl->buffer_write;
225
            Write_message( cl, NULL, "Password:\xff\xfb\x01", WRITE_MODE_PWD );
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251

            TAB_APPEND( p_sys->i_clients, p_sys->clients, cl );
        }

        /* to do a proper select */
        FD_ZERO( &fds_read );
        FD_ZERO( &fds_write );

        for( i = 0 ; i < p_sys->i_clients ; i++ )
        {
            telnet_client_t *cl = p_sys->clients[i];

            if( cl->i_mode == WRITE_MODE_PWD || cl->i_mode == WRITE_MODE_CMD )
            {
                FD_SET( cl->fd , &fds_write );
            }
            else
            {
                FD_SET( cl->fd , &fds_read );
            }
            i_handle_max = __MAX( i_handle_max, cl->fd );
        }

        timeout.tv_sec = 0;
        timeout.tv_usec = 500*1000;

252
        i_ret = select( i_handle_max + 1, &fds_read, &fds_write, 0, &timeout );
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
        if( i_ret == -1 && errno != EINTR )
        {
            msg_Warn( p_intf, "cannot select sockets" );
            msleep( 1000 );
            continue;
        }
        else if( i_ret <= 0 )
        {
            continue;
        }

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

            if( FD_ISSET(cl->fd , &fds_write) && cl->i_buffer_write > 0 )
            {
                i_len = send( cl->fd , cl->p_buffer_write ,
                              cl->i_buffer_write , 0 );
                if( i_len > 0 )
                {
                    cl->p_buffer_write += i_len;
                    cl->i_buffer_write -= i_len;
                }
            }
            else if( FD_ISSET( cl->fd, &fds_read) )
            {
                int i_end = 0;
282
                int i_recv;
283

284
                while( (i_recv=recv( cl->fd, cl->p_buffer_read, 1, 0 )) > 0 &&
285
286
287
288
                       cl->p_buffer_read - cl->buffer_read < 999 )
                {
                    switch( cl->i_tel_cmd )
                    {
289
290
291
292
293
294
295
296
297
298
299
300
                    case 0:
                        switch( *cl->p_buffer_read )
                        {
                        case '\r':
                            break;
                        case '\n':
                            *cl->p_buffer_read = '\n';
                            i_end = 1;
                            break;
                        case TEL_IAC: // telnet specific command
                            cl->i_tel_cmd = 1;
                            cl->p_buffer_read++;
301
                            break;
302
303
                        default:
                            cl->p_buffer_read++;
304
                            break;
305
306
307
308
309
310
311
312
313
314
315
                        }
                        break;
                    case 1:
                        switch( *cl->p_buffer_read )
                        {
                        case TEL_WILL: case TEL_WONT:
                        case TEL_DO: case TEL_DONT:
                            cl->i_tel_cmd++;
                            cl->p_buffer_read++;
                            break;
                        default:
316
                            cl->i_tel_cmd = 0;
317
                            cl->p_buffer_read--;
318
                            break;
319
320
321
322
323
                        }
                        break;
                    case 2:
                        cl->i_tel_cmd = 0;
                        cl->p_buffer_read -= 2;
324
325
                        break;
                    }
326
327

                    if( i_end != 0 ) break;
328
329
                }

330
                if( cl->p_buffer_read - cl->buffer_read == 999 )
331
                {
332
                    Write_message( cl, NULL, "Line too long\r\n",
333
                                   cl->i_mode + 2 );
334
                }
335
336
337
338
339
340
341
342

                if (i_recv == 0)
                {
                    net_Close( cl->fd );
                    TAB_REMOVE( p_intf->p_sys->i_clients ,
                                p_intf->p_sys->clients , cl );
                    free( cl );
                }
343
344
345
346
347
348
349
350
            }
        }

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

351
            if( cl->i_mode >= WRITE_MODE_PWD && cl->i_buffer_write == 0 )
352
            {
353
               // we have finished to send
354
355
               cl->i_mode -= 2; // corresponding READ MODE
            }
356
357
            else if( cl->i_mode == READ_MODE_PWD &&
                     *cl->p_buffer_read == '\n' )
358
359
            {
                *cl->p_buffer_read = '\0';
360
                if( strcmp( psz_password, cl->buffer_read ) == 0 )
361
                {
362
363
                    Write_message( cl, NULL, "\xff\xfc\x01\r\nWelcome, "
                                   "Master\r\n> ", WRITE_MODE_CMD );
364
365
366
367
                }
                else
                {
                    /* wrong password */
368
369
                    Write_message( cl, NULL, "\r\nTry again, you polio:",
                                   WRITE_MODE_PWD );
370
371
                }
            }
372
373
            else if( cl->i_mode == READ_MODE_CMD &&
                     *cl->p_buffer_read == '\n' )
374
375
376
377
378
379
            {
                /* 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 ) )
                {
380
                    net_Close( cl->fd );
381
382
                    TAB_REMOVE( p_intf->p_sys->i_clients ,
                                p_intf->p_sys->clients , cl );
383
384
385
386
387
388
389
390
391
                    free( cl );
                }
                else if( !strncmp( cl->buffer_read, "shutdown", 8 ) )
                {
                    msg_Err( p_intf, "shutdown requested" );
                    p_intf->p_vlc->b_die = VLC_TRUE;
                }
                else
                {
392
                    vlm_message_t *message;
393
394
395
396

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

397
398
399
                    vlm_ExecuteCommand( p_sys->mediatheque, cl->buffer_read,
                                        &message );
                    Write_message( cl, message, NULL, WRITE_MODE_CMD );
400
                    vlm_MessageDelete( message );
401
402
403
404
405
406
                }
            }
        }
    }
}

407
408
static void Write_message( telnet_client_t *client, vlm_message_t *message,
                           char *string_message, int i_mode )
409
{
410
411
    char *psz_message;

412
413
414
    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 );
415
416

    /* generate the psz_message string */
417
    if( message )
418
    {
419
420
        /* ok, look for vlm_message_t */
        psz_message = MessageToString( message, 0 );
421
    }
422
    else
423
    {
424
        /* it is a basic string_message */
425
426
427
        psz_message = strdup( string_message );
    }

428
    client->buffer_write = client->p_buffer_write = psz_message;
429
    client->i_buffer_write = strlen( psz_message );
430
431
432
    client->i_mode = i_mode;
}

433
434
435
/* 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 )
436
{
437
438
439
#define STRING_CR "\r\n"
#define STRING_TAIL "> "

440
    char *psz_message;
441
    int i, i_message = sizeof( STRING_TAIL );
442

443
    if( !message || !message->psz_name )
444
    {
445
        return strdup( STRING_CR STRING_TAIL );
446
    }
447
    else if( !i_level && !message->i_child && !message->psz_value  )
448
    {
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
        /* A command is successful. Don't write anything */
        return strdup( STRING_CR STRING_TAIL );
    }

    i_message += strlen( message->psz_name ) + i_level * sizeof( "    " ) + 1;
    psz_message = malloc( i_message ); *psz_message = 0;
    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 );
466
467
468
    }
    else
    {
469
470
471
472
        i_message += sizeof( STRING_CR );
        psz_message = realloc( psz_message, i_message );
        strcat( psz_message, STRING_CR );
    }
473

474
475
476
477
    for( i = 0; i < message->i_child; i++ )
    {
        char *child_message =
            MessageToString( message->child[i], i_level + 1 );
478

479
480
481
482
        i_message += strlen( child_message );
        psz_message = realloc( psz_message, i_message );
        strcat( psz_message, child_message );
        free( child_message );
483
    }
484
485
486
487

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

    return psz_message;
488
}