telnet.lua 8.98 KB
Newer Older
1
2
3
4
--[==========================================================================[
 telnet.lua: VLM interface plugin
--[==========================================================================[
 Copyright (C) 2007 the VideoLAN team
Christophe Mutricy's avatar
Christophe Mutricy committed
5
 $Id$
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

 Authors: Antoine Cellerier <dionoea at videolan dot 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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
--]==========================================================================]

dionoea's avatar
dionoea committed
24
25
26
27
description=
[============================================================================[
 VLM Interface plugin

28
29
30
31
32
33
34
35
36
 Copy (features wise) of the original VLC modules/control/telnet.c module.

 Differences are:
    * it's in Lua
    * 'lock' command to lock the telnet promt
    * possibility to listen on different hosts including stdin
      for example:
        listen on stdin: vlc -I lua --lua-intf telnet --lua-config "telnet={host='*console'}"
        listen on stdin + 2 ports on localhost: vlc -I lua --lua-intf telnet --lua-config "telnet={hosts={'localhost:4212','localhost:5678','*console'}}"
dionoea's avatar
dionoea committed
37
38
39
40
41
42
43
 
 Configuration options setable throught the --lua-config option are:
    * hosts: A list of hosts to listen on (see examples above).
    * host: A host to listen on. (won't be used if `hosts' is set)
    * password: The password used for remote clients.
    * prompt: The prompt.
]============================================================================]
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

require "host"

--[[ Some telnet command special characters ]]
WILL = "\251" -- Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option.
WONT = "\252" -- Indicates the refusal to perform, or continue performing, the indicated option.
DO   = "\253" -- Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option.
DONT = "\254" -- Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option.
IAC  = "\255" -- Interpret as command

ECHO = "\001"

--[[ Client status change callbacks ]]
function on_password( client )
    if client.type == host.client_type.net then
        client:send( "Password: " ..IAC..WILL..ECHO )
    else
Rémi Denis-Courmont's avatar
Typos    
Rémi Denis-Courmont committed
61
        -- no authentication needed on stdin
62
63
64
65
        client:switch_status( host.status.read )
    end
end
function on_read( client )
dionoea's avatar
dionoea committed
66
    client:send( config.prompt and tostring(config.prompt) or "> " )
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
end
function on_write( client )
end

--[[ Misc functions ]]
function telnet_commands( client )
    -- remove telnet command replies from the client's data
    client.buffer = string.gsub( client.buffer, IAC.."["..DO..DONT..WILL..WONT.."].", "" )
end

function vlm_message_to_string(client,message,prefix)
    local prefix = prefix or ""
    if message.value then
        client:append(prefix .. message.name .. " : " .. message.value)
        return
    else
        client:append(prefix .. message.name)
        if message.children then
            for i,c in ipairs(message.children) do
                vlm_message_to_string(client,c,prefix.."    ")
            end
        end
        return
    end
end

--[[ Configure the host ]]
h = host.host()

h.status_callbacks[host.status.password] = on_password
h.status_callbacks[host.status.read] = on_read
h.status_callbacks[host.status.write] = on_write

h:listen( config.hosts or config.host or "localhost:4212" )
password = config.password or "admin"

--[[ Launch vlm ]]
104
vlm = vlc.vlm()
105
106
107
108
109

--[[ Commands ]]
function shutdown(client)
    h:broadcast("Shutting down.\r\n")
    vlc.msg.err("shutdown requested")
110
    vlc.misc.quit()
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
    return true
end
function logout(client)
    client:del()
    return true
end
function quit(client)
    if client.type == host.client_type.net then
        return logout(client)
    else
        return shutdown(client)
    end
end
function lock(client)
    client:send("\r\n")
    client:switch_status( host.status.password )
    client.buffer = ""
    return false
end
dionoea's avatar
dionoea committed
130
131
132
133
134
135
function print_text(text)
    return function(client)
        client:append(string.gsub(text,"\r?\n","\r\n"))
        return true
    end
end
136
137
138
139
140
141
142
143
function help(client)
    client:append("    Telnet Specific Commands:")
    for c,t in pairs(commands) do
        client:append("        "..c.." : "..t.help)
    end
    return true
end
commands = {
dionoea's avatar
dionoea committed
144
145
146
147
148
    ["shutdown"]    = { func = shutdown, help = "shutdown VLC" },
    ["quit"]        = { func = quit, help = "logout from telnet/shutdown VLC from local shell" },
    ["logout"]      = { func = logout, help = "logout" },
    ["lock"]        = { func = lock, help = "lock the telnet prompt" },
    ["description"] = { func = print_text(description), help = "describe this module" },
149
    ["license"]     = { func = print_text(vlc.misc.license()), help = "print VLC's license message" },
dionoea's avatar
dionoea committed
150
    ["help"]        = { func = help, help = "show this help", dovlm = true },
151
152
153
154
155
156
157
    }

function client_command( client )
    local cmd = client.buffer
    client.buffer = ""
    if not commands[cmd] or not commands[cmd].func or commands[cmd].dovlm then
        -- if it's not an interface specific command, it has to be a VLM command
158
        local message, vlc_err = vlm:execute_command( cmd )
159
160
        vlm_message_to_string( client, message )
        if not commands[cmd] or not commands[cmd].func and not commands[cmd].dovlm then
dionoea's avatar
dionoea committed
161
            if vlc_err ~= 0 then client:append( "Type `help' for help." ) end
162
163
164
165
166
167
168
169
170
171
172
173
            return true
        end
    end
    ok, msg = pcall( commands[cmd].func, client )
    if not ok then
        client:append( "Error in `"..cmd.."' "..msg )
        return true
    end
    return msg
end

--[[ The main loop ]]
174
while not vlc.misc.should_die() do
175
    local w, r = h:accept_and_select()
176
177
178
179
180
181
182
183
184
185

    -- Handle writes
    for _, client in pairs(w) do
        local len = client:send()
        client.buffer = string.sub(client.buffer,len+1)
        if client.buffer == "" then client:switch_status( host.status.read ) end
    end

    -- Handle reads
    for _, client in pairs(r) do
186
        local str = string.gsub(client:recv(1000),"\r","\n")
187
        local done = false
188
189
190

        -- the telnet client program has leave
        if not str then
191
192
            client.buffer = "quit"
            done = true
193
194

        -- Caught a ^D
195
196
197
198
199
        elseif client.buffer == ""
           and ((client.type == host.client_type.stdio and str == "")
           or  (client.type == host.client_type.net and str == "\004")) then
            client.buffer = "quit"
            done = true
200
201
202
203
204
205
206

        -- '\n' found: a command was sent
        elseif string.match(str,"\n") then
            client.buffer = client.buffer .. str
            done = true

        -- The command is not finished yet
207
208
209
        else
            client.buffer = client.buffer .. str
        end
210
211

        -- Some cleaning for telnet
212
213
214
        if client.type == host.client_type.net then
            telnet_commands( client )
        end
215
216

        -- If a command must be parsed
217
        if done then
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
            -- loop on all commands (might have more than one commands seperated by '\n'
            local returned_values = ""
            while not (client.buffer == "") do
                -- pick the first command
                local commands = ""
                if string.find(client.buffer, "\n") then
                    commands = string.sub(client.buffer, string.find(client.buffer, "\n") + 1)
                    client.buffer = string.sub(client.buffer, 0, string.find(client.buffer, "\n") - 1)
                end
                local cmd = client.buffer

                if client.status == host.status.password then
                    if client.buffer == password then
                        client:send( IAC..WONT..ECHO.."\r\nWelcome, Master\r\n" )
                        client.buffer = ""
                        client:switch_status( host.status.write )
                    else
                        client:send( "\r\nWrong password\r\nPassword: " )
                        client.buffer = ""
                    end
                elseif client_command( client ) then
239
                    client:switch_status( host.status.write )
240
241
                    -- special case to exit the loop
                    if cmd == "quit" or cmd == "shutdown" then break end
242
                end
243
244
                returned_values = returned_values .. client.buffer
                client.buffer = commands
245
            end
246
247
            vlc.msg.err("end of loop")
            client.buffer = returned_values
248
249
250
251
252
        end
    end
end

--[[ Clean up ]]
253
vlm = nil