Implement new RPC authorization using --rpc-secret option

pull/195/head
Tatsuhiro Tsujikawa 2014-02-02 17:34:07 +09:00
parent 30e4077440
commit 7f6987a4b4
12 changed files with 232 additions and 79 deletions

View File

@ -926,9 +926,9 @@ RPC Options
.. option:: --enable-rpc[=true|false] .. option:: --enable-rpc[=true|false]
Enable JSON-RPC/XML-RPC server. It is strongly recommended to set username Enable JSON-RPC/XML-RPC server. It is strongly recommended to set
and password using :option:`--rpc-user` and :option:`--rpc-passwd` secret authorization token using :option:`--rpc-secret` option. See
option. See also :option:`--rpc-listen-port` option. Default: ``false`` also :option:`--rpc-listen-port` option. Default: ``false``
.. option:: --pause[=true|false] .. option:: --pause[=true|false]
@ -1004,6 +1004,11 @@ RPC Options
:func:`aria2.addTorrent` or :func:`aria2.addMetalink` will not be :func:`aria2.addTorrent` or :func:`aria2.addMetalink` will not be
saved by :option:`--save-session` option. Default: ``false`` saved by :option:`--save-session` option. Default: ``false``
.. option:: --rpc-secret=<TOKEN>
Set RPC secret authorization token. Read :ref:`rpc_auth` to know
how this option value is used.
.. option:: --rpc-secure[=true|false] .. option:: --rpc-secure[=true|false]
RPC transport will be encrypted by SSL/TLS. The RPC clients must RPC transport will be encrypted by SSL/TLS. The RPC clients must
@ -2021,13 +2026,40 @@ GID
<--gid>` option. When querying download by GID, you can specify the <--gid>` option. When querying download by GID, you can specify the
prefix of GID as long as it is a unique prefix among others. prefix of GID as long as it is a unique prefix among others.
.. _rpc_auth:
RPC authorization secret token
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 1.18.4, in addition to HTTP basic authorization, aria2 provides
RPC method-level authorization. In the future release, HTTP basic
authorization will be removed and RPC method-level authorization will
become mandatory.
To use RPC method-level authorization, user has to specify RPC secret
authorization token using :option:`--rpc-secret` option. For each RPC
method call, the caller has to include the token prefixed with
``token:``. If :option:`--rpc-secret` option is not used and first
parameter in the RPC method is a String and starts with ``token:``, it
is removed from the parameter before being processed.
For example, if RPC secret authorization token is ``$$secret$$``, to
call `aria2.addUri` RPC method would look like this::
aria2.addUri("token::$$secret$$", ["http://example.org/file"])
The `system.multicall` RPC method is treated specially. Since XML-RPC
specification only allows one array as a paremter for this method, we
don't specify token in its call. Instead, each nested method call has
to provide token as 1st parameter as described above.
Methods Methods
~~~~~~~ ~~~~~~~
All code examples come from Python2.7 interpreter. All code examples come from Python2.7 interpreter.
For *secret* parameter, see :ref:`rpc_auth`.
.. function:: aria2.addUri([secret], uris[, options[, position]])
.. function:: aria2.addUri(uris[, options[, position]])
This method adds new HTTP(S)/FTP/BitTorrent Magnet URI. *uris* is of This method adds new HTTP(S)/FTP/BitTorrent Magnet URI. *uris* is of
type array and its element is URI which is of type string. For type array and its element is URI which is of type string. For
@ -2075,7 +2107,7 @@ All code examples come from Python2.7 interpreter.
>>> s.aria2.addUri(['http://example.org/file'], {}, 0) >>> s.aria2.addUri(['http://example.org/file'], {}, 0)
'ca3d829cee549a4d' 'ca3d829cee549a4d'
.. function:: aria2.addTorrent(torrent[, uris[, options[, position]]]) .. function:: aria2.addTorrent([secret], torrent[, uris[, options[, position]]])
This method adds BitTorrent download by uploading ".torrent" file. This method adds BitTorrent download by uploading ".torrent" file.
If you want to add BitTorrent Magnet URI, use :func:`aria2.addUri` If you want to add BitTorrent Magnet URI, use :func:`aria2.addUri`
@ -2125,7 +2157,7 @@ All code examples come from Python2.7 interpreter.
>>> s.aria2.addTorrent(xmlrpclib.Binary(open('file.torrent').read())) >>> s.aria2.addTorrent(xmlrpclib.Binary(open('file.torrent').read()))
'2089b05ecca3d829' '2089b05ecca3d829'
.. function:: aria2.addMetalink(metalink[, options[, position]]) .. function:: aria2.addMetalink([secret], metalink[, options[, position]])
This method adds Metalink download by uploading ".metalink" file. This method adds Metalink download by uploading ".metalink" file.
*metalink* is of type base64 which contains Base64-encoded *metalink* is of type base64 which contains Base64-encoded
@ -2170,7 +2202,7 @@ All code examples come from Python2.7 interpreter.
>>> s.aria2.addMetalink(xmlrpclib.Binary(open('file.meta4').read())) >>> s.aria2.addMetalink(xmlrpclib.Binary(open('file.meta4').read()))
['2089b05ecca3d829'] ['2089b05ecca3d829']
.. function:: aria2.remove(gid) .. function:: aria2.remove([secret], gid)
This method removes the download denoted by *gid*. *gid* is of type This method removes the download denoted by *gid*. *gid* is of type
string. If specified download is in progress, it is stopped at string. If specified download is in progress, it is stopped at
@ -2200,14 +2232,14 @@ All code examples come from Python2.7 interpreter.
>>> s.aria2.remove('2089b05ecca3d829') >>> s.aria2.remove('2089b05ecca3d829')
'2089b05ecca3d829' '2089b05ecca3d829'
.. function:: aria2.forceRemove(gid) .. function:: aria2.forceRemove([secret], gid)
This method removes the download denoted by *gid*. This method This method removes the download denoted by *gid*. This method
behaves just like :func:`aria2.remove` except that this method removes behaves just like :func:`aria2.remove` except that this method removes
download without any action which takes time such as contacting download without any action which takes time such as contacting
BitTorrent tracker. BitTorrent tracker.
.. function:: aria2.pause(gid) .. function:: aria2.pause([secret], gid)
This method pauses the download denoted by *gid*. *gid* is of type This method pauses the download denoted by *gid*. *gid* is of type
string. The status of paused download becomes ``paused``. If the string. The status of paused download becomes ``paused``. If the
@ -2216,36 +2248,36 @@ All code examples come from Python2.7 interpreter.
started. To change status to ``waiting``, use :func:`aria2.unpause` method. started. To change status to ``waiting``, use :func:`aria2.unpause` method.
This method returns GID of paused download. This method returns GID of paused download.
.. function:: aria2.pauseAll() .. function:: aria2.pauseAll([secret])
This method is equal to calling :func:`aria2.pause` for every active/waiting This method is equal to calling :func:`aria2.pause` for every active/waiting
download. This methods returns ``OK`` for success. download. This methods returns ``OK`` for success.
.. function:: aria2.forcePause(pid) .. function:: aria2.forcePause([secret], pid)
This method pauses the download denoted by *gid*. This method This method pauses the download denoted by *gid*. This method
behaves just like :func:`aria2.pause` except that this method pauses behaves just like :func:`aria2.pause` except that this method pauses
download without any action which takes time such as contacting download without any action which takes time such as contacting
BitTorrent tracker. BitTorrent tracker.
.. function:: aria2.forcePauseAll() .. function:: aria2.forcePauseAll([secret])
This method is equal to calling :func:`aria2.forcePause` for every This method is equal to calling :func:`aria2.forcePause` for every
active/waiting download. This methods returns ``OK`` for success. active/waiting download. This methods returns ``OK`` for success.
.. function:: aria2.unpause(gid) .. function:: aria2.unpause([secret], gid)
This method changes the status of the download denoted by *gid* from This method changes the status of the download denoted by *gid* from
``paused`` to ``waiting``. This makes the download eligible to restart. ``paused`` to ``waiting``. This makes the download eligible to restart.
*gid* is of type string. This method returns GID of unpaused *gid* is of type string. This method returns GID of unpaused
download. download.
.. function:: aria2.unpauseAll() .. function:: aria2.unpauseAll([secret])
This method is equal to calling :func:`aria2.unpause` for every active/waiting This method is equal to calling :func:`aria2.unpause` for every active/waiting
download. This methods returns ``OK`` for success. download. This methods returns ``OK`` for success.
.. function:: aria2.tellStatus(gid[, keys]) .. function:: aria2.tellStatus([secret], gid[, keys])
This method returns download progress of the download denoted by This method returns download progress of the download denoted by
*gid*. *gid* is of type string. *keys* is array of string. If it is *gid*. *gid* is of type string. *keys* is array of string. If it is
@ -2443,7 +2475,7 @@ All code examples come from Python2.7 interpreter.
>>> pprint(r) >>> pprint(r)
{'completedLength': '34896138', 'gid': '2089b05ecca3d829', 'totalLength': '34896138'} {'completedLength': '34896138', 'gid': '2089b05ecca3d829', 'totalLength': '34896138'}
.. function:: aria2.getUris(gid) .. function:: aria2.getUris([secret], gid)
This method returns URIs used in the download denoted by *gid*. *gid* This method returns URIs used in the download denoted by *gid*. *gid*
is of type string. The response is of type array and its element is of is of type string. The response is of type array and its element is of
@ -2481,7 +2513,7 @@ All code examples come from Python2.7 interpreter.
>>> pprint(r) >>> pprint(r)
[{'status': 'used', 'uri': 'http://example.org/file'}] [{'status': 'used', 'uri': 'http://example.org/file'}]
.. function:: aria2.getFiles(gid) .. function:: aria2.getFiles([secret], gid)
This method returns file list of the download denoted by *gid*. *gid* This method returns file list of the download denoted by *gid*. *gid*
is of type string. The response is of type array and its element is of is of type string. The response is of type array and its element is of
@ -2553,7 +2585,7 @@ All code examples come from Python2.7 interpreter.
'uris': [{'status': 'used', 'uris': [{'status': 'used',
'uri': 'http://example.org/file'}]}] 'uri': 'http://example.org/file'}]}]
.. function:: aria2.getPeers(gid) .. function:: aria2.getPeers([secret], gid)
This method returns peer list of the download denoted by *gid*. *gid* This method returns peer list of the download denoted by *gid*. *gid*
is of type string. This method is for BitTorrent only. The response is of type string. This method is for BitTorrent only. The response
@ -2648,7 +2680,7 @@ All code examples come from Python2.7 interpreter.
'seeder': 'false, 'seeder': 'false,
'uploadSpeed': '6890'}] 'uploadSpeed': '6890'}]
.. function:: aria2.getServers(gid) .. function:: aria2.getServers([secret], gid)
This method returns currently connected HTTP(S)/FTP servers of the download denoted by *gid*. *gid* is of type string. The response This method returns currently connected HTTP(S)/FTP servers of the download denoted by *gid*. *gid* is of type string. The response
is of type array and its element is of type struct and it contains is of type array and its element is of type struct and it contains
@ -2701,14 +2733,14 @@ All code examples come from Python2.7 interpreter.
'downloadSpeed': '20285', 'downloadSpeed': '20285',
'uri': 'http://example.org/file'}]}] 'uri': 'http://example.org/file'}]}]
.. function:: aria2.tellActive([keys]) .. function:: aria2.tellActive([secret], [keys])
This method returns the list of active downloads. The response is of This method returns the list of active downloads. The response is of
type array and its element is the same struct returned by type array and its element is the same struct returned by
:func:`aria2.tellStatus` method. For *keys* parameter, please refer to :func:`aria2.tellStatus` method. For *keys* parameter, please refer to
:func:`aria2.tellStatus` method. :func:`aria2.tellStatus` method.
.. function:: aria2.tellWaiting(offset, num, [keys]) .. function:: aria2.tellWaiting([secret], offset, num, [keys])
This method returns the list of waiting download, including paused This method returns the list of waiting download, including paused
downloads. *offset* is of type integer and specifies the offset from downloads. *offset* is of type integer and specifies the offset from
@ -2732,7 +2764,7 @@ All code examples come from Python2.7 interpreter.
The response is of type array and its element is the same struct The response is of type array and its element is the same struct
returned by :func:`aria2.tellStatus` method. returned by :func:`aria2.tellStatus` method.
.. function:: aria2.tellStopped(offset, num, [keys]) .. function:: aria2.tellStopped([secret], offset, num, [keys])
This method returns the list of stopped download. *offset* is of type This method returns the list of stopped download. *offset* is of type
integer and specifies the offset from the oldest download. *num* is of integer and specifies the offset from the oldest download. *num* is of
@ -2745,7 +2777,7 @@ All code examples come from Python2.7 interpreter.
The response is of type array and its element is the same struct The response is of type array and its element is the same struct
returned by :func:`aria2.tellStatus` method. returned by :func:`aria2.tellStatus` method.
.. function:: aria2.changePosition(gid, pos, how) .. function:: aria2.changePosition([secret], gid, pos, how)
This method changes the position of the download denoted by This method changes the position of the download denoted by
*gid*. *pos* is of type integer. *how* is of type string. If *how* is *gid*. *pos* is of type integer. *how* is of type string. If *how* is
@ -2789,7 +2821,7 @@ All code examples come from Python2.7 interpreter.
>>> s.aria2.changePosition('2089b05ecca3d829', 0, 'POS_SET') >>> s.aria2.changePosition('2089b05ecca3d829', 0, 'POS_SET')
0 0
.. function:: aria2.changeUri(gid, fileIndex, delUris, addUris[, position]) .. function:: aria2.changeUri([secret], gid, fileIndex, delUris, addUris[, position])
This method removes URIs in *delUris* from and appends URIs in This method removes URIs in *delUris* from and appends URIs in
*addUris* to download denoted by *gid*. *delUris* and *addUris* are *addUris* to download denoted by *gid*. *delUris* and *addUris* are
@ -2837,7 +2869,7 @@ All code examples come from Python2.7 interpreter.
['http://example.org/file']) ['http://example.org/file'])
[0, 1] [0, 1]
.. function:: aria2.getOption(gid) .. function:: aria2.getOption([secret], gid)
This method returns options of the download denoted by *gid*. The This method returns options of the download denoted by *gid*. The
response is of type struct. Its key is the name of option. The value response is of type struct. Its key is the name of option. The value
@ -2882,7 +2914,7 @@ All code examples come from Python2.7 interpreter.
'async-dns': 'true', 'async-dns': 'true',
.... ....
.. function:: aria2.changeOption(gid, options) .. function:: aria2.changeOption([secret], gid, options)
This method changes options of the download denoted by *gid* This method changes options of the download denoted by *gid*
dynamically. *gid* is of type string. *options* is of type struct. dynamically. *gid* is of type string. *options* is of type struct.
@ -2933,7 +2965,7 @@ All code examples come from Python2.7 interpreter.
>>> s.aria2.changeOption('2089b05ecca3d829', {'max-download-limit':'20K'}) >>> s.aria2.changeOption('2089b05ecca3d829', {'max-download-limit':'20K'})
'OK' 'OK'
.. function:: aria2.getGlobalOption() .. function:: aria2.getGlobalOption([secret])
This method returns global options. The response is of type This method returns global options. The response is of type
struct. Its key is the name of option. The value type is string. struct. Its key is the name of option. The value type is string.
@ -2943,7 +2975,7 @@ All code examples come from Python2.7 interpreter.
for the options of newly added download, the response contains keys for the options of newly added download, the response contains keys
returned by :func:`aria2.getOption` method. returned by :func:`aria2.getOption` method.
.. function:: aria2.changeGlobalOption(options) .. function:: aria2.changeGlobalOption([secret], options)
This method changes global options dynamically. *options* is of type This method changes global options dynamically. *options* is of type
struct. struct.
@ -2974,7 +3006,7 @@ All code examples come from Python2.7 interpreter.
value. Note that log file is always opened in append mode. This method value. Note that log file is always opened in append mode. This method
returns ``OK`` for success. returns ``OK`` for success.
.. function:: aria2.getGlobalStat() .. function:: aria2.getGlobalStat([secret])
This method returns global statistics such as overall download and This method returns global statistics such as overall download and
upload speed. The response is of type struct and contains following upload speed. The response is of type struct and contains following
@ -3026,12 +3058,12 @@ All code examples come from Python2.7 interpreter.
'numWaiting': '0', 'numWaiting': '0',
'uploadSpeed': '0'} 'uploadSpeed': '0'}
.. function:: aria2.purgeDownloadResult() .. function:: aria2.purgeDownloadResult([secret])
This method purges completed/error/removed downloads to free memory. This method purges completed/error/removed downloads to free memory.
This method returns ``OK``. This method returns ``OK``.
.. function:: aria2.removeDownloadResult(gid) .. function:: aria2.removeDownloadResult([secret], gid)
This method removes completed/error/removed download denoted by *gid* This method removes completed/error/removed download denoted by *gid*
from memory. This method returns ``OK`` for success. from memory. This method returns ``OK`` for success.
@ -3061,7 +3093,7 @@ All code examples come from Python2.7 interpreter.
>>> s.aria2.removeDownloadResult('2089b05ecca3d829') >>> s.aria2.removeDownloadResult('2089b05ecca3d829')
'OK' 'OK'
.. function:: aria2.getVersion() .. function:: aria2.getVersion([secret])
This method returns version of the program and the list of enabled This method returns version of the program and the list of enabled
features. The response is of type struct and contains following keys. features. The response is of type struct and contains following keys.
@ -3111,7 +3143,7 @@ All code examples come from Python2.7 interpreter.
'XML-RPC'], 'XML-RPC'],
'version': '1.11.0'} 'version': '1.11.0'}
.. function:: aria2.getSessionInfo() .. function:: aria2.getSessionInfo([secret])
This method returns session information. This method returns session information.
The response is of type struct and contains following key. The response is of type struct and contains following key.
@ -3140,11 +3172,11 @@ All code examples come from Python2.7 interpreter.
>>> s.aria2.getSessionInfo() >>> s.aria2.getSessionInfo()
{'sessionId': 'cd6a3bc6a1de28eb5bfa181e5f6b916d44af31a9'} {'sessionId': 'cd6a3bc6a1de28eb5bfa181e5f6b916d44af31a9'}
.. function:: aria2.shutdown() .. function:: aria2.shutdown([secret])
This method shutdowns aria2. This method returns ``OK``. This method shutdowns aria2. This method returns ``OK``.
.. function:: aria2.forceShutdown() .. function:: aria2.forceShutdown([secret])
This method shutdowns :func:`aria2. This method behaves like aria2.shutdown` This method shutdowns :func:`aria2. This method behaves like aria2.shutdown`
except that any actions which takes time such as contacting BitTorrent except that any actions which takes time such as contacting BitTorrent

