ABC Web Service / Command System
Version: 4.0
Date: 8/19/2006
Design: Tim Tucker, based on earlier work by Michel Hartmann and Choopan Rattanapoka
ABC can be found at: http://pingpong-abc.sourceforge.net/
The next iteration of ABC's web service is intended to be an easily scriptable command system.
Commands may be issued via the web service or via launching ABC with the name of a command file as an argument.
Command files use the extension ".abccommand" and are associated with ABC when it is installed.
Commands should allow scripting or remote control of most things that can be done via interface in ABC.
Currently, the few things that are not in the command spec include:
- Changing web service preferences (allowing the preferences for which commands are allowed to be changed via a command doesn't seem like a wise idea)
- Scraping to get seeds/peers for a torrent (may take much longer than most other commands to complete)
- Forcing a hash-check (may take much longer than most other commands to complete)
- Creating torrents (haven't come up with a good syntax for doing this)
- Getting/Changing priorities for individual files within a multi-file torrent (haven't come up with a good syntax for doing this)
The content of a command is a bencoded dictionary, containing the keys listed below. All character string values are UTF-8 encoded.
One notable change from previous iterations of ABC's webservice protocol is that now both a username and password are required for additional security.
Further, the password should never be sent in plain-text. Commands should contain a signature hash taken from the password, the current time, and the commands array.
Specifically, the signature should be in the form of HMAC-SHA-1(sha(password), HMAC-SHA-1(time, commands))
For more information on HMAC-SHA-1, see RFC 2104.
As an example of how to calculate the hash to send in the password field (assuming Python standard libraries):
- User enters the password pass
- User wants to send a command to stop all torrents, then close ABC.
Assuming that the dictionary for the commands is named mydict, the "commands" option would look like this:
mydict["commands"] = [{ "name": "close", "target": "abc" },
{ "name": "close", "target": "abc"} ]
- Remote application takes the SHA-1 hash of the password entered:
password_hash = sha.sha("pass").hexdigest()
(Value of password_hash = 9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684)
- Remote application bencodes the commands array:
bencoded_commands = bencode(mydict["commands"])
(Value of commands_bencoded = ld4:name5:close6:target3:abced4:name5:close6:target3:abcee)
- Remote application gets the current time in seconds past the epoch and converts it to a string:
now = str(long(time()))
(Assuming the command was executed at time 2001-09-08 21:46:40, the value of current_time would be: 1000000000)
- Remote application takes calculates a HMAC using the time string as the key and the bencoded commands array as the message:
step1 = hmac.new(current_time, msg = bencoded_commands, digestmod = sha).hexdigest()
(Value of step1: 1d86390350c3ce96cb2ce3489d9fdc49901ec726)
- Remote application takes calculates a HMAC using the password hash as the key and the previous HMAC value as the message:
final_hash = hmac.new(password_hash, msg = step1, digestmod = sha).hexdigest()
(Final value: 1f4797143caf99391e61b4d638a353e5c61d597e)
- Remote application uses the value of final_hash as the signature value to send.
Taken together, the following Python code will calculate the required signature:
import hmac
from sha import sha
from time import time
from BitTornado.bencode import bencode
def get_signature(password, commands):
"""
Get a signature hash to attach to a message
"""
# Get the current time in seconds past
# the epoch as a string
current_time = str(long(time()))
# Get the SHA-1 hash of the password
password_hash = sha(password).hexdigest()
# Get the bencoded value of the commands
bencoded_commands = bencode(commands)
# Step 1:
# Calculate the HMAC using:
# - the current time as the key
# - and the commands as the message
step1 = hmac.new(current_time,
msg = bencoded_commands,
digestmod = sha).hexdigest()
# Step 2:
# Calculate the HMAC using:
# - the password hash as the key
# - and the value from Step 1 as the message
final_hash = hmac.new(password_hash,
msg = step1,
digestmod = sha).hexdigest()
# Return the result
return final_hash
At this point you might as yourself: why go to so much trouble? Why not just send the password in plain-text, or send the hash of the password?
Here's some of the reasoning behind what could happen if someone views the command send in each situation (though traffic sniffing, etc.):
- Sending the password in plain-text:
- Password is compromised
- Attacker can sent any command they wish to the system
- If the password is used to login to other systems, those logins may now be compromised
- Sending the hash value of the password:
- Password is not compromised
- Attacker can sent any command they wish to the system
- Sending a combination hash value of the password hash plus the bencoded value of the commands:
- Password is not compromised
- Attacker can only replay the command that was intercepted
- Sending a combination hash value of the password hash, plus the current time, plus the bencoded value of the commands:
- Password is not compromised
- Attacker can only replay the command that was intercepted within a small window of time (* See note below)
Note: as a precaution against a remote attacker replaying an intercepted command within the possible time window that it is valid, ABC will reject a password hash if it is presented a second time within 1.5x the timespan for which it is valid.
As an example (using time in seconds past the epoch):
- A hash arrives at time 1000000900
- ABC checks to see if the hash is valid between 1000000000 through 1000001800
- ABC finds that the hash is valid
- ABC will now reject that hash until 1000003600
For a remote application sending valid passwords and commands, the only time a command should fail is if the exact same command is sent more than once within a 1 second period (which is probably not a good idea anyway).
Also note that for for authentication to work, the time on the remote must be within +/- 15 minutes of the time on the system running ABC.
As a further precaution, after 5 failed login attempts from an IP, that IP will be blocked from connecting to ABC for the next 15 minutes.
- credentials: A dictionary of login credentials with the following values:
(note: credentials is not needed for commands run locally from command files)
- username: The username set in ABC's webservice preferences
- signature: A HMAC-SHA1 hash taken from the password, the current time, and the commands array. (see details)
(note: Passwords should be utf-8 encoded before hashing)
- remote: (optional) A dictionary containing the following structure: If the remote key is present, ABC will relay the command to another instance of ABC running on a remote system.)
- credentials: A dictionary of login credentials for the remote system with the following values:
- username: The username set in ABC's webservice preferences on the remote system.
- signature: A HMAC-SHA1 hash taken from the password, the current time, and the commands array. (see details)
(note: Passwords should be utf-8 encoded before hashing)
- host: The IP address set in ABC's webservice preferences on the remote system.
- port: The port set in ABC's webservice preferences on the remote system.
- forcelaunch: Takes values of "True" or "False". (Defaults to "False") Whether or not to start ABC in order to perform the command. May be useful for things like adding a "reduce upload rate" or "stop all torrents" command to Windows' Scheduled Tasks.
(note: forcelaunch is only relevant for commands run locally from command files)
- commands: A list of dictionaries, one for each command. Each dictionary in this list contains the following keys:
- name: The name of the command
- options: A dictionary, the contents of which will vary for each commands. (may be optional for some commands)
The return response will be in the following form:
- feedback: A dictionaries containing the following keys:
- success: Whether or not the connection was successful
- message: (optional) Any errors or other feedback messages.
- commands: A list of dictionaries, one for each command executed. Each dictionary in this list contains the following keys:
- name: The name of the command
- result: A dictionary, the contents of which will vary for each commands.
The following are valid commands and their results:
Close
Tell ABC to close either itself or the web service.
- name: close
- options: A dictionary containing the following structure:
- target: values can be either "webservice" or "abc"
"webservice" - Shuts down the web service.
"abc" - Shuts down ABC.
Result:
No result returned (since the web service will no longer be active to return the result)
Query
Get current status of torrents.
- name: query
- options: A dictionary containing the following structure:
- target: values can be either "all", "active", "inactive", "paused", "seeding", "downloading", or "torrents"
- torrents: if target is set to "torrents", a list of infohash values
- columns: A list of column ids for columns to return.
(note: See english.lang for a definition of field numbers.)
(note: if this key is not specified, all columns will be returned)
Result:
- name: query
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
- headers: A dictionary containing column ids as keys and their associated names as values
Example: "{ 1: "Title", 2: "Filesize" }"
- torrents: A dictionary containing entries with the following structure:
- (key): The infohash for the torrent as the key.
- (value): A dictionary containing column ids as keys and their associated values for the torrent.
Example (assuming 1 torrent queried): "{"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3": { 1: "My Torrent", 2: "200 MB" }}"
Version
Returns the version information for ABC
Result:
- name: version
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
- version: The version for the release.
Example: "3.2"
- build: The build identifier for the release.
Example: "Build 188"
- build_date: The build date for the release (will be in the local date format).
Example: "8/11/2006"
Delete
Delete either completed torrents or a list of torrents (by infohash)
- name: delete
- options: A dictionary containing the following structure:
- target: values can be "completed", or "torrents"
"completed" - Removes torrents from the list only if they are complete (note if target is "completed", torrents and removefiles will be ignored)
- torrents: if target is set to "torrents", a list of infohash values
(note: if target is not set to "torrents", this key is ignored)
- removefiles: (optional) value can be "True"
"True" - When removing torrents from the list, also removes the files for those torrents.
(note: if removefiles is not present or set to anything other than "True", torrents will be removed from the list only.)
(note: if target is not set to "torrents", this key is ignored)
Result:
- name: delete
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Add
Add one or more torrents into torrent list. You must either have a default download folder specified or specify a destination for each torrent.
- name: add
- options: A dictionary containing the following structure:
- torrents: A list of dictionaries with the following structure:
- url: The url where the torrent can be found
or
- file: bencoded .torrent file
(note, each dictionary should contain either a url or a file, but not both)
- destination: (optional) Target destination for where to save the torrent.
Result:
- name: add
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
- torrents: A list of dictionaries, one for each torrent in the command, with the following structure:
- source: "url", "file", or "unknown" depending on the options specified for adding the torrent
- success: "True" or "False" depending on whether or not the torrent was added
- message: (optional) Any additional feedback for adding the torrent.
Resume
Resume either all stopped torrents, or indidivual torrents
- name: resume
- options: A dictionary containing the following structure:
- target: values can be either "all", "active", "inactive", "paused", "seeding", "downloading", "stopped", or "torrents"
- "stopped" - resumes all stopped torrents
- torrents: if target is set to "torrents", a list of infohash values
Result:
- name: resume
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Pause
Pause some or all of the torrents in the list.
- name: pause
- options: A dictionary containing the following structure:
- target: values can be either "all", "active", "inactive", "seeding", "downloading", or "torrents"
- torrents: if target is set to "torrents", a list of infohash values
Result:
- name: pause
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Unpause
Unpause some or all of the torrents in the list.
- name: unpause
- options: A dictionary containing the following structure:
- target: values can be either "all", "active", "inactive", "paused", "seeding", "downloading", or "torrents"
- torrents: if target is set to "torrents", a list of infohash values
Result:
- name: unpause
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Stop
Stop all or some of the torrents in the list.
- name: stop
- options: A dictionary containing the following structure:
- target: values can be either "all", "active", "inactive", "paused", "seeding", "downloading", or "torrents"
- torrents: if target is set to "torrents", a list of infohash values
Result:
- name: stop
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Queue
Queue all or some torrents in the list.
- name: queue
- options: A dictionary containing the following structure:
- target: values can be either "all", "active", "inactive", "paused", "seeding", "downloading", or "torrents"
- torrents: if target is set to "torrents", a list of infohash values
Result:
- name: queue
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Super-seed
Enable super-seed for all or some seeding torrents in the list.
- name: superseed
- options: A dictionary containing the following structure:
- target: values can be either "all", "active", "seeding", or "torrents"
- torrents: if target is set to "torrents", a list of infohash values
Result:
- name: superseed
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Move
Move torrents in the list.
- name: move
- options: A dictionary containing the following structure:
- direction: Takes values of "up", "down", "top", or "bottom"
Specify a direction of how to move the torrents
- target: values can be either "all", "active", "inactive", "paused", "seeding", "downloading", or "torrents"
- torrents: if target is set to "torrents", a list of infohash values
Result:
- name: move
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Priority
Change the priority of torrents.
- name: priority
- options: A dictionary containing the following structure:
- torrents: A dictionary with the entries in the following structure:
- (key) The infohash of the torrent.
- (value) The priority to set the torrent to. Must be an integer between 0 and 4.
Result:
- name: priority
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Get String
Get the values for strings in the current language file
- name: getstring
- options: A dictionary containing the following structure:
- strings: A list of language strings
Result:
- name: getstring
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
- strings: A dictionaries of entries containing the following structure:
- (key) The name of the string
- (value) The string value
Set String
Set the value for a string in the user language file
- name: setstring
- options: A dictionary containing the following structure:
- params: A dictionary containing entries with the following structure:
- (key) The name of the string
- (value) The value to change the string to
Result:
- name: setparam
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Get Param
Get the values for parameters in the config file
- name: getparam
- options: A dictionary containing the following structure:
- params: A list of parameters
Result:
- name: getparam
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
- params: A dictionary containing entries in the following structure:
- (key) The name of the parameter
- (value) The parameter value
Set Param
Set the values for parameters in the config file (note: changing some values may require torrents to be restarted to take effect)
- name: setparam
- options: A dictionary containing the following structure:
- params: A dictionary containing entries with the following structure:
- (key) The name of the parameter to change.
- (value) The value to set the parameter to.
Result:
- name: setparam
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
Get Torrent Param
Get the values for parameters for one or more torrents
- name: gettorrentparam
- options: A dictionary containing the following structure:
- target: values can be either "all", "active", "inactive", "paused", "seeding", "downloading", or "torrents"
- torrents: if target is set to "torrents", a list of infohash values
- params: A list of torrent parameters to return.
(note: valid parameters include "priority")
Result:
- name: gettorrentparam
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.
- torrents: A dictionary containing entries with the following structure:
- (key): The infohash for the torrent as the key.
- (value): A dictionary containing the parameters requested as keys and their associated values for the torrent.
Example (assuming 1 torrent queried for its priority): "{"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3": { "priority": "3" }}"
Set Torrent Param
Set the values for parameters for one or more torrents
- name: settorrentparam
- options: A dictionary containing the following structure:
- target: values can be either "all", "active", "inactive", "paused", "seeding", "downloading", or "torrents"
- torrents: if target is set to "torrents", a list of infohash values
- params: A dictionary containing entries with the following structure:
- (key) The name of the parameter to change.
- (value) The value to set the parameter to.
Result:
- name: settorrentparam
- result: A dictionary containing the following structure:
- success: "True" or "False" depending on whether or not the command succeeded
- message: (optional) Any additional feedback for the command.