View File

@ -239,6 +239,8 @@ OptionParser.new do |opt|
opt.on("--user USERNAME", "XML-RPC username"){|val| options["user"]=val } opt.on("--user USERNAME", "XML-RPC username"){|val| options["user"]=val }
opt.on("--passwd PASSWORD", "XML-RPC password"){|val| options["passwd"]=val } opt.on("--passwd PASSWORD", "XML-RPC password"){|val| options["passwd"]=val }
opt.on("--secret SECRET", "XML-RPC secret authorization token"){|val| options["secret"]=val }
opt.banner=<<EOS opt.banner=<<EOS
Usage: #{program_name} addUri URI... [options] Usage: #{program_name} addUri URI... [options]
#{program_name} addTorrent /path/to/torrent_file URI... [options] #{program_name} addTorrent /path/to/torrent_file URI... [options]
@ -300,6 +302,7 @@ end
if not options.has_key?("port") then if not options.has_key?("port") then
options["port"]="6800" options["port"]="6800"
end end
secret = if options.has_key?("secret") then "token:"+options["secret"] else nil end
client=XMLRPC::Client.new3({:host => options["server"], client=XMLRPC::Client.new3({:host => options["server"],
:port => options["port"], :port => options["port"],
@ -311,79 +314,89 @@ options.delete("server")
options.delete("port") options.delete("port")
options.delete("user") options.delete("user")
options.delete("passwd") options.delete("passwd")
options.delete("secret")
def client_call client, secret, method, *params
if secret.nil?
client.call(method, *params)
else
client.call(method, secret, *params)
end
end
if command == "addUri" then if command == "addUri" then
result=client.call("aria2."+command, resources, options) result=client_call(client, secret, "aria2."+command, resources, options)
elsif command == "addTorrent" then elsif command == "addTorrent" then
torrentData=IO.read(resources[0]) torrentData=IO.read(resources[0])
result=client.call("aria2."+command, result=client_call(client, secret, "aria2."+command,
XMLRPC::Base64.new(torrentData), resources[1..-1], options) XMLRPC::Base64.new(torrentData), resources[1..-1], options)
elsif command == "addMetalink" then elsif command == "addMetalink" then
metalinkData=IO.read(resources[0]) metalinkData=IO.read(resources[0])
result=client.call("aria2."+command, result=client_call(client, secret, "aria2."+command,
XMLRPC::Base64.new(metalinkData), options) XMLRPC::Base64.new(metalinkData), options)
elsif command == "tellStatus" then elsif command == "tellStatus" then
result=client.call("aria2."+command, resources[0], resources[1..-1]) result=client_call(client, secret, "aria2."+command,
resources[0], resources[1..-1])
elsif command == "tellActive" then elsif command == "tellActive" then
result=client.call("aria2."+command, resources[0..-1]) result=client_call(client, secret, "aria2."+command, resources[0..-1])
elsif command == "tellWaiting" then elsif command == "tellWaiting" then
result=client.call("aria2."+command, resources[0].to_i(), resources[1].to_i(), result=client_call(client, secret, "aria2."+command, resources[0].to_i(),
resources[2..-1]) resources[1].to_i(), resources[2..-1])
elsif command == "tellStopped" then elsif command == "tellStopped" then
result=client.call("aria2."+command, resources[0].to_i(), resources[1].to_i(), result=client_call(client, secret, "aria2."+command, resources[0].to_i(),
resources[2..-1]) resources[1].to_i(), resources[2..-1])
elsif command == "getOption" then elsif command == "getOption" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "getGlobalOption" then elsif command == "getGlobalOption" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "pause" then elsif command == "pause" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "pauseAll" then elsif command == "pauseAll" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "forcePause" then elsif command == "forcePause" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "forcePauseAll" then elsif command == "forcePauseAll" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "unpause" then elsif command == "unpause" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "unpauseAll" then elsif command == "unpauseAll" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "remove" then elsif command == "remove" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "forceRemove" then elsif command == "forceRemove" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "changePosition" then elsif command == "changePosition" then
result=client.call("aria2."+command, resources[0], resources[1].to_i(), result=client_call(client, secret, "aria2."+command, resources[0],
resources[2]) resources[1].to_i(), resources[2])
elsif command == "getFiles" then elsif command == "getFiles" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "getUris" then elsif command == "getUris" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "getPeers" then elsif command == "getPeers" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "getServers" then elsif command == "getServers" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "purgeDownloadResult" then elsif command == "purgeDownloadResult" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "removeDownloadResult" then elsif command == "removeDownloadResult" then
result=client.call("aria2."+command, resources[0]) result=client_call(client, secret, "aria2."+command, resources[0])
elsif command == "changeOption" then elsif command == "changeOption" then
result=client.call("aria2."+command, resources[0], options) result=client_call(client, secret, "aria2."+command, resources[0], options)
elsif command == "changeGlobalOption" then elsif command == "changeGlobalOption" then
result=client.call("aria2."+command, options) result=client_call(client, secret, "aria2."+command, options)
elsif command == "getVersion" then elsif command == "getVersion" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "getSessionInfo" then elsif command == "getSessionInfo" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "shutdown" then elsif command == "shutdown" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "forceShutdown" then elsif command == "forceShutdown" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "getGlobalStat" then elsif command == "getGlobalStat" then
result=client.call("aria2."+command) result=client_call(client, secret, "aria2."+command)
elsif command == "appendUri" then elsif command == "appendUri" then
result=client.call("aria2.changeUri", resources[0], resources[1].to_i(), [], result=client_call(client, secret, "aria2.changeUri", resources[0],
resources[2..-1]) resources[1].to_i(), [], resources[2..-1])
else else
puts "Command not recognized" puts "Command not recognized"
exit 1 exit 1

View File

@ -865,6 +865,14 @@ std::vector<OptionHandler*> OptionHandlerFactory::createOptionHandlers()
op->setChangeGlobalOption(true); op->setChangeGlobalOption(true);
handlers.push_back(op); handlers.push_back(op);
} }
{
OptionHandler* op(new DefaultOptionHandler
(PREF_RPC_SECRET,
TEXT_RPC_SECRET));
op->addTag(TAG_RPC);
op->setEraseAfterParse(true);
handlers.push_back(op);
}
{ {
OptionHandler* op(new BooleanOptionHandler OptionHandler* op(new BooleanOptionHandler
(PREF_RPC_SECURE, (PREF_RPC_SECURE,

View File

@ -48,6 +48,7 @@
#include "fmt.h" #include "fmt.h"
#include "DlAbortEx.h" #include "DlAbortEx.h"
#include "a2functional.h" #include "a2functional.h"
#include "util.h"
namespace aria2 { namespace aria2 {
@ -68,9 +69,33 @@ std::unique_ptr<ValueBase> RpcMethod::createErrorResponse
return std::move(params); return std::move(params);
} }
void RpcMethod::authorize(RpcRequest& req, DownloadEngine* e)
{
std::string token;
// We always treat first parameter as token if it is string and
// starts with "token:" and remove it from parameter list, so that
// we don't have to add conditionals to all RPCMethod
// implementations.
if(req.params && !req.params->empty()) {
auto t = downcast<String>(req.params->get(0));
if(t) {
if(util::startsWith(t->s(), "token:")) {
token = t->s().substr(6);
req.params->pop_front();
}
}
}
if(e && e->getOption()->defined(PREF_RPC_SECRET)) {
if(token != e->getOption()->get(PREF_RPC_SECRET)) {
throw DL_ABORT_EX("Unauthorized");
}
}
}
RpcResponse RpcMethod::execute(RpcRequest req, DownloadEngine* e) RpcResponse RpcMethod::execute(RpcRequest req, DownloadEngine* e)
{ {
try { try {
authorize(req, e);
auto r = process(req, e); auto r = process(req, e);
return RpcResponse(0, std::move(r), std::move(req.id)); return RpcResponse(0, std::move(r), std::move(req.id));
} catch(RecoverableException& ex) { } catch(RecoverableException& ex) {

View File

@ -93,6 +93,8 @@ public:
virtual ~RpcMethod(); virtual ~RpcMethod();
virtual void authorize(RpcRequest& req, DownloadEngine* e);
// Do work to fulfill RpcRequest req and returns its result as // Do work to fulfill RpcRequest req and returns its result as
// RpcResponse. This method delegates to process() method. // RpcResponse. This method delegates to process() method.
RpcResponse execute(RpcRequest req, DownloadEngine* e); RpcResponse execute(RpcRequest req, DownloadEngine* e);

View File

@ -572,6 +572,14 @@ protected:
virtual std::unique_ptr<ValueBase> process virtual std::unique_ptr<ValueBase> process
(const RpcRequest& req, DownloadEngine* e) CXX11_OVERRIDE; (const RpcRequest& req, DownloadEngine* e) CXX11_OVERRIDE;
public: public:
virtual void authorize(RpcRequest& req, DownloadEngine* e) CXX11_OVERRIDE
{
// Batch calls (e.g., system.multicall) authorizes only nested
// methods. This is because XML-RPC system.multicall only accpets
// methods array and there is no room for us to insert token
// parameter.
}
static const char* getMethodName() static const char* getMethodName()
{ {
return "system.multicall"; return "system.multicall";

View File

@ -151,6 +151,16 @@ void List::set(size_t index, std::unique_ptr<ValueBase> v)
list_[index] = std::move(v); list_[index] = std::move(v);
} }
void List::pop_front()
{
list_.pop_front();
}
void List::pop_back()
{
list_.pop_back();
}
void List::append(std::unique_ptr<ValueBase> v) void List::append(std::unique_ptr<ValueBase> v)
{ {
list_.push_back(std::move(v)); list_.push_back(std::move(v));

View File

@ -38,7 +38,7 @@
#include "common.h" #include "common.h"
#include <string> #include <string>
#include <vector> #include <deque>
#include <map> #include <map>
#include <memory> #include <memory>
@ -170,7 +170,7 @@ private:
class List:public ValueBase { class List:public ValueBase {
public: public:
typedef std::vector<std::unique_ptr<ValueBase>> ValueType; typedef std::deque<std::unique_ptr<ValueBase>> ValueType;
List(); List();
@ -196,6 +196,12 @@ public:
// Returns the const reference of the object at the given index. // Returns the const reference of the object at the given index.
ValueBase* operator[](size_t index) const; ValueBase* operator[](size_t index) const;
// Pops the value in the front of the list.
void pop_front();
// Pops the value in the back of the list.
void pop_back();
// Returns a read/write iterator that points to the first object in // Returns a read/write iterator that points to the first object in
// list. // list.
ValueType::iterator begin(); ValueType::iterator begin();

View File

@ -359,6 +359,8 @@ PrefPtr PREF_GID = makePref("gid");
// values: 1*digit // values: 1*digit
PrefPtr PREF_SAVE_SESSION_INTERVAL = makePref("save-session-interval"); PrefPtr PREF_SAVE_SESSION_INTERVAL = makePref("save-session-interval");
PrefPtr PREF_ENABLE_COLOR = makePref("enable-color"); PrefPtr PREF_ENABLE_COLOR = makePref("enable-color");
// value: string
PrefPtr PREF_RPC_SECRET = makePref("rpc-secret");
/** /**
* FTP related preferences * FTP related preferences

View File

@ -296,6 +296,8 @@ extern PrefPtr PREF_GID;
extern PrefPtr PREF_SAVE_SESSION_INTERVAL; extern PrefPtr PREF_SAVE_SESSION_INTERVAL;
// value: true |false // value: true |false
extern PrefPtr PREF_ENABLE_COLOR; extern PrefPtr PREF_ENABLE_COLOR;
// value: string
extern PrefPtr PREF_RPC_SECRET;
/** /**
* FTP related preferences * FTP related preferences

View File

@ -770,11 +770,11 @@
" option is useful when the system does not have\n" \ " option is useful when the system does not have\n" \
" /etc/resolv.conf and user does not have the\n" \ " /etc/resolv.conf and user does not have the\n" \
" permission to create it.") " permission to create it.")
#define TEXT_ENABLE_RPC \ #define TEXT_ENABLE_RPC \
_(" --enable-rpc[=true|false] Enable JSON-RPC/XML-RPC server.\n" \ _(" --enable-rpc[=true|false] Enable JSON-RPC/XML-RPC server.\n" \
" It is strongly recommended to set username and\n" \ " It is strongly recommended to set secret\n" \
" password using --rpc-user and --rpc-passwd\n" \ " authorization token using --rpc-secret option.\n" \
" option. See also --rpc-listen-port option.") " See also --rpc-listen-port option.")
#define TEXT_RPC_MAX_REQUEST_SIZE \ #define TEXT_RPC_MAX_REQUEST_SIZE \
_(" --rpc-max-request-size=SIZE Set max size of JSON-RPC/XML-RPC request. If aria2\n" \ _(" --rpc-max-request-size=SIZE Set max size of JSON-RPC/XML-RPC request. If aria2\n" \
" detects the request is more than SIZE bytes, it\n" \ " detects the request is more than SIZE bytes, it\n" \
@ -960,3 +960,5 @@
" when aria2 exits.") " when aria2 exits.")
#define TEXT_ENABLE_COLOR \ #define TEXT_ENABLE_COLOR \
_(" --enable-color[=true|false] Enable color output for a terminal.") _(" --enable-color[=true|false] Enable color output for a terminal.")
#define TEXT_RPC_SECRET \
_(" --rpc-secret=TOKEN Set RPC secret authorization token.")

View File

@ -33,6 +33,7 @@ namespace rpc {
class RpcMethodTest:public CppUnit::TestFixture { class RpcMethodTest:public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(RpcMethodTest); CPPUNIT_TEST_SUITE(RpcMethodTest);
CPPUNIT_TEST(testAuthorize);
CPPUNIT_TEST(testAddUri); CPPUNIT_TEST(testAddUri);
CPPUNIT_TEST(testAddUri_withoutUri); CPPUNIT_TEST(testAddUri_withoutUri);
CPPUNIT_TEST(testAddUri_notUri); CPPUNIT_TEST(testAddUri_notUri);
@ -96,6 +97,7 @@ public:
(std::vector<std::shared_ptr<RequestGroup>>{}, 1, option_.get())); (std::vector<std::shared_ptr<RequestGroup>>{}, 1, option_.get()));
} }
void testAuthorize();
void testAddUri(); void testAddUri();
void testAddUri_withoutUri(); void testAddUri_withoutUri();
void testAddUri_notUri(); void testAddUri_notUri();
@ -159,6 +161,47 @@ RpcRequest createReq(std::string methodName)
} }
} // namespace } // namespace
void RpcMethodTest::testAuthorize()
{
// Select RPC method which takes non-string parameter to make sure
// that token: prefixed parameter is stripped before the call.
TellActiveRpcMethod m;
// no secret token set and no token: prefixed parameter is given
{
auto req = createReq(TellActiveRpcMethod::getMethodName());
auto res = m.execute(std::move(req), e_.get());
CPPUNIT_ASSERT_EQUAL(0, res.code);
}
// no secret token set and token: prefixed parameter is given
{
auto req = createReq(GetVersionRpcMethod::getMethodName());
req.params->append("token:foo");
auto res = m.execute(std::move(req), e_.get());
CPPUNIT_ASSERT_EQUAL(0, res.code);
}
e_->getOption()->put(PREF_RPC_SECRET, "foo");
// secret token set and no token: prefixed parameter is given
{
auto req = createReq(GetVersionRpcMethod::getMethodName());
auto res = m.execute(std::move(req), e_.get());
CPPUNIT_ASSERT_EQUAL(1, res.code);
}
// secret token set and token: prefixed parameter is given
{
auto req = createReq(GetVersionRpcMethod::getMethodName());
req.params->append("token:foo");
auto res = m.execute(std::move(req), e_.get());
CPPUNIT_ASSERT_EQUAL(0, res.code);
}
// secret token set and bad token: prefixed parameter is given
{
auto req = createReq(GetVersionRpcMethod::getMethodName());
req.params->append("token:foo2");
auto res = m.execute(std::move(req), e_.get());
CPPUNIT_ASSERT_EQUAL(1, res.code);
}
}
void RpcMethodTest::testAddUri() void RpcMethodTest::testAddUri()
{ {
AddUriRpcMethod m; AddUriRpcMethod m